switchroom 0.20.8 → 0.20.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/bin/handoff-briefing.sh +57 -5
- package/bin/working-state-reload-hook.sh +262 -0
- package/dist/agent-scheduler/index.js +16 -13
- package/dist/auth-broker/index.js +70 -30
- 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 +42 -13
- 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/_base/start.sh.hbs +49 -0
- package/profiles/default/CLAUDE.md.hbs +12 -13
- package/telegram-plugin/ask-user.ts +6 -7
- package/telegram-plugin/dist/gateway/gateway.js +192 -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
- package/vendor/hindsight-memory/CHANGELOG.md +31 -0
- package/vendor/hindsight-memory/hooks/hooks.json +2 -1
- package/vendor/hindsight-memory/scripts/session_start.py +35 -8
- package/vendor/hindsight-memory/scripts/tests/test_session_start_durability.py +107 -0
|
@@ -7308,6 +7308,12 @@ function refillNormalizedUtils(q, now) {
|
|
|
7308
7308
|
sevenDayRefilled
|
|
7309
7309
|
};
|
|
7310
7310
|
}
|
|
7311
|
+
var ENTITLEMENT_BLOCKED_ERROR_CODES;
|
|
7312
|
+
var init_quota = __esm(() => {
|
|
7313
|
+
ENTITLEMENT_BLOCKED_ERROR_CODES = new Set([
|
|
7314
|
+
"oauth_not_allowed_for_organization"
|
|
7315
|
+
]);
|
|
7316
|
+
});
|
|
7311
7317
|
|
|
7312
7318
|
// ../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
7313
7319
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
@@ -11329,7 +11335,8 @@ var init_protocol = __esm(() => {
|
|
|
11329
11335
|
v: exports_external.literal(PROTOCOL_VERSION),
|
|
11330
11336
|
op: exports_external.literal("mark-throttled"),
|
|
11331
11337
|
id: exports_external.string().min(1),
|
|
11332
|
-
until: exports_external.number().int().positive()
|
|
11338
|
+
until: exports_external.number().int().positive(),
|
|
11339
|
+
probeOnly: exports_external.boolean().optional()
|
|
11333
11340
|
});
|
|
11334
11341
|
RefreshAccountRequestSchema = exports_external.object({
|
|
11335
11342
|
v: exports_external.literal(PROTOCOL_VERSION),
|
|
@@ -11725,12 +11732,13 @@ class AuthBrokerClient {
|
|
|
11725
11732
|
const data = await this.send(req);
|
|
11726
11733
|
return data;
|
|
11727
11734
|
}
|
|
11728
|
-
async markThrottled(until) {
|
|
11735
|
+
async markThrottled(until, probeOnly = false) {
|
|
11729
11736
|
const data = await this.send({
|
|
11730
11737
|
v: PROTOCOL_VERSION,
|
|
11731
11738
|
id: randomUUID2(),
|
|
11732
11739
|
op: "mark-throttled",
|
|
11733
|
-
until
|
|
11740
|
+
until,
|
|
11741
|
+
...probeOnly ? { probeOnly: true } : {}
|
|
11734
11742
|
});
|
|
11735
11743
|
return data;
|
|
11736
11744
|
}
|
|
@@ -12693,6 +12701,7 @@ function buildSnapshotsFromCachedState(state) {
|
|
|
12693
12701
|
}
|
|
12694
12702
|
var THROTTLING_THRESHOLD_PCT = 80, OVERAGE_EXHAUSTED_REASONS, HEALTH_EMOJI, TABLE_HEALTH_RANK;
|
|
12695
12703
|
var init_auth_snapshot_format = __esm(() => {
|
|
12704
|
+
init_quota();
|
|
12696
12705
|
init_client();
|
|
12697
12706
|
init_demo_mask();
|
|
12698
12707
|
init_card_format();
|
|
@@ -13480,12 +13489,27 @@ var init_dollar_math_guard = __esm(() => {
|
|
|
13480
13489
|
});
|
|
13481
13490
|
|
|
13482
13491
|
// render/emphasis-guard.ts
|
|
13492
|
+
function isLineLeadingBullet(text, starIndex) {
|
|
13493
|
+
const next = text[starIndex + 1];
|
|
13494
|
+
if (next !== " " && next !== "\t")
|
|
13495
|
+
return false;
|
|
13496
|
+
let i = starIndex - 1;
|
|
13497
|
+
let indent = 0;
|
|
13498
|
+
while (i >= 0 && (text[i] === " " || text[i] === "\t")) {
|
|
13499
|
+
if (++indent > 3)
|
|
13500
|
+
return false;
|
|
13501
|
+
i--;
|
|
13502
|
+
}
|
|
13503
|
+
return i < 0 || text[i] === `
|
|
13504
|
+
`;
|
|
13505
|
+
}
|
|
13483
13506
|
function guardAccidentalEmphasis(text) {
|
|
13484
13507
|
if (!text.includes("_") && !text.includes("*"))
|
|
13485
13508
|
return text;
|
|
13486
13509
|
const segments = splitProtectedSegments(text);
|
|
13487
13510
|
let hasIntraUnderscore = false;
|
|
13488
13511
|
let hasIntraAsterisk = false;
|
|
13512
|
+
let hasBoundaryAsterisk = false;
|
|
13489
13513
|
let underscoreCount = 0;
|
|
13490
13514
|
let asteriskCount = 0;
|
|
13491
13515
|
for (const seg of segments) {
|
|
@@ -13495,14 +13519,24 @@ function guardAccidentalEmphasis(text) {
|
|
|
13495
13519
|
hasIntraUnderscore = true;
|
|
13496
13520
|
if (INTRA_WORD_ASTERISK.test(seg.text))
|
|
13497
13521
|
hasIntraAsterisk = true;
|
|
13522
|
+
if (!hasBoundaryAsterisk) {
|
|
13523
|
+
for (const m of seg.text.matchAll(BOUNDARY_FLANKED_ASTERISK)) {
|
|
13524
|
+
if (!isLineLeadingBullet(seg.text, m.index ?? 0)) {
|
|
13525
|
+
hasBoundaryAsterisk = true;
|
|
13526
|
+
break;
|
|
13527
|
+
}
|
|
13528
|
+
}
|
|
13529
|
+
}
|
|
13498
13530
|
underscoreCount += seg.text.match(ANY_UNDERSCORE)?.length ?? 0;
|
|
13499
13531
|
asteriskCount += seg.text.match(ANY_ASTERISK)?.length ?? 0;
|
|
13500
13532
|
}
|
|
13501
13533
|
INTRA_WORD_UNDERSCORE.lastIndex = 0;
|
|
13502
13534
|
INTRA_WORD_ASTERISK.lastIndex = 0;
|
|
13535
|
+
BOUNDARY_FLANKED_ASTERISK.lastIndex = 0;
|
|
13503
13536
|
const armUnderscore = hasIntraUnderscore && underscoreCount >= 2;
|
|
13504
13537
|
const armAsterisk = hasIntraAsterisk && asteriskCount >= 2;
|
|
13505
|
-
|
|
13538
|
+
const armBoundaryAsterisk = hasBoundaryAsterisk;
|
|
13539
|
+
if (!armUnderscore && !armAsterisk && !armBoundaryAsterisk)
|
|
13506
13540
|
return text;
|
|
13507
13541
|
return segments.map((seg) => {
|
|
13508
13542
|
if (seg.code)
|
|
@@ -13512,13 +13546,17 @@ function guardAccidentalEmphasis(text) {
|
|
|
13512
13546
|
out = out.replace(INTRA_WORD_UNDERSCORE, "\\_");
|
|
13513
13547
|
if (armAsterisk)
|
|
13514
13548
|
out = out.replace(INTRA_WORD_ASTERISK, "\\*");
|
|
13549
|
+
if (armBoundaryAsterisk) {
|
|
13550
|
+
out = out.replace(BOUNDARY_FLANKED_ASTERISK, (m, offset, str) => isLineLeadingBullet(str, offset) ? m : "\\*");
|
|
13551
|
+
}
|
|
13515
13552
|
return out;
|
|
13516
13553
|
}).join("");
|
|
13517
13554
|
}
|
|
13518
|
-
var INTRA_WORD_UNDERSCORE, INTRA_WORD_ASTERISK, ANY_UNDERSCORE, ANY_ASTERISK;
|
|
13555
|
+
var INTRA_WORD_UNDERSCORE, INTRA_WORD_ASTERISK, BOUNDARY_FLANKED_ASTERISK, ANY_UNDERSCORE, ANY_ASTERISK;
|
|
13519
13556
|
var init_emphasis_guard = __esm(() => {
|
|
13520
13557
|
INTRA_WORD_UNDERSCORE = /(?<=[A-Za-z0-9])_(?=[A-Za-z0-9])/g;
|
|
13521
13558
|
INTRA_WORD_ASTERISK = /(?<=[A-Za-z0-9])\*(?=[A-Za-z0-9])/g;
|
|
13559
|
+
BOUNDARY_FLANKED_ASTERISK = /(?<=^|\s)\*(?=\s|$)/g;
|
|
13522
13560
|
ANY_UNDERSCORE = /(?<!\\)_/g;
|
|
13523
13561
|
ANY_ASTERISK = /(?<!\\)\*/g;
|
|
13524
13562
|
});
|
|
@@ -13569,7 +13607,7 @@ function guardAccidentalBlockConstructs(text) {
|
|
|
13569
13607
|
return out;
|
|
13570
13608
|
}
|
|
13571
13609
|
function escapeAccidentalHeadingLine(line) {
|
|
13572
|
-
return line.replace(ACCIDENTAL_HEADING, "$1\\$2");
|
|
13610
|
+
return line.replace(ACCIDENTAL_HEADING, "$1\\$2").replace(ACCIDENTAL_HEADING_AFTER_MARKER, "$1\\$2");
|
|
13573
13611
|
}
|
|
13574
13612
|
function guardAccidentalHeading(text) {
|
|
13575
13613
|
if (!text.includes("#"))
|
|
@@ -13599,11 +13637,12 @@ function guardAccidentalHeading(text) {
|
|
|
13599
13637
|
}
|
|
13600
13638
|
return out;
|
|
13601
13639
|
}
|
|
13602
|
-
var ACCIDENTAL_BLOCKQUOTE, ACCIDENTAL_ORDERED_LIST, ACCIDENTAL_HEADING;
|
|
13640
|
+
var ACCIDENTAL_BLOCKQUOTE, ACCIDENTAL_ORDERED_LIST, ACCIDENTAL_HEADING, ACCIDENTAL_HEADING_AFTER_MARKER;
|
|
13603
13641
|
var init_line_start_guard = __esm(() => {
|
|
13604
13642
|
ACCIDENTAL_BLOCKQUOTE = /^>[0-9=]/;
|
|
13605
13643
|
ACCIDENTAL_ORDERED_LIST = /^(\d{4,})([.)])(\s|$)/;
|
|
13606
13644
|
ACCIDENTAL_HEADING = /^([ \t]{0,3})(#{1,6})(?=[^\s#])/;
|
|
13645
|
+
ACCIDENTAL_HEADING_AFTER_MARKER = /^([ \t]{0,3}(?:(?:[-*+]|\d{1,3}[.)])[ \t]+|>[ \t]*)+)(#{1,6})(?=[^\s#])/;
|
|
13607
13646
|
});
|
|
13608
13647
|
|
|
13609
13648
|
// render/inline-pairs-guard.ts
|
|
@@ -22616,7 +22655,7 @@ function deriveOverlayTitle(raw, fileName) {
|
|
|
22616
22655
|
return;
|
|
22617
22656
|
return base.length > 0 ? base : undefined;
|
|
22618
22657
|
}
|
|
22619
|
-
function readOverlayFile(agentName, file, agentCfg, warnings) {
|
|
22658
|
+
function readOverlayFile(agentName, file, agentCfg, warnings, source) {
|
|
22620
22659
|
try {
|
|
22621
22660
|
return readFileSync5(file, "utf-8");
|
|
22622
22661
|
} catch (err) {
|
|
@@ -22629,7 +22668,7 @@ function readOverlayFile(agentName, file, agentCfg, warnings) {
|
|
|
22629
22668
|
reason: `read error: ${err.message}`,
|
|
22630
22669
|
code: code ?? "EUNKNOWN"
|
|
22631
22670
|
};
|
|
22632
|
-
recordReadFailure(agentCfg, { file, code: w.code });
|
|
22671
|
+
recordReadFailure(agentCfg, { file, code: w.code, source });
|
|
22633
22672
|
warnings.push(w);
|
|
22634
22673
|
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
|
|
22635
22674
|
return;
|
|
@@ -22693,14 +22732,14 @@ function applyAgentOverlays(config) {
|
|
|
22693
22732
|
reason: `read error: cannot list overlay directory (${code})`,
|
|
22694
22733
|
code
|
|
22695
22734
|
};
|
|
22696
|
-
recordReadFailure(agentCfg, { file: scheduleDir, code });
|
|
22735
|
+
recordReadFailure(agentCfg, { file: scheduleDir, code, source: "schedule" });
|
|
22697
22736
|
warnings.push(w);
|
|
22698
22737
|
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${scheduleDir}': ${w.reason}`);
|
|
22699
22738
|
});
|
|
22700
22739
|
if (files.length > 0) {
|
|
22701
22740
|
const merged = [...agentCfg.schedule ?? []];
|
|
22702
22741
|
for (const file of files) {
|
|
22703
|
-
const raw = readOverlayFile(agentName, file, agentCfg, warnings);
|
|
22742
|
+
const raw = readOverlayFile(agentName, file, agentCfg, warnings, "schedule");
|
|
22704
22743
|
if (raw === undefined)
|
|
22705
22744
|
continue;
|
|
22706
22745
|
try {
|
|
@@ -22745,7 +22784,7 @@ function applyAgentOverlays(config) {
|
|
|
22745
22784
|
reason: `read error: cannot list overlay directory (${code})`,
|
|
22746
22785
|
code
|
|
22747
22786
|
};
|
|
22748
|
-
recordReadFailure(agentCfg, { file: skillsDir, code });
|
|
22787
|
+
recordReadFailure(agentCfg, { file: skillsDir, code, source: "skills" });
|
|
22749
22788
|
warnings.push(w);
|
|
22750
22789
|
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${skillsDir}': ${w.reason}`);
|
|
22751
22790
|
});
|
|
@@ -22753,7 +22792,7 @@ function applyAgentOverlays(config) {
|
|
|
22753
22792
|
const merged = [...agentCfg.skills ?? []];
|
|
22754
22793
|
const seen = new Set(merged);
|
|
22755
22794
|
for (const file of skillFiles) {
|
|
22756
|
-
const raw = readOverlayFile(agentName, file, agentCfg, warnings);
|
|
22795
|
+
const raw = readOverlayFile(agentName, file, agentCfg, warnings, "skills");
|
|
22757
22796
|
if (raw === undefined)
|
|
22758
22797
|
continue;
|
|
22759
22798
|
try {
|
|
@@ -38273,6 +38312,7 @@ function buildSnapshotsFromCachedState2(state7) {
|
|
|
38273
38312
|
}
|
|
38274
38313
|
var THROTTLING_THRESHOLD_PCT2 = 80, OVERAGE_EXHAUSTED_REASONS2, HEALTH_EMOJI2, TABLE_HEALTH_RANK2;
|
|
38275
38314
|
var init_auth_snapshot_format2 = __esm(() => {
|
|
38315
|
+
init_quota();
|
|
38276
38316
|
init_client();
|
|
38277
38317
|
init_demo_mask();
|
|
38278
38318
|
init_card_format();
|
|
@@ -39001,6 +39041,7 @@ function renderUsageCard(snapshots, exhaustedByLabel, opts = {}) {
|
|
|
39001
39041
|
}
|
|
39002
39042
|
var FIVE_HOUR_MS, SEVEN_DAY_MS, BAR_WIDTH = 10, STATUS_LABEL;
|
|
39003
39043
|
var init_quota_bar_format = __esm(() => {
|
|
39044
|
+
init_quota();
|
|
39004
39045
|
init_client();
|
|
39005
39046
|
init_auth_snapshot_format();
|
|
39006
39047
|
init_card_format();
|
|
@@ -39617,6 +39658,22 @@ function redactAuthCodeMessage(api, chatId, messageId, log) {
|
|
|
39617
39658
|
|
|
39618
39659
|
// ask-user.ts
|
|
39619
39660
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
39661
|
+
|
|
39662
|
+
// gateway/source-message-id.ts
|
|
39663
|
+
var MAX_TELEGRAM_MESSAGE_ID = 2 ** 31;
|
|
39664
|
+
function parseSourceMessageId(raw) {
|
|
39665
|
+
if (raw == null)
|
|
39666
|
+
return null;
|
|
39667
|
+
const s = String(raw);
|
|
39668
|
+
if (!/^\d+$/.test(s))
|
|
39669
|
+
return null;
|
|
39670
|
+
const n = Number(s);
|
|
39671
|
+
if (!Number.isSafeInteger(n) || n <= 0 || n >= MAX_TELEGRAM_MESSAGE_ID)
|
|
39672
|
+
return null;
|
|
39673
|
+
return n;
|
|
39674
|
+
}
|
|
39675
|
+
|
|
39676
|
+
// ask-user.ts
|
|
39620
39677
|
var ASK_USER_DEFAULT_TIMEOUT_MS = 300000;
|
|
39621
39678
|
var ASK_USER_MAX_TIMEOUT_MS = 1800000;
|
|
39622
39679
|
var ASK_USER_MIN_TIMEOUT_MS = 5000;
|
|
@@ -39653,13 +39710,7 @@ function validateAskUserArgs(args) {
|
|
|
39653
39710
|
throw new Error("ask_user: message_thread_id must be a positive integer string");
|
|
39654
39711
|
}
|
|
39655
39712
|
}
|
|
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
|
-
}
|
|
39713
|
+
const replyTo = parseSourceMessageId(args.reply_to) ?? undefined;
|
|
39663
39714
|
let timeoutMs = args.timeout_ms ?? ASK_USER_DEFAULT_TIMEOUT_MS;
|
|
39664
39715
|
if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs)) {
|
|
39665
39716
|
throw new Error("ask_user: timeout_ms must be a number");
|
|
@@ -39739,6 +39790,7 @@ function applyChecklistPatch(cl, patch) {
|
|
|
39739
39790
|
return { title: patch.title ?? cl.title, tasks };
|
|
39740
39791
|
}
|
|
39741
39792
|
function buildNativeChecklistPayload(p) {
|
|
39793
|
+
const replyAnchor = parseSourceMessageId(p.replyToMessageId);
|
|
39742
39794
|
return {
|
|
39743
39795
|
business_connection_id: p.businessConnectionId,
|
|
39744
39796
|
chat_id: p.chatId,
|
|
@@ -39746,7 +39798,7 @@ function buildNativeChecklistPayload(p) {
|
|
|
39746
39798
|
title: p.title,
|
|
39747
39799
|
tasks: p.tasks.map((t) => ({ id: t.id, text: t.text }))
|
|
39748
39800
|
},
|
|
39749
|
-
...
|
|
39801
|
+
...replyAnchor != null ? { reply_parameters: { message_id: replyAnchor } } : {},
|
|
39750
39802
|
...p.protectContent === true ? { protect_content: true } : {}
|
|
39751
39803
|
};
|
|
39752
39804
|
}
|
|
@@ -40030,13 +40082,7 @@ function resolveStickerSendArgs(raw, aliasMap) {
|
|
|
40030
40082
|
throw new Error("send_sticker: message_thread_id must be a positive integer string");
|
|
40031
40083
|
}
|
|
40032
40084
|
}
|
|
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
|
-
}
|
|
40085
|
+
const replyTo = parseSourceMessageId(raw.reply_to) ?? undefined;
|
|
40040
40086
|
return {
|
|
40041
40087
|
chatId: raw.chat_id,
|
|
40042
40088
|
fileId,
|
|
@@ -40088,13 +40134,7 @@ function resolveGifSendArgs(raw) {
|
|
|
40088
40134
|
throw new Error("send_gif: message_thread_id must be a positive integer string");
|
|
40089
40135
|
}
|
|
40090
40136
|
}
|
|
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
|
-
}
|
|
40137
|
+
const replyTo = parseSourceMessageId(raw.reply_to) ?? undefined;
|
|
40098
40138
|
return {
|
|
40099
40139
|
chatId: raw.chat_id,
|
|
40100
40140
|
animationRef,
|
|
@@ -50208,6 +50248,20 @@ class PinRightsCache2 {
|
|
|
50208
50248
|
}
|
|
50209
50249
|
}
|
|
50210
50250
|
|
|
50251
|
+
// gateway/source-message-id.ts
|
|
50252
|
+
var MAX_TELEGRAM_MESSAGE_ID2 = 2 ** 31;
|
|
50253
|
+
function parseSourceMessageId2(raw) {
|
|
50254
|
+
if (raw == null)
|
|
50255
|
+
return null;
|
|
50256
|
+
const s = String(raw);
|
|
50257
|
+
if (!/^\d+$/.test(s))
|
|
50258
|
+
return null;
|
|
50259
|
+
const n = Number(s);
|
|
50260
|
+
if (!Number.isSafeInteger(n) || n <= 0 || n >= MAX_TELEGRAM_MESSAGE_ID2)
|
|
50261
|
+
return null;
|
|
50262
|
+
return n;
|
|
50263
|
+
}
|
|
50264
|
+
|
|
50211
50265
|
// gateway/permission-timeout.ts
|
|
50212
50266
|
var SIGNATURE_SEP = String.fromCharCode(0);
|
|
50213
50267
|
function permissionSignature(toolName, inputPreview) {
|
|
@@ -51221,7 +51275,7 @@ function createAuthBrokerClient() {
|
|
|
51221
51275
|
listState: () => broker.listState(),
|
|
51222
51276
|
setActive: (label) => broker.setActive(label),
|
|
51223
51277
|
markExhausted: (until) => broker.markExhausted(until),
|
|
51224
|
-
markThrottled: (until) => broker.markThrottled(until),
|
|
51278
|
+
markThrottled: (until, probeOnly) => broker.markThrottled(until, probeOnly),
|
|
51225
51279
|
rmAccount: (label) => broker.rmAccount(label),
|
|
51226
51280
|
refreshAccount: (label) => broker.refreshAccount(label),
|
|
51227
51281
|
setOverride: (agent, account) => broker.setOverride(agent, account),
|
|
@@ -73979,7 +74033,7 @@ function createAuthBrokerClient2() {
|
|
|
73979
74033
|
listState: () => broker.listState(),
|
|
73980
74034
|
setActive: (label) => broker.setActive(label),
|
|
73981
74035
|
markExhausted: (until) => broker.markExhausted(until),
|
|
73982
|
-
markThrottled: (until) => broker.markThrottled(until),
|
|
74036
|
+
markThrottled: (until, probeOnly) => broker.markThrottled(until, probeOnly),
|
|
73983
74037
|
rmAccount: (label) => broker.rmAccount(label),
|
|
73984
74038
|
refreshAccount: (label) => broker.refreshAccount(label),
|
|
73985
74039
|
setOverride: (agent, account) => broker.setOverride(agent, account),
|
|
@@ -74398,12 +74452,13 @@ class AuthBrokerClient2 {
|
|
|
74398
74452
|
const data = await this.send(req);
|
|
74399
74453
|
return data;
|
|
74400
74454
|
}
|
|
74401
|
-
async markThrottled(until) {
|
|
74455
|
+
async markThrottled(until, probeOnly = false) {
|
|
74402
74456
|
const data = await this.send({
|
|
74403
74457
|
v: PROTOCOL_VERSION,
|
|
74404
74458
|
id: randomUUID5(),
|
|
74405
74459
|
op: "mark-throttled",
|
|
74406
|
-
until
|
|
74460
|
+
until,
|
|
74461
|
+
...probeOnly ? { probeOnly: true } : {}
|
|
74407
74462
|
});
|
|
74408
74463
|
return data;
|
|
74409
74464
|
}
|
|
@@ -77103,6 +77158,15 @@ function classify429Detail2(text4) {
|
|
|
77103
77158
|
return "litellm-local";
|
|
77104
77159
|
return "generic-transient";
|
|
77105
77160
|
}
|
|
77161
|
+
function classification429WarrantsCorroboration(classification) {
|
|
77162
|
+
return classification === "generic-transient";
|
|
77163
|
+
}
|
|
77164
|
+
function routeRateLimit429(classification, runner, agent) {
|
|
77165
|
+
if (!classification429WarrantsCorroboration(classification))
|
|
77166
|
+
return false;
|
|
77167
|
+
runner.fireProbeOnly(agent);
|
|
77168
|
+
return true;
|
|
77169
|
+
}
|
|
77106
77170
|
function build429ClassifiedMetric(opts) {
|
|
77107
77171
|
const detail = typeof opts.detail === "string" ? opts.detail : "";
|
|
77108
77172
|
const litellm = parseLitellmLimitDetail(detail, new Date(opts.now));
|
|
@@ -77199,6 +77263,12 @@ function createThrottleTierRunner(deps) {
|
|
|
77199
77263
|
deps.log(`[throttle-tier] resume suppressed (${verdict}) reason=${reason}`);
|
|
77200
77264
|
}
|
|
77201
77265
|
}
|
|
77266
|
+
async function announceEscalation(client3, account, rolledTo, triggerAgent, armedAtMs) {
|
|
77267
|
+
deps.log(`[throttle-tier] escalated to wall account=${account ?? "?"} ` + `rolledTo=${rolledTo ?? "none (all blocked)"}`);
|
|
77268
|
+
await broadcastDeduped(client3, "throttle-escalation", account, renderThrottleEscalationNotice({ account, agent: triggerAgent, rolledTo }));
|
|
77269
|
+
if (rolledTo)
|
|
77270
|
+
nudgeResume("throttle-escalation-resume", armedAtMs);
|
|
77271
|
+
}
|
|
77202
77272
|
async function fire(triggerAgent, throttledUntilMs, resetParsed) {
|
|
77203
77273
|
const armedAtMs = now();
|
|
77204
77274
|
let client3 = null;
|
|
@@ -77219,10 +77289,7 @@ function createThrottleTierRunner(deps) {
|
|
|
77219
77289
|
deps.log(`[throttle-tier] markThrottled failed agent=${triggerAgent}: ${err?.message ?? err}`);
|
|
77220
77290
|
}
|
|
77221
77291
|
if (escalated) {
|
|
77222
|
-
|
|
77223
|
-
await broadcastDeduped(client3, "throttle-escalation", account, renderThrottleEscalationNotice({ account, agent: triggerAgent, rolledTo }));
|
|
77224
|
-
if (rolledTo)
|
|
77225
|
-
nudgeResume("throttle-escalation-resume", armedAtMs);
|
|
77292
|
+
await announceEscalation(client3, account, rolledTo, triggerAgent, armedAtMs);
|
|
77226
77293
|
return;
|
|
77227
77294
|
}
|
|
77228
77295
|
const cooldownKey = account ?? `agent:${triggerAgent}`;
|
|
@@ -77247,8 +77314,34 @@ function createThrottleTierRunner(deps) {
|
|
|
77247
77314
|
nudgeResume("throttle-retry-resume", armedAtMs);
|
|
77248
77315
|
}, delayMs);
|
|
77249
77316
|
}
|
|
77317
|
+
async function fireProbeOnly(triggerAgent) {
|
|
77318
|
+
const armedAtMs = now();
|
|
77319
|
+
let client3 = null;
|
|
77320
|
+
let account = null;
|
|
77321
|
+
let escalated = false;
|
|
77322
|
+
let rolledTo = null;
|
|
77323
|
+
try {
|
|
77324
|
+
client3 = await deps.getBrokerClient();
|
|
77325
|
+
if (client3) {
|
|
77326
|
+
const r = await client3.markThrottled(now() + 1, true);
|
|
77327
|
+
account = r.account;
|
|
77328
|
+
escalated = r.escalated;
|
|
77329
|
+
rolledTo = r.rolledTo ?? null;
|
|
77330
|
+
} else {
|
|
77331
|
+
deps.log(`[throttle-tier] broker unreachable \u2014 probe-only skipped agent=${triggerAgent}`);
|
|
77332
|
+
}
|
|
77333
|
+
} catch (err) {
|
|
77334
|
+
deps.log(`[throttle-tier] probe-only markThrottled failed agent=${triggerAgent}: ${err?.message ?? err}`);
|
|
77335
|
+
}
|
|
77336
|
+
if (escalated) {
|
|
77337
|
+
await announceEscalation(client3, account, rolledTo, triggerAgent, armedAtMs);
|
|
77338
|
+
return;
|
|
77339
|
+
}
|
|
77340
|
+
deps.log(`[throttle-tier] generic-transient probe-only inert (no wall) agent=${triggerAgent}`);
|
|
77341
|
+
}
|
|
77250
77342
|
return {
|
|
77251
77343
|
fire,
|
|
77344
|
+
fireProbeOnly,
|
|
77252
77345
|
inspect: () => ({ noticeState, nudgePending: pendingNudge != null })
|
|
77253
77346
|
};
|
|
77254
77347
|
}
|
|
@@ -79457,6 +79550,26 @@ function stampsHandbackMarker(source) {
|
|
|
79457
79550
|
return known.decoupledCompletion;
|
|
79458
79551
|
}
|
|
79459
79552
|
var HANDBACK_RECENCY_WINDOW_MS = 60000;
|
|
79553
|
+
var TASK_NOTIFICATION_DEDUP_TTL_MS = 30000;
|
|
79554
|
+
var NOTIF_TERMINAL_STATUSES = new Set(["completed", "failed", "killed"]);
|
|
79555
|
+
|
|
79556
|
+
class CliTaskNotificationLedger {
|
|
79557
|
+
seen = new Map;
|
|
79558
|
+
record(taskId, status, now) {
|
|
79559
|
+
if (taskId.length === 0 || !NOTIF_TERMINAL_STATUSES.has(status))
|
|
79560
|
+
return;
|
|
79561
|
+
this.seen.set(taskId, now);
|
|
79562
|
+
for (const [id, ts] of this.seen) {
|
|
79563
|
+
if (now - ts > TASK_NOTIFICATION_DEDUP_TTL_MS)
|
|
79564
|
+
this.seen.delete(id);
|
|
79565
|
+
}
|
|
79566
|
+
}
|
|
79567
|
+
seenRecently(taskId, now) {
|
|
79568
|
+
const ts = this.seen.get(taskId);
|
|
79569
|
+
return ts != null && now - ts <= TASK_NOTIFICATION_DEDUP_TTL_MS;
|
|
79570
|
+
}
|
|
79571
|
+
}
|
|
79572
|
+
var cliTaskNotifLedger = new CliTaskNotificationLedger;
|
|
79460
79573
|
var MAIN_THREAD_KEY = "<main>";
|
|
79461
79574
|
|
|
79462
79575
|
class SubagentHandbackMarker {
|
|
@@ -80205,7 +80318,7 @@ async function sendReply(deps, req) {
|
|
|
80205
80318
|
}
|
|
80206
80319
|
const files = args.files ?? [];
|
|
80207
80320
|
const quoteOptIn = args.quote !== false;
|
|
80208
|
-
let reply_to =
|
|
80321
|
+
let reply_to = parseSourceMessageId(args.reply_to) ?? undefined;
|
|
80209
80322
|
const protectContent = args.protect_content === true;
|
|
80210
80323
|
const quoteText = args.quote_text;
|
|
80211
80324
|
const access = loadAccess();
|
|
@@ -82011,20 +82124,6 @@ function formatEventDetail(event) {
|
|
|
82011
82124
|
}
|
|
82012
82125
|
}
|
|
82013
82126
|
|
|
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
82127
|
// gateway/status-surface-log.ts
|
|
82029
82128
|
function formatTurnLifecycle(action, reason, t, now) {
|
|
82030
82129
|
const ageMs = action === "clear" ? Math.max(0, now - t.startedAt) : 0;
|
|
@@ -84230,6 +84329,26 @@ function isTurnFlushSafetyEnabled(env = process.env) {
|
|
|
84230
84329
|
}
|
|
84231
84330
|
|
|
84232
84331
|
// gateway/subagent-handback-marker.ts
|
|
84332
|
+
var TASK_NOTIFICATION_DEDUP_TTL_MS2 = 30000;
|
|
84333
|
+
var NOTIF_TERMINAL_STATUSES2 = new Set(["completed", "failed", "killed"]);
|
|
84334
|
+
|
|
84335
|
+
class CliTaskNotificationLedger2 {
|
|
84336
|
+
seen = new Map;
|
|
84337
|
+
record(taskId, status, now) {
|
|
84338
|
+
if (taskId.length === 0 || !NOTIF_TERMINAL_STATUSES2.has(status))
|
|
84339
|
+
return;
|
|
84340
|
+
this.seen.set(taskId, now);
|
|
84341
|
+
for (const [id, ts] of this.seen) {
|
|
84342
|
+
if (now - ts > TASK_NOTIFICATION_DEDUP_TTL_MS2)
|
|
84343
|
+
this.seen.delete(id);
|
|
84344
|
+
}
|
|
84345
|
+
}
|
|
84346
|
+
seenRecently(taskId, now) {
|
|
84347
|
+
const ts = this.seen.get(taskId);
|
|
84348
|
+
return ts != null && now - ts <= TASK_NOTIFICATION_DEDUP_TTL_MS2;
|
|
84349
|
+
}
|
|
84350
|
+
}
|
|
84351
|
+
var cliTaskNotifLedger2 = new CliTaskNotificationLedger2;
|
|
84233
84352
|
var MAIN_THREAD_KEY2 = "<main>";
|
|
84234
84353
|
|
|
84235
84354
|
class SubagentHandbackMarker2 {
|
|
@@ -97011,6 +97130,9 @@ function decideSubagentHandback(input) {
|
|
|
97011
97130
|
if (!input.isBackground) {
|
|
97012
97131
|
return { deliver: false, reason: "foreground" };
|
|
97013
97132
|
}
|
|
97133
|
+
if (input.cliTaskNotificationSeen === true) {
|
|
97134
|
+
return { deliver: false, reason: "cli-task-notification" };
|
|
97135
|
+
}
|
|
97014
97136
|
const chatId = input.fleetChatId || input.ownerChatId;
|
|
97015
97137
|
if (!chatId) {
|
|
97016
97138
|
return { deliver: false, reason: "no-chat" };
|
|
@@ -102693,10 +102815,10 @@ function startOutboxSweep(deps) {
|
|
|
102693
102815
|
}
|
|
102694
102816
|
|
|
102695
102817
|
// ../src/build-info.ts
|
|
102696
|
-
var VERSION2 = "0.20.
|
|
102697
|
-
var COMMIT_SHA = "
|
|
102698
|
-
var COMMIT_DATE = "2026-08-
|
|
102699
|
-
var LATEST_PR =
|
|
102818
|
+
var VERSION2 = "0.20.10";
|
|
102819
|
+
var COMMIT_SHA = "ebb54738";
|
|
102820
|
+
var COMMIT_DATE = "2026-08-05T10:11:19Z";
|
|
102821
|
+
var LATEST_PR = 4398;
|
|
102700
102822
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
102701
102823
|
|
|
102702
102824
|
// gateway/boot-version.ts
|
|
@@ -107873,6 +107995,7 @@ function emitGatewayOperatorEvent(event) {
|
|
|
107873
107995
|
agent,
|
|
107874
107996
|
shouldEmitCard: (a) => shouldEmitOperatorEvent(a, "rate-limited")
|
|
107875
107997
|
});
|
|
107998
|
+
routeRateLimit429(rateLimit429Classification, throttleTierRunner, agent);
|
|
107876
107999
|
if (surface === "litellm-local-notice") {
|
|
107877
108000
|
process.stderr.write(`telegram gateway: 429 classified litellm-proxy-local agent=${agent} \u2014 ` + `calm path, no account attribution, no failover
|
|
107878
108001
|
`);
|
|
@@ -109150,6 +109273,8 @@ if (isGatewayMain)
|
|
|
109150
109273
|
}
|
|
109151
109274
|
}
|
|
109152
109275
|
const ev = msg.event;
|
|
109276
|
+
if (ev.kind === "task_notification")
|
|
109277
|
+
cliTaskNotifLedger2.record(ev.taskId, ev.status, Date.now());
|
|
109153
109278
|
handleSessionEvent2(ev);
|
|
109154
109279
|
toolFlightTracker.onEvent(ev);
|
|
109155
109280
|
if (pendingDeferredInterrupt != null && !toolFlightTracker.isMidToolCall()) {
|
|
@@ -109867,7 +109992,7 @@ async function executeSendChecklist(args) {
|
|
|
109867
109992
|
if (!Array.isArray(tasks) || tasks.length === 0)
|
|
109868
109993
|
throw new Error("send_checklist: tasks must be a non-empty array");
|
|
109869
109994
|
const threadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
109870
|
-
const replyTo =
|
|
109995
|
+
const replyTo = parseSourceMessageId2(args.reply_to) ?? undefined;
|
|
109871
109996
|
const protectContent = args.protect_content === true;
|
|
109872
109997
|
assertAllowedChat(chat_id);
|
|
109873
109998
|
const { title: redactedTitle, tasks: redactedTasks } = redactChecklistFields(title, tasks, (t) => redactOutboundText(t, "send_checklist"));
|
|
@@ -116715,11 +116840,12 @@ async function startGateway() {
|
|
|
116715
116840
|
ownerChatId: hbOwnerDm,
|
|
116716
116841
|
taskDescription: description2,
|
|
116717
116842
|
resultText,
|
|
116718
|
-
jsonlAgentId: agentId
|
|
116843
|
+
jsonlAgentId: agentId,
|
|
116844
|
+
cliTaskNotificationSeen: cliTaskNotifLedger2.seenRecently(agentId, Date.now())
|
|
116719
116845
|
});
|
|
116720
116846
|
if (!decision.deliver) {
|
|
116721
|
-
if (decision.reason === "no-chat") {
|
|
116722
|
-
process.stderr.write(`telegram gateway: subagent-handback ${agentId} \u2014 no chat to deliver to
|
|
116847
|
+
if (decision.reason === "no-chat" || decision.reason === "cli-task-notification") {
|
|
116848
|
+
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
116849
|
`);
|
|
116724
116850
|
}
|
|
116725
116851
|
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
|