@livx.cc/agentx 0.99.86 → 0.99.87

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 ?? "?";
@@ -16971,10 +17228,10 @@ function loadEnvFile(file) {
16971
17228
  }
16972
17229
  function loadInstallEnv() {
16973
17230
  let dir = dirname6(import.meta.path);
16974
- for (let i = 0; i < 5 && !existsSync9(join15(dir, "package.json")); i++) dir = dirname6(dir);
17231
+ for (let i = 0; i < 5 && !existsSync9(join16(dir, "package.json")); i++) dir = dirname6(dir);
16975
17232
  for (const name of [".env", ".env.local"]) {
16976
- loadEnvFile(join15(dir, name));
16977
- loadEnvFile(join15(homedir10(), ".agent", name));
17233
+ loadEnvFile(join16(dir, name));
17234
+ loadEnvFile(join16(homedir10(), ".agent", name));
16978
17235
  }
16979
17236
  }
16980
17237
  async function loadBodifySecrets() {
@@ -17471,7 +17728,7 @@ function turnCost(model, usage) {
17471
17728
  const priced = model.startsWith("claude-code/") ? `anthropic/${resolveModelAlias(model.slice("claude-code/".length))}` : model;
17472
17729
  return costOf(getModelInfo(priced)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, priced);
17473
17730
  }
17474
- async function evaluateGoal(ai, condition, transcript, log36) {
17731
+ async function evaluateGoal(ai, condition, transcript, log37) {
17475
17732
  const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
17476
17733
  const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
17477
17734
  return text.slice(0, 600);
@@ -17491,7 +17748,7 @@ ${recent}` }
17491
17748
  const match = r.content.match(/\{[\s\S]*\}/);
17492
17749
  if (match) return JSON.parse(match[0]);
17493
17750
  } catch (e) {
17494
- log36(dim(` (goal evaluator error: ${e?.message ?? e})
17751
+ log37(dim(` (goal evaluator error: ${e?.message ?? e})
17495
17752
  `));
17496
17753
  }
17497
17754
  return { met: false, reason: "evaluation unclear" };
@@ -17717,13 +17974,13 @@ function mcpAgentTools(mounted, opts) {
17717
17974
  return tools;
17718
17975
  }
17719
17976
  async function closeMcp(mounted) {
17720
- await Promise.all(mounted.map((m) => m.client.close().catch((e) => log35.debug("mcp close failed", e))));
17977
+ await Promise.all(mounted.map((m) => m.client.close().catch((e) => log36.debug("mcp close failed", e))));
17721
17978
  }
17722
17979
  var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
17723
17980
  function mentionRefs(line) {
17724
17981
  return [...line.matchAll(/(?:^|\s)@(?:"([^"]+)"|(\S+))/g)].map((m) => m[1] ?? m[2].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
17725
17982
  }
17726
- var untilde = (p) => p.startsWith("~/") || IS_WIN && p.startsWith("~\\") ? join15(homedir10(), p.slice(2)) : p;
17983
+ var untilde = (p) => p.startsWith("~/") || IS_WIN && p.startsWith("~\\") ? join16(homedir10(), p.slice(2)) : p;
17727
17984
  var PATH_LIKE = IS_WIN ? /^(\/|~[\\/]|\.\.?[\\/]|[A-Za-z]:[\\/]|\\\\)/ : /^(\/|~\/|\.\/|\.\.\/)/;
17728
17985
  var userPath = (cwd, p) => {
17729
17986
  const t = untilde(p);
@@ -17776,7 +18033,7 @@ async function expandMentions(fs, line) {
17776
18033
  if (loaded.includes(ref) || missing.includes(ref)) continue;
17777
18034
  if (ref.includes(":") && mcpMentionResolver) {
17778
18035
  const body = await mcpMentionResolver(ref).catch((e) => {
17779
- log35.debug("mcp mention resolve failed", e);
18036
+ log36.debug("mcp mention resolve failed", e);
17780
18037
  return null;
17781
18038
  });
17782
18039
  if (body != null) {
@@ -17867,7 +18124,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
17867
18124
  try {
17868
18125
  store.save(session);
17869
18126
  } catch (ex) {
17870
- log35.debug("mid-turn session flush failed", ex);
18127
+ log36.debug("mid-turn session flush failed", ex);
17871
18128
  }
17872
18129
  }
17873
18130
  return origNotify(e);
@@ -17935,7 +18192,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
17935
18192
  }
17936
18193
  return { ok, res };
17937
18194
  }
17938
- var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [join15(store.dir, `${data.meta.id}.images`), cwd]);
18195
+ var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [join16(store.dir, `${data.meta.id}.images`), cwd]);
17939
18196
  function startSession(args, store, agent, cwd) {
17940
18197
  if (args.resume || args.cont) {
17941
18198
  const data = args.resume ? store.load(args.resume) ?? globalSessionLoad(args.resume) : store.latestData();
@@ -17982,19 +18239,19 @@ var AGENTS_MD_TEMPLATE = `# ${"${name}"}
17982
18239
  `;
17983
18240
  function initInstructions(cwd) {
17984
18241
  for (const f of ["AGENTS.md", "CLAUDE.md"]) {
17985
- if (existsSync9(join15(cwd, f))) {
18242
+ if (existsSync9(join16(cwd, f))) {
17986
18243
  err(yellow(` ${f} already exists \u2014 leaving it as-is
17987
18244
  `));
17988
18245
  return;
17989
18246
  }
17990
18247
  }
17991
- const path = join15(cwd, "AGENTS.md");
18248
+ const path = join16(cwd, "AGENTS.md");
17992
18249
  writeFileSync9(path, AGENTS_MD_TEMPLATE.replace("${name}", basename4(cwd)));
17993
18250
  err(green(` created ${path}
17994
18251
  `) + dim(" edit it, then it auto-loads into every run.\n"));
17995
18252
  }
17996
18253
  function persistSetting(cwd, key, value) {
17997
- const path = join15(cwd, ".agent", "settings.json");
18254
+ const path = join16(cwd, ".agent", "settings.json");
17998
18255
  try {
17999
18256
  const obj = existsSync9(path) ? JSON.parse(readFileSync10(path, "utf8")) : {};
18000
18257
  if (obj[key] === value) return;
@@ -18016,14 +18273,14 @@ var isCancelTeardown = (e) => {
18016
18273
  function installCancelGuards(mounted) {
18017
18274
  process.on("unhandledRejection", (e) => {
18018
18275
  if (isCancelTeardown(e)) {
18019
- log35.debug("suppressed unhandledRejection (cursor stream cancel)", e);
18276
+ log36.debug("suppressed unhandledRejection (cursor stream cancel)", e);
18020
18277
  return;
18021
18278
  }
18022
- log35.error("unhandledRejection", e);
18279
+ log36.error("unhandledRejection", e);
18023
18280
  });
18024
18281
  process.on("uncaughtException", (e) => {
18025
18282
  if (isCancelTeardown(e)) {
18026
- log35.debug("suppressed uncaughtException (cursor stream cancel)", e);
18283
+ log36.debug("suppressed uncaughtException (cursor stream cancel)", e);
18027
18284
  return;
18028
18285
  }
18029
18286
  console.error(e);
@@ -18032,7 +18289,7 @@ function installCancelGuards(mounted) {
18032
18289
  });
18033
18290
  }
18034
18291
  async function repl(args, ai, cfg, cwd) {
18035
- const oauth = new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") });
18292
+ const oauth = new McpOAuth({ storePath: join16(cwd, ".agent", "mcp-auth.json") });
18036
18293
  const mounted = await mountMcp(cfg, oauth);
18037
18294
  let mcpToolNames = [];
18038
18295
  const initialMcpTools = mcpAgentTools(mounted);
@@ -18310,7 +18567,7 @@ async function repl(args, ai, cfg, cwd) {
18310
18567
  quickLook: {
18311
18568
  branch: () => {
18312
18569
  try {
18313
- const head = readFileSync10(join15(cwd, ".git", "HEAD"), "utf8").trim();
18570
+ const head = readFileSync10(join16(cwd, ".git", "HEAD"), "utf8").trim();
18314
18571
  return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
18315
18572
  } catch {
18316
18573
  return "not a git repository";
@@ -18444,11 +18701,11 @@ async function repl(args, ai, cfg, cwd) {
18444
18701
  const pendingImages = [];
18445
18702
  const bangContext = [];
18446
18703
  const grabClipboardAttachment = () => {
18447
- const dir = join15(store.dir, `${session.meta.id}.images`);
18704
+ const dir = join16(store.dir, `${session.meta.id}.images`);
18448
18705
  try {
18449
18706
  mkdirSync12(dir, { recursive: true });
18450
18707
  } catch (e) {
18451
- log35.debug("paste image dir create failed", e);
18708
+ log36.debug("paste image dir create failed", e);
18452
18709
  }
18453
18710
  const img = grabClipboardImage(dir, String(Date.now()));
18454
18711
  return img ? { display: "Image", ref: "@" + img.path, path: img.path } : null;
@@ -18502,7 +18759,7 @@ async function repl(args, ai, cfg, cwd) {
18502
18759
  err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
18503
18760
  `));
18504
18761
  }
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 });
18762
+ 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
18763
  const cpHooks = checkpoints.hooks?.();
18507
18764
  if (cpHooks) {
18508
18765
  agent.options.hooks = composeHooks(agent.options.hooks, cpHooks);
@@ -18554,14 +18811,14 @@ ${lines.join("\n")}
18554
18811
  Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
18555
18812
  </system-reminder>`;
18556
18813
  };
18557
- const histPath = join15(cwd, ".agent", "history");
18814
+ const histPath = join16(cwd, ".agent", "history");
18558
18815
  const history = existsSync9(histPath) ? readFileSync10(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
18559
18816
  const remember2 = (line) => {
18560
18817
  try {
18561
- mkdirSync12(join15(cwd, ".agent"), { recursive: true });
18818
+ mkdirSync12(join16(cwd, ".agent"), { recursive: true });
18562
18819
  appendFileSync(histPath, line + "\n");
18563
18820
  } catch (e) {
18564
- log35.debug("history write failed", e);
18821
+ log36.debug("history write failed", e);
18565
18822
  }
18566
18823
  };
18567
18824
  const ago = (t) => {
@@ -18633,7 +18890,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
18633
18890
  try {
18634
18891
  store.save(session);
18635
18892
  } catch (e) {
18636
- log35.debug("session save after rewind failed", e);
18893
+ log36.debug("session save after rewind failed", e);
18637
18894
  }
18638
18895
  err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
18639
18896
  `));
@@ -18668,7 +18925,7 @@ ${task}`;
18668
18925
  bangContext.length = 0;
18669
18926
  }
18670
18927
  const delta = await refreshCatalogs().catch((e) => {
18671
- log35.debug("catalog refresh failed", e);
18928
+ log36.debug("catalog refresh failed", e);
18672
18929
  return "";
18673
18930
  });
18674
18931
  if (delta) {
@@ -18931,7 +19188,7 @@ ${task}`;
18931
19188
  desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
18932
19189
  run: async () => {
18933
19190
  await refreshCatalogs().catch((e) => {
18934
- log35.debug("catalog refresh failed", e);
19191
+ log36.debug("catalog refresh failed", e);
18935
19192
  });
18936
19193
  face.reprepare();
18937
19194
  err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
@@ -19434,7 +19691,7 @@ ${task}`;
19434
19691
  try {
19435
19692
  for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
19436
19693
  } catch (e) {
19437
- log35.debug(`loadAgents(${d}) failed`, e);
19694
+ log36.debug(`loadAgents(${d}) failed`, e);
19438
19695
  }
19439
19696
  }
19440
19697
  if (!seen.size) {
@@ -19522,7 +19779,7 @@ ${task}`;
19522
19779
  }
19523
19780
  if (idx >= 0) {
19524
19781
  const old = mounted.splice(idx, 1)[0];
19525
- await old.client.close().catch((e) => log35.debug("mcp close failed", e));
19782
+ await old.client.close().catch((e) => log36.debug("mcp close failed", e));
19526
19783
  }
19527
19784
  try {
19528
19785
  const m = await mountMcpServer(name, conf);
@@ -19550,7 +19807,7 @@ ${task}`;
19550
19807
  }
19551
19808
  const m = mounted.splice(idx, 1)[0];
19552
19809
  remountMcpTools();
19553
- await m.client.close().catch((e) => log35.debug("mcp close failed", e));
19810
+ await m.client.close().catch((e) => log36.debug("mcp close failed", e));
19554
19811
  err(dim(` removed "${name}"
19555
19812
  `));
19556
19813
  return;
@@ -19665,7 +19922,7 @@ ${task}`;
19665
19922
  return;
19666
19923
  }
19667
19924
  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`);
19925
+ const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join16(".agent", "exports", `${session.meta.id}.md`);
19669
19926
  const path = resolve5(cwd, name);
19670
19927
  try {
19671
19928
  mkdirSync12(dirname6(path), { recursive: true });
@@ -19751,9 +20008,9 @@ ${task}`;
19751
20008
  });
19752
20009
  const listDir = (absDir) => {
19753
20010
  try {
19754
- return readdirSync4(join15(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
20011
+ return readdirSync4(join16(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
19755
20012
  } catch (e) {
19756
- log35.debug("completion readdir failed", absDir, e);
20013
+ log36.debug("completion readdir failed", absDir, e);
19757
20014
  return null;
19758
20015
  }
19759
20016
  };
@@ -19890,7 +20147,7 @@ ${out}
19890
20147
  return;
19891
20148
  }
19892
20149
  await refreshCatalogs().catch((e) => {
19893
- log35.debug("catalog refresh failed", e);
20150
+ log36.debug("catalog refresh failed", e);
19894
20151
  });
19895
20152
  const sk = skills.find((x) => x.name === name);
19896
20153
  if (sk) {
@@ -19936,7 +20193,7 @@ ${out}
19936
20193
  return false;
19937
20194
  }
19938
20195
  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`));
20196
+ voiceDiag = new JsonlDiagSink(join16(store.dir, `${session.meta.id}.voice.jsonl`));
19940
20197
  voiceIO = new VoiceIO({
19941
20198
  ...fakeVoice ?? {},
19942
20199
  onDiag: (ev) => voiceDiag?.push(ev),
@@ -20001,7 +20258,7 @@ ${out}
20001
20258
  if (delivery || cut) dx?.parkCutDelivery(delivery, !!cut, heard);
20002
20259
  const cutReply = cut && cut.full.length - cut.heard.length > 40 ? cut : null;
20003
20260
  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}`);
20261
+ log36.warn(`barge intent failed \u2014 model decides: ${e.message}`);
20005
20262
  return void 0;
20006
20263
  }) : Promise.resolve(void 0)).then(async (intent) => interruptionNote(cutReply, intent) + ackedNote(await instinct?.ackFor(text)));
20007
20264
  const cutIn = replaces ? replaces.cutIn : interjectNote;
@@ -20357,16 +20614,19 @@ var leakDecider = () => process.env.TYPESAFE_AI_KEY ? new DecideClient({ apiKey:
20357
20614
  function makeSecondChannel(ai, reflexModel, opts) {
20358
20615
  const decider = leakDecider();
20359
20616
  const instinct = decider ? new Instinct({ decider }) : void 0;
20617
+ const model = process.env.AGENTX_CHANNEL_MODEL || reflexModel;
20360
20618
  return new SecondChannel({
20619
+ // Banks in the user's language: generated once per language, cached + topped up across sessions (~/.agent/cache).
20620
+ banks: new ChannelBanks({ generate: fastBankGenerator(ai, model), cacheDir: join16(homedir10(), ".agent", "cache", "channel-banks") }),
20361
20621
  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`),
20622
+ generate: fastLineGenerator(ai, model),
20623
+ onEvent: (e) => log36.debug(`channel ${e.trigger}: rule=${e.rule} choice=${e.choice ?? "-"} \u2192 ${e.acted}${e.text ? ` "${e.text}"` : ""} ${e.ms}ms`),
20364
20624
  ...opts
20365
20625
  });
20366
20626
  }
20367
20627
  async function duplexServe(args, ai, cfg, cwd) {
20368
20628
  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") }));
20629
+ const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join16(cwd, ".agent", "mcp-auth.json") }));
20370
20630
  installCancelGuards(mounted);
20371
20631
  const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted, { quiet: true }), mounted.map((m) => m.name));
20372
20632
  const { host: _h, stream: _s, signal: _g, providerOptions: _po, ...wo } = agent.options;
@@ -20418,7 +20678,7 @@ async function duplexServe(args, ai, cfg, cwd) {
20418
20678
  try {
20419
20679
  store.save(session);
20420
20680
  } catch (e) {
20421
- log35.warn("duplex session save failed", e);
20681
+ log36.warn("duplex session save failed", e);
20422
20682
  }
20423
20683
  };
20424
20684
  out({ type: "ready", sessionId: session.meta.id, reflexModel: dx.options.reflexModel, actModel: dx.options.actModel, ...notes.length ? { notes } : {} });
@@ -20454,7 +20714,7 @@ async function duplexServe(args, ai, cfg, cwd) {
20454
20714
  done2();
20455
20715
  out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error", ...why ? { error: why } : {} });
20456
20716
  }).catch((e) => {
20457
- log35.error("duplex turn failed", e);
20717
+ log36.error("duplex turn failed", e);
20458
20718
  done2();
20459
20719
  out({ type: "turn_done", id, finishReason: "error", error: String(e?.message ?? e) });
20460
20720
  });
@@ -20615,7 +20875,7 @@ async function main() {
20615
20875
  worktreeCleanup?.();
20616
20876
  process.exit(0);
20617
20877
  }
20618
- const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") }));
20878
+ const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join16(cwd, ".agent", "mcp-auth.json") }));
20619
20879
  const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted), mounted.map((m) => m.name));
20620
20880
  const store = new SessionStore(cwd);
20621
20881
  const session = startSession(args, store, agent, cwd);