switchroom 0.20.3 → 0.20.5

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 (28) hide show
  1. package/dist/cli/switchroom.js +10256 -1233
  2. package/dist/host-control/main.js +1 -1
  3. package/package.json +3 -3
  4. package/skills/switchroom-release/SKILL.md +3 -2
  5. package/telegram-plugin/dist/gateway/gateway.js +452 -99
  6. package/telegram-plugin/edit-flood-fuse.ts +332 -14
  7. package/telegram-plugin/gateway/feed-open-gate.ts +28 -8
  8. package/telegram-plugin/gateway/feed-reopen-gate.ts +88 -6
  9. package/telegram-plugin/gateway/gateway.ts +130 -104
  10. package/telegram-plugin/gateway/narrative-lane.ts +19 -0
  11. package/telegram-plugin/gateway/progress-fallback-cap.ts +195 -0
  12. package/telegram-plugin/gateway/stream-render.ts +67 -12
  13. package/telegram-plugin/gateway/subagent-handback-inbound-builder.ts +13 -0
  14. package/telegram-plugin/gateway/subagent-origin-surface.ts +181 -0
  15. package/telegram-plugin/gateway/subagent-progress-inbound-builder.ts +7 -0
  16. package/telegram-plugin/registry/subagents-schema.ts +61 -0
  17. package/telegram-plugin/registry/turns-schema.ts +26 -0
  18. package/telegram-plugin/silence-poke.ts +80 -0
  19. package/telegram-plugin/tests/edit-flood-fuse-cosmetic-fairness.test.ts +229 -0
  20. package/telegram-plugin/tests/feed-open-gate.test.ts +42 -0
  21. package/telegram-plugin/tests/feed-reopen-gate.test.ts +114 -0
  22. package/telegram-plugin/tests/progress-cap.test.ts +182 -0
  23. package/telegram-plugin/tests/progress-fallback-cap.test.ts +91 -0
  24. package/telegram-plugin/tests/progress-update.test.ts +108 -12
  25. package/telegram-plugin/tests/silence-poke-card-render.test.ts +300 -0
  26. package/telegram-plugin/tests/subagent-handback-inbound-builder.test.ts +46 -0
  27. package/telegram-plugin/tests/subagent-progress-inbound-builder.test.ts +26 -0
  28. package/telegram-plugin/tests/worker-origin-gap-dispatch.test.ts +304 -0
@@ -65060,6 +65060,9 @@ var EDIT_FLOOD_FUSE_DEFAULTS = {
65060
65060
  perChatSendMaxPerWindow: 25,
65061
65061
  cosmeticPerMessageMaxPerWindow: 4,
65062
65062
  cosmeticPerChatMaxPerWindow: 6,
65063
+ cosmeticFloorPerWindow: 2,
65064
+ cosmeticFloorSlots: 2,
65065
+ throttleNoticeMs: 45000,
65063
65066
  perChatTotalMaxPerWindow: 20,
65064
65067
  perChatReplyReserve: 8,
65065
65068
  perChatCriticalMinPerWindow: 3,
@@ -65094,6 +65097,11 @@ function editFloodFuseConfigFromEnv(env) {
65094
65097
  };
65095
65098
  assign("cosmeticPerMessageMaxPerWindow", envInt(env.SWITCHROOM_FEED_EDIT_MAX_PER_MSG_PER_MIN));
65096
65099
  assign("cosmeticPerChatMaxPerWindow", envInt(env.SWITCHROOM_FEED_EDIT_MAX_PER_CHAT_PER_MIN));
65100
+ if (env.SWITCHROOM_FEED_FAIR_SHARE === "0")
65101
+ cfg.cosmeticFairShareEnabled = false;
65102
+ assign("cosmeticFloorPerWindow", envInt(env.SWITCHROOM_FEED_EDIT_FLOOR_PER_MSG_PER_MIN));
65103
+ assign("cosmeticFloorSlots", envInt(env.SWITCHROOM_FEED_EDIT_FLOOR_SLOTS));
65104
+ assign("throttleNoticeMs", envInt(env.SWITCHROOM_FEED_THROTTLE_NOTICE_MS));
65097
65105
  assign("perChatTotalMaxPerWindow", envInt(env.SWITCHROOM_CHAT_TOTAL_MAX_PER_MIN));
65098
65106
  assign("perChatReplyReserve", envInt(env.SWITCHROOM_CHAT_REPLY_RESERVE));
65099
65107
  assign("perChatCriticalMinPerWindow", envInt(env.SWITCHROOM_CHAT_CRITICAL_MIN_PER_MIN));
@@ -65118,6 +65126,11 @@ function createEditFloodFuse(config = {}) {
65118
65126
  const perChatSendMax = config.perChatSendMaxPerWindow ?? D.perChatSendMaxPerWindow;
65119
65127
  const cosmeticPerMessageMax = Math.min(perMessageMax, config.cosmeticPerMessageMaxPerWindow ?? D.cosmeticPerMessageMaxPerWindow);
65120
65128
  const cosmeticPerChatMax = Math.min(perChatEditMax, config.cosmeticPerChatMaxPerWindow ?? D.cosmeticPerChatMaxPerWindow);
65129
+ const cosmeticFairShareEnabled = config.cosmeticFairShareEnabled ?? true;
65130
+ const cosmeticFloorPerWindow = Math.min(cosmeticPerMessageMax, Math.max(0, config.cosmeticFloorPerWindow ?? D.cosmeticFloorPerWindow));
65131
+ const cosmeticFloorAggMax = Math.min(cosmeticPerChatMax, cosmeticFloorPerWindow * Math.max(0, config.cosmeticFloorSlots ?? D.cosmeticFloorSlots));
65132
+ const cosmeticRemainderMax = Math.max(0, cosmeticPerChatMax - cosmeticFloorAggMax);
65133
+ const throttleNoticeMs = Math.max(0, config.throttleNoticeMs ?? D.throttleNoticeMs);
65121
65134
  const perChatTotalMax = config.perChatTotalMaxPerWindow ?? D.perChatTotalMaxPerWindow;
65122
65135
  const perChatWindowMs = config.perChatWindowMs ?? D.perChatWindowMs;
65123
65136
  const perChatReplyReserve = Math.min(Math.max(0, config.perChatReplyReserve ?? D.perChatReplyReserve), Math.max(0, perChatTotalMax - 1));
@@ -65142,7 +65155,8 @@ function createEditFloodFuse(config = {}) {
65142
65155
  superseded: 0,
65143
65156
  floodObserved: 0,
65144
65157
  meteredByDefault: 0,
65145
- chatless: 0
65158
+ chatless: 0,
65159
+ throttled: 0
65146
65160
  };
65147
65161
  let tightenLevel = 0;
65148
65162
  let tightenedUntil = 0;
@@ -65189,6 +65203,12 @@ function createEditFloodFuse(config = {}) {
65189
65203
  return eff;
65190
65204
  return Math.max(eff, Math.min(base, perChatCriticalMin));
65191
65205
  }
65206
+ function cosmeticMessageCeiling(now) {
65207
+ const eff = ceiling(cosmeticPerMessageMax, now);
65208
+ if (!cosmeticFairShareEnabled)
65209
+ return eff;
65210
+ return Math.max(eff, Math.min(cosmeticPerMessageMax, cosmeticFloorPerWindow));
65211
+ }
65192
65212
  function cosmeticTotalMax(now) {
65193
65213
  const eff = ceiling(perChatTotalMax, now);
65194
65214
  if (replyReserveFraction <= 0)
@@ -65281,9 +65301,87 @@ function createEditFloodFuse(config = {}) {
65281
65301
  if (i >= 0)
65282
65302
  w.ts.splice(i, 1);
65283
65303
  }
65304
+ function makeThrottleNotice(method, key, cls) {
65305
+ if (!cosmeticFairShareEnabled || cls !== "cosmetic" || throttleNoticeMs <= 0) {
65306
+ return { tick: () => {}, sleepCap: () => Number.POSITIVE_INFINITY };
65307
+ }
65308
+ let firstAt = Number.NaN;
65309
+ let fired = false;
65310
+ return {
65311
+ tick: (now) => {
65312
+ if (Number.isNaN(firstAt))
65313
+ firstAt = now;
65314
+ if (fired || now - firstAt < throttleNoticeMs)
65315
+ return;
65316
+ fired = true;
65317
+ counters.throttled++;
65318
+ onTrip?.({ method, key, action: "throttled", cls });
65319
+ },
65320
+ sleepCap: (now) => {
65321
+ if (fired || Number.isNaN(firstAt))
65322
+ return Number.POSITIVE_INFINITY;
65323
+ return Math.max(1, firstAt + throttleNoticeMs - now);
65324
+ }
65325
+ };
65326
+ }
65327
+ async function awaitCosmeticChatFair(chat, msg, method, cls, dropGuard, deadline, lateReleaseKey) {
65328
+ const fMsgKey = `cmf:${chat}:${msg}`;
65329
+ const fAggKey = `cfa:${chat}`;
65330
+ const remKey = `cer:${chat}`;
65331
+ const fw = win(fMsgKey);
65332
+ const aw = win(fAggKey);
65333
+ const rw = win(remKey);
65334
+ let counted = false;
65335
+ const throttle = makeThrottleNotice(method, remKey, cls);
65336
+ for (;; ) {
65337
+ const now = clock.now();
65338
+ const wFloor = Math.max(waitFor(fw, now, perChatWindowMs, cosmeticFloorPerWindow), waitFor(aw, now, perChatWindowMs, cosmeticFloorAggMax));
65339
+ const remCap = cosmeticRemainderMax <= 0 ? 0 : ceiling(cosmeticRemainderMax, now);
65340
+ const wRem = waitFor(rw, now, perChatWindowMs, remCap);
65341
+ if (wFloor === 0) {
65342
+ fw.ts.push(now);
65343
+ aw.ts.push(now);
65344
+ return [[fMsgKey, now], [fAggKey, now]];
65345
+ }
65346
+ if (remCap > 0 && wRem === 0) {
65347
+ rw.ts.push(now);
65348
+ return [[remKey, now]];
65349
+ }
65350
+ if (now >= deadline) {
65351
+ if (dropGuard()) {
65352
+ counters.dropped++;
65353
+ onTrip?.({ method, key: remKey, action: "dropped", cls });
65354
+ return null;
65355
+ }
65356
+ const lw = win(lateReleaseKey);
65357
+ prune(lw, now, perChatWindowMs);
65358
+ if (lw.ts.length >= ceiling(lateReleaseMax, now)) {
65359
+ counters.dropped++;
65360
+ onTrip?.({ method, key: remKey, action: "dropped", cls });
65361
+ return null;
65362
+ }
65363
+ lw.ts.push(now);
65364
+ if (cosmeticRemainderMax > 0) {
65365
+ rw.ts.push(now);
65366
+ return [[remKey, now]];
65367
+ }
65368
+ fw.ts.push(now);
65369
+ aw.ts.push(now);
65370
+ return [[fMsgKey, now], [fAggKey, now]];
65371
+ }
65372
+ if (!counted) {
65373
+ counters.deferred++;
65374
+ counted = true;
65375
+ onTrip?.({ method, key: remKey, action: "deferred", cls });
65376
+ }
65377
+ throttle.tick(now);
65378
+ await clock.sleep(Math.min(Math.min(wFloor, wRem), deadline - now, throttle.sleepCap(now)));
65379
+ }
65380
+ }
65284
65381
  async function awaitRoom(key, windowMs, maxFor, method, mode, cls, dropGuard, deadline, lateReleaseKey) {
65285
65382
  const w = win(key);
65286
65383
  let counted = false;
65384
+ const throttle = makeThrottleNotice(method, key, cls);
65287
65385
  for (;; ) {
65288
65386
  const now = clock.now();
65289
65387
  const wait = waitFor(w, now, windowMs, maxFor(now));
@@ -65315,6 +65413,8 @@ function createEditFloodFuse(config = {}) {
65315
65413
  counted = true;
65316
65414
  onTrip?.({ method, key, action: "deferred", cls });
65317
65415
  }
65416
+ throttle.tick(now);
65417
+ const nap = Math.min(wait, deadline - now, throttle.sleepCap(now));
65318
65418
  let killed = false;
65319
65419
  if (mode === "supersede") {
65320
65420
  w.waiter?.kill();
@@ -65324,7 +65424,7 @@ function createEditFloodFuse(config = {}) {
65324
65424
  resolve6();
65325
65425
  } };
65326
65426
  });
65327
- await Promise.race([clock.sleep(Math.min(wait, deadline - now)), superseded]);
65427
+ await Promise.race([clock.sleep(nap), superseded]);
65328
65428
  if (killed) {
65329
65429
  counters.superseded++;
65330
65430
  onTrip?.({ method, key, action: "superseded", cls });
@@ -65332,7 +65432,7 @@ function createEditFloodFuse(config = {}) {
65332
65432
  }
65333
65433
  w.waiter = null;
65334
65434
  } else {
65335
- await clock.sleep(Math.min(wait, deadline - now));
65435
+ await clock.sleep(nap);
65336
65436
  }
65337
65437
  }
65338
65438
  }
@@ -65368,7 +65468,7 @@ function createEditFloodFuse(config = {}) {
65368
65468
  const dropGuard = () => mw.inflight > 1;
65369
65469
  const lateKey = cls === "cosmetic" ? `lr:${chat}` : undefined;
65370
65470
  try {
65371
- const msgSlot = await awaitRoom(msgKey, perMessageWindowMs, cls === "cosmetic" ? (t) => ceiling(cosmeticPerMessageMax, t) : (t) => classCeiling(perMessageMax, cls, t), method, "supersede", cls, dropGuard, deadline, lateKey);
65471
+ const msgSlot = await awaitRoom(msgKey, perMessageWindowMs, cls === "cosmetic" ? (t) => cosmeticMessageCeiling(t) : (t) => classCeiling(perMessageMax, cls, t), method, "supersede", cls, dropGuard, deadline, lateKey);
65372
65472
  if (msgSlot === null)
65373
65473
  return DROPPED_RESULT;
65374
65474
  const reserved = [[msgKey, msgSlot]];
@@ -65376,13 +65476,22 @@ function createEditFloodFuse(config = {}) {
65376
65476
  for (const [k, at] of reserved)
65377
65477
  unreserve(k, at);
65378
65478
  };
65379
- const chatKey2 = `ce:${chat}`;
65380
- const chatSlot = await awaitRoom(chatKey2, perChatWindowMs, cls === "cosmetic" ? (t) => ceiling(cosmeticPerChatMax, t) : (t) => classCeiling(perChatEditMax, cls, t), method, "drop", cls, dropGuard, deadline, lateKey);
65381
- if (chatSlot === null) {
65382
- giveBack();
65383
- return DROPPED_RESULT;
65479
+ if (cls === "cosmetic" && cosmeticFairShareEnabled) {
65480
+ const fairSlots = await awaitCosmeticChatFair(chat, msg, method, cls, dropGuard, deadline, lateKey);
65481
+ if (fairSlots === null) {
65482
+ giveBack();
65483
+ return DROPPED_RESULT;
65484
+ }
65485
+ reserved.push(...fairSlots);
65486
+ } else {
65487
+ const chatKey2 = `ce:${chat}`;
65488
+ const chatSlot = await awaitRoom(chatKey2, perChatWindowMs, cls === "cosmetic" ? (t) => ceiling(cosmeticPerChatMax, t) : (t) => classCeiling(perChatEditMax, cls, t), method, "drop", cls, dropGuard, deadline, lateKey);
65489
+ if (chatSlot === null) {
65490
+ giveBack();
65491
+ return DROPPED_RESULT;
65492
+ }
65493
+ reserved.push([chatKey2, chatSlot]);
65384
65494
  }
65385
- reserved.push([chatKey2, chatSlot]);
65386
65495
  const totalSlot = await awaitRoom(totalKey, perChatWindowMs, chatTotalMax, method, cls === "cosmetic" ? "drop" : "release", cls, dropGuard, deadline, lateKey);
65387
65496
  if (totalSlot === null) {
65388
65497
  giveBack();
@@ -65431,6 +65540,8 @@ function createEditFloodFuse(config = {}) {
65431
65540
  deferred: counters.deferred,
65432
65541
  dropped: counters.dropped,
65433
65542
  superseded: counters.superseded,
65543
+ throttled: counters.throttled,
65544
+ cosmeticFairShareEnabled,
65434
65545
  floodObserved: counters.floodObserved,
65435
65546
  tightened: isTightened(now),
65436
65547
  tightenLevel: levelAt(now),
@@ -71444,6 +71555,7 @@ __export(exports_silence_poke, {
71444
71555
  noteThinking: () => noteThinking,
71445
71556
  noteProduction: () => noteProduction,
71446
71557
  noteOutbound: () => noteOutbound2,
71558
+ noteCardRender: () => noteCardRender,
71447
71559
  noteBackgroundShellDead: () => noteBackgroundShellDead,
71448
71560
  noteBackgroundShellAlive: () => noteBackgroundShellAlive,
71449
71561
  longestInFlightTool: () => longestInFlightTool,
@@ -71456,7 +71568,8 @@ __export(exports_silence_poke, {
71456
71568
  __getStateForTests: () => __getStateForTests,
71457
71569
  __bgMarkerParserConfirmedForTests: () => __bgMarkerParserConfirmedForTests,
71458
71570
  DEFAULT_THRESHOLDS: () => DEFAULT_THRESHOLDS,
71459
- DEFAULT_POLL_INTERVAL_MS: () => DEFAULT_POLL_INTERVAL_MS
71571
+ DEFAULT_POLL_INTERVAL_MS: () => DEFAULT_POLL_INTERVAL_MS,
71572
+ CARD_RENDER_FRESH_MS: () => CARD_RENDER_FRESH_MS
71460
71573
  });
71461
71574
 
71462
71575
  // turn-liveness-floor.ts
@@ -71522,6 +71635,7 @@ var DEFAULT_THRESHOLDS = {
71522
71635
  fallback: 300000
71523
71636
  };
71524
71637
  var DEFAULT_POLL_INTERVAL_MS = 5000;
71638
+ var CARD_RENDER_FRESH_MS = 30000;
71525
71639
  var state2 = new Map;
71526
71640
  var timer = null;
71527
71641
  var activeDeps = null;
@@ -71540,6 +71654,7 @@ function startTurn(key, now) {
71540
71654
  fallbackFired: false,
71541
71655
  floorFired: false,
71542
71656
  inFlightTools: new Map,
71657
+ lastCardRenderAt: null,
71543
71658
  sawBashThisTurn: false,
71544
71659
  aliveShells: new Set
71545
71660
  });
@@ -71571,6 +71686,12 @@ function noteProduction(key, now) {
71571
71686
  s.lastOutboundAt = now;
71572
71687
  s.fallbackFired = false;
71573
71688
  }
71689
+ function noteCardRender(key, now) {
71690
+ const s = state2.get(key);
71691
+ if (s == null)
71692
+ return;
71693
+ s.lastCardRenderAt = now;
71694
+ }
71574
71695
  function noteThinking(key, now) {
71575
71696
  const s = state2.get(key);
71576
71697
  if (s == null)
@@ -71717,6 +71838,8 @@ function tick(now) {
71717
71838
  if (underCeiling) {
71718
71839
  if (activeDeps.isCompactionInFlight?.(key) === true)
71719
71840
  continue;
71841
+ if (s.lastCardRenderAt != null && now - s.lastCardRenderAt < CARD_RENDER_FRESH_MS)
71842
+ continue;
71720
71843
  const forceDisable = process.env.SWITCHROOM_SILENCE_DEFER_INFLIGHT_TOOLS === "0";
71721
71844
  if (!forceDisable && activeDeps.isLegitimatelyWorking != null) {
71722
71845
  if (activeDeps.isLegitimatelyWorking(key))
@@ -78753,6 +78876,28 @@ init_rich_send();
78753
78876
  init_format();
78754
78877
 
78755
78878
  // registry/subagents-schema.ts
78879
+ function mapSubagentRow(row) {
78880
+ return {
78881
+ id: row.id,
78882
+ parent_session_id: row.parent_session_id,
78883
+ parent_turn_key: row.parent_turn_key,
78884
+ agent_type: row.agent_type,
78885
+ description: row.description,
78886
+ background: row.background !== 0,
78887
+ started_at: row.started_at,
78888
+ last_activity_at: row.last_activity_at,
78889
+ ended_at: row.ended_at,
78890
+ status: row.status,
78891
+ result_summary: row.result_summary,
78892
+ jsonl_agent_id: row.jsonl_agent_id,
78893
+ parent_agent_id: row.parent_agent_id ?? null,
78894
+ model: row.model ?? null
78895
+ };
78896
+ }
78897
+ function getSubagentByJsonlId(db2, jsonlAgentId) {
78898
+ const row = db2.prepare("SELECT * FROM subagents WHERE jsonl_agent_id = ?").get(jsonlAgentId);
78899
+ return row ? mapSubagentRow(row) : null;
78900
+ }
78756
78901
  function countRunningBackgroundSubagents(db2) {
78757
78902
  const row = db2.prepare("SELECT count(*) AS n FROM subagents WHERE background = 1 AND status = 'running'").get();
78758
78903
  return row?.n ?? 0;
@@ -78847,6 +78992,19 @@ function recordNestedSubagentDispatch(db2, args) {
78847
78992
  WHERE id = ?
78848
78993
  `).run(args.parentJsonlAgentId, args.parentJsonlAgentId, args.toolUseId);
78849
78994
  }
78995
+ function stampSubagentDispatchTurn(db2, args) {
78996
+ db2.prepare(`
78997
+ INSERT OR IGNORE INTO subagents
78998
+ (id, parent_session_id, parent_turn_key, agent_type, description,
78999
+ background, started_at, last_activity_at, status)
79000
+ VALUES (?, NULL, ?, ?, ?, ?, ?, ?, 'running')
79001
+ `).run(args.toolUseId, args.parentTurnKey, args.agentType ?? null, args.description ?? null, args.background ? 1 : 0, args.now, args.now);
79002
+ db2.prepare(`
79003
+ UPDATE subagents
79004
+ SET parent_turn_key = COALESCE(parent_turn_key, ?)
79005
+ WHERE id = ?
79006
+ `).run(args.parentTurnKey, args.toolUseId);
79007
+ }
78850
79008
  function resolveSubagentOriginTurnKey(db2, jsonlAgentId, maxHops = 5) {
78851
79009
  const seen = new Set;
78852
79010
  let currentJsonlId = jsonlAgentId;
@@ -81177,6 +81335,28 @@ function isDraftOfReply(textBlock, replyText) {
81177
81335
  }
81178
81336
 
81179
81337
  // registry/turns-schema.ts
81338
+ function mapRow(row) {
81339
+ return {
81340
+ turn_key: row.turn_key,
81341
+ chat_id: row.chat_id,
81342
+ thread_id: row.thread_id,
81343
+ started_at: row.started_at,
81344
+ ended_at: row.ended_at,
81345
+ ended_via: row.ended_via ?? null,
81346
+ last_assistant_msg_id: row.last_assistant_msg_id,
81347
+ last_assistant_done: row.last_assistant_done === null ? null : row.last_assistant_done !== 0,
81348
+ last_user_msg_id: row.last_user_msg_id,
81349
+ user_prompt_preview: row.user_prompt_preview,
81350
+ assistant_reply_preview: row.assistant_reply_preview,
81351
+ tool_call_count: row.tool_call_count,
81352
+ interrupt_reason: row.interrupt_reason,
81353
+ resumed_at: row.resumed_at,
81354
+ session_id: row.session_id ?? null,
81355
+ answer_redelivered_at: row.answer_redelivered_at ?? null,
81356
+ created_at: row.created_at,
81357
+ updated_at: row.updated_at
81358
+ };
81359
+ }
81180
81360
  function recordTurnStart(db3, args) {
81181
81361
  const now = Date.now();
81182
81362
  db3.prepare(`
@@ -81201,6 +81381,19 @@ function recordTurnEnd(db3, args) {
81201
81381
  WHERE turn_key = ?
81202
81382
  `).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);
81203
81383
  }
81384
+ function getTurnByKey(db3, turnKey3) {
81385
+ const row = db3.prepare(`SELECT * FROM turns WHERE turn_key = ?`).get(turnKey3);
81386
+ return row ? mapRow(row) : null;
81387
+ }
81388
+ function findMostRecentTurn(db3, beforeOrAtMs) {
81389
+ const row = db3.prepare(`
81390
+ SELECT * FROM turns
81391
+ WHERE started_at <= ?
81392
+ ORDER BY started_at DESC
81393
+ LIMIT 1
81394
+ `).get(beforeOrAtMs);
81395
+ return row ? mapRow(row) : null;
81396
+ }
81204
81397
  var INTERRUPTED_VIA = new Set([
81205
81398
  "restart",
81206
81399
  "reaped_stale",
@@ -81316,7 +81509,7 @@ function mayOpenActivityCard(input) {
81316
81509
  if (input.crossTurnAnswerDelivered)
81317
81510
  return false;
81318
81511
  if (input.finalAnswerEverDelivered) {
81319
- if (input.postAnswerSubagentActivity && input.producer === "tool")
81512
+ if ((input.postAnswerSubagentActivity || input.postAnswerMainActivity) && input.producer === "tool")
81320
81513
  return true;
81321
81514
  return false;
81322
81515
  }
@@ -81429,17 +81622,24 @@ class EmissionAuthority {
81429
81622
  }
81430
81623
 
81431
81624
  // gateway/feed-reopen-gate.ts
81625
+ var SUBSTANTIVE_REOPEN_MIN_LABELS = 2;
81432
81626
  function shouldReopenFeedAfterAck(input) {
81433
81627
  if (!input.finalAnswerDelivered)
81434
81628
  return false;
81435
- if (input.finalAnswerSubstantive)
81436
- return false;
81629
+ if (input.finalAnswerSubstantive) {
81630
+ if (input.reopenAfterSubstantiveEnabled !== true)
81631
+ return false;
81632
+ return (input.postSubstantiveToolLabelCount ?? 0) >= SUBSTANTIVE_REOPEN_MIN_LABELS;
81633
+ }
81437
81634
  return input.enabled === true;
81438
81635
  }
81439
81636
  function decideFeedReopen(input) {
81440
81637
  if (!shouldReopenFeedAfterAck(input)) {
81441
81638
  return { dropLabel: true };
81442
81639
  }
81640
+ if (input.finalAnswerSubstantive) {
81641
+ return { dropLabel: false, liftLeverOne: true };
81642
+ }
81443
81643
  return {
81444
81644
  dropLabel: false,
81445
81645
  reset: {
@@ -81950,6 +82150,12 @@ function noteProduction2(key, now) {
81950
82150
  s.lastOutboundAt = now;
81951
82151
  s.fallbackFired = false;
81952
82152
  }
82153
+ function noteCardRender2(key, now) {
82154
+ const s = state5.get(key);
82155
+ if (s == null)
82156
+ return;
82157
+ s.lastCardRenderAt = now;
82158
+ }
81953
82159
  function endTurn2(key) {
81954
82160
  state5.delete(key);
81955
82161
  }
@@ -82165,6 +82371,8 @@ function beginTurn(deps, ev) {
82165
82371
  replyCalled: false,
82166
82372
  finalAnswerDelivered: false,
82167
82373
  finalAnswerSubstantive: false,
82374
+ postSubstantiveToolLabelCount: 0,
82375
+ postAnswerMainActivity: false,
82168
82376
  finalAnswerEverDelivered: false,
82169
82377
  answerDelivered: false,
82170
82378
  flushedAnswerText: null,
@@ -82278,6 +82486,7 @@ function handleSessionEvent(deps, ev) {
82278
82486
  CONTEXT_EXHAUSTION_COOLDOWN_MS,
82279
82487
  DELIVERY_CONFIRM_ENABLED,
82280
82488
  FEED_REOPEN_AFTER_ACK_ENABLED,
82489
+ FEED_REOPEN_AFTER_SUBSTANTIVE_ENABLED,
82281
82490
  HANDBACK_PRETURN_ENABLED,
82282
82491
  HISTORY_ENABLED,
82283
82492
  LIVENESS_TERMINAL_HONESTY,
@@ -82472,6 +82681,21 @@ function handleSessionEvent(deps, ev) {
82472
82681
  resolvePendingNarrativeOnTool(turn, ev.toolName, ev.input);
82473
82682
  turn.toolCallCount++;
82474
82683
  touchTurnActiveMarker(STATE_DIR);
82684
+ if ((ev.toolName === "Agent" || ev.toolName === "Task") && ev.toolUseId != null && ev.toolUseId.length > 0 && turn.registryKey != null && turnsDb != null) {
82685
+ try {
82686
+ stampSubagentDispatchTurn(turnsDb, {
82687
+ toolUseId: ev.toolUseId,
82688
+ parentTurnKey: turn.registryKey,
82689
+ agentType: typeof ev.input?.subagent_type === "string" ? ev.input.subagent_type : null,
82690
+ description: typeof ev.input?.description === "string" ? ev.input.description : null,
82691
+ background: ev.input?.run_in_background === true,
82692
+ now: Date.now()
82693
+ });
82694
+ } catch (err) {
82695
+ process.stderr.write(`telegram gateway: dispatch-time parent_turn_key stamp failed toolUseId=${ev.toolUseId}: ${err.message}
82696
+ `);
82697
+ }
82698
+ }
82475
82699
  preambleSuppressor.onTool({ isReplyTool: isTelegramSurfaceTool(ev.toolName) });
82476
82700
  surfaceMemoryLegibility(turn, ev.toolName, ev.toolUseId, ev.input);
82477
82701
  const ctrl = activeStatusReactions.get(statusKey(turn.sessionChatId, turn.sessionThreadId));
@@ -82510,16 +82734,24 @@ function handleSessionEvent(deps, ev) {
82510
82734
  if (isTelegramSurfaceTool(ev.toolName))
82511
82735
  return;
82512
82736
  if (turn.finalAnswerDelivered) {
82737
+ if (turn.finalAnswerSubstantive)
82738
+ turn.postSubstantiveToolLabelCount++;
82513
82739
  const reopen = decideFeedReopen({
82514
82740
  finalAnswerDelivered: turn.finalAnswerDelivered,
82515
82741
  finalAnswerSubstantive: turn.finalAnswerSubstantive,
82516
- enabled: FEED_REOPEN_AFTER_ACK_ENABLED
82742
+ enabled: FEED_REOPEN_AFTER_ACK_ENABLED,
82743
+ reopenAfterSubstantiveEnabled: FEED_REOPEN_AFTER_SUBSTANTIVE_ENABLED,
82744
+ postSubstantiveToolLabelCount: turn.postSubstantiveToolLabelCount
82517
82745
  });
82518
82746
  if (reopen.dropLabel)
82519
82747
  return;
82520
- turn.finalAnswerDelivered = reopen.reset.finalAnswerDelivered;
82521
- turn.activityMessageId = reopen.reset.activityMessageId;
82522
- turn.activityLastSentRender = reopen.reset.activityLastSentRender;
82748
+ if (reopen.reset != null) {
82749
+ turn.finalAnswerDelivered = reopen.reset.finalAnswerDelivered;
82750
+ turn.activityMessageId = reopen.reset.activityMessageId;
82751
+ turn.activityLastSentRender = reopen.reset.activityLastSentRender;
82752
+ }
82753
+ if (reopen.liftLeverOne)
82754
+ turn.postAnswerMainActivity = true;
82523
82755
  }
82524
82756
  const rendered = appendActivityLabel(turn.mirrorLines, ev.label);
82525
82757
  if (rendered != null) {
@@ -83450,7 +83682,8 @@ function createNarrativeLane(deps) {
83450
83682
  finalAnswerEverDelivered: turn.finalAnswerEverDelivered,
83451
83683
  labeledToolCount: turn.labeledToolCount,
83452
83684
  crossTurnAnswerDelivered,
83453
- postAnswerSubagentActivity: openFlags?.postAnswerSubagentActivity
83685
+ postAnswerSubagentActivity: openFlags?.postAnswerSubagentActivity,
83686
+ postAnswerMainActivity: turn.postAnswerMainActivity
83454
83687
  })) {
83455
83688
  break;
83456
83689
  }
@@ -83491,6 +83724,7 @@ function createNarrativeLane(deps) {
83491
83724
  if (isSendGateShed(editRes))
83492
83725
  break;
83493
83726
  }
83727
+ noteCardRender2(statusKey(chat, thread), Date.now());
83494
83728
  turn.activityLastSentRender = target;
83495
83729
  } catch (err) {
83496
83730
  const msg = err instanceof Error ? err.message : String(err);
@@ -94966,6 +95200,77 @@ function createTurnStartSurfaces(deps) {
94966
95200
  return { armTurnStartSurfaces };
94967
95201
  }
94968
95202
 
95203
+ // gateway/progress-fallback-cap.ts
95204
+ var recentSends = new Map;
95205
+ var PROGRESS_FALLBACK_WINDOW_MS = 15 * 60000;
95206
+ var PROGRESS_FALLBACK_MAX = 5;
95207
+ function prune(key, now) {
95208
+ const cutoff = now - PROGRESS_FALLBACK_WINDOW_MS;
95209
+ const recent = (recentSends.get(key) ?? []).filter((ts) => ts > cutoff);
95210
+ if (recent.length === 0)
95211
+ recentSends.delete(key);
95212
+ else
95213
+ recentSends.set(key, recent);
95214
+ return recent;
95215
+ }
95216
+ function reserveProgressFallbackSlot(key, now) {
95217
+ const recent = prune(key, now);
95218
+ if (recent.length >= PROGRESS_FALLBACK_MAX)
95219
+ return null;
95220
+ recent.push(now);
95221
+ recentSends.set(key, recent);
95222
+ let released = false;
95223
+ return {
95224
+ release: () => {
95225
+ if (released)
95226
+ return;
95227
+ released = true;
95228
+ const arr = recentSends.get(key);
95229
+ if (!arr)
95230
+ return;
95231
+ const idx = arr.indexOf(now);
95232
+ if (idx >= 0)
95233
+ arr.splice(idx, 1);
95234
+ if (arr.length === 0)
95235
+ recentSends.delete(key);
95236
+ else
95237
+ recentSends.set(key, arr);
95238
+ }
95239
+ };
95240
+ }
95241
+ var PROGRESS_TURN_MAX = 5;
95242
+ function reserveProgressSlot(deps) {
95243
+ const { key, now, turnStart, turnCount } = deps;
95244
+ if (turnStart != null) {
95245
+ const current = turnCount.get(key) ?? 0;
95246
+ if (current >= PROGRESS_TURN_MAX)
95247
+ return null;
95248
+ turnCount.set(key, current + 1);
95249
+ let released = false;
95250
+ return {
95251
+ release: () => {
95252
+ if (released)
95253
+ return;
95254
+ released = true;
95255
+ turnCount.set(key, Math.max(0, (turnCount.get(key) ?? 0) - 1));
95256
+ }
95257
+ };
95258
+ }
95259
+ return reserveProgressFallbackSlot(key, now);
95260
+ }
95261
+ async function sendWithProgressCap(deps, send) {
95262
+ const reservation = reserveProgressSlot(deps);
95263
+ if (reservation === null)
95264
+ return { capped: true };
95265
+ try {
95266
+ const result = await send();
95267
+ return { capped: false, result };
95268
+ } catch (err) {
95269
+ reservation.release();
95270
+ throw err;
95271
+ }
95272
+ }
95273
+
94969
95274
  // gateway/delivery-confirm-wiring.ts
94970
95275
  function createDeliveryConfirmWiring(deps) {
94971
95276
  const {
@@ -96432,6 +96737,7 @@ ${result}
96432
96737
  meta: {
96433
96738
  source: "subagent_handback",
96434
96739
  outcome: opts.ctx.outcome,
96740
+ chat_id: opts.ctx.chatId,
96435
96741
  message_id: String(ts),
96436
96742
  ...opts.ctx.threadId != null ? { message_thread_id: String(opts.ctx.threadId) } : {},
96437
96743
  ...opts.ctx.jsonlAgentId ? { subagent_jsonl_id: opts.ctx.jsonlAgentId } : {}
@@ -96511,6 +96817,7 @@ ${summary}
96511
96817
  text: text4,
96512
96818
  meta: {
96513
96819
  source: "subagent_progress",
96820
+ chat_id: opts.ctx.chatId,
96514
96821
  ...opts.ctx.threadId != null ? { message_thread_id: String(opts.ctx.threadId) } : {},
96515
96822
  subagent_jsonl_id: opts.ctx.subagentJsonlId,
96516
96823
  bucket_idx: String(opts.ctx.bucketIdx),
@@ -102121,10 +102428,10 @@ function startOutboxSweep(deps) {
102121
102428
  }
102122
102429
 
102123
102430
  // ../src/build-info.ts
102124
- var VERSION2 = "0.20.3";
102125
- var COMMIT_SHA = "02c60960";
102126
- var COMMIT_DATE = "2026-08-03T13:46:01Z";
102127
- var LATEST_PR = 4322;
102431
+ var VERSION2 = "0.20.5";
102432
+ var COMMIT_SHA = "fc40cc8e";
102433
+ var COMMIT_DATE = "2026-08-04T04:03:52Z";
102434
+ var LATEST_PR = 4338;
102128
102435
  var COMMITS_AHEAD_OF_TAG = 0;
102129
102436
 
102130
102437
  // gateway/boot-version.ts
@@ -103176,7 +103483,7 @@ function openTurnsDb(agentDir) {
103176
103483
  } catch {}
103177
103484
  return db3;
103178
103485
  }
103179
- function mapRow(row) {
103486
+ function mapRow2(row) {
103180
103487
  return {
103181
103488
  turn_key: row.turn_key,
103182
103489
  chat_id: row.chat_id,
@@ -103213,10 +103520,6 @@ function recordTurnEnd2(db3, args) {
103213
103520
  WHERE turn_key = ?
103214
103521
  `).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);
103215
103522
  }
103216
- function getTurnByKey(db3, turnKey3) {
103217
- const row = db3.prepare(`SELECT * FROM turns WHERE turn_key = ?`).get(turnKey3);
103218
- return row ? mapRow(row) : null;
103219
- }
103220
103523
  function markOrphanedWithTimeoutClassification(db3, opts) {
103221
103524
  const now = opts.now ?? Date.now();
103222
103525
  const isHang = opts.markerAgeMs != null && opts.markerAgeMs >= opts.hangThresholdMs && opts.markerTurnKey != null && opts.markerTurnKey.length > 0;
@@ -103307,7 +103610,7 @@ function findLatestTurnIfInterrupted(db3) {
103307
103610
  `).get();
103308
103611
  if (!row)
103309
103612
  return null;
103310
- const turn = mapRow(row);
103613
+ const turn = mapRow2(row);
103311
103614
  if (turn.resumed_at != null)
103312
103615
  return null;
103313
103616
  if (turn.ended_at == null)
@@ -104296,7 +104599,7 @@ function applySubagentsSchema(db3) {
104296
104599
  db3.exec("CREATE INDEX IF NOT EXISTS subagents_jsonl_id ON subagents(jsonl_agent_id)");
104297
104600
  db3.exec("CREATE INDEX IF NOT EXISTS subagents_parent_agent ON subagents(parent_agent_id)");
104298
104601
  }
104299
- function mapSubagentRow(row) {
104602
+ function mapSubagentRow2(row) {
104300
104603
  return {
104301
104604
  id: row.id,
104302
104605
  parent_session_id: row.parent_session_id,
@@ -104314,9 +104617,9 @@ function mapSubagentRow(row) {
104314
104617
  model: row.model ?? null
104315
104618
  };
104316
104619
  }
104317
- function getSubagentByJsonlId(db3, jsonlAgentId) {
104620
+ function getSubagentByJsonlId2(db3, jsonlAgentId) {
104318
104621
  const row = db3.prepare("SELECT * FROM subagents WHERE jsonl_agent_id = ?").get(jsonlAgentId);
104319
- return row ? mapSubagentRow(row) : null;
104622
+ return row ? mapSubagentRow2(row) : null;
104320
104623
  }
104321
104624
  function listNonTerminalSubagentsForTurn(db3, parentTurnKey) {
104322
104625
  const rows = db3.prepare(`
@@ -104325,23 +104628,74 @@ function listNonTerminalSubagentsForTurn(db3, parentTurnKey) {
104325
104628
  AND status NOT IN ('completed', 'failed')
104326
104629
  ORDER BY started_at ASC
104327
104630
  `).all(parentTurnKey);
104328
- return rows.map(mapSubagentRow);
104631
+ return rows.map(mapSubagentRow2);
104329
104632
  }
104330
- function resolveSubagentOriginTurnKey2(db3, jsonlAgentId, maxHops = 5) {
104331
- const seen = new Set;
104332
- let currentJsonlId = jsonlAgentId;
104333
- for (let hop = 0;hop <= maxHops && currentJsonlId != null; hop++) {
104334
- if (seen.has(currentJsonlId))
104633
+
104634
+ // gateway/subagent-origin-surface.ts
104635
+ function turnToSurfaceChat(turn) {
104636
+ if (turn == null || turn.chat_id.length === 0)
104637
+ return null;
104638
+ const threadNum = turn.thread_id != null && turn.thread_id.length > 0 ? Number(turn.thread_id) : NaN;
104639
+ return {
104640
+ chatId: turn.chat_id,
104641
+ ...Number.isFinite(threadNum) ? { threadId: threadNum } : {}
104642
+ };
104643
+ }
104644
+ function resolveSubagentOriginChatDb(db3, jsonlAgentId) {
104645
+ try {
104646
+ const originKey = resolveSubagentOriginTurnKey(db3, jsonlAgentId);
104647
+ if (originKey == null)
104335
104648
  return null;
104336
- seen.add(currentJsonlId);
104337
- const row = db3.prepare("SELECT parent_turn_key, parent_agent_id FROM subagents WHERE jsonl_agent_id = ? LIMIT 1").get(currentJsonlId);
104338
- if (row == null)
104649
+ return turnToSurfaceChat(getTurnByKey(db3, originKey));
104650
+ } catch {
104651
+ return null;
104652
+ }
104653
+ }
104654
+ function resolveRecentTurnFallbackChat(db3, jsonlAgentId) {
104655
+ try {
104656
+ const worker = getSubagentByJsonlId(db3, jsonlAgentId);
104657
+ if (worker == null)
104339
104658
  return null;
104340
- if (row.parent_turn_key != null && row.parent_turn_key.length > 0)
104341
- return row.parent_turn_key;
104342
- currentJsonlId = row.parent_agent_id ?? null;
104659
+ return turnToSurfaceChat(findMostRecentTurn(db3, worker.started_at));
104660
+ } catch {
104661
+ return null;
104343
104662
  }
104344
- return null;
104663
+ }
104664
+ var RECENT_TURN_FLOOR_LOG_CAP = 256;
104665
+ var recentTurnFloorLogged = new Set;
104666
+ function noteWorkerRecentTurnFloor(agentId, dest, log = (line) => process.stderr.write(line)) {
104667
+ if (recentTurnFloorLogged.has(agentId))
104668
+ return;
104669
+ recentTurnFloorLogged.add(agentId);
104670
+ if (recentTurnFloorLogged.size > RECENT_TURN_FLOOR_LOG_CAP) {
104671
+ const oldest = recentTurnFloorLogged.values().next().value;
104672
+ if (oldest != null)
104673
+ recentTurnFloorLogged.delete(oldest);
104674
+ }
104675
+ log(`telegram gateway: worker origin unresolved agent=${agentId} \u2014 flooring to pre-dispatch recent turn chat=${dest.chatId}${dest.threadId != null ? ` thread=${dest.threadId}` : ""}
104676
+ `);
104677
+ }
104678
+ function resolveWorkerSurfaceForDecider(db3, jsonlAgentId, opts) {
104679
+ const dest = resolveWorkerSurfaceChat(db3, jsonlAgentId, opts);
104680
+ if (dest.via === "recent-turn")
104681
+ noteWorkerRecentTurnFloor(jsonlAgentId, dest);
104682
+ return {
104683
+ fleetChatId: dest.via === "owner-dm" || dest.via === "none" ? "" : dest.chatId,
104684
+ ...dest.threadId != null ? { originThreadId: dest.threadId } : {}
104685
+ };
104686
+ }
104687
+ function resolveWorkerSurfaceChat(db3, jsonlAgentId, opts) {
104688
+ const origin = db3 != null ? resolveSubagentOriginChatDb(db3, jsonlAgentId) : null;
104689
+ if (origin != null && origin.chatId.length > 0)
104690
+ return { ...origin, via: "origin" };
104691
+ if (opts.fleetChatId.length > 0)
104692
+ return { chatId: opts.fleetChatId, via: "fleet" };
104693
+ const recent = db3 != null ? resolveRecentTurnFallbackChat(db3, jsonlAgentId) : null;
104694
+ if (recent != null)
104695
+ return { ...recent, via: "recent-turn" };
104696
+ if (opts.ownerDm.length > 0)
104697
+ return { chatId: opts.ownerDm, via: "owner-dm" };
104698
+ return { chatId: "", via: "none" };
104345
104699
  }
104346
104700
 
104347
104701
  // gateway/worker-feed-dispatch.ts
@@ -104887,21 +105241,12 @@ if (isGatewayMain)
104887
105241
  function resolveSubagentOriginChat(agentId) {
104888
105242
  if (turnsDb == null)
104889
105243
  return null;
104890
- try {
104891
- const originKey = resolveSubagentOriginTurnKey2(turnsDb, agentId);
104892
- if (originKey == null)
104893
- return null;
104894
- const turn = getTurnByKey(turnsDb, originKey);
104895
- if (turn == null || turn.chat_id.length === 0)
104896
- return null;
104897
- const threadNum = turn.thread_id != null && turn.thread_id.length > 0 ? Number(turn.thread_id) : NaN;
104898
- return {
104899
- chatId: turn.chat_id,
104900
- threadId: Number.isFinite(threadNum) ? threadNum : undefined
104901
- };
104902
- } catch {
105244
+ return resolveSubagentOriginChatDb(turnsDb, agentId);
105245
+ }
105246
+ function recentTurnFallbackChat(agentId) {
105247
+ if (turnsDb == null)
104903
105248
  return null;
104904
- }
105249
+ return resolveRecentTurnFallbackChat(turnsDb, agentId);
104905
105250
  }
104906
105251
  var WORKER_FEED_FALLBACK_LOG_CAP = 256;
104907
105252
  var WORKER_FEED_STALE_TTL_MARGIN_MS = 5 * 60000;
@@ -104925,17 +105270,16 @@ function noteWorkerFeedOwnerDmFallback(agentId) {
104925
105270
  }
104926
105271
  var workerFeedOriginDeferrals = new Map;
104927
105272
  var WORKER_FEED_ORIGIN_DEFER_MAX = 10;
104928
- function resolveWorkerFeedChat(agentId, fleetChatId, fallbackThreadId) {
104929
- const origin = resolveSubagentOriginChat(agentId);
104930
- if (origin != null && origin.chatId.length > 0)
104931
- return origin;
104932
- if (fleetChatId.length > 0)
104933
- return { chatId: fleetChatId, threadId: fallbackThreadId };
104934
- const ownerDm = loadAccess().allowFrom[0] ?? "";
104935
- if (origin == null && fleetChatId.length === 0 && ownerDm.length > 0) {
105273
+ function resolveWorkerFeedChat(agentId, fleetChatId) {
105274
+ const dest = resolveWorkerSurfaceChat(turnsDb, agentId, {
105275
+ fleetChatId,
105276
+ ownerDm: loadAccess().allowFrom[0] ?? ""
105277
+ });
105278
+ if (dest.via === "recent-turn")
105279
+ noteWorkerRecentTurnFloor(agentId, dest);
105280
+ if (dest.via === "owner-dm")
104936
105281
  noteWorkerFeedOwnerDmFallback(agentId);
104937
- }
104938
- return { chatId: ownerDm, threadId: origin?.threadId ?? fallbackThreadId };
105282
+ return { chatId: dest.chatId, ...dest.threadId != null ? { threadId: dest.threadId } : {} };
104939
105283
  }
104940
105284
  var REGISTRY_REAPER_INTERVAL_MS = 6 * 60 * 60 * 1000;
104941
105285
  function runHistoryReaperNow(reason) {
@@ -105197,6 +105541,7 @@ var TOPIC_FRAMING_ENABLED = process.env.SWITCHROOM_TOPIC_FRAMING !== "0";
105197
105541
  var QUEUED_STATUS_UX_ENABLED = process.env.SWITCHROOM_QUEUED_STATUS_UX !== "0";
105198
105542
  var MIDFLIGHT_BUSY_ACK_ENABLED = process.env.SWITCHROOM_MIDFLIGHT_BUSY_ACK !== "0";
105199
105543
  var FEED_REOPEN_AFTER_ACK_ENABLED = process.env.SWITCHROOM_FEED_REOPEN_AFTER_ACK !== "0";
105544
+ var FEED_REOPEN_AFTER_SUBSTANTIVE_ENABLED = process.env.SWITCHROOM_FEED_REOPEN_AFTER_SUBSTANTIVE !== "0";
105200
105545
  var FEED_HEARTBEAT_ENABLED = process.env.SWITCHROOM_FEED_HEARTBEAT !== "0";
105201
105546
  var FEED_HEARTBEAT_TICK_MS = 6000;
105202
105547
  var FEED_HEARTBEAT_MIN_STALE_MS = 6000;
@@ -107697,7 +108042,7 @@ async function runMidSessionCardReaper() {
107697
108042
  if (turnsDb == null)
107698
108043
  return "unknown";
107699
108044
  try {
107700
- const row = getSubagentByJsonlId(turnsDb, agentId);
108045
+ const row = getSubagentByJsonlId2(turnsDb, agentId);
107701
108046
  if (row == null)
107702
108047
  return "unknown";
107703
108048
  if (row.status === "completed" || row.status === "failed")
@@ -109571,26 +109916,23 @@ async function executeProgressUpdate(args) {
109571
109916
  }
109572
109917
  }
109573
109918
  const turnStart = activeTurnStartedAt.get(key);
109574
- if (turnStart != null) {
109575
- const currentCount = progressUpdateTurnCount.get(key) ?? 0;
109576
- if (currentCount >= 5) {
109577
- return {
109578
- content: [
109579
- {
109580
- type: "text",
109581
- text: JSON.stringify({ ok: false, reason: "turn_limit" })
109582
- }
109583
- ]
109584
- };
109585
- }
109586
- progressUpdateTurnCount.set(key, currentCount + 1);
109587
- }
109588
109919
  const access = loadAccess();
109589
109920
  const literalText = (access.parseMode ?? "html") === "text";
109590
109921
  const sendOpts = {
109591
109922
  ...threadId != null ? { message_thread_id: threadId } : {}
109592
109923
  };
109593
- const sent = await robustApiCall(() => literalText ? lockedBot.api.sendMessage(chat_id, text5, sendOpts) : lockedBot.api.sendRichMessage(chat_id, richMessage2(text5), sendOpts), { verb: "sendMessage", chat_id, threadId });
109924
+ const capped = await sendWithProgressCap({ key, now, turnStart, turnCount: progressUpdateTurnCount }, () => robustApiCall(() => literalText ? lockedBot.api.sendMessage(chat_id, text5, sendOpts) : lockedBot.api.sendRichMessage(chat_id, richMessage2(text5), sendOpts), { verb: "sendMessage", chat_id, threadId }));
109925
+ if (capped.capped) {
109926
+ return {
109927
+ content: [
109928
+ {
109929
+ type: "text",
109930
+ text: JSON.stringify({ ok: false, reason: "turn_limit" })
109931
+ }
109932
+ ]
109933
+ };
109934
+ }
109935
+ const sent = capped.result;
109594
109936
  if (HISTORY_ENABLED) {
109595
109937
  recordOutbound({
109596
109938
  chat_id,
@@ -109603,6 +109945,9 @@ async function executeProgressUpdate(args) {
109603
109945
  try {
109604
109946
  noteSignal(key, Date.now());
109605
109947
  } catch {}
109948
+ try {
109949
+ noteOutbound2(key, Date.now());
109950
+ } catch {}
109606
109951
  return {
109607
109952
  content: [
109608
109953
  {
@@ -110421,6 +110766,7 @@ function gatewayStreamRenderDeps() {
110421
110766
  CONTEXT_EXHAUSTION_COOLDOWN_MS,
110422
110767
  DELIVERY_CONFIRM_ENABLED,
110423
110768
  FEED_REOPEN_AFTER_ACK_ENABLED,
110769
+ FEED_REOPEN_AFTER_SUBSTANTIVE_ENABLED,
110424
110770
  HANDBACK_PRETURN_ENABLED,
110425
110771
  HISTORY_ENABLED,
110426
110772
  LIVENESS_TERMINAL_HONESTY,
@@ -116006,7 +116352,7 @@ async function startGateway() {
116006
116352
  let dispatch = resolveWorkerFeedDispatch(null, description2, entryBackground);
116007
116353
  if (turnsDb != null) {
116008
116354
  try {
116009
- dispatch = resolveWorkerFeedDispatch(getSubagentByJsonlId(turnsDb, agentId), description2, entryBackground);
116355
+ dispatch = resolveWorkerFeedDispatch(getSubagentByJsonlId2(turnsDb, agentId), description2, entryBackground);
116010
116356
  } catch {}
116011
116357
  }
116012
116358
  let isBackground = dispatch.isBackground;
@@ -116088,14 +116434,13 @@ async function startGateway() {
116088
116434
  model: dispatch.feedModel ?? undefined
116089
116435
  });
116090
116436
  }
116091
- const handbackOrigin = resolveSubagentOriginChat(agentId);
116437
+ const hbOwnerDm = loadAccess().allowFrom[0] ?? "";
116092
116438
  const decision = decideSubagentHandback({
116093
116439
  handbackEnvValue: process.env.SWITCHROOM_SUBAGENT_HANDBACK,
116094
116440
  outcome,
116095
116441
  isBackground,
116096
- fleetChatId: handbackOrigin?.chatId || fleetChatId,
116097
- ...handbackOrigin?.threadId != null ? { originThreadId: handbackOrigin.threadId } : {},
116098
- ownerChatId: loadAccess().allowFrom[0] ?? "",
116442
+ ...resolveWorkerSurfaceForDecider(turnsDb, agentId, { fleetChatId, ownerDm: hbOwnerDm }),
116443
+ ownerChatId: hbOwnerDm,
116099
116444
  taskDescription: description2,
116100
116445
  resultText,
116101
116446
  jsonlAgentId: agentId
@@ -116136,7 +116481,7 @@ async function startGateway() {
116136
116481
  let dispatch = resolveWorkerFeedDispatch(null, description2);
116137
116482
  if (turnsDb != null) {
116138
116483
  try {
116139
- dispatch = resolveWorkerFeedDispatch(getSubagentByJsonlId(turnsDb, agentId), description2);
116484
+ dispatch = resolveWorkerFeedDispatch(getSubagentByJsonlId2(turnsDb, agentId), description2);
116140
116485
  } catch {}
116141
116486
  }
116142
116487
  const isBackground = dispatch.isBackground || dispatch.isNested;
@@ -116212,14 +116557,16 @@ async function startGateway() {
116212
116557
  stampTurn.subagentActivityAt = Date.now();
116213
116558
  }
116214
116559
  if (workerFeedEnabled) {
116560
+ const wfOrigin = resolveSubagentOriginChat(agentId);
116561
+ const wfStamp = stampTurn != null ? { chatId: stampTurn.sessionChatId, threadId: stampTurn.sessionThreadId } : wfOrigin == null ? recentTurnFallbackChat(agentId) : null;
116215
116562
  const dest = decideWorkerFeedDestination({
116216
- origin: resolveSubagentOriginChat(agentId),
116563
+ origin: wfOrigin,
116217
116564
  cardExists: workerActivityFeed?.has(agentId) === true,
116218
116565
  priorDeferrals: workerFeedOriginDeferrals.get(agentId) ?? 0,
116219
116566
  maxDeferrals: WORKER_FEED_ORIGIN_DEFER_MAX,
116220
116567
  fleetChatId,
116221
- stampChatId: stampTurn?.sessionChatId,
116222
- stampThreadId: stampTurn?.sessionThreadId,
116568
+ stampChatId: wfStamp?.chatId,
116569
+ stampThreadId: wfStamp?.threadId,
116223
116570
  ownerDm: loadAccess().allowFrom[0] ?? ""
116224
116571
  });
116225
116572
  if (dest.action === "defer") {
@@ -116233,6 +116580,9 @@ async function startGateway() {
116233
116580
  }
116234
116581
  if (dest.ownerDmFallback)
116235
116582
  noteWorkerFeedOwnerDmFallback(agentId);
116583
+ if (stampTurn == null && wfOrigin == null && wfStamp != null && dest.chatId === wfStamp.chatId) {
116584
+ noteWorkerRecentTurnFloor(agentId, wfStamp);
116585
+ }
116236
116586
  workerActivityFeed?.update(agentId, dest.chatId, {
116237
116587
  description: dispatch.feedDescription,
116238
116588
  lastTool,
@@ -116245,14 +116595,17 @@ async function startGateway() {
116245
116595
  }, dest.threadId);
116246
116596
  return;
116247
116597
  }
116248
- const progressOrigin = resolveSubagentOriginChat(agentId);
116598
+ const pgOwnerDm = loadAccess().allowFrom[0] ?? "";
116599
+ const progressSurface = resolveWorkerSurfaceForDecider(turnsDb, agentId, {
116600
+ fleetChatId,
116601
+ ownerDm: pgOwnerDm
116602
+ });
116249
116603
  const decision = decideSubagentProgress({
116250
116604
  skeleton: skeleton === true,
116251
116605
  disableEnvValue: process.env.SWITCHROOM_DISABLE_SUBAGENT_PROGRESS,
116252
116606
  isBackground,
116253
- fleetChatId: progressOrigin?.chatId || fleetChatId,
116254
- ...progressOrigin?.threadId != null ? { originThreadId: progressOrigin.threadId } : {},
116255
- ownerChatId: loadAccess().allowFrom[0] ?? "",
116607
+ ...progressSurface,
116608
+ ownerChatId: pgOwnerDm,
116256
116609
  subagentJsonlId: agentId,
116257
116610
  taskDescription: description2,
116258
116611
  latestSummary,
@@ -116264,7 +116617,7 @@ async function startGateway() {
116264
116617
  return;
116265
116618
  setBucketIdx(decision.bucketIdx);
116266
116619
  pendingInboundBuffer.push(process.env.SWITCHROOM_AGENT_NAME ?? "", decision.inbound);
116267
- clearPending(statusKey(decision.chatId, progressOrigin?.threadId), "progress");
116620
+ clearPending(statusKey(decision.chatId, progressSurface.originThreadId), "progress");
116268
116621
  process.stderr.write(`telegram gateway: subagent-progress queued agent=${agentId} bucket=${decision.bucketIdx} elapsed_ms=${elapsedMs} chat=${decision.chatId}
116269
116622
  `);
116270
116623
  }