@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/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
- log30.debug(`cpu probe failed for ${pgid}: ${e}`);
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
- log30.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
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
- log30.debug("shell spawn error", err2);
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 log30, clean2, DETACHED, shellArgv, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
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
- log30 = forComponent("shell");
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 lower = q2.toLowerCase();
3971
- matcher = (l) => l.toLowerCase().includes(lower);
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
- if (opts.hooks?.preToolUse) childOpts.hooks = { preToolUse: opts.hooks.preToolUse.bind(opts.hooks) };
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 log8 = forComponent("Agent");
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 bridged = (t) => o.ccBridge === "all" || CC_BRIDGED.has(t.name);
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
- log8.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
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: o.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
- log8.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` : ""})`);
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
- log8.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.`);
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)) log8.warn(v);
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
- log8.warn(`model refused image input (${err2?.message}) \u2014 resending with image Reads as text stubs`);
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) log8.warn("model stall after tool(s) already ran this step \u2014 not retrying (a retry would replay them)");
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
- log8.warn(`${empty ? "empty model turn" : stalled ? "model stall" : "network drop"} mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
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
- log8.warn(`model stalled out after retries: ${err2?.message ?? err2}`);
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
- log8.warn(`model returned empty/silent turns after retries \u2014 surfacing as failed`);
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
- log8.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, errLog);
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
- log8.error(`chat() failed: ${err2.message}`);
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
- log8.warn(`empty model turn after tool work (step ${steps}, provider finishReason=${res.finishReason ?? "n/a"}) \u2014 nudging once`);
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
- log8.warn(`model returned empty turns after tool work \u2014 surfacing as failed`);
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()) log8.warn(`fabricated tool use: text names ${fabricated.join(", ")} but no step/budget left to demand the call \u2014 finishing with the existing answer`);
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
- log8.warn(`fabricated tool use: text names ${fabricated.join(", ")} but the run invoked ${this.invokedTools.size ? [...this.invokedTools].join(", ") : "nothing"} \u2014 demanding the real call`);
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
- log8.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
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
- log8.verbose(`completed in ${steps} step(s)`);
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
- log8.warn(`needs_input: ${question.question} (steps=${steps})`);
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
- log8.verbose(`turn ended by a tool (steps=${steps})`);
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
- log8.warn(`stall watchdog: no stream output for ${stallMs}ms \u2014 held by ${who}`);
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
- log8.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(", ")})` : ""));
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
- log8.debug(`${tc.function.name} -> ${blocked}`);
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
- log8.debug(`${tc.function.name} -> ${earlyError}`);
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
- log8.debug(`${tc.function.name}(${tc.function.arguments})`);
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
- log8.debug(`onToolOutput hook error: ${e}`);
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
- log8.debug(`${tc.function.name} -> error: ${msg}`);
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
- log8.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
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 log9 = forComponent("scratch");
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
- log9.debug("scratch write failed; returning raw", e);
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
- log9.debug("scratch spill failed; cropping lossy", e);
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
- log9.debug("Ask peek failed", e);
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 log10 = forComponent("Lessons");
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) => log10.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
6836
- log10.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
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 log11 = forComponent("Reflect");
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
- log11.warn(`reflection call failed: ${e?.message ?? e}`);
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
- log11.warn(`could not persist lesson: ${e?.message ?? e}`);
7075
+ log12.warn(`could not persist lesson: ${e?.message ?? e}`);
6871
7076
  return null;
6872
7077
  }
6873
- log11.debug(`reflection persisted ${slug2}`);
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 log12 = forComponent2("Emotion");
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
- log12.debug(`dropping unknown emotion tag ${m[0]}`);
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 log12.debug(`dropping unknown emotion tag ${m[0]}`);
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 lower = trimmed.toLowerCase();
7212
- for (const marker of CONTINUATION_MARKERS) if (lower.endsWith(marker)) return false;
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 log13 = forComponent2("VoiceEngine");
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
- log13.warn("bargeInMode=aec but mic does not report echoCancellation applied \u2014 STT may transcribe the assistant");
7633
- log13.debug(`voice I/O up (${this.usingAec ? "AEC" : "heuristic echo"} capture, mode=${this.options.bargeInMode}, mic reports aec=${this.stt.usingAec})`);
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
- log13.debug(`onDiag threw \u2014 diagnostics disabled: ${e instanceof Error ? e.message : e}`);
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) log13.debug(`ttft: ${Math.round(this.clock.now() - this.turnStartAt)}ms`);
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) log13.debug(`turn: ${Math.round(this.clock.now() - this.turnStartAt)}ms (incl. playback)`);
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
- log13.warn(`onSpeechEnd threw: ${e instanceof Error ? e.message : e}`);
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
- log13.warn(`pickAck failed \u2014 using the default pools: ${e instanceof Error ? e.message : e}`);
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) log13.info(`barge-in cut ${droppedQueued} queued worker utterance(s) \u2014 handed to the host`);
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
- log13.debug(`speculate: "${this.specText.slice(0, 60)}"`);
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
- log13.debug(`backchannel: "${phrase}"`);
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
- log13.verbose(`stripped backchannel echo "${this.lastBcPhrase}" from dispatching final`);
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
- log13.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
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
- log13.verbose(`dropped duplicate final "${text.slice(0, 40)}" (identical to the just-dispatched utterance, no reply between)`);
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
- log13.verbose(`dropped re-finalization "${text.slice(0, 60)}" (superset of the just-dispatched utterance, reply already flowing)`);
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 log14 = forComponent2("SonioxSTT");
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
- log14.debug(`onDiag threw \u2014 STT diagnostics disabled: ${e instanceof Error ? e.message : e}`);
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
- log14.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
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
- log14.error(`soniox reconnect failed: ${e.message}`);
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) log14.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
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
- log14.error(`stt: no mic audio for >${Math.round(noAudioMs / 1e3)}s \u2014 capture device stopped delivering`);
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 log14.error(`soniox: ${m.error_message}`);
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) log14.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
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 log15 = forComponent2("CartesiaTTS");
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
- log15.debug(`onDiag threw \u2014 TTS diagnostics disabled: ${e instanceof Error ? e.message : e}`);
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
- log15.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
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
- log15.error(`cartesia reconnect failed: ${e.message}`);
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
- log15.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
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 || "") ? log15.debug : log15.warn)(`cartesia: ${JSON.stringify(m)}`);
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 ? log15.debug : log15.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
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 log16 = forComponent2("FishTTS");
9509
+ var log17 = forComponent2("FishTTS");
9305
9510
 
9306
9511
  // node_modules/@bod.ee/voice/src/adapters/grok.ts
9307
- var log17 = forComponent2("GrokTTS");
9512
+ var log18 = forComponent2("GrokTTS");
9308
9513
 
9309
9514
  // node_modules/@bod.ee/voice/src/adapters/grok-stt.ts
9310
- var log18 = forComponent2("GrokSTT");
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 log19 = forComponent("Overlap");
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") log19.warn(`overlap "${heard.slice(0, 60)}": no verdict (${by}, ${d.ms}ms) \u2192 cut`);
9407
- else log19.debug(`overlap "${heard.slice(0, 60)}": ${verdict} by ${by} ${d.ms}ms`);
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) log19.warn(`overlap model failed: ${e.message}`);
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
- () => log19.debug(`fast model warm in ${Math.round(performance.now() - t0)}ms`),
9441
- (e) => log19.warn(`fast model warm-up failed (the first overlap verdict may be late): ${e?.message ?? 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 log20 = forComponent("Instinct");
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
- log20.debug(`ack: cut-in turn \u2192 silent (engine context "${ctx.text.slice(0, 40)}" is the previous final)`);
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
- log20.warn(`barge intent for the ack failed \u2014 situational ack: ${e.message}`);
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
- log20.debug(`ack: no engine outcome in ${this.o.ackSettleMs}ms \u2014 not claimed`);
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
- log20.warn(`${kind} decision failed \u2014 falling back to engine defaults: ${e.message}`);
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 log21 = forComponent("Unsaid");
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
- log21.warn(`unsaid judge failed \u2014 only near-zero-overlap clauses count as unsaid: ${e.message}`);
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 log22 = forComponent("ChannelBanks");
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) log22.debug(`line "${raw}" reads as meta \u2014 rejected`);
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
- log22.debug(`line "${l}" is not ${lang} \u2014 rejected`);
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
- log22.warn(`bank ${lang}: only ${out[k].length} valid ${k} lines \u2014 rejected`);
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") log22.warn(`bank cache ${f} unreadable \u2014 regenerating: ${e.message}`);
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
- log22.debug(`bank ${lang}: cache v${cached.v ?? 1} predates v${BANK_VERSION} \u2014 regenerating`);
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
- log22.debug(`bank ${lang}: cache hit (${BANK_KEYS.map((k) => valid[k].length).join("/")})${complete ? "" : " \u2014 missing kinds, refreshing"}`);
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
- log22.debug(`bank ${lang}: ${have ? "refreshed" : "generated"} (${BANK_KEYS.map((k) => bank[k].length).join("/")}) in ${Math.round(this.o.now() - t0)}ms`);
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) => log22.warn(`bank cache write ${f} failed: ${e.message}`));
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
- log22.warn(`bank ${lang}: generation failed \u2014 ${have ? "keeping the cached bank" : lang in this.o.fallback ? "fallback bank" : "silent"}: ${e.message}`);
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 log23 = forComponent("DuplexAgent");
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
- log23.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
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
- log23.warn(`reflex ${why} ("${leak.trim().slice(0, 40)}\u2026") \u2014 cut before TTS`);
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
- log23.warn(`leak judge failed \u2014 speaking the sentence: ${e.message}`);
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
- log23.warn("analysis leak cut, but its text was not found in the transcript to scrub");
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
- log23.warn(`reflex ${model} rate-limited \u2014 re-running the turn on ${fb}`);
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
- log23.warn(`follow-through nudge failed: ${e instanceof Error ? e.message : e}`);
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
- log23.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
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
- log23.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
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
- log23.warn(`bargeIntent judge failed \u2014 using the allowlist: ${e.message}`);
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
- log23.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
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
- log23.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
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
- log23.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
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
- log23.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
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
- log23.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
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
- log23.warn(`task ${rec.id} failed: ${msg}`);
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 log24 = forComponent("ModelRouter");
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
- log24.warn(`classifier failed, falling back to standard: ${msg}`);
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
- log24.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`);
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
- log24.info(`offload ${cont ? "continue" : "fresh"} done \xB7 ${res.finishReason} \xB7 ${res.steps} steps`);
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
- log24.warn(`brief failed, sending the raw request: ${e instanceof Error ? e.message : String(e)}`);
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
- log24.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
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 log25 = forComponent("Progress");
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
- log25.warn(`progress line failed: ${e?.message ?? e}`);
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 log26 = forComponent("BackchannelPlan");
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) log26.debug(`unusable plan: ${(r.content ?? "").slice(0, 120)}`);
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 log27 = forComponent("SecondChannel");
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
- log27.debug(`${what}: over the ${ms}ms budget \u2014 dropped`);
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)) log27.debug(`${what}: aborted after its budget`);
13216
- else log27.warn(`${what} failed \u2014 falling back: ${msg}`);
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
- log27.debug(`language ${this.lang} \u2192 ${lang}`);
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
- log27.warn(`cancel threw: ${e.message}`);
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
- log27.info("chatty mode without echo cancellation \u2014 normal timing (our lines would re-trigger the mic)");
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) log27.debug(`decider chose ${choice} for a ${rule} moment \u2014 the rule stands`);
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) => log27.debug(`bridge prefetch failed: ${e?.message ?? 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) log27.debug(`generated "${text}" repeats a recent line \u2014 canned`);
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
- log27.warn(`speak threw \u2014 line dropped: ${e.message}`);
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
- log27.warn(`channel line playback failed: ${e?.message ?? e}`);
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 log28 = forComponent("VoiceCall");
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
- log28.warn(`cancel threw: ${e.message}`);
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
- log28.warn(`speak threw: ${e.message}`);
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 log29 = forComponent("mcp");
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) => log29.debug(`[${command}] stderr:`, chunk.trimEnd()));
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
- log29.debug("dropping non-JSON line from MCP server:", line, e);
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
- log29.debug("onProgress handler threw", e);
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) => log29.debug("cancel notify failed", 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
- log29.debug("stdin end failed", e);
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) => log29.debug("cancel notify failed", 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
- log29.debug("skipping unparseable SSE data line", e);
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
- log29.debug("onProgress handler threw", e);
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) => log29.debug(`close after failed mount of "${name}": ${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
- log29.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
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) log29.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
14441
- else log29.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
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
- log29.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
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) => log29.debug(`warm-pool replace close failed: ${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) => log29.debug(`warm-pool evict close failed: ${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 log31 = forComponent("notify");
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
- log31.debug("notification failed", e);
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 log32 = forComponent("cli-util");
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
- log32.debug(`parseJson(${what}) failed: ${e.message}`);
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
- log32.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
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 log33 = forComponent("VoiceIO");
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) => log33.warn(`ffplay error: ${e.message}`));
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
- log33.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
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
- log33.debug(`ffmpeg dshow mic: ${mic}`);
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
- log33.info("compiling AEC mic helper (first run)\u2026");
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
- log33.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
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
- log33.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
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
- log33.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
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) => log33.warn(`ffmpeg: ${String(d).trim()}`));
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) log33.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
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) log33.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
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 log33.debug(`mic-aec: ${s}`);
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
- log33.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)");
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
- log33.warn(`ack outcome tap failed: ${e instanceof Error ? e.message : e}`);
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
- log33.warn(`backchannel blip failed \u2014 line dropped: ${e.message}`);
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 log33.warn(`agent_state: unknown state ${JSON.stringify(m.state)} \u2014 ignored`);
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 log34 = forComponent("VoiceDiag");
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
- log34.debug(`voice diag sink disabled (${this.path}): ${e instanceof Error ? e.message : e}`);
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 log35 = forComponent("hooks");
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
- log35.debug(`hook command failed: ${rule.command}`, e);
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 log36 = forComponent("session");
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
- log36.debug(`rejecting unsafe session id: ${id}`);
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
- log36.debug(`unreadable session ${id} \u2014 ignoring`, e);
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
- log36.debug(`skipping unreadable session file ${f}`, e);
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 log37 = forComponent("checkpoints");
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
- log37.debug(`git checkpoints unavailable for ${this.workTree}`, e);
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) => log37.debug(`force-add failed: ${p}`, 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) => log37.debug(`force-capture failed: ${p}`, e));
16510
- await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log37.debug("amend failed", 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
- log37.debug("checkpoint prune failed", e);
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) => log37.debug("re-point failed", 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
- log37.debug("checkpoint commit failed", e);
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) => log37.debug("amendForced failed", 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 applyKey(s, key, str) {
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 = applyKey(s, key ?? {}, str);
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 log38 = forComponent("os-sched");
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
- log38.debug(`cancel ${id}`, e);
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 log39 = forComponent("remote-trigger");
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) => log39.debug("trigger conn error", e));
19103
+ conn.on("error", (e) => log40.debug("trigger conn error", e));
18697
19104
  });
18698
- this.server.on("error", (e) => log39.debug("trigger server 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
- log39.debug("trigger server unavailable", e);
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 log40 = forComponent("cli");
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 === "--duplex") a.duplex = true;
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, log41) {
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
- log41(dim(` (goal evaluator error: ${e?.message ?? e})
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) => log40.debug("mcp close failed", 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
- log40.debug("mcp mention resolve failed", e);
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
- log40.debug("mid-turn session flush failed", ex);
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
- log40.debug("suppressed unhandledRejection (cursor stream cancel)", e);
20722
+ log41.debug("suppressed unhandledRejection (cursor stream cancel)", e);
20279
20723
  return;
20280
20724
  }
20281
- log40.error("unhandledRejection", e);
20725
+ log41.error("unhandledRejection", e);
20282
20726
  });
20283
20727
  process.on("uncaughtException", (e) => {
20284
20728
  if (isCancelTeardown(e)) {
20285
- log40.debug("suppressed uncaughtException (cursor stream cancel)", e);
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
- log40.error("call turn failed", e);
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
- log40.debug("paste image dir create failed", e);
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
- log40.debug("history write failed", e);
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
- log40.debug("session save after rewind failed", e);
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
- log40.debug("catalog refresh failed", e);
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
- log40.debug("catalog refresh failed", e);
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
- log40.debug(`loadAgents(${d}) failed`, e);
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) => log40.debug("mcp close failed", 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) => log40.debug("mcp close failed", 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
- log40.debug("completion readdir failed", absDir, e);
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 = applyKey(sEd, key ?? {}, _s);
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
- log40.debug("catalog refresh failed", e);
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
- log40.warn(`barge intent failed \u2014 model decides: ${e.message}`);
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) => log40.debug(`channel ${e.trigger}: rule=${e.rule} choice=${e.choice ?? "-"} \u2192 ${e.acted}${e.text ? ` "${e.text}"` : ""}${e.cue ? " (planned cue)" : ""} ${e.ms}ms`)
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
- log40.warn("duplex session save failed", e);
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
- log40.error("duplex turn failed", e);
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 = (task) => runTurn(agent, store, session, task, void 0, cwd, autoRouteSend(args, agent, () => session, (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name))));
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,