@mrciphersmith/keryx 0.3.62 → 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 +1764 -394
- package/dist/core.js +15 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9600,6 +9600,9 @@ function renderMetaprojectGitignoreBlock() {
|
|
|
9600
9600
|
# entries rather than inheriting coverage that does not exist.
|
|
9601
9601
|
.metaproject/data/learning/observations/
|
|
9602
9602
|
.metaproject/data/learning/candidates/
|
|
9603
|
+
# Recommendation journal (flow 392): what the human chose and why they deviated.
|
|
9604
|
+
# Personal runtime state, written 0o600.
|
|
9605
|
+
.metaproject/data/decisions/
|
|
9603
9606
|
`;
|
|
9604
9607
|
}
|
|
9605
9608
|
function escapeRegExp2(value) {
|
|
@@ -20104,7 +20107,7 @@ var package_default;
|
|
|
20104
20107
|
var init_package = __esm(() => {
|
|
20105
20108
|
package_default = {
|
|
20106
20109
|
name: "@mrciphersmith/keryx",
|
|
20107
|
-
version: "0.3.
|
|
20110
|
+
version: "0.3.64",
|
|
20108
20111
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
20109
20112
|
private: false,
|
|
20110
20113
|
publishConfig: {
|
|
@@ -30909,6 +30912,9 @@ function summarizeSubmittedLine(line) {
|
|
|
30909
30912
|
function isZeroWidth(cp) {
|
|
30910
30913
|
return cp >= 768 && cp <= 879 || cp >= 8203 && cp <= 8207 || cp >= 65024 && cp <= 65039 || cp >= 8288 && cp <= 8292;
|
|
30911
30914
|
}
|
|
30915
|
+
function isEmojiModifier(cp) {
|
|
30916
|
+
return cp >= 127995 && cp <= 127999;
|
|
30917
|
+
}
|
|
30912
30918
|
function codePointWidth(cp) {
|
|
30913
30919
|
for (const [lo, hi] of WIDE_RANGES) {
|
|
30914
30920
|
if (cp >= lo && cp <= hi) {
|
|
@@ -30919,11 +30925,17 @@ function codePointWidth(cp) {
|
|
|
30919
30925
|
}
|
|
30920
30926
|
function visualWidth(text) {
|
|
30921
30927
|
let width = 0;
|
|
30928
|
+
let previous = 0;
|
|
30922
30929
|
for (const ch of text) {
|
|
30923
30930
|
const codePoint = ch.codePointAt(0) ?? 0;
|
|
30931
|
+
const joined = previous === ZERO_WIDTH_JOINER;
|
|
30932
|
+
previous = codePoint;
|
|
30924
30933
|
if (isZeroWidth(codePoint)) {
|
|
30925
30934
|
continue;
|
|
30926
30935
|
}
|
|
30936
|
+
if (joined && codePointWidth(codePoint) === 2 || isEmojiModifier(codePoint)) {
|
|
30937
|
+
continue;
|
|
30938
|
+
}
|
|
30927
30939
|
width += codePointWidth(codePoint);
|
|
30928
30940
|
}
|
|
30929
30941
|
return width;
|
|
@@ -30936,7 +30948,7 @@ function hugWidth(text, chrome) {
|
|
|
30936
30948
|
}
|
|
30937
30949
|
return widest + chrome;
|
|
30938
30950
|
}
|
|
30939
|
-
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;
|
|
30940
30952
|
var init_md_blocks = __esm(() => {
|
|
30941
30953
|
FENCE_LINE = /^[ \t]{0,3}(```|~~~)(.*)\r?$/;
|
|
30942
30954
|
HUNK_HEADER = /^@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@/;
|
|
@@ -31184,8 +31196,48 @@ var init_md_blocks = __esm(() => {
|
|
|
31184
31196
|
[65072, 65135],
|
|
31185
31197
|
[65280, 65376],
|
|
31186
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],
|
|
31187
31237
|
[127744, 128591],
|
|
31238
|
+
[128640, 128767],
|
|
31188
31239
|
[129280, 129535],
|
|
31240
|
+
[129536, 129791],
|
|
31189
31241
|
[131072, 262141]
|
|
31190
31242
|
];
|
|
31191
31243
|
});
|
|
@@ -100458,6 +100510,264 @@ var init_live_render = __esm(() => {
|
|
|
100458
100510
|
ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
100459
100511
|
});
|
|
100460
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
|
+
|
|
100461
100771
|
// src/tui/reasoning-display.ts
|
|
100462
100772
|
function parseThinkDisplayMode(raw) {
|
|
100463
100773
|
if (raw === undefined) {
|
|
@@ -100517,7 +100827,7 @@ var init_reasoning_display = __esm(() => {
|
|
|
100517
100827
|
});
|
|
100518
100828
|
|
|
100519
100829
|
// src/tui/debug-log.ts
|
|
100520
|
-
import
|
|
100830
|
+
import path229 from "path";
|
|
100521
100831
|
function isDebugEnabled() {
|
|
100522
100832
|
return activeRun !== undefined;
|
|
100523
100833
|
}
|
|
@@ -100545,11 +100855,11 @@ function startDebugRun(opts = {}) {
|
|
|
100545
100855
|
const dir = ensureKeryxSubdir(["debug", name], opts.configDir);
|
|
100546
100856
|
const run = {
|
|
100547
100857
|
dir,
|
|
100548
|
-
shellLog:
|
|
100549
|
-
watcherLog:
|
|
100858
|
+
shellLog: path229.join(dir, "shell.ndjson"),
|
|
100859
|
+
watcherLog: path229.join(dir, "watcher.ndjson")
|
|
100550
100860
|
};
|
|
100551
100861
|
try {
|
|
100552
|
-
writeOwnerOnlyFile(
|
|
100862
|
+
writeOwnerOnlyFile(path229.join(path229.dirname(dir), "latest.txt"), `${dir}
|
|
100553
100863
|
`);
|
|
100554
100864
|
} catch {}
|
|
100555
100865
|
activeRun = run;
|
|
@@ -100615,7 +100925,7 @@ var init_debug_log = __esm(() => {
|
|
|
100615
100925
|
|
|
100616
100926
|
// src/tui/theme.ts
|
|
100617
100927
|
import { existsSync as existsSync39 } from "fs";
|
|
100618
|
-
import
|
|
100928
|
+
import path230 from "path";
|
|
100619
100929
|
function hexChannels(hex) {
|
|
100620
100930
|
return [1, 3, 5].map((offset) => Number.parseInt(hex.slice(offset, offset + 2), 16));
|
|
100621
100931
|
}
|
|
@@ -100753,7 +101063,7 @@ function onThemeChange(listener) {
|
|
|
100753
101063
|
};
|
|
100754
101064
|
}
|
|
100755
101065
|
function tuiConfigPath(dir) {
|
|
100756
|
-
return
|
|
101066
|
+
return path230.join(keryxConfigDir(dir), "tui.json");
|
|
100757
101067
|
}
|
|
100758
101068
|
function loadPersistedThemeId(dir) {
|
|
100759
101069
|
try {
|
|
@@ -101149,6 +101459,17 @@ function buildSettingsRows(state) {
|
|
|
101149
101459
|
usage: "/external-agents [on|off]",
|
|
101150
101460
|
actions: toggleActions("/external-agents", state.externalAgents.on)
|
|
101151
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
|
+
},
|
|
101152
101473
|
{
|
|
101153
101474
|
id: "jevprofile",
|
|
101154
101475
|
group: "External",
|
|
@@ -101187,6 +101508,7 @@ var SETTING_GROUPS, onOff = (value) => value ? "on" : "off", EXTERNAL_PRIVACY_DE
|
|
|
101187
101508
|
var init_settings_model = __esm(() => {
|
|
101188
101509
|
init_agent();
|
|
101189
101510
|
init_permission_mode();
|
|
101511
|
+
init_rendering_mode();
|
|
101190
101512
|
init_reasoning_display();
|
|
101191
101513
|
init_theme();
|
|
101192
101514
|
SETTING_GROUPS = ["Safety", "Routing", "Display", "External"];
|
|
@@ -101306,7 +101628,7 @@ var init_jev_profile = __esm(() => {
|
|
|
101306
101628
|
|
|
101307
101629
|
// src/commands/review-jev-profile.ts
|
|
101308
101630
|
import { readFile as readFile106 } from "fs/promises";
|
|
101309
|
-
import
|
|
101631
|
+
import path231 from "path";
|
|
101310
101632
|
function rejectUnknownJevProfileFlags(args) {
|
|
101311
101633
|
const unknown = args.filter((arg) => arg.startsWith("--") && !JEV_PROFILE_FLAGS.includes(arg.split("=")[0]));
|
|
101312
101634
|
if (unknown.length > 0) {
|
|
@@ -101315,7 +101637,7 @@ function rejectUnknownJevProfileFlags(args) {
|
|
|
101315
101637
|
}
|
|
101316
101638
|
async function readRawConfig(cwd) {
|
|
101317
101639
|
try {
|
|
101318
|
-
return JSON.parse(await readFile106(
|
|
101640
|
+
return JSON.parse(await readFile106(path231.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
|
|
101319
101641
|
} catch {
|
|
101320
101642
|
return;
|
|
101321
101643
|
}
|
|
@@ -101339,7 +101661,7 @@ async function runJevProfile(args) {
|
|
|
101339
101661
|
return;
|
|
101340
101662
|
}
|
|
101341
101663
|
const merged = mergeRecommendedJevProfile(raw);
|
|
101342
|
-
await writeFileAtomic(
|
|
101664
|
+
await writeFileAtomic(path231.join(cwd, REVIEW_GATE_CONFIG_PATH), `${JSON.stringify(merged, null, 2)}
|
|
101343
101665
|
`);
|
|
101344
101666
|
const current = currentJevProfile(merged);
|
|
101345
101667
|
if (args.includes("--json")) {
|
|
@@ -101354,13 +101676,13 @@ async function readJevProfileForShell(cwd) {
|
|
|
101354
101676
|
async function toggleJevProfileKeyForShell(cwd, key) {
|
|
101355
101677
|
const raw = await readRawConfig(cwd);
|
|
101356
101678
|
const merged = toggleJevProfileKey(raw, key);
|
|
101357
|
-
await writeFileAtomic(
|
|
101679
|
+
await writeFileAtomic(path231.join(cwd, REVIEW_GATE_CONFIG_PATH), `${JSON.stringify(merged, null, 2)}
|
|
101358
101680
|
`);
|
|
101359
101681
|
}
|
|
101360
101682
|
async function applyRecommendedJevProfileForShell(cwd) {
|
|
101361
101683
|
const raw = await readRawConfig(cwd);
|
|
101362
101684
|
const merged = mergeRecommendedJevProfile(raw);
|
|
101363
|
-
await writeFileAtomic(
|
|
101685
|
+
await writeFileAtomic(path231.join(cwd, REVIEW_GATE_CONFIG_PATH), `${JSON.stringify(merged, null, 2)}
|
|
101364
101686
|
`);
|
|
101365
101687
|
}
|
|
101366
101688
|
var JEV_PROFILE_FLAGS;
|
|
@@ -101382,14 +101704,14 @@ import {
|
|
|
101382
101704
|
openSync as openSync4,
|
|
101383
101705
|
realpathSync as realpathSync16,
|
|
101384
101706
|
renameSync as renameSync4,
|
|
101385
|
-
statSync as
|
|
101386
|
-
unlinkSync as
|
|
101707
|
+
statSync as statSync15,
|
|
101708
|
+
unlinkSync as unlinkSync5,
|
|
101387
101709
|
writeFileSync as writeFileSync7
|
|
101388
101710
|
} from "fs";
|
|
101389
|
-
import
|
|
101711
|
+
import path232 from "path";
|
|
101390
101712
|
import { randomUUID as randomUUID28 } from "crypto";
|
|
101391
101713
|
function projectRegistryPath(dir) {
|
|
101392
|
-
return
|
|
101714
|
+
return path232.join(keryxConfigDir(dir), "projects.json");
|
|
101393
101715
|
}
|
|
101394
101716
|
function withRegistryLock(dir, fn, onWaiting) {
|
|
101395
101717
|
return withFileLock2(`${projectRegistryPath(dir)}.lock`, fn, {
|
|
@@ -101405,7 +101727,7 @@ function validEntry(candidate) {
|
|
|
101405
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";
|
|
101406
101728
|
}
|
|
101407
101729
|
function projectIdentity(projectPath) {
|
|
101408
|
-
const absolute =
|
|
101730
|
+
const absolute = path232.resolve(projectPath);
|
|
101409
101731
|
try {
|
|
101410
101732
|
return realpathSync16.native(absolute);
|
|
101411
101733
|
} catch {
|
|
@@ -101521,7 +101843,7 @@ function saveProjectRegistry(registry, dir, onWarn) {
|
|
|
101521
101843
|
return true;
|
|
101522
101844
|
} catch {
|
|
101523
101845
|
try {
|
|
101524
|
-
|
|
101846
|
+
unlinkSync5(temp);
|
|
101525
101847
|
} catch {}
|
|
101526
101848
|
return false;
|
|
101527
101849
|
}
|
|
@@ -101560,7 +101882,7 @@ function registerProject(projectPath, options = {}) {
|
|
|
101560
101882
|
const entry = {
|
|
101561
101883
|
projectId: randomUUID28(),
|
|
101562
101884
|
path: absolute,
|
|
101563
|
-
displayName: options.displayName ??
|
|
101885
|
+
displayName: options.displayName ?? path232.basename(absolute),
|
|
101564
101886
|
state: "active",
|
|
101565
101887
|
registeredAt: now,
|
|
101566
101888
|
lastSeenAt: now
|
|
@@ -101597,7 +101919,7 @@ function forgetProject(projectId, dir, onWarn) {
|
|
|
101597
101919
|
return outcome ?? "write-failed";
|
|
101598
101920
|
}
|
|
101599
101921
|
function isInitializedProject(projectPath) {
|
|
101600
|
-
return
|
|
101922
|
+
return statSync15(path232.join(projectPath, ".metaproject"), { throwIfNoEntry: false })?.isDirectory() === true;
|
|
101601
101923
|
}
|
|
101602
101924
|
function listProjects(dir, onWarn) {
|
|
101603
101925
|
const registry = loadProjectRegistry(dir, onWarn);
|
|
@@ -101651,9 +101973,9 @@ var init_project_registry = __esm(() => {
|
|
|
101651
101973
|
});
|
|
101652
101974
|
|
|
101653
101975
|
// src/lib/permission-mode-config.ts
|
|
101654
|
-
import
|
|
101976
|
+
import path233 from "path";
|
|
101655
101977
|
function permissionModeConfigPath(dir) {
|
|
101656
|
-
return
|
|
101978
|
+
return path233.join(keryxConfigDir(dir), "permission-mode.json");
|
|
101657
101979
|
}
|
|
101658
101980
|
function withRegistryLock2(dir, fn) {
|
|
101659
101981
|
return withFileLock2(`${permissionModeConfigPath(dir)}.lock`, fn, {
|
|
@@ -101760,10 +102082,10 @@ var init_jev_edit_guard = __esm(() => {
|
|
|
101760
102082
|
|
|
101761
102083
|
// src/review/jev-edit-guard-config.ts
|
|
101762
102084
|
import { readFile as readFile107 } from "fs/promises";
|
|
101763
|
-
import
|
|
102085
|
+
import path234 from "path";
|
|
101764
102086
|
async function readReviewJevBlock(cwd) {
|
|
101765
102087
|
try {
|
|
101766
|
-
const parsed = JSON.parse(await readFile107(
|
|
102088
|
+
const parsed = JSON.parse(await readFile107(path234.join(cwd, REVIEW_GATE_CONFIG_PATH), "utf8"));
|
|
101767
102089
|
if (typeof parsed !== "object" || parsed === null)
|
|
101768
102090
|
return;
|
|
101769
102091
|
const review = parsed["review"];
|
|
@@ -101808,7 +102130,7 @@ async function readJevEditGuardConfig(cwd, opts) {
|
|
|
101808
102130
|
return { enabled, threshold, maxCalls };
|
|
101809
102131
|
}
|
|
101810
102132
|
async function writeJevEditGuardEnabled(cwd, enabled) {
|
|
101811
|
-
const file =
|
|
102133
|
+
const file = path234.join(cwd, REVIEW_GATE_CONFIG_PATH);
|
|
101812
102134
|
let root = {};
|
|
101813
102135
|
if (await pathExists(file)) {
|
|
101814
102136
|
try {
|
|
@@ -101842,6 +102164,14 @@ function readReasoning2(sessionOverride, globalEffort) {
|
|
|
101842
102164
|
const afterRestart = describeReasoningEffortSource({ globalEffort });
|
|
101843
102165
|
return resolved.source === "session" && afterRestart.source === "env" ? { ...resolved, envWinsOnRestart: true } : resolved;
|
|
101844
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
|
+
}
|
|
101845
102175
|
async function orElse(read, fallback) {
|
|
101846
102176
|
try {
|
|
101847
102177
|
return await read();
|
|
@@ -101873,7 +102203,8 @@ async function loadSettingsSnapshot(cwd, live, configDir) {
|
|
|
101873
102203
|
on: RECOMMENDED_JEV_PROFILE.filter((entry) => jevProfile[entry.key] === true).length,
|
|
101874
102204
|
total: RECOMMENDED_JEV_PROFILE.length
|
|
101875
102205
|
},
|
|
101876
|
-
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)
|
|
101877
102208
|
};
|
|
101878
102209
|
}
|
|
101879
102210
|
var init_settings_state = __esm(() => {
|
|
@@ -101883,6 +102214,8 @@ var init_settings_state = __esm(() => {
|
|
|
101883
102214
|
init_external_switch();
|
|
101884
102215
|
init_permission_mode_config();
|
|
101885
102216
|
init_shell_config();
|
|
102217
|
+
init_rendering_config();
|
|
102218
|
+
init_rendering_mode();
|
|
101886
102219
|
init_jev_edit_guard_config();
|
|
101887
102220
|
init_jev_profile();
|
|
101888
102221
|
init_reasoning_display();
|
|
@@ -101938,7 +102271,7 @@ class NextStepSuggestionGate {
|
|
|
101938
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: .";
|
|
101939
102272
|
|
|
101940
102273
|
// src/lib/version-check.ts
|
|
101941
|
-
import
|
|
102274
|
+
import path235 from "path";
|
|
101942
102275
|
function formatVersionUpdateAdvisory(result) {
|
|
101943
102276
|
if (result.status !== "update-available")
|
|
101944
102277
|
return;
|
|
@@ -102125,7 +102458,7 @@ async function checkVersion(options) {
|
|
|
102125
102458
|
const now = options.now ?? Date.now;
|
|
102126
102459
|
const timestamp = now();
|
|
102127
102460
|
const configDir = ensureKeryxConfigDir(options.cacheDir);
|
|
102128
|
-
const cacheFile =
|
|
102461
|
+
const cacheFile = path235.join(configDir, "version-check.json");
|
|
102129
102462
|
const cache = parseCache(cacheFile);
|
|
102130
102463
|
if (cache?.latestVersion !== undefined && cache.successAt !== undefined && timestamp - cache.successAt >= 0 && timestamp - cache.successAt < SUCCESS_CACHE_TTL_MS) {
|
|
102131
102464
|
return resultFor(options.currentVersion, current, cache.latestVersion, "cache");
|
|
@@ -102453,7 +102786,7 @@ var init_schemas2 = __esm(() => {
|
|
|
102453
102786
|
var init_types7 = () => {};
|
|
102454
102787
|
|
|
102455
102788
|
// src/standard/profiles.ts
|
|
102456
|
-
import
|
|
102789
|
+
import path236 from "path";
|
|
102457
102790
|
import { readFile as readFile108 } from "fs/promises";
|
|
102458
102791
|
function namedEntrypoints(manifest) {
|
|
102459
102792
|
const agentEntrypoints = manifest.agentEntrypoints;
|
|
@@ -102491,7 +102824,7 @@ function enabledModuleKeys(manifest) {
|
|
|
102491
102824
|
}
|
|
102492
102825
|
async function rootEntrypointLinksIndex(cwd, manifest) {
|
|
102493
102826
|
for (const { path: name } of namedEntrypoints(manifest)) {
|
|
102494
|
-
const filePath =
|
|
102827
|
+
const filePath = path236.join(cwd, name);
|
|
102495
102828
|
if (!await pathExists(filePath)) {
|
|
102496
102829
|
continue;
|
|
102497
102830
|
}
|
|
@@ -102503,16 +102836,16 @@ async function rootEntrypointLinksIndex(cwd, manifest) {
|
|
|
102503
102836
|
return false;
|
|
102504
102837
|
}
|
|
102505
102838
|
async function minimalSatisfied(cwd) {
|
|
102506
|
-
const metaprojectRoot =
|
|
102839
|
+
const metaprojectRoot = path236.join(cwd, ".metaproject");
|
|
102507
102840
|
const requiredFiles = ["index.md", "README.md", "metaproject.json"];
|
|
102508
102841
|
const requiredDirs = ["modules", "rules", "skills", "data"];
|
|
102509
102842
|
for (const file of requiredFiles) {
|
|
102510
|
-
if (!await pathExists(
|
|
102843
|
+
if (!await pathExists(path236.join(metaprojectRoot, file))) {
|
|
102511
102844
|
return false;
|
|
102512
102845
|
}
|
|
102513
102846
|
}
|
|
102514
102847
|
for (const dir of requiredDirs) {
|
|
102515
|
-
if (!await pathExists(
|
|
102848
|
+
if (!await pathExists(path236.join(metaprojectRoot, dir))) {
|
|
102516
102849
|
return false;
|
|
102517
102850
|
}
|
|
102518
102851
|
}
|
|
@@ -102525,7 +102858,7 @@ async function ciSatisfied(cwd, manifest) {
|
|
|
102525
102858
|
return false;
|
|
102526
102859
|
}
|
|
102527
102860
|
for (const key of ciModules) {
|
|
102528
|
-
if (await pathExists(
|
|
102861
|
+
if (await pathExists(path236.join(cwd, ".metaproject", "data", key, "artifacts"))) {
|
|
102529
102862
|
return true;
|
|
102530
102863
|
}
|
|
102531
102864
|
}
|
|
@@ -102538,7 +102871,7 @@ async function evaluateProfiles(cwd, manifest) {
|
|
|
102538
102871
|
satisfied.push("minimal");
|
|
102539
102872
|
}
|
|
102540
102873
|
const hasAgentModule = AGENT_MODULES.some((key) => new Set(enabledModuleKeys(manifest)).has(key));
|
|
102541
|
-
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"));
|
|
102542
102875
|
if (agent) {
|
|
102543
102876
|
satisfied.push("agent");
|
|
102544
102877
|
}
|
|
@@ -102571,7 +102904,7 @@ var init_profiles2 = __esm(() => {
|
|
|
102571
102904
|
});
|
|
102572
102905
|
|
|
102573
102906
|
// src/standard/validate.ts
|
|
102574
|
-
import
|
|
102907
|
+
import path237 from "path";
|
|
102575
102908
|
import { readFile as readFile109 } from "fs/promises";
|
|
102576
102909
|
function isPlainObject14(value) {
|
|
102577
102910
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -102700,20 +103033,20 @@ function issue(code, message, fix) {
|
|
|
102700
103033
|
async function validateWorkspace2(cwd) {
|
|
102701
103034
|
const errors = [];
|
|
102702
103035
|
const warnings = [];
|
|
102703
|
-
const metaprojectRoot =
|
|
103036
|
+
const metaprojectRoot = path237.join(cwd, ".metaproject");
|
|
102704
103037
|
const requiredFiles = ["index.md", "README.md", "metaproject.json"];
|
|
102705
103038
|
for (const file of requiredFiles) {
|
|
102706
|
-
if (!await pathExists(
|
|
103039
|
+
if (!await pathExists(path237.join(metaprojectRoot, file))) {
|
|
102707
103040
|
errors.push(issue("missing-required-file", `Required file .metaproject/${file} is missing`, `Run \`keryx init\` to scaffold .metaproject/${file}.`));
|
|
102708
103041
|
}
|
|
102709
103042
|
}
|
|
102710
103043
|
const requiredDirs = ["modules", "rules", "skills", "data"];
|
|
102711
103044
|
for (const dir of requiredDirs) {
|
|
102712
|
-
if (!await pathExists(
|
|
103045
|
+
if (!await pathExists(path237.join(metaprojectRoot, dir))) {
|
|
102713
103046
|
errors.push(issue("missing-required-dir", `Required directory .metaproject/${dir}/ is missing`, `Run \`keryx init\` to create .metaproject/${dir}/.`));
|
|
102714
103047
|
}
|
|
102715
103048
|
}
|
|
102716
|
-
const manifestPath =
|
|
103049
|
+
const manifestPath = path237.join(metaprojectRoot, "metaproject.json");
|
|
102717
103050
|
if (!await pathExists(manifestPath)) {
|
|
102718
103051
|
return { ok: errors.length === 0, errors, warnings };
|
|
102719
103052
|
}
|
|
@@ -102745,7 +103078,7 @@ async function validateWorkspace2(cwd) {
|
|
|
102745
103078
|
if (typeof value !== "string") {
|
|
102746
103079
|
continue;
|
|
102747
103080
|
}
|
|
102748
|
-
if (!await pathExists(
|
|
103081
|
+
if (!await pathExists(path237.join(cwd, value))) {
|
|
102749
103082
|
errors.push(issue("missing-declared-path", `Declared path paths.${key} -> ${value} does not exist`, `Create ${value} or remove paths.${key} from metaproject.json.`));
|
|
102750
103083
|
}
|
|
102751
103084
|
}
|
|
@@ -102755,7 +103088,7 @@ async function validateWorkspace2(cwd) {
|
|
|
102755
103088
|
continue;
|
|
102756
103089
|
}
|
|
102757
103090
|
if (typeof entry.manifest === "string") {
|
|
102758
|
-
if (!await pathExists(
|
|
103091
|
+
if (!await pathExists(path237.join(cwd, entry.manifest))) {
|
|
102759
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.`));
|
|
102760
103093
|
}
|
|
102761
103094
|
}
|
|
@@ -102764,14 +103097,14 @@ async function validateWorkspace2(cwd) {
|
|
|
102764
103097
|
if (typeof value !== "string") {
|
|
102765
103098
|
continue;
|
|
102766
103099
|
}
|
|
102767
|
-
if (!await pathExists(
|
|
103100
|
+
if (!await pathExists(path237.join(cwd, value))) {
|
|
102768
103101
|
const sink = field === "data" ? warnings : errors;
|
|
102769
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.`));
|
|
102770
103103
|
}
|
|
102771
103104
|
}
|
|
102772
103105
|
}
|
|
102773
103106
|
for (const { path: name, scope } of namedEntrypoints(manifest)) {
|
|
102774
|
-
const filePath =
|
|
103107
|
+
const filePath = path237.join(cwd, name);
|
|
102775
103108
|
if (!await pathExists(filePath)) {
|
|
102776
103109
|
continue;
|
|
102777
103110
|
}
|
|
@@ -103436,6 +103769,12 @@ var init_help_groups = __esm(() => {
|
|
|
103436
103769
|
group: "Automation",
|
|
103437
103770
|
summary: "Connect, test or disconnect Telegram for this machine: bot token, one-time code, group. [status]."
|
|
103438
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
|
+
},
|
|
103439
103778
|
{
|
|
103440
103779
|
kind: "cli",
|
|
103441
103780
|
name: "acp",
|
|
@@ -103628,6 +103967,12 @@ var init_help_groups = __esm(() => {
|
|
|
103628
103967
|
group: "Maintenance and diagnostics",
|
|
103629
103968
|
summary: "Bound stores that grow without bound (gdctx raw/artifacts, owner write-conflict sidecars)."
|
|
103630
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
|
+
},
|
|
103631
103976
|
{
|
|
103632
103977
|
kind: "cli",
|
|
103633
103978
|
name: "forgetting",
|
|
@@ -103644,7 +103989,7 @@ var init_help_groups = __esm(() => {
|
|
|
103644
103989
|
});
|
|
103645
103990
|
|
|
103646
103991
|
// src/standard/service.ts
|
|
103647
|
-
import
|
|
103992
|
+
import path238 from "path";
|
|
103648
103993
|
import { readFile as readFile110 } from "fs/promises";
|
|
103649
103994
|
async function runValidate(cwd) {
|
|
103650
103995
|
return validateWorkspace2(cwd);
|
|
@@ -103653,7 +103998,7 @@ async function runDoctor2(cwd) {
|
|
|
103653
103998
|
return validateWorkspace2(cwd);
|
|
103654
103999
|
}
|
|
103655
104000
|
async function readManifest2(cwd) {
|
|
103656
|
-
const manifestPath =
|
|
104001
|
+
const manifestPath = path238.join(cwd, ".metaproject", "metaproject.json");
|
|
103657
104002
|
if (!await pathExists(manifestPath)) {
|
|
103658
104003
|
throw new Error("Metaproject is not initialized (missing .metaproject/metaproject.json). Run `keryx init` first.");
|
|
103659
104004
|
}
|
|
@@ -103680,7 +104025,7 @@ var init_service14 = __esm(() => {
|
|
|
103680
104025
|
|
|
103681
104026
|
// src/rules/entrypoint-inspection.ts
|
|
103682
104027
|
import { readFile as readFile111 } from "fs/promises";
|
|
103683
|
-
import
|
|
104028
|
+
import path239 from "path";
|
|
103684
104029
|
async function inspectEntrypoints(projectRoot, agentEntrypoints, options = {}) {
|
|
103685
104030
|
const git = await resolveGitCommonDir(projectRoot) !== undefined;
|
|
103686
104031
|
const resolved = await resolveProjectEntrypoints(projectRoot, agentEntrypoints);
|
|
@@ -103765,14 +104110,14 @@ async function inspectEntrypoints(projectRoot, agentEntrypoints, options = {}) {
|
|
|
103765
104110
|
codex = { entry, mode: "skip" };
|
|
103766
104111
|
continue;
|
|
103767
104112
|
}
|
|
103768
|
-
const sourceExists = await pathExists(
|
|
104113
|
+
const sourceExists = await pathExists(path239.join(projectRoot, entry.source));
|
|
103769
104114
|
const state = await codexOverrideState(projectRoot, entry);
|
|
103770
104115
|
const bytes = await codexOverrideByteSize(projectRoot, entry);
|
|
103771
104116
|
codex = { entry, mode: "override", sourceExists, state, ...bytes === undefined ? {} : { bytes } };
|
|
103772
104117
|
localTargets.push({ path: entry.path, kind: "codex", exists: state !== "missing", expected: sourceExists });
|
|
103773
104118
|
}
|
|
103774
104119
|
if (targets.claudeSettings.scope === "local") {
|
|
103775
|
-
const exists = await pathExists(
|
|
104120
|
+
const exists = await pathExists(path239.join(projectRoot, ...hooksTarget.split("/")));
|
|
103776
104121
|
localTargets.push({ path: hooksTarget, kind: "claudeSettings", exists, expected: options.hooksExpected === true });
|
|
103777
104122
|
}
|
|
103778
104123
|
const existing = localTargets.filter((target) => target.exists);
|
|
@@ -103901,14 +104246,14 @@ function codexPreviewLines(codex) {
|
|
|
103901
104246
|
}
|
|
103902
104247
|
async function readText3(projectRoot, relativePath) {
|
|
103903
104248
|
try {
|
|
103904
|
-
return await readFile111(
|
|
104249
|
+
return await readFile111(path239.join(projectRoot, ...relativePath.split("/")), "utf8");
|
|
103905
104250
|
} catch {
|
|
103906
104251
|
return;
|
|
103907
104252
|
}
|
|
103908
104253
|
}
|
|
103909
104254
|
function displayPath2(projectRoot, target) {
|
|
103910
|
-
const relative =
|
|
103911
|
-
return relative.startsWith("..") ||
|
|
104255
|
+
const relative = path239.relative(projectRoot, target);
|
|
104256
|
+
return relative.startsWith("..") || path239.isAbsolute(relative) ? target : relative.split(path239.sep).join("/");
|
|
103912
104257
|
}
|
|
103913
104258
|
function isRecord10(value) {
|
|
103914
104259
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -103932,7 +104277,7 @@ var init_entrypoint_inspection = __esm(() => {
|
|
|
103932
104277
|
import { readdir as readdir35, readFile as readFile112 } from "fs/promises";
|
|
103933
104278
|
import { existsSync as existsSync41 } from "fs";
|
|
103934
104279
|
import { homedir as homedir10, tmpdir as tmpdir10 } from "os";
|
|
103935
|
-
import
|
|
104280
|
+
import path240 from "path";
|
|
103936
104281
|
async function registeredWorktrees(cwd) {
|
|
103937
104282
|
if (!Bun.which("git"))
|
|
103938
104283
|
return new Set;
|
|
@@ -103945,7 +104290,7 @@ async function registeredWorktrees(cwd) {
|
|
|
103945
104290
|
for (const line of stdout.split(`
|
|
103946
104291
|
`)) {
|
|
103947
104292
|
if (line.startsWith("worktree ")) {
|
|
103948
|
-
paths.add(
|
|
104293
|
+
paths.add(path240.resolve(line.slice("worktree ".length).trim()));
|
|
103949
104294
|
}
|
|
103950
104295
|
}
|
|
103951
104296
|
return paths;
|
|
@@ -103955,7 +104300,7 @@ async function registeredWorktrees(cwd) {
|
|
|
103955
104300
|
}
|
|
103956
104301
|
async function checkWorktrees(cwd) {
|
|
103957
104302
|
const mainRoot = await resolveMainCheckoutRoot(cwd) ?? cwd;
|
|
103958
|
-
const worktreesDir =
|
|
104303
|
+
const worktreesDir = path240.join(mainRoot, ".claude", "worktrees");
|
|
103959
104304
|
let entries;
|
|
103960
104305
|
try {
|
|
103961
104306
|
entries = (await readdir35(worktreesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
@@ -103967,8 +104312,8 @@ async function checkWorktrees(cwd) {
|
|
|
103967
104312
|
}
|
|
103968
104313
|
const registered = await registeredWorktrees(cwd);
|
|
103969
104314
|
const stale = entries.filter((name) => {
|
|
103970
|
-
const full =
|
|
103971
|
-
return !existsSync41(
|
|
104315
|
+
const full = path240.resolve(worktreesDir, name);
|
|
104316
|
+
return !existsSync41(path240.join(full, ".git")) || !registered.has(full);
|
|
103972
104317
|
});
|
|
103973
104318
|
if (stale.length === 0) {
|
|
103974
104319
|
return {
|
|
@@ -104134,7 +104479,7 @@ async function checkEntrypoints(cwd) {
|
|
|
104134
104479
|
const projectRoot = resolveProjectRoot2(cwd);
|
|
104135
104480
|
let manifest;
|
|
104136
104481
|
try {
|
|
104137
|
-
manifest = JSON.parse(await readFile112(
|
|
104482
|
+
manifest = JSON.parse(await readFile112(path240.join(projectRoot, ".metaproject", "metaproject.json"), "utf8"));
|
|
104138
104483
|
} catch {
|
|
104139
104484
|
manifest = undefined;
|
|
104140
104485
|
}
|
|
@@ -104289,22 +104634,22 @@ async function checkWikiFreshness(cwd) {
|
|
|
104289
104634
|
};
|
|
104290
104635
|
}
|
|
104291
104636
|
function isExcludedProjectRootCandidate(dir) {
|
|
104292
|
-
return dir ===
|
|
104637
|
+
return dir === path240.parse(dir).root || dir === "/tmp" || dir === tmpdir10();
|
|
104293
104638
|
}
|
|
104294
104639
|
function findMetaprojectUpward(cwd, ceiling) {
|
|
104295
|
-
let current =
|
|
104296
|
-
const resolvedCeiling =
|
|
104640
|
+
let current = path240.resolve(cwd);
|
|
104641
|
+
const resolvedCeiling = path240.resolve(ceiling);
|
|
104297
104642
|
for (;; ) {
|
|
104298
104643
|
if (isExcludedProjectRootCandidate(current)) {
|
|
104299
104644
|
return;
|
|
104300
104645
|
}
|
|
104301
|
-
if (existsSync41(
|
|
104646
|
+
if (existsSync41(path240.join(current, ".metaproject"))) {
|
|
104302
104647
|
return current;
|
|
104303
104648
|
}
|
|
104304
104649
|
if (current === resolvedCeiling) {
|
|
104305
104650
|
return;
|
|
104306
104651
|
}
|
|
104307
|
-
const parent =
|
|
104652
|
+
const parent = path240.dirname(current);
|
|
104308
104653
|
if (parent === current) {
|
|
104309
104654
|
return;
|
|
104310
104655
|
}
|
|
@@ -104318,7 +104663,7 @@ async function isKeryxProject(cwd, overrides = {}) {
|
|
|
104318
104663
|
return true;
|
|
104319
104664
|
}
|
|
104320
104665
|
const mainRoot = await resolveMainCheckoutRoot(cwd);
|
|
104321
|
-
return mainRoot !== undefined && existsSync41(
|
|
104666
|
+
return mainRoot !== undefined && existsSync41(path240.join(mainRoot, ".metaproject"));
|
|
104322
104667
|
}
|
|
104323
104668
|
async function buildDoctorReport(cwd, env = process.env, overrides = {}) {
|
|
104324
104669
|
const globalChecks = await Promise.all([
|
|
@@ -104406,7 +104751,7 @@ var init_doctor2 = __esm(() => {
|
|
|
104406
104751
|
});
|
|
104407
104752
|
|
|
104408
104753
|
// src/commands/goal-command.ts
|
|
104409
|
-
import
|
|
104754
|
+
import path241 from "path";
|
|
104410
104755
|
import { readFile as readFile113 } from "fs/promises";
|
|
104411
104756
|
function parseGoalArgs(rest) {
|
|
104412
104757
|
const trimmed = rest.trim();
|
|
@@ -104479,7 +104824,7 @@ function getFlowService() {
|
|
|
104479
104824
|
async function autoProvisionFlow(cwd, goalText) {
|
|
104480
104825
|
const service = getFlowService();
|
|
104481
104826
|
const result = await service.init({ cwd, title: goalText });
|
|
104482
|
-
const acFile =
|
|
104827
|
+
const acFile = path241.join(cwd, result.dir, "acceptance-criteria.md");
|
|
104483
104828
|
await writeFileAtomic(acFile, [
|
|
104484
104829
|
"# Acceptance Criteria",
|
|
104485
104830
|
"",
|
|
@@ -107517,7 +107862,7 @@ __export(exports_flow, {
|
|
|
107517
107862
|
signerIdentityArgs: () => signerIdentityArgs
|
|
107518
107863
|
});
|
|
107519
107864
|
import { randomInt } from "crypto";
|
|
107520
|
-
import
|
|
107865
|
+
import path242 from "path";
|
|
107521
107866
|
function parseTaskKind(raw) {
|
|
107522
107867
|
if (raw === undefined) {
|
|
107523
107868
|
return;
|
|
@@ -108432,7 +108777,7 @@ async function runSchema(args) {
|
|
|
108432
108777
|
`;
|
|
108433
108778
|
const out = optionValue(args, "--out");
|
|
108434
108779
|
if (out) {
|
|
108435
|
-
const target =
|
|
108780
|
+
const target = path242.isAbsolute(out) ? out : path242.join(process.cwd(), out);
|
|
108436
108781
|
await writeFileAtomic(target, json);
|
|
108437
108782
|
console.log(` ${style.green(symbols.ok)} Wrote flow-state schema ${style.cyan(symbols.arrow)} ${out}`);
|
|
108438
108783
|
return;
|
|
@@ -108724,7 +109069,7 @@ var init_lifecycle_flag = __esm(() => {
|
|
|
108724
109069
|
// src/sac/catch-up.ts
|
|
108725
109070
|
import { randomUUID as randomUUID30 } from "crypto";
|
|
108726
109071
|
import { readdir as readdir36, stat as stat24 } from "fs/promises";
|
|
108727
|
-
import
|
|
109072
|
+
import path243 from "path";
|
|
108728
109073
|
async function buildCatchUp(input) {
|
|
108729
109074
|
const [proposals, sessionCategories, lifecycleFlagsAll, unreviewedPathsAll] = await Promise.all([
|
|
108730
109075
|
collectProposals(input.cwd, input.workspaceId),
|
|
@@ -108843,7 +109188,7 @@ async function readExternalUnboundCandidates(cwd) {
|
|
|
108843
109188
|
}
|
|
108844
109189
|
return groups;
|
|
108845
109190
|
}, []));
|
|
108846
|
-
const metaPath =
|
|
109191
|
+
const metaPath = path243.join(extDir, `${id}.json`);
|
|
108847
109192
|
if (await pathExists(unboundDismissedReceiptPath(metaPath)))
|
|
108848
109193
|
continue;
|
|
108849
109194
|
candidates.push({
|
|
@@ -108857,7 +109202,7 @@ async function readExternalUnboundCandidates(cwd) {
|
|
|
108857
109202
|
return candidates;
|
|
108858
109203
|
}
|
|
108859
109204
|
async function readNewestUnboundCandidateForExternal(cwd, externalSessionId) {
|
|
108860
|
-
const evidenceDir =
|
|
109205
|
+
const evidenceDir = path243.join(externalSlatesDir(cwd), externalSessionId);
|
|
108861
109206
|
let entries;
|
|
108862
109207
|
try {
|
|
108863
109208
|
entries = (await readdir36(evidenceDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
|
|
@@ -108866,7 +109211,7 @@ async function readNewestUnboundCandidateForExternal(cwd, externalSessionId) {
|
|
|
108866
109211
|
}
|
|
108867
109212
|
entries.sort();
|
|
108868
109213
|
for (let i = entries.length - 1;i >= 0; i--) {
|
|
108869
|
-
const evidencePath =
|
|
109214
|
+
const evidencePath = path243.join(evidenceDir, entries[i]);
|
|
108870
109215
|
if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
|
|
108871
109216
|
continue;
|
|
108872
109217
|
const result = readConfigFile(evidencePath);
|
|
@@ -108907,7 +109252,7 @@ async function collectSessionCategories(cwd) {
|
|
|
108907
109252
|
}
|
|
108908
109253
|
async function collectReceiptTargets(cwd, owner) {
|
|
108909
109254
|
const targets = new Set;
|
|
108910
|
-
const workspacesDir =
|
|
109255
|
+
const workspacesDir = path243.join(cwd, ".metaproject", "workspaces");
|
|
108911
109256
|
let workspaceIds;
|
|
108912
109257
|
try {
|
|
108913
109258
|
workspaceIds = (await readdir36(workspacesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
@@ -108915,7 +109260,7 @@ async function collectReceiptTargets(cwd, owner) {
|
|
|
108915
109260
|
return targets;
|
|
108916
109261
|
}
|
|
108917
109262
|
for (const workspaceId of workspaceIds) {
|
|
108918
|
-
const receiptsDir =
|
|
109263
|
+
const receiptsDir = path243.join(workspacesDir, workspaceId, `${owner}-write-receipts`);
|
|
108919
109264
|
let files;
|
|
108920
109265
|
try {
|
|
108921
109266
|
files = (await readdir36(receiptsDir)).filter((name) => name.endsWith(".json"));
|
|
@@ -108923,7 +109268,7 @@ async function collectReceiptTargets(cwd, owner) {
|
|
|
108923
109268
|
continue;
|
|
108924
109269
|
}
|
|
108925
109270
|
for (const file of files) {
|
|
108926
|
-
const result = readConfigFile(
|
|
109271
|
+
const result = readConfigFile(path243.join(receiptsDir, file));
|
|
108927
109272
|
if (!result.ok)
|
|
108928
109273
|
continue;
|
|
108929
109274
|
try {
|
|
@@ -108966,7 +109311,7 @@ async function findSkillFiles(dir) {
|
|
|
108966
109311
|
}
|
|
108967
109312
|
const found = [];
|
|
108968
109313
|
for (const entry of entries) {
|
|
108969
|
-
const full =
|
|
109314
|
+
const full = path243.join(dir, entry.name);
|
|
108970
109315
|
if (entry.isDirectory()) {
|
|
108971
109316
|
found.push(...await findSkillFiles(full));
|
|
108972
109317
|
} else if (entry.isFile() && entry.name === "SKILL.md") {
|
|
@@ -109025,10 +109370,10 @@ async function detectUnreviewedSacPathChanges(cwd, sessions) {
|
|
|
109025
109370
|
changedAt: attribution.changedAt
|
|
109026
109371
|
});
|
|
109027
109372
|
}
|
|
109028
|
-
const sacSkillsDir =
|
|
109373
|
+
const sacSkillsDir = path243.join(cwd, ".metaproject", "project-skills", "sac");
|
|
109029
109374
|
const skillFiles = await findSkillFiles(sacSkillsDir);
|
|
109030
109375
|
for (const absolutePath of skillFiles) {
|
|
109031
|
-
const targetRef =
|
|
109376
|
+
const targetRef = path243.relative(path243.join(cwd, ".metaproject"), absolutePath).split(path243.sep).join("/");
|
|
109032
109377
|
if (skillReceipts.has(targetRef))
|
|
109033
109378
|
continue;
|
|
109034
109379
|
const attribution = await attributeToSession(absolutePath, scoped);
|
|
@@ -109046,12 +109391,12 @@ async function detectUnreviewedSacPathChanges(cwd, sessions) {
|
|
|
109046
109391
|
return items;
|
|
109047
109392
|
}
|
|
109048
109393
|
async function isSlateEngaged(dir) {
|
|
109049
|
-
if (await pathExists(
|
|
109394
|
+
if (await pathExists(path243.join(dir, "slate.json")))
|
|
109050
109395
|
return true;
|
|
109051
|
-
if (await pathExists(
|
|
109396
|
+
if (await pathExists(path243.join(dir, "terminal-state.json")))
|
|
109052
109397
|
return true;
|
|
109053
109398
|
try {
|
|
109054
|
-
const entries = await readdir36(
|
|
109399
|
+
const entries = await readdir36(path243.join(dir, "slate-archive"));
|
|
109055
109400
|
return entries.length > 0;
|
|
109056
109401
|
} catch {
|
|
109057
109402
|
return false;
|
|
@@ -109076,7 +109421,7 @@ async function safeReadSlate(dir) {
|
|
|
109076
109421
|
}
|
|
109077
109422
|
}
|
|
109078
109423
|
async function readTerminalState(dir) {
|
|
109079
|
-
const result = readConfigFile(
|
|
109424
|
+
const result = readConfigFile(path243.join(dir, "terminal-state.json"));
|
|
109080
109425
|
if (!result.ok) {
|
|
109081
109426
|
return;
|
|
109082
109427
|
}
|
|
@@ -109100,7 +109445,7 @@ async function resolveUnboundCandidateTarget(cwd, target) {
|
|
|
109100
109445
|
if (session === undefined) {
|
|
109101
109446
|
throw new Error(`No session or evidence path matches "${target}"`);
|
|
109102
109447
|
}
|
|
109103
|
-
const archiveDir =
|
|
109448
|
+
const archiveDir = path243.join(sessionDir(session.projectPath, session.id), "slate-archive");
|
|
109104
109449
|
let entries;
|
|
109105
109450
|
try {
|
|
109106
109451
|
entries = (await readdir36(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
|
|
@@ -109110,7 +109455,7 @@ async function resolveUnboundCandidateTarget(cwd, target) {
|
|
|
109110
109455
|
if (entries.length === 0)
|
|
109111
109456
|
throw new Error(`No unbound-candidate artifacts for session ${session.id}`);
|
|
109112
109457
|
entries.sort();
|
|
109113
|
-
return
|
|
109458
|
+
return path243.join(archiveDir, entries[entries.length - 1]);
|
|
109114
109459
|
}
|
|
109115
109460
|
async function dismissUnboundByTarget(cwd, target, reason) {
|
|
109116
109461
|
const evidencePath = await resolveUnboundCandidateTarget(cwd, target);
|
|
@@ -109136,7 +109481,7 @@ async function dismissUnboundCandidate(candidatePath, reason) {
|
|
|
109136
109481
|
return { removed: candidatePath, receipt: receiptPath };
|
|
109137
109482
|
}
|
|
109138
109483
|
async function readNewestUnboundCandidate(dir) {
|
|
109139
|
-
const archiveDir =
|
|
109484
|
+
const archiveDir = path243.join(dir, "slate-archive");
|
|
109140
109485
|
let entries;
|
|
109141
109486
|
try {
|
|
109142
109487
|
entries = (await readdir36(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
|
|
@@ -109145,7 +109490,7 @@ async function readNewestUnboundCandidate(dir) {
|
|
|
109145
109490
|
}
|
|
109146
109491
|
entries.sort();
|
|
109147
109492
|
for (let i = entries.length - 1;i >= 0; i--) {
|
|
109148
|
-
const evidencePath =
|
|
109493
|
+
const evidencePath = path243.join(archiveDir, entries[i]);
|
|
109149
109494
|
if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
|
|
109150
109495
|
continue;
|
|
109151
109496
|
const result = readConfigFile(evidencePath);
|
|
@@ -109175,7 +109520,7 @@ function isFailureOutcome2(g) {
|
|
|
109175
109520
|
return g.outcome === "error" || g.outcome === "no_credential" || g.outcome === "conflict";
|
|
109176
109521
|
}
|
|
109177
109522
|
async function readNewestWrapUpOutcome(dir) {
|
|
109178
|
-
const archiveDir =
|
|
109523
|
+
const archiveDir = path243.join(dir, "slate-archive");
|
|
109179
109524
|
let entries;
|
|
109180
109525
|
try {
|
|
109181
109526
|
entries = (await readdir36(archiveDir)).filter((name) => name.endsWith("-wrap-up-outcome.json"));
|
|
@@ -109184,7 +109529,7 @@ async function readNewestWrapUpOutcome(dir) {
|
|
|
109184
109529
|
}
|
|
109185
109530
|
entries.sort();
|
|
109186
109531
|
for (let i = entries.length - 1;i >= 0; i--) {
|
|
109187
|
-
const evidencePath =
|
|
109532
|
+
const evidencePath = path243.join(archiveDir, entries[i]);
|
|
109188
109533
|
const result = readConfigFile(evidencePath);
|
|
109189
109534
|
if (!result.ok) {
|
|
109190
109535
|
continue;
|
|
@@ -109934,12 +110279,12 @@ var init_extract3 = __esm(() => {
|
|
|
109934
110279
|
// src/product/corpus.ts
|
|
109935
110280
|
import { createHash as createHash62 } from "crypto";
|
|
109936
110281
|
import { readdir as readdir37, readFile as readFile115 } from "fs/promises";
|
|
109937
|
-
import
|
|
110282
|
+
import path244 from "path";
|
|
109938
110283
|
function flowsDir(cwd) {
|
|
109939
|
-
return
|
|
110284
|
+
return path244.join(cwd, ".metaproject", "flows");
|
|
109940
110285
|
}
|
|
109941
110286
|
function requirementsDir(cwd) {
|
|
109942
|
-
return
|
|
110287
|
+
return path244.join(cwd, "docs", "requirements");
|
|
109943
110288
|
}
|
|
109944
110289
|
async function readOptional(file) {
|
|
109945
110290
|
try {
|
|
@@ -109992,23 +110337,23 @@ async function corpusFingerprint(cwd) {
|
|
|
109992
110337
|
for (const name of await listFlowPackages(cwd)) {
|
|
109993
110338
|
lines.push(`package\x00flow\x00${name}`);
|
|
109994
110339
|
for (const file of FLOW_SOURCE_FILES) {
|
|
109995
|
-
const content = await readBytes(
|
|
110340
|
+
const content = await readBytes(path244.join(flowsDir(cwd), name, file));
|
|
109996
110341
|
lines.push(`file\x00${FLOW_PREFIX}${name}/${file}\x00${digestOf4(content)}`);
|
|
109997
110342
|
}
|
|
109998
110343
|
}
|
|
109999
110344
|
for (const name of await requirementPackages(cwd)) {
|
|
110000
110345
|
lines.push(`package\x00docpack\x00${name}`);
|
|
110001
|
-
const dir =
|
|
110346
|
+
const dir = path244.join(requirementsDir(cwd), name);
|
|
110002
110347
|
for (const candidate of PRIMARY_DOCS) {
|
|
110003
|
-
const content = await readBytes(
|
|
110348
|
+
const content = await readBytes(path244.join(dir, candidate));
|
|
110004
110349
|
if (content === null)
|
|
110005
110350
|
continue;
|
|
110006
110351
|
lines.push(`file\x00docs/requirements/${name}/${candidate}\x00${digestOf4(content)}`);
|
|
110007
110352
|
break;
|
|
110008
110353
|
}
|
|
110009
|
-
const readme = await readBytes(
|
|
110354
|
+
const readme = await readBytes(path244.join(dir, "README.md"));
|
|
110010
110355
|
lines.push(`file\x00docs/requirements/${name}/README.md\x00${digestOf4(readme)}`);
|
|
110011
|
-
const specification = await readBytes(
|
|
110356
|
+
const specification = await readBytes(path244.join(dir, "specification.md"));
|
|
110012
110357
|
lines.push(`file\x00docs/requirements/${name}/specification.md\x00${digestOf4(specification)}`);
|
|
110013
110358
|
}
|
|
110014
110359
|
return createHash62("sha256").update([...lines].sort().join(`
|
|
@@ -110050,19 +110395,19 @@ async function buildIntentIndex(cwd) {
|
|
|
110050
110395
|
const intents = [];
|
|
110051
110396
|
const failures = [];
|
|
110052
110397
|
for (const name of await listFlowPackages(cwd)) {
|
|
110053
|
-
const dir =
|
|
110398
|
+
const dir = path244.join(flowsDir(cwd), name);
|
|
110054
110399
|
const repoPath = `${FLOW_PREFIX}${name}`;
|
|
110055
110400
|
try {
|
|
110056
|
-
const flowJson = await readSource(
|
|
110401
|
+
const flowJson = await readSource(path244.join(dir, "flow.json"), `${repoPath}/flow.json`);
|
|
110057
110402
|
if (flowJson === null) {
|
|
110058
110403
|
failures.push(`${repoPath}: no flow.json`);
|
|
110059
110404
|
continue;
|
|
110060
110405
|
}
|
|
110061
|
-
const journal = await readSource(
|
|
110406
|
+
const journal = await readSource(path244.join(dir, "journal.md"), `${repoPath}/journal.md`);
|
|
110062
110407
|
intents.push(extractFlowIntent({
|
|
110063
110408
|
flowJson,
|
|
110064
|
-
description: await readSource(
|
|
110065
|
-
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`),
|
|
110066
110411
|
journal
|
|
110067
110412
|
}, repoPath));
|
|
110068
110413
|
const problem = observationProblem(journal);
|
|
@@ -110073,17 +110418,17 @@ async function buildIntentIndex(cwd) {
|
|
|
110073
110418
|
}
|
|
110074
110419
|
}
|
|
110075
110420
|
for (const name of await requirementPackages(cwd)) {
|
|
110076
|
-
const dir =
|
|
110421
|
+
const dir = path244.join(requirementsDir(cwd), name);
|
|
110077
110422
|
const repoPath = `docs/requirements/${name}`;
|
|
110078
110423
|
try {
|
|
110079
110424
|
let primary = null;
|
|
110080
110425
|
for (const candidate of PRIMARY_DOCS) {
|
|
110081
|
-
primary = await readSource(
|
|
110426
|
+
primary = await readSource(path244.join(dir, candidate), `${repoPath}/${candidate}`);
|
|
110082
110427
|
if (primary !== null)
|
|
110083
110428
|
break;
|
|
110084
110429
|
}
|
|
110085
|
-
const specification = await readSource(
|
|
110086
|
-
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`);
|
|
110087
110432
|
const source = { name, primary, specification, readme };
|
|
110088
110433
|
intents.push(extractDocpackIntent(source, repoPath));
|
|
110089
110434
|
const problem = docpackObservationProblem(source);
|
|
@@ -110143,12 +110488,12 @@ var init_by_origin = __esm(() => {
|
|
|
110143
110488
|
|
|
110144
110489
|
// src/product/store.ts
|
|
110145
110490
|
import { readFile as readFile116 } from "fs/promises";
|
|
110146
|
-
import
|
|
110491
|
+
import path245 from "path";
|
|
110147
110492
|
function productDataRoot(cwd) {
|
|
110148
|
-
return
|
|
110493
|
+
return path245.join(cwd, ".metaproject", "data", "product");
|
|
110149
110494
|
}
|
|
110150
110495
|
function indexPath(cwd) {
|
|
110151
|
-
return
|
|
110496
|
+
return path245.join(productDataRoot(cwd), "index.json");
|
|
110152
110497
|
}
|
|
110153
110498
|
function serializeIndex(index) {
|
|
110154
110499
|
return `${JSON.stringify(index, null, 2)}
|
|
@@ -111639,9 +111984,9 @@ var init_repair = __esm(() => {
|
|
|
111639
111984
|
|
|
111640
111985
|
// src/review/managed.ts
|
|
111641
111986
|
import { mkdir as mkdir55, readFile as readFile117, readdir as readdir38, realpath as realpath18, rm as rm16, stat as stat25 } from "fs/promises";
|
|
111642
|
-
import
|
|
111987
|
+
import path246 from "path";
|
|
111643
111988
|
function reviewsRoot(cwd) {
|
|
111644
|
-
return
|
|
111989
|
+
return path246.join(cwd, ".metaproject", "reviews");
|
|
111645
111990
|
}
|
|
111646
111991
|
async function resolveGitHead2(cwd) {
|
|
111647
111992
|
try {
|
|
@@ -111774,26 +112119,26 @@ async function createManagedReviewPackage(input) {
|
|
|
111774
112119
|
throw new Error(`Refusing to record findings that do not satisfy review-finding.schema.json: ${contractErrors.map((error) => `${error.path} ${error.message}`).join("; ")}`);
|
|
111775
112120
|
}
|
|
111776
112121
|
await mkdir55(packageDir, { recursive: true });
|
|
111777
|
-
await writeFileAtomic(
|
|
112122
|
+
await writeFileAtomic(path246.join(packageDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
111778
112123
|
`);
|
|
111779
|
-
await writeFileAtomic(
|
|
111780
|
-
await writeFileAtomic(
|
|
111781
|
-
await writeFileAtomic(
|
|
111782
|
-
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)}
|
|
111783
112128
|
`);
|
|
111784
|
-
await writeFileAtomic(
|
|
111785
|
-
await writeFileAtomic(
|
|
112129
|
+
await writeFileAtomic(path246.join(packageDir, "learning.md"), renderLearning(findings));
|
|
112130
|
+
await writeFileAtomic(path246.join(packageDir, "decisions.md"), renderDecisions(findings));
|
|
111786
112131
|
await persistScreenRadius(packageDir, scopeB.resolved);
|
|
111787
112132
|
const reviewNotes = await writeReviewNotes(findings, {
|
|
111788
112133
|
cwd: input.cwd,
|
|
111789
112134
|
reviewId,
|
|
111790
112135
|
head,
|
|
111791
|
-
packagePath:
|
|
112136
|
+
packagePath: path246.relative(input.cwd, packageDir),
|
|
111792
112137
|
...input.now === undefined ? {} : { now: input.now }
|
|
111793
112138
|
});
|
|
111794
112139
|
return {
|
|
111795
112140
|
reviewId,
|
|
111796
|
-
path:
|
|
112141
|
+
path: path246.relative(input.cwd, packageDir),
|
|
111797
112142
|
manifest,
|
|
111798
112143
|
verification: verification.counts,
|
|
111799
112144
|
verificationRejections: verification.rejections,
|
|
@@ -111810,7 +112155,7 @@ async function screenScopeBFindings(input, packageDir, findings) {
|
|
|
111810
112155
|
const scopeB = findings.filter((finding) => isBlastRadiusScopedFinding(finding, reviewers));
|
|
111811
112156
|
if (resolved === undefined) {
|
|
111812
112157
|
if (scopeB.length > 0) {
|
|
111813
|
-
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.`);
|
|
111814
112159
|
}
|
|
111815
112160
|
return { retained: [...findings], record: { source: "none", scopeBFindings: 0 }, resolved: undefined };
|
|
111816
112161
|
}
|
|
@@ -111827,13 +112172,13 @@ async function screenScopeBFindings(input, packageDir, findings) {
|
|
|
111827
112172
|
};
|
|
111828
112173
|
}
|
|
111829
112174
|
function handoffBlastRadiusPath(packageDir) {
|
|
111830
|
-
return
|
|
112175
|
+
return path246.join(path246.dirname(packageDir), BLAST_RADIUS_ARTIFACT);
|
|
111831
112176
|
}
|
|
111832
112177
|
async function resolveScreenRadius(input, packageDir) {
|
|
111833
112178
|
if (input.blastRadius !== undefined) {
|
|
111834
112179
|
return { radius: input.blastRadius, source: "input" };
|
|
111835
112180
|
}
|
|
111836
|
-
const inPackage =
|
|
112181
|
+
const inPackage = path246.join(packageDir, BLAST_RADIUS_ARTIFACT);
|
|
111837
112182
|
const fromPackage = await readBlastRadiusRecordFile(inPackage);
|
|
111838
112183
|
if (fromPackage !== undefined) {
|
|
111839
112184
|
return { radius: fromPackage, source: "package", file: inPackage };
|
|
@@ -111859,7 +112204,7 @@ async function persistScreenRadius(packageDir, resolved) {
|
|
|
111859
112204
|
if (resolved === undefined) {
|
|
111860
112205
|
return;
|
|
111861
112206
|
}
|
|
111862
|
-
const inPackage =
|
|
112207
|
+
const inPackage = path246.join(packageDir, BLAST_RADIUS_ARTIFACT);
|
|
111863
112208
|
if (resolved.source !== "package") {
|
|
111864
112209
|
await writeFileAtomic(inPackage, `${JSON.stringify(resolved.radius, null, 2)}
|
|
111865
112210
|
`);
|
|
@@ -111890,12 +112235,12 @@ function fromVerifierRefutations(findings) {
|
|
|
111890
112235
|
}));
|
|
111891
112236
|
}
|
|
111892
112237
|
async function getManagedReviewStatus(cwd, ref) {
|
|
111893
|
-
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");
|
|
111894
112239
|
return JSON.parse(await readFile117(manifestPath, "utf8"));
|
|
111895
112240
|
}
|
|
111896
112241
|
async function completeManagedReview(cwd, ref, options = {}) {
|
|
111897
112242
|
const packageDir = await resolveReviewPackagePath(cwd, ref);
|
|
111898
|
-
const manifestPath =
|
|
112243
|
+
const manifestPath = path246.join(packageDir, "manifest.json");
|
|
111899
112244
|
const manifest = JSON.parse(await readFile117(manifestPath, "utf8"));
|
|
111900
112245
|
const missing = await missingArtifacts(packageDir);
|
|
111901
112246
|
if (missing.length > 0) {
|
|
@@ -111916,12 +112261,12 @@ async function completeManagedReview(cwd, ref, options = {}) {
|
|
|
111916
112261
|
}
|
|
111917
112262
|
await writeFileAtomic(manifestPath, `${JSON.stringify(updated, null, 2)}
|
|
111918
112263
|
`);
|
|
111919
|
-
const recorded = JSON.parse(await readFile117(
|
|
112264
|
+
const recorded = JSON.parse(await readFile117(path246.join(packageDir, "findings.json"), "utf8"));
|
|
111920
112265
|
const reviewNotes = await writeReviewNotes(recorded, {
|
|
111921
112266
|
cwd,
|
|
111922
112267
|
reviewId: updated.reviewId,
|
|
111923
112268
|
head: updated.target.head ?? null,
|
|
111924
|
-
packagePath:
|
|
112269
|
+
packagePath: path246.relative(cwd, packageDir),
|
|
111925
112270
|
...options.now === undefined ? {} : { now: options.now }
|
|
111926
112271
|
});
|
|
111927
112272
|
return { manifest: updated, reviewNotes, dispositions: tallyDispositions(recorded) };
|
|
@@ -111987,16 +112332,16 @@ async function resolvePackageFlow(input) {
|
|
|
111987
112332
|
}
|
|
111988
112333
|
function packagePath(cwd, mode, reviewId, flowMatch) {
|
|
111989
112334
|
if ((mode === "attach-review" || mode === "ingest") && flowMatch) {
|
|
111990
|
-
return
|
|
112335
|
+
return path246.join(flowsRoot(cwd), flowMatch.dir, "reviews", reviewId);
|
|
111991
112336
|
}
|
|
111992
112337
|
if (mode === "attach-review") {
|
|
111993
112338
|
throw new Error("attach-review requires an explicit or matched flow");
|
|
111994
112339
|
}
|
|
111995
|
-
return
|
|
112340
|
+
return path246.join(reviewsRoot(cwd), reviewId);
|
|
111996
112341
|
}
|
|
111997
112342
|
function buildManifest(args) {
|
|
111998
112343
|
const crossFamily = args.input.crossFamilyReview;
|
|
111999
|
-
const artifactPath = (name) =>
|
|
112344
|
+
const artifactPath = (name) => path246.relative(args.input.cwd, path246.join(args.packageDir, name));
|
|
112000
112345
|
const manifest = {
|
|
112001
112346
|
schemaVersion: 1,
|
|
112002
112347
|
reviewId: args.reviewId,
|
|
@@ -112043,7 +112388,7 @@ async function readReport(input) {
|
|
|
112043
112388
|
return input.reportText;
|
|
112044
112389
|
}
|
|
112045
112390
|
if (input.reportPath) {
|
|
112046
|
-
return readFile117(
|
|
112391
|
+
return readFile117(path246.resolve(input.cwd, input.reportPath), "utf8");
|
|
112047
112392
|
}
|
|
112048
112393
|
if (input.mode === "ingest") {
|
|
112049
112394
|
throw new Error("ingest requires --report or reportText");
|
|
@@ -112054,7 +112399,7 @@ No reviewer findings recorded yet.
|
|
|
112054
112399
|
`;
|
|
112055
112400
|
}
|
|
112056
112401
|
function reportLabel(input) {
|
|
112057
|
-
return input.reportPath ?
|
|
112402
|
+
return input.reportPath ? path246.resolve(input.cwd, input.reportPath) : "the report text passed to keryx review";
|
|
112058
112403
|
}
|
|
112059
112404
|
async function normalizeFindings(args) {
|
|
112060
112405
|
if (args.source !== undefined) {
|
|
@@ -112109,16 +112454,16 @@ async function anchorFindings(findings, input, head) {
|
|
|
112109
112454
|
}
|
|
112110
112455
|
}
|
|
112111
112456
|
async function containedRelativePath(cwd, relative) {
|
|
112112
|
-
const root = await realpath18(cwd).catch(() =>
|
|
112113
|
-
const resolved =
|
|
112457
|
+
const root = await realpath18(cwd).catch(() => path246.resolve(cwd));
|
|
112458
|
+
const resolved = path246.resolve(root, relative);
|
|
112114
112459
|
const real = await realpath18(resolved).catch(() => null);
|
|
112115
|
-
if (real === null || real !== root && !real.startsWith(`${root}${
|
|
112460
|
+
if (real === null || real !== root && !real.startsWith(`${root}${path246.sep}`)) {
|
|
112116
112461
|
return null;
|
|
112117
112462
|
}
|
|
112118
112463
|
return real;
|
|
112119
112464
|
}
|
|
112120
112465
|
async function readFileAtCommit(cwd, commit, absolute) {
|
|
112121
|
-
const relative =
|
|
112466
|
+
const relative = path246.relative(await realpath18(cwd).catch(() => path246.resolve(cwd)), absolute);
|
|
112122
112467
|
try {
|
|
112123
112468
|
const proc = Bun.spawn(["git", "show", `${commit}:${relative}`], {
|
|
112124
112469
|
cwd,
|
|
@@ -112137,10 +112482,10 @@ async function readFileAtCommit(cwd, commit, absolute) {
|
|
|
112137
112482
|
}
|
|
112138
112483
|
function readFromWorkingTree(input) {
|
|
112139
112484
|
return async (relative) => {
|
|
112140
|
-
const root = await realpath18(input.cwd).catch(() =>
|
|
112141
|
-
const resolved =
|
|
112485
|
+
const root = await realpath18(input.cwd).catch(() => path246.resolve(input.cwd));
|
|
112486
|
+
const resolved = path246.resolve(root, relative);
|
|
112142
112487
|
const real = await realpath18(resolved).catch(() => null);
|
|
112143
|
-
if (real === null || real !== root && !real.startsWith(`${root}${
|
|
112488
|
+
if (real === null || real !== root && !real.startsWith(`${root}${path246.sep}`)) {
|
|
112144
112489
|
return null;
|
|
112145
112490
|
}
|
|
112146
112491
|
try {
|
|
@@ -112272,7 +112617,7 @@ function refutedClassification(state) {
|
|
|
112272
112617
|
return state === "dismissed-incorrect" ? "false_positive" : "out_of_scope";
|
|
112273
112618
|
}
|
|
112274
112619
|
async function recordDispositions(packageDir, records) {
|
|
112275
|
-
const findingsPath =
|
|
112620
|
+
const findingsPath = path246.join(packageDir, "findings.json");
|
|
112276
112621
|
const findings = JSON.parse(await readFile117(findingsPath, "utf8"));
|
|
112277
112622
|
const schema = await loadSchema("review-finding");
|
|
112278
112623
|
const dispositionSchema = schema.properties?.["disposition"];
|
|
@@ -112599,7 +112944,7 @@ function upsertPreFilterScopeBlock(text, block) {
|
|
|
112599
112944
|
${body}`;
|
|
112600
112945
|
}
|
|
112601
112946
|
async function readPreFilterScopeBlock(packageDir) {
|
|
112602
|
-
const scopePath =
|
|
112947
|
+
const scopePath = path246.join(packageDir, "scope.md");
|
|
112603
112948
|
if (!await pathExists(scopePath)) {
|
|
112604
112949
|
return;
|
|
112605
112950
|
}
|
|
@@ -112773,39 +113118,39 @@ function matchReason(flow, dir, kind, ref) {
|
|
|
112773
113118
|
async function missingArtifacts(packageDir) {
|
|
112774
113119
|
const missing = [];
|
|
112775
113120
|
for (const artifact of ["scope.md", "coverage.md", "report.md", "findings.json", "learning.md", "decisions.md"]) {
|
|
112776
|
-
if (!await pathExists(
|
|
113121
|
+
if (!await pathExists(path246.join(packageDir, artifact))) {
|
|
112777
113122
|
missing.push(artifact);
|
|
112778
113123
|
}
|
|
112779
113124
|
}
|
|
112780
113125
|
return missing;
|
|
112781
113126
|
}
|
|
112782
113127
|
async function resolveReviewPackagePath(cwd, ref) {
|
|
112783
|
-
const absolute =
|
|
112784
|
-
if (await pathExists(
|
|
113128
|
+
const absolute = path246.resolve(cwd, ref);
|
|
113129
|
+
if (await pathExists(path246.join(absolute, "manifest.json"))) {
|
|
112785
113130
|
return absolute;
|
|
112786
113131
|
}
|
|
112787
113132
|
if (await pathExists(absolute) && absolute.endsWith("manifest.json")) {
|
|
112788
|
-
return
|
|
113133
|
+
return path246.dirname(absolute);
|
|
112789
113134
|
}
|
|
112790
|
-
const standalone =
|
|
112791
|
-
if (await pathExists(
|
|
113135
|
+
const standalone = path246.join(reviewsRoot(cwd), ref);
|
|
113136
|
+
if (await pathExists(path246.join(standalone, "manifest.json"))) {
|
|
112792
113137
|
return standalone;
|
|
112793
113138
|
}
|
|
112794
113139
|
for (const flowDir of await listFlowDirs(cwd)) {
|
|
112795
|
-
const reviewsDir =
|
|
113140
|
+
const reviewsDir = path246.join(flowsRoot(cwd), flowDir, "reviews");
|
|
112796
113141
|
if (!await pathExists(reviewsDir)) {
|
|
112797
113142
|
continue;
|
|
112798
113143
|
}
|
|
112799
113144
|
for (const entry of await readdir38(reviewsDir, { withFileTypes: true })) {
|
|
112800
113145
|
if (entry.isDirectory() && entry.name === ref) {
|
|
112801
|
-
return
|
|
113146
|
+
return path246.join(reviewsDir, entry.name);
|
|
112802
113147
|
}
|
|
112803
113148
|
}
|
|
112804
113149
|
}
|
|
112805
113150
|
throw new Error(`Managed review package not found: ${ref}`);
|
|
112806
113151
|
}
|
|
112807
113152
|
async function loadDocpackSchema(cwd) {
|
|
112808
|
-
const schemaPath =
|
|
113153
|
+
const schemaPath = path246.join(cwd, "docs", "requirements", "managed-review-feedback-loop", "schemas", "managed-review-package.schema.json");
|
|
112809
113154
|
if (!await pathExists(schemaPath)) {
|
|
112810
113155
|
return null;
|
|
112811
113156
|
}
|
|
@@ -112911,7 +113256,7 @@ function checkPullOpen(facts) {
|
|
|
112911
113256
|
|
|
112912
113257
|
// src/review/bot/packages.ts
|
|
112913
113258
|
import { readdir as readdir39, readFile as readFile118 } from "fs/promises";
|
|
112914
|
-
import
|
|
113259
|
+
import path247 from "path";
|
|
112915
113260
|
async function subdirectories2(dir) {
|
|
112916
113261
|
try {
|
|
112917
113262
|
const entries = await readdir39(dir, { withFileTypes: true });
|
|
@@ -112934,12 +113279,12 @@ function pullFromTarget(target) {
|
|
|
112934
113279
|
}
|
|
112935
113280
|
async function readPackage(dir) {
|
|
112936
113281
|
try {
|
|
112937
|
-
const manifest = JSON.parse(await readFile118(
|
|
113282
|
+
const manifest = JSON.parse(await readFile118(path247.join(dir, "manifest.json"), "utf8"));
|
|
112938
113283
|
if (manifest.target?.kind !== "pr")
|
|
112939
113284
|
return null;
|
|
112940
113285
|
let findings = [];
|
|
112941
113286
|
try {
|
|
112942
|
-
const parsed = JSON.parse(await readFile118(
|
|
113287
|
+
const parsed = JSON.parse(await readFile118(path247.join(dir, "findings.json"), "utf8"));
|
|
112943
113288
|
if (Array.isArray(parsed))
|
|
112944
113289
|
findings = parsed;
|
|
112945
113290
|
} catch {
|
|
@@ -112954,16 +113299,16 @@ async function readPackage(dir) {
|
|
|
112954
113299
|
function candidateDirs2(cwd) {
|
|
112955
113300
|
return (async () => {
|
|
112956
113301
|
const dirs = [];
|
|
112957
|
-
const reviews =
|
|
113302
|
+
const reviews = path247.join(cwd, ".metaproject", "reviews");
|
|
112958
113303
|
for (const name of await subdirectories2(reviews)) {
|
|
112959
113304
|
if (name !== "bot")
|
|
112960
|
-
dirs.push(
|
|
113305
|
+
dirs.push(path247.join(reviews, name));
|
|
112961
113306
|
}
|
|
112962
|
-
const flows =
|
|
113307
|
+
const flows = path247.join(cwd, ".metaproject", "flows");
|
|
112963
113308
|
for (const flow of await subdirectories2(flows)) {
|
|
112964
|
-
const nested =
|
|
113309
|
+
const nested = path247.join(flows, flow, "reviews");
|
|
112965
113310
|
for (const name of await subdirectories2(nested))
|
|
112966
|
-
dirs.push(
|
|
113311
|
+
dirs.push(path247.join(nested, name));
|
|
112967
113312
|
}
|
|
112968
113313
|
return dirs;
|
|
112969
113314
|
})();
|
|
@@ -112980,7 +113325,7 @@ async function listReviewPackages(cwd) {
|
|
|
112980
113325
|
}
|
|
112981
113326
|
async function loadReviewPackage(cwd, reviewId) {
|
|
112982
113327
|
for (const dir of await candidateDirs2(cwd)) {
|
|
112983
|
-
if (
|
|
113328
|
+
if (path247.basename(dir) !== reviewId)
|
|
112984
113329
|
continue;
|
|
112985
113330
|
const listed = await readPackage(dir);
|
|
112986
113331
|
if (listed !== null && listed.reviewId === reviewId)
|
|
@@ -112991,7 +113336,7 @@ async function loadReviewPackage(cwd, reviewId) {
|
|
|
112991
113336
|
var init_packages = () => {};
|
|
112992
113337
|
|
|
112993
113338
|
// src/review/bot/run.ts
|
|
112994
|
-
import
|
|
113339
|
+
import path248 from "path";
|
|
112995
113340
|
function capDiff(diff, maxBytes) {
|
|
112996
113341
|
const totalBytes = Buffer.byteLength(diff);
|
|
112997
113342
|
if (totalBytes <= maxBytes)
|
|
@@ -113036,7 +113381,7 @@ function parseVerifierVerdict2(reply) {
|
|
|
113036
113381
|
return unverifiable;
|
|
113037
113382
|
}
|
|
113038
113383
|
function botStatePath(cwd, repo, number) {
|
|
113039
|
-
return
|
|
113384
|
+
return path248.join(cwd, ".metaproject", "reviews", "bot", `${repo.replace(/\//g, "__")}__${number}.json`);
|
|
113040
113385
|
}
|
|
113041
113386
|
function emptyBotState(repo, number) {
|
|
113042
113387
|
return { schemaVersion: 1, repo, number, reviews: [], pull: null };
|
|
@@ -113659,9 +114004,9 @@ var init_reviews_inspector = __esm(() => {
|
|
|
113659
114004
|
|
|
113660
114005
|
// src/lib/serve-config.ts
|
|
113661
114006
|
import { existsSync as existsSync42 } from "fs";
|
|
113662
|
-
import
|
|
114007
|
+
import path249 from "path";
|
|
113663
114008
|
function serveConfigPath(dir) {
|
|
113664
|
-
return
|
|
114009
|
+
return path249.join(keryxConfigDir(dir), "serve.json");
|
|
113665
114010
|
}
|
|
113666
114011
|
function parseIpv4(value) {
|
|
113667
114012
|
const parts = value.split(".");
|
|
@@ -113787,7 +114132,7 @@ function projectServeConfig(value, onDrop) {
|
|
|
113787
114132
|
if (!isRecord12(value)) {
|
|
113788
114133
|
return null;
|
|
113789
114134
|
}
|
|
113790
|
-
reportUndeclared(value,
|
|
114135
|
+
reportUndeclared(value, CONFIG_KEYS2, "", onDrop);
|
|
113791
114136
|
if (value.schemaVersion !== SERVE_CONFIG_SCHEMA_VERSION) {
|
|
113792
114137
|
return null;
|
|
113793
114138
|
}
|
|
@@ -113959,10 +114304,10 @@ function saveServeConfig(config, dir, onWarn) {
|
|
|
113959
114304
|
return false;
|
|
113960
114305
|
}
|
|
113961
114306
|
}
|
|
113962
|
-
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;
|
|
113963
114308
|
var init_serve_config = __esm(() => {
|
|
113964
114309
|
init_config_dir();
|
|
113965
|
-
|
|
114310
|
+
CONFIG_KEYS2 = [
|
|
113966
114311
|
"schemaVersion",
|
|
113967
114312
|
"enabled",
|
|
113968
114313
|
"bind",
|
|
@@ -114098,25 +114443,6 @@ var init_protocol2 = __esm(() => {
|
|
|
114098
114443
|
MESSAGE_STATES = ["reading", "working", "done", "failed"];
|
|
114099
114444
|
});
|
|
114100
114445
|
|
|
114101
|
-
// src/remote/paths.ts
|
|
114102
|
-
import path249 from "path";
|
|
114103
|
-
function remoteDirPath(dir) {
|
|
114104
|
-
return path249.join(keryxConfigDir(dir), REMOTE_DIRNAME);
|
|
114105
|
-
}
|
|
114106
|
-
function ensureRemoteDir(dir, ...sub) {
|
|
114107
|
-
return ensureKeryxSubdir([REMOTE_DIRNAME, ...sub], dir);
|
|
114108
|
-
}
|
|
114109
|
-
function botTokenPath(dir) {
|
|
114110
|
-
return path249.join(remoteDirPath(dir), BOT_TOKEN_FILE);
|
|
114111
|
-
}
|
|
114112
|
-
function remoteConfigPath(dir) {
|
|
114113
|
-
return path249.join(remoteDirPath(dir), REMOTE_CONFIG_FILE);
|
|
114114
|
-
}
|
|
114115
|
-
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";
|
|
114116
|
-
var init_paths4 = __esm(() => {
|
|
114117
|
-
init_config_dir();
|
|
114118
|
-
});
|
|
114119
|
-
|
|
114120
114446
|
// src/remote/endpoint.ts
|
|
114121
114447
|
import path250 from "path";
|
|
114122
114448
|
function endpointPath(dir) {
|
|
@@ -114155,8 +114481,8 @@ import {
|
|
|
114155
114481
|
fsyncSync as fsyncSync2,
|
|
114156
114482
|
openSync as openSync5,
|
|
114157
114483
|
renameSync as renameSync5,
|
|
114158
|
-
statSync as
|
|
114159
|
-
unlinkSync as
|
|
114484
|
+
statSync as statSync16,
|
|
114485
|
+
unlinkSync as unlinkSync6,
|
|
114160
114486
|
writeFileSync as writeFileSync8
|
|
114161
114487
|
} from "fs";
|
|
114162
114488
|
import path251 from "path";
|
|
@@ -114190,7 +114516,7 @@ function isGroupOrOtherAccessible(file) {
|
|
|
114190
114516
|
if (process.platform === "win32") {
|
|
114191
114517
|
return false;
|
|
114192
114518
|
}
|
|
114193
|
-
const mode =
|
|
114519
|
+
const mode = statSync16(file, { throwIfNoEntry: false })?.mode;
|
|
114194
114520
|
return mode !== undefined && (mode & 63) !== 0;
|
|
114195
114521
|
}
|
|
114196
114522
|
function readServeCredential(dir) {
|
|
@@ -114247,7 +114573,7 @@ function writeStore(store, dir) {
|
|
|
114247
114573
|
return true;
|
|
114248
114574
|
} catch {
|
|
114249
114575
|
try {
|
|
114250
|
-
|
|
114576
|
+
unlinkSync6(temp);
|
|
114251
114577
|
} catch {}
|
|
114252
114578
|
return false;
|
|
114253
114579
|
}
|
|
@@ -114323,7 +114649,7 @@ var init_serve_credential = __esm(() => {
|
|
|
114323
114649
|
|
|
114324
114650
|
// src/remote/shell-token.ts
|
|
114325
114651
|
import { createHash as createHash64, createHmac as createHmac5, randomBytes as randomBytes12 } from "crypto";
|
|
114326
|
-
import { statSync as
|
|
114652
|
+
import { statSync as statSync17 } from "fs";
|
|
114327
114653
|
import path252 from "path";
|
|
114328
114654
|
function shellTokenPath(dir) {
|
|
114329
114655
|
return path252.join(remoteDirPath(dir), SHELL_TOKEN_FILE);
|
|
@@ -114331,7 +114657,7 @@ function shellTokenPath(dir) {
|
|
|
114331
114657
|
function readValidated(file) {
|
|
114332
114658
|
if (process.platform !== "win32") {
|
|
114333
114659
|
try {
|
|
114334
|
-
const mode =
|
|
114660
|
+
const mode = statSync17(file).mode & 511;
|
|
114335
114661
|
if ((mode & 63) !== 0) {
|
|
114336
114662
|
return { ok: false, reason: `shell token file ${file} is accessible to other users (mode ${mode.toString(8)}); run: chmod 600 ${file}` };
|
|
114337
114663
|
}
|
|
@@ -114344,7 +114670,7 @@ function readValidated(file) {
|
|
|
114344
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})` };
|
|
114345
114671
|
}
|
|
114346
114672
|
const token = read.text.trim();
|
|
114347
|
-
if (!
|
|
114673
|
+
if (!TOKEN_SHAPE2.test(token)) {
|
|
114348
114674
|
return { ok: false, reason: `shell token file ${file} does not hold a shell token; delete it and restart \`keryx serve\`` };
|
|
114349
114675
|
}
|
|
114350
114676
|
return { ok: true, value: token };
|
|
@@ -114409,12 +114735,12 @@ function createShellTokenVerifier(token) {
|
|
|
114409
114735
|
return constantTimeEqual(createHash64("sha256").update(presented, "utf8").digest(), expected);
|
|
114410
114736
|
};
|
|
114411
114737
|
}
|
|
114412
|
-
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;
|
|
114413
114739
|
var init_shell_token = __esm(() => {
|
|
114414
114740
|
init_config_dir();
|
|
114415
114741
|
init_serve_credential();
|
|
114416
114742
|
init_paths4();
|
|
114417
|
-
|
|
114743
|
+
TOKEN_SHAPE2 = /^[A-Za-z0-9_-]{32,128}$/;
|
|
114418
114744
|
NONCE_SHAPE = /^[A-Za-z0-9_-]{22,64}$/;
|
|
114419
114745
|
MAC_SHAPE = /^[A-Za-z0-9_-]{43}$/;
|
|
114420
114746
|
});
|
|
@@ -115862,162 +116188,6 @@ var init_shell_bridge = __esm(() => {
|
|
|
115862
116188
|
init_protocol2();
|
|
115863
116189
|
});
|
|
115864
116190
|
|
|
115865
|
-
// src/remote/config.ts
|
|
115866
|
-
import { statSync as statSync17, unlinkSync as unlinkSync6 } from "fs";
|
|
115867
|
-
function isSafeInt(value) {
|
|
115868
|
-
return typeof value === "number" && Number.isSafeInteger(value);
|
|
115869
|
-
}
|
|
115870
|
-
function parseRemoteConfig(value) {
|
|
115871
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
115872
|
-
return { ok: false, reason: "remote config must be a JSON object" };
|
|
115873
|
-
}
|
|
115874
|
-
const doc = value;
|
|
115875
|
-
for (const key of Object.keys(doc)) {
|
|
115876
|
-
if (!CONFIG_KEYS2.has(key)) {
|
|
115877
|
-
const shown = /^[A-Za-z_][A-Za-z0-9_]{0,30}$/.test(key) ? key : "<not shown>";
|
|
115878
|
-
return { ok: false, reason: `remote config has an unknown key "${shown}" (the schema is closed)` };
|
|
115879
|
-
}
|
|
115880
|
-
}
|
|
115881
|
-
if (doc.schemaVersion !== REMOTE_CONFIG_SCHEMA_VERSION) {
|
|
115882
|
-
return { ok: false, reason: `remote config schemaVersion must be ${REMOTE_CONFIG_SCHEMA_VERSION}` };
|
|
115883
|
-
}
|
|
115884
|
-
if (!isSafeInt(doc.chatId) || doc.chatId === 0) {
|
|
115885
|
-
return { ok: false, reason: "remote config chatId must be a non-zero integer (the supergroup id)" };
|
|
115886
|
-
}
|
|
115887
|
-
const ids = doc.allowedUserIds;
|
|
115888
|
-
if (!Array.isArray(ids) || ids.length === 0 || !ids.every((id) => isSafeInt(id) && id > 0)) {
|
|
115889
|
-
return { ok: false, reason: "remote config allowedUserIds must be a non-empty array of positive integers" };
|
|
115890
|
-
}
|
|
115891
|
-
const timeout = (key, fallback) => {
|
|
115892
|
-
const raw = doc[key];
|
|
115893
|
-
if (raw === undefined) {
|
|
115894
|
-
return { ok: true, value: fallback };
|
|
115895
|
-
}
|
|
115896
|
-
if (!isSafeInt(raw) || raw < 1 || raw > MAX_TIMEOUT_MS3) {
|
|
115897
|
-
return { ok: false, reason: `remote config ${key} must be an integer from 1 to ${MAX_TIMEOUT_MS3} (milliseconds)` };
|
|
115898
|
-
}
|
|
115899
|
-
return { ok: true, value: raw };
|
|
115900
|
-
};
|
|
115901
|
-
const orphan = timeout("orphanMs", DEFAULT_ORPHAN_MS);
|
|
115902
|
-
if (!orphan.ok) {
|
|
115903
|
-
return orphan;
|
|
115904
|
-
}
|
|
115905
|
-
const run = timeout("runTimeoutMs", DEFAULT_RUN_TIMEOUT_MS);
|
|
115906
|
-
if (!run.ok) {
|
|
115907
|
-
return run;
|
|
115908
|
-
}
|
|
115909
|
-
return {
|
|
115910
|
-
ok: true,
|
|
115911
|
-
value: {
|
|
115912
|
-
schemaVersion: REMOTE_CONFIG_SCHEMA_VERSION,
|
|
115913
|
-
chatId: doc.chatId,
|
|
115914
|
-
allowedUserIds: [...new Set(ids)],
|
|
115915
|
-
orphanMs: orphan.value,
|
|
115916
|
-
runTimeoutMs: run.value
|
|
115917
|
-
}
|
|
115918
|
-
};
|
|
115919
|
-
}
|
|
115920
|
-
function loadRemoteConfig(dir) {
|
|
115921
|
-
const file = remoteConfigPath(dir);
|
|
115922
|
-
const read = readConfigFile(file);
|
|
115923
|
-
if (!read.ok) {
|
|
115924
|
-
return {
|
|
115925
|
-
ok: false,
|
|
115926
|
-
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})`
|
|
115927
|
-
};
|
|
115928
|
-
}
|
|
115929
|
-
let parsed;
|
|
115930
|
-
try {
|
|
115931
|
-
parsed = JSON.parse(read.text);
|
|
115932
|
-
} catch {
|
|
115933
|
-
return { ok: false, reason: `remote-control config at ${file} is not valid JSON` };
|
|
115934
|
-
}
|
|
115935
|
-
return parseRemoteConfig(parsed);
|
|
115936
|
-
}
|
|
115937
|
-
function saveRemoteConfig(config, dir) {
|
|
115938
|
-
const checked = parseRemoteConfig(config);
|
|
115939
|
-
if (!checked.ok) {
|
|
115940
|
-
return checked;
|
|
115941
|
-
}
|
|
115942
|
-
ensureRemoteDir(dir);
|
|
115943
|
-
writeOwnerOnlyFileAtomic(remoteConfigPath(dir), `${JSON.stringify(checked.value, null, 2)}
|
|
115944
|
-
`);
|
|
115945
|
-
return checked;
|
|
115946
|
-
}
|
|
115947
|
-
function loadBotToken(dir) {
|
|
115948
|
-
const file = botTokenPath(dir);
|
|
115949
|
-
let mode;
|
|
115950
|
-
try {
|
|
115951
|
-
const stats = statSync17(file);
|
|
115952
|
-
if (!stats.isFile()) {
|
|
115953
|
-
return { ok: false, reason: `bot token path ${file} is not a regular file` };
|
|
115954
|
-
}
|
|
115955
|
-
mode = stats.mode & 511;
|
|
115956
|
-
} catch {
|
|
115957
|
-
return {
|
|
115958
|
-
ok: false,
|
|
115959
|
-
reason: `no bot token file at ${file}; put the token on one line in it and run: chmod 600 ${file}`
|
|
115960
|
-
};
|
|
115961
|
-
}
|
|
115962
|
-
if (process.platform !== "win32" && (mode & 63) !== 0) {
|
|
115963
|
-
return {
|
|
115964
|
-
ok: false,
|
|
115965
|
-
reason: `bot token file ${file} is accessible to other users (mode ${mode.toString(8)}); run: chmod 600 ${file}`
|
|
115966
|
-
};
|
|
115967
|
-
}
|
|
115968
|
-
const read = readConfigFile(file);
|
|
115969
|
-
if (!read.ok) {
|
|
115970
|
-
return { ok: false, reason: `bot token file ${file} cannot be read (${read.reason})` };
|
|
115971
|
-
}
|
|
115972
|
-
const token = read.text.trim();
|
|
115973
|
-
if (!TOKEN_SHAPE2.test(token)) {
|
|
115974
|
-
return {
|
|
115975
|
-
ok: false,
|
|
115976
|
-
reason: `bot token file ${file} does not hold a bot token (expected <digits>:<secret> on a single line)`
|
|
115977
|
-
};
|
|
115978
|
-
}
|
|
115979
|
-
return { ok: true, value: token };
|
|
115980
|
-
}
|
|
115981
|
-
function saveBotToken(token, dir) {
|
|
115982
|
-
const trimmed = token.trim();
|
|
115983
|
-
if (!TOKEN_SHAPE2.test(trimmed)) {
|
|
115984
|
-
return { ok: false, reason: "that does not look like a bot token (expected <digits>:<secret>, as BotFather gives it)" };
|
|
115985
|
-
}
|
|
115986
|
-
const file = botTokenPath(dir);
|
|
115987
|
-
try {
|
|
115988
|
-
ensureRemoteDir(dir);
|
|
115989
|
-
writeOwnerOnlyFileAtomic(file, `${trimmed}
|
|
115990
|
-
`);
|
|
115991
|
-
} catch {
|
|
115992
|
-
return { ok: false, reason: `could not write the bot token file at ${file}` };
|
|
115993
|
-
}
|
|
115994
|
-
return { ok: true, value: true };
|
|
115995
|
-
}
|
|
115996
|
-
function removeFile2(file) {
|
|
115997
|
-
try {
|
|
115998
|
-
unlinkSync6(file);
|
|
115999
|
-
return true;
|
|
116000
|
-
} catch (error) {
|
|
116001
|
-
return error.code === "ENOENT";
|
|
116002
|
-
}
|
|
116003
|
-
}
|
|
116004
|
-
function removeBotToken(dir) {
|
|
116005
|
-
return removeFile2(botTokenPath(dir));
|
|
116006
|
-
}
|
|
116007
|
-
function removeRemoteConfig(dir) {
|
|
116008
|
-
return removeFile2(remoteConfigPath(dir));
|
|
116009
|
-
}
|
|
116010
|
-
var REMOTE_CONFIG_SCHEMA_VERSION = 1, DEFAULT_ORPHAN_MS, DEFAULT_RUN_TIMEOUT_MS, MAX_TIMEOUT_MS3, CONFIG_KEYS2, TOKEN_SHAPE2;
|
|
116011
|
-
var init_config11 = __esm(() => {
|
|
116012
|
-
init_config_dir();
|
|
116013
|
-
init_paths4();
|
|
116014
|
-
DEFAULT_ORPHAN_MS = 10 * 60000;
|
|
116015
|
-
DEFAULT_RUN_TIMEOUT_MS = 30 * 60000;
|
|
116016
|
-
MAX_TIMEOUT_MS3 = 7 * 24 * 60 * 60000;
|
|
116017
|
-
CONFIG_KEYS2 = new Set(["schemaVersion", "chatId", "allowedUserIds", "orphanMs", "runTimeoutMs"]);
|
|
116018
|
-
TOKEN_SHAPE2 = /^\d{5,}:[A-Za-z0-9_-]{20,}$/;
|
|
116019
|
-
});
|
|
116020
|
-
|
|
116021
116191
|
// src/remote/channels-client.ts
|
|
116022
116192
|
function fileExists(file) {
|
|
116023
116193
|
const read = readConfigFile(file);
|
|
@@ -116589,6 +116759,7 @@ async function loadChannelsSnapshot(client) {
|
|
|
116589
116759
|
configured: telegram.state !== "not-connected" || local.tokenFile || local.configFile,
|
|
116590
116760
|
machine,
|
|
116591
116761
|
sessions: telegram.sessions,
|
|
116762
|
+
...telegram.rendering !== undefined ? { rendering: telegram.rendering } : {},
|
|
116592
116763
|
...telegram.reason !== undefined ? { reason: telegram.reason } : {}
|
|
116593
116764
|
};
|
|
116594
116765
|
}
|
|
@@ -116619,6 +116790,19 @@ function projectChannelsRow(snapshot, width) {
|
|
|
116619
116790
|
return { text: fit2("off", width), role: "muted", action: "open" };
|
|
116620
116791
|
}
|
|
116621
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
|
+
}
|
|
116622
116806
|
function channelsStatusLines(snapshot) {
|
|
116623
116807
|
const machine = snapshot.machine === undefined ? [] : [`Machine: ${snapshot.machine}`];
|
|
116624
116808
|
switch (snapshot.state) {
|
|
@@ -116642,6 +116826,7 @@ function channelsStatusLines(snapshot) {
|
|
|
116642
116826
|
"Telegram: connected",
|
|
116643
116827
|
...machine,
|
|
116644
116828
|
`Sessions in Telegram: ${snapshot.sessions ?? 0}`,
|
|
116829
|
+
...renderingLines(snapshot.rendering),
|
|
116645
116830
|
"Test sends one message to the General topic. Disconnect deletes the topics and erases the token.",
|
|
116646
116831
|
"Sessions join through /remote-control in each shell."
|
|
116647
116832
|
];
|
|
@@ -116650,6 +116835,7 @@ function channelsStatusLines(snapshot) {
|
|
|
116650
116835
|
"Telegram: configured, but not running",
|
|
116651
116836
|
...machine,
|
|
116652
116837
|
...snapshot.reason === undefined ? [] : [`Why: ${snapshot.reason}`],
|
|
116838
|
+
...renderingLines(snapshot.rendering),
|
|
116653
116839
|
"Retry starts it again from the saved token and config. Disconnect erases them. Test works once Telegram is running."
|
|
116654
116840
|
];
|
|
116655
116841
|
case "serve-down":
|
|
@@ -117322,7 +117508,7 @@ function mountChannelsPanel(otui, renderer, parent, options) {
|
|
|
117322
117508
|
}
|
|
117323
117509
|
};
|
|
117324
117510
|
}
|
|
117325
|
-
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) => {
|
|
117326
117512
|
event.preventDefault?.();
|
|
117327
117513
|
event.stopPropagation?.();
|
|
117328
117514
|
}, CHANNELS_ROW_POLL_MS = 1e4;
|
|
@@ -117334,6 +117520,12 @@ var init_channels_surface = __esm(() => {
|
|
|
117334
117520
|
init_theme_repaint();
|
|
117335
117521
|
init_theme_text();
|
|
117336
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
|
+
};
|
|
117337
117529
|
CHANNELS_FOOTER = [
|
|
117338
117530
|
{ key: "c", label: "connect" },
|
|
117339
117531
|
{ key: "t", label: "test" },
|
|
@@ -117394,19 +117586,19 @@ function formatRemoteStatusLines(status) {
|
|
|
117394
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>.`);
|
|
117395
117587
|
return lines;
|
|
117396
117588
|
}
|
|
117397
|
-
function
|
|
117589
|
+
function clock2(at) {
|
|
117398
117590
|
return new Date(at).toISOString().slice(11, 19);
|
|
117399
117591
|
}
|
|
117400
117592
|
function formatRemoteEventLines(status) {
|
|
117401
117593
|
if (status.events.length === 0)
|
|
117402
117594
|
return ["No remote events yet."];
|
|
117403
|
-
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}`);
|
|
117404
117596
|
}
|
|
117405
117597
|
function formatRemoteCommandLines(status) {
|
|
117406
117598
|
const commands = status.events.filter((event) => event.kind === "command");
|
|
117407
117599
|
if (commands.length === 0)
|
|
117408
117600
|
return ["No remote commands yet."];
|
|
117409
|
-
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}`);
|
|
117410
117602
|
}
|
|
117411
117603
|
function renderRemoteControlText(status) {
|
|
117412
117604
|
return `${[...formatRemoteStatusLines(status), "", "Recent events:", ...formatRemoteEventLines(status)].join(`
|
|
@@ -117802,6 +117994,11 @@ var init_agent_commands = __esm(() => {
|
|
|
117802
117994
|
description: "Connect, test or disconnect Telegram for this machine: /channels [status]",
|
|
117803
117995
|
modes: AGENT_ONLY
|
|
117804
117996
|
},
|
|
117997
|
+
{
|
|
117998
|
+
name: "/rendering",
|
|
117999
|
+
description: "How Telegram replies are written: /rendering [auto|rich|html|plain]",
|
|
118000
|
+
modes: AGENT_ONLY
|
|
118001
|
+
},
|
|
117805
118002
|
{
|
|
117806
118003
|
name: "/product",
|
|
117807
118004
|
description: "Intents closed in code that nobody looked back at, with their outcome criteria",
|
|
@@ -137858,6 +138055,9 @@ async function runSettingsCommand(command, handlers) {
|
|
|
137858
138055
|
case "/external-agents":
|
|
137859
138056
|
handlers.onSystem(await handlers.externalAgents(arg));
|
|
137860
138057
|
return;
|
|
138058
|
+
case "/rendering":
|
|
138059
|
+
handlers.onSystem(handlers.rendering(arg));
|
|
138060
|
+
return;
|
|
137861
138061
|
case "/reasoning":
|
|
137862
138062
|
handlers.reasoning(arg);
|
|
137863
138063
|
return;
|
|
@@ -152857,6 +153057,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
152857
153057
|
route: setRoutingEnabled,
|
|
152858
153058
|
external: setExternalEnabled,
|
|
152859
153059
|
externalAgents: (arg) => runExternalAgentsCommand(arg, sessionCwd),
|
|
153060
|
+
rendering: (arg) => runRenderingCommand(arg),
|
|
152860
153061
|
reasoning: runReasoningCommand,
|
|
152861
153062
|
think: runThinkCommand,
|
|
152862
153063
|
theme: runThemeCommand,
|
|
@@ -153949,6 +154150,11 @@ ${formatThemeList(getThemeId())}`);
|
|
|
153949
154150
|
showEditGuard();
|
|
153950
154151
|
return;
|
|
153951
154152
|
}
|
|
154153
|
+
if (isRenderingCommand(command.name)) {
|
|
154154
|
+
const arg = line.trim().split(/\s+/).slice(1).join(" ");
|
|
154155
|
+
io.onSystem?.(runRenderingCommand(arg));
|
|
154156
|
+
return;
|
|
154157
|
+
}
|
|
153952
154158
|
if (isExternalAgentsCommand(command.name)) {
|
|
153953
154159
|
const arg = line.trim().split(/\s+/).slice(1).join(" ");
|
|
153954
154160
|
trackCommandWork(() => runExternalAgentsCommand(arg, opts.session?.cwd ?? process.cwd()).then((text) => io.onSystem?.(text)));
|
|
@@ -154788,6 +154994,7 @@ var init_tui_shell = __esm(() => {
|
|
|
154788
154994
|
init_turn_guard();
|
|
154789
154995
|
init_routing_classifier_source();
|
|
154790
154996
|
init_external_command();
|
|
154997
|
+
init_rendering_command();
|
|
154791
154998
|
init_external_switch();
|
|
154792
154999
|
init_external_providers();
|
|
154793
155000
|
init_jev_client();
|
|
@@ -157363,6 +157570,8 @@ ${GUTTER}${turnSeparator()}
|
|
|
157363
157570
|
agentIo.onSystem?.(readlineRemoteControlText(line));
|
|
157364
157571
|
} else if (isChannelsCommand(command)) {
|
|
157365
157572
|
agentIo.onSystem?.(readlineChannelsText(line, await loadChannelsSnapshot(new ChannelsClient)));
|
|
157573
|
+
} else if (isRenderingCommand(command)) {
|
|
157574
|
+
agentIo.onSystem?.(runRenderingCommand(rest));
|
|
157366
157575
|
} else if (isSessionInfoCommand(command)) {
|
|
157367
157576
|
const cwd = sessionCwd;
|
|
157368
157577
|
const [workspaces, flows] = await Promise.all([
|
|
@@ -158485,6 +158694,7 @@ var init_shell = __esm(() => {
|
|
|
158485
158694
|
init_statusbar();
|
|
158486
158695
|
init_metaproject_state();
|
|
158487
158696
|
init_live_render();
|
|
158697
|
+
init_rendering_command();
|
|
158488
158698
|
init_settings_model();
|
|
158489
158699
|
init_settings_state();
|
|
158490
158700
|
init_theme();
|
|
@@ -158565,6 +158775,7 @@ var init_shell = __esm(() => {
|
|
|
158565
158775
|
"/reviews",
|
|
158566
158776
|
"/remote-control",
|
|
158567
158777
|
"/channels",
|
|
158778
|
+
"/rendering",
|
|
158568
158779
|
"/theme",
|
|
158569
158780
|
"/settings",
|
|
158570
158781
|
"/mode",
|
|
@@ -181413,6 +181624,25 @@ var init_command_registry = __esm(() => {
|
|
|
181413
181624
|
"does not touch content already relayed to an agent, exported copies, or git history"
|
|
181414
181625
|
]
|
|
181415
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
|
+
},
|
|
181416
181646
|
{
|
|
181417
181647
|
module: "bus",
|
|
181418
181648
|
command: "bus list",
|
|
@@ -192707,6 +192937,24 @@ function createHttpBotApi(options) {
|
|
|
192707
192937
|
}
|
|
192708
192938
|
return call("sendMessage", body);
|
|
192709
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
|
+
},
|
|
192710
192958
|
async editMessageReplyMarkup(params) {
|
|
192711
192959
|
await call("editMessageReplyMarkup", {
|
|
192712
192960
|
chat_id: params.chatId,
|
|
@@ -192787,6 +193035,187 @@ var init_bot_api_http = __esm(() => {
|
|
|
192787
193035
|
init_types11();
|
|
192788
193036
|
});
|
|
192789
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
|
+
|
|
192790
193219
|
// src/remote/format.ts
|
|
192791
193220
|
function fenceOpening(line) {
|
|
192792
193221
|
const match = FENCE_LINE2.exec(line);
|
|
@@ -192834,13 +193263,40 @@ function cutAt(text, max) {
|
|
|
192834
193263
|
}
|
|
192835
193264
|
function toPieces(text, capacity) {
|
|
192836
193265
|
const pieces = [];
|
|
193266
|
+
const raws = text.split(/(?<=\n)/);
|
|
193267
|
+
const bare = raws.map((raw) => raw.endsWith(`
|
|
193268
|
+
`) ? raw.slice(0, -1) : raw);
|
|
192837
193269
|
let fence;
|
|
192838
|
-
for (
|
|
192839
|
-
const
|
|
192840
|
-
|
|
193270
|
+
for (let at = 0;at < raws.length; at += 1) {
|
|
193271
|
+
const raw = raws[at];
|
|
193272
|
+
const line = bare[at];
|
|
192841
193273
|
const before = fence;
|
|
192842
193274
|
if (fence === undefined) {
|
|
192843
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
|
+
}
|
|
192844
193300
|
} else if (closesFence(line, fence)) {
|
|
192845
193301
|
fence = undefined;
|
|
192846
193302
|
}
|
|
@@ -192848,24 +193304,33 @@ function toPieces(text, capacity) {
|
|
|
192848
193304
|
let rest = raw;
|
|
192849
193305
|
while (rest.length > room) {
|
|
192850
193306
|
const cut = cutAt(rest, room);
|
|
192851
|
-
pieces.push({ text: rest.slice(0, cut), fence: before ?? fence });
|
|
193307
|
+
pieces.push({ text: rest.slice(0, cut), fence: before ?? fence, cost: cut });
|
|
192852
193308
|
rest = rest.slice(cut);
|
|
192853
193309
|
}
|
|
192854
193310
|
if (rest.length > 0) {
|
|
192855
|
-
|
|
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 });
|
|
192856
193314
|
}
|
|
192857
193315
|
}
|
|
192858
193316
|
return pieces;
|
|
192859
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
|
+
}
|
|
192860
193322
|
function pack(text, capacity) {
|
|
192861
193323
|
const parts = [];
|
|
192862
193324
|
let current = "";
|
|
193325
|
+
let used = 0;
|
|
192863
193326
|
let hasContent = false;
|
|
192864
193327
|
let open;
|
|
192865
|
-
|
|
193328
|
+
let lastTable;
|
|
193329
|
+
const finish = (next) => {
|
|
192866
193330
|
let body = current.trimEnd();
|
|
192867
193331
|
if (!hasContent || body.length === 0) {
|
|
192868
193332
|
current = "";
|
|
193333
|
+
used = 0;
|
|
192869
193334
|
hasContent = false;
|
|
192870
193335
|
return;
|
|
192871
193336
|
}
|
|
@@ -192876,6 +193341,11 @@ ${open.marker}`;
|
|
|
192876
193341
|
parts.push(body);
|
|
192877
193342
|
current = open === undefined ? "" : `${open.header}
|
|
192878
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
|
+
}
|
|
192879
193349
|
hasContent = false;
|
|
192880
193350
|
};
|
|
192881
193351
|
for (const piece of toPieces(text, capacity)) {
|
|
@@ -192883,15 +193353,17 @@ ${open.marker}`;
|
|
|
192883
193353
|
continue;
|
|
192884
193354
|
}
|
|
192885
193355
|
const closing = piece.fence === undefined ? 0 : piece.fence.marker.length + 1;
|
|
192886
|
-
if (hasContent &&
|
|
192887
|
-
finish();
|
|
193356
|
+
if (hasContent && used + piece.cost + closing > capacity) {
|
|
193357
|
+
finish(piece);
|
|
192888
193358
|
if (piece.text.trim().length === 0) {
|
|
192889
193359
|
continue;
|
|
192890
193360
|
}
|
|
192891
193361
|
}
|
|
192892
193362
|
current += piece.text;
|
|
193363
|
+
used += piece.cost;
|
|
192893
193364
|
hasContent = true;
|
|
192894
193365
|
open = piece.fence;
|
|
193366
|
+
lastTable = piece.table;
|
|
192895
193367
|
}
|
|
192896
193368
|
finish();
|
|
192897
193369
|
return parts;
|
|
@@ -192905,7 +193377,7 @@ function formatReply(text, limit = TELEGRAM_MAX_TEXT) {
|
|
|
192905
193377
|
if (text.trim().length === 0) {
|
|
192906
193378
|
return [];
|
|
192907
193379
|
}
|
|
192908
|
-
if (text
|
|
193380
|
+
if (renderedLength(text) <= limit) {
|
|
192909
193381
|
return [text];
|
|
192910
193382
|
}
|
|
192911
193383
|
for (let digits = 1;digits <= 3; digits += 1) {
|
|
@@ -192926,10 +193398,101 @@ ${part}`);
|
|
|
192926
193398
|
}
|
|
192927
193399
|
var MIN_LIMIT = 64, MAX_FENCE_MARKER = 16, MAX_FENCE_INFO = 40, FENCE_LINE2;
|
|
192928
193400
|
var init_format2 = __esm(() => {
|
|
193401
|
+
init_format_blocks();
|
|
193402
|
+
init_format_table();
|
|
192929
193403
|
init_types11();
|
|
192930
193404
|
FENCE_LINE2 = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
192931
193405
|
});
|
|
192932
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
|
+
|
|
192933
193496
|
// src/remote/format-html.ts
|
|
192934
193497
|
function escapeHtml2(text) {
|
|
192935
193498
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -193063,7 +193626,8 @@ function link(text, index, context) {
|
|
|
193063
193626
|
if (!HTTP_URL.test(url)) {
|
|
193064
193627
|
return;
|
|
193065
193628
|
}
|
|
193066
|
-
|
|
193629
|
+
const marker = escapeHtml2(hostMarker(label, url));
|
|
193630
|
+
return { html: `<a href="${escapeAttribute(url)}">${renderInline2(label, { ...context, link: true })}</a>${marker}`, end };
|
|
193067
193631
|
}
|
|
193068
193632
|
function renderInline2(text, context) {
|
|
193069
193633
|
let out = "";
|
|
@@ -193175,7 +193739,19 @@ function renderTelegramHtml(part) {
|
|
|
193175
193739
|
}
|
|
193176
193740
|
continue;
|
|
193177
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
|
+
}
|
|
193178
193750
|
index += 1;
|
|
193751
|
+
if (RULE.test(line)) {
|
|
193752
|
+
push(RULE_GLYPH.repeat(RULE_LENGTH), false);
|
|
193753
|
+
continue;
|
|
193754
|
+
}
|
|
193179
193755
|
const heading = HEADING2.exec(line);
|
|
193180
193756
|
if (heading !== null && heading[1].trim().length > 0) {
|
|
193181
193757
|
push(`<b>${renderInline2(heading[1], { ...OUTSIDE, bold: true })}</b>`, false);
|
|
@@ -193183,6 +193759,13 @@ function renderTelegramHtml(part) {
|
|
|
193183
193759
|
}
|
|
193184
193760
|
const bullet = BULLET.exec(line);
|
|
193185
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
|
+
}
|
|
193186
193769
|
push(`${bullet[1]}\u2022 ${renderInline2(bullet[2], OUTSIDE)}`, false);
|
|
193187
193770
|
continue;
|
|
193188
193771
|
}
|
|
@@ -193190,6 +193773,93 @@ function renderTelegramHtml(part) {
|
|
|
193190
193773
|
}
|
|
193191
193774
|
return out;
|
|
193192
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
|
+
}
|
|
193193
193863
|
function isEntityParseError(error) {
|
|
193194
193864
|
return isBotApiError(error) && error.kind === "rejected" && error.status === 400 && /can't parse entities/i.test(error.message);
|
|
193195
193865
|
}
|
|
@@ -193204,7 +193874,7 @@ async function sendHtml(api, params, onFallback) {
|
|
|
193204
193874
|
try {
|
|
193205
193875
|
onFallback?.(error);
|
|
193206
193876
|
} catch {}
|
|
193207
|
-
return api.sendMessage(plain);
|
|
193877
|
+
return api.sendMessage({ ...plain, text: renderPlainText(plain.text) });
|
|
193208
193878
|
}
|
|
193209
193879
|
}
|
|
193210
193880
|
async function editHtml(api, params, onFallback) {
|
|
@@ -193217,15 +193887,19 @@ async function editHtml(api, params, onFallback) {
|
|
|
193217
193887
|
try {
|
|
193218
193888
|
onFallback?.(error);
|
|
193219
193889
|
} catch {}
|
|
193220
|
-
await api.editMessageText(params);
|
|
193890
|
+
await api.editMessageText({ ...params, text: renderPlainText(params.text) });
|
|
193221
193891
|
}
|
|
193222
193892
|
}
|
|
193223
193893
|
function isNotModified(error) {
|
|
193224
193894
|
return isBotApiError(error) && error.kind === "rejected" && /message is not modified/i.test(error.message);
|
|
193225
193895
|
}
|
|
193226
|
-
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;
|
|
193227
193897
|
var init_format_html = __esm(() => {
|
|
193228
193898
|
init_format2();
|
|
193899
|
+
init_format_blocks();
|
|
193900
|
+
init_format_link();
|
|
193901
|
+
init_format_plain();
|
|
193902
|
+
init_format_table();
|
|
193229
193903
|
init_types11();
|
|
193230
193904
|
OUTSIDE = { bold: false, italic: false, strike: false, link: false };
|
|
193231
193905
|
WORD = /[\p{L}\p{N}_]/u;
|
|
@@ -193236,10 +193910,9 @@ var init_format_html = __esm(() => {
|
|
|
193236
193910
|
HTTP_URL = /^https?:\/\/[^\s<>"'`]+$/i;
|
|
193237
193911
|
LANGUAGE = /^[A-Za-z0-9_+#.-]{1,32}$/;
|
|
193238
193912
|
TAGS2 = { bold: "b", italic: "i", strike: "s" };
|
|
193239
|
-
HEADING2 = /^ {0,3}#{1,6}[ \t]+(.*?)(?:[ \t]+#+)?[ \t]*$/;
|
|
193240
|
-
BULLET = /^([ \t]*)[-*][ \t]+(.*)$/;
|
|
193241
|
-
QUOTE = /^ {0,3}>(?: (.*))?$/;
|
|
193242
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;
|
|
193243
193916
|
});
|
|
193244
193917
|
|
|
193245
193918
|
// src/remote/durable-log.ts
|
|
@@ -193556,6 +194229,512 @@ var init_journal3 = __esm(() => {
|
|
|
193556
194229
|
init_paths4();
|
|
193557
194230
|
});
|
|
193558
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
|
+
|
|
193559
194738
|
// src/remote/outbound-queue.ts
|
|
193560
194739
|
import { randomUUID as randomUUID51 } from "crypto";
|
|
193561
194740
|
import path363 from "path";
|
|
@@ -193582,6 +194761,7 @@ class OutboundQueue {
|
|
|
193582
194761
|
log;
|
|
193583
194762
|
onDrop;
|
|
193584
194763
|
onFallback;
|
|
194764
|
+
rendering;
|
|
193585
194765
|
retryDelayMs;
|
|
193586
194766
|
sentHooks = new Map;
|
|
193587
194767
|
blockedUntil = 0;
|
|
@@ -193595,6 +194775,7 @@ class OutboundQueue {
|
|
|
193595
194775
|
this.onFallback = options.onFallback ?? (() => {
|
|
193596
194776
|
return;
|
|
193597
194777
|
});
|
|
194778
|
+
this.rendering = options.rendering ?? new RenderingState({ now: options.now });
|
|
193598
194779
|
this.retryDelayMs = options.retryDelayMs ?? 5000;
|
|
193599
194780
|
const directory = ensureRemoteDir(options.dir);
|
|
193600
194781
|
this.log = new DurableLog({
|
|
@@ -193683,17 +194864,17 @@ class OutboundQueue {
|
|
|
193683
194864
|
return { sent, dropped, remaining: this.log.size, retryInMs: wait, stoppedBy: "rate limited" };
|
|
193684
194865
|
}
|
|
193685
194866
|
try {
|
|
193686
|
-
const result = await
|
|
194867
|
+
const result = await sendRendered(this.api, {
|
|
193687
194868
|
chatId: entry.chatId,
|
|
193688
194869
|
text: entry.text,
|
|
193689
194870
|
...entry.threadId === undefined ? {} : { messageThreadId: entry.threadId },
|
|
193690
194871
|
...entry.keyboard === undefined ? {} : { inlineKeyboard: entry.keyboard }
|
|
193691
|
-
}, () => this.onFallback(entry));
|
|
194872
|
+
}, { state: this.rendering, onFallback: (fallback) => this.onFallback(entry, fallback) });
|
|
193692
194873
|
this.log.ack(entry.id);
|
|
193693
194874
|
sent += 1;
|
|
193694
194875
|
this.notifySent(entry, result.message_id);
|
|
193695
194876
|
} catch (error) {
|
|
193696
|
-
const description =
|
|
194877
|
+
const description = describe2(error);
|
|
193697
194878
|
if (isBotApiError(error) && error.kind === "rate-limited") {
|
|
193698
194879
|
const retryMs = Math.max(1, error.retryAfterSec ?? 1) * 1000;
|
|
193699
194880
|
this.blockedUntil = this.now() + retryMs;
|
|
@@ -193710,7 +194891,7 @@ class OutboundQueue {
|
|
|
193710
194891
|
}
|
|
193711
194892
|
}
|
|
193712
194893
|
}
|
|
193713
|
-
function
|
|
194894
|
+
function describe2(error) {
|
|
193714
194895
|
return redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
193715
194896
|
}
|
|
193716
194897
|
var OUTBOUND_MAX_ENTRIES = 200;
|
|
@@ -193719,7 +194900,7 @@ var init_outbound_queue = __esm(() => {
|
|
|
193719
194900
|
init_durable_log();
|
|
193720
194901
|
init_paths4();
|
|
193721
194902
|
init_format2();
|
|
193722
|
-
|
|
194903
|
+
init_rendering();
|
|
193723
194904
|
init_types11();
|
|
193724
194905
|
});
|
|
193725
194906
|
|
|
@@ -193829,7 +195010,7 @@ class UpdatePoller {
|
|
|
193829
195010
|
continue;
|
|
193830
195011
|
}
|
|
193831
195012
|
failures += 1;
|
|
193832
|
-
this.set({ state: "running", reason:
|
|
195013
|
+
this.set({ state: "running", reason: describe3(error) });
|
|
193833
195014
|
await this.sleep(this.backoffMs[Math.min(failures, this.backoffMs.length) - 1] ?? 1000, signal);
|
|
193834
195015
|
continue;
|
|
193835
195016
|
}
|
|
@@ -193849,13 +195030,13 @@ class UpdatePoller {
|
|
|
193849
195030
|
}
|
|
193850
195031
|
} catch (error) {
|
|
193851
195032
|
failures += 1;
|
|
193852
|
-
this.set({ state: "running", reason:
|
|
195033
|
+
this.set({ state: "running", reason: describe3(error) });
|
|
193853
195034
|
await this.sleep(this.backoffMs[Math.min(failures, this.backoffMs.length) - 1] ?? 1000, signal);
|
|
193854
195035
|
}
|
|
193855
195036
|
}
|
|
193856
195037
|
}
|
|
193857
195038
|
}
|
|
193858
|
-
function
|
|
195039
|
+
function describe3(error) {
|
|
193859
195040
|
return redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
193860
195041
|
}
|
|
193861
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;
|
|
@@ -193866,7 +195047,7 @@ var init_poller = __esm(() => {
|
|
|
193866
195047
|
|
|
193867
195048
|
// src/remote/service-topics.ts
|
|
193868
195049
|
import path364 from "path";
|
|
193869
|
-
function
|
|
195050
|
+
function parse2(value) {
|
|
193870
195051
|
if (typeof value !== "object" || value === null)
|
|
193871
195052
|
return;
|
|
193872
195053
|
const raw = value;
|
|
@@ -193893,7 +195074,7 @@ class ServiceTopics {
|
|
|
193893
195074
|
if (!Array.isArray(records))
|
|
193894
195075
|
return;
|
|
193895
195076
|
for (const candidate of records) {
|
|
193896
|
-
const record =
|
|
195077
|
+
const record = parse2(candidate);
|
|
193897
195078
|
if (record !== undefined)
|
|
193898
195079
|
this.byName.set(nameKey(record.name), record);
|
|
193899
195080
|
}
|
|
@@ -194077,6 +195258,7 @@ class RemoteHub {
|
|
|
194077
195258
|
registry;
|
|
194078
195259
|
inbound;
|
|
194079
195260
|
outbound;
|
|
195261
|
+
rendering;
|
|
194080
195262
|
serviceTopics;
|
|
194081
195263
|
droppedReasons = new Map;
|
|
194082
195264
|
journal;
|
|
@@ -194115,15 +195297,23 @@ class RemoteHub {
|
|
|
194115
195297
|
};
|
|
194116
195298
|
this.registry = new SessionRegistry({ ...options.dir === undefined ? {} : { dir: options.dir }, now: this.now });
|
|
194117
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
|
+
});
|
|
194118
195307
|
this.outbound = new OutboundQueue({
|
|
194119
195308
|
api: this.api,
|
|
195309
|
+
rendering: this.rendering,
|
|
194120
195310
|
...options.dir === undefined ? {} : { dir: options.dir },
|
|
194121
195311
|
now: this.now,
|
|
194122
195312
|
onDrop: (entry, reason) => {
|
|
194123
195313
|
this.droppedReasons.set(entry.id, reason);
|
|
194124
195314
|
this.event("outbound-dropped", `${reason}${entry.threadId === undefined ? "" : ` (topic ${entry.threadId})`}`);
|
|
194125
195315
|
},
|
|
194126
|
-
onFallback: (entry) => this.
|
|
195316
|
+
onFallback: (entry, fallback) => this.fallbackEvent(fallback, entry.threadId === undefined ? "" : ` (topic ${entry.threadId})`, "sent")
|
|
194127
195317
|
});
|
|
194128
195318
|
this.journal = new RejectedJournal({ ...options.dir === undefined ? {} : { dir: options.dir }, now: this.now });
|
|
194129
195319
|
this.offset = new PollerState(options.dir === undefined ? {} : { dir: options.dir });
|
|
@@ -194264,7 +195454,13 @@ class RemoteHub {
|
|
|
194264
195454
|
});
|
|
194265
195455
|
}
|
|
194266
195456
|
async sendGeneral(text) {
|
|
194267
|
-
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}`);
|
|
194268
195464
|
}
|
|
194269
195465
|
heartbeat(sessionId) {
|
|
194270
195466
|
return this.serial(async () => {
|
|
@@ -194383,7 +195579,7 @@ class RemoteHub {
|
|
|
194383
195579
|
return "gone";
|
|
194384
195580
|
}
|
|
194385
195581
|
try {
|
|
194386
|
-
await
|
|
195582
|
+
await editRendered(this.api, { chatId: record.chatId, messageId, text }, { state: this.rendering, onFallback: (fallback) => this.fallbackEvent(fallback, ` (topic ${record.threadId})`, "edited") });
|
|
194387
195583
|
return "edited";
|
|
194388
195584
|
} catch (error) {
|
|
194389
195585
|
if (isNotModified(error)) {
|
|
@@ -194796,11 +195992,14 @@ var realTimers, MAX_EVENTS = 50, ANSWER_CALLBACK_TIMEOUT_MS = 5000;
|
|
|
194796
195992
|
var init_hub = __esm(() => {
|
|
194797
195993
|
init_service2();
|
|
194798
195994
|
init_lease();
|
|
195995
|
+
init_config11();
|
|
194799
195996
|
init_format_html();
|
|
194800
195997
|
init_inbound();
|
|
194801
195998
|
init_journal3();
|
|
194802
195999
|
init_naming();
|
|
194803
196000
|
init_outbound_queue();
|
|
196001
|
+
init_rendering();
|
|
196002
|
+
init_rendering_mode();
|
|
194804
196003
|
init_command_gateway();
|
|
194805
196004
|
init_message_state();
|
|
194806
196005
|
init_poller();
|
|
@@ -195815,10 +197014,10 @@ function newPairingCode() {
|
|
|
195815
197014
|
}
|
|
195816
197015
|
return code;
|
|
195817
197016
|
}
|
|
195818
|
-
function
|
|
197017
|
+
function describe4(error) {
|
|
195819
197018
|
return redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
195820
197019
|
}
|
|
195821
|
-
function
|
|
197020
|
+
function normalise4(text) {
|
|
195822
197021
|
return text.toUpperCase().replace(/[^A-Z0-9]/g, "");
|
|
195823
197022
|
}
|
|
195824
197023
|
|
|
@@ -195876,7 +197075,7 @@ class Pairing {
|
|
|
195876
197075
|
if (isBotApiError(error) && error.kind === "rejected") {
|
|
195877
197076
|
return { ok: false, kind: "rejected", reason: "Telegram does not accept this bot token; check it with BotFather and try again." };
|
|
195878
197077
|
}
|
|
195879
|
-
return { ok: false, kind: "unreachable", reason: `could not reach Telegram: ${
|
|
197078
|
+
return { ok: false, kind: "unreachable", reason: `could not reach Telegram: ${describe4(error)}` };
|
|
195880
197079
|
}
|
|
195881
197080
|
return { ok: true, pairing: new Pairing(options, bot) };
|
|
195882
197081
|
}
|
|
@@ -196003,7 +197202,7 @@ class Pairing {
|
|
|
196003
197202
|
return;
|
|
196004
197203
|
}
|
|
196005
197204
|
const sent = message.text.trim().replace(/^\/start(?:@\w+)?\s+/i, "");
|
|
196006
|
-
if (this.code === undefined ||
|
|
197205
|
+
if (this.code === undefined || normalise4(sent) !== this.code) {
|
|
196007
197206
|
return;
|
|
196008
197207
|
}
|
|
196009
197208
|
this.userId = message.from.id;
|
|
@@ -196112,7 +197311,7 @@ class Pairing {
|
|
|
196112
197311
|
problems.push("Give the bot the Manage Topics right in the group's administrator settings.");
|
|
196113
197312
|
}
|
|
196114
197313
|
} catch (error) {
|
|
196115
|
-
problems.push(`could not inspect the group: ${
|
|
197314
|
+
problems.push(`could not inspect the group: ${describe4(error)}`);
|
|
196116
197315
|
}
|
|
196117
197316
|
this.expireIfDue();
|
|
196118
197317
|
if (FINAL.has(this.state)) {
|
|
@@ -196137,7 +197336,7 @@ var init_pairing = __esm(() => {
|
|
|
196137
197336
|
});
|
|
196138
197337
|
|
|
196139
197338
|
// src/remote/channels.ts
|
|
196140
|
-
function
|
|
197339
|
+
function describe5(error) {
|
|
196141
197340
|
return redactSensitiveText(error instanceof Error ? error.message : String(error));
|
|
196142
197341
|
}
|
|
196143
197342
|
|
|
@@ -196200,11 +197399,25 @@ class ChannelsController {
|
|
|
196200
197399
|
}
|
|
196201
197400
|
return { state: "not-connected" };
|
|
196202
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
|
+
}
|
|
196203
197410
|
status() {
|
|
196204
197411
|
const { state, reason } = this.state();
|
|
197412
|
+
const rendering = this.rendering();
|
|
196205
197413
|
return ok2({
|
|
196206
197414
|
machine: this.host.machine,
|
|
196207
|
-
telegram: {
|
|
197415
|
+
telegram: {
|
|
197416
|
+
state,
|
|
197417
|
+
...reason === undefined ? {} : { reason },
|
|
197418
|
+
sessions: this.host.hub()?.list().length ?? 0,
|
|
197419
|
+
...rendering === undefined ? {} : { rendering }
|
|
197420
|
+
}
|
|
196208
197421
|
});
|
|
196209
197422
|
}
|
|
196210
197423
|
async dropPairing() {
|
|
@@ -196300,7 +197513,7 @@ class ChannelsController {
|
|
|
196300
197513
|
try {
|
|
196301
197514
|
await hub.sendGeneral(`Keryx test message from ${this.host.machine}: Telegram is connected.`);
|
|
196302
197515
|
} catch (error) {
|
|
196303
|
-
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)}`);
|
|
196304
197517
|
}
|
|
196305
197518
|
return ok2({ delivered: true, machine: this.host.machine });
|
|
196306
197519
|
}
|
|
@@ -196326,6 +197539,7 @@ var init_channels = __esm(() => {
|
|
|
196326
197539
|
init_http_surface();
|
|
196327
197540
|
init_pairing();
|
|
196328
197541
|
init_paths4();
|
|
197542
|
+
init_rendering_mode();
|
|
196329
197543
|
});
|
|
196330
197544
|
|
|
196331
197545
|
// src/remote/poll-lock.ts
|
|
@@ -203117,6 +204331,156 @@ var init_retention2 = __esm(() => {
|
|
|
203117
204331
|
init_sweep();
|
|
203118
204332
|
});
|
|
203119
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
|
+
|
|
203120
204484
|
// src/commands/forgetting.ts
|
|
203121
204485
|
async function forgettingCommand(args) {
|
|
203122
204486
|
const command = args[0];
|
|
@@ -204904,7 +206268,7 @@ function buildHeader(entry, size) {
|
|
|
204904
206268
|
chk.copy(header, 148);
|
|
204905
206269
|
return { ok: true, value: header };
|
|
204906
206270
|
}
|
|
204907
|
-
function
|
|
206271
|
+
function pad5(buf) {
|
|
204908
206272
|
const rem = buf.length % BLOCK;
|
|
204909
206273
|
if (rem === 0)
|
|
204910
206274
|
return buf;
|
|
@@ -204939,7 +206303,7 @@ function buildBundleArchive(files, manifestBytes) {
|
|
|
204939
206303
|
return header;
|
|
204940
206304
|
chunks.push(header.value);
|
|
204941
206305
|
if (entry.bytes && entry.bytes.length > 0) {
|
|
204942
|
-
chunks.push(
|
|
206306
|
+
chunks.push(pad5(entry.bytes));
|
|
204943
206307
|
}
|
|
204944
206308
|
}
|
|
204945
206309
|
chunks.push(Buffer.alloc(BLOCK * 2));
|
|
@@ -209141,6 +210505,9 @@ var VERSION3, CLI_ROUTES, MCP_KNOWN_SUBCOMMANDS, USAGE_BODY = `Usage:
|
|
|
209141
210505
|
keryx retention sweep [--apply] [--target <id>]... [--max-age-days <n>] [--max-bytes <n>] [--json]
|
|
209142
210506
|
Bound gdctx raw/artifacts logs and owner write-conflict
|
|
209143
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
|
|
209144
210511
|
keryx forgetting trail [--limit <n>] [--json]
|
|
209145
210512
|
keryx forgetting lookup "<ref-or-path>" [--layer <layer>] [--search] [--json]
|
|
209146
210513
|
Read the deletion trail: what was removed, when, at
|
|
@@ -209238,6 +210605,7 @@ Commands:
|
|
|
209238
210605
|
metrics Provenance-aware execution observability: run records, baselines, benchmarks
|
|
209239
210606
|
workspace Shared Agent Context: workspaces, FWK reads, propose/review (module sac)
|
|
209240
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
|
|
209241
210609
|
forgetting Read the deletion trail \u2014 was this removed, or did it never exist?
|
|
209242
210610
|
trigger Fire one declared project trigger (git hook, cron line, CI job) \u2014 one pass, one exit code
|
|
209243
210611
|
schedule Scheduled agent tasks in the background: create (with confirmation), list, pause, resume, remove
|
|
@@ -209295,6 +210663,7 @@ var init_cli_registry = __esm(() => {
|
|
|
209295
210663
|
init_external2();
|
|
209296
210664
|
init_auth();
|
|
209297
210665
|
init_retention2();
|
|
210666
|
+
init_remote();
|
|
209298
210667
|
init_forgetting();
|
|
209299
210668
|
init_trigger();
|
|
209300
210669
|
init_schedule2();
|
|
@@ -209363,6 +210732,7 @@ var init_cli_registry = __esm(() => {
|
|
|
209363
210732
|
version: versionCommand,
|
|
209364
210733
|
workspace: workspaceCommand,
|
|
209365
210734
|
retention: retentionCommand,
|
|
210735
|
+
remote: remoteCommand,
|
|
209366
210736
|
forgetting: forgettingCommand,
|
|
209367
210737
|
trigger: triggerCommand,
|
|
209368
210738
|
schedule: scheduleCommand,
|