switchroom 0.21.10 → 0.21.11
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 +143 -27
- package/dist/host-control/main.js +1 -1
- package/package.json +3 -2
- package/telegram-plugin/dist/gateway/gateway.js +385 -35
- package/telegram-plugin/format.ts +56 -0
- package/telegram-plugin/gateway/answer-route-overrides.ts +163 -0
- package/telegram-plugin/gateway/answer-thread-resolve.test.ts +175 -1
- package/telegram-plugin/gateway/answer-thread-resolve.ts +58 -6
- package/telegram-plugin/gateway/escalation-staleness.ts +526 -0
- package/telegram-plugin/gateway/gateway.ts +61 -68
- package/telegram-plugin/gateway/obligation-wiring.ts +91 -3
- package/telegram-plugin/gateway/outbound-send-path.ts +55 -1
- package/telegram-plugin/gateway/reply-route-log.test.ts +134 -0
- package/telegram-plugin/gateway/reply-route-log.ts +118 -0
- package/telegram-plugin/gateway/stream-render.ts +1 -1
- package/telegram-plugin/history.ts +21 -0
- package/telegram-plugin/registry/subagents-bugs.test.ts +3 -3
- package/telegram-plugin/render/html-fold.ts +22 -4
- package/telegram-plugin/render/parse.ts +9 -1
- package/telegram-plugin/render/render.ts +14 -13
- package/telegram-plugin/tests/answer-route-side-effect.test.ts +111 -0
- package/telegram-plugin/tests/catch-all-forwarded-history.test.ts +3 -3
- package/telegram-plugin/tests/escalation-staleness.test.ts +1275 -0
- package/telegram-plugin/tests/forwarded-rich-message-coalesce.test.ts +6 -6
- package/telegram-plugin/tests/forwarded-rich-message.test.ts +8 -8
- package/telegram-plugin/tests/history.test.ts +78 -0
- package/telegram-plugin/tests/multitopic-routing-wiring.test.ts +29 -1
- package/telegram-plugin/tests/orphaned-db-sweep.test.ts +17 -1
- package/telegram-plugin/tests/render/html-dialect-content-loss.test.ts +76 -3
- package/telegram-plugin/tests/send-reply-golden.test.ts +192 -3
- package/telegram-plugin/tests/status-pin.test.ts +2 -2
- package/telegram-plugin/tests/subagent-handback-inbound-builder.test.ts +2 -2
- package/telegram-plugin/tests/subagent-progress-inbound-builder.test.ts +2 -2
- package/telegram-plugin/tests/telegram-format.test.ts +52 -0
- package/telegram-plugin/tests/turn-supersede-finalizes-prior-card.test.ts +1 -1
- package/telegram-plugin/tests/worker-origin-gap-dispatch.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-supergroup-reply-channel.test.ts +1 -1
- package/telegram-plugin/worker-activity-feed.ts +2 -2
|
@@ -6769,6 +6769,20 @@ function escapeMarkdown(text) {
|
|
|
6769
6769
|
function codeSpanSafe(s) {
|
|
6770
6770
|
return s.replace(/`/g, "`\u200b");
|
|
6771
6771
|
}
|
|
6772
|
+
function codeFenceFor(text) {
|
|
6773
|
+
let longestRun = 0;
|
|
6774
|
+
let current = 0;
|
|
6775
|
+
for (let i = 0;i < text.length; i++) {
|
|
6776
|
+
if (text[i] === "`") {
|
|
6777
|
+
current++;
|
|
6778
|
+
if (current > longestRun)
|
|
6779
|
+
longestRun = current;
|
|
6780
|
+
} else {
|
|
6781
|
+
current = 0;
|
|
6782
|
+
}
|
|
6783
|
+
}
|
|
6784
|
+
return "`".repeat(Math.max(3, longestRun + 1));
|
|
6785
|
+
}
|
|
6772
6786
|
function escapeLinkHref(href) {
|
|
6773
6787
|
return href.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)").replace(/[\x00-\x20\x7f]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
|
|
6774
6788
|
}
|
|
@@ -30565,12 +30579,16 @@ function getRecentOutboundCount(chatId, withinSeconds) {
|
|
|
30565
30579
|
const row = prep2("SELECT COUNT(*) as cnt FROM messages WHERE chat_id = ? AND role = ? AND ts >= ?").get(chatId, "assistant", cutoff);
|
|
30566
30580
|
return row?.cnt ?? 0;
|
|
30567
30581
|
}
|
|
30568
|
-
function hasOutboundDeliveredSince2(chatId, sinceMs, threadId, minChars = 200) {
|
|
30582
|
+
function hasOutboundDeliveredSince2(chatId, sinceMs, threadId, minChars = 200, untilMs) {
|
|
30569
30583
|
try {
|
|
30570
30584
|
const cutoffSec = Math.floor(sinceMs / 1000);
|
|
30571
30585
|
const minLen = Math.max(1, Math.floor(minChars));
|
|
30572
30586
|
const params = [chatId, cutoffSec, minLen];
|
|
30573
30587
|
let sql = "SELECT 1 FROM messages WHERE chat_id = ? AND role = 'assistant' AND ts >= ? AND LENGTH(text) >= ?";
|
|
30588
|
+
if (untilMs != null && Number.isFinite(untilMs)) {
|
|
30589
|
+
sql += " AND ts <= ?";
|
|
30590
|
+
params.push(Math.ceil(untilMs / 1000));
|
|
30591
|
+
}
|
|
30574
30592
|
if (threadId !== undefined) {
|
|
30575
30593
|
if (threadId === null) {
|
|
30576
30594
|
sql += " AND thread_id IS NULL";
|
|
@@ -65182,6 +65200,7 @@ function tokenizeHtml(raw, base) {
|
|
|
65182
65200
|
}
|
|
65183
65201
|
|
|
65184
65202
|
// render/parse.ts
|
|
65203
|
+
init_format();
|
|
65185
65204
|
function pos(node2) {
|
|
65186
65205
|
const p = node2.position;
|
|
65187
65206
|
return {
|
|
@@ -65488,7 +65507,7 @@ function buildPreBlock(innerItems, ctx, span) {
|
|
|
65488
65507
|
if (ctx.noBreaks === true) {
|
|
65489
65508
|
return [{ type: "code", text: text4.replace(/\s*\r?\n\s*/g, " "), ...span }];
|
|
65490
65509
|
}
|
|
65491
|
-
const fence =
|
|
65510
|
+
const fence = codeFenceFor(text4);
|
|
65492
65511
|
return [
|
|
65493
65512
|
mkSeparator(ctx, span.start, span.start),
|
|
65494
65513
|
{
|
|
@@ -65722,8 +65741,7 @@ function renderBlockquote(node2) {
|
|
|
65722
65741
|
}
|
|
65723
65742
|
function renderCodeBlock(node2) {
|
|
65724
65743
|
const lang = node2.language ?? "";
|
|
65725
|
-
const
|
|
65726
|
-
const fence = "`".repeat(Math.max(3, longestRun + 1));
|
|
65744
|
+
const fence = codeFenceFor(node2.text);
|
|
65727
65745
|
return `${fence}${lang}
|
|
65728
65746
|
${node2.text}
|
|
65729
65747
|
${fence}`;
|
|
@@ -65823,8 +65841,7 @@ function tableDegradationReason(node2) {
|
|
|
65823
65841
|
}
|
|
65824
65842
|
function degradeToCodeFence(source, node2) {
|
|
65825
65843
|
const raw = source.slice(node2.start, node2.end);
|
|
65826
|
-
const
|
|
65827
|
-
const fence = "`".repeat(Math.max(3, longestRun + 1));
|
|
65844
|
+
const fence = codeFenceFor(raw);
|
|
65828
65845
|
return `${fence}
|
|
65829
65846
|
${raw}
|
|
65830
65847
|
${fence}`;
|
|
@@ -77239,12 +77256,16 @@ function lookupMessageRoleAndText(chatId, messageId, opts) {
|
|
|
77239
77256
|
return null;
|
|
77240
77257
|
return { role: row.role, text: row.text ?? "", kind: row.kind ?? null };
|
|
77241
77258
|
}
|
|
77242
|
-
function hasOutboundDeliveredSince(chatId, sinceMs, threadId, minChars = 200) {
|
|
77259
|
+
function hasOutboundDeliveredSince(chatId, sinceMs, threadId, minChars = 200, untilMs) {
|
|
77243
77260
|
try {
|
|
77244
77261
|
const cutoffSec = Math.floor(sinceMs / 1000);
|
|
77245
77262
|
const minLen = Math.max(1, Math.floor(minChars));
|
|
77246
77263
|
const params = [chatId, cutoffSec, minLen];
|
|
77247
77264
|
let sql = "SELECT 1 FROM messages WHERE chat_id = ? AND role = 'assistant' AND ts >= ? AND LENGTH(text) >= ?";
|
|
77265
|
+
if (untilMs != null && Number.isFinite(untilMs)) {
|
|
77266
|
+
sql += " AND ts <= ?";
|
|
77267
|
+
params.push(Math.ceil(untilMs / 1000));
|
|
77268
|
+
}
|
|
77248
77269
|
if (threadId !== undefined) {
|
|
77249
77270
|
if (threadId === null) {
|
|
77250
77271
|
sql += " AND thread_id IS NULL";
|
|
@@ -81148,6 +81169,33 @@ function journalExternalDelivery(args, stateDir, now = Date.now()) {
|
|
|
81148
81169
|
clearOutboxRecord(nonce, stateDir);
|
|
81149
81170
|
}
|
|
81150
81171
|
|
|
81172
|
+
// gateway/answer-thread-resolve.ts
|
|
81173
|
+
function isCrossChatAnchor(targetChatId, anchorChatId) {
|
|
81174
|
+
if (targetChatId == null || targetChatId === "")
|
|
81175
|
+
return false;
|
|
81176
|
+
if (anchorChatId == null || anchorChatId === "")
|
|
81177
|
+
return false;
|
|
81178
|
+
return String(anchorChatId) !== String(targetChatId);
|
|
81179
|
+
}
|
|
81180
|
+
|
|
81181
|
+
// gateway/reply-route-log.ts
|
|
81182
|
+
function formatReplyRouteLog(i) {
|
|
81183
|
+
const originCrossChat = isCrossChatAnchor(i.chatId, i.originTurn?.sessionChatId);
|
|
81184
|
+
const liveCrossChat = isCrossChatAnchor(i.chatId, i.liveTurn?.sessionChatId);
|
|
81185
|
+
const crossChatDropped = originCrossChat || liveCrossChat;
|
|
81186
|
+
const originAnchor = originCrossChat ? null : i.originTurn;
|
|
81187
|
+
const liveAnchor = liveCrossChat ? null : i.liveTurn;
|
|
81188
|
+
const explicit = i.explicitThreadId;
|
|
81189
|
+
const via = i.frameworkTopicAuthority ? originAnchor != null ? i.originVia === "quoted" ? "quoted" : "origin" : liveAnchor != null ? "live" : explicit != null ? "explicit" : i.recovered != null ? "recovered" : "none" : explicit != null ? "explicit" : originAnchor != null ? i.originVia === "quoted" ? "quoted" : "origin" : liveAnchor?.sessionThreadId != null ? "live" : i.recovered != null ? "recovered" : "none";
|
|
81190
|
+
const explicitOverridden = i.frameworkTopicAuthority && explicit != null && (originAnchor != null || liveAnchor != null) && i.threadId !== explicit;
|
|
81191
|
+
const ownerTurn = originAnchor ?? i.recovered ?? liveAnchor;
|
|
81192
|
+
const isSupergroup = i.chatId.startsWith("-100");
|
|
81193
|
+
const unrouted = isSupergroup && i.threadId == null && ownerTurn == null && !crossChatDropped;
|
|
81194
|
+
const misrouteRisk = isSupergroup && via === "live" && i.hasDifferentThreadedRecentTurn(liveAnchor?.sessionThreadId);
|
|
81195
|
+
return `telegram gateway: reply-route surface=${i.surface} chat=${i.chatId} ` + `resolved_thread=${i.threadId ?? "-"} via=${via} late=${liveAnchor == null} ` + `originTurn=${ownerTurn?.turnId ?? "-"} origin_thread=${ownerTurn?.sessionThreadId ?? "-"}` + (via === "recovered" ? " RECOVERED" : "") + (via === "quoted" ? " QUOTED(framework-origin)" : "") + (unrouted ? " UNROUTED(supergroup\u2192no-topic)" : "") + (misrouteRisk ? " MISROUTE_RISK(no-echo\u2192live-successor)" : "") + (crossChatDropped ? ` CROSS_CHAT_ANCHOR_DROPPED(target=${i.chatId}` + (originCrossChat ? `,origin_chat=${i.originTurn?.sessionChatId},origin_thread=${i.originTurn?.sessionThreadId ?? "-"}` : "") + (liveCrossChat ? `,live_chat=${i.liveTurn?.sessionChatId},live_thread=${i.liveTurn?.sessionThreadId ?? "-"}` : "") + `,routed\u2192${i.threadId ?? "-"})` : "") + (explicitOverridden ? ` EXPLICIT_OVERRIDDEN(model\u2192${explicit},routed\u2192${i.threadId ?? "-"})` : "") + `
|
|
81196
|
+
`;
|
|
81197
|
+
}
|
|
81198
|
+
|
|
81151
81199
|
// gateway/flood-reply-queue.ts
|
|
81152
81200
|
var FLOOD_QUEUED_SOURCE = "flood-deferred-reply";
|
|
81153
81201
|
function isFloodRejection(err) {
|
|
@@ -82357,7 +82405,24 @@ ${url}`;
|
|
|
82357
82405
|
replyRoutedOriginTurn = originTurn ?? null;
|
|
82358
82406
|
threadId = resolveAnswerThreadWithLog(chat_id, Number.isFinite(explicit) ? explicit : undefined, originTurn, originTurn == null ? null : echoedTurn != null ? "echo" : "quoted", turn, "reply");
|
|
82359
82407
|
} else {
|
|
82360
|
-
|
|
82408
|
+
const anchorCrossChat = isCrossChatAnchor(chat_id, turn?.sessionChatId);
|
|
82409
|
+
const anchorThreadId = anchorCrossChat ? undefined : turn?.sessionThreadId;
|
|
82410
|
+
threadId = resolveThreadId(chat_id, args.message_thread_id ?? (anchorThreadId != null ? anchorThreadId : undefined));
|
|
82411
|
+
if (anchorCrossChat) {
|
|
82412
|
+
const explicitRaw = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
82413
|
+
process.stderr.write(formatReplyRouteLog({
|
|
82414
|
+
surface: "reply",
|
|
82415
|
+
chatId: chat_id,
|
|
82416
|
+
threadId,
|
|
82417
|
+
explicitThreadId: Number.isFinite(explicitRaw) ? explicitRaw : undefined,
|
|
82418
|
+
originTurn: null,
|
|
82419
|
+
originVia: null,
|
|
82420
|
+
liveTurn: turn,
|
|
82421
|
+
recovered: null,
|
|
82422
|
+
frameworkTopicAuthority: false,
|
|
82423
|
+
hasDifferentThreadedRecentTurn: () => false
|
|
82424
|
+
}));
|
|
82425
|
+
}
|
|
82361
82426
|
}
|
|
82362
82427
|
let antecedentFromQuoteOptInDefault = false;
|
|
82363
82428
|
if (reply_to == null && quoteOptIn && HISTORY_ENABLED) {
|
|
@@ -97095,27 +97160,120 @@ class CurrentTurnMap {
|
|
|
97095
97160
|
}
|
|
97096
97161
|
|
|
97097
97162
|
// gateway/answer-thread-resolve.ts
|
|
97163
|
+
function isCrossChatAnchor2(targetChatId, anchorChatId) {
|
|
97164
|
+
if (targetChatId == null || targetChatId === "")
|
|
97165
|
+
return false;
|
|
97166
|
+
if (anchorChatId == null || anchorChatId === "")
|
|
97167
|
+
return false;
|
|
97168
|
+
return String(anchorChatId) !== String(targetChatId);
|
|
97169
|
+
}
|
|
97098
97170
|
function resolveAnswerThreadId(input) {
|
|
97171
|
+
const originCrossChat = isCrossChatAnchor2(input.targetChatId, input.originChatId);
|
|
97172
|
+
const liveCrossChat = isCrossChatAnchor2(input.targetChatId, input.liveChatId);
|
|
97173
|
+
const originResolved = input.originResolved && !originCrossChat;
|
|
97174
|
+
const originThreadId = originCrossChat ? undefined : input.originThreadId;
|
|
97175
|
+
const liveTurnPresent = input.liveTurnPresent === true && !liveCrossChat;
|
|
97176
|
+
const liveThreadId = liveCrossChat ? undefined : input.liveThreadId;
|
|
97099
97177
|
if (input.frameworkTopicAuthority === false) {
|
|
97100
97178
|
if (input.explicitThreadId != null)
|
|
97101
97179
|
return input.explicitThreadId;
|
|
97102
|
-
if (
|
|
97103
|
-
return
|
|
97104
|
-
if (
|
|
97105
|
-
return
|
|
97180
|
+
if (originResolved)
|
|
97181
|
+
return originThreadId;
|
|
97182
|
+
if (liveThreadId != null)
|
|
97183
|
+
return liveThreadId;
|
|
97106
97184
|
if (input.lastEndedResolvedForChat)
|
|
97107
97185
|
return input.lastEndedThreadIdForChat;
|
|
97108
|
-
return
|
|
97186
|
+
return liveThreadId;
|
|
97109
97187
|
}
|
|
97110
|
-
if (
|
|
97111
|
-
return
|
|
97112
|
-
if (
|
|
97113
|
-
return
|
|
97188
|
+
if (originResolved)
|
|
97189
|
+
return originThreadId;
|
|
97190
|
+
if (liveTurnPresent)
|
|
97191
|
+
return liveThreadId;
|
|
97114
97192
|
if (input.explicitThreadId != null)
|
|
97115
97193
|
return input.explicitThreadId;
|
|
97116
97194
|
if (input.lastEndedResolvedForChat)
|
|
97117
97195
|
return input.lastEndedThreadIdForChat;
|
|
97118
|
-
return
|
|
97196
|
+
return liveThreadId;
|
|
97197
|
+
}
|
|
97198
|
+
|
|
97199
|
+
// gateway/answer-route-overrides.ts
|
|
97200
|
+
function keyFor(chatId, threadId) {
|
|
97201
|
+
return `${chatId}:${threadId ?? "_"}`;
|
|
97202
|
+
}
|
|
97203
|
+
function createAnswerRouteOverrides(maxKeys = 64, maxPerKey = 8) {
|
|
97204
|
+
const byKey = new Map;
|
|
97205
|
+
return {
|
|
97206
|
+
note(args) {
|
|
97207
|
+
const overridden = args.enabled && args.explicitThreadId != null && args.anchored && args.routedThreadId !== args.explicitThreadId;
|
|
97208
|
+
if (!overridden)
|
|
97209
|
+
return false;
|
|
97210
|
+
const key = keyFor(args.chatId, args.explicitThreadId);
|
|
97211
|
+
const list2 = byKey.get(key) ?? [];
|
|
97212
|
+
list2.push({ routedThreadId: args.routedThreadId ?? null, atMs: args.nowMs });
|
|
97213
|
+
while (list2.length > maxPerKey)
|
|
97214
|
+
list2.shift();
|
|
97215
|
+
byKey.set(key, list2);
|
|
97216
|
+
while (byKey.size > maxKeys) {
|
|
97217
|
+
const oldest = byKey.keys().next().value;
|
|
97218
|
+
if (oldest === undefined)
|
|
97219
|
+
break;
|
|
97220
|
+
byKey.delete(oldest);
|
|
97221
|
+
}
|
|
97222
|
+
return true;
|
|
97223
|
+
},
|
|
97224
|
+
routedOverridesSince(chatId, intendedThreadId, notBeforeMs) {
|
|
97225
|
+
const list2 = byKey.get(keyFor(chatId, intendedThreadId));
|
|
97226
|
+
if (list2 == null)
|
|
97227
|
+
return [];
|
|
97228
|
+
const out = [];
|
|
97229
|
+
for (const e of list2) {
|
|
97230
|
+
if (e.atMs < notBeforeMs)
|
|
97231
|
+
continue;
|
|
97232
|
+
if (out.some((seen) => seen.routedThreadId === e.routedThreadId))
|
|
97233
|
+
continue;
|
|
97234
|
+
out.push({ ...e });
|
|
97235
|
+
}
|
|
97236
|
+
return out;
|
|
97237
|
+
},
|
|
97238
|
+
newestOverrideSince(chatId, intendedThreadId, notBeforeMs) {
|
|
97239
|
+
const list2 = byKey.get(keyFor(chatId, intendedThreadId));
|
|
97240
|
+
if (list2 == null)
|
|
97241
|
+
return;
|
|
97242
|
+
let newest;
|
|
97243
|
+
for (const e of list2) {
|
|
97244
|
+
if (e.atMs < notBeforeMs)
|
|
97245
|
+
continue;
|
|
97246
|
+
if (newest == null || e.atMs > newest.atMs)
|
|
97247
|
+
newest = e;
|
|
97248
|
+
}
|
|
97249
|
+
return newest == null ? undefined : { ...newest };
|
|
97250
|
+
},
|
|
97251
|
+
size() {
|
|
97252
|
+
return byKey.size;
|
|
97253
|
+
},
|
|
97254
|
+
clear() {
|
|
97255
|
+
byKey.clear();
|
|
97256
|
+
}
|
|
97257
|
+
};
|
|
97258
|
+
}
|
|
97259
|
+
var answerRouteOverrides = createAnswerRouteOverrides();
|
|
97260
|
+
|
|
97261
|
+
// gateway/reply-route-log.ts
|
|
97262
|
+
function formatReplyRouteLog2(i) {
|
|
97263
|
+
const originCrossChat = isCrossChatAnchor(i.chatId, i.originTurn?.sessionChatId);
|
|
97264
|
+
const liveCrossChat = isCrossChatAnchor(i.chatId, i.liveTurn?.sessionChatId);
|
|
97265
|
+
const crossChatDropped = originCrossChat || liveCrossChat;
|
|
97266
|
+
const originAnchor = originCrossChat ? null : i.originTurn;
|
|
97267
|
+
const liveAnchor = liveCrossChat ? null : i.liveTurn;
|
|
97268
|
+
const explicit = i.explicitThreadId;
|
|
97269
|
+
const via = i.frameworkTopicAuthority ? originAnchor != null ? i.originVia === "quoted" ? "quoted" : "origin" : liveAnchor != null ? "live" : explicit != null ? "explicit" : i.recovered != null ? "recovered" : "none" : explicit != null ? "explicit" : originAnchor != null ? i.originVia === "quoted" ? "quoted" : "origin" : liveAnchor?.sessionThreadId != null ? "live" : i.recovered != null ? "recovered" : "none";
|
|
97270
|
+
const explicitOverridden = i.frameworkTopicAuthority && explicit != null && (originAnchor != null || liveAnchor != null) && i.threadId !== explicit;
|
|
97271
|
+
const ownerTurn = originAnchor ?? i.recovered ?? liveAnchor;
|
|
97272
|
+
const isSupergroup = i.chatId.startsWith("-100");
|
|
97273
|
+
const unrouted = isSupergroup && i.threadId == null && ownerTurn == null && !crossChatDropped;
|
|
97274
|
+
const misrouteRisk = isSupergroup && via === "live" && i.hasDifferentThreadedRecentTurn(liveAnchor?.sessionThreadId);
|
|
97275
|
+
return `telegram gateway: reply-route surface=${i.surface} chat=${i.chatId} ` + `resolved_thread=${i.threadId ?? "-"} via=${via} late=${liveAnchor == null} ` + `originTurn=${ownerTurn?.turnId ?? "-"} origin_thread=${ownerTurn?.sessionThreadId ?? "-"}` + (via === "recovered" ? " RECOVERED" : "") + (via === "quoted" ? " QUOTED(framework-origin)" : "") + (unrouted ? " UNROUTED(supergroup\u2192no-topic)" : "") + (misrouteRisk ? " MISROUTE_RISK(no-echo\u2192live-successor)" : "") + (crossChatDropped ? ` CROSS_CHAT_ANCHOR_DROPPED(target=${i.chatId}` + (originCrossChat ? `,origin_chat=${i.originTurn?.sessionChatId},origin_thread=${i.originTurn?.sessionThreadId ?? "-"}` : "") + (liveCrossChat ? `,live_chat=${i.liveTurn?.sessionChatId},live_thread=${i.liveTurn?.sessionThreadId ?? "-"}` : "") + `,routed\u2192${i.threadId ?? "-"})` : "") + (explicitOverridden ? ` EXPLICIT_OVERRIDDEN(model\u2192${explicit},routed\u2192${i.threadId ?? "-"})` : "") + `
|
|
97276
|
+
`;
|
|
97119
97277
|
}
|
|
97120
97278
|
|
|
97121
97279
|
// gateway/latest-turn-lookup.ts
|
|
@@ -98532,6 +98690,153 @@ function shouldDeferEscalationForBridge(deps) {
|
|
|
98532
98690
|
return !deps.bridgeAlive;
|
|
98533
98691
|
}
|
|
98534
98692
|
|
|
98693
|
+
// gateway/escalation-staleness.ts
|
|
98694
|
+
var OBLIGATION_ESCALATE_SETTLE_MS_DEFAULT = 8500;
|
|
98695
|
+
var OBLIGATION_ESCALATE_SETTLE_MS_MAX = 60000;
|
|
98696
|
+
var REROUTE_MATCH_GRACE_MS = 1e4;
|
|
98697
|
+
var OBLIGATION_REROUTE_MATCH_MS_MAX = OBLIGATION_ESCALATE_SETTLE_MS_MAX + REROUTE_MATCH_GRACE_MS;
|
|
98698
|
+
function resolveRerouteMatchWindowMs(settleMs, env = process.env) {
|
|
98699
|
+
const derived = Math.max(settleMs, 0) + REROUTE_MATCH_GRACE_MS;
|
|
98700
|
+
const raw = env.SWITCHROOM_OBLIGATION_REROUTE_MATCH_MS;
|
|
98701
|
+
if (raw == null || raw.trim() === "")
|
|
98702
|
+
return derived;
|
|
98703
|
+
const n = Number(raw);
|
|
98704
|
+
if (!(Number.isFinite(n) && n >= 0))
|
|
98705
|
+
return derived;
|
|
98706
|
+
return Math.min(n, OBLIGATION_REROUTE_MATCH_MS_MAX);
|
|
98707
|
+
}
|
|
98708
|
+
function resolveEscalateSettleMs(env = process.env) {
|
|
98709
|
+
const raw = env.SWITCHROOM_OBLIGATION_ESCALATE_SETTLE_MS;
|
|
98710
|
+
if (raw == null || raw.trim() === "")
|
|
98711
|
+
return OBLIGATION_ESCALATE_SETTLE_MS_DEFAULT;
|
|
98712
|
+
const n = Number(raw);
|
|
98713
|
+
if (!(Number.isFinite(n) && n >= 0))
|
|
98714
|
+
return OBLIGATION_ESCALATE_SETTLE_MS_DEFAULT;
|
|
98715
|
+
return Math.min(n, OBLIGATION_ESCALATE_SETTLE_MS_MAX);
|
|
98716
|
+
}
|
|
98717
|
+
function answeredSinceOpen(o, deps) {
|
|
98718
|
+
const NOT_ANSWERED = { answered: false, via: null };
|
|
98719
|
+
if (!deps.historyEnabled)
|
|
98720
|
+
return NOT_ANSWERED;
|
|
98721
|
+
if (deps.hasOutboundDeliveredSince(o.chatId, o.openedAt, o.threadId)) {
|
|
98722
|
+
return { answered: true, via: "thread" };
|
|
98723
|
+
}
|
|
98724
|
+
if (!(deps.rerouteMatchWindowMs > 0))
|
|
98725
|
+
return NOT_ANSWERED;
|
|
98726
|
+
const notBefore = Math.max(o.openedAt, deps.anchorMs - deps.rerouteMatchWindowMs);
|
|
98727
|
+
for (const ovr of deps.routeOverrides.routedOverridesSince(o.chatId, o.threadId, notBefore)) {
|
|
98728
|
+
const since = Math.max(ovr.atMs, o.openedAt);
|
|
98729
|
+
const until = ovr.atMs + deps.rerouteMatchWindowMs;
|
|
98730
|
+
if (deps.hasOutboundDeliveredBetween(o.chatId, since, until, ovr.routedThreadId)) {
|
|
98731
|
+
return { answered: true, via: "reroute", routedThreadId: ovr.routedThreadId };
|
|
98732
|
+
}
|
|
98733
|
+
}
|
|
98734
|
+
const newest = deps.routeOverrides.newestOverrideSince(o.chatId, o.threadId, o.openedAt);
|
|
98735
|
+
if (newest != null && newest.atMs < notBefore) {
|
|
98736
|
+
return { answered: false, via: null, staleOverrideAgeMs: Math.max(0, deps.anchorMs - newest.atMs) };
|
|
98737
|
+
}
|
|
98738
|
+
return NOT_ANSWERED;
|
|
98739
|
+
}
|
|
98740
|
+
function createEscalationSettleGate(settleMs, maxKeys = 256) {
|
|
98741
|
+
const entries = new Map;
|
|
98742
|
+
return {
|
|
98743
|
+
shouldDefer(id, now, openedAt) {
|
|
98744
|
+
if (!(settleMs > 0))
|
|
98745
|
+
return false;
|
|
98746
|
+
const prev = entries.get(id);
|
|
98747
|
+
if (prev == null || prev.openedAt !== openedAt) {
|
|
98748
|
+
entries.set(id, { firstAt: now, openedAt });
|
|
98749
|
+
while (entries.size > maxKeys) {
|
|
98750
|
+
const oldest = entries.keys().next().value;
|
|
98751
|
+
if (oldest === undefined)
|
|
98752
|
+
break;
|
|
98753
|
+
entries.delete(oldest);
|
|
98754
|
+
}
|
|
98755
|
+
return true;
|
|
98756
|
+
}
|
|
98757
|
+
if (now < prev.firstAt) {
|
|
98758
|
+
entries.set(id, { firstAt: now, openedAt });
|
|
98759
|
+
return true;
|
|
98760
|
+
}
|
|
98761
|
+
return now - prev.firstAt < settleMs;
|
|
98762
|
+
},
|
|
98763
|
+
firstAt(id, openedAt) {
|
|
98764
|
+
const prev = entries.get(id);
|
|
98765
|
+
if (prev == null || prev.openedAt !== openedAt)
|
|
98766
|
+
return;
|
|
98767
|
+
return prev.firstAt;
|
|
98768
|
+
},
|
|
98769
|
+
clear(id) {
|
|
98770
|
+
entries.delete(id);
|
|
98771
|
+
},
|
|
98772
|
+
size() {
|
|
98773
|
+
return entries.size;
|
|
98774
|
+
}
|
|
98775
|
+
};
|
|
98776
|
+
}
|
|
98777
|
+
|
|
98778
|
+
// gateway/answer-route-overrides.ts
|
|
98779
|
+
function keyFor2(chatId, threadId) {
|
|
98780
|
+
return `${chatId}:${threadId ?? "_"}`;
|
|
98781
|
+
}
|
|
98782
|
+
function createAnswerRouteOverrides2(maxKeys = 64, maxPerKey = 8) {
|
|
98783
|
+
const byKey = new Map;
|
|
98784
|
+
return {
|
|
98785
|
+
note(args) {
|
|
98786
|
+
const overridden = args.enabled && args.explicitThreadId != null && args.anchored && args.routedThreadId !== args.explicitThreadId;
|
|
98787
|
+
if (!overridden)
|
|
98788
|
+
return false;
|
|
98789
|
+
const key = keyFor2(args.chatId, args.explicitThreadId);
|
|
98790
|
+
const list2 = byKey.get(key) ?? [];
|
|
98791
|
+
list2.push({ routedThreadId: args.routedThreadId ?? null, atMs: args.nowMs });
|
|
98792
|
+
while (list2.length > maxPerKey)
|
|
98793
|
+
list2.shift();
|
|
98794
|
+
byKey.set(key, list2);
|
|
98795
|
+
while (byKey.size > maxKeys) {
|
|
98796
|
+
const oldest = byKey.keys().next().value;
|
|
98797
|
+
if (oldest === undefined)
|
|
98798
|
+
break;
|
|
98799
|
+
byKey.delete(oldest);
|
|
98800
|
+
}
|
|
98801
|
+
return true;
|
|
98802
|
+
},
|
|
98803
|
+
routedOverridesSince(chatId, intendedThreadId, notBeforeMs) {
|
|
98804
|
+
const list2 = byKey.get(keyFor2(chatId, intendedThreadId));
|
|
98805
|
+
if (list2 == null)
|
|
98806
|
+
return [];
|
|
98807
|
+
const out = [];
|
|
98808
|
+
for (const e of list2) {
|
|
98809
|
+
if (e.atMs < notBeforeMs)
|
|
98810
|
+
continue;
|
|
98811
|
+
if (out.some((seen) => seen.routedThreadId === e.routedThreadId))
|
|
98812
|
+
continue;
|
|
98813
|
+
out.push({ ...e });
|
|
98814
|
+
}
|
|
98815
|
+
return out;
|
|
98816
|
+
},
|
|
98817
|
+
newestOverrideSince(chatId, intendedThreadId, notBeforeMs) {
|
|
98818
|
+
const list2 = byKey.get(keyFor2(chatId, intendedThreadId));
|
|
98819
|
+
if (list2 == null)
|
|
98820
|
+
return;
|
|
98821
|
+
let newest;
|
|
98822
|
+
for (const e of list2) {
|
|
98823
|
+
if (e.atMs < notBeforeMs)
|
|
98824
|
+
continue;
|
|
98825
|
+
if (newest == null || e.atMs > newest.atMs)
|
|
98826
|
+
newest = e;
|
|
98827
|
+
}
|
|
98828
|
+
return newest == null ? undefined : { ...newest };
|
|
98829
|
+
},
|
|
98830
|
+
size() {
|
|
98831
|
+
return byKey.size;
|
|
98832
|
+
},
|
|
98833
|
+
clear() {
|
|
98834
|
+
byKey.clear();
|
|
98835
|
+
}
|
|
98836
|
+
};
|
|
98837
|
+
}
|
|
98838
|
+
var answerRouteOverrides2 = createAnswerRouteOverrides2();
|
|
98839
|
+
|
|
98535
98840
|
// gateway/stream-render.ts
|
|
98536
98841
|
init_history();
|
|
98537
98842
|
init_rich_send();
|
|
@@ -98570,14 +98875,19 @@ function createObligationWiring(deps) {
|
|
|
98570
98875
|
bridgeAlive,
|
|
98571
98876
|
sendEscalationNudge
|
|
98572
98877
|
} = deps;
|
|
98878
|
+
const escalateSettleMs = resolveEscalateSettleMs();
|
|
98879
|
+
const escalateSettleGate = createEscalationSettleGate(escalateSettleMs);
|
|
98880
|
+
const rerouteMatchWindowMs = resolveRerouteMatchWindowMs(escalateSettleMs);
|
|
98573
98881
|
function closeObligationOnSubstantiveReply(args, liveTurn, routedOriginTurn) {
|
|
98574
98882
|
if (!OBLIGATION_LEDGER_ENABLED)
|
|
98575
98883
|
return;
|
|
98576
98884
|
const echoed = findTurnByOriginId(args.origin_turn_id);
|
|
98577
98885
|
const routedOriginId = routedOriginTurn != null && echoed == null ? routedOriginTurn.turnId : null;
|
|
98578
98886
|
const target = obligationLedger.resolveCloseTarget(echoed?.turnId, liveTurn?.turnId, routedOriginId);
|
|
98579
|
-
if (target != null)
|
|
98887
|
+
if (target != null) {
|
|
98580
98888
|
obligationLedger.close(target);
|
|
98889
|
+
escalateSettleGate.clear(target);
|
|
98890
|
+
}
|
|
98581
98891
|
}
|
|
98582
98892
|
function openObligationFromInbound(inboundMsg, gate) {
|
|
98583
98893
|
if (!OBLIGATION_LEDGER_ENABLED)
|
|
@@ -98609,6 +98919,7 @@ function createObligationWiring(deps) {
|
|
|
98609
98919
|
if (turn == null)
|
|
98610
98920
|
return;
|
|
98611
98921
|
if (obligationLedger.close(turn.turnId)) {
|
|
98922
|
+
escalateSettleGate.clear(turn.turnId);
|
|
98612
98923
|
process.stderr.write(`telegram gateway: obligation cancelled by interrupt origin=${turn.turnId}
|
|
98613
98924
|
`);
|
|
98614
98925
|
}
|
|
@@ -98649,6 +98960,7 @@ function createObligationWiring(deps) {
|
|
|
98649
98960
|
})) {
|
|
98650
98961
|
process.stderr.write(`telegram gateway: obligation closed silently \u2014 reply delivered since last represent (no re-fire) origin=${o.originTurnId}
|
|
98651
98962
|
`);
|
|
98963
|
+
escalateSettleGate.clear(o.originTurnId);
|
|
98652
98964
|
obligationLedger.close(o.originTurnId);
|
|
98653
98965
|
return;
|
|
98654
98966
|
}
|
|
@@ -98670,12 +98982,31 @@ function createObligationWiring(deps) {
|
|
|
98670
98982
|
`);
|
|
98671
98983
|
return;
|
|
98672
98984
|
}
|
|
98673
|
-
|
|
98674
|
-
|
|
98985
|
+
const answered = answeredSinceOpen(o, {
|
|
98986
|
+
historyEnabled: HISTORY_ENABLED,
|
|
98987
|
+
hasOutboundDeliveredSince: hasOutboundDeliveredSince3,
|
|
98988
|
+
hasOutboundDeliveredBetween: (chatId, sinceMs, untilMs, threadId) => hasOutboundDeliveredSince3(chatId, sinceMs, threadId, undefined, untilMs),
|
|
98989
|
+
routeOverrides: answerRouteOverrides2,
|
|
98990
|
+
anchorMs: escalateSettleGate.firstAt(o.originTurnId, o.openedAt) ?? now,
|
|
98991
|
+
rerouteMatchWindowMs
|
|
98992
|
+
});
|
|
98993
|
+
if (answered.answered) {
|
|
98994
|
+
const scope = answered.via === "reroute" ? `via=reroute routed_thread=${answered.routedThreadId ?? "-"}` : "via=thread";
|
|
98995
|
+
process.stderr.write(`telegram gateway: obligation closed silently \u2014 outbound delivered since open ${scope} origin=${o.originTurnId}
|
|
98675
98996
|
`);
|
|
98997
|
+
escalateSettleGate.clear(o.originTurnId);
|
|
98676
98998
|
obligationLedger.close(o.originTurnId);
|
|
98677
98999
|
return;
|
|
98678
99000
|
}
|
|
99001
|
+
if (answered.staleOverrideAgeMs != null) {
|
|
99002
|
+
process.stderr.write(`telegram gateway: obligation reroute record rejected age=${answered.staleOverrideAgeMs}ms ` + `window=${rerouteMatchWindowMs}ms origin=${o.originTurnId}
|
|
99003
|
+
`);
|
|
99004
|
+
}
|
|
99005
|
+
if (escalateSettleGate.shouldDefer(o.originTurnId, now, o.openedAt)) {
|
|
99006
|
+
process.stderr.write(`telegram gateway: obligation escalation deferred \u2014 settling (re-checking for an in-flight answer) origin=${o.originTurnId}
|
|
99007
|
+
`);
|
|
99008
|
+
return;
|
|
99009
|
+
}
|
|
98679
99010
|
driveEscalation({
|
|
98680
99011
|
escId: o.originTurnId,
|
|
98681
99012
|
inFlight: obligationEscalateInFlight,
|
|
@@ -98684,6 +99015,7 @@ function createObligationWiring(deps) {
|
|
|
98684
99015
|
maxAttempts: OBLIGATION_ESCALATE_MAX,
|
|
98685
99016
|
deadlineMs: OBLIGATION_ESCALATE_SEND_DEADLINE_MS
|
|
98686
99017
|
});
|
|
99018
|
+
escalateSettleGate.clear(o.originTurnId);
|
|
98687
99019
|
}
|
|
98688
99020
|
return {
|
|
98689
99021
|
closeObligationOnSubstantiveReply,
|
|
@@ -105407,10 +105739,10 @@ function startOutboxSweep(deps) {
|
|
|
105407
105739
|
}
|
|
105408
105740
|
|
|
105409
105741
|
// ../src/build-info.ts
|
|
105410
|
-
var VERSION2 = "0.21.
|
|
105411
|
-
var COMMIT_SHA = "
|
|
105412
|
-
var COMMIT_DATE = "2026-08-
|
|
105413
|
-
var LATEST_PR =
|
|
105742
|
+
var VERSION2 = "0.21.11";
|
|
105743
|
+
var COMMIT_SHA = "7a80af9d";
|
|
105744
|
+
var COMMIT_DATE = "2026-08-14T09:19:49Z";
|
|
105745
|
+
var LATEST_PR = 4713;
|
|
105414
105746
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
105415
105747
|
|
|
105416
105748
|
// gateway/boot-version.ts
|
|
@@ -108939,7 +109271,9 @@ function resolveReplyOwnerTurn(liveTurn, chatId, args) {
|
|
|
108939
109271
|
return resolveReplyOwnerTurnWith({ findTurnByOriginId, findTurnByQuotedMessageId, findLatestTurnForChat, now: Date.now }, liveTurn, chatId, args);
|
|
108940
109272
|
}
|
|
108941
109273
|
function resolveAnswerThreadWithLog(chatId, explicitThreadId, originTurn, originVia, liveTurn, surface) {
|
|
108942
|
-
const
|
|
109274
|
+
const originAnchor = isCrossChatAnchor2(chatId, originTurn?.sessionChatId) ? null : originTurn;
|
|
109275
|
+
const liveAnchor = isCrossChatAnchor2(chatId, liveTurn?.sessionChatId) ? null : liveTurn;
|
|
109276
|
+
const recovered = LATE_REPLY_TOPIC_RECOVERY_ENABLED && explicitThreadId == null && originAnchor == null && liveAnchor == null ? findLatestTurnForChat(chatId, { endedOnly: false }) : null;
|
|
108943
109277
|
const threadId = resolveAnswerThreadId({
|
|
108944
109278
|
explicitThreadId,
|
|
108945
109279
|
originResolved: originTurn != null,
|
|
@@ -108948,16 +109282,31 @@ function resolveAnswerThreadWithLog(chatId, explicitThreadId, originTurn, origin
|
|
|
108948
109282
|
liveTurnPresent: liveTurn != null,
|
|
108949
109283
|
lastEndedResolvedForChat: recovered != null,
|
|
108950
109284
|
lastEndedThreadIdForChat: recovered?.sessionThreadId,
|
|
108951
|
-
frameworkTopicAuthority: REPLY_TOPIC_AUTHORITY_ENABLED
|
|
109285
|
+
frameworkTopicAuthority: REPLY_TOPIC_AUTHORITY_ENABLED,
|
|
109286
|
+
targetChatId: chatId,
|
|
109287
|
+
originChatId: originTurn?.sessionChatId,
|
|
109288
|
+
liveChatId: liveTurn?.sessionChatId
|
|
108952
109289
|
});
|
|
108953
|
-
|
|
108954
|
-
|
|
108955
|
-
|
|
108956
|
-
|
|
108957
|
-
|
|
108958
|
-
|
|
108959
|
-
|
|
108960
|
-
|
|
109290
|
+
answerRouteOverrides.note({
|
|
109291
|
+
chatId,
|
|
109292
|
+
enabled: REPLY_TOPIC_AUTHORITY_ENABLED,
|
|
109293
|
+
explicitThreadId,
|
|
109294
|
+
anchored: originAnchor != null || liveAnchor != null,
|
|
109295
|
+
routedThreadId: threadId,
|
|
109296
|
+
nowMs: Date.now()
|
|
109297
|
+
});
|
|
109298
|
+
process.stderr.write(formatReplyRouteLog2({
|
|
109299
|
+
surface,
|
|
109300
|
+
chatId,
|
|
109301
|
+
threadId,
|
|
109302
|
+
explicitThreadId,
|
|
109303
|
+
originTurn,
|
|
109304
|
+
originVia,
|
|
109305
|
+
liveTurn,
|
|
109306
|
+
recovered,
|
|
109307
|
+
frameworkTopicAuthority: REPLY_TOPIC_AUTHORITY_ENABLED,
|
|
109308
|
+
hasDifferentThreadedRecentTurn: (liveThreadId) => hasDifferentThreadedRecentTurn(chatId, liveThreadId)
|
|
109309
|
+
}));
|
|
108961
109310
|
return threadId;
|
|
108962
109311
|
}
|
|
108963
109312
|
function hasDifferentThreadedRecentTurn(chatId, liveThreadId) {
|
|
@@ -119823,6 +120172,7 @@ async function startGateway() {
|
|
|
119823
120172
|
if (isGatewayMain)
|
|
119824
120173
|
startGateway();
|
|
119825
120174
|
export {
|
|
120175
|
+
resolveAnswerThreadWithLog,
|
|
119826
120176
|
initVaultApprovalPosture,
|
|
119827
120177
|
handleInboundCoalesced,
|
|
119828
120178
|
handleInbound,
|
|
@@ -85,6 +85,62 @@ export function codeSpanSafe(s: string): string {
|
|
|
85
85
|
return s.replace(/`/g, '`')
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Compute the delimiter for a fenced code block whose body is `text`.
|
|
90
|
+
*
|
|
91
|
+
* Fenced content is verbatim per the formatting guide; the only hazard is a
|
|
92
|
+
* backtick run inside the body CLOSING the fence early. A hardcoded ``` around
|
|
93
|
+
* a body that carries its own ``` ships three delimiters instead of two: the
|
|
94
|
+
* wire opens at the first, closes at the embedded one, reads the remainder as
|
|
95
|
+
* prose, and the trailing delimiter opens a fence that never terminates —
|
|
96
|
+
* `can't find end of Pre entity`, a 400, and a whole-message plain-text
|
|
97
|
+
* resend. Widening the delimiter to one backtick longer than the longest run
|
|
98
|
+
* already present makes the open/close pair the only runs of that width, so
|
|
99
|
+
* nothing in the body can match them.
|
|
100
|
+
*
|
|
101
|
+
* This is the canonical home for the rule (as `codeSpanSafe` is for the code
|
|
102
|
+
* SPAN hazard above): the markdown fence (`renderCodeBlock`), the degraded
|
|
103
|
+
* table fence (`degradeToCodeFence`) and the raw-HTML `<pre>` fold
|
|
104
|
+
* (`buildPreBlock`, render/parse.ts) all call it, so there is exactly ONE
|
|
105
|
+
* implementation of the width rule rather than a copy per call site.
|
|
106
|
+
*
|
|
107
|
+
* The scan is a LOOP, not `Math.max(0, ...runs.map(…))`. The spread form
|
|
108
|
+
* (which both former copies of this rule used) passes one argument per run,
|
|
109
|
+
* so a body with enough separate backtick runs blows the argument limit with
|
|
110
|
+
* `RangeError: Maximum call stack size exceeded`, throwing out of the render
|
|
111
|
+
* path entirely.
|
|
112
|
+
*
|
|
113
|
+
* The limit is ENGINE-DEPENDENT, so no single number describes it. The
|
|
114
|
+
* shipping runtime is Bun (`telegram-plugin/package.json` `start`,
|
|
115
|
+
* docker/Dockerfile.agent), where it measures at ~639k runs (~1.9 MB body);
|
|
116
|
+
* this file is also collected by the vitest/Node runner, where it is ~125k
|
|
117
|
+
* (~375 KB). Both measured on the pinned toolchain (Bun 1.3.13 — the CI
|
|
118
|
+
* default in .github/actions/setup-switchroom/action.yml — and Node 22), and
|
|
119
|
+
* both move with an engine upgrade.
|
|
120
|
+
*
|
|
121
|
+
* We deliberately do NOT rely on that threshold. Input here is unbounded:
|
|
122
|
+
* `renderOutboundChunks` renders the WHOLE raw body before any
|
|
123
|
+
* `splitMarkdownChunks` (render/rich-render.ts:146), so the fence scan is not
|
|
124
|
+
* bounded by the 32768-character rich cap. Being honest about the size: on
|
|
125
|
+
* Bun a ~1.9 MB single message is an unlikely body, so there this is a latent
|
|
126
|
+
* crash rather than a routine one. The loop is O(n) either way and cannot
|
|
127
|
+
* throw, so correctness costs nothing and the reachability argument does not
|
|
128
|
+
* have to be won.
|
|
129
|
+
*/
|
|
130
|
+
export function codeFenceFor(text: string): string {
|
|
131
|
+
let longestRun = 0
|
|
132
|
+
let current = 0
|
|
133
|
+
for (let i = 0; i < text.length; i++) {
|
|
134
|
+
if (text[i] === '`') {
|
|
135
|
+
current++
|
|
136
|
+
if (current > longestRun) longestRun = current
|
|
137
|
+
} else {
|
|
138
|
+
current = 0
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return '`'.repeat(Math.max(3, longestRun + 1))
|
|
142
|
+
}
|
|
143
|
+
|
|
88
144
|
/**
|
|
89
145
|
* Make a URL safe to interpolate as the destination of a `[label](href)`
|
|
90
146
|
* inline link.
|