agent.libx.js 0.93.10 → 0.93.11
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/cli/cli.ts +38 -5
- package/dist/cli.js +49 -12
- package/dist/cli.js.map +1 -1
- package/dist/index.js +11 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/cli/cli.ts
CHANGED
|
@@ -1034,13 +1034,25 @@ async function repl(args: Args, ai: ChatLike, cfg: Partial<AgentConfig>, cwd: st
|
|
|
1034
1034
|
const duplexAsk = async (call: ToolUse): Promise<{ decision: 'allow' | 'deny' }> => {
|
|
1035
1035
|
if (args.voice && dx) {
|
|
1036
1036
|
const hint = summarizeCall(call.name, call.args).slice(0, 80);
|
|
1037
|
-
//
|
|
1038
|
-
|
|
1037
|
+
// Default: approve like a normal session — suspend the editor, pop an interactive picker
|
|
1038
|
+
// (Allow once / always / Deny). Set `voiceAskUi: 'relay'` to opt into the spoken park/relay flow.
|
|
1039
|
+
if ((cfg as any).voiceAskUi !== 'relay') {
|
|
1039
1040
|
editorRef?.suspend();
|
|
1040
|
-
const v = await selectMenu(process.stderr, {
|
|
1041
|
+
const v = await selectMenu(process.stderr, {
|
|
1042
|
+
title: `? background worker asks to run ${call.name} ${hint}`,
|
|
1043
|
+
items: [{ label: 'Allow once', value: 'y' }, { label: 'Allow always', value: 'a' }, { label: 'Deny', value: 'n' }],
|
|
1044
|
+
current: 'y',
|
|
1045
|
+
});
|
|
1041
1046
|
editorRef?.resume();
|
|
1042
1047
|
editorRef?.redrawNow();
|
|
1043
|
-
|
|
1048
|
+
if (v === 'a') {
|
|
1049
|
+
// Remember a command-scoped allow: a live session rule (wins next ask; glob has no `*`
|
|
1050
|
+
// → exact-command match) + persist to .agent/permissions.json for future sessions.
|
|
1051
|
+
const cmd = typeof call.args?.command === 'string' ? call.args.command : null;
|
|
1052
|
+
work.permissions?.options.rules.unshift(cmd ? { tool: call.name, pathGlob: cmd, decision: 'allow' } : { tool: call.name, decision: 'allow' });
|
|
1053
|
+
persistRule(cwd, 'allow', cmd ? `${call.name}(${cmd})` : call.name);
|
|
1054
|
+
}
|
|
1055
|
+
return { decision: v === 'y' || v === 'a' ? 'allow' : 'deny' };
|
|
1044
1056
|
}
|
|
1045
1057
|
// NB: perm asks are keyed perm-N (PermissionPolicy.ask carries no task identity), so a
|
|
1046
1058
|
// cancelled task can't clean its parked perm question — bounded by askTimeoutMs → deny.
|
|
@@ -2120,7 +2132,28 @@ async function repl(args: Args, ai: ChatLike, cfg: Partial<AgentConfig>, cwd: st
|
|
|
2120
2132
|
err(dim(` … cancelled ${running.length} background task(s)\n`));
|
|
2121
2133
|
} else if (running.length) {
|
|
2122
2134
|
err(dim(` … waiting for ${running.length} background task(s) (Ctrl-C to force quit)\n`));
|
|
2123
|
-
|
|
2135
|
+
// stdin is still in raw mode here, so Ctrl-C arrives as a 0x03 byte (no SIGINT).
|
|
2136
|
+
// Race the drain against a raw Ctrl-C: on press, abort all workers and bail.
|
|
2137
|
+
let forced = false;
|
|
2138
|
+
let onCtrlC = () => {};
|
|
2139
|
+
const onByte = (b: Buffer) => {
|
|
2140
|
+
if (!b.includes(0x03)) return; // Ctrl-C
|
|
2141
|
+
forced = true;
|
|
2142
|
+
for (const t of running) { t.status = 'cancelled'; t.controller.abort(); }
|
|
2143
|
+
err(dim(`\n … force-quit — cancelled ${running.length} background task(s)\n`));
|
|
2144
|
+
onCtrlC();
|
|
2145
|
+
};
|
|
2146
|
+
process.stdin.on('data', onByte);
|
|
2147
|
+
await Promise.race([dx.idle(), new Promise<void>((res) => { onCtrlC = res; })]);
|
|
2148
|
+
process.stdin.off('data', onByte);
|
|
2149
|
+
if (forced) {
|
|
2150
|
+
// User force-quit: tear down and hard-exit — don't trust the event loop to drain
|
|
2151
|
+
// (voice children / sockets / MCP handles can keep the process alive otherwise).
|
|
2152
|
+
voiceIO?.stop();
|
|
2153
|
+
releaseStdin();
|
|
2154
|
+
await closeMcp(mounted);
|
|
2155
|
+
process.exit(130);
|
|
2156
|
+
}
|
|
2124
2157
|
(face.options.host as { flushText?: () => void } | undefined)?.flushText?.();
|
|
2125
2158
|
duplexPersist();
|
|
2126
2159
|
}
|
package/dist/cli.js
CHANGED
|
@@ -3520,7 +3520,7 @@ var DuplexAgentOptions = class {
|
|
|
3520
3520
|
reflexModel = "groq/openai/gpt-oss-20b";
|
|
3521
3521
|
actModel = "anthropic/claude-sonnet-4-6";
|
|
3522
3522
|
/** Premium reasoning model. Set to `false` to disable the Think tier entirely. */
|
|
3523
|
-
thinkModel = "anthropic/claude-opus-4-
|
|
3523
|
+
thinkModel = "anthropic/claude-opus-4-8";
|
|
3524
3524
|
/** Escape hatches merged over the derived per-agent options. */
|
|
3525
3525
|
reflexOptions;
|
|
3526
3526
|
actOptions;
|
|
@@ -4125,7 +4125,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
4125
4125
|
this.stt.onLevel = (rms) => this.handleLevel(rms);
|
|
4126
4126
|
await Promise.all([this.tts.connect(), this.stt.start()]);
|
|
4127
4127
|
this.setState("listening");
|
|
4128
|
-
log7.
|
|
4128
|
+
log7.debug(`voice I/O up (${this.stt.usingAec ? "AEC" : "heuristic echo"} capture)`);
|
|
4129
4129
|
}
|
|
4130
4130
|
get usingAec() {
|
|
4131
4131
|
return this.stt.usingAec;
|
|
@@ -4168,7 +4168,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
4168
4168
|
this.spokeDeltas = true;
|
|
4169
4169
|
this.ackAt = now();
|
|
4170
4170
|
}
|
|
4171
|
-
this.turnStartAt = now();
|
|
4171
|
+
if (!this.turnStartAt) this.turnStartAt = now();
|
|
4172
4172
|
this.setState("thinking");
|
|
4173
4173
|
}
|
|
4174
4174
|
speakDelta(text) {
|
|
@@ -4177,7 +4177,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
4177
4177
|
this.reply += text;
|
|
4178
4178
|
for (const w of this.words(this.reply)) this.echoWords.add(w);
|
|
4179
4179
|
this.tts.speak(text, true);
|
|
4180
|
-
if (!this.spokeDeltas && this.turnStartAt) log7.
|
|
4180
|
+
if (!this.spokeDeltas && this.turnStartAt) log7.debug(`ttft: ${Math.round(now() - this.turnStartAt)}ms`);
|
|
4181
4181
|
this.spokeDeltas = true;
|
|
4182
4182
|
this.setState("speaking");
|
|
4183
4183
|
}
|
|
@@ -4198,7 +4198,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
4198
4198
|
}
|
|
4199
4199
|
this.drainTimer = null;
|
|
4200
4200
|
this.speaking = false;
|
|
4201
|
-
if (this.turnStartAt) log7.
|
|
4201
|
+
if (this.turnStartAt) log7.debug(`turn: ${Math.round(now() - this.turnStartAt)}ms (incl. playback)`);
|
|
4202
4202
|
this.echoUntil = now() + 2500;
|
|
4203
4203
|
if (!this.usingAec) this.stt.reset();
|
|
4204
4204
|
this.setState("listening");
|
|
@@ -4353,7 +4353,10 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
4353
4353
|
}
|
|
4354
4354
|
const text = this.pendingUtt;
|
|
4355
4355
|
this.pendingUtt = "";
|
|
4356
|
-
if (text)
|
|
4356
|
+
if (text) {
|
|
4357
|
+
this.turnStartAt = now();
|
|
4358
|
+
this.options.onUtterance(text);
|
|
4359
|
+
}
|
|
4357
4360
|
}
|
|
4358
4361
|
get overlapCapable() {
|
|
4359
4362
|
return this.usingAec && this.options.overlapPause && !!this.player.pause && !!this.player.resume;
|
|
@@ -4494,7 +4497,7 @@ var SonioxSTT = class {
|
|
|
4494
4497
|
this.endpointTimer = setInterval(() => {
|
|
4495
4498
|
const combined = (this.finalText + this.partialText).trim();
|
|
4496
4499
|
if (!combined || now2() - this.lastChangeAt < this.options.silenceEndpointMs) return;
|
|
4497
|
-
if (this.firstTokenAt) log8.
|
|
4500
|
+
if (this.firstTokenAt) log8.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
|
|
4498
4501
|
this.reset();
|
|
4499
4502
|
this.onUtterance(combined, now2());
|
|
4500
4503
|
}, 120);
|
|
@@ -4527,7 +4530,7 @@ var SonioxSTT = class {
|
|
|
4527
4530
|
this.onPartial(combined);
|
|
4528
4531
|
if (endpoint && this.finalText.trim()) {
|
|
4529
4532
|
const utterance = this.finalText.trim();
|
|
4530
|
-
if (this.firstTokenAt) log8.
|
|
4533
|
+
if (this.firstTokenAt) log8.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
|
|
4531
4534
|
this.reset();
|
|
4532
4535
|
this.onUtterance(utterance, now2());
|
|
4533
4536
|
}
|
|
@@ -8350,12 +8353,21 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
8350
8353
|
const duplexAsk = async (call) => {
|
|
8351
8354
|
if (args.voice && dx) {
|
|
8352
8355
|
const hint = summarizeCall(call.name, call.args).slice(0, 80);
|
|
8353
|
-
if (cfg.voiceAskUi
|
|
8356
|
+
if (cfg.voiceAskUi !== "relay") {
|
|
8354
8357
|
editorRef?.suspend();
|
|
8355
|
-
const v = await selectMenu(process.stderr, {
|
|
8358
|
+
const v = await selectMenu(process.stderr, {
|
|
8359
|
+
title: `? background worker asks to run ${call.name} ${hint}`,
|
|
8360
|
+
items: [{ label: "Allow once", value: "y" }, { label: "Allow always", value: "a" }, { label: "Deny", value: "n" }],
|
|
8361
|
+
current: "y"
|
|
8362
|
+
});
|
|
8356
8363
|
editorRef?.resume();
|
|
8357
8364
|
editorRef?.redrawNow();
|
|
8358
|
-
|
|
8365
|
+
if (v === "a") {
|
|
8366
|
+
const cmd = typeof call.args?.command === "string" ? call.args.command : null;
|
|
8367
|
+
work.permissions?.options.rules.unshift(cmd ? { tool: call.name, pathGlob: cmd, decision: "allow" } : { tool: call.name, decision: "allow" });
|
|
8368
|
+
persistRule(cwd, "allow", cmd ? `${call.name}(${cmd})` : call.name);
|
|
8369
|
+
}
|
|
8370
|
+
return { decision: v === "y" || v === "a" ? "allow" : "deny" };
|
|
8359
8371
|
}
|
|
8360
8372
|
const id = `perm-${++permSeq}`;
|
|
8361
8373
|
const a = await dx.parkQuestion(id, `Permission: may the background worker run ${call.name}${hint ? ` (${hint})` : ""}? Answer yes or no (you can also type it).`);
|
|
@@ -9704,7 +9716,32 @@ ${extra}` : body);
|
|
|
9704
9716
|
} else if (running.length) {
|
|
9705
9717
|
err(dim(` \u2026 waiting for ${running.length} background task(s) (Ctrl-C to force quit)
|
|
9706
9718
|
`));
|
|
9707
|
-
|
|
9719
|
+
let forced = false;
|
|
9720
|
+
let onCtrlC = () => {
|
|
9721
|
+
};
|
|
9722
|
+
const onByte = (b) => {
|
|
9723
|
+
if (!b.includes(3)) return;
|
|
9724
|
+
forced = true;
|
|
9725
|
+
for (const t of running) {
|
|
9726
|
+
t.status = "cancelled";
|
|
9727
|
+
t.controller.abort();
|
|
9728
|
+
}
|
|
9729
|
+
err(dim(`
|
|
9730
|
+
\u2026 force-quit \u2014 cancelled ${running.length} background task(s)
|
|
9731
|
+
`));
|
|
9732
|
+
onCtrlC();
|
|
9733
|
+
};
|
|
9734
|
+
process.stdin.on("data", onByte);
|
|
9735
|
+
await Promise.race([dx.idle(), new Promise((res) => {
|
|
9736
|
+
onCtrlC = res;
|
|
9737
|
+
})]);
|
|
9738
|
+
process.stdin.off("data", onByte);
|
|
9739
|
+
if (forced) {
|
|
9740
|
+
voiceIO?.stop();
|
|
9741
|
+
releaseStdin();
|
|
9742
|
+
await closeMcp(mounted);
|
|
9743
|
+
process.exit(130);
|
|
9744
|
+
}
|
|
9708
9745
|
face.options.host?.flushText?.();
|
|
9709
9746
|
duplexPersist();
|
|
9710
9747
|
}
|