switchroom 0.16.47 → 0.17.1

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 (117) hide show
  1. package/dist/agent-scheduler/index.js +3 -1
  2. package/dist/auth-broker/index.js +24 -8
  3. package/dist/cli/drive-write-pretool.mjs +0 -5
  4. package/dist/cli/notion-write-pretool.mjs +3 -1
  5. package/dist/cli/switchroom.js +1359 -1030
  6. package/dist/cli/ui/index.html +84 -12
  7. package/dist/host-control/main.js +53 -17
  8. package/dist/vault/approvals/kernel-server.js +4 -1
  9. package/dist/vault/broker/server.js +201 -56
  10. package/package.json +3 -3
  11. package/profiles/_base/cron-session.sh.hbs +1 -1
  12. package/profiles/_base/start.sh.hbs +54 -3
  13. package/skills/switchroom-architecture/telegram.md +8 -15
  14. package/skills/switchroom-cli/SKILL.md +4 -5
  15. package/skills/telegram-test-harness/SKILL.md +1 -1
  16. package/telegram-plugin/README.md +18 -29
  17. package/telegram-plugin/bridge/bridge.ts +1 -41
  18. package/telegram-plugin/bridge/tool-filter.ts +3 -4
  19. package/telegram-plugin/dist/bridge/bridge.js +8 -43
  20. package/telegram-plugin/dist/gateway/gateway.js +842 -832
  21. package/telegram-plugin/dist/server.js +8 -43
  22. package/telegram-plugin/format.ts +119 -17
  23. package/telegram-plugin/gateway/approvals-commands.ts +6 -2
  24. package/telegram-plugin/gateway/busy-key-reaper.ts +113 -0
  25. package/telegram-plugin/gateway/disconnect-flush.ts +11 -0
  26. package/telegram-plugin/gateway/escalation-bridge-gate.ts +46 -0
  27. package/telegram-plugin/gateway/gate-parity-probe.ts +102 -0
  28. package/telegram-plugin/gateway/gateway.ts +535 -627
  29. package/telegram-plugin/gateway/inbound-delivery-confirm.ts +89 -7
  30. package/telegram-plugin/gateway/inbound-spool.ts +108 -10
  31. package/telegram-plugin/gateway/model-command.ts +51 -3
  32. package/telegram-plugin/gateway/ms365-write-approval.test.ts +13 -0
  33. package/telegram-plugin/gateway/ms365-write-approval.ts +5 -1
  34. package/telegram-plugin/gateway/pending-inbound-buffer.ts +26 -0
  35. package/telegram-plugin/gateway/represent-guard.ts +28 -11
  36. package/telegram-plugin/gateway/status-pin-store.ts +124 -45
  37. package/telegram-plugin/gateway/vault-request-access-card.ts +5 -1
  38. package/telegram-plugin/gateway/worker-feed-dispatch.ts +19 -0
  39. package/telegram-plugin/history.ts +5 -0
  40. package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +1 -2
  41. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +9 -1
  42. package/telegram-plugin/registry/subagents-schema.ts +126 -1
  43. package/telegram-plugin/registry/turns-schema.ts +65 -1
  44. package/telegram-plugin/session-tail.ts +26 -4
  45. package/telegram-plugin/slot-banner-driver.ts +42 -2
  46. package/telegram-plugin/status-query-telemetry.ts +100 -0
  47. package/telegram-plugin/stream-reply-handler.ts +15 -16
  48. package/telegram-plugin/subagent-watcher.ts +182 -30
  49. package/telegram-plugin/tests/buffer-gate-broadened.test.ts +4 -10
  50. package/telegram-plugin/tests/busy-key-reaper.test.ts +191 -0
  51. package/telegram-plugin/tests/emission-authority-facade.test.ts +11 -17
  52. package/telegram-plugin/tests/emission-determinism-wiring.test.ts +5 -26
  53. package/telegram-plugin/tests/escalation-bridge-gate.test.ts +38 -0
  54. package/telegram-plugin/tests/format-consistency.test.ts +79 -0
  55. package/telegram-plugin/tests/gate-parity-probe.test.ts +171 -0
  56. package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +13 -0
  57. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +14 -11
  58. package/telegram-plugin/tests/inbound-delivery-confirm.test.ts +146 -0
  59. package/telegram-plugin/tests/inbound-spool.test.ts +143 -0
  60. package/telegram-plugin/tests/model-command.test.ts +54 -1
  61. package/telegram-plugin/tests/multitopic-routing-wiring.test.ts +5 -11
  62. package/telegram-plugin/tests/nested-worker-visibility-harness.test.ts +329 -0
  63. package/telegram-plugin/tests/pending-inbound-buffer.test.ts +53 -0
  64. package/telegram-plugin/tests/progress-update-redact.test.ts +99 -0
  65. package/telegram-plugin/tests/registry-turns.test.ts +67 -0
  66. package/telegram-plugin/tests/represent-guard.test.ts +42 -6
  67. package/telegram-plugin/tests/resume-inbound-builder.test.ts +1 -0
  68. package/telegram-plugin/tests/session-tail.test.ts +10 -1
  69. package/telegram-plugin/tests/slot-banner-boot-recovery.test.ts +246 -0
  70. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +0 -14
  71. package/telegram-plugin/tests/status-pin-store.test.ts +220 -5
  72. package/telegram-plugin/tests/status-query-telemetry.test.ts +115 -0
  73. package/telegram-plugin/tests/subagent-nested-dispatch.test.ts +209 -0
  74. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +37 -0
  75. package/telegram-plugin/tests/subagent-watcher-boot-promotion-replay.test.ts +167 -0
  76. package/telegram-plugin/tests/subagent-watcher-env-thresholds.test.ts +46 -3
  77. package/telegram-plugin/tests/subagent-watcher-stall-notification.test.ts +70 -0
  78. package/telegram-plugin/tests/tool-activity-summary.test.ts +16 -0
  79. package/telegram-plugin/tests/tool-filter.test.ts +1 -3
  80. package/telegram-plugin/tests/tool-label-pretool.test.ts +1 -4
  81. package/telegram-plugin/tests/turn-flush-safety.test.ts +222 -1
  82. package/telegram-plugin/tests/vault-request-access-card.test.ts +17 -0
  83. package/telegram-plugin/tests/welcome-text.test.ts +64 -0
  84. package/telegram-plugin/tests/worker-activity-feed.test.ts +202 -9
  85. package/telegram-plugin/tests/worker-feed-dispatch.test.ts +25 -0
  86. package/telegram-plugin/tests/worker-visibility-prose-silent-harness.test.ts +295 -0
  87. package/telegram-plugin/tool-activity-summary.ts +19 -0
  88. package/telegram-plugin/turn-flush-safety.ts +16 -1
  89. package/telegram-plugin/uat/scenarios/jtbd-answer-pings.test.ts +8 -9
  90. package/telegram-plugin/uat/scenarios/jtbd-foreground-feed-visibility-dm.test.ts +1 -1
  91. package/telegram-plugin/uat/scenarios/jtbd-narration-intent-dm.test.ts +1 -1
  92. package/telegram-plugin/welcome-text.ts +13 -9
  93. package/telegram-plugin/worker-activity-feed.ts +75 -15
  94. package/vendor/hindsight-memory/CHANGELOG.md +66 -0
  95. package/vendor/hindsight-memory/README.md +5 -0
  96. package/vendor/hindsight-memory/scripts/lib/client.py +31 -1
  97. package/vendor/hindsight-memory/scripts/lib/config.py +41 -2
  98. package/vendor/hindsight-memory/scripts/lib/content.py +43 -4
  99. package/vendor/hindsight-memory/scripts/lib/daemon.py +11 -2
  100. package/vendor/hindsight-memory/scripts/recall.py +74 -1
  101. package/vendor/hindsight-memory/scripts/retain.py +79 -11
  102. package/vendor/hindsight-memory/scripts/tests/test_config_client_casts.py +111 -0
  103. package/vendor/hindsight-memory/scripts/tests/test_recall_context_slice.py +126 -0
  104. package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +85 -1
  105. package/vendor/hindsight-memory/scripts/tests/test_recall_tag_filters.py +107 -0
  106. package/vendor/hindsight-memory/scripts/tests/test_retain_window.py +261 -0
  107. package/vendor/hindsight-memory/settings.json +4 -0
  108. package/vendor/hindsight-memory/tests/test_client.py +130 -0
  109. package/vendor/hindsight-memory/tests/test_config.py +47 -0
  110. package/vendor/hindsight-memory/tests/test_content.py +123 -0
  111. package/vendor/hindsight-memory/tests/test_hooks.py +62 -0
  112. package/telegram-plugin/gateway/error-envelope-card.ts +0 -64
  113. package/telegram-plugin/gateway/resolve-calling-subagent.ts +0 -78
  114. package/telegram-plugin/silent-reply.ts +0 -58
  115. package/telegram-plugin/tests/error-envelope-unlock-card.test.ts +0 -79
  116. package/telegram-plugin/tests/resolve-calling-subagent.test.ts +0 -269
  117. package/telegram-plugin/tests/silent-reply-guard.test.ts +0 -122
@@ -6713,6 +6713,77 @@ function escapeMarkdown(text) {
6713
6713
  function codeSpanSafe(s) {
6714
6714
  return s.replace(/`/g, "`\u200b");
6715
6715
  }
6716
+ function maskCodeRegions(text, nonce) {
6717
+ const FENCE_MASK_PH = `\x00RMF${nonce}_`;
6718
+ const INLINE_MASK_PH = `\x00RMI${nonce}_`;
6719
+ const codeMasks = [];
6720
+ const masked = text.replace(/```[\s\S]*?```/g, (m) => {
6721
+ const idx = codeMasks.length;
6722
+ codeMasks.push(m);
6723
+ return `${FENCE_MASK_PH}${idx}\x00`;
6724
+ }).replace(/`[^`\n]+`/g, (m) => {
6725
+ const idx = codeMasks.length;
6726
+ codeMasks.push(m);
6727
+ return `${INLINE_MASK_PH}${idx}\x00`;
6728
+ });
6729
+ const escNonce = nonce.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6730
+ const anyMaskRe = new RegExp(`\x00RM[FI]${escNonce}_(\\d+)\x00`, "g");
6731
+ const restore = (s) => s.replace(anyMaskRe, (_m, idx) => codeMasks[Number(idx)] ?? _m);
6732
+ const stripPlaceholders = (s) => s.replace(anyMaskRe, "");
6733
+ return { masked, restore, placeholder: FENCE_MASK_PH, stripPlaceholders };
6734
+ }
6735
+ function hardenCardBreaks(text) {
6736
+ if (!text.includes(`
6737
+ `))
6738
+ return text;
6739
+ const nonce = Math.random().toString(36).slice(2);
6740
+ const { masked, restore, placeholder } = maskCodeRegions(text, nonce);
6741
+ const out = masked.replace(/\n{3,}/g, `
6742
+
6743
+ `);
6744
+ const isBlockConstructLine = (line) => isListItemLine(line) || isTableRowLine(line) || isTableDelimiterLine(line) || isBlockquoteLine(line) || isHeadingLine(line) || isFenceOpenLine(line, placeholder);
6745
+ const lines = out.split(`
6746
+ `);
6747
+ const pieces = [];
6748
+ for (let i = 0;i < lines.length; i++) {
6749
+ let line = lines[i];
6750
+ const isLast = i === lines.length - 1;
6751
+ const next = isLast ? "" : lines[i + 1];
6752
+ const promote = !isLast && line.trim() !== "" && next.trim() !== "" && !isBlockConstructLine(line) && !isBlockConstructLine(next);
6753
+ if (promote) {
6754
+ line = line.replace(/[ \t\r]+$/, "");
6755
+ }
6756
+ pieces.push(line);
6757
+ if (isLast)
6758
+ break;
6759
+ pieces.push(promote ? `
6760
+ ` : `
6761
+ `);
6762
+ }
6763
+ return restore(pieces.join(""));
6764
+ }
6765
+ function isListItemLine(line) {
6766
+ const t = line.trimStart();
6767
+ return /^[-*+]\s/.test(t) || /^\d+[.)]\s/.test(t);
6768
+ }
6769
+ function isTableRowLine(line) {
6770
+ return /^\s*\|/.test(line);
6771
+ }
6772
+ function isTableDelimiterLine(line) {
6773
+ return /^\s*\|?\s*:?-{1,}:?\s*(\|\s*:?-{1,}:?\s*)*\|?\s*$/.test(line);
6774
+ }
6775
+ function isFenceOpenLine(line, placeholder) {
6776
+ const t = line.trimStart();
6777
+ if (placeholder != null && placeholder.length > 0 && t.startsWith(placeholder))
6778
+ return true;
6779
+ return t.startsWith("```");
6780
+ }
6781
+ function isBlockquoteLine(line) {
6782
+ return line.trimStart().startsWith(">");
6783
+ }
6784
+ function isHeadingLine(line) {
6785
+ return /^#{1,6}\s/.test(line.trimStart());
6786
+ }
6716
6787
  var RICH_MESSAGE_MAX_CHARS = 32768;
6717
6788
 
6718
6789
  // text-voice-scrub.ts
@@ -16826,13 +16897,11 @@ var exports_client = {};
16826
16897
  __export(exports_client, {
16827
16898
  withAuthBrokerClient: () => withAuthBrokerClient,
16828
16899
  resolveAuthBrokerSocketPath: () => resolveAuthBrokerSocketPath,
16829
- authBrokerSocketExists: () => authBrokerSocketExists,
16830
16900
  AuthBrokerUnreachableError: () => AuthBrokerUnreachableError,
16831
16901
  AuthBrokerError: () => AuthBrokerError,
16832
16902
  AuthBrokerClient: () => AuthBrokerClient
16833
16903
  });
16834
16904
  import * as net from "node:net";
16835
- import { existsSync as existsSync7 } from "node:fs";
16836
16905
  import { homedir as homedir3 } from "node:os";
16837
16906
  import { randomUUID as randomUUID3 } from "node:crypto";
16838
16907
  import { join as join11 } from "node:path";
@@ -17133,9 +17202,6 @@ async function withAuthBrokerClient(fn, opts) {
17133
17202
  await client3.close();
17134
17203
  }
17135
17204
  }
17136
- function authBrokerSocketExists(opts) {
17137
- return existsSync7(resolveAuthBrokerSocketPath(opts));
17138
- }
17139
17205
  var DEFAULT_TIMEOUT_MS = 5000, AuthBrokerError, AuthBrokerUnreachableError;
17140
17206
  var init_client = __esm(() => {
17141
17207
  init_protocol();
@@ -17166,11 +17232,14 @@ var init_flock = () => {};
17166
17232
  import { randomBytes as randomBytes3, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
17167
17233
  import {
17168
17234
  readFileSync as readFileSync7,
17169
- writeFileSync as writeFileSync8,
17170
- existsSync as existsSync8,
17235
+ writeSync,
17236
+ existsSync as existsSync7,
17171
17237
  renameSync as renameSync4,
17172
17238
  mkdirSync as mkdirSync10,
17173
17239
  unlinkSync as unlinkSync4,
17240
+ fsyncSync,
17241
+ openSync,
17242
+ closeSync,
17174
17243
  lstatSync,
17175
17244
  realpathSync
17176
17245
  } from "node:fs";
@@ -17205,7 +17274,7 @@ function normalizeSecrets(raw) {
17205
17274
  return out;
17206
17275
  }
17207
17276
  function openVault(passphrase, vaultPath) {
17208
- if (!existsSync8(vaultPath)) {
17277
+ if (!existsSync7(vaultPath)) {
17209
17278
  throw new VaultError(`Vault file not found: ${vaultPath}`);
17210
17279
  }
17211
17280
  let vaultFile;
@@ -24385,7 +24454,9 @@ var init_schema = __esm(() => {
24385
24454
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
24386
24455
  max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
24387
24456
  resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
24388
- resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
24457
+ resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale."),
24458
+ session_retention_max_count: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): keep at most this many " + "newest session transcripts under .claude/projects; older ones " + "past both this count and the age bound are pruned by the Stop " + "hook. The newest sessions (and the handoff source) are always " + "kept. Default 20; set 0 to disable the count bound."),
24459
+ session_retention_max_age_days: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): prune session transcripts " + "older than this many days (a file is deleted only when it is BOTH " + "over the count bound and older than this). Default 30; set 0 to " + "disable the age bound.")
24389
24460
  }).optional();
24390
24461
  webhookDispatchRule = exports_external.object({
24391
24462
  description: exports_external.string().optional(),
@@ -24912,7 +24983,7 @@ var init_schema = __esm(() => {
24912
24983
  });
24913
24984
 
24914
24985
  // ../src/config/paths.ts
24915
- import { existsSync as existsSync9 } from "node:fs";
24986
+ import { existsSync as existsSync8 } from "node:fs";
24916
24987
  import { resolve as resolve2 } from "node:path";
24917
24988
  function home() {
24918
24989
  return process.env.HOME ?? "/root";
@@ -24928,7 +24999,7 @@ function resolveStatePath2(fragment) {
24928
24999
  const h = home();
24929
25000
  const primary = resolve2(h, DEFAULT_STATE_DIR, fragment);
24930
25001
  const legacy = resolve2(h, LEGACY_STATE_DIR, fragment);
24931
- if (!existsSync9(primary) && existsSync9(legacy)) {
25002
+ if (!existsSync8(primary) && existsSync8(legacy)) {
24932
25003
  warnLegacyStateOnce(legacy);
24933
25004
  return legacy;
24934
25005
  }
@@ -24941,9 +25012,9 @@ function resolveDualPath(pathStr) {
24941
25012
  const absolute = resolve2(h, rest);
24942
25013
  if (rest.startsWith(`${DEFAULT_STATE_DIR}/`)) {
24943
25014
  const frag = rest.slice(DEFAULT_STATE_DIR.length + 1);
24944
- if (!existsSync9(absolute)) {
25015
+ if (!existsSync8(absolute)) {
24945
25016
  const legacy = resolve2(h, LEGACY_STATE_DIR, frag);
24946
- if (existsSync9(legacy)) {
25017
+ if (existsSync8(legacy)) {
24947
25018
  warnLegacyStateOnce(legacy);
24948
25019
  return legacy;
24949
25020
  }
@@ -24968,7 +25039,7 @@ var init_overlay_schema = __esm(() => {
24968
25039
  });
24969
25040
 
24970
25041
  // ../src/config/overlay-loader.ts
24971
- import { existsSync as existsSync10, readFileSync as readFileSync8, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
25042
+ import { existsSync as existsSync9, readFileSync as readFileSync8, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
24972
25043
  import { basename as basename4, resolve as resolve3 } from "node:path";
24973
25044
  function deriveOverlayTitle(raw, fileName) {
24974
25045
  const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
@@ -24984,7 +25055,7 @@ function overlayDirFor(agentName3, subdir) {
24984
25055
  return resolve3(base);
24985
25056
  }
24986
25057
  function listYamlFiles(dir) {
24987
- if (!existsSync10(dir))
25058
+ if (!existsSync9(dir))
24988
25059
  return [];
24989
25060
  let entries;
24990
25061
  try {
@@ -25509,7 +25580,7 @@ function validateNotionWorkspaceConfig(config) {
25509
25580
  }
25510
25581
 
25511
25582
  // ../src/config/loader.ts
25512
- import { readFileSync as readFileSync9, existsSync as existsSync11 } from "node:fs";
25583
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "node:fs";
25513
25584
  import { homedir as homedir4 } from "node:os";
25514
25585
  import { resolve as resolve4 } from "node:path";
25515
25586
  function formatZodErrors(error) {
@@ -25576,7 +25647,7 @@ function findConfigFile(startDir) {
25576
25647
  resolve4(userDir, "clerk.yml")
25577
25648
  ].filter(Boolean);
25578
25649
  for (const path of searchPaths) {
25579
- if (existsSync11(path)) {
25650
+ if (existsSync10(path)) {
25580
25651
  return path;
25581
25652
  }
25582
25653
  }
@@ -25584,7 +25655,7 @@ function findConfigFile(startDir) {
25584
25655
  }
25585
25656
  function loadConfig(configPath) {
25586
25657
  const filePath = configPath ?? findConfigFile();
25587
- if (!existsSync11(filePath)) {
25658
+ if (!existsSync10(filePath)) {
25588
25659
  throw new ConfigError(`Config file not found: ${filePath}`);
25589
25660
  }
25590
25661
  let raw;
@@ -25725,7 +25796,6 @@ function getPeerCred(fd) {
25725
25796
  return null;
25726
25797
  }
25727
25798
  }
25728
-
25729
25799
  // ../src/vault/broker/peercred.ts
25730
25800
  function isReservedAgentName(name) {
25731
25801
  return RESERVED_AGENT_NAMES.has(name);
@@ -26258,7 +26328,7 @@ import {
26258
26328
  openSync as openSync2,
26259
26329
  rmSync,
26260
26330
  statSync as statSync5,
26261
- writeSync
26331
+ writeSync as writeSync2
26262
26332
  } from "node:fs";
26263
26333
  import { join as join13 } from "node:path";
26264
26334
  import { tmpdir } from "node:os";
@@ -26325,7 +26395,7 @@ function writeFileExclusive(filePath, content) {
26325
26395
  const buf = typeof content === "string" ? Buffer.from(content, "utf8") : content;
26326
26396
  const fd = openSync2(filePath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, 384);
26327
26397
  try {
26328
- writeSync(fd, buf);
26398
+ writeSync2(fd, buf);
26329
26399
  } finally {
26330
26400
  closeSync2(fd);
26331
26401
  }
@@ -28861,7 +28931,7 @@ __export(exports_history, {
28861
28931
  checkpointWal: () => checkpointWal,
28862
28932
  _resetForTests: () => _resetForTests
28863
28933
  });
28864
- import { chmodSync as chmodSync3, existsSync as existsSync18, mkdirSync as mkdirSync15 } from "fs";
28934
+ import { chmodSync as chmodSync3, existsSync as existsSync17, mkdirSync as mkdirSync15 } from "fs";
28865
28935
  import { join as join16 } from "path";
28866
28936
  function loadDatabaseClass() {
28867
28937
  if (DatabaseClass != null)
@@ -28890,6 +28960,7 @@ function initHistory(stateDir, retentionDays = 30) {
28890
28960
  db = new Database(path, { create: true });
28891
28961
  db.exec("PRAGMA journal_mode = WAL");
28892
28962
  db.exec("PRAGMA synchronous = NORMAL");
28963
+ db.exec("PRAGMA busy_timeout = 5000");
28893
28964
  db.exec(`
28894
28965
  CREATE TABLE IF NOT EXISTS messages (
28895
28966
  chat_id TEXT NOT NULL,
@@ -28922,7 +28993,7 @@ function initHistory(stateDir, retentionDays = 30) {
28922
28993
  }
28923
28994
  for (const suffix of ["", "-shm", "-wal"]) {
28924
28995
  const f = path + suffix;
28925
- if (existsSync18(f)) {
28996
+ if (existsSync17(f)) {
28926
28997
  try {
28927
28998
  chmodSync3(f, 420);
28928
28999
  } catch {}
@@ -28947,7 +29018,7 @@ function checkpointWal() {
28947
29018
  if (dbPath) {
28948
29019
  for (const suffix of ["-shm", "-wal"]) {
28949
29020
  const f = dbPath + suffix;
28950
- if (existsSync18(f)) {
29021
+ if (existsSync17(f)) {
28951
29022
  try {
28952
29023
  chmodSync3(f, 420);
28953
29024
  } catch {}
@@ -29114,11 +29185,11 @@ var init_history = __esm(() => {
29114
29185
  });
29115
29186
 
29116
29187
  // quota-check.ts
29117
- import { readFileSync as readFileSync14, existsSync as existsSync19 } from "fs";
29188
+ import { readFileSync as readFileSync14, existsSync as existsSync18 } from "fs";
29118
29189
  import { join as join17 } from "path";
29119
29190
  function readOauthToken(claudeConfigDir) {
29120
29191
  const tokenFile = join17(claudeConfigDir, ".oauth-token");
29121
- if (!existsSync19(tokenFile))
29192
+ if (!existsSync18(tokenFile))
29122
29193
  return null;
29123
29194
  try {
29124
29195
  const raw = readFileSync14(tokenFile, "utf-8").trim();
@@ -29455,7 +29526,7 @@ function renderAuthLine(state4, agentName3, now = Date.now()) {
29455
29526
  }
29456
29527
 
29457
29528
  // gateway/quota-cache.ts
29458
- import { existsSync as existsSync32, readFileSync as readFileSync29, writeFileSync as writeFileSync21, mkdirSync as mkdirSync22 } from "fs";
29529
+ import { existsSync as existsSync31, readFileSync as readFileSync29, writeFileSync as writeFileSync20, mkdirSync as mkdirSync22 } from "fs";
29459
29530
  import { join as join28, dirname as dirname9 } from "path";
29460
29531
  function defaultCachePath() {
29461
29532
  return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join28(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
@@ -29463,7 +29534,7 @@ function defaultCachePath() {
29463
29534
  function readQuotaCache(opts = {}) {
29464
29535
  const path = opts.path ?? defaultCachePath();
29465
29536
  const now = opts.now ?? Date.now();
29466
- if (!existsSync32(path))
29537
+ if (!existsSync31(path))
29467
29538
  return null;
29468
29539
  let entry;
29469
29540
  try {
@@ -29494,7 +29565,7 @@ function writeQuotaCache(result, opts = {}) {
29494
29565
  };
29495
29566
  try {
29496
29567
  mkdirSync22(dirname9(path), { recursive: true });
29497
- writeFileSync21(path, JSON.stringify(entry, null, 2), { mode: 384 });
29568
+ writeFileSync20(path, JSON.stringify(entry, null, 2), { mode: 384 });
29498
29569
  } catch {}
29499
29570
  }
29500
29571
  var DEFAULT_TTL_MS4, RATE_LIMIT_TTL_MS;
@@ -29504,10 +29575,10 @@ var init_quota_cache = __esm(() => {
29504
29575
  });
29505
29576
 
29506
29577
  // gateway/boot-probes.ts
29507
- import { readFileSync as readFileSync30, readdirSync as readdirSync6, existsSync as existsSync33 } from "fs";
29578
+ import { readFileSync as readFileSync30, readdirSync as readdirSync6, existsSync as existsSync32 } from "fs";
29508
29579
  import { join as join29 } from "path";
29509
29580
  import { execFile as execFileCb } from "child_process";
29510
- import { promisify as promisify3 } from "util";
29581
+ import { promisify } from "util";
29511
29582
  async function withTimeout(label, p, timeoutMs = PROBE_TIMEOUT_MS) {
29512
29583
  let timer3;
29513
29584
  const timeout = new Promise((resolve6) => {
@@ -29572,7 +29643,7 @@ async function probeAccount(agentDir) {
29572
29643
  join29(claudeDir, ".oauth-token.meta.json"),
29573
29644
  join29(claudeDir, "accounts", "default", ".oauth-token.meta.json")
29574
29645
  ]) {
29575
- if (existsSync33(candidate)) {
29646
+ if (existsSync32(candidate)) {
29576
29647
  try {
29577
29648
  const meta = JSON.parse(readFileSync30(candidate, "utf8"));
29578
29649
  if (meta.expiresAt) {
@@ -29749,7 +29820,7 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
29749
29820
  if (!cgroup)
29750
29821
  return null;
29751
29822
  const procsPath = `/sys/fs/cgroup${cgroup}/cgroup.procs`;
29752
- if (!existsSync33(procsPath))
29823
+ if (!existsSync32(procsPath))
29753
29824
  return null;
29754
29825
  const pidsRaw = readFileSync30(procsPath, "utf-8");
29755
29826
  const pids = pidsRaw.split(`
@@ -29826,7 +29897,7 @@ async function probeAgentProcess(agentName3, opts = {}) {
29826
29897
  const retryIntervalMs = opts.retryIntervalMs ?? AGENT_RETRY_INTERVAL_MS;
29827
29898
  const retryMaxMs = opts.retryMaxMs ?? AGENT_RETRY_MAX_MS;
29828
29899
  const sleep2 = opts.sleepImpl ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
29829
- const execFileFn = opts.execFileImpl ?? execFile3;
29900
+ const execFileFn = opts.execFileImpl ?? execFile;
29830
29901
  return withTimeout("Agent", (async () => {
29831
29902
  const startMs = Date.now();
29832
29903
  while (true) {
@@ -29868,7 +29939,7 @@ async function* watchAgentProcess(agentName3, opts = {}) {
29868
29939
  const pollIntervalMs = opts.pollIntervalMs ?? AGENT_LIVE_POLL_INTERVAL_MS;
29869
29940
  const followupRepollMs = opts.followupRepollMs ?? AGENT_LIVE_FOLLOWUP_REPOLL_MS;
29870
29941
  const sleep2 = opts.sleepImpl ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
29871
- const execFileFn = opts.execFileImpl ?? execFile3;
29942
+ const execFileFn = opts.execFileImpl ?? execFile;
29872
29943
  const now = opts.nowImpl ?? (() => Date.now());
29873
29944
  const startMs = now();
29874
29945
  let lastYieldedDetail = null;
@@ -29958,7 +30029,7 @@ async function probeQuota(claudeConfigDir, _agentDir, fetchImpl = fetch, opts =
29958
30029
  claudeConfigDir,
29959
30030
  join29(claudeConfigDir, "accounts", "default")
29960
30031
  ]) {
29961
- if (existsSync33(join29(candidate, ".oauth-token"))) {
30032
+ if (existsSync32(join29(candidate, ".oauth-token"))) {
29962
30033
  claudeDirForProbe = candidate;
29963
30034
  break;
29964
30035
  }
@@ -30129,7 +30200,7 @@ async function probeUds(label, socketPath, opts = {}) {
30129
30200
  }
30130
30201
  return withTimeout(label, (async () => {
30131
30202
  if (!opts.connectImpl) {
30132
- if (!existsSync33(socketPath)) {
30203
+ if (!existsSync32(socketPath)) {
30133
30204
  return {
30134
30205
  status: "fail",
30135
30206
  label,
@@ -30285,11 +30356,11 @@ async function probeConnections(agentDir, opts = {}) {
30285
30356
  };
30286
30357
  })());
30287
30358
  }
30288
- var execFile3, PROBE_TIMEOUT_MS = 2000, QUOTA_BROKER_TIMEOUT_MS = 7000, QUOTA_DIRECT_FALLBACK_TIMEOUT_MS = 5000, QUOTA_PROBE_OUTER_TIMEOUT_MS = 9000, TOKEN_EXPIRING_SOON_DAYS = 7, AGENT_RETRY_INTERVAL_MS = 1500, AGENT_RETRY_MAX_MS = 12000, AGENT_LIVE_WINDOW_MS = 45000, AGENT_LIVE_POLL_INTERVAL_MS = 2000, AGENT_LIVE_FOLLOWUP_REPOLL_MS = 30000, realProcFs, SCHEDULER_LOCK_PATH_DEFAULT = "/state/agent/scheduler.lock", SCHEDULER_JSONL_PATH_DEFAULT = "/state/agent/scheduler.jsonl", SCHEDULER_FRESH_BOOT_MS = 30000, realSchedulerFs, realSkillsFs;
30359
+ var execFile, PROBE_TIMEOUT_MS = 2000, QUOTA_BROKER_TIMEOUT_MS = 7000, QUOTA_DIRECT_FALLBACK_TIMEOUT_MS = 5000, QUOTA_PROBE_OUTER_TIMEOUT_MS = 9000, TOKEN_EXPIRING_SOON_DAYS = 7, AGENT_RETRY_INTERVAL_MS = 1500, AGENT_RETRY_MAX_MS = 12000, AGENT_LIVE_WINDOW_MS = 45000, AGENT_LIVE_POLL_INTERVAL_MS = 2000, AGENT_LIVE_FOLLOWUP_REPOLL_MS = 30000, realProcFs, SCHEDULER_LOCK_PATH_DEFAULT = "/state/agent/scheduler.lock", SCHEDULER_JSONL_PATH_DEFAULT = "/state/agent/scheduler.jsonl", SCHEDULER_FRESH_BOOT_MS = 30000, realSchedulerFs, realSkillsFs;
30289
30360
  var init_boot_probes = __esm(() => {
30290
30361
  init_quota_cache();
30291
30362
  init_quota_check();
30292
- execFile3 = promisify3(execFileCb);
30363
+ execFile = promisify(execFileCb);
30293
30364
  realProcFs = {
30294
30365
  readdir: (p) => readdirSync6(p),
30295
30366
  readFile: (p) => readFileSync30(p, "utf-8")
@@ -30300,16 +30371,16 @@ var init_boot_probes = __esm(() => {
30300
30371
  const { statSync: statSync10 } = __require("fs");
30301
30372
  return statSync10(p).mtimeMs;
30302
30373
  },
30303
- exists: (p) => existsSync33(p)
30374
+ exists: (p) => existsSync32(p)
30304
30375
  };
30305
30376
  realSkillsFs = {
30306
30377
  readdir: (p) => readdirSync6(p),
30307
- exists: (p) => existsSync33(p)
30378
+ exists: (p) => existsSync32(p)
30308
30379
  };
30309
30380
  });
30310
30381
 
30311
30382
  // gateway/boot-issue-cache.ts
30312
- import { existsSync as existsSync34, readFileSync as readFileSync31, writeFileSync as writeFileSync22, mkdirSync as mkdirSync23, renameSync as renameSync11 } from "fs";
30383
+ import { existsSync as existsSync33, readFileSync as readFileSync31, writeFileSync as writeFileSync21, mkdirSync as mkdirSync23, renameSync as renameSync11 } from "fs";
30313
30384
  import { dirname as dirname10 } from "path";
30314
30385
  function fingerprintProbe(key, r) {
30315
30386
  if (r.status === "ok")
@@ -30389,7 +30460,7 @@ function diffProbes(probes, cache, opts = {}) {
30389
30460
  return out;
30390
30461
  }
30391
30462
  function loadCache(path, now = Date.now) {
30392
- if (!existsSync34(path))
30463
+ if (!existsSync33(path))
30393
30464
  return { ...EMPTY_CACHE, probes: {} };
30394
30465
  let raw;
30395
30466
  try {
@@ -30438,7 +30509,7 @@ function applyAndSave(path, cache, diff) {
30438
30509
  try {
30439
30510
  mkdirSync23(dirname10(path), { recursive: true });
30440
30511
  const tmp = `${path}.tmp`;
30441
- writeFileSync22(tmp, JSON.stringify(next), { mode: 384 });
30512
+ writeFileSync21(tmp, JSON.stringify(next), { mode: 384 });
30442
30513
  renameSync11(tmp, path);
30443
30514
  } catch {}
30444
30515
  return next;
@@ -30452,7 +30523,7 @@ var init_boot_issue_cache = __esm(() => {
30452
30523
 
30453
30524
  // gateway/config-snapshot.ts
30454
30525
  import { createHash as createHash2 } from "crypto";
30455
- import { existsSync as existsSync35, readFileSync as readFileSync32, writeFileSync as writeFileSync23, mkdirSync as mkdirSync24, renameSync as renameSync12 } from "fs";
30526
+ import { existsSync as existsSync34, readFileSync as readFileSync32, writeFileSync as writeFileSync22, mkdirSync as mkdirSync24, renameSync as renameSync12 } from "fs";
30456
30527
  import { dirname as dirname11 } from "path";
30457
30528
  function hashStringArray(items) {
30458
30529
  if (!items || items.length === 0)
@@ -30517,7 +30588,7 @@ function renderConfigChangeDim(dim) {
30517
30588
  }
30518
30589
  }
30519
30590
  function loadSnapshot(path, now = Date.now) {
30520
- if (!existsSync35(path))
30591
+ if (!existsSync34(path))
30521
30592
  return null;
30522
30593
  let raw;
30523
30594
  try {
@@ -30553,7 +30624,7 @@ function persistSnapshot(path, snapshot) {
30553
30624
  try {
30554
30625
  mkdirSync24(dirname11(path), { recursive: true });
30555
30626
  const tmp = `${path}.tmp`;
30556
- writeFileSync23(tmp, JSON.stringify(snapshot), { mode: 384 });
30627
+ writeFileSync22(tmp, JSON.stringify(snapshot), { mode: 384 });
30557
30628
  renameSync12(tmp, path);
30558
30629
  } catch {}
30559
30630
  }
@@ -30562,7 +30633,7 @@ var init_config_snapshot = __esm(() => {
30562
30633
  });
30563
30634
 
30564
30635
  // gateway/boot-card-msgid.ts
30565
- import { readFileSync as readFileSync33, writeFileSync as writeFileSync24 } from "node:fs";
30636
+ import { readFileSync as readFileSync33, writeFileSync as writeFileSync23 } from "node:fs";
30566
30637
  function bootCardChatKey(chatId, threadId) {
30567
30638
  return `${chatId}:${threadId ?? ""}`;
30568
30639
  }
@@ -30587,7 +30658,7 @@ function saveBootCardMsgId(path, chatKey3, messageId) {
30587
30658
  if (store2[chatKey3] === messageId)
30588
30659
  return;
30589
30660
  store2[chatKey3] = messageId;
30590
- writeFileSync24(path, JSON.stringify(store2), "utf8");
30661
+ writeFileSync23(path, JSON.stringify(store2), "utf8");
30591
30662
  } catch {}
30592
30663
  }
30593
30664
  var init_boot_card_msgid = () => {};
@@ -31476,7 +31547,7 @@ __export(exports_materialize_bot_token, {
31476
31547
  materializeBotToken: () => materializeBotToken,
31477
31548
  BotTokenMaterializeError: () => BotTokenMaterializeError
31478
31549
  });
31479
- import { existsSync as existsSync43 } from "node:fs";
31550
+ import { existsSync as existsSync42 } from "node:fs";
31480
31551
  function pickConfiguredToken(config, agentName3) {
31481
31552
  if (agentName3) {
31482
31553
  const agent = config.agents?.[agentName3];
@@ -31490,7 +31561,7 @@ function tryDirectVaultRead3(ref, config, passphrase) {
31490
31561
  if (!passphrase)
31491
31562
  return null;
31492
31563
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
31493
- if (!existsSync43(vaultPath))
31564
+ if (!existsSync42(vaultPath))
31494
31565
  return null;
31495
31566
  try {
31496
31567
  const secrets = openVault(passphrase, vaultPath);
@@ -31582,7 +31653,7 @@ __export(exports_tmux, {
31582
31653
  captureAgentPane: () => captureAgentPane
31583
31654
  });
31584
31655
  import { execFileSync as execFileSync5 } from "node:child_process";
31585
- import { mkdirSync as mkdirSync31, readdirSync as readdirSync8, statSync as statSync14, unlinkSync as unlinkSync15, writeFileSync as writeFileSync30 } from "node:fs";
31656
+ import { mkdirSync as mkdirSync31, readdirSync as readdirSync8, statSync as statSync14, unlinkSync as unlinkSync15, writeFileSync as writeFileSync29 } from "node:fs";
31586
31657
  import { resolve as resolve7 } from "node:path";
31587
31658
  function captureAgentPane(opts) {
31588
31659
  const { agentName: agentName3, agentDir, reason } = opts;
@@ -31628,7 +31699,7 @@ function captureAgentPane(opts) {
31628
31699
  ` + `
31629
31700
  `;
31630
31701
  try {
31631
- writeFileSync30(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
31702
+ writeFileSync29(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
31632
31703
  mode: 420
31633
31704
  });
31634
31705
  } catch (err) {
@@ -32074,11 +32145,11 @@ function registerApprovalsCommands(bot, opts) {
32074
32145
  return `\`${d.id.slice(0, 8)}\` ` + `${escapeMarkdown(d.agent_unit)} \u2192 ` + `\`${codeSpanSafe(d.scope)}\` ` + `(${escapeMarkdown(d.action)}, ${ttl}) ` + `\u00b7 /approvals revoke ${escapeMarkdown(d.id)}`;
32075
32146
  }).join(`
32076
32147
  `);
32077
- await ctx.replyWithRichMessage(richMessage(`**Active approvals**
32148
+ await ctx.replyWithRichMessage(richMessage(hardenCardBreaks(`**Active approvals**
32078
32149
 
32079
32150
  ${summary}
32080
32151
 
32081
- ${detail}`));
32152
+ ${detail}`)));
32082
32153
  return;
32083
32154
  }
32084
32155
  if (sub === "revoke") {
@@ -32191,7 +32262,7 @@ import { randomBytes as randomBytes8 } from "crypto";
32191
32262
  import { execFileSync as execFileSync6, execSync as execSync2, spawn } from "child_process";
32192
32263
  import {
32193
32264
  readFileSync as readFileSync41,
32194
- writeFileSync as writeFileSync31,
32265
+ writeFileSync as writeFileSync30,
32195
32266
  mkdirSync as mkdirSync32,
32196
32267
  readdirSync as readdirSync9,
32197
32268
  rmSync as rmSync4,
@@ -32201,7 +32272,7 @@ import {
32201
32272
  chmodSync as chmodSync7,
32202
32273
  openSync as openSync9,
32203
32274
  closeSync as closeSync9,
32204
- existsSync as existsSync44,
32275
+ existsSync as existsSync43,
32205
32276
  unlinkSync as unlinkSync16,
32206
32277
  appendFileSync as appendFileSync6
32207
32278
  } from "fs";
@@ -34776,6 +34847,12 @@ function formatFeedElapsed(ms) {
34776
34847
  const m = Math.floor(s / 60);
34777
34848
  return `${m}m${(s % 60).toString().padStart(2, "0")}s`;
34778
34849
  }
34850
+ var STEP_TIMER_MIN_MS = 1e4;
34851
+ function formatStepSuffix(stepElapsedMs) {
34852
+ if (stepElapsedMs < STEP_TIMER_MIN_MS)
34853
+ return "";
34854
+ return ` \u00b7 ${formatFeedElapsed(stepElapsedMs)}`;
34855
+ }
34779
34856
  function escapeStepLine(raw) {
34780
34857
  const cleaned = stripMarkdown(raw).replace(/\s+/g, " ").trim();
34781
34858
  return escapeMarkdown(truncate(cleaned, STATUS_LINE_MAX));
@@ -34976,9 +35053,10 @@ function createWorkerActivityFeed(opts) {
34976
35053
  const line = view.latestSummary.trim();
34977
35054
  if (line.length === 0)
34978
35055
  return;
34979
- if (h.narrative[h.narrative.length - 1] === line)
35056
+ if (h.narrative.includes(line))
34980
35057
  return;
34981
35058
  h.narrative.push(line);
35059
+ h.stepStartedAtMs = nowFn();
34982
35060
  if (h.narrative.length > STATUS_ROLLING_LINES) {
34983
35061
  h.narrative.splice(0, h.narrative.length - STATUS_ROLLING_LINES);
34984
35062
  }
@@ -35045,7 +35123,7 @@ function createWorkerActivityFeed(opts) {
35045
35123
  function heartbeatTick() {
35046
35124
  const now = nowFn();
35047
35125
  for (const h of handles.values()) {
35048
- if (h.messageId == null)
35126
+ if (!handles.has(h.agentId))
35049
35127
  continue;
35050
35128
  if (h.lastView == null)
35051
35129
  continue;
@@ -35053,13 +35131,23 @@ function createWorkerActivityFeed(opts) {
35053
35131
  continue;
35054
35132
  if (now < h.cooldownUntil)
35055
35133
  continue;
35134
+ const liveElapsed = h.dispatchAtMs != null ? now - h.dispatchAtMs : h.lastView.elapsedMs;
35135
+ if (h.messageId == null) {
35136
+ if (liveElapsed < firstPaintMin)
35137
+ continue;
35138
+ const view2 = { ...h.lastView, elapsedMs: Math.max(h.lastView.elapsedMs, liveElapsed) };
35139
+ h.chain = h.chain.then(() => doUpdate(h, view2)).catch((err) => {
35140
+ log(`worker-feed: heartbeat first-paint chain error ${h.agentId}: ${err.message}`);
35141
+ });
35142
+ continue;
35143
+ }
35056
35144
  if (now - h.lastEditAt < minEditInterval)
35057
35145
  continue;
35058
35146
  const stale = now - h.lastEditAt >= heartbeatTickMs;
35059
35147
  if (!stale)
35060
35148
  continue;
35061
- const liveElapsed = h.dispatchAtMs != null ? now - h.dispatchAtMs : h.lastView.elapsedMs;
35062
- const liveSuffix = " \u00b7 " + formatFeedElapsed(liveElapsed);
35149
+ const stepElapsed = h.stepStartedAtMs != null ? now - h.stepStartedAtMs : liveElapsed;
35150
+ const liveSuffix = formatStepSuffix(stepElapsed);
35063
35151
  const view = { ...h.lastView, elapsedMs: Math.max(h.lastView.elapsedMs, liveElapsed) };
35064
35152
  h.chain = h.chain.then(() => doUpdate(h, view, liveSuffix)).catch((err) => {
35065
35153
  log(`worker-feed: heartbeat chain error ${h.agentId}: ${err.message}`);
@@ -35093,7 +35181,8 @@ function createWorkerActivityFeed(opts) {
35093
35181
  narrative: [],
35094
35182
  chain: Promise.resolve(),
35095
35183
  lastView: null,
35096
- dispatchAtMs: null
35184
+ dispatchAtMs: null,
35185
+ stepStartedAtMs: null
35097
35186
  };
35098
35187
  handles.set(agentId, h);
35099
35188
  }
@@ -35285,8 +35374,8 @@ function renderVaultRequestAccessCard(req) {
35285
35374
  }
35286
35375
  lines.push("");
35287
35376
  lines.push(`_Tap Approve to mint a scoped grant token (same flow as \`switchroom vault grant\`). Tap Deny to refuse \u2014 the agent will receive a denial result._`);
35288
- return lines.join(`
35289
- `);
35377
+ return hardenCardBreaks(lines.join(`
35378
+ `));
35290
35379
  }
35291
35380
 
35292
35381
  // gateway/permission-card-store.ts
@@ -35389,6 +35478,12 @@ function formatFeedElapsed2(ms) {
35389
35478
  const m = Math.floor(s / 60);
35390
35479
  return `${m}m${(s % 60).toString().padStart(2, "0")}s`;
35391
35480
  }
35481
+ var STEP_TIMER_MIN_MS2 = 1e4;
35482
+ function formatStepSuffix2(stepElapsedMs) {
35483
+ if (stepElapsedMs < STEP_TIMER_MIN_MS2)
35484
+ return "";
35485
+ return ` \u00b7 ${formatFeedElapsed2(stepElapsedMs)}`;
35486
+ }
35392
35487
  function escapeStepLine2(raw) {
35393
35488
  const cleaned = stripMarkdown(raw).replace(/\s+/g, " ").trim();
35394
35489
  return escapeMarkdown(truncate(cleaned, STATUS_LINE_MAX));
@@ -35767,9 +35862,6 @@ function chatKey(chatId, threadId) {
35767
35862
  const t = threadId == null || threadId === 0 ? "_" : String(threadId);
35768
35863
  return `${chatId}:${t}`;
35769
35864
  }
35770
- function chatKeyWithSuffix(chatId, threadId, suffix) {
35771
- return `${chatKey(chatId, threadId)}:${suffix}`;
35772
- }
35773
35865
  function chatIdOfChatKey(key) {
35774
35866
  const idx = key.indexOf(":");
35775
35867
  return idx === -1 ? key : key.slice(0, idx);
@@ -36218,185 +36310,6 @@ function handlePtyPartialPure(text, state, deps) {
36218
36310
  stream.update(text).catch(() => {});
36219
36311
  return created ? "update-new" : "update-existing";
36220
36312
  }
36221
- // stream-reply-handler.ts
36222
- function buildAccentHeader(accent) {
36223
- switch (accent) {
36224
- case "in-progress":
36225
- return `\uD83D\uDD35 _In progress\u2026_
36226
-
36227
- `;
36228
- case "done":
36229
- return `\u2705 **Done**
36230
-
36231
- `;
36232
- case "issue":
36233
- return `\u26a0\ufe0f **Issue**
36234
-
36235
- `;
36236
- default:
36237
- return "";
36238
- }
36239
- }
36240
- function streamKey2(chatId, threadId, lane, turnKey) {
36241
- const base = lane != null && lane.length > 0 ? chatKeyWithSuffix(chatId, threadId ?? null, lane) : chatKey(chatId, threadId ?? null);
36242
- return turnKey != null && turnKey.length > 0 ? `${base}:${turnKey}` : base;
36243
- }
36244
- async function handleStreamReply(args, state, deps) {
36245
- const chat_id = args.chat_id;
36246
- let rawText = deps.normalizeParagraphBreaks ? deps.normalizeParagraphBreaks(deps.repairEscapedWhitespace(args.text)) : deps.repairEscapedWhitespace(args.text);
36247
- if (deps.normalizePunctuation)
36248
- rawText = deps.normalizePunctuation(rawText);
36249
- if (deps.stripExcessBold)
36250
- rawText = deps.stripExcessBold(rawText);
36251
- const done = Boolean(args.done);
36252
- const format = args.format ?? deps.defaultFormat;
36253
- if (done) {
36254
- process.stderr.write(`telegram channel: stream_reply: invoked done=true chatId=${chat_id} lane=${args.lane ?? "default"} charCount=${rawText.length}
36255
- `);
36256
- }
36257
- deps.assertAllowedChat(chat_id);
36258
- const threadId = deps.resolveThreadId(chat_id, args.message_thread_id);
36259
- const literalText = format === "text";
36260
- let effectiveText = !literalText && deps.addParagraphSpacers ? deps.addParagraphSpacers(rawText) : rawText;
36261
- if (args.accent != null) {
36262
- const accentHeader = buildAccentHeader(args.accent);
36263
- if (accentHeader.length > 0) {
36264
- effectiveText = accentHeader + effectiveText;
36265
- }
36266
- }
36267
- if (effectiveText.length > RICH_MESSAGE_MAX_CHARS) {
36268
- throw new Error(`stream_reply rejected: text exceeds Telegram's ${RICH_MESSAGE_MAX_CHARS}-char rich-message limit ` + `(length=${effectiveText.length}, format=${format}). stream_reply does not ` + `auto-chunk \u2014 split the text or use \`reply\`, which chunks.`);
36269
- }
36270
- const sKey = streamKey2(chat_id, threadId, args.lane, args.turnKey);
36271
- state.suppressPtyPreview?.add(streamKey2(chat_id, threadId));
36272
- let stream = state.activeDraftStreams.get(sKey);
36273
- const streamExisted = stream != null;
36274
- deps.logStreamingEvent({
36275
- kind: "stream_reply_called",
36276
- chatId: chat_id,
36277
- charCount: effectiveText.length,
36278
- done,
36279
- streamExisted
36280
- });
36281
- if (!stream) {
36282
- let replyToMessageId;
36283
- if (args.reply_to != null) {
36284
- replyToMessageId = Number(args.reply_to);
36285
- } else if (args.quote !== false && deps.getLatestInboundMessageId != null) {
36286
- try {
36287
- const latest = deps.getLatestInboundMessageId(chat_id, threadId ?? null);
36288
- if (latest != null)
36289
- replyToMessageId = latest;
36290
- } catch (err) {
36291
- deps.writeError(`telegram channel: stream_reply quote-lookup failed: ${err}
36292
- `);
36293
- }
36294
- }
36295
- let initialMessageId;
36296
- if (deps.lookupExistingMessageId != null) {
36297
- try {
36298
- const looked = deps.lookupExistingMessageId({
36299
- chatId: chat_id,
36300
- threadId,
36301
- lane: args.lane,
36302
- turnKey: args.turnKey
36303
- });
36304
- if (typeof looked === "number" && Number.isFinite(looked)) {
36305
- initialMessageId = looked;
36306
- }
36307
- } catch (err) {
36308
- deps.writeError(`telegram channel: stream_reply lookupExistingMessageId failed: ${err}
36309
- `);
36310
- }
36311
- }
36312
- stream = createStreamController({
36313
- bot: deps.bot,
36314
- chatId: chat_id,
36315
- threadId,
36316
- literalText,
36317
- disableLinkPreview: deps.disableLinkPreview,
36318
- ...deps.throttleMs != null ? { throttleMs: deps.throttleMs } : {},
36319
- retry: deps.retry,
36320
- ...replyToMessageId != null ? { replyToMessageId } : {},
36321
- ...args.quote_text != null && replyToMessageId != null ? { quoteText: args.quote_text } : {},
36322
- ...args.protect_content === true ? { protectContent: true } : {},
36323
- ...args.disable_notification === true ? { disableNotification: true } : {},
36324
- ...args.reply_markup != null ? { replyMarkup: args.reply_markup } : {},
36325
- isPrivateChat: deps.isPrivateChat === true,
36326
- ...initialMessageId != null ? { initialMessageId } : {},
36327
- onSend: (messageId, charCount) => deps.logStreamingEvent({ kind: "draft_send", chatId: chat_id, messageId, charCount }),
36328
- onEdit: (messageId, charCount) => deps.logStreamingEvent({
36329
- kind: "draft_edit",
36330
- chatId: chat_id,
36331
- messageId,
36332
- charCount,
36333
- sameAsLast: false
36334
- }),
36335
- log: (msg) => {
36336
- if (msg.startsWith("stream \u2192 sent") || msg.startsWith("stream \u2192 edited") || msg.startsWith("stream \u2192 not modified") || msg.startsWith("stream finalized") || msg.startsWith("stream \u2192 materialized"))
36337
- return;
36338
- deps.writeError(`telegram channel: stream_reply ${msg}
36339
- `);
36340
- },
36341
- warn: (msg) => {
36342
- deps.writeError(`telegram channel: stream_reply ${msg}
36343
- `);
36344
- }
36345
- });
36346
- state.activeDraftStreams.set(sKey, stream);
36347
- }
36348
- await stream.update(effectiveText);
36349
- if (done) {
36350
- await stream.finalize();
36351
- state.activeDraftStreams.delete(sKey);
36352
- if (stream.getMessageId() == null) {
36353
- throw new Error(`stream_reply finalized without sending any message (length=${rawText.length}, ` + `max=${RICH_MESSAGE_MAX_CHARS}). Telegram's rich-message limit is ${RICH_MESSAGE_MAX_CHARS} chars and ` + `stream_reply does not auto-chunk. Split the text or use \`reply\` (which chunks).`);
36354
- }
36355
- if (deps.historyEnabled) {
36356
- const finalId = stream.getMessageId();
36357
- if (finalId != null) {
36358
- try {
36359
- deps.recordOutbound({
36360
- chat_id,
36361
- thread_id: threadId ?? null,
36362
- message_ids: [finalId],
36363
- texts: [rawText]
36364
- });
36365
- } catch (err) {
36366
- deps.writeError(`telegram channel: history recordOutbound (stream_reply) failed: ${err}
36367
- `);
36368
- }
36369
- }
36370
- }
36371
- }
36372
- const finalMessageId = stream.getMessageId();
36373
- if (done) {
36374
- process.stderr.write(`telegram channel: stream_reply: finalized done=true chatId=${chat_id} lane=${args.lane ?? "default"} messageId=${finalMessageId ?? "null"}
36375
- `);
36376
- const isDefaultLaneForCompletion = args.lane == null || args.lane.length === 0;
36377
- if (deps.recordOutboundDelivered != null && finalMessageId != null && isDefaultLaneForCompletion) {
36378
- try {
36379
- deps.recordOutboundDelivered(chat_id, threadId);
36380
- } catch (err) {
36381
- deps.writeError(`telegram channel: stream_reply: recordOutboundDelivered hook threw: ${err}
36382
- `);
36383
- }
36384
- }
36385
- if (deps.forceCompleteTurn != null && isDefaultLaneForCompletion) {
36386
- try {
36387
- deps.forceCompleteTurn(chat_id, threadId);
36388
- } catch (err) {
36389
- deps.writeError(`telegram channel: stream_reply: forceCompleteTurn hook threw: ${err}
36390
- `);
36391
- }
36392
- }
36393
- }
36394
- return {
36395
- messageId: finalMessageId,
36396
- status: done ? "finalized" : "updated"
36397
- };
36398
- }
36399
-
36400
36313
  // chat-lock.ts
36401
36314
  function createChatLock() {
36402
36315
  const chains = new Map;
@@ -43677,8 +43590,8 @@ function defaultAddAccount(label, credentials, opts) {
43677
43590
  }
43678
43591
 
43679
43592
  // ../src/auth/broker/client.ts
43680
- import * as net3 from "node:net";
43681
43593
  init_protocol();
43594
+ import * as net3 from "node:net";
43682
43595
  import { homedir as homedir6 } from "node:os";
43683
43596
  import { randomUUID as randomUUID4 } from "node:crypto";
43684
43597
  import { join as join14 } from "node:path";
@@ -43997,13 +43910,13 @@ class AuthBrokerClient2 {
43997
43910
  init_loader();
43998
43911
  init_resolver();
43999
43912
  init_vault();
44000
- import { existsSync as existsSync13 } from "node:fs";
43913
+ import { existsSync as existsSync12 } from "node:fs";
44001
43914
  var DEFAULT_VOICE_API_KEY_REF = "vault:openai/api-key";
44002
43915
  function tryDirectVaultRead(ref, config, passphrase) {
44003
43916
  if (!passphrase)
44004
43917
  return null;
44005
43918
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
44006
- if (!existsSync13(vaultPath))
43919
+ if (!existsSync12(vaultPath))
44007
43920
  return null;
44008
43921
  try {
44009
43922
  const secrets = openVault(passphrase, vaultPath);
@@ -44059,14 +43972,14 @@ async function materializeVoiceKey(opts = {}, logger2 = (line) => process.stderr
44059
43972
  init_loader();
44060
43973
  init_resolver();
44061
43974
  init_vault();
44062
- import { existsSync as existsSync14 } from "node:fs";
43975
+ import { existsSync as existsSync13 } from "node:fs";
44063
43976
  var VOICE_SIDECAR_TOKEN_KEY = "voice/sidecar-token";
44064
43977
  var DEFAULT_VOICE_SIDECAR_TOKEN_REF = `vault:${VOICE_SIDECAR_TOKEN_KEY}`;
44065
43978
  function tryDirectVaultRead2(ref, config, passphrase) {
44066
43979
  if (!passphrase)
44067
43980
  return null;
44068
43981
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
44069
- if (!existsSync14(vaultPath))
43982
+ if (!existsSync13(vaultPath))
44070
43983
  return null;
44071
43984
  try {
44072
43985
  const secrets = openVault(passphrase, vaultPath);
@@ -44119,14 +44032,14 @@ async function materializeSidecarToken(opts = {}, logger2 = (line) => process.st
44119
44032
  }
44120
44033
 
44121
44034
  // ../src/setup/host-capabilities.ts
44122
- import { existsSync as existsSync15, mkdirSync as mkdirSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "node:fs";
44035
+ import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "node:fs";
44123
44036
  init_paths();
44124
44037
  function hostCapabilitiesPath() {
44125
44038
  return resolveStatePath2("host-capabilities.json");
44126
44039
  }
44127
44040
  function loadHostCapabilities() {
44128
44041
  const path = hostCapabilitiesPath();
44129
- if (!existsSync15(path))
44042
+ if (!existsSync14(path))
44130
44043
  return null;
44131
44044
  try {
44132
44045
  const parsed = JSON.parse(readFileSync11(path, "utf-8"));
@@ -44227,7 +44140,7 @@ function resolveExhaustUntil(resetAtMs, now = Date.now()) {
44227
44140
 
44228
44141
  // gateway/auth-add-flow.ts
44229
44142
  import { execFileSync } from "node:child_process";
44230
- import { existsSync as existsSync17, mkdirSync as mkdirSync14, readFileSync as readFileSync13, readdirSync as readdirSync4, rmSync as rmSync3, statSync as statSync7, writeFileSync as writeFileSync11 } from "node:fs";
44143
+ import { existsSync as existsSync16, mkdirSync as mkdirSync14, readFileSync as readFileSync13, readdirSync as readdirSync4, rmSync as rmSync3, statSync as statSync7, writeFileSync as writeFileSync10 } from "node:fs";
44231
44144
  import { homedir as homedir7 } from "node:os";
44232
44145
  import { join as join15 } from "node:path";
44233
44146
  import { randomBytes as randomBytes4 } from "node:crypto";
@@ -44236,8 +44149,8 @@ import { randomBytes as randomBytes4 } from "node:crypto";
44236
44149
  import {
44237
44150
  readFileSync as readFileSync12,
44238
44151
  readdirSync as readdirSync3,
44239
- existsSync as existsSync16,
44240
- writeFileSync as writeFileSync10,
44152
+ existsSync as existsSync15,
44153
+ writeFileSync as writeFileSync9,
44241
44154
  mkdirSync as mkdirSync13,
44242
44155
  mkdtempSync as mkdtempSync2,
44243
44156
  rmSync as rmSync2,
@@ -44263,7 +44176,7 @@ function parseSetupTokenUrl(output) {
44263
44176
  }
44264
44177
  function readTokenFromCredentialsFile(credentialsFilePath) {
44265
44178
  try {
44266
- if (!existsSync16(credentialsFilePath))
44179
+ if (!existsSync15(credentialsFilePath))
44267
44180
  return null;
44268
44181
  const raw = readFileSync12(credentialsFilePath, "utf-8");
44269
44182
  const parsed = JSON.parse(raw);
@@ -44335,7 +44248,7 @@ function cleanScratchDir(scratchDir) {
44335
44248
  var AUTH_TMUX_SESSION_FILE = ".auth-tmux-session";
44336
44249
  function sweepOrphanSessions(home2, tmux, nowMs2 = Date.now()) {
44337
44250
  const inProgressDir = join15(home2, ".switchroom", "accounts", ".in-progress");
44338
- if (!existsSync17(inProgressDir))
44251
+ if (!existsSync16(inProgressDir))
44339
44252
  return;
44340
44253
  let entries;
44341
44254
  try {
@@ -44347,7 +44260,7 @@ function sweepOrphanSessions(home2, tmux, nowMs2 = Date.now()) {
44347
44260
  for (const entry of entries) {
44348
44261
  const dir = join15(inProgressDir, entry);
44349
44262
  const sessionFile = join15(dir, AUTH_TMUX_SESSION_FILE);
44350
- if (!existsSync17(sessionFile))
44263
+ if (!existsSync16(sessionFile))
44351
44264
  continue;
44352
44265
  let fileContents;
44353
44266
  let fileMtime;
@@ -44386,7 +44299,7 @@ async function startAccountAuthSession(label, opts = {}) {
44386
44299
  const tmuxSocket = `switchroom-${agentName3}`;
44387
44300
  const tmuxSession = `auth-add-${label}-${hexSuffix}`.slice(0, 64);
44388
44301
  try {
44389
- writeFileSync11(join15(scratchDir, AUTH_TMUX_SESSION_FILE), `${tmuxSocket}
44302
+ writeFileSync10(join15(scratchDir, AUTH_TMUX_SESSION_FILE), `${tmuxSocket}
44390
44303
  ${tmuxSession}`, "utf8");
44391
44304
  } catch {}
44392
44305
  const sessionEnv = {
@@ -44445,7 +44358,7 @@ async function submitAccountAuthCode(flow, code, opts = {}) {
44445
44358
  const deadline = Date.now() + pollTimeoutMs;
44446
44359
  while (Date.now() < deadline) {
44447
44360
  await new Promise((r) => setTimeout(r, pollIntervalMs));
44448
- if (existsSync17(credentialsPath)) {
44361
+ if (existsSync16(credentialsPath)) {
44449
44362
  const token = readTokenFromCredentialsFile(credentialsPath);
44450
44363
  if (token) {
44451
44364
  try {
@@ -44459,7 +44372,7 @@ async function submitAccountAuthCode(flow, code, opts = {}) {
44459
44372
  }
44460
44373
  }
44461
44374
  if (!tmux.hasSession(flow.tmuxSocket, flow.tmuxSession)) {
44462
- if (!existsSync17(credentialsPath)) {
44375
+ if (!existsSync16(credentialsPath)) {
44463
44376
  cleanScratchDir(flow.scratchDir);
44464
44377
  throw new Error("claude setup-token exited without writing credentials \u2014 the code may be invalid or expired");
44465
44378
  }
@@ -45220,26 +45133,29 @@ function repairEscapedWhitespace(text) {
45220
45133
  return text;
45221
45134
  const nonce = Math.random().toString(36).slice(2);
45222
45135
  const BACKSLASH_PH = `\x00BK${nonce}_`;
45223
- const { masked, restore: restore2 } = maskCodeRegions(text, nonce);
45136
+ const { masked, restore: restore2 } = maskCodeRegions2(text, nonce);
45224
45137
  const unescaped = masked.replace(/\\\\/g, BACKSLASH_PH).replace(/\\n/g, `
45225
45138
  `).replace(/\\r/g, "\r").replace(/\\t/g, "\t").replace(/\\"/g, '"').replace(new RegExp(BACKSLASH_PH.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), "\\");
45226
45139
  return restore2(unescaped);
45227
45140
  }
45228
- function maskCodeRegions(text, nonce) {
45229
- const CODE_MASK_PH = `\x00RM${nonce}_`;
45141
+ function maskCodeRegions2(text, nonce) {
45142
+ const FENCE_MASK_PH = `\x00RMF${nonce}_`;
45143
+ const INLINE_MASK_PH = `\x00RMI${nonce}_`;
45230
45144
  const codeMasks = [];
45231
45145
  const masked = text.replace(/```[\s\S]*?```/g, (m) => {
45232
45146
  const idx = codeMasks.length;
45233
45147
  codeMasks.push(m);
45234
- return `${CODE_MASK_PH}${idx}\x00`;
45148
+ return `${FENCE_MASK_PH}${idx}\x00`;
45235
45149
  }).replace(/`[^`\n]+`/g, (m) => {
45236
45150
  const idx = codeMasks.length;
45237
45151
  codeMasks.push(m);
45238
- return `${CODE_MASK_PH}${idx}\x00`;
45152
+ return `${INLINE_MASK_PH}${idx}\x00`;
45239
45153
  });
45240
- const restoreRe = new RegExp(`${CODE_MASK_PH.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(\\d+)\x00`, "g");
45241
- const restore2 = (s) => s.replace(restoreRe, (_m, idx) => codeMasks[Number(idx)] ?? _m);
45242
- return { masked, restore: restore2, placeholder: CODE_MASK_PH };
45154
+ const escNonce = nonce.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45155
+ const anyMaskRe = new RegExp(`\x00RM[FI]${escNonce}_(\\d+)\x00`, "g");
45156
+ const restore2 = (s) => s.replace(anyMaskRe, (_m, idx) => codeMasks[Number(idx)] ?? _m);
45157
+ const stripPlaceholders = (s) => s.replace(anyMaskRe, "");
45158
+ return { masked, restore: restore2, placeholder: FENCE_MASK_PH, stripPlaceholders };
45243
45159
  }
45244
45160
  function splitCollapsedInlineBullets(text) {
45245
45161
  if (!/[\u2022\u00b7]/.test(text))
@@ -45261,7 +45177,7 @@ function normalizeParagraphBreaks(text) {
45261
45177
  `) && !/[\u2022\u00b7]/.test(text))
45262
45178
  return text;
45263
45179
  const nonce = Math.random().toString(36).slice(2);
45264
- const { masked: maskedRaw, restore: restore2, placeholder } = maskCodeRegions(text, nonce);
45180
+ const { masked: maskedRaw, restore: restore2, placeholder } = maskCodeRegions2(text, nonce);
45265
45181
  const masked = splitCollapsedInlineBullets(maskedRaw);
45266
45182
  let out = masked.replace(/\n[ \t\r]+\n(?:[ \t\r]*\n)*/g, `
45267
45183
 
@@ -45290,6 +45206,36 @@ function normalizeParagraphBreaks(text) {
45290
45206
  out = ensureBlockBoundaries(out, placeholder);
45291
45207
  return restore2(out);
45292
45208
  }
45209
+ function hardenCardBreaks2(text) {
45210
+ if (!text.includes(`
45211
+ `))
45212
+ return text;
45213
+ const nonce = Math.random().toString(36).slice(2);
45214
+ const { masked, restore: restore2, placeholder } = maskCodeRegions2(text, nonce);
45215
+ const out = masked.replace(/\n{3,}/g, `
45216
+
45217
+ `);
45218
+ const isBlockConstructLine = (line) => isListItemLine2(line) || isTableRowLine2(line) || isTableDelimiterLine2(line) || isBlockquoteLine2(line) || isHeadingLine2(line) || isFenceOpenLine2(line, placeholder);
45219
+ const lines = out.split(`
45220
+ `);
45221
+ const pieces = [];
45222
+ for (let i = 0;i < lines.length; i++) {
45223
+ let line = lines[i];
45224
+ const isLast = i === lines.length - 1;
45225
+ const next = isLast ? "" : lines[i + 1];
45226
+ const promote = !isLast && line.trim() !== "" && next.trim() !== "" && !isBlockConstructLine(line) && !isBlockConstructLine(next);
45227
+ if (promote) {
45228
+ line = line.replace(/[ \t\r]+$/, "");
45229
+ }
45230
+ pieces.push(line);
45231
+ if (isLast)
45232
+ break;
45233
+ pieces.push(promote ? `
45234
+ ` : `
45235
+ `);
45236
+ }
45237
+ return restore2(pieces.join(""));
45238
+ }
45293
45239
  var PARAGRAPH_SPACER = "\u00a0";
45294
45240
  function addParagraphSpacers(text) {
45295
45241
  if (!text.includes(`
@@ -45297,7 +45243,7 @@ function addParagraphSpacers(text) {
45297
45243
  `))
45298
45244
  return text;
45299
45245
  const nonce = Math.random().toString(36).slice(2);
45300
- const { masked, restore: restore2, placeholder } = maskCodeRegions(text, nonce);
45246
+ const { masked, restore: restore2, placeholder } = maskCodeRegions2(text, nonce);
45301
45247
  if (!masked.includes(`
45302
45248
 
45303
45249
  `))
@@ -45308,15 +45254,15 @@ function addParagraphSpacers(text) {
45308
45254
  const blockKind = (line) => {
45309
45255
  if (asciiTrim(line) === spacerLine)
45310
45256
  return "spacer";
45311
- if (isFenceOpenLine(line, placeholder))
45257
+ if (isFenceOpenLine2(line, placeholder))
45312
45258
  return "fence";
45313
- if (isListItemLine(line))
45259
+ if (isListItemLine2(line))
45314
45260
  return "list";
45315
- if (isTableRowLine(line) || isTableDelimiterLine(line))
45261
+ if (isTableRowLine2(line) || isTableDelimiterLine2(line))
45316
45262
  return "table";
45317
- if (isBlockquoteLine(line))
45263
+ if (isBlockquoteLine2(line))
45318
45264
  return "quote";
45319
- if (isHeadingLine(line))
45265
+ if (isHeadingLine2(line))
45320
45266
  return "heading";
45321
45267
  if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line.trimStart()))
45322
45268
  return "divider";
@@ -45382,7 +45328,7 @@ function normalizePunctuation(text) {
45382
45328
  if (!/[\u2014\u2013\u2022\u00b7]/.test(text))
45383
45329
  return text;
45384
45330
  const nonce = Math.random().toString(36).slice(2);
45385
- const { masked, restore: restore2 } = maskCodeRegions(text, nonce);
45331
+ const { masked, restore: restore2 } = maskCodeRegions2(text, nonce);
45386
45332
  let out = masked.replace(/(\S)[ \t][\u2014\u2013][ \t](?=(\S))/g, (_m, a, b) => /\d/.test(a) && /\d/.test(b) ? `${a}-` : `${a}, `).replace(/(\w)\u2014(?=(\w))/g, (_m, a, b) => /\d/.test(a) && /\d/.test(b) ? `${a}-` : `${a}, `).replace(/(\w)\u2013(?=\w)/g, "$1-");
45387
45333
  out = out.split(`
45388
45334
  `).map((line) => line.replace(/^([ \t]*)[\u2022\u00b7][ \t]+/, "$1- ")).join(`
@@ -45402,9 +45348,8 @@ function stripExcessBold(text) {
45402
45348
  if (!text.includes("**"))
45403
45349
  return text;
45404
45350
  const nonce = Math.random().toString(36).slice(2);
45405
- const { masked, restore: restore2, placeholder } = maskCodeRegions(text, nonce);
45406
- const placeholderRe = new RegExp(`${placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\d+\x00`, "g");
45407
- const visible = masked.replace(placeholderRe, "");
45351
+ const { masked, restore: restore2, placeholder, stripPlaceholders } = maskCodeRegions2(text, nonce);
45352
+ const visible = stripPlaceholders(masked);
45408
45353
  if (visible.length < 100)
45409
45354
  return restore2(masked);
45410
45355
  let boldChars = 0;
@@ -45419,7 +45364,7 @@ function stripExcessBold(text) {
45419
45364
  `).filter((l) => l.trim() !== "");
45420
45365
  if (lines.length === 0 || !block.includes("**"))
45421
45366
  return block;
45422
- const listLines = lines.filter((l) => isListItemLine(l));
45367
+ const listLines = lines.filter((l) => isListItemLine2(l));
45423
45368
  if (listLines.length >= 2 && listLines.length === lines.length) {
45424
45369
  if (lines.every((l) => isFullyBolded(listItemContent(l))))
45425
45370
  return unbold(block);
@@ -45442,26 +45387,26 @@ function stripExcessBold(text) {
45442
45387
  `) + rebuilt[i];
45443
45388
  return restore2(out);
45444
45389
  }
45445
- function isListItemLine(line) {
45390
+ function isListItemLine2(line) {
45446
45391
  const t = line.trimStart();
45447
45392
  return /^[-*+]\s/.test(t) || /^\d+[.)]\s/.test(t);
45448
45393
  }
45449
- function isTableRowLine(line) {
45394
+ function isTableRowLine2(line) {
45450
45395
  return /^\s*\|/.test(line);
45451
45396
  }
45452
- function isTableDelimiterLine(line) {
45397
+ function isTableDelimiterLine2(line) {
45453
45398
  return /^\s*\|?\s*:?-{1,}:?\s*(\|\s*:?-{1,}:?\s*)*\|?\s*$/.test(line);
45454
45399
  }
45455
- function isFenceOpenLine(line, placeholder) {
45400
+ function isFenceOpenLine2(line, placeholder) {
45456
45401
  const t = line.trimStart();
45457
45402
  if (placeholder != null && placeholder.length > 0 && t.startsWith(placeholder))
45458
45403
  return true;
45459
45404
  return t.startsWith("```");
45460
45405
  }
45461
- function isBlockquoteLine(line) {
45406
+ function isBlockquoteLine2(line) {
45462
45407
  return line.trimStart().startsWith(">");
45463
45408
  }
45464
- function isHeadingLine(line) {
45409
+ function isHeadingLine2(line) {
45465
45410
  return /^#{1,6}\s/.test(line.trimStart());
45466
45411
  }
45467
45412
  function ensureBlockBoundaries(text, placeholder) {
@@ -45478,24 +45423,24 @@ function ensureBlockBoundaries(text, placeholder) {
45478
45423
  const curBlank = line.trim() === "";
45479
45424
  if (prevNonBlank && !curBlank) {
45480
45425
  const next = i + 1 < lines.length ? lines[i + 1] : "";
45481
- const prevIsTable = isTableRowLine(prev);
45482
- const startsTableHere = !prevIsTable && (line.includes("|") && isTableDelimiterLine(next) || isTableRowLine(line) && isTableDelimiterLine(next));
45483
- const startsFence = isFenceOpenLine(line, placeholder) && !isFenceOpenLine(prev, placeholder);
45484
- const startsQuote = isBlockquoteLine(line) && !isBlockquoteLine(prev);
45485
- const startsHeading = isHeadingLine(line) && !isHeadingLine(prev);
45486
- const startsList = isListItemLine(line) && !isListItemLine(prev);
45426
+ const prevIsTable = isTableRowLine2(prev);
45427
+ const startsTableHere = !prevIsTable && (line.includes("|") && isTableDelimiterLine2(next) || isTableRowLine2(line) && isTableDelimiterLine2(next));
45428
+ const startsFence = isFenceOpenLine2(line, placeholder) && !isFenceOpenLine2(prev, placeholder);
45429
+ const startsQuote = isBlockquoteLine2(line) && !isBlockquoteLine2(prev);
45430
+ const startsHeading = isHeadingLine2(line) && !isHeadingLine2(prev);
45431
+ const startsList = isListItemLine2(line) && !isListItemLine2(prev);
45487
45432
  if (startsTableHere || startsFence || startsQuote || startsHeading || startsList) {
45488
45433
  result.push("");
45489
45434
  }
45490
45435
  }
45491
- if (prevNonBlank && !curBlank && isFenceOpenLine(prev, placeholder)) {
45436
+ if (prevNonBlank && !curBlank && isFenceOpenLine2(prev, placeholder)) {
45492
45437
  const alreadySeparated = result.length > 0 && result[result.length - 1].trim() === "";
45493
- const curIsBlockStart = isFenceOpenLine(line, placeholder) || isBlockquoteLine(line) || isHeadingLine(line) || isTableRowLine(line) || isTableDelimiterLine(line);
45438
+ const curIsBlockStart = isFenceOpenLine2(line, placeholder) || isBlockquoteLine2(line) || isHeadingLine2(line) || isTableRowLine2(line) || isTableDelimiterLine2(line);
45494
45439
  if (!alreadySeparated && !curIsBlockStart) {
45495
45440
  result.push("");
45496
45441
  }
45497
45442
  }
45498
- if (prevNonBlank && !curBlank && isListItemLine(prev) && !isListItemLine(line)) {
45443
+ if (prevNonBlank && !curBlank && isListItemLine2(prev) && !isListItemLine2(line)) {
45499
45444
  const isIndentedContinuation = /^(\t| {4,})\S/.test(line);
45500
45445
  const alreadySeparated = result.length > 0 && result[result.length - 1].trim() === "";
45501
45446
  if (!isIndentedContinuation && !alreadySeparated) {
@@ -45513,7 +45458,7 @@ function isMarkerLine(line, placeholder) {
45513
45458
  const t = line.trimStart();
45514
45459
  if (placeholder != null && placeholder.length > 0 && t.startsWith(placeholder))
45515
45460
  return true;
45516
- return /^[-*+]\s/.test(t) || /^\d+[.)]\s/.test(t) || t.startsWith(">") || /^#{1,6}\s/.test(t) || isTableRowLine(line) || isTableDelimiterLine(line) || t.startsWith("```") || /^(-{3,}|\*{3,}|_{3,})\s*$/.test(t);
45461
+ return /^[-*+]\s/.test(t) || /^\d+[.)]\s/.test(t) || t.startsWith(">") || /^#{1,6}\s/.test(t) || isTableRowLine2(line) || isTableDelimiterLine2(line) || t.startsWith("```") || /^(-{3,}|\*{3,}|_{3,})\s*$/.test(t);
45517
45462
  }
45518
45463
  function shouldPromoteBreak(prev, next, placeholder) {
45519
45464
  if (isMarkerLine(prev, placeholder) || isMarkerLine(next, placeholder))
@@ -45944,7 +45889,7 @@ function formatAgentLine(meta) {
45944
45889
  function startText(agentName3, dmDisabled) {
45945
45890
  if (dmDisabled)
45946
45891
  return "This bot isn't accepting new connections.";
45947
- return [
45892
+ return stackCardLines([
45948
45893
  `**Switchroom** \u2014 Telegram on your Claude Pro or Max subscription.`,
45949
45894
  ``,
45950
45895
  `This bot is the **${escapeHtml(agentName3)}** agent. Pair first, then send messages here and they reach the agent; replies and reactions come back.`,
@@ -45954,11 +45899,10 @@ function startText(agentName3, dmDisabled) {
45954
45899
  `2. In Claude Code: \`/telegram:access pair <code>\``,
45955
45900
  ``,
45956
45901
  `After pairing, try \`/status\` or \`/commands\`.`
45957
- ].join(`
45958
- `);
45902
+ ]);
45959
45903
  }
45960
45904
  function helpText(agentName3) {
45961
- return [
45905
+ return stackCardLines([
45962
45906
  `**Switchroom** \u2014 your Pro/Max subscription, wired to Telegram.`,
45963
45907
  ``,
45964
45908
  `This bot is the **${escapeHtml(agentName3)}** agent. Text and photos route through to it; replies, reactions and progress cards come back.`,
@@ -45969,8 +45913,7 @@ function helpText(agentName3) {
45969
45913
  `\`/status\` \u2014 agent, model, auth`,
45970
45914
  `\`/vault audit <agent>\` \u2014 admin: review agent's vault access + one-tap [\uD83D\uDD13 Allow] on recent denials`,
45971
45915
  `\`/commands\` \u2014 full command list`
45972
- ].join(`
45973
- `);
45916
+ ]);
45974
45917
  }
45975
45918
  var STATUS_DOT = {
45976
45919
  ok: "\uD83D\uDFE2",
@@ -46016,13 +45959,14 @@ function statusPairedText(params) {
46016
45959
  if (audit.memoryBank)
46017
45960
  lines.push(`**Memory** ${escapeHtml(audit.memoryBank)}`);
46018
45961
  }
46019
- return lines.join(`
46020
- `);
45962
+ return stackCardLines(lines);
46021
45963
  }
46022
45964
  function statusPendingText(code) {
46023
- return `Pending pairing \u2014 run in Claude Code:
46024
-
46025
- \`/telegram:access pair ${code}\``;
45965
+ return stackCardLines([
45966
+ `Pending pairing \u2014 run in Claude Code:`,
45967
+ ``,
45968
+ `\`/telegram:access pair ${code}\``
45969
+ ]);
46026
45970
  }
46027
45971
  function statusUnpairedText() {
46028
45972
  return "Not paired. Send me a message to get a pairing code.";
@@ -46053,7 +45997,7 @@ var TELEGRAM_MENU_COMMANDS = [
46053
45997
  var TELEGRAM_BASE_COMMANDS = TELEGRAM_MENU_COMMANDS.slice(0, 3);
46054
45998
  var TELEGRAM_SWITCHROOM_COMMANDS = TELEGRAM_MENU_COMMANDS.slice(3);
46055
45999
  function switchroomHelpText(agentName3) {
46056
- return [
46000
+ return stackCardLines([
46057
46001
  `**Switchroom bot** \u2014 commands for the **${escapeHtml(agentName3)}** agent.`,
46058
46002
  ``,
46059
46003
  `**Session & approvals**`,
@@ -46103,8 +46047,7 @@ function switchroomHelpText(agentName3) {
46103
46047
  `\`/commands\` \u2014 this help`,
46104
46048
  ``,
46105
46049
  `_Tip: \`/update\` shows the plan; \`/update apply\` executes it; \`/restart\` bounces a stuck agent; \`/version\` checks what's running._`
46106
- ].join(`
46107
- `);
46050
+ ]);
46108
46051
  }
46109
46052
  function restartAckText(agentName3) {
46110
46053
  return `\uD83D\uDD04 Restarting **${escapeHtml(agentName3)}**\u2026`;
@@ -46228,6 +46171,7 @@ function decideTurnFlush(input) {
46228
46171
  if (input.chatId == null)
46229
46172
  return { kind: "skip", reason: "no-inbound-chat" };
46230
46173
  const joined = input.capturedText.join(`
46174
+
46231
46175
  `).trim();
46232
46176
  if (joined.length === 0)
46233
46177
  return { kind: "skip", reason: "empty-text" };
@@ -46259,7 +46203,7 @@ function resolveAgentDirFromEnv() {
46259
46203
  }
46260
46204
 
46261
46205
  // active-reactions.ts
46262
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, renameSync as renameSync5, existsSync as existsSync20, unlinkSync as unlinkSync5 } from "node:fs";
46206
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, renameSync as renameSync5, existsSync as existsSync19, unlinkSync as unlinkSync5 } from "node:fs";
46263
46207
  import { join as join18 } from "node:path";
46264
46208
  var ACTIVE_REACTIONS_FILENAME = ".active-reactions.json";
46265
46209
  function reactionsPath(agentDir) {
@@ -46267,7 +46211,7 @@ function reactionsPath(agentDir) {
46267
46211
  }
46268
46212
  function readActiveReactions(agentDir) {
46269
46213
  const p = reactionsPath(agentDir);
46270
- if (!existsSync20(p))
46214
+ if (!existsSync19(p))
46271
46215
  return [];
46272
46216
  let raw;
46273
46217
  try {
@@ -46303,7 +46247,7 @@ function writeActiveReactions(agentDir, reactions) {
46303
46247
  }
46304
46248
  const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
46305
46249
  try {
46306
- writeFileSync12(tmp, JSON.stringify(reactions) + `
46250
+ writeFileSync11(tmp, JSON.stringify(reactions) + `
46307
46251
  `, "utf-8");
46308
46252
  renameSync5(tmp, p);
46309
46253
  } catch {}
@@ -46327,7 +46271,7 @@ function clearActiveReactions(agentDir) {
46327
46271
  }
46328
46272
 
46329
46273
  // active-reactions.ts
46330
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync13, renameSync as renameSync6, existsSync as existsSync21, unlinkSync as unlinkSync6 } from "node:fs";
46274
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, renameSync as renameSync6, existsSync as existsSync20, unlinkSync as unlinkSync6 } from "node:fs";
46331
46275
  import { join as join19 } from "node:path";
46332
46276
  var ACTIVE_REACTIONS_FILENAME2 = ".active-reactions.json";
46333
46277
  function reactionsPath2(agentDir) {
@@ -46335,7 +46279,7 @@ function reactionsPath2(agentDir) {
46335
46279
  }
46336
46280
  function readActiveReactions2(agentDir) {
46337
46281
  const p = reactionsPath2(agentDir);
46338
- if (!existsSync21(p))
46282
+ if (!existsSync20(p))
46339
46283
  return [];
46340
46284
  let raw;
46341
46285
  try {
@@ -46399,6 +46343,7 @@ function flushOnAgentDisconnect(deps) {
46399
46343
  activeReactionMsgIds,
46400
46344
  activeTurnStartedAt,
46401
46345
  claudeBusyKeys,
46346
+ claudeBusyKeySince,
46402
46347
  activeDraftStreams,
46403
46348
  clearActiveReactions: clearActiveReactions3,
46404
46349
  disposeProgressDriver,
@@ -46415,6 +46360,7 @@ function flushOnAgentDisconnect(deps) {
46415
46360
  activeReactionMsgIds.delete(key);
46416
46361
  activeTurnStartedAt.delete(key);
46417
46362
  claudeBusyKeys.delete(key);
46363
+ claudeBusyKeySince.delete(key);
46418
46364
  }
46419
46365
  clearActiveReactions3();
46420
46366
  const danglingKeys = [...activeTurnStartedAt.keys()];
@@ -46423,6 +46369,7 @@ function flushOnAgentDisconnect(deps) {
46423
46369
  activeTurnStartedAt.delete(k);
46424
46370
  activeReactionMsgIds.delete(k);
46425
46371
  claudeBusyKeys.delete(k);
46372
+ claudeBusyKeySince.delete(k);
46426
46373
  }
46427
46374
  log(`telegram gateway: disconnect-flush swept ${danglingKeys.length} dangling turn key(s) ` + `post-bridge-death (controller loop missed \u2014 finalize raced disconnect)`);
46428
46375
  onDanglingTurnsSwept?.(danglingKeys);
@@ -46431,6 +46378,8 @@ function flushOnAgentDisconnect(deps) {
46431
46378
  const orphanCount = claudeBusyKeys.size;
46432
46379
  const orphanKeys = [...claudeBusyKeys];
46433
46380
  claudeBusyKeys.clear();
46381
+ for (const k of orphanKeys)
46382
+ claudeBusyKeySince.delete(k);
46434
46383
  log(`telegram gateway: disconnect-flush cleared ${orphanCount} orphan claudeBusyKeys ` + `entr${orphanCount === 1 ? "y" : "ies"} (synthetic-inbound deliveries that never turn_ended)` + ` keys=${orphanKeys.join(",")}`);
46435
46384
  }
46436
46385
  disposeProgressDriver();
@@ -46442,6 +46391,33 @@ function flushOnAgentDisconnect(deps) {
46442
46391
  return true;
46443
46392
  }
46444
46393
 
46394
+ // gateway/busy-key-reaper.ts
46395
+ function markBusyKeyLockstep(keys, since, key, now) {
46396
+ const wasBusy = keys.has(key);
46397
+ keys.add(key);
46398
+ if (!wasBusy)
46399
+ since.set(key, now);
46400
+ }
46401
+ function reapOrphanBusyKeys(keys, since, now, opts) {
46402
+ const reaped = [];
46403
+ for (const key of [...since.keys()]) {
46404
+ if (!keys.has(key)) {
46405
+ since.delete(key);
46406
+ continue;
46407
+ }
46408
+ if (opts.hasPendingDelivery(key))
46409
+ continue;
46410
+ const stampedAt = since.get(key) ?? now;
46411
+ if (now - stampedAt >= opts.ttlMs) {
46412
+ keys.delete(key);
46413
+ since.delete(key);
46414
+ reaped.push(key);
46415
+ opts.log?.(`telegram gateway: reaped orphan busy key=${key} (no turn in flight, ` + `no pending delivery, >${opts.ttlMs}ms) \u2014 unwedging idle-drain`);
46416
+ }
46417
+ }
46418
+ return reaped;
46419
+ }
46420
+
46445
46421
  // gateway/preamble-suppressor.ts
46446
46422
  var DEFAULT_BUFFER_MS = 150;
46447
46423
 
@@ -47214,14 +47190,14 @@ async function approvalRecord(args, opts) {
47214
47190
  }
47215
47191
 
47216
47192
  // quota-check.ts
47217
- import { readFileSync as readFileSync17, existsSync as existsSync22 } from "fs";
47193
+ import { readFileSync as readFileSync17, existsSync as existsSync21 } from "fs";
47218
47194
  import { join as join20 } from "path";
47219
47195
  var OAUTH_BETA2 = "oauth-2025-04-20";
47220
47196
  var DEFAULT_USER_AGENT2 = "claude-cli/1.0.0 (external, cli)";
47221
47197
  var DEFAULT_PROBE_MODEL2 = "claude-haiku-4-5-20251001";
47222
47198
  function readOauthToken2(claudeConfigDir) {
47223
47199
  const tokenFile = join20(claudeConfigDir, ".oauth-token");
47224
- if (!existsSync22(tokenFile))
47200
+ if (!existsSync21(tokenFile))
47225
47201
  return null;
47226
47202
  try {
47227
47203
  const raw = readFileSync17(tokenFile, "utf-8").trim();
@@ -47401,8 +47377,7 @@ init_loader();
47401
47377
  var TOKEN_VALIDITY_MS2 = 365 * 24 * 60 * 60000;
47402
47378
 
47403
47379
  // ../src/agents/inject.ts
47404
- import { execFile, execFileSync as execFileSync2 } from "node:child_process";
47405
- import { promisify } from "node:util";
47380
+ import { execFileSync as execFileSync2 } from "node:child_process";
47406
47381
 
47407
47382
  // ../src/agents/pane-lock.ts
47408
47383
  var tails = new Map;
@@ -47419,7 +47394,6 @@ function withPaneLock(key, fn) {
47419
47394
  }
47420
47395
 
47421
47396
  // ../src/agents/inject.ts
47422
- var execFileAsync = promisify(execFile);
47423
47397
  var INJECT_COMMANDS = new Map([
47424
47398
  ["/cost", { description: "Show session cost", expectsOutput: true, dialog: true }],
47425
47399
  ["/status", { description: "Show session status", expectsOutput: true, dialog: true }],
@@ -47715,9 +47689,7 @@ async function injectSlashCommandWith(runner, args) {
47715
47689
  }
47716
47690
 
47717
47691
  // ../src/agents/inject.ts
47718
- import { execFile as execFile2, execFileSync as execFileSync3 } from "node:child_process";
47719
- import { promisify as promisify2 } from "node:util";
47720
- var execFileAsync2 = promisify2(execFile2);
47692
+ import { execFileSync as execFileSync3 } from "node:child_process";
47721
47693
  var INJECT_COMMANDS2 = new Map([
47722
47694
  ["/cost", { description: "Show session cost", expectsOutput: true, dialog: true }],
47723
47695
  ["/status", { description: "Show session status", expectsOutput: true, dialog: true }],
@@ -47785,7 +47757,7 @@ function makeTmuxRunner2(tmuxBin) {
47785
47757
  };
47786
47758
  }
47787
47759
  // stream-reply-handler.ts
47788
- function buildAccentHeader2(accent) {
47760
+ function buildAccentHeader(accent) {
47789
47761
  switch (accent) {
47790
47762
  case "in-progress":
47791
47763
  return `\uD83D\uDD35 _In progress\u2026_
@@ -47909,7 +47881,7 @@ Allowed: \`${deps.escapeHtml(allow)}\``, { html: true });
47909
47881
  }
47910
47882
  }
47911
47883
  const { body, accent } = shapeReply(result, slashCommand, deps);
47912
- const finalText = buildAccentHeader2(accent) + body;
47884
+ const finalText = buildAccentHeader(accent) + body;
47913
47885
  await deps.reply(ctx, finalText, { html: true, accent });
47914
47886
  }
47915
47887
 
@@ -48023,11 +47995,22 @@ async function handleModelCommand(parsed, deps) {
48023
47995
  };
48024
47996
  }
48025
47997
  if (result.outcome === "ok") {
47998
+ const confirmation = modelSwitchConfirmationLine(result.output);
47999
+ if (confirmation) {
48000
+ return {
48001
+ text: [
48002
+ `${verbHtml}`,
48003
+ deps.preBlock(confirmation),
48004
+ ...result.truncated ? ["_truncated_"] : [],
48005
+ PERSIST_NOTE
48006
+ ].join(`
48007
+ `),
48008
+ html: true
48009
+ };
48010
+ }
48026
48011
  return {
48027
48012
  text: [
48028
- `${verbHtml}`,
48029
- deps.preBlock(result.output),
48030
- ...result.truncated ? ["_truncated_"] : [],
48013
+ `${verbHtml} \u2014 switched (session).`,
48031
48014
  PERSIST_NOTE
48032
48015
  ].join(`
48033
48016
  `),
@@ -48321,6 +48304,12 @@ async function handleModelMenuCallback(data, deps) {
48321
48304
  function isSrToClaudeTransition(prevModel, nextModel) {
48322
48305
  return !!prevModel?.startsWith("sr-") && !nextModel.startsWith("sr-");
48323
48306
  }
48307
+ function modelSwitchConfirmationLine(output) {
48308
+ const line = output.split(`
48309
+ `).map((l) => l.trim()).find((l) => MODEL_SWITCH_CONFIRMATION_PREFIX.test(l));
48310
+ return line && line.length > 0 ? line : null;
48311
+ }
48312
+ var MODEL_SWITCH_CONFIRMATION_PREFIX = /^\s*[\u23fa\u25cf\u2022>-]?\s*(?:Set model to|Switched to|Kept model as)\b/i;
48324
48313
  function sessionModelFromConfirmation(confirmation) {
48325
48314
  const m = /(?:Set model to|Switched to)\s+(.+?)(?:\s+for (?:this|the) session|\s*\(|\s*$)/i.exec(confirmation.trim());
48326
48315
  const name = m?.[1]?.trim();
@@ -48781,7 +48770,7 @@ init_schema();
48781
48770
  init_paths();
48782
48771
  init_overlay_loader();
48783
48772
  init_merge();
48784
- import { readFileSync as readFileSync18, existsSync as existsSync23 } from "node:fs";
48773
+ import { readFileSync as readFileSync18, existsSync as existsSync22 } from "node:fs";
48785
48774
  import { homedir as homedir8 } from "node:os";
48786
48775
  import { resolve as resolve5 } from "node:path";
48787
48776
 
@@ -48857,7 +48846,7 @@ function findConfigFile2(startDir) {
48857
48846
  resolve5(userDir, "clerk.yml")
48858
48847
  ].filter(Boolean);
48859
48848
  for (const path of searchPaths) {
48860
- if (existsSync23(path)) {
48849
+ if (existsSync22(path)) {
48861
48850
  return path;
48862
48851
  }
48863
48852
  }
@@ -48865,7 +48854,7 @@ function findConfigFile2(startDir) {
48865
48854
  }
48866
48855
  function loadConfig2(configPath) {
48867
48856
  const filePath = configPath ?? findConfigFile2();
48868
- if (!existsSync23(filePath)) {
48857
+ if (!existsSync22(filePath)) {
48869
48858
  throw new ConfigError2(`Config file not found: ${filePath}`);
48870
48859
  }
48871
48860
  let raw;
@@ -49363,9 +49352,9 @@ function topicForRecipient(args) {
49363
49352
  }
49364
49353
 
49365
49354
  // ../src/agents/perf.ts
49366
- import { existsSync as existsSync24, readFileSync as readFileSync19 } from "node:fs";
49355
+ import { existsSync as existsSync23, readFileSync as readFileSync19 } from "node:fs";
49367
49356
  function readTurnUsages(jsonlPath, lastN) {
49368
- if (!existsSync24(jsonlPath))
49357
+ if (!existsSync23(jsonlPath))
49369
49358
  return [];
49370
49359
  if (lastN <= 0)
49371
49360
  return [];
@@ -49462,7 +49451,7 @@ function numField(obj, key) {
49462
49451
  }
49463
49452
 
49464
49453
  // gateway/context-occupancy.ts
49465
- import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync14 } from "node:fs";
49454
+ import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync13 } from "node:fs";
49466
49455
  import { join as join21 } from "node:path";
49467
49456
  var CONTEXT_OCCUPANCY_FILENAME = "context-occupancy.json";
49468
49457
  var TIGHT_FRACTION = 0.8;
@@ -49488,7 +49477,7 @@ function writeContextOccupancySnapshot(stateDir, snapshot, deps) {
49488
49477
  try {
49489
49478
  const path = join21(stateDir, CONTEXT_OCCUPANCY_FILENAME);
49490
49479
  (deps?.mkdir ?? ((p, o) => mkdirSync16(p, o)))(stateDir, { recursive: true });
49491
- (deps?.writeFile ?? ((p, d) => writeFileSync14(p, d)))(path, JSON.stringify(snapshot, null, 2) + `
49480
+ (deps?.writeFile ?? ((p, d) => writeFileSync13(p, d)))(path, JSON.stringify(snapshot, null, 2) + `
49492
49481
  `);
49493
49482
  } catch {}
49494
49483
  }
@@ -49571,7 +49560,7 @@ function nextCompactNotify(state3, ev) {
49571
49560
  }
49572
49561
 
49573
49562
  // gateway/hostd-dispatch.ts
49574
- import { existsSync as existsSync25 } from "node:fs";
49563
+ import { existsSync as existsSync24 } from "node:fs";
49575
49564
  import { randomBytes as randomBytes5 } from "node:crypto";
49576
49565
 
49577
49566
  // ../src/host-control/client.ts
@@ -49896,13 +49885,13 @@ function hostdSocketPath(agentName3) {
49896
49885
  function hostdWillBeUsed(agentName3) {
49897
49886
  if (!isHostdEnabled())
49898
49887
  return false;
49899
- return existsSync25(hostdSocketPath(agentName3));
49888
+ return existsSync24(hostdSocketPath(agentName3));
49900
49889
  }
49901
49890
  async function tryHostdDispatch(agentName3, req, timeoutMs = 5000) {
49902
49891
  if (!isHostdEnabled())
49903
49892
  return "not-configured";
49904
49893
  const sockPath = hostdSocketPath(agentName3);
49905
- if (!existsSync25(sockPath))
49894
+ if (!existsSync24(sockPath))
49906
49895
  return "not-configured";
49907
49896
  try {
49908
49897
  return await hostdRequest({ socketPath: sockPath, timeoutMs }, req);
@@ -49926,7 +49915,7 @@ async function hostdGetStatusOnce(agentName3, targetRequestId) {
49926
49915
  if (!isHostdEnabled())
49927
49916
  return "not-configured";
49928
49917
  const sockPath = hostdSocketPath(agentName3);
49929
- if (!existsSync25(sockPath))
49918
+ if (!existsSync24(sockPath))
49930
49919
  return "not-configured";
49931
49920
  try {
49932
49921
  const resp = await hostdRequest({ socketPath: sockPath, timeoutMs: 3000 }, {
@@ -49947,7 +49936,7 @@ async function pollHostdStatus(agentName3, targetRequestId, opts) {
49947
49936
  if (!isHostdEnabled())
49948
49937
  return "not-configured";
49949
49938
  const sockPath = hostdSocketPath(agentName3);
49950
- if (!existsSync25(sockPath))
49939
+ if (!existsSync24(sockPath))
49951
49940
  return "not-configured";
49952
49941
  const now = opts.now ?? Date.now;
49953
49942
  const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
@@ -50026,7 +50015,7 @@ function shouldSweepChatAtBoot(chatId) {
50026
50015
 
50027
50016
  // gateway/webhook-ingest-server.ts
50028
50017
  import net4 from "node:net";
50029
- import { chmodSync as chmodSync4, existsSync as existsSync26, unlinkSync as unlinkSync7 } from "node:fs";
50018
+ import { chmodSync as chmodSync4, existsSync as existsSync25, unlinkSync as unlinkSync7 } from "node:fs";
50030
50019
  var MAX_REQUEST_BYTES = 1024 * 1024;
50031
50020
  function fdOf(conn) {
50032
50021
  const handle = conn._handle;
@@ -50038,7 +50027,7 @@ function startWebhookIngestServer(opts) {
50038
50027
  const log = opts.log ?? ((s) => process.stderr.write(s));
50039
50028
  const allowed = new Set(opts.allowedUids);
50040
50029
  try {
50041
- if (existsSync26(opts.socketPath))
50030
+ if (existsSync25(opts.socketPath))
50042
50031
  unlinkSync7(opts.socketPath);
50043
50032
  } catch (err) {
50044
50033
  log(`webhook-ingest-server: could not unlink stale socket: ${err.message}
@@ -50131,7 +50120,7 @@ function startWebhookIngestServer(opts) {
50131
50120
  server.close();
50132
50121
  } catch {}
50133
50122
  try {
50134
- if (existsSync26(opts.socketPath))
50123
+ if (existsSync25(opts.socketPath))
50135
50124
  unlinkSync7(opts.socketPath);
50136
50125
  } catch {}
50137
50126
  }
@@ -50144,13 +50133,13 @@ import { join as join24 } from "path";
50144
50133
  import { homedir as homedir10 } from "os";
50145
50134
 
50146
50135
  // ../src/web/webhook-handler.ts
50147
- import { appendFileSync as appendFileSync4, existsSync as existsSync27, mkdirSync as mkdirSync17, readFileSync as readFileSync20, writeFileSync as writeFileSync15 } from "fs";
50136
+ import { appendFileSync as appendFileSync4, existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync20, writeFileSync as writeFileSync14 } from "fs";
50148
50137
  import { join as join22 } from "path";
50149
50138
  var DEDUP_MAX = 1000;
50150
50139
  var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
50151
50140
  function loadDedupFile(path) {
50152
50141
  try {
50153
- if (!existsSync27(path))
50142
+ if (!existsSync26(path))
50154
50143
  return {};
50155
50144
  const raw = JSON.parse(readFileSync20(path, "utf-8"));
50156
50145
  return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
@@ -50166,7 +50155,7 @@ function saveDedupFile(path, deliveries, now) {
50166
50155
  }
50167
50156
  const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
50168
50157
  const final = Object.fromEntries(sorted);
50169
- writeFileSync15(path, JSON.stringify({ deliveries: final }), {
50158
+ writeFileSync14(path, JSON.stringify({ deliveries: final }), {
50170
50159
  mode: 384
50171
50160
  });
50172
50161
  }
@@ -50196,7 +50185,7 @@ var tokenBuckets = new Map;
50196
50185
  var throttleIssueWindow = new Map;
50197
50186
 
50198
50187
  // ../src/web/webhook-dispatch.ts
50199
- import { existsSync as existsSync28, mkdirSync as mkdirSync18, readFileSync as readFileSync21, writeFileSync as writeFileSync16 } from "fs";
50188
+ import { existsSync as existsSync27, mkdirSync as mkdirSync18, readFileSync as readFileSync21, writeFileSync as writeFileSync15 } from "fs";
50200
50189
  import { join as join23 } from "path";
50201
50190
  import { homedir as homedir9 } from "os";
50202
50191
 
@@ -50496,7 +50485,7 @@ function cooldownKey(source, eventType, repo, number, ruleIndex) {
50496
50485
  }
50497
50486
  function loadCooldownFile(path) {
50498
50487
  try {
50499
- if (!existsSync28(path))
50488
+ if (!existsSync27(path))
50500
50489
  return {};
50501
50490
  const raw = JSON.parse(readFileSync21(path, "utf-8"));
50502
50491
  return typeof raw.dispatches === "object" && raw.dispatches !== null ? raw.dispatches : {};
@@ -50506,7 +50495,7 @@ function loadCooldownFile(path) {
50506
50495
  }
50507
50496
  function saveCooldownFile(path, dispatches) {
50508
50497
  try {
50509
- writeFileSync16(path, JSON.stringify({ dispatches }), {
50498
+ writeFileSync15(path, JSON.stringify({ dispatches }), {
50510
50499
  mode: 384
50511
50500
  });
50512
50501
  } catch {}
@@ -51689,8 +51678,8 @@ function buildMs365CardText(p) {
51689
51678
  }
51690
51679
  lines.push("");
51691
51680
  lines.push("\u26a0\ufe0f Weak attestation (RFC \u00a78 v1): operator should click through to verify the actual change before approving. Structural diff coming v1.5.");
51692
- return lines.join(`
51693
- `);
51681
+ return hardenCardBreaks(lines.join(`
51682
+ `));
51694
51683
  }
51695
51684
  function truncate3(s, n) {
51696
51685
  if (s.length <= n)
@@ -51971,6 +51960,11 @@ function createPendingInboundBuffer(opts = {}) {
51971
51960
  evicted = true;
51972
51961
  log(`pending-inbound-buffer: agent=${agent} cap=${cap} reached \u2014 ` + `dropped oldest entry source=${dropped?.meta?.source ?? "-"} ts=${dropped?.ts ?? "-"}
51973
51962
  `);
51963
+ if (dropped != null && opts.onEvict != null) {
51964
+ try {
51965
+ opts.onEvict(agent, dropped);
51966
+ } catch {}
51967
+ }
51974
51968
  }
51975
51969
  q.push(msg);
51976
51970
  spool?.put(agent, msg);
@@ -52265,28 +52259,41 @@ async function runStatusPinBootCleanup(args) {
52265
52259
  persistStatusPins(args.path, args.fs, [], log);
52266
52260
  return { cleared, total: persisted.length };
52267
52261
  }
52268
- async function reconcileAndPersistStatusPin(args) {
52262
+ var storeLockTails = new Map;
52263
+ function withStoreLock(path, fn) {
52264
+ const prev = storeLockTails.get(path) ?? Promise.resolve();
52265
+ const run3 = prev.then(fn, fn);
52266
+ storeLockTails.set(path, run3.then(() => {
52267
+ return;
52268
+ }, () => {
52269
+ return;
52270
+ }));
52271
+ return run3;
52272
+ }
52273
+ function applyStatusPinRow(path, fs2, pinKey, row, log) {
52274
+ const current = loadStatusPins(path, fs2);
52275
+ const others = current.filter((p) => p.pinKey !== pinKey);
52276
+ const next = row == null ? others : [...others, row];
52277
+ persistStatusPins(path, fs2, next, log);
52278
+ }
52279
+ function reconcileAndPersistStatusPin(args) {
52269
52280
  const { path, fs: fs2, pinKey, chatId, op } = args;
52270
52281
  const log = args.log ?? ((l) => process.stderr.write(l));
52271
- if (op.kind === "pin") {
52272
- persistStatusPins(path, fs2, [
52273
- ...args.snapshotOthers(),
52274
- { pinKey, chatId, messageId: op.messageId, pending: true }
52275
- ], log);
52276
- const next2 = await args.applyPin();
52277
- if (next2 == null) {
52278
- persistStatusPins(path, fs2, args.snapshotOthers(), log);
52279
- return null;
52282
+ return withStoreLock(path, async () => {
52283
+ if (op.kind === "pin") {
52284
+ applyStatusPinRow(path, fs2, pinKey, { pinKey, chatId, messageId: op.messageId, pending: true }, log);
52285
+ const next2 = await args.applyPin();
52286
+ if (next2 == null) {
52287
+ applyStatusPinRow(path, fs2, pinKey, null, log);
52288
+ return null;
52289
+ }
52290
+ applyStatusPinRow(path, fs2, pinKey, { pinKey, chatId, messageId: next2.messageId }, log);
52291
+ return next2;
52280
52292
  }
52281
- persistStatusPins(path, fs2, [
52282
- ...args.snapshotOthers(),
52283
- { pinKey, chatId, messageId: next2.messageId }
52284
- ], log);
52285
- return next2;
52286
- }
52287
- const next = await args.applyPin();
52288
- persistStatusPins(path, fs2, args.snapshotOthers(), log);
52289
- return next;
52293
+ const next = await args.applyPin();
52294
+ applyStatusPinRow(path, fs2, pinKey, null, log);
52295
+ return next;
52296
+ });
52290
52297
  }
52291
52298
 
52292
52299
  // gateway/with-deadline.ts
@@ -52336,11 +52343,19 @@ function driveEscalation(args) {
52336
52343
  function shouldSuppressRepresent(o, deps) {
52337
52344
  if (!deps.historyEnabled)
52338
52345
  return false;
52339
- if (o.lastRepresentedAt == null)
52340
- return false;
52346
+ if (o.lastRepresentedAt == null) {
52347
+ if (o.openedAt == null)
52348
+ return false;
52349
+ return deps.hasOutboundDeliveredSince(o.chatId, o.openedAt, o.threadId);
52350
+ }
52341
52351
  return deps.hasOutboundDeliveredSince(o.chatId, o.lastRepresentedAt, o.threadId);
52342
52352
  }
52343
52353
 
52354
+ // gateway/escalation-bridge-gate.ts
52355
+ function shouldDeferEscalationForBridge(deps) {
52356
+ return !deps.bridgeAlive;
52357
+ }
52358
+
52344
52359
  // gateway/inbound-spool.ts
52345
52360
  function spoolId(msg) {
52346
52361
  if (msg.meta?.source === "subagent_handback" && typeof msg.meta?.subagent_jsonl_id === "string" && msg.meta.subagent_jsonl_id.length > 0) {
@@ -52372,6 +52387,8 @@ function createInboundSpool(opts) {
52372
52387
  const escalateNoticeCooldownMs = opts.escalateNoticeCooldownMs ?? 30 * 60 * 1000;
52373
52388
  const live = new Map;
52374
52389
  const escAttemptByChat = new Map;
52390
+ let consecutiveAppendFailures = 0;
52391
+ let degraded = false;
52375
52392
  function parseLine(line) {
52376
52393
  const s = line.trim();
52377
52394
  if (!s)
@@ -52439,9 +52456,33 @@ function createInboundSpool(opts) {
52439
52456
  try {
52440
52457
  fs2.appendFileSync(path, JSON.stringify(rec) + `
52441
52458
  `);
52459
+ if (consecutiveAppendFailures > 0) {
52460
+ log(`inbound-spool: append recovered path=${path} after ` + `${consecutiveAppendFailures} failure(s) \u2014 durability restored
52461
+ `);
52462
+ consecutiveAppendFailures = 0;
52463
+ if (degraded) {
52464
+ degraded = false;
52465
+ try {
52466
+ opts.onDegraded?.({ degraded: false, consecutiveFailures: 0, path });
52467
+ } catch {}
52468
+ }
52469
+ }
52442
52470
  } catch (err) {
52443
- log(`inbound-spool: append FAILED path=${path} id=${rec.id} t=${rec.t}: ` + `${err.message} \u2014 durability degraded to in-memory
52471
+ consecutiveAppendFailures++;
52472
+ const message = err.message;
52473
+ log(`inbound-spool: append FAILED path=${path} id=${rec.id} t=${rec.t}: ` + `${message} \u2014 durability degraded to in-memory ` + `(consecutive failures=${consecutiveAppendFailures})
52444
52474
  `);
52475
+ if (!degraded) {
52476
+ degraded = true;
52477
+ try {
52478
+ opts.onDegraded?.({
52479
+ degraded: true,
52480
+ consecutiveFailures: consecutiveAppendFailures,
52481
+ path,
52482
+ error: message
52483
+ });
52484
+ } catch {}
52485
+ }
52445
52486
  }
52446
52487
  }
52447
52488
  function maybeCompact() {
@@ -52531,11 +52572,18 @@ function createInboundSpool(opts) {
52531
52572
  sweepEscalations(onEscalate) {
52532
52573
  const tNow = now();
52533
52574
  const cutoff = tNow - escalateAfterMs;
52534
- let dropped = 0;
52535
- let posted = 0;
52536
- for (const [id, e] of [...live.entries()]) {
52575
+ const toDrop = [];
52576
+ const perChatCount = new Map;
52577
+ for (const [id, e] of live.entries()) {
52537
52578
  if (e.firstAt > cutoff)
52538
52579
  continue;
52580
+ toDrop.push({ id, e });
52581
+ const key = escChatKey(e.msg);
52582
+ perChatCount.set(key, (perChatCount.get(key) ?? 0) + 1);
52583
+ }
52584
+ let dropped = 0;
52585
+ let posted = 0;
52586
+ for (const { id, e } of toDrop) {
52539
52587
  live.delete(id);
52540
52588
  appendRecord({ t: "ack", id });
52541
52589
  const key = escChatKey(e.msg);
@@ -52546,7 +52594,7 @@ function createInboundSpool(opts) {
52546
52594
  const thread = typeof threadRaw === "string" && threadRaw.length > 0 ? threadRaw : undefined;
52547
52595
  appendRecord({ t: "esc", chat: e.msg.chatId, thread, at: tNow });
52548
52596
  try {
52549
- onEscalate({ agent: e.agent, msg: e.msg }, { postNotice });
52597
+ onEscalate({ agent: e.agent, msg: e.msg }, { postNotice, droppedCount: perChatCount.get(key) ?? 1 });
52550
52598
  } catch (err) {
52551
52599
  log(`inbound-spool: onEscalate threw id=${id}: ${err.message}
52552
52600
  `);
@@ -52565,6 +52613,12 @@ function createInboundSpool(opts) {
52565
52613
  },
52566
52614
  liveCount() {
52567
52615
  return live.size;
52616
+ },
52617
+ isDegraded() {
52618
+ return degraded;
52619
+ },
52620
+ appendFailureCount() {
52621
+ return consecutiveAppendFailures;
52568
52622
  }
52569
52623
  };
52570
52624
  }
@@ -52931,12 +52985,23 @@ function createDeliveryQueue() {
52931
52985
  function trackDelivery(q, key, inbound, now, messageId = null) {
52932
52986
  q.pending.set(key, { key, inbound, messageId, lastAttemptAt: now });
52933
52987
  }
52934
- function ackDelivery(q, key, enqueueMessageId = null) {
52988
+ function extractEnqueueMessageIds(content) {
52989
+ const ids = [];
52990
+ const re = /(?:^|[\s"'])message_id="([^"]+)"/g;
52991
+ let m;
52992
+ while ((m = re.exec(content)) != null)
52993
+ ids.push(m[1]);
52994
+ return ids;
52995
+ }
52996
+ function ackDelivery(q, key, enqueueMessageId = null, enqueueContent = null) {
52935
52997
  const entry = q.pending.get(key);
52936
52998
  if (!entry)
52937
52999
  return false;
52938
- if (entry.messageId != null && entry.messageId !== enqueueMessageId)
52939
- return false;
53000
+ if (entry.messageId != null) {
53001
+ const matches = entry.messageId === enqueueMessageId || enqueueContent != null && extractEnqueueMessageIds(enqueueContent).includes(entry.messageId);
53002
+ if (!matches)
53003
+ return false;
53004
+ }
52940
53005
  q.pending.delete(key);
52941
53006
  return true;
52942
53007
  }
@@ -52950,6 +53015,13 @@ function sweep(q, now, timeoutMs) {
52950
53015
  }
52951
53016
  return redeliver;
52952
53017
  }
53018
+ function isRedeliverySuspended(key, suspended) {
53019
+ if (suspended.keys.has(key))
53020
+ return true;
53021
+ const idx = key.indexOf(":");
53022
+ const chatId = idx < 0 ? key : key.slice(0, idx);
53023
+ return suspended.chats.has(chatId);
53024
+ }
52953
53025
  function forgetDelivery(q, key) {
52954
53026
  q.pending.delete(key);
52955
53027
  }
@@ -53426,6 +53498,27 @@ function dispatchOne(effect, ctx) {
53426
53498
  }
53427
53499
  }
53428
53500
 
53501
+ // gateway/gate-parity-probe.ts
53502
+ function gateParityDivergence(machineInTurn, busyKeysSize) {
53503
+ const busy = busyKeysSize > 0;
53504
+ if (machineInTurn === busy)
53505
+ return "none";
53506
+ if (!machineInTurn && busy)
53507
+ return "busykeys_dangle";
53508
+ return "machine_over_holds";
53509
+ }
53510
+ function isDangerousGateDivergence(d) {
53511
+ return d === "machine_over_holds";
53512
+ }
53513
+ function probeGateParity(machineInTurn, busyKeysSize, log = (line) => process.stderr.write(line)) {
53514
+ const d = gateParityDivergence(machineInTurn, busyKeysSize);
53515
+ if (isDangerousGateDivergence(d)) {
53516
+ log(`gw-trace gate-drift kind=${d} machineInTurn=${machineInTurn} ` + `busyKeys=${busyKeysSize} note=machine-authoritative-gate-holds-while-imperative-idle
53517
+ `);
53518
+ }
53519
+ return machineInTurn;
53520
+ }
53521
+
53429
53522
  // gateway/prefix-warmup.ts
53430
53523
  var WARMUP_COOLDOWN_MS = 5 * 60000;
53431
53524
  var lastWarmupAtPerAgent = new Map;
@@ -53686,11 +53779,11 @@ function buildSkillProposalApplyInbound(opts) {
53686
53779
  // ../src/self-improve/skill-proposals.ts
53687
53780
  import {
53688
53781
  closeSync as closeSync3,
53689
- existsSync as existsSync29,
53782
+ existsSync as existsSync28,
53690
53783
  mkdirSync as mkdirSync20,
53691
53784
  openSync as openSync3,
53692
53785
  readFileSync as readFileSync22,
53693
- writeSync as writeSync2
53786
+ writeSync as writeSync3
53694
53787
  } from "node:fs";
53695
53788
  import { join as join25 } from "node:path";
53696
53789
  import { randomUUID as randomUUID5 } from "node:crypto";
@@ -53705,21 +53798,21 @@ function rejectedPath(stateDir) {
53705
53798
  return join25(stateDir, REJECTED_FILE);
53706
53799
  }
53707
53800
  function ensureDir2(stateDir) {
53708
- if (!existsSync29(stateDir)) {
53801
+ if (!existsSync28(stateDir)) {
53709
53802
  mkdirSync20(stateDir, { recursive: true, mode: 493 });
53710
53803
  }
53711
53804
  }
53712
53805
  function appendLine(path, obj) {
53713
53806
  const fd = openSync3(path, "a");
53714
53807
  try {
53715
- writeSync2(fd, JSON.stringify(obj) + `
53808
+ writeSync3(fd, JSON.stringify(obj) + `
53716
53809
  `);
53717
53810
  } finally {
53718
53811
  closeSync3(fd);
53719
53812
  }
53720
53813
  }
53721
53814
  function readLines(path, isValid2) {
53722
- if (!existsSync29(path))
53815
+ if (!existsSync28(path))
53723
53816
  return [];
53724
53817
  let raw;
53725
53818
  try {
@@ -54352,10 +54445,10 @@ function escapeBody2(s) {
54352
54445
  }
54353
54446
 
54354
54447
  // gateway/pid-file.ts
54355
- import { writeFileSync as writeFileSync17, readFileSync as readFileSync23, unlinkSync as unlinkSync9, renameSync as renameSync8 } from "node:fs";
54448
+ import { writeFileSync as writeFileSync16, readFileSync as readFileSync23, unlinkSync as unlinkSync9, renameSync as renameSync8 } from "node:fs";
54356
54449
  function writePidFile(path, record) {
54357
54450
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
54358
- writeFileSync17(tmp, JSON.stringify(record), "utf-8");
54451
+ writeFileSync16(tmp, JSON.stringify(record), "utf-8");
54359
54452
  renameSync8(tmp, path);
54360
54453
  }
54361
54454
  function clearPidFile(path) {
@@ -54577,10 +54670,10 @@ function safeCount(fn) {
54577
54670
  }
54578
54671
 
54579
54672
  // gateway/session-marker.ts
54580
- import { writeFileSync as writeFileSync18, readFileSync as readFileSync25, renameSync as renameSync9, unlinkSync as unlinkSync10 } from "node:fs";
54673
+ import { writeFileSync as writeFileSync17, readFileSync as readFileSync25, renameSync as renameSync9, unlinkSync as unlinkSync10 } from "node:fs";
54581
54674
  function writeSessionMarker(path, marker) {
54582
54675
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
54583
- writeFileSync18(tmp, JSON.stringify(marker), "utf-8");
54676
+ writeFileSync17(tmp, JSON.stringify(marker), "utf-8");
54584
54677
  renameSync9(tmp, path);
54585
54678
  }
54586
54679
  function readSessionMarker(path) {
@@ -54607,11 +54700,11 @@ function shouldFireRestartBanner(input) {
54607
54700
  }
54608
54701
 
54609
54702
  // gateway/clean-shutdown-marker.ts
54610
- import { writeFileSync as writeFileSync19, readFileSync as readFileSync26, renameSync as renameSync10, unlinkSync as unlinkSync11 } from "node:fs";
54703
+ import { writeFileSync as writeFileSync18, readFileSync as readFileSync26, renameSync as renameSync10, unlinkSync as unlinkSync11 } from "node:fs";
54611
54704
  var DEFAULT_MAX_AGE_MS = 60000;
54612
54705
  function writeCleanShutdownMarker(path, marker) {
54613
54706
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
54614
- writeFileSync19(tmp, JSON.stringify(marker), "utf-8");
54707
+ writeFileSync18(tmp, JSON.stringify(marker), "utf-8");
54615
54708
  renameSync10(tmp, path);
54616
54709
  }
54617
54710
  function readCleanShutdownMarker(path) {
@@ -55207,7 +55300,7 @@ function classifyAdminGate(text, myAgentName) {
55207
55300
 
55208
55301
  // subagent-watcher.ts
55209
55302
  import {
55210
- existsSync as existsSync31,
55303
+ existsSync as existsSync30,
55211
55304
  openSync as openSync5,
55212
55305
  readSync,
55213
55306
  statSync as statSync9,
@@ -55408,7 +55501,12 @@ function projectSubagentLine(line, agentId, state4) {
55408
55501
  if (ct === "tool_use") {
55409
55502
  const name = c.name ?? "";
55410
55503
  if (name === "Agent" || name === "Task") {
55411
- events.push({ kind: "sub_agent_nested_spawn", agentId });
55504
+ events.push({
55505
+ kind: "sub_agent_nested_spawn",
55506
+ agentId,
55507
+ toolUseId: c.id ?? null,
55508
+ input: c.input ?? undefined
55509
+ });
55412
55510
  } else {
55413
55511
  events.push({
55414
55512
  kind: "sub_agent_tool_use",
@@ -55657,24 +55755,43 @@ function bumpSubagentActivity(db2, args) {
55657
55755
  WHERE id = ?
55658
55756
  `).run(args.ts, args.id);
55659
55757
  }
55758
+ function recordNestedSubagentDispatch(db2, args) {
55759
+ db2.prepare(`
55760
+ INSERT OR IGNORE INTO subagents
55761
+ (id, parent_session_id, parent_turn_key, agent_type, description,
55762
+ background, started_at, last_activity_at, status, jsonl_agent_id,
55763
+ parent_agent_id)
55764
+ VALUES (?, NULL, NULL, ?, ?, ?, ?, ?, 'running', NULL, ?)
55765
+ `).run(args.toolUseId, args.agentType ?? null, args.description ?? null, args.background ? 1 : 0, args.now, args.now, args.parentJsonlAgentId);
55766
+ db2.prepare(`
55767
+ UPDATE subagents
55768
+ SET parent_agent_id = COALESCE(parent_agent_id, ?),
55769
+ parent_turn_key = COALESCE(
55770
+ parent_turn_key,
55771
+ (SELECT p.parent_turn_key FROM subagents p
55772
+ WHERE p.jsonl_agent_id = ? LIMIT 1)
55773
+ )
55774
+ WHERE id = ?
55775
+ `).run(args.parentJsonlAgentId, args.parentJsonlAgentId, args.toolUseId);
55776
+ }
55660
55777
 
55661
55778
  // gateway/turn-active-marker.ts
55662
55779
  import {
55663
55780
  closeSync as closeSync4,
55664
- existsSync as existsSync30,
55781
+ existsSync as existsSync29,
55665
55782
  mkdirSync as mkdirSync21,
55666
55783
  openSync as openSync4,
55667
55784
  readFileSync as readFileSync27,
55668
55785
  statSync as statSync8,
55669
55786
  unlinkSync as unlinkSync12,
55670
55787
  utimesSync,
55671
- writeFileSync as writeFileSync20
55788
+ writeFileSync as writeFileSync19
55672
55789
  } from "node:fs";
55673
55790
  import { join as join26 } from "node:path";
55674
55791
  var TURN_ACTIVE_MARKER_FILE = "turn-active.json";
55675
55792
  function touchTurnActiveMarker(stateDir) {
55676
55793
  const path = join26(stateDir, TURN_ACTIVE_MARKER_FILE);
55677
- if (!existsSync30(path))
55794
+ if (!existsSync29(path))
55678
55795
  return;
55679
55796
  const now = new Date;
55680
55797
  try {
@@ -55692,6 +55809,10 @@ var DEFAULT_RESCAN_MS = 1000;
55692
55809
  var DEFAULT_STALL_THRESHOLD_MS = 60000;
55693
55810
  var DEFAULT_SILENT_SYNTHESIS_STALL_THRESHOLD_MS = 300000;
55694
55811
  var DEFAULT_SILENT_STALL_TERMINAL_MS = 300000;
55812
+ var LONG_RUNNING_TOOLS = new Set(["Bash"]);
55813
+ function isLongRunningTool(name) {
55814
+ return name != null && LONG_RUNNING_TOOLS.has(name);
55815
+ }
55695
55816
  var DEFAULT_INFLIGHT_PROMOTE_MAX_AGE_MS = 15 * 60000;
55696
55817
  var SUBAGENT_RESULT_TEXT_MAX = 3000;
55697
55818
  function parseEnvMs(varName) {
@@ -55706,6 +55827,7 @@ function parseEnvMs(varName) {
55706
55827
  var DEFAULT_REAPER_TTL_MS = 60 * 60000;
55707
55828
  var DEFAULT_REAPER_INTERVAL_MS = 15 * 60000;
55708
55829
  var TERMINAL_CLEANUP_GRACE_MS = 30000;
55830
+ var BACKFILL_RETRY_INTERVAL_MS = 3000;
55709
55831
  function backfillJsonlAgentId(db2, jsonlPath, agentId, log) {
55710
55832
  const metaPath = jsonlPath.replace(/\.jsonl$/, ".meta.json");
55711
55833
  let meta;
@@ -55754,14 +55876,38 @@ function backfillJsonlAgentId(db2, jsonlPath, agentId, log) {
55754
55876
  db2.prepare("UPDATE subagents SET jsonl_agent_id = ? WHERE id = ?").run(agentId, candidateId);
55755
55877
  log?.(`subagent-watcher: backfill linked ${agentId} \u2192 ${candidateId}`);
55756
55878
  try {
55757
- const linkedRow = db2.prepare("SELECT started_at, parent_turn_key FROM subagents WHERE id = ?").get(candidateId);
55879
+ const linkedRow = db2.prepare("SELECT started_at, parent_turn_key, parent_agent_id FROM subagents WHERE id = ?").get(candidateId);
55758
55880
  if (linkedRow != null && linkedRow.parent_turn_key == null) {
55759
- const turn = db2.prepare(`SELECT turn_key FROM turns
55760
- WHERE started_at <= ? AND (ended_at IS NULL OR ended_at >= ?)
55761
- ORDER BY started_at DESC LIMIT 1`).get(linkedRow.started_at, linkedRow.started_at);
55762
- if (turn?.turn_key != null) {
55763
- db2.prepare("UPDATE subagents SET parent_turn_key = ? WHERE id = ?").run(turn.turn_key, candidateId);
55764
- log?.(`subagent-watcher: backfill parent_turn_key ${candidateId} \u2192 ${turn.turn_key}`);
55881
+ let resolvedKey = null;
55882
+ if (linkedRow.parent_agent_id != null && linkedRow.parent_agent_id.length > 0) {
55883
+ const seen = new Set([agentId]);
55884
+ let cursor = linkedRow.parent_agent_id;
55885
+ for (let hop = 0;hop < 5 && cursor != null && !seen.has(cursor); hop++) {
55886
+ seen.add(cursor);
55887
+ const parentRow = db2.prepare("SELECT parent_turn_key, parent_agent_id FROM subagents WHERE jsonl_agent_id = ? LIMIT 1").get(cursor);
55888
+ if (parentRow == null)
55889
+ break;
55890
+ if (parentRow.parent_turn_key != null && parentRow.parent_turn_key.length > 0) {
55891
+ resolvedKey = parentRow.parent_turn_key;
55892
+ break;
55893
+ }
55894
+ cursor = parentRow.parent_agent_id ?? null;
55895
+ }
55896
+ if (resolvedKey != null) {
55897
+ log?.(`subagent-watcher: backfill parent_turn_key ${candidateId} \u2192 ${resolvedKey} (inherited via nested-parent chain)`);
55898
+ }
55899
+ }
55900
+ if (resolvedKey == null) {
55901
+ const turn = db2.prepare(`SELECT turn_key FROM turns
55902
+ WHERE started_at <= ? AND (ended_at IS NULL OR ended_at >= ?)
55903
+ ORDER BY started_at DESC LIMIT 1`).get(linkedRow.started_at, linkedRow.started_at);
55904
+ if (turn?.turn_key != null) {
55905
+ resolvedKey = turn.turn_key;
55906
+ log?.(`subagent-watcher: backfill parent_turn_key ${candidateId} \u2192 ${resolvedKey}`);
55907
+ }
55908
+ }
55909
+ if (resolvedKey != null) {
55910
+ db2.prepare("UPDATE subagents SET parent_turn_key = ? WHERE id = ?").run(resolvedKey, candidateId);
55765
55911
  }
55766
55912
  }
55767
55913
  } catch (err) {
@@ -55791,6 +55937,14 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
55791
55937
  const existing = db2.prepare("SELECT id, background FROM subagents WHERE jsonl_agent_id = ?").get(entry.agentId);
55792
55938
  if (existing == null) {
55793
55939
  log?.(`subagent-watcher: liveness skip ${entry.agentId} \u2014 row not in DB yet (Phase 2 Pre hook pending)`);
55940
+ if (entry.lastBackfillAttemptAt == null || now - entry.lastBackfillAttemptAt >= BACKFILL_RETRY_INTERVAL_MS) {
55941
+ entry.lastBackfillAttemptAt = now;
55942
+ try {
55943
+ backfillJsonlAgentId(db2, entry.filePath, entry.agentId, log);
55944
+ } catch (bfErr) {
55945
+ log?.(`subagent-watcher: backfill retry error ${entry.agentId}: ${bfErr.message}`);
55946
+ }
55947
+ }
55794
55948
  } else {
55795
55949
  bumpSubagentActivity(db2, { id: existing.id, ts: now });
55796
55950
  isForeground = existing.background === 0;
@@ -55913,6 +56067,23 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
55913
56067
  }
55914
56068
  }
55915
56069
  }
56070
+ } else if (ev.kind === "sub_agent_nested_spawn") {
56071
+ if (db2 != null && ev.toolUseId != null && ev.toolUseId.length > 0) {
56072
+ try {
56073
+ const nestedInput = ev.input ?? {};
56074
+ recordNestedSubagentDispatch(db2, {
56075
+ toolUseId: ev.toolUseId,
56076
+ parentJsonlAgentId: entry.agentId,
56077
+ agentType: typeof nestedInput.subagent_type === "string" ? nestedInput.subagent_type : null,
56078
+ description: typeof nestedInput.description === "string" ? nestedInput.description : null,
56079
+ background: nestedInput.run_in_background === true,
56080
+ now
56081
+ });
56082
+ log?.(`subagent-watcher: nested dispatch recorded parent=${entry.agentId} toolUseId=${ev.toolUseId}`);
56083
+ } catch (dbErr) {
56084
+ log?.(`subagent-watcher: nested dispatch record error ${entry.agentId}: ${dbErr.message}`);
56085
+ }
56086
+ }
55916
56087
  } else if (ev.kind === "sub_agent_text") {
55917
56088
  entry.lastSummaryLine = clipNarrative(ev.text);
55918
56089
  entry.lastResultText = ev.text.trim().slice(0, SUBAGENT_RESULT_TEXT_MAX);
@@ -55984,7 +56155,7 @@ function startSubagentWatcher(config) {
55984
56155
  clearTimeout(ref.ref);
55985
56156
  });
55986
56157
  const fs2 = config.fs ?? {
55987
- existsSync: existsSync31,
56158
+ existsSync: existsSync30,
55988
56159
  readdirSync: readdirSync5,
55989
56160
  statSync: statSync9,
55990
56161
  openSync: openSync5,
@@ -56064,6 +56235,15 @@ function startSubagentWatcher(config) {
56064
56235
  log?.(`subagent-watcher: backfill error for ${agentId}: ${err.message}`);
56065
56236
  }
56066
56237
  }
56238
+ entry.toolCount = 0;
56239
+ entry.lastTool = null;
56240
+ entry.pendingNarrative = null;
56241
+ tail.cursor = 0;
56242
+ tail.pendingPartial = "";
56243
+ tail.hasEmittedStart = false;
56244
+ readSubTail(entry, tail, n, (desc) => {
56245
+ log?.(`subagent-watcher: description updated for ${agentId}: ${desc}`);
56246
+ }, fs2, log, db2, parentStateDir, config.onUnstall, undefined, config.onProgress);
56067
56247
  }
56068
56248
  }
56069
56249
  if (isHistorical && entry.state === "done") {
@@ -56172,7 +56352,7 @@ function startSubagentWatcher(config) {
56172
56352
  if (entry.stallNotified)
56173
56353
  continue;
56174
56354
  const idleMs = n - entry.lastActivityAt;
56175
- const threshold = entry.toolCount === 0 ? silentSynthesisStallThresholdMs : stallThresholdMs;
56355
+ const threshold = entry.toolCount === 0 || isLongRunningTool(entry.lastTool?.name) ? silentSynthesisStallThresholdMs : stallThresholdMs;
56176
56356
  if (idleMs >= threshold) {
56177
56357
  entry.stallNotified = true;
56178
56358
  entry.stalledAt = n;
@@ -56448,7 +56628,7 @@ function determineRestartReason(opts) {
56448
56628
  init_boot_card();
56449
56629
 
56450
56630
  // gateway/update-announce.ts
56451
- import { existsSync as existsSync36, mkdirSync as mkdirSync25, openSync as openSync6, closeSync as closeSync6, readFileSync as readFileSync34 } from "node:fs";
56631
+ import { existsSync as existsSync35, mkdirSync as mkdirSync25, openSync as openSync6, closeSync as closeSync6, readFileSync as readFileSync34 } from "node:fs";
56452
56632
  import { join as join32 } from "node:path";
56453
56633
  import { homedir as homedir12 } from "node:os";
56454
56634
 
@@ -56577,7 +56757,7 @@ function readAndFilter(raw, filters, limit) {
56577
56757
  var DEFAULT_LOOKBACK_MS = 10 * 60 * 1000;
56578
56758
  function readLastTerminalUpdateAudit(opts = {}) {
56579
56759
  const path = opts.auditLogPath ?? defaultAuditLogPath();
56580
- const exists = opts.exists ?? existsSync36;
56760
+ const exists = opts.exists ?? existsSync35;
56581
56761
  const readFile = opts.readFile ?? ((p) => readFileSync34(p, "utf-8"));
56582
56762
  if (!exists(path))
56583
56763
  return null;
@@ -56667,7 +56847,7 @@ function maybeRenderUpdateAnnouncement(opts = {}) {
56667
56847
 
56668
56848
  // issues-card.ts
56669
56849
  init_card_format();
56670
- import { readFileSync as readFileSync35, writeFileSync as writeFileSync25 } from "node:fs";
56850
+ import { readFileSync as readFileSync35, writeFileSync as writeFileSync24 } from "node:fs";
56671
56851
  var SEVERITY_EMOJI = {
56672
56852
  info: "\u2139\ufe0f",
56673
56853
  warn: "\u26a0\ufe0f",
@@ -56775,7 +56955,7 @@ function readPersistedMessageId(path, log) {
56775
56955
  }
56776
56956
  function writePersistedMessageId(path, messageId, log) {
56777
56957
  try {
56778
- writeFileSync25(path, JSON.stringify({ messageId }) + `
56958
+ writeFileSync24(path, JSON.stringify({ messageId }) + `
56779
56959
  `, { mode: 384 });
56780
56960
  } catch (err) {
56781
56961
  log(`issues-card: persist write failed (${err.message})`);
@@ -56868,13 +57048,13 @@ function createIssuesCardHandle(opts) {
56868
57048
  }
56869
57049
 
56870
57050
  // issues-watcher.ts
56871
- import { existsSync as existsSync38, statSync as statSync11 } from "node:fs";
57051
+ import { existsSync as existsSync37, statSync as statSync11 } from "node:fs";
56872
57052
  import { join as join34 } from "node:path";
56873
57053
 
56874
57054
  // ../src/issues/store.ts
56875
57055
  import {
56876
57056
  closeSync as closeSync7,
56877
- existsSync as existsSync37,
57057
+ existsSync as existsSync36,
56878
57058
  mkdirSync as mkdirSync26,
56879
57059
  openSync as openSync7,
56880
57060
  readdirSync as readdirSync7,
@@ -56882,8 +57062,8 @@ import {
56882
57062
  renameSync as renameSync13,
56883
57063
  statSync as statSync10,
56884
57064
  unlinkSync as unlinkSync13,
56885
- writeFileSync as writeFileSync26,
56886
- writeSync as writeSync3
57065
+ writeFileSync as writeFileSync25,
57066
+ writeSync as writeSync4
56887
57067
  } from "node:fs";
56888
57068
  import { join as join33 } from "node:path";
56889
57069
  import { randomBytes as randomBytes6 } from "node:crypto";
@@ -56905,7 +57085,7 @@ var ISSUES_FILE = "issues.jsonl";
56905
57085
  var ISSUES_LOCK = "issues.lock";
56906
57086
  function readAll(stateDir) {
56907
57087
  const path = join33(stateDir, ISSUES_FILE);
56908
- if (!existsSync37(path))
57088
+ if (!existsSync36(path))
56909
57089
  return [];
56910
57090
  let raw;
56911
57091
  try {
@@ -56941,7 +57121,7 @@ function list(stateDir, opts = {}) {
56941
57121
  });
56942
57122
  }
56943
57123
  function resolve6(stateDir, fingerprint, nowFn = Date.now) {
56944
- if (!existsSync37(join33(stateDir, ISSUES_FILE)))
57124
+ if (!existsSync36(join33(stateDir, ISSUES_FILE)))
56945
57125
  return 0;
56946
57126
  return withLock(stateDir, () => {
56947
57127
  const all = readAll(stateDir);
@@ -56965,7 +57145,7 @@ function writeAll(stateDir, events) {
56965
57145
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
56966
57146
  `) + `
56967
57147
  `;
56968
- writeFileSync26(tmp, body, "utf-8");
57148
+ writeFileSync25(tmp, body, "utf-8");
56969
57149
  renameSync13(tmp, path);
56970
57150
  }
56971
57151
  var ORPHAN_TMP_TTL_MS = 60000;
@@ -57000,7 +57180,7 @@ function withLock(stateDir, fn) {
57000
57180
  try {
57001
57181
  fd = openSync7(lockPath, "wx");
57002
57182
  try {
57003
- writeSync3(fd, String(process.pid));
57183
+ writeSync4(fd, String(process.pid));
57004
57184
  } catch {}
57005
57185
  } catch (err) {
57006
57186
  const e = err;
@@ -57126,7 +57306,7 @@ function startIssuesWatcher(opts) {
57126
57306
  };
57127
57307
  }
57128
57308
  function defaultSignatureProvider(path) {
57129
- if (!existsSync38(path))
57309
+ if (!existsSync37(path))
57130
57310
  return null;
57131
57311
  try {
57132
57312
  const stat = statSync11(path);
@@ -58127,7 +58307,7 @@ function extractFlowItems(line) {
58127
58307
 
58128
58308
  // credits-watch.ts
58129
58309
  init_card_format();
58130
- import { readFileSync as readFileSync37, writeFileSync as writeFileSync27, existsSync as existsSync39, mkdirSync as mkdirSync27 } from "fs";
58310
+ import { readFileSync as readFileSync37, writeFileSync as writeFileSync26, existsSync as existsSync38, mkdirSync as mkdirSync27 } from "fs";
58131
58311
  import { join as join35 } from "path";
58132
58312
  var STATE_FILE = "credits-watch.json";
58133
58313
  var DEFAULT_CREDIT_FATAL_REASONS = new Set;
@@ -58151,7 +58331,7 @@ function emptyCreditState() {
58151
58331
  }
58152
58332
  function readClaudeJsonOverage(claudeConfigDir) {
58153
58333
  const path = join35(claudeConfigDir, ".claude.json");
58154
- if (!existsSync39(path))
58334
+ if (!existsSync38(path))
58155
58335
  return null;
58156
58336
  let raw;
58157
58337
  try {
@@ -58234,7 +58414,7 @@ function humanizeReason(reason) {
58234
58414
  }
58235
58415
  function loadCreditState(stateDir) {
58236
58416
  const path = join35(stateDir, STATE_FILE);
58237
- if (!existsSync39(path))
58417
+ if (!existsSync38(path))
58238
58418
  return emptyCreditState();
58239
58419
  try {
58240
58420
  const raw = readFileSync37(path, "utf-8");
@@ -58251,12 +58431,12 @@ function loadCreditState(stateDir) {
58251
58431
  function saveCreditState(stateDir, state4) {
58252
58432
  mkdirSync27(stateDir, { recursive: true });
58253
58433
  const path = join35(stateDir, STATE_FILE);
58254
- writeFileSync27(path, JSON.stringify(state4, null, 2) + `
58434
+ writeFileSync26(path, JSON.stringify(state4, null, 2) + `
58255
58435
  `, { mode: 384 });
58256
58436
  }
58257
58437
 
58258
58438
  // quota-watch.ts
58259
- import { readFileSync as readFileSync38, writeFileSync as writeFileSync28, existsSync as existsSync40, mkdirSync as mkdirSync28 } from "fs";
58439
+ import { readFileSync as readFileSync38, writeFileSync as writeFileSync27, existsSync as existsSync39, mkdirSync as mkdirSync28 } from "fs";
58260
58440
  import { join as join36 } from "path";
58261
58441
  init_card_format();
58262
58442
  var STATE_FILE2 = "quota-watch.json";
@@ -58453,7 +58633,7 @@ function buildRecoveryMessage(agentName3, snap) {
58453
58633
  }
58454
58634
  function loadQuotaWatchState(stateDir) {
58455
58635
  const path = join36(stateDir, STATE_FILE2);
58456
- if (!existsSync40(path))
58636
+ if (!existsSync39(path))
58457
58637
  return emptyQuotaWatchState();
58458
58638
  try {
58459
58639
  const raw = readFileSync38(path, "utf-8");
@@ -58475,7 +58655,7 @@ function loadQuotaWatchState(stateDir) {
58475
58655
  function saveQuotaWatchState(stateDir, state4) {
58476
58656
  mkdirSync28(stateDir, { recursive: true });
58477
58657
  const path = join36(stateDir, STATE_FILE2);
58478
- writeFileSync28(path, JSON.stringify(state4, null, 2) + `
58658
+ writeFileSync27(path, JSON.stringify(state4, null, 2) + `
58479
58659
  `, { mode: 384 });
58480
58660
  }
58481
58661
  function patchQuotaWatchState(current, accountLabel, accountState) {
@@ -58505,27 +58685,27 @@ function maskVaultKey(name) {
58505
58685
  // gateway/turn-active-marker.ts
58506
58686
  import {
58507
58687
  closeSync as closeSync8,
58508
- existsSync as existsSync41,
58688
+ existsSync as existsSync40,
58509
58689
  mkdirSync as mkdirSync29,
58510
58690
  openSync as openSync8,
58511
58691
  readFileSync as readFileSync39,
58512
58692
  statSync as statSync12,
58513
58693
  unlinkSync as unlinkSync14,
58514
58694
  utimesSync as utimesSync2,
58515
- writeFileSync as writeFileSync29
58695
+ writeFileSync as writeFileSync28
58516
58696
  } from "node:fs";
58517
58697
  import { join as join37 } from "node:path";
58518
58698
  var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
58519
58699
  function writeTurnActiveMarker(stateDir, marker) {
58520
58700
  try {
58521
58701
  mkdirSync29(stateDir, { recursive: true });
58522
- writeFileSync29(join37(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
58702
+ writeFileSync28(join37(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
58523
58703
  `, { mode: 384 });
58524
58704
  } catch {}
58525
58705
  }
58526
58706
  function touchTurnActiveMarker2(stateDir) {
58527
58707
  const path = join37(stateDir, TURN_ACTIVE_MARKER_FILE2);
58528
- if (!existsSync41(path))
58708
+ if (!existsSync40(path))
58529
58709
  return;
58530
58710
  const now = new Date;
58531
58711
  try {
@@ -58544,7 +58724,7 @@ function removeTurnActiveMarker(stateDir) {
58544
58724
  }
58545
58725
  function sweepStaleTurnActiveMarker(stateDir, opts) {
58546
58726
  const path = join37(stateDir, TURN_ACTIVE_MARKER_FILE2);
58547
- if (!existsSync41(path))
58727
+ if (!existsSync40(path))
58548
58728
  return false;
58549
58729
  const now = opts.now ?? Date.now();
58550
58730
  try {
@@ -58584,11 +58764,11 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
58584
58764
  }
58585
58765
 
58586
58766
  // ../src/build-info.ts
58587
- var VERSION = "0.15.48";
58588
- var COMMIT_SHA = "eee77212";
58589
- var COMMIT_DATE = "2026-07-04T12:23:42+10:00";
58590
- var LATEST_PR = null;
58591
- var COMMITS_AHEAD_OF_TAG = 2;
58767
+ var VERSION = "0.17.1";
58768
+ var COMMIT_SHA = "d7162c0";
58769
+ var COMMIT_DATE = "2026-07-05T05:44:23Z";
58770
+ var LATEST_PR = 2831;
58771
+ var COMMITS_AHEAD_OF_TAG = 0;
58592
58772
 
58593
58773
  // gateway/boot-version.ts
58594
58774
  function formatRelativeAgo(iso) {
@@ -59630,6 +59810,7 @@ var SCHEMA_SQL = `
59630
59810
  assistant_reply_preview TEXT,
59631
59811
  tool_call_count INTEGER,
59632
59812
  interrupt_reason TEXT,
59813
+ resumed_at INTEGER,
59633
59814
  created_at INTEGER NOT NULL,
59634
59815
  updated_at INTEGER NOT NULL
59635
59816
  );
@@ -59643,12 +59824,15 @@ var PHASE1_MIGRATIONS = [
59643
59824
  var PHASE2_MIGRATIONS = [
59644
59825
  `ALTER TABLE turns ADD COLUMN interrupt_reason TEXT`
59645
59826
  ];
59827
+ var PHASE3_MIGRATIONS = [
59828
+ `ALTER TABLE turns ADD COLUMN resumed_at INTEGER`
59829
+ ];
59646
59830
  function applySchema(db2) {
59647
59831
  db2.exec("PRAGMA journal_mode = WAL");
59648
59832
  db2.exec("PRAGMA synchronous = NORMAL");
59649
59833
  db2.exec("PRAGMA busy_timeout = 5000");
59650
59834
  db2.exec(SCHEMA_SQL);
59651
- for (const sql of [...PHASE1_MIGRATIONS, ...PHASE2_MIGRATIONS]) {
59835
+ for (const sql of [...PHASE1_MIGRATIONS, ...PHASE2_MIGRATIONS, ...PHASE3_MIGRATIONS]) {
59652
59836
  try {
59653
59837
  db2.exec(sql);
59654
59838
  } catch (err) {
@@ -59690,6 +59874,7 @@ function mapRow(row) {
59690
59874
  assistant_reply_preview: row.assistant_reply_preview,
59691
59875
  tool_call_count: row.tool_call_count,
59692
59876
  interrupt_reason: row.interrupt_reason,
59877
+ resumed_at: row.resumed_at,
59693
59878
  created_at: row.created_at,
59694
59879
  updated_at: row.updated_at
59695
59880
  };
@@ -59753,6 +59938,14 @@ var INTERRUPTED_VIA = new Set([
59753
59938
  "timeout",
59754
59939
  "unknown"
59755
59940
  ]);
59941
+ function markTurnResumed(db2, turnKey, now = Date.now()) {
59942
+ db2.prepare(`
59943
+ UPDATE turns
59944
+ SET resumed_at = ?,
59945
+ updated_at = ?
59946
+ WHERE turn_key = ? AND resumed_at IS NULL
59947
+ `).run(now, now, turnKey);
59948
+ }
59756
59949
  function findLatestTurnIfInterrupted(db2) {
59757
59950
  const row = db2.prepare(`
59758
59951
  SELECT * FROM turns
@@ -59762,6 +59955,8 @@ function findLatestTurnIfInterrupted(db2) {
59762
59955
  if (!row)
59763
59956
  return null;
59764
59957
  const turn = mapRow(row);
59958
+ if (turn.resumed_at != null)
59959
+ return null;
59765
59960
  if (turn.ended_at == null)
59766
59961
  return turn;
59767
59962
  if (turn.ended_via != null && INTERRUPTED_VIA.has(turn.ended_via))
@@ -59885,7 +60080,8 @@ var SUBAGENTS_SCHEMA_SQL = `
59885
60080
  ended_at INTEGER,
59886
60081
  status TEXT NOT NULL,
59887
60082
  result_summary TEXT,
59888
- jsonl_agent_id TEXT
60083
+ jsonl_agent_id TEXT,
60084
+ parent_agent_id TEXT
59889
60085
  );
59890
60086
  CREATE INDEX IF NOT EXISTS subagents_turn ON subagents(parent_turn_key);
59891
60087
  CREATE INDEX IF NOT EXISTS subagents_status ON subagents(status);
@@ -59897,6 +60093,10 @@ function applySubagentsSchema(db2) {
59897
60093
  if (!hasJsonlId) {
59898
60094
  db2.exec("ALTER TABLE subagents ADD COLUMN jsonl_agent_id TEXT");
59899
60095
  }
60096
+ const hasParentAgentId = cols.some((c) => c.name === "parent_agent_id");
60097
+ if (!hasParentAgentId) {
60098
+ db2.exec("ALTER TABLE subagents ADD COLUMN parent_agent_id TEXT");
60099
+ }
59900
60100
  db2.exec("CREATE INDEX IF NOT EXISTS subagents_jsonl_id ON subagents(jsonl_agent_id)");
59901
60101
  }
59902
60102
  function mapSubagentRow(row) {
@@ -59912,19 +60112,38 @@ function mapSubagentRow(row) {
59912
60112
  ended_at: row.ended_at,
59913
60113
  status: row.status,
59914
60114
  result_summary: row.result_summary,
59915
- jsonl_agent_id: row.jsonl_agent_id
60115
+ jsonl_agent_id: row.jsonl_agent_id,
60116
+ parent_agent_id: row.parent_agent_id ?? null
59916
60117
  };
59917
60118
  }
59918
60119
  function getSubagentByJsonlId(db2, jsonlAgentId) {
59919
60120
  const row = db2.prepare("SELECT * FROM subagents WHERE jsonl_agent_id = ?").get(jsonlAgentId);
59920
60121
  return row ? mapSubagentRow(row) : null;
59921
60122
  }
60123
+ function resolveSubagentOriginTurnKey(db2, jsonlAgentId, maxHops = 5) {
60124
+ const seen = new Set;
60125
+ let currentJsonlId = jsonlAgentId;
60126
+ for (let hop = 0;hop <= maxHops && currentJsonlId != null; hop++) {
60127
+ if (seen.has(currentJsonlId))
60128
+ return null;
60129
+ seen.add(currentJsonlId);
60130
+ const row = db2.prepare("SELECT parent_turn_key, parent_agent_id FROM subagents WHERE jsonl_agent_id = ? LIMIT 1").get(currentJsonlId);
60131
+ if (row == null)
60132
+ return null;
60133
+ if (row.parent_turn_key != null && row.parent_turn_key.length > 0)
60134
+ return row.parent_turn_key;
60135
+ currentJsonlId = row.parent_agent_id ?? null;
60136
+ }
60137
+ return null;
60138
+ }
59922
60139
 
59923
60140
  // gateway/worker-feed-dispatch.ts
59924
60141
  function resolveWorkerFeedDispatch(sub, watcherDescription) {
59925
60142
  return {
59926
60143
  isBackground: sub?.background ?? false,
59927
- feedDescription: (sub?.description ?? "") || watcherDescription
60144
+ feedDescription: (sub?.description ?? "") || watcherDescription,
60145
+ hasRow: sub != null,
60146
+ isNested: sub?.parent_agent_id != null
59928
60147
  };
59929
60148
  }
59930
60149
 
@@ -59943,35 +60162,19 @@ function isOrphanSubagentStatusEnabled(envVal) {
59943
60162
  return envVal !== "0";
59944
60163
  }
59945
60164
 
59946
- // gateway/resolve-calling-subagent.ts
59947
- function resolveCallingSubagent(opts) {
59948
- if (opts.db == null)
59949
- return null;
59950
- try {
59951
- if (opts.agentIdHint != null) {
59952
- const row = opts.db.prepare("SELECT jsonl_agent_id FROM subagents WHERE jsonl_agent_id = ? AND status = 'running'").get(opts.agentIdHint);
59953
- if (row?.jsonl_agent_id)
59954
- return { agentId: row.jsonl_agent_id };
59955
- }
59956
- if (opts.toolUseIdHint != null) {
59957
- const row = opts.db.prepare("SELECT jsonl_agent_id FROM subagents WHERE id = ? AND status = 'running'").get(opts.toolUseIdHint);
59958
- if (row?.jsonl_agent_id)
59959
- return { agentId: row.jsonl_agent_id };
59960
- }
59961
- const turnRow = opts.db.prepare("SELECT turn_key FROM turns WHERE chat_id = ? AND ended_at IS NULL ORDER BY started_at DESC LIMIT 1").get(opts.chatId);
59962
- if (turnRow?.turn_key == null)
59963
- return null;
59964
- const candidates = opts.db.prepare("SELECT jsonl_agent_id FROM subagents WHERE parent_turn_key = ? AND status = 'running' AND jsonl_agent_id IS NOT NULL ORDER BY started_at DESC").all(turnRow.turn_key);
59965
- if (candidates.length === 0)
59966
- return null;
59967
- if (candidates.length > 1) {
59968
- console.warn(`progress_update: heuristic resolution selected most-recent of ${candidates.length} running sub-agents (chat=${opts.chatId}); pass agent_id explicitly to avoid mis-attribution`);
59969
- }
59970
- return { agentId: candidates[0].jsonl_agent_id };
59971
- } catch (err) {
59972
- console.warn("progress_update: resolveCallingSubagent SQL error", err);
59973
- return null;
59974
- }
60165
+ // status-query-telemetry.ts
60166
+ function deriveStatusQueryTelemetry(s) {
60167
+ const idle = !s.turnActive && s.runningWorkers <= 0 && s.workerFeedSize <= 0 && s.pendingAsync <= 0;
60168
+ const stage = idle ? "idle" : s.turnActive ? "turn-active" : "background-work";
60169
+ return { stage, idle };
60170
+ }
60171
+ function formatStatusQueryLine(agentName3, chatId, thread, s, t) {
60172
+ return `telegram gateway: status-query agent=${agentName3} chat_id=${chatId} thread=${thread} ` + `stage=${t.stage} turn_age_s=${s.turnAgeS} running_workers=${s.runningWorkers} ` + `worker_feed=${s.workerFeedSize} pending_async=${s.pendingAsync}
60173
+ `;
60174
+ }
60175
+ function formatStatusQueryUxFailureLine(agentName3, chatId, thread) {
60176
+ return `telegram gateway: ux-failure: status-query agent=${agentName3} chat_id=${chatId} thread=${thread} ` + `stage=idle turn_age_s=-1 running_workers=0 worker_feed=0 pending_async=0
60177
+ `;
59975
60178
  }
59976
60179
 
59977
60180
  // gateway/gateway.ts
@@ -60277,7 +60480,7 @@ function saveAccess(a) {
60277
60480
  return;
60278
60481
  mkdirSync32(STATE_DIR, { recursive: true, mode: 448 });
60279
60482
  const tmp = ACCESS_FILE + ".tmp";
60280
- writeFileSync31(tmp, JSON.stringify(a, null, 2) + `
60483
+ writeFileSync30(tmp, JSON.stringify(a, null, 2) + `
60281
60484
  `, { mode: 384 });
60282
60485
  renameSync14(tmp, ACCESS_FILE);
60283
60486
  }
@@ -60314,7 +60517,7 @@ try {
60314
60517
  let markerAgeMs = null;
60315
60518
  try {
60316
60519
  const markerPath = join40(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
60317
- if (existsSync44(markerPath)) {
60520
+ if (existsSync43(markerPath)) {
60318
60521
  const st = statSync15(markerPath);
60319
60522
  markerAgeMs = Date.now() - st.mtimeMs;
60320
60523
  try {
@@ -60400,13 +60603,13 @@ try {
60400
60603
  pending2.interrupt_reason != null ? `SWITCHROOM_PENDING_INTERRUPT_REASON=${pending2.interrupt_reason}` : `SWITCHROOM_PENDING_INTERRUPT_REASON=`
60401
60604
  ];
60402
60605
  const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
60403
- writeFileSync31(pendingEnvTmp, lines.join(`
60606
+ writeFileSync30(pendingEnvTmp, lines.join(`
60404
60607
  `) + `
60405
60608
  `, { mode: 384 });
60406
60609
  renameSync14(pendingEnvTmp, pendingEnvPath);
60407
60610
  process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
60408
60611
  `);
60409
- } else if (existsSync44(pendingEnvPath)) {
60612
+ } else if (existsSync43(pendingEnvPath)) {
60410
60613
  rmSync4(pendingEnvPath, { force: true });
60411
60614
  process.stderr.write(`telegram gateway: pending-turn env cleared (clean previous shutdown)
60412
60615
  `);
@@ -60424,10 +60627,10 @@ function resolveSubagentOriginChat(agentId) {
60424
60627
  if (turnsDb == null)
60425
60628
  return null;
60426
60629
  try {
60427
- const sub = getSubagentByJsonlId(turnsDb, agentId);
60428
- if (sub?.parent_turn_key == null)
60630
+ const originKey = resolveSubagentOriginTurnKey(turnsDb, agentId);
60631
+ if (originKey == null)
60429
60632
  return null;
60430
- const turn = getTurnByKey(turnsDb, sub.parent_turn_key);
60633
+ const turn = getTurnByKey(turnsDb, originKey);
60431
60634
  if (turn == null || turn.chat_id.length === 0)
60432
60635
  return null;
60433
60636
  const threadNum = turn.thread_id != null && turn.thread_id.length > 0 ? Number(turn.thread_id) : NaN;
@@ -60506,6 +60709,7 @@ var chatAvailableReactions = new Map;
60506
60709
  var chatProbesInFlight = new Set;
60507
60710
  var activeTurnStartedAt = new Map;
60508
60711
  var claudeBusyKeys = new Set;
60712
+ var claudeBusyKeySince = new Map;
60509
60713
  var reactionTransitionCounts = new Map;
60510
60714
  var firstTextReplyLogged = new Set;
60511
60715
  function markClaudeBusyForInbound(m) {
@@ -60516,9 +60720,20 @@ function markClaudeBusyForInbound(m) {
60516
60720
  tid = n;
60517
60721
  }
60518
60722
  const key = chatKey2(m.chatId, tid);
60519
- claudeBusyKeys.add(key);
60723
+ markBusyKeyLockstep(claudeBusyKeys, claudeBusyKeySince, key, Date.now());
60520
60724
  return key;
60521
60725
  }
60726
+ var _busyOrphanTtlRaw = process.env.SWITCHROOM_BUSY_ORPHAN_TTL_MS;
60727
+ var _busyOrphanTtlParsed = _busyOrphanTtlRaw != null && _busyOrphanTtlRaw !== "" ? Number(_busyOrphanTtlRaw) : 360000;
60728
+ var CLAUDE_BUSY_ORPHAN_TTL_MS = Number.isFinite(_busyOrphanTtlParsed) && _busyOrphanTtlParsed > 0 ? _busyOrphanTtlParsed : 360000;
60729
+ function reapOrphanBusyKeysNow(now) {
60730
+ reapOrphanBusyKeys(claudeBusyKeys, claudeBusyKeySince, now, {
60731
+ ttlMs: CLAUDE_BUSY_ORPHAN_TTL_MS,
60732
+ hasPendingDelivery: (key) => deliveryQueue.pending.has(key),
60733
+ log: (msg) => process.stderr.write(`${msg}
60734
+ `)
60735
+ });
60736
+ }
60522
60737
  var DELIVERY_CONFIRM_ENABLED = process.env.SWITCHROOM_INBOUND_DELIVERY_CONFIRM !== "0";
60523
60738
  var _deliveryTimeoutRaw = process.env.SWITCHROOM_INBOUND_DELIVERY_TIMEOUT_MS;
60524
60739
  var _deliveryTimeoutParsed = _deliveryTimeoutRaw != null && _deliveryTimeoutRaw !== "" ? Number(_deliveryTimeoutRaw) : 15000;
@@ -60572,9 +60787,9 @@ function noteAgentOutputAt(key, ts) {
60572
60787
  var OBLIGATION_STORE_PATH = join40(STATE_DIR, "obligations.json");
60573
60788
  var obligationStoreFs = {
60574
60789
  readFileSync: (p) => readFileSync41(p, "utf8"),
60575
- writeFileSync: (p, d) => writeFileSync31(p, d),
60790
+ writeFileSync: (p, d) => writeFileSync30(p, d),
60576
60791
  renameSync: (a, b) => renameSync14(a, b),
60577
- existsSync: (p) => existsSync44(p)
60792
+ existsSync: (p) => existsSync43(p)
60578
60793
  };
60579
60794
  var obligationLedger = new ObligationLedger(OBLIGATION_REPRESENT_MAX, {
60580
60795
  onChange: STATIC || !OBLIGATION_LEDGER_ENABLED ? undefined : (snapshot) => persistObligations(OBLIGATION_STORE_PATH, obligationStoreFs, snapshot)
@@ -60608,15 +60823,10 @@ var FEED_LIVENESS_OPEN_MS = (() => {
60608
60823
  return Number.isFinite(n) && n > 0 ? n : 1200;
60609
60824
  })();
60610
60825
  var POST_ANSWER_LIVENESS_STALE_MS = parsePostAnswerLivenessMs(process.env.SWITCHROOM_POST_ANSWER_LIVENESS_STALE_MS) || 30000;
60611
- function formatFeedElapsed3(ms) {
60612
- const s = Math.floor(ms / 1000);
60613
- if (s < 60)
60614
- return `${s}s`;
60615
- const m = Math.floor(s / 60);
60616
- return `${m}m${(s % 60).toString().padStart(2, "0")}s`;
60617
- }
60618
60826
  function turnInFlightForGate() {
60619
- return isDeliveryCutoverEnabled() ? isMachineInTurn() : claudeBusyKeys.size > 0;
60827
+ if (!isDeliveryCutoverEnabled())
60828
+ return claudeBusyKeys.size > 0;
60829
+ return probeGateParity(isMachineInTurn(), claudeBusyKeys.size);
60620
60830
  }
60621
60831
  function deliverResumeSyntheticOrBuffer(agent, inbound) {
60622
60832
  const decision = decideInboundDelivery({
@@ -60936,7 +61146,7 @@ var pendingPtyPartial = null;
60936
61146
  function statusKey(chatId, threadId) {
60937
61147
  return chatKey2(chatId, threadId);
60938
61148
  }
60939
- function streamKey3(chatId, threadId) {
61149
+ function streamKey2(chatId, threadId) {
60940
61150
  return chatKey2(chatId, threadId);
60941
61151
  }
60942
61152
  var pendingCrossTurnGate = new Map;
@@ -61002,6 +61212,7 @@ function purgeReactionTracking(key, endingTurn) {
61002
61212
  reapQueuedStatus(pqChatId, Number.isFinite(pqThread) ? pqThread : undefined);
61003
61213
  }
61004
61214
  claudeBusyKeys.delete(key);
61215
+ claudeBusyKeySince.delete(key);
61005
61216
  reactionTransitionCounts.delete(key);
61006
61217
  firstTextReplyLogged.delete(key);
61007
61218
  if (endingTurn != null) {
@@ -61064,6 +61275,7 @@ function releaseTurnBufferGate(key, endingTurn) {
61064
61275
  return;
61065
61276
  activeTurnStartedAt.delete(key);
61066
61277
  claudeBusyKeys.delete(key);
61278
+ claudeBusyKeySince.delete(key);
61067
61279
  shadowEmit({ kind: "turnEnd", key, at: Date.now(), outboundEmitted: true });
61068
61280
  drainBufferedIfAllowed(endingTurn, "reply-released-gate");
61069
61281
  }
@@ -62024,24 +62236,12 @@ var statusPinChatIds = new Map;
62024
62236
  var STATUS_PIN_STORE_PATH = join40(STATE_DIR, "status-pins.json");
62025
62237
  var statusPinStoreFs = {
62026
62238
  readFileSync: (p) => readFileSync41(p, "utf8"),
62027
- writeFileSync: (p, d) => writeFileSync31(p, d),
62239
+ writeFileSync: (p, d) => writeFileSync30(p, d),
62028
62240
  renameSync: (a, b) => renameSync14(a, b),
62029
- existsSync: (p) => existsSync44(p)
62241
+ existsSync: (p) => existsSync43(p)
62030
62242
  };
62031
62243
  var statusPinPersistEnabled = !STATIC && PIN_STATUS_WHILE_WORKING;
62032
- function snapshotStatusPins() {
62033
- const snapshot = [];
62034
- for (const [pinKey, state4] of statusPinState) {
62035
- const chatId = statusPinChatIds.get(pinKey);
62036
- if (chatId == null)
62037
- continue;
62038
- snapshot.push({ pinKey, chatId, messageId: state4.messageId });
62039
- }
62040
- return snapshot;
62041
- }
62042
- function snapshotStatusPinsExcept(exceptKey) {
62043
- return snapshotStatusPins().filter((p) => p.pinKey !== exceptKey);
62044
- }
62244
+ var bannerPinPersistEnabled = !STATIC;
62045
62245
  function statusPinApi() {
62046
62246
  return {
62047
62247
  pinChatMessage: (chat_id, message_id, opts) => robustApiCall(() => lockedBot.api.pinChatMessage(chat_id, message_id, opts), { chat_id: String(chat_id), verb: "status-pin.pin" }),
@@ -62049,7 +62249,7 @@ function statusPinApi() {
62049
62249
  };
62050
62250
  }
62051
62251
  async function statusPinBootCleanup() {
62052
- if (!statusPinPersistEnabled)
62252
+ if (!statusPinPersistEnabled && !bannerPinPersistEnabled)
62053
62253
  return;
62054
62254
  const api = statusPinApi();
62055
62255
  const { cleared, total } = await runStatusPinBootCleanup({
@@ -62107,7 +62307,6 @@ async function reconcileStatusPinInner(pinKey, chatId, desired) {
62107
62307
  pinKey,
62108
62308
  chatId,
62109
62309
  op,
62110
- snapshotOthers: () => snapshotStatusPinsExcept(pinKey),
62111
62310
  applyPin: runReconcile
62112
62311
  });
62113
62312
  if (next == null) {
@@ -62459,14 +62658,35 @@ async function redeliverStrandedInbound(p) {
62459
62658
  forgetDelivery(deliveryQueue, p.key);
62460
62659
  }
62461
62660
  }
62661
+ function sweepSuspendedTargets() {
62662
+ const keys = new Set;
62663
+ const chats = new Set;
62664
+ for (const p of pendingPermissions.values()) {
62665
+ for (const c of p.cards) {
62666
+ if (c.threadId != null)
62667
+ keys.add(chatKey2(c.chatId, c.threadId));
62668
+ else
62669
+ chats.add(c.chatId);
62670
+ }
62671
+ }
62672
+ for (const a of pendingAskUser.values()) {
62673
+ if (a.threadId != null)
62674
+ keys.add(chatKey2(a.chatId, a.threadId));
62675
+ else
62676
+ chats.add(a.chatId);
62677
+ }
62678
+ return { keys, chats };
62679
+ }
62462
62680
  var _deliveryConfirmSweep = setInterval(() => {
62463
62681
  if (!DELIVERY_CONFIRM_ENABLED)
62464
62682
  return;
62465
62683
  if (currentTurn != null)
62466
62684
  return;
62467
- if (pendingPermissions.size > 0 || pendingAskUser.size > 0)
62468
- return;
62685
+ reapOrphanBusyKeysNow(Date.now());
62686
+ const suspended = sweepSuspendedTargets();
62469
62687
  for (const p of sweep(deliveryQueue, Date.now(), DELIVERY_CONFIRM_TIMEOUT_MS)) {
62688
+ if (isRedeliverySuspended(p.key, suspended))
62689
+ continue;
62470
62690
  redeliverStrandedInbound(p);
62471
62691
  }
62472
62692
  }, DELIVERY_CONFIRM_SWEEP_MS);
@@ -62490,13 +62710,33 @@ var inboundSpool = STATIC ? undefined : createInboundSpool({
62490
62710
  fs: {
62491
62711
  appendFileSync: (p, d) => appendFileSync6(p, d),
62492
62712
  readFileSync: (p) => readFileSync41(p, "utf8"),
62493
- writeFileSync: (p, d) => writeFileSync31(p, d),
62713
+ writeFileSync: (p, d) => writeFileSync30(p, d),
62494
62714
  renameSync: (a, b) => renameSync14(a, b),
62495
- existsSync: (p) => existsSync44(p),
62715
+ existsSync: (p) => existsSync43(p),
62496
62716
  statSizeSync: (p) => statSync15(p).size
62717
+ },
62718
+ onDegraded: (info) => {
62719
+ process.stderr.write(`telegram gateway: inbound-spool durability ${info.degraded ? "DEGRADED to in-memory-only" : "RECOVERED"} path=${info.path} consecutiveFailures=${info.consecutiveFailures}${info.error != null ? ` error=${info.error}` : ""}
62720
+ `);
62721
+ }
62722
+ });
62723
+ var EVICT_NOTICE_COOLDOWN_MS = 300000;
62724
+ var evictNoticeByChat = new Map;
62725
+ var pendingInboundBuffer = createPendingInboundBuffer({
62726
+ spool: inboundSpool,
62727
+ onEvict: (_agent, evicted) => {
62728
+ const chat = evicted.chatId;
62729
+ const evThread = typeof evicted.meta?.threadId === "string" && evicted.meta.threadId ? Number(evicted.meta.threadId) : undefined;
62730
+ const key = `${chat}:${evThread ?? "-"}`;
62731
+ const last = evictNoticeByChat.get(key);
62732
+ const nowMs2 = Date.now();
62733
+ if (last != null && nowMs2 - last < EVICT_NOTICE_COOLDOWN_MS)
62734
+ return;
62735
+ evictNoticeByChat.set(key, nowMs2);
62736
+ const threadOpts = evThread != null ? { message_thread_id: evThread } : {};
62737
+ swallowingApiCall(() => bot.api.sendMessage(chat, "\u23F3 Messages are arriving faster than I can process them. Your messages are saved and will be handled once I finish the current turn \u2014 if any can't be picked up, I'll ask you to resend it.", { ...threadOpts }), { chat_id: chat, verb: "inbound-buffer-eviction" });
62497
62738
  }
62498
62739
  });
62499
- var pendingInboundBuffer = createPendingInboundBuffer({ spool: inboundSpool });
62500
62740
  function agentHasInFlightBackgroundWork(now) {
62501
62741
  if (countRunningWorkers() > 0)
62502
62742
  return true;
@@ -62546,6 +62786,11 @@ function obligationSweep() {
62546
62786
  pendingCrossTurnGate.set(o.originTurnId, { sinceMs: o.openedAt });
62547
62787
  const attempt = obligationLedger.markRepresented(o.originTurnId);
62548
62788
  process.stderr.write(`telegram gateway: obligation re-presented origin=${o.originTurnId} attempt=${attempt}/${OBLIGATION_REPRESENT_MAX}
62789
+ `);
62790
+ return;
62791
+ }
62792
+ if (shouldDeferEscalationForBridge({ bridgeAlive: ipcServer.getClient(agent)?.isAlive() === true })) {
62793
+ process.stderr.write(`telegram gateway: obligation escalation deferred \u2014 bridge down (nudge waits for reconnect) origin=${o.originTurnId}
62549
62794
  `);
62550
62795
  return;
62551
62796
  }
@@ -62575,6 +62820,15 @@ if (bootResumeInbound != null) {
62575
62820
  } else {
62576
62821
  pendingInboundBuffer.push(bootResumeInbound.agent, bootResumeInbound.msg);
62577
62822
  }
62823
+ const resumeTurnKey = bootResumeInbound.msg.meta?.resume_turn_key;
62824
+ if (turnsDb != null && typeof resumeTurnKey === "string" && resumeTurnKey.length > 0) {
62825
+ try {
62826
+ markTurnResumed(turnsDb, resumeTurnKey);
62827
+ } catch (err) {
62828
+ process.stderr.write(`telegram gateway: markTurnResumed failed turnKey=${resumeTurnKey}: ${err.message}
62829
+ `);
62830
+ }
62831
+ }
62578
62832
  }
62579
62833
  if (inboundSpool != null) {
62580
62834
  const replay = inboundSpool.liveEntries();
@@ -62759,6 +63013,7 @@ var ipcServer = createIpcServer({
62759
63013
  activeReactionMsgIds,
62760
63014
  activeTurnStartedAt,
62761
63015
  claudeBusyKeys,
63016
+ claudeBusyKeySince,
62762
63017
  activeDraftStreams,
62763
63018
  clearActiveReactions: () => {
62764
63019
  const ad = resolveAgentDirFromEnv();
@@ -62800,9 +63055,6 @@ var ipcServer = createIpcServer({
62800
63055
  if (msg.activeFile)
62801
63056
  lastSessionActiveFile = msg.activeFile;
62802
63057
  const ev = msg.event;
62803
- const chatHint = msg.chatId || null;
62804
- const threadHint = msg.threadId != null ? String(msg.threadId) : undefined;
62805
- progressDriver?.ingest(ev, chatHint, threadHint);
62806
63058
  handleSessionEvent(ev);
62807
63059
  toolFlightTracker.onEvent(ev);
62808
63060
  if (pendingDeferredInterrupt != null && !toolFlightTracker.isMidToolCall()) {
@@ -62872,7 +63124,8 @@ var ipcServer = createIpcServer({
62872
63124
  }), { threadId, chat_id: chatId, verb: "permission_request" }).then((sent) => {
62873
63125
  const pend = pendingPermissions.get(requestId);
62874
63126
  if (pend && sent && typeof sent.message_id === "number") {
62875
- pend.cards.push({ chatId, messageId: sent.message_id });
63127
+ const landedThreadId = sent.message_thread_id ?? undefined;
63128
+ pend.cards.push({ chatId, messageId: sent.message_id, threadId: landedThreadId });
62876
63129
  permCardStore.add({
62877
63130
  requestId,
62878
63131
  chatId,
@@ -63383,20 +63636,21 @@ if (!STATIC) {
63383
63636
  process.stderr.write(`telegram gateway: idle-drain flushed ${r.redelivered}/${r.drained} buffered inbound for ${selfAgent}${r.rebuffered > 0 ? ` (${r.rebuffered} re-buffered)` : ""}
63384
63637
  `);
63385
63638
  }
63386
- inboundSpool?.sweepEscalations((e, { postNotice }) => {
63639
+ inboundSpool?.sweepEscalations((e, { postNotice, droppedCount }) => {
63387
63640
  const chat = e.msg.chatId;
63388
63641
  const escThread = typeof e.msg.meta?.threadId === "string" && e.msg.meta.threadId ? Number(e.msg.meta.threadId) : undefined;
63389
63642
  const threadOpts = escThread != null ? { message_thread_id: escThread } : {};
63390
63643
  reapQueuedStatus(chat, escThread);
63391
63644
  if (!postNotice)
63392
63645
  return;
63393
- swallowingApiCall(() => bot.api.sendMessage(chat, "\u26A0\uFE0F I couldn't deliver an earlier message to the agent after repeated retries (it survived restarts but the agent never picked it up). Please resend it.", { ...threadOpts }), { chat_id: chat, verb: "inbound-spool-escalation" });
63646
+ const n = droppedCount > 0 ? droppedCount : 1;
63647
+ const noticeText = n === 1 ? "\u26A0\uFE0F I couldn't deliver an earlier message to the agent after repeated retries (it survived restarts but the agent never picked it up). Please resend it." : `\u26A0\uFE0F I couldn't deliver ${n} earlier messages to the agent after repeated retries (they survived restarts but the agent never picked them up). Please resend them.`;
63648
+ swallowingApiCall(() => bot.api.sendMessage(chat, noticeText, { ...threadOpts }), { chat_id: chat, verb: "inbound-spool-escalation" });
63394
63649
  });
63395
63650
  }, IDLE_DRAIN_INTERVAL_MS).unref();
63396
63651
  }
63397
63652
  var ALLOWED_TOOLS = new Set([
63398
63653
  "reply",
63399
- "stream_reply",
63400
63654
  "progress_update",
63401
63655
  "react",
63402
63656
  "download_attachment",
@@ -63425,8 +63679,6 @@ async function executeToolCall(tool, args) {
63425
63679
  switch (tool) {
63426
63680
  case "reply":
63427
63681
  return executeReply(args);
63428
- case "stream_reply":
63429
- return executeStreamReply(args);
63430
63682
  case "progress_update":
63431
63683
  return executeProgressUpdate(args);
63432
63684
  case "react":
@@ -63895,7 +64147,7 @@ ${url}`;
63895
64147
  }
63896
64148
  }
63897
64149
  }
63898
- const replySKey = streamKey3(chat_id, threadId);
64150
+ const replySKey = streamKey2(chat_id, threadId);
63899
64151
  suppressPtyPreview.add(replySKey);
63900
64152
  let previewMessageId = null;
63901
64153
  const openStream = activeDraftStreams.get(replySKey);
@@ -64293,221 +64545,6 @@ ${url}`;
64293
64545
  }
64294
64546
  return { content: [{ type: "text", text: result }] };
64295
64547
  }
64296
- async function executeStreamReply(args) {
64297
- const turn = currentTurn;
64298
- if (!args.chat_id)
64299
- throw new Error("stream_reply: chat_id is required");
64300
- if (args.text == null || args.text === "")
64301
- throw new Error("stream_reply: text is required and cannot be empty");
64302
- {
64303
- const _rawChatId = String(args.chat_id ?? "");
64304
- const resolved = resolveChatIdFallback(_rawChatId, loadAccess(), turn?.sessionChatId, lastActiveTurnChatId, turn != null);
64305
- if (resolved.tier !== "raw") {
64306
- process.stderr.write(`telegram gateway: stream_reply: model passed chat_id "${_rawChatId}" (not allowlisted) \u2014 ` + `routing to ${resolved.tier} turn chat "${resolved.chatId}"
64307
- `);
64308
- args.chat_id = resolved.chatId;
64309
- }
64310
- }
64311
- let streamRoutedOriginTurn = null;
64312
- let streamOriginVia = null;
64313
- if (TURN_ORIGIN_ROUTING_ENABLED) {
64314
- const echoedTurn = findTurnByOriginId(args.origin_turn_id);
64315
- const quotedTurn = echoedTurn == null ? findTurnByQuotedMessageId(String(args.chat_id), args.reply_to) : null;
64316
- const originTurn = echoedTurn ?? quotedTurn;
64317
- streamRoutedOriginTurn = originTurn ?? null;
64318
- streamOriginVia = originTurn == null ? null : echoedTurn != null ? "echo" : "quoted";
64319
- }
64320
- if (args.message_thread_id == null) {
64321
- let injected;
64322
- if (TURN_ORIGIN_ROUTING_ENABLED) {
64323
- injected = resolveAnswerThreadWithLog(String(args.chat_id), undefined, streamRoutedOriginTurn, streamOriginVia, turn, "stream_reply");
64324
- } else {
64325
- injected = turn?.sessionThreadId;
64326
- }
64327
- if (injected != null)
64328
- args.message_thread_id = String(injected);
64329
- }
64330
- args.text = redactOutboundText(args.text, "stream_reply");
64331
- {
64332
- args.text = normalizePunctuation(args.text);
64333
- const scrub = scrubVoice(args.text);
64334
- if (scrub.replaced > 0) {
64335
- args.text = scrub.scrubbed;
64336
- emitRuntimeMetric({
64337
- kind: "voice_scrub_applied",
64338
- chatKey: statusKey(String(args.chat_id ?? ""), args.message_thread_id != null ? Number(args.message_thread_id) : undefined),
64339
- replaced: scrub.replaced,
64340
- site: "stream_reply"
64341
- });
64342
- }
64343
- }
64344
- if (args.done === true) {
64345
- const sChatId = String(args.chat_id ?? "");
64346
- const sThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
64347
- const sText = args.text;
64348
- const dup = outboundDedup.check(sChatId, sThreadId, sText, Date.now(), currentTurn?.registryKey ?? null);
64349
- if (dup != null) {
64350
- process.stderr.write(`telegram gateway: stream_reply: deduped (#546) chatId=${sChatId} ageMs=${dup.ageMs} preview=${JSON.stringify(dup.preview)}
64351
- `);
64352
- return { content: [{ type: "text", text: "sent (deduped \u2014 same content sent via earlier path)" }] };
64353
- }
64354
- }
64355
- const access = loadAccess();
64356
- const streamChatId = String(args.chat_id ?? "");
64357
- const streamIsPrivate = isDmChatId(streamChatId);
64358
- const streamIsForumTopic = args.message_thread_id != null && args.message_thread_id !== "";
64359
- let streamReplyMarkup;
64360
- let streamButtonMeta;
64361
- const rawStreamKeyboard = args.inline_keyboard;
64362
- if (rawStreamKeyboard != null && Boolean(args.done)) {
64363
- const validationErrors = validateInlineKeyboard(rawStreamKeyboard);
64364
- if (validationErrors.length > 0) {
64365
- const summary = validationErrors.map((e) => `${e.path}.${e.field}: ${e.reason}`).join("; ");
64366
- throw new Error(`inline_keyboard validation failed: ${summary}`);
64367
- }
64368
- streamButtonMeta = extractAgentButtonMeta(rawStreamKeyboard);
64369
- streamReplyMarkup = { inline_keyboard: wrapAgentCallbacks(rawStreamKeyboard) };
64370
- }
64371
- {
64372
- const streamThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
64373
- const sKeyBefore = streamKey3(streamChatId, streamThreadId);
64374
- if (!activeDraftStreams.has(sKeyBefore)) {
64375
- const sKey = statusKey(streamChatId, streamThreadId);
64376
- noteOutbound(sKey, Date.now());
64377
- noteOutbound2(sKey, Date.now());
64378
- shadowEmit({ kind: "modelOutbound", key: sKey, at: Date.now() });
64379
- if (turn != null && !firstTextReplyLogged.has(sKey)) {
64380
- firstTextReplyLogged.add(sKey);
64381
- logStreamingEvent({
64382
- kind: "turn_reply_timing",
64383
- chatId: streamChatId,
64384
- threadId: streamThreadId,
64385
- turnId: turn.turnId,
64386
- timeToFirstTextReplyMs: Date.now() - turn.gatewayReceiveAt
64387
- });
64388
- }
64389
- if (isFinalAnswerReply({
64390
- text: args.text ?? "",
64391
- disableNotification: args.disable_notification === true,
64392
- done: args.done === true
64393
- })) {
64394
- clearSilentEndState(sKey);
64395
- }
64396
- }
64397
- }
64398
- if (turn != null && isSubstantiveFinalReply({
64399
- text: args.text ?? "",
64400
- disableNotification: args.disable_notification === true,
64401
- done: args.done === true
64402
- })) {
64403
- const ea = emissionAuthorityFor(turn);
64404
- ea.markSubstantiveFinalDelivered(() => {
64405
- turn.finalAnswerEverDelivered = true;
64406
- if (turn.finalAnswerDeliveredAt == null)
64407
- turn.finalAnswerDeliveredAt = Date.now();
64408
- });
64409
- ea.finalizeCard(() => {
64410
- clearActivitySummary(turn);
64411
- });
64412
- }
64413
- const result = await handleStreamReply({
64414
- chat_id: streamChatId,
64415
- text: args.text,
64416
- done: Boolean(args.done),
64417
- message_thread_id: args.message_thread_id,
64418
- format: args.format,
64419
- reply_to: args.reply_to,
64420
- quote: args.quote,
64421
- ...args.protect_content === true ? { protect_content: true } : {},
64422
- ...args.quote_text != null ? { quote_text: args.quote_text } : {},
64423
- ...streamReplyMarkup != null ? { reply_markup: streamReplyMarkup } : {},
64424
- ...args.disable_notification === true ? { disable_notification: true } : {}
64425
- }, { activeDraftStreams, suppressPtyPreview }, {
64426
- bot: lockedBot,
64427
- retry: robustApiCall,
64428
- repairEscapedWhitespace,
64429
- normalizeParagraphBreaks,
64430
- normalizePunctuation,
64431
- stripExcessBold,
64432
- addParagraphSpacers,
64433
- assertAllowedChat,
64434
- resolveThreadId,
64435
- disableLinkPreview: access.disableLinkPreview !== false,
64436
- defaultFormat: access.parseMode ?? "html",
64437
- logStreamingEvent,
64438
- isPrivateChat: streamIsPrivate,
64439
- isForumTopic: streamIsForumTopic,
64440
- recordOutboundDelivered: (chatId, threadId) => {
64441
- progressDriver?.recordOutboundDelivered(chatId, threadId != null ? String(threadId) : undefined);
64442
- },
64443
- forceCompleteTurn: (chatId, threadId) => {
64444
- progressDriver?.forceCompleteTurn({
64445
- chatId,
64446
- threadId: threadId != null ? String(threadId) : undefined
64447
- });
64448
- },
64449
- historyEnabled: HISTORY_ENABLED,
64450
- recordOutbound,
64451
- ...HISTORY_ENABLED ? { getLatestInboundMessageId } : {},
64452
- writeError: (line) => process.stderr.write(line),
64453
- ...STREAM_THROTTLE_MS_OVERRIDE != null ? { throttleMs: STREAM_THROTTLE_MS_OVERRIDE } : {},
64454
- progressCardActive: streamMode === "checklist"
64455
- });
64456
- if (result.messageId != null) {
64457
- try {
64458
- progressDriver?.recordOutboundDelivered(String(args.chat_id ?? ""), args.message_thread_id);
64459
- } catch {}
64460
- try {
64461
- const threadIdNum2 = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
64462
- noteSignal(statusKey(String(args.chat_id ?? ""), threadIdNum2), Date.now());
64463
- } catch {}
64464
- }
64465
- if (args.done === true && result.messageId != null && streamButtonMeta != null && streamButtonMeta.size > 0) {
64466
- rememberAgentButtonMeta(String(args.chat_id ?? ""), result.messageId, streamButtonMeta);
64467
- }
64468
- if (args.done === true && result.messageId != null) {
64469
- const sChatId = String(args.chat_id ?? "");
64470
- const sThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
64471
- outboundDedup.record(sChatId, sThreadId, args.text, Date.now(), currentTurn?.registryKey ?? null);
64472
- const streamFormat = args.format ?? (access.parseMode ?? "html");
64473
- const streamLiteralText = streamFormat === "text";
64474
- clearPending(statusKey(sChatId, sThreadId), "reply_finalize");
64475
- noteOutbound3(statusKey(sChatId, sThreadId), {
64476
- messageId: result.messageId,
64477
- text: args.text,
64478
- literalText: streamLiteralText
64479
- });
64480
- }
64481
- if (turn != null && isFinalAnswerReply({
64482
- text: args.text ?? "",
64483
- disableNotification: args.disable_notification === true,
64484
- done: args.done === true
64485
- })) {
64486
- turn.finalAnswerDelivered = true;
64487
- turn.finalAnswerSubstantive = isSubstantiveFinalReply({
64488
- text: args.text ?? "",
64489
- disableNotification: args.disable_notification === true,
64490
- done: args.done === true
64491
- });
64492
- if (turn.finalAnswerSubstantive)
64493
- turn.finalAnswerEverDelivered = true;
64494
- if (turn.finalAnswerSubstantive && turn.finalAnswerDeliveredAt == null)
64495
- turn.finalAnswerDeliveredAt = Date.now();
64496
- if (turn.finalAnswerSubstantive)
64497
- closeObligationOnSubstantiveReply(args, turn, streamRoutedOriginTurn);
64498
- const streamThreadIdForClear = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
64499
- clearSilentEndState(statusKey(streamChatId, streamThreadIdForClear));
64500
- }
64501
- {
64502
- const sChat = String(args.chat_id ?? "");
64503
- const sThread = resolveThreadId(sChat, args.message_thread_id);
64504
- releaseTurnBufferGate(statusKey(sChat, sThread), turn ?? undefined);
64505
- if (turn?.finalAnswerDelivered === true) {
64506
- reapQueuedStatus(turn.sessionChatId, turn.sessionThreadId);
64507
- }
64508
- }
64509
- return { content: [{ type: "text", text: `${result.status} (id: ${result.messageId ?? "pending"})` }] };
64510
- }
64511
64548
  async function executeProgressUpdate(args) {
64512
64549
  if (!args.chat_id)
64513
64550
  throw new Error("progress_update: chat_id is required");
@@ -64518,6 +64555,7 @@ async function executeProgressUpdate(args) {
64518
64555
  const threadId = resolveThreadId(chat_id, args.message_thread_id);
64519
64556
  const key = statusKey(chat_id, threadId);
64520
64557
  assertAllowedChat(chat_id);
64558
+ text2 = redactOutboundText(text2, "progress_update");
64521
64559
  if (text2.length > 300) {
64522
64560
  text2 = text2.slice(0, 299) + "\u2026";
64523
64561
  }
@@ -64551,38 +64589,6 @@ async function executeProgressUpdate(args) {
64551
64589
  }
64552
64590
  progressUpdateTurnCount.set(key, currentCount + 1);
64553
64591
  }
64554
- const agentIdHint = typeof args.agent_id === "string" && args.agent_id || null;
64555
- const toolUseIdHint = typeof args.tool_use_id === "string" && args.tool_use_id || null;
64556
- const subAgent = resolveCallingSubagent({
64557
- db: turnsDb,
64558
- chatId: chat_id,
64559
- threadId,
64560
- agentIdHint,
64561
- toolUseIdHint
64562
- });
64563
- if (subAgent != null && progressDriver != null) {
64564
- const cardText = text2.length > 200 ? text2.slice(0, 199) + "\u2026" : text2;
64565
- const result = progressDriver.recordSubAgentNarrative({
64566
- chatId: chat_id,
64567
- threadId: threadId != null ? String(threadId) : undefined,
64568
- agentId: subAgent.agentId,
64569
- text: cardText
64570
- });
64571
- if (result.ok) {
64572
- progressUpdateLastSent.set(key, now);
64573
- try {
64574
- noteSignal(key, Date.now());
64575
- } catch {}
64576
- return {
64577
- content: [
64578
- {
64579
- type: "text",
64580
- text: JSON.stringify({ ok: true, mode: "card", agent_id: subAgent.agentId })
64581
- }
64582
- ]
64583
- };
64584
- }
64585
- }
64586
64592
  const access = loadAccess();
64587
64593
  const literalText = (access.parseMode ?? "html") === "text";
64588
64594
  const sendOpts = {
@@ -64764,7 +64770,7 @@ async function publishToTelegraph(text2, shortName, authorName) {
64764
64770
  const accountPath = join40(STATE_DIR, "telegraph-account.json");
64765
64771
  let account = null;
64766
64772
  try {
64767
- if (existsSync44(accountPath)) {
64773
+ if (existsSync43(accountPath)) {
64768
64774
  const raw = readFileSync41(accountPath, "utf-8");
64769
64775
  const parsed = JSON.parse(raw);
64770
64776
  if (parsed.shortName && parsed.accessToken) {
@@ -64785,7 +64791,7 @@ async function publishToTelegraph(text2, shortName, authorName) {
64785
64791
  account = created.value;
64786
64792
  try {
64787
64793
  mkdirSync32(STATE_DIR, { recursive: true, mode: 448 });
64788
- writeFileSync31(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
64794
+ writeFileSync30(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
64789
64795
  } catch (err) {
64790
64796
  process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
64791
64797
  `);
@@ -64830,8 +64836,8 @@ function renderVaultRequestSaveCard(req, agentSlug) {
64830
64836
  }
64831
64837
  lines.push("");
64832
64838
  lines.push(`_Tap Save to write to the host vault, Rename to change the key name, or Discard to drop it. The value is held in this chat's gateway memory until you decide._`);
64833
- return lines.join(`
64834
- `);
64839
+ return hardenCardBreaks2(lines.join(`
64840
+ `));
64835
64841
  }
64836
64842
  async function executeVaultRequestSave(args) {
64837
64843
  const chat_id = String(args.chat_id ?? "");
@@ -65227,7 +65233,7 @@ async function executeDownloadAttachment(args) {
65227
65233
  });
65228
65234
  mkdirSync32(INBOX_DIR, { recursive: true, mode: 448 });
65229
65235
  assertInsideInbox(INBOX_DIR, dlPath);
65230
- writeFileSync31(dlPath, buf, { mode: 384 });
65236
+ writeFileSync30(dlPath, buf, { mode: 384 });
65231
65237
  return { content: [{ type: "text", text: dlPath }] };
65232
65238
  }
65233
65239
  async function executeEditMessage(args) {
@@ -65402,7 +65408,7 @@ function resetOrphanedReplyTimeout() {
65402
65408
  currentSessionChatId: turn.sessionChatId,
65403
65409
  capturedTextCount: turn.capturedText.length,
65404
65410
  replyCalled: turn.replyCalled,
65405
- progressCardActive: progressDriver != null
65411
+ progressCardActive: false
65406
65412
  })) {
65407
65413
  turn.orphanedReplyTimeoutId = setTimeout(() => {
65408
65414
  const t = currentTurn;
@@ -65413,7 +65419,7 @@ function resetOrphanedReplyTimeout() {
65413
65419
  currentSessionChatId: t.sessionChatId,
65414
65420
  capturedTextCount: t.capturedText.length,
65415
65421
  replyCalled: t.replyCalled,
65416
- progressCardActive: progressDriver != null
65422
+ progressCardActive: false
65417
65423
  })) {
65418
65424
  const turnKey = statusKey(t.sessionChatId, t.sessionThreadId);
65419
65425
  const working = isLegitimatelyWorking(turnKey);
@@ -65583,7 +65589,7 @@ function openLivenessFeedIfDue(turn) {
65583
65589
  toolCount: turn.labeledToolCount,
65584
65590
  state: "running"
65585
65591
  };
65586
- const rendered = renderActivityFeedWithNested(lines, [], false, ` \xB7 ${formatFeedElapsed3(age)}`, undefined, livenessHeader);
65592
+ const rendered = renderActivityFeedWithNested(lines, [], false, formatStepSuffix2(age), undefined, livenessHeader);
65587
65593
  if (rendered == null)
65588
65594
  return;
65589
65595
  turn.activityPendingRender = rendered;
@@ -65646,7 +65652,7 @@ function feedHeartbeatTick() {
65646
65652
  };
65647
65653
  const lines = turn.mirrorLines.length > 0 ? turn.mirrorLines : ["Working in background\u2026"];
65648
65654
  const elapsed2 = Date.now() - subagentAt;
65649
- const rendered2 = renderActivityFeedWithNested(lines, [], false, ` \xB7 ${formatFeedElapsed3(elapsed2)}`, undefined, livenessHeader);
65655
+ const rendered2 = renderActivityFeedWithNested(lines, [], false, formatStepSuffix2(elapsed2), undefined, livenessHeader);
65650
65656
  if (rendered2 == null)
65651
65657
  return;
65652
65658
  turn.activityPendingRender = rendered2;
@@ -65671,7 +65677,7 @@ function feedHeartbeatTick() {
65671
65677
  const elapsed = Date.now() - turn.lastToolLabelAt;
65672
65678
  if (elapsed < FEED_HEARTBEAT_MIN_STALE_MS)
65673
65679
  return;
65674
- const rendered = composeTurnActivity(turn, false, ` \xB7 ${formatFeedElapsed3(elapsed)}`);
65680
+ const rendered = composeTurnActivity(turn, false, formatStepSuffix2(elapsed));
65675
65681
  if (rendered == null)
65676
65682
  return;
65677
65683
  turn.activityPendingRender = rendered;
@@ -65799,7 +65805,7 @@ function handleSessionEvent(ev) {
65799
65805
  rememberRecentTurn(next);
65800
65806
  promoteQueuedStatus(ev.chatId, enqThreadIdNum);
65801
65807
  if (DELIVERY_CONFIRM_ENABLED) {
65802
- ackDelivery(deliveryQueue, chatKey2(ev.chatId, ev.threadId != null ? Number(ev.threadId) : null), ev.messageId);
65808
+ ackDelivery(deliveryQueue, chatKey2(ev.chatId, ev.threadId != null ? Number(ev.threadId) : null), ev.messageId, ev.rawContent);
65803
65809
  }
65804
65810
  shadowEmit({
65805
65811
  kind: "turnStart",
@@ -66149,7 +66155,7 @@ function handleSessionEvent(ev) {
66149
66155
  unpinProgressCardForChat?.(chatId, threadId);
66150
66156
  finalizeStatusReaction(chatId, threadId, "done");
66151
66157
  {
66152
- const sKey = streamKey3(chatId, threadId);
66158
+ const sKey = streamKey2(chatId, threadId);
66153
66159
  const turnDurationMs = turn.startedAt > 0 ? Date.now() - turn.startedAt : 0;
66154
66160
  logStreamingEvent({
66155
66161
  kind: "turn_end",
@@ -66194,10 +66200,12 @@ function handleSessionEvent(ev) {
66194
66200
  }
66195
66201
  if (flushDecision.kind === "flush") {
66196
66202
  let capturedText = flushDecision.text;
66203
+ capturedText = normalizeParagraphBreaks(repairEscapedWhitespace(capturedText));
66197
66204
  const backstopChatId = chatId;
66198
66205
  const backstopThreadId = threadId;
66199
66206
  const backstopCtrl = ctrl;
66200
66207
  capturedText = redactOutboundText(capturedText, "turn_flush");
66208
+ capturedText = stripExcessBold(normalizePunctuation(capturedText));
66201
66209
  {
66202
66210
  const scrub = scrubVoice(capturedText);
66203
66211
  if (scrub.replaced > 0) {
@@ -66247,7 +66255,8 @@ function handleSessionEvent(ev) {
66247
66255
  link_preview_options: { is_disabled: true }
66248
66256
  };
66249
66257
  const limit = RICH_MESSAGE_MAX_CHARS2;
66250
- const htmlChunks = splitMarkdownChunks(capturedText, limit);
66258
+ const renderedText = addParagraphSpacers(capturedText);
66259
+ const htmlChunks = splitMarkdownChunks(renderedText, limit);
66251
66260
  const sentIds = [];
66252
66261
  try {
66253
66262
  let firstSendUsedEdit = false;
@@ -66336,7 +66345,7 @@ function handleSessionEvent(ev) {
66336
66345
  }
66337
66346
  finalizeStatusReaction(chatId, threadId, terminalReason);
66338
66347
  {
66339
- const sKey = streamKey3(chatId, threadId);
66348
+ const sKey = streamKey2(chatId, threadId);
66340
66349
  const turnDurationMs = turn.startedAt > 0 ? Date.now() - turn.startedAt : 0;
66341
66350
  logStreamingEvent({
66342
66351
  kind: "turn_end",
@@ -66736,15 +66745,23 @@ async function handleInbound(ctx, text2, downloadImage, attachment, extraAttachm
66736
66745
  }
66737
66746
  if (STATUS_QUERY_RE.test(text2)) {
66738
66747
  try {
66739
- const threadKey = messageThreadId != null ? String(messageThreadId) : undefined;
66740
- const cardState = progressDriver?.peek(chat_id, threadKey);
66741
- const turnAgeS = cardState?.turnStartedAt ? Math.max(0, Math.floor((Date.now() - cardState.turnStartedAt) / 1000)) : -1;
66742
- const stage = cardState?.stage ?? "idle";
66743
- const itemCount = cardState?.items.length ?? 0;
66744
- const subAgentCount = cardState?.subAgents.size ?? 0;
66748
+ const thread = messageThreadId != null ? String(messageThreadId) : "none";
66749
+ const key = statusKey(chat_id, messageThreadId);
66750
+ const turnStartedAt = activeTurnStartedAt.get(key);
66751
+ const turnActive = turnStartedAt != null;
66752
+ const surfaces = {
66753
+ turnActive,
66754
+ turnAgeS: turnActive ? Math.max(0, Math.floor((Date.now() - turnStartedAt) / 1000)) : -1,
66755
+ runningWorkers: countRunningWorkers(),
66756
+ workerFeedSize: workerActivityFeed?.size ?? 0,
66757
+ pendingAsync: hasPendingAsyncDispatch(key) ? 1 : 0
66758
+ };
66759
+ const telemetry = deriveStatusQueryTelemetry(surfaces);
66745
66760
  const agentName3 = process.env.SWITCHROOM_AGENT_NAME ?? "-";
66746
- process.stderr.write(`telegram gateway: ux-failure: status-query agent=${agentName3} chat_id=${chat_id} thread=${threadKey ?? "none"} card_stage=${stage} card_turn_age_s=${turnAgeS} card_items=${itemCount} card_subagents=${subAgentCount}
66747
- `);
66761
+ process.stderr.write(formatStatusQueryLine(agentName3, String(chat_id), thread, surfaces, telemetry));
66762
+ if (telemetry.idle) {
66763
+ process.stderr.write(formatStatusQueryUxFailureLine(agentName3, String(chat_id), thread));
66764
+ }
66748
66765
  } catch (err) {
66749
66766
  process.stderr.write(`telegram gateway: status-query telemetry failed: ${err.message}
66750
66767
  `);
@@ -67113,7 +67130,7 @@ ${preBlock(write.output)}`;
67113
67130
  bot.api.setMessageReaction(chat_id, msgId, [{ type: "emoji", emoji: "\uD83D\uDC40" }]).catch(() => {});
67114
67131
  logStreamingEvent({ kind: "inbound_ack", chatId: chat_id, messageId: msgId, ackDelayMs: Date.now() - inboundReceivedAt });
67115
67132
  } else {
67116
- const sKey = streamKey3(chat_id, messageThreadId);
67133
+ const sKey = streamKey2(chat_id, messageThreadId);
67117
67134
  const priorStream = activeDraftStreams.get(sKey);
67118
67135
  if (priorStream && !priorStream.isFinal()) {
67119
67136
  activeDraftStreams.delete(sKey);
@@ -67427,7 +67444,7 @@ async function switchroomReply(ctx, text2, options = {}) {
67427
67444
  ...options.reply_markup ? { reply_markup: options.reply_markup } : {}
67428
67445
  };
67429
67446
  if (options.html) {
67430
- await ctx.replyWithRichMessage(richMessage2(text2), replyOpts);
67447
+ await ctx.replyWithRichMessage(richMessage2(hardenCardBreaks2(text2)), replyOpts);
67431
67448
  } else {
67432
67449
  await ctx.reply(text2, replyOpts);
67433
67450
  }
@@ -67486,7 +67503,7 @@ function writeRestartMarker(marker) {
67486
67503
  if (!p)
67487
67504
  return;
67488
67505
  try {
67489
- writeFileSync31(p, JSON.stringify(marker));
67506
+ writeFileSync30(p, JSON.stringify(marker));
67490
67507
  lastPlannedRestartAt = Date.now();
67491
67508
  process.stderr.write(`telegram gateway: restart-marker: write chat_id=${marker.chat_id} thread_id=${marker.thread_id ?? "-"} ack=${marker.ack_message_id ?? "-"} path=${p}
67492
67509
  `);
@@ -67654,7 +67671,7 @@ var _dockerReachable;
67654
67671
  function isDockerReachable() {
67655
67672
  if (_dockerReachable !== undefined)
67656
67673
  return _dockerReachable;
67657
- if (!existsSync44("/var/run/docker.sock")) {
67674
+ if (!existsSync43("/var/run/docker.sock")) {
67658
67675
  _dockerReachable = false;
67659
67676
  return _dockerReachable;
67660
67677
  }
@@ -67676,7 +67693,7 @@ function spawnSwitchroomDetached(args, onFailure) {
67676
67693
  try {
67677
67694
  mkdirSync32(STATE_DIR, { recursive: true });
67678
67695
  outFd = openSync9(logPath, "a");
67679
- writeFileSync31(logPath, `
67696
+ writeFileSync30(logPath, `
67680
67697
  [${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
67681
67698
  `, { flag: "a" });
67682
67699
  } catch {}
@@ -68070,7 +68087,7 @@ bot.use(async (ctx, next) => {
68070
68087
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
68071
68088
  try {
68072
68089
  const auditPath = join40(homedir14(), ".switchroom", "vault-audit.log");
68073
- if (!existsSync44(auditPath))
68090
+ if (!existsSync43(auditPath))
68074
68091
  return [];
68075
68092
  const raw = readFileSync41(auditPath, "utf8");
68076
68093
  return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
@@ -68498,7 +68515,7 @@ function flushAgentHandoff(agentDir) {
68498
68515
  for (const fname of [".handoff.md", ".handoff-topic"]) {
68499
68516
  const p = join40(agentDir, fname);
68500
68517
  try {
68501
- if (existsSync44(p)) {
68518
+ if (existsSync43(p)) {
68502
68519
  unlinkSync16(p);
68503
68520
  removed++;
68504
68521
  }
@@ -68554,7 +68571,7 @@ async function handleNewCommand(ctx) {
68554
68571
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
68555
68572
  if (agentDir != null) {
68556
68573
  try {
68557
- writeFileSync31(join40(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
68574
+ writeFileSync30(join40(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
68558
68575
  `, "utf8");
68559
68576
  } catch (err) {
68560
68577
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -68918,15 +68935,15 @@ bot.command("interrupt", async (ctx) => {
68918
68935
  });
68919
68936
  var lockoutOps = {
68920
68937
  readFileSync: (p, enc) => readFileSync41(p, enc),
68921
- writeFileSync: (p, data, opts) => writeFileSync31(p, data, opts),
68922
- existsSync: (p) => existsSync44(p),
68938
+ writeFileSync: (p, data, opts) => writeFileSync30(p, data, opts),
68939
+ existsSync: (p) => existsSync43(p),
68923
68940
  mkdirSync: (p, opts) => mkdirSync32(p, opts),
68924
68941
  joinPath: (...parts) => join40(...parts)
68925
68942
  };
68926
68943
  var FLEET_FALLBACK_DEDUP_MS = 30000;
68927
68944
  function isAuthBrokerSocketReachable() {
68928
68945
  try {
68929
- return existsSync44(resolveAuthBrokerSocketPath2());
68946
+ return existsSync43(resolveAuthBrokerSocketPath2());
68930
68947
  } catch {
68931
68948
  return false;
68932
68949
  }
@@ -69582,7 +69599,7 @@ async function handleVaultRecentDenialCallback(ctx, data) {
69582
69599
  const tokenPath = join40(homedir14(), ".switchroom", "agents", agentName3, ".vault-token");
69583
69600
  try {
69584
69601
  mkdirSync32(join40(homedir14(), ".switchroom", "agents", agentName3), { recursive: true });
69585
- writeFileSync31(tokenPath, token, { mode: 384 });
69602
+ writeFileSync30(tokenPath, token, { mode: 384 });
69586
69603
  } catch (err) {
69587
69604
  await switchroomReply(ctx, `**Grant created (${escapeHtmlForTg2(id)}) but token write failed:** ${escapeHtmlForTg2(String(err))}
69588
69605
  _Recover with: \`switchroom vault grant ${escapeHtmlForTg2(agentName3)} --keys ${escapeHtmlForTg2(keyName)} --duration 30d\` on the host._`, { html: true });
@@ -69660,7 +69677,7 @@ async function performVaultAccessApproval(ctx, pending2, stageId, senderId, atte
69660
69677
  const tokenPath = join40(homedir14(), ".switchroom", "agents", pending2.agent, ".vault-token");
69661
69678
  try {
69662
69679
  mkdirSync32(join40(homedir14(), ".switchroom", "agents", pending2.agent), { recursive: true });
69663
- writeFileSync31(tokenPath, token, { mode: 384 });
69680
+ writeFileSync30(tokenPath, token, { mode: 384 });
69664
69681
  } catch (err) {
69665
69682
  await switchroomReply(ctx, `**Grant created (${escapeHtmlForTg2(id)}) but token write failed:** ${escapeHtmlForTg2(String(err))}
69666
69683
  _Recover with: \`switchroom vault grant ${escapeHtmlForTg2(pending2.agent)} --keys ${escapeHtmlForTg2(pending2.key)} --duration ${Math.round(pending2.ttl_seconds / 86400)}d\` on the host._`, { html: true });
@@ -70232,7 +70249,7 @@ async function executeGrantWizard(ctx, chatId, state4) {
70232
70249
  const tokenPath = join40(homedir14(), ".switchroom", "agents", state4.agent, ".vault-token");
70233
70250
  try {
70234
70251
  mkdirSync32(join40(homedir14(), ".switchroom", "agents", state4.agent), { recursive: true });
70235
- writeFileSync31(tokenPath, token, { mode: 384 });
70252
+ writeFileSync30(tokenPath, token, { mode: 384 });
70236
70253
  } catch (err) {
70237
70254
  await switchroomReply(ctx, `**Grant created but token write failed:** ${escapeHtmlForTg2(String(err))}`, { html: true });
70238
70255
  return;
@@ -71962,7 +71979,7 @@ bot.on("message:photo", async (ctx) => {
71962
71979
  });
71963
71980
  mkdirSync32(INBOX_DIR, { recursive: true, mode: 448 });
71964
71981
  assertInsideInbox(INBOX_DIR, dlPath);
71965
- writeFileSync31(dlPath, buf, { mode: 384 });
71982
+ writeFileSync30(dlPath, buf, { mode: 384 });
71966
71983
  return dlPath;
71967
71984
  } catch (err) {
71968
71985
  const msg = err instanceof Error ? err.message : "unknown error";
@@ -73152,28 +73169,6 @@ var didOneTimeSetup = false;
73152
73169
  parentStateDir: STATE_DIR,
73153
73170
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
73154
73171
  `),
73155
- onStall: (agentId, idleMs, description) => {
73156
- progressDriver?.onSubAgentStall(agentId, idleMs, description);
73157
- },
73158
- onUnstall: (agentId, description) => {
73159
- progressDriver?.onSubAgentUnstall?.(agentId, description);
73160
- },
73161
- onStallTerminal: (agentId) => {
73162
- try {
73163
- const fleets = progressDriver?.peekAllFleets() ?? [];
73164
- for (const f of fleets) {
73165
- if (f.fleet.has(agentId)) {
73166
- progressDriver?.ingest({ kind: "sub_agent_turn_end", agentId }, f.chatId ?? null, undefined);
73167
- return;
73168
- }
73169
- }
73170
- process.stderr.write(`telegram gateway: subagent-watcher: onStallTerminal ${agentId} \u2014 no fleet entry to retarget; synthetic sub_agent_turn_end skipped
73171
- `);
73172
- } catch (err) {
73173
- process.stderr.write(`telegram gateway: subagent-watcher: onStallTerminal ${agentId} ingest error: ${err.message}
73174
- `);
73175
- }
73176
- },
73177
73172
  onFinish: ({ agentId, outcome, description, resultText, toolCount, durationMs }) => {
73178
73173
  deferredDoneReactions.promote();
73179
73174
  let fleetChatId = "";
@@ -73193,6 +73188,20 @@ var didOneTimeSetup = false;
73193
73188
  } catch {}
73194
73189
  }
73195
73190
  const isBackground = dispatch.isBackground;
73191
+ if (dispatch.isNested) {
73192
+ if (workerFeedEnabled) {
73193
+ workerActivityFeed?.finish(agentId, {
73194
+ description: dispatch.feedDescription,
73195
+ lastTool: null,
73196
+ toolCount,
73197
+ latestSummary: resultText,
73198
+ elapsedMs: durationMs,
73199
+ state: outcome === "failed" ? "failed" : "done"
73200
+ });
73201
+ reconcileWorkerPin(agentId, null, false);
73202
+ }
73203
+ return;
73204
+ }
73196
73205
  if (!isBackground) {
73197
73206
  const turn = currentTurn;
73198
73207
  if (turn != null && turn.foregroundSubAgents.has(agentId)) {
@@ -73302,11 +73311,12 @@ var didOneTimeSetup = false;
73302
73311
  dispatch = resolveWorkerFeedDispatch(getSubagentByJsonlId(turnsDb, agentId), description);
73303
73312
  } catch {}
73304
73313
  }
73305
- const isBackground = dispatch.isBackground;
73314
+ const isBackground = dispatch.isBackground || dispatch.isNested;
73315
+ const stepLine = progressLine != null && progressLine.length > 0 ? progressLine : latestSummary;
73306
73316
  if (!isBackground) {
73307
73317
  const surface = resolveSubagentStatusSurface({
73308
73318
  isBackground: false,
73309
- liveTurnPresent: currentTurn != null,
73319
+ liveTurnPresent: currentTurn != null && dispatch.hasRow,
73310
73320
  workerFeedEnabled,
73311
73321
  orphanStatusEnabled
73312
73322
  });
@@ -73317,7 +73327,7 @@ var didOneTimeSetup = false;
73317
73327
  description: dispatch.feedDescription,
73318
73328
  lastTool,
73319
73329
  toolCount,
73320
- latestSummary,
73330
+ latestSummary: stepLine,
73321
73331
  elapsedMs,
73322
73332
  state: "running"
73323
73333
  }, origin?.threadId)?.then(() => reconcileWorkerPin(agentId, wkChat, true));
@@ -73341,7 +73351,7 @@ var didOneTimeSetup = false;
73341
73351
  narrative = [];
73342
73352
  turn.foregroundSubAgents.set(agentId, narrative);
73343
73353
  }
73344
- if (narrative[narrative.length - 1] !== child) {
73354
+ if (!narrative.includes(child)) {
73345
73355
  narrative.push(child);
73346
73356
  if (narrative.length > FOREGROUND_SUBAGENT_ACCUM_MAX) {
73347
73357
  narrative.splice(0, narrative.length - FOREGROUND_SUBAGENT_ACCUM_MAX);
@@ -73375,7 +73385,7 @@ var didOneTimeSetup = false;
73375
73385
  description: dispatch.feedDescription,
73376
73386
  lastTool,
73377
73387
  toolCount,
73378
- latestSummary,
73388
+ latestSummary: stepLine,
73379
73389
  elapsedMs,
73380
73390
  state: "running"
73381
73391
  }, origin?.threadId)?.then(() => reconcileWorkerPin(agentId, wkChat, true));