@livx.cc/agentx 0.99.86 → 0.99.88

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
@@ -3431,7 +3431,7 @@ function copyTextToClipboard(text, platform2 = process.platform) {
3431
3431
  }
3432
3432
 
3433
3433
  // cli/cli.ts
3434
- import { join as join15, resolve as resolve5, basename as basename4, extname, dirname as dirname6 } from "path";
3434
+ import { join as join16, resolve as resolve5, basename as basename4, extname, dirname as dirname6 } from "path";
3435
3435
  import { AIClient, DecideClient, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions, disposeClaudeCodeSessions } from "ai.libx.js";
3436
3436
 
3437
3437
  // src/ccBridge.ts
@@ -13144,6 +13144,7 @@ function bindSecondChannel(v, sc, live) {
13144
13144
  speak: (text, kind) => {
13145
13145
  if (!live() || v.audible) return false;
13146
13146
  if (kind === "backchannel") {
13147
+ if (sc.language !== "en") return false;
13147
13148
  blip = "";
13148
13149
  v.fireBackchannel();
13149
13150
  return;
@@ -13160,6 +13161,7 @@ function bindSecondChannel(v, sc, live) {
13160
13161
  function channelInput(sc, m) {
13161
13162
  if (m.type === "interim") {
13162
13163
  if (typeof m.aec === "boolean") sc.options.aec = m.aec;
13164
+ if (typeof m.lang === "string") sc.setLanguage(m.lang, true);
13163
13165
  if (typeof m.text === "string") sc.onInterim(m.text);
13164
13166
  return true;
13165
13167
  }
@@ -13346,12 +13348,244 @@ var JsonlDiagSink = class {
13346
13348
 
13347
13349
  // src/voice/secondChannel.ts
13348
13350
  init_logging();
13349
- var log29 = forComponent("SecondChannel");
13351
+
13352
+ // src/voice/channelLang.ts
13353
+ init_logging();
13354
+ import { mkdir, readFile, writeFile } from "fs/promises";
13355
+ import { join as join8 } from "path";
13356
+ var log29 = forComponent("ChannelBanks");
13357
+ var LINE_KINDS = ["backchannel", "filler", "bridge"];
13358
+ var BANK_KEYS = [...LINE_KINDS, "phatic"];
13359
+ var EN_PHATIC = [
13360
+ "hey",
13361
+ "hi",
13362
+ "hello",
13363
+ "yo",
13364
+ "hiya",
13365
+ "morning",
13366
+ "good morning",
13367
+ "good evening",
13368
+ "good afternoon",
13369
+ "thanks",
13370
+ "thank you",
13371
+ "ok",
13372
+ "okay",
13373
+ "yes",
13374
+ "yeah",
13375
+ "yep",
13376
+ "no",
13377
+ "nope",
13378
+ "sure",
13379
+ "bye",
13380
+ "goodbye",
13381
+ "how are you",
13382
+ "what's up",
13383
+ "whats up",
13384
+ "can you hear me"
13385
+ ];
13386
+ var EN_BANK = { ...CHANNEL_BANK, phatic: EN_PHATIC };
13387
+ var primaryLang = (tag) => tag.trim().toLowerCase().split(/[-_]/)[0];
13388
+ function isPhatic(text, bank) {
13389
+ const t = norm2(text);
13390
+ return !!t && !!bank?.phatic?.some((p) => {
13391
+ const q2 = norm2(p);
13392
+ return !!q2 && (t === q2 || t.startsWith(q2 + " "));
13393
+ });
13394
+ }
13395
+ var SCRIPTS = [
13396
+ [new RegExp("\\p{Script=Hebrew}", "gu"), "he"],
13397
+ [new RegExp("\\p{Script=Arabic}", "gu"), "ar"],
13398
+ [new RegExp("\\p{Script=Cyrillic}", "gu"), "ru"],
13399
+ [new RegExp("\\p{Script=Greek}", "gu"), "el"],
13400
+ [/[\p{Script=Hiragana}\p{Script=Katakana}]/gu, "ja"],
13401
+ [new RegExp("\\p{Script=Hangul}", "gu"), "ko"],
13402
+ [new RegExp("\\p{Script=Han}", "gu"), "zh"],
13403
+ [new RegExp("\\p{Script=Thai}", "gu"), "th"],
13404
+ [new RegExp("\\p{Script=Devanagari}", "gu"), "hi"],
13405
+ [new RegExp("\\p{Script=Latin}", "gu"), "latin"]
13406
+ ];
13407
+ var STOP3 = Object.fromEntries(Object.entries({
13408
+ en: "hi hello hey yes thanks let see check one sec just give right well look the and is are you i it to of that what this was have with for not but my me",
13409
+ es: "hola s\xED gracias bueno a me te lo se le del al el la los las que es y de en un una por para con no pero mi yo qu\xE9 est\xE1",
13410
+ fr: "bonjour salut oui merci le la les est et de des un une que je tu il pas pour avec mais mon qui",
13411
+ de: "hallo ja danke der die das und ist ich du nicht ein eine mit zu was aber mein wie auch",
13412
+ it: "ciao grazie s\xEC a mi ti si il lo la gli che \xE8 e di un una non per con ma mio io sono",
13413
+ pt: "ol\xE1 oi sim obrigado obrigada o a os as que \xE9 e de em um uma n\xE3o para com mas meu eu voc\xEA",
13414
+ nl: "hoi dank de het een en is ik je niet van dat met voor maar mijn wat"
13415
+ }).map(([k, v]) => [k, new Set(v.split(" "))]));
13416
+ var scriptOf = (w) => SCRIPTS.find(([re]) => {
13417
+ re.lastIndex = 0;
13418
+ return re.test(w);
13419
+ })?.[1] ?? null;
13420
+ function detectLanguage(text) {
13421
+ const ws = text.toLowerCase().match(new RegExp("\\p{L}+", "gu")) ?? [];
13422
+ const n = /* @__PURE__ */ new Map();
13423
+ for (const w of ws) {
13424
+ const s = scriptOf(w);
13425
+ if (s) n.set(s, (n.get(s) ?? 0) + 1);
13426
+ }
13427
+ const [best, c] = [...n].sort((a2, b2) => b2[1] - a2[1])[0] ?? [null, 0];
13428
+ if (best !== "latin") return c >= 1 && (best === "zh" || best === "ja" || c >= 2 || ws.length === 1) ? best : null;
13429
+ const score = Object.entries(STOP3).map(([l2, s]) => [l2, ws.filter((w) => s.has(w)).length]).sort((a2, b2) => b2[1] - a2[1]);
13430
+ const [[l, a], [, b]] = score;
13431
+ return a > b && (a >= 2 || a === 1 && ws.length <= 3) ? l : null;
13432
+ }
13433
+ var NAMES = new Intl.DisplayNames(["en"], { type: "language" });
13434
+ var languageName = (lang) => {
13435
+ try {
13436
+ return NAMES.of(lang) ?? lang;
13437
+ } catch {
13438
+ return lang;
13439
+ }
13440
+ };
13441
+ function fitsLanguage(line, lang) {
13442
+ const own = SCRIPTS.find(([, l]) => l === lang)?.[0];
13443
+ if (own) return (line.match(own)?.length ?? 0) > 0 && !new RegExp("\\p{Script=Latin}{2,}", "u").test(line);
13444
+ if (/[^\p{Script=Latin}\p{M}\p{P}\p{Z}\p{N}\p{S}]/u.test(line)) return false;
13445
+ const mine = STOP3[lang];
13446
+ const ws = line.toLowerCase().match(new RegExp("\\p{L}+", "gu")) ?? [];
13447
+ return !ws.some((w) => !mine?.has(w) && Object.entries(STOP3).some(([l, s]) => l !== lang && s.has(w)));
13448
+ }
13449
+ var norm2 = (s) => s.toLowerCase().replace(/[^\p{L}\p{N}' ]/gu, "").replace(/\s+/g, " ").trim();
13450
+ function cleanLine(s, lang, maxWords = 6) {
13451
+ const raw = s.trim().split("\n")[0];
13452
+ const l = raw.replace(/^[-*•\d.)\s]+/, "").replace(/^["'“”«»]+|["'“”«»]+$/g, "").replace(new RegExp("^\\p{L}+:\\s*", "u"), "").trim();
13453
+ if (!l || isLeakSuspect(raw) || isLeakSuspect(l)) {
13454
+ if (l) log29.debug(`line "${raw}" reads as meta \u2014 rejected`);
13455
+ return "";
13456
+ }
13457
+ if (lang && !fitsLanguage(l, lang)) {
13458
+ log29.debug(`line "${l}" is not ${lang} \u2014 rejected`);
13459
+ return "";
13460
+ }
13461
+ return l.split(/\s+/).length > maxWords || l.length > 60 ? "" : l;
13462
+ }
13463
+ function validateBank(raw, lang, minPerKind = 3) {
13464
+ if (!raw || typeof raw !== "object") return null;
13465
+ const out = {};
13466
+ for (const k of BANK_KEYS) {
13467
+ const v = raw[k];
13468
+ const seen = /* @__PURE__ */ new Set();
13469
+ out[k] = (Array.isArray(v) ? v : []).filter((x) => typeof x === "string").map((x) => cleanLine(x, lang)).filter((x) => x && !seen.has(norm2(x)) && seen.add(norm2(x)));
13470
+ if (out[k].length < minPerKind) {
13471
+ log29.warn(`bank ${lang}: only ${out[k].length} valid ${k} lines \u2014 rejected`);
13472
+ return null;
13473
+ }
13474
+ }
13475
+ return out;
13476
+ }
13477
+ var KIND_HINT = {
13478
+ backchannel: 'tiny listener cues while they keep talking (like "mm-hm", "right") \u2014 never takes the turn',
13479
+ filler: 'soft thinking sounds at their pause (like "hmm\u2026")',
13480
+ bridge: 'quick lead-ins before the real answer while it is prepared (like "let me check\u2014")',
13481
+ phatic: 'greetings, thanks and yes/no openers a person starts a turn with (like "hi", "thanks", "how are you")'
13482
+ };
13483
+ function fastBankGenerator(ai, model, perKind = 12) {
13484
+ return async (lang, avoid, signal) => {
13485
+ const name = languageName(lang);
13486
+ const prompt = `Write short spoken side-sounds for a live voice conversation, in ${name} (natural, casual, how native ${name} speakers really talk \u2014 not translated English). ${perKind} varied lines per kind, 1-4 words each \u2014 short, idiomatic, grammatical everyday phrases a native would actually say:
13487
+ ${BANK_KEYS.map((k) => `- ${k}: ${KIND_HINT[k]}`).join("\n")}
13488
+ ` + (avoid.length ? `Do not repeat these existing ones: ${avoid.slice(0, 60).join(" | ")}
13489
+ ` : "") + `Reply with JSON only: {"backchannel":[...],"filler":[...],"bridge":[...],"phatic":[...]}. Every line in ${name}; no explanations.`;
13490
+ const reasoning = /gpt-oss/.test(model) ? { providerOptions: { reasoning_effort: "low" } } : {};
13491
+ const r = await ai.chat({ model, messages: [{ role: "user", content: prompt }], stream: false, signal, maxTokens: 2e3, ...reasoning });
13492
+ const m = (r.content ?? "").match(/\{[\s\S]*\}/);
13493
+ if (!m) throw new Error(`no JSON in bank reply: ${(r.content ?? "").slice(0, 80)}`);
13494
+ return JSON.parse(m[0]);
13495
+ };
13496
+ }
13497
+ var ChannelBanksOptions = class {
13498
+ /** Raw bank for a language (fastBankGenerator). Absent → English fallback only. */
13499
+ generate;
13500
+ /** Disk cache dir (one `<lang>.json` per language). Absent → in-memory only. */
13501
+ cacheDir;
13502
+ /** A cached bank older than this is still used, and topped up by one background call. */
13503
+ refreshMs = 24 * 36e5;
13504
+ /** Lines kept per kind (newest win) — the cap on growth. */
13505
+ maxPerKind = 40;
13506
+ timeoutMs = 2e4;
13507
+ /** After a failed cold generation, the next attempt waits this long. */
13508
+ retryMs = 6e4;
13509
+ /** Cold-start/offline banks (English only by default). */
13510
+ fallback = { en: EN_BANK };
13511
+ now = () => Date.now();
13512
+ };
13513
+ var ChannelBanks = class {
13514
+ constructor(options) {
13515
+ this.options = options;
13516
+ this.options = { ...new ChannelBanksOptions(), ...options };
13517
+ }
13518
+ options;
13519
+ banks = /* @__PURE__ */ new Map();
13520
+ pending = /* @__PURE__ */ new Map();
13521
+ get o() {
13522
+ return this.options;
13523
+ }
13524
+ /** The bank to speak from for `lang` now — generated/cached, else the fallback, else null (= stay silent). */
13525
+ get(lang) {
13526
+ return this.banks.get(lang) ?? this.o.fallback[lang] ?? null;
13527
+ }
13528
+ /** Load (cache) and/or generate `lang`'s bank in the background. Never throws, never blocks the caller. */
13529
+ ensure(lang) {
13530
+ if (!this.pending.has(lang)) this.pending.set(lang, this.load(lang));
13531
+ return this.pending.get(lang);
13532
+ }
13533
+ file(lang) {
13534
+ return this.o.cacheDir ? join8(this.o.cacheDir, `${lang.replace(/[^\w-]/g, "")}.json`) : null;
13535
+ }
13536
+ async load(lang) {
13537
+ const f = this.file(lang);
13538
+ let cached = null;
13539
+ if (f) cached = await readFile(f, "utf8").then((s) => JSON.parse(s)).catch((e) => {
13540
+ if (e.code !== "ENOENT") log29.warn(`bank cache ${f} unreadable \u2014 regenerating: ${e.message}`);
13541
+ return null;
13542
+ });
13543
+ const valid = cached && validateBank(cached.bank, lang, 1);
13544
+ if (valid) {
13545
+ this.banks.set(lang, valid);
13546
+ log29.debug(`bank ${lang}: cache hit (${BANK_KEYS.map((k) => valid[k].length).join("/")})`);
13547
+ }
13548
+ if (valid && this.o.now() - cached.at < this.o.refreshMs) return;
13549
+ await this.generate(lang, valid);
13550
+ }
13551
+ async generate(lang, have) {
13552
+ if (!this.o.generate) return;
13553
+ const ac = new AbortController();
13554
+ const t = setTimeout(() => ac.abort(), this.o.timeoutMs);
13555
+ const t0 = this.o.now();
13556
+ try {
13557
+ const fresh = validateBank(await this.o.generate(lang, have ? BANK_KEYS.flatMap((k) => have[k]) : [], ac.signal), lang);
13558
+ if (!fresh) return;
13559
+ const bank = {};
13560
+ for (const k of BANK_KEYS) {
13561
+ const seen = /* @__PURE__ */ new Set();
13562
+ bank[k] = [...fresh[k], ...have?.[k] ?? []].filter((x) => !seen.has(norm2(x)) && seen.add(norm2(x))).slice(0, this.o.maxPerKind);
13563
+ }
13564
+ this.banks.set(lang, bank);
13565
+ log29.info(`bank ${lang}: ${have ? "refreshed" : "generated"} (${BANK_KEYS.map((k) => bank[k].length).join("/")}) in ${Math.round(this.o.now() - t0)}ms`);
13566
+ const f = this.file(lang);
13567
+ if (f) await mkdir(this.o.cacheDir, { recursive: true }).then(() => writeFile(f, JSON.stringify({ at: this.o.now(), bank }))).catch((e) => log29.warn(`bank cache write ${f} failed: ${e.message}`));
13568
+ } catch (e) {
13569
+ log29.warn(`bank ${lang}: generation failed \u2014 ${have ? "keeping the cached bank" : lang in this.o.fallback ? "fallback bank" : "silent"}: ${e.message}`);
13570
+ } finally {
13571
+ clearTimeout(t);
13572
+ if (!this.banks.has(lang)) setTimeout(() => this.pending.delete(lang), this.o.retryMs).unref?.();
13573
+ }
13574
+ }
13575
+ };
13576
+
13577
+ // src/voice/secondChannel.ts
13578
+ var log30 = forComponent("SecondChannel");
13350
13579
  var SecondChannelOptions = class {
13351
13580
  /** Refiner (Jev/fast LLM): the choice over CHANNEL_ACTIONS, or null on failure. Absent → the rule alone decides. */
13352
13581
  decide;
13353
13582
  /** Fast-model line writer (see fastLineGenerator). Absent or late → the canned bank. */
13354
13583
  generate;
13584
+ /** Per-language banks (ChannelBanks). Absent → the hardcoded English bank, and silence in any other language. */
13585
+ banks;
13586
+ /** The host's language (BCP-47; pinned — detection never overrides it). Absent → unknown until the user's words
13587
+ * tell it confidently; the channel is silent until then (never English by default). */
13588
+ lang;
13355
13589
  /** Kinds worth generating (content-conditioned); the rest are canned sounds. */
13356
13590
  generateFor = ["backchannel", "bridge"];
13357
13591
  /** Host TTS on the second channel; resolves when the line finished playing. `false` = the host could not play it. */
@@ -13388,17 +13622,16 @@ var SecondChannelOptions = class {
13388
13622
  random = Math.random;
13389
13623
  };
13390
13624
  var words2 = (s) => s.trim() ? s.trim().split(/\s+/) : [];
13391
- var QUICK = /^(hey|hi|hello|yo|hiya|morning|good (morning|evening|afternoon)|thanks|thank you|ok(ay)?|yes|yeah|yep|no|nope|sure|bye|goodbye|how are you|what'?s up|can you hear me)\b/i;
13392
13625
  function withBudget(p, ms, what) {
13393
13626
  let t;
13394
13627
  const late = new Promise((r) => {
13395
13628
  t = setTimeout(() => {
13396
- log29.debug(`${what}: over the ${ms}ms budget \u2014 dropped`);
13629
+ log30.debug(`${what}: over the ${ms}ms budget \u2014 dropped`);
13397
13630
  r(null);
13398
13631
  }, ms);
13399
13632
  });
13400
13633
  return Promise.race([p.catch((e) => {
13401
- log29.warn(`${what} failed \u2014 falling back: ${e?.message ?? e}`);
13634
+ log30.warn(`${what} failed \u2014 falling back: ${e?.message ?? e}`);
13402
13635
  return null;
13403
13636
  }), late]).finally(() => clearTimeout(t));
13404
13637
  }
@@ -13407,6 +13640,7 @@ var SecondChannel = class {
13407
13640
  this.options = options;
13408
13641
  this.options = { ...new SecondChannelOptions(), ...options };
13409
13642
  this.startedAt = this.o.now();
13643
+ if (this.o.lang) this.setLanguage(this.o.lang, true);
13410
13644
  }
13411
13645
  options;
13412
13646
  heard = "";
@@ -13431,6 +13665,33 @@ var SecondChannel = class {
13431
13665
  picks = /* @__PURE__ */ new Map();
13432
13666
  inflight = null;
13433
13667
  startedAt;
13668
+ lang = null;
13669
+ pinned = false;
13670
+ /** The language lines are spoken in (detected from the user's words, or set by the host). */
13671
+ get language() {
13672
+ return this.lang;
13673
+ }
13674
+ /** Switch language: its bank starts loading in the background. `host` = the host's STT-reported language — it pins
13675
+ * the language (later detection never overrides it; only another host call does). Tags normalize to 'he-IL' → 'he'. */
13676
+ setLanguage(tag, host = false) {
13677
+ if (!tag || this.pinned && !host) return;
13678
+ if (host) this.pinned = true;
13679
+ const lang = primaryLang(tag);
13680
+ if (!lang || lang === this.lang) return;
13681
+ log30.info(`language ${this.lang} \u2192 ${lang}`);
13682
+ this.lang = lang;
13683
+ this.recent = [];
13684
+ this.picks.clear();
13685
+ void this.o.banks?.ensure(lang);
13686
+ }
13687
+ detect(text) {
13688
+ if (this.pinned) return;
13689
+ const l = detectLanguage(text);
13690
+ if (l) this.setLanguage(l);
13691
+ }
13692
+ get bank() {
13693
+ return this.lang ? this.o.banks ? this.o.banks.get(this.lang) : this.lang === "en" ? EN_BANK : null : null;
13694
+ }
13434
13695
  get o() {
13435
13696
  return this.options;
13436
13697
  }
@@ -13440,6 +13701,7 @@ var SecondChannel = class {
13440
13701
  if (!t || t === this.heard) return;
13441
13702
  if (this.main !== "idle") this.onAgentState("idle");
13442
13703
  this.heard = t;
13704
+ this.detect(t);
13443
13705
  this.userSpeaking = true;
13444
13706
  if (this.pauseTimer) clearTimeout(this.pauseTimer);
13445
13707
  this.pauseTimer = setTimeout(() => {
@@ -13482,6 +13744,7 @@ var SecondChannel = class {
13482
13744
  /** The user's final line was dispatched to the main agent (its words are the bridge's micro-context). */
13483
13745
  onUserTurn(text) {
13484
13746
  this.heard = text.trim();
13747
+ this.detect(this.heard);
13485
13748
  this.main = "idle";
13486
13749
  this.onAgentState("queued");
13487
13750
  }
@@ -13499,7 +13762,7 @@ var SecondChannel = class {
13499
13762
  try {
13500
13763
  this.o.cancel();
13501
13764
  } catch (e) {
13502
- log29.warn(`cancel threw: ${e.message}`);
13765
+ log30.warn(`cancel threw: ${e.message}`);
13503
13766
  }
13504
13767
  }
13505
13768
  }
@@ -13533,7 +13796,8 @@ var SecondChannel = class {
13533
13796
  /** 0ms rule: the candidate line for this trigger, or silence. */
13534
13797
  rule(trigger, pauseMs = 0) {
13535
13798
  const w = words2(this.heard);
13536
- if (w.length <= 3 || QUICK.test(this.heard) && w.length <= 6) return "silence";
13799
+ if (!this.lang) return "silence";
13800
+ if (w.length <= 3 || w.length <= 6 && isPhatic(this.heard, this.bank)) return "silence";
13537
13801
  const k = trigger === "interim" ? w.length >= this.o.backchannelMinWords && /[,.;:!?…]$/.test(this.heard) ? "backchannel" : null : trigger === "pause" ? pauseMs >= this.o.fillerPauseMs && w.length >= this.o.fillerMinWords ? "filler" : null : w.length >= this.o.bridgeMinWords ? "bridge" : null;
13538
13802
  return k && this.allowed(k) ? k : "silence";
13539
13803
  }
@@ -13559,6 +13823,7 @@ var SecondChannel = class {
13559
13823
  if (stale()) return ev({ choice, kind, acted: "stale" });
13560
13824
  if (!this.allowed(kind)) return ev({ choice, kind, acted: "gated" });
13561
13825
  const text = await this.line(kind);
13826
+ if (!text) return ev({ choice, kind, acted: "no_bank" });
13562
13827
  if (stale()) return ev({ choice, kind, text, acted: "stale" });
13563
13828
  if (!this.allowed(kind)) return ev({ choice, kind, text, acted: "gated" });
13564
13829
  ev({ choice, kind, text, acted: this.say(kind, text) ? "spoken" : "skipped" });
@@ -13580,18 +13845,20 @@ var SecondChannel = class {
13580
13845
  async line(kind) {
13581
13846
  if (this.o.generate && this.o.generateFor.includes(kind)) {
13582
13847
  const ac = new AbortController();
13583
- const ctx = { kind, heard: this.tail(), state: this.main, avoid: [...this.recent], ...this.lead ? { lead: this.lead } : {} };
13848
+ const ctx = { kind, heard: this.tail(), state: this.main, avoid: [...this.recent], lang: this.lang, ...this.lead ? { lead: this.lead } : {} };
13584
13849
  const raw = await withBudget(this.o.generate(ctx, ac.signal), this.o.generateBudgetMs, `${kind} generate`);
13585
13850
  if (raw === null) ac.abort();
13586
- const text = cleanLine(raw ?? "");
13851
+ const text = cleanLine(raw ?? "", this.lang);
13587
13852
  if (text && !this.recent.some((r) => norm2(r) === norm2(text))) return text;
13588
- if (text) log29.debug(`generated "${text}" repeats a recent line \u2014 canned`);
13853
+ if (text) log30.debug(`generated "${text}" repeats a recent line \u2014 canned`);
13589
13854
  }
13590
13855
  return this.pick(kind);
13591
13856
  }
13592
- /** Canned rotation: never one of the last `bankExclude` picks of this kind, nor a recent line. */
13857
+ /** Canned rotation in the user's language: never one of the last `bankExclude` picks of this kind, nor a recent
13858
+ * line. '' = no bank for this language yet (silence). */
13593
13859
  pick(kind) {
13594
- const pool = CHANNEL_BANK[kind];
13860
+ const pool = this.bank?.[kind] ?? [];
13861
+ if (!pool.length) return "";
13595
13862
  const hist = this.picks.get(kind) ?? [];
13596
13863
  const recent = new Set([...hist.slice(-this.o.bankExclude), ...this.recent].map(norm2));
13597
13864
  const fresh = pool.filter((x) => !recent.has(norm2(x)));
@@ -13605,7 +13872,7 @@ var SecondChannel = class {
13605
13872
  try {
13606
13873
  r = this.o.speak(text, kind);
13607
13874
  } catch (e) {
13608
- log29.warn(`speak threw \u2014 line dropped: ${e.message}`);
13875
+ log30.warn(`speak threw \u2014 line dropped: ${e.message}`);
13609
13876
  return false;
13610
13877
  }
13611
13878
  if (r === false) return false;
@@ -13617,7 +13884,7 @@ var SecondChannel = class {
13617
13884
  this.o.show(text, kind);
13618
13885
  const p = Promise.resolve(r).then(() => {
13619
13886
  }, (e) => {
13620
- log29.warn(`channel line playback failed: ${e?.message ?? e}`);
13887
+ log30.warn(`channel line playback failed: ${e?.message ?? e}`);
13621
13888
  }).finally(() => {
13622
13889
  if (this.inflight === p) this.inflight = null;
13623
13890
  });
@@ -13625,29 +13892,19 @@ var SecondChannel = class {
13625
13892
  return true;
13626
13893
  }
13627
13894
  };
13628
- var norm2 = (s) => s.toLowerCase().replace(/[^a-z' ]/g, "").trim();
13629
- function cleanLine(s) {
13630
- const raw = s.trim().split("\n")[0];
13631
- const l = raw.replace(/^["'“”]+|["'“”]+$/g, "").replace(/^\w+:\s*/, "").trim();
13632
- if (isLeakSuspect(raw) || isLeakSuspect(l)) {
13633
- if (l) log29.debug(`generated "${raw}" reads as meta \u2014 canned`);
13634
- return "";
13635
- }
13636
- return words2(l).length > 6 ? "" : l;
13637
- }
13638
- var KIND_HINT = {
13895
+ var KIND_HINT2 = {
13639
13896
  backchannel: "a tiny listener cue while they keep talking \u2014 shows you follow THEIR point, does not take the turn",
13640
13897
  filler: "a soft thinking sound at their pause",
13641
13898
  bridge: "a quick lead-in before the real answer, while it is being prepared"
13642
13899
  };
13643
13900
  function fastLineGenerator(ai, model) {
13644
13901
  return async (c, signal) => {
13645
- const prompt = `You voice the tiny side-sounds of a live spoken conversation. Write ONE line of 1-5 words: ${KIND_HINT[c.kind]}.
13902
+ const prompt = `You voice the tiny side-sounds of a live spoken conversation. Write ONE line of 1-5 words: ${KIND_HINT2[c.kind]}.
13646
13903
  The user just said: "${c.heard}"
13647
13904
  The assistant is: ${c.state}.
13648
13905
  ` + (c.lead ? `The real answer begins: "${c.lead.trim()}" \u2014 lead naturally into it; do not repeat it.
13649
13906
  ` : "") + (c.avoid.length ? `Do not reuse any of: ${c.avoid.map((a) => `"${a}"`).join(", ")}.
13650
- ` : "") + "React to their actual words, casually. Output only the line.";
13907
+ ` : "") + `React to their actual words, casually, in ${languageName(c.lang)}. Output only the line.`;
13651
13908
  const reasoning = /gpt-oss/.test(model) ? { providerOptions: { reasoning_effort: "low" } } : {};
13652
13909
  const r = await ai.chat({ model, messages: [{ role: "user", content: prompt }], stream: false, signal, maxTokens: 150, ...reasoning });
13653
13910
  return r.content ?? "";
@@ -13657,13 +13914,13 @@ The assistant is: ${c.state}.
13657
13914
  // cli/config.ts
13658
13915
  import { homedir as homedir5 } from "os";
13659
13916
  import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
13660
- import { join as join9 } from "path";
13917
+ import { join as join10 } from "path";
13661
13918
  import { pathToFileURL } from "url";
13662
13919
 
13663
13920
  // cli/permissions.ts
13664
13921
  import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
13665
13922
  import { homedir as homedir4 } from "os";
13666
- import { join as join8 } from "path";
13923
+ import { join as join9 } from "path";
13667
13924
  var RULE_RE = /^(\w[\w*-]*)(?:\((.+)\))?$/;
13668
13925
  function parseOne(raw, decision) {
13669
13926
  const m = RULE_RE.exec(raw.trim());
@@ -13685,13 +13942,13 @@ function parsePermRules(perms, explicit = false) {
13685
13942
  function describeRule(r) {
13686
13943
  return `${r.decision.padEnd(5)} ${r.tool ?? "*"}${r.pathGlob ? `(${r.pathGlob})` : ""}`;
13687
13944
  }
13688
- var PERM_FILE = (cwd) => join8(cwd, ".agent", "permissions.json");
13945
+ var PERM_FILE = (cwd) => join9(cwd, ".agent", "permissions.json");
13689
13946
  function loadPersistedRules(cwd) {
13690
13947
  const j = readJsonFile(PERM_FILE(cwd), null);
13691
13948
  return j ? { allow: j.allow ?? [], ask: j.ask ?? [], deny: j.deny ?? [] } : {};
13692
13949
  }
13693
13950
  function loadClaudeSettings(cwd, home = homedir4()) {
13694
- const files = [join8(home, ".claude", "settings.json"), join8(cwd, ".claude", "settings.json"), join8(cwd, ".claude", "settings.local.json")];
13951
+ const files = [join9(home, ".claude", "settings.json"), join9(cwd, ".claude", "settings.json"), join9(cwd, ".claude", "settings.local.json")];
13695
13952
  let out = {};
13696
13953
  for (const p of files) {
13697
13954
  const perms = readJsonFile(p, null)?.permissions;
@@ -13704,7 +13961,7 @@ function persistRule(cwd, decision, ruleStr) {
13704
13961
  const list = cur[decision] ??= [];
13705
13962
  if (!list.includes(ruleStr)) list.push(ruleStr);
13706
13963
  try {
13707
- mkdirSync7(join8(cwd, ".agent"), { recursive: true });
13964
+ mkdirSync7(join9(cwd, ".agent"), { recursive: true });
13708
13965
  writeFileSync4(PERM_FILE(cwd), JSON.stringify(cur, null, 2) + "\n");
13709
13966
  } catch {
13710
13967
  }
@@ -13718,7 +13975,7 @@ function mergePerms(a, b) {
13718
13975
  }
13719
13976
  return Object.keys(out).length ? out : void 0;
13720
13977
  }
13721
- var TRUST_FILE = join8(homedir4(), ".agent", "trusted.json");
13978
+ var TRUST_FILE = join9(homedir4(), ".agent", "trusted.json");
13722
13979
  function isTrusted(cwd, file = TRUST_FILE) {
13723
13980
  const list = readJsonFile(file, []);
13724
13981
  return Array.isArray(list) && list.includes(cwd);
@@ -13728,7 +13985,7 @@ function trustDir(cwd, file = TRUST_FILE) {
13728
13985
  if (!Array.isArray(list)) list = [];
13729
13986
  if (!list.includes(cwd)) list.push(cwd);
13730
13987
  try {
13731
- mkdirSync7(join8(file, ".."), { recursive: true });
13988
+ mkdirSync7(join9(file, ".."), { recursive: true });
13732
13989
  writeFileSync4(file, JSON.stringify(list, null, 2) + "\n");
13733
13990
  } catch {
13734
13991
  }
@@ -13738,7 +13995,7 @@ function trustDir(cwd, file = TRUST_FILE) {
13738
13995
  var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
13739
13996
  async function loadFrom(dir) {
13740
13997
  for (const f of FILES) {
13741
- const p = join9(dir, ".agent", f);
13998
+ const p = join10(dir, ".agent", f);
13742
13999
  if (!existsSync5(p)) continue;
13743
14000
  try {
13744
14001
  const mod = await import(pathToFileURL(p).href, f.endsWith(".json") ? { with: { type: "json" } } : void 0);
@@ -13751,7 +14008,7 @@ async function loadFrom(dir) {
13751
14008
  return {};
13752
14009
  }
13753
14010
  function loadSettings(dir) {
13754
- const p = join9(dir, ".agent", "settings.json");
14011
+ const p = join10(dir, ".agent", "settings.json");
13755
14012
  if (!existsSync5(p)) return {};
13756
14013
  try {
13757
14014
  const raw = JSON.parse(readFileSync7(p, "utf8"));
@@ -13800,7 +14057,7 @@ function applyOverlay(cfg, file) {
13800
14057
  // cli/hooks-config.ts
13801
14058
  import { spawnSync as spawnSync4 } from "child_process";
13802
14059
  init_platform();
13803
- var log30 = forComponent("hooks");
14060
+ var log31 = forComponent("hooks");
13804
14061
  var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
13805
14062
  function ruleMatches(rule, toolName) {
13806
14063
  if (!rule.tool || rule.tool === "*") return true;
@@ -13819,7 +14076,7 @@ function runCmd(rule, env) {
13819
14076
  });
13820
14077
  return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
13821
14078
  } catch (e) {
13822
- log30.debug(`hook command failed: ${rule.command}`, e);
14079
+ log31.debug(`hook command failed: ${rule.command}`, e);
13823
14080
  return { code: 1, out: String(e?.message ?? e) };
13824
14081
  }
13825
14082
  }
@@ -13925,10 +14182,10 @@ function formatDiff(ops, opts = {}) {
13925
14182
  // cli/session.ts
13926
14183
  import { existsSync as existsSync6, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync5, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync, statSync as statSync7 } from "fs";
13927
14184
  import { homedir as homedir6 } from "os";
13928
- import { join as join10 } from "path";
14185
+ import { join as join11 } from "path";
13929
14186
  init_platform();
13930
- var log31 = forComponent("session");
13931
- var globalDir = () => join10(homedir6(), ".agent", "sessions");
14187
+ var log32 = forComponent("session");
14188
+ var globalDir = () => join11(homedir6(), ".agent", "sessions");
13932
14189
  function indexLink(target, link2) {
13933
14190
  try {
13934
14191
  symlinkSync2(target, link2);
@@ -13950,7 +14207,7 @@ function readIndexLink(link2) {
13950
14207
  var SessionStore = class {
13951
14208
  dir;
13952
14209
  constructor(cwd) {
13953
- this.dir = join10(cwd, ".agent", "sessions");
14210
+ this.dir = join11(cwd, ".agent", "sessions");
13954
14211
  }
13955
14212
  /** Sortable, human-readable id: `YYYYMMDD-HHMMSS-<folder>`. */
13956
14213
  newId(now4 = Date.now(), cwd) {
@@ -13958,10 +14215,10 @@ var SessionStore = class {
13958
14215
  const p = (n, w = 2) => String(n).padStart(w, "0");
13959
14216
  const slug2 = pathTail(cwd ?? process.cwd())?.replace(/[^A-Za-z0-9_-]/g, "") || "session";
13960
14217
  let id = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${slug2}`;
13961
- if (existsSync6(this.dir) && existsSync6(join10(this.dir, `${id}.json`))) {
14218
+ if (existsSync6(this.dir) && existsSync6(join11(this.dir, `${id}.json`))) {
13962
14219
  for (let i = 2; i <= 99; i++) {
13963
14220
  const c = `${id}-${i}`;
13964
- if (!existsSync6(join10(this.dir, `${c}.json`))) {
14221
+ if (!existsSync6(join11(this.dir, `${c}.json`))) {
13965
14222
  id = c;
13966
14223
  break;
13967
14224
  }
@@ -13976,29 +14233,29 @@ var SessionStore = class {
13976
14233
  save(data) {
13977
14234
  if (!this.safeId(data.meta.id)) throw new Error(`unsafe session id: ${data.meta.id}`);
13978
14235
  if (!existsSync6(this.dir)) mkdirSync8(this.dir, { recursive: true });
13979
- const path = join10(this.dir, `${data.meta.id}.json`);
14236
+ const path = join11(this.dir, `${data.meta.id}.json`);
13980
14237
  const tmp = `${path}.${process.pid}.tmp`;
13981
14238
  writeFileSync5(tmp, JSON.stringify(data));
13982
14239
  renameSync(tmp, path);
13983
14240
  try {
13984
14241
  const gd = globalDir();
13985
14242
  if (!existsSync6(gd)) mkdirSync8(gd, { recursive: true });
13986
- const link2 = join10(gd, `${data.meta.id}.json`);
14243
+ const link2 = join11(gd, `${data.meta.id}.json`);
13987
14244
  if (!existsSync6(link2)) indexLink(path, link2);
13988
14245
  } catch {
13989
14246
  }
13990
14247
  }
13991
14248
  load(id) {
13992
14249
  if (!this.safeId(id)) {
13993
- log31.debug(`rejecting unsafe session id: ${id}`);
14250
+ log32.debug(`rejecting unsafe session id: ${id}`);
13994
14251
  return void 0;
13995
14252
  }
13996
- const path = join10(this.dir, `${id}.json`);
14253
+ const path = join11(this.dir, `${id}.json`);
13997
14254
  if (!existsSync6(path)) return void 0;
13998
14255
  try {
13999
14256
  return JSON.parse(readFileSync8(path, "utf8"));
14000
14257
  } catch (e) {
14001
- log31.debug(`unreadable session ${id} \u2014 ignoring`, e);
14258
+ log32.debug(`unreadable session ${id} \u2014 ignoring`, e);
14002
14259
  return void 0;
14003
14260
  }
14004
14261
  }
@@ -14009,9 +14266,9 @@ var SessionStore = class {
14009
14266
  for (const f of readdirSync(this.dir)) {
14010
14267
  if (!f.endsWith(".json")) continue;
14011
14268
  try {
14012
- metas.push(JSON.parse(readFileSync8(join10(this.dir, f), "utf8")).meta);
14269
+ metas.push(JSON.parse(readFileSync8(join11(this.dir, f), "utf8")).meta);
14013
14270
  } catch (e) {
14014
- log31.debug(`skipping unreadable session file ${f}`, e);
14271
+ log32.debug(`skipping unreadable session file ${f}`, e);
14015
14272
  }
14016
14273
  }
14017
14274
  return metas.sort((a, b) => b.updated - a.updated);
@@ -14024,7 +14281,7 @@ var SessionStore = class {
14024
14281
  for (const f of readdirSync(this.dir)) {
14025
14282
  if (!f.endsWith(".json")) continue;
14026
14283
  try {
14027
- const st = statSync7(join10(this.dir, f));
14284
+ const st = statSync7(join11(this.dir, f));
14028
14285
  out.push({ id: f.slice(0, -5), mtime: st.mtimeMs, size: st.size });
14029
14286
  } catch {
14030
14287
  }
@@ -14043,7 +14300,7 @@ var SessionStore = class {
14043
14300
  function globalSessionLoad(idOrPrefix) {
14044
14301
  const gd = globalDir();
14045
14302
  if (!existsSync6(gd)) return void 0;
14046
- const exact = join10(gd, `${idOrPrefix}.json`);
14303
+ const exact = join11(gd, `${idOrPrefix}.json`);
14047
14304
  if (existsSync6(exact)) {
14048
14305
  try {
14049
14306
  const target = readIndexLink(exact);
@@ -14057,7 +14314,7 @@ function globalSessionLoad(idOrPrefix) {
14057
14314
  if (!f.endsWith(".json")) continue;
14058
14315
  const base = f.slice(0, -5);
14059
14316
  if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
14060
- const target = readIndexLink(join10(gd, f));
14317
+ const target = readIndexLink(join11(gd, f));
14061
14318
  return JSON.parse(readFileSync8(target, "utf8"));
14062
14319
  }
14063
14320
  }
@@ -14072,10 +14329,10 @@ function globalSessionList() {
14072
14329
  for (const f of readdirSync(gd)) {
14073
14330
  if (!f.endsWith(".json")) continue;
14074
14331
  try {
14075
- const target = readIndexLink(join10(gd, f));
14332
+ const target = readIndexLink(join11(gd, f));
14076
14333
  if (!existsSync6(target)) {
14077
14334
  try {
14078
- unlinkSync(join10(gd, f));
14335
+ unlinkSync(join11(gd, f));
14079
14336
  } catch {
14080
14337
  }
14081
14338
  continue;
@@ -14249,8 +14506,8 @@ import { execFile as execFile4 } from "child_process";
14249
14506
  import { promisify } from "util";
14250
14507
  import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync9 } from "fs";
14251
14508
  import { homedir as homedir7 } from "os";
14252
- import { join as join11, resolve as resolve4, sep as sep3, parse as parsePath } from "path";
14253
- var log32 = forComponent("checkpoints");
14509
+ import { join as join12, resolve as resolve4, sep as sep3, parse as parsePath } from "path";
14510
+ var log33 = forComponent("checkpoints");
14254
14511
  var exec = promisify(execFile4);
14255
14512
  var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
14256
14513
  function isBroadRoot(p) {
@@ -14292,11 +14549,11 @@ var ShadowRepo = class {
14292
14549
  mkdirSync9(this.gitDir, { recursive: true });
14293
14550
  const valid = await this.run("rev-parse", "--git-dir").then(() => true, () => false);
14294
14551
  if (!valid) await this.run("init", "-q");
14295
- mkdirSync9(join11(this.gitDir, "info"), { recursive: true });
14296
- writeFileSync6(join11(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
14552
+ mkdirSync9(join12(this.gitDir, "info"), { recursive: true });
14553
+ writeFileSync6(join12(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
14297
14554
  this.ready = true;
14298
14555
  } catch (e) {
14299
- log32.debug(`git checkpoints unavailable for ${this.workTree}`, e);
14556
+ log33.debug(`git checkpoints unavailable for ${this.workTree}`, e);
14300
14557
  this.ready = false;
14301
14558
  }
14302
14559
  return this.ready;
@@ -14307,7 +14564,7 @@ var ShadowRepo = class {
14307
14564
  }
14308
14565
  async commit(label, forced = []) {
14309
14566
  await this.run("add", "-A");
14310
- for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log32.debug(`force-add failed: ${p}`, e));
14567
+ for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log33.debug(`force-add failed: ${p}`, e));
14311
14568
  await this.run("commit", "--allow-empty", "-q", "-m", label);
14312
14569
  }
14313
14570
  /** Inject the CURRENT (pre-edit) content of `paths` into the turn-open restore point by amending it.
@@ -14315,8 +14572,8 @@ var ShadowRepo = class {
14315
14572
  * turn-boundary `add -A` would never have captured it. Amend (vs a new commit) keeps one restore
14316
14573
  * point per turn, so the REPL's turn↔frame mapping stays intact. */
14317
14574
  async amendForced(paths) {
14318
- for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log32.debug(`force-capture failed: ${p}`, e));
14319
- await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log32.debug("amend failed", e));
14575
+ for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log33.debug(`force-capture failed: ${p}`, e));
14576
+ await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log33.debug("amend failed", e));
14320
14577
  }
14321
14578
  /** Commits on `ref`, oldest-first (canonical index space). */
14322
14579
  async log(ref) {
@@ -14376,7 +14633,7 @@ var ShadowRepo = class {
14376
14633
  await this.run("gc", "--auto", "-q").catch(() => {
14377
14634
  });
14378
14635
  } catch (e) {
14379
- log32.debug("checkpoint prune failed", e);
14636
+ log33.debug("checkpoint prune failed", e);
14380
14637
  }
14381
14638
  }
14382
14639
  };
@@ -14408,7 +14665,7 @@ var GitCheckpoints = class {
14408
14665
  const abs = resolve4(d);
14409
14666
  if (abs === cwd || abs.startsWith(cwd + sep3)) continue;
14410
14667
  if (cwd.startsWith(abs + sep3)) continue;
14411
- out.push({ workTree: abs, gitDir: join11(abs, ".agent", "checkpoints.git") });
14668
+ out.push({ workTree: abs, gitDir: join12(abs, ".agent", "checkpoints.git") });
14412
14669
  }
14413
14670
  return out;
14414
14671
  }
@@ -14444,7 +14701,7 @@ var GitCheckpoints = class {
14444
14701
  use(sessionId) {
14445
14702
  if (sessionId === this.session) return;
14446
14703
  this.session = sessionId;
14447
- if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log32.debug("re-point failed", e));
14704
+ if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log33.debug("re-point failed", e));
14448
14705
  }
14449
14706
  async begin(label) {
14450
14707
  if (!await this.start()) return;
@@ -14464,7 +14721,7 @@ var GitCheckpoints = class {
14464
14721
  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
14465
14722
  `);
14466
14723
  } else {
14467
- log32.debug("checkpoint commit failed", e);
14724
+ log33.debug("checkpoint commit failed", e);
14468
14725
  survivors.push(this.repos[i]);
14469
14726
  survivorCaches.push(this.caches[i] ?? []);
14470
14727
  }
@@ -14499,7 +14756,7 @@ var GitCheckpoints = class {
14499
14756
  if (this.forced.has(abs)) continue;
14500
14757
  this.forced.add(abs);
14501
14758
  const i = this.repoIndexFor(abs);
14502
- if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log32.debug("amendForced failed", e));
14759
+ if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log33.debug("amendForced failed", e));
14503
14760
  }
14504
14761
  }
14505
14762
  };
@@ -14551,7 +14808,7 @@ var GitCheckpointsOptions = class {
14551
14808
  /** Real working tree to snapshot (the launch cwd). */
14552
14809
  workTree = process.cwd();
14553
14810
  /** Isolated git dir for the cwd shadow repo (kept out of the user's real .git). */
14554
- gitDir = join11(process.cwd(), ".agent", "checkpoints.git");
14811
+ gitDir = join12(process.cwd(), ".agent", "checkpoints.git");
14555
14812
  /** Extra mounted dirs (`--add-dir`); those outside cwd each get their own shadow repo. */
14556
14813
  addDirs = [];
14557
14814
  /** Conversation id → per-session restore-point ref. */
@@ -14627,7 +14884,7 @@ import { emitKeypressEvents } from "readline";
14627
14884
  import { spawnSync as spawnSync5 } from "child_process";
14628
14885
  import { writeFileSync as writeFileSync7, readFileSync as readFileSync9, unlinkSync as unlinkSync2 } from "fs";
14629
14886
  import { tmpdir as tmpdir2 } from "os";
14630
- import { join as join12 } from "path";
14887
+ import { join as join13 } from "path";
14631
14888
 
14632
14889
  // cli/bidi.ts
14633
14890
  var RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
@@ -15815,7 +16072,7 @@ function createLineEditor(out, input = keyInput) {
15815
16072
  function externalEdit(s) {
15816
16073
  const spec = process.env.VISUAL || process.env.EDITOR || DEFAULT_EDITOR;
15817
16074
  const [cmd, ...cargs] = spec.split(" ").filter(Boolean);
15818
- const file = join12(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
16075
+ const file = join13(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
15819
16076
  try {
15820
16077
  writeFileSync7(file, s.buf);
15821
16078
  process.stdin.setRawMode(false);
@@ -16283,21 +16540,21 @@ init_hostPath();
16283
16540
  import { spawnSync as spawnSync6 } from "child_process";
16284
16541
  import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync7 } from "fs";
16285
16542
  import { homedir as homedir8 } from "os";
16286
- import { join as join13 } from "path";
16287
- var log33 = forComponent("os-sched");
16543
+ import { join as join14 } from "path";
16544
+ var log34 = forComponent("os-sched");
16288
16545
  var OsScheduler = class {
16289
16546
  options;
16290
16547
  constructor(options) {
16291
16548
  this.options = { ...new OsSchedulerOptions(), ...options };
16292
16549
  }
16293
16550
  get dir() {
16294
- return join13(this.options.home, ".agent", "sched");
16551
+ return join14(this.options.home, ".agent", "sched");
16295
16552
  }
16296
16553
  label(id) {
16297
16554
  return `cc.livx.agentx.sched-${id}`;
16298
16555
  }
16299
16556
  plistPath(id) {
16300
- return join13(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
16557
+ return join14(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
16301
16558
  }
16302
16559
  run(cmd, args, input) {
16303
16560
  return this.options.exec(cmd, args, input);
@@ -16313,11 +16570,11 @@ var OsScheduler = class {
16313
16570
  const script = this.writeScript(spec, oneOff);
16314
16571
  const mechanism = this.options.platform === "darwin" ? this.scheduleDarwin(spec, script) : this.scheduleLinux(spec, script, oneOff);
16315
16572
  const meta = { ...spec, created: Date.now(), mechanism };
16316
- writeFileSync8(join13(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
16573
+ writeFileSync8(join14(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
16317
16574
  return mechanism;
16318
16575
  }
16319
16576
  cancel(id) {
16320
- const meta = readJsonFile(join13(this.dir, `${id}.json`), null);
16577
+ const meta = readJsonFile(join14(this.dir, `${id}.json`), null);
16321
16578
  if (!meta) return false;
16322
16579
  try {
16323
16580
  if (this.options.platform === "darwin") {
@@ -16346,11 +16603,11 @@ var OsScheduler = class {
16346
16603
  }
16347
16604
  }
16348
16605
  } catch (e) {
16349
- log33.debug(`cancel ${id}`, e);
16606
+ log34.debug(`cancel ${id}`, e);
16350
16607
  }
16351
16608
  for (const f of [`${id}.json`, `${id}.sh`]) {
16352
16609
  try {
16353
- unlinkSync3(join13(this.dir, f));
16610
+ unlinkSync3(join14(this.dir, f));
16354
16611
  } catch {
16355
16612
  }
16356
16613
  }
@@ -16358,19 +16615,19 @@ var OsScheduler = class {
16358
16615
  }
16359
16616
  list() {
16360
16617
  if (!existsSync7(this.dir)) return [];
16361
- return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join13(this.dir, f), null)).filter(Boolean);
16618
+ return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join14(this.dir, f), null)).filter(Boolean);
16362
16619
  }
16363
16620
  /** The per-job runner script: cd to the project, headless-resume the session, log, notify. */
16364
16621
  writeScript(spec, oneOff) {
16365
- const p = join13(this.dir, `${spec.id}.sh`);
16622
+ const p = join14(this.dir, `${spec.id}.sh`);
16366
16623
  const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
16367
- const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(join13(this.dir, `${spec.id}.json`))} ${q2(p)}
16368
- ` : `rm -f ${q2(join13(this.dir, `${spec.id}.json`))} ${q2(p)}
16624
+ const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(join14(this.dir, `${spec.id}.json`))} ${q2(p)}
16625
+ ` : `rm -f ${q2(join14(this.dir, `${spec.id}.json`))} ${q2(p)}
16369
16626
  ` : "";
16370
16627
  writeFileSync8(p, `#!/bin/sh
16371
16628
  # agentx scheduled job ${spec.id}${spec.label ? ` \u2014 ${spec.label}` : ""}
16372
16629
  cd ${q2(spec.cwd)} || exit 1
16373
- ${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join13(this.dir, `${spec.id}.log`))} 2>&1
16630
+ ${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join14(this.dir, `${spec.id}.log`))} 2>&1
16374
16631
  ${cleanup}`);
16375
16632
  chmodSync(p, 493);
16376
16633
  return p;
@@ -16407,7 +16664,7 @@ ${trigger}
16407
16664
  <key>RunAtLoad</key><false/>
16408
16665
  </dict></plist>
16409
16666
  `;
16410
- mkdirSync10(join13(this.options.home, "Library", "LaunchAgents"), { recursive: true });
16667
+ mkdirSync10(join14(this.options.home, "Library", "LaunchAgents"), { recursive: true });
16411
16668
  writeFileSync8(this.plistPath(spec.id), plist);
16412
16669
  this.run("launchctl", ["load", this.plistPath(spec.id)]);
16413
16670
  return `launchd:${this.label(spec.id)}`;
@@ -16462,11 +16719,11 @@ import { createServer as createServer2, createConnection } from "net";
16462
16719
  import { spawn as spawn3 } from "child_process";
16463
16720
  import { existsSync as existsSync8, mkdirSync as mkdirSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
16464
16721
  import { homedir as homedir9 } from "os";
16465
- import { join as join14 } from "path";
16722
+ import { join as join15 } from "path";
16466
16723
  init_platform();
16467
- var log34 = forComponent("remote-trigger");
16468
- var TRIGGER_DIR = () => join14(homedir9(), ".agent", "triggers");
16469
- var sockPath = (sessionId, dir = TRIGGER_DIR()) => join14(dir, `${sessionId}.sock`);
16724
+ var log35 = forComponent("remote-trigger");
16725
+ var TRIGGER_DIR = () => join15(homedir9(), ".agent", "triggers");
16726
+ var sockPath = (sessionId, dir = TRIGGER_DIR()) => join15(dir, `${sessionId}.sock`);
16470
16727
  var TriggerServer = class {
16471
16728
  constructor(onTrigger) {
16472
16729
  this.onTrigger = onTrigger;
@@ -16502,13 +16759,13 @@ var TriggerServer = class {
16502
16759
  conn.end(JSON.stringify({ ok: false, error: String(e) }) + "\n");
16503
16760
  }
16504
16761
  });
16505
- conn.on("error", (e) => log34.debug("trigger conn error", e));
16762
+ conn.on("error", (e) => log35.debug("trigger conn error", e));
16506
16763
  });
16507
- this.server.on("error", (e) => log34.debug("trigger server error", e));
16764
+ this.server.on("error", (e) => log35.debug("trigger server error", e));
16508
16765
  this.server.listen(p);
16509
16766
  this.path = p;
16510
16767
  } catch (e) {
16511
- log34.debug("trigger server unavailable", e);
16768
+ log35.debug("trigger server unavailable", e);
16512
16769
  }
16513
16770
  }
16514
16771
  /** Re-bind on /resume (the session id changed). */
@@ -16640,7 +16897,7 @@ var strike = C("9");
16640
16897
  var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
16641
16898
  var mdPalette = { bold, dim, cyan, italic, strike, green, link };
16642
16899
  var err = (s) => process.stderr.write(s);
16643
- var log35 = forComponent("cli");
16900
+ var log36 = forComponent("cli");
16644
16901
  var VERSION = (() => {
16645
16902
  try {
16646
16903
  return JSON.parse(readFileSync10(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
@@ -16958,6 +17215,7 @@ function resolveModelOrNewest(model) {
16958
17215
  return fallback;
16959
17216
  }
16960
17217
  var ENV_KEY_ALIASES = { google: ["GEMINI_API_KEY"] };
17218
+ var fileEnvNames = /* @__PURE__ */ new Set();
16961
17219
  function loadEnvFile(file) {
16962
17220
  if (!existsSync9(file)) return;
16963
17221
  for (const line of readFileSync10(file, "utf8").split("\n")) {
@@ -16967,14 +17225,15 @@ function loadEnvFile(file) {
16967
17225
  if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) val = val.slice(1, -1);
16968
17226
  else val = val.replace(/\s+#.*$/, "").trim();
16969
17227
  process.env[m[1]] = val;
17228
+ fileEnvNames.add(m[1]);
16970
17229
  }
16971
17230
  }
16972
17231
  function loadInstallEnv() {
16973
17232
  let dir = dirname6(import.meta.path);
16974
- for (let i = 0; i < 5 && !existsSync9(join15(dir, "package.json")); i++) dir = dirname6(dir);
17233
+ for (let i = 0; i < 5 && !existsSync9(join16(dir, "package.json")); i++) dir = dirname6(dir);
16975
17234
  for (const name of [".env", ".env.local"]) {
16976
- loadEnvFile(join15(dir, name));
16977
- loadEnvFile(join15(homedir10(), ".agent", name));
17235
+ loadEnvFile(join16(dir, name));
17236
+ loadEnvFile(join16(homedir10(), ".agent", name));
16978
17237
  }
16979
17238
  }
16980
17239
  async function loadBodifySecrets() {
@@ -16993,7 +17252,7 @@ async function loadBodifySecrets() {
16993
17252
  const { values } = await res.json();
16994
17253
  if (!values) return;
16995
17254
  for (const [k, v] of Object.entries(values)) {
16996
- if (!(k in process.env)) process.env[k] = v;
17255
+ if (!(k in process.env) || fileEnvNames.has(k)) process.env[k] = v;
16997
17256
  }
16998
17257
  } catch (e) {
16999
17258
  console.error(` bodify secrets: ${e.message ?? e}`);
@@ -17471,7 +17730,7 @@ function turnCost(model, usage) {
17471
17730
  const priced = model.startsWith("claude-code/") ? `anthropic/${resolveModelAlias(model.slice("claude-code/".length))}` : model;
17472
17731
  return costOf(getModelInfo(priced)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, priced);
17473
17732
  }
17474
- async function evaluateGoal(ai, condition, transcript, log36) {
17733
+ async function evaluateGoal(ai, condition, transcript, log37) {
17475
17734
  const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
17476
17735
  const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
17477
17736
  return text.slice(0, 600);
@@ -17491,7 +17750,7 @@ ${recent}` }
17491
17750
  const match = r.content.match(/\{[\s\S]*\}/);
17492
17751
  if (match) return JSON.parse(match[0]);
17493
17752
  } catch (e) {
17494
- log36(dim(` (goal evaluator error: ${e?.message ?? e})
17753
+ log37(dim(` (goal evaluator error: ${e?.message ?? e})
17495
17754
  `));
17496
17755
  }
17497
17756
  return { met: false, reason: "evaluation unclear" };
@@ -17717,13 +17976,13 @@ function mcpAgentTools(mounted, opts) {
17717
17976
  return tools;
17718
17977
  }
17719
17978
  async function closeMcp(mounted) {
17720
- await Promise.all(mounted.map((m) => m.client.close().catch((e) => log35.debug("mcp close failed", e))));
17979
+ await Promise.all(mounted.map((m) => m.client.close().catch((e) => log36.debug("mcp close failed", e))));
17721
17980
  }
17722
17981
  var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
17723
17982
  function mentionRefs(line) {
17724
17983
  return [...line.matchAll(/(?:^|\s)@(?:"([^"]+)"|(\S+))/g)].map((m) => m[1] ?? m[2].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
17725
17984
  }
17726
- var untilde = (p) => p.startsWith("~/") || IS_WIN && p.startsWith("~\\") ? join15(homedir10(), p.slice(2)) : p;
17985
+ var untilde = (p) => p.startsWith("~/") || IS_WIN && p.startsWith("~\\") ? join16(homedir10(), p.slice(2)) : p;
17727
17986
  var PATH_LIKE = IS_WIN ? /^(\/|~[\\/]|\.\.?[\\/]|[A-Za-z]:[\\/]|\\\\)/ : /^(\/|~\/|\.\/|\.\.\/)/;
17728
17987
  var userPath = (cwd, p) => {
17729
17988
  const t = untilde(p);
@@ -17776,7 +18035,7 @@ async function expandMentions(fs, line) {
17776
18035
  if (loaded.includes(ref) || missing.includes(ref)) continue;
17777
18036
  if (ref.includes(":") && mcpMentionResolver) {
17778
18037
  const body = await mcpMentionResolver(ref).catch((e) => {
17779
- log35.debug("mcp mention resolve failed", e);
18038
+ log36.debug("mcp mention resolve failed", e);
17780
18039
  return null;
17781
18040
  });
17782
18041
  if (body != null) {
@@ -17867,7 +18126,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
17867
18126
  try {
17868
18127
  store.save(session);
17869
18128
  } catch (ex) {
17870
- log35.debug("mid-turn session flush failed", ex);
18129
+ log36.debug("mid-turn session flush failed", ex);
17871
18130
  }
17872
18131
  }
17873
18132
  return origNotify(e);
@@ -17935,7 +18194,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
17935
18194
  }
17936
18195
  return { ok, res };
17937
18196
  }
17938
- var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [join15(store.dir, `${data.meta.id}.images`), cwd]);
18197
+ var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [join16(store.dir, `${data.meta.id}.images`), cwd]);
17939
18198
  function startSession(args, store, agent, cwd) {
17940
18199
  if (args.resume || args.cont) {
17941
18200
  const data = args.resume ? store.load(args.resume) ?? globalSessionLoad(args.resume) : store.latestData();
@@ -17982,19 +18241,19 @@ var AGENTS_MD_TEMPLATE = `# ${"${name}"}
17982
18241
  `;
17983
18242
  function initInstructions(cwd) {
17984
18243
  for (const f of ["AGENTS.md", "CLAUDE.md"]) {
17985
- if (existsSync9(join15(cwd, f))) {
18244
+ if (existsSync9(join16(cwd, f))) {
17986
18245
  err(yellow(` ${f} already exists \u2014 leaving it as-is
17987
18246
  `));
17988
18247
  return;
17989
18248
  }
17990
18249
  }
17991
- const path = join15(cwd, "AGENTS.md");
18250
+ const path = join16(cwd, "AGENTS.md");
17992
18251
  writeFileSync9(path, AGENTS_MD_TEMPLATE.replace("${name}", basename4(cwd)));
17993
18252
  err(green(` created ${path}
17994
18253
  `) + dim(" edit it, then it auto-loads into every run.\n"));
17995
18254
  }
17996
18255
  function persistSetting(cwd, key, value) {
17997
- const path = join15(cwd, ".agent", "settings.json");
18256
+ const path = join16(cwd, ".agent", "settings.json");
17998
18257
  try {
17999
18258
  const obj = existsSync9(path) ? JSON.parse(readFileSync10(path, "utf8")) : {};
18000
18259
  if (obj[key] === value) return;
@@ -18016,14 +18275,14 @@ var isCancelTeardown = (e) => {
18016
18275
  function installCancelGuards(mounted) {
18017
18276
  process.on("unhandledRejection", (e) => {
18018
18277
  if (isCancelTeardown(e)) {
18019
- log35.debug("suppressed unhandledRejection (cursor stream cancel)", e);
18278
+ log36.debug("suppressed unhandledRejection (cursor stream cancel)", e);
18020
18279
  return;
18021
18280
  }
18022
- log35.error("unhandledRejection", e);
18281
+ log36.error("unhandledRejection", e);
18023
18282
  });
18024
18283
  process.on("uncaughtException", (e) => {
18025
18284
  if (isCancelTeardown(e)) {
18026
- log35.debug("suppressed uncaughtException (cursor stream cancel)", e);
18285
+ log36.debug("suppressed uncaughtException (cursor stream cancel)", e);
18027
18286
  return;
18028
18287
  }
18029
18288
  console.error(e);
@@ -18032,7 +18291,7 @@ function installCancelGuards(mounted) {
18032
18291
  });
18033
18292
  }
18034
18293
  async function repl(args, ai, cfg, cwd) {
18035
- const oauth = new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") });
18294
+ const oauth = new McpOAuth({ storePath: join16(cwd, ".agent", "mcp-auth.json") });
18036
18295
  const mounted = await mountMcp(cfg, oauth);
18037
18296
  let mcpToolNames = [];
18038
18297
  const initialMcpTools = mcpAgentTools(mounted);
@@ -18310,7 +18569,7 @@ async function repl(args, ai, cfg, cwd) {
18310
18569
  quickLook: {
18311
18570
  branch: () => {
18312
18571
  try {
18313
- const head = readFileSync10(join15(cwd, ".git", "HEAD"), "utf8").trim();
18572
+ const head = readFileSync10(join16(cwd, ".git", "HEAD"), "utf8").trim();
18314
18573
  return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
18315
18574
  } catch {
18316
18575
  return "not a git repository";
@@ -18444,11 +18703,11 @@ async function repl(args, ai, cfg, cwd) {
18444
18703
  const pendingImages = [];
18445
18704
  const bangContext = [];
18446
18705
  const grabClipboardAttachment = () => {
18447
- const dir = join15(store.dir, `${session.meta.id}.images`);
18706
+ const dir = join16(store.dir, `${session.meta.id}.images`);
18448
18707
  try {
18449
18708
  mkdirSync12(dir, { recursive: true });
18450
18709
  } catch (e) {
18451
- log35.debug("paste image dir create failed", e);
18710
+ log36.debug("paste image dir create failed", e);
18452
18711
  }
18453
18712
  const img = grabClipboardImage(dir, String(Date.now()));
18454
18713
  return img ? { display: "Image", ref: "@" + img.path, path: img.path } : null;
@@ -18502,7 +18761,7 @@ async function repl(args, ai, cfg, cwd) {
18502
18761
  err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
18503
18762
  `));
18504
18763
  }
18505
- const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir: join15(cwd, ".agent", "checkpoints.git"), addDirs: args.addDirs, sessionId: session.meta.id });
18764
+ const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir: join16(cwd, ".agent", "checkpoints.git"), addDirs: args.addDirs, sessionId: session.meta.id });
18506
18765
  const cpHooks = checkpoints.hooks?.();
18507
18766
  if (cpHooks) {
18508
18767
  agent.options.hooks = composeHooks(agent.options.hooks, cpHooks);
@@ -18554,14 +18813,14 @@ ${lines.join("\n")}
18554
18813
  Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
18555
18814
  </system-reminder>`;
18556
18815
  };
18557
- const histPath = join15(cwd, ".agent", "history");
18816
+ const histPath = join16(cwd, ".agent", "history");
18558
18817
  const history = existsSync9(histPath) ? readFileSync10(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
18559
18818
  const remember2 = (line) => {
18560
18819
  try {
18561
- mkdirSync12(join15(cwd, ".agent"), { recursive: true });
18820
+ mkdirSync12(join16(cwd, ".agent"), { recursive: true });
18562
18821
  appendFileSync(histPath, line + "\n");
18563
18822
  } catch (e) {
18564
- log35.debug("history write failed", e);
18823
+ log36.debug("history write failed", e);
18565
18824
  }
18566
18825
  };
18567
18826
  const ago = (t) => {
@@ -18633,7 +18892,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
18633
18892
  try {
18634
18893
  store.save(session);
18635
18894
  } catch (e) {
18636
- log35.debug("session save after rewind failed", e);
18895
+ log36.debug("session save after rewind failed", e);
18637
18896
  }
18638
18897
  err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
18639
18898
  `));
@@ -18668,7 +18927,7 @@ ${task}`;
18668
18927
  bangContext.length = 0;
18669
18928
  }
18670
18929
  const delta = await refreshCatalogs().catch((e) => {
18671
- log35.debug("catalog refresh failed", e);
18930
+ log36.debug("catalog refresh failed", e);
18672
18931
  return "";
18673
18932
  });
18674
18933
  if (delta) {
@@ -18931,7 +19190,7 @@ ${task}`;
18931
19190
  desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
18932
19191
  run: async () => {
18933
19192
  await refreshCatalogs().catch((e) => {
18934
- log35.debug("catalog refresh failed", e);
19193
+ log36.debug("catalog refresh failed", e);
18935
19194
  });
18936
19195
  face.reprepare();
18937
19196
  err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
@@ -19434,7 +19693,7 @@ ${task}`;
19434
19693
  try {
19435
19694
  for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
19436
19695
  } catch (e) {
19437
- log35.debug(`loadAgents(${d}) failed`, e);
19696
+ log36.debug(`loadAgents(${d}) failed`, e);
19438
19697
  }
19439
19698
  }
19440
19699
  if (!seen.size) {
@@ -19522,7 +19781,7 @@ ${task}`;
19522
19781
  }
19523
19782
  if (idx >= 0) {
19524
19783
  const old = mounted.splice(idx, 1)[0];
19525
- await old.client.close().catch((e) => log35.debug("mcp close failed", e));
19784
+ await old.client.close().catch((e) => log36.debug("mcp close failed", e));
19526
19785
  }
19527
19786
  try {
19528
19787
  const m = await mountMcpServer(name, conf);
@@ -19550,7 +19809,7 @@ ${task}`;
19550
19809
  }
19551
19810
  const m = mounted.splice(idx, 1)[0];
19552
19811
  remountMcpTools();
19553
- await m.client.close().catch((e) => log35.debug("mcp close failed", e));
19812
+ await m.client.close().catch((e) => log36.debug("mcp close failed", e));
19554
19813
  err(dim(` removed "${name}"
19555
19814
  `));
19556
19815
  return;
@@ -19665,7 +19924,7 @@ ${task}`;
19665
19924
  return;
19666
19925
  }
19667
19926
  const md = exportMarkdown(session.meta, shown);
19668
- const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join15(".agent", "exports", `${session.meta.id}.md`);
19927
+ const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join16(".agent", "exports", `${session.meta.id}.md`);
19669
19928
  const path = resolve5(cwd, name);
19670
19929
  try {
19671
19930
  mkdirSync12(dirname6(path), { recursive: true });
@@ -19751,9 +20010,9 @@ ${task}`;
19751
20010
  });
19752
20011
  const listDir = (absDir) => {
19753
20012
  try {
19754
- return readdirSync4(join15(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
20013
+ return readdirSync4(join16(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
19755
20014
  } catch (e) {
19756
- log35.debug("completion readdir failed", absDir, e);
20015
+ log36.debug("completion readdir failed", absDir, e);
19757
20016
  return null;
19758
20017
  }
19759
20018
  };
@@ -19890,7 +20149,7 @@ ${out}
19890
20149
  return;
19891
20150
  }
19892
20151
  await refreshCatalogs().catch((e) => {
19893
- log35.debug("catalog refresh failed", e);
20152
+ log36.debug("catalog refresh failed", e);
19894
20153
  });
19895
20154
  const sk = skills.find((x) => x.name === name);
19896
20155
  if (sk) {
@@ -19936,7 +20195,7 @@ ${out}
19936
20195
  return false;
19937
20196
  }
19938
20197
  const fakeVoice = process.env.AGENTX_VOICE_FAKE ? fakeVoiceParts(process.env.AGENTX_VOICE_FAKE) : null;
19939
- voiceDiag = new JsonlDiagSink(join15(store.dir, `${session.meta.id}.voice.jsonl`));
20198
+ voiceDiag = new JsonlDiagSink(join16(store.dir, `${session.meta.id}.voice.jsonl`));
19940
20199
  voiceIO = new VoiceIO({
19941
20200
  ...fakeVoice ?? {},
19942
20201
  onDiag: (ev) => voiceDiag?.push(ev),
@@ -20001,7 +20260,7 @@ ${out}
20001
20260
  if (delivery || cut) dx?.parkCutDelivery(delivery, !!cut, heard);
20002
20261
  const cutReply = cut && cut.full.length - cut.heard.length > 40 ? cut : null;
20003
20262
  const noteP = replaces ? replaces.note : !cutReply ? Promise.resolve("") : (instinct ? instinct.bargeIntent(text).catch((e) => {
20004
- log35.warn(`barge intent failed \u2014 model decides: ${e.message}`);
20263
+ log36.warn(`barge intent failed \u2014 model decides: ${e.message}`);
20005
20264
  return void 0;
20006
20265
  }) : Promise.resolve(void 0)).then(async (intent) => interruptionNote(cutReply, intent) + ackedNote(await instinct?.ackFor(text)));
20007
20266
  const cutIn = replaces ? replaces.cutIn : interjectNote;
@@ -20357,16 +20616,19 @@ var leakDecider = () => process.env.TYPESAFE_AI_KEY ? new DecideClient({ apiKey:
20357
20616
  function makeSecondChannel(ai, reflexModel, opts) {
20358
20617
  const decider = leakDecider();
20359
20618
  const instinct = decider ? new Instinct({ decider }) : void 0;
20619
+ const model = process.env.AGENTX_CHANNEL_MODEL || reflexModel;
20360
20620
  return new SecondChannel({
20621
+ // Banks in the user's language: generated once per language, cached + topped up across sessions (~/.agent/cache).
20622
+ banks: new ChannelBanks({ generate: fastBankGenerator(ai, model), cacheDir: join16(homedir10(), ".agent", "cache", "channel-banks") }),
20361
20623
  decide: instinct ? (heard, state) => instinct.channel(heard, state) : void 0,
20362
- generate: fastLineGenerator(ai, process.env.AGENTX_CHANNEL_MODEL || reflexModel),
20363
- onEvent: (e) => log35.debug(`channel ${e.trigger}: rule=${e.rule} choice=${e.choice ?? "-"} \u2192 ${e.acted}${e.text ? ` "${e.text}"` : ""} ${e.ms}ms`),
20624
+ generate: fastLineGenerator(ai, model),
20625
+ onEvent: (e) => log36.debug(`channel ${e.trigger}: rule=${e.rule} choice=${e.choice ?? "-"} \u2192 ${e.acted}${e.text ? ` "${e.text}"` : ""} ${e.ms}ms`),
20364
20626
  ...opts
20365
20627
  });
20366
20628
  }
20367
20629
  async function duplexServe(args, ai, cfg, cwd) {
20368
20630
  const out = (o) => process.stdout.write(JSON.stringify(o) + "\n");
20369
- const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") }));
20631
+ const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join16(cwd, ".agent", "mcp-auth.json") }));
20370
20632
  installCancelGuards(mounted);
20371
20633
  const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted, { quiet: true }), mounted.map((m) => m.name));
20372
20634
  const { host: _h, stream: _s, signal: _g, providerOptions: _po, ...wo } = agent.options;
@@ -20418,7 +20680,7 @@ async function duplexServe(args, ai, cfg, cwd) {
20418
20680
  try {
20419
20681
  store.save(session);
20420
20682
  } catch (e) {
20421
- log35.warn("duplex session save failed", e);
20683
+ log36.warn("duplex session save failed", e);
20422
20684
  }
20423
20685
  };
20424
20686
  out({ type: "ready", sessionId: session.meta.id, reflexModel: dx.options.reflexModel, actModel: dx.options.actModel, ...notes.length ? { notes } : {} });
@@ -20454,7 +20716,7 @@ async function duplexServe(args, ai, cfg, cwd) {
20454
20716
  done2();
20455
20717
  out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error", ...why ? { error: why } : {} });
20456
20718
  }).catch((e) => {
20457
- log35.error("duplex turn failed", e);
20719
+ log36.error("duplex turn failed", e);
20458
20720
  done2();
20459
20721
  out({ type: "turn_done", id, finishReason: "error", error: String(e?.message ?? e) });
20460
20722
  });
@@ -20615,7 +20877,7 @@ async function main() {
20615
20877
  worktreeCleanup?.();
20616
20878
  process.exit(0);
20617
20879
  }
20618
- const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") }));
20880
+ const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join16(cwd, ".agent", "mcp-auth.json") }));
20619
20881
  const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted), mounted.map((m) => m.name));
20620
20882
  const store = new SessionStore(cwd);
20621
20883
  const session = startSession(args, store, agent, cwd);