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.
Files changed (25) hide show
  1. package/dist/cli/switchroom.js +24 -1
  2. package/dist/host-control/main.js +1 -1
  3. package/package.json +1 -1
  4. package/profiles/_shared/delegation-golden-rule.md.hbs +9 -0
  5. package/profiles/_shared/dev-protocol.md.hbs +2 -0
  6. package/profiles/_shared/execution-discipline.md.hbs +2 -2
  7. package/profiles/coding/CLAUDE.md.hbs +1 -1
  8. package/telegram-plugin/dist/gateway/gateway.js +268 -61
  9. package/telegram-plugin/flushed-turn-supersede.ts +230 -0
  10. package/telegram-plugin/gateway/gateway.ts +88 -1
  11. package/telegram-plugin/gateway/subagent-progress-inbound-builder.ts +17 -0
  12. package/telegram-plugin/registry/subagents-schema.ts +6 -0
  13. package/telegram-plugin/subagent-watcher.ts +86 -1
  14. package/telegram-plugin/tests/flushed-turn-supersede.test.ts +206 -0
  15. package/telegram-plugin/tests/nested-worker-visibility-harness.test.ts +20 -0
  16. package/telegram-plugin/tests/subagent-progress-inbound-builder.test.ts +30 -0
  17. package/telegram-plugin/tests/subagent-watcher-first-paint-independence.test.ts +171 -0
  18. package/telegram-plugin/tests/subagent-watcher-narrative-early-paint.test.ts +7 -5
  19. package/telegram-plugin/tests/subagent-watcher.test.ts +13 -12
  20. package/telegram-plugin/tests/turn-flush-safety.test.ts +71 -0
  21. package/telegram-plugin/tests/worker-activity-feed.test.ts +13 -8
  22. package/telegram-plugin/tests/worker-feed-coalesce.test.ts +196 -0
  23. package/telegram-plugin/tests/worker-feed-terminal-state-truthful.test.ts +40 -0
  24. package/telegram-plugin/turn-flush-safety.ts +74 -1
  25. 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
- if (g.workers.size === 0)
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.workers.size > 0 ? g.messageId : null;
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 settleTerminal = () => {
40607
- g.pendingFinalize = null;
40608
- if (opts2.finishingAgentId != null)
40609
- removeWorker(g, opts2.finishingAgentId);
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
- settleTerminal();
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
- settleTerminal();
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
- settleTerminal();
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=${opts2.finishingAgentId ?? "-"} ` + `state=${opts2.terminalRecap?.state ?? "done"} bytes=${body.length}`);
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
- settleTerminal();
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
- settleTerminal();
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
- settleTerminal();
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 (!row.finished && now - row.lastUpdateAt >= staleWorkerTtlMs) {
40753
- staleAgentIds.push(row.agentId);
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 != null && now >= g.cooldownUntil) {
40763
- const recap = g.pendingFinalize;
40764
- const finishingAgentId = [...g.workers.values()].find((w) => w.finished)?.agentId;
40765
- g.chain = g.chain.then(() => doRender(g, { force: true, terminalRecap: recap, finishingAgentId })).catch((err) => {
40766
- log(`worker-feed: heartbeat finalize re-drive error feed=${g.feedKey}: ${err.message}`);
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.workers.size > 0;
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: null
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(turnKey, deps) {
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 !== 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=${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: joined };
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: joined };
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, turnKey, activityMessageId, log = (l) => process.stderr.write(l)) {
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 !== turnKey || activityMessageId !== undefined && c.activityMessageId !== activityMessageId);
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.20";
82727
- var COMMIT_SHA = "f82e440f";
82728
- var COMMIT_DATE = "2026-07-13T22:44:46+10:00";
82729
- var LATEST_PR = 3230;
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, turnKey) {
83822
- const row = db2.prepare(`SELECT * FROM turns WHERE turn_key = ?`).get(turnKey);
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, turnKey, now = Date.now()) {
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, turnKey);
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 turnKey = typeof parsed.turnKey === "string" ? parsed.turnKey : "unknown";
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=${turnKey} chat=${chatId} started=${new Date(startedAt).toISOString()}`;
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 turnKey = statusKey(t.sessionChatId, t.sessionThreadId);
91469
- const working = isLegitimatelyWorking(turnKey);
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(turnKey)})
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 turnKey = chatKeyWithSuffix2(ev.chatId, evThreadIdNum, String(startedAt));
92052
- next.registryKey = turnKey;
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=${turnKey}: ${err.message}
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,