switchroom 0.18.19 → 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 +1073 -182
- package/telegram-plugin/format.ts +179 -20
- package/telegram-plugin/gateway/cron-session.ts +32 -0
- package/telegram-plugin/gateway/gateway.ts +775 -105
- 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 +9 -9
- 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/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/stream-reply-handler.ts +14 -5
- 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 +54 -34
- package/telegram-plugin/tests/formatting-parse-regression.test.ts +6 -5
- package/telegram-plugin/tests/formatting-torture-set.ts +1 -1
- package/telegram-plugin/tests/idle-clear.test.ts +315 -37
- 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 +5 -4
- package/telegram-plugin/tests/paragraph-normalizer.test.ts +100 -42
- 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/stream-reply-handler.test.ts +12 -9
- package/telegram-plugin/tests/subagent-watcher-narrative-early-paint.test.ts +218 -0
- package/telegram-plugin/tests/telegram-format.test.ts +36 -23
- package/telegram-plugin/tests/turn-flush-safety.test.ts +21 -17
- 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 +4 -4
- package/telegram-plugin/worker-activity-feed.ts +181 -30
|
@@ -6824,6 +6824,85 @@ function hardenCardBreaks(text) {
|
|
|
6824
6824
|
}
|
|
6825
6825
|
return restore(pieces.join(""));
|
|
6826
6826
|
}
|
|
6827
|
+
function addParagraphSpacers(text) {
|
|
6828
|
+
if (!text.includes(`
|
|
6829
|
+
|
|
6830
|
+
`))
|
|
6831
|
+
return text;
|
|
6832
|
+
const nonce = Math.random().toString(36).slice(2);
|
|
6833
|
+
const { masked, restore, placeholder } = maskCodeRegions(text, nonce);
|
|
6834
|
+
if (!masked.includes(`
|
|
6835
|
+
|
|
6836
|
+
`))
|
|
6837
|
+
return restore(masked);
|
|
6838
|
+
const SP = PARAGRAPH_SPACER;
|
|
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
|
+
};
|
|
6844
|
+
const blockKind = (line) => {
|
|
6845
|
+
if (isFenceOpenLine(line, placeholder))
|
|
6846
|
+
return "fence";
|
|
6847
|
+
if (isListItemLine(line))
|
|
6848
|
+
return "list";
|
|
6849
|
+
if (isTableRowLine(line) || isTableDelimiterLine(line))
|
|
6850
|
+
return "table";
|
|
6851
|
+
if (isBlockquoteLine(line))
|
|
6852
|
+
return "quote";
|
|
6853
|
+
if (isHeadingLine(line))
|
|
6854
|
+
return "heading";
|
|
6855
|
+
if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line.trimStart()))
|
|
6856
|
+
return "divider";
|
|
6857
|
+
return "prose";
|
|
6858
|
+
};
|
|
6859
|
+
const SAME_KIND_TIGHT = new Set([
|
|
6860
|
+
"list",
|
|
6861
|
+
"table",
|
|
6862
|
+
"quote",
|
|
6863
|
+
"fence",
|
|
6864
|
+
"divider"
|
|
6865
|
+
]);
|
|
6866
|
+
const shouldSpaceGap = (above, below) => {
|
|
6867
|
+
const a = blockKind(above);
|
|
6868
|
+
const b = blockKind(below);
|
|
6869
|
+
if (a === b && SAME_KIND_TIGHT.has(a))
|
|
6870
|
+
return false;
|
|
6871
|
+
return true;
|
|
6872
|
+
};
|
|
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
|
+
}
|
|
6883
|
+
const out = [];
|
|
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);
|
|
6901
|
+
}
|
|
6902
|
+
}
|
|
6903
|
+
return restore(out.join(`
|
|
6904
|
+
`));
|
|
6905
|
+
}
|
|
6827
6906
|
function normalizePunctuation(text) {
|
|
6828
6907
|
if (!/[\u2014\u2013\u2022\u00b7]/.test(text))
|
|
6829
6908
|
return text;
|
|
@@ -7051,10 +7130,11 @@ function splitMarkdownChunks(text, maxLen = RICH_MESSAGE_MAX_CHARS) {
|
|
|
7051
7130
|
return chunks.map((c) => stripBoundarySpacers(c, "trailing"));
|
|
7052
7131
|
}
|
|
7053
7132
|
function stripBoundarySpacers(chunk, side) {
|
|
7133
|
+
const sp = PARAGRAPH_SPACER;
|
|
7054
7134
|
if (side === "leading") {
|
|
7055
|
-
return chunk.replace(
|
|
7135
|
+
return chunk.replace(new RegExp(`^(?:[ \\t]*${sp}?[ \\t]*\\n+)+`), "");
|
|
7056
7136
|
}
|
|
7057
|
-
return chunk.replace(
|
|
7137
|
+
return chunk.replace(new RegExp(`(?:\\n+[ \\t]*${sp}?[ \\t]*)+$`), "");
|
|
7058
7138
|
}
|
|
7059
7139
|
function backOffOpenFence(text, cut) {
|
|
7060
7140
|
if (cut <= 0 || cut >= text.length)
|
|
@@ -7103,7 +7183,7 @@ function backOffOpenInline(text, cut) {
|
|
|
7103
7183
|
}
|
|
7104
7184
|
return earliest;
|
|
7105
7185
|
}
|
|
7106
|
-
var RICH_MESSAGE_MAX_CHARS = 32768, INLINE_SPAN_PATTERNS;
|
|
7186
|
+
var RICH_MESSAGE_MAX_CHARS = 32768, PARAGRAPH_SPACER = "\u00a0", INLINE_SPAN_PATTERNS;
|
|
7107
7187
|
var init_format = __esm(() => {
|
|
7108
7188
|
INLINE_SPAN_PATTERNS = [
|
|
7109
7189
|
/`[^`\n]+`/g,
|
|
@@ -40217,27 +40297,43 @@ function renderActivityFeedWithNested(lines, childLines, final = false, liveSuff
|
|
|
40217
40297
|
});
|
|
40218
40298
|
}
|
|
40219
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
|
+
}
|
|
40220
40308
|
function renderCombinedWorkerFeed(rows, opts) {
|
|
40221
40309
|
if (rows.length === 0)
|
|
40222
40310
|
return null;
|
|
40223
40311
|
const maxRows = Math.max(1, Math.floor(opts.maxRows));
|
|
40224
|
-
const
|
|
40312
|
+
const rowHeader = (r) => {
|
|
40225
40313
|
const desc = escapeMarkdown(truncate(stripMarkdown(r.description).replace(/\s+/g, " ").trim() || "background task", COMBINED_ROW_DESC_MAX));
|
|
40226
40314
|
const toolWord = r.toolCount === 1 ? "tool" : "tools";
|
|
40227
40315
|
const modelLabel = formatModelLabel(r.model);
|
|
40228
40316
|
const modelPart = modelLabel != null ? ` \u00b7 ${escapeMarkdown(modelLabel)}` : "";
|
|
40229
|
-
|
|
40230
|
-
|
|
40231
|
-
|
|
40232
|
-
|
|
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);
|
|
40233
40322
|
};
|
|
40234
40323
|
const compose = (visibleCount) => {
|
|
40235
40324
|
const shown = rows.slice(0, visibleCount);
|
|
40236
40325
|
const hidden = rows.length - shown.length;
|
|
40326
|
+
const depth = combinedHistoryDepth(shown.length);
|
|
40237
40327
|
const out = [`\uD83D\uDEE0 **Workers** \u00b7 _${rows.length} running_`];
|
|
40238
40328
|
for (const r of shown) {
|
|
40239
|
-
|
|
40240
|
-
|
|
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);
|
|
40241
40337
|
}
|
|
40242
40338
|
if (hidden > 0)
|
|
40243
40339
|
out.push(`_+${hidden} more working\u2026_`);
|
|
@@ -40314,7 +40410,7 @@ function isWorkerActivityFeedEnabled(envVal) {
|
|
|
40314
40410
|
var DESC_MAX = 80;
|
|
40315
40411
|
function renderWorkerActivity(v, liveSuffix = "") {
|
|
40316
40412
|
const desc = truncate(stripMarkdown(v.description).trim() || "background task", DESC_MAX);
|
|
40317
|
-
const finished = v.state === "done" || v.state === "failed";
|
|
40413
|
+
const finished = v.state === "done" || v.state === "failed" || v.state === "incomplete";
|
|
40318
40414
|
const rawSteps = (v.narrativeLines ?? []).filter((s) => s != null && s.trim().length > 0);
|
|
40319
40415
|
let steps = rawSteps;
|
|
40320
40416
|
if (steps.length === 0 && !finished) {
|
|
@@ -40332,7 +40428,7 @@ function renderWorkerActivity(v, liveSuffix = "") {
|
|
|
40332
40428
|
model: v.model
|
|
40333
40429
|
};
|
|
40334
40430
|
let result;
|
|
40335
|
-
if (finished) {
|
|
40431
|
+
if (finished && v.state !== "incomplete") {
|
|
40336
40432
|
const text = cleanWorkerResultParagraph(v.latestSummary);
|
|
40337
40433
|
if (text.length > 0)
|
|
40338
40434
|
result = { emoji: v.state === "done" ? "\u2705" : "\u26a0\ufe0f", text };
|
|
@@ -40386,6 +40482,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
40386
40482
|
const firstPaintMin = opts.firstPaintMinMs ?? 8000;
|
|
40387
40483
|
const heartbeatTickMs = opts.heartbeatTickMs ?? 6000;
|
|
40388
40484
|
const maxRows = Math.max(1, Math.floor(opts.maxRows ?? 8));
|
|
40485
|
+
const staleWorkerTtlMs = Math.max(1, Math.floor(opts.staleWorkerTtlMs ?? 50 * 60000));
|
|
40389
40486
|
const reconcilePinFn = opts.reconcilePin ?? (() => {});
|
|
40390
40487
|
const setIntervalFn = opts.setInterval ?? ((cb, ms) => {
|
|
40391
40488
|
const t = setInterval(cb, ms);
|
|
@@ -40487,6 +40584,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
40487
40584
|
elapsedMs: elapsedFor(r),
|
|
40488
40585
|
toolCount: v.toolCount,
|
|
40489
40586
|
currentStep,
|
|
40587
|
+
historyLines: r.narrative.length > 0 ? [...r.narrative] : undefined,
|
|
40490
40588
|
model: v.model
|
|
40491
40589
|
};
|
|
40492
40590
|
});
|
|
@@ -40606,8 +40704,60 @@ function createWorkerActivityFeed(opts) {
|
|
|
40606
40704
|
log(`worker-feed: edit transient error feed=${g.feedKey}: ${err.message}`);
|
|
40607
40705
|
}
|
|
40608
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
|
+
}
|
|
40609
40747
|
function heartbeatTick() {
|
|
40610
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
|
+
}
|
|
40611
40761
|
for (const g of [...groups.values()]) {
|
|
40612
40762
|
if (g.pendingFinalize != null && now >= g.cooldownUntil) {
|
|
40613
40763
|
const recap = g.pendingFinalize;
|
|
@@ -40693,12 +40843,14 @@ function createWorkerActivityFeed(opts) {
|
|
|
40693
40843
|
lastView: null,
|
|
40694
40844
|
state: "running",
|
|
40695
40845
|
finished: false,
|
|
40846
|
+
lastUpdateAt: nowFn(),
|
|
40696
40847
|
dispatchAtMs: null,
|
|
40697
40848
|
stepStartedAtMs: null
|
|
40698
40849
|
};
|
|
40699
40850
|
g.workers.set(agentId, row);
|
|
40700
40851
|
agentIndex.set(agentId, feedKey);
|
|
40701
40852
|
}
|
|
40853
|
+
row.lastUpdateAt = nowFn();
|
|
40702
40854
|
accumulateNarrative(row, view);
|
|
40703
40855
|
row.state = "running";
|
|
40704
40856
|
row.lastView = { ...view, narrativeLines: [...row.narrative] };
|
|
@@ -40717,23 +40869,10 @@ function createWorkerActivityFeed(opts) {
|
|
|
40717
40869
|
markFinalized(agentId);
|
|
40718
40870
|
return Promise.resolve();
|
|
40719
40871
|
}
|
|
40720
|
-
row
|
|
40721
|
-
|
|
40722
|
-
|
|
40723
|
-
|
|
40724
|
-
group.chain = group.chain.then(() => {
|
|
40725
|
-
const others = runningRows(group).filter((w) => w.agentId !== agentId);
|
|
40726
|
-
if (others.length > 0) {
|
|
40727
|
-
removeWorker(group, agentId);
|
|
40728
|
-
syncPin(group);
|
|
40729
|
-
return doRender(group, { force: true });
|
|
40730
|
-
}
|
|
40731
|
-
const recap = { ...view, narrativeLines: [...row.narrative] };
|
|
40732
|
-
return doRender(group, { force: true, terminalRecap: recap, finishingAgentId: agentId });
|
|
40733
|
-
}).catch((err) => {
|
|
40734
|
-
log(`worker-feed: finish chain error ${agentId}: ${err.message}`);
|
|
40735
|
-
});
|
|
40736
|
-
return group.chain;
|
|
40872
|
+
return finalizeWorker(g, agentId, row, view);
|
|
40873
|
+
},
|
|
40874
|
+
terminate(agentId) {
|
|
40875
|
+
return terminateWorker(agentId);
|
|
40737
40876
|
},
|
|
40738
40877
|
drop(agentId) {
|
|
40739
40878
|
markFinalized(agentId);
|
|
@@ -45037,6 +45176,9 @@ function runSilentTurnHeartbeatTick(view, deps) {
|
|
|
45037
45176
|
|
|
45038
45177
|
// narrative-dedup.ts
|
|
45039
45178
|
var REPLY_TOOLS = new Set(["reply", "stream_reply"]);
|
|
45179
|
+
|
|
45180
|
+
// narrative-dedup.ts
|
|
45181
|
+
var REPLY_TOOLS2 = new Set(["reply", "stream_reply"]);
|
|
45040
45182
|
function normalizeNarrative(s) {
|
|
45041
45183
|
return s.replace(/[*_`>#~]/g, "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
45042
45184
|
}
|
|
@@ -45056,6 +45198,83 @@ function isDraftOfReply(textBlock, replyText) {
|
|
|
45056
45198
|
return prefixSimilarity(textBlock, replyText) >= DRAFT_SUPPRESS_THRESHOLD;
|
|
45057
45199
|
}
|
|
45058
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
|
+
|
|
45059
45278
|
// tool-labels.ts
|
|
45060
45279
|
var MAX_LABEL_CHARS = 60;
|
|
45061
45280
|
var MAX_BASH_CHARS = 40;
|
|
@@ -61788,6 +62007,7 @@ function startTimer(deps) {
|
|
|
61788
62007
|
var EDIT_INTERVAL_MS = 60000;
|
|
61789
62008
|
var POLL_INTERVAL_MS = 5000;
|
|
61790
62009
|
var MAX_LIFETIME_MS = 30 * 60000;
|
|
62010
|
+
var BACKGROUND_WORK_SUPPRESS_TTL_MS = MAX_LIFETIME_MS;
|
|
61791
62011
|
var TELEGRAM_MSG_CAP2 = 32768;
|
|
61792
62012
|
var SUFFIX_RE = /\n\n(?:\u2014 |_)still working \(\d+m\)( \u00b7 message me anytime, I'll keep you posted)?_?$/;
|
|
61793
62013
|
var stateByKey = new Map;
|
|
@@ -61805,6 +62025,7 @@ function ensure(key) {
|
|
|
61805
62025
|
if (!s) {
|
|
61806
62026
|
s = {
|
|
61807
62027
|
pending: false,
|
|
62028
|
+
dispatchedAt: null,
|
|
61808
62029
|
anchorMessageId: null,
|
|
61809
62030
|
anchorOriginalText: "",
|
|
61810
62031
|
anchorLiteralText: false,
|
|
@@ -61818,7 +62039,9 @@ function ensure(key) {
|
|
|
61818
62039
|
function noteAsyncDispatch(key) {
|
|
61819
62040
|
if (!enabled4())
|
|
61820
62041
|
return;
|
|
61821
|
-
ensure(key)
|
|
62042
|
+
const s = ensure(key);
|
|
62043
|
+
s.pending = true;
|
|
62044
|
+
s.dispatchedAt = nowMs();
|
|
61822
62045
|
}
|
|
61823
62046
|
function noteOutbound3(key, opts) {
|
|
61824
62047
|
if (!enabled4())
|
|
@@ -61848,6 +62071,17 @@ function noteTurnEnd(key) {
|
|
|
61848
62071
|
function hasPendingAsyncDispatch(key) {
|
|
61849
62072
|
return stateByKey.get(key)?.pending === true;
|
|
61850
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
|
+
}
|
|
61851
62085
|
function clearPending(key, reason) {
|
|
61852
62086
|
if (!stateByKey.has(key))
|
|
61853
62087
|
return;
|
|
@@ -62015,6 +62249,30 @@ function clearSilentEndState(turnKey, deps) {
|
|
|
62015
62249
|
`);
|
|
62016
62250
|
} catch {}
|
|
62017
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
|
+
}
|
|
62018
62276
|
function readSilentEndState(deps) {
|
|
62019
62277
|
const statePath = resolveStatePath2(deps);
|
|
62020
62278
|
if (!existsSync14(statePath))
|
|
@@ -62120,6 +62378,56 @@ function isSilentFlushMarker(text4) {
|
|
|
62120
62378
|
return SILENT_MARKERS.has(trimmed.toUpperCase());
|
|
62121
62379
|
}
|
|
62122
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
|
+
}
|
|
62123
62431
|
|
|
62124
62432
|
// answer-stream.ts
|
|
62125
62433
|
var MIN_INITIAL_CHARS = 50;
|
|
@@ -66523,6 +66831,86 @@ function hardenCardBreaks2(text4) {
|
|
|
66523
66831
|
}
|
|
66524
66832
|
return restore2(pieces.join(""));
|
|
66525
66833
|
}
|
|
66834
|
+
var PARAGRAPH_SPACER2 = "\u00a0";
|
|
66835
|
+
function addParagraphSpacers2(text4) {
|
|
66836
|
+
if (!text4.includes(`
|
|
66837
|
+
|
|
66838
|
+
`))
|
|
66839
|
+
return text4;
|
|
66840
|
+
const nonce = Math.random().toString(36).slice(2);
|
|
66841
|
+
const { masked, restore: restore2, placeholder } = maskCodeRegions2(text4, nonce);
|
|
66842
|
+
if (!masked.includes(`
|
|
66843
|
+
|
|
66844
|
+
`))
|
|
66845
|
+
return restore2(masked);
|
|
66846
|
+
const SP = PARAGRAPH_SPACER2;
|
|
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
|
+
};
|
|
66852
|
+
const blockKind = (line) => {
|
|
66853
|
+
if (isFenceOpenLine2(line, placeholder))
|
|
66854
|
+
return "fence";
|
|
66855
|
+
if (isListItemLine2(line))
|
|
66856
|
+
return "list";
|
|
66857
|
+
if (isTableRowLine2(line) || isTableDelimiterLine2(line))
|
|
66858
|
+
return "table";
|
|
66859
|
+
if (isBlockquoteLine2(line))
|
|
66860
|
+
return "quote";
|
|
66861
|
+
if (isHeadingLine2(line))
|
|
66862
|
+
return "heading";
|
|
66863
|
+
if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line.trimStart()))
|
|
66864
|
+
return "divider";
|
|
66865
|
+
return "prose";
|
|
66866
|
+
};
|
|
66867
|
+
const SAME_KIND_TIGHT = new Set([
|
|
66868
|
+
"list",
|
|
66869
|
+
"table",
|
|
66870
|
+
"quote",
|
|
66871
|
+
"fence",
|
|
66872
|
+
"divider"
|
|
66873
|
+
]);
|
|
66874
|
+
const shouldSpaceGap = (above, below) => {
|
|
66875
|
+
const a = blockKind(above);
|
|
66876
|
+
const b = blockKind(below);
|
|
66877
|
+
if (a === b && SAME_KIND_TIGHT.has(a))
|
|
66878
|
+
return false;
|
|
66879
|
+
return true;
|
|
66880
|
+
};
|
|
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
|
+
}
|
|
66891
|
+
const out = [];
|
|
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);
|
|
66909
|
+
}
|
|
66910
|
+
}
|
|
66911
|
+
return restore2(out.join(`
|
|
66912
|
+
`));
|
|
66913
|
+
}
|
|
66526
66914
|
function normalizePunctuation2(text4) {
|
|
66527
66915
|
if (!/[\u2014\u2013\u2022\u00b7]/.test(text4))
|
|
66528
66916
|
return text4;
|
|
@@ -66750,10 +67138,11 @@ function splitMarkdownChunks2(text4, maxLen = RICH_MESSAGE_MAX_CHARS2) {
|
|
|
66750
67138
|
return chunks.map((c) => stripBoundarySpacers2(c, "trailing"));
|
|
66751
67139
|
}
|
|
66752
67140
|
function stripBoundarySpacers2(chunk2, side) {
|
|
67141
|
+
const sp = PARAGRAPH_SPACER2;
|
|
66753
67142
|
if (side === "leading") {
|
|
66754
|
-
return chunk2.replace(
|
|
67143
|
+
return chunk2.replace(new RegExp(`^(?:[ \\t]*${sp}?[ \\t]*\\n+)+`), "");
|
|
66755
67144
|
}
|
|
66756
|
-
return chunk2.replace(
|
|
67145
|
+
return chunk2.replace(new RegExp(`(?:\\n+[ \\t]*${sp}?[ \\t]*)+$`), "");
|
|
66757
67146
|
}
|
|
66758
67147
|
function backOffOpenFence2(text4, cut) {
|
|
66759
67148
|
if (cut <= 0 || cut >= text4.length)
|
|
@@ -66943,8 +67332,8 @@ function normalizeOutboundBody(rawText, site, redact2) {
|
|
|
66943
67332
|
}
|
|
66944
67333
|
return { text: text4, voiceReplaced };
|
|
66945
67334
|
}
|
|
66946
|
-
function computeEffectiveText(text4,
|
|
66947
|
-
return text4;
|
|
67335
|
+
function computeEffectiveText(text4, literalText) {
|
|
67336
|
+
return literalText ? text4 : addParagraphSpacers(text4);
|
|
66948
67337
|
}
|
|
66949
67338
|
function computeReplyChunks(args) {
|
|
66950
67339
|
const { effectiveText, literalText, limit, chunkMode } = args;
|
|
@@ -67612,7 +68001,7 @@ function isSilentFlushMarker2(text4) {
|
|
|
67612
68001
|
return SILENT_MARKERS2.has(trimmed.toUpperCase());
|
|
67613
68002
|
}
|
|
67614
68003
|
var TRIVIAL_CONFIRMATIONS2 = new Set(["SENT", "DONE", "OK", "OKAY", "ACK"]);
|
|
67615
|
-
function
|
|
68004
|
+
function isTrivialConfirmationLine2(line) {
|
|
67616
68005
|
let t = line.trim();
|
|
67617
68006
|
if (t.length === 0 || t.length > 8)
|
|
67618
68007
|
return false;
|
|
@@ -67620,7 +68009,7 @@ function isTrivialConfirmationLine(line) {
|
|
|
67620
68009
|
t = t.slice(0, -1);
|
|
67621
68010
|
return TRIVIAL_CONFIRMATIONS2.has(t.toUpperCase());
|
|
67622
68011
|
}
|
|
67623
|
-
function
|
|
68012
|
+
function isCompositeSilentNoise2(text4) {
|
|
67624
68013
|
if (typeof text4 !== "string")
|
|
67625
68014
|
return false;
|
|
67626
68015
|
const lines = text4.split(`
|
|
@@ -67630,9 +68019,9 @@ function isCompositeSilentNoise(text4) {
|
|
|
67630
68019
|
const hasMarker = lines.some((l) => isSilentFlushMarker2(l));
|
|
67631
68020
|
if (!hasMarker)
|
|
67632
68021
|
return false;
|
|
67633
|
-
return lines.every((l) => isSilentFlushMarker2(l) ||
|
|
68022
|
+
return lines.every((l) => isSilentFlushMarker2(l) || isTrivialConfirmationLine2(l));
|
|
67634
68023
|
}
|
|
67635
|
-
function
|
|
68024
|
+
function endsWithSilentMarker2(text4) {
|
|
67636
68025
|
if (typeof text4 !== "string")
|
|
67637
68026
|
return false;
|
|
67638
68027
|
const lines = text4.split(`
|
|
@@ -67641,7 +68030,7 @@ function endsWithSilentMarker(text4) {
|
|
|
67641
68030
|
return false;
|
|
67642
68031
|
return isSilentFlushMarker2(lines[lines.length - 1]);
|
|
67643
68032
|
}
|
|
67644
|
-
function
|
|
68033
|
+
function decideTurnFlush2(input) {
|
|
67645
68034
|
const flushEnabled = input.flushEnabled !== false;
|
|
67646
68035
|
if (!flushEnabled)
|
|
67647
68036
|
return { kind: "skip", reason: "flag-disabled" };
|
|
@@ -67656,9 +68045,9 @@ function decideTurnFlush(input) {
|
|
|
67656
68045
|
return { kind: "skip", reason: "empty-text" };
|
|
67657
68046
|
if (isSilentFlushMarker2(joined))
|
|
67658
68047
|
return { kind: "skip", reason: "silent-marker" };
|
|
67659
|
-
if (
|
|
68048
|
+
if (isCompositeSilentNoise2(joined))
|
|
67660
68049
|
return { kind: "skip", reason: "silent-marker" };
|
|
67661
|
-
if (
|
|
68050
|
+
if (endsWithSilentMarker2(joined))
|
|
67662
68051
|
return { kind: "skip", reason: "silent-marker" };
|
|
67663
68052
|
return { kind: "flush", text: joined };
|
|
67664
68053
|
}
|
|
@@ -67672,6 +68061,72 @@ function isTurnFlushSafetyEnabled(env = process.env) {
|
|
|
67672
68061
|
return true;
|
|
67673
68062
|
}
|
|
67674
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
|
+
|
|
67675
68130
|
// gateway/turn-end-gate.ts
|
|
67676
68131
|
function decideTurnEndGate(snapshot) {
|
|
67677
68132
|
const { flushDecision, finalAnswerDelivered } = snapshot;
|
|
@@ -69098,11 +69553,12 @@ async function injectSlashCommand(agentName3, command, opts = {}) {
|
|
|
69098
69553
|
session,
|
|
69099
69554
|
command: command.trim(),
|
|
69100
69555
|
settleMs,
|
|
69101
|
-
timeoutMs
|
|
69556
|
+
timeoutMs,
|
|
69557
|
+
precondition: opts.precondition
|
|
69102
69558
|
}));
|
|
69103
69559
|
}
|
|
69104
69560
|
async function injectSlashCommandWith(runner, args) {
|
|
69105
|
-
const { socket, session, command, settleMs, timeoutMs } = args;
|
|
69561
|
+
const { socket, session, command, settleMs, timeoutMs, precondition } = args;
|
|
69106
69562
|
let bareVerb;
|
|
69107
69563
|
try {
|
|
69108
69564
|
bareVerb = validateInjectCommand(command);
|
|
@@ -69132,6 +69588,17 @@ async function injectSlashCommandWith(runner, args) {
|
|
|
69132
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.`
|
|
69133
69589
|
};
|
|
69134
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
|
+
}
|
|
69135
69602
|
const before = runner.capture(socket, session) ?? "";
|
|
69136
69603
|
try {
|
|
69137
69604
|
runner.send(socket, session, ["send-keys", "-l", command]);
|
|
@@ -69336,6 +69803,12 @@ _truncated_`;
|
|
|
69336
69803
|
}
|
|
69337
69804
|
return { body: verbHtml, accent: "done" };
|
|
69338
69805
|
}
|
|
69806
|
+
if (result.outcome === "skipped") {
|
|
69807
|
+
return {
|
|
69808
|
+
body: `${verbHtml} \u2014 skipped (precondition not met at send time)`,
|
|
69809
|
+
accent: "issue"
|
|
69810
|
+
};
|
|
69811
|
+
}
|
|
69339
69812
|
const code2 = result.errorCode ?? "tmux_failed";
|
|
69340
69813
|
const msg = result.errorMessage ?? "unknown error";
|
|
69341
69814
|
if (code2 === "blocked") {
|
|
@@ -72018,6 +72491,8 @@ function decideIdleClear(state3, now) {
|
|
|
72018
72491
|
return { clear: false };
|
|
72019
72492
|
if (state3.turnInFlight)
|
|
72020
72493
|
return { clear: false };
|
|
72494
|
+
if (state3.backgroundWorkInFlight)
|
|
72495
|
+
return { clear: false };
|
|
72021
72496
|
if (state3.alreadyCleared)
|
|
72022
72497
|
return { clear: false };
|
|
72023
72498
|
if (now - lastEventAt(state3) < state3.idleClearMs)
|
|
@@ -72029,6 +72504,67 @@ function classifyIdleEvent(kind, durationMs) {
|
|
|
72029
72504
|
const synthetic = turnEnded && durationMs === -1;
|
|
72030
72505
|
return { activity: !synthetic, turnEnded };
|
|
72031
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
|
+
}
|
|
72032
72568
|
function idleDurationToMs(raw) {
|
|
72033
72569
|
const m = /^(\d+)([smh])$/.exec(raw.trim());
|
|
72034
72570
|
if (!m)
|
|
@@ -74350,6 +74886,9 @@ function cronIdentity(agent) {
|
|
|
74350
74886
|
function isCronIdentity(name) {
|
|
74351
74887
|
return typeof name === "string" && name.endsWith(CRON_IDENTITY_SUFFIX);
|
|
74352
74888
|
}
|
|
74889
|
+
function isCronInjectFire(meta) {
|
|
74890
|
+
return meta?.source === "cron" || meta?.session === "cron";
|
|
74891
|
+
}
|
|
74353
74892
|
function resolveInjectTarget(agentName3, meta) {
|
|
74354
74893
|
return meta?.session === "cron" ? cronIdentity(agentName3) : agentName3;
|
|
74355
74894
|
}
|
|
@@ -75612,6 +76151,44 @@ function maybeRotate(path2, fs2, maxBytes = TURNS_JSONL_MAX_BYTES) {
|
|
|
75612
76151
|
return true;
|
|
75613
76152
|
}
|
|
75614
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
|
+
|
|
75615
76192
|
// gateway/inbound-delivery-confirm.ts
|
|
75616
76193
|
function createDeliveryQueue() {
|
|
75617
76194
|
return { pending: new Map };
|
|
@@ -78210,25 +78787,81 @@ function redactSecrets(text4) {
|
|
|
78210
78787
|
return out;
|
|
78211
78788
|
}
|
|
78212
78789
|
|
|
78213
|
-
// narrative-
|
|
78214
|
-
var
|
|
78215
|
-
|
|
78216
|
-
|
|
78217
|
-
|
|
78218
|
-
|
|
78219
|
-
|
|
78220
|
-
|
|
78221
|
-
|
|
78222
|
-
|
|
78223
|
-
|
|
78224
|
-
|
|
78225
|
-
|
|
78226
|
-
|
|
78227
|
-
|
|
78228
|
-
|
|
78229
|
-
|
|
78230
|
-
|
|
78231
|
-
|
|
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
|
+
}
|
|
78232
78865
|
}
|
|
78233
78866
|
|
|
78234
78867
|
// subagent-watcher.ts
|
|
@@ -78365,6 +78998,9 @@ var DEFAULT_STALL_THRESHOLD_MS = 60000;
|
|
|
78365
78998
|
var DEFAULT_SILENT_SYNTHESIS_STALL_THRESHOLD_MS = 300000;
|
|
78366
78999
|
var DEFAULT_SILENT_STALL_TERMINAL_MS = 300000;
|
|
78367
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
|
+
}
|
|
78368
79004
|
var DEFAULT_DEFERRAL_LOG_INTERVAL_MS = 60000;
|
|
78369
79005
|
var LONG_RUNNING_TOOLS = new Set(["Bash"]);
|
|
78370
79006
|
var MAX_RESURRECTIONS = 1;
|
|
@@ -78478,6 +79114,8 @@ function backfillJsonlAgentId(db2, jsonlPath, agentId, log) {
|
|
|
78478
79114
|
}
|
|
78479
79115
|
function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, parentStateDir, onUnstall, onFileVanished, onProgress) {
|
|
78480
79116
|
try {
|
|
79117
|
+
if (entry.narrativeGate != null)
|
|
79118
|
+
entry.narrativeGate.tick(now);
|
|
78481
79119
|
const stat = fs2.statSync(entry.filePath);
|
|
78482
79120
|
if (stat.size < tail.cursor) {
|
|
78483
79121
|
tail.cursor = 0;
|
|
@@ -78538,43 +79176,87 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
|
|
|
78538
79176
|
entry.errorDetail = errInfo.detail.slice(0, SUBAGENT_RESULT_TEXT_MAX);
|
|
78539
79177
|
}
|
|
78540
79178
|
const events = projectSubagentLine(line, entry.agentId, startState);
|
|
78541
|
-
const
|
|
78542
|
-
|
|
78543
|
-
|
|
78544
|
-
|
|
78545
|
-
onProgress
|
|
78546
|
-
|
|
78547
|
-
|
|
78548
|
-
|
|
78549
|
-
|
|
78550
|
-
|
|
78551
|
-
|
|
78552
|
-
|
|
78553
|
-
|
|
78554
|
-
|
|
78555
|
-
|
|
78556
|
-
|
|
78557
|
-
|
|
78558
|
-
|
|
78559
|
-
|
|
78560
|
-
|
|
78561
|
-
|
|
78562
|
-
|
|
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
|
+
};
|
|
78563
79252
|
};
|
|
78564
79253
|
const resolvePendingSubNarrative = (toolName, toolInput) => {
|
|
78565
|
-
if (entry.
|
|
79254
|
+
if (entry.narrativeGate == null)
|
|
78566
79255
|
return false;
|
|
78567
|
-
|
|
78568
|
-
|
|
78569
|
-
if (toolName != null && REPLY_TOOLS2.has(toolName)) {
|
|
78570
|
-
const replyText = typeof toolInput?.text === "string" ? toolInput.text : "";
|
|
78571
|
-
if (isDraftOfReply2(pending.text, replyText))
|
|
78572
|
-
return false;
|
|
78573
|
-
} else if (toolName == null && entry.lastReplyText != null && entry.lastReplyText.length > 0) {
|
|
78574
|
-
if (isDraftOfReply2(pending.text, entry.lastReplyText))
|
|
78575
|
-
return false;
|
|
79256
|
+
if (toolName == null) {
|
|
79257
|
+
return entry.narrativeGate.resolveAtTurnEnd(entry.lastReplyText ?? "");
|
|
78576
79258
|
}
|
|
78577
|
-
return
|
|
79259
|
+
return entry.narrativeGate.resolveOnTool(toolName, toolInput);
|
|
78578
79260
|
};
|
|
78579
79261
|
for (const ev of events) {
|
|
78580
79262
|
const idleSecBeforeBump = Math.round((now - entry.lastActivityAt) / 1000);
|
|
@@ -78681,10 +79363,9 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
|
|
|
78681
79363
|
} else if (ev.kind === "sub_agent_text") {
|
|
78682
79364
|
entry.lastSummaryLine = clipNarrative(ev.text);
|
|
78683
79365
|
entry.lastResultText = ev.text.trim().slice(0, SUBAGENT_RESULT_TEXT_MAX);
|
|
78684
|
-
if (entry.
|
|
78685
|
-
|
|
78686
|
-
|
|
78687
|
-
entry.pendingNarrative = { text: ev.text };
|
|
79366
|
+
if (entry.narrativeGate == null)
|
|
79367
|
+
entry.narrativeGate = buildNarrativeGate();
|
|
79368
|
+
entry.narrativeGate.stage(ev.text);
|
|
78688
79369
|
} else if (ev.kind === "sub_agent_tool_result") {
|
|
78689
79370
|
if (ev.toolUseId != null && ev.toolUseId !== "") {
|
|
78690
79371
|
entry.inflightToolUseIds.delete(ev.toolUseId);
|
|
@@ -78731,7 +79412,7 @@ function startSubagentWatcher(config) {
|
|
|
78731
79412
|
const stallThresholdMs = config.stallThresholdMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_STALL_MS") ?? DEFAULT_STALL_THRESHOLD_MS;
|
|
78732
79413
|
const silentSynthesisStallThresholdMs = config.silentSynthesisStallThresholdMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_SILENT_SYNTH_STALL_MS") ?? DEFAULT_SILENT_SYNTHESIS_STALL_THRESHOLD_MS;
|
|
78733
79414
|
const silentStallTerminalMs = config.silentStallTerminalMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_STALL_TERMINAL_MS") ?? DEFAULT_SILENT_STALL_TERMINAL_MS;
|
|
78734
|
-
const inflightTerminalCapMs = config.inflightTerminalCapMs
|
|
79415
|
+
const inflightTerminalCapMs = resolveInflightTerminalCapMs(config.inflightTerminalCapMs);
|
|
78735
79416
|
const deferralLogIntervalMs = config.deferralLogIntervalMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_DEFERRAL_LOG_INTERVAL_MS") ?? DEFAULT_DEFERRAL_LOG_INTERVAL_MS;
|
|
78736
79417
|
const inflightPromoteMaxAgeMs = config.inflightPromoteMaxAgeMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_INFLIGHT_MAX_AGE_MS") ?? DEFAULT_INFLIGHT_PROMOTE_MAX_AGE_MS;
|
|
78737
79418
|
const terminatedAgentIdsCap = config.terminatedAgentIdsCap ?? TERMINATED_AGENT_IDS_CAP;
|
|
@@ -78892,7 +79573,8 @@ function startSubagentWatcher(config) {
|
|
|
78892
79573
|
}
|
|
78893
79574
|
entry.toolCount = 0;
|
|
78894
79575
|
entry.lastTool = null;
|
|
78895
|
-
entry.
|
|
79576
|
+
entry.narrativeGate?.reset();
|
|
79577
|
+
entry.narrativeGate = null;
|
|
78896
79578
|
tail.cursor = 0;
|
|
78897
79579
|
tail.pendingPartial = "";
|
|
78898
79580
|
tail.hasEmittedStart = false;
|
|
@@ -78991,6 +79673,13 @@ function startSubagentWatcher(config) {
|
|
|
78991
79673
|
}
|
|
78992
79674
|
terminatedAgentIds.add(agentId);
|
|
78993
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
|
+
}
|
|
78994
79683
|
}
|
|
78995
79684
|
function recordFalseFinish(agentId, filePath, n) {
|
|
78996
79685
|
let size = 0;
|
|
@@ -82034,10 +82723,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
|
|
|
82034
82723
|
}
|
|
82035
82724
|
|
|
82036
82725
|
// ../src/build-info.ts
|
|
82037
|
-
var VERSION = "0.18.
|
|
82038
|
-
var COMMIT_SHA = "
|
|
82039
|
-
var COMMIT_DATE = "2026-07-
|
|
82040
|
-
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;
|
|
82041
82730
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
82042
82731
|
|
|
82043
82732
|
// gateway/boot-version.ts
|
|
@@ -84462,6 +85151,7 @@ function resolveSubagentOriginChat(agentId) {
|
|
|
84462
85151
|
}
|
|
84463
85152
|
}
|
|
84464
85153
|
var WORKER_FEED_FALLBACK_LOG_CAP = 256;
|
|
85154
|
+
var WORKER_FEED_STALE_TTL_MARGIN_MS = 300000;
|
|
84465
85155
|
var workerFeedOwnerDmFallbackLogged = new Set;
|
|
84466
85156
|
function resolveWorkerFeedChat(agentId, fleetChatId) {
|
|
84467
85157
|
const origin = resolveSubagentOriginChat(agentId);
|
|
@@ -85359,14 +86049,14 @@ function releaseTurnBufferGate(key, endingTurn) {
|
|
|
85359
86049
|
}
|
|
85360
86050
|
function emitTurnRecord(turn, endedAt) {
|
|
85361
86051
|
try {
|
|
85362
|
-
const rec = JSON.stringify({
|
|
85363
|
-
ts: Math.floor(endedAt / 1000),
|
|
86052
|
+
const rec = JSON.stringify(buildTurnRecord({
|
|
85364
86053
|
agent: process.env.SWITCHROOM_AGENT_NAME ?? "unknown",
|
|
85365
|
-
|
|
85366
|
-
|
|
85367
|
-
|
|
85368
|
-
|
|
85369
|
-
|
|
86054
|
+
startedAt: turn.startedAt,
|
|
86055
|
+
toolCallCount: turn.toolCallCount ?? 0,
|
|
86056
|
+
turnId: turn.turnId,
|
|
86057
|
+
finalAnswerDelivered: turn.finalAnswerDelivered,
|
|
86058
|
+
deliveryOutcome: turn.deliveryOutcome
|
|
86059
|
+
}, endedAt)) + `
|
|
85370
86060
|
`;
|
|
85371
86061
|
const turnsPath = "/state/agent/turns.jsonl";
|
|
85372
86062
|
maybeRotate(turnsPath, {
|
|
@@ -85382,15 +86072,18 @@ function emitTurnRecord(turn, endedAt) {
|
|
|
85382
86072
|
appendFileSync6(turnsPath, rec);
|
|
85383
86073
|
} catch {}
|
|
85384
86074
|
}
|
|
85385
|
-
function endCurrentTurnAtomic(turn) {
|
|
86075
|
+
function endCurrentTurnAtomic(turn, opts) {
|
|
85386
86076
|
const key = statusKey(turn.sessionChatId, turn.sessionThreadId);
|
|
85387
86077
|
if (!turnLiveForItsTopic(turn))
|
|
85388
|
-
return;
|
|
86078
|
+
return null;
|
|
86079
|
+
clearAnswerReadyFlushTimeout(turn);
|
|
85389
86080
|
endCurrentTurnForKey(turn, key);
|
|
85390
86081
|
const turnEndedAt = Date.now();
|
|
85391
86082
|
process.stderr.write(`telegram gateway: ${formatTurnLifecycle("clear", "turn_end", turn, turnEndedAt)}
|
|
85392
86083
|
`);
|
|
85393
|
-
|
|
86084
|
+
if (opts?.deferRecord !== true) {
|
|
86085
|
+
emitTurnRecord(turn, turnEndedAt);
|
|
86086
|
+
}
|
|
85394
86087
|
const degraded = detectStatusSurfaceDegraded(turn);
|
|
85395
86088
|
if (degraded != null) {
|
|
85396
86089
|
process.stderr.write(`telegram gateway: status-surface DEGRADED reason=${degraded.reason} turnId=${turn.turnId} chat=${turn.sessionChatId} thread=${turn.sessionThreadId ?? "-"} ${degraded.detail}
|
|
@@ -85407,8 +86100,10 @@ function endCurrentTurnAtomic(turn) {
|
|
|
85407
86100
|
clearTimeout(turn.noReplyDrainTimer);
|
|
85408
86101
|
turn.noReplyDrainTimer = null;
|
|
85409
86102
|
}
|
|
86103
|
+
turn.narrativeGate?.teardown();
|
|
85410
86104
|
purgeReactionTracking(statusKey(turn.sessionChatId, turn.sessionThreadId), turn);
|
|
85411
86105
|
armNoReplyDrainTimer(turn);
|
|
86106
|
+
return turnEndedAt;
|
|
85412
86107
|
}
|
|
85413
86108
|
function maybeProactiveCompact() {
|
|
85414
86109
|
if (compactDispatching)
|
|
@@ -85468,16 +86163,9 @@ function maybeProactiveCompact() {
|
|
|
85468
86163
|
compactDispatching = false;
|
|
85469
86164
|
});
|
|
85470
86165
|
}
|
|
85471
|
-
var
|
|
85472
|
-
var lastIdleTurnEndAt = null;
|
|
85473
|
-
var idleAutoCleared = false;
|
|
85474
|
-
var idleClearDispatching = false;
|
|
86166
|
+
var idleTracker = new IdleTracker(Date.now());
|
|
85475
86167
|
function markIdleActivity() {
|
|
85476
|
-
|
|
85477
|
-
idleAutoCleared = false;
|
|
85478
|
-
}
|
|
85479
|
-
function markIdleTurnEnd() {
|
|
85480
|
-
lastIdleTurnEndAt = Date.now();
|
|
86168
|
+
idleTracker.noteInbound(Date.now());
|
|
85481
86169
|
}
|
|
85482
86170
|
function resolveIdleClearMs() {
|
|
85483
86171
|
const env = process.env.SWITCHROOM_IDLE_CLEAR_MS;
|
|
@@ -85502,30 +86190,39 @@ function resolveIdleClearMs() {
|
|
|
85502
86190
|
}
|
|
85503
86191
|
}
|
|
85504
86192
|
function maybeIdleClear() {
|
|
85505
|
-
if (
|
|
86193
|
+
if (idleTracker.isDispatching)
|
|
85506
86194
|
return;
|
|
85507
86195
|
const agentName3 = process.env.SWITCHROOM_AGENT_NAME;
|
|
85508
86196
|
if (!agentName3)
|
|
85509
86197
|
return;
|
|
85510
86198
|
const idleClearMs = resolveIdleClearMs();
|
|
85511
|
-
const decision =
|
|
85512
|
-
lastActivityAt: lastIdleActivityAt,
|
|
85513
|
-
lastTurnEndedAt: lastIdleTurnEndAt,
|
|
86199
|
+
const decision = idleTracker.decide(Date.now(), {
|
|
85514
86200
|
idleClearMs,
|
|
85515
|
-
|
|
85516
|
-
|
|
85517
|
-
}
|
|
86201
|
+
turnInFlight: turnInFlightForGate(),
|
|
86202
|
+
backgroundWorkInFlight: anyPendingAsyncDispatchWithin(BACKGROUND_WORK_SUPPRESS_TTL_MS)
|
|
86203
|
+
});
|
|
85518
86204
|
if (!decision.clear)
|
|
85519
86205
|
return;
|
|
85520
|
-
|
|
85521
|
-
|
|
86206
|
+
idleTracker.markClearFired();
|
|
86207
|
+
idleTracker.beginDispatch();
|
|
85522
86208
|
process.stderr.write(`telegram gateway: idle auto-/clear for ${agentName3} (idle >= ${Math.round(idleClearMs / 60000)}m)
|
|
85523
86209
|
`);
|
|
85524
|
-
|
|
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) => {
|
|
85525
86222
|
process.stderr.write(`telegram gateway: idle /clear inject failed for ${agentName3}: ${err instanceof Error ? err.message : String(err)}
|
|
85526
86223
|
`);
|
|
85527
86224
|
}).finally(() => {
|
|
85528
|
-
|
|
86225
|
+
idleTracker.endDispatch();
|
|
85529
86226
|
});
|
|
85530
86227
|
}
|
|
85531
86228
|
async function postCompactCard(occ, cap) {
|
|
@@ -86905,6 +87602,7 @@ var STREAM_THROTTLE_MS_OVERRIDE = (() => {
|
|
|
86905
87602
|
return Number.isFinite(n) && n >= 0 ? n : undefined;
|
|
86906
87603
|
})();
|
|
86907
87604
|
var TURN_FLUSH_SAFETY_ENABLED = isTurnFlushSafetyEnabled();
|
|
87605
|
+
var ANSWER_READY_FLUSH_MS2 = resolveAnswerReadyFlushMs(process.env);
|
|
86908
87606
|
var ANSWER_STREAM_VISIBLE_ENABLED = parseVisibleAnswerStreamEnabled(process.env.SWITCHROOM_VISIBLE_ANSWER_STREAM);
|
|
86909
87607
|
var ANSWER_LANE = resolveAnswerLaneConfig({
|
|
86910
87608
|
visibleEnabled: ANSWER_STREAM_VISIBLE_ENABLED
|
|
@@ -87384,6 +88082,7 @@ var SILENCE_FALLBACK_MS = parsePositiveMsEnv("SWITCHROOM_SILENCE_FALLBACK_MS", 3
|
|
|
87384
88082
|
var SILENCE_FALLBACK_HARD_MS = parsePositiveMsEnv("SWITCHROOM_SILENCE_FALLBACK_HARD_MS", 900000);
|
|
87385
88083
|
var SILENCE_FLOOR_MS = parsePositiveMsEnv("SWITCHROOM_SILENCE_FLOOR_MS", 45000);
|
|
87386
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";
|
|
87387
88086
|
var SILENCE_DEFER_INFLIGHT_TOOLS = process.env.SWITCHROOM_SILENCE_DEFER_INFLIGHT_TOOLS === "1";
|
|
87388
88087
|
var SILENCE_LIVENESS_PRODUCTION = process.env.SWITCHROOM_SILENCE_LIVENESS_PRODUCTION !== "0";
|
|
87389
88088
|
var MEMORY_LEGIBILITY_ENABLED = isMemoryLegibilityEnabled(process.env.SWITCHROOM_MEMORY_LEGIBILITY);
|
|
@@ -87698,6 +88397,103 @@ function agentHasInFlightBackgroundWork(now) {
|
|
|
87698
88397
|
return ageMs != null && ageMs < TURN_ACTIVE_MARKER_FRESH_MS;
|
|
87699
88398
|
}
|
|
87700
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
|
+
}
|
|
87701
88497
|
function obligationSweep() {
|
|
87702
88498
|
if (!OBLIGATION_LEDGER_ENABLED)
|
|
87703
88499
|
return;
|
|
@@ -88475,7 +89271,8 @@ var ipcServer = createIpcServer({
|
|
|
88475
89271
|
swallowingApiCall(() => bot.api.editMessageText(operator, msg.messageId, richMessage2(msg.text), {}), { chat_id: String(operator), verb: "rollout-status-edit" });
|
|
88476
89272
|
},
|
|
88477
89273
|
onInjectInbound(_client, msg) {
|
|
88478
|
-
|
|
89274
|
+
if (!isCronInjectFire(msg.inbound.meta))
|
|
89275
|
+
markIdleActivity();
|
|
88479
89276
|
const promptKey = typeof msg.inbound.meta?.prompt_key === "string" ? msg.inbound.meta.prompt_key : "unknown";
|
|
88480
89277
|
const source = typeof msg.inbound.meta?.source === "string" ? msg.inbound.meta.source : "unknown";
|
|
88481
89278
|
const isDurableReplay = inboundSpool != null && typeof msg.inbound.meta?.replay_fire_ms === "string" && msg.inbound.meta.replay_fire_ms.length > 0;
|
|
@@ -90488,7 +91285,7 @@ async function executeEditMessage(args) {
|
|
|
90488
91285
|
editRawText = normalizeParagraphBreaks2(editRawText);
|
|
90489
91286
|
editRawText = redactOutboundText(editRawText, "edit_message");
|
|
90490
91287
|
if (!editLiteralText)
|
|
90491
|
-
editRawText = stripExcessBold2(normalizePunctuation2(editRawText));
|
|
91288
|
+
editRawText = addParagraphSpacers2(stripExcessBold2(normalizePunctuation2(editRawText)));
|
|
90492
91289
|
{
|
|
90493
91290
|
const scrub = scrubVoice2(editRawText);
|
|
90494
91291
|
if (scrub.replaced > 0) {
|
|
@@ -90703,6 +91500,33 @@ function resetOrphanedReplyTimeout() {
|
|
|
90703
91500
|
}, ORPHANED_REPLY_TIMEOUT_MS);
|
|
90704
91501
|
}
|
|
90705
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
|
+
}
|
|
90706
91530
|
function closeActivityLane(chatId, threadId) {
|
|
90707
91531
|
const key = chatKeyWithSuffix2(chatId, threadId, "activity");
|
|
90708
91532
|
const stream = activeDraftStreams.get(key);
|
|
@@ -90736,6 +91560,45 @@ function composeTurnActivity(turn, final = false, liveSuffix = "") {
|
|
|
90736
91560
|
};
|
|
90737
91561
|
return renderActivityFeedWithNested2(turn.mirrorLines, childLines, final, liveSuffix, stepCount, header);
|
|
90738
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
|
+
}
|
|
90739
91602
|
function showNarrativeStep(turn, text5) {
|
|
90740
91603
|
const rendered = appendActivityLabel(turn.mirrorLines, clipNarrative2(text5));
|
|
90741
91604
|
if (rendered == null)
|
|
@@ -90751,31 +91614,13 @@ function showNarrativeStep(turn, text5) {
|
|
|
90751
91614
|
});
|
|
90752
91615
|
}
|
|
90753
91616
|
function resolvePendingNarrativeOnTool(turn, toolName, input) {
|
|
90754
|
-
|
|
90755
|
-
if (pending2 == null)
|
|
90756
|
-
return;
|
|
90757
|
-
turn.pendingNarrative = null;
|
|
90758
|
-
if (REPLY_TOOLS.has(toolName)) {
|
|
90759
|
-
const replyText = typeof input?.text === "string" ? input.text : "";
|
|
90760
|
-
if (isDraftOfReply(pending2.text, replyText))
|
|
90761
|
-
return;
|
|
90762
|
-
}
|
|
90763
|
-
showNarrativeStep(turn, pending2.text);
|
|
91617
|
+
turn.narrativeGate.resolveOnTool(toolName, input);
|
|
90764
91618
|
}
|
|
90765
91619
|
function stagePendingNarrative(turn, text5) {
|
|
90766
|
-
|
|
90767
|
-
showNarrativeStep(turn, turn.pendingNarrative.text);
|
|
90768
|
-
}
|
|
90769
|
-
turn.pendingNarrative = { text: text5 };
|
|
91620
|
+
turn.narrativeGate.stage(text5);
|
|
90770
91621
|
}
|
|
90771
91622
|
function flushPendingNarrativeAtTurnEnd(turn, lastReplyText) {
|
|
90772
|
-
|
|
90773
|
-
if (pending2 == null)
|
|
90774
|
-
return;
|
|
90775
|
-
turn.pendingNarrative = null;
|
|
90776
|
-
if (lastReplyText.length > 0 && isDraftOfReply(pending2.text, lastReplyText))
|
|
90777
|
-
return;
|
|
90778
|
-
showNarrativeStep(turn, pending2.text);
|
|
91623
|
+
turn.narrativeGate.flushAtTurnEnd(lastReplyText);
|
|
90779
91624
|
}
|
|
90780
91625
|
async function drainActivitySummary(turn, producer = "tool", openFlags) {
|
|
90781
91626
|
try {
|
|
@@ -91115,11 +91960,7 @@ function handleSessionEvent(ev) {
|
|
|
91115
91960
|
}
|
|
91116
91961
|
{
|
|
91117
91962
|
const durationMs = ev.kind === "turn_end" ? ev.durationMs : undefined;
|
|
91118
|
-
|
|
91119
|
-
if (signal.activity)
|
|
91120
|
-
markIdleActivity();
|
|
91121
|
-
if (signal.turnEnded)
|
|
91122
|
-
markIdleTurnEnd();
|
|
91963
|
+
idleTracker.noteEvent(ev.kind, Date.now(), durationMs);
|
|
91123
91964
|
}
|
|
91124
91965
|
switch (ev.kind) {
|
|
91125
91966
|
case "enqueue": {
|
|
@@ -91139,6 +91980,7 @@ function handleSessionEvent(ev) {
|
|
|
91139
91980
|
clearTimeout(prior.orphanedReplyTimeoutId);
|
|
91140
91981
|
prior.orphanedReplyTimeoutId = null;
|
|
91141
91982
|
}
|
|
91983
|
+
prior?.narrativeGate?.teardown();
|
|
91142
91984
|
const startedAt = Date.now();
|
|
91143
91985
|
const enqThreadIdNum = ev.threadId != null ? Number(ev.threadId) : undefined;
|
|
91144
91986
|
const turnId = deriveTurnId(ev.chatId, enqThreadIdNum ?? null, ev.messageId) ?? `${chatKey2(ev.chatId, enqThreadIdNum ?? null)}#synthetic-${startedAt}`;
|
|
@@ -91164,6 +92006,7 @@ function handleSessionEvent(ev) {
|
|
|
91164
92006
|
silentAnchorText: "",
|
|
91165
92007
|
capturedText: [],
|
|
91166
92008
|
orphanedReplyTimeoutId: null,
|
|
92009
|
+
answerReadyFlushTimeoutId: null,
|
|
91167
92010
|
liveness: new LivenessTracker(startedAt),
|
|
91168
92011
|
turnId,
|
|
91169
92012
|
registryKey: null,
|
|
@@ -91179,13 +92022,14 @@ function handleSessionEvent(ev) {
|
|
|
91179
92022
|
activityEverOpened: false,
|
|
91180
92023
|
activityDrainFailures: 0,
|
|
91181
92024
|
mirrorLines: [],
|
|
91182
|
-
|
|
92025
|
+
narrativeGate: undefined,
|
|
91183
92026
|
lastReplyText: "",
|
|
91184
92027
|
foregroundSubAgents: new Map,
|
|
91185
92028
|
answerStream: null,
|
|
91186
92029
|
isDm: isDmChatId2(ev.chatId),
|
|
91187
92030
|
emissionAuthority: new EmissionAuthority(statusKey(ev.chatId, enqThreadIdNum))
|
|
91188
92031
|
};
|
|
92032
|
+
next.narrativeGate = makeNarrativeGate(next);
|
|
91189
92033
|
setCurrentTurn(next, statusKey(ev.chatId, enqThreadIdNum));
|
|
91190
92034
|
scheduleEarlyLivenessOpen(next);
|
|
91191
92035
|
process.stderr.write(`telegram gateway: ${formatTurnLifecycle("set", "enqueue", next, startedAt)}
|
|
@@ -91257,6 +92101,7 @@ function handleSessionEvent(ev) {
|
|
|
91257
92101
|
const turn = currentTurn;
|
|
91258
92102
|
if (turn == null)
|
|
91259
92103
|
return;
|
|
92104
|
+
clearAnswerReadyFlushTimeout(turn);
|
|
91260
92105
|
resolvePendingNarrativeOnTool(turn, ev.toolName, ev.input);
|
|
91261
92106
|
turn.toolCallCount++;
|
|
91262
92107
|
touchTurnActiveMarker2(STATE_DIR);
|
|
@@ -91291,6 +92136,7 @@ function handleSessionEvent(ev) {
|
|
|
91291
92136
|
const turn = currentTurn;
|
|
91292
92137
|
if (turn == null)
|
|
91293
92138
|
return;
|
|
92139
|
+
clearAnswerReadyFlushTimeout(turn);
|
|
91294
92140
|
resetOrphanedReplyTimeout();
|
|
91295
92141
|
if (isTelegramSurfaceTool2(ev.toolName))
|
|
91296
92142
|
return;
|
|
@@ -91400,6 +92246,7 @@ function handleSessionEvent(ev) {
|
|
|
91400
92246
|
preambleSuppressor.onText(ev.text);
|
|
91401
92247
|
}
|
|
91402
92248
|
resetOrphanedReplyTimeout();
|
|
92249
|
+
resetAnswerReadyFlushTimeout();
|
|
91403
92250
|
if (isContextExhaustionText(ev.text) && turn != null) {
|
|
91404
92251
|
const chatId = turn.sessionChatId;
|
|
91405
92252
|
const threadId = turn.sessionThreadId;
|
|
@@ -91451,7 +92298,7 @@ function handleSessionEvent(ev) {
|
|
|
91451
92298
|
return;
|
|
91452
92299
|
}
|
|
91453
92300
|
case "turn_end": {
|
|
91454
|
-
if (ev.durationMs === -1) {
|
|
92301
|
+
if (ev.durationMs === -1 && ev.reason !== "answer-ready-quiescence") {
|
|
91455
92302
|
const turn = currentTurn;
|
|
91456
92303
|
const key = turn != null ? statusKey(turn.sessionChatId, turn.sessionThreadId) : "";
|
|
91457
92304
|
const recentlyStreaming = turn != null && turn.liveness.recentlyStreaming(Date.now(), ORPHANED_REPLY_STREAM_WINDOW_MS);
|
|
@@ -91525,7 +92372,7 @@ function handleSessionEvent(ev) {
|
|
|
91525
92372
|
const chatId = turn.sessionChatId;
|
|
91526
92373
|
const threadId = turn.sessionThreadId;
|
|
91527
92374
|
const ctrl = activeStatusReactions.get(statusKey(chatId, threadId));
|
|
91528
|
-
const flushDecision = streamFinalizedAsAnswer ? { kind: "skip", reason: "reply-called" } :
|
|
92375
|
+
const flushDecision = streamFinalizedAsAnswer ? { kind: "skip", reason: "reply-called" } : decideTurnFlush2({
|
|
91529
92376
|
chatId: turn.sessionChatId,
|
|
91530
92377
|
replyCalled: turn.replyCalled,
|
|
91531
92378
|
capturedText: turn.capturedText,
|
|
@@ -91635,7 +92482,7 @@ function handleSessionEvent(ev) {
|
|
|
91635
92482
|
}) ?? { wasEmitted: false, turnKey: null };
|
|
91636
92483
|
const backstopCardMessageId = cardTakeover.wasEmitted && cardTakeover.turnKey != null ? getPinnedProgressCardMessageId?.(cardTakeover.turnKey) ?? null : null;
|
|
91637
92484
|
const backstopCardTurnKey = cardTakeover.turnKey;
|
|
91638
|
-
endCurrentTurnAtomic(turn);
|
|
92485
|
+
const backstopTurnEndedAt = endCurrentTurnAtomic(turn, { deferRecord: true });
|
|
91639
92486
|
preambleSuppressor.dropNow();
|
|
91640
92487
|
{
|
|
91641
92488
|
const tKey = statusKey(chatId, threadId);
|
|
@@ -91652,6 +92499,10 @@ function handleSessionEvent(ev) {
|
|
|
91652
92499
|
if (recentCount > 0) {
|
|
91653
92500
|
process.stderr.write(`telegram gateway: turn-flush suppressed \u2014 reply tool sent ${recentCount} message(s) within 2s
|
|
91654
92501
|
`);
|
|
92502
|
+
if (backstopTurnEndedAt != null) {
|
|
92503
|
+
turn.deliveryOutcome = "suppressed";
|
|
92504
|
+
emitTurnRecord(turn, backstopTurnEndedAt);
|
|
92505
|
+
}
|
|
91655
92506
|
return;
|
|
91656
92507
|
}
|
|
91657
92508
|
} catch {}
|
|
@@ -91663,10 +92514,12 @@ function handleSessionEvent(ev) {
|
|
|
91663
92514
|
link_preview_options: { is_disabled: true }
|
|
91664
92515
|
};
|
|
91665
92516
|
const limit = RICH_MESSAGE_MAX_CHARS2;
|
|
91666
|
-
|
|
91667
|
-
const htmlChunks = splitMarkdownChunks2(renderedText, limit);
|
|
92517
|
+
let htmlChunks = [];
|
|
91668
92518
|
const sentIds = [];
|
|
92519
|
+
let sendThrew = false;
|
|
91669
92520
|
try {
|
|
92521
|
+
const renderedText = addParagraphSpacers2(capturedText);
|
|
92522
|
+
htmlChunks = splitMarkdownChunks2(renderedText, limit);
|
|
91670
92523
|
let firstSendUsedEdit = false;
|
|
91671
92524
|
let liveThreadId = backstopThreadId;
|
|
91672
92525
|
if (backstopCardMessageId != null && htmlChunks.length > 0) {
|
|
@@ -91729,10 +92582,20 @@ function handleSessionEvent(ev) {
|
|
|
91729
92582
|
unpinProgressCardForChat?.(backstopChatId, backstopThreadId);
|
|
91730
92583
|
}
|
|
91731
92584
|
} catch (err) {
|
|
92585
|
+
sendThrew = true;
|
|
91732
92586
|
process.stderr.write(`telegram gateway: turn-flush send failed: ${err.message}
|
|
91733
92587
|
`);
|
|
91734
92588
|
if (backstopCtrl)
|
|
91735
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
|
+
}
|
|
91736
92599
|
}
|
|
91737
92600
|
})();
|
|
91738
92601
|
return;
|
|
@@ -91774,21 +92637,40 @@ function handleSessionEvent(ev) {
|
|
|
91774
92637
|
ended_via: outboundMetrics.outboundCount > 0 ? "reply" : "silent"
|
|
91775
92638
|
});
|
|
91776
92639
|
if (turnEndDecision === "reprompt") {
|
|
91777
|
-
const
|
|
91778
|
-
|
|
91779
|
-
|
|
91780
|
-
|
|
91781
|
-
|
|
91782
|
-
|
|
91783
|
-
|
|
91784
|
-
}, silentEndDeps);
|
|
91785
|
-
if (silentEnd.exhausted) {
|
|
91786
|
-
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)
|
|
91787
|
-
`);
|
|
91788
|
-
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) => {
|
|
91789
|
-
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)
|
|
91790
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
|
|
91791
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
|
+
}
|
|
91792
92674
|
}
|
|
91793
92675
|
}
|
|
91794
92676
|
clear(tKey);
|
|
@@ -98269,6 +99151,7 @@ var didOneTimeSetup = false;
|
|
|
98269
99151
|
},
|
|
98270
99152
|
floodWaitRemainingMs: probeFloodWaitRemainingMs,
|
|
98271
99153
|
maxRows: workerFeedMaxRows,
|
|
99154
|
+
staleWorkerTtlMs: resolveInflightTerminalCapMs() + WORKER_FEED_STALE_TTL_MARGIN_MS,
|
|
98272
99155
|
reconcilePin: ({ feedKey, chatId, messageId }) => {
|
|
98273
99156
|
if (!PIN_STATUS_WHILE_WORKING)
|
|
98274
99157
|
return;
|
|
@@ -98314,6 +99197,14 @@ var didOneTimeSetup = false;
|
|
|
98314
99197
|
process.stderr.write(`telegram gateway: worker ${agentId} NAMED AS LOST \u2014 falsely finalised twice, resurrection chain bound reached (issue #3023)
|
|
98315
99198
|
`);
|
|
98316
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
|
+
},
|
|
98317
99208
|
onFinish: ({ agentId, outcome, description, resultText, toolCount, durationMs, background: entryBackground }) => {
|
|
98318
99209
|
deferredDoneReactions.promote();
|
|
98319
99210
|
let fleetChatId = "";
|