switchroom 0.16.23 → 0.16.27

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 (138) hide show
  1. package/dist/agent-scheduler/index.js +80 -80
  2. package/dist/auth-broker/index.js +80 -80
  3. package/dist/cli/autoaccept-poll.js +8 -8
  4. package/dist/cli/drive-write-pretool.mjs +10 -10
  5. package/dist/cli/notion-write-pretool.mjs +82 -82
  6. package/dist/cli/self-improve-apply-guard-pretool.mjs +6 -0
  7. package/dist/cli/skill-validate-pretool.mjs +2936 -119
  8. package/dist/cli/switchroom.js +804 -465
  9. package/dist/host-control/main.js +169 -163
  10. package/dist/vault/approvals/kernel-server.js +82 -82
  11. package/dist/vault/broker/server.js +83 -83
  12. package/package.json +4 -4
  13. package/telegram-plugin/answer-stream.ts +20 -49
  14. package/telegram-plugin/auth-snapshot-format.ts +27 -30
  15. package/telegram-plugin/auto-fallback-fleet.ts +6 -11
  16. package/telegram-plugin/bridge/bridge.ts +1 -1
  17. package/telegram-plugin/card-format.ts +28 -25
  18. package/telegram-plugin/credits-watch.ts +5 -10
  19. package/telegram-plugin/dist/bridge/bridge.js +113 -113
  20. package/telegram-plugin/dist/gateway/gateway.js +2085 -2102
  21. package/telegram-plugin/dist/server.js +161 -161
  22. package/telegram-plugin/draft-stream.ts +4 -4
  23. package/telegram-plugin/format.ts +427 -680
  24. package/telegram-plugin/gateway/approval-callback.ts +2 -3
  25. package/telegram-plugin/gateway/approval-card.test.ts +17 -4
  26. package/telegram-plugin/gateway/approval-card.ts +16 -6
  27. package/telegram-plugin/gateway/approvals-commands.ts +18 -24
  28. package/telegram-plugin/gateway/auth-command.ts +74 -74
  29. package/telegram-plugin/gateway/auth-line.ts +5 -15
  30. package/telegram-plugin/gateway/boot-card.ts +20 -22
  31. package/telegram-plugin/gateway/boot-version.ts +3 -2
  32. package/telegram-plugin/gateway/config-approval-handler.test.ts +35 -33
  33. package/telegram-plugin/gateway/config-approval-handler.ts +24 -24
  34. package/telegram-plugin/gateway/config-snapshot.ts +9 -9
  35. package/telegram-plugin/gateway/diff-preview-card.test.ts +8 -8
  36. package/telegram-plugin/gateway/diff-preview-card.ts +2 -5
  37. package/telegram-plugin/gateway/disconnect-flush.ts +0 -4
  38. package/telegram-plugin/gateway/drive-write-approval.test.ts +10 -10
  39. package/telegram-plugin/gateway/drive-write-approval.ts +14 -8
  40. package/telegram-plugin/gateway/effort-command.ts +17 -17
  41. package/telegram-plugin/gateway/folder-picker-handler.test.ts +8 -2
  42. package/telegram-plugin/gateway/folder-picker-handler.ts +3 -4
  43. package/telegram-plugin/gateway/gateway.ts +881 -633
  44. package/telegram-plugin/gateway/inject-handler.test.ts +15 -13
  45. package/telegram-plugin/gateway/inject-handler.ts +5 -5
  46. package/telegram-plugin/gateway/ipc-protocol.ts +33 -1
  47. package/telegram-plugin/gateway/ipc-server.ts +39 -6
  48. package/telegram-plugin/gateway/linear-activity.ts +16 -14
  49. package/telegram-plugin/gateway/linear-setup.ts +1 -1
  50. package/telegram-plugin/gateway/model-command.ts +25 -25
  51. package/telegram-plugin/gateway/oversize-card-body.ts +6 -7
  52. package/telegram-plugin/gateway/permission-timeout.ts +76 -0
  53. package/telegram-plugin/gateway/skill-proposal-card.ts +167 -0
  54. package/telegram-plugin/inline-keyboard-callbacks.ts +19 -13
  55. package/telegram-plugin/issues-card.ts +6 -7
  56. package/telegram-plugin/model-unavailable.ts +8 -12
  57. package/telegram-plugin/operator-events-history.ts +1 -1
  58. package/telegram-plugin/operator-events.ts +24 -28
  59. package/telegram-plugin/package.json +1 -1
  60. package/telegram-plugin/pending-work-progress.ts +36 -36
  61. package/telegram-plugin/permission-title.ts +39 -20
  62. package/telegram-plugin/pty-partial-handler.ts +5 -13
  63. package/telegram-plugin/quota-check.ts +5 -5
  64. package/telegram-plugin/quota-watch.ts +13 -18
  65. package/telegram-plugin/recent-outbound-dedup.ts +5 -5
  66. package/telegram-plugin/registry/turns-schema.ts +43 -3
  67. package/telegram-plugin/retry-api-call.ts +42 -7
  68. package/telegram-plugin/rich-send.ts +86 -0
  69. package/telegram-plugin/secret-detect/vault-error.test.ts +6 -6
  70. package/telegram-plugin/secret-detect/vault-error.ts +29 -22
  71. package/telegram-plugin/shared/bot-runtime.ts +29 -7
  72. package/telegram-plugin/silence-poke.ts +26 -69
  73. package/telegram-plugin/silent-reply-anchor.ts +9 -2
  74. package/telegram-plugin/slot-banner-driver.ts +9 -6
  75. package/telegram-plugin/slot-banner.ts +5 -8
  76. package/telegram-plugin/status-no-truncate.ts +11 -5
  77. package/telegram-plugin/steering.ts +0 -4
  78. package/telegram-plugin/stream-controller.ts +59 -62
  79. package/telegram-plugin/stream-reply-handler.ts +49 -98
  80. package/telegram-plugin/subagent-watcher.ts +2 -2
  81. package/telegram-plugin/tests/answer-stream-silent-markers.test.ts +5 -2
  82. package/telegram-plugin/tests/answer-stream.test.ts +54 -63
  83. package/telegram-plugin/tests/auth-command-format2.test.ts +4 -4
  84. package/telegram-plugin/tests/auth-command-vernacular.test.ts +3 -2
  85. package/telegram-plugin/tests/auth-snapshot-format.test.ts +19 -18
  86. package/telegram-plugin/tests/auto-fallback-fleet.test.ts +15 -13
  87. package/telegram-plugin/tests/boot-card-reason-to-render.test.ts +27 -12
  88. package/telegram-plugin/tests/boot-card-render.test.ts +59 -48
  89. package/telegram-plugin/tests/boot-version-string.test.ts +0 -0
  90. package/telegram-plugin/tests/bot-api.harness.ts +23 -1
  91. package/telegram-plugin/tests/bot-runtime.test.ts +23 -18
  92. package/telegram-plugin/tests/card-format.test.ts +6 -4
  93. package/telegram-plugin/tests/config-snapshot.test.ts +1 -1
  94. package/telegram-plugin/tests/credits-watch.test.ts +5 -5
  95. package/telegram-plugin/tests/fake-bot-api.ts +58 -4
  96. package/telegram-plugin/tests/finalize-callback.test.ts +11 -9
  97. package/telegram-plugin/tests/foreground-nesting.test.ts +1 -1
  98. package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +3 -13
  99. package/telegram-plugin/tests/ipc-server-validate-send-outbound.test.ts +6 -2
  100. package/telegram-plugin/tests/issues-card.test.ts +15 -12
  101. package/telegram-plugin/tests/length-error-classify.test.ts +131 -0
  102. package/telegram-plugin/tests/linear-agent-activity.test.ts +8 -5
  103. package/telegram-plugin/tests/model-command.test.ts +2 -2
  104. package/telegram-plugin/tests/model-unavailable.test.ts +13 -13
  105. package/telegram-plugin/tests/multi-turn-continuity.test.ts +6 -10
  106. package/telegram-plugin/tests/operator-events.test.ts +7 -9
  107. package/telegram-plugin/tests/paragraph-normalizer.test.ts +273 -0
  108. package/telegram-plugin/tests/pending-work-progress.test.ts +20 -21
  109. package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +12 -2
  110. package/telegram-plugin/tests/permission-timeout.test.ts +77 -0
  111. package/telegram-plugin/tests/permission-title.test.ts +88 -41
  112. package/telegram-plugin/tests/pty-partial-handler.test.ts +8 -8
  113. package/telegram-plugin/tests/quota-check.test.ts +3 -3
  114. package/telegram-plugin/tests/quota-watch.test.ts +8 -4
  115. package/telegram-plugin/tests/secret-detect-delete-must-surface-failures.test.ts +4 -3
  116. package/telegram-plugin/tests/silence-poke.test.ts +75 -112
  117. package/telegram-plugin/tests/single-mode-stream-reply.test.ts +137 -0
  118. package/telegram-plugin/tests/skill-proposal-card.test.ts +103 -0
  119. package/telegram-plugin/tests/slot-banner-driver.e2e.test.ts +36 -24
  120. package/telegram-plugin/tests/slot-banner.test.ts +9 -6
  121. package/telegram-plugin/tests/status-accent.test.ts +29 -32
  122. package/telegram-plugin/tests/{stream-controller-html-fallback.test.ts → stream-controller-parse-fallback.test.ts} +40 -42
  123. package/telegram-plugin/tests/stream-controller.test.ts +63 -52
  124. package/telegram-plugin/tests/stream-reply-error-paths.test.ts +43 -38
  125. package/telegram-plugin/tests/stream-reply-handler.test.ts +122 -249
  126. package/telegram-plugin/tests/streaming-e2e.test.ts +35 -30
  127. package/telegram-plugin/tests/streaming-orchestration.test.ts +29 -28
  128. package/telegram-plugin/tests/telegram-format.test.ts +120 -1083
  129. package/telegram-plugin/tests/tool-activity-summary.test.ts +144 -145
  130. package/telegram-plugin/tests/welcome-text.test.ts +72 -65
  131. package/telegram-plugin/tests/worker-activity-feed.test.ts +119 -137
  132. package/telegram-plugin/text-voice-scrub.ts +8 -11
  133. package/telegram-plugin/tool-activity-summary.ts +29 -29
  134. package/telegram-plugin/welcome-text.ts +82 -83
  135. package/telegram-plugin/worker-activity-feed.ts +2 -3
  136. package/telegram-plugin/html-sanitize.ts +0 -244
  137. package/telegram-plugin/tests/html-sanitize.test.ts +0 -146
  138. package/telegram-plugin/tests/parse-mode-rotation.test.ts +0 -162
@@ -95,17 +95,17 @@ export interface EffortCommandReply {
95
95
  }
96
96
 
97
97
  const PERSIST_NOTE =
98
- '<i>Session-only — reverts to the configured default on restart. To change the default, set <code>thinking_effort:</code> in switchroom.yaml and restart.</i>'
98
+ '_Session-only — reverts to the configured default on restart. To change the default, set \`thinking_effort:\` in switchroom.yaml and restart._'
99
99
 
100
- const LEVELS_INLINE = EFFORT_LEVELS.map(l => `<code>${l}</code>`).join(' · ')
100
+ const LEVELS_INLINE = EFFORT_LEVELS.map(l => `\`${l}\``).join(' · ')
101
101
 
102
102
  function helpText(deps: EffortCommandDeps, reason?: string): EffortCommandReply {
103
103
  const lines: string[] = []
104
104
  if (reason) lines.push(`⚠️ ${deps.escapeHtml(reason)}`)
105
105
  lines.push(
106
- '<b>/effort</b> — show or switch the reasoning effort (faster→smarter)',
107
- '<code>/effort</code> — show the configured effort + a tap menu',
108
- `<code>/effort &lt;level&gt;</code> — switch the live session (${LEVELS_INLINE})`,
106
+ '**/effort** — show or switch the reasoning effort (faster→smarter)',
107
+ '\`/effort\` — show the configured effort + a tap menu',
108
+ `\`/effort <level>\` — switch the live session (${LEVELS_INLINE})`,
109
109
  PERSIST_NOTE,
110
110
  )
111
111
  return { text: lines.join('\n'), html: true }
@@ -122,9 +122,9 @@ export async function handleEffortCommand(
122
122
  const shown = configured && configured.length > 0 ? configured : 'low'
123
123
  return {
124
124
  text: [
125
- `<b>Effort — ${deps.escapeHtml(deps.getAgentName())}</b>`,
126
- `Configured default: <code>${deps.escapeHtml(shown)}</code>`,
127
- `Switch the live session: ${EFFORT_LEVELS.map(l => `<code>/effort ${l}</code>`).join(' · ')}`,
125
+ `**Effort — ${deps.escapeHtml(deps.getAgentName())}**`,
126
+ `Configured default: \`${deps.escapeHtml(shown)}\``,
127
+ `Switch the live session: ${EFFORT_LEVELS.map(l => `\`/effort ${l}\``).join(' · ')}`,
128
128
  PERSIST_NOTE,
129
129
  ].join('\n'),
130
130
  html: true,
@@ -136,7 +136,7 @@ export async function handleEffortCommand(
136
136
  if (!isValidEffortArg(parsed.level)) {
137
137
  return helpText(deps, `not a valid effort level: ${parsed.level}`)
138
138
  }
139
- const verbHtml = `<code>/effort ${deps.escapeHtml(parsed.level)}</code>`
139
+ const verbHtml = `\`/effort ${deps.escapeHtml(parsed.level)}\``
140
140
  let result: EffortApplyResult
141
141
  try {
142
142
  result = await deps.applyEffort(deps.getAgentName(), parsed.level)
@@ -153,17 +153,17 @@ export async function handleEffortCommand(
153
153
  * say so honestly rather than just claiming success.
154
154
  */
155
155
  function applyResultText(level: string, result: EffortApplyResult, deps: EffortCommandDeps): string {
156
- const verbHtml = `<code>/effort ${deps.escapeHtml(level)}</code>`
156
+ const verbHtml = `\`/effort ${deps.escapeHtml(level)}\``
157
157
  if (result.ok) {
158
158
  const lines = [`✅ ${verbHtml} — ${deps.escapeHtml(result.output)}`]
159
159
  if (result.confirmed) {
160
- lines.push('<i>Switched mid-conversation — your next turn re-reads the cached history (slower, one time).</i>')
160
+ lines.push('_Switched mid-conversation — your next turn re-reads the cached history (slower, one time)._')
161
161
  }
162
162
  lines.push(PERSIST_NOTE)
163
163
  return lines.join('\n')
164
164
  }
165
165
  if (result.reason === 'session_missing') {
166
- return '❌ tmux session not found — the agent must be running under the tmux supervisor (the default). Remove <code>experimental.legacy_pty: true</code> if set.'
166
+ return '❌ tmux session not found — the agent must be running under the tmux supervisor (the default). Remove \`experimental.legacy_pty: true\` if set.'
167
167
  }
168
168
  if (result.reason === 'confirm_failed') {
169
169
  const wedged = result.wedged
@@ -172,7 +172,7 @@ function applyResultText(level: string, result: EffortApplyResult, deps: EffortC
172
172
  return `❌ ${verbHtml} — couldn't confirm the switch.${wedged}`
173
173
  }
174
174
  // apply_unverified
175
- return `❌ ${verbHtml} — sent, but couldn't confirm it applied. The agent may be mid-turn; check <code>/inject /status</code>.`
175
+ return `❌ ${verbHtml} — sent, but couldn't confirm it applied. The agent may be mid-turn; check \`/inject /status\`.`
176
176
  }
177
177
 
178
178
  // ---------------------------------------------------------------------------
@@ -220,8 +220,8 @@ export function buildEffortMenu(deps: EffortCommandDeps, highlight?: string): Ef
220
220
  const live = highlight ?? configured
221
221
  return {
222
222
  text: [
223
- `<b>Effort — ${deps.escapeHtml(deps.getAgentName())}</b>`,
224
- `Default: <code>${deps.escapeHtml(configured)}</code> · faster → smarter: ${LEVELS_INLINE}`,
223
+ `**Effort — ${deps.escapeHtml(deps.getAgentName())}**`,
224
+ `Default: \`${deps.escapeHtml(configured)}\` · faster → smarter: ${LEVELS_INLINE}`,
225
225
  'Tap to switch the live session:',
226
226
  PERSIST_NOTE,
227
227
  ].join('\n'),
@@ -260,8 +260,8 @@ export async function handleEffortMenuCallback(
260
260
  const result = await deps.applyEffort(deps.getAgentName(), level)
261
261
  if (result.ok) {
262
262
  banner = result.confirmed
263
- ? `✅ Effort → <code>${deps.escapeHtml(level)}</code> (mid-conversation: next turn re-reads history)`
264
- : `✅ Effort → <code>${deps.escapeHtml(level)}</code> for this session`
263
+ ? `✅ Effort → \`${deps.escapeHtml(level)}\` (mid-conversation: next turn re-reads history)`
264
+ : `✅ Effort → \`${deps.escapeHtml(level)}\` for this session`
265
265
  selected = level
266
266
  } else if (result.reason === 'session_missing') {
267
267
  banner = '❌ tmux session not found — is the agent running under the supervisor?'
@@ -42,12 +42,18 @@ function fakeCtx(userId = 12345): { ctx: Context; spy: FakeCtx } {
42
42
  });
43
43
  return { message_id: 1 };
44
44
  },
45
- editMessageText: async (text: string, opts?: { reply_markup?: { inline_keyboard?: unknown[][] } }) => {
45
+ editMessageText: async (
46
+ text: string | { markdown: string },
47
+ opts?: { reply_markup?: { inline_keyboard?: unknown[][] } },
48
+ ) => {
46
49
  const rows = (opts?.reply_markup?.inline_keyboard ?? []) as Array<
47
50
  Array<{ text: string }>
48
51
  >;
52
+ // #2669: the rich path passes `{ markdown }`; normalize to the body
53
+ // string so the substring assertions keep working.
54
+ const body = typeof text === "object" && text != null ? text.markdown : text;
49
55
  spy.edits.push({
50
- text,
56
+ text: body,
51
57
  keyboardRows: opts?.reply_markup
52
58
  ? rows.map((r) => r.map((b) => b.text))
53
59
  : undefined,
@@ -302,8 +302,8 @@ async function recordGrantAndConfirm(
302
302
 
303
303
  const confirmText =
304
304
  `✅ Granted ${deps.agentName} access to folder ${parsed.folder_id}\n` +
305
- `Scope: <code>${scope}</code>\n` +
306
- `Revoke with: <code>/approvals revoke ${decisionId}</code>`;
305
+ `Scope: \`${scope}\`\n` +
306
+ `Revoke with: \`/approvals revoke ${decisionId}\``;
307
307
 
308
308
  // Keep an [Open in Drive] URL button on the confirmation so the
309
309
  // operator can verify which folder they granted.
@@ -312,8 +312,7 @@ async function recordGrantAndConfirm(
312
312
  `https://drive.google.com/drive/folders/${parsed.folder_id}`,
313
313
  );
314
314
  try {
315
- await ctx.editMessageText(confirmText, {
316
- parse_mode: "HTML",
315
+ await ctx.editMessageText({ markdown: confirmText }, {
317
316
  reply_markup: kb,
318
317
  });
319
318
  } catch {