@livx.cc/agentx 0.99.120 → 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 +719 -254
- 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 +7 -2
- package/dist/index.js +392 -158
- package/dist/index.js.map +1 -1
- package/dist/{secondChannel-DepBV0Zv.d.ts → secondChannel-BddfVzTp.d.ts} +109 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2954,7 +2954,7 @@ async function psGroupCpuSeconds(pgid) {
|
|
|
2954
2954
|
try {
|
|
2955
2955
|
return await winTreeCpuSeconds(pgid);
|
|
2956
2956
|
} catch (e) {
|
|
2957
|
-
|
|
2957
|
+
log31.debug(`cpu probe failed for ${pgid}: ${e}`);
|
|
2958
2958
|
return void 0;
|
|
2959
2959
|
}
|
|
2960
2960
|
}
|
|
@@ -2963,7 +2963,7 @@ async function psGroupCpuSeconds(pgid) {
|
|
|
2963
2963
|
const out = await new Promise((res, rej) => execFile5("ps", ["-A", "-o", "pid=,ppid=,pgid=,time="], { timeout: 5e3, env: { ...process.env, LC_ALL: "C" } }, (e, so) => e ? rej(e) : res(String(so))));
|
|
2964
2964
|
return sumTreeCpu(out, pgid);
|
|
2965
2965
|
} catch (e) {
|
|
2966
|
-
|
|
2966
|
+
log31.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
|
|
2967
2967
|
return void 0;
|
|
2968
2968
|
}
|
|
2969
2969
|
}
|
|
@@ -3112,7 +3112,7 @@ function makeRealShellTool(options) {
|
|
|
3112
3112
|
proc.stderr?.on("data", collect);
|
|
3113
3113
|
proc.on("error", (err2) => {
|
|
3114
3114
|
if (err2?.name === "AbortError" || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean2(out)));
|
|
3115
|
-
|
|
3115
|
+
log31.debug("shell spawn error", err2);
|
|
3116
3116
|
finish(`[exit 1] ${err2?.message ?? err2}${out ? "\n" + clean2(out) : ""}`);
|
|
3117
3117
|
});
|
|
3118
3118
|
proc.on("close", (code) => {
|
|
@@ -3191,7 +3191,7 @@ ${clean2(out) || "(no output yet)"}`;
|
|
|
3191
3191
|
}
|
|
3192
3192
|
];
|
|
3193
3193
|
}
|
|
3194
|
-
var
|
|
3194
|
+
var log31, clean2, DETACHED, shellArgv, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
|
|
3195
3195
|
var init_tools_shell = __esm({
|
|
3196
3196
|
"src/tools.shell.ts"() {
|
|
3197
3197
|
"use strict";
|
|
@@ -3201,7 +3201,7 @@ var init_tools_shell = __esm({
|
|
|
3201
3201
|
init_shell_sandbox();
|
|
3202
3202
|
init_tools_jobs();
|
|
3203
3203
|
init_platform();
|
|
3204
|
-
|
|
3204
|
+
log31 = forComponent("shell");
|
|
3205
3205
|
clean2 = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
|
|
3206
3206
|
DETACHED = { stdio: ["ignore", "pipe", "pipe"], ...DETACH_OPTIONS };
|
|
3207
3207
|
shellArgv = (sh, command) => ({ bin: sh.bin, args: sh.args(command), ...sh.spawnOptions ? { spawnOptions: sh.spawnOptions } : {} });
|
|
@@ -4385,7 +4385,11 @@ function childOptionsFor(opts, fs, depth, maxDepth, agentType, model, root) {
|
|
|
4385
4385
|
if (po) childOpts.providerOptions = po;
|
|
4386
4386
|
const deny = opts.permissions?.options.rules.filter((r) => r.decision === "deny") ?? [];
|
|
4387
4387
|
if (deny.length) childOpts.permissions = new PermissionPolicy({ rules: deny, default: "allow" });
|
|
4388
|
-
|
|
4388
|
+
const h = opts.hooks;
|
|
4389
|
+
if (h?.preToolUse || opts.inheritPostToolUse && h?.postToolUse) childOpts.hooks = {
|
|
4390
|
+
...h.preToolUse ? { preToolUse: h.preToolUse.bind(h) } : {},
|
|
4391
|
+
...opts.inheritPostToolUse && h.postToolUse ? { postToolUse: h.postToolUse.bind(h) } : {}
|
|
4392
|
+
};
|
|
4389
4393
|
return childOpts;
|
|
4390
4394
|
}
|
|
4391
4395
|
function makeTaskTool(opts) {
|
|
@@ -4609,6 +4613,147 @@ async function loadAgents(fs, dir) {
|
|
|
4609
4613
|
init_OverlayFilesystem();
|
|
4610
4614
|
init_logging();
|
|
4611
4615
|
|
|
4616
|
+
// src/modes.ts
|
|
4617
|
+
init_logging();
|
|
4618
|
+
var log8 = forComponent("modes");
|
|
4619
|
+
var WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "ApplyEdits"]);
|
|
4620
|
+
var PLANNER_BLOCKED = /* @__PURE__ */ new Set([...WRITE_TOOLS, "bash", "Shell"]);
|
|
4621
|
+
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.";
|
|
4622
|
+
var PLAN_FIRST = "Refused: check your plan with `Advise` before the first write (files, data shape, the hard parts). Then write.";
|
|
4623
|
+
var ADVISE_LIMIT = 3;
|
|
4624
|
+
var SNAPSHOT_CAP = 6e4;
|
|
4625
|
+
function pairOk(kind, main2, other) {
|
|
4626
|
+
if (other === main2) {
|
|
4627
|
+
log8.warn(`${kind} off: both sides are ${main2}`);
|
|
4628
|
+
return false;
|
|
4629
|
+
}
|
|
4630
|
+
if (main2.startsWith("cursor/") || other.startsWith("cursor/")) {
|
|
4631
|
+
log8.warn(`${kind} off: cursor models are not supported (${main2} / ${other})`);
|
|
4632
|
+
return false;
|
|
4633
|
+
}
|
|
4634
|
+
return true;
|
|
4635
|
+
}
|
|
4636
|
+
function delegateWorkerFor(planner, worker) {
|
|
4637
|
+
return worker && pairOk("delegate", planner, worker) ? worker : void 0;
|
|
4638
|
+
}
|
|
4639
|
+
function advisorFor(worker, advisor) {
|
|
4640
|
+
return advisor && pairOk("advisor", worker, advisor) ? advisor : void 0;
|
|
4641
|
+
}
|
|
4642
|
+
function delegateTools(tools, planner) {
|
|
4643
|
+
return tools.map((t) => !PLANNER_BLOCKED.has(t.name) ? t : {
|
|
4644
|
+
...t,
|
|
4645
|
+
run: async (...a) => a[1]?.model === planner ? PLANNER_REFUSAL : t.run(...a)
|
|
4646
|
+
});
|
|
4647
|
+
}
|
|
4648
|
+
function delegatePrompt(worker) {
|
|
4649
|
+
return `
|
|
4650
|
+
|
|
4651
|
+
## You are the PLANNER
|
|
4652
|
+
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.`;
|
|
4653
|
+
}
|
|
4654
|
+
function advisorPrompt(advisor) {
|
|
4655
|
+
return `
|
|
4656
|
+
|
|
4657
|
+
## You have an ADVISOR
|
|
4658
|
+
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.`;
|
|
4659
|
+
}
|
|
4660
|
+
async function textOf(r) {
|
|
4661
|
+
if (r && typeof r[Symbol.asyncIterator] === "function") {
|
|
4662
|
+
let s = "";
|
|
4663
|
+
for await (const c of r) if (typeof c?.content === "string") s += c.content;
|
|
4664
|
+
return s;
|
|
4665
|
+
}
|
|
4666
|
+
return String(r?.content ?? r?.text ?? "");
|
|
4667
|
+
}
|
|
4668
|
+
function makeAdvisor(o) {
|
|
4669
|
+
let used = 0;
|
|
4670
|
+
const limit = o.limit ?? ADVISE_LIMIT;
|
|
4671
|
+
const tool = {
|
|
4672
|
+
name: "Advise",
|
|
4673
|
+
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.`,
|
|
4674
|
+
parameters: { type: "object", properties: { question: { type: "string", description: "what you want advice on, with the context it needs" } }, required: ["question"] },
|
|
4675
|
+
async run(args) {
|
|
4676
|
+
const question = String(args?.question ?? "").trim();
|
|
4677
|
+
if (!question) return "No advice: the question is empty.";
|
|
4678
|
+
if (used >= limit) return `No advice: the limit of ${limit} calls is used. Continue with your own judgment.`;
|
|
4679
|
+
used++;
|
|
4680
|
+
let state = "";
|
|
4681
|
+
if (o.snapshot) {
|
|
4682
|
+
const s = String(await Promise.resolve(o.snapshot()).catch((err2) => {
|
|
4683
|
+
log8.warn("advisor snapshot failed:", String(err2));
|
|
4684
|
+
return "";
|
|
4685
|
+
}) || "(empty)");
|
|
4686
|
+
state = `
|
|
4687
|
+
|
|
4688
|
+
## Current state
|
|
4689
|
+
${s.length > SNAPSHOT_CAP ? `${s.slice(0, SNAPSHOT_CAP)}
|
|
4690
|
+
\u2026(cut)` : s}`;
|
|
4691
|
+
}
|
|
4692
|
+
const review = used > 1 ? "" : "\n\n(When the work is complete, call Advise again for a review.)";
|
|
4693
|
+
try {
|
|
4694
|
+
const r = await o.ai.chat({
|
|
4695
|
+
model: o.advisor,
|
|
4696
|
+
messages: [
|
|
4697
|
+
{ 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." },
|
|
4698
|
+
{ role: "user", content: `## User request
|
|
4699
|
+
${o.request()}${state}
|
|
4700
|
+
|
|
4701
|
+
## Question
|
|
4702
|
+
${question}` }
|
|
4703
|
+
],
|
|
4704
|
+
maxTokens: 2e3
|
|
4705
|
+
});
|
|
4706
|
+
return ((await textOf(r)).trim() || "No advice: the advisor returned nothing.") + review;
|
|
4707
|
+
} catch (err2) {
|
|
4708
|
+
log8.warn("advisor call failed:", String(err2));
|
|
4709
|
+
return `No advice: the advisor call failed (${String(err2?.message ?? err2).slice(0, 120)}). Continue with your own judgment.`;
|
|
4710
|
+
}
|
|
4711
|
+
}
|
|
4712
|
+
};
|
|
4713
|
+
const gate = (tools) => tools.map((t) => !WRITE_TOOLS.has(t.name) ? t : { ...t, run: async (...a) => used ? t.run(...a) : PLAN_FIRST });
|
|
4714
|
+
return { tool, gate, used: () => used };
|
|
4715
|
+
}
|
|
4716
|
+
function meterWorkers(ai, main2) {
|
|
4717
|
+
let total;
|
|
4718
|
+
let resolved;
|
|
4719
|
+
const seen = () => {
|
|
4720
|
+
resolved = ai.modelUsed ?? resolved;
|
|
4721
|
+
};
|
|
4722
|
+
const add = (u) => {
|
|
4723
|
+
if (!u) return;
|
|
4724
|
+
const t = total ??= { promptTokens: 0, completionTokens: 0, totalTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
4725
|
+
t.promptTokens += u.promptTokens ?? 0;
|
|
4726
|
+
t.completionTokens += u.completionTokens ?? 0;
|
|
4727
|
+
t.totalTokens += u.totalTokens ?? 0;
|
|
4728
|
+
t.cacheCreationTokens += u.cacheCreationTokens ?? 0;
|
|
4729
|
+
t.cacheReadTokens += u.cacheReadTokens ?? 0;
|
|
4730
|
+
};
|
|
4731
|
+
async function* tap(it) {
|
|
4732
|
+
let last;
|
|
4733
|
+
try {
|
|
4734
|
+
for await (const c of it) {
|
|
4735
|
+
if (c?.usage) last = c.usage;
|
|
4736
|
+
yield c;
|
|
4737
|
+
}
|
|
4738
|
+
} finally {
|
|
4739
|
+
add(last);
|
|
4740
|
+
seen();
|
|
4741
|
+
}
|
|
4742
|
+
}
|
|
4743
|
+
const chat = async (o) => {
|
|
4744
|
+
const r = await ai.chat(o);
|
|
4745
|
+
if (o?.model === main2) return r;
|
|
4746
|
+
if (r && typeof r[Symbol.asyncIterator] === "function") return tap(r);
|
|
4747
|
+
add(r?.usage);
|
|
4748
|
+
seen();
|
|
4749
|
+
return r;
|
|
4750
|
+
};
|
|
4751
|
+
return { ai: new Proxy(ai, { get: (t, k) => k === "chat" ? chat : Reflect.get(t, k) }), usage: () => total && { ...total }, model: () => resolved, reset: () => {
|
|
4752
|
+
total = void 0;
|
|
4753
|
+
resolved = void 0;
|
|
4754
|
+
} };
|
|
4755
|
+
}
|
|
4756
|
+
|
|
4612
4757
|
// src/lint.ts
|
|
4613
4758
|
var CODE_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
4614
4759
|
function checkSyntax(path, content) {
|
|
@@ -4688,7 +4833,7 @@ function reasoningToChatFragment(model, effort) {
|
|
|
4688
4833
|
}
|
|
4689
4834
|
|
|
4690
4835
|
// src/Agent.ts
|
|
4691
|
-
var
|
|
4836
|
+
var log9 = forComponent("Agent");
|
|
4692
4837
|
function envInt(key, fallback) {
|
|
4693
4838
|
const n = typeof process !== "undefined" ? Number(process.env?.[key]) : NaN;
|
|
4694
4839
|
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
@@ -4817,6 +4962,13 @@ var AgentOptions = class {
|
|
|
4817
4962
|
hooks;
|
|
4818
4963
|
/** If true: add the `Task` tool so the agent can spawn depth-limited child agents over the VFS. */
|
|
4819
4964
|
subagents = false;
|
|
4965
|
+
/** DELEGATE MODE: `model` plans, `worker` writes. The planner's Write/Edit/MultiEdit/ApplyEdits/bash/Shell are
|
|
4966
|
+
* refused at execution; Task/TaskBatch are on (maxDepth 1, children default to `worker`) and children get the
|
|
4967
|
+
* parent's `postToolUse` too. Off when the models are equal or either is cursor/*. Wins over `advisor`. */
|
|
4968
|
+
delegate;
|
|
4969
|
+
/** ADVISOR MODE: `model` does the work; an `Advise` tool asks `advisor.model` (≤`limit`, default 3) with only
|
|
4970
|
+
* the request + `snapshot()` (host state, optional) + the question. The first write waits for one consult. */
|
|
4971
|
+
advisor;
|
|
4820
4972
|
/** VFS dir of typed-subagent defs (`<dir>/<name>.md`). If set with `subagents`: inject a catalog + enable the `Task` `agentType` param. */
|
|
4821
4973
|
agentsDir;
|
|
4822
4974
|
/** Current recursion depth (0 = top-level); spawned children run at depth+1. */
|
|
@@ -4925,6 +5077,11 @@ var Agent = class _Agent {
|
|
|
4925
5077
|
lastTrimNotified = 0;
|
|
4926
5078
|
// last auto-trim drop count surfaced via host.notify (dedup)
|
|
4927
5079
|
activeTools = [];
|
|
5080
|
+
meter;
|
|
5081
|
+
// delegate/advisor: meters the second model's calls
|
|
5082
|
+
modeModel;
|
|
5083
|
+
lastTask = "";
|
|
5084
|
+
// the current turn's text — what the advisor reads as the request
|
|
4928
5085
|
/** claude-code bridge for agentx-only tools (ccBridge.ts) + the current step's executor it calls. */
|
|
4929
5086
|
ccServer;
|
|
4930
5087
|
ccExec;
|
|
@@ -5108,7 +5265,7 @@ var Agent = class _Agent {
|
|
|
5108
5265
|
const disk = new NodeDiskFilesystem2(process.cwd());
|
|
5109
5266
|
await disk.init();
|
|
5110
5267
|
this.options.fs = new JailedFilesystem2(disk);
|
|
5111
|
-
|
|
5268
|
+
log9.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
|
|
5112
5269
|
}
|
|
5113
5270
|
this.buildCtx();
|
|
5114
5271
|
}
|
|
@@ -5125,6 +5282,12 @@ var Agent = class _Agent {
|
|
|
5125
5282
|
const fs = o.fs;
|
|
5126
5283
|
let systemPrompt = o.systemPrompt;
|
|
5127
5284
|
let tools = o.tools;
|
|
5285
|
+
this.ctx.model = o.model;
|
|
5286
|
+
const worker = o.delegate && delegateWorkerFor(o.model, o.delegate.worker);
|
|
5287
|
+
const advisor = !worker && o.advisor ? advisorFor(o.model, o.advisor.model) : void 0;
|
|
5288
|
+
this.meter = worker || advisor ? meterWorkers(o.ai, o.model) : void 0;
|
|
5289
|
+
const ai = this.meter?.ai ?? o.ai;
|
|
5290
|
+
const adv = advisor ? makeAdvisor({ ai, advisor, request: () => this.lastTask, snapshot: o.advisor.snapshot, limit: o.advisor.limit }) : void 0;
|
|
5128
5291
|
if (o.instructionFiles !== false) {
|
|
5129
5292
|
const names = Array.isArray(o.instructionFiles) ? o.instructionFiles : void 0;
|
|
5130
5293
|
const ins = await loadInstructions(fs, names);
|
|
@@ -5155,7 +5318,7 @@ var Agent = class _Agent {
|
|
|
5155
5318
|
if (tool) tools = [...tools, tool];
|
|
5156
5319
|
}
|
|
5157
5320
|
if (o.host && o.askUserQuestion) tools = [...tools, askUserQuestionTool];
|
|
5158
|
-
if (o.subagents) {
|
|
5321
|
+
if (o.subagents || worker) {
|
|
5159
5322
|
let agents;
|
|
5160
5323
|
if (o.agentsDir) {
|
|
5161
5324
|
const loaded = await loadAgents(fs, o.agentsDir);
|
|
@@ -5163,17 +5326,18 @@ var Agent = class _Agent {
|
|
|
5163
5326
|
if (loaded.catalog) systemPrompt += "\n\n" + loaded.catalog;
|
|
5164
5327
|
}
|
|
5165
5328
|
const taskOpts = {
|
|
5166
|
-
ai
|
|
5167
|
-
model: o.model,
|
|
5329
|
+
ai,
|
|
5330
|
+
model: worker || o.model,
|
|
5168
5331
|
fs,
|
|
5169
5332
|
depth: o.depth,
|
|
5170
|
-
maxDepth: o.maxDepth,
|
|
5333
|
+
maxDepth: worker ? Math.min(o.maxDepth, o.depth + 1) : o.maxDepth,
|
|
5171
5334
|
agents,
|
|
5335
|
+
inheritPostToolUse: !!worker,
|
|
5172
5336
|
hooks: o.hooks,
|
|
5173
5337
|
traceDir: o.traceDir,
|
|
5174
5338
|
permissions: o.permissions,
|
|
5175
5339
|
// a child obeys the parent's allow/deny rules (else Task is a one-call bypass)
|
|
5176
|
-
tools: [...o.tools, ...this.injectedTools],
|
|
5340
|
+
tools: worker ? delegateTools([...o.tools, ...this.injectedTools], o.model) : [...o.tools, ...this.injectedTools],
|
|
5177
5341
|
systemPrompt: o.systemPrompt,
|
|
5178
5342
|
// A child never resumes the parent's own claude-code session (the host's `resume` link is the parent's).
|
|
5179
5343
|
providerOptionsFor: o.providerOptionsFor ?? ((m) => modelProvider(m) === modelProvider(o.model) && o.providerOptions ? { ...o.providerOptions, resume: void 0, onClaudeCodeSession: void 0 } : void 0)
|
|
@@ -5186,6 +5350,15 @@ var Agent = class _Agent {
|
|
|
5186
5350
|
if (plan) tools = [...tools, plan.tool];
|
|
5187
5351
|
this.activeHooks = composeHooks(o.hooks, plan?.hooks, o.permissions?.hooks());
|
|
5188
5352
|
this.activeTools = [...tools, ...this.injectedTools];
|
|
5353
|
+
if (worker) {
|
|
5354
|
+
this.activeTools = delegateTools(this.activeTools, o.model);
|
|
5355
|
+
systemPrompt += delegatePrompt(worker);
|
|
5356
|
+
}
|
|
5357
|
+
if (adv) {
|
|
5358
|
+
this.activeTools = [...adv.gate(this.activeTools), adv.tool];
|
|
5359
|
+
systemPrompt += advisorPrompt(advisor);
|
|
5360
|
+
}
|
|
5361
|
+
this.modeModel = worker || advisor;
|
|
5189
5362
|
this.systemPromptCache = systemPrompt;
|
|
5190
5363
|
this.prepared = true;
|
|
5191
5364
|
return systemPrompt;
|
|
@@ -5198,6 +5371,7 @@ var Agent = class _Agent {
|
|
|
5198
5371
|
this.started = false;
|
|
5199
5372
|
this.invokedTools.clear();
|
|
5200
5373
|
this.fabricatedClaims.clear();
|
|
5374
|
+
this.lastTask = contentText(task);
|
|
5201
5375
|
const systemPrompt = await this.prepare(contentText(task));
|
|
5202
5376
|
const startCtx = await this.fireSessionStart();
|
|
5203
5377
|
const userContent = await this.applyPromptSubmit(task);
|
|
@@ -5233,6 +5407,7 @@ var Agent = class _Agent {
|
|
|
5233
5407
|
*/
|
|
5234
5408
|
async send(task) {
|
|
5235
5409
|
await this.ensureFs();
|
|
5410
|
+
this.lastTask = contentText(task);
|
|
5236
5411
|
const systemPrompt = await this.prepare(contentText(task));
|
|
5237
5412
|
const startCtx = await this.fireSessionStart();
|
|
5238
5413
|
const userContent = await this.applyPromptSubmit(task);
|
|
@@ -5283,6 +5458,12 @@ var Agent = class _Agent {
|
|
|
5283
5458
|
if (next !== this.transcript) this.transcript = next;
|
|
5284
5459
|
}
|
|
5285
5460
|
async runLoop() {
|
|
5461
|
+
this.meter?.reset();
|
|
5462
|
+
const res = await this.loop();
|
|
5463
|
+
const workerUsage = this.meter?.usage();
|
|
5464
|
+
return workerUsage ? { ...res, workerModel: this.meter.model() ?? this.modeModel, workerUsage } : res;
|
|
5465
|
+
}
|
|
5466
|
+
async loop() {
|
|
5286
5467
|
const o = this.options;
|
|
5287
5468
|
const wireTools = toWireTools(this.activeTools);
|
|
5288
5469
|
const useStream = o.stream === true && typeof o.host?.notify === "function";
|
|
@@ -5302,7 +5483,7 @@ var Agent = class _Agent {
|
|
|
5302
5483
|
let lastProgressAt = start;
|
|
5303
5484
|
const turnUser = this.transcript[this.transcript.length - 1];
|
|
5304
5485
|
const kill = (finishReason) => {
|
|
5305
|
-
|
|
5486
|
+
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` : ""})`);
|
|
5306
5487
|
this.jobs.killAll(`run ended: ${finishReason}`, true);
|
|
5307
5488
|
const claims = this.survivingClaims();
|
|
5308
5489
|
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 } : {} };
|
|
@@ -5328,9 +5509,9 @@ var Agent = class _Agent {
|
|
|
5328
5509
|
this.nativeHoldMs = o.nativeHoldMs;
|
|
5329
5510
|
if (isCursorWithTools) {
|
|
5330
5511
|
if (typeof hostToolTimeoutMs === "number" && effectiveToolTimeoutMs !== hostToolTimeoutMs) {
|
|
5331
|
-
|
|
5512
|
+
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.`);
|
|
5332
5513
|
}
|
|
5333
|
-
for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs))
|
|
5514
|
+
for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs)) log9.warn(v);
|
|
5334
5515
|
}
|
|
5335
5516
|
const hostExec = async (name, args) => {
|
|
5336
5517
|
const tc = { id: `host-${Date.now()}`, type: "function", function: { name, arguments: JSON.stringify(args) } };
|
|
@@ -5389,7 +5570,7 @@ var Agent = class _Agent {
|
|
|
5389
5570
|
} catch (err2) {
|
|
5390
5571
|
if (!this.imagesOff && /does not support .*input/i.test(String(err2?.message ?? "")) && sent.some((m) => Array.isArray(m.content) && m.role === "tool")) {
|
|
5391
5572
|
this.imagesOff = true;
|
|
5392
|
-
|
|
5573
|
+
log9.warn(`model refused image input (${err2?.message}) \u2014 resending with image Reads as text stubs`);
|
|
5393
5574
|
sent = this.trimContext();
|
|
5394
5575
|
attempt--;
|
|
5395
5576
|
continue;
|
|
@@ -5401,11 +5582,11 @@ var Agent = class _Agent {
|
|
|
5401
5582
|
const empty = err2?.code === "empty" || err2?.code === "cursor_silent";
|
|
5402
5583
|
const maxAttempts = empty ? Math.max(o.emptyRetries, 0) : 2;
|
|
5403
5584
|
const replaysTool = stalled && this.toolRanThisAttempt;
|
|
5404
|
-
if (replaysTool)
|
|
5585
|
+
if (replaysTool) log9.warn("model stall after tool(s) already ran this step \u2014 not retrying (a retry would replay them)");
|
|
5405
5586
|
const transient = !o.signal?.aborted && !isAbortError(err2) && !replaysTool && attempt < maxAttempts && (network || serverSide || stalled || empty);
|
|
5406
5587
|
if (!transient) throw err2;
|
|
5407
5588
|
const waitMs = (empty ? o.emptyRetryBaseMs : 1e3) * (attempt + 1);
|
|
5408
|
-
|
|
5589
|
+
log9.warn(`${empty ? "empty model turn" : stalled ? "model stall" : "network drop"} mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
|
|
5409
5590
|
o.host?.notify?.({ kind: "retry", message: `${empty ? "model returned nothing" : stalled ? "model stalled" : "connection dropped"} \u2014 retrying step (#${attempt + 1})` });
|
|
5410
5591
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
5411
5592
|
}
|
|
@@ -5413,12 +5594,12 @@ var Agent = class _Agent {
|
|
|
5413
5594
|
} catch (err2) {
|
|
5414
5595
|
if (err2?.code === "budget") return kill("budget");
|
|
5415
5596
|
if (err2?.code === "stall") {
|
|
5416
|
-
|
|
5597
|
+
log9.warn(`model stalled out after retries: ${err2?.message ?? err2}`);
|
|
5417
5598
|
return { ...kill("timeout"), providerFinishReason: "none (stalled)", error: err2 };
|
|
5418
5599
|
}
|
|
5419
5600
|
if (o.signal?.aborted || isAbortError(err2)) return kill("aborted");
|
|
5420
5601
|
if (err2?.code === "empty" || err2?.code === "cursor_silent") {
|
|
5421
|
-
|
|
5602
|
+
log9.warn(`model returned empty/silent turns after retries \u2014 surfacing as failed`);
|
|
5422
5603
|
const pfr = err2?.providerFinishReason;
|
|
5423
5604
|
return { text: "", steps, finishReason: "empty", ...pfr ? { providerFinishReason: pfr } : {}, messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
5424
5605
|
}
|
|
@@ -5431,13 +5612,13 @@ var Agent = class _Agent {
|
|
|
5431
5612
|
}
|
|
5432
5613
|
if (bodyStr && err2 instanceof Error && !err2.message.includes(bodyStr)) err2.detail = bodyStr;
|
|
5433
5614
|
const errLog = err2 instanceof Error ? { message: err2.message, ...err2.detail ? { detail: err2.detail } : {}, stack: err2.stack } : err2;
|
|
5434
|
-
|
|
5615
|
+
log9.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, errLog);
|
|
5435
5616
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
5436
5617
|
}
|
|
5437
5618
|
if (o.signal?.aborted) return kill("aborted");
|
|
5438
5619
|
if (res.finishReason === "error" && !res.toolCalls?.length) {
|
|
5439
5620
|
const err2 = new Error(contentText(res.content ?? "").trim() || "provider error");
|
|
5440
|
-
|
|
5621
|
+
log9.error(`chat() failed: ${err2.message}`);
|
|
5441
5622
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
5442
5623
|
}
|
|
5443
5624
|
if (!res.usage) {
|
|
@@ -5505,21 +5686,21 @@ var Agent = class _Agent {
|
|
|
5505
5686
|
if (emptyTurn && o.failEmptyTurns) {
|
|
5506
5687
|
if (!emptyNudged && canNudge()) {
|
|
5507
5688
|
emptyNudged = true;
|
|
5508
|
-
|
|
5689
|
+
log9.warn(`empty model turn after tool work (step ${steps}, provider finishReason=${res.finishReason ?? "n/a"}) \u2014 nudging once`);
|
|
5509
5690
|
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." });
|
|
5510
5691
|
continue;
|
|
5511
5692
|
}
|
|
5512
|
-
|
|
5693
|
+
log9.warn(`model returned empty turns after tool work \u2014 surfacing as failed`);
|
|
5513
5694
|
const e = new Error("empty_response: model returned an empty turn (no text, no tools) after a re-prompt");
|
|
5514
5695
|
e.code = "empty";
|
|
5515
5696
|
return { text: "", steps, finishReason: "empty", ...res.finishReason ? { providerFinishReason: res.finishReason } : {}, messages: this.transcript, usage, usageEstimated, error: e };
|
|
5516
5697
|
}
|
|
5517
5698
|
const fabricated = o.catchFabricatedToolUse && !closureNudged ? this.fabricatedToolNames(contentText(res.content ?? "")) : [];
|
|
5518
5699
|
for (const n of fabricated) this.fabricatedClaims.add(n);
|
|
5519
|
-
if (fabricated.length && !canNudge())
|
|
5700
|
+
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`);
|
|
5520
5701
|
if (fabricated.length && canNudge()) {
|
|
5521
5702
|
closureNudged = true;
|
|
5522
|
-
|
|
5703
|
+
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`);
|
|
5523
5704
|
this.transcript.push({
|
|
5524
5705
|
role: "user",
|
|
5525
5706
|
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.`
|
|
@@ -5528,14 +5709,14 @@ var Agent = class _Agent {
|
|
|
5528
5709
|
}
|
|
5529
5710
|
if (o.closeDelegatedTurns && res.endedWithoutClosing && !closureNudged && canNudge()) {
|
|
5530
5711
|
closureNudged = true;
|
|
5531
|
-
|
|
5712
|
+
log9.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
|
|
5532
5713
|
this.transcript.push({
|
|
5533
5714
|
role: "user",
|
|
5534
5715
|
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."
|
|
5535
5716
|
});
|
|
5536
5717
|
continue;
|
|
5537
5718
|
}
|
|
5538
|
-
|
|
5719
|
+
log9.verbose(`completed in ${steps} step(s)`);
|
|
5539
5720
|
const drained = await this.jobs.drain(o.signal);
|
|
5540
5721
|
if (o.signal?.aborted) return kill("aborted");
|
|
5541
5722
|
if (drained.length) lastProgressAt = Date.now();
|
|
@@ -5569,7 +5750,7 @@ var Agent = class _Agent {
|
|
|
5569
5750
|
if (this.ctx.needsInput) {
|
|
5570
5751
|
const question = this.ctx.needsInput;
|
|
5571
5752
|
this.ctx.needsInput = void 0;
|
|
5572
|
-
|
|
5753
|
+
log9.warn(`needs_input: ${question.question} (steps=${steps})`);
|
|
5573
5754
|
await this.jobs.drain();
|
|
5574
5755
|
const text = [
|
|
5575
5756
|
`OPEN QUESTION (run paused \u2014 no human was reachable): ${question.question}`,
|
|
@@ -5580,7 +5761,7 @@ var Agent = class _Agent {
|
|
|
5580
5761
|
if (this.ctx.endTurn) {
|
|
5581
5762
|
this.ctx.endTurn = void 0;
|
|
5582
5763
|
if (this.drainInjections()) continue;
|
|
5583
|
-
|
|
5764
|
+
log9.verbose(`turn ended by a tool (steps=${steps})`);
|
|
5584
5765
|
await this.jobs.drain(o.signal);
|
|
5585
5766
|
return { text: "", steps, finishReason: "stop", messages: this.transcript, usage, usageEstimated };
|
|
5586
5767
|
}
|
|
@@ -5618,12 +5799,12 @@ var Agent = class _Agent {
|
|
|
5618
5799
|
if (this.toolHoldActive()) {
|
|
5619
5800
|
const now4 = Date.now();
|
|
5620
5801
|
const who = this.jobs.holding().map((j) => `${j.kind} "${j.label}" (${Math.round((now4 - j.startedAt) / 1e3)}s, quiet ${Math.round((now4 - j.lastActivityAt) / 1e3)}s)`).join(", ");
|
|
5621
|
-
|
|
5802
|
+
log9.warn(`stall watchdog: no stream output for ${stallMs}ms \u2014 held by ${who}`);
|
|
5622
5803
|
this.options.host?.notify?.({ kind: "tool_hold", message: `waiting on ${who}` });
|
|
5623
5804
|
return poke();
|
|
5624
5805
|
}
|
|
5625
5806
|
const inFlight = this.jobs.list().filter((j) => j.status === "running");
|
|
5626
|
-
|
|
5807
|
+
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(", ")})` : ""));
|
|
5627
5808
|
fired = true;
|
|
5628
5809
|
ac.abort(stallError());
|
|
5629
5810
|
}, stallMs);
|
|
@@ -5793,13 +5974,13 @@ var Agent = class _Agent {
|
|
|
5793
5974
|
const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
|
|
5794
5975
|
if (decision?.block) {
|
|
5795
5976
|
const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
|
|
5796
|
-
|
|
5977
|
+
log9.debug(`${tc.function.name} -> ${blocked}`);
|
|
5797
5978
|
await hooks?.postToolUse?.(call, blocked, meta);
|
|
5798
5979
|
return blocked;
|
|
5799
5980
|
}
|
|
5800
5981
|
this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
|
|
5801
5982
|
if (earlyError) {
|
|
5802
|
-
|
|
5983
|
+
log9.debug(`${tc.function.name} -> ${earlyError}`);
|
|
5803
5984
|
await hooks?.postToolUse?.(call, earlyError, meta);
|
|
5804
5985
|
this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
|
|
5805
5986
|
return earlyError;
|
|
@@ -5808,12 +5989,12 @@ var Agent = class _Agent {
|
|
|
5808
5989
|
let images;
|
|
5809
5990
|
let threw = false;
|
|
5810
5991
|
try {
|
|
5811
|
-
|
|
5992
|
+
log9.debug(`${tc.function.name}(${tc.function.arguments})`);
|
|
5812
5993
|
this.ctx.emit = hooks?.onToolOutput ? (chunk) => {
|
|
5813
5994
|
try {
|
|
5814
5995
|
hooks.onToolOutput(call, chunk, meta);
|
|
5815
5996
|
} catch (e) {
|
|
5816
|
-
|
|
5997
|
+
log9.debug(`onToolOutput hook error: ${e}`);
|
|
5817
5998
|
}
|
|
5818
5999
|
} : void 0;
|
|
5819
6000
|
const raw = await tool.run(args, this.ctx);
|
|
@@ -5825,7 +6006,7 @@ var Agent = class _Agent {
|
|
|
5825
6006
|
}
|
|
5826
6007
|
} catch (e) {
|
|
5827
6008
|
const msg = e instanceof Error ? e.message : String(e);
|
|
5828
|
-
|
|
6009
|
+
log9.debug(`${tc.function.name} -> error: ${msg}`);
|
|
5829
6010
|
result = `Error: ${msg}`;
|
|
5830
6011
|
threw = true;
|
|
5831
6012
|
} finally {
|
|
@@ -6102,7 +6283,7 @@ function fitTokenBudget(messages, maxTokens, keepRecentImages = 1, vision = true
|
|
|
6102
6283
|
const ids = callIdSet(messages.slice(from));
|
|
6103
6284
|
while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
|
|
6104
6285
|
if (total > maxTokens)
|
|
6105
|
-
|
|
6286
|
+
log9.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
|
|
6106
6287
|
return [...head, ...messages.slice(from)];
|
|
6107
6288
|
}
|
|
6108
6289
|
function compact(m, max, focus) {
|
|
@@ -6682,7 +6863,7 @@ init_tools_web();
|
|
|
6682
6863
|
init_tools();
|
|
6683
6864
|
init_tools_structured();
|
|
6684
6865
|
init_logging();
|
|
6685
|
-
var
|
|
6866
|
+
var log10 = forComponent("scratch");
|
|
6686
6867
|
var SCRATCH_DIR = "/scratch";
|
|
6687
6868
|
function shortArgs(args) {
|
|
6688
6869
|
try {
|
|
@@ -6722,7 +6903,7 @@ var Scratch = class {
|
|
|
6722
6903
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
6723
6904
|
await this.fs.writeFile(path, header + raw);
|
|
6724
6905
|
} catch (e) {
|
|
6725
|
-
|
|
6906
|
+
log10.debug("scratch write failed; returning raw", e);
|
|
6726
6907
|
return raw;
|
|
6727
6908
|
}
|
|
6728
6909
|
const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
|
|
@@ -6752,7 +6933,7 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
|
|
|
6752
6933
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
6753
6934
|
await this.fs.writeFile(path, header + full);
|
|
6754
6935
|
} catch (e) {
|
|
6755
|
-
|
|
6936
|
+
log10.debug("scratch spill failed; cropping lossy", e);
|
|
6756
6937
|
return full.slice(0, pageBytes) + `
|
|
6757
6938
|
|
|
6758
6939
|
[output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
|
|
@@ -6798,7 +6979,7 @@ Question: ${q2}`);
|
|
|
6798
6979
|
const answer = (res.text ?? "").trim();
|
|
6799
6980
|
return answer || "(no answer found in scratch)";
|
|
6800
6981
|
} catch (e) {
|
|
6801
|
-
|
|
6982
|
+
log10.debug("Ask peek failed", e);
|
|
6802
6983
|
return `Error querying scratch: ${e?.message ?? e}`;
|
|
6803
6984
|
}
|
|
6804
6985
|
}
|
|
@@ -6807,7 +6988,7 @@ Question: ${q2}`);
|
|
|
6807
6988
|
|
|
6808
6989
|
// src/lessons.ts
|
|
6809
6990
|
init_logging();
|
|
6810
|
-
var
|
|
6991
|
+
var log11 = forComponent("Lessons");
|
|
6811
6992
|
var LessonOptionsDefaults = class {
|
|
6812
6993
|
minRepeats = 2;
|
|
6813
6994
|
};
|
|
@@ -6832,8 +7013,8 @@ function lessonCapture(options) {
|
|
|
6832
7013
|
counts.set(lesson.slug, n);
|
|
6833
7014
|
if (n < o.minRepeats) return;
|
|
6834
7015
|
written.add(lesson.slug);
|
|
6835
|
-
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) =>
|
|
6836
|
-
|
|
7016
|
+
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log11.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
|
|
7017
|
+
log11.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
|
|
6837
7018
|
}
|
|
6838
7019
|
};
|
|
6839
7020
|
}
|
|
@@ -6841,7 +7022,7 @@ function lessonCapture(options) {
|
|
|
6841
7022
|
// src/reflect.ts
|
|
6842
7023
|
init_llm();
|
|
6843
7024
|
init_logging();
|
|
6844
|
-
var
|
|
7025
|
+
var log12 = forComponent("Reflect");
|
|
6845
7026
|
async function reflectOnRun(o) {
|
|
6846
7027
|
const digest = digestRun(o.result.messages, o.maxDigestChars ?? 6e3);
|
|
6847
7028
|
if (!digest.trim()) return null;
|
|
@@ -6857,7 +7038,7 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
6857
7038
|
const r = await o.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false });
|
|
6858
7039
|
text = r?.content ?? "";
|
|
6859
7040
|
} catch (e) {
|
|
6860
|
-
|
|
7041
|
+
log12.warn(`reflection call failed: ${e?.message ?? e}`);
|
|
6861
7042
|
return null;
|
|
6862
7043
|
}
|
|
6863
7044
|
const m = text.match(/LESSON:\s*(.+)/i);
|
|
@@ -6867,10 +7048,10 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
6867
7048
|
try {
|
|
6868
7049
|
await writeFact(o.fs, o.dir, slug2, lesson);
|
|
6869
7050
|
} catch (e) {
|
|
6870
|
-
|
|
7051
|
+
log12.warn(`could not persist lesson: ${e?.message ?? e}`);
|
|
6871
7052
|
return null;
|
|
6872
7053
|
}
|
|
6873
|
-
|
|
7054
|
+
log12.debug(`reflection persisted ${slug2}`);
|
|
6874
7055
|
return slug2;
|
|
6875
7056
|
}
|
|
6876
7057
|
function digestRun(messages, maxChars) {
|
|
@@ -6920,7 +7101,7 @@ function forComponent2(name) {
|
|
|
6920
7101
|
}
|
|
6921
7102
|
|
|
6922
7103
|
// node_modules/@bod.ee/voice/src/core/emotion.ts
|
|
6923
|
-
var
|
|
7104
|
+
var log13 = forComponent2("Emotion");
|
|
6924
7105
|
var EMOTIONS = [
|
|
6925
7106
|
// primary (best results)
|
|
6926
7107
|
"neutral",
|
|
@@ -7052,7 +7233,7 @@ function renderEmotions(text, opts = { show: true }) {
|
|
|
7052
7233
|
speech += cartesiaTag(tag);
|
|
7053
7234
|
if (opts.show) display += m[0];
|
|
7054
7235
|
} else {
|
|
7055
|
-
|
|
7236
|
+
log13.debug(`dropping unknown emotion tag ${m[0]}`);
|
|
7056
7237
|
}
|
|
7057
7238
|
last = m.index + m[0].length;
|
|
7058
7239
|
}
|
|
@@ -7094,7 +7275,7 @@ var EmotionStream = class {
|
|
|
7094
7275
|
if (tag) {
|
|
7095
7276
|
this.pending = tag;
|
|
7096
7277
|
if (this.show) display += m[0];
|
|
7097
|
-
} else
|
|
7278
|
+
} else log13.debug(`dropping unknown emotion tag ${m[0]}`);
|
|
7098
7279
|
last = m.index + m[0].length;
|
|
7099
7280
|
}
|
|
7100
7281
|
this.emit(body.slice(last), (s, d, p) => {
|
|
@@ -7241,7 +7422,7 @@ function computeHoldMs(ctx) {
|
|
|
7241
7422
|
}
|
|
7242
7423
|
|
|
7243
7424
|
// node_modules/@bod.ee/voice/src/core/engine.ts
|
|
7244
|
-
var
|
|
7425
|
+
var log14 = forComponent2("VoiceEngine");
|
|
7245
7426
|
var realClock = {
|
|
7246
7427
|
now: () => performance.now(),
|
|
7247
7428
|
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
@@ -7629,8 +7810,8 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7629
7810
|
this.tts.warmup?.();
|
|
7630
7811
|
this.setState("listening");
|
|
7631
7812
|
if (this.options.bargeInMode === "aec" && !this.stt.usingAec)
|
|
7632
|
-
|
|
7633
|
-
|
|
7813
|
+
log14.warn("bargeInMode=aec but mic does not report echoCancellation applied \u2014 STT may transcribe the assistant");
|
|
7814
|
+
log14.debug(`voice I/O up (${this.usingAec ? "AEC" : "heuristic echo"} capture, mode=${this.options.bargeInMode}, mic reports aec=${this.stt.usingAec})`);
|
|
7634
7815
|
}
|
|
7635
7816
|
/** The RESOLVED barge-in tier (the single seam every tier branch reads). `bargeInMode` overrides
|
|
7636
7817
|
* the mic's self-report — see the option doc for why the report is not trustworthy on mobile. */
|
|
@@ -7655,7 +7836,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7655
7836
|
this.options.onDiag({ t: this.clock.now(), kind, ...fields });
|
|
7656
7837
|
} catch (e) {
|
|
7657
7838
|
this.diagOn = false;
|
|
7658
|
-
|
|
7839
|
+
log14.debug(`onDiag threw \u2014 diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
7659
7840
|
}
|
|
7660
7841
|
}
|
|
7661
7842
|
idleWaiters = [];
|
|
@@ -7794,7 +7975,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7794
7975
|
}
|
|
7795
7976
|
for (const w of this.words(this.reply)) this.echoWords.add(w);
|
|
7796
7977
|
this.tts.speak(forSpeech(speech), true);
|
|
7797
|
-
if (!this.spokeDeltas && this.turnStartAt)
|
|
7978
|
+
if (!this.spokeDeltas && this.turnStartAt) log14.debug(`ttft: ${Math.round(this.clock.now() - this.turnStartAt)}ms`);
|
|
7798
7979
|
this.spokeDeltas = true;
|
|
7799
7980
|
this.setState("speaking");
|
|
7800
7981
|
if (this.options.revealMode !== "off" && display) {
|
|
@@ -7907,13 +8088,13 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7907
8088
|
this.drainTimer = null;
|
|
7908
8089
|
this.speaking = false;
|
|
7909
8090
|
this.stopWordReveal(true);
|
|
7910
|
-
if (this.turnStartAt)
|
|
8091
|
+
if (this.turnStartAt) log14.debug(`turn: ${Math.round(this.clock.now() - this.turnStartAt)}ms (incl. playback)`);
|
|
7911
8092
|
this.echoUntil = this.clock.now() + 2500;
|
|
7912
8093
|
if (!this.usingAec) this.stt.reset();
|
|
7913
8094
|
try {
|
|
7914
8095
|
this.options.onSpeechEnd();
|
|
7915
8096
|
} catch (e) {
|
|
7916
|
-
|
|
8097
|
+
log14.warn(`onSpeechEnd threw: ${e instanceof Error ? e.message : e}`);
|
|
7917
8098
|
}
|
|
7918
8099
|
this.setState("listening");
|
|
7919
8100
|
if (this.uttQueue.length) this.pumpQueue();
|
|
@@ -7987,7 +8168,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7987
8168
|
if (!host) return pick;
|
|
7988
8169
|
const ctx = { text: this.lastDispatchText, question: this.lastDispatchWasQuestion, barged: this.bargedBeforeDispatch };
|
|
7989
8170
|
const fallback = (e) => {
|
|
7990
|
-
|
|
8171
|
+
log14.warn(`pickAck failed \u2014 using the default pools: ${e instanceof Error ? e.message : e}`);
|
|
7991
8172
|
pick.picked = false;
|
|
7992
8173
|
return void 0;
|
|
7993
8174
|
};
|
|
@@ -8077,7 +8258,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8077
8258
|
this.ackProbe = null;
|
|
8078
8259
|
const queued = this.uttQueue.splice(0);
|
|
8079
8260
|
const droppedQueued = queued.length;
|
|
8080
|
-
if (droppedQueued)
|
|
8261
|
+
if (droppedQueued) log14.info(`barge-in cut ${droppedQueued} queued worker utterance(s) \u2014 handed to the host`);
|
|
8081
8262
|
this.overlapKeptUntil = 0;
|
|
8082
8263
|
if (!this.speaking && !this.drainTimer) {
|
|
8083
8264
|
if (droppedQueued) this.lastInterrupted = { full: "", heard: "", queued, queuedTurn: false };
|
|
@@ -8306,7 +8487,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8306
8487
|
if (flat && this.lastDispatchFlat && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS && (this.lastDispatchFlat.startsWith(flat) || flat.startsWith(this.lastDispatchFlat))) return;
|
|
8307
8488
|
this.specSpent = true;
|
|
8308
8489
|
this.specText = this.specPartial;
|
|
8309
|
-
|
|
8490
|
+
log14.debug(`speculate: "${this.specText.slice(0, 60)}"`);
|
|
8310
8491
|
this.diag("speculate", { text: this.specText });
|
|
8311
8492
|
this.options.onSpeculate(this.specText);
|
|
8312
8493
|
}, this.options.speculativeMs);
|
|
@@ -8375,7 +8556,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8375
8556
|
};
|
|
8376
8557
|
this.tts.newContext();
|
|
8377
8558
|
this.tts.speak(phrase, false);
|
|
8378
|
-
|
|
8559
|
+
log14.debug(`backchannel: "${phrase}"`);
|
|
8379
8560
|
this.diag("backchannel", { phrase });
|
|
8380
8561
|
this.options.onBackchannel(phrase);
|
|
8381
8562
|
}
|
|
@@ -8408,7 +8589,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8408
8589
|
});
|
|
8409
8590
|
if (idx < 0) return text;
|
|
8410
8591
|
toks.splice(idx, 1);
|
|
8411
|
-
|
|
8592
|
+
log14.verbose(`stripped backchannel echo "${this.lastBcPhrase}" from dispatching final`);
|
|
8412
8593
|
return toks.join(" ");
|
|
8413
8594
|
}
|
|
8414
8595
|
/** Merge a resumed utterance into the pending one, deduping any word-overlap. Soniox re-finalizes
|
|
@@ -8594,7 +8775,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8594
8775
|
}
|
|
8595
8776
|
const bargeHold = !this.looksIncomplete(this.pendingUtt) && this.unfinishedBargeLine(this.pendingUtt);
|
|
8596
8777
|
if (this.options.incompleteMergeMs && (bargeHold || this.looksIncomplete(this.pendingUtt))) {
|
|
8597
|
-
|
|
8778
|
+
log14.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
|
|
8598
8779
|
this.diag("hold", { reason: bargeHold ? "barge_unfinished" : "incomplete", tail: this.pendingUtt.slice(-40) });
|
|
8599
8780
|
this.options.onHold();
|
|
8600
8781
|
if (this.options.holdFiller && !this.speaking) this.speakFiller(this.options.holdFiller);
|
|
@@ -8633,13 +8814,13 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8633
8814
|
const answered = this.answeredSinceDispatch();
|
|
8634
8815
|
const part = flat !== this.lastDispatchFlat && !answered && this.echoesDispatch(text);
|
|
8635
8816
|
if (part || flat && flat === this.lastDispatchFlat && !answered && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS) {
|
|
8636
|
-
|
|
8817
|
+
log14.verbose(`dropped duplicate final "${text.slice(0, 40)}" (identical to the just-dispatched utterance, no reply between)`);
|
|
8637
8818
|
this.diag("utterance_drop", { reason: "dup_final", text: text.slice(0, 60) });
|
|
8638
8819
|
return;
|
|
8639
8820
|
}
|
|
8640
8821
|
const refinal = this.refinalizes(text);
|
|
8641
8822
|
if (refinal && this.repliedSinceDispatch) {
|
|
8642
|
-
|
|
8823
|
+
log14.verbose(`dropped re-finalization "${text.slice(0, 60)}" (superset of the just-dispatched utterance, reply already flowing)`);
|
|
8643
8824
|
this.diag("utterance_drop", { reason: "refinalized", text: text.slice(0, 60) });
|
|
8644
8825
|
this.lastDispatchFlat = flat;
|
|
8645
8826
|
this.lastDispatchWords = text.trim().split(/\s+/);
|
|
@@ -8918,7 +9099,7 @@ var SpokenSplitter = class {
|
|
|
8918
9099
|
};
|
|
8919
9100
|
|
|
8920
9101
|
// node_modules/@bod.ee/voice/src/adapters/soniox.ts
|
|
8921
|
-
var
|
|
9102
|
+
var log15 = forComponent2("SonioxSTT");
|
|
8922
9103
|
var now = () => performance.now();
|
|
8923
9104
|
var SonioxSTTOptions = class {
|
|
8924
9105
|
auth = "";
|
|
@@ -8963,7 +9144,7 @@ var SonioxSTT = class {
|
|
|
8963
9144
|
this.onDiag({ t: now(), kind, ...fields });
|
|
8964
9145
|
} catch (e) {
|
|
8965
9146
|
this.diagOn = false;
|
|
8966
|
-
|
|
9147
|
+
log15.debug(`onDiag threw \u2014 STT diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
8967
9148
|
}
|
|
8968
9149
|
}
|
|
8969
9150
|
lastChunkAt = 0;
|
|
@@ -9005,11 +9186,11 @@ var SonioxSTT = class {
|
|
|
9005
9186
|
this.ws.onmessage = (ev) => this.handle(JSON.parse(String(ev.data)));
|
|
9006
9187
|
this.ws.onclose = (ev) => {
|
|
9007
9188
|
if (this.stopped) return;
|
|
9008
|
-
|
|
9189
|
+
log15.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
|
|
9009
9190
|
this.diag("stt_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: true });
|
|
9010
9191
|
this.reset();
|
|
9011
9192
|
this.connectWs().catch((e) => {
|
|
9012
|
-
|
|
9193
|
+
log15.error(`soniox reconnect failed: ${e.message}`);
|
|
9013
9194
|
this.diag("stt_reconnect_failed", { message: e.message });
|
|
9014
9195
|
});
|
|
9015
9196
|
};
|
|
@@ -9021,7 +9202,7 @@ var SonioxSTT = class {
|
|
|
9021
9202
|
this.endpointTimer = setInterval(() => {
|
|
9022
9203
|
const combined = (this.finalText + this.partialText).trim();
|
|
9023
9204
|
if (!combined || now() - this.lastChangeAt < this.options.silenceEndpointMs) return;
|
|
9024
|
-
if (this.firstTokenAt)
|
|
9205
|
+
if (this.firstTokenAt) log15.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
|
|
9025
9206
|
this.reset();
|
|
9026
9207
|
this.onUtterance(combined, now());
|
|
9027
9208
|
}, 120);
|
|
@@ -9033,7 +9214,7 @@ var SonioxSTT = class {
|
|
|
9033
9214
|
if (this.stopped) return;
|
|
9034
9215
|
const ref = this.lastChunkAt || this.startedChunksAt;
|
|
9035
9216
|
if (now() - ref > noAudioMs) {
|
|
9036
|
-
|
|
9217
|
+
log15.error(`stt: no mic audio for >${Math.round(noAudioMs / 1e3)}s \u2014 capture device stopped delivering`);
|
|
9037
9218
|
this.diag("stt_watchdog_fatal", { noAudioMs });
|
|
9038
9219
|
this.onFatal("microphone stopped delivering audio (try a different input device, e.g. AirPods, or check System Settings \u2192 Sound \u2192 Input)");
|
|
9039
9220
|
this.stop();
|
|
@@ -9056,7 +9237,7 @@ var SonioxSTT = class {
|
|
|
9056
9237
|
handle(m) {
|
|
9057
9238
|
if (m.error_message) {
|
|
9058
9239
|
this.diag("stt_error", { message: String(m.error_message), code: m.error_code });
|
|
9059
|
-
return
|
|
9240
|
+
return log15.error(`soniox: ${m.error_message}`);
|
|
9060
9241
|
}
|
|
9061
9242
|
let endpoint = false;
|
|
9062
9243
|
for (const t of m.tokens ?? []) {
|
|
@@ -9073,7 +9254,7 @@ var SonioxSTT = class {
|
|
|
9073
9254
|
this.onPartial(combined);
|
|
9074
9255
|
if (endpoint && this.finalText.trim()) {
|
|
9075
9256
|
const utterance = this.finalText.trim();
|
|
9076
|
-
if (this.firstTokenAt)
|
|
9257
|
+
if (this.firstTokenAt) log15.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
|
|
9077
9258
|
this.reset();
|
|
9078
9259
|
this.onUtterance(utterance, now());
|
|
9079
9260
|
}
|
|
@@ -9095,7 +9276,7 @@ var SonioxSTT = class {
|
|
|
9095
9276
|
};
|
|
9096
9277
|
|
|
9097
9278
|
// node_modules/@bod.ee/voice/src/adapters/cartesia.ts
|
|
9098
|
-
var
|
|
9279
|
+
var log16 = forComponent2("CartesiaTTS");
|
|
9099
9280
|
var now2 = () => performance.now();
|
|
9100
9281
|
var CartesiaTTSOptions = class {
|
|
9101
9282
|
auth = "";
|
|
@@ -9131,7 +9312,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
9131
9312
|
this.onDiag({ t: now2(), kind, ...fields });
|
|
9132
9313
|
} catch (e) {
|
|
9133
9314
|
this.diagOn = false;
|
|
9134
|
-
|
|
9315
|
+
log16.debug(`onDiag threw \u2014 TTS diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
9135
9316
|
}
|
|
9136
9317
|
}
|
|
9137
9318
|
firstAudioAt = 0;
|
|
@@ -9166,11 +9347,11 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
9166
9347
|
this.ws.onerror = (e) => rej(new Error(`cartesia ws: ${e.message || "connect failed"}`));
|
|
9167
9348
|
});
|
|
9168
9349
|
this.ws.onclose = (ev) => {
|
|
9169
|
-
|
|
9350
|
+
log16.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
|
|
9170
9351
|
this.diag("tts_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: !this.closed });
|
|
9171
9352
|
if (!this.closed) {
|
|
9172
9353
|
this.connecting = this.doConnect().catch((e) => {
|
|
9173
|
-
|
|
9354
|
+
log16.error(`cartesia reconnect failed: ${e.message}`);
|
|
9174
9355
|
this.diag("tts_reconnect_failed", { message: e.message });
|
|
9175
9356
|
});
|
|
9176
9357
|
}
|
|
@@ -9198,12 +9379,12 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
9198
9379
|
this.down = true;
|
|
9199
9380
|
this.downAt = now2();
|
|
9200
9381
|
this.consecutiveOk = 0;
|
|
9201
|
-
|
|
9382
|
+
log16.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
|
|
9202
9383
|
this.diag("tts_breaker_open", { errors: this.consecutiveErrors });
|
|
9203
9384
|
this.onDone();
|
|
9204
9385
|
this.startProbe();
|
|
9205
9386
|
} else if (!this.down) {
|
|
9206
|
-
(/No valid transcripts/i.test(m.message || "") ?
|
|
9387
|
+
(/No valid transcripts/i.test(m.message || "") ? log16.debug : log16.warn)(`cartesia: ${JSON.stringify(m)}`);
|
|
9207
9388
|
}
|
|
9208
9389
|
}
|
|
9209
9390
|
};
|
|
@@ -9218,7 +9399,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
9218
9399
|
this.stopProbe();
|
|
9219
9400
|
const downMs = this.downAt ? now2() - this.downAt : 0;
|
|
9220
9401
|
this.diag("tts_breaker_close", { downMs: Math.round(downMs) });
|
|
9221
|
-
(downMs < 2e3 ?
|
|
9402
|
+
(downMs < 2e3 ? log16.debug : log16.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
|
|
9222
9403
|
}
|
|
9223
9404
|
/** Ensure the WS is open before sending — reconnects if idle-closed. */
|
|
9224
9405
|
async ensureConnected() {
|
|
@@ -9301,13 +9482,13 @@ function base64ToBytes(b64) {
|
|
|
9301
9482
|
}
|
|
9302
9483
|
|
|
9303
9484
|
// node_modules/@bod.ee/voice/src/adapters/fish.ts
|
|
9304
|
-
var
|
|
9485
|
+
var log17 = forComponent2("FishTTS");
|
|
9305
9486
|
|
|
9306
9487
|
// node_modules/@bod.ee/voice/src/adapters/grok.ts
|
|
9307
|
-
var
|
|
9488
|
+
var log18 = forComponent2("GrokTTS");
|
|
9308
9489
|
|
|
9309
9490
|
// node_modules/@bod.ee/voice/src/adapters/grok-stt.ts
|
|
9310
|
-
var
|
|
9491
|
+
var log19 = forComponent2("GrokSTT");
|
|
9311
9492
|
|
|
9312
9493
|
// src/voice/engine.ts
|
|
9313
9494
|
init_logging();
|
|
@@ -9323,7 +9504,7 @@ import { AIClient, DecideClient, JevAdapter, LlmDecideAdapter } from "ai.libx.js
|
|
|
9323
9504
|
|
|
9324
9505
|
// src/voice/overlap.ts
|
|
9325
9506
|
init_logging();
|
|
9326
|
-
var
|
|
9507
|
+
var log20 = forComponent("Overlap");
|
|
9327
9508
|
var norm2 = (s) => s.toLowerCase().replace(/[’']/g, "'").replace(/[^\p{L}\p{N}' -]+/gu, " ").replace(/\s+/g, " ").trim();
|
|
9328
9509
|
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/;
|
|
9329
9510
|
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(" "));
|
|
@@ -9403,8 +9584,8 @@ var OverlapJudge = class {
|
|
|
9403
9584
|
const t0 = this.o.now();
|
|
9404
9585
|
const done = (verdict, by) => {
|
|
9405
9586
|
const d = { verdict, by, ms: Math.round(this.o.now() - t0) };
|
|
9406
|
-
if (by === "late" || by === "error" || by === "none")
|
|
9407
|
-
else
|
|
9587
|
+
if (by === "late" || by === "error" || by === "none") log20.warn(`overlap "${heard.slice(0, 60)}": no verdict (${by}, ${d.ms}ms) \u2192 cut`);
|
|
9588
|
+
else log20.debug(`overlap "${heard.slice(0, 60)}": ${verdict} by ${by} ${d.ms}ms`);
|
|
9408
9589
|
this.o.onDecision(heard, d);
|
|
9409
9590
|
return d;
|
|
9410
9591
|
};
|
|
@@ -9421,7 +9602,7 @@ var OverlapJudge = class {
|
|
|
9421
9602
|
});
|
|
9422
9603
|
try {
|
|
9423
9604
|
const v = await Promise.race([this.o.model(heard, ctl.signal).catch((e) => {
|
|
9424
|
-
if (!ctl.signal.aborted)
|
|
9605
|
+
if (!ctl.signal.aborted) log20.warn(`overlap model failed: ${e.message}`);
|
|
9425
9606
|
return "error";
|
|
9426
9607
|
}), late]);
|
|
9427
9608
|
if (v === "late" || v === "error") return done("cut", v);
|
|
@@ -9437,15 +9618,15 @@ function overlapModelFor(ai, env = process.env) {
|
|
|
9437
9618
|
const m = fastOverlapModel(ai, model);
|
|
9438
9619
|
const t0 = performance.now();
|
|
9439
9620
|
void m("yeah", AbortSignal.timeout(1e4)).then(
|
|
9440
|
-
() =>
|
|
9441
|
-
(e) =>
|
|
9621
|
+
() => log20.debug(`fast model warm in ${Math.round(performance.now() - t0)}ms`),
|
|
9622
|
+
(e) => log20.warn(`fast model warm-up failed (the first overlap verdict may be late): ${e?.message ?? e}`)
|
|
9442
9623
|
);
|
|
9443
9624
|
return m;
|
|
9444
9625
|
}
|
|
9445
9626
|
var softBarge = (mode) => mode !== "turn";
|
|
9446
9627
|
|
|
9447
9628
|
// src/voice/instinct.ts
|
|
9448
|
-
var
|
|
9629
|
+
var log21 = forComponent("Instinct");
|
|
9449
9630
|
function voiceDeciderOptions(env = process.env) {
|
|
9450
9631
|
if (env.TYPESAFE_AI_KEY) return { apiKey: env.TYPESAFE_AI_KEY };
|
|
9451
9632
|
if (env.GROQ_API_KEY) return { provider: "llm", model: "groq/openai/gpt-oss-120b", apiKey: env.GROQ_API_KEY };
|
|
@@ -9716,13 +9897,13 @@ var Instinct = class {
|
|
|
9716
9897
|
* unfinished partial is no turn to react to — the interjection itself is the reply. */
|
|
9717
9898
|
pickAck(ctx) {
|
|
9718
9899
|
if (this.interjecting) {
|
|
9719
|
-
|
|
9900
|
+
log21.debug(`ack: cut-in turn \u2192 silent (engine context "${ctx.text.slice(0, 40)}" is the previous final)`);
|
|
9720
9901
|
return Promise.resolve(null);
|
|
9721
9902
|
}
|
|
9722
9903
|
const text = ctx.text.trim();
|
|
9723
9904
|
const p = (async () => {
|
|
9724
9905
|
const intentP = ctx.barged ? this.bargeIntent(text).catch((e) => {
|
|
9725
|
-
|
|
9906
|
+
log21.warn(`barge intent for the ack failed \u2014 situational ack: ${e.message}`);
|
|
9726
9907
|
return null;
|
|
9727
9908
|
}) : null;
|
|
9728
9909
|
const d = await this.ask(
|
|
@@ -9744,7 +9925,7 @@ var Instinct = class {
|
|
|
9744
9925
|
settle = r;
|
|
9745
9926
|
});
|
|
9746
9927
|
const timer = setTimeout(() => {
|
|
9747
|
-
|
|
9928
|
+
log21.debug(`ack: no engine outcome in ${this.o.ackSettleMs}ms \u2014 not claimed`);
|
|
9748
9929
|
settle(null);
|
|
9749
9930
|
}, this.o.ackSettleMs);
|
|
9750
9931
|
void spoken.then(() => clearTimeout(timer));
|
|
@@ -9793,7 +9974,7 @@ var Instinct = class {
|
|
|
9793
9974
|
});
|
|
9794
9975
|
return { kind, heard, choice: answers.act.choice, probabilities: answers.act.probabilities, ms: Math.round(this.o.now() - t0) };
|
|
9795
9976
|
} catch (e) {
|
|
9796
|
-
|
|
9977
|
+
log21.warn(`${kind} decision failed \u2014 falling back to engine defaults: ${e.message}`);
|
|
9797
9978
|
if (rethrow) throw e;
|
|
9798
9979
|
return null;
|
|
9799
9980
|
}
|
|
@@ -9802,7 +9983,7 @@ var Instinct = class {
|
|
|
9802
9983
|
|
|
9803
9984
|
// src/voice/unsaid.ts
|
|
9804
9985
|
init_logging();
|
|
9805
|
-
var
|
|
9986
|
+
var log22 = forComponent("Unsaid");
|
|
9806
9987
|
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(" "));
|
|
9807
9988
|
var contentStems = (s) => (s.toLowerCase().match(/[a-z0-9']+/g) ?? []).filter((w) => w.length >= 4 && !STOP2.has(w)).map((w) => w.slice(0, 4));
|
|
9808
9989
|
var SAID_OVERLAP = 0.6;
|
|
@@ -9842,7 +10023,7 @@ async function unsaidClauses(pieces, spoken, decider, timeoutMs = 5e3) {
|
|
|
9842
10023
|
return p < CONVEYED_MIN;
|
|
9843
10024
|
}).map((x) => x.c);
|
|
9844
10025
|
} catch (e) {
|
|
9845
|
-
|
|
10026
|
+
log22.warn(`unsaid judge failed \u2014 only near-zero-overlap clauses count as unsaid: ${e.message}`);
|
|
9846
10027
|
return fallback();
|
|
9847
10028
|
} finally {
|
|
9848
10029
|
clearTimeout(timer);
|
|
@@ -9963,7 +10144,7 @@ var RESULT_CLAIM_RE = new RegExp(LEAD_TAGS + String.raw`(?:(?:all\s+)?done|finis
|
|
|
9963
10144
|
var DONE_CLAIM_RE = new RegExp(LEAD_TAGS + String.raw`(?:(?:all\s+)?done|finished|it['’]?s\s+(?:done|finished)|completed?)\b`, "i");
|
|
9964
10145
|
|
|
9965
10146
|
// src/voice/channelLang.ts
|
|
9966
|
-
var
|
|
10147
|
+
var log23 = forComponent("ChannelBanks");
|
|
9967
10148
|
var LINE_KINDS = ["backchannel", "filler", "bridge"];
|
|
9968
10149
|
var BANK_KEYS = [...LINE_KINDS, "phatic"];
|
|
9969
10150
|
var EN_PHATIC = [
|
|
@@ -10027,8 +10208,17 @@ function isPhatic(text, bank) {
|
|
|
10027
10208
|
return !!q2 && (t === q2 || t.startsWith(q2 + " "));
|
|
10028
10209
|
});
|
|
10029
10210
|
}
|
|
10211
|
+
var CLOSER = new Set("ok okay k cool great nice perfect awesome amazing lovely excellent alright fine good thanks thank thx you so much very a lot bye goodbye cheers later see ya night got it i that that's thats is it's its all for now sounds understood noted makes sense oh ah wow right then just \u05EA\u05D5\u05D3\u05D4 \u05E8\u05D1\u05D4 \u05E1\u05D1\u05D1\u05D4 \u05D0\u05D5\u05E7\u05D9\u05D9 \u05D0\u05D5\u05E7\u05D9 \u05D1\u05E1\u05D3\u05E8 \u05D9\u05D5\u05E4\u05D9 \u05D0\u05D7\u05DC\u05D4 \u05DE\u05E2\u05D5\u05DC\u05D4 \u05DE\u05E6\u05D5\u05D9\u05DF \u05DE\u05D2\u05E0\u05D9\u05D1 \u05E0\u05D4\u05D3\u05E8 \u05D1\u05D9\u05D9 \u05DC\u05D4\u05EA\u05E8\u05D0\u05D5\u05EA \u05D8\u05D5\u05D1 \u05D6\u05D4\u05D5 \u05D6\u05D4 \u05D4\u05DB\u05DC \u05D1\u05D9\u05E0\u05EA\u05D9\u05D9\u05DD \u05D4\u05D1\u05E0\u05EA\u05D9 \u05DE\u05DE\u05E9 \u05DC\u05DA \u05D0\u05D6".split(" "));
|
|
10212
|
+
function turnIntent(text) {
|
|
10213
|
+
const t = text.trim();
|
|
10214
|
+
if (/\?[\s"')\]]*$/.test(t)) return "ask";
|
|
10215
|
+
const w = norm3(t).split(" ").filter(Boolean);
|
|
10216
|
+
const asks = requestOpener(t) && !/^ת/.test(lead(t)[0] ?? "") || pendingQuestion(t);
|
|
10217
|
+
if (w.length && w.length <= 8 && w.every((x) => CLOSER.has(x)) && !asks) return "closer";
|
|
10218
|
+
return asks || requestOpener(t) ? "ask" : "say";
|
|
10219
|
+
}
|
|
10030
10220
|
var SKIP = new Set("um uh uhm erm hmm mm oh ok okay so no yes yeah yep well hey hi and but right alright actually like now then wait hang on me speaker \u05D0\u05D5\u05E7\u05D9\u05D9 \u05D8\u05D5\u05D1 \u05D0\u05D6 \u05DC\u05D0 \u05DB\u05DF \u05E8\u05D2\u05E2 \u05D0\u05D4 \u05D0\u05DE \u05D9\u05D0\u05DC\u05DC\u05D4 \u05D5".split(" "));
|
|
10031
|
-
var ASK = "what whats how why where when who which whose is are was were do does did can could would will should shall have has may isnt arent dont doesnt didnt cant wont check open show tell find search look send run go lets please take give make write explain select focus order try compare list use set activate highlight outline capture start stop read play call book create add remove delete put type click close switch help get summarize translate mark keep remind schedule \u05DE\u05D4 \u05D0\u05D9\u05DA \u05DC\u05DE\u05D4 \u05D0\u05D9\u05E4\u05D4 \u05DE\u05EA\u05D9 \u05DE\u05D9 \u05D4\u05D0\u05DD \u05DB\u05DE\u05D4 \u05EA\u05DF \u05EA\u05E0\u05D9 \u05EA\u05D2\u05D9\u05D3 \u05EA\u05D2\u05D9\u05D3\u05D9 \u05D1\u05D5\u05D0 \u05D1\u05D5\u05D0\u05D9".split(" ");
|
|
10221
|
+
var ASK = "what whats how why where when who which whose is are was were do does did can could would will should shall have has may isnt arent dont doesnt didnt cant wont check open show tell find search look send run go lets please take give make write explain select focus order try compare list use set activate highlight outline capture start stop read play call book create add remove delete put type click close switch help get name recommend suggest summarize translate mark keep remind schedule \u05DE\u05D4 \u05D0\u05D9\u05DA \u05DC\u05DE\u05D4 \u05D0\u05D9\u05E4\u05D4 \u05DE\u05EA\u05D9 \u05DE\u05D9 \u05D4\u05D0\u05DD \u05DB\u05DE\u05D4 \u05EA\u05DF \u05EA\u05E0\u05D9 \u05EA\u05D2\u05D9\u05D3 \u05EA\u05D2\u05D9\u05D3\u05D9 \u05D1\u05D5\u05D0 \u05D1\u05D5\u05D0\u05D9".split(" ");
|
|
10032
10222
|
var WANT = /^(i (want|need|would like|d like|wanna)|אני (רוצה|צריך|צריכה|מבקש|מבקשת))(\s|$)/u;
|
|
10033
10223
|
var lead = (text) => {
|
|
10034
10224
|
const w = text.toLowerCase().replace(/[^\p{L}\p{N}\s]+/gu, "").split(/\s+/).filter(Boolean);
|
|
@@ -10108,11 +10298,11 @@ function cleanLine(s, lang, maxWords = 6) {
|
|
|
10108
10298
|
const raw = s.trim().split("\n")[0];
|
|
10109
10299
|
const l = raw.replace(/^[-*•\d.)\s]+/, "").replace(/^["'“”«»]+|["'“”«»]+$/g, "").replace(new RegExp("^\\p{L}+:\\s*", "u"), "").trim();
|
|
10110
10300
|
if (!l || isLeakSuspect(raw) || isLeakSuspect(l)) {
|
|
10111
|
-
if (l)
|
|
10301
|
+
if (l) log23.debug(`line "${raw}" reads as meta \u2014 rejected`);
|
|
10112
10302
|
return "";
|
|
10113
10303
|
}
|
|
10114
10304
|
if (lang && !fitsLanguage(l, lang)) {
|
|
10115
|
-
|
|
10305
|
+
log23.debug(`line "${l}" is not ${lang} \u2014 rejected`);
|
|
10116
10306
|
return "";
|
|
10117
10307
|
}
|
|
10118
10308
|
return l.split(/\s+/).length > maxWords || l.length > 60 ? "" : l;
|
|
@@ -10126,7 +10316,7 @@ function validateBank(raw, lang, minPerKind = 3) {
|
|
|
10126
10316
|
const seen = /* @__PURE__ */ new Set();
|
|
10127
10317
|
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)));
|
|
10128
10318
|
if (out[k].length < minPerKind) {
|
|
10129
|
-
|
|
10319
|
+
log23.warn(`bank ${lang}: only ${out[k].length} valid ${k} lines \u2014 rejected`);
|
|
10130
10320
|
return null;
|
|
10131
10321
|
}
|
|
10132
10322
|
}
|
|
@@ -10202,11 +10392,11 @@ var ChannelBanks = class {
|
|
|
10202
10392
|
const f = this.file(lang);
|
|
10203
10393
|
let cached = null;
|
|
10204
10394
|
if (f) cached = await readFile(f, "utf8").then((s) => JSON.parse(s)).catch((e) => {
|
|
10205
|
-
if (e.code !== "ENOENT")
|
|
10395
|
+
if (e.code !== "ENOENT") log23.warn(`bank cache ${f} unreadable \u2014 regenerating: ${e.message}`);
|
|
10206
10396
|
return null;
|
|
10207
10397
|
});
|
|
10208
10398
|
if (cached && cached.v !== BANK_VERSION) {
|
|
10209
|
-
|
|
10399
|
+
log23.debug(`bank ${lang}: cache v${cached.v ?? 1} predates v${BANK_VERSION} \u2014 regenerating`);
|
|
10210
10400
|
cached = null;
|
|
10211
10401
|
}
|
|
10212
10402
|
const valid = cached && validateBank(cached.bank, lang, 0);
|
|
@@ -10214,7 +10404,7 @@ var ChannelBanks = class {
|
|
|
10214
10404
|
if (valid) {
|
|
10215
10405
|
for (const k of BANK_KEYS) if (!valid[k].length) valid[k] = [...this.o.fallback[lang]?.[k] ?? []];
|
|
10216
10406
|
this.banks.set(lang, valid);
|
|
10217
|
-
|
|
10407
|
+
log23.debug(`bank ${lang}: cache hit (${BANK_KEYS.map((k) => valid[k].length).join("/")})${complete ? "" : " \u2014 missing kinds, refreshing"}`);
|
|
10218
10408
|
}
|
|
10219
10409
|
if (complete && this.o.now() - cached.at < this.o.refreshMs) return;
|
|
10220
10410
|
await this.generate(lang, valid);
|
|
@@ -10233,11 +10423,11 @@ var ChannelBanks = class {
|
|
|
10233
10423
|
bank[k] = [...fresh[k], ...have?.[k] ?? []].filter((x) => !seen.has(norm3(x)) && seen.add(norm3(x))).slice(0, this.o.maxPerKind);
|
|
10234
10424
|
}
|
|
10235
10425
|
this.banks.set(lang, bank);
|
|
10236
|
-
|
|
10426
|
+
log23.debug(`bank ${lang}: ${have ? "refreshed" : "generated"} (${BANK_KEYS.map((k) => bank[k].length).join("/")}) in ${Math.round(this.o.now() - t0)}ms`);
|
|
10237
10427
|
const f = this.file(lang);
|
|
10238
|
-
if (f) await mkdir(this.o.cacheDir, { recursive: true }).then(() => writeFile(f, JSON.stringify({ at: this.o.now(), v: BANK_VERSION, bank }))).catch((e) =>
|
|
10428
|
+
if (f) await mkdir(this.o.cacheDir, { recursive: true }).then(() => writeFile(f, JSON.stringify({ at: this.o.now(), v: BANK_VERSION, bank }))).catch((e) => log23.warn(`bank cache write ${f} failed: ${e.message}`));
|
|
10239
10429
|
} catch (e) {
|
|
10240
|
-
|
|
10430
|
+
log23.warn(`bank ${lang}: generation failed \u2014 ${have ? "keeping the cached bank" : lang in this.o.fallback ? "fallback bank" : "silent"}: ${e.message}`);
|
|
10241
10431
|
} finally {
|
|
10242
10432
|
clearTimeout(t);
|
|
10243
10433
|
if (!this.banks.has(lang)) setTimeout(() => this.pending.delete(lang), this.o.retryMs).unref?.();
|
|
@@ -10430,7 +10620,7 @@ var TurnGate = class {
|
|
|
10430
10620
|
};
|
|
10431
10621
|
|
|
10432
10622
|
// src/duplex.ts
|
|
10433
|
-
var
|
|
10623
|
+
var log24 = forComponent("DuplexAgent");
|
|
10434
10624
|
function describeCall(call) {
|
|
10435
10625
|
const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
|
|
10436
10626
|
const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
|
|
@@ -10765,7 +10955,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10765
10955
|
const m = this.reflexBuf.match(RESERVED_EVENT_MARKER) ?? this.reflexBuf.match(RESERVED_EVENT_OPENER);
|
|
10766
10956
|
if (m) {
|
|
10767
10957
|
this.fabricationCut = true;
|
|
10768
|
-
|
|
10958
|
+
log24.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
|
|
10769
10959
|
this.reflexBuf = this.reflexBuf.slice(0, Math.max(m.index, this.reflexForwarded));
|
|
10770
10960
|
this.pumpReflex(true);
|
|
10771
10961
|
return;
|
|
@@ -10907,7 +11097,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10907
11097
|
this.leakHit = true;
|
|
10908
11098
|
this.leakCuts.push(leak.trim().slice(0, 40));
|
|
10909
11099
|
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";
|
|
10910
|
-
|
|
11100
|
+
log24.warn(`reflex ${why} ("${leak.trim().slice(0, 40)}\u2026") \u2014 cut before TTS`);
|
|
10911
11101
|
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" });
|
|
10912
11102
|
const next = this.stepMarks.find((k) => k > at);
|
|
10913
11103
|
if (next === void 0) {
|
|
@@ -10948,7 +11138,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10948
11138
|
this.notify("diag", "leak_judge", { text: s.slice(0, 80), p: Math.round(p * 100) / 100, ms: Math.round(performance.now() - t0) });
|
|
10949
11139
|
return p >= LEAK_JUDGE_THRESHOLD;
|
|
10950
11140
|
}, (e) => {
|
|
10951
|
-
|
|
11141
|
+
log24.warn(`leak judge failed \u2014 speaking the sentence: ${e.message}`);
|
|
10952
11142
|
return false;
|
|
10953
11143
|
}).then((leak) => {
|
|
10954
11144
|
clearTimeout(timer);
|
|
@@ -11020,7 +11210,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11020
11210
|
let i = t.length - 1;
|
|
11021
11211
|
while (i >= this.turnBase && !(t[i].role === "assistant" && typeof t[i].content === "string" && t[i].content.includes(key))) i--;
|
|
11022
11212
|
if (i < this.turnBase) {
|
|
11023
|
-
|
|
11213
|
+
log24.warn("analysis leak cut, but its text was not found in the transcript to scrub");
|
|
11024
11214
|
continue;
|
|
11025
11215
|
}
|
|
11026
11216
|
const m = t[i];
|
|
@@ -11077,7 +11267,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11077
11267
|
const res = await this.sendWithContext(content, context, note);
|
|
11078
11268
|
const fb = this.options.reflexFallbackModel, model = this.voice.options.model;
|
|
11079
11269
|
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;
|
|
11080
|
-
|
|
11270
|
+
log24.warn(`reflex ${model} rate-limited \u2014 re-running the turn on ${fb}`);
|
|
11081
11271
|
this.notify("diag", "reflex_fallback", { from: model, to: fb });
|
|
11082
11272
|
this.voice.transcript.length = base;
|
|
11083
11273
|
this.voice.options.model = fb;
|
|
@@ -11126,7 +11316,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11126
11316
|
try {
|
|
11127
11317
|
await this.sendWithContext(reminder, this.lastContext);
|
|
11128
11318
|
} catch (e) {
|
|
11129
|
-
|
|
11319
|
+
log24.warn(`follow-through nudge failed: ${e instanceof Error ? e.message : e}`);
|
|
11130
11320
|
} finally {
|
|
11131
11321
|
const i = this.voice.transcript.findIndex((m, k) => k >= base && m.role === "user" && m.content === reminder);
|
|
11132
11322
|
if (i >= 0) this.voice.transcript.splice(i, 1);
|
|
@@ -11187,7 +11377,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11187
11377
|
return {
|
|
11188
11378
|
preToolUse: (call) => {
|
|
11189
11379
|
if (this.spec?.state === "pending" && !_DuplexAgent.SPEC_SAFE_TOOLS.has(call.name)) {
|
|
11190
|
-
|
|
11380
|
+
log24.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
|
|
11191
11381
|
this.abortSpeculation();
|
|
11192
11382
|
return { block: true, reason: "Speculative turn aborted." };
|
|
11193
11383
|
}
|
|
@@ -11269,7 +11459,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11269
11459
|
try {
|
|
11270
11460
|
await this.sendWithContext(reminder, this.lastContext);
|
|
11271
11461
|
} catch (e) {
|
|
11272
|
-
|
|
11462
|
+
log24.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
|
|
11273
11463
|
} finally {
|
|
11274
11464
|
this.nudging = false;
|
|
11275
11465
|
const i = this.voice.transcript.findIndex((m, k) => k >= base && m.role === "user" && m.content === reminder);
|
|
@@ -11325,7 +11515,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11325
11515
|
const accepted = this.offerOpen && this.parkedRedeliver.length > 0 && this.parkedRedeliver.every((p) => p.task === this.offer.task) && (isOfferAcceptance(content) || isTrivialBarge(content));
|
|
11326
11516
|
const judge = accepted ? void 0 : this.options.bargeIntent;
|
|
11327
11517
|
return (accepted ? Promise.resolve("resume") : judge ? judge(content).catch((e) => {
|
|
11328
|
-
|
|
11518
|
+
log24.warn(`bargeIntent judge failed \u2014 using the allowlist: ${e.message}`);
|
|
11329
11519
|
return fallback();
|
|
11330
11520
|
}) : Promise.resolve(fallback())).then((intent) => {
|
|
11331
11521
|
this.notify("diag", "barge_intent", { intent, text: content.slice(0, 80) });
|
|
@@ -11528,7 +11718,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11528
11718
|
try {
|
|
11529
11719
|
res = await this.sendWithContext(spec.text, this.lastContext);
|
|
11530
11720
|
} catch (e) {
|
|
11531
|
-
|
|
11721
|
+
log24.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
|
|
11532
11722
|
} finally {
|
|
11533
11723
|
this.voice.options.signal = prevSignal;
|
|
11534
11724
|
}
|
|
@@ -11542,7 +11732,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11542
11732
|
if (d === "abort") {
|
|
11543
11733
|
if (this.voice.transcript.length > base) this.voice.transcript.length = base;
|
|
11544
11734
|
this.speculativeAbortedCalls++;
|
|
11545
|
-
|
|
11735
|
+
log24.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
|
|
11546
11736
|
if (this.spec === spec) this.spec = void 0;
|
|
11547
11737
|
return res ?? empty;
|
|
11548
11738
|
}
|
|
@@ -11928,7 +12118,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
11928
12118
|
this.notify("task_verify", `task ${id}: verifying`, { id });
|
|
11929
12119
|
const cres = await new Agent(checkerOpts).run(checkBrief);
|
|
11930
12120
|
if (cres.finishReason !== "stop") {
|
|
11931
|
-
|
|
12121
|
+
log24.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
|
|
11932
12122
|
this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
|
|
11933
12123
|
}
|
|
11934
12124
|
const sum = (a = 0, b = 0) => a + b;
|
|
@@ -12064,7 +12254,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
12064
12254
|
rec.status = "done";
|
|
12065
12255
|
rec.result = res.text;
|
|
12066
12256
|
const incomplete = res.finishReason !== "stop" && res.finishReason !== "empty";
|
|
12067
|
-
|
|
12257
|
+
log24.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
|
|
12068
12258
|
this.notify("task_done", `task ${id} (${rec.label}) completed`, {
|
|
12069
12259
|
id,
|
|
12070
12260
|
text: res.text,
|
|
@@ -12098,14 +12288,14 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
12098
12288
|
foldIfSuperseded(rec, note) {
|
|
12099
12289
|
if (rec.deliveryParked !== "moved_on") return false;
|
|
12100
12290
|
if (note.trim()) this.voice.transcript.push({ role: "assistant", content: note });
|
|
12101
|
-
|
|
12291
|
+
log24.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
|
|
12102
12292
|
return true;
|
|
12103
12293
|
}
|
|
12104
12294
|
failTask(rec, msg) {
|
|
12105
12295
|
this.dropAsk(rec.id);
|
|
12106
12296
|
rec.status = "error";
|
|
12107
12297
|
rec.result = msg;
|
|
12108
|
-
|
|
12298
|
+
log24.warn(`task ${rec.id} failed: ${msg}`);
|
|
12109
12299
|
this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`, { id: rec.id });
|
|
12110
12300
|
if (this.foldIfSuperseded(rec, `The "${rec.label}" task failed: ${msg}`)) return;
|
|
12111
12301
|
this.dropParked(rec);
|
|
@@ -12409,7 +12599,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
12409
12599
|
init_llm();
|
|
12410
12600
|
init_logging();
|
|
12411
12601
|
init_models();
|
|
12412
|
-
var
|
|
12602
|
+
var log25 = forComponent("ModelRouter");
|
|
12413
12603
|
var ModelRouterOptions = class {
|
|
12414
12604
|
classifier = jevClassifier();
|
|
12415
12605
|
/** Capability tiers → model id (any provider). basic/standard run the main session; premium is only ever an offload. */
|
|
@@ -12493,10 +12683,10 @@ var ModelRouter = class {
|
|
|
12493
12683
|
if (tier === "premium") d.offload = this.state.offload && (c.sameThread ?? 0) >= o.sameThreadAt ? "continue" : "fresh";
|
|
12494
12684
|
} catch (e) {
|
|
12495
12685
|
const msg = e instanceof Error ? e.message : String(e);
|
|
12496
|
-
|
|
12686
|
+
log25.warn(`classifier failed, falling back to standard: ${msg}`);
|
|
12497
12687
|
d = { tier: "standard", model: o.models.standard, probs: {}, fallback: msg, classifyMs: Math.round(performance.now() - t0) };
|
|
12498
12688
|
}
|
|
12499
|
-
|
|
12689
|
+
log25.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`);
|
|
12500
12690
|
return d;
|
|
12501
12691
|
}
|
|
12502
12692
|
/** Run one user turn on `agent` with routing. Returns a RunResult carrying `costUsd` + `routing`. */
|
|
@@ -12548,7 +12738,7 @@ ${text}`;
|
|
|
12548
12738
|
const answer = res.text || `(premium sub-agent finished with no reply; finishReason=${res.finishReason})`;
|
|
12549
12739
|
agent.transcript.push({ role: "user", content }, { role: "assistant", content: answer });
|
|
12550
12740
|
this.state.offload = { messages: child.transcript, mainLen: agent.transcript.length, question: text };
|
|
12551
|
-
|
|
12741
|
+
log25.info(`offload ${cont ? "continue" : "fresh"} done \xB7 ${res.finishReason} \xB7 ${res.steps} steps`);
|
|
12552
12742
|
const costUsd = o.costOf(main2, briefUsage) + o.costOf(o.models.premium, res.usage);
|
|
12553
12743
|
return { ...res, text: answer, messages: agent.transcript, usage: addUsage(briefUsage, res.usage), costUsd, routing: d };
|
|
12554
12744
|
}
|
|
@@ -12564,7 +12754,7 @@ The request:
|
|
|
12564
12754
|
${text}`);
|
|
12565
12755
|
return { text: r.text.trim() || text, usage: r.usage };
|
|
12566
12756
|
} catch (e) {
|
|
12567
|
-
|
|
12757
|
+
log25.warn(`brief failed, sending the raw request: ${e instanceof Error ? e.message : String(e)}`);
|
|
12568
12758
|
return { text };
|
|
12569
12759
|
} finally {
|
|
12570
12760
|
agent.transcript = snap;
|
|
@@ -12609,7 +12799,7 @@ async function jevAvailable(opts = {}) {
|
|
|
12609
12799
|
const { env } = await loadJev(opts);
|
|
12610
12800
|
return JEV_KEYS.some((k) => env[k]?.trim());
|
|
12611
12801
|
} catch (e) {
|
|
12612
|
-
|
|
12802
|
+
log25.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
|
|
12613
12803
|
return false;
|
|
12614
12804
|
}
|
|
12615
12805
|
}
|
|
@@ -12836,7 +13026,7 @@ init_models();
|
|
|
12836
13026
|
|
|
12837
13027
|
// src/voice/progress.ts
|
|
12838
13028
|
init_logging();
|
|
12839
|
-
var
|
|
13029
|
+
var log26 = forComponent("Progress");
|
|
12840
13030
|
var HE_RESULT_CLAIM = /(?:^|\s)(?:מצאתי|סיימתי|בוצע|הנה|אין)(?=\s|$|[.,!…])/u;
|
|
12841
13031
|
var TOPICS = [
|
|
12842
13032
|
[/toolsearch|^mcp__.*__(list_commands|call_info)$/, null],
|
|
@@ -12981,7 +13171,7 @@ var Progress = class {
|
|
|
12981
13171
|
}, this.o.generateBudgetMs);
|
|
12982
13172
|
});
|
|
12983
13173
|
const raw = await Promise.race([this.o.generate(tool, argsHint(args), lang, ac.signal).catch((e) => {
|
|
12984
|
-
|
|
13174
|
+
log26.warn(`progress line failed: ${e?.message ?? e}`);
|
|
12985
13175
|
return null;
|
|
12986
13176
|
}), late]).finally(() => clearTimeout(t));
|
|
12987
13177
|
const l = cleanLine(raw ?? "", lang, 5);
|
|
@@ -13003,7 +13193,7 @@ init_logging();
|
|
|
13003
13193
|
|
|
13004
13194
|
// src/voice/backchannelPlan.ts
|
|
13005
13195
|
init_logging();
|
|
13006
|
-
var
|
|
13196
|
+
var log27 = forComponent("BackchannelPlan");
|
|
13007
13197
|
var TURN_TYPES = ["story", "list", "complaint", "explanation", "short_command", "question", "other"];
|
|
13008
13198
|
var DENSITIES = ["none", "low", "normal", "high"];
|
|
13009
13199
|
var silentTurn = (p) => p.density === "none" || p.turn === "short_command" || p.turn === "question";
|
|
@@ -13068,13 +13258,13 @@ Reply JSON only: {"turn":"...","density":"...","cues":[{"when":["phrase",...],"l
|
|
|
13068
13258
|
const reasoning = /gpt-oss/.test(o.model) ? { providerOptions: { reasoning_effort: "low" } } : {};
|
|
13069
13259
|
const r = await this.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false, signal, maxTokens: o.maxTokens, ...reasoning });
|
|
13070
13260
|
const p = parsePlan(r.content ?? "", c.lang);
|
|
13071
|
-
if (!p)
|
|
13261
|
+
if (!p) log27.debug(`unusable plan: ${(r.content ?? "").slice(0, 120)}`);
|
|
13072
13262
|
return p;
|
|
13073
13263
|
}
|
|
13074
13264
|
};
|
|
13075
13265
|
|
|
13076
13266
|
// src/voice/secondChannel.ts
|
|
13077
|
-
var
|
|
13267
|
+
var log28 = forComponent("SecondChannel");
|
|
13078
13268
|
var CHANNEL_MODES = {
|
|
13079
13269
|
turn: { kinds: ["bridge"], bridgeDelayMs: 1500 },
|
|
13080
13270
|
normal: {},
|
|
@@ -13164,6 +13354,9 @@ var SecondChannelOptions = class {
|
|
|
13164
13354
|
/** Duplex v3 (§3 first sound): a bridge skips the decider (the veto stays for backchannels/fillers) and speaks a
|
|
13165
13355
|
* canned line at once; a generated one is started at dispatch and used only if it already landed. */
|
|
13166
13356
|
instantBridge = false;
|
|
13357
|
+
/** Duplex v3 §3 late bridge: a turn that expects an answer (not a closer) and got no bridge yet gets one canned bridge
|
|
13358
|
+
* this long after the user's last word if channel-1 is still silent — the short turns the 0ms rule skips ("Hi"). 0 = off. */
|
|
13359
|
+
lateBridgeMs = 0;
|
|
13167
13360
|
/** No new interim for this long → a pause (hosts may also call onPause). */
|
|
13168
13361
|
fillerPauseMs = 700;
|
|
13169
13362
|
fillerMinWords = 4;
|
|
@@ -13193,15 +13386,15 @@ function withBudget(p, ms, what, ac) {
|
|
|
13193
13386
|
let t;
|
|
13194
13387
|
const late = new Promise((r) => {
|
|
13195
13388
|
t = setTimeout(() => {
|
|
13196
|
-
|
|
13389
|
+
log28.debug(`${what}: over the ${ms}ms budget \u2014 dropped`);
|
|
13197
13390
|
ac?.abort();
|
|
13198
13391
|
r(null);
|
|
13199
13392
|
}, ms);
|
|
13200
13393
|
});
|
|
13201
13394
|
return Promise.race([p.catch((e) => {
|
|
13202
13395
|
const msg = String(e?.message ?? e);
|
|
13203
|
-
if (e?.name === "AbortError" || /aborted/i.test(msg))
|
|
13204
|
-
else
|
|
13396
|
+
if (e?.name === "AbortError" || /aborted/i.test(msg)) log28.debug(`${what}: aborted after its budget`);
|
|
13397
|
+
else log28.warn(`${what} failed \u2014 falling back: ${msg}`);
|
|
13205
13398
|
return null;
|
|
13206
13399
|
}), late]).finally(() => clearTimeout(t));
|
|
13207
13400
|
}
|
|
@@ -13227,6 +13420,11 @@ var SecondChannel = class {
|
|
|
13227
13420
|
pauseTimer = null;
|
|
13228
13421
|
bcTimer = null;
|
|
13229
13422
|
bridgeTimer = null;
|
|
13423
|
+
lateTimer = null;
|
|
13424
|
+
late = false;
|
|
13425
|
+
// the bridge moment being considered is the late one (no length rule)
|
|
13426
|
+
lastWordAgo = 0;
|
|
13427
|
+
// the dispatched turn: ms since the user's last word (anchors lateBridgeMs)
|
|
13230
13428
|
lastAt = -Infinity;
|
|
13231
13429
|
lastKind = null;
|
|
13232
13430
|
live = null;
|
|
@@ -13275,7 +13473,7 @@ var SecondChannel = class {
|
|
|
13275
13473
|
if (host) this.pinned = true;
|
|
13276
13474
|
const lang = primaryLang(tag);
|
|
13277
13475
|
if (!lang || lang === this.lang) return;
|
|
13278
|
-
|
|
13476
|
+
log28.debug(`language ${this.lang} \u2192 ${lang}`);
|
|
13279
13477
|
this.lang = lang;
|
|
13280
13478
|
this.recent = [];
|
|
13281
13479
|
this.picks.clear();
|
|
@@ -13345,9 +13543,11 @@ var SecondChannel = class {
|
|
|
13345
13543
|
if (s === this.main) return;
|
|
13346
13544
|
this.main = s;
|
|
13347
13545
|
if (s === "first_audio" || s === "speaking") this.live = null;
|
|
13348
|
-
if (s !== "queued" && s !== "thinking"
|
|
13349
|
-
|
|
13350
|
-
|
|
13546
|
+
if (s !== "queued" && s !== "thinking") {
|
|
13547
|
+
for (const k of ["bridgeTimer", "lateTimer"]) if (this[k]) {
|
|
13548
|
+
clearTimeout(this[k]);
|
|
13549
|
+
this[k] = null;
|
|
13550
|
+
}
|
|
13351
13551
|
}
|
|
13352
13552
|
if (s === "idle") {
|
|
13353
13553
|
this.heard = "";
|
|
@@ -13368,7 +13568,7 @@ var SecondChannel = class {
|
|
|
13368
13568
|
this.userSpeaking = false;
|
|
13369
13569
|
this.bridged = false;
|
|
13370
13570
|
if (this.lastKind === "bridge") this.lastKind = null;
|
|
13371
|
-
for (const k of ["pauseTimer", "bcTimer", "bridgeTimer"]) {
|
|
13571
|
+
for (const k of ["pauseTimer", "bcTimer", "bridgeTimer", "lateTimer"]) {
|
|
13372
13572
|
if (this[k]) clearTimeout(this[k]);
|
|
13373
13573
|
this[k] = null;
|
|
13374
13574
|
}
|
|
@@ -13379,11 +13579,21 @@ var SecondChannel = class {
|
|
|
13379
13579
|
void this.consider("working");
|
|
13380
13580
|
}, wait);
|
|
13381
13581
|
else void this.consider("working");
|
|
13582
|
+
const late = this.o.lateBridgeMs - this.lastWordAgo;
|
|
13583
|
+
if (this.o.lateBridgeMs > 0) this.lateTimer = setTimeout(() => {
|
|
13584
|
+
this.lateTimer = null;
|
|
13585
|
+
if (this.bridged || this.busy || turnIntent(this.heard) === "closer" || this.blocked("bridge")) return;
|
|
13586
|
+
this.late = true;
|
|
13587
|
+
void this.consider("working").finally(() => {
|
|
13588
|
+
this.late = false;
|
|
13589
|
+
});
|
|
13590
|
+
}, Math.max(late, wait + 1));
|
|
13382
13591
|
}
|
|
13383
13592
|
}
|
|
13384
13593
|
/** The user's final line was dispatched to the main agent (its words are the bridge's micro-context). */
|
|
13385
|
-
onUserTurn(text) {
|
|
13594
|
+
onUserTurn(text, lastWordAgoMs = 0) {
|
|
13386
13595
|
this.heard = text.trim();
|
|
13596
|
+
this.lastWordAgo = Math.max(0, lastWordAgoMs);
|
|
13387
13597
|
this.floorWords = this.carried + words3(this.heard).length;
|
|
13388
13598
|
this.lastUser = this.heard;
|
|
13389
13599
|
this.detect(this.heard);
|
|
@@ -13408,7 +13618,7 @@ var SecondChannel = class {
|
|
|
13408
13618
|
try {
|
|
13409
13619
|
this.o.cancel();
|
|
13410
13620
|
} catch (e) {
|
|
13411
|
-
|
|
13621
|
+
log28.warn(`cancel threw: ${e.message}`);
|
|
13412
13622
|
}
|
|
13413
13623
|
}
|
|
13414
13624
|
}
|
|
@@ -13451,12 +13661,19 @@ var SecondChannel = class {
|
|
|
13451
13661
|
}
|
|
13452
13662
|
/** Why this turn gets no bridge by rule ('' = it may) — language, a quick/phatic turn, or blocked(). */
|
|
13453
13663
|
bridgeBlock() {
|
|
13454
|
-
const w = words3(this.heard);
|
|
13664
|
+
const w = words3(this.heard), intent = turnIntent(this.heard);
|
|
13455
13665
|
if (!this.lang) return "language unknown";
|
|
13456
|
-
if (
|
|
13457
|
-
if (
|
|
13666
|
+
if (intent === "closer") return "no answer due";
|
|
13667
|
+
if (this.late) return this.blocked("bridge");
|
|
13668
|
+
const why = intent === "ask" ? w.length <= 6 && isPhatic(this.heard, this.bank) ? "phatic" : "" : this.shortTurn(w.length);
|
|
13669
|
+
if (why) return this.o.lateBridgeMs > 0 ? `${why} (late bridge armed)` : why;
|
|
13458
13670
|
return this.blocked("bridge");
|
|
13459
13671
|
}
|
|
13672
|
+
shortTurn(n) {
|
|
13673
|
+
if (n <= 3 || n <= 6 && isPhatic(this.heard, this.bank)) return n <= 3 ? "quick turn" : "phatic";
|
|
13674
|
+
if (n < this.o.bridgeMinWords) return `under ${this.o.bridgeMinWords} words`;
|
|
13675
|
+
return "";
|
|
13676
|
+
}
|
|
13460
13677
|
bcTimes = [];
|
|
13461
13678
|
capped() {
|
|
13462
13679
|
if (!this.o.maxPerWindow) return false;
|
|
@@ -13468,8 +13685,9 @@ var SecondChannel = class {
|
|
|
13468
13685
|
rule(trigger, pauseMs = 0, breath = false, cue = null) {
|
|
13469
13686
|
const w = words3(this.heard);
|
|
13470
13687
|
if (!this.lang) return "silence";
|
|
13688
|
+
if (trigger === "working") return this.bridgeBlock() ? "silence" : "bridge";
|
|
13471
13689
|
if (w.length <= 3 || w.length <= 6 && isPhatic(this.heard, this.bank)) return "silence";
|
|
13472
|
-
const k = trigger === "interim" ? this.backchannelMoment(w.length, breath) && (!this.o.anticipate || !this.plan || !silentTurn(this.plan) && (!!cue || !this.o.cueOnly && this.plan.density !== "low")) ? "backchannel" : null : trigger === "pause" ? pauseMs >= this.o.fillerPauseMs && w.length >= this.o.fillerMinWords ? "filler" : null :
|
|
13690
|
+
const k = trigger === "interim" ? this.backchannelMoment(w.length, breath) && (!this.o.anticipate || !this.plan || !silentTurn(this.plan) && (!!cue || !this.o.cueOnly && this.plan.density !== "low")) ? "backchannel" : null : trigger === "pause" ? pauseMs >= this.o.fillerPauseMs && w.length >= this.o.fillerMinWords ? "filler" : null : null;
|
|
13473
13691
|
return k && this.allowed(k) ? k : "silence";
|
|
13474
13692
|
}
|
|
13475
13693
|
/** Plan (or, with `heard`, revise) the backchannels of the user's turn — async, never on the hot path. */
|
|
@@ -13512,7 +13730,7 @@ var SecondChannel = class {
|
|
|
13512
13730
|
const opts = this.options;
|
|
13513
13731
|
if (opts.mode === "chatty" && !opts.aec && !this.degradedLogged) {
|
|
13514
13732
|
this.degradedLogged = true;
|
|
13515
|
-
|
|
13733
|
+
log28.info("chatty mode without echo cancellation \u2014 normal timing (our lines would re-trigger the mic)");
|
|
13516
13734
|
}
|
|
13517
13735
|
const rule = this.rule(trigger, pauseMs, breath, cue);
|
|
13518
13736
|
if (rule === "silence") {
|
|
@@ -13531,7 +13749,7 @@ var SecondChannel = class {
|
|
|
13531
13749
|
const d = await withBudget(this.o.decide(this.tail(), { ...this.state(pauseMs), rule }, ac.signal), this.o.decisionBudgetMs, "channel decision", ac);
|
|
13532
13750
|
choice = d?.choice;
|
|
13533
13751
|
if (choice && choice in CHANNEL_ACTIONS && (choice === "silence" || this.allowed(choice))) kind = choice;
|
|
13534
|
-
else if (choice && choice in CHANNEL_ACTIONS)
|
|
13752
|
+
else if (choice && choice in CHANNEL_ACTIONS) log28.debug(`decider chose ${choice} for a ${rule} moment \u2014 the rule stands`);
|
|
13535
13753
|
}
|
|
13536
13754
|
if (kind === "silence") return ev({ choice, acted: "silence" });
|
|
13537
13755
|
if (stale()) return ev({ choice, kind, acted: "stale" });
|
|
@@ -13569,7 +13787,7 @@ var SecondChannel = class {
|
|
|
13569
13787
|
void withBudget(this.o.generate(ctx, ac.signal), Math.max(this.o.generateBudgetMs, this.o.bridgeDelayMs), "bridge generate", ac).then((raw) => {
|
|
13570
13788
|
const text = cleanLine(raw ?? "", ctx.lang);
|
|
13571
13789
|
if (text && gen === this.gen) this.bridgeDraft = { gen, text };
|
|
13572
|
-
}).catch((e) =>
|
|
13790
|
+
}).catch((e) => log28.debug(`bridge prefetch failed: ${e?.message ?? e}`));
|
|
13573
13791
|
}
|
|
13574
13792
|
async line(kind, react = true) {
|
|
13575
13793
|
if (kind === "bridge" && this.o.instantBridge) {
|
|
@@ -13585,7 +13803,7 @@ var SecondChannel = class {
|
|
|
13585
13803
|
if (raw === null) ac.abort();
|
|
13586
13804
|
const text = cleanLine(raw ?? "", this.lang);
|
|
13587
13805
|
if (text && !this.recent.some((r) => norm3(r) === norm3(text))) return text;
|
|
13588
|
-
if (text)
|
|
13806
|
+
if (text) log28.debug(`generated "${text}" repeats a recent line \u2014 canned`);
|
|
13589
13807
|
}
|
|
13590
13808
|
return this.pick(kind);
|
|
13591
13809
|
}
|
|
@@ -13607,7 +13825,7 @@ var SecondChannel = class {
|
|
|
13607
13825
|
try {
|
|
13608
13826
|
r = this.o.speak(text, kind);
|
|
13609
13827
|
} catch (e) {
|
|
13610
|
-
|
|
13828
|
+
log28.warn(`speak threw \u2014 line dropped: ${e.message}`);
|
|
13611
13829
|
return false;
|
|
13612
13830
|
}
|
|
13613
13831
|
if (r === false) return false;
|
|
@@ -13620,7 +13838,7 @@ var SecondChannel = class {
|
|
|
13620
13838
|
this.o.show(text, kind);
|
|
13621
13839
|
const p = Promise.resolve(r).then(() => {
|
|
13622
13840
|
}, (e) => {
|
|
13623
|
-
|
|
13841
|
+
log28.warn(`channel line playback failed: ${e?.message ?? e}`);
|
|
13624
13842
|
}).finally(() => {
|
|
13625
13843
|
if (this.inflight === p) this.inflight = null;
|
|
13626
13844
|
});
|
|
@@ -13678,7 +13896,7 @@ function closedQuestion(reply) {
|
|
|
13678
13896
|
}
|
|
13679
13897
|
|
|
13680
13898
|
// src/voice/call.ts
|
|
13681
|
-
var
|
|
13899
|
+
var log29 = forComponent("VoiceCall");
|
|
13682
13900
|
var VoiceCallOptions = class {
|
|
13683
13901
|
/** Host runs MAIN with exactly `text` (a replace: roll the `replaces` turn back first). */
|
|
13684
13902
|
sendTurn = () => {
|
|
@@ -13736,8 +13954,11 @@ var VoiceCall = class {
|
|
|
13736
13954
|
aec: o.aec,
|
|
13737
13955
|
// v3 §3: first sound <1.2s p50 from the last word — the endpoint takes ~0.6s and the bridge's TTS ~0.2s, so the bridge
|
|
13738
13956
|
// goes 300ms after the turn if MAIN is still silent (it always is that early: warm MAIN's first token is ≥1s), no veto
|
|
13957
|
+
// A short question gets that bridge too (by intent, not length); any other turn expecting an answer that got none
|
|
13958
|
+
// gets a late one at 1.3s from the last word while MAIN is still silent (API first byte runs 1-8s); a closer, none.
|
|
13739
13959
|
bridgeDelayMs: 300,
|
|
13740
13960
|
instantBridge: true,
|
|
13961
|
+
lateBridgeMs: 1300,
|
|
13741
13962
|
...this.lang ? { lang: this.lang } : {},
|
|
13742
13963
|
...o.channel,
|
|
13743
13964
|
// the ledger: a turn's bridge moment that stayed silent, and why (its wait then has no first sound until MAIN speaks)
|
|
@@ -13901,7 +14122,7 @@ var VoiceCall = class {
|
|
|
13901
14122
|
try {
|
|
13902
14123
|
this.o.cancel();
|
|
13903
14124
|
} catch (e) {
|
|
13904
|
-
|
|
14125
|
+
log29.warn(`cancel threw: ${e.message}`);
|
|
13905
14126
|
}
|
|
13906
14127
|
if (this.cur?.running) {
|
|
13907
14128
|
this.cur.running = false;
|
|
@@ -13961,7 +14182,7 @@ var VoiceCall = class {
|
|
|
13961
14182
|
if (this.cur) this.close(this.cur, t.replaces === this.cur.id ? "replaced" : void 0);
|
|
13962
14183
|
this.follow(t.text);
|
|
13963
14184
|
this.clearProgress();
|
|
13964
|
-
this.cur = { id: t.id, text: t.text, at, finalAt, lastWord, running: true, streamed: false, sounded: false, worded: false, done: false, marks: { sent: now4 }, closed: false };
|
|
14185
|
+
this.cur = { id: t.id, text: t.text, at, finalAt, lastWord, running: true, streamed: false, sounded: false, worded: false, done: false, marks: { sent: now4 }, closed: false, due: turnIntent(t.text) !== "closer" };
|
|
13965
14186
|
const early = this.earlyHeard;
|
|
13966
14187
|
this.earlyHeard = null;
|
|
13967
14188
|
if (early && early.at >= at) {
|
|
@@ -13971,7 +14192,7 @@ var VoiceCall = class {
|
|
|
13971
14192
|
this.progress.turn();
|
|
13972
14193
|
this.o.onMetric({ name: "turn", id: t.id, kind: t.kind, text: t.text });
|
|
13973
14194
|
this.o.sendTurn(t.text, { id: t.id, ...t.replaces ? { replaces: t.replaces } : {} });
|
|
13974
|
-
if (t.kind === "new") this.fill.onUserTurn(t.text);
|
|
14195
|
+
if (t.kind === "new") this.fill.onUserTurn(t.text, now4 - at);
|
|
13975
14196
|
}
|
|
13976
14197
|
onDrop(text, why) {
|
|
13977
14198
|
if (!this.gate.holding) {
|
|
@@ -13988,7 +14209,7 @@ var VoiceCall = class {
|
|
|
13988
14209
|
try {
|
|
13989
14210
|
r = this.o.speak(text, kind);
|
|
13990
14211
|
} catch (e) {
|
|
13991
|
-
|
|
14212
|
+
log29.warn(`speak threw: ${e.message}`);
|
|
13992
14213
|
return false;
|
|
13993
14214
|
}
|
|
13994
14215
|
if (r === false) return false;
|
|
@@ -14010,7 +14231,7 @@ var VoiceCall = class {
|
|
|
14010
14231
|
sounded(c, by, at = this.o.now()) {
|
|
14011
14232
|
if (c.sounded) return;
|
|
14012
14233
|
c.sounded = true;
|
|
14013
|
-
this.o.onMetric({ name: "first_sound", id: c.id, ms: Math.round(at - c.at), by });
|
|
14234
|
+
this.o.onMetric(c.due ? { name: "first_sound", id: c.id, ms: Math.round(at - c.at), by } : { name: "first_sound", id: c.id, by, kind: "no answer due" });
|
|
14014
14235
|
}
|
|
14015
14236
|
/** The turn is over (answered + audible, replaced, the next turn, the call's end): its timeline, once. A turn whose MAIN
|
|
14016
14237
|
* never became audible still reports first_word — with no ms and why (cut / replaced / silent) — so every turn has one. */
|
|
@@ -14030,7 +14251,8 @@ var VoiceCall = class {
|
|
|
14030
14251
|
mainText: rel(m.mainText),
|
|
14031
14252
|
mainAudio: rel(m.mainAudio),
|
|
14032
14253
|
lastWord: c.lastWord,
|
|
14033
|
-
end: c.end
|
|
14254
|
+
end: c.end,
|
|
14255
|
+
...c.due ? {} : { due: false }
|
|
14034
14256
|
};
|
|
14035
14257
|
for (const k of Object.keys(tl)) if (tl[k] === void 0) delete tl[k];
|
|
14036
14258
|
this.o.onMetric({ name: "timeline", id: c.id, text: c.text, timeline: tl });
|
|
@@ -14073,7 +14295,7 @@ init_logging();
|
|
|
14073
14295
|
init_platform();
|
|
14074
14296
|
import { spawn } from "child_process";
|
|
14075
14297
|
import { createHash } from "crypto";
|
|
14076
|
-
var
|
|
14298
|
+
var log30 = forComponent("mcp");
|
|
14077
14299
|
var PROTOCOL_VERSION = "2025-06-18";
|
|
14078
14300
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
14079
14301
|
var DEFAULT_CALL_TIMEOUT_MS = 30 * 6e4;
|
|
@@ -14105,7 +14327,7 @@ var StdioTransport = class {
|
|
|
14105
14327
|
proc.stdout.setEncoding("utf8");
|
|
14106
14328
|
proc.stdout.on("data", (chunk) => this.onData(chunk));
|
|
14107
14329
|
proc.stderr.setEncoding("utf8");
|
|
14108
|
-
proc.stderr.on("data", (chunk) =>
|
|
14330
|
+
proc.stderr.on("data", (chunk) => log30.debug(`[${command}] stderr:`, chunk.trimEnd()));
|
|
14109
14331
|
proc.on("exit", (code) => this.failAll(new Error(`MCP server "${command}" exited (code ${code})`)));
|
|
14110
14332
|
proc.on("error", (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));
|
|
14111
14333
|
}
|
|
@@ -14119,7 +14341,7 @@ var StdioTransport = class {
|
|
|
14119
14341
|
try {
|
|
14120
14342
|
this.dispatch(JSON.parse(line));
|
|
14121
14343
|
} catch (e) {
|
|
14122
|
-
|
|
14344
|
+
log30.debug("dropping non-JSON line from MCP server:", line, e);
|
|
14123
14345
|
}
|
|
14124
14346
|
}
|
|
14125
14347
|
}
|
|
@@ -14129,7 +14351,7 @@ var StdioTransport = class {
|
|
|
14129
14351
|
try {
|
|
14130
14352
|
p2?.onProgress?.(msg.params ?? {});
|
|
14131
14353
|
} catch (e) {
|
|
14132
|
-
|
|
14354
|
+
log30.debug("onProgress handler threw", e);
|
|
14133
14355
|
}
|
|
14134
14356
|
return;
|
|
14135
14357
|
}
|
|
@@ -14167,7 +14389,7 @@ var StdioTransport = class {
|
|
|
14167
14389
|
const onAbort = () => {
|
|
14168
14390
|
if (!this.pending.delete(id)) return;
|
|
14169
14391
|
clearTimeout(timer);
|
|
14170
|
-
this.notify("notifications/cancelled", { requestId: id, reason: String(signal?.reason?.message ?? "cancelled") }).catch((e) =>
|
|
14392
|
+
this.notify("notifications/cancelled", { requestId: id, reason: String(signal?.reason?.message ?? "cancelled") }).catch((e) => log30.debug("cancel notify failed", e));
|
|
14171
14393
|
reject(abortError(signal));
|
|
14172
14394
|
};
|
|
14173
14395
|
const done = () => signal?.removeEventListener("abort", onAbort);
|
|
@@ -14191,7 +14413,7 @@ var StdioTransport = class {
|
|
|
14191
14413
|
try {
|
|
14192
14414
|
this.proc?.stdin?.end();
|
|
14193
14415
|
} catch (e) {
|
|
14194
|
-
|
|
14416
|
+
log30.debug("stdin end failed", e);
|
|
14195
14417
|
}
|
|
14196
14418
|
if (!(IS_WIN && killTree(this.proc?.pid, "SIGTERM"))) this.proc?.kill();
|
|
14197
14419
|
}
|
|
@@ -14226,7 +14448,7 @@ var HttpTransport = class {
|
|
|
14226
14448
|
return await this.post({ jsonrpc: "2.0", id, method, params: opts.onProgress ? withProgressToken(params, id) : params }, false, signal, opts.onProgress);
|
|
14227
14449
|
} catch (e) {
|
|
14228
14450
|
if (opts.signal?.aborted) {
|
|
14229
|
-
void this.notify("notifications/cancelled", { requestId: id, reason: String(opts.signal.reason?.message ?? "cancelled") }).catch((err2) =>
|
|
14451
|
+
void this.notify("notifications/cancelled", { requestId: id, reason: String(opts.signal.reason?.message ?? "cancelled") }).catch((err2) => log30.debug("cancel notify failed", err2));
|
|
14230
14452
|
throw abortError(opts.signal);
|
|
14231
14453
|
}
|
|
14232
14454
|
throw e;
|
|
@@ -14269,7 +14491,7 @@ function sseMessage(line) {
|
|
|
14269
14491
|
try {
|
|
14270
14492
|
return JSON.parse(trimmed.slice(5).trim());
|
|
14271
14493
|
} catch (e) {
|
|
14272
|
-
|
|
14494
|
+
log30.debug("skipping unparseable SSE data line", e);
|
|
14273
14495
|
return void 0;
|
|
14274
14496
|
}
|
|
14275
14497
|
}
|
|
@@ -14279,7 +14501,7 @@ async function readSseResponse(resp, id, onProgress) {
|
|
|
14279
14501
|
try {
|
|
14280
14502
|
onProgress?.(obj.params ?? {});
|
|
14281
14503
|
} catch (e) {
|
|
14282
|
-
|
|
14504
|
+
log30.debug("onProgress handler threw", e);
|
|
14283
14505
|
}
|
|
14284
14506
|
return void 0;
|
|
14285
14507
|
}
|
|
@@ -14378,7 +14600,7 @@ async function mountWithDeadline(name, cfg, mountTimeoutMs) {
|
|
|
14378
14600
|
return { name, client, tools, specs, serverInfo: init?.serverInfo, config: cfg };
|
|
14379
14601
|
})(), mountTimeoutMs, name);
|
|
14380
14602
|
} catch (e) {
|
|
14381
|
-
await client.close().catch((err2) =>
|
|
14603
|
+
await client.close().catch((err2) => log30.debug(`close after failed mount of "${name}": ${err2}`));
|
|
14382
14604
|
throw e;
|
|
14383
14605
|
}
|
|
14384
14606
|
}
|
|
@@ -14389,15 +14611,15 @@ function validEntries(servers) {
|
|
|
14389
14611
|
return Object.entries(servers).filter(([name, cfg]) => {
|
|
14390
14612
|
if (!cfg || cfg.disabled) return false;
|
|
14391
14613
|
if (!cfg.command && !cfg.url) {
|
|
14392
|
-
|
|
14614
|
+
log30.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
|
|
14393
14615
|
return false;
|
|
14394
14616
|
}
|
|
14395
14617
|
return true;
|
|
14396
14618
|
});
|
|
14397
14619
|
}
|
|
14398
14620
|
function logMountFailure(name, e) {
|
|
14399
|
-
if (e instanceof McpAuthError)
|
|
14400
|
-
else
|
|
14621
|
+
if (e instanceof McpAuthError) log30.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
|
|
14622
|
+
else log30.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
|
|
14401
14623
|
}
|
|
14402
14624
|
async function mountMcpServers(servers = {}, opts = {}) {
|
|
14403
14625
|
const entries = validEntries(servers);
|
|
@@ -14407,7 +14629,7 @@ async function mountMcpServers(servers = {}, opts = {}) {
|
|
|
14407
14629
|
const name = entries[i][0];
|
|
14408
14630
|
if (r.status === "fulfilled") {
|
|
14409
14631
|
out.push(r.value);
|
|
14410
|
-
|
|
14632
|
+
log30.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
|
|
14411
14633
|
} else logMountFailure(name, r.reason);
|
|
14412
14634
|
});
|
|
14413
14635
|
return out;
|
|
@@ -14447,7 +14669,7 @@ var McpPool = class {
|
|
|
14447
14669
|
const prev = this.warm.get(key);
|
|
14448
14670
|
if (prev) {
|
|
14449
14671
|
clearTimeout(prev.timer);
|
|
14450
|
-
if (prev.client !== client) void prev.client.close().catch((err2) =>
|
|
14672
|
+
if (prev.client !== client) void prev.client.close().catch((err2) => log30.debug(`warm-pool replace close failed: ${err2}`));
|
|
14451
14673
|
}
|
|
14452
14674
|
const e = { client, timer: void 0 };
|
|
14453
14675
|
this.warm.set(key, e);
|
|
@@ -14464,7 +14686,7 @@ var McpPool = class {
|
|
|
14464
14686
|
const e = this.warm.get(key);
|
|
14465
14687
|
if (!e) return;
|
|
14466
14688
|
this.warm.delete(key);
|
|
14467
|
-
await e.client.close().catch((err2) =>
|
|
14689
|
+
await e.client.close().catch((err2) => log30.debug(`warm-pool evict close failed: ${err2}`));
|
|
14468
14690
|
}
|
|
14469
14691
|
async closeAll() {
|
|
14470
14692
|
for (const e of this.warm.values()) {
|
|
@@ -14683,7 +14905,7 @@ init_tools_shell();
|
|
|
14683
14905
|
// src/tools.notify.ts
|
|
14684
14906
|
init_logging();
|
|
14685
14907
|
import { execFile as execFile2 } from "child_process";
|
|
14686
|
-
var
|
|
14908
|
+
var log32 = forComponent("notify");
|
|
14687
14909
|
var WIN_TOAST = [
|
|
14688
14910
|
"[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null",
|
|
14689
14911
|
"$t = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)",
|
|
@@ -14714,7 +14936,7 @@ function makeNotifyTool(opts = {}) {
|
|
|
14714
14936
|
return new Promise((resolve6) => {
|
|
14715
14937
|
run(argv[0], argv[1], { timeout: 5e3, ...extra }, (e) => {
|
|
14716
14938
|
if (e) {
|
|
14717
|
-
|
|
14939
|
+
log32.debug("notification failed", e);
|
|
14718
14940
|
resolve6(`Notification failed: ${e.message}`);
|
|
14719
14941
|
} else resolve6("Notification shown.");
|
|
14720
14942
|
});
|
|
@@ -14730,7 +14952,7 @@ import { BodDB as BodDB2 } from "@bod.ee/db";
|
|
|
14730
14952
|
init_logging();
|
|
14731
14953
|
import { existsSync as existsSync2, readFileSync as readFileSync5 } from "fs";
|
|
14732
14954
|
import { homedir } from "os";
|
|
14733
|
-
var
|
|
14955
|
+
var log33 = forComponent("cli-util");
|
|
14734
14956
|
function dotDirs(base, sub, opts = {}) {
|
|
14735
14957
|
const home = opts.home ?? homedir();
|
|
14736
14958
|
const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
|
|
@@ -14747,7 +14969,7 @@ function parseJson(text, fallback, what = "json") {
|
|
|
14747
14969
|
try {
|
|
14748
14970
|
return JSON.parse(text);
|
|
14749
14971
|
} catch (e) {
|
|
14750
|
-
|
|
14972
|
+
log33.debug(`parseJson(${what}) failed: ${e.message}`);
|
|
14751
14973
|
return fallback;
|
|
14752
14974
|
}
|
|
14753
14975
|
}
|
|
@@ -14757,7 +14979,7 @@ function readJsonFile(path, fallback) {
|
|
|
14757
14979
|
try {
|
|
14758
14980
|
text = readFileSync5(path, "utf8");
|
|
14759
14981
|
} catch (e) {
|
|
14760
|
-
|
|
14982
|
+
log33.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
|
|
14761
14983
|
return fallback;
|
|
14762
14984
|
}
|
|
14763
14985
|
return parseJson(text, fallback, path);
|
|
@@ -15061,7 +15283,7 @@ import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as rea
|
|
|
15061
15283
|
import { homedir as homedir3 } from "os";
|
|
15062
15284
|
import { dirname as dirname4, join as join8 } from "path";
|
|
15063
15285
|
import { fileURLToPath } from "url";
|
|
15064
|
-
var
|
|
15286
|
+
var log34 = forComponent("VoiceIO");
|
|
15065
15287
|
var now3 = () => performance.now();
|
|
15066
15288
|
var Player = class {
|
|
15067
15289
|
proc = null;
|
|
@@ -15075,7 +15297,7 @@ var Player = class {
|
|
|
15075
15297
|
["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
|
|
15076
15298
|
{ stdio: ["pipe", "ignore", "ignore"] }
|
|
15077
15299
|
);
|
|
15078
|
-
this.proc.on("error", (e) =>
|
|
15300
|
+
this.proc.on("error", (e) => log34.warn(`ffplay error: ${e.message}`));
|
|
15079
15301
|
this.proc.stdin.on("error", () => {
|
|
15080
15302
|
});
|
|
15081
15303
|
this.bytesWritten = 0;
|
|
@@ -15113,7 +15335,7 @@ function detectFfmpegMic() {
|
|
|
15113
15335
|
const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
|
|
15114
15336
|
const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
|
|
15115
15337
|
if (!mic) throw new Error("no audio input device found");
|
|
15116
|
-
|
|
15338
|
+
log34.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
|
|
15117
15339
|
return `:${mic.idx}`;
|
|
15118
15340
|
}
|
|
15119
15341
|
function detectDshowMic() {
|
|
@@ -15124,7 +15346,7 @@ function detectDshowMic() {
|
|
|
15124
15346
|
const devices = [...(r.stderr ?? "").matchAll(/"([^"]+)"\s*\(audio\)/g)].map((m) => m[1]);
|
|
15125
15347
|
const mic = devices.find((d) => !/stereo mix|loopback|virtual|cable|teams/i.test(d)) ?? devices[0];
|
|
15126
15348
|
if (!mic) throw new Error("no audio input device found (set MIC_DEVICE to the DirectShow device name)");
|
|
15127
|
-
|
|
15349
|
+
log34.debug(`ffmpeg dshow mic: ${mic}`);
|
|
15128
15350
|
return `audio=${mic}`;
|
|
15129
15351
|
}
|
|
15130
15352
|
function detectedInputDevice() {
|
|
@@ -15160,15 +15382,15 @@ function resolveAecBinary() {
|
|
|
15160
15382
|
if (existsSync4(bin) && statSync6(bin).mtimeMs >= statSync6(src).mtimeMs) return bin;
|
|
15161
15383
|
if (spawnSync3("which", ["swiftc"]).status !== 0) return null;
|
|
15162
15384
|
mkdirSync5(cacheDir, { recursive: true });
|
|
15163
|
-
|
|
15385
|
+
log34.info("compiling AEC mic helper (first run)\u2026");
|
|
15164
15386
|
const build = spawnSync3("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
|
|
15165
15387
|
if (build.status !== 0) {
|
|
15166
|
-
|
|
15388
|
+
log34.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
|
|
15167
15389
|
return null;
|
|
15168
15390
|
}
|
|
15169
15391
|
const sign = spawnSync3("codesign", ["-fs", "-", bin], { encoding: "utf8" });
|
|
15170
15392
|
if (sign.status !== 0) {
|
|
15171
|
-
|
|
15393
|
+
log34.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
|
|
15172
15394
|
return null;
|
|
15173
15395
|
}
|
|
15174
15396
|
return bin;
|
|
@@ -15210,17 +15432,17 @@ var NodeMicSource = class {
|
|
|
15210
15432
|
this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
|
|
15211
15433
|
} else {
|
|
15212
15434
|
if (!hasCommand("ffmpeg")) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
|
|
15213
|
-
|
|
15435
|
+
log34.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
|
|
15214
15436
|
const input = process.platform === "win32" ? ["-f", "dshow", "-i", detectDshowMic()] : ["-f", "avfoundation", "-i", detectFfmpegMic()];
|
|
15215
15437
|
this.proc = spawn2(
|
|
15216
15438
|
"ffmpeg",
|
|
15217
15439
|
["-loglevel", "error", ...input, "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
|
|
15218
15440
|
{ stdio: ["ignore", "pipe", "pipe"], ...process.platform === "win32" ? { windowsHide: true } : {} }
|
|
15219
15441
|
);
|
|
15220
|
-
this.proc.stderr.on("data", (d) =>
|
|
15442
|
+
this.proc.stderr.on("data", (d) => log34.warn(`ffmpeg: ${String(d).trim()}`));
|
|
15221
15443
|
}
|
|
15222
15444
|
this.proc.on("exit", (c) => {
|
|
15223
|
-
if (c && !this.stopped)
|
|
15445
|
+
if (c && !this.stopped) log34.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
|
|
15224
15446
|
});
|
|
15225
15447
|
this.proc.stdout.on("data", (chunk) => onChunk(chunk));
|
|
15226
15448
|
}
|
|
@@ -15286,7 +15508,7 @@ var AecDuplexAudio = class {
|
|
|
15286
15508
|
this.proc.stdin.on("error", () => {
|
|
15287
15509
|
});
|
|
15288
15510
|
this.proc.on("exit", (c) => {
|
|
15289
|
-
if (c && !this.stopped)
|
|
15511
|
+
if (c && !this.stopped) log34.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
|
|
15290
15512
|
});
|
|
15291
15513
|
this.proc.stdout.on("data", (chunk) => {
|
|
15292
15514
|
this.gotChunk = true;
|
|
@@ -15302,7 +15524,7 @@ var AecDuplexAudio = class {
|
|
|
15302
15524
|
openMicSettings();
|
|
15303
15525
|
this.onFatal?.("microphone permission denied \u2014 enable it in System Settings \u2192 Privacy & Security \u2192 Microphone for your terminal, then restart it");
|
|
15304
15526
|
}
|
|
15305
|
-
} else
|
|
15527
|
+
} else log34.debug(`mic-aec: ${s}`);
|
|
15306
15528
|
}
|
|
15307
15529
|
});
|
|
15308
15530
|
if (!this.noVpio && !this.triedFallback) {
|
|
@@ -15311,7 +15533,7 @@ var AecDuplexAudio = class {
|
|
|
15311
15533
|
this.triedFallback = true;
|
|
15312
15534
|
this.noVpio = true;
|
|
15313
15535
|
this._aec = false;
|
|
15314
|
-
|
|
15536
|
+
log34.warn("mic-aec: VPIO delivered no audio in 2.5s \u2014 falling back to non-VPIO capture (no AEC \u2192 half-duplex, no barge-in)");
|
|
15315
15537
|
this.onDegrade?.();
|
|
15316
15538
|
this.killProc();
|
|
15317
15539
|
this.spawnHelper();
|
|
@@ -15482,7 +15704,7 @@ function bindAckInstinct(v, instinct) {
|
|
|
15482
15704
|
if (ev.kind === "ack_fired") instinct.ackOutcome(String(ev.phrase));
|
|
15483
15705
|
else if (ev.kind === "ack_skipped" || ev.kind === "ack_cancelled") instinct.ackOutcome(null);
|
|
15484
15706
|
} catch (e) {
|
|
15485
|
-
|
|
15707
|
+
log34.warn(`ack outcome tap failed: ${e instanceof Error ? e.message : e}`);
|
|
15486
15708
|
}
|
|
15487
15709
|
onDiag(ev);
|
|
15488
15710
|
};
|
|
@@ -15604,7 +15826,7 @@ function speakBlip(v, text) {
|
|
|
15604
15826
|
v.fireBackchannel();
|
|
15605
15827
|
return true;
|
|
15606
15828
|
} catch (e) {
|
|
15607
|
-
|
|
15829
|
+
log34.warn(`backchannel blip failed \u2014 line dropped: ${e.message}`);
|
|
15608
15830
|
return false;
|
|
15609
15831
|
} finally {
|
|
15610
15832
|
E.BACKCHANNELS = pool;
|
|
@@ -15623,7 +15845,7 @@ function channelInput(sc, m) {
|
|
|
15623
15845
|
}
|
|
15624
15846
|
if (m.type === "agent_state") {
|
|
15625
15847
|
if (typeof m.state === "string" && MAIN_STATES.has(m.state)) sc.onAgentState(m.state);
|
|
15626
|
-
else
|
|
15848
|
+
else log34.warn(`agent_state: unknown state ${JSON.stringify(m.state)} \u2014 ignored`);
|
|
15627
15849
|
return true;
|
|
15628
15850
|
}
|
|
15629
15851
|
return false;
|
|
@@ -15749,7 +15971,7 @@ function fakeVoiceParts(uttFile) {
|
|
|
15749
15971
|
init_logging();
|
|
15750
15972
|
import { appendFile, mkdirSync as mkdirSync6 } from "fs";
|
|
15751
15973
|
import { dirname as dirname5 } from "path";
|
|
15752
|
-
var
|
|
15974
|
+
var log35 = forComponent("VoiceDiag");
|
|
15753
15975
|
var JsonlDiagSink = class {
|
|
15754
15976
|
constructor(path) {
|
|
15755
15977
|
this.path = path;
|
|
@@ -15790,7 +16012,7 @@ var JsonlDiagSink = class {
|
|
|
15790
16012
|
disable(e) {
|
|
15791
16013
|
this.disabled = true;
|
|
15792
16014
|
this.queue.length = 0;
|
|
15793
|
-
|
|
16015
|
+
log35.debug(`voice diag sink disabled (${this.path}): ${e instanceof Error ? e.message : e}`);
|
|
15794
16016
|
}
|
|
15795
16017
|
/** True once a failure permanently disabled the sink (tests/inspection). */
|
|
15796
16018
|
get isDisabled() {
|
|
@@ -15948,7 +16170,7 @@ function applyOverlay(cfg, file) {
|
|
|
15948
16170
|
// cli/hooks-config.ts
|
|
15949
16171
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
15950
16172
|
init_platform();
|
|
15951
|
-
var
|
|
16173
|
+
var log36 = forComponent("hooks");
|
|
15952
16174
|
var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
15953
16175
|
function ruleMatches(rule, toolName) {
|
|
15954
16176
|
if (!rule.tool || rule.tool === "*") return true;
|
|
@@ -15967,7 +16189,7 @@ function runCmd(rule, env) {
|
|
|
15967
16189
|
});
|
|
15968
16190
|
return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
|
|
15969
16191
|
} catch (e) {
|
|
15970
|
-
|
|
16192
|
+
log36.debug(`hook command failed: ${rule.command}`, e);
|
|
15971
16193
|
return { code: 1, out: String(e?.message ?? e) };
|
|
15972
16194
|
}
|
|
15973
16195
|
}
|
|
@@ -16075,7 +16297,7 @@ import { existsSync as existsSync6, mkdirSync as mkdirSync8, readFileSync as rea
|
|
|
16075
16297
|
import { homedir as homedir6 } from "os";
|
|
16076
16298
|
import { join as join11 } from "path";
|
|
16077
16299
|
init_platform();
|
|
16078
|
-
var
|
|
16300
|
+
var log37 = forComponent("session");
|
|
16079
16301
|
var globalDir = () => join11(homedir6(), ".agent", "sessions");
|
|
16080
16302
|
function indexLink(target, link2) {
|
|
16081
16303
|
try {
|
|
@@ -16138,7 +16360,7 @@ var SessionStore = class {
|
|
|
16138
16360
|
}
|
|
16139
16361
|
load(id) {
|
|
16140
16362
|
if (!this.safeId(id)) {
|
|
16141
|
-
|
|
16363
|
+
log37.debug(`rejecting unsafe session id: ${id}`);
|
|
16142
16364
|
return void 0;
|
|
16143
16365
|
}
|
|
16144
16366
|
const path = join11(this.dir, `${id}.json`);
|
|
@@ -16146,7 +16368,7 @@ var SessionStore = class {
|
|
|
16146
16368
|
try {
|
|
16147
16369
|
return JSON.parse(readFileSync8(path, "utf8"));
|
|
16148
16370
|
} catch (e) {
|
|
16149
|
-
|
|
16371
|
+
log37.debug(`unreadable session ${id} \u2014 ignoring`, e);
|
|
16150
16372
|
return void 0;
|
|
16151
16373
|
}
|
|
16152
16374
|
}
|
|
@@ -16159,7 +16381,7 @@ var SessionStore = class {
|
|
|
16159
16381
|
try {
|
|
16160
16382
|
metas.push(JSON.parse(readFileSync8(join11(this.dir, f), "utf8")).meta);
|
|
16161
16383
|
} catch (e) {
|
|
16162
|
-
|
|
16384
|
+
log37.debug(`skipping unreadable session file ${f}`, e);
|
|
16163
16385
|
}
|
|
16164
16386
|
}
|
|
16165
16387
|
return metas.sort((a, b) => b.updated - a.updated);
|
|
@@ -16295,6 +16517,199 @@ ${body || "(no conversation)"}
|
|
|
16295
16517
|
(That is everything recorded. Where it does not say why something was done or what was meant, say you do not know \u2014 never supply a reason.)`;
|
|
16296
16518
|
}
|
|
16297
16519
|
|
|
16520
|
+
// src/directives.ts
|
|
16521
|
+
init_models();
|
|
16522
|
+
var VENDOR_SHORTHAND = { cc: "claude-code", cur: "cursor", oa: "openai" };
|
|
16523
|
+
var DIRECTIVE_KEYS = ["model", "turns", "thinking", "think", "effort", "engine", "resume", "delegate", "advisor", "mode"];
|
|
16524
|
+
function splitModePair(token) {
|
|
16525
|
+
const i = token.search(/->|<-/);
|
|
16526
|
+
if (i === -1) return null;
|
|
16527
|
+
const right = token.slice(i + 2).trim();
|
|
16528
|
+
return right ? { left: token.slice(0, i).trim(), op: token.slice(i, i + 2), right } : null;
|
|
16529
|
+
}
|
|
16530
|
+
var isModel = (t, p) => {
|
|
16531
|
+
const h = resolveModelToken(t, p);
|
|
16532
|
+
return !!h && "model" in h;
|
|
16533
|
+
};
|
|
16534
|
+
var defaultResolver = null;
|
|
16535
|
+
var opt = (o) => ({
|
|
16536
|
+
resolveModel: defaultResolver,
|
|
16537
|
+
resolverFirst: false,
|
|
16538
|
+
aliases: MODEL_ALIASES,
|
|
16539
|
+
vendors: VENDOR_SHORTHAND,
|
|
16540
|
+
keys: DIRECTIVE_KEYS,
|
|
16541
|
+
strict: false,
|
|
16542
|
+
catalogOnly: false,
|
|
16543
|
+
warn: (m) => console.warn(m),
|
|
16544
|
+
...o
|
|
16545
|
+
});
|
|
16546
|
+
var DIRECTIVE_RE = /^\s*\[([^\]]*)\](?=\s|$)\s*([\s\S]*)/;
|
|
16547
|
+
var isQualifiedModel = (v) => /^[a-z0-9._-]+\/[a-z0-9./_-]+$/.test(v);
|
|
16548
|
+
function expandVendor(token, vendors = VENDOR_SHORTHAND) {
|
|
16549
|
+
const v = token.trim().toLowerCase();
|
|
16550
|
+
const m = v.match(/^([a-z0-9]+)[-/](.+)$/);
|
|
16551
|
+
if (m && vendors[m[1]]) return `${vendors[m[1]]}/${m[2]}`;
|
|
16552
|
+
const slash = v.indexOf("/");
|
|
16553
|
+
return slash > 0 && Object.values(vendors).includes(v.slice(0, slash)) ? v : null;
|
|
16554
|
+
}
|
|
16555
|
+
function resolveModelToken(token, o) {
|
|
16556
|
+
const p = opt(o);
|
|
16557
|
+
const v = token.trim().toLowerCase();
|
|
16558
|
+
const vendored = expandVendor(v, p.vendors);
|
|
16559
|
+
if (vendored) return p.resolveModel ? p.resolveModel(vendored) : p.catalogOnly ? null : { model: vendored };
|
|
16560
|
+
if (p.catalogOnly) return v.includes("/") || p.aliases[v] || /\d/.test(v) ? p.resolveModel?.(v) ?? null : null;
|
|
16561
|
+
const alias = p.aliases[v] ? { model: p.aliases[v] } : null;
|
|
16562
|
+
if (!p.resolverFirst) return alias ?? p.resolveModel?.(v) ?? null;
|
|
16563
|
+
const hit = p.resolveModel?.(v) ?? null;
|
|
16564
|
+
return hit && "model" in hit ? hit : alias ?? hit;
|
|
16565
|
+
}
|
|
16566
|
+
var THINK = ["think", "adaptive", "nothink", "nothinking"];
|
|
16567
|
+
function isDirectiveGroup(raw, o) {
|
|
16568
|
+
const p = opt(o);
|
|
16569
|
+
const tokens = raw.split(",").map((t) => t.trim()).filter(Boolean);
|
|
16570
|
+
if (!tokens.length) return false;
|
|
16571
|
+
return tokens.every((t) => {
|
|
16572
|
+
const i = t.indexOf(":");
|
|
16573
|
+
if (i !== -1) return p.keys.includes(t.slice(0, i).trim().toLowerCase());
|
|
16574
|
+
const v = t.toLowerCase();
|
|
16575
|
+
const pair = splitModePair(v);
|
|
16576
|
+
if (pair) return p.keys.includes(pair.op === "->" ? "delegate" : "advisor") && (!!pair.left && isModel(pair.left, p) || isModel(pair.right, p));
|
|
16577
|
+
if (v === "off") return p.keys.includes("mode");
|
|
16578
|
+
return !!resolveModelToken(v, p) || p.catalogOnly && !!p.aliases[v] || p.keys.includes("turns") && /^\d+$/.test(v) || p.keys.includes("thinking") && THINK.includes(v);
|
|
16579
|
+
});
|
|
16580
|
+
}
|
|
16581
|
+
function splitDirectiveGroups(text, o) {
|
|
16582
|
+
const p = opt(o);
|
|
16583
|
+
let rest = text;
|
|
16584
|
+
const groups = [];
|
|
16585
|
+
for (; ; ) {
|
|
16586
|
+
const m = rest.match(DIRECTIVE_RE);
|
|
16587
|
+
if (!m) break;
|
|
16588
|
+
const g = m[1].trim();
|
|
16589
|
+
if ((groups.length || p.strict) && g && !isDirectiveGroup(g, p)) break;
|
|
16590
|
+
groups.push(g);
|
|
16591
|
+
rest = m[2];
|
|
16592
|
+
}
|
|
16593
|
+
return { groups, body: rest };
|
|
16594
|
+
}
|
|
16595
|
+
function parseDirectives(text, o) {
|
|
16596
|
+
const p = opt(o);
|
|
16597
|
+
const { groups, body } = splitDirectiveGroups(text, p);
|
|
16598
|
+
if (!groups.length) return { prompt: text, directives: {} };
|
|
16599
|
+
const prompt = body.trim();
|
|
16600
|
+
const raw = groups.filter(Boolean).join(",");
|
|
16601
|
+
if (!raw) return { prompt, directives: {} };
|
|
16602
|
+
const d = {};
|
|
16603
|
+
const out = { prompt, directives: d };
|
|
16604
|
+
const tokens = raw.split(",");
|
|
16605
|
+
const proven = tokens.some((t) => {
|
|
16606
|
+
const i = t.indexOf(":");
|
|
16607
|
+
return i !== -1 && p.keys.includes(t.slice(0, i).trim().toLowerCase());
|
|
16608
|
+
});
|
|
16609
|
+
const fail = (tok, hit) => {
|
|
16610
|
+
out.unresolvedModel ??= tok;
|
|
16611
|
+
if (hit && "ambiguous" in hit) out.ambiguous ??= hit.ambiguous;
|
|
16612
|
+
};
|
|
16613
|
+
const setModel = (val) => {
|
|
16614
|
+
const hit = resolveModelToken(val, p);
|
|
16615
|
+
if (!hit || !("model" in hit)) return hit;
|
|
16616
|
+
d.model = hit.model;
|
|
16617
|
+
if (hit.engine && !d.engine) d.engine = hit.engine;
|
|
16618
|
+
return true;
|
|
16619
|
+
};
|
|
16620
|
+
let pos = 0;
|
|
16621
|
+
for (const token of tokens) {
|
|
16622
|
+
const t = token.trim();
|
|
16623
|
+
if (!t) continue;
|
|
16624
|
+
const i = t.indexOf(":");
|
|
16625
|
+
if (i !== -1) {
|
|
16626
|
+
const key = t.slice(0, i).trim().toLowerCase(), val2 = t.slice(i + 1).trim().toLowerCase();
|
|
16627
|
+
if (!p.keys.includes(key)) {
|
|
16628
|
+
p.warn(`[directives] Unknown directive key: "${key}"`);
|
|
16629
|
+
continue;
|
|
16630
|
+
}
|
|
16631
|
+
if (key === "delegate" || key === "advisor") {
|
|
16632
|
+
const hit = resolveModelToken(val2, p);
|
|
16633
|
+
if (hit && "model" in hit) d[key] = hit.model;
|
|
16634
|
+
else if (!hit && isQualifiedModel(val2) && !p.catalogOnly) d[key] = val2;
|
|
16635
|
+
else fail(val2, hit);
|
|
16636
|
+
continue;
|
|
16637
|
+
}
|
|
16638
|
+
if (key === "model") {
|
|
16639
|
+
const r2 = setModel(val2);
|
|
16640
|
+
if (r2 === true) continue;
|
|
16641
|
+
if (!r2 && isQualifiedModel(val2) && !p.catalogOnly) {
|
|
16642
|
+
d.model = val2;
|
|
16643
|
+
continue;
|
|
16644
|
+
}
|
|
16645
|
+
fail(val2, r2);
|
|
16646
|
+
} else applyKey(d, key, val2, p.warn);
|
|
16647
|
+
continue;
|
|
16648
|
+
}
|
|
16649
|
+
const val = t.toLowerCase();
|
|
16650
|
+
const pair = splitModePair(val);
|
|
16651
|
+
if (pair && p.keys.includes(pair.op === "->" ? "delegate" : "advisor")) {
|
|
16652
|
+
const lhit = pair.left ? resolveModelToken(pair.left, p) : null, rhit = resolveModelToken(pair.right, p);
|
|
16653
|
+
const lok = !pair.left || !!lhit && "model" in lhit, rok = !!rhit && "model" in rhit;
|
|
16654
|
+
if (lok && rok) {
|
|
16655
|
+
if (pair.left) setModel(pair.left);
|
|
16656
|
+
if (pair.op === "->") d.delegate = rhit.model;
|
|
16657
|
+
else d.advisor = rhit.model;
|
|
16658
|
+
} else if (lok || rok) {
|
|
16659
|
+
if (!lok) fail(pair.left, lhit);
|
|
16660
|
+
else fail(pair.right, rhit);
|
|
16661
|
+
} else p.warn(`[directives] Unknown models in "${t}"`);
|
|
16662
|
+
pos++;
|
|
16663
|
+
continue;
|
|
16664
|
+
}
|
|
16665
|
+
if (val === "off" && p.keys.includes("mode")) {
|
|
16666
|
+
d.modeOff = true;
|
|
16667
|
+
pos++;
|
|
16668
|
+
continue;
|
|
16669
|
+
}
|
|
16670
|
+
const r = pos === 0 ? setModel(val) : null;
|
|
16671
|
+
if (r === true) {
|
|
16672
|
+
} else if (pos <= 1 && /^\d+$/.test(val) && p.keys.includes("turns")) d.turns = parseInt(val, 10);
|
|
16673
|
+
else if (["think", "adaptive"].includes(val) && p.keys.includes("thinking")) d.thinking = "adaptive";
|
|
16674
|
+
else if (["nothink", "nothinking"].includes(val) && p.keys.includes("thinking")) d.thinking = "disabled";
|
|
16675
|
+
else if (pos === 0) {
|
|
16676
|
+
if (r || p.catalogOnly && p.aliases[val] || proven && !isQualifiedModel(val)) fail(t, r);
|
|
16677
|
+
p.warn(`[directives] Unknown model: "${t}"`);
|
|
16678
|
+
}
|
|
16679
|
+
pos++;
|
|
16680
|
+
}
|
|
16681
|
+
return out;
|
|
16682
|
+
}
|
|
16683
|
+
function applyKey(d, key, val, warn) {
|
|
16684
|
+
switch (key) {
|
|
16685
|
+
case "turns":
|
|
16686
|
+
if (/^\d+$/.test(val)) d.turns = parseInt(val, 10);
|
|
16687
|
+
else warn(`[directives] Invalid turns value: "${val}"`);
|
|
16688
|
+
break;
|
|
16689
|
+
case "thinking":
|
|
16690
|
+
case "think":
|
|
16691
|
+
if (["adaptive", "enabled", "disabled"].includes(val)) d.thinking = val;
|
|
16692
|
+
else warn(`[directives] Invalid thinking value: "${val}"`);
|
|
16693
|
+
break;
|
|
16694
|
+
case "effort":
|
|
16695
|
+
if (["low", "medium", "high", "max"].includes(val)) d.effort = val;
|
|
16696
|
+
else warn(`[directives] Invalid effort value: "${val}"`);
|
|
16697
|
+
break;
|
|
16698
|
+
case "engine":
|
|
16699
|
+
d.engine = val;
|
|
16700
|
+
break;
|
|
16701
|
+
case "mode":
|
|
16702
|
+
if (val === "off") d.modeOff = true;
|
|
16703
|
+
else warn(`[directives] Invalid mode value: "${val}"`);
|
|
16704
|
+
break;
|
|
16705
|
+
case "resume":
|
|
16706
|
+
if (["on", "true", "1"].includes(val)) d.resume = true;
|
|
16707
|
+
else if (["off", "false", "0"].includes(val)) d.resume = false;
|
|
16708
|
+
else warn(`[directives] Invalid resume value: "${val}"`);
|
|
16709
|
+
break;
|
|
16710
|
+
}
|
|
16711
|
+
}
|
|
16712
|
+
|
|
16298
16713
|
// cli/checkpoints.ts
|
|
16299
16714
|
var MUTATORS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "ApplyEdits"]);
|
|
16300
16715
|
function mutatedPaths(call) {
|
|
@@ -16398,7 +16813,7 @@ import { promisify } from "util";
|
|
|
16398
16813
|
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync9 } from "fs";
|
|
16399
16814
|
import { homedir as homedir7 } from "os";
|
|
16400
16815
|
import { join as join12, resolve as resolve4, sep as sep3, parse as parsePath } from "path";
|
|
16401
|
-
var
|
|
16816
|
+
var log38 = forComponent("checkpoints");
|
|
16402
16817
|
var exec = promisify(execFile4);
|
|
16403
16818
|
var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
|
|
16404
16819
|
function isBroadRoot(p) {
|
|
@@ -16446,7 +16861,7 @@ var ShadowRepo = class {
|
|
|
16446
16861
|
writeFileSync6(join12(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
|
|
16447
16862
|
this.ready = true;
|
|
16448
16863
|
} catch (e) {
|
|
16449
|
-
|
|
16864
|
+
log38.debug(`git checkpoints unavailable for ${this.workTree}`, e);
|
|
16450
16865
|
this.ready = false;
|
|
16451
16866
|
}
|
|
16452
16867
|
return this.ready;
|
|
@@ -16457,7 +16872,7 @@ var ShadowRepo = class {
|
|
|
16457
16872
|
}
|
|
16458
16873
|
async commit(label, forced = []) {
|
|
16459
16874
|
await this.run("add", "-A");
|
|
16460
|
-
for (const p of forced) await this.run("add", "-f", "--", p).catch((e) =>
|
|
16875
|
+
for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log38.debug(`force-add failed: ${p}`, e));
|
|
16461
16876
|
await this.run("commit", "--allow-empty", "-q", "-m", label);
|
|
16462
16877
|
}
|
|
16463
16878
|
/** Inject the CURRENT (pre-edit) content of `paths` into the turn-open restore point by amending it.
|
|
@@ -16465,8 +16880,8 @@ var ShadowRepo = class {
|
|
|
16465
16880
|
* turn-boundary `add -A` would never have captured it. Amend (vs a new commit) keeps one restore
|
|
16466
16881
|
* point per turn, so the REPL's turn↔frame mapping stays intact. */
|
|
16467
16882
|
async amendForced(paths) {
|
|
16468
|
-
for (const p of paths) await this.run("add", "-f", "--", p).catch((e) =>
|
|
16469
|
-
await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) =>
|
|
16883
|
+
for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log38.debug(`force-capture failed: ${p}`, e));
|
|
16884
|
+
await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log38.debug("amend failed", e));
|
|
16470
16885
|
}
|
|
16471
16886
|
/** Commits on `ref`, oldest-first (canonical index space). */
|
|
16472
16887
|
async log(ref) {
|
|
@@ -16526,7 +16941,7 @@ var ShadowRepo = class {
|
|
|
16526
16941
|
await this.run("gc", "--auto", "-q").catch(() => {
|
|
16527
16942
|
});
|
|
16528
16943
|
} catch (e) {
|
|
16529
|
-
|
|
16944
|
+
log38.debug("checkpoint prune failed", e);
|
|
16530
16945
|
}
|
|
16531
16946
|
}
|
|
16532
16947
|
};
|
|
@@ -16594,7 +17009,7 @@ var GitCheckpoints = class {
|
|
|
16594
17009
|
use(sessionId) {
|
|
16595
17010
|
if (sessionId === this.session) return;
|
|
16596
17011
|
this.session = sessionId;
|
|
16597
|
-
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) =>
|
|
17012
|
+
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log38.debug("re-point failed", e));
|
|
16598
17013
|
}
|
|
16599
17014
|
async begin(label) {
|
|
16600
17015
|
if (!await this.start()) return;
|
|
@@ -16614,7 +17029,7 @@ var GitCheckpoints = class {
|
|
|
16614
17029
|
process.stderr.write(`\x1B[2m checkpoints: '${this.repos[i].root}' too large to snapshot in ${Math.round(this.options.opTimeoutMs / 1e3)}s \u2014 file rewind disabled here\x1B[0m
|
|
16615
17030
|
`);
|
|
16616
17031
|
} else {
|
|
16617
|
-
|
|
17032
|
+
log38.debug("checkpoint commit failed", e);
|
|
16618
17033
|
survivors.push(this.repos[i]);
|
|
16619
17034
|
survivorCaches.push(this.caches[i] ?? []);
|
|
16620
17035
|
}
|
|
@@ -16649,7 +17064,7 @@ var GitCheckpoints = class {
|
|
|
16649
17064
|
if (this.forced.has(abs)) continue;
|
|
16650
17065
|
this.forced.add(abs);
|
|
16651
17066
|
const i = this.repoIndexFor(abs);
|
|
16652
|
-
if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) =>
|
|
17067
|
+
if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log38.debug("amendForced failed", e));
|
|
16653
17068
|
}
|
|
16654
17069
|
}
|
|
16655
17070
|
};
|
|
@@ -17760,7 +18175,7 @@ function applyVimNormal(s, key, str) {
|
|
|
17760
18175
|
}
|
|
17761
18176
|
return "none";
|
|
17762
18177
|
}
|
|
17763
|
-
function
|
|
18178
|
+
function applyKey2(s, key, str) {
|
|
17764
18179
|
const k = key?.name;
|
|
17765
18180
|
if (s.vim === "normal" && !s.pasting && !s.searching) {
|
|
17766
18181
|
const r = applyVimNormal(s, key ?? {}, str);
|
|
@@ -18088,7 +18503,7 @@ function createLineEditor(out, input = keyInput) {
|
|
|
18088
18503
|
});
|
|
18089
18504
|
return;
|
|
18090
18505
|
}
|
|
18091
|
-
const action =
|
|
18506
|
+
const action = applyKey2(s, key ?? {}, str);
|
|
18092
18507
|
if (action === "submit") {
|
|
18093
18508
|
finish();
|
|
18094
18509
|
opts.onSubmit?.();
|
|
@@ -18434,7 +18849,7 @@ import { spawnSync as spawnSync6 } from "child_process";
|
|
|
18434
18849
|
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync7 } from "fs";
|
|
18435
18850
|
import { homedir as homedir8 } from "os";
|
|
18436
18851
|
import { join as join14 } from "path";
|
|
18437
|
-
var
|
|
18852
|
+
var log39 = forComponent("os-sched");
|
|
18438
18853
|
var OsScheduler = class {
|
|
18439
18854
|
options;
|
|
18440
18855
|
constructor(options) {
|
|
@@ -18496,7 +18911,7 @@ var OsScheduler = class {
|
|
|
18496
18911
|
}
|
|
18497
18912
|
}
|
|
18498
18913
|
} catch (e) {
|
|
18499
|
-
|
|
18914
|
+
log39.debug(`cancel ${id}`, e);
|
|
18500
18915
|
}
|
|
18501
18916
|
for (const f of [`${id}.json`, `${id}.sh`]) {
|
|
18502
18917
|
try {
|
|
@@ -18614,7 +19029,7 @@ import { existsSync as existsSync8, mkdirSync as mkdirSync11, unlinkSync as unli
|
|
|
18614
19029
|
import { homedir as homedir9 } from "os";
|
|
18615
19030
|
import { join as join15 } from "path";
|
|
18616
19031
|
init_platform();
|
|
18617
|
-
var
|
|
19032
|
+
var log40 = forComponent("remote-trigger");
|
|
18618
19033
|
var TRIGGER_DIR = () => join15(homedir9(), ".agent", "triggers");
|
|
18619
19034
|
var sockPath = (sessionId, dir = TRIGGER_DIR()) => join15(dir, `${sessionId}.sock`);
|
|
18620
19035
|
var TriggerServer = class {
|
|
@@ -18652,13 +19067,13 @@ var TriggerServer = class {
|
|
|
18652
19067
|
conn.end(JSON.stringify({ ok: false, error: String(e) }) + "\n");
|
|
18653
19068
|
}
|
|
18654
19069
|
});
|
|
18655
|
-
conn.on("error", (e) =>
|
|
19070
|
+
conn.on("error", (e) => log40.debug("trigger conn error", e));
|
|
18656
19071
|
});
|
|
18657
|
-
this.server.on("error", (e) =>
|
|
19072
|
+
this.server.on("error", (e) => log40.debug("trigger server error", e));
|
|
18658
19073
|
this.server.listen(p);
|
|
18659
19074
|
this.path = p;
|
|
18660
19075
|
} catch (e) {
|
|
18661
|
-
|
|
19076
|
+
log40.debug("trigger server unavailable", e);
|
|
18662
19077
|
}
|
|
18663
19078
|
}
|
|
18664
19079
|
/** Re-bind on /resume (the session id changed). */
|
|
@@ -18790,7 +19205,7 @@ var strike = C("9");
|
|
|
18790
19205
|
var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
|
|
18791
19206
|
var mdPalette = { bold, dim, cyan, italic, strike, green, link };
|
|
18792
19207
|
var err = (s) => process.stderr.write(s);
|
|
18793
|
-
var
|
|
19208
|
+
var log41 = forComponent("cli");
|
|
18794
19209
|
var VERSION = (() => {
|
|
18795
19210
|
try {
|
|
18796
19211
|
return JSON.parse(readFileSync10(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
|
|
@@ -18930,7 +19345,13 @@ function parseArgs(argv) {
|
|
|
18930
19345
|
a.harden = true;
|
|
18931
19346
|
a.hardenNet = true;
|
|
18932
19347
|
} else if (x === "--subagents") a.subagents = true;
|
|
18933
|
-
else if (x === "--
|
|
19348
|
+
else if (x === "--delegate") a.delegate = argv[++i];
|
|
19349
|
+
else if (x === "--advisor") a.advisor = argv[++i];
|
|
19350
|
+
else if (x === "--mode-scope") {
|
|
19351
|
+
const v = argv[++i];
|
|
19352
|
+
if (v !== "sticky" && v !== "message") throw new Error("--mode-scope must be sticky|message");
|
|
19353
|
+
a.modeScope = v;
|
|
19354
|
+
} else if (x === "--duplex") a.duplex = true;
|
|
18934
19355
|
else if (x === "--duplex-v2") {
|
|
18935
19356
|
a.duplex = true;
|
|
18936
19357
|
a.duplexV2 = true;
|
|
@@ -19064,6 +19485,9 @@ Flags:
|
|
|
19064
19485
|
--add-dir <path> mount another directory into the workspace (repeatable; disk mode only)
|
|
19065
19486
|
--mcp-config <file> JSON { mcpServers?, permissions? } merged over .agent config for this run only
|
|
19066
19487
|
--subagents allow the Task tool (spawn child agents)
|
|
19488
|
+
--delegate <worker> delegate mode: the main model plans (its writes are refused), <worker> writes via Task
|
|
19489
|
+
--advisor <model> advisor mode: the main model works; an Advise tool consults <model> (first write waits for it)
|
|
19490
|
+
--mode-scope <s> sticky (default): a [a->b] / [a<-b] prompt prefix holds for later turns (and -r resumes) until [off]; message: this turn only
|
|
19067
19491
|
--reasoning <e> extended thinking: off|low|medium|high or a token budget (anthropic/openai)
|
|
19068
19492
|
--session-id <id> use this id for the session (instead of an auto-generated one)
|
|
19069
19493
|
--fork-session with -r/-c: branch the resumed session into a new id (source left untouched)
|
|
@@ -19673,7 +20097,7 @@ function turnCost(model, usage) {
|
|
|
19673
20097
|
const priced = model.startsWith("claude-code/") ? `anthropic/${resolveModelAlias(model.slice("claude-code/".length))}` : model;
|
|
19674
20098
|
return costOf(getModelInfo(priced)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, priced);
|
|
19675
20099
|
}
|
|
19676
|
-
async function evaluateGoal(ai, condition, transcript,
|
|
20100
|
+
async function evaluateGoal(ai, condition, transcript, log42) {
|
|
19677
20101
|
const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
|
|
19678
20102
|
const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
|
|
19679
20103
|
return text.slice(0, 600);
|
|
@@ -19693,7 +20117,7 @@ ${recent}` }
|
|
|
19693
20117
|
const match = r.content.match(/\{[\s\S]*\}/);
|
|
19694
20118
|
if (match) return JSON.parse(match[0]);
|
|
19695
20119
|
} catch (e) {
|
|
19696
|
-
|
|
20120
|
+
log42(dim(` (goal evaluator error: ${e?.message ?? e})
|
|
19697
20121
|
`));
|
|
19698
20122
|
}
|
|
19699
20123
|
return { met: false, reason: "evaluation unclear" };
|
|
@@ -19920,7 +20344,7 @@ function mcpAgentTools(mounted, opts) {
|
|
|
19920
20344
|
return tools;
|
|
19921
20345
|
}
|
|
19922
20346
|
async function closeMcp(mounted) {
|
|
19923
|
-
await Promise.all(mounted.map((m) => m.client.close().catch((e) =>
|
|
20347
|
+
await Promise.all(mounted.map((m) => m.client.close().catch((e) => log41.debug("mcp close failed", e))));
|
|
19924
20348
|
}
|
|
19925
20349
|
var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
|
|
19926
20350
|
function mentionRefs(line) {
|
|
@@ -19979,7 +20403,7 @@ async function expandMentions(fs, line) {
|
|
|
19979
20403
|
if (loaded.includes(ref) || missing.includes(ref)) continue;
|
|
19980
20404
|
if (ref.includes(":") && mcpMentionResolver) {
|
|
19981
20405
|
const body = await mcpMentionResolver(ref).catch((e) => {
|
|
19982
|
-
|
|
20406
|
+
log41.debug("mcp mention resolve failed", e);
|
|
19983
20407
|
return null;
|
|
19984
20408
|
});
|
|
19985
20409
|
if (body != null) {
|
|
@@ -20004,6 +20428,33 @@ ${await fs.readFile(path)}`);
|
|
|
20004
20428
|
|
|
20005
20429
|
${blocks.join("\n\n")}` : line, loaded, missing };
|
|
20006
20430
|
}
|
|
20431
|
+
function applyTurnMode(agent, session, task, flags = {}, onModel) {
|
|
20432
|
+
const p = parseDirectives(task, { strict: true, keys: ["model", "turns", "delegate", "advisor", "mode"], warn: (m) => log41.debug(m) });
|
|
20433
|
+
const d = p.directives, isMode = !!(d.delegate || d.advisor || d.modeOff);
|
|
20434
|
+
if (p.unresolvedModel && /->|<-|delegate:|advisor:/.test(task.slice(0, task.length - p.prompt.length))) {
|
|
20435
|
+
err(red(` \u2717 unknown model "${p.unresolvedModel}"${p.ambiguous ? ` (could be: ${p.ambiguous.join(", ")})` : ""} \u2014 turn not sent
|
|
20436
|
+
`));
|
|
20437
|
+
return null;
|
|
20438
|
+
}
|
|
20439
|
+
const r = (m) => m ? resolveModelOrNewest(m) : void 0;
|
|
20440
|
+
const fromDirective = isMode ? d.modeOff ? {} : { model: r(d.model), delegate: r(d.delegate), advisor: r(d.advisor) } : void 0;
|
|
20441
|
+
if (fromDirective && flags.modeScope !== "message") session.meta.mode = fromDirective;
|
|
20442
|
+
const mode = fromDirective ?? session.meta.mode ?? { delegate: flags.delegate && r(flags.delegate), advisor: flags.advisor && r(flags.advisor) };
|
|
20443
|
+
const o = agent.options, before = JSON.stringify([o.model, o.delegate, o.advisor]);
|
|
20444
|
+
if (mode.model && mode.model !== o.model) {
|
|
20445
|
+
o.model = mode.model;
|
|
20446
|
+
o.contextWindow = getModelInfo(mode.model)?.contextWindow ?? 0;
|
|
20447
|
+
onModel?.(mode.model);
|
|
20448
|
+
}
|
|
20449
|
+
o.delegate = mode.delegate ? { worker: mode.delegate } : void 0;
|
|
20450
|
+
o.advisor = mode.advisor ? { ...o.advisor, model: mode.advisor } : void 0;
|
|
20451
|
+
if (JSON.stringify([o.model, o.delegate, o.advisor]) !== before) {
|
|
20452
|
+
agent.reprepare();
|
|
20453
|
+
err(dim(` \u21C4 ${o.delegate ? `delegate: ${o.model} plans \u2192 ${o.delegate.worker} writes` : o.advisor ? `advisor: ${o.model} works \u2190 ${o.advisor.model} advises` : "single-model mode"}
|
|
20454
|
+
`));
|
|
20455
|
+
}
|
|
20456
|
+
return isMode ? p.prompt : task;
|
|
20457
|
+
}
|
|
20007
20458
|
function jsonResult(res, session) {
|
|
20008
20459
|
const lastUser = res.messages.map((m) => m.role).lastIndexOf("user");
|
|
20009
20460
|
return {
|
|
@@ -20015,6 +20466,7 @@ function jsonResult(res, session) {
|
|
|
20015
20466
|
tools: res.messages.slice(lastUser).filter((m) => m.role === "tool").length,
|
|
20016
20467
|
usage: res.usage,
|
|
20017
20468
|
...res.budget ? { budget: res.budget } : {},
|
|
20469
|
+
...res.workerUsage ? { workerModel: res.workerModel, workerUsage: res.workerUsage } : {},
|
|
20018
20470
|
...res.routing ? { costUsd: res.costUsd, routing: res.routing } : {},
|
|
20019
20471
|
// cast: tsup dts hoists a modelRouter import above the shebang
|
|
20020
20472
|
sessionId: session.meta.id,
|
|
@@ -20070,7 +20522,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
20070
20522
|
try {
|
|
20071
20523
|
store.save(session);
|
|
20072
20524
|
} catch (ex) {
|
|
20073
|
-
|
|
20525
|
+
log41.debug("mid-turn session flush failed", ex);
|
|
20074
20526
|
}
|
|
20075
20527
|
}
|
|
20076
20528
|
return origNotify(e);
|
|
@@ -20099,7 +20551,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
20099
20551
|
agent.options.host?.flushText?.();
|
|
20100
20552
|
flushLookupGroup();
|
|
20101
20553
|
const secs2 = ((Date.now() - t0) / 1e3).toFixed(1);
|
|
20102
|
-
const cost = res.costUsd ?? turnCost(agent.options.model, res.usage);
|
|
20554
|
+
const cost = (res.costUsd ?? turnCost(agent.options.model, res.usage)) + (res.workerUsage && res.workerModel ? turnCost(res.workerModel, res.workerUsage) : 0);
|
|
20103
20555
|
const m = res.usageEstimated ? "~" : "";
|
|
20104
20556
|
const tok = res.usage?.totalTokens ? `${m}${(res.usage.totalTokens / 1e3).toFixed(1)}k tok \xB7 ${cost > 0 ? m + fmtUsd(cost) + " \xB7 " : ""}` : "";
|
|
20105
20557
|
agent.options.host?.takeToolCount?.();
|
|
@@ -20234,14 +20686,14 @@ var isCancelTeardown = (e) => {
|
|
|
20234
20686
|
function installCancelGuards(mounted) {
|
|
20235
20687
|
process.on("unhandledRejection", (e) => {
|
|
20236
20688
|
if (isCancelTeardown(e)) {
|
|
20237
|
-
|
|
20689
|
+
log41.debug("suppressed unhandledRejection (cursor stream cancel)", e);
|
|
20238
20690
|
return;
|
|
20239
20691
|
}
|
|
20240
|
-
|
|
20692
|
+
log41.error("unhandledRejection", e);
|
|
20241
20693
|
});
|
|
20242
20694
|
process.on("uncaughtException", (e) => {
|
|
20243
20695
|
if (isCancelTeardown(e)) {
|
|
20244
|
-
|
|
20696
|
+
log41.debug("suppressed uncaughtException (cursor stream cancel)", e);
|
|
20245
20697
|
return;
|
|
20246
20698
|
}
|
|
20247
20699
|
console.error(e);
|
|
@@ -20610,7 +21062,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
20610
21062
|
editorRef?.abort();
|
|
20611
21063
|
}
|
|
20612
21064
|
} catch (e) {
|
|
20613
|
-
|
|
21065
|
+
log41.error("call turn failed", e);
|
|
20614
21066
|
} finally {
|
|
20615
21067
|
rec.running = false;
|
|
20616
21068
|
call?.main({ type: "done", id });
|
|
@@ -20742,7 +21194,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
20742
21194
|
try {
|
|
20743
21195
|
mkdirSync12(dir, { recursive: true });
|
|
20744
21196
|
} catch (e) {
|
|
20745
|
-
|
|
21197
|
+
log41.debug("paste image dir create failed", e);
|
|
20746
21198
|
}
|
|
20747
21199
|
const img = grabClipboardImage(dir, String(Date.now()));
|
|
20748
21200
|
return img ? { display: "Image", ref: "@" + img.path, path: img.path } : null;
|
|
@@ -20855,7 +21307,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
20855
21307
|
mkdirSync12(join16(cwd, ".agent"), { recursive: true });
|
|
20856
21308
|
appendFileSync(histPath, line + "\n");
|
|
20857
21309
|
} catch (e) {
|
|
20858
|
-
|
|
21310
|
+
log41.debug("history write failed", e);
|
|
20859
21311
|
}
|
|
20860
21312
|
};
|
|
20861
21313
|
const ago = (t) => {
|
|
@@ -20927,7 +21379,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
20927
21379
|
try {
|
|
20928
21380
|
store.save(session);
|
|
20929
21381
|
} catch (e) {
|
|
20930
|
-
|
|
21382
|
+
log41.debug("session save after rewind failed", e);
|
|
20931
21383
|
}
|
|
20932
21384
|
err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
|
|
20933
21385
|
`));
|
|
@@ -20962,13 +21414,20 @@ ${task}`;
|
|
|
20962
21414
|
bangContext.length = 0;
|
|
20963
21415
|
}
|
|
20964
21416
|
const delta = await refreshCatalogs().catch((e) => {
|
|
20965
|
-
|
|
21417
|
+
log41.debug("catalog refresh failed", e);
|
|
20966
21418
|
return "";
|
|
20967
21419
|
});
|
|
20968
21420
|
if (delta) {
|
|
20969
21421
|
err(dim(" \u27F3 skill/command catalog changed \u2014 delta attached to this turn\n"));
|
|
20970
21422
|
task += "\n\n" + delta;
|
|
20971
21423
|
}
|
|
21424
|
+
if (!duplex) {
|
|
21425
|
+
const t = applyTurnMode(face, session, task, args, (m) => {
|
|
21426
|
+
face.options.providerOptions = providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name));
|
|
21427
|
+
});
|
|
21428
|
+
if (t === null) return { ok: false };
|
|
21429
|
+
task = t;
|
|
21430
|
+
}
|
|
20972
21431
|
const r = await runTurn(face, store, session, task, duplex ? void 0 : checkpoints, cwd, sendVia ? (c) => sendVia(c, opts) : routeVia);
|
|
20973
21432
|
if (voiceIO) {
|
|
20974
21433
|
voiceReasonEnd();
|
|
@@ -21225,7 +21684,7 @@ ${task}`;
|
|
|
21225
21684
|
desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
|
|
21226
21685
|
run: async () => {
|
|
21227
21686
|
await refreshCatalogs().catch((e) => {
|
|
21228
|
-
|
|
21687
|
+
log41.debug("catalog refresh failed", e);
|
|
21229
21688
|
});
|
|
21230
21689
|
face.reprepare();
|
|
21231
21690
|
err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
|
|
@@ -21728,7 +22187,7 @@ ${task}`;
|
|
|
21728
22187
|
try {
|
|
21729
22188
|
for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
|
|
21730
22189
|
} catch (e) {
|
|
21731
|
-
|
|
22190
|
+
log41.debug(`loadAgents(${d}) failed`, e);
|
|
21732
22191
|
}
|
|
21733
22192
|
}
|
|
21734
22193
|
if (!seen.size) {
|
|
@@ -21816,7 +22275,7 @@ ${task}`;
|
|
|
21816
22275
|
}
|
|
21817
22276
|
if (idx >= 0) {
|
|
21818
22277
|
const old = mounted.splice(idx, 1)[0];
|
|
21819
|
-
await old.client.close().catch((e) =>
|
|
22278
|
+
await old.client.close().catch((e) => log41.debug("mcp close failed", e));
|
|
21820
22279
|
}
|
|
21821
22280
|
try {
|
|
21822
22281
|
const m = await mountMcpServer(name, conf);
|
|
@@ -21844,7 +22303,7 @@ ${task}`;
|
|
|
21844
22303
|
}
|
|
21845
22304
|
const m = mounted.splice(idx, 1)[0];
|
|
21846
22305
|
remountMcpTools();
|
|
21847
|
-
await m.client.close().catch((e) =>
|
|
22306
|
+
await m.client.close().catch((e) => log41.debug("mcp close failed", e));
|
|
21848
22307
|
err(dim(` removed "${name}"
|
|
21849
22308
|
`));
|
|
21850
22309
|
return;
|
|
@@ -22047,7 +22506,7 @@ ${task}`;
|
|
|
22047
22506
|
try {
|
|
22048
22507
|
return readdirSync4(join16(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
|
|
22049
22508
|
} catch (e) {
|
|
22050
|
-
|
|
22509
|
+
log41.debug("completion readdir failed", absDir, e);
|
|
22051
22510
|
return null;
|
|
22052
22511
|
}
|
|
22053
22512
|
};
|
|
@@ -22107,7 +22566,7 @@ ${task}`;
|
|
|
22107
22566
|
}
|
|
22108
22567
|
return;
|
|
22109
22568
|
}
|
|
22110
|
-
const action =
|
|
22569
|
+
const action = applyKey2(sEd, key ?? {}, _s);
|
|
22111
22570
|
if (action === "submit") {
|
|
22112
22571
|
const text = sEd.expand().trim();
|
|
22113
22572
|
sEd.reset();
|
|
@@ -22184,7 +22643,7 @@ ${out}
|
|
|
22184
22643
|
return;
|
|
22185
22644
|
}
|
|
22186
22645
|
await refreshCatalogs().catch((e) => {
|
|
22187
|
-
|
|
22646
|
+
log41.debug("catalog refresh failed", e);
|
|
22188
22647
|
});
|
|
22189
22648
|
const sk = skills.find((x) => x.name === name);
|
|
22190
22649
|
if (sk) {
|
|
@@ -22309,7 +22768,7 @@ ${out}
|
|
|
22309
22768
|
if (delivery || cut) dx?.parkCutDelivery(delivery, !!cut, heard);
|
|
22310
22769
|
const cutReply = cut && cut.full.length - cut.heard.length > 40 ? cut : null;
|
|
22311
22770
|
const noteP = replaces ? replaces.note : !cutReply ? Promise.resolve("") : (instinct ? instinct.bargeIntent(text).catch((e) => {
|
|
22312
|
-
|
|
22771
|
+
log41.warn(`barge intent failed \u2014 model decides: ${e.message}`);
|
|
22313
22772
|
return void 0;
|
|
22314
22773
|
}) : Promise.resolve(void 0)).then(async (intent) => interruptionNote(cutReply, intent) + ackedNote(await instinct?.ackFor(text)));
|
|
22315
22774
|
const cutIn = replaces ? replaces.cutIn : interjectNote;
|
|
@@ -22688,7 +23147,7 @@ function channelOptions(ai, model, gender) {
|
|
|
22688
23147
|
// Anticipated backchannels: on in chatty mode (its preset), else off until the offline replay proves them (tests/voice-bc-anticipate-eval.ts)
|
|
22689
23148
|
plan: (c, s) => planner.plan(c, s),
|
|
22690
23149
|
...process.env.AGENTX_CHANNEL_ANTICIPATE === "1" ? { anticipate: true } : {},
|
|
22691
|
-
onEvent: (e) =>
|
|
23150
|
+
onEvent: (e) => log41.debug(`channel ${e.trigger}: rule=${e.rule} choice=${e.choice ?? "-"} \u2192 ${e.acted}${e.text ? ` "${e.text}"` : ""}${e.cue ? " (planned cue)" : ""} ${e.ms}ms`)
|
|
22692
23151
|
};
|
|
22693
23152
|
}
|
|
22694
23153
|
function overlapResponder(ai, mode, out, aside) {
|
|
@@ -22759,7 +23218,7 @@ async function duplexServe(args, ai, cfg, cwd) {
|
|
|
22759
23218
|
try {
|
|
22760
23219
|
store.save(session);
|
|
22761
23220
|
} catch (e) {
|
|
22762
|
-
|
|
23221
|
+
log41.warn("duplex session save failed", e);
|
|
22763
23222
|
}
|
|
22764
23223
|
};
|
|
22765
23224
|
const overlap2 = overlapResponder(ai, args.duplexV2 ? args.channelMode : void 0, out);
|
|
@@ -22799,7 +23258,7 @@ async function duplexServe(args, ai, cfg, cwd) {
|
|
|
22799
23258
|
done2();
|
|
22800
23259
|
out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error", ...why ? { error: why } : {} });
|
|
22801
23260
|
}).catch((e) => {
|
|
22802
|
-
|
|
23261
|
+
log41.error("duplex turn failed", e);
|
|
22803
23262
|
done2();
|
|
22804
23263
|
out({ type: "turn_done", id, finishReason: "error", error: String(e?.message ?? e) });
|
|
22805
23264
|
});
|
|
@@ -23054,7 +23513,12 @@ async function main() {
|
|
|
23054
23513
|
const session = startSession(args, store, agent, cwd);
|
|
23055
23514
|
process.once("SIGINT", () => activeTurn?.abort());
|
|
23056
23515
|
steers?.steer((text) => agent.inject(text));
|
|
23057
|
-
const turn =
|
|
23516
|
+
const turn = async (task) => {
|
|
23517
|
+
const t = applyTurnMode(agent, session, task, args, (m) => {
|
|
23518
|
+
agent.options.providerOptions = providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name));
|
|
23519
|
+
});
|
|
23520
|
+
return t === null ? { ok: false } : runTurn(agent, store, session, t, void 0, cwd, autoRouteSend(args, agent, () => session, (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name))));
|
|
23521
|
+
};
|
|
23058
23522
|
if (warmBoot && steers) {
|
|
23059
23523
|
bindClaudeCodeSession(agent, session);
|
|
23060
23524
|
await agent.prewarm().catch((e) => err(dim(` (prewarm failed: ${e?.message ?? e})
|
|
@@ -23116,6 +23580,7 @@ export {
|
|
|
23116
23580
|
abortActiveTurn,
|
|
23117
23581
|
apiKeysFromEnv,
|
|
23118
23582
|
appendMemoryNote,
|
|
23583
|
+
applyTurnMode,
|
|
23119
23584
|
cacheMultipliers,
|
|
23120
23585
|
condenseReplay,
|
|
23121
23586
|
costOf,
|