@livx.cc/agentx 0.99.120 → 0.99.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 = [
@@ -10027,8 +10208,17 @@ function isPhatic(text, bank) {
10027
10208
  return !!q2 && (t === q2 || t.startsWith(q2 + " "));
10028
10209
  });
10029
10210
  }
10211
+ var CLOSER = new Set("ok okay k cool great nice perfect awesome amazing lovely excellent alright fine good thanks thank thx you so much very a lot bye goodbye cheers later see ya night got it i that that's thats is it's its all for now sounds understood noted makes sense oh ah wow right then just \u05EA\u05D5\u05D3\u05D4 \u05E8\u05D1\u05D4 \u05E1\u05D1\u05D1\u05D4 \u05D0\u05D5\u05E7\u05D9\u05D9 \u05D0\u05D5\u05E7\u05D9 \u05D1\u05E1\u05D3\u05E8 \u05D9\u05D5\u05E4\u05D9 \u05D0\u05D7\u05DC\u05D4 \u05DE\u05E2\u05D5\u05DC\u05D4 \u05DE\u05E6\u05D5\u05D9\u05DF \u05DE\u05D2\u05E0\u05D9\u05D1 \u05E0\u05D4\u05D3\u05E8 \u05D1\u05D9\u05D9 \u05DC\u05D4\u05EA\u05E8\u05D0\u05D5\u05EA \u05D8\u05D5\u05D1 \u05D6\u05D4\u05D5 \u05D6\u05D4 \u05D4\u05DB\u05DC \u05D1\u05D9\u05E0\u05EA\u05D9\u05D9\u05DD \u05D4\u05D1\u05E0\u05EA\u05D9 \u05DE\u05DE\u05E9 \u05DC\u05DA \u05D0\u05D6".split(" "));
10212
+ function turnIntent(text) {
10213
+ const t = text.trim();
10214
+ if (/\?[\s"')\]]*$/.test(t)) return "ask";
10215
+ const w = norm3(t).split(" ").filter(Boolean);
10216
+ const asks = requestOpener(t) && !/^ת/.test(lead(t)[0] ?? "") || pendingQuestion(t);
10217
+ if (w.length && w.length <= 8 && w.every((x) => CLOSER.has(x)) && !asks) return "closer";
10218
+ return asks || requestOpener(t) ? "ask" : "say";
10219
+ }
10030
10220
  var SKIP = new Set("um uh uhm erm hmm mm oh ok okay so no yes yeah yep well hey hi and but right alright actually like now then wait hang on me speaker \u05D0\u05D5\u05E7\u05D9\u05D9 \u05D8\u05D5\u05D1 \u05D0\u05D6 \u05DC\u05D0 \u05DB\u05DF \u05E8\u05D2\u05E2 \u05D0\u05D4 \u05D0\u05DE \u05D9\u05D0\u05DC\u05DC\u05D4 \u05D5".split(" "));
10031
- var ASK = "what whats how why where when who which whose is are was were do does did can could would will should shall have has may isnt arent dont doesnt didnt cant wont check open show tell find search look send run go lets please take give make write explain select focus order try compare list use set activate highlight outline capture start stop read play call book create add remove delete put type click close switch help get summarize translate mark keep remind schedule \u05DE\u05D4 \u05D0\u05D9\u05DA \u05DC\u05DE\u05D4 \u05D0\u05D9\u05E4\u05D4 \u05DE\u05EA\u05D9 \u05DE\u05D9 \u05D4\u05D0\u05DD \u05DB\u05DE\u05D4 \u05EA\u05DF \u05EA\u05E0\u05D9 \u05EA\u05D2\u05D9\u05D3 \u05EA\u05D2\u05D9\u05D3\u05D9 \u05D1\u05D5\u05D0 \u05D1\u05D5\u05D0\u05D9".split(" ");
10221
+ var ASK = "what whats how why where when who which whose is are was were do does did can could would will should shall have has may isnt arent dont doesnt didnt cant wont check open show tell find search look send run go lets please take give make write explain select focus order try compare list use set activate highlight outline capture start stop read play call book create add remove delete put type click close switch help get name recommend suggest summarize translate mark keep remind schedule \u05DE\u05D4 \u05D0\u05D9\u05DA \u05DC\u05DE\u05D4 \u05D0\u05D9\u05E4\u05D4 \u05DE\u05EA\u05D9 \u05DE\u05D9 \u05D4\u05D0\u05DD \u05DB\u05DE\u05D4 \u05EA\u05DF \u05EA\u05E0\u05D9 \u05EA\u05D2\u05D9\u05D3 \u05EA\u05D2\u05D9\u05D3\u05D9 \u05D1\u05D5\u05D0 \u05D1\u05D5\u05D0\u05D9".split(" ");
10032
10222
  var WANT = /^(i (want|need|would like|d like|wanna)|אני (רוצה|צריך|צריכה|מבקש|מבקשת))(\s|$)/u;
10033
10223
  var lead = (text) => {
10034
10224
  const w = text.toLowerCase().replace(/[^\p{L}\p{N}\s]+/gu, "").split(/\s+/).filter(Boolean);
@@ -10108,11 +10298,11 @@ function cleanLine(s, lang, maxWords = 6) {
10108
10298
  const raw = s.trim().split("\n")[0];
10109
10299
  const l = raw.replace(/^[-*•\d.)\s]+/, "").replace(/^["'“”«»]+|["'“”«»]+$/g, "").replace(new RegExp("^\\p{L}+:\\s*", "u"), "").trim();
10110
10300
  if (!l || isLeakSuspect(raw) || isLeakSuspect(l)) {
10111
- if (l) log22.debug(`line "${raw}" reads as meta \u2014 rejected`);
10301
+ if (l) log23.debug(`line "${raw}" reads as meta \u2014 rejected`);
10112
10302
  return "";
10113
10303
  }
10114
10304
  if (lang && !fitsLanguage(l, lang)) {
10115
- log22.debug(`line "${l}" is not ${lang} \u2014 rejected`);
10305
+ log23.debug(`line "${l}" is not ${lang} \u2014 rejected`);
10116
10306
  return "";
10117
10307
  }
10118
10308
  return l.split(/\s+/).length > maxWords || l.length > 60 ? "" : l;
@@ -10126,7 +10316,7 @@ function validateBank(raw, lang, minPerKind = 3) {
10126
10316
  const seen = /* @__PURE__ */ new Set();
10127
10317
  out[k] = (Array.isArray(v) ? v : []).filter((x) => typeof x === "string").map((x) => cleanLine(x, lang)).filter((x) => x && !(k === "filler" && SPEAKER_FILLER.test(norm3(x))) && !seen.has(norm3(x)) && seen.add(norm3(x)));
10128
10318
  if (out[k].length < minPerKind) {
10129
- 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`);
10130
10320
  return null;
10131
10321
  }
10132
10322
  }
@@ -10202,11 +10392,11 @@ var ChannelBanks = class {
10202
10392
  const f = this.file(lang);
10203
10393
  let cached = null;
10204
10394
  if (f) cached = await readFile(f, "utf8").then((s) => JSON.parse(s)).catch((e) => {
10205
- if (e.code !== "ENOENT") 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}`);
10206
10396
  return null;
10207
10397
  });
10208
10398
  if (cached && cached.v !== BANK_VERSION) {
10209
- 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`);
10210
10400
  cached = null;
10211
10401
  }
10212
10402
  const valid = cached && validateBank(cached.bank, lang, 0);
@@ -10214,7 +10404,7 @@ var ChannelBanks = class {
10214
10404
  if (valid) {
10215
10405
  for (const k of BANK_KEYS) if (!valid[k].length) valid[k] = [...this.o.fallback[lang]?.[k] ?? []];
10216
10406
  this.banks.set(lang, valid);
10217
- 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"}`);
10218
10408
  }
10219
10409
  if (complete && this.o.now() - cached.at < this.o.refreshMs) return;
10220
10410
  await this.generate(lang, valid);
@@ -10233,11 +10423,11 @@ var ChannelBanks = class {
10233
10423
  bank[k] = [...fresh[k], ...have?.[k] ?? []].filter((x) => !seen.has(norm3(x)) && seen.add(norm3(x))).slice(0, this.o.maxPerKind);
10234
10424
  }
10235
10425
  this.banks.set(lang, bank);
10236
- 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`);
10237
10427
  const f = this.file(lang);
10238
- if (f) await mkdir(this.o.cacheDir, { recursive: true }).then(() => writeFile(f, JSON.stringify({ at: this.o.now(), v: BANK_VERSION, bank }))).catch((e) => 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}`));
10239
10429
  } catch (e) {
10240
- 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}`);
10241
10431
  } finally {
10242
10432
  clearTimeout(t);
10243
10433
  if (!this.banks.has(lang)) setTimeout(() => this.pending.delete(lang), this.o.retryMs).unref?.();
@@ -10430,7 +10620,7 @@ var TurnGate = class {
10430
10620
  };
10431
10621
 
10432
10622
  // src/duplex.ts
10433
- var log23 = forComponent("DuplexAgent");
10623
+ var log24 = forComponent("DuplexAgent");
10434
10624
  function describeCall(call) {
10435
10625
  const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
10436
10626
  const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
@@ -10765,7 +10955,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
10765
10955
  const m = this.reflexBuf.match(RESERVED_EVENT_MARKER) ?? this.reflexBuf.match(RESERVED_EVENT_OPENER);
10766
10956
  if (m) {
10767
10957
  this.fabricationCut = true;
10768
- 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)`);
10769
10959
  this.reflexBuf = this.reflexBuf.slice(0, Math.max(m.index, this.reflexForwarded));
10770
10960
  this.pumpReflex(true);
10771
10961
  return;
@@ -10907,7 +11097,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
10907
11097
  this.leakHit = true;
10908
11098
  this.leakCuts.push(leak.trim().slice(0, 40));
10909
11099
  const why = this.cutKind === "claim" ? "stated a result no task has given yet" : this.cutKind === "restart" ? "started a second, restated reply" : "spoke its own analysis";
10910
- 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`);
10911
11101
  this.notify("diag", this.cutKind === "claim" ? "result_claim" : this.cutKind === "restart" ? "reply_restart" : "analysis_leak", { text: leak.trim().slice(0, 80), at: this.turnSpoken.trim() ? "mid_reply" : "before_speech" });
10912
11102
  const next = this.stepMarks.find((k) => k > at);
10913
11103
  if (next === void 0) {
@@ -10948,7 +11138,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
10948
11138
  this.notify("diag", "leak_judge", { text: s.slice(0, 80), p: Math.round(p * 100) / 100, ms: Math.round(performance.now() - t0) });
10949
11139
  return p >= LEAK_JUDGE_THRESHOLD;
10950
11140
  }, (e) => {
10951
- log23.warn(`leak judge failed \u2014 speaking the sentence: ${e.message}`);
11141
+ log24.warn(`leak judge failed \u2014 speaking the sentence: ${e.message}`);
10952
11142
  return false;
10953
11143
  }).then((leak) => {
10954
11144
  clearTimeout(timer);
@@ -11020,7 +11210,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11020
11210
  let i = t.length - 1;
11021
11211
  while (i >= this.turnBase && !(t[i].role === "assistant" && typeof t[i].content === "string" && t[i].content.includes(key))) i--;
11022
11212
  if (i < this.turnBase) {
11023
- 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");
11024
11214
  continue;
11025
11215
  }
11026
11216
  const m = t[i];
@@ -11077,7 +11267,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11077
11267
  const res = await this.sendWithContext(content, context, note);
11078
11268
  const fb = this.options.reflexFallbackModel, model = this.voice.options.model;
11079
11269
  if (res.finishReason !== "error" || !rateLimited(res.error) || !fb || fb === model || fb.includes("/") && model.includes("/") && fb.split("/")[0] !== model.split("/")[0] || this.turnDispatched || this.turnSpoken.trim()) return res;
11080
- 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}`);
11081
11271
  this.notify("diag", "reflex_fallback", { from: model, to: fb });
11082
11272
  this.voice.transcript.length = base;
11083
11273
  this.voice.options.model = fb;
@@ -11126,7 +11316,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11126
11316
  try {
11127
11317
  await this.sendWithContext(reminder, this.lastContext);
11128
11318
  } catch (e) {
11129
- 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}`);
11130
11320
  } finally {
11131
11321
  const i = this.voice.transcript.findIndex((m, k) => k >= base && m.role === "user" && m.content === reminder);
11132
11322
  if (i >= 0) this.voice.transcript.splice(i, 1);
@@ -11187,7 +11377,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11187
11377
  return {
11188
11378
  preToolUse: (call) => {
11189
11379
  if (this.spec?.state === "pending" && !_DuplexAgent.SPEC_SAFE_TOOLS.has(call.name)) {
11190
- log23.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
11380
+ log24.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
11191
11381
  this.abortSpeculation();
11192
11382
  return { block: true, reason: "Speculative turn aborted." };
11193
11383
  }
@@ -11269,7 +11459,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11269
11459
  try {
11270
11460
  await this.sendWithContext(reminder, this.lastContext);
11271
11461
  } catch (e) {
11272
- log23.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
11462
+ log24.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
11273
11463
  } finally {
11274
11464
  this.nudging = false;
11275
11465
  const i = this.voice.transcript.findIndex((m, k) => k >= base && m.role === "user" && m.content === reminder);
@@ -11325,7 +11515,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11325
11515
  const accepted = this.offerOpen && this.parkedRedeliver.length > 0 && this.parkedRedeliver.every((p) => p.task === this.offer.task) && (isOfferAcceptance(content) || isTrivialBarge(content));
11326
11516
  const judge = accepted ? void 0 : this.options.bargeIntent;
11327
11517
  return (accepted ? Promise.resolve("resume") : judge ? judge(content).catch((e) => {
11328
- log23.warn(`bargeIntent judge failed \u2014 using the allowlist: ${e.message}`);
11518
+ log24.warn(`bargeIntent judge failed \u2014 using the allowlist: ${e.message}`);
11329
11519
  return fallback();
11330
11520
  }) : Promise.resolve(fallback())).then((intent) => {
11331
11521
  this.notify("diag", "barge_intent", { intent, text: content.slice(0, 80) });
@@ -11528,7 +11718,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11528
11718
  try {
11529
11719
  res = await this.sendWithContext(spec.text, this.lastContext);
11530
11720
  } catch (e) {
11531
- log23.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
11721
+ log24.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
11532
11722
  } finally {
11533
11723
  this.voice.options.signal = prevSignal;
11534
11724
  }
@@ -11542,7 +11732,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11542
11732
  if (d === "abort") {
11543
11733
  if (this.voice.transcript.length > base) this.voice.transcript.length = base;
11544
11734
  this.speculativeAbortedCalls++;
11545
- 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)}"`);
11546
11736
  if (this.spec === spec) this.spec = void 0;
11547
11737
  return res ?? empty;
11548
11738
  }
@@ -11928,7 +12118,7 @@ Another agent just implemented the above. Independently check the CURRENT state
11928
12118
  this.notify("task_verify", `task ${id}: verifying`, { id });
11929
12119
  const cres = await new Agent(checkerOpts).run(checkBrief);
11930
12120
  if (cres.finishReason !== "stop") {
11931
- log23.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
12121
+ log24.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
11932
12122
  this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
11933
12123
  }
11934
12124
  const sum = (a = 0, b = 0) => a + b;
@@ -12064,7 +12254,7 @@ Another agent just implemented the above. Independently check the CURRENT state
12064
12254
  rec.status = "done";
12065
12255
  rec.result = res.text;
12066
12256
  const incomplete = res.finishReason !== "stop" && res.finishReason !== "empty";
12067
- 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}` : ""})`);
12068
12258
  this.notify("task_done", `task ${id} (${rec.label}) completed`, {
12069
12259
  id,
12070
12260
  text: res.text,
@@ -12098,14 +12288,14 @@ Another agent just implemented the above. Independently check the CURRENT state
12098
12288
  foldIfSuperseded(rec, note) {
12099
12289
  if (rec.deliveryParked !== "moved_on") return false;
12100
12290
  if (note.trim()) this.voice.transcript.push({ role: "assistant", content: note });
12101
- 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`);
12102
12292
  return true;
12103
12293
  }
12104
12294
  failTask(rec, msg) {
12105
12295
  this.dropAsk(rec.id);
12106
12296
  rec.status = "error";
12107
12297
  rec.result = msg;
12108
- log23.warn(`task ${rec.id} failed: ${msg}`);
12298
+ log24.warn(`task ${rec.id} failed: ${msg}`);
12109
12299
  this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`, { id: rec.id });
12110
12300
  if (this.foldIfSuperseded(rec, `The "${rec.label}" task failed: ${msg}`)) return;
12111
12301
  this.dropParked(rec);
@@ -12409,7 +12599,7 @@ Another agent just implemented the above. Independently check the CURRENT state
12409
12599
  init_llm();
12410
12600
  init_logging();
12411
12601
  init_models();
12412
- var log24 = forComponent("ModelRouter");
12602
+ var log25 = forComponent("ModelRouter");
12413
12603
  var ModelRouterOptions = class {
12414
12604
  classifier = jevClassifier();
12415
12605
  /** Capability tiers → model id (any provider). basic/standard run the main session; premium is only ever an offload. */
@@ -12493,10 +12683,10 @@ var ModelRouter = class {
12493
12683
  if (tier === "premium") d.offload = this.state.offload && (c.sameThread ?? 0) >= o.sameThreadAt ? "continue" : "fresh";
12494
12684
  } catch (e) {
12495
12685
  const msg = e instanceof Error ? e.message : String(e);
12496
- log24.warn(`classifier failed, falling back to standard: ${msg}`);
12686
+ log25.warn(`classifier failed, falling back to standard: ${msg}`);
12497
12687
  d = { tier: "standard", model: o.models.standard, probs: {}, fallback: msg, classifyMs: Math.round(performance.now() - t0) };
12498
12688
  }
12499
- 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`);
12500
12690
  return d;
12501
12691
  }
12502
12692
  /** Run one user turn on `agent` with routing. Returns a RunResult carrying `costUsd` + `routing`. */
@@ -12548,7 +12738,7 @@ ${text}`;
12548
12738
  const answer = res.text || `(premium sub-agent finished with no reply; finishReason=${res.finishReason})`;
12549
12739
  agent.transcript.push({ role: "user", content }, { role: "assistant", content: answer });
12550
12740
  this.state.offload = { messages: child.transcript, mainLen: agent.transcript.length, question: text };
12551
- 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`);
12552
12742
  const costUsd = o.costOf(main2, briefUsage) + o.costOf(o.models.premium, res.usage);
12553
12743
  return { ...res, text: answer, messages: agent.transcript, usage: addUsage(briefUsage, res.usage), costUsd, routing: d };
12554
12744
  }
@@ -12564,7 +12754,7 @@ The request:
12564
12754
  ${text}`);
12565
12755
  return { text: r.text.trim() || text, usage: r.usage };
12566
12756
  } catch (e) {
12567
- 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)}`);
12568
12758
  return { text };
12569
12759
  } finally {
12570
12760
  agent.transcript = snap;
@@ -12609,7 +12799,7 @@ async function jevAvailable(opts = {}) {
12609
12799
  const { env } = await loadJev(opts);
12610
12800
  return JEV_KEYS.some((k) => env[k]?.trim());
12611
12801
  } catch (e) {
12612
- log24.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
12802
+ log25.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
12613
12803
  return false;
12614
12804
  }
12615
12805
  }
@@ -12836,7 +13026,7 @@ init_models();
12836
13026
 
12837
13027
  // src/voice/progress.ts
12838
13028
  init_logging();
12839
- var log25 = forComponent("Progress");
13029
+ var log26 = forComponent("Progress");
12840
13030
  var HE_RESULT_CLAIM = /(?:^|\s)(?:מצאתי|סיימתי|בוצע|הנה|אין)(?=\s|$|[.,!…])/u;
12841
13031
  var TOPICS = [
12842
13032
  [/toolsearch|^mcp__.*__(list_commands|call_info)$/, null],
@@ -12981,7 +13171,7 @@ var Progress = class {
12981
13171
  }, this.o.generateBudgetMs);
12982
13172
  });
12983
13173
  const raw = await Promise.race([this.o.generate(tool, argsHint(args), lang, ac.signal).catch((e) => {
12984
- log25.warn(`progress line failed: ${e?.message ?? e}`);
13174
+ log26.warn(`progress line failed: ${e?.message ?? e}`);
12985
13175
  return null;
12986
13176
  }), late]).finally(() => clearTimeout(t));
12987
13177
  const l = cleanLine(raw ?? "", lang, 5);
@@ -13003,7 +13193,7 @@ init_logging();
13003
13193
 
13004
13194
  // src/voice/backchannelPlan.ts
13005
13195
  init_logging();
13006
- var log26 = forComponent("BackchannelPlan");
13196
+ var log27 = forComponent("BackchannelPlan");
13007
13197
  var TURN_TYPES = ["story", "list", "complaint", "explanation", "short_command", "question", "other"];
13008
13198
  var DENSITIES = ["none", "low", "normal", "high"];
13009
13199
  var silentTurn = (p) => p.density === "none" || p.turn === "short_command" || p.turn === "question";
@@ -13068,13 +13258,13 @@ Reply JSON only: {"turn":"...","density":"...","cues":[{"when":["phrase",...],"l
13068
13258
  const reasoning = /gpt-oss/.test(o.model) ? { providerOptions: { reasoning_effort: "low" } } : {};
13069
13259
  const r = await this.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false, signal, maxTokens: o.maxTokens, ...reasoning });
13070
13260
  const p = parsePlan(r.content ?? "", c.lang);
13071
- if (!p) log26.debug(`unusable plan: ${(r.content ?? "").slice(0, 120)}`);
13261
+ if (!p) log27.debug(`unusable plan: ${(r.content ?? "").slice(0, 120)}`);
13072
13262
  return p;
13073
13263
  }
13074
13264
  };
13075
13265
 
13076
13266
  // src/voice/secondChannel.ts
13077
- var log27 = forComponent("SecondChannel");
13267
+ var log28 = forComponent("SecondChannel");
13078
13268
  var CHANNEL_MODES = {
13079
13269
  turn: { kinds: ["bridge"], bridgeDelayMs: 1500 },
13080
13270
  normal: {},
@@ -13164,6 +13354,9 @@ var SecondChannelOptions = class {
13164
13354
  /** Duplex v3 (§3 first sound): a bridge skips the decider (the veto stays for backchannels/fillers) and speaks a
13165
13355
  * canned line at once; a generated one is started at dispatch and used only if it already landed. */
13166
13356
  instantBridge = false;
13357
+ /** Duplex v3 §3 late bridge: a turn that expects an answer (not a closer) and got no bridge yet gets one canned bridge
13358
+ * this long after the user's last word if channel-1 is still silent — the short turns the 0ms rule skips ("Hi"). 0 = off. */
13359
+ lateBridgeMs = 0;
13167
13360
  /** No new interim for this long → a pause (hosts may also call onPause). */
13168
13361
  fillerPauseMs = 700;
13169
13362
  fillerMinWords = 4;
@@ -13193,15 +13386,15 @@ function withBudget(p, ms, what, ac) {
13193
13386
  let t;
13194
13387
  const late = new Promise((r) => {
13195
13388
  t = setTimeout(() => {
13196
- log27.debug(`${what}: over the ${ms}ms budget \u2014 dropped`);
13389
+ log28.debug(`${what}: over the ${ms}ms budget \u2014 dropped`);
13197
13390
  ac?.abort();
13198
13391
  r(null);
13199
13392
  }, ms);
13200
13393
  });
13201
13394
  return Promise.race([p.catch((e) => {
13202
13395
  const msg = String(e?.message ?? e);
13203
- if (e?.name === "AbortError" || /aborted/i.test(msg)) log27.debug(`${what}: aborted after its budget`);
13204
- 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}`);
13205
13398
  return null;
13206
13399
  }), late]).finally(() => clearTimeout(t));
13207
13400
  }
@@ -13227,6 +13420,11 @@ var SecondChannel = class {
13227
13420
  pauseTimer = null;
13228
13421
  bcTimer = null;
13229
13422
  bridgeTimer = null;
13423
+ lateTimer = null;
13424
+ late = false;
13425
+ // the bridge moment being considered is the late one (no length rule)
13426
+ lastWordAgo = 0;
13427
+ // the dispatched turn: ms since the user's last word (anchors lateBridgeMs)
13230
13428
  lastAt = -Infinity;
13231
13429
  lastKind = null;
13232
13430
  live = null;
@@ -13275,7 +13473,7 @@ var SecondChannel = class {
13275
13473
  if (host) this.pinned = true;
13276
13474
  const lang = primaryLang(tag);
13277
13475
  if (!lang || lang === this.lang) return;
13278
- log27.debug(`language ${this.lang} \u2192 ${lang}`);
13476
+ log28.debug(`language ${this.lang} \u2192 ${lang}`);
13279
13477
  this.lang = lang;
13280
13478
  this.recent = [];
13281
13479
  this.picks.clear();
@@ -13345,9 +13543,11 @@ var SecondChannel = class {
13345
13543
  if (s === this.main) return;
13346
13544
  this.main = s;
13347
13545
  if (s === "first_audio" || s === "speaking") this.live = null;
13348
- if (s !== "queued" && s !== "thinking" && this.bridgeTimer) {
13349
- clearTimeout(this.bridgeTimer);
13350
- this.bridgeTimer = null;
13546
+ if (s !== "queued" && s !== "thinking") {
13547
+ for (const k of ["bridgeTimer", "lateTimer"]) if (this[k]) {
13548
+ clearTimeout(this[k]);
13549
+ this[k] = null;
13550
+ }
13351
13551
  }
13352
13552
  if (s === "idle") {
13353
13553
  this.heard = "";
@@ -13368,7 +13568,7 @@ var SecondChannel = class {
13368
13568
  this.userSpeaking = false;
13369
13569
  this.bridged = false;
13370
13570
  if (this.lastKind === "bridge") this.lastKind = null;
13371
- for (const k of ["pauseTimer", "bcTimer", "bridgeTimer"]) {
13571
+ for (const k of ["pauseTimer", "bcTimer", "bridgeTimer", "lateTimer"]) {
13372
13572
  if (this[k]) clearTimeout(this[k]);
13373
13573
  this[k] = null;
13374
13574
  }
@@ -13379,11 +13579,21 @@ var SecondChannel = class {
13379
13579
  void this.consider("working");
13380
13580
  }, wait);
13381
13581
  else void this.consider("working");
13582
+ const late = this.o.lateBridgeMs - this.lastWordAgo;
13583
+ if (this.o.lateBridgeMs > 0) this.lateTimer = setTimeout(() => {
13584
+ this.lateTimer = null;
13585
+ if (this.bridged || this.busy || turnIntent(this.heard) === "closer" || this.blocked("bridge")) return;
13586
+ this.late = true;
13587
+ void this.consider("working").finally(() => {
13588
+ this.late = false;
13589
+ });
13590
+ }, Math.max(late, wait + 1));
13382
13591
  }
13383
13592
  }
13384
13593
  /** The user's final line was dispatched to the main agent (its words are the bridge's micro-context). */
13385
- onUserTurn(text) {
13594
+ onUserTurn(text, lastWordAgoMs = 0) {
13386
13595
  this.heard = text.trim();
13596
+ this.lastWordAgo = Math.max(0, lastWordAgoMs);
13387
13597
  this.floorWords = this.carried + words3(this.heard).length;
13388
13598
  this.lastUser = this.heard;
13389
13599
  this.detect(this.heard);
@@ -13408,7 +13618,7 @@ var SecondChannel = class {
13408
13618
  try {
13409
13619
  this.o.cancel();
13410
13620
  } catch (e) {
13411
- log27.warn(`cancel threw: ${e.message}`);
13621
+ log28.warn(`cancel threw: ${e.message}`);
13412
13622
  }
13413
13623
  }
13414
13624
  }
@@ -13451,12 +13661,19 @@ var SecondChannel = class {
13451
13661
  }
13452
13662
  /** Why this turn gets no bridge by rule ('' = it may) — language, a quick/phatic turn, or blocked(). */
13453
13663
  bridgeBlock() {
13454
- const w = words3(this.heard);
13664
+ const w = words3(this.heard), intent = turnIntent(this.heard);
13455
13665
  if (!this.lang) return "language unknown";
13456
- if (w.length <= 3 || w.length <= 6 && isPhatic(this.heard, this.bank)) return w.length <= 3 ? "quick turn" : "phatic";
13457
- if (w.length < this.o.bridgeMinWords) return `under ${this.o.bridgeMinWords} words`;
13666
+ if (intent === "closer") return "no answer due";
13667
+ if (this.late) return this.blocked("bridge");
13668
+ const why = intent === "ask" ? w.length <= 6 && isPhatic(this.heard, this.bank) ? "phatic" : "" : this.shortTurn(w.length);
13669
+ if (why) return this.o.lateBridgeMs > 0 ? `${why} (late bridge armed)` : why;
13458
13670
  return this.blocked("bridge");
13459
13671
  }
13672
+ shortTurn(n) {
13673
+ if (n <= 3 || n <= 6 && isPhatic(this.heard, this.bank)) return n <= 3 ? "quick turn" : "phatic";
13674
+ if (n < this.o.bridgeMinWords) return `under ${this.o.bridgeMinWords} words`;
13675
+ return "";
13676
+ }
13460
13677
  bcTimes = [];
13461
13678
  capped() {
13462
13679
  if (!this.o.maxPerWindow) return false;
@@ -13468,8 +13685,9 @@ var SecondChannel = class {
13468
13685
  rule(trigger, pauseMs = 0, breath = false, cue = null) {
13469
13686
  const w = words3(this.heard);
13470
13687
  if (!this.lang) return "silence";
13688
+ if (trigger === "working") return this.bridgeBlock() ? "silence" : "bridge";
13471
13689
  if (w.length <= 3 || w.length <= 6 && isPhatic(this.heard, this.bank)) return "silence";
13472
- const k = trigger === "interim" ? this.backchannelMoment(w.length, breath) && (!this.o.anticipate || !this.plan || !silentTurn(this.plan) && (!!cue || !this.o.cueOnly && this.plan.density !== "low")) ? "backchannel" : null : trigger === "pause" ? pauseMs >= this.o.fillerPauseMs && w.length >= this.o.fillerMinWords ? "filler" : null : w.length >= this.o.bridgeMinWords ? "bridge" : null;
13690
+ const k = trigger === "interim" ? this.backchannelMoment(w.length, breath) && (!this.o.anticipate || !this.plan || !silentTurn(this.plan) && (!!cue || !this.o.cueOnly && this.plan.density !== "low")) ? "backchannel" : null : trigger === "pause" ? pauseMs >= this.o.fillerPauseMs && w.length >= this.o.fillerMinWords ? "filler" : null : null;
13473
13691
  return k && this.allowed(k) ? k : "silence";
13474
13692
  }
13475
13693
  /** Plan (or, with `heard`, revise) the backchannels of the user's turn — async, never on the hot path. */
@@ -13512,7 +13730,7 @@ var SecondChannel = class {
13512
13730
  const opts = this.options;
13513
13731
  if (opts.mode === "chatty" && !opts.aec && !this.degradedLogged) {
13514
13732
  this.degradedLogged = true;
13515
- 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)");
13516
13734
  }
13517
13735
  const rule = this.rule(trigger, pauseMs, breath, cue);
13518
13736
  if (rule === "silence") {
@@ -13531,7 +13749,7 @@ var SecondChannel = class {
13531
13749
  const d = await withBudget(this.o.decide(this.tail(), { ...this.state(pauseMs), rule }, ac.signal), this.o.decisionBudgetMs, "channel decision", ac);
13532
13750
  choice = d?.choice;
13533
13751
  if (choice && choice in CHANNEL_ACTIONS && (choice === "silence" || this.allowed(choice))) kind = choice;
13534
- else if (choice && choice in CHANNEL_ACTIONS) 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`);
13535
13753
  }
13536
13754
  if (kind === "silence") return ev({ choice, acted: "silence" });
13537
13755
  if (stale()) return ev({ choice, kind, acted: "stale" });
@@ -13569,7 +13787,7 @@ var SecondChannel = class {
13569
13787
  void withBudget(this.o.generate(ctx, ac.signal), Math.max(this.o.generateBudgetMs, this.o.bridgeDelayMs), "bridge generate", ac).then((raw) => {
13570
13788
  const text = cleanLine(raw ?? "", ctx.lang);
13571
13789
  if (text && gen === this.gen) this.bridgeDraft = { gen, text };
13572
- }).catch((e) => log27.debug(`bridge prefetch failed: ${e?.message ?? e}`));
13790
+ }).catch((e) => log28.debug(`bridge prefetch failed: ${e?.message ?? e}`));
13573
13791
  }
13574
13792
  async line(kind, react = true) {
13575
13793
  if (kind === "bridge" && this.o.instantBridge) {
@@ -13585,7 +13803,7 @@ var SecondChannel = class {
13585
13803
  if (raw === null) ac.abort();
13586
13804
  const text = cleanLine(raw ?? "", this.lang);
13587
13805
  if (text && !this.recent.some((r) => norm3(r) === norm3(text))) return text;
13588
- if (text) log27.debug(`generated "${text}" repeats a recent line \u2014 canned`);
13806
+ if (text) log28.debug(`generated "${text}" repeats a recent line \u2014 canned`);
13589
13807
  }
13590
13808
  return this.pick(kind);
13591
13809
  }
@@ -13607,7 +13825,7 @@ var SecondChannel = class {
13607
13825
  try {
13608
13826
  r = this.o.speak(text, kind);
13609
13827
  } catch (e) {
13610
- log27.warn(`speak threw \u2014 line dropped: ${e.message}`);
13828
+ log28.warn(`speak threw \u2014 line dropped: ${e.message}`);
13611
13829
  return false;
13612
13830
  }
13613
13831
  if (r === false) return false;
@@ -13620,7 +13838,7 @@ var SecondChannel = class {
13620
13838
  this.o.show(text, kind);
13621
13839
  const p = Promise.resolve(r).then(() => {
13622
13840
  }, (e) => {
13623
- log27.warn(`channel line playback failed: ${e?.message ?? e}`);
13841
+ log28.warn(`channel line playback failed: ${e?.message ?? e}`);
13624
13842
  }).finally(() => {
13625
13843
  if (this.inflight === p) this.inflight = null;
13626
13844
  });
@@ -13678,7 +13896,7 @@ function closedQuestion(reply) {
13678
13896
  }
13679
13897
 
13680
13898
  // src/voice/call.ts
13681
- var log28 = forComponent("VoiceCall");
13899
+ var log29 = forComponent("VoiceCall");
13682
13900
  var VoiceCallOptions = class {
13683
13901
  /** Host runs MAIN with exactly `text` (a replace: roll the `replaces` turn back first). */
13684
13902
  sendTurn = () => {
@@ -13736,8 +13954,11 @@ var VoiceCall = class {
13736
13954
  aec: o.aec,
13737
13955
  // v3 §3: first sound <1.2s p50 from the last word — the endpoint takes ~0.6s and the bridge's TTS ~0.2s, so the bridge
13738
13956
  // goes 300ms after the turn if MAIN is still silent (it always is that early: warm MAIN's first token is ≥1s), no veto
13957
+ // A short question gets that bridge too (by intent, not length); any other turn expecting an answer that got none
13958
+ // gets a late one at 1.3s from the last word while MAIN is still silent (API first byte runs 1-8s); a closer, none.
13739
13959
  bridgeDelayMs: 300,
13740
13960
  instantBridge: true,
13961
+ lateBridgeMs: 1300,
13741
13962
  ...this.lang ? { lang: this.lang } : {},
13742
13963
  ...o.channel,
13743
13964
  // the ledger: a turn's bridge moment that stayed silent, and why (its wait then has no first sound until MAIN speaks)
@@ -13901,7 +14122,7 @@ var VoiceCall = class {
13901
14122
  try {
13902
14123
  this.o.cancel();
13903
14124
  } catch (e) {
13904
- log28.warn(`cancel threw: ${e.message}`);
14125
+ log29.warn(`cancel threw: ${e.message}`);
13905
14126
  }
13906
14127
  if (this.cur?.running) {
13907
14128
  this.cur.running = false;
@@ -13961,7 +14182,7 @@ var VoiceCall = class {
13961
14182
  if (this.cur) this.close(this.cur, t.replaces === this.cur.id ? "replaced" : void 0);
13962
14183
  this.follow(t.text);
13963
14184
  this.clearProgress();
13964
- this.cur = { id: t.id, text: t.text, at, finalAt, lastWord, running: true, streamed: false, sounded: false, worded: false, done: false, marks: { sent: now4 }, closed: false };
14185
+ this.cur = { id: t.id, text: t.text, at, finalAt, lastWord, running: true, streamed: false, sounded: false, worded: false, done: false, marks: { sent: now4 }, closed: false, due: turnIntent(t.text) !== "closer" };
13965
14186
  const early = this.earlyHeard;
13966
14187
  this.earlyHeard = null;
13967
14188
  if (early && early.at >= at) {
@@ -13971,7 +14192,7 @@ var VoiceCall = class {
13971
14192
  this.progress.turn();
13972
14193
  this.o.onMetric({ name: "turn", id: t.id, kind: t.kind, text: t.text });
13973
14194
  this.o.sendTurn(t.text, { id: t.id, ...t.replaces ? { replaces: t.replaces } : {} });
13974
- if (t.kind === "new") this.fill.onUserTurn(t.text);
14195
+ if (t.kind === "new") this.fill.onUserTurn(t.text, now4 - at);
13975
14196
  }
13976
14197
  onDrop(text, why) {
13977
14198
  if (!this.gate.holding) {
@@ -13988,7 +14209,7 @@ var VoiceCall = class {
13988
14209
  try {
13989
14210
  r = this.o.speak(text, kind);
13990
14211
  } catch (e) {
13991
- log28.warn(`speak threw: ${e.message}`);
14212
+ log29.warn(`speak threw: ${e.message}`);
13992
14213
  return false;
13993
14214
  }
13994
14215
  if (r === false) return false;
@@ -14010,7 +14231,7 @@ var VoiceCall = class {
14010
14231
  sounded(c, by, at = this.o.now()) {
14011
14232
  if (c.sounded) return;
14012
14233
  c.sounded = true;
14013
- this.o.onMetric({ name: "first_sound", id: c.id, ms: Math.round(at - c.at), by });
14234
+ this.o.onMetric(c.due ? { name: "first_sound", id: c.id, ms: Math.round(at - c.at), by } : { name: "first_sound", id: c.id, by, kind: "no answer due" });
14014
14235
  }
14015
14236
  /** The turn is over (answered + audible, replaced, the next turn, the call's end): its timeline, once. A turn whose MAIN
14016
14237
  * never became audible still reports first_word — with no ms and why (cut / replaced / silent) — so every turn has one. */
@@ -14030,7 +14251,8 @@ var VoiceCall = class {
14030
14251
  mainText: rel(m.mainText),
14031
14252
  mainAudio: rel(m.mainAudio),
14032
14253
  lastWord: c.lastWord,
14033
- end: c.end
14254
+ end: c.end,
14255
+ ...c.due ? {} : { due: false }
14034
14256
  };
14035
14257
  for (const k of Object.keys(tl)) if (tl[k] === void 0) delete tl[k];
14036
14258
  this.o.onMetric({ name: "timeline", id: c.id, text: c.text, timeline: tl });
@@ -14073,7 +14295,7 @@ init_logging();
14073
14295
  init_platform();
14074
14296
  import { spawn } from "child_process";
14075
14297
  import { createHash } from "crypto";
14076
- var log29 = forComponent("mcp");
14298
+ var log30 = forComponent("mcp");
14077
14299
  var PROTOCOL_VERSION = "2025-06-18";
14078
14300
  var DEFAULT_TIMEOUT_MS = 3e4;
14079
14301
  var DEFAULT_CALL_TIMEOUT_MS = 30 * 6e4;
@@ -14105,7 +14327,7 @@ var StdioTransport = class {
14105
14327
  proc.stdout.setEncoding("utf8");
14106
14328
  proc.stdout.on("data", (chunk) => this.onData(chunk));
14107
14329
  proc.stderr.setEncoding("utf8");
14108
- proc.stderr.on("data", (chunk) => log29.debug(`[${command}] stderr:`, chunk.trimEnd()));
14330
+ proc.stderr.on("data", (chunk) => log30.debug(`[${command}] stderr:`, chunk.trimEnd()));
14109
14331
  proc.on("exit", (code) => this.failAll(new Error(`MCP server "${command}" exited (code ${code})`)));
14110
14332
  proc.on("error", (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));
14111
14333
  }
@@ -14119,7 +14341,7 @@ var StdioTransport = class {
14119
14341
  try {
14120
14342
  this.dispatch(JSON.parse(line));
14121
14343
  } catch (e) {
14122
- log29.debug("dropping non-JSON line from MCP server:", line, e);
14344
+ log30.debug("dropping non-JSON line from MCP server:", line, e);
14123
14345
  }
14124
14346
  }
14125
14347
  }
@@ -14129,7 +14351,7 @@ var StdioTransport = class {
14129
14351
  try {
14130
14352
  p2?.onProgress?.(msg.params ?? {});
14131
14353
  } catch (e) {
14132
- log29.debug("onProgress handler threw", e);
14354
+ log30.debug("onProgress handler threw", e);
14133
14355
  }
14134
14356
  return;
14135
14357
  }
@@ -14167,7 +14389,7 @@ var StdioTransport = class {
14167
14389
  const onAbort = () => {
14168
14390
  if (!this.pending.delete(id)) return;
14169
14391
  clearTimeout(timer);
14170
- this.notify("notifications/cancelled", { requestId: id, reason: String(signal?.reason?.message ?? "cancelled") }).catch((e) => 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));
14171
14393
  reject(abortError(signal));
14172
14394
  };
14173
14395
  const done = () => signal?.removeEventListener("abort", onAbort);
@@ -14191,7 +14413,7 @@ var StdioTransport = class {
14191
14413
  try {
14192
14414
  this.proc?.stdin?.end();
14193
14415
  } catch (e) {
14194
- log29.debug("stdin end failed", e);
14416
+ log30.debug("stdin end failed", e);
14195
14417
  }
14196
14418
  if (!(IS_WIN && killTree(this.proc?.pid, "SIGTERM"))) this.proc?.kill();
14197
14419
  }
@@ -14226,7 +14448,7 @@ var HttpTransport = class {
14226
14448
  return await this.post({ jsonrpc: "2.0", id, method, params: opts.onProgress ? withProgressToken(params, id) : params }, false, signal, opts.onProgress);
14227
14449
  } catch (e) {
14228
14450
  if (opts.signal?.aborted) {
14229
- void this.notify("notifications/cancelled", { requestId: id, reason: String(opts.signal.reason?.message ?? "cancelled") }).catch((err2) => 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));
14230
14452
  throw abortError(opts.signal);
14231
14453
  }
14232
14454
  throw e;
@@ -14269,7 +14491,7 @@ function sseMessage(line) {
14269
14491
  try {
14270
14492
  return JSON.parse(trimmed.slice(5).trim());
14271
14493
  } catch (e) {
14272
- log29.debug("skipping unparseable SSE data line", e);
14494
+ log30.debug("skipping unparseable SSE data line", e);
14273
14495
  return void 0;
14274
14496
  }
14275
14497
  }
@@ -14279,7 +14501,7 @@ async function readSseResponse(resp, id, onProgress) {
14279
14501
  try {
14280
14502
  onProgress?.(obj.params ?? {});
14281
14503
  } catch (e) {
14282
- log29.debug("onProgress handler threw", e);
14504
+ log30.debug("onProgress handler threw", e);
14283
14505
  }
14284
14506
  return void 0;
14285
14507
  }
@@ -14378,7 +14600,7 @@ async function mountWithDeadline(name, cfg, mountTimeoutMs) {
14378
14600
  return { name, client, tools, specs, serverInfo: init?.serverInfo, config: cfg };
14379
14601
  })(), mountTimeoutMs, name);
14380
14602
  } catch (e) {
14381
- await client.close().catch((err2) => log29.debug(`close after failed mount of "${name}": ${err2}`));
14603
+ await client.close().catch((err2) => log30.debug(`close after failed mount of "${name}": ${err2}`));
14382
14604
  throw e;
14383
14605
  }
14384
14606
  }
@@ -14389,15 +14611,15 @@ function validEntries(servers) {
14389
14611
  return Object.entries(servers).filter(([name, cfg]) => {
14390
14612
  if (!cfg || cfg.disabled) return false;
14391
14613
  if (!cfg.command && !cfg.url) {
14392
- 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`);
14393
14615
  return false;
14394
14616
  }
14395
14617
  return true;
14396
14618
  });
14397
14619
  }
14398
14620
  function logMountFailure(name, e) {
14399
- if (e instanceof McpAuthError) log29.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
14400
- 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}`);
14401
14623
  }
14402
14624
  async function mountMcpServers(servers = {}, opts = {}) {
14403
14625
  const entries = validEntries(servers);
@@ -14407,7 +14629,7 @@ async function mountMcpServers(servers = {}, opts = {}) {
14407
14629
  const name = entries[i][0];
14408
14630
  if (r.status === "fulfilled") {
14409
14631
  out.push(r.value);
14410
- 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}` : ""}`);
14411
14633
  } else logMountFailure(name, r.reason);
14412
14634
  });
14413
14635
  return out;
@@ -14447,7 +14669,7 @@ var McpPool = class {
14447
14669
  const prev = this.warm.get(key);
14448
14670
  if (prev) {
14449
14671
  clearTimeout(prev.timer);
14450
- if (prev.client !== client) void prev.client.close().catch((err2) => 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}`));
14451
14673
  }
14452
14674
  const e = { client, timer: void 0 };
14453
14675
  this.warm.set(key, e);
@@ -14464,7 +14686,7 @@ var McpPool = class {
14464
14686
  const e = this.warm.get(key);
14465
14687
  if (!e) return;
14466
14688
  this.warm.delete(key);
14467
- await e.client.close().catch((err2) => log29.debug(`warm-pool evict close failed: ${err2}`));
14689
+ await e.client.close().catch((err2) => log30.debug(`warm-pool evict close failed: ${err2}`));
14468
14690
  }
14469
14691
  async closeAll() {
14470
14692
  for (const e of this.warm.values()) {
@@ -14683,7 +14905,7 @@ init_tools_shell();
14683
14905
  // src/tools.notify.ts
14684
14906
  init_logging();
14685
14907
  import { execFile as execFile2 } from "child_process";
14686
- var log31 = forComponent("notify");
14908
+ var log32 = forComponent("notify");
14687
14909
  var WIN_TOAST = [
14688
14910
  "[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null",
14689
14911
  "$t = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)",
@@ -14714,7 +14936,7 @@ function makeNotifyTool(opts = {}) {
14714
14936
  return new Promise((resolve6) => {
14715
14937
  run(argv[0], argv[1], { timeout: 5e3, ...extra }, (e) => {
14716
14938
  if (e) {
14717
- log31.debug("notification failed", e);
14939
+ log32.debug("notification failed", e);
14718
14940
  resolve6(`Notification failed: ${e.message}`);
14719
14941
  } else resolve6("Notification shown.");
14720
14942
  });
@@ -14730,7 +14952,7 @@ import { BodDB as BodDB2 } from "@bod.ee/db";
14730
14952
  init_logging();
14731
14953
  import { existsSync as existsSync2, readFileSync as readFileSync5 } from "fs";
14732
14954
  import { homedir } from "os";
14733
- var log32 = forComponent("cli-util");
14955
+ var log33 = forComponent("cli-util");
14734
14956
  function dotDirs(base, sub, opts = {}) {
14735
14957
  const home = opts.home ?? homedir();
14736
14958
  const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
@@ -14747,7 +14969,7 @@ function parseJson(text, fallback, what = "json") {
14747
14969
  try {
14748
14970
  return JSON.parse(text);
14749
14971
  } catch (e) {
14750
- log32.debug(`parseJson(${what}) failed: ${e.message}`);
14972
+ log33.debug(`parseJson(${what}) failed: ${e.message}`);
14751
14973
  return fallback;
14752
14974
  }
14753
14975
  }
@@ -14757,7 +14979,7 @@ function readJsonFile(path, fallback) {
14757
14979
  try {
14758
14980
  text = readFileSync5(path, "utf8");
14759
14981
  } catch (e) {
14760
- log32.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
14982
+ log33.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
14761
14983
  return fallback;
14762
14984
  }
14763
14985
  return parseJson(text, fallback, path);
@@ -15061,7 +15283,7 @@ import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as rea
15061
15283
  import { homedir as homedir3 } from "os";
15062
15284
  import { dirname as dirname4, join as join8 } from "path";
15063
15285
  import { fileURLToPath } from "url";
15064
- var log33 = forComponent("VoiceIO");
15286
+ var log34 = forComponent("VoiceIO");
15065
15287
  var now3 = () => performance.now();
15066
15288
  var Player = class {
15067
15289
  proc = null;
@@ -15075,7 +15297,7 @@ var Player = class {
15075
15297
  ["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
15076
15298
  { stdio: ["pipe", "ignore", "ignore"] }
15077
15299
  );
15078
- this.proc.on("error", (e) => log33.warn(`ffplay error: ${e.message}`));
15300
+ this.proc.on("error", (e) => log34.warn(`ffplay error: ${e.message}`));
15079
15301
  this.proc.stdin.on("error", () => {
15080
15302
  });
15081
15303
  this.bytesWritten = 0;
@@ -15113,7 +15335,7 @@ function detectFfmpegMic() {
15113
15335
  const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
15114
15336
  const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
15115
15337
  if (!mic) throw new Error("no audio input device found");
15116
- log33.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
15338
+ log34.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
15117
15339
  return `:${mic.idx}`;
15118
15340
  }
15119
15341
  function detectDshowMic() {
@@ -15124,7 +15346,7 @@ function detectDshowMic() {
15124
15346
  const devices = [...(r.stderr ?? "").matchAll(/"([^"]+)"\s*\(audio\)/g)].map((m) => m[1]);
15125
15347
  const mic = devices.find((d) => !/stereo mix|loopback|virtual|cable|teams/i.test(d)) ?? devices[0];
15126
15348
  if (!mic) throw new Error("no audio input device found (set MIC_DEVICE to the DirectShow device name)");
15127
- log33.debug(`ffmpeg dshow mic: ${mic}`);
15349
+ log34.debug(`ffmpeg dshow mic: ${mic}`);
15128
15350
  return `audio=${mic}`;
15129
15351
  }
15130
15352
  function detectedInputDevice() {
@@ -15160,15 +15382,15 @@ function resolveAecBinary() {
15160
15382
  if (existsSync4(bin) && statSync6(bin).mtimeMs >= statSync6(src).mtimeMs) return bin;
15161
15383
  if (spawnSync3("which", ["swiftc"]).status !== 0) return null;
15162
15384
  mkdirSync5(cacheDir, { recursive: true });
15163
- log33.info("compiling AEC mic helper (first run)\u2026");
15385
+ log34.info("compiling AEC mic helper (first run)\u2026");
15164
15386
  const build = spawnSync3("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
15165
15387
  if (build.status !== 0) {
15166
- log33.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
15388
+ log34.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
15167
15389
  return null;
15168
15390
  }
15169
15391
  const sign = spawnSync3("codesign", ["-fs", "-", bin], { encoding: "utf8" });
15170
15392
  if (sign.status !== 0) {
15171
- log33.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
15393
+ log34.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
15172
15394
  return null;
15173
15395
  }
15174
15396
  return bin;
@@ -15210,17 +15432,17 @@ var NodeMicSource = class {
15210
15432
  this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
15211
15433
  } else {
15212
15434
  if (!hasCommand("ffmpeg")) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
15213
- 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");
15214
15436
  const input = process.platform === "win32" ? ["-f", "dshow", "-i", detectDshowMic()] : ["-f", "avfoundation", "-i", detectFfmpegMic()];
15215
15437
  this.proc = spawn2(
15216
15438
  "ffmpeg",
15217
15439
  ["-loglevel", "error", ...input, "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
15218
15440
  { stdio: ["ignore", "pipe", "pipe"], ...process.platform === "win32" ? { windowsHide: true } : {} }
15219
15441
  );
15220
- this.proc.stderr.on("data", (d) => log33.warn(`ffmpeg: ${String(d).trim()}`));
15442
+ this.proc.stderr.on("data", (d) => log34.warn(`ffmpeg: ${String(d).trim()}`));
15221
15443
  }
15222
15444
  this.proc.on("exit", (c) => {
15223
- if (c && !this.stopped) 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`);
15224
15446
  });
15225
15447
  this.proc.stdout.on("data", (chunk) => onChunk(chunk));
15226
15448
  }
@@ -15286,7 +15508,7 @@ var AecDuplexAudio = class {
15286
15508
  this.proc.stdin.on("error", () => {
15287
15509
  });
15288
15510
  this.proc.on("exit", (c) => {
15289
- if (c && !this.stopped) 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`);
15290
15512
  });
15291
15513
  this.proc.stdout.on("data", (chunk) => {
15292
15514
  this.gotChunk = true;
@@ -15302,7 +15524,7 @@ var AecDuplexAudio = class {
15302
15524
  openMicSettings();
15303
15525
  this.onFatal?.("microphone permission denied \u2014 enable it in System Settings \u2192 Privacy & Security \u2192 Microphone for your terminal, then restart it");
15304
15526
  }
15305
- } else log33.debug(`mic-aec: ${s}`);
15527
+ } else log34.debug(`mic-aec: ${s}`);
15306
15528
  }
15307
15529
  });
15308
15530
  if (!this.noVpio && !this.triedFallback) {
@@ -15311,7 +15533,7 @@ var AecDuplexAudio = class {
15311
15533
  this.triedFallback = true;
15312
15534
  this.noVpio = true;
15313
15535
  this._aec = false;
15314
- 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)");
15315
15537
  this.onDegrade?.();
15316
15538
  this.killProc();
15317
15539
  this.spawnHelper();
@@ -15482,7 +15704,7 @@ function bindAckInstinct(v, instinct) {
15482
15704
  if (ev.kind === "ack_fired") instinct.ackOutcome(String(ev.phrase));
15483
15705
  else if (ev.kind === "ack_skipped" || ev.kind === "ack_cancelled") instinct.ackOutcome(null);
15484
15706
  } catch (e) {
15485
- 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}`);
15486
15708
  }
15487
15709
  onDiag(ev);
15488
15710
  };
@@ -15604,7 +15826,7 @@ function speakBlip(v, text) {
15604
15826
  v.fireBackchannel();
15605
15827
  return true;
15606
15828
  } catch (e) {
15607
- log33.warn(`backchannel blip failed \u2014 line dropped: ${e.message}`);
15829
+ log34.warn(`backchannel blip failed \u2014 line dropped: ${e.message}`);
15608
15830
  return false;
15609
15831
  } finally {
15610
15832
  E.BACKCHANNELS = pool;
@@ -15623,7 +15845,7 @@ function channelInput(sc, m) {
15623
15845
  }
15624
15846
  if (m.type === "agent_state") {
15625
15847
  if (typeof m.state === "string" && MAIN_STATES.has(m.state)) sc.onAgentState(m.state);
15626
- else 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`);
15627
15849
  return true;
15628
15850
  }
15629
15851
  return false;
@@ -15749,7 +15971,7 @@ function fakeVoiceParts(uttFile) {
15749
15971
  init_logging();
15750
15972
  import { appendFile, mkdirSync as mkdirSync6 } from "fs";
15751
15973
  import { dirname as dirname5 } from "path";
15752
- var log34 = forComponent("VoiceDiag");
15974
+ var log35 = forComponent("VoiceDiag");
15753
15975
  var JsonlDiagSink = class {
15754
15976
  constructor(path) {
15755
15977
  this.path = path;
@@ -15790,7 +16012,7 @@ var JsonlDiagSink = class {
15790
16012
  disable(e) {
15791
16013
  this.disabled = true;
15792
16014
  this.queue.length = 0;
15793
- 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}`);
15794
16016
  }
15795
16017
  /** True once a failure permanently disabled the sink (tests/inspection). */
15796
16018
  get isDisabled() {
@@ -15948,7 +16170,7 @@ function applyOverlay(cfg, file) {
15948
16170
  // cli/hooks-config.ts
15949
16171
  import { spawnSync as spawnSync4 } from "child_process";
15950
16172
  init_platform();
15951
- var log35 = forComponent("hooks");
16173
+ var log36 = forComponent("hooks");
15952
16174
  var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
15953
16175
  function ruleMatches(rule, toolName) {
15954
16176
  if (!rule.tool || rule.tool === "*") return true;
@@ -15967,7 +16189,7 @@ function runCmd(rule, env) {
15967
16189
  });
15968
16190
  return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
15969
16191
  } catch (e) {
15970
- log35.debug(`hook command failed: ${rule.command}`, e);
16192
+ log36.debug(`hook command failed: ${rule.command}`, e);
15971
16193
  return { code: 1, out: String(e?.message ?? e) };
15972
16194
  }
15973
16195
  }
@@ -16075,7 +16297,7 @@ import { existsSync as existsSync6, mkdirSync as mkdirSync8, readFileSync as rea
16075
16297
  import { homedir as homedir6 } from "os";
16076
16298
  import { join as join11 } from "path";
16077
16299
  init_platform();
16078
- var log36 = forComponent("session");
16300
+ var log37 = forComponent("session");
16079
16301
  var globalDir = () => join11(homedir6(), ".agent", "sessions");
16080
16302
  function indexLink(target, link2) {
16081
16303
  try {
@@ -16138,7 +16360,7 @@ var SessionStore = class {
16138
16360
  }
16139
16361
  load(id) {
16140
16362
  if (!this.safeId(id)) {
16141
- log36.debug(`rejecting unsafe session id: ${id}`);
16363
+ log37.debug(`rejecting unsafe session id: ${id}`);
16142
16364
  return void 0;
16143
16365
  }
16144
16366
  const path = join11(this.dir, `${id}.json`);
@@ -16146,7 +16368,7 @@ var SessionStore = class {
16146
16368
  try {
16147
16369
  return JSON.parse(readFileSync8(path, "utf8"));
16148
16370
  } catch (e) {
16149
- log36.debug(`unreadable session ${id} \u2014 ignoring`, e);
16371
+ log37.debug(`unreadable session ${id} \u2014 ignoring`, e);
16150
16372
  return void 0;
16151
16373
  }
16152
16374
  }
@@ -16159,7 +16381,7 @@ var SessionStore = class {
16159
16381
  try {
16160
16382
  metas.push(JSON.parse(readFileSync8(join11(this.dir, f), "utf8")).meta);
16161
16383
  } catch (e) {
16162
- log36.debug(`skipping unreadable session file ${f}`, e);
16384
+ log37.debug(`skipping unreadable session file ${f}`, e);
16163
16385
  }
16164
16386
  }
16165
16387
  return metas.sort((a, b) => b.updated - a.updated);
@@ -16295,6 +16517,199 @@ ${body || "(no conversation)"}
16295
16517
  (That is everything recorded. Where it does not say why something was done or what was meant, say you do not know \u2014 never supply a reason.)`;
16296
16518
  }
16297
16519
 
16520
+ // src/directives.ts
16521
+ init_models();
16522
+ var VENDOR_SHORTHAND = { cc: "claude-code", cur: "cursor", oa: "openai" };
16523
+ var DIRECTIVE_KEYS = ["model", "turns", "thinking", "think", "effort", "engine", "resume", "delegate", "advisor", "mode"];
16524
+ function splitModePair(token) {
16525
+ const i = token.search(/->|<-/);
16526
+ if (i === -1) return null;
16527
+ const right = token.slice(i + 2).trim();
16528
+ return right ? { left: token.slice(0, i).trim(), op: token.slice(i, i + 2), right } : null;
16529
+ }
16530
+ var isModel = (t, p) => {
16531
+ const h = resolveModelToken(t, p);
16532
+ return !!h && "model" in h;
16533
+ };
16534
+ var defaultResolver = null;
16535
+ var opt = (o) => ({
16536
+ resolveModel: defaultResolver,
16537
+ resolverFirst: false,
16538
+ aliases: MODEL_ALIASES,
16539
+ vendors: VENDOR_SHORTHAND,
16540
+ keys: DIRECTIVE_KEYS,
16541
+ strict: false,
16542
+ catalogOnly: false,
16543
+ warn: (m) => console.warn(m),
16544
+ ...o
16545
+ });
16546
+ var DIRECTIVE_RE = /^\s*\[([^\]]*)\](?=\s|$)\s*([\s\S]*)/;
16547
+ var isQualifiedModel = (v) => /^[a-z0-9._-]+\/[a-z0-9./_-]+$/.test(v);
16548
+ function expandVendor(token, vendors = VENDOR_SHORTHAND) {
16549
+ const v = token.trim().toLowerCase();
16550
+ const m = v.match(/^([a-z0-9]+)[-/](.+)$/);
16551
+ if (m && vendors[m[1]]) return `${vendors[m[1]]}/${m[2]}`;
16552
+ const slash = v.indexOf("/");
16553
+ return slash > 0 && Object.values(vendors).includes(v.slice(0, slash)) ? v : null;
16554
+ }
16555
+ function resolveModelToken(token, o) {
16556
+ const p = opt(o);
16557
+ const v = token.trim().toLowerCase();
16558
+ const vendored = expandVendor(v, p.vendors);
16559
+ if (vendored) return p.resolveModel ? p.resolveModel(vendored) : p.catalogOnly ? null : { model: vendored };
16560
+ if (p.catalogOnly) return v.includes("/") || p.aliases[v] || /\d/.test(v) ? p.resolveModel?.(v) ?? null : null;
16561
+ const alias = p.aliases[v] ? { model: p.aliases[v] } : null;
16562
+ if (!p.resolverFirst) return alias ?? p.resolveModel?.(v) ?? null;
16563
+ const hit = p.resolveModel?.(v) ?? null;
16564
+ return hit && "model" in hit ? hit : alias ?? hit;
16565
+ }
16566
+ var THINK = ["think", "adaptive", "nothink", "nothinking"];
16567
+ function isDirectiveGroup(raw, o) {
16568
+ const p = opt(o);
16569
+ const tokens = raw.split(",").map((t) => t.trim()).filter(Boolean);
16570
+ if (!tokens.length) return false;
16571
+ return tokens.every((t) => {
16572
+ const i = t.indexOf(":");
16573
+ if (i !== -1) return p.keys.includes(t.slice(0, i).trim().toLowerCase());
16574
+ const v = t.toLowerCase();
16575
+ const pair = splitModePair(v);
16576
+ if (pair) return p.keys.includes(pair.op === "->" ? "delegate" : "advisor") && (!!pair.left && isModel(pair.left, p) || isModel(pair.right, p));
16577
+ if (v === "off") return p.keys.includes("mode");
16578
+ return !!resolveModelToken(v, p) || p.catalogOnly && !!p.aliases[v] || p.keys.includes("turns") && /^\d+$/.test(v) || p.keys.includes("thinking") && THINK.includes(v);
16579
+ });
16580
+ }
16581
+ function splitDirectiveGroups(text, o) {
16582
+ const p = opt(o);
16583
+ let rest = text;
16584
+ const groups = [];
16585
+ for (; ; ) {
16586
+ const m = rest.match(DIRECTIVE_RE);
16587
+ if (!m) break;
16588
+ const g = m[1].trim();
16589
+ if ((groups.length || p.strict) && g && !isDirectiveGroup(g, p)) break;
16590
+ groups.push(g);
16591
+ rest = m[2];
16592
+ }
16593
+ return { groups, body: rest };
16594
+ }
16595
+ function parseDirectives(text, o) {
16596
+ const p = opt(o);
16597
+ const { groups, body } = splitDirectiveGroups(text, p);
16598
+ if (!groups.length) return { prompt: text, directives: {} };
16599
+ const prompt = body.trim();
16600
+ const raw = groups.filter(Boolean).join(",");
16601
+ if (!raw) return { prompt, directives: {} };
16602
+ const d = {};
16603
+ const out = { prompt, directives: d };
16604
+ const tokens = raw.split(",");
16605
+ const proven = tokens.some((t) => {
16606
+ const i = t.indexOf(":");
16607
+ return i !== -1 && p.keys.includes(t.slice(0, i).trim().toLowerCase());
16608
+ });
16609
+ const fail = (tok, hit) => {
16610
+ out.unresolvedModel ??= tok;
16611
+ if (hit && "ambiguous" in hit) out.ambiguous ??= hit.ambiguous;
16612
+ };
16613
+ const setModel = (val) => {
16614
+ const hit = resolveModelToken(val, p);
16615
+ if (!hit || !("model" in hit)) return hit;
16616
+ d.model = hit.model;
16617
+ if (hit.engine && !d.engine) d.engine = hit.engine;
16618
+ return true;
16619
+ };
16620
+ let pos = 0;
16621
+ for (const token of tokens) {
16622
+ const t = token.trim();
16623
+ if (!t) continue;
16624
+ const i = t.indexOf(":");
16625
+ if (i !== -1) {
16626
+ const key = t.slice(0, i).trim().toLowerCase(), val2 = t.slice(i + 1).trim().toLowerCase();
16627
+ if (!p.keys.includes(key)) {
16628
+ p.warn(`[directives] Unknown directive key: "${key}"`);
16629
+ continue;
16630
+ }
16631
+ if (key === "delegate" || key === "advisor") {
16632
+ const hit = resolveModelToken(val2, p);
16633
+ if (hit && "model" in hit) d[key] = hit.model;
16634
+ else if (!hit && isQualifiedModel(val2) && !p.catalogOnly) d[key] = val2;
16635
+ else fail(val2, hit);
16636
+ continue;
16637
+ }
16638
+ if (key === "model") {
16639
+ const r2 = setModel(val2);
16640
+ if (r2 === true) continue;
16641
+ if (!r2 && isQualifiedModel(val2) && !p.catalogOnly) {
16642
+ d.model = val2;
16643
+ continue;
16644
+ }
16645
+ fail(val2, r2);
16646
+ } else applyKey(d, key, val2, p.warn);
16647
+ continue;
16648
+ }
16649
+ const val = t.toLowerCase();
16650
+ const pair = splitModePair(val);
16651
+ if (pair && p.keys.includes(pair.op === "->" ? "delegate" : "advisor")) {
16652
+ const lhit = pair.left ? resolveModelToken(pair.left, p) : null, rhit = resolveModelToken(pair.right, p);
16653
+ const lok = !pair.left || !!lhit && "model" in lhit, rok = !!rhit && "model" in rhit;
16654
+ if (lok && rok) {
16655
+ if (pair.left) setModel(pair.left);
16656
+ if (pair.op === "->") d.delegate = rhit.model;
16657
+ else d.advisor = rhit.model;
16658
+ } else if (lok || rok) {
16659
+ if (!lok) fail(pair.left, lhit);
16660
+ else fail(pair.right, rhit);
16661
+ } else p.warn(`[directives] Unknown models in "${t}"`);
16662
+ pos++;
16663
+ continue;
16664
+ }
16665
+ if (val === "off" && p.keys.includes("mode")) {
16666
+ d.modeOff = true;
16667
+ pos++;
16668
+ continue;
16669
+ }
16670
+ const r = pos === 0 ? setModel(val) : null;
16671
+ if (r === true) {
16672
+ } else if (pos <= 1 && /^\d+$/.test(val) && p.keys.includes("turns")) d.turns = parseInt(val, 10);
16673
+ else if (["think", "adaptive"].includes(val) && p.keys.includes("thinking")) d.thinking = "adaptive";
16674
+ else if (["nothink", "nothinking"].includes(val) && p.keys.includes("thinking")) d.thinking = "disabled";
16675
+ else if (pos === 0) {
16676
+ if (r || p.catalogOnly && p.aliases[val] || proven && !isQualifiedModel(val)) fail(t, r);
16677
+ p.warn(`[directives] Unknown model: "${t}"`);
16678
+ }
16679
+ pos++;
16680
+ }
16681
+ return out;
16682
+ }
16683
+ function applyKey(d, key, val, warn) {
16684
+ switch (key) {
16685
+ case "turns":
16686
+ if (/^\d+$/.test(val)) d.turns = parseInt(val, 10);
16687
+ else warn(`[directives] Invalid turns value: "${val}"`);
16688
+ break;
16689
+ case "thinking":
16690
+ case "think":
16691
+ if (["adaptive", "enabled", "disabled"].includes(val)) d.thinking = val;
16692
+ else warn(`[directives] Invalid thinking value: "${val}"`);
16693
+ break;
16694
+ case "effort":
16695
+ if (["low", "medium", "high", "max"].includes(val)) d.effort = val;
16696
+ else warn(`[directives] Invalid effort value: "${val}"`);
16697
+ break;
16698
+ case "engine":
16699
+ d.engine = val;
16700
+ break;
16701
+ case "mode":
16702
+ if (val === "off") d.modeOff = true;
16703
+ else warn(`[directives] Invalid mode value: "${val}"`);
16704
+ break;
16705
+ case "resume":
16706
+ if (["on", "true", "1"].includes(val)) d.resume = true;
16707
+ else if (["off", "false", "0"].includes(val)) d.resume = false;
16708
+ else warn(`[directives] Invalid resume value: "${val}"`);
16709
+ break;
16710
+ }
16711
+ }
16712
+
16298
16713
  // cli/checkpoints.ts
16299
16714
  var MUTATORS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "ApplyEdits"]);
16300
16715
  function mutatedPaths(call) {
@@ -16398,7 +16813,7 @@ import { promisify } from "util";
16398
16813
  import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync9 } from "fs";
16399
16814
  import { homedir as homedir7 } from "os";
16400
16815
  import { join as join12, resolve as resolve4, sep as sep3, parse as parsePath } from "path";
16401
- var log37 = forComponent("checkpoints");
16816
+ var log38 = forComponent("checkpoints");
16402
16817
  var exec = promisify(execFile4);
16403
16818
  var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
16404
16819
  function isBroadRoot(p) {
@@ -16446,7 +16861,7 @@ var ShadowRepo = class {
16446
16861
  writeFileSync6(join12(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
16447
16862
  this.ready = true;
16448
16863
  } catch (e) {
16449
- log37.debug(`git checkpoints unavailable for ${this.workTree}`, e);
16864
+ log38.debug(`git checkpoints unavailable for ${this.workTree}`, e);
16450
16865
  this.ready = false;
16451
16866
  }
16452
16867
  return this.ready;
@@ -16457,7 +16872,7 @@ var ShadowRepo = class {
16457
16872
  }
16458
16873
  async commit(label, forced = []) {
16459
16874
  await this.run("add", "-A");
16460
- for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => 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));
16461
16876
  await this.run("commit", "--allow-empty", "-q", "-m", label);
16462
16877
  }
16463
16878
  /** Inject the CURRENT (pre-edit) content of `paths` into the turn-open restore point by amending it.
@@ -16465,8 +16880,8 @@ var ShadowRepo = class {
16465
16880
  * turn-boundary `add -A` would never have captured it. Amend (vs a new commit) keeps one restore
16466
16881
  * point per turn, so the REPL's turn↔frame mapping stays intact. */
16467
16882
  async amendForced(paths) {
16468
- for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log37.debug(`force-capture failed: ${p}`, e));
16469
- 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));
16470
16885
  }
16471
16886
  /** Commits on `ref`, oldest-first (canonical index space). */
16472
16887
  async log(ref) {
@@ -16526,7 +16941,7 @@ var ShadowRepo = class {
16526
16941
  await this.run("gc", "--auto", "-q").catch(() => {
16527
16942
  });
16528
16943
  } catch (e) {
16529
- log37.debug("checkpoint prune failed", e);
16944
+ log38.debug("checkpoint prune failed", e);
16530
16945
  }
16531
16946
  }
16532
16947
  };
@@ -16594,7 +17009,7 @@ var GitCheckpoints = class {
16594
17009
  use(sessionId) {
16595
17010
  if (sessionId === this.session) return;
16596
17011
  this.session = sessionId;
16597
- if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => 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));
16598
17013
  }
16599
17014
  async begin(label) {
16600
17015
  if (!await this.start()) return;
@@ -16614,7 +17029,7 @@ var GitCheckpoints = class {
16614
17029
  process.stderr.write(`\x1B[2m checkpoints: '${this.repos[i].root}' too large to snapshot in ${Math.round(this.options.opTimeoutMs / 1e3)}s \u2014 file rewind disabled here\x1B[0m
16615
17030
  `);
16616
17031
  } else {
16617
- log37.debug("checkpoint commit failed", e);
17032
+ log38.debug("checkpoint commit failed", e);
16618
17033
  survivors.push(this.repos[i]);
16619
17034
  survivorCaches.push(this.caches[i] ?? []);
16620
17035
  }
@@ -16649,7 +17064,7 @@ var GitCheckpoints = class {
16649
17064
  if (this.forced.has(abs)) continue;
16650
17065
  this.forced.add(abs);
16651
17066
  const i = this.repoIndexFor(abs);
16652
- if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log37.debug("amendForced failed", e));
17067
+ if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log38.debug("amendForced failed", e));
16653
17068
  }
16654
17069
  }
16655
17070
  };
@@ -17760,7 +18175,7 @@ function applyVimNormal(s, key, str) {
17760
18175
  }
17761
18176
  return "none";
17762
18177
  }
17763
- function applyKey(s, key, str) {
18178
+ function applyKey2(s, key, str) {
17764
18179
  const k = key?.name;
17765
18180
  if (s.vim === "normal" && !s.pasting && !s.searching) {
17766
18181
  const r = applyVimNormal(s, key ?? {}, str);
@@ -18088,7 +18503,7 @@ function createLineEditor(out, input = keyInput) {
18088
18503
  });
18089
18504
  return;
18090
18505
  }
18091
- const action = applyKey(s, key ?? {}, str);
18506
+ const action = applyKey2(s, key ?? {}, str);
18092
18507
  if (action === "submit") {
18093
18508
  finish();
18094
18509
  opts.onSubmit?.();
@@ -18434,7 +18849,7 @@ import { spawnSync as spawnSync6 } from "child_process";
18434
18849
  import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync7 } from "fs";
18435
18850
  import { homedir as homedir8 } from "os";
18436
18851
  import { join as join14 } from "path";
18437
- var log38 = forComponent("os-sched");
18852
+ var log39 = forComponent("os-sched");
18438
18853
  var OsScheduler = class {
18439
18854
  options;
18440
18855
  constructor(options) {
@@ -18496,7 +18911,7 @@ var OsScheduler = class {
18496
18911
  }
18497
18912
  }
18498
18913
  } catch (e) {
18499
- log38.debug(`cancel ${id}`, e);
18914
+ log39.debug(`cancel ${id}`, e);
18500
18915
  }
18501
18916
  for (const f of [`${id}.json`, `${id}.sh`]) {
18502
18917
  try {
@@ -18614,7 +19029,7 @@ import { existsSync as existsSync8, mkdirSync as mkdirSync11, unlinkSync as unli
18614
19029
  import { homedir as homedir9 } from "os";
18615
19030
  import { join as join15 } from "path";
18616
19031
  init_platform();
18617
- var log39 = forComponent("remote-trigger");
19032
+ var log40 = forComponent("remote-trigger");
18618
19033
  var TRIGGER_DIR = () => join15(homedir9(), ".agent", "triggers");
18619
19034
  var sockPath = (sessionId, dir = TRIGGER_DIR()) => join15(dir, `${sessionId}.sock`);
18620
19035
  var TriggerServer = class {
@@ -18652,13 +19067,13 @@ var TriggerServer = class {
18652
19067
  conn.end(JSON.stringify({ ok: false, error: String(e) }) + "\n");
18653
19068
  }
18654
19069
  });
18655
- conn.on("error", (e) => log39.debug("trigger conn error", e));
19070
+ conn.on("error", (e) => log40.debug("trigger conn error", e));
18656
19071
  });
18657
- this.server.on("error", (e) => log39.debug("trigger server error", e));
19072
+ this.server.on("error", (e) => log40.debug("trigger server error", e));
18658
19073
  this.server.listen(p);
18659
19074
  this.path = p;
18660
19075
  } catch (e) {
18661
- log39.debug("trigger server unavailable", e);
19076
+ log40.debug("trigger server unavailable", e);
18662
19077
  }
18663
19078
  }
18664
19079
  /** Re-bind on /resume (the session id changed). */
@@ -18790,7 +19205,7 @@ var strike = C("9");
18790
19205
  var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
18791
19206
  var mdPalette = { bold, dim, cyan, italic, strike, green, link };
18792
19207
  var err = (s) => process.stderr.write(s);
18793
- var log40 = forComponent("cli");
19208
+ var log41 = forComponent("cli");
18794
19209
  var VERSION = (() => {
18795
19210
  try {
18796
19211
  return JSON.parse(readFileSync10(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
@@ -18930,7 +19345,13 @@ function parseArgs(argv) {
18930
19345
  a.harden = true;
18931
19346
  a.hardenNet = true;
18932
19347
  } else if (x === "--subagents") a.subagents = true;
18933
- else if (x === "--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;
18934
19355
  else if (x === "--duplex-v2") {
18935
19356
  a.duplex = true;
18936
19357
  a.duplexV2 = true;
@@ -19064,6 +19485,9 @@ Flags:
19064
19485
  --add-dir <path> mount another directory into the workspace (repeatable; disk mode only)
19065
19486
  --mcp-config <file> JSON { mcpServers?, permissions? } merged over .agent config for this run only
19066
19487
  --subagents allow the Task tool (spawn child agents)
19488
+ --delegate <worker> delegate mode: the main model plans (its writes are refused), <worker> writes via Task
19489
+ --advisor <model> advisor mode: the main model works; an Advise tool consults <model> (first write waits for it)
19490
+ --mode-scope <s> sticky (default): a [a->b] / [a<-b] prompt prefix holds for later turns (and -r resumes) until [off]; message: this turn only
19067
19491
  --reasoning <e> extended thinking: off|low|medium|high or a token budget (anthropic/openai)
19068
19492
  --session-id <id> use this id for the session (instead of an auto-generated one)
19069
19493
  --fork-session with -r/-c: branch the resumed session into a new id (source left untouched)
@@ -19673,7 +20097,7 @@ function turnCost(model, usage) {
19673
20097
  const priced = model.startsWith("claude-code/") ? `anthropic/${resolveModelAlias(model.slice("claude-code/".length))}` : model;
19674
20098
  return costOf(getModelInfo(priced)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, priced);
19675
20099
  }
19676
- async function evaluateGoal(ai, condition, transcript, log41) {
20100
+ async function evaluateGoal(ai, condition, transcript, log42) {
19677
20101
  const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
19678
20102
  const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
19679
20103
  return text.slice(0, 600);
@@ -19693,7 +20117,7 @@ ${recent}` }
19693
20117
  const match = r.content.match(/\{[\s\S]*\}/);
19694
20118
  if (match) return JSON.parse(match[0]);
19695
20119
  } catch (e) {
19696
- log41(dim(` (goal evaluator error: ${e?.message ?? e})
20120
+ log42(dim(` (goal evaluator error: ${e?.message ?? e})
19697
20121
  `));
19698
20122
  }
19699
20123
  return { met: false, reason: "evaluation unclear" };
@@ -19920,7 +20344,7 @@ function mcpAgentTools(mounted, opts) {
19920
20344
  return tools;
19921
20345
  }
19922
20346
  async function closeMcp(mounted) {
19923
- await Promise.all(mounted.map((m) => m.client.close().catch((e) => log40.debug("mcp close failed", e))));
20347
+ await Promise.all(mounted.map((m) => m.client.close().catch((e) => log41.debug("mcp close failed", e))));
19924
20348
  }
19925
20349
  var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
19926
20350
  function mentionRefs(line) {
@@ -19979,7 +20403,7 @@ async function expandMentions(fs, line) {
19979
20403
  if (loaded.includes(ref) || missing.includes(ref)) continue;
19980
20404
  if (ref.includes(":") && mcpMentionResolver) {
19981
20405
  const body = await mcpMentionResolver(ref).catch((e) => {
19982
- log40.debug("mcp mention resolve failed", e);
20406
+ log41.debug("mcp mention resolve failed", e);
19983
20407
  return null;
19984
20408
  });
19985
20409
  if (body != null) {
@@ -20004,6 +20428,33 @@ ${await fs.readFile(path)}`);
20004
20428
 
20005
20429
  ${blocks.join("\n\n")}` : line, loaded, missing };
20006
20430
  }
20431
+ function applyTurnMode(agent, session, task, flags = {}, onModel) {
20432
+ const p = parseDirectives(task, { strict: true, keys: ["model", "turns", "delegate", "advisor", "mode"], warn: (m) => log41.debug(m) });
20433
+ const d = p.directives, isMode = !!(d.delegate || d.advisor || d.modeOff);
20434
+ if (p.unresolvedModel && /->|<-|delegate:|advisor:/.test(task.slice(0, task.length - p.prompt.length))) {
20435
+ err(red(` \u2717 unknown model "${p.unresolvedModel}"${p.ambiguous ? ` (could be: ${p.ambiguous.join(", ")})` : ""} \u2014 turn not sent
20436
+ `));
20437
+ return null;
20438
+ }
20439
+ const r = (m) => m ? resolveModelOrNewest(m) : void 0;
20440
+ const fromDirective = isMode ? d.modeOff ? {} : { model: r(d.model), delegate: r(d.delegate), advisor: r(d.advisor) } : void 0;
20441
+ if (fromDirective && flags.modeScope !== "message") session.meta.mode = fromDirective;
20442
+ const mode = fromDirective ?? session.meta.mode ?? { delegate: flags.delegate && r(flags.delegate), advisor: flags.advisor && r(flags.advisor) };
20443
+ const o = agent.options, before = JSON.stringify([o.model, o.delegate, o.advisor]);
20444
+ if (mode.model && mode.model !== o.model) {
20445
+ o.model = mode.model;
20446
+ o.contextWindow = getModelInfo(mode.model)?.contextWindow ?? 0;
20447
+ onModel?.(mode.model);
20448
+ }
20449
+ o.delegate = mode.delegate ? { worker: mode.delegate } : void 0;
20450
+ o.advisor = mode.advisor ? { ...o.advisor, model: mode.advisor } : void 0;
20451
+ if (JSON.stringify([o.model, o.delegate, o.advisor]) !== before) {
20452
+ agent.reprepare();
20453
+ err(dim(` \u21C4 ${o.delegate ? `delegate: ${o.model} plans \u2192 ${o.delegate.worker} writes` : o.advisor ? `advisor: ${o.model} works \u2190 ${o.advisor.model} advises` : "single-model mode"}
20454
+ `));
20455
+ }
20456
+ return isMode ? p.prompt : task;
20457
+ }
20007
20458
  function jsonResult(res, session) {
20008
20459
  const lastUser = res.messages.map((m) => m.role).lastIndexOf("user");
20009
20460
  return {
@@ -20015,6 +20466,7 @@ function jsonResult(res, session) {
20015
20466
  tools: res.messages.slice(lastUser).filter((m) => m.role === "tool").length,
20016
20467
  usage: res.usage,
20017
20468
  ...res.budget ? { budget: res.budget } : {},
20469
+ ...res.workerUsage ? { workerModel: res.workerModel, workerUsage: res.workerUsage } : {},
20018
20470
  ...res.routing ? { costUsd: res.costUsd, routing: res.routing } : {},
20019
20471
  // cast: tsup dts hoists a modelRouter import above the shebang
20020
20472
  sessionId: session.meta.id,
@@ -20070,7 +20522,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
20070
20522
  try {
20071
20523
  store.save(session);
20072
20524
  } catch (ex) {
20073
- log40.debug("mid-turn session flush failed", ex);
20525
+ log41.debug("mid-turn session flush failed", ex);
20074
20526
  }
20075
20527
  }
20076
20528
  return origNotify(e);
@@ -20099,7 +20551,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
20099
20551
  agent.options.host?.flushText?.();
20100
20552
  flushLookupGroup();
20101
20553
  const secs2 = ((Date.now() - t0) / 1e3).toFixed(1);
20102
- const cost = res.costUsd ?? turnCost(agent.options.model, res.usage);
20554
+ const cost = (res.costUsd ?? turnCost(agent.options.model, res.usage)) + (res.workerUsage && res.workerModel ? turnCost(res.workerModel, res.workerUsage) : 0);
20103
20555
  const m = res.usageEstimated ? "~" : "";
20104
20556
  const tok = res.usage?.totalTokens ? `${m}${(res.usage.totalTokens / 1e3).toFixed(1)}k tok \xB7 ${cost > 0 ? m + fmtUsd(cost) + " \xB7 " : ""}` : "";
20105
20557
  agent.options.host?.takeToolCount?.();
@@ -20234,14 +20686,14 @@ var isCancelTeardown = (e) => {
20234
20686
  function installCancelGuards(mounted) {
20235
20687
  process.on("unhandledRejection", (e) => {
20236
20688
  if (isCancelTeardown(e)) {
20237
- log40.debug("suppressed unhandledRejection (cursor stream cancel)", e);
20689
+ log41.debug("suppressed unhandledRejection (cursor stream cancel)", e);
20238
20690
  return;
20239
20691
  }
20240
- log40.error("unhandledRejection", e);
20692
+ log41.error("unhandledRejection", e);
20241
20693
  });
20242
20694
  process.on("uncaughtException", (e) => {
20243
20695
  if (isCancelTeardown(e)) {
20244
- log40.debug("suppressed uncaughtException (cursor stream cancel)", e);
20696
+ log41.debug("suppressed uncaughtException (cursor stream cancel)", e);
20245
20697
  return;
20246
20698
  }
20247
20699
  console.error(e);
@@ -20610,7 +21062,7 @@ async function repl(args, ai, cfg, cwd) {
20610
21062
  editorRef?.abort();
20611
21063
  }
20612
21064
  } catch (e) {
20613
- log40.error("call turn failed", e);
21065
+ log41.error("call turn failed", e);
20614
21066
  } finally {
20615
21067
  rec.running = false;
20616
21068
  call?.main({ type: "done", id });
@@ -20742,7 +21194,7 @@ async function repl(args, ai, cfg, cwd) {
20742
21194
  try {
20743
21195
  mkdirSync12(dir, { recursive: true });
20744
21196
  } catch (e) {
20745
- log40.debug("paste image dir create failed", e);
21197
+ log41.debug("paste image dir create failed", e);
20746
21198
  }
20747
21199
  const img = grabClipboardImage(dir, String(Date.now()));
20748
21200
  return img ? { display: "Image", ref: "@" + img.path, path: img.path } : null;
@@ -20855,7 +21307,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
20855
21307
  mkdirSync12(join16(cwd, ".agent"), { recursive: true });
20856
21308
  appendFileSync(histPath, line + "\n");
20857
21309
  } catch (e) {
20858
- log40.debug("history write failed", e);
21310
+ log41.debug("history write failed", e);
20859
21311
  }
20860
21312
  };
20861
21313
  const ago = (t) => {
@@ -20927,7 +21379,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
20927
21379
  try {
20928
21380
  store.save(session);
20929
21381
  } catch (e) {
20930
- log40.debug("session save after rewind failed", e);
21382
+ log41.debug("session save after rewind failed", e);
20931
21383
  }
20932
21384
  err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
20933
21385
  `));
@@ -20962,13 +21414,20 @@ ${task}`;
20962
21414
  bangContext.length = 0;
20963
21415
  }
20964
21416
  const delta = await refreshCatalogs().catch((e) => {
20965
- log40.debug("catalog refresh failed", e);
21417
+ log41.debug("catalog refresh failed", e);
20966
21418
  return "";
20967
21419
  });
20968
21420
  if (delta) {
20969
21421
  err(dim(" \u27F3 skill/command catalog changed \u2014 delta attached to this turn\n"));
20970
21422
  task += "\n\n" + delta;
20971
21423
  }
21424
+ if (!duplex) {
21425
+ const t = applyTurnMode(face, session, task, args, (m) => {
21426
+ face.options.providerOptions = providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name));
21427
+ });
21428
+ if (t === null) return { ok: false };
21429
+ task = t;
21430
+ }
20972
21431
  const r = await runTurn(face, store, session, task, duplex ? void 0 : checkpoints, cwd, sendVia ? (c) => sendVia(c, opts) : routeVia);
20973
21432
  if (voiceIO) {
20974
21433
  voiceReasonEnd();
@@ -21225,7 +21684,7 @@ ${task}`;
21225
21684
  desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
21226
21685
  run: async () => {
21227
21686
  await refreshCatalogs().catch((e) => {
21228
- log40.debug("catalog refresh failed", e);
21687
+ log41.debug("catalog refresh failed", e);
21229
21688
  });
21230
21689
  face.reprepare();
21231
21690
  err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
@@ -21728,7 +22187,7 @@ ${task}`;
21728
22187
  try {
21729
22188
  for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
21730
22189
  } catch (e) {
21731
- log40.debug(`loadAgents(${d}) failed`, e);
22190
+ log41.debug(`loadAgents(${d}) failed`, e);
21732
22191
  }
21733
22192
  }
21734
22193
  if (!seen.size) {
@@ -21816,7 +22275,7 @@ ${task}`;
21816
22275
  }
21817
22276
  if (idx >= 0) {
21818
22277
  const old = mounted.splice(idx, 1)[0];
21819
- await old.client.close().catch((e) => log40.debug("mcp close failed", e));
22278
+ await old.client.close().catch((e) => log41.debug("mcp close failed", e));
21820
22279
  }
21821
22280
  try {
21822
22281
  const m = await mountMcpServer(name, conf);
@@ -21844,7 +22303,7 @@ ${task}`;
21844
22303
  }
21845
22304
  const m = mounted.splice(idx, 1)[0];
21846
22305
  remountMcpTools();
21847
- await m.client.close().catch((e) => log40.debug("mcp close failed", e));
22306
+ await m.client.close().catch((e) => log41.debug("mcp close failed", e));
21848
22307
  err(dim(` removed "${name}"
21849
22308
  `));
21850
22309
  return;
@@ -22047,7 +22506,7 @@ ${task}`;
22047
22506
  try {
22048
22507
  return readdirSync4(join16(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
22049
22508
  } catch (e) {
22050
- log40.debug("completion readdir failed", absDir, e);
22509
+ log41.debug("completion readdir failed", absDir, e);
22051
22510
  return null;
22052
22511
  }
22053
22512
  };
@@ -22107,7 +22566,7 @@ ${task}`;
22107
22566
  }
22108
22567
  return;
22109
22568
  }
22110
- const action = applyKey(sEd, key ?? {}, _s);
22569
+ const action = applyKey2(sEd, key ?? {}, _s);
22111
22570
  if (action === "submit") {
22112
22571
  const text = sEd.expand().trim();
22113
22572
  sEd.reset();
@@ -22184,7 +22643,7 @@ ${out}
22184
22643
  return;
22185
22644
  }
22186
22645
  await refreshCatalogs().catch((e) => {
22187
- log40.debug("catalog refresh failed", e);
22646
+ log41.debug("catalog refresh failed", e);
22188
22647
  });
22189
22648
  const sk = skills.find((x) => x.name === name);
22190
22649
  if (sk) {
@@ -22309,7 +22768,7 @@ ${out}
22309
22768
  if (delivery || cut) dx?.parkCutDelivery(delivery, !!cut, heard);
22310
22769
  const cutReply = cut && cut.full.length - cut.heard.length > 40 ? cut : null;
22311
22770
  const noteP = replaces ? replaces.note : !cutReply ? Promise.resolve("") : (instinct ? instinct.bargeIntent(text).catch((e) => {
22312
- log40.warn(`barge intent failed \u2014 model decides: ${e.message}`);
22771
+ log41.warn(`barge intent failed \u2014 model decides: ${e.message}`);
22313
22772
  return void 0;
22314
22773
  }) : Promise.resolve(void 0)).then(async (intent) => interruptionNote(cutReply, intent) + ackedNote(await instinct?.ackFor(text)));
22315
22774
  const cutIn = replaces ? replaces.cutIn : interjectNote;
@@ -22688,7 +23147,7 @@ function channelOptions(ai, model, gender) {
22688
23147
  // Anticipated backchannels: on in chatty mode (its preset), else off until the offline replay proves them (tests/voice-bc-anticipate-eval.ts)
22689
23148
  plan: (c, s) => planner.plan(c, s),
22690
23149
  ...process.env.AGENTX_CHANNEL_ANTICIPATE === "1" ? { anticipate: true } : {},
22691
- onEvent: (e) => 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`)
22692
23151
  };
22693
23152
  }
22694
23153
  function overlapResponder(ai, mode, out, aside) {
@@ -22759,7 +23218,7 @@ async function duplexServe(args, ai, cfg, cwd) {
22759
23218
  try {
22760
23219
  store.save(session);
22761
23220
  } catch (e) {
22762
- log40.warn("duplex session save failed", e);
23221
+ log41.warn("duplex session save failed", e);
22763
23222
  }
22764
23223
  };
22765
23224
  const overlap2 = overlapResponder(ai, args.duplexV2 ? args.channelMode : void 0, out);
@@ -22799,7 +23258,7 @@ async function duplexServe(args, ai, cfg, cwd) {
22799
23258
  done2();
22800
23259
  out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error", ...why ? { error: why } : {} });
22801
23260
  }).catch((e) => {
22802
- log40.error("duplex turn failed", e);
23261
+ log41.error("duplex turn failed", e);
22803
23262
  done2();
22804
23263
  out({ type: "turn_done", id, finishReason: "error", error: String(e?.message ?? e) });
22805
23264
  });
@@ -23054,7 +23513,12 @@ async function main() {
23054
23513
  const session = startSession(args, store, agent, cwd);
23055
23514
  process.once("SIGINT", () => activeTurn?.abort());
23056
23515
  steers?.steer((text) => agent.inject(text));
23057
- const turn = (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
+ };
23058
23522
  if (warmBoot && steers) {
23059
23523
  bindClaudeCodeSession(agent, session);
23060
23524
  await agent.prewarm().catch((e) => err(dim(` (prewarm failed: ${e?.message ?? e})
@@ -23116,6 +23580,7 @@ export {
23116
23580
  abortActiveTurn,
23117
23581
  apiKeysFromEnv,
23118
23582
  appendMemoryNote,
23583
+ applyTurnMode,
23119
23584
  cacheMultipliers,
23120
23585
  condenseReplay,
23121
23586
  costOf,