open-agents-ai 0.185.24 → 0.185.26

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 (2) hide show
  1. package/dist/index.js +816 -240
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11914,8 +11914,8 @@ async function loadTranscribeCli() {
11914
11914
  const nvmBase = join19(homedir7(), ".nvm", "versions", "node");
11915
11915
  if (existsSync16(nvmBase)) {
11916
11916
  try {
11917
- const { readdirSync: readdirSync23 } = await import("node:fs");
11918
- for (const ver of readdirSync23(nvmBase)) {
11917
+ const { readdirSync: readdirSync24 } = await import("node:fs");
11918
+ for (const ver of readdirSync24(nvmBase)) {
11919
11919
  const tcPath = join19(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
11920
11920
  if (existsSync16(join19(tcPath, "dist", "index.js"))) {
11921
11921
  const { createRequire: createRequire6 } = await import("node:module");
@@ -13960,8 +13960,8 @@ var init_memory_metabolism = __esm({
13960
13960
  const trajDir = join23(this.cwd, ".oa", "rlm-trajectories");
13961
13961
  let lessons = [];
13962
13962
  try {
13963
- const { readdirSync: readdirSync23, readFileSync: readFileSync45 } = await import("node:fs");
13964
- const files = readdirSync23(trajDir).filter((f) => f.endsWith(".jsonl")).sort().reverse().slice(0, 3);
13963
+ const { readdirSync: readdirSync24, readFileSync: readFileSync45 } = await import("node:fs");
13964
+ const files = readdirSync24(trajDir).filter((f) => f.endsWith(".jsonl")).sort().reverse().slice(0, 3);
13965
13965
  for (const file of files) {
13966
13966
  const lines = readFileSync45(join23(trajDir, file), "utf8").split("\n").filter((l) => l.trim());
13967
13967
  for (const line of lines) {
@@ -30460,8 +30460,8 @@ var init_listen = __esm({
30460
30460
  const nvmBase = join48(homedir11(), ".nvm", "versions", "node");
30461
30461
  if (existsSync32(nvmBase)) {
30462
30462
  try {
30463
- const { readdirSync: readdirSync23 } = await import("node:fs");
30464
- for (const ver of readdirSync23(nvmBase)) {
30463
+ const { readdirSync: readdirSync24 } = await import("node:fs");
30464
+ for (const ver of readdirSync24(nvmBase)) {
30465
30465
  const tcPath = join48(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
30466
30466
  if (existsSync32(join48(tcPath, "dist", "index.js"))) {
30467
30467
  const { createRequire: createRequire6 } = await import("node:module");
@@ -35595,6 +35595,351 @@ connect();
35595
35595
  </body>
35596
35596
  </html>`;
35597
35597
  }
35598
+ function generatePersonaPlexHTML(ppWsUrl, textPrompt, voicePrompt) {
35599
+ return `<!DOCTYPE html>
35600
+ <html lang="en">
35601
+ <head>
35602
+ <meta charset="UTF-8">
35603
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
35604
+ <title>OA \u2014 PersonaPlex Voice</title>
35605
+ <style>
35606
+ * { margin: 0; padding: 0; box-sizing: border-box; }
35607
+ body {
35608
+ background: #1a1a1e;
35609
+ color: #b0b0b0;
35610
+ font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
35611
+ display: flex;
35612
+ flex-direction: column;
35613
+ min-height: 100vh;
35614
+ }
35615
+ #header {
35616
+ background: #1e1e22;
35617
+ padding: 8px 16px;
35618
+ display: flex;
35619
+ align-items: center;
35620
+ gap: 12px;
35621
+ border-bottom: 1px solid #2a2a30;
35622
+ }
35623
+ #header .accent { color: #b2920a; font-weight: bold; font-size: 0.8rem; }
35624
+ #header .badge { font-size: 0.6rem; color: #555; background: #2a2a30; padding: 2px 6px; border-radius: 2px; }
35625
+ #header .status { font-size: 0.7rem; color: #555; }
35626
+ #header .status.live { color: #b2920a; }
35627
+ #waveform {
35628
+ height: 48px;
35629
+ display: flex;
35630
+ align-items: center;
35631
+ justify-content: center;
35632
+ font-size: 1.6rem;
35633
+ line-height: 1;
35634
+ letter-spacing: 0.04em;
35635
+ white-space: nowrap;
35636
+ overflow: hidden;
35637
+ user-select: none;
35638
+ background: #1e1e22;
35639
+ border-bottom: 1px solid #2a2a30;
35640
+ }
35641
+ #conversation {
35642
+ flex: 1;
35643
+ overflow-y: auto;
35644
+ padding: 12px 16px;
35645
+ }
35646
+ .msg {
35647
+ padding: 6px 0;
35648
+ font-size: 0.82rem;
35649
+ line-height: 1.4;
35650
+ }
35651
+ .msg.user { color: #888; }
35652
+ .msg.user::before { content: '\\25B8 '; color: #555; }
35653
+ .msg.agent { color: #b2920a; }
35654
+ .msg.agent::before { content: '\\25B9 '; color: #b2920a; }
35655
+ #footer {
35656
+ background: #1e1e22;
35657
+ padding: 8px 16px;
35658
+ display: flex;
35659
+ align-items: center;
35660
+ gap: 10px;
35661
+ border-top: 1px solid #2a2a30;
35662
+ }
35663
+ button {
35664
+ background: #2a2a30;
35665
+ border: 1px solid #3a3a42;
35666
+ color: #b2920a;
35667
+ padding: 6px 16px;
35668
+ border-radius: 3px;
35669
+ font-family: inherit;
35670
+ font-size: 0.75rem;
35671
+ cursor: pointer;
35672
+ transition: background 0.15s;
35673
+ }
35674
+ button:hover { background: #3a3a42; }
35675
+ button.active { background: #3a2a10; border-color: #b2920a; }
35676
+ #footer .hint { font-size: 0.65rem; color: #444; }
35677
+ </style>
35678
+ </head>
35679
+ <body>
35680
+ <div id="header">
35681
+ <span class="accent">OA</span>
35682
+ <span class="badge">PersonaPlex 7B</span>
35683
+ <span id="status" class="status">connecting</span>
35684
+ </div>
35685
+ <div id="waveform"></div>
35686
+ <div id="conversation"></div>
35687
+ <div id="footer">
35688
+ <button id="micBtn" onclick="toggleMic()">mic</button>
35689
+ <span class="hint" id="hint">tap to start full-duplex voice</span>
35690
+ </div>
35691
+
35692
+ <script>
35693
+ const waveEl = document.getElementById('waveform');
35694
+ const statusEl = document.getElementById('status');
35695
+ const convoEl = document.getElementById('conversation');
35696
+ const micBtn = document.getElementById('micBtn');
35697
+ const hintEl = document.getElementById('hint');
35698
+
35699
+ // PersonaPlex WebSocket URL (moshi.server)
35700
+ const PP_WS_URL = ${JSON.stringify(ppWsUrl)};
35701
+ const TEXT_PROMPT = ${JSON.stringify(textPrompt)};
35702
+ const VOICE_PROMPT = ${JSON.stringify(voicePrompt)};
35703
+
35704
+ // Braille waveform
35705
+ const DENSITY = ['\\u2800','\\u2840','\\u28C0','\\u28C4','\\u28E4','\\u28E6','\\u28F6','\\u28F7','\\u28FF'];
35706
+ const WAVE = [...DENSITY, ...DENSITY.slice(1,-1).reverse()];
35707
+ const THEME_IDLE = ['#1e1e22','#2a2a30','#333338','#3e3e44','#4a4a52','#555560','#666670','#777780','#888890'];
35708
+ const THEME_SPEAKING = ['#1e1e22','#2a2510','#3a3518','#4a4520','#5a5528','#6a6530','#8a7a20','#a89010','#b2920a'];
35709
+ const THEME_LISTENING = ['#1e1e22','#2a2818','#3a3820','#4a4828','#5a5830','#6a6838','#8a8830','#a8a020','#b2920a'];
35710
+ function buildColorRamp(r) { return [...r, ...r.slice(1,-1).reverse()]; }
35711
+
35712
+ let waveFrame = 0, waveState = 'idle', micLevel = 0;
35713
+ function renderWave() {
35714
+ const cols = Math.min(40, Math.floor(waveEl.offsetWidth / 20)) || 30;
35715
+ const cl = WAVE.length;
35716
+ const theme = waveState==='speaking'?THEME_SPEAKING:waveState==='listening'?THEME_LISTENING:THEME_IDLE;
35717
+ const cr = buildColorRamp(theme);
35718
+ const bp = Math.sin(waveFrame*0.06);
35719
+ const speed = waveState==='speaking'?2.5+bp*0.5:waveState==='listening'?2.0+micLevel*3.0+bp*0.3:1.2+bp*0.8;
35720
+ const ds = waveState==='idle'?0.35+bp*0.15:waveState==='listening'?0.3+micLevel*0.7:0.6+bp*0.2;
35721
+ const sa = 1.5+ds*2.0;
35722
+ let html='';
35723
+ for(let c=0;c<cols;c++){
35724
+ const so=Math.sin(c*0.1+waveFrame*0.02)*sa;
35725
+ const rp=c*speed+waveFrame+so;
35726
+ const np=((rp%cl)+cl)%cl;
35727
+ let wi=Math.round(np)%cl;
35728
+ let amp=wi<=8?wi:16-wi;
35729
+ const sc=Math.round(amp*ds);
35730
+ let si=wi<=8?sc:16-sc;
35731
+ si=Math.max(0,Math.min(cl-1,si));
35732
+ html+='<span style="color:'+cr[si]+'">'+WAVE[si]+'</span>';
35733
+ }
35734
+ waveEl.innerHTML=html;
35735
+ }
35736
+ setInterval(()=>{waveFrame++;renderWave();},80);
35737
+
35738
+ // \u2500\u2500 PersonaPlex connection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
35739
+ let ppWs = null;
35740
+ let micCtx = null, micStream = null, micActive = false;
35741
+ let playbackCtx = null;
35742
+ let handshakeReceived = false;
35743
+ let opusEncoder = null, opusDecoder = null;
35744
+ let agentText = '';
35745
+
35746
+ function connectPP() {
35747
+ const url = PP_WS_URL + '/api/chat?text_prompt=' + encodeURIComponent(TEXT_PROMPT) +
35748
+ '&voice_prompt=' + encodeURIComponent(VOICE_PROMPT) + '&seed=-1';
35749
+ statusEl.textContent = 'connecting to PersonaPlex...';
35750
+ ppWs = new WebSocket(url);
35751
+ ppWs.binaryType = 'arraybuffer';
35752
+
35753
+ ppWs.onopen = () => {
35754
+ statusEl.textContent = 'loading persona...';
35755
+ statusEl.className = 'status';
35756
+ };
35757
+
35758
+ ppWs.onmessage = (evt) => {
35759
+ if (!(evt.data instanceof ArrayBuffer)) return;
35760
+ const bytes = new Uint8Array(evt.data);
35761
+ if (bytes.length === 0) return;
35762
+
35763
+ const kind = bytes[0];
35764
+ if (kind === 0x00) {
35765
+ // Handshake \u2014 server is ready
35766
+ handshakeReceived = true;
35767
+ statusEl.textContent = 'ready \u2014 full duplex';
35768
+ statusEl.className = 'status live';
35769
+ hintEl.textContent = 'tap mic to start talking';
35770
+ } else if (kind === 0x01) {
35771
+ // Audio (Opus) from PersonaPlex
35772
+ const opusData = bytes.slice(1);
35773
+ playOpus(opusData);
35774
+ waveState = 'speaking';
35775
+ } else if (kind === 0x02) {
35776
+ // Text token from PersonaPlex
35777
+ const text = new TextDecoder().decode(bytes.slice(1));
35778
+ agentText += text;
35779
+ // Flush on sentence boundary
35780
+ if (/[.!?]\\s*$/.test(agentText) || agentText.length > 200) {
35781
+ addTranscript('agent', agentText.trim());
35782
+ agentText = '';
35783
+ }
35784
+ }
35785
+ };
35786
+
35787
+ ppWs.onclose = () => {
35788
+ handshakeReceived = false;
35789
+ statusEl.textContent = 'disconnected';
35790
+ statusEl.className = 'status';
35791
+ if (agentText.trim()) { addTranscript('agent', agentText.trim()); agentText=''; }
35792
+ setTimeout(connectPP, 3000);
35793
+ };
35794
+
35795
+ ppWs.onerror = () => {};
35796
+ }
35797
+
35798
+ function addTranscript(speaker, text) {
35799
+ const div = document.createElement('div');
35800
+ div.className = 'msg ' + speaker;
35801
+ div.textContent = text;
35802
+ convoEl.appendChild(div);
35803
+ convoEl.scrollTop = convoEl.scrollHeight;
35804
+ }
35805
+
35806
+ // \u2500\u2500 Opus playback via WebAudio \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
35807
+ // PersonaPlex sends Opus-encoded audio via sphn. We decode via AudioDecoder or fallback.
35808
+
35809
+ async function playOpus(opusData) {
35810
+ // Use raw PCM fallback \u2014 decode Opus in a worker or use AudioContext.decodeAudioData
35811
+ // For simplicity, we'll use the AudioDecoder API if available
35812
+ if (!playbackCtx) playbackCtx = new AudioContext({ sampleRate: 24000 });
35813
+
35814
+ try {
35815
+ // Wrap opus in a minimal ogg-like container isn't practical,
35816
+ // so we attempt raw decode via AudioDecoder (Chrome 94+)
35817
+ if (typeof AudioDecoder !== 'undefined' && !opusDecoder) {
35818
+ opusDecoder = new AudioDecoder({
35819
+ output: (frame) => {
35820
+ const buf = playbackCtx.createBuffer(1, frame.numberOfFrames, frame.sampleRate);
35821
+ frame.copyTo(buf.getChannelData(0), { planeIndex: 0 });
35822
+ const src = playbackCtx.createBufferSource();
35823
+ src.buffer = buf;
35824
+ src.connect(playbackCtx.destination);
35825
+ src.start();
35826
+ frame.close();
35827
+ },
35828
+ error: (e) => { console.warn('opus decode err:', e); }
35829
+ });
35830
+ opusDecoder.configure({
35831
+ codec: 'opus',
35832
+ sampleRate: 24000,
35833
+ numberOfChannels: 1,
35834
+ });
35835
+ }
35836
+ if (opusDecoder && opusDecoder.state === 'configured') {
35837
+ opusDecoder.decode(new EncodedAudioChunk({
35838
+ type: 'key',
35839
+ timestamp: 0,
35840
+ data: opusData,
35841
+ }));
35842
+ }
35843
+ } catch (e) {
35844
+ // Fallback: skip frame
35845
+ console.warn('Opus playback unavailable:', e);
35846
+ }
35847
+ }
35848
+
35849
+ // \u2500\u2500 Microphone \u2192 Opus \u2192 PersonaPlex \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
35850
+
35851
+ let scriptProcessor = null;
35852
+
35853
+ async function toggleMic() {
35854
+ if (micActive) { stopMic(); return; }
35855
+ if (!handshakeReceived) {
35856
+ hintEl.textContent = 'waiting for PersonaPlex...';
35857
+ return;
35858
+ }
35859
+ try {
35860
+ if (!micCtx) micCtx = new AudioContext({ sampleRate: 24000 });
35861
+ micStream = await navigator.mediaDevices.getUserMedia({
35862
+ audio: { sampleRate: 24000, channelCount: 1, echoCancellation: true, noiseSuppression: true }
35863
+ });
35864
+ const source = micCtx.createMediaStreamSource(micStream);
35865
+ scriptProcessor = micCtx.createScriptProcessor(1920, 1, 1); // 80ms at 24kHz
35866
+
35867
+ // Use AudioEncoder to send Opus to PersonaPlex
35868
+ let audioEncoder = null;
35869
+ if (typeof AudioEncoder !== 'undefined') {
35870
+ audioEncoder = new AudioEncoder({
35871
+ output: (chunk) => {
35872
+ if (!ppWs || ppWs.readyState !== 1) return;
35873
+ const buf = new ArrayBuffer(chunk.byteLength + 1);
35874
+ const view = new Uint8Array(buf);
35875
+ view[0] = 0x01; // audio kind byte
35876
+ chunk.copyTo(view.subarray(1));
35877
+ ppWs.send(buf);
35878
+ },
35879
+ error: (e) => { console.warn('encode err:', e); }
35880
+ });
35881
+ audioEncoder.configure({
35882
+ codec: 'opus',
35883
+ sampleRate: 24000,
35884
+ numberOfChannels: 1,
35885
+ bitrate: 24000,
35886
+ });
35887
+ }
35888
+
35889
+ let frameCounter = 0;
35890
+ scriptProcessor.onaudioprocess = (e) => {
35891
+ if (!micActive || !ppWs || ppWs.readyState !== 1) return;
35892
+ const input = e.inputBuffer.getChannelData(0);
35893
+ micLevel = Math.min(1, rms(input) * 5);
35894
+
35895
+ if (audioEncoder && audioEncoder.state === 'configured') {
35896
+ const data = new AudioData({
35897
+ format: 'f32',
35898
+ sampleRate: 24000,
35899
+ numberOfFrames: input.length,
35900
+ numberOfChannels: 1,
35901
+ timestamp: frameCounter * (input.length / 24000) * 1e6,
35902
+ data: input,
35903
+ });
35904
+ audioEncoder.encode(data);
35905
+ data.close();
35906
+ frameCounter++;
35907
+ }
35908
+ };
35909
+ source.connect(scriptProcessor);
35910
+ scriptProcessor.connect(micCtx.destination);
35911
+ micActive = true;
35912
+ waveState = 'listening';
35913
+ micBtn.textContent = 'stop';
35914
+ micBtn.classList.add('active');
35915
+ hintEl.textContent = 'full-duplex active \u2014 speak freely';
35916
+ } catch (err) {
35917
+ hintEl.textContent = 'mic error: ' + err.message;
35918
+ }
35919
+ }
35920
+
35921
+ function stopMic() {
35922
+ micActive = false;
35923
+ micLevel = 0;
35924
+ waveState = 'idle';
35925
+ if (scriptProcessor) { scriptProcessor.disconnect(); scriptProcessor = null; }
35926
+ if (micStream) { micStream.getTracks().forEach(t => t.stop()); micStream = null; }
35927
+ micBtn.textContent = 'mic';
35928
+ micBtn.classList.remove('active');
35929
+ hintEl.textContent = 'tap mic to start talking';
35930
+ }
35931
+
35932
+ function rms(arr) {
35933
+ let sum = 0;
35934
+ for (let i = 0; i < arr.length; i++) sum += arr[i] * arr[i];
35935
+ return Math.sqrt(sum / arr.length);
35936
+ }
35937
+
35938
+ connectPP();
35939
+ </script>
35940
+ </body>
35941
+ </html>`;
35942
+ }
35598
35943
  function renderVoiceSessionStart(tunnelUrl) {
35599
35944
  process.stdout.write(`
35600
35945
  ${c2.cyan("\u2601")} ${c2.bold("Live Voice Session")}
@@ -35641,6 +35986,10 @@ var init_voice_session = __esm({
35641
35986
  runtimeTimer = null;
35642
35987
  idleTimer = null;
35643
35988
  ttsSpeaking = false;
35989
+ /** When set, serve PersonaPlex frontend that connects directly to this moshi.server WS URL */
35990
+ personaPlexWsUrl = null;
35991
+ personaPlexTextPrompt = "You enjoy having a good conversation.";
35992
+ personaPlexVoicePrompt = "NATF2.pt";
35644
35993
  /** Idle timeout before auto-closing (ms). Default 60s — no users connected → session ends. */
35645
35994
  idleTimeoutMs = 6e4;
35646
35995
  /** Callback invoked when user audio chunk arrives (PCM 16kHz 16-bit mono) */
@@ -35829,7 +36178,11 @@ var init_voice_session = __esm({
35829
36178
  handleHTTP(req, res) {
35830
36179
  if (req.url === "/" || req.url === "/index.html") {
35831
36180
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
35832
- res.end(generateFrontendHTML());
36181
+ if (this.personaPlexWsUrl) {
36182
+ res.end(generatePersonaPlexHTML(this.personaPlexWsUrl, this.personaPlexTextPrompt, this.personaPlexVoicePrompt));
36183
+ } else {
36184
+ res.end(generateFrontendHTML());
36185
+ }
35833
36186
  } else if (req.url === "/health") {
35834
36187
  res.writeHead(200, { "Content-Type": "application/json" });
35835
36188
  res.end(JSON.stringify({
@@ -40727,18 +41080,24 @@ var init_tui_select = __esm({
40727
41080
  // packages/cli/dist/tui/personaplex.js
40728
41081
  var personaplex_exports = {};
40729
41082
  __export(personaplex_exports, {
41083
+ autoSetupPersonaPlex: () => autoSetupPersonaPlex,
41084
+ clonePersonaPlexVoice: () => clonePersonaPlexVoice,
40730
41085
  detectPersonaPlexCapability: () => detectPersonaPlexCapability,
40731
41086
  getPersonaPlexWSUrl: () => getPersonaPlexWSUrl,
40732
41087
  installPersonaPlex: () => installPersonaPlex,
40733
41088
  isPersonaPlexInstalled: () => isPersonaPlexInstalled,
40734
41089
  isPersonaPlexRunning: () => isPersonaPlexRunning,
41090
+ listPersonaPlexVoices: () => listPersonaPlexVoices,
41091
+ patchFrontendVoiceList: () => patchFrontendVoiceList,
41092
+ provisionShippedVoices: () => provisionShippedVoices,
40735
41093
  startPersonaPlexDaemon: () => startPersonaPlexDaemon,
40736
41094
  stopPersonaPlex: () => stopPersonaPlex
40737
41095
  });
40738
- import { existsSync as existsSync37, writeFileSync as writeFileSync16, readFileSync as readFileSync28, mkdirSync as mkdirSync15 } from "node:fs";
40739
- import { join as join54 } from "node:path";
41096
+ import { existsSync as existsSync37, writeFileSync as writeFileSync16, readFileSync as readFileSync28, mkdirSync as mkdirSync15, copyFileSync as copyFileSync2, readdirSync as readdirSync11 } from "node:fs";
41097
+ import { join as join54, dirname as dirname18 } from "node:path";
40740
41098
  import { homedir as homedir13 } from "node:os";
40741
41099
  import { execSync as execSync27, spawn as spawn19 } from "node:child_process";
41100
+ import { fileURLToPath as fileURLToPath11 } from "node:url";
40742
41101
  function detectPersonaPlexCapability() {
40743
41102
  try {
40744
41103
  const nvsmi = execSync27("nvidia-smi --query-gpu=name,memory.total --format=csv,noheader,nounits", {
@@ -40786,7 +41145,7 @@ function getPersonaPlexWSUrl() {
40786
41145
  if (!existsSync37(PORT_FILE))
40787
41146
  return null;
40788
41147
  const port = parseInt(readFileSync28(PORT_FILE, "utf8").trim(), 10);
40789
- return isNaN(port) ? null : `ws://127.0.0.1:${port}`;
41148
+ return isNaN(port) ? null : `wss://127.0.0.1:${port}`;
40790
41149
  }
40791
41150
  function isPersonaPlexInstalled() {
40792
41151
  return existsSync37(join54(PERSONAPLEX_DIR, "model_ready"));
@@ -40807,167 +41166,57 @@ async function installPersonaPlex(onInfo) {
40807
41166
  }
40808
41167
  const pip = process.platform === "win32" ? join54(venvDir, "Scripts", "pip.exe") : join54(venvDir, "bin", "pip");
40809
41168
  const python = process.platform === "win32" ? join54(venvDir, "Scripts", "python.exe") : join54(venvDir, "bin", "python3");
40810
- log("Installing PersonaPlex dependencies (torch, transformers, websockets)...");
41169
+ log("Checking system dependencies (libopus)...");
40811
41170
  try {
40812
- execSync27(`"${pip}" install --quiet torch torchaudio transformers websockets soundfile huggingface_hub`, { timeout: 3e5, stdio: "pipe" });
40813
- } catch (err) {
40814
- log(`Dependency install failed: ${err instanceof Error ? err.message : String(err)}`);
40815
- return false;
41171
+ if (process.platform === "linux") {
41172
+ execSync27("dpkg -l libopus-dev 2>/dev/null || sudo apt-get install -y libopus-dev", { timeout: 3e4, stdio: "pipe" });
41173
+ } else if (process.platform === "darwin") {
41174
+ execSync27("brew list opus 2>/dev/null || brew install opus", { timeout: 6e4, stdio: "pipe" });
41175
+ }
41176
+ } catch {
40816
41177
  }
40817
- log("Downloading PersonaPlex-7B model (~14GB, this may take a while)...");
41178
+ log("Installing PersonaPlex (moshi package)...");
41179
+ const repoDir = join54(PERSONAPLEX_DIR, "personaplex-repo");
40818
41180
  try {
40819
- execSync27(`"${python}" -c "from huggingface_hub import snapshot_download; snapshot_download('nvidia/personaplex-7b-v1', local_dir='${join54(PERSONAPLEX_DIR, "model").replace(/\\/g, "\\\\")}')"`, { timeout: 36e5, stdio: "pipe" });
41181
+ if (!existsSync37(repoDir)) {
41182
+ execSync27(`git clone https://github.com/NVIDIA/personaplex.git "${repoDir}"`, { timeout: 12e4, stdio: "pipe" });
41183
+ }
41184
+ execSync27(`"${pip}" install --quiet "${join54(repoDir, "moshi")}/."`, { timeout: 3e5, stdio: "pipe" });
40820
41185
  } catch (err) {
40821
- log(`Model download failed: ${err instanceof Error ? err.message : String(err)}`);
41186
+ log(`Moshi install failed: ${err instanceof Error ? err.message : String(err)}`);
41187
+ try {
41188
+ execSync27(`"${pip}" install --quiet torch torchaudio websockets soundfile huggingface_hub`, { timeout: 3e5, stdio: "pipe" });
41189
+ } catch {
41190
+ }
40822
41191
  return false;
40823
41192
  }
41193
+ const serverPy = join54(venvDir, "lib", `python3.${process.versions.node ? "12" : "10"}`, "site-packages", "moshi", "server.py");
41194
+ try {
41195
+ const sitePackages = execSync27(`"${python}" -c "import moshi, os; print(os.path.dirname(moshi.__file__))"`, {
41196
+ encoding: "utf8",
41197
+ timeout: 5e3,
41198
+ stdio: "pipe"
41199
+ }).trim();
41200
+ const serverFile = join54(sitePackages, "server.py");
41201
+ if (existsSync37(serverFile)) {
41202
+ let src = readFileSync28(serverFile, "utf8");
41203
+ if (src.includes('int(request["seed"])')) {
41204
+ src = src.replace('int(request["seed"])', 'int(request.query["seed"])');
41205
+ writeFileSync16(serverFile, src);
41206
+ log("Applied seed parameter bug fix to moshi server.");
41207
+ }
41208
+ }
41209
+ } catch {
41210
+ }
41211
+ log("PersonaPlex installed. Model will download on first launch (~14GB).");
40824
41212
  writeFileSync16(join54(PERSONAPLEX_DIR, "model_ready"), (/* @__PURE__ */ new Date()).toISOString());
40825
41213
  log("PersonaPlex installed successfully.");
40826
41214
  return true;
40827
41215
  }
40828
- function writeDaemonScript() {
40829
- const scriptPath2 = join54(PERSONAPLEX_DIR, "daemon.py");
40830
- const modelDir2 = join54(PERSONAPLEX_DIR, "model");
40831
- const script = `#!/usr/bin/env python3
40832
- """PersonaPlex daemon \u2014 full-duplex voice WebSocket server."""
40833
- import asyncio
40834
- import json
40835
- import os
40836
- import signal
40837
- import sys
40838
- import numpy as np
40839
-
40840
- # Write PID file
40841
- pid_file = os.path.join(os.path.dirname(__file__), "daemon.pid")
40842
- with open(pid_file, "w") as f:
40843
- f.write(str(os.getpid()))
40844
-
40845
- try:
40846
- import torch
40847
- import torchaudio
40848
- from transformers import AutoModel, AutoProcessor
40849
- import websockets
40850
- import soundfile as sf
40851
- except ImportError as e:
40852
- print(f"Missing dependency: {e}", file=sys.stderr)
40853
- sys.exit(1)
40854
-
40855
- MODEL_DIR = ${JSON.stringify(modelDir2)}
40856
- SAMPLE_RATE = 24000
40857
- PORT = int(os.environ.get("PERSONAPLEX_PORT", "0")) # 0 = auto-assign
40858
-
40859
- print("Loading PersonaPlex model...", file=sys.stderr)
40860
- device = "cuda" if torch.cuda.is_available() else "cpu"
40861
- dtype = torch.bfloat16 if device == "cuda" else torch.float32
40862
-
40863
- try:
40864
- model = AutoModel.from_pretrained(MODEL_DIR, trust_remote_code=True, torch_dtype=dtype)
40865
- model = model.to(device)
40866
- model.eval()
40867
- processor = AutoProcessor.from_pretrained(MODEL_DIR, trust_remote_code=True)
40868
- print(f"Model loaded on {device}", file=sys.stderr)
40869
- except Exception as e:
40870
- print(f"Model load failed: {e}", file=sys.stderr)
40871
- sys.exit(1)
40872
-
40873
- # Persona state
40874
- persona_text = "You are a helpful voice assistant."
40875
- voice_prompt = None # Audio conditioning tensor
40876
-
40877
- async def handle_client(websocket, path):
40878
- """Handle a single WebSocket client connection."""
40879
- global persona_text, voice_prompt
40880
- print(f"Client connected from {websocket.remote_address}", file=sys.stderr)
40881
-
40882
- try:
40883
- async for message in websocket:
40884
- if isinstance(message, str):
40885
- # JSON control message
40886
- try:
40887
- msg = json.loads(message)
40888
- if msg.get("type") == "persona":
40889
- persona_text = msg.get("text", persona_text)
40890
- print(f"Persona updated: {persona_text[:60]}", file=sys.stderr)
40891
- elif msg.get("type") == "voice_prompt":
40892
- # Audio conditioning: base64 PCM \u2192 tensor
40893
- import base64
40894
- pcm_bytes = base64.b64decode(msg["audio"])
40895
- audio = np.frombuffer(pcm_bytes, dtype=np.int16).astype(np.float32) / 32768.0
40896
- voice_prompt = torch.tensor(audio, dtype=dtype, device=device).unsqueeze(0)
40897
- print(f"Voice prompt set ({len(audio)} samples)", file=sys.stderr)
40898
- elif msg.get("type") == "keepalive":
40899
- await websocket.send(json.dumps({"type": "pong"}))
40900
- except json.JSONDecodeError:
40901
- pass
40902
- else:
40903
- # Binary audio data: PCM Int16 @ 24kHz
40904
- pcm = np.frombuffer(message, dtype=np.int16).astype(np.float32) / 32768.0
40905
- audio_tensor = torch.tensor(pcm, dtype=dtype, device=device).unsqueeze(0)
40906
-
40907
- # Generate response
40908
- with torch.no_grad():
40909
- inputs = processor(
40910
- audio=audio_tensor,
40911
- text=persona_text,
40912
- sampling_rate=SAMPLE_RATE,
40913
- return_tensors="pt"
40914
- ).to(device)
40915
-
40916
- if voice_prompt is not None:
40917
- inputs["voice_prompt"] = voice_prompt
40918
-
40919
- outputs = model.generate(**inputs, max_new_tokens=512)
40920
-
40921
- # Extract audio response
40922
- if hasattr(outputs, "audio"):
40923
- response_pcm = outputs.audio.cpu().numpy().flatten()
40924
- else:
40925
- response_pcm = outputs[0].cpu().numpy().flatten()
40926
-
40927
- # Convert to Int16 and send
40928
- response_int16 = (response_pcm * 32767).astype(np.int16)
40929
- await websocket.send(response_int16.tobytes())
40930
-
40931
- # Send text if available
40932
- if hasattr(outputs, "text") and outputs.text:
40933
- await websocket.send(json.dumps({
40934
- "type": "transcript",
40935
- "speaker": "agent",
40936
- "text": outputs.text
40937
- }))
40938
-
40939
- except websockets.exceptions.ConnectionClosed:
40940
- print("Client disconnected", file=sys.stderr)
40941
-
40942
- async def main():
40943
- port = PORT
40944
- server = await websockets.serve(handle_client, "127.0.0.1", port)
40945
- actual_port = server.sockets[0].getsockname()[1]
40946
-
40947
- # Write port file
40948
- port_file = os.path.join(os.path.dirname(__file__), "daemon.port")
40949
- with open(port_file, "w") as f:
40950
- f.write(str(actual_port))
40951
-
40952
- print(f"PersonaPlex daemon listening on ws://127.0.0.1:{actual_port}", file=sys.stderr)
40953
-
40954
- # Handle shutdown
40955
- loop = asyncio.get_event_loop()
40956
- stop = asyncio.Future()
40957
- for sig in (signal.SIGINT, signal.SIGTERM):
40958
- loop.add_signal_handler(sig, stop.set_result, None)
40959
- await stop
40960
- server.close()
40961
- await server.wait_closed()
40962
-
40963
- asyncio.run(main())
40964
- `;
40965
- writeFileSync16(scriptPath2, script, "utf8");
40966
- return scriptPath2;
40967
- }
40968
41216
  async function startPersonaPlexDaemon(onInfo) {
40969
41217
  const log = onInfo ?? (() => {
40970
41218
  });
41219
+ const PORT = 8998;
40971
41220
  if (isPersonaPlexRunning()) {
40972
41221
  const url = getPersonaPlexWSUrl();
40973
41222
  if (url) {
@@ -40980,29 +41229,63 @@ async function startPersonaPlexDaemon(onInfo) {
40980
41229
  return null;
40981
41230
  }
40982
41231
  mkdirSync15(PERSONAPLEX_DIR, { recursive: true });
40983
- const scriptPath2 = writeDaemonScript();
40984
41232
  const venvPython2 = process.platform === "win32" ? join54(PERSONAPLEX_DIR, "venv", "Scripts", "python.exe") : join54(PERSONAPLEX_DIR, "venv", "bin", "python3");
40985
- log("Starting PersonaPlex daemon...");
40986
- const child = spawn19(venvPython2, [scriptPath2], {
41233
+ const sslDir = join54(PERSONAPLEX_DIR, "ssl");
41234
+ mkdirSync15(sslDir, { recursive: true });
41235
+ log("Starting PersonaPlex daemon (loading ~7B model)...");
41236
+ const child = spawn19(venvPython2, [
41237
+ "-m",
41238
+ "moshi.server",
41239
+ "--host",
41240
+ "0.0.0.0",
41241
+ "--port",
41242
+ String(PORT),
41243
+ "--ssl",
41244
+ sslDir,
41245
+ "--device",
41246
+ "cuda"
41247
+ ], {
40987
41248
  stdio: ["ignore", "pipe", "pipe"],
40988
41249
  detached: true,
40989
- env: { ...process.env, PERSONAPLEX_PORT: "0" }
41250
+ env: { ...process.env },
41251
+ cwd: PERSONAPLEX_DIR
40990
41252
  });
41253
+ if (child.pid) {
41254
+ writeFileSync16(PID_FILE, String(child.pid));
41255
+ writeFileSync16(PORT_FILE, String(PORT));
41256
+ }
40991
41257
  child.unref();
41258
+ const { createWriteStream } = await import("node:fs");
41259
+ const logStream = createWriteStream(LOG_FILE, { flags: "w" });
41260
+ child.stdout?.pipe(logStream);
41261
+ child.stderr?.pipe(logStream);
40992
41262
  const startTime = Date.now();
40993
- const timeout = 6e4;
41263
+ const timeout = 12e4;
40994
41264
  while (Date.now() - startTime < timeout) {
40995
- if (existsSync37(PORT_FILE)) {
40996
- const port = parseInt(readFileSync28(PORT_FILE, "utf8").trim(), 10);
40997
- if (!isNaN(port) && port > 0) {
40998
- const url = `ws://127.0.0.1:${port}`;
40999
- log(`PersonaPlex ready at ${url}`);
41000
- return url;
41001
- }
41265
+ try {
41266
+ if (child.pid)
41267
+ process.kill(child.pid, 0);
41268
+ } catch {
41269
+ log("PersonaPlex daemon exited unexpectedly. Check daemon.log.");
41270
+ return null;
41002
41271
  }
41003
- await new Promise((r) => setTimeout(r, 1e3));
41272
+ try {
41273
+ execSync27(`curl -sk -o /dev/null -w "%{http_code}" https://127.0.0.1:${PORT}/`, {
41274
+ timeout: 3e3,
41275
+ stdio: "pipe",
41276
+ encoding: "utf8"
41277
+ });
41278
+ const url = `wss://127.0.0.1:${PORT}`;
41279
+ log(`PersonaPlex ready at ${url}`);
41280
+ return url;
41281
+ } catch {
41282
+ }
41283
+ await new Promise((r) => setTimeout(r, 2e3));
41284
+ const elapsed = Math.round((Date.now() - startTime) / 1e3);
41285
+ if (elapsed % 10 === 0)
41286
+ log(`Still loading... (${elapsed}s)`);
41004
41287
  }
41005
- log("PersonaPlex daemon failed to start within 60s.");
41288
+ log("PersonaPlex daemon failed to start within 120s. Check daemon.log.");
41006
41289
  return null;
41007
41290
  }
41008
41291
  function stopPersonaPlex() {
@@ -41020,7 +41303,260 @@ function stopPersonaPlex() {
41020
41303
  } catch {
41021
41304
  }
41022
41305
  }
41023
- var PERSONAPLEX_DIR, PID_FILE, PORT_FILE, LOG_FILE;
41306
+ function listPersonaPlexVoices() {
41307
+ const voices = [];
41308
+ const builtins = [
41309
+ "NATF0",
41310
+ "NATF1",
41311
+ "NATF2",
41312
+ "NATF3",
41313
+ "NATM0",
41314
+ "NATM1",
41315
+ "NATM2",
41316
+ "NATM3",
41317
+ "VARF0",
41318
+ "VARF1",
41319
+ "VARF2",
41320
+ "VARF3",
41321
+ "VARF4",
41322
+ "VARM0",
41323
+ "VARM1",
41324
+ "VARM2",
41325
+ "VARM3",
41326
+ "VARM4"
41327
+ ];
41328
+ for (const name of builtins) {
41329
+ voices.push({ name, type: "builtin", path: `${name}.pt` });
41330
+ }
41331
+ if (existsSync37(CUSTOM_VOICES_DIR)) {
41332
+ try {
41333
+ const { readdirSync: readdirSync24 } = __require("node:fs");
41334
+ for (const f of readdirSync24(CUSTOM_VOICES_DIR)) {
41335
+ if (f.endsWith(".pt")) {
41336
+ const name = f.replace(/\.pt$/, "");
41337
+ voices.push({ name, type: "custom", path: join54(CUSTOM_VOICES_DIR, f) });
41338
+ }
41339
+ }
41340
+ } catch {
41341
+ }
41342
+ }
41343
+ return voices;
41344
+ }
41345
+ async function clonePersonaPlexVoice(inputWav, voiceName, onInfo) {
41346
+ const log = onInfo ?? (() => {
41347
+ });
41348
+ if (!isPersonaPlexInstalled()) {
41349
+ log("PersonaPlex not installed. Run /voice personaplex first.");
41350
+ return null;
41351
+ }
41352
+ if (!existsSync37(inputWav)) {
41353
+ log(`Input WAV not found: ${inputWav}`);
41354
+ return null;
41355
+ }
41356
+ mkdirSync15(CUSTOM_VOICES_DIR, { recursive: true });
41357
+ const outputPt = join54(CUSTOM_VOICES_DIR, `${voiceName}.pt`);
41358
+ if (existsSync37(outputPt)) {
41359
+ log(`Voice "${voiceName}" already exists. Delete ${outputPt} to re-clone.`);
41360
+ return outputPt;
41361
+ }
41362
+ const venvPython2 = process.platform === "win32" ? join54(PERSONAPLEX_DIR, "venv", "Scripts", "python.exe") : join54(PERSONAPLEX_DIR, "venv", "bin", "python3");
41363
+ const cloneScript = join54(PERSONAPLEX_DIR, "clone-voice.py");
41364
+ if (!existsSync37(cloneScript)) {
41365
+ log("clone-voice.py not found. Reinstall PersonaPlex.");
41366
+ return null;
41367
+ }
41368
+ log(`Cloning voice "${voiceName}" from ${inputWav}...`);
41369
+ log("This requires loading the full 7B model \u2014 may take 30-60s...");
41370
+ return new Promise((resolve36) => {
41371
+ const child = spawn19(venvPython2, [
41372
+ cloneScript,
41373
+ "--input",
41374
+ inputWav,
41375
+ "--name",
41376
+ voiceName,
41377
+ "--device",
41378
+ "cuda"
41379
+ ], {
41380
+ stdio: ["ignore", "pipe", "pipe"],
41381
+ env: { ...process.env },
41382
+ cwd: PERSONAPLEX_DIR
41383
+ });
41384
+ let output = "";
41385
+ child.stdout?.on("data", (d) => {
41386
+ const line = d.toString();
41387
+ output += line;
41388
+ for (const l of line.split("\n").filter((s) => s.trim())) {
41389
+ log(l.trim());
41390
+ }
41391
+ });
41392
+ child.stderr?.on("data", (d) => {
41393
+ output += d.toString();
41394
+ });
41395
+ child.on("close", (code) => {
41396
+ if (code === 0 && existsSync37(outputPt)) {
41397
+ log(`Voice "${voiceName}" cloned successfully.`);
41398
+ resolve36(outputPt);
41399
+ } else {
41400
+ log(`Voice cloning failed (exit ${code}).`);
41401
+ resolve36(null);
41402
+ }
41403
+ });
41404
+ });
41405
+ }
41406
+ function getShippedVoicesDir() {
41407
+ const candidates = [
41408
+ join54(PERSONAPLEX_DIR, "shipped_voices"),
41409
+ // cached copy
41410
+ join54(process.cwd(), "voices", "personaplex")
41411
+ // repo root
41412
+ ];
41413
+ try {
41414
+ const modDir = dirname18(fileURLToPath11(import.meta.url));
41415
+ candidates.push(join54(modDir, "..", "..", "..", "voices", "personaplex"));
41416
+ candidates.push(join54(modDir, "..", "..", "..", "..", "voices", "personaplex"));
41417
+ } catch {
41418
+ }
41419
+ for (const dir of candidates) {
41420
+ if (existsSync37(dir)) {
41421
+ try {
41422
+ const files = readdirSync11(dir);
41423
+ if (files.some((f) => f.endsWith(".pt")))
41424
+ return dir;
41425
+ } catch {
41426
+ }
41427
+ }
41428
+ }
41429
+ return null;
41430
+ }
41431
+ function provisionShippedVoices(onInfo) {
41432
+ const log = onInfo ?? (() => {
41433
+ });
41434
+ const shippedDir = getShippedVoicesDir();
41435
+ if (!shippedDir)
41436
+ return 0;
41437
+ const hfVoicesDir = getHFVoicesDir();
41438
+ mkdirSync15(CUSTOM_VOICES_DIR, { recursive: true });
41439
+ let deployed = 0;
41440
+ try {
41441
+ for (const f of readdirSync11(shippedDir)) {
41442
+ if (!f.endsWith(".pt"))
41443
+ continue;
41444
+ const customDst = join54(CUSTOM_VOICES_DIR, f);
41445
+ if (!existsSync37(customDst)) {
41446
+ copyFileSync2(join54(shippedDir, f), customDst);
41447
+ }
41448
+ if (hfVoicesDir) {
41449
+ const hfDst = join54(hfVoicesDir, f);
41450
+ if (!existsSync37(hfDst)) {
41451
+ copyFileSync2(join54(shippedDir, f), hfDst);
41452
+ log(`Deployed voice: ${f.replace(".pt", "")}`);
41453
+ deployed++;
41454
+ }
41455
+ }
41456
+ }
41457
+ } catch {
41458
+ }
41459
+ const shippedScript = join54(shippedDir, "clone-voice.py");
41460
+ const targetScript = join54(PERSONAPLEX_DIR, "clone-voice.py");
41461
+ if (existsSync37(shippedScript) && !existsSync37(targetScript)) {
41462
+ try {
41463
+ copyFileSync2(shippedScript, targetScript);
41464
+ } catch {
41465
+ }
41466
+ }
41467
+ return deployed;
41468
+ }
41469
+ function getHFVoicesDir() {
41470
+ const hfBase = join54(homedir13(), ".cache", "huggingface", "hub", "models--nvidia--personaplex-7b-v1");
41471
+ if (!existsSync37(hfBase))
41472
+ return null;
41473
+ try {
41474
+ const snapshots = join54(hfBase, "snapshots");
41475
+ if (!existsSync37(snapshots))
41476
+ return null;
41477
+ for (const snap of readdirSync11(snapshots)) {
41478
+ const voicesDir = join54(snapshots, snap, "voices");
41479
+ if (existsSync37(voicesDir))
41480
+ return voicesDir;
41481
+ }
41482
+ } catch {
41483
+ }
41484
+ return null;
41485
+ }
41486
+ function patchFrontendVoiceList(onInfo) {
41487
+ const log = onInfo ?? (() => {
41488
+ });
41489
+ const hfBase = join54(homedir13(), ".cache", "huggingface", "hub", "models--nvidia--personaplex-7b-v1");
41490
+ if (!existsSync37(hfBase))
41491
+ return;
41492
+ try {
41493
+ const snapshots = join54(hfBase, "snapshots");
41494
+ for (const snap of readdirSync11(snapshots)) {
41495
+ const distDir = join54(snapshots, snap, "dist", "assets");
41496
+ if (!existsSync37(distDir))
41497
+ continue;
41498
+ for (const f of readdirSync11(distDir)) {
41499
+ if (!f.startsWith("index-") || !f.endsWith(".js"))
41500
+ continue;
41501
+ const jsPath = join54(distDir, f);
41502
+ let js = readFileSync28(jsPath, "utf8");
41503
+ const customVoices = [];
41504
+ if (existsSync37(CUSTOM_VOICES_DIR)) {
41505
+ for (const vf of readdirSync11(CUSTOM_VOICES_DIR)) {
41506
+ if (vf.endsWith(".pt")) {
41507
+ const name = vf.replace(".pt", "");
41508
+ if (!js.includes(`"${vf}"`)) {
41509
+ customVoices.push(vf);
41510
+ }
41511
+ }
41512
+ }
41513
+ }
41514
+ if (customVoices.length === 0)
41515
+ continue;
41516
+ const needle = '"VARM4.pt"]';
41517
+ if (js.includes(needle)) {
41518
+ const additions = customVoices.map((v) => `"${v}"`).join(", ");
41519
+ js = js.replace(needle, `"VARM4.pt", ${additions}]`);
41520
+ writeFileSync16(jsPath, js);
41521
+ log(`Added ${customVoices.length} custom voice(s) to frontend: ${customVoices.map((v) => v.replace(".pt", "")).join(", ")}`);
41522
+ }
41523
+ }
41524
+ }
41525
+ } catch {
41526
+ }
41527
+ }
41528
+ async function autoSetupPersonaPlex(onInfo) {
41529
+ const log = onInfo ?? (() => {
41530
+ });
41531
+ const caps = detectPersonaPlexCapability();
41532
+ if (!caps.supported) {
41533
+ log(`PersonaPlex not available: ${caps.reason}`);
41534
+ return null;
41535
+ }
41536
+ log(`GPU: ${caps.gpuName} (${caps.vramGB.toFixed(0)}GB) \u2014 PersonaPlex compatible`);
41537
+ if (!isPersonaPlexInstalled()) {
41538
+ log("Installing PersonaPlex (first time setup)...");
41539
+ const ok = await installPersonaPlex(log);
41540
+ if (!ok) {
41541
+ log("PersonaPlex installation failed.");
41542
+ return null;
41543
+ }
41544
+ }
41545
+ const deployed = provisionShippedVoices(log);
41546
+ if (deployed > 0) {
41547
+ log(`Provisioned ${deployed} shipped voice(s)`);
41548
+ }
41549
+ patchFrontendVoiceList(log);
41550
+ if (isPersonaPlexRunning()) {
41551
+ const url = getPersonaPlexWSUrl();
41552
+ if (url) {
41553
+ log(`PersonaPlex already running at ${url}`);
41554
+ return url;
41555
+ }
41556
+ }
41557
+ return await startPersonaPlexDaemon(log);
41558
+ }
41559
+ var PERSONAPLEX_DIR, PID_FILE, PORT_FILE, LOG_FILE, CUSTOM_VOICES_DIR;
41024
41560
  var init_personaplex = __esm({
41025
41561
  "packages/cli/dist/tui/personaplex.js"() {
41026
41562
  "use strict";
@@ -41029,6 +41565,7 @@ var init_personaplex = __esm({
41029
41565
  PID_FILE = join54(PERSONAPLEX_DIR, "daemon.pid");
41030
41566
  PORT_FILE = join54(PERSONAPLEX_DIR, "daemon.port");
41031
41567
  LOG_FILE = join54(PERSONAPLEX_DIR, "daemon.log");
41568
+ CUSTOM_VOICES_DIR = join54(PERSONAPLEX_DIR, "custom_voices");
41032
41569
  }
41033
41570
  });
41034
41571
 
@@ -44539,8 +45076,8 @@ __export(voice_exports, {
44539
45076
  registerCustomOnnxModel: () => registerCustomOnnxModel,
44540
45077
  resetNarrationContext: () => resetNarrationContext
44541
45078
  });
44542
- import { existsSync as existsSync42, mkdirSync as mkdirSync18, writeFileSync as writeFileSync19, readFileSync as readFileSync31, unlinkSync as unlinkSync9, readdirSync as readdirSync11, renameSync, statSync as statSync13 } from "node:fs";
44543
- import { join as join58, dirname as dirname18 } from "node:path";
45079
+ import { existsSync as existsSync42, mkdirSync as mkdirSync18, writeFileSync as writeFileSync19, readFileSync as readFileSync31, unlinkSync as unlinkSync9, readdirSync as readdirSync12, renameSync, statSync as statSync13 } from "node:fs";
45080
+ import { join as join58, dirname as dirname19 } from "node:path";
44544
45081
  import { homedir as homedir15, tmpdir as tmpdir9, platform as platform3 } from "node:os";
44545
45082
  import { execSync as execSync30, spawn as nodeSpawn } from "node:child_process";
44546
45083
  import { createRequire } from "node:module";
@@ -44598,7 +45135,7 @@ function writeDetectTorchScript(targetPath) {
44598
45135
  if (existsSync42(targetPath))
44599
45136
  return;
44600
45137
  try {
44601
- mkdirSync18(dirname18(targetPath), { recursive: true });
45138
+ mkdirSync18(dirname19(targetPath), { recursive: true });
44602
45139
  } catch {
44603
45140
  }
44604
45141
  const script = `#!/usr/bin/env python3
@@ -45665,7 +46202,7 @@ var init_voice = __esm({
45665
46202
  if (!existsSync42(dir))
45666
46203
  return [];
45667
46204
  const meta = this.loadCloneMeta();
45668
- const files = readdirSync11(dir).filter((f) => {
46205
+ const files = readdirSync12(dir).filter((f) => {
45669
46206
  const ext = f.split(".").pop()?.toLowerCase() ?? "";
45670
46207
  return _VoiceEngine.AUDIO_EXTS.has(ext);
45671
46208
  });
@@ -47289,7 +47826,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
47289
47826
  // packages/cli/dist/tui/commands.js
47290
47827
  import * as nodeOs from "node:os";
47291
47828
  import { execSync as nodeExecSync } from "node:child_process";
47292
- import { existsSync as existsSync43, readFileSync as readFileSync32, writeFileSync as writeFileSync20, mkdirSync as mkdirSync19, readdirSync as readdirSync12, statSync as statSync14, rmSync } from "node:fs";
47829
+ import { existsSync as existsSync43, readFileSync as readFileSync32, writeFileSync as writeFileSync20, mkdirSync as mkdirSync19, readdirSync as readdirSync13, statSync as statSync14, rmSync } from "node:fs";
47293
47830
  import { join as join59 } from "node:path";
47294
47831
  function safeLog(text) {
47295
47832
  if (isNeovimActive()) {
@@ -48098,7 +48635,7 @@ async function handleSlashCommand(input, ctx) {
48098
48635
  let heliaBytes = 0;
48099
48636
  try {
48100
48637
  if (existsSync43(ipfsLocalDir)) {
48101
- const files = readdirSync12(ipfsLocalDir).filter((f) => f.endsWith(".json"));
48638
+ const files = readdirSync13(ipfsLocalDir).filter((f) => f.endsWith(".json"));
48102
48639
  ipfsFiles = files.length;
48103
48640
  for (const f of files) {
48104
48641
  try {
@@ -48110,7 +48647,7 @@ async function handleSlashCommand(input, ctx) {
48110
48647
  const heliaBlockDir = join59(ipfsDir, "blocks");
48111
48648
  if (existsSync43(heliaBlockDir)) {
48112
48649
  const walkDir = (dir) => {
48113
- for (const entry of readdirSync12(dir, { withFileTypes: true })) {
48650
+ for (const entry of readdirSync13(dir, { withFileTypes: true })) {
48114
48651
  if (entry.isDirectory())
48115
48652
  walkDir(join59(dir, entry.name));
48116
48653
  else {
@@ -48233,7 +48770,7 @@ async function handleSlashCommand(input, ctx) {
48233
48770
  const categories = {};
48234
48771
  const walkStorage = (dir, category) => {
48235
48772
  try {
48236
- for (const entry of readdirSync12(dir, { withFileTypes: true })) {
48773
+ for (const entry of readdirSync13(dir, { withFileTypes: true })) {
48237
48774
  const full = join59(dir, entry.name);
48238
48775
  if (entry.isDirectory()) {
48239
48776
  const subCat = category || entry.name;
@@ -48267,7 +48804,7 @@ async function handleSlashCommand(input, ctx) {
48267
48804
  const sensitiveFound = [];
48268
48805
  const checkSensitive = (dir) => {
48269
48806
  try {
48270
- for (const entry of readdirSync12(dir, { withFileTypes: true })) {
48807
+ for (const entry of readdirSync13(dir, { withFileTypes: true })) {
48271
48808
  const name = entry.name.toLowerCase();
48272
48809
  if (sensitivePatterns.some((p) => name.includes(p))) {
48273
48810
  sensitiveFound.push(join59(dir, entry.name).replace(oaDir + "/", ""));
@@ -48567,6 +49104,45 @@ async function handleSlashCommand(input, ctx) {
48567
49104
  }
48568
49105
  return "handled";
48569
49106
  }
49107
+ if (arg.startsWith("clone")) {
49108
+ const parts = arg.slice(5).trim().split(/\s+/);
49109
+ if (parts.length < 2) {
49110
+ renderInfo("Usage: /voice clone <wav-file> <voice-name>");
49111
+ renderInfo("Example: /voice clone ~/my_voice.wav MyVoice");
49112
+ renderInfo("");
49113
+ renderInfo("Record 4-10 seconds of clean speech (24kHz mono WAV recommended).");
49114
+ renderInfo("The voice embedding will be extracted and saved for PersonaPlex.");
49115
+ return "handled";
49116
+ }
49117
+ const [wavPath, voiceName] = parts;
49118
+ const { clonePersonaPlexVoice: clonePersonaPlexVoice2 } = await Promise.resolve().then(() => (init_personaplex(), personaplex_exports));
49119
+ const result = await clonePersonaPlexVoice2(wavPath, voiceName, (msg2) => renderInfo(msg2));
49120
+ if (result) {
49121
+ renderInfo(`Use with /call by setting voice_prompt to "${voiceName}.pt"`);
49122
+ }
49123
+ return "handled";
49124
+ }
49125
+ if (arg === "list" || arg === "voices") {
49126
+ const { listPersonaPlexVoices: listPersonaPlexVoices2 } = await Promise.resolve().then(() => (init_personaplex(), personaplex_exports));
49127
+ const voices = listPersonaPlexVoices2();
49128
+ const builtins = voices.filter((v) => v.type === "builtin");
49129
+ const customs = voices.filter((v) => v.type === "custom");
49130
+ renderInfo("Natural voices:");
49131
+ renderInfo(" Female: " + builtins.filter((v) => v.name.startsWith("NATF")).map((v) => v.name).join(", "));
49132
+ renderInfo(" Male: " + builtins.filter((v) => v.name.startsWith("NATM")).map((v) => v.name).join(", "));
49133
+ renderInfo("Variety voices:");
49134
+ renderInfo(" Female: " + builtins.filter((v) => v.name.startsWith("VARF")).map((v) => v.name).join(", "));
49135
+ renderInfo(" Male: " + builtins.filter((v) => v.name.startsWith("VARM")).map((v) => v.name).join(", "));
49136
+ if (customs.length > 0) {
49137
+ renderInfo("Custom cloned voices:");
49138
+ for (const v of customs) {
49139
+ renderInfo(` ${v.name} (${v.path})`);
49140
+ }
49141
+ } else {
49142
+ renderInfo("No custom voices. Use /voice clone <wav> <name> to create one.");
49143
+ }
49144
+ return "handled";
49145
+ }
48570
49146
  if (arg === "enable" || arg === "on") {
48571
49147
  const msg2 = await ctx.voiceToggle();
48572
49148
  const isOn = msg2.toLowerCase().includes("enabled") || msg2.toLowerCase().includes("on");
@@ -48858,17 +49434,6 @@ async function handleSlashCommand(input, ctx) {
48858
49434
  renderWarning("Call mode not available in this context.");
48859
49435
  return "handled";
48860
49436
  }
48861
- try {
48862
- const { isPersonaPlexRunning: isPersonaPlexRunning2, getPersonaPlexWSUrl: getPersonaPlexWSUrl2 } = await Promise.resolve().then(() => (init_personaplex(), personaplex_exports));
48863
- if (isPersonaPlexRunning2()) {
48864
- const ppUrl = getPersonaPlexWSUrl2();
48865
- if (ppUrl) {
48866
- renderInfo(`PersonaPlex active \u2014 full-duplex mode (${ppUrl})`);
48867
- renderInfo("257ms response latency, concurrent listen+speak");
48868
- }
48869
- }
48870
- } catch {
48871
- }
48872
49437
  if (arg === "stop" || arg === "off") {
48873
49438
  if (ctx.isCallActive?.()) {
48874
49439
  await ctx.callStop?.();
@@ -50178,7 +50743,7 @@ async function showCohereDashboard(ctx) {
50178
50743
  } else if (idResult.key === "history") {
50179
50744
  const snapDir = join59(ctx.repoRoot, ".oa", "identity", "snapshots");
50180
50745
  if (existsSync43(snapDir)) {
50181
- const snaps = readdirSync12(snapDir).filter((f) => f.endsWith(".json")).sort().reverse();
50746
+ const snaps = readdirSync13(snapDir).filter((f) => f.endsWith(".json")).sort().reverse();
50182
50747
  const snapItems = snaps.slice(0, 20).map((f) => ({
50183
50748
  key: f,
50184
50749
  label: f.replace(".json", ""),
@@ -50503,14 +51068,14 @@ async function handleVoiceMenu(ctx, save, hasLocal) {
50503
51068
  continue;
50504
51069
  }
50505
51070
  const { basename: basename18, join: pathJoin } = await import("node:path");
50506
- const { copyFileSync: copyFileSync2, mkdirSync: mkdirSync31, existsSync: exists } = await import("node:fs");
51071
+ const { copyFileSync: copyFileSync3, mkdirSync: mkdirSync31, existsSync: exists } = await import("node:fs");
50507
51072
  const { homedir: homedir21 } = await import("node:os");
50508
51073
  const modelName = basename18(onnxDrop.path, ".onnx").replace(/[^a-zA-Z0-9_-]/g, "-");
50509
51074
  const destDir = pathJoin(homedir21(), ".open-agents", "voice", "models", modelName);
50510
51075
  if (!exists(destDir))
50511
51076
  mkdirSync31(destDir, { recursive: true });
50512
- copyFileSync2(onnxDrop.path, pathJoin(destDir, "model.onnx"));
50513
- copyFileSync2(jsonDrop.path, pathJoin(destDir, "config.json"));
51077
+ copyFileSync3(onnxDrop.path, pathJoin(destDir, "model.onnx"));
51078
+ copyFileSync3(jsonDrop.path, pathJoin(destDir, "config.json"));
50514
51079
  const { registerCustomOnnxModel: registerCustomOnnxModel2 } = await Promise.resolve().then(() => (init_voice(), voice_exports));
50515
51080
  registerCustomOnnxModel2(modelName, modelName);
50516
51081
  await ctx.voiceSetModel(modelName);
@@ -50636,8 +51201,8 @@ async function handleVoiceList(ctx, focusFilename) {
50636
51201
  helpers.getInput("Export to:", defaultPath).then((destPath) => {
50637
51202
  if (destPath !== null && destPath.trim()) {
50638
51203
  try {
50639
- const { copyFileSync: copyFileSync2 } = __require("node:fs");
50640
- copyFileSync2(clone.path, destPath.trim());
51204
+ const { copyFileSync: copyFileSync3 } = __require("node:fs");
51205
+ copyFileSync3(clone.path, destPath.trim());
50641
51206
  renderInfo(`Exported "${clone.name}" \u2192 ${destPath.trim()}`);
50642
51207
  } catch (err) {
50643
51208
  renderError(`Export failed: ${err.message}`);
@@ -51348,9 +51913,9 @@ async function handlePeerEndpoint(peerId, authKey, ctx, local) {
51348
51913
  if (models.length > 0) {
51349
51914
  try {
51350
51915
  const { writeFileSync: writeFileSync30, mkdirSync: mkdirSync31 } = await import("node:fs");
51351
- const { join: join77, dirname: dirname23 } = await import("node:path");
51916
+ const { join: join77, dirname: dirname24 } = await import("node:path");
51352
51917
  const cachePath = join77(ctx.repoRoot || process.cwd(), ".oa", "nexus", "peer-models-cache.json");
51353
- mkdirSync31(dirname23(cachePath), { recursive: true });
51918
+ mkdirSync31(dirname24(cachePath), { recursive: true });
51354
51919
  writeFileSync30(cachePath, JSON.stringify({
51355
51920
  peerId,
51356
51921
  cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -51549,11 +52114,11 @@ async function handleUpdate(subcommand, ctx) {
51549
52114
  let currentVersion = "0.0.0";
51550
52115
  try {
51551
52116
  const { createRequire: createRequire6 } = await import("node:module");
51552
- const { fileURLToPath: fileURLToPath15 } = await import("node:url");
51553
- const { dirname: dirname23, join: join77 } = await import("node:path");
52117
+ const { fileURLToPath: fileURLToPath16 } = await import("node:url");
52118
+ const { dirname: dirname24, join: join77 } = await import("node:path");
51554
52119
  const { existsSync: existsSync57 } = await import("node:fs");
51555
52120
  const req = createRequire6(import.meta.url);
51556
- const thisDir = dirname23(fileURLToPath15(import.meta.url));
52121
+ const thisDir = dirname24(fileURLToPath16(import.meta.url));
51557
52122
  const candidates = [
51558
52123
  join77(thisDir, "..", "package.json"),
51559
52124
  join77(thisDir, "..", "..", "package.json"),
@@ -52414,7 +52979,7 @@ var init_commands = __esm({
52414
52979
  });
52415
52980
 
52416
52981
  // packages/cli/dist/tui/project-context.js
52417
- import { existsSync as existsSync44, readFileSync as readFileSync33, readdirSync as readdirSync13 } from "node:fs";
52982
+ import { existsSync as existsSync44, readFileSync as readFileSync33, readdirSync as readdirSync14 } from "node:fs";
52418
52983
  import { join as join60, basename as basename12 } from "node:path";
52419
52984
  import { execSync as execSync31 } from "node:child_process";
52420
52985
  import { homedir as homedir17, platform as platform4, release } from "node:os";
@@ -52515,7 +53080,7 @@ function loadMemoryDir(memDir, scope) {
52515
53080
  return "";
52516
53081
  const lines = [];
52517
53082
  try {
52518
- const files = readdirSync13(memDir).filter((f) => f.endsWith(".json"));
53083
+ const files = readdirSync14(memDir).filter((f) => f.endsWith(".json"));
52519
53084
  for (const file of files.slice(0, 10)) {
52520
53085
  try {
52521
53086
  const raw = readFileSync33(join60(memDir, file), "utf-8");
@@ -53873,8 +54438,8 @@ function listBannerDesigns(workDir) {
53873
54438
  if (!existsSync45(dir))
53874
54439
  return [];
53875
54440
  try {
53876
- const { readdirSync: readdirSync23 } = __require("node:fs");
53877
- return readdirSync23(dir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
54441
+ const { readdirSync: readdirSync24 } = __require("node:fs");
54442
+ return readdirSync24(dir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
53878
54443
  } catch {
53879
54444
  return [];
53880
54445
  }
@@ -54228,7 +54793,7 @@ var init_banner = __esm({
54228
54793
  });
54229
54794
 
54230
54795
  // packages/cli/dist/tui/carousel-descriptors.js
54231
- import { existsSync as existsSync46, readFileSync as readFileSync35, writeFileSync as writeFileSync22, mkdirSync as mkdirSync21, readdirSync as readdirSync14 } from "node:fs";
54796
+ import { existsSync as existsSync46, readFileSync as readFileSync35, writeFileSync as writeFileSync22, mkdirSync as mkdirSync21, readdirSync as readdirSync15 } from "node:fs";
54232
54797
  import { join as join62, basename as basename13 } from "node:path";
54233
54798
  function loadToolProfile(repoRoot) {
54234
54799
  const filePath = join62(repoRoot, OA_DIR, "context", TOOL_PROFILE_FILE);
@@ -54441,7 +55006,7 @@ function extractFromMemory(repoRoot, tags) {
54441
55006
  try {
54442
55007
  if (!existsSync46(memoryDir))
54443
55008
  return;
54444
- const files = readdirSync14(memoryDir).filter((f) => f.endsWith(".json"));
55009
+ const files = readdirSync15(memoryDir).filter((f) => f.endsWith(".json"));
54445
55010
  for (const file of files) {
54446
55011
  const topic = file.replace(/\.json$/, "").replace(/[-_]/g, " ");
54447
55012
  tags.push(topic);
@@ -55233,8 +55798,8 @@ var init_edit_history = __esm({
55233
55798
 
55234
55799
  // packages/cli/dist/tui/promptLoader.js
55235
55800
  import { readFileSync as readFileSync36, existsSync as existsSync47 } from "node:fs";
55236
- import { join as join64, dirname as dirname19 } from "node:path";
55237
- import { fileURLToPath as fileURLToPath11 } from "node:url";
55801
+ import { join as join64, dirname as dirname20 } from "node:path";
55802
+ import { fileURLToPath as fileURLToPath12 } from "node:url";
55238
55803
  function loadPrompt3(promptPath, vars) {
55239
55804
  let content = cache3.get(promptPath);
55240
55805
  if (content === void 0) {
@@ -55253,8 +55818,8 @@ var __filename3, __dirname6, devPath2, publishedPath2, PROMPTS_DIR3, cache3;
55253
55818
  var init_promptLoader3 = __esm({
55254
55819
  "packages/cli/dist/tui/promptLoader.js"() {
55255
55820
  "use strict";
55256
- __filename3 = fileURLToPath11(import.meta.url);
55257
- __dirname6 = dirname19(__filename3);
55821
+ __filename3 = fileURLToPath12(import.meta.url);
55822
+ __dirname6 = dirname20(__filename3);
55258
55823
  devPath2 = join64(__dirname6, "..", "..", "prompts");
55259
55824
  publishedPath2 = join64(__dirname6, "..", "prompts");
55260
55825
  PROMPTS_DIR3 = existsSync47(devPath2) ? devPath2 : publishedPath2;
@@ -55263,7 +55828,7 @@ var init_promptLoader3 = __esm({
55263
55828
  });
55264
55829
 
55265
55830
  // packages/cli/dist/tui/dream-engine.js
55266
- import { mkdirSync as mkdirSync23, writeFileSync as writeFileSync23, readFileSync as readFileSync37, existsSync as existsSync48, cpSync, rmSync as rmSync2, readdirSync as readdirSync15 } from "node:fs";
55831
+ import { mkdirSync as mkdirSync23, writeFileSync as writeFileSync23, readFileSync as readFileSync37, existsSync as existsSync48, cpSync, rmSync as rmSync2, readdirSync as readdirSync16 } from "node:fs";
55267
55832
  import { join as join65, basename as basename14 } from "node:path";
55268
55833
  import { execSync as execSync32 } from "node:child_process";
55269
55834
  function loadAutoresearchMemory(repoRoot) {
@@ -56469,7 +57034,7 @@ Each proposal includes implementation entrypoints and estimated effort.
56469
57034
  /** Update the master proposal index */
56470
57035
  updateProposalIndex() {
56471
57036
  try {
56472
- const files = readdirSync15(this.dreamsDir).filter((f) => f.endsWith(".md") && f !== "PROPOSAL-INDEX.md" && f !== "dream-state.json").sort();
57037
+ const files = readdirSync16(this.dreamsDir).filter((f) => f.endsWith(".md") && f !== "PROPOSAL-INDEX.md" && f !== "dream-state.json").sort();
56473
57038
  const index = `# Dream Proposals Index
56474
57039
 
56475
57040
  **Last updated**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
@@ -56871,7 +57436,7 @@ var init_bless_engine = __esm({
56871
57436
  });
56872
57437
 
56873
57438
  // packages/cli/dist/tui/dmn-engine.js
56874
- import { existsSync as existsSync49, readFileSync as readFileSync38, writeFileSync as writeFileSync24, mkdirSync as mkdirSync24, readdirSync as readdirSync16, unlinkSync as unlinkSync10 } from "node:fs";
57439
+ import { existsSync as existsSync49, readFileSync as readFileSync38, writeFileSync as writeFileSync24, mkdirSync as mkdirSync24, readdirSync as readdirSync17, unlinkSync as unlinkSync10 } from "node:fs";
56875
57440
  import { join as join66, basename as basename15 } from "node:path";
56876
57441
  function buildDMNGatherPrompt(recentTaskSummaries, dueReminders, attentionItems, memoryTopics, capabilities, competence, reflectionBuffer) {
56877
57442
  const competenceReport = competence.length > 0 ? competence.map((c3) => {
@@ -57590,7 +58155,7 @@ OUTPUT: Call task_complete with JSON:
57590
58155
  if (!existsSync49(dir))
57591
58156
  continue;
57592
58157
  try {
57593
- const files = readdirSync16(dir).filter((f) => f.endsWith(".json"));
58158
+ const files = readdirSync17(dir).filter((f) => f.endsWith(".json"));
57594
58159
  for (const f of files) {
57595
58160
  const topic = basename15(f, ".json");
57596
58161
  if (!topics.includes(topic))
@@ -57621,7 +58186,7 @@ OUTPUT: Call task_complete with JSON:
57621
58186
  try {
57622
58187
  const filename = `cycle-${result.cycleNumber}-${Date.now()}.json`;
57623
58188
  writeFileSync24(join66(this.historyDir, filename), JSON.stringify(result, null, 2) + "\n", "utf-8");
57624
- const files = readdirSync16(this.historyDir).filter((f) => f.startsWith("cycle-") && f.endsWith(".json")).sort();
58189
+ const files = readdirSync17(this.historyDir).filter((f) => f.startsWith("cycle-") && f.endsWith(".json")).sort();
57625
58190
  if (files.length > 50) {
57626
58191
  for (const old of files.slice(0, files.length - 50)) {
57627
58192
  try {
@@ -57638,7 +58203,7 @@ OUTPUT: Call task_complete with JSON:
57638
58203
  });
57639
58204
 
57640
58205
  // packages/cli/dist/tui/snr-engine.js
57641
- import { existsSync as existsSync50, readdirSync as readdirSync17, readFileSync as readFileSync39 } from "node:fs";
58206
+ import { existsSync as existsSync50, readdirSync as readdirSync18, readFileSync as readFileSync39 } from "node:fs";
57642
58207
  import { join as join67, basename as basename16 } from "node:path";
57643
58208
  function computeDPrime(signalScores, noiseScores) {
57644
58209
  if (signalScores.length === 0 || noiseScores.length === 0)
@@ -57886,7 +58451,7 @@ Call task_complete with the JSON array when done.`, onEvent)
57886
58451
  if (!existsSync50(dir))
57887
58452
  continue;
57888
58453
  try {
57889
- const files = readdirSync17(dir).filter((f) => f.endsWith(".json"));
58454
+ const files = readdirSync18(dir).filter((f) => f.endsWith(".json"));
57890
58455
  for (const f of files) {
57891
58456
  const topic = basename16(f, ".json");
57892
58457
  if (topics.length > 0 && !topics.includes(topic))
@@ -58459,7 +59024,7 @@ var init_tool_policy = __esm({
58459
59024
  });
58460
59025
 
58461
59026
  // packages/cli/dist/tui/telegram-bridge.js
58462
- import { mkdirSync as mkdirSync25, existsSync as existsSync51, unlinkSync as unlinkSync11, readdirSync as readdirSync18, statSync as statSync15 } from "node:fs";
59027
+ import { mkdirSync as mkdirSync25, existsSync as existsSync51, unlinkSync as unlinkSync11, readdirSync as readdirSync19, statSync as statSync15 } from "node:fs";
58463
59028
  import { join as join68, resolve as resolve30 } from "node:path";
58464
59029
  import { writeFile as writeFileAsync } from "node:fs/promises";
58465
59030
  function convertMarkdownToTelegramHTML(md) {
@@ -63571,7 +64136,7 @@ var init_direct_input = __esm({
63571
64136
  });
63572
64137
 
63573
64138
  // packages/cli/dist/api/profiles.js
63574
- import { existsSync as existsSync52, readFileSync as readFileSync41, writeFileSync as writeFileSync25, mkdirSync as mkdirSync26, readdirSync as readdirSync19, unlinkSync as unlinkSync12 } from "node:fs";
64139
+ import { existsSync as existsSync52, readFileSync as readFileSync41, writeFileSync as writeFileSync25, mkdirSync as mkdirSync26, readdirSync as readdirSync20, unlinkSync as unlinkSync12 } from "node:fs";
63575
64140
  import { join as join69 } from "node:path";
63576
64141
  import { homedir as homedir18 } from "node:os";
63577
64142
  import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, randomBytes as randomBytes15, scryptSync as scryptSync3, createHash as createHash5 } from "node:crypto";
@@ -63586,7 +64151,7 @@ function listProfiles(projectDir) {
63586
64151
  const seen = /* @__PURE__ */ new Set();
63587
64152
  const projDir = projectProfileDir(projectDir);
63588
64153
  if (existsSync52(projDir)) {
63589
- for (const f of readdirSync19(projDir).filter((f2) => f2.endsWith(".json"))) {
64154
+ for (const f of readdirSync20(projDir).filter((f2) => f2.endsWith(".json"))) {
63590
64155
  try {
63591
64156
  const raw = JSON.parse(readFileSync41(join69(projDir, f), "utf8"));
63592
64157
  const name = f.replace(".json", "");
@@ -63603,7 +64168,7 @@ function listProfiles(projectDir) {
63603
64168
  }
63604
64169
  const globDir = globalProfileDir();
63605
64170
  if (existsSync52(globDir)) {
63606
- for (const f of readdirSync19(globDir).filter((f2) => f2.endsWith(".json"))) {
64171
+ for (const f of readdirSync20(globDir).filter((f2) => f2.endsWith(".json"))) {
63607
64172
  const name = f.replace(".json", "");
63608
64173
  if (seen.has(name))
63609
64174
  continue;
@@ -63746,15 +64311,15 @@ __export(serve_exports, {
63746
64311
  import * as http from "node:http";
63747
64312
  import * as https from "node:https";
63748
64313
  import { createRequire as createRequire3 } from "node:module";
63749
- import { fileURLToPath as fileURLToPath12 } from "node:url";
63750
- import { dirname as dirname20, join as join70, resolve as resolve31 } from "node:path";
64314
+ import { fileURLToPath as fileURLToPath13 } from "node:url";
64315
+ import { dirname as dirname21, join as join70, resolve as resolve31 } from "node:path";
63751
64316
  import { spawn as spawn21 } from "node:child_process";
63752
- import { mkdirSync as mkdirSync27, writeFileSync as writeFileSync26, readFileSync as readFileSync42, readdirSync as readdirSync20, existsSync as existsSync53 } from "node:fs";
64317
+ import { mkdirSync as mkdirSync27, writeFileSync as writeFileSync26, readFileSync as readFileSync42, readdirSync as readdirSync21, existsSync as existsSync53 } from "node:fs";
63753
64318
  import { randomBytes as randomBytes16 } from "node:crypto";
63754
64319
  function getVersion3() {
63755
64320
  try {
63756
64321
  const require2 = createRequire3(import.meta.url);
63757
- const thisDir = dirname20(fileURLToPath12(import.meta.url));
64322
+ const thisDir = dirname21(fileURLToPath13(import.meta.url));
63758
64323
  const candidates = [
63759
64324
  join70(thisDir, "..", "package.json"),
63760
64325
  join70(thisDir, "..", "..", "package.json"),
@@ -64075,7 +64640,7 @@ function listJobs() {
64075
64640
  const dir = jobsDir();
64076
64641
  if (!existsSync53(dir))
64077
64642
  return [];
64078
- const files = readdirSync20(dir).filter((f) => f.endsWith(".json")).sort();
64643
+ const files = readdirSync21(dir).filter((f) => f.endsWith(".json")).sort();
64079
64644
  const jobs = [];
64080
64645
  for (const file of files) {
64081
64646
  try {
@@ -65172,10 +65737,10 @@ var init_serve = __esm({
65172
65737
 
65173
65738
  // packages/cli/dist/tui/interactive.js
65174
65739
  import { cwd } from "node:process";
65175
- import { resolve as resolve32, join as join71, dirname as dirname21, extname as extname11 } from "node:path";
65740
+ import { resolve as resolve32, join as join71, dirname as dirname22, extname as extname11 } from "node:path";
65176
65741
  import { createRequire as createRequire4 } from "node:module";
65177
- import { fileURLToPath as fileURLToPath13 } from "node:url";
65178
- import { readFileSync as readFileSync43, writeFileSync as writeFileSync27, appendFileSync as appendFileSync4, rmSync as rmSync3, readdirSync as readdirSync21, mkdirSync as mkdirSync28 } from "node:fs";
65742
+ import { fileURLToPath as fileURLToPath14 } from "node:url";
65743
+ import { readFileSync as readFileSync43, writeFileSync as writeFileSync27, appendFileSync as appendFileSync4, rmSync as rmSync3, readdirSync as readdirSync22, mkdirSync as mkdirSync28 } from "node:fs";
65179
65744
  import { existsSync as existsSync54 } from "node:fs";
65180
65745
  import { execSync as execSync34 } from "node:child_process";
65181
65746
  import { homedir as homedir19 } from "node:os";
@@ -65195,7 +65760,7 @@ function formatTimeAgo(date) {
65195
65760
  function getVersion4() {
65196
65761
  try {
65197
65762
  const require2 = createRequire4(import.meta.url);
65198
- const thisDir = dirname21(fileURLToPath13(import.meta.url));
65763
+ const thisDir = dirname22(fileURLToPath14(import.meta.url));
65199
65764
  const candidates = [
65200
65765
  join71(thisDir, "..", "package.json"),
65201
65766
  join71(thisDir, "..", "..", "package.json"),
@@ -65445,7 +66010,7 @@ function gatherMemorySnippets(root) {
65445
66010
  if (!existsSync54(dir))
65446
66011
  continue;
65447
66012
  try {
65448
- for (const f of readdirSync21(dir).filter((f2) => f2.endsWith(".json"))) {
66013
+ for (const f of readdirSync22(dir).filter((f2) => f2.endsWith(".json"))) {
65449
66014
  const data = JSON.parse(readFileSync43(join71(dir, f), "utf-8"));
65450
66015
  for (const val of Object.values(data)) {
65451
66016
  const v = typeof val === "object" && val !== null && "value" in val ? String(val.value) : String(val);
@@ -68258,6 +68823,17 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
68258
68823
  adminSessionKey = generateSessionKey();
68259
68824
  }
68260
68825
  voiceSession = new VoiceSession();
68826
+ try {
68827
+ const { isPersonaPlexRunning: isPersonaPlexRunning2, getPersonaPlexWSUrl: getPersonaPlexWSUrl2 } = await Promise.resolve().then(() => (init_personaplex(), personaplex_exports));
68828
+ if (isPersonaPlexRunning2()) {
68829
+ const ppUrl = getPersonaPlexWSUrl2();
68830
+ if (ppUrl) {
68831
+ voiceSession.personaPlexWsUrl = ppUrl;
68832
+ writeContent(() => renderInfo(`PersonaPlex active \u2014 full-duplex mode at ${ppUrl}`));
68833
+ }
68834
+ }
68835
+ } catch {
68836
+ }
68261
68837
  const callState = {
68262
68838
  transcribers: /* @__PURE__ */ new Map(),
68263
68839
  loading: false,
@@ -70090,7 +70666,7 @@ __export(run_exports, {
70090
70666
  });
70091
70667
  import { resolve as resolve33 } from "node:path";
70092
70668
  import { spawn as spawn22 } from "node:child_process";
70093
- import { mkdirSync as mkdirSync29, writeFileSync as writeFileSync28, readFileSync as readFileSync44, readdirSync as readdirSync22, existsSync as existsSync55 } from "node:fs";
70669
+ import { mkdirSync as mkdirSync29, writeFileSync as writeFileSync28, readFileSync as readFileSync44, readdirSync as readdirSync23, existsSync as existsSync55 } from "node:fs";
70094
70670
  import { randomBytes as randomBytes17 } from "node:crypto";
70095
70671
  import { join as join72 } from "node:path";
70096
70672
  function jobsDir2(repoPath) {
@@ -70234,7 +70810,7 @@ function statusCommand(jobId, repoPath) {
70234
70810
  }
70235
70811
  function jobsCommand(repoPath) {
70236
70812
  const dir = jobsDir2(repoPath);
70237
- const files = readdirSync22(dir).filter((f) => f.endsWith(".json")).sort();
70813
+ const files = readdirSync23(dir).filter((f) => f.endsWith(".json")).sort();
70238
70814
  if (files.length === 0) {
70239
70815
  console.log("No jobs found.");
70240
70816
  return;
@@ -71160,8 +71736,8 @@ init_output();
71160
71736
  init_updater();
71161
71737
  import { parseArgs as nodeParseArgs2 } from "node:util";
71162
71738
  import { createRequire as createRequire5 } from "node:module";
71163
- import { fileURLToPath as fileURLToPath14 } from "node:url";
71164
- import { dirname as dirname22, join as join76 } from "node:path";
71739
+ import { fileURLToPath as fileURLToPath15 } from "node:url";
71740
+ import { dirname as dirname23, join as join76 } from "node:path";
71165
71741
 
71166
71742
  // packages/cli/dist/cli.js
71167
71743
  import { createInterface } from "node:readline";
@@ -71268,7 +71844,7 @@ init_output();
71268
71844
  function getVersion5() {
71269
71845
  try {
71270
71846
  const require2 = createRequire5(import.meta.url);
71271
- const pkgPath = join76(dirname22(fileURLToPath14(import.meta.url)), "..", "package.json");
71847
+ const pkgPath = join76(dirname23(fileURLToPath15(import.meta.url)), "..", "package.json");
71272
71848
  const pkg = require2(pkgPath);
71273
71849
  return pkg.version;
71274
71850
  } catch {