@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 +396 -136
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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)
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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: ${
|
|
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
|
-
` : "") +
|
|
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
|
|
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
|
|
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) =>
|
|
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 = [
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
14185
|
+
import { join as join11 } from "path";
|
|
13929
14186
|
init_platform();
|
|
13930
|
-
var
|
|
13931
|
-
var globalDir = () =>
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
14250
|
+
log32.debug(`rejecting unsafe session id: ${id}`);
|
|
13994
14251
|
return void 0;
|
|
13995
14252
|
}
|
|
13996
|
-
const path =
|
|
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
|
-
|
|
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(
|
|
14269
|
+
metas.push(JSON.parse(readFileSync8(join11(this.dir, f), "utf8")).meta);
|
|
14013
14270
|
} catch (e) {
|
|
14014
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
14332
|
+
const target = readIndexLink(join11(gd, f));
|
|
14076
14333
|
if (!existsSync6(target)) {
|
|
14077
14334
|
try {
|
|
14078
|
-
unlinkSync(
|
|
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
|
|
14253
|
-
var
|
|
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(
|
|
14296
|
-
writeFileSync6(
|
|
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
|
-
|
|
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) =>
|
|
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) =>
|
|
14319
|
-
await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((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
|
-
|
|
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:
|
|
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) =>
|
|
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
|
-
|
|
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) =>
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
16287
|
-
var
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
16606
|
+
log34.debug(`cancel ${id}`, e);
|
|
16350
16607
|
}
|
|
16351
16608
|
for (const f of [`${id}.json`, `${id}.sh`]) {
|
|
16352
16609
|
try {
|
|
16353
|
-
unlinkSync3(
|
|
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(
|
|
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 =
|
|
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(
|
|
16368
|
-
` : `rm -f ${q2(
|
|
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(
|
|
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(
|
|
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
|
|
16722
|
+
import { join as join15 } from "path";
|
|
16466
16723
|
init_platform();
|
|
16467
|
-
var
|
|
16468
|
-
var TRIGGER_DIR = () =>
|
|
16469
|
-
var sockPath = (sessionId, dir = TRIGGER_DIR()) =>
|
|
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) =>
|
|
16762
|
+
conn.on("error", (e) => log35.debug("trigger conn error", e));
|
|
16506
16763
|
});
|
|
16507
|
-
this.server.on("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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
16977
|
-
loadEnvFile(
|
|
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,
|
|
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
|
-
|
|
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) =>
|
|
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("~\\") ?
|
|
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
|
-
|
|
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
|
-
|
|
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, [
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
18276
|
+
log36.debug("suppressed unhandledRejection (cursor stream cancel)", e);
|
|
18020
18277
|
return;
|
|
18021
18278
|
}
|
|
18022
|
-
|
|
18279
|
+
log36.error("unhandledRejection", e);
|
|
18023
18280
|
});
|
|
18024
18281
|
process.on("uncaughtException", (e) => {
|
|
18025
18282
|
if (isCancelTeardown(e)) {
|
|
18026
|
-
|
|
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:
|
|
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(
|
|
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 =
|
|
18704
|
+
const dir = join16(store.dir, `${session.meta.id}.images`);
|
|
18448
18705
|
try {
|
|
18449
18706
|
mkdirSync12(dir, { recursive: true });
|
|
18450
18707
|
} catch (e) {
|
|
18451
|
-
|
|
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:
|
|
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 =
|
|
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(
|
|
18818
|
+
mkdirSync12(join16(cwd, ".agent"), { recursive: true });
|
|
18562
18819
|
appendFileSync(histPath, line + "\n");
|
|
18563
18820
|
} catch (e) {
|
|
18564
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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) =>
|
|
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" :
|
|
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(
|
|
20011
|
+
return readdirSync4(join16(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
|
|
19755
20012
|
} catch (e) {
|
|
19756
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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,
|
|
20363
|
-
onEvent: (e) =>
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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);
|