switchroom 0.18.19 → 0.18.21
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 +59 -6
- package/dist/host-control/main.js +1 -1
- package/package.json +1 -1
- package/profiles/_shared/delegation-golden-rule.md.hbs +9 -0
- package/profiles/_shared/dev-protocol.md.hbs +2 -0
- package/profiles/_shared/execution-discipline.md.hbs +2 -2
- package/profiles/coding/CLAUDE.md.hbs +1 -1
- package/telegram-plugin/answer-ready-flush.ts +187 -0
- package/telegram-plugin/dist/gateway/gateway.js +1114 -184
- package/telegram-plugin/format.ts +179 -20
- package/telegram-plugin/gateway/cron-session.ts +32 -0
- package/telegram-plugin/gateway/gateway.ts +794 -106
- 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/subagent-progress-inbound-builder.ts +17 -0
- 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/registry/subagents-schema.ts +6 -0
- 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 +330 -82
- 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/nested-worker-visibility-harness.test.ts +20 -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-progress-inbound-builder.test.ts +30 -0
- package/telegram-plugin/tests/subagent-watcher-first-paint-independence.test.ts +171 -0
- package/telegram-plugin/tests/subagent-watcher-narrative-early-paint.test.ts +220 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +13 -12
- 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 +165 -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 };
|
|
@@ -76682,6 +77259,9 @@ function decideSubagentProgress(input) {
|
|
|
76682
77259
|
if (isEnvFlagOn(input.disableEnvValue)) {
|
|
76683
77260
|
return { deliver: false, reason: "env-disabled" };
|
|
76684
77261
|
}
|
|
77262
|
+
if (input.skeleton === true) {
|
|
77263
|
+
return { deliver: false, reason: "skeleton-liveness" };
|
|
77264
|
+
}
|
|
76685
77265
|
if (!input.isBackground) {
|
|
76686
77266
|
return { deliver: false, reason: "foreground" };
|
|
76687
77267
|
}
|
|
@@ -78210,25 +78790,81 @@ function redactSecrets(text4) {
|
|
|
78210
78790
|
return out;
|
|
78211
78791
|
}
|
|
78212
78792
|
|
|
78213
|
-
// narrative-
|
|
78214
|
-
var
|
|
78215
|
-
|
|
78216
|
-
|
|
78217
|
-
|
|
78218
|
-
|
|
78219
|
-
|
|
78220
|
-
|
|
78221
|
-
|
|
78222
|
-
|
|
78223
|
-
|
|
78224
|
-
|
|
78225
|
-
|
|
78226
|
-
|
|
78227
|
-
|
|
78228
|
-
|
|
78229
|
-
|
|
78230
|
-
|
|
78231
|
-
|
|
78793
|
+
// narrative-flush.ts
|
|
78794
|
+
var PENDING_NARRATIVE_FLUSH_MS2 = 250;
|
|
78795
|
+
|
|
78796
|
+
class NarrativeFlushController2 {
|
|
78797
|
+
effects;
|
|
78798
|
+
scheduler;
|
|
78799
|
+
flushMs;
|
|
78800
|
+
pending = null;
|
|
78801
|
+
timerShown = null;
|
|
78802
|
+
constructor(effects, scheduler, flushMs) {
|
|
78803
|
+
this.effects = effects;
|
|
78804
|
+
this.scheduler = scheduler;
|
|
78805
|
+
this.flushMs = flushMs;
|
|
78806
|
+
}
|
|
78807
|
+
get pendingText() {
|
|
78808
|
+
return this.pending;
|
|
78809
|
+
}
|
|
78810
|
+
get timerShownText() {
|
|
78811
|
+
return this.timerShown;
|
|
78812
|
+
}
|
|
78813
|
+
stage(text4) {
|
|
78814
|
+
this.scheduler.disarm();
|
|
78815
|
+
if (this.pending != null) {
|
|
78816
|
+
this.effects.show(this.pending);
|
|
78817
|
+
}
|
|
78818
|
+
this.pending = text4;
|
|
78819
|
+
this.scheduler.arm(() => this.onTimerFire(), this.flushMs);
|
|
78820
|
+
}
|
|
78821
|
+
onTimerFire() {
|
|
78822
|
+
if (this.pending == null)
|
|
78823
|
+
return;
|
|
78824
|
+
const text4 = this.pending;
|
|
78825
|
+
this.pending = null;
|
|
78826
|
+
this.timerShown = text4;
|
|
78827
|
+
this.effects.show(text4);
|
|
78828
|
+
}
|
|
78829
|
+
resolveOnTool(toolName, input) {
|
|
78830
|
+
this.scheduler.disarm();
|
|
78831
|
+
const replyText = REPLY_TOOLS2.has(toolName) && typeof input?.text === "string" ? input.text : null;
|
|
78832
|
+
if (replyText != null)
|
|
78833
|
+
this.maybeRetract(replyText);
|
|
78834
|
+
const pending = this.pending;
|
|
78835
|
+
if (pending == null)
|
|
78836
|
+
return;
|
|
78837
|
+
this.pending = null;
|
|
78838
|
+
if (replyText != null && isDraftOfReply(pending, replyText))
|
|
78839
|
+
return;
|
|
78840
|
+
this.effects.show(pending);
|
|
78841
|
+
}
|
|
78842
|
+
flushAtTurnEnd(lastReplyText) {
|
|
78843
|
+
this.scheduler.disarm();
|
|
78844
|
+
if (lastReplyText.length > 0)
|
|
78845
|
+
this.maybeRetract(lastReplyText);
|
|
78846
|
+
const pending = this.pending;
|
|
78847
|
+
if (pending == null)
|
|
78848
|
+
return;
|
|
78849
|
+
this.pending = null;
|
|
78850
|
+
if (lastReplyText.length > 0 && isDraftOfReply(pending, lastReplyText))
|
|
78851
|
+
return;
|
|
78852
|
+
this.effects.show(pending);
|
|
78853
|
+
}
|
|
78854
|
+
teardown() {
|
|
78855
|
+
this.scheduler.disarm();
|
|
78856
|
+
this.pending = null;
|
|
78857
|
+
this.timerShown = null;
|
|
78858
|
+
}
|
|
78859
|
+
maybeRetract(replyText) {
|
|
78860
|
+
const shown = this.timerShown;
|
|
78861
|
+
if (shown == null)
|
|
78862
|
+
return;
|
|
78863
|
+
if (!isDraftOfReply(shown, replyText))
|
|
78864
|
+
return;
|
|
78865
|
+
this.timerShown = null;
|
|
78866
|
+
this.effects.retractShown(shown);
|
|
78867
|
+
}
|
|
78232
78868
|
}
|
|
78233
78869
|
|
|
78234
78870
|
// subagent-watcher.ts
|
|
@@ -78365,6 +79001,9 @@ var DEFAULT_STALL_THRESHOLD_MS = 60000;
|
|
|
78365
79001
|
var DEFAULT_SILENT_SYNTHESIS_STALL_THRESHOLD_MS = 300000;
|
|
78366
79002
|
var DEFAULT_SILENT_STALL_TERMINAL_MS = 300000;
|
|
78367
79003
|
var DEFAULT_INFLIGHT_TERMINAL_CAP_MS = 45 * 60000;
|
|
79004
|
+
function resolveInflightTerminalCapMs(configVal) {
|
|
79005
|
+
return configVal ?? parseEnvMs("SWITCHROOM_SUBAGENT_INFLIGHT_TERMINAL_CAP_MS") ?? DEFAULT_INFLIGHT_TERMINAL_CAP_MS;
|
|
79006
|
+
}
|
|
78368
79007
|
var DEFAULT_DEFERRAL_LOG_INTERVAL_MS = 60000;
|
|
78369
79008
|
var LONG_RUNNING_TOOLS = new Set(["Bash"]);
|
|
78370
79009
|
var MAX_RESURRECTIONS = 1;
|
|
@@ -78478,13 +79117,47 @@ function backfillJsonlAgentId(db2, jsonlPath, agentId, log) {
|
|
|
78478
79117
|
}
|
|
78479
79118
|
function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, parentStateDir, onUnstall, onFileVanished, onProgress) {
|
|
78480
79119
|
try {
|
|
79120
|
+
if (entry.narrativeGate != null)
|
|
79121
|
+
entry.narrativeGate.tick(now);
|
|
78481
79122
|
const stat = fs2.statSync(entry.filePath);
|
|
78482
79123
|
if (stat.size < tail.cursor) {
|
|
78483
79124
|
tail.cursor = 0;
|
|
78484
79125
|
tail.pendingPartial = "";
|
|
78485
79126
|
}
|
|
78486
|
-
if (stat.size === tail.cursor)
|
|
79127
|
+
if (stat.size === tail.cursor) {
|
|
79128
|
+
if (onProgress != null && entry.state === "running" && !entry.historical) {
|
|
79129
|
+
let hasChild = false;
|
|
79130
|
+
if (db2 != null) {
|
|
79131
|
+
try {
|
|
79132
|
+
const kid = db2.prepare("SELECT 1 FROM subagents WHERE parent_agent_id = ? LIMIT 1").get(entry.agentId);
|
|
79133
|
+
hasChild = kid != null;
|
|
79134
|
+
} catch (kidErr) {
|
|
79135
|
+
log?.(`subagent-watcher: skeleton child-check error ${entry.agentId}: ${kidErr.message}`);
|
|
79136
|
+
}
|
|
79137
|
+
}
|
|
79138
|
+
if (!hasChild) {
|
|
79139
|
+
try {
|
|
79140
|
+
onProgress({
|
|
79141
|
+
agentId: entry.agentId,
|
|
79142
|
+
description: entry.description,
|
|
79143
|
+
latestSummary: "",
|
|
79144
|
+
elapsedMs: now - entry.dispatchedAt,
|
|
79145
|
+
prevBucketIdx: entry.lastProgressBucketIdx,
|
|
79146
|
+
setBucketIdx: (b) => {
|
|
79147
|
+
entry.lastProgressBucketIdx = b;
|
|
79148
|
+
},
|
|
79149
|
+
lastTool: entry.lastTool,
|
|
79150
|
+
toolCount: entry.toolCount,
|
|
79151
|
+
model: entry.currentModel,
|
|
79152
|
+
skeleton: true
|
|
79153
|
+
});
|
|
79154
|
+
} catch (cbErr) {
|
|
79155
|
+
log?.(`subagent-watcher: onProgress (skeleton) callback error ${entry.agentId}: ${cbErr.message}`);
|
|
79156
|
+
}
|
|
79157
|
+
}
|
|
79158
|
+
}
|
|
78487
79159
|
return;
|
|
79160
|
+
}
|
|
78488
79161
|
const buf = Buffer.alloc(stat.size - tail.cursor);
|
|
78489
79162
|
const fd = fs2.openSync(entry.filePath, "r");
|
|
78490
79163
|
try {
|
|
@@ -78538,43 +79211,87 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
|
|
|
78538
79211
|
entry.errorDetail = errInfo.detail.slice(0, SUBAGENT_RESULT_TEXT_MAX);
|
|
78539
79212
|
}
|
|
78540
79213
|
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
|
-
|
|
79214
|
+
const buildNarrativeGate = () => {
|
|
79215
|
+
const nowRef = { value: now };
|
|
79216
|
+
let cueFired = false;
|
|
79217
|
+
const fireCue = () => {
|
|
79218
|
+
if (onProgress == null || entry.state !== "running" || entry.historical)
|
|
79219
|
+
return false;
|
|
79220
|
+
try {
|
|
79221
|
+
onProgress({
|
|
79222
|
+
agentId: entry.agentId,
|
|
79223
|
+
description: entry.description,
|
|
79224
|
+
latestSummary: entry.lastResultText,
|
|
79225
|
+
elapsedMs: nowRef.value - entry.dispatchedAt,
|
|
79226
|
+
prevBucketIdx: entry.lastProgressBucketIdx,
|
|
79227
|
+
setBucketIdx: (b) => {
|
|
79228
|
+
entry.lastProgressBucketIdx = b;
|
|
79229
|
+
},
|
|
79230
|
+
lastTool: entry.lastTool,
|
|
79231
|
+
toolCount: entry.toolCount,
|
|
79232
|
+
model: entry.currentModel
|
|
79233
|
+
});
|
|
79234
|
+
return true;
|
|
79235
|
+
} catch (cbErr) {
|
|
79236
|
+
log?.(`subagent-watcher: onProgress callback error ${entry.agentId}: ${cbErr.message}`);
|
|
79237
|
+
return false;
|
|
79238
|
+
}
|
|
79239
|
+
};
|
|
79240
|
+
const scheduler = {
|
|
79241
|
+
armedFn: null,
|
|
79242
|
+
deadline: 0
|
|
79243
|
+
};
|
|
79244
|
+
const controller = new NarrativeFlushController2({
|
|
79245
|
+
show: () => {
|
|
79246
|
+
cueFired = fireCue();
|
|
79247
|
+
},
|
|
79248
|
+
retractShown: () => {
|
|
79249
|
+
log?.(`subagent-watcher: narrative early-paint retract (no-op on replace-on-write worker card) ${entry.agentId}`);
|
|
79250
|
+
}
|
|
79251
|
+
}, {
|
|
79252
|
+
arm: (fn, ms) => {
|
|
79253
|
+
scheduler.armedFn = fn;
|
|
79254
|
+
scheduler.deadline = nowRef.value + ms;
|
|
79255
|
+
},
|
|
79256
|
+
disarm: () => {
|
|
79257
|
+
scheduler.armedFn = null;
|
|
79258
|
+
}
|
|
79259
|
+
}, PENDING_NARRATIVE_FLUSH_MS2);
|
|
79260
|
+
return {
|
|
79261
|
+
tick: (n) => {
|
|
79262
|
+
nowRef.value = n;
|
|
79263
|
+
if (scheduler.armedFn != null && n >= scheduler.deadline) {
|
|
79264
|
+
const fn = scheduler.armedFn;
|
|
79265
|
+
scheduler.armedFn = null;
|
|
79266
|
+
fn();
|
|
79267
|
+
}
|
|
79268
|
+
},
|
|
79269
|
+
stage: (text5) => {
|
|
79270
|
+
controller.stage(text5);
|
|
79271
|
+
},
|
|
79272
|
+
resolveOnTool: (toolName, input) => {
|
|
79273
|
+
cueFired = false;
|
|
79274
|
+
controller.resolveOnTool(toolName ?? "", input);
|
|
79275
|
+
return cueFired;
|
|
79276
|
+
},
|
|
79277
|
+
resolveAtTurnEnd: (lastReplyText) => {
|
|
79278
|
+
cueFired = false;
|
|
79279
|
+
controller.flushAtTurnEnd(lastReplyText);
|
|
79280
|
+
return cueFired;
|
|
79281
|
+
},
|
|
79282
|
+
reset: () => {
|
|
79283
|
+
controller.teardown();
|
|
79284
|
+
scheduler.armedFn = null;
|
|
79285
|
+
}
|
|
79286
|
+
};
|
|
78563
79287
|
};
|
|
78564
79288
|
const resolvePendingSubNarrative = (toolName, toolInput) => {
|
|
78565
|
-
if (entry.
|
|
79289
|
+
if (entry.narrativeGate == null)
|
|
78566
79290
|
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;
|
|
79291
|
+
if (toolName == null) {
|
|
79292
|
+
return entry.narrativeGate.resolveAtTurnEnd(entry.lastReplyText ?? "");
|
|
78576
79293
|
}
|
|
78577
|
-
return
|
|
79294
|
+
return entry.narrativeGate.resolveOnTool(toolName, toolInput);
|
|
78578
79295
|
};
|
|
78579
79296
|
for (const ev of events) {
|
|
78580
79297
|
const idleSecBeforeBump = Math.round((now - entry.lastActivityAt) / 1000);
|
|
@@ -78681,10 +79398,9 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
|
|
|
78681
79398
|
} else if (ev.kind === "sub_agent_text") {
|
|
78682
79399
|
entry.lastSummaryLine = clipNarrative(ev.text);
|
|
78683
79400
|
entry.lastResultText = ev.text.trim().slice(0, SUBAGENT_RESULT_TEXT_MAX);
|
|
78684
|
-
if (entry.
|
|
78685
|
-
|
|
78686
|
-
|
|
78687
|
-
entry.pendingNarrative = { text: ev.text };
|
|
79401
|
+
if (entry.narrativeGate == null)
|
|
79402
|
+
entry.narrativeGate = buildNarrativeGate();
|
|
79403
|
+
entry.narrativeGate.stage(ev.text);
|
|
78688
79404
|
} else if (ev.kind === "sub_agent_tool_result") {
|
|
78689
79405
|
if (ev.toolUseId != null && ev.toolUseId !== "") {
|
|
78690
79406
|
entry.inflightToolUseIds.delete(ev.toolUseId);
|
|
@@ -78731,7 +79447,7 @@ function startSubagentWatcher(config) {
|
|
|
78731
79447
|
const stallThresholdMs = config.stallThresholdMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_STALL_MS") ?? DEFAULT_STALL_THRESHOLD_MS;
|
|
78732
79448
|
const silentSynthesisStallThresholdMs = config.silentSynthesisStallThresholdMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_SILENT_SYNTH_STALL_MS") ?? DEFAULT_SILENT_SYNTHESIS_STALL_THRESHOLD_MS;
|
|
78733
79449
|
const silentStallTerminalMs = config.silentStallTerminalMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_STALL_TERMINAL_MS") ?? DEFAULT_SILENT_STALL_TERMINAL_MS;
|
|
78734
|
-
const inflightTerminalCapMs = config.inflightTerminalCapMs
|
|
79450
|
+
const inflightTerminalCapMs = resolveInflightTerminalCapMs(config.inflightTerminalCapMs);
|
|
78735
79451
|
const deferralLogIntervalMs = config.deferralLogIntervalMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_DEFERRAL_LOG_INTERVAL_MS") ?? DEFAULT_DEFERRAL_LOG_INTERVAL_MS;
|
|
78736
79452
|
const inflightPromoteMaxAgeMs = config.inflightPromoteMaxAgeMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_INFLIGHT_MAX_AGE_MS") ?? DEFAULT_INFLIGHT_PROMOTE_MAX_AGE_MS;
|
|
78737
79453
|
const terminatedAgentIdsCap = config.terminatedAgentIdsCap ?? TERMINATED_AGENT_IDS_CAP;
|
|
@@ -78892,7 +79608,8 @@ function startSubagentWatcher(config) {
|
|
|
78892
79608
|
}
|
|
78893
79609
|
entry.toolCount = 0;
|
|
78894
79610
|
entry.lastTool = null;
|
|
78895
|
-
entry.
|
|
79611
|
+
entry.narrativeGate?.reset();
|
|
79612
|
+
entry.narrativeGate = null;
|
|
78896
79613
|
tail.cursor = 0;
|
|
78897
79614
|
tail.pendingPartial = "";
|
|
78898
79615
|
tail.hasEmittedStart = false;
|
|
@@ -78991,6 +79708,13 @@ function startSubagentWatcher(config) {
|
|
|
78991
79708
|
}
|
|
78992
79709
|
terminatedAgentIds.add(agentId);
|
|
78993
79710
|
log?.(`subagent-watcher: cleaned up terminal agent ${agentId}`);
|
|
79711
|
+
if (config.onTerminalCleanup) {
|
|
79712
|
+
try {
|
|
79713
|
+
config.onTerminalCleanup(agentId);
|
|
79714
|
+
} catch (cbErr) {
|
|
79715
|
+
log?.(`subagent-watcher: onTerminalCleanup callback error ${agentId}: ${cbErr.message}`);
|
|
79716
|
+
}
|
|
79717
|
+
}
|
|
78994
79718
|
}
|
|
78995
79719
|
function recordFalseFinish(agentId, filePath, n) {
|
|
78996
79720
|
let size = 0;
|
|
@@ -82034,10 +82758,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
|
|
|
82034
82758
|
}
|
|
82035
82759
|
|
|
82036
82760
|
// ../src/build-info.ts
|
|
82037
|
-
var VERSION = "0.18.
|
|
82038
|
-
var COMMIT_SHA = "
|
|
82039
|
-
var COMMIT_DATE = "2026-07-
|
|
82040
|
-
var LATEST_PR =
|
|
82761
|
+
var VERSION = "0.18.21";
|
|
82762
|
+
var COMMIT_SHA = "c237ff59";
|
|
82763
|
+
var COMMIT_DATE = "2026-07-14T01:11:18+10:00";
|
|
82764
|
+
var LATEST_PR = 3234;
|
|
82041
82765
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
82042
82766
|
|
|
82043
82767
|
// gateway/boot-version.ts
|
|
@@ -83725,6 +84449,7 @@ function applySubagentsSchema(db2) {
|
|
|
83725
84449
|
db2.exec("ALTER TABLE subagents ADD COLUMN model TEXT");
|
|
83726
84450
|
}
|
|
83727
84451
|
db2.exec("CREATE INDEX IF NOT EXISTS subagents_jsonl_id ON subagents(jsonl_agent_id)");
|
|
84452
|
+
db2.exec("CREATE INDEX IF NOT EXISTS subagents_parent_agent ON subagents(parent_agent_id)");
|
|
83728
84453
|
}
|
|
83729
84454
|
function mapSubagentRow(row) {
|
|
83730
84455
|
return {
|
|
@@ -84462,6 +85187,7 @@ function resolveSubagentOriginChat(agentId) {
|
|
|
84462
85187
|
}
|
|
84463
85188
|
}
|
|
84464
85189
|
var WORKER_FEED_FALLBACK_LOG_CAP = 256;
|
|
85190
|
+
var WORKER_FEED_STALE_TTL_MARGIN_MS = 300000;
|
|
84465
85191
|
var workerFeedOwnerDmFallbackLogged = new Set;
|
|
84466
85192
|
function resolveWorkerFeedChat(agentId, fleetChatId) {
|
|
84467
85193
|
const origin = resolveSubagentOriginChat(agentId);
|
|
@@ -85359,14 +86085,14 @@ function releaseTurnBufferGate(key, endingTurn) {
|
|
|
85359
86085
|
}
|
|
85360
86086
|
function emitTurnRecord(turn, endedAt) {
|
|
85361
86087
|
try {
|
|
85362
|
-
const rec = JSON.stringify({
|
|
85363
|
-
ts: Math.floor(endedAt / 1000),
|
|
86088
|
+
const rec = JSON.stringify(buildTurnRecord({
|
|
85364
86089
|
agent: process.env.SWITCHROOM_AGENT_NAME ?? "unknown",
|
|
85365
|
-
|
|
85366
|
-
|
|
85367
|
-
|
|
85368
|
-
|
|
85369
|
-
|
|
86090
|
+
startedAt: turn.startedAt,
|
|
86091
|
+
toolCallCount: turn.toolCallCount ?? 0,
|
|
86092
|
+
turnId: turn.turnId,
|
|
86093
|
+
finalAnswerDelivered: turn.finalAnswerDelivered,
|
|
86094
|
+
deliveryOutcome: turn.deliveryOutcome
|
|
86095
|
+
}, endedAt)) + `
|
|
85370
86096
|
`;
|
|
85371
86097
|
const turnsPath = "/state/agent/turns.jsonl";
|
|
85372
86098
|
maybeRotate(turnsPath, {
|
|
@@ -85382,15 +86108,18 @@ function emitTurnRecord(turn, endedAt) {
|
|
|
85382
86108
|
appendFileSync6(turnsPath, rec);
|
|
85383
86109
|
} catch {}
|
|
85384
86110
|
}
|
|
85385
|
-
function endCurrentTurnAtomic(turn) {
|
|
86111
|
+
function endCurrentTurnAtomic(turn, opts) {
|
|
85386
86112
|
const key = statusKey(turn.sessionChatId, turn.sessionThreadId);
|
|
85387
86113
|
if (!turnLiveForItsTopic(turn))
|
|
85388
|
-
return;
|
|
86114
|
+
return null;
|
|
86115
|
+
clearAnswerReadyFlushTimeout(turn);
|
|
85389
86116
|
endCurrentTurnForKey(turn, key);
|
|
85390
86117
|
const turnEndedAt = Date.now();
|
|
85391
86118
|
process.stderr.write(`telegram gateway: ${formatTurnLifecycle("clear", "turn_end", turn, turnEndedAt)}
|
|
85392
86119
|
`);
|
|
85393
|
-
|
|
86120
|
+
if (opts?.deferRecord !== true) {
|
|
86121
|
+
emitTurnRecord(turn, turnEndedAt);
|
|
86122
|
+
}
|
|
85394
86123
|
const degraded = detectStatusSurfaceDegraded(turn);
|
|
85395
86124
|
if (degraded != null) {
|
|
85396
86125
|
process.stderr.write(`telegram gateway: status-surface DEGRADED reason=${degraded.reason} turnId=${turn.turnId} chat=${turn.sessionChatId} thread=${turn.sessionThreadId ?? "-"} ${degraded.detail}
|
|
@@ -85407,8 +86136,10 @@ function endCurrentTurnAtomic(turn) {
|
|
|
85407
86136
|
clearTimeout(turn.noReplyDrainTimer);
|
|
85408
86137
|
turn.noReplyDrainTimer = null;
|
|
85409
86138
|
}
|
|
86139
|
+
turn.narrativeGate?.teardown();
|
|
85410
86140
|
purgeReactionTracking(statusKey(turn.sessionChatId, turn.sessionThreadId), turn);
|
|
85411
86141
|
armNoReplyDrainTimer(turn);
|
|
86142
|
+
return turnEndedAt;
|
|
85412
86143
|
}
|
|
85413
86144
|
function maybeProactiveCompact() {
|
|
85414
86145
|
if (compactDispatching)
|
|
@@ -85468,16 +86199,9 @@ function maybeProactiveCompact() {
|
|
|
85468
86199
|
compactDispatching = false;
|
|
85469
86200
|
});
|
|
85470
86201
|
}
|
|
85471
|
-
var
|
|
85472
|
-
var lastIdleTurnEndAt = null;
|
|
85473
|
-
var idleAutoCleared = false;
|
|
85474
|
-
var idleClearDispatching = false;
|
|
86202
|
+
var idleTracker = new IdleTracker(Date.now());
|
|
85475
86203
|
function markIdleActivity() {
|
|
85476
|
-
|
|
85477
|
-
idleAutoCleared = false;
|
|
85478
|
-
}
|
|
85479
|
-
function markIdleTurnEnd() {
|
|
85480
|
-
lastIdleTurnEndAt = Date.now();
|
|
86204
|
+
idleTracker.noteInbound(Date.now());
|
|
85481
86205
|
}
|
|
85482
86206
|
function resolveIdleClearMs() {
|
|
85483
86207
|
const env = process.env.SWITCHROOM_IDLE_CLEAR_MS;
|
|
@@ -85502,30 +86226,39 @@ function resolveIdleClearMs() {
|
|
|
85502
86226
|
}
|
|
85503
86227
|
}
|
|
85504
86228
|
function maybeIdleClear() {
|
|
85505
|
-
if (
|
|
86229
|
+
if (idleTracker.isDispatching)
|
|
85506
86230
|
return;
|
|
85507
86231
|
const agentName3 = process.env.SWITCHROOM_AGENT_NAME;
|
|
85508
86232
|
if (!agentName3)
|
|
85509
86233
|
return;
|
|
85510
86234
|
const idleClearMs = resolveIdleClearMs();
|
|
85511
|
-
const decision =
|
|
85512
|
-
lastActivityAt: lastIdleActivityAt,
|
|
85513
|
-
lastTurnEndedAt: lastIdleTurnEndAt,
|
|
86235
|
+
const decision = idleTracker.decide(Date.now(), {
|
|
85514
86236
|
idleClearMs,
|
|
85515
|
-
|
|
85516
|
-
|
|
85517
|
-
}
|
|
86237
|
+
turnInFlight: turnInFlightForGate(),
|
|
86238
|
+
backgroundWorkInFlight: anyPendingAsyncDispatchWithin(BACKGROUND_WORK_SUPPRESS_TTL_MS)
|
|
86239
|
+
});
|
|
85518
86240
|
if (!decision.clear)
|
|
85519
86241
|
return;
|
|
85520
|
-
|
|
85521
|
-
|
|
86242
|
+
idleTracker.markClearFired();
|
|
86243
|
+
idleTracker.beginDispatch();
|
|
85522
86244
|
process.stderr.write(`telegram gateway: idle auto-/clear for ${agentName3} (idle >= ${Math.round(idleClearMs / 60000)}m)
|
|
85523
86245
|
`);
|
|
85524
|
-
|
|
86246
|
+
const stillIdleAtWrite = () => idleTracker.decideIgnoringLatch(Date.now(), {
|
|
86247
|
+
idleClearMs: resolveIdleClearMs(),
|
|
86248
|
+
turnInFlight: turnInFlightForGate(),
|
|
86249
|
+
backgroundWorkInFlight: anyPendingAsyncDispatchWithin(BACKGROUND_WORK_SUPPRESS_TTL_MS)
|
|
86250
|
+
}).clear;
|
|
86251
|
+
injectSlashCommand(agentName3, "/clear", { precondition: stillIdleAtWrite }).then((result) => {
|
|
86252
|
+
if (result.outcome === "skipped") {
|
|
86253
|
+
idleTracker.reArm();
|
|
86254
|
+
process.stderr.write(`telegram gateway: idle /clear suppressed for ${agentName3} (activity in check-to-send gap)
|
|
86255
|
+
`);
|
|
86256
|
+
}
|
|
86257
|
+
}).catch((err) => {
|
|
85525
86258
|
process.stderr.write(`telegram gateway: idle /clear inject failed for ${agentName3}: ${err instanceof Error ? err.message : String(err)}
|
|
85526
86259
|
`);
|
|
85527
86260
|
}).finally(() => {
|
|
85528
|
-
|
|
86261
|
+
idleTracker.endDispatch();
|
|
85529
86262
|
});
|
|
85530
86263
|
}
|
|
85531
86264
|
async function postCompactCard(occ, cap) {
|
|
@@ -86905,6 +87638,7 @@ var STREAM_THROTTLE_MS_OVERRIDE = (() => {
|
|
|
86905
87638
|
return Number.isFinite(n) && n >= 0 ? n : undefined;
|
|
86906
87639
|
})();
|
|
86907
87640
|
var TURN_FLUSH_SAFETY_ENABLED = isTurnFlushSafetyEnabled();
|
|
87641
|
+
var ANSWER_READY_FLUSH_MS2 = resolveAnswerReadyFlushMs(process.env);
|
|
86908
87642
|
var ANSWER_STREAM_VISIBLE_ENABLED = parseVisibleAnswerStreamEnabled(process.env.SWITCHROOM_VISIBLE_ANSWER_STREAM);
|
|
86909
87643
|
var ANSWER_LANE = resolveAnswerLaneConfig({
|
|
86910
87644
|
visibleEnabled: ANSWER_STREAM_VISIBLE_ENABLED
|
|
@@ -87384,6 +88118,7 @@ var SILENCE_FALLBACK_MS = parsePositiveMsEnv("SWITCHROOM_SILENCE_FALLBACK_MS", 3
|
|
|
87384
88118
|
var SILENCE_FALLBACK_HARD_MS = parsePositiveMsEnv("SWITCHROOM_SILENCE_FALLBACK_HARD_MS", 900000);
|
|
87385
88119
|
var SILENCE_FLOOR_MS = parsePositiveMsEnv("SWITCHROOM_SILENCE_FLOOR_MS", 45000);
|
|
87386
88120
|
var LIVENESS_TERMINAL_HONESTY = process.env.SWITCHROOM_TG_TERMINAL_HONESTY !== "0";
|
|
88121
|
+
var CAPTURED_PROSE_DELIVERY_ENABLED = process.env.SWITCHROOM_TG_CAPTURED_PROSE_DELIVERY !== "0";
|
|
87387
88122
|
var SILENCE_DEFER_INFLIGHT_TOOLS = process.env.SWITCHROOM_SILENCE_DEFER_INFLIGHT_TOOLS === "1";
|
|
87388
88123
|
var SILENCE_LIVENESS_PRODUCTION = process.env.SWITCHROOM_SILENCE_LIVENESS_PRODUCTION !== "0";
|
|
87389
88124
|
var MEMORY_LEGIBILITY_ENABLED = isMemoryLegibilityEnabled(process.env.SWITCHROOM_MEMORY_LEGIBILITY);
|
|
@@ -87698,6 +88433,103 @@ function agentHasInFlightBackgroundWork(now) {
|
|
|
87698
88433
|
return ageMs != null && ageMs < TURN_ACTIVE_MARKER_FRESH_MS;
|
|
87699
88434
|
}
|
|
87700
88435
|
var lastBgWorkDeferLogMs = 0;
|
|
88436
|
+
async function deliverCapturedProse(args) {
|
|
88437
|
+
const { chatId, threadId, statusKeyStr, registryKey, originTurnId, text: text5, turnDurationMs } = args;
|
|
88438
|
+
const now = Date.now();
|
|
88439
|
+
let outcome;
|
|
88440
|
+
const already = outboundDedup.check(chatId, threadId, text5, now, registryKey);
|
|
88441
|
+
if (already == null) {
|
|
88442
|
+
let out = normalizeParagraphBreaks2(repairEscapedWhitespace2(text5));
|
|
88443
|
+
out = redactOutboundText(out, "captured_prose");
|
|
88444
|
+
const chunks = splitMarkdownChunks2(out, RICH_MESSAGE_MAX_CHARS2);
|
|
88445
|
+
const sentIds = [];
|
|
88446
|
+
try {
|
|
88447
|
+
let liveThreadId = threadId;
|
|
88448
|
+
for (const c of chunks) {
|
|
88449
|
+
const sent = await retryWithThreadFallback2(robustApiCall, (tid) => {
|
|
88450
|
+
const opts = {
|
|
88451
|
+
link_preview_options: { is_disabled: true },
|
|
88452
|
+
...tid != null ? { message_thread_id: tid } : {}
|
|
88453
|
+
};
|
|
88454
|
+
return bot.api.sendRichMessage(chatId, richMessage2(c), opts);
|
|
88455
|
+
}, { threadId: liveThreadId, chat_id: chatId, verb: "captured-prose.sendMessage" });
|
|
88456
|
+
if (liveThreadId != null && sent.message_thread_id == null) {
|
|
88457
|
+
liveThreadId = undefined;
|
|
88458
|
+
}
|
|
88459
|
+
sentIds.push(sent.message_id);
|
|
88460
|
+
}
|
|
88461
|
+
if (HISTORY_ENABLED && sentIds.length > 0) {
|
|
88462
|
+
try {
|
|
88463
|
+
recordOutbound({
|
|
88464
|
+
chat_id: chatId,
|
|
88465
|
+
thread_id: threadId ?? null,
|
|
88466
|
+
message_ids: sentIds,
|
|
88467
|
+
texts: chunks
|
|
88468
|
+
});
|
|
88469
|
+
} catch {}
|
|
88470
|
+
}
|
|
88471
|
+
outboundDedup.record(chatId, threadId, text5, now, registryKey);
|
|
88472
|
+
process.stderr.write(`telegram gateway: captured-prose delivery \u2014 sent ${out.length} chars recovered from transcript scan (chat=${chatId} origin=${originTurnId})
|
|
88473
|
+
`);
|
|
88474
|
+
outcome = "sent";
|
|
88475
|
+
} catch (err) {
|
|
88476
|
+
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})
|
|
88477
|
+
`);
|
|
88478
|
+
outcome = "failed";
|
|
88479
|
+
}
|
|
88480
|
+
} else {
|
|
88481
|
+
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
|
|
88482
|
+
`);
|
|
88483
|
+
outcome = "skipped-dedup";
|
|
88484
|
+
}
|
|
88485
|
+
const settlement = settleCapturedProseDelivery(outcome, {
|
|
88486
|
+
closeObligation: () => {
|
|
88487
|
+
if (OBLIGATION_LEDGER_ENABLED) {
|
|
88488
|
+
try {
|
|
88489
|
+
obligationLedger.close(originTurnId);
|
|
88490
|
+
} catch {}
|
|
88491
|
+
}
|
|
88492
|
+
},
|
|
88493
|
+
clearState: () => clearSilentEndState(statusKeyStr),
|
|
88494
|
+
recordUndelivered: () => {
|
|
88495
|
+
try {
|
|
88496
|
+
const silentEndDeps = HISTORY_ENABLED ? {
|
|
88497
|
+
hasOutboundDeliveredSince: (cid, sinceMs, tid) => hasOutboundDeliveredSince(cid, sinceMs, tid, 1)
|
|
88498
|
+
} : undefined;
|
|
88499
|
+
return recordUndeliveredTurnEnd({ chatId, threadId: threadId ?? null, turnKey: statusKeyStr }, silentEndDeps);
|
|
88500
|
+
} catch (netErr) {
|
|
88501
|
+
process.stderr.write(`telegram gateway: captured-prose recovery-net arm failed: ${netErr.message} (chat=${chatId} origin=${originTurnId})
|
|
88502
|
+
`);
|
|
88503
|
+
return { exhausted: false };
|
|
88504
|
+
}
|
|
88505
|
+
}
|
|
88506
|
+
});
|
|
88507
|
+
if (outcome === "failed" && settlement.exhausted) {
|
|
88508
|
+
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})
|
|
88509
|
+
`);
|
|
88510
|
+
const plain = redactOutboundText(text5, "captured_prose");
|
|
88511
|
+
const plainChunks = splitMarkdownChunks2(plain, RICH_MESSAGE_MAX_CHARS2);
|
|
88512
|
+
try {
|
|
88513
|
+
let liveThreadId = threadId;
|
|
88514
|
+
for (const c of plainChunks) {
|
|
88515
|
+
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" });
|
|
88516
|
+
if (liveThreadId != null && sent.message_thread_id == null) {
|
|
88517
|
+
liveThreadId = undefined;
|
|
88518
|
+
}
|
|
88519
|
+
}
|
|
88520
|
+
outboundDedup.record(chatId, threadId, text5, Date.now(), registryKey);
|
|
88521
|
+
process.stderr.write(`telegram gateway: captured-prose recovered via plain-text fallback (chat=${chatId} origin=${originTurnId})
|
|
88522
|
+
`);
|
|
88523
|
+
} catch (plainErr) {
|
|
88524
|
+
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})
|
|
88525
|
+
`);
|
|
88526
|
+
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) => {
|
|
88527
|
+
process.stderr.write(`telegram gateway: captured-prose apology fallback send failed: ${err instanceof Error ? err.message : String(err)}
|
|
88528
|
+
`);
|
|
88529
|
+
});
|
|
88530
|
+
}
|
|
88531
|
+
}
|
|
88532
|
+
}
|
|
87701
88533
|
function obligationSweep() {
|
|
87702
88534
|
if (!OBLIGATION_LEDGER_ENABLED)
|
|
87703
88535
|
return;
|
|
@@ -88475,7 +89307,8 @@ var ipcServer = createIpcServer({
|
|
|
88475
89307
|
swallowingApiCall(() => bot.api.editMessageText(operator, msg.messageId, richMessage2(msg.text), {}), { chat_id: String(operator), verb: "rollout-status-edit" });
|
|
88476
89308
|
},
|
|
88477
89309
|
onInjectInbound(_client, msg) {
|
|
88478
|
-
|
|
89310
|
+
if (!isCronInjectFire(msg.inbound.meta))
|
|
89311
|
+
markIdleActivity();
|
|
88479
89312
|
const promptKey = typeof msg.inbound.meta?.prompt_key === "string" ? msg.inbound.meta.prompt_key : "unknown";
|
|
88480
89313
|
const source = typeof msg.inbound.meta?.source === "string" ? msg.inbound.meta.source : "unknown";
|
|
88481
89314
|
const isDurableReplay = inboundSpool != null && typeof msg.inbound.meta?.replay_fire_ms === "string" && msg.inbound.meta.replay_fire_ms.length > 0;
|
|
@@ -90488,7 +91321,7 @@ async function executeEditMessage(args) {
|
|
|
90488
91321
|
editRawText = normalizeParagraphBreaks2(editRawText);
|
|
90489
91322
|
editRawText = redactOutboundText(editRawText, "edit_message");
|
|
90490
91323
|
if (!editLiteralText)
|
|
90491
|
-
editRawText = stripExcessBold2(normalizePunctuation2(editRawText));
|
|
91324
|
+
editRawText = addParagraphSpacers2(stripExcessBold2(normalizePunctuation2(editRawText)));
|
|
90492
91325
|
{
|
|
90493
91326
|
const scrub = scrubVoice2(editRawText);
|
|
90494
91327
|
if (scrub.replaced > 0) {
|
|
@@ -90703,6 +91536,33 @@ function resetOrphanedReplyTimeout() {
|
|
|
90703
91536
|
}, ORPHANED_REPLY_TIMEOUT_MS);
|
|
90704
91537
|
}
|
|
90705
91538
|
}
|
|
91539
|
+
var answerReadyFlush = new AnswerReadyFlushController({
|
|
91540
|
+
getCurrentTurn: () => currentTurn,
|
|
91541
|
+
getArmInput: (turn) => ({
|
|
91542
|
+
flush: {
|
|
91543
|
+
chatId: turn.sessionChatId,
|
|
91544
|
+
replyCalled: turn.replyCalled,
|
|
91545
|
+
capturedText: turn.capturedText,
|
|
91546
|
+
flushEnabled: TURN_FLUSH_SAFETY_ENABLED
|
|
91547
|
+
},
|
|
91548
|
+
inFlightToolCount: toolFlightTracker.inFlightCount(),
|
|
91549
|
+
hasPendingAsyncDispatch: hasPendingAsyncDispatch(statusKey(turn.sessionChatId, turn.sessionThreadId)),
|
|
91550
|
+
flushWindowMs: ANSWER_READY_FLUSH_MS2
|
|
91551
|
+
}),
|
|
91552
|
+
getTimerHandle: (turn) => turn.answerReadyFlushTimeoutId,
|
|
91553
|
+
setTimerHandle: (turn, handle) => {
|
|
91554
|
+
turn.answerReadyFlushTimeoutId = handle;
|
|
91555
|
+
},
|
|
91556
|
+
onFlush: () => handleSessionEvent({ kind: "turn_end", durationMs: -1, reason: "answer-ready-quiescence" }),
|
|
91557
|
+
log: (msg) => process.stderr.write(`telegram gateway: ${msg}
|
|
91558
|
+
`)
|
|
91559
|
+
});
|
|
91560
|
+
function clearAnswerReadyFlushTimeout(turn) {
|
|
91561
|
+
answerReadyFlush.clear(turn);
|
|
91562
|
+
}
|
|
91563
|
+
function resetAnswerReadyFlushTimeout() {
|
|
91564
|
+
answerReadyFlush.reset();
|
|
91565
|
+
}
|
|
90706
91566
|
function closeActivityLane(chatId, threadId) {
|
|
90707
91567
|
const key = chatKeyWithSuffix2(chatId, threadId, "activity");
|
|
90708
91568
|
const stream = activeDraftStreams.get(key);
|
|
@@ -90736,6 +91596,45 @@ function composeTurnActivity(turn, final = false, liveSuffix = "") {
|
|
|
90736
91596
|
};
|
|
90737
91597
|
return renderActivityFeedWithNested2(turn.mirrorLines, childLines, final, liveSuffix, stepCount, header);
|
|
90738
91598
|
}
|
|
91599
|
+
function retractNarrativeLine(turn, text5) {
|
|
91600
|
+
const clipped = clipNarrative2(text5);
|
|
91601
|
+
const idx = turn.mirrorLines.lastIndexOf(clipped);
|
|
91602
|
+
if (idx === -1)
|
|
91603
|
+
return;
|
|
91604
|
+
turn.mirrorLines.splice(idx, 1);
|
|
91605
|
+
const rerender = composeTurnActivity(turn);
|
|
91606
|
+
if (rerender == null)
|
|
91607
|
+
return;
|
|
91608
|
+
turn.activityPendingRender = rerender;
|
|
91609
|
+
const ea = emissionAuthorityFor(turn);
|
|
91610
|
+
cardDrainGate(turn, ea, () => {
|
|
91611
|
+
if (ea.mayDrain(turn)) {
|
|
91612
|
+
ea.openOrEditCard("narrative", () => {
|
|
91613
|
+
turn.activityInFlight = drainActivitySummary(turn, "narrative");
|
|
91614
|
+
});
|
|
91615
|
+
}
|
|
91616
|
+
});
|
|
91617
|
+
}
|
|
91618
|
+
function makeNarrativeGate(turn) {
|
|
91619
|
+
let handle = null;
|
|
91620
|
+
return new NarrativeFlushController({
|
|
91621
|
+
show: (text5) => showNarrativeStep(turn, text5),
|
|
91622
|
+
retractShown: (text5) => retractNarrativeLine(turn, text5)
|
|
91623
|
+
}, {
|
|
91624
|
+
arm: (fn, ms) => {
|
|
91625
|
+
if (handle != null)
|
|
91626
|
+
clearTimeout(handle);
|
|
91627
|
+
handle = setTimeout(fn, ms);
|
|
91628
|
+
handle.unref?.();
|
|
91629
|
+
},
|
|
91630
|
+
disarm: () => {
|
|
91631
|
+
if (handle != null) {
|
|
91632
|
+
clearTimeout(handle);
|
|
91633
|
+
handle = null;
|
|
91634
|
+
}
|
|
91635
|
+
}
|
|
91636
|
+
}, PENDING_NARRATIVE_FLUSH_MS);
|
|
91637
|
+
}
|
|
90739
91638
|
function showNarrativeStep(turn, text5) {
|
|
90740
91639
|
const rendered = appendActivityLabel(turn.mirrorLines, clipNarrative2(text5));
|
|
90741
91640
|
if (rendered == null)
|
|
@@ -90751,31 +91650,13 @@ function showNarrativeStep(turn, text5) {
|
|
|
90751
91650
|
});
|
|
90752
91651
|
}
|
|
90753
91652
|
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);
|
|
91653
|
+
turn.narrativeGate.resolveOnTool(toolName, input);
|
|
90764
91654
|
}
|
|
90765
91655
|
function stagePendingNarrative(turn, text5) {
|
|
90766
|
-
|
|
90767
|
-
showNarrativeStep(turn, turn.pendingNarrative.text);
|
|
90768
|
-
}
|
|
90769
|
-
turn.pendingNarrative = { text: text5 };
|
|
91656
|
+
turn.narrativeGate.stage(text5);
|
|
90770
91657
|
}
|
|
90771
91658
|
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);
|
|
91659
|
+
turn.narrativeGate.flushAtTurnEnd(lastReplyText);
|
|
90779
91660
|
}
|
|
90780
91661
|
async function drainActivitySummary(turn, producer = "tool", openFlags) {
|
|
90781
91662
|
try {
|
|
@@ -91115,11 +91996,7 @@ function handleSessionEvent(ev) {
|
|
|
91115
91996
|
}
|
|
91116
91997
|
{
|
|
91117
91998
|
const durationMs = ev.kind === "turn_end" ? ev.durationMs : undefined;
|
|
91118
|
-
|
|
91119
|
-
if (signal.activity)
|
|
91120
|
-
markIdleActivity();
|
|
91121
|
-
if (signal.turnEnded)
|
|
91122
|
-
markIdleTurnEnd();
|
|
91999
|
+
idleTracker.noteEvent(ev.kind, Date.now(), durationMs);
|
|
91123
92000
|
}
|
|
91124
92001
|
switch (ev.kind) {
|
|
91125
92002
|
case "enqueue": {
|
|
@@ -91139,6 +92016,7 @@ function handleSessionEvent(ev) {
|
|
|
91139
92016
|
clearTimeout(prior.orphanedReplyTimeoutId);
|
|
91140
92017
|
prior.orphanedReplyTimeoutId = null;
|
|
91141
92018
|
}
|
|
92019
|
+
prior?.narrativeGate?.teardown();
|
|
91142
92020
|
const startedAt = Date.now();
|
|
91143
92021
|
const enqThreadIdNum = ev.threadId != null ? Number(ev.threadId) : undefined;
|
|
91144
92022
|
const turnId = deriveTurnId(ev.chatId, enqThreadIdNum ?? null, ev.messageId) ?? `${chatKey2(ev.chatId, enqThreadIdNum ?? null)}#synthetic-${startedAt}`;
|
|
@@ -91164,6 +92042,7 @@ function handleSessionEvent(ev) {
|
|
|
91164
92042
|
silentAnchorText: "",
|
|
91165
92043
|
capturedText: [],
|
|
91166
92044
|
orphanedReplyTimeoutId: null,
|
|
92045
|
+
answerReadyFlushTimeoutId: null,
|
|
91167
92046
|
liveness: new LivenessTracker(startedAt),
|
|
91168
92047
|
turnId,
|
|
91169
92048
|
registryKey: null,
|
|
@@ -91179,13 +92058,14 @@ function handleSessionEvent(ev) {
|
|
|
91179
92058
|
activityEverOpened: false,
|
|
91180
92059
|
activityDrainFailures: 0,
|
|
91181
92060
|
mirrorLines: [],
|
|
91182
|
-
|
|
92061
|
+
narrativeGate: undefined,
|
|
91183
92062
|
lastReplyText: "",
|
|
91184
92063
|
foregroundSubAgents: new Map,
|
|
91185
92064
|
answerStream: null,
|
|
91186
92065
|
isDm: isDmChatId2(ev.chatId),
|
|
91187
92066
|
emissionAuthority: new EmissionAuthority(statusKey(ev.chatId, enqThreadIdNum))
|
|
91188
92067
|
};
|
|
92068
|
+
next.narrativeGate = makeNarrativeGate(next);
|
|
91189
92069
|
setCurrentTurn(next, statusKey(ev.chatId, enqThreadIdNum));
|
|
91190
92070
|
scheduleEarlyLivenessOpen(next);
|
|
91191
92071
|
process.stderr.write(`telegram gateway: ${formatTurnLifecycle("set", "enqueue", next, startedAt)}
|
|
@@ -91257,6 +92137,7 @@ function handleSessionEvent(ev) {
|
|
|
91257
92137
|
const turn = currentTurn;
|
|
91258
92138
|
if (turn == null)
|
|
91259
92139
|
return;
|
|
92140
|
+
clearAnswerReadyFlushTimeout(turn);
|
|
91260
92141
|
resolvePendingNarrativeOnTool(turn, ev.toolName, ev.input);
|
|
91261
92142
|
turn.toolCallCount++;
|
|
91262
92143
|
touchTurnActiveMarker2(STATE_DIR);
|
|
@@ -91291,6 +92172,7 @@ function handleSessionEvent(ev) {
|
|
|
91291
92172
|
const turn = currentTurn;
|
|
91292
92173
|
if (turn == null)
|
|
91293
92174
|
return;
|
|
92175
|
+
clearAnswerReadyFlushTimeout(turn);
|
|
91294
92176
|
resetOrphanedReplyTimeout();
|
|
91295
92177
|
if (isTelegramSurfaceTool2(ev.toolName))
|
|
91296
92178
|
return;
|
|
@@ -91400,6 +92282,7 @@ function handleSessionEvent(ev) {
|
|
|
91400
92282
|
preambleSuppressor.onText(ev.text);
|
|
91401
92283
|
}
|
|
91402
92284
|
resetOrphanedReplyTimeout();
|
|
92285
|
+
resetAnswerReadyFlushTimeout();
|
|
91403
92286
|
if (isContextExhaustionText(ev.text) && turn != null) {
|
|
91404
92287
|
const chatId = turn.sessionChatId;
|
|
91405
92288
|
const threadId = turn.sessionThreadId;
|
|
@@ -91451,7 +92334,7 @@ function handleSessionEvent(ev) {
|
|
|
91451
92334
|
return;
|
|
91452
92335
|
}
|
|
91453
92336
|
case "turn_end": {
|
|
91454
|
-
if (ev.durationMs === -1) {
|
|
92337
|
+
if (ev.durationMs === -1 && ev.reason !== "answer-ready-quiescence") {
|
|
91455
92338
|
const turn = currentTurn;
|
|
91456
92339
|
const key = turn != null ? statusKey(turn.sessionChatId, turn.sessionThreadId) : "";
|
|
91457
92340
|
const recentlyStreaming = turn != null && turn.liveness.recentlyStreaming(Date.now(), ORPHANED_REPLY_STREAM_WINDOW_MS);
|
|
@@ -91525,7 +92408,7 @@ function handleSessionEvent(ev) {
|
|
|
91525
92408
|
const chatId = turn.sessionChatId;
|
|
91526
92409
|
const threadId = turn.sessionThreadId;
|
|
91527
92410
|
const ctrl = activeStatusReactions.get(statusKey(chatId, threadId));
|
|
91528
|
-
const flushDecision = streamFinalizedAsAnswer ? { kind: "skip", reason: "reply-called" } :
|
|
92411
|
+
const flushDecision = streamFinalizedAsAnswer ? { kind: "skip", reason: "reply-called" } : decideTurnFlush2({
|
|
91529
92412
|
chatId: turn.sessionChatId,
|
|
91530
92413
|
replyCalled: turn.replyCalled,
|
|
91531
92414
|
capturedText: turn.capturedText,
|
|
@@ -91635,7 +92518,7 @@ function handleSessionEvent(ev) {
|
|
|
91635
92518
|
}) ?? { wasEmitted: false, turnKey: null };
|
|
91636
92519
|
const backstopCardMessageId = cardTakeover.wasEmitted && cardTakeover.turnKey != null ? getPinnedProgressCardMessageId?.(cardTakeover.turnKey) ?? null : null;
|
|
91637
92520
|
const backstopCardTurnKey = cardTakeover.turnKey;
|
|
91638
|
-
endCurrentTurnAtomic(turn);
|
|
92521
|
+
const backstopTurnEndedAt = endCurrentTurnAtomic(turn, { deferRecord: true });
|
|
91639
92522
|
preambleSuppressor.dropNow();
|
|
91640
92523
|
{
|
|
91641
92524
|
const tKey = statusKey(chatId, threadId);
|
|
@@ -91652,6 +92535,10 @@ function handleSessionEvent(ev) {
|
|
|
91652
92535
|
if (recentCount > 0) {
|
|
91653
92536
|
process.stderr.write(`telegram gateway: turn-flush suppressed \u2014 reply tool sent ${recentCount} message(s) within 2s
|
|
91654
92537
|
`);
|
|
92538
|
+
if (backstopTurnEndedAt != null) {
|
|
92539
|
+
turn.deliveryOutcome = "suppressed";
|
|
92540
|
+
emitTurnRecord(turn, backstopTurnEndedAt);
|
|
92541
|
+
}
|
|
91655
92542
|
return;
|
|
91656
92543
|
}
|
|
91657
92544
|
} catch {}
|
|
@@ -91663,10 +92550,12 @@ function handleSessionEvent(ev) {
|
|
|
91663
92550
|
link_preview_options: { is_disabled: true }
|
|
91664
92551
|
};
|
|
91665
92552
|
const limit = RICH_MESSAGE_MAX_CHARS2;
|
|
91666
|
-
|
|
91667
|
-
const htmlChunks = splitMarkdownChunks2(renderedText, limit);
|
|
92553
|
+
let htmlChunks = [];
|
|
91668
92554
|
const sentIds = [];
|
|
92555
|
+
let sendThrew = false;
|
|
91669
92556
|
try {
|
|
92557
|
+
const renderedText = addParagraphSpacers2(capturedText);
|
|
92558
|
+
htmlChunks = splitMarkdownChunks2(renderedText, limit);
|
|
91670
92559
|
let firstSendUsedEdit = false;
|
|
91671
92560
|
let liveThreadId = backstopThreadId;
|
|
91672
92561
|
if (backstopCardMessageId != null && htmlChunks.length > 0) {
|
|
@@ -91729,10 +92618,20 @@ function handleSessionEvent(ev) {
|
|
|
91729
92618
|
unpinProgressCardForChat?.(backstopChatId, backstopThreadId);
|
|
91730
92619
|
}
|
|
91731
92620
|
} catch (err) {
|
|
92621
|
+
sendThrew = true;
|
|
91732
92622
|
process.stderr.write(`telegram gateway: turn-flush send failed: ${err.message}
|
|
91733
92623
|
`);
|
|
91734
92624
|
if (backstopCtrl)
|
|
91735
92625
|
backstopCtrl.finalize("error");
|
|
92626
|
+
} finally {
|
|
92627
|
+
if (backstopTurnEndedAt != null) {
|
|
92628
|
+
finalizeBackstopSend(turn, {
|
|
92629
|
+
threw: sendThrew,
|
|
92630
|
+
sentCount: sentIds.length,
|
|
92631
|
+
chunkCount: htmlChunks.length
|
|
92632
|
+
});
|
|
92633
|
+
emitTurnRecord(turn, backstopTurnEndedAt);
|
|
92634
|
+
}
|
|
91736
92635
|
}
|
|
91737
92636
|
})();
|
|
91738
92637
|
return;
|
|
@@ -91774,21 +92673,40 @@ function handleSessionEvent(ev) {
|
|
|
91774
92673
|
ended_via: outboundMetrics.outboundCount > 0 ? "reply" : "silent"
|
|
91775
92674
|
});
|
|
91776
92675
|
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)}
|
|
92676
|
+
const proseDecision = CAPTURED_PROSE_DELIVERY_ENABLED ? decideCapturedProseDelivery({
|
|
92677
|
+
turnKey: tKey,
|
|
92678
|
+
turnId: turn.turnId,
|
|
92679
|
+
minChars: CAPTURED_PROSE_MIN_CHARS
|
|
92680
|
+
}) : { deliver: false, reason: "no-state" };
|
|
92681
|
+
if (proseDecision.deliver && proseDecision.text != null) {
|
|
92682
|
+
process.stderr.write(`telegram gateway: captured-prose delivery engaged on first silent-end chat=${chatId} turnKey=${tKey} (#3227)
|
|
91790
92683
|
`);
|
|
92684
|
+
deliverCapturedProse({
|
|
92685
|
+
chatId,
|
|
92686
|
+
threadId,
|
|
92687
|
+
statusKeyStr: tKey,
|
|
92688
|
+
registryKey: turn.registryKey ?? null,
|
|
92689
|
+
originTurnId: turn.turnId,
|
|
92690
|
+
text: proseDecision.text,
|
|
92691
|
+
turnDurationMs
|
|
91791
92692
|
});
|
|
92693
|
+
} else {
|
|
92694
|
+
const silentEndDeps = HISTORY_ENABLED ? {
|
|
92695
|
+
hasOutboundDeliveredSince: (cid, sinceMs, tid) => hasOutboundDeliveredSince(cid, sinceMs, tid, 1)
|
|
92696
|
+
} : undefined;
|
|
92697
|
+
const silentEnd = recordUndeliveredTurnEnd({
|
|
92698
|
+
chatId,
|
|
92699
|
+
threadId: threadId ?? null,
|
|
92700
|
+
turnKey: tKey
|
|
92701
|
+
}, silentEndDeps);
|
|
92702
|
+
if (silentEnd.exhausted) {
|
|
92703
|
+
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)
|
|
92704
|
+
`);
|
|
92705
|
+
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) => {
|
|
92706
|
+
process.stderr.write(`telegram gateway: silent-end fallback send failed: ${err instanceof Error ? err.message : String(err)}
|
|
92707
|
+
`);
|
|
92708
|
+
});
|
|
92709
|
+
}
|
|
91792
92710
|
}
|
|
91793
92711
|
}
|
|
91794
92712
|
clear(tKey);
|
|
@@ -98269,6 +99187,7 @@ var didOneTimeSetup = false;
|
|
|
98269
99187
|
},
|
|
98270
99188
|
floodWaitRemainingMs: probeFloodWaitRemainingMs,
|
|
98271
99189
|
maxRows: workerFeedMaxRows,
|
|
99190
|
+
staleWorkerTtlMs: resolveInflightTerminalCapMs() + WORKER_FEED_STALE_TTL_MARGIN_MS,
|
|
98272
99191
|
reconcilePin: ({ feedKey, chatId, messageId }) => {
|
|
98273
99192
|
if (!PIN_STATUS_WHILE_WORKING)
|
|
98274
99193
|
return;
|
|
@@ -98314,6 +99233,14 @@ var didOneTimeSetup = false;
|
|
|
98314
99233
|
process.stderr.write(`telegram gateway: worker ${agentId} NAMED AS LOST \u2014 falsely finalised twice, resurrection chain bound reached (issue #3023)
|
|
98315
99234
|
`);
|
|
98316
99235
|
},
|
|
99236
|
+
onTerminalCleanup: (agentId) => {
|
|
99237
|
+
try {
|
|
99238
|
+
workerActivityFeed?.terminate(agentId);
|
|
99239
|
+
} catch (err) {
|
|
99240
|
+
process.stderr.write(`telegram gateway: worker terminal-cleanup feed removal error agent=${agentId}: ${err.message}
|
|
99241
|
+
`);
|
|
99242
|
+
}
|
|
99243
|
+
},
|
|
98317
99244
|
onFinish: ({ agentId, outcome, description, resultText, toolCount, durationMs, background: entryBackground }) => {
|
|
98318
99245
|
deferredDoneReactions.promote();
|
|
98319
99246
|
let fleetChatId = "";
|
|
@@ -98442,7 +99369,7 @@ var didOneTimeSetup = false;
|
|
|
98442
99369
|
process.stderr.write(`telegram gateway: subagent-handback queued agent=${agentId} outcome=${outcome} chat=${decision.chatId} resultChars=${resultText.length}
|
|
98443
99370
|
`);
|
|
98444
99371
|
},
|
|
98445
|
-
onProgress: ({ agentId, description, latestSummary, elapsedMs, prevBucketIdx, setBucketIdx, lastTool, toolCount, progressLine, model }) => {
|
|
99372
|
+
onProgress: ({ agentId, description, latestSummary, elapsedMs, prevBucketIdx, setBucketIdx, lastTool, toolCount, progressLine, model, skeleton }) => {
|
|
98446
99373
|
let fleetChatId = "";
|
|
98447
99374
|
try {
|
|
98448
99375
|
const fleets = progressDriver?.peekAllFleets() ?? [];
|
|
@@ -98485,6 +99412,8 @@ var didOneTimeSetup = false;
|
|
|
98485
99412
|
}
|
|
98486
99413
|
if (surface !== "nest")
|
|
98487
99414
|
return;
|
|
99415
|
+
if (skeleton)
|
|
99416
|
+
return;
|
|
98488
99417
|
const turn = currentTurn;
|
|
98489
99418
|
if (turn == null)
|
|
98490
99419
|
return;
|
|
@@ -98544,6 +99473,7 @@ var didOneTimeSetup = false;
|
|
|
98544
99473
|
}
|
|
98545
99474
|
const progressOrigin = resolveSubagentOriginChat(agentId);
|
|
98546
99475
|
const decision = decideSubagentProgress({
|
|
99476
|
+
skeleton: skeleton === true,
|
|
98547
99477
|
disableEnvValue: process.env.SWITCHROOM_DISABLE_SUBAGENT_PROGRESS,
|
|
98548
99478
|
isBackground,
|
|
98549
99479
|
fleetChatId: progressOrigin?.chatId || fleetChatId,
|