@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 +89 -28
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.js +14 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
4770
|
-
return !!this.options.host &&
|
|
4772
|
+
get silentTurn() {
|
|
4773
|
+
return !!this.options.host && !this.spokeThisTurn;
|
|
4771
4774
|
}
|
|
4772
|
-
/** A
|
|
4773
|
-
*
|
|
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.
|
|
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.
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
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,${
|
|
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(
|
|
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 =
|
|
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) ?
|
|
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.
|