@yieldfabric/terminal 1.2.0 → 1.3.1

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.
package/dist/index.cjs CHANGED
@@ -2234,18 +2234,21 @@ function CopyButton({ content }) {
2234
2234
  }
2235
2235
  );
2236
2236
  }
2237
+ var MESSAGES = {
2238
+ rounds_exhausted: "Partial answer \u2014 I reached the step limit. Ask a narrower follow-up to continue.",
2239
+ budget_exhausted: "Partial answer \u2014 this question needs more context than fits in one answer. Try narrowing the question.",
2240
+ deadline: "Partial answer \u2014 I ran out of time before finishing. Ask a narrower follow-up to continue.",
2241
+ output_limit: "Partial answer \u2014 it was cut off at its length limit. Ask for the rest, or a narrower follow-up.",
2242
+ awaiting_tool_results: "This answer paused before its next step. Ask again to review a fresh answer.",
2243
+ empty: "No answer was returned. Please try again.",
2244
+ failed: "The answer could not be completed. What arrived so far is kept here.",
2245
+ interrupted: "Connection interrupted. The assistant may still be working; check the conversation before sending again.",
2246
+ cancelled: "Stopped. You can change your question and ask again."
2247
+ };
2237
2248
  function AgentCompletionNotice({ completion }) {
2238
2249
  const t = useTheme();
2239
2250
  if (!completion || completion.stop_reason === "answered") return null;
2240
- const message = {
2241
- rounds_exhausted: "Partial answer \u2014 I reached the step limit. Ask a narrower follow-up to continue.",
2242
- budget_exhausted: "Partial answer \u2014 this question needs more context than fits in one answer. Try narrowing the question.",
2243
- awaiting_tool_results: "This answer paused before its next step. Ask again to review a fresh answer.",
2244
- empty: "No answer was returned. Please try again.",
2245
- failed: "The answer could not be completed. What arrived so far is kept here.",
2246
- interrupted: "Connection interrupted. The assistant may still be working; check the conversation before sending again.",
2247
- cancelled: "Stopped. You can change your question and ask again."
2248
- }[completion.stop_reason];
2251
+ const message = MESSAGES[completion.stop_reason];
2249
2252
  if (!message) return null;
2250
2253
  return /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", className: "mt-2 rounded-md px-3 py-2 text-xs", style: { color: t.textSecondary, backgroundColor: t.bgRaisedAlt, border: `1px solid ${t.border}` }, children: message });
2251
2254
  }
@@ -7856,6 +7859,54 @@ function encodePersona(persona) {
7856
7859
  return Object.keys(fields).length ? JSON.stringify(fields) : null;
7857
7860
  }
7858
7861
 
7862
+ // src/voice/screenAwake.ts
7863
+ function browserWakeLock() {
7864
+ return typeof navigator === "undefined" ? void 0 : navigator.wakeLock;
7865
+ }
7866
+ var ScreenAwake = class {
7867
+ constructor(api = browserWakeLock()) {
7868
+ this.api = api;
7869
+ this.sentinel = null;
7870
+ this.wanted = false;
7871
+ this.requesting = false;
7872
+ this.onVisibility = () => {
7873
+ if (document.visibilityState === "visible") void this.take();
7874
+ };
7875
+ }
7876
+ /** The screen is being kept on right now. */
7877
+ get held() {
7878
+ return !!this.sentinel && !this.sentinel.released;
7879
+ }
7880
+ /** Keep the screen on until `release`. Call it from the tap that starts
7881
+ * the session. */
7882
+ hold() {
7883
+ if (!this.api || this.wanted) return;
7884
+ this.wanted = true;
7885
+ document.addEventListener("visibilitychange", this.onVisibility);
7886
+ void this.take();
7887
+ }
7888
+ release() {
7889
+ if (!this.wanted) return;
7890
+ this.wanted = false;
7891
+ document.removeEventListener("visibilitychange", this.onVisibility);
7892
+ const sentinel = this.sentinel;
7893
+ this.sentinel = null;
7894
+ void sentinel?.release().catch(() => void 0);
7895
+ }
7896
+ async take() {
7897
+ if (!this.api || !this.wanted || this.held || this.requesting || document.visibilityState !== "visible") return;
7898
+ this.requesting = true;
7899
+ try {
7900
+ const sentinel = await this.api.request("screen");
7901
+ if (this.wanted) this.sentinel = sentinel;
7902
+ else void sentinel.release().catch(() => void 0);
7903
+ } catch {
7904
+ } finally {
7905
+ this.requesting = false;
7906
+ }
7907
+ }
7908
+ };
7909
+
7859
7910
  // src/voice/worklet.ts
7860
7911
  var VOICE_CAPTURE_WORKLET_SOURCE = `class YfVoiceCapture extends AudioWorkletProcessor {
7861
7912
  constructor() {
@@ -7991,6 +8042,8 @@ var VoiceSession = class {
7991
8042
  this.ready = false;
7992
8043
  this.ended = false;
7993
8044
  this.phase = null;
8045
+ /** Keeps a phone from locking mid-conversation (see `screenAwake.ts`). */
8046
+ this.awake = new ScreenAwake();
7994
8047
  /** The voice's questions to the host, one at a time (see `askQueue.ts`). */
7995
8048
  this.asks = new AskQueue(
7996
8049
  (ask, signal) => {
@@ -8013,6 +8066,7 @@ var VoiceSession = class {
8013
8066
  this.saying = "";
8014
8067
  this.endingReason = null;
8015
8068
  this.finishTimer = null;
8069
+ this.muted = false;
8016
8070
  this.blobUrl = null;
8017
8071
  }
8018
8072
  /** Call from the tap itself: iOS only lets audio start inside a gesture,
@@ -8026,6 +8080,7 @@ var VoiceSession = class {
8026
8080
  const ctx = new Ctx();
8027
8081
  this.ctx = ctx;
8028
8082
  void ctx.resume();
8083
+ this.awake.hold();
8029
8084
  this.setPhase("connecting");
8030
8085
  const status = await getVoiceStatus(this.connection);
8031
8086
  if (this.ended) return;
@@ -8041,6 +8096,7 @@ var VoiceSession = class {
8041
8096
  this.stream = await navigator.mediaDevices.getUserMedia({
8042
8097
  audio: { channelCount: 1, echoCancellation: true, noiseSuppression: true, autoGainControl: true }
8043
8098
  });
8099
+ this.applyMute();
8044
8100
  } catch (e) {
8045
8101
  this.end("mic_denied", e?.message);
8046
8102
  return;
@@ -8112,6 +8168,24 @@ var VoiceSession = class {
8112
8168
  stop() {
8113
8169
  this.end("stopped");
8114
8170
  }
8171
+ /**
8172
+ * Mute the microphone, or not. The track goes silent rather than the
8173
+ * stream stopping: the service keeps hearing (silence), so whatever the
8174
+ * person was saying ends as a turn and nothing hangs half-heard. The
8175
+ * voice can still talk while they are muted.
8176
+ */
8177
+ setMuted(muted) {
8178
+ this.muted = muted;
8179
+ this.applyMute();
8180
+ }
8181
+ get isMuted() {
8182
+ return this.muted;
8183
+ }
8184
+ applyMute() {
8185
+ this.stream?.getAudioTracks().forEach((track) => {
8186
+ track.enabled = !this.muted;
8187
+ });
8188
+ }
8115
8189
  /** Cheap enough to call every animation frame. */
8116
8190
  levels() {
8117
8191
  if (this.ended) return SILENT;
@@ -8235,6 +8309,7 @@ var VoiceSession = class {
8235
8309
  this.finishTimer = null;
8236
8310
  }
8237
8311
  release() {
8312
+ this.awake.release();
8238
8313
  this.node?.port.close();
8239
8314
  this.node?.disconnect();
8240
8315
  this.stream?.getTracks().forEach((t) => t.stop());
@@ -8329,6 +8404,7 @@ function useVoiceSession(host, connection) {
8329
8404
  const [saying, setSaying] = React15.useState("");
8330
8405
  const [ended, setEnded] = React15.useState(null);
8331
8406
  const [startedAt, setStartedAt] = React15.useState(null);
8407
+ const [muted, setMuted] = React15.useState(false);
8332
8408
  const session = React15.useRef(null);
8333
8409
  const hostRef = React15.useRef(host);
8334
8410
  hostRef.current = host;
@@ -8351,6 +8427,7 @@ function useVoiceSession(host, connection) {
8351
8427
  setHeard("");
8352
8428
  setSaying("");
8353
8429
  setStartedAt(null);
8430
+ setMuted(false);
8354
8431
  const s = new VoiceSession(
8355
8432
  {
8356
8433
  onPhase: (p) => {
@@ -8364,6 +8441,7 @@ function useVoiceSession(host, connection) {
8364
8441
  onReply: (r) => hostRef.current.onReply?.(r),
8365
8442
  onEnd: (reason, detail) => {
8366
8443
  if (session.current === s) session.current = null;
8444
+ setMuted(false);
8367
8445
  setPhase("off");
8368
8446
  setStartedAt(null);
8369
8447
  setEnded(reason === "stopped" || reason === "finished" ? null : { reason, detail });
@@ -8377,6 +8455,12 @@ function useVoiceSession(host, connection) {
8377
8455
  const stop = React15.useCallback(() => session.current?.stop(), []);
8378
8456
  const toggle = React15.useCallback(() => session.current ? stop() : start(), [start, stop]);
8379
8457
  const dismiss = React15.useCallback(() => setEnded(null), []);
8458
+ const toggleMute = React15.useCallback(() => {
8459
+ const s = session.current;
8460
+ if (!s) return;
8461
+ s.setMuted(!s.isMuted);
8462
+ setMuted(s.isMuted);
8463
+ }, []);
8380
8464
  const levels = React15.useCallback(() => session.current?.levels() ?? SILENT, []);
8381
8465
  const level = React15.useCallback(() => {
8382
8466
  const l = levels();
@@ -8395,6 +8479,8 @@ function useVoiceSession(host, connection) {
8395
8479
  levels,
8396
8480
  level,
8397
8481
  buttonState: phase === "off" ? "off" : phase === "connecting" ? "connecting" : "live",
8482
+ muted,
8483
+ toggleMute,
8398
8484
  start,
8399
8485
  stop,
8400
8486
  toggle,
@@ -8478,6 +8564,7 @@ var VOICE_DECK_CSS = `
8478
8564
  }
8479
8565
  .yf-voice-deck[data-phase='listening'] .yf-voice-beacon { animation-duration: 3.2s; }
8480
8566
  .yf-voice-phase {
8567
+ white-space: nowrap;
8481
8568
  font-size: 10.5px;
8482
8569
  font-weight: 600;
8483
8570
  letter-spacing: 0.14em;
@@ -8505,12 +8592,36 @@ var VOICE_DECK_CSS = `
8505
8592
  .yf-voice-end:hover { background: color-mix(in srgb, var(--yf-voice-ink) 10%, transparent); border-color: color-mix(in srgb, var(--yf-voice-ink) 28%, transparent); color: var(--yf-voice-ink); }
8506
8593
  .yf-voice-end:active { transform: scale(0.97); }
8507
8594
  .yf-voice-end__mark { width: 8px; height: 8px; border-radius: 2px; background: currentColor; }
8595
+ .yf-voice-mute {
8596
+ display: inline-flex;
8597
+ align-items: center;
8598
+ justify-content: center;
8599
+ width: 28px;
8600
+ height: 28px;
8601
+ border-radius: 999px;
8602
+ border: 1px solid color-mix(in srgb, var(--yf-voice-ink) 16%, transparent);
8603
+ background: color-mix(in srgb, var(--yf-voice-ink) 5%, transparent);
8604
+ color: color-mix(in srgb, var(--yf-voice-ink) 85%, transparent);
8605
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
8606
+ -webkit-tap-highlight-color: transparent;
8607
+ }
8608
+ .yf-voice-mute svg { width: 14px; height: 14px; }
8609
+ .yf-voice-mute:hover { background: color-mix(in srgb, var(--yf-voice-ink) 10%, transparent); color: var(--yf-voice-ink); }
8610
+ .yf-voice-mute:active { transform: scale(0.95); }
8611
+ .yf-voice-mute:disabled { opacity: 0.4; }
8612
+ .yf-voice-mute[aria-pressed='true'] {
8613
+ border-color: color-mix(in srgb, var(--yf-voice-accent) 55%, transparent);
8614
+ background: color-mix(in srgb, var(--yf-voice-accent) 18%, transparent);
8615
+ color: var(--yf-voice-accent);
8616
+ }
8617
+ .yf-voice-deck[data-muted] .yf-voice-beacon { background: var(--yf-voice-muted); animation: none; }
8618
+ .yf-voice-deck[data-muted] .yf-voice-phase { color: var(--yf-voice-muted); }
8508
8619
  .yf-voice-print {
8509
8620
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 12%, #000 100%);
8510
8621
  mask-image: linear-gradient(90deg, transparent 0, #000 12%, #000 100%);
8511
8622
  }
8512
8623
  .yf-voice-caption { display: flex; min-width: 0; align-items: baseline; gap: 8px; color: color-mix(in srgb, var(--yf-voice-ink) 72%, transparent); }
8513
- .yf-voice-caption--javier { display: block; color: var(--yf-voice-ink); font-weight: 450; text-wrap: pretty; }
8624
+ .yf-voice-caption--javier { display: block; color: var(--yf-voice-ink); font-weight: 450; text-wrap: pretty; overflow-wrap: anywhere; }
8514
8625
  .yf-voice-hint { color: var(--yf-voice-muted); }
8515
8626
  .yf-voice-who {
8516
8627
  flex-shrink: 0;
@@ -8520,9 +8631,27 @@ var VOICE_DECK_CSS = `
8520
8631
  text-transform: uppercase;
8521
8632
  color: var(--yf-voice-muted);
8522
8633
  }
8634
+ .yf-voice-deck[data-fill] { display: flex; flex-direction: column; height: 100%; min-height: 0; }
8635
+ .yf-voice-deck[data-fill] .yf-voice-deck__panel { flex: 1; display: flex; flex-direction: column; min-height: 0; }
8636
+ .yf-voice-detail {
8637
+ flex: 1;
8638
+ min-height: 0;
8639
+ overflow-y: auto;
8640
+ overflow-x: hidden;
8641
+ overflow-wrap: anywhere;
8642
+ overscroll-behavior: contain;
8643
+ -webkit-overflow-scrolling: touch;
8644
+ padding: 10px 16px 16px;
8645
+ border-top: 1px solid color-mix(in srgb, var(--yf-voice-ink) 8%, transparent);
8646
+ -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 14px, #000 calc(100% - 18px), transparent 100%);
8647
+ mask-image: linear-gradient(180deg, transparent 0, #000 14px, #000 calc(100% - 18px), transparent 100%);
8648
+ animation: yf-voice-rise 0.32s cubic-bezier(0.2, 0.7, 0.2, 1) both;
8649
+ }
8523
8650
  .yf-voice-deck[data-phone] { border-radius: 20px; }
8524
8651
  .yf-voice-deck[data-phone] .yf-voice-deck__panel { border-radius: 19px; }
8525
8652
  .yf-voice-deck[data-phone] .yf-voice-end { height: 40px; padding: 0 16px 0 14px; font-size: 14px; }
8653
+ .yf-voice-deck[data-phone] .yf-voice-mute { width: 40px; height: 40px; margin-right: 2px; }
8654
+ .yf-voice-deck[data-phone] .yf-voice-mute svg { width: 18px; height: 18px; }
8526
8655
  .yf-voice-deck[data-phone] .yf-voice-phase { font-size: 11.5px; }
8527
8656
  @media (prefers-reduced-motion: reduce) {
8528
8657
  .yf-voice-deck, .yf-voice-beacon, .yf-voice-deck__panel { animation: none; }
@@ -8536,7 +8665,7 @@ function ensureVoiceDeckStyles(doc = typeof document === "undefined" ? void 0 :
8536
8665
  style.textContent = VOICE_DECK_CSS;
8537
8666
  doc.head.appendChild(style);
8538
8667
  }
8539
- function VoiceDeck({ voice, name, phone = false, hint }) {
8668
+ function VoiceDeck({ voice, name, phone = false, hint, fill = false, children }) {
8540
8669
  const t = useTheme();
8541
8670
  React15.useEffect(() => ensureVoiceDeckStyles(), []);
8542
8671
  const now = useClock(voice.startedAt !== null);
@@ -8546,9 +8675,10 @@ function VoiceDeck({ voice, name, phone = false, hint }) {
8546
8675
  connecting: "Connecting",
8547
8676
  listening: "Listening",
8548
8677
  hearing: "Hearing you",
8549
- thinking: "Working on it",
8678
+ thinking: "Working",
8550
8679
  speaking: speaker
8551
8680
  };
8681
+ const phaseWord = voice.muted && voice.phase !== "speaking" && voice.phase !== "connecting" ? "Muted" : phaseWords[voice.phase];
8552
8682
  const { startedAt, maxSessionSecs } = voice;
8553
8683
  const elapsed = startedAt === null ? 0 : now - startedAt;
8554
8684
  const remaining = maxSessionSecs > 0 && startedAt !== null ? maxSessionSecs * 1e3 - elapsed : Infinity;
@@ -8561,26 +8691,40 @@ function VoiceDeck({ voice, name, phone = false, hint }) {
8561
8691
  "--yf-voice-muted": t.textMuted,
8562
8692
  "--yf-voice-panel": t.bgRaised
8563
8693
  };
8564
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "yf-voice-deck", "data-phase": voice.phase, "data-phone": phone ? "" : void 0, style: vars, children: [
8694
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "yf-voice-deck", "data-phase": voice.phase, "data-phone": phone ? "" : void 0, "data-fill": fill ? "" : void 0, "data-muted": voice.muted ? "" : void 0, style: vars, children: [
8565
8695
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "yf-voice-deck__panel", children: [
8566
8696
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex items-center gap-2.5 ${phone ? "px-4 pt-3.5" : "px-4 pt-3"}`, children: [
8567
8697
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "yf-voice-beacon", "aria-hidden": "true" }),
8568
- /* @__PURE__ */ jsxRuntime.jsx("span", { role: "status", "aria-live": "polite", className: "yf-voice-phase", children: phaseWords[voice.phase] }),
8698
+ /* @__PURE__ */ jsxRuntime.jsx("span", { role: "status", "aria-live": "polite", className: "yf-voice-phase", children: phaseWord }),
8569
8699
  startedAt !== null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "yf-voice-clock", "data-soon": endingSoon ? "" : void 0, title: endingSoon ? "Voice ends soon" : "Time in this voice session", children: endingSoon && Number.isFinite(remaining) ? `ends in ${clock(remaining)}` : clock(elapsed) }) : null,
8570
8700
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1" }),
8701
+ /* @__PURE__ */ jsxRuntime.jsx(
8702
+ "button",
8703
+ {
8704
+ type: "button",
8705
+ onClick: voice.toggleMute,
8706
+ className: "yf-voice-mute",
8707
+ "aria-pressed": voice.muted,
8708
+ "aria-label": voice.muted ? "Unmute microphone" : "Mute microphone",
8709
+ title: voice.muted ? "Unmute" : "Mute",
8710
+ disabled: voice.phase === "connecting",
8711
+ children: voice.muted ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, { "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { "aria-hidden": "true" })
8712
+ }
8713
+ ),
8571
8714
  /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: voice.stop, className: "yf-voice-end", "aria-label": "End voice", children: [
8572
8715
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "yf-voice-end__mark", "aria-hidden": "true" }),
8573
8716
  "End"
8574
8717
  ] })
8575
8718
  ] }),
8576
- /* @__PURE__ */ jsxRuntime.jsx(Voiceprint, { phase: voice.phase, levels: voice.levels, height: phone ? 104 : 76, cell: phone ? 13 : 11 }),
8719
+ /* @__PURE__ */ jsxRuntime.jsx(Voiceprint, { phase: voice.phase, levels: voice.levels, height: fill ? 148 : phone ? 104 : 76, cell: fill ? 14 : phone ? 13 : 11 }),
8577
8720
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex flex-col gap-1.5 ${phone ? "px-4 pb-4" : "px-4 pb-3.5"}`, style: { minHeight: "1.25rem" }, "aria-live": "off", children: [
8578
8721
  words ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "yf-voice-caption", style: { fontSize: phone ? 13.5 : 12.5 }, children: [
8579
8722
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "yf-voice-who", children: "You" }),
8580
8723
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: words })
8581
8724
  ] }) : null,
8582
- said ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "yf-voice-caption yf-voice-caption--javier", style: { fontSize: phone ? 16 : 14, lineHeight: phone ? 1.45 : 1.5 }, children: said }) : !words ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "yf-voice-hint", style: { fontSize: phone ? 14 : 12.5 }, children: voice.phase === "connecting" ? "Opening the line\u2026" : hint ?? "Ask anything. Talk over me to interrupt." }) : null
8583
- ] })
8725
+ said ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "yf-voice-caption yf-voice-caption--javier", style: { fontSize: phone ? 16 : 14, lineHeight: phone ? 1.45 : 1.5 }, children: said }) : !words ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "yf-voice-hint", style: { fontSize: phone ? 14 : 12.5 }, children: voice.phase === "connecting" ? "Opening the line\u2026" : voice.muted ? "Your microphone is off. Tap it to talk." : hint ?? "Ask anything. Talk over me to interrupt." }) : null
8726
+ ] }),
8727
+ fill && children ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "yf-voice-detail", children }) : null
8584
8728
  ] }),
8585
8729
  /* @__PURE__ */ jsxRuntime.jsx("span", { "data-voice-probe": "ink", hidden: true, style: { color: "var(--yf-voice-ink)" } }),
8586
8730
  /* @__PURE__ */ jsxRuntime.jsx("span", { "data-voice-probe": "accent", hidden: true, style: { color: "var(--yf-voice-accent)" } }),
@@ -10511,8 +10655,9 @@ function EdgeDetailDrawer({ edge, annotations, onClose, onNavigate }) {
10511
10655
  ]
10512
10656
  }
10513
10657
  ),
10514
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-1", style: { backgroundColor: "#1e1e1e", borderTop: "1px solid #282828", borderBottom: "1px solid #282828" }, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] text-gray-500", children: [
10515
- "\u2193 ",
10658
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-1", style: { backgroundColor: "#1e1e1e", borderTop: "1px solid #282828", borderBottom: "1px solid #282828" }, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 text-[10px] text-gray-500", children: [
10659
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-3 w-3" }),
10660
+ " ",
10516
10661
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color }, children: typeLabel })
10517
10662
  ] }) }),
10518
10663
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -11433,7 +11578,7 @@ function SchemaStage({
11433
11578
  style: { flex: 1, fontSize: 11, color: "#aaa", backgroundColor: "transparent", border: "1px solid #333", borderRadius: 4, padding: "5px 8px", outline: "none" }
11434
11579
  }
11435
11580
  ),
11436
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#555", alignSelf: "center", fontSize: 12 }, children: "\u2192" }),
11581
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#555", alignSelf: "center", fontSize: 12 }, children: "\u203A" }),
11437
11582
  /* @__PURE__ */ jsxRuntime.jsx(
11438
11583
  "input",
11439
11584
  {
@@ -11543,7 +11688,7 @@ function SchemaStage({
11543
11688
  onMouseUp: (e) => {
11544
11689
  e.currentTarget.style.transform = "translateY(0)";
11545
11690
  },
11546
- children: "Looks good \u2014 read the document \u2192"
11691
+ children: "Looks good \u2014 read the document \u203A"
11547
11692
  }
11548
11693
  )
11549
11694
  ]
@@ -12228,7 +12373,7 @@ function StageCanvas({ nodeCount, onForceGraph, initialPhase, initialTeam, kgId
12228
12373
  {
12229
12374
  onClick: onForceGraph,
12230
12375
  style: { display: "block", margin: "16px auto 0", color: "#ff8c42", fontSize: 12, background: "none", border: "1px solid #ff8c4230", padding: "6px 16px", borderRadius: 8, cursor: "pointer" },
12231
- children: "Show Graph \u2192"
12376
+ children: "Show graph \u203A"
12232
12377
  }
12233
12378
  )
12234
12379
  ] })
@@ -15685,7 +15830,7 @@ function CheckpointCard({
15685
15830
  style: { backgroundColor: `${config.color}20`, color: config.color, border: `1px solid ${config.color}30` },
15686
15831
  children: [
15687
15832
  config.proceedLabel,
15688
- " \u2192"
15833
+ " \u203A"
15689
15834
  ]
15690
15835
  }
15691
15836
  )
@@ -15801,6 +15946,7 @@ exports.SSE_KEEPALIVE_MS = SSE_KEEPALIVE_MS;
15801
15946
  exports.SSE_STALL_MS = SSE_STALL_MS;
15802
15947
  exports.SYNTHESIS_DONE_SIGNAL = SYNTHESIS_DONE_SIGNAL;
15803
15948
  exports.ScopedReasoningProvider = ScopedReasoningProvider;
15949
+ exports.ScreenAwake = ScreenAwake;
15804
15950
  exports.SettlementBar = SettlementBar;
15805
15951
  exports.SlashMentionPicker = SlashMentionPicker;
15806
15952
  exports.StageCanvas = StageCanvas;
package/dist/index.d.cts CHANGED
@@ -639,7 +639,7 @@ interface AgentToolStep {
639
639
  ms: number;
640
640
  }
641
641
  interface AgentCompletion {
642
- stop_reason: 'answered' | 'rounds_exhausted' | 'budget_exhausted' | 'empty' | 'failed' | 'interrupted' | 'cancelled' | 'awaiting_tool_results';
642
+ stop_reason: 'answered' | 'rounds_exhausted' | 'budget_exhausted' | 'empty' | 'output_limit' | 'deadline' | 'failed' | 'interrupted' | 'cancelled' | 'awaiting_tool_results';
643
643
  truncated?: boolean;
644
644
  rounds_used?: number;
645
645
  prompt_tokens?: number;
@@ -1477,6 +1477,8 @@ declare class VoiceSession {
1477
1477
  private ready;
1478
1478
  private ended;
1479
1479
  private phase;
1480
+ /** Keeps a phone from locking mid-conversation (see `screenAwake.ts`). */
1481
+ private readonly awake;
1480
1482
  /** The voice's questions to the host, one at a time (see `askQueue.ts`). */
1481
1483
  private readonly asks;
1482
1484
  private heard;
@@ -1484,12 +1486,22 @@ declare class VoiceSession {
1484
1486
  private saying;
1485
1487
  private endingReason;
1486
1488
  private finishTimer;
1489
+ private muted;
1487
1490
  private blobUrl;
1488
1491
  constructor(h: VoiceHandlers, connection: VoiceConnection);
1489
1492
  /** Call from the tap itself: iOS only lets audio start inside a gesture,
1490
1493
  * so the AudioContext is created before the first `await`. */
1491
1494
  start(): Promise<void>;
1492
1495
  stop(): void;
1496
+ /**
1497
+ * Mute the microphone, or not. The track goes silent rather than the
1498
+ * stream stopping: the service keeps hearing (silence), so whatever the
1499
+ * person was saying ends as a turn and nothing hangs half-heard. The
1500
+ * voice can still talk while they are muted.
1501
+ */
1502
+ setMuted(muted: boolean): void;
1503
+ get isMuted(): boolean;
1504
+ private applyMute;
1493
1505
  /** Cheap enough to call every animation frame. */
1494
1506
  levels(): VoiceLevels;
1495
1507
  private setPhase;
@@ -1532,6 +1544,9 @@ interface VoiceSessionState {
1532
1544
  level: () => number;
1533
1545
  /** For `VoiceInputButton`. */
1534
1546
  buttonState: VoiceButtonState;
1547
+ /** The microphone is muted (the voice can still talk). */
1548
+ muted: boolean;
1549
+ toggleMute: () => void;
1535
1550
  start: () => void;
1536
1551
  stop: () => void;
1537
1552
  toggle: () => void;
@@ -1577,6 +1592,13 @@ interface VoiceDeckProps {
1577
1592
  phone?: boolean;
1578
1593
  /** What to show before anything has been said. */
1579
1594
  hint?: string;
1595
+ /** Take the whole height it is given (a phone, where the transcript
1596
+ * behind it is too small to read): a taller band, and `children` in a
1597
+ * scrolling area beneath the live words. */
1598
+ fill?: boolean;
1599
+ /** What the host has worth reading while the voice runs — its agent's
1600
+ * steps, the answer and its sources. Shown with `fill`. */
1601
+ children?: React.ReactNode;
1580
1602
  }
1581
1603
  /**
1582
1604
  * Live voice, above a composer: a band that weaves the conversation into
@@ -1588,7 +1610,7 @@ interface VoiceDeckProps {
1588
1610
  * Styled from the terminal theme (`TerminalThemeProvider`): render it inside
1589
1611
  * the same provider as your input bar.
1590
1612
  */
1591
- declare function VoiceDeck({ voice, name, phone, hint }: VoiceDeckProps): react_jsx_runtime.JSX.Element;
1613
+ declare function VoiceDeck({ voice, name, phone, hint, fill, children }: VoiceDeckProps): react_jsx_runtime.JSX.Element;
1592
1614
 
1593
1615
  /**
1594
1616
  * The voice's questions to the host, one at a time.
@@ -1626,6 +1648,40 @@ declare class AskQueue<A extends QueuedAsk> {
1626
1648
  private next;
1627
1649
  }
1628
1650
 
1651
+ /**
1652
+ * Keeps the screen on while a voice session runs.
1653
+ *
1654
+ * A phone that locks mid-conversation suspends the page — on iOS the
1655
+ * microphone stops and the session ends — so a live session holds a Screen
1656
+ * Wake Lock (`navigator.wakeLock`) from the tap that starts it until it ends.
1657
+ * The browser drops the lock whenever the page is hidden (another app, the
1658
+ * tab switcher), so it is taken again when the page is visible again, as
1659
+ * long as the session is still on. Where the API is missing or refuses
1660
+ * (battery saver, policy), the screen simply locks as it always did.
1661
+ */
1662
+ interface Sentinel {
1663
+ readonly released?: boolean;
1664
+ release(): Promise<void>;
1665
+ }
1666
+ interface WakeLockApi {
1667
+ request(type: 'screen'): Promise<Sentinel>;
1668
+ }
1669
+ declare class ScreenAwake {
1670
+ private readonly api;
1671
+ private sentinel;
1672
+ private wanted;
1673
+ private requesting;
1674
+ private readonly onVisibility;
1675
+ constructor(api?: WakeLockApi | undefined);
1676
+ /** The screen is being kept on right now. */
1677
+ get held(): boolean;
1678
+ /** Keep the screen on until `release`. Call it from the tap that starts
1679
+ * the session. */
1680
+ hold(): void;
1681
+ release(): void;
1682
+ private take;
1683
+ }
1684
+
1629
1685
  /**
1630
1686
  * The microphone capture AudioWorklet, as source.
1631
1687
  *
@@ -2909,4 +2965,4 @@ declare const terminalClassNames: {
2909
2965
  promptSymbol: string;
2910
2966
  };
2911
2967
 
2912
- export { ADVANCE_PHASE_SIGNAL, AGENT_PALETTE, type AIAction, type ActivityItem, AgentAvatar, type AgentAvatarProps, type AgentAvatarSize, type AgentCompletion, AgentPresenceBar, AgentStepTrace, type AgentToolStep, type Annotation, AnnotationThread, type ApiEndpoint, type ApiParameter, AskQueue, type BaseTerminalProps, CLAIM_STATUS_COLORS, ChatTerminal, type ChatTerminalProps, CheckpointCard, type CheckpointType, ClaimNode, type ClaimNodeData, type ClaimStatus, ConstructionProgress, CoreTerminal, type CoreTerminalProps, type CreateNoteResult, DEFAULT_AGENT_LABEL, DOCUMENT_PIPELINE_STEPS, type DocumentProcessingEvent, DocumentProcessingProgress, EDGE_TYPE_COLORS, EdgeDetailDrawer, EdgeMarkerDefs, CoreTerminal as EmbeddedTerminal, type CoreTerminalProps as EmbeddedTerminalProps, ExpandableTerminal, type ExpandableTerminalProps, type FileUploadResult, type GapOption, type GapQuestion, type GenericKGSnapshot, type GenericSnapshotEdge, type GenericSnapshotNode, GraphChatPanel, type GraphChatPanelProps, GraphEdge, type GraphEdgeData, type GraphLayer, GraphNode, type GraphNodeData, type HelpTopic, InlineTerminal, type InlineTerminalProps, type KGAnnotation, type KGChunkLink$1 as KGChunkLink, type KGScope, type KGSearchResult, type KGSnapshot, type KGSnapshotEdge, type KGSnapshotNode, type KnowledgeGraphDataCallbacks, KnowledgeGraphView, LIVE_KG_ID, type LegacyKGSnapshot, type LegacyKGSnapshotEdge, type LegacyKGSnapshotNode, type LineageEntry, type ListRagFilesResult, type ListedRagFile, LiveActivityTicker, MarkdownContent, MarkdownEditor, type Mention, type MentionTarget, type MessageAudience, type MessageSender, NODE_TYPE_COLORS, NODE_TYPE_LABELS, NodeDetailDrawer, PROGRESS_EVENT_NAME, type PageSuggestion, type ParsedCommand, type PipelineBuilderAction, type PipelineDraft, type PipelineDraftStep, PipelineProgressBar, type PluginContext, type ProcessingDetail, type ProgressEventDetail, type ProgressState, type QueuedAsk, REPLACED_OUTPUT, type RagFileActionResult, ReasoningHistoryCard, ReasoningOverlay, type ReasoningOverlayProps, type ReasoningProgressEvent, type ReasoningSnapshot, ReasoningView, type ReasoningViewProps, SILENT, SSE_KEEPALIVE_MS, SSE_STALL_MS, SYNTHESIS_DONE_SIGNAL, type SaveNoteResult, ScopedReasoningProvider, SettlementBar, type SlashMentionCandidate, SlashMentionPicker, StageCanvas, StandardTerminal, type StandardTerminalProps, type StreamChunk, type StreamHandler, type StreamHandlers, type SubgraphEdge, type SubgraphNode, type TeamSetupAgent, type TerminalCommand, type TerminalCommandResult, type TerminalConfig, type TerminalEntry, TerminalInputBar, type TerminalInputBarProps, type TerminalPlugin, type TerminalSlots, type TerminalSource, type TerminalTheme, TerminalThemeProvider, type TerminalWidget, VOICE_CAPTURE_WORKLET_SOURCE, VOICE_LANGUAGES, VOICE_NAMES, type VoiceButtonState, type VoiceConnection, VoiceDeck, type VoiceDeckProps, type VoiceEndReason, type VoiceEnded, type VoiceHandlers, type VoiceHost, VoiceInputButton, type VoiceInputButtonProps, type VoiceLanguage, type VoiceLevels, type VoiceName, type VoicePersona, type VoicePhase, type VoiceProgress, type VoiceReply, VoiceSession, type VoiceSessionState, type VoiceStatus, type VoiceUtterance, type WorkingGroupContext, agentSeed, agentStepTraceExtra, avatarColor, blueScope, buildClaimsGraph, buildTerminalCommand, buttonShadow, captureReasoningSnapshot, defaultTheme, dispatchProgressEvent, documentEventToActivity, emeraldCollab, emeraldRevised, encodePersona, eventToActivity, extractKGSources, extractKgId, filterCommands, formatAssetDisplay, formatPaymentAmount, formatTokenAssetId, getAgentColor, getBuiltInCommands, getClaimStatusColors, getEdgeColor, getEdgeTypeLabel, getNodeColors, getNodeTypeLabel, getVocabTypeColors, getVocabTypeLabel, getVoiceStatus, greenAction, handleSSEStream, handleStreamingResponse, hostApp, initials, isFixedKgId, isNaturalLanguage, layoutGraph, mergeTheme, openPipelineEventStream, orangeRebuttal, parseTerminalCommand, pipelineBg, processStreamLines, purple, reduceProgress, renderMarkdownBlock, resolvePaymentAssetDisplay, snapshotToSubgraph, socketBase, terminalClassNames, terminalColors, useScopedProgressEvent, useSlashMentionPicker, useTheme, useVoiceSession, voiceEndedMessage, voiceSocketUrl, warmBg, withStallTimeout };
2968
+ export { ADVANCE_PHASE_SIGNAL, AGENT_PALETTE, type AIAction, type ActivityItem, AgentAvatar, type AgentAvatarProps, type AgentAvatarSize, type AgentCompletion, AgentPresenceBar, AgentStepTrace, type AgentToolStep, type Annotation, AnnotationThread, type ApiEndpoint, type ApiParameter, AskQueue, type BaseTerminalProps, CLAIM_STATUS_COLORS, ChatTerminal, type ChatTerminalProps, CheckpointCard, type CheckpointType, ClaimNode, type ClaimNodeData, type ClaimStatus, ConstructionProgress, CoreTerminal, type CoreTerminalProps, type CreateNoteResult, DEFAULT_AGENT_LABEL, DOCUMENT_PIPELINE_STEPS, type DocumentProcessingEvent, DocumentProcessingProgress, EDGE_TYPE_COLORS, EdgeDetailDrawer, EdgeMarkerDefs, CoreTerminal as EmbeddedTerminal, type CoreTerminalProps as EmbeddedTerminalProps, ExpandableTerminal, type ExpandableTerminalProps, type FileUploadResult, type GapOption, type GapQuestion, type GenericKGSnapshot, type GenericSnapshotEdge, type GenericSnapshotNode, GraphChatPanel, type GraphChatPanelProps, GraphEdge, type GraphEdgeData, type GraphLayer, GraphNode, type GraphNodeData, type HelpTopic, InlineTerminal, type InlineTerminalProps, type KGAnnotation, type KGChunkLink$1 as KGChunkLink, type KGScope, type KGSearchResult, type KGSnapshot, type KGSnapshotEdge, type KGSnapshotNode, type KnowledgeGraphDataCallbacks, KnowledgeGraphView, LIVE_KG_ID, type LegacyKGSnapshot, type LegacyKGSnapshotEdge, type LegacyKGSnapshotNode, type LineageEntry, type ListRagFilesResult, type ListedRagFile, LiveActivityTicker, MarkdownContent, MarkdownEditor, type Mention, type MentionTarget, type MessageAudience, type MessageSender, NODE_TYPE_COLORS, NODE_TYPE_LABELS, NodeDetailDrawer, PROGRESS_EVENT_NAME, type PageSuggestion, type ParsedCommand, type PipelineBuilderAction, type PipelineDraft, type PipelineDraftStep, PipelineProgressBar, type PluginContext, type ProcessingDetail, type ProgressEventDetail, type ProgressState, type QueuedAsk, REPLACED_OUTPUT, type RagFileActionResult, ReasoningHistoryCard, ReasoningOverlay, type ReasoningOverlayProps, type ReasoningProgressEvent, type ReasoningSnapshot, ReasoningView, type ReasoningViewProps, SILENT, SSE_KEEPALIVE_MS, SSE_STALL_MS, SYNTHESIS_DONE_SIGNAL, type SaveNoteResult, ScopedReasoningProvider, ScreenAwake, SettlementBar, type SlashMentionCandidate, SlashMentionPicker, StageCanvas, StandardTerminal, type StandardTerminalProps, type StreamChunk, type StreamHandler, type StreamHandlers, type SubgraphEdge, type SubgraphNode, type TeamSetupAgent, type TerminalCommand, type TerminalCommandResult, type TerminalConfig, type TerminalEntry, TerminalInputBar, type TerminalInputBarProps, type TerminalPlugin, type TerminalSlots, type TerminalSource, type TerminalTheme, TerminalThemeProvider, type TerminalWidget, VOICE_CAPTURE_WORKLET_SOURCE, VOICE_LANGUAGES, VOICE_NAMES, type VoiceButtonState, type VoiceConnection, VoiceDeck, type VoiceDeckProps, type VoiceEndReason, type VoiceEnded, type VoiceHandlers, type VoiceHost, VoiceInputButton, type VoiceInputButtonProps, type VoiceLanguage, type VoiceLevels, type VoiceName, type VoicePersona, type VoicePhase, type VoiceProgress, type VoiceReply, VoiceSession, type VoiceSessionState, type VoiceStatus, type VoiceUtterance, type WorkingGroupContext, agentSeed, agentStepTraceExtra, avatarColor, blueScope, buildClaimsGraph, buildTerminalCommand, buttonShadow, captureReasoningSnapshot, defaultTheme, dispatchProgressEvent, documentEventToActivity, emeraldCollab, emeraldRevised, encodePersona, eventToActivity, extractKGSources, extractKgId, filterCommands, formatAssetDisplay, formatPaymentAmount, formatTokenAssetId, getAgentColor, getBuiltInCommands, getClaimStatusColors, getEdgeColor, getEdgeTypeLabel, getNodeColors, getNodeTypeLabel, getVocabTypeColors, getVocabTypeLabel, getVoiceStatus, greenAction, handleSSEStream, handleStreamingResponse, hostApp, initials, isFixedKgId, isNaturalLanguage, layoutGraph, mergeTheme, openPipelineEventStream, orangeRebuttal, parseTerminalCommand, pipelineBg, processStreamLines, purple, reduceProgress, renderMarkdownBlock, resolvePaymentAssetDisplay, snapshotToSubgraph, socketBase, terminalClassNames, terminalColors, useScopedProgressEvent, useSlashMentionPicker, useTheme, useVoiceSession, voiceEndedMessage, voiceSocketUrl, warmBg, withStallTimeout };
package/dist/index.d.ts CHANGED
@@ -639,7 +639,7 @@ interface AgentToolStep {
639
639
  ms: number;
640
640
  }
641
641
  interface AgentCompletion {
642
- stop_reason: 'answered' | 'rounds_exhausted' | 'budget_exhausted' | 'empty' | 'failed' | 'interrupted' | 'cancelled' | 'awaiting_tool_results';
642
+ stop_reason: 'answered' | 'rounds_exhausted' | 'budget_exhausted' | 'empty' | 'output_limit' | 'deadline' | 'failed' | 'interrupted' | 'cancelled' | 'awaiting_tool_results';
643
643
  truncated?: boolean;
644
644
  rounds_used?: number;
645
645
  prompt_tokens?: number;
@@ -1477,6 +1477,8 @@ declare class VoiceSession {
1477
1477
  private ready;
1478
1478
  private ended;
1479
1479
  private phase;
1480
+ /** Keeps a phone from locking mid-conversation (see `screenAwake.ts`). */
1481
+ private readonly awake;
1480
1482
  /** The voice's questions to the host, one at a time (see `askQueue.ts`). */
1481
1483
  private readonly asks;
1482
1484
  private heard;
@@ -1484,12 +1486,22 @@ declare class VoiceSession {
1484
1486
  private saying;
1485
1487
  private endingReason;
1486
1488
  private finishTimer;
1489
+ private muted;
1487
1490
  private blobUrl;
1488
1491
  constructor(h: VoiceHandlers, connection: VoiceConnection);
1489
1492
  /** Call from the tap itself: iOS only lets audio start inside a gesture,
1490
1493
  * so the AudioContext is created before the first `await`. */
1491
1494
  start(): Promise<void>;
1492
1495
  stop(): void;
1496
+ /**
1497
+ * Mute the microphone, or not. The track goes silent rather than the
1498
+ * stream stopping: the service keeps hearing (silence), so whatever the
1499
+ * person was saying ends as a turn and nothing hangs half-heard. The
1500
+ * voice can still talk while they are muted.
1501
+ */
1502
+ setMuted(muted: boolean): void;
1503
+ get isMuted(): boolean;
1504
+ private applyMute;
1493
1505
  /** Cheap enough to call every animation frame. */
1494
1506
  levels(): VoiceLevels;
1495
1507
  private setPhase;
@@ -1532,6 +1544,9 @@ interface VoiceSessionState {
1532
1544
  level: () => number;
1533
1545
  /** For `VoiceInputButton`. */
1534
1546
  buttonState: VoiceButtonState;
1547
+ /** The microphone is muted (the voice can still talk). */
1548
+ muted: boolean;
1549
+ toggleMute: () => void;
1535
1550
  start: () => void;
1536
1551
  stop: () => void;
1537
1552
  toggle: () => void;
@@ -1577,6 +1592,13 @@ interface VoiceDeckProps {
1577
1592
  phone?: boolean;
1578
1593
  /** What to show before anything has been said. */
1579
1594
  hint?: string;
1595
+ /** Take the whole height it is given (a phone, where the transcript
1596
+ * behind it is too small to read): a taller band, and `children` in a
1597
+ * scrolling area beneath the live words. */
1598
+ fill?: boolean;
1599
+ /** What the host has worth reading while the voice runs — its agent's
1600
+ * steps, the answer and its sources. Shown with `fill`. */
1601
+ children?: React.ReactNode;
1580
1602
  }
1581
1603
  /**
1582
1604
  * Live voice, above a composer: a band that weaves the conversation into
@@ -1588,7 +1610,7 @@ interface VoiceDeckProps {
1588
1610
  * Styled from the terminal theme (`TerminalThemeProvider`): render it inside
1589
1611
  * the same provider as your input bar.
1590
1612
  */
1591
- declare function VoiceDeck({ voice, name, phone, hint }: VoiceDeckProps): react_jsx_runtime.JSX.Element;
1613
+ declare function VoiceDeck({ voice, name, phone, hint, fill, children }: VoiceDeckProps): react_jsx_runtime.JSX.Element;
1592
1614
 
1593
1615
  /**
1594
1616
  * The voice's questions to the host, one at a time.
@@ -1626,6 +1648,40 @@ declare class AskQueue<A extends QueuedAsk> {
1626
1648
  private next;
1627
1649
  }
1628
1650
 
1651
+ /**
1652
+ * Keeps the screen on while a voice session runs.
1653
+ *
1654
+ * A phone that locks mid-conversation suspends the page — on iOS the
1655
+ * microphone stops and the session ends — so a live session holds a Screen
1656
+ * Wake Lock (`navigator.wakeLock`) from the tap that starts it until it ends.
1657
+ * The browser drops the lock whenever the page is hidden (another app, the
1658
+ * tab switcher), so it is taken again when the page is visible again, as
1659
+ * long as the session is still on. Where the API is missing or refuses
1660
+ * (battery saver, policy), the screen simply locks as it always did.
1661
+ */
1662
+ interface Sentinel {
1663
+ readonly released?: boolean;
1664
+ release(): Promise<void>;
1665
+ }
1666
+ interface WakeLockApi {
1667
+ request(type: 'screen'): Promise<Sentinel>;
1668
+ }
1669
+ declare class ScreenAwake {
1670
+ private readonly api;
1671
+ private sentinel;
1672
+ private wanted;
1673
+ private requesting;
1674
+ private readonly onVisibility;
1675
+ constructor(api?: WakeLockApi | undefined);
1676
+ /** The screen is being kept on right now. */
1677
+ get held(): boolean;
1678
+ /** Keep the screen on until `release`. Call it from the tap that starts
1679
+ * the session. */
1680
+ hold(): void;
1681
+ release(): void;
1682
+ private take;
1683
+ }
1684
+
1629
1685
  /**
1630
1686
  * The microphone capture AudioWorklet, as source.
1631
1687
  *
@@ -2909,4 +2965,4 @@ declare const terminalClassNames: {
2909
2965
  promptSymbol: string;
2910
2966
  };
2911
2967
 
2912
- export { ADVANCE_PHASE_SIGNAL, AGENT_PALETTE, type AIAction, type ActivityItem, AgentAvatar, type AgentAvatarProps, type AgentAvatarSize, type AgentCompletion, AgentPresenceBar, AgentStepTrace, type AgentToolStep, type Annotation, AnnotationThread, type ApiEndpoint, type ApiParameter, AskQueue, type BaseTerminalProps, CLAIM_STATUS_COLORS, ChatTerminal, type ChatTerminalProps, CheckpointCard, type CheckpointType, ClaimNode, type ClaimNodeData, type ClaimStatus, ConstructionProgress, CoreTerminal, type CoreTerminalProps, type CreateNoteResult, DEFAULT_AGENT_LABEL, DOCUMENT_PIPELINE_STEPS, type DocumentProcessingEvent, DocumentProcessingProgress, EDGE_TYPE_COLORS, EdgeDetailDrawer, EdgeMarkerDefs, CoreTerminal as EmbeddedTerminal, type CoreTerminalProps as EmbeddedTerminalProps, ExpandableTerminal, type ExpandableTerminalProps, type FileUploadResult, type GapOption, type GapQuestion, type GenericKGSnapshot, type GenericSnapshotEdge, type GenericSnapshotNode, GraphChatPanel, type GraphChatPanelProps, GraphEdge, type GraphEdgeData, type GraphLayer, GraphNode, type GraphNodeData, type HelpTopic, InlineTerminal, type InlineTerminalProps, type KGAnnotation, type KGChunkLink$1 as KGChunkLink, type KGScope, type KGSearchResult, type KGSnapshot, type KGSnapshotEdge, type KGSnapshotNode, type KnowledgeGraphDataCallbacks, KnowledgeGraphView, LIVE_KG_ID, type LegacyKGSnapshot, type LegacyKGSnapshotEdge, type LegacyKGSnapshotNode, type LineageEntry, type ListRagFilesResult, type ListedRagFile, LiveActivityTicker, MarkdownContent, MarkdownEditor, type Mention, type MentionTarget, type MessageAudience, type MessageSender, NODE_TYPE_COLORS, NODE_TYPE_LABELS, NodeDetailDrawer, PROGRESS_EVENT_NAME, type PageSuggestion, type ParsedCommand, type PipelineBuilderAction, type PipelineDraft, type PipelineDraftStep, PipelineProgressBar, type PluginContext, type ProcessingDetail, type ProgressEventDetail, type ProgressState, type QueuedAsk, REPLACED_OUTPUT, type RagFileActionResult, ReasoningHistoryCard, ReasoningOverlay, type ReasoningOverlayProps, type ReasoningProgressEvent, type ReasoningSnapshot, ReasoningView, type ReasoningViewProps, SILENT, SSE_KEEPALIVE_MS, SSE_STALL_MS, SYNTHESIS_DONE_SIGNAL, type SaveNoteResult, ScopedReasoningProvider, SettlementBar, type SlashMentionCandidate, SlashMentionPicker, StageCanvas, StandardTerminal, type StandardTerminalProps, type StreamChunk, type StreamHandler, type StreamHandlers, type SubgraphEdge, type SubgraphNode, type TeamSetupAgent, type TerminalCommand, type TerminalCommandResult, type TerminalConfig, type TerminalEntry, TerminalInputBar, type TerminalInputBarProps, type TerminalPlugin, type TerminalSlots, type TerminalSource, type TerminalTheme, TerminalThemeProvider, type TerminalWidget, VOICE_CAPTURE_WORKLET_SOURCE, VOICE_LANGUAGES, VOICE_NAMES, type VoiceButtonState, type VoiceConnection, VoiceDeck, type VoiceDeckProps, type VoiceEndReason, type VoiceEnded, type VoiceHandlers, type VoiceHost, VoiceInputButton, type VoiceInputButtonProps, type VoiceLanguage, type VoiceLevels, type VoiceName, type VoicePersona, type VoicePhase, type VoiceProgress, type VoiceReply, VoiceSession, type VoiceSessionState, type VoiceStatus, type VoiceUtterance, type WorkingGroupContext, agentSeed, agentStepTraceExtra, avatarColor, blueScope, buildClaimsGraph, buildTerminalCommand, buttonShadow, captureReasoningSnapshot, defaultTheme, dispatchProgressEvent, documentEventToActivity, emeraldCollab, emeraldRevised, encodePersona, eventToActivity, extractKGSources, extractKgId, filterCommands, formatAssetDisplay, formatPaymentAmount, formatTokenAssetId, getAgentColor, getBuiltInCommands, getClaimStatusColors, getEdgeColor, getEdgeTypeLabel, getNodeColors, getNodeTypeLabel, getVocabTypeColors, getVocabTypeLabel, getVoiceStatus, greenAction, handleSSEStream, handleStreamingResponse, hostApp, initials, isFixedKgId, isNaturalLanguage, layoutGraph, mergeTheme, openPipelineEventStream, orangeRebuttal, parseTerminalCommand, pipelineBg, processStreamLines, purple, reduceProgress, renderMarkdownBlock, resolvePaymentAssetDisplay, snapshotToSubgraph, socketBase, terminalClassNames, terminalColors, useScopedProgressEvent, useSlashMentionPicker, useTheme, useVoiceSession, voiceEndedMessage, voiceSocketUrl, warmBg, withStallTimeout };
2968
+ export { ADVANCE_PHASE_SIGNAL, AGENT_PALETTE, type AIAction, type ActivityItem, AgentAvatar, type AgentAvatarProps, type AgentAvatarSize, type AgentCompletion, AgentPresenceBar, AgentStepTrace, type AgentToolStep, type Annotation, AnnotationThread, type ApiEndpoint, type ApiParameter, AskQueue, type BaseTerminalProps, CLAIM_STATUS_COLORS, ChatTerminal, type ChatTerminalProps, CheckpointCard, type CheckpointType, ClaimNode, type ClaimNodeData, type ClaimStatus, ConstructionProgress, CoreTerminal, type CoreTerminalProps, type CreateNoteResult, DEFAULT_AGENT_LABEL, DOCUMENT_PIPELINE_STEPS, type DocumentProcessingEvent, DocumentProcessingProgress, EDGE_TYPE_COLORS, EdgeDetailDrawer, EdgeMarkerDefs, CoreTerminal as EmbeddedTerminal, type CoreTerminalProps as EmbeddedTerminalProps, ExpandableTerminal, type ExpandableTerminalProps, type FileUploadResult, type GapOption, type GapQuestion, type GenericKGSnapshot, type GenericSnapshotEdge, type GenericSnapshotNode, GraphChatPanel, type GraphChatPanelProps, GraphEdge, type GraphEdgeData, type GraphLayer, GraphNode, type GraphNodeData, type HelpTopic, InlineTerminal, type InlineTerminalProps, type KGAnnotation, type KGChunkLink$1 as KGChunkLink, type KGScope, type KGSearchResult, type KGSnapshot, type KGSnapshotEdge, type KGSnapshotNode, type KnowledgeGraphDataCallbacks, KnowledgeGraphView, LIVE_KG_ID, type LegacyKGSnapshot, type LegacyKGSnapshotEdge, type LegacyKGSnapshotNode, type LineageEntry, type ListRagFilesResult, type ListedRagFile, LiveActivityTicker, MarkdownContent, MarkdownEditor, type Mention, type MentionTarget, type MessageAudience, type MessageSender, NODE_TYPE_COLORS, NODE_TYPE_LABELS, NodeDetailDrawer, PROGRESS_EVENT_NAME, type PageSuggestion, type ParsedCommand, type PipelineBuilderAction, type PipelineDraft, type PipelineDraftStep, PipelineProgressBar, type PluginContext, type ProcessingDetail, type ProgressEventDetail, type ProgressState, type QueuedAsk, REPLACED_OUTPUT, type RagFileActionResult, ReasoningHistoryCard, ReasoningOverlay, type ReasoningOverlayProps, type ReasoningProgressEvent, type ReasoningSnapshot, ReasoningView, type ReasoningViewProps, SILENT, SSE_KEEPALIVE_MS, SSE_STALL_MS, SYNTHESIS_DONE_SIGNAL, type SaveNoteResult, ScopedReasoningProvider, ScreenAwake, SettlementBar, type SlashMentionCandidate, SlashMentionPicker, StageCanvas, StandardTerminal, type StandardTerminalProps, type StreamChunk, type StreamHandler, type StreamHandlers, type SubgraphEdge, type SubgraphNode, type TeamSetupAgent, type TerminalCommand, type TerminalCommandResult, type TerminalConfig, type TerminalEntry, TerminalInputBar, type TerminalInputBarProps, type TerminalPlugin, type TerminalSlots, type TerminalSource, type TerminalTheme, TerminalThemeProvider, type TerminalWidget, VOICE_CAPTURE_WORKLET_SOURCE, VOICE_LANGUAGES, VOICE_NAMES, type VoiceButtonState, type VoiceConnection, VoiceDeck, type VoiceDeckProps, type VoiceEndReason, type VoiceEnded, type VoiceHandlers, type VoiceHost, VoiceInputButton, type VoiceInputButtonProps, type VoiceLanguage, type VoiceLevels, type VoiceName, type VoicePersona, type VoicePhase, type VoiceProgress, type VoiceReply, VoiceSession, type VoiceSessionState, type VoiceStatus, type VoiceUtterance, type WorkingGroupContext, agentSeed, agentStepTraceExtra, avatarColor, blueScope, buildClaimsGraph, buildTerminalCommand, buttonShadow, captureReasoningSnapshot, defaultTheme, dispatchProgressEvent, documentEventToActivity, emeraldCollab, emeraldRevised, encodePersona, eventToActivity, extractKGSources, extractKgId, filterCommands, formatAssetDisplay, formatPaymentAmount, formatTokenAssetId, getAgentColor, getBuiltInCommands, getClaimStatusColors, getEdgeColor, getEdgeTypeLabel, getNodeColors, getNodeTypeLabel, getVocabTypeColors, getVocabTypeLabel, getVoiceStatus, greenAction, handleSSEStream, handleStreamingResponse, hostApp, initials, isFixedKgId, isNaturalLanguage, layoutGraph, mergeTheme, openPipelineEventStream, orangeRebuttal, parseTerminalCommand, pipelineBg, processStreamLines, purple, reduceProgress, renderMarkdownBlock, resolvePaymentAssetDisplay, snapshotToSubgraph, socketBase, terminalClassNames, terminalColors, useScopedProgressEvent, useSlashMentionPicker, useTheme, useVoiceSession, voiceEndedMessage, voiceSocketUrl, warmBg, withStallTimeout };
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * Runtime use requires a separate express license.
7
7
  */
8
8
  import React15, { createContext, memo, useMemo, useRef, useEffect, useCallback, useContext, useState, useReducer } from 'react';
9
- import { X, Check, Plus, FileText, MessageCircle, ChevronRight, Paperclip, FolderOpen, Brain, XCircle, Loader2, Minimize2, Maximize2, Mic, AlertTriangle, HelpCircle, ChevronLeft, Pencil, ChevronDown, ExternalLink, Search, Network, Boxes, Settings, Terminal, CheckCircle, ArrowRight, Users, Eye, AlertCircle, CheckCircle2, Copy, Zap, Download, Trash2, Link2 } from 'lucide-react';
9
+ import { X, Check, Plus, FileText, MessageCircle, ChevronRight, Paperclip, FolderOpen, Brain, XCircle, Loader2, Minimize2, Maximize2, Mic, MicOff, AlertTriangle, HelpCircle, ChevronLeft, Pencil, ChevronDown, ExternalLink, Search, Network, Boxes, Settings, Terminal, CheckCircle, ArrowRight, Users, Eye, AlertCircle, CheckCircle2, Copy, Zap, Download, Trash2, Link2 } from 'lucide-react';
10
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
11
  import ReactMarkdown from 'react-markdown';
12
12
  import remarkGfm from 'remark-gfm';
@@ -2225,18 +2225,21 @@ function CopyButton({ content }) {
2225
2225
  }
2226
2226
  );
2227
2227
  }
2228
+ var MESSAGES = {
2229
+ rounds_exhausted: "Partial answer \u2014 I reached the step limit. Ask a narrower follow-up to continue.",
2230
+ budget_exhausted: "Partial answer \u2014 this question needs more context than fits in one answer. Try narrowing the question.",
2231
+ deadline: "Partial answer \u2014 I ran out of time before finishing. Ask a narrower follow-up to continue.",
2232
+ output_limit: "Partial answer \u2014 it was cut off at its length limit. Ask for the rest, or a narrower follow-up.",
2233
+ awaiting_tool_results: "This answer paused before its next step. Ask again to review a fresh answer.",
2234
+ empty: "No answer was returned. Please try again.",
2235
+ failed: "The answer could not be completed. What arrived so far is kept here.",
2236
+ interrupted: "Connection interrupted. The assistant may still be working; check the conversation before sending again.",
2237
+ cancelled: "Stopped. You can change your question and ask again."
2238
+ };
2228
2239
  function AgentCompletionNotice({ completion }) {
2229
2240
  const t = useTheme();
2230
2241
  if (!completion || completion.stop_reason === "answered") return null;
2231
- const message = {
2232
- rounds_exhausted: "Partial answer \u2014 I reached the step limit. Ask a narrower follow-up to continue.",
2233
- budget_exhausted: "Partial answer \u2014 this question needs more context than fits in one answer. Try narrowing the question.",
2234
- awaiting_tool_results: "This answer paused before its next step. Ask again to review a fresh answer.",
2235
- empty: "No answer was returned. Please try again.",
2236
- failed: "The answer could not be completed. What arrived so far is kept here.",
2237
- interrupted: "Connection interrupted. The assistant may still be working; check the conversation before sending again.",
2238
- cancelled: "Stopped. You can change your question and ask again."
2239
- }[completion.stop_reason];
2242
+ const message = MESSAGES[completion.stop_reason];
2240
2243
  if (!message) return null;
2241
2244
  return /* @__PURE__ */ jsx("p", { role: "status", className: "mt-2 rounded-md px-3 py-2 text-xs", style: { color: t.textSecondary, backgroundColor: t.bgRaisedAlt, border: `1px solid ${t.border}` }, children: message });
2242
2245
  }
@@ -7847,6 +7850,54 @@ function encodePersona(persona) {
7847
7850
  return Object.keys(fields).length ? JSON.stringify(fields) : null;
7848
7851
  }
7849
7852
 
7853
+ // src/voice/screenAwake.ts
7854
+ function browserWakeLock() {
7855
+ return typeof navigator === "undefined" ? void 0 : navigator.wakeLock;
7856
+ }
7857
+ var ScreenAwake = class {
7858
+ constructor(api = browserWakeLock()) {
7859
+ this.api = api;
7860
+ this.sentinel = null;
7861
+ this.wanted = false;
7862
+ this.requesting = false;
7863
+ this.onVisibility = () => {
7864
+ if (document.visibilityState === "visible") void this.take();
7865
+ };
7866
+ }
7867
+ /** The screen is being kept on right now. */
7868
+ get held() {
7869
+ return !!this.sentinel && !this.sentinel.released;
7870
+ }
7871
+ /** Keep the screen on until `release`. Call it from the tap that starts
7872
+ * the session. */
7873
+ hold() {
7874
+ if (!this.api || this.wanted) return;
7875
+ this.wanted = true;
7876
+ document.addEventListener("visibilitychange", this.onVisibility);
7877
+ void this.take();
7878
+ }
7879
+ release() {
7880
+ if (!this.wanted) return;
7881
+ this.wanted = false;
7882
+ document.removeEventListener("visibilitychange", this.onVisibility);
7883
+ const sentinel = this.sentinel;
7884
+ this.sentinel = null;
7885
+ void sentinel?.release().catch(() => void 0);
7886
+ }
7887
+ async take() {
7888
+ if (!this.api || !this.wanted || this.held || this.requesting || document.visibilityState !== "visible") return;
7889
+ this.requesting = true;
7890
+ try {
7891
+ const sentinel = await this.api.request("screen");
7892
+ if (this.wanted) this.sentinel = sentinel;
7893
+ else void sentinel.release().catch(() => void 0);
7894
+ } catch {
7895
+ } finally {
7896
+ this.requesting = false;
7897
+ }
7898
+ }
7899
+ };
7900
+
7850
7901
  // src/voice/worklet.ts
7851
7902
  var VOICE_CAPTURE_WORKLET_SOURCE = `class YfVoiceCapture extends AudioWorkletProcessor {
7852
7903
  constructor() {
@@ -7982,6 +8033,8 @@ var VoiceSession = class {
7982
8033
  this.ready = false;
7983
8034
  this.ended = false;
7984
8035
  this.phase = null;
8036
+ /** Keeps a phone from locking mid-conversation (see `screenAwake.ts`). */
8037
+ this.awake = new ScreenAwake();
7985
8038
  /** The voice's questions to the host, one at a time (see `askQueue.ts`). */
7986
8039
  this.asks = new AskQueue(
7987
8040
  (ask, signal) => {
@@ -8004,6 +8057,7 @@ var VoiceSession = class {
8004
8057
  this.saying = "";
8005
8058
  this.endingReason = null;
8006
8059
  this.finishTimer = null;
8060
+ this.muted = false;
8007
8061
  this.blobUrl = null;
8008
8062
  }
8009
8063
  /** Call from the tap itself: iOS only lets audio start inside a gesture,
@@ -8017,6 +8071,7 @@ var VoiceSession = class {
8017
8071
  const ctx = new Ctx();
8018
8072
  this.ctx = ctx;
8019
8073
  void ctx.resume();
8074
+ this.awake.hold();
8020
8075
  this.setPhase("connecting");
8021
8076
  const status = await getVoiceStatus(this.connection);
8022
8077
  if (this.ended) return;
@@ -8032,6 +8087,7 @@ var VoiceSession = class {
8032
8087
  this.stream = await navigator.mediaDevices.getUserMedia({
8033
8088
  audio: { channelCount: 1, echoCancellation: true, noiseSuppression: true, autoGainControl: true }
8034
8089
  });
8090
+ this.applyMute();
8035
8091
  } catch (e) {
8036
8092
  this.end("mic_denied", e?.message);
8037
8093
  return;
@@ -8103,6 +8159,24 @@ var VoiceSession = class {
8103
8159
  stop() {
8104
8160
  this.end("stopped");
8105
8161
  }
8162
+ /**
8163
+ * Mute the microphone, or not. The track goes silent rather than the
8164
+ * stream stopping: the service keeps hearing (silence), so whatever the
8165
+ * person was saying ends as a turn and nothing hangs half-heard. The
8166
+ * voice can still talk while they are muted.
8167
+ */
8168
+ setMuted(muted) {
8169
+ this.muted = muted;
8170
+ this.applyMute();
8171
+ }
8172
+ get isMuted() {
8173
+ return this.muted;
8174
+ }
8175
+ applyMute() {
8176
+ this.stream?.getAudioTracks().forEach((track) => {
8177
+ track.enabled = !this.muted;
8178
+ });
8179
+ }
8106
8180
  /** Cheap enough to call every animation frame. */
8107
8181
  levels() {
8108
8182
  if (this.ended) return SILENT;
@@ -8226,6 +8300,7 @@ var VoiceSession = class {
8226
8300
  this.finishTimer = null;
8227
8301
  }
8228
8302
  release() {
8303
+ this.awake.release();
8229
8304
  this.node?.port.close();
8230
8305
  this.node?.disconnect();
8231
8306
  this.stream?.getTracks().forEach((t) => t.stop());
@@ -8320,6 +8395,7 @@ function useVoiceSession(host, connection) {
8320
8395
  const [saying, setSaying] = useState("");
8321
8396
  const [ended, setEnded] = useState(null);
8322
8397
  const [startedAt, setStartedAt] = useState(null);
8398
+ const [muted, setMuted] = useState(false);
8323
8399
  const session = useRef(null);
8324
8400
  const hostRef = useRef(host);
8325
8401
  hostRef.current = host;
@@ -8342,6 +8418,7 @@ function useVoiceSession(host, connection) {
8342
8418
  setHeard("");
8343
8419
  setSaying("");
8344
8420
  setStartedAt(null);
8421
+ setMuted(false);
8345
8422
  const s = new VoiceSession(
8346
8423
  {
8347
8424
  onPhase: (p) => {
@@ -8355,6 +8432,7 @@ function useVoiceSession(host, connection) {
8355
8432
  onReply: (r) => hostRef.current.onReply?.(r),
8356
8433
  onEnd: (reason, detail) => {
8357
8434
  if (session.current === s) session.current = null;
8435
+ setMuted(false);
8358
8436
  setPhase("off");
8359
8437
  setStartedAt(null);
8360
8438
  setEnded(reason === "stopped" || reason === "finished" ? null : { reason, detail });
@@ -8368,6 +8446,12 @@ function useVoiceSession(host, connection) {
8368
8446
  const stop = useCallback(() => session.current?.stop(), []);
8369
8447
  const toggle = useCallback(() => session.current ? stop() : start(), [start, stop]);
8370
8448
  const dismiss = useCallback(() => setEnded(null), []);
8449
+ const toggleMute = useCallback(() => {
8450
+ const s = session.current;
8451
+ if (!s) return;
8452
+ s.setMuted(!s.isMuted);
8453
+ setMuted(s.isMuted);
8454
+ }, []);
8371
8455
  const levels = useCallback(() => session.current?.levels() ?? SILENT, []);
8372
8456
  const level = useCallback(() => {
8373
8457
  const l = levels();
@@ -8386,6 +8470,8 @@ function useVoiceSession(host, connection) {
8386
8470
  levels,
8387
8471
  level,
8388
8472
  buttonState: phase === "off" ? "off" : phase === "connecting" ? "connecting" : "live",
8473
+ muted,
8474
+ toggleMute,
8389
8475
  start,
8390
8476
  stop,
8391
8477
  toggle,
@@ -8469,6 +8555,7 @@ var VOICE_DECK_CSS = `
8469
8555
  }
8470
8556
  .yf-voice-deck[data-phase='listening'] .yf-voice-beacon { animation-duration: 3.2s; }
8471
8557
  .yf-voice-phase {
8558
+ white-space: nowrap;
8472
8559
  font-size: 10.5px;
8473
8560
  font-weight: 600;
8474
8561
  letter-spacing: 0.14em;
@@ -8496,12 +8583,36 @@ var VOICE_DECK_CSS = `
8496
8583
  .yf-voice-end:hover { background: color-mix(in srgb, var(--yf-voice-ink) 10%, transparent); border-color: color-mix(in srgb, var(--yf-voice-ink) 28%, transparent); color: var(--yf-voice-ink); }
8497
8584
  .yf-voice-end:active { transform: scale(0.97); }
8498
8585
  .yf-voice-end__mark { width: 8px; height: 8px; border-radius: 2px; background: currentColor; }
8586
+ .yf-voice-mute {
8587
+ display: inline-flex;
8588
+ align-items: center;
8589
+ justify-content: center;
8590
+ width: 28px;
8591
+ height: 28px;
8592
+ border-radius: 999px;
8593
+ border: 1px solid color-mix(in srgb, var(--yf-voice-ink) 16%, transparent);
8594
+ background: color-mix(in srgb, var(--yf-voice-ink) 5%, transparent);
8595
+ color: color-mix(in srgb, var(--yf-voice-ink) 85%, transparent);
8596
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
8597
+ -webkit-tap-highlight-color: transparent;
8598
+ }
8599
+ .yf-voice-mute svg { width: 14px; height: 14px; }
8600
+ .yf-voice-mute:hover { background: color-mix(in srgb, var(--yf-voice-ink) 10%, transparent); color: var(--yf-voice-ink); }
8601
+ .yf-voice-mute:active { transform: scale(0.95); }
8602
+ .yf-voice-mute:disabled { opacity: 0.4; }
8603
+ .yf-voice-mute[aria-pressed='true'] {
8604
+ border-color: color-mix(in srgb, var(--yf-voice-accent) 55%, transparent);
8605
+ background: color-mix(in srgb, var(--yf-voice-accent) 18%, transparent);
8606
+ color: var(--yf-voice-accent);
8607
+ }
8608
+ .yf-voice-deck[data-muted] .yf-voice-beacon { background: var(--yf-voice-muted); animation: none; }
8609
+ .yf-voice-deck[data-muted] .yf-voice-phase { color: var(--yf-voice-muted); }
8499
8610
  .yf-voice-print {
8500
8611
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 12%, #000 100%);
8501
8612
  mask-image: linear-gradient(90deg, transparent 0, #000 12%, #000 100%);
8502
8613
  }
8503
8614
  .yf-voice-caption { display: flex; min-width: 0; align-items: baseline; gap: 8px; color: color-mix(in srgb, var(--yf-voice-ink) 72%, transparent); }
8504
- .yf-voice-caption--javier { display: block; color: var(--yf-voice-ink); font-weight: 450; text-wrap: pretty; }
8615
+ .yf-voice-caption--javier { display: block; color: var(--yf-voice-ink); font-weight: 450; text-wrap: pretty; overflow-wrap: anywhere; }
8505
8616
  .yf-voice-hint { color: var(--yf-voice-muted); }
8506
8617
  .yf-voice-who {
8507
8618
  flex-shrink: 0;
@@ -8511,9 +8622,27 @@ var VOICE_DECK_CSS = `
8511
8622
  text-transform: uppercase;
8512
8623
  color: var(--yf-voice-muted);
8513
8624
  }
8625
+ .yf-voice-deck[data-fill] { display: flex; flex-direction: column; height: 100%; min-height: 0; }
8626
+ .yf-voice-deck[data-fill] .yf-voice-deck__panel { flex: 1; display: flex; flex-direction: column; min-height: 0; }
8627
+ .yf-voice-detail {
8628
+ flex: 1;
8629
+ min-height: 0;
8630
+ overflow-y: auto;
8631
+ overflow-x: hidden;
8632
+ overflow-wrap: anywhere;
8633
+ overscroll-behavior: contain;
8634
+ -webkit-overflow-scrolling: touch;
8635
+ padding: 10px 16px 16px;
8636
+ border-top: 1px solid color-mix(in srgb, var(--yf-voice-ink) 8%, transparent);
8637
+ -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 14px, #000 calc(100% - 18px), transparent 100%);
8638
+ mask-image: linear-gradient(180deg, transparent 0, #000 14px, #000 calc(100% - 18px), transparent 100%);
8639
+ animation: yf-voice-rise 0.32s cubic-bezier(0.2, 0.7, 0.2, 1) both;
8640
+ }
8514
8641
  .yf-voice-deck[data-phone] { border-radius: 20px; }
8515
8642
  .yf-voice-deck[data-phone] .yf-voice-deck__panel { border-radius: 19px; }
8516
8643
  .yf-voice-deck[data-phone] .yf-voice-end { height: 40px; padding: 0 16px 0 14px; font-size: 14px; }
8644
+ .yf-voice-deck[data-phone] .yf-voice-mute { width: 40px; height: 40px; margin-right: 2px; }
8645
+ .yf-voice-deck[data-phone] .yf-voice-mute svg { width: 18px; height: 18px; }
8517
8646
  .yf-voice-deck[data-phone] .yf-voice-phase { font-size: 11.5px; }
8518
8647
  @media (prefers-reduced-motion: reduce) {
8519
8648
  .yf-voice-deck, .yf-voice-beacon, .yf-voice-deck__panel { animation: none; }
@@ -8527,7 +8656,7 @@ function ensureVoiceDeckStyles(doc = typeof document === "undefined" ? void 0 :
8527
8656
  style.textContent = VOICE_DECK_CSS;
8528
8657
  doc.head.appendChild(style);
8529
8658
  }
8530
- function VoiceDeck({ voice, name, phone = false, hint }) {
8659
+ function VoiceDeck({ voice, name, phone = false, hint, fill = false, children }) {
8531
8660
  const t = useTheme();
8532
8661
  useEffect(() => ensureVoiceDeckStyles(), []);
8533
8662
  const now = useClock(voice.startedAt !== null);
@@ -8537,9 +8666,10 @@ function VoiceDeck({ voice, name, phone = false, hint }) {
8537
8666
  connecting: "Connecting",
8538
8667
  listening: "Listening",
8539
8668
  hearing: "Hearing you",
8540
- thinking: "Working on it",
8669
+ thinking: "Working",
8541
8670
  speaking: speaker
8542
8671
  };
8672
+ const phaseWord = voice.muted && voice.phase !== "speaking" && voice.phase !== "connecting" ? "Muted" : phaseWords[voice.phase];
8543
8673
  const { startedAt, maxSessionSecs } = voice;
8544
8674
  const elapsed = startedAt === null ? 0 : now - startedAt;
8545
8675
  const remaining = maxSessionSecs > 0 && startedAt !== null ? maxSessionSecs * 1e3 - elapsed : Infinity;
@@ -8552,26 +8682,40 @@ function VoiceDeck({ voice, name, phone = false, hint }) {
8552
8682
  "--yf-voice-muted": t.textMuted,
8553
8683
  "--yf-voice-panel": t.bgRaised
8554
8684
  };
8555
- return /* @__PURE__ */ jsxs("div", { className: "yf-voice-deck", "data-phase": voice.phase, "data-phone": phone ? "" : void 0, style: vars, children: [
8685
+ return /* @__PURE__ */ jsxs("div", { className: "yf-voice-deck", "data-phase": voice.phase, "data-phone": phone ? "" : void 0, "data-fill": fill ? "" : void 0, "data-muted": voice.muted ? "" : void 0, style: vars, children: [
8556
8686
  /* @__PURE__ */ jsxs("div", { className: "yf-voice-deck__panel", children: [
8557
8687
  /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-2.5 ${phone ? "px-4 pt-3.5" : "px-4 pt-3"}`, children: [
8558
8688
  /* @__PURE__ */ jsx("span", { className: "yf-voice-beacon", "aria-hidden": "true" }),
8559
- /* @__PURE__ */ jsx("span", { role: "status", "aria-live": "polite", className: "yf-voice-phase", children: phaseWords[voice.phase] }),
8689
+ /* @__PURE__ */ jsx("span", { role: "status", "aria-live": "polite", className: "yf-voice-phase", children: phaseWord }),
8560
8690
  startedAt !== null ? /* @__PURE__ */ jsx("span", { className: "yf-voice-clock", "data-soon": endingSoon ? "" : void 0, title: endingSoon ? "Voice ends soon" : "Time in this voice session", children: endingSoon && Number.isFinite(remaining) ? `ends in ${clock(remaining)}` : clock(elapsed) }) : null,
8561
8691
  /* @__PURE__ */ jsx("span", { className: "flex-1" }),
8692
+ /* @__PURE__ */ jsx(
8693
+ "button",
8694
+ {
8695
+ type: "button",
8696
+ onClick: voice.toggleMute,
8697
+ className: "yf-voice-mute",
8698
+ "aria-pressed": voice.muted,
8699
+ "aria-label": voice.muted ? "Unmute microphone" : "Mute microphone",
8700
+ title: voice.muted ? "Unmute" : "Mute",
8701
+ disabled: voice.phase === "connecting",
8702
+ children: voice.muted ? /* @__PURE__ */ jsx(MicOff, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Mic, { "aria-hidden": "true" })
8703
+ }
8704
+ ),
8562
8705
  /* @__PURE__ */ jsxs("button", { type: "button", onClick: voice.stop, className: "yf-voice-end", "aria-label": "End voice", children: [
8563
8706
  /* @__PURE__ */ jsx("span", { className: "yf-voice-end__mark", "aria-hidden": "true" }),
8564
8707
  "End"
8565
8708
  ] })
8566
8709
  ] }),
8567
- /* @__PURE__ */ jsx(Voiceprint, { phase: voice.phase, levels: voice.levels, height: phone ? 104 : 76, cell: phone ? 13 : 11 }),
8710
+ /* @__PURE__ */ jsx(Voiceprint, { phase: voice.phase, levels: voice.levels, height: fill ? 148 : phone ? 104 : 76, cell: fill ? 14 : phone ? 13 : 11 }),
8568
8711
  /* @__PURE__ */ jsxs("div", { className: `flex flex-col gap-1.5 ${phone ? "px-4 pb-4" : "px-4 pb-3.5"}`, style: { minHeight: "1.25rem" }, "aria-live": "off", children: [
8569
8712
  words ? /* @__PURE__ */ jsxs("p", { className: "yf-voice-caption", style: { fontSize: phone ? 13.5 : 12.5 }, children: [
8570
8713
  /* @__PURE__ */ jsx("span", { className: "yf-voice-who", children: "You" }),
8571
8714
  /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: words })
8572
8715
  ] }) : null,
8573
- said ? /* @__PURE__ */ jsx("p", { className: "yf-voice-caption yf-voice-caption--javier", style: { fontSize: phone ? 16 : 14, lineHeight: phone ? 1.45 : 1.5 }, children: said }) : !words ? /* @__PURE__ */ jsx("p", { className: "yf-voice-hint", style: { fontSize: phone ? 14 : 12.5 }, children: voice.phase === "connecting" ? "Opening the line\u2026" : hint ?? "Ask anything. Talk over me to interrupt." }) : null
8574
- ] })
8716
+ said ? /* @__PURE__ */ jsx("p", { className: "yf-voice-caption yf-voice-caption--javier", style: { fontSize: phone ? 16 : 14, lineHeight: phone ? 1.45 : 1.5 }, children: said }) : !words ? /* @__PURE__ */ jsx("p", { className: "yf-voice-hint", style: { fontSize: phone ? 14 : 12.5 }, children: voice.phase === "connecting" ? "Opening the line\u2026" : voice.muted ? "Your microphone is off. Tap it to talk." : hint ?? "Ask anything. Talk over me to interrupt." }) : null
8717
+ ] }),
8718
+ fill && children ? /* @__PURE__ */ jsx("div", { className: "yf-voice-detail", children }) : null
8575
8719
  ] }),
8576
8720
  /* @__PURE__ */ jsx("span", { "data-voice-probe": "ink", hidden: true, style: { color: "var(--yf-voice-ink)" } }),
8577
8721
  /* @__PURE__ */ jsx("span", { "data-voice-probe": "accent", hidden: true, style: { color: "var(--yf-voice-accent)" } }),
@@ -10502,8 +10646,9 @@ function EdgeDetailDrawer({ edge, annotations, onClose, onNavigate }) {
10502
10646
  ]
10503
10647
  }
10504
10648
  ),
10505
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-1", style: { backgroundColor: "#1e1e1e", borderTop: "1px solid #282828", borderBottom: "1px solid #282828" }, children: /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-gray-500", children: [
10506
- "\u2193 ",
10649
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-1", style: { backgroundColor: "#1e1e1e", borderTop: "1px solid #282828", borderBottom: "1px solid #282828" }, children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-[10px] text-gray-500", children: [
10650
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3" }),
10651
+ " ",
10507
10652
  /* @__PURE__ */ jsx("span", { style: { color }, children: typeLabel })
10508
10653
  ] }) }),
10509
10654
  /* @__PURE__ */ jsxs(
@@ -11424,7 +11569,7 @@ function SchemaStage({
11424
11569
  style: { flex: 1, fontSize: 11, color: "#aaa", backgroundColor: "transparent", border: "1px solid #333", borderRadius: 4, padding: "5px 8px", outline: "none" }
11425
11570
  }
11426
11571
  ),
11427
- /* @__PURE__ */ jsx("span", { style: { color: "#555", alignSelf: "center", fontSize: 12 }, children: "\u2192" }),
11572
+ /* @__PURE__ */ jsx("span", { style: { color: "#555", alignSelf: "center", fontSize: 12 }, children: "\u203A" }),
11428
11573
  /* @__PURE__ */ jsx(
11429
11574
  "input",
11430
11575
  {
@@ -11534,7 +11679,7 @@ function SchemaStage({
11534
11679
  onMouseUp: (e) => {
11535
11680
  e.currentTarget.style.transform = "translateY(0)";
11536
11681
  },
11537
- children: "Looks good \u2014 read the document \u2192"
11682
+ children: "Looks good \u2014 read the document \u203A"
11538
11683
  }
11539
11684
  )
11540
11685
  ]
@@ -12219,7 +12364,7 @@ function StageCanvas({ nodeCount, onForceGraph, initialPhase, initialTeam, kgId
12219
12364
  {
12220
12365
  onClick: onForceGraph,
12221
12366
  style: { display: "block", margin: "16px auto 0", color: "#ff8c42", fontSize: 12, background: "none", border: "1px solid #ff8c4230", padding: "6px 16px", borderRadius: 8, cursor: "pointer" },
12222
- children: "Show Graph \u2192"
12367
+ children: "Show graph \u203A"
12223
12368
  }
12224
12369
  )
12225
12370
  ] })
@@ -15676,7 +15821,7 @@ function CheckpointCard({
15676
15821
  style: { backgroundColor: `${config.color}20`, color: config.color, border: `1px solid ${config.color}30` },
15677
15822
  children: [
15678
15823
  config.proceedLabel,
15679
- " \u2192"
15824
+ " \u203A"
15680
15825
  ]
15681
15826
  }
15682
15827
  )
@@ -15748,4 +15893,4 @@ var terminalClassNames = {
15748
15893
  promptSymbol: "terminal-prompt-symbol"
15749
15894
  };
15750
15895
 
15751
- export { ADVANCE_PHASE_SIGNAL, AGENT_PALETTE, AgentAvatar, AgentPresenceBar, AgentStepTrace, AnnotationThread, AskQueue, CLAIM_STATUS_COLORS, ChatTerminal, CheckpointCard, ClaimNode, ConstructionProgress, CoreTerminal, DEFAULT_AGENT_LABEL, DOCUMENT_PIPELINE_STEPS, DocumentProcessingProgress, EDGE_TYPE_COLORS, EdgeDetailDrawer, EdgeMarkerDefs, CoreTerminal as EmbeddedTerminal, ExpandableTerminal, GraphChatPanel, GraphEdge, GraphNode, InlineTerminal, KnowledgeGraphView, LIVE_KG_ID, LiveActivityTicker, MarkdownContent, MarkdownEditor, NODE_TYPE_COLORS, NODE_TYPE_LABELS, NodeDetailDrawer, PROGRESS_EVENT_NAME, PipelineProgressBar, REPLACED_OUTPUT, ReasoningHistoryCard, ReasoningOverlay, ReasoningView, SILENT, SSE_KEEPALIVE_MS, SSE_STALL_MS, SYNTHESIS_DONE_SIGNAL, ScopedReasoningProvider, SettlementBar, SlashMentionPicker, StageCanvas, StandardTerminal, TerminalInputBar, TerminalThemeProvider, VOICE_CAPTURE_WORKLET_SOURCE, VOICE_LANGUAGES, VOICE_NAMES, VoiceDeck, VoiceInputButton, VoiceSession, agentSeed, agentStepTraceExtra, avatarColor, blueScope, buildClaimsGraph, buildTerminalCommand, buttonShadow, captureReasoningSnapshot, defaultTheme, dispatchProgressEvent, documentEventToActivity, emeraldCollab, emeraldRevised, encodePersona, eventToActivity, extractKGSources, extractKgId, filterCommands, formatAssetDisplay, formatPaymentAmount, formatTokenAssetId, getAgentColor, getBuiltInCommands, getClaimStatusColors, getEdgeColor, getEdgeTypeLabel, getNodeColors, getNodeTypeLabel, getVocabTypeColors, getVocabTypeLabel, getVoiceStatus, greenAction, handleSSEStream, handleStreamingResponse, hostApp, initials, isFixedKgId, isNaturalLanguage, layoutGraph, mergeTheme, openPipelineEventStream, orangeRebuttal, parseTerminalCommand, pipelineBg, processStreamLines, purple, reduceProgress, renderMarkdownBlock, resolvePaymentAssetDisplay, snapshotToSubgraph, socketBase, terminalClassNames, terminalColors, useScopedProgressEvent, useSlashMentionPicker, useTheme, useVoiceSession, voiceEndedMessage, voiceSocketUrl, warmBg, withStallTimeout };
15896
+ export { ADVANCE_PHASE_SIGNAL, AGENT_PALETTE, AgentAvatar, AgentPresenceBar, AgentStepTrace, AnnotationThread, AskQueue, CLAIM_STATUS_COLORS, ChatTerminal, CheckpointCard, ClaimNode, ConstructionProgress, CoreTerminal, DEFAULT_AGENT_LABEL, DOCUMENT_PIPELINE_STEPS, DocumentProcessingProgress, EDGE_TYPE_COLORS, EdgeDetailDrawer, EdgeMarkerDefs, CoreTerminal as EmbeddedTerminal, ExpandableTerminal, GraphChatPanel, GraphEdge, GraphNode, InlineTerminal, KnowledgeGraphView, LIVE_KG_ID, LiveActivityTicker, MarkdownContent, MarkdownEditor, NODE_TYPE_COLORS, NODE_TYPE_LABELS, NodeDetailDrawer, PROGRESS_EVENT_NAME, PipelineProgressBar, REPLACED_OUTPUT, ReasoningHistoryCard, ReasoningOverlay, ReasoningView, SILENT, SSE_KEEPALIVE_MS, SSE_STALL_MS, SYNTHESIS_DONE_SIGNAL, ScopedReasoningProvider, ScreenAwake, SettlementBar, SlashMentionPicker, StageCanvas, StandardTerminal, TerminalInputBar, TerminalThemeProvider, VOICE_CAPTURE_WORKLET_SOURCE, VOICE_LANGUAGES, VOICE_NAMES, VoiceDeck, VoiceInputButton, VoiceSession, agentSeed, agentStepTraceExtra, avatarColor, blueScope, buildClaimsGraph, buildTerminalCommand, buttonShadow, captureReasoningSnapshot, defaultTheme, dispatchProgressEvent, documentEventToActivity, emeraldCollab, emeraldRevised, encodePersona, eventToActivity, extractKGSources, extractKgId, filterCommands, formatAssetDisplay, formatPaymentAmount, formatTokenAssetId, getAgentColor, getBuiltInCommands, getClaimStatusColors, getEdgeColor, getEdgeTypeLabel, getNodeColors, getNodeTypeLabel, getVocabTypeColors, getVocabTypeLabel, getVoiceStatus, greenAction, handleSSEStream, handleStreamingResponse, hostApp, initials, isFixedKgId, isNaturalLanguage, layoutGraph, mergeTheme, openPipelineEventStream, orangeRebuttal, parseTerminalCommand, pipelineBg, processStreamLines, purple, reduceProgress, renderMarkdownBlock, resolvePaymentAssetDisplay, snapshotToSubgraph, socketBase, terminalClassNames, terminalColors, useScopedProgressEvent, useSlashMentionPicker, useTheme, useVoiceSession, voiceEndedMessage, voiceSocketUrl, warmBg, withStallTimeout };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yieldfabric/terminal",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Reusable AI-powered terminal + knowledge-graph component for YieldFabric",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "type": "module",