switchroom 0.7.15 → 0.10.0

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 (301) hide show
  1. package/README.md +51 -59
  2. package/bin/run-hook.sh +27 -11
  3. package/bin/timezone-hook.sh +9 -7
  4. package/dist/agent-scheduler/index.js +410 -133
  5. package/dist/auth-broker/index.js +13932 -0
  6. package/dist/cli/switchroom.js +26937 -5601
  7. package/dist/host-control/main.js +12702 -0
  8. package/dist/vault/approvals/kernel-server.js +467 -184
  9. package/dist/vault/broker/server.js +1430 -724
  10. package/examples/minimal.yaml +63 -0
  11. package/examples/personal-google-workspace-mcp/.env.example +34 -0
  12. package/examples/personal-google-workspace-mcp/README.md +194 -0
  13. package/examples/personal-google-workspace-mcp/compose.yaml +66 -0
  14. package/examples/switchroom.yaml +220 -0
  15. package/package.json +7 -4
  16. package/profiles/_base/settings.json.hbs +20 -5
  17. package/profiles/_base/start.sh.hbs +16 -3
  18. package/profiles/_shared/agent-self-service.md.hbs +126 -0
  19. package/profiles/_shared/telegram-style.md.hbs +20 -90
  20. package/profiles/_shared/vault-protocol.md.hbs +68 -0
  21. package/profiles/default/CLAUDE.md +50 -96
  22. package/profiles/default/CLAUDE.md.hbs +36 -6
  23. package/profiles/default/workspace/SOUL.md.hbs +12 -5
  24. package/skills/buildkite-agent-infrastructure/SKILL.md +30 -11
  25. package/skills/buildkite-agent-runtime/SKILL.md +44 -11
  26. package/skills/buildkite-api/SKILL.md +31 -8
  27. package/skills/buildkite-cli/SKILL.md +27 -9
  28. package/skills/buildkite-migration/SKILL.md +22 -9
  29. package/skills/buildkite-pipelines/SKILL.md +26 -9
  30. package/skills/buildkite-secure-delivery/SKILL.md +23 -9
  31. package/skills/buildkite-test-engine/SKILL.md +25 -8
  32. package/skills/docx/SKILL.md +1 -1
  33. package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-313.pyc +0 -0
  34. package/skills/docx/scripts/office/validators/__pycache__/base.cpython-313.pyc +0 -0
  35. package/skills/file-bug/SKILL.md +34 -6
  36. package/skills/humanizer/SKILL.md +15 -0
  37. package/skills/humanizer-calibrate/SKILL.md +7 -1
  38. package/skills/mcp-builder/SKILL.md +1 -1
  39. package/skills/pdf/SKILL.md +1 -1
  40. package/skills/pptx/SKILL.md +1 -1
  41. package/skills/skill-creator/SKILL.md +21 -1
  42. package/skills/skill-creator/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  43. package/skills/skill-creator/scripts/__pycache__/generate_report.cpython-313.pyc +0 -0
  44. package/skills/skill-creator/scripts/__pycache__/improve_description.cpython-313.pyc +0 -0
  45. package/skills/skill-creator/scripts/__pycache__/run_eval.cpython-313.pyc +0 -0
  46. package/skills/skill-creator/scripts/__pycache__/run_loop.cpython-313.pyc +0 -0
  47. package/skills/skill-creator/scripts/__pycache__/utils.cpython-313.pyc +0 -0
  48. package/skills/switchroom-cli/SKILL.md +63 -64
  49. package/skills/switchroom-health/SKILL.md +23 -10
  50. package/skills/switchroom-install/SKILL.md +3 -3
  51. package/skills/switchroom-manage/SKILL.md +26 -19
  52. package/skills/switchroom-runtime/SKILL.md +191 -0
  53. package/skills/switchroom-status/SKILL.md +27 -2
  54. package/skills/telegram-test-harness/SKILL.md +3 -0
  55. package/skills/token-helpers/SKILL.md +24 -1
  56. package/skills/webapp-testing/SKILL.md +31 -1
  57. package/skills/xlsx/SKILL.md +1 -1
  58. package/telegram-plugin/admin-commands/index.ts +7 -5
  59. package/telegram-plugin/analytics-posthog.ts +191 -0
  60. package/telegram-plugin/bridge/bridge.ts +69 -0
  61. package/telegram-plugin/bridge/ipc-client.ts +4 -1
  62. package/telegram-plugin/dist/bridge/bridge.js +194 -119
  63. package/telegram-plugin/dist/gateway/gateway.js +23611 -19671
  64. package/telegram-plugin/dist/server.js +245 -189
  65. package/telegram-plugin/first-paint.ts +3 -24
  66. package/telegram-plugin/gateway/auth-add-flow.ts +326 -0
  67. package/telegram-plugin/gateway/auth-broker-client.ts +75 -0
  68. package/telegram-plugin/gateway/auth-command.ts +794 -0
  69. package/telegram-plugin/gateway/auth-line.ts +123 -0
  70. package/telegram-plugin/gateway/boot-card.ts +169 -40
  71. package/telegram-plugin/gateway/boot-issue-cache.ts +308 -0
  72. package/telegram-plugin/gateway/boot-probes.ts +166 -123
  73. package/telegram-plugin/gateway/boot-reason.ts +41 -7
  74. package/telegram-plugin/gateway/boot-version.ts +66 -0
  75. package/telegram-plugin/gateway/gateway.ts +3499 -1885
  76. package/telegram-plugin/gateway/hostd-dispatch.ts +117 -0
  77. package/telegram-plugin/gateway/ipc-protocol.ts +18 -0
  78. package/telegram-plugin/gateway/pending-inbound-buffer.ts +106 -0
  79. package/telegram-plugin/gateway/quarantine.ts +69 -0
  80. package/telegram-plugin/gateway/quota-cache.ts +9 -4
  81. package/telegram-plugin/gateway/reaction-trigger.ts +401 -0
  82. package/telegram-plugin/gateway/recent-denials.test.ts +103 -0
  83. package/telegram-plugin/gateway/recent-denials.ts +77 -0
  84. package/telegram-plugin/gateway/startup-network-retry.ts +109 -31
  85. package/telegram-plugin/gateway/vault-grant-inbound-builders.ts +125 -0
  86. package/telegram-plugin/history.ts +91 -0
  87. package/telegram-plugin/hooks/hooks.json +10 -0
  88. package/telegram-plugin/hooks/sandbox-hint-posttool.mjs +130 -0
  89. package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +19 -2
  90. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +22 -2
  91. package/telegram-plugin/hooks/tool-label-pretool.mjs +11 -0
  92. package/telegram-plugin/hooks/wedge-detect-posttool.mjs +303 -0
  93. package/telegram-plugin/inbound-classifier.ts +50 -0
  94. package/telegram-plugin/inline-keyboard-callbacks.ts +136 -0
  95. package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  96. package/telegram-plugin/package.json +4 -2
  97. package/telegram-plugin/permission-rule.ts +51 -0
  98. package/telegram-plugin/permission-title.ts +56 -0
  99. package/telegram-plugin/quota-check.ts +19 -41
  100. package/telegram-plugin/registry/reaper.ts +223 -0
  101. package/telegram-plugin/retry-api-call.ts +80 -0
  102. package/telegram-plugin/runtime-metrics.ts +177 -0
  103. package/telegram-plugin/scripts/build.mjs +0 -1
  104. package/telegram-plugin/secret-detect/index.ts +24 -0
  105. package/telegram-plugin/secret-detect/vault-error.test.ts +64 -12
  106. package/telegram-plugin/secret-detect/vault-error.ts +78 -11
  107. package/telegram-plugin/secret-detect/vault-write.ts +14 -2
  108. package/telegram-plugin/server.js +41795 -0
  109. package/telegram-plugin/session-tail.ts +6 -1
  110. package/telegram-plugin/shared/bot-runtime.ts +5 -4
  111. package/telegram-plugin/silence-poke.ts +420 -0
  112. package/telegram-plugin/silent-end.ts +174 -0
  113. package/telegram-plugin/stream-controller.ts +13 -0
  114. package/telegram-plugin/stream-reply-handler.ts +7 -0
  115. package/telegram-plugin/subagent-watcher.ts +213 -4
  116. package/telegram-plugin/tests/auth-add-flow.test.ts +559 -0
  117. package/telegram-plugin/tests/auth-code-redact.test.ts +8 -4
  118. package/telegram-plugin/tests/auth-command-vernacular.test.ts +531 -0
  119. package/telegram-plugin/tests/boot-card-issue-dedup.test.ts +247 -0
  120. package/telegram-plugin/tests/boot-card-reason-to-render.test.ts +182 -0
  121. package/telegram-plugin/tests/boot-card-reason.test.ts +65 -2
  122. package/telegram-plugin/tests/boot-card-render.test.ts +146 -0
  123. package/telegram-plugin/tests/boot-card-silent-on-operator.test.ts +103 -0
  124. package/telegram-plugin/tests/boot-probes.test.ts +216 -10
  125. package/telegram-plugin/tests/boot-version-string.test.ts +0 -0
  126. package/telegram-plugin/tests/finalize-callback.test.ts +190 -0
  127. package/telegram-plugin/tests/gateway-message-validator.test.ts +26 -0
  128. package/telegram-plugin/tests/gateway-secret-detect.test.ts +12 -3
  129. package/telegram-plugin/tests/gateway-startup-network-retry.test.ts +104 -0
  130. package/telegram-plugin/tests/history-reaper.test.ts +378 -0
  131. package/telegram-plugin/tests/hostd-dispatch.test.ts +129 -0
  132. package/telegram-plugin/tests/inbound-classifier.test.ts +76 -0
  133. package/telegram-plugin/tests/inbound-message-types.test.ts +267 -0
  134. package/telegram-plugin/tests/issues-card.test.ts +49 -0
  135. package/telegram-plugin/tests/pending-inbound-buffer.test.ts +132 -0
  136. package/telegram-plugin/tests/permission-rule.test.ts +80 -1
  137. package/telegram-plugin/tests/permission-title.test.ts +31 -0
  138. package/telegram-plugin/tests/quota-check.test.ts +5 -35
  139. package/telegram-plugin/tests/races.test.ts +179 -0
  140. package/telegram-plugin/tests/reaction-trigger-flow.test.ts +353 -0
  141. package/telegram-plugin/tests/reaction-trigger.test.ts +397 -0
  142. package/telegram-plugin/tests/retry-api-call.test.ts +152 -1
  143. package/telegram-plugin/tests/runtime-metrics.test.ts +145 -0
  144. package/telegram-plugin/tests/sandbox-hint-posttool.test.ts +155 -0
  145. package/telegram-plugin/tests/secret-detect-delete-must-surface-failures.test.ts +133 -0
  146. package/telegram-plugin/tests/secret-detect-false-positives.test.ts +137 -0
  147. package/telegram-plugin/tests/silence-poke.test.ts +493 -0
  148. package/telegram-plugin/tests/silent-end.test.ts +206 -0
  149. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +107 -0
  150. package/telegram-plugin/tests/subagent-watcher-env-thresholds.test.ts +224 -0
  151. package/telegram-plugin/tests/subagent-watcher-stall-terminal.test.ts +316 -0
  152. package/telegram-plugin/tests/subagent-watcher.test.ts +263 -0
  153. package/telegram-plugin/tests/turn-signal-tracker.test.ts +81 -0
  154. package/telegram-plugin/tests/vault-approval-posture.test.ts +256 -0
  155. package/telegram-plugin/tests/vault-grant-auto-resume.test.ts +73 -0
  156. package/telegram-plugin/tests/vault-grant-inbound-builders.test.ts +226 -0
  157. package/telegram-plugin/tests/vault-grant-union.test.ts +130 -0
  158. package/telegram-plugin/tests/vault-key-regex-allows-slash.test.ts +140 -0
  159. package/telegram-plugin/tests/vault-posture-quarantine.test.ts +104 -0
  160. package/telegram-plugin/tests/vault-request-access-tool.test.ts +114 -0
  161. package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +106 -0
  162. package/telegram-plugin/turn-signal-tracker.ts +100 -24
  163. package/telegram-plugin/uat/SETUP.md +210 -35
  164. package/telegram-plugin/uat/assertions.ts +264 -37
  165. package/telegram-plugin/uat/driver-info.ts +57 -0
  166. package/telegram-plugin/uat/driver.ts +590 -51
  167. package/telegram-plugin/uat/harness.ts +140 -94
  168. package/telegram-plugin/uat/load-env.test.ts +72 -0
  169. package/telegram-plugin/uat/load-env.ts +48 -0
  170. package/telegram-plugin/uat/login.ts +96 -53
  171. package/telegram-plugin/uat/runners/agent-self-sufficiency.ts +457 -0
  172. package/telegram-plugin/uat/runners/paraphrases.ts +231 -0
  173. package/telegram-plugin/uat/runners/report.ts +150 -0
  174. package/telegram-plugin/uat/runners/run-agent-self-sufficiency.sh +50 -0
  175. package/telegram-plugin/uat/runners/scorer.test.ts +196 -0
  176. package/telegram-plugin/uat/runners/scorer.ts +106 -0
  177. package/telegram-plugin/uat/runners/skill-coverage.test.ts +100 -0
  178. package/telegram-plugin/uat/runners/skill-coverage.ts +620 -0
  179. package/telegram-plugin/uat/scenarios/ask-user-button-tap-dm.test.ts +141 -0
  180. package/telegram-plugin/uat/scenarios/bg-sub-agent-dispatch-dm.test.ts +191 -0
  181. package/telegram-plugin/uat/scenarios/fuzz-extended-dm.test.ts +255 -0
  182. package/telegram-plugin/uat/scenarios/fuzz-human-style-dm.test.ts +275 -0
  183. package/telegram-plugin/uat/scenarios/fuzz-random-prompts-dm.test.ts +146 -0
  184. package/telegram-plugin/uat/scenarios/fuzz-status-ask-dm.test.ts +486 -0
  185. package/telegram-plugin/uat/scenarios/jtbd-interrupt-marker-dm.test.ts +67 -0
  186. package/telegram-plugin/uat/scenarios/jtbd-rapid-followup-dm.test.ts +100 -0
  187. package/telegram-plugin/uat/scenarios/jtbd-soft-commit-dm.test.ts +67 -0
  188. package/telegram-plugin/uat/scenarios/jtbd-status-query-dm.test.ts +49 -0
  189. package/telegram-plugin/uat/scenarios/location-inbound-dm.test.ts +65 -0
  190. package/telegram-plugin/uat/scenarios/midturn-silent-dm.test.ts +175 -0
  191. package/telegram-plugin/uat/scenarios/reactions-dm.test.ts +142 -0
  192. package/telegram-plugin/uat/scenarios/reactions-trigger-turn-dm.test.ts +96 -0
  193. package/telegram-plugin/uat/scenarios/secret-redaction-deletes-original-dm.test.ts +123 -0
  194. package/telegram-plugin/uat/scenarios/secret-redaction-no-false-positive-dm.test.ts +87 -0
  195. package/telegram-plugin/uat/scenarios/silence-poke-soft-dm.test.ts +155 -0
  196. package/telegram-plugin/uat/scenarios/silent-end-recovery-dm.test.ts +95 -0
  197. package/telegram-plugin/uat/scenarios/smoke-dm-reply.test.ts +57 -0
  198. package/telegram-plugin/uat/scenarios/subagent-watcher-no-rerun-dm.test.ts +135 -0
  199. package/telegram-plugin/uat/scenarios/vault-approval-posture-telegram-id-dm.test.ts +191 -0
  200. package/telegram-plugin/uat/scenarios/vault-audit-allow-dm.test.ts +108 -0
  201. package/telegram-plugin/uat/scenarios/vault-grant-auto-resume-dm.test.ts +121 -0
  202. package/telegram-plugin/uat/scenarios/vault-request-access-concurrent-dm.test.ts +161 -0
  203. package/telegram-plugin/uat/scenarios/vault-request-access-end-to-end-dm.test.ts +158 -0
  204. package/telegram-plugin/uat/scenarios/voice-inbound-dm.test.ts +65 -0
  205. package/telegram-plugin/vault-approval-posture.ts +42 -0
  206. package/telegram-plugin/welcome-text.ts +1 -0
  207. package/telegram-plugin/active-pins-sweep.ts +0 -204
  208. package/telegram-plugin/active-pins.ts +0 -146
  209. package/telegram-plugin/auth-dashboard.ts +0 -1104
  210. package/telegram-plugin/auth-slot-parser.ts +0 -497
  211. package/telegram-plugin/card-event-log.ts +0 -138
  212. package/telegram-plugin/dist/foreman/foreman.js +0 -31106
  213. package/telegram-plugin/docs/multi-agent-card-design.md +0 -847
  214. package/telegram-plugin/docs/pinned-progress-card-reliability.md +0 -144
  215. package/telegram-plugin/foreman/foreman-create-flow.ts +0 -202
  216. package/telegram-plugin/foreman/foreman-handlers.ts +0 -493
  217. package/telegram-plugin/foreman/foreman.ts +0 -1165
  218. package/telegram-plugin/foreman/setup-flow.ts +0 -345
  219. package/telegram-plugin/foreman/setup-state.ts +0 -239
  220. package/telegram-plugin/foreman/state.ts +0 -203
  221. package/telegram-plugin/pin-event-log.ts +0 -76
  222. package/telegram-plugin/progress-card-driver.ts +0 -2886
  223. package/telegram-plugin/progress-card-pin-manager.ts +0 -589
  224. package/telegram-plugin/progress-card-pin-watchdog.ts +0 -98
  225. package/telegram-plugin/progress-card.ts +0 -1409
  226. package/telegram-plugin/tests/HARNESS.md +0 -340
  227. package/telegram-plugin/tests/_progress-card-harness.ts +0 -109
  228. package/telegram-plugin/tests/active-pins-boot-reaper.test.ts +0 -211
  229. package/telegram-plugin/tests/active-pins-sweep.test.ts +0 -309
  230. package/telegram-plugin/tests/active-pins.test.ts +0 -187
  231. package/telegram-plugin/tests/auth-account-identity-surface.test.ts +0 -118
  232. package/telegram-plugin/tests/auth-dashboard-edge-cases.test.ts +0 -260
  233. package/telegram-plugin/tests/auth-dashboard-restart-flow.test.ts +0 -140
  234. package/telegram-plugin/tests/auth-dashboard-v3b.test.ts +0 -559
  235. package/telegram-plugin/tests/auth-dashboard.test.ts +0 -1045
  236. package/telegram-plugin/tests/auth-slot-commands.test.ts +0 -640
  237. package/telegram-plugin/tests/bg-agent-progress-card-757.test.ts +0 -201
  238. package/telegram-plugin/tests/boot-card-account-quota.test.ts +0 -137
  239. package/telegram-plugin/tests/card-event-log.test.ts +0 -145
  240. package/telegram-plugin/tests/first-paint.test.ts +0 -257
  241. package/telegram-plugin/tests/foreman-create-flow.test.ts +0 -359
  242. package/telegram-plugin/tests/foreman-handlers.test.ts +0 -347
  243. package/telegram-plugin/tests/foreman-state.test.ts +0 -164
  244. package/telegram-plugin/tests/foreman-write-ops.test.ts +0 -214
  245. package/telegram-plugin/tests/harness-ordering-invariants.test.ts +0 -243
  246. package/telegram-plugin/tests/pin-event-log.test.ts +0 -124
  247. package/telegram-plugin/tests/progress-card-api-failure-during-deferred.test.ts +0 -73
  248. package/telegram-plugin/tests/progress-card-close-paths-converge.test.ts +0 -272
  249. package/telegram-plugin/tests/progress-card-cross-turn.test.ts +0 -258
  250. package/telegram-plugin/tests/progress-card-delay-842.test.ts +0 -160
  251. package/telegram-plugin/tests/progress-card-dispose-preservepending.test.ts +0 -81
  252. package/telegram-plugin/tests/progress-card-draft-flag.test.ts +0 -80
  253. package/telegram-plugin/tests/progress-card-driver-eviction.test.ts +0 -215
  254. package/telegram-plugin/tests/progress-card-driver-fleet-shadow.test.ts +0 -123
  255. package/telegram-plugin/tests/progress-card-driver-force-complete-parent-done.test.ts +0 -76
  256. package/telegram-plugin/tests/progress-card-edit-timestamps-budget.test.ts +0 -62
  257. package/telegram-plugin/tests/progress-card-memory-bounds.test.ts +0 -84
  258. package/telegram-plugin/tests/progress-card-pin-failure-paths.test.ts +0 -139
  259. package/telegram-plugin/tests/progress-card-pin-manager.test.ts +0 -773
  260. package/telegram-plugin/tests/progress-card-pin-race-fast-turn.test.ts +0 -66
  261. package/telegram-plugin/tests/progress-card-pin-sidecar-partial-write.test.ts +0 -64
  262. package/telegram-plugin/tests/progress-card-pin-watchdog.test.ts +0 -190
  263. package/telegram-plugin/tests/progress-card-sigterm-pin-flush.test.ts +0 -146
  264. package/telegram-plugin/tests/real-gateway-f1-ladder-integrity.test.ts +0 -123
  265. package/telegram-plugin/tests/real-gateway-f2-instant-draft.test.ts +0 -82
  266. package/telegram-plugin/tests/real-gateway-f3-late-card.test.ts +0 -114
  267. package/telegram-plugin/tests/real-gateway-harness.ts +0 -699
  268. package/telegram-plugin/tests/real-gateway-i6-turn-flush-replay-dedup.test.ts +0 -313
  269. package/telegram-plugin/tests/real-gateway-ipc-lifecycle.test.ts +0 -299
  270. package/telegram-plugin/tests/real-gateway-spec.test.ts +0 -487
  271. package/telegram-plugin/tests/real-gateway.smoke.test.ts +0 -101
  272. package/telegram-plugin/tests/setup-flow.test.ts +0 -510
  273. package/telegram-plugin/tests/setup-state.test.ts +0 -146
  274. package/telegram-plugin/tests/sync-chat-running-subagents.test.ts +0 -116
  275. package/telegram-plugin/tests/turn-end-regressions.test.ts +0 -489
  276. package/telegram-plugin/tests/turn-flush-card-takeover.test.ts +0 -218
  277. package/telegram-plugin/tests/turn-flush-prose-recovery.test.ts +0 -78
  278. package/telegram-plugin/tests/two-zone-bg-carry-full-lifecycle.test.ts +0 -131
  279. package/telegram-plugin/tests/two-zone-bg-detection.test.ts +0 -120
  280. package/telegram-plugin/tests/two-zone-bg-done-when-all-terminal.test.ts +0 -116
  281. package/telegram-plugin/tests/two-zone-bg-early-turn-end.test.ts +0 -87
  282. package/telegram-plugin/tests/two-zone-bg-survives-next-turn.test.ts +0 -211
  283. package/telegram-plugin/tests/two-zone-card-cap.test.ts +0 -62
  284. package/telegram-plugin/tests/two-zone-card-fleet-row.test.ts +0 -101
  285. package/telegram-plugin/tests/two-zone-card-header-phases.test.ts +0 -78
  286. package/telegram-plugin/tests/two-zone-card-html-balance.test.ts +0 -110
  287. package/telegram-plugin/tests/two-zone-card-lifecycle.test.ts +0 -128
  288. package/telegram-plugin/tests/two-zone-card-sanitise.test.ts +0 -58
  289. package/telegram-plugin/tests/two-zone-card-snapshot.test.ts +0 -133
  290. package/telegram-plugin/tests/two-zone-concurrent-turns-isolation.test.ts +0 -155
  291. package/telegram-plugin/tests/two-zone-phasefor-precedence.test.ts +0 -117
  292. package/telegram-plugin/tests/two-zone-snapshot-extras.test.ts +0 -187
  293. package/telegram-plugin/tests/two-zone-stuck-edit-throttle.test.ts +0 -149
  294. package/telegram-plugin/tests/two-zone-stuck-header-escalation.test.ts +0 -101
  295. package/telegram-plugin/tests/two-zone-stuck-per-member.test.ts +0 -114
  296. package/telegram-plugin/tests/two-zone-stuck-recovery.test.ts +0 -105
  297. package/telegram-plugin/tests/waiting-ux-harness.ts +0 -381
  298. package/telegram-plugin/tests/waiting-ux.e2e.test.ts +0 -233
  299. package/telegram-plugin/turn-flush-prose-recovery.ts +0 -40
  300. package/telegram-plugin/two-zone-card.ts +0 -269
  301. package/telegram-plugin/uat/scenarios/smoke-clerk-reply.test.ts +0 -61
@@ -1,269 +0,0 @@
1
- /**
2
- * P1 of #662 — `renderTwoZoneCard` pure renderer.
3
- *
4
- * Two-zone status card: PARENT bullets + FLEET rows. Replaces the v1
5
- * expandable-blockquote-per-sub-agent layout (still live behind the
6
- * default codepath; gated on `TWO_ZONE_CARD=1` for now).
7
- *
8
- * Pure: no IO, no globals, no clock except the caller-supplied `now`.
9
- * All sanitisation happens upstream in `fleet-state.ts`; this module
10
- * only formats already-sanitised values.
11
- *
12
- * Spec: `reference/status-card-design.md` (PR #661).
13
- */
14
-
15
- import type { FleetMember, FleetStatus } from './fleet-state.js'
16
- import { cap } from './fleet-state.js'
17
- import type { ProgressCardState, RenderOptions, TaskNum } from './progress-card.js'
18
- import { escapeHtml, formatDuration } from './card-format.js'
19
- import { mcpDisplayName, toolFallbackLabel } from './tool-labels.js'
20
-
21
- const PARENT_BULLET_CAP = 8
22
- const FLEET_ROW_CAP = 5
23
- const STUCK_IDLE_MS = 60_000
24
-
25
- export interface TwoZoneCardInput {
26
- state: ProgressCardState
27
- fleet: ReadonlyMap<string, FleetMember>
28
- now: number
29
- taskNum?: TaskNum
30
- opts?: RenderOptions
31
- }
32
-
33
- export interface PhaseResolution {
34
- icon: string
35
- label: string
36
- }
37
-
38
- // ─── Public entry point ─────────────────────────────────────────────────────
39
-
40
- export function renderTwoZoneCard(input: TwoZoneCardInput): string {
41
- const { state, fleet, now, opts } = input
42
- if (state.turnStartedAt === 0) {
43
- return '⏳ Waiting…'
44
- }
45
- const phase = phaseFor(state, fleet, now, opts as Record<string, unknown> | undefined)
46
- const elapsedMs = Math.max(0, now - state.turnStartedAt)
47
- const totalTools = countTotalTools(state, fleet)
48
- const subCount = fleet.size
49
- const lines: string[] = []
50
- lines.push(renderHeader(phase, elapsedMs, totalTools, subCount, input.taskNum))
51
- const parentZone = renderParentZone(state)
52
- if (parentZone) {
53
- lines.push('')
54
- lines.push(parentZone)
55
- }
56
- const fleetZone = renderFleetZone(fleet, now)
57
- if (fleetZone) {
58
- lines.push('')
59
- lines.push(fleetZone)
60
- }
61
- return lines.join('\n')
62
- }
63
-
64
- // ─── Phase resolver ─────────────────────────────────────────────────────────
65
-
66
- /**
67
- * Maps (parent state, fleet, opts) to a single header phase. Truth
68
- * table from `reference/status-card-design.md` §Header. Precedence:
69
- * forced-close > silent-end (gated on parent terminal) > stalled >
70
- * background > done > working.
71
- *
72
- * SilentEnd is lifted ABOVE the background/done checks so that a fleet
73
- * still running can't suppress the silent-end label once the parent has
74
- * terminated without a reply. It IS gated on `parentDone || stage===done`
75
- * to prevent firing prematurely while the parent is still in flight.
76
- */
77
- export function phaseFor(
78
- state: ProgressCardState,
79
- fleet: ReadonlyMap<string, FleetMember>,
80
- now: number,
81
- opts?: Record<string, unknown>,
82
- ): PhaseResolution {
83
- const stalledClose = opts?.stalledClose === true
84
- const silentEnd = opts?.silentEnd === true
85
- const parentDone = opts?.parentDone === true || state.stage === 'done'
86
-
87
- if (stalledClose) return { icon: '⚠', label: 'Forced close' }
88
-
89
- const fleetRunning = anyFleetActive(fleet)
90
- // Stalled = "the only fleet members that could still make progress have
91
- // gone idle past the threshold". An empty filtered list (every fleet
92
- // member is already terminal) MUST NOT count as stalled — Array#every
93
- // returns true vacuously on `[]`, which previously caused the card to
94
- // freeze at ⚠ Stalled the moment the last sub-agent finished while the
95
- // parent was still running. We require at least one running-or-stuck
96
- // member before claiming the fleet is stalled.
97
- const runningOrStuck = [...fleet.values()].filter((m) => m.status === 'running' || m.status === 'stuck')
98
- const fleetAllStuck = runningOrStuck.length > 0 && runningOrStuck.every((m) => isStuck(m, now))
99
-
100
- // SilentEnd: parent terminated without a reply. Lifted above the
101
- // background/done branches so a still-running fleet can't mask it,
102
- // but gated on parentDone so we don't fire while parent is in flight.
103
- if (silentEnd && parentDone) {
104
- return { icon: '🙊', label: 'Ended without reply' }
105
- }
106
-
107
- // Stalled: every running-or-stuck member is past the idle threshold.
108
- // Members already terminal (done/failed) are excluded from this check —
109
- // a fleet of [done, stuck] still surfaces as Stalled because the only
110
- // member that could still make progress is no longer doing so. The
111
- // `fleetAllStuck` calc itself guards against an empty filtered list
112
- // (otherwise `[].every()` would lock the card at Stalled the moment
113
- // the last sub-agent finished).
114
- if (fleetAllStuck && !parentDone) {
115
- return { icon: '⚠', label: 'Stalled' }
116
- }
117
-
118
- // Background: parentDone but at least one fleet member still running
119
- if (parentDone && fleetRunning) {
120
- return { icon: '⏸', label: 'Background' }
121
- }
122
-
123
- // Done: parent terminal + no fleet still running
124
- if (parentDone && !fleetRunning) {
125
- return { icon: '✅', label: 'Done' }
126
- }
127
-
128
- // Default: active work
129
- return { icon: '⚙️', label: 'Working…' }
130
- }
131
-
132
- function anyFleetActive(fleet: ReadonlyMap<string, FleetMember>): boolean {
133
- for (const m of fleet.values()) {
134
- if (m.status === 'running' || m.status === 'background' || m.status === 'stuck') return true
135
- }
136
- return false
137
- }
138
-
139
- function isStuck(m: FleetMember, now: number): boolean {
140
- if (m.status === 'stuck') return true
141
- return m.status === 'running' && now - m.lastActivityAt > STUCK_IDLE_MS
142
- }
143
-
144
- // ─── Header ─────────────────────────────────────────────────────────────────
145
-
146
- function renderHeader(
147
- phase: PhaseResolution,
148
- elapsedMs: number,
149
- totalTools: number,
150
- subCount: number,
151
- taskNum?: TaskNum,
152
- ): string {
153
- const tools = totalTools >= 100 ? '99+' : String(totalTools)
154
- const elapsed = formatDuration(elapsedMs)
155
- const parts = [`${phase.icon} <b>${escapeHtml(phase.label)}</b>`, `⏱ ${elapsed}`, `🔧 ${tools}`]
156
- if (subCount > 0) parts.push(`🤖 ${subCount}`)
157
- if (taskNum && taskNum.total > 1) parts.push(`#${taskNum.index}/${taskNum.total}`)
158
- return parts.join(' · ')
159
- }
160
-
161
- // ─── Parent zone ────────────────────────────────────────────────────────────
162
-
163
- function renderParentZone(state: ProgressCardState): string {
164
- const items = state.items
165
- if (items.length === 0) return ''
166
- const lines: string[] = ['<b>PARENT</b>']
167
- const visible = items.slice(-PARENT_BULLET_CAP)
168
- const earlier = items.length - visible.length
169
- if (earlier > 0) lines.push(`(+${earlier} earlier)`)
170
- const inFlight = state.stage !== 'done'
171
- const lastIdx = visible.length - 1
172
- for (let i = 0; i < visible.length; i++) {
173
- const it = visible[i]
174
- let text: string
175
- if (it.label) {
176
- text = escapeHtml(truncate(it.label, 80))
177
- } else {
178
- const tool = it.tool || ''
179
- const fallback = tool.startsWith('mcp__')
180
- ? mcpDisplayName(tool) || toolFallbackLabel(tool)
181
- : toolFallbackLabel(tool)
182
- text = escapeHtml(fallback)
183
- }
184
- if (inFlight && i === lastIdx) {
185
- lines.push(`◉ ${text}`)
186
- } else {
187
- lines.push(`● ${text}`)
188
- }
189
- }
190
- return lines.join('\n')
191
- }
192
-
193
- // ─── Fleet zone ─────────────────────────────────────────────────────────────
194
-
195
- export function renderFleetZone(fleet: ReadonlyMap<string, FleetMember>, now: number): string {
196
- if (fleet.size === 0) return ''
197
- const all = [...fleet.values()]
198
- const { visible, hidden } = cap(all, FLEET_ROW_CAP)
199
- const lines: string[] = [`<b>FLEET (${fleet.size})</b>`]
200
- for (const m of visible) lines.push(renderFleetRow(m, now))
201
- if (hidden > 0) lines.push(`+ ${hidden} more`)
202
- return lines.join('\n')
203
- }
204
-
205
- export function renderFleetRow(m: FleetMember, now: number): string {
206
- const glyph = glyphForFleetStatus(m.status)
207
- const role = escapeHtml(truncate(m.role || 'agent', 30))
208
- const id6 = escapeHtml(m.agentId.slice(0, 6))
209
- const tools = `${m.toolCount}t`
210
- const activity = formatLastActivity(m, now)
211
- return `${glyph} ${role} <code>${id6}</code> · ${tools} · ${activity}`
212
- }
213
-
214
- export function formatLastActivity(m: FleetMember, now: number): string {
215
- // Terminal states show "<status> <relative-time>"
216
- if (m.terminalAt != null) {
217
- const age = formatRelativeTime(Math.max(0, now - m.terminalAt))
218
- return `${escapeHtml(m.status)} ${age}`
219
- }
220
- // Stuck — show idle duration
221
- if (m.status === 'stuck') {
222
- const idle = formatRelativeTime(Math.max(0, now - m.lastActivityAt))
223
- return `idle ${idle}`
224
- }
225
- // Running — show last tool + age
226
- if (m.lastTool == null) {
227
- const age = formatRelativeTime(Math.max(0, now - m.lastActivityAt))
228
- return `started ${age}`
229
- }
230
- const age = formatRelativeTime(Math.max(0, now - m.lastActivityAt))
231
- const arg = m.lastTool.sanitisedArg
232
- ? ` <code>${escapeHtml(truncate(m.lastTool.sanitisedArg, 60))}</code>`
233
- : ''
234
- return `${escapeHtml(m.lastTool.name)}${arg} (${age})`
235
- }
236
-
237
- export function glyphForFleetStatus(status: FleetStatus): string {
238
- switch (status) {
239
- case 'running': return '↻'
240
- case 'background': return '⏸'
241
- case 'done': return '✓'
242
- case 'failed': return '✗'
243
- case 'stuck': return '⚠'
244
- case 'killed': return '✗'
245
- case 'capped': return '⚠'
246
- }
247
- }
248
-
249
- // ─── Helpers ────────────────────────────────────────────────────────────────
250
-
251
- export function formatRelativeTime(ms: number): string {
252
- const s = Math.floor(ms / 1000)
253
- if (s < 60) return `${s}s ago`
254
- const m = Math.floor(s / 60)
255
- const r = s % 60
256
- if (r === 0) return `${m}m ago`
257
- return `${m}m${r}s ago`
258
- }
259
-
260
- function truncate(s: string, n: number): string {
261
- if (s.length <= n) return s
262
- return s.slice(0, n - 1) + '…'
263
- }
264
-
265
- function countTotalTools(state: ProgressCardState, fleet: ReadonlyMap<string, FleetMember>): number {
266
- let n = state.items.length
267
- for (const m of fleet.values()) n += m.toolCount
268
- return n
269
- }
@@ -1,61 +0,0 @@
1
- /**
2
- * Smoke scenario — clerk replies to a simple text message.
3
- *
4
- * Part of: https://github.com/switchroom/switchroom/issues/866
5
- *
6
- * Phase 1 status: this file exercises the harness shape (typecheck-
7
- * clean, reads as the canonical example) but will FAIL at runtime
8
- * because `harness.spinUp` is stubbed. Phase 2 wires the real
9
- * lifecycle and this becomes the first green UAT scenario.
10
- *
11
- * The shape mirrors the canonical scenario in epic #863 so reviewers
12
- * can map directly between the design doc and the code.
13
- */
14
-
15
- import { describe, it, expect } from "vitest";
16
- import { spinUp } from "../harness.js";
17
-
18
- describe("uat: clerk smoke", () => {
19
- it("replies to a text message with the right reaction sequence + HTML reply", async () => {
20
- const sc = await spinUp({ agent: "clerk", topic: "smoke-clerk-reply" });
21
-
22
- try {
23
- const sent = await sc.driver.sendText(
24
- sc.chatId,
25
- "summarize this short note",
26
- { messageThreadId: sc.threadId },
27
- );
28
-
29
- // Status reactions should walk 👀 → 🤔 → 🔥 → 👍 on the inbound
30
- // message within 30s on a healthy run.
31
- await sc.expectReaction(
32
- sent.messageId,
33
- ["👀", "🤔", "🔥", "👍"],
34
- { timeout: 30_000 },
35
- );
36
-
37
- // The progress card should be pinned within a few seconds.
38
- const card = await sc.expectPinnedCard({ timeout: 5_000 });
39
-
40
- // And ride to `done` within the model's normal turn budget.
41
- const finalCard = await sc.waitForCardPhase(card, "done", {
42
- timeout: 60_000,
43
- });
44
- expect(finalCard.text).toMatch(/Done|✅/);
45
-
46
- // The actual reply prose lands as a fresh bot message, in HTML.
47
- const reply = await sc.expectMessage(/./, {
48
- from: "bot",
49
- timeout: 60_000,
50
- });
51
- // Reviewer note: parse_mode isn't on the wire-level update;
52
- // we proxy via "did the rendered HTML contain a `<` we
53
- // recognize, or did markdown leak as plain `*`?". Phase 2
54
- // will pick the right shape — left as a TODO so this file
55
- // typechecks today.
56
- expect(reply.text.length).toBeGreaterThan(0);
57
- } finally {
58
- await sc.tearDown();
59
- }
60
- });
61
- });