@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,127 @@
1
+ /**
2
+ * Pending-reply bookkeeping for the ReachyMini SDK.
3
+ *
4
+ * The data channel multiplexes four request/response disciplines, each
5
+ * with its own correlation rule:
6
+ *
7
+ * 1. Reply slots — legacy `{type: ...}` commands whose replies carry
8
+ * no request id, correlated by message TYPE. At most one call per
9
+ * slot is in flight; a newer call supersedes the older waiter.
10
+ * 2. JSON-RPC — app-control calls correlated by an `id` we generate.
11
+ * 3. Motion completions — `wake_up` / `goto_sleep` acks, correlated
12
+ * by command name, FIFO per command.
13
+ * 4. Broadcast waiters — daemon broadcasts (move / audio playback
14
+ * lifecycle) matched by caller-supplied predicate.
15
+ *
16
+ * This class owns the WAITERS only: sending stays with `ReachyMini`
17
+ * (it owns the data channel), which passes send actions in as
18
+ * callbacks. Every session-teardown path settles the whole ledger at
19
+ * once via `settleAll`, so no caller is ever left hanging on a dead
20
+ * channel — and no future mechanism can be forgotten on a teardown
21
+ * path, because there is only one.
22
+ */
23
+ import type { FaceTarget, ImuData } from './types.js';
24
+ /**
25
+ * Fail-open ceiling for a single reply-slot request/response.
26
+ * Every slot command has a strict "one reply per request" contract, so
27
+ * a missing reply means the daemon either never got it or - crucially -
28
+ * predates that command entirely: an older daemon silently drops an
29
+ * unknown `type` and sends nothing back, which would otherwise leave the
30
+ * caller's promise pending forever (e.g. a newer SDK calling a command a
31
+ * 1.8.x daemon doesn't implement). Resolving `null` on timeout maps
32
+ * cleanly onto the "unsupported / failed" value every slot caller already
33
+ * handles. 4 s is comfortably above a WebRTC data-channel round trip on a
34
+ * congested phone link while still failing fast enough that a gated UI
35
+ * doesn't feel hung.
36
+ */
37
+ export declare const SLOT_ROUNDTRIP_TIMEOUT_MS = 4000;
38
+ /**
39
+ * Value type carried by each single-slot reply waiter. These daemon
40
+ * replies are correlated by message TYPE — the legacy command set
41
+ * carries no request ids — so at most one call per slot is in flight.
42
+ */
43
+ export interface ReplySlotValues {
44
+ version: string | null;
45
+ hardware_id: string | null;
46
+ volume: number | null;
47
+ mic_volume: number | null;
48
+ first_wake_up: boolean | null;
49
+ tracked_face: FaceTarget | null;
50
+ robot_name: string | null;
51
+ delete_hf_token: boolean | null;
52
+ apply_audio_config: boolean | null;
53
+ read_audio_parameter: number[] | null;
54
+ imu: ImuData | null;
55
+ }
56
+ export type ReplySlotKey = keyof ReplySlotValues;
57
+ export type MotionCommand = 'wake_up' | 'goto_sleep';
58
+ /**
59
+ * Sentinel for a broadcast waiter that ran out its own timeout (as
60
+ * opposed to being rejected by a session teardown via `settleAll`).
61
+ * Callers that fail-open on timeout — `request()` — detect it by type
62
+ * instead of coupling to the message string.
63
+ */
64
+ export declare class BroadcastTimeoutError extends Error {
65
+ }
66
+ export declare class PendingReplies {
67
+ private readonly _replySlots;
68
+ private _rpcCounter;
69
+ private readonly _pendingRpc;
70
+ private readonly _pendingMotion;
71
+ private _broadcastWaiters;
72
+ /**
73
+ * Register a slot waiter, run `send`, and await the matching daemon
74
+ * response. If a previous request on the same slot is still pending
75
+ * when a new one comes in, the older promise is resolved to `null`
76
+ * so its caller doesn't hang forever.
77
+ */
78
+ slotRoundtrip<K extends ReplySlotKey>(slot: K, send: () => void): Promise<ReplySlotValues[K] | null>;
79
+ /**
80
+ * Deliver a daemon reply to the pending waiter for `slot`, if any.
81
+ * Returns `true` when a waiter consumed the value — some message-handler
82
+ * branches use that to decide whether the message was theirs to swallow.
83
+ */
84
+ settleReplySlot<K extends ReplySlotKey>(slot: K, value: ReplySlotValues[K]): boolean;
85
+ /**
86
+ * One JSON-RPC round-trip: mint the correlation id, hand it to
87
+ * `send` (which reports `false` when the channel isn't open — the
88
+ * promise then rejects immediately), and await the matching
89
+ * response; rejects on timeout. Same shape as `slotRoundtrip`.
90
+ */
91
+ rpcRoundtrip(method: string, timeoutMs: number, send: (id: string) => boolean): Promise<unknown>;
92
+ /**
93
+ * Route a JSON-RPC RESPONSE (id + result/error) to its waiter.
94
+ * Returns `true` when the message was response-shaped — even if the
95
+ * waiter already timed out — so the caller can stop processing it;
96
+ * `false` for notifications and anything else.
97
+ */
98
+ settleRpcResponse(data: Record<string, unknown>): boolean;
99
+ /** FIFO waiter for one `wake_up` / `goto_sleep` ack; rejects on timeout. */
100
+ awaitMotion(command: MotionCommand, timeoutMs: number): Promise<void>;
101
+ /**
102
+ * Deliver a `wake_up` / `goto_sleep` reply to the oldest waiter.
103
+ * Returns `true` when a waiter consumed it; `false` when nothing was
104
+ * pending or the reply is neither a completion nor an error (the
105
+ * message then falls through to the caller's other branches).
106
+ */
107
+ settleMotion(command: MotionCommand, data: Record<string, unknown>): boolean;
108
+ /** Await the first broadcast matching `predicate`; rejects on timeout. */
109
+ awaitBroadcast(predicate: (m: Record<string, unknown>) => boolean, { timeoutMs, debugLabel }?: {
110
+ timeoutMs?: number;
111
+ debugLabel?: string;
112
+ }): Promise<Record<string, unknown>>;
113
+ /**
114
+ * Offer a broadcast to the waiters; the first (most recent) matching
115
+ * waiter consumes it. Returns `true` when one did.
116
+ */
117
+ matchBroadcast(data: Record<string, unknown>): boolean;
118
+ /**
119
+ * Settle the whole ledger against a dead channel: reply slots
120
+ * resolve `null` (the "no answer" value all slot callers handle;
121
+ * public wrappers coerce where needed, e.g. `applyAudioConfig` maps
122
+ * it to `false`), everything else rejects with `err`. Called from
123
+ * every session-teardown path.
124
+ */
125
+ settleAll(err: Error): void;
126
+ }
127
+ //# sourceMappingURL=pending-replies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-replies.d.ts","sourceRoot":"","sources":["../../lib/pending-replies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,eAAe,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,kBAAkB,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,oBAAoB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACtC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;CACvB;AACD,MAAM,MAAM,YAAY,GAAG,MAAM,eAAe,CAAC;AAEjD,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,YAAY,CAAC;AAErD;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;CAAG;AAqBnD,qBAAa,cAAc;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAiD;IAC7E,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAiC;IAC7D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAG7B;IACF,OAAO,CAAC,iBAAiB,CAAyB;IAIlD;;;;;OAKG;IACH,aAAa,CAAC,CAAC,SAAS,YAAY,EAChC,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,MAAM,IAAI,GACjB,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IA4BrC;;;;OAIG;IACH,eAAe,CAAC,CAAC,SAAS,YAAY,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,OAAO;IASpF;;;;;OAKG;IACH,YAAY,CACR,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GAC9B,OAAO,CAAC,OAAO,CAAC;IAcnB;;;;;OAKG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAqBzD,4EAA4E;IAC5E,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBrE;;;;;OAKG;IACH,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAmB5E,0EAA0E;IAC1E,cAAc,CACV,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,EAClD,EAAE,SAAgB,EAAE,UAAe,EAAE,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAO,GACxF,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAgBnC;;;OAGG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAetD;;;;;;OAMG;IACH,SAAS,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI;CA2B9B"}
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Pending-reply bookkeeping for the ReachyMini SDK.
3
+ *
4
+ * The data channel multiplexes four request/response disciplines, each
5
+ * with its own correlation rule:
6
+ *
7
+ * 1. Reply slots — legacy `{type: ...}` commands whose replies carry
8
+ * no request id, correlated by message TYPE. At most one call per
9
+ * slot is in flight; a newer call supersedes the older waiter.
10
+ * 2. JSON-RPC — app-control calls correlated by an `id` we generate.
11
+ * 3. Motion completions — `wake_up` / `goto_sleep` acks, correlated
12
+ * by command name, FIFO per command.
13
+ * 4. Broadcast waiters — daemon broadcasts (move / audio playback
14
+ * lifecycle) matched by caller-supplied predicate.
15
+ *
16
+ * This class owns the WAITERS only: sending stays with `ReachyMini`
17
+ * (it owns the data channel), which passes send actions in as
18
+ * callbacks. Every session-teardown path settles the whole ledger at
19
+ * once via `settleAll`, so no caller is ever left hanging on a dead
20
+ * channel — and no future mechanism can be forgotten on a teardown
21
+ * path, because there is only one.
22
+ */
23
+ /**
24
+ * Fail-open ceiling for a single reply-slot request/response.
25
+ * Every slot command has a strict "one reply per request" contract, so
26
+ * a missing reply means the daemon either never got it or - crucially -
27
+ * predates that command entirely: an older daemon silently drops an
28
+ * unknown `type` and sends nothing back, which would otherwise leave the
29
+ * caller's promise pending forever (e.g. a newer SDK calling a command a
30
+ * 1.8.x daemon doesn't implement). Resolving `null` on timeout maps
31
+ * cleanly onto the "unsupported / failed" value every slot caller already
32
+ * handles. 4 s is comfortably above a WebRTC data-channel round trip on a
33
+ * congested phone link while still failing fast enough that a gated UI
34
+ * doesn't feel hung.
35
+ */
36
+ export const SLOT_ROUNDTRIP_TIMEOUT_MS = 4000;
37
+ /**
38
+ * Sentinel for a broadcast waiter that ran out its own timeout (as
39
+ * opposed to being rejected by a session teardown via `settleAll`).
40
+ * Callers that fail-open on timeout — `request()` — detect it by type
41
+ * instead of coupling to the message string.
42
+ */
43
+ export class BroadcastTimeoutError extends Error {
44
+ }
45
+ export class PendingReplies {
46
+ _replySlots = new Map();
47
+ _rpcCounter = 0;
48
+ _pendingRpc = new Map();
49
+ _pendingMotion = {
50
+ wake_up: [],
51
+ goto_sleep: [],
52
+ };
53
+ _broadcastWaiters = [];
54
+ /* ─── Reply slots ────────────────────────────────────────────────── */
55
+ /**
56
+ * Register a slot waiter, run `send`, and await the matching daemon
57
+ * response. If a previous request on the same slot is still pending
58
+ * when a new one comes in, the older promise is resolved to `null`
59
+ * so its caller doesn't hang forever.
60
+ */
61
+ slotRoundtrip(slot, send) {
62
+ return new Promise((resolve) => {
63
+ const prev = this._replySlots.get(slot);
64
+ if (prev)
65
+ prev(null);
66
+ let timer;
67
+ // Single settle path shared by the daemon response, supersession
68
+ // by a newer call, and the fail-open timeout below. It clears the
69
+ // timer once and detaches itself from the slot only if it's still
70
+ // the current occupant, so a stale settle (timed-out or superseded)
71
+ // can't clear a newer call's slot registration.
72
+ // Note: slots are keyed by command type, not request id, so this
73
+ // does not prevent a genuinely late daemon reply from being routed
74
+ // to a newer same-command caller — that cross-talk is inherent to
75
+ // the single-flight slot design.
76
+ const settle = (v) => {
77
+ if (timer !== undefined) {
78
+ clearTimeout(timer);
79
+ timer = undefined;
80
+ }
81
+ if (this._replySlots.get(slot) === settle)
82
+ this._replySlots.delete(slot);
83
+ resolve(v);
84
+ };
85
+ this._replySlots.set(slot, settle);
86
+ timer = setTimeout(() => settle(null), SLOT_ROUNDTRIP_TIMEOUT_MS);
87
+ send();
88
+ });
89
+ }
90
+ /**
91
+ * Deliver a daemon reply to the pending waiter for `slot`, if any.
92
+ * Returns `true` when a waiter consumed the value — some message-handler
93
+ * branches use that to decide whether the message was theirs to swallow.
94
+ */
95
+ settleReplySlot(slot, value) {
96
+ const waiter = this._replySlots.get(slot);
97
+ if (!waiter)
98
+ return false;
99
+ waiter(value);
100
+ return true;
101
+ }
102
+ /* ─── JSON-RPC ───────────────────────────────────────────────────── */
103
+ /**
104
+ * One JSON-RPC round-trip: mint the correlation id, hand it to
105
+ * `send` (which reports `false` when the channel isn't open — the
106
+ * promise then rejects immediately), and await the matching
107
+ * response; rejects on timeout. Same shape as `slotRoundtrip`.
108
+ */
109
+ rpcRoundtrip(method, timeoutMs, send) {
110
+ const id = `rpc-${++this._rpcCounter}`;
111
+ if (!send(id)) {
112
+ return Promise.reject(new Error(`rpcCall(${method}): data channel not open`));
113
+ }
114
+ return new Promise((resolve, reject) => {
115
+ const timer = setTimeout(() => {
116
+ this._pendingRpc.delete(id);
117
+ reject(new Error(`rpcCall(${method}) timed out after ${timeoutMs}ms`));
118
+ }, timeoutMs);
119
+ this._pendingRpc.set(id, { resolve, reject, timer });
120
+ });
121
+ }
122
+ /**
123
+ * Route a JSON-RPC RESPONSE (id + result/error) to its waiter.
124
+ * Returns `true` when the message was response-shaped — even if the
125
+ * waiter already timed out — so the caller can stop processing it;
126
+ * `false` for notifications and anything else.
127
+ */
128
+ settleRpcResponse(data) {
129
+ if (!('id' in data) || data.id == null || !('result' in data || 'error' in data)) {
130
+ return false;
131
+ }
132
+ const pending = this._pendingRpc.get(data.id);
133
+ if (!pending)
134
+ return true; // late reply to a timed-out call
135
+ this._pendingRpc.delete(data.id);
136
+ clearTimeout(pending.timer);
137
+ if ('error' in data && data.error) {
138
+ const err = data.error;
139
+ const e = new Error(err.message ?? 'rpc error');
140
+ e.reason = err.data?.reason;
141
+ pending.reject(e);
142
+ }
143
+ else {
144
+ pending.resolve(data.result);
145
+ }
146
+ return true;
147
+ }
148
+ /* ─── Motion completions (wake_up / goto_sleep) ──────────────────── */
149
+ /** FIFO waiter for one `wake_up` / `goto_sleep` ack; rejects on timeout. */
150
+ awaitMotion(command, timeoutMs) {
151
+ return new Promise((resolve, reject) => {
152
+ const entry = {
153
+ resolve,
154
+ reject,
155
+ timer: setTimeout(() => {
156
+ const queue = this._pendingMotion[command];
157
+ const idx = queue.indexOf(entry);
158
+ if (idx !== -1)
159
+ queue.splice(idx, 1);
160
+ reject(new Error(`${command} timed out after ${timeoutMs}ms`));
161
+ }, timeoutMs),
162
+ };
163
+ this._pendingMotion[command].push(entry);
164
+ });
165
+ }
166
+ /**
167
+ * Deliver a `wake_up` / `goto_sleep` reply to the oldest waiter.
168
+ * Returns `true` when a waiter consumed it; `false` when nothing was
169
+ * pending or the reply is neither a completion nor an error (the
170
+ * message then falls through to the caller's other branches).
171
+ */
172
+ settleMotion(command, data) {
173
+ const queue = this._pendingMotion[command];
174
+ if (data.completed === true && queue.length > 0) {
175
+ const entry = queue.shift();
176
+ clearTimeout(entry.timer);
177
+ entry.resolve();
178
+ return true;
179
+ }
180
+ if (data.error && queue.length > 0) {
181
+ const entry = queue.shift();
182
+ clearTimeout(entry.timer);
183
+ entry.reject(new Error(`${command}: ${data.error}`));
184
+ return true;
185
+ }
186
+ return false;
187
+ }
188
+ /* ─── Broadcast waiters ──────────────────────────────────────────── */
189
+ /** Await the first broadcast matching `predicate`; rejects on timeout. */
190
+ awaitBroadcast(predicate, { timeoutMs = 5000, debugLabel = '' } = {}) {
191
+ return new Promise((resolve, reject) => {
192
+ const slot = {
193
+ predicate,
194
+ resolve,
195
+ reject,
196
+ timer: setTimeout(() => {
197
+ const i = this._broadcastWaiters.indexOf(slot);
198
+ if (i !== -1)
199
+ this._broadcastWaiters.splice(i, 1);
200
+ reject(new BroadcastTimeoutError(`broadcast timeout (${timeoutMs} ms): ${debugLabel}`));
201
+ }, timeoutMs),
202
+ };
203
+ this._broadcastWaiters.push(slot);
204
+ });
205
+ }
206
+ /**
207
+ * Offer a broadcast to the waiters; the first (most recent) matching
208
+ * waiter consumes it. Returns `true` when one did.
209
+ */
210
+ matchBroadcast(data) {
211
+ for (let i = this._broadcastWaiters.length - 1; i >= 0; i--) {
212
+ const slot = this._broadcastWaiters[i];
213
+ if (slot.predicate(data)) {
214
+ this._broadcastWaiters.splice(i, 1);
215
+ clearTimeout(slot.timer);
216
+ slot.resolve(data);
217
+ return true;
218
+ }
219
+ }
220
+ return false;
221
+ }
222
+ /* ─── Teardown ───────────────────────────────────────────────────── */
223
+ /**
224
+ * Settle the whole ledger against a dead channel: reply slots
225
+ * resolve `null` (the "no answer" value all slot callers handle;
226
+ * public wrappers coerce where needed, e.g. `applyAudioConfig` maps
227
+ * it to `false`), everything else rejects with `err`. Called from
228
+ * every session-teardown path.
229
+ */
230
+ settleAll(err) {
231
+ for (const waiter of [...this._replySlots.values()]) {
232
+ waiter(null);
233
+ }
234
+ this._replySlots.clear();
235
+ for (const pending of this._pendingRpc.values()) {
236
+ clearTimeout(pending.timer);
237
+ pending.reject(err);
238
+ }
239
+ this._pendingRpc.clear();
240
+ for (const command of Object.keys(this._pendingMotion)) {
241
+ const queue = this._pendingMotion[command];
242
+ while (queue.length) {
243
+ const entry = queue.shift();
244
+ clearTimeout(entry.timer);
245
+ entry.reject(err);
246
+ }
247
+ }
248
+ for (const slot of this._broadcastWaiters) {
249
+ clearTimeout(slot.timer);
250
+ slot.reject(err);
251
+ }
252
+ this._broadcastWaiters = [];
253
+ }
254
+ }
255
+ //# sourceMappingURL=pending-replies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-replies.js","sourceRoot":"","sources":["../../lib/pending-replies.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=pending-replies.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-replies.test.d.ts","sourceRoot":"","sources":["../../lib/pending-replies.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,217 @@
1
+ /**
2
+ * PendingReplies ledger tests.
3
+ *
4
+ * The properties that must hold, per discipline:
5
+ * - reply slots: single-flight per slot — a newer call supersedes the
6
+ * older waiter with `null`; a missing daemon reply fails open to
7
+ * `null` after the roundtrip timeout; a stale settle can't clobber
8
+ * a newer call's registration;
9
+ * - JSON-RPC: responses are routed by id; error responses carry the
10
+ * message and machine-readable `reason`; a late reply to a
11
+ * timed-out call is swallowed (still counts as response-shaped);
12
+ * - motion completions: FIFO per command; replies that are neither a
13
+ * completion nor an error fall through to the caller;
14
+ * - broadcast waiters: predicate match consumes the most recent
15
+ * waiter; timeout carries the debug label;
16
+ * - settleAll(): one call settles all four ledgers — slots resolve
17
+ * `null`, everything else rejects — so no caller can be left
18
+ * hanging on a dead channel.
19
+ */
20
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
21
+ import { PendingReplies } from './pending-replies.js';
22
+ let pending;
23
+ beforeEach(() => {
24
+ vi.useFakeTimers();
25
+ pending = new PendingReplies();
26
+ });
27
+ afterEach(() => {
28
+ vi.useRealTimers();
29
+ });
30
+ describe('reply slots', () => {
31
+ it('resolves the waiter when the daemon reply lands', async () => {
32
+ const send = vi.fn();
33
+ const p = pending.slotRoundtrip('version', send);
34
+ expect(send).toHaveBeenCalledTimes(1);
35
+ expect(pending.settleReplySlot('version', '1.2.3')).toBe(true);
36
+ await expect(p).resolves.toBe('1.2.3');
37
+ });
38
+ it('reports an unclaimed reply so the handler can fall through', () => {
39
+ expect(pending.settleReplySlot('version', '1.2.3')).toBe(false);
40
+ });
41
+ it('supersedes a pending call on the same slot with null', async () => {
42
+ const first = pending.slotRoundtrip('volume', vi.fn());
43
+ const second = pending.slotRoundtrip('volume', vi.fn());
44
+ await expect(first).resolves.toBeNull();
45
+ pending.settleReplySlot('volume', 42);
46
+ await expect(second).resolves.toBe(42);
47
+ });
48
+ it('keeps unrelated slots independent', async () => {
49
+ const version = pending.slotRoundtrip('version', vi.fn());
50
+ const volume = pending.slotRoundtrip('volume', vi.fn());
51
+ pending.settleReplySlot('volume', 10);
52
+ pending.settleReplySlot('version', 'v');
53
+ await expect(volume).resolves.toBe(10);
54
+ await expect(version).resolves.toBe('v');
55
+ });
56
+ it('fails open to null when the daemon never answers (old daemon)', async () => {
57
+ const p = pending.slotRoundtrip('hardware_id', vi.fn());
58
+ await vi.advanceTimersByTimeAsync(4000);
59
+ await expect(p).resolves.toBeNull();
60
+ // The slot is free again: a late daemon reply finds no waiter.
61
+ expect(pending.settleReplySlot('hardware_id', 'late')).toBe(false);
62
+ });
63
+ it('a timed-out call does not clear a newer call on the same slot', async () => {
64
+ const first = pending.slotRoundtrip('robot_name', vi.fn());
65
+ // Just before the first call's timeout, a second call supersedes it…
66
+ await vi.advanceTimersByTimeAsync(3999);
67
+ const second = pending.slotRoundtrip('robot_name', vi.fn());
68
+ await expect(first).resolves.toBeNull();
69
+ // …and the second call's registration survives the first's timer.
70
+ await vi.advanceTimersByTimeAsync(1);
71
+ expect(pending.settleReplySlot('robot_name', 'Reachy')).toBe(true);
72
+ await expect(second).resolves.toBe('Reachy');
73
+ });
74
+ });
75
+ describe('JSON-RPC', () => {
76
+ /** Capture the id `rpcRoundtrip` mints for the send callback. */
77
+ function roundtrip(method, timeoutMs) {
78
+ let id = '';
79
+ const p = pending.rpcRoundtrip(method, timeoutMs, (mintedId) => {
80
+ id = mintedId;
81
+ return true;
82
+ });
83
+ return { id, p };
84
+ }
85
+ it('routes a result response to its waiter by id', async () => {
86
+ const { id, p } = roundtrip('apps.start', 1000);
87
+ expect(pending.settleRpcResponse({ id, result: { ok: true } })).toBe(true);
88
+ await expect(p).resolves.toEqual({ ok: true });
89
+ });
90
+ it('rejects immediately when the send reports a closed channel', async () => {
91
+ await expect(pending.rpcRoundtrip('apps.start', 1000, () => false)).rejects.toThrow('rpcCall(apps.start): data channel not open');
92
+ });
93
+ it('rejects with message and machine-readable reason on an error response', async () => {
94
+ const { id, p } = roundtrip('apps.start', 1000);
95
+ expect(pending.settleRpcResponse({
96
+ id,
97
+ error: { message: 'nope', data: { reason: 'app_not_found' } },
98
+ })).toBe(true);
99
+ await expect(p).rejects.toMatchObject({
100
+ message: 'nope',
101
+ reason: 'app_not_found',
102
+ });
103
+ });
104
+ it('swallows a late reply to a timed-out call (still response-shaped)', async () => {
105
+ const { id, p } = roundtrip('conversation.say', 1000);
106
+ void p.catch(() => { });
107
+ await vi.advanceTimersByTimeAsync(1000);
108
+ await expect(p).rejects.toThrow(/timed out after 1000ms/);
109
+ // The late reply must not fall through to the notification path.
110
+ expect(pending.settleRpcResponse({ id, result: 'late' })).toBe(true);
111
+ });
112
+ it('leaves notifications (no id) to the caller', () => {
113
+ expect(pending.settleRpcResponse({ method: 'conversation.turn', params: {} })).toBe(false);
114
+ // id present but neither result nor error: not a response either.
115
+ expect(pending.settleRpcResponse({ id: 'rpc-1', method: 'x' })).toBe(false);
116
+ });
117
+ it('mints unique correlation ids', () => {
118
+ const first = roundtrip('a', 1000);
119
+ const second = roundtrip('b', 1000);
120
+ expect(first.id).not.toBe(second.id);
121
+ void first.p.catch(() => { });
122
+ void second.p.catch(() => { });
123
+ });
124
+ });
125
+ describe('motion completions', () => {
126
+ it('resolves waiters in FIFO order per command', async () => {
127
+ const first = pending.awaitMotion('wake_up', 1000);
128
+ const second = pending.awaitMotion('wake_up', 1000);
129
+ const order = [];
130
+ void first.then(() => order.push('first'));
131
+ void second.then(() => order.push('second'));
132
+ expect(pending.settleMotion('wake_up', { completed: true })).toBe(true);
133
+ expect(pending.settleMotion('wake_up', { completed: true })).toBe(true);
134
+ await vi.runAllTimersAsync();
135
+ expect(order).toEqual(['first', 'second']);
136
+ });
137
+ it('rejects the oldest waiter on an error reply', async () => {
138
+ const p = pending.awaitMotion('goto_sleep', 1000);
139
+ expect(pending.settleMotion('goto_sleep', { error: 'motors off' })).toBe(true);
140
+ await expect(p).rejects.toThrow('goto_sleep: motors off');
141
+ });
142
+ it('falls through when the reply is neither completion nor error', () => {
143
+ void pending.awaitMotion('wake_up', 1000).catch(() => { });
144
+ // An intermediate ack without `completed` must not consume the waiter.
145
+ expect(pending.settleMotion('wake_up', { status: 'started' })).toBe(false);
146
+ });
147
+ it('falls through when nothing is pending', () => {
148
+ expect(pending.settleMotion('wake_up', { completed: true })).toBe(false);
149
+ });
150
+ it('rejects on timeout and forgets the waiter', async () => {
151
+ const p = pending.awaitMotion('wake_up', 1000);
152
+ void p.catch(() => { });
153
+ await vi.advanceTimersByTimeAsync(1000);
154
+ await expect(p).rejects.toThrow(/wake_up timed out/);
155
+ expect(pending.settleMotion('wake_up', { completed: true })).toBe(false);
156
+ });
157
+ });
158
+ describe('broadcast waiters', () => {
159
+ it('the first matching broadcast resolves the waiter', async () => {
160
+ const p = pending.awaitBroadcast((m) => m.type === 'move_done');
161
+ expect(pending.matchBroadcast({ type: 'other' })).toBe(false);
162
+ expect(pending.matchBroadcast({ type: 'move_done', id: 7 })).toBe(true);
163
+ await expect(p).resolves.toEqual({ type: 'move_done', id: 7 });
164
+ });
165
+ it('the most recent matching waiter wins', async () => {
166
+ const older = pending.awaitBroadcast((m) => m.type === 'x', { debugLabel: 'older' });
167
+ const newer = pending.awaitBroadcast((m) => m.type === 'x', { debugLabel: 'newer' });
168
+ expect(pending.matchBroadcast({ type: 'x', n: 1 })).toBe(true);
169
+ await expect(newer).resolves.toEqual({ type: 'x', n: 1 });
170
+ expect(pending.matchBroadcast({ type: 'x', n: 2 })).toBe(true);
171
+ await expect(older).resolves.toEqual({ type: 'x', n: 2 });
172
+ });
173
+ it('rejects on timeout with the debug label', async () => {
174
+ const p = pending.awaitBroadcast(() => false, { timeoutMs: 2000, debugLabel: 'move ack' });
175
+ void p.catch(() => { });
176
+ await vi.advanceTimersByTimeAsync(2000);
177
+ await expect(p).rejects.toThrow('broadcast timeout (2000 ms): move ack');
178
+ });
179
+ });
180
+ describe('settleAll (session teardown)', () => {
181
+ it('settles all four ledgers in one call', async () => {
182
+ const slot = pending.slotRoundtrip('version', vi.fn());
183
+ const rpc = pending.rpcRoundtrip('apps.start', 60_000, () => true);
184
+ const motion = pending.awaitMotion('wake_up', 60_000);
185
+ const broadcast = pending.awaitBroadcast(() => true, { timeoutMs: 60_000 });
186
+ // Pre-attach handlers so the synchronous rejections below don't
187
+ // trip Node's late-handling warning before the assertions run.
188
+ for (const p of [rpc, motion, broadcast])
189
+ void p.catch(() => { });
190
+ pending.settleAll(new Error('Session stopped'));
191
+ // Slots fail open (all slot callers handle null)…
192
+ await expect(slot).resolves.toBeNull();
193
+ // …everything else rejects with the teardown error.
194
+ await expect(rpc).rejects.toThrow('Session stopped');
195
+ await expect(motion).rejects.toThrow('Session stopped');
196
+ await expect(broadcast).rejects.toThrow('Session stopped');
197
+ });
198
+ it('leaves the ledger empty afterwards', () => {
199
+ void pending.slotRoundtrip('version', vi.fn());
200
+ void pending.awaitMotion('wake_up', 1000).catch(() => { });
201
+ void pending.awaitBroadcast(() => true).catch(() => { });
202
+ pending.settleAll(new Error('gone'));
203
+ expect(pending.settleReplySlot('version', 'v')).toBe(false);
204
+ expect(pending.settleMotion('wake_up', { completed: true })).toBe(false);
205
+ expect(pending.matchBroadcast({ type: 'any' })).toBe(false);
206
+ });
207
+ it('cancels the pending timers (no late rejection fires)', async () => {
208
+ // A rejection firing after settleAll would be an unhandled one —
209
+ // runAllTimersAsync below would surface it.
210
+ const motion = pending.awaitMotion('wake_up', 1000);
211
+ void motion.catch(() => { });
212
+ pending.settleAll(new Error('gone'));
213
+ await expect(motion).rejects.toThrow('gone');
214
+ await vi.runAllTimersAsync();
215
+ });
216
+ });
217
+ //# sourceMappingURL=pending-replies.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-replies.test.js","sourceRoot":"","sources":["../../lib/pending-replies.test.ts"],"names":[],"mappings":"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"}