@livx.cc/agentx 0.99.121 → 0.99.123
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 +30 -3
- package/dist/cli.js +703 -246
- 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 +8 -5
- package/dist/index.js +370 -150
- package/dist/index.js.map +1 -1
- package/dist/{mcp-CUo-9LUe.d.ts → mcp-DZhcLur2.d.ts} +1 -1
- package/dist/mcp.client.d.ts +2 -2
- package/dist/{secondChannel-D33Wvruo.d.ts → secondChannel-DES_VIeN.d.ts} +125 -2
- package/dist/{tools-DZHZIyxh.d.ts → tools-DwfeF8VQ.d.ts} +3 -0
- package/dist/tools.shell.d.ts +1 -1
- package/dist/tools.shell.js +1 -0
- package/dist/tools.shell.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -825,6 +825,7 @@ var init_tools_structured = __esm({
|
|
|
825
825
|
};
|
|
826
826
|
writeTool = {
|
|
827
827
|
name: "Write",
|
|
828
|
+
writes: true,
|
|
828
829
|
description: "Create or overwrite a file with the given contents, creating parent directories as needed. Use for new files instead of `bash echo >`. To replace an EXISTING file you must Read it first \u2014 overwriting an un-Read file is refused (it would destroy unseen content).",
|
|
829
830
|
parameters: {
|
|
830
831
|
type: "object",
|
|
@@ -850,6 +851,7 @@ var init_tools_structured = __esm({
|
|
|
850
851
|
};
|
|
851
852
|
multiEditTool = {
|
|
852
853
|
name: "MultiEdit",
|
|
854
|
+
writes: true,
|
|
853
855
|
description: "Apply several exact-substring replacements to one file in order, in a single call. Requires a prior Read. Each `old_string` must be unique at the time it is applied. All-or-nothing: if any edit fails, none are written.",
|
|
854
856
|
parameters: {
|
|
855
857
|
type: "object",
|
|
@@ -891,6 +893,7 @@ var init_tools_structured = __esm({
|
|
|
891
893
|
};
|
|
892
894
|
applyEditsTool = {
|
|
893
895
|
name: "ApplyEdits",
|
|
896
|
+
writes: true,
|
|
894
897
|
description: "Apply edits across one or MORE files in a single call \u2014 for cross-file refactors (rename/extract/move). edits=[{path, old_string?, new_string}]. WITH old_string: replace that exact substring (must be UNIQUE in the file \u2014 add surrounding context; read fresh + verified, no prior Read needed). WITHOUT old_string: write new_string as the whole file (creates it + parent dirs; but to OVERWRITE a file that already exists you must Read it first, else use old_string to edit part of it). Locate sites first with Grep (its output shows the exact text). Atomic: validated across all files before any write.",
|
|
895
898
|
parameters: {
|
|
896
899
|
type: "object",
|
|
@@ -1584,6 +1587,7 @@ var init_tools = __esm({
|
|
|
1584
1587
|
};
|
|
1585
1588
|
bashTool = {
|
|
1586
1589
|
name: "bash",
|
|
1590
|
+
writes: true,
|
|
1587
1591
|
description: "Run a shell command. Supports ls, cat, grep, find, head, tail, echo, mkdir, rm, mv, cp, wc, pipes (|), redirects (>, >>), and chaining (&&, ||, ;). Best for: running tests/builds, file operations (mkdir/mv/rm), and piped workflows. For searching file contents, prefer `Grep` (structured results, no re-parse). For finding files by name, prefer `Glob`.",
|
|
1588
1592
|
parameters: {
|
|
1589
1593
|
type: "object",
|
|
@@ -1661,6 +1665,7 @@ var init_tools = __esm({
|
|
|
1661
1665
|
};
|
|
1662
1666
|
editTool = {
|
|
1663
1667
|
name: "Edit",
|
|
1668
|
+
writes: true,
|
|
1664
1669
|
description: "Replace an exact substring in a file. Requires a prior Read of the same file. `old_string` must occur exactly once \u2014 include surrounding context to disambiguate.",
|
|
1665
1670
|
parameters: {
|
|
1666
1671
|
type: "object",
|
|
@@ -2954,7 +2959,7 @@ async function psGroupCpuSeconds(pgid) {
|
|
|
2954
2959
|
try {
|
|
2955
2960
|
return await winTreeCpuSeconds(pgid);
|
|
2956
2961
|
} catch (e) {
|
|
2957
|
-
|
|
2962
|
+
log31.debug(`cpu probe failed for ${pgid}: ${e}`);
|
|
2958
2963
|
return void 0;
|
|
2959
2964
|
}
|
|
2960
2965
|
}
|
|
@@ -2963,7 +2968,7 @@ async function psGroupCpuSeconds(pgid) {
|
|
|
2963
2968
|
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
2969
|
return sumTreeCpu(out, pgid);
|
|
2965
2970
|
} catch (e) {
|
|
2966
|
-
|
|
2971
|
+
log31.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
|
|
2967
2972
|
return void 0;
|
|
2968
2973
|
}
|
|
2969
2974
|
}
|
|
@@ -2992,6 +2997,7 @@ function makeRealShellTool(options) {
|
|
|
2992
2997
|
};
|
|
2993
2998
|
return {
|
|
2994
2999
|
name: "Shell",
|
|
3000
|
+
writes: true,
|
|
2995
3001
|
// Rebind for an isolated child agent (git worktree): same policy/env/timeouts, new cwd. The
|
|
2996
3002
|
// `registry` is deliberately dropped — it is bound to the PARENT's cwd (and, in hosts like
|
|
2997
3003
|
// shraga-ee, to the parent's session), so a background job started from the child would run
|
|
@@ -3112,7 +3118,7 @@ function makeRealShellTool(options) {
|
|
|
3112
3118
|
proc.stderr?.on("data", collect);
|
|
3113
3119
|
proc.on("error", (err2) => {
|
|
3114
3120
|
if (err2?.name === "AbortError" || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean2(out)));
|
|
3115
|
-
|
|
3121
|
+
log31.debug("shell spawn error", err2);
|
|
3116
3122
|
finish(`[exit 1] ${err2?.message ?? err2}${out ? "\n" + clean2(out) : ""}`);
|
|
3117
3123
|
});
|
|
3118
3124
|
proc.on("close", (code) => {
|
|
@@ -3191,7 +3197,7 @@ ${clean2(out) || "(no output yet)"}`;
|
|
|
3191
3197
|
}
|
|
3192
3198
|
];
|
|
3193
3199
|
}
|
|
3194
|
-
var
|
|
3200
|
+
var log31, clean2, DETACHED, shellArgv, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
|
|
3195
3201
|
var init_tools_shell = __esm({
|
|
3196
3202
|
"src/tools.shell.ts"() {
|
|
3197
3203
|
"use strict";
|
|
@@ -3201,7 +3207,7 @@ var init_tools_shell = __esm({
|
|
|
3201
3207
|
init_shell_sandbox();
|
|
3202
3208
|
init_tools_jobs();
|
|
3203
3209
|
init_platform();
|
|
3204
|
-
|
|
3210
|
+
log31 = forComponent("shell");
|
|
3205
3211
|
clean2 = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
|
|
3206
3212
|
DETACHED = { stdio: ["ignore", "pipe", "pipe"], ...DETACH_OPTIONS };
|
|
3207
3213
|
shellArgv = (sh, command) => ({ bin: sh.bin, args: sh.args(command), ...sh.spawnOptions ? { spawnOptions: sh.spawnOptions } : {} });
|
|
@@ -3967,8 +3973,8 @@ function memorySearchTool(fs, dirs) {
|
|
|
3967
3973
|
return `Error: invalid regex: ${e}`;
|
|
3968
3974
|
}
|
|
3969
3975
|
} else {
|
|
3970
|
-
const
|
|
3971
|
-
matcher = (l) => l.toLowerCase().includes(
|
|
3976
|
+
const lower2 = q2.toLowerCase();
|
|
3977
|
+
matcher = (l) => l.toLowerCase().includes(lower2);
|
|
3972
3978
|
}
|
|
3973
3979
|
const loaded = [];
|
|
3974
3980
|
for (const slug2 of slugs) {
|
|
@@ -4385,7 +4391,11 @@ function childOptionsFor(opts, fs, depth, maxDepth, agentType, model, root) {
|
|
|
4385
4391
|
if (po) childOpts.providerOptions = po;
|
|
4386
4392
|
const deny = opts.permissions?.options.rules.filter((r) => r.decision === "deny") ?? [];
|
|
4387
4393
|
if (deny.length) childOpts.permissions = new PermissionPolicy({ rules: deny, default: "allow" });
|
|
4388
|
-
|
|
4394
|
+
const h = opts.hooks;
|
|
4395
|
+
if (h?.preToolUse || opts.inheritPostToolUse && h?.postToolUse) childOpts.hooks = {
|
|
4396
|
+
...h.preToolUse ? { preToolUse: h.preToolUse.bind(h) } : {},
|
|
4397
|
+
...opts.inheritPostToolUse && h.postToolUse ? { postToolUse: h.postToolUse.bind(h) } : {}
|
|
4398
|
+
};
|
|
4389
4399
|
return childOpts;
|
|
4390
4400
|
}
|
|
4391
4401
|
function makeTaskTool(opts) {
|
|
@@ -4609,6 +4619,159 @@ async function loadAgents(fs, dir) {
|
|
|
4609
4619
|
init_OverlayFilesystem();
|
|
4610
4620
|
init_logging();
|
|
4611
4621
|
|
|
4622
|
+
// src/modes.ts
|
|
4623
|
+
init_logging();
|
|
4624
|
+
var log8 = forComponent("modes");
|
|
4625
|
+
var WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "ApplyEdits"]);
|
|
4626
|
+
var PLANNER_BLOCKED = /* @__PURE__ */ new Set([...WRITE_TOOLS, "bash", "Shell"]);
|
|
4627
|
+
var lower = (xs) => new Set([...xs].map((x) => x.toLowerCase()));
|
|
4628
|
+
var PLANNER_BLOCKED_LC = lower(PLANNER_BLOCKED);
|
|
4629
|
+
var WRITE_TOOLS_LC = lower(WRITE_TOOLS);
|
|
4630
|
+
var plannerBlocks = (t) => !!t.writes || PLANNER_BLOCKED_LC.has(t.name.toLowerCase());
|
|
4631
|
+
var isFileWrite = (t) => WRITE_TOOLS_LC.has(t.name.toLowerCase());
|
|
4632
|
+
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.";
|
|
4633
|
+
var PLAN_FIRST = "Refused: check your plan with `Advise` before the first write (files, data shape, the hard parts). Then write.";
|
|
4634
|
+
var ADVISE_LIMIT = 3;
|
|
4635
|
+
var SNAPSHOT_CAP = 6e4;
|
|
4636
|
+
function pairOk(kind, main2, other) {
|
|
4637
|
+
if (other === main2) {
|
|
4638
|
+
log8.warn(`${kind} off: both sides are ${main2}`);
|
|
4639
|
+
return false;
|
|
4640
|
+
}
|
|
4641
|
+
if (main2.startsWith("cursor/") || other.startsWith("cursor/")) {
|
|
4642
|
+
log8.warn(`${kind} off: cursor models are not supported (${main2} / ${other})`);
|
|
4643
|
+
return false;
|
|
4644
|
+
}
|
|
4645
|
+
return true;
|
|
4646
|
+
}
|
|
4647
|
+
function delegateWorkerFor(planner, worker) {
|
|
4648
|
+
return worker && pairOk("delegate", planner, worker) ? worker : void 0;
|
|
4649
|
+
}
|
|
4650
|
+
function advisorFor(worker, advisor) {
|
|
4651
|
+
return advisor && pairOk("advisor", worker, advisor) ? advisor : void 0;
|
|
4652
|
+
}
|
|
4653
|
+
function delegateTools(tools, planner) {
|
|
4654
|
+
return tools.map((t) => !plannerBlocks(t) ? t : {
|
|
4655
|
+
...t,
|
|
4656
|
+
run: async (...a) => a[1]?.model === planner ? PLANNER_REFUSAL : t.run(...a)
|
|
4657
|
+
});
|
|
4658
|
+
}
|
|
4659
|
+
function delegatePrompt(worker) {
|
|
4660
|
+
return `
|
|
4661
|
+
|
|
4662
|
+
## You are the PLANNER
|
|
4663
|
+
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.`;
|
|
4664
|
+
}
|
|
4665
|
+
function advisorPrompt(advisor) {
|
|
4666
|
+
return `
|
|
4667
|
+
|
|
4668
|
+
## You have an ADVISOR
|
|
4669
|
+
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.`;
|
|
4670
|
+
}
|
|
4671
|
+
async function textOf(r) {
|
|
4672
|
+
if (r && typeof r[Symbol.asyncIterator] === "function") {
|
|
4673
|
+
let s = "";
|
|
4674
|
+
for await (const c of r) if (typeof c?.content === "string") s += c.content;
|
|
4675
|
+
return s;
|
|
4676
|
+
}
|
|
4677
|
+
return String(r?.content ?? r?.text ?? "");
|
|
4678
|
+
}
|
|
4679
|
+
function makeAdvisor(o) {
|
|
4680
|
+
let used = 0;
|
|
4681
|
+
const limit = o.limit ?? ADVISE_LIMIT;
|
|
4682
|
+
const tool = {
|
|
4683
|
+
name: "Advise",
|
|
4684
|
+
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.`,
|
|
4685
|
+
parameters: { type: "object", properties: { question: { type: "string", description: "what you want advice on, with the context it needs" } }, required: ["question"] },
|
|
4686
|
+
async run(args) {
|
|
4687
|
+
const question = String(args?.question ?? "").trim();
|
|
4688
|
+
if (!question) return "No advice: the question is empty.";
|
|
4689
|
+
if (used >= limit) return `No advice: the limit of ${limit} calls is used. Continue with your own judgment.`;
|
|
4690
|
+
used++;
|
|
4691
|
+
let state = "";
|
|
4692
|
+
if (o.snapshot) {
|
|
4693
|
+
const s = String(await Promise.resolve(o.snapshot()).catch((err2) => {
|
|
4694
|
+
log8.warn("advisor snapshot failed:", String(err2));
|
|
4695
|
+
return "";
|
|
4696
|
+
}) || "(empty)");
|
|
4697
|
+
state = `
|
|
4698
|
+
|
|
4699
|
+
## Current state
|
|
4700
|
+
${s.length > SNAPSHOT_CAP ? `${s.slice(0, SNAPSHOT_CAP)}
|
|
4701
|
+
\u2026(cut)` : s}`;
|
|
4702
|
+
}
|
|
4703
|
+
const review = used > 1 ? "" : "\n\n(When the work is complete, call Advise again for a review.)";
|
|
4704
|
+
try {
|
|
4705
|
+
const r = await o.ai.chat({
|
|
4706
|
+
model: o.advisor,
|
|
4707
|
+
messages: [
|
|
4708
|
+
{ 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." },
|
|
4709
|
+
{ role: "user", content: `## User request
|
|
4710
|
+
${o.request()}${state}
|
|
4711
|
+
|
|
4712
|
+
## Question
|
|
4713
|
+
${question}` }
|
|
4714
|
+
],
|
|
4715
|
+
maxTokens: 2e3
|
|
4716
|
+
});
|
|
4717
|
+
return ((await textOf(r)).trim() || "No advice: the advisor returned nothing.") + review;
|
|
4718
|
+
} catch (err2) {
|
|
4719
|
+
log8.warn("advisor call failed:", String(err2));
|
|
4720
|
+
return `No advice: the advisor call failed (${String(err2?.message ?? err2).slice(0, 120)}). Continue with your own judgment.`;
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
};
|
|
4724
|
+
const gate = (tools) => tools.map((t) => !isFileWrite(t) ? t : { ...t, run: async (...a) => used ? t.run(...a) : PLAN_FIRST });
|
|
4725
|
+
return { tool, gate, used: () => used };
|
|
4726
|
+
}
|
|
4727
|
+
function meterWorkers(ai, main2) {
|
|
4728
|
+
let total;
|
|
4729
|
+
let resolved;
|
|
4730
|
+
const seen = () => {
|
|
4731
|
+
resolved = ai.modelUsed ?? resolved;
|
|
4732
|
+
};
|
|
4733
|
+
const add = (u) => {
|
|
4734
|
+
if (!u) return;
|
|
4735
|
+
const t = total ??= { promptTokens: 0, completionTokens: 0, totalTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
4736
|
+
t.promptTokens += u.promptTokens ?? 0;
|
|
4737
|
+
t.completionTokens += u.completionTokens ?? 0;
|
|
4738
|
+
t.totalTokens += u.totalTokens ?? 0;
|
|
4739
|
+
t.cacheCreationTokens += u.cacheCreationTokens ?? 0;
|
|
4740
|
+
t.cacheReadTokens += u.cacheReadTokens ?? 0;
|
|
4741
|
+
};
|
|
4742
|
+
async function* tap(it) {
|
|
4743
|
+
let last;
|
|
4744
|
+
try {
|
|
4745
|
+
for await (const c of it) {
|
|
4746
|
+
if (c?.usage) last = c.usage;
|
|
4747
|
+
yield c;
|
|
4748
|
+
}
|
|
4749
|
+
} finally {
|
|
4750
|
+
add(last);
|
|
4751
|
+
seen();
|
|
4752
|
+
}
|
|
4753
|
+
}
|
|
4754
|
+
const chat = async (o) => {
|
|
4755
|
+
const r = await ai.chat(o);
|
|
4756
|
+
if (o?.model === main2) return r;
|
|
4757
|
+
if (r && typeof r[Symbol.asyncIterator] === "function") return tap(r);
|
|
4758
|
+
add(r?.usage);
|
|
4759
|
+
seen();
|
|
4760
|
+
return r;
|
|
4761
|
+
};
|
|
4762
|
+
return { ai: new Proxy(ai, { get: (t, k) => k === "chat" ? chat : Reflect.get(t, k) }), usage: () => total && { ...total }, model: () => resolved, reset: () => {
|
|
4763
|
+
total = void 0;
|
|
4764
|
+
resolved = void 0;
|
|
4765
|
+
} };
|
|
4766
|
+
}
|
|
4767
|
+
var CC_WRITE_TOOLS = ["Write", "Edit", "MultiEdit", "NotebookEdit"];
|
|
4768
|
+
function ccModeOptions(kind, hostHooks, used) {
|
|
4769
|
+
if (kind === "delegate") return { bridge: /* @__PURE__ */ new Set(["Task", "TaskBatch"]), disallow: [...CC_WRITE_TOOLS, "Bash", "Task", "Agent"] };
|
|
4770
|
+
if (kind !== "advisor") return { bridge: /* @__PURE__ */ new Set(), disallow: [] };
|
|
4771
|
+
const gate = { matcher: CC_WRITE_TOOLS.join("|"), hooks: [async () => used() ? {} : { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: PLAN_FIRST.replace("`Advise`", "`mcp__agentx__Advise`") } }] };
|
|
4772
|
+
return { bridge: /* @__PURE__ */ new Set(["Advise"]), disallow: [], hooks: { ...hostHooks, PreToolUse: [...hostHooks?.PreToolUse ?? [], gate] } };
|
|
4773
|
+
}
|
|
4774
|
+
|
|
4612
4775
|
// src/lint.ts
|
|
4613
4776
|
var CODE_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
4614
4777
|
function checkSyntax(path, content) {
|
|
@@ -4688,7 +4851,7 @@ function reasoningToChatFragment(model, effort) {
|
|
|
4688
4851
|
}
|
|
4689
4852
|
|
|
4690
4853
|
// src/Agent.ts
|
|
4691
|
-
var
|
|
4854
|
+
var log9 = forComponent("Agent");
|
|
4692
4855
|
function envInt(key, fallback) {
|
|
4693
4856
|
const n = typeof process !== "undefined" ? Number(process.env?.[key]) : NaN;
|
|
4694
4857
|
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
@@ -4817,6 +4980,13 @@ var AgentOptions = class {
|
|
|
4817
4980
|
hooks;
|
|
4818
4981
|
/** If true: add the `Task` tool so the agent can spawn depth-limited child agents over the VFS. */
|
|
4819
4982
|
subagents = false;
|
|
4983
|
+
/** DELEGATE MODE: `model` plans, `worker` writes. The planner's Write/Edit/MultiEdit/ApplyEdits/bash/Shell are
|
|
4984
|
+
* refused at execution; Task/TaskBatch are on (maxDepth 1, children default to `worker`) and children get the
|
|
4985
|
+
* parent's `postToolUse` too. Off when the models are equal or either is cursor/*. Wins over `advisor`. */
|
|
4986
|
+
delegate;
|
|
4987
|
+
/** ADVISOR MODE: `model` does the work; an `Advise` tool asks `advisor.model` (≤`limit`, default 3) with only
|
|
4988
|
+
* the request + `snapshot()` (host state, optional) + the question. The first write waits for one consult. */
|
|
4989
|
+
advisor;
|
|
4820
4990
|
/** VFS dir of typed-subagent defs (`<dir>/<name>.md`). If set with `subagents`: inject a catalog + enable the `Task` `agentType` param. */
|
|
4821
4991
|
agentsDir;
|
|
4822
4992
|
/** Current recursion depth (0 = top-level); spawned children run at depth+1. */
|
|
@@ -4925,6 +5095,13 @@ var Agent = class _Agent {
|
|
|
4925
5095
|
lastTrimNotified = 0;
|
|
4926
5096
|
// last auto-trim drop count surfaced via host.notify (dedup)
|
|
4927
5097
|
activeTools = [];
|
|
5098
|
+
meter;
|
|
5099
|
+
// delegate/advisor: meters the second model's calls
|
|
5100
|
+
modeModel;
|
|
5101
|
+
modeKind;
|
|
5102
|
+
adv;
|
|
5103
|
+
lastTask = "";
|
|
5104
|
+
// the current turn's text — what the advisor reads as the request
|
|
4928
5105
|
/** claude-code bridge for agentx-only tools (ccBridge.ts) + the current step's executor it calls. */
|
|
4929
5106
|
ccServer;
|
|
4930
5107
|
ccExec;
|
|
@@ -5027,12 +5204,15 @@ var Agent = class _Agent {
|
|
|
5027
5204
|
const o = this.options;
|
|
5028
5205
|
if (!o.model.startsWith("claude-code/")) return void 0;
|
|
5029
5206
|
const po = { steer: () => this.drainPendingSteers() };
|
|
5030
|
-
const
|
|
5207
|
+
const mode = ccModeOptions(this.modeKind, o.providerOptions?.hooks, () => this.adv?.used() ?? 0);
|
|
5208
|
+
const bridged = (t) => o.ccBridge === "all" || CC_BRIDGED.has(t.name) || mode.bridge.has(t.name);
|
|
5031
5209
|
if (o.providerOptions?.claudeCodeSession && this.activeTools.some(bridged)) {
|
|
5032
5210
|
this.ccServer ??= ccToolServer("agentx", () => this.activeTools.filter(bridged), (n, a) => this.ccExec(n, a));
|
|
5033
5211
|
po.mcpServers = { ...o.providerOptions?.mcpServers, agentx: this.ccServer };
|
|
5034
5212
|
}
|
|
5035
|
-
const denyTools = claudeCodeDisallowedTools(o.permissions);
|
|
5213
|
+
const denyTools = [...claudeCodeDisallowedTools(o.permissions), ...mode.disallow];
|
|
5214
|
+
if (mode.hooks) po.hooks = mode.hooks;
|
|
5215
|
+
if (this.modeKind && o.providerOptions?.claudeCodeSession) po.claudeCodeSession = `${o.providerOptions.claudeCodeSession}:${this.modeKind}`;
|
|
5036
5216
|
if (denyTools.length) {
|
|
5037
5217
|
const prior = o.providerOptions?.disallowedTools ?? [];
|
|
5038
5218
|
po.disallowedTools = [.../* @__PURE__ */ new Set([...prior, ...denyTools])];
|
|
@@ -5108,7 +5288,7 @@ var Agent = class _Agent {
|
|
|
5108
5288
|
const disk = new NodeDiskFilesystem2(process.cwd());
|
|
5109
5289
|
await disk.init();
|
|
5110
5290
|
this.options.fs = new JailedFilesystem2(disk);
|
|
5111
|
-
|
|
5291
|
+
log9.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
|
|
5112
5292
|
}
|
|
5113
5293
|
this.buildCtx();
|
|
5114
5294
|
}
|
|
@@ -5125,6 +5305,13 @@ var Agent = class _Agent {
|
|
|
5125
5305
|
const fs = o.fs;
|
|
5126
5306
|
let systemPrompt = o.systemPrompt;
|
|
5127
5307
|
let tools = o.tools;
|
|
5308
|
+
this.ctx.model = o.model;
|
|
5309
|
+
const worker = o.delegate && delegateWorkerFor(o.model, o.delegate.worker);
|
|
5310
|
+
const advisor = !worker && o.advisor ? advisorFor(o.model, o.advisor.model) : void 0;
|
|
5311
|
+
this.meter = worker || advisor ? meterWorkers(o.ai, o.model) : void 0;
|
|
5312
|
+
const ai = this.meter?.ai ?? o.ai;
|
|
5313
|
+
this.modeKind = worker ? "delegate" : advisor ? "advisor" : void 0;
|
|
5314
|
+
const adv = this.adv = advisor ? makeAdvisor({ ai, advisor, request: () => this.lastTask, snapshot: o.advisor.snapshot, limit: o.advisor.limit }) : void 0;
|
|
5128
5315
|
if (o.instructionFiles !== false) {
|
|
5129
5316
|
const names = Array.isArray(o.instructionFiles) ? o.instructionFiles : void 0;
|
|
5130
5317
|
const ins = await loadInstructions(fs, names);
|
|
@@ -5155,7 +5342,7 @@ var Agent = class _Agent {
|
|
|
5155
5342
|
if (tool) tools = [...tools, tool];
|
|
5156
5343
|
}
|
|
5157
5344
|
if (o.host && o.askUserQuestion) tools = [...tools, askUserQuestionTool];
|
|
5158
|
-
if (o.subagents) {
|
|
5345
|
+
if (o.subagents || worker) {
|
|
5159
5346
|
let agents;
|
|
5160
5347
|
if (o.agentsDir) {
|
|
5161
5348
|
const loaded = await loadAgents(fs, o.agentsDir);
|
|
@@ -5163,17 +5350,18 @@ var Agent = class _Agent {
|
|
|
5163
5350
|
if (loaded.catalog) systemPrompt += "\n\n" + loaded.catalog;
|
|
5164
5351
|
}
|
|
5165
5352
|
const taskOpts = {
|
|
5166
|
-
ai
|
|
5167
|
-
model: o.model,
|
|
5353
|
+
ai,
|
|
5354
|
+
model: worker || o.model,
|
|
5168
5355
|
fs,
|
|
5169
5356
|
depth: o.depth,
|
|
5170
|
-
maxDepth: o.maxDepth,
|
|
5357
|
+
maxDepth: worker ? Math.min(o.maxDepth, o.depth + 1) : o.maxDepth,
|
|
5171
5358
|
agents,
|
|
5359
|
+
inheritPostToolUse: !!worker,
|
|
5172
5360
|
hooks: o.hooks,
|
|
5173
5361
|
traceDir: o.traceDir,
|
|
5174
5362
|
permissions: o.permissions,
|
|
5175
5363
|
// a child obeys the parent's allow/deny rules (else Task is a one-call bypass)
|
|
5176
|
-
tools: [...o.tools, ...this.injectedTools],
|
|
5364
|
+
tools: worker ? delegateTools([...o.tools, ...this.injectedTools], o.model) : [...o.tools, ...this.injectedTools],
|
|
5177
5365
|
systemPrompt: o.systemPrompt,
|
|
5178
5366
|
// A child never resumes the parent's own claude-code session (the host's `resume` link is the parent's).
|
|
5179
5367
|
providerOptionsFor: o.providerOptionsFor ?? ((m) => modelProvider(m) === modelProvider(o.model) && o.providerOptions ? { ...o.providerOptions, resume: void 0, onClaudeCodeSession: void 0 } : void 0)
|
|
@@ -5186,6 +5374,15 @@ var Agent = class _Agent {
|
|
|
5186
5374
|
if (plan) tools = [...tools, plan.tool];
|
|
5187
5375
|
this.activeHooks = composeHooks(o.hooks, plan?.hooks, o.permissions?.hooks());
|
|
5188
5376
|
this.activeTools = [...tools, ...this.injectedTools];
|
|
5377
|
+
if (worker) {
|
|
5378
|
+
this.activeTools = delegateTools(this.activeTools, o.model);
|
|
5379
|
+
systemPrompt += delegatePrompt(worker);
|
|
5380
|
+
}
|
|
5381
|
+
if (adv) {
|
|
5382
|
+
this.activeTools = [...adv.gate(this.activeTools), adv.tool];
|
|
5383
|
+
systemPrompt += advisorPrompt(advisor);
|
|
5384
|
+
}
|
|
5385
|
+
this.modeModel = worker || advisor;
|
|
5189
5386
|
this.systemPromptCache = systemPrompt;
|
|
5190
5387
|
this.prepared = true;
|
|
5191
5388
|
return systemPrompt;
|
|
@@ -5198,6 +5395,7 @@ var Agent = class _Agent {
|
|
|
5198
5395
|
this.started = false;
|
|
5199
5396
|
this.invokedTools.clear();
|
|
5200
5397
|
this.fabricatedClaims.clear();
|
|
5398
|
+
this.lastTask = contentText(task);
|
|
5201
5399
|
const systemPrompt = await this.prepare(contentText(task));
|
|
5202
5400
|
const startCtx = await this.fireSessionStart();
|
|
5203
5401
|
const userContent = await this.applyPromptSubmit(task);
|
|
@@ -5233,6 +5431,7 @@ var Agent = class _Agent {
|
|
|
5233
5431
|
*/
|
|
5234
5432
|
async send(task) {
|
|
5235
5433
|
await this.ensureFs();
|
|
5434
|
+
this.lastTask = contentText(task);
|
|
5236
5435
|
const systemPrompt = await this.prepare(contentText(task));
|
|
5237
5436
|
const startCtx = await this.fireSessionStart();
|
|
5238
5437
|
const userContent = await this.applyPromptSubmit(task);
|
|
@@ -5283,6 +5482,12 @@ var Agent = class _Agent {
|
|
|
5283
5482
|
if (next !== this.transcript) this.transcript = next;
|
|
5284
5483
|
}
|
|
5285
5484
|
async runLoop() {
|
|
5485
|
+
this.meter?.reset();
|
|
5486
|
+
const res = await this.loop();
|
|
5487
|
+
const workerUsage = this.meter?.usage();
|
|
5488
|
+
return workerUsage ? { ...res, workerModel: this.meter.model() ?? this.modeModel, workerUsage } : res;
|
|
5489
|
+
}
|
|
5490
|
+
async loop() {
|
|
5286
5491
|
const o = this.options;
|
|
5287
5492
|
const wireTools = toWireTools(this.activeTools);
|
|
5288
5493
|
const useStream = o.stream === true && typeof o.host?.notify === "function";
|
|
@@ -5302,7 +5507,7 @@ var Agent = class _Agent {
|
|
|
5302
5507
|
let lastProgressAt = start;
|
|
5303
5508
|
const turnUser = this.transcript[this.transcript.length - 1];
|
|
5304
5509
|
const kill = (finishReason) => {
|
|
5305
|
-
|
|
5510
|
+
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
5511
|
this.jobs.killAll(`run ended: ${finishReason}`, true);
|
|
5307
5512
|
const claims = this.survivingClaims();
|
|
5308
5513
|
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 +5533,9 @@ var Agent = class _Agent {
|
|
|
5328
5533
|
this.nativeHoldMs = o.nativeHoldMs;
|
|
5329
5534
|
if (isCursorWithTools) {
|
|
5330
5535
|
if (typeof hostToolTimeoutMs === "number" && effectiveToolTimeoutMs !== hostToolTimeoutMs) {
|
|
5331
|
-
|
|
5536
|
+
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
5537
|
}
|
|
5333
|
-
for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs))
|
|
5538
|
+
for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs)) log9.warn(v);
|
|
5334
5539
|
}
|
|
5335
5540
|
const hostExec = async (name, args) => {
|
|
5336
5541
|
const tc = { id: `host-${Date.now()}`, type: "function", function: { name, arguments: JSON.stringify(args) } };
|
|
@@ -5389,7 +5594,7 @@ var Agent = class _Agent {
|
|
|
5389
5594
|
} catch (err2) {
|
|
5390
5595
|
if (!this.imagesOff && /does not support .*input/i.test(String(err2?.message ?? "")) && sent.some((m) => Array.isArray(m.content) && m.role === "tool")) {
|
|
5391
5596
|
this.imagesOff = true;
|
|
5392
|
-
|
|
5597
|
+
log9.warn(`model refused image input (${err2?.message}) \u2014 resending with image Reads as text stubs`);
|
|
5393
5598
|
sent = this.trimContext();
|
|
5394
5599
|
attempt--;
|
|
5395
5600
|
continue;
|
|
@@ -5401,11 +5606,11 @@ var Agent = class _Agent {
|
|
|
5401
5606
|
const empty = err2?.code === "empty" || err2?.code === "cursor_silent";
|
|
5402
5607
|
const maxAttempts = empty ? Math.max(o.emptyRetries, 0) : 2;
|
|
5403
5608
|
const replaysTool = stalled && this.toolRanThisAttempt;
|
|
5404
|
-
if (replaysTool)
|
|
5609
|
+
if (replaysTool) log9.warn("model stall after tool(s) already ran this step \u2014 not retrying (a retry would replay them)");
|
|
5405
5610
|
const transient = !o.signal?.aborted && !isAbortError(err2) && !replaysTool && attempt < maxAttempts && (network || serverSide || stalled || empty);
|
|
5406
5611
|
if (!transient) throw err2;
|
|
5407
5612
|
const waitMs = (empty ? o.emptyRetryBaseMs : 1e3) * (attempt + 1);
|
|
5408
|
-
|
|
5613
|
+
log9.warn(`${empty ? "empty model turn" : stalled ? "model stall" : "network drop"} mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
|
|
5409
5614
|
o.host?.notify?.({ kind: "retry", message: `${empty ? "model returned nothing" : stalled ? "model stalled" : "connection dropped"} \u2014 retrying step (#${attempt + 1})` });
|
|
5410
5615
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
5411
5616
|
}
|
|
@@ -5413,12 +5618,12 @@ var Agent = class _Agent {
|
|
|
5413
5618
|
} catch (err2) {
|
|
5414
5619
|
if (err2?.code === "budget") return kill("budget");
|
|
5415
5620
|
if (err2?.code === "stall") {
|
|
5416
|
-
|
|
5621
|
+
log9.warn(`model stalled out after retries: ${err2?.message ?? err2}`);
|
|
5417
5622
|
return { ...kill("timeout"), providerFinishReason: "none (stalled)", error: err2 };
|
|
5418
5623
|
}
|
|
5419
5624
|
if (o.signal?.aborted || isAbortError(err2)) return kill("aborted");
|
|
5420
5625
|
if (err2?.code === "empty" || err2?.code === "cursor_silent") {
|
|
5421
|
-
|
|
5626
|
+
log9.warn(`model returned empty/silent turns after retries \u2014 surfacing as failed`);
|
|
5422
5627
|
const pfr = err2?.providerFinishReason;
|
|
5423
5628
|
return { text: "", steps, finishReason: "empty", ...pfr ? { providerFinishReason: pfr } : {}, messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
5424
5629
|
}
|
|
@@ -5431,13 +5636,13 @@ var Agent = class _Agent {
|
|
|
5431
5636
|
}
|
|
5432
5637
|
if (bodyStr && err2 instanceof Error && !err2.message.includes(bodyStr)) err2.detail = bodyStr;
|
|
5433
5638
|
const errLog = err2 instanceof Error ? { message: err2.message, ...err2.detail ? { detail: err2.detail } : {}, stack: err2.stack } : err2;
|
|
5434
|
-
|
|
5639
|
+
log9.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, errLog);
|
|
5435
5640
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
5436
5641
|
}
|
|
5437
5642
|
if (o.signal?.aborted) return kill("aborted");
|
|
5438
5643
|
if (res.finishReason === "error" && !res.toolCalls?.length) {
|
|
5439
5644
|
const err2 = new Error(contentText(res.content ?? "").trim() || "provider error");
|
|
5440
|
-
|
|
5645
|
+
log9.error(`chat() failed: ${err2.message}`);
|
|
5441
5646
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
5442
5647
|
}
|
|
5443
5648
|
if (!res.usage) {
|
|
@@ -5505,21 +5710,21 @@ var Agent = class _Agent {
|
|
|
5505
5710
|
if (emptyTurn && o.failEmptyTurns) {
|
|
5506
5711
|
if (!emptyNudged && canNudge()) {
|
|
5507
5712
|
emptyNudged = true;
|
|
5508
|
-
|
|
5713
|
+
log9.warn(`empty model turn after tool work (step ${steps}, provider finishReason=${res.finishReason ?? "n/a"}) \u2014 nudging once`);
|
|
5509
5714
|
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
5715
|
continue;
|
|
5511
5716
|
}
|
|
5512
|
-
|
|
5717
|
+
log9.warn(`model returned empty turns after tool work \u2014 surfacing as failed`);
|
|
5513
5718
|
const e = new Error("empty_response: model returned an empty turn (no text, no tools) after a re-prompt");
|
|
5514
5719
|
e.code = "empty";
|
|
5515
5720
|
return { text: "", steps, finishReason: "empty", ...res.finishReason ? { providerFinishReason: res.finishReason } : {}, messages: this.transcript, usage, usageEstimated, error: e };
|
|
5516
5721
|
}
|
|
5517
5722
|
const fabricated = o.catchFabricatedToolUse && !closureNudged ? this.fabricatedToolNames(contentText(res.content ?? "")) : [];
|
|
5518
5723
|
for (const n of fabricated) this.fabricatedClaims.add(n);
|
|
5519
|
-
if (fabricated.length && !canNudge())
|
|
5724
|
+
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
5725
|
if (fabricated.length && canNudge()) {
|
|
5521
5726
|
closureNudged = true;
|
|
5522
|
-
|
|
5727
|
+
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
5728
|
this.transcript.push({
|
|
5524
5729
|
role: "user",
|
|
5525
5730
|
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 +5733,14 @@ var Agent = class _Agent {
|
|
|
5528
5733
|
}
|
|
5529
5734
|
if (o.closeDelegatedTurns && res.endedWithoutClosing && !closureNudged && canNudge()) {
|
|
5530
5735
|
closureNudged = true;
|
|
5531
|
-
|
|
5736
|
+
log9.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
|
|
5532
5737
|
this.transcript.push({
|
|
5533
5738
|
role: "user",
|
|
5534
5739
|
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
5740
|
});
|
|
5536
5741
|
continue;
|
|
5537
5742
|
}
|
|
5538
|
-
|
|
5743
|
+
log9.verbose(`completed in ${steps} step(s)`);
|
|
5539
5744
|
const drained = await this.jobs.drain(o.signal);
|
|
5540
5745
|
if (o.signal?.aborted) return kill("aborted");
|
|
5541
5746
|
if (drained.length) lastProgressAt = Date.now();
|
|
@@ -5569,7 +5774,7 @@ var Agent = class _Agent {
|
|
|
5569
5774
|
if (this.ctx.needsInput) {
|
|
5570
5775
|
const question = this.ctx.needsInput;
|
|
5571
5776
|
this.ctx.needsInput = void 0;
|
|
5572
|
-
|
|
5777
|
+
log9.warn(`needs_input: ${question.question} (steps=${steps})`);
|
|
5573
5778
|
await this.jobs.drain();
|
|
5574
5779
|
const text = [
|
|
5575
5780
|
`OPEN QUESTION (run paused \u2014 no human was reachable): ${question.question}`,
|
|
@@ -5580,7 +5785,7 @@ var Agent = class _Agent {
|
|
|
5580
5785
|
if (this.ctx.endTurn) {
|
|
5581
5786
|
this.ctx.endTurn = void 0;
|
|
5582
5787
|
if (this.drainInjections()) continue;
|
|
5583
|
-
|
|
5788
|
+
log9.verbose(`turn ended by a tool (steps=${steps})`);
|
|
5584
5789
|
await this.jobs.drain(o.signal);
|
|
5585
5790
|
return { text: "", steps, finishReason: "stop", messages: this.transcript, usage, usageEstimated };
|
|
5586
5791
|
}
|
|
@@ -5618,12 +5823,12 @@ var Agent = class _Agent {
|
|
|
5618
5823
|
if (this.toolHoldActive()) {
|
|
5619
5824
|
const now4 = Date.now();
|
|
5620
5825
|
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
|
-
|
|
5826
|
+
log9.warn(`stall watchdog: no stream output for ${stallMs}ms \u2014 held by ${who}`);
|
|
5622
5827
|
this.options.host?.notify?.({ kind: "tool_hold", message: `waiting on ${who}` });
|
|
5623
5828
|
return poke();
|
|
5624
5829
|
}
|
|
5625
5830
|
const inFlight = this.jobs.list().filter((j) => j.status === "running");
|
|
5626
|
-
|
|
5831
|
+
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
5832
|
fired = true;
|
|
5628
5833
|
ac.abort(stallError());
|
|
5629
5834
|
}, stallMs);
|
|
@@ -5793,13 +5998,13 @@ var Agent = class _Agent {
|
|
|
5793
5998
|
const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
|
|
5794
5999
|
if (decision?.block) {
|
|
5795
6000
|
const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
|
|
5796
|
-
|
|
6001
|
+
log9.debug(`${tc.function.name} -> ${blocked}`);
|
|
5797
6002
|
await hooks?.postToolUse?.(call, blocked, meta);
|
|
5798
6003
|
return blocked;
|
|
5799
6004
|
}
|
|
5800
6005
|
this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
|
|
5801
6006
|
if (earlyError) {
|
|
5802
|
-
|
|
6007
|
+
log9.debug(`${tc.function.name} -> ${earlyError}`);
|
|
5803
6008
|
await hooks?.postToolUse?.(call, earlyError, meta);
|
|
5804
6009
|
this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
|
|
5805
6010
|
return earlyError;
|
|
@@ -5808,12 +6013,12 @@ var Agent = class _Agent {
|
|
|
5808
6013
|
let images;
|
|
5809
6014
|
let threw = false;
|
|
5810
6015
|
try {
|
|
5811
|
-
|
|
6016
|
+
log9.debug(`${tc.function.name}(${tc.function.arguments})`);
|
|
5812
6017
|
this.ctx.emit = hooks?.onToolOutput ? (chunk) => {
|
|
5813
6018
|
try {
|
|
5814
6019
|
hooks.onToolOutput(call, chunk, meta);
|
|
5815
6020
|
} catch (e) {
|
|
5816
|
-
|
|
6021
|
+
log9.debug(`onToolOutput hook error: ${e}`);
|
|
5817
6022
|
}
|
|
5818
6023
|
} : void 0;
|
|
5819
6024
|
const raw = await tool.run(args, this.ctx);
|
|
@@ -5825,7 +6030,7 @@ var Agent = class _Agent {
|
|
|
5825
6030
|
}
|
|
5826
6031
|
} catch (e) {
|
|
5827
6032
|
const msg = e instanceof Error ? e.message : String(e);
|
|
5828
|
-
|
|
6033
|
+
log9.debug(`${tc.function.name} -> error: ${msg}`);
|
|
5829
6034
|
result = `Error: ${msg}`;
|
|
5830
6035
|
threw = true;
|
|
5831
6036
|
} finally {
|
|
@@ -6102,7 +6307,7 @@ function fitTokenBudget(messages, maxTokens, keepRecentImages = 1, vision = true
|
|
|
6102
6307
|
const ids = callIdSet(messages.slice(from));
|
|
6103
6308
|
while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
|
|
6104
6309
|
if (total > maxTokens)
|
|
6105
|
-
|
|
6310
|
+
log9.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
|
|
6106
6311
|
return [...head, ...messages.slice(from)];
|
|
6107
6312
|
}
|
|
6108
6313
|
function compact(m, max, focus) {
|
|
@@ -6682,7 +6887,7 @@ init_tools_web();
|
|
|
6682
6887
|
init_tools();
|
|
6683
6888
|
init_tools_structured();
|
|
6684
6889
|
init_logging();
|
|
6685
|
-
var
|
|
6890
|
+
var log10 = forComponent("scratch");
|
|
6686
6891
|
var SCRATCH_DIR = "/scratch";
|
|
6687
6892
|
function shortArgs(args) {
|
|
6688
6893
|
try {
|
|
@@ -6722,7 +6927,7 @@ var Scratch = class {
|
|
|
6722
6927
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
6723
6928
|
await this.fs.writeFile(path, header + raw);
|
|
6724
6929
|
} catch (e) {
|
|
6725
|
-
|
|
6930
|
+
log10.debug("scratch write failed; returning raw", e);
|
|
6726
6931
|
return raw;
|
|
6727
6932
|
}
|
|
6728
6933
|
const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
|
|
@@ -6752,7 +6957,7 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
|
|
|
6752
6957
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
6753
6958
|
await this.fs.writeFile(path, header + full);
|
|
6754
6959
|
} catch (e) {
|
|
6755
|
-
|
|
6960
|
+
log10.debug("scratch spill failed; cropping lossy", e);
|
|
6756
6961
|
return full.slice(0, pageBytes) + `
|
|
6757
6962
|
|
|
6758
6963
|
[output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
|
|
@@ -6798,7 +7003,7 @@ Question: ${q2}`);
|
|
|
6798
7003
|
const answer = (res.text ?? "").trim();
|
|
6799
7004
|
return answer || "(no answer found in scratch)";
|
|
6800
7005
|
} catch (e) {
|
|
6801
|
-
|
|
7006
|
+
log10.debug("Ask peek failed", e);
|
|
6802
7007
|
return `Error querying scratch: ${e?.message ?? e}`;
|
|
6803
7008
|
}
|
|
6804
7009
|
}
|
|
@@ -6807,7 +7012,7 @@ Question: ${q2}`);
|
|
|
6807
7012
|
|
|
6808
7013
|
// src/lessons.ts
|
|
6809
7014
|
init_logging();
|
|
6810
|
-
var
|
|
7015
|
+
var log11 = forComponent("Lessons");
|
|
6811
7016
|
var LessonOptionsDefaults = class {
|
|
6812
7017
|
minRepeats = 2;
|
|
6813
7018
|
};
|
|
@@ -6832,8 +7037,8 @@ function lessonCapture(options) {
|
|
|
6832
7037
|
counts.set(lesson.slug, n);
|
|
6833
7038
|
if (n < o.minRepeats) return;
|
|
6834
7039
|
written.add(lesson.slug);
|
|
6835
|
-
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) =>
|
|
6836
|
-
|
|
7040
|
+
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log11.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
|
|
7041
|
+
log11.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
|
|
6837
7042
|
}
|
|
6838
7043
|
};
|
|
6839
7044
|
}
|
|
@@ -6841,7 +7046,7 @@ function lessonCapture(options) {
|
|
|
6841
7046
|
// src/reflect.ts
|
|
6842
7047
|
init_llm();
|
|
6843
7048
|
init_logging();
|
|
6844
|
-
var
|
|
7049
|
+
var log12 = forComponent("Reflect");
|
|
6845
7050
|
async function reflectOnRun(o) {
|
|
6846
7051
|
const digest = digestRun(o.result.messages, o.maxDigestChars ?? 6e3);
|
|
6847
7052
|
if (!digest.trim()) return null;
|
|
@@ -6857,7 +7062,7 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
6857
7062
|
const r = await o.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false });
|
|
6858
7063
|
text = r?.content ?? "";
|
|
6859
7064
|
} catch (e) {
|
|
6860
|
-
|
|
7065
|
+
log12.warn(`reflection call failed: ${e?.message ?? e}`);
|
|
6861
7066
|
return null;
|
|
6862
7067
|
}
|
|
6863
7068
|
const m = text.match(/LESSON:\s*(.+)/i);
|
|
@@ -6867,10 +7072,10 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
6867
7072
|
try {
|
|
6868
7073
|
await writeFact(o.fs, o.dir, slug2, lesson);
|
|
6869
7074
|
} catch (e) {
|
|
6870
|
-
|
|
7075
|
+
log12.warn(`could not persist lesson: ${e?.message ?? e}`);
|
|
6871
7076
|
return null;
|
|
6872
7077
|
}
|
|
6873
|
-
|
|
7078
|
+
log12.debug(`reflection persisted ${slug2}`);
|
|
6874
7079
|
return slug2;
|
|
6875
7080
|
}
|
|
6876
7081
|
function digestRun(messages, maxChars) {
|
|
@@ -6920,7 +7125,7 @@ function forComponent2(name) {
|
|
|
6920
7125
|
}
|
|
6921
7126
|
|
|
6922
7127
|
// node_modules/@bod.ee/voice/src/core/emotion.ts
|
|
6923
|
-
var
|
|
7128
|
+
var log13 = forComponent2("Emotion");
|
|
6924
7129
|
var EMOTIONS = [
|
|
6925
7130
|
// primary (best results)
|
|
6926
7131
|
"neutral",
|
|
@@ -7052,7 +7257,7 @@ function renderEmotions(text, opts = { show: true }) {
|
|
|
7052
7257
|
speech += cartesiaTag(tag);
|
|
7053
7258
|
if (opts.show) display += m[0];
|
|
7054
7259
|
} else {
|
|
7055
|
-
|
|
7260
|
+
log13.debug(`dropping unknown emotion tag ${m[0]}`);
|
|
7056
7261
|
}
|
|
7057
7262
|
last = m.index + m[0].length;
|
|
7058
7263
|
}
|
|
@@ -7094,7 +7299,7 @@ var EmotionStream = class {
|
|
|
7094
7299
|
if (tag) {
|
|
7095
7300
|
this.pending = tag;
|
|
7096
7301
|
if (this.show) display += m[0];
|
|
7097
|
-
} else
|
|
7302
|
+
} else log13.debug(`dropping unknown emotion tag ${m[0]}`);
|
|
7098
7303
|
last = m.index + m[0].length;
|
|
7099
7304
|
}
|
|
7100
7305
|
this.emit(body.slice(last), (s, d, p) => {
|
|
@@ -7208,8 +7413,8 @@ function isTranscriptComplete(text) {
|
|
|
7208
7413
|
const lastWord = words4[words4.length - 1];
|
|
7209
7414
|
if (COMPLETE_ENDINGS.has(lastWord)) return true;
|
|
7210
7415
|
if (INCOMPLETE_ENDINGS.has(lastWord)) return false;
|
|
7211
|
-
const
|
|
7212
|
-
for (const marker of CONTINUATION_MARKERS) if (
|
|
7416
|
+
const lower2 = trimmed.toLowerCase();
|
|
7417
|
+
for (const marker of CONTINUATION_MARKERS) if (lower2.endsWith(marker)) return false;
|
|
7213
7418
|
if (words4.length <= 2) return false;
|
|
7214
7419
|
return true;
|
|
7215
7420
|
}
|
|
@@ -7241,7 +7446,7 @@ function computeHoldMs(ctx) {
|
|
|
7241
7446
|
}
|
|
7242
7447
|
|
|
7243
7448
|
// node_modules/@bod.ee/voice/src/core/engine.ts
|
|
7244
|
-
var
|
|
7449
|
+
var log14 = forComponent2("VoiceEngine");
|
|
7245
7450
|
var realClock = {
|
|
7246
7451
|
now: () => performance.now(),
|
|
7247
7452
|
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
@@ -7629,8 +7834,8 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7629
7834
|
this.tts.warmup?.();
|
|
7630
7835
|
this.setState("listening");
|
|
7631
7836
|
if (this.options.bargeInMode === "aec" && !this.stt.usingAec)
|
|
7632
|
-
|
|
7633
|
-
|
|
7837
|
+
log14.warn("bargeInMode=aec but mic does not report echoCancellation applied \u2014 STT may transcribe the assistant");
|
|
7838
|
+
log14.debug(`voice I/O up (${this.usingAec ? "AEC" : "heuristic echo"} capture, mode=${this.options.bargeInMode}, mic reports aec=${this.stt.usingAec})`);
|
|
7634
7839
|
}
|
|
7635
7840
|
/** The RESOLVED barge-in tier (the single seam every tier branch reads). `bargeInMode` overrides
|
|
7636
7841
|
* the mic's self-report — see the option doc for why the report is not trustworthy on mobile. */
|
|
@@ -7655,7 +7860,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7655
7860
|
this.options.onDiag({ t: this.clock.now(), kind, ...fields });
|
|
7656
7861
|
} catch (e) {
|
|
7657
7862
|
this.diagOn = false;
|
|
7658
|
-
|
|
7863
|
+
log14.debug(`onDiag threw \u2014 diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
7659
7864
|
}
|
|
7660
7865
|
}
|
|
7661
7866
|
idleWaiters = [];
|
|
@@ -7794,7 +7999,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7794
7999
|
}
|
|
7795
8000
|
for (const w of this.words(this.reply)) this.echoWords.add(w);
|
|
7796
8001
|
this.tts.speak(forSpeech(speech), true);
|
|
7797
|
-
if (!this.spokeDeltas && this.turnStartAt)
|
|
8002
|
+
if (!this.spokeDeltas && this.turnStartAt) log14.debug(`ttft: ${Math.round(this.clock.now() - this.turnStartAt)}ms`);
|
|
7798
8003
|
this.spokeDeltas = true;
|
|
7799
8004
|
this.setState("speaking");
|
|
7800
8005
|
if (this.options.revealMode !== "off" && display) {
|
|
@@ -7907,13 +8112,13 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7907
8112
|
this.drainTimer = null;
|
|
7908
8113
|
this.speaking = false;
|
|
7909
8114
|
this.stopWordReveal(true);
|
|
7910
|
-
if (this.turnStartAt)
|
|
8115
|
+
if (this.turnStartAt) log14.debug(`turn: ${Math.round(this.clock.now() - this.turnStartAt)}ms (incl. playback)`);
|
|
7911
8116
|
this.echoUntil = this.clock.now() + 2500;
|
|
7912
8117
|
if (!this.usingAec) this.stt.reset();
|
|
7913
8118
|
try {
|
|
7914
8119
|
this.options.onSpeechEnd();
|
|
7915
8120
|
} catch (e) {
|
|
7916
|
-
|
|
8121
|
+
log14.warn(`onSpeechEnd threw: ${e instanceof Error ? e.message : e}`);
|
|
7917
8122
|
}
|
|
7918
8123
|
this.setState("listening");
|
|
7919
8124
|
if (this.uttQueue.length) this.pumpQueue();
|
|
@@ -7987,7 +8192,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
7987
8192
|
if (!host) return pick;
|
|
7988
8193
|
const ctx = { text: this.lastDispatchText, question: this.lastDispatchWasQuestion, barged: this.bargedBeforeDispatch };
|
|
7989
8194
|
const fallback = (e) => {
|
|
7990
|
-
|
|
8195
|
+
log14.warn(`pickAck failed \u2014 using the default pools: ${e instanceof Error ? e.message : e}`);
|
|
7991
8196
|
pick.picked = false;
|
|
7992
8197
|
return void 0;
|
|
7993
8198
|
};
|
|
@@ -8077,7 +8282,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8077
8282
|
this.ackProbe = null;
|
|
8078
8283
|
const queued = this.uttQueue.splice(0);
|
|
8079
8284
|
const droppedQueued = queued.length;
|
|
8080
|
-
if (droppedQueued)
|
|
8285
|
+
if (droppedQueued) log14.info(`barge-in cut ${droppedQueued} queued worker utterance(s) \u2014 handed to the host`);
|
|
8081
8286
|
this.overlapKeptUntil = 0;
|
|
8082
8287
|
if (!this.speaking && !this.drainTimer) {
|
|
8083
8288
|
if (droppedQueued) this.lastInterrupted = { full: "", heard: "", queued, queuedTurn: false };
|
|
@@ -8306,7 +8511,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8306
8511
|
if (flat && this.lastDispatchFlat && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS && (this.lastDispatchFlat.startsWith(flat) || flat.startsWith(this.lastDispatchFlat))) return;
|
|
8307
8512
|
this.specSpent = true;
|
|
8308
8513
|
this.specText = this.specPartial;
|
|
8309
|
-
|
|
8514
|
+
log14.debug(`speculate: "${this.specText.slice(0, 60)}"`);
|
|
8310
8515
|
this.diag("speculate", { text: this.specText });
|
|
8311
8516
|
this.options.onSpeculate(this.specText);
|
|
8312
8517
|
}, this.options.speculativeMs);
|
|
@@ -8375,7 +8580,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8375
8580
|
};
|
|
8376
8581
|
this.tts.newContext();
|
|
8377
8582
|
this.tts.speak(phrase, false);
|
|
8378
|
-
|
|
8583
|
+
log14.debug(`backchannel: "${phrase}"`);
|
|
8379
8584
|
this.diag("backchannel", { phrase });
|
|
8380
8585
|
this.options.onBackchannel(phrase);
|
|
8381
8586
|
}
|
|
@@ -8408,7 +8613,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8408
8613
|
});
|
|
8409
8614
|
if (idx < 0) return text;
|
|
8410
8615
|
toks.splice(idx, 1);
|
|
8411
|
-
|
|
8616
|
+
log14.verbose(`stripped backchannel echo "${this.lastBcPhrase}" from dispatching final`);
|
|
8412
8617
|
return toks.join(" ");
|
|
8413
8618
|
}
|
|
8414
8619
|
/** Merge a resumed utterance into the pending one, deduping any word-overlap. Soniox re-finalizes
|
|
@@ -8594,7 +8799,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8594
8799
|
}
|
|
8595
8800
|
const bargeHold = !this.looksIncomplete(this.pendingUtt) && this.unfinishedBargeLine(this.pendingUtt);
|
|
8596
8801
|
if (this.options.incompleteMergeMs && (bargeHold || this.looksIncomplete(this.pendingUtt))) {
|
|
8597
|
-
|
|
8802
|
+
log14.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
|
|
8598
8803
|
this.diag("hold", { reason: bargeHold ? "barge_unfinished" : "incomplete", tail: this.pendingUtt.slice(-40) });
|
|
8599
8804
|
this.options.onHold();
|
|
8600
8805
|
if (this.options.holdFiller && !this.speaking) this.speakFiller(this.options.holdFiller);
|
|
@@ -8633,13 +8838,13 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
8633
8838
|
const answered = this.answeredSinceDispatch();
|
|
8634
8839
|
const part = flat !== this.lastDispatchFlat && !answered && this.echoesDispatch(text);
|
|
8635
8840
|
if (part || flat && flat === this.lastDispatchFlat && !answered && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS) {
|
|
8636
|
-
|
|
8841
|
+
log14.verbose(`dropped duplicate final "${text.slice(0, 40)}" (identical to the just-dispatched utterance, no reply between)`);
|
|
8637
8842
|
this.diag("utterance_drop", { reason: "dup_final", text: text.slice(0, 60) });
|
|
8638
8843
|
return;
|
|
8639
8844
|
}
|
|
8640
8845
|
const refinal = this.refinalizes(text);
|
|
8641
8846
|
if (refinal && this.repliedSinceDispatch) {
|
|
8642
|
-
|
|
8847
|
+
log14.verbose(`dropped re-finalization "${text.slice(0, 60)}" (superset of the just-dispatched utterance, reply already flowing)`);
|
|
8643
8848
|
this.diag("utterance_drop", { reason: "refinalized", text: text.slice(0, 60) });
|
|
8644
8849
|
this.lastDispatchFlat = flat;
|
|
8645
8850
|
this.lastDispatchWords = text.trim().split(/\s+/);
|
|
@@ -8918,7 +9123,7 @@ var SpokenSplitter = class {
|
|
|
8918
9123
|
};
|
|
8919
9124
|
|
|
8920
9125
|
// node_modules/@bod.ee/voice/src/adapters/soniox.ts
|
|
8921
|
-
var
|
|
9126
|
+
var log15 = forComponent2("SonioxSTT");
|
|
8922
9127
|
var now = () => performance.now();
|
|
8923
9128
|
var SonioxSTTOptions = class {
|
|
8924
9129
|
auth = "";
|
|
@@ -8963,7 +9168,7 @@ var SonioxSTT = class {
|
|
|
8963
9168
|
this.onDiag({ t: now(), kind, ...fields });
|
|
8964
9169
|
} catch (e) {
|
|
8965
9170
|
this.diagOn = false;
|
|
8966
|
-
|
|
9171
|
+
log15.debug(`onDiag threw \u2014 STT diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
8967
9172
|
}
|
|
8968
9173
|
}
|
|
8969
9174
|
lastChunkAt = 0;
|
|
@@ -9005,11 +9210,11 @@ var SonioxSTT = class {
|
|
|
9005
9210
|
this.ws.onmessage = (ev) => this.handle(JSON.parse(String(ev.data)));
|
|
9006
9211
|
this.ws.onclose = (ev) => {
|
|
9007
9212
|
if (this.stopped) return;
|
|
9008
|
-
|
|
9213
|
+
log15.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
|
|
9009
9214
|
this.diag("stt_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: true });
|
|
9010
9215
|
this.reset();
|
|
9011
9216
|
this.connectWs().catch((e) => {
|
|
9012
|
-
|
|
9217
|
+
log15.error(`soniox reconnect failed: ${e.message}`);
|
|
9013
9218
|
this.diag("stt_reconnect_failed", { message: e.message });
|
|
9014
9219
|
});
|
|
9015
9220
|
};
|
|
@@ -9021,7 +9226,7 @@ var SonioxSTT = class {
|
|
|
9021
9226
|
this.endpointTimer = setInterval(() => {
|
|
9022
9227
|
const combined = (this.finalText + this.partialText).trim();
|
|
9023
9228
|
if (!combined || now() - this.lastChangeAt < this.options.silenceEndpointMs) return;
|
|
9024
|
-
if (this.firstTokenAt)
|
|
9229
|
+
if (this.firstTokenAt) log15.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
|
|
9025
9230
|
this.reset();
|
|
9026
9231
|
this.onUtterance(combined, now());
|
|
9027
9232
|
}, 120);
|
|
@@ -9033,7 +9238,7 @@ var SonioxSTT = class {
|
|
|
9033
9238
|
if (this.stopped) return;
|
|
9034
9239
|
const ref = this.lastChunkAt || this.startedChunksAt;
|
|
9035
9240
|
if (now() - ref > noAudioMs) {
|
|
9036
|
-
|
|
9241
|
+
log15.error(`stt: no mic audio for >${Math.round(noAudioMs / 1e3)}s \u2014 capture device stopped delivering`);
|
|
9037
9242
|
this.diag("stt_watchdog_fatal", { noAudioMs });
|
|
9038
9243
|
this.onFatal("microphone stopped delivering audio (try a different input device, e.g. AirPods, or check System Settings \u2192 Sound \u2192 Input)");
|
|
9039
9244
|
this.stop();
|
|
@@ -9056,7 +9261,7 @@ var SonioxSTT = class {
|
|
|
9056
9261
|
handle(m) {
|
|
9057
9262
|
if (m.error_message) {
|
|
9058
9263
|
this.diag("stt_error", { message: String(m.error_message), code: m.error_code });
|
|
9059
|
-
return
|
|
9264
|
+
return log15.error(`soniox: ${m.error_message}`);
|
|
9060
9265
|
}
|
|
9061
9266
|
let endpoint = false;
|
|
9062
9267
|
for (const t of m.tokens ?? []) {
|
|
@@ -9073,7 +9278,7 @@ var SonioxSTT = class {
|
|
|
9073
9278
|
this.onPartial(combined);
|
|
9074
9279
|
if (endpoint && this.finalText.trim()) {
|
|
9075
9280
|
const utterance = this.finalText.trim();
|
|
9076
|
-
if (this.firstTokenAt)
|
|
9281
|
+
if (this.firstTokenAt) log15.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
|
|
9077
9282
|
this.reset();
|
|
9078
9283
|
this.onUtterance(utterance, now());
|
|
9079
9284
|
}
|
|
@@ -9095,7 +9300,7 @@ var SonioxSTT = class {
|
|
|
9095
9300
|
};
|
|
9096
9301
|
|
|
9097
9302
|
// node_modules/@bod.ee/voice/src/adapters/cartesia.ts
|
|
9098
|
-
var
|
|
9303
|
+
var log16 = forComponent2("CartesiaTTS");
|
|
9099
9304
|
var now2 = () => performance.now();
|
|
9100
9305
|
var CartesiaTTSOptions = class {
|
|
9101
9306
|
auth = "";
|
|
@@ -9131,7 +9336,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
9131
9336
|
this.onDiag({ t: now2(), kind, ...fields });
|
|
9132
9337
|
} catch (e) {
|
|
9133
9338
|
this.diagOn = false;
|
|
9134
|
-
|
|
9339
|
+
log16.debug(`onDiag threw \u2014 TTS diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
9135
9340
|
}
|
|
9136
9341
|
}
|
|
9137
9342
|
firstAudioAt = 0;
|
|
@@ -9166,11 +9371,11 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
9166
9371
|
this.ws.onerror = (e) => rej(new Error(`cartesia ws: ${e.message || "connect failed"}`));
|
|
9167
9372
|
});
|
|
9168
9373
|
this.ws.onclose = (ev) => {
|
|
9169
|
-
|
|
9374
|
+
log16.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
|
|
9170
9375
|
this.diag("tts_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: !this.closed });
|
|
9171
9376
|
if (!this.closed) {
|
|
9172
9377
|
this.connecting = this.doConnect().catch((e) => {
|
|
9173
|
-
|
|
9378
|
+
log16.error(`cartesia reconnect failed: ${e.message}`);
|
|
9174
9379
|
this.diag("tts_reconnect_failed", { message: e.message });
|
|
9175
9380
|
});
|
|
9176
9381
|
}
|
|
@@ -9198,12 +9403,12 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
9198
9403
|
this.down = true;
|
|
9199
9404
|
this.downAt = now2();
|
|
9200
9405
|
this.consecutiveOk = 0;
|
|
9201
|
-
|
|
9406
|
+
log16.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
|
|
9202
9407
|
this.diag("tts_breaker_open", { errors: this.consecutiveErrors });
|
|
9203
9408
|
this.onDone();
|
|
9204
9409
|
this.startProbe();
|
|
9205
9410
|
} else if (!this.down) {
|
|
9206
|
-
(/No valid transcripts/i.test(m.message || "") ?
|
|
9411
|
+
(/No valid transcripts/i.test(m.message || "") ? log16.debug : log16.warn)(`cartesia: ${JSON.stringify(m)}`);
|
|
9207
9412
|
}
|
|
9208
9413
|
}
|
|
9209
9414
|
};
|
|
@@ -9218,7 +9423,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
9218
9423
|
this.stopProbe();
|
|
9219
9424
|
const downMs = this.downAt ? now2() - this.downAt : 0;
|
|
9220
9425
|
this.diag("tts_breaker_close", { downMs: Math.round(downMs) });
|
|
9221
|
-
(downMs < 2e3 ?
|
|
9426
|
+
(downMs < 2e3 ? log16.debug : log16.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
|
|
9222
9427
|
}
|
|
9223
9428
|
/** Ensure the WS is open before sending — reconnects if idle-closed. */
|
|
9224
9429
|
async ensureConnected() {
|
|
@@ -9301,13 +9506,13 @@ function base64ToBytes(b64) {
|
|
|
9301
9506
|
}
|
|
9302
9507
|
|
|
9303
9508
|
// node_modules/@bod.ee/voice/src/adapters/fish.ts
|
|
9304
|
-
var
|
|
9509
|
+
var log17 = forComponent2("FishTTS");
|
|
9305
9510
|
|
|
9306
9511
|
// node_modules/@bod.ee/voice/src/adapters/grok.ts
|
|
9307
|
-
var
|
|
9512
|
+
var log18 = forComponent2("GrokTTS");
|
|
9308
9513
|
|
|
9309
9514
|
// node_modules/@bod.ee/voice/src/adapters/grok-stt.ts
|
|
9310
|
-
var
|
|
9515
|
+
var log19 = forComponent2("GrokSTT");
|
|
9311
9516
|
|
|
9312
9517
|
// src/voice/engine.ts
|
|
9313
9518
|
init_logging();
|
|
@@ -9323,7 +9528,7 @@ import { AIClient, DecideClient, JevAdapter, LlmDecideAdapter } from "ai.libx.js
|
|
|
9323
9528
|
|
|
9324
9529
|
// src/voice/overlap.ts
|
|
9325
9530
|
init_logging();
|
|
9326
|
-
var
|
|
9531
|
+
var log20 = forComponent("Overlap");
|
|
9327
9532
|
var norm2 = (s) => s.toLowerCase().replace(/[’']/g, "'").replace(/[^\p{L}\p{N}' -]+/gu, " ").replace(/\s+/g, " ").trim();
|
|
9328
9533
|
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
9534
|
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 +9608,8 @@ var OverlapJudge = class {
|
|
|
9403
9608
|
const t0 = this.o.now();
|
|
9404
9609
|
const done = (verdict, by) => {
|
|
9405
9610
|
const d = { verdict, by, ms: Math.round(this.o.now() - t0) };
|
|
9406
|
-
if (by === "late" || by === "error" || by === "none")
|
|
9407
|
-
else
|
|
9611
|
+
if (by === "late" || by === "error" || by === "none") log20.warn(`overlap "${heard.slice(0, 60)}": no verdict (${by}, ${d.ms}ms) \u2192 cut`);
|
|
9612
|
+
else log20.debug(`overlap "${heard.slice(0, 60)}": ${verdict} by ${by} ${d.ms}ms`);
|
|
9408
9613
|
this.o.onDecision(heard, d);
|
|
9409
9614
|
return d;
|
|
9410
9615
|
};
|
|
@@ -9421,7 +9626,7 @@ var OverlapJudge = class {
|
|
|
9421
9626
|
});
|
|
9422
9627
|
try {
|
|
9423
9628
|
const v = await Promise.race([this.o.model(heard, ctl.signal).catch((e) => {
|
|
9424
|
-
if (!ctl.signal.aborted)
|
|
9629
|
+
if (!ctl.signal.aborted) log20.warn(`overlap model failed: ${e.message}`);
|
|
9425
9630
|
return "error";
|
|
9426
9631
|
}), late]);
|
|
9427
9632
|
if (v === "late" || v === "error") return done("cut", v);
|
|
@@ -9437,15 +9642,15 @@ function overlapModelFor(ai, env = process.env) {
|
|
|
9437
9642
|
const m = fastOverlapModel(ai, model);
|
|
9438
9643
|
const t0 = performance.now();
|
|
9439
9644
|
void m("yeah", AbortSignal.timeout(1e4)).then(
|
|
9440
|
-
() =>
|
|
9441
|
-
(e) =>
|
|
9645
|
+
() => log20.debug(`fast model warm in ${Math.round(performance.now() - t0)}ms`),
|
|
9646
|
+
(e) => log20.warn(`fast model warm-up failed (the first overlap verdict may be late): ${e?.message ?? e}`)
|
|
9442
9647
|
);
|
|
9443
9648
|
return m;
|
|
9444
9649
|
}
|
|
9445
9650
|
var softBarge = (mode) => mode !== "turn";
|
|
9446
9651
|
|
|
9447
9652
|
// src/voice/instinct.ts
|
|
9448
|
-
var
|
|
9653
|
+
var log21 = forComponent("Instinct");
|
|
9449
9654
|
function voiceDeciderOptions(env = process.env) {
|
|
9450
9655
|
if (env.TYPESAFE_AI_KEY) return { apiKey: env.TYPESAFE_AI_KEY };
|
|
9451
9656
|
if (env.GROQ_API_KEY) return { provider: "llm", model: "groq/openai/gpt-oss-120b", apiKey: env.GROQ_API_KEY };
|
|
@@ -9716,13 +9921,13 @@ var Instinct = class {
|
|
|
9716
9921
|
* unfinished partial is no turn to react to — the interjection itself is the reply. */
|
|
9717
9922
|
pickAck(ctx) {
|
|
9718
9923
|
if (this.interjecting) {
|
|
9719
|
-
|
|
9924
|
+
log21.debug(`ack: cut-in turn \u2192 silent (engine context "${ctx.text.slice(0, 40)}" is the previous final)`);
|
|
9720
9925
|
return Promise.resolve(null);
|
|
9721
9926
|
}
|
|
9722
9927
|
const text = ctx.text.trim();
|
|
9723
9928
|
const p = (async () => {
|
|
9724
9929
|
const intentP = ctx.barged ? this.bargeIntent(text).catch((e) => {
|
|
9725
|
-
|
|
9930
|
+
log21.warn(`barge intent for the ack failed \u2014 situational ack: ${e.message}`);
|
|
9726
9931
|
return null;
|
|
9727
9932
|
}) : null;
|
|
9728
9933
|
const d = await this.ask(
|
|
@@ -9744,7 +9949,7 @@ var Instinct = class {
|
|
|
9744
9949
|
settle = r;
|
|
9745
9950
|
});
|
|
9746
9951
|
const timer = setTimeout(() => {
|
|
9747
|
-
|
|
9952
|
+
log21.debug(`ack: no engine outcome in ${this.o.ackSettleMs}ms \u2014 not claimed`);
|
|
9748
9953
|
settle(null);
|
|
9749
9954
|
}, this.o.ackSettleMs);
|
|
9750
9955
|
void spoken.then(() => clearTimeout(timer));
|
|
@@ -9793,7 +9998,7 @@ var Instinct = class {
|
|
|
9793
9998
|
});
|
|
9794
9999
|
return { kind, heard, choice: answers.act.choice, probabilities: answers.act.probabilities, ms: Math.round(this.o.now() - t0) };
|
|
9795
10000
|
} catch (e) {
|
|
9796
|
-
|
|
10001
|
+
log21.warn(`${kind} decision failed \u2014 falling back to engine defaults: ${e.message}`);
|
|
9797
10002
|
if (rethrow) throw e;
|
|
9798
10003
|
return null;
|
|
9799
10004
|
}
|
|
@@ -9802,7 +10007,7 @@ var Instinct = class {
|
|
|
9802
10007
|
|
|
9803
10008
|
// src/voice/unsaid.ts
|
|
9804
10009
|
init_logging();
|
|
9805
|
-
var
|
|
10010
|
+
var log22 = forComponent("Unsaid");
|
|
9806
10011
|
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
10012
|
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
10013
|
var SAID_OVERLAP = 0.6;
|
|
@@ -9842,7 +10047,7 @@ async function unsaidClauses(pieces, spoken, decider, timeoutMs = 5e3) {
|
|
|
9842
10047
|
return p < CONVEYED_MIN;
|
|
9843
10048
|
}).map((x) => x.c);
|
|
9844
10049
|
} catch (e) {
|
|
9845
|
-
|
|
10050
|
+
log22.warn(`unsaid judge failed \u2014 only near-zero-overlap clauses count as unsaid: ${e.message}`);
|
|
9846
10051
|
return fallback();
|
|
9847
10052
|
} finally {
|
|
9848
10053
|
clearTimeout(timer);
|
|
@@ -9963,7 +10168,7 @@ var RESULT_CLAIM_RE = new RegExp(LEAD_TAGS + String.raw`(?:(?:all\s+)?done|finis
|
|
|
9963
10168
|
var DONE_CLAIM_RE = new RegExp(LEAD_TAGS + String.raw`(?:(?:all\s+)?done|finished|it['’]?s\s+(?:done|finished)|completed?)\b`, "i");
|
|
9964
10169
|
|
|
9965
10170
|
// src/voice/channelLang.ts
|
|
9966
|
-
var
|
|
10171
|
+
var log23 = forComponent("ChannelBanks");
|
|
9967
10172
|
var LINE_KINDS = ["backchannel", "filler", "bridge"];
|
|
9968
10173
|
var BANK_KEYS = [...LINE_KINDS, "phatic"];
|
|
9969
10174
|
var EN_PHATIC = [
|
|
@@ -10117,11 +10322,11 @@ function cleanLine(s, lang, maxWords = 6) {
|
|
|
10117
10322
|
const raw = s.trim().split("\n")[0];
|
|
10118
10323
|
const l = raw.replace(/^[-*•\d.)\s]+/, "").replace(/^["'“”«»]+|["'“”«»]+$/g, "").replace(new RegExp("^\\p{L}+:\\s*", "u"), "").trim();
|
|
10119
10324
|
if (!l || isLeakSuspect(raw) || isLeakSuspect(l)) {
|
|
10120
|
-
if (l)
|
|
10325
|
+
if (l) log23.debug(`line "${raw}" reads as meta \u2014 rejected`);
|
|
10121
10326
|
return "";
|
|
10122
10327
|
}
|
|
10123
10328
|
if (lang && !fitsLanguage(l, lang)) {
|
|
10124
|
-
|
|
10329
|
+
log23.debug(`line "${l}" is not ${lang} \u2014 rejected`);
|
|
10125
10330
|
return "";
|
|
10126
10331
|
}
|
|
10127
10332
|
return l.split(/\s+/).length > maxWords || l.length > 60 ? "" : l;
|
|
@@ -10135,7 +10340,7 @@ function validateBank(raw, lang, minPerKind = 3) {
|
|
|
10135
10340
|
const seen = /* @__PURE__ */ new Set();
|
|
10136
10341
|
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)));
|
|
10137
10342
|
if (out[k].length < minPerKind) {
|
|
10138
|
-
|
|
10343
|
+
log23.warn(`bank ${lang}: only ${out[k].length} valid ${k} lines \u2014 rejected`);
|
|
10139
10344
|
return null;
|
|
10140
10345
|
}
|
|
10141
10346
|
}
|
|
@@ -10211,11 +10416,11 @@ var ChannelBanks = class {
|
|
|
10211
10416
|
const f = this.file(lang);
|
|
10212
10417
|
let cached = null;
|
|
10213
10418
|
if (f) cached = await readFile(f, "utf8").then((s) => JSON.parse(s)).catch((e) => {
|
|
10214
|
-
if (e.code !== "ENOENT")
|
|
10419
|
+
if (e.code !== "ENOENT") log23.warn(`bank cache ${f} unreadable \u2014 regenerating: ${e.message}`);
|
|
10215
10420
|
return null;
|
|
10216
10421
|
});
|
|
10217
10422
|
if (cached && cached.v !== BANK_VERSION) {
|
|
10218
|
-
|
|
10423
|
+
log23.debug(`bank ${lang}: cache v${cached.v ?? 1} predates v${BANK_VERSION} \u2014 regenerating`);
|
|
10219
10424
|
cached = null;
|
|
10220
10425
|
}
|
|
10221
10426
|
const valid = cached && validateBank(cached.bank, lang, 0);
|
|
@@ -10223,7 +10428,7 @@ var ChannelBanks = class {
|
|
|
10223
10428
|
if (valid) {
|
|
10224
10429
|
for (const k of BANK_KEYS) if (!valid[k].length) valid[k] = [...this.o.fallback[lang]?.[k] ?? []];
|
|
10225
10430
|
this.banks.set(lang, valid);
|
|
10226
|
-
|
|
10431
|
+
log23.debug(`bank ${lang}: cache hit (${BANK_KEYS.map((k) => valid[k].length).join("/")})${complete ? "" : " \u2014 missing kinds, refreshing"}`);
|
|
10227
10432
|
}
|
|
10228
10433
|
if (complete && this.o.now() - cached.at < this.o.refreshMs) return;
|
|
10229
10434
|
await this.generate(lang, valid);
|
|
@@ -10242,11 +10447,11 @@ var ChannelBanks = class {
|
|
|
10242
10447
|
bank[k] = [...fresh[k], ...have?.[k] ?? []].filter((x) => !seen.has(norm3(x)) && seen.add(norm3(x))).slice(0, this.o.maxPerKind);
|
|
10243
10448
|
}
|
|
10244
10449
|
this.banks.set(lang, bank);
|
|
10245
|
-
|
|
10450
|
+
log23.debug(`bank ${lang}: ${have ? "refreshed" : "generated"} (${BANK_KEYS.map((k) => bank[k].length).join("/")}) in ${Math.round(this.o.now() - t0)}ms`);
|
|
10246
10451
|
const f = this.file(lang);
|
|
10247
|
-
if (f) await mkdir(this.o.cacheDir, { recursive: true }).then(() => writeFile(f, JSON.stringify({ at: this.o.now(), v: BANK_VERSION, bank }))).catch((e) =>
|
|
10452
|
+
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}`));
|
|
10248
10453
|
} catch (e) {
|
|
10249
|
-
|
|
10454
|
+
log23.warn(`bank ${lang}: generation failed \u2014 ${have ? "keeping the cached bank" : lang in this.o.fallback ? "fallback bank" : "silent"}: ${e.message}`);
|
|
10250
10455
|
} finally {
|
|
10251
10456
|
clearTimeout(t);
|
|
10252
10457
|
if (!this.banks.has(lang)) setTimeout(() => this.pending.delete(lang), this.o.retryMs).unref?.();
|
|
@@ -10439,7 +10644,7 @@ var TurnGate = class {
|
|
|
10439
10644
|
};
|
|
10440
10645
|
|
|
10441
10646
|
// src/duplex.ts
|
|
10442
|
-
var
|
|
10647
|
+
var log24 = forComponent("DuplexAgent");
|
|
10443
10648
|
function describeCall(call) {
|
|
10444
10649
|
const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
|
|
10445
10650
|
const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
|
|
@@ -10774,7 +10979,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10774
10979
|
const m = this.reflexBuf.match(RESERVED_EVENT_MARKER) ?? this.reflexBuf.match(RESERVED_EVENT_OPENER);
|
|
10775
10980
|
if (m) {
|
|
10776
10981
|
this.fabricationCut = true;
|
|
10777
|
-
|
|
10982
|
+
log24.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
|
|
10778
10983
|
this.reflexBuf = this.reflexBuf.slice(0, Math.max(m.index, this.reflexForwarded));
|
|
10779
10984
|
this.pumpReflex(true);
|
|
10780
10985
|
return;
|
|
@@ -10916,7 +11121,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10916
11121
|
this.leakHit = true;
|
|
10917
11122
|
this.leakCuts.push(leak.trim().slice(0, 40));
|
|
10918
11123
|
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";
|
|
10919
|
-
|
|
11124
|
+
log24.warn(`reflex ${why} ("${leak.trim().slice(0, 40)}\u2026") \u2014 cut before TTS`);
|
|
10920
11125
|
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" });
|
|
10921
11126
|
const next = this.stepMarks.find((k) => k > at);
|
|
10922
11127
|
if (next === void 0) {
|
|
@@ -10957,7 +11162,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10957
11162
|
this.notify("diag", "leak_judge", { text: s.slice(0, 80), p: Math.round(p * 100) / 100, ms: Math.round(performance.now() - t0) });
|
|
10958
11163
|
return p >= LEAK_JUDGE_THRESHOLD;
|
|
10959
11164
|
}, (e) => {
|
|
10960
|
-
|
|
11165
|
+
log24.warn(`leak judge failed \u2014 speaking the sentence: ${e.message}`);
|
|
10961
11166
|
return false;
|
|
10962
11167
|
}).then((leak) => {
|
|
10963
11168
|
clearTimeout(timer);
|
|
@@ -11029,7 +11234,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11029
11234
|
let i = t.length - 1;
|
|
11030
11235
|
while (i >= this.turnBase && !(t[i].role === "assistant" && typeof t[i].content === "string" && t[i].content.includes(key))) i--;
|
|
11031
11236
|
if (i < this.turnBase) {
|
|
11032
|
-
|
|
11237
|
+
log24.warn("analysis leak cut, but its text was not found in the transcript to scrub");
|
|
11033
11238
|
continue;
|
|
11034
11239
|
}
|
|
11035
11240
|
const m = t[i];
|
|
@@ -11086,7 +11291,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11086
11291
|
const res = await this.sendWithContext(content, context, note);
|
|
11087
11292
|
const fb = this.options.reflexFallbackModel, model = this.voice.options.model;
|
|
11088
11293
|
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;
|
|
11089
|
-
|
|
11294
|
+
log24.warn(`reflex ${model} rate-limited \u2014 re-running the turn on ${fb}`);
|
|
11090
11295
|
this.notify("diag", "reflex_fallback", { from: model, to: fb });
|
|
11091
11296
|
this.voice.transcript.length = base;
|
|
11092
11297
|
this.voice.options.model = fb;
|
|
@@ -11135,7 +11340,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11135
11340
|
try {
|
|
11136
11341
|
await this.sendWithContext(reminder, this.lastContext);
|
|
11137
11342
|
} catch (e) {
|
|
11138
|
-
|
|
11343
|
+
log24.warn(`follow-through nudge failed: ${e instanceof Error ? e.message : e}`);
|
|
11139
11344
|
} finally {
|
|
11140
11345
|
const i = this.voice.transcript.findIndex((m, k) => k >= base && m.role === "user" && m.content === reminder);
|
|
11141
11346
|
if (i >= 0) this.voice.transcript.splice(i, 1);
|
|
@@ -11196,7 +11401,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11196
11401
|
return {
|
|
11197
11402
|
preToolUse: (call) => {
|
|
11198
11403
|
if (this.spec?.state === "pending" && !_DuplexAgent.SPEC_SAFE_TOOLS.has(call.name)) {
|
|
11199
|
-
|
|
11404
|
+
log24.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
|
|
11200
11405
|
this.abortSpeculation();
|
|
11201
11406
|
return { block: true, reason: "Speculative turn aborted." };
|
|
11202
11407
|
}
|
|
@@ -11278,7 +11483,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11278
11483
|
try {
|
|
11279
11484
|
await this.sendWithContext(reminder, this.lastContext);
|
|
11280
11485
|
} catch (e) {
|
|
11281
|
-
|
|
11486
|
+
log24.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
|
|
11282
11487
|
} finally {
|
|
11283
11488
|
this.nudging = false;
|
|
11284
11489
|
const i = this.voice.transcript.findIndex((m, k) => k >= base && m.role === "user" && m.content === reminder);
|
|
@@ -11334,7 +11539,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11334
11539
|
const accepted = this.offerOpen && this.parkedRedeliver.length > 0 && this.parkedRedeliver.every((p) => p.task === this.offer.task) && (isOfferAcceptance(content) || isTrivialBarge(content));
|
|
11335
11540
|
const judge = accepted ? void 0 : this.options.bargeIntent;
|
|
11336
11541
|
return (accepted ? Promise.resolve("resume") : judge ? judge(content).catch((e) => {
|
|
11337
|
-
|
|
11542
|
+
log24.warn(`bargeIntent judge failed \u2014 using the allowlist: ${e.message}`);
|
|
11338
11543
|
return fallback();
|
|
11339
11544
|
}) : Promise.resolve(fallback())).then((intent) => {
|
|
11340
11545
|
this.notify("diag", "barge_intent", { intent, text: content.slice(0, 80) });
|
|
@@ -11537,7 +11742,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11537
11742
|
try {
|
|
11538
11743
|
res = await this.sendWithContext(spec.text, this.lastContext);
|
|
11539
11744
|
} catch (e) {
|
|
11540
|
-
|
|
11745
|
+
log24.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
|
|
11541
11746
|
} finally {
|
|
11542
11747
|
this.voice.options.signal = prevSignal;
|
|
11543
11748
|
}
|
|
@@ -11551,7 +11756,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11551
11756
|
if (d === "abort") {
|
|
11552
11757
|
if (this.voice.transcript.length > base) this.voice.transcript.length = base;
|
|
11553
11758
|
this.speculativeAbortedCalls++;
|
|
11554
|
-
|
|
11759
|
+
log24.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
|
|
11555
11760
|
if (this.spec === spec) this.spec = void 0;
|
|
11556
11761
|
return res ?? empty;
|
|
11557
11762
|
}
|
|
@@ -11937,7 +12142,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
11937
12142
|
this.notify("task_verify", `task ${id}: verifying`, { id });
|
|
11938
12143
|
const cres = await new Agent(checkerOpts).run(checkBrief);
|
|
11939
12144
|
if (cres.finishReason !== "stop") {
|
|
11940
|
-
|
|
12145
|
+
log24.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
|
|
11941
12146
|
this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
|
|
11942
12147
|
}
|
|
11943
12148
|
const sum = (a = 0, b = 0) => a + b;
|
|
@@ -12073,7 +12278,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
12073
12278
|
rec.status = "done";
|
|
12074
12279
|
rec.result = res.text;
|
|
12075
12280
|
const incomplete = res.finishReason !== "stop" && res.finishReason !== "empty";
|
|
12076
|
-
|
|
12281
|
+
log24.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
|
|
12077
12282
|
this.notify("task_done", `task ${id} (${rec.label}) completed`, {
|
|
12078
12283
|
id,
|
|
12079
12284
|
text: res.text,
|
|
@@ -12107,14 +12312,14 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
12107
12312
|
foldIfSuperseded(rec, note) {
|
|
12108
12313
|
if (rec.deliveryParked !== "moved_on") return false;
|
|
12109
12314
|
if (note.trim()) this.voice.transcript.push({ role: "assistant", content: note });
|
|
12110
|
-
|
|
12315
|
+
log24.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
|
|
12111
12316
|
return true;
|
|
12112
12317
|
}
|
|
12113
12318
|
failTask(rec, msg) {
|
|
12114
12319
|
this.dropAsk(rec.id);
|
|
12115
12320
|
rec.status = "error";
|
|
12116
12321
|
rec.result = msg;
|
|
12117
|
-
|
|
12322
|
+
log24.warn(`task ${rec.id} failed: ${msg}`);
|
|
12118
12323
|
this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`, { id: rec.id });
|
|
12119
12324
|
if (this.foldIfSuperseded(rec, `The "${rec.label}" task failed: ${msg}`)) return;
|
|
12120
12325
|
this.dropParked(rec);
|
|
@@ -12418,7 +12623,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
12418
12623
|
init_llm();
|
|
12419
12624
|
init_logging();
|
|
12420
12625
|
init_models();
|
|
12421
|
-
var
|
|
12626
|
+
var log25 = forComponent("ModelRouter");
|
|
12422
12627
|
var ModelRouterOptions = class {
|
|
12423
12628
|
classifier = jevClassifier();
|
|
12424
12629
|
/** Capability tiers → model id (any provider). basic/standard run the main session; premium is only ever an offload. */
|
|
@@ -12502,10 +12707,10 @@ var ModelRouter = class {
|
|
|
12502
12707
|
if (tier === "premium") d.offload = this.state.offload && (c.sameThread ?? 0) >= o.sameThreadAt ? "continue" : "fresh";
|
|
12503
12708
|
} catch (e) {
|
|
12504
12709
|
const msg = e instanceof Error ? e.message : String(e);
|
|
12505
|
-
|
|
12710
|
+
log25.warn(`classifier failed, falling back to standard: ${msg}`);
|
|
12506
12711
|
d = { tier: "standard", model: o.models.standard, probs: {}, fallback: msg, classifyMs: Math.round(performance.now() - t0) };
|
|
12507
12712
|
}
|
|
12508
|
-
|
|
12713
|
+
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`);
|
|
12509
12714
|
return d;
|
|
12510
12715
|
}
|
|
12511
12716
|
/** Run one user turn on `agent` with routing. Returns a RunResult carrying `costUsd` + `routing`. */
|
|
@@ -12557,7 +12762,7 @@ ${text}`;
|
|
|
12557
12762
|
const answer = res.text || `(premium sub-agent finished with no reply; finishReason=${res.finishReason})`;
|
|
12558
12763
|
agent.transcript.push({ role: "user", content }, { role: "assistant", content: answer });
|
|
12559
12764
|
this.state.offload = { messages: child.transcript, mainLen: agent.transcript.length, question: text };
|
|
12560
|
-
|
|
12765
|
+
log25.info(`offload ${cont ? "continue" : "fresh"} done \xB7 ${res.finishReason} \xB7 ${res.steps} steps`);
|
|
12561
12766
|
const costUsd = o.costOf(main2, briefUsage) + o.costOf(o.models.premium, res.usage);
|
|
12562
12767
|
return { ...res, text: answer, messages: agent.transcript, usage: addUsage(briefUsage, res.usage), costUsd, routing: d };
|
|
12563
12768
|
}
|
|
@@ -12573,7 +12778,7 @@ The request:
|
|
|
12573
12778
|
${text}`);
|
|
12574
12779
|
return { text: r.text.trim() || text, usage: r.usage };
|
|
12575
12780
|
} catch (e) {
|
|
12576
|
-
|
|
12781
|
+
log25.warn(`brief failed, sending the raw request: ${e instanceof Error ? e.message : String(e)}`);
|
|
12577
12782
|
return { text };
|
|
12578
12783
|
} finally {
|
|
12579
12784
|
agent.transcript = snap;
|
|
@@ -12618,7 +12823,7 @@ async function jevAvailable(opts = {}) {
|
|
|
12618
12823
|
const { env } = await loadJev(opts);
|
|
12619
12824
|
return JEV_KEYS.some((k) => env[k]?.trim());
|
|
12620
12825
|
} catch (e) {
|
|
12621
|
-
|
|
12826
|
+
log25.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
|
|
12622
12827
|
return false;
|
|
12623
12828
|
}
|
|
12624
12829
|
}
|
|
@@ -12845,7 +13050,7 @@ init_models();
|
|
|
12845
13050
|
|
|
12846
13051
|
// src/voice/progress.ts
|
|
12847
13052
|
init_logging();
|
|
12848
|
-
var
|
|
13053
|
+
var log26 = forComponent("Progress");
|
|
12849
13054
|
var HE_RESULT_CLAIM = /(?:^|\s)(?:מצאתי|סיימתי|בוצע|הנה|אין)(?=\s|$|[.,!…])/u;
|
|
12850
13055
|
var TOPICS = [
|
|
12851
13056
|
[/toolsearch|^mcp__.*__(list_commands|call_info)$/, null],
|
|
@@ -12990,7 +13195,7 @@ var Progress = class {
|
|
|
12990
13195
|
}, this.o.generateBudgetMs);
|
|
12991
13196
|
});
|
|
12992
13197
|
const raw = await Promise.race([this.o.generate(tool, argsHint(args), lang, ac.signal).catch((e) => {
|
|
12993
|
-
|
|
13198
|
+
log26.warn(`progress line failed: ${e?.message ?? e}`);
|
|
12994
13199
|
return null;
|
|
12995
13200
|
}), late]).finally(() => clearTimeout(t));
|
|
12996
13201
|
const l = cleanLine(raw ?? "", lang, 5);
|
|
@@ -13012,7 +13217,7 @@ init_logging();
|
|
|
13012
13217
|
|
|
13013
13218
|
// src/voice/backchannelPlan.ts
|
|
13014
13219
|
init_logging();
|
|
13015
|
-
var
|
|
13220
|
+
var log27 = forComponent("BackchannelPlan");
|
|
13016
13221
|
var TURN_TYPES = ["story", "list", "complaint", "explanation", "short_command", "question", "other"];
|
|
13017
13222
|
var DENSITIES = ["none", "low", "normal", "high"];
|
|
13018
13223
|
var silentTurn = (p) => p.density === "none" || p.turn === "short_command" || p.turn === "question";
|
|
@@ -13077,13 +13282,13 @@ Reply JSON only: {"turn":"...","density":"...","cues":[{"when":["phrase",...],"l
|
|
|
13077
13282
|
const reasoning = /gpt-oss/.test(o.model) ? { providerOptions: { reasoning_effort: "low" } } : {};
|
|
13078
13283
|
const r = await this.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false, signal, maxTokens: o.maxTokens, ...reasoning });
|
|
13079
13284
|
const p = parsePlan(r.content ?? "", c.lang);
|
|
13080
|
-
if (!p)
|
|
13285
|
+
if (!p) log27.debug(`unusable plan: ${(r.content ?? "").slice(0, 120)}`);
|
|
13081
13286
|
return p;
|
|
13082
13287
|
}
|
|
13083
13288
|
};
|
|
13084
13289
|
|
|
13085
13290
|
// src/voice/secondChannel.ts
|
|
13086
|
-
var
|
|
13291
|
+
var log28 = forComponent("SecondChannel");
|
|
13087
13292
|
var CHANNEL_MODES = {
|
|
13088
13293
|
turn: { kinds: ["bridge"], bridgeDelayMs: 1500 },
|
|
13089
13294
|
normal: {},
|
|
@@ -13205,15 +13410,15 @@ function withBudget(p, ms, what, ac) {
|
|
|
13205
13410
|
let t;
|
|
13206
13411
|
const late = new Promise((r) => {
|
|
13207
13412
|
t = setTimeout(() => {
|
|
13208
|
-
|
|
13413
|
+
log28.debug(`${what}: over the ${ms}ms budget \u2014 dropped`);
|
|
13209
13414
|
ac?.abort();
|
|
13210
13415
|
r(null);
|
|
13211
13416
|
}, ms);
|
|
13212
13417
|
});
|
|
13213
13418
|
return Promise.race([p.catch((e) => {
|
|
13214
13419
|
const msg = String(e?.message ?? e);
|
|
13215
|
-
if (e?.name === "AbortError" || /aborted/i.test(msg))
|
|
13216
|
-
else
|
|
13420
|
+
if (e?.name === "AbortError" || /aborted/i.test(msg)) log28.debug(`${what}: aborted after its budget`);
|
|
13421
|
+
else log28.warn(`${what} failed \u2014 falling back: ${msg}`);
|
|
13217
13422
|
return null;
|
|
13218
13423
|
}), late]).finally(() => clearTimeout(t));
|
|
13219
13424
|
}
|
|
@@ -13292,7 +13497,7 @@ var SecondChannel = class {
|
|
|
13292
13497
|
if (host) this.pinned = true;
|
|
13293
13498
|
const lang = primaryLang(tag);
|
|
13294
13499
|
if (!lang || lang === this.lang) return;
|
|
13295
|
-
|
|
13500
|
+
log28.debug(`language ${this.lang} \u2192 ${lang}`);
|
|
13296
13501
|
this.lang = lang;
|
|
13297
13502
|
this.recent = [];
|
|
13298
13503
|
this.picks.clear();
|
|
@@ -13437,7 +13642,7 @@ var SecondChannel = class {
|
|
|
13437
13642
|
try {
|
|
13438
13643
|
this.o.cancel();
|
|
13439
13644
|
} catch (e) {
|
|
13440
|
-
|
|
13645
|
+
log28.warn(`cancel threw: ${e.message}`);
|
|
13441
13646
|
}
|
|
13442
13647
|
}
|
|
13443
13648
|
}
|
|
@@ -13549,7 +13754,7 @@ var SecondChannel = class {
|
|
|
13549
13754
|
const opts = this.options;
|
|
13550
13755
|
if (opts.mode === "chatty" && !opts.aec && !this.degradedLogged) {
|
|
13551
13756
|
this.degradedLogged = true;
|
|
13552
|
-
|
|
13757
|
+
log28.info("chatty mode without echo cancellation \u2014 normal timing (our lines would re-trigger the mic)");
|
|
13553
13758
|
}
|
|
13554
13759
|
const rule = this.rule(trigger, pauseMs, breath, cue);
|
|
13555
13760
|
if (rule === "silence") {
|
|
@@ -13568,7 +13773,7 @@ var SecondChannel = class {
|
|
|
13568
13773
|
const d = await withBudget(this.o.decide(this.tail(), { ...this.state(pauseMs), rule }, ac.signal), this.o.decisionBudgetMs, "channel decision", ac);
|
|
13569
13774
|
choice = d?.choice;
|
|
13570
13775
|
if (choice && choice in CHANNEL_ACTIONS && (choice === "silence" || this.allowed(choice))) kind = choice;
|
|
13571
|
-
else if (choice && choice in CHANNEL_ACTIONS)
|
|
13776
|
+
else if (choice && choice in CHANNEL_ACTIONS) log28.debug(`decider chose ${choice} for a ${rule} moment \u2014 the rule stands`);
|
|
13572
13777
|
}
|
|
13573
13778
|
if (kind === "silence") return ev({ choice, acted: "silence" });
|
|
13574
13779
|
if (stale()) return ev({ choice, kind, acted: "stale" });
|
|
@@ -13606,7 +13811,7 @@ var SecondChannel = class {
|
|
|
13606
13811
|
void withBudget(this.o.generate(ctx, ac.signal), Math.max(this.o.generateBudgetMs, this.o.bridgeDelayMs), "bridge generate", ac).then((raw) => {
|
|
13607
13812
|
const text = cleanLine(raw ?? "", ctx.lang);
|
|
13608
13813
|
if (text && gen === this.gen) this.bridgeDraft = { gen, text };
|
|
13609
|
-
}).catch((e) =>
|
|
13814
|
+
}).catch((e) => log28.debug(`bridge prefetch failed: ${e?.message ?? e}`));
|
|
13610
13815
|
}
|
|
13611
13816
|
async line(kind, react = true) {
|
|
13612
13817
|
if (kind === "bridge" && this.o.instantBridge) {
|
|
@@ -13622,7 +13827,7 @@ var SecondChannel = class {
|
|
|
13622
13827
|
if (raw === null) ac.abort();
|
|
13623
13828
|
const text = cleanLine(raw ?? "", this.lang);
|
|
13624
13829
|
if (text && !this.recent.some((r) => norm3(r) === norm3(text))) return text;
|
|
13625
|
-
if (text)
|
|
13830
|
+
if (text) log28.debug(`generated "${text}" repeats a recent line \u2014 canned`);
|
|
13626
13831
|
}
|
|
13627
13832
|
return this.pick(kind);
|
|
13628
13833
|
}
|
|
@@ -13644,7 +13849,7 @@ var SecondChannel = class {
|
|
|
13644
13849
|
try {
|
|
13645
13850
|
r = this.o.speak(text, kind);
|
|
13646
13851
|
} catch (e) {
|
|
13647
|
-
|
|
13852
|
+
log28.warn(`speak threw \u2014 line dropped: ${e.message}`);
|
|
13648
13853
|
return false;
|
|
13649
13854
|
}
|
|
13650
13855
|
if (r === false) return false;
|
|
@@ -13657,7 +13862,7 @@ var SecondChannel = class {
|
|
|
13657
13862
|
this.o.show(text, kind);
|
|
13658
13863
|
const p = Promise.resolve(r).then(() => {
|
|
13659
13864
|
}, (e) => {
|
|
13660
|
-
|
|
13865
|
+
log28.warn(`channel line playback failed: ${e?.message ?? e}`);
|
|
13661
13866
|
}).finally(() => {
|
|
13662
13867
|
if (this.inflight === p) this.inflight = null;
|
|
13663
13868
|
});
|
|
@@ -13715,7 +13920,7 @@ function closedQuestion(reply) {
|
|
|
13715
13920
|
}
|
|
13716
13921
|
|
|
13717
13922
|
// src/voice/call.ts
|
|
13718
|
-
var
|
|
13923
|
+
var log29 = forComponent("VoiceCall");
|
|
13719
13924
|
var VoiceCallOptions = class {
|
|
13720
13925
|
/** Host runs MAIN with exactly `text` (a replace: roll the `replaces` turn back first). */
|
|
13721
13926
|
sendTurn = () => {
|
|
@@ -13941,7 +14146,7 @@ var VoiceCall = class {
|
|
|
13941
14146
|
try {
|
|
13942
14147
|
this.o.cancel();
|
|
13943
14148
|
} catch (e) {
|
|
13944
|
-
|
|
14149
|
+
log29.warn(`cancel threw: ${e.message}`);
|
|
13945
14150
|
}
|
|
13946
14151
|
if (this.cur?.running) {
|
|
13947
14152
|
this.cur.running = false;
|
|
@@ -14028,7 +14233,7 @@ var VoiceCall = class {
|
|
|
14028
14233
|
try {
|
|
14029
14234
|
r = this.o.speak(text, kind);
|
|
14030
14235
|
} catch (e) {
|
|
14031
|
-
|
|
14236
|
+
log29.warn(`speak threw: ${e.message}`);
|
|
14032
14237
|
return false;
|
|
14033
14238
|
}
|
|
14034
14239
|
if (r === false) return false;
|
|
@@ -14114,7 +14319,7 @@ init_logging();
|
|
|
14114
14319
|
init_platform();
|
|
14115
14320
|
import { spawn } from "child_process";
|
|
14116
14321
|
import { createHash } from "crypto";
|
|
14117
|
-
var
|
|
14322
|
+
var log30 = forComponent("mcp");
|
|
14118
14323
|
var PROTOCOL_VERSION = "2025-06-18";
|
|
14119
14324
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
14120
14325
|
var DEFAULT_CALL_TIMEOUT_MS = 30 * 6e4;
|
|
@@ -14146,7 +14351,7 @@ var StdioTransport = class {
|
|
|
14146
14351
|
proc.stdout.setEncoding("utf8");
|
|
14147
14352
|
proc.stdout.on("data", (chunk) => this.onData(chunk));
|
|
14148
14353
|
proc.stderr.setEncoding("utf8");
|
|
14149
|
-
proc.stderr.on("data", (chunk) =>
|
|
14354
|
+
proc.stderr.on("data", (chunk) => log30.debug(`[${command}] stderr:`, chunk.trimEnd()));
|
|
14150
14355
|
proc.on("exit", (code) => this.failAll(new Error(`MCP server "${command}" exited (code ${code})`)));
|
|
14151
14356
|
proc.on("error", (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));
|
|
14152
14357
|
}
|
|
@@ -14160,7 +14365,7 @@ var StdioTransport = class {
|
|
|
14160
14365
|
try {
|
|
14161
14366
|
this.dispatch(JSON.parse(line));
|
|
14162
14367
|
} catch (e) {
|
|
14163
|
-
|
|
14368
|
+
log30.debug("dropping non-JSON line from MCP server:", line, e);
|
|
14164
14369
|
}
|
|
14165
14370
|
}
|
|
14166
14371
|
}
|
|
@@ -14170,7 +14375,7 @@ var StdioTransport = class {
|
|
|
14170
14375
|
try {
|
|
14171
14376
|
p2?.onProgress?.(msg.params ?? {});
|
|
14172
14377
|
} catch (e) {
|
|
14173
|
-
|
|
14378
|
+
log30.debug("onProgress handler threw", e);
|
|
14174
14379
|
}
|
|
14175
14380
|
return;
|
|
14176
14381
|
}
|
|
@@ -14208,7 +14413,7 @@ var StdioTransport = class {
|
|
|
14208
14413
|
const onAbort = () => {
|
|
14209
14414
|
if (!this.pending.delete(id)) return;
|
|
14210
14415
|
clearTimeout(timer);
|
|
14211
|
-
this.notify("notifications/cancelled", { requestId: id, reason: String(signal?.reason?.message ?? "cancelled") }).catch((e) =>
|
|
14416
|
+
this.notify("notifications/cancelled", { requestId: id, reason: String(signal?.reason?.message ?? "cancelled") }).catch((e) => log30.debug("cancel notify failed", e));
|
|
14212
14417
|
reject(abortError(signal));
|
|
14213
14418
|
};
|
|
14214
14419
|
const done = () => signal?.removeEventListener("abort", onAbort);
|
|
@@ -14232,7 +14437,7 @@ var StdioTransport = class {
|
|
|
14232
14437
|
try {
|
|
14233
14438
|
this.proc?.stdin?.end();
|
|
14234
14439
|
} catch (e) {
|
|
14235
|
-
|
|
14440
|
+
log30.debug("stdin end failed", e);
|
|
14236
14441
|
}
|
|
14237
14442
|
if (!(IS_WIN && killTree(this.proc?.pid, "SIGTERM"))) this.proc?.kill();
|
|
14238
14443
|
}
|
|
@@ -14267,7 +14472,7 @@ var HttpTransport = class {
|
|
|
14267
14472
|
return await this.post({ jsonrpc: "2.0", id, method, params: opts.onProgress ? withProgressToken(params, id) : params }, false, signal, opts.onProgress);
|
|
14268
14473
|
} catch (e) {
|
|
14269
14474
|
if (opts.signal?.aborted) {
|
|
14270
|
-
void this.notify("notifications/cancelled", { requestId: id, reason: String(opts.signal.reason?.message ?? "cancelled") }).catch((err2) =>
|
|
14475
|
+
void this.notify("notifications/cancelled", { requestId: id, reason: String(opts.signal.reason?.message ?? "cancelled") }).catch((err2) => log30.debug("cancel notify failed", err2));
|
|
14271
14476
|
throw abortError(opts.signal);
|
|
14272
14477
|
}
|
|
14273
14478
|
throw e;
|
|
@@ -14310,7 +14515,7 @@ function sseMessage(line) {
|
|
|
14310
14515
|
try {
|
|
14311
14516
|
return JSON.parse(trimmed.slice(5).trim());
|
|
14312
14517
|
} catch (e) {
|
|
14313
|
-
|
|
14518
|
+
log30.debug("skipping unparseable SSE data line", e);
|
|
14314
14519
|
return void 0;
|
|
14315
14520
|
}
|
|
14316
14521
|
}
|
|
@@ -14320,7 +14525,7 @@ async function readSseResponse(resp, id, onProgress) {
|
|
|
14320
14525
|
try {
|
|
14321
14526
|
onProgress?.(obj.params ?? {});
|
|
14322
14527
|
} catch (e) {
|
|
14323
|
-
|
|
14528
|
+
log30.debug("onProgress handler threw", e);
|
|
14324
14529
|
}
|
|
14325
14530
|
return void 0;
|
|
14326
14531
|
}
|
|
@@ -14419,7 +14624,7 @@ async function mountWithDeadline(name, cfg, mountTimeoutMs) {
|
|
|
14419
14624
|
return { name, client, tools, specs, serverInfo: init?.serverInfo, config: cfg };
|
|
14420
14625
|
})(), mountTimeoutMs, name);
|
|
14421
14626
|
} catch (e) {
|
|
14422
|
-
await client.close().catch((err2) =>
|
|
14627
|
+
await client.close().catch((err2) => log30.debug(`close after failed mount of "${name}": ${err2}`));
|
|
14423
14628
|
throw e;
|
|
14424
14629
|
}
|
|
14425
14630
|
}
|
|
@@ -14430,15 +14635,15 @@ function validEntries(servers) {
|
|
|
14430
14635
|
return Object.entries(servers).filter(([name, cfg]) => {
|
|
14431
14636
|
if (!cfg || cfg.disabled) return false;
|
|
14432
14637
|
if (!cfg.command && !cfg.url) {
|
|
14433
|
-
|
|
14638
|
+
log30.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
|
|
14434
14639
|
return false;
|
|
14435
14640
|
}
|
|
14436
14641
|
return true;
|
|
14437
14642
|
});
|
|
14438
14643
|
}
|
|
14439
14644
|
function logMountFailure(name, e) {
|
|
14440
|
-
if (e instanceof McpAuthError)
|
|
14441
|
-
else
|
|
14645
|
+
if (e instanceof McpAuthError) log30.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
|
|
14646
|
+
else log30.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
|
|
14442
14647
|
}
|
|
14443
14648
|
async function mountMcpServers(servers = {}, opts = {}) {
|
|
14444
14649
|
const entries = validEntries(servers);
|
|
@@ -14448,7 +14653,7 @@ async function mountMcpServers(servers = {}, opts = {}) {
|
|
|
14448
14653
|
const name = entries[i][0];
|
|
14449
14654
|
if (r.status === "fulfilled") {
|
|
14450
14655
|
out.push(r.value);
|
|
14451
|
-
|
|
14656
|
+
log30.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
|
|
14452
14657
|
} else logMountFailure(name, r.reason);
|
|
14453
14658
|
});
|
|
14454
14659
|
return out;
|
|
@@ -14488,7 +14693,7 @@ var McpPool = class {
|
|
|
14488
14693
|
const prev = this.warm.get(key);
|
|
14489
14694
|
if (prev) {
|
|
14490
14695
|
clearTimeout(prev.timer);
|
|
14491
|
-
if (prev.client !== client) void prev.client.close().catch((err2) =>
|
|
14696
|
+
if (prev.client !== client) void prev.client.close().catch((err2) => log30.debug(`warm-pool replace close failed: ${err2}`));
|
|
14492
14697
|
}
|
|
14493
14698
|
const e = { client, timer: void 0 };
|
|
14494
14699
|
this.warm.set(key, e);
|
|
@@ -14505,7 +14710,7 @@ var McpPool = class {
|
|
|
14505
14710
|
const e = this.warm.get(key);
|
|
14506
14711
|
if (!e) return;
|
|
14507
14712
|
this.warm.delete(key);
|
|
14508
|
-
await e.client.close().catch((err2) =>
|
|
14713
|
+
await e.client.close().catch((err2) => log30.debug(`warm-pool evict close failed: ${err2}`));
|
|
14509
14714
|
}
|
|
14510
14715
|
async closeAll() {
|
|
14511
14716
|
for (const e of this.warm.values()) {
|
|
@@ -14724,7 +14929,7 @@ init_tools_shell();
|
|
|
14724
14929
|
// src/tools.notify.ts
|
|
14725
14930
|
init_logging();
|
|
14726
14931
|
import { execFile as execFile2 } from "child_process";
|
|
14727
|
-
var
|
|
14932
|
+
var log32 = forComponent("notify");
|
|
14728
14933
|
var WIN_TOAST = [
|
|
14729
14934
|
"[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null",
|
|
14730
14935
|
"$t = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)",
|
|
@@ -14755,7 +14960,7 @@ function makeNotifyTool(opts = {}) {
|
|
|
14755
14960
|
return new Promise((resolve6) => {
|
|
14756
14961
|
run(argv[0], argv[1], { timeout: 5e3, ...extra }, (e) => {
|
|
14757
14962
|
if (e) {
|
|
14758
|
-
|
|
14963
|
+
log32.debug("notification failed", e);
|
|
14759
14964
|
resolve6(`Notification failed: ${e.message}`);
|
|
14760
14965
|
} else resolve6("Notification shown.");
|
|
14761
14966
|
});
|
|
@@ -14771,7 +14976,7 @@ import { BodDB as BodDB2 } from "@bod.ee/db";
|
|
|
14771
14976
|
init_logging();
|
|
14772
14977
|
import { existsSync as existsSync2, readFileSync as readFileSync5 } from "fs";
|
|
14773
14978
|
import { homedir } from "os";
|
|
14774
|
-
var
|
|
14979
|
+
var log33 = forComponent("cli-util");
|
|
14775
14980
|
function dotDirs(base, sub, opts = {}) {
|
|
14776
14981
|
const home = opts.home ?? homedir();
|
|
14777
14982
|
const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
|
|
@@ -14788,7 +14993,7 @@ function parseJson(text, fallback, what = "json") {
|
|
|
14788
14993
|
try {
|
|
14789
14994
|
return JSON.parse(text);
|
|
14790
14995
|
} catch (e) {
|
|
14791
|
-
|
|
14996
|
+
log33.debug(`parseJson(${what}) failed: ${e.message}`);
|
|
14792
14997
|
return fallback;
|
|
14793
14998
|
}
|
|
14794
14999
|
}
|
|
@@ -14798,13 +15003,14 @@ function readJsonFile(path, fallback) {
|
|
|
14798
15003
|
try {
|
|
14799
15004
|
text = readFileSync5(path, "utf8");
|
|
14800
15005
|
} catch (e) {
|
|
14801
|
-
|
|
15006
|
+
log33.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
|
|
14802
15007
|
return fallback;
|
|
14803
15008
|
}
|
|
14804
15009
|
return parseJson(text, fallback, path);
|
|
14805
15010
|
}
|
|
14806
15011
|
|
|
14807
15012
|
// cli/core.ts
|
|
15013
|
+
init_models();
|
|
14808
15014
|
init_hostPath();
|
|
14809
15015
|
init_platform();
|
|
14810
15016
|
var DEFAULT_TOOLS = ["bash", "Read", "Edit", "Write", "Grep", "Glob", "MultiEdit", "ApplyEdits", "RepoMap", "TodoWrite"];
|
|
@@ -15001,6 +15207,14 @@ Reference files in them by their mount path (the left side).`;
|
|
|
15001
15207
|
const po = providerOptionsFor(o.model, cwd, o.mcpServers, o.hostMcpServers);
|
|
15002
15208
|
return po ? { providerOptions: o.lean && isClaudeCode ? { ...po, settingSources: [] } : po } : {};
|
|
15003
15209
|
})(),
|
|
15210
|
+
// A child on ANOTHER provider (delegate mode's anthropic planner → claude-code/* worker, or back) gets that
|
|
15211
|
+
// runtime's own options (cwd, env, MCP) — the Agent default inherits only same-provider options, so a claude-code
|
|
15212
|
+
// worker under an anthropic planner ran with none. Same provider: the Agent default (parent's, minus its resume).
|
|
15213
|
+
providerOptionsFor: (m) => {
|
|
15214
|
+
const a = agentRef.agent, po = a?.options.providerOptions;
|
|
15215
|
+
if (modelProvider(m) === modelProvider(a?.options.model ?? "")) return po ? { ...po, resume: void 0, onClaudeCodeSession: void 0 } : void 0;
|
|
15216
|
+
return providerOptionsFor(m, cwd, o.mcpServers, o.hostMcpServers);
|
|
15217
|
+
},
|
|
15004
15218
|
...(() => {
|
|
15005
15219
|
const now4 = /* @__PURE__ */ new Date();
|
|
15006
15220
|
const platformNames = { darwin: "macOS", linux: "Linux", win32: "Windows" };
|
|
@@ -15102,7 +15316,7 @@ import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as rea
|
|
|
15102
15316
|
import { homedir as homedir3 } from "os";
|
|
15103
15317
|
import { dirname as dirname4, join as join8 } from "path";
|
|
15104
15318
|
import { fileURLToPath } from "url";
|
|
15105
|
-
var
|
|
15319
|
+
var log34 = forComponent("VoiceIO");
|
|
15106
15320
|
var now3 = () => performance.now();
|
|
15107
15321
|
var Player = class {
|
|
15108
15322
|
proc = null;
|
|
@@ -15116,7 +15330,7 @@ var Player = class {
|
|
|
15116
15330
|
["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
|
|
15117
15331
|
{ stdio: ["pipe", "ignore", "ignore"] }
|
|
15118
15332
|
);
|
|
15119
|
-
this.proc.on("error", (e) =>
|
|
15333
|
+
this.proc.on("error", (e) => log34.warn(`ffplay error: ${e.message}`));
|
|
15120
15334
|
this.proc.stdin.on("error", () => {
|
|
15121
15335
|
});
|
|
15122
15336
|
this.bytesWritten = 0;
|
|
@@ -15154,7 +15368,7 @@ function detectFfmpegMic() {
|
|
|
15154
15368
|
const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
|
|
15155
15369
|
const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
|
|
15156
15370
|
if (!mic) throw new Error("no audio input device found");
|
|
15157
|
-
|
|
15371
|
+
log34.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
|
|
15158
15372
|
return `:${mic.idx}`;
|
|
15159
15373
|
}
|
|
15160
15374
|
function detectDshowMic() {
|
|
@@ -15165,7 +15379,7 @@ function detectDshowMic() {
|
|
|
15165
15379
|
const devices = [...(r.stderr ?? "").matchAll(/"([^"]+)"\s*\(audio\)/g)].map((m) => m[1]);
|
|
15166
15380
|
const mic = devices.find((d) => !/stereo mix|loopback|virtual|cable|teams/i.test(d)) ?? devices[0];
|
|
15167
15381
|
if (!mic) throw new Error("no audio input device found (set MIC_DEVICE to the DirectShow device name)");
|
|
15168
|
-
|
|
15382
|
+
log34.debug(`ffmpeg dshow mic: ${mic}`);
|
|
15169
15383
|
return `audio=${mic}`;
|
|
15170
15384
|
}
|
|
15171
15385
|
function detectedInputDevice() {
|
|
@@ -15201,15 +15415,15 @@ function resolveAecBinary() {
|
|
|
15201
15415
|
if (existsSync4(bin) && statSync6(bin).mtimeMs >= statSync6(src).mtimeMs) return bin;
|
|
15202
15416
|
if (spawnSync3("which", ["swiftc"]).status !== 0) return null;
|
|
15203
15417
|
mkdirSync5(cacheDir, { recursive: true });
|
|
15204
|
-
|
|
15418
|
+
log34.info("compiling AEC mic helper (first run)\u2026");
|
|
15205
15419
|
const build = spawnSync3("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
|
|
15206
15420
|
if (build.status !== 0) {
|
|
15207
|
-
|
|
15421
|
+
log34.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
|
|
15208
15422
|
return null;
|
|
15209
15423
|
}
|
|
15210
15424
|
const sign = spawnSync3("codesign", ["-fs", "-", bin], { encoding: "utf8" });
|
|
15211
15425
|
if (sign.status !== 0) {
|
|
15212
|
-
|
|
15426
|
+
log34.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
|
|
15213
15427
|
return null;
|
|
15214
15428
|
}
|
|
15215
15429
|
return bin;
|
|
@@ -15251,17 +15465,17 @@ var NodeMicSource = class {
|
|
|
15251
15465
|
this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
|
|
15252
15466
|
} else {
|
|
15253
15467
|
if (!hasCommand("ffmpeg")) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
|
|
15254
|
-
|
|
15468
|
+
log34.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
|
|
15255
15469
|
const input = process.platform === "win32" ? ["-f", "dshow", "-i", detectDshowMic()] : ["-f", "avfoundation", "-i", detectFfmpegMic()];
|
|
15256
15470
|
this.proc = spawn2(
|
|
15257
15471
|
"ffmpeg",
|
|
15258
15472
|
["-loglevel", "error", ...input, "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
|
|
15259
15473
|
{ stdio: ["ignore", "pipe", "pipe"], ...process.platform === "win32" ? { windowsHide: true } : {} }
|
|
15260
15474
|
);
|
|
15261
|
-
this.proc.stderr.on("data", (d) =>
|
|
15475
|
+
this.proc.stderr.on("data", (d) => log34.warn(`ffmpeg: ${String(d).trim()}`));
|
|
15262
15476
|
}
|
|
15263
15477
|
this.proc.on("exit", (c) => {
|
|
15264
|
-
if (c && !this.stopped)
|
|
15478
|
+
if (c && !this.stopped) log34.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
|
|
15265
15479
|
});
|
|
15266
15480
|
this.proc.stdout.on("data", (chunk) => onChunk(chunk));
|
|
15267
15481
|
}
|
|
@@ -15327,7 +15541,7 @@ var AecDuplexAudio = class {
|
|
|
15327
15541
|
this.proc.stdin.on("error", () => {
|
|
15328
15542
|
});
|
|
15329
15543
|
this.proc.on("exit", (c) => {
|
|
15330
|
-
if (c && !this.stopped)
|
|
15544
|
+
if (c && !this.stopped) log34.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
|
|
15331
15545
|
});
|
|
15332
15546
|
this.proc.stdout.on("data", (chunk) => {
|
|
15333
15547
|
this.gotChunk = true;
|
|
@@ -15343,7 +15557,7 @@ var AecDuplexAudio = class {
|
|
|
15343
15557
|
openMicSettings();
|
|
15344
15558
|
this.onFatal?.("microphone permission denied \u2014 enable it in System Settings \u2192 Privacy & Security \u2192 Microphone for your terminal, then restart it");
|
|
15345
15559
|
}
|
|
15346
|
-
} else
|
|
15560
|
+
} else log34.debug(`mic-aec: ${s}`);
|
|
15347
15561
|
}
|
|
15348
15562
|
});
|
|
15349
15563
|
if (!this.noVpio && !this.triedFallback) {
|
|
@@ -15352,7 +15566,7 @@ var AecDuplexAudio = class {
|
|
|
15352
15566
|
this.triedFallback = true;
|
|
15353
15567
|
this.noVpio = true;
|
|
15354
15568
|
this._aec = false;
|
|
15355
|
-
|
|
15569
|
+
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)");
|
|
15356
15570
|
this.onDegrade?.();
|
|
15357
15571
|
this.killProc();
|
|
15358
15572
|
this.spawnHelper();
|
|
@@ -15523,7 +15737,7 @@ function bindAckInstinct(v, instinct) {
|
|
|
15523
15737
|
if (ev.kind === "ack_fired") instinct.ackOutcome(String(ev.phrase));
|
|
15524
15738
|
else if (ev.kind === "ack_skipped" || ev.kind === "ack_cancelled") instinct.ackOutcome(null);
|
|
15525
15739
|
} catch (e) {
|
|
15526
|
-
|
|
15740
|
+
log34.warn(`ack outcome tap failed: ${e instanceof Error ? e.message : e}`);
|
|
15527
15741
|
}
|
|
15528
15742
|
onDiag(ev);
|
|
15529
15743
|
};
|
|
@@ -15645,7 +15859,7 @@ function speakBlip(v, text) {
|
|
|
15645
15859
|
v.fireBackchannel();
|
|
15646
15860
|
return true;
|
|
15647
15861
|
} catch (e) {
|
|
15648
|
-
|
|
15862
|
+
log34.warn(`backchannel blip failed \u2014 line dropped: ${e.message}`);
|
|
15649
15863
|
return false;
|
|
15650
15864
|
} finally {
|
|
15651
15865
|
E.BACKCHANNELS = pool;
|
|
@@ -15664,7 +15878,7 @@ function channelInput(sc, m) {
|
|
|
15664
15878
|
}
|
|
15665
15879
|
if (m.type === "agent_state") {
|
|
15666
15880
|
if (typeof m.state === "string" && MAIN_STATES.has(m.state)) sc.onAgentState(m.state);
|
|
15667
|
-
else
|
|
15881
|
+
else log34.warn(`agent_state: unknown state ${JSON.stringify(m.state)} \u2014 ignored`);
|
|
15668
15882
|
return true;
|
|
15669
15883
|
}
|
|
15670
15884
|
return false;
|
|
@@ -15790,7 +16004,7 @@ function fakeVoiceParts(uttFile) {
|
|
|
15790
16004
|
init_logging();
|
|
15791
16005
|
import { appendFile, mkdirSync as mkdirSync6 } from "fs";
|
|
15792
16006
|
import { dirname as dirname5 } from "path";
|
|
15793
|
-
var
|
|
16007
|
+
var log35 = forComponent("VoiceDiag");
|
|
15794
16008
|
var JsonlDiagSink = class {
|
|
15795
16009
|
constructor(path) {
|
|
15796
16010
|
this.path = path;
|
|
@@ -15831,7 +16045,7 @@ var JsonlDiagSink = class {
|
|
|
15831
16045
|
disable(e) {
|
|
15832
16046
|
this.disabled = true;
|
|
15833
16047
|
this.queue.length = 0;
|
|
15834
|
-
|
|
16048
|
+
log35.debug(`voice diag sink disabled (${this.path}): ${e instanceof Error ? e.message : e}`);
|
|
15835
16049
|
}
|
|
15836
16050
|
/** True once a failure permanently disabled the sink (tests/inspection). */
|
|
15837
16051
|
get isDisabled() {
|
|
@@ -15989,7 +16203,7 @@ function applyOverlay(cfg, file) {
|
|
|
15989
16203
|
// cli/hooks-config.ts
|
|
15990
16204
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
15991
16205
|
init_platform();
|
|
15992
|
-
var
|
|
16206
|
+
var log36 = forComponent("hooks");
|
|
15993
16207
|
var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
15994
16208
|
function ruleMatches(rule, toolName) {
|
|
15995
16209
|
if (!rule.tool || rule.tool === "*") return true;
|
|
@@ -16008,7 +16222,7 @@ function runCmd(rule, env) {
|
|
|
16008
16222
|
});
|
|
16009
16223
|
return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
|
|
16010
16224
|
} catch (e) {
|
|
16011
|
-
|
|
16225
|
+
log36.debug(`hook command failed: ${rule.command}`, e);
|
|
16012
16226
|
return { code: 1, out: String(e?.message ?? e) };
|
|
16013
16227
|
}
|
|
16014
16228
|
}
|
|
@@ -16116,7 +16330,7 @@ import { existsSync as existsSync6, mkdirSync as mkdirSync8, readFileSync as rea
|
|
|
16116
16330
|
import { homedir as homedir6 } from "os";
|
|
16117
16331
|
import { join as join11 } from "path";
|
|
16118
16332
|
init_platform();
|
|
16119
|
-
var
|
|
16333
|
+
var log37 = forComponent("session");
|
|
16120
16334
|
var globalDir = () => join11(homedir6(), ".agent", "sessions");
|
|
16121
16335
|
function indexLink(target, link2) {
|
|
16122
16336
|
try {
|
|
@@ -16179,7 +16393,7 @@ var SessionStore = class {
|
|
|
16179
16393
|
}
|
|
16180
16394
|
load(id) {
|
|
16181
16395
|
if (!this.safeId(id)) {
|
|
16182
|
-
|
|
16396
|
+
log37.debug(`rejecting unsafe session id: ${id}`);
|
|
16183
16397
|
return void 0;
|
|
16184
16398
|
}
|
|
16185
16399
|
const path = join11(this.dir, `${id}.json`);
|
|
@@ -16187,7 +16401,7 @@ var SessionStore = class {
|
|
|
16187
16401
|
try {
|
|
16188
16402
|
return JSON.parse(readFileSync8(path, "utf8"));
|
|
16189
16403
|
} catch (e) {
|
|
16190
|
-
|
|
16404
|
+
log37.debug(`unreadable session ${id} \u2014 ignoring`, e);
|
|
16191
16405
|
return void 0;
|
|
16192
16406
|
}
|
|
16193
16407
|
}
|
|
@@ -16200,7 +16414,7 @@ var SessionStore = class {
|
|
|
16200
16414
|
try {
|
|
16201
16415
|
metas.push(JSON.parse(readFileSync8(join11(this.dir, f), "utf8")).meta);
|
|
16202
16416
|
} catch (e) {
|
|
16203
|
-
|
|
16417
|
+
log37.debug(`skipping unreadable session file ${f}`, e);
|
|
16204
16418
|
}
|
|
16205
16419
|
}
|
|
16206
16420
|
return metas.sort((a, b) => b.updated - a.updated);
|
|
@@ -16336,6 +16550,199 @@ ${body || "(no conversation)"}
|
|
|
16336
16550
|
(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.)`;
|
|
16337
16551
|
}
|
|
16338
16552
|
|
|
16553
|
+
// src/directives.ts
|
|
16554
|
+
init_models();
|
|
16555
|
+
var VENDOR_SHORTHAND = { cc: "claude-code", cur: "cursor", oa: "openai" };
|
|
16556
|
+
var DIRECTIVE_KEYS = ["model", "turns", "thinking", "think", "effort", "engine", "resume", "delegate", "advisor", "mode"];
|
|
16557
|
+
function splitModePair(token) {
|
|
16558
|
+
const i = token.search(/->|<-/);
|
|
16559
|
+
if (i === -1) return null;
|
|
16560
|
+
const right = token.slice(i + 2).trim();
|
|
16561
|
+
return right ? { left: token.slice(0, i).trim(), op: token.slice(i, i + 2), right } : null;
|
|
16562
|
+
}
|
|
16563
|
+
var isModel = (t, p) => {
|
|
16564
|
+
const h = resolveModelToken(t, p);
|
|
16565
|
+
return !!h && "model" in h;
|
|
16566
|
+
};
|
|
16567
|
+
var defaultResolver = null;
|
|
16568
|
+
var opt = (o) => ({
|
|
16569
|
+
resolveModel: defaultResolver,
|
|
16570
|
+
resolverFirst: false,
|
|
16571
|
+
aliases: MODEL_ALIASES,
|
|
16572
|
+
vendors: VENDOR_SHORTHAND,
|
|
16573
|
+
keys: DIRECTIVE_KEYS,
|
|
16574
|
+
strict: false,
|
|
16575
|
+
catalogOnly: false,
|
|
16576
|
+
warn: (m) => console.warn(m),
|
|
16577
|
+
...o
|
|
16578
|
+
});
|
|
16579
|
+
var DIRECTIVE_RE = /^\s*\[([^\]]*)\](?=\s|$)\s*([\s\S]*)/;
|
|
16580
|
+
var isQualifiedModel = (v) => /^[a-z0-9._-]+\/[a-z0-9./_-]+$/.test(v);
|
|
16581
|
+
function expandVendor(token, vendors = VENDOR_SHORTHAND) {
|
|
16582
|
+
const v = token.trim().toLowerCase();
|
|
16583
|
+
const m = v.match(/^([a-z0-9]+)[-/](.+)$/);
|
|
16584
|
+
if (m && vendors[m[1]]) return `${vendors[m[1]]}/${m[2]}`;
|
|
16585
|
+
const slash = v.indexOf("/");
|
|
16586
|
+
return slash > 0 && Object.values(vendors).includes(v.slice(0, slash)) ? v : null;
|
|
16587
|
+
}
|
|
16588
|
+
function resolveModelToken(token, o) {
|
|
16589
|
+
const p = opt(o);
|
|
16590
|
+
const v = token.trim().toLowerCase();
|
|
16591
|
+
const vendored = expandVendor(v, p.vendors);
|
|
16592
|
+
if (vendored) return p.resolveModel ? p.resolveModel(vendored) : p.catalogOnly ? null : { model: vendored };
|
|
16593
|
+
if (p.catalogOnly) return v.includes("/") || p.aliases[v] || /\d/.test(v) ? p.resolveModel?.(v) ?? null : null;
|
|
16594
|
+
const alias = p.aliases[v] ? { model: p.aliases[v] } : null;
|
|
16595
|
+
if (!p.resolverFirst) return alias ?? p.resolveModel?.(v) ?? null;
|
|
16596
|
+
const hit = p.resolveModel?.(v) ?? null;
|
|
16597
|
+
return hit && "model" in hit ? hit : alias ?? hit;
|
|
16598
|
+
}
|
|
16599
|
+
var THINK = ["think", "adaptive", "nothink", "nothinking"];
|
|
16600
|
+
function isDirectiveGroup(raw, o) {
|
|
16601
|
+
const p = opt(o);
|
|
16602
|
+
const tokens = raw.split(",").map((t) => t.trim()).filter(Boolean);
|
|
16603
|
+
if (!tokens.length) return false;
|
|
16604
|
+
return tokens.every((t) => {
|
|
16605
|
+
const i = t.indexOf(":");
|
|
16606
|
+
if (i !== -1) return p.keys.includes(t.slice(0, i).trim().toLowerCase());
|
|
16607
|
+
const v = t.toLowerCase();
|
|
16608
|
+
const pair = splitModePair(v);
|
|
16609
|
+
if (pair) return p.keys.includes(pair.op === "->" ? "delegate" : "advisor") && (!!pair.left && isModel(pair.left, p) || isModel(pair.right, p));
|
|
16610
|
+
if (v === "off") return p.keys.includes("mode");
|
|
16611
|
+
return !!resolveModelToken(v, p) || p.catalogOnly && !!p.aliases[v] || p.keys.includes("turns") && /^\d+$/.test(v) || p.keys.includes("thinking") && THINK.includes(v);
|
|
16612
|
+
});
|
|
16613
|
+
}
|
|
16614
|
+
function splitDirectiveGroups(text, o) {
|
|
16615
|
+
const p = opt(o);
|
|
16616
|
+
let rest = text;
|
|
16617
|
+
const groups = [];
|
|
16618
|
+
for (; ; ) {
|
|
16619
|
+
const m = rest.match(DIRECTIVE_RE);
|
|
16620
|
+
if (!m) break;
|
|
16621
|
+
const g = m[1].trim();
|
|
16622
|
+
if ((groups.length || p.strict) && g && !isDirectiveGroup(g, p)) break;
|
|
16623
|
+
groups.push(g);
|
|
16624
|
+
rest = m[2];
|
|
16625
|
+
}
|
|
16626
|
+
return { groups, body: rest };
|
|
16627
|
+
}
|
|
16628
|
+
function parseDirectives(text, o) {
|
|
16629
|
+
const p = opt(o);
|
|
16630
|
+
const { groups, body } = splitDirectiveGroups(text, p);
|
|
16631
|
+
if (!groups.length) return { prompt: text, directives: {} };
|
|
16632
|
+
const prompt = body.trim();
|
|
16633
|
+
const raw = groups.filter(Boolean).join(",");
|
|
16634
|
+
if (!raw) return { prompt, directives: {} };
|
|
16635
|
+
const d = {};
|
|
16636
|
+
const out = { prompt, directives: d };
|
|
16637
|
+
const tokens = raw.split(",");
|
|
16638
|
+
const proven = tokens.some((t) => {
|
|
16639
|
+
const i = t.indexOf(":");
|
|
16640
|
+
return i !== -1 && p.keys.includes(t.slice(0, i).trim().toLowerCase());
|
|
16641
|
+
});
|
|
16642
|
+
const fail = (tok, hit) => {
|
|
16643
|
+
out.unresolvedModel ??= tok;
|
|
16644
|
+
if (hit && "ambiguous" in hit) out.ambiguous ??= hit.ambiguous;
|
|
16645
|
+
};
|
|
16646
|
+
const setModel = (val) => {
|
|
16647
|
+
const hit = resolveModelToken(val, p);
|
|
16648
|
+
if (!hit || !("model" in hit)) return hit;
|
|
16649
|
+
d.model = hit.model;
|
|
16650
|
+
if (hit.engine && !d.engine) d.engine = hit.engine;
|
|
16651
|
+
return true;
|
|
16652
|
+
};
|
|
16653
|
+
let pos = 0;
|
|
16654
|
+
for (const token of tokens) {
|
|
16655
|
+
const t = token.trim();
|
|
16656
|
+
if (!t) continue;
|
|
16657
|
+
const i = t.indexOf(":");
|
|
16658
|
+
if (i !== -1) {
|
|
16659
|
+
const key = t.slice(0, i).trim().toLowerCase(), val2 = t.slice(i + 1).trim().toLowerCase();
|
|
16660
|
+
if (!p.keys.includes(key)) {
|
|
16661
|
+
p.warn(`[directives] Unknown directive key: "${key}"`);
|
|
16662
|
+
continue;
|
|
16663
|
+
}
|
|
16664
|
+
if (key === "delegate" || key === "advisor") {
|
|
16665
|
+
const hit = resolveModelToken(val2, p);
|
|
16666
|
+
if (hit && "model" in hit) d[key] = hit.model;
|
|
16667
|
+
else if (!hit && isQualifiedModel(val2) && !p.catalogOnly) d[key] = val2;
|
|
16668
|
+
else fail(val2, hit);
|
|
16669
|
+
continue;
|
|
16670
|
+
}
|
|
16671
|
+
if (key === "model") {
|
|
16672
|
+
const r2 = setModel(val2);
|
|
16673
|
+
if (r2 === true) continue;
|
|
16674
|
+
if (!r2 && isQualifiedModel(val2) && !p.catalogOnly) {
|
|
16675
|
+
d.model = val2;
|
|
16676
|
+
continue;
|
|
16677
|
+
}
|
|
16678
|
+
fail(val2, r2);
|
|
16679
|
+
} else applyKey(d, key, val2, p.warn);
|
|
16680
|
+
continue;
|
|
16681
|
+
}
|
|
16682
|
+
const val = t.toLowerCase();
|
|
16683
|
+
const pair = splitModePair(val);
|
|
16684
|
+
if (pair && p.keys.includes(pair.op === "->" ? "delegate" : "advisor")) {
|
|
16685
|
+
const lhit = pair.left ? resolveModelToken(pair.left, p) : null, rhit = resolveModelToken(pair.right, p);
|
|
16686
|
+
const lok = !pair.left || !!lhit && "model" in lhit, rok = !!rhit && "model" in rhit;
|
|
16687
|
+
if (lok && rok) {
|
|
16688
|
+
if (pair.left) setModel(pair.left);
|
|
16689
|
+
if (pair.op === "->") d.delegate = rhit.model;
|
|
16690
|
+
else d.advisor = rhit.model;
|
|
16691
|
+
} else if (lok || rok) {
|
|
16692
|
+
if (!lok) fail(pair.left, lhit);
|
|
16693
|
+
else fail(pair.right, rhit);
|
|
16694
|
+
} else p.warn(`[directives] Unknown models in "${t}"`);
|
|
16695
|
+
pos++;
|
|
16696
|
+
continue;
|
|
16697
|
+
}
|
|
16698
|
+
if (val === "off" && p.keys.includes("mode")) {
|
|
16699
|
+
d.modeOff = true;
|
|
16700
|
+
pos++;
|
|
16701
|
+
continue;
|
|
16702
|
+
}
|
|
16703
|
+
const r = pos === 0 ? setModel(val) : null;
|
|
16704
|
+
if (r === true) {
|
|
16705
|
+
} else if (pos <= 1 && /^\d+$/.test(val) && p.keys.includes("turns")) d.turns = parseInt(val, 10);
|
|
16706
|
+
else if (["think", "adaptive"].includes(val) && p.keys.includes("thinking")) d.thinking = "adaptive";
|
|
16707
|
+
else if (["nothink", "nothinking"].includes(val) && p.keys.includes("thinking")) d.thinking = "disabled";
|
|
16708
|
+
else if (pos === 0) {
|
|
16709
|
+
if (r || p.catalogOnly && p.aliases[val] || proven && !isQualifiedModel(val)) fail(t, r);
|
|
16710
|
+
p.warn(`[directives] Unknown model: "${t}"`);
|
|
16711
|
+
}
|
|
16712
|
+
pos++;
|
|
16713
|
+
}
|
|
16714
|
+
return out;
|
|
16715
|
+
}
|
|
16716
|
+
function applyKey(d, key, val, warn) {
|
|
16717
|
+
switch (key) {
|
|
16718
|
+
case "turns":
|
|
16719
|
+
if (/^\d+$/.test(val)) d.turns = parseInt(val, 10);
|
|
16720
|
+
else warn(`[directives] Invalid turns value: "${val}"`);
|
|
16721
|
+
break;
|
|
16722
|
+
case "thinking":
|
|
16723
|
+
case "think":
|
|
16724
|
+
if (["adaptive", "enabled", "disabled"].includes(val)) d.thinking = val;
|
|
16725
|
+
else warn(`[directives] Invalid thinking value: "${val}"`);
|
|
16726
|
+
break;
|
|
16727
|
+
case "effort":
|
|
16728
|
+
if (["low", "medium", "high", "max"].includes(val)) d.effort = val;
|
|
16729
|
+
else warn(`[directives] Invalid effort value: "${val}"`);
|
|
16730
|
+
break;
|
|
16731
|
+
case "engine":
|
|
16732
|
+
d.engine = val;
|
|
16733
|
+
break;
|
|
16734
|
+
case "mode":
|
|
16735
|
+
if (val === "off") d.modeOff = true;
|
|
16736
|
+
else warn(`[directives] Invalid mode value: "${val}"`);
|
|
16737
|
+
break;
|
|
16738
|
+
case "resume":
|
|
16739
|
+
if (["on", "true", "1"].includes(val)) d.resume = true;
|
|
16740
|
+
else if (["off", "false", "0"].includes(val)) d.resume = false;
|
|
16741
|
+
else warn(`[directives] Invalid resume value: "${val}"`);
|
|
16742
|
+
break;
|
|
16743
|
+
}
|
|
16744
|
+
}
|
|
16745
|
+
|
|
16339
16746
|
// cli/checkpoints.ts
|
|
16340
16747
|
var MUTATORS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "ApplyEdits"]);
|
|
16341
16748
|
function mutatedPaths(call) {
|
|
@@ -16439,7 +16846,7 @@ import { promisify } from "util";
|
|
|
16439
16846
|
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync9 } from "fs";
|
|
16440
16847
|
import { homedir as homedir7 } from "os";
|
|
16441
16848
|
import { join as join12, resolve as resolve4, sep as sep3, parse as parsePath } from "path";
|
|
16442
|
-
var
|
|
16849
|
+
var log38 = forComponent("checkpoints");
|
|
16443
16850
|
var exec = promisify(execFile4);
|
|
16444
16851
|
var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
|
|
16445
16852
|
function isBroadRoot(p) {
|
|
@@ -16487,7 +16894,7 @@ var ShadowRepo = class {
|
|
|
16487
16894
|
writeFileSync6(join12(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
|
|
16488
16895
|
this.ready = true;
|
|
16489
16896
|
} catch (e) {
|
|
16490
|
-
|
|
16897
|
+
log38.debug(`git checkpoints unavailable for ${this.workTree}`, e);
|
|
16491
16898
|
this.ready = false;
|
|
16492
16899
|
}
|
|
16493
16900
|
return this.ready;
|
|
@@ -16498,7 +16905,7 @@ var ShadowRepo = class {
|
|
|
16498
16905
|
}
|
|
16499
16906
|
async commit(label, forced = []) {
|
|
16500
16907
|
await this.run("add", "-A");
|
|
16501
|
-
for (const p of forced) await this.run("add", "-f", "--", p).catch((e) =>
|
|
16908
|
+
for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log38.debug(`force-add failed: ${p}`, e));
|
|
16502
16909
|
await this.run("commit", "--allow-empty", "-q", "-m", label);
|
|
16503
16910
|
}
|
|
16504
16911
|
/** Inject the CURRENT (pre-edit) content of `paths` into the turn-open restore point by amending it.
|
|
@@ -16506,8 +16913,8 @@ var ShadowRepo = class {
|
|
|
16506
16913
|
* turn-boundary `add -A` would never have captured it. Amend (vs a new commit) keeps one restore
|
|
16507
16914
|
* point per turn, so the REPL's turn↔frame mapping stays intact. */
|
|
16508
16915
|
async amendForced(paths) {
|
|
16509
|
-
for (const p of paths) await this.run("add", "-f", "--", p).catch((e) =>
|
|
16510
|
-
await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) =>
|
|
16916
|
+
for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log38.debug(`force-capture failed: ${p}`, e));
|
|
16917
|
+
await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log38.debug("amend failed", e));
|
|
16511
16918
|
}
|
|
16512
16919
|
/** Commits on `ref`, oldest-first (canonical index space). */
|
|
16513
16920
|
async log(ref) {
|
|
@@ -16567,7 +16974,7 @@ var ShadowRepo = class {
|
|
|
16567
16974
|
await this.run("gc", "--auto", "-q").catch(() => {
|
|
16568
16975
|
});
|
|
16569
16976
|
} catch (e) {
|
|
16570
|
-
|
|
16977
|
+
log38.debug("checkpoint prune failed", e);
|
|
16571
16978
|
}
|
|
16572
16979
|
}
|
|
16573
16980
|
};
|
|
@@ -16635,7 +17042,7 @@ var GitCheckpoints = class {
|
|
|
16635
17042
|
use(sessionId) {
|
|
16636
17043
|
if (sessionId === this.session) return;
|
|
16637
17044
|
this.session = sessionId;
|
|
16638
|
-
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) =>
|
|
17045
|
+
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log38.debug("re-point failed", e));
|
|
16639
17046
|
}
|
|
16640
17047
|
async begin(label) {
|
|
16641
17048
|
if (!await this.start()) return;
|
|
@@ -16655,7 +17062,7 @@ var GitCheckpoints = class {
|
|
|
16655
17062
|
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
|
|
16656
17063
|
`);
|
|
16657
17064
|
} else {
|
|
16658
|
-
|
|
17065
|
+
log38.debug("checkpoint commit failed", e);
|
|
16659
17066
|
survivors.push(this.repos[i]);
|
|
16660
17067
|
survivorCaches.push(this.caches[i] ?? []);
|
|
16661
17068
|
}
|
|
@@ -16690,7 +17097,7 @@ var GitCheckpoints = class {
|
|
|
16690
17097
|
if (this.forced.has(abs)) continue;
|
|
16691
17098
|
this.forced.add(abs);
|
|
16692
17099
|
const i = this.repoIndexFor(abs);
|
|
16693
|
-
if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) =>
|
|
17100
|
+
if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log38.debug("amendForced failed", e));
|
|
16694
17101
|
}
|
|
16695
17102
|
}
|
|
16696
17103
|
};
|
|
@@ -17801,7 +18208,7 @@ function applyVimNormal(s, key, str) {
|
|
|
17801
18208
|
}
|
|
17802
18209
|
return "none";
|
|
17803
18210
|
}
|
|
17804
|
-
function
|
|
18211
|
+
function applyKey2(s, key, str) {
|
|
17805
18212
|
const k = key?.name;
|
|
17806
18213
|
if (s.vim === "normal" && !s.pasting && !s.searching) {
|
|
17807
18214
|
const r = applyVimNormal(s, key ?? {}, str);
|
|
@@ -18129,7 +18536,7 @@ function createLineEditor(out, input = keyInput) {
|
|
|
18129
18536
|
});
|
|
18130
18537
|
return;
|
|
18131
18538
|
}
|
|
18132
|
-
const action =
|
|
18539
|
+
const action = applyKey2(s, key ?? {}, str);
|
|
18133
18540
|
if (action === "submit") {
|
|
18134
18541
|
finish();
|
|
18135
18542
|
opts.onSubmit?.();
|
|
@@ -18475,7 +18882,7 @@ import { spawnSync as spawnSync6 } from "child_process";
|
|
|
18475
18882
|
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync7 } from "fs";
|
|
18476
18883
|
import { homedir as homedir8 } from "os";
|
|
18477
18884
|
import { join as join14 } from "path";
|
|
18478
|
-
var
|
|
18885
|
+
var log39 = forComponent("os-sched");
|
|
18479
18886
|
var OsScheduler = class {
|
|
18480
18887
|
options;
|
|
18481
18888
|
constructor(options) {
|
|
@@ -18537,7 +18944,7 @@ var OsScheduler = class {
|
|
|
18537
18944
|
}
|
|
18538
18945
|
}
|
|
18539
18946
|
} catch (e) {
|
|
18540
|
-
|
|
18947
|
+
log39.debug(`cancel ${id}`, e);
|
|
18541
18948
|
}
|
|
18542
18949
|
for (const f of [`${id}.json`, `${id}.sh`]) {
|
|
18543
18950
|
try {
|
|
@@ -18655,7 +19062,7 @@ import { existsSync as existsSync8, mkdirSync as mkdirSync11, unlinkSync as unli
|
|
|
18655
19062
|
import { homedir as homedir9 } from "os";
|
|
18656
19063
|
import { join as join15 } from "path";
|
|
18657
19064
|
init_platform();
|
|
18658
|
-
var
|
|
19065
|
+
var log40 = forComponent("remote-trigger");
|
|
18659
19066
|
var TRIGGER_DIR = () => join15(homedir9(), ".agent", "triggers");
|
|
18660
19067
|
var sockPath = (sessionId, dir = TRIGGER_DIR()) => join15(dir, `${sessionId}.sock`);
|
|
18661
19068
|
var TriggerServer = class {
|
|
@@ -18693,13 +19100,13 @@ var TriggerServer = class {
|
|
|
18693
19100
|
conn.end(JSON.stringify({ ok: false, error: String(e) }) + "\n");
|
|
18694
19101
|
}
|
|
18695
19102
|
});
|
|
18696
|
-
conn.on("error", (e) =>
|
|
19103
|
+
conn.on("error", (e) => log40.debug("trigger conn error", e));
|
|
18697
19104
|
});
|
|
18698
|
-
this.server.on("error", (e) =>
|
|
19105
|
+
this.server.on("error", (e) => log40.debug("trigger server error", e));
|
|
18699
19106
|
this.server.listen(p);
|
|
18700
19107
|
this.path = p;
|
|
18701
19108
|
} catch (e) {
|
|
18702
|
-
|
|
19109
|
+
log40.debug("trigger server unavailable", e);
|
|
18703
19110
|
}
|
|
18704
19111
|
}
|
|
18705
19112
|
/** Re-bind on /resume (the session id changed). */
|
|
@@ -18831,7 +19238,7 @@ var strike = C("9");
|
|
|
18831
19238
|
var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
|
|
18832
19239
|
var mdPalette = { bold, dim, cyan, italic, strike, green, link };
|
|
18833
19240
|
var err = (s) => process.stderr.write(s);
|
|
18834
|
-
var
|
|
19241
|
+
var log41 = forComponent("cli");
|
|
18835
19242
|
var VERSION = (() => {
|
|
18836
19243
|
try {
|
|
18837
19244
|
return JSON.parse(readFileSync10(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
|
|
@@ -18971,7 +19378,13 @@ function parseArgs(argv) {
|
|
|
18971
19378
|
a.harden = true;
|
|
18972
19379
|
a.hardenNet = true;
|
|
18973
19380
|
} else if (x === "--subagents") a.subagents = true;
|
|
18974
|
-
else if (x === "--
|
|
19381
|
+
else if (x === "--delegate") a.delegate = argv[++i];
|
|
19382
|
+
else if (x === "--advisor") a.advisor = argv[++i];
|
|
19383
|
+
else if (x === "--mode-scope") {
|
|
19384
|
+
const v = argv[++i];
|
|
19385
|
+
if (v !== "sticky" && v !== "message") throw new Error("--mode-scope must be sticky|message");
|
|
19386
|
+
a.modeScope = v;
|
|
19387
|
+
} else if (x === "--duplex") a.duplex = true;
|
|
18975
19388
|
else if (x === "--duplex-v2") {
|
|
18976
19389
|
a.duplex = true;
|
|
18977
19390
|
a.duplexV2 = true;
|
|
@@ -19105,6 +19518,9 @@ Flags:
|
|
|
19105
19518
|
--add-dir <path> mount another directory into the workspace (repeatable; disk mode only)
|
|
19106
19519
|
--mcp-config <file> JSON { mcpServers?, permissions? } merged over .agent config for this run only
|
|
19107
19520
|
--subagents allow the Task tool (spawn child agents)
|
|
19521
|
+
--delegate <worker> delegate mode: the main model plans (its writes are refused), <worker> writes via Task
|
|
19522
|
+
--advisor <model> advisor mode: the main model works; an Advise tool consults <model> (first write waits for it)
|
|
19523
|
+
--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
|
|
19108
19524
|
--reasoning <e> extended thinking: off|low|medium|high or a token budget (anthropic/openai)
|
|
19109
19525
|
--session-id <id> use this id for the session (instead of an auto-generated one)
|
|
19110
19526
|
--fork-session with -r/-c: branch the resumed session into a new id (source left untouched)
|
|
@@ -19714,7 +20130,7 @@ function turnCost(model, usage) {
|
|
|
19714
20130
|
const priced = model.startsWith("claude-code/") ? `anthropic/${resolveModelAlias(model.slice("claude-code/".length))}` : model;
|
|
19715
20131
|
return costOf(getModelInfo(priced)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, priced);
|
|
19716
20132
|
}
|
|
19717
|
-
async function evaluateGoal(ai, condition, transcript,
|
|
20133
|
+
async function evaluateGoal(ai, condition, transcript, log42) {
|
|
19718
20134
|
const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
|
|
19719
20135
|
const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
|
|
19720
20136
|
return text.slice(0, 600);
|
|
@@ -19734,7 +20150,7 @@ ${recent}` }
|
|
|
19734
20150
|
const match = r.content.match(/\{[\s\S]*\}/);
|
|
19735
20151
|
if (match) return JSON.parse(match[0]);
|
|
19736
20152
|
} catch (e) {
|
|
19737
|
-
|
|
20153
|
+
log42(dim(` (goal evaluator error: ${e?.message ?? e})
|
|
19738
20154
|
`));
|
|
19739
20155
|
}
|
|
19740
20156
|
return { met: false, reason: "evaluation unclear" };
|
|
@@ -19961,7 +20377,7 @@ function mcpAgentTools(mounted, opts) {
|
|
|
19961
20377
|
return tools;
|
|
19962
20378
|
}
|
|
19963
20379
|
async function closeMcp(mounted) {
|
|
19964
|
-
await Promise.all(mounted.map((m) => m.client.close().catch((e) =>
|
|
20380
|
+
await Promise.all(mounted.map((m) => m.client.close().catch((e) => log41.debug("mcp close failed", e))));
|
|
19965
20381
|
}
|
|
19966
20382
|
var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
|
|
19967
20383
|
function mentionRefs(line) {
|
|
@@ -20020,7 +20436,7 @@ async function expandMentions(fs, line) {
|
|
|
20020
20436
|
if (loaded.includes(ref) || missing.includes(ref)) continue;
|
|
20021
20437
|
if (ref.includes(":") && mcpMentionResolver) {
|
|
20022
20438
|
const body = await mcpMentionResolver(ref).catch((e) => {
|
|
20023
|
-
|
|
20439
|
+
log41.debug("mcp mention resolve failed", e);
|
|
20024
20440
|
return null;
|
|
20025
20441
|
});
|
|
20026
20442
|
if (body != null) {
|
|
@@ -20045,6 +20461,33 @@ ${await fs.readFile(path)}`);
|
|
|
20045
20461
|
|
|
20046
20462
|
${blocks.join("\n\n")}` : line, loaded, missing };
|
|
20047
20463
|
}
|
|
20464
|
+
function applyTurnMode(agent, session, task, flags = {}, onModel) {
|
|
20465
|
+
const p = parseDirectives(task, { strict: true, keys: ["model", "turns", "delegate", "advisor", "mode"], warn: (m) => log41.debug(m) });
|
|
20466
|
+
const d = p.directives, isMode = !!(d.delegate || d.advisor || d.modeOff);
|
|
20467
|
+
if (p.unresolvedModel && /->|<-|delegate:|advisor:/.test(task.slice(0, task.length - p.prompt.length))) {
|
|
20468
|
+
err(red(` \u2717 unknown model "${p.unresolvedModel}"${p.ambiguous ? ` (could be: ${p.ambiguous.join(", ")})` : ""} \u2014 turn not sent
|
|
20469
|
+
`));
|
|
20470
|
+
return null;
|
|
20471
|
+
}
|
|
20472
|
+
const r = (m) => m ? resolveModelOrNewest(m) : void 0;
|
|
20473
|
+
const fromDirective = isMode ? d.modeOff ? {} : { model: r(d.model), delegate: r(d.delegate), advisor: r(d.advisor) } : void 0;
|
|
20474
|
+
if (fromDirective && flags.modeScope !== "message") session.meta.mode = fromDirective;
|
|
20475
|
+
const mode = fromDirective ?? session.meta.mode ?? { delegate: flags.delegate && r(flags.delegate), advisor: flags.advisor && r(flags.advisor) };
|
|
20476
|
+
const o = agent.options, before = JSON.stringify([o.model, o.delegate, o.advisor]);
|
|
20477
|
+
if (mode.model && mode.model !== o.model) {
|
|
20478
|
+
o.model = mode.model;
|
|
20479
|
+
o.contextWindow = getModelInfo(mode.model)?.contextWindow ?? 0;
|
|
20480
|
+
onModel?.(mode.model);
|
|
20481
|
+
}
|
|
20482
|
+
o.delegate = mode.delegate ? { worker: mode.delegate } : void 0;
|
|
20483
|
+
o.advisor = mode.advisor ? { ...o.advisor, model: mode.advisor } : void 0;
|
|
20484
|
+
if (JSON.stringify([o.model, o.delegate, o.advisor]) !== before) {
|
|
20485
|
+
agent.reprepare();
|
|
20486
|
+
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"}
|
|
20487
|
+
`));
|
|
20488
|
+
}
|
|
20489
|
+
return isMode ? p.prompt : task;
|
|
20490
|
+
}
|
|
20048
20491
|
function jsonResult(res, session) {
|
|
20049
20492
|
const lastUser = res.messages.map((m) => m.role).lastIndexOf("user");
|
|
20050
20493
|
return {
|
|
@@ -20056,6 +20499,7 @@ function jsonResult(res, session) {
|
|
|
20056
20499
|
tools: res.messages.slice(lastUser).filter((m) => m.role === "tool").length,
|
|
20057
20500
|
usage: res.usage,
|
|
20058
20501
|
...res.budget ? { budget: res.budget } : {},
|
|
20502
|
+
...res.workerUsage ? { workerModel: res.workerModel, workerUsage: res.workerUsage } : {},
|
|
20059
20503
|
...res.routing ? { costUsd: res.costUsd, routing: res.routing } : {},
|
|
20060
20504
|
// cast: tsup dts hoists a modelRouter import above the shebang
|
|
20061
20505
|
sessionId: session.meta.id,
|
|
@@ -20111,7 +20555,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
20111
20555
|
try {
|
|
20112
20556
|
store.save(session);
|
|
20113
20557
|
} catch (ex) {
|
|
20114
|
-
|
|
20558
|
+
log41.debug("mid-turn session flush failed", ex);
|
|
20115
20559
|
}
|
|
20116
20560
|
}
|
|
20117
20561
|
return origNotify(e);
|
|
@@ -20140,7 +20584,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
20140
20584
|
agent.options.host?.flushText?.();
|
|
20141
20585
|
flushLookupGroup();
|
|
20142
20586
|
const secs2 = ((Date.now() - t0) / 1e3).toFixed(1);
|
|
20143
|
-
const cost = res.costUsd ?? turnCost(agent.options.model, res.usage);
|
|
20587
|
+
const cost = (res.costUsd ?? turnCost(agent.options.model, res.usage)) + (res.workerUsage && res.workerModel ? turnCost(res.workerModel, res.workerUsage) : 0);
|
|
20144
20588
|
const m = res.usageEstimated ? "~" : "";
|
|
20145
20589
|
const tok = res.usage?.totalTokens ? `${m}${(res.usage.totalTokens / 1e3).toFixed(1)}k tok \xB7 ${cost > 0 ? m + fmtUsd(cost) + " \xB7 " : ""}` : "";
|
|
20146
20590
|
agent.options.host?.takeToolCount?.();
|
|
@@ -20275,14 +20719,14 @@ var isCancelTeardown = (e) => {
|
|
|
20275
20719
|
function installCancelGuards(mounted) {
|
|
20276
20720
|
process.on("unhandledRejection", (e) => {
|
|
20277
20721
|
if (isCancelTeardown(e)) {
|
|
20278
|
-
|
|
20722
|
+
log41.debug("suppressed unhandledRejection (cursor stream cancel)", e);
|
|
20279
20723
|
return;
|
|
20280
20724
|
}
|
|
20281
|
-
|
|
20725
|
+
log41.error("unhandledRejection", e);
|
|
20282
20726
|
});
|
|
20283
20727
|
process.on("uncaughtException", (e) => {
|
|
20284
20728
|
if (isCancelTeardown(e)) {
|
|
20285
|
-
|
|
20729
|
+
log41.debug("suppressed uncaughtException (cursor stream cancel)", e);
|
|
20286
20730
|
return;
|
|
20287
20731
|
}
|
|
20288
20732
|
console.error(e);
|
|
@@ -20651,7 +21095,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
20651
21095
|
editorRef?.abort();
|
|
20652
21096
|
}
|
|
20653
21097
|
} catch (e) {
|
|
20654
|
-
|
|
21098
|
+
log41.error("call turn failed", e);
|
|
20655
21099
|
} finally {
|
|
20656
21100
|
rec.running = false;
|
|
20657
21101
|
call?.main({ type: "done", id });
|
|
@@ -20783,7 +21227,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
20783
21227
|
try {
|
|
20784
21228
|
mkdirSync12(dir, { recursive: true });
|
|
20785
21229
|
} catch (e) {
|
|
20786
|
-
|
|
21230
|
+
log41.debug("paste image dir create failed", e);
|
|
20787
21231
|
}
|
|
20788
21232
|
const img = grabClipboardImage(dir, String(Date.now()));
|
|
20789
21233
|
return img ? { display: "Image", ref: "@" + img.path, path: img.path } : null;
|
|
@@ -20896,7 +21340,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
20896
21340
|
mkdirSync12(join16(cwd, ".agent"), { recursive: true });
|
|
20897
21341
|
appendFileSync(histPath, line + "\n");
|
|
20898
21342
|
} catch (e) {
|
|
20899
|
-
|
|
21343
|
+
log41.debug("history write failed", e);
|
|
20900
21344
|
}
|
|
20901
21345
|
};
|
|
20902
21346
|
const ago = (t) => {
|
|
@@ -20968,7 +21412,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
20968
21412
|
try {
|
|
20969
21413
|
store.save(session);
|
|
20970
21414
|
} catch (e) {
|
|
20971
|
-
|
|
21415
|
+
log41.debug("session save after rewind failed", e);
|
|
20972
21416
|
}
|
|
20973
21417
|
err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
|
|
20974
21418
|
`));
|
|
@@ -21003,13 +21447,20 @@ ${task}`;
|
|
|
21003
21447
|
bangContext.length = 0;
|
|
21004
21448
|
}
|
|
21005
21449
|
const delta = await refreshCatalogs().catch((e) => {
|
|
21006
|
-
|
|
21450
|
+
log41.debug("catalog refresh failed", e);
|
|
21007
21451
|
return "";
|
|
21008
21452
|
});
|
|
21009
21453
|
if (delta) {
|
|
21010
21454
|
err(dim(" \u27F3 skill/command catalog changed \u2014 delta attached to this turn\n"));
|
|
21011
21455
|
task += "\n\n" + delta;
|
|
21012
21456
|
}
|
|
21457
|
+
if (!duplex) {
|
|
21458
|
+
const t = applyTurnMode(face, session, task, args, (m) => {
|
|
21459
|
+
face.options.providerOptions = providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name));
|
|
21460
|
+
});
|
|
21461
|
+
if (t === null) return { ok: false };
|
|
21462
|
+
task = t;
|
|
21463
|
+
}
|
|
21013
21464
|
const r = await runTurn(face, store, session, task, duplex ? void 0 : checkpoints, cwd, sendVia ? (c) => sendVia(c, opts) : routeVia);
|
|
21014
21465
|
if (voiceIO) {
|
|
21015
21466
|
voiceReasonEnd();
|
|
@@ -21266,7 +21717,7 @@ ${task}`;
|
|
|
21266
21717
|
desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
|
|
21267
21718
|
run: async () => {
|
|
21268
21719
|
await refreshCatalogs().catch((e) => {
|
|
21269
|
-
|
|
21720
|
+
log41.debug("catalog refresh failed", e);
|
|
21270
21721
|
});
|
|
21271
21722
|
face.reprepare();
|
|
21272
21723
|
err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
|
|
@@ -21769,7 +22220,7 @@ ${task}`;
|
|
|
21769
22220
|
try {
|
|
21770
22221
|
for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
|
|
21771
22222
|
} catch (e) {
|
|
21772
|
-
|
|
22223
|
+
log41.debug(`loadAgents(${d}) failed`, e);
|
|
21773
22224
|
}
|
|
21774
22225
|
}
|
|
21775
22226
|
if (!seen.size) {
|
|
@@ -21857,7 +22308,7 @@ ${task}`;
|
|
|
21857
22308
|
}
|
|
21858
22309
|
if (idx >= 0) {
|
|
21859
22310
|
const old = mounted.splice(idx, 1)[0];
|
|
21860
|
-
await old.client.close().catch((e) =>
|
|
22311
|
+
await old.client.close().catch((e) => log41.debug("mcp close failed", e));
|
|
21861
22312
|
}
|
|
21862
22313
|
try {
|
|
21863
22314
|
const m = await mountMcpServer(name, conf);
|
|
@@ -21885,7 +22336,7 @@ ${task}`;
|
|
|
21885
22336
|
}
|
|
21886
22337
|
const m = mounted.splice(idx, 1)[0];
|
|
21887
22338
|
remountMcpTools();
|
|
21888
|
-
await m.client.close().catch((e) =>
|
|
22339
|
+
await m.client.close().catch((e) => log41.debug("mcp close failed", e));
|
|
21889
22340
|
err(dim(` removed "${name}"
|
|
21890
22341
|
`));
|
|
21891
22342
|
return;
|
|
@@ -22088,7 +22539,7 @@ ${task}`;
|
|
|
22088
22539
|
try {
|
|
22089
22540
|
return readdirSync4(join16(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
|
|
22090
22541
|
} catch (e) {
|
|
22091
|
-
|
|
22542
|
+
log41.debug("completion readdir failed", absDir, e);
|
|
22092
22543
|
return null;
|
|
22093
22544
|
}
|
|
22094
22545
|
};
|
|
@@ -22148,7 +22599,7 @@ ${task}`;
|
|
|
22148
22599
|
}
|
|
22149
22600
|
return;
|
|
22150
22601
|
}
|
|
22151
|
-
const action =
|
|
22602
|
+
const action = applyKey2(sEd, key ?? {}, _s);
|
|
22152
22603
|
if (action === "submit") {
|
|
22153
22604
|
const text = sEd.expand().trim();
|
|
22154
22605
|
sEd.reset();
|
|
@@ -22225,7 +22676,7 @@ ${out}
|
|
|
22225
22676
|
return;
|
|
22226
22677
|
}
|
|
22227
22678
|
await refreshCatalogs().catch((e) => {
|
|
22228
|
-
|
|
22679
|
+
log41.debug("catalog refresh failed", e);
|
|
22229
22680
|
});
|
|
22230
22681
|
const sk = skills.find((x) => x.name === name);
|
|
22231
22682
|
if (sk) {
|
|
@@ -22350,7 +22801,7 @@ ${out}
|
|
|
22350
22801
|
if (delivery || cut) dx?.parkCutDelivery(delivery, !!cut, heard);
|
|
22351
22802
|
const cutReply = cut && cut.full.length - cut.heard.length > 40 ? cut : null;
|
|
22352
22803
|
const noteP = replaces ? replaces.note : !cutReply ? Promise.resolve("") : (instinct ? instinct.bargeIntent(text).catch((e) => {
|
|
22353
|
-
|
|
22804
|
+
log41.warn(`barge intent failed \u2014 model decides: ${e.message}`);
|
|
22354
22805
|
return void 0;
|
|
22355
22806
|
}) : Promise.resolve(void 0)).then(async (intent) => interruptionNote(cutReply, intent) + ackedNote(await instinct?.ackFor(text)));
|
|
22356
22807
|
const cutIn = replaces ? replaces.cutIn : interjectNote;
|
|
@@ -22729,7 +23180,7 @@ function channelOptions(ai, model, gender) {
|
|
|
22729
23180
|
// Anticipated backchannels: on in chatty mode (its preset), else off until the offline replay proves them (tests/voice-bc-anticipate-eval.ts)
|
|
22730
23181
|
plan: (c, s) => planner.plan(c, s),
|
|
22731
23182
|
...process.env.AGENTX_CHANNEL_ANTICIPATE === "1" ? { anticipate: true } : {},
|
|
22732
|
-
onEvent: (e) =>
|
|
23183
|
+
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`)
|
|
22733
23184
|
};
|
|
22734
23185
|
}
|
|
22735
23186
|
function overlapResponder(ai, mode, out, aside) {
|
|
@@ -22800,7 +23251,7 @@ async function duplexServe(args, ai, cfg, cwd) {
|
|
|
22800
23251
|
try {
|
|
22801
23252
|
store.save(session);
|
|
22802
23253
|
} catch (e) {
|
|
22803
|
-
|
|
23254
|
+
log41.warn("duplex session save failed", e);
|
|
22804
23255
|
}
|
|
22805
23256
|
};
|
|
22806
23257
|
const overlap2 = overlapResponder(ai, args.duplexV2 ? args.channelMode : void 0, out);
|
|
@@ -22840,7 +23291,7 @@ async function duplexServe(args, ai, cfg, cwd) {
|
|
|
22840
23291
|
done2();
|
|
22841
23292
|
out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error", ...why ? { error: why } : {} });
|
|
22842
23293
|
}).catch((e) => {
|
|
22843
|
-
|
|
23294
|
+
log41.error("duplex turn failed", e);
|
|
22844
23295
|
done2();
|
|
22845
23296
|
out({ type: "turn_done", id, finishReason: "error", error: String(e?.message ?? e) });
|
|
22846
23297
|
});
|
|
@@ -23095,7 +23546,12 @@ async function main() {
|
|
|
23095
23546
|
const session = startSession(args, store, agent, cwd);
|
|
23096
23547
|
process.once("SIGINT", () => activeTurn?.abort());
|
|
23097
23548
|
steers?.steer((text) => agent.inject(text));
|
|
23098
|
-
const turn =
|
|
23549
|
+
const turn = async (task) => {
|
|
23550
|
+
const t = applyTurnMode(agent, session, task, args, (m) => {
|
|
23551
|
+
agent.options.providerOptions = providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name));
|
|
23552
|
+
});
|
|
23553
|
+
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))));
|
|
23554
|
+
};
|
|
23099
23555
|
if (warmBoot && steers) {
|
|
23100
23556
|
bindClaudeCodeSession(agent, session);
|
|
23101
23557
|
await agent.prewarm().catch((e) => err(dim(` (prewarm failed: ${e?.message ?? e})
|
|
@@ -23157,6 +23613,7 @@ export {
|
|
|
23157
23613
|
abortActiveTurn,
|
|
23158
23614
|
apiKeysFromEnv,
|
|
23159
23615
|
appendMemoryNote,
|
|
23616
|
+
applyTurnMode,
|
|
23160
23617
|
cacheMultipliers,
|
|
23161
23618
|
condenseReplay,
|
|
23162
23619
|
costOf,
|