@burdenoff/fe-libs 2026.1001.2 → 2026.1001.4

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 (55) hide show
  1. package/dist/shared/assistant/conversationDeleteCommand.d.ts +31 -0
  2. package/dist/shared/assistant/conversationDeleteCommand.d.ts.map +1 -0
  3. package/dist/shared/assistant/conversationDeleteCommand.js +14 -0
  4. package/dist/shared/assistant/history/historyControls.d.ts +9 -0
  5. package/dist/shared/assistant/history/historyControls.d.ts.map +1 -1
  6. package/dist/shared/assistant/history/historyControls.js +7 -1
  7. package/dist/shared/assistant/history/historyList.d.ts +10 -0
  8. package/dist/shared/assistant/history/historyList.d.ts.map +1 -1
  9. package/dist/shared/assistant/history/historyList.js +33 -28
  10. package/dist/shared/assistant/history/useHistoryRowControls.d.ts +13 -0
  11. package/dist/shared/assistant/history/useHistoryRowControls.d.ts.map +1 -0
  12. package/dist/shared/assistant/history/useHistoryRowControls.js +24 -0
  13. package/dist/shared/assistant/i18nManifest.d.ts +36 -0
  14. package/dist/shared/assistant/i18nManifest.d.ts.map +1 -1
  15. package/dist/shared/assistant/i18nManifest.js +36 -0
  16. package/dist/shared/assistant/index.d.ts +6 -0
  17. package/dist/shared/assistant/index.d.ts.map +1 -1
  18. package/dist/shared/assistant/localUiCommands.d.ts +8 -0
  19. package/dist/shared/assistant/localUiCommands.d.ts.map +1 -1
  20. package/dist/shared/assistant/localUiCommands.js +20 -19
  21. package/dist/shared/assistant/ui/AIAssistantPanel.d.ts.map +1 -1
  22. package/dist/shared/assistant/ui/AIAssistantPanel.js +67 -66
  23. package/dist/shared/assistant/ui/AssistantChatArea.d.ts.map +1 -1
  24. package/dist/shared/assistant/ui/AssistantChatArea.js +978 -891
  25. package/dist/shared/assistant/ui/AssistantConversationFilters.d.ts +12 -0
  26. package/dist/shared/assistant/ui/AssistantConversationFilters.d.ts.map +1 -0
  27. package/dist/shared/assistant/ui/AssistantConversationFilters.js +92 -0
  28. package/dist/shared/assistant/ui/AssistantConversationList.d.ts +9 -0
  29. package/dist/shared/assistant/ui/AssistantConversationList.d.ts.map +1 -1
  30. package/dist/shared/assistant/ui/AssistantConversationList.js +314 -214
  31. package/dist/shared/assistant/ui/AssistantDeleteConversationDialog.d.ts +59 -0
  32. package/dist/shared/assistant/ui/AssistantDeleteConversationDialog.d.ts.map +1 -0
  33. package/dist/shared/assistant/ui/AssistantDeleteConversationDialog.js +84 -0
  34. package/dist/shared/assistant/ui/AssistantHistoryNotices.d.ts +18 -0
  35. package/dist/shared/assistant/ui/AssistantHistoryNotices.d.ts.map +1 -1
  36. package/dist/shared/assistant/ui/AssistantHistoryNotices.js +76 -29
  37. package/dist/shared/assistant/ui/conversationDelete.d.ts +18 -0
  38. package/dist/shared/assistant/ui/conversationDelete.d.ts.map +1 -0
  39. package/dist/shared/assistant/ui/conversationDelete.js +16 -0
  40. package/dist/shared/assistant/ui/conversationDeleteDialog.d.ts +32 -0
  41. package/dist/shared/assistant/ui/conversationDeleteDialog.d.ts.map +1 -0
  42. package/dist/shared/assistant/ui/conversationDeleteDialog.js +54 -0
  43. package/dist/shared/assistant/ui/useVoiceSession.d.ts +5 -0
  44. package/dist/shared/assistant/ui/useVoiceSession.d.ts.map +1 -1
  45. package/dist/shared/assistant/ui/useVoiceSession.js +129 -121
  46. package/dist/shared/assistant/voice/turnVisibility.d.ts +9 -0
  47. package/dist/shared/assistant/voice/turnVisibility.d.ts.map +1 -1
  48. package/dist/shared/assistant/voice/voiceReopen.d.ts +134 -11
  49. package/dist/shared/assistant/voice/voiceReopen.d.ts.map +1 -1
  50. package/dist/shared/assistant/voice/voiceReopen.js +83 -25
  51. package/dist/shared/assistant/voice/voiceSession.d.ts +10 -0
  52. package/dist/shared/assistant/voice/voiceSession.d.ts.map +1 -1
  53. package/dist/shared/assistant/voice/voiceSession.js +6 -0
  54. package/dist/shared-assistant.js +170 -164
  55. package/package.json +1 -1
@@ -2,156 +2,162 @@ import { useVoiceInput as e } from "../voice/useVoiceInput.js";
2
2
  import { INITIAL_SESSION as t, INTERRUPT_MAX_MS as ee, NOTHING_HEARD as n, SILENCE_SUBMIT_MS as te, advanceHeardClock as ne, isCapturing as r, isSubmittable as re, planListeningHold as ie, planReplyConsumption as ae, planVoiceAnnouncement as oe, spokenSoFar as i, voiceSessionReducer as se } from "../voice/voiceSession.js";
3
3
  import { turnClockMs as a } from "../turnTimeline.js";
4
4
  import { useAssistantSpeech as ce } from "./AssistantSpeechProvider.js";
5
- import { useCallback as o, useEffect as s, useLayoutEffect as c, useMemo as le, useReducer as ue, useRef as l, useState as u } from "react";
5
+ import { useCallback as o, useEffect as s, useLayoutEffect as le, useMemo as ue, useReducer as de, useRef as c, useState as l } from "react";
6
6
  //#region src/shared/assistant/ui/useVoiceSession.ts
7
- var de = 4e3, fe = 2500, pe = 5e3, me = 12e3, he = "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=";
8
- function d({ onSubmit: d, latestReply: f, language: ge }) {
9
- let p = l(null), [m, h] = ue(o((e, t) => se(e, t, p.current), []), t), g = ce(), { setSessionSpeaking: _, setSessionMicOpen: v, speakingMessageId: y, stop: b } = g, x = l(g.toggle), S = g.toggle;
10
- c(() => {
11
- x.current = S;
12
- }, [S]);
13
- let C = l(null), w = l({}), T = l(n), E = l(null), D = l(!1), [O, k] = u(null), [A, j] = u(!1), [M, N] = u(!1), P = l(m);
14
- P.current = m;
15
- let F = l([]), I = l(null), L = l(!1), R = o(() => {
16
- F.current = [], I.current = null, L.current = !1;
17
- }, []), z = o(() => {
7
+ var fe = 4e3, pe = 2500, me = 5e3, he = 12e3, ge = "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=";
8
+ function u({ onSubmit: u, latestReply: d, language: _e }) {
9
+ let f = c(null), [p, m] = de(o((e, t) => se(e, t, f.current), []), t), h = ce(), { setSessionSpeaking: g, setSessionMicOpen: _, speakingMessageId: v, stop: y } = h, b = c(h.toggle), x = h.toggle;
10
+ le(() => {
11
+ b.current = x;
12
+ }, [x]);
13
+ let S = c(null), C = c({}), w = c(n), T = c(null), E = c(!1), [D, O] = l(null), [k, A] = l(!1), [j, M] = l(!1), N = c(p);
14
+ N.current = p;
15
+ let P = c([]), F = c(null), I = c(!1), L = o(() => {
16
+ P.current = [], F.current = null, I.current = !1;
17
+ }, []), R = o(() => {
18
18
  for (;;) {
19
- let e = F.current.shift();
19
+ let e = P.current.shift();
20
20
  if (!e) return !1;
21
21
  if (!(e.stillWanted && !e.stillWanted())) {
22
- I.current = e, p.current = p.current ? `${p.current} ${e.text}` : e.text;
22
+ F.current = e, f.current = f.current ? `${f.current} ${e.text}` : e.text;
23
23
  try {
24
- x.current(e.id, e.text, e.lang);
24
+ b.current(e.id, e.text, e.lang);
25
25
  } catch {
26
- I.current = null;
26
+ F.current = null;
27
27
  continue;
28
28
  }
29
29
  return !0;
30
30
  }
31
31
  }
32
- }, []), B = l(!1), V = o((e) => {
32
+ }, []), z = c(!1), B = o((e) => {
33
33
  let t = ie({
34
34
  requested: e,
35
- held: B.current,
36
- muted: P.current.muted
35
+ held: z.current,
36
+ muted: N.current.muted
37
37
  });
38
- B.current = t.held, t.mute !== null && h({
38
+ z.current = t.held, t.mute !== null && m({
39
39
  type: "mute",
40
40
  muted: t.mute
41
41
  });
42
+ }, []), V = o((e) => {
43
+ let t = oe(N.current);
44
+ t !== "drop" && (F.current?.id === e.id || P.current.some((t) => t.id === e.id) || (P.current.push(e), t === "start" && !I.current && (I.current = !0, m({ type: "announce" }))));
42
45
  }, []), H = o((e) => {
43
- let t = oe(P.current);
44
- t !== "drop" && (I.current?.id === e.id || F.current.some((t) => t.id === e.id) || (F.current.push(e), t === "start" && !L.current && (L.current = !0, h({ type: "announce" }))));
45
- }, []), U = l(f);
46
- U.current = f, s(() => {
47
- if (m.phase === "speaking") {
48
- p.current = f?.text ?? null;
46
+ if (N.current.phase !== "thinking") {
47
+ V(e);
49
48
  return;
50
49
  }
51
- if (p.current === null) return;
50
+ P.current.push(e), I.current = !0, m({ type: "answered-locally" });
51
+ }, [V]), U = c(d);
52
+ U.current = d, s(() => {
53
+ if (p.phase === "speaking") {
54
+ f.current = d?.text ?? null;
55
+ return;
56
+ }
57
+ if (f.current === null) return;
52
58
  let e = setTimeout(() => {
53
- p.current = null;
54
- }, fe);
59
+ f.current = null;
60
+ }, pe);
55
61
  return () => clearTimeout(e);
56
- }, [m.phase, f]);
62
+ }, [p.phase, d]);
57
63
  let W = o((e, t) => {
58
- h({
64
+ m({
59
65
  type: "speech",
60
66
  transcript: e,
61
67
  final: t
62
68
  });
63
- }, []), G = l(0), K = o(() => {
64
- R(), b(), G.current = Date.now(), j(!0), h({ type: "interrupt" });
65
- }, [b, R]), q = e({
69
+ }, []), G = c(0), K = o(() => {
70
+ L(), y(), G.current = Date.now(), A(!0), m({ type: "interrupt" });
71
+ }, [y, L]), q = e({
66
72
  onFinalize: o((e) => W(e, !0), [W]),
67
- language: ge
73
+ language: _e
68
74
  });
69
75
  s(() => {
70
76
  q.interim && W(q.interim, !1);
71
77
  }, [q.interim, W]), s(() => {
72
- if (C.current && clearTimeout(C.current), N(!1), T.current = ne(T.current, m, a()), m.phase !== "listening" || m.muted || !re(i(m))) return;
73
- let e = setTimeout(() => N(!0), 800), t = a();
74
- return C.current = setTimeout(() => {
75
- w.current = {
76
- speechEnded: T.current.at ?? t,
78
+ if (S.current && clearTimeout(S.current), M(!1), w.current = ne(w.current, p, a()), p.phase !== "listening" || p.muted || !re(i(p))) return;
79
+ let e = setTimeout(() => M(!0), 800), t = a();
80
+ return S.current = setTimeout(() => {
81
+ C.current = {
82
+ speechEnded: w.current.at ?? t,
77
83
  graceStarted: t,
78
84
  graceEnded: a()
79
- }, h({ type: "silence" });
85
+ }, m({ type: "silence" });
80
86
  }, te), () => {
81
- clearTimeout(e), C.current && clearTimeout(C.current);
87
+ clearTimeout(e), S.current && clearTimeout(S.current);
82
88
  };
83
- }, [m]), s(() => {
84
- if (!A) return;
85
- let e = Date.now() - G.current, t = q.isListening ? Math.max(0, 500 - e) : Math.max(0, ee - e), n = setTimeout(() => j(!1), t);
89
+ }, [p]), s(() => {
90
+ if (!k) return;
91
+ let e = Date.now() - G.current, t = q.isListening ? Math.max(0, 500 - e) : Math.max(0, ee - e), n = setTimeout(() => A(!1), t);
86
92
  return () => clearTimeout(n);
87
- }, [A, q.isListening]);
88
- let J = l("");
89
- m.phase === "listening" && (J.current = i(m)), s(() => {
90
- if (m.phase !== "thinking") return;
91
- v(!1), R();
93
+ }, [k, q.isListening]);
94
+ let J = c("");
95
+ p.phase === "listening" && (J.current = i(p)), s(() => {
96
+ if (p.phase !== "thinking") return;
97
+ _(!1), L();
92
98
  let e = J.current.trim();
93
- e && d(e, Y.current ?? `vs-orphan-${Date.now().toString(36)}`, w.current), w.current = {}, T.current = n;
94
- }, [m.phase]), s(() => {
95
- let e = ae(f?.id, m.phase, E.current);
96
- e.claim && (E.current = f?.id ?? null, e.advance && f && h({
99
+ e && u(e, Y.current ?? `vs-orphan-${Date.now().toString(36)}`, C.current), C.current = {}, w.current = n;
100
+ }, [p.phase]), s(() => {
101
+ let e = ae(d?.id, p.phase, T.current);
102
+ e.claim && (T.current = d?.id ?? null, e.advance && d && m({
97
103
  type: "reply",
98
- text: f.text
104
+ text: d.text
99
105
  }));
100
- }, [f, m.phase]), s(() => {
101
- let e = y !== null;
102
- D.current && !e && (I.current = null, (P.current.phase !== "speaking" || !z()) && h({ type: "speech-ended" })), D.current = e;
103
- }, [y, z]), s(() => {
104
- m.phase !== "listening" && k((e) => e === "nothing-heard" ? null : e);
105
- }, [m.phase]), s(() => {
106
- if (m.phase !== "speaking" || y !== null) return;
106
+ }, [d, p.phase]), s(() => {
107
+ let e = v !== null;
108
+ E.current && !e && (F.current = null, (N.current.phase !== "speaking" || !R()) && m({ type: "speech-ended" })), E.current = e;
109
+ }, [v, R]), s(() => {
110
+ p.phase !== "listening" && O((e) => e === "nothing-heard" ? null : e);
111
+ }, [p.phase]), s(() => {
112
+ if (p.phase !== "speaking" || v !== null) return;
107
113
  let e = setTimeout(() => {
108
- D.current || (R(), h({ type: "speech-ended" }));
109
- }, de);
114
+ E.current || (L(), m({ type: "speech-ended" }));
115
+ }, fe);
110
116
  return () => clearTimeout(e);
111
117
  }, [
112
- m.phase,
113
- y,
114
- R
118
+ p.phase,
119
+ v,
120
+ L
115
121
  ]), s(() => {
116
- let e = r(m);
117
- v(e), e ? q.isActive || q.start("auto") : q.isActive && q.stop();
122
+ let e = r(p);
123
+ _(e), e ? q.isActive || q.start("auto") : q.isActive && q.stop();
118
124
  }, [
119
- m,
125
+ p,
120
126
  q,
121
- v
127
+ _
122
128
  ]), s(() => {
123
- m.phase !== "speaking" || !L.current || (L.current = !1, z() || h({ type: "speech-ended" }));
124
- }, [m.phase, z]), s(() => {
125
- q.error === "not-allowed" || q.error === "audio-capture" ? h({
129
+ p.phase !== "speaking" || !I.current || (I.current = !1, R() || m({ type: "speech-ended" }));
130
+ }, [p.phase, R]), s(() => {
131
+ q.error === "not-allowed" || q.error === "audio-capture" ? m({
126
132
  type: "fail",
127
133
  error: "mic-denied"
128
- }) : q.error === "language-not-supported" ? h({
134
+ }) : q.error === "language-not-supported" ? m({
129
135
  type: "fail",
130
136
  error: "no-speech-support"
131
- }) : q.error === "network" ? k("network") : q.error === "unknown" && k("recognition-failed");
137
+ }) : q.error === "network" ? O("network") : q.error === "unknown" && O("recognition-failed");
132
138
  }, [q.error]), s(() => {
133
- q.error !== null || !q.isListening || k((e) => e === "network" || e === "recognition-failed" ? null : e);
139
+ q.error !== null || !q.isListening || O((e) => e === "network" || e === "recognition-failed" ? null : e);
134
140
  }, [q.error, q.isListening]), s(() => {
135
- if (q.isListening && k((e) => e === "not-capturing" ? null : e), !r(m) || q.isListening) return;
136
- let e = setTimeout(() => k("not-capturing"), pe);
141
+ if (q.isListening && O((e) => e === "not-capturing" ? null : e), !r(p) || q.isListening) return;
142
+ let e = setTimeout(() => O("not-capturing"), me);
137
143
  return () => clearTimeout(e);
138
- }, [m, q.isListening]), s(() => {
139
- if (m.phase !== "listening" || m.muted || !q.isListening || i(m) !== "") return;
140
- let e = setTimeout(() => k("nothing-heard"), me);
144
+ }, [p, q.isListening]), s(() => {
145
+ if (p.phase !== "listening" || p.muted || !q.isListening || i(p) !== "") return;
146
+ let e = setTimeout(() => O("nothing-heard"), he);
141
147
  return () => clearTimeout(e);
142
- }, [m, q.isListening]), s(() => {
143
- i(m) !== "" && k(null);
144
- }, [m]);
145
- let Y = l(null), X = o(() => {
146
- if (j(!1), N(!1), R(), B.current = !1, !q.isSupported) {
147
- h({
148
+ }, [p, q.isListening]), s(() => {
149
+ i(p) !== "" && O(null);
150
+ }, [p]);
151
+ let Y = c(null), X = o(() => {
152
+ if (A(!1), M(!1), L(), z.current = !1, !q.isSupported) {
153
+ m({
148
154
  type: "fail",
149
155
  error: "no-speech-support"
150
156
  });
151
157
  return;
152
158
  }
153
- if (g.isBlocked) {
154
- h({
159
+ if (h.isBlocked) {
160
+ m({
155
161
  type: "fail",
156
162
  error: "no-voice-support"
157
163
  });
@@ -166,63 +172,65 @@ function d({ onSubmit: d, latestReply: f, language: ge }) {
166
172
  } catch {}
167
173
  try {
168
174
  if (typeof window < "u" && typeof window.Audio == "function") {
169
- let e = new window.Audio(he);
175
+ let e = new window.Audio(ge);
170
176
  e.volume = 0, e.play().catch(() => {});
171
177
  }
172
178
  } catch {}
173
- _(!0), k(null), E.current = U.current?.id ?? null, Y.current = `vs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`, h({ type: "start" });
179
+ g(!0), O(null), T.current = U.current?.id ?? null, Y.current = `vs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`, m({ type: "start" });
174
180
  }, [
175
181
  q.isSupported,
176
- g.isBlocked,
177
- _,
178
- R
182
+ h.isBlocked,
183
+ g,
184
+ L
179
185
  ]), Z = o(() => {
180
- j(!1), N(!1), R(), B.current = !1, Y.current = null, h({ type: "end" }), b(), _(!1);
186
+ A(!1), M(!1), L(), z.current = !1, Y.current = null, m({ type: "end" }), y(), g(!1);
181
187
  }, [
182
- b,
183
- _,
184
- R
188
+ y,
189
+ g,
190
+ L
185
191
  ]), Q = o((e) => Y.current === e, []), $ = o(() => {
186
- h({
192
+ m({
187
193
  type: "mute",
188
- muted: !P.current.muted
194
+ muted: !N.current.muted
189
195
  });
190
196
  }, []);
191
197
  return s(() => () => {
192
- _(!1), v(!1);
193
- }, [_, v]), le(() => ({
194
- session: m,
195
- notice: O,
196
- spokenChunks: g.spokenChunks,
198
+ g(!1), _(!1);
199
+ }, [g, _]), ue(() => ({
200
+ session: p,
201
+ notice: D,
202
+ spokenChunks: h.spokenChunks,
197
203
  capturing: q.isListening,
198
- active: m.phase !== "idle" && (m.phase !== "ended" || m.error !== null),
199
- interrupting: A,
200
- awaitingEndpoint: M,
201
- available: q.isSupported && !g.isBlocked,
204
+ active: p.phase !== "idle" && (p.phase !== "ended" || p.error !== null),
205
+ interrupting: k,
206
+ awaitingEndpoint: j,
207
+ available: q.isSupported && !h.isBlocked,
202
208
  start: X,
203
209
  end: Z,
204
210
  isCurrentSession: Q,
205
211
  toggleMute: $,
206
212
  interrupt: K,
207
- announce: H,
208
- holdListening: V
213
+ announce: V,
214
+ holdListening: B,
215
+ answerLocally: H
209
216
  }), [
210
- m,
211
- O,
212
- A,
213
- M,
217
+ p,
218
+ D,
219
+ k,
220
+ j,
214
221
  q.isListening,
215
222
  q.isSupported,
216
- g.isBlocked,
217
- g.spokenChunks,
223
+ h.isBlocked,
224
+ h.spokenChunks,
218
225
  X,
219
226
  Z,
220
227
  Q,
221
228
  $,
222
229
  K,
223
- H,
224
- V
230
+ V,
231
+ B,
232
+ H
225
233
  ]);
226
234
  }
227
235
  //#endregion
228
- export { d as useVoiceSession };
236
+ export { u as useVoiceSession };
@@ -20,6 +20,15 @@
20
20
  * we left off" work. Genuinely separate backend sessions are a larger change
21
21
  * and would cost that.
22
22
  *
23
+ * ★★ BOFF-8159 S5 (operator, 2026-09-30: VOICE IS ALWAYS SEPARATE): a live
24
+ * voice session now speaks into a conversation of its OWN, created at its first
25
+ * spoken entry (`planVoiceSessionStart`), and never into an unrelated chat. The
26
+ * split still matters wherever both channels meet in one conversation: a
27
+ * session that took over an empty placeholder and was later typed into, an
28
+ * entry attached to the open conversation for a waiting proposal or a running
29
+ * turn, and every mixed conversation saved before S5 (shown filtered, never
30
+ * split).
31
+ *
23
32
  * ★ Product-agnostic by construction: it knows about turns and surfaces, never
24
33
  * about crops, workflows or nodes. Nothing here needs to change for a product
25
34
  * whose modes are `fluidgrids-node` rather than `api-calls`.
@@ -1 +1 @@
1
- {"version":3,"file":"turnVisibility.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/voice/turnVisibility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,UAAU,QAAQ;IAChB,QAAQ,CAAC,EAAE,CAAC;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;CACjG;AAED,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAEtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAGtE;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG;IAAE,KAAK,CAAC,EAAE,IAAI,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,CAEpG;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAEzE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAE9E;AAED,yEAAyE;AACzE,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC;;;OAGG;IACH,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,KAAK,EAAE,CAAC,EAAE,CAAC;CACZ;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAE9F;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,EACzC,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,GAC3C,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAWxB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;;;OASG;IACH,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B"}
1
+ {"version":3,"file":"turnVisibility.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/voice/turnVisibility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,UAAU,QAAQ;IAChB,QAAQ,CAAC,EAAE,CAAC;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;CACjG;AAED,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAEtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAGtE;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG;IAAE,KAAK,CAAC,EAAE,IAAI,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,CAEpG;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAEzE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAE9E;AAED,yEAAyE;AACzE,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC;;;OAGG;IACH,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,KAAK,EAAE,CAAC,EAAE,CAAC;CACZ;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAE9F;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,EACzC,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,GAC3C,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAWxB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;;;OASG;IACH,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B"}
@@ -8,13 +8,131 @@
8
8
  * read-only transcript", "end the session" and "drop a spoken turn". None can
9
9
  * start one.
10
10
  */
11
+ /**
12
+ * ★★ BOFF-8159 S5 — VOICE IS ALWAYS SEPARATE (operator, 2026-09-30). A live
13
+ * session that has not yet spoken into a conversation of its OWN.
14
+ *
15
+ * The session used to adopt whatever conversation was open when the mic was
16
+ * tapped — including one restored from storage days ago, since
17
+ * `activeConversationId` is persisted — so a reload left the next spoken
18
+ * exchange (and its spoken local commands) inside an unrelated typed thread.
19
+ * Now nothing is adopted: the session's conversation is created LAZILY, at
20
+ * its first spoken entry, exactly as a send with no conversation open creates
21
+ * one. A session that says nothing leaves no conversation, no save and no
22
+ * tombstone behind.
23
+ *
24
+ * Conversation identity is separate from sandbox, runtime and model-session
25
+ * identity: the sandbox for the new conversation's mode is warmed at the mic
26
+ * tap (and reused when warm), but no conversation id is ever shared to save
27
+ * the session's start-up.
28
+ */
29
+ export interface VoiceSessionAwaitingOwn {
30
+ readonly pending: "own";
31
+ /**
32
+ * The conversation on screen when the session started (null: none). It is
33
+ * not the session's: opening another one before the session has its own
34
+ * ends the session, as a switch always has.
35
+ */
36
+ readonly openAtStart: string | null;
37
+ }
11
38
  /**
12
39
  * The conversation a live voice session belongs to.
13
40
  *
14
- * `undefined`: no session is live. `null`: a session is live but has not
15
- * spoken into any conversation yet — its first turn creates one.
41
+ * `undefined`: no session is live. A string: the session's conversation — its
42
+ * own, or the empty placeholder it took over. `VoiceSessionAwaitingOwn`: live,
43
+ * with no conversation of its own yet (`planVoiceSpokenEntry` decides where
44
+ * each entry goes until it has one). `null`: the pre-S5 "adopt the next active
45
+ * conversation" value, kept for compatibility; no start path writes it now.
46
+ */
47
+ export type VoiceSessionBinding = string | null | undefined | VoiceSessionAwaitingOwn;
48
+ /** The session is live and still awaits a conversation of its own. */
49
+ export declare function isAwaitingOwnConversation(binding: VoiceSessionBinding): binding is VoiceSessionAwaitingOwn;
50
+ /**
51
+ * The conversation a binding names: the session's own, or — while it awaits
52
+ * one — the conversation that was on screen when it started.
53
+ */
54
+ export declare function voiceBindingConversationId(binding: VoiceSessionBinding): string | null;
55
+ /** The open conversation, as the voice start and entry rules read it. */
56
+ export interface VoiceOpenConversation {
57
+ id: string;
58
+ /**
59
+ * An empty, unsent placeholder this device would save as a new
60
+ * conversation (`isReusableHistoryDraft`): nothing in it to keep apart.
61
+ */
62
+ reusableDraft: boolean;
63
+ /** Its 5a proposals still waiting for a person's button. */
64
+ liveProposals: number;
65
+ /** One of its turns is in flight. */
66
+ turnInFlight: boolean;
67
+ }
68
+ /**
69
+ * Where a new voice session goes: a conversation of its own, or — the two
70
+ * operator exceptions — the open one.
71
+ *
72
+ * - `draft`: an empty unsent placeholder BECOMES the voice conversation.
73
+ * - `proposal` / `turn`: a waiting 5a proposal or a turn in flight keeps the
74
+ * session attached to the open conversation, for that transaction only: a
75
+ * spoken correction, decline or "yes" still binds to the card there
76
+ * (`planWriteReply`), and nothing is withdrawn by leaving. Once it resolves,
77
+ * the session's next entry goes to a conversation of its own.
78
+ */
79
+ export type VoiceStartPlan = {
80
+ kind: "own";
81
+ } | {
82
+ kind: "join";
83
+ conversationId: string;
84
+ why: "draft" | "proposal" | "turn";
85
+ };
86
+ /**
87
+ * ★★ BOFF-8159 S5: the rules, in order. Pure; the panel applies them at the
88
+ * mic tap (`voiceBindingAtStart`) and again at every spoken entry while the
89
+ * session has no conversation of its own (`planVoiceSpokenEntry`).
90
+ *
91
+ * 1. Nothing open: its own (created at the first spoken entry).
92
+ * 2. An empty reusable placeholder: it becomes the session's.
93
+ * 3. A waiting proposal or a turn in flight: attached, for the transaction.
94
+ * 4. Anything else — a typed chat, a voice conversation of an earlier
95
+ * session, one restored from storage: its own. There is no "recently
96
+ * used" window (the operator overrode it).
97
+ */
98
+ export declare function planVoiceSessionStart(open: VoiceOpenConversation | null): VoiceStartPlan;
99
+ /**
100
+ * The binding a session starts with. Only the placeholder is taken over at
101
+ * the tap; every other plan — its own, or attached for a transaction — starts
102
+ * AWAITING its own, so nothing is created or made active at the tap (and
103
+ * nothing is withdrawn from the conversation on screen), and an attachment
104
+ * lasts no longer than the transaction that caused it.
16
105
  */
17
- export type VoiceSessionBinding = string | null | undefined;
106
+ export declare function voiceBindingAtStart(plan: VoiceStartPlan, openAtStart: string | null): VoiceSessionBinding;
107
+ /**
108
+ * Where ONE spoken entry (a turn, or a local command) of a session still
109
+ * awaiting its own conversation goes — decided with what the open
110
+ * conversation holds NOW, not at the tap: a proposal restored after a reload
111
+ * may have arrived since (BOFF-8128), and a transaction that was open at the
112
+ * tap may have resolved.
113
+ *
114
+ * - `own`: start the session's conversation now, the way a send with no
115
+ * conversation open starts one (Auto routing and its consent hold apply
116
+ * unchanged), and bind the session to it.
117
+ * - `adopt`: the open conversation is an empty placeholder; it becomes the
118
+ * session's.
119
+ * - `attach`: into the open conversation, for its waiting proposal or running
120
+ * turn; the session still awaits its own.
121
+ * - `gone`: another conversation was opened since the session started — the
122
+ * session is ending, and the entry goes nowhere.
123
+ */
124
+ export type VoiceSpokenEntry = {
125
+ kind: "own";
126
+ } | {
127
+ kind: "adopt";
128
+ conversationId: string;
129
+ } | {
130
+ kind: "attach";
131
+ conversationId: string;
132
+ } | {
133
+ kind: "gone";
134
+ };
135
+ export declare function planVoiceSpokenEntry(binding: VoiceSessionAwaitingOwn, activeConversationId: string | null, open: VoiceOpenConversation | null): VoiceSpokenEntry;
18
136
  /**
19
137
  * Keep a live voice session in the conversation it started in.
20
138
  *
@@ -25,18 +143,20 @@ export type VoiceSessionBinding = string | null | undefined;
25
143
  * answers aloud — into a conversation the person was no longer talking to. The
26
144
  * session now ends instead; nothing is moved.
27
145
  *
28
- * A session that started with no conversation adopts the first one that
29
- * becomes active — the one its own first turn creates. A conversation the
30
- * session's own turn starts (the send path records it as the new binding
31
- * first) is not a switch — provided the caller passes where the store is NOW:
32
- * the send moves the store and the binding together, after the render the
33
- * caller's effect belongs to.
146
+ * A session awaiting its own conversation (BOFF-8159 S5) is bound to the one
147
+ * its own first entry creates. A conversation the session's own turn starts
148
+ * (the send path records it as the new binding first) is not a switch —
149
+ * provided the caller passes where the store is NOW: the send moves the store
150
+ * and the binding together, after the render the caller's effect belongs to.
34
151
  */
35
152
  export declare function planVoiceSessionBinding(args: {
36
153
  sessionLive: boolean;
37
154
  bound: VoiceSessionBinding;
38
155
  activeConversationId: string | null;
39
- /** The bound conversation is still in the store. */
156
+ /**
157
+ * The conversation the binding names (`voiceBindingConversationId`) is
158
+ * still in the store.
159
+ */
40
160
  boundConversationExists: boolean;
41
161
  }): {
42
162
  bound: VoiceSessionBinding;
@@ -64,7 +184,10 @@ export declare function maySendSpokenTurn(args: {
64
184
  /** The session's conversation when the turn was asked. */
65
185
  boundAtSubmit: VoiceSessionBinding;
66
186
  activeConversationId: string | null;
67
- /** `boundAtSubmit` is still in the store. */
187
+ /**
188
+ * The conversation `boundAtSubmit` names (`voiceBindingConversationId`) is
189
+ * still in the store.
190
+ */
68
191
  boundConversationExists: boolean;
69
192
  }): boolean;
70
193
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"voiceReopen.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/voice/voiceReopen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAE5D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,mBAAmB,CAAC;IAC3B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,oDAAoD;IACpD,uBAAuB,EAAE,OAAO,CAAC;CAClC,GAAG;IAAE,KAAK,EAAE,mBAAmB,CAAC;IAAC,GAAG,EAAE,OAAO,CAAA;CAAE,CAY/C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,gEAAgE;IAChE,WAAW,EAAE,OAAO,CAAC;IACrB,0DAA0D;IAC1D,aAAa,EAAE,mBAAmB,CAAC;IACnC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,6CAA6C;IAC7C,uBAAuB,EAAE,OAAO,CAAC;CAClC,GAAG,OAAO,CAOV;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,4BAA4B,EAAE,uBAG1C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,uBAAuB,EACjC,GAAG,EAAE;IACH,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,cAAc,EAAE,mBAAmB,CAAC;IACpC,YAAY,EAAE,OAAO,CAAC;CACvB,GACA;IAAE,KAAK,EAAE,uBAAuB,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAqBnD"}
1
+ {"version":3,"file":"voiceReopen.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/voice/voiceReopen.ts"],"names":[],"mappings":"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"}