@livx.cc/agentx 0.99.86 → 0.99.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +399 -137
- 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 ?? "?";
|
|
@@ -16958,6 +17215,7 @@ function resolveModelOrNewest(model) {
|
|
|
16958
17215
|
return fallback;
|
|
16959
17216
|
}
|
|
16960
17217
|
var ENV_KEY_ALIASES = { google: ["GEMINI_API_KEY"] };
|
|
17218
|
+
var fileEnvNames = /* @__PURE__ */ new Set();
|
|
16961
17219
|
function loadEnvFile(file) {
|
|
16962
17220
|
if (!existsSync9(file)) return;
|
|
16963
17221
|
for (const line of readFileSync10(file, "utf8").split("\n")) {
|
|
@@ -16967,14 +17225,15 @@ function loadEnvFile(file) {
|
|
|
16967
17225
|
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) val = val.slice(1, -1);
|
|
16968
17226
|
else val = val.replace(/\s+#.*$/, "").trim();
|
|
16969
17227
|
process.env[m[1]] = val;
|
|
17228
|
+
fileEnvNames.add(m[1]);
|
|
16970
17229
|
}
|
|
16971
17230
|
}
|
|
16972
17231
|
function loadInstallEnv() {
|
|
16973
17232
|
let dir = dirname6(import.meta.path);
|
|
16974
|
-
for (let i = 0; i < 5 && !existsSync9(
|
|
17233
|
+
for (let i = 0; i < 5 && !existsSync9(join16(dir, "package.json")); i++) dir = dirname6(dir);
|
|
16975
17234
|
for (const name of [".env", ".env.local"]) {
|
|
16976
|
-
loadEnvFile(
|
|
16977
|
-
loadEnvFile(
|
|
17235
|
+
loadEnvFile(join16(dir, name));
|
|
17236
|
+
loadEnvFile(join16(homedir10(), ".agent", name));
|
|
16978
17237
|
}
|
|
16979
17238
|
}
|
|
16980
17239
|
async function loadBodifySecrets() {
|
|
@@ -16993,7 +17252,7 @@ async function loadBodifySecrets() {
|
|
|
16993
17252
|
const { values } = await res.json();
|
|
16994
17253
|
if (!values) return;
|
|
16995
17254
|
for (const [k, v] of Object.entries(values)) {
|
|
16996
|
-
if (!(k in process.env)) process.env[k] = v;
|
|
17255
|
+
if (!(k in process.env) || fileEnvNames.has(k)) process.env[k] = v;
|
|
16997
17256
|
}
|
|
16998
17257
|
} catch (e) {
|
|
16999
17258
|
console.error(` bodify secrets: ${e.message ?? e}`);
|
|
@@ -17471,7 +17730,7 @@ function turnCost(model, usage) {
|
|
|
17471
17730
|
const priced = model.startsWith("claude-code/") ? `anthropic/${resolveModelAlias(model.slice("claude-code/".length))}` : model;
|
|
17472
17731
|
return costOf(getModelInfo(priced)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, priced);
|
|
17473
17732
|
}
|
|
17474
|
-
async function evaluateGoal(ai, condition, transcript,
|
|
17733
|
+
async function evaluateGoal(ai, condition, transcript, log37) {
|
|
17475
17734
|
const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
|
|
17476
17735
|
const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
|
|
17477
17736
|
return text.slice(0, 600);
|
|
@@ -17491,7 +17750,7 @@ ${recent}` }
|
|
|
17491
17750
|
const match = r.content.match(/\{[\s\S]*\}/);
|
|
17492
17751
|
if (match) return JSON.parse(match[0]);
|
|
17493
17752
|
} catch (e) {
|
|
17494
|
-
|
|
17753
|
+
log37(dim(` (goal evaluator error: ${e?.message ?? e})
|
|
17495
17754
|
`));
|
|
17496
17755
|
}
|
|
17497
17756
|
return { met: false, reason: "evaluation unclear" };
|
|
@@ -17717,13 +17976,13 @@ function mcpAgentTools(mounted, opts) {
|
|
|
17717
17976
|
return tools;
|
|
17718
17977
|
}
|
|
17719
17978
|
async function closeMcp(mounted) {
|
|
17720
|
-
await Promise.all(mounted.map((m) => m.client.close().catch((e) =>
|
|
17979
|
+
await Promise.all(mounted.map((m) => m.client.close().catch((e) => log36.debug("mcp close failed", e))));
|
|
17721
17980
|
}
|
|
17722
17981
|
var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
|
|
17723
17982
|
function mentionRefs(line) {
|
|
17724
17983
|
return [...line.matchAll(/(?:^|\s)@(?:"([^"]+)"|(\S+))/g)].map((m) => m[1] ?? m[2].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
|
|
17725
17984
|
}
|
|
17726
|
-
var untilde = (p) => p.startsWith("~/") || IS_WIN && p.startsWith("~\\") ?
|
|
17985
|
+
var untilde = (p) => p.startsWith("~/") || IS_WIN && p.startsWith("~\\") ? join16(homedir10(), p.slice(2)) : p;
|
|
17727
17986
|
var PATH_LIKE = IS_WIN ? /^(\/|~[\\/]|\.\.?[\\/]|[A-Za-z]:[\\/]|\\\\)/ : /^(\/|~\/|\.\/|\.\.\/)/;
|
|
17728
17987
|
var userPath = (cwd, p) => {
|
|
17729
17988
|
const t = untilde(p);
|
|
@@ -17776,7 +18035,7 @@ async function expandMentions(fs, line) {
|
|
|
17776
18035
|
if (loaded.includes(ref) || missing.includes(ref)) continue;
|
|
17777
18036
|
if (ref.includes(":") && mcpMentionResolver) {
|
|
17778
18037
|
const body = await mcpMentionResolver(ref).catch((e) => {
|
|
17779
|
-
|
|
18038
|
+
log36.debug("mcp mention resolve failed", e);
|
|
17780
18039
|
return null;
|
|
17781
18040
|
});
|
|
17782
18041
|
if (body != null) {
|
|
@@ -17867,7 +18126,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
17867
18126
|
try {
|
|
17868
18127
|
store.save(session);
|
|
17869
18128
|
} catch (ex) {
|
|
17870
|
-
|
|
18129
|
+
log36.debug("mid-turn session flush failed", ex);
|
|
17871
18130
|
}
|
|
17872
18131
|
}
|
|
17873
18132
|
return origNotify(e);
|
|
@@ -17935,7 +18194,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
17935
18194
|
}
|
|
17936
18195
|
return { ok, res };
|
|
17937
18196
|
}
|
|
17938
|
-
var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [
|
|
18197
|
+
var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [join16(store.dir, `${data.meta.id}.images`), cwd]);
|
|
17939
18198
|
function startSession(args, store, agent, cwd) {
|
|
17940
18199
|
if (args.resume || args.cont) {
|
|
17941
18200
|
const data = args.resume ? store.load(args.resume) ?? globalSessionLoad(args.resume) : store.latestData();
|
|
@@ -17982,19 +18241,19 @@ var AGENTS_MD_TEMPLATE = `# ${"${name}"}
|
|
|
17982
18241
|
`;
|
|
17983
18242
|
function initInstructions(cwd) {
|
|
17984
18243
|
for (const f of ["AGENTS.md", "CLAUDE.md"]) {
|
|
17985
|
-
if (existsSync9(
|
|
18244
|
+
if (existsSync9(join16(cwd, f))) {
|
|
17986
18245
|
err(yellow(` ${f} already exists \u2014 leaving it as-is
|
|
17987
18246
|
`));
|
|
17988
18247
|
return;
|
|
17989
18248
|
}
|
|
17990
18249
|
}
|
|
17991
|
-
const path =
|
|
18250
|
+
const path = join16(cwd, "AGENTS.md");
|
|
17992
18251
|
writeFileSync9(path, AGENTS_MD_TEMPLATE.replace("${name}", basename4(cwd)));
|
|
17993
18252
|
err(green(` created ${path}
|
|
17994
18253
|
`) + dim(" edit it, then it auto-loads into every run.\n"));
|
|
17995
18254
|
}
|
|
17996
18255
|
function persistSetting(cwd, key, value) {
|
|
17997
|
-
const path =
|
|
18256
|
+
const path = join16(cwd, ".agent", "settings.json");
|
|
17998
18257
|
try {
|
|
17999
18258
|
const obj = existsSync9(path) ? JSON.parse(readFileSync10(path, "utf8")) : {};
|
|
18000
18259
|
if (obj[key] === value) return;
|
|
@@ -18016,14 +18275,14 @@ var isCancelTeardown = (e) => {
|
|
|
18016
18275
|
function installCancelGuards(mounted) {
|
|
18017
18276
|
process.on("unhandledRejection", (e) => {
|
|
18018
18277
|
if (isCancelTeardown(e)) {
|
|
18019
|
-
|
|
18278
|
+
log36.debug("suppressed unhandledRejection (cursor stream cancel)", e);
|
|
18020
18279
|
return;
|
|
18021
18280
|
}
|
|
18022
|
-
|
|
18281
|
+
log36.error("unhandledRejection", e);
|
|
18023
18282
|
});
|
|
18024
18283
|
process.on("uncaughtException", (e) => {
|
|
18025
18284
|
if (isCancelTeardown(e)) {
|
|
18026
|
-
|
|
18285
|
+
log36.debug("suppressed uncaughtException (cursor stream cancel)", e);
|
|
18027
18286
|
return;
|
|
18028
18287
|
}
|
|
18029
18288
|
console.error(e);
|
|
@@ -18032,7 +18291,7 @@ function installCancelGuards(mounted) {
|
|
|
18032
18291
|
});
|
|
18033
18292
|
}
|
|
18034
18293
|
async function repl(args, ai, cfg, cwd) {
|
|
18035
|
-
const oauth = new McpOAuth({ storePath:
|
|
18294
|
+
const oauth = new McpOAuth({ storePath: join16(cwd, ".agent", "mcp-auth.json") });
|
|
18036
18295
|
const mounted = await mountMcp(cfg, oauth);
|
|
18037
18296
|
let mcpToolNames = [];
|
|
18038
18297
|
const initialMcpTools = mcpAgentTools(mounted);
|
|
@@ -18310,7 +18569,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
18310
18569
|
quickLook: {
|
|
18311
18570
|
branch: () => {
|
|
18312
18571
|
try {
|
|
18313
|
-
const head = readFileSync10(
|
|
18572
|
+
const head = readFileSync10(join16(cwd, ".git", "HEAD"), "utf8").trim();
|
|
18314
18573
|
return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
|
|
18315
18574
|
} catch {
|
|
18316
18575
|
return "not a git repository";
|
|
@@ -18444,11 +18703,11 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
18444
18703
|
const pendingImages = [];
|
|
18445
18704
|
const bangContext = [];
|
|
18446
18705
|
const grabClipboardAttachment = () => {
|
|
18447
|
-
const dir =
|
|
18706
|
+
const dir = join16(store.dir, `${session.meta.id}.images`);
|
|
18448
18707
|
try {
|
|
18449
18708
|
mkdirSync12(dir, { recursive: true });
|
|
18450
18709
|
} catch (e) {
|
|
18451
|
-
|
|
18710
|
+
log36.debug("paste image dir create failed", e);
|
|
18452
18711
|
}
|
|
18453
18712
|
const img = grabClipboardImage(dir, String(Date.now()));
|
|
18454
18713
|
return img ? { display: "Image", ref: "@" + img.path, path: img.path } : null;
|
|
@@ -18502,7 +18761,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
18502
18761
|
err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
|
|
18503
18762
|
`));
|
|
18504
18763
|
}
|
|
18505
|
-
const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir:
|
|
18764
|
+
const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir: join16(cwd, ".agent", "checkpoints.git"), addDirs: args.addDirs, sessionId: session.meta.id });
|
|
18506
18765
|
const cpHooks = checkpoints.hooks?.();
|
|
18507
18766
|
if (cpHooks) {
|
|
18508
18767
|
agent.options.hooks = composeHooks(agent.options.hooks, cpHooks);
|
|
@@ -18554,14 +18813,14 @@ ${lines.join("\n")}
|
|
|
18554
18813
|
Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
|
|
18555
18814
|
</system-reminder>`;
|
|
18556
18815
|
};
|
|
18557
|
-
const histPath =
|
|
18816
|
+
const histPath = join16(cwd, ".agent", "history");
|
|
18558
18817
|
const history = existsSync9(histPath) ? readFileSync10(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
|
|
18559
18818
|
const remember2 = (line) => {
|
|
18560
18819
|
try {
|
|
18561
|
-
mkdirSync12(
|
|
18820
|
+
mkdirSync12(join16(cwd, ".agent"), { recursive: true });
|
|
18562
18821
|
appendFileSync(histPath, line + "\n");
|
|
18563
18822
|
} catch (e) {
|
|
18564
|
-
|
|
18823
|
+
log36.debug("history write failed", e);
|
|
18565
18824
|
}
|
|
18566
18825
|
};
|
|
18567
18826
|
const ago = (t) => {
|
|
@@ -18633,7 +18892,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
18633
18892
|
try {
|
|
18634
18893
|
store.save(session);
|
|
18635
18894
|
} catch (e) {
|
|
18636
|
-
|
|
18895
|
+
log36.debug("session save after rewind failed", e);
|
|
18637
18896
|
}
|
|
18638
18897
|
err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
|
|
18639
18898
|
`));
|
|
@@ -18668,7 +18927,7 @@ ${task}`;
|
|
|
18668
18927
|
bangContext.length = 0;
|
|
18669
18928
|
}
|
|
18670
18929
|
const delta = await refreshCatalogs().catch((e) => {
|
|
18671
|
-
|
|
18930
|
+
log36.debug("catalog refresh failed", e);
|
|
18672
18931
|
return "";
|
|
18673
18932
|
});
|
|
18674
18933
|
if (delta) {
|
|
@@ -18931,7 +19190,7 @@ ${task}`;
|
|
|
18931
19190
|
desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
|
|
18932
19191
|
run: async () => {
|
|
18933
19192
|
await refreshCatalogs().catch((e) => {
|
|
18934
|
-
|
|
19193
|
+
log36.debug("catalog refresh failed", e);
|
|
18935
19194
|
});
|
|
18936
19195
|
face.reprepare();
|
|
18937
19196
|
err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
|
|
@@ -19434,7 +19693,7 @@ ${task}`;
|
|
|
19434
19693
|
try {
|
|
19435
19694
|
for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
|
|
19436
19695
|
} catch (e) {
|
|
19437
|
-
|
|
19696
|
+
log36.debug(`loadAgents(${d}) failed`, e);
|
|
19438
19697
|
}
|
|
19439
19698
|
}
|
|
19440
19699
|
if (!seen.size) {
|
|
@@ -19522,7 +19781,7 @@ ${task}`;
|
|
|
19522
19781
|
}
|
|
19523
19782
|
if (idx >= 0) {
|
|
19524
19783
|
const old = mounted.splice(idx, 1)[0];
|
|
19525
|
-
await old.client.close().catch((e) =>
|
|
19784
|
+
await old.client.close().catch((e) => log36.debug("mcp close failed", e));
|
|
19526
19785
|
}
|
|
19527
19786
|
try {
|
|
19528
19787
|
const m = await mountMcpServer(name, conf);
|
|
@@ -19550,7 +19809,7 @@ ${task}`;
|
|
|
19550
19809
|
}
|
|
19551
19810
|
const m = mounted.splice(idx, 1)[0];
|
|
19552
19811
|
remountMcpTools();
|
|
19553
|
-
await m.client.close().catch((e) =>
|
|
19812
|
+
await m.client.close().catch((e) => log36.debug("mcp close failed", e));
|
|
19554
19813
|
err(dim(` removed "${name}"
|
|
19555
19814
|
`));
|
|
19556
19815
|
return;
|
|
@@ -19665,7 +19924,7 @@ ${task}`;
|
|
|
19665
19924
|
return;
|
|
19666
19925
|
}
|
|
19667
19926
|
const md = exportMarkdown(session.meta, shown);
|
|
19668
|
-
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" :
|
|
19927
|
+
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join16(".agent", "exports", `${session.meta.id}.md`);
|
|
19669
19928
|
const path = resolve5(cwd, name);
|
|
19670
19929
|
try {
|
|
19671
19930
|
mkdirSync12(dirname6(path), { recursive: true });
|
|
@@ -19751,9 +20010,9 @@ ${task}`;
|
|
|
19751
20010
|
});
|
|
19752
20011
|
const listDir = (absDir) => {
|
|
19753
20012
|
try {
|
|
19754
|
-
return readdirSync4(
|
|
20013
|
+
return readdirSync4(join16(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
|
|
19755
20014
|
} catch (e) {
|
|
19756
|
-
|
|
20015
|
+
log36.debug("completion readdir failed", absDir, e);
|
|
19757
20016
|
return null;
|
|
19758
20017
|
}
|
|
19759
20018
|
};
|
|
@@ -19890,7 +20149,7 @@ ${out}
|
|
|
19890
20149
|
return;
|
|
19891
20150
|
}
|
|
19892
20151
|
await refreshCatalogs().catch((e) => {
|
|
19893
|
-
|
|
20152
|
+
log36.debug("catalog refresh failed", e);
|
|
19894
20153
|
});
|
|
19895
20154
|
const sk = skills.find((x) => x.name === name);
|
|
19896
20155
|
if (sk) {
|
|
@@ -19936,7 +20195,7 @@ ${out}
|
|
|
19936
20195
|
return false;
|
|
19937
20196
|
}
|
|
19938
20197
|
const fakeVoice = process.env.AGENTX_VOICE_FAKE ? fakeVoiceParts(process.env.AGENTX_VOICE_FAKE) : null;
|
|
19939
|
-
voiceDiag = new JsonlDiagSink(
|
|
20198
|
+
voiceDiag = new JsonlDiagSink(join16(store.dir, `${session.meta.id}.voice.jsonl`));
|
|
19940
20199
|
voiceIO = new VoiceIO({
|
|
19941
20200
|
...fakeVoice ?? {},
|
|
19942
20201
|
onDiag: (ev) => voiceDiag?.push(ev),
|
|
@@ -20001,7 +20260,7 @@ ${out}
|
|
|
20001
20260
|
if (delivery || cut) dx?.parkCutDelivery(delivery, !!cut, heard);
|
|
20002
20261
|
const cutReply = cut && cut.full.length - cut.heard.length > 40 ? cut : null;
|
|
20003
20262
|
const noteP = replaces ? replaces.note : !cutReply ? Promise.resolve("") : (instinct ? instinct.bargeIntent(text).catch((e) => {
|
|
20004
|
-
|
|
20263
|
+
log36.warn(`barge intent failed \u2014 model decides: ${e.message}`);
|
|
20005
20264
|
return void 0;
|
|
20006
20265
|
}) : Promise.resolve(void 0)).then(async (intent) => interruptionNote(cutReply, intent) + ackedNote(await instinct?.ackFor(text)));
|
|
20007
20266
|
const cutIn = replaces ? replaces.cutIn : interjectNote;
|
|
@@ -20357,16 +20616,19 @@ var leakDecider = () => process.env.TYPESAFE_AI_KEY ? new DecideClient({ apiKey:
|
|
|
20357
20616
|
function makeSecondChannel(ai, reflexModel, opts) {
|
|
20358
20617
|
const decider = leakDecider();
|
|
20359
20618
|
const instinct = decider ? new Instinct({ decider }) : void 0;
|
|
20619
|
+
const model = process.env.AGENTX_CHANNEL_MODEL || reflexModel;
|
|
20360
20620
|
return new SecondChannel({
|
|
20621
|
+
// Banks in the user's language: generated once per language, cached + topped up across sessions (~/.agent/cache).
|
|
20622
|
+
banks: new ChannelBanks({ generate: fastBankGenerator(ai, model), cacheDir: join16(homedir10(), ".agent", "cache", "channel-banks") }),
|
|
20361
20623
|
decide: instinct ? (heard, state) => instinct.channel(heard, state) : void 0,
|
|
20362
|
-
generate: fastLineGenerator(ai,
|
|
20363
|
-
onEvent: (e) =>
|
|
20624
|
+
generate: fastLineGenerator(ai, model),
|
|
20625
|
+
onEvent: (e) => log36.debug(`channel ${e.trigger}: rule=${e.rule} choice=${e.choice ?? "-"} \u2192 ${e.acted}${e.text ? ` "${e.text}"` : ""} ${e.ms}ms`),
|
|
20364
20626
|
...opts
|
|
20365
20627
|
});
|
|
20366
20628
|
}
|
|
20367
20629
|
async function duplexServe(args, ai, cfg, cwd) {
|
|
20368
20630
|
const out = (o) => process.stdout.write(JSON.stringify(o) + "\n");
|
|
20369
|
-
const mounted = await mountMcp(cfg, new McpOAuth({ storePath:
|
|
20631
|
+
const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join16(cwd, ".agent", "mcp-auth.json") }));
|
|
20370
20632
|
installCancelGuards(mounted);
|
|
20371
20633
|
const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted, { quiet: true }), mounted.map((m) => m.name));
|
|
20372
20634
|
const { host: _h, stream: _s, signal: _g, providerOptions: _po, ...wo } = agent.options;
|
|
@@ -20418,7 +20680,7 @@ async function duplexServe(args, ai, cfg, cwd) {
|
|
|
20418
20680
|
try {
|
|
20419
20681
|
store.save(session);
|
|
20420
20682
|
} catch (e) {
|
|
20421
|
-
|
|
20683
|
+
log36.warn("duplex session save failed", e);
|
|
20422
20684
|
}
|
|
20423
20685
|
};
|
|
20424
20686
|
out({ type: "ready", sessionId: session.meta.id, reflexModel: dx.options.reflexModel, actModel: dx.options.actModel, ...notes.length ? { notes } : {} });
|
|
@@ -20454,7 +20716,7 @@ async function duplexServe(args, ai, cfg, cwd) {
|
|
|
20454
20716
|
done2();
|
|
20455
20717
|
out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error", ...why ? { error: why } : {} });
|
|
20456
20718
|
}).catch((e) => {
|
|
20457
|
-
|
|
20719
|
+
log36.error("duplex turn failed", e);
|
|
20458
20720
|
done2();
|
|
20459
20721
|
out({ type: "turn_done", id, finishReason: "error", error: String(e?.message ?? e) });
|
|
20460
20722
|
});
|
|
@@ -20615,7 +20877,7 @@ async function main() {
|
|
|
20615
20877
|
worktreeCleanup?.();
|
|
20616
20878
|
process.exit(0);
|
|
20617
20879
|
}
|
|
20618
|
-
const mounted = await mountMcp(cfg, new McpOAuth({ storePath:
|
|
20880
|
+
const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join16(cwd, ".agent", "mcp-auth.json") }));
|
|
20619
20881
|
const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted), mounted.map((m) => m.name));
|
|
20620
20882
|
const store = new SessionStore(cwd);
|
|
20621
20883
|
const session = startSession(args, store, agent, cwd);
|