switchroom 0.20.8 → 0.20.9
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/agent-scheduler/index.js +16 -13
- package/dist/auth-broker/index.js +51 -29
- package/dist/cli/autoaccept-poll.js +5 -3
- package/dist/cli/drive-write-pretool.mjs +5 -3
- package/dist/cli/ms-365-write-pretool.mjs +5 -3
- package/dist/cli/notion-write-pretool.mjs +6 -6
- package/dist/cli/switchroom.js +12 -10
- package/dist/host-control/main.js +7 -7
- package/dist/vault/approvals/kernel-server.js +6 -6
- package/dist/vault/broker/server.js +6 -6
- package/package.json +1 -1
- package/profiles/default/CLAUDE.md.hbs +12 -13
- package/telegram-plugin/ask-user.ts +6 -7
- package/telegram-plugin/dist/gateway/gateway.js +183 -66
- package/telegram-plugin/gateway/auth-broker-client.ts +1 -1
- package/telegram-plugin/gateway/auth-command.ts +4 -2
- package/telegram-plugin/gateway/checklist-fallback.ts +8 -1
- package/telegram-plugin/gateway/gateway.ts +8 -4
- package/telegram-plugin/gateway/outbound-send-path.ts +9 -1
- package/telegram-plugin/gateway/subagent-handback-inbound-builder.ts +21 -1
- package/telegram-plugin/gateway/subagent-handback-marker.ts +94 -0
- package/telegram-plugin/gateway/throttle-tier-wiring.ts +93 -18
- package/telegram-plugin/render/emphasis-guard.ts +92 -12
- package/telegram-plugin/render/line-start-guard.ts +27 -2
- package/telegram-plugin/sticker-aliases.ts +12 -14
- package/telegram-plugin/tests/ask-user.test.ts +15 -0
- package/telegram-plugin/tests/checklist-fallback.test.ts +21 -0
- package/telegram-plugin/tests/handback-tasknotif-dedup.test.ts +248 -0
- package/telegram-plugin/tests/render/emphasis-guard.test.ts +105 -6
- package/telegram-plugin/tests/render/heading-guard-blockquote-glued-hash.test.ts +123 -36
- package/telegram-plugin/tests/reply-quote-wire.test.ts +47 -0
- package/telegram-plugin/tests/sticker-aliases.test.ts +43 -0
- package/telegram-plugin/tests/throttle-tier-probe-only.test.ts +216 -0
- package/telegram-plugin/tests/throttle-tier-route-429-wiring.test.ts +92 -0
- package/telegram-plugin/tests/throttle-tier-route-429.test.ts +71 -0
- package/telegram-plugin/throttle-tier.ts +59 -0
|
@@ -11329,7 +11329,8 @@ var init_protocol = __esm(() => {
|
|
|
11329
11329
|
v: exports_external.literal(PROTOCOL_VERSION),
|
|
11330
11330
|
op: exports_external.literal("mark-throttled"),
|
|
11331
11331
|
id: exports_external.string().min(1),
|
|
11332
|
-
until: exports_external.number().int().positive()
|
|
11332
|
+
until: exports_external.number().int().positive(),
|
|
11333
|
+
probeOnly: exports_external.boolean().optional()
|
|
11333
11334
|
});
|
|
11334
11335
|
RefreshAccountRequestSchema = exports_external.object({
|
|
11335
11336
|
v: exports_external.literal(PROTOCOL_VERSION),
|
|
@@ -11725,12 +11726,13 @@ class AuthBrokerClient {
|
|
|
11725
11726
|
const data = await this.send(req);
|
|
11726
11727
|
return data;
|
|
11727
11728
|
}
|
|
11728
|
-
async markThrottled(until) {
|
|
11729
|
+
async markThrottled(until, probeOnly = false) {
|
|
11729
11730
|
const data = await this.send({
|
|
11730
11731
|
v: PROTOCOL_VERSION,
|
|
11731
11732
|
id: randomUUID2(),
|
|
11732
11733
|
op: "mark-throttled",
|
|
11733
|
-
until
|
|
11734
|
+
until,
|
|
11735
|
+
...probeOnly ? { probeOnly: true } : {}
|
|
11734
11736
|
});
|
|
11735
11737
|
return data;
|
|
11736
11738
|
}
|
|
@@ -13480,12 +13482,27 @@ var init_dollar_math_guard = __esm(() => {
|
|
|
13480
13482
|
});
|
|
13481
13483
|
|
|
13482
13484
|
// render/emphasis-guard.ts
|
|
13485
|
+
function isLineLeadingBullet(text, starIndex) {
|
|
13486
|
+
const next = text[starIndex + 1];
|
|
13487
|
+
if (next !== " " && next !== "\t")
|
|
13488
|
+
return false;
|
|
13489
|
+
let i = starIndex - 1;
|
|
13490
|
+
let indent = 0;
|
|
13491
|
+
while (i >= 0 && (text[i] === " " || text[i] === "\t")) {
|
|
13492
|
+
if (++indent > 3)
|
|
13493
|
+
return false;
|
|
13494
|
+
i--;
|
|
13495
|
+
}
|
|
13496
|
+
return i < 0 || text[i] === `
|
|
13497
|
+
`;
|
|
13498
|
+
}
|
|
13483
13499
|
function guardAccidentalEmphasis(text) {
|
|
13484
13500
|
if (!text.includes("_") && !text.includes("*"))
|
|
13485
13501
|
return text;
|
|
13486
13502
|
const segments = splitProtectedSegments(text);
|
|
13487
13503
|
let hasIntraUnderscore = false;
|
|
13488
13504
|
let hasIntraAsterisk = false;
|
|
13505
|
+
let hasBoundaryAsterisk = false;
|
|
13489
13506
|
let underscoreCount = 0;
|
|
13490
13507
|
let asteriskCount = 0;
|
|
13491
13508
|
for (const seg of segments) {
|
|
@@ -13495,14 +13512,24 @@ function guardAccidentalEmphasis(text) {
|
|
|
13495
13512
|
hasIntraUnderscore = true;
|
|
13496
13513
|
if (INTRA_WORD_ASTERISK.test(seg.text))
|
|
13497
13514
|
hasIntraAsterisk = true;
|
|
13515
|
+
if (!hasBoundaryAsterisk) {
|
|
13516
|
+
for (const m of seg.text.matchAll(BOUNDARY_FLANKED_ASTERISK)) {
|
|
13517
|
+
if (!isLineLeadingBullet(seg.text, m.index ?? 0)) {
|
|
13518
|
+
hasBoundaryAsterisk = true;
|
|
13519
|
+
break;
|
|
13520
|
+
}
|
|
13521
|
+
}
|
|
13522
|
+
}
|
|
13498
13523
|
underscoreCount += seg.text.match(ANY_UNDERSCORE)?.length ?? 0;
|
|
13499
13524
|
asteriskCount += seg.text.match(ANY_ASTERISK)?.length ?? 0;
|
|
13500
13525
|
}
|
|
13501
13526
|
INTRA_WORD_UNDERSCORE.lastIndex = 0;
|
|
13502
13527
|
INTRA_WORD_ASTERISK.lastIndex = 0;
|
|
13528
|
+
BOUNDARY_FLANKED_ASTERISK.lastIndex = 0;
|
|
13503
13529
|
const armUnderscore = hasIntraUnderscore && underscoreCount >= 2;
|
|
13504
13530
|
const armAsterisk = hasIntraAsterisk && asteriskCount >= 2;
|
|
13505
|
-
|
|
13531
|
+
const armBoundaryAsterisk = hasBoundaryAsterisk;
|
|
13532
|
+
if (!armUnderscore && !armAsterisk && !armBoundaryAsterisk)
|
|
13506
13533
|
return text;
|
|
13507
13534
|
return segments.map((seg) => {
|
|
13508
13535
|
if (seg.code)
|
|
@@ -13512,13 +13539,17 @@ function guardAccidentalEmphasis(text) {
|
|
|
13512
13539
|
out = out.replace(INTRA_WORD_UNDERSCORE, "\\_");
|
|
13513
13540
|
if (armAsterisk)
|
|
13514
13541
|
out = out.replace(INTRA_WORD_ASTERISK, "\\*");
|
|
13542
|
+
if (armBoundaryAsterisk) {
|
|
13543
|
+
out = out.replace(BOUNDARY_FLANKED_ASTERISK, (m, offset, str) => isLineLeadingBullet(str, offset) ? m : "\\*");
|
|
13544
|
+
}
|
|
13515
13545
|
return out;
|
|
13516
13546
|
}).join("");
|
|
13517
13547
|
}
|
|
13518
|
-
var INTRA_WORD_UNDERSCORE, INTRA_WORD_ASTERISK, ANY_UNDERSCORE, ANY_ASTERISK;
|
|
13548
|
+
var INTRA_WORD_UNDERSCORE, INTRA_WORD_ASTERISK, BOUNDARY_FLANKED_ASTERISK, ANY_UNDERSCORE, ANY_ASTERISK;
|
|
13519
13549
|
var init_emphasis_guard = __esm(() => {
|
|
13520
13550
|
INTRA_WORD_UNDERSCORE = /(?<=[A-Za-z0-9])_(?=[A-Za-z0-9])/g;
|
|
13521
13551
|
INTRA_WORD_ASTERISK = /(?<=[A-Za-z0-9])\*(?=[A-Za-z0-9])/g;
|
|
13552
|
+
BOUNDARY_FLANKED_ASTERISK = /(?<=^|\s)\*(?=\s|$)/g;
|
|
13522
13553
|
ANY_UNDERSCORE = /(?<!\\)_/g;
|
|
13523
13554
|
ANY_ASTERISK = /(?<!\\)\*/g;
|
|
13524
13555
|
});
|
|
@@ -13569,7 +13600,7 @@ function guardAccidentalBlockConstructs(text) {
|
|
|
13569
13600
|
return out;
|
|
13570
13601
|
}
|
|
13571
13602
|
function escapeAccidentalHeadingLine(line) {
|
|
13572
|
-
return line.replace(ACCIDENTAL_HEADING, "$1\\$2");
|
|
13603
|
+
return line.replace(ACCIDENTAL_HEADING, "$1\\$2").replace(ACCIDENTAL_HEADING_AFTER_MARKER, "$1\\$2");
|
|
13573
13604
|
}
|
|
13574
13605
|
function guardAccidentalHeading(text) {
|
|
13575
13606
|
if (!text.includes("#"))
|
|
@@ -13599,11 +13630,12 @@ function guardAccidentalHeading(text) {
|
|
|
13599
13630
|
}
|
|
13600
13631
|
return out;
|
|
13601
13632
|
}
|
|
13602
|
-
var ACCIDENTAL_BLOCKQUOTE, ACCIDENTAL_ORDERED_LIST, ACCIDENTAL_HEADING;
|
|
13633
|
+
var ACCIDENTAL_BLOCKQUOTE, ACCIDENTAL_ORDERED_LIST, ACCIDENTAL_HEADING, ACCIDENTAL_HEADING_AFTER_MARKER;
|
|
13603
13634
|
var init_line_start_guard = __esm(() => {
|
|
13604
13635
|
ACCIDENTAL_BLOCKQUOTE = /^>[0-9=]/;
|
|
13605
13636
|
ACCIDENTAL_ORDERED_LIST = /^(\d{4,})([.)])(\s|$)/;
|
|
13606
13637
|
ACCIDENTAL_HEADING = /^([ \t]{0,3})(#{1,6})(?=[^\s#])/;
|
|
13638
|
+
ACCIDENTAL_HEADING_AFTER_MARKER = /^([ \t]{0,3}(?:(?:[-*+]|\d{1,3}[.)])[ \t]+|>[ \t]*)+)(#{1,6})(?=[^\s#])/;
|
|
13607
13639
|
});
|
|
13608
13640
|
|
|
13609
13641
|
// render/inline-pairs-guard.ts
|
|
@@ -22616,7 +22648,7 @@ function deriveOverlayTitle(raw, fileName) {
|
|
|
22616
22648
|
return;
|
|
22617
22649
|
return base.length > 0 ? base : undefined;
|
|
22618
22650
|
}
|
|
22619
|
-
function readOverlayFile(agentName, file, agentCfg, warnings) {
|
|
22651
|
+
function readOverlayFile(agentName, file, agentCfg, warnings, source) {
|
|
22620
22652
|
try {
|
|
22621
22653
|
return readFileSync5(file, "utf-8");
|
|
22622
22654
|
} catch (err) {
|
|
@@ -22629,7 +22661,7 @@ function readOverlayFile(agentName, file, agentCfg, warnings) {
|
|
|
22629
22661
|
reason: `read error: ${err.message}`,
|
|
22630
22662
|
code: code ?? "EUNKNOWN"
|
|
22631
22663
|
};
|
|
22632
|
-
recordReadFailure(agentCfg, { file, code: w.code });
|
|
22664
|
+
recordReadFailure(agentCfg, { file, code: w.code, source });
|
|
22633
22665
|
warnings.push(w);
|
|
22634
22666
|
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
|
|
22635
22667
|
return;
|
|
@@ -22693,14 +22725,14 @@ function applyAgentOverlays(config) {
|
|
|
22693
22725
|
reason: `read error: cannot list overlay directory (${code})`,
|
|
22694
22726
|
code
|
|
22695
22727
|
};
|
|
22696
|
-
recordReadFailure(agentCfg, { file: scheduleDir, code });
|
|
22728
|
+
recordReadFailure(agentCfg, { file: scheduleDir, code, source: "schedule" });
|
|
22697
22729
|
warnings.push(w);
|
|
22698
22730
|
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${scheduleDir}': ${w.reason}`);
|
|
22699
22731
|
});
|
|
22700
22732
|
if (files.length > 0) {
|
|
22701
22733
|
const merged = [...agentCfg.schedule ?? []];
|
|
22702
22734
|
for (const file of files) {
|
|
22703
|
-
const raw = readOverlayFile(agentName, file, agentCfg, warnings);
|
|
22735
|
+
const raw = readOverlayFile(agentName, file, agentCfg, warnings, "schedule");
|
|
22704
22736
|
if (raw === undefined)
|
|
22705
22737
|
continue;
|
|
22706
22738
|
try {
|
|
@@ -22745,7 +22777,7 @@ function applyAgentOverlays(config) {
|
|
|
22745
22777
|
reason: `read error: cannot list overlay directory (${code})`,
|
|
22746
22778
|
code
|
|
22747
22779
|
};
|
|
22748
|
-
recordReadFailure(agentCfg, { file: skillsDir, code });
|
|
22780
|
+
recordReadFailure(agentCfg, { file: skillsDir, code, source: "skills" });
|
|
22749
22781
|
warnings.push(w);
|
|
22750
22782
|
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${skillsDir}': ${w.reason}`);
|
|
22751
22783
|
});
|
|
@@ -22753,7 +22785,7 @@ function applyAgentOverlays(config) {
|
|
|
22753
22785
|
const merged = [...agentCfg.skills ?? []];
|
|
22754
22786
|
const seen = new Set(merged);
|
|
22755
22787
|
for (const file of skillFiles) {
|
|
22756
|
-
const raw = readOverlayFile(agentName, file, agentCfg, warnings);
|
|
22788
|
+
const raw = readOverlayFile(agentName, file, agentCfg, warnings, "skills");
|
|
22757
22789
|
if (raw === undefined)
|
|
22758
22790
|
continue;
|
|
22759
22791
|
try {
|
|
@@ -39617,6 +39649,22 @@ function redactAuthCodeMessage(api, chatId, messageId, log) {
|
|
|
39617
39649
|
|
|
39618
39650
|
// ask-user.ts
|
|
39619
39651
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
39652
|
+
|
|
39653
|
+
// gateway/source-message-id.ts
|
|
39654
|
+
var MAX_TELEGRAM_MESSAGE_ID = 2 ** 31;
|
|
39655
|
+
function parseSourceMessageId(raw) {
|
|
39656
|
+
if (raw == null)
|
|
39657
|
+
return null;
|
|
39658
|
+
const s = String(raw);
|
|
39659
|
+
if (!/^\d+$/.test(s))
|
|
39660
|
+
return null;
|
|
39661
|
+
const n = Number(s);
|
|
39662
|
+
if (!Number.isSafeInteger(n) || n <= 0 || n >= MAX_TELEGRAM_MESSAGE_ID)
|
|
39663
|
+
return null;
|
|
39664
|
+
return n;
|
|
39665
|
+
}
|
|
39666
|
+
|
|
39667
|
+
// ask-user.ts
|
|
39620
39668
|
var ASK_USER_DEFAULT_TIMEOUT_MS = 300000;
|
|
39621
39669
|
var ASK_USER_MAX_TIMEOUT_MS = 1800000;
|
|
39622
39670
|
var ASK_USER_MIN_TIMEOUT_MS = 5000;
|
|
@@ -39653,13 +39701,7 @@ function validateAskUserArgs(args) {
|
|
|
39653
39701
|
throw new Error("ask_user: message_thread_id must be a positive integer string");
|
|
39654
39702
|
}
|
|
39655
39703
|
}
|
|
39656
|
-
|
|
39657
|
-
if (args.reply_to != null) {
|
|
39658
|
-
replyTo = Number(args.reply_to);
|
|
39659
|
-
if (!Number.isFinite(replyTo) || replyTo <= 0) {
|
|
39660
|
-
throw new Error("ask_user: reply_to must be a positive integer string");
|
|
39661
|
-
}
|
|
39662
|
-
}
|
|
39704
|
+
const replyTo = parseSourceMessageId(args.reply_to) ?? undefined;
|
|
39663
39705
|
let timeoutMs = args.timeout_ms ?? ASK_USER_DEFAULT_TIMEOUT_MS;
|
|
39664
39706
|
if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs)) {
|
|
39665
39707
|
throw new Error("ask_user: timeout_ms must be a number");
|
|
@@ -39739,6 +39781,7 @@ function applyChecklistPatch(cl, patch) {
|
|
|
39739
39781
|
return { title: patch.title ?? cl.title, tasks };
|
|
39740
39782
|
}
|
|
39741
39783
|
function buildNativeChecklistPayload(p) {
|
|
39784
|
+
const replyAnchor = parseSourceMessageId(p.replyToMessageId);
|
|
39742
39785
|
return {
|
|
39743
39786
|
business_connection_id: p.businessConnectionId,
|
|
39744
39787
|
chat_id: p.chatId,
|
|
@@ -39746,7 +39789,7 @@ function buildNativeChecklistPayload(p) {
|
|
|
39746
39789
|
title: p.title,
|
|
39747
39790
|
tasks: p.tasks.map((t) => ({ id: t.id, text: t.text }))
|
|
39748
39791
|
},
|
|
39749
|
-
...
|
|
39792
|
+
...replyAnchor != null ? { reply_parameters: { message_id: replyAnchor } } : {},
|
|
39750
39793
|
...p.protectContent === true ? { protect_content: true } : {}
|
|
39751
39794
|
};
|
|
39752
39795
|
}
|
|
@@ -40030,13 +40073,7 @@ function resolveStickerSendArgs(raw, aliasMap) {
|
|
|
40030
40073
|
throw new Error("send_sticker: message_thread_id must be a positive integer string");
|
|
40031
40074
|
}
|
|
40032
40075
|
}
|
|
40033
|
-
|
|
40034
|
-
if (raw.reply_to != null) {
|
|
40035
|
-
replyTo = Number(raw.reply_to);
|
|
40036
|
-
if (!Number.isFinite(replyTo) || replyTo <= 0) {
|
|
40037
|
-
throw new Error("send_sticker: reply_to must be a positive integer string");
|
|
40038
|
-
}
|
|
40039
|
-
}
|
|
40076
|
+
const replyTo = parseSourceMessageId(raw.reply_to) ?? undefined;
|
|
40040
40077
|
return {
|
|
40041
40078
|
chatId: raw.chat_id,
|
|
40042
40079
|
fileId,
|
|
@@ -40088,13 +40125,7 @@ function resolveGifSendArgs(raw) {
|
|
|
40088
40125
|
throw new Error("send_gif: message_thread_id must be a positive integer string");
|
|
40089
40126
|
}
|
|
40090
40127
|
}
|
|
40091
|
-
|
|
40092
|
-
if (raw.reply_to != null) {
|
|
40093
|
-
replyTo = Number(raw.reply_to);
|
|
40094
|
-
if (!Number.isFinite(replyTo) || replyTo <= 0) {
|
|
40095
|
-
throw new Error("send_gif: reply_to must be a positive integer string");
|
|
40096
|
-
}
|
|
40097
|
-
}
|
|
40128
|
+
const replyTo = parseSourceMessageId(raw.reply_to) ?? undefined;
|
|
40098
40129
|
return {
|
|
40099
40130
|
chatId: raw.chat_id,
|
|
40100
40131
|
animationRef,
|
|
@@ -50208,6 +50239,20 @@ class PinRightsCache2 {
|
|
|
50208
50239
|
}
|
|
50209
50240
|
}
|
|
50210
50241
|
|
|
50242
|
+
// gateway/source-message-id.ts
|
|
50243
|
+
var MAX_TELEGRAM_MESSAGE_ID2 = 2 ** 31;
|
|
50244
|
+
function parseSourceMessageId2(raw) {
|
|
50245
|
+
if (raw == null)
|
|
50246
|
+
return null;
|
|
50247
|
+
const s = String(raw);
|
|
50248
|
+
if (!/^\d+$/.test(s))
|
|
50249
|
+
return null;
|
|
50250
|
+
const n = Number(s);
|
|
50251
|
+
if (!Number.isSafeInteger(n) || n <= 0 || n >= MAX_TELEGRAM_MESSAGE_ID2)
|
|
50252
|
+
return null;
|
|
50253
|
+
return n;
|
|
50254
|
+
}
|
|
50255
|
+
|
|
50211
50256
|
// gateway/permission-timeout.ts
|
|
50212
50257
|
var SIGNATURE_SEP = String.fromCharCode(0);
|
|
50213
50258
|
function permissionSignature(toolName, inputPreview) {
|
|
@@ -51221,7 +51266,7 @@ function createAuthBrokerClient() {
|
|
|
51221
51266
|
listState: () => broker.listState(),
|
|
51222
51267
|
setActive: (label) => broker.setActive(label),
|
|
51223
51268
|
markExhausted: (until) => broker.markExhausted(until),
|
|
51224
|
-
markThrottled: (until) => broker.markThrottled(until),
|
|
51269
|
+
markThrottled: (until, probeOnly) => broker.markThrottled(until, probeOnly),
|
|
51225
51270
|
rmAccount: (label) => broker.rmAccount(label),
|
|
51226
51271
|
refreshAccount: (label) => broker.refreshAccount(label),
|
|
51227
51272
|
setOverride: (agent, account) => broker.setOverride(agent, account),
|
|
@@ -73979,7 +74024,7 @@ function createAuthBrokerClient2() {
|
|
|
73979
74024
|
listState: () => broker.listState(),
|
|
73980
74025
|
setActive: (label) => broker.setActive(label),
|
|
73981
74026
|
markExhausted: (until) => broker.markExhausted(until),
|
|
73982
|
-
markThrottled: (until) => broker.markThrottled(until),
|
|
74027
|
+
markThrottled: (until, probeOnly) => broker.markThrottled(until, probeOnly),
|
|
73983
74028
|
rmAccount: (label) => broker.rmAccount(label),
|
|
73984
74029
|
refreshAccount: (label) => broker.refreshAccount(label),
|
|
73985
74030
|
setOverride: (agent, account) => broker.setOverride(agent, account),
|
|
@@ -74398,12 +74443,13 @@ class AuthBrokerClient2 {
|
|
|
74398
74443
|
const data = await this.send(req);
|
|
74399
74444
|
return data;
|
|
74400
74445
|
}
|
|
74401
|
-
async markThrottled(until) {
|
|
74446
|
+
async markThrottled(until, probeOnly = false) {
|
|
74402
74447
|
const data = await this.send({
|
|
74403
74448
|
v: PROTOCOL_VERSION,
|
|
74404
74449
|
id: randomUUID5(),
|
|
74405
74450
|
op: "mark-throttled",
|
|
74406
|
-
until
|
|
74451
|
+
until,
|
|
74452
|
+
...probeOnly ? { probeOnly: true } : {}
|
|
74407
74453
|
});
|
|
74408
74454
|
return data;
|
|
74409
74455
|
}
|
|
@@ -77103,6 +77149,15 @@ function classify429Detail2(text4) {
|
|
|
77103
77149
|
return "litellm-local";
|
|
77104
77150
|
return "generic-transient";
|
|
77105
77151
|
}
|
|
77152
|
+
function classification429WarrantsCorroboration(classification) {
|
|
77153
|
+
return classification === "generic-transient";
|
|
77154
|
+
}
|
|
77155
|
+
function routeRateLimit429(classification, runner, agent) {
|
|
77156
|
+
if (!classification429WarrantsCorroboration(classification))
|
|
77157
|
+
return false;
|
|
77158
|
+
runner.fireProbeOnly(agent);
|
|
77159
|
+
return true;
|
|
77160
|
+
}
|
|
77106
77161
|
function build429ClassifiedMetric(opts) {
|
|
77107
77162
|
const detail = typeof opts.detail === "string" ? opts.detail : "";
|
|
77108
77163
|
const litellm = parseLitellmLimitDetail(detail, new Date(opts.now));
|
|
@@ -77199,6 +77254,12 @@ function createThrottleTierRunner(deps) {
|
|
|
77199
77254
|
deps.log(`[throttle-tier] resume suppressed (${verdict}) reason=${reason}`);
|
|
77200
77255
|
}
|
|
77201
77256
|
}
|
|
77257
|
+
async function announceEscalation(client3, account, rolledTo, triggerAgent, armedAtMs) {
|
|
77258
|
+
deps.log(`[throttle-tier] escalated to wall account=${account ?? "?"} ` + `rolledTo=${rolledTo ?? "none (all blocked)"}`);
|
|
77259
|
+
await broadcastDeduped(client3, "throttle-escalation", account, renderThrottleEscalationNotice({ account, agent: triggerAgent, rolledTo }));
|
|
77260
|
+
if (rolledTo)
|
|
77261
|
+
nudgeResume("throttle-escalation-resume", armedAtMs);
|
|
77262
|
+
}
|
|
77202
77263
|
async function fire(triggerAgent, throttledUntilMs, resetParsed) {
|
|
77203
77264
|
const armedAtMs = now();
|
|
77204
77265
|
let client3 = null;
|
|
@@ -77219,10 +77280,7 @@ function createThrottleTierRunner(deps) {
|
|
|
77219
77280
|
deps.log(`[throttle-tier] markThrottled failed agent=${triggerAgent}: ${err?.message ?? err}`);
|
|
77220
77281
|
}
|
|
77221
77282
|
if (escalated) {
|
|
77222
|
-
|
|
77223
|
-
await broadcastDeduped(client3, "throttle-escalation", account, renderThrottleEscalationNotice({ account, agent: triggerAgent, rolledTo }));
|
|
77224
|
-
if (rolledTo)
|
|
77225
|
-
nudgeResume("throttle-escalation-resume", armedAtMs);
|
|
77283
|
+
await announceEscalation(client3, account, rolledTo, triggerAgent, armedAtMs);
|
|
77226
77284
|
return;
|
|
77227
77285
|
}
|
|
77228
77286
|
const cooldownKey = account ?? `agent:${triggerAgent}`;
|
|
@@ -77247,8 +77305,34 @@ function createThrottleTierRunner(deps) {
|
|
|
77247
77305
|
nudgeResume("throttle-retry-resume", armedAtMs);
|
|
77248
77306
|
}, delayMs);
|
|
77249
77307
|
}
|
|
77308
|
+
async function fireProbeOnly(triggerAgent) {
|
|
77309
|
+
const armedAtMs = now();
|
|
77310
|
+
let client3 = null;
|
|
77311
|
+
let account = null;
|
|
77312
|
+
let escalated = false;
|
|
77313
|
+
let rolledTo = null;
|
|
77314
|
+
try {
|
|
77315
|
+
client3 = await deps.getBrokerClient();
|
|
77316
|
+
if (client3) {
|
|
77317
|
+
const r = await client3.markThrottled(now() + 1, true);
|
|
77318
|
+
account = r.account;
|
|
77319
|
+
escalated = r.escalated;
|
|
77320
|
+
rolledTo = r.rolledTo ?? null;
|
|
77321
|
+
} else {
|
|
77322
|
+
deps.log(`[throttle-tier] broker unreachable \u2014 probe-only skipped agent=${triggerAgent}`);
|
|
77323
|
+
}
|
|
77324
|
+
} catch (err) {
|
|
77325
|
+
deps.log(`[throttle-tier] probe-only markThrottled failed agent=${triggerAgent}: ${err?.message ?? err}`);
|
|
77326
|
+
}
|
|
77327
|
+
if (escalated) {
|
|
77328
|
+
await announceEscalation(client3, account, rolledTo, triggerAgent, armedAtMs);
|
|
77329
|
+
return;
|
|
77330
|
+
}
|
|
77331
|
+
deps.log(`[throttle-tier] generic-transient probe-only inert (no wall) agent=${triggerAgent}`);
|
|
77332
|
+
}
|
|
77250
77333
|
return {
|
|
77251
77334
|
fire,
|
|
77335
|
+
fireProbeOnly,
|
|
77252
77336
|
inspect: () => ({ noticeState, nudgePending: pendingNudge != null })
|
|
77253
77337
|
};
|
|
77254
77338
|
}
|
|
@@ -79457,6 +79541,26 @@ function stampsHandbackMarker(source) {
|
|
|
79457
79541
|
return known.decoupledCompletion;
|
|
79458
79542
|
}
|
|
79459
79543
|
var HANDBACK_RECENCY_WINDOW_MS = 60000;
|
|
79544
|
+
var TASK_NOTIFICATION_DEDUP_TTL_MS = 30000;
|
|
79545
|
+
var NOTIF_TERMINAL_STATUSES = new Set(["completed", "failed", "killed"]);
|
|
79546
|
+
|
|
79547
|
+
class CliTaskNotificationLedger {
|
|
79548
|
+
seen = new Map;
|
|
79549
|
+
record(taskId, status, now) {
|
|
79550
|
+
if (taskId.length === 0 || !NOTIF_TERMINAL_STATUSES.has(status))
|
|
79551
|
+
return;
|
|
79552
|
+
this.seen.set(taskId, now);
|
|
79553
|
+
for (const [id, ts] of this.seen) {
|
|
79554
|
+
if (now - ts > TASK_NOTIFICATION_DEDUP_TTL_MS)
|
|
79555
|
+
this.seen.delete(id);
|
|
79556
|
+
}
|
|
79557
|
+
}
|
|
79558
|
+
seenRecently(taskId, now) {
|
|
79559
|
+
const ts = this.seen.get(taskId);
|
|
79560
|
+
return ts != null && now - ts <= TASK_NOTIFICATION_DEDUP_TTL_MS;
|
|
79561
|
+
}
|
|
79562
|
+
}
|
|
79563
|
+
var cliTaskNotifLedger = new CliTaskNotificationLedger;
|
|
79460
79564
|
var MAIN_THREAD_KEY = "<main>";
|
|
79461
79565
|
|
|
79462
79566
|
class SubagentHandbackMarker {
|
|
@@ -80205,7 +80309,7 @@ async function sendReply(deps, req) {
|
|
|
80205
80309
|
}
|
|
80206
80310
|
const files = args.files ?? [];
|
|
80207
80311
|
const quoteOptIn = args.quote !== false;
|
|
80208
|
-
let reply_to =
|
|
80312
|
+
let reply_to = parseSourceMessageId(args.reply_to) ?? undefined;
|
|
80209
80313
|
const protectContent = args.protect_content === true;
|
|
80210
80314
|
const quoteText = args.quote_text;
|
|
80211
80315
|
const access = loadAccess();
|
|
@@ -82011,20 +82115,6 @@ function formatEventDetail(event) {
|
|
|
82011
82115
|
}
|
|
82012
82116
|
}
|
|
82013
82117
|
|
|
82014
|
-
// gateway/source-message-id.ts
|
|
82015
|
-
var MAX_TELEGRAM_MESSAGE_ID = 2 ** 31;
|
|
82016
|
-
function parseSourceMessageId(raw) {
|
|
82017
|
-
if (raw == null)
|
|
82018
|
-
return null;
|
|
82019
|
-
const s = String(raw);
|
|
82020
|
-
if (!/^\d+$/.test(s))
|
|
82021
|
-
return null;
|
|
82022
|
-
const n = Number(s);
|
|
82023
|
-
if (!Number.isSafeInteger(n) || n <= 0 || n >= MAX_TELEGRAM_MESSAGE_ID)
|
|
82024
|
-
return null;
|
|
82025
|
-
return n;
|
|
82026
|
-
}
|
|
82027
|
-
|
|
82028
82118
|
// gateway/status-surface-log.ts
|
|
82029
82119
|
function formatTurnLifecycle(action, reason, t, now) {
|
|
82030
82120
|
const ageMs = action === "clear" ? Math.max(0, now - t.startedAt) : 0;
|
|
@@ -84230,6 +84320,26 @@ function isTurnFlushSafetyEnabled(env = process.env) {
|
|
|
84230
84320
|
}
|
|
84231
84321
|
|
|
84232
84322
|
// gateway/subagent-handback-marker.ts
|
|
84323
|
+
var TASK_NOTIFICATION_DEDUP_TTL_MS2 = 30000;
|
|
84324
|
+
var NOTIF_TERMINAL_STATUSES2 = new Set(["completed", "failed", "killed"]);
|
|
84325
|
+
|
|
84326
|
+
class CliTaskNotificationLedger2 {
|
|
84327
|
+
seen = new Map;
|
|
84328
|
+
record(taskId, status, now) {
|
|
84329
|
+
if (taskId.length === 0 || !NOTIF_TERMINAL_STATUSES2.has(status))
|
|
84330
|
+
return;
|
|
84331
|
+
this.seen.set(taskId, now);
|
|
84332
|
+
for (const [id, ts] of this.seen) {
|
|
84333
|
+
if (now - ts > TASK_NOTIFICATION_DEDUP_TTL_MS2)
|
|
84334
|
+
this.seen.delete(id);
|
|
84335
|
+
}
|
|
84336
|
+
}
|
|
84337
|
+
seenRecently(taskId, now) {
|
|
84338
|
+
const ts = this.seen.get(taskId);
|
|
84339
|
+
return ts != null && now - ts <= TASK_NOTIFICATION_DEDUP_TTL_MS2;
|
|
84340
|
+
}
|
|
84341
|
+
}
|
|
84342
|
+
var cliTaskNotifLedger2 = new CliTaskNotificationLedger2;
|
|
84233
84343
|
var MAIN_THREAD_KEY2 = "<main>";
|
|
84234
84344
|
|
|
84235
84345
|
class SubagentHandbackMarker2 {
|
|
@@ -97011,6 +97121,9 @@ function decideSubagentHandback(input) {
|
|
|
97011
97121
|
if (!input.isBackground) {
|
|
97012
97122
|
return { deliver: false, reason: "foreground" };
|
|
97013
97123
|
}
|
|
97124
|
+
if (input.cliTaskNotificationSeen === true) {
|
|
97125
|
+
return { deliver: false, reason: "cli-task-notification" };
|
|
97126
|
+
}
|
|
97014
97127
|
const chatId = input.fleetChatId || input.ownerChatId;
|
|
97015
97128
|
if (!chatId) {
|
|
97016
97129
|
return { deliver: false, reason: "no-chat" };
|
|
@@ -102693,10 +102806,10 @@ function startOutboxSweep(deps) {
|
|
|
102693
102806
|
}
|
|
102694
102807
|
|
|
102695
102808
|
// ../src/build-info.ts
|
|
102696
|
-
var VERSION2 = "0.20.
|
|
102697
|
-
var COMMIT_SHA = "
|
|
102698
|
-
var COMMIT_DATE = "2026-08-
|
|
102699
|
-
var LATEST_PR =
|
|
102809
|
+
var VERSION2 = "0.20.9";
|
|
102810
|
+
var COMMIT_SHA = "63c4c44b";
|
|
102811
|
+
var COMMIT_DATE = "2026-08-05T03:51:28Z";
|
|
102812
|
+
var LATEST_PR = 4388;
|
|
102700
102813
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
102701
102814
|
|
|
102702
102815
|
// gateway/boot-version.ts
|
|
@@ -107873,6 +107986,7 @@ function emitGatewayOperatorEvent(event) {
|
|
|
107873
107986
|
agent,
|
|
107874
107987
|
shouldEmitCard: (a) => shouldEmitOperatorEvent(a, "rate-limited")
|
|
107875
107988
|
});
|
|
107989
|
+
routeRateLimit429(rateLimit429Classification, throttleTierRunner, agent);
|
|
107876
107990
|
if (surface === "litellm-local-notice") {
|
|
107877
107991
|
process.stderr.write(`telegram gateway: 429 classified litellm-proxy-local agent=${agent} \u2014 ` + `calm path, no account attribution, no failover
|
|
107878
107992
|
`);
|
|
@@ -109150,6 +109264,8 @@ if (isGatewayMain)
|
|
|
109150
109264
|
}
|
|
109151
109265
|
}
|
|
109152
109266
|
const ev = msg.event;
|
|
109267
|
+
if (ev.kind === "task_notification")
|
|
109268
|
+
cliTaskNotifLedger2.record(ev.taskId, ev.status, Date.now());
|
|
109153
109269
|
handleSessionEvent2(ev);
|
|
109154
109270
|
toolFlightTracker.onEvent(ev);
|
|
109155
109271
|
if (pendingDeferredInterrupt != null && !toolFlightTracker.isMidToolCall()) {
|
|
@@ -109867,7 +109983,7 @@ async function executeSendChecklist(args) {
|
|
|
109867
109983
|
if (!Array.isArray(tasks) || tasks.length === 0)
|
|
109868
109984
|
throw new Error("send_checklist: tasks must be a non-empty array");
|
|
109869
109985
|
const threadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
109870
|
-
const replyTo =
|
|
109986
|
+
const replyTo = parseSourceMessageId2(args.reply_to) ?? undefined;
|
|
109871
109987
|
const protectContent = args.protect_content === true;
|
|
109872
109988
|
assertAllowedChat(chat_id);
|
|
109873
109989
|
const { title: redactedTitle, tasks: redactedTasks } = redactChecklistFields(title, tasks, (t) => redactOutboundText(t, "send_checklist"));
|
|
@@ -116715,11 +116831,12 @@ async function startGateway() {
|
|
|
116715
116831
|
ownerChatId: hbOwnerDm,
|
|
116716
116832
|
taskDescription: description2,
|
|
116717
116833
|
resultText,
|
|
116718
|
-
jsonlAgentId: agentId
|
|
116834
|
+
jsonlAgentId: agentId,
|
|
116835
|
+
cliTaskNotificationSeen: cliTaskNotifLedger2.seenRecently(agentId, Date.now())
|
|
116719
116836
|
});
|
|
116720
116837
|
if (!decision.deliver) {
|
|
116721
|
-
if (decision.reason === "no-chat") {
|
|
116722
|
-
process.stderr.write(`telegram gateway: subagent-handback ${agentId} \u2014 no chat to deliver to
|
|
116838
|
+
if (decision.reason === "no-chat" || decision.reason === "cli-task-notification") {
|
|
116839
|
+
process.stderr.write(`telegram gateway: subagent-handback ${agentId} skipped \u2014 ${decision.reason === "no-chat" ? "no chat to deliver to" : "CLI task-notification already woke the parent for this completion (double-wake dedup)"}
|
|
116723
116840
|
`);
|
|
116724
116841
|
}
|
|
116725
116842
|
return;
|
|
@@ -28,7 +28,7 @@ export function createAuthBrokerClient(): {
|
|
|
28
28
|
listState: () => broker.listState(),
|
|
29
29
|
setActive: (label: string) => broker.setActive(label),
|
|
30
30
|
markExhausted: (until?: number) => broker.markExhausted(until),
|
|
31
|
-
markThrottled: (until: number) => broker.markThrottled(until),
|
|
31
|
+
markThrottled: (until: number, probeOnly?: boolean) => broker.markThrottled(until, probeOnly),
|
|
32
32
|
rmAccount: (label: string) => broker.rmAccount(label),
|
|
33
33
|
refreshAccount: (label: string) => broker.refreshAccount(label),
|
|
34
34
|
setOverride: (agent: string, account: string | null) =>
|
|
@@ -334,10 +334,12 @@ export interface AuthBrokerClient {
|
|
|
334
334
|
* per-account rate limit on the CALLER's own account — `throttled_until`
|
|
335
335
|
* in the quota ledger — WITHOUT rolling the fleet and WITHOUT touching
|
|
336
336
|
* eligibility. `escalated` is true when the broker's escalation guard
|
|
337
|
-
* (
|
|
337
|
+
* (a first-hit live probe corroborating a wall) converted it into the
|
|
338
338
|
* standard mark-exhausted + roll; `rolledTo` names the roll target then.
|
|
339
|
+
* `probeOnly` (#failover-429-corroborate, generic-transient origin): run ONLY
|
|
340
|
+
* the escalation probe — a healthy probe records nothing (account stays inert).
|
|
339
341
|
*/
|
|
340
|
-
markThrottled(until: number): Promise<{
|
|
342
|
+
markThrottled(until: number, probeOnly?: boolean): Promise<{
|
|
341
343
|
account: string
|
|
342
344
|
throttled_until: number
|
|
343
345
|
escalated: boolean
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
* (same pattern as checklist-message-handler.ts).
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
+
import { parseSourceMessageId } from './source-message-id.js'
|
|
37
|
+
|
|
36
38
|
export interface ChecklistTaskState {
|
|
37
39
|
/** 1-based sequential id assigned at send time (mirrors the native API's
|
|
38
40
|
* required per-task integer id; doubles as the patch handle in text mode). */
|
|
@@ -130,6 +132,11 @@ export function buildNativeChecklistPayload(p: {
|
|
|
130
132
|
replyToMessageId?: number
|
|
131
133
|
protectContent?: boolean
|
|
132
134
|
}): Record<string, unknown> {
|
|
135
|
+
// #4368 — defense at the payload boundary: a fabricated / out-of-int32
|
|
136
|
+
// reply anchor is dropped here so the native checklist sends UNANCHORED
|
|
137
|
+
// rather than Telegram 400ing on `reply_parameters.message_id`. Independent
|
|
138
|
+
// of any caller-side guard so this builder can never emit a bad anchor.
|
|
139
|
+
const replyAnchor = parseSourceMessageId(p.replyToMessageId)
|
|
133
140
|
return {
|
|
134
141
|
business_connection_id: p.businessConnectionId,
|
|
135
142
|
chat_id: p.chatId,
|
|
@@ -137,7 +144,7 @@ export function buildNativeChecklistPayload(p: {
|
|
|
137
144
|
title: p.title,
|
|
138
145
|
tasks: p.tasks.map((t) => ({ id: t.id, text: t.text })),
|
|
139
146
|
},
|
|
140
|
-
...(
|
|
147
|
+
...(replyAnchor != null ? { reply_parameters: { message_id: replyAnchor } } : {}),
|
|
141
148
|
...(p.protectContent === true ? { protect_content: true } : {}),
|
|
142
149
|
}
|
|
143
150
|
}
|
|
@@ -427,6 +427,7 @@ import {
|
|
|
427
427
|
build429ClassifiedMetric,
|
|
428
428
|
classify429Detail,
|
|
429
429
|
decideThrottleTier,
|
|
430
|
+
routeRateLimit429,
|
|
430
431
|
throttleRetryInPlaceMaxMs,
|
|
431
432
|
} from '../throttle-tier.js'
|
|
432
433
|
import { createThrottleTierRunner } from './throttle-tier-wiring.js'
|
|
@@ -497,7 +498,7 @@ import {
|
|
|
497
498
|
type ReplyOwnerTier, type ReplyOwnerCandidates,
|
|
498
499
|
type AnswerDeliveredLatch,
|
|
499
500
|
} from '../reply-owner-resolve.js'
|
|
500
|
-
import { SubagentHandbackMarker } from './subagent-handback-marker.js'
|
|
501
|
+
import { SubagentHandbackMarker, cliTaskNotifLedger } from './subagent-handback-marker.js'
|
|
501
502
|
// PR A — deterministic answer-ready quiescence flush (late-delivery fix).
|
|
502
503
|
import { AnswerReadyFlushController, resolveAnswerReadyFlushMs, resolveAnswerStageMs } from '../answer-ready-flush.js'
|
|
503
504
|
// #1667 — pure decision core for the turn_end answer-delivery gate (#1664).
|
|
@@ -7827,6 +7828,7 @@ function emitGatewayOperatorEvent(event: OperatorEvent): void {
|
|
|
7827
7828
|
agent,
|
|
7828
7829
|
shouldEmitCard: (a) => shouldEmitOperatorEvent(a, 'rate-limited'),
|
|
7829
7830
|
})
|
|
7831
|
+
routeRateLimit429(rateLimit429Classification, throttleTierRunner, agent) // #failover-429-corroborate probe-only seam; boolean return is for test observability (throttle-tier-route-429.test.ts), intentionally discarded here — see docstring
|
|
7830
7832
|
if (surface === 'litellm-local-notice') {
|
|
7831
7833
|
process.stderr.write(
|
|
7832
7834
|
`telegram gateway: 429 classified litellm-proxy-local agent=${agent} — ` +
|
|
@@ -10690,6 +10692,7 @@ if (isGatewayMain) ipcServer = createIpcServer({
|
|
|
10690
10692
|
}
|
|
10691
10693
|
}
|
|
10692
10694
|
const ev = msg.event as unknown as SessionEvent
|
|
10695
|
+
if (ev.kind === 'task_notification') cliTaskNotifLedger.record(ev.taskId, ev.status, Date.now()) // double-wake dedup — ledger doc in subagent-handback-marker.ts
|
|
10693
10696
|
// #1122/#1126: session events used to be ingested into the pinned progress
|
|
10694
10697
|
// card here (`progressDriver.ingest`). The card is retired and the driver
|
|
10695
10698
|
// is permanently null, so that call was a dead no-op — removed. Session
|
|
@@ -11917,7 +11920,7 @@ async function executeSendChecklist(args: Record<string, unknown>): Promise<{ co
|
|
|
11917
11920
|
const tasks = args.tasks as Array<{ text: string; done?: boolean }> | undefined
|
|
11918
11921
|
if (!Array.isArray(tasks) || tasks.length === 0) throw new Error('send_checklist: tasks must be a non-empty array')
|
|
11919
11922
|
const threadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined
|
|
11920
|
-
const replyTo = args.reply_to
|
|
11923
|
+
const replyTo = parseSourceMessageId(args.reply_to as string | number | null | undefined) ?? undefined // #4368: drop a fabricated/out-of-int32 anchor so the send lands unanchored, not 400
|
|
11921
11924
|
const protectContent = args.protect_content === true
|
|
11922
11925
|
|
|
11923
11926
|
assertAllowedChat(chat_id)
|
|
@@ -24370,11 +24373,12 @@ async function startGateway(): Promise<void> { // #2996 P0c: the boot IIFE, now
|
|
|
24370
24373
|
// deterministic dedup key — closes the #1719
|
|
24371
24374
|
// re-fire-on-restart class.
|
|
24372
24375
|
jsonlAgentId: agentId,
|
|
24376
|
+
cliTaskNotificationSeen: cliTaskNotifLedger.seenRecently(agentId, Date.now()), // double-wake dedup, fail-open — ledger doc in subagent-handback-marker.ts
|
|
24373
24377
|
})
|
|
24374
24378
|
if (!decision.deliver) {
|
|
24375
|
-
if (decision.reason === 'no-chat') {
|
|
24379
|
+
if (decision.reason === 'no-chat' || decision.reason === 'cli-task-notification') {
|
|
24376
24380
|
process.stderr.write(
|
|
24377
|
-
`telegram gateway: subagent-handback ${agentId} — no chat to deliver to
|
|
24381
|
+
`telegram gateway: subagent-handback ${agentId} skipped — ${decision.reason === 'no-chat' ? 'no chat to deliver to' : 'CLI task-notification already woke the parent for this completion (double-wake dedup)'}\n`,
|
|
24378
24382
|
)
|
|
24379
24383
|
}
|
|
24380
24384
|
return
|
|
@@ -73,6 +73,7 @@ import { getBuzzMirror } from './buzz-mirror.js'
|
|
|
73
73
|
import { isFinalAnswerReply, isSubstantiveFinalReply, shouldJournalReplySiteDelivery } from '../final-answer-detect.js'
|
|
74
74
|
import { decideOverPing, type OverPingDecision } from '../over-ping-safety-net.js'
|
|
75
75
|
import { decideSilentReplyAnchor } from '../silent-reply-anchor.js'
|
|
76
|
+
import { parseSourceMessageId } from './source-message-id.js'
|
|
76
77
|
import {
|
|
77
78
|
decideSupersedeCorrection,
|
|
78
79
|
flushedAnswerMatchesReply,
|
|
@@ -1343,7 +1344,14 @@ export async function sendReply(
|
|
|
1343
1344
|
|
|
1344
1345
|
const files = (args.files as string[] | undefined) ?? []
|
|
1345
1346
|
const quoteOptIn = args.quote !== false
|
|
1346
|
-
|
|
1347
|
+
// #4368 — the model's reply tool can quote a synthetic inbound (a boot-
|
|
1348
|
+
// resume/handback/cron fabricated id at `Date.now()` scale). Route it through
|
|
1349
|
+
// the canonical guard so an out-of-int32 anchor is DROPPED (send lands
|
|
1350
|
+
// unanchored) rather than 400ing every chunk on `reply_parameters.message_id`.
|
|
1351
|
+
// A later `reply_to = latest` (quote-opt-in default) is a Telegram-returned
|
|
1352
|
+
// id and needs no re-check. Also closes the pre-existing NaN hole: a non-
|
|
1353
|
+
// numeric `reply_to` used to coerce to NaN and still build the anchor.
|
|
1354
|
+
let reply_to = parseSourceMessageId(args.reply_to as string | number | null | undefined) ?? undefined
|
|
1347
1355
|
const protectContent = args.protect_content === true
|
|
1348
1356
|
const quoteText = args.quote_text as string | undefined
|
|
1349
1357
|
const access = loadAccess()
|