@livx.cc/agentx 0.95.2 → 0.95.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1907,7 +1907,7 @@ var init_tools_shell = __esm({
1907
1907
 
1908
1908
  // cli/cli.ts
1909
1909
  import { createInterface } from "readline/promises";
1910
- import { existsSync as existsSync10, readFileSync as readFileSync7, appendFileSync, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9, readdirSync as readdirSync4, statSync as statSync4, unlinkSync as unlinkSync5 } from "fs";
1910
+ import { existsSync as existsSync10, readFileSync as readFileSync8, appendFileSync, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9, readdirSync as readdirSync4, statSync as statSync4, unlinkSync as unlinkSync5 } from "fs";
1911
1911
  import { homedir as homedir9, tmpdir as tmpdir3 } from "os";
1912
1912
 
1913
1913
  // cli/clipboard.ts
@@ -4764,23 +4764,28 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
4764
4764
  }
4765
4765
  };
4766
4766
  }
4767
- /** True when the just-finished turn dispatched a task but voiced nothing — dead air to repair.
4767
+ /** True when the just-finished turn voiced NOTHING — dead air to repair. Two ways this happens:
4768
+ * (a) a dispatch with no spoken ack, and (b) an INLINE turn whose `final` channel came back empty —
4769
+ * gpt-oss harmony sometimes puts the whole reply in `analysis` (→ thinking_delta, suppressed in
4770
+ * voice) and emits an empty `final`, so no text_delta ever streams. Both ship silence; both repair.
4768
4771
  * Requires a host: without one there's no stream to detect speech on (and no one to speak to). */
4769
- get silentDispatch() {
4770
- return !!this.options.host && this.turnDispatched && !this.spokeThisTurn;
4772
+ get silentTurn() {
4773
+ return !!this.options.host && !this.spokeThisTurn;
4771
4774
  }
4772
- /** A dispatch with no spoken text is dead air. Re-prompt the reflex ONCE so the LLM itself voices a
4773
- * short ack (no template). If it STILL says nothing, fall back to a minimal line so silence never ships. */
4775
+ /** A turn that voiced nothing is dead air. Re-prompt the reflex ONCE so the LLM itself voices a short
4776
+ * line (no template). If it STILL says nothing, fall back to a minimal line so silence never ships.
4777
+ * Wording adapts to whether work was dispatched (an ack) or the inline reply was simply lost. */
4774
4778
  async ackIfSilent() {
4779
+ const dispatched = this.turnDispatched;
4775
4780
  this.nudging = true;
4776
4781
  try {
4777
- await this.voice.send("[reminder] You dispatched a task but said nothing to the user. Say ONE short spoken acknowledgement now \u2014 no tools.");
4782
+ await this.voice.send(dispatched ? "[reminder] You dispatched a task but said nothing to the user. Say ONE short spoken acknowledgement now \u2014 no tools." : "[reminder] You said nothing to the user this turn. Give your ONE short spoken reply now \u2014 no tools.");
4778
4783
  } catch (e) {
4779
4784
  log8.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
4780
4785
  } finally {
4781
4786
  this.nudging = false;
4782
4787
  }
4783
- if (!this.spokeThisTurn) this.options.host?.notify?.({ kind: "text_delta", message: "Okay, on it." });
4788
+ if (!this.spokeThisTurn) this.options.host?.notify?.({ kind: "text_delta", message: dispatched ? "Okay, on it." : "Sorry, could you say that again?" });
4784
4789
  }
4785
4790
  /** One user turn: the voice agent streams the reply (and may Act/Think). Serialized with re-voice turns. */
4786
4791
  send(content) {
@@ -4788,7 +4793,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
4788
4793
  await this.initMemory();
4789
4794
  this.resetTurn();
4790
4795
  const res = await this.voice.send(content);
4791
- if (this.silentDispatch) await this.ackIfSilent();
4796
+ if (this.silentTurn) await this.ackIfSilent();
4792
4797
  return res;
4793
4798
  });
4794
4799
  }
@@ -4833,7 +4838,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
4833
4838
  if (!events.length) return;
4834
4839
  this.resetTurn();
4835
4840
  await this.voice.send(events.join("\n"));
4836
- if (this.silentDispatch) await this.ackIfSilent();
4841
+ if (this.silentTurn) await this.ackIfSilent();
4837
4842
  this.notify("revoice_done", "");
4838
4843
  });
4839
4844
  }
@@ -6901,7 +6906,7 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
6901
6906
  // cli/voice.ts
6902
6907
  init_logging();
6903
6908
  import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
6904
- import { existsSync as existsSync4, mkdirSync as mkdirSync5, statSync as statSync3 } from "fs";
6909
+ import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
6905
6910
  import { homedir as homedir3 } from "os";
6906
6911
  import { dirname as dirname3, join as join6 } from "path";
6907
6912
  import { fileURLToPath } from "url";
@@ -7182,10 +7187,63 @@ var VoiceIO = class extends VoiceEngine {
7182
7187
  return !!(env.SONIOX_API_KEY && env.CARTESIA_API_KEY && env.CARTESIA_VOICE_ID);
7183
7188
  }
7184
7189
  };
7190
+ function fakeVoiceParts(uttFile) {
7191
+ let timer = null;
7192
+ let offset = 0;
7193
+ const stt = {
7194
+ usingAec: true,
7195
+ onPartial: (_) => {
7196
+ },
7197
+ onUtterance: (_, __) => {
7198
+ },
7199
+ onLevel: (_) => {
7200
+ },
7201
+ start() {
7202
+ timer = setInterval(() => {
7203
+ let buf;
7204
+ try {
7205
+ buf = readFileSync4(uttFile, "utf8");
7206
+ } catch {
7207
+ return;
7208
+ }
7209
+ if (buf.length <= offset) return;
7210
+ const fresh = buf.slice(offset);
7211
+ offset = buf.length;
7212
+ for (const line of fresh.split("\n")) {
7213
+ const t = line.trim();
7214
+ if (t) stt.onUtterance(t, Date.now());
7215
+ }
7216
+ }, 100);
7217
+ },
7218
+ reset() {
7219
+ },
7220
+ stop() {
7221
+ if (timer) {
7222
+ clearInterval(timer);
7223
+ timer = null;
7224
+ }
7225
+ }
7226
+ };
7227
+ const tts = { connect() {
7228
+ }, newContext() {
7229
+ }, speak() {
7230
+ }, end() {
7231
+ tts.onDone?.();
7232
+ }, cancel() {
7233
+ }, close() {
7234
+ }, onAudio: (_) => {
7235
+ }, onDone: () => {
7236
+ } };
7237
+ const player = { markTurn() {
7238
+ }, write() {
7239
+ }, drainMs: () => 0, playedMs: () => 0, kill() {
7240
+ } };
7241
+ return { stt, tts, player };
7242
+ }
7185
7243
 
7186
7244
  // cli/config.ts
7187
7245
  import { homedir as homedir4 } from "os";
7188
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
7246
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
7189
7247
  import { join as join7 } from "path";
7190
7248
  import { pathToFileURL } from "url";
7191
7249
  var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
@@ -7207,7 +7265,7 @@ function loadSettings(dir) {
7207
7265
  const p = join7(dir, ".agent", "settings.json");
7208
7266
  if (!existsSync5(p)) return {};
7209
7267
  try {
7210
- const raw = JSON.parse(readFileSync4(p, "utf8"));
7268
+ const raw = JSON.parse(readFileSync5(p, "utf8"));
7211
7269
  const cfg = {};
7212
7270
  if (raw.mcpServers && typeof raw.mcpServers === "object") cfg.mcpServers = raw.mcpServers;
7213
7271
  if (raw.permissions && typeof raw.permissions === "object") cfg.permissions = raw.permissions;
@@ -7362,7 +7420,7 @@ function formatDiff(ops, opts = {}) {
7362
7420
  }
7363
7421
 
7364
7422
  // cli/session.ts
7365
- import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync } from "fs";
7423
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync } from "fs";
7366
7424
  import { homedir as homedir5 } from "os";
7367
7425
  import { join as join8 } from "path";
7368
7426
  var log18 = forComponent("session");
@@ -7416,7 +7474,7 @@ var SessionStore = class {
7416
7474
  const path = join8(this.dir, `${id}.json`);
7417
7475
  if (!existsSync6(path)) return void 0;
7418
7476
  try {
7419
- return JSON.parse(readFileSync5(path, "utf8"));
7477
+ return JSON.parse(readFileSync6(path, "utf8"));
7420
7478
  } catch (e) {
7421
7479
  log18.debug(`unreadable session ${id} \u2014 ignoring`, e);
7422
7480
  return void 0;
@@ -7429,7 +7487,7 @@ var SessionStore = class {
7429
7487
  for (const f of readdirSync(this.dir)) {
7430
7488
  if (!f.endsWith(".json")) continue;
7431
7489
  try {
7432
- metas.push(JSON.parse(readFileSync5(join8(this.dir, f), "utf8")).meta);
7490
+ metas.push(JSON.parse(readFileSync6(join8(this.dir, f), "utf8")).meta);
7433
7491
  } catch (e) {
7434
7492
  log18.debug(`skipping unreadable session file ${f}`, e);
7435
7493
  }
@@ -7452,7 +7510,7 @@ function globalSessionLoad(idOrPrefix) {
7452
7510
  if (existsSync6(exact)) {
7453
7511
  try {
7454
7512
  const target = readlinkSync(exact);
7455
- return JSON.parse(readFileSync5(target, "utf8"));
7513
+ return JSON.parse(readFileSync6(target, "utf8"));
7456
7514
  } catch {
7457
7515
  return void 0;
7458
7516
  }
@@ -7463,7 +7521,7 @@ function globalSessionLoad(idOrPrefix) {
7463
7521
  const base = f.slice(0, -5);
7464
7522
  if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
7465
7523
  const target = readlinkSync(join8(gd, f));
7466
- return JSON.parse(readFileSync5(target, "utf8"));
7524
+ return JSON.parse(readFileSync6(target, "utf8"));
7467
7525
  }
7468
7526
  }
7469
7527
  } catch {
@@ -7485,7 +7543,7 @@ function globalSessionList() {
7485
7543
  }
7486
7544
  continue;
7487
7545
  }
7488
- metas.push(JSON.parse(readFileSync5(target, "utf8")).meta);
7546
+ metas.push(JSON.parse(readFileSync6(target, "utf8")).meta);
7489
7547
  } catch {
7490
7548
  }
7491
7549
  }
@@ -8012,7 +8070,7 @@ function completePath(listDir, ref) {
8012
8070
  // cli/lineEditor.ts
8013
8071
  import { emitKeypressEvents } from "readline";
8014
8072
  import { spawnSync as spawnSync4 } from "child_process";
8015
- import { writeFileSync as writeFileSync7, readFileSync as readFileSync6, unlinkSync as unlinkSync2 } from "fs";
8073
+ import { writeFileSync as writeFileSync7, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
8016
8074
  import { tmpdir as tmpdir2 } from "os";
8017
8075
  import { join as join11 } from "path";
8018
8076
 
@@ -9098,7 +9156,7 @@ function createLineEditor(out) {
9098
9156
  out.write("\x1B[?2004l");
9099
9157
  const r = spawnSync4(cmd, [...cargs, file], { stdio: "inherit" });
9100
9158
  if (r.status === 0) {
9101
- const text = readFileSync6(file, "utf8").replace(/\n$/, "");
9159
+ const text = readFileSync7(file, "utf8").replace(/\n$/, "");
9102
9160
  s.reset();
9103
9161
  if (text) s.insert(text);
9104
9162
  }
@@ -9871,7 +9929,7 @@ var err = (s) => process.stderr.write(s);
9871
9929
  var log22 = forComponent("cli");
9872
9930
  var VERSION = (() => {
9873
9931
  try {
9874
- return JSON.parse(readFileSync7(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
9932
+ return JSON.parse(readFileSync8(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
9875
9933
  } catch {
9876
9934
  return "?";
9877
9935
  }
@@ -10100,7 +10158,7 @@ function loadInstallEnv() {
10100
10158
  for (const name of [".env", ".env.local"]) {
10101
10159
  const file = join14(dir, name);
10102
10160
  if (!existsSync10(file)) continue;
10103
- for (const line of readFileSync7(file, "utf8").split("\n")) {
10161
+ for (const line of readFileSync8(file, "utf8").split("\n")) {
10104
10162
  const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
10105
10163
  if (!m || m[1] in process.env) continue;
10106
10164
  let val = m[2].trim();
@@ -10701,7 +10759,7 @@ function readImageParts(cwd, line) {
10701
10759
  if (!mime) continue;
10702
10760
  const abs = ref.startsWith("~/") ? untilde(ref) : resolve3(cwd, ref);
10703
10761
  try {
10704
- parts.push(imagePart(`data:${mime};base64,${readFileSync7(abs).toString("base64")}`));
10762
+ parts.push(imagePart(`data:${mime};base64,${readFileSync8(abs).toString("base64")}`));
10705
10763
  } catch {
10706
10764
  }
10707
10765
  }
@@ -10946,7 +11004,7 @@ function initInstructions(cwd) {
10946
11004
  function persistSetting(cwd, key, value) {
10947
11005
  const path = join14(cwd, ".agent", "settings.json");
10948
11006
  try {
10949
- const obj = existsSync10(path) ? JSON.parse(readFileSync7(path, "utf8")) : {};
11007
+ const obj = existsSync10(path) ? JSON.parse(readFileSync8(path, "utf8")) : {};
10950
11008
  if (obj[key] === value) return;
10951
11009
  obj[key] = value;
10952
11010
  mkdirSync11(dirname4(path), { recursive: true });
@@ -11103,6 +11161,7 @@ async function repl(args, ai, cfg, cwd) {
11103
11161
  notify(e) {
11104
11162
  if (voiceIO && (e.kind === "thinking_delta" || e.kind === "turn_start")) return;
11105
11163
  if (e.kind === "text_delta" && voiceIO) {
11164
+ spinner.stop();
11106
11165
  voiceIO.speakDelta(e.message);
11107
11166
  editorRef?.suspend();
11108
11167
  voiceEcho(e.message);
@@ -11190,7 +11249,7 @@ async function repl(args, ai, cfg, cwd) {
11190
11249
  quickLook: {
11191
11250
  branch: () => {
11192
11251
  try {
11193
- const head = readFileSync7(join14(cwd, ".git", "HEAD"), "utf8").trim();
11252
+ const head = readFileSync8(join14(cwd, ".git", "HEAD"), "utf8").trim();
11194
11253
  return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
11195
11254
  } catch {
11196
11255
  return "not a git repository";
@@ -11425,7 +11484,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
11425
11484
  </system-reminder>`;
11426
11485
  };
11427
11486
  const histPath = join14(cwd, ".agent", "history");
11428
- const history = existsSync10(histPath) ? readFileSync7(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
11487
+ const history = existsSync10(histPath) ? readFileSync8(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
11429
11488
  const remember = (line) => {
11430
11489
  try {
11431
11490
  mkdirSync11(join14(cwd, ".agent"), { recursive: true });
@@ -12718,11 +12777,13 @@ ${out}
12718
12777
  err(dim(" (voice needs --duplex on a TTY)\n"));
12719
12778
  return false;
12720
12779
  }
12721
- if (!VoiceIO.available()) {
12780
+ if (!process.env.AGENTX_VOICE_FAKE && !VoiceIO.available()) {
12722
12781
  err(dim(" (voice I/O off \u2014 set SONIOX_API_KEY, CARTESIA_API_KEY, CARTESIA_VOICE_ID to talk)\n"));
12723
12782
  return false;
12724
12783
  }
12784
+ const fakeVoice = process.env.AGENTX_VOICE_FAKE ? fakeVoiceParts(process.env.AGENTX_VOICE_FAKE) : null;
12725
12785
  voiceIO = new VoiceIO({
12786
+ ...fakeVoice ?? {},
12726
12787
  // No ack phrase by default: a fixed "Mm-hm," every turn reads robotic, Haiku's TTFT doesn't
12727
12788
  // need masking (~0.7-1.2s full turns), and the conversational register already opens with a
12728
12789
  // natural reaction. The mechanism (+ echo-leak guard) stays for slower voice models.