@livx.cc/agentx 0.99.121 → 0.99.122
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/README.md +2 -0
- package/dist/cli.d.ts +29 -2
- package/dist/cli.js +664 -240
- package/dist/cli.js.map +1 -1
- package/dist/directives.d.ts +15 -1
- package/dist/directives.js +47 -2
- package/dist/directives.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +337 -144
- package/dist/index.js.map +1 -1
- package/dist/{secondChannel-D33Wvruo.d.ts → secondChannel-BddfVzTp.d.ts} +101 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2754,7 +2754,7 @@ async function psGroupCpuSeconds(pgid) {
|
|
|
2754
2754
|
try {
|
|
2755
2755
|
return await winTreeCpuSeconds(pgid);
|
|
2756
2756
|
} catch (e) {
|
|
2757
|
-
|
|
2757
|
+
log10.debug(`cpu probe failed for ${pgid}: ${e}`);
|
|
2758
2758
|
return void 0;
|
|
2759
2759
|
}
|
|
2760
2760
|
}
|
|
@@ -2763,7 +2763,7 @@ async function psGroupCpuSeconds(pgid) {
|
|
|
2763
2763
|
const out = await new Promise((res, rej) => execFile2("ps", ["-A", "-o", "pid=,ppid=,pgid=,time="], { timeout: 5e3, env: { ...process.env, LC_ALL: "C" } }, (e, so) => e ? rej(e) : res(String(so))));
|
|
2764
2764
|
return sumTreeCpu(out, pgid);
|
|
2765
2765
|
} catch (e) {
|
|
2766
|
-
|
|
2766
|
+
log10.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
|
|
2767
2767
|
return void 0;
|
|
2768
2768
|
}
|
|
2769
2769
|
}
|
|
@@ -2912,7 +2912,7 @@ function makeRealShellTool(options) {
|
|
|
2912
2912
|
proc.stderr?.on("data", collect);
|
|
2913
2913
|
proc.on("error", (err) => {
|
|
2914
2914
|
if (err?.name === "AbortError" || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean(out)));
|
|
2915
|
-
|
|
2915
|
+
log10.debug("shell spawn error", err);
|
|
2916
2916
|
finish(`[exit 1] ${err?.message ?? err}${out ? "\n" + clean(out) : ""}`);
|
|
2917
2917
|
});
|
|
2918
2918
|
proc.on("close", (code) => {
|
|
@@ -2991,7 +2991,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
2991
2991
|
}
|
|
2992
2992
|
];
|
|
2993
2993
|
}
|
|
2994
|
-
var
|
|
2994
|
+
var log10, clean, DETACHED, shellArgv, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
|
|
2995
2995
|
var init_tools_shell = __esm({
|
|
2996
2996
|
"src/tools.shell.ts"() {
|
|
2997
2997
|
"use strict";
|
|
@@ -3001,7 +3001,7 @@ var init_tools_shell = __esm({
|
|
|
3001
3001
|
init_shell_sandbox();
|
|
3002
3002
|
init_tools_jobs();
|
|
3003
3003
|
init_platform();
|
|
3004
|
-
|
|
3004
|
+
log10 = forComponent("shell");
|
|
3005
3005
|
clean = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
|
|
3006
3006
|
DETACHED = { stdio: ["ignore", "pipe", "pipe"], ...DETACH_OPTIONS };
|
|
3007
3007
|
shellArgv = (sh, command) => ({ bin: sh.bin, args: sh.args(command), ...sh.spawnOptions ? { spawnOptions: sh.spawnOptions } : {} });
|
|
@@ -4276,7 +4276,11 @@ function childOptionsFor(opts, fs, depth, maxDepth, agentType, model, root) {
|
|
|
4276
4276
|
if (po) childOpts.providerOptions = po;
|
|
4277
4277
|
const deny = opts.permissions?.options.rules.filter((r) => r.decision === "deny") ?? [];
|
|
4278
4278
|
if (deny.length) childOpts.permissions = new PermissionPolicy({ rules: deny, default: "allow" });
|
|
4279
|
-
|
|
4279
|
+
const h = opts.hooks;
|
|
4280
|
+
if (h?.preToolUse || opts.inheritPostToolUse && h?.postToolUse) childOpts.hooks = {
|
|
4281
|
+
...h.preToolUse ? { preToolUse: h.preToolUse.bind(h) } : {},
|
|
4282
|
+
...opts.inheritPostToolUse && h.postToolUse ? { postToolUse: h.postToolUse.bind(h) } : {}
|
|
4283
|
+
};
|
|
4280
4284
|
return childOpts;
|
|
4281
4285
|
}
|
|
4282
4286
|
function makeTaskTool(opts) {
|
|
@@ -4500,6 +4504,147 @@ async function loadAgents(fs, dir) {
|
|
|
4500
4504
|
init_OverlayFilesystem();
|
|
4501
4505
|
init_logging();
|
|
4502
4506
|
|
|
4507
|
+
// src/modes.ts
|
|
4508
|
+
init_logging();
|
|
4509
|
+
var log8 = forComponent("modes");
|
|
4510
|
+
var WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "ApplyEdits"]);
|
|
4511
|
+
var PLANNER_BLOCKED = /* @__PURE__ */ new Set([...WRITE_TOOLS, "bash", "Shell"]);
|
|
4512
|
+
var PLANNER_REFUSAL = "Refused: you are the planner and do not write files. Delegate the change with Task/TaskBatch (model set to the worker) and give the worker a full spec.";
|
|
4513
|
+
var PLAN_FIRST = "Refused: check your plan with `Advise` before the first write (files, data shape, the hard parts). Then write.";
|
|
4514
|
+
var ADVISE_LIMIT = 3;
|
|
4515
|
+
var SNAPSHOT_CAP = 6e4;
|
|
4516
|
+
function pairOk(kind, main, other) {
|
|
4517
|
+
if (other === main) {
|
|
4518
|
+
log8.warn(`${kind} off: both sides are ${main}`);
|
|
4519
|
+
return false;
|
|
4520
|
+
}
|
|
4521
|
+
if (main.startsWith("cursor/") || other.startsWith("cursor/")) {
|
|
4522
|
+
log8.warn(`${kind} off: cursor models are not supported (${main} / ${other})`);
|
|
4523
|
+
return false;
|
|
4524
|
+
}
|
|
4525
|
+
return true;
|
|
4526
|
+
}
|
|
4527
|
+
function delegateWorkerFor(planner, worker) {
|
|
4528
|
+
return worker && pairOk("delegate", planner, worker) ? worker : void 0;
|
|
4529
|
+
}
|
|
4530
|
+
function advisorFor(worker, advisor) {
|
|
4531
|
+
return advisor && pairOk("advisor", worker, advisor) ? advisor : void 0;
|
|
4532
|
+
}
|
|
4533
|
+
function delegateTools(tools, planner) {
|
|
4534
|
+
return tools.map((t) => !PLANNER_BLOCKED.has(t.name) ? t : {
|
|
4535
|
+
...t,
|
|
4536
|
+
run: async (...a) => a[1]?.model === planner ? PLANNER_REFUSAL : t.run(...a)
|
|
4537
|
+
});
|
|
4538
|
+
}
|
|
4539
|
+
function delegatePrompt(worker) {
|
|
4540
|
+
return `
|
|
4541
|
+
|
|
4542
|
+
## You are the PLANNER
|
|
4543
|
+
You cannot write files: Write, Edit, MultiEdit, ApplyEdits and the shells are refused for you. Every file change goes through a worker. Read what you need, decide the design (files, data shape, interfaces, the rules that matter), then hand the writing to workers with \`TaskBatch\` (independent files in parallel) or \`Task\`, with \`model: "${worker}"\` (the default). Give each worker a complete, self-contained spec: it sees the files but not your reasoning. When they finish, read the result and send fix-up tasks for anything wrong or missing. Finish only when the work is complete.`;
|
|
4544
|
+
}
|
|
4545
|
+
function advisorPrompt(advisor) {
|
|
4546
|
+
return `
|
|
4547
|
+
|
|
4548
|
+
## You have an ADVISOR
|
|
4549
|
+
The \`Advise\` tool asks a stronger model (${advisor}). You do the work yourself. Call \`Advise\` once BEFORE you write \u2014 writes are refused until you do (ask it to check your plan: files, data shape, the hard parts) \u2014 and once AFTER the work is complete (ask it to review for defects), then fix what it finds. Do not call it for small steps.`;
|
|
4550
|
+
}
|
|
4551
|
+
async function textOf(r) {
|
|
4552
|
+
if (r && typeof r[Symbol.asyncIterator] === "function") {
|
|
4553
|
+
let s = "";
|
|
4554
|
+
for await (const c of r) if (typeof c?.content === "string") s += c.content;
|
|
4555
|
+
return s;
|
|
4556
|
+
}
|
|
4557
|
+
return String(r?.content ?? r?.text ?? "");
|
|
4558
|
+
}
|
|
4559
|
+
function makeAdvisor(o) {
|
|
4560
|
+
let used = 0;
|
|
4561
|
+
const limit = o.limit ?? ADVISE_LIMIT;
|
|
4562
|
+
const tool = {
|
|
4563
|
+
name: "Advise",
|
|
4564
|
+
description: `Ask a stronger model (${o.advisor}) for advice. It sees the user's request${o.snapshot ? " and the current state" : ""}, not this conversation, so put everything it needs in \`question\`. Use it at most ${limit} times: once for the plan before you write, once to review the finished work.`,
|
|
4565
|
+
parameters: { type: "object", properties: { question: { type: "string", description: "what you want advice on, with the context it needs" } }, required: ["question"] },
|
|
4566
|
+
async run(args) {
|
|
4567
|
+
const question = String(args?.question ?? "").trim();
|
|
4568
|
+
if (!question) return "No advice: the question is empty.";
|
|
4569
|
+
if (used >= limit) return `No advice: the limit of ${limit} calls is used. Continue with your own judgment.`;
|
|
4570
|
+
used++;
|
|
4571
|
+
let state = "";
|
|
4572
|
+
if (o.snapshot) {
|
|
4573
|
+
const s = String(await Promise.resolve(o.snapshot()).catch((err) => {
|
|
4574
|
+
log8.warn("advisor snapshot failed:", String(err));
|
|
4575
|
+
return "";
|
|
4576
|
+
}) || "(empty)");
|
|
4577
|
+
state = `
|
|
4578
|
+
|
|
4579
|
+
## Current state
|
|
4580
|
+
${s.length > SNAPSHOT_CAP ? `${s.slice(0, SNAPSHOT_CAP)}
|
|
4581
|
+
\u2026(cut)` : s}`;
|
|
4582
|
+
}
|
|
4583
|
+
const review = used > 1 ? "" : "\n\n(When the work is complete, call Advise again for a review.)";
|
|
4584
|
+
try {
|
|
4585
|
+
const r = await o.ai.chat({
|
|
4586
|
+
model: o.advisor,
|
|
4587
|
+
messages: [
|
|
4588
|
+
{ role: "system", content: "You advise a developer mid-task. Answer the question directly and concretely: the plan, the data shape, the risks, or the defects with their fix. Be brief. Do not write the whole solution." },
|
|
4589
|
+
{ role: "user", content: `## User request
|
|
4590
|
+
${o.request()}${state}
|
|
4591
|
+
|
|
4592
|
+
## Question
|
|
4593
|
+
${question}` }
|
|
4594
|
+
],
|
|
4595
|
+
maxTokens: 2e3
|
|
4596
|
+
});
|
|
4597
|
+
return ((await textOf(r)).trim() || "No advice: the advisor returned nothing.") + review;
|
|
4598
|
+
} catch (err) {
|
|
4599
|
+
log8.warn("advisor call failed:", String(err));
|
|
4600
|
+
return `No advice: the advisor call failed (${String(err?.message ?? err).slice(0, 120)}). Continue with your own judgment.`;
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
};
|
|
4604
|
+
const gate = (tools) => tools.map((t) => !WRITE_TOOLS.has(t.name) ? t : { ...t, run: async (...a) => used ? t.run(...a) : PLAN_FIRST });
|
|
4605
|
+
return { tool, gate, used: () => used };
|
|
4606
|
+
}
|
|
4607
|
+
function meterWorkers(ai, main) {
|
|
4608
|
+
let total;
|
|
4609
|
+
let resolved;
|
|
4610
|
+
const seen = () => {
|
|
4611
|
+
resolved = ai.modelUsed ?? resolved;
|
|
4612
|
+
};
|
|
4613
|
+
const add = (u) => {
|
|
4614
|
+
if (!u) return;
|
|
4615
|
+
const t = total ??= { promptTokens: 0, completionTokens: 0, totalTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
4616
|
+
t.promptTokens += u.promptTokens ?? 0;
|
|
4617
|
+
t.completionTokens += u.completionTokens ?? 0;
|
|
4618
|
+
t.totalTokens += u.totalTokens ?? 0;
|
|
4619
|
+
t.cacheCreationTokens += u.cacheCreationTokens ?? 0;
|
|
4620
|
+
t.cacheReadTokens += u.cacheReadTokens ?? 0;
|
|
4621
|
+
};
|
|
4622
|
+
async function* tap(it) {
|
|
4623
|
+
let last;
|
|
4624
|
+
try {
|
|
4625
|
+
for await (const c of it) {
|
|
4626
|
+
if (c?.usage) last = c.usage;
|
|
4627
|
+
yield c;
|
|
4628
|
+
}
|
|
4629
|
+
} finally {
|
|
4630
|
+
add(last);
|
|
4631
|
+
seen();
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
const chat = async (o) => {
|
|
4635
|
+
const r = await ai.chat(o);
|
|
4636
|
+
if (o?.model === main) return r;
|
|
4637
|
+
if (r && typeof r[Symbol.asyncIterator] === "function") return tap(r);
|
|
4638
|
+
add(r?.usage);
|
|
4639
|
+
seen();
|
|
4640
|
+
return r;
|
|
4641
|
+
};
|
|
4642
|
+
return { ai: new Proxy(ai, { get: (t, k) => k === "chat" ? chat : Reflect.get(t, k) }), usage: () => total && { ...total }, model: () => resolved, reset: () => {
|
|
4643
|
+
total = void 0;
|
|
4644
|
+
resolved = void 0;
|
|
4645
|
+
} };
|
|
4646
|
+
}
|
|
4647
|
+
|
|
4503
4648
|
// src/lint.ts
|
|
4504
4649
|
var CODE_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
4505
4650
|
function checkSyntax(path, content) {
|
|
@@ -4579,7 +4724,7 @@ function reasoningToChatFragment(model, effort) {
|
|
|
4579
4724
|
}
|
|
4580
4725
|
|
|
4581
4726
|
// src/Agent.ts
|
|
4582
|
-
var
|
|
4727
|
+
var log9 = forComponent("Agent");
|
|
4583
4728
|
function envInt(key, fallback) {
|
|
4584
4729
|
const n = typeof process !== "undefined" ? Number(process.env?.[key]) : NaN;
|
|
4585
4730
|
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
@@ -4708,6 +4853,13 @@ var AgentOptions = class {
|
|
|
4708
4853
|
hooks;
|
|
4709
4854
|
/** If true: add the `Task` tool so the agent can spawn depth-limited child agents over the VFS. */
|
|
4710
4855
|
subagents = false;
|
|
4856
|
+
/** DELEGATE MODE: `model` plans, `worker` writes. The planner's Write/Edit/MultiEdit/ApplyEdits/bash/Shell are
|
|
4857
|
+
* refused at execution; Task/TaskBatch are on (maxDepth 1, children default to `worker`) and children get the
|
|
4858
|
+
* parent's `postToolUse` too. Off when the models are equal or either is cursor/*. Wins over `advisor`. */
|
|
4859
|
+
delegate;
|
|
4860
|
+
/** ADVISOR MODE: `model` does the work; an `Advise` tool asks `advisor.model` (≤`limit`, default 3) with only
|
|
4861
|
+
* the request + `snapshot()` (host state, optional) + the question. The first write waits for one consult. */
|
|
4862
|
+
advisor;
|
|
4711
4863
|
/** VFS dir of typed-subagent defs (`<dir>/<name>.md`). If set with `subagents`: inject a catalog + enable the `Task` `agentType` param. */
|
|
4712
4864
|
agentsDir;
|
|
4713
4865
|
/** Current recursion depth (0 = top-level); spawned children run at depth+1. */
|
|
@@ -4816,6 +4968,11 @@ var Agent = class _Agent {
|
|
|
4816
4968
|
lastTrimNotified = 0;
|
|
4817
4969
|
// last auto-trim drop count surfaced via host.notify (dedup)
|
|
4818
4970
|
activeTools = [];
|
|
4971
|
+
meter;
|
|
4972
|
+
// delegate/advisor: meters the second model's calls
|
|
4973
|
+
modeModel;
|
|
4974
|
+
lastTask = "";
|
|
4975
|
+
// the current turn's text — what the advisor reads as the request
|
|
4819
4976
|
/** claude-code bridge for agentx-only tools (ccBridge.ts) + the current step's executor it calls. */
|
|
4820
4977
|
ccServer;
|
|
4821
4978
|
ccExec;
|
|
@@ -4999,7 +5156,7 @@ var Agent = class _Agent {
|
|
|
4999
5156
|
const disk = new NodeDiskFilesystem2(process.cwd());
|
|
5000
5157
|
await disk.init();
|
|
5001
5158
|
this.options.fs = new JailedFilesystem2(disk);
|
|
5002
|
-
|
|
5159
|
+
log9.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
|
|
5003
5160
|
}
|
|
5004
5161
|
this.buildCtx();
|
|
5005
5162
|
}
|
|
@@ -5016,6 +5173,12 @@ var Agent = class _Agent {
|
|
|
5016
5173
|
const fs = o.fs;
|
|
5017
5174
|
let systemPrompt = o.systemPrompt;
|
|
5018
5175
|
let tools = o.tools;
|
|
5176
|
+
this.ctx.model = o.model;
|
|
5177
|
+
const worker = o.delegate && delegateWorkerFor(o.model, o.delegate.worker);
|
|
5178
|
+
const advisor = !worker && o.advisor ? advisorFor(o.model, o.advisor.model) : void 0;
|
|
5179
|
+
this.meter = worker || advisor ? meterWorkers(o.ai, o.model) : void 0;
|
|
5180
|
+
const ai = this.meter?.ai ?? o.ai;
|
|
5181
|
+
const adv = advisor ? makeAdvisor({ ai, advisor, request: () => this.lastTask, snapshot: o.advisor.snapshot, limit: o.advisor.limit }) : void 0;
|
|
5019
5182
|
if (o.instructionFiles !== false) {
|
|
5020
5183
|
const names = Array.isArray(o.instructionFiles) ? o.instructionFiles : void 0;
|
|
5021
5184
|
const ins = await loadInstructions(fs, names);
|
|
@@ -5046,7 +5209,7 @@ var Agent = class _Agent {
|
|
|
5046
5209
|
if (tool) tools = [...tools, tool];
|
|
5047
5210
|
}
|
|
5048
5211
|
if (o.host && o.askUserQuestion) tools = [...tools, askUserQuestionTool];
|
|
5049
|
-
if (o.subagents) {
|
|
5212
|
+
if (o.subagents || worker) {
|
|
5050
5213
|
let agents;
|
|
5051
5214
|
if (o.agentsDir) {
|
|
5052
5215
|
const loaded = await loadAgents(fs, o.agentsDir);
|
|
@@ -5054,17 +5217,18 @@ var Agent = class _Agent {
|
|
|
5054
5217
|
if (loaded.catalog) systemPrompt += "\n\n" + loaded.catalog;
|
|
5055
5218
|
}
|
|
5056
5219
|
const taskOpts = {
|
|
5057
|
-
ai
|
|
5058
|
-
model: o.model,
|
|
5220
|
+
ai,
|
|
5221
|
+
model: worker || o.model,
|
|
5059
5222
|
fs,
|
|
5060
5223
|
depth: o.depth,
|
|
5061
|
-
maxDepth: o.maxDepth,
|
|
5224
|
+
maxDepth: worker ? Math.min(o.maxDepth, o.depth + 1) : o.maxDepth,
|
|
5062
5225
|
agents,
|
|
5226
|
+
inheritPostToolUse: !!worker,
|
|
5063
5227
|
hooks: o.hooks,
|
|
5064
5228
|
traceDir: o.traceDir,
|
|
5065
5229
|
permissions: o.permissions,
|
|
5066
5230
|
// a child obeys the parent's allow/deny rules (else Task is a one-call bypass)
|
|
5067
|
-
tools: [...o.tools, ...this.injectedTools],
|
|
5231
|
+
tools: worker ? delegateTools([...o.tools, ...this.injectedTools], o.model) : [...o.tools, ...this.injectedTools],
|
|
5068
5232
|
systemPrompt: o.systemPrompt,
|
|
5069
5233
|
// A child never resumes the parent's own claude-code session (the host's `resume` link is the parent's).
|
|
5070
5234
|
providerOptionsFor: o.providerOptionsFor ?? ((m) => modelProvider(m) === modelProvider(o.model) && o.providerOptions ? { ...o.providerOptions, resume: void 0, onClaudeCodeSession: void 0 } : void 0)
|
|
@@ -5077,6 +5241,15 @@ var Agent = class _Agent {
|
|
|
5077
5241
|
if (plan) tools = [...tools, plan.tool];
|
|
5078
5242
|
this.activeHooks = composeHooks(o.hooks, plan?.hooks, o.permissions?.hooks());
|
|
5079
5243
|
this.activeTools = [...tools, ...this.injectedTools];
|
|
5244
|
+
if (worker) {
|
|
5245
|
+
this.activeTools = delegateTools(this.activeTools, o.model);
|
|
5246
|
+
systemPrompt += delegatePrompt(worker);
|
|
5247
|
+
}
|
|
5248
|
+
if (adv) {
|
|
5249
|
+
this.activeTools = [...adv.gate(this.activeTools), adv.tool];
|
|
5250
|
+
systemPrompt += advisorPrompt(advisor);
|
|
5251
|
+
}
|
|
5252
|
+
this.modeModel = worker || advisor;
|
|
5080
5253
|
this.systemPromptCache = systemPrompt;
|
|
5081
5254
|
this.prepared = true;
|
|
5082
5255
|
return systemPrompt;
|
|
@@ -5089,6 +5262,7 @@ var Agent = class _Agent {
|
|
|
5089
5262
|
this.started = false;
|
|
5090
5263
|
this.invokedTools.clear();
|
|
5091
5264
|
this.fabricatedClaims.clear();
|
|
5265
|
+
this.lastTask = contentText(task);
|
|
5092
5266
|
const systemPrompt = await this.prepare(contentText(task));
|
|
5093
5267
|
const startCtx = await this.fireSessionStart();
|
|
5094
5268
|
const userContent = await this.applyPromptSubmit(task);
|
|
@@ -5124,6 +5298,7 @@ var Agent = class _Agent {
|
|
|
5124
5298
|
*/
|
|
5125
5299
|
async send(task) {
|
|
5126
5300
|
await this.ensureFs();
|
|
5301
|
+
this.lastTask = contentText(task);
|
|
5127
5302
|
const systemPrompt = await this.prepare(contentText(task));
|
|
5128
5303
|
const startCtx = await this.fireSessionStart();
|
|
5129
5304
|
const userContent = await this.applyPromptSubmit(task);
|
|
@@ -5174,6 +5349,12 @@ var Agent = class _Agent {
|
|
|
5174
5349
|
if (next !== this.transcript) this.transcript = next;
|
|
5175
5350
|
}
|
|
5176
5351
|
async runLoop() {
|
|
5352
|
+
this.meter?.reset();
|
|
5353
|
+
const res = await this.loop();
|
|
5354
|
+
const workerUsage = this.meter?.usage();
|
|
5355
|
+
return workerUsage ? { ...res, workerModel: this.meter.model() ?? this.modeModel, workerUsage } : res;
|
|
5356
|
+
}
|
|
5357
|
+
async loop() {
|
|
5177
5358
|
const o = this.options;
|
|
5178
5359
|
const wireTools = toWireTools(this.activeTools);
|
|
5179
5360
|
const useStream = o.stream === true && typeof o.host?.notify === "function";
|
|
@@ -5193,7 +5374,7 @@ var Agent = class _Agent {
|
|
|
5193
5374
|
let lastProgressAt = start;
|
|
5194
5375
|
const turnUser = this.transcript[this.transcript.length - 1];
|
|
5195
5376
|
const kill = (finishReason) => {
|
|
5196
|
-
|
|
5377
|
+
log9.warn(`kill-switch: ${finishReason} (steps=${steps}, tokens=${usage.totalTokens}, budgetTokens=${Math.round(spent)}, ms=${Date.now() - start - this.parkedMs}${finishReason === "timeout" ? `, idle=${Date.now() - lastProgressAt - this.parkedMs}ms` : ""}${this.parkedMs ? ` +${this.parkedMs} parked` : ""})`);
|
|
5197
5378
|
this.jobs.killAll(`run ended: ${finishReason}`, true);
|
|
5198
5379
|
const claims = this.survivingClaims();
|
|
5199
5380
|
return { text: lastAssistantText(this.transcript, turnUser), steps, finishReason, messages: this.transcript, usage, usageEstimated, ...finishReason === "budget" ? { budget: { spent: Math.round(spent), limit: o.maxTokens, stepPrompt } } : {}, ...claims.length ? { fabricatedToolClaims: claims } : {} };
|
|
@@ -5219,9 +5400,9 @@ var Agent = class _Agent {
|
|
|
5219
5400
|
this.nativeHoldMs = o.nativeHoldMs;
|
|
5220
5401
|
if (isCursorWithTools) {
|
|
5221
5402
|
if (typeof hostToolTimeoutMs === "number" && effectiveToolTimeoutMs !== hostToolTimeoutMs) {
|
|
5222
|
-
|
|
5403
|
+
log9.warn(`providerOptions.toolTimeoutMs=${hostToolTimeoutMs}ms is below what the registered tools need \u2014 RAISED to ${effectiveToolTimeoutMs}ms. ${toolTimeoutViolations(this.activeTools, hostToolTimeoutMs).length} tool(s) could outlive it, and the transport would have destroyed their results. To lower it, lower the tools' own maxDurationMs (e.g. the Shell tool's maxTimeoutMs) instead.`);
|
|
5223
5404
|
}
|
|
5224
|
-
for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs))
|
|
5405
|
+
for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs)) log9.warn(v);
|
|
5225
5406
|
}
|
|
5226
5407
|
const hostExec = async (name, args) => {
|
|
5227
5408
|
const tc = { id: `host-${Date.now()}`, type: "function", function: { name, arguments: JSON.stringify(args) } };
|
|
@@ -5280,7 +5461,7 @@ var Agent = class _Agent {
|
|
|
5280
5461
|
} catch (err) {
|
|
5281
5462
|
if (!this.imagesOff && /does not support .*input/i.test(String(err?.message ?? "")) && sent.some((m) => Array.isArray(m.content) && m.role === "tool")) {
|
|
5282
5463
|
this.imagesOff = true;
|
|
5283
|
-
|
|
5464
|
+
log9.warn(`model refused image input (${err?.message}) \u2014 resending with image Reads as text stubs`);
|
|
5284
5465
|
sent = this.trimContext();
|
|
5285
5466
|
attempt--;
|
|
5286
5467
|
continue;
|
|
@@ -5292,11 +5473,11 @@ var Agent = class _Agent {
|
|
|
5292
5473
|
const empty = err?.code === "empty" || err?.code === "cursor_silent";
|
|
5293
5474
|
const maxAttempts = empty ? Math.max(o.emptyRetries, 0) : 2;
|
|
5294
5475
|
const replaysTool = stalled && this.toolRanThisAttempt;
|
|
5295
|
-
if (replaysTool)
|
|
5476
|
+
if (replaysTool) log9.warn("model stall after tool(s) already ran this step \u2014 not retrying (a retry would replay them)");
|
|
5296
5477
|
const transient = !o.signal?.aborted && !isAbortError(err) && !replaysTool && attempt < maxAttempts && (network || serverSide || stalled || empty);
|
|
5297
5478
|
if (!transient) throw err;
|
|
5298
5479
|
const waitMs = (empty ? o.emptyRetryBaseMs : 1e3) * (attempt + 1);
|
|
5299
|
-
|
|
5480
|
+
log9.warn(`${empty ? "empty model turn" : stalled ? "model stall" : "network drop"} mid-step (${err?.message ?? err}) \u2014 retrying in ${waitMs}ms`);
|
|
5300
5481
|
o.host?.notify?.({ kind: "retry", message: `${empty ? "model returned nothing" : stalled ? "model stalled" : "connection dropped"} \u2014 retrying step (#${attempt + 1})` });
|
|
5301
5482
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
5302
5483
|
}
|
|
@@ -5304,12 +5485,12 @@ var Agent = class _Agent {
|
|
|
5304
5485
|
} catch (err) {
|
|
5305
5486
|
if (err?.code === "budget") return kill("budget");
|
|
5306
5487
|
if (err?.code === "stall") {
|
|
5307
|
-
|
|
5488
|
+
log9.warn(`model stalled out after retries: ${err?.message ?? err}`);
|
|
5308
5489
|
return { ...kill("timeout"), providerFinishReason: "none (stalled)", error: err };
|
|
5309
5490
|
}
|
|
5310
5491
|
if (o.signal?.aborted || isAbortError(err)) return kill("aborted");
|
|
5311
5492
|
if (err?.code === "empty" || err?.code === "cursor_silent") {
|
|
5312
|
-
|
|
5493
|
+
log9.warn(`model returned empty/silent turns after retries \u2014 surfacing as failed`);
|
|
5313
5494
|
const pfr = err?.providerFinishReason;
|
|
5314
5495
|
return { text: "", steps, finishReason: "empty", ...pfr ? { providerFinishReason: pfr } : {}, messages: this.transcript, usage, usageEstimated, error: err };
|
|
5315
5496
|
}
|
|
@@ -5322,13 +5503,13 @@ var Agent = class _Agent {
|
|
|
5322
5503
|
}
|
|
5323
5504
|
if (bodyStr && err instanceof Error && !err.message.includes(bodyStr)) err.detail = bodyStr;
|
|
5324
5505
|
const errLog = err instanceof Error ? { message: err.message, ...err.detail ? { detail: err.detail } : {}, stack: err.stack } : err;
|
|
5325
|
-
|
|
5506
|
+
log9.error(`chat() failed: ${err?.message ?? err}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, errLog);
|
|
5326
5507
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err };
|
|
5327
5508
|
}
|
|
5328
5509
|
if (o.signal?.aborted) return kill("aborted");
|
|
5329
5510
|
if (res.finishReason === "error" && !res.toolCalls?.length) {
|
|
5330
5511
|
const err = new Error(contentText(res.content ?? "").trim() || "provider error");
|
|
5331
|
-
|
|
5512
|
+
log9.error(`chat() failed: ${err.message}`);
|
|
5332
5513
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err };
|
|
5333
5514
|
}
|
|
5334
5515
|
if (!res.usage) {
|
|
@@ -5396,21 +5577,21 @@ var Agent = class _Agent {
|
|
|
5396
5577
|
if (emptyTurn && o.failEmptyTurns) {
|
|
5397
5578
|
if (!emptyNudged && canNudge()) {
|
|
5398
5579
|
emptyNudged = true;
|
|
5399
|
-
|
|
5580
|
+
log9.warn(`empty model turn after tool work (step ${steps}, provider finishReason=${res.finishReason ?? "n/a"}) \u2014 nudging once`);
|
|
5400
5581
|
this.transcript.push({ role: "user", content: "Continue \u2014 you returned nothing. Answer the user now using the tool results above, or keep working if you are not done." });
|
|
5401
5582
|
continue;
|
|
5402
5583
|
}
|
|
5403
|
-
|
|
5584
|
+
log9.warn(`model returned empty turns after tool work \u2014 surfacing as failed`);
|
|
5404
5585
|
const e = new Error("empty_response: model returned an empty turn (no text, no tools) after a re-prompt");
|
|
5405
5586
|
e.code = "empty";
|
|
5406
5587
|
return { text: "", steps, finishReason: "empty", ...res.finishReason ? { providerFinishReason: res.finishReason } : {}, messages: this.transcript, usage, usageEstimated, error: e };
|
|
5407
5588
|
}
|
|
5408
5589
|
const fabricated = o.catchFabricatedToolUse && !closureNudged ? this.fabricatedToolNames(contentText(res.content ?? "")) : [];
|
|
5409
5590
|
for (const n of fabricated) this.fabricatedClaims.add(n);
|
|
5410
|
-
if (fabricated.length && !canNudge())
|
|
5591
|
+
if (fabricated.length && !canNudge()) log9.warn(`fabricated tool use: text names ${fabricated.join(", ")} but no step/budget left to demand the call \u2014 finishing with the existing answer`);
|
|
5411
5592
|
if (fabricated.length && canNudge()) {
|
|
5412
5593
|
closureNudged = true;
|
|
5413
|
-
|
|
5594
|
+
log9.warn(`fabricated tool use: text names ${fabricated.join(", ")} but the run invoked ${this.invokedTools.size ? [...this.invokedTools].join(", ") : "nothing"} \u2014 demanding the real call`);
|
|
5414
5595
|
this.transcript.push({
|
|
5415
5596
|
role: "user",
|
|
5416
5597
|
content: `You referred to ${fabricated.map((n) => `\`${n}\``).join(", ")} in your reply, but you never actually called ${fabricated.length > 1 ? "those tools" : "that tool"} \u2014 no tool call was made, so any result you reported for ${fabricated.length > 1 ? "them" : "it"} was invented. Either call ${fabricated.length > 1 ? "them" : "it"} for real now, or answer without ${fabricated.length > 1 ? "them" : "it"} and do NOT claim any outcome \u2014 including a failure or a timeout \u2014 for a call you did not make.`
|
|
@@ -5419,14 +5600,14 @@ var Agent = class _Agent {
|
|
|
5419
5600
|
}
|
|
5420
5601
|
if (o.closeDelegatedTurns && res.endedWithoutClosing && !closureNudged && canNudge()) {
|
|
5421
5602
|
closureNudged = true;
|
|
5422
|
-
|
|
5603
|
+
log9.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
|
|
5423
5604
|
this.transcript.push({
|
|
5424
5605
|
role: "user",
|
|
5425
5606
|
content: "You ran tools but did not reply. Answer the user now using the tool results above; if a tool failed, say so plainly. Do NOT re-run those tools, do NOT narrate what you did, and do NOT ask for permission to continue."
|
|
5426
5607
|
});
|
|
5427
5608
|
continue;
|
|
5428
5609
|
}
|
|
5429
|
-
|
|
5610
|
+
log9.verbose(`completed in ${steps} step(s)`);
|
|
5430
5611
|
const drained = await this.jobs.drain(o.signal);
|
|
5431
5612
|
if (o.signal?.aborted) return kill("aborted");
|
|
5432
5613
|
if (drained.length) lastProgressAt = Date.now();
|
|
@@ -5460,7 +5641,7 @@ var Agent = class _Agent {
|
|
|
5460
5641
|
if (this.ctx.needsInput) {
|
|
5461
5642
|
const question = this.ctx.needsInput;
|
|
5462
5643
|
this.ctx.needsInput = void 0;
|
|
5463
|
-
|
|
5644
|
+
log9.warn(`needs_input: ${question.question} (steps=${steps})`);
|
|
5464
5645
|
await this.jobs.drain();
|
|
5465
5646
|
const text = [
|
|
5466
5647
|
`OPEN QUESTION (run paused \u2014 no human was reachable): ${question.question}`,
|
|
@@ -5471,7 +5652,7 @@ var Agent = class _Agent {
|
|
|
5471
5652
|
if (this.ctx.endTurn) {
|
|
5472
5653
|
this.ctx.endTurn = void 0;
|
|
5473
5654
|
if (this.drainInjections()) continue;
|
|
5474
|
-
|
|
5655
|
+
log9.verbose(`turn ended by a tool (steps=${steps})`);
|
|
5475
5656
|
await this.jobs.drain(o.signal);
|
|
5476
5657
|
return { text: "", steps, finishReason: "stop", messages: this.transcript, usage, usageEstimated };
|
|
5477
5658
|
}
|
|
@@ -5509,12 +5690,12 @@ var Agent = class _Agent {
|
|
|
5509
5690
|
if (this.toolHoldActive()) {
|
|
5510
5691
|
const now3 = Date.now();
|
|
5511
5692
|
const who = this.jobs.holding().map((j) => `${j.kind} "${j.label}" (${Math.round((now3 - j.startedAt) / 1e3)}s, quiet ${Math.round((now3 - j.lastActivityAt) / 1e3)}s)`).join(", ");
|
|
5512
|
-
|
|
5693
|
+
log9.warn(`stall watchdog: no stream output for ${stallMs}ms \u2014 held by ${who}`);
|
|
5513
5694
|
this.options.host?.notify?.({ kind: "tool_hold", message: `waiting on ${who}` });
|
|
5514
5695
|
return poke();
|
|
5515
5696
|
}
|
|
5516
5697
|
const inFlight = this.jobs.list().filter((j) => j.status === "running");
|
|
5517
|
-
|
|
5698
|
+
log9.warn(`stall watchdog: no stream output for ${stallMs}ms and no tool holding the stream \u2014 aborting the request` + (inFlight.length ? ` (${inFlight.length} detached job(s) running, not awaited by the stream: ${inFlight.map((j) => `${j.id}/${j.kind}`).join(", ")})` : ""));
|
|
5518
5699
|
fired = true;
|
|
5519
5700
|
ac.abort(stallError());
|
|
5520
5701
|
}, stallMs);
|
|
@@ -5684,13 +5865,13 @@ var Agent = class _Agent {
|
|
|
5684
5865
|
const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
|
|
5685
5866
|
if (decision?.block) {
|
|
5686
5867
|
const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
|
|
5687
|
-
|
|
5868
|
+
log9.debug(`${tc.function.name} -> ${blocked}`);
|
|
5688
5869
|
await hooks?.postToolUse?.(call, blocked, meta);
|
|
5689
5870
|
return blocked;
|
|
5690
5871
|
}
|
|
5691
5872
|
this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
|
|
5692
5873
|
if (earlyError) {
|
|
5693
|
-
|
|
5874
|
+
log9.debug(`${tc.function.name} -> ${earlyError}`);
|
|
5694
5875
|
await hooks?.postToolUse?.(call, earlyError, meta);
|
|
5695
5876
|
this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
|
|
5696
5877
|
return earlyError;
|
|
@@ -5699,12 +5880,12 @@ var Agent = class _Agent {
|
|
|
5699
5880
|
let images;
|
|
5700
5881
|
let threw = false;
|
|
5701
5882
|
try {
|
|
5702
|
-
|
|
5883
|
+
log9.debug(`${tc.function.name}(${tc.function.arguments})`);
|
|
5703
5884
|
this.ctx.emit = hooks?.onToolOutput ? (chunk) => {
|
|
5704
5885
|
try {
|
|
5705
5886
|
hooks.onToolOutput(call, chunk, meta);
|
|
5706
5887
|
} catch (e) {
|
|
5707
|
-
|
|
5888
|
+
log9.debug(`onToolOutput hook error: ${e}`);
|
|
5708
5889
|
}
|
|
5709
5890
|
} : void 0;
|
|
5710
5891
|
const raw = await tool.run(args, this.ctx);
|
|
@@ -5716,7 +5897,7 @@ var Agent = class _Agent {
|
|
|
5716
5897
|
}
|
|
5717
5898
|
} catch (e) {
|
|
5718
5899
|
const msg = e instanceof Error ? e.message : String(e);
|
|
5719
|
-
|
|
5900
|
+
log9.debug(`${tc.function.name} -> error: ${msg}`);
|
|
5720
5901
|
result = `Error: ${msg}`;
|
|
5721
5902
|
threw = true;
|
|
5722
5903
|
} finally {
|
|
@@ -5993,7 +6174,7 @@ function fitTokenBudget(messages, maxTokens, keepRecentImages = 1, vision = true
|
|
|
5993
6174
|
const ids = callIdSet(messages.slice(from));
|
|
5994
6175
|
while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
|
|
5995
6176
|
if (total > maxTokens)
|
|
5996
|
-
|
|
6177
|
+
log9.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
|
|
5997
6178
|
return [...head, ...messages.slice(from)];
|
|
5998
6179
|
}
|
|
5999
6180
|
function compact(m, max, focus) {
|
|
@@ -6678,7 +6859,7 @@ init_tools_web();
|
|
|
6678
6859
|
init_tools();
|
|
6679
6860
|
init_tools_structured();
|
|
6680
6861
|
init_logging();
|
|
6681
|
-
var
|
|
6862
|
+
var log11 = forComponent("scratch");
|
|
6682
6863
|
var SCRATCH_DIR = "/scratch";
|
|
6683
6864
|
function shortArgs(args) {
|
|
6684
6865
|
try {
|
|
@@ -6718,7 +6899,7 @@ var Scratch = class {
|
|
|
6718
6899
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
6719
6900
|
await this.fs.writeFile(path, header + raw);
|
|
6720
6901
|
} catch (e) {
|
|
6721
|
-
|
|
6902
|
+
log11.debug("scratch write failed; returning raw", e);
|
|
6722
6903
|
return raw;
|
|
6723
6904
|
}
|
|
6724
6905
|
const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
|
|
@@ -6748,7 +6929,7 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
|
|
|
6748
6929
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
6749
6930
|
await this.fs.writeFile(path, header + full);
|
|
6750
6931
|
} catch (e) {
|
|
6751
|
-
|
|
6932
|
+
log11.debug("scratch spill failed; cropping lossy", e);
|
|
6752
6933
|
return full.slice(0, pageBytes) + `
|
|
6753
6934
|
|
|
6754
6935
|
[output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
|
|
@@ -6794,7 +6975,7 @@ Question: ${q}`);
|
|
|
6794
6975
|
const answer = (res.text ?? "").trim();
|
|
6795
6976
|
return answer || "(no answer found in scratch)";
|
|
6796
6977
|
} catch (e) {
|
|
6797
|
-
|
|
6978
|
+
log11.debug("Ask peek failed", e);
|
|
6798
6979
|
return `Error querying scratch: ${e?.message ?? e}`;
|
|
6799
6980
|
}
|
|
6800
6981
|
}
|
|
@@ -6803,7 +6984,7 @@ Question: ${q}`);
|
|
|
6803
6984
|
|
|
6804
6985
|
// src/lessons.ts
|
|
6805
6986
|
init_logging();
|
|
6806
|
-
var
|
|
6987
|
+
var log12 = forComponent("Lessons");
|
|
6807
6988
|
var LessonOptionsDefaults = class {
|
|
6808
6989
|
minRepeats = 2;
|
|
6809
6990
|
};
|
|
@@ -6828,8 +7009,8 @@ function lessonCapture(options) {
|
|
|
6828
7009
|
counts.set(lesson.slug, n);
|
|
6829
7010
|
if (n < o.minRepeats) return;
|
|
6830
7011
|
written.add(lesson.slug);
|
|
6831
|
-
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) =>
|
|
6832
|
-
|
|
7012
|
+
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log12.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
|
|
7013
|
+
log12.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
|
|
6833
7014
|
}
|
|
6834
7015
|
};
|
|
6835
7016
|
}
|
|
@@ -6837,7 +7018,7 @@ function lessonCapture(options) {
|
|
|
6837
7018
|
// src/reflect.ts
|
|
6838
7019
|
init_llm();
|
|
6839
7020
|
init_logging();
|
|
6840
|
-
var
|
|
7021
|
+
var log13 = forComponent("Reflect");
|
|
6841
7022
|
async function reflectOnRun(o) {
|
|
6842
7023
|
const digest = digestRun(o.result.messages, o.maxDigestChars ?? 6e3);
|
|
6843
7024
|
if (!digest.trim()) return null;
|
|
@@ -6853,7 +7034,7 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
6853
7034
|
const r = await o.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false });
|
|
6854
7035
|
text = r?.content ?? "";
|
|
6855
7036
|
} catch (e) {
|
|
6856
|
-
|
|
7037
|
+
log13.warn(`reflection call failed: ${e?.message ?? e}`);
|
|
6857
7038
|
return null;
|
|
6858
7039
|
}
|
|
6859
7040
|
const m = text.match(/LESSON:\s*(.+)/i);
|
|
@@ -6863,10 +7044,10 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
6863
7044
|
try {
|
|
6864
7045
|
await writeFact(o.fs, o.dir, slug2, lesson);
|
|
6865
7046
|
} catch (e) {
|
|
6866
|
-
|
|
7047
|
+
log13.warn(`could not persist lesson: ${e?.message ?? e}`);
|
|
6867
7048
|
return null;
|
|
6868
7049
|
}
|
|
6869
|
-
|
|
7050
|
+
log13.debug(`reflection persisted ${slug2}`);
|
|
6870
7051
|
return slug2;
|
|
6871
7052
|
}
|
|
6872
7053
|
function digestRun(messages, maxChars) {
|
|
@@ -6916,7 +7097,7 @@ function forComponent2(name) {
|
|
|
6916
7097
|
}
|
|
6917
7098
|
|
|
6918
7099
|
// node_modules/@bod.ee/voice/src/core/emotion.ts
|
|
6919
|
-
var
|
|
7100
|
+
var log14 = forComponent2("Emotion");
|
|
6920
7101
|
var EMOTIONS = [
|
|
6921
7102
|
// primary (best results)
|
|
6922
7103
|
"neutral",
|
|
@@ -7070,7 +7251,7 @@ var EmotionStream = class {
|
|
|
7070
7251
|
if (tag) {
|
|
7071
7252
|
this.pending = tag;
|
|
7072
7253
|
if (this.show) display += m[0];
|
|
7073
|
-
} else
|
|
7254
|
+
} else log14.debug(`dropping unknown emotion tag ${m[0]}`);
|
|
7074
7255
|
last = m.index + m[0].length;
|
|
7075
7256
|
}
|
|
7076
7257
|
this.emit(body.slice(last), (s, d, p) => {
|
|
@@ -7217,7 +7398,7 @@ function computeHoldMs(ctx) {
|
|
|
7217
7398
|
}
|
|
7218
7399
|
|
|
7219
7400
|
// node_modules/@bod.ee/voice/src/core/engine.ts
|
|
7220
|
-
var
|
|
7401
|
+
var log15 = forComponent2("VoiceEngine");
|
|
7221
7402
|
var realClock = {
|
|
7222
7403
|
now: () => performance.now(),
|
|
7223
7404
|
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
@@ -7605,8 +7786,8 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7605
7786
|
this.tts.warmup?.();
|
|
7606
7787
|
this.setState("listening");
|
|
7607
7788
|
if (this.options.bargeInMode === "aec" && !this.stt.usingAec)
|
|
7608
|
-
|
|
7609
|
-
|
|
7789
|
+
log15.warn("bargeInMode=aec but mic does not report echoCancellation applied \u2014 STT may transcribe the assistant");
|
|
7790
|
+
log15.debug(`voice I/O up (${this.usingAec ? "AEC" : "heuristic echo"} capture, mode=${this.options.bargeInMode}, mic reports aec=${this.stt.usingAec})`);
|
|
7610
7791
|
}
|
|
7611
7792
|
/** The RESOLVED barge-in tier (the single seam every tier branch reads). `bargeInMode` overrides
|
|
7612
7793
|
* the mic's self-report — see the option doc for why the report is not trustworthy on mobile. */
|
|
@@ -7631,7 +7812,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7631
7812
|
this.options.onDiag({ t: this.clock.now(), kind, ...fields });
|
|
7632
7813
|
} catch (e) {
|
|
7633
7814
|
this.diagOn = false;
|
|
7634
|
-
|
|
7815
|
+
log15.debug(`onDiag threw \u2014 diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
7635
7816
|
}
|
|
7636
7817
|
}
|
|
7637
7818
|
idleWaiters = [];
|
|
@@ -7770,7 +7951,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7770
7951
|
}
|
|
7771
7952
|
for (const w of this.words(this.reply)) this.echoWords.add(w);
|
|
7772
7953
|
this.tts.speak(forSpeech(speech), true);
|
|
7773
|
-
if (!this.spokeDeltas && this.turnStartAt)
|
|
7954
|
+
if (!this.spokeDeltas && this.turnStartAt) log15.debug(`ttft: ${Math.round(this.clock.now() - this.turnStartAt)}ms`);
|
|
7774
7955
|
this.spokeDeltas = true;
|
|
7775
7956
|
this.setState("speaking");
|
|
7776
7957
|
if (this.options.revealMode !== "off" && display) {
|
|
@@ -7883,13 +8064,13 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7883
8064
|
this.drainTimer = null;
|
|
7884
8065
|
this.speaking = false;
|
|
7885
8066
|
this.stopWordReveal(true);
|
|
7886
|
-
if (this.turnStartAt)
|
|
8067
|
+
if (this.turnStartAt) log15.debug(`turn: ${Math.round(this.clock.now() - this.turnStartAt)}ms (incl. playback)`);
|
|
7887
8068
|
this.echoUntil = this.clock.now() + 2500;
|
|
7888
8069
|
if (!this.usingAec) this.stt.reset();
|
|
7889
8070
|
try {
|
|
7890
8071
|
this.options.onSpeechEnd();
|
|
7891
8072
|
} catch (e) {
|
|
7892
|
-
|
|
8073
|
+
log15.warn(`onSpeechEnd threw: ${e instanceof Error ? e.message : e}`);
|
|
7893
8074
|
}
|
|
7894
8075
|
this.setState("listening");
|
|
7895
8076
|
if (this.uttQueue.length) this.pumpQueue();
|
|
@@ -7963,7 +8144,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7963
8144
|
if (!host) return pick;
|
|
7964
8145
|
const ctx = { text: this.lastDispatchText, question: this.lastDispatchWasQuestion, barged: this.bargedBeforeDispatch };
|
|
7965
8146
|
const fallback = (e) => {
|
|
7966
|
-
|
|
8147
|
+
log15.warn(`pickAck failed \u2014 using the default pools: ${e instanceof Error ? e.message : e}`);
|
|
7967
8148
|
pick.picked = false;
|
|
7968
8149
|
return void 0;
|
|
7969
8150
|
};
|
|
@@ -8053,7 +8234,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8053
8234
|
this.ackProbe = null;
|
|
8054
8235
|
const queued = this.uttQueue.splice(0);
|
|
8055
8236
|
const droppedQueued = queued.length;
|
|
8056
|
-
if (droppedQueued)
|
|
8237
|
+
if (droppedQueued) log15.info(`barge-in cut ${droppedQueued} queued worker utterance(s) \u2014 handed to the host`);
|
|
8057
8238
|
this.overlapKeptUntil = 0;
|
|
8058
8239
|
if (!this.speaking && !this.drainTimer) {
|
|
8059
8240
|
if (droppedQueued) this.lastInterrupted = { full: "", heard: "", queued, queuedTurn: false };
|
|
@@ -8282,7 +8463,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8282
8463
|
if (flat && this.lastDispatchFlat && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS && (this.lastDispatchFlat.startsWith(flat) || flat.startsWith(this.lastDispatchFlat))) return;
|
|
8283
8464
|
this.specSpent = true;
|
|
8284
8465
|
this.specText = this.specPartial;
|
|
8285
|
-
|
|
8466
|
+
log15.debug(`speculate: "${this.specText.slice(0, 60)}"`);
|
|
8286
8467
|
this.diag("speculate", { text: this.specText });
|
|
8287
8468
|
this.options.onSpeculate(this.specText);
|
|
8288
8469
|
}, this.options.speculativeMs);
|
|
@@ -8351,7 +8532,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8351
8532
|
};
|
|
8352
8533
|
this.tts.newContext();
|
|
8353
8534
|
this.tts.speak(phrase, false);
|
|
8354
|
-
|
|
8535
|
+
log15.debug(`backchannel: "${phrase}"`);
|
|
8355
8536
|
this.diag("backchannel", { phrase });
|
|
8356
8537
|
this.options.onBackchannel(phrase);
|
|
8357
8538
|
}
|
|
@@ -8384,7 +8565,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8384
8565
|
});
|
|
8385
8566
|
if (idx < 0) return text;
|
|
8386
8567
|
toks.splice(idx, 1);
|
|
8387
|
-
|
|
8568
|
+
log15.verbose(`stripped backchannel echo "${this.lastBcPhrase}" from dispatching final`);
|
|
8388
8569
|
return toks.join(" ");
|
|
8389
8570
|
}
|
|
8390
8571
|
/** Merge a resumed utterance into the pending one, deduping any word-overlap. Soniox re-finalizes
|
|
@@ -8570,7 +8751,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8570
8751
|
}
|
|
8571
8752
|
const bargeHold = !this.looksIncomplete(this.pendingUtt) && this.unfinishedBargeLine(this.pendingUtt);
|
|
8572
8753
|
if (this.options.incompleteMergeMs && (bargeHold || this.looksIncomplete(this.pendingUtt))) {
|
|
8573
|
-
|
|
8754
|
+
log15.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
|
|
8574
8755
|
this.diag("hold", { reason: bargeHold ? "barge_unfinished" : "incomplete", tail: this.pendingUtt.slice(-40) });
|
|
8575
8756
|
this.options.onHold();
|
|
8576
8757
|
if (this.options.holdFiller && !this.speaking) this.speakFiller(this.options.holdFiller);
|
|
@@ -8609,13 +8790,13 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8609
8790
|
const answered = this.answeredSinceDispatch();
|
|
8610
8791
|
const part = flat !== this.lastDispatchFlat && !answered && this.echoesDispatch(text);
|
|
8611
8792
|
if (part || flat && flat === this.lastDispatchFlat && !answered && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS) {
|
|
8612
|
-
|
|
8793
|
+
log15.verbose(`dropped duplicate final "${text.slice(0, 40)}" (identical to the just-dispatched utterance, no reply between)`);
|
|
8613
8794
|
this.diag("utterance_drop", { reason: "dup_final", text: text.slice(0, 60) });
|
|
8614
8795
|
return;
|
|
8615
8796
|
}
|
|
8616
8797
|
const refinal = this.refinalizes(text);
|
|
8617
8798
|
if (refinal && this.repliedSinceDispatch) {
|
|
8618
|
-
|
|
8799
|
+
log15.verbose(`dropped re-finalization "${text.slice(0, 60)}" (superset of the just-dispatched utterance, reply already flowing)`);
|
|
8619
8800
|
this.diag("utterance_drop", { reason: "refinalized", text: text.slice(0, 60) });
|
|
8620
8801
|
this.lastDispatchFlat = flat;
|
|
8621
8802
|
this.lastDispatchWords = text.trim().split(/\s+/);
|
|
@@ -8894,7 +9075,7 @@ var SpokenSplitter = class {
|
|
|
8894
9075
|
};
|
|
8895
9076
|
|
|
8896
9077
|
// node_modules/@bod.ee/voice/src/adapters/soniox.ts
|
|
8897
|
-
var
|
|
9078
|
+
var log16 = forComponent2("SonioxSTT");
|
|
8898
9079
|
var now = () => performance.now();
|
|
8899
9080
|
var SonioxSTTOptions = class {
|
|
8900
9081
|
auth = "";
|
|
@@ -8939,7 +9120,7 @@ var SonioxSTT = class {
|
|
|
8939
9120
|
this.onDiag({ t: now(), kind, ...fields });
|
|
8940
9121
|
} catch (e) {
|
|
8941
9122
|
this.diagOn = false;
|
|
8942
|
-
|
|
9123
|
+
log16.debug(`onDiag threw \u2014 STT diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
8943
9124
|
}
|
|
8944
9125
|
}
|
|
8945
9126
|
lastChunkAt = 0;
|
|
@@ -8981,11 +9162,11 @@ var SonioxSTT = class {
|
|
|
8981
9162
|
this.ws.onmessage = (ev) => this.handle(JSON.parse(String(ev.data)));
|
|
8982
9163
|
this.ws.onclose = (ev) => {
|
|
8983
9164
|
if (this.stopped) return;
|
|
8984
|
-
|
|
9165
|
+
log16.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
|
|
8985
9166
|
this.diag("stt_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: true });
|
|
8986
9167
|
this.reset();
|
|
8987
9168
|
this.connectWs().catch((e) => {
|
|
8988
|
-
|
|
9169
|
+
log16.error(`soniox reconnect failed: ${e.message}`);
|
|
8989
9170
|
this.diag("stt_reconnect_failed", { message: e.message });
|
|
8990
9171
|
});
|
|
8991
9172
|
};
|
|
@@ -8997,7 +9178,7 @@ var SonioxSTT = class {
|
|
|
8997
9178
|
this.endpointTimer = setInterval(() => {
|
|
8998
9179
|
const combined = (this.finalText + this.partialText).trim();
|
|
8999
9180
|
if (!combined || now() - this.lastChangeAt < this.options.silenceEndpointMs) return;
|
|
9000
|
-
if (this.firstTokenAt)
|
|
9181
|
+
if (this.firstTokenAt) log16.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
|
|
9001
9182
|
this.reset();
|
|
9002
9183
|
this.onUtterance(combined, now());
|
|
9003
9184
|
}, 120);
|
|
@@ -9009,7 +9190,7 @@ var SonioxSTT = class {
|
|
|
9009
9190
|
if (this.stopped) return;
|
|
9010
9191
|
const ref = this.lastChunkAt || this.startedChunksAt;
|
|
9011
9192
|
if (now() - ref > noAudioMs) {
|
|
9012
|
-
|
|
9193
|
+
log16.error(`stt: no mic audio for >${Math.round(noAudioMs / 1e3)}s \u2014 capture device stopped delivering`);
|
|
9013
9194
|
this.diag("stt_watchdog_fatal", { noAudioMs });
|
|
9014
9195
|
this.onFatal("microphone stopped delivering audio (try a different input device, e.g. AirPods, or check System Settings \u2192 Sound \u2192 Input)");
|
|
9015
9196
|
this.stop();
|
|
@@ -9032,7 +9213,7 @@ var SonioxSTT = class {
|
|
|
9032
9213
|
handle(m) {
|
|
9033
9214
|
if (m.error_message) {
|
|
9034
9215
|
this.diag("stt_error", { message: String(m.error_message), code: m.error_code });
|
|
9035
|
-
return
|
|
9216
|
+
return log16.error(`soniox: ${m.error_message}`);
|
|
9036
9217
|
}
|
|
9037
9218
|
let endpoint = false;
|
|
9038
9219
|
for (const t of m.tokens ?? []) {
|
|
@@ -9049,7 +9230,7 @@ var SonioxSTT = class {
|
|
|
9049
9230
|
this.onPartial(combined);
|
|
9050
9231
|
if (endpoint && this.finalText.trim()) {
|
|
9051
9232
|
const utterance = this.finalText.trim();
|
|
9052
|
-
if (this.firstTokenAt)
|
|
9233
|
+
if (this.firstTokenAt) log16.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
|
|
9053
9234
|
this.reset();
|
|
9054
9235
|
this.onUtterance(utterance, now());
|
|
9055
9236
|
}
|
|
@@ -9071,7 +9252,7 @@ var SonioxSTT = class {
|
|
|
9071
9252
|
};
|
|
9072
9253
|
|
|
9073
9254
|
// node_modules/@bod.ee/voice/src/adapters/cartesia.ts
|
|
9074
|
-
var
|
|
9255
|
+
var log17 = forComponent2("CartesiaTTS");
|
|
9075
9256
|
var now2 = () => performance.now();
|
|
9076
9257
|
var CartesiaTTSOptions = class {
|
|
9077
9258
|
auth = "";
|
|
@@ -9107,7 +9288,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
9107
9288
|
this.onDiag({ t: now2(), kind, ...fields });
|
|
9108
9289
|
} catch (e) {
|
|
9109
9290
|
this.diagOn = false;
|
|
9110
|
-
|
|
9291
|
+
log17.debug(`onDiag threw \u2014 TTS diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
9111
9292
|
}
|
|
9112
9293
|
}
|
|
9113
9294
|
firstAudioAt = 0;
|
|
@@ -9142,11 +9323,11 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
9142
9323
|
this.ws.onerror = (e) => rej(new Error(`cartesia ws: ${e.message || "connect failed"}`));
|
|
9143
9324
|
});
|
|
9144
9325
|
this.ws.onclose = (ev) => {
|
|
9145
|
-
|
|
9326
|
+
log17.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
|
|
9146
9327
|
this.diag("tts_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: !this.closed });
|
|
9147
9328
|
if (!this.closed) {
|
|
9148
9329
|
this.connecting = this.doConnect().catch((e) => {
|
|
9149
|
-
|
|
9330
|
+
log17.error(`cartesia reconnect failed: ${e.message}`);
|
|
9150
9331
|
this.diag("tts_reconnect_failed", { message: e.message });
|
|
9151
9332
|
});
|
|
9152
9333
|
}
|
|
@@ -9174,12 +9355,12 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
9174
9355
|
this.down = true;
|
|
9175
9356
|
this.downAt = now2();
|
|
9176
9357
|
this.consecutiveOk = 0;
|
|
9177
|
-
|
|
9358
|
+
log17.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
|
|
9178
9359
|
this.diag("tts_breaker_open", { errors: this.consecutiveErrors });
|
|
9179
9360
|
this.onDone();
|
|
9180
9361
|
this.startProbe();
|
|
9181
9362
|
} else if (!this.down) {
|
|
9182
|
-
(/No valid transcripts/i.test(m.message || "") ?
|
|
9363
|
+
(/No valid transcripts/i.test(m.message || "") ? log17.debug : log17.warn)(`cartesia: ${JSON.stringify(m)}`);
|
|
9183
9364
|
}
|
|
9184
9365
|
}
|
|
9185
9366
|
};
|
|
@@ -9194,7 +9375,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
9194
9375
|
this.stopProbe();
|
|
9195
9376
|
const downMs = this.downAt ? now2() - this.downAt : 0;
|
|
9196
9377
|
this.diag("tts_breaker_close", { downMs: Math.round(downMs) });
|
|
9197
|
-
(downMs < 2e3 ?
|
|
9378
|
+
(downMs < 2e3 ? log17.debug : log17.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
|
|
9198
9379
|
}
|
|
9199
9380
|
/** Ensure the WS is open before sending — reconnects if idle-closed. */
|
|
9200
9381
|
async ensureConnected() {
|
|
@@ -9277,13 +9458,13 @@ function base64ToBytes(b64) {
|
|
|
9277
9458
|
}
|
|
9278
9459
|
|
|
9279
9460
|
// node_modules/@bod.ee/voice/src/adapters/fish.ts
|
|
9280
|
-
var
|
|
9461
|
+
var log18 = forComponent2("FishTTS");
|
|
9281
9462
|
|
|
9282
9463
|
// node_modules/@bod.ee/voice/src/adapters/grok.ts
|
|
9283
|
-
var
|
|
9464
|
+
var log19 = forComponent2("GrokTTS");
|
|
9284
9465
|
|
|
9285
9466
|
// node_modules/@bod.ee/voice/src/adapters/grok-stt.ts
|
|
9286
|
-
var
|
|
9467
|
+
var log20 = forComponent2("GrokSTT");
|
|
9287
9468
|
|
|
9288
9469
|
// src/voice/engine.ts
|
|
9289
9470
|
init_logging();
|
|
@@ -9299,7 +9480,7 @@ import { AIClient, DecideClient, JevAdapter, LlmDecideAdapter } from "ai.libx.js
|
|
|
9299
9480
|
|
|
9300
9481
|
// src/voice/overlap.ts
|
|
9301
9482
|
init_logging();
|
|
9302
|
-
var
|
|
9483
|
+
var log21 = forComponent("Overlap");
|
|
9303
9484
|
var norm2 = (s) => s.toLowerCase().replace(/[’']/g, "'").replace(/[^\p{L}\p{N}' -]+/gu, " ").replace(/\s+/g, " ").trim();
|
|
9304
9485
|
var HARD = /\b(?:stop|wait|no|nope|nah|hold on|hang on|hold up|pause|cancel|quiet|enough|shut up|never ?mind|actually|but|sorry what|what|why|how|who|when|where|which|can you|could you|tell me|i mean|excuse me)\b/;
|
|
9305
9486
|
var ACK = new Set("yeah yea yes yep yup ya ok okay k right sure true totally exactly definitely absolutely indeed mm mmm hmm mhm mm-hm uh-huh uh huh aha ah oh ooh wow nice cool great awesome perfect good fine interesting gotcha got it i see makes sense fair agreed correct of course for sure lol ha haha hahaha heh hehe yeah yeah alright all right sweet neat oh nice oh wow oh cool oh okay oh yeah oh right that's right that's true thats right thats true love it i know me too same".split(" "));
|
|
@@ -9379,8 +9560,8 @@ var OverlapJudge = class {
|
|
|
9379
9560
|
const t0 = this.o.now();
|
|
9380
9561
|
const done = (verdict, by) => {
|
|
9381
9562
|
const d = { verdict, by, ms: Math.round(this.o.now() - t0) };
|
|
9382
|
-
if (by === "late" || by === "error" || by === "none")
|
|
9383
|
-
else
|
|
9563
|
+
if (by === "late" || by === "error" || by === "none") log21.warn(`overlap "${heard.slice(0, 60)}": no verdict (${by}, ${d.ms}ms) \u2192 cut`);
|
|
9564
|
+
else log21.debug(`overlap "${heard.slice(0, 60)}": ${verdict} by ${by} ${d.ms}ms`);
|
|
9384
9565
|
this.o.onDecision(heard, d);
|
|
9385
9566
|
return d;
|
|
9386
9567
|
};
|
|
@@ -9397,7 +9578,7 @@ var OverlapJudge = class {
|
|
|
9397
9578
|
});
|
|
9398
9579
|
try {
|
|
9399
9580
|
const v = await Promise.race([this.o.model(heard, ctl.signal).catch((e) => {
|
|
9400
|
-
if (!ctl.signal.aborted)
|
|
9581
|
+
if (!ctl.signal.aborted) log21.warn(`overlap model failed: ${e.message}`);
|
|
9401
9582
|
return "error";
|
|
9402
9583
|
}), late]);
|
|
9403
9584
|
if (v === "late" || v === "error") return done("cut", v);
|
|
@@ -9413,14 +9594,14 @@ function overlapModelFor(ai, env = process.env) {
|
|
|
9413
9594
|
const m = fastOverlapModel(ai, model);
|
|
9414
9595
|
const t0 = performance.now();
|
|
9415
9596
|
void m("yeah", AbortSignal.timeout(1e4)).then(
|
|
9416
|
-
() =>
|
|
9417
|
-
(e) =>
|
|
9597
|
+
() => log21.debug(`fast model warm in ${Math.round(performance.now() - t0)}ms`),
|
|
9598
|
+
(e) => log21.warn(`fast model warm-up failed (the first overlap verdict may be late): ${e?.message ?? e}`)
|
|
9418
9599
|
);
|
|
9419
9600
|
return m;
|
|
9420
9601
|
}
|
|
9421
9602
|
|
|
9422
9603
|
// src/voice/instinct.ts
|
|
9423
|
-
var
|
|
9604
|
+
var log22 = forComponent("Instinct");
|
|
9424
9605
|
var ACK_SITUATIONS = {
|
|
9425
9606
|
hard_question: `a question that takes real thought, judgment or looking something up ("what's the biggest takeaway?", "how should I structure this?")`,
|
|
9426
9607
|
simple_question: 'a quick question with an instant, obvious answer ("how are you?", "what time is it?", "can you hear me?")',
|
|
@@ -9470,7 +9651,7 @@ function interruptionNote(cut, intent) {
|
|
|
9470
9651
|
|
|
9471
9652
|
// src/voice/unsaid.ts
|
|
9472
9653
|
init_logging();
|
|
9473
|
-
var
|
|
9654
|
+
var log23 = forComponent("Unsaid");
|
|
9474
9655
|
var STOP2 = new Set("that this with have from they them their there then than what when were will would your about into just also been some only over more most very which while after before because these those does here".split(" "));
|
|
9475
9656
|
var contentStems = (s) => (s.toLowerCase().match(/[a-z0-9']+/g) ?? []).filter((w) => w.length >= 4 && !STOP2.has(w)).map((w) => w.slice(0, 4));
|
|
9476
9657
|
var SAID_OVERLAP = 0.6;
|
|
@@ -9510,7 +9691,7 @@ async function unsaidClauses(pieces, spoken, decider, timeoutMs = 5e3) {
|
|
|
9510
9691
|
return p < CONVEYED_MIN;
|
|
9511
9692
|
}).map((x) => x.c);
|
|
9512
9693
|
} catch (e) {
|
|
9513
|
-
|
|
9694
|
+
log23.warn(`unsaid judge failed \u2014 only near-zero-overlap clauses count as unsaid: ${e.message}`);
|
|
9514
9695
|
return fallback();
|
|
9515
9696
|
} finally {
|
|
9516
9697
|
clearTimeout(timer);
|
|
@@ -9631,7 +9812,7 @@ var RESULT_CLAIM_RE = new RegExp(LEAD_TAGS + String.raw`(?:(?:all\s+)?done|finis
|
|
|
9631
9812
|
var DONE_CLAIM_RE = new RegExp(LEAD_TAGS + String.raw`(?:(?:all\s+)?done|finished|it['’]?s\s+(?:done|finished)|completed?)\b`, "i");
|
|
9632
9813
|
|
|
9633
9814
|
// src/voice/channelLang.ts
|
|
9634
|
-
var
|
|
9815
|
+
var log24 = forComponent("ChannelBanks");
|
|
9635
9816
|
var LINE_KINDS = ["backchannel", "filler", "bridge"];
|
|
9636
9817
|
var BANK_KEYS = [...LINE_KINDS, "phatic"];
|
|
9637
9818
|
var EN_PHATIC = [
|
|
@@ -9785,11 +9966,11 @@ function cleanLine(s, lang, maxWords = 6) {
|
|
|
9785
9966
|
const raw = s.trim().split("\n")[0];
|
|
9786
9967
|
const l = raw.replace(/^[-*•\d.)\s]+/, "").replace(/^["'“”«»]+|["'“”«»]+$/g, "").replace(new RegExp("^\\p{L}+:\\s*", "u"), "").trim();
|
|
9787
9968
|
if (!l || isLeakSuspect(raw) || isLeakSuspect(l)) {
|
|
9788
|
-
if (l)
|
|
9969
|
+
if (l) log24.debug(`line "${raw}" reads as meta \u2014 rejected`);
|
|
9789
9970
|
return "";
|
|
9790
9971
|
}
|
|
9791
9972
|
if (lang && !fitsLanguage(l, lang)) {
|
|
9792
|
-
|
|
9973
|
+
log24.debug(`line "${l}" is not ${lang} \u2014 rejected`);
|
|
9793
9974
|
return "";
|
|
9794
9975
|
}
|
|
9795
9976
|
return l.split(/\s+/).length > maxWords || l.length > 60 ? "" : l;
|
|
@@ -9803,7 +9984,7 @@ function validateBank(raw, lang, minPerKind = 3) {
|
|
|
9803
9984
|
const seen = /* @__PURE__ */ new Set();
|
|
9804
9985
|
out[k] = (Array.isArray(v) ? v : []).filter((x) => typeof x === "string").map((x) => cleanLine(x, lang)).filter((x) => x && !(k === "filler" && SPEAKER_FILLER.test(norm3(x))) && !seen.has(norm3(x)) && seen.add(norm3(x)));
|
|
9805
9986
|
if (out[k].length < minPerKind) {
|
|
9806
|
-
|
|
9987
|
+
log24.warn(`bank ${lang}: only ${out[k].length} valid ${k} lines \u2014 rejected`);
|
|
9807
9988
|
return null;
|
|
9808
9989
|
}
|
|
9809
9990
|
}
|
|
@@ -9879,11 +10060,11 @@ var ChannelBanks = class {
|
|
|
9879
10060
|
const f = this.file(lang);
|
|
9880
10061
|
let cached = null;
|
|
9881
10062
|
if (f) cached = await readFile(f, "utf8").then((s) => JSON.parse(s)).catch((e) => {
|
|
9882
|
-
if (e.code !== "ENOENT")
|
|
10063
|
+
if (e.code !== "ENOENT") log24.warn(`bank cache ${f} unreadable \u2014 regenerating: ${e.message}`);
|
|
9883
10064
|
return null;
|
|
9884
10065
|
});
|
|
9885
10066
|
if (cached && cached.v !== BANK_VERSION) {
|
|
9886
|
-
|
|
10067
|
+
log24.debug(`bank ${lang}: cache v${cached.v ?? 1} predates v${BANK_VERSION} \u2014 regenerating`);
|
|
9887
10068
|
cached = null;
|
|
9888
10069
|
}
|
|
9889
10070
|
const valid = cached && validateBank(cached.bank, lang, 0);
|
|
@@ -9891,7 +10072,7 @@ var ChannelBanks = class {
|
|
|
9891
10072
|
if (valid) {
|
|
9892
10073
|
for (const k of BANK_KEYS) if (!valid[k].length) valid[k] = [...this.o.fallback[lang]?.[k] ?? []];
|
|
9893
10074
|
this.banks.set(lang, valid);
|
|
9894
|
-
|
|
10075
|
+
log24.debug(`bank ${lang}: cache hit (${BANK_KEYS.map((k) => valid[k].length).join("/")})${complete ? "" : " \u2014 missing kinds, refreshing"}`);
|
|
9895
10076
|
}
|
|
9896
10077
|
if (complete && this.o.now() - cached.at < this.o.refreshMs) return;
|
|
9897
10078
|
await this.generate(lang, valid);
|
|
@@ -9910,11 +10091,11 @@ var ChannelBanks = class {
|
|
|
9910
10091
|
bank[k] = [...fresh[k], ...have?.[k] ?? []].filter((x) => !seen.has(norm3(x)) && seen.add(norm3(x))).slice(0, this.o.maxPerKind);
|
|
9911
10092
|
}
|
|
9912
10093
|
this.banks.set(lang, bank);
|
|
9913
|
-
|
|
10094
|
+
log24.debug(`bank ${lang}: ${have ? "refreshed" : "generated"} (${BANK_KEYS.map((k) => bank[k].length).join("/")}) in ${Math.round(this.o.now() - t0)}ms`);
|
|
9914
10095
|
const f = this.file(lang);
|
|
9915
|
-
if (f) await mkdir(this.o.cacheDir, { recursive: true }).then(() => writeFile(f, JSON.stringify({ at: this.o.now(), v: BANK_VERSION, bank }))).catch((e) =>
|
|
10096
|
+
if (f) await mkdir(this.o.cacheDir, { recursive: true }).then(() => writeFile(f, JSON.stringify({ at: this.o.now(), v: BANK_VERSION, bank }))).catch((e) => log24.warn(`bank cache write ${f} failed: ${e.message}`));
|
|
9916
10097
|
} catch (e) {
|
|
9917
|
-
|
|
10098
|
+
log24.warn(`bank ${lang}: generation failed \u2014 ${have ? "keeping the cached bank" : lang in this.o.fallback ? "fallback bank" : "silent"}: ${e.message}`);
|
|
9918
10099
|
} finally {
|
|
9919
10100
|
clearTimeout(t);
|
|
9920
10101
|
if (!this.banks.has(lang)) setTimeout(() => this.pending.delete(lang), this.o.retryMs).unref?.();
|
|
@@ -10107,7 +10288,7 @@ var TurnGate = class {
|
|
|
10107
10288
|
};
|
|
10108
10289
|
|
|
10109
10290
|
// src/duplex.ts
|
|
10110
|
-
var
|
|
10291
|
+
var log25 = forComponent("DuplexAgent");
|
|
10111
10292
|
function describeCall(call) {
|
|
10112
10293
|
const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
|
|
10113
10294
|
const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
|
|
@@ -10442,7 +10623,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10442
10623
|
const m = this.reflexBuf.match(RESERVED_EVENT_MARKER) ?? this.reflexBuf.match(RESERVED_EVENT_OPENER);
|
|
10443
10624
|
if (m) {
|
|
10444
10625
|
this.fabricationCut = true;
|
|
10445
|
-
|
|
10626
|
+
log25.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
|
|
10446
10627
|
this.reflexBuf = this.reflexBuf.slice(0, Math.max(m.index, this.reflexForwarded));
|
|
10447
10628
|
this.pumpReflex(true);
|
|
10448
10629
|
return;
|
|
@@ -10584,7 +10765,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10584
10765
|
this.leakHit = true;
|
|
10585
10766
|
this.leakCuts.push(leak.trim().slice(0, 40));
|
|
10586
10767
|
const why = this.cutKind === "claim" ? "stated a result no task has given yet" : this.cutKind === "restart" ? "started a second, restated reply" : "spoke its own analysis";
|
|
10587
|
-
|
|
10768
|
+
log25.warn(`reflex ${why} ("${leak.trim().slice(0, 40)}\u2026") \u2014 cut before TTS`);
|
|
10588
10769
|
this.notify("diag", this.cutKind === "claim" ? "result_claim" : this.cutKind === "restart" ? "reply_restart" : "analysis_leak", { text: leak.trim().slice(0, 80), at: this.turnSpoken.trim() ? "mid_reply" : "before_speech" });
|
|
10589
10770
|
const next = this.stepMarks.find((k) => k > at);
|
|
10590
10771
|
if (next === void 0) {
|
|
@@ -10625,7 +10806,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10625
10806
|
this.notify("diag", "leak_judge", { text: s.slice(0, 80), p: Math.round(p * 100) / 100, ms: Math.round(performance.now() - t0) });
|
|
10626
10807
|
return p >= LEAK_JUDGE_THRESHOLD;
|
|
10627
10808
|
}, (e) => {
|
|
10628
|
-
|
|
10809
|
+
log25.warn(`leak judge failed \u2014 speaking the sentence: ${e.message}`);
|
|
10629
10810
|
return false;
|
|
10630
10811
|
}).then((leak) => {
|
|
10631
10812
|
clearTimeout(timer);
|
|
@@ -10697,7 +10878,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10697
10878
|
let i = t.length - 1;
|
|
10698
10879
|
while (i >= this.turnBase && !(t[i].role === "assistant" && typeof t[i].content === "string" && t[i].content.includes(key))) i--;
|
|
10699
10880
|
if (i < this.turnBase) {
|
|
10700
|
-
|
|
10881
|
+
log25.warn("analysis leak cut, but its text was not found in the transcript to scrub");
|
|
10701
10882
|
continue;
|
|
10702
10883
|
}
|
|
10703
10884
|
const m = t[i];
|
|
@@ -10754,7 +10935,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10754
10935
|
const res = await this.sendWithContext(content, context, note);
|
|
10755
10936
|
const fb = this.options.reflexFallbackModel, model = this.voice.options.model;
|
|
10756
10937
|
if (res.finishReason !== "error" || !rateLimited(res.error) || !fb || fb === model || fb.includes("/") && model.includes("/") && fb.split("/")[0] !== model.split("/")[0] || this.turnDispatched || this.turnSpoken.trim()) return res;
|
|
10757
|
-
|
|
10938
|
+
log25.warn(`reflex ${model} rate-limited \u2014 re-running the turn on ${fb}`);
|
|
10758
10939
|
this.notify("diag", "reflex_fallback", { from: model, to: fb });
|
|
10759
10940
|
this.voice.transcript.length = base;
|
|
10760
10941
|
this.voice.options.model = fb;
|
|
@@ -10803,7 +10984,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10803
10984
|
try {
|
|
10804
10985
|
await this.sendWithContext(reminder, this.lastContext);
|
|
10805
10986
|
} catch (e) {
|
|
10806
|
-
|
|
10987
|
+
log25.warn(`follow-through nudge failed: ${e instanceof Error ? e.message : e}`);
|
|
10807
10988
|
} finally {
|
|
10808
10989
|
const i = this.voice.transcript.findIndex((m, k) => k >= base && m.role === "user" && m.content === reminder);
|
|
10809
10990
|
if (i >= 0) this.voice.transcript.splice(i, 1);
|
|
@@ -10864,7 +11045,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10864
11045
|
return {
|
|
10865
11046
|
preToolUse: (call) => {
|
|
10866
11047
|
if (this.spec?.state === "pending" && !_DuplexAgent.SPEC_SAFE_TOOLS.has(call.name)) {
|
|
10867
|
-
|
|
11048
|
+
log25.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
|
|
10868
11049
|
this.abortSpeculation();
|
|
10869
11050
|
return { block: true, reason: "Speculative turn aborted." };
|
|
10870
11051
|
}
|
|
@@ -10946,7 +11127,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10946
11127
|
try {
|
|
10947
11128
|
await this.sendWithContext(reminder, this.lastContext);
|
|
10948
11129
|
} catch (e) {
|
|
10949
|
-
|
|
11130
|
+
log25.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
|
|
10950
11131
|
} finally {
|
|
10951
11132
|
this.nudging = false;
|
|
10952
11133
|
const i = this.voice.transcript.findIndex((m, k) => k >= base && m.role === "user" && m.content === reminder);
|
|
@@ -11002,7 +11183,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11002
11183
|
const accepted = this.offerOpen && this.parkedRedeliver.length > 0 && this.parkedRedeliver.every((p) => p.task === this.offer.task) && (isOfferAcceptance(content) || isTrivialBarge(content));
|
|
11003
11184
|
const judge = accepted ? void 0 : this.options.bargeIntent;
|
|
11004
11185
|
return (accepted ? Promise.resolve("resume") : judge ? judge(content).catch((e) => {
|
|
11005
|
-
|
|
11186
|
+
log25.warn(`bargeIntent judge failed \u2014 using the allowlist: ${e.message}`);
|
|
11006
11187
|
return fallback();
|
|
11007
11188
|
}) : Promise.resolve(fallback())).then((intent) => {
|
|
11008
11189
|
this.notify("diag", "barge_intent", { intent, text: content.slice(0, 80) });
|
|
@@ -11205,7 +11386,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11205
11386
|
try {
|
|
11206
11387
|
res = await this.sendWithContext(spec.text, this.lastContext);
|
|
11207
11388
|
} catch (e) {
|
|
11208
|
-
|
|
11389
|
+
log25.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
|
|
11209
11390
|
} finally {
|
|
11210
11391
|
this.voice.options.signal = prevSignal;
|
|
11211
11392
|
}
|
|
@@ -11219,7 +11400,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11219
11400
|
if (d === "abort") {
|
|
11220
11401
|
if (this.voice.transcript.length > base) this.voice.transcript.length = base;
|
|
11221
11402
|
this.speculativeAbortedCalls++;
|
|
11222
|
-
|
|
11403
|
+
log25.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
|
|
11223
11404
|
if (this.spec === spec) this.spec = void 0;
|
|
11224
11405
|
return res ?? empty;
|
|
11225
11406
|
}
|
|
@@ -11605,7 +11786,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
11605
11786
|
this.notify("task_verify", `task ${id}: verifying`, { id });
|
|
11606
11787
|
const cres = await new Agent(checkerOpts).run(checkBrief);
|
|
11607
11788
|
if (cres.finishReason !== "stop") {
|
|
11608
|
-
|
|
11789
|
+
log25.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
|
|
11609
11790
|
this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
|
|
11610
11791
|
}
|
|
11611
11792
|
const sum = (a = 0, b = 0) => a + b;
|
|
@@ -11741,7 +11922,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
11741
11922
|
rec.status = "done";
|
|
11742
11923
|
rec.result = res.text;
|
|
11743
11924
|
const incomplete = res.finishReason !== "stop" && res.finishReason !== "empty";
|
|
11744
|
-
|
|
11925
|
+
log25.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
|
|
11745
11926
|
this.notify("task_done", `task ${id} (${rec.label}) completed`, {
|
|
11746
11927
|
id,
|
|
11747
11928
|
text: res.text,
|
|
@@ -11775,14 +11956,14 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
11775
11956
|
foldIfSuperseded(rec, note) {
|
|
11776
11957
|
if (rec.deliveryParked !== "moved_on") return false;
|
|
11777
11958
|
if (note.trim()) this.voice.transcript.push({ role: "assistant", content: note });
|
|
11778
|
-
|
|
11959
|
+
log25.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
|
|
11779
11960
|
return true;
|
|
11780
11961
|
}
|
|
11781
11962
|
failTask(rec, msg) {
|
|
11782
11963
|
this.dropAsk(rec.id);
|
|
11783
11964
|
rec.status = "error";
|
|
11784
11965
|
rec.result = msg;
|
|
11785
|
-
|
|
11966
|
+
log25.warn(`task ${rec.id} failed: ${msg}`);
|
|
11786
11967
|
this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`, { id: rec.id });
|
|
11787
11968
|
if (this.foldIfSuperseded(rec, `The "${rec.label}" task failed: ${msg}`)) return;
|
|
11788
11969
|
this.dropParked(rec);
|
|
@@ -12086,7 +12267,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
12086
12267
|
init_llm();
|
|
12087
12268
|
init_logging();
|
|
12088
12269
|
init_models();
|
|
12089
|
-
var
|
|
12270
|
+
var log26 = forComponent("ModelRouter");
|
|
12090
12271
|
var ModelRouterOptions = class {
|
|
12091
12272
|
classifier = jevClassifier();
|
|
12092
12273
|
/** Capability tiers → model id (any provider). basic/standard run the main session; premium is only ever an offload. */
|
|
@@ -12170,10 +12351,10 @@ var ModelRouter = class {
|
|
|
12170
12351
|
if (tier === "premium") d.offload = this.state.offload && (c.sameThread ?? 0) >= o.sameThreadAt ? "continue" : "fresh";
|
|
12171
12352
|
} catch (e) {
|
|
12172
12353
|
const msg = e instanceof Error ? e.message : String(e);
|
|
12173
|
-
|
|
12354
|
+
log26.warn(`classifier failed, falling back to standard: ${msg}`);
|
|
12174
12355
|
d = { tier: "standard", model: o.models.standard, probs: {}, fallback: msg, classifyMs: Math.round(performance.now() - t0) };
|
|
12175
12356
|
}
|
|
12176
|
-
|
|
12357
|
+
log26.info(`route ${d.tier}${d.offload ? `(${d.offload})` : ""} \xB7 argmax=${d.choice ?? "-"} \xB7 p=${JSON.stringify(d.probs)}${d.sameThread != null ? ` \xB7 same=${d.sameThread.toFixed(2)}` : ""} \xB7 ${d.classifyMs}ms`);
|
|
12177
12358
|
return d;
|
|
12178
12359
|
}
|
|
12179
12360
|
/** Run one user turn on `agent` with routing. Returns a RunResult carrying `costUsd` + `routing`. */
|
|
@@ -12225,7 +12406,7 @@ ${text}`;
|
|
|
12225
12406
|
const answer = res.text || `(premium sub-agent finished with no reply; finishReason=${res.finishReason})`;
|
|
12226
12407
|
agent.transcript.push({ role: "user", content }, { role: "assistant", content: answer });
|
|
12227
12408
|
this.state.offload = { messages: child.transcript, mainLen: agent.transcript.length, question: text };
|
|
12228
|
-
|
|
12409
|
+
log26.info(`offload ${cont ? "continue" : "fresh"} done \xB7 ${res.finishReason} \xB7 ${res.steps} steps`);
|
|
12229
12410
|
const costUsd = o.costOf(main, briefUsage) + o.costOf(o.models.premium, res.usage);
|
|
12230
12411
|
return { ...res, text: answer, messages: agent.transcript, usage: addUsage(briefUsage, res.usage), costUsd, routing: d };
|
|
12231
12412
|
}
|
|
@@ -12241,7 +12422,7 @@ The request:
|
|
|
12241
12422
|
${text}`);
|
|
12242
12423
|
return { text: r.text.trim() || text, usage: r.usage };
|
|
12243
12424
|
} catch (e) {
|
|
12244
|
-
|
|
12425
|
+
log26.warn(`brief failed, sending the raw request: ${e instanceof Error ? e.message : String(e)}`);
|
|
12245
12426
|
return { text };
|
|
12246
12427
|
} finally {
|
|
12247
12428
|
agent.transcript = snap;
|
|
@@ -12286,7 +12467,7 @@ async function jevAvailable(opts = {}) {
|
|
|
12286
12467
|
const { env } = await loadJev(opts);
|
|
12287
12468
|
return JEV_KEYS.some((k) => env[k]?.trim());
|
|
12288
12469
|
} catch (e) {
|
|
12289
|
-
|
|
12470
|
+
log26.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
|
|
12290
12471
|
return false;
|
|
12291
12472
|
}
|
|
12292
12473
|
}
|
|
@@ -12571,7 +12752,7 @@ init_models();
|
|
|
12571
12752
|
|
|
12572
12753
|
// src/voice/progress.ts
|
|
12573
12754
|
init_logging();
|
|
12574
|
-
var
|
|
12755
|
+
var log27 = forComponent("Progress");
|
|
12575
12756
|
var HE_RESULT_CLAIM = /(?:^|\s)(?:מצאתי|סיימתי|בוצע|הנה|אין)(?=\s|$|[.,!…])/u;
|
|
12576
12757
|
var TOPICS = [
|
|
12577
12758
|
[/toolsearch|^mcp__.*__(list_commands|call_info)$/, null],
|
|
@@ -12716,7 +12897,7 @@ var Progress = class {
|
|
|
12716
12897
|
}, this.o.generateBudgetMs);
|
|
12717
12898
|
});
|
|
12718
12899
|
const raw = await Promise.race([this.o.generate(tool, argsHint(args), lang, ac.signal).catch((e) => {
|
|
12719
|
-
|
|
12900
|
+
log27.warn(`progress line failed: ${e?.message ?? e}`);
|
|
12720
12901
|
return null;
|
|
12721
12902
|
}), late]).finally(() => clearTimeout(t));
|
|
12722
12903
|
const l = cleanLine(raw ?? "", lang, 5);
|
|
@@ -12738,7 +12919,7 @@ init_logging();
|
|
|
12738
12919
|
|
|
12739
12920
|
// src/voice/backchannelPlan.ts
|
|
12740
12921
|
init_logging();
|
|
12741
|
-
var
|
|
12922
|
+
var log28 = forComponent("BackchannelPlan");
|
|
12742
12923
|
var silentTurn = (p) => p.density === "none" || p.turn === "short_command" || p.turn === "question";
|
|
12743
12924
|
var cueWords = (s) => s.toLowerCase().replace(/[^\p{L}\p{N}'\s]+/gu, " ").split(/\s+/).filter(Boolean);
|
|
12744
12925
|
function matchCue(plan, heard, from = 0) {
|
|
@@ -12756,7 +12937,7 @@ function matchCue(plan, heard, from = 0) {
|
|
|
12756
12937
|
}
|
|
12757
12938
|
|
|
12758
12939
|
// src/voice/secondChannel.ts
|
|
12759
|
-
var
|
|
12940
|
+
var log29 = forComponent("SecondChannel");
|
|
12760
12941
|
var CHANNEL_MODES = {
|
|
12761
12942
|
turn: { kinds: ["bridge"], bridgeDelayMs: 1500 },
|
|
12762
12943
|
normal: {},
|
|
@@ -12873,15 +13054,15 @@ function withBudget(p, ms, what, ac) {
|
|
|
12873
13054
|
let t;
|
|
12874
13055
|
const late = new Promise((r) => {
|
|
12875
13056
|
t = setTimeout(() => {
|
|
12876
|
-
|
|
13057
|
+
log29.debug(`${what}: over the ${ms}ms budget \u2014 dropped`);
|
|
12877
13058
|
ac?.abort();
|
|
12878
13059
|
r(null);
|
|
12879
13060
|
}, ms);
|
|
12880
13061
|
});
|
|
12881
13062
|
return Promise.race([p.catch((e) => {
|
|
12882
13063
|
const msg = String(e?.message ?? e);
|
|
12883
|
-
if (e?.name === "AbortError" || /aborted/i.test(msg))
|
|
12884
|
-
else
|
|
13064
|
+
if (e?.name === "AbortError" || /aborted/i.test(msg)) log29.debug(`${what}: aborted after its budget`);
|
|
13065
|
+
else log29.warn(`${what} failed \u2014 falling back: ${msg}`);
|
|
12885
13066
|
return null;
|
|
12886
13067
|
}), late]).finally(() => clearTimeout(t));
|
|
12887
13068
|
}
|
|
@@ -12960,7 +13141,7 @@ var SecondChannel = class {
|
|
|
12960
13141
|
if (host) this.pinned = true;
|
|
12961
13142
|
const lang = primaryLang(tag);
|
|
12962
13143
|
if (!lang || lang === this.lang) return;
|
|
12963
|
-
|
|
13144
|
+
log29.debug(`language ${this.lang} \u2192 ${lang}`);
|
|
12964
13145
|
this.lang = lang;
|
|
12965
13146
|
this.recent = [];
|
|
12966
13147
|
this.picks.clear();
|
|
@@ -13105,7 +13286,7 @@ var SecondChannel = class {
|
|
|
13105
13286
|
try {
|
|
13106
13287
|
this.o.cancel();
|
|
13107
13288
|
} catch (e) {
|
|
13108
|
-
|
|
13289
|
+
log29.warn(`cancel threw: ${e.message}`);
|
|
13109
13290
|
}
|
|
13110
13291
|
}
|
|
13111
13292
|
}
|
|
@@ -13217,7 +13398,7 @@ var SecondChannel = class {
|
|
|
13217
13398
|
const opts = this.options;
|
|
13218
13399
|
if (opts.mode === "chatty" && !opts.aec && !this.degradedLogged) {
|
|
13219
13400
|
this.degradedLogged = true;
|
|
13220
|
-
|
|
13401
|
+
log29.info("chatty mode without echo cancellation \u2014 normal timing (our lines would re-trigger the mic)");
|
|
13221
13402
|
}
|
|
13222
13403
|
const rule = this.rule(trigger, pauseMs, breath, cue);
|
|
13223
13404
|
if (rule === "silence") {
|
|
@@ -13236,7 +13417,7 @@ var SecondChannel = class {
|
|
|
13236
13417
|
const d = await withBudget(this.o.decide(this.tail(), { ...this.state(pauseMs), rule }, ac.signal), this.o.decisionBudgetMs, "channel decision", ac);
|
|
13237
13418
|
choice = d?.choice;
|
|
13238
13419
|
if (choice && choice in CHANNEL_ACTIONS && (choice === "silence" || this.allowed(choice))) kind = choice;
|
|
13239
|
-
else if (choice && choice in CHANNEL_ACTIONS)
|
|
13420
|
+
else if (choice && choice in CHANNEL_ACTIONS) log29.debug(`decider chose ${choice} for a ${rule} moment \u2014 the rule stands`);
|
|
13240
13421
|
}
|
|
13241
13422
|
if (kind === "silence") return ev({ choice, acted: "silence" });
|
|
13242
13423
|
if (stale()) return ev({ choice, kind, acted: "stale" });
|
|
@@ -13274,7 +13455,7 @@ var SecondChannel = class {
|
|
|
13274
13455
|
void withBudget(this.o.generate(ctx, ac.signal), Math.max(this.o.generateBudgetMs, this.o.bridgeDelayMs), "bridge generate", ac).then((raw) => {
|
|
13275
13456
|
const text = cleanLine(raw ?? "", ctx.lang);
|
|
13276
13457
|
if (text && gen === this.gen) this.bridgeDraft = { gen, text };
|
|
13277
|
-
}).catch((e) =>
|
|
13458
|
+
}).catch((e) => log29.debug(`bridge prefetch failed: ${e?.message ?? e}`));
|
|
13278
13459
|
}
|
|
13279
13460
|
async line(kind, react = true) {
|
|
13280
13461
|
if (kind === "bridge" && this.o.instantBridge) {
|
|
@@ -13290,7 +13471,7 @@ var SecondChannel = class {
|
|
|
13290
13471
|
if (raw === null) ac.abort();
|
|
13291
13472
|
const text = cleanLine(raw ?? "", this.lang);
|
|
13292
13473
|
if (text && !this.recent.some((r) => norm3(r) === norm3(text))) return text;
|
|
13293
|
-
if (text)
|
|
13474
|
+
if (text) log29.debug(`generated "${text}" repeats a recent line \u2014 canned`);
|
|
13294
13475
|
}
|
|
13295
13476
|
return this.pick(kind);
|
|
13296
13477
|
}
|
|
@@ -13312,7 +13493,7 @@ var SecondChannel = class {
|
|
|
13312
13493
|
try {
|
|
13313
13494
|
r = this.o.speak(text, kind);
|
|
13314
13495
|
} catch (e) {
|
|
13315
|
-
|
|
13496
|
+
log29.warn(`speak threw \u2014 line dropped: ${e.message}`);
|
|
13316
13497
|
return false;
|
|
13317
13498
|
}
|
|
13318
13499
|
if (r === false) return false;
|
|
@@ -13325,7 +13506,7 @@ var SecondChannel = class {
|
|
|
13325
13506
|
this.o.show(text, kind);
|
|
13326
13507
|
const p = Promise.resolve(r).then(() => {
|
|
13327
13508
|
}, (e) => {
|
|
13328
|
-
|
|
13509
|
+
log29.warn(`channel line playback failed: ${e?.message ?? e}`);
|
|
13329
13510
|
}).finally(() => {
|
|
13330
13511
|
if (this.inflight === p) this.inflight = null;
|
|
13331
13512
|
});
|
|
@@ -13383,7 +13564,7 @@ function closedQuestion(reply) {
|
|
|
13383
13564
|
}
|
|
13384
13565
|
|
|
13385
13566
|
// src/voice/call.ts
|
|
13386
|
-
var
|
|
13567
|
+
var log30 = forComponent("VoiceCall");
|
|
13387
13568
|
var VoiceCallOptions = class {
|
|
13388
13569
|
/** Host runs MAIN with exactly `text` (a replace: roll the `replaces` turn back first). */
|
|
13389
13570
|
sendTurn = () => {
|
|
@@ -13609,7 +13790,7 @@ var VoiceCall = class {
|
|
|
13609
13790
|
try {
|
|
13610
13791
|
this.o.cancel();
|
|
13611
13792
|
} catch (e) {
|
|
13612
|
-
|
|
13793
|
+
log30.warn(`cancel threw: ${e.message}`);
|
|
13613
13794
|
}
|
|
13614
13795
|
if (this.cur?.running) {
|
|
13615
13796
|
this.cur.running = false;
|
|
@@ -13696,7 +13877,7 @@ var VoiceCall = class {
|
|
|
13696
13877
|
try {
|
|
13697
13878
|
r = this.o.speak(text, kind);
|
|
13698
13879
|
} catch (e) {
|
|
13699
|
-
|
|
13880
|
+
log30.warn(`speak threw: ${e.message}`);
|
|
13700
13881
|
return false;
|
|
13701
13882
|
}
|
|
13702
13883
|
if (r === false) return false;
|
|
@@ -13776,6 +13957,7 @@ var VoiceCall = class {
|
|
|
13776
13957
|
init_llm();
|
|
13777
13958
|
import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor as CommandExecutor2, registerHeadlessCommands as registerHeadlessCommands2 } from "@livx.cc/wcli/core";
|
|
13778
13959
|
export {
|
|
13960
|
+
ADVISE_LIMIT,
|
|
13779
13961
|
Agent,
|
|
13780
13962
|
AgentOptions,
|
|
13781
13963
|
BodDbFilesystem,
|
|
@@ -13806,6 +13988,9 @@ export {
|
|
|
13806
13988
|
MountFilesystem,
|
|
13807
13989
|
NodeDiskFilesystem,
|
|
13808
13990
|
OverlayFilesystem,
|
|
13991
|
+
PLANNER_BLOCKED,
|
|
13992
|
+
PLANNER_REFUSAL,
|
|
13993
|
+
PLAN_FIRST,
|
|
13809
13994
|
PROGRESS_TEMPLATES,
|
|
13810
13995
|
PermissionOptions,
|
|
13811
13996
|
PermissionPolicy,
|
|
@@ -13830,6 +14015,9 @@ export {
|
|
|
13830
14015
|
VoiceCallOptions,
|
|
13831
14016
|
VoiceEngine,
|
|
13832
14017
|
VoiceEngineOptions,
|
|
14018
|
+
WRITE_TOOLS,
|
|
14019
|
+
advisorFor,
|
|
14020
|
+
advisorPrompt,
|
|
13833
14021
|
applyEditsTool,
|
|
13834
14022
|
askUserQuestionTool,
|
|
13835
14023
|
bashTool,
|
|
@@ -13846,6 +14034,9 @@ export {
|
|
|
13846
14034
|
cronMatches,
|
|
13847
14035
|
decodeDdgUrl,
|
|
13848
14036
|
defaultTools,
|
|
14037
|
+
delegatePrompt,
|
|
14038
|
+
delegateTools,
|
|
14039
|
+
delegateWorkerFor,
|
|
13849
14040
|
diskAgentOptions,
|
|
13850
14041
|
editTool,
|
|
13851
14042
|
elideImages,
|
|
@@ -13885,6 +14076,7 @@ export {
|
|
|
13885
14076
|
loadMemory,
|
|
13886
14077
|
loadSkills,
|
|
13887
14078
|
log,
|
|
14079
|
+
makeAdvisor,
|
|
13888
14080
|
makeAskTool,
|
|
13889
14081
|
makeContext,
|
|
13890
14082
|
makeGlobTool,
|
|
@@ -13904,6 +14096,7 @@ export {
|
|
|
13904
14096
|
messageHasImage,
|
|
13905
14097
|
messageHasImageRef,
|
|
13906
14098
|
messageHasInlineImage,
|
|
14099
|
+
meterWorkers,
|
|
13907
14100
|
mintImageRef,
|
|
13908
14101
|
mkdirp,
|
|
13909
14102
|
modelShortLabel,
|