@livx.cc/agentx 0.99.58 → 0.99.60

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
@@ -2561,7 +2561,7 @@ async function psGroupCpuSeconds(pgid) {
2561
2561
  const out = await new Promise((res, rej) => execFile4("ps", ["-A", "-o", "pid=,ppid=,pgid=,time="], { timeout: 5e3, env: { ...process.env, LC_ALL: "C" } }, (e, so) => e ? rej(e) : res(String(so))));
2562
2562
  return sumTreeCpu(out, pgid);
2563
2563
  } catch (e) {
2564
- log22.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
2564
+ log23.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
2565
2565
  return void 0;
2566
2566
  }
2567
2567
  }
@@ -2709,7 +2709,7 @@ function makeRealShellTool(options) {
2709
2709
  proc.stderr?.on("data", collect);
2710
2710
  proc.on("error", (err2) => {
2711
2711
  if (err2?.name === "AbortError" || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean2(out)));
2712
- log22.debug("shell spawn error", err2);
2712
+ log23.debug("shell spawn error", err2);
2713
2713
  finish(`[exit 1] ${err2?.message ?? err2}${out ? "\n" + clean2(out) : ""}`);
2714
2714
  });
2715
2715
  proc.on("close", (code) => {
@@ -2782,7 +2782,7 @@ ${clean2(out) || "(no output yet)"}`;
2782
2782
  }
2783
2783
  ];
2784
2784
  }
2785
- var log22, clean2, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
2785
+ var log23, clean2, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
2786
2786
  var init_tools_shell = __esm({
2787
2787
  "src/tools.shell.ts"() {
2788
2788
  "use strict";
@@ -2791,7 +2791,7 @@ var init_tools_shell = __esm({
2791
2791
  init_logging();
2792
2792
  init_shell_sandbox();
2793
2793
  init_tools_jobs();
2794
- log22 = forComponent("shell");
2794
+ log23 = forComponent("shell");
2795
2795
  clean2 = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
2796
2796
  DETACHED = { stdio: ["ignore", "pipe", "pipe"], detached: true };
2797
2797
  SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
@@ -3571,6 +3571,7 @@ function resolveModelAlias(input) {
3571
3571
  const slash = input.indexOf("/");
3572
3572
  const prefix = slash === -1 ? "" : input.slice(0, slash + 1);
3573
3573
  const rest = slash === -1 ? input : input.slice(slash + 1);
3574
+ if (prefix === "claude-code/") return input;
3574
3575
  return prefix + (MODEL_ALIASES[rest.toLowerCase()] ?? rest);
3575
3576
  }
3576
3577
  function modelProvider(model) {
@@ -10662,6 +10663,250 @@ Another agent just implemented the above. Independently check the CURRENT state
10662
10663
  }
10663
10664
  };
10664
10665
 
10666
+ // src/modelRouter.ts
10667
+ init_llm();
10668
+ init_logging();
10669
+ var log21 = forComponent("ModelRouter");
10670
+ var ModelRouterOptions = class {
10671
+ classifier = jevClassifier();
10672
+ /** Capability tiers → model id (any provider). basic/standard run the main session; premium is only ever an offload. */
10673
+ models = { basic: "anthropic/claude-haiku-4-5-20251001", standard: "anthropic/claude-sonnet-4-6", premium: "anthropic/claude-opus-5-5" };
10674
+ /** Escalate to a stronger tier when it holds at least this much probability mass (upward bias). */
10675
+ threshold = 0.25;
10676
+ /** P(same thread) at or above which the previous premium side-conversation is resumed. */
10677
+ sameThreadAt = 0.5;
10678
+ classifyTimeoutMs = 1e4;
10679
+ /** Host-supplied pricing (model, usage) → USD, so mixed-model turns are costed per model. */
10680
+ costOf = () => 0;
10681
+ /** Step budget for the premium sub-agent. */
10682
+ offloadMaxSteps = 40;
10683
+ /** Host-supplied provider options per model (e.g. claude-code's subscription env); applied on every tier switch + the offload child. */
10684
+ providerOptionsFor;
10685
+ };
10686
+ function pickTier(c, th) {
10687
+ const o = c.probs.premium ?? 0, s = c.probs.standard ?? 0;
10688
+ if (o >= th || c.choice === "premium") return "premium";
10689
+ if (c.choice === "standard" || s + o >= th) return "standard";
10690
+ return "basic";
10691
+ }
10692
+ function textTurns(msgs) {
10693
+ const out = [];
10694
+ for (const m of msgs) {
10695
+ if (m.role === "user") {
10696
+ const t = contentText(m.content).trim();
10697
+ if (t) out.push({ user: t, assistant: "" });
10698
+ } else if (m.role === "assistant" && out.length) {
10699
+ const t = contentText(m.content).trim();
10700
+ if (t) out[out.length - 1].assistant = (out[out.length - 1].assistant + "\n" + t).trim();
10701
+ }
10702
+ }
10703
+ return out;
10704
+ }
10705
+ var fmtTurns = (ts) => ts.map((t) => `USER: ${t.user}
10706
+ ASSISTANT: ${t.assistant || "(no text reply)"}`).join("\n\n");
10707
+ var addUsage = (a, b) => {
10708
+ if (!a) return b;
10709
+ if (!b) return a;
10710
+ return {
10711
+ promptTokens: a.promptTokens + b.promptTokens,
10712
+ completionTokens: a.completionTokens + b.completionTokens,
10713
+ totalTokens: a.totalTokens + b.totalTokens,
10714
+ cacheCreationTokens: (a.cacheCreationTokens ?? 0) + (b.cacheCreationTokens ?? 0),
10715
+ cacheReadTokens: (a.cacheReadTokens ?? 0) + (b.cacheReadTokens ?? 0)
10716
+ };
10717
+ };
10718
+ var ModelRouter = class {
10719
+ constructor(options) {
10720
+ this.options = options;
10721
+ this.options = { ...new ModelRouterOptions(), ...options };
10722
+ }
10723
+ options;
10724
+ state = {};
10725
+ get o() {
10726
+ return this.options;
10727
+ }
10728
+ /** Classify one turn. Never throws: classifier failure/timeout ⇒ standard. */
10729
+ async decide(transcript, next) {
10730
+ const o = this.o;
10731
+ const main2 = this.state.main === o.models.basic ? "basic" : "standard";
10732
+ const input = {
10733
+ previousModel: main2,
10734
+ history: textTurns(transcript).slice(-4).map((t) => ({ user: t.user.slice(0, 1500), assistant: t.assistant.slice(0, 1500) })),
10735
+ next,
10736
+ lastOffload: this.state.offload?.question
10737
+ };
10738
+ const t0 = performance.now();
10739
+ let d;
10740
+ try {
10741
+ let timer;
10742
+ const c = await Promise.race([
10743
+ o.classifier(input),
10744
+ new Promise((_, rej) => {
10745
+ timer = setTimeout(() => rej(new Error(`classifier timeout ${o.classifyTimeoutMs}ms`)), o.classifyTimeoutMs);
10746
+ })
10747
+ ]).finally(() => clearTimeout(timer));
10748
+ const tier = pickTier(c, o.threshold);
10749
+ d = { tier, model: o.models[tier], probs: c.probs, choice: c.choice, sameThread: c.sameThread, classifyMs: Math.round(performance.now() - t0) };
10750
+ if (tier === "premium") d.offload = this.state.offload && (c.sameThread ?? 0) >= o.sameThreadAt ? "continue" : "fresh";
10751
+ } catch (e) {
10752
+ const msg = e instanceof Error ? e.message : String(e);
10753
+ log21.warn(`classifier failed, falling back to standard: ${msg}`);
10754
+ d = { tier: "standard", model: o.models.standard, probs: {}, fallback: msg, classifyMs: Math.round(performance.now() - t0) };
10755
+ }
10756
+ log21.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`);
10757
+ return d;
10758
+ }
10759
+ /** Run one user turn on `agent` with routing. Returns a RunResult carrying `costUsd` + `routing`. */
10760
+ async send(agent, content) {
10761
+ const o = this.o;
10762
+ const text = contentText(content);
10763
+ const d = await this.decide(agent.transcript, text);
10764
+ if (d.tier !== "premium") {
10765
+ this.use(agent, this.state.main = d.model);
10766
+ const res = await agent.send(content);
10767
+ return { ...res, costUsd: o.costOf(d.model, res.usage), routing: d };
10768
+ }
10769
+ this.use(agent, this.state.main ??= o.models.standard);
10770
+ return this.offload(agent, content, text, d);
10771
+ }
10772
+ async offload(agent, content, text, d) {
10773
+ const o = this.o, main2 = this.state.main;
10774
+ const prev = this.state.offload;
10775
+ const cont = d.offload === "continue" && prev && prev.mainLen <= agent.transcript.length;
10776
+ let briefUsage, prompt;
10777
+ if (cont) {
10778
+ const delta = textTurns(agent.transcript.slice(prev.mainLen));
10779
+ prompt = `Follow-up in the same thread.${delta.length ? ` Since your last answer the main conversation continued:
10780
+
10781
+ ${fmtTurns(delta)}
10782
+ ` : ""}
10783
+
10784
+ New request:
10785
+ ${text}`;
10786
+ } else {
10787
+ const b = await this.brief(agent, text);
10788
+ briefUsage = b.usage;
10789
+ const last2 = textTurns(agent.transcript).slice(-2);
10790
+ prompt = `You are handling a hard turn delegated by the main agent of an ongoing session. You do NOT see its conversation \u2014 only this brief. Do the work with your tools, then reply to the user directly (your reply is shown as the session's answer).
10791
+
10792
+ --- brief from the main agent ---
10793
+ ${b.text}
10794
+
10795
+ ${last2.length ? `--- last turns (verbatim) ---
10796
+ ${fmtTurns(last2)}
10797
+
10798
+ ` : ""}--- current user request (verbatim) ---
10799
+ ${text}`;
10800
+ }
10801
+ const child = this.spawn(agent);
10802
+ const res = cont ? (child.transcript = prev.messages, await child.send(prompt)) : await child.run(prompt);
10803
+ this.state.offloads = (this.state.offloads ?? 0) + 1;
10804
+ if (cont) this.state.reuses = (this.state.reuses ?? 0) + 1;
10805
+ const answer = res.text || `(premium sub-agent finished with no reply; finishReason=${res.finishReason})`;
10806
+ agent.transcript.push({ role: "user", content }, { role: "assistant", content: answer });
10807
+ this.state.offload = { messages: child.transcript, mainLen: agent.transcript.length, question: text };
10808
+ log21.info(`offload ${cont ? "continue" : "fresh"} done \xB7 ${res.finishReason} \xB7 ${res.steps} steps`);
10809
+ const costUsd = o.costOf(main2, briefUsage) + o.costOf(o.models.premium, res.usage);
10810
+ return { ...res, text: answer, messages: agent.transcript, usage: addUsage(briefUsage, res.usage), costUsd, routing: d };
10811
+ }
10812
+ /** The main model writes the task brief on its own (cached) context, tools off; the transcript is restored after. */
10813
+ async brief(agent, text) {
10814
+ const snap = agent.transcript.slice();
10815
+ const { host, toolChoice, maxSteps } = agent.options;
10816
+ Object.assign(agent.options, { host: void 0, toolChoice: "none", maxSteps: 1 });
10817
+ try {
10818
+ const r = await agent.send(`[router] The next request is being delegated to a stronger sub-agent that will NOT see this conversation. Write the self-contained brief you would hand it (<=200 words): the task, the relevant facts/files/decisions established so far, constraints. Output only the brief.
10819
+
10820
+ The request:
10821
+ ${text}`);
10822
+ return { text: r.text.trim() || text, usage: r.usage };
10823
+ } catch (e) {
10824
+ log21.warn(`brief failed, sending the raw request: ${e instanceof Error ? e.message : String(e)}`);
10825
+ return { text };
10826
+ } finally {
10827
+ agent.transcript = snap;
10828
+ Object.assign(agent.options, { host, toolChoice, maxSteps });
10829
+ }
10830
+ }
10831
+ use(agent, model) {
10832
+ agent.options.model = model;
10833
+ if (this.o.providerOptionsFor) agent.options.providerOptions = this.o.providerOptionsFor(model);
10834
+ }
10835
+ spawn(agent) {
10836
+ const a = agent.options;
10837
+ const co = childOptionsFor(
10838
+ { ai: a.ai, model: a.model, fs: a.fs, tools: a.tools, systemPrompt: a.systemPrompt, hooks: a.hooks, maxSteps: this.o.offloadMaxSteps, traceDir: a.traceDir, providerOptionsFor: this.o.providerOptionsFor },
10839
+ a.fs,
10840
+ a.depth ?? 0,
10841
+ a.maxDepth ?? 2,
10842
+ void 0,
10843
+ this.o.models.premium
10844
+ );
10845
+ if (typeof co === "string") throw new Error(co);
10846
+ co.signal = a.signal;
10847
+ return new Agent(co);
10848
+ }
10849
+ };
10850
+ var JEV_KEYS = ["AI_GATEWAY_API_KEY", "OPENROUTER_API_KEY", "TYPESAFE_API_KEY"];
10851
+ async function loadJev(opts) {
10852
+ const mod = await import(opts.jevModule ?? process.env.AGENTX_JEV_MODULE ?? "@livx.cc/mcp-jev/jev");
10853
+ const env = { ...process.env };
10854
+ const file = opts.envFile ?? process.env.AGENTX_JEV_ENV;
10855
+ if (file) {
10856
+ const { readFileSync: readFileSync10 } = await import("fs");
10857
+ for (const l of readFileSync10(file, "utf8").split("\n")) {
10858
+ const m = l.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*?)\s*$/);
10859
+ if (m && !env[m[1]]) env[m[1]] = m[2].replace(/^['"]|['"]$/g, "");
10860
+ }
10861
+ }
10862
+ return { mod, env };
10863
+ }
10864
+ async function jevAvailable(opts = {}) {
10865
+ try {
10866
+ const { env } = await loadJev(opts);
10867
+ return JEV_KEYS.some((k) => env[k]?.trim());
10868
+ } catch (e) {
10869
+ log21.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
10870
+ return false;
10871
+ }
10872
+ }
10873
+ function jevClassifier(opts = {}) {
10874
+ let router;
10875
+ const load = async () => {
10876
+ const { mod, env } = await loadJev(opts);
10877
+ return new mod.JevRouter({ env });
10878
+ };
10879
+ return async (input) => {
10880
+ const jev = await (router ??= load().catch((e) => {
10881
+ router = void 0;
10882
+ throw e;
10883
+ }));
10884
+ const questions = [{
10885
+ id: "tier",
10886
+ type: "choice",
10887
+ instructions: "Pick the CHEAPEST model that will handle the NEXT assistant turn correctly for this coding-agent conversation. Staying on previousModel keeps its prompt cache warm; switch only when the next turn clearly needs more (or much less) capability.",
10888
+ criteria: {
10889
+ basic: "Trivial: commands, lookups, acknowledgements, commits, mechanical single-file edits, short rewrites.",
10890
+ standard: "Standard engineering: features, bounded refactors, reviews, tests, following a clear trace, summarising.",
10891
+ premium: "Hard: root-causing subtle/intermittent bugs, concurrency, security semantics, architecture trade-offs, quantitative reasoning."
10892
+ }
10893
+ }];
10894
+ if (input.lastOffload) questions.push({
10895
+ id: "sameThread",
10896
+ type: "boolean",
10897
+ instructions: "Does nextUserMessage continue the same line of work as lastOffload (the previous hard request, handled by a specialist) \u2014 a follow-up, refinement or next step of it \u2014 rather than a new, unrelated topic?"
10898
+ });
10899
+ const state = { previousModel: input.previousModel, history: input.history, nextUserMessage: input.next, ...input.lastOffload ? { lastOffload: input.lastOffload } : {} };
10900
+ const r = await jev.evaluate({ state, questions }, opts.provider);
10901
+ const a = r.answers.tier;
10902
+ const map = (k) => k;
10903
+ const probs = {};
10904
+ for (const [k, v] of Object.entries(a.probabilities ?? {})) probs[map(k)] = v;
10905
+ const st = r.answers.sameThread;
10906
+ return { choice: map(a.choice), probs, sameThread: st ? st.probability ?? (st.value ? 1 : 0) : void 0 };
10907
+ };
10908
+ }
10909
+
10665
10910
  // src/mcp.ts
10666
10911
  function toResult(result) {
10667
10912
  if (result == null) return { text: "" };
@@ -10849,7 +11094,7 @@ import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor a
10849
11094
  init_logging();
10850
11095
  import { spawn } from "child_process";
10851
11096
  import { createHash } from "crypto";
10852
- var log21 = forComponent("mcp");
11097
+ var log22 = forComponent("mcp");
10853
11098
  var PROTOCOL_VERSION = "2025-06-18";
10854
11099
  var DEFAULT_TIMEOUT_MS = 3e4;
10855
11100
  var DEFAULT_CALL_TIMEOUT_MS = 30 * 6e4;
@@ -10879,7 +11124,7 @@ var StdioTransport = class {
10879
11124
  proc.stdout.setEncoding("utf8");
10880
11125
  proc.stdout.on("data", (chunk) => this.onData(chunk));
10881
11126
  proc.stderr.setEncoding("utf8");
10882
- proc.stderr.on("data", (chunk) => log21.debug(`[${command}] stderr:`, chunk.trimEnd()));
11127
+ proc.stderr.on("data", (chunk) => log22.debug(`[${command}] stderr:`, chunk.trimEnd()));
10883
11128
  proc.on("exit", (code) => this.failAll(new Error(`MCP server "${command}" exited (code ${code})`)));
10884
11129
  proc.on("error", (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));
10885
11130
  }
@@ -10893,7 +11138,7 @@ var StdioTransport = class {
10893
11138
  try {
10894
11139
  this.dispatch(JSON.parse(line));
10895
11140
  } catch (e) {
10896
- log21.debug("dropping non-JSON line from MCP server:", line, e);
11141
+ log22.debug("dropping non-JSON line from MCP server:", line, e);
10897
11142
  }
10898
11143
  }
10899
11144
  }
@@ -10903,7 +11148,7 @@ var StdioTransport = class {
10903
11148
  try {
10904
11149
  p2?.onProgress?.(msg.params ?? {});
10905
11150
  } catch (e) {
10906
- log21.debug("onProgress handler threw", e);
11151
+ log22.debug("onProgress handler threw", e);
10907
11152
  }
10908
11153
  return;
10909
11154
  }
@@ -10941,7 +11186,7 @@ var StdioTransport = class {
10941
11186
  const onAbort = () => {
10942
11187
  if (!this.pending.delete(id)) return;
10943
11188
  clearTimeout(timer);
10944
- this.notify("notifications/cancelled", { requestId: id, reason: String(signal?.reason?.message ?? "cancelled") }).catch((e) => log21.debug("cancel notify failed", e));
11189
+ this.notify("notifications/cancelled", { requestId: id, reason: String(signal?.reason?.message ?? "cancelled") }).catch((e) => log22.debug("cancel notify failed", e));
10945
11190
  reject(abortError(signal));
10946
11191
  };
10947
11192
  const done = () => signal?.removeEventListener("abort", onAbort);
@@ -10965,7 +11210,7 @@ var StdioTransport = class {
10965
11210
  try {
10966
11211
  this.proc?.stdin?.end();
10967
11212
  } catch (e) {
10968
- log21.debug("stdin end failed", e);
11213
+ log22.debug("stdin end failed", e);
10969
11214
  }
10970
11215
  this.proc?.kill();
10971
11216
  }
@@ -11000,7 +11245,7 @@ var HttpTransport = class {
11000
11245
  return await this.post({ jsonrpc: "2.0", id, method, params: opts.onProgress ? withProgressToken(params, id) : params }, false, signal, opts.onProgress);
11001
11246
  } catch (e) {
11002
11247
  if (opts.signal?.aborted) {
11003
- void this.notify("notifications/cancelled", { requestId: id, reason: String(opts.signal.reason?.message ?? "cancelled") }).catch((err2) => log21.debug("cancel notify failed", err2));
11248
+ void this.notify("notifications/cancelled", { requestId: id, reason: String(opts.signal.reason?.message ?? "cancelled") }).catch((err2) => log22.debug("cancel notify failed", err2));
11004
11249
  throw abortError(opts.signal);
11005
11250
  }
11006
11251
  throw e;
@@ -11043,7 +11288,7 @@ function sseMessage(line) {
11043
11288
  try {
11044
11289
  return JSON.parse(trimmed.slice(5).trim());
11045
11290
  } catch (e) {
11046
- log21.debug("skipping unparseable SSE data line", e);
11291
+ log22.debug("skipping unparseable SSE data line", e);
11047
11292
  return void 0;
11048
11293
  }
11049
11294
  }
@@ -11053,7 +11298,7 @@ async function readSseResponse(resp, id, onProgress) {
11053
11298
  try {
11054
11299
  onProgress?.(obj.params ?? {});
11055
11300
  } catch (e) {
11056
- log21.debug("onProgress handler threw", e);
11301
+ log22.debug("onProgress handler threw", e);
11057
11302
  }
11058
11303
  return void 0;
11059
11304
  }
@@ -11152,7 +11397,7 @@ async function mountWithDeadline(name, cfg, mountTimeoutMs) {
11152
11397
  return { name, client, tools, specs, serverInfo: init?.serverInfo, config: cfg };
11153
11398
  })(), mountTimeoutMs, name);
11154
11399
  } catch (e) {
11155
- await client.close().catch((err2) => log21.debug(`close after failed mount of "${name}": ${err2}`));
11400
+ await client.close().catch((err2) => log22.debug(`close after failed mount of "${name}": ${err2}`));
11156
11401
  throw e;
11157
11402
  }
11158
11403
  }
@@ -11163,15 +11408,15 @@ function validEntries(servers) {
11163
11408
  return Object.entries(servers).filter(([name, cfg]) => {
11164
11409
  if (!cfg || cfg.disabled) return false;
11165
11410
  if (!cfg.command && !cfg.url) {
11166
- log21.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
11411
+ log22.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
11167
11412
  return false;
11168
11413
  }
11169
11414
  return true;
11170
11415
  });
11171
11416
  }
11172
11417
  function logMountFailure(name, e) {
11173
- if (e instanceof McpAuthError) log21.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
11174
- else log21.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
11418
+ if (e instanceof McpAuthError) log22.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
11419
+ else log22.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
11175
11420
  }
11176
11421
  async function mountMcpServers(servers = {}, opts = {}) {
11177
11422
  const entries = validEntries(servers);
@@ -11181,7 +11426,7 @@ async function mountMcpServers(servers = {}, opts = {}) {
11181
11426
  const name = entries[i][0];
11182
11427
  if (r.status === "fulfilled") {
11183
11428
  out.push(r.value);
11184
- log21.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
11429
+ log22.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
11185
11430
  } else logMountFailure(name, r.reason);
11186
11431
  });
11187
11432
  return out;
@@ -11221,7 +11466,7 @@ var McpPool = class {
11221
11466
  const prev = this.warm.get(key);
11222
11467
  if (prev) {
11223
11468
  clearTimeout(prev.timer);
11224
- if (prev.client !== client) void prev.client.close().catch((err2) => log21.debug(`warm-pool replace close failed: ${err2}`));
11469
+ if (prev.client !== client) void prev.client.close().catch((err2) => log22.debug(`warm-pool replace close failed: ${err2}`));
11225
11470
  }
11226
11471
  const e = { client, timer: void 0 };
11227
11472
  this.warm.set(key, e);
@@ -11238,7 +11483,7 @@ var McpPool = class {
11238
11483
  const e = this.warm.get(key);
11239
11484
  if (!e) return;
11240
11485
  this.warm.delete(key);
11241
- await e.client.close().catch((err2) => log21.debug(`warm-pool evict close failed: ${err2}`));
11486
+ await e.client.close().catch((err2) => log22.debug(`warm-pool evict close failed: ${err2}`));
11242
11487
  }
11243
11488
  async closeAll() {
11244
11489
  for (const e of this.warm.values()) {
@@ -11456,7 +11701,7 @@ init_tools_shell();
11456
11701
  // src/tools.notify.ts
11457
11702
  init_logging();
11458
11703
  import { execFile } from "child_process";
11459
- var log23 = forComponent("notify");
11704
+ var log24 = forComponent("notify");
11460
11705
  function makeNotifyTool(opts = {}) {
11461
11706
  const platform2 = opts.platform ?? process.platform;
11462
11707
  const run = opts.exec ?? execFile;
@@ -11480,7 +11725,7 @@ function makeNotifyTool(opts = {}) {
11480
11725
  return new Promise((resolve5) => {
11481
11726
  run(argv[0], argv[1], { timeout: 5e3 }, (e) => {
11482
11727
  if (e) {
11483
- log23.debug("notification failed", e);
11728
+ log24.debug("notification failed", e);
11484
11729
  resolve5(`Notification failed: ${e.message}`);
11485
11730
  } else resolve5("Notification shown.");
11486
11731
  });
@@ -11496,7 +11741,7 @@ import { BodDB as BodDB2 } from "@bod.ee/db";
11496
11741
  init_logging();
11497
11742
  import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
11498
11743
  import { homedir } from "os";
11499
- var log24 = forComponent("cli-util");
11744
+ var log25 = forComponent("cli-util");
11500
11745
  function dotDirs(base, sub, opts = {}) {
11501
11746
  const home = opts.home ?? homedir();
11502
11747
  const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
@@ -11513,7 +11758,7 @@ function parseJson(text, fallback, what = "json") {
11513
11758
  try {
11514
11759
  return JSON.parse(text);
11515
11760
  } catch (e) {
11516
- log24.debug(`parseJson(${what}) failed: ${e.message}`);
11761
+ log25.debug(`parseJson(${what}) failed: ${e.message}`);
11517
11762
  return fallback;
11518
11763
  }
11519
11764
  }
@@ -11523,7 +11768,7 @@ function readJsonFile(path, fallback) {
11523
11768
  try {
11524
11769
  text = readFileSync4(path, "utf8");
11525
11770
  } catch (e) {
11526
- log24.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
11771
+ log25.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
11527
11772
  return fallback;
11528
11773
  }
11529
11774
  return parseJson(text, fallback, path);
@@ -11822,7 +12067,7 @@ import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as rea
11822
12067
  import { homedir as homedir3 } from "os";
11823
12068
  import { dirname as dirname3, join as join6 } from "path";
11824
12069
  import { fileURLToPath } from "url";
11825
- var log25 = forComponent("VoiceIO");
12070
+ var log26 = forComponent("VoiceIO");
11826
12071
  var now3 = () => performance.now();
11827
12072
  var Player = class {
11828
12073
  proc = null;
@@ -11836,7 +12081,7 @@ var Player = class {
11836
12081
  ["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
11837
12082
  { stdio: ["pipe", "ignore", "ignore"] }
11838
12083
  );
11839
- this.proc.on("error", (e) => log25.warn(`ffplay error: ${e.message}`));
12084
+ this.proc.on("error", (e) => log26.warn(`ffplay error: ${e.message}`));
11840
12085
  this.proc.stdin.on("error", () => {
11841
12086
  });
11842
12087
  this.bytesWritten = 0;
@@ -11874,7 +12119,7 @@ function detectFfmpegMic() {
11874
12119
  const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
11875
12120
  const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
11876
12121
  if (!mic) throw new Error("no audio input device found");
11877
- log25.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
12122
+ log26.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
11878
12123
  return `:${mic.idx}`;
11879
12124
  }
11880
12125
  function detectedInputDevice() {
@@ -11910,15 +12155,15 @@ function resolveAecBinary() {
11910
12155
  if (existsSync4(bin) && statSync4(bin).mtimeMs >= statSync4(src).mtimeMs) return bin;
11911
12156
  if (spawnSync2("which", ["swiftc"]).status !== 0) return null;
11912
12157
  mkdirSync5(cacheDir, { recursive: true });
11913
- log25.info("compiling AEC mic helper (first run)\u2026");
12158
+ log26.info("compiling AEC mic helper (first run)\u2026");
11914
12159
  const build = spawnSync2("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
11915
12160
  if (build.status !== 0) {
11916
- log25.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
12161
+ log26.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
11917
12162
  return null;
11918
12163
  }
11919
12164
  const sign = spawnSync2("codesign", ["-fs", "-", bin], { encoding: "utf8" });
11920
12165
  if (sign.status !== 0) {
11921
- log25.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
12166
+ log26.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
11922
12167
  return null;
11923
12168
  }
11924
12169
  return bin;
@@ -11960,16 +12205,16 @@ var NodeMicSource = class {
11960
12205
  this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
11961
12206
  } else {
11962
12207
  if (spawnSync2("which", ["ffmpeg"]).status !== 0) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
11963
- log25.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
12208
+ log26.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
11964
12209
  this.proc = spawn2(
11965
12210
  "ffmpeg",
11966
12211
  ["-loglevel", "error", "-f", "avfoundation", "-i", detectFfmpegMic(), "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
11967
12212
  { stdio: ["ignore", "pipe", "pipe"] }
11968
12213
  );
11969
- this.proc.stderr.on("data", (d) => log25.warn(`ffmpeg: ${String(d).trim()}`));
12214
+ this.proc.stderr.on("data", (d) => log26.warn(`ffmpeg: ${String(d).trim()}`));
11970
12215
  }
11971
12216
  this.proc.on("exit", (c) => {
11972
- if (c && !this.stopped) log25.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
12217
+ if (c && !this.stopped) log26.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
11973
12218
  });
11974
12219
  this.proc.stdout.on("data", (chunk) => onChunk(chunk));
11975
12220
  }
@@ -12035,7 +12280,7 @@ var AecDuplexAudio = class {
12035
12280
  this.proc.stdin.on("error", () => {
12036
12281
  });
12037
12282
  this.proc.on("exit", (c) => {
12038
- if (c && !this.stopped) log25.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
12283
+ if (c && !this.stopped) log26.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
12039
12284
  });
12040
12285
  this.proc.stdout.on("data", (chunk) => {
12041
12286
  this.gotChunk = true;
@@ -12051,7 +12296,7 @@ var AecDuplexAudio = class {
12051
12296
  openMicSettings();
12052
12297
  this.onFatal?.("microphone permission denied \u2014 enable it in System Settings \u2192 Privacy & Security \u2192 Microphone for your terminal, then restart it");
12053
12298
  }
12054
- } else log25.debug(`mic-aec: ${s}`);
12299
+ } else log26.debug(`mic-aec: ${s}`);
12055
12300
  }
12056
12301
  });
12057
12302
  if (!this.noVpio && !this.triedFallback) {
@@ -12060,7 +12305,7 @@ var AecDuplexAudio = class {
12060
12305
  this.triedFallback = true;
12061
12306
  this.noVpio = true;
12062
12307
  this._aec = false;
12063
- log25.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)");
12308
+ log26.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)");
12064
12309
  this.onDegrade?.();
12065
12310
  this.killProc();
12066
12311
  this.spawnHelper();
@@ -12231,7 +12476,7 @@ function bindAckInstinct(v, instinct) {
12231
12476
  if (ev.kind === "ack_fired") instinct.ackOutcome(String(ev.phrase));
12232
12477
  else if (ev.kind === "ack_skipped" || ev.kind === "ack_cancelled") instinct.ackOutcome(null);
12233
12478
  } catch (e) {
12234
- log25.warn(`ack outcome tap failed: ${e instanceof Error ? e.message : e}`);
12479
+ log26.warn(`ack outcome tap failed: ${e instanceof Error ? e.message : e}`);
12235
12480
  }
12236
12481
  onDiag(ev);
12237
12482
  };
@@ -12352,7 +12597,7 @@ function fakeVoiceParts(uttFile) {
12352
12597
  init_logging();
12353
12598
  import { appendFile, mkdirSync as mkdirSync6 } from "fs";
12354
12599
  import { dirname as dirname4 } from "path";
12355
- var log26 = forComponent("VoiceDiag");
12600
+ var log27 = forComponent("VoiceDiag");
12356
12601
  var JsonlDiagSink = class {
12357
12602
  constructor(path) {
12358
12603
  this.path = path;
@@ -12393,7 +12638,7 @@ var JsonlDiagSink = class {
12393
12638
  disable(e) {
12394
12639
  this.disabled = true;
12395
12640
  this.queue.length = 0;
12396
- log26.debug(`voice diag sink disabled (${this.path}): ${e instanceof Error ? e.message : e}`);
12641
+ log27.debug(`voice diag sink disabled (${this.path}): ${e instanceof Error ? e.message : e}`);
12397
12642
  }
12398
12643
  /** True once a failure permanently disabled the sink (tests/inspection). */
12399
12644
  get isDisabled() {
@@ -12463,7 +12708,7 @@ async function loadConfig(cwd) {
12463
12708
 
12464
12709
  // cli/hooks-config.ts
12465
12710
  import { spawnSync as spawnSync3 } from "child_process";
12466
- var log27 = forComponent("hooks");
12711
+ var log28 = forComponent("hooks");
12467
12712
  var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
12468
12713
  function ruleMatches(rule, toolName) {
12469
12714
  if (!rule.tool || rule.tool === "*") return true;
@@ -12480,7 +12725,7 @@ function runCmd(rule, env) {
12480
12725
  });
12481
12726
  return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
12482
12727
  } catch (e) {
12483
- log27.debug(`hook command failed: ${rule.command}`, e);
12728
+ log28.debug(`hook command failed: ${rule.command}`, e);
12484
12729
  return { code: 1, out: String(e?.message ?? e) };
12485
12730
  }
12486
12731
  }
@@ -12587,7 +12832,7 @@ function formatDiff(ops, opts = {}) {
12587
12832
  import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync4, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync, statSync as statSync5 } from "fs";
12588
12833
  import { homedir as homedir5 } from "os";
12589
12834
  import { join as join8 } from "path";
12590
- var log28 = forComponent("session");
12835
+ var log29 = forComponent("session");
12591
12836
  var globalDir = () => join8(homedir5(), ".agent", "sessions");
12592
12837
  var SessionStore = class {
12593
12838
  dir;
@@ -12632,7 +12877,7 @@ var SessionStore = class {
12632
12877
  }
12633
12878
  load(id) {
12634
12879
  if (!this.safeId(id)) {
12635
- log28.debug(`rejecting unsafe session id: ${id}`);
12880
+ log29.debug(`rejecting unsafe session id: ${id}`);
12636
12881
  return void 0;
12637
12882
  }
12638
12883
  const path = join8(this.dir, `${id}.json`);
@@ -12640,7 +12885,7 @@ var SessionStore = class {
12640
12885
  try {
12641
12886
  return JSON.parse(readFileSync7(path, "utf8"));
12642
12887
  } catch (e) {
12643
- log28.debug(`unreadable session ${id} \u2014 ignoring`, e);
12888
+ log29.debug(`unreadable session ${id} \u2014 ignoring`, e);
12644
12889
  return void 0;
12645
12890
  }
12646
12891
  }
@@ -12653,7 +12898,7 @@ var SessionStore = class {
12653
12898
  try {
12654
12899
  metas.push(JSON.parse(readFileSync7(join8(this.dir, f), "utf8")).meta);
12655
12900
  } catch (e) {
12656
- log28.debug(`skipping unreadable session file ${f}`, e);
12901
+ log29.debug(`skipping unreadable session file ${f}`, e);
12657
12902
  }
12658
12903
  }
12659
12904
  return metas.sort((a, b) => b.updated - a.updated);
@@ -12892,7 +13137,7 @@ import { promisify } from "util";
12892
13137
  import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync8 } from "fs";
12893
13138
  import { homedir as homedir6 } from "os";
12894
13139
  import { join as join9, resolve as resolve3, sep as sep3, parse as parsePath } from "path";
12895
- var log29 = forComponent("checkpoints");
13140
+ var log30 = forComponent("checkpoints");
12896
13141
  var exec = promisify(execFile3);
12897
13142
  var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
12898
13143
  function isBroadRoot(p) {
@@ -12938,7 +13183,7 @@ var ShadowRepo = class {
12938
13183
  writeFileSync5(join9(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
12939
13184
  this.ready = true;
12940
13185
  } catch (e) {
12941
- log29.debug(`git checkpoints unavailable for ${this.workTree}`, e);
13186
+ log30.debug(`git checkpoints unavailable for ${this.workTree}`, e);
12942
13187
  this.ready = false;
12943
13188
  }
12944
13189
  return this.ready;
@@ -12949,7 +13194,7 @@ var ShadowRepo = class {
12949
13194
  }
12950
13195
  async commit(label, forced = []) {
12951
13196
  await this.run("add", "-A");
12952
- for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log29.debug(`force-add failed: ${p}`, e));
13197
+ for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log30.debug(`force-add failed: ${p}`, e));
12953
13198
  await this.run("commit", "--allow-empty", "-q", "-m", label);
12954
13199
  }
12955
13200
  /** Inject the CURRENT (pre-edit) content of `paths` into the turn-open restore point by amending it.
@@ -12957,8 +13202,8 @@ var ShadowRepo = class {
12957
13202
  * turn-boundary `add -A` would never have captured it. Amend (vs a new commit) keeps one restore
12958
13203
  * point per turn, so the REPL's turn↔frame mapping stays intact. */
12959
13204
  async amendForced(paths) {
12960
- for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log29.debug(`force-capture failed: ${p}`, e));
12961
- await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log29.debug("amend failed", e));
13205
+ for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log30.debug(`force-capture failed: ${p}`, e));
13206
+ await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log30.debug("amend failed", e));
12962
13207
  }
12963
13208
  /** Commits on `ref`, oldest-first (canonical index space). */
12964
13209
  async log(ref) {
@@ -13018,7 +13263,7 @@ var ShadowRepo = class {
13018
13263
  await this.run("gc", "--auto", "-q").catch(() => {
13019
13264
  });
13020
13265
  } catch (e) {
13021
- log29.debug("checkpoint prune failed", e);
13266
+ log30.debug("checkpoint prune failed", e);
13022
13267
  }
13023
13268
  }
13024
13269
  };
@@ -13086,7 +13331,7 @@ var GitCheckpoints = class {
13086
13331
  use(sessionId) {
13087
13332
  if (sessionId === this.session) return;
13088
13333
  this.session = sessionId;
13089
- if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log29.debug("re-point failed", e));
13334
+ if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log30.debug("re-point failed", e));
13090
13335
  }
13091
13336
  async begin(label) {
13092
13337
  if (!await this.start()) return;
@@ -13106,7 +13351,7 @@ var GitCheckpoints = class {
13106
13351
  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
13107
13352
  `);
13108
13353
  } else {
13109
- log29.debug("checkpoint commit failed", e);
13354
+ log30.debug("checkpoint commit failed", e);
13110
13355
  survivors.push(this.repos[i]);
13111
13356
  survivorCaches.push(this.caches[i] ?? []);
13112
13357
  }
@@ -13141,7 +13386,7 @@ var GitCheckpoints = class {
13141
13386
  if (this.forced.has(abs)) continue;
13142
13387
  this.forced.add(abs);
13143
13388
  const i = this.repoIndexFor(abs);
13144
- if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log29.debug("amendForced failed", e));
13389
+ if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log30.debug("amendForced failed", e));
13145
13390
  }
13146
13391
  }
13147
13392
  };
@@ -14994,7 +15239,7 @@ import { spawnSync as spawnSync5 } from "child_process";
14994
15239
  import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync7 } from "fs";
14995
15240
  import { homedir as homedir8 } from "os";
14996
15241
  import { join as join12 } from "path";
14997
- var log30 = forComponent("os-sched");
15242
+ var log31 = forComponent("os-sched");
14998
15243
  var OsScheduler = class {
14999
15244
  options;
15000
15245
  constructor(options) {
@@ -15056,7 +15301,7 @@ var OsScheduler = class {
15056
15301
  }
15057
15302
  }
15058
15303
  } catch (e) {
15059
- log30.debug(`cancel ${id}`, e);
15304
+ log31.debug(`cancel ${id}`, e);
15060
15305
  }
15061
15306
  for (const f of [`${id}.json`, `${id}.sh`]) {
15062
15307
  try {
@@ -15173,7 +15418,7 @@ import { spawn as spawn3 } from "child_process";
15173
15418
  import { existsSync as existsSync8, mkdirSync as mkdirSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
15174
15419
  import { homedir as homedir9 } from "os";
15175
15420
  import { join as join13 } from "path";
15176
- var log31 = forComponent("remote-trigger");
15421
+ var log32 = forComponent("remote-trigger");
15177
15422
  var TRIGGER_DIR = () => join13(homedir9(), ".agent", "triggers");
15178
15423
  var sockPath = (sessionId, dir = TRIGGER_DIR()) => join13(dir, `${sessionId}.sock`);
15179
15424
  var TriggerServer = class {
@@ -15211,13 +15456,13 @@ var TriggerServer = class {
15211
15456
  conn.end(JSON.stringify({ ok: false, error: String(e) }) + "\n");
15212
15457
  }
15213
15458
  });
15214
- conn.on("error", (e) => log31.debug("trigger conn error", e));
15459
+ conn.on("error", (e) => log32.debug("trigger conn error", e));
15215
15460
  });
15216
- this.server.on("error", (e) => log31.debug("trigger server error", e));
15461
+ this.server.on("error", (e) => log32.debug("trigger server error", e));
15217
15462
  this.server.listen(p);
15218
15463
  this.path = p;
15219
15464
  } catch (e) {
15220
- log31.debug("trigger server unavailable", e);
15465
+ log32.debug("trigger server unavailable", e);
15221
15466
  }
15222
15467
  }
15223
15468
  /** Re-bind on /resume (the session id changed). */
@@ -15339,7 +15584,7 @@ var strike = C("9");
15339
15584
  var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
15340
15585
  var mdPalette = { bold, dim, cyan, italic, strike, green, link };
15341
15586
  var err = (s) => process.stderr.write(s);
15342
- var log32 = forComponent("cli");
15587
+ var log33 = forComponent("cli");
15343
15588
  var VERSION = (() => {
15344
15589
  try {
15345
15590
  return JSON.parse(readFileSync9(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
@@ -15446,6 +15691,10 @@ function parseArgs(argv) {
15446
15691
  a.listModels = true;
15447
15692
  if (nxt === "list" || nxt === "ls" || nxt === "?") i++;
15448
15693
  } else a.model = argv[++i];
15694
+ if (a.model === "auto") {
15695
+ a.autoRoute = true;
15696
+ a.model = autoModels().standard;
15697
+ }
15449
15698
  } else if (x === "-C" || x === "--cwd") a.cwd = val(++i, x);
15450
15699
  else if (x === "-p" || x === "--print") {
15451
15700
  const nxt = argv[i + 1];
@@ -15524,7 +15773,7 @@ var HELP = `agentx \u2014 agent runtime CLI
15524
15773
  agentx -c "<task>" continue the most recent session
15525
15774
 
15526
15775
  Flags:
15527
- -m, --model <id> model (default anthropic/claude-sonnet-4-6); bare -m or -m list = list all models
15776
+ -m, --model <id> model (default auto: per-turn haiku/sonnet + opus offload when Jev is configured, else anthropic/claude-sonnet-4-6); bare -m or -m list = list all models
15528
15777
  -C, --cwd <dir> working dir / project root (default .)
15529
15778
  -c, --continue resume the most recent session in this dir
15530
15779
  -r, --resume [id] resume a session \u2014 with an id, or bare for an interactive picker
@@ -16092,10 +16341,25 @@ function costOf(pricing, promptTokens = 0, completionTokens = 0, cacheCreationTo
16092
16341
  const fresh = Math.max(0, promptTokens - cacheCreationTokens - cacheReadTokens);
16093
16342
  return fresh / 1e3 * pricing.inputCostPer1K + cacheCreationTokens / 1e3 * pricing.inputCostPer1K * mult.write + cacheReadTokens / 1e3 * pricing.inputCostPer1K * mult.read + completionTokens / 1e3 * pricing.outputCostPer1K;
16094
16343
  }
16344
+ var autoModels = () => ({ ...new ModelRouterOptions().models, ...JSON.parse(process.env.AGENTX_AUTO_MODELS || "{}") });
16345
+ function autoRouteSend(args, agent, session, pof) {
16346
+ if (!args.autoRoute) return void 0;
16347
+ const router = new ModelRouter({ costOf: turnCost, models: autoModels(), providerOptionsFor: pof });
16348
+ return async (c) => {
16349
+ const s = session();
16350
+ router.state = s.meta.router ?? {};
16351
+ try {
16352
+ return await router.send(agent, c);
16353
+ } finally {
16354
+ s.meta.router = router.state;
16355
+ }
16356
+ };
16357
+ }
16095
16358
  function turnCost(model, usage) {
16096
- return costOf(getModelInfo(model)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, model);
16359
+ const priced = model.startsWith("claude-code/") ? `anthropic/${resolveModelAlias(model.slice("claude-code/".length))}` : model;
16360
+ return costOf(getModelInfo(priced)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, priced);
16097
16361
  }
16098
- async function evaluateGoal(ai, condition, transcript, log33) {
16362
+ async function evaluateGoal(ai, condition, transcript, log34) {
16099
16363
  const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
16100
16364
  const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
16101
16365
  return text.slice(0, 600);
@@ -16115,7 +16379,7 @@ ${recent}` }
16115
16379
  const match = r.content.match(/\{[\s\S]*\}/);
16116
16380
  if (match) return JSON.parse(match[0]);
16117
16381
  } catch (e) {
16118
- log33(dim(` (goal evaluator error: ${e?.message ?? e})
16382
+ log34(dim(` (goal evaluator error: ${e?.message ?? e})
16119
16383
  `));
16120
16384
  }
16121
16385
  return { met: false, reason: "evaluation unclear" };
@@ -16336,7 +16600,7 @@ function mcpAgentTools(mounted, opts) {
16336
16600
  return tools;
16337
16601
  }
16338
16602
  async function closeMcp(mounted) {
16339
- await Promise.all(mounted.map((m) => m.client.close().catch((e) => log32.debug("mcp close failed", e))));
16603
+ await Promise.all(mounted.map((m) => m.client.close().catch((e) => log33.debug("mcp close failed", e))));
16340
16604
  }
16341
16605
  var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
16342
16606
  function mentionRefs(line) {
@@ -16388,7 +16652,7 @@ async function expandMentions(fs, line) {
16388
16652
  if (loaded.includes(ref) || missing.includes(ref)) continue;
16389
16653
  if (ref.includes(":") && mcpMentionResolver) {
16390
16654
  const body = await mcpMentionResolver(ref).catch((e) => {
16391
- log32.debug("mcp mention resolve failed", e);
16655
+ log33.debug("mcp mention resolve failed", e);
16392
16656
  return null;
16393
16657
  });
16394
16658
  if (body != null) {
@@ -16423,6 +16687,8 @@ function jsonResult(res, session) {
16423
16687
  steps: res.steps,
16424
16688
  tools: res.messages.slice(lastUser).filter((m) => m.role === "tool").length,
16425
16689
  usage: res.usage,
16690
+ ...res.routing ? { costUsd: res.costUsd, routing: res.routing } : {},
16691
+ // cast: tsup dts hoists a modelRouter import above the shebang
16426
16692
  sessionId: session.meta.id,
16427
16693
  // The parked question, machine-readable — the caller answers it and resumes `sessionId`.
16428
16694
  ...res.finishReason === "needs_input" && res.question ? { question: res.question } : {},
@@ -16475,7 +16741,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
16475
16741
  try {
16476
16742
  store.save(session);
16477
16743
  } catch (ex) {
16478
- log32.debug("mid-turn session flush failed", ex);
16744
+ log33.debug("mid-turn session flush failed", ex);
16479
16745
  }
16480
16746
  }
16481
16747
  return origNotify(e);
@@ -16503,7 +16769,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
16503
16769
  agent.options.host?.flushText?.();
16504
16770
  flushLookupGroup();
16505
16771
  const secs2 = ((Date.now() - t0) / 1e3).toFixed(1);
16506
- const cost = turnCost(agent.options.model, res.usage);
16772
+ const cost = res.costUsd ?? turnCost(agent.options.model, res.usage);
16507
16773
  const m = res.usageEstimated ? "~" : "";
16508
16774
  const tok = res.usage?.totalTokens ? `${m}${(res.usage.totalTokens / 1e3).toFixed(1)}k tok \xB7 ${cost > 0 ? m + fmtUsd(cost) + " \xB7 " : ""}` : "";
16509
16775
  agent.options.host?.takeToolCount?.();
@@ -16624,14 +16890,14 @@ var isCancelTeardown = (e) => {
16624
16890
  function installCancelGuards(mounted) {
16625
16891
  process.on("unhandledRejection", (e) => {
16626
16892
  if (isCancelTeardown(e)) {
16627
- log32.debug("suppressed unhandledRejection (cursor stream cancel)", e);
16893
+ log33.debug("suppressed unhandledRejection (cursor stream cancel)", e);
16628
16894
  return;
16629
16895
  }
16630
- log32.error("unhandledRejection", e);
16896
+ log33.error("unhandledRejection", e);
16631
16897
  });
16632
16898
  process.on("uncaughtException", (e) => {
16633
16899
  if (isCancelTeardown(e)) {
16634
- log32.debug("suppressed uncaughtException (cursor stream cancel)", e);
16900
+ log33.debug("suppressed uncaughtException (cursor stream cancel)", e);
16635
16901
  return;
16636
16902
  }
16637
16903
  console.error(e);
@@ -16948,6 +17214,7 @@ async function repl(args, ai, cfg, cwd) {
16948
17214
  const face = dx ? dx.voice : agent;
16949
17215
  const work = workerOptions ?? agent.options;
16950
17216
  const sendVia = dx ? (c, o) => dx.send(c, o) : void 0;
17217
+ const routeVia = autoRouteSend(args, agent, () => session, (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name)));
16951
17218
  const baseRules = () => [
16952
17219
  ...parsePermRules({ deny: args.disallowedTools }),
16953
17220
  ...parsePermRules({ allow: args.allowedTools }),
@@ -17052,7 +17319,7 @@ async function repl(args, ai, cfg, cwd) {
17052
17319
  try {
17053
17320
  mkdirSync12(dir, { recursive: true });
17054
17321
  } catch (e) {
17055
- log32.debug("paste image dir create failed", e);
17322
+ log33.debug("paste image dir create failed", e);
17056
17323
  }
17057
17324
  const img = grabClipboardImage(dir, String(Date.now()));
17058
17325
  return img ? { display: "Image", ref: "@" + img.path, path: img.path } : null;
@@ -17165,7 +17432,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
17165
17432
  mkdirSync12(join14(cwd, ".agent"), { recursive: true });
17166
17433
  appendFileSync(histPath, line + "\n");
17167
17434
  } catch (e) {
17168
- log32.debug("history write failed", e);
17435
+ log33.debug("history write failed", e);
17169
17436
  }
17170
17437
  };
17171
17438
  const ago = (t) => {
@@ -17237,7 +17504,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
17237
17504
  try {
17238
17505
  store.save(session);
17239
17506
  } catch (e) {
17240
- log32.debug("session save after rewind failed", e);
17507
+ log33.debug("session save after rewind failed", e);
17241
17508
  }
17242
17509
  err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
17243
17510
  `));
@@ -17272,14 +17539,14 @@ ${task}`;
17272
17539
  bangContext.length = 0;
17273
17540
  }
17274
17541
  const delta = await refreshCatalogs().catch((e) => {
17275
- log32.debug("catalog refresh failed", e);
17542
+ log33.debug("catalog refresh failed", e);
17276
17543
  return "";
17277
17544
  });
17278
17545
  if (delta) {
17279
17546
  err(dim(" \u27F3 skill/command catalog changed \u2014 delta attached to this turn\n"));
17280
17547
  task += "\n\n" + delta;
17281
17548
  }
17282
- const r = await runTurn(face, store, session, task, duplex ? void 0 : checkpoints, cwd, sendVia && ((c) => sendVia(c, opts)));
17549
+ const r = await runTurn(face, store, session, task, duplex ? void 0 : checkpoints, cwd, sendVia ? (c) => sendVia(c, opts) : routeVia);
17283
17550
  if (voiceIO) {
17284
17551
  voiceReasonEnd();
17285
17552
  voiceEchoEnd();
@@ -17536,7 +17803,7 @@ ${task}`;
17536
17803
  desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
17537
17804
  run: async () => {
17538
17805
  await refreshCatalogs().catch((e) => {
17539
- log32.debug("catalog refresh failed", e);
17806
+ log33.debug("catalog refresh failed", e);
17540
17807
  });
17541
17808
  face.reprepare();
17542
17809
  err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
@@ -18038,7 +18305,7 @@ ${task}`;
18038
18305
  try {
18039
18306
  for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
18040
18307
  } catch (e) {
18041
- log32.debug(`loadAgents(${d}) failed`, e);
18308
+ log33.debug(`loadAgents(${d}) failed`, e);
18042
18309
  }
18043
18310
  }
18044
18311
  if (!seen.size) {
@@ -18126,7 +18393,7 @@ ${task}`;
18126
18393
  }
18127
18394
  if (idx >= 0) {
18128
18395
  const old = mounted.splice(idx, 1)[0];
18129
- await old.client.close().catch((e) => log32.debug("mcp close failed", e));
18396
+ await old.client.close().catch((e) => log33.debug("mcp close failed", e));
18130
18397
  }
18131
18398
  try {
18132
18399
  const m = await mountMcpServer(name, conf);
@@ -18154,7 +18421,7 @@ ${task}`;
18154
18421
  }
18155
18422
  const m = mounted.splice(idx, 1)[0];
18156
18423
  remountMcpTools();
18157
- await m.client.close().catch((e) => log32.debug("mcp close failed", e));
18424
+ await m.client.close().catch((e) => log33.debug("mcp close failed", e));
18158
18425
  err(dim(` removed "${name}"
18159
18426
  `));
18160
18427
  return;
@@ -18357,7 +18624,7 @@ ${task}`;
18357
18624
  try {
18358
18625
  return readdirSync4(join14(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
18359
18626
  } catch (e) {
18360
- log32.debug("completion readdir failed", absDir, e);
18627
+ log33.debug("completion readdir failed", absDir, e);
18361
18628
  return null;
18362
18629
  }
18363
18630
  };
@@ -18494,7 +18761,7 @@ ${out}
18494
18761
  return;
18495
18762
  }
18496
18763
  await refreshCatalogs().catch((e) => {
18497
- log32.debug("catalog refresh failed", e);
18764
+ log33.debug("catalog refresh failed", e);
18498
18765
  });
18499
18766
  const sk = skills.find((x) => x.name === name);
18500
18767
  if (sk) {
@@ -18605,7 +18872,7 @@ ${out}
18605
18872
  if (delivery || cut) dx?.parkCutDelivery(delivery, !!cut, heard);
18606
18873
  const cutReply = cut && cut.full.length - cut.heard.length > 40 ? cut : null;
18607
18874
  const noteP = replaces ? replaces.note : !cutReply ? Promise.resolve("") : (instinct ? instinct.bargeIntent(text).catch((e) => {
18608
- log32.warn(`barge intent failed \u2014 model decides: ${e.message}`);
18875
+ log33.warn(`barge intent failed \u2014 model decides: ${e.message}`);
18609
18876
  return void 0;
18610
18877
  }) : Promise.resolve(void 0)).then(async (intent) => interruptionNote(cutReply, intent) + ackedNote(await instinct?.ackFor(text)));
18611
18878
  const cutIn = replaces ? replaces.cutIn : interjectNote;
@@ -18994,6 +19261,10 @@ async function main() {
18994
19261
  }
18995
19262
  const cwd = resolve4(args.cwd ?? process.cwd());
18996
19263
  const cfg = await loadConfig(cwd);
19264
+ if (!args.autoRoute && !args.model && (cfg.model === "auto" || !cfg.model && await jevAvailable())) {
19265
+ args.autoRoute = true;
19266
+ args.model = autoModels().standard;
19267
+ }
18997
19268
  cfg.permissions = mergePerms(loadClaudeSettings(cwd), mergePerms(loadPersistedRules(cwd), cfg.permissions));
18998
19269
  if (canPrompt && !args.yes && !isTrusted(cwd)) {
18999
19270
  const ok = await makeHost().confirm?.(`Trust this directory? agent.libx will read and edit files in:
@@ -19043,7 +19314,7 @@ async function main() {
19043
19314
  const store = new SessionStore(cwd);
19044
19315
  const session = startSession(args, store, agent, cwd);
19045
19316
  process.once("SIGINT", () => activeTurn?.abort());
19046
- const { ok, res } = await runTurn(agent, store, session, args.task, void 0, cwd);
19317
+ const { ok, res } = await runTurn(agent, store, session, args.task, void 0, cwd, autoRouteSend(args, agent, () => session, (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name))));
19047
19318
  if (cfg.reflectOnFailure && !ok && res?.finishReason !== "needs_input" && res && agent.options.memoryDir) {
19048
19319
  const _fsBase = agent.options.fs.getCwd() === "/" ? "" : agent.options.fs.getCwd();
19049
19320
  const slug2 = await reflectOnRun({ ai, model: agent.options.model, fs: agent.options.fs, dir: primaryMemDir(agent.options.memoryDir, `${_fsBase}/.agent/memory`), result: res });
@@ -19090,6 +19361,7 @@ export {
19090
19361
  runShellLine,
19091
19362
  runTurn,
19092
19363
  setMcpMentionResolver,
19093
- summarizeResult
19364
+ summarizeResult,
19365
+ turnCost
19094
19366
  };
19095
19367
  //# sourceMappingURL=cli.js.map