@mrciphersmith/keryx 0.3.63 → 0.3.64
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/README.md +10 -2
- package/dist/cli.js +1761 -394
- package/dist/core.js +12 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20107,7 +20107,7 @@ var package_default;
|
|
|
20107
20107
|
var init_package = __esm(() => {
|
|
20108
20108
|
package_default = {
|
|
20109
20109
|
name: "@mrciphersmith/keryx",
|
|
20110
|
-
version: "0.3.
|
|
20110
|
+
version: "0.3.64",
|
|
20111
20111
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
20112
20112
|
private: false,
|
|
20113
20113
|
publishConfig: {
|
|
@@ -30912,6 +30912,9 @@ function summarizeSubmittedLine(line) {
|
|
|
30912
30912
|
function isZeroWidth(cp) {
|
|
30913
30913
|
return cp >= 768 && cp <= 879 || cp >= 8203 && cp <= 8207 || cp >= 65024 && cp <= 65039 || cp >= 8288 && cp <= 8292;
|
|
30914
30914
|
}
|
|
30915
|
+
function isEmojiModifier(cp) {
|
|
30916
|
+
return cp >= 127995 && cp <= 127999;
|
|
30917
|
+
}
|
|
30915
30918
|
function codePointWidth(cp) {
|
|
30916
30919
|
for (const [lo, hi] of WIDE_RANGES) {
|
|
30917
30920
|
if (cp >= lo && cp <= hi) {
|
|
@@ -30922,11 +30925,17 @@ function codePointWidth(cp) {
|
|
|
30922
30925
|
}
|
|
30923
30926
|
function visualWidth(text) {
|
|
30924
30927
|
let width = 0;
|
|
30928
|
+
let previous = 0;
|
|
30925
30929
|
for (const ch of text) {
|
|
30926
30930
|
const codePoint = ch.codePointAt(0) ?? 0;
|
|
30931
|
+
const joined = previous === ZERO_WIDTH_JOINER;
|
|
30932
|
+
previous = codePoint;
|
|
30927
30933
|
if (isZeroWidth(codePoint)) {
|
|
30928
30934
|
continue;
|
|
30929
30935
|
}
|
|
30936
|
+
if (joined && codePointWidth(codePoint) === 2 || isEmojiModifier(codePoint)) {
|
|
30937
|
+
continue;
|
|
30938
|
+
}
|
|
30930
30939
|
width += codePointWidth(codePoint);
|
|
30931
30940
|
}
|
|
30932
30941
|
return width;
|
|
@@ -30939,7 +30948,7 @@ function hugWidth(text, chrome) {
|
|
|
30939
30948
|
}
|
|
30940
30949
|
return widest + chrome;
|
|
30941
30950
|
}
|
|
30942
|
-
var FENCE_LINE, HUNK_HEADER, MARKDOWN_LANGS, DIFF_LANGS, CODE_LANG_ALIASES, GENERIC_KEYWORDS, CODE_LANG_KEYWORDS, CODE_WORD_OR_NUMBER, WIDE_RANGES;
|
|
30951
|
+
var FENCE_LINE, HUNK_HEADER, MARKDOWN_LANGS, DIFF_LANGS, CODE_LANG_ALIASES, GENERIC_KEYWORDS, CODE_LANG_KEYWORDS, CODE_WORD_OR_NUMBER, WIDE_RANGES, ZERO_WIDTH_JOINER = 8205;
|
|
30943
30952
|
var init_md_blocks = __esm(() => {
|
|
30944
30953
|
FENCE_LINE = /^[ \t]{0,3}(```|~~~)(.*)\r?$/;
|
|
30945
30954
|
HUNK_HEADER = /^@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@/;
|
|
@@ -31187,8 +31196,48 @@ var init_md_blocks = __esm(() => {
|
|
|
31187
31196
|
[65072, 65135],
|
|
31188
31197
|
[65280, 65376],
|
|
31189
31198
|
[65504, 65510],
|
|
31199
|
+
[8986, 8987],
|
|
31200
|
+
[9193, 9196],
|
|
31201
|
+
[9200, 9200],
|
|
31202
|
+
[9203, 9203],
|
|
31203
|
+
[9725, 9726],
|
|
31204
|
+
[9748, 9749],
|
|
31205
|
+
[9800, 9811],
|
|
31206
|
+
[9855, 9855],
|
|
31207
|
+
[9875, 9875],
|
|
31208
|
+
[9889, 9889],
|
|
31209
|
+
[9898, 9899],
|
|
31210
|
+
[9917, 9918],
|
|
31211
|
+
[9924, 9925],
|
|
31212
|
+
[9934, 9934],
|
|
31213
|
+
[9940, 9940],
|
|
31214
|
+
[9962, 9962],
|
|
31215
|
+
[9970, 9971],
|
|
31216
|
+
[9973, 9973],
|
|
31217
|
+
[9978, 9978],
|
|
31218
|
+
[9981, 9981],
|
|
31219
|
+
[9989, 9989],
|
|
31220
|
+
[9994, 9995],
|
|
31221
|
+
[10024, 10024],
|
|
31222
|
+
[10060, 10060],
|
|
31223
|
+
[10062, 10062],
|
|
31224
|
+
[10067, 10069],
|
|
31225
|
+
[10071, 10071],
|
|
31226
|
+
[10133, 10135],
|
|
31227
|
+
[10160, 10160],
|
|
31228
|
+
[10175, 10175],
|
|
31229
|
+
[11035, 11036],
|
|
31230
|
+
[11088, 11088],
|
|
31231
|
+
[11093, 11093],
|
|
31232
|
+
[126980, 126980],
|
|
31233
|
+
[127183, 127183],
|
|
31234
|
+
[127374, 127374],
|
|
31235
|
+
[127377, 127386],
|
|
31236
|
+
[127488, 127743],
|
|
31190
31237
|
[127744, 128591],
|
|
31238
|
+
[128640, 128767],
|
|
31191
31239
|
[129280, 129535],
|
|
31240
|
+
[129536, 129791],
|
|
31192
31241
|
[131072, 262141]
|
|
31193
31242
|
];
|
|
31194
31243
|
});
|
|
@@ -100461,6 +100510,264 @@ var init_live_render = __esm(() => {
|
|
|
100461
100510
|
ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
100462
100511
|
});
|
|
100463
100512
|
|
|
100513
|
+
// src/remote/paths.ts
|
|
100514
|
+
import path228 from "path";
|
|
100515
|
+
function remoteDirPath(dir) {
|
|
100516
|
+
return path228.join(keryxConfigDir(dir), REMOTE_DIRNAME);
|
|
100517
|
+
}
|
|
100518
|
+
function ensureRemoteDir(dir, ...sub) {
|
|
100519
|
+
return ensureKeryxSubdir([REMOTE_DIRNAME, ...sub], dir);
|
|
100520
|
+
}
|
|
100521
|
+
function botTokenPath(dir) {
|
|
100522
|
+
return path228.join(remoteDirPath(dir), BOT_TOKEN_FILE);
|
|
100523
|
+
}
|
|
100524
|
+
function remoteConfigPath(dir) {
|
|
100525
|
+
return path228.join(remoteDirPath(dir), REMOTE_CONFIG_FILE);
|
|
100526
|
+
}
|
|
100527
|
+
var REMOTE_DIRNAME = "remote", BOT_TOKEN_FILE = "bot-token", REMOTE_CONFIG_FILE = "config.json", REGISTRY_FILE = "sessions.json", SERVICE_TOPICS_FILE = "service-topics.json", POLLER_STATE_FILE = "poller-state.json", OUTBOUND_FILE = "outbound.jsonl", REJECTED_JOURNAL_FILE = "rejected.jsonl", INBOUND_DIRNAME = "inbound";
|
|
100528
|
+
var init_paths4 = __esm(() => {
|
|
100529
|
+
init_config_dir();
|
|
100530
|
+
});
|
|
100531
|
+
|
|
100532
|
+
// src/remote/rendering-mode.ts
|
|
100533
|
+
function isRenderMode(value) {
|
|
100534
|
+
return typeof value === "string" && RENDER_MODES.includes(value);
|
|
100535
|
+
}
|
|
100536
|
+
var RENDER_MODES, DEFAULT_RENDER_MODE = "auto", RENDER_MODE_CHOICES;
|
|
100537
|
+
var init_rendering_mode = __esm(() => {
|
|
100538
|
+
RENDER_MODES = ["auto", "rich", "html", "plain"];
|
|
100539
|
+
RENDER_MODE_CHOICES = `one of ${RENDER_MODES.join(", ")}`;
|
|
100540
|
+
});
|
|
100541
|
+
|
|
100542
|
+
// src/remote/config.ts
|
|
100543
|
+
import { statSync as statSync14, unlinkSync as unlinkSync4 } from "fs";
|
|
100544
|
+
function isSafeInt(value) {
|
|
100545
|
+
return typeof value === "number" && Number.isSafeInteger(value);
|
|
100546
|
+
}
|
|
100547
|
+
function parseRemoteConfig(value) {
|
|
100548
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
100549
|
+
return { ok: false, reason: "remote config must be a JSON object" };
|
|
100550
|
+
}
|
|
100551
|
+
const doc = value;
|
|
100552
|
+
for (const key of Object.keys(doc)) {
|
|
100553
|
+
if (!CONFIG_KEYS.has(key)) {
|
|
100554
|
+
const shown = /^[A-Za-z_][A-Za-z0-9_]{0,30}$/.test(key) ? key : "<not shown>";
|
|
100555
|
+
return { ok: false, reason: `remote config has an unknown key "${shown}" (the schema is closed)` };
|
|
100556
|
+
}
|
|
100557
|
+
}
|
|
100558
|
+
if (doc.schemaVersion !== REMOTE_CONFIG_SCHEMA_VERSION) {
|
|
100559
|
+
return { ok: false, reason: `remote config schemaVersion must be ${REMOTE_CONFIG_SCHEMA_VERSION}` };
|
|
100560
|
+
}
|
|
100561
|
+
if (!isSafeInt(doc.chatId) || doc.chatId === 0) {
|
|
100562
|
+
return { ok: false, reason: "remote config chatId must be a non-zero integer (the supergroup id)" };
|
|
100563
|
+
}
|
|
100564
|
+
const ids = doc.allowedUserIds;
|
|
100565
|
+
if (!Array.isArray(ids) || ids.length === 0 || !ids.every((id) => isSafeInt(id) && id > 0)) {
|
|
100566
|
+
return { ok: false, reason: "remote config allowedUserIds must be a non-empty array of positive integers" };
|
|
100567
|
+
}
|
|
100568
|
+
const timeout = (key, fallback) => {
|
|
100569
|
+
const raw = doc[key];
|
|
100570
|
+
if (raw === undefined) {
|
|
100571
|
+
return { ok: true, value: fallback };
|
|
100572
|
+
}
|
|
100573
|
+
if (!isSafeInt(raw) || raw < 1 || raw > MAX_TIMEOUT_MS3) {
|
|
100574
|
+
return { ok: false, reason: `remote config ${key} must be an integer from 1 to ${MAX_TIMEOUT_MS3} (milliseconds)` };
|
|
100575
|
+
}
|
|
100576
|
+
return { ok: true, value: raw };
|
|
100577
|
+
};
|
|
100578
|
+
const orphan = timeout("orphanMs", DEFAULT_ORPHAN_MS);
|
|
100579
|
+
if (!orphan.ok) {
|
|
100580
|
+
return orphan;
|
|
100581
|
+
}
|
|
100582
|
+
const run = timeout("runTimeoutMs", DEFAULT_RUN_TIMEOUT_MS);
|
|
100583
|
+
if (!run.ok) {
|
|
100584
|
+
return run;
|
|
100585
|
+
}
|
|
100586
|
+
if (doc.rendering !== undefined && !isRenderMode(doc.rendering)) {
|
|
100587
|
+
return { ok: false, reason: `remote config rendering must be ${RENDER_MODE_CHOICES}` };
|
|
100588
|
+
}
|
|
100589
|
+
return {
|
|
100590
|
+
ok: true,
|
|
100591
|
+
value: {
|
|
100592
|
+
schemaVersion: REMOTE_CONFIG_SCHEMA_VERSION,
|
|
100593
|
+
chatId: doc.chatId,
|
|
100594
|
+
allowedUserIds: [...new Set(ids)],
|
|
100595
|
+
orphanMs: orphan.value,
|
|
100596
|
+
runTimeoutMs: run.value,
|
|
100597
|
+
...doc.rendering === undefined ? {} : { rendering: doc.rendering }
|
|
100598
|
+
}
|
|
100599
|
+
};
|
|
100600
|
+
}
|
|
100601
|
+
function loadRemoteConfig(dir) {
|
|
100602
|
+
const file = remoteConfigPath(dir);
|
|
100603
|
+
const read = readConfigFile(file);
|
|
100604
|
+
if (!read.ok) {
|
|
100605
|
+
return {
|
|
100606
|
+
ok: false,
|
|
100607
|
+
reason: read.reason === "absent" ? `no remote-control config at ${file}; create it with chatId and allowedUserIds` : `remote-control config at ${file} cannot be read (${read.reason})`
|
|
100608
|
+
};
|
|
100609
|
+
}
|
|
100610
|
+
let parsed;
|
|
100611
|
+
try {
|
|
100612
|
+
parsed = JSON.parse(read.text);
|
|
100613
|
+
} catch {
|
|
100614
|
+
return { ok: false, reason: `remote-control config at ${file} is not valid JSON` };
|
|
100615
|
+
}
|
|
100616
|
+
return parseRemoteConfig(parsed);
|
|
100617
|
+
}
|
|
100618
|
+
function saveRemoteConfig(config, dir) {
|
|
100619
|
+
const checked = parseRemoteConfig(config);
|
|
100620
|
+
if (!checked.ok) {
|
|
100621
|
+
return checked;
|
|
100622
|
+
}
|
|
100623
|
+
ensureRemoteDir(dir);
|
|
100624
|
+
writeOwnerOnlyFileAtomic(remoteConfigPath(dir), `${JSON.stringify(checked.value, null, 2)}
|
|
100625
|
+
`);
|
|
100626
|
+
return checked;
|
|
100627
|
+
}
|
|
100628
|
+
function loadBotToken(dir) {
|
|
100629
|
+
const file = botTokenPath(dir);
|
|
100630
|
+
let mode;
|
|
100631
|
+
try {
|
|
100632
|
+
const stats = statSync14(file);
|
|
100633
|
+
if (!stats.isFile()) {
|
|
100634
|
+
return { ok: false, reason: `bot token path ${file} is not a regular file` };
|
|
100635
|
+
}
|
|
100636
|
+
mode = stats.mode & 511;
|
|
100637
|
+
} catch {
|
|
100638
|
+
return {
|
|
100639
|
+
ok: false,
|
|
100640
|
+
reason: `no bot token file at ${file}; put the token on one line in it and run: chmod 600 ${file}`
|
|
100641
|
+
};
|
|
100642
|
+
}
|
|
100643
|
+
if (process.platform !== "win32" && (mode & 63) !== 0) {
|
|
100644
|
+
return {
|
|
100645
|
+
ok: false,
|
|
100646
|
+
reason: `bot token file ${file} is accessible to other users (mode ${mode.toString(8)}); run: chmod 600 ${file}`
|
|
100647
|
+
};
|
|
100648
|
+
}
|
|
100649
|
+
const read = readConfigFile(file);
|
|
100650
|
+
if (!read.ok) {
|
|
100651
|
+
return { ok: false, reason: `bot token file ${file} cannot be read (${read.reason})` };
|
|
100652
|
+
}
|
|
100653
|
+
const token = read.text.trim();
|
|
100654
|
+
if (!TOKEN_SHAPE.test(token)) {
|
|
100655
|
+
return {
|
|
100656
|
+
ok: false,
|
|
100657
|
+
reason: `bot token file ${file} does not hold a bot token (expected <digits>:<secret> on a single line)`
|
|
100658
|
+
};
|
|
100659
|
+
}
|
|
100660
|
+
return { ok: true, value: token };
|
|
100661
|
+
}
|
|
100662
|
+
function saveBotToken(token, dir) {
|
|
100663
|
+
const trimmed = token.trim();
|
|
100664
|
+
if (!TOKEN_SHAPE.test(trimmed)) {
|
|
100665
|
+
return { ok: false, reason: "that does not look like a bot token (expected <digits>:<secret>, as BotFather gives it)" };
|
|
100666
|
+
}
|
|
100667
|
+
const file = botTokenPath(dir);
|
|
100668
|
+
try {
|
|
100669
|
+
ensureRemoteDir(dir);
|
|
100670
|
+
writeOwnerOnlyFileAtomic(file, `${trimmed}
|
|
100671
|
+
`);
|
|
100672
|
+
} catch {
|
|
100673
|
+
return { ok: false, reason: `could not write the bot token file at ${file}` };
|
|
100674
|
+
}
|
|
100675
|
+
return { ok: true, value: true };
|
|
100676
|
+
}
|
|
100677
|
+
function removeFile2(file) {
|
|
100678
|
+
try {
|
|
100679
|
+
unlinkSync4(file);
|
|
100680
|
+
return true;
|
|
100681
|
+
} catch (error) {
|
|
100682
|
+
return error.code === "ENOENT";
|
|
100683
|
+
}
|
|
100684
|
+
}
|
|
100685
|
+
function removeBotToken(dir) {
|
|
100686
|
+
return removeFile2(botTokenPath(dir));
|
|
100687
|
+
}
|
|
100688
|
+
function removeRemoteConfig(dir) {
|
|
100689
|
+
return removeFile2(remoteConfigPath(dir));
|
|
100690
|
+
}
|
|
100691
|
+
var REMOTE_CONFIG_SCHEMA_VERSION = 1, DEFAULT_ORPHAN_MS, DEFAULT_RUN_TIMEOUT_MS, MAX_TIMEOUT_MS3, CONFIG_KEYS, TOKEN_SHAPE;
|
|
100692
|
+
var init_config11 = __esm(() => {
|
|
100693
|
+
init_config_dir();
|
|
100694
|
+
init_paths4();
|
|
100695
|
+
init_rendering_mode();
|
|
100696
|
+
DEFAULT_ORPHAN_MS = 10 * 60000;
|
|
100697
|
+
DEFAULT_RUN_TIMEOUT_MS = 30 * 60000;
|
|
100698
|
+
MAX_TIMEOUT_MS3 = 7 * 24 * 60 * 60000;
|
|
100699
|
+
CONFIG_KEYS = new Set(["schemaVersion", "chatId", "allowedUserIds", "orphanMs", "runTimeoutMs", "rendering"]);
|
|
100700
|
+
TOKEN_SHAPE = /^\d{5,}:[A-Za-z0-9_-]{20,}$/;
|
|
100701
|
+
});
|
|
100702
|
+
|
|
100703
|
+
// src/remote/rendering-config.ts
|
|
100704
|
+
function readRenderingSetting(dir) {
|
|
100705
|
+
const loaded = loadRemoteConfig(dir);
|
|
100706
|
+
if (!loaded.ok) {
|
|
100707
|
+
return { mode: DEFAULT_RENDER_MODE, source: "default", saveable: false };
|
|
100708
|
+
}
|
|
100709
|
+
const mode = loaded.value.rendering;
|
|
100710
|
+
return mode === undefined ? { mode: DEFAULT_RENDER_MODE, source: "default", saveable: true } : { mode, source: "config", saveable: true };
|
|
100711
|
+
}
|
|
100712
|
+
function writeRenderingMode(raw, dir) {
|
|
100713
|
+
const wanted = raw.trim().toLowerCase();
|
|
100714
|
+
if (!isRenderMode(wanted)) {
|
|
100715
|
+
return { ok: false, reason: `rendering must be ${RENDER_MODE_CHOICES}` };
|
|
100716
|
+
}
|
|
100717
|
+
const loaded = loadRemoteConfig(dir);
|
|
100718
|
+
if (!loaded.ok) {
|
|
100719
|
+
return { ok: false, reason: `remote control is not set up on this machine, so the mode cannot be saved yet (${loaded.reason})` };
|
|
100720
|
+
}
|
|
100721
|
+
const saved = saveRemoteConfig({ ...loaded.value, rendering: wanted }, dir);
|
|
100722
|
+
return saved.ok ? { ok: true, value: wanted } : saved;
|
|
100723
|
+
}
|
|
100724
|
+
var init_rendering_config = __esm(() => {
|
|
100725
|
+
init_config11();
|
|
100726
|
+
init_rendering_mode();
|
|
100727
|
+
});
|
|
100728
|
+
|
|
100729
|
+
// src/tui/rendering-command.ts
|
|
100730
|
+
function isRenderingCommand(line) {
|
|
100731
|
+
const token = line.trim().split(/\s+/)[0] ?? "";
|
|
100732
|
+
return token === RENDERING_COMMAND;
|
|
100733
|
+
}
|
|
100734
|
+
function runRenderingCommand(argText, dir) {
|
|
100735
|
+
const arg = argText.trim();
|
|
100736
|
+
try {
|
|
100737
|
+
if (arg.length > 0) {
|
|
100738
|
+
const saved = writeRenderingMode(arg, dir);
|
|
100739
|
+
return saved.ok ? `Telegram rendering: ${saved.value} (${RENDERING_MEANING[saved.value]}). Saved; it applies to the next message.
|
|
100740
|
+
` : `${RENDERING_COMMAND}: ${saved.reason}
|
|
100741
|
+
`;
|
|
100742
|
+
}
|
|
100743
|
+
const current = readRenderingSetting(dir);
|
|
100744
|
+
const where = current.source === "config" ? "saved in the remote config" : current.saveable ? `default (${DEFAULT_RENDER_MODE})` : "default, remote control is not set up yet";
|
|
100745
|
+
return [
|
|
100746
|
+
`Telegram rendering: ${current.mode} (${where})`,
|
|
100747
|
+
...RENDER_MODES.map((mode) => ` ${mode === current.mode ? "*" : " "} ${mode}: ${RENDERING_MEANING[mode]}`),
|
|
100748
|
+
RENDERING_FALLBACK_NOTE,
|
|
100749
|
+
`Usage: ${RENDERING_COMMAND} [${RENDER_MODES.join("|")}]`,
|
|
100750
|
+
""
|
|
100751
|
+
].join(`
|
|
100752
|
+
`);
|
|
100753
|
+
} catch (error) {
|
|
100754
|
+
return `${RENDERING_COMMAND}: ${error instanceof Error ? error.message : String(error)}
|
|
100755
|
+
`;
|
|
100756
|
+
}
|
|
100757
|
+
}
|
|
100758
|
+
var RENDERING_COMMAND = "/rendering", RENDERING_SUMMARY, RENDERING_MEANING, RENDERING_FALLBACK_NOTE = "Whatever the mode, a refused message is resent once one step down: rich, then HTML, then plain text.";
|
|
100759
|
+
var init_rendering_command = __esm(() => {
|
|
100760
|
+
init_rendering_config();
|
|
100761
|
+
init_rendering_mode();
|
|
100762
|
+
RENDERING_SUMMARY = `How Telegram replies are written: ${RENDERING_COMMAND} [${RENDER_MODES.join("|")}]`;
|
|
100763
|
+
RENDERING_MEANING = {
|
|
100764
|
+
auto: "a reply with a table goes as a rich message, any other reply as HTML",
|
|
100765
|
+
rich: "every reply goes as a rich message",
|
|
100766
|
+
html: "every reply goes as Telegram HTML",
|
|
100767
|
+
plain: "every reply goes as plain text, a table as aligned lines"
|
|
100768
|
+
};
|
|
100769
|
+
});
|
|
100770
|
+
|
|
100464
100771
|
// src/tui/reasoning-display.ts
|
|
100465
100772
|
function parseThinkDisplayMode(raw) {
|
|
100466
100773
|
if (raw === undefined) {
|
|
@@ -100520,7 +100827,7 @@ var init_reasoning_display = __esm(() => {
|
|
|
100520
100827
|
});
|
|
100521
100828
|
|
|
100522
100829
|
// src/tui/debug-log.ts
|
|
100523
|
-
import
|
|
100830
|
+
import path229 from "path";
|
|
100524
100831
|
function isDebugEnabled() {
|
|
100525
100832
|
return activeRun !== undefined;
|
|
100526
100833
|
}
|
|
@@ -100548,11 +100855,11 @@ function startDebugRun(opts = {}) {
|
|
|
100548
100855
|
const dir = ensureKeryxSubdir(["debug", name], opts.configDir);
|
|
100549
100856
|
const run = {
|
|
100550
100857
|
dir,
|
|
100551
|
-
shellLog:
|
|
100552
|
-
watcherLog:
|
|
100858
|
+
shellLog: path229.join(dir, "shell.ndjson"),
|
|
100859
|
+
watcherLog: path229.join(dir, "watcher.ndjson")
|
|
100553
100860
|
};
|
|
100554
100861
|
try {
|
|
100555
|
-
writeOwnerOnlyFile(
|
|
100862
|
+
writeOwnerOnlyFile(path229.join(path229.dirname(dir), "latest.txt"), `${dir}
|
|
100556
100863
|
`);
|
|
100557
100864
|
} catch {}
|
|
100558
100865
|
activeRun = run;
|
|
@@ -100618,7 +100925,7 @@ var init_debug_log = __esm(() => {
|
|
|
100618
100925
|
|
|
100619
100926
|
// src/tui/theme.ts
|
|
100620
100927
|
import { existsSync as existsSync39 } from "fs";
|
|
100621
|
-
import
|
|
100928
|
+
import path230 from "path";
|
|
100622
100929
|
function hexChannels(hex) {
|
|
100623
100930
|
return [1, 3, 5].map((offset) => Number.parseInt(hex.slice(offset, offset + 2), 16));
|
|
100624
100931
|
}
|
|
@@ -100756,7 +101063,7 @@ function onThemeChange(listener) {
|
|
|
100756
101063
|
};
|
|
100757
101064
|
}
|
|
100758
101065
|
function tuiConfigPath(dir) {
|
|
100759
|
-
return
|
|
101066
|
+
return path230.join(keryxConfigDir(dir), "tui.json");
|
|
100760
101067
|
}
|
|
100761
101068
|
function loadPersistedThemeId(dir) {
|
|
100762
101069
|
try {
|
|
@@ -101152,6 +101459,17 @@ function buildSettingsRows(state) {
|
|
|
101152
101459
|
usage: "/external-agents [on|off]",
|
|
101153
101460
|
actions: toggleActions("/external-agents", state.externalAgents.on)
|
|
101154
101461
|
},
|
|
101462
|
+
{
|
|
101463
|
+
id: "rendering",
|
|
101464
|
+
group: "External",
|
|
101465
|
+
label: "Telegram rendering",
|
|
101466
|
+
value: state.rendering.mode,
|
|
101467
|
+
scope: "saved",
|
|
101468
|
+
...state.rendering.saveable ? {} : { detail: "remote control is not set up yet" },
|
|
101469
|
+
command: "/rendering",
|
|
101470
|
+
usage: `/rendering [${RENDER_MODES.join("|")}]`,
|
|
101471
|
+
actions: RENDER_MODES.map((mode) => action(mode, `/rendering ${mode}`, mode === state.rendering.mode))
|
|
101472
|
+
},
|
|
101155
101473
|
{
|
|
101156
101474
|
id: "jevprofile",
|
|
101157
101475
|
group: "External",
|
|
@@ -101190,6 +101508,7 @@ var SETTING_GROUPS, onOff = (value) => value ? "on" : "off", EXTERNAL_PRIVACY_DE
|
|
|
101190
101508
|
var init_settings_model = __esm(() => {
|
|
101191
101509
|
init_agent();
|
|
101192
101510
|
init_permission_mode();
|
|
101511
|
+
init_rendering_mode();
|
|
101193
101512
|
init_reasoning_display();
|
|
101194
101513
|
init_theme();
|
|
101195
101514
|
SETTING_GROUPS = ["Safety", "Routing", "Display", "External"];
|
|
@@ -101309,7 +101628,7 @@ var init_jev_profile = __esm(() => {
|
|
|
101309
101628
|
|
|
101310
101629
|
// src/commands/review-jev-profile.ts
|
|
101311
101630
|
import { readFile as readFile106 } from "fs/promises";
|
|
101312
|
-
import
|
|
101631
|
+
import path231 from "path";
|
|
101313
101632
|
function rejectUnknownJevProfileFlags(args) {
|
|
101314
101633
|
const unknown = args.filter((arg) => arg.startsWith("--") && !JEV_PROFILE_FLAGS.includes(arg.split("=")[0]));
|
|
101315
101634
|
if (unknown.length > 0) {
|
|
@@ -101318,7 +101637,7 @@ function rejectUnknownJevProfileFlags(args) {
|
|
|
101318
101637
|
}
|
|
101319
101638
|
async function readRawConfig(cwd) {
|
|
101320
101639
|
try {
|
|
101321
|
-
return JSON.parse(await readFile106(
|
|
101640
|
+
return JSON.parse(await readFile106(path231.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
|
|
101322
101641
|
} catch {
|
|
101323
101642
|
return;
|
|
101324
101643
|
}
|
|
@@ -101342,7 +101661,7 @@ async function runJevProfile(args) {
|
|
|
101342
101661
|
return;
|
|
101343
101662
|
}
|
|
101344
101663
|
const merged = mergeRecommendedJevProfile(raw);
|
|
101345
|
-
await writeFileAtomic(
|
|
101664
|
+
await writeFileAtomic(path231.join(cwd, REVIEW_GATE_CONFIG_PATH), `${JSON.stringify(merged, null, 2)}
|
|
101346
101665
|
`);
|
|
101347
101666
|
const current = currentJevProfile(merged);
|
|
101348
101667
|
if (args.includes("--json")) {
|
|
@@ -101357,13 +101676,13 @@ async function readJevProfileForShell(cwd) {
|
|
|
101357
101676
|
async function toggleJevProfileKeyForShell(cwd, key) {
|
|
101358
101677
|
const raw = await readRawConfig(cwd);
|
|
101359
101678
|
const merged = toggleJevProfileKey(raw, key);
|
|
101360
|
-
await writeFileAtomic(
|
|
101679
|
+
await writeFileAtomic(path231.join(cwd, REVIEW_GATE_CONFIG_PATH), `${JSON.stringify(merged, null, 2)}
|
|
101361
101680
|
`);
|
|
101362
101681
|
}
|
|
101363
101682
|
async function applyRecommendedJevProfileForShell(cwd) {
|
|
101364
101683
|
const raw = await readRawConfig(cwd);
|
|
101365
101684
|
const merged = mergeRecommendedJevProfile(raw);
|
|
101366
|
-
await writeFileAtomic(
|
|
101685
|
+
await writeFileAtomic(path231.join(cwd, REVIEW_GATE_CONFIG_PATH), `${JSON.stringify(merged, null, 2)}
|
|
101367
101686
|
`);
|
|
101368
101687
|
}
|
|
101369
101688
|
var JEV_PROFILE_FLAGS;
|
|
@@ -101385,14 +101704,14 @@ import {
|
|
|
101385
101704
|
openSync as openSync4,
|
|
101386
101705
|
realpathSync as realpathSync16,
|
|
101387
101706
|
renameSync as renameSync4,
|
|
101388
|
-
statSync as
|
|
101389
|
-
unlinkSync as
|
|
101707
|
+
statSync as statSync15,
|
|
101708
|
+
unlinkSync as unlinkSync5,
|
|
101390
101709
|
writeFileSync as writeFileSync7
|
|
101391
101710
|
} from "fs";
|
|
101392
|
-
import
|
|
101711
|
+
import path232 from "path";
|
|
101393
101712
|
import { randomUUID as randomUUID28 } from "crypto";
|
|
101394
101713
|
function projectRegistryPath(dir) {
|
|
101395
|
-
return
|
|
101714
|
+
return path232.join(keryxConfigDir(dir), "projects.json");
|
|
101396
101715
|
}
|
|
101397
101716
|
function withRegistryLock(dir, fn, onWaiting) {
|
|
101398
101717
|
return withFileLock2(`${projectRegistryPath(dir)}.lock`, fn, {
|
|
@@ -101408,7 +101727,7 @@ function validEntry(candidate) {
|
|
|
101408
101727
|
return typeof entry.path === "string" && entry.path.length > 0 && typeof entry.projectId === "string" && entry.projectId.length > 0 && typeof entry.displayName === "string" && (entry.state === "active" || entry.state === "missing") && typeof entry.registeredAt === "string";
|
|
101409
101728
|
}
|
|
101410
101729
|
function projectIdentity(projectPath) {
|
|
101411
|
-
const absolute =
|
|
101730
|
+
const absolute = path232.resolve(projectPath);
|
|
101412
101731
|
try {
|
|
101413
101732
|
return realpathSync16.native(absolute);
|
|
101414
101733
|
} catch {
|
|
@@ -101524,7 +101843,7 @@ function saveProjectRegistry(registry, dir, onWarn) {
|
|
|
101524
101843
|
return true;
|
|
101525
101844
|
} catch {
|
|
101526
101845
|
try {
|
|
101527
|
-
|
|
101846
|
+
unlinkSync5(temp);
|
|
101528
101847
|
} catch {}
|
|
101529
101848
|
return false;
|
|
101530
101849
|
}
|
|
@@ -101563,7 +101882,7 @@ function registerProject(projectPath, options = {}) {
|
|
|
101563
101882
|
const entry = {
|
|
101564
101883
|
projectId: randomUUID28(),
|
|
101565
101884
|
path: absolute,
|
|
101566
|
-
displayName: options.displayName ??
|
|
101885
|
+
displayName: options.displayName ?? path232.basename(absolute),
|
|
101567
101886
|
state: "active",
|
|
101568
101887
|
registeredAt: now,
|
|
101569
101888
|
lastSeenAt: now
|
|
@@ -101600,7 +101919,7 @@ function forgetProject(projectId, dir, onWarn) {
|
|
|
101600
101919
|
return outcome ?? "write-failed";
|
|
101601
101920
|
}
|
|
101602
101921
|
function isInitializedProject(projectPath) {
|
|
101603
|
-
return
|
|
101922
|
+
return statSync15(path232.join(projectPath, ".metaproject"), { throwIfNoEntry: false })?.isDirectory() === true;
|
|
101604
101923
|
}
|
|
101605
101924
|
function listProjects(dir, onWarn) {
|
|
101606
101925
|
const registry = loadProjectRegistry(dir, onWarn);
|
|
@@ -101654,9 +101973,9 @@ var init_project_registry = __esm(() => {
|
|
|
101654
101973
|
});
|
|
101655
101974
|
|
|
101656
101975
|
// src/lib/permission-mode-config.ts
|
|
101657
|
-
import
|
|
101976
|
+
import path233 from "path";
|
|
101658
101977
|
function permissionModeConfigPath(dir) {
|
|
101659
|
-
return
|
|
101978
|
+
return path233.join(keryxConfigDir(dir), "permission-mode.json");
|
|
101660
101979
|
}
|
|
101661
101980
|
function withRegistryLock2(dir, fn) {
|
|
101662
101981
|
return withFileLock2(`${permissionModeConfigPath(dir)}.lock`, fn, {
|
|
@@ -101763,10 +102082,10 @@ var init_jev_edit_guard = __esm(() => {
|
|
|
101763
102082
|
|
|
101764
102083
|
// src/review/jev-edit-guard-config.ts
|
|
101765
102084
|
import { readFile as readFile107 } from "fs/promises";
|
|
101766
|
-
import
|
|
102085
|
+
import path234 from "path";
|
|
101767
102086
|
async function readReviewJevBlock(cwd) {
|
|
101768
102087
|
try {
|
|
101769
|
-
const parsed = JSON.parse(await readFile107(
|
|
102088
|
+
const parsed = JSON.parse(await readFile107(path234.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
|
|
101770
102089
|
if (typeof parsed !== "object" || parsed === null)
|
|
101771
102090
|
return;
|
|
101772
102091
|
const review = parsed["review"];
|
|
@@ -101811,7 +102130,7 @@ async function readJevEditGuardConfig(cwd, opts) {
|
|
|
101811
102130
|
return { enabled, threshold, maxCalls };
|
|
101812
102131
|
}
|
|
101813
102132
|
async function writeJevEditGuardEnabled(cwd, enabled) {
|
|
101814
|
-
const file =
|
|
102133
|
+
const file = path234.join(cwd, REVIEW_GATE_CONFIG_PATH);
|
|
101815
102134
|
let root = {};
|
|
101816
102135
|
if (await pathExists(file)) {
|
|
101817
102136
|
try {
|
|
@@ -101845,6 +102164,14 @@ function readReasoning2(sessionOverride, globalEffort) {
|
|
|
101845
102164
|
const afterRestart = describeReasoningEffortSource({ globalEffort });
|
|
101846
102165
|
return resolved.source === "session" && afterRestart.source === "env" ? { ...resolved, envWinsOnRestart: true } : resolved;
|
|
101847
102166
|
}
|
|
102167
|
+
function readRendering(dir) {
|
|
102168
|
+
try {
|
|
102169
|
+
const { mode, saveable } = readRenderingSetting(dir);
|
|
102170
|
+
return { mode, saveable };
|
|
102171
|
+
} catch {
|
|
102172
|
+
return { mode: DEFAULT_RENDER_MODE, saveable: false };
|
|
102173
|
+
}
|
|
102174
|
+
}
|
|
101848
102175
|
async function orElse(read, fallback) {
|
|
101849
102176
|
try {
|
|
101850
102177
|
return await read();
|
|
@@ -101876,7 +102203,8 @@ async function loadSettingsSnapshot(cwd, live, configDir) {
|
|
|
101876
102203
|
on: RECOMMENDED_JEV_PROFILE.filter((entry) => jevProfile[entry.key] === true).length,
|
|
101877
102204
|
total: RECOMMENDED_JEV_PROFILE.length
|
|
101878
102205
|
},
|
|
101879
|
-
externalAgents: agents.ok ? { on: true } : { on: false, reason: agents.reason }
|
|
102206
|
+
externalAgents: agents.ok ? { on: true } : { on: false, reason: agents.reason },
|
|
102207
|
+
rendering: readRendering(configDir)
|
|
101880
102208
|
};
|
|
101881
102209
|
}
|
|
101882
102210
|
var init_settings_state = __esm(() => {
|
|
@@ -101886,6 +102214,8 @@ var init_settings_state = __esm(() => {
|
|
|
101886
102214
|
init_external_switch();
|
|
101887
102215
|
init_permission_mode_config();
|
|
101888
102216
|
init_shell_config();
|
|
102217
|
+
init_rendering_config();
|
|
102218
|
+
init_rendering_mode();
|
|
101889
102219
|
init_jev_edit_guard_config();
|
|
101890
102220
|
init_jev_profile();
|
|
101891
102221
|
init_reasoning_display();
|
|
@@ -101941,7 +102271,7 @@ class NextStepSuggestionGate {
|
|
|
101941
102271
|
var NEXT_STEP_SUGGESTION_MAX_LENGTH = 80, NEXT_STEP_SUGGESTION_MAX_WORDS = 20, NEXT_STEP_SUGGESTION_TIMEOUT_MS = 8000, NEXT_STEP_USER_TAIL = 800, NEXT_STEP_ASSISTANT_TAIL = 3000, NEXT_STEP_SYSTEM_PROMPT = "You are the next-step advisor of a coding assistant terminal. Based on the user's last request and the assistant's final reply, propose ONE short follow-up the user could do next: imperative, no quotes, no markdown, at most 80 characters. If nothing useful exists, reply with exactly one dot: .";
|
|
101942
102272
|
|
|
101943
102273
|
// src/lib/version-check.ts
|
|
101944
|
-
import
|
|
102274
|
+
import path235 from "path";
|
|
101945
102275
|
function formatVersionUpdateAdvisory(result) {
|
|
101946
102276
|
if (result.status !== "update-available")
|
|
101947
102277
|
return;
|
|
@@ -102128,7 +102458,7 @@ async function checkVersion(options) {
|
|
|
102128
102458
|
const now = options.now ?? Date.now;
|
|
102129
102459
|
const timestamp = now();
|
|
102130
102460
|
const configDir = ensureKeryxConfigDir(options.cacheDir);
|
|
102131
|
-
const cacheFile =
|
|
102461
|
+
const cacheFile = path235.join(configDir, "version-check.json");
|
|
102132
102462
|
const cache = parseCache(cacheFile);
|
|
102133
102463
|
if (cache?.latestVersion !== undefined && cache.successAt !== undefined && timestamp - cache.successAt >= 0 && timestamp - cache.successAt < SUCCESS_CACHE_TTL_MS) {
|
|
102134
102464
|
return resultFor(options.currentVersion, current, cache.latestVersion, "cache");
|
|
@@ -102456,7 +102786,7 @@ var init_schemas2 = __esm(() => {
|
|
|
102456
102786
|
var init_types7 = () => {};
|
|
102457
102787
|
|
|
102458
102788
|
// src/standard/profiles.ts
|
|
102459
|
-
import
|
|
102789
|
+
import path236 from "path";
|
|
102460
102790
|
import { readFile as readFile108 } from "fs/promises";
|
|
102461
102791
|
function namedEntrypoints(manifest) {
|
|
102462
102792
|
const agentEntrypoints = manifest.agentEntrypoints;
|
|
@@ -102494,7 +102824,7 @@ function enabledModuleKeys(manifest) {
|
|
|
102494
102824
|
}
|
|
102495
102825
|
async function rootEntrypointLinksIndex(cwd, manifest) {
|
|
102496
102826
|
for (const { path: name } of namedEntrypoints(manifest)) {
|
|
102497
|
-
const filePath =
|
|
102827
|
+
const filePath = path236.join(cwd, name);
|
|
102498
102828
|
if (!await pathExists(filePath)) {
|
|
102499
102829
|
continue;
|
|
102500
102830
|
}
|
|
@@ -102506,16 +102836,16 @@ async function rootEntrypointLinksIndex(cwd, manifest) {
|
|
|
102506
102836
|
return false;
|
|
102507
102837
|
}
|
|
102508
102838
|
async function minimalSatisfied(cwd) {
|
|
102509
|
-
const metaprojectRoot =
|
|
102839
|
+
const metaprojectRoot = path236.join(cwd, ".metaproject");
|
|
102510
102840
|
const requiredFiles = ["index.md", "README.md", "metaproject.json"];
|
|
102511
102841
|
const requiredDirs = ["modules", "rules", "skills", "data"];
|
|
102512
102842
|
for (const file of requiredFiles) {
|
|
102513
|
-
if (!await pathExists(
|
|
102843
|
+
if (!await pathExists(path236.join(metaprojectRoot, file))) {
|
|
102514
102844
|
return false;
|
|
102515
102845
|
}
|
|
102516
102846
|
}
|
|
102517
102847
|
for (const dir of requiredDirs) {
|
|
102518
|
-
if (!await pathExists(
|
|
102848
|
+
if (!await pathExists(path236.join(metaprojectRoot, dir))) {
|
|
102519
102849
|
return false;
|
|
102520
102850
|
}
|
|
102521
102851
|
}
|
|
@@ -102528,7 +102858,7 @@ async function ciSatisfied(cwd, manifest) {
|
|
|
102528
102858
|
return false;
|
|
102529
102859
|
}
|
|
102530
102860
|
for (const key of ciModules) {
|
|
102531
|
-
if (await pathExists(
|
|
102861
|
+
if (await pathExists(path236.join(cwd, ".metaproject", "data", key, "artifacts"))) {
|
|
102532
102862
|
return true;
|
|
102533
102863
|
}
|
|
102534
102864
|
}
|
|
@@ -102541,7 +102871,7 @@ async function evaluateProfiles(cwd, manifest) {
|
|
|
102541
102871
|
satisfied.push("minimal");
|
|
102542
102872
|
}
|
|
102543
102873
|
const hasAgentModule = AGENT_MODULES.some((key) => new Set(enabledModuleKeys(manifest)).has(key));
|
|
102544
|
-
const agent = minimal && hasAgentModule && await rootEntrypointLinksIndex(cwd, manifest) && await pathExists(
|
|
102874
|
+
const agent = minimal && hasAgentModule && await rootEntrypointLinksIndex(cwd, manifest) && await pathExists(path236.join(cwd, ".metaproject", "rules"));
|
|
102545
102875
|
if (agent) {
|
|
102546
102876
|
satisfied.push("agent");
|
|
102547
102877
|
}
|
|
@@ -102574,7 +102904,7 @@ var init_profiles2 = __esm(() => {
|
|
|
102574
102904
|
});
|
|
102575
102905
|
|
|
102576
102906
|
// src/standard/validate.ts
|
|
102577
|
-
import
|
|
102907
|
+
import path237 from "path";
|
|
102578
102908
|
import { readFile as readFile109 } from "fs/promises";
|
|
102579
102909
|
function isPlainObject14(value) {
|
|
102580
102910
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -102703,20 +103033,20 @@ function issue(code, message, fix) {
|
|
|
102703
103033
|
async function validateWorkspace2(cwd) {
|
|
102704
103034
|
const errors = [];
|
|
102705
103035
|
const warnings = [];
|
|
102706
|
-
const metaprojectRoot =
|
|
103036
|
+
const metaprojectRoot = path237.join(cwd, ".metaproject");
|
|
102707
103037
|
const requiredFiles = ["index.md", "README.md", "metaproject.json"];
|
|
102708
103038
|
for (const file of requiredFiles) {
|
|
102709
|
-
if (!await pathExists(
|
|
103039
|
+
if (!await pathExists(path237.join(metaprojectRoot, file))) {
|
|
102710
103040
|
errors.push(issue("missing-required-file", `Required file .metaproject/${file} is missing`, `Run \`keryx init\` to scaffold .metaproject/${file}.`));
|
|
102711
103041
|
}
|
|
102712
103042
|
}
|
|
102713
103043
|
const requiredDirs = ["modules", "rules", "skills", "data"];
|
|
102714
103044
|
for (const dir of requiredDirs) {
|
|
102715
|
-
if (!await pathExists(
|
|
103045
|
+
if (!await pathExists(path237.join(metaprojectRoot, dir))) {
|
|
102716
103046
|
errors.push(issue("missing-required-dir", `Required directory .metaproject/${dir}/ is missing`, `Run \`keryx init\` to create .metaproject/${dir}/.`));
|
|
102717
103047
|
}
|
|
102718
103048
|
}
|
|
102719
|
-
const manifestPath =
|
|
103049
|
+
const manifestPath = path237.join(metaprojectRoot, "metaproject.json");
|
|
102720
103050
|
if (!await pathExists(manifestPath)) {
|
|
102721
103051
|
return { ok: errors.length === 0, errors, warnings };
|
|
102722
103052
|
}
|
|
@@ -102748,7 +103078,7 @@ async function validateWorkspace2(cwd) {
|
|
|
102748
103078
|
if (typeof value !== "string") {
|
|
102749
103079
|
continue;
|
|
102750
103080
|
}
|
|
102751
|
-
if (!await pathExists(
|
|
103081
|
+
if (!await pathExists(path237.join(cwd, value))) {
|
|
102752
103082
|
errors.push(issue("missing-declared-path", `Declared path paths.${key} -> ${value} does not exist`, `Create ${value} or remove paths.${key} from metaproject.json.`));
|
|
102753
103083
|
}
|
|
102754
103084
|
}
|
|
@@ -102758,7 +103088,7 @@ async function validateWorkspace2(cwd) {
|
|
|
102758
103088
|
continue;
|
|
102759
103089
|
}
|
|
102760
103090
|
if (typeof entry.manifest === "string") {
|
|
102761
|
-
if (!await pathExists(
|
|
103091
|
+
if (!await pathExists(path237.join(cwd, entry.manifest))) {
|
|
102762
103092
|
errors.push(issue("missing-module-manifest", `Enabled module "${key}" manifest ${entry.manifest} is missing`, `Create ${entry.manifest} or run \`keryx update\` to regenerate module manifests.`));
|
|
102763
103093
|
}
|
|
102764
103094
|
}
|
|
@@ -102767,14 +103097,14 @@ async function validateWorkspace2(cwd) {
|
|
|
102767
103097
|
if (typeof value !== "string") {
|
|
102768
103098
|
continue;
|
|
102769
103099
|
}
|
|
102770
|
-
if (!await pathExists(
|
|
103100
|
+
if (!await pathExists(path237.join(cwd, value))) {
|
|
102771
103101
|
const sink = field === "data" ? warnings : errors;
|
|
102772
103102
|
sink.push(issue("missing-module-path", `Module "${key}" declares ${String(field)} ${value}, which does not exist`, field === "data" ? `Run the module (e.g. \`keryx ${key} ...\`) to generate ${value}, or update modules.${key}.data in metaproject.json.` : `Create ${value} or update modules.${key}.${String(field)} in metaproject.json.`));
|
|
102773
103103
|
}
|
|
102774
103104
|
}
|
|
102775
103105
|
}
|
|
102776
103106
|
for (const { path: name, scope } of namedEntrypoints(manifest)) {
|
|
102777
|
-
const filePath =
|
|
103107
|
+
const filePath = path237.join(cwd, name);
|
|
102778
103108
|
if (!await pathExists(filePath)) {
|
|
102779
103109
|
continue;
|
|
102780
103110
|
}
|
|
@@ -103439,6 +103769,12 @@ var init_help_groups = __esm(() => {
|
|
|
103439
103769
|
group: "Automation",
|
|
103440
103770
|
summary: "Connect, test or disconnect Telegram for this machine: bot token, one-time code, group. [status]."
|
|
103441
103771
|
},
|
|
103772
|
+
{
|
|
103773
|
+
kind: "slash",
|
|
103774
|
+
name: "/rendering",
|
|
103775
|
+
group: "Automation",
|
|
103776
|
+
summary: "How Telegram replies are written: tables and lists as rich messages or HTML, with a fallback. [auto|rich|html|plain]."
|
|
103777
|
+
},
|
|
103442
103778
|
{
|
|
103443
103779
|
kind: "cli",
|
|
103444
103780
|
name: "acp",
|
|
@@ -103631,6 +103967,12 @@ var init_help_groups = __esm(() => {
|
|
|
103631
103967
|
group: "Maintenance and diagnostics",
|
|
103632
103968
|
summary: "Bound stores that grow without bound (gdctx raw/artifacts, owner write-conflict sidecars)."
|
|
103633
103969
|
},
|
|
103970
|
+
{
|
|
103971
|
+
kind: "cli",
|
|
103972
|
+
name: "remote",
|
|
103973
|
+
group: "Automation",
|
|
103974
|
+
summary: "Preview how Telegram replies are rendered (tables, lists, rules) in every mode, with no network: format-sample."
|
|
103975
|
+
},
|
|
103634
103976
|
{
|
|
103635
103977
|
kind: "cli",
|
|
103636
103978
|
name: "forgetting",
|
|
@@ -103647,7 +103989,7 @@ var init_help_groups = __esm(() => {
|
|
|
103647
103989
|
});
|
|
103648
103990
|
|
|
103649
103991
|
// src/standard/service.ts
|
|
103650
|
-
import
|
|
103992
|
+
import path238 from "path";
|
|
103651
103993
|
import { readFile as readFile110 } from "fs/promises";
|
|
103652
103994
|
async function runValidate(cwd) {
|
|
103653
103995
|
return validateWorkspace2(cwd);
|
|
@@ -103656,7 +103998,7 @@ async function runDoctor2(cwd) {
|
|
|
103656
103998
|
return validateWorkspace2(cwd);
|
|
103657
103999
|
}
|
|
103658
104000
|
async function readManifest2(cwd) {
|
|
103659
|
-
const manifestPath =
|
|
104001
|
+
const manifestPath = path238.join(cwd, ".metaproject", "metaproject.json");
|
|
103660
104002
|
if (!await pathExists(manifestPath)) {
|
|
103661
104003
|
throw new Error("Metaproject is not initialized (missing .metaproject/metaproject.json). Run `keryx init` first.");
|
|
103662
104004
|
}
|
|
@@ -103683,7 +104025,7 @@ var init_service14 = __esm(() => {
|
|
|
103683
104025
|
|
|
103684
104026
|
// src/rules/entrypoint-inspection.ts
|
|
103685
104027
|
import { readFile as readFile111 } from "fs/promises";
|
|
103686
|
-
import
|
|
104028
|
+
import path239 from "path";
|
|
103687
104029
|
async function inspectEntrypoints(projectRoot, agentEntrypoints, options = {}) {
|
|
103688
104030
|
const git = await resolveGitCommonDir(projectRoot) !== undefined;
|
|
103689
104031
|
const resolved = await resolveProjectEntrypoints(projectRoot, agentEntrypoints);
|
|
@@ -103768,14 +104110,14 @@ async function inspectEntrypoints(projectRoot, agentEntrypoints, options = {}) {
|
|
|
103768
104110
|
codex = { entry, mode: "skip" };
|
|
103769
104111
|
continue;
|
|
103770
104112
|
}
|
|
103771
|
-
const sourceExists = await pathExists(
|
|
104113
|
+
const sourceExists = await pathExists(path239.join(projectRoot, entry.source));
|
|
103772
104114
|
const state = await codexOverrideState(projectRoot, entry);
|
|
103773
104115
|
const bytes = await codexOverrideByteSize(projectRoot, entry);
|
|
103774
104116
|
codex = { entry, mode: "override", sourceExists, state, ...bytes === undefined ? {} : { bytes } };
|
|
103775
104117
|
localTargets.push({ path: entry.path, kind: "codex", exists: state !== "missing", expected: sourceExists });
|
|
103776
104118
|
}
|
|
103777
104119
|
if (targets.claudeSettings.scope === "local") {
|
|
103778
|
-
const exists = await pathExists(
|
|
104120
|
+
const exists = await pathExists(path239.join(projectRoot, ...hooksTarget.split("/")));
|
|
103779
104121
|
localTargets.push({ path: hooksTarget, kind: "claudeSettings", exists, expected: options.hooksExpected === true });
|
|
103780
104122
|
}
|
|
103781
104123
|
const existing = localTargets.filter((target) => target.exists);
|
|
@@ -103904,14 +104246,14 @@ function codexPreviewLines(codex) {
|
|
|
103904
104246
|
}
|
|
103905
104247
|
async function readText3(projectRoot, relativePath) {
|
|
103906
104248
|
try {
|
|
103907
|
-
return await readFile111(
|
|
104249
|
+
return await readFile111(path239.join(projectRoot, ...relativePath.split("/")), "utf8");
|
|
103908
104250
|
} catch {
|
|
103909
104251
|
return;
|
|
103910
104252
|
}
|
|
103911
104253
|
}
|
|
103912
104254
|
function displayPath2(projectRoot, target) {
|
|
103913
|
-
const relative =
|
|
103914
|
-
return relative.startsWith("..") ||
|
|
104255
|
+
const relative = path239.relative(projectRoot, target);
|
|
104256
|
+
return relative.startsWith("..") || path239.isAbsolute(relative) ? target : relative.split(path239.sep).join("/");
|
|
103915
104257
|
}
|
|
103916
104258
|
function isRecord10(value) {
|
|
103917
104259
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -103935,7 +104277,7 @@ var init_entrypoint_inspection = __esm(() => {
|
|
|
103935
104277
|
import { readdir as readdir35, readFile as readFile112 } from "fs/promises";
|
|
103936
104278
|
import { existsSync as existsSync41 } from "fs";
|
|
103937
104279
|
import { homedir as homedir10, tmpdir as tmpdir10 } from "os";
|
|
103938
|
-
import
|
|
104280
|
+
import path240 from "path";
|
|
103939
104281
|
async function registeredWorktrees(cwd) {
|
|
103940
104282
|
if (!Bun.which("git"))
|
|
103941
104283
|
return new Set;
|
|
@@ -103948,7 +104290,7 @@ async function registeredWorktrees(cwd) {
|
|
|
103948
104290
|
for (const line of stdout.split(`
|
|
103949
104291
|
`)) {
|
|
103950
104292
|
if (line.startsWith("worktree ")) {
|
|
103951
|
-
paths.add(
|
|
104293
|
+
paths.add(path240.resolve(line.slice("worktree ".length).trim()));
|
|
103952
104294
|
}
|
|
103953
104295
|
}
|
|
103954
104296
|
return paths;
|
|
@@ -103958,7 +104300,7 @@ async function registeredWorktrees(cwd) {
|
|
|
103958
104300
|
}
|
|
103959
104301
|
async function checkWorktrees(cwd) {
|
|
103960
104302
|
const mainRoot = await resolveMainCheckoutRoot(cwd) ?? cwd;
|
|
103961
|
-
const worktreesDir =
|
|
104303
|
+
const worktreesDir = path240.join(mainRoot, ".claude", "worktrees");
|
|
103962
104304
|
let entries;
|
|
103963
104305
|
try {
|
|
103964
104306
|
entries = (await readdir35(worktreesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
@@ -103970,8 +104312,8 @@ async function checkWorktrees(cwd) {
|
|
|
103970
104312
|
}
|
|
103971
104313
|
const registered = await registeredWorktrees(cwd);
|
|
103972
104314
|
const stale = entries.filter((name) => {
|
|
103973
|
-
const full =
|
|
103974
|
-
return !existsSync41(
|
|
104315
|
+
const full = path240.resolve(worktreesDir, name);
|
|
104316
|
+
return !existsSync41(path240.join(full, ".git")) || !registered.has(full);
|
|
103975
104317
|
});
|
|
103976
104318
|
if (stale.length === 0) {
|
|
103977
104319
|
return {
|
|
@@ -104137,7 +104479,7 @@ async function checkEntrypoints(cwd) {
|
|
|
104137
104479
|
const projectRoot = resolveProjectRoot2(cwd);
|
|
104138
104480
|
let manifest;
|
|
104139
104481
|
try {
|
|
104140
|
-
manifest = JSON.parse(await readFile112(
|
|
104482
|
+
manifest = JSON.parse(await readFile112(path240.join(projectRoot, ".metaproject", "metaproject.json"), "utf8"));
|
|
104141
104483
|
} catch {
|
|
104142
104484
|
manifest = undefined;
|
|
104143
104485
|
}
|
|
@@ -104292,22 +104634,22 @@ async function checkWikiFreshness(cwd) {
|
|
|
104292
104634
|
};
|
|
104293
104635
|
}
|
|
104294
104636
|
function isExcludedProjectRootCandidate(dir) {
|
|
104295
|
-
return dir ===
|
|
104637
|
+
return dir === path240.parse(dir).root || dir === "/tmp" || dir === tmpdir10();
|
|
104296
104638
|
}
|
|
104297
104639
|
function findMetaprojectUpward(cwd, ceiling) {
|
|
104298
|
-
let current =
|
|
104299
|
-
const resolvedCeiling =
|
|
104640
|
+
let current = path240.resolve(cwd);
|
|
104641
|
+
const resolvedCeiling = path240.resolve(ceiling);
|
|
104300
104642
|
for (;; ) {
|
|
104301
104643
|
if (isExcludedProjectRootCandidate(current)) {
|
|
104302
104644
|
return;
|
|
104303
104645
|
}
|
|
104304
|
-
if (existsSync41(
|
|
104646
|
+
if (existsSync41(path240.join(current, ".metaproject"))) {
|
|
104305
104647
|
return current;
|
|
104306
104648
|
}
|
|
104307
104649
|
if (current === resolvedCeiling) {
|
|
104308
104650
|
return;
|
|
104309
104651
|
}
|
|
104310
|
-
const parent =
|
|
104652
|
+
const parent = path240.dirname(current);
|
|
104311
104653
|
if (parent === current) {
|
|
104312
104654
|
return;
|
|
104313
104655
|
}
|
|
@@ -104321,7 +104663,7 @@ async function isKeryxProject(cwd, overrides = {}) {
|
|
|
104321
104663
|
return true;
|
|
104322
104664
|
}
|
|
104323
104665
|
const mainRoot = await resolveMainCheckoutRoot(cwd);
|
|
104324
|
-
return mainRoot !== undefined && existsSync41(
|
|
104666
|
+
return mainRoot !== undefined && existsSync41(path240.join(mainRoot, ".metaproject"));
|
|
104325
104667
|
}
|
|
104326
104668
|
async function buildDoctorReport(cwd, env = process.env, overrides = {}) {
|
|
104327
104669
|
const globalChecks = await Promise.all([
|
|
@@ -104409,7 +104751,7 @@ var init_doctor2 = __esm(() => {
|
|
|
104409
104751
|
});
|
|
104410
104752
|
|
|
104411
104753
|
// src/commands/goal-command.ts
|
|
104412
|
-
import
|
|
104754
|
+
import path241 from "path";
|
|
104413
104755
|
import { readFile as readFile113 } from "fs/promises";
|
|
104414
104756
|
function parseGoalArgs(rest) {
|
|
104415
104757
|
const trimmed = rest.trim();
|
|
@@ -104482,7 +104824,7 @@ function getFlowService() {
|
|
|
104482
104824
|
async function autoProvisionFlow(cwd, goalText) {
|
|
104483
104825
|
const service = getFlowService();
|
|
104484
104826
|
const result = await service.init({ cwd, title: goalText });
|
|
104485
|
-
const acFile =
|
|
104827
|
+
const acFile = path241.join(cwd, result.dir, "acceptance-criteria.md");
|
|
104486
104828
|
await writeFileAtomic(acFile, [
|
|
104487
104829
|
"# Acceptance Criteria",
|
|
104488
104830
|
"",
|
|
@@ -107520,7 +107862,7 @@ __export(exports_flow, {
|
|
|
107520
107862
|
signerIdentityArgs: () => signerIdentityArgs
|
|
107521
107863
|
});
|
|
107522
107864
|
import { randomInt } from "crypto";
|
|
107523
|
-
import
|
|
107865
|
+
import path242 from "path";
|
|
107524
107866
|
function parseTaskKind(raw) {
|
|
107525
107867
|
if (raw === undefined) {
|
|
107526
107868
|
return;
|
|
@@ -108435,7 +108777,7 @@ async function runSchema(args) {
|
|
|
108435
108777
|
`;
|
|
108436
108778
|
const out = optionValue(args, "--out");
|
|
108437
108779
|
if (out) {
|
|
108438
|
-
const target =
|
|
108780
|
+
const target = path242.isAbsolute(out) ? out : path242.join(process.cwd(), out);
|
|
108439
108781
|
await writeFileAtomic(target, json);
|
|
108440
108782
|
console.log(` ${style.green(symbols.ok)} Wrote flow-state schema ${style.cyan(symbols.arrow)} ${out}`);
|
|
108441
108783
|
return;
|
|
@@ -108727,7 +109069,7 @@ var init_lifecycle_flag = __esm(() => {
|
|
|
108727
109069
|
// src/sac/catch-up.ts
|
|
108728
109070
|
import { randomUUID as randomUUID30 } from "crypto";
|
|
108729
109071
|
import { readdir as readdir36, stat as stat24 } from "fs/promises";
|
|
108730
|
-
import
|
|
109072
|
+
import path243 from "path";
|
|
108731
109073
|
async function buildCatchUp(input) {
|
|
108732
109074
|
const [proposals, sessionCategories, lifecycleFlagsAll, unreviewedPathsAll] = await Promise.all([
|
|
108733
109075
|
collectProposals(input.cwd, input.workspaceId),
|
|
@@ -108846,7 +109188,7 @@ async function readExternalUnboundCandidates(cwd) {
|
|
|
108846
109188
|
}
|
|
108847
109189
|
return groups;
|
|
108848
109190
|
}, []));
|
|
108849
|
-
const metaPath =
|
|
109191
|
+
const metaPath = path243.join(extDir, `${id}.json`);
|
|
108850
109192
|
if (await pathExists(unboundDismissedReceiptPath(metaPath)))
|
|
108851
109193
|
continue;
|
|
108852
109194
|
candidates.push({
|
|
@@ -108860,7 +109202,7 @@ async function readExternalUnboundCandidates(cwd) {
|
|
|
108860
109202
|
return candidates;
|
|
108861
109203
|
}
|
|
108862
109204
|
async function readNewestUnboundCandidateForExternal(cwd, externalSessionId) {
|
|
108863
|
-
const evidenceDir =
|
|
109205
|
+
const evidenceDir = path243.join(externalSlatesDir(cwd), externalSessionId);
|
|
108864
109206
|
let entries;
|
|
108865
109207
|
try {
|
|
108866
109208
|
entries = (await readdir36(evidenceDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
|
|
@@ -108869,7 +109211,7 @@ async function readNewestUnboundCandidateForExternal(cwd, externalSessionId) {
|
|
|
108869
109211
|
}
|
|
108870
109212
|
entries.sort();
|
|
108871
109213
|
for (let i = entries.length - 1;i >= 0; i--) {
|
|
108872
|
-
const evidencePath =
|
|
109214
|
+
const evidencePath = path243.join(evidenceDir, entries[i]);
|
|
108873
109215
|
if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
|
|
108874
109216
|
continue;
|
|
108875
109217
|
const result = readConfigFile(evidencePath);
|
|
@@ -108910,7 +109252,7 @@ async function collectSessionCategories(cwd) {
|
|
|
108910
109252
|
}
|
|
108911
109253
|
async function collectReceiptTargets(cwd, owner) {
|
|
108912
109254
|
const targets = new Set;
|
|
108913
|
-
const workspacesDir =
|
|
109255
|
+
const workspacesDir = path243.join(cwd, ".metaproject", "workspaces");
|
|
108914
109256
|
let workspaceIds;
|
|
108915
109257
|
try {
|
|
108916
109258
|
workspaceIds = (await readdir36(workspacesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
@@ -108918,7 +109260,7 @@ async function collectReceiptTargets(cwd, owner) {
|
|
|
108918
109260
|
return targets;
|
|
108919
109261
|
}
|
|
108920
109262
|
for (const workspaceId of workspaceIds) {
|
|
108921
|
-
const receiptsDir =
|
|
109263
|
+
const receiptsDir = path243.join(workspacesDir, workspaceId, `${owner}-write-receipts`);
|
|
108922
109264
|
let files;
|
|
108923
109265
|
try {
|
|
108924
109266
|
files = (await readdir36(receiptsDir)).filter((name) => name.endsWith(".json"));
|
|
@@ -108926,7 +109268,7 @@ async function collectReceiptTargets(cwd, owner) {
|
|
|
108926
109268
|
continue;
|
|
108927
109269
|
}
|
|
108928
109270
|
for (const file of files) {
|
|
108929
|
-
const result = readConfigFile(
|
|
109271
|
+
const result = readConfigFile(path243.join(receiptsDir, file));
|
|
108930
109272
|
if (!result.ok)
|
|
108931
109273
|
continue;
|
|
108932
109274
|
try {
|
|
@@ -108969,7 +109311,7 @@ async function findSkillFiles(dir) {
|
|
|
108969
109311
|
}
|
|
108970
109312
|
const found = [];
|
|
108971
109313
|
for (const entry of entries) {
|
|
108972
|
-
const full =
|
|
109314
|
+
const full = path243.join(dir, entry.name);
|
|
108973
109315
|
if (entry.isDirectory()) {
|
|
108974
109316
|
found.push(...await findSkillFiles(full));
|
|
108975
109317
|
} else if (entry.isFile() && entry.name === "SKILL.md") {
|
|
@@ -109028,10 +109370,10 @@ async function detectUnreviewedSacPathChanges(cwd, sessions) {
|
|
|
109028
109370
|
changedAt: attribution.changedAt
|
|
109029
109371
|
});
|
|
109030
109372
|
}
|
|
109031
|
-
const sacSkillsDir =
|
|
109373
|
+
const sacSkillsDir = path243.join(cwd, ".metaproject", "project-skills", "sac");
|
|
109032
109374
|
const skillFiles = await findSkillFiles(sacSkillsDir);
|
|
109033
109375
|
for (const absolutePath of skillFiles) {
|
|
109034
|
-
const targetRef =
|
|
109376
|
+
const targetRef = path243.relative(path243.join(cwd, ".metaproject"), absolutePath).split(path243.sep).join("/");
|
|
109035
109377
|
if (skillReceipts.has(targetRef))
|
|
109036
109378
|
continue;
|
|
109037
109379
|
const attribution = await attributeToSession(absolutePath, scoped);
|
|
@@ -109049,12 +109391,12 @@ async function detectUnreviewedSacPathChanges(cwd, sessions) {
|
|
|
109049
109391
|
return items;
|
|
109050
109392
|
}
|
|
109051
109393
|
async function isSlateEngaged(dir) {
|
|
109052
|
-
if (await pathExists(
|
|
109394
|
+
if (await pathExists(path243.join(dir, "slate.json")))
|
|
109053
109395
|
return true;
|
|
109054
|
-
if (await pathExists(
|
|
109396
|
+
if (await pathExists(path243.join(dir, "terminal-state.json")))
|
|
109055
109397
|
return true;
|
|
109056
109398
|
try {
|
|
109057
|
-
const entries = await readdir36(
|
|
109399
|
+
const entries = await readdir36(path243.join(dir, "slate-archive"));
|
|
109058
109400
|
return entries.length > 0;
|
|
109059
109401
|
} catch {
|
|
109060
109402
|
return false;
|
|
@@ -109079,7 +109421,7 @@ async function safeReadSlate(dir) {
|
|
|
109079
109421
|
}
|
|
109080
109422
|
}
|
|
109081
109423
|
async function readTerminalState(dir) {
|
|
109082
|
-
const result = readConfigFile(
|
|
109424
|
+
const result = readConfigFile(path243.join(dir, "terminal-state.json"));
|
|
109083
109425
|
if (!result.ok) {
|
|
109084
109426
|
return;
|
|
109085
109427
|
}
|
|
@@ -109103,7 +109445,7 @@ async function resolveUnboundCandidateTarget(cwd, target) {
|
|
|
109103
109445
|
if (session === undefined) {
|
|
109104
109446
|
throw new Error(`No session or evidence path matches "${target}"`);
|
|
109105
109447
|
}
|
|
109106
|
-
const archiveDir =
|
|
109448
|
+
const archiveDir = path243.join(sessionDir(session.projectPath, session.id), "slate-archive");
|
|
109107
109449
|
let entries;
|
|
109108
109450
|
try {
|
|
109109
109451
|
entries = (await readdir36(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
|
|
@@ -109113,7 +109455,7 @@ async function resolveUnboundCandidateTarget(cwd, target) {
|
|
|
109113
109455
|
if (entries.length === 0)
|
|
109114
109456
|
throw new Error(`No unbound-candidate artifacts for session ${session.id}`);
|
|
109115
109457
|
entries.sort();
|
|
109116
|
-
return
|
|
109458
|
+
return path243.join(archiveDir, entries[entries.length - 1]);
|
|
109117
109459
|
}
|
|
109118
109460
|
async function dismissUnboundByTarget(cwd, target, reason) {
|
|
109119
109461
|
const evidencePath = await resolveUnboundCandidateTarget(cwd, target);
|
|
@@ -109139,7 +109481,7 @@ async function dismissUnboundCandidate(candidatePath, reason) {
|
|
|
109139
109481
|
return { removed: candidatePath, receipt: receiptPath };
|
|
109140
109482
|
}
|
|
109141
109483
|
async function readNewestUnboundCandidate(dir) {
|
|
109142
|
-
const archiveDir =
|
|
109484
|
+
const archiveDir = path243.join(dir, "slate-archive");
|
|
109143
109485
|
let entries;
|
|
109144
109486
|
try {
|
|
109145
109487
|
entries = (await readdir36(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
|
|
@@ -109148,7 +109490,7 @@ async function readNewestUnboundCandidate(dir) {
|
|
|
109148
109490
|
}
|
|
109149
109491
|
entries.sort();
|
|
109150
109492
|
for (let i = entries.length - 1;i >= 0; i--) {
|
|
109151
|
-
const evidencePath =
|
|
109493
|
+
const evidencePath = path243.join(archiveDir, entries[i]);
|
|
109152
109494
|
if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
|
|
109153
109495
|
continue;
|
|
109154
109496
|
const result = readConfigFile(evidencePath);
|
|
@@ -109178,7 +109520,7 @@ function isFailureOutcome2(g) {
|
|
|
109178
109520
|
return g.outcome === "error" || g.outcome === "no_credential" || g.outcome === "conflict";
|
|
109179
109521
|
}
|
|
109180
109522
|
async function readNewestWrapUpOutcome(dir) {
|
|
109181
|
-
const archiveDir =
|
|
109523
|
+
const archiveDir = path243.join(dir, "slate-archive");
|
|
109182
109524
|
let entries;
|
|
109183
109525
|
try {
|
|
109184
109526
|
entries = (await readdir36(archiveDir)).filter((name) => name.endsWith("-wrap-up-outcome.json"));
|
|
@@ -109187,7 +109529,7 @@ async function readNewestWrapUpOutcome(dir) {
|
|
|
109187
109529
|
}
|
|
109188
109530
|
entries.sort();
|
|
109189
109531
|
for (let i = entries.length - 1;i >= 0; i--) {
|
|
109190
|
-
const evidencePath =
|
|
109532
|
+
const evidencePath = path243.join(archiveDir, entries[i]);
|
|
109191
109533
|
const result = readConfigFile(evidencePath);
|
|
109192
109534
|
if (!result.ok) {
|
|
109193
109535
|
continue;
|
|
@@ -109937,12 +110279,12 @@ var init_extract3 = __esm(() => {
|
|
|
109937
110279
|
// src/product/corpus.ts
|
|
109938
110280
|
import { createHash as createHash62 } from "crypto";
|
|
109939
110281
|
import { readdir as readdir37, readFile as readFile115 } from "fs/promises";
|
|
109940
|
-
import
|
|
110282
|
+
import path244 from "path";
|
|
109941
110283
|
function flowsDir(cwd) {
|
|
109942
|
-
return
|
|
110284
|
+
return path244.join(cwd, ".metaproject", "flows");
|
|
109943
110285
|
}
|
|
109944
110286
|
function requirementsDir(cwd) {
|
|
109945
|
-
return
|
|
110287
|
+
return path244.join(cwd, "docs", "requirements");
|
|
109946
110288
|
}
|
|
109947
110289
|
async function readOptional(file) {
|
|
109948
110290
|
try {
|
|
@@ -109995,23 +110337,23 @@ async function corpusFingerprint(cwd) {
|
|
|
109995
110337
|
for (const name of await listFlowPackages(cwd)) {
|
|
109996
110338
|
lines.push(`package\x00flow\x00${name}`);
|
|
109997
110339
|
for (const file of FLOW_SOURCE_FILES) {
|
|
109998
|
-
const content = await readBytes(
|
|
110340
|
+
const content = await readBytes(path244.join(flowsDir(cwd), name, file));
|
|
109999
110341
|
lines.push(`file\x00${FLOW_PREFIX}${name}/${file}\x00${digestOf4(content)}`);
|
|
110000
110342
|
}
|
|
110001
110343
|
}
|
|
110002
110344
|
for (const name of await requirementPackages(cwd)) {
|
|
110003
110345
|
lines.push(`package\x00docpack\x00${name}`);
|
|
110004
|
-
const dir =
|
|
110346
|
+
const dir = path244.join(requirementsDir(cwd), name);
|
|
110005
110347
|
for (const candidate of PRIMARY_DOCS) {
|
|
110006
|
-
const content = await readBytes(
|
|
110348
|
+
const content = await readBytes(path244.join(dir, candidate));
|
|
110007
110349
|
if (content === null)
|
|
110008
110350
|
continue;
|
|
110009
110351
|
lines.push(`file\x00docs/requirements/${name}/${candidate}\x00${digestOf4(content)}`);
|
|
110010
110352
|
break;
|
|
110011
110353
|
}
|
|
110012
|
-
const readme = await readBytes(
|
|
110354
|
+
const readme = await readBytes(path244.join(dir, "README.md"));
|
|
110013
110355
|
lines.push(`file\x00docs/requirements/${name}/README.md\x00${digestOf4(readme)}`);
|
|
110014
|
-
const specification = await readBytes(
|
|
110356
|
+
const specification = await readBytes(path244.join(dir, "specification.md"));
|
|
110015
110357
|
lines.push(`file\x00docs/requirements/${name}/specification.md\x00${digestOf4(specification)}`);
|
|
110016
110358
|
}
|
|
110017
110359
|
return createHash62("sha256").update([...lines].sort().join(`
|
|
@@ -110053,19 +110395,19 @@ async function buildIntentIndex(cwd) {
|
|
|
110053
110395
|
const intents = [];
|
|
110054
110396
|
const failures = [];
|
|
110055
110397
|
for (const name of await listFlowPackages(cwd)) {
|
|
110056
|
-
const dir =
|
|
110398
|
+
const dir = path244.join(flowsDir(cwd), name);
|
|
110057
110399
|
const repoPath = `${FLOW_PREFIX}${name}`;
|
|
110058
110400
|
try {
|
|
110059
|
-
const flowJson = await readSource(
|
|
110401
|
+
const flowJson = await readSource(path244.join(dir, "flow.json"), `${repoPath}/flow.json`);
|
|
110060
110402
|
if (flowJson === null) {
|
|
110061
110403
|
failures.push(`${repoPath}: no flow.json`);
|
|
110062
110404
|
continue;
|
|
110063
110405
|
}
|
|
110064
|
-
const journal = await readSource(
|
|
110406
|
+
const journal = await readSource(path244.join(dir, "journal.md"), `${repoPath}/journal.md`);
|
|
110065
110407
|
intents.push(extractFlowIntent({
|
|
110066
110408
|
flowJson,
|
|
110067
|
-
description: await readSource(
|
|
110068
|
-
criteria: await readSource(
|
|
110409
|
+
description: await readSource(path244.join(dir, "description.md"), `${repoPath}/description.md`),
|
|
110410
|
+
criteria: await readSource(path244.join(dir, "acceptance-criteria.md"), `${repoPath}/acceptance-criteria.md`),
|
|
110069
110411
|
journal
|
|
110070
110412
|
}, repoPath));
|
|
110071
110413
|
const problem = observationProblem(journal);
|
|
@@ -110076,17 +110418,17 @@ async function buildIntentIndex(cwd) {
|
|
|
110076
110418
|
}
|
|
110077
110419
|
}
|
|
110078
110420
|
for (const name of await requirementPackages(cwd)) {
|
|
110079
|
-
const dir =
|
|
110421
|
+
const dir = path244.join(requirementsDir(cwd), name);
|
|
110080
110422
|
const repoPath = `docs/requirements/${name}`;
|
|
110081
110423
|
try {
|
|
110082
110424
|
let primary = null;
|
|
110083
110425
|
for (const candidate of PRIMARY_DOCS) {
|
|
110084
|
-
primary = await readSource(
|
|
110426
|
+
primary = await readSource(path244.join(dir, candidate), `${repoPath}/${candidate}`);
|
|
110085
110427
|
if (primary !== null)
|
|
110086
110428
|
break;
|
|
110087
110429
|
}
|
|
110088
|
-
const specification = await readSource(
|
|
110089
|
-
const readme = await readSource(
|
|
110430
|
+
const specification = await readSource(path244.join(dir, "specification.md"), `${repoPath}/specification.md`);
|
|
110431
|
+
const readme = await readSource(path244.join(dir, "README.md"), `${repoPath}/README.md`);
|
|
110090
110432
|
const source = { name, primary, specification, readme };
|
|
110091
110433
|
intents.push(extractDocpackIntent(source, repoPath));
|
|
110092
110434
|
const problem = docpackObservationProblem(source);
|
|
@@ -110146,12 +110488,12 @@ var init_by_origin = __esm(() => {
|
|
|
110146
110488
|
|
|
110147
110489
|
// src/product/store.ts
|
|
110148
110490
|
import { readFile as readFile116 } from "fs/promises";
|
|
110149
|
-
import
|
|
110491
|
+
import path245 from "path";
|
|
110150
110492
|
function productDataRoot(cwd) {
|
|
110151
|
-
return
|
|
110493
|
+
return path245.join(cwd, ".metaproject", "data", "product");
|
|
110152
110494
|
}
|
|
110153
110495
|
function indexPath(cwd) {
|
|
110154
|
-
return
|
|
110496
|
+
return path245.join(productDataRoot(cwd), "index.json");
|
|
110155
110497
|
}
|
|
110156
110498
|
function serializeIndex(index) {
|
|
110157
110499
|
return `${JSON.stringify(index, null, 2)}
|
|
@@ -111642,9 +111984,9 @@ var init_repair = __esm(() => {
|
|
|
111642
111984
|
|
|
111643
111985
|
// src/review/managed.ts
|
|
111644
111986
|
import { mkdir as mkdir55, readFile as readFile117, readdir as readdir38, realpath as realpath18, rm as rm16, stat as stat25 } from "fs/promises";
|
|
111645
|
-
import
|
|
111987
|
+
import path246 from "path";
|
|
111646
111988
|
function reviewsRoot(cwd) {
|
|
111647
|
-
return
|
|
111989
|
+
return path246.join(cwd, ".metaproject", "reviews");
|
|
111648
111990
|
}
|
|
111649
111991
|
async function resolveGitHead2(cwd) {
|
|
111650
111992
|
try {
|
|
@@ -111777,26 +112119,26 @@ async function createManagedReviewPackage(input) {
|
|
|
111777
112119
|
throw new Error(`Refusing to record findings that do not satisfy review-finding.schema.json: ${contractErrors.map((error) => `${error.path} ${error.message}`).join("; ")}`);
|
|
111778
112120
|
}
|
|
111779
112121
|
await mkdir55(packageDir, { recursive: true });
|
|
111780
|
-
await writeFileAtomic(
|
|
112122
|
+
await writeFileAtomic(path246.join(packageDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
111781
112123
|
`);
|
|
111782
|
-
await writeFileAtomic(
|
|
111783
|
-
await writeFileAtomic(
|
|
111784
|
-
await writeFileAtomic(
|
|
111785
|
-
await writeFileAtomic(
|
|
112124
|
+
await writeFileAtomic(path246.join(packageDir, "scope.md"), renderScope(input, flowMatch, at, verification, carried, caps, externalReclaims, scopeB.record, filterStats));
|
|
112125
|
+
await writeFileAtomic(path246.join(packageDir, "coverage.md"), renderCoverage(coverage));
|
|
112126
|
+
await writeFileAtomic(path246.join(packageDir, "report.md"), renderReport2(report, input.mode));
|
|
112127
|
+
await writeFileAtomic(path246.join(packageDir, "findings.json"), `${JSON.stringify(findings.map(toContractFinding), null, 2)}
|
|
111786
112128
|
`);
|
|
111787
|
-
await writeFileAtomic(
|
|
111788
|
-
await writeFileAtomic(
|
|
112129
|
+
await writeFileAtomic(path246.join(packageDir, "learning.md"), renderLearning(findings));
|
|
112130
|
+
await writeFileAtomic(path246.join(packageDir, "decisions.md"), renderDecisions(findings));
|
|
111789
112131
|
await persistScreenRadius(packageDir, scopeB.resolved);
|
|
111790
112132
|
const reviewNotes = await writeReviewNotes(findings, {
|
|
111791
112133
|
cwd: input.cwd,
|
|
111792
112134
|
reviewId,
|
|
111793
112135
|
head,
|
|
111794
|
-
packagePath:
|
|
112136
|
+
packagePath: path246.relative(input.cwd, packageDir),
|
|
111795
112137
|
...input.now === undefined ? {} : { now: input.now }
|
|
111796
112138
|
});
|
|
111797
112139
|
return {
|
|
111798
112140
|
reviewId,
|
|
111799
|
-
path:
|
|
112141
|
+
path: path246.relative(input.cwd, packageDir),
|
|
111800
112142
|
manifest,
|
|
111801
112143
|
verification: verification.counts,
|
|
111802
112144
|
verificationRejections: verification.rejections,
|
|
@@ -111813,7 +112155,7 @@ async function screenScopeBFindings(input, packageDir, findings) {
|
|
|
111813
112155
|
const scopeB = findings.filter((finding) => isBlastRadiusScopedFinding(finding, reviewers));
|
|
111814
112156
|
if (resolved === undefined) {
|
|
111815
112157
|
if (scopeB.length > 0) {
|
|
111816
|
-
throw new Error(`Refusing to record ${scopeB.length} finding(s) raised under scope B (${scopeB.map((finding) => `${finding.id} by ${finding.reviewer}`).join(", ")}) with no blast-radius record: AC3 is enforced in code, and the screen cannot be applied without the computed set the round was dispatched over. Two ways out, and the first is the one that always works: (1) compute the set with \`keryx review blast-radius --json > <file>\` and pass \`--blast-radius <file>\` to this ingest; (2) write that record to \`${
|
|
112158
|
+
throw new Error(`Refusing to record ${scopeB.length} finding(s) raised under scope B (${scopeB.map((finding) => `${finding.id} by ${finding.reviewer}`).join(", ")}) with no blast-radius record: AC3 is enforced in code, and the screen cannot be applied without the computed set the round was dispatched over. Two ways out, and the first is the one that always works: (1) compute the set with \`keryx review blast-radius --json > <file>\` and pass \`--blast-radius <file>\` to this ingest; (2) write that record to \`${path246.relative(input.cwd, handoffBlastRadiusPath(packageDir))}\` and ingest again \u2014 the ingest reads it from there and consumes it. Do NOT write it to \`${path246.relative(input.cwd, path246.join(packageDir, BLAST_RADIUS_ARTIFACT))}\`: a round that names no --review-id has not allocated that directory yet, so creating it makes the next ingest take the NEXT free name and find nothing there either.`);
|
|
111817
112159
|
}
|
|
111818
112160
|
return { retained: [...findings], record: { source: "none", scopeBFindings: 0 }, resolved: undefined };
|
|
111819
112161
|
}
|
|
@@ -111830,13 +112172,13 @@ async function screenScopeBFindings(input, packageDir, findings) {
|
|
|
111830
112172
|
};
|
|
111831
112173
|
}
|
|
111832
112174
|
function handoffBlastRadiusPath(packageDir) {
|
|
111833
|
-
return
|
|
112175
|
+
return path246.join(path246.dirname(packageDir), BLAST_RADIUS_ARTIFACT);
|
|
111834
112176
|
}
|
|
111835
112177
|
async function resolveScreenRadius(input, packageDir) {
|
|
111836
112178
|
if (input.blastRadius !== undefined) {
|
|
111837
112179
|
return { radius: input.blastRadius, source: "input" };
|
|
111838
112180
|
}
|
|
111839
|
-
const inPackage =
|
|
112181
|
+
const inPackage = path246.join(packageDir, BLAST_RADIUS_ARTIFACT);
|
|
111840
112182
|
const fromPackage = await readBlastRadiusRecordFile(inPackage);
|
|
111841
112183
|
if (fromPackage !== undefined) {
|
|
111842
112184
|
return { radius: fromPackage, source: "package", file: inPackage };
|
|
@@ -111862,7 +112204,7 @@ async function persistScreenRadius(packageDir, resolved) {
|
|
|
111862
112204
|
if (resolved === undefined) {
|
|
111863
112205
|
return;
|
|
111864
112206
|
}
|
|
111865
|
-
const inPackage =
|
|
112207
|
+
const inPackage = path246.join(packageDir, BLAST_RADIUS_ARTIFACT);
|
|
111866
112208
|
if (resolved.source !== "package") {
|
|
111867
112209
|
await writeFileAtomic(inPackage, `${JSON.stringify(resolved.radius, null, 2)}
|
|
111868
112210
|
`);
|
|
@@ -111893,12 +112235,12 @@ function fromVerifierRefutations(findings) {
|
|
|
111893
112235
|
}));
|
|
111894
112236
|
}
|
|
111895
112237
|
async function getManagedReviewStatus(cwd, ref) {
|
|
111896
|
-
const manifestPath = ref.endsWith("manifest.json") ?
|
|
112238
|
+
const manifestPath = ref.endsWith("manifest.json") ? path246.resolve(cwd, ref) : path246.join(await resolveReviewPackagePath(cwd, ref), "manifest.json");
|
|
111897
112239
|
return JSON.parse(await readFile117(manifestPath, "utf8"));
|
|
111898
112240
|
}
|
|
111899
112241
|
async function completeManagedReview(cwd, ref, options = {}) {
|
|
111900
112242
|
const packageDir = await resolveReviewPackagePath(cwd, ref);
|
|
111901
|
-
const manifestPath =
|
|
112243
|
+
const manifestPath = path246.join(packageDir, "manifest.json");
|
|
111902
112244
|
const manifest = JSON.parse(await readFile117(manifestPath, "utf8"));
|
|
111903
112245
|
const missing = await missingArtifacts(packageDir);
|
|
111904
112246
|
if (missing.length > 0) {
|
|
@@ -111919,12 +112261,12 @@ async function completeManagedReview(cwd, ref, options = {}) {
|
|
|
111919
112261
|
}
|
|
111920
112262
|
await writeFileAtomic(manifestPath, `${JSON.stringify(updated, null, 2)}
|
|
111921
112263
|
`);
|
|
111922
|
-
const recorded = JSON.parse(await readFile117(
|
|
112264
|
+
const recorded = JSON.parse(await readFile117(path246.join(packageDir, "findings.json"), "utf8"));
|
|
111923
112265
|
const reviewNotes = await writeReviewNotes(recorded, {
|
|
111924
112266
|
cwd,
|
|
111925
112267
|
reviewId: updated.reviewId,
|
|
111926
112268
|
head: updated.target.head ?? null,
|
|
111927
|
-
packagePath:
|
|
112269
|
+
packagePath: path246.relative(cwd, packageDir),
|
|
111928
112270
|
...options.now === undefined ? {} : { now: options.now }
|
|
111929
112271
|
});
|
|
111930
112272
|
return { manifest: updated, reviewNotes, dispositions: tallyDispositions(recorded) };
|
|
@@ -111990,16 +112332,16 @@ async function resolvePackageFlow(input) {
|
|
|
111990
112332
|
}
|
|
111991
112333
|
function packagePath(cwd, mode, reviewId, flowMatch) {
|
|
111992
112334
|
if ((mode === "attach-review" || mode === "ingest") && flowMatch) {
|
|
111993
|
-
return
|
|
112335
|
+
return path246.join(flowsRoot(cwd), flowMatch.dir, "reviews", reviewId);
|
|
111994
112336
|
}
|
|
111995
112337
|
if (mode === "attach-review") {
|
|
111996
112338
|
throw new Error("attach-review requires an explicit or matched flow");
|
|
111997
112339
|
}
|
|
111998
|
-
return
|
|
112340
|
+
return path246.join(reviewsRoot(cwd), reviewId);
|
|
111999
112341
|
}
|
|
112000
112342
|
function buildManifest(args) {
|
|
112001
112343
|
const crossFamily = args.input.crossFamilyReview;
|
|
112002
|
-
const artifactPath = (name) =>
|
|
112344
|
+
const artifactPath = (name) => path246.relative(args.input.cwd, path246.join(args.packageDir, name));
|
|
112003
112345
|
const manifest = {
|
|
112004
112346
|
schemaVersion: 1,
|
|
112005
112347
|
reviewId: args.reviewId,
|
|
@@ -112046,7 +112388,7 @@ async function readReport(input) {
|
|
|
112046
112388
|
return input.reportText;
|
|
112047
112389
|
}
|
|
112048
112390
|
if (input.reportPath) {
|
|
112049
|
-
return readFile117(
|
|
112391
|
+
return readFile117(path246.resolve(input.cwd, input.reportPath), "utf8");
|
|
112050
112392
|
}
|
|
112051
112393
|
if (input.mode === "ingest") {
|
|
112052
112394
|
throw new Error("ingest requires --report or reportText");
|
|
@@ -112057,7 +112399,7 @@ No reviewer findings recorded yet.
|
|
|
112057
112399
|
`;
|
|
112058
112400
|
}
|
|
112059
112401
|
function reportLabel(input) {
|
|
112060
|
-
return input.reportPath ?
|
|
112402
|
+
return input.reportPath ? path246.resolve(input.cwd, input.reportPath) : "the report text passed to keryx review";
|
|
112061
112403
|
}
|
|
112062
112404
|
async function normalizeFindings(args) {
|
|
112063
112405
|
if (args.source !== undefined) {
|
|
@@ -112112,16 +112454,16 @@ async function anchorFindings(findings, input, head) {
|
|
|
112112
112454
|
}
|
|
112113
112455
|
}
|
|
112114
112456
|
async function containedRelativePath(cwd, relative) {
|
|
112115
|
-
const root = await realpath18(cwd).catch(() =>
|
|
112116
|
-
const resolved =
|
|
112457
|
+
const root = await realpath18(cwd).catch(() => path246.resolve(cwd));
|
|
112458
|
+
const resolved = path246.resolve(root, relative);
|
|
112117
112459
|
const real = await realpath18(resolved).catch(() => null);
|
|
112118
|
-
if (real === null || real !== root && !real.startsWith(`${root}${
|
|
112460
|
+
if (real === null || real !== root && !real.startsWith(`${root}${path246.sep}`)) {
|
|
112119
112461
|
return null;
|
|
112120
112462
|
}
|
|
112121
112463
|
return real;
|
|
112122
112464
|
}
|
|
112123
112465
|
async function readFileAtCommit(cwd, commit, absolute) {
|
|
112124
|
-
const relative =
|
|
112466
|
+
const relative = path246.relative(await realpath18(cwd).catch(() => path246.resolve(cwd)), absolute);
|
|
112125
112467
|
try {
|
|
112126
112468
|
const proc = Bun.spawn(["git", "show", `${commit}:${relative}`], {
|
|
112127
112469
|
cwd,
|
|
@@ -112140,10 +112482,10 @@ async function readFileAtCommit(cwd, commit, absolute) {
|
|
|
112140
112482
|
}
|
|
112141
112483
|
function readFromWorkingTree(input) {
|
|
112142
112484
|
return async (relative) => {
|
|
112143
|
-
const root = await realpath18(input.cwd).catch(() =>
|
|
112144
|
-
const resolved =
|
|
112485
|
+
const root = await realpath18(input.cwd).catch(() => path246.resolve(input.cwd));
|
|
112486
|
+
const resolved = path246.resolve(root, relative);
|
|
112145
112487
|
const real = await realpath18(resolved).catch(() => null);
|
|
112146
|
-
if (real === null || real !== root && !real.startsWith(`${root}${
|
|
112488
|
+
if (real === null || real !== root && !real.startsWith(`${root}${path246.sep}`)) {
|
|
112147
112489
|
return null;
|
|
112148
112490
|
}
|
|
112149
112491
|
try {
|
|
@@ -112275,7 +112617,7 @@ function refutedClassification(state) {
|
|
|
112275
112617
|
return state === "dismissed-incorrect" ? "false_positive" : "out_of_scope";
|
|
112276
112618
|
}
|
|
112277
112619
|
async function recordDispositions(packageDir, records) {
|
|
112278
|
-
const findingsPath =
|
|
112620
|
+
const findingsPath = path246.join(packageDir, "findings.json");
|
|
112279
112621
|
const findings = JSON.parse(await readFile117(findingsPath, "utf8"));
|
|
112280
112622
|
const schema = await loadSchema("review-finding");
|
|
112281
112623
|
const dispositionSchema = schema.properties?.["disposition"];
|
|
@@ -112602,7 +112944,7 @@ function upsertPreFilterScopeBlock(text, block) {
|
|
|
112602
112944
|
${body}`;
|
|
112603
112945
|
}
|
|
112604
112946
|
async function readPreFilterScopeBlock(packageDir) {
|
|
112605
|
-
const scopePath =
|
|
112947
|
+
const scopePath = path246.join(packageDir, "scope.md");
|
|
112606
112948
|
if (!await pathExists(scopePath)) {
|
|
112607
112949
|
return;
|
|
112608
112950
|
}
|
|
@@ -112776,39 +113118,39 @@ function matchReason(flow, dir, kind, ref) {
|
|
|
112776
113118
|
async function missingArtifacts(packageDir) {
|
|
112777
113119
|
const missing = [];
|
|
112778
113120
|
for (const artifact of ["scope.md", "coverage.md", "report.md", "findings.json", "learning.md", "decisions.md"]) {
|
|
112779
|
-
if (!await pathExists(
|
|
113121
|
+
if (!await pathExists(path246.join(packageDir, artifact))) {
|
|
112780
113122
|
missing.push(artifact);
|
|
112781
113123
|
}
|
|
112782
113124
|
}
|
|
112783
113125
|
return missing;
|
|
112784
113126
|
}
|
|
112785
113127
|
async function resolveReviewPackagePath(cwd, ref) {
|
|
112786
|
-
const absolute =
|
|
112787
|
-
if (await pathExists(
|
|
113128
|
+
const absolute = path246.resolve(cwd, ref);
|
|
113129
|
+
if (await pathExists(path246.join(absolute, "manifest.json"))) {
|
|
112788
113130
|
return absolute;
|
|
112789
113131
|
}
|
|
112790
113132
|
if (await pathExists(absolute) && absolute.endsWith("manifest.json")) {
|
|
112791
|
-
return
|
|
113133
|
+
return path246.dirname(absolute);
|
|
112792
113134
|
}
|
|
112793
|
-
const standalone =
|
|
112794
|
-
if (await pathExists(
|
|
113135
|
+
const standalone = path246.join(reviewsRoot(cwd), ref);
|
|
113136
|
+
if (await pathExists(path246.join(standalone, "manifest.json"))) {
|
|
112795
113137
|
return standalone;
|
|
112796
113138
|
}
|
|
112797
113139
|
for (const flowDir of await listFlowDirs(cwd)) {
|
|
112798
|
-
const reviewsDir =
|
|
113140
|
+
const reviewsDir = path246.join(flowsRoot(cwd), flowDir, "reviews");
|
|
112799
113141
|
if (!await pathExists(reviewsDir)) {
|
|
112800
113142
|
continue;
|
|
112801
113143
|
}
|
|
112802
113144
|
for (const entry of await readdir38(reviewsDir, { withFileTypes: true })) {
|
|
112803
113145
|
if (entry.isDirectory() && entry.name === ref) {
|
|
112804
|
-
return
|
|
113146
|
+
return path246.join(reviewsDir, entry.name);
|
|
112805
113147
|
}
|
|
112806
113148
|
}
|
|
112807
113149
|
}
|
|
112808
113150
|
throw new Error(`Managed review package not found: ${ref}`);
|
|
112809
113151
|
}
|
|
112810
113152
|
async function loadDocpackSchema(cwd) {
|
|
112811
|
-
const schemaPath =
|
|
113153
|
+
const schemaPath = path246.join(cwd, "docs", "requirements", "managed-review-feedback-loop", "schemas", "managed-review-package.schema.json");
|
|
112812
113154
|
if (!await pathExists(schemaPath)) {
|
|
112813
113155
|
return null;
|
|
112814
113156
|
}
|
|
@@ -112914,7 +113256,7 @@ function checkPullOpen(facts) {
|
|
|
112914
113256
|
|
|
112915
113257
|
// src/review/bot/packages.ts
|
|
112916
113258
|
import { readdir as readdir39, readFile as readFile118 } from "fs/promises";
|
|
112917
|
-
import
|
|
113259
|
+
import path247 from "path";
|
|
112918
113260
|
async function subdirectories2(dir) {
|
|
112919
113261
|
try {
|
|
112920
113262
|
const entries = await readdir39(dir, { withFileTypes: true });
|
|
@@ -112937,12 +113279,12 @@ function pullFromTarget(target) {
|
|
|
112937
113279
|
}
|
|
112938
113280
|
async function readPackage(dir) {
|
|
112939
113281
|
try {
|
|
112940
|
-
const manifest = JSON.parse(await readFile118(
|
|
113282
|
+
const manifest = JSON.parse(await readFile118(path247.join(dir, "manifest.json"), "utf8"));
|
|
112941
113283
|
if (manifest.target?.kind !== "pr")
|
|
112942
113284
|
return null;
|
|
112943
113285
|
let findings = [];
|
|
112944
113286
|
try {
|
|
112945
|
-
const parsed = JSON.parse(await readFile118(
|
|
113287
|
+
const parsed = JSON.parse(await readFile118(path247.join(dir, "findings.json"), "utf8"));
|
|
112946
113288
|
if (Array.isArray(parsed))
|
|
112947
113289
|
findings = parsed;
|
|
112948
113290
|
} catch {
|
|
@@ -112957,16 +113299,16 @@ async function readPackage(dir) {
|
|
|
112957
113299
|
function candidateDirs2(cwd) {
|
|
112958
113300
|
return (async () => {
|
|
112959
113301
|
const dirs = [];
|
|
112960
|
-
const reviews =
|
|
113302
|
+
const reviews = path247.join(cwd, ".metaproject", "reviews");
|
|
112961
113303
|
for (const name of await subdirectories2(reviews)) {
|
|
112962
113304
|
if (name !== "bot")
|
|
112963
|
-
dirs.push(
|
|
113305
|
+
dirs.push(path247.join(reviews, name));
|
|
112964
113306
|
}
|
|
112965
|
-
const flows =
|
|
113307
|
+
const flows = path247.join(cwd, ".metaproject", "flows");
|
|
112966
113308
|
for (const flow of await subdirectories2(flows)) {
|
|
112967
|
-
const nested =
|
|
113309
|
+
const nested = path247.join(flows, flow, "reviews");
|
|
112968
113310
|
for (const name of await subdirectories2(nested))
|
|
112969
|
-
dirs.push(
|
|
113311
|
+
dirs.push(path247.join(nested, name));
|
|
112970
113312
|
}
|
|
112971
113313
|
return dirs;
|
|
112972
113314
|
})();
|
|
@@ -112983,7 +113325,7 @@ async function listReviewPackages(cwd) {
|
|
|
112983
113325
|
}
|
|
112984
113326
|
async function loadReviewPackage(cwd, reviewId) {
|
|
112985
113327
|
for (const dir of await candidateDirs2(cwd)) {
|
|
112986
|
-
if (
|
|
113328
|
+
if (path247.basename(dir) !== reviewId)
|
|
112987
113329
|
continue;
|
|
112988
113330
|
const listed = await readPackage(dir);
|
|
112989
113331
|
if (listed !== null && listed.reviewId === reviewId)
|
|
@@ -112994,7 +113336,7 @@ async function loadReviewPackage(cwd, reviewId) {
|
|
|
112994
113336
|
var init_packages = () => {};
|
|
112995
113337
|
|
|
112996
113338
|
// src/review/bot/run.ts
|
|
112997
|
-
import
|
|
113339
|
+
import path248 from "path";
|
|
112998
113340
|
function capDiff(diff, maxBytes) {
|
|
112999
113341
|
const totalBytes = Buffer.byteLength(diff);
|
|
113000
113342
|
if (totalBytes <= maxBytes)
|
|
@@ -113039,7 +113381,7 @@ function parseVerifierVerdict2(reply) {
|
|
|
113039
113381
|
return unverifiable;
|
|
113040
113382
|
}
|
|
113041
113383
|
function botStatePath(cwd, repo, number) {
|
|
113042
|
-
return
|
|
113384
|
+
return path248.join(cwd, ".metaproject", "reviews", "bot", `${repo.replace(/\//g, "__")}__${number}.json`);
|
|
113043
113385
|
}
|
|
113044
113386
|
function emptyBotState(repo, number) {
|
|
113045
113387
|
return { schemaVersion: 1, repo, number, reviews: [], pull: null };
|
|
@@ -113662,9 +114004,9 @@ var init_reviews_inspector = __esm(() => {
|
|
|
113662
114004
|
|
|
113663
114005
|
// src/lib/serve-config.ts
|
|
113664
114006
|
import { existsSync as existsSync42 } from "fs";
|
|
113665
|
-
import
|
|
114007
|
+
import path249 from "path";
|
|
113666
114008
|
function serveConfigPath(dir) {
|
|
113667
|
-
return
|
|
114009
|
+
return path249.join(keryxConfigDir(dir), "serve.json");
|
|
113668
114010
|
}
|
|
113669
114011
|
function parseIpv4(value) {
|
|
113670
114012
|
const parts = value.split(".");
|
|
@@ -113790,7 +114132,7 @@ function projectServeConfig(value, onDrop) {
|
|
|
113790
114132
|
if (!isRecord12(value)) {
|
|
113791
114133
|
return null;
|
|
113792
114134
|
}
|
|
113793
|
-
reportUndeclared(value,
|
|
114135
|
+
reportUndeclared(value, CONFIG_KEYS2, "", onDrop);
|
|
113794
114136
|
if (value.schemaVersion !== SERVE_CONFIG_SCHEMA_VERSION) {
|
|
113795
114137
|
return null;
|
|
113796
114138
|
}
|
|
@@ -113962,10 +114304,10 @@ function saveServeConfig(config, dir, onWarn) {
|
|
|
113962
114304
|
return false;
|
|
113963
114305
|
}
|
|
113964
114306
|
}
|
|
113965
|
-
var SERVE_CONFIG_SCHEMA_VERSION = "1.0.0", DEFAULT_SERVE_BIND_ADDRESS = "127.0.0.1", DEFAULT_SERVE_PORT = 7377, DEFAULT_SERVE_PROFILE = "remote-restricted",
|
|
114307
|
+
var SERVE_CONFIG_SCHEMA_VERSION = "1.0.0", DEFAULT_SERVE_BIND_ADDRESS = "127.0.0.1", DEFAULT_SERVE_PORT = 7377, DEFAULT_SERVE_PROFILE = "remote-restricted", CONFIG_KEYS2, BIND_KEYS, CREDENTIAL_REF_KEYS, APPROVAL_KEYS, BOUNDS_KEYS;
|
|
113966
114308
|
var init_serve_config = __esm(() => {
|
|
113967
114309
|
init_config_dir();
|
|
113968
|
-
|
|
114310
|
+
CONFIG_KEYS2 = [
|
|
113969
114311
|
"schemaVersion",
|
|
113970
114312
|
"enabled",
|
|
113971
114313
|
"bind",
|
|
@@ -114101,25 +114443,6 @@ var init_protocol2 = __esm(() => {
|
|
|
114101
114443
|
MESSAGE_STATES = ["reading", "working", "done", "failed"];
|
|
114102
114444
|
});
|
|
114103
114445
|
|
|
114104
|
-
// src/remote/paths.ts
|
|
114105
|
-
import path249 from "path";
|
|
114106
|
-
function remoteDirPath(dir) {
|
|
114107
|
-
return path249.join(keryxConfigDir(dir), REMOTE_DIRNAME);
|
|
114108
|
-
}
|
|
114109
|
-
function ensureRemoteDir(dir, ...sub) {
|
|
114110
|
-
return ensureKeryxSubdir([REMOTE_DIRNAME, ...sub], dir);
|
|
114111
|
-
}
|
|
114112
|
-
function botTokenPath(dir) {
|
|
114113
|
-
return path249.join(remoteDirPath(dir), BOT_TOKEN_FILE);
|
|
114114
|
-
}
|
|
114115
|
-
function remoteConfigPath(dir) {
|
|
114116
|
-
return path249.join(remoteDirPath(dir), REMOTE_CONFIG_FILE);
|
|
114117
|
-
}
|
|
114118
|
-
var REMOTE_DIRNAME = "remote", BOT_TOKEN_FILE = "bot-token", REMOTE_CONFIG_FILE = "config.json", REGISTRY_FILE = "sessions.json", SERVICE_TOPICS_FILE = "service-topics.json", POLLER_STATE_FILE = "poller-state.json", OUTBOUND_FILE = "outbound.jsonl", REJECTED_JOURNAL_FILE = "rejected.jsonl", INBOUND_DIRNAME = "inbound";
|
|
114119
|
-
var init_paths4 = __esm(() => {
|
|
114120
|
-
init_config_dir();
|
|
114121
|
-
});
|
|
114122
|
-
|
|
114123
114446
|
// src/remote/endpoint.ts
|
|
114124
114447
|
import path250 from "path";
|
|
114125
114448
|
function endpointPath(dir) {
|
|
@@ -114158,8 +114481,8 @@ import {
|
|
|
114158
114481
|
fsyncSync as fsyncSync2,
|
|
114159
114482
|
openSync as openSync5,
|
|
114160
114483
|
renameSync as renameSync5,
|
|
114161
|
-
statSync as
|
|
114162
|
-
unlinkSync as
|
|
114484
|
+
statSync as statSync16,
|
|
114485
|
+
unlinkSync as unlinkSync6,
|
|
114163
114486
|
writeFileSync as writeFileSync8
|
|
114164
114487
|
} from "fs";
|
|
114165
114488
|
import path251 from "path";
|
|
@@ -114193,7 +114516,7 @@ function isGroupOrOtherAccessible(file) {
|
|
|
114193
114516
|
if (process.platform === "win32") {
|
|
114194
114517
|
return false;
|
|
114195
114518
|
}
|
|
114196
|
-
const mode =
|
|
114519
|
+
const mode = statSync16(file, { throwIfNoEntry: false })?.mode;
|
|
114197
114520
|
return mode !== undefined && (mode & 63) !== 0;
|
|
114198
114521
|
}
|
|
114199
114522
|
function readServeCredential(dir) {
|
|
@@ -114250,7 +114573,7 @@ function writeStore(store, dir) {
|
|
|
114250
114573
|
return true;
|
|
114251
114574
|
} catch {
|
|
114252
114575
|
try {
|
|
114253
|
-
|
|
114576
|
+
unlinkSync6(temp);
|
|
114254
114577
|
} catch {}
|
|
114255
114578
|
return false;
|
|
114256
114579
|
}
|
|
@@ -114326,7 +114649,7 @@ var init_serve_credential = __esm(() => {
|
|
|
114326
114649
|
|
|
114327
114650
|
// src/remote/shell-token.ts
|
|
114328
114651
|
import { createHash as createHash64, createHmac as createHmac5, randomBytes as randomBytes12 } from "crypto";
|
|
114329
|
-
import { statSync as
|
|
114652
|
+
import { statSync as statSync17 } from "fs";
|
|
114330
114653
|
import path252 from "path";
|
|
114331
114654
|
function shellTokenPath(dir) {
|
|
114332
114655
|
return path252.join(remoteDirPath(dir), SHELL_TOKEN_FILE);
|
|
@@ -114334,7 +114657,7 @@ function shellTokenPath(dir) {
|
|
|
114334
114657
|
function readValidated(file) {
|
|
114335
114658
|
if (process.platform !== "win32") {
|
|
114336
114659
|
try {
|
|
114337
|
-
const mode =
|
|
114660
|
+
const mode = statSync17(file).mode & 511;
|
|
114338
114661
|
if ((mode & 63) !== 0) {
|
|
114339
114662
|
return { ok: false, reason: `shell token file ${file} is accessible to other users (mode ${mode.toString(8)}); run: chmod 600 ${file}` };
|
|
114340
114663
|
}
|
|
@@ -114347,7 +114670,7 @@ function readValidated(file) {
|
|
|
114347
114670
|
return { ok: false, reason: read.reason === "absent" ? `no shell token at ${file}; is \`keryx serve\` running with remote control configured?` : `shell token file ${file} cannot be read (${read.reason})` };
|
|
114348
114671
|
}
|
|
114349
114672
|
const token = read.text.trim();
|
|
114350
|
-
if (!
|
|
114673
|
+
if (!TOKEN_SHAPE2.test(token)) {
|
|
114351
114674
|
return { ok: false, reason: `shell token file ${file} does not hold a shell token; delete it and restart \`keryx serve\`` };
|
|
114352
114675
|
}
|
|
114353
114676
|
return { ok: true, value: token };
|
|
@@ -114412,12 +114735,12 @@ function createShellTokenVerifier(token) {
|
|
|
114412
114735
|
return constantTimeEqual(createHash64("sha256").update(presented, "utf8").digest(), expected);
|
|
114413
114736
|
};
|
|
114414
114737
|
}
|
|
114415
|
-
var SHELL_TOKEN_FILE = "shell-token",
|
|
114738
|
+
var SHELL_TOKEN_FILE = "shell-token", TOKEN_SHAPE2, SERVE_PROOF_HEADER = "x-keryx-serve-proof", DERIVED_BEARER_PREFIX = "ksp1.", AUTH_DOMAIN = "keryx-shell-auth-v1", PROOF_DOMAIN = "keryx-serve-proof-v1", NONCE_SHAPE, MAC_SHAPE;
|
|
114416
114739
|
var init_shell_token = __esm(() => {
|
|
114417
114740
|
init_config_dir();
|
|
114418
114741
|
init_serve_credential();
|
|
114419
114742
|
init_paths4();
|
|
114420
|
-
|
|
114743
|
+
TOKEN_SHAPE2 = /^[A-Za-z0-9_-]{32,128}$/;
|
|
114421
114744
|
NONCE_SHAPE = /^[A-Za-z0-9_-]{22,64}$/;
|
|
114422
114745
|
MAC_SHAPE = /^[A-Za-z0-9_-]{43}$/;
|
|
114423
114746
|
});
|
|
@@ -115865,162 +116188,6 @@ var init_shell_bridge = __esm(() => {
|
|
|
115865
116188
|
init_protocol2();
|
|
115866
116189
|
});
|
|
115867
116190
|
|
|
115868
|
-
// src/remote/config.ts
|
|
115869
|
-
import { statSync as statSync17, unlinkSync as unlinkSync6 } from "fs";
|
|
115870
|
-
function isSafeInt(value) {
|
|
115871
|
-
return typeof value === "number" && Number.isSafeInteger(value);
|
|
115872
|
-
}
|
|
115873
|
-
function parseRemoteConfig(value) {
|
|
115874
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
115875
|
-
return { ok: false, reason: "remote config must be a JSON object" };
|
|
115876
|
-
}
|
|
115877
|
-
const doc = value;
|
|
115878
|
-
for (const key of Object.keys(doc)) {
|
|
115879
|
-
if (!CONFIG_KEYS2.has(key)) {
|
|
115880
|
-
const shown = /^[A-Za-z_][A-Za-z0-9_]{0,30}$/.test(key) ? key : "<not shown>";
|
|
115881
|
-
return { ok: false, reason: `remote config has an unknown key "${shown}" (the schema is closed)` };
|
|
115882
|
-
}
|
|
115883
|
-
}
|
|
115884
|
-
if (doc.schemaVersion !== REMOTE_CONFIG_SCHEMA_VERSION) {
|
|
115885
|
-
return { ok: false, reason: `remote config schemaVersion must be ${REMOTE_CONFIG_SCHEMA_VERSION}` };
|
|
115886
|
-
}
|
|
115887
|
-
if (!isSafeInt(doc.chatId) || doc.chatId === 0) {
|
|
115888
|
-
return { ok: false, reason: "remote config chatId must be a non-zero integer (the supergroup id)" };
|
|
115889
|
-
}
|
|
115890
|
-
const ids = doc.allowedUserIds;
|
|
115891
|
-
if (!Array.isArray(ids) || ids.length === 0 || !ids.every((id) => isSafeInt(id) && id > 0)) {
|
|
115892
|
-
return { ok: false, reason: "remote config allowedUserIds must be a non-empty array of positive integers" };
|
|
115893
|
-
}
|
|
115894
|
-
const timeout = (key, fallback) => {
|
|
115895
|
-
const raw = doc[key];
|
|
115896
|
-
if (raw === undefined) {
|
|
115897
|
-
return { ok: true, value: fallback };
|
|
115898
|
-
}
|
|
115899
|
-
if (!isSafeInt(raw) || raw < 1 || raw > MAX_TIMEOUT_MS3) {
|
|
115900
|
-
return { ok: false, reason: `remote config ${key} must be an integer from 1 to ${MAX_TIMEOUT_MS3} (milliseconds)` };
|
|
115901
|
-
}
|
|
115902
|
-
return { ok: true, value: raw };
|
|
115903
|
-
};
|
|
115904
|
-
const orphan = timeout("orphanMs", DEFAULT_ORPHAN_MS);
|
|
115905
|
-
if (!orphan.ok) {
|
|
115906
|
-
return orphan;
|
|
115907
|
-
}
|
|
115908
|
-
const run = timeout("runTimeoutMs", DEFAULT_RUN_TIMEOUT_MS);
|
|
115909
|
-
if (!run.ok) {
|
|
115910
|
-
return run;
|
|
115911
|
-
}
|
|
115912
|
-
return {
|
|
115913
|
-
ok: true,
|
|
115914
|
-
value: {
|
|
115915
|
-
schemaVersion: REMOTE_CONFIG_SCHEMA_VERSION,
|
|
115916
|
-
chatId: doc.chatId,
|
|
115917
|
-
allowedUserIds: [...new Set(ids)],
|
|
115918
|
-
orphanMs: orphan.value,
|
|
115919
|
-
runTimeoutMs: run.value
|
|
115920
|
-
}
|
|
115921
|
-
};
|
|
115922
|
-
}
|
|
115923
|
-
function loadRemoteConfig(dir) {
|
|
115924
|
-
const file = remoteConfigPath(dir);
|
|
115925
|
-
const read = readConfigFile(file);
|
|
115926
|
-
if (!read.ok) {
|
|
115927
|
-
return {
|
|
115928
|
-
ok: false,
|
|
115929
|
-
reason: read.reason === "absent" ? `no remote-control config at ${file}; create it with chatId and allowedUserIds` : `remote-control config at ${file} cannot be read (${read.reason})`
|
|
115930
|
-
};
|
|
115931
|
-
}
|
|
115932
|
-
let parsed;
|
|
115933
|
-
try {
|
|
115934
|
-
parsed = JSON.parse(read.text);
|
|
115935
|
-
} catch {
|
|
115936
|
-
return { ok: false, reason: `remote-control config at ${file} is not valid JSON` };
|
|
115937
|
-
}
|
|
115938
|
-
return parseRemoteConfig(parsed);
|
|
115939
|
-
}
|
|
115940
|
-
function saveRemoteConfig(config, dir) {
|
|
115941
|
-
const checked = parseRemoteConfig(config);
|
|
115942
|
-
if (!checked.ok) {
|
|
115943
|
-
return checked;
|
|
115944
|
-
}
|
|
115945
|
-
ensureRemoteDir(dir);
|
|
115946
|
-
writeOwnerOnlyFileAtomic(remoteConfigPath(dir), `${JSON.stringify(checked.value, null, 2)}
|
|
115947
|
-
`);
|
|
115948
|
-
return checked;
|
|
115949
|
-
}
|
|
115950
|
-
function loadBotToken(dir) {
|
|
115951
|
-
const file = botTokenPath(dir);
|
|
115952
|
-
let mode;
|
|
115953
|
-
try {
|
|
115954
|
-
const stats = statSync17(file);
|
|
115955
|
-
if (!stats.isFile()) {
|
|
115956
|
-
return { ok: false, reason: `bot token path ${file} is not a regular file` };
|
|
115957
|
-
}
|
|
115958
|
-
mode = stats.mode & 511;
|
|
115959
|
-
} catch {
|
|
115960
|
-
return {
|
|
115961
|
-
ok: false,
|
|
115962
|
-
reason: `no bot token file at ${file}; put the token on one line in it and run: chmod 600 ${file}`
|
|
115963
|
-
};
|
|
115964
|
-
}
|
|
115965
|
-
if (process.platform !== "win32" && (mode & 63) !== 0) {
|
|
115966
|
-
return {
|
|
115967
|
-
ok: false,
|
|
115968
|
-
reason: `bot token file ${file} is accessible to other users (mode ${mode.toString(8)}); run: chmod 600 ${file}`
|
|
115969
|
-
};
|
|
115970
|
-
}
|
|
115971
|
-
const read = readConfigFile(file);
|
|
115972
|
-
if (!read.ok) {
|
|
115973
|
-
return { ok: false, reason: `bot token file ${file} cannot be read (${read.reason})` };
|
|
115974
|
-
}
|
|
115975
|
-
const token = read.text.trim();
|
|
115976
|
-
if (!TOKEN_SHAPE2.test(token)) {
|
|
115977
|
-
return {
|
|
115978
|
-
ok: false,
|
|
115979
|
-
reason: `bot token file ${file} does not hold a bot token (expected <digits>:<secret> on a single line)`
|
|
115980
|
-
};
|
|
115981
|
-
}
|
|
115982
|
-
return { ok: true, value: token };
|
|
115983
|
-
}
|
|
115984
|
-
function saveBotToken(token, dir) {
|
|
115985
|
-
const trimmed = token.trim();
|
|
115986
|
-
if (!TOKEN_SHAPE2.test(trimmed)) {
|
|
115987
|
-
return { ok: false, reason: "that does not look like a bot token (expected <digits>:<secret>, as BotFather gives it)" };
|
|
115988
|
-
}
|
|
115989
|
-
const file = botTokenPath(dir);
|
|
115990
|
-
try {
|
|
115991
|
-
ensureRemoteDir(dir);
|
|
115992
|
-
writeOwnerOnlyFileAtomic(file, `${trimmed}
|
|
115993
|
-
`);
|
|
115994
|
-
} catch {
|
|
115995
|
-
return { ok: false, reason: `could not write the bot token file at ${file}` };
|
|
115996
|
-
}
|
|
115997
|
-
return { ok: true, value: true };
|
|
115998
|
-
}
|
|
115999
|
-
function removeFile2(file) {
|
|
116000
|
-
try {
|
|
116001
|
-
unlinkSync6(file);
|
|
116002
|
-
return true;
|
|
116003
|
-
} catch (error) {
|
|
116004
|
-
return error.code === "ENOENT";
|
|
116005
|
-
}
|
|
116006
|
-
}
|
|
116007
|
-
function removeBotToken(dir) {
|
|
116008
|
-
return removeFile2(botTokenPath(dir));
|
|
116009
|
-
}
|
|
116010
|
-
function removeRemoteConfig(dir) {
|
|
116011
|
-
return removeFile2(remoteConfigPath(dir));
|
|
116012
|
-
}
|
|
116013
|
-
var REMOTE_CONFIG_SCHEMA_VERSION = 1, DEFAULT_ORPHAN_MS, DEFAULT_RUN_TIMEOUT_MS, MAX_TIMEOUT_MS3, CONFIG_KEYS2, TOKEN_SHAPE2;
|
|
116014
|
-
var init_config11 = __esm(() => {
|
|
116015
|
-
init_config_dir();
|
|
116016
|
-
init_paths4();
|
|
116017
|
-
DEFAULT_ORPHAN_MS = 10 * 60000;
|
|
116018
|
-
DEFAULT_RUN_TIMEOUT_MS = 30 * 60000;
|
|
116019
|
-
MAX_TIMEOUT_MS3 = 7 * 24 * 60 * 60000;
|
|
116020
|
-
CONFIG_KEYS2 = new Set(["schemaVersion", "chatId", "allowedUserIds", "orphanMs", "runTimeoutMs"]);
|
|
116021
|
-
TOKEN_SHAPE2 = /^\d{5,}:[A-Za-z0-9_-]{20,}$/;
|
|
116022
|
-
});
|
|
116023
|
-
|
|
116024
116191
|
// src/remote/channels-client.ts
|
|
116025
116192
|
function fileExists(file) {
|
|
116026
116193
|
const read = readConfigFile(file);
|
|
@@ -116592,6 +116759,7 @@ async function loadChannelsSnapshot(client) {
|
|
|
116592
116759
|
configured: telegram.state !== "not-connected" || local.tokenFile || local.configFile,
|
|
116593
116760
|
machine,
|
|
116594
116761
|
sessions: telegram.sessions,
|
|
116762
|
+
...telegram.rendering !== undefined ? { rendering: telegram.rendering } : {},
|
|
116595
116763
|
...telegram.reason !== undefined ? { reason: telegram.reason } : {}
|
|
116596
116764
|
};
|
|
116597
116765
|
}
|
|
@@ -116622,6 +116790,19 @@ function projectChannelsRow(snapshot, width) {
|
|
|
116622
116790
|
return { text: fit2("off", width), role: "muted", action: "open" };
|
|
116623
116791
|
}
|
|
116624
116792
|
}
|
|
116793
|
+
function clock(at) {
|
|
116794
|
+
return `${new Date(at).toISOString().slice(11, 19)} UTC`;
|
|
116795
|
+
}
|
|
116796
|
+
function renderingLines(rendering) {
|
|
116797
|
+
if (rendering === undefined)
|
|
116798
|
+
return [];
|
|
116799
|
+
const last = rendering.lastFallback;
|
|
116800
|
+
const step = last?.step === "rich-to-html" ? "rich to HTML" : "HTML to plain text";
|
|
116801
|
+
return [
|
|
116802
|
+
`Rendering: ${rendering.mode} (${RENDERING_MEANING2[rendering.mode]}). Change it in /settings.`,
|
|
116803
|
+
last === undefined ? "Last fallback: none since keryx serve started." : `Last fallback: ${step} at ${clock(last.at)}: ${last.reason}`
|
|
116804
|
+
];
|
|
116805
|
+
}
|
|
116625
116806
|
function channelsStatusLines(snapshot) {
|
|
116626
116807
|
const machine = snapshot.machine === undefined ? [] : [`Machine: ${snapshot.machine}`];
|
|
116627
116808
|
switch (snapshot.state) {
|
|
@@ -116645,6 +116826,7 @@ function channelsStatusLines(snapshot) {
|
|
|
116645
116826
|
"Telegram: connected",
|
|
116646
116827
|
...machine,
|
|
116647
116828
|
`Sessions in Telegram: ${snapshot.sessions ?? 0}`,
|
|
116829
|
+
...renderingLines(snapshot.rendering),
|
|
116648
116830
|
"Test sends one message to the General topic. Disconnect deletes the topics and erases the token.",
|
|
116649
116831
|
"Sessions join through /remote-control in each shell."
|
|
116650
116832
|
];
|
|
@@ -116653,6 +116835,7 @@ function channelsStatusLines(snapshot) {
|
|
|
116653
116835
|
"Telegram: configured, but not running",
|
|
116654
116836
|
...machine,
|
|
116655
116837
|
...snapshot.reason === undefined ? [] : [`Why: ${snapshot.reason}`],
|
|
116838
|
+
...renderingLines(snapshot.rendering),
|
|
116656
116839
|
"Retry starts it again from the saved token and config. Disconnect erases them. Test works once Telegram is running."
|
|
116657
116840
|
];
|
|
116658
116841
|
case "serve-down":
|
|
@@ -117325,7 +117508,7 @@ function mountChannelsPanel(otui, renderer, parent, options) {
|
|
|
117325
117508
|
}
|
|
117326
117509
|
};
|
|
117327
117510
|
}
|
|
117328
|
-
var CHANNELS_COMMAND = "/channels", CHANNELS_USAGE, CHANNELS_FOOTER, TOKEN_CHAR, TOKEN_MAX = 200, PAIRING_POLL_MS = 2000, swallow = (event) => {
|
|
117511
|
+
var CHANNELS_COMMAND = "/channels", CHANNELS_USAGE, RENDERING_MEANING2, CHANNELS_FOOTER, TOKEN_CHAR, TOKEN_MAX = 200, PAIRING_POLL_MS = 2000, swallow = (event) => {
|
|
117329
117512
|
event.preventDefault?.();
|
|
117330
117513
|
event.stopPropagation?.();
|
|
117331
117514
|
}, CHANNELS_ROW_POLL_MS = 1e4;
|
|
@@ -117337,6 +117520,12 @@ var init_channels_surface = __esm(() => {
|
|
|
117337
117520
|
init_theme_repaint();
|
|
117338
117521
|
init_theme_text();
|
|
117339
117522
|
CHANNELS_USAGE = `Usage: ${CHANNELS_COMMAND} [status]`;
|
|
117523
|
+
RENDERING_MEANING2 = {
|
|
117524
|
+
auto: "rich for a reply with a table, HTML otherwise",
|
|
117525
|
+
rich: "every reply as a rich message",
|
|
117526
|
+
html: "every reply as Telegram HTML",
|
|
117527
|
+
plain: "every reply as plain text"
|
|
117528
|
+
};
|
|
117340
117529
|
CHANNELS_FOOTER = [
|
|
117341
117530
|
{ key: "c", label: "connect" },
|
|
117342
117531
|
{ key: "t", label: "test" },
|
|
@@ -117397,19 +117586,19 @@ function formatRemoteStatusLines(status) {
|
|
|
117397
117586
|
lines.push(`Turn it off with ${REMOTE_CONTROL_COMMAND} off, or press o here. Rename: ${REMOTE_CONTROL_COMMAND} off, then ${REMOTE_CONTROL_COMMAND} <name>.`);
|
|
117398
117587
|
return lines;
|
|
117399
117588
|
}
|
|
117400
|
-
function
|
|
117589
|
+
function clock2(at) {
|
|
117401
117590
|
return new Date(at).toISOString().slice(11, 19);
|
|
117402
117591
|
}
|
|
117403
117592
|
function formatRemoteEventLines(status) {
|
|
117404
117593
|
if (status.events.length === 0)
|
|
117405
117594
|
return ["No remote events yet."];
|
|
117406
|
-
return [...status.events].reverse().slice(0, REMOTE_EVENT_LIMIT).map((event) => `${
|
|
117595
|
+
return [...status.events].reverse().slice(0, REMOTE_EVENT_LIMIT).map((event) => `${clock2(event.at)} ${event.kind.padEnd(8)} ${event.text}`);
|
|
117407
117596
|
}
|
|
117408
117597
|
function formatRemoteCommandLines(status) {
|
|
117409
117598
|
const commands = status.events.filter((event) => event.kind === "command");
|
|
117410
117599
|
if (commands.length === 0)
|
|
117411
117600
|
return ["No remote commands yet."];
|
|
117412
|
-
return [...commands].reverse().slice(0, REMOTE_EVENT_LIMIT).map((event) => `${
|
|
117601
|
+
return [...commands].reverse().slice(0, REMOTE_EVENT_LIMIT).map((event) => `${clock2(event.at)} ${event.text}`);
|
|
117413
117602
|
}
|
|
117414
117603
|
function renderRemoteControlText(status) {
|
|
117415
117604
|
return `${[...formatRemoteStatusLines(status), "", "Recent events:", ...formatRemoteEventLines(status)].join(`
|
|
@@ -117805,6 +117994,11 @@ var init_agent_commands = __esm(() => {
|
|
|
117805
117994
|
description: "Connect, test or disconnect Telegram for this machine: /channels [status]",
|
|
117806
117995
|
modes: AGENT_ONLY
|
|
117807
117996
|
},
|
|
117997
|
+
{
|
|
117998
|
+
name: "/rendering",
|
|
117999
|
+
description: "How Telegram replies are written: /rendering [auto|rich|html|plain]",
|
|
118000
|
+
modes: AGENT_ONLY
|
|
118001
|
+
},
|
|
117808
118002
|
{
|
|
117809
118003
|
name: "/product",
|
|
117810
118004
|
description: "Intents closed in code that nobody looked back at, with their outcome criteria",
|
|
@@ -137861,6 +138055,9 @@ async function runSettingsCommand(command, handlers) {
|
|
|
137861
138055
|
case "/external-agents":
|
|
137862
138056
|
handlers.onSystem(await handlers.externalAgents(arg));
|
|
137863
138057
|
return;
|
|
138058
|
+
case "/rendering":
|
|
138059
|
+
handlers.onSystem(handlers.rendering(arg));
|
|
138060
|
+
return;
|
|
137864
138061
|
case "/reasoning":
|
|
137865
138062
|
handlers.reasoning(arg);
|
|
137866
138063
|
return;
|
|
@@ -152860,6 +153057,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
152860
153057
|
route: setRoutingEnabled,
|
|
152861
153058
|
external: setExternalEnabled,
|
|
152862
153059
|
externalAgents: (arg) => runExternalAgentsCommand(arg, sessionCwd),
|
|
153060
|
+
rendering: (arg) => runRenderingCommand(arg),
|
|
152863
153061
|
reasoning: runReasoningCommand,
|
|
152864
153062
|
think: runThinkCommand,
|
|
152865
153063
|
theme: runThemeCommand,
|
|
@@ -153952,6 +154150,11 @@ ${formatThemeList(getThemeId())}`);
|
|
|
153952
154150
|
showEditGuard();
|
|
153953
154151
|
return;
|
|
153954
154152
|
}
|
|
154153
|
+
if (isRenderingCommand(command.name)) {
|
|
154154
|
+
const arg = line.trim().split(/\s+/).slice(1).join(" ");
|
|
154155
|
+
io.onSystem?.(runRenderingCommand(arg));
|
|
154156
|
+
return;
|
|
154157
|
+
}
|
|
153955
154158
|
if (isExternalAgentsCommand(command.name)) {
|
|
153956
154159
|
const arg = line.trim().split(/\s+/).slice(1).join(" ");
|
|
153957
154160
|
trackCommandWork(() => runExternalAgentsCommand(arg, opts.session?.cwd ?? process.cwd()).then((text) => io.onSystem?.(text)));
|
|
@@ -154791,6 +154994,7 @@ var init_tui_shell = __esm(() => {
|
|
|
154791
154994
|
init_turn_guard();
|
|
154792
154995
|
init_routing_classifier_source();
|
|
154793
154996
|
init_external_command();
|
|
154997
|
+
init_rendering_command();
|
|
154794
154998
|
init_external_switch();
|
|
154795
154999
|
init_external_providers();
|
|
154796
155000
|
init_jev_client();
|
|
@@ -157366,6 +157570,8 @@ ${GUTTER}${turnSeparator()}
|
|
|
157366
157570
|
agentIo.onSystem?.(readlineRemoteControlText(line));
|
|
157367
157571
|
} else if (isChannelsCommand(command)) {
|
|
157368
157572
|
agentIo.onSystem?.(readlineChannelsText(line, await loadChannelsSnapshot(new ChannelsClient)));
|
|
157573
|
+
} else if (isRenderingCommand(command)) {
|
|
157574
|
+
agentIo.onSystem?.(runRenderingCommand(rest));
|
|
157369
157575
|
} else if (isSessionInfoCommand(command)) {
|
|
157370
157576
|
const cwd = sessionCwd;
|
|
157371
157577
|
const [workspaces, flows] = await Promise.all([
|
|
@@ -158488,6 +158694,7 @@ var init_shell = __esm(() => {
|
|
|
158488
158694
|
init_statusbar();
|
|
158489
158695
|
init_metaproject_state();
|
|
158490
158696
|
init_live_render();
|
|
158697
|
+
init_rendering_command();
|
|
158491
158698
|
init_settings_model();
|
|
158492
158699
|
init_settings_state();
|
|
158493
158700
|
init_theme();
|
|
@@ -158568,6 +158775,7 @@ var init_shell = __esm(() => {
|
|
|
158568
158775
|
"/reviews",
|
|
158569
158776
|
"/remote-control",
|
|
158570
158777
|
"/channels",
|
|
158778
|
+
"/rendering",
|
|
158571
158779
|
"/theme",
|
|
158572
158780
|
"/settings",
|
|
158573
158781
|
"/mode",
|
|
@@ -181416,6 +181624,25 @@ var init_command_registry = __esm(() => {
|
|
|
181416
181624
|
"does not touch content already relayed to an agent, exported copies, or git history"
|
|
181417
181625
|
]
|
|
181418
181626
|
},
|
|
181627
|
+
{
|
|
181628
|
+
module: "remote",
|
|
181629
|
+
command: "remote format-sample",
|
|
181630
|
+
summary: "Print the fixed sample reply (a table, lists, a rule) the way each Telegram rendering mode sends it. No network, no token.",
|
|
181631
|
+
intent: [
|
|
181632
|
+
"\u043A\u0430\u043A \u0432\u044B\u0433\u043B\u044F\u0434\u0438\u0442 \u043E\u0442\u0432\u0435\u0442 \u0432 \u0442\u0435\u043B\u0435\u0433\u0440\u0430\u043C\u0435",
|
|
181633
|
+
"remote format-sample",
|
|
181634
|
+
"preview telegram rendering",
|
|
181635
|
+
"how are tables rendered in telegram",
|
|
181636
|
+
"show the telegram sample reply"
|
|
181637
|
+
],
|
|
181638
|
+
args: [
|
|
181639
|
+
{ name: "mode", type: "string", required: false, desc: "only this mode: auto, rich, html or plain" },
|
|
181640
|
+
{ name: "full", type: "bool", required: false, desc: "print the whole rich message instead of a count of its blocks" },
|
|
181641
|
+
{ name: "json", type: "bool", required: false, desc: "structured sample source and per-mode parts" }
|
|
181642
|
+
],
|
|
181643
|
+
json: true,
|
|
181644
|
+
read: true
|
|
181645
|
+
},
|
|
181419
181646
|
{
|
|
181420
181647
|
module: "bus",
|
|
181421
181648
|
command: "bus list",
|
|
@@ -192710,6 +192937,24 @@ function createHttpBotApi(options) {
|
|
|
192710
192937
|
}
|
|
192711
192938
|
return call("sendMessage", body);
|
|
192712
192939
|
},
|
|
192940
|
+
async sendRichMessage(params) {
|
|
192941
|
+
const body = { chat_id: params.chatId, rich_message: params.richMessage };
|
|
192942
|
+
if (params.messageThreadId !== undefined) {
|
|
192943
|
+
body.message_thread_id = params.messageThreadId;
|
|
192944
|
+
}
|
|
192945
|
+
if (params.inlineKeyboard !== undefined) {
|
|
192946
|
+
body.reply_markup = { inline_keyboard: params.inlineKeyboard };
|
|
192947
|
+
}
|
|
192948
|
+
return call("sendRichMessage", body);
|
|
192949
|
+
},
|
|
192950
|
+
async editRichMessage(params) {
|
|
192951
|
+
await call("editMessageText", {
|
|
192952
|
+
chat_id: params.chatId,
|
|
192953
|
+
message_id: params.messageId,
|
|
192954
|
+
rich_message: params.richMessage,
|
|
192955
|
+
reply_markup: { inline_keyboard: params.inlineKeyboard ?? [] }
|
|
192956
|
+
});
|
|
192957
|
+
},
|
|
192713
192958
|
async editMessageReplyMarkup(params) {
|
|
192714
192959
|
await call("editMessageReplyMarkup", {
|
|
192715
192960
|
chat_id: params.chatId,
|
|
@@ -192790,6 +193035,187 @@ var init_bot_api_http = __esm(() => {
|
|
|
192790
193035
|
init_types11();
|
|
192791
193036
|
});
|
|
192792
193037
|
|
|
193038
|
+
// src/remote/format-blocks.ts
|
|
193039
|
+
var HEADING2, BULLET, ORDERED, QUOTE, RULE, TASK, UNCHECKED_BOX = "\u2610", CHECKED_BOX = "\u2611", RULE_GLYPH = "\u2500", RULE_LENGTH = 12;
|
|
193040
|
+
var init_format_blocks = __esm(() => {
|
|
193041
|
+
HEADING2 = /^ {0,3}#{1,6}[ \t]+(.*?)(?:[ \t]+#+)?[ \t]*$/;
|
|
193042
|
+
BULLET = /^([ \t]*)[-*][ \t]+(.*)$/;
|
|
193043
|
+
ORDERED = /^([ \t]*)(\d{1,9})([.)])[ \t]+(.*)$/;
|
|
193044
|
+
QUOTE = /^ {0,3}>(?: (.*))?$/;
|
|
193045
|
+
RULE = /^ {0,3}-{3,}[ \t]*$/;
|
|
193046
|
+
TASK = /^\[([ xX])\](?:[ \t]+(.*))?$/;
|
|
193047
|
+
});
|
|
193048
|
+
|
|
193049
|
+
// src/remote/format-table.ts
|
|
193050
|
+
function splitRow(line) {
|
|
193051
|
+
const cells = [];
|
|
193052
|
+
let current = "";
|
|
193053
|
+
let sawPipe = false;
|
|
193054
|
+
for (let index = 0;index < line.length; index += 1) {
|
|
193055
|
+
const char = line[index];
|
|
193056
|
+
if (char === "\\" && line[index + 1] === "|") {
|
|
193057
|
+
current += "|";
|
|
193058
|
+
index += 1;
|
|
193059
|
+
continue;
|
|
193060
|
+
}
|
|
193061
|
+
if (char === "|") {
|
|
193062
|
+
sawPipe = true;
|
|
193063
|
+
cells.push(current);
|
|
193064
|
+
current = "";
|
|
193065
|
+
continue;
|
|
193066
|
+
}
|
|
193067
|
+
current += char;
|
|
193068
|
+
}
|
|
193069
|
+
if (!sawPipe) {
|
|
193070
|
+
return;
|
|
193071
|
+
}
|
|
193072
|
+
cells.push(current);
|
|
193073
|
+
if (cells[0]?.trim() === "") {
|
|
193074
|
+
cells.shift();
|
|
193075
|
+
}
|
|
193076
|
+
if (cells.length > 0 && cells[cells.length - 1]?.trim() === "") {
|
|
193077
|
+
cells.pop();
|
|
193078
|
+
}
|
|
193079
|
+
return cells.map((cell) => cell.trim());
|
|
193080
|
+
}
|
|
193081
|
+
function separatorAlign(line) {
|
|
193082
|
+
const cells = splitRow(line);
|
|
193083
|
+
if (cells === undefined || cells.length === 0) {
|
|
193084
|
+
return;
|
|
193085
|
+
}
|
|
193086
|
+
const align = [];
|
|
193087
|
+
for (const cell of cells) {
|
|
193088
|
+
const match = SEPARATOR_CELL.exec(cell);
|
|
193089
|
+
if (match === null) {
|
|
193090
|
+
return;
|
|
193091
|
+
}
|
|
193092
|
+
align.push(match[1] !== undefined && match[2] !== undefined ? "center" : match[2] !== undefined ? "right" : "left");
|
|
193093
|
+
}
|
|
193094
|
+
return align;
|
|
193095
|
+
}
|
|
193096
|
+
function endsTable(line) {
|
|
193097
|
+
return line.trim().length === 0 || !line.includes("|");
|
|
193098
|
+
}
|
|
193099
|
+
function fitRow(cells, width) {
|
|
193100
|
+
const row = cells.slice(0, width);
|
|
193101
|
+
while (row.length < width) {
|
|
193102
|
+
row.push("");
|
|
193103
|
+
}
|
|
193104
|
+
return row;
|
|
193105
|
+
}
|
|
193106
|
+
function tableAt(lines, start) {
|
|
193107
|
+
const headerLine = lines[start];
|
|
193108
|
+
const separatorLine = lines[start + 1];
|
|
193109
|
+
if (headerLine === undefined || separatorLine === undefined || endsTable(headerLine)) {
|
|
193110
|
+
return;
|
|
193111
|
+
}
|
|
193112
|
+
const header = splitRow(headerLine);
|
|
193113
|
+
const align = separatorAlign(separatorLine);
|
|
193114
|
+
if (header === undefined || align === undefined || header.length !== align.length || header.length > MAX_TABLE_COLUMNS) {
|
|
193115
|
+
return;
|
|
193116
|
+
}
|
|
193117
|
+
const rows = [];
|
|
193118
|
+
let end = start + 2;
|
|
193119
|
+
while (end < lines.length && !endsTable(lines[end])) {
|
|
193120
|
+
const cells = splitRow(lines[end]);
|
|
193121
|
+
if (cells === undefined) {
|
|
193122
|
+
break;
|
|
193123
|
+
}
|
|
193124
|
+
rows.push(fitRow(cells, header.length));
|
|
193125
|
+
end += 1;
|
|
193126
|
+
}
|
|
193127
|
+
return { table: { header, align, rows }, end };
|
|
193128
|
+
}
|
|
193129
|
+
function plainCell(text) {
|
|
193130
|
+
return text.replace(/\[([^\]]*)\]\((https?:\/\/[^\s)]*)\)/g, (_all, label, url) => label.trim().length > 0 ? `${label} (${url})` : url).replace(/(\*\*|__|~~)(.+?)\1/g, "$2").replace(/`+([^`]+)`+/g, "$1").replace(/<br\s*\/?>/gi, " ").trim();
|
|
193131
|
+
}
|
|
193132
|
+
function columnWidths(table) {
|
|
193133
|
+
return table.header.map((cell, column) => {
|
|
193134
|
+
let widest = visualWidth(plainCell(cell));
|
|
193135
|
+
for (const row of table.rows) {
|
|
193136
|
+
widest = Math.max(widest, visualWidth(plainCell(row[column])));
|
|
193137
|
+
}
|
|
193138
|
+
return widest;
|
|
193139
|
+
});
|
|
193140
|
+
}
|
|
193141
|
+
function pad4(text, width, align) {
|
|
193142
|
+
const gap = Math.max(0, width - visualWidth(text));
|
|
193143
|
+
if (align === "right") {
|
|
193144
|
+
return `${" ".repeat(gap)}${text}`;
|
|
193145
|
+
}
|
|
193146
|
+
if (align === "center") {
|
|
193147
|
+
const left = Math.floor(gap / 2);
|
|
193148
|
+
return `${" ".repeat(left)}${text}${" ".repeat(gap - left)}`;
|
|
193149
|
+
}
|
|
193150
|
+
return `${text}${" ".repeat(gap)}`;
|
|
193151
|
+
}
|
|
193152
|
+
function renderTableRow(cells, widths, align) {
|
|
193153
|
+
return cells.map((cell, column) => pad4(plainCell(cell), widths[column], align[column])).join(COLUMN_GAP).trimEnd();
|
|
193154
|
+
}
|
|
193155
|
+
function alignedCosts(table) {
|
|
193156
|
+
const widths = columnWidths(table);
|
|
193157
|
+
return { header: renderedRowCost(table.header, widths), rows: table.rows.map((row) => renderedRowCost(row, widths)) };
|
|
193158
|
+
}
|
|
193159
|
+
function tableLayout(table) {
|
|
193160
|
+
const costs = alignedCosts(table);
|
|
193161
|
+
return [costs.header, ...costs.rows].some((cost) => cost > MAX_TABLE_ROW_COST) ? "stacked" : "aligned";
|
|
193162
|
+
}
|
|
193163
|
+
function stackedRowLines(table, row) {
|
|
193164
|
+
const lines = [];
|
|
193165
|
+
row.forEach((cell, column) => {
|
|
193166
|
+
const text = plainCell(cell);
|
|
193167
|
+
if (text.length > 0) {
|
|
193168
|
+
lines.push(`${plainCell(table.header[column])}: ${text}`);
|
|
193169
|
+
}
|
|
193170
|
+
});
|
|
193171
|
+
return lines.length > 0 ? lines : ["(empty)"];
|
|
193172
|
+
}
|
|
193173
|
+
function renderStackedLines(table) {
|
|
193174
|
+
if (table.rows.length === 0) {
|
|
193175
|
+
return [table.header.map(plainCell).join(COLUMN_GAP).trim()];
|
|
193176
|
+
}
|
|
193177
|
+
return table.rows.flatMap((row, index) => index === 0 ? stackedRowLines(table, row) : ["", ...stackedRowLines(table, row)]);
|
|
193178
|
+
}
|
|
193179
|
+
function renderTableLines(table, widths) {
|
|
193180
|
+
if (widths === undefined && tableLayout(table) === "stacked") {
|
|
193181
|
+
return renderStackedLines(table);
|
|
193182
|
+
}
|
|
193183
|
+
const use = widths ?? columnWidths(table);
|
|
193184
|
+
return [table.header, ...table.rows].map((row) => renderTableRow(row, use, table.align));
|
|
193185
|
+
}
|
|
193186
|
+
function renderedRowCost(cells, widths) {
|
|
193187
|
+
let cost = 1;
|
|
193188
|
+
cells.forEach((cell, column) => {
|
|
193189
|
+
const text = plainCell(cell);
|
|
193190
|
+
cost += text.length + Math.max(0, widths[column] - visualWidth(text));
|
|
193191
|
+
if (column > 0) {
|
|
193192
|
+
cost += COLUMN_GAP.length;
|
|
193193
|
+
}
|
|
193194
|
+
});
|
|
193195
|
+
return cost;
|
|
193196
|
+
}
|
|
193197
|
+
function tableCosts(table) {
|
|
193198
|
+
const aligned = alignedCosts(table);
|
|
193199
|
+
if (tableLayout(table) === "aligned") {
|
|
193200
|
+
return aligned;
|
|
193201
|
+
}
|
|
193202
|
+
return {
|
|
193203
|
+
header: Math.min(aligned.header, MAX_TABLE_ROW_COST),
|
|
193204
|
+
rows: table.rows.map((row, index) => {
|
|
193205
|
+
let stacked = 1;
|
|
193206
|
+
stackedRowLines(table, row).forEach((line) => {
|
|
193207
|
+
stacked += line.length + 1;
|
|
193208
|
+
});
|
|
193209
|
+
return Math.max(stacked, Math.min(aligned.rows[index], MAX_TABLE_ROW_COST));
|
|
193210
|
+
})
|
|
193211
|
+
};
|
|
193212
|
+
}
|
|
193213
|
+
var MAX_TABLE_COLUMNS = 20, MAX_TABLE_ROW_COST = 1500, SEPARATOR_CELL, COLUMN_GAP = " \u2502 ";
|
|
193214
|
+
var init_format_table = __esm(() => {
|
|
193215
|
+
init_md_blocks();
|
|
193216
|
+
SEPARATOR_CELL = /^\s*(:)?-+(:)?\s*$/;
|
|
193217
|
+
});
|
|
193218
|
+
|
|
192793
193219
|
// src/remote/format.ts
|
|
192794
193220
|
function fenceOpening(line) {
|
|
192795
193221
|
const match = FENCE_LINE2.exec(line);
|
|
@@ -192837,13 +193263,40 @@ function cutAt(text, max) {
|
|
|
192837
193263
|
}
|
|
192838
193264
|
function toPieces(text, capacity) {
|
|
192839
193265
|
const pieces = [];
|
|
193266
|
+
const raws = text.split(/(?<=\n)/);
|
|
193267
|
+
const bare = raws.map((raw) => raw.endsWith(`
|
|
193268
|
+
`) ? raw.slice(0, -1) : raw);
|
|
192840
193269
|
let fence;
|
|
192841
|
-
for (
|
|
192842
|
-
const
|
|
192843
|
-
|
|
193270
|
+
for (let at = 0;at < raws.length; at += 1) {
|
|
193271
|
+
const raw = raws[at];
|
|
193272
|
+
const line = bare[at];
|
|
192844
193273
|
const before = fence;
|
|
192845
193274
|
if (fence === undefined) {
|
|
192846
193275
|
fence = openingFence(line);
|
|
193276
|
+
if (fence === undefined) {
|
|
193277
|
+
const found = tableAt(bare, at);
|
|
193278
|
+
if (found !== undefined) {
|
|
193279
|
+
const costs = tableCosts(found.table);
|
|
193280
|
+
const headerCost = costs.header;
|
|
193281
|
+
const widest = Math.max(...costs.rows, 0);
|
|
193282
|
+
if (headerCost + widest <= capacity) {
|
|
193283
|
+
const run = { headerSource: `${raw}${raws[at + 1]}`, headerCost };
|
|
193284
|
+
const rows = found.table.rows;
|
|
193285
|
+
const leadEnd = at + 2 + (rows.length > 0 ? 1 : 0);
|
|
193286
|
+
pieces.push({
|
|
193287
|
+
text: raws.slice(at, leadEnd).join(""),
|
|
193288
|
+
fence: undefined,
|
|
193289
|
+
cost: headerCost + (costs.rows[0] ?? 0),
|
|
193290
|
+
table: run
|
|
193291
|
+
});
|
|
193292
|
+
for (let row = leadEnd;row < found.end; row += 1) {
|
|
193293
|
+
pieces.push({ text: raws[row], fence: undefined, cost: costs.rows[row - at - 2], table: run });
|
|
193294
|
+
}
|
|
193295
|
+
at = found.end - 1;
|
|
193296
|
+
continue;
|
|
193297
|
+
}
|
|
193298
|
+
}
|
|
193299
|
+
}
|
|
192847
193300
|
} else if (closesFence(line, fence)) {
|
|
192848
193301
|
fence = undefined;
|
|
192849
193302
|
}
|
|
@@ -192851,24 +193304,33 @@ function toPieces(text, capacity) {
|
|
|
192851
193304
|
let rest = raw;
|
|
192852
193305
|
while (rest.length > room) {
|
|
192853
193306
|
const cut = cutAt(rest, room);
|
|
192854
|
-
pieces.push({ text: rest.slice(0, cut), fence: before ?? fence });
|
|
193307
|
+
pieces.push({ text: rest.slice(0, cut), fence: before ?? fence, cost: cut });
|
|
192855
193308
|
rest = rest.slice(cut);
|
|
192856
193309
|
}
|
|
192857
193310
|
if (rest.length > 0) {
|
|
192858
|
-
|
|
193311
|
+
const rule = before === undefined && fence === undefined && RULE.test(line);
|
|
193312
|
+
pieces.push({ text: rest, fence, cost: rule ? Math.max(rest.length, RULE_LENGTH + (rest.endsWith(`
|
|
193313
|
+
`) ? 1 : 0)) : rest.length });
|
|
192859
193314
|
}
|
|
192860
193315
|
}
|
|
192861
193316
|
return pieces;
|
|
192862
193317
|
}
|
|
193318
|
+
function renderedLength(text) {
|
|
193319
|
+
return toPieces(text.replace(/\r\n?/g, `
|
|
193320
|
+
`), Number.POSITIVE_INFINITY).reduce((sum, piece) => sum + piece.cost, 0);
|
|
193321
|
+
}
|
|
192863
193322
|
function pack(text, capacity) {
|
|
192864
193323
|
const parts = [];
|
|
192865
193324
|
let current = "";
|
|
193325
|
+
let used = 0;
|
|
192866
193326
|
let hasContent = false;
|
|
192867
193327
|
let open;
|
|
192868
|
-
|
|
193328
|
+
let lastTable;
|
|
193329
|
+
const finish = (next) => {
|
|
192869
193330
|
let body = current.trimEnd();
|
|
192870
193331
|
if (!hasContent || body.length === 0) {
|
|
192871
193332
|
current = "";
|
|
193333
|
+
used = 0;
|
|
192872
193334
|
hasContent = false;
|
|
192873
193335
|
return;
|
|
192874
193336
|
}
|
|
@@ -192879,6 +193341,11 @@ ${open.marker}`;
|
|
|
192879
193341
|
parts.push(body);
|
|
192880
193342
|
current = open === undefined ? "" : `${open.header}
|
|
192881
193343
|
`;
|
|
193344
|
+
used = open === undefined ? 0 : open.header.length + 1;
|
|
193345
|
+
if (open === undefined && next?.table !== undefined && next.table === lastTable) {
|
|
193346
|
+
current = next.table.headerSource;
|
|
193347
|
+
used = next.table.headerCost;
|
|
193348
|
+
}
|
|
192882
193349
|
hasContent = false;
|
|
192883
193350
|
};
|
|
192884
193351
|
for (const piece of toPieces(text, capacity)) {
|
|
@@ -192886,15 +193353,17 @@ ${open.marker}`;
|
|
|
192886
193353
|
continue;
|
|
192887
193354
|
}
|
|
192888
193355
|
const closing = piece.fence === undefined ? 0 : piece.fence.marker.length + 1;
|
|
192889
|
-
if (hasContent &&
|
|
192890
|
-
finish();
|
|
193356
|
+
if (hasContent && used + piece.cost + closing > capacity) {
|
|
193357
|
+
finish(piece);
|
|
192891
193358
|
if (piece.text.trim().length === 0) {
|
|
192892
193359
|
continue;
|
|
192893
193360
|
}
|
|
192894
193361
|
}
|
|
192895
193362
|
current += piece.text;
|
|
193363
|
+
used += piece.cost;
|
|
192896
193364
|
hasContent = true;
|
|
192897
193365
|
open = piece.fence;
|
|
193366
|
+
lastTable = piece.table;
|
|
192898
193367
|
}
|
|
192899
193368
|
finish();
|
|
192900
193369
|
return parts;
|
|
@@ -192908,7 +193377,7 @@ function formatReply(text, limit = TELEGRAM_MAX_TEXT) {
|
|
|
192908
193377
|
if (text.trim().length === 0) {
|
|
192909
193378
|
return [];
|
|
192910
193379
|
}
|
|
192911
|
-
if (text
|
|
193380
|
+
if (renderedLength(text) <= limit) {
|
|
192912
193381
|
return [text];
|
|
192913
193382
|
}
|
|
192914
193383
|
for (let digits = 1;digits <= 3; digits += 1) {
|
|
@@ -192929,10 +193398,101 @@ ${part}`);
|
|
|
192929
193398
|
}
|
|
192930
193399
|
var MIN_LIMIT = 64, MAX_FENCE_MARKER = 16, MAX_FENCE_INFO = 40, FENCE_LINE2;
|
|
192931
193400
|
var init_format2 = __esm(() => {
|
|
193401
|
+
init_format_blocks();
|
|
193402
|
+
init_format_table();
|
|
192932
193403
|
init_types11();
|
|
192933
193404
|
FENCE_LINE2 = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
192934
193405
|
});
|
|
192935
193406
|
|
|
193407
|
+
// src/remote/format-link.ts
|
|
193408
|
+
function parse(text) {
|
|
193409
|
+
try {
|
|
193410
|
+
const url = new URL(text);
|
|
193411
|
+
return { host: normalise3(url.hostname), userinfo: url.username !== "" || url.password !== "" };
|
|
193412
|
+
} catch {
|
|
193413
|
+
const authority = /^[a-z][a-z0-9+.-]*:\/\/([^/?#]*)/i.exec(text)?.[1] ?? text.split(/[/?#]/, 1)[0] ?? "";
|
|
193414
|
+
const at = authority.lastIndexOf("@");
|
|
193415
|
+
const host = (at < 0 ? authority : authority.slice(at + 1)).replace(/:\d*$/, "");
|
|
193416
|
+
return { host: normalise3(host), userinfo: at >= 0 };
|
|
193417
|
+
}
|
|
193418
|
+
}
|
|
193419
|
+
function normalise3(host) {
|
|
193420
|
+
return host.toLowerCase().replace(/\.$/, "").replace(/^www\./, "");
|
|
193421
|
+
}
|
|
193422
|
+
function bare(label) {
|
|
193423
|
+
return label.replace(/^[\s*_~`]+|[\s*_~`]+$/g, "");
|
|
193424
|
+
}
|
|
193425
|
+
function looksLikeAddress(label) {
|
|
193426
|
+
return SCHEME.test(label) || /^www\./i.test(label) || HOST_LIKE.test(label) && !FILE_EXTENSION.test(label);
|
|
193427
|
+
}
|
|
193428
|
+
function mismatchedHost(label, url) {
|
|
193429
|
+
const text = bare(label);
|
|
193430
|
+
if (!looksLikeAddress(text)) {
|
|
193431
|
+
return;
|
|
193432
|
+
}
|
|
193433
|
+
const target = parse(url);
|
|
193434
|
+
const shown = parse(SCHEME.test(text) ? text : `https://${text}`);
|
|
193435
|
+
if (target.host.length === 0) {
|
|
193436
|
+
return;
|
|
193437
|
+
}
|
|
193438
|
+
if (!target.userinfo && !shown.userinfo && shown.host === target.host) {
|
|
193439
|
+
return;
|
|
193440
|
+
}
|
|
193441
|
+
const room = Math.max(1, url.length - 1);
|
|
193442
|
+
return target.host.length > room ? `${target.host.slice(0, room - 1)}\u2026` : target.host;
|
|
193443
|
+
}
|
|
193444
|
+
function hostMarker(label, url) {
|
|
193445
|
+
const host = mismatchedHost(label, url);
|
|
193446
|
+
return host === undefined ? "" : ` (\u2192 ${host})`;
|
|
193447
|
+
}
|
|
193448
|
+
var SCHEME, HOST_LIKE, FILE_EXTENSION;
|
|
193449
|
+
var init_format_link = __esm(() => {
|
|
193450
|
+
SCHEME = /^https?:\/\//i;
|
|
193451
|
+
HOST_LIKE = /^[\p{L}\p{N}-]+(?:\.[\p{L}\p{N}-]+)*\.\p{L}{2,}(?::\d+)?(?:[/?#]\S*)?$/u;
|
|
193452
|
+
FILE_EXTENSION = /\.(?:md|mdx|txt|json|jsonc|ya?ml|toml|lock|log|csv|tsv|xml|html?|css|scss|less|[cm]?[jt]sx?|py|rb|rs|go|java|kt|swift|sh|zsh|bash|sql|png|jpe?g|gif|svg|webp|pdf|zip|tgz|gz|ini|env|conf|cfg)(?:[?#]\S*)?$/i;
|
|
193453
|
+
});
|
|
193454
|
+
|
|
193455
|
+
// src/remote/format-plain.ts
|
|
193456
|
+
function renderPlainText(part) {
|
|
193457
|
+
if (!part.includes("|")) {
|
|
193458
|
+
return part;
|
|
193459
|
+
}
|
|
193460
|
+
const lines = part.split(`
|
|
193461
|
+
`);
|
|
193462
|
+
const bare = lines.map((entry) => entry.replace(/\r$/, ""));
|
|
193463
|
+
const out = [];
|
|
193464
|
+
let fence;
|
|
193465
|
+
let changed = false;
|
|
193466
|
+
let index = 0;
|
|
193467
|
+
while (index < lines.length) {
|
|
193468
|
+
const line = lines[index];
|
|
193469
|
+
if (fence !== undefined) {
|
|
193470
|
+
if (closesFence(line, fence)) {
|
|
193471
|
+
fence = undefined;
|
|
193472
|
+
}
|
|
193473
|
+
out.push(line);
|
|
193474
|
+
index += 1;
|
|
193475
|
+
continue;
|
|
193476
|
+
}
|
|
193477
|
+
fence = fenceOpening(line);
|
|
193478
|
+
const found = fence === undefined ? tableAt(bare, index) : undefined;
|
|
193479
|
+
if (found === undefined) {
|
|
193480
|
+
out.push(line);
|
|
193481
|
+
index += 1;
|
|
193482
|
+
continue;
|
|
193483
|
+
}
|
|
193484
|
+
out.push(...renderTableLines(found.table));
|
|
193485
|
+
changed = true;
|
|
193486
|
+
index = found.end;
|
|
193487
|
+
}
|
|
193488
|
+
return changed ? out.join(`
|
|
193489
|
+
`) : part;
|
|
193490
|
+
}
|
|
193491
|
+
var init_format_plain = __esm(() => {
|
|
193492
|
+
init_format2();
|
|
193493
|
+
init_format_table();
|
|
193494
|
+
});
|
|
193495
|
+
|
|
192936
193496
|
// src/remote/format-html.ts
|
|
192937
193497
|
function escapeHtml2(text) {
|
|
192938
193498
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -193066,7 +193626,8 @@ function link(text, index, context) {
|
|
|
193066
193626
|
if (!HTTP_URL.test(url)) {
|
|
193067
193627
|
return;
|
|
193068
193628
|
}
|
|
193069
|
-
|
|
193629
|
+
const marker = escapeHtml2(hostMarker(label, url));
|
|
193630
|
+
return { html: `<a href="${escapeAttribute(url)}">${renderInline2(label, { ...context, link: true })}</a>${marker}`, end };
|
|
193070
193631
|
}
|
|
193071
193632
|
function renderInline2(text, context) {
|
|
193072
193633
|
let out = "";
|
|
@@ -193178,7 +193739,19 @@ function renderTelegramHtml(part) {
|
|
|
193178
193739
|
}
|
|
193179
193740
|
continue;
|
|
193180
193741
|
}
|
|
193742
|
+
const found = tableAt(lines, index);
|
|
193743
|
+
if (found !== undefined) {
|
|
193744
|
+
const text = escapeHtml2(renderTableLines(found.table).join(`
|
|
193745
|
+
`));
|
|
193746
|
+
push(tableLayout(found.table) === "aligned" ? `<pre>${text}</pre>` : text, true);
|
|
193747
|
+
index = found.end;
|
|
193748
|
+
continue;
|
|
193749
|
+
}
|
|
193181
193750
|
index += 1;
|
|
193751
|
+
if (RULE.test(line)) {
|
|
193752
|
+
push(RULE_GLYPH.repeat(RULE_LENGTH), false);
|
|
193753
|
+
continue;
|
|
193754
|
+
}
|
|
193182
193755
|
const heading = HEADING2.exec(line);
|
|
193183
193756
|
if (heading !== null && heading[1].trim().length > 0) {
|
|
193184
193757
|
push(`<b>${renderInline2(heading[1], { ...OUTSIDE, bold: true })}</b>`, false);
|
|
@@ -193186,6 +193759,13 @@ function renderTelegramHtml(part) {
|
|
|
193186
193759
|
}
|
|
193187
193760
|
const bullet = BULLET.exec(line);
|
|
193188
193761
|
if (bullet !== null) {
|
|
193762
|
+
const task = TASK.exec(bullet[2]);
|
|
193763
|
+
if (task !== null) {
|
|
193764
|
+
const box = task[1] === " " ? UNCHECKED_BOX : CHECKED_BOX;
|
|
193765
|
+
const label = renderInline2(task[2] ?? "", OUTSIDE);
|
|
193766
|
+
push(`${bullet[1]}${box}${label.length > 0 ? ` ${label}` : ""}`, false);
|
|
193767
|
+
continue;
|
|
193768
|
+
}
|
|
193189
193769
|
push(`${bullet[1]}\u2022 ${renderInline2(bullet[2], OUTSIDE)}`, false);
|
|
193190
193770
|
continue;
|
|
193191
193771
|
}
|
|
@@ -193193,6 +193773,93 @@ function renderTelegramHtml(part) {
|
|
|
193193
193773
|
}
|
|
193194
193774
|
return out;
|
|
193195
193775
|
}
|
|
193776
|
+
function unescapeEntities(text) {
|
|
193777
|
+
return text.replace(/&(amp|lt|gt|quot|#\d{1,7}|#x[0-9a-f]{1,6});/gi, (_all, name) => {
|
|
193778
|
+
switch (name.toLowerCase()) {
|
|
193779
|
+
case "amp":
|
|
193780
|
+
return "&";
|
|
193781
|
+
case "lt":
|
|
193782
|
+
return "<";
|
|
193783
|
+
case "gt":
|
|
193784
|
+
return ">";
|
|
193785
|
+
case "quot":
|
|
193786
|
+
return '"';
|
|
193787
|
+
default:
|
|
193788
|
+
return String.fromCodePoint(name[1] === "x" || name[1] === "X" ? Number.parseInt(name.slice(2), 16) : Number.parseInt(name.slice(1), 10));
|
|
193789
|
+
}
|
|
193790
|
+
});
|
|
193791
|
+
}
|
|
193792
|
+
function checkTelegramHtml(html) {
|
|
193793
|
+
const stack = [];
|
|
193794
|
+
let text = "";
|
|
193795
|
+
let index = 0;
|
|
193796
|
+
const fail = (reason) => ({ ok: false, reason: `can't parse entities: ${reason} at byte offset ${index}` });
|
|
193797
|
+
while (index < html.length) {
|
|
193798
|
+
const char = html[index];
|
|
193799
|
+
if (char === "&") {
|
|
193800
|
+
const entity = ENTITY.exec(html.slice(index, index + 12));
|
|
193801
|
+
if (entity === null) {
|
|
193802
|
+
return fail("unescaped &");
|
|
193803
|
+
}
|
|
193804
|
+
text += unescapeEntities(entity[0]);
|
|
193805
|
+
index += entity[0].length;
|
|
193806
|
+
continue;
|
|
193807
|
+
}
|
|
193808
|
+
if (char === ">") {
|
|
193809
|
+
return fail("unescaped >");
|
|
193810
|
+
}
|
|
193811
|
+
if (char !== "<") {
|
|
193812
|
+
text += char;
|
|
193813
|
+
index += 1;
|
|
193814
|
+
continue;
|
|
193815
|
+
}
|
|
193816
|
+
const match = TAG.exec(html.slice(index));
|
|
193817
|
+
if (match === null) {
|
|
193818
|
+
return fail("unsupported start tag");
|
|
193819
|
+
}
|
|
193820
|
+
const closing = match[1] === "/";
|
|
193821
|
+
const name = match[2].toLowerCase();
|
|
193822
|
+
const attributes = match[3].trim();
|
|
193823
|
+
const top = stack[stack.length - 1];
|
|
193824
|
+
if (closing) {
|
|
193825
|
+
if (attributes.length > 0 || top === undefined || top.name !== name) {
|
|
193826
|
+
return fail(`can't find start tag for </${name}>`);
|
|
193827
|
+
}
|
|
193828
|
+
stack.pop();
|
|
193829
|
+
index += match[0].length;
|
|
193830
|
+
continue;
|
|
193831
|
+
}
|
|
193832
|
+
const known = SIMPLE_TAGS.has(name) || ["a", "code", "pre", "span", "blockquote"].includes(name);
|
|
193833
|
+
if (!known) {
|
|
193834
|
+
return fail(`unsupported start tag "${name}"`);
|
|
193835
|
+
}
|
|
193836
|
+
if (stack.some((entry) => entry.name === "code") || top?.name === "pre" && name !== "code") {
|
|
193837
|
+
return fail(`<${name}> inside code or pre`);
|
|
193838
|
+
}
|
|
193839
|
+
if (name === "code" || name === "pre") {
|
|
193840
|
+
const holder = stack.find((entry) => entry.name !== "blockquote" && !(name === "code" && entry === top && entry.name === "pre"));
|
|
193841
|
+
if (holder !== undefined) {
|
|
193842
|
+
return fail(`<${name}> inside <${holder.name}>`);
|
|
193843
|
+
}
|
|
193844
|
+
}
|
|
193845
|
+
if (name === "pre" && stack.some((entry) => entry.name !== "blockquote")) {
|
|
193846
|
+
return fail("pre inside another entity");
|
|
193847
|
+
}
|
|
193848
|
+
if (name === "blockquote" && stack.some((entry) => entry.name === "blockquote") || name === "a" && stack.some((entry) => entry.name === "a")) {
|
|
193849
|
+
return fail(`<${name}> nested in itself`);
|
|
193850
|
+
}
|
|
193851
|
+
const attributeOk = SIMPLE_TAGS.has(name) || name === "pre" ? attributes.length === 0 : name === "a" ? /^href="[^"<>]+"$/.test(attributes) : name === "code" ? attributes.length === 0 || /^class="language-[A-Za-z0-9_+#.-]+"$/.test(attributes) : name === "span" ? attributes === 'class="tg-spoiler"' : attributes.length === 0 || attributes === "expandable";
|
|
193852
|
+
if (!attributeOk) {
|
|
193853
|
+
return fail(`bad attributes on <${name}>`);
|
|
193854
|
+
}
|
|
193855
|
+
stack.push({ name });
|
|
193856
|
+
index += match[0].length;
|
|
193857
|
+
}
|
|
193858
|
+
if (stack.length > 0) {
|
|
193859
|
+
return fail(`can't find end tag for <${stack[stack.length - 1].name}>`);
|
|
193860
|
+
}
|
|
193861
|
+
return { ok: true, text };
|
|
193862
|
+
}
|
|
193196
193863
|
function isEntityParseError(error) {
|
|
193197
193864
|
return isBotApiError(error) && error.kind === "rejected" && error.status === 400 && /can't parse entities/i.test(error.message);
|
|
193198
193865
|
}
|
|
@@ -193207,7 +193874,7 @@ async function sendHtml(api, params, onFallback) {
|
|
|
193207
193874
|
try {
|
|
193208
193875
|
onFallback?.(error);
|
|
193209
193876
|
} catch {}
|
|
193210
|
-
return api.sendMessage(plain);
|
|
193877
|
+
return api.sendMessage({ ...plain, text: renderPlainText(plain.text) });
|
|
193211
193878
|
}
|
|
193212
193879
|
}
|
|
193213
193880
|
async function editHtml(api, params, onFallback) {
|
|
@@ -193220,15 +193887,19 @@ async function editHtml(api, params, onFallback) {
|
|
|
193220
193887
|
try {
|
|
193221
193888
|
onFallback?.(error);
|
|
193222
193889
|
} catch {}
|
|
193223
|
-
await api.editMessageText(params);
|
|
193890
|
+
await api.editMessageText({ ...params, text: renderPlainText(params.text) });
|
|
193224
193891
|
}
|
|
193225
193892
|
}
|
|
193226
193893
|
function isNotModified(error) {
|
|
193227
193894
|
return isBotApiError(error) && error.kind === "rejected" && /message is not modified/i.test(error.message);
|
|
193228
193895
|
}
|
|
193229
|
-
var OUTSIDE, WORD, ALNUM, SPACE, SINGLE_OPEN_BAD, SINGLE_CLOSE_BAD, HTTP_URL, LANGUAGE, EXPANDABLE_QUOTE_LINES = 8, TAGS2,
|
|
193896
|
+
var OUTSIDE, WORD, ALNUM, SPACE, SINGLE_OPEN_BAD, SINGLE_CLOSE_BAD, HTTP_URL, LANGUAGE, EXPANDABLE_QUOTE_LINES = 8, TAGS2, SIMPLE_TAGS, TAG, ENTITY;
|
|
193230
193897
|
var init_format_html = __esm(() => {
|
|
193231
193898
|
init_format2();
|
|
193899
|
+
init_format_blocks();
|
|
193900
|
+
init_format_link();
|
|
193901
|
+
init_format_plain();
|
|
193902
|
+
init_format_table();
|
|
193232
193903
|
init_types11();
|
|
193233
193904
|
OUTSIDE = { bold: false, italic: false, strike: false, link: false };
|
|
193234
193905
|
WORD = /[\p{L}\p{N}_]/u;
|
|
@@ -193239,10 +193910,9 @@ var init_format_html = __esm(() => {
|
|
|
193239
193910
|
HTTP_URL = /^https?:\/\/[^\s<>"'`]+$/i;
|
|
193240
193911
|
LANGUAGE = /^[A-Za-z0-9_+#.-]{1,32}$/;
|
|
193241
193912
|
TAGS2 = { bold: "b", italic: "i", strike: "s" };
|
|
193242
|
-
HEADING2 = /^ {0,3}#{1,6}[ \t]+(.*?)(?:[ \t]+#+)?[ \t]*$/;
|
|
193243
|
-
BULLET = /^([ \t]*)[-*][ \t]+(.*)$/;
|
|
193244
|
-
QUOTE = /^ {0,3}>(?: (.*))?$/;
|
|
193245
193913
|
SIMPLE_TAGS = new Set(["b", "strong", "i", "em", "u", "ins", "s", "strike", "del"]);
|
|
193914
|
+
TAG = /^<(\/?)([a-z][a-z-]*)((?:\s+[a-z-]+(?:="[^"<>]*")?)*)\s*>/i;
|
|
193915
|
+
ENTITY = /^&(?:amp|lt|gt|quot|#\d{1,7}|#x[0-9a-f]{1,6});/i;
|
|
193246
193916
|
});
|
|
193247
193917
|
|
|
193248
193918
|
// src/remote/durable-log.ts
|
|
@@ -193559,6 +194229,512 @@ var init_journal3 = __esm(() => {
|
|
|
193559
194229
|
init_paths4();
|
|
193560
194230
|
});
|
|
193561
194231
|
|
|
194232
|
+
// src/remote/rich-types.ts
|
|
194233
|
+
var RICH_LIMITS;
|
|
194234
|
+
var init_rich_types = __esm(() => {
|
|
194235
|
+
RICH_LIMITS = {
|
|
194236
|
+
characters: 32768,
|
|
194237
|
+
blocks: 500,
|
|
194238
|
+
nesting: 16,
|
|
194239
|
+
tableColumns: 20
|
|
194240
|
+
};
|
|
194241
|
+
});
|
|
194242
|
+
|
|
194243
|
+
// src/remote/format-rich.ts
|
|
194244
|
+
function unescape(text) {
|
|
194245
|
+
return text.replace(/&(amp|lt|gt|quot);/g, (_all, name) => ({ amp: "&", lt: "<", gt: ">", quot: '"' })[name]);
|
|
194246
|
+
}
|
|
194247
|
+
function collapse(children) {
|
|
194248
|
+
const merged = [];
|
|
194249
|
+
for (const child of children) {
|
|
194250
|
+
const last = merged[merged.length - 1];
|
|
194251
|
+
if (typeof child === "string" && typeof last === "string") {
|
|
194252
|
+
merged[merged.length - 1] = last + child;
|
|
194253
|
+
} else {
|
|
194254
|
+
merged.push(child);
|
|
194255
|
+
}
|
|
194256
|
+
}
|
|
194257
|
+
if (merged.length === 0) {
|
|
194258
|
+
return "";
|
|
194259
|
+
}
|
|
194260
|
+
return merged.length === 1 ? merged[0] : merged;
|
|
194261
|
+
}
|
|
194262
|
+
function htmlToRichText(html) {
|
|
194263
|
+
const root = { type: "bold", children: [] };
|
|
194264
|
+
const stack = [root];
|
|
194265
|
+
const token = /<(\/?)(b|i|s|code|a)(?: href="([^"]*)")?>|([^<]+)/g;
|
|
194266
|
+
for (let match = token.exec(html);match !== null; match = token.exec(html)) {
|
|
194267
|
+
const top = stack[stack.length - 1];
|
|
194268
|
+
if (match[4] !== undefined) {
|
|
194269
|
+
top.children.push(unescape(match[4]));
|
|
194270
|
+
} else if (match[1] === "/") {
|
|
194271
|
+
const closed = stack.pop();
|
|
194272
|
+
const text = collapse(closed.children);
|
|
194273
|
+
const node = closed.type === "url" ? { type: "url", text, url: closed.url } : { type: closed.type, text };
|
|
194274
|
+
stack[stack.length - 1].children.push(node);
|
|
194275
|
+
} else {
|
|
194276
|
+
stack.push({ type: TAG_TYPES[match[2]], ...match[3] === undefined ? {} : { url: unescape(match[3]) }, children: [] });
|
|
194277
|
+
}
|
|
194278
|
+
}
|
|
194279
|
+
return collapse(root.children);
|
|
194280
|
+
}
|
|
194281
|
+
function inline(text) {
|
|
194282
|
+
return htmlToRichText(renderInline2(text, OUTSIDE));
|
|
194283
|
+
}
|
|
194284
|
+
function isEmpty(text) {
|
|
194285
|
+
return text === "" || Array.isArray(text) && text.length === 0;
|
|
194286
|
+
}
|
|
194287
|
+
function indentOf2(whitespace) {
|
|
194288
|
+
return whitespace.replace(/\t/g, " ").length;
|
|
194289
|
+
}
|
|
194290
|
+
function listEntry(line) {
|
|
194291
|
+
const bullet = BULLET.exec(line);
|
|
194292
|
+
if (bullet !== null) {
|
|
194293
|
+
const task = TASK.exec(bullet[2]);
|
|
194294
|
+
return {
|
|
194295
|
+
indent: indentOf2(bullet[1]),
|
|
194296
|
+
ordered: false,
|
|
194297
|
+
...task === null ? {} : { checkbox: task[1] === " " ? "open" : "done" },
|
|
194298
|
+
text: task === null ? bullet[2] : task[2] ?? ""
|
|
194299
|
+
};
|
|
194300
|
+
}
|
|
194301
|
+
const ordered = ORDERED.exec(line);
|
|
194302
|
+
if (ordered !== null) {
|
|
194303
|
+
return { indent: indentOf2(ordered[1]), ordered: true, number: Number.parseInt(ordered[2], 10), text: ordered[4] };
|
|
194304
|
+
}
|
|
194305
|
+
return;
|
|
194306
|
+
}
|
|
194307
|
+
function listItem(entry) {
|
|
194308
|
+
const text = inline(entry.text);
|
|
194309
|
+
return {
|
|
194310
|
+
blocks: [{ type: "paragraph", text: isEmpty(text) ? " " : text }],
|
|
194311
|
+
...entry.checkbox === undefined ? {} : { has_checkbox: true, ...entry.checkbox === "done" ? { is_checked: true } : {} },
|
|
194312
|
+
...entry.ordered ? { value: entry.number, type: "1" } : {}
|
|
194313
|
+
};
|
|
194314
|
+
}
|
|
194315
|
+
function buildLists(entries) {
|
|
194316
|
+
const root = [];
|
|
194317
|
+
const stack = [];
|
|
194318
|
+
for (const entry of entries) {
|
|
194319
|
+
while (stack.length > 0 && stack[stack.length - 1].indent > entry.indent) {
|
|
194320
|
+
stack.pop();
|
|
194321
|
+
}
|
|
194322
|
+
let top = stack[stack.length - 1];
|
|
194323
|
+
let parent;
|
|
194324
|
+
if (top !== undefined && top.indent === entry.indent && top.ordered !== entry.ordered) {
|
|
194325
|
+
parent = top.parent;
|
|
194326
|
+
stack.pop();
|
|
194327
|
+
top = stack[stack.length - 1];
|
|
194328
|
+
}
|
|
194329
|
+
if (top === undefined || top.indent < entry.indent || parent !== undefined) {
|
|
194330
|
+
const holder = parent ?? (top === undefined ? root : top.items[top.items.length - 1].blocks);
|
|
194331
|
+
const frame = { indent: entry.indent, ordered: entry.ordered, items: [], parent: holder };
|
|
194332
|
+
holder.push({ type: "list", items: frame.items });
|
|
194333
|
+
stack.push(frame);
|
|
194334
|
+
top = frame;
|
|
194335
|
+
}
|
|
194336
|
+
top.items.push(listItem(entry));
|
|
194337
|
+
}
|
|
194338
|
+
return root;
|
|
194339
|
+
}
|
|
194340
|
+
function countBlocks(blocks, depth) {
|
|
194341
|
+
let count = 0;
|
|
194342
|
+
let deepest = depth;
|
|
194343
|
+
for (const block of blocks) {
|
|
194344
|
+
count += 1;
|
|
194345
|
+
if (block.type === "list") {
|
|
194346
|
+
for (const item of block.items) {
|
|
194347
|
+
count += 1;
|
|
194348
|
+
const inner = countBlocks(item.blocks, depth + 2);
|
|
194349
|
+
count += inner.count;
|
|
194350
|
+
deepest = Math.max(deepest, inner.depth);
|
|
194351
|
+
}
|
|
194352
|
+
} else if (block.type === "blockquote") {
|
|
194353
|
+
const inner = countBlocks(block.blocks, depth + 1);
|
|
194354
|
+
count += inner.count;
|
|
194355
|
+
deepest = Math.max(deepest, inner.depth);
|
|
194356
|
+
} else if (block.type === "table") {
|
|
194357
|
+
count += block.cells.length;
|
|
194358
|
+
}
|
|
194359
|
+
}
|
|
194360
|
+
return { count, depth: deepest };
|
|
194361
|
+
}
|
|
194362
|
+
function containsTable(part) {
|
|
194363
|
+
const lines = part.replace(/\r\n?/g, `
|
|
194364
|
+
`).split(`
|
|
194365
|
+
`);
|
|
194366
|
+
let fence;
|
|
194367
|
+
for (let index = 0;index < lines.length; index += 1) {
|
|
194368
|
+
const line = lines[index];
|
|
194369
|
+
if (fence !== undefined) {
|
|
194370
|
+
if (closesFence(line, fence)) {
|
|
194371
|
+
fence = undefined;
|
|
194372
|
+
}
|
|
194373
|
+
continue;
|
|
194374
|
+
}
|
|
194375
|
+
fence = fenceOpening(line);
|
|
194376
|
+
if (fence === undefined && tableAt(lines, index) !== undefined) {
|
|
194377
|
+
return true;
|
|
194378
|
+
}
|
|
194379
|
+
}
|
|
194380
|
+
return false;
|
|
194381
|
+
}
|
|
194382
|
+
function tableBlock(table) {
|
|
194383
|
+
const cell = (text, column, header) => {
|
|
194384
|
+
const content = inline(text.replace(/<br\s*\/?>/gi, " ").trim());
|
|
194385
|
+
return {
|
|
194386
|
+
...isEmpty(content) ? {} : { text: content },
|
|
194387
|
+
...header ? { is_header: true } : {},
|
|
194388
|
+
align: table.align[column],
|
|
194389
|
+
valign: "top"
|
|
194390
|
+
};
|
|
194391
|
+
};
|
|
194392
|
+
return {
|
|
194393
|
+
type: "table",
|
|
194394
|
+
is_bordered: true,
|
|
194395
|
+
cells: [table.header.map((text, column) => cell(text, column, true)), ...table.rows.map((row) => row.map((text, column) => cell(text, column, false)))]
|
|
194396
|
+
};
|
|
194397
|
+
}
|
|
194398
|
+
function renderRichMessage(part) {
|
|
194399
|
+
const lines = part.replace(/\r\n?/g, `
|
|
194400
|
+
`).split(`
|
|
194401
|
+
`);
|
|
194402
|
+
const blocks = [];
|
|
194403
|
+
let index = 0;
|
|
194404
|
+
while (index < lines.length) {
|
|
194405
|
+
const line = lines[index];
|
|
194406
|
+
const fence = fenceOpening(line);
|
|
194407
|
+
if (fence !== undefined) {
|
|
194408
|
+
const body = [];
|
|
194409
|
+
let cursor = index + 1;
|
|
194410
|
+
while (cursor < lines.length && !closesFence(lines[cursor], fence)) {
|
|
194411
|
+
body.push(lines[cursor]);
|
|
194412
|
+
cursor += 1;
|
|
194413
|
+
}
|
|
194414
|
+
const closed = cursor < lines.length;
|
|
194415
|
+
const code = body.join(`
|
|
194416
|
+
`);
|
|
194417
|
+
if (code.trim().length === 0) {
|
|
194418
|
+
for (const raw of lines.slice(index, closed ? cursor + 1 : cursor)) {
|
|
194419
|
+
blocks.push({ type: "paragraph", text: raw });
|
|
194420
|
+
}
|
|
194421
|
+
} else {
|
|
194422
|
+
const language = fence.info.split(/\s+/)[0];
|
|
194423
|
+
blocks.push({ type: "pre", text: code, ...LANGUAGE2.test(language) ? { language } : {} });
|
|
194424
|
+
}
|
|
194425
|
+
index = closed ? cursor + 1 : cursor;
|
|
194426
|
+
continue;
|
|
194427
|
+
}
|
|
194428
|
+
if (QUOTE.test(line)) {
|
|
194429
|
+
const quoted = [];
|
|
194430
|
+
const start = index;
|
|
194431
|
+
while (index < lines.length) {
|
|
194432
|
+
const next = QUOTE.exec(lines[index]);
|
|
194433
|
+
if (next === null) {
|
|
194434
|
+
break;
|
|
194435
|
+
}
|
|
194436
|
+
quoted.push(next[1] ?? "");
|
|
194437
|
+
index += 1;
|
|
194438
|
+
}
|
|
194439
|
+
if (quoted.every((entry) => entry.trim().length === 0)) {
|
|
194440
|
+
for (const raw of lines.slice(start, index)) {
|
|
194441
|
+
blocks.push({ type: "paragraph", text: raw });
|
|
194442
|
+
}
|
|
194443
|
+
} else if (quoted.length >= EXPANDABLE_QUOTE_LINES2) {
|
|
194444
|
+
const text = [];
|
|
194445
|
+
quoted.forEach((entry, position) => {
|
|
194446
|
+
if (position > 0) {
|
|
194447
|
+
text.push(`
|
|
194448
|
+
`);
|
|
194449
|
+
}
|
|
194450
|
+
text.push(inline(entry));
|
|
194451
|
+
});
|
|
194452
|
+
blocks.push({ type: "expandable_blockquote", text });
|
|
194453
|
+
} else {
|
|
194454
|
+
blocks.push({
|
|
194455
|
+
type: "blockquote",
|
|
194456
|
+
blocks: quoted.flatMap((entry) => {
|
|
194457
|
+
const text = inline(entry);
|
|
194458
|
+
return isEmpty(text) ? [] : [{ type: "paragraph", text }];
|
|
194459
|
+
})
|
|
194460
|
+
});
|
|
194461
|
+
}
|
|
194462
|
+
continue;
|
|
194463
|
+
}
|
|
194464
|
+
const found = tableAt(lines, index);
|
|
194465
|
+
if (found !== undefined) {
|
|
194466
|
+
blocks.push(tableBlock(found.table));
|
|
194467
|
+
index = found.end;
|
|
194468
|
+
continue;
|
|
194469
|
+
}
|
|
194470
|
+
if (listEntry(line) !== undefined && !RULE.test(line)) {
|
|
194471
|
+
const entries = [];
|
|
194472
|
+
while (index < lines.length) {
|
|
194473
|
+
const entry = listEntry(lines[index]);
|
|
194474
|
+
if (entry === undefined || RULE.test(lines[index])) {
|
|
194475
|
+
break;
|
|
194476
|
+
}
|
|
194477
|
+
entries.push(entry);
|
|
194478
|
+
index += 1;
|
|
194479
|
+
}
|
|
194480
|
+
blocks.push(...buildLists(entries));
|
|
194481
|
+
continue;
|
|
194482
|
+
}
|
|
194483
|
+
index += 1;
|
|
194484
|
+
if (RULE.test(line)) {
|
|
194485
|
+
blocks.push({ type: "divider" });
|
|
194486
|
+
continue;
|
|
194487
|
+
}
|
|
194488
|
+
const heading = HEADING2.exec(line);
|
|
194489
|
+
if (heading !== null && heading[1].trim().length > 0) {
|
|
194490
|
+
const level = line.trimStart().match(/^#+/)[0].length;
|
|
194491
|
+
blocks.push({ type: "heading", text: inline(heading[1]), size: Math.min(level + 1, 6) });
|
|
194492
|
+
continue;
|
|
194493
|
+
}
|
|
194494
|
+
const text = inline(line);
|
|
194495
|
+
if (!isEmpty(text) && line.trim().length > 0) {
|
|
194496
|
+
blocks.push({ type: "paragraph", text });
|
|
194497
|
+
}
|
|
194498
|
+
}
|
|
194499
|
+
const measured = countBlocks(blocks, 1);
|
|
194500
|
+
if (blocks.length === 0) {
|
|
194501
|
+
throw new RichRenderError("nothing to send as a rich message");
|
|
194502
|
+
}
|
|
194503
|
+
if (measured.count > RICH_LIMITS.blocks) {
|
|
194504
|
+
throw new RichRenderError(`${measured.count} blocks, over the limit of ${RICH_LIMITS.blocks}`);
|
|
194505
|
+
}
|
|
194506
|
+
if (measured.depth > RICH_LIMITS.nesting) {
|
|
194507
|
+
throw new RichRenderError(`${measured.depth} nesting levels, over the limit of ${RICH_LIMITS.nesting}`);
|
|
194508
|
+
}
|
|
194509
|
+
return { blocks };
|
|
194510
|
+
}
|
|
194511
|
+
var RichRenderError, LANGUAGE2, EXPANDABLE_QUOTE_LINES2 = 8, TAG_TYPES;
|
|
194512
|
+
var init_format_rich = __esm(() => {
|
|
194513
|
+
init_format_blocks();
|
|
194514
|
+
init_format2();
|
|
194515
|
+
init_format_html();
|
|
194516
|
+
init_format_table();
|
|
194517
|
+
init_rich_types();
|
|
194518
|
+
RichRenderError = class RichRenderError extends Error {
|
|
194519
|
+
constructor(message) {
|
|
194520
|
+
super(message);
|
|
194521
|
+
this.name = "RichRenderError";
|
|
194522
|
+
}
|
|
194523
|
+
};
|
|
194524
|
+
LANGUAGE2 = /^[A-Za-z0-9_+#.-]{1,32}$/;
|
|
194525
|
+
TAG_TYPES = { b: "bold", i: "italic", s: "strikethrough", code: "code", a: "url" };
|
|
194526
|
+
});
|
|
194527
|
+
|
|
194528
|
+
// src/remote/rendering.ts
|
|
194529
|
+
function capReason(reason) {
|
|
194530
|
+
return reason.length > MAX_REASON_LENGTH2 ? `${reason.slice(0, MAX_REASON_LENGTH2 - 1)}\u2026` : reason;
|
|
194531
|
+
}
|
|
194532
|
+
|
|
194533
|
+
class RenderingState {
|
|
194534
|
+
now;
|
|
194535
|
+
readMode;
|
|
194536
|
+
last;
|
|
194537
|
+
pausedUntil = 0;
|
|
194538
|
+
pauseReason;
|
|
194539
|
+
serverKey;
|
|
194540
|
+
serverFailures = 0;
|
|
194541
|
+
constructor(options = {}) {
|
|
194542
|
+
this.now = options.now ?? Date.now;
|
|
194543
|
+
this.readMode = options.mode ?? (() => DEFAULT_RENDER_MODE);
|
|
194544
|
+
}
|
|
194545
|
+
mode() {
|
|
194546
|
+
try {
|
|
194547
|
+
return this.readMode();
|
|
194548
|
+
} catch {
|
|
194549
|
+
return DEFAULT_RENDER_MODE;
|
|
194550
|
+
}
|
|
194551
|
+
}
|
|
194552
|
+
lastFallback() {
|
|
194553
|
+
return this.last;
|
|
194554
|
+
}
|
|
194555
|
+
record(step, reason) {
|
|
194556
|
+
this.last = { step, reason: capReason(redactSensitiveText(reason)), at: this.now() };
|
|
194557
|
+
return this.last;
|
|
194558
|
+
}
|
|
194559
|
+
pauseRich(reason) {
|
|
194560
|
+
this.pausedUntil = this.now() + RICH_PAUSE_MS;
|
|
194561
|
+
this.pauseReason = capReason(redactSensitiveText(reason));
|
|
194562
|
+
}
|
|
194563
|
+
richServerFailure(key) {
|
|
194564
|
+
this.serverFailures = this.serverKey === key ? this.serverFailures + 1 : 1;
|
|
194565
|
+
this.serverKey = key;
|
|
194566
|
+
return this.serverFailures;
|
|
194567
|
+
}
|
|
194568
|
+
richServerReset() {
|
|
194569
|
+
this.serverKey = undefined;
|
|
194570
|
+
this.serverFailures = 0;
|
|
194571
|
+
}
|
|
194572
|
+
richPausedReason() {
|
|
194573
|
+
if (this.pausedUntil > this.now()) {
|
|
194574
|
+
return this.pauseReason ?? "rich messages are paused";
|
|
194575
|
+
}
|
|
194576
|
+
return;
|
|
194577
|
+
}
|
|
194578
|
+
snapshot() {
|
|
194579
|
+
return {
|
|
194580
|
+
mode: this.mode(),
|
|
194581
|
+
...this.last === undefined ? {} : { lastFallback: this.last },
|
|
194582
|
+
...this.pausedUntil > this.now() ? { richPausedUntil: this.pausedUntil } : {}
|
|
194583
|
+
};
|
|
194584
|
+
}
|
|
194585
|
+
}
|
|
194586
|
+
function firstStep(mode, text) {
|
|
194587
|
+
if (mode === "auto") {
|
|
194588
|
+
return containsTable(text) ? "rich" : "html";
|
|
194589
|
+
}
|
|
194590
|
+
return mode;
|
|
194591
|
+
}
|
|
194592
|
+
function descriptionOf(error) {
|
|
194593
|
+
return error.message.replace(/^\w+:\s*(?:\d{3}\s*)?/, "");
|
|
194594
|
+
}
|
|
194595
|
+
function describe(error) {
|
|
194596
|
+
if (!isBotApiError(error)) {
|
|
194597
|
+
return capReason(redactSensitiveText(error instanceof Error ? error.message : String(error)));
|
|
194598
|
+
}
|
|
194599
|
+
const text = redactSensitiveText(descriptionOf(error));
|
|
194600
|
+
const cut = text.search(/["\u201c\u00ab`]/);
|
|
194601
|
+
const head = (cut < 0 ? text : text.slice(0, cut)).replace(/[\s:,(]+$/, "");
|
|
194602
|
+
return capReason(error.status === undefined ? head : `${error.status} ${head}`);
|
|
194603
|
+
}
|
|
194604
|
+
function isRefusal(error) {
|
|
194605
|
+
return isBotApiError(error) && error.kind === "rejected";
|
|
194606
|
+
}
|
|
194607
|
+
function isStanding(error) {
|
|
194608
|
+
if (!isBotApiError(error) || error.kind !== "rejected") {
|
|
194609
|
+
return false;
|
|
194610
|
+
}
|
|
194611
|
+
const text = descriptionOf(error);
|
|
194612
|
+
if (error.status === 404 || error.status === 405) {
|
|
194613
|
+
return MENTIONS_RICH_METHOD.test(text) || !MENTIONS_CHAT.test(text);
|
|
194614
|
+
}
|
|
194615
|
+
return error.status === 403 && MENTIONS_RICH_METHOD.test(text);
|
|
194616
|
+
}
|
|
194617
|
+
function isChatScoped(error) {
|
|
194618
|
+
if (!isBotApiError(error) || error.kind !== "rejected" || isStanding(error)) {
|
|
194619
|
+
return false;
|
|
194620
|
+
}
|
|
194621
|
+
const text = descriptionOf(error);
|
|
194622
|
+
return !MENTIONS_RICH_METHOD.test(text) && (error.status === 403 || (error.status === 400 || error.status === 404) && MENTIONS_CHAT.test(text));
|
|
194623
|
+
}
|
|
194624
|
+
function fallTo(hooks, step, reason) {
|
|
194625
|
+
const recorded = hooks.state.record(step, reason);
|
|
194626
|
+
try {
|
|
194627
|
+
hooks.onFallback?.(recorded);
|
|
194628
|
+
} catch {}
|
|
194629
|
+
}
|
|
194630
|
+
async function sendRendered(api, params, hooks) {
|
|
194631
|
+
const { parseMode: _ignored, ...plain } = params;
|
|
194632
|
+
const step = firstStep(hooks.state.mode(), params.text);
|
|
194633
|
+
if (step === "plain") {
|
|
194634
|
+
return api.sendMessage({ ...plain, text: renderPlainText(params.text) });
|
|
194635
|
+
}
|
|
194636
|
+
if (step === "rich") {
|
|
194637
|
+
const paused = hooks.state.richPausedReason();
|
|
194638
|
+
if (paused !== undefined) {
|
|
194639
|
+
fallTo(hooks, "rich-to-html", `rich messages paused: ${paused}`);
|
|
194640
|
+
} else {
|
|
194641
|
+
try {
|
|
194642
|
+
if (api.sendRichMessage === undefined) {
|
|
194643
|
+
throw new RichUnavailable(MISSING_METHOD);
|
|
194644
|
+
}
|
|
194645
|
+
const sent = await api.sendRichMessage({
|
|
194646
|
+
chatId: params.chatId,
|
|
194647
|
+
richMessage: renderRichMessage(params.text),
|
|
194648
|
+
...params.messageThreadId === undefined ? {} : { messageThreadId: params.messageThreadId },
|
|
194649
|
+
...params.inlineKeyboard === undefined ? {} : { inlineKeyboard: params.inlineKeyboard }
|
|
194650
|
+
});
|
|
194651
|
+
hooks.state.richServerReset();
|
|
194652
|
+
return sent;
|
|
194653
|
+
} catch (error) {
|
|
194654
|
+
handleRichFailure(error, hooks, `send:${params.chatId}:${params.messageThreadId ?? ""}:${params.text}`);
|
|
194655
|
+
}
|
|
194656
|
+
}
|
|
194657
|
+
}
|
|
194658
|
+
return sendHtml(api, plain, (error) => fallTo(hooks, "html-to-plain", describe(error)));
|
|
194659
|
+
}
|
|
194660
|
+
async function editRendered(api, params, hooks) {
|
|
194661
|
+
const step = firstStep(hooks.state.mode(), params.text);
|
|
194662
|
+
if (step === "plain") {
|
|
194663
|
+
await api.editMessageText({ ...params, text: renderPlainText(params.text) });
|
|
194664
|
+
return;
|
|
194665
|
+
}
|
|
194666
|
+
if (step === "rich") {
|
|
194667
|
+
const paused = hooks.state.richPausedReason();
|
|
194668
|
+
if (paused !== undefined) {
|
|
194669
|
+
fallTo(hooks, "rich-to-html", `rich messages paused: ${paused}`);
|
|
194670
|
+
} else {
|
|
194671
|
+
try {
|
|
194672
|
+
if (api.editRichMessage === undefined) {
|
|
194673
|
+
throw new RichUnavailable(MISSING_METHOD);
|
|
194674
|
+
}
|
|
194675
|
+
await api.editRichMessage({
|
|
194676
|
+
chatId: params.chatId,
|
|
194677
|
+
messageId: params.messageId,
|
|
194678
|
+
richMessage: renderRichMessage(params.text),
|
|
194679
|
+
...params.inlineKeyboard === undefined ? {} : { inlineKeyboard: params.inlineKeyboard }
|
|
194680
|
+
});
|
|
194681
|
+
hooks.state.richServerReset();
|
|
194682
|
+
return;
|
|
194683
|
+
} catch (error) {
|
|
194684
|
+
if (isNotModified(error)) {
|
|
194685
|
+
throw error;
|
|
194686
|
+
}
|
|
194687
|
+
handleRichFailure(error, hooks, `edit:${params.chatId}:${params.messageId}:${params.text}`);
|
|
194688
|
+
}
|
|
194689
|
+
}
|
|
194690
|
+
}
|
|
194691
|
+
await editHtml(api, params, (error) => fallTo(hooks, "html-to-plain", describe(error)));
|
|
194692
|
+
}
|
|
194693
|
+
function handleRichFailure(error, hooks, key) {
|
|
194694
|
+
if (error instanceof RichUnavailable) {
|
|
194695
|
+
hooks.state.pauseRich(error.message);
|
|
194696
|
+
fallTo(hooks, "rich-to-html", error.message);
|
|
194697
|
+
return;
|
|
194698
|
+
}
|
|
194699
|
+
if (error instanceof RichRenderError) {
|
|
194700
|
+
fallTo(hooks, "rich-to-html", describe(error));
|
|
194701
|
+
return;
|
|
194702
|
+
}
|
|
194703
|
+
if (isBotApiError(error) && error.kind === "server") {
|
|
194704
|
+
if (hooks.state.richServerFailure(key) < RICH_SERVER_ATTEMPTS) {
|
|
194705
|
+
throw error;
|
|
194706
|
+
}
|
|
194707
|
+
hooks.state.richServerReset();
|
|
194708
|
+
fallTo(hooks, "rich-to-html", `rich failed ${RICH_SERVER_ATTEMPTS} times: ${describe(error)}`);
|
|
194709
|
+
return;
|
|
194710
|
+
}
|
|
194711
|
+
if (!isRefusal(error)) {
|
|
194712
|
+
throw error;
|
|
194713
|
+
}
|
|
194714
|
+
if (isChatScoped(error)) {
|
|
194715
|
+
return;
|
|
194716
|
+
}
|
|
194717
|
+
const reason = describe(error);
|
|
194718
|
+
if (isStanding(error)) {
|
|
194719
|
+
hooks.state.pauseRich(reason);
|
|
194720
|
+
}
|
|
194721
|
+
fallTo(hooks, "rich-to-html", reason);
|
|
194722
|
+
}
|
|
194723
|
+
var RICH_PAUSE_MS, RICH_SERVER_ATTEMPTS = 3, MAX_REASON_LENGTH2 = 200, MENTIONS_RICH_METHOD, MENTIONS_CHAT, MISSING_METHOD = "this Bot API client has no rich message method", RichUnavailable;
|
|
194724
|
+
var init_rendering = __esm(() => {
|
|
194725
|
+
init_service2();
|
|
194726
|
+
init_format_html();
|
|
194727
|
+
init_format_rich();
|
|
194728
|
+
init_format_plain();
|
|
194729
|
+
init_rendering_mode();
|
|
194730
|
+
init_types11();
|
|
194731
|
+
RICH_PAUSE_MS = 10 * 60000;
|
|
194732
|
+
MENTIONS_RICH_METHOD = /rich|method|unknown parameter|not supported|unsupported/i;
|
|
194733
|
+
MENTIONS_CHAT = /chat|thread|topic|user|member|group|channel|blocked|kicked|deactivated|rights/i;
|
|
194734
|
+
RichUnavailable = class RichUnavailable extends Error {
|
|
194735
|
+
};
|
|
194736
|
+
});
|
|
194737
|
+
|
|
193562
194738
|
// src/remote/outbound-queue.ts
|
|
193563
194739
|
import { randomUUID as randomUUID51 } from "crypto";
|
|
193564
194740
|
import path363 from "path";
|
|
@@ -193585,6 +194761,7 @@ class OutboundQueue {
|
|
|
193585
194761
|
log;
|
|
193586
194762
|
onDrop;
|
|
193587
194763
|
onFallback;
|
|
194764
|
+
rendering;
|
|
193588
194765
|
retryDelayMs;
|
|
193589
194766
|
sentHooks = new Map;
|
|
193590
194767
|
blockedUntil = 0;
|
|
@@ -193598,6 +194775,7 @@ class OutboundQueue {
|
|
|
193598
194775
|
this.onFallback = options.onFallback ?? (() => {
|
|
193599
194776
|
return;
|
|
193600
194777
|
});
|
|
194778
|
+
this.rendering = options.rendering ?? new RenderingState({ now: options.now });
|
|
193601
194779
|
this.retryDelayMs = options.retryDelayMs ?? 5000;
|
|
193602
194780
|
const directory = ensureRemoteDir(options.dir);
|
|
193603
194781
|
this.log = new DurableLog({
|
|
@@ -193686,17 +194864,17 @@ class OutboundQueue {
|
|
|
193686
194864
|
return { sent, dropped, remaining: this.log.size, retryInMs: wait, stoppedBy: "rate limited" };
|
|
193687
194865
|
}
|
|
193688
194866
|
try {
|
|
193689
|
-
const result = await
|
|
194867
|
+
const result = await sendRendered(this.api, {
|
|
193690
194868
|
chatId: entry.chatId,
|
|
193691
194869
|
text: entry.text,
|
|
193692
194870
|
...entry.threadId === undefined ? {} : { messageThreadId: entry.threadId },
|
|
193693
194871
|
...entry.keyboard === undefined ? {} : { inlineKeyboard: entry.keyboard }
|
|
193694
|
-
}, () => this.onFallback(entry));
|
|
194872
|
+
}, { state: this.rendering, onFallback: (fallback) => this.onFallback(entry, fallback) });
|
|
193695
194873
|
this.log.ack(entry.id);
|
|
193696
194874
|
sent += 1;
|
|
193697
194875
|
this.notifySent(entry, result.message_id);
|
|
193698
194876
|
} catch (error) {
|
|
193699
|
-
const description =
|
|
194877
|
+
const description = describe2(error);
|
|
193700
194878
|
if (isBotApiError(error) && error.kind === "rate-limited") {
|
|
193701
194879
|
const retryMs = Math.max(1, error.retryAfterSec ?? 1) * 1000;
|
|
193702
194880
|
this.blockedUntil = this.now() + retryMs;
|
|
@@ -193713,7 +194891,7 @@ class OutboundQueue {
|
|
|
193713
194891
|
}
|
|
193714
194892
|
}
|
|
193715
194893
|
}
|
|
193716
|
-
function
|
|
194894
|
+
function describe2(error) {
|
|
193717
194895
|
return redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
193718
194896
|
}
|
|
193719
194897
|
var OUTBOUND_MAX_ENTRIES = 200;
|
|
@@ -193722,7 +194900,7 @@ var init_outbound_queue = __esm(() => {
|
|
|
193722
194900
|
init_durable_log();
|
|
193723
194901
|
init_paths4();
|
|
193724
194902
|
init_format2();
|
|
193725
|
-
|
|
194903
|
+
init_rendering();
|
|
193726
194904
|
init_types11();
|
|
193727
194905
|
});
|
|
193728
194906
|
|
|
@@ -193832,7 +195010,7 @@ class UpdatePoller {
|
|
|
193832
195010
|
continue;
|
|
193833
195011
|
}
|
|
193834
195012
|
failures += 1;
|
|
193835
|
-
this.set({ state: "running", reason:
|
|
195013
|
+
this.set({ state: "running", reason: describe3(error) });
|
|
193836
195014
|
await this.sleep(this.backoffMs[Math.min(failures, this.backoffMs.length) - 1] ?? 1000, signal);
|
|
193837
195015
|
continue;
|
|
193838
195016
|
}
|
|
@@ -193852,13 +195030,13 @@ class UpdatePoller {
|
|
|
193852
195030
|
}
|
|
193853
195031
|
} catch (error) {
|
|
193854
195032
|
failures += 1;
|
|
193855
|
-
this.set({ state: "running", reason:
|
|
195033
|
+
this.set({ state: "running", reason: describe3(error) });
|
|
193856
195034
|
await this.sleep(this.backoffMs[Math.min(failures, this.backoffMs.length) - 1] ?? 1000, signal);
|
|
193857
195035
|
}
|
|
193858
195036
|
}
|
|
193859
195037
|
}
|
|
193860
195038
|
}
|
|
193861
|
-
function
|
|
195039
|
+
function describe3(error) {
|
|
193862
195040
|
return redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
193863
195041
|
}
|
|
193864
195042
|
var POLLER_CONFLICT_REASON = "another getUpdates poller already owns this bot token (HTTP 409 Conflict); only one `keryx serve` may poll a token, so this one will not poll. Stop the other instance or use a different bot.", SHORT_POLL_PAUSE_MS = 1000;
|
|
@@ -193869,7 +195047,7 @@ var init_poller = __esm(() => {
|
|
|
193869
195047
|
|
|
193870
195048
|
// src/remote/service-topics.ts
|
|
193871
195049
|
import path364 from "path";
|
|
193872
|
-
function
|
|
195050
|
+
function parse2(value) {
|
|
193873
195051
|
if (typeof value !== "object" || value === null)
|
|
193874
195052
|
return;
|
|
193875
195053
|
const raw = value;
|
|
@@ -193896,7 +195074,7 @@ class ServiceTopics {
|
|
|
193896
195074
|
if (!Array.isArray(records))
|
|
193897
195075
|
return;
|
|
193898
195076
|
for (const candidate of records) {
|
|
193899
|
-
const record =
|
|
195077
|
+
const record = parse2(candidate);
|
|
193900
195078
|
if (record !== undefined)
|
|
193901
195079
|
this.byName.set(nameKey(record.name), record);
|
|
193902
195080
|
}
|
|
@@ -194080,6 +195258,7 @@ class RemoteHub {
|
|
|
194080
195258
|
registry;
|
|
194081
195259
|
inbound;
|
|
194082
195260
|
outbound;
|
|
195261
|
+
rendering;
|
|
194083
195262
|
serviceTopics;
|
|
194084
195263
|
droppedReasons = new Map;
|
|
194085
195264
|
journal;
|
|
@@ -194118,15 +195297,23 @@ class RemoteHub {
|
|
|
194118
195297
|
};
|
|
194119
195298
|
this.registry = new SessionRegistry({ ...options.dir === undefined ? {} : { dir: options.dir }, now: this.now });
|
|
194120
195299
|
this.inbound = new InboundQueues(options.dir === undefined ? {} : { dir: options.dir });
|
|
195300
|
+
this.rendering = new RenderingState({
|
|
195301
|
+
now: this.now,
|
|
195302
|
+
mode: options.renderingMode ?? (() => {
|
|
195303
|
+
const fresh = loadRemoteConfig(options.dir);
|
|
195304
|
+
return (fresh.ok ? fresh.value.rendering : undefined) ?? options.config.rendering ?? DEFAULT_RENDER_MODE;
|
|
195305
|
+
})
|
|
195306
|
+
});
|
|
194121
195307
|
this.outbound = new OutboundQueue({
|
|
194122
195308
|
api: this.api,
|
|
195309
|
+
rendering: this.rendering,
|
|
194123
195310
|
...options.dir === undefined ? {} : { dir: options.dir },
|
|
194124
195311
|
now: this.now,
|
|
194125
195312
|
onDrop: (entry, reason) => {
|
|
194126
195313
|
this.droppedReasons.set(entry.id, reason);
|
|
194127
195314
|
this.event("outbound-dropped", `${reason}${entry.threadId === undefined ? "" : ` (topic ${entry.threadId})`}`);
|
|
194128
195315
|
},
|
|
194129
|
-
onFallback: (entry) => this.
|
|
195316
|
+
onFallback: (entry, fallback) => this.fallbackEvent(fallback, entry.threadId === undefined ? "" : ` (topic ${entry.threadId})`, "sent")
|
|
194130
195317
|
});
|
|
194131
195318
|
this.journal = new RejectedJournal({ ...options.dir === undefined ? {} : { dir: options.dir }, now: this.now });
|
|
194132
195319
|
this.offset = new PollerState(options.dir === undefined ? {} : { dir: options.dir });
|
|
@@ -194267,7 +195454,13 @@ class RemoteHub {
|
|
|
194267
195454
|
});
|
|
194268
195455
|
}
|
|
194269
195456
|
async sendGeneral(text) {
|
|
194270
|
-
await
|
|
195457
|
+
await sendRendered(this.api, { chatId: this.config.chatId, text }, { state: this.rendering, onFallback: (fallback) => this.fallbackEvent(fallback, " (General)", "sent") });
|
|
195458
|
+
}
|
|
195459
|
+
renderingStatus() {
|
|
195460
|
+
return this.rendering.snapshot();
|
|
195461
|
+
}
|
|
195462
|
+
fallbackEvent(fallback, where, verb) {
|
|
195463
|
+
this.event("format-fallback", fallback.step === "html-to-plain" ? `Telegram refused the formatting; ${verb} as plain text${where}` : `Telegram refused the rich message (${fallback.reason}); ${verb} as HTML${where}`);
|
|
194271
195464
|
}
|
|
194272
195465
|
heartbeat(sessionId) {
|
|
194273
195466
|
return this.serial(async () => {
|
|
@@ -194386,7 +195579,7 @@ class RemoteHub {
|
|
|
194386
195579
|
return "gone";
|
|
194387
195580
|
}
|
|
194388
195581
|
try {
|
|
194389
|
-
await
|
|
195582
|
+
await editRendered(this.api, { chatId: record.chatId, messageId, text }, { state: this.rendering, onFallback: (fallback) => this.fallbackEvent(fallback, ` (topic ${record.threadId})`, "edited") });
|
|
194390
195583
|
return "edited";
|
|
194391
195584
|
} catch (error) {
|
|
194392
195585
|
if (isNotModified(error)) {
|
|
@@ -194799,11 +195992,14 @@ var realTimers, MAX_EVENTS = 50, ANSWER_CALLBACK_TIMEOUT_MS = 5000;
|
|
|
194799
195992
|
var init_hub = __esm(() => {
|
|
194800
195993
|
init_service2();
|
|
194801
195994
|
init_lease();
|
|
195995
|
+
init_config11();
|
|
194802
195996
|
init_format_html();
|
|
194803
195997
|
init_inbound();
|
|
194804
195998
|
init_journal3();
|
|
194805
195999
|
init_naming();
|
|
194806
196000
|
init_outbound_queue();
|
|
196001
|
+
init_rendering();
|
|
196002
|
+
init_rendering_mode();
|
|
194807
196003
|
init_command_gateway();
|
|
194808
196004
|
init_message_state();
|
|
194809
196005
|
init_poller();
|
|
@@ -195818,10 +197014,10 @@ function newPairingCode() {
|
|
|
195818
197014
|
}
|
|
195819
197015
|
return code;
|
|
195820
197016
|
}
|
|
195821
|
-
function
|
|
197017
|
+
function describe4(error) {
|
|
195822
197018
|
return redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
195823
197019
|
}
|
|
195824
|
-
function
|
|
197020
|
+
function normalise4(text) {
|
|
195825
197021
|
return text.toUpperCase().replace(/[^A-Z0-9]/g, "");
|
|
195826
197022
|
}
|
|
195827
197023
|
|
|
@@ -195879,7 +197075,7 @@ class Pairing {
|
|
|
195879
197075
|
if (isBotApiError(error) && error.kind === "rejected") {
|
|
195880
197076
|
return { ok: false, kind: "rejected", reason: "Telegram does not accept this bot token; check it with BotFather and try again." };
|
|
195881
197077
|
}
|
|
195882
|
-
return { ok: false, kind: "unreachable", reason: `could not reach Telegram: ${
|
|
197078
|
+
return { ok: false, kind: "unreachable", reason: `could not reach Telegram: ${describe4(error)}` };
|
|
195883
197079
|
}
|
|
195884
197080
|
return { ok: true, pairing: new Pairing(options, bot) };
|
|
195885
197081
|
}
|
|
@@ -196006,7 +197202,7 @@ class Pairing {
|
|
|
196006
197202
|
return;
|
|
196007
197203
|
}
|
|
196008
197204
|
const sent = message.text.trim().replace(/^\/start(?:@\w+)?\s+/i, "");
|
|
196009
|
-
if (this.code === undefined ||
|
|
197205
|
+
if (this.code === undefined || normalise4(sent) !== this.code) {
|
|
196010
197206
|
return;
|
|
196011
197207
|
}
|
|
196012
197208
|
this.userId = message.from.id;
|
|
@@ -196115,7 +197311,7 @@ class Pairing {
|
|
|
196115
197311
|
problems.push("Give the bot the Manage Topics right in the group's administrator settings.");
|
|
196116
197312
|
}
|
|
196117
197313
|
} catch (error) {
|
|
196118
|
-
problems.push(`could not inspect the group: ${
|
|
197314
|
+
problems.push(`could not inspect the group: ${describe4(error)}`);
|
|
196119
197315
|
}
|
|
196120
197316
|
this.expireIfDue();
|
|
196121
197317
|
if (FINAL.has(this.state)) {
|
|
@@ -196140,7 +197336,7 @@ var init_pairing = __esm(() => {
|
|
|
196140
197336
|
});
|
|
196141
197337
|
|
|
196142
197338
|
// src/remote/channels.ts
|
|
196143
|
-
function
|
|
197339
|
+
function describe5(error) {
|
|
196144
197340
|
return redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
196145
197341
|
}
|
|
196146
197342
|
|
|
@@ -196203,11 +197399,25 @@ class ChannelsController {
|
|
|
196203
197399
|
}
|
|
196204
197400
|
return { state: "not-connected" };
|
|
196205
197401
|
}
|
|
197402
|
+
rendering() {
|
|
197403
|
+
const hub = this.host.hub();
|
|
197404
|
+
if (hub !== undefined && typeof hub.renderingStatus === "function") {
|
|
197405
|
+
return hub.renderingStatus();
|
|
197406
|
+
}
|
|
197407
|
+
const config = loadRemoteConfig(this.host.dir);
|
|
197408
|
+
return config.ok ? { mode: config.value.rendering ?? DEFAULT_RENDER_MODE } : undefined;
|
|
197409
|
+
}
|
|
196206
197410
|
status() {
|
|
196207
197411
|
const { state, reason } = this.state();
|
|
197412
|
+
const rendering = this.rendering();
|
|
196208
197413
|
return ok2({
|
|
196209
197414
|
machine: this.host.machine,
|
|
196210
|
-
telegram: {
|
|
197415
|
+
telegram: {
|
|
197416
|
+
state,
|
|
197417
|
+
...reason === undefined ? {} : { reason },
|
|
197418
|
+
sessions: this.host.hub()?.list().length ?? 0,
|
|
197419
|
+
...rendering === undefined ? {} : { rendering }
|
|
197420
|
+
}
|
|
196211
197421
|
});
|
|
196212
197422
|
}
|
|
196213
197423
|
async dropPairing() {
|
|
@@ -196303,7 +197513,7 @@ class ChannelsController {
|
|
|
196303
197513
|
try {
|
|
196304
197514
|
await hub.sendGeneral(`Keryx test message from ${this.host.machine}: Telegram is connected.`);
|
|
196305
197515
|
} catch (error) {
|
|
196306
|
-
return fail4(502, "send-failed", `Telegram did not accept the test message: ${
|
|
197516
|
+
return fail4(502, "send-failed", `Telegram did not accept the test message: ${describe5(error)}`);
|
|
196307
197517
|
}
|
|
196308
197518
|
return ok2({ delivered: true, machine: this.host.machine });
|
|
196309
197519
|
}
|
|
@@ -196329,6 +197539,7 @@ var init_channels = __esm(() => {
|
|
|
196329
197539
|
init_http_surface();
|
|
196330
197540
|
init_pairing();
|
|
196331
197541
|
init_paths4();
|
|
197542
|
+
init_rendering_mode();
|
|
196332
197543
|
});
|
|
196333
197544
|
|
|
196334
197545
|
// src/remote/poll-lock.ts
|
|
@@ -203120,6 +204331,156 @@ var init_retention2 = __esm(() => {
|
|
|
203120
204331
|
init_sweep();
|
|
203121
204332
|
});
|
|
203122
204333
|
|
|
204334
|
+
// src/remote/rendering-sample.ts
|
|
204335
|
+
function describeBlocks(message) {
|
|
204336
|
+
const counts = new Map;
|
|
204337
|
+
const walk = (blocks) => {
|
|
204338
|
+
for (const block of blocks) {
|
|
204339
|
+
counts.set(block.type, (counts.get(block.type) ?? 0) + 1);
|
|
204340
|
+
for (const item of block.items ?? []) {
|
|
204341
|
+
walk(item.blocks ?? []);
|
|
204342
|
+
}
|
|
204343
|
+
}
|
|
204344
|
+
};
|
|
204345
|
+
walk(message.blocks);
|
|
204346
|
+
return [...counts.entries()].map(([type, count]) => `${count} ${type}`).join(", ");
|
|
204347
|
+
}
|
|
204348
|
+
function htmlOrPlain(part) {
|
|
204349
|
+
const html = renderTelegramHtml(part);
|
|
204350
|
+
const checked = checkTelegramHtml(html);
|
|
204351
|
+
return checked.ok ? { kind: "html", text: checked.text } : { kind: "plain", text: renderPlainText(part), note: `HTML refused locally (${checked.reason}); plain text` };
|
|
204352
|
+
}
|
|
204353
|
+
function renderSample(mode, reply = SAMPLE_REPLY) {
|
|
204354
|
+
const pieces = formatReply(reply);
|
|
204355
|
+
const parts = pieces.map((piece, position) => {
|
|
204356
|
+
const base = { index: position + 1, total: pieces.length };
|
|
204357
|
+
const step = firstStep(mode, piece);
|
|
204358
|
+
if (step === "plain") {
|
|
204359
|
+
return { ...base, kind: "plain", text: renderPlainText(piece) };
|
|
204360
|
+
}
|
|
204361
|
+
if (step === "rich") {
|
|
204362
|
+
try {
|
|
204363
|
+
const richMessage = renderRichMessage(piece);
|
|
204364
|
+
return { ...base, kind: "rich", text: describeBlocks(richMessage), richMessage };
|
|
204365
|
+
} catch (error) {
|
|
204366
|
+
if (!(error instanceof RichRenderError)) {
|
|
204367
|
+
throw error;
|
|
204368
|
+
}
|
|
204369
|
+
return { ...base, ...htmlOrPlain(piece), note: `not renderable as a rich message (${error.message}); HTML instead` };
|
|
204370
|
+
}
|
|
204371
|
+
}
|
|
204372
|
+
return { ...base, ...htmlOrPlain(piece) };
|
|
204373
|
+
});
|
|
204374
|
+
return { mode, parts };
|
|
204375
|
+
}
|
|
204376
|
+
function renderAllSamples(reply = SAMPLE_REPLY) {
|
|
204377
|
+
return RENDER_MODES.map((mode) => renderSample(mode, reply));
|
|
204378
|
+
}
|
|
204379
|
+
function formatSampleText(renderings, options = {}) {
|
|
204380
|
+
const lines = ["Sample reply (source):", "", ...SAMPLE_REPLY.split(`
|
|
204381
|
+
`).map((line) => ` ${line}`), ""];
|
|
204382
|
+
for (const rendering of renderings) {
|
|
204383
|
+
lines.push(`=== ${MODE_HEADLINE[rendering.mode]} ===`);
|
|
204384
|
+
for (const part of rendering.parts) {
|
|
204385
|
+
lines.push(`--- part ${part.index}/${part.total}, sent as ${part.kind}${part.note === undefined ? "" : ` (${part.note})`} ---`);
|
|
204386
|
+
if (part.kind === "rich" && options.full === true) {
|
|
204387
|
+
lines.push(JSON.stringify(part.richMessage, null, 2));
|
|
204388
|
+
} else if (part.kind === "rich") {
|
|
204389
|
+
lines.push(`rich blocks: ${part.text}`);
|
|
204390
|
+
} else {
|
|
204391
|
+
lines.push(part.text);
|
|
204392
|
+
}
|
|
204393
|
+
}
|
|
204394
|
+
lines.push("");
|
|
204395
|
+
}
|
|
204396
|
+
return `${lines.join(`
|
|
204397
|
+
`)}
|
|
204398
|
+
`;
|
|
204399
|
+
}
|
|
204400
|
+
var SAMPLE_REPLY, MODE_HEADLINE;
|
|
204401
|
+
var init_rendering_sample = __esm(() => {
|
|
204402
|
+
init_format2();
|
|
204403
|
+
init_format_html();
|
|
204404
|
+
init_format_plain();
|
|
204405
|
+
init_format_rich();
|
|
204406
|
+
init_rendering();
|
|
204407
|
+
init_rendering_mode();
|
|
204408
|
+
SAMPLE_REPLY = [
|
|
204409
|
+
"## Release check",
|
|
204410
|
+
"",
|
|
204411
|
+
"Everything below is **one reply**: a table, lists, a rule and some `code`.",
|
|
204412
|
+
"",
|
|
204413
|
+
"| Step | Result | Time |",
|
|
204414
|
+
"|:-----|:------:|-----:|",
|
|
204415
|
+
"| build | `ok` | 41s |",
|
|
204416
|
+
"| unit tests | ok | 2m 08s |",
|
|
204417
|
+
"| smoke | **failed** | 12s |",
|
|
204418
|
+
"",
|
|
204419
|
+
"1. Fix the smoke test",
|
|
204420
|
+
"2. Re-run the release check",
|
|
204421
|
+
" - on the branch first",
|
|
204422
|
+
" - then on main",
|
|
204423
|
+
"",
|
|
204424
|
+
"- [x] changelog written",
|
|
204425
|
+
"- [ ] tag pushed",
|
|
204426
|
+
"",
|
|
204427
|
+
"---",
|
|
204428
|
+
"",
|
|
204429
|
+
"> The tag is pushed by the operator, not by the agent."
|
|
204430
|
+
].join(`
|
|
204431
|
+
`);
|
|
204432
|
+
MODE_HEADLINE = {
|
|
204433
|
+
auto: "auto: rich where there is a table, otherwise HTML",
|
|
204434
|
+
rich: "rich: every reply as a rich message",
|
|
204435
|
+
html: "html: every reply as Telegram HTML",
|
|
204436
|
+
plain: "plain: every reply as plain text"
|
|
204437
|
+
};
|
|
204438
|
+
});
|
|
204439
|
+
|
|
204440
|
+
// src/commands/remote.ts
|
|
204441
|
+
function remoteCommand(args) {
|
|
204442
|
+
const [sub, ...rest] = args;
|
|
204443
|
+
if (sub === undefined || sub === "--help" || sub === "-h" || sub === "help") {
|
|
204444
|
+
console.log(REMOTE_USAGE);
|
|
204445
|
+
return;
|
|
204446
|
+
}
|
|
204447
|
+
if (sub !== "format-sample") {
|
|
204448
|
+
console.error(`keryx remote: unknown subcommand '${sub}'. Try \`keryx remote format-sample\`.`);
|
|
204449
|
+
process.exitCode = 1;
|
|
204450
|
+
return;
|
|
204451
|
+
}
|
|
204452
|
+
const wanted = optionValue(rest, "--mode");
|
|
204453
|
+
if (wanted !== undefined && !isRenderMode(wanted)) {
|
|
204454
|
+
console.error(`keryx remote format-sample: --mode must be ${RENDER_MODE_CHOICES}`);
|
|
204455
|
+
process.exitCode = 1;
|
|
204456
|
+
return;
|
|
204457
|
+
}
|
|
204458
|
+
const renderings = wanted === undefined ? renderAllSamples() : [renderSample(wanted)];
|
|
204459
|
+
if (rest.includes("--json")) {
|
|
204460
|
+
console.log(JSON.stringify({ sample: SAMPLE_REPLY, renderings }, null, 2));
|
|
204461
|
+
return;
|
|
204462
|
+
}
|
|
204463
|
+
process.stdout.write(formatSampleText(renderings, { full: rest.includes("--full") }));
|
|
204464
|
+
}
|
|
204465
|
+
var REMOTE_USAGE;
|
|
204466
|
+
var init_remote = __esm(() => {
|
|
204467
|
+
init_args();
|
|
204468
|
+
init_rendering_sample();
|
|
204469
|
+
init_rendering_mode();
|
|
204470
|
+
REMOTE_USAGE = `keryx remote format-sample \u2014 show the sample reply in every Telegram rendering mode (no network)
|
|
204471
|
+
|
|
204472
|
+
Usage:
|
|
204473
|
+
keryx remote format-sample [--mode ${RENDER_MODES.join("|")}] [--full] [--json]
|
|
204474
|
+
|
|
204475
|
+
--mode Only that mode. Without it all four are printed.
|
|
204476
|
+
--full Print the whole rich message (JSON) instead of a count of its blocks.
|
|
204477
|
+
--json Machine-readable: the sample source and, per mode, each part with how it is sent.
|
|
204478
|
+
|
|
204479
|
+
The mode in effect for a running session is set with \`/rendering\` in the shell, or the
|
|
204480
|
+
"Telegram rendering" row of \`/settings\`; \`/channels\` shows it together with the last fallback.
|
|
204481
|
+
`;
|
|
204482
|
+
});
|
|
204483
|
+
|
|
203123
204484
|
// src/commands/forgetting.ts
|
|
203124
204485
|
async function forgettingCommand(args) {
|
|
203125
204486
|
const command = args[0];
|
|
@@ -204907,7 +206268,7 @@ function buildHeader(entry, size) {
|
|
|
204907
206268
|
chk.copy(header, 148);
|
|
204908
206269
|
return { ok: true, value: header };
|
|
204909
206270
|
}
|
|
204910
|
-
function
|
|
206271
|
+
function pad5(buf) {
|
|
204911
206272
|
const rem = buf.length % BLOCK;
|
|
204912
206273
|
if (rem === 0)
|
|
204913
206274
|
return buf;
|
|
@@ -204942,7 +206303,7 @@ function buildBundleArchive(files, manifestBytes) {
|
|
|
204942
206303
|
return header;
|
|
204943
206304
|
chunks.push(header.value);
|
|
204944
206305
|
if (entry.bytes && entry.bytes.length > 0) {
|
|
204945
|
-
chunks.push(
|
|
206306
|
+
chunks.push(pad5(entry.bytes));
|
|
204946
206307
|
}
|
|
204947
206308
|
}
|
|
204948
206309
|
chunks.push(Buffer.alloc(BLOCK * 2));
|
|
@@ -209144,6 +210505,9 @@ var VERSION3, CLI_ROUTES, MCP_KNOWN_SUBCOMMANDS, USAGE_BODY = `Usage:
|
|
|
209144
210505
|
keryx retention sweep [--apply] [--target <id>]... [--max-age-days <n>] [--max-bytes <n>] [--json]
|
|
209145
210506
|
Bound gdctx raw/artifacts logs and owner write-conflict
|
|
209146
210507
|
sidecars; dry run by default, --apply removes
|
|
210508
|
+
keryx remote format-sample [--mode auto|rich|html|plain] [--full] [--json]
|
|
210509
|
+
Print the sample reply in each Telegram rendering mode
|
|
210510
|
+
(tables, lists, rules); no network
|
|
209147
210511
|
keryx forgetting trail [--limit <n>] [--json]
|
|
209148
210512
|
keryx forgetting lookup "<ref-or-path>" [--layer <layer>] [--search] [--json]
|
|
209149
210513
|
Read the deletion trail: what was removed, when, at
|
|
@@ -209241,6 +210605,7 @@ Commands:
|
|
|
209241
210605
|
metrics Provenance-aware execution observability: run records, baselines, benchmarks
|
|
209242
210606
|
workspace Shared Agent Context: workspaces, FWK reads, propose/review (module sac)
|
|
209243
210607
|
retention Bound stores that grow without bound (gdctx raw/artifacts, owner write-conflict sidecars)
|
|
210608
|
+
remote Telegram remote control: preview how replies are rendered, with no network
|
|
209244
210609
|
forgetting Read the deletion trail \u2014 was this removed, or did it never exist?
|
|
209245
210610
|
trigger Fire one declared project trigger (git hook, cron line, CI job) \u2014 one pass, one exit code
|
|
209246
210611
|
schedule Scheduled agent tasks in the background: create (with confirmation), list, pause, resume, remove
|
|
@@ -209298,6 +210663,7 @@ var init_cli_registry = __esm(() => {
|
|
|
209298
210663
|
init_external2();
|
|
209299
210664
|
init_auth();
|
|
209300
210665
|
init_retention2();
|
|
210666
|
+
init_remote();
|
|
209301
210667
|
init_forgetting();
|
|
209302
210668
|
init_trigger();
|
|
209303
210669
|
init_schedule2();
|
|
@@ -209366,6 +210732,7 @@ var init_cli_registry = __esm(() => {
|
|
|
209366
210732
|
version: versionCommand,
|
|
209367
210733
|
workspace: workspaceCommand,
|
|
209368
210734
|
retention: retentionCommand,
|
|
210735
|
+
remote: remoteCommand,
|
|
209369
210736
|
forgetting: forgettingCommand,
|
|
209370
210737
|
trigger: triggerCommand,
|
|
209371
210738
|
schedule: scheduleCommand,
|