switchroom 0.18.18 → 0.18.20
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/ms-365-write-pretool.mjs +92 -20
- package/dist/cli/switchroom.js +36 -6
- package/dist/host-control/main.js +1 -1
- package/package.json +1 -1
- package/telegram-plugin/answer-ready-flush.ts +187 -0
- package/telegram-plugin/dist/gateway/gateway.js +1131 -285
- package/telegram-plugin/dist/server.js +6 -0
- package/telegram-plugin/format.ts +208 -125
- package/telegram-plugin/gateway/cron-session.ts +32 -0
- package/telegram-plugin/gateway/gateway.ts +800 -107
- package/telegram-plugin/gateway/idle-clear.ts +170 -0
- package/telegram-plugin/gateway/inject-handler.ts +11 -0
- package/telegram-plugin/gateway/outbound-send-path.ts +5 -3
- package/telegram-plugin/gateway/turn-record-status.ts +134 -0
- package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +23 -0
- package/telegram-plugin/hooks/silent-end-scan.mjs +98 -8
- package/telegram-plugin/llm-error-present.ts +68 -30
- package/telegram-plugin/narrative-flush.ts +181 -0
- package/telegram-plugin/pending-work-progress.ts +65 -1
- package/telegram-plugin/session-tail.ts +6 -1
- package/telegram-plugin/silent-end.ts +182 -0
- package/telegram-plugin/subagent-watcher.ts +244 -81
- package/telegram-plugin/tests/answer-ready-flush.test.ts +343 -0
- package/telegram-plugin/tests/cron-inject-idle-clock.test.ts +54 -0
- package/telegram-plugin/tests/emission-authority-facade.test.ts +13 -10
- package/telegram-plugin/tests/format-consistency.test.ts +39 -4
- package/telegram-plugin/tests/gateway-outbound-redact.test.ts +26 -0
- package/telegram-plugin/tests/idle-clear.test.ts +315 -37
- package/telegram-plugin/tests/llm-error-present.test.ts +110 -9
- package/telegram-plugin/tests/narrative-flush.test.ts +213 -0
- package/telegram-plugin/tests/narrative-splice-before-finalize.test.ts +167 -0
- package/telegram-plugin/tests/outbound-send-path.test.ts +2 -0
- package/telegram-plugin/tests/paragraph-spacer-golden.test.ts +150 -0
- package/telegram-plugin/tests/per-topic-current-turn.test.ts +4 -1
- package/telegram-plugin/tests/silent-end-interrupt-stop-scan.test.ts +194 -0
- package/telegram-plugin/tests/silent-end.test.ts +296 -0
- package/telegram-plugin/tests/subagent-watcher-narrative-early-paint.test.ts +218 -0
- package/telegram-plugin/tests/telegram-format.test.ts +72 -4
- package/telegram-plugin/tests/turn-record-status.test.ts +119 -0
- package/telegram-plugin/tests/worker-feed-coalesce.test.ts +218 -1
- package/telegram-plugin/tests/worker-feed-terminal-cleanup.test.ts +254 -0
- package/telegram-plugin/tests/worker-feed-terminal-state-truthful.test.ts +125 -0
- package/telegram-plugin/tool-activity-summary.ts +78 -16
- package/telegram-plugin/turn-flush-safety.ts +2 -1
- package/telegram-plugin/worker-activity-feed.ts +181 -30
|
@@ -6835,12 +6835,13 @@ function addParagraphSpacers(text) {
|
|
|
6835
6835
|
|
|
6836
6836
|
`))
|
|
6837
6837
|
return restore(masked);
|
|
6838
|
-
const
|
|
6839
|
-
const isBlankLine = (line) => /^[ \t\r\f\v]*$/.test(line);
|
|
6838
|
+
const SP = PARAGRAPH_SPACER;
|
|
6840
6839
|
const asciiTrim = (line) => line.replace(/^[ \t\r\f\v]+|[ \t\r\f\v]+$/g, "");
|
|
6840
|
+
const isBlankish = (line) => {
|
|
6841
|
+
const t = asciiTrim(line);
|
|
6842
|
+
return t === "" || t === SP;
|
|
6843
|
+
};
|
|
6841
6844
|
const blockKind = (line) => {
|
|
6842
|
-
if (asciiTrim(line) === spacerLine)
|
|
6843
|
-
return "spacer";
|
|
6844
6845
|
if (isFenceOpenLine(line, placeholder))
|
|
6845
6846
|
return "fence";
|
|
6846
6847
|
if (isListItemLine(line))
|
|
@@ -6865,58 +6866,65 @@ function addParagraphSpacers(text) {
|
|
|
6865
6866
|
const shouldSpaceGap = (above, below) => {
|
|
6866
6867
|
const a = blockKind(above);
|
|
6867
6868
|
const b = blockKind(below);
|
|
6868
|
-
if (a === "spacer" || b === "spacer")
|
|
6869
|
-
return false;
|
|
6870
6869
|
if (a === b && SAME_KIND_TIGHT.has(a))
|
|
6871
6870
|
return false;
|
|
6872
6871
|
return true;
|
|
6873
6872
|
};
|
|
6874
|
-
const
|
|
6875
|
-
`
|
|
6873
|
+
const toks = [];
|
|
6874
|
+
for (const line of masked.split(`
|
|
6875
|
+
`)) {
|
|
6876
|
+
const kind = isBlankish(line) ? "gap" : "content";
|
|
6877
|
+
const last = toks[toks.length - 1];
|
|
6878
|
+
if (last && last.kind === kind)
|
|
6879
|
+
last.lines.push(line);
|
|
6880
|
+
else
|
|
6881
|
+
toks.push({ kind, lines: [line] });
|
|
6882
|
+
}
|
|
6876
6883
|
const out = [];
|
|
6877
|
-
for (let i = 0;i <
|
|
6878
|
-
const
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6884
|
+
for (let i = 0;i < toks.length; i++) {
|
|
6885
|
+
const tok = toks[i];
|
|
6886
|
+
if (tok.kind === "content") {
|
|
6887
|
+
out.push(...tok.lines);
|
|
6888
|
+
continue;
|
|
6889
|
+
}
|
|
6890
|
+
const prev = toks[i - 1];
|
|
6891
|
+
const next = toks[i + 1];
|
|
6892
|
+
if (prev?.kind === "content" && next?.kind === "content") {
|
|
6893
|
+
const above = prev.lines[prev.lines.length - 1];
|
|
6894
|
+
const below = next.lines[0];
|
|
6895
|
+
if (shouldSpaceGap(above, below))
|
|
6896
|
+
out.push("", SP, "");
|
|
6897
|
+
else
|
|
6898
|
+
out.push("");
|
|
6899
|
+
} else {
|
|
6900
|
+
out.push(...tok.lines);
|
|
6894
6901
|
}
|
|
6895
|
-
out.push(line);
|
|
6896
6902
|
}
|
|
6897
6903
|
return restore(out.join(`
|
|
6898
6904
|
`));
|
|
6899
6905
|
}
|
|
6900
|
-
function lastNonBlank(arr, isBlank) {
|
|
6901
|
-
for (let i = arr.length - 1;i >= 0; i--) {
|
|
6902
|
-
if (!isBlank(arr[i]))
|
|
6903
|
-
return arr[i];
|
|
6904
|
-
}
|
|
6905
|
-
return null;
|
|
6906
|
-
}
|
|
6907
|
-
function nextNonBlank(lines, from, isBlank) {
|
|
6908
|
-
for (let i = from;i < lines.length; i++) {
|
|
6909
|
-
if (!isBlank(lines[i]))
|
|
6910
|
-
return lines[i];
|
|
6911
|
-
}
|
|
6912
|
-
return null;
|
|
6913
|
-
}
|
|
6914
6906
|
function normalizePunctuation(text) {
|
|
6915
6907
|
if (!/[\u2014\u2013\u2022\u00b7]/.test(text))
|
|
6916
6908
|
return text;
|
|
6917
6909
|
const nonce = Math.random().toString(36).slice(2);
|
|
6918
6910
|
const { masked, restore } = maskCodeRegions(text, nonce);
|
|
6919
|
-
|
|
6911
|
+
const linkMasks = [];
|
|
6912
|
+
const LINK_MASK_PH = `\x00RML${nonce}_`;
|
|
6913
|
+
const maskedLinks = masked.replace(/(\]\()([^)\n]*)(\))/g, (_m, open, href, close) => {
|
|
6914
|
+
const idx = linkMasks.length;
|
|
6915
|
+
linkMasks.push(href);
|
|
6916
|
+
return `${open}${LINK_MASK_PH}${idx}\x00${close}`;
|
|
6917
|
+
});
|
|
6918
|
+
const maskedAutolinks = maskedLinks.replace(/(<)([a-zA-Z][a-zA-Z0-9+.-]*:[^>\s]*)(>)/g, (_m, lt, uri, gt) => {
|
|
6919
|
+
const idx = linkMasks.length;
|
|
6920
|
+
linkMasks.push(uri);
|
|
6921
|
+
return `${lt}${LINK_MASK_PH}${idx}\x00${gt}`;
|
|
6922
|
+
});
|
|
6923
|
+
const escNonce = nonce.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6924
|
+
const linkRestoreRe = new RegExp(`\x00RML${escNonce}_(\\d+)\x00`, "g");
|
|
6925
|
+
const restoreLinks = (s) => s.replace(linkRestoreRe, (_m, idx) => linkMasks[Number(idx)] ?? _m);
|
|
6926
|
+
let out = maskedAutolinks.replace(/(\S)[ \t][\u2014\u2013][ \t](?=(\S))/g, (_m, a, b) => /\d/.test(a) && /\d/.test(b) ? `${a}-` : `${a}, `).replace(/(\w)\u2014(?=(\w))/g, (_m, a, b) => /\d/.test(a) && /\d/.test(b) ? `${a}-` : `${a}, `).replace(/(\w)\u2013(?=\w)/g, "$1-");
|
|
6927
|
+
out = restoreLinks(out);
|
|
6920
6928
|
out = out.split(`
|
|
6921
6929
|
`).map((line) => line.replace(/^([ \t]*)[\u2022\u00b7][ \t]+/, "$1- ")).join(`
|
|
6922
6930
|
`);
|
|
@@ -7109,6 +7117,7 @@ function splitMarkdownChunks(text, maxLen = RICH_MESSAGE_MAX_CHARS) {
|
|
|
7109
7117
|
}
|
|
7110
7118
|
cut = backOffOpenFence(rest, cut);
|
|
7111
7119
|
cut = backOffTableRow(rest, cut);
|
|
7120
|
+
cut = backOffOpenInline(rest, cut);
|
|
7112
7121
|
if (cut <= 0) {
|
|
7113
7122
|
const sliced = hardSliceToCap(rest, maxLen);
|
|
7114
7123
|
chunks.push(stripBoundarySpacers(sliced[0], "trailing"));
|
|
@@ -7154,7 +7163,38 @@ function backOffTableRow(text, cut) {
|
|
|
7154
7163
|
}
|
|
7155
7164
|
return cut;
|
|
7156
7165
|
}
|
|
7157
|
-
|
|
7166
|
+
function backOffOpenInline(text, cut) {
|
|
7167
|
+
if (cut <= 0 || cut >= text.length)
|
|
7168
|
+
return cut;
|
|
7169
|
+
let earliest = cut;
|
|
7170
|
+
for (const re of INLINE_SPAN_PATTERNS) {
|
|
7171
|
+
re.lastIndex = 0;
|
|
7172
|
+
let m;
|
|
7173
|
+
while ((m = re.exec(text)) !== null) {
|
|
7174
|
+
const start = m.index;
|
|
7175
|
+
const end = start + m[0].length;
|
|
7176
|
+
if (start < cut && cut < end && start < earliest)
|
|
7177
|
+
earliest = start;
|
|
7178
|
+
if (start >= cut)
|
|
7179
|
+
break;
|
|
7180
|
+
if (re.lastIndex === start)
|
|
7181
|
+
re.lastIndex = start + 1;
|
|
7182
|
+
}
|
|
7183
|
+
}
|
|
7184
|
+
return earliest;
|
|
7185
|
+
}
|
|
7186
|
+
var RICH_MESSAGE_MAX_CHARS = 32768, PARAGRAPH_SPACER = "\u00a0", INLINE_SPAN_PATTERNS;
|
|
7187
|
+
var init_format = __esm(() => {
|
|
7188
|
+
INLINE_SPAN_PATTERNS = [
|
|
7189
|
+
/`[^`\n]+`/g,
|
|
7190
|
+
/\*\*\*[^*\n]+\*\*\*/g,
|
|
7191
|
+
/___[^_\n]+___/g,
|
|
7192
|
+
/\*\*[^*\n]+\*\*/g,
|
|
7193
|
+
/__[^_\n]+__/g,
|
|
7194
|
+
/(?<![\w*])_[^_\n]+_(?![\w*])/g,
|
|
7195
|
+
/\[[^\]\n]*\]\([^)\n]*\)/g
|
|
7196
|
+
];
|
|
7197
|
+
});
|
|
7158
7198
|
|
|
7159
7199
|
// text-voice-scrub.ts
|
|
7160
7200
|
function enabled() {
|
|
@@ -7339,6 +7379,7 @@ function cleanWorkerResultParagraph(s) {
|
|
|
7339
7379
|
return kept.join(" ").replace(/\s+/g, " ").trim();
|
|
7340
7380
|
}
|
|
7341
7381
|
var init_card_format = __esm(() => {
|
|
7382
|
+
init_format();
|
|
7342
7383
|
init_text_voice_scrub();
|
|
7343
7384
|
});
|
|
7344
7385
|
|
|
@@ -7480,6 +7521,7 @@ function ttlMsFromToken(token) {
|
|
|
7480
7521
|
}
|
|
7481
7522
|
var import_grammy3;
|
|
7482
7523
|
var init_approval_card = __esm(() => {
|
|
7524
|
+
init_format();
|
|
7483
7525
|
import_grammy3 = __toESM(require_mod2(), 1);
|
|
7484
7526
|
});
|
|
7485
7527
|
|
|
@@ -36885,6 +36927,7 @@ function parseConfigApprovalCallback(data) {
|
|
|
36885
36927
|
var pending, TELEGRAM_SENDMESSAGE_LIMIT2 = 32768, RENDERED_BODY_CAP2 = 32000, REASON_MAX_CHARS = 500, REASON_ELLIPSIS = "\u2026", DIFF_SENTINEL = `
|
|
36886
36928
|
[\u2026 diff continues, see attached file]`;
|
|
36887
36929
|
var init_config_approval_handler = __esm(() => {
|
|
36930
|
+
init_format();
|
|
36888
36931
|
pending = new Map;
|
|
36889
36932
|
});
|
|
36890
36933
|
|
|
@@ -37009,6 +37052,7 @@ ${detail}`)));
|
|
|
37009
37052
|
});
|
|
37010
37053
|
}
|
|
37011
37054
|
var init_approvals_commands = __esm(() => {
|
|
37055
|
+
init_format();
|
|
37012
37056
|
init_rich_send();
|
|
37013
37057
|
init_client3();
|
|
37014
37058
|
});
|
|
@@ -39778,6 +39822,7 @@ class DeferredDoneReactions {
|
|
|
39778
39822
|
init_card_format();
|
|
39779
39823
|
|
|
39780
39824
|
// status-no-truncate.ts
|
|
39825
|
+
init_format();
|
|
39781
39826
|
var STATUS_ROLLING_LINES = 5;
|
|
39782
39827
|
var STATUS_LINE_MAX = 200;
|
|
39783
39828
|
var STATUS_CARD_CHAR_BUDGET = RICH_MESSAGE_MAX_CHARS;
|
|
@@ -40252,27 +40297,43 @@ function renderActivityFeedWithNested(lines, childLines, final = false, liveSuff
|
|
|
40252
40297
|
});
|
|
40253
40298
|
}
|
|
40254
40299
|
var COMBINED_ROW_DESC_MAX = 72;
|
|
40300
|
+
var MAX_COMBINED_BODY_LINES = 13;
|
|
40301
|
+
var PER_WORKER_HEADER_COST = 1;
|
|
40302
|
+
function combinedHistoryDepth(w) {
|
|
40303
|
+
if (w <= 0)
|
|
40304
|
+
return 1;
|
|
40305
|
+
const raw = Math.floor((MAX_COMBINED_BODY_LINES - PER_WORKER_HEADER_COST * w) / w);
|
|
40306
|
+
return Math.max(1, Math.min(STATUS_ROLLING_LINES, raw));
|
|
40307
|
+
}
|
|
40255
40308
|
function renderCombinedWorkerFeed(rows, opts) {
|
|
40256
40309
|
if (rows.length === 0)
|
|
40257
40310
|
return null;
|
|
40258
40311
|
const maxRows = Math.max(1, Math.floor(opts.maxRows));
|
|
40259
|
-
const
|
|
40312
|
+
const rowHeader = (r) => {
|
|
40260
40313
|
const desc = escapeMarkdown(truncate(stripMarkdown(r.description).replace(/\s+/g, " ").trim() || "background task", COMBINED_ROW_DESC_MAX));
|
|
40261
40314
|
const toolWord = r.toolCount === 1 ? "tool" : "tools";
|
|
40262
40315
|
const modelLabel = formatModelLabel(r.model);
|
|
40263
40316
|
const modelPart = modelLabel != null ? ` \u00b7 ${escapeMarkdown(modelLabel)}` : "";
|
|
40264
|
-
|
|
40265
|
-
|
|
40266
|
-
|
|
40267
|
-
|
|
40317
|
+
return `**${desc}** _\u00b7 ${formatFeedElapsed(r.elapsedMs)} \u00b7 ${r.toolCount} ${toolWord}${modelPart}_`;
|
|
40318
|
+
};
|
|
40319
|
+
const rowHistory = (r) => {
|
|
40320
|
+
const src = r.historyLines != null && r.historyLines.length > 0 ? r.historyLines : [r.currentStep];
|
|
40321
|
+
return src.filter((s) => s != null && stripMarkdown(s).replace(/\s+/g, " ").trim().length > 0);
|
|
40268
40322
|
};
|
|
40269
40323
|
const compose = (visibleCount) => {
|
|
40270
40324
|
const shown = rows.slice(0, visibleCount);
|
|
40271
40325
|
const hidden = rows.length - shown.length;
|
|
40326
|
+
const depth = combinedHistoryDepth(shown.length);
|
|
40272
40327
|
const out = [`\uD83D\uDEE0 **Workers** \u00b7 _${rows.length} running_`];
|
|
40273
40328
|
for (const r of shown) {
|
|
40274
|
-
|
|
40275
|
-
|
|
40329
|
+
out.push(rowHeader(r));
|
|
40330
|
+
const hist = rowHistory(r);
|
|
40331
|
+
if (hist.length === 0) {
|
|
40332
|
+
out.push("\u2192 _starting\u2026_");
|
|
40333
|
+
continue;
|
|
40334
|
+
}
|
|
40335
|
+
const esc = hist.slice(-depth).map(escapeStepLine);
|
|
40336
|
+
renderStepFeed(out, esc, false);
|
|
40276
40337
|
}
|
|
40277
40338
|
if (hidden > 0)
|
|
40278
40339
|
out.push(`_+${hidden} more working\u2026_`);
|
|
@@ -40349,7 +40410,7 @@ function isWorkerActivityFeedEnabled(envVal) {
|
|
|
40349
40410
|
var DESC_MAX = 80;
|
|
40350
40411
|
function renderWorkerActivity(v, liveSuffix = "") {
|
|
40351
40412
|
const desc = truncate(stripMarkdown(v.description).trim() || "background task", DESC_MAX);
|
|
40352
|
-
const finished = v.state === "done" || v.state === "failed";
|
|
40413
|
+
const finished = v.state === "done" || v.state === "failed" || v.state === "incomplete";
|
|
40353
40414
|
const rawSteps = (v.narrativeLines ?? []).filter((s) => s != null && s.trim().length > 0);
|
|
40354
40415
|
let steps = rawSteps;
|
|
40355
40416
|
if (steps.length === 0 && !finished) {
|
|
@@ -40367,7 +40428,7 @@ function renderWorkerActivity(v, liveSuffix = "") {
|
|
|
40367
40428
|
model: v.model
|
|
40368
40429
|
};
|
|
40369
40430
|
let result;
|
|
40370
|
-
if (finished) {
|
|
40431
|
+
if (finished && v.state !== "incomplete") {
|
|
40371
40432
|
const text = cleanWorkerResultParagraph(v.latestSummary);
|
|
40372
40433
|
if (text.length > 0)
|
|
40373
40434
|
result = { emoji: v.state === "done" ? "\u2705" : "\u26a0\ufe0f", text };
|
|
@@ -40421,6 +40482,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
40421
40482
|
const firstPaintMin = opts.firstPaintMinMs ?? 8000;
|
|
40422
40483
|
const heartbeatTickMs = opts.heartbeatTickMs ?? 6000;
|
|
40423
40484
|
const maxRows = Math.max(1, Math.floor(opts.maxRows ?? 8));
|
|
40485
|
+
const staleWorkerTtlMs = Math.max(1, Math.floor(opts.staleWorkerTtlMs ?? 50 * 60000));
|
|
40424
40486
|
const reconcilePinFn = opts.reconcilePin ?? (() => {});
|
|
40425
40487
|
const setIntervalFn = opts.setInterval ?? ((cb, ms) => {
|
|
40426
40488
|
const t = setInterval(cb, ms);
|
|
@@ -40522,6 +40584,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
40522
40584
|
elapsedMs: elapsedFor(r),
|
|
40523
40585
|
toolCount: v.toolCount,
|
|
40524
40586
|
currentStep,
|
|
40587
|
+
historyLines: r.narrative.length > 0 ? [...r.narrative] : undefined,
|
|
40525
40588
|
model: v.model
|
|
40526
40589
|
};
|
|
40527
40590
|
});
|
|
@@ -40641,8 +40704,60 @@ function createWorkerActivityFeed(opts) {
|
|
|
40641
40704
|
log(`worker-feed: edit transient error feed=${g.feedKey}: ${err.message}`);
|
|
40642
40705
|
}
|
|
40643
40706
|
}
|
|
40707
|
+
function finalizeWorker(group, agentId, row, view) {
|
|
40708
|
+
row.finished = true;
|
|
40709
|
+
row.state = view.state;
|
|
40710
|
+
markFinalized(agentId);
|
|
40711
|
+
group.chain = group.chain.then(() => {
|
|
40712
|
+
const others = runningRows(group).filter((w) => w.agentId !== agentId);
|
|
40713
|
+
if (others.length > 0) {
|
|
40714
|
+
removeWorker(group, agentId);
|
|
40715
|
+
syncPin(group);
|
|
40716
|
+
return doRender(group, { force: true });
|
|
40717
|
+
}
|
|
40718
|
+
const recap = { ...view, narrativeLines: [...row.narrative] };
|
|
40719
|
+
return doRender(group, { force: true, terminalRecap: recap, finishingAgentId: agentId });
|
|
40720
|
+
}).catch((err) => {
|
|
40721
|
+
log(`worker-feed: finalize chain error ${agentId}: ${err.message}`);
|
|
40722
|
+
});
|
|
40723
|
+
return group.chain;
|
|
40724
|
+
}
|
|
40725
|
+
function terminateWorker(agentId) {
|
|
40726
|
+
const g = groupOfAgent(agentId);
|
|
40727
|
+
const row = g?.workers.get(agentId);
|
|
40728
|
+
if (g == null || row == null) {
|
|
40729
|
+
markFinalized(agentId);
|
|
40730
|
+
return Promise.resolve();
|
|
40731
|
+
}
|
|
40732
|
+
if (row.finished) {
|
|
40733
|
+
return g.chain;
|
|
40734
|
+
}
|
|
40735
|
+
const lv = row.lastView;
|
|
40736
|
+
const view = {
|
|
40737
|
+
description: lv?.description ?? "background task",
|
|
40738
|
+
lastTool: null,
|
|
40739
|
+
toolCount: lv?.toolCount ?? 0,
|
|
40740
|
+
latestSummary: "",
|
|
40741
|
+
elapsedMs: liveElapsed(row, nowFn()),
|
|
40742
|
+
state: "incomplete",
|
|
40743
|
+
model: lv?.model
|
|
40744
|
+
};
|
|
40745
|
+
return finalizeWorker(g, agentId, row, view);
|
|
40746
|
+
}
|
|
40644
40747
|
function heartbeatTick() {
|
|
40645
40748
|
const now = nowFn();
|
|
40749
|
+
const staleAgentIds = [];
|
|
40750
|
+
for (const g of groups.values()) {
|
|
40751
|
+
for (const row of g.workers.values()) {
|
|
40752
|
+
if (!row.finished && now - row.lastUpdateAt >= staleWorkerTtlMs) {
|
|
40753
|
+
staleAgentIds.push(row.agentId);
|
|
40754
|
+
}
|
|
40755
|
+
}
|
|
40756
|
+
}
|
|
40757
|
+
for (const agentId of staleAgentIds) {
|
|
40758
|
+
log(`worker-feed: TTL reap agent=${agentId} \u2014 no update in ${Math.floor((now - (groupOfAgent(agentId)?.workers.get(agentId)?.lastUpdateAt ?? now)) / 1000)}s (>= ${Math.floor(staleWorkerTtlMs / 1000)}s); force-terminating leaked row`);
|
|
40759
|
+
terminateWorker(agentId);
|
|
40760
|
+
}
|
|
40646
40761
|
for (const g of [...groups.values()]) {
|
|
40647
40762
|
if (g.pendingFinalize != null && now >= g.cooldownUntil) {
|
|
40648
40763
|
const recap = g.pendingFinalize;
|
|
@@ -40728,12 +40843,14 @@ function createWorkerActivityFeed(opts) {
|
|
|
40728
40843
|
lastView: null,
|
|
40729
40844
|
state: "running",
|
|
40730
40845
|
finished: false,
|
|
40846
|
+
lastUpdateAt: nowFn(),
|
|
40731
40847
|
dispatchAtMs: null,
|
|
40732
40848
|
stepStartedAtMs: null
|
|
40733
40849
|
};
|
|
40734
40850
|
g.workers.set(agentId, row);
|
|
40735
40851
|
agentIndex.set(agentId, feedKey);
|
|
40736
40852
|
}
|
|
40853
|
+
row.lastUpdateAt = nowFn();
|
|
40737
40854
|
accumulateNarrative(row, view);
|
|
40738
40855
|
row.state = "running";
|
|
40739
40856
|
row.lastView = { ...view, narrativeLines: [...row.narrative] };
|
|
@@ -40752,23 +40869,10 @@ function createWorkerActivityFeed(opts) {
|
|
|
40752
40869
|
markFinalized(agentId);
|
|
40753
40870
|
return Promise.resolve();
|
|
40754
40871
|
}
|
|
40755
|
-
row
|
|
40756
|
-
|
|
40757
|
-
|
|
40758
|
-
|
|
40759
|
-
group.chain = group.chain.then(() => {
|
|
40760
|
-
const others = runningRows(group).filter((w) => w.agentId !== agentId);
|
|
40761
|
-
if (others.length > 0) {
|
|
40762
|
-
removeWorker(group, agentId);
|
|
40763
|
-
syncPin(group);
|
|
40764
|
-
return doRender(group, { force: true });
|
|
40765
|
-
}
|
|
40766
|
-
const recap = { ...view, narrativeLines: [...row.narrative] };
|
|
40767
|
-
return doRender(group, { force: true, terminalRecap: recap, finishingAgentId: agentId });
|
|
40768
|
-
}).catch((err) => {
|
|
40769
|
-
log(`worker-feed: finish chain error ${agentId}: ${err.message}`);
|
|
40770
|
-
});
|
|
40771
|
-
return group.chain;
|
|
40872
|
+
return finalizeWorker(g, agentId, row, view);
|
|
40873
|
+
},
|
|
40874
|
+
terminate(agentId) {
|
|
40875
|
+
return terminateWorker(agentId);
|
|
40772
40876
|
},
|
|
40773
40877
|
drop(agentId) {
|
|
40774
40878
|
markFinalized(agentId);
|
|
@@ -41247,6 +41351,7 @@ var import_grammy2 = __toESM(require_mod2(), 1);
|
|
|
41247
41351
|
var import_runner = __toESM(require_mod4(), 1);
|
|
41248
41352
|
import { AsyncLocalStorage } from "async_hooks";
|
|
41249
41353
|
init_flood_circuit_breaker();
|
|
41354
|
+
init_format();
|
|
41250
41355
|
|
|
41251
41356
|
// shared/gw-trace-gate.ts
|
|
41252
41357
|
function computeGwTraceVerbose(flag) {
|
|
@@ -41281,6 +41386,7 @@ function escapeHtmlForTg(text) {
|
|
|
41281
41386
|
|
|
41282
41387
|
// gateway/vault-request-access-card.ts
|
|
41283
41388
|
init_approval_card();
|
|
41389
|
+
init_format();
|
|
41284
41390
|
function renderVaultRequestAccessCard(req) {
|
|
41285
41391
|
const lines = [];
|
|
41286
41392
|
const scopeLabel = req.scope === "write" ? "write" : "read";
|
|
@@ -45070,6 +45176,9 @@ function runSilentTurnHeartbeatTick(view, deps) {
|
|
|
45070
45176
|
|
|
45071
45177
|
// narrative-dedup.ts
|
|
45072
45178
|
var REPLY_TOOLS = new Set(["reply", "stream_reply"]);
|
|
45179
|
+
|
|
45180
|
+
// narrative-dedup.ts
|
|
45181
|
+
var REPLY_TOOLS2 = new Set(["reply", "stream_reply"]);
|
|
45073
45182
|
function normalizeNarrative(s) {
|
|
45074
45183
|
return s.replace(/[*_`>#~]/g, "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
45075
45184
|
}
|
|
@@ -45089,6 +45198,83 @@ function isDraftOfReply(textBlock, replyText) {
|
|
|
45089
45198
|
return prefixSimilarity(textBlock, replyText) >= DRAFT_SUPPRESS_THRESHOLD;
|
|
45090
45199
|
}
|
|
45091
45200
|
|
|
45201
|
+
// narrative-flush.ts
|
|
45202
|
+
var PENDING_NARRATIVE_FLUSH_MS = 250;
|
|
45203
|
+
|
|
45204
|
+
class NarrativeFlushController {
|
|
45205
|
+
effects;
|
|
45206
|
+
scheduler;
|
|
45207
|
+
flushMs;
|
|
45208
|
+
pending = null;
|
|
45209
|
+
timerShown = null;
|
|
45210
|
+
constructor(effects, scheduler, flushMs) {
|
|
45211
|
+
this.effects = effects;
|
|
45212
|
+
this.scheduler = scheduler;
|
|
45213
|
+
this.flushMs = flushMs;
|
|
45214
|
+
}
|
|
45215
|
+
get pendingText() {
|
|
45216
|
+
return this.pending;
|
|
45217
|
+
}
|
|
45218
|
+
get timerShownText() {
|
|
45219
|
+
return this.timerShown;
|
|
45220
|
+
}
|
|
45221
|
+
stage(text) {
|
|
45222
|
+
this.scheduler.disarm();
|
|
45223
|
+
if (this.pending != null) {
|
|
45224
|
+
this.effects.show(this.pending);
|
|
45225
|
+
}
|
|
45226
|
+
this.pending = text;
|
|
45227
|
+
this.scheduler.arm(() => this.onTimerFire(), this.flushMs);
|
|
45228
|
+
}
|
|
45229
|
+
onTimerFire() {
|
|
45230
|
+
if (this.pending == null)
|
|
45231
|
+
return;
|
|
45232
|
+
const text = this.pending;
|
|
45233
|
+
this.pending = null;
|
|
45234
|
+
this.timerShown = text;
|
|
45235
|
+
this.effects.show(text);
|
|
45236
|
+
}
|
|
45237
|
+
resolveOnTool(toolName, input) {
|
|
45238
|
+
this.scheduler.disarm();
|
|
45239
|
+
const replyText = REPLY_TOOLS2.has(toolName) && typeof input?.text === "string" ? input.text : null;
|
|
45240
|
+
if (replyText != null)
|
|
45241
|
+
this.maybeRetract(replyText);
|
|
45242
|
+
const pending = this.pending;
|
|
45243
|
+
if (pending == null)
|
|
45244
|
+
return;
|
|
45245
|
+
this.pending = null;
|
|
45246
|
+
if (replyText != null && isDraftOfReply(pending, replyText))
|
|
45247
|
+
return;
|
|
45248
|
+
this.effects.show(pending);
|
|
45249
|
+
}
|
|
45250
|
+
flushAtTurnEnd(lastReplyText) {
|
|
45251
|
+
this.scheduler.disarm();
|
|
45252
|
+
if (lastReplyText.length > 0)
|
|
45253
|
+
this.maybeRetract(lastReplyText);
|
|
45254
|
+
const pending = this.pending;
|
|
45255
|
+
if (pending == null)
|
|
45256
|
+
return;
|
|
45257
|
+
this.pending = null;
|
|
45258
|
+
if (lastReplyText.length > 0 && isDraftOfReply(pending, lastReplyText))
|
|
45259
|
+
return;
|
|
45260
|
+
this.effects.show(pending);
|
|
45261
|
+
}
|
|
45262
|
+
teardown() {
|
|
45263
|
+
this.scheduler.disarm();
|
|
45264
|
+
this.pending = null;
|
|
45265
|
+
this.timerShown = null;
|
|
45266
|
+
}
|
|
45267
|
+
maybeRetract(replyText) {
|
|
45268
|
+
const shown = this.timerShown;
|
|
45269
|
+
if (shown == null)
|
|
45270
|
+
return;
|
|
45271
|
+
if (!isDraftOfReply(shown, replyText))
|
|
45272
|
+
return;
|
|
45273
|
+
this.timerShown = null;
|
|
45274
|
+
this.effects.retractShown(shown);
|
|
45275
|
+
}
|
|
45276
|
+
}
|
|
45277
|
+
|
|
45092
45278
|
// tool-labels.ts
|
|
45093
45279
|
var MAX_LABEL_CHARS = 60;
|
|
45094
45280
|
var MAX_BASH_CHARS = 40;
|
|
@@ -54817,6 +55003,7 @@ function parse2(markdown) {
|
|
|
54817
55003
|
}
|
|
54818
55004
|
|
|
54819
55005
|
// render/render.ts
|
|
55006
|
+
init_format();
|
|
54820
55007
|
function renderInline(node2, ctx = {}) {
|
|
54821
55008
|
switch (node2.type) {
|
|
54822
55009
|
case "plain":
|
|
@@ -55022,6 +55209,7 @@ function renderSafe(doc, source, maxLen = RICH_MESSAGE_MAX_CHARS) {
|
|
|
55022
55209
|
}
|
|
55023
55210
|
|
|
55024
55211
|
// render/rich-render.ts
|
|
55212
|
+
init_format();
|
|
55025
55213
|
var PLAIN_TEXT_MAX_CHARS = 4096;
|
|
55026
55214
|
function parseRichRenderEnabled(raw) {
|
|
55027
55215
|
if (raw == null)
|
|
@@ -55307,6 +55495,10 @@ function handlePtyPartialPure(text4, state, deps) {
|
|
|
55307
55495
|
stream.update(text4).catch(() => {});
|
|
55308
55496
|
return created ? "update-new" : "update-existing";
|
|
55309
55497
|
}
|
|
55498
|
+
|
|
55499
|
+
// stream-reply-handler.ts
|
|
55500
|
+
init_format();
|
|
55501
|
+
|
|
55310
55502
|
// chat-lock.ts
|
|
55311
55503
|
function createChatLock() {
|
|
55312
55504
|
const chains = new Map;
|
|
@@ -56401,6 +56593,7 @@ var import_runner2 = __toESM(require_mod4(), 1);
|
|
|
56401
56593
|
import { createHash as createHash2 } from "crypto";
|
|
56402
56594
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
56403
56595
|
init_flood_circuit_breaker();
|
|
56596
|
+
init_format();
|
|
56404
56597
|
var tgPostTagStore2 = new AsyncLocalStorage2;
|
|
56405
56598
|
function _getTgPostTags() {
|
|
56406
56599
|
return tgPostTagStore2.getStore();
|
|
@@ -61497,6 +61690,7 @@ function decideOverPing(input) {
|
|
|
61497
61690
|
}
|
|
61498
61691
|
|
|
61499
61692
|
// silent-reply-anchor.ts
|
|
61693
|
+
init_format();
|
|
61500
61694
|
var TELEGRAM_MSG_CAP = RICH_MESSAGE_MAX_CHARS;
|
|
61501
61695
|
function enabled3() {
|
|
61502
61696
|
const v = process.env.SWITCHROOM_DISABLE_SILENT_REPLY_AUTOEDIT;
|
|
@@ -61813,6 +62007,7 @@ function startTimer(deps) {
|
|
|
61813
62007
|
var EDIT_INTERVAL_MS = 60000;
|
|
61814
62008
|
var POLL_INTERVAL_MS = 5000;
|
|
61815
62009
|
var MAX_LIFETIME_MS = 30 * 60000;
|
|
62010
|
+
var BACKGROUND_WORK_SUPPRESS_TTL_MS = MAX_LIFETIME_MS;
|
|
61816
62011
|
var TELEGRAM_MSG_CAP2 = 32768;
|
|
61817
62012
|
var SUFFIX_RE = /\n\n(?:\u2014 |_)still working \(\d+m\)( \u00b7 message me anytime, I'll keep you posted)?_?$/;
|
|
61818
62013
|
var stateByKey = new Map;
|
|
@@ -61830,6 +62025,7 @@ function ensure(key) {
|
|
|
61830
62025
|
if (!s) {
|
|
61831
62026
|
s = {
|
|
61832
62027
|
pending: false,
|
|
62028
|
+
dispatchedAt: null,
|
|
61833
62029
|
anchorMessageId: null,
|
|
61834
62030
|
anchorOriginalText: "",
|
|
61835
62031
|
anchorLiteralText: false,
|
|
@@ -61843,7 +62039,9 @@ function ensure(key) {
|
|
|
61843
62039
|
function noteAsyncDispatch(key) {
|
|
61844
62040
|
if (!enabled4())
|
|
61845
62041
|
return;
|
|
61846
|
-
ensure(key)
|
|
62042
|
+
const s = ensure(key);
|
|
62043
|
+
s.pending = true;
|
|
62044
|
+
s.dispatchedAt = nowMs();
|
|
61847
62045
|
}
|
|
61848
62046
|
function noteOutbound3(key, opts) {
|
|
61849
62047
|
if (!enabled4())
|
|
@@ -61873,6 +62071,17 @@ function noteTurnEnd(key) {
|
|
|
61873
62071
|
function hasPendingAsyncDispatch(key) {
|
|
61874
62072
|
return stateByKey.get(key)?.pending === true;
|
|
61875
62073
|
}
|
|
62074
|
+
function anyPendingAsyncDispatchWithin(ttlMs) {
|
|
62075
|
+
if (ttlMs <= 0)
|
|
62076
|
+
return false;
|
|
62077
|
+
const now = nowMs();
|
|
62078
|
+
for (const s of stateByKey.values()) {
|
|
62079
|
+
if (s.pending === true && s.dispatchedAt != null && now - s.dispatchedAt < ttlMs) {
|
|
62080
|
+
return true;
|
|
62081
|
+
}
|
|
62082
|
+
}
|
|
62083
|
+
return false;
|
|
62084
|
+
}
|
|
61876
62085
|
function clearPending(key, reason) {
|
|
61877
62086
|
if (!stateByKey.has(key))
|
|
61878
62087
|
return;
|
|
@@ -62040,6 +62249,30 @@ function clearSilentEndState(turnKey, deps) {
|
|
|
62040
62249
|
`);
|
|
62041
62250
|
} catch {}
|
|
62042
62251
|
}
|
|
62252
|
+
var CAPTURED_PROSE_MIN_CHARS = 200;
|
|
62253
|
+
function decideCapturedProseDelivery(args, deps) {
|
|
62254
|
+
const minChars = args.minChars ?? CAPTURED_PROSE_MIN_CHARS;
|
|
62255
|
+
const state3 = readSilentEndState(deps);
|
|
62256
|
+
if (state3 == null)
|
|
62257
|
+
return { deliver: false, reason: "no-state" };
|
|
62258
|
+
if (state3.turnKey !== args.turnKey)
|
|
62259
|
+
return { deliver: false, reason: "turnkey-mismatch" };
|
|
62260
|
+
if (typeof state3.turnId === "string" && state3.turnId !== "" && args.turnId != null && args.turnId !== "" && state3.turnId !== args.turnId) {
|
|
62261
|
+
return { deliver: false, reason: "turnid-mismatch" };
|
|
62262
|
+
}
|
|
62263
|
+
const text4 = typeof state3.pendingText === "string" ? state3.pendingText : "";
|
|
62264
|
+
if (text4.trim().length < minChars)
|
|
62265
|
+
return { deliver: false, reason: "no-substantive-prose" };
|
|
62266
|
+
return { deliver: true, text: text4, reason: "captured-prose" };
|
|
62267
|
+
}
|
|
62268
|
+
function settleCapturedProseDelivery(outcome, effects) {
|
|
62269
|
+
if (outcome === "failed") {
|
|
62270
|
+
return { exhausted: effects.recordUndelivered().exhausted };
|
|
62271
|
+
}
|
|
62272
|
+
effects.closeObligation();
|
|
62273
|
+
effects.clearState();
|
|
62274
|
+
return { exhausted: false };
|
|
62275
|
+
}
|
|
62043
62276
|
function readSilentEndState(deps) {
|
|
62044
62277
|
const statePath = resolveStatePath2(deps);
|
|
62045
62278
|
if (!existsSync14(statePath))
|
|
@@ -62145,6 +62378,56 @@ function isSilentFlushMarker(text4) {
|
|
|
62145
62378
|
return SILENT_MARKERS.has(trimmed.toUpperCase());
|
|
62146
62379
|
}
|
|
62147
62380
|
var TRIVIAL_CONFIRMATIONS = new Set(["SENT", "DONE", "OK", "OKAY", "ACK"]);
|
|
62381
|
+
function isTrivialConfirmationLine(line) {
|
|
62382
|
+
let t = line.trim();
|
|
62383
|
+
if (t.length === 0 || t.length > 8)
|
|
62384
|
+
return false;
|
|
62385
|
+
if (/\W$/.test(t))
|
|
62386
|
+
t = t.slice(0, -1);
|
|
62387
|
+
return TRIVIAL_CONFIRMATIONS.has(t.toUpperCase());
|
|
62388
|
+
}
|
|
62389
|
+
function isCompositeSilentNoise(text4) {
|
|
62390
|
+
if (typeof text4 !== "string")
|
|
62391
|
+
return false;
|
|
62392
|
+
const lines = text4.split(`
|
|
62393
|
+
`).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
62394
|
+
if (lines.length === 0)
|
|
62395
|
+
return false;
|
|
62396
|
+
const hasMarker = lines.some((l) => isSilentFlushMarker(l));
|
|
62397
|
+
if (!hasMarker)
|
|
62398
|
+
return false;
|
|
62399
|
+
return lines.every((l) => isSilentFlushMarker(l) || isTrivialConfirmationLine(l));
|
|
62400
|
+
}
|
|
62401
|
+
function endsWithSilentMarker(text4) {
|
|
62402
|
+
if (typeof text4 !== "string")
|
|
62403
|
+
return false;
|
|
62404
|
+
const lines = text4.split(`
|
|
62405
|
+
`).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
62406
|
+
if (lines.length === 0)
|
|
62407
|
+
return false;
|
|
62408
|
+
return isSilentFlushMarker(lines[lines.length - 1]);
|
|
62409
|
+
}
|
|
62410
|
+
function decideTurnFlush(input) {
|
|
62411
|
+
const flushEnabled = input.flushEnabled !== false;
|
|
62412
|
+
if (!flushEnabled)
|
|
62413
|
+
return { kind: "skip", reason: "flag-disabled" };
|
|
62414
|
+
if (input.replyCalled)
|
|
62415
|
+
return { kind: "skip", reason: "reply-called" };
|
|
62416
|
+
if (input.chatId == null)
|
|
62417
|
+
return { kind: "skip", reason: "no-inbound-chat" };
|
|
62418
|
+
const joined = input.capturedText.join(`
|
|
62419
|
+
|
|
62420
|
+
`).trim();
|
|
62421
|
+
if (joined.length === 0)
|
|
62422
|
+
return { kind: "skip", reason: "empty-text" };
|
|
62423
|
+
if (isSilentFlushMarker(joined))
|
|
62424
|
+
return { kind: "skip", reason: "silent-marker" };
|
|
62425
|
+
if (isCompositeSilentNoise(joined))
|
|
62426
|
+
return { kind: "skip", reason: "silent-marker" };
|
|
62427
|
+
if (endsWithSilentMarker(joined))
|
|
62428
|
+
return { kind: "skip", reason: "silent-marker" };
|
|
62429
|
+
return { kind: "flush", text: joined };
|
|
62430
|
+
}
|
|
62148
62431
|
|
|
62149
62432
|
// answer-stream.ts
|
|
62150
62433
|
var MIN_INITIAL_CHARS = 50;
|
|
@@ -62634,6 +62917,7 @@ function safeResolvePersonName(directory, opts, rawFallback) {
|
|
|
62634
62917
|
}
|
|
62635
62918
|
|
|
62636
62919
|
// gateway/auth-command.ts
|
|
62920
|
+
init_format();
|
|
62637
62921
|
init_auth_snapshot_format();
|
|
62638
62922
|
init_demo_mask();
|
|
62639
62923
|
var AUTH_RM_CONFIRM_TTL_MS = 60000;
|
|
@@ -64783,6 +65067,10 @@ function autoClassifyMidTurnInbound(i) {
|
|
|
64783
65067
|
return { decision: "queue", reason: "cross_topic", sameTopic: false };
|
|
64784
65068
|
return recent && i.msSinceLastAgentOutput <= i.topicSteerWindowMs ? { decision: "steer", reason: "same_topic_recent", sameTopic: true } : { decision: "queue", reason: "same_topic_stale", sameTopic: true };
|
|
64785
65069
|
}
|
|
65070
|
+
|
|
65071
|
+
// operator-events.ts
|
|
65072
|
+
init_format();
|
|
65073
|
+
|
|
64786
65074
|
// raw-error-scrub.ts
|
|
64787
65075
|
function stripRawErrorBytes(raw) {
|
|
64788
65076
|
if (typeof raw !== "string" || raw.length === 0)
|
|
@@ -65352,7 +65640,9 @@ function renderThrottleEscalationNotice(opts) {
|
|
|
65352
65640
|
return `${head}
|
|
65353
65641
|
${tail}`;
|
|
65354
65642
|
}
|
|
65643
|
+
|
|
65355
65644
|
// operator-events.ts
|
|
65645
|
+
init_format();
|
|
65356
65646
|
function classifyClaudeError(raw) {
|
|
65357
65647
|
try {
|
|
65358
65648
|
return classifyInner(raw);
|
|
@@ -65539,6 +65829,26 @@ function formatRelativeTail(deltaMs) {
|
|
|
65539
65829
|
const remH = hours % 24;
|
|
65540
65830
|
return remH > 0 ? `~in ${days}d ${remH}h` : `~in ${days}d`;
|
|
65541
65831
|
}
|
|
65832
|
+
function formatResetClock(resetAt, tz) {
|
|
65833
|
+
if (resetAt == null)
|
|
65834
|
+
return "";
|
|
65835
|
+
const ms = resetAt.getTime();
|
|
65836
|
+
if (!Number.isFinite(ms))
|
|
65837
|
+
return "";
|
|
65838
|
+
return `${fmtLocalClock(ms, tz)} ${tzAbbrev(ms, tz)}`;
|
|
65839
|
+
}
|
|
65840
|
+
function buildRecommendation(parsed, tz) {
|
|
65841
|
+
switch (parsed.kind) {
|
|
65842
|
+
case "auth":
|
|
65843
|
+
return "\u2192 Re-authenticate this account to continue.";
|
|
65844
|
+
case "quota_wall": {
|
|
65845
|
+
const reset2 = formatResetClock(parsed.resetAt, tz);
|
|
65846
|
+
return reset2 ? `\u2192 Switch to another account, or wait for the quota to reset at ${reset2}.` : "\u2192 Switch to another account, or wait for the quota to reset.";
|
|
65847
|
+
}
|
|
65848
|
+
default:
|
|
65849
|
+
return;
|
|
65850
|
+
}
|
|
65851
|
+
}
|
|
65542
65852
|
function renderLlmError(parsed, agent, tz, now = new Date) {
|
|
65543
65853
|
const safeAgent = escapeAgent(agent);
|
|
65544
65854
|
const emoji = kindEmoji(parsed.kind);
|
|
@@ -65548,32 +65858,18 @@ function renderLlmError(parsed, agent, tz, now = new Date) {
|
|
|
65548
65858
|
lines.push(`_${resetLine}_`);
|
|
65549
65859
|
if (parsed.model)
|
|
65550
65860
|
lines.push(`_model: ${escapeAgent(parsed.model)}_`);
|
|
65551
|
-
const
|
|
65552
|
-
|
|
65553
|
-
|
|
65554
|
-
|
|
65555
|
-
|
|
65556
|
-
|
|
65557
|
-
|
|
65558
|
-
|
|
65559
|
-
|
|
65560
|
-
|
|
65561
|
-
|
|
65562
|
-
|
|
65563
|
-
]
|
|
65564
|
-
}
|
|
65565
|
-
};
|
|
65566
|
-
case "quota_wall":
|
|
65567
|
-
return {
|
|
65568
|
-
text: text4,
|
|
65569
|
-
keyboard: {
|
|
65570
|
-
inline_keyboard: [
|
|
65571
|
-
[{ text: "\u23f3 Wait", callback_data: `op:dismiss:${encodeURIComponent(agent)}` }]
|
|
65572
|
-
]
|
|
65573
|
-
}
|
|
65574
|
-
};
|
|
65575
|
-
default:
|
|
65576
|
-
return { text: text4 };
|
|
65861
|
+
const recommendation2 = buildRecommendation(parsed, tz);
|
|
65862
|
+
if (recommendation2)
|
|
65863
|
+
lines.push(recommendation2);
|
|
65864
|
+
return { text: lines.join(`
|
|
65865
|
+
`) };
|
|
65866
|
+
}
|
|
65867
|
+
function renderLlmErrorSafe(parsed, agent, tz, now = new Date) {
|
|
65868
|
+
try {
|
|
65869
|
+
return renderLlmError(parsed, agent, tz, now);
|
|
65870
|
+
} catch {
|
|
65871
|
+
const safeAgent = escapeAgent(agent);
|
|
65872
|
+
return { text: `${kindEmoji(parsed.kind)} ${parsed.coreText} (**${safeAgent}**)` };
|
|
65577
65873
|
}
|
|
65578
65874
|
}
|
|
65579
65875
|
function kindEmoji(kind) {
|
|
@@ -66547,12 +66843,13 @@ function addParagraphSpacers2(text4) {
|
|
|
66547
66843
|
|
|
66548
66844
|
`))
|
|
66549
66845
|
return restore2(masked);
|
|
66550
|
-
const
|
|
66551
|
-
const isBlankLine = (line) => /^[ \t\r\f\v]*$/.test(line);
|
|
66846
|
+
const SP = PARAGRAPH_SPACER2;
|
|
66552
66847
|
const asciiTrim = (line) => line.replace(/^[ \t\r\f\v]+|[ \t\r\f\v]+$/g, "");
|
|
66848
|
+
const isBlankish = (line) => {
|
|
66849
|
+
const t = asciiTrim(line);
|
|
66850
|
+
return t === "" || t === SP;
|
|
66851
|
+
};
|
|
66553
66852
|
const blockKind = (line) => {
|
|
66554
|
-
if (asciiTrim(line) === spacerLine)
|
|
66555
|
-
return "spacer";
|
|
66556
66853
|
if (isFenceOpenLine2(line, placeholder))
|
|
66557
66854
|
return "fence";
|
|
66558
66855
|
if (isListItemLine2(line))
|
|
@@ -66577,58 +66874,65 @@ function addParagraphSpacers2(text4) {
|
|
|
66577
66874
|
const shouldSpaceGap = (above, below) => {
|
|
66578
66875
|
const a = blockKind(above);
|
|
66579
66876
|
const b = blockKind(below);
|
|
66580
|
-
if (a === "spacer" || b === "spacer")
|
|
66581
|
-
return false;
|
|
66582
66877
|
if (a === b && SAME_KIND_TIGHT.has(a))
|
|
66583
66878
|
return false;
|
|
66584
66879
|
return true;
|
|
66585
66880
|
};
|
|
66586
|
-
const
|
|
66587
|
-
`
|
|
66881
|
+
const toks = [];
|
|
66882
|
+
for (const line of masked.split(`
|
|
66883
|
+
`)) {
|
|
66884
|
+
const kind = isBlankish(line) ? "gap" : "content";
|
|
66885
|
+
const last = toks[toks.length - 1];
|
|
66886
|
+
if (last && last.kind === kind)
|
|
66887
|
+
last.lines.push(line);
|
|
66888
|
+
else
|
|
66889
|
+
toks.push({ kind, lines: [line] });
|
|
66890
|
+
}
|
|
66588
66891
|
const out = [];
|
|
66589
|
-
for (let i = 0;i <
|
|
66590
|
-
const
|
|
66591
|
-
|
|
66592
|
-
|
|
66593
|
-
|
|
66594
|
-
|
|
66595
|
-
|
|
66596
|
-
|
|
66597
|
-
|
|
66598
|
-
|
|
66599
|
-
|
|
66600
|
-
|
|
66601
|
-
|
|
66602
|
-
|
|
66603
|
-
|
|
66604
|
-
|
|
66605
|
-
|
|
66892
|
+
for (let i = 0;i < toks.length; i++) {
|
|
66893
|
+
const tok = toks[i];
|
|
66894
|
+
if (tok.kind === "content") {
|
|
66895
|
+
out.push(...tok.lines);
|
|
66896
|
+
continue;
|
|
66897
|
+
}
|
|
66898
|
+
const prev = toks[i - 1];
|
|
66899
|
+
const next = toks[i + 1];
|
|
66900
|
+
if (prev?.kind === "content" && next?.kind === "content") {
|
|
66901
|
+
const above = prev.lines[prev.lines.length - 1];
|
|
66902
|
+
const below = next.lines[0];
|
|
66903
|
+
if (shouldSpaceGap(above, below))
|
|
66904
|
+
out.push("", SP, "");
|
|
66905
|
+
else
|
|
66906
|
+
out.push("");
|
|
66907
|
+
} else {
|
|
66908
|
+
out.push(...tok.lines);
|
|
66606
66909
|
}
|
|
66607
|
-
out.push(line);
|
|
66608
66910
|
}
|
|
66609
66911
|
return restore2(out.join(`
|
|
66610
66912
|
`));
|
|
66611
66913
|
}
|
|
66612
|
-
function lastNonBlank2(arr, isBlank) {
|
|
66613
|
-
for (let i = arr.length - 1;i >= 0; i--) {
|
|
66614
|
-
if (!isBlank(arr[i]))
|
|
66615
|
-
return arr[i];
|
|
66616
|
-
}
|
|
66617
|
-
return null;
|
|
66618
|
-
}
|
|
66619
|
-
function nextNonBlank2(lines, from, isBlank) {
|
|
66620
|
-
for (let i = from;i < lines.length; i++) {
|
|
66621
|
-
if (!isBlank(lines[i]))
|
|
66622
|
-
return lines[i];
|
|
66623
|
-
}
|
|
66624
|
-
return null;
|
|
66625
|
-
}
|
|
66626
66914
|
function normalizePunctuation2(text4) {
|
|
66627
66915
|
if (!/[\u2014\u2013\u2022\u00b7]/.test(text4))
|
|
66628
66916
|
return text4;
|
|
66629
66917
|
const nonce = Math.random().toString(36).slice(2);
|
|
66630
66918
|
const { masked, restore: restore2 } = maskCodeRegions2(text4, nonce);
|
|
66631
|
-
|
|
66919
|
+
const linkMasks = [];
|
|
66920
|
+
const LINK_MASK_PH = `\x00RML${nonce}_`;
|
|
66921
|
+
const maskedLinks = masked.replace(/(\]\()([^)\n]*)(\))/g, (_m, open, href, close) => {
|
|
66922
|
+
const idx = linkMasks.length;
|
|
66923
|
+
linkMasks.push(href);
|
|
66924
|
+
return `${open}${LINK_MASK_PH}${idx}\x00${close}`;
|
|
66925
|
+
});
|
|
66926
|
+
const maskedAutolinks = maskedLinks.replace(/(<)([a-zA-Z][a-zA-Z0-9+.-]*:[^>\s]*)(>)/g, (_m, lt, uri, gt) => {
|
|
66927
|
+
const idx = linkMasks.length;
|
|
66928
|
+
linkMasks.push(uri);
|
|
66929
|
+
return `${lt}${LINK_MASK_PH}${idx}\x00${gt}`;
|
|
66930
|
+
});
|
|
66931
|
+
const escNonce = nonce.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
66932
|
+
const linkRestoreRe = new RegExp(`\x00RML${escNonce}_(\\d+)\x00`, "g");
|
|
66933
|
+
const restoreLinks = (s) => s.replace(linkRestoreRe, (_m, idx) => linkMasks[Number(idx)] ?? _m);
|
|
66934
|
+
let out = maskedAutolinks.replace(/(\S)[ \t][\u2014\u2013][ \t](?=(\S))/g, (_m, a, b) => /\d/.test(a) && /\d/.test(b) ? `${a}-` : `${a}, `).replace(/(\w)\u2014(?=(\w))/g, (_m, a, b) => /\d/.test(a) && /\d/.test(b) ? `${a}-` : `${a}, `).replace(/(\w)\u2013(?=\w)/g, "$1-");
|
|
66935
|
+
out = restoreLinks(out);
|
|
66632
66936
|
out = out.split(`
|
|
66633
66937
|
`).map((line) => line.replace(/^([ \t]*)[\u2022\u00b7][ \t]+/, "$1- ")).join(`
|
|
66634
66938
|
`);
|
|
@@ -66821,6 +67125,7 @@ function splitMarkdownChunks2(text4, maxLen = RICH_MESSAGE_MAX_CHARS2) {
|
|
|
66821
67125
|
}
|
|
66822
67126
|
cut = backOffOpenFence2(rest, cut);
|
|
66823
67127
|
cut = backOffTableRow2(rest, cut);
|
|
67128
|
+
cut = backOffOpenInline2(rest, cut);
|
|
66824
67129
|
if (cut <= 0) {
|
|
66825
67130
|
const sliced = hardSliceToCap2(rest, maxLen);
|
|
66826
67131
|
chunks.push(stripBoundarySpacers2(sliced[0], "trailing"));
|
|
@@ -66866,6 +67171,35 @@ function backOffTableRow2(text4, cut) {
|
|
|
66866
67171
|
}
|
|
66867
67172
|
return cut;
|
|
66868
67173
|
}
|
|
67174
|
+
var INLINE_SPAN_PATTERNS2 = [
|
|
67175
|
+
/`[^`\n]+`/g,
|
|
67176
|
+
/\*\*\*[^*\n]+\*\*\*/g,
|
|
67177
|
+
/___[^_\n]+___/g,
|
|
67178
|
+
/\*\*[^*\n]+\*\*/g,
|
|
67179
|
+
/__[^_\n]+__/g,
|
|
67180
|
+
/(?<![\w*])_[^_\n]+_(?![\w*])/g,
|
|
67181
|
+
/\[[^\]\n]*\]\([^)\n]*\)/g
|
|
67182
|
+
];
|
|
67183
|
+
function backOffOpenInline2(text4, cut) {
|
|
67184
|
+
if (cut <= 0 || cut >= text4.length)
|
|
67185
|
+
return cut;
|
|
67186
|
+
let earliest = cut;
|
|
67187
|
+
for (const re of INLINE_SPAN_PATTERNS2) {
|
|
67188
|
+
re.lastIndex = 0;
|
|
67189
|
+
let m;
|
|
67190
|
+
while ((m = re.exec(text4)) !== null) {
|
|
67191
|
+
const start = m.index;
|
|
67192
|
+
const end = start + m[0].length;
|
|
67193
|
+
if (start < cut && cut < end && start < earliest)
|
|
67194
|
+
earliest = start;
|
|
67195
|
+
if (start >= cut)
|
|
67196
|
+
break;
|
|
67197
|
+
if (re.lastIndex === start)
|
|
67198
|
+
re.lastIndex = start + 1;
|
|
67199
|
+
}
|
|
67200
|
+
}
|
|
67201
|
+
return earliest;
|
|
67202
|
+
}
|
|
66869
67203
|
|
|
66870
67204
|
// rich-send.ts
|
|
66871
67205
|
var import_grammy8 = __toESM(require_mod2(), 1);
|
|
@@ -66984,6 +67318,7 @@ function scrubVoice2(text4) {
|
|
|
66984
67318
|
}
|
|
66985
67319
|
|
|
66986
67320
|
// gateway/outbound-send-path.ts
|
|
67321
|
+
init_format();
|
|
66987
67322
|
init_text_voice_scrub();
|
|
66988
67323
|
function normalizeOutboundBody(rawText, site, redact2) {
|
|
66989
67324
|
let text4 = normalizeParagraphBreaks(repairEscapedWhitespace(rawText));
|
|
@@ -67666,7 +68001,7 @@ function isSilentFlushMarker2(text4) {
|
|
|
67666
68001
|
return SILENT_MARKERS2.has(trimmed.toUpperCase());
|
|
67667
68002
|
}
|
|
67668
68003
|
var TRIVIAL_CONFIRMATIONS2 = new Set(["SENT", "DONE", "OK", "OKAY", "ACK"]);
|
|
67669
|
-
function
|
|
68004
|
+
function isTrivialConfirmationLine2(line) {
|
|
67670
68005
|
let t = line.trim();
|
|
67671
68006
|
if (t.length === 0 || t.length > 8)
|
|
67672
68007
|
return false;
|
|
@@ -67674,7 +68009,7 @@ function isTrivialConfirmationLine(line) {
|
|
|
67674
68009
|
t = t.slice(0, -1);
|
|
67675
68010
|
return TRIVIAL_CONFIRMATIONS2.has(t.toUpperCase());
|
|
67676
68011
|
}
|
|
67677
|
-
function
|
|
68012
|
+
function isCompositeSilentNoise2(text4) {
|
|
67678
68013
|
if (typeof text4 !== "string")
|
|
67679
68014
|
return false;
|
|
67680
68015
|
const lines = text4.split(`
|
|
@@ -67684,9 +68019,9 @@ function isCompositeSilentNoise(text4) {
|
|
|
67684
68019
|
const hasMarker = lines.some((l) => isSilentFlushMarker2(l));
|
|
67685
68020
|
if (!hasMarker)
|
|
67686
68021
|
return false;
|
|
67687
|
-
return lines.every((l) => isSilentFlushMarker2(l) ||
|
|
68022
|
+
return lines.every((l) => isSilentFlushMarker2(l) || isTrivialConfirmationLine2(l));
|
|
67688
68023
|
}
|
|
67689
|
-
function
|
|
68024
|
+
function endsWithSilentMarker2(text4) {
|
|
67690
68025
|
if (typeof text4 !== "string")
|
|
67691
68026
|
return false;
|
|
67692
68027
|
const lines = text4.split(`
|
|
@@ -67695,7 +68030,7 @@ function endsWithSilentMarker(text4) {
|
|
|
67695
68030
|
return false;
|
|
67696
68031
|
return isSilentFlushMarker2(lines[lines.length - 1]);
|
|
67697
68032
|
}
|
|
67698
|
-
function
|
|
68033
|
+
function decideTurnFlush2(input) {
|
|
67699
68034
|
const flushEnabled = input.flushEnabled !== false;
|
|
67700
68035
|
if (!flushEnabled)
|
|
67701
68036
|
return { kind: "skip", reason: "flag-disabled" };
|
|
@@ -67710,9 +68045,9 @@ function decideTurnFlush(input) {
|
|
|
67710
68045
|
return { kind: "skip", reason: "empty-text" };
|
|
67711
68046
|
if (isSilentFlushMarker2(joined))
|
|
67712
68047
|
return { kind: "skip", reason: "silent-marker" };
|
|
67713
|
-
if (
|
|
68048
|
+
if (isCompositeSilentNoise2(joined))
|
|
67714
68049
|
return { kind: "skip", reason: "silent-marker" };
|
|
67715
|
-
if (
|
|
68050
|
+
if (endsWithSilentMarker2(joined))
|
|
67716
68051
|
return { kind: "skip", reason: "silent-marker" };
|
|
67717
68052
|
return { kind: "flush", text: joined };
|
|
67718
68053
|
}
|
|
@@ -67726,6 +68061,72 @@ function isTurnFlushSafetyEnabled(env = process.env) {
|
|
|
67726
68061
|
return true;
|
|
67727
68062
|
}
|
|
67728
68063
|
|
|
68064
|
+
// answer-ready-flush.ts
|
|
68065
|
+
var ANSWER_READY_FLUSH_MS = 1000;
|
|
68066
|
+
function resolveAnswerReadyFlushMs(env) {
|
|
68067
|
+
const raw = env.SWITCHROOM_ANSWER_READY_FLUSH_MS;
|
|
68068
|
+
if (raw == null || raw.trim() === "")
|
|
68069
|
+
return ANSWER_READY_FLUSH_MS;
|
|
68070
|
+
const n = Number(raw);
|
|
68071
|
+
if (!Number.isFinite(n))
|
|
68072
|
+
return ANSWER_READY_FLUSH_MS;
|
|
68073
|
+
if (n <= 0)
|
|
68074
|
+
return 0;
|
|
68075
|
+
return Math.floor(n);
|
|
68076
|
+
}
|
|
68077
|
+
function shouldArmAnswerReadyFlush(input) {
|
|
68078
|
+
if (input.flushWindowMs <= 0)
|
|
68079
|
+
return false;
|
|
68080
|
+
if (input.inFlightToolCount > 0)
|
|
68081
|
+
return false;
|
|
68082
|
+
if (input.hasPendingAsyncDispatch)
|
|
68083
|
+
return false;
|
|
68084
|
+
return decideTurnFlush(input.flush).kind === "flush";
|
|
68085
|
+
}
|
|
68086
|
+
|
|
68087
|
+
class AnswerReadyFlushController {
|
|
68088
|
+
deps;
|
|
68089
|
+
constructor(deps) {
|
|
68090
|
+
this.deps = deps;
|
|
68091
|
+
}
|
|
68092
|
+
get setTimeoutFn() {
|
|
68093
|
+
return this.deps.setTimeoutFn ?? ((fn, ms) => setTimeout(fn, ms));
|
|
68094
|
+
}
|
|
68095
|
+
get clearTimeoutFn() {
|
|
68096
|
+
return this.deps.clearTimeoutFn ?? ((h) => clearTimeout(h));
|
|
68097
|
+
}
|
|
68098
|
+
clear(turn) {
|
|
68099
|
+
if (turn == null)
|
|
68100
|
+
return;
|
|
68101
|
+
const handle = this.deps.getTimerHandle(turn);
|
|
68102
|
+
if (handle != null) {
|
|
68103
|
+
this.clearTimeoutFn(handle);
|
|
68104
|
+
this.deps.setTimerHandle(turn, null);
|
|
68105
|
+
}
|
|
68106
|
+
}
|
|
68107
|
+
reset() {
|
|
68108
|
+
const turn = this.deps.getCurrentTurn();
|
|
68109
|
+
this.clear(turn);
|
|
68110
|
+
if (turn == null)
|
|
68111
|
+
return;
|
|
68112
|
+
const armInput = this.deps.getArmInput(turn);
|
|
68113
|
+
if (!shouldArmAnswerReadyFlush(armInput))
|
|
68114
|
+
return;
|
|
68115
|
+
const handle = this.setTimeoutFn(() => this.onExpiry(turn), armInput.flushWindowMs);
|
|
68116
|
+
this.deps.setTimerHandle(turn, handle);
|
|
68117
|
+
}
|
|
68118
|
+
onExpiry(armedTurn) {
|
|
68119
|
+
const live = this.deps.getCurrentTurn();
|
|
68120
|
+
if (live == null || live !== armedTurn)
|
|
68121
|
+
return;
|
|
68122
|
+
this.deps.setTimerHandle(live, null);
|
|
68123
|
+
if (!shouldArmAnswerReadyFlush(this.deps.getArmInput(live)))
|
|
68124
|
+
return;
|
|
68125
|
+
this.deps.log?.("answer-ready quiescence flush \u2014 delivering composed terminal answer");
|
|
68126
|
+
this.deps.onFlush(live);
|
|
68127
|
+
}
|
|
68128
|
+
}
|
|
68129
|
+
|
|
67729
68130
|
// gateway/turn-end-gate.ts
|
|
67730
68131
|
function decideTurnEndGate(snapshot) {
|
|
67731
68132
|
const { flushDecision, finalAnswerDelivered } = snapshot;
|
|
@@ -69152,11 +69553,12 @@ async function injectSlashCommand(agentName3, command, opts = {}) {
|
|
|
69152
69553
|
session,
|
|
69153
69554
|
command: command.trim(),
|
|
69154
69555
|
settleMs,
|
|
69155
|
-
timeoutMs
|
|
69556
|
+
timeoutMs,
|
|
69557
|
+
precondition: opts.precondition
|
|
69156
69558
|
}));
|
|
69157
69559
|
}
|
|
69158
69560
|
async function injectSlashCommandWith(runner, args) {
|
|
69159
|
-
const { socket, session, command, settleMs, timeoutMs } = args;
|
|
69561
|
+
const { socket, session, command, settleMs, timeoutMs, precondition } = args;
|
|
69160
69562
|
let bareVerb;
|
|
69161
69563
|
try {
|
|
69162
69564
|
bareVerb = validateInjectCommand(command);
|
|
@@ -69186,6 +69588,17 @@ async function injectSlashCommandWith(runner, args) {
|
|
|
69186
69588
|
errorMessage: `tmux session "${session}" on socket "${socket}" not found. ` + `Is the agent running under the tmux supervisor (the default)? ` + `If experimental.legacy_pty=true is set, inject is unsupported.`
|
|
69187
69589
|
};
|
|
69188
69590
|
}
|
|
69591
|
+
if (precondition && !precondition()) {
|
|
69592
|
+
return {
|
|
69593
|
+
outcome: "skipped",
|
|
69594
|
+
output: "",
|
|
69595
|
+
truncated: false,
|
|
69596
|
+
command: bareVerb,
|
|
69597
|
+
meta,
|
|
69598
|
+
errorCode: "precondition_failed",
|
|
69599
|
+
errorMessage: "inject precondition returned false at write time; send aborted " + "(no keys sent)."
|
|
69600
|
+
};
|
|
69601
|
+
}
|
|
69189
69602
|
const before = runner.capture(socket, session) ?? "";
|
|
69190
69603
|
try {
|
|
69191
69604
|
runner.send(socket, session, ["send-keys", "-l", command]);
|
|
@@ -69339,7 +69752,9 @@ function makeTmuxRunner2(tmuxBin) {
|
|
|
69339
69752
|
}
|
|
69340
69753
|
};
|
|
69341
69754
|
}
|
|
69755
|
+
|
|
69342
69756
|
// stream-reply-handler.ts
|
|
69757
|
+
init_format();
|
|
69343
69758
|
function buildAccentHeader(accent) {
|
|
69344
69759
|
switch (accent) {
|
|
69345
69760
|
case "in-progress":
|
|
@@ -69388,6 +69803,12 @@ _truncated_`;
|
|
|
69388
69803
|
}
|
|
69389
69804
|
return { body: verbHtml, accent: "done" };
|
|
69390
69805
|
}
|
|
69806
|
+
if (result.outcome === "skipped") {
|
|
69807
|
+
return {
|
|
69808
|
+
body: `${verbHtml} \u2014 skipped (precondition not met at send time)`,
|
|
69809
|
+
accent: "issue"
|
|
69810
|
+
};
|
|
69811
|
+
}
|
|
69391
69812
|
const code2 = result.errorCode ?? "tmux_failed";
|
|
69392
69813
|
const msg = result.errorMessage ?? "unknown error";
|
|
69393
69814
|
if (code2 === "blocked") {
|
|
@@ -72070,6 +72491,8 @@ function decideIdleClear(state3, now) {
|
|
|
72070
72491
|
return { clear: false };
|
|
72071
72492
|
if (state3.turnInFlight)
|
|
72072
72493
|
return { clear: false };
|
|
72494
|
+
if (state3.backgroundWorkInFlight)
|
|
72495
|
+
return { clear: false };
|
|
72073
72496
|
if (state3.alreadyCleared)
|
|
72074
72497
|
return { clear: false };
|
|
72075
72498
|
if (now - lastEventAt(state3) < state3.idleClearMs)
|
|
@@ -72081,6 +72504,67 @@ function classifyIdleEvent(kind, durationMs) {
|
|
|
72081
72504
|
const synthetic = turnEnded && durationMs === -1;
|
|
72082
72505
|
return { activity: !synthetic, turnEnded };
|
|
72083
72506
|
}
|
|
72507
|
+
|
|
72508
|
+
class IdleTracker {
|
|
72509
|
+
lastActivityAt;
|
|
72510
|
+
lastTurnEndedAt = null;
|
|
72511
|
+
alreadyCleared = false;
|
|
72512
|
+
dispatching = false;
|
|
72513
|
+
constructor(startedAt) {
|
|
72514
|
+
this.lastActivityAt = startedAt;
|
|
72515
|
+
}
|
|
72516
|
+
get activityAt() {
|
|
72517
|
+
return this.lastActivityAt;
|
|
72518
|
+
}
|
|
72519
|
+
get turnEndedAt() {
|
|
72520
|
+
return this.lastTurnEndedAt;
|
|
72521
|
+
}
|
|
72522
|
+
get cleared() {
|
|
72523
|
+
return this.alreadyCleared;
|
|
72524
|
+
}
|
|
72525
|
+
get isDispatching() {
|
|
72526
|
+
return this.dispatching;
|
|
72527
|
+
}
|
|
72528
|
+
noteInbound(now) {
|
|
72529
|
+
this.lastActivityAt = now;
|
|
72530
|
+
this.alreadyCleared = false;
|
|
72531
|
+
}
|
|
72532
|
+
noteEvent(kind, now, durationMs) {
|
|
72533
|
+
const signal = classifyIdleEvent(kind, durationMs);
|
|
72534
|
+
if (signal.activity)
|
|
72535
|
+
this.noteInbound(now);
|
|
72536
|
+
if (signal.turnEnded)
|
|
72537
|
+
this.lastTurnEndedAt = now;
|
|
72538
|
+
}
|
|
72539
|
+
stateFor(inputs, alreadyCleared) {
|
|
72540
|
+
return {
|
|
72541
|
+
lastActivityAt: this.lastActivityAt,
|
|
72542
|
+
lastTurnEndedAt: this.lastTurnEndedAt,
|
|
72543
|
+
idleClearMs: inputs.idleClearMs,
|
|
72544
|
+
alreadyCleared,
|
|
72545
|
+
turnInFlight: inputs.turnInFlight,
|
|
72546
|
+
backgroundWorkInFlight: inputs.backgroundWorkInFlight
|
|
72547
|
+
};
|
|
72548
|
+
}
|
|
72549
|
+
decide(now, inputs) {
|
|
72550
|
+
return decideIdleClear(this.stateFor(inputs, this.alreadyCleared), now);
|
|
72551
|
+
}
|
|
72552
|
+
decideIgnoringLatch(now, inputs) {
|
|
72553
|
+
return decideIdleClear(this.stateFor(inputs, false), now);
|
|
72554
|
+
}
|
|
72555
|
+
markClearFired() {
|
|
72556
|
+
this.alreadyCleared = true;
|
|
72557
|
+
}
|
|
72558
|
+
reArm() {
|
|
72559
|
+
this.alreadyCleared = false;
|
|
72560
|
+
}
|
|
72561
|
+
beginDispatch() {
|
|
72562
|
+
this.dispatching = true;
|
|
72563
|
+
}
|
|
72564
|
+
endDispatch() {
|
|
72565
|
+
this.dispatching = false;
|
|
72566
|
+
}
|
|
72567
|
+
}
|
|
72084
72568
|
function idleDurationToMs(raw) {
|
|
72085
72569
|
const m = /^(\d+)([smh])$/.exec(raw.trim());
|
|
72086
72570
|
if (!m)
|
|
@@ -73169,6 +73653,7 @@ function recordWebhookEvent(rec, deps = {}) {
|
|
|
73169
73653
|
}
|
|
73170
73654
|
|
|
73171
73655
|
// gateway/ipc-server.ts
|
|
73656
|
+
init_format();
|
|
73172
73657
|
import { renameSync as renameSync11, unlinkSync as unlinkSync14, chmodSync as chmodSync9 } from "fs";
|
|
73173
73658
|
var MAX_BUFFER_SIZE = 1024 * 1024;
|
|
73174
73659
|
var VALID_OPERATOR_KINDS = new Set([
|
|
@@ -73874,6 +74359,7 @@ function validateInput(input) {
|
|
|
73874
74359
|
}
|
|
73875
74360
|
|
|
73876
74361
|
// gateway/drive-write-approval.ts
|
|
74362
|
+
init_format();
|
|
73877
74363
|
var DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
73878
74364
|
var MAX_TTL_MS = 30 * 60 * 1000;
|
|
73879
74365
|
var MIN_TTL_MS = 30 * 1000;
|
|
@@ -74004,6 +74490,7 @@ function clampTtl(requested, fallback, min, max) {
|
|
|
74004
74490
|
}
|
|
74005
74491
|
|
|
74006
74492
|
// gateway/ms365-write-approval.ts
|
|
74493
|
+
init_format();
|
|
74007
74494
|
function validateMs365Preview(input) {
|
|
74008
74495
|
if (!input || typeof input !== "object")
|
|
74009
74496
|
return null;
|
|
@@ -74182,6 +74669,7 @@ async function handleRequestMs365Approval(client3, msg, deps) {
|
|
|
74182
74669
|
}
|
|
74183
74670
|
|
|
74184
74671
|
// gateway/diff-preview-card.ts
|
|
74672
|
+
init_format();
|
|
74185
74673
|
var import_grammy10 = __toESM(require_mod2(), 1);
|
|
74186
74674
|
var REQUEST_ID_RE = /^[0-9a-f]{32}$/;
|
|
74187
74675
|
var PENDING_FILE_ID_SENTINEL = "pending-create";
|
|
@@ -74398,6 +74886,9 @@ function cronIdentity(agent) {
|
|
|
74398
74886
|
function isCronIdentity(name) {
|
|
74399
74887
|
return typeof name === "string" && name.endsWith(CRON_IDENTITY_SUFFIX);
|
|
74400
74888
|
}
|
|
74889
|
+
function isCronInjectFire(meta) {
|
|
74890
|
+
return meta?.source === "cron" || meta?.session === "cron";
|
|
74891
|
+
}
|
|
74401
74892
|
function resolveInjectTarget(agentName3, meta) {
|
|
74402
74893
|
return meta?.session === "cron" ? cronIdentity(agentName3) : agentName3;
|
|
74403
74894
|
}
|
|
@@ -75660,6 +76151,44 @@ function maybeRotate(path2, fs2, maxBytes = TURNS_JSONL_MAX_BYTES) {
|
|
|
75660
76151
|
return true;
|
|
75661
76152
|
}
|
|
75662
76153
|
|
|
76154
|
+
// gateway/turn-record-status.ts
|
|
76155
|
+
function computeTurnStatus(turn) {
|
|
76156
|
+
switch (turn.deliveryOutcome) {
|
|
76157
|
+
case "failed":
|
|
76158
|
+
return "send_failed";
|
|
76159
|
+
case "delivered":
|
|
76160
|
+
return "complete";
|
|
76161
|
+
case "suppressed":
|
|
76162
|
+
return turn.finalAnswerDelivered ? "complete" : "no_reply";
|
|
76163
|
+
default:
|
|
76164
|
+
return turn.finalAnswerDelivered ? "complete" : "no_reply";
|
|
76165
|
+
}
|
|
76166
|
+
}
|
|
76167
|
+
function backstopSendOutcome(args) {
|
|
76168
|
+
if (args.threw)
|
|
76169
|
+
return "failed";
|
|
76170
|
+
if (args.chunkCount === 0)
|
|
76171
|
+
return "failed";
|
|
76172
|
+
if (args.sentCount < args.chunkCount)
|
|
76173
|
+
return "failed";
|
|
76174
|
+
return "delivered";
|
|
76175
|
+
}
|
|
76176
|
+
function finalizeBackstopSend(turn, send) {
|
|
76177
|
+
const outcome = backstopSendOutcome(send);
|
|
76178
|
+
turn.deliveryOutcome = outcome;
|
|
76179
|
+
return outcome;
|
|
76180
|
+
}
|
|
76181
|
+
function buildTurnRecord(turn, endedAt) {
|
|
76182
|
+
return {
|
|
76183
|
+
ts: Math.floor(endedAt / 1000),
|
|
76184
|
+
agent: turn.agent,
|
|
76185
|
+
duration_ms: turn.startedAt > 0 ? endedAt - turn.startedAt : 0,
|
|
76186
|
+
tools: turn.toolCallCount ?? 0,
|
|
76187
|
+
status: computeTurnStatus(turn),
|
|
76188
|
+
turn_id: turn.turnId
|
|
76189
|
+
};
|
|
76190
|
+
}
|
|
76191
|
+
|
|
75663
76192
|
// gateway/inbound-delivery-confirm.ts
|
|
75664
76193
|
function createDeliveryQueue() {
|
|
75665
76194
|
return { pending: new Map };
|
|
@@ -76340,6 +76869,7 @@ function maybeFireWarmup(ctx) {
|
|
|
76340
76869
|
|
|
76341
76870
|
// gateway/mental-model-propose-card.ts
|
|
76342
76871
|
init_approval_card();
|
|
76872
|
+
init_format();
|
|
76343
76873
|
function renderMentalModelProposeCard(req) {
|
|
76344
76874
|
const lines = [];
|
|
76345
76875
|
lines.push(`\uD83E\uDDE0 **${escapeHtmlForTg(req.agent)}** proposes a mental model`);
|
|
@@ -78257,25 +78787,81 @@ function redactSecrets(text4) {
|
|
|
78257
78787
|
return out;
|
|
78258
78788
|
}
|
|
78259
78789
|
|
|
78260
|
-
// narrative-
|
|
78261
|
-
var
|
|
78262
|
-
|
|
78263
|
-
|
|
78264
|
-
|
|
78265
|
-
|
|
78266
|
-
|
|
78267
|
-
|
|
78268
|
-
|
|
78269
|
-
|
|
78270
|
-
|
|
78271
|
-
|
|
78272
|
-
|
|
78273
|
-
|
|
78274
|
-
|
|
78275
|
-
|
|
78276
|
-
|
|
78277
|
-
|
|
78278
|
-
|
|
78790
|
+
// narrative-flush.ts
|
|
78791
|
+
var PENDING_NARRATIVE_FLUSH_MS2 = 250;
|
|
78792
|
+
|
|
78793
|
+
class NarrativeFlushController2 {
|
|
78794
|
+
effects;
|
|
78795
|
+
scheduler;
|
|
78796
|
+
flushMs;
|
|
78797
|
+
pending = null;
|
|
78798
|
+
timerShown = null;
|
|
78799
|
+
constructor(effects, scheduler, flushMs) {
|
|
78800
|
+
this.effects = effects;
|
|
78801
|
+
this.scheduler = scheduler;
|
|
78802
|
+
this.flushMs = flushMs;
|
|
78803
|
+
}
|
|
78804
|
+
get pendingText() {
|
|
78805
|
+
return this.pending;
|
|
78806
|
+
}
|
|
78807
|
+
get timerShownText() {
|
|
78808
|
+
return this.timerShown;
|
|
78809
|
+
}
|
|
78810
|
+
stage(text4) {
|
|
78811
|
+
this.scheduler.disarm();
|
|
78812
|
+
if (this.pending != null) {
|
|
78813
|
+
this.effects.show(this.pending);
|
|
78814
|
+
}
|
|
78815
|
+
this.pending = text4;
|
|
78816
|
+
this.scheduler.arm(() => this.onTimerFire(), this.flushMs);
|
|
78817
|
+
}
|
|
78818
|
+
onTimerFire() {
|
|
78819
|
+
if (this.pending == null)
|
|
78820
|
+
return;
|
|
78821
|
+
const text4 = this.pending;
|
|
78822
|
+
this.pending = null;
|
|
78823
|
+
this.timerShown = text4;
|
|
78824
|
+
this.effects.show(text4);
|
|
78825
|
+
}
|
|
78826
|
+
resolveOnTool(toolName, input) {
|
|
78827
|
+
this.scheduler.disarm();
|
|
78828
|
+
const replyText = REPLY_TOOLS2.has(toolName) && typeof input?.text === "string" ? input.text : null;
|
|
78829
|
+
if (replyText != null)
|
|
78830
|
+
this.maybeRetract(replyText);
|
|
78831
|
+
const pending = this.pending;
|
|
78832
|
+
if (pending == null)
|
|
78833
|
+
return;
|
|
78834
|
+
this.pending = null;
|
|
78835
|
+
if (replyText != null && isDraftOfReply(pending, replyText))
|
|
78836
|
+
return;
|
|
78837
|
+
this.effects.show(pending);
|
|
78838
|
+
}
|
|
78839
|
+
flushAtTurnEnd(lastReplyText) {
|
|
78840
|
+
this.scheduler.disarm();
|
|
78841
|
+
if (lastReplyText.length > 0)
|
|
78842
|
+
this.maybeRetract(lastReplyText);
|
|
78843
|
+
const pending = this.pending;
|
|
78844
|
+
if (pending == null)
|
|
78845
|
+
return;
|
|
78846
|
+
this.pending = null;
|
|
78847
|
+
if (lastReplyText.length > 0 && isDraftOfReply(pending, lastReplyText))
|
|
78848
|
+
return;
|
|
78849
|
+
this.effects.show(pending);
|
|
78850
|
+
}
|
|
78851
|
+
teardown() {
|
|
78852
|
+
this.scheduler.disarm();
|
|
78853
|
+
this.pending = null;
|
|
78854
|
+
this.timerShown = null;
|
|
78855
|
+
}
|
|
78856
|
+
maybeRetract(replyText) {
|
|
78857
|
+
const shown = this.timerShown;
|
|
78858
|
+
if (shown == null)
|
|
78859
|
+
return;
|
|
78860
|
+
if (!isDraftOfReply(shown, replyText))
|
|
78861
|
+
return;
|
|
78862
|
+
this.timerShown = null;
|
|
78863
|
+
this.effects.retractShown(shown);
|
|
78864
|
+
}
|
|
78279
78865
|
}
|
|
78280
78866
|
|
|
78281
78867
|
// subagent-watcher.ts
|
|
@@ -78412,6 +78998,9 @@ var DEFAULT_STALL_THRESHOLD_MS = 60000;
|
|
|
78412
78998
|
var DEFAULT_SILENT_SYNTHESIS_STALL_THRESHOLD_MS = 300000;
|
|
78413
78999
|
var DEFAULT_SILENT_STALL_TERMINAL_MS = 300000;
|
|
78414
79000
|
var DEFAULT_INFLIGHT_TERMINAL_CAP_MS = 45 * 60000;
|
|
79001
|
+
function resolveInflightTerminalCapMs(configVal) {
|
|
79002
|
+
return configVal ?? parseEnvMs("SWITCHROOM_SUBAGENT_INFLIGHT_TERMINAL_CAP_MS") ?? DEFAULT_INFLIGHT_TERMINAL_CAP_MS;
|
|
79003
|
+
}
|
|
78415
79004
|
var DEFAULT_DEFERRAL_LOG_INTERVAL_MS = 60000;
|
|
78416
79005
|
var LONG_RUNNING_TOOLS = new Set(["Bash"]);
|
|
78417
79006
|
var MAX_RESURRECTIONS = 1;
|
|
@@ -78525,6 +79114,8 @@ function backfillJsonlAgentId(db2, jsonlPath, agentId, log) {
|
|
|
78525
79114
|
}
|
|
78526
79115
|
function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, parentStateDir, onUnstall, onFileVanished, onProgress) {
|
|
78527
79116
|
try {
|
|
79117
|
+
if (entry.narrativeGate != null)
|
|
79118
|
+
entry.narrativeGate.tick(now);
|
|
78528
79119
|
const stat = fs2.statSync(entry.filePath);
|
|
78529
79120
|
if (stat.size < tail.cursor) {
|
|
78530
79121
|
tail.cursor = 0;
|
|
@@ -78585,43 +79176,87 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
|
|
|
78585
79176
|
entry.errorDetail = errInfo.detail.slice(0, SUBAGENT_RESULT_TEXT_MAX);
|
|
78586
79177
|
}
|
|
78587
79178
|
const events = projectSubagentLine(line, entry.agentId, startState);
|
|
78588
|
-
const
|
|
78589
|
-
|
|
78590
|
-
|
|
78591
|
-
|
|
78592
|
-
onProgress
|
|
78593
|
-
|
|
78594
|
-
|
|
78595
|
-
|
|
78596
|
-
|
|
78597
|
-
|
|
78598
|
-
|
|
78599
|
-
|
|
78600
|
-
|
|
78601
|
-
|
|
78602
|
-
|
|
78603
|
-
|
|
78604
|
-
|
|
78605
|
-
|
|
78606
|
-
|
|
78607
|
-
|
|
78608
|
-
|
|
78609
|
-
|
|
79179
|
+
const buildNarrativeGate = () => {
|
|
79180
|
+
const nowRef = { value: now };
|
|
79181
|
+
let cueFired = false;
|
|
79182
|
+
const fireCue = () => {
|
|
79183
|
+
if (onProgress == null || entry.state !== "running" || entry.historical)
|
|
79184
|
+
return false;
|
|
79185
|
+
try {
|
|
79186
|
+
onProgress({
|
|
79187
|
+
agentId: entry.agentId,
|
|
79188
|
+
description: entry.description,
|
|
79189
|
+
latestSummary: entry.lastResultText,
|
|
79190
|
+
elapsedMs: nowRef.value - entry.dispatchedAt,
|
|
79191
|
+
prevBucketIdx: entry.lastProgressBucketIdx,
|
|
79192
|
+
setBucketIdx: (b) => {
|
|
79193
|
+
entry.lastProgressBucketIdx = b;
|
|
79194
|
+
},
|
|
79195
|
+
lastTool: entry.lastTool,
|
|
79196
|
+
toolCount: entry.toolCount,
|
|
79197
|
+
model: entry.currentModel
|
|
79198
|
+
});
|
|
79199
|
+
return true;
|
|
79200
|
+
} catch (cbErr) {
|
|
79201
|
+
log?.(`subagent-watcher: onProgress callback error ${entry.agentId}: ${cbErr.message}`);
|
|
79202
|
+
return false;
|
|
79203
|
+
}
|
|
79204
|
+
};
|
|
79205
|
+
const scheduler = {
|
|
79206
|
+
armedFn: null,
|
|
79207
|
+
deadline: 0
|
|
79208
|
+
};
|
|
79209
|
+
const controller = new NarrativeFlushController2({
|
|
79210
|
+
show: () => {
|
|
79211
|
+
cueFired = fireCue();
|
|
79212
|
+
},
|
|
79213
|
+
retractShown: () => {
|
|
79214
|
+
log?.(`subagent-watcher: narrative early-paint retract (no-op on replace-on-write worker card) ${entry.agentId}`);
|
|
79215
|
+
}
|
|
79216
|
+
}, {
|
|
79217
|
+
arm: (fn, ms) => {
|
|
79218
|
+
scheduler.armedFn = fn;
|
|
79219
|
+
scheduler.deadline = nowRef.value + ms;
|
|
79220
|
+
},
|
|
79221
|
+
disarm: () => {
|
|
79222
|
+
scheduler.armedFn = null;
|
|
79223
|
+
}
|
|
79224
|
+
}, PENDING_NARRATIVE_FLUSH_MS2);
|
|
79225
|
+
return {
|
|
79226
|
+
tick: (n) => {
|
|
79227
|
+
nowRef.value = n;
|
|
79228
|
+
if (scheduler.armedFn != null && n >= scheduler.deadline) {
|
|
79229
|
+
const fn = scheduler.armedFn;
|
|
79230
|
+
scheduler.armedFn = null;
|
|
79231
|
+
fn();
|
|
79232
|
+
}
|
|
79233
|
+
},
|
|
79234
|
+
stage: (text5) => {
|
|
79235
|
+
controller.stage(text5);
|
|
79236
|
+
},
|
|
79237
|
+
resolveOnTool: (toolName, input) => {
|
|
79238
|
+
cueFired = false;
|
|
79239
|
+
controller.resolveOnTool(toolName ?? "", input);
|
|
79240
|
+
return cueFired;
|
|
79241
|
+
},
|
|
79242
|
+
resolveAtTurnEnd: (lastReplyText) => {
|
|
79243
|
+
cueFired = false;
|
|
79244
|
+
controller.flushAtTurnEnd(lastReplyText);
|
|
79245
|
+
return cueFired;
|
|
79246
|
+
},
|
|
79247
|
+
reset: () => {
|
|
79248
|
+
controller.teardown();
|
|
79249
|
+
scheduler.armedFn = null;
|
|
79250
|
+
}
|
|
79251
|
+
};
|
|
78610
79252
|
};
|
|
78611
79253
|
const resolvePendingSubNarrative = (toolName, toolInput) => {
|
|
78612
|
-
if (entry.
|
|
79254
|
+
if (entry.narrativeGate == null)
|
|
78613
79255
|
return false;
|
|
78614
|
-
|
|
78615
|
-
|
|
78616
|
-
if (toolName != null && REPLY_TOOLS2.has(toolName)) {
|
|
78617
|
-
const replyText = typeof toolInput?.text === "string" ? toolInput.text : "";
|
|
78618
|
-
if (isDraftOfReply2(pending.text, replyText))
|
|
78619
|
-
return false;
|
|
78620
|
-
} else if (toolName == null && entry.lastReplyText != null && entry.lastReplyText.length > 0) {
|
|
78621
|
-
if (isDraftOfReply2(pending.text, entry.lastReplyText))
|
|
78622
|
-
return false;
|
|
79256
|
+
if (toolName == null) {
|
|
79257
|
+
return entry.narrativeGate.resolveAtTurnEnd(entry.lastReplyText ?? "");
|
|
78623
79258
|
}
|
|
78624
|
-
return
|
|
79259
|
+
return entry.narrativeGate.resolveOnTool(toolName, toolInput);
|
|
78625
79260
|
};
|
|
78626
79261
|
for (const ev of events) {
|
|
78627
79262
|
const idleSecBeforeBump = Math.round((now - entry.lastActivityAt) / 1000);
|
|
@@ -78728,10 +79363,9 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
|
|
|
78728
79363
|
} else if (ev.kind === "sub_agent_text") {
|
|
78729
79364
|
entry.lastSummaryLine = clipNarrative(ev.text);
|
|
78730
79365
|
entry.lastResultText = ev.text.trim().slice(0, SUBAGENT_RESULT_TEXT_MAX);
|
|
78731
|
-
if (entry.
|
|
78732
|
-
|
|
78733
|
-
|
|
78734
|
-
entry.pendingNarrative = { text: ev.text };
|
|
79366
|
+
if (entry.narrativeGate == null)
|
|
79367
|
+
entry.narrativeGate = buildNarrativeGate();
|
|
79368
|
+
entry.narrativeGate.stage(ev.text);
|
|
78735
79369
|
} else if (ev.kind === "sub_agent_tool_result") {
|
|
78736
79370
|
if (ev.toolUseId != null && ev.toolUseId !== "") {
|
|
78737
79371
|
entry.inflightToolUseIds.delete(ev.toolUseId);
|
|
@@ -78778,7 +79412,7 @@ function startSubagentWatcher(config) {
|
|
|
78778
79412
|
const stallThresholdMs = config.stallThresholdMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_STALL_MS") ?? DEFAULT_STALL_THRESHOLD_MS;
|
|
78779
79413
|
const silentSynthesisStallThresholdMs = config.silentSynthesisStallThresholdMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_SILENT_SYNTH_STALL_MS") ?? DEFAULT_SILENT_SYNTHESIS_STALL_THRESHOLD_MS;
|
|
78780
79414
|
const silentStallTerminalMs = config.silentStallTerminalMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_STALL_TERMINAL_MS") ?? DEFAULT_SILENT_STALL_TERMINAL_MS;
|
|
78781
|
-
const inflightTerminalCapMs = config.inflightTerminalCapMs
|
|
79415
|
+
const inflightTerminalCapMs = resolveInflightTerminalCapMs(config.inflightTerminalCapMs);
|
|
78782
79416
|
const deferralLogIntervalMs = config.deferralLogIntervalMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_DEFERRAL_LOG_INTERVAL_MS") ?? DEFAULT_DEFERRAL_LOG_INTERVAL_MS;
|
|
78783
79417
|
const inflightPromoteMaxAgeMs = config.inflightPromoteMaxAgeMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_INFLIGHT_MAX_AGE_MS") ?? DEFAULT_INFLIGHT_PROMOTE_MAX_AGE_MS;
|
|
78784
79418
|
const terminatedAgentIdsCap = config.terminatedAgentIdsCap ?? TERMINATED_AGENT_IDS_CAP;
|
|
@@ -78939,7 +79573,8 @@ function startSubagentWatcher(config) {
|
|
|
78939
79573
|
}
|
|
78940
79574
|
entry.toolCount = 0;
|
|
78941
79575
|
entry.lastTool = null;
|
|
78942
|
-
entry.
|
|
79576
|
+
entry.narrativeGate?.reset();
|
|
79577
|
+
entry.narrativeGate = null;
|
|
78943
79578
|
tail.cursor = 0;
|
|
78944
79579
|
tail.pendingPartial = "";
|
|
78945
79580
|
tail.hasEmittedStart = false;
|
|
@@ -79038,6 +79673,13 @@ function startSubagentWatcher(config) {
|
|
|
79038
79673
|
}
|
|
79039
79674
|
terminatedAgentIds.add(agentId);
|
|
79040
79675
|
log?.(`subagent-watcher: cleaned up terminal agent ${agentId}`);
|
|
79676
|
+
if (config.onTerminalCleanup) {
|
|
79677
|
+
try {
|
|
79678
|
+
config.onTerminalCleanup(agentId);
|
|
79679
|
+
} catch (cbErr) {
|
|
79680
|
+
log?.(`subagent-watcher: onTerminalCleanup callback error ${agentId}: ${cbErr.message}`);
|
|
79681
|
+
}
|
|
79682
|
+
}
|
|
79041
79683
|
}
|
|
79042
79684
|
function recordFalseFinish(agentId, filePath, n) {
|
|
79043
79685
|
let size = 0;
|
|
@@ -82081,10 +82723,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
|
|
|
82081
82723
|
}
|
|
82082
82724
|
|
|
82083
82725
|
// ../src/build-info.ts
|
|
82084
|
-
var VERSION = "0.18.
|
|
82085
|
-
var COMMIT_SHA = "
|
|
82086
|
-
var COMMIT_DATE = "2026-07-
|
|
82087
|
-
var LATEST_PR =
|
|
82726
|
+
var VERSION = "0.18.20";
|
|
82727
|
+
var COMMIT_SHA = "f82e440f";
|
|
82728
|
+
var COMMIT_DATE = "2026-07-13T22:44:46+10:00";
|
|
82729
|
+
var LATEST_PR = 3230;
|
|
82088
82730
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
82089
82731
|
|
|
82090
82732
|
// gateway/boot-version.ts
|
|
@@ -82341,6 +82983,7 @@ async function listGrantsViaBroker2(agent, opts) {
|
|
|
82341
82983
|
}
|
|
82342
82984
|
|
|
82343
82985
|
// gateway/linear-activity.ts
|
|
82986
|
+
init_format();
|
|
82344
82987
|
init_client();
|
|
82345
82988
|
|
|
82346
82989
|
// ../src/linear/oauth-refresh.ts
|
|
@@ -84508,6 +85151,7 @@ function resolveSubagentOriginChat(agentId) {
|
|
|
84508
85151
|
}
|
|
84509
85152
|
}
|
|
84510
85153
|
var WORKER_FEED_FALLBACK_LOG_CAP = 256;
|
|
85154
|
+
var WORKER_FEED_STALE_TTL_MARGIN_MS = 300000;
|
|
84511
85155
|
var workerFeedOwnerDmFallbackLogged = new Set;
|
|
84512
85156
|
function resolveWorkerFeedChat(agentId, fleetChatId) {
|
|
84513
85157
|
const origin = resolveSubagentOriginChat(agentId);
|
|
@@ -85405,14 +86049,14 @@ function releaseTurnBufferGate(key, endingTurn) {
|
|
|
85405
86049
|
}
|
|
85406
86050
|
function emitTurnRecord(turn, endedAt) {
|
|
85407
86051
|
try {
|
|
85408
|
-
const rec = JSON.stringify({
|
|
85409
|
-
ts: Math.floor(endedAt / 1000),
|
|
86052
|
+
const rec = JSON.stringify(buildTurnRecord({
|
|
85410
86053
|
agent: process.env.SWITCHROOM_AGENT_NAME ?? "unknown",
|
|
85411
|
-
|
|
85412
|
-
|
|
85413
|
-
|
|
85414
|
-
|
|
85415
|
-
|
|
86054
|
+
startedAt: turn.startedAt,
|
|
86055
|
+
toolCallCount: turn.toolCallCount ?? 0,
|
|
86056
|
+
turnId: turn.turnId,
|
|
86057
|
+
finalAnswerDelivered: turn.finalAnswerDelivered,
|
|
86058
|
+
deliveryOutcome: turn.deliveryOutcome
|
|
86059
|
+
}, endedAt)) + `
|
|
85416
86060
|
`;
|
|
85417
86061
|
const turnsPath = "/state/agent/turns.jsonl";
|
|
85418
86062
|
maybeRotate(turnsPath, {
|
|
@@ -85428,15 +86072,18 @@ function emitTurnRecord(turn, endedAt) {
|
|
|
85428
86072
|
appendFileSync6(turnsPath, rec);
|
|
85429
86073
|
} catch {}
|
|
85430
86074
|
}
|
|
85431
|
-
function endCurrentTurnAtomic(turn) {
|
|
86075
|
+
function endCurrentTurnAtomic(turn, opts) {
|
|
85432
86076
|
const key = statusKey(turn.sessionChatId, turn.sessionThreadId);
|
|
85433
86077
|
if (!turnLiveForItsTopic(turn))
|
|
85434
|
-
return;
|
|
86078
|
+
return null;
|
|
86079
|
+
clearAnswerReadyFlushTimeout(turn);
|
|
85435
86080
|
endCurrentTurnForKey(turn, key);
|
|
85436
86081
|
const turnEndedAt = Date.now();
|
|
85437
86082
|
process.stderr.write(`telegram gateway: ${formatTurnLifecycle("clear", "turn_end", turn, turnEndedAt)}
|
|
85438
86083
|
`);
|
|
85439
|
-
|
|
86084
|
+
if (opts?.deferRecord !== true) {
|
|
86085
|
+
emitTurnRecord(turn, turnEndedAt);
|
|
86086
|
+
}
|
|
85440
86087
|
const degraded = detectStatusSurfaceDegraded(turn);
|
|
85441
86088
|
if (degraded != null) {
|
|
85442
86089
|
process.stderr.write(`telegram gateway: status-surface DEGRADED reason=${degraded.reason} turnId=${turn.turnId} chat=${turn.sessionChatId} thread=${turn.sessionThreadId ?? "-"} ${degraded.detail}
|
|
@@ -85453,8 +86100,10 @@ function endCurrentTurnAtomic(turn) {
|
|
|
85453
86100
|
clearTimeout(turn.noReplyDrainTimer);
|
|
85454
86101
|
turn.noReplyDrainTimer = null;
|
|
85455
86102
|
}
|
|
86103
|
+
turn.narrativeGate?.teardown();
|
|
85456
86104
|
purgeReactionTracking(statusKey(turn.sessionChatId, turn.sessionThreadId), turn);
|
|
85457
86105
|
armNoReplyDrainTimer(turn);
|
|
86106
|
+
return turnEndedAt;
|
|
85458
86107
|
}
|
|
85459
86108
|
function maybeProactiveCompact() {
|
|
85460
86109
|
if (compactDispatching)
|
|
@@ -85514,16 +86163,9 @@ function maybeProactiveCompact() {
|
|
|
85514
86163
|
compactDispatching = false;
|
|
85515
86164
|
});
|
|
85516
86165
|
}
|
|
85517
|
-
var
|
|
85518
|
-
var lastIdleTurnEndAt = null;
|
|
85519
|
-
var idleAutoCleared = false;
|
|
85520
|
-
var idleClearDispatching = false;
|
|
86166
|
+
var idleTracker = new IdleTracker(Date.now());
|
|
85521
86167
|
function markIdleActivity() {
|
|
85522
|
-
|
|
85523
|
-
idleAutoCleared = false;
|
|
85524
|
-
}
|
|
85525
|
-
function markIdleTurnEnd() {
|
|
85526
|
-
lastIdleTurnEndAt = Date.now();
|
|
86168
|
+
idleTracker.noteInbound(Date.now());
|
|
85527
86169
|
}
|
|
85528
86170
|
function resolveIdleClearMs() {
|
|
85529
86171
|
const env = process.env.SWITCHROOM_IDLE_CLEAR_MS;
|
|
@@ -85548,30 +86190,39 @@ function resolveIdleClearMs() {
|
|
|
85548
86190
|
}
|
|
85549
86191
|
}
|
|
85550
86192
|
function maybeIdleClear() {
|
|
85551
|
-
if (
|
|
86193
|
+
if (idleTracker.isDispatching)
|
|
85552
86194
|
return;
|
|
85553
86195
|
const agentName3 = process.env.SWITCHROOM_AGENT_NAME;
|
|
85554
86196
|
if (!agentName3)
|
|
85555
86197
|
return;
|
|
85556
86198
|
const idleClearMs = resolveIdleClearMs();
|
|
85557
|
-
const decision =
|
|
85558
|
-
lastActivityAt: lastIdleActivityAt,
|
|
85559
|
-
lastTurnEndedAt: lastIdleTurnEndAt,
|
|
86199
|
+
const decision = idleTracker.decide(Date.now(), {
|
|
85560
86200
|
idleClearMs,
|
|
85561
|
-
|
|
85562
|
-
|
|
85563
|
-
}
|
|
86201
|
+
turnInFlight: turnInFlightForGate(),
|
|
86202
|
+
backgroundWorkInFlight: anyPendingAsyncDispatchWithin(BACKGROUND_WORK_SUPPRESS_TTL_MS)
|
|
86203
|
+
});
|
|
85564
86204
|
if (!decision.clear)
|
|
85565
86205
|
return;
|
|
85566
|
-
|
|
85567
|
-
|
|
86206
|
+
idleTracker.markClearFired();
|
|
86207
|
+
idleTracker.beginDispatch();
|
|
85568
86208
|
process.stderr.write(`telegram gateway: idle auto-/clear for ${agentName3} (idle >= ${Math.round(idleClearMs / 60000)}m)
|
|
85569
86209
|
`);
|
|
85570
|
-
|
|
86210
|
+
const stillIdleAtWrite = () => idleTracker.decideIgnoringLatch(Date.now(), {
|
|
86211
|
+
idleClearMs: resolveIdleClearMs(),
|
|
86212
|
+
turnInFlight: turnInFlightForGate(),
|
|
86213
|
+
backgroundWorkInFlight: anyPendingAsyncDispatchWithin(BACKGROUND_WORK_SUPPRESS_TTL_MS)
|
|
86214
|
+
}).clear;
|
|
86215
|
+
injectSlashCommand(agentName3, "/clear", { precondition: stillIdleAtWrite }).then((result) => {
|
|
86216
|
+
if (result.outcome === "skipped") {
|
|
86217
|
+
idleTracker.reArm();
|
|
86218
|
+
process.stderr.write(`telegram gateway: idle /clear suppressed for ${agentName3} (activity in check-to-send gap)
|
|
86219
|
+
`);
|
|
86220
|
+
}
|
|
86221
|
+
}).catch((err) => {
|
|
85571
86222
|
process.stderr.write(`telegram gateway: idle /clear inject failed for ${agentName3}: ${err instanceof Error ? err.message : String(err)}
|
|
85572
86223
|
`);
|
|
85573
86224
|
}).finally(() => {
|
|
85574
|
-
|
|
86225
|
+
idleTracker.endDispatch();
|
|
85575
86226
|
});
|
|
85576
86227
|
}
|
|
85577
86228
|
async function postCompactCard(occ, cap) {
|
|
@@ -86768,6 +87419,7 @@ var inboundCoalescer = createInboundCoalescer({
|
|
|
86768
87419
|
});
|
|
86769
87420
|
function emitGatewayOperatorEvent(event) {
|
|
86770
87421
|
const { agent, kind } = event;
|
|
87422
|
+
event = { ...event, detail: redactOutboundText(event.detail, "operator_event") };
|
|
86771
87423
|
let throttleEscalation = null;
|
|
86772
87424
|
let escalationFired = false;
|
|
86773
87425
|
let rateLimitedCooldownConsulted = false;
|
|
@@ -86872,9 +87524,9 @@ function emitGatewayOperatorEvent(event) {
|
|
|
86872
87524
|
return;
|
|
86873
87525
|
}
|
|
86874
87526
|
const tz = process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ ?? "UTC";
|
|
86875
|
-
const r =
|
|
87527
|
+
const r = renderLlmErrorSafe(parsed, agent, tz, new Date(now));
|
|
86876
87528
|
renderedText = r.text;
|
|
86877
|
-
renderedKeyboard =
|
|
87529
|
+
renderedKeyboard = undefined;
|
|
86878
87530
|
} else {
|
|
86879
87531
|
try {
|
|
86880
87532
|
const r = renderOperatorEvent(event);
|
|
@@ -86950,6 +87602,7 @@ var STREAM_THROTTLE_MS_OVERRIDE = (() => {
|
|
|
86950
87602
|
return Number.isFinite(n) && n >= 0 ? n : undefined;
|
|
86951
87603
|
})();
|
|
86952
87604
|
var TURN_FLUSH_SAFETY_ENABLED = isTurnFlushSafetyEnabled();
|
|
87605
|
+
var ANSWER_READY_FLUSH_MS2 = resolveAnswerReadyFlushMs(process.env);
|
|
86953
87606
|
var ANSWER_STREAM_VISIBLE_ENABLED = parseVisibleAnswerStreamEnabled(process.env.SWITCHROOM_VISIBLE_ANSWER_STREAM);
|
|
86954
87607
|
var ANSWER_LANE = resolveAnswerLaneConfig({
|
|
86955
87608
|
visibleEnabled: ANSWER_STREAM_VISIBLE_ENABLED
|
|
@@ -87429,6 +88082,7 @@ var SILENCE_FALLBACK_MS = parsePositiveMsEnv("SWITCHROOM_SILENCE_FALLBACK_MS", 3
|
|
|
87429
88082
|
var SILENCE_FALLBACK_HARD_MS = parsePositiveMsEnv("SWITCHROOM_SILENCE_FALLBACK_HARD_MS", 900000);
|
|
87430
88083
|
var SILENCE_FLOOR_MS = parsePositiveMsEnv("SWITCHROOM_SILENCE_FLOOR_MS", 45000);
|
|
87431
88084
|
var LIVENESS_TERMINAL_HONESTY = process.env.SWITCHROOM_TG_TERMINAL_HONESTY !== "0";
|
|
88085
|
+
var CAPTURED_PROSE_DELIVERY_ENABLED = process.env.SWITCHROOM_TG_CAPTURED_PROSE_DELIVERY !== "0";
|
|
87432
88086
|
var SILENCE_DEFER_INFLIGHT_TOOLS = process.env.SWITCHROOM_SILENCE_DEFER_INFLIGHT_TOOLS === "1";
|
|
87433
88087
|
var SILENCE_LIVENESS_PRODUCTION = process.env.SWITCHROOM_SILENCE_LIVENESS_PRODUCTION !== "0";
|
|
87434
88088
|
var MEMORY_LEGIBILITY_ENABLED = isMemoryLegibilityEnabled(process.env.SWITCHROOM_MEMORY_LEGIBILITY);
|
|
@@ -87743,6 +88397,103 @@ function agentHasInFlightBackgroundWork(now) {
|
|
|
87743
88397
|
return ageMs != null && ageMs < TURN_ACTIVE_MARKER_FRESH_MS;
|
|
87744
88398
|
}
|
|
87745
88399
|
var lastBgWorkDeferLogMs = 0;
|
|
88400
|
+
async function deliverCapturedProse(args) {
|
|
88401
|
+
const { chatId, threadId, statusKeyStr, registryKey, originTurnId, text: text5, turnDurationMs } = args;
|
|
88402
|
+
const now = Date.now();
|
|
88403
|
+
let outcome;
|
|
88404
|
+
const already = outboundDedup.check(chatId, threadId, text5, now, registryKey);
|
|
88405
|
+
if (already == null) {
|
|
88406
|
+
let out = normalizeParagraphBreaks2(repairEscapedWhitespace2(text5));
|
|
88407
|
+
out = redactOutboundText(out, "captured_prose");
|
|
88408
|
+
const chunks = splitMarkdownChunks2(out, RICH_MESSAGE_MAX_CHARS2);
|
|
88409
|
+
const sentIds = [];
|
|
88410
|
+
try {
|
|
88411
|
+
let liveThreadId = threadId;
|
|
88412
|
+
for (const c of chunks) {
|
|
88413
|
+
const sent = await retryWithThreadFallback2(robustApiCall, (tid) => {
|
|
88414
|
+
const opts = {
|
|
88415
|
+
link_preview_options: { is_disabled: true },
|
|
88416
|
+
...tid != null ? { message_thread_id: tid } : {}
|
|
88417
|
+
};
|
|
88418
|
+
return bot.api.sendRichMessage(chatId, richMessage2(c), opts);
|
|
88419
|
+
}, { threadId: liveThreadId, chat_id: chatId, verb: "captured-prose.sendMessage" });
|
|
88420
|
+
if (liveThreadId != null && sent.message_thread_id == null) {
|
|
88421
|
+
liveThreadId = undefined;
|
|
88422
|
+
}
|
|
88423
|
+
sentIds.push(sent.message_id);
|
|
88424
|
+
}
|
|
88425
|
+
if (HISTORY_ENABLED && sentIds.length > 0) {
|
|
88426
|
+
try {
|
|
88427
|
+
recordOutbound({
|
|
88428
|
+
chat_id: chatId,
|
|
88429
|
+
thread_id: threadId ?? null,
|
|
88430
|
+
message_ids: sentIds,
|
|
88431
|
+
texts: chunks
|
|
88432
|
+
});
|
|
88433
|
+
} catch {}
|
|
88434
|
+
}
|
|
88435
|
+
outboundDedup.record(chatId, threadId, text5, now, registryKey);
|
|
88436
|
+
process.stderr.write(`telegram gateway: captured-prose delivery \u2014 sent ${out.length} chars recovered from transcript scan (chat=${chatId} origin=${originTurnId})
|
|
88437
|
+
`);
|
|
88438
|
+
outcome = "sent";
|
|
88439
|
+
} catch (err) {
|
|
88440
|
+
process.stderr.write(`telegram gateway: captured-prose delivery failed: ${err.message} \u2014 ` + `arming the silent-end re-prompt net (recordUndeliveredTurnEnd) so the answer is recoverable (chat=${chatId} origin=${originTurnId})
|
|
88441
|
+
`);
|
|
88442
|
+
outcome = "failed";
|
|
88443
|
+
}
|
|
88444
|
+
} else {
|
|
88445
|
+
process.stderr.write(`telegram gateway: captured-prose delivery skipped \u2014 this answer already went out ` + `(dedup age=${already.ageMs}ms chat=${chatId} origin=${originTurnId}); settling bookkeeping
|
|
88446
|
+
`);
|
|
88447
|
+
outcome = "skipped-dedup";
|
|
88448
|
+
}
|
|
88449
|
+
const settlement = settleCapturedProseDelivery(outcome, {
|
|
88450
|
+
closeObligation: () => {
|
|
88451
|
+
if (OBLIGATION_LEDGER_ENABLED) {
|
|
88452
|
+
try {
|
|
88453
|
+
obligationLedger.close(originTurnId);
|
|
88454
|
+
} catch {}
|
|
88455
|
+
}
|
|
88456
|
+
},
|
|
88457
|
+
clearState: () => clearSilentEndState(statusKeyStr),
|
|
88458
|
+
recordUndelivered: () => {
|
|
88459
|
+
try {
|
|
88460
|
+
const silentEndDeps = HISTORY_ENABLED ? {
|
|
88461
|
+
hasOutboundDeliveredSince: (cid, sinceMs, tid) => hasOutboundDeliveredSince(cid, sinceMs, tid, 1)
|
|
88462
|
+
} : undefined;
|
|
88463
|
+
return recordUndeliveredTurnEnd({ chatId, threadId: threadId ?? null, turnKey: statusKeyStr }, silentEndDeps);
|
|
88464
|
+
} catch (netErr) {
|
|
88465
|
+
process.stderr.write(`telegram gateway: captured-prose recovery-net arm failed: ${netErr.message} (chat=${chatId} origin=${originTurnId})
|
|
88466
|
+
`);
|
|
88467
|
+
return { exhausted: false };
|
|
88468
|
+
}
|
|
88469
|
+
}
|
|
88470
|
+
});
|
|
88471
|
+
if (outcome === "failed" && settlement.exhausted) {
|
|
88472
|
+
process.stderr.write(`telegram gateway: WARN captured-prose exhausted-boundary fallback \u2014 rich send ` + `failed with the re-prompt budget already spent; attempting a plain-text delivery of the recovered answer before the generic apology (chat=${chatId} origin=${originTurnId})
|
|
88473
|
+
`);
|
|
88474
|
+
const plain = redactOutboundText(text5, "captured_prose");
|
|
88475
|
+
const plainChunks = splitMarkdownChunks2(plain, RICH_MESSAGE_MAX_CHARS2);
|
|
88476
|
+
try {
|
|
88477
|
+
let liveThreadId = threadId;
|
|
88478
|
+
for (const c of plainChunks) {
|
|
88479
|
+
const sent = await retryWithThreadFallback2(robustApiCall, (tid) => bot.api.sendMessage(chatId, c, tid != null ? { message_thread_id: tid } : {}), { threadId: liveThreadId, chat_id: chatId, verb: "captured-prose-plain-fallback.sendMessage" });
|
|
88480
|
+
if (liveThreadId != null && sent.message_thread_id == null) {
|
|
88481
|
+
liveThreadId = undefined;
|
|
88482
|
+
}
|
|
88483
|
+
}
|
|
88484
|
+
outboundDedup.record(chatId, threadId, text5, Date.now(), registryKey);
|
|
88485
|
+
process.stderr.write(`telegram gateway: captured-prose recovered via plain-text fallback (chat=${chatId} origin=${originTurnId})
|
|
88486
|
+
`);
|
|
88487
|
+
} catch (plainErr) {
|
|
88488
|
+
process.stderr.write(`telegram gateway: captured-prose plain-text fallback ALSO failed: ${plainErr.message} \u2014 posting the generic silent-end apology (chat=${chatId} origin=${originTurnId})
|
|
88489
|
+
`);
|
|
88490
|
+
retryWithThreadFallback2(robustApiCall, (tid) => bot.api.sendMessage(chatId, silentEndFallbackText(turnDurationMs), tid != null ? { message_thread_id: tid } : {}), { threadId, chat_id: chatId, verb: "captured-prose-apology-fallback.sendMessage" }).catch((err) => {
|
|
88491
|
+
process.stderr.write(`telegram gateway: captured-prose apology fallback send failed: ${err instanceof Error ? err.message : String(err)}
|
|
88492
|
+
`);
|
|
88493
|
+
});
|
|
88494
|
+
}
|
|
88495
|
+
}
|
|
88496
|
+
}
|
|
87746
88497
|
function obligationSweep() {
|
|
87747
88498
|
if (!OBLIGATION_LEDGER_ENABLED)
|
|
87748
88499
|
return;
|
|
@@ -88520,7 +89271,8 @@ var ipcServer = createIpcServer({
|
|
|
88520
89271
|
swallowingApiCall(() => bot.api.editMessageText(operator, msg.messageId, richMessage2(msg.text), {}), { chat_id: String(operator), verb: "rollout-status-edit" });
|
|
88521
89272
|
},
|
|
88522
89273
|
onInjectInbound(_client, msg) {
|
|
88523
|
-
|
|
89274
|
+
if (!isCronInjectFire(msg.inbound.meta))
|
|
89275
|
+
markIdleActivity();
|
|
88524
89276
|
const promptKey = typeof msg.inbound.meta?.prompt_key === "string" ? msg.inbound.meta.prompt_key : "unknown";
|
|
88525
89277
|
const source = typeof msg.inbound.meta?.source === "string" ? msg.inbound.meta.source : "unknown";
|
|
88526
89278
|
const isDurableReplay = inboundSpool != null && typeof msg.inbound.meta?.replay_fire_ms === "string" && msg.inbound.meta.replay_fire_ms.length > 0;
|
|
@@ -90748,6 +91500,33 @@ function resetOrphanedReplyTimeout() {
|
|
|
90748
91500
|
}, ORPHANED_REPLY_TIMEOUT_MS);
|
|
90749
91501
|
}
|
|
90750
91502
|
}
|
|
91503
|
+
var answerReadyFlush = new AnswerReadyFlushController({
|
|
91504
|
+
getCurrentTurn: () => currentTurn,
|
|
91505
|
+
getArmInput: (turn) => ({
|
|
91506
|
+
flush: {
|
|
91507
|
+
chatId: turn.sessionChatId,
|
|
91508
|
+
replyCalled: turn.replyCalled,
|
|
91509
|
+
capturedText: turn.capturedText,
|
|
91510
|
+
flushEnabled: TURN_FLUSH_SAFETY_ENABLED
|
|
91511
|
+
},
|
|
91512
|
+
inFlightToolCount: toolFlightTracker.inFlightCount(),
|
|
91513
|
+
hasPendingAsyncDispatch: hasPendingAsyncDispatch(statusKey(turn.sessionChatId, turn.sessionThreadId)),
|
|
91514
|
+
flushWindowMs: ANSWER_READY_FLUSH_MS2
|
|
91515
|
+
}),
|
|
91516
|
+
getTimerHandle: (turn) => turn.answerReadyFlushTimeoutId,
|
|
91517
|
+
setTimerHandle: (turn, handle) => {
|
|
91518
|
+
turn.answerReadyFlushTimeoutId = handle;
|
|
91519
|
+
},
|
|
91520
|
+
onFlush: () => handleSessionEvent({ kind: "turn_end", durationMs: -1, reason: "answer-ready-quiescence" }),
|
|
91521
|
+
log: (msg) => process.stderr.write(`telegram gateway: ${msg}
|
|
91522
|
+
`)
|
|
91523
|
+
});
|
|
91524
|
+
function clearAnswerReadyFlushTimeout(turn) {
|
|
91525
|
+
answerReadyFlush.clear(turn);
|
|
91526
|
+
}
|
|
91527
|
+
function resetAnswerReadyFlushTimeout() {
|
|
91528
|
+
answerReadyFlush.reset();
|
|
91529
|
+
}
|
|
90751
91530
|
function closeActivityLane(chatId, threadId) {
|
|
90752
91531
|
const key = chatKeyWithSuffix2(chatId, threadId, "activity");
|
|
90753
91532
|
const stream = activeDraftStreams.get(key);
|
|
@@ -90781,6 +91560,45 @@ function composeTurnActivity(turn, final = false, liveSuffix = "") {
|
|
|
90781
91560
|
};
|
|
90782
91561
|
return renderActivityFeedWithNested2(turn.mirrorLines, childLines, final, liveSuffix, stepCount, header);
|
|
90783
91562
|
}
|
|
91563
|
+
function retractNarrativeLine(turn, text5) {
|
|
91564
|
+
const clipped = clipNarrative2(text5);
|
|
91565
|
+
const idx = turn.mirrorLines.lastIndexOf(clipped);
|
|
91566
|
+
if (idx === -1)
|
|
91567
|
+
return;
|
|
91568
|
+
turn.mirrorLines.splice(idx, 1);
|
|
91569
|
+
const rerender = composeTurnActivity(turn);
|
|
91570
|
+
if (rerender == null)
|
|
91571
|
+
return;
|
|
91572
|
+
turn.activityPendingRender = rerender;
|
|
91573
|
+
const ea = emissionAuthorityFor(turn);
|
|
91574
|
+
cardDrainGate(turn, ea, () => {
|
|
91575
|
+
if (ea.mayDrain(turn)) {
|
|
91576
|
+
ea.openOrEditCard("narrative", () => {
|
|
91577
|
+
turn.activityInFlight = drainActivitySummary(turn, "narrative");
|
|
91578
|
+
});
|
|
91579
|
+
}
|
|
91580
|
+
});
|
|
91581
|
+
}
|
|
91582
|
+
function makeNarrativeGate(turn) {
|
|
91583
|
+
let handle = null;
|
|
91584
|
+
return new NarrativeFlushController({
|
|
91585
|
+
show: (text5) => showNarrativeStep(turn, text5),
|
|
91586
|
+
retractShown: (text5) => retractNarrativeLine(turn, text5)
|
|
91587
|
+
}, {
|
|
91588
|
+
arm: (fn, ms) => {
|
|
91589
|
+
if (handle != null)
|
|
91590
|
+
clearTimeout(handle);
|
|
91591
|
+
handle = setTimeout(fn, ms);
|
|
91592
|
+
handle.unref?.();
|
|
91593
|
+
},
|
|
91594
|
+
disarm: () => {
|
|
91595
|
+
if (handle != null) {
|
|
91596
|
+
clearTimeout(handle);
|
|
91597
|
+
handle = null;
|
|
91598
|
+
}
|
|
91599
|
+
}
|
|
91600
|
+
}, PENDING_NARRATIVE_FLUSH_MS);
|
|
91601
|
+
}
|
|
90784
91602
|
function showNarrativeStep(turn, text5) {
|
|
90785
91603
|
const rendered = appendActivityLabel(turn.mirrorLines, clipNarrative2(text5));
|
|
90786
91604
|
if (rendered == null)
|
|
@@ -90796,31 +91614,13 @@ function showNarrativeStep(turn, text5) {
|
|
|
90796
91614
|
});
|
|
90797
91615
|
}
|
|
90798
91616
|
function resolvePendingNarrativeOnTool(turn, toolName, input) {
|
|
90799
|
-
|
|
90800
|
-
if (pending2 == null)
|
|
90801
|
-
return;
|
|
90802
|
-
turn.pendingNarrative = null;
|
|
90803
|
-
if (REPLY_TOOLS.has(toolName)) {
|
|
90804
|
-
const replyText = typeof input?.text === "string" ? input.text : "";
|
|
90805
|
-
if (isDraftOfReply(pending2.text, replyText))
|
|
90806
|
-
return;
|
|
90807
|
-
}
|
|
90808
|
-
showNarrativeStep(turn, pending2.text);
|
|
91617
|
+
turn.narrativeGate.resolveOnTool(toolName, input);
|
|
90809
91618
|
}
|
|
90810
91619
|
function stagePendingNarrative(turn, text5) {
|
|
90811
|
-
|
|
90812
|
-
showNarrativeStep(turn, turn.pendingNarrative.text);
|
|
90813
|
-
}
|
|
90814
|
-
turn.pendingNarrative = { text: text5 };
|
|
91620
|
+
turn.narrativeGate.stage(text5);
|
|
90815
91621
|
}
|
|
90816
91622
|
function flushPendingNarrativeAtTurnEnd(turn, lastReplyText) {
|
|
90817
|
-
|
|
90818
|
-
if (pending2 == null)
|
|
90819
|
-
return;
|
|
90820
|
-
turn.pendingNarrative = null;
|
|
90821
|
-
if (lastReplyText.length > 0 && isDraftOfReply(pending2.text, lastReplyText))
|
|
90822
|
-
return;
|
|
90823
|
-
showNarrativeStep(turn, pending2.text);
|
|
91623
|
+
turn.narrativeGate.flushAtTurnEnd(lastReplyText);
|
|
90824
91624
|
}
|
|
90825
91625
|
async function drainActivitySummary(turn, producer = "tool", openFlags) {
|
|
90826
91626
|
try {
|
|
@@ -91160,11 +91960,7 @@ function handleSessionEvent(ev) {
|
|
|
91160
91960
|
}
|
|
91161
91961
|
{
|
|
91162
91962
|
const durationMs = ev.kind === "turn_end" ? ev.durationMs : undefined;
|
|
91163
|
-
|
|
91164
|
-
if (signal.activity)
|
|
91165
|
-
markIdleActivity();
|
|
91166
|
-
if (signal.turnEnded)
|
|
91167
|
-
markIdleTurnEnd();
|
|
91963
|
+
idleTracker.noteEvent(ev.kind, Date.now(), durationMs);
|
|
91168
91964
|
}
|
|
91169
91965
|
switch (ev.kind) {
|
|
91170
91966
|
case "enqueue": {
|
|
@@ -91184,6 +91980,7 @@ function handleSessionEvent(ev) {
|
|
|
91184
91980
|
clearTimeout(prior.orphanedReplyTimeoutId);
|
|
91185
91981
|
prior.orphanedReplyTimeoutId = null;
|
|
91186
91982
|
}
|
|
91983
|
+
prior?.narrativeGate?.teardown();
|
|
91187
91984
|
const startedAt = Date.now();
|
|
91188
91985
|
const enqThreadIdNum = ev.threadId != null ? Number(ev.threadId) : undefined;
|
|
91189
91986
|
const turnId = deriveTurnId(ev.chatId, enqThreadIdNum ?? null, ev.messageId) ?? `${chatKey2(ev.chatId, enqThreadIdNum ?? null)}#synthetic-${startedAt}`;
|
|
@@ -91209,6 +92006,7 @@ function handleSessionEvent(ev) {
|
|
|
91209
92006
|
silentAnchorText: "",
|
|
91210
92007
|
capturedText: [],
|
|
91211
92008
|
orphanedReplyTimeoutId: null,
|
|
92009
|
+
answerReadyFlushTimeoutId: null,
|
|
91212
92010
|
liveness: new LivenessTracker(startedAt),
|
|
91213
92011
|
turnId,
|
|
91214
92012
|
registryKey: null,
|
|
@@ -91224,13 +92022,14 @@ function handleSessionEvent(ev) {
|
|
|
91224
92022
|
activityEverOpened: false,
|
|
91225
92023
|
activityDrainFailures: 0,
|
|
91226
92024
|
mirrorLines: [],
|
|
91227
|
-
|
|
92025
|
+
narrativeGate: undefined,
|
|
91228
92026
|
lastReplyText: "",
|
|
91229
92027
|
foregroundSubAgents: new Map,
|
|
91230
92028
|
answerStream: null,
|
|
91231
92029
|
isDm: isDmChatId2(ev.chatId),
|
|
91232
92030
|
emissionAuthority: new EmissionAuthority(statusKey(ev.chatId, enqThreadIdNum))
|
|
91233
92031
|
};
|
|
92032
|
+
next.narrativeGate = makeNarrativeGate(next);
|
|
91234
92033
|
setCurrentTurn(next, statusKey(ev.chatId, enqThreadIdNum));
|
|
91235
92034
|
scheduleEarlyLivenessOpen(next);
|
|
91236
92035
|
process.stderr.write(`telegram gateway: ${formatTurnLifecycle("set", "enqueue", next, startedAt)}
|
|
@@ -91302,6 +92101,7 @@ function handleSessionEvent(ev) {
|
|
|
91302
92101
|
const turn = currentTurn;
|
|
91303
92102
|
if (turn == null)
|
|
91304
92103
|
return;
|
|
92104
|
+
clearAnswerReadyFlushTimeout(turn);
|
|
91305
92105
|
resolvePendingNarrativeOnTool(turn, ev.toolName, ev.input);
|
|
91306
92106
|
turn.toolCallCount++;
|
|
91307
92107
|
touchTurnActiveMarker2(STATE_DIR);
|
|
@@ -91336,6 +92136,7 @@ function handleSessionEvent(ev) {
|
|
|
91336
92136
|
const turn = currentTurn;
|
|
91337
92137
|
if (turn == null)
|
|
91338
92138
|
return;
|
|
92139
|
+
clearAnswerReadyFlushTimeout(turn);
|
|
91339
92140
|
resetOrphanedReplyTimeout();
|
|
91340
92141
|
if (isTelegramSurfaceTool2(ev.toolName))
|
|
91341
92142
|
return;
|
|
@@ -91445,6 +92246,7 @@ function handleSessionEvent(ev) {
|
|
|
91445
92246
|
preambleSuppressor.onText(ev.text);
|
|
91446
92247
|
}
|
|
91447
92248
|
resetOrphanedReplyTimeout();
|
|
92249
|
+
resetAnswerReadyFlushTimeout();
|
|
91448
92250
|
if (isContextExhaustionText(ev.text) && turn != null) {
|
|
91449
92251
|
const chatId = turn.sessionChatId;
|
|
91450
92252
|
const threadId = turn.sessionThreadId;
|
|
@@ -91496,7 +92298,7 @@ function handleSessionEvent(ev) {
|
|
|
91496
92298
|
return;
|
|
91497
92299
|
}
|
|
91498
92300
|
case "turn_end": {
|
|
91499
|
-
if (ev.durationMs === -1) {
|
|
92301
|
+
if (ev.durationMs === -1 && ev.reason !== "answer-ready-quiescence") {
|
|
91500
92302
|
const turn = currentTurn;
|
|
91501
92303
|
const key = turn != null ? statusKey(turn.sessionChatId, turn.sessionThreadId) : "";
|
|
91502
92304
|
const recentlyStreaming = turn != null && turn.liveness.recentlyStreaming(Date.now(), ORPHANED_REPLY_STREAM_WINDOW_MS);
|
|
@@ -91570,7 +92372,7 @@ function handleSessionEvent(ev) {
|
|
|
91570
92372
|
const chatId = turn.sessionChatId;
|
|
91571
92373
|
const threadId = turn.sessionThreadId;
|
|
91572
92374
|
const ctrl = activeStatusReactions.get(statusKey(chatId, threadId));
|
|
91573
|
-
const flushDecision = streamFinalizedAsAnswer ? { kind: "skip", reason: "reply-called" } :
|
|
92375
|
+
const flushDecision = streamFinalizedAsAnswer ? { kind: "skip", reason: "reply-called" } : decideTurnFlush2({
|
|
91574
92376
|
chatId: turn.sessionChatId,
|
|
91575
92377
|
replyCalled: turn.replyCalled,
|
|
91576
92378
|
capturedText: turn.capturedText,
|
|
@@ -91680,7 +92482,7 @@ function handleSessionEvent(ev) {
|
|
|
91680
92482
|
}) ?? { wasEmitted: false, turnKey: null };
|
|
91681
92483
|
const backstopCardMessageId = cardTakeover.wasEmitted && cardTakeover.turnKey != null ? getPinnedProgressCardMessageId?.(cardTakeover.turnKey) ?? null : null;
|
|
91682
92484
|
const backstopCardTurnKey = cardTakeover.turnKey;
|
|
91683
|
-
endCurrentTurnAtomic(turn);
|
|
92485
|
+
const backstopTurnEndedAt = endCurrentTurnAtomic(turn, { deferRecord: true });
|
|
91684
92486
|
preambleSuppressor.dropNow();
|
|
91685
92487
|
{
|
|
91686
92488
|
const tKey = statusKey(chatId, threadId);
|
|
@@ -91697,6 +92499,10 @@ function handleSessionEvent(ev) {
|
|
|
91697
92499
|
if (recentCount > 0) {
|
|
91698
92500
|
process.stderr.write(`telegram gateway: turn-flush suppressed \u2014 reply tool sent ${recentCount} message(s) within 2s
|
|
91699
92501
|
`);
|
|
92502
|
+
if (backstopTurnEndedAt != null) {
|
|
92503
|
+
turn.deliveryOutcome = "suppressed";
|
|
92504
|
+
emitTurnRecord(turn, backstopTurnEndedAt);
|
|
92505
|
+
}
|
|
91700
92506
|
return;
|
|
91701
92507
|
}
|
|
91702
92508
|
} catch {}
|
|
@@ -91708,10 +92514,12 @@ function handleSessionEvent(ev) {
|
|
|
91708
92514
|
link_preview_options: { is_disabled: true }
|
|
91709
92515
|
};
|
|
91710
92516
|
const limit = RICH_MESSAGE_MAX_CHARS2;
|
|
91711
|
-
|
|
91712
|
-
const htmlChunks = splitMarkdownChunks2(renderedText, limit);
|
|
92517
|
+
let htmlChunks = [];
|
|
91713
92518
|
const sentIds = [];
|
|
92519
|
+
let sendThrew = false;
|
|
91714
92520
|
try {
|
|
92521
|
+
const renderedText = addParagraphSpacers2(capturedText);
|
|
92522
|
+
htmlChunks = splitMarkdownChunks2(renderedText, limit);
|
|
91715
92523
|
let firstSendUsedEdit = false;
|
|
91716
92524
|
let liveThreadId = backstopThreadId;
|
|
91717
92525
|
if (backstopCardMessageId != null && htmlChunks.length > 0) {
|
|
@@ -91774,10 +92582,20 @@ function handleSessionEvent(ev) {
|
|
|
91774
92582
|
unpinProgressCardForChat?.(backstopChatId, backstopThreadId);
|
|
91775
92583
|
}
|
|
91776
92584
|
} catch (err) {
|
|
92585
|
+
sendThrew = true;
|
|
91777
92586
|
process.stderr.write(`telegram gateway: turn-flush send failed: ${err.message}
|
|
91778
92587
|
`);
|
|
91779
92588
|
if (backstopCtrl)
|
|
91780
92589
|
backstopCtrl.finalize("error");
|
|
92590
|
+
} finally {
|
|
92591
|
+
if (backstopTurnEndedAt != null) {
|
|
92592
|
+
finalizeBackstopSend(turn, {
|
|
92593
|
+
threw: sendThrew,
|
|
92594
|
+
sentCount: sentIds.length,
|
|
92595
|
+
chunkCount: htmlChunks.length
|
|
92596
|
+
});
|
|
92597
|
+
emitTurnRecord(turn, backstopTurnEndedAt);
|
|
92598
|
+
}
|
|
91781
92599
|
}
|
|
91782
92600
|
})();
|
|
91783
92601
|
return;
|
|
@@ -91819,21 +92637,40 @@ function handleSessionEvent(ev) {
|
|
|
91819
92637
|
ended_via: outboundMetrics.outboundCount > 0 ? "reply" : "silent"
|
|
91820
92638
|
});
|
|
91821
92639
|
if (turnEndDecision === "reprompt") {
|
|
91822
|
-
const
|
|
91823
|
-
|
|
91824
|
-
|
|
91825
|
-
|
|
91826
|
-
|
|
91827
|
-
|
|
91828
|
-
|
|
91829
|
-
}, silentEndDeps);
|
|
91830
|
-
if (silentEnd.exhausted) {
|
|
91831
|
-
process.stderr.write(`telegram gateway: WARN silent-end fallback \u2014 agent stayed ` + `silent after the Stop-hook re-prompt; delivering fallback message chat=${chatId} turnKey=${tKey} (#1161)
|
|
91832
|
-
`);
|
|
91833
|
-
retryWithThreadFallback2(robustApiCall, (tid) => bot.api.sendMessage(chatId, silentEndFallbackText(turnDurationMs), tid != null ? { message_thread_id: tid } : {}), { threadId, chat_id: chatId, verb: "silent-end-fallback.sendMessage" }).catch((err) => {
|
|
91834
|
-
process.stderr.write(`telegram gateway: silent-end fallback send failed: ${err instanceof Error ? err.message : String(err)}
|
|
92640
|
+
const proseDecision = CAPTURED_PROSE_DELIVERY_ENABLED ? decideCapturedProseDelivery({
|
|
92641
|
+
turnKey: tKey,
|
|
92642
|
+
turnId: turn.turnId,
|
|
92643
|
+
minChars: CAPTURED_PROSE_MIN_CHARS
|
|
92644
|
+
}) : { deliver: false, reason: "no-state" };
|
|
92645
|
+
if (proseDecision.deliver && proseDecision.text != null) {
|
|
92646
|
+
process.stderr.write(`telegram gateway: captured-prose delivery engaged on first silent-end chat=${chatId} turnKey=${tKey} (#3227)
|
|
91835
92647
|
`);
|
|
92648
|
+
deliverCapturedProse({
|
|
92649
|
+
chatId,
|
|
92650
|
+
threadId,
|
|
92651
|
+
statusKeyStr: tKey,
|
|
92652
|
+
registryKey: turn.registryKey ?? null,
|
|
92653
|
+
originTurnId: turn.turnId,
|
|
92654
|
+
text: proseDecision.text,
|
|
92655
|
+
turnDurationMs
|
|
91836
92656
|
});
|
|
92657
|
+
} else {
|
|
92658
|
+
const silentEndDeps = HISTORY_ENABLED ? {
|
|
92659
|
+
hasOutboundDeliveredSince: (cid, sinceMs, tid) => hasOutboundDeliveredSince(cid, sinceMs, tid, 1)
|
|
92660
|
+
} : undefined;
|
|
92661
|
+
const silentEnd = recordUndeliveredTurnEnd({
|
|
92662
|
+
chatId,
|
|
92663
|
+
threadId: threadId ?? null,
|
|
92664
|
+
turnKey: tKey
|
|
92665
|
+
}, silentEndDeps);
|
|
92666
|
+
if (silentEnd.exhausted) {
|
|
92667
|
+
process.stderr.write(`telegram gateway: WARN silent-end fallback \u2014 agent stayed ` + `silent after the Stop-hook re-prompt; delivering fallback message chat=${chatId} turnKey=${tKey} (#1161)
|
|
92668
|
+
`);
|
|
92669
|
+
retryWithThreadFallback2(robustApiCall, (tid) => bot.api.sendMessage(chatId, silentEndFallbackText(turnDurationMs), tid != null ? { message_thread_id: tid } : {}), { threadId, chat_id: chatId, verb: "silent-end-fallback.sendMessage" }).catch((err) => {
|
|
92670
|
+
process.stderr.write(`telegram gateway: silent-end fallback send failed: ${err instanceof Error ? err.message : String(err)}
|
|
92671
|
+
`);
|
|
92672
|
+
});
|
|
92673
|
+
}
|
|
91837
92674
|
}
|
|
91838
92675
|
}
|
|
91839
92676
|
clear(tKey);
|
|
@@ -98314,6 +99151,7 @@ var didOneTimeSetup = false;
|
|
|
98314
99151
|
},
|
|
98315
99152
|
floodWaitRemainingMs: probeFloodWaitRemainingMs,
|
|
98316
99153
|
maxRows: workerFeedMaxRows,
|
|
99154
|
+
staleWorkerTtlMs: resolveInflightTerminalCapMs() + WORKER_FEED_STALE_TTL_MARGIN_MS,
|
|
98317
99155
|
reconcilePin: ({ feedKey, chatId, messageId }) => {
|
|
98318
99156
|
if (!PIN_STATUS_WHILE_WORKING)
|
|
98319
99157
|
return;
|
|
@@ -98359,6 +99197,14 @@ var didOneTimeSetup = false;
|
|
|
98359
99197
|
process.stderr.write(`telegram gateway: worker ${agentId} NAMED AS LOST \u2014 falsely finalised twice, resurrection chain bound reached (issue #3023)
|
|
98360
99198
|
`);
|
|
98361
99199
|
},
|
|
99200
|
+
onTerminalCleanup: (agentId) => {
|
|
99201
|
+
try {
|
|
99202
|
+
workerActivityFeed?.terminate(agentId);
|
|
99203
|
+
} catch (err) {
|
|
99204
|
+
process.stderr.write(`telegram gateway: worker terminal-cleanup feed removal error agent=${agentId}: ${err.message}
|
|
99205
|
+
`);
|
|
99206
|
+
}
|
|
99207
|
+
},
|
|
98362
99208
|
onFinish: ({ agentId, outcome, description, resultText, toolCount, durationMs, background: entryBackground }) => {
|
|
98363
99209
|
deferredDoneReactions.promote();
|
|
98364
99210
|
let fleetChatId = "";
|