ugly-app 0.1.992 → 0.1.994

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,2 +1,2 @@
1
- export declare const CLI_VERSION = "0.1.992";
1
+ export declare const CLI_VERSION = "0.1.994";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by prebuild — do not edit manually
2
- export const CLI_VERSION = "0.1.992";
2
+ export const CLI_VERSION = "0.1.994";
3
3
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useTTS.d.ts","sourceRoot":"","sources":["../../../src/client/audio/useTTS.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,aAAa,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,UAAU;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IACvC;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAUD,wBAAgB,MAAM,CAAC,MAAM,EAAE,aAAa;iBAoE3B,MAAM,YAAY,UAAU;;;;;;;;;;;;;;;;;EAmN5C"}
1
+ {"version":3,"file":"useTTS.d.ts","sourceRoot":"","sources":["../../../src/client/audio/useTTS.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,aAAa,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,UAAU;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IACvC;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAmCD,wBAAgB,MAAM,CAAC,MAAM,EAAE,aAAa;iBAoE3B,MAAM,YAAY,UAAU;;;;;;;;;;;;;;;;;EAiQ5C"}
@@ -8,6 +8,26 @@ let streamCounter = 0;
8
8
  // silently or the server hung. Fail loudly instead of leaving `playing` true
9
9
  // forever with the rAF loop spinning and the socket held.
10
10
  const WATCHDOG_MS = 30_000;
11
+ /**
12
+ * Report a failed or silent read-aloud on the framework's UX audit channel.
13
+ *
14
+ * ⚠️ THIS EXISTS BECAUSE "TTS does not play" WAS UNDIAGNOSABLE. The hook set its
15
+ * local `error` state and stopped there, so a failure was visible only to
16
+ * whatever the app chose to render — and apps mostly render nothing, or abort
17
+ * quietly. Production reports therefore said "TTS does not play" with no page,
18
+ * no route and no reason, and answering them meant asking the user which screen
19
+ * they were on. `[ugly.ux] …` is the same channel the image/keyboard/safe-area
20
+ * audits use: the Logger ships it to the project's error log with the page
21
+ * attached, so every app gets this for free and none has to build its own.
22
+ */
23
+ function reportAudioFailure(reason, detail, extra) {
24
+ const page = typeof window === 'undefined' ? '' : window.location.pathname;
25
+ console.error(`[ugly.ux] audio: read-aloud ${detail}`, {
26
+ reason,
27
+ page,
28
+ ...extra,
29
+ });
30
+ }
11
31
  export function useTTS(socket) {
12
32
  const [playing, setPlaying] = useState(false);
13
33
  const [currentWord, setCurrentWord] = useState('');
@@ -116,6 +136,17 @@ export function useTTS(socket) {
116
136
  // Flush any remaining caption indices so the text fully reveals.
117
137
  while (wi < words.length)
118
138
  options?.onSubtitleIndex?.(words[wi++].charEnd);
139
+ // Audio was delivered and the stream is over, but the worklet's own
140
+ // sample clock never moved: whatever the cause (a context that says
141
+ // "running" and isn't, a worklet that failed to load, output routed
142
+ // nowhere), the user heard nothing. Belt to the first-chunk check's
143
+ // braces — that one names the common cause, this one catches the rest.
144
+ if (statsRef.current.chunks > 0 && player.getPlaybackTimeMs() === 0)
145
+ reportAudioFailure('no-playback', 'delivered audio but the playback clock never advanced — the user heard nothing', {
146
+ voice: options?.voice,
147
+ ctx: player.contextState(),
148
+ ...statsRef.current,
149
+ });
119
150
  setPlaying(false);
120
151
  setCurrentWord('');
121
152
  cleanupListeners();
@@ -130,6 +161,7 @@ export function useTTS(socket) {
130
161
  if (streamIdRef.current !== streamId)
131
162
  return;
132
163
  setError('TTS stream timed out — no response from server');
164
+ reportAudioFailure('stream-timeout', `received no audio for ${WATCHDOG_MS / 1000}s and gave up`, { voice: options?.voice, ...statsRef.current });
133
165
  finish();
134
166
  }, WATCHDOG_MS);
135
167
  };
@@ -179,6 +211,7 @@ export function useTTS(socket) {
179
211
  if (streamIdRef.current !== streamId || serverDone)
180
212
  return;
181
213
  setError('connection lost');
214
+ reportAudioFailure('socket-closed', 'lost its connection to ugly.bot mid-stream', { voice: options?.voice, ...statsRef.current });
182
215
  finish();
183
216
  });
184
217
  unsubsRef.current.push(socket.on('tts:chunk', (data) => {
@@ -186,6 +219,16 @@ export function useTTS(socket) {
186
219
  return;
187
220
  armWatchdog();
188
221
  statsRef.current.chunks++;
222
+ // THE SILENT FAILURE, caught at the one moment it is unambiguous.
223
+ // Audio has arrived, so the server and the socket are fine — but a
224
+ // context the browser never unlocked will not play a sample of it,
225
+ // and nothing downstream will ever raise an error. This is what
226
+ // "TTS does not play" has meant every time so far, and it was
227
+ // invisible: `playing` stays true, no error fires, the app shows a
228
+ // spinner or nothing. Report it on the first chunk, once per stream.
229
+ if (statsRef.current.chunks === 1 &&
230
+ player.contextState() !== 'running')
231
+ reportAudioFailure('context-not-running', `arrived but the AudioContext is "${player.contextState()}" — the browser never unlocked it, so nothing will be heard. Call warmup() inside the tap that starts playback.`, { voice: options?.voice });
189
232
  void player.feedChunk(data.audio);
190
233
  if (data.visemes) {
191
234
  for (const v of data.visemes) {
@@ -225,6 +268,7 @@ export function useTTS(socket) {
225
268
  if (data.streamId !== streamId)
226
269
  return;
227
270
  setError(data.message);
271
+ reportAudioFailure('server-error', `was refused by ugly.bot — ${data.message}`, { voice: options?.voice, ...statsRef.current });
228
272
  finish();
229
273
  }), ...(unsubClose ? [unsubClose] : []));
230
274
  rafRef.current = requestAnimationFrame(tick);
@@ -1 +1 @@
1
- {"version":3,"file":"useTTS.js","sourceRoot":"","sources":["../../../src/client/audio/useTTS.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useTTS.js","sourceRoot":"","sources":["../../../src/client/audio/useTTS.ts"],"names":[],"mappings":"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"}
@@ -94,6 +94,10 @@ export declare const BUNDLED_TOOLS: readonly [{
94
94
  readonly name: "yt-dlp";
95
95
  readonly category: "media";
96
96
  readonly description: "yt-dlp — download media from the web.";
97
+ }, {
98
+ readonly name: "whisper-cli";
99
+ readonly category: "media";
100
+ readonly description: "whisper.cpp — offline speech-to-text transcription.";
97
101
  }, {
98
102
  readonly name: "pandoc";
99
103
  readonly category: "doc";
@@ -1 +1 @@
1
- {"version":3,"file":"bundledTools.d.ts","sourceRoot":"","sources":["../../src/native/bundledTools.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,WAAW;IAC1B,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;IACzD,4EAA4E;IAC5E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4DAA4D;IAC5D,WAAW,EAAE,MAAM,CAAC;CACrB;AAKD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkIiB,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAMrE,2DAA2D;AAC3D,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAEjE"}
1
+ {"version":3,"file":"bundledTools.d.ts","sourceRoot":"","sources":["../../src/native/bundledTools.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,WAAW;IAC1B,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;IACzD,4EAA4E;IAC5E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4DAA4D;IAC5D,WAAW,EAAE,MAAM,CAAC;CACrB;AAKD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2IiB,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAMrE,2DAA2D;AAC3D,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAEjE"}
@@ -114,6 +114,15 @@ export const BUNDLED_TOOLS = [
114
114
  category: 'media',
115
115
  description: 'yt-dlp — download media from the web.',
116
116
  },
117
+ // whisper.cpp's Homebrew formula is `whisper-cpp`, but its executable —
118
+ // and what every consumer actually spawns — is `whisper-cli`. `name` is
119
+ // the request/grant key AND the default spawn name (what `checkProcess`
120
+ // matches against), so it must be `whisper-cli`, not the formula name.
121
+ {
122
+ name: 'whisper-cli',
123
+ category: 'media',
124
+ description: 'whisper.cpp — offline speech-to-text transcription.',
125
+ },
117
126
  // doc
118
127
  {
119
128
  name: 'pandoc',
@@ -1 +1 @@
1
- {"version":3,"file":"bundledTools.js","sourceRoot":"","sources":["../../src/native/bundledTools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bundledTools.js","sourceRoot":"","sources":["../../src/native/bundledTools.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"net.d.ts","sourceRoot":"","sources":["../../../src/native/facades/net.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAChC,MAAM,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IAC9C,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IACzC,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC;IACvC,GAAG,IAAI,IAAI,CAAC;IACZ,KAAK,IAAI,IAAI,CAAC;CACf;AACD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IAChD,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IAChD,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAC5C;AAED,KAAK,MAAM,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AACtE,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;AAiE5E;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAC1B,SAAS,CAwEX"}
1
+ {"version":3,"file":"net.d.ts","sourceRoot":"","sources":["../../../src/native/facades/net.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAChC,MAAM,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IAC9C,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IACzC,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC;IACvC,GAAG,IAAI,IAAI,CAAC;IACZ,KAAK,IAAI,IAAI,CAAC;CACf;AACD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IAChD,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IAChD,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAC5C;AAED,KAAK,MAAM,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AACtE,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;AAiE5E;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAC1B,SAAS,CAuIX"}
@@ -54,7 +54,13 @@ function buildSocket(invoke, subscribe, id, alreadyConnected) {
54
54
  export function createNetFacade(invoke, subscribe, ensure) {
55
55
  return {
56
56
  connect(host, port) {
57
- void ensure();
57
+ // Fire the permission check; the host daemon also enforces it inside
58
+ // `invoke('net.connect', …)` below, so a rejection here is redundant
59
+ // with (and always followed by) that call's own rejection — which IS
60
+ // routed to `onError` below. Swallow it here purely to avoid an
61
+ // unhandled-rejection warning; don't double-report. (Mirrors the
62
+ // `process` facade's `spawn()` — see the comment there for why.)
63
+ void ensure().catch(() => { });
58
64
  const cbs = {
59
65
  connect: [],
60
66
  data: [],
@@ -62,7 +68,14 @@ export function createNetFacade(invoke, subscribe, ensure) {
62
68
  error: [],
63
69
  };
64
70
  const unsubs = [];
65
- const ready = invoke('net.connect', { host, port }).then((r) => {
71
+ // Set once the connect invoke rejects (e.g. the permission gate denies
72
+ // `net`). Lets `onError` replay the failure to a callback registered
73
+ // AFTER the rejection already happened, without double-delivering to
74
+ // one registered before — see the `onError` implementation below for
75
+ // exactly how, and why the set-before-forEach ordering here matters.
76
+ let connectFailure;
77
+ const ready = invoke('net.connect', { host, port })
78
+ .then((r) => {
66
79
  const id = r.id;
67
80
  unsubs.push(subscribe(`net.connect:${id}`, () => cbs.connect.forEach((cb) => cb())));
68
81
  unsubs.push(subscribe(`net.data:${id}`, (d) => cbs.data.forEach((cb) => cb(binStrToBytes(d.data)))));
@@ -72,20 +85,68 @@ export function createNetFacade(invoke, subscribe, ensure) {
72
85
  unsubs.forEach((u) => u());
73
86
  }));
74
87
  return id;
88
+ })
89
+ .catch((e) => {
90
+ // The socket itself never connected — e.g. the permission gate
91
+ // denied `net`. That's an `error`, never a `close`: no socket ever
92
+ // opened, so there is nothing to report as closed, and a caller
93
+ // that treated a denial as "closed" would mask the real failure.
94
+ // Callbacks already registered get notified now; `onError` below
95
+ // replays this to any callback registered later, exactly once each.
96
+ connectFailure = e instanceof Error ? e.message : String(e);
97
+ cbs.error.forEach((cb) => cb(connectFailure));
98
+ throw e; // keep `ready` rejected so `withId` below stays a no-op
75
99
  });
100
+ // `ready` must stay rejected (so every `withId` call below is a no-op),
101
+ // but that leaves it a dangling rejected promise whenever a caller
102
+ // never invokes write/end/close — which Node/browsers flag as an
103
+ // unhandled rejection even though the failure was already delivered
104
+ // via `onError` above. Attach a standing no-op handler to silence
105
+ // that; it doesn't consume `ready`'s rejection for the `withId` chain
106
+ // below, since each `.then()/.catch()` call derives its own
107
+ // independent promise.
108
+ ready.catch(() => { });
76
109
  const withId = (fn) => void ready.then(fn).catch(() => { });
77
110
  return {
78
111
  onConnect: (cb) => cbs.connect.push(cb),
79
112
  onData: (cb) => cbs.data.push(cb),
80
113
  onClose: (cb) => cbs.close.push(cb),
81
- onError: (cb) => cbs.error.push(cb),
114
+ onError: (cb) => {
115
+ cbs.error.push(cb);
116
+ // Late subscriber: the connect already failed before this
117
+ // callback registered, so it missed the `.catch` above's
118
+ // `forEach` (that `forEach` only visits entries `cbs.error` held
119
+ // when it started iterating — a push during/after it is never
120
+ // visited). Replay the failure here instead. This does NOT
121
+ // double-deliver to a callback that was already notified:
122
+ // `connectFailure` is set BEFORE that `forEach` runs — so by the
123
+ // time any `onError` call happens, either (a) the failure hasn't
124
+ // happened yet and this `if` is skipped, or (b) it has, and every
125
+ // callback in `cbs.error` before this `push()` was already
126
+ // invoked by that `forEach`, so only the newly-pushed one still
127
+ // needs it. That ordering (set-before-forEach) is load-bearing —
128
+ // swap it and a callback registered while inside another
129
+ // `onError` callback is silently dropped instead of replayed.
130
+ if (connectFailure !== undefined)
131
+ cb(connectFailure);
132
+ },
133
+ // write/end/close after a failed connect: `withId` never calls `fn`
134
+ // because `ready` stays rejected, so these are silent no-ops.
135
+ // That's intentional — the socket never existed, there's nothing to
136
+ // write/end/close, and the caller already got the failure via
137
+ // `onError`; a second error here would just duplicate that signal.
82
138
  write: (data) => withId((id) => void invoke('net.write', { id, data: bytesToBinStr(data) })),
83
139
  end: () => withId((id) => void invoke('net.end', { id })),
84
140
  close: () => withId((id) => void invoke('net.close', { id })),
85
141
  };
86
142
  },
87
143
  async listen(port) {
88
- void ensure();
144
+ // Advisory only, like `connect()` above: `invoke('net.listen', …)`
145
+ // just below is awaited directly, so its own rejection already
146
+ // propagates to the caller as a rejected `listen()` promise — no
147
+ // buffering needed here. Swallow this one purely to avoid an
148
+ // unhandled-rejection warning from the redundant advisory check.
149
+ void ensure().catch(() => { });
89
150
  const r = (await invoke('net.listen', { port }));
90
151
  const connCbs = [];
91
152
  subscribe(`net.connection:${r.id}`, (d) => {
@@ -1 +1 @@
1
- {"version":3,"file":"net.js","sourceRoot":"","sources":["../../../src/native/facades/net.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"net.js","sourceRoot":"","sources":["../../../src/native/facades/net.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../src/native/facades/process.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC5C,QAAQ,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC5C,MAAM,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAAC;IAChD,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IACzC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,IAAI,IAAI,CAAC;IACnB,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC;CACnE;AAED;;;mFAGmF;AACnF,KAAK,MAAM,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AACtE,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;AAE5E;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAC1B,aAAa,CAsDf"}
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../src/native/facades/process.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC5C,QAAQ,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC5C,MAAM,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAAC;IAChD,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IACzC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,IAAI,IAAI,CAAC;IACnB,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC;CACnE;AAED;;;mFAGmF;AACnF,KAAK,MAAM,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AACtE,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;AAE5E;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAC1B,aAAa,CAmHf"}
@@ -9,7 +9,12 @@
9
9
  export function createProcessFacade(invoke, subscribe, ensure) {
10
10
  return {
11
11
  spawn(cmd, args, opts) {
12
- void ensure(); // fire the permission check; the host daemon also enforces
12
+ // Fire the permission check; the host daemon also enforces it inside
13
+ // `invoke('process.spawn', …)` below, so a rejection here is redundant
14
+ // with (and always followed by) that call's own rejection — which IS
15
+ // routed to `onError` below. Swallow it here purely to avoid an
16
+ // unhandled-rejection warning; don't double-report.
17
+ void ensure().catch(() => { });
13
18
  const cbs = {
14
19
  stdout: [],
15
20
  stderr: [],
@@ -17,7 +22,15 @@ export function createProcessFacade(invoke, subscribe, ensure) {
17
22
  error: [],
18
23
  };
19
24
  const unsubs = [];
20
- const ready = invoke('process.spawn', { cmd, args, opts }).then((r) => {
25
+ // Set once the spawn invoke rejects (e.g. the permission gate denies
26
+ // the binary). Lets `onError` replay the failure to a callback that
27
+ // registers AFTER the rejection already happened — callers may call
28
+ // `spawn()` and register `onError` in the same synchronous tick, but
29
+ // nothing guarantees that ordering, so a purely "fire once at rejection
30
+ // time" callback would silently drop late subscribers.
31
+ let spawnFailure;
32
+ const ready = invoke('process.spawn', { cmd, args, opts })
33
+ .then((r) => {
21
34
  const id = r.id;
22
35
  unsubs.push(subscribe(`process.stdout:${id}`, (d) => cbs.stdout.forEach((cb) => cb(d.chunk))));
23
36
  unsubs.push(subscribe(`process.stderr:${id}`, (d) => cbs.stderr.forEach((cb) => cb(d.chunk))));
@@ -27,13 +40,58 @@ export function createProcessFacade(invoke, subscribe, ensure) {
27
40
  unsubs.forEach((u) => u());
28
41
  }));
29
42
  return id;
43
+ })
44
+ .catch((e) => {
45
+ // The spawn itself never happened — e.g. the permission gate
46
+ // denied the binary (`not a bundled tool` / `tool not granted`).
47
+ // That's an `error`, never an `exit`: no process ever started, so
48
+ // there is no exit code to report, and a caller that treated a
49
+ // denial as "exited" could log a bogus code. Callbacks already
50
+ // registered get notified now; `onError` below replays this to
51
+ // any callback registered later, exactly once each.
52
+ spawnFailure = e instanceof Error ? e.message : String(e);
53
+ cbs.error.forEach((cb) => cb(spawnFailure));
54
+ throw e; // keep `ready` rejected so `withId` below stays a no-op
30
55
  });
56
+ // `ready` must stay rejected (so every `withId` call below is a no-op),
57
+ // but that leaves it a dangling rejected promise whenever a caller
58
+ // never invokes write/kill/closeStdin — which Node/browsers flag as an
59
+ // unhandled rejection even though the failure was already delivered via
60
+ // `onError` above. Attach a standing no-op handler to silence that;
61
+ // it doesn't consume `ready`'s rejection for the `withId` chain below,
62
+ // since each `.then()/.catch()` call derives its own independent promise.
63
+ ready.catch(() => { });
31
64
  const withId = (fn) => void ready.then(fn).catch(() => { });
32
65
  return {
33
66
  onStdout: (cb) => cbs.stdout.push(cb),
34
67
  onStderr: (cb) => cbs.stderr.push(cb),
35
68
  onExit: (cb) => cbs.exit.push(cb),
36
- onError: (cb) => cbs.error.push(cb),
69
+ onError: (cb) => {
70
+ cbs.error.push(cb);
71
+ // Late subscriber: the spawn already failed before this callback
72
+ // registered, so it missed the `.catch` above's `forEach` (that
73
+ // `forEach` only visits entries `cbs.error` held at the time it
74
+ // started iterating — a push during/after it, e.g. from another
75
+ // `onError` callback, is never visited). Replay the failure here
76
+ // instead. This does NOT double-deliver to a callback that was
77
+ // already notified: `spawnFailure` is set at line 98, BEFORE the
78
+ // `forEach` at line 99 runs — so by the time any `onError` call
79
+ // happens, either (a) the failure hasn't happened yet and this
80
+ // `if` is skipped, or (b) it has, and every callback in
81
+ // `cbs.error` before this `push()` was already invoked by that
82
+ // `forEach`, so only the newly-pushed one still needs it. That
83
+ // ordering (set-before-forEach) is load-bearing — swap it and a
84
+ // callback registered while inside another `onError` callback is
85
+ // silently dropped instead of replayed.
86
+ if (spawnFailure !== undefined)
87
+ cb(spawnFailure);
88
+ },
89
+ // write/kill/closeStdin after a failed spawn: `withId` never calls
90
+ // `fn` because `ready` stays rejected, so these are silent no-ops.
91
+ // That's intentional, not an oversight — the process never existed,
92
+ // there's nothing to kill/write/close, and the caller already got
93
+ // the failure via `onError`; surfacing a second error here would
94
+ // just be a duplicate signal for the same event.
37
95
  write: (data) => withId((id) => void invoke('process.write', { id, data })),
38
96
  closeStdin: () => withId((id) => void invoke('process.closeStdin', { id })),
39
97
  kill: (signal) => withId((id) => void invoke('process.kill', { id, signal })),
@@ -1 +1 @@
1
- {"version":3,"file":"process.js","sourceRoot":"","sources":["../../../src/native/facades/process.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"process.js","sourceRoot":"","sources":["../../../src/native/facades/process.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ugly-app",
3
3
  "packageManager": "pnpm@10.34.5",
4
- "version": "0.1.992",
4
+ "version": "0.1.994",
5
5
  "type": "module",
6
6
  "comment:files": "Allowlist what ships to npm. dist = runtime; src = sourcemap targets (dist/*.js.map reference ../../src/); templates = CLI scaffold. Everything else at repo root (.pgdata local Postgres, coverage, assets/icons sources, test/, test-results/) is excluded by omission. The !negations strip the scaffold's installed deps + cruft (templates/node_modules is 200MB+ and must never ship). package.json/README/LICENSE ship automatically.",
7
7
  "files": [
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by prebuild — do not edit manually
2
- export const CLI_VERSION = "0.1.992";
2
+ export const CLI_VERSION = "0.1.994";
@@ -35,6 +35,31 @@ let streamCounter = 0;
35
35
  // forever with the rAF loop spinning and the socket held.
36
36
  const WATCHDOG_MS = 30_000;
37
37
 
38
+ /**
39
+ * Report a failed or silent read-aloud on the framework's UX audit channel.
40
+ *
41
+ * ⚠️ THIS EXISTS BECAUSE "TTS does not play" WAS UNDIAGNOSABLE. The hook set its
42
+ * local `error` state and stopped there, so a failure was visible only to
43
+ * whatever the app chose to render — and apps mostly render nothing, or abort
44
+ * quietly. Production reports therefore said "TTS does not play" with no page,
45
+ * no route and no reason, and answering them meant asking the user which screen
46
+ * they were on. `[ugly.ux] …` is the same channel the image/keyboard/safe-area
47
+ * audits use: the Logger ships it to the project's error log with the page
48
+ * attached, so every app gets this for free and none has to build its own.
49
+ */
50
+ function reportAudioFailure(
51
+ reason: string,
52
+ detail: string,
53
+ extra: Record<string, unknown>,
54
+ ): void {
55
+ const page = typeof window === 'undefined' ? '' : window.location.pathname;
56
+ console.error(`[ugly.ux] audio: read-aloud ${detail}`, {
57
+ reason,
58
+ page,
59
+ ...extra,
60
+ });
61
+ }
62
+
38
63
  export function useTTS(socket: UglyBotSocket) {
39
64
  const [playing, setPlaying] = useState(false);
40
65
  const [currentWord, setCurrentWord] = useState('');
@@ -152,6 +177,21 @@ export function useTTS(socket: UglyBotSocket) {
152
177
  // Flush any remaining caption indices so the text fully reveals.
153
178
  while (wi < words.length)
154
179
  options?.onSubtitleIndex?.(words[wi++]!.charEnd);
180
+ // Audio was delivered and the stream is over, but the worklet's own
181
+ // sample clock never moved: whatever the cause (a context that says
182
+ // "running" and isn't, a worklet that failed to load, output routed
183
+ // nowhere), the user heard nothing. Belt to the first-chunk check's
184
+ // braces — that one names the common cause, this one catches the rest.
185
+ if (statsRef.current.chunks > 0 && player.getPlaybackTimeMs() === 0)
186
+ reportAudioFailure(
187
+ 'no-playback',
188
+ 'delivered audio but the playback clock never advanced — the user heard nothing',
189
+ {
190
+ voice: options?.voice,
191
+ ctx: player.contextState(),
192
+ ...statsRef.current,
193
+ },
194
+ );
155
195
  setPlaying(false);
156
196
  setCurrentWord('');
157
197
  cleanupListeners();
@@ -166,6 +206,11 @@ export function useTTS(socket: UglyBotSocket) {
166
206
  watchdogRef.current = null;
167
207
  if (streamIdRef.current !== streamId) return;
168
208
  setError('TTS stream timed out — no response from server');
209
+ reportAudioFailure(
210
+ 'stream-timeout',
211
+ `received no audio for ${WATCHDOG_MS / 1000}s and gave up`,
212
+ { voice: options?.voice, ...statsRef.current },
213
+ );
169
214
  finish();
170
215
  }, WATCHDOG_MS);
171
216
  };
@@ -215,6 +260,11 @@ export function useTTS(socket: UglyBotSocket) {
215
260
  // let the rAF loop finish it normally.
216
261
  if (streamIdRef.current !== streamId || serverDone) return;
217
262
  setError('connection lost');
263
+ reportAudioFailure(
264
+ 'socket-closed',
265
+ 'lost its connection to ugly.bot mid-stream',
266
+ { voice: options?.voice, ...statsRef.current },
267
+ );
218
268
  finish();
219
269
  });
220
270
  unsubsRef.current.push(
@@ -222,6 +272,22 @@ export function useTTS(socket: UglyBotSocket) {
222
272
  if (data.streamId !== streamId) return;
223
273
  armWatchdog();
224
274
  statsRef.current.chunks++;
275
+ // THE SILENT FAILURE, caught at the one moment it is unambiguous.
276
+ // Audio has arrived, so the server and the socket are fine — but a
277
+ // context the browser never unlocked will not play a sample of it,
278
+ // and nothing downstream will ever raise an error. This is what
279
+ // "TTS does not play" has meant every time so far, and it was
280
+ // invisible: `playing` stays true, no error fires, the app shows a
281
+ // spinner or nothing. Report it on the first chunk, once per stream.
282
+ if (
283
+ statsRef.current.chunks === 1 &&
284
+ player.contextState() !== 'running'
285
+ )
286
+ reportAudioFailure(
287
+ 'context-not-running',
288
+ `arrived but the AudioContext is "${player.contextState()}" — the browser never unlocked it, so nothing will be heard. Call warmup() inside the tap that starts playback.`,
289
+ { voice: options?.voice },
290
+ );
225
291
  void player.feedChunk(data.audio);
226
292
  if (data.visemes) {
227
293
  for (const v of data.visemes) {
@@ -261,6 +327,11 @@ export function useTTS(socket: UglyBotSocket) {
261
327
  socket.on('tts:error', (data) => {
262
328
  if (data.streamId !== streamId) return;
263
329
  setError(data.message);
330
+ reportAudioFailure(
331
+ 'server-error',
332
+ `was refused by ugly.bot — ${data.message}`,
333
+ { voice: options?.voice, ...statsRef.current },
334
+ );
264
335
  finish();
265
336
  }),
266
337
  ...(unsubClose ? [unsubClose] : []),
@@ -23,4 +23,15 @@ describe('bundled tool catalog', () => {
23
23
  it('returns undefined for unknown tools', () => {
24
24
  expect(bundledTool('definitely-not-a-tool')).toBeUndefined();
25
25
  });
26
+
27
+ it('whisper.cpp is requestable under the name consumers actually spawn', () => {
28
+ // The Homebrew formula is `whisper-cpp`, but the executable — and what
29
+ // callers pass to `host.spawn(...)` — is `whisper-cli`. `checkProcess`
30
+ // matches on the spawn name, so the catalog `name` must be
31
+ // `whisper-cli` for a real spawn request to be granted.
32
+ const t = bundledTool('whisper-cli');
33
+ expect(t).toBeDefined();
34
+ expect(t!.category).toBe('media');
35
+ expect(bundledTool('whisper-cpp')).toBeUndefined();
36
+ });
26
37
  });
@@ -130,6 +130,15 @@ export const BUNDLED_TOOLS = [
130
130
  category: 'media',
131
131
  description: 'yt-dlp — download media from the web.',
132
132
  },
133
+ // whisper.cpp's Homebrew formula is `whisper-cpp`, but its executable —
134
+ // and what every consumer actually spawns — is `whisper-cli`. `name` is
135
+ // the request/grant key AND the default spawn name (what `checkProcess`
136
+ // matches against), so it must be `whisper-cli`, not the formula name.
137
+ {
138
+ name: 'whisper-cli',
139
+ category: 'media',
140
+ description: 'whisper.cpp — offline speech-to-text transcription.',
141
+ },
133
142
  // doc
134
143
  {
135
144
  name: 'pandoc',
@@ -0,0 +1,191 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { createNetFacade } from './net.js';
3
+
4
+ describe('net facade — uniform invoke/subscribe protocol (no UglyDesktop)', () => {
5
+ it('connects, streams data, closes, and routes write/close by id', async () => {
6
+ const listeners = new Map<string, (d: unknown) => void>();
7
+ const invoked: { ch: string; p: unknown }[] = [];
8
+ const invoke = async (ch: string, p: unknown): Promise<unknown> => {
9
+ invoked.push({ ch, p });
10
+ if (ch === 'net.connect') return { id: 'S1' };
11
+ return undefined;
12
+ };
13
+ const subscribe = (ev: string, cb: (d: unknown) => void) => {
14
+ listeners.set(ev, cb);
15
+ return () => listeners.delete(ev);
16
+ };
17
+
18
+ const facade = createNetFacade(invoke, subscribe, async () => {});
19
+ const chunks: Uint8Array[] = [];
20
+ let closed = false;
21
+
22
+ const sock = facade.connect('example.com', 443);
23
+ sock.onData((c) => chunks.push(c));
24
+ sock.onClose(() => (closed = true));
25
+
26
+ await Promise.resolve();
27
+ await Promise.resolve();
28
+
29
+ expect(invoked[0]).toEqual({
30
+ ch: 'net.connect',
31
+ p: { host: 'example.com', port: 443 },
32
+ });
33
+
34
+ listeners.get('net.data:S1')!({ data: 'hi' });
35
+ listeners.get('net.close:S1')!({});
36
+ expect(chunks).toHaveLength(1);
37
+ expect(closed).toBe(true);
38
+
39
+ sock.write('x');
40
+ sock.close();
41
+ await Promise.resolve();
42
+ await Promise.resolve();
43
+ expect(invoked).toContainEqual({
44
+ ch: 'net.write',
45
+ p: { id: 'S1', data: 'x' },
46
+ });
47
+ expect(invoked).toContainEqual({ ch: 'net.close', p: { id: 'S1' } });
48
+ });
49
+
50
+ it('routes a denied/rejected connect to onError (callback registered BEFORE the rejection)', async () => {
51
+ const invoke = async (ch: string): Promise<unknown> => {
52
+ if (ch === 'net.connect') {
53
+ throw new Error(
54
+ 'Permission denied for … (net example.com:443): net not granted',
55
+ );
56
+ }
57
+ return undefined;
58
+ };
59
+ const subscribe = () => () => {};
60
+
61
+ const facade = createNetFacade(invoke, subscribe, async () => {});
62
+ const errors: string[] = [];
63
+ const closes: number[] = [];
64
+
65
+ const sock = facade.connect('example.com', 443);
66
+ sock.onError((e) => errors.push(e));
67
+ sock.onClose(() => closes.push(1));
68
+
69
+ // Let the invoke() rejection propagate through the connect promise chain.
70
+ await Promise.resolve();
71
+ await Promise.resolve();
72
+ await Promise.resolve();
73
+
74
+ expect(errors).toHaveLength(1);
75
+ expect(errors[0]).toContain('net not granted');
76
+ // A denied connect never opened a socket — it must never look like close.
77
+ expect(closes).toHaveLength(0);
78
+ });
79
+
80
+ it('routes a denied/rejected connect to onError (callback registered AFTER the rejection), exactly once', async () => {
81
+ const invoke = async (ch: string): Promise<unknown> => {
82
+ if (ch === 'net.connect') throw new Error('net not granted');
83
+ return undefined;
84
+ };
85
+ const subscribe = () => () => {};
86
+
87
+ const facade = createNetFacade(invoke, subscribe, async () => {});
88
+ const early: string[] = [];
89
+ const late: string[] = [];
90
+
91
+ const sock = facade.connect('example.com', 443);
92
+ sock.onError((e) => early.push(e));
93
+
94
+ // Let the rejection actually happen before the late subscriber registers.
95
+ await Promise.resolve();
96
+ await Promise.resolve();
97
+ await Promise.resolve();
98
+
99
+ sock.onError((e) => late.push(e));
100
+ await Promise.resolve();
101
+
102
+ // Both callbacks see the failure exactly once each — the early one is
103
+ // not re-invoked by the late registration, and the late one isn't
104
+ // dropped for having missed the original rejection.
105
+ expect(early).toEqual(['net not granted']);
106
+ expect(late).toEqual(['net not granted']);
107
+ });
108
+
109
+ it('write/end/close after a failed connect are silent no-ops (no invoke calls)', async () => {
110
+ const invoked: { ch: string; p: unknown }[] = [];
111
+ const invoke = async (ch: string, p: unknown): Promise<unknown> => {
112
+ invoked.push({ ch, p });
113
+ if (ch === 'net.connect') throw new Error('net not granted');
114
+ return undefined;
115
+ };
116
+ const subscribe = () => () => {};
117
+
118
+ const facade = createNetFacade(invoke, subscribe, async () => {});
119
+ const sock = facade.connect('example.com', 443);
120
+ sock.onError(() => {}); // observe, but don't assert on it here
121
+
122
+ await Promise.resolve();
123
+ await Promise.resolve();
124
+ await Promise.resolve();
125
+
126
+ sock.write('x');
127
+ sock.end();
128
+ sock.close();
129
+ await Promise.resolve();
130
+ await Promise.resolve();
131
+
132
+ expect(invoked).toEqual([
133
+ { ch: 'net.connect', p: { host: 'example.com', port: 443 } },
134
+ ]);
135
+ });
136
+
137
+ it('a rejected ensure() permission pre-check does not throw unhandled and does not itself fire onError', async () => {
138
+ const invoke = async (ch: string): Promise<unknown> => {
139
+ if (ch === 'net.connect') return { id: 'S2' };
140
+ return undefined;
141
+ };
142
+ const listeners = new Map<string, (d: unknown) => void>();
143
+ const subscribe = (ev: string, cb: (d: unknown) => void) => {
144
+ listeners.set(ev, cb);
145
+ return () => listeners.delete(ev);
146
+ };
147
+ const ensure = async () => {
148
+ throw new Error('permission not yet granted');
149
+ };
150
+
151
+ const facade = createNetFacade(invoke, subscribe, ensure);
152
+ const errors: string[] = [];
153
+ const sock = facade.connect('example.com', 443);
154
+ sock.onError((e) => errors.push(e));
155
+
156
+ await Promise.resolve();
157
+ await Promise.resolve();
158
+ await Promise.resolve();
159
+
160
+ // The real connect succeeded (host daemon is the actual enforcer), so no
161
+ // error should fire even though the advisory ensure() rejected.
162
+ expect(errors).toHaveLength(0);
163
+ });
164
+
165
+ it('a callback registered from inside another onError callback is not dropped', async () => {
166
+ const invoke = async (ch: string): Promise<unknown> => {
167
+ if (ch === 'net.connect') throw new Error('net not granted');
168
+ return undefined;
169
+ };
170
+ const subscribe = () => () => {};
171
+
172
+ const facade = createNetFacade(invoke, subscribe, async () => {});
173
+ let outer = 0;
174
+ let nested = 0;
175
+
176
+ const sock = facade.connect('example.com', 443);
177
+ sock.onError(() => {
178
+ outer++;
179
+ sock.onError(() => {
180
+ nested++;
181
+ });
182
+ });
183
+
184
+ await Promise.resolve();
185
+ await Promise.resolve();
186
+ await Promise.resolve();
187
+
188
+ expect(outer).toBe(1);
189
+ expect(nested).toBe(1);
190
+ });
191
+ });
@@ -96,7 +96,14 @@ export function createNetFacade(
96
96
  ): NetFacade {
97
97
  return {
98
98
  connect(host, port) {
99
- void ensure();
99
+ // Fire the permission check; the host daemon also enforces it inside
100
+ // `invoke('net.connect', …)` below, so a rejection here is redundant
101
+ // with (and always followed by) that call's own rejection — which IS
102
+ // routed to `onError` below. Swallow it here purely to avoid an
103
+ // unhandled-rejection warning; don't double-report. (Mirrors the
104
+ // `process` facade's `spawn()` — see the comment there for why.)
105
+ void ensure().catch(() => {});
106
+
100
107
  const cbs = {
101
108
  connect: [] as (() => void)[],
102
109
  data: [] as ((b: Uint8Array) => void)[],
@@ -104,40 +111,91 @@ export function createNetFacade(
104
111
  error: [] as ((e: string) => void)[],
105
112
  };
106
113
  const unsubs: (() => void)[] = [];
107
- const ready = invoke('net.connect', { host, port }).then((r) => {
108
- const id = (r as { id: string }).id;
109
- unsubs.push(
110
- subscribe(`net.connect:${id}`, () =>
111
- cbs.connect.forEach((cb) => cb()),
112
- ),
113
- );
114
- unsubs.push(
115
- subscribe(`net.data:${id}`, (d) =>
116
- cbs.data.forEach((cb) =>
117
- cb(binStrToBytes((d as { data: string }).data)),
114
+
115
+ // Set once the connect invoke rejects (e.g. the permission gate denies
116
+ // `net`). Lets `onError` replay the failure to a callback registered
117
+ // AFTER the rejection already happened, without double-delivering to
118
+ // one registered before — see the `onError` implementation below for
119
+ // exactly how, and why the set-before-forEach ordering here matters.
120
+ let connectFailure: string | undefined;
121
+
122
+ const ready = invoke('net.connect', { host, port })
123
+ .then((r) => {
124
+ const id = (r as { id: string }).id;
125
+ unsubs.push(
126
+ subscribe(`net.connect:${id}`, () =>
127
+ cbs.connect.forEach((cb) => cb()),
118
128
  ),
119
- ),
120
- );
121
- unsubs.push(
122
- subscribe(`net.error:${id}`, (d) =>
123
- cbs.error.forEach((cb) => cb((d as { err: string }).err)),
124
- ),
125
- );
126
- unsubs.push(
127
- subscribe(`net.close:${id}`, () => {
128
- cbs.close.forEach((cb) => cb());
129
- unsubs.forEach((u) => u());
130
- }),
131
- );
132
- return id;
133
- });
129
+ );
130
+ unsubs.push(
131
+ subscribe(`net.data:${id}`, (d) =>
132
+ cbs.data.forEach((cb) =>
133
+ cb(binStrToBytes((d as { data: string }).data)),
134
+ ),
135
+ ),
136
+ );
137
+ unsubs.push(
138
+ subscribe(`net.error:${id}`, (d) =>
139
+ cbs.error.forEach((cb) => cb((d as { err: string }).err)),
140
+ ),
141
+ );
142
+ unsubs.push(
143
+ subscribe(`net.close:${id}`, () => {
144
+ cbs.close.forEach((cb) => cb());
145
+ unsubs.forEach((u) => u());
146
+ }),
147
+ );
148
+ return id;
149
+ })
150
+ .catch((e: unknown) => {
151
+ // The socket itself never connected — e.g. the permission gate
152
+ // denied `net`. That's an `error`, never a `close`: no socket ever
153
+ // opened, so there is nothing to report as closed, and a caller
154
+ // that treated a denial as "closed" would mask the real failure.
155
+ // Callbacks already registered get notified now; `onError` below
156
+ // replays this to any callback registered later, exactly once each.
157
+ connectFailure = e instanceof Error ? e.message : String(e);
158
+ cbs.error.forEach((cb) => cb(connectFailure!));
159
+ throw e; // keep `ready` rejected so `withId` below stays a no-op
160
+ });
161
+ // `ready` must stay rejected (so every `withId` call below is a no-op),
162
+ // but that leaves it a dangling rejected promise whenever a caller
163
+ // never invokes write/end/close — which Node/browsers flag as an
164
+ // unhandled rejection even though the failure was already delivered
165
+ // via `onError` above. Attach a standing no-op handler to silence
166
+ // that; it doesn't consume `ready`'s rejection for the `withId` chain
167
+ // below, since each `.then()/.catch()` call derives its own
168
+ // independent promise.
169
+ ready.catch(() => {});
134
170
  const withId = (fn: (id: string) => void) =>
135
171
  void ready.then(fn).catch(() => {});
136
172
  return {
137
173
  onConnect: (cb) => cbs.connect.push(cb),
138
174
  onData: (cb) => cbs.data.push(cb),
139
175
  onClose: (cb) => cbs.close.push(cb),
140
- onError: (cb) => cbs.error.push(cb),
176
+ onError: (cb) => {
177
+ cbs.error.push(cb);
178
+ // Late subscriber: the connect already failed before this
179
+ // callback registered, so it missed the `.catch` above's
180
+ // `forEach` (that `forEach` only visits entries `cbs.error` held
181
+ // when it started iterating — a push during/after it is never
182
+ // visited). Replay the failure here instead. This does NOT
183
+ // double-deliver to a callback that was already notified:
184
+ // `connectFailure` is set BEFORE that `forEach` runs — so by the
185
+ // time any `onError` call happens, either (a) the failure hasn't
186
+ // happened yet and this `if` is skipped, or (b) it has, and every
187
+ // callback in `cbs.error` before this `push()` was already
188
+ // invoked by that `forEach`, so only the newly-pushed one still
189
+ // needs it. That ordering (set-before-forEach) is load-bearing —
190
+ // swap it and a callback registered while inside another
191
+ // `onError` callback is silently dropped instead of replayed.
192
+ if (connectFailure !== undefined) cb(connectFailure);
193
+ },
194
+ // write/end/close after a failed connect: `withId` never calls `fn`
195
+ // because `ready` stays rejected, so these are silent no-ops.
196
+ // That's intentional — the socket never existed, there's nothing to
197
+ // write/end/close, and the caller already got the failure via
198
+ // `onError`; a second error here would just duplicate that signal.
141
199
  write: (data) =>
142
200
  withId(
143
201
  (id) => void invoke('net.write', { id, data: bytesToBinStr(data) }),
@@ -147,7 +205,12 @@ export function createNetFacade(
147
205
  };
148
206
  },
149
207
  async listen(port) {
150
- void ensure();
208
+ // Advisory only, like `connect()` above: `invoke('net.listen', …)`
209
+ // just below is awaited directly, so its own rejection already
210
+ // propagates to the caller as a rejected `listen()` promise — no
211
+ // buffering needed here. Swallow this one purely to avoid an
212
+ // unhandled-rejection warning from the redundant advisory check.
213
+ void ensure().catch(() => {});
151
214
  const r = (await invoke('net.listen', { port })) as {
152
215
  id: string;
153
216
  port: number;
@@ -51,4 +51,153 @@ describe('process facade — uniform invoke/subscribe protocol (no UglyDesktop)'
51
51
  p: { id: 'P1', signal: 'SIGTERM' },
52
52
  });
53
53
  });
54
+
55
+ it('routes a denied/rejected spawn to onError (callback registered BEFORE the rejection)', async () => {
56
+ const invoke = async (ch: string): Promise<unknown> => {
57
+ if (ch === 'process.spawn') {
58
+ throw new Error(
59
+ 'Permission denied for … (process whisper-cli): not a bundled tool',
60
+ );
61
+ }
62
+ return undefined;
63
+ };
64
+ const subscribe = () => () => {};
65
+
66
+ const facade = createProcessFacade(invoke, subscribe, async () => {});
67
+ const errors: string[] = [];
68
+ const exits: (number | null)[] = [];
69
+
70
+ const proc = facade.spawn('whisper-cli', []);
71
+ proc.onError((e) => errors.push(e));
72
+ proc.onExit((c) => exits.push(c));
73
+
74
+ // Let the invoke() rejection propagate through the spawn promise chain.
75
+ await Promise.resolve();
76
+ await Promise.resolve();
77
+ await Promise.resolve();
78
+
79
+ expect(errors).toHaveLength(1);
80
+ expect(errors[0]).toContain('not a bundled tool');
81
+ // A denied spawn never started a process — it must never look like exit.
82
+ expect(exits).toHaveLength(0);
83
+ });
84
+
85
+ it('routes a denied/rejected spawn to onError (callback registered AFTER the rejection), exactly once', async () => {
86
+ const invoke = async (ch: string): Promise<unknown> => {
87
+ if (ch === 'process.spawn') throw new Error('tool not granted');
88
+ return undefined;
89
+ };
90
+ const subscribe = () => () => {};
91
+
92
+ const facade = createProcessFacade(invoke, subscribe, async () => {});
93
+ const early: string[] = [];
94
+ const late: string[] = [];
95
+
96
+ const proc = facade.spawn('whisper-cli', []);
97
+ proc.onError((e) => early.push(e));
98
+
99
+ // Let the rejection actually happen before the late subscriber registers.
100
+ await Promise.resolve();
101
+ await Promise.resolve();
102
+ await Promise.resolve();
103
+
104
+ proc.onError((e) => late.push(e));
105
+ await Promise.resolve();
106
+
107
+ // Both callbacks see the failure exactly once each — the early one is
108
+ // not re-invoked by the late registration, and the late one isn't
109
+ // dropped for having missed the original rejection.
110
+ expect(early).toEqual(['tool not granted']);
111
+ expect(late).toEqual(['tool not granted']);
112
+ });
113
+
114
+ it('write/kill/closeStdin after a failed spawn are silent no-ops (no invoke calls)', async () => {
115
+ const invoked: { ch: string; p: unknown }[] = [];
116
+ const invoke = async (ch: string, p: unknown): Promise<unknown> => {
117
+ invoked.push({ ch, p });
118
+ if (ch === 'process.spawn') throw new Error('not a bundled tool');
119
+ return undefined;
120
+ };
121
+ const subscribe = () => () => {};
122
+
123
+ const facade = createProcessFacade(invoke, subscribe, async () => {});
124
+ const proc = facade.spawn('whisper-cli', []);
125
+ proc.onError(() => {}); // observe, but don't assert on it here
126
+
127
+ await Promise.resolve();
128
+ await Promise.resolve();
129
+ await Promise.resolve();
130
+
131
+ proc.write('x');
132
+ proc.kill('SIGTERM');
133
+ proc.closeStdin();
134
+ await Promise.resolve();
135
+ await Promise.resolve();
136
+
137
+ expect(invoked).toEqual([
138
+ {
139
+ ch: 'process.spawn',
140
+ p: { cmd: 'whisper-cli', args: [], opts: undefined },
141
+ },
142
+ ]);
143
+ });
144
+
145
+ it('a rejected ensure() permission pre-check does not throw unhandled and does not itself fire onError', async () => {
146
+ const invoke = async (ch: string): Promise<unknown> => {
147
+ if (ch === 'process.spawn') return { id: 'P2', pid: 7 };
148
+ return undefined;
149
+ };
150
+ const listeners = new Map<string, (d: unknown) => void>();
151
+ const subscribe = (ev: string, cb: (d: unknown) => void) => {
152
+ listeners.set(ev, cb);
153
+ return () => listeners.delete(ev);
154
+ };
155
+ const ensure = async () => {
156
+ throw new Error('permission not yet granted');
157
+ };
158
+
159
+ const facade = createProcessFacade(invoke, subscribe, ensure);
160
+ const errors: string[] = [];
161
+ const proc = facade.spawn('whisper-cli', []);
162
+ proc.onError((e) => errors.push(e));
163
+
164
+ await Promise.resolve();
165
+ await Promise.resolve();
166
+ await Promise.resolve();
167
+
168
+ // The real spawn succeeded (host daemon is the actual enforcer), so no
169
+ // error should fire even though the advisory ensure() rejected.
170
+ expect(errors).toHaveLength(0);
171
+ });
172
+
173
+ it('a callback registered from inside another onError callback is not dropped', async () => {
174
+ const invoke = async (ch: string): Promise<unknown> => {
175
+ if (ch === 'process.spawn') throw new Error('tool not granted');
176
+ return undefined;
177
+ };
178
+ const subscribe = () => () => {};
179
+
180
+ const facade = createProcessFacade(invoke, subscribe, async () => {});
181
+ let outer = 0;
182
+ let nested = 0;
183
+
184
+ const proc = facade.spawn('whisper-cli', []);
185
+ proc.onError(() => {
186
+ outer++;
187
+ // Registering from inside another onError callback is exactly the
188
+ // ordering where a naive "set spawnFailure after the forEach" would
189
+ // drop this nested callback (forEach caches length before this push
190
+ // ever runs, and the flag wouldn't be set yet either).
191
+ proc.onError(() => {
192
+ nested++;
193
+ });
194
+ });
195
+
196
+ await Promise.resolve();
197
+ await Promise.resolve();
198
+ await Promise.resolve();
199
+
200
+ expect(outer).toBe(1);
201
+ expect(nested).toBe(1);
202
+ });
54
203
  });
@@ -36,7 +36,13 @@ export function createProcessFacade(
36
36
  ): ProcessFacade {
37
37
  return {
38
38
  spawn(cmd, args, opts) {
39
- void ensure(); // fire the permission check; the host daemon also enforces
39
+ // Fire the permission check; the host daemon also enforces it inside
40
+ // `invoke('process.spawn', …)` below, so a rejection here is redundant
41
+ // with (and always followed by) that call's own rejection — which IS
42
+ // routed to `onError` below. Swallow it here purely to avoid an
43
+ // unhandled-rejection warning; don't double-report.
44
+ void ensure().catch(() => {});
45
+
40
46
  const cbs = {
41
47
  stdout: [] as ((c: string) => void)[],
42
48
  stderr: [] as ((c: string) => void)[],
@@ -45,31 +51,60 @@ export function createProcessFacade(
45
51
  };
46
52
  const unsubs: (() => void)[] = [];
47
53
 
48
- const ready = invoke('process.spawn', { cmd, args, opts }).then((r) => {
49
- const id = (r as { id: string }).id;
50
- unsubs.push(
51
- subscribe(`process.stdout:${id}`, (d) =>
52
- cbs.stdout.forEach((cb) => cb((d as { chunk: string }).chunk)),
53
- ),
54
- );
55
- unsubs.push(
56
- subscribe(`process.stderr:${id}`, (d) =>
57
- cbs.stderr.forEach((cb) => cb((d as { chunk: string }).chunk)),
58
- ),
59
- );
60
- unsubs.push(
61
- subscribe(`process.error:${id}`, (d) =>
62
- cbs.error.forEach((cb) => cb((d as { err: string }).err)),
63
- ),
64
- );
65
- unsubs.push(
66
- subscribe(`process.exit:${id}`, (d) => {
67
- cbs.exit.forEach((cb) => cb((d as { code: number | null }).code));
68
- unsubs.forEach((u) => u());
69
- }),
70
- );
71
- return id;
72
- });
54
+ // Set once the spawn invoke rejects (e.g. the permission gate denies
55
+ // the binary). Lets `onError` replay the failure to a callback that
56
+ // registers AFTER the rejection already happened — callers may call
57
+ // `spawn()` and register `onError` in the same synchronous tick, but
58
+ // nothing guarantees that ordering, so a purely "fire once at rejection
59
+ // time" callback would silently drop late subscribers.
60
+ let spawnFailure: string | undefined;
61
+
62
+ const ready = invoke('process.spawn', { cmd, args, opts })
63
+ .then((r) => {
64
+ const id = (r as { id: string }).id;
65
+ unsubs.push(
66
+ subscribe(`process.stdout:${id}`, (d) =>
67
+ cbs.stdout.forEach((cb) => cb((d as { chunk: string }).chunk)),
68
+ ),
69
+ );
70
+ unsubs.push(
71
+ subscribe(`process.stderr:${id}`, (d) =>
72
+ cbs.stderr.forEach((cb) => cb((d as { chunk: string }).chunk)),
73
+ ),
74
+ );
75
+ unsubs.push(
76
+ subscribe(`process.error:${id}`, (d) =>
77
+ cbs.error.forEach((cb) => cb((d as { err: string }).err)),
78
+ ),
79
+ );
80
+ unsubs.push(
81
+ subscribe(`process.exit:${id}`, (d) => {
82
+ cbs.exit.forEach((cb) => cb((d as { code: number | null }).code));
83
+ unsubs.forEach((u) => u());
84
+ }),
85
+ );
86
+ return id;
87
+ })
88
+ .catch((e: unknown) => {
89
+ // The spawn itself never happened — e.g. the permission gate
90
+ // denied the binary (`not a bundled tool` / `tool not granted`).
91
+ // That's an `error`, never an `exit`: no process ever started, so
92
+ // there is no exit code to report, and a caller that treated a
93
+ // denial as "exited" could log a bogus code. Callbacks already
94
+ // registered get notified now; `onError` below replays this to
95
+ // any callback registered later, exactly once each.
96
+ spawnFailure = e instanceof Error ? e.message : String(e);
97
+ cbs.error.forEach((cb) => cb(spawnFailure!));
98
+ throw e; // keep `ready` rejected so `withId` below stays a no-op
99
+ });
100
+ // `ready` must stay rejected (so every `withId` call below is a no-op),
101
+ // but that leaves it a dangling rejected promise whenever a caller
102
+ // never invokes write/kill/closeStdin — which Node/browsers flag as an
103
+ // unhandled rejection even though the failure was already delivered via
104
+ // `onError` above. Attach a standing no-op handler to silence that;
105
+ // it doesn't consume `ready`'s rejection for the `withId` chain below,
106
+ // since each `.then()/.catch()` call derives its own independent promise.
107
+ ready.catch(() => {});
73
108
  const withId = (fn: (id: string) => void) =>
74
109
  void ready.then(fn).catch(() => {});
75
110
 
@@ -77,7 +112,31 @@ export function createProcessFacade(
77
112
  onStdout: (cb) => cbs.stdout.push(cb),
78
113
  onStderr: (cb) => cbs.stderr.push(cb),
79
114
  onExit: (cb) => cbs.exit.push(cb),
80
- onError: (cb) => cbs.error.push(cb),
115
+ onError: (cb) => {
116
+ cbs.error.push(cb);
117
+ // Late subscriber: the spawn already failed before this callback
118
+ // registered, so it missed the `.catch` above's `forEach` (that
119
+ // `forEach` only visits entries `cbs.error` held at the time it
120
+ // started iterating — a push during/after it, e.g. from another
121
+ // `onError` callback, is never visited). Replay the failure here
122
+ // instead. This does NOT double-deliver to a callback that was
123
+ // already notified: `spawnFailure` is set at line 98, BEFORE the
124
+ // `forEach` at line 99 runs — so by the time any `onError` call
125
+ // happens, either (a) the failure hasn't happened yet and this
126
+ // `if` is skipped, or (b) it has, and every callback in
127
+ // `cbs.error` before this `push()` was already invoked by that
128
+ // `forEach`, so only the newly-pushed one still needs it. That
129
+ // ordering (set-before-forEach) is load-bearing — swap it and a
130
+ // callback registered while inside another `onError` callback is
131
+ // silently dropped instead of replayed.
132
+ if (spawnFailure !== undefined) cb(spawnFailure);
133
+ },
134
+ // write/kill/closeStdin after a failed spawn: `withId` never calls
135
+ // `fn` because `ready` stays rejected, so these are silent no-ops.
136
+ // That's intentional, not an oversight — the process never existed,
137
+ // there's nothing to kill/write/close, and the caller already got
138
+ // the failure via `onError`; surfacing a second error here would
139
+ // just be a duplicate signal for the same event.
81
140
  write: (data) =>
82
141
  withId((id) => void invoke('process.write', { id, data })),
83
142
  closeStdin: () =>