switchroom 0.16.24 → 0.16.28
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/cli/switchroom.js +135 -37
- package/dist/host-control/main.js +13 -7
- package/package.json +2 -2
- package/telegram-plugin/answer-stream.ts +7 -6
- package/telegram-plugin/bridge/bridge.ts +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +255 -30
- package/telegram-plugin/dist/server.js +1 -1
- package/telegram-plugin/format.ts +335 -27
- package/telegram-plugin/gateway/drive-write-approval.test.ts +10 -10
- package/telegram-plugin/gateway/drive-write-approval.ts +14 -8
- package/telegram-plugin/gateway/gateway.ts +169 -12
- package/telegram-plugin/gateway/ipc-server.ts +11 -6
- package/telegram-plugin/gateway/permission-timeout.ts +76 -0
- package/telegram-plugin/permission-title.ts +3 -0
- package/telegram-plugin/retry-api-call.ts +27 -0
- package/telegram-plugin/rich-send.ts +29 -0
- package/telegram-plugin/shared/bot-runtime.ts +6 -1
- package/telegram-plugin/silent-reply-anchor.ts +9 -2
- package/telegram-plugin/status-no-truncate.ts +11 -5
- package/telegram-plugin/stream-reply-handler.ts +9 -1
- package/telegram-plugin/tests/ipc-server-validate-send-outbound.test.ts +6 -2
- package/telegram-plugin/tests/length-error-classify.test.ts +131 -0
- package/telegram-plugin/tests/paragraph-normalizer.test.ts +273 -0
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +12 -2
- package/telegram-plugin/tests/permission-timeout.test.ts +77 -0
- package/telegram-plugin/tests/permission-title.test.ts +43 -0
- package/telegram-plugin/tests/poll-health.test.ts +64 -0
|
@@ -6588,9 +6588,17 @@ function richMessage(markdown) {
|
|
|
6588
6588
|
function isParseEntitiesError(err) {
|
|
6589
6589
|
if (!(err instanceof import_grammy.GrammyError) || err.error_code !== 400)
|
|
6590
6590
|
return false;
|
|
6591
|
+
if (isLengthError(err))
|
|
6592
|
+
return false;
|
|
6591
6593
|
const d = (err.description || "").toLowerCase();
|
|
6592
6594
|
return d.includes("can't parse entities") || d.includes("can\u2019t parse entities") || d.includes("can't parse") || d.includes("can\u2019t parse") || d.includes("parse markdown") || d.includes("parse rich") || d.includes("can't find end of the entity") || d.includes("can\u2019t find end of the entity") || d.includes("unsupported start tag") || d.includes("unclosed start tag") || d.includes("expected end tag");
|
|
6593
6595
|
}
|
|
6596
|
+
function isLengthError(err) {
|
|
6597
|
+
if (!(err instanceof import_grammy.GrammyError) || err.error_code !== 400)
|
|
6598
|
+
return false;
|
|
6599
|
+
const d = (err.description || "").toLowerCase();
|
|
6600
|
+
return d.includes("rich_message_text_too_long") || d.includes("message_too_long") || d.includes("text_too_long") || d.includes("message is too long") || d.includes("text is too long");
|
|
6601
|
+
}
|
|
6594
6602
|
var import_grammy;
|
|
6595
6603
|
var init_rich_send = __esm(() => {
|
|
6596
6604
|
import_grammy = __toESM(require_mod2(), 1);
|
|
@@ -33006,7 +33014,7 @@ init_card_format();
|
|
|
33006
33014
|
// status-no-truncate.ts
|
|
33007
33015
|
var STATUS_ROLLING_LINES = 5;
|
|
33008
33016
|
var STATUS_LINE_MAX = 200;
|
|
33009
|
-
var STATUS_CARD_CHAR_BUDGET =
|
|
33017
|
+
var STATUS_CARD_CHAR_BUDGET = RICH_MESSAGE_MAX_CHARS;
|
|
33010
33018
|
var NESTED_PREFIX = " \u21b3 ";
|
|
33011
33019
|
|
|
33012
33020
|
// tool-activity-summary.ts
|
|
@@ -33545,6 +33553,23 @@ function isRecentTimeoutDuplicate(timeouts, sig, now, windowMs) {
|
|
|
33545
33553
|
const at = timeouts.get(sig);
|
|
33546
33554
|
return at != null && now - at <= windowMs;
|
|
33547
33555
|
}
|
|
33556
|
+
var PERMISSION_TTL_MS = 10 * 60000;
|
|
33557
|
+
var HOSTD_PERMISSION_TTL_MS = 30 * 60000;
|
|
33558
|
+
function ttlForTool(toolName) {
|
|
33559
|
+
return toolName && toolName.startsWith("mcp__hostd__") ? HOSTD_PERMISSION_TTL_MS : PERMISSION_TTL_MS;
|
|
33560
|
+
}
|
|
33561
|
+
var TIMED_OUT_FOOTER = `
|
|
33562
|
+
|
|
33563
|
+
\u23f1 Timed out \u2014 re-request to act`;
|
|
33564
|
+
function buildTimedOutCardEdits(cardText, cards) {
|
|
33565
|
+
return cards.map(({ chatId, messageId }) => ({
|
|
33566
|
+
chatId,
|
|
33567
|
+
messageId,
|
|
33568
|
+
text: `${cardText}${TIMED_OUT_FOOTER}`,
|
|
33569
|
+
stripKeyboard: true
|
|
33570
|
+
}));
|
|
33571
|
+
}
|
|
33572
|
+
var STALE_TAP_NOTICE = "This request already resolved (timed out) \u2014 ask again to act.";
|
|
33548
33573
|
|
|
33549
33574
|
// gateway/permission-card-origin.ts
|
|
33550
33575
|
function pickRecoveredPermissionOrigin(recentTurns, now, maxAgeMs) {
|
|
@@ -34452,7 +34477,7 @@ function streamKey2(chatId, threadId, lane, turnKey) {
|
|
|
34452
34477
|
}
|
|
34453
34478
|
async function handleStreamReply(args, state, deps) {
|
|
34454
34479
|
const chat_id = args.chat_id;
|
|
34455
|
-
const rawText = deps.repairEscapedWhitespace(args.text);
|
|
34480
|
+
const rawText = deps.normalizeParagraphBreaks ? deps.normalizeParagraphBreaks(deps.repairEscapedWhitespace(args.text)) : deps.repairEscapedWhitespace(args.text);
|
|
34456
34481
|
const done = Boolean(args.done);
|
|
34457
34482
|
const format = args.format ?? deps.defaultFormat;
|
|
34458
34483
|
if (done) {
|
|
@@ -34736,9 +34761,17 @@ async function retryWithThreadFallback(retry, send, opts) {
|
|
|
34736
34761
|
function isHtmlParseRejectError(err) {
|
|
34737
34762
|
if (!(err instanceof import_grammy2.GrammyError) || err.error_code !== 400)
|
|
34738
34763
|
return false;
|
|
34764
|
+
if (isMessageTooLongError(err))
|
|
34765
|
+
return false;
|
|
34739
34766
|
const d = (err.description || "").toLowerCase();
|
|
34740
34767
|
return d.includes("can't parse entities") || d.includes("can\u2019t parse entities") || d.includes("can't parse") || d.includes("can\u2019t parse") || d.includes("parse markdown") || d.includes("parse rich") || d.includes("unsupported start tag") || d.includes("unclosed start tag") || d.includes("can't find end of the entity") || d.includes("can\u2019t find end of the entity") || d.includes("expected end tag");
|
|
34741
34768
|
}
|
|
34769
|
+
function isMessageTooLongError(err) {
|
|
34770
|
+
if (!(err instanceof import_grammy2.GrammyError) || err.error_code !== 400)
|
|
34771
|
+
return false;
|
|
34772
|
+
const d = (err.description || "").toLowerCase();
|
|
34773
|
+
return d.includes("rich_message_text_too_long") || d.includes("message_too_long") || d.includes("text_too_long") || d.includes("message is too long") || d.includes("text is too long");
|
|
34774
|
+
}
|
|
34742
34775
|
|
|
34743
34776
|
// shared/bot-runtime.ts
|
|
34744
34777
|
var import_grammy4 = __toESM(require_mod2(), 1);
|
|
@@ -39603,7 +39636,7 @@ function decideOverPing(input) {
|
|
|
39603
39636
|
}
|
|
39604
39637
|
|
|
39605
39638
|
// silent-reply-anchor.ts
|
|
39606
|
-
var TELEGRAM_MSG_CAP =
|
|
39639
|
+
var TELEGRAM_MSG_CAP = RICH_MESSAGE_MAX_CHARS;
|
|
39607
39640
|
function enabled2() {
|
|
39608
39641
|
const v = process.env.SWITCHROOM_DISABLE_SILENT_REPLY_AUTOEDIT;
|
|
39609
39642
|
return !(v === "1" || v === "true");
|
|
@@ -43235,8 +43268,14 @@ function repairEscapedWhitespace(text) {
|
|
|
43235
43268
|
if (!/\\[nrt"\\]/.test(text))
|
|
43236
43269
|
return text;
|
|
43237
43270
|
const nonce = Math.random().toString(36).slice(2);
|
|
43238
|
-
const CODE_MASK_PH = `\x00RM${nonce}_`;
|
|
43239
43271
|
const BACKSLASH_PH = `\x00BK${nonce}_`;
|
|
43272
|
+
const { masked, restore } = maskCodeRegions(text, nonce);
|
|
43273
|
+
const unescaped = masked.replace(/\\\\/g, BACKSLASH_PH).replace(/\\n/g, `
|
|
43274
|
+
`).replace(/\\r/g, "\r").replace(/\\t/g, "\t").replace(/\\"/g, '"').replace(new RegExp(BACKSLASH_PH.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), "\\");
|
|
43275
|
+
return restore(unescaped);
|
|
43276
|
+
}
|
|
43277
|
+
function maskCodeRegions(text, nonce) {
|
|
43278
|
+
const CODE_MASK_PH = `\x00RM${nonce}_`;
|
|
43240
43279
|
const codeMasks = [];
|
|
43241
43280
|
const masked = text.replace(/```[\s\S]*?```/g, (m) => {
|
|
43242
43281
|
const idx = codeMasks.length;
|
|
@@ -43247,10 +43286,127 @@ function repairEscapedWhitespace(text) {
|
|
|
43247
43286
|
codeMasks.push(m);
|
|
43248
43287
|
return `${CODE_MASK_PH}${idx}\x00`;
|
|
43249
43288
|
});
|
|
43250
|
-
const unescaped = masked.replace(/\\\\/g, BACKSLASH_PH).replace(/\\n/g, `
|
|
43251
|
-
`).replace(/\\r/g, "\r").replace(/\\t/g, "\t").replace(/\\"/g, '"').replace(new RegExp(BACKSLASH_PH.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), "\\");
|
|
43252
43289
|
const restoreRe = new RegExp(`${CODE_MASK_PH.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(\\d+)\x00`, "g");
|
|
43253
|
-
|
|
43290
|
+
const restore = (s) => s.replace(restoreRe, (_m, idx) => codeMasks[Number(idx)] ?? _m);
|
|
43291
|
+
return { masked, restore, placeholder: CODE_MASK_PH };
|
|
43292
|
+
}
|
|
43293
|
+
function normalizeParagraphBreaks(text) {
|
|
43294
|
+
if (!text.includes(`
|
|
43295
|
+
`))
|
|
43296
|
+
return text;
|
|
43297
|
+
const nonce = Math.random().toString(36).slice(2);
|
|
43298
|
+
const { masked, restore, placeholder } = maskCodeRegions(text, nonce);
|
|
43299
|
+
let out = masked.replace(/\n{3,}/g, `
|
|
43300
|
+
|
|
43301
|
+
`);
|
|
43302
|
+
const lines = out.split(`
|
|
43303
|
+
`);
|
|
43304
|
+
const pieces = [];
|
|
43305
|
+
for (let i = 0;i < lines.length; i++) {
|
|
43306
|
+
let line = lines[i];
|
|
43307
|
+
const isLast = i === lines.length - 1;
|
|
43308
|
+
const next = isLast ? "" : lines[i + 1];
|
|
43309
|
+
const promote = !isLast && line.trim() !== "" && next.trim() !== "" && shouldPromoteBreak(line, next, placeholder);
|
|
43310
|
+
if (promote) {
|
|
43311
|
+
line = line.replace(/[ \t\r]+$/, "");
|
|
43312
|
+
}
|
|
43313
|
+
pieces.push(line);
|
|
43314
|
+
if (isLast)
|
|
43315
|
+
break;
|
|
43316
|
+
pieces.push(promote ? `
|
|
43317
|
+
` : `
|
|
43318
|
+
`);
|
|
43319
|
+
}
|
|
43320
|
+
out = pieces.join("");
|
|
43321
|
+
out = ensureBlockBoundaries(out, placeholder);
|
|
43322
|
+
return restore(out);
|
|
43323
|
+
}
|
|
43324
|
+
function isListItemLine(line) {
|
|
43325
|
+
const t = line.trimStart();
|
|
43326
|
+
return /^[-*+]\s/.test(t) || /^\d+[.)]\s/.test(t);
|
|
43327
|
+
}
|
|
43328
|
+
function isTableRowLine(line) {
|
|
43329
|
+
return /^\s*\|/.test(line);
|
|
43330
|
+
}
|
|
43331
|
+
function isTableDelimiterLine(line) {
|
|
43332
|
+
return /^\s*\|?\s*:?-{1,}:?\s*(\|\s*:?-{1,}:?\s*)*\|?\s*$/.test(line);
|
|
43333
|
+
}
|
|
43334
|
+
function isFenceOpenLine(line, placeholder) {
|
|
43335
|
+
const t = line.trimStart();
|
|
43336
|
+
if (placeholder != null && placeholder.length > 0 && t.startsWith(placeholder))
|
|
43337
|
+
return true;
|
|
43338
|
+
return t.startsWith("```");
|
|
43339
|
+
}
|
|
43340
|
+
function isBlockquoteLine(line) {
|
|
43341
|
+
return line.trimStart().startsWith(">");
|
|
43342
|
+
}
|
|
43343
|
+
function isHeadingLine(line) {
|
|
43344
|
+
return /^#{1,6}\s/.test(line.trimStart());
|
|
43345
|
+
}
|
|
43346
|
+
function ensureBlockBoundaries(text, placeholder) {
|
|
43347
|
+
if (!text.includes(`
|
|
43348
|
+
`))
|
|
43349
|
+
return text;
|
|
43350
|
+
const lines = text.split(`
|
|
43351
|
+
`);
|
|
43352
|
+
const result = [];
|
|
43353
|
+
for (let i = 0;i < lines.length; i++) {
|
|
43354
|
+
const line = lines[i];
|
|
43355
|
+
const prev = result.length > 0 ? result[result.length - 1] : null;
|
|
43356
|
+
const prevNonBlank = prev != null && prev.trim() !== "";
|
|
43357
|
+
const curBlank = line.trim() === "";
|
|
43358
|
+
if (prevNonBlank && !curBlank) {
|
|
43359
|
+
const next = i + 1 < lines.length ? lines[i + 1] : "";
|
|
43360
|
+
const prevIsTable = isTableRowLine(prev);
|
|
43361
|
+
const startsTableHere = !prevIsTable && (line.includes("|") && isTableDelimiterLine(next) || isTableRowLine(line) && isTableDelimiterLine(next));
|
|
43362
|
+
const startsFence = isFenceOpenLine(line, placeholder) && !isFenceOpenLine(prev, placeholder);
|
|
43363
|
+
const startsQuote = isBlockquoteLine(line) && !isBlockquoteLine(prev);
|
|
43364
|
+
const startsHeading = isHeadingLine(line) && !isHeadingLine(prev);
|
|
43365
|
+
if (startsTableHere || startsFence || startsQuote || startsHeading) {
|
|
43366
|
+
result.push("");
|
|
43367
|
+
}
|
|
43368
|
+
}
|
|
43369
|
+
if (prevNonBlank && !curBlank && isListItemLine(prev) && !isListItemLine(line)) {
|
|
43370
|
+
const isIndentedContinuation = /^(\t| {4,})\S/.test(line);
|
|
43371
|
+
const alreadySeparated = result.length > 0 && result[result.length - 1].trim() === "";
|
|
43372
|
+
if (!isIndentedContinuation && !alreadySeparated) {
|
|
43373
|
+
result.push("");
|
|
43374
|
+
}
|
|
43375
|
+
}
|
|
43376
|
+
result.push(line);
|
|
43377
|
+
}
|
|
43378
|
+
return result.join(`
|
|
43379
|
+
`);
|
|
43380
|
+
}
|
|
43381
|
+
function isMarkerLine(line, placeholder) {
|
|
43382
|
+
if (/^ {4,}\S/.test(line))
|
|
43383
|
+
return true;
|
|
43384
|
+
const t = line.trimStart();
|
|
43385
|
+
if (placeholder != null && placeholder.length > 0 && t.startsWith(placeholder))
|
|
43386
|
+
return true;
|
|
43387
|
+
return /^[-*+]\s/.test(t) || /^\d+[.)]\s/.test(t) || t.startsWith(">") || /^#{1,6}\s/.test(t) || t.startsWith("|") || line.includes(" | ") || t.startsWith("```") || /^(-{3,}|\*{3,}|_{3,})\s*$/.test(t);
|
|
43388
|
+
}
|
|
43389
|
+
function shouldPromoteBreak(prev, next, placeholder) {
|
|
43390
|
+
if (isMarkerLine(prev, placeholder) || isMarkerLine(next, placeholder))
|
|
43391
|
+
return false;
|
|
43392
|
+
const nextTrimmed = next.trimStart();
|
|
43393
|
+
if (nextTrimmed.length === 0)
|
|
43394
|
+
return false;
|
|
43395
|
+
const prevTrimmed = prev.trimEnd();
|
|
43396
|
+
const unwrapped = prevTrimmed.replace(/[)"'\u2019\u201d\]]+$/, "");
|
|
43397
|
+
const terminator = unwrapped.slice(-1);
|
|
43398
|
+
return terminator === "." || terminator === "!" || terminator === "?" || terminator === ":";
|
|
43399
|
+
}
|
|
43400
|
+
function hardSliceToCap(text, cap = RICH_MESSAGE_MAX_CHARS2) {
|
|
43401
|
+
if (cap <= 0)
|
|
43402
|
+
return [text];
|
|
43403
|
+
if (text.length <= cap)
|
|
43404
|
+
return [text];
|
|
43405
|
+
const out = [];
|
|
43406
|
+
for (let i = 0;i < text.length; i += cap) {
|
|
43407
|
+
out.push(text.slice(i, i + cap));
|
|
43408
|
+
}
|
|
43409
|
+
return out;
|
|
43254
43410
|
}
|
|
43255
43411
|
function splitMarkdownChunks(text, maxLen = RICH_MESSAGE_MAX_CHARS2) {
|
|
43256
43412
|
if (text.length <= maxLen)
|
|
@@ -47227,7 +47383,8 @@ var UpdateApplyRequestSchema = exports_external.object({
|
|
|
47227
47383
|
skip_images: exports_external.boolean().optional(),
|
|
47228
47384
|
rebuild: exports_external.boolean().optional(),
|
|
47229
47385
|
channel: exports_external.enum(["dev", "rc", "latest"]).nullable().optional(),
|
|
47230
|
-
pin: exports_external.string().regex(/^(sha-[0-9a-f]{7,40}|v\d+\.\d+\.\d+)$/).nullable().optional()
|
|
47386
|
+
pin: exports_external.string().regex(/^(sha-[0-9a-f]{7,40}|v\d+\.\d+\.\d+)$/).nullable().optional(),
|
|
47387
|
+
reason: exports_external.string().max(512).optional()
|
|
47231
47388
|
}).optional()
|
|
47232
47389
|
});
|
|
47233
47390
|
var ApplyRequestSchema = exports_external.object({
|
|
@@ -47242,21 +47399,24 @@ var RolloutRequestSchema = exports_external.object({
|
|
|
47242
47399
|
pin: exports_external.string().regex(/^v\d+\.\d+\.\d+$/),
|
|
47243
47400
|
agents: exports_external.array(AgentNameSchema2).min(1).optional(),
|
|
47244
47401
|
skip_web: exports_external.boolean().optional(),
|
|
47245
|
-
allow_downgrade: exports_external.boolean().optional()
|
|
47402
|
+
allow_downgrade: exports_external.boolean().optional(),
|
|
47403
|
+
reason: exports_external.string().max(512).optional()
|
|
47246
47404
|
}).required({ pin: true })
|
|
47247
47405
|
});
|
|
47248
47406
|
var AgentStartRequestSchema = exports_external.object({
|
|
47249
47407
|
...RequestEnvelope,
|
|
47250
47408
|
op: exports_external.literal("agent_start"),
|
|
47251
47409
|
args: exports_external.object({
|
|
47252
|
-
name: AgentNameSchema2
|
|
47410
|
+
name: AgentNameSchema2,
|
|
47411
|
+
reason: exports_external.string().max(512).optional()
|
|
47253
47412
|
})
|
|
47254
47413
|
});
|
|
47255
47414
|
var AgentStopRequestSchema = exports_external.object({
|
|
47256
47415
|
...RequestEnvelope,
|
|
47257
47416
|
op: exports_external.literal("agent_stop"),
|
|
47258
47417
|
args: exports_external.object({
|
|
47259
|
-
name: AgentNameSchema2
|
|
47418
|
+
name: AgentNameSchema2,
|
|
47419
|
+
reason: exports_external.string().max(512).optional()
|
|
47260
47420
|
})
|
|
47261
47421
|
});
|
|
47262
47422
|
var AgentLogsRequestSchema = exports_external.object({
|
|
@@ -47264,7 +47424,8 @@ var AgentLogsRequestSchema = exports_external.object({
|
|
|
47264
47424
|
op: exports_external.literal("agent_logs"),
|
|
47265
47425
|
args: exports_external.object({
|
|
47266
47426
|
name: AgentNameSchema2,
|
|
47267
|
-
tail: exports_external.number().int().positive().max(2000).optional()
|
|
47427
|
+
tail: exports_external.number().int().positive().max(2000).optional(),
|
|
47428
|
+
reason: exports_external.string().max(512).optional()
|
|
47268
47429
|
})
|
|
47269
47430
|
});
|
|
47270
47431
|
var AgentExecRequestSchema = exports_external.object({
|
|
@@ -47272,7 +47433,8 @@ var AgentExecRequestSchema = exports_external.object({
|
|
|
47272
47433
|
op: exports_external.literal("agent_exec"),
|
|
47273
47434
|
args: exports_external.object({
|
|
47274
47435
|
name: AgentNameSchema2,
|
|
47275
|
-
argv: exports_external.array(exports_external.string().min(1)).min(1).max(32)
|
|
47436
|
+
argv: exports_external.array(exports_external.string().min(1)).min(1).max(32),
|
|
47437
|
+
reason: exports_external.string().max(512).optional()
|
|
47276
47438
|
})
|
|
47277
47439
|
});
|
|
47278
47440
|
var DoctorRequestSchema = exports_external.object({
|
|
@@ -48480,7 +48642,7 @@ function validateClientMessage(msg) {
|
|
|
48480
48642
|
return false;
|
|
48481
48643
|
if (typeof m.chatId !== "string" || m.chatId.length === 0)
|
|
48482
48644
|
return false;
|
|
48483
|
-
if (typeof m.text !== "string" || m.text.length === 0 || m.text.length >
|
|
48645
|
+
if (typeof m.text !== "string" || m.text.length === 0 || m.text.length > RICH_MESSAGE_MAX_CHARS)
|
|
48484
48646
|
return false;
|
|
48485
48647
|
if (m.threadId !== undefined && (typeof m.threadId !== "number" || !Number.isInteger(m.threadId)))
|
|
48486
48648
|
return false;
|
|
@@ -49044,8 +49206,8 @@ function validateInput(input) {
|
|
|
49044
49206
|
var DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
49045
49207
|
var MAX_TTL_MS = 30 * 60 * 1000;
|
|
49046
49208
|
var MIN_TTL_MS = 30 * 1000;
|
|
49047
|
-
var TELEGRAM_SENDMESSAGE_LIMIT =
|
|
49048
|
-
var RENDERED_BODY_CAP =
|
|
49209
|
+
var TELEGRAM_SENDMESSAGE_LIMIT = RICH_MESSAGE_MAX_CHARS;
|
|
49210
|
+
var RENDERED_BODY_CAP = RICH_MESSAGE_MAX_CHARS - 200;
|
|
49049
49211
|
var OVERSIZE_SENTINEL = `
|
|
49050
49212
|
[\u2026 preview truncated; open in Drive for full context]`;
|
|
49051
49213
|
async function handleRequestDriveApproval(client3, msg, deps) {
|
|
@@ -54699,6 +54861,9 @@ var MCP_TOOL_DESCRIPTIONS = {
|
|
|
54699
54861
|
mcp__hostd__agent_exec: "Run a read-only inspection inside another agent",
|
|
54700
54862
|
mcp__hostd__update_check: "Check what a fleet-wide update would do",
|
|
54701
54863
|
mcp__hostd__update_apply: "Apply a fleet-wide update (pull + recreate)",
|
|
54864
|
+
mcp__hostd__rollout: "Roll the fleet to a pinned version",
|
|
54865
|
+
mcp__hostd__config_propose_edit: "Propose an edit to switchroom.yaml",
|
|
54866
|
+
mcp__hostd__get_status: "Read the last fleet-update status",
|
|
54702
54867
|
mcp__hindsight__recall: "Recall relevant memories",
|
|
54703
54868
|
mcp__hindsight__retain: "Retain a memory",
|
|
54704
54869
|
mcp__hindsight__reflect: "Reflect across its memory bank",
|
|
@@ -55999,11 +56164,11 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
|
|
|
55999
56164
|
}
|
|
56000
56165
|
|
|
56001
56166
|
// ../src/build-info.ts
|
|
56002
|
-
var VERSION = "0.16.
|
|
56003
|
-
var COMMIT_SHA = "
|
|
56004
|
-
var COMMIT_DATE = "2026-06-
|
|
56005
|
-
var LATEST_PR =
|
|
56006
|
-
var COMMITS_AHEAD_OF_TAG =
|
|
56167
|
+
var VERSION = "0.16.28";
|
|
56168
|
+
var COMMIT_SHA = "08b2121e";
|
|
56169
|
+
var COMMIT_DATE = "2026-06-30T04:25:13Z";
|
|
56170
|
+
var LATEST_PR = 2683;
|
|
56171
|
+
var COMMITS_AHEAD_OF_TAG = 0;
|
|
56007
56172
|
|
|
56008
56173
|
// gateway/boot-version.ts
|
|
56009
56174
|
function formatRelativeAgo(iso) {
|
|
@@ -57520,6 +57685,19 @@ var TOPIC_ID = process.env.TELEGRAM_TOPIC_ID ? Number(process.env.TELEGRAM_TOPIC
|
|
|
57520
57685
|
var AGENT_ADMIN = process.env.SWITCHROOM_AGENT_ADMIN === "true";
|
|
57521
57686
|
var bot = new import_grammy11.Bot(TOKEN);
|
|
57522
57687
|
installTgPostLogger(bot);
|
|
57688
|
+
var lastGetUpdatesHeartbeatMs = Date.now();
|
|
57689
|
+
bot.api.config.use(async (prev, method, payload, signal) => {
|
|
57690
|
+
try {
|
|
57691
|
+
const result = await prev(method, payload, signal);
|
|
57692
|
+
if (method === "getUpdates")
|
|
57693
|
+
lastGetUpdatesHeartbeatMs = Date.now();
|
|
57694
|
+
return result;
|
|
57695
|
+
} catch (err) {
|
|
57696
|
+
if (method === "getUpdates")
|
|
57697
|
+
lastGetUpdatesHeartbeatMs = Date.now();
|
|
57698
|
+
throw err;
|
|
57699
|
+
}
|
|
57700
|
+
});
|
|
57523
57701
|
var GRAMMY_VERSION = (() => {
|
|
57524
57702
|
try {
|
|
57525
57703
|
const raw = readFileSync37(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
|
|
@@ -58946,7 +59124,6 @@ function logOutbound(path, chatId, messageId, chars, extra, opts) {
|
|
|
58946
59124
|
var STATUS_QUERY_RE = /^\s*status\??\s*$/i;
|
|
58947
59125
|
var PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i;
|
|
58948
59126
|
var pendingPermissions = new Map;
|
|
58949
|
-
var PERMISSION_TTL_MS = 600000;
|
|
58950
59127
|
var PERMISSION_NO_REPEAT_ENABLED = process.env.SWITCHROOM_PERMISSION_NO_REPEAT !== "0";
|
|
58951
59128
|
var PERMISSION_DUPLICATE_WINDOW_MS = 3600000;
|
|
58952
59129
|
var permissionTimeoutSignatures = new Map;
|
|
@@ -59188,14 +59365,16 @@ var pendingStateReaper = setInterval(() => {
|
|
|
59188
59365
|
pendingVaultOps.delete(k);
|
|
59189
59366
|
}
|
|
59190
59367
|
for (const [k, v] of pendingPermissions) {
|
|
59191
|
-
|
|
59192
|
-
|
|
59368
|
+
const ttl = ttlForTool(v.tool_name);
|
|
59369
|
+
if (now - v.startedAt > ttl) {
|
|
59370
|
+
const timeoutMinutes = Math.round(ttl / 60000);
|
|
59193
59371
|
dispatchPermissionVerdict({
|
|
59194
59372
|
type: "permission",
|
|
59195
59373
|
requestId: k,
|
|
59196
59374
|
behavior: "deny",
|
|
59197
59375
|
message: timeoutDenyMessage(timeoutMinutes)
|
|
59198
59376
|
});
|
|
59377
|
+
stripTimedOutPermissionCards(v.card_text, v.cards);
|
|
59199
59378
|
resumeReactionAfterVerdict();
|
|
59200
59379
|
postPermissionResumeMessage({
|
|
59201
59380
|
behavior: "deny",
|
|
@@ -59837,6 +60016,11 @@ function buildPermissionActionRow(requestId, showAlways) {
|
|
|
59837
60016
|
kb.text("\uD83D\uDD01 Always\u2026", `perm:always:${requestId}`);
|
|
59838
60017
|
return kb;
|
|
59839
60018
|
}
|
|
60019
|
+
async function stripTimedOutPermissionCards(cardText, cards) {
|
|
60020
|
+
for (const edit of buildTimedOutCardEdits(cardText, cards)) {
|
|
60021
|
+
await swallowingApiCall(() => bot.api.editMessageText(edit.chatId, edit.messageId, richMessage2(edit.text), {}), { chat_id: edit.chatId, verb: "permission_timeout.strip" });
|
|
60022
|
+
}
|
|
60023
|
+
}
|
|
59840
60024
|
function dispatchPermissionVerdict(ev) {
|
|
59841
60025
|
const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
|
|
59842
60026
|
const delivered = ipcServer.sendToAgent(selfAgent, ev);
|
|
@@ -60094,13 +60278,13 @@ var ipcServer = createIpcServer({
|
|
|
60094
60278
|
return;
|
|
60095
60279
|
}
|
|
60096
60280
|
}
|
|
60097
|
-
pendingPermissions.set(requestId, { tool_name: toolName, description, input_preview: inputPreview, startedAt: Date.now() });
|
|
60098
60281
|
const text2 = formatPermissionCardBody({
|
|
60099
60282
|
toolName,
|
|
60100
60283
|
inputPreview,
|
|
60101
60284
|
description,
|
|
60102
60285
|
agentName: _client.agentName
|
|
60103
60286
|
});
|
|
60287
|
+
pendingPermissions.set(requestId, { tool_name: toolName, description, input_preview: inputPreview, startedAt: Date.now(), card_text: text2, cards: [] });
|
|
60104
60288
|
const showAlways = resolveScopedAllowChoices(toolName, inputPreview) != null;
|
|
60105
60289
|
const keyboard = buildPermissionActionRow(requestId, showAlways);
|
|
60106
60290
|
const activeTurn = currentTurn;
|
|
@@ -60109,7 +60293,12 @@ var ipcServer = createIpcServer({
|
|
|
60109
60293
|
retryWithThreadFallback(robustApiCall, (tid) => bot.api.sendRichMessage(chatId, richMessage2(text2), {
|
|
60110
60294
|
reply_markup: keyboard,
|
|
60111
60295
|
...tid != null ? { message_thread_id: tid } : {}
|
|
60112
|
-
}), { threadId, chat_id: chatId, verb: "permission_request" }).
|
|
60296
|
+
}), { threadId, chat_id: chatId, verb: "permission_request" }).then((sent) => {
|
|
60297
|
+
const pend = pendingPermissions.get(requestId);
|
|
60298
|
+
if (pend && sent && typeof sent.message_id === "number") {
|
|
60299
|
+
pend.cards.push({ chatId, messageId: sent.message_id });
|
|
60300
|
+
}
|
|
60301
|
+
}).catch((e) => {
|
|
60113
60302
|
process.stderr.write(`telegram gateway: permission_request send to ${chatId} failed: ${e}
|
|
60114
60303
|
`);
|
|
60115
60304
|
});
|
|
@@ -60757,7 +60946,7 @@ async function executeReply(args) {
|
|
|
60757
60946
|
const rawText = args.text;
|
|
60758
60947
|
if (rawText == null || rawText === "")
|
|
60759
60948
|
throw new Error("reply: text is required and cannot be empty");
|
|
60760
|
-
let text2 = repairEscapedWhitespace(rawText);
|
|
60949
|
+
let text2 = normalizeParagraphBreaks(repairEscapedWhitespace(rawText));
|
|
60761
60950
|
text2 = redactOutboundText(text2, "reply");
|
|
60762
60951
|
{
|
|
60763
60952
|
const scrub = scrubVoice(text2);
|
|
@@ -61102,6 +61291,24 @@ ${url}`;
|
|
|
61102
61291
|
delete richOpts.link_preview_options;
|
|
61103
61292
|
return lockedBot.api.sendRichMessage(chat_id, richMessage2(chunks[i]), richOpts);
|
|
61104
61293
|
};
|
|
61294
|
+
const sendChunkResplit = async (opts) => {
|
|
61295
|
+
const subPieces = splitMarkdownChunks(chunks[i], RICH_MESSAGE_MAX_CHARS2);
|
|
61296
|
+
const pieces = subPieces.length > 1 ? subPieces : hardSliceToCap(chunks[i], RICH_MESSAGE_MAX_CHARS2);
|
|
61297
|
+
for (let p = 0;p < pieces.length; p++) {
|
|
61298
|
+
let sent;
|
|
61299
|
+
if (literalText) {
|
|
61300
|
+
sent = await lockedBot.api.sendMessage(chat_id, pieces[p], opts);
|
|
61301
|
+
} else {
|
|
61302
|
+
const ro = { ...opts };
|
|
61303
|
+
delete ro.link_preview_options;
|
|
61304
|
+
sent = await lockedBot.api.sendRichMessage(chat_id, richMessage2(pieces[p]), ro);
|
|
61305
|
+
}
|
|
61306
|
+
sentIds.push(sent.message_id);
|
|
61307
|
+
logOutbound("reply", chat_id, sent.message_id, pieces[p].length, `chunk=${i + 1}/${chunks.length} resplit=${p + 1}/${pieces.length}`);
|
|
61308
|
+
}
|
|
61309
|
+
process.stderr.write(`telegram gateway: rich body too long \u2014 re-split chunk ${i + 1}/${chunks.length} into ${pieces.length} piece(s)
|
|
61310
|
+
`);
|
|
61311
|
+
};
|
|
61105
61312
|
try {
|
|
61106
61313
|
const sent = await robustApiCall(() => sendChunk(sendOpts), { threadId, chat_id });
|
|
61107
61314
|
sentIds.push(sent.message_id);
|
|
@@ -61115,11 +61322,15 @@ ${url}`;
|
|
|
61115
61322
|
const sent = await sendChunk(retryOpts);
|
|
61116
61323
|
sentIds.push(sent.message_id);
|
|
61117
61324
|
} catch (retryErr) {
|
|
61118
|
-
if (
|
|
61325
|
+
if (isMessageTooLongError(retryErr))
|
|
61326
|
+
await sendChunkResplit(retryOpts);
|
|
61327
|
+
else if (isHtmlParseRejectError(retryErr))
|
|
61119
61328
|
await sendChunkPlainText(retryOpts);
|
|
61120
61329
|
else
|
|
61121
61330
|
throw retryErr;
|
|
61122
61331
|
}
|
|
61332
|
+
} else if (isMessageTooLongError(err)) {
|
|
61333
|
+
await sendChunkResplit(sendOpts);
|
|
61123
61334
|
} else if (isHtmlParseRejectError(err)) {
|
|
61124
61335
|
await sendChunkPlainText(sendOpts);
|
|
61125
61336
|
} else {
|
|
@@ -61369,6 +61580,7 @@ async function executeStreamReply(args) {
|
|
|
61369
61580
|
bot: lockedBot,
|
|
61370
61581
|
retry: robustApiCall,
|
|
61371
61582
|
repairEscapedWhitespace,
|
|
61583
|
+
normalizeParagraphBreaks,
|
|
61372
61584
|
assertAllowedChat,
|
|
61373
61585
|
resolveThreadId,
|
|
61374
61586
|
disableLinkPreview: access.disableLinkPreview !== false,
|
|
@@ -62199,6 +62411,8 @@ async function executeEditMessage(args) {
|
|
|
62199
62411
|
const editFormat = args.format ?? (editAccess.parseMode ?? "html");
|
|
62200
62412
|
const editLiteralText = editFormat === "text";
|
|
62201
62413
|
let editRawText = repairEscapedWhitespace(args.text);
|
|
62414
|
+
if (!editLiteralText)
|
|
62415
|
+
editRawText = normalizeParagraphBreaks(editRawText);
|
|
62202
62416
|
editRawText = redactOutboundText(editRawText, "edit_message");
|
|
62203
62417
|
{
|
|
62204
62418
|
const scrub = scrubVoice(editRawText);
|
|
@@ -64353,7 +64567,7 @@ function switchroomExecCombined(args, timeoutMs = 15000) {
|
|
|
64353
64567
|
shell: "/bin/bash"
|
|
64354
64568
|
});
|
|
64355
64569
|
}
|
|
64356
|
-
function formatSwitchroomOutput(output, maxLen =
|
|
64570
|
+
function formatSwitchroomOutput(output, maxLen = RICH_MESSAGE_MAX_CHARS2) {
|
|
64357
64571
|
const trimmed = output.trim();
|
|
64358
64572
|
if (trimmed.length <= maxLen)
|
|
64359
64573
|
return trimmed;
|
|
@@ -68699,6 +68913,10 @@ ${editLabel}` : editLabel
|
|
|
68699
68913
|
});
|
|
68700
68914
|
return;
|
|
68701
68915
|
}
|
|
68916
|
+
if (!pendingPermissions.has(request_id)) {
|
|
68917
|
+
await ctx.answerCallbackQuery({ text: STALE_TAP_NOTICE }).catch(() => {});
|
|
68918
|
+
return;
|
|
68919
|
+
}
|
|
68702
68920
|
clearPermissionTimeoutSuppression("operator answered a permission card");
|
|
68703
68921
|
const pd = pendingPermissions.get(request_id);
|
|
68704
68922
|
const resumeAction = pd ? naturalAction(pd.tool_name, pd.input_preview) : "";
|
|
@@ -69551,7 +69769,14 @@ var POLL_HEALTH_THRESHOLD = Number(process.env.SWITCHROOM_POLL_HEALTH_THRESHOLD
|
|
|
69551
69769
|
var pollHealthCheck = null;
|
|
69552
69770
|
if (POLL_HEALTH_INTERVAL_MS > 0) {
|
|
69553
69771
|
pollHealthCheck = createPollHealthCheck({
|
|
69554
|
-
ping: () =>
|
|
69772
|
+
ping: async () => {
|
|
69773
|
+
await bot.api.getMe();
|
|
69774
|
+
const staleMs = Date.now() - lastGetUpdatesHeartbeatMs;
|
|
69775
|
+
const staleThresholdMs = POLL_HEALTH_INTERVAL_MS * POLL_HEALTH_THRESHOLD;
|
|
69776
|
+
if (staleMs > staleThresholdMs) {
|
|
69777
|
+
throw new Error(`getUpdates heartbeat stale: last seen ${Math.round(staleMs / 1000)}s ago (threshold ${Math.round(staleThresholdMs / 1000)}s) \u2014 runner loop frozen`);
|
|
69778
|
+
}
|
|
69779
|
+
},
|
|
69555
69780
|
onStall: async () => {
|
|
69556
69781
|
recoverFromPollStall({ agentName: process.env.SWITCHROOM_AGENT_NAME ?? "-" });
|
|
69557
69782
|
},
|
|
@@ -24388,7 +24388,7 @@ var init_bridge = __esm(async () => {
|
|
|
24388
24388
|
},
|
|
24389
24389
|
{
|
|
24390
24390
|
name: "stream_reply",
|
|
24391
|
-
description: "Post the final answer for this turn. The plugin renders an event-driven progress card (Plan \u2192 Run \u2192 Done with live tool bullets, elapsed time, and status emoji) for free while the turn is in-flight, so you do not need to narrate intermediate progress. Call `stream_reply` exactly once per turn with done=true and the complete answer text. Hard
|
|
24391
|
+
description: "Post the final answer for this turn. The plugin renders an event-driven progress card (Plan \u2192 Run \u2192 Done with live tool bullets, elapsed time, and status emoji) for free while the turn is in-flight, so you do not need to narrate intermediate progress. Call `stream_reply` exactly once per turn with done=true and the complete answer text. Hard cap is 32768 chars (the rich-message wire limit) \u2014 longer text is dropped by a defensive guard, so use `reply` for anything that long (it chunks). Calling with done=false is an error in this environment (the progress card already owns the mid-turn surface). inline_keyboard adds tappable buttons under the final message \u2014 see `reply` for shape and constraints.",
|
|
24392
24392
|
inputSchema: {
|
|
24393
24393
|
type: "object",
|
|
24394
24394
|
properties: {
|