switchroom 0.19.8 → 0.19.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth-broker/index.js +9 -8
- package/dist/cli/switchroom.js +957 -721
- package/dist/host-control/main.js +17 -15
- package/dist/vault/approvals/kernel-server.js +5 -4
- package/dist/vault/broker/server.js +9 -8
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +57 -11
- package/telegram-plugin/render/unsupported-token-guard.ts +121 -0
- package/telegram-plugin/rich-send.ts +9 -0
- package/telegram-plugin/tests/render/unsupported-token-guard.test.ts +125 -0
- package/skills/telegram-formatting/SKILL.md +0 -147
|
@@ -20614,7 +20614,7 @@ async function waitForConfigRecovery(opts) {
|
|
|
20614
20614
|
}
|
|
20615
20615
|
|
|
20616
20616
|
// src/agents/scaffold.ts
|
|
20617
|
-
import { join as join5, resolve as resolve5 } from "node:path";
|
|
20617
|
+
import { dirname, isAbsolute, join as join5, relative, resolve as resolve5 } from "node:path";
|
|
20618
20618
|
init_atomic();
|
|
20619
20619
|
|
|
20620
20620
|
// src/agents/agent-uid.ts
|
|
@@ -20748,6 +20748,7 @@ var PROFILE_MEMORY_DEFAULTS = {
|
|
|
20748
20748
|
|
|
20749
20749
|
// src/agents/reconcile-default-skills.ts
|
|
20750
20750
|
var warnedMissingPool = new Set;
|
|
20751
|
+
var warnedMissingDefault = new Set;
|
|
20751
20752
|
|
|
20752
20753
|
// src/vault/vault.ts
|
|
20753
20754
|
var KNOWN_VAULT_ARTIFACT_NAMES = new Set([
|
|
@@ -21239,7 +21240,7 @@ import {
|
|
|
21239
21240
|
copyFileSync as copyFileSync2,
|
|
21240
21241
|
unlinkSync as unlinkSync2
|
|
21241
21242
|
} from "node:fs";
|
|
21242
|
-
import { join as join10, dirname as
|
|
21243
|
+
import { join as join10, dirname as dirname7, resolve as resolve8 } from "node:path";
|
|
21243
21244
|
import { createHash as createHash5, randomUUID as randomUUID2, randomBytes } from "node:crypto";
|
|
21244
21245
|
|
|
21245
21246
|
// src/host-control/protocol.ts
|
|
@@ -21488,7 +21489,7 @@ function deniedResponse(request_id, error, duration_ms = 0) {
|
|
|
21488
21489
|
import { createHash as createHash2 } from "node:crypto";
|
|
21489
21490
|
|
|
21490
21491
|
// node_modules/.bun/posthog-node@5.29.2/node_modules/posthog-node/dist/extensions/error-tracking/modifiers/module.node.mjs
|
|
21491
|
-
import { dirname, posix, sep } from "path";
|
|
21492
|
+
import { dirname as dirname2, posix, sep } from "path";
|
|
21492
21493
|
function createModulerModifier() {
|
|
21493
21494
|
const getModuleFromFileName = createGetModuleFromFilename();
|
|
21494
21495
|
return async (frames) => {
|
|
@@ -21497,7 +21498,7 @@ function createModulerModifier() {
|
|
|
21497
21498
|
return frames;
|
|
21498
21499
|
};
|
|
21499
21500
|
}
|
|
21500
|
-
function createGetModuleFromFilename(basePath = process.argv[1] ?
|
|
21501
|
+
function createGetModuleFromFilename(basePath = process.argv[1] ? dirname2(process.argv[1]) : process.cwd(), isWindows = sep === "\\") {
|
|
21501
21502
|
const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath;
|
|
21502
21503
|
return (filename) => {
|
|
21503
21504
|
if (!filename)
|
|
@@ -25934,7 +25935,7 @@ import {
|
|
|
25934
25935
|
readFileSync as readFileSync6,
|
|
25935
25936
|
writeFileSync as writeFileSync3
|
|
25936
25937
|
} from "node:fs";
|
|
25937
|
-
import { dirname as
|
|
25938
|
+
import { dirname as dirname3 } from "node:path";
|
|
25938
25939
|
import { randomUUID } from "node:crypto";
|
|
25939
25940
|
var DEFAULT_KEY = "phc_qKY87cKWZm6ZyCtk7LcRd2cU8Sg42u7Ywhui5stYCegd";
|
|
25940
25941
|
var DEFAULT_HOST = "https://us.i.posthog.com";
|
|
@@ -25961,7 +25962,7 @@ function getDistinctId() {
|
|
|
25961
25962
|
const id = randomUUID();
|
|
25962
25963
|
cachedDistinctId = id;
|
|
25963
25964
|
try {
|
|
25964
|
-
mkdirSync3(
|
|
25965
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
25965
25966
|
writeFileSync3(path, id, "utf-8");
|
|
25966
25967
|
} catch {}
|
|
25967
25968
|
return id;
|
|
@@ -26660,10 +26661,10 @@ init_atomic();
|
|
|
26660
26661
|
|
|
26661
26662
|
// src/cli/resolve-version.ts
|
|
26662
26663
|
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
|
|
26663
|
-
import { dirname as
|
|
26664
|
+
import { dirname as dirname5, join as join7 } from "node:path";
|
|
26664
26665
|
|
|
26665
26666
|
// src/build-info.ts
|
|
26666
|
-
var VERSION = "0.19.
|
|
26667
|
+
var VERSION = "0.19.10";
|
|
26667
26668
|
|
|
26668
26669
|
// src/cli/resolve-version.ts
|
|
26669
26670
|
function readPackageVersion() {
|
|
@@ -26678,7 +26679,7 @@ function readPackageVersion() {
|
|
|
26678
26679
|
}
|
|
26679
26680
|
} catch {}
|
|
26680
26681
|
}
|
|
26681
|
-
dir =
|
|
26682
|
+
dir = dirname5(dir);
|
|
26682
26683
|
}
|
|
26683
26684
|
return null;
|
|
26684
26685
|
}
|
|
@@ -26863,7 +26864,7 @@ function parseRolloutResultLine(stdout) {
|
|
|
26863
26864
|
var import__2 = __toESM(require_commander(), 1);
|
|
26864
26865
|
|
|
26865
26866
|
// src/cli/update.ts
|
|
26866
|
-
import { join as join8, dirname as
|
|
26867
|
+
import { join as join8, dirname as dirname6, resolve as resolve7 } from "node:path";
|
|
26867
26868
|
import { homedir as homedir5 } from "node:os";
|
|
26868
26869
|
|
|
26869
26870
|
// src/agents/lifecycle.ts
|
|
@@ -26985,7 +26986,7 @@ function parseUpdateResultLine(stdout) {
|
|
|
26985
26986
|
var import_yaml5 = __toESM(require_dist(), 1);
|
|
26986
26987
|
import { mkdtempSync, writeFileSync as writeFileSync4, rmSync as rmSync2, existsSync as existsSync10, readFileSync as readFileSync8 } from "node:fs";
|
|
26987
26988
|
import { tmpdir } from "node:os";
|
|
26988
|
-
import { join as join9, isAbsolute as
|
|
26989
|
+
import { join as join9, isAbsolute as isAbsolute3, normalize, basename as basename2 } from "node:path";
|
|
26989
26990
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
26990
26991
|
import { isDeepStrictEqual } from "node:util";
|
|
26991
26992
|
var MAX_PATCH_BYTES = 1024 * 1024;
|
|
@@ -26998,12 +26999,12 @@ function isTargetPathHeader(headerPath, targetBasename) {
|
|
|
26998
26999
|
let p = headerPath;
|
|
26999
27000
|
if (p.startsWith("a/") || p.startsWith("b/"))
|
|
27000
27001
|
p = p.slice(2);
|
|
27001
|
-
if (
|
|
27002
|
+
if (isAbsolute3(p))
|
|
27002
27003
|
return false;
|
|
27003
27004
|
if (p.includes(".."))
|
|
27004
27005
|
return false;
|
|
27005
27006
|
const norm = normalize(p);
|
|
27006
|
-
if (norm.includes("..") ||
|
|
27007
|
+
if (norm.includes("..") || isAbsolute3(norm))
|
|
27007
27008
|
return false;
|
|
27008
27009
|
return norm === targetBasename || basename2(norm) === targetBasename;
|
|
27009
27010
|
}
|
|
@@ -28448,7 +28449,8 @@ class HostdServer {
|
|
|
28448
28449
|
return [
|
|
28449
28450
|
join10(root, "profiles"),
|
|
28450
28451
|
join10(root, "profiles", "default"),
|
|
28451
|
-
join10(root, "vendor", "hindsight-memory")
|
|
28452
|
+
join10(root, "vendor", "hindsight-memory"),
|
|
28453
|
+
join10(root, "skills")
|
|
28452
28454
|
].filter((p) => !existsSync11(p));
|
|
28453
28455
|
}
|
|
28454
28456
|
applyAssetPreflight(request_id, started) {
|
|
@@ -29579,7 +29581,7 @@ ${output.recovery.stderr}` : "";
|
|
|
29579
29581
|
appendAuditRow(row) {
|
|
29580
29582
|
const path2 = this.auditLogPath();
|
|
29581
29583
|
this.auditAppendChain = this.auditAppendChain.then(async () => {
|
|
29582
|
-
await mkdir(
|
|
29584
|
+
await mkdir(dirname7(path2), { recursive: true }).catch(() => {
|
|
29583
29585
|
return;
|
|
29584
29586
|
});
|
|
29585
29587
|
if (this.auditChainState === undefined) {
|
|
@@ -18215,7 +18215,7 @@ var require_lib = __commonJS((exports, module) => {
|
|
|
18215
18215
|
// src/vault/approvals/kernel-server.ts
|
|
18216
18216
|
import * as net from "node:net";
|
|
18217
18217
|
import { mkdirSync as mkdirSync2, chmodSync, chownSync, existsSync as existsSync5, unlinkSync, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
|
|
18218
|
-
import { dirname, resolve as resolve6, basename as basename2 } from "node:path";
|
|
18218
|
+
import { dirname as dirname2, resolve as resolve6, basename as basename2 } from "node:path";
|
|
18219
18219
|
import { Database } from "bun:sqlite";
|
|
18220
18220
|
|
|
18221
18221
|
// src/vault/broker/protocol.ts
|
|
@@ -19017,7 +19017,7 @@ function getNonce(db, request_id) {
|
|
|
19017
19017
|
init_schema();
|
|
19018
19018
|
|
|
19019
19019
|
// src/agents/scaffold.ts
|
|
19020
|
-
import { join as join4, resolve as resolve5 } from "node:path";
|
|
19020
|
+
import { dirname, isAbsolute, join as join4, relative, resolve as resolve5 } from "node:path";
|
|
19021
19021
|
init_atomic();
|
|
19022
19022
|
|
|
19023
19023
|
// src/agents/agent-uid.ts
|
|
@@ -19132,6 +19132,7 @@ var PROFILE_MEMORY_DEFAULTS = {
|
|
|
19132
19132
|
};
|
|
19133
19133
|
// src/agents/reconcile-default-skills.ts
|
|
19134
19134
|
var warnedMissingPool = new Set;
|
|
19135
|
+
var warnedMissingDefault = new Set;
|
|
19135
19136
|
|
|
19136
19137
|
// src/telegram/state.ts
|
|
19137
19138
|
init_paths();
|
|
@@ -19224,7 +19225,7 @@ function checkApprovalAclByAgent(listenerAgent, claimedAgentUnit) {
|
|
|
19224
19225
|
var DEFAULT_SOCKET_PARENT = "/run/switchroom/kernel";
|
|
19225
19226
|
var DEFAULT_DB_PATH = "/state/approvals/kernel.db";
|
|
19226
19227
|
function openKernelDb(dbPath) {
|
|
19227
|
-
const dir =
|
|
19228
|
+
const dir = dirname2(dbPath);
|
|
19228
19229
|
mkdirSync2(dir, { recursive: true });
|
|
19229
19230
|
const db = new Database(dbPath, { create: true });
|
|
19230
19231
|
try {
|
|
@@ -19603,7 +19604,7 @@ async function bootstrap(opts) {
|
|
|
19603
19604
|
}
|
|
19604
19605
|
async function main() {
|
|
19605
19606
|
const socketEnv = process.env.SWITCHROOM_KERNEL_SOCKET ?? `${DEFAULT_SOCKET_PARENT}/approval-kernel.sock`;
|
|
19606
|
-
const socketParent =
|
|
19607
|
+
const socketParent = dirname2(resolve6(socketEnv));
|
|
19607
19608
|
const dbPath = process.env.SWITCHROOM_KERNEL_DB_PATH ?? DEFAULT_DB_PATH;
|
|
19608
19609
|
const configPath = process.env.SWITCHROOM_CONFIG;
|
|
19609
19610
|
const operatorUidRaw = process.env.SWITCHROOM_KERNEL_OPERATOR_UID;
|
|
@@ -18220,7 +18220,7 @@ import { mkdirSync as mkdirSync7, chmodSync as chmodSync5, chownSync, existsSync
|
|
|
18220
18220
|
init_schema();
|
|
18221
18221
|
|
|
18222
18222
|
// src/agents/scaffold.ts
|
|
18223
|
-
import { join as join4, resolve as resolve6 } from "node:path";
|
|
18223
|
+
import { dirname as dirname2, isAbsolute, join as join4, relative, resolve as resolve6 } from "node:path";
|
|
18224
18224
|
init_atomic();
|
|
18225
18225
|
|
|
18226
18226
|
// src/agents/agent-uid.ts
|
|
@@ -18840,6 +18840,7 @@ var PROFILE_MEMORY_DEFAULTS = {
|
|
|
18840
18840
|
};
|
|
18841
18841
|
// src/agents/reconcile-default-skills.ts
|
|
18842
18842
|
var warnedMissingPool = new Set;
|
|
18843
|
+
var warnedMissingDefault = new Set;
|
|
18843
18844
|
|
|
18844
18845
|
// src/telegram/state.ts
|
|
18845
18846
|
init_paths();
|
|
@@ -19719,7 +19720,7 @@ function migrateLegacyGrantsDbLocation(newDbPath = getGrantsDbPath(), deps) {
|
|
|
19719
19720
|
var BIND_MOUNT_EXACT_SOURCE_DENY = new Set(["/var/run/docker.sock"]);
|
|
19720
19721
|
|
|
19721
19722
|
// src/vault/broker/server.ts
|
|
19722
|
-
import { dirname as
|
|
19723
|
+
import { dirname as dirname7, resolve as resolve9, basename as basename4 } from "node:path";
|
|
19723
19724
|
import * as os3 from "node:os";
|
|
19724
19725
|
import * as path4 from "node:path";
|
|
19725
19726
|
|
|
@@ -19740,7 +19741,7 @@ import {
|
|
|
19740
19741
|
unlinkSync as unlinkSync3
|
|
19741
19742
|
} from "node:fs";
|
|
19742
19743
|
import { createHash as createHash2 } from "node:crypto";
|
|
19743
|
-
import { basename as basename3, dirname as
|
|
19744
|
+
import { basename as basename3, dirname as dirname4, join as join6 } from "node:path";
|
|
19744
19745
|
function vaultLayoutPaths(home2) {
|
|
19745
19746
|
const switchroomRoot = join6(home2, ".switchroom");
|
|
19746
19747
|
return {
|
|
@@ -19827,7 +19828,7 @@ function sha256File(path2) {
|
|
|
19827
19828
|
return createHash2("sha256").update(data).digest("hex");
|
|
19828
19829
|
}
|
|
19829
19830
|
function atomicReplaceWithSymlink(target, linkTarget) {
|
|
19830
|
-
const tmp = join6(
|
|
19831
|
+
const tmp = join6(dirname4(target), `.${basename3(target)}.symlink-tmp`);
|
|
19831
19832
|
if (existsSync8(tmp)) {
|
|
19832
19833
|
try {
|
|
19833
19834
|
unlinkSync3(tmp);
|
|
@@ -22614,7 +22615,7 @@ class VaultBroker {
|
|
|
22614
22615
|
this.passphrase = this.testOpts._testPassphrase;
|
|
22615
22616
|
}
|
|
22616
22617
|
process.umask(63);
|
|
22617
|
-
const parentDir =
|
|
22618
|
+
const parentDir = dirname7(this.socketPath);
|
|
22618
22619
|
mkdirSync7(parentDir, { recursive: true, mode: 448 });
|
|
22619
22620
|
try {
|
|
22620
22621
|
chmodSync5(parentDir, 448);
|
|
@@ -24159,15 +24160,15 @@ class VaultBroker {
|
|
|
24159
24160
|
}
|
|
24160
24161
|
}
|
|
24161
24162
|
function detectVaultLayoutDrift(vaultPath) {
|
|
24162
|
-
const dir =
|
|
24163
|
+
const dir = dirname7(vaultPath);
|
|
24163
24164
|
if (basename4(dir) !== "vault")
|
|
24164
24165
|
return;
|
|
24165
24166
|
if (basename4(vaultPath) !== "vault.enc")
|
|
24166
24167
|
return;
|
|
24167
|
-
const switchroomDir =
|
|
24168
|
+
const switchroomDir = dirname7(dir);
|
|
24168
24169
|
if (basename4(switchroomDir) !== ".switchroom")
|
|
24169
24170
|
return;
|
|
24170
|
-
const home2 =
|
|
24171
|
+
const home2 = dirname7(switchroomDir);
|
|
24171
24172
|
const result = inspectVaultLayout(home2);
|
|
24172
24173
|
if (result.kind === "divergent") {
|
|
24173
24174
|
throw new VaultError(`Vault layout divergence detected at boot: ${result.details.oldPath} and ${result.details.newPath} are both regular files with different content. An older switchroom CLI may have written to the legacy path after migration ran. Run \`switchroom apply\` from the host to surface the recovery recipe (state E refusal with literal \`mv\` commands). See docs/operators/state-e-recovery.md.`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchroom",
|
|
3
3
|
"//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
|
|
4
|
-
"version": "0.19.
|
|
4
|
+
"version": "0.19.10",
|
|
5
5
|
"description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -8755,9 +8755,51 @@ var init_inline_pairs_guard = __esm(() => {
|
|
|
8755
8755
|
SPOILER_OP = /(?<=\w)\|\|(?=\w)/g;
|
|
8756
8756
|
});
|
|
8757
8757
|
|
|
8758
|
+
// render/unsupported-token-guard.ts
|
|
8759
|
+
function toExpandableBlockquote(title, body) {
|
|
8760
|
+
const lines = [];
|
|
8761
|
+
const t = title.trim();
|
|
8762
|
+
if (t)
|
|
8763
|
+
lines.push(t);
|
|
8764
|
+
for (const raw of body.split(`
|
|
8765
|
+
`)) {
|
|
8766
|
+
const line = raw.trimEnd();
|
|
8767
|
+
if (line.trim() === "" && lines.length === 0)
|
|
8768
|
+
continue;
|
|
8769
|
+
lines.push(line);
|
|
8770
|
+
}
|
|
8771
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === "")
|
|
8772
|
+
lines.pop();
|
|
8773
|
+
if (lines.length === 0)
|
|
8774
|
+
return "";
|
|
8775
|
+
return lines.map((line, i) => i === 0 ? `**> ${line}` : `> ${line}`).join(`
|
|
8776
|
+
`);
|
|
8777
|
+
}
|
|
8778
|
+
function repairProse(text) {
|
|
8779
|
+
let out = text;
|
|
8780
|
+
out = out.replace(DETAILS_BLOCK, (_m, summary, body) => toExpandableBlockquote(summary ?? "", body ?? ""));
|
|
8781
|
+
out = out.replace(ORPHAN_TAGS, "");
|
|
8782
|
+
out = out.replace(CARET_PAIR, (_m, inner) => inner);
|
|
8783
|
+
out = out.replace(FOOTNOTE_MARKER, "");
|
|
8784
|
+
return out;
|
|
8785
|
+
}
|
|
8786
|
+
function guardUnsupportedTokens(text) {
|
|
8787
|
+
if (!/[\^]|<details|<\/details|<summary/i.test(text))
|
|
8788
|
+
return text;
|
|
8789
|
+
return splitProtectedSegments(text).map((seg) => seg.code ? seg.text : repairProse(seg.text)).join("");
|
|
8790
|
+
}
|
|
8791
|
+
var CARET_PAIR, FOOTNOTE_MARKER, DETAILS_BLOCK, ORPHAN_TAGS;
|
|
8792
|
+
var init_unsupported_token_guard = __esm(() => {
|
|
8793
|
+
CARET_PAIR = /\^([A-Za-z0-9]+)\^/g;
|
|
8794
|
+
FOOTNOTE_MARKER = /\[\^\d+\](?!:)/g;
|
|
8795
|
+
DETAILS_BLOCK = /<details[^>]*>\s*(?:<summary[^>]*>([\s\S]*?)<\/summary>)?([\s\S]*?)<\/details>/gi;
|
|
8796
|
+
ORPHAN_TAGS = /<\/?(?:details|summary)[^>]*>/gi;
|
|
8797
|
+
});
|
|
8798
|
+
|
|
8758
8799
|
// rich-send.ts
|
|
8759
8800
|
function guardAccidentalFormatting(markdown) {
|
|
8760
8801
|
let out = markdown;
|
|
8802
|
+
out = guardUnsupportedTokens(out);
|
|
8761
8803
|
out = guardAccidentalEmphasis(out);
|
|
8762
8804
|
out = guardAccidentalHeading(out);
|
|
8763
8805
|
out = guardAccidentalBlockConstructs(out);
|
|
@@ -8788,6 +8830,7 @@ var init_rich_send = __esm(() => {
|
|
|
8788
8830
|
init_emphasis_guard();
|
|
8789
8831
|
init_line_start_guard();
|
|
8790
8832
|
init_inline_pairs_guard();
|
|
8833
|
+
init_unsupported_token_guard();
|
|
8791
8834
|
import_grammy = __toESM(require_mod2(), 1);
|
|
8792
8835
|
});
|
|
8793
8836
|
|
|
@@ -35216,7 +35259,7 @@ function renderAuthLine(state7, agentName3, now = Date.now()) {
|
|
|
35216
35259
|
|
|
35217
35260
|
// gateway/quota-cache.ts
|
|
35218
35261
|
import { existsSync as existsSync41, readFileSync as readFileSync42, writeFileSync as writeFileSync35, mkdirSync as mkdirSync36 } from "fs";
|
|
35219
|
-
import { join as join46, dirname as
|
|
35262
|
+
import { join as join46, dirname as dirname15 } from "path";
|
|
35220
35263
|
function defaultCachePath() {
|
|
35221
35264
|
return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join46(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
|
|
35222
35265
|
}
|
|
@@ -35253,7 +35296,7 @@ function writeQuotaCache(result, opts = {}) {
|
|
|
35253
35296
|
result
|
|
35254
35297
|
};
|
|
35255
35298
|
try {
|
|
35256
|
-
mkdirSync36(
|
|
35299
|
+
mkdirSync36(dirname15(path2), { recursive: true });
|
|
35257
35300
|
writeFileSync35(path2, JSON.stringify(entry, null, 2), { mode: 384 });
|
|
35258
35301
|
} catch {}
|
|
35259
35302
|
}
|
|
@@ -36070,7 +36113,7 @@ var init_boot_probes = __esm(() => {
|
|
|
36070
36113
|
|
|
36071
36114
|
// gateway/boot-issue-cache.ts
|
|
36072
36115
|
import { existsSync as existsSync43, readFileSync as readFileSync44, writeFileSync as writeFileSync36, mkdirSync as mkdirSync37, renameSync as renameSync17 } from "fs";
|
|
36073
|
-
import { dirname as
|
|
36116
|
+
import { dirname as dirname16 } from "path";
|
|
36074
36117
|
function fingerprintProbe(key, r) {
|
|
36075
36118
|
if (r.status === "ok")
|
|
36076
36119
|
return `${key}:ok`;
|
|
@@ -36196,7 +36239,7 @@ function applyAndSave(path2, cache, diff) {
|
|
|
36196
36239
|
}
|
|
36197
36240
|
}
|
|
36198
36241
|
try {
|
|
36199
|
-
mkdirSync37(
|
|
36242
|
+
mkdirSync37(dirname16(path2), { recursive: true });
|
|
36200
36243
|
const tmp = `${path2}.tmp`;
|
|
36201
36244
|
writeFileSync36(tmp, JSON.stringify(next), { mode: 384 });
|
|
36202
36245
|
renameSync17(tmp, path2);
|
|
@@ -36213,7 +36256,7 @@ var init_boot_issue_cache = __esm(() => {
|
|
|
36213
36256
|
// gateway/config-snapshot.ts
|
|
36214
36257
|
import { createHash as createHash3 } from "crypto";
|
|
36215
36258
|
import { existsSync as existsSync44, readFileSync as readFileSync45, writeFileSync as writeFileSync37, mkdirSync as mkdirSync38, renameSync as renameSync18 } from "fs";
|
|
36216
|
-
import { dirname as
|
|
36259
|
+
import { dirname as dirname17 } from "path";
|
|
36217
36260
|
function hashStringArray(items) {
|
|
36218
36261
|
if (!items || items.length === 0)
|
|
36219
36262
|
return null;
|
|
@@ -36311,7 +36354,7 @@ function loadSnapshot(path2, now = Date.now) {
|
|
|
36311
36354
|
}
|
|
36312
36355
|
function persistSnapshot(path2, snapshot) {
|
|
36313
36356
|
try {
|
|
36314
|
-
mkdirSync38(
|
|
36357
|
+
mkdirSync38(dirname17(path2), { recursive: true });
|
|
36315
36358
|
const tmp = `${path2}.tmp`;
|
|
36316
36359
|
writeFileSync37(tmp, JSON.stringify(snapshot), { mode: 384 });
|
|
36317
36360
|
renameSync18(tmp, path2);
|
|
@@ -73578,9 +73621,11 @@ init_dollar_math_guard();
|
|
|
73578
73621
|
init_emphasis_guard();
|
|
73579
73622
|
init_line_start_guard();
|
|
73580
73623
|
init_inline_pairs_guard();
|
|
73624
|
+
init_unsupported_token_guard();
|
|
73581
73625
|
var import_grammy9 = __toESM(require_mod2(), 1);
|
|
73582
73626
|
function guardAccidentalFormatting2(markdown) {
|
|
73583
73627
|
let out = markdown;
|
|
73628
|
+
out = guardUnsupportedTokens(out);
|
|
73584
73629
|
out = guardAccidentalEmphasis(out);
|
|
73585
73630
|
out = guardAccidentalHeading(out);
|
|
73586
73631
|
out = guardAccidentalBlockConstructs(out);
|
|
@@ -81001,7 +81046,7 @@ async function discoverModels(agentName3, opts = {}) {
|
|
|
81001
81046
|
}
|
|
81002
81047
|
|
|
81003
81048
|
// ../src/agents/scaffold.ts
|
|
81004
|
-
import { join as join38, resolve as resolve8 } from "node:path";
|
|
81049
|
+
import { dirname as dirname14, isAbsolute, join as join38, relative, resolve as resolve8 } from "node:path";
|
|
81005
81050
|
init_atomic();
|
|
81006
81051
|
|
|
81007
81052
|
// ../src/agents/agent-uid.ts
|
|
@@ -81105,6 +81150,7 @@ var PROFILE_MEMORY_DEFAULTS = {
|
|
|
81105
81150
|
};
|
|
81106
81151
|
// ../src/agents/reconcile-default-skills.ts
|
|
81107
81152
|
var warnedMissingPool = new Set;
|
|
81153
|
+
var warnedMissingDefault = new Set;
|
|
81108
81154
|
|
|
81109
81155
|
// ../src/telegram/state.ts
|
|
81110
81156
|
init_paths();
|
|
@@ -93568,10 +93614,10 @@ function startGatewayHeartbeat(stateDir, intervalMs = GATEWAY_HEARTBEAT_INTERVAL
|
|
|
93568
93614
|
}
|
|
93569
93615
|
|
|
93570
93616
|
// ../src/build-info.ts
|
|
93571
|
-
var VERSION = "0.19.
|
|
93572
|
-
var COMMIT_SHA = "
|
|
93573
|
-
var COMMIT_DATE = "2026-07-
|
|
93574
|
-
var LATEST_PR =
|
|
93617
|
+
var VERSION = "0.19.10";
|
|
93618
|
+
var COMMIT_SHA = "d88584dd";
|
|
93619
|
+
var COMMIT_DATE = "2026-07-21T11:39:08Z";
|
|
93620
|
+
var LATEST_PR = 3495;
|
|
93575
93621
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
93576
93622
|
|
|
93577
93623
|
// gateway/boot-version.ts
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Outbound repair of markdown tokens Telegram's rich GFM parser (Bot API 10.1)
|
|
2
|
+
// CANNOT render, so the reader would otherwise see literal noise glyphs.
|
|
3
|
+
//
|
|
4
|
+
// ── Root cause ───────────────────────────────────────────────────────────
|
|
5
|
+
// Assistant replies are composed by a model that habitually emits constructs
|
|
6
|
+
// from OTHER surfaces (GitHub / Obsidian / LaTeX): caret highlight/superscript
|
|
7
|
+
// `^…^`, footnote markers `[^1]`, and HTML `<details><summary>` collapsibles.
|
|
8
|
+
// None of these are part of Telegram's rich markdown; they degrade to literal
|
|
9
|
+
// carets, stray `[^1]`, and raw `<details>` tags on the reader's screen — the
|
|
10
|
+
// worst kind of consistency failure (a broken glyph in chat). The resident
|
|
11
|
+
// floor card tells the model not to emit them, but prompt discipline is not a
|
|
12
|
+
// guarantee. This guard makes the repair deterministic at send time.
|
|
13
|
+
//
|
|
14
|
+
// ── What it repairs (deterministic, pure string transform) ─────────────────
|
|
15
|
+
// • `<details><summary>Title</summary>body</details>` → a Telegram EXPANDABLE
|
|
16
|
+
// blockquote (`**> Title` first line + `> …` continuation) — the native
|
|
17
|
+
// equivalent of a collapsible. `<details>` without a `<summary>` folds the
|
|
18
|
+
// whole body into an expandable blockquote. Any orphan `<details>` /
|
|
19
|
+
// `</details>` / `<summary>` tags left over are stripped.
|
|
20
|
+
// • `^highlight^` / `x^2^` caret pairs → the inner text, carets removed
|
|
21
|
+
// (Telegram has no highlight/superscript; the carets render literally).
|
|
22
|
+
// • Footnote reference markers `[^id]` → removed (Telegram has no footnotes).
|
|
23
|
+
// A footnote DEFINITION line `[^id]: …` is left alone (the `]:` lookahead).
|
|
24
|
+
//
|
|
25
|
+
// ── What it deliberately does NOT touch ────────────────────────────────────
|
|
26
|
+
// • `$…$` math: already neutralised deterministically upstream by
|
|
27
|
+
// `guardDollarMath` (it backslash-escapes `$` so the pair can never typeset
|
|
28
|
+
// as math — the reader sees literal `$`). Re-processing `$` here would
|
|
29
|
+
// double-process and risk corrupting currency prose, so this guard leaves
|
|
30
|
+
// `$` untouched by design. Math repair is COVERED, just in the sibling guard.
|
|
31
|
+
// • `~sub~` tilde pairs: the strikethrough/tilde trigger is owned by
|
|
32
|
+
// `guardAccidentalInlinePairs` (disjoint char set). This guard never
|
|
33
|
+
// inspects or inserts `~`.
|
|
34
|
+
// • `__underline__`: renders as BOLD in Telegram — legible, not broken — so it
|
|
35
|
+
// is left as-is (the floor card asks the model to avoid it, but there is no
|
|
36
|
+
// glyph-level failure to repair).
|
|
37
|
+
//
|
|
38
|
+
// Code spans / fenced blocks / link destinations / table rows are emitted
|
|
39
|
+
// verbatim (shared `splitProtectedSegments`). A strict no-op for any body
|
|
40
|
+
// without one of these tokens, and idempotent (running twice is a no-op the
|
|
41
|
+
// second time — a repaired blockquote contains no `<details>`, a stripped caret
|
|
42
|
+
// pair contains no `^`). Safe to compose once per send alongside the #3252
|
|
43
|
+
// accidental-formatting guards.
|
|
44
|
+
|
|
45
|
+
import { splitProtectedSegments } from "./code-segments.js";
|
|
46
|
+
|
|
47
|
+
/** Paired caret highlight / superscript: `^text^` where the inner run is a
|
|
48
|
+
* single ALPHANUMERIC token (no newline, no whitespace, no nested caret, and
|
|
49
|
+
* crucially no expression punctuation like `+ = -`). Non-greedy inner run.
|
|
50
|
+
* Replaced by the inner text.
|
|
51
|
+
*
|
|
52
|
+
* The alphanumeric-only constraint is deliberate and NARROWER than a plain
|
|
53
|
+
* "non-caret non-space" run: it distinguishes a genuine single superscript /
|
|
54
|
+
* highlight token (`x^2^`, `^highlighted^`) from a whitespace-free math
|
|
55
|
+
* expression whose carets are INDEPENDENT exponents (`a^2+b^2=c^2`). With a
|
|
56
|
+
* permissive inner run the first two carets of `a^2+b^2=c^2` pair up (`^2+b^`)
|
|
57
|
+
* and get stripped, mangling the math; requiring the inner run to be pure
|
|
58
|
+
* alphanumerics means `^2+b^` never matches (the `+` breaks the run), so
|
|
59
|
+
* `a^2+b^2=c^2`, `2^8`, and `x^n` all pass through untouched. */
|
|
60
|
+
const CARET_PAIR = /\^([A-Za-z0-9]+)\^/g;
|
|
61
|
+
|
|
62
|
+
/** Footnote reference marker `[^N]` (digits only) NOT immediately followed by
|
|
63
|
+
* `:` (which would make it a footnote DEFINITION line we leave intact). Removed
|
|
64
|
+
* entirely. Restricting the id to digits keeps this off in-prose bracket
|
|
65
|
+
* literals like `array[^index]` and regex-ish `[^/]`, which are NOT footnotes
|
|
66
|
+
* and would otherwise be silently eaten. */
|
|
67
|
+
const FOOTNOTE_MARKER = /\[\^\d+\](?!:)/g;
|
|
68
|
+
|
|
69
|
+
/** `<details>…</details>` with an optional leading `<summary>…</summary>`.
|
|
70
|
+
* Dot-all via `[\s\S]`; non-greedy so adjacent blocks don't merge. */
|
|
71
|
+
const DETAILS_BLOCK =
|
|
72
|
+
/<details[^>]*>\s*(?:<summary[^>]*>([\s\S]*?)<\/summary>)?([\s\S]*?)<\/details>/gi;
|
|
73
|
+
|
|
74
|
+
/** Orphan collapsible tags left after DETAILS_BLOCK (malformed / unpaired). */
|
|
75
|
+
const ORPHAN_TAGS = /<\/?(?:details|summary)[^>]*>/gi;
|
|
76
|
+
|
|
77
|
+
/** Fold `title` + `body` into a Telegram expandable blockquote: the first
|
|
78
|
+
* emitted line carries the `**> ` marker (switchroom's expandable-blockquote
|
|
79
|
+
* encoding — see parse.ts), every subsequent line a plain `> `. */
|
|
80
|
+
function toExpandableBlockquote(title: string, body: string): string {
|
|
81
|
+
const lines: string[] = [];
|
|
82
|
+
const t = title.trim();
|
|
83
|
+
if (t) lines.push(t);
|
|
84
|
+
for (const raw of body.split("\n")) {
|
|
85
|
+
const line = raw.trimEnd();
|
|
86
|
+
// Collapse leading/trailing blank lines but keep interior structure.
|
|
87
|
+
if (line.trim() === "" && lines.length === 0) continue;
|
|
88
|
+
lines.push(line);
|
|
89
|
+
}
|
|
90
|
+
// Trim trailing blanks.
|
|
91
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === "") lines.pop();
|
|
92
|
+
if (lines.length === 0) return "";
|
|
93
|
+
return lines
|
|
94
|
+
.map((line, i) => (i === 0 ? `**> ${line}` : `> ${line}`))
|
|
95
|
+
.join("\n");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Repair unsupported tokens in a single PROSE segment. */
|
|
99
|
+
function repairProse(text: string): string {
|
|
100
|
+
let out = text;
|
|
101
|
+
out = out.replace(DETAILS_BLOCK, (_m, summary: string | undefined, body: string) =>
|
|
102
|
+
toExpandableBlockquote(summary ?? "", body ?? ""),
|
|
103
|
+
);
|
|
104
|
+
out = out.replace(ORPHAN_TAGS, "");
|
|
105
|
+
out = out.replace(CARET_PAIR, (_m, inner: string) => inner);
|
|
106
|
+
out = out.replace(FOOTNOTE_MARKER, "");
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Neutralise Telegram-unrenderable tokens (`<details>`, `^…^`, `[^1]`) on the
|
|
112
|
+
* FINAL rendered rich-markdown string. Code / links / tables are verbatim.
|
|
113
|
+
* Deterministic, idempotent, and a strict no-op absent any target token.
|
|
114
|
+
*/
|
|
115
|
+
export function guardUnsupportedTokens(text: string): string {
|
|
116
|
+
// Cheap pre-check: nothing to do unless a target trigger char is present.
|
|
117
|
+
if (!/[\^]|<details|<\/details|<summary/i.test(text)) return text;
|
|
118
|
+
return splitProtectedSegments(text)
|
|
119
|
+
.map((seg) => (seg.code ? seg.text : repairProse(seg.text)))
|
|
120
|
+
.join("");
|
|
121
|
+
}
|
|
@@ -22,6 +22,7 @@ import { guardDollarMath } from './render/dollar-math-guard.js'
|
|
|
22
22
|
import { guardAccidentalEmphasis } from './render/emphasis-guard.js'
|
|
23
23
|
import { guardAccidentalBlockConstructs, guardAccidentalHeading } from './render/line-start-guard.js'
|
|
24
24
|
import { guardAccidentalInlinePairs } from './render/inline-pairs-guard.js'
|
|
25
|
+
import { guardUnsupportedTokens } from './render/unsupported-token-guard.js'
|
|
25
26
|
|
|
26
27
|
/** The `InputRichMessage` shape grammy 1.44 accepts on send AND edit. */
|
|
27
28
|
export interface InputRichMessageMarkdown {
|
|
@@ -56,6 +57,14 @@ export interface InputRichMessageMarkdown {
|
|
|
56
57
|
*/
|
|
57
58
|
export function guardAccidentalFormatting(markdown: string): string {
|
|
58
59
|
let out = markdown
|
|
60
|
+
// Repair Telegram-unrenderable tokens FIRST: `<details>` folds into a `**> `
|
|
61
|
+
// expandable blockquote whose `> `-with-space lines are invisible to the
|
|
62
|
+
// block-construct guard (which only escapes space-LESS `>digit`/`>=`), and
|
|
63
|
+
// `**> ` is the exact marker switchroom's own render path emits — so the
|
|
64
|
+
// downstream emphasis/block guards treat it identically to a native
|
|
65
|
+
// expandable quote. Caret/footnote removal inserts no trigger char for any
|
|
66
|
+
// sibling guard, so this pass neither creates nor destroys their signals.
|
|
67
|
+
out = guardUnsupportedTokens(out)
|
|
59
68
|
out = guardAccidentalEmphasis(out)
|
|
60
69
|
out = guardAccidentalHeading(out)
|
|
61
70
|
out = guardAccidentalBlockConstructs(out)
|