@livx.cc/agentx 0.99.121 → 0.99.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -2954,7 +2954,7 @@ async function psGroupCpuSeconds(pgid) {
2954
2954
  try {
2955
2955
  return await winTreeCpuSeconds(pgid);
2956
2956
  } catch (e) {
2957
- log30.debug(`cpu probe failed for ${pgid}: ${e}`);
2957
+ log31.debug(`cpu probe failed for ${pgid}: ${e}`);
2958
2958
  return void 0;
2959
2959
  }
2960
2960
  }
@@ -2963,7 +2963,7 @@ async function psGroupCpuSeconds(pgid) {
2963
2963
  const out = await new Promise((res, rej) => execFile5("ps", ["-A", "-o", "pid=,ppid=,pgid=,time="], { timeout: 5e3, env: { ...process.env, LC_ALL: "C" } }, (e, so) => e ? rej(e) : res(String(so))));
2964
2964
  return sumTreeCpu(out, pgid);
2965
2965
  } catch (e) {
2966
- log30.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
2966
+ log31.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
2967
2967
  return void 0;
2968
2968
  }
2969
2969
  }
@@ -3112,7 +3112,7 @@ function makeRealShellTool(options) {
3112
3112
  proc.stderr?.on("data", collect);
3113
3113
  proc.on("error", (err2) => {
3114
3114
  if (err2?.name === "AbortError" || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean2(out)));
3115
- log30.debug("shell spawn error", err2);
3115
+ log31.debug("shell spawn error", err2);
3116
3116
  finish(`[exit 1] ${err2?.message ?? err2}${out ? "\n" + clean2(out) : ""}`);
3117
3117
  });
3118
3118
  proc.on("close", (code) => {
@@ -3191,7 +3191,7 @@ ${clean2(out) || "(no output yet)"}`;
3191
3191
  }
3192
3192
  ];
3193
3193
  }
3194
- var log30, clean2, DETACHED, shellArgv, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
3194
+ var log31, clean2, DETACHED, shellArgv, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
3195
3195
  var init_tools_shell = __esm({
3196
3196
  "src/tools.shell.ts"() {
3197
3197
  "use strict";
@@ -3201,7 +3201,7 @@ var init_tools_shell = __esm({
3201
3201
  init_shell_sandbox();
3202
3202
  init_tools_jobs();
3203
3203
  init_platform();
3204
- log30 = forComponent("shell");
3204
+ log31 = forComponent("shell");
3205
3205
  clean2 = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
3206
3206
  DETACHED = { stdio: ["ignore", "pipe", "pipe"], ...DETACH_OPTIONS };
3207
3207
  shellArgv = (sh, command) => ({ bin: sh.bin, args: sh.args(command), ...sh.spawnOptions ? { spawnOptions: sh.spawnOptions } : {} });
@@ -4385,7 +4385,11 @@ function childOptionsFor(opts, fs, depth, maxDepth, agentType, model, root) {
4385
4385
  if (po) childOpts.providerOptions = po;
4386
4386
  const deny = opts.permissions?.options.rules.filter((r) => r.decision === "deny") ?? [];
4387
4387
  if (deny.length) childOpts.permissions = new PermissionPolicy({ rules: deny, default: "allow" });
4388
- if (opts.hooks?.preToolUse) childOpts.hooks = { preToolUse: opts.hooks.preToolUse.bind(opts.hooks) };
4388
+ const h = opts.hooks;
4389
+ if (h?.preToolUse || opts.inheritPostToolUse && h?.postToolUse) childOpts.hooks = {
4390
+ ...h.preToolUse ? { preToolUse: h.preToolUse.bind(h) } : {},
4391
+ ...opts.inheritPostToolUse && h.postToolUse ? { postToolUse: h.postToolUse.bind(h) } : {}
4392
+ };
4389
4393
  return childOpts;
4390
4394
  }
4391
4395
  function makeTaskTool(opts) {
@@ -4609,6 +4613,147 @@ async function loadAgents(fs, dir) {
4609
4613
  init_OverlayFilesystem();
4610
4614
  init_logging();
4611
4615
 
4616
+ // src/modes.ts
4617
+ init_logging();
4618
+ var log8 = forComponent("modes");
4619
+ var WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "ApplyEdits"]);
4620
+ var PLANNER_BLOCKED = /* @__PURE__ */ new Set([...WRITE_TOOLS, "bash", "Shell"]);
4621
+ var PLANNER_REFUSAL = "Refused: you are the planner and do not write files. Delegate the change with Task/TaskBatch (model set to the worker) and give the worker a full spec.";
4622
+ var PLAN_FIRST = "Refused: check your plan with `Advise` before the first write (files, data shape, the hard parts). Then write.";
4623
+ var ADVISE_LIMIT = 3;
4624
+ var SNAPSHOT_CAP = 6e4;
4625
+ function pairOk(kind, main2, other) {
4626
+ if (other === main2) {
4627
+ log8.warn(`${kind} off: both sides are ${main2}`);
4628
+ return false;
4629
+ }
4630
+ if (main2.startsWith("cursor/") || other.startsWith("cursor/")) {
4631
+ log8.warn(`${kind} off: cursor models are not supported (${main2} / ${other})`);
4632
+ return false;
4633
+ }
4634
+ return true;
4635
+ }
4636
+ function delegateWorkerFor(planner, worker) {
4637
+ return worker && pairOk("delegate", planner, worker) ? worker : void 0;
4638
+ }
4639
+ function advisorFor(worker, advisor) {
4640
+ return advisor && pairOk("advisor", worker, advisor) ? advisor : void 0;
4641
+ }
4642
+ function delegateTools(tools, planner) {
4643
+ return tools.map((t) => !PLANNER_BLOCKED.has(t.name) ? t : {
4644
+ ...t,
4645
+ run: async (...a) => a[1]?.model === planner ? PLANNER_REFUSAL : t.run(...a)
4646
+ });
4647
+ }
4648
+ function delegatePrompt(worker) {
4649
+ return `
4650
+
4651
+ ## You are the PLANNER
4652
+ You cannot write files: Write, Edit, MultiEdit, ApplyEdits and the shells are refused for you. Every file change goes through a worker. Read what you need, decide the design (files, data shape, interfaces, the rules that matter), then hand the writing to workers with \`TaskBatch\` (independent files in parallel) or \`Task\`, with \`model: "${worker}"\` (the default). Give each worker a complete, self-contained spec: it sees the files but not your reasoning. When they finish, read the result and send fix-up tasks for anything wrong or missing. Finish only when the work is complete.`;
4653
+ }
4654
+ function advisorPrompt(advisor) {
4655
+ return `
4656
+
4657
+ ## You have an ADVISOR
4658
+ The \`Advise\` tool asks a stronger model (${advisor}). You do the work yourself. Call \`Advise\` once BEFORE you write \u2014 writes are refused until you do (ask it to check your plan: files, data shape, the hard parts) \u2014 and once AFTER the work is complete (ask it to review for defects), then fix what it finds. Do not call it for small steps.`;
4659
+ }
4660
+ async function textOf(r) {
4661
+ if (r && typeof r[Symbol.asyncIterator] === "function") {
4662
+ let s = "";
4663
+ for await (const c of r) if (typeof c?.content === "string") s += c.content;
4664
+ return s;
4665
+ }
4666
+ return String(r?.content ?? r?.text ?? "");
4667
+ }
4668
+ function makeAdvisor(o) {
4669
+ let used = 0;
4670
+ const limit = o.limit ?? ADVISE_LIMIT;
4671
+ const tool = {
4672
+ name: "Advise",
4673
+ description: `Ask a stronger model (${o.advisor}) for advice. It sees the user's request${o.snapshot ? " and the current state" : ""}, not this conversation, so put everything it needs in \`question\`. Use it at most ${limit} times: once for the plan before you write, once to review the finished work.`,
4674
+ parameters: { type: "object", properties: { question: { type: "string", description: "what you want advice on, with the context it needs" } }, required: ["question"] },
4675
+ async run(args) {
4676
+ const question = String(args?.question ?? "").trim();
4677
+ if (!question) return "No advice: the question is empty.";
4678
+ if (used >= limit) return `No advice: the limit of ${limit} calls is used. Continue with your own judgment.`;
4679
+ used++;
4680
+ let state = "";
4681
+ if (o.snapshot) {
4682
+ const s = String(await Promise.resolve(o.snapshot()).catch((err2) => {
4683
+ log8.warn("advisor snapshot failed:", String(err2));
4684
+ return "";
4685
+ }) || "(empty)");
4686
+ state = `
4687
+
4688
+ ## Current state
4689
+ ${s.length > SNAPSHOT_CAP ? `${s.slice(0, SNAPSHOT_CAP)}
4690
+ \u2026(cut)` : s}`;
4691
+ }
4692
+ const review = used > 1 ? "" : "\n\n(When the work is complete, call Advise again for a review.)";
4693
+ try {
4694
+ const r = await o.ai.chat({
4695
+ model: o.advisor,
4696
+ messages: [
4697
+ { role: "system", content: "You advise a developer mid-task. Answer the question directly and concretely: the plan, the data shape, the risks, or the defects with their fix. Be brief. Do not write the whole solution." },
4698
+ { role: "user", content: `## User request
4699
+ ${o.request()}${state}
4700
+
4701
+ ## Question
4702
+ ${question}` }
4703
+ ],
4704
+ maxTokens: 2e3
4705
+ });
4706
+ return ((await textOf(r)).trim() || "No advice: the advisor returned nothing.") + review;
4707
+ } catch (err2) {
4708
+ log8.warn("advisor call failed:", String(err2));
4709
+ return `No advice: the advisor call failed (${String(err2?.message ?? err2).slice(0, 120)}). Continue with your own judgment.`;
4710
+ }
4711
+ }
4712
+ };
4713
+ const gate = (tools) => tools.map((t) => !WRITE_TOOLS.has(t.name) ? t : { ...t, run: async (...a) => used ? t.run(...a) : PLAN_FIRST });
4714
+ return { tool, gate, used: () => used };
4715
+ }
4716
+ function meterWorkers(ai, main2) {
4717
+ let total;
4718
+ let resolved;
4719
+ const seen = () => {
4720
+ resolved = ai.modelUsed ?? resolved;
4721
+ };
4722
+ const add = (u) => {
4723
+ if (!u) return;
4724
+ const t = total ??= { promptTokens: 0, completionTokens: 0, totalTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
4725
+ t.promptTokens += u.promptTokens ?? 0;
4726
+ t.completionTokens += u.completionTokens ?? 0;
4727
+ t.totalTokens += u.totalTokens ?? 0;
4728
+ t.cacheCreationTokens += u.cacheCreationTokens ?? 0;
4729
+ t.cacheReadTokens += u.cacheReadTokens ?? 0;
4730
+ };
4731
+ async function* tap(it) {
4732
+ let last;
4733
+ try {
4734
+ for await (const c of it) {
4735
+ if (c?.usage) last = c.usage;
4736
+ yield c;
4737
+ }
4738
+ } finally {
4739
+ add(last);
4740
+ seen();
4741
+ }
4742
+ }
4743
+ const chat = async (o) => {
4744
+ const r = await ai.chat(o);
4745
+ if (o?.model === main2) return r;
4746
+ if (r && typeof r[Symbol.asyncIterator] === "function") return tap(r);
4747
+ add(r?.usage);
4748
+ seen();
4749
+ return r;
4750
+ };
4751
+ return { ai: new Proxy(ai, { get: (t, k) => k === "chat" ? chat : Reflect.get(t, k) }), usage: () => total && { ...total }, model: () => resolved, reset: () => {
4752
+ total = void 0;
4753
+ resolved = void 0;
4754
+ } };
4755
+ }
4756
+
4612
4757
  // src/lint.ts
4613
4758
  var CODE_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
4614
4759
  function checkSyntax(path, content) {
@@ -4688,7 +4833,7 @@ function reasoningToChatFragment(model, effort) {
4688
4833
  }
4689
4834
 
4690
4835
  // src/Agent.ts
4691
- var log8 = forComponent("Agent");
4836
+ var log9 = forComponent("Agent");
4692
4837
  function envInt(key, fallback) {
4693
4838
  const n = typeof process !== "undefined" ? Number(process.env?.[key]) : NaN;
4694
4839
  return Number.isFinite(n) && n >= 0 ? n : fallback;
@@ -4817,6 +4962,13 @@ var AgentOptions = class {
4817
4962
  hooks;
4818
4963
  /** If true: add the `Task` tool so the agent can spawn depth-limited child agents over the VFS. */
4819
4964
  subagents = false;
4965
+ /** DELEGATE MODE: `model` plans, `worker` writes. The planner's Write/Edit/MultiEdit/ApplyEdits/bash/Shell are
4966
+ * refused at execution; Task/TaskBatch are on (maxDepth 1, children default to `worker`) and children get the
4967
+ * parent's `postToolUse` too. Off when the models are equal or either is cursor/*. Wins over `advisor`. */
4968
+ delegate;
4969
+ /** ADVISOR MODE: `model` does the work; an `Advise` tool asks `advisor.model` (≤`limit`, default 3) with only
4970
+ * the request + `snapshot()` (host state, optional) + the question. The first write waits for one consult. */
4971
+ advisor;
4820
4972
  /** VFS dir of typed-subagent defs (`<dir>/<name>.md`). If set with `subagents`: inject a catalog + enable the `Task` `agentType` param. */
4821
4973
  agentsDir;
4822
4974
  /** Current recursion depth (0 = top-level); spawned children run at depth+1. */
@@ -4925,6 +5077,11 @@ var Agent = class _Agent {
4925
5077
  lastTrimNotified = 0;
4926
5078
  // last auto-trim drop count surfaced via host.notify (dedup)
4927
5079
  activeTools = [];
5080
+ meter;
5081
+ // delegate/advisor: meters the second model's calls
5082
+ modeModel;
5083
+ lastTask = "";
5084
+ // the current turn's text — what the advisor reads as the request
4928
5085
  /** claude-code bridge for agentx-only tools (ccBridge.ts) + the current step's executor it calls. */
4929
5086
  ccServer;
4930
5087
  ccExec;
@@ -5108,7 +5265,7 @@ var Agent = class _Agent {
5108
5265
  const disk = new NodeDiskFilesystem2(process.cwd());
5109
5266
  await disk.init();
5110
5267
  this.options.fs = new JailedFilesystem2(disk);
5111
- log8.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
5268
+ log9.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
5112
5269
  }
5113
5270
  this.buildCtx();
5114
5271
  }
@@ -5125,6 +5282,12 @@ var Agent = class _Agent {
5125
5282
  const fs = o.fs;
5126
5283
  let systemPrompt = o.systemPrompt;
5127
5284
  let tools = o.tools;
5285
+ this.ctx.model = o.model;
5286
+ const worker = o.delegate && delegateWorkerFor(o.model, o.delegate.worker);
5287
+ const advisor = !worker && o.advisor ? advisorFor(o.model, o.advisor.model) : void 0;
5288
+ this.meter = worker || advisor ? meterWorkers(o.ai, o.model) : void 0;
5289
+ const ai = this.meter?.ai ?? o.ai;
5290
+ const adv = advisor ? makeAdvisor({ ai, advisor, request: () => this.lastTask, snapshot: o.advisor.snapshot, limit: o.advisor.limit }) : void 0;
5128
5291
  if (o.instructionFiles !== false) {
5129
5292
  const names = Array.isArray(o.instructionFiles) ? o.instructionFiles : void 0;
5130
5293
  const ins = await loadInstructions(fs, names);
@@ -5155,7 +5318,7 @@ var Agent = class _Agent {
5155
5318
  if (tool) tools = [...tools, tool];
5156
5319
  }
5157
5320
  if (o.host && o.askUserQuestion) tools = [...tools, askUserQuestionTool];
5158
- if (o.subagents) {
5321
+ if (o.subagents || worker) {
5159
5322
  let agents;
5160
5323
  if (o.agentsDir) {
5161
5324
  const loaded = await loadAgents(fs, o.agentsDir);
@@ -5163,17 +5326,18 @@ var Agent = class _Agent {
5163
5326
  if (loaded.catalog) systemPrompt += "\n\n" + loaded.catalog;
5164
5327
  }
5165
5328
  const taskOpts = {
5166
- ai: o.ai,
5167
- model: o.model,
5329
+ ai,
5330
+ model: worker || o.model,
5168
5331
  fs,
5169
5332
  depth: o.depth,
5170
- maxDepth: o.maxDepth,
5333
+ maxDepth: worker ? Math.min(o.maxDepth, o.depth + 1) : o.maxDepth,
5171
5334
  agents,
5335
+ inheritPostToolUse: !!worker,
5172
5336
  hooks: o.hooks,
5173
5337
  traceDir: o.traceDir,
5174
5338
  permissions: o.permissions,
5175
5339
  // a child obeys the parent's allow/deny rules (else Task is a one-call bypass)
5176
- tools: [...o.tools, ...this.injectedTools],
5340
+ tools: worker ? delegateTools([...o.tools, ...this.injectedTools], o.model) : [...o.tools, ...this.injectedTools],
5177
5341
  systemPrompt: o.systemPrompt,
5178
5342
  // A child never resumes the parent's own claude-code session (the host's `resume` link is the parent's).
5179
5343
  providerOptionsFor: o.providerOptionsFor ?? ((m) => modelProvider(m) === modelProvider(o.model) && o.providerOptions ? { ...o.providerOptions, resume: void 0, onClaudeCodeSession: void 0 } : void 0)
@@ -5186,6 +5350,15 @@ var Agent = class _Agent {
5186
5350
  if (plan) tools = [...tools, plan.tool];
5187
5351
  this.activeHooks = composeHooks(o.hooks, plan?.hooks, o.permissions?.hooks());
5188
5352
  this.activeTools = [...tools, ...this.injectedTools];
5353
+ if (worker) {
5354
+ this.activeTools = delegateTools(this.activeTools, o.model);
5355
+ systemPrompt += delegatePrompt(worker);
5356
+ }
5357
+ if (adv) {
5358
+ this.activeTools = [...adv.gate(this.activeTools), adv.tool];
5359
+ systemPrompt += advisorPrompt(advisor);
5360
+ }
5361
+ this.modeModel = worker || advisor;
5189
5362
  this.systemPromptCache = systemPrompt;
5190
5363
  this.prepared = true;
5191
5364
  return systemPrompt;
@@ -5198,6 +5371,7 @@ var Agent = class _Agent {
5198
5371
  this.started = false;
5199
5372
  this.invokedTools.clear();
5200
5373
  this.fabricatedClaims.clear();
5374
+ this.lastTask = contentText(task);
5201
5375
  const systemPrompt = await this.prepare(contentText(task));
5202
5376
  const startCtx = await this.fireSessionStart();
5203
5377
  const userContent = await this.applyPromptSubmit(task);
@@ -5233,6 +5407,7 @@ var Agent = class _Agent {
5233
5407
  */
5234
5408
  async send(task) {
5235
5409
  await this.ensureFs();
5410
+ this.lastTask = contentText(task);
5236
5411
  const systemPrompt = await this.prepare(contentText(task));
5237
5412
  const startCtx = await this.fireSessionStart();
5238
5413
  const userContent = await this.applyPromptSubmit(task);
@@ -5283,6 +5458,12 @@ var Agent = class _Agent {
5283
5458
  if (next !== this.transcript) this.transcript = next;
5284
5459
  }
5285
5460
  async runLoop() {
5461
+ this.meter?.reset();
5462
+ const res = await this.loop();
5463
+ const workerUsage = this.meter?.usage();
5464
+ return workerUsage ? { ...res, workerModel: this.meter.model() ?? this.modeModel, workerUsage } : res;
5465
+ }
5466
+ async loop() {
5286
5467
  const o = this.options;
5287
5468
  const wireTools = toWireTools(this.activeTools);
5288
5469
  const useStream = o.stream === true && typeof o.host?.notify === "function";
@@ -5302,7 +5483,7 @@ var Agent = class _Agent {
5302
5483
  let lastProgressAt = start;
5303
5484
  const turnUser = this.transcript[this.transcript.length - 1];
5304
5485
  const kill = (finishReason) => {
5305
- 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` : ""})`);
5486
+ log9.warn(`kill-switch: ${finishReason} (steps=${steps}, tokens=${usage.totalTokens}, budgetTokens=${Math.round(spent)}, ms=${Date.now() - start - this.parkedMs}${finishReason === "timeout" ? `, idle=${Date.now() - lastProgressAt - this.parkedMs}ms` : ""}${this.parkedMs ? ` +${this.parkedMs} parked` : ""})`);
5306
5487
  this.jobs.killAll(`run ended: ${finishReason}`, true);
5307
5488
  const claims = this.survivingClaims();
5308
5489
  return { text: lastAssistantText(this.transcript, turnUser), steps, finishReason, messages: this.transcript, usage, usageEstimated, ...finishReason === "budget" ? { budget: { spent: Math.round(spent), limit: o.maxTokens, stepPrompt } } : {}, ...claims.length ? { fabricatedToolClaims: claims } : {} };
@@ -5328,9 +5509,9 @@ var Agent = class _Agent {
5328
5509
  this.nativeHoldMs = o.nativeHoldMs;
5329
5510
  if (isCursorWithTools) {
5330
5511
  if (typeof hostToolTimeoutMs === "number" && effectiveToolTimeoutMs !== hostToolTimeoutMs) {
5331
- 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.`);
5512
+ log9.warn(`providerOptions.toolTimeoutMs=${hostToolTimeoutMs}ms is below what the registered tools need \u2014 RAISED to ${effectiveToolTimeoutMs}ms. ${toolTimeoutViolations(this.activeTools, hostToolTimeoutMs).length} tool(s) could outlive it, and the transport would have destroyed their results. To lower it, lower the tools' own maxDurationMs (e.g. the Shell tool's maxTimeoutMs) instead.`);
5332
5513
  }
5333
- for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs)) log8.warn(v);
5514
+ for (const v of toolTimeoutViolations(this.activeTools, effectiveToolTimeoutMs)) log9.warn(v);
5334
5515
  }
5335
5516
  const hostExec = async (name, args) => {
5336
5517
  const tc = { id: `host-${Date.now()}`, type: "function", function: { name, arguments: JSON.stringify(args) } };
@@ -5389,7 +5570,7 @@ var Agent = class _Agent {
5389
5570
  } catch (err2) {
5390
5571
  if (!this.imagesOff && /does not support .*input/i.test(String(err2?.message ?? "")) && sent.some((m) => Array.isArray(m.content) && m.role === "tool")) {
5391
5572
  this.imagesOff = true;
5392
- log8.warn(`model refused image input (${err2?.message}) \u2014 resending with image Reads as text stubs`);
5573
+ log9.warn(`model refused image input (${err2?.message}) \u2014 resending with image Reads as text stubs`);
5393
5574
  sent = this.trimContext();
5394
5575
  attempt--;
5395
5576
  continue;
@@ -5401,11 +5582,11 @@ var Agent = class _Agent {
5401
5582
  const empty = err2?.code === "empty" || err2?.code === "cursor_silent";
5402
5583
  const maxAttempts = empty ? Math.max(o.emptyRetries, 0) : 2;
5403
5584
  const replaysTool = stalled && this.toolRanThisAttempt;
5404
- if (replaysTool) log8.warn("model stall after tool(s) already ran this step \u2014 not retrying (a retry would replay them)");
5585
+ if (replaysTool) log9.warn("model stall after tool(s) already ran this step \u2014 not retrying (a retry would replay them)");
5405
5586
  const transient = !o.signal?.aborted && !isAbortError(err2) && !replaysTool && attempt < maxAttempts && (network || serverSide || stalled || empty);
5406
5587
  if (!transient) throw err2;
5407
5588
  const waitMs = (empty ? o.emptyRetryBaseMs : 1e3) * (attempt + 1);
5408
- log8.warn(`${empty ? "empty model turn" : stalled ? "model stall" : "network drop"} mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
5589
+ log9.warn(`${empty ? "empty model turn" : stalled ? "model stall" : "network drop"} mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
5409
5590
  o.host?.notify?.({ kind: "retry", message: `${empty ? "model returned nothing" : stalled ? "model stalled" : "connection dropped"} \u2014 retrying step (#${attempt + 1})` });
5410
5591
  await new Promise((r) => setTimeout(r, waitMs));
5411
5592
  }
@@ -5413,12 +5594,12 @@ var Agent = class _Agent {
5413
5594
  } catch (err2) {
5414
5595
  if (err2?.code === "budget") return kill("budget");
5415
5596
  if (err2?.code === "stall") {
5416
- log8.warn(`model stalled out after retries: ${err2?.message ?? err2}`);
5597
+ log9.warn(`model stalled out after retries: ${err2?.message ?? err2}`);
5417
5598
  return { ...kill("timeout"), providerFinishReason: "none (stalled)", error: err2 };
5418
5599
  }
5419
5600
  if (o.signal?.aborted || isAbortError(err2)) return kill("aborted");
5420
5601
  if (err2?.code === "empty" || err2?.code === "cursor_silent") {
5421
- log8.warn(`model returned empty/silent turns after retries \u2014 surfacing as failed`);
5602
+ log9.warn(`model returned empty/silent turns after retries \u2014 surfacing as failed`);
5422
5603
  const pfr = err2?.providerFinishReason;
5423
5604
  return { text: "", steps, finishReason: "empty", ...pfr ? { providerFinishReason: pfr } : {}, messages: this.transcript, usage, usageEstimated, error: err2 };
5424
5605
  }
@@ -5431,13 +5612,13 @@ var Agent = class _Agent {
5431
5612
  }
5432
5613
  if (bodyStr && err2 instanceof Error && !err2.message.includes(bodyStr)) err2.detail = bodyStr;
5433
5614
  const errLog = err2 instanceof Error ? { message: err2.message, ...err2.detail ? { detail: err2.detail } : {}, stack: err2.stack } : err2;
5434
- log8.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, errLog);
5615
+ log9.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, errLog);
5435
5616
  return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
5436
5617
  }
5437
5618
  if (o.signal?.aborted) return kill("aborted");
5438
5619
  if (res.finishReason === "error" && !res.toolCalls?.length) {
5439
5620
  const err2 = new Error(contentText(res.content ?? "").trim() || "provider error");
5440
- log8.error(`chat() failed: ${err2.message}`);
5621
+ log9.error(`chat() failed: ${err2.message}`);
5441
5622
  return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
5442
5623
  }
5443
5624
  if (!res.usage) {
@@ -5505,21 +5686,21 @@ var Agent = class _Agent {
5505
5686
  if (emptyTurn && o.failEmptyTurns) {
5506
5687
  if (!emptyNudged && canNudge()) {
5507
5688
  emptyNudged = true;
5508
- log8.warn(`empty model turn after tool work (step ${steps}, provider finishReason=${res.finishReason ?? "n/a"}) \u2014 nudging once`);
5689
+ log9.warn(`empty model turn after tool work (step ${steps}, provider finishReason=${res.finishReason ?? "n/a"}) \u2014 nudging once`);
5509
5690
  this.transcript.push({ role: "user", content: "Continue \u2014 you returned nothing. Answer the user now using the tool results above, or keep working if you are not done." });
5510
5691
  continue;
5511
5692
  }
5512
- log8.warn(`model returned empty turns after tool work \u2014 surfacing as failed`);
5693
+ log9.warn(`model returned empty turns after tool work \u2014 surfacing as failed`);
5513
5694
  const e = new Error("empty_response: model returned an empty turn (no text, no tools) after a re-prompt");
5514
5695
  e.code = "empty";
5515
5696
  return { text: "", steps, finishReason: "empty", ...res.finishReason ? { providerFinishReason: res.finishReason } : {}, messages: this.transcript, usage, usageEstimated, error: e };
5516
5697
  }
5517
5698
  const fabricated = o.catchFabricatedToolUse && !closureNudged ? this.fabricatedToolNames(contentText(res.content ?? "")) : [];
5518
5699
  for (const n of fabricated) this.fabricatedClaims.add(n);
5519
- if (fabricated.length && !canNudge()) log8.warn(`fabricated tool use: text names ${fabricated.join(", ")} but no step/budget left to demand the call \u2014 finishing with the existing answer`);
5700
+ if (fabricated.length && !canNudge()) log9.warn(`fabricated tool use: text names ${fabricated.join(", ")} but no step/budget left to demand the call \u2014 finishing with the existing answer`);
5520
5701
  if (fabricated.length && canNudge()) {
5521
5702
  closureNudged = true;
5522
- 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`);
5703
+ log9.warn(`fabricated tool use: text names ${fabricated.join(", ")} but the run invoked ${this.invokedTools.size ? [...this.invokedTools].join(", ") : "nothing"} \u2014 demanding the real call`);
5523
5704
  this.transcript.push({
5524
5705
  role: "user",
5525
5706
  content: `You referred to ${fabricated.map((n) => `\`${n}\``).join(", ")} in your reply, but you never actually called ${fabricated.length > 1 ? "those tools" : "that tool"} \u2014 no tool call was made, so any result you reported for ${fabricated.length > 1 ? "them" : "it"} was invented. Either call ${fabricated.length > 1 ? "them" : "it"} for real now, or answer without ${fabricated.length > 1 ? "them" : "it"} and do NOT claim any outcome \u2014 including a failure or a timeout \u2014 for a call you did not make.`
@@ -5528,14 +5709,14 @@ var Agent = class _Agent {
5528
5709
  }
5529
5710
  if (o.closeDelegatedTurns && res.endedWithoutClosing && !closureNudged && canNudge()) {
5530
5711
  closureNudged = true;
5531
- log8.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
5712
+ log9.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
5532
5713
  this.transcript.push({
5533
5714
  role: "user",
5534
5715
  content: "You ran tools but did not reply. Answer the user now using the tool results above; if a tool failed, say so plainly. Do NOT re-run those tools, do NOT narrate what you did, and do NOT ask for permission to continue."
5535
5716
  });
5536
5717
  continue;
5537
5718
  }
5538
- log8.verbose(`completed in ${steps} step(s)`);
5719
+ log9.verbose(`completed in ${steps} step(s)`);
5539
5720
  const drained = await this.jobs.drain(o.signal);
5540
5721
  if (o.signal?.aborted) return kill("aborted");
5541
5722
  if (drained.length) lastProgressAt = Date.now();
@@ -5569,7 +5750,7 @@ var Agent = class _Agent {
5569
5750
  if (this.ctx.needsInput) {
5570
5751
  const question = this.ctx.needsInput;
5571
5752
  this.ctx.needsInput = void 0;
5572
- log8.warn(`needs_input: ${question.question} (steps=${steps})`);
5753
+ log9.warn(`needs_input: ${question.question} (steps=${steps})`);
5573
5754
  await this.jobs.drain();
5574
5755
  const text = [
5575
5756
  `OPEN QUESTION (run paused \u2014 no human was reachable): ${question.question}`,
@@ -5580,7 +5761,7 @@ var Agent = class _Agent {
5580
5761
  if (this.ctx.endTurn) {
5581
5762
  this.ctx.endTurn = void 0;
5582
5763
  if (this.drainInjections()) continue;
5583
- log8.verbose(`turn ended by a tool (steps=${steps})`);
5764
+ log9.verbose(`turn ended by a tool (steps=${steps})`);
5584
5765
  await this.jobs.drain(o.signal);
5585
5766
  return { text: "", steps, finishReason: "stop", messages: this.transcript, usage, usageEstimated };
5586
5767
  }
@@ -5618,12 +5799,12 @@ var Agent = class _Agent {
5618
5799
  if (this.toolHoldActive()) {
5619
5800
  const now4 = Date.now();
5620
5801
  const who = this.jobs.holding().map((j) => `${j.kind} "${j.label}" (${Math.round((now4 - j.startedAt) / 1e3)}s, quiet ${Math.round((now4 - j.lastActivityAt) / 1e3)}s)`).join(", ");
5621
- log8.warn(`stall watchdog: no stream output for ${stallMs}ms \u2014 held by ${who}`);
5802
+ log9.warn(`stall watchdog: no stream output for ${stallMs}ms \u2014 held by ${who}`);
5622
5803
  this.options.host?.notify?.({ kind: "tool_hold", message: `waiting on ${who}` });
5623
5804
  return poke();
5624
5805
  }
5625
5806
  const inFlight = this.jobs.list().filter((j) => j.status === "running");
5626
- 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(", ")})` : ""));
5807
+ log9.warn(`stall watchdog: no stream output for ${stallMs}ms and no tool holding the stream \u2014 aborting the request` + (inFlight.length ? ` (${inFlight.length} detached job(s) running, not awaited by the stream: ${inFlight.map((j) => `${j.id}/${j.kind}`).join(", ")})` : ""));
5627
5808
  fired = true;
5628
5809
  ac.abort(stallError());
5629
5810
  }, stallMs);
@@ -5793,13 +5974,13 @@ var Agent = class _Agent {
5793
5974
  const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
5794
5975
  if (decision?.block) {
5795
5976
  const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
5796
- log8.debug(`${tc.function.name} -> ${blocked}`);
5977
+ log9.debug(`${tc.function.name} -> ${blocked}`);
5797
5978
  await hooks?.postToolUse?.(call, blocked, meta);
5798
5979
  return blocked;
5799
5980
  }
5800
5981
  this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
5801
5982
  if (earlyError) {
5802
- log8.debug(`${tc.function.name} -> ${earlyError}`);
5983
+ log9.debug(`${tc.function.name} -> ${earlyError}`);
5803
5984
  await hooks?.postToolUse?.(call, earlyError, meta);
5804
5985
  this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
5805
5986
  return earlyError;
@@ -5808,12 +5989,12 @@ var Agent = class _Agent {
5808
5989
  let images;
5809
5990
  let threw = false;
5810
5991
  try {
5811
- log8.debug(`${tc.function.name}(${tc.function.arguments})`);
5992
+ log9.debug(`${tc.function.name}(${tc.function.arguments})`);
5812
5993
  this.ctx.emit = hooks?.onToolOutput ? (chunk) => {
5813
5994
  try {
5814
5995
  hooks.onToolOutput(call, chunk, meta);
5815
5996
  } catch (e) {
5816
- log8.debug(`onToolOutput hook error: ${e}`);
5997
+ log9.debug(`onToolOutput hook error: ${e}`);
5817
5998
  }
5818
5999
  } : void 0;
5819
6000
  const raw = await tool.run(args, this.ctx);
@@ -5825,7 +6006,7 @@ var Agent = class _Agent {
5825
6006
  }
5826
6007
  } catch (e) {
5827
6008
  const msg = e instanceof Error ? e.message : String(e);
5828
- log8.debug(`${tc.function.name} -> error: ${msg}`);
6009
+ log9.debug(`${tc.function.name} -> error: ${msg}`);
5829
6010
  result = `Error: ${msg}`;
5830
6011
  threw = true;
5831
6012
  } finally {
@@ -6102,7 +6283,7 @@ function fitTokenBudget(messages, maxTokens, keepRecentImages = 1, vision = true
6102
6283
  const ids = callIdSet(messages.slice(from));
6103
6284
  while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
6104
6285
  if (total > maxTokens)
6105
- log8.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
6286
+ log9.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
6106
6287
  return [...head, ...messages.slice(from)];
6107
6288
  }
6108
6289
  function compact(m, max, focus) {
@@ -6682,7 +6863,7 @@ init_tools_web();
6682
6863
  init_tools();
6683
6864
  init_tools_structured();
6684
6865
  init_logging();
6685
- var log9 = forComponent("scratch");
6866
+ var log10 = forComponent("scratch");
6686
6867
  var SCRATCH_DIR = "/scratch";
6687
6868
  function shortArgs(args) {
6688
6869
  try {
@@ -6722,7 +6903,7 @@ var Scratch = class {
6722
6903
  await (this.dirReady ??= mkdirp(this.fs, dir));
6723
6904
  await this.fs.writeFile(path, header + raw);
6724
6905
  } catch (e) {
6725
- log9.debug("scratch write failed; returning raw", e);
6906
+ log10.debug("scratch write failed; returning raw", e);
6726
6907
  return raw;
6727
6908
  }
6728
6909
  const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
@@ -6752,7 +6933,7 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
6752
6933
  await (this.dirReady ??= mkdirp(this.fs, dir));
6753
6934
  await this.fs.writeFile(path, header + full);
6754
6935
  } catch (e) {
6755
- log9.debug("scratch spill failed; cropping lossy", e);
6936
+ log10.debug("scratch spill failed; cropping lossy", e);
6756
6937
  return full.slice(0, pageBytes) + `
6757
6938
 
6758
6939
  [output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
@@ -6798,7 +6979,7 @@ Question: ${q2}`);
6798
6979
  const answer = (res.text ?? "").trim();
6799
6980
  return answer || "(no answer found in scratch)";
6800
6981
  } catch (e) {
6801
- log9.debug("Ask peek failed", e);
6982
+ log10.debug("Ask peek failed", e);
6802
6983
  return `Error querying scratch: ${e?.message ?? e}`;
6803
6984
  }
6804
6985
  }
@@ -6807,7 +6988,7 @@ Question: ${q2}`);
6807
6988
 
6808
6989
  // src/lessons.ts
6809
6990
  init_logging();
6810
- var log10 = forComponent("Lessons");
6991
+ var log11 = forComponent("Lessons");
6811
6992
  var LessonOptionsDefaults = class {
6812
6993
  minRepeats = 2;
6813
6994
  };
@@ -6832,8 +7013,8 @@ function lessonCapture(options) {
6832
7013
  counts.set(lesson.slug, n);
6833
7014
  if (n < o.minRepeats) return;
6834
7015
  written.add(lesson.slug);
6835
- await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log10.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
6836
- log10.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
7016
+ await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log11.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
7017
+ log11.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
6837
7018
  }
6838
7019
  };
6839
7020
  }
@@ -6841,7 +7022,7 @@ function lessonCapture(options) {
6841
7022
  // src/reflect.ts
6842
7023
  init_llm();
6843
7024
  init_logging();
6844
- var log11 = forComponent("Reflect");
7025
+ var log12 = forComponent("Reflect");
6845
7026
  async function reflectOnRun(o) {
6846
7027
  const digest = digestRun(o.result.messages, o.maxDigestChars ?? 6e3);
6847
7028
  if (!digest.trim()) return null;
@@ -6857,7 +7038,7 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
6857
7038
  const r = await o.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false });
6858
7039
  text = r?.content ?? "";
6859
7040
  } catch (e) {
6860
- log11.warn(`reflection call failed: ${e?.message ?? e}`);
7041
+ log12.warn(`reflection call failed: ${e?.message ?? e}`);
6861
7042
  return null;
6862
7043
  }
6863
7044
  const m = text.match(/LESSON:\s*(.+)/i);
@@ -6867,10 +7048,10 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
6867
7048
  try {
6868
7049
  await writeFact(o.fs, o.dir, slug2, lesson);
6869
7050
  } catch (e) {
6870
- log11.warn(`could not persist lesson: ${e?.message ?? e}`);
7051
+ log12.warn(`could not persist lesson: ${e?.message ?? e}`);
6871
7052
  return null;
6872
7053
  }
6873
- log11.debug(`reflection persisted ${slug2}`);
7054
+ log12.debug(`reflection persisted ${slug2}`);
6874
7055
  return slug2;
6875
7056
  }
6876
7057
  function digestRun(messages, maxChars) {
@@ -6920,7 +7101,7 @@ function forComponent2(name) {
6920
7101
  }
6921
7102
 
6922
7103
  // node_modules/@bod.ee/voice/src/core/emotion.ts
6923
- var log12 = forComponent2("Emotion");
7104
+ var log13 = forComponent2("Emotion");
6924
7105
  var EMOTIONS = [
6925
7106
  // primary (best results)
6926
7107
  "neutral",
@@ -7052,7 +7233,7 @@ function renderEmotions(text, opts = { show: true }) {
7052
7233
  speech += cartesiaTag(tag);
7053
7234
  if (opts.show) display += m[0];
7054
7235
  } else {
7055
- log12.debug(`dropping unknown emotion tag ${m[0]}`);
7236
+ log13.debug(`dropping unknown emotion tag ${m[0]}`);
7056
7237
  }
7057
7238
  last = m.index + m[0].length;
7058
7239
  }
@@ -7094,7 +7275,7 @@ var EmotionStream = class {
7094
7275
  if (tag) {
7095
7276
  this.pending = tag;
7096
7277
  if (this.show) display += m[0];
7097
- } else log12.debug(`dropping unknown emotion tag ${m[0]}`);
7278
+ } else log13.debug(`dropping unknown emotion tag ${m[0]}`);
7098
7279
  last = m.index + m[0].length;
7099
7280
  }
7100
7281
  this.emit(body.slice(last), (s, d, p) => {
@@ -7241,7 +7422,7 @@ function computeHoldMs(ctx) {
7241
7422
  }
7242
7423
 
7243
7424
  // node_modules/@bod.ee/voice/src/core/engine.ts
7244
- var log13 = forComponent2("VoiceEngine");
7425
+ var log14 = forComponent2("VoiceEngine");
7245
7426
  var realClock = {
7246
7427
  now: () => performance.now(),
7247
7428
  setTimeout: (fn, ms) => setTimeout(fn, ms),
@@ -7629,8 +7810,8 @@ var VoiceEngine = class _VoiceEngine {
7629
7810
  this.tts.warmup?.();
7630
7811
  this.setState("listening");
7631
7812
  if (this.options.bargeInMode === "aec" && !this.stt.usingAec)
7632
- 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})`);
7813
+ log14.warn("bargeInMode=aec but mic does not report echoCancellation applied \u2014 STT may transcribe the assistant");
7814
+ log14.debug(`voice I/O up (${this.usingAec ? "AEC" : "heuristic echo"} capture, mode=${this.options.bargeInMode}, mic reports aec=${this.stt.usingAec})`);
7634
7815
  }
7635
7816
  /** The RESOLVED barge-in tier (the single seam every tier branch reads). `bargeInMode` overrides
7636
7817
  * the mic's self-report — see the option doc for why the report is not trustworthy on mobile. */
@@ -7655,7 +7836,7 @@ var VoiceEngine = class _VoiceEngine {
7655
7836
  this.options.onDiag({ t: this.clock.now(), kind, ...fields });
7656
7837
  } catch (e) {
7657
7838
  this.diagOn = false;
7658
- log13.debug(`onDiag threw \u2014 diagnostics disabled: ${e instanceof Error ? e.message : e}`);
7839
+ log14.debug(`onDiag threw \u2014 diagnostics disabled: ${e instanceof Error ? e.message : e}`);
7659
7840
  }
7660
7841
  }
7661
7842
  idleWaiters = [];
@@ -7794,7 +7975,7 @@ var VoiceEngine = class _VoiceEngine {
7794
7975
  }
7795
7976
  for (const w of this.words(this.reply)) this.echoWords.add(w);
7796
7977
  this.tts.speak(forSpeech(speech), true);
7797
- if (!this.spokeDeltas && this.turnStartAt) log13.debug(`ttft: ${Math.round(this.clock.now() - this.turnStartAt)}ms`);
7978
+ if (!this.spokeDeltas && this.turnStartAt) log14.debug(`ttft: ${Math.round(this.clock.now() - this.turnStartAt)}ms`);
7798
7979
  this.spokeDeltas = true;
7799
7980
  this.setState("speaking");
7800
7981
  if (this.options.revealMode !== "off" && display) {
@@ -7907,13 +8088,13 @@ var VoiceEngine = class _VoiceEngine {
7907
8088
  this.drainTimer = null;
7908
8089
  this.speaking = false;
7909
8090
  this.stopWordReveal(true);
7910
- if (this.turnStartAt) log13.debug(`turn: ${Math.round(this.clock.now() - this.turnStartAt)}ms (incl. playback)`);
8091
+ if (this.turnStartAt) log14.debug(`turn: ${Math.round(this.clock.now() - this.turnStartAt)}ms (incl. playback)`);
7911
8092
  this.echoUntil = this.clock.now() + 2500;
7912
8093
  if (!this.usingAec) this.stt.reset();
7913
8094
  try {
7914
8095
  this.options.onSpeechEnd();
7915
8096
  } catch (e) {
7916
- log13.warn(`onSpeechEnd threw: ${e instanceof Error ? e.message : e}`);
8097
+ log14.warn(`onSpeechEnd threw: ${e instanceof Error ? e.message : e}`);
7917
8098
  }
7918
8099
  this.setState("listening");
7919
8100
  if (this.uttQueue.length) this.pumpQueue();
@@ -7987,7 +8168,7 @@ var VoiceEngine = class _VoiceEngine {
7987
8168
  if (!host) return pick;
7988
8169
  const ctx = { text: this.lastDispatchText, question: this.lastDispatchWasQuestion, barged: this.bargedBeforeDispatch };
7989
8170
  const fallback = (e) => {
7990
- log13.warn(`pickAck failed \u2014 using the default pools: ${e instanceof Error ? e.message : e}`);
8171
+ log14.warn(`pickAck failed \u2014 using the default pools: ${e instanceof Error ? e.message : e}`);
7991
8172
  pick.picked = false;
7992
8173
  return void 0;
7993
8174
  };
@@ -8077,7 +8258,7 @@ var VoiceEngine = class _VoiceEngine {
8077
8258
  this.ackProbe = null;
8078
8259
  const queued = this.uttQueue.splice(0);
8079
8260
  const droppedQueued = queued.length;
8080
- if (droppedQueued) log13.info(`barge-in cut ${droppedQueued} queued worker utterance(s) \u2014 handed to the host`);
8261
+ if (droppedQueued) log14.info(`barge-in cut ${droppedQueued} queued worker utterance(s) \u2014 handed to the host`);
8081
8262
  this.overlapKeptUntil = 0;
8082
8263
  if (!this.speaking && !this.drainTimer) {
8083
8264
  if (droppedQueued) this.lastInterrupted = { full: "", heard: "", queued, queuedTurn: false };
@@ -8306,7 +8487,7 @@ var VoiceEngine = class _VoiceEngine {
8306
8487
  if (flat && this.lastDispatchFlat && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS && (this.lastDispatchFlat.startsWith(flat) || flat.startsWith(this.lastDispatchFlat))) return;
8307
8488
  this.specSpent = true;
8308
8489
  this.specText = this.specPartial;
8309
- log13.debug(`speculate: "${this.specText.slice(0, 60)}"`);
8490
+ log14.debug(`speculate: "${this.specText.slice(0, 60)}"`);
8310
8491
  this.diag("speculate", { text: this.specText });
8311
8492
  this.options.onSpeculate(this.specText);
8312
8493
  }, this.options.speculativeMs);
@@ -8375,7 +8556,7 @@ var VoiceEngine = class _VoiceEngine {
8375
8556
  };
8376
8557
  this.tts.newContext();
8377
8558
  this.tts.speak(phrase, false);
8378
- log13.debug(`backchannel: "${phrase}"`);
8559
+ log14.debug(`backchannel: "${phrase}"`);
8379
8560
  this.diag("backchannel", { phrase });
8380
8561
  this.options.onBackchannel(phrase);
8381
8562
  }
@@ -8408,7 +8589,7 @@ var VoiceEngine = class _VoiceEngine {
8408
8589
  });
8409
8590
  if (idx < 0) return text;
8410
8591
  toks.splice(idx, 1);
8411
- log13.verbose(`stripped backchannel echo "${this.lastBcPhrase}" from dispatching final`);
8592
+ log14.verbose(`stripped backchannel echo "${this.lastBcPhrase}" from dispatching final`);
8412
8593
  return toks.join(" ");
8413
8594
  }
8414
8595
  /** Merge a resumed utterance into the pending one, deduping any word-overlap. Soniox re-finalizes
@@ -8594,7 +8775,7 @@ var VoiceEngine = class _VoiceEngine {
8594
8775
  }
8595
8776
  const bargeHold = !this.looksIncomplete(this.pendingUtt) && this.unfinishedBargeLine(this.pendingUtt);
8596
8777
  if (this.options.incompleteMergeMs && (bargeHold || this.looksIncomplete(this.pendingUtt))) {
8597
- log13.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
8778
+ log14.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
8598
8779
  this.diag("hold", { reason: bargeHold ? "barge_unfinished" : "incomplete", tail: this.pendingUtt.slice(-40) });
8599
8780
  this.options.onHold();
8600
8781
  if (this.options.holdFiller && !this.speaking) this.speakFiller(this.options.holdFiller);
@@ -8633,13 +8814,13 @@ var VoiceEngine = class _VoiceEngine {
8633
8814
  const answered = this.answeredSinceDispatch();
8634
8815
  const part = flat !== this.lastDispatchFlat && !answered && this.echoesDispatch(text);
8635
8816
  if (part || flat && flat === this.lastDispatchFlat && !answered && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS) {
8636
- log13.verbose(`dropped duplicate final "${text.slice(0, 40)}" (identical to the just-dispatched utterance, no reply between)`);
8817
+ log14.verbose(`dropped duplicate final "${text.slice(0, 40)}" (identical to the just-dispatched utterance, no reply between)`);
8637
8818
  this.diag("utterance_drop", { reason: "dup_final", text: text.slice(0, 60) });
8638
8819
  return;
8639
8820
  }
8640
8821
  const refinal = this.refinalizes(text);
8641
8822
  if (refinal && this.repliedSinceDispatch) {
8642
- log13.verbose(`dropped re-finalization "${text.slice(0, 60)}" (superset of the just-dispatched utterance, reply already flowing)`);
8823
+ log14.verbose(`dropped re-finalization "${text.slice(0, 60)}" (superset of the just-dispatched utterance, reply already flowing)`);
8643
8824
  this.diag("utterance_drop", { reason: "refinalized", text: text.slice(0, 60) });
8644
8825
  this.lastDispatchFlat = flat;
8645
8826
  this.lastDispatchWords = text.trim().split(/\s+/);
@@ -8918,7 +9099,7 @@ var SpokenSplitter = class {
8918
9099
  };
8919
9100
 
8920
9101
  // node_modules/@bod.ee/voice/src/adapters/soniox.ts
8921
- var log14 = forComponent2("SonioxSTT");
9102
+ var log15 = forComponent2("SonioxSTT");
8922
9103
  var now = () => performance.now();
8923
9104
  var SonioxSTTOptions = class {
8924
9105
  auth = "";
@@ -8963,7 +9144,7 @@ var SonioxSTT = class {
8963
9144
  this.onDiag({ t: now(), kind, ...fields });
8964
9145
  } catch (e) {
8965
9146
  this.diagOn = false;
8966
- log14.debug(`onDiag threw \u2014 STT diagnostics disabled: ${e instanceof Error ? e.message : e}`);
9147
+ log15.debug(`onDiag threw \u2014 STT diagnostics disabled: ${e instanceof Error ? e.message : e}`);
8967
9148
  }
8968
9149
  }
8969
9150
  lastChunkAt = 0;
@@ -9005,11 +9186,11 @@ var SonioxSTT = class {
9005
9186
  this.ws.onmessage = (ev) => this.handle(JSON.parse(String(ev.data)));
9006
9187
  this.ws.onclose = (ev) => {
9007
9188
  if (this.stopped) return;
9008
- log14.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
9189
+ log15.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
9009
9190
  this.diag("stt_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: true });
9010
9191
  this.reset();
9011
9192
  this.connectWs().catch((e) => {
9012
- log14.error(`soniox reconnect failed: ${e.message}`);
9193
+ log15.error(`soniox reconnect failed: ${e.message}`);
9013
9194
  this.diag("stt_reconnect_failed", { message: e.message });
9014
9195
  });
9015
9196
  };
@@ -9021,7 +9202,7 @@ var SonioxSTT = class {
9021
9202
  this.endpointTimer = setInterval(() => {
9022
9203
  const combined = (this.finalText + this.partialText).trim();
9023
9204
  if (!combined || now() - this.lastChangeAt < this.options.silenceEndpointMs) return;
9024
- if (this.firstTokenAt) log14.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
9205
+ if (this.firstTokenAt) log15.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
9025
9206
  this.reset();
9026
9207
  this.onUtterance(combined, now());
9027
9208
  }, 120);
@@ -9033,7 +9214,7 @@ var SonioxSTT = class {
9033
9214
  if (this.stopped) return;
9034
9215
  const ref = this.lastChunkAt || this.startedChunksAt;
9035
9216
  if (now() - ref > noAudioMs) {
9036
- log14.error(`stt: no mic audio for >${Math.round(noAudioMs / 1e3)}s \u2014 capture device stopped delivering`);
9217
+ log15.error(`stt: no mic audio for >${Math.round(noAudioMs / 1e3)}s \u2014 capture device stopped delivering`);
9037
9218
  this.diag("stt_watchdog_fatal", { noAudioMs });
9038
9219
  this.onFatal("microphone stopped delivering audio (try a different input device, e.g. AirPods, or check System Settings \u2192 Sound \u2192 Input)");
9039
9220
  this.stop();
@@ -9056,7 +9237,7 @@ var SonioxSTT = class {
9056
9237
  handle(m) {
9057
9238
  if (m.error_message) {
9058
9239
  this.diag("stt_error", { message: String(m.error_message), code: m.error_code });
9059
- return log14.error(`soniox: ${m.error_message}`);
9240
+ return log15.error(`soniox: ${m.error_message}`);
9060
9241
  }
9061
9242
  let endpoint = false;
9062
9243
  for (const t of m.tokens ?? []) {
@@ -9073,7 +9254,7 @@ var SonioxSTT = class {
9073
9254
  this.onPartial(combined);
9074
9255
  if (endpoint && this.finalText.trim()) {
9075
9256
  const utterance = this.finalText.trim();
9076
- if (this.firstTokenAt) log14.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
9257
+ if (this.firstTokenAt) log15.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
9077
9258
  this.reset();
9078
9259
  this.onUtterance(utterance, now());
9079
9260
  }
@@ -9095,7 +9276,7 @@ var SonioxSTT = class {
9095
9276
  };
9096
9277
 
9097
9278
  // node_modules/@bod.ee/voice/src/adapters/cartesia.ts
9098
- var log15 = forComponent2("CartesiaTTS");
9279
+ var log16 = forComponent2("CartesiaTTS");
9099
9280
  var now2 = () => performance.now();
9100
9281
  var CartesiaTTSOptions = class {
9101
9282
  auth = "";
@@ -9131,7 +9312,7 @@ var CartesiaTTS = class _CartesiaTTS {
9131
9312
  this.onDiag({ t: now2(), kind, ...fields });
9132
9313
  } catch (e) {
9133
9314
  this.diagOn = false;
9134
- log15.debug(`onDiag threw \u2014 TTS diagnostics disabled: ${e instanceof Error ? e.message : e}`);
9315
+ log16.debug(`onDiag threw \u2014 TTS diagnostics disabled: ${e instanceof Error ? e.message : e}`);
9135
9316
  }
9136
9317
  }
9137
9318
  firstAudioAt = 0;
@@ -9166,11 +9347,11 @@ var CartesiaTTS = class _CartesiaTTS {
9166
9347
  this.ws.onerror = (e) => rej(new Error(`cartesia ws: ${e.message || "connect failed"}`));
9167
9348
  });
9168
9349
  this.ws.onclose = (ev) => {
9169
- log15.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
9350
+ log16.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
9170
9351
  this.diag("tts_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: !this.closed });
9171
9352
  if (!this.closed) {
9172
9353
  this.connecting = this.doConnect().catch((e) => {
9173
- log15.error(`cartesia reconnect failed: ${e.message}`);
9354
+ log16.error(`cartesia reconnect failed: ${e.message}`);
9174
9355
  this.diag("tts_reconnect_failed", { message: e.message });
9175
9356
  });
9176
9357
  }
@@ -9198,12 +9379,12 @@ var CartesiaTTS = class _CartesiaTTS {
9198
9379
  this.down = true;
9199
9380
  this.downAt = now2();
9200
9381
  this.consecutiveOk = 0;
9201
- log15.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
9382
+ log16.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
9202
9383
  this.diag("tts_breaker_open", { errors: this.consecutiveErrors });
9203
9384
  this.onDone();
9204
9385
  this.startProbe();
9205
9386
  } else if (!this.down) {
9206
- (/No valid transcripts/i.test(m.message || "") ? log15.debug : log15.warn)(`cartesia: ${JSON.stringify(m)}`);
9387
+ (/No valid transcripts/i.test(m.message || "") ? log16.debug : log16.warn)(`cartesia: ${JSON.stringify(m)}`);
9207
9388
  }
9208
9389
  }
9209
9390
  };
@@ -9218,7 +9399,7 @@ var CartesiaTTS = class _CartesiaTTS {
9218
9399
  this.stopProbe();
9219
9400
  const downMs = this.downAt ? now2() - this.downAt : 0;
9220
9401
  this.diag("tts_breaker_close", { downMs: Math.round(downMs) });
9221
- (downMs < 2e3 ? log15.debug : log15.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
9402
+ (downMs < 2e3 ? log16.debug : log16.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
9222
9403
  }
9223
9404
  /** Ensure the WS is open before sending — reconnects if idle-closed. */
9224
9405
  async ensureConnected() {
@@ -9301,13 +9482,13 @@ function base64ToBytes(b64) {
9301
9482
  }
9302
9483
 
9303
9484
  // node_modules/@bod.ee/voice/src/adapters/fish.ts
9304
- var log16 = forComponent2("FishTTS");
9485
+ var log17 = forComponent2("FishTTS");
9305
9486
 
9306
9487
  // node_modules/@bod.ee/voice/src/adapters/grok.ts
9307
- var log17 = forComponent2("GrokTTS");
9488
+ var log18 = forComponent2("GrokTTS");
9308
9489
 
9309
9490
  // node_modules/@bod.ee/voice/src/adapters/grok-stt.ts
9310
- var log18 = forComponent2("GrokSTT");
9491
+ var log19 = forComponent2("GrokSTT");
9311
9492
 
9312
9493
  // src/voice/engine.ts
9313
9494
  init_logging();
@@ -9323,7 +9504,7 @@ import { AIClient, DecideClient, JevAdapter, LlmDecideAdapter } from "ai.libx.js
9323
9504
 
9324
9505
  // src/voice/overlap.ts
9325
9506
  init_logging();
9326
- var log19 = forComponent("Overlap");
9507
+ var log20 = forComponent("Overlap");
9327
9508
  var norm2 = (s) => s.toLowerCase().replace(/[’']/g, "'").replace(/[^\p{L}\p{N}' -]+/gu, " ").replace(/\s+/g, " ").trim();
9328
9509
  var HARD = /\b(?:stop|wait|no|nope|nah|hold on|hang on|hold up|pause|cancel|quiet|enough|shut up|never ?mind|actually|but|sorry what|what|why|how|who|when|where|which|can you|could you|tell me|i mean|excuse me)\b/;
9329
9510
  var ACK = new Set("yeah yea yes yep yup ya ok okay k right sure true totally exactly definitely absolutely indeed mm mmm hmm mhm mm-hm uh-huh uh huh aha ah oh ooh wow nice cool great awesome perfect good fine interesting gotcha got it i see makes sense fair agreed correct of course for sure lol ha haha hahaha heh hehe yeah yeah alright all right sweet neat oh nice oh wow oh cool oh okay oh yeah oh right that's right that's true thats right thats true love it i know me too same".split(" "));
@@ -9403,8 +9584,8 @@ var OverlapJudge = class {
9403
9584
  const t0 = this.o.now();
9404
9585
  const done = (verdict, by) => {
9405
9586
  const d = { verdict, by, ms: Math.round(this.o.now() - t0) };
9406
- if (by === "late" || by === "error" || by === "none") 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`);
9587
+ if (by === "late" || by === "error" || by === "none") log20.warn(`overlap "${heard.slice(0, 60)}": no verdict (${by}, ${d.ms}ms) \u2192 cut`);
9588
+ else log20.debug(`overlap "${heard.slice(0, 60)}": ${verdict} by ${by} ${d.ms}ms`);
9408
9589
  this.o.onDecision(heard, d);
9409
9590
  return d;
9410
9591
  };
@@ -9421,7 +9602,7 @@ var OverlapJudge = class {
9421
9602
  });
9422
9603
  try {
9423
9604
  const v = await Promise.race([this.o.model(heard, ctl.signal).catch((e) => {
9424
- if (!ctl.signal.aborted) log19.warn(`overlap model failed: ${e.message}`);
9605
+ if (!ctl.signal.aborted) log20.warn(`overlap model failed: ${e.message}`);
9425
9606
  return "error";
9426
9607
  }), late]);
9427
9608
  if (v === "late" || v === "error") return done("cut", v);
@@ -9437,15 +9618,15 @@ function overlapModelFor(ai, env = process.env) {
9437
9618
  const m = fastOverlapModel(ai, model);
9438
9619
  const t0 = performance.now();
9439
9620
  void m("yeah", AbortSignal.timeout(1e4)).then(
9440
- () => 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}`)
9621
+ () => log20.debug(`fast model warm in ${Math.round(performance.now() - t0)}ms`),
9622
+ (e) => log20.warn(`fast model warm-up failed (the first overlap verdict may be late): ${e?.message ?? e}`)
9442
9623
  );
9443
9624
  return m;
9444
9625
  }
9445
9626
  var softBarge = (mode) => mode !== "turn";
9446
9627
 
9447
9628
  // src/voice/instinct.ts
9448
- var log20 = forComponent("Instinct");
9629
+ var log21 = forComponent("Instinct");
9449
9630
  function voiceDeciderOptions(env = process.env) {
9450
9631
  if (env.TYPESAFE_AI_KEY) return { apiKey: env.TYPESAFE_AI_KEY };
9451
9632
  if (env.GROQ_API_KEY) return { provider: "llm", model: "groq/openai/gpt-oss-120b", apiKey: env.GROQ_API_KEY };
@@ -9716,13 +9897,13 @@ var Instinct = class {
9716
9897
  * unfinished partial is no turn to react to — the interjection itself is the reply. */
9717
9898
  pickAck(ctx) {
9718
9899
  if (this.interjecting) {
9719
- log20.debug(`ack: cut-in turn \u2192 silent (engine context "${ctx.text.slice(0, 40)}" is the previous final)`);
9900
+ log21.debug(`ack: cut-in turn \u2192 silent (engine context "${ctx.text.slice(0, 40)}" is the previous final)`);
9720
9901
  return Promise.resolve(null);
9721
9902
  }
9722
9903
  const text = ctx.text.trim();
9723
9904
  const p = (async () => {
9724
9905
  const intentP = ctx.barged ? this.bargeIntent(text).catch((e) => {
9725
- log20.warn(`barge intent for the ack failed \u2014 situational ack: ${e.message}`);
9906
+ log21.warn(`barge intent for the ack failed \u2014 situational ack: ${e.message}`);
9726
9907
  return null;
9727
9908
  }) : null;
9728
9909
  const d = await this.ask(
@@ -9744,7 +9925,7 @@ var Instinct = class {
9744
9925
  settle = r;
9745
9926
  });
9746
9927
  const timer = setTimeout(() => {
9747
- log20.debug(`ack: no engine outcome in ${this.o.ackSettleMs}ms \u2014 not claimed`);
9928
+ log21.debug(`ack: no engine outcome in ${this.o.ackSettleMs}ms \u2014 not claimed`);
9748
9929
  settle(null);
9749
9930
  }, this.o.ackSettleMs);
9750
9931
  void spoken.then(() => clearTimeout(timer));
@@ -9793,7 +9974,7 @@ var Instinct = class {
9793
9974
  });
9794
9975
  return { kind, heard, choice: answers.act.choice, probabilities: answers.act.probabilities, ms: Math.round(this.o.now() - t0) };
9795
9976
  } catch (e) {
9796
- log20.warn(`${kind} decision failed \u2014 falling back to engine defaults: ${e.message}`);
9977
+ log21.warn(`${kind} decision failed \u2014 falling back to engine defaults: ${e.message}`);
9797
9978
  if (rethrow) throw e;
9798
9979
  return null;
9799
9980
  }
@@ -9802,7 +9983,7 @@ var Instinct = class {
9802
9983
 
9803
9984
  // src/voice/unsaid.ts
9804
9985
  init_logging();
9805
- var log21 = forComponent("Unsaid");
9986
+ var log22 = forComponent("Unsaid");
9806
9987
  var STOP2 = new Set("that this with have from they them their there then than what when were will would your about into just also been some only over more most very which while after before because these those does here".split(" "));
9807
9988
  var contentStems = (s) => (s.toLowerCase().match(/[a-z0-9']+/g) ?? []).filter((w) => w.length >= 4 && !STOP2.has(w)).map((w) => w.slice(0, 4));
9808
9989
  var SAID_OVERLAP = 0.6;
@@ -9842,7 +10023,7 @@ async function unsaidClauses(pieces, spoken, decider, timeoutMs = 5e3) {
9842
10023
  return p < CONVEYED_MIN;
9843
10024
  }).map((x) => x.c);
9844
10025
  } catch (e) {
9845
- log21.warn(`unsaid judge failed \u2014 only near-zero-overlap clauses count as unsaid: ${e.message}`);
10026
+ log22.warn(`unsaid judge failed \u2014 only near-zero-overlap clauses count as unsaid: ${e.message}`);
9846
10027
  return fallback();
9847
10028
  } finally {
9848
10029
  clearTimeout(timer);
@@ -9963,7 +10144,7 @@ var RESULT_CLAIM_RE = new RegExp(LEAD_TAGS + String.raw`(?:(?:all\s+)?done|finis
9963
10144
  var DONE_CLAIM_RE = new RegExp(LEAD_TAGS + String.raw`(?:(?:all\s+)?done|finished|it['’]?s\s+(?:done|finished)|completed?)\b`, "i");
9964
10145
 
9965
10146
  // src/voice/channelLang.ts
9966
- var log22 = forComponent("ChannelBanks");
10147
+ var log23 = forComponent("ChannelBanks");
9967
10148
  var LINE_KINDS = ["backchannel", "filler", "bridge"];
9968
10149
  var BANK_KEYS = [...LINE_KINDS, "phatic"];
9969
10150
  var EN_PHATIC = [
@@ -10117,11 +10298,11 @@ function cleanLine(s, lang, maxWords = 6) {
10117
10298
  const raw = s.trim().split("\n")[0];
10118
10299
  const l = raw.replace(/^[-*•\d.)\s]+/, "").replace(/^["'“”«»]+|["'“”«»]+$/g, "").replace(new RegExp("^\\p{L}+:\\s*", "u"), "").trim();
10119
10300
  if (!l || isLeakSuspect(raw) || isLeakSuspect(l)) {
10120
- if (l) log22.debug(`line "${raw}" reads as meta \u2014 rejected`);
10301
+ if (l) log23.debug(`line "${raw}" reads as meta \u2014 rejected`);
10121
10302
  return "";
10122
10303
  }
10123
10304
  if (lang && !fitsLanguage(l, lang)) {
10124
- log22.debug(`line "${l}" is not ${lang} \u2014 rejected`);
10305
+ log23.debug(`line "${l}" is not ${lang} \u2014 rejected`);
10125
10306
  return "";
10126
10307
  }
10127
10308
  return l.split(/\s+/).length > maxWords || l.length > 60 ? "" : l;
@@ -10135,7 +10316,7 @@ function validateBank(raw, lang, minPerKind = 3) {
10135
10316
  const seen = /* @__PURE__ */ new Set();
10136
10317
  out[k] = (Array.isArray(v) ? v : []).filter((x) => typeof x === "string").map((x) => cleanLine(x, lang)).filter((x) => x && !(k === "filler" && SPEAKER_FILLER.test(norm3(x))) && !seen.has(norm3(x)) && seen.add(norm3(x)));
10137
10318
  if (out[k].length < minPerKind) {
10138
- log22.warn(`bank ${lang}: only ${out[k].length} valid ${k} lines \u2014 rejected`);
10319
+ log23.warn(`bank ${lang}: only ${out[k].length} valid ${k} lines \u2014 rejected`);
10139
10320
  return null;
10140
10321
  }
10141
10322
  }
@@ -10211,11 +10392,11 @@ var ChannelBanks = class {
10211
10392
  const f = this.file(lang);
10212
10393
  let cached = null;
10213
10394
  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}`);
10395
+ if (e.code !== "ENOENT") log23.warn(`bank cache ${f} unreadable \u2014 regenerating: ${e.message}`);
10215
10396
  return null;
10216
10397
  });
10217
10398
  if (cached && cached.v !== BANK_VERSION) {
10218
- log22.debug(`bank ${lang}: cache v${cached.v ?? 1} predates v${BANK_VERSION} \u2014 regenerating`);
10399
+ log23.debug(`bank ${lang}: cache v${cached.v ?? 1} predates v${BANK_VERSION} \u2014 regenerating`);
10219
10400
  cached = null;
10220
10401
  }
10221
10402
  const valid = cached && validateBank(cached.bank, lang, 0);
@@ -10223,7 +10404,7 @@ var ChannelBanks = class {
10223
10404
  if (valid) {
10224
10405
  for (const k of BANK_KEYS) if (!valid[k].length) valid[k] = [...this.o.fallback[lang]?.[k] ?? []];
10225
10406
  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"}`);
10407
+ log23.debug(`bank ${lang}: cache hit (${BANK_KEYS.map((k) => valid[k].length).join("/")})${complete ? "" : " \u2014 missing kinds, refreshing"}`);
10227
10408
  }
10228
10409
  if (complete && this.o.now() - cached.at < this.o.refreshMs) return;
10229
10410
  await this.generate(lang, valid);
@@ -10242,11 +10423,11 @@ var ChannelBanks = class {
10242
10423
  bank[k] = [...fresh[k], ...have?.[k] ?? []].filter((x) => !seen.has(norm3(x)) && seen.add(norm3(x))).slice(0, this.o.maxPerKind);
10243
10424
  }
10244
10425
  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`);
10426
+ log23.debug(`bank ${lang}: ${have ? "refreshed" : "generated"} (${BANK_KEYS.map((k) => bank[k].length).join("/")}) in ${Math.round(this.o.now() - t0)}ms`);
10246
10427
  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}`));
10428
+ if (f) await mkdir(this.o.cacheDir, { recursive: true }).then(() => writeFile(f, JSON.stringify({ at: this.o.now(), v: BANK_VERSION, bank }))).catch((e) => log23.warn(`bank cache write ${f} failed: ${e.message}`));
10248
10429
  } 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}`);
10430
+ log23.warn(`bank ${lang}: generation failed \u2014 ${have ? "keeping the cached bank" : lang in this.o.fallback ? "fallback bank" : "silent"}: ${e.message}`);
10250
10431
  } finally {
10251
10432
  clearTimeout(t);
10252
10433
  if (!this.banks.has(lang)) setTimeout(() => this.pending.delete(lang), this.o.retryMs).unref?.();
@@ -10439,7 +10620,7 @@ var TurnGate = class {
10439
10620
  };
10440
10621
 
10441
10622
  // src/duplex.ts
10442
- var log23 = forComponent("DuplexAgent");
10623
+ var log24 = forComponent("DuplexAgent");
10443
10624
  function describeCall(call) {
10444
10625
  const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
10445
10626
  const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
@@ -10774,7 +10955,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
10774
10955
  const m = this.reflexBuf.match(RESERVED_EVENT_MARKER) ?? this.reflexBuf.match(RESERVED_EVENT_OPENER);
10775
10956
  if (m) {
10776
10957
  this.fabricationCut = true;
10777
- log23.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
10958
+ log24.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
10778
10959
  this.reflexBuf = this.reflexBuf.slice(0, Math.max(m.index, this.reflexForwarded));
10779
10960
  this.pumpReflex(true);
10780
10961
  return;
@@ -10916,7 +11097,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
10916
11097
  this.leakHit = true;
10917
11098
  this.leakCuts.push(leak.trim().slice(0, 40));
10918
11099
  const why = this.cutKind === "claim" ? "stated a result no task has given yet" : this.cutKind === "restart" ? "started a second, restated reply" : "spoke its own analysis";
10919
- log23.warn(`reflex ${why} ("${leak.trim().slice(0, 40)}\u2026") \u2014 cut before TTS`);
11100
+ log24.warn(`reflex ${why} ("${leak.trim().slice(0, 40)}\u2026") \u2014 cut before TTS`);
10920
11101
  this.notify("diag", this.cutKind === "claim" ? "result_claim" : this.cutKind === "restart" ? "reply_restart" : "analysis_leak", { text: leak.trim().slice(0, 80), at: this.turnSpoken.trim() ? "mid_reply" : "before_speech" });
10921
11102
  const next = this.stepMarks.find((k) => k > at);
10922
11103
  if (next === void 0) {
@@ -10957,7 +11138,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
10957
11138
  this.notify("diag", "leak_judge", { text: s.slice(0, 80), p: Math.round(p * 100) / 100, ms: Math.round(performance.now() - t0) });
10958
11139
  return p >= LEAK_JUDGE_THRESHOLD;
10959
11140
  }, (e) => {
10960
- log23.warn(`leak judge failed \u2014 speaking the sentence: ${e.message}`);
11141
+ log24.warn(`leak judge failed \u2014 speaking the sentence: ${e.message}`);
10961
11142
  return false;
10962
11143
  }).then((leak) => {
10963
11144
  clearTimeout(timer);
@@ -11029,7 +11210,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11029
11210
  let i = t.length - 1;
11030
11211
  while (i >= this.turnBase && !(t[i].role === "assistant" && typeof t[i].content === "string" && t[i].content.includes(key))) i--;
11031
11212
  if (i < this.turnBase) {
11032
- log23.warn("analysis leak cut, but its text was not found in the transcript to scrub");
11213
+ log24.warn("analysis leak cut, but its text was not found in the transcript to scrub");
11033
11214
  continue;
11034
11215
  }
11035
11216
  const m = t[i];
@@ -11086,7 +11267,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11086
11267
  const res = await this.sendWithContext(content, context, note);
11087
11268
  const fb = this.options.reflexFallbackModel, model = this.voice.options.model;
11088
11269
  if (res.finishReason !== "error" || !rateLimited(res.error) || !fb || fb === model || fb.includes("/") && model.includes("/") && fb.split("/")[0] !== model.split("/")[0] || this.turnDispatched || this.turnSpoken.trim()) return res;
11089
- log23.warn(`reflex ${model} rate-limited \u2014 re-running the turn on ${fb}`);
11270
+ log24.warn(`reflex ${model} rate-limited \u2014 re-running the turn on ${fb}`);
11090
11271
  this.notify("diag", "reflex_fallback", { from: model, to: fb });
11091
11272
  this.voice.transcript.length = base;
11092
11273
  this.voice.options.model = fb;
@@ -11135,7 +11316,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11135
11316
  try {
11136
11317
  await this.sendWithContext(reminder, this.lastContext);
11137
11318
  } catch (e) {
11138
- log23.warn(`follow-through nudge failed: ${e instanceof Error ? e.message : e}`);
11319
+ log24.warn(`follow-through nudge failed: ${e instanceof Error ? e.message : e}`);
11139
11320
  } finally {
11140
11321
  const i = this.voice.transcript.findIndex((m, k) => k >= base && m.role === "user" && m.content === reminder);
11141
11322
  if (i >= 0) this.voice.transcript.splice(i, 1);
@@ -11196,7 +11377,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11196
11377
  return {
11197
11378
  preToolUse: (call) => {
11198
11379
  if (this.spec?.state === "pending" && !_DuplexAgent.SPEC_SAFE_TOOLS.has(call.name)) {
11199
- log23.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
11380
+ log24.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
11200
11381
  this.abortSpeculation();
11201
11382
  return { block: true, reason: "Speculative turn aborted." };
11202
11383
  }
@@ -11278,7 +11459,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11278
11459
  try {
11279
11460
  await this.sendWithContext(reminder, this.lastContext);
11280
11461
  } catch (e) {
11281
- log23.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
11462
+ log24.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
11282
11463
  } finally {
11283
11464
  this.nudging = false;
11284
11465
  const i = this.voice.transcript.findIndex((m, k) => k >= base && m.role === "user" && m.content === reminder);
@@ -11334,7 +11515,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11334
11515
  const accepted = this.offerOpen && this.parkedRedeliver.length > 0 && this.parkedRedeliver.every((p) => p.task === this.offer.task) && (isOfferAcceptance(content) || isTrivialBarge(content));
11335
11516
  const judge = accepted ? void 0 : this.options.bargeIntent;
11336
11517
  return (accepted ? Promise.resolve("resume") : judge ? judge(content).catch((e) => {
11337
- log23.warn(`bargeIntent judge failed \u2014 using the allowlist: ${e.message}`);
11518
+ log24.warn(`bargeIntent judge failed \u2014 using the allowlist: ${e.message}`);
11338
11519
  return fallback();
11339
11520
  }) : Promise.resolve(fallback())).then((intent) => {
11340
11521
  this.notify("diag", "barge_intent", { intent, text: content.slice(0, 80) });
@@ -11537,7 +11718,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11537
11718
  try {
11538
11719
  res = await this.sendWithContext(spec.text, this.lastContext);
11539
11720
  } catch (e) {
11540
- log23.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
11721
+ log24.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
11541
11722
  } finally {
11542
11723
  this.voice.options.signal = prevSignal;
11543
11724
  }
@@ -11551,7 +11732,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11551
11732
  if (d === "abort") {
11552
11733
  if (this.voice.transcript.length > base) this.voice.transcript.length = base;
11553
11734
  this.speculativeAbortedCalls++;
11554
- log23.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
11735
+ log24.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
11555
11736
  if (this.spec === spec) this.spec = void 0;
11556
11737
  return res ?? empty;
11557
11738
  }
@@ -11937,7 +12118,7 @@ Another agent just implemented the above. Independently check the CURRENT state
11937
12118
  this.notify("task_verify", `task ${id}: verifying`, { id });
11938
12119
  const cres = await new Agent(checkerOpts).run(checkBrief);
11939
12120
  if (cres.finishReason !== "stop") {
11940
- log23.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
12121
+ log24.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
11941
12122
  this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
11942
12123
  }
11943
12124
  const sum = (a = 0, b = 0) => a + b;
@@ -12073,7 +12254,7 @@ Another agent just implemented the above. Independently check the CURRENT state
12073
12254
  rec.status = "done";
12074
12255
  rec.result = res.text;
12075
12256
  const incomplete = res.finishReason !== "stop" && res.finishReason !== "empty";
12076
- log23.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
12257
+ log24.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
12077
12258
  this.notify("task_done", `task ${id} (${rec.label}) completed`, {
12078
12259
  id,
12079
12260
  text: res.text,
@@ -12107,14 +12288,14 @@ Another agent just implemented the above. Independently check the CURRENT state
12107
12288
  foldIfSuperseded(rec, note) {
12108
12289
  if (rec.deliveryParked !== "moved_on") return false;
12109
12290
  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`);
12291
+ log24.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
12111
12292
  return true;
12112
12293
  }
12113
12294
  failTask(rec, msg) {
12114
12295
  this.dropAsk(rec.id);
12115
12296
  rec.status = "error";
12116
12297
  rec.result = msg;
12117
- log23.warn(`task ${rec.id} failed: ${msg}`);
12298
+ log24.warn(`task ${rec.id} failed: ${msg}`);
12118
12299
  this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`, { id: rec.id });
12119
12300
  if (this.foldIfSuperseded(rec, `The "${rec.label}" task failed: ${msg}`)) return;
12120
12301
  this.dropParked(rec);
@@ -12418,7 +12599,7 @@ Another agent just implemented the above. Independently check the CURRENT state
12418
12599
  init_llm();
12419
12600
  init_logging();
12420
12601
  init_models();
12421
- var log24 = forComponent("ModelRouter");
12602
+ var log25 = forComponent("ModelRouter");
12422
12603
  var ModelRouterOptions = class {
12423
12604
  classifier = jevClassifier();
12424
12605
  /** Capability tiers → model id (any provider). basic/standard run the main session; premium is only ever an offload. */
@@ -12502,10 +12683,10 @@ var ModelRouter = class {
12502
12683
  if (tier === "premium") d.offload = this.state.offload && (c.sameThread ?? 0) >= o.sameThreadAt ? "continue" : "fresh";
12503
12684
  } catch (e) {
12504
12685
  const msg = e instanceof Error ? e.message : String(e);
12505
- log24.warn(`classifier failed, falling back to standard: ${msg}`);
12686
+ log25.warn(`classifier failed, falling back to standard: ${msg}`);
12506
12687
  d = { tier: "standard", model: o.models.standard, probs: {}, fallback: msg, classifyMs: Math.round(performance.now() - t0) };
12507
12688
  }
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`);
12689
+ log25.info(`route ${d.tier}${d.offload ? `(${d.offload})` : ""} \xB7 argmax=${d.choice ?? "-"} \xB7 p=${JSON.stringify(d.probs)}${d.sameThread != null ? ` \xB7 same=${d.sameThread.toFixed(2)}` : ""} \xB7 ${d.classifyMs}ms`);
12509
12690
  return d;
12510
12691
  }
12511
12692
  /** Run one user turn on `agent` with routing. Returns a RunResult carrying `costUsd` + `routing`. */
@@ -12557,7 +12738,7 @@ ${text}`;
12557
12738
  const answer = res.text || `(premium sub-agent finished with no reply; finishReason=${res.finishReason})`;
12558
12739
  agent.transcript.push({ role: "user", content }, { role: "assistant", content: answer });
12559
12740
  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`);
12741
+ log25.info(`offload ${cont ? "continue" : "fresh"} done \xB7 ${res.finishReason} \xB7 ${res.steps} steps`);
12561
12742
  const costUsd = o.costOf(main2, briefUsage) + o.costOf(o.models.premium, res.usage);
12562
12743
  return { ...res, text: answer, messages: agent.transcript, usage: addUsage(briefUsage, res.usage), costUsd, routing: d };
12563
12744
  }
@@ -12573,7 +12754,7 @@ The request:
12573
12754
  ${text}`);
12574
12755
  return { text: r.text.trim() || text, usage: r.usage };
12575
12756
  } catch (e) {
12576
- log24.warn(`brief failed, sending the raw request: ${e instanceof Error ? e.message : String(e)}`);
12757
+ log25.warn(`brief failed, sending the raw request: ${e instanceof Error ? e.message : String(e)}`);
12577
12758
  return { text };
12578
12759
  } finally {
12579
12760
  agent.transcript = snap;
@@ -12618,7 +12799,7 @@ async function jevAvailable(opts = {}) {
12618
12799
  const { env } = await loadJev(opts);
12619
12800
  return JEV_KEYS.some((k) => env[k]?.trim());
12620
12801
  } catch (e) {
12621
- log24.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
12802
+ log25.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
12622
12803
  return false;
12623
12804
  }
12624
12805
  }
@@ -12845,7 +13026,7 @@ init_models();
12845
13026
 
12846
13027
  // src/voice/progress.ts
12847
13028
  init_logging();
12848
- var log25 = forComponent("Progress");
13029
+ var log26 = forComponent("Progress");
12849
13030
  var HE_RESULT_CLAIM = /(?:^|\s)(?:מצאתי|סיימתי|בוצע|הנה|אין)(?=\s|$|[.,!…])/u;
12850
13031
  var TOPICS = [
12851
13032
  [/toolsearch|^mcp__.*__(list_commands|call_info)$/, null],
@@ -12990,7 +13171,7 @@ var Progress = class {
12990
13171
  }, this.o.generateBudgetMs);
12991
13172
  });
12992
13173
  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}`);
13174
+ log26.warn(`progress line failed: ${e?.message ?? e}`);
12994
13175
  return null;
12995
13176
  }), late]).finally(() => clearTimeout(t));
12996
13177
  const l = cleanLine(raw ?? "", lang, 5);
@@ -13012,7 +13193,7 @@ init_logging();
13012
13193
 
13013
13194
  // src/voice/backchannelPlan.ts
13014
13195
  init_logging();
13015
- var log26 = forComponent("BackchannelPlan");
13196
+ var log27 = forComponent("BackchannelPlan");
13016
13197
  var TURN_TYPES = ["story", "list", "complaint", "explanation", "short_command", "question", "other"];
13017
13198
  var DENSITIES = ["none", "low", "normal", "high"];
13018
13199
  var silentTurn = (p) => p.density === "none" || p.turn === "short_command" || p.turn === "question";
@@ -13077,13 +13258,13 @@ Reply JSON only: {"turn":"...","density":"...","cues":[{"when":["phrase",...],"l
13077
13258
  const reasoning = /gpt-oss/.test(o.model) ? { providerOptions: { reasoning_effort: "low" } } : {};
13078
13259
  const r = await this.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false, signal, maxTokens: o.maxTokens, ...reasoning });
13079
13260
  const p = parsePlan(r.content ?? "", c.lang);
13080
- if (!p) log26.debug(`unusable plan: ${(r.content ?? "").slice(0, 120)}`);
13261
+ if (!p) log27.debug(`unusable plan: ${(r.content ?? "").slice(0, 120)}`);
13081
13262
  return p;
13082
13263
  }
13083
13264
  };
13084
13265
 
13085
13266
  // src/voice/secondChannel.ts
13086
- var log27 = forComponent("SecondChannel");
13267
+ var log28 = forComponent("SecondChannel");
13087
13268
  var CHANNEL_MODES = {
13088
13269
  turn: { kinds: ["bridge"], bridgeDelayMs: 1500 },
13089
13270
  normal: {},
@@ -13205,15 +13386,15 @@ function withBudget(p, ms, what, ac) {
13205
13386
  let t;
13206
13387
  const late = new Promise((r) => {
13207
13388
  t = setTimeout(() => {
13208
- log27.debug(`${what}: over the ${ms}ms budget \u2014 dropped`);
13389
+ log28.debug(`${what}: over the ${ms}ms budget \u2014 dropped`);
13209
13390
  ac?.abort();
13210
13391
  r(null);
13211
13392
  }, ms);
13212
13393
  });
13213
13394
  return Promise.race([p.catch((e) => {
13214
13395
  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}`);
13396
+ if (e?.name === "AbortError" || /aborted/i.test(msg)) log28.debug(`${what}: aborted after its budget`);
13397
+ else log28.warn(`${what} failed \u2014 falling back: ${msg}`);
13217
13398
  return null;
13218
13399
  }), late]).finally(() => clearTimeout(t));
13219
13400
  }
@@ -13292,7 +13473,7 @@ var SecondChannel = class {
13292
13473
  if (host) this.pinned = true;
13293
13474
  const lang = primaryLang(tag);
13294
13475
  if (!lang || lang === this.lang) return;
13295
- log27.debug(`language ${this.lang} \u2192 ${lang}`);
13476
+ log28.debug(`language ${this.lang} \u2192 ${lang}`);
13296
13477
  this.lang = lang;
13297
13478
  this.recent = [];
13298
13479
  this.picks.clear();
@@ -13437,7 +13618,7 @@ var SecondChannel = class {
13437
13618
  try {
13438
13619
  this.o.cancel();
13439
13620
  } catch (e) {
13440
- log27.warn(`cancel threw: ${e.message}`);
13621
+ log28.warn(`cancel threw: ${e.message}`);
13441
13622
  }
13442
13623
  }
13443
13624
  }
@@ -13549,7 +13730,7 @@ var SecondChannel = class {
13549
13730
  const opts = this.options;
13550
13731
  if (opts.mode === "chatty" && !opts.aec && !this.degradedLogged) {
13551
13732
  this.degradedLogged = true;
13552
- log27.info("chatty mode without echo cancellation \u2014 normal timing (our lines would re-trigger the mic)");
13733
+ log28.info("chatty mode without echo cancellation \u2014 normal timing (our lines would re-trigger the mic)");
13553
13734
  }
13554
13735
  const rule = this.rule(trigger, pauseMs, breath, cue);
13555
13736
  if (rule === "silence") {
@@ -13568,7 +13749,7 @@ var SecondChannel = class {
13568
13749
  const d = await withBudget(this.o.decide(this.tail(), { ...this.state(pauseMs), rule }, ac.signal), this.o.decisionBudgetMs, "channel decision", ac);
13569
13750
  choice = d?.choice;
13570
13751
  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`);
13752
+ else if (choice && choice in CHANNEL_ACTIONS) log28.debug(`decider chose ${choice} for a ${rule} moment \u2014 the rule stands`);
13572
13753
  }
13573
13754
  if (kind === "silence") return ev({ choice, acted: "silence" });
13574
13755
  if (stale()) return ev({ choice, kind, acted: "stale" });
@@ -13606,7 +13787,7 @@ var SecondChannel = class {
13606
13787
  void withBudget(this.o.generate(ctx, ac.signal), Math.max(this.o.generateBudgetMs, this.o.bridgeDelayMs), "bridge generate", ac).then((raw) => {
13607
13788
  const text = cleanLine(raw ?? "", ctx.lang);
13608
13789
  if (text && gen === this.gen) this.bridgeDraft = { gen, text };
13609
- }).catch((e) => log27.debug(`bridge prefetch failed: ${e?.message ?? e}`));
13790
+ }).catch((e) => log28.debug(`bridge prefetch failed: ${e?.message ?? e}`));
13610
13791
  }
13611
13792
  async line(kind, react = true) {
13612
13793
  if (kind === "bridge" && this.o.instantBridge) {
@@ -13622,7 +13803,7 @@ var SecondChannel = class {
13622
13803
  if (raw === null) ac.abort();
13623
13804
  const text = cleanLine(raw ?? "", this.lang);
13624
13805
  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`);
13806
+ if (text) log28.debug(`generated "${text}" repeats a recent line \u2014 canned`);
13626
13807
  }
13627
13808
  return this.pick(kind);
13628
13809
  }
@@ -13644,7 +13825,7 @@ var SecondChannel = class {
13644
13825
  try {
13645
13826
  r = this.o.speak(text, kind);
13646
13827
  } catch (e) {
13647
- log27.warn(`speak threw \u2014 line dropped: ${e.message}`);
13828
+ log28.warn(`speak threw \u2014 line dropped: ${e.message}`);
13648
13829
  return false;
13649
13830
  }
13650
13831
  if (r === false) return false;
@@ -13657,7 +13838,7 @@ var SecondChannel = class {
13657
13838
  this.o.show(text, kind);
13658
13839
  const p = Promise.resolve(r).then(() => {
13659
13840
  }, (e) => {
13660
- log27.warn(`channel line playback failed: ${e?.message ?? e}`);
13841
+ log28.warn(`channel line playback failed: ${e?.message ?? e}`);
13661
13842
  }).finally(() => {
13662
13843
  if (this.inflight === p) this.inflight = null;
13663
13844
  });
@@ -13715,7 +13896,7 @@ function closedQuestion(reply) {
13715
13896
  }
13716
13897
 
13717
13898
  // src/voice/call.ts
13718
- var log28 = forComponent("VoiceCall");
13899
+ var log29 = forComponent("VoiceCall");
13719
13900
  var VoiceCallOptions = class {
13720
13901
  /** Host runs MAIN with exactly `text` (a replace: roll the `replaces` turn back first). */
13721
13902
  sendTurn = () => {
@@ -13941,7 +14122,7 @@ var VoiceCall = class {
13941
14122
  try {
13942
14123
  this.o.cancel();
13943
14124
  } catch (e) {
13944
- log28.warn(`cancel threw: ${e.message}`);
14125
+ log29.warn(`cancel threw: ${e.message}`);
13945
14126
  }
13946
14127
  if (this.cur?.running) {
13947
14128
  this.cur.running = false;
@@ -14028,7 +14209,7 @@ var VoiceCall = class {
14028
14209
  try {
14029
14210
  r = this.o.speak(text, kind);
14030
14211
  } catch (e) {
14031
- log28.warn(`speak threw: ${e.message}`);
14212
+ log29.warn(`speak threw: ${e.message}`);
14032
14213
  return false;
14033
14214
  }
14034
14215
  if (r === false) return false;
@@ -14114,7 +14295,7 @@ init_logging();
14114
14295
  init_platform();
14115
14296
  import { spawn } from "child_process";
14116
14297
  import { createHash } from "crypto";
14117
- var log29 = forComponent("mcp");
14298
+ var log30 = forComponent("mcp");
14118
14299
  var PROTOCOL_VERSION = "2025-06-18";
14119
14300
  var DEFAULT_TIMEOUT_MS = 3e4;
14120
14301
  var DEFAULT_CALL_TIMEOUT_MS = 30 * 6e4;
@@ -14146,7 +14327,7 @@ var StdioTransport = class {
14146
14327
  proc.stdout.setEncoding("utf8");
14147
14328
  proc.stdout.on("data", (chunk) => this.onData(chunk));
14148
14329
  proc.stderr.setEncoding("utf8");
14149
- proc.stderr.on("data", (chunk) => log29.debug(`[${command}] stderr:`, chunk.trimEnd()));
14330
+ proc.stderr.on("data", (chunk) => log30.debug(`[${command}] stderr:`, chunk.trimEnd()));
14150
14331
  proc.on("exit", (code) => this.failAll(new Error(`MCP server "${command}" exited (code ${code})`)));
14151
14332
  proc.on("error", (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));
14152
14333
  }
@@ -14160,7 +14341,7 @@ var StdioTransport = class {
14160
14341
  try {
14161
14342
  this.dispatch(JSON.parse(line));
14162
14343
  } catch (e) {
14163
- log29.debug("dropping non-JSON line from MCP server:", line, e);
14344
+ log30.debug("dropping non-JSON line from MCP server:", line, e);
14164
14345
  }
14165
14346
  }
14166
14347
  }
@@ -14170,7 +14351,7 @@ var StdioTransport = class {
14170
14351
  try {
14171
14352
  p2?.onProgress?.(msg.params ?? {});
14172
14353
  } catch (e) {
14173
- log29.debug("onProgress handler threw", e);
14354
+ log30.debug("onProgress handler threw", e);
14174
14355
  }
14175
14356
  return;
14176
14357
  }
@@ -14208,7 +14389,7 @@ var StdioTransport = class {
14208
14389
  const onAbort = () => {
14209
14390
  if (!this.pending.delete(id)) return;
14210
14391
  clearTimeout(timer);
14211
- this.notify("notifications/cancelled", { requestId: id, reason: String(signal?.reason?.message ?? "cancelled") }).catch((e) => log29.debug("cancel notify failed", e));
14392
+ this.notify("notifications/cancelled", { requestId: id, reason: String(signal?.reason?.message ?? "cancelled") }).catch((e) => log30.debug("cancel notify failed", e));
14212
14393
  reject(abortError(signal));
14213
14394
  };
14214
14395
  const done = () => signal?.removeEventListener("abort", onAbort);
@@ -14232,7 +14413,7 @@ var StdioTransport = class {
14232
14413
  try {
14233
14414
  this.proc?.stdin?.end();
14234
14415
  } catch (e) {
14235
- log29.debug("stdin end failed", e);
14416
+ log30.debug("stdin end failed", e);
14236
14417
  }
14237
14418
  if (!(IS_WIN && killTree(this.proc?.pid, "SIGTERM"))) this.proc?.kill();
14238
14419
  }
@@ -14267,7 +14448,7 @@ var HttpTransport = class {
14267
14448
  return await this.post({ jsonrpc: "2.0", id, method, params: opts.onProgress ? withProgressToken(params, id) : params }, false, signal, opts.onProgress);
14268
14449
  } catch (e) {
14269
14450
  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));
14451
+ void this.notify("notifications/cancelled", { requestId: id, reason: String(opts.signal.reason?.message ?? "cancelled") }).catch((err2) => log30.debug("cancel notify failed", err2));
14271
14452
  throw abortError(opts.signal);
14272
14453
  }
14273
14454
  throw e;
@@ -14310,7 +14491,7 @@ function sseMessage(line) {
14310
14491
  try {
14311
14492
  return JSON.parse(trimmed.slice(5).trim());
14312
14493
  } catch (e) {
14313
- log29.debug("skipping unparseable SSE data line", e);
14494
+ log30.debug("skipping unparseable SSE data line", e);
14314
14495
  return void 0;
14315
14496
  }
14316
14497
  }
@@ -14320,7 +14501,7 @@ async function readSseResponse(resp, id, onProgress) {
14320
14501
  try {
14321
14502
  onProgress?.(obj.params ?? {});
14322
14503
  } catch (e) {
14323
- log29.debug("onProgress handler threw", e);
14504
+ log30.debug("onProgress handler threw", e);
14324
14505
  }
14325
14506
  return void 0;
14326
14507
  }
@@ -14419,7 +14600,7 @@ async function mountWithDeadline(name, cfg, mountTimeoutMs) {
14419
14600
  return { name, client, tools, specs, serverInfo: init?.serverInfo, config: cfg };
14420
14601
  })(), mountTimeoutMs, name);
14421
14602
  } catch (e) {
14422
- await client.close().catch((err2) => log29.debug(`close after failed mount of "${name}": ${err2}`));
14603
+ await client.close().catch((err2) => log30.debug(`close after failed mount of "${name}": ${err2}`));
14423
14604
  throw e;
14424
14605
  }
14425
14606
  }
@@ -14430,15 +14611,15 @@ function validEntries(servers) {
14430
14611
  return Object.entries(servers).filter(([name, cfg]) => {
14431
14612
  if (!cfg || cfg.disabled) return false;
14432
14613
  if (!cfg.command && !cfg.url) {
14433
- log29.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
14614
+ log30.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
14434
14615
  return false;
14435
14616
  }
14436
14617
  return true;
14437
14618
  });
14438
14619
  }
14439
14620
  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}`);
14621
+ if (e instanceof McpAuthError) log30.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
14622
+ else log30.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
14442
14623
  }
14443
14624
  async function mountMcpServers(servers = {}, opts = {}) {
14444
14625
  const entries = validEntries(servers);
@@ -14448,7 +14629,7 @@ async function mountMcpServers(servers = {}, opts = {}) {
14448
14629
  const name = entries[i][0];
14449
14630
  if (r.status === "fulfilled") {
14450
14631
  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}` : ""}`);
14632
+ log30.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
14452
14633
  } else logMountFailure(name, r.reason);
14453
14634
  });
14454
14635
  return out;
@@ -14488,7 +14669,7 @@ var McpPool = class {
14488
14669
  const prev = this.warm.get(key);
14489
14670
  if (prev) {
14490
14671
  clearTimeout(prev.timer);
14491
- if (prev.client !== client) void prev.client.close().catch((err2) => log29.debug(`warm-pool replace close failed: ${err2}`));
14672
+ if (prev.client !== client) void prev.client.close().catch((err2) => log30.debug(`warm-pool replace close failed: ${err2}`));
14492
14673
  }
14493
14674
  const e = { client, timer: void 0 };
14494
14675
  this.warm.set(key, e);
@@ -14505,7 +14686,7 @@ var McpPool = class {
14505
14686
  const e = this.warm.get(key);
14506
14687
  if (!e) return;
14507
14688
  this.warm.delete(key);
14508
- await e.client.close().catch((err2) => log29.debug(`warm-pool evict close failed: ${err2}`));
14689
+ await e.client.close().catch((err2) => log30.debug(`warm-pool evict close failed: ${err2}`));
14509
14690
  }
14510
14691
  async closeAll() {
14511
14692
  for (const e of this.warm.values()) {
@@ -14724,7 +14905,7 @@ init_tools_shell();
14724
14905
  // src/tools.notify.ts
14725
14906
  init_logging();
14726
14907
  import { execFile as execFile2 } from "child_process";
14727
- var log31 = forComponent("notify");
14908
+ var log32 = forComponent("notify");
14728
14909
  var WIN_TOAST = [
14729
14910
  "[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null",
14730
14911
  "$t = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)",
@@ -14755,7 +14936,7 @@ function makeNotifyTool(opts = {}) {
14755
14936
  return new Promise((resolve6) => {
14756
14937
  run(argv[0], argv[1], { timeout: 5e3, ...extra }, (e) => {
14757
14938
  if (e) {
14758
- log31.debug("notification failed", e);
14939
+ log32.debug("notification failed", e);
14759
14940
  resolve6(`Notification failed: ${e.message}`);
14760
14941
  } else resolve6("Notification shown.");
14761
14942
  });
@@ -14771,7 +14952,7 @@ import { BodDB as BodDB2 } from "@bod.ee/db";
14771
14952
  init_logging();
14772
14953
  import { existsSync as existsSync2, readFileSync as readFileSync5 } from "fs";
14773
14954
  import { homedir } from "os";
14774
- var log32 = forComponent("cli-util");
14955
+ var log33 = forComponent("cli-util");
14775
14956
  function dotDirs(base, sub, opts = {}) {
14776
14957
  const home = opts.home ?? homedir();
14777
14958
  const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
@@ -14788,7 +14969,7 @@ function parseJson(text, fallback, what = "json") {
14788
14969
  try {
14789
14970
  return JSON.parse(text);
14790
14971
  } catch (e) {
14791
- log32.debug(`parseJson(${what}) failed: ${e.message}`);
14972
+ log33.debug(`parseJson(${what}) failed: ${e.message}`);
14792
14973
  return fallback;
14793
14974
  }
14794
14975
  }
@@ -14798,7 +14979,7 @@ function readJsonFile(path, fallback) {
14798
14979
  try {
14799
14980
  text = readFileSync5(path, "utf8");
14800
14981
  } catch (e) {
14801
- log32.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
14982
+ log33.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
14802
14983
  return fallback;
14803
14984
  }
14804
14985
  return parseJson(text, fallback, path);
@@ -15102,7 +15283,7 @@ import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as rea
15102
15283
  import { homedir as homedir3 } from "os";
15103
15284
  import { dirname as dirname4, join as join8 } from "path";
15104
15285
  import { fileURLToPath } from "url";
15105
- var log33 = forComponent("VoiceIO");
15286
+ var log34 = forComponent("VoiceIO");
15106
15287
  var now3 = () => performance.now();
15107
15288
  var Player = class {
15108
15289
  proc = null;
@@ -15116,7 +15297,7 @@ var Player = class {
15116
15297
  ["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
15117
15298
  { stdio: ["pipe", "ignore", "ignore"] }
15118
15299
  );
15119
- this.proc.on("error", (e) => log33.warn(`ffplay error: ${e.message}`));
15300
+ this.proc.on("error", (e) => log34.warn(`ffplay error: ${e.message}`));
15120
15301
  this.proc.stdin.on("error", () => {
15121
15302
  });
15122
15303
  this.bytesWritten = 0;
@@ -15154,7 +15335,7 @@ function detectFfmpegMic() {
15154
15335
  const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
15155
15336
  const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
15156
15337
  if (!mic) throw new Error("no audio input device found");
15157
- log33.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
15338
+ log34.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
15158
15339
  return `:${mic.idx}`;
15159
15340
  }
15160
15341
  function detectDshowMic() {
@@ -15165,7 +15346,7 @@ function detectDshowMic() {
15165
15346
  const devices = [...(r.stderr ?? "").matchAll(/"([^"]+)"\s*\(audio\)/g)].map((m) => m[1]);
15166
15347
  const mic = devices.find((d) => !/stereo mix|loopback|virtual|cable|teams/i.test(d)) ?? devices[0];
15167
15348
  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}`);
15349
+ log34.debug(`ffmpeg dshow mic: ${mic}`);
15169
15350
  return `audio=${mic}`;
15170
15351
  }
15171
15352
  function detectedInputDevice() {
@@ -15201,15 +15382,15 @@ function resolveAecBinary() {
15201
15382
  if (existsSync4(bin) && statSync6(bin).mtimeMs >= statSync6(src).mtimeMs) return bin;
15202
15383
  if (spawnSync3("which", ["swiftc"]).status !== 0) return null;
15203
15384
  mkdirSync5(cacheDir, { recursive: true });
15204
- log33.info("compiling AEC mic helper (first run)\u2026");
15385
+ log34.info("compiling AEC mic helper (first run)\u2026");
15205
15386
  const build = spawnSync3("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
15206
15387
  if (build.status !== 0) {
15207
- log33.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
15388
+ log34.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
15208
15389
  return null;
15209
15390
  }
15210
15391
  const sign = spawnSync3("codesign", ["-fs", "-", bin], { encoding: "utf8" });
15211
15392
  if (sign.status !== 0) {
15212
- log33.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
15393
+ log34.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
15213
15394
  return null;
15214
15395
  }
15215
15396
  return bin;
@@ -15251,17 +15432,17 @@ var NodeMicSource = class {
15251
15432
  this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
15252
15433
  } else {
15253
15434
  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");
15435
+ log34.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
15255
15436
  const input = process.platform === "win32" ? ["-f", "dshow", "-i", detectDshowMic()] : ["-f", "avfoundation", "-i", detectFfmpegMic()];
15256
15437
  this.proc = spawn2(
15257
15438
  "ffmpeg",
15258
15439
  ["-loglevel", "error", ...input, "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
15259
15440
  { stdio: ["ignore", "pipe", "pipe"], ...process.platform === "win32" ? { windowsHide: true } : {} }
15260
15441
  );
15261
- this.proc.stderr.on("data", (d) => log33.warn(`ffmpeg: ${String(d).trim()}`));
15442
+ this.proc.stderr.on("data", (d) => log34.warn(`ffmpeg: ${String(d).trim()}`));
15262
15443
  }
15263
15444
  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`);
15445
+ if (c && !this.stopped) log34.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
15265
15446
  });
15266
15447
  this.proc.stdout.on("data", (chunk) => onChunk(chunk));
15267
15448
  }
@@ -15327,7 +15508,7 @@ var AecDuplexAudio = class {
15327
15508
  this.proc.stdin.on("error", () => {
15328
15509
  });
15329
15510
  this.proc.on("exit", (c) => {
15330
- if (c && !this.stopped) log33.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
15511
+ if (c && !this.stopped) log34.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
15331
15512
  });
15332
15513
  this.proc.stdout.on("data", (chunk) => {
15333
15514
  this.gotChunk = true;
@@ -15343,7 +15524,7 @@ var AecDuplexAudio = class {
15343
15524
  openMicSettings();
15344
15525
  this.onFatal?.("microphone permission denied \u2014 enable it in System Settings \u2192 Privacy & Security \u2192 Microphone for your terminal, then restart it");
15345
15526
  }
15346
- } else log33.debug(`mic-aec: ${s}`);
15527
+ } else log34.debug(`mic-aec: ${s}`);
15347
15528
  }
15348
15529
  });
15349
15530
  if (!this.noVpio && !this.triedFallback) {
@@ -15352,7 +15533,7 @@ var AecDuplexAudio = class {
15352
15533
  this.triedFallback = true;
15353
15534
  this.noVpio = true;
15354
15535
  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)");
15536
+ log34.warn("mic-aec: VPIO delivered no audio in 2.5s \u2014 falling back to non-VPIO capture (no AEC \u2192 half-duplex, no barge-in)");
15356
15537
  this.onDegrade?.();
15357
15538
  this.killProc();
15358
15539
  this.spawnHelper();
@@ -15523,7 +15704,7 @@ function bindAckInstinct(v, instinct) {
15523
15704
  if (ev.kind === "ack_fired") instinct.ackOutcome(String(ev.phrase));
15524
15705
  else if (ev.kind === "ack_skipped" || ev.kind === "ack_cancelled") instinct.ackOutcome(null);
15525
15706
  } catch (e) {
15526
- log33.warn(`ack outcome tap failed: ${e instanceof Error ? e.message : e}`);
15707
+ log34.warn(`ack outcome tap failed: ${e instanceof Error ? e.message : e}`);
15527
15708
  }
15528
15709
  onDiag(ev);
15529
15710
  };
@@ -15645,7 +15826,7 @@ function speakBlip(v, text) {
15645
15826
  v.fireBackchannel();
15646
15827
  return true;
15647
15828
  } catch (e) {
15648
- log33.warn(`backchannel blip failed \u2014 line dropped: ${e.message}`);
15829
+ log34.warn(`backchannel blip failed \u2014 line dropped: ${e.message}`);
15649
15830
  return false;
15650
15831
  } finally {
15651
15832
  E.BACKCHANNELS = pool;
@@ -15664,7 +15845,7 @@ function channelInput(sc, m) {
15664
15845
  }
15665
15846
  if (m.type === "agent_state") {
15666
15847
  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`);
15848
+ else log34.warn(`agent_state: unknown state ${JSON.stringify(m.state)} \u2014 ignored`);
15668
15849
  return true;
15669
15850
  }
15670
15851
  return false;
@@ -15790,7 +15971,7 @@ function fakeVoiceParts(uttFile) {
15790
15971
  init_logging();
15791
15972
  import { appendFile, mkdirSync as mkdirSync6 } from "fs";
15792
15973
  import { dirname as dirname5 } from "path";
15793
- var log34 = forComponent("VoiceDiag");
15974
+ var log35 = forComponent("VoiceDiag");
15794
15975
  var JsonlDiagSink = class {
15795
15976
  constructor(path) {
15796
15977
  this.path = path;
@@ -15831,7 +16012,7 @@ var JsonlDiagSink = class {
15831
16012
  disable(e) {
15832
16013
  this.disabled = true;
15833
16014
  this.queue.length = 0;
15834
- log34.debug(`voice diag sink disabled (${this.path}): ${e instanceof Error ? e.message : e}`);
16015
+ log35.debug(`voice diag sink disabled (${this.path}): ${e instanceof Error ? e.message : e}`);
15835
16016
  }
15836
16017
  /** True once a failure permanently disabled the sink (tests/inspection). */
15837
16018
  get isDisabled() {
@@ -15989,7 +16170,7 @@ function applyOverlay(cfg, file) {
15989
16170
  // cli/hooks-config.ts
15990
16171
  import { spawnSync as spawnSync4 } from "child_process";
15991
16172
  init_platform();
15992
- var log35 = forComponent("hooks");
16173
+ var log36 = forComponent("hooks");
15993
16174
  var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
15994
16175
  function ruleMatches(rule, toolName) {
15995
16176
  if (!rule.tool || rule.tool === "*") return true;
@@ -16008,7 +16189,7 @@ function runCmd(rule, env) {
16008
16189
  });
16009
16190
  return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
16010
16191
  } catch (e) {
16011
- log35.debug(`hook command failed: ${rule.command}`, e);
16192
+ log36.debug(`hook command failed: ${rule.command}`, e);
16012
16193
  return { code: 1, out: String(e?.message ?? e) };
16013
16194
  }
16014
16195
  }
@@ -16116,7 +16297,7 @@ import { existsSync as existsSync6, mkdirSync as mkdirSync8, readFileSync as rea
16116
16297
  import { homedir as homedir6 } from "os";
16117
16298
  import { join as join11 } from "path";
16118
16299
  init_platform();
16119
- var log36 = forComponent("session");
16300
+ var log37 = forComponent("session");
16120
16301
  var globalDir = () => join11(homedir6(), ".agent", "sessions");
16121
16302
  function indexLink(target, link2) {
16122
16303
  try {
@@ -16179,7 +16360,7 @@ var SessionStore = class {
16179
16360
  }
16180
16361
  load(id) {
16181
16362
  if (!this.safeId(id)) {
16182
- log36.debug(`rejecting unsafe session id: ${id}`);
16363
+ log37.debug(`rejecting unsafe session id: ${id}`);
16183
16364
  return void 0;
16184
16365
  }
16185
16366
  const path = join11(this.dir, `${id}.json`);
@@ -16187,7 +16368,7 @@ var SessionStore = class {
16187
16368
  try {
16188
16369
  return JSON.parse(readFileSync8(path, "utf8"));
16189
16370
  } catch (e) {
16190
- log36.debug(`unreadable session ${id} \u2014 ignoring`, e);
16371
+ log37.debug(`unreadable session ${id} \u2014 ignoring`, e);
16191
16372
  return void 0;
16192
16373
  }
16193
16374
  }
@@ -16200,7 +16381,7 @@ var SessionStore = class {
16200
16381
  try {
16201
16382
  metas.push(JSON.parse(readFileSync8(join11(this.dir, f), "utf8")).meta);
16202
16383
  } catch (e) {
16203
- log36.debug(`skipping unreadable session file ${f}`, e);
16384
+ log37.debug(`skipping unreadable session file ${f}`, e);
16204
16385
  }
16205
16386
  }
16206
16387
  return metas.sort((a, b) => b.updated - a.updated);
@@ -16336,6 +16517,199 @@ ${body || "(no conversation)"}
16336
16517
  (That is everything recorded. Where it does not say why something was done or what was meant, say you do not know \u2014 never supply a reason.)`;
16337
16518
  }
16338
16519
 
16520
+ // src/directives.ts
16521
+ init_models();
16522
+ var VENDOR_SHORTHAND = { cc: "claude-code", cur: "cursor", oa: "openai" };
16523
+ var DIRECTIVE_KEYS = ["model", "turns", "thinking", "think", "effort", "engine", "resume", "delegate", "advisor", "mode"];
16524
+ function splitModePair(token) {
16525
+ const i = token.search(/->|<-/);
16526
+ if (i === -1) return null;
16527
+ const right = token.slice(i + 2).trim();
16528
+ return right ? { left: token.slice(0, i).trim(), op: token.slice(i, i + 2), right } : null;
16529
+ }
16530
+ var isModel = (t, p) => {
16531
+ const h = resolveModelToken(t, p);
16532
+ return !!h && "model" in h;
16533
+ };
16534
+ var defaultResolver = null;
16535
+ var opt = (o) => ({
16536
+ resolveModel: defaultResolver,
16537
+ resolverFirst: false,
16538
+ aliases: MODEL_ALIASES,
16539
+ vendors: VENDOR_SHORTHAND,
16540
+ keys: DIRECTIVE_KEYS,
16541
+ strict: false,
16542
+ catalogOnly: false,
16543
+ warn: (m) => console.warn(m),
16544
+ ...o
16545
+ });
16546
+ var DIRECTIVE_RE = /^\s*\[([^\]]*)\](?=\s|$)\s*([\s\S]*)/;
16547
+ var isQualifiedModel = (v) => /^[a-z0-9._-]+\/[a-z0-9./_-]+$/.test(v);
16548
+ function expandVendor(token, vendors = VENDOR_SHORTHAND) {
16549
+ const v = token.trim().toLowerCase();
16550
+ const m = v.match(/^([a-z0-9]+)[-/](.+)$/);
16551
+ if (m && vendors[m[1]]) return `${vendors[m[1]]}/${m[2]}`;
16552
+ const slash = v.indexOf("/");
16553
+ return slash > 0 && Object.values(vendors).includes(v.slice(0, slash)) ? v : null;
16554
+ }
16555
+ function resolveModelToken(token, o) {
16556
+ const p = opt(o);
16557
+ const v = token.trim().toLowerCase();
16558
+ const vendored = expandVendor(v, p.vendors);
16559
+ if (vendored) return p.resolveModel ? p.resolveModel(vendored) : p.catalogOnly ? null : { model: vendored };
16560
+ if (p.catalogOnly) return v.includes("/") || p.aliases[v] || /\d/.test(v) ? p.resolveModel?.(v) ?? null : null;
16561
+ const alias = p.aliases[v] ? { model: p.aliases[v] } : null;
16562
+ if (!p.resolverFirst) return alias ?? p.resolveModel?.(v) ?? null;
16563
+ const hit = p.resolveModel?.(v) ?? null;
16564
+ return hit && "model" in hit ? hit : alias ?? hit;
16565
+ }
16566
+ var THINK = ["think", "adaptive", "nothink", "nothinking"];
16567
+ function isDirectiveGroup(raw, o) {
16568
+ const p = opt(o);
16569
+ const tokens = raw.split(",").map((t) => t.trim()).filter(Boolean);
16570
+ if (!tokens.length) return false;
16571
+ return tokens.every((t) => {
16572
+ const i = t.indexOf(":");
16573
+ if (i !== -1) return p.keys.includes(t.slice(0, i).trim().toLowerCase());
16574
+ const v = t.toLowerCase();
16575
+ const pair = splitModePair(v);
16576
+ if (pair) return p.keys.includes(pair.op === "->" ? "delegate" : "advisor") && (!!pair.left && isModel(pair.left, p) || isModel(pair.right, p));
16577
+ if (v === "off") return p.keys.includes("mode");
16578
+ return !!resolveModelToken(v, p) || p.catalogOnly && !!p.aliases[v] || p.keys.includes("turns") && /^\d+$/.test(v) || p.keys.includes("thinking") && THINK.includes(v);
16579
+ });
16580
+ }
16581
+ function splitDirectiveGroups(text, o) {
16582
+ const p = opt(o);
16583
+ let rest = text;
16584
+ const groups = [];
16585
+ for (; ; ) {
16586
+ const m = rest.match(DIRECTIVE_RE);
16587
+ if (!m) break;
16588
+ const g = m[1].trim();
16589
+ if ((groups.length || p.strict) && g && !isDirectiveGroup(g, p)) break;
16590
+ groups.push(g);
16591
+ rest = m[2];
16592
+ }
16593
+ return { groups, body: rest };
16594
+ }
16595
+ function parseDirectives(text, o) {
16596
+ const p = opt(o);
16597
+ const { groups, body } = splitDirectiveGroups(text, p);
16598
+ if (!groups.length) return { prompt: text, directives: {} };
16599
+ const prompt = body.trim();
16600
+ const raw = groups.filter(Boolean).join(",");
16601
+ if (!raw) return { prompt, directives: {} };
16602
+ const d = {};
16603
+ const out = { prompt, directives: d };
16604
+ const tokens = raw.split(",");
16605
+ const proven = tokens.some((t) => {
16606
+ const i = t.indexOf(":");
16607
+ return i !== -1 && p.keys.includes(t.slice(0, i).trim().toLowerCase());
16608
+ });
16609
+ const fail = (tok, hit) => {
16610
+ out.unresolvedModel ??= tok;
16611
+ if (hit && "ambiguous" in hit) out.ambiguous ??= hit.ambiguous;
16612
+ };
16613
+ const setModel = (val) => {
16614
+ const hit = resolveModelToken(val, p);
16615
+ if (!hit || !("model" in hit)) return hit;
16616
+ d.model = hit.model;
16617
+ if (hit.engine && !d.engine) d.engine = hit.engine;
16618
+ return true;
16619
+ };
16620
+ let pos = 0;
16621
+ for (const token of tokens) {
16622
+ const t = token.trim();
16623
+ if (!t) continue;
16624
+ const i = t.indexOf(":");
16625
+ if (i !== -1) {
16626
+ const key = t.slice(0, i).trim().toLowerCase(), val2 = t.slice(i + 1).trim().toLowerCase();
16627
+ if (!p.keys.includes(key)) {
16628
+ p.warn(`[directives] Unknown directive key: "${key}"`);
16629
+ continue;
16630
+ }
16631
+ if (key === "delegate" || key === "advisor") {
16632
+ const hit = resolveModelToken(val2, p);
16633
+ if (hit && "model" in hit) d[key] = hit.model;
16634
+ else if (!hit && isQualifiedModel(val2) && !p.catalogOnly) d[key] = val2;
16635
+ else fail(val2, hit);
16636
+ continue;
16637
+ }
16638
+ if (key === "model") {
16639
+ const r2 = setModel(val2);
16640
+ if (r2 === true) continue;
16641
+ if (!r2 && isQualifiedModel(val2) && !p.catalogOnly) {
16642
+ d.model = val2;
16643
+ continue;
16644
+ }
16645
+ fail(val2, r2);
16646
+ } else applyKey(d, key, val2, p.warn);
16647
+ continue;
16648
+ }
16649
+ const val = t.toLowerCase();
16650
+ const pair = splitModePair(val);
16651
+ if (pair && p.keys.includes(pair.op === "->" ? "delegate" : "advisor")) {
16652
+ const lhit = pair.left ? resolveModelToken(pair.left, p) : null, rhit = resolveModelToken(pair.right, p);
16653
+ const lok = !pair.left || !!lhit && "model" in lhit, rok = !!rhit && "model" in rhit;
16654
+ if (lok && rok) {
16655
+ if (pair.left) setModel(pair.left);
16656
+ if (pair.op === "->") d.delegate = rhit.model;
16657
+ else d.advisor = rhit.model;
16658
+ } else if (lok || rok) {
16659
+ if (!lok) fail(pair.left, lhit);
16660
+ else fail(pair.right, rhit);
16661
+ } else p.warn(`[directives] Unknown models in "${t}"`);
16662
+ pos++;
16663
+ continue;
16664
+ }
16665
+ if (val === "off" && p.keys.includes("mode")) {
16666
+ d.modeOff = true;
16667
+ pos++;
16668
+ continue;
16669
+ }
16670
+ const r = pos === 0 ? setModel(val) : null;
16671
+ if (r === true) {
16672
+ } else if (pos <= 1 && /^\d+$/.test(val) && p.keys.includes("turns")) d.turns = parseInt(val, 10);
16673
+ else if (["think", "adaptive"].includes(val) && p.keys.includes("thinking")) d.thinking = "adaptive";
16674
+ else if (["nothink", "nothinking"].includes(val) && p.keys.includes("thinking")) d.thinking = "disabled";
16675
+ else if (pos === 0) {
16676
+ if (r || p.catalogOnly && p.aliases[val] || proven && !isQualifiedModel(val)) fail(t, r);
16677
+ p.warn(`[directives] Unknown model: "${t}"`);
16678
+ }
16679
+ pos++;
16680
+ }
16681
+ return out;
16682
+ }
16683
+ function applyKey(d, key, val, warn) {
16684
+ switch (key) {
16685
+ case "turns":
16686
+ if (/^\d+$/.test(val)) d.turns = parseInt(val, 10);
16687
+ else warn(`[directives] Invalid turns value: "${val}"`);
16688
+ break;
16689
+ case "thinking":
16690
+ case "think":
16691
+ if (["adaptive", "enabled", "disabled"].includes(val)) d.thinking = val;
16692
+ else warn(`[directives] Invalid thinking value: "${val}"`);
16693
+ break;
16694
+ case "effort":
16695
+ if (["low", "medium", "high", "max"].includes(val)) d.effort = val;
16696
+ else warn(`[directives] Invalid effort value: "${val}"`);
16697
+ break;
16698
+ case "engine":
16699
+ d.engine = val;
16700
+ break;
16701
+ case "mode":
16702
+ if (val === "off") d.modeOff = true;
16703
+ else warn(`[directives] Invalid mode value: "${val}"`);
16704
+ break;
16705
+ case "resume":
16706
+ if (["on", "true", "1"].includes(val)) d.resume = true;
16707
+ else if (["off", "false", "0"].includes(val)) d.resume = false;
16708
+ else warn(`[directives] Invalid resume value: "${val}"`);
16709
+ break;
16710
+ }
16711
+ }
16712
+
16339
16713
  // cli/checkpoints.ts
16340
16714
  var MUTATORS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "ApplyEdits"]);
16341
16715
  function mutatedPaths(call) {
@@ -16439,7 +16813,7 @@ import { promisify } from "util";
16439
16813
  import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync9 } from "fs";
16440
16814
  import { homedir as homedir7 } from "os";
16441
16815
  import { join as join12, resolve as resolve4, sep as sep3, parse as parsePath } from "path";
16442
- var log37 = forComponent("checkpoints");
16816
+ var log38 = forComponent("checkpoints");
16443
16817
  var exec = promisify(execFile4);
16444
16818
  var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
16445
16819
  function isBroadRoot(p) {
@@ -16487,7 +16861,7 @@ var ShadowRepo = class {
16487
16861
  writeFileSync6(join12(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
16488
16862
  this.ready = true;
16489
16863
  } catch (e) {
16490
- log37.debug(`git checkpoints unavailable for ${this.workTree}`, e);
16864
+ log38.debug(`git checkpoints unavailable for ${this.workTree}`, e);
16491
16865
  this.ready = false;
16492
16866
  }
16493
16867
  return this.ready;
@@ -16498,7 +16872,7 @@ var ShadowRepo = class {
16498
16872
  }
16499
16873
  async commit(label, forced = []) {
16500
16874
  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));
16875
+ for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log38.debug(`force-add failed: ${p}`, e));
16502
16876
  await this.run("commit", "--allow-empty", "-q", "-m", label);
16503
16877
  }
16504
16878
  /** Inject the CURRENT (pre-edit) content of `paths` into the turn-open restore point by amending it.
@@ -16506,8 +16880,8 @@ var ShadowRepo = class {
16506
16880
  * turn-boundary `add -A` would never have captured it. Amend (vs a new commit) keeps one restore
16507
16881
  * point per turn, so the REPL's turn↔frame mapping stays intact. */
16508
16882
  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));
16883
+ for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log38.debug(`force-capture failed: ${p}`, e));
16884
+ await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log38.debug("amend failed", e));
16511
16885
  }
16512
16886
  /** Commits on `ref`, oldest-first (canonical index space). */
16513
16887
  async log(ref) {
@@ -16567,7 +16941,7 @@ var ShadowRepo = class {
16567
16941
  await this.run("gc", "--auto", "-q").catch(() => {
16568
16942
  });
16569
16943
  } catch (e) {
16570
- log37.debug("checkpoint prune failed", e);
16944
+ log38.debug("checkpoint prune failed", e);
16571
16945
  }
16572
16946
  }
16573
16947
  };
@@ -16635,7 +17009,7 @@ var GitCheckpoints = class {
16635
17009
  use(sessionId) {
16636
17010
  if (sessionId === this.session) return;
16637
17011
  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));
17012
+ if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log38.debug("re-point failed", e));
16639
17013
  }
16640
17014
  async begin(label) {
16641
17015
  if (!await this.start()) return;
@@ -16655,7 +17029,7 @@ var GitCheckpoints = class {
16655
17029
  process.stderr.write(`\x1B[2m checkpoints: '${this.repos[i].root}' too large to snapshot in ${Math.round(this.options.opTimeoutMs / 1e3)}s \u2014 file rewind disabled here\x1B[0m
16656
17030
  `);
16657
17031
  } else {
16658
- log37.debug("checkpoint commit failed", e);
17032
+ log38.debug("checkpoint commit failed", e);
16659
17033
  survivors.push(this.repos[i]);
16660
17034
  survivorCaches.push(this.caches[i] ?? []);
16661
17035
  }
@@ -16690,7 +17064,7 @@ var GitCheckpoints = class {
16690
17064
  if (this.forced.has(abs)) continue;
16691
17065
  this.forced.add(abs);
16692
17066
  const i = this.repoIndexFor(abs);
16693
- if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log37.debug("amendForced failed", e));
17067
+ if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log38.debug("amendForced failed", e));
16694
17068
  }
16695
17069
  }
16696
17070
  };
@@ -17801,7 +18175,7 @@ function applyVimNormal(s, key, str) {
17801
18175
  }
17802
18176
  return "none";
17803
18177
  }
17804
- function applyKey(s, key, str) {
18178
+ function applyKey2(s, key, str) {
17805
18179
  const k = key?.name;
17806
18180
  if (s.vim === "normal" && !s.pasting && !s.searching) {
17807
18181
  const r = applyVimNormal(s, key ?? {}, str);
@@ -18129,7 +18503,7 @@ function createLineEditor(out, input = keyInput) {
18129
18503
  });
18130
18504
  return;
18131
18505
  }
18132
- const action = applyKey(s, key ?? {}, str);
18506
+ const action = applyKey2(s, key ?? {}, str);
18133
18507
  if (action === "submit") {
18134
18508
  finish();
18135
18509
  opts.onSubmit?.();
@@ -18475,7 +18849,7 @@ import { spawnSync as spawnSync6 } from "child_process";
18475
18849
  import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync7 } from "fs";
18476
18850
  import { homedir as homedir8 } from "os";
18477
18851
  import { join as join14 } from "path";
18478
- var log38 = forComponent("os-sched");
18852
+ var log39 = forComponent("os-sched");
18479
18853
  var OsScheduler = class {
18480
18854
  options;
18481
18855
  constructor(options) {
@@ -18537,7 +18911,7 @@ var OsScheduler = class {
18537
18911
  }
18538
18912
  }
18539
18913
  } catch (e) {
18540
- log38.debug(`cancel ${id}`, e);
18914
+ log39.debug(`cancel ${id}`, e);
18541
18915
  }
18542
18916
  for (const f of [`${id}.json`, `${id}.sh`]) {
18543
18917
  try {
@@ -18655,7 +19029,7 @@ import { existsSync as existsSync8, mkdirSync as mkdirSync11, unlinkSync as unli
18655
19029
  import { homedir as homedir9 } from "os";
18656
19030
  import { join as join15 } from "path";
18657
19031
  init_platform();
18658
- var log39 = forComponent("remote-trigger");
19032
+ var log40 = forComponent("remote-trigger");
18659
19033
  var TRIGGER_DIR = () => join15(homedir9(), ".agent", "triggers");
18660
19034
  var sockPath = (sessionId, dir = TRIGGER_DIR()) => join15(dir, `${sessionId}.sock`);
18661
19035
  var TriggerServer = class {
@@ -18693,13 +19067,13 @@ var TriggerServer = class {
18693
19067
  conn.end(JSON.stringify({ ok: false, error: String(e) }) + "\n");
18694
19068
  }
18695
19069
  });
18696
- conn.on("error", (e) => log39.debug("trigger conn error", e));
19070
+ conn.on("error", (e) => log40.debug("trigger conn error", e));
18697
19071
  });
18698
- this.server.on("error", (e) => log39.debug("trigger server error", e));
19072
+ this.server.on("error", (e) => log40.debug("trigger server error", e));
18699
19073
  this.server.listen(p);
18700
19074
  this.path = p;
18701
19075
  } catch (e) {
18702
- log39.debug("trigger server unavailable", e);
19076
+ log40.debug("trigger server unavailable", e);
18703
19077
  }
18704
19078
  }
18705
19079
  /** Re-bind on /resume (the session id changed). */
@@ -18831,7 +19205,7 @@ var strike = C("9");
18831
19205
  var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
18832
19206
  var mdPalette = { bold, dim, cyan, italic, strike, green, link };
18833
19207
  var err = (s) => process.stderr.write(s);
18834
- var log40 = forComponent("cli");
19208
+ var log41 = forComponent("cli");
18835
19209
  var VERSION = (() => {
18836
19210
  try {
18837
19211
  return JSON.parse(readFileSync10(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
@@ -18971,7 +19345,13 @@ function parseArgs(argv) {
18971
19345
  a.harden = true;
18972
19346
  a.hardenNet = true;
18973
19347
  } else if (x === "--subagents") a.subagents = true;
18974
- else if (x === "--duplex") a.duplex = true;
19348
+ else if (x === "--delegate") a.delegate = argv[++i];
19349
+ else if (x === "--advisor") a.advisor = argv[++i];
19350
+ else if (x === "--mode-scope") {
19351
+ const v = argv[++i];
19352
+ if (v !== "sticky" && v !== "message") throw new Error("--mode-scope must be sticky|message");
19353
+ a.modeScope = v;
19354
+ } else if (x === "--duplex") a.duplex = true;
18975
19355
  else if (x === "--duplex-v2") {
18976
19356
  a.duplex = true;
18977
19357
  a.duplexV2 = true;
@@ -19105,6 +19485,9 @@ Flags:
19105
19485
  --add-dir <path> mount another directory into the workspace (repeatable; disk mode only)
19106
19486
  --mcp-config <file> JSON { mcpServers?, permissions? } merged over .agent config for this run only
19107
19487
  --subagents allow the Task tool (spawn child agents)
19488
+ --delegate <worker> delegate mode: the main model plans (its writes are refused), <worker> writes via Task
19489
+ --advisor <model> advisor mode: the main model works; an Advise tool consults <model> (first write waits for it)
19490
+ --mode-scope <s> sticky (default): a [a->b] / [a<-b] prompt prefix holds for later turns (and -r resumes) until [off]; message: this turn only
19108
19491
  --reasoning <e> extended thinking: off|low|medium|high or a token budget (anthropic/openai)
19109
19492
  --session-id <id> use this id for the session (instead of an auto-generated one)
19110
19493
  --fork-session with -r/-c: branch the resumed session into a new id (source left untouched)
@@ -19714,7 +20097,7 @@ function turnCost(model, usage) {
19714
20097
  const priced = model.startsWith("claude-code/") ? `anthropic/${resolveModelAlias(model.slice("claude-code/".length))}` : model;
19715
20098
  return costOf(getModelInfo(priced)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, priced);
19716
20099
  }
19717
- async function evaluateGoal(ai, condition, transcript, log41) {
20100
+ async function evaluateGoal(ai, condition, transcript, log42) {
19718
20101
  const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
19719
20102
  const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
19720
20103
  return text.slice(0, 600);
@@ -19734,7 +20117,7 @@ ${recent}` }
19734
20117
  const match = r.content.match(/\{[\s\S]*\}/);
19735
20118
  if (match) return JSON.parse(match[0]);
19736
20119
  } catch (e) {
19737
- log41(dim(` (goal evaluator error: ${e?.message ?? e})
20120
+ log42(dim(` (goal evaluator error: ${e?.message ?? e})
19738
20121
  `));
19739
20122
  }
19740
20123
  return { met: false, reason: "evaluation unclear" };
@@ -19961,7 +20344,7 @@ function mcpAgentTools(mounted, opts) {
19961
20344
  return tools;
19962
20345
  }
19963
20346
  async function closeMcp(mounted) {
19964
- await Promise.all(mounted.map((m) => m.client.close().catch((e) => log40.debug("mcp close failed", e))));
20347
+ await Promise.all(mounted.map((m) => m.client.close().catch((e) => log41.debug("mcp close failed", e))));
19965
20348
  }
19966
20349
  var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
19967
20350
  function mentionRefs(line) {
@@ -20020,7 +20403,7 @@ async function expandMentions(fs, line) {
20020
20403
  if (loaded.includes(ref) || missing.includes(ref)) continue;
20021
20404
  if (ref.includes(":") && mcpMentionResolver) {
20022
20405
  const body = await mcpMentionResolver(ref).catch((e) => {
20023
- log40.debug("mcp mention resolve failed", e);
20406
+ log41.debug("mcp mention resolve failed", e);
20024
20407
  return null;
20025
20408
  });
20026
20409
  if (body != null) {
@@ -20045,6 +20428,33 @@ ${await fs.readFile(path)}`);
20045
20428
 
20046
20429
  ${blocks.join("\n\n")}` : line, loaded, missing };
20047
20430
  }
20431
+ function applyTurnMode(agent, session, task, flags = {}, onModel) {
20432
+ const p = parseDirectives(task, { strict: true, keys: ["model", "turns", "delegate", "advisor", "mode"], warn: (m) => log41.debug(m) });
20433
+ const d = p.directives, isMode = !!(d.delegate || d.advisor || d.modeOff);
20434
+ if (p.unresolvedModel && /->|<-|delegate:|advisor:/.test(task.slice(0, task.length - p.prompt.length))) {
20435
+ err(red(` \u2717 unknown model "${p.unresolvedModel}"${p.ambiguous ? ` (could be: ${p.ambiguous.join(", ")})` : ""} \u2014 turn not sent
20436
+ `));
20437
+ return null;
20438
+ }
20439
+ const r = (m) => m ? resolveModelOrNewest(m) : void 0;
20440
+ const fromDirective = isMode ? d.modeOff ? {} : { model: r(d.model), delegate: r(d.delegate), advisor: r(d.advisor) } : void 0;
20441
+ if (fromDirective && flags.modeScope !== "message") session.meta.mode = fromDirective;
20442
+ const mode = fromDirective ?? session.meta.mode ?? { delegate: flags.delegate && r(flags.delegate), advisor: flags.advisor && r(flags.advisor) };
20443
+ const o = agent.options, before = JSON.stringify([o.model, o.delegate, o.advisor]);
20444
+ if (mode.model && mode.model !== o.model) {
20445
+ o.model = mode.model;
20446
+ o.contextWindow = getModelInfo(mode.model)?.contextWindow ?? 0;
20447
+ onModel?.(mode.model);
20448
+ }
20449
+ o.delegate = mode.delegate ? { worker: mode.delegate } : void 0;
20450
+ o.advisor = mode.advisor ? { ...o.advisor, model: mode.advisor } : void 0;
20451
+ if (JSON.stringify([o.model, o.delegate, o.advisor]) !== before) {
20452
+ agent.reprepare();
20453
+ err(dim(` \u21C4 ${o.delegate ? `delegate: ${o.model} plans \u2192 ${o.delegate.worker} writes` : o.advisor ? `advisor: ${o.model} works \u2190 ${o.advisor.model} advises` : "single-model mode"}
20454
+ `));
20455
+ }
20456
+ return isMode ? p.prompt : task;
20457
+ }
20048
20458
  function jsonResult(res, session) {
20049
20459
  const lastUser = res.messages.map((m) => m.role).lastIndexOf("user");
20050
20460
  return {
@@ -20056,6 +20466,7 @@ function jsonResult(res, session) {
20056
20466
  tools: res.messages.slice(lastUser).filter((m) => m.role === "tool").length,
20057
20467
  usage: res.usage,
20058
20468
  ...res.budget ? { budget: res.budget } : {},
20469
+ ...res.workerUsage ? { workerModel: res.workerModel, workerUsage: res.workerUsage } : {},
20059
20470
  ...res.routing ? { costUsd: res.costUsd, routing: res.routing } : {},
20060
20471
  // cast: tsup dts hoists a modelRouter import above the shebang
20061
20472
  sessionId: session.meta.id,
@@ -20111,7 +20522,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
20111
20522
  try {
20112
20523
  store.save(session);
20113
20524
  } catch (ex) {
20114
- log40.debug("mid-turn session flush failed", ex);
20525
+ log41.debug("mid-turn session flush failed", ex);
20115
20526
  }
20116
20527
  }
20117
20528
  return origNotify(e);
@@ -20140,7 +20551,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
20140
20551
  agent.options.host?.flushText?.();
20141
20552
  flushLookupGroup();
20142
20553
  const secs2 = ((Date.now() - t0) / 1e3).toFixed(1);
20143
- const cost = res.costUsd ?? turnCost(agent.options.model, res.usage);
20554
+ const cost = (res.costUsd ?? turnCost(agent.options.model, res.usage)) + (res.workerUsage && res.workerModel ? turnCost(res.workerModel, res.workerUsage) : 0);
20144
20555
  const m = res.usageEstimated ? "~" : "";
20145
20556
  const tok = res.usage?.totalTokens ? `${m}${(res.usage.totalTokens / 1e3).toFixed(1)}k tok \xB7 ${cost > 0 ? m + fmtUsd(cost) + " \xB7 " : ""}` : "";
20146
20557
  agent.options.host?.takeToolCount?.();
@@ -20275,14 +20686,14 @@ var isCancelTeardown = (e) => {
20275
20686
  function installCancelGuards(mounted) {
20276
20687
  process.on("unhandledRejection", (e) => {
20277
20688
  if (isCancelTeardown(e)) {
20278
- log40.debug("suppressed unhandledRejection (cursor stream cancel)", e);
20689
+ log41.debug("suppressed unhandledRejection (cursor stream cancel)", e);
20279
20690
  return;
20280
20691
  }
20281
- log40.error("unhandledRejection", e);
20692
+ log41.error("unhandledRejection", e);
20282
20693
  });
20283
20694
  process.on("uncaughtException", (e) => {
20284
20695
  if (isCancelTeardown(e)) {
20285
- log40.debug("suppressed uncaughtException (cursor stream cancel)", e);
20696
+ log41.debug("suppressed uncaughtException (cursor stream cancel)", e);
20286
20697
  return;
20287
20698
  }
20288
20699
  console.error(e);
@@ -20651,7 +21062,7 @@ async function repl(args, ai, cfg, cwd) {
20651
21062
  editorRef?.abort();
20652
21063
  }
20653
21064
  } catch (e) {
20654
- log40.error("call turn failed", e);
21065
+ log41.error("call turn failed", e);
20655
21066
  } finally {
20656
21067
  rec.running = false;
20657
21068
  call?.main({ type: "done", id });
@@ -20783,7 +21194,7 @@ async function repl(args, ai, cfg, cwd) {
20783
21194
  try {
20784
21195
  mkdirSync12(dir, { recursive: true });
20785
21196
  } catch (e) {
20786
- log40.debug("paste image dir create failed", e);
21197
+ log41.debug("paste image dir create failed", e);
20787
21198
  }
20788
21199
  const img = grabClipboardImage(dir, String(Date.now()));
20789
21200
  return img ? { display: "Image", ref: "@" + img.path, path: img.path } : null;
@@ -20896,7 +21307,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
20896
21307
  mkdirSync12(join16(cwd, ".agent"), { recursive: true });
20897
21308
  appendFileSync(histPath, line + "\n");
20898
21309
  } catch (e) {
20899
- log40.debug("history write failed", e);
21310
+ log41.debug("history write failed", e);
20900
21311
  }
20901
21312
  };
20902
21313
  const ago = (t) => {
@@ -20968,7 +21379,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
20968
21379
  try {
20969
21380
  store.save(session);
20970
21381
  } catch (e) {
20971
- log40.debug("session save after rewind failed", e);
21382
+ log41.debug("session save after rewind failed", e);
20972
21383
  }
20973
21384
  err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
20974
21385
  `));
@@ -21003,13 +21414,20 @@ ${task}`;
21003
21414
  bangContext.length = 0;
21004
21415
  }
21005
21416
  const delta = await refreshCatalogs().catch((e) => {
21006
- log40.debug("catalog refresh failed", e);
21417
+ log41.debug("catalog refresh failed", e);
21007
21418
  return "";
21008
21419
  });
21009
21420
  if (delta) {
21010
21421
  err(dim(" \u27F3 skill/command catalog changed \u2014 delta attached to this turn\n"));
21011
21422
  task += "\n\n" + delta;
21012
21423
  }
21424
+ if (!duplex) {
21425
+ const t = applyTurnMode(face, session, task, args, (m) => {
21426
+ face.options.providerOptions = providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name));
21427
+ });
21428
+ if (t === null) return { ok: false };
21429
+ task = t;
21430
+ }
21013
21431
  const r = await runTurn(face, store, session, task, duplex ? void 0 : checkpoints, cwd, sendVia ? (c) => sendVia(c, opts) : routeVia);
21014
21432
  if (voiceIO) {
21015
21433
  voiceReasonEnd();
@@ -21266,7 +21684,7 @@ ${task}`;
21266
21684
  desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
21267
21685
  run: async () => {
21268
21686
  await refreshCatalogs().catch((e) => {
21269
- log40.debug("catalog refresh failed", e);
21687
+ log41.debug("catalog refresh failed", e);
21270
21688
  });
21271
21689
  face.reprepare();
21272
21690
  err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
@@ -21769,7 +22187,7 @@ ${task}`;
21769
22187
  try {
21770
22188
  for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
21771
22189
  } catch (e) {
21772
- log40.debug(`loadAgents(${d}) failed`, e);
22190
+ log41.debug(`loadAgents(${d}) failed`, e);
21773
22191
  }
21774
22192
  }
21775
22193
  if (!seen.size) {
@@ -21857,7 +22275,7 @@ ${task}`;
21857
22275
  }
21858
22276
  if (idx >= 0) {
21859
22277
  const old = mounted.splice(idx, 1)[0];
21860
- await old.client.close().catch((e) => log40.debug("mcp close failed", e));
22278
+ await old.client.close().catch((e) => log41.debug("mcp close failed", e));
21861
22279
  }
21862
22280
  try {
21863
22281
  const m = await mountMcpServer(name, conf);
@@ -21885,7 +22303,7 @@ ${task}`;
21885
22303
  }
21886
22304
  const m = mounted.splice(idx, 1)[0];
21887
22305
  remountMcpTools();
21888
- await m.client.close().catch((e) => log40.debug("mcp close failed", e));
22306
+ await m.client.close().catch((e) => log41.debug("mcp close failed", e));
21889
22307
  err(dim(` removed "${name}"
21890
22308
  `));
21891
22309
  return;
@@ -22088,7 +22506,7 @@ ${task}`;
22088
22506
  try {
22089
22507
  return readdirSync4(join16(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
22090
22508
  } catch (e) {
22091
- log40.debug("completion readdir failed", absDir, e);
22509
+ log41.debug("completion readdir failed", absDir, e);
22092
22510
  return null;
22093
22511
  }
22094
22512
  };
@@ -22148,7 +22566,7 @@ ${task}`;
22148
22566
  }
22149
22567
  return;
22150
22568
  }
22151
- const action = applyKey(sEd, key ?? {}, _s);
22569
+ const action = applyKey2(sEd, key ?? {}, _s);
22152
22570
  if (action === "submit") {
22153
22571
  const text = sEd.expand().trim();
22154
22572
  sEd.reset();
@@ -22225,7 +22643,7 @@ ${out}
22225
22643
  return;
22226
22644
  }
22227
22645
  await refreshCatalogs().catch((e) => {
22228
- log40.debug("catalog refresh failed", e);
22646
+ log41.debug("catalog refresh failed", e);
22229
22647
  });
22230
22648
  const sk = skills.find((x) => x.name === name);
22231
22649
  if (sk) {
@@ -22350,7 +22768,7 @@ ${out}
22350
22768
  if (delivery || cut) dx?.parkCutDelivery(delivery, !!cut, heard);
22351
22769
  const cutReply = cut && cut.full.length - cut.heard.length > 40 ? cut : null;
22352
22770
  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}`);
22771
+ log41.warn(`barge intent failed \u2014 model decides: ${e.message}`);
22354
22772
  return void 0;
22355
22773
  }) : Promise.resolve(void 0)).then(async (intent) => interruptionNote(cutReply, intent) + ackedNote(await instinct?.ackFor(text)));
22356
22774
  const cutIn = replaces ? replaces.cutIn : interjectNote;
@@ -22729,7 +23147,7 @@ function channelOptions(ai, model, gender) {
22729
23147
  // Anticipated backchannels: on in chatty mode (its preset), else off until the offline replay proves them (tests/voice-bc-anticipate-eval.ts)
22730
23148
  plan: (c, s) => planner.plan(c, s),
22731
23149
  ...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`)
23150
+ onEvent: (e) => log41.debug(`channel ${e.trigger}: rule=${e.rule} choice=${e.choice ?? "-"} \u2192 ${e.acted}${e.text ? ` "${e.text}"` : ""}${e.cue ? " (planned cue)" : ""} ${e.ms}ms`)
22733
23151
  };
22734
23152
  }
22735
23153
  function overlapResponder(ai, mode, out, aside) {
@@ -22800,7 +23218,7 @@ async function duplexServe(args, ai, cfg, cwd) {
22800
23218
  try {
22801
23219
  store.save(session);
22802
23220
  } catch (e) {
22803
- log40.warn("duplex session save failed", e);
23221
+ log41.warn("duplex session save failed", e);
22804
23222
  }
22805
23223
  };
22806
23224
  const overlap2 = overlapResponder(ai, args.duplexV2 ? args.channelMode : void 0, out);
@@ -22840,7 +23258,7 @@ async function duplexServe(args, ai, cfg, cwd) {
22840
23258
  done2();
22841
23259
  out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error", ...why ? { error: why } : {} });
22842
23260
  }).catch((e) => {
22843
- log40.error("duplex turn failed", e);
23261
+ log41.error("duplex turn failed", e);
22844
23262
  done2();
22845
23263
  out({ type: "turn_done", id, finishReason: "error", error: String(e?.message ?? e) });
22846
23264
  });
@@ -23095,7 +23513,12 @@ async function main() {
23095
23513
  const session = startSession(args, store, agent, cwd);
23096
23514
  process.once("SIGINT", () => activeTurn?.abort());
23097
23515
  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))));
23516
+ const turn = async (task) => {
23517
+ const t = applyTurnMode(agent, session, task, args, (m) => {
23518
+ agent.options.providerOptions = providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name));
23519
+ });
23520
+ return t === null ? { ok: false } : runTurn(agent, store, session, t, void 0, cwd, autoRouteSend(args, agent, () => session, (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name))));
23521
+ };
23099
23522
  if (warmBoot && steers) {
23100
23523
  bindClaudeCodeSession(agent, session);
23101
23524
  await agent.prewarm().catch((e) => err(dim(` (prewarm failed: ${e?.message ?? e})
@@ -23157,6 +23580,7 @@ export {
23157
23580
  abortActiveTurn,
23158
23581
  apiKeysFromEnv,
23159
23582
  appendMemoryNote,
23583
+ applyTurnMode,
23160
23584
  cacheMultipliers,
23161
23585
  condenseReplay,
23162
23586
  costOf,