switchroom 0.18.20 → 0.18.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/switchroom.js +24 -1
- 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/dist/gateway/gateway.js +268 -61
- package/telegram-plugin/flushed-turn-supersede.ts +230 -0
- package/telegram-plugin/gateway/gateway.ts +88 -1
- package/telegram-plugin/gateway/subagent-progress-inbound-builder.ts +17 -0
- package/telegram-plugin/registry/subagents-schema.ts +6 -0
- package/telegram-plugin/subagent-watcher.ts +86 -1
- package/telegram-plugin/tests/flushed-turn-supersede.test.ts +206 -0
- package/telegram-plugin/tests/nested-worker-visibility-harness.test.ts +20 -0
- 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 +7 -5
- package/telegram-plugin/tests/subagent-watcher.test.ts +13 -12
- package/telegram-plugin/tests/turn-flush-safety.test.ts +71 -0
- package/telegram-plugin/tests/worker-activity-feed.test.ts +13 -8
- package/telegram-plugin/tests/worker-feed-coalesce.test.ts +196 -0
- package/telegram-plugin/tests/worker-feed-terminal-state-truthful.test.ts +40 -0
- package/telegram-plugin/turn-flush-safety.ts +74 -1
- package/telegram-plugin/worker-activity-feed.ts +155 -45
|
@@ -39283,6 +39283,92 @@ function normalizeForDedup(text) {
|
|
|
39283
39283
|
return text.replace(/<\/?[a-zA-Z][^>]*>/g, "").replace(/&[a-zA-Z]+;|&#\d+;/g, " ").replace(/(\*\*|__|`)+/g, "").replace(/^[#>\-*+]\s+/gm, "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
39284
39284
|
}
|
|
39285
39285
|
|
|
39286
|
+
// flushed-turn-supersede.ts
|
|
39287
|
+
var DEFAULT_SUPERSEDE_TTL_MS = 60000;
|
|
39288
|
+
function decideSupersede(record, args) {
|
|
39289
|
+
const ttlMs = args.ttlMs ?? DEFAULT_SUPERSEDE_TTL_MS;
|
|
39290
|
+
if (record == null)
|
|
39291
|
+
return { supersede: false, deleteMessageIds: [], reason: "no-record" };
|
|
39292
|
+
if (args.now - record.ts > ttlMs) {
|
|
39293
|
+
return { supersede: false, deleteMessageIds: [], reason: "expired" };
|
|
39294
|
+
}
|
|
39295
|
+
const sameTurn = record.turnId != null ? record.turnId === args.liveTurnId : args.liveTurnId == null;
|
|
39296
|
+
if (!sameTurn) {
|
|
39297
|
+
return { supersede: false, deleteMessageIds: [], reason: "different-turn" };
|
|
39298
|
+
}
|
|
39299
|
+
return { supersede: true, deleteMessageIds: [...record.messageIds], reason: "supersede" };
|
|
39300
|
+
}
|
|
39301
|
+
var NULL_TURN_KEY = "<<null-turn>>";
|
|
39302
|
+
function turnKey(turnId) {
|
|
39303
|
+
return turnId == null ? NULL_TURN_KEY : turnId;
|
|
39304
|
+
}
|
|
39305
|
+
|
|
39306
|
+
class FlushedTurnSupersedeRegistry {
|
|
39307
|
+
entries = new Map;
|
|
39308
|
+
ttlMs;
|
|
39309
|
+
constructor(opts = {}) {
|
|
39310
|
+
this.ttlMs = opts.ttlMs ?? DEFAULT_SUPERSEDE_TTL_MS;
|
|
39311
|
+
}
|
|
39312
|
+
record(chatId, threadId, rec, now) {
|
|
39313
|
+
if (rec.messageIds.length === 0)
|
|
39314
|
+
return;
|
|
39315
|
+
this.sweep(now);
|
|
39316
|
+
const lane = makeKey2(chatId, threadId);
|
|
39317
|
+
let laneMap = this.entries.get(lane);
|
|
39318
|
+
if (laneMap == null) {
|
|
39319
|
+
laneMap = new Map;
|
|
39320
|
+
this.entries.set(lane, laneMap);
|
|
39321
|
+
}
|
|
39322
|
+
laneMap.set(turnKey(rec.turnId), {
|
|
39323
|
+
turnId: rec.turnId,
|
|
39324
|
+
messageIds: [...rec.messageIds],
|
|
39325
|
+
text: rec.text,
|
|
39326
|
+
ts: now
|
|
39327
|
+
});
|
|
39328
|
+
}
|
|
39329
|
+
peek(chatId, threadId, args) {
|
|
39330
|
+
const rec = this.entries.get(makeKey2(chatId, threadId))?.get(turnKey(args.liveTurnId));
|
|
39331
|
+
return decideSupersede(rec, { liveTurnId: args.liveTurnId, now: args.now, ttlMs: this.ttlMs });
|
|
39332
|
+
}
|
|
39333
|
+
take(chatId, threadId, args) {
|
|
39334
|
+
const lane = makeKey2(chatId, threadId);
|
|
39335
|
+
const decision = this.peek(chatId, threadId, args);
|
|
39336
|
+
if (decision.supersede) {
|
|
39337
|
+
const laneMap = this.entries.get(lane);
|
|
39338
|
+
laneMap?.delete(turnKey(args.liveTurnId));
|
|
39339
|
+
if (laneMap != null && laneMap.size === 0)
|
|
39340
|
+
this.entries.delete(lane);
|
|
39341
|
+
}
|
|
39342
|
+
return decision;
|
|
39343
|
+
}
|
|
39344
|
+
forget(chatId, threadId) {
|
|
39345
|
+
this.entries.delete(makeKey2(chatId, threadId));
|
|
39346
|
+
}
|
|
39347
|
+
clear() {
|
|
39348
|
+
this.entries.clear();
|
|
39349
|
+
}
|
|
39350
|
+
sweep(now) {
|
|
39351
|
+
for (const [lane, laneMap] of this.entries) {
|
|
39352
|
+
for (const [tk, rec] of laneMap) {
|
|
39353
|
+
if (now - rec.ts > this.ttlMs)
|
|
39354
|
+
laneMap.delete(tk);
|
|
39355
|
+
}
|
|
39356
|
+
if (laneMap.size === 0)
|
|
39357
|
+
this.entries.delete(lane);
|
|
39358
|
+
}
|
|
39359
|
+
}
|
|
39360
|
+
size(now) {
|
|
39361
|
+
this.sweep(now);
|
|
39362
|
+
let total = 0;
|
|
39363
|
+
for (const laneMap of this.entries.values())
|
|
39364
|
+
total += laneMap.size;
|
|
39365
|
+
return total;
|
|
39366
|
+
}
|
|
39367
|
+
}
|
|
39368
|
+
function makeKey2(chatId, threadId) {
|
|
39369
|
+
return threadId == null ? chatId : `${chatId}|${threadId}`;
|
|
39370
|
+
}
|
|
39371
|
+
|
|
39286
39372
|
// gateway/inbound-coalesce.ts
|
|
39287
39373
|
function createInboundCoalescer(opts) {
|
|
39288
39374
|
const buffer = new Map;
|
|
@@ -40593,44 +40679,63 @@ function createWorkerActivityFeed(opts) {
|
|
|
40593
40679
|
function removeWorker(g, agentId) {
|
|
40594
40680
|
g.workers.delete(agentId);
|
|
40595
40681
|
agentIndex.delete(agentId);
|
|
40596
|
-
|
|
40682
|
+
maybeDeleteGroup(g);
|
|
40683
|
+
}
|
|
40684
|
+
function maybeDeleteGroup(g) {
|
|
40685
|
+
if (g.workers.size === 0 && g.pendingFinalize.size === 0)
|
|
40597
40686
|
groups.delete(g.feedKey);
|
|
40598
40687
|
}
|
|
40688
|
+
function hasLiveWorker(g) {
|
|
40689
|
+
for (const w of g.workers.values())
|
|
40690
|
+
if (!w.finished)
|
|
40691
|
+
return true;
|
|
40692
|
+
return false;
|
|
40693
|
+
}
|
|
40599
40694
|
function syncPin(g) {
|
|
40600
|
-
const messageId = g.messageId != null && g
|
|
40695
|
+
const messageId = g.messageId != null && hasLiveWorker(g) ? g.messageId : null;
|
|
40601
40696
|
reconcilePinFn({ feedKey: g.feedKey, chatId: g.chatId, threadId: g.threadId, messageId });
|
|
40602
40697
|
}
|
|
40603
40698
|
async function doRender(g, opts2 = {}) {
|
|
40604
40699
|
const now = nowFn();
|
|
40605
40700
|
const isTerminal = opts2.terminalRecap != null;
|
|
40606
|
-
const
|
|
40607
|
-
|
|
40608
|
-
if (opts2.
|
|
40609
|
-
|
|
40701
|
+
const finishingAgentId = opts2.finishingAgentId;
|
|
40702
|
+
const stageRecap = () => {
|
|
40703
|
+
if (isTerminal && finishingAgentId != null && opts2.terminalRecap != null) {
|
|
40704
|
+
g.pendingFinalize.set(finishingAgentId, opts2.terminalRecap);
|
|
40705
|
+
}
|
|
40706
|
+
};
|
|
40707
|
+
const clearStaged = () => {
|
|
40708
|
+
if (finishingAgentId != null)
|
|
40709
|
+
g.pendingFinalize.delete(finishingAgentId);
|
|
40710
|
+
maybeDeleteGroup(g);
|
|
40610
40711
|
syncPin(g);
|
|
40611
40712
|
};
|
|
40713
|
+
if (isTerminal) {
|
|
40714
|
+
stageRecap();
|
|
40715
|
+
if (finishingAgentId != null && g.workers.has(finishingAgentId)) {
|
|
40716
|
+
removeWorker(g, finishingAgentId);
|
|
40717
|
+
}
|
|
40718
|
+
g.terminalPainted = !hasLiveWorker(g);
|
|
40719
|
+
syncPin(g);
|
|
40720
|
+
}
|
|
40612
40721
|
if (now < g.cooldownUntil) {
|
|
40613
|
-
if (isTerminal && opts2.terminalRecap != null)
|
|
40614
|
-
g.pendingFinalize = opts2.terminalRecap;
|
|
40615
40722
|
return;
|
|
40616
40723
|
}
|
|
40617
40724
|
if (parkIfFloodWindowOpen(g)) {
|
|
40618
|
-
if (isTerminal && opts2.terminalRecap != null)
|
|
40619
|
-
g.pendingFinalize = opts2.terminalRecap;
|
|
40620
40725
|
return;
|
|
40621
40726
|
}
|
|
40622
40727
|
const body = renderGroupBody(g, now, opts2.terminalRecap ?? null, opts2.heartbeat ?? false);
|
|
40623
40728
|
if (body == null) {
|
|
40624
40729
|
if (isTerminal)
|
|
40625
|
-
|
|
40730
|
+
clearStaged();
|
|
40626
40731
|
return;
|
|
40627
40732
|
}
|
|
40628
40733
|
if (g.messageId == null) {
|
|
40629
|
-
const maxElapsed = Math.max(0, ...runningRows(g).map((r) => liveElapsed(r, now)));
|
|
40630
40734
|
if (isTerminal) {
|
|
40631
|
-
|
|
40735
|
+
clearStaged();
|
|
40632
40736
|
return;
|
|
40633
40737
|
}
|
|
40738
|
+
const maxElapsed = Math.max(0, ...runningRows(g).map((r) => liveElapsed(r, now)));
|
|
40634
40739
|
if (maxElapsed < firstPaintMin)
|
|
40635
40740
|
return;
|
|
40636
40741
|
try {
|
|
@@ -40643,6 +40748,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
40643
40748
|
g.messageId = sent.message_id;
|
|
40644
40749
|
g.lastBody = body;
|
|
40645
40750
|
g.lastEditAt = now;
|
|
40751
|
+
g.terminalPainted = false;
|
|
40646
40752
|
syncPin(g);
|
|
40647
40753
|
log(`worker-feed: paint feed=${g.feedKey} chat=${g.chatId} ` + `thread=${g.threadId ?? "-"} msgId=${g.messageId} workers=${g.workers.size} bytes=${body.length}`);
|
|
40648
40754
|
} catch (err) {
|
|
@@ -40653,7 +40759,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
40653
40759
|
}
|
|
40654
40760
|
if (body === g.lastBody) {
|
|
40655
40761
|
if (isTerminal)
|
|
40656
|
-
|
|
40762
|
+
clearStaged();
|
|
40657
40763
|
return;
|
|
40658
40764
|
}
|
|
40659
40765
|
if (!opts2.force && now - g.lastEditAt < minEditInterval)
|
|
@@ -40662,45 +40768,39 @@ function createWorkerActivityFeed(opts) {
|
|
|
40662
40768
|
const res = await opts.bot.editMessageText(g.chatId, g.messageId, body, sendOptsFor(g));
|
|
40663
40769
|
if (isSendGateShed(res)) {
|
|
40664
40770
|
parkIfFloodWindowOpen(g);
|
|
40665
|
-
if (isTerminal && opts2.terminalRecap != null)
|
|
40666
|
-
g.pendingFinalize = opts2.terminalRecap;
|
|
40667
40771
|
return;
|
|
40668
40772
|
}
|
|
40669
40773
|
g.lastBody = body;
|
|
40670
40774
|
g.lastEditAt = now;
|
|
40671
40775
|
if (isTerminal) {
|
|
40672
|
-
log(`worker-feed: finish feed=${g.feedKey} chat=${g.chatId} thread=${g.threadId ?? "-"} ` + `msgId=${g.messageId} agent=${
|
|
40776
|
+
log(`worker-feed: finish feed=${g.feedKey} chat=${g.chatId} thread=${g.threadId ?? "-"} ` + `msgId=${g.messageId} agent=${finishingAgentId ?? "-"} ` + `state=${opts2.terminalRecap?.state ?? "done"} bytes=${body.length}`);
|
|
40673
40777
|
} else {
|
|
40674
40778
|
log(`worker-feed: edit feed=${g.feedKey} chat=${g.chatId} ` + `thread=${g.threadId ?? "-"} msgId=${g.messageId} workers=${g.workers.size} bytes=${body.length}`);
|
|
40675
40779
|
}
|
|
40676
40780
|
if (isTerminal)
|
|
40677
|
-
|
|
40781
|
+
clearStaged();
|
|
40678
40782
|
} catch (err) {
|
|
40679
40783
|
const outcome = classifyEditError(err);
|
|
40680
40784
|
if (outcome === "rate_limited") {
|
|
40681
40785
|
noteRateLimited(g, err, isTerminal ? "finish" : "edit");
|
|
40682
|
-
if (isTerminal && opts2.terminalRecap != null)
|
|
40683
|
-
g.pendingFinalize = opts2.terminalRecap;
|
|
40684
40786
|
return;
|
|
40685
40787
|
}
|
|
40686
40788
|
if (outcome === "not_modified") {
|
|
40687
40789
|
g.lastBody = body;
|
|
40688
40790
|
g.lastEditAt = now;
|
|
40689
40791
|
if (isTerminal)
|
|
40690
|
-
|
|
40792
|
+
clearStaged();
|
|
40691
40793
|
return;
|
|
40692
40794
|
}
|
|
40693
40795
|
if (outcome === "gone") {
|
|
40694
40796
|
g.messageId = null;
|
|
40695
40797
|
g.lastBody = null;
|
|
40696
40798
|
if (isTerminal)
|
|
40697
|
-
|
|
40799
|
+
clearStaged();
|
|
40698
40800
|
else
|
|
40699
40801
|
syncPin(g);
|
|
40700
40802
|
return;
|
|
40701
40803
|
}
|
|
40702
|
-
if (isTerminal && opts2.terminalRecap != null)
|
|
40703
|
-
g.pendingFinalize = opts2.terminalRecap;
|
|
40704
40804
|
log(`worker-feed: edit transient error feed=${g.feedKey}: ${err.message}`);
|
|
40705
40805
|
}
|
|
40706
40806
|
}
|
|
@@ -40747,24 +40847,34 @@ function createWorkerActivityFeed(opts) {
|
|
|
40747
40847
|
function heartbeatTick() {
|
|
40748
40848
|
const now = nowFn();
|
|
40749
40849
|
const staleAgentIds = [];
|
|
40850
|
+
const staleFinished = [];
|
|
40750
40851
|
for (const g of groups.values()) {
|
|
40751
40852
|
for (const row of g.workers.values()) {
|
|
40752
|
-
if (
|
|
40753
|
-
|
|
40853
|
+
if (now - row.lastUpdateAt >= staleWorkerTtlMs) {
|
|
40854
|
+
if (row.finished)
|
|
40855
|
+
staleFinished.push({ g, agentId: row.agentId });
|
|
40856
|
+
else
|
|
40857
|
+
staleAgentIds.push(row.agentId);
|
|
40754
40858
|
}
|
|
40755
40859
|
}
|
|
40756
40860
|
}
|
|
40861
|
+
for (const { g, agentId } of staleFinished) {
|
|
40862
|
+
log(`worker-feed: TTL GC finished row agent=${agentId} feed=${g.feedKey} \u2014 reaping leaked finished row`);
|
|
40863
|
+
g.pendingFinalize.delete(agentId);
|
|
40864
|
+
removeWorker(g, agentId);
|
|
40865
|
+
syncPin(g);
|
|
40866
|
+
}
|
|
40757
40867
|
for (const agentId of staleAgentIds) {
|
|
40758
40868
|
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
40869
|
terminateWorker(agentId);
|
|
40760
40870
|
}
|
|
40761
40871
|
for (const g of [...groups.values()]) {
|
|
40762
|
-
if (g.pendingFinalize
|
|
40763
|
-
const recap
|
|
40764
|
-
|
|
40765
|
-
|
|
40766
|
-
|
|
40767
|
-
}
|
|
40872
|
+
if (g.pendingFinalize.size > 0 && now >= g.cooldownUntil) {
|
|
40873
|
+
for (const [agentId, recap] of [...g.pendingFinalize]) {
|
|
40874
|
+
g.chain = g.chain.then(() => doRender(g, { force: true, terminalRecap: recap, finishingAgentId: agentId })).catch((err) => {
|
|
40875
|
+
log(`worker-feed: heartbeat finalize re-drive error feed=${g.feedKey}: ${err.message}`);
|
|
40876
|
+
});
|
|
40877
|
+
}
|
|
40768
40878
|
continue;
|
|
40769
40879
|
}
|
|
40770
40880
|
if (now < g.cooldownUntil)
|
|
@@ -40802,7 +40912,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
40802
40912
|
},
|
|
40803
40913
|
hasRunningInFeed(feedKey) {
|
|
40804
40914
|
const g = groups.get(feedKey);
|
|
40805
|
-
return g != null && g
|
|
40915
|
+
return g != null && hasLiveWorker(g);
|
|
40806
40916
|
},
|
|
40807
40917
|
get size() {
|
|
40808
40918
|
let n = 0;
|
|
@@ -40831,10 +40941,18 @@ function createWorkerActivityFeed(opts) {
|
|
|
40831
40941
|
cooldownUntil: 0,
|
|
40832
40942
|
chain: Promise.resolve(),
|
|
40833
40943
|
workers: new Map,
|
|
40834
|
-
pendingFinalize:
|
|
40944
|
+
pendingFinalize: new Map,
|
|
40945
|
+
terminalPainted: false
|
|
40835
40946
|
};
|
|
40836
40947
|
groups.set(feedKey, g);
|
|
40837
40948
|
}
|
|
40949
|
+
if (!g.workers.has(agentId) && g.terminalPainted && !hasLiveWorker(g)) {
|
|
40950
|
+
g.messageId = null;
|
|
40951
|
+
g.lastBody = null;
|
|
40952
|
+
g.pendingFinalize.clear();
|
|
40953
|
+
g.terminalPainted = false;
|
|
40954
|
+
syncPin(g);
|
|
40955
|
+
}
|
|
40838
40956
|
let row = g.workers.get(agentId);
|
|
40839
40957
|
if (row == null) {
|
|
40840
40958
|
row = {
|
|
@@ -62236,16 +62354,16 @@ function writeSilentEndState(args, deps) {
|
|
|
62236
62354
|
`);
|
|
62237
62355
|
}
|
|
62238
62356
|
}
|
|
62239
|
-
function clearSilentEndState(
|
|
62357
|
+
function clearSilentEndState(turnKey2, deps) {
|
|
62240
62358
|
const statePath = resolveStatePath2(deps);
|
|
62241
62359
|
if (!existsSync14(statePath))
|
|
62242
62360
|
return;
|
|
62243
62361
|
try {
|
|
62244
62362
|
const prev = JSON.parse(readFileSync16(statePath, "utf8"));
|
|
62245
|
-
if (prev.turnKey != null && prev.turnKey !==
|
|
62363
|
+
if (prev.turnKey != null && prev.turnKey !== turnKey2)
|
|
62246
62364
|
return;
|
|
62247
62365
|
unlinkSync9(statePath);
|
|
62248
|
-
emitLog(deps, `silent-end: cleared state file turnKey=${
|
|
62366
|
+
emitLog(deps, `silent-end: cleared state file turnKey=${turnKey2}
|
|
62249
62367
|
`);
|
|
62250
62368
|
} catch {}
|
|
62251
62369
|
}
|
|
@@ -62407,6 +62525,23 @@ function endsWithSilentMarker(text4) {
|
|
|
62407
62525
|
return false;
|
|
62408
62526
|
return isSilentFlushMarker(lines[lines.length - 1]);
|
|
62409
62527
|
}
|
|
62528
|
+
function selectFlushDeliveryText(blocks) {
|
|
62529
|
+
const candidates = blocks.map((b) => b.trim()).filter((b) => b.length > 0);
|
|
62530
|
+
if (candidates.length === 0)
|
|
62531
|
+
return "";
|
|
62532
|
+
if (candidates.length === 1)
|
|
62533
|
+
return candidates[0];
|
|
62534
|
+
const answer = candidates[candidates.length - 1];
|
|
62535
|
+
const preceding = candidates.slice(0, -1);
|
|
62536
|
+
const allNarration = preceding.every(isNarrationBlock);
|
|
62537
|
+
return allNarration ? answer : candidates.join(`
|
|
62538
|
+
|
|
62539
|
+
`);
|
|
62540
|
+
}
|
|
62541
|
+
var NARRATION_OPENER = /^(let me\b|lemme\b|i'?ll\b|i will\b|i am going to\b|i'?m going to\b|i'?m about to\b|going to\b|first,?\s+(?:let me|i'?ll|i will)\b|now,?\s+(?:let me|i'?ll|i will)\b|next,?\s+(?:let me|i'?ll|i will)\b|let'?s\b)/i;
|
|
62542
|
+
function isNarrationBlock(block) {
|
|
62543
|
+
return NARRATION_OPENER.test(block.trimStart());
|
|
62544
|
+
}
|
|
62410
62545
|
function decideTurnFlush(input) {
|
|
62411
62546
|
const flushEnabled = input.flushEnabled !== false;
|
|
62412
62547
|
if (!flushEnabled)
|
|
@@ -62426,7 +62561,7 @@ function decideTurnFlush(input) {
|
|
|
62426
62561
|
return { kind: "skip", reason: "silent-marker" };
|
|
62427
62562
|
if (endsWithSilentMarker(joined))
|
|
62428
62563
|
return { kind: "skip", reason: "silent-marker" };
|
|
62429
|
-
return { kind: "flush", text:
|
|
62564
|
+
return { kind: "flush", text: selectFlushDeliveryText(input.capturedText) };
|
|
62430
62565
|
}
|
|
62431
62566
|
|
|
62432
62567
|
// answer-stream.ts
|
|
@@ -68030,6 +68165,23 @@ function endsWithSilentMarker2(text4) {
|
|
|
68030
68165
|
return false;
|
|
68031
68166
|
return isSilentFlushMarker2(lines[lines.length - 1]);
|
|
68032
68167
|
}
|
|
68168
|
+
function selectFlushDeliveryText2(blocks) {
|
|
68169
|
+
const candidates = blocks.map((b) => b.trim()).filter((b) => b.length > 0);
|
|
68170
|
+
if (candidates.length === 0)
|
|
68171
|
+
return "";
|
|
68172
|
+
if (candidates.length === 1)
|
|
68173
|
+
return candidates[0];
|
|
68174
|
+
const answer = candidates[candidates.length - 1];
|
|
68175
|
+
const preceding = candidates.slice(0, -1);
|
|
68176
|
+
const allNarration = preceding.every(isNarrationBlock2);
|
|
68177
|
+
return allNarration ? answer : candidates.join(`
|
|
68178
|
+
|
|
68179
|
+
`);
|
|
68180
|
+
}
|
|
68181
|
+
var NARRATION_OPENER2 = /^(let me\b|lemme\b|i'?ll\b|i will\b|i am going to\b|i'?m going to\b|i'?m about to\b|going to\b|first,?\s+(?:let me|i'?ll|i will)\b|now,?\s+(?:let me|i'?ll|i will)\b|next,?\s+(?:let me|i'?ll|i will)\b|let'?s\b)/i;
|
|
68182
|
+
function isNarrationBlock2(block) {
|
|
68183
|
+
return NARRATION_OPENER2.test(block.trimStart());
|
|
68184
|
+
}
|
|
68033
68185
|
function decideTurnFlush2(input) {
|
|
68034
68186
|
const flushEnabled = input.flushEnabled !== false;
|
|
68035
68187
|
if (!flushEnabled)
|
|
@@ -68049,7 +68201,7 @@ function decideTurnFlush2(input) {
|
|
|
68049
68201
|
return { kind: "skip", reason: "silent-marker" };
|
|
68050
68202
|
if (endsWithSilentMarker2(joined))
|
|
68051
68203
|
return { kind: "skip", reason: "silent-marker" };
|
|
68052
|
-
return { kind: "flush", text:
|
|
68204
|
+
return { kind: "flush", text: selectFlushDeliveryText2(input.capturedText) };
|
|
68053
68205
|
}
|
|
68054
68206
|
function isTurnFlushSafetyEnabled(env = process.env) {
|
|
68055
68207
|
const raw = env.SWITCHROOM_TG_TURN_FLUSH_SAFETY;
|
|
@@ -75253,11 +75405,11 @@ function writeActivityCardRecord(path2, fs2, record, log = (l) => process.stderr
|
|
|
75253
75405
|
const others = current.filter((c) => c.turnKey !== record.turnKey);
|
|
75254
75406
|
persistActivityCards(path2, fs2, [...others, record], log);
|
|
75255
75407
|
}
|
|
75256
|
-
function clearActivityCardRecord(path2, fs2,
|
|
75408
|
+
function clearActivityCardRecord(path2, fs2, turnKey2, activityMessageId, log = (l) => process.stderr.write(l)) {
|
|
75257
75409
|
const current = loadActivityCards(path2, fs2);
|
|
75258
75410
|
if (current.length === 0)
|
|
75259
75411
|
return;
|
|
75260
|
-
const next = current.filter((c) => c.turnKey !==
|
|
75412
|
+
const next = current.filter((c) => c.turnKey !== turnKey2 || activityMessageId !== undefined && c.activityMessageId !== activityMessageId);
|
|
75261
75413
|
if (next.length === current.length)
|
|
75262
75414
|
return;
|
|
75263
75415
|
persistActivityCards(path2, fs2, next, log);
|
|
@@ -77259,6 +77411,9 @@ function decideSubagentProgress(input) {
|
|
|
77259
77411
|
if (isEnvFlagOn(input.disableEnvValue)) {
|
|
77260
77412
|
return { deliver: false, reason: "env-disabled" };
|
|
77261
77413
|
}
|
|
77414
|
+
if (input.skeleton === true) {
|
|
77415
|
+
return { deliver: false, reason: "skeleton-liveness" };
|
|
77416
|
+
}
|
|
77262
77417
|
if (!input.isBackground) {
|
|
77263
77418
|
return { deliver: false, reason: "foreground" };
|
|
77264
77419
|
}
|
|
@@ -79121,8 +79276,40 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
|
|
|
79121
79276
|
tail.cursor = 0;
|
|
79122
79277
|
tail.pendingPartial = "";
|
|
79123
79278
|
}
|
|
79124
|
-
if (stat.size === tail.cursor)
|
|
79279
|
+
if (stat.size === tail.cursor) {
|
|
79280
|
+
if (onProgress != null && entry.state === "running" && !entry.historical) {
|
|
79281
|
+
let hasChild = false;
|
|
79282
|
+
if (db2 != null) {
|
|
79283
|
+
try {
|
|
79284
|
+
const kid = db2.prepare("SELECT 1 FROM subagents WHERE parent_agent_id = ? LIMIT 1").get(entry.agentId);
|
|
79285
|
+
hasChild = kid != null;
|
|
79286
|
+
} catch (kidErr) {
|
|
79287
|
+
log?.(`subagent-watcher: skeleton child-check error ${entry.agentId}: ${kidErr.message}`);
|
|
79288
|
+
}
|
|
79289
|
+
}
|
|
79290
|
+
if (!hasChild) {
|
|
79291
|
+
try {
|
|
79292
|
+
onProgress({
|
|
79293
|
+
agentId: entry.agentId,
|
|
79294
|
+
description: entry.description,
|
|
79295
|
+
latestSummary: "",
|
|
79296
|
+
elapsedMs: now - entry.dispatchedAt,
|
|
79297
|
+
prevBucketIdx: entry.lastProgressBucketIdx,
|
|
79298
|
+
setBucketIdx: (b) => {
|
|
79299
|
+
entry.lastProgressBucketIdx = b;
|
|
79300
|
+
},
|
|
79301
|
+
lastTool: entry.lastTool,
|
|
79302
|
+
toolCount: entry.toolCount,
|
|
79303
|
+
model: entry.currentModel,
|
|
79304
|
+
skeleton: true
|
|
79305
|
+
});
|
|
79306
|
+
} catch (cbErr) {
|
|
79307
|
+
log?.(`subagent-watcher: onProgress (skeleton) callback error ${entry.agentId}: ${cbErr.message}`);
|
|
79308
|
+
}
|
|
79309
|
+
}
|
|
79310
|
+
}
|
|
79125
79311
|
return;
|
|
79312
|
+
}
|
|
79126
79313
|
const buf = Buffer.alloc(stat.size - tail.cursor);
|
|
79127
79314
|
const fd = fs2.openSync(entry.filePath, "r");
|
|
79128
79315
|
try {
|
|
@@ -82723,10 +82910,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
|
|
|
82723
82910
|
}
|
|
82724
82911
|
|
|
82725
82912
|
// ../src/build-info.ts
|
|
82726
|
-
var VERSION = "0.18.
|
|
82727
|
-
var COMMIT_SHA = "
|
|
82728
|
-
var COMMIT_DATE = "2026-07-
|
|
82729
|
-
var LATEST_PR =
|
|
82913
|
+
var VERSION = "0.18.22";
|
|
82914
|
+
var COMMIT_SHA = "5da23264";
|
|
82915
|
+
var COMMIT_DATE = "2026-07-14T09:12:02+10:00";
|
|
82916
|
+
var LATEST_PR = 3236;
|
|
82730
82917
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
82731
82918
|
|
|
82732
82919
|
// gateway/boot-version.ts
|
|
@@ -83818,8 +84005,8 @@ function recordTurnEnd(db2, args) {
|
|
|
83818
84005
|
WHERE turn_key = ?
|
|
83819
84006
|
`).run(now, args.endedVia, args.lastAssistantMsgId ?? null, args.lastAssistantDone !== undefined ? args.lastAssistantDone ? 1 : 0 : null, args.assistantReplyPreview ?? null, args.toolCallCount !== undefined ? args.toolCallCount : null, now, args.turnKey);
|
|
83820
84007
|
}
|
|
83821
|
-
function getTurnByKey(db2,
|
|
83822
|
-
const row = db2.prepare(`SELECT * FROM turns WHERE turn_key = ?`).get(
|
|
84008
|
+
function getTurnByKey(db2, turnKey2) {
|
|
84009
|
+
const row = db2.prepare(`SELECT * FROM turns WHERE turn_key = ?`).get(turnKey2);
|
|
83823
84010
|
return row ? mapRow(row) : null;
|
|
83824
84011
|
}
|
|
83825
84012
|
function markOrphanedWithTimeoutClassification(db2, opts) {
|
|
@@ -83877,13 +84064,13 @@ var INTERRUPTED_VIA = new Set([
|
|
|
83877
84064
|
"timeout",
|
|
83878
84065
|
"unknown"
|
|
83879
84066
|
]);
|
|
83880
|
-
function markTurnResumed(db2,
|
|
84067
|
+
function markTurnResumed(db2, turnKey2, now = Date.now()) {
|
|
83881
84068
|
db2.prepare(`
|
|
83882
84069
|
UPDATE turns
|
|
83883
84070
|
SET resumed_at = ?,
|
|
83884
84071
|
updated_at = ?
|
|
83885
84072
|
WHERE turn_key = ? AND resumed_at IS NULL
|
|
83886
|
-
`).run(now, now,
|
|
84073
|
+
`).run(now, now, turnKey2);
|
|
83887
84074
|
}
|
|
83888
84075
|
function findLatestTurnIfInterrupted(db2) {
|
|
83889
84076
|
const row = db2.prepare(`
|
|
@@ -84414,6 +84601,7 @@ function applySubagentsSchema(db2) {
|
|
|
84414
84601
|
db2.exec("ALTER TABLE subagents ADD COLUMN model TEXT");
|
|
84415
84602
|
}
|
|
84416
84603
|
db2.exec("CREATE INDEX IF NOT EXISTS subagents_jsonl_id ON subagents(jsonl_agent_id)");
|
|
84604
|
+
db2.exec("CREATE INDEX IF NOT EXISTS subagents_parent_agent ON subagents(parent_agent_id)");
|
|
84417
84605
|
}
|
|
84418
84606
|
function mapSubagentRow(row) {
|
|
84419
84607
|
return {
|
|
@@ -85239,6 +85427,7 @@ var deferredDoneReactions = new DeferredDoneReactions({
|
|
|
85239
85427
|
purge: (key) => purgeReactionTracking(key)
|
|
85240
85428
|
});
|
|
85241
85429
|
var outboundDedup = new OutboundDedupCache;
|
|
85430
|
+
var flushedTurnSupersede = new FlushedTurnSupersedeRegistry;
|
|
85242
85431
|
var chatAvailableReactions = new Map;
|
|
85243
85432
|
var chatProbesInFlight = new Set;
|
|
85244
85433
|
var activeTurnStartedAt = new Map;
|
|
@@ -86920,10 +87109,10 @@ function summariseMarkerPayload(payload) {
|
|
|
86920
87109
|
return "payload=missing";
|
|
86921
87110
|
try {
|
|
86922
87111
|
const parsed = JSON.parse(payload);
|
|
86923
|
-
const
|
|
87112
|
+
const turnKey2 = typeof parsed.turnKey === "string" ? parsed.turnKey : "unknown";
|
|
86924
87113
|
const chatId = typeof parsed.chatId === "string" ? parsed.chatId : "unknown";
|
|
86925
87114
|
const startedAt = typeof parsed.startedAt === "number" ? parsed.startedAt : 0;
|
|
86926
|
-
return `turnKey=${
|
|
87115
|
+
return `turnKey=${turnKey2} chat=${chatId} started=${new Date(startedAt).toISOString()}`;
|
|
86927
87116
|
} catch {
|
|
86928
87117
|
return "payload=unparseable";
|
|
86929
87118
|
}
|
|
@@ -89853,6 +90042,18 @@ async function executeReply(args) {
|
|
|
89853
90042
|
return { content: [{ type: "text", text: "sent (deduped \u2014 same content sent via earlier path)" }] };
|
|
89854
90043
|
}
|
|
89855
90044
|
}
|
|
90045
|
+
{
|
|
90046
|
+
const replyThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
90047
|
+
const resolvedTurnId = turn?.turnId ?? findTurnByOriginId(args.origin_turn_id)?.turnId ?? null;
|
|
90048
|
+
const decision = flushedTurnSupersede.take(chat_id, replyThreadId, { liveTurnId: resolvedTurnId, now: Date.now() });
|
|
90049
|
+
if (decision.supersede) {
|
|
90050
|
+
process.stderr.write(`telegram gateway: reply: superseding flushed turn message(s) chatId=${chat_id} ids=${JSON.stringify(decision.deleteMessageIds)}
|
|
90051
|
+
`);
|
|
90052
|
+
for (const id of decision.deleteMessageIds) {
|
|
90053
|
+
await swallowingApiCall(() => lockedBot.api.deleteMessage(chat_id, id), { chat_id, verb: "reply.supersedeFlushed" });
|
|
90054
|
+
}
|
|
90055
|
+
}
|
|
90056
|
+
}
|
|
89856
90057
|
const files = args.files ?? [];
|
|
89857
90058
|
const quoteOptIn = args.quote !== false;
|
|
89858
90059
|
let reply_to = args.reply_to != null ? Number(args.reply_to) : undefined;
|
|
@@ -91465,8 +91666,8 @@ function resetOrphanedReplyTimeout() {
|
|
|
91465
91666
|
replyCalled: t.replyCalled,
|
|
91466
91667
|
progressCardActive: false
|
|
91467
91668
|
})) {
|
|
91468
|
-
const
|
|
91469
|
-
const working = isLegitimatelyWorking(
|
|
91669
|
+
const turnKey2 = statusKey(t.sessionChatId, t.sessionThreadId);
|
|
91670
|
+
const working = isLegitimatelyWorking(turnKey2);
|
|
91470
91671
|
const humanWaiting = (() => {
|
|
91471
91672
|
for (const entry of pendingAskUser.values()) {
|
|
91472
91673
|
if (entry.chatId === t.sessionChatId)
|
|
@@ -91484,7 +91685,7 @@ function resetOrphanedReplyTimeout() {
|
|
|
91484
91685
|
maxRearms: ORPHANED_REPLY_MAX_REARMS
|
|
91485
91686
|
});
|
|
91486
91687
|
if (decision.rearm) {
|
|
91487
|
-
process.stderr.write(`telegram gateway: orphaned-reply fuse expired \u2014 re-arming` + ` (rearm ${t.liveness.orphanedReplyRearmCount}/${ORPHANED_REPLY_MAX_REARMS}, in_flight=${toolFlightTracker.inFlightCount()}, human_wait=${humanWaiting}, recently_streaming=${recentlyStreaming}, bg_work=${hasPendingAsyncDispatch(
|
|
91688
|
+
process.stderr.write(`telegram gateway: orphaned-reply fuse expired \u2014 re-arming` + ` (rearm ${t.liveness.orphanedReplyRearmCount}/${ORPHANED_REPLY_MAX_REARMS}, in_flight=${toolFlightTracker.inFlightCount()}, human_wait=${humanWaiting}, recently_streaming=${recentlyStreaming}, bg_work=${hasPendingAsyncDispatch(turnKey2)})
|
|
91488
91689
|
`);
|
|
91489
91690
|
resetOrphanedReplyTimeout();
|
|
91490
91691
|
return;
|
|
@@ -92048,23 +92249,23 @@ function handleSessionEvent(ev) {
|
|
|
92048
92249
|
clearSilentEndState(statusKey(ev.chatId, ev.threadId != null ? Number(ev.threadId) : null));
|
|
92049
92250
|
if (turnsDb != null) {
|
|
92050
92251
|
const evThreadIdNum = ev.threadId != null ? Number(ev.threadId) : null;
|
|
92051
|
-
const
|
|
92052
|
-
next.registryKey =
|
|
92252
|
+
const turnKey2 = chatKeyWithSuffix2(ev.chatId, evThreadIdNum, String(startedAt));
|
|
92253
|
+
next.registryKey = turnKey2;
|
|
92053
92254
|
const userPromptPreview = extractUserPromptPreview(ev.rawContent);
|
|
92054
92255
|
try {
|
|
92055
92256
|
recordTurnStart(turnsDb, {
|
|
92056
|
-
turnKey,
|
|
92257
|
+
turnKey: turnKey2,
|
|
92057
92258
|
chatId: String(ev.chatId),
|
|
92058
92259
|
threadId: ev.threadId != null ? String(ev.threadId) : null,
|
|
92059
92260
|
lastUserMsgId: ev.messageId != null ? String(ev.messageId) : null,
|
|
92060
92261
|
userPromptPreview
|
|
92061
92262
|
});
|
|
92062
92263
|
} catch (err) {
|
|
92063
|
-
process.stderr.write(`telegram gateway: recordTurnStart failed turnKey=${
|
|
92264
|
+
process.stderr.write(`telegram gateway: recordTurnStart failed turnKey=${turnKey2}: ${err.message}
|
|
92064
92265
|
`);
|
|
92065
92266
|
}
|
|
92066
92267
|
writeTurnActiveMarker(STATE_DIR, {
|
|
92067
|
-
turnKey,
|
|
92268
|
+
turnKey: turnKey2,
|
|
92068
92269
|
chatId: String(ev.chatId),
|
|
92069
92270
|
threadId: ev.threadId != null ? String(ev.threadId) : null,
|
|
92070
92271
|
startedAt
|
|
@@ -92570,6 +92771,9 @@ function handleSessionEvent(ev) {
|
|
|
92570
92771
|
} catch {}
|
|
92571
92772
|
}
|
|
92572
92773
|
outboundDedup.record(backstopChatId, backstopThreadId, capturedText, Date.now(), currentTurn?.registryKey ?? null);
|
|
92774
|
+
if (sentIds.length > 0) {
|
|
92775
|
+
flushedTurnSupersede.record(backstopChatId, backstopThreadId, { turnId: turn.turnId, messageIds: sentIds, text: capturedText }, Date.now());
|
|
92776
|
+
}
|
|
92573
92777
|
if (backstopCtrl)
|
|
92574
92778
|
backstopCtrl.finalize("done");
|
|
92575
92779
|
if (backstopCardTurnKey != null) {
|
|
@@ -99333,7 +99537,7 @@ var didOneTimeSetup = false;
|
|
|
99333
99537
|
process.stderr.write(`telegram gateway: subagent-handback queued agent=${agentId} outcome=${outcome} chat=${decision.chatId} resultChars=${resultText.length}
|
|
99334
99538
|
`);
|
|
99335
99539
|
},
|
|
99336
|
-
onProgress: ({ agentId, description, latestSummary, elapsedMs, prevBucketIdx, setBucketIdx, lastTool, toolCount, progressLine, model }) => {
|
|
99540
|
+
onProgress: ({ agentId, description, latestSummary, elapsedMs, prevBucketIdx, setBucketIdx, lastTool, toolCount, progressLine, model, skeleton }) => {
|
|
99337
99541
|
let fleetChatId = "";
|
|
99338
99542
|
try {
|
|
99339
99543
|
const fleets = progressDriver?.peekAllFleets() ?? [];
|
|
@@ -99376,6 +99580,8 @@ var didOneTimeSetup = false;
|
|
|
99376
99580
|
}
|
|
99377
99581
|
if (surface !== "nest")
|
|
99378
99582
|
return;
|
|
99583
|
+
if (skeleton)
|
|
99584
|
+
return;
|
|
99379
99585
|
const turn = currentTurn;
|
|
99380
99586
|
if (turn == null)
|
|
99381
99587
|
return;
|
|
@@ -99435,6 +99641,7 @@ var didOneTimeSetup = false;
|
|
|
99435
99641
|
}
|
|
99436
99642
|
const progressOrigin = resolveSubagentOriginChat(agentId);
|
|
99437
99643
|
const decision = decideSubagentProgress({
|
|
99644
|
+
skeleton: skeleton === true,
|
|
99438
99645
|
disableEnvValue: process.env.SWITCHROOM_DISABLE_SUBAGENT_PROGRESS,
|
|
99439
99646
|
isBackground,
|
|
99440
99647
|
fleetChatId: progressOrigin?.chatId || fleetChatId,
|