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,201 +0,0 @@
1
- /**
2
- * Regression tests for #757 — progress card goes silent for background
3
- * Agent workers (run_in_background: true).
4
- *
5
- * Root cause: `applyToolUse` in fleet-state.ts only promoted `stuck →
6
- * running`; background members stayed at `status: 'background'` even
7
- * while actively running tools. The fleet row rendered ⏸ idle instead
8
- * of ↻ + last-tool, so the card appeared frozen.
9
- *
10
- * Fix: applyToolUse now also promotes `background → running` on the
11
- * first live tool event. A separate sticky `isBackgroundDispatch` flag
12
- * preserves the background-carry semantics used by hasLiveBackground
13
- * (keeps PerChatState alive past parent turn_end until bg member
14
- * reaches terminal status).
15
- */
16
-
17
- import { describe, it, expect } from 'vitest'
18
- import { createProgressDriver } from '../progress-card-driver.js'
19
- import { applyToolUse, createFleetMember, hasLiveBackground } from '../fleet-state.js'
20
- import type { SessionEvent } from '../session-tail.js'
21
-
22
- const T0 = 1_700_000_000_000
23
-
24
- // ─── Pure-function unit tests ────────────────────────────────────────────────
25
-
26
- describe('applyToolUse: background → running promotion (#757)', () => {
27
- it('promotes background to running on first tool event', () => {
28
- const m = { ...createFleetMember({ agentId: 'a', role: 'worker', startedAt: T0, originatingTurnKey: 'k', isBackgroundDispatch: true }), status: 'background' as const }
29
- const after = applyToolUse(m, 'Read', { file_path: '/foo/bar.ts' }, T0 + 1000)
30
- expect(after.status).toBe('running')
31
- expect(after.lastTool?.name).toBe('Read')
32
- })
33
-
34
- it('preserves isBackgroundDispatch after promotion', () => {
35
- const m = { ...createFleetMember({ agentId: 'a', role: 'worker', startedAt: T0, originatingTurnKey: 'k', isBackgroundDispatch: true }), status: 'background' as const }
36
- const after = applyToolUse(m, 'Bash', { command: 'ls' }, T0 + 1000)
37
- expect(after.isBackgroundDispatch).toBe(true)
38
- })
39
-
40
- it('does not affect foreground members (status stays running)', () => {
41
- const m = createFleetMember({ agentId: 'a', role: 'worker', startedAt: T0, originatingTurnKey: 'k' })
42
- const after = applyToolUse(m, 'Read', { file_path: '/x' }, T0 + 1000)
43
- expect(after.status).toBe('running')
44
- expect(after.isBackgroundDispatch).toBe(false)
45
- })
46
- })
47
-
48
- describe('hasLiveBackground: sticky flag survives status promotion (#757)', () => {
49
- it('returns true when background member is promoted to running (not yet terminal)', () => {
50
- const fleet = new Map([
51
- ['a', { ...createFleetMember({ agentId: 'a', role: 'w', startedAt: T0, originatingTurnKey: 'k', isBackgroundDispatch: true }), status: 'running' as const }],
52
- ])
53
- expect(hasLiveBackground(fleet)).toBe(true)
54
- })
55
-
56
- it('returns false when background member reaches terminal status', () => {
57
- const fleet = new Map([
58
- ['a', { ...createFleetMember({ agentId: 'a', role: 'w', startedAt: T0, originatingTurnKey: 'k', isBackgroundDispatch: true }), status: 'done' as const, terminalAt: T0 + 5000 }],
59
- ])
60
- expect(hasLiveBackground(fleet)).toBe(false)
61
- })
62
-
63
- it('returns false when no members are background dispatches', () => {
64
- const fleet = new Map([
65
- ['a', createFleetMember({ agentId: 'a', role: 'w', startedAt: T0, originatingTurnKey: 'k' })],
66
- ])
67
- expect(hasLiveBackground(fleet)).toBe(false)
68
- })
69
- })
70
-
71
- // ─── Integration: driver-level lifecycle ─────────────────────────────────────
72
-
73
- function harness() {
74
- let now = 1000
75
- const timers: Array<{ fireAt: number; fn: () => void; ref: number; repeat?: number }> = []
76
- let nextRef = 0
77
- const completions: string[] = []
78
- const driver = createProgressDriver({
79
- emit: () => {},
80
- minIntervalMs: 500,
81
- coalesceMs: 400,
82
- initialDelayMs: 0,
83
- promoteAfterMs: 999_999,
84
- onTurnComplete: (s) => completions.push(s.turnKey),
85
- now: () => now,
86
- setTimeout: (fn, ms) => {
87
- const ref = nextRef++
88
- timers.push({ fireAt: now + ms, fn, ref })
89
- return { ref }
90
- },
91
- clearTimeout: (h) => {
92
- const ref = (h as { ref: number }).ref
93
- const idx = timers.findIndex((t) => t.ref === ref)
94
- if (idx !== -1) timers.splice(idx, 1)
95
- },
96
- setInterval: (fn, ms) => {
97
- const ref = nextRef++
98
- timers.push({ fireAt: now + ms, fn, ref, repeat: ms })
99
- return { ref }
100
- },
101
- clearInterval: (h) => {
102
- const ref = (h as { ref: number }).ref
103
- const idx = timers.findIndex((t) => t.ref === ref)
104
- if (idx !== -1) timers.splice(idx, 1)
105
- },
106
- })
107
- function advance(ms: number) {
108
- const target = now + ms
109
- while (true) {
110
- const due = timers.filter((t) => t.fireAt <= target).sort((a, b) => a.fireAt - b.fireAt)
111
- if (due.length === 0) break
112
- const t = due[0]
113
- now = t.fireAt
114
- t.fn()
115
- if (t.repeat) t.fireAt = now + t.repeat
116
- else timers.splice(timers.indexOf(t), 1)
117
- }
118
- now = target
119
- }
120
- return { driver, completions, advance, getNow: () => now }
121
- }
122
-
123
- const enqueue = (chatId: string): SessionEvent => ({
124
- kind: 'enqueue',
125
- chatId,
126
- messageId: '1',
127
- threadId: null,
128
- rawContent: `<channel chat_id="${chatId}">go</channel>`,
129
- })
130
-
131
- describe('driver integration: bg worker tool activity (#757)', () => {
132
- it('background fleet member promotes to running when tool events arrive', () => {
133
- const { driver } = harness()
134
- const CHAT = 'c1'
135
- driver.ingest(enqueue(CHAT), null)
136
- driver.ingest(
137
- { kind: 'tool_use', toolName: 'Agent', toolUseId: 'tu1', input: { prompt: 'bg work', run_in_background: true } },
138
- CHAT,
139
- )
140
- driver.ingest({ kind: 'sub_agent_started', agentId: 'sa1', firstPromptText: 'bg work' }, CHAT)
141
-
142
- // Initial state: background.
143
- expect(driver.peekFleet(CHAT)!.get('sa1')!.status).toBe('background')
144
-
145
- // Tool activity arrives from the sub-agent JSONL.
146
- driver.ingest({ kind: 'sub_agent_tool_use', agentId: 'sa1', toolUseId: 't1', toolName: 'Bash', input: { command: 'npm test' } }, CHAT)
147
-
148
- const m = driver.peekFleet(CHAT)!.get('sa1')!
149
- // Promoted to running — card now shows active tool work.
150
- expect(m.status).toBe('running')
151
- expect(m.lastTool?.name).toBe('Bash')
152
- // Sticky flag preserved — bg-carry still works.
153
- expect(m.isBackgroundDispatch).toBe(true)
154
- })
155
-
156
- it('background carry survives promotion: turn completion holds until bg reaches terminal', () => {
157
- const { driver, completions } = harness()
158
- const CHAT = 'c2'
159
- driver.ingest(enqueue(CHAT), null)
160
- driver.ingest(
161
- { kind: 'tool_use', toolName: 'Agent', toolUseId: 'tu1', input: { prompt: 'bg', run_in_background: true } },
162
- CHAT,
163
- )
164
- driver.ingest({ kind: 'sub_agent_started', agentId: 'sa1', firstPromptText: 'bg' }, CHAT)
165
- // Bg worker starts doing tool work — status becomes running.
166
- driver.ingest({ kind: 'sub_agent_tool_use', agentId: 'sa1', toolUseId: 't1', toolName: 'Read', input: { file_path: '/a' } }, CHAT)
167
- expect(driver.peekFleet(CHAT)!.get('sa1')!.status).toBe('running')
168
-
169
- driver.ingest({ kind: 'tool_use', toolName: 'mcp__switchroom-telegram__reply' }, CHAT)
170
- driver.recordOutboundDelivered(CHAT)
171
- // Parent ends while bg worker is still running.
172
- driver.ingest({ kind: 'turn_end', durationMs: 100 }, CHAT)
173
-
174
- // Turn completion must NOT fire — bg worker is still active.
175
- expect(completions.length).toBe(0)
176
-
177
- // Bg worker finishes.
178
- driver.ingest({ kind: 'sub_agent_turn_end', agentId: 'sa1' }, CHAT)
179
- expect(completions.length).toBe(1)
180
- })
181
-
182
- it('terminal state reached after promotion fires completion correctly', () => {
183
- const { driver, completions } = harness()
184
- const CHAT = 'c3'
185
- driver.ingest(enqueue(CHAT), null)
186
- driver.ingest(
187
- { kind: 'tool_use', toolName: 'Agent', toolUseId: 'tu1', input: { prompt: 'bg', run_in_background: true } },
188
- CHAT,
189
- )
190
- driver.ingest({ kind: 'sub_agent_started', agentId: 'sa1', firstPromptText: 'bg' }, CHAT)
191
- driver.ingest({ kind: 'sub_agent_tool_use', agentId: 'sa1', toolUseId: 't1', toolName: 'Write', input: { file_path: '/out.ts' } }, CHAT)
192
-
193
- driver.ingest({ kind: 'tool_use', toolName: 'mcp__switchroom-telegram__reply' }, CHAT)
194
- driver.recordOutboundDelivered(CHAT)
195
- driver.ingest({ kind: 'turn_end', durationMs: 100 }, CHAT)
196
- // Peek before sub_agent_turn_end so fleet is still live.
197
- expect(driver.peekFleet(CHAT)!.get('sa1')!.status).toBe('running')
198
- driver.ingest({ kind: 'sub_agent_turn_end', agentId: 'sa1' }, CHAT)
199
- expect(completions.length).toBe(1)
200
- })
201
- })
@@ -1,137 +0,0 @@
1
- /**
2
- * Boot card per-account quota rendering — issue #708.
3
- *
4
- * Verifies:
5
- * - When `accounts` is absent / empty, the card stays silent (today's
6
- * contract — no accounts section).
7
- * - When `accounts` is present, the card appends an "Accounts (N)"
8
- * header and one line per account with 5h % / 7d % / nearest reset.
9
- * - The ▶ marker tags the active-for-this-agent account; ↳ tags the
10
- * rest.
11
- * - HTML escaping on account labels.
12
- * - Account with no quota fields (label only) renders the row without
13
- * any percent / reset suffix.
14
- */
15
-
16
- import { describe, it, expect } from 'vitest'
17
- import {
18
- renderBootCard,
19
- renderAccountRows,
20
- } from '../gateway/boot-card.js'
21
- import type { AccountSummary } from '../auth-dashboard.js'
22
-
23
- const NOW = new Date('2026-05-05T10:00:00Z')
24
-
25
- function mk(overrides: Partial<AccountSummary> = {}): AccountSummary {
26
- return {
27
- label: 'pixsoul@gmail.com',
28
- health: 'healthy',
29
- enabledHere: true,
30
- activeForThisAgent: false,
31
- fiveHourPct: 10,
32
- sevenDayPct: 79,
33
- fiveHourResetAt: NOW.getTime() + 2 * 3600_000 + 14 * 60_000,
34
- sevenDayResetAt: NOW.getTime() + 5 * 86_400_000,
35
- ...overrides,
36
- }
37
- }
38
-
39
- describe('renderBootCard — per-account quota (issue #708)', () => {
40
- it('omits the accounts section when accounts is undefined', () => {
41
- const out = renderBootCard({ agentName: 'clerk', version: 'v0.7.0' })
42
- expect(out).not.toContain('Accounts')
43
- })
44
-
45
- it('omits the accounts section when accounts is empty', () => {
46
- const out = renderBootCard({
47
- agentName: 'clerk',
48
- version: 'v0.7.0',
49
- accounts: [],
50
- })
51
- expect(out).not.toContain('Accounts')
52
- })
53
-
54
- it('renders the active account with ▶ and inline 5h / 7d / reset', () => {
55
- const out = renderBootCard({
56
- agentName: 'clerk',
57
- version: 'v0.7.0',
58
- accounts: [mk({ activeForThisAgent: true })],
59
- now: NOW,
60
- })
61
- expect(out).toContain('Accounts (1)')
62
- expect(out).toContain('▶')
63
- expect(out).toContain('pixsoul@gmail.com')
64
- expect(out).toContain('10%')
65
- expect(out).toContain('79%')
66
- expect(out).toContain('5h resets in')
67
- })
68
-
69
- it('renders fallback accounts with ↳', () => {
70
- const out = renderBootCard({
71
- agentName: 'clerk',
72
- version: 'v0.7.0',
73
- now: NOW,
74
- accounts: [
75
- mk({ activeForThisAgent: true }),
76
- mk({ label: 'ken+work@example.com', activeForThisAgent: false }),
77
- ],
78
- })
79
- expect(out).toContain('Accounts (2)')
80
- expect(out).toMatch(/↳ <code>ken\+work@example\.com<\/code>/)
81
- })
82
-
83
- it('escapes HTML in labels', () => {
84
- const out = renderAccountRows(
85
- [mk({ label: 'evil<script>', activeForThisAgent: true })],
86
- NOW,
87
- )
88
- expect(out.join('\n')).toContain('evil&lt;script&gt;')
89
- expect(out.join('\n')).not.toContain('<script>')
90
- })
91
-
92
- it('renders a row with no quota numbers as label-only', () => {
93
- const summary: AccountSummary = {
94
- label: 'just-added',
95
- health: 'healthy',
96
- enabledHere: true,
97
- activeForThisAgent: false,
98
- }
99
- const out = renderAccountRows([summary], NOW)
100
- expect(out).toHaveLength(2)
101
- expect(out[1]).toBe('↳ <code>just-added</code>')
102
- })
103
-
104
- it('shows 7d reset when 5h reset is missing', () => {
105
- const out = renderAccountRows(
106
- [
107
- mk({
108
- activeForThisAgent: true,
109
- fiveHourPct: 0,
110
- sevenDayPct: 99,
111
- fiveHourResetAt: undefined,
112
- sevenDayResetAt: NOW.getTime() + 86_400_000 + 3 * 3600_000,
113
- }),
114
- ],
115
- NOW,
116
- )
117
- expect(out.join('\n')).toContain('7d resets in')
118
- })
119
-
120
- it('drops the reset suffix once the reset timestamp has elapsed', () => {
121
- const out = renderAccountRows(
122
- [
123
- mk({
124
- activeForThisAgent: true,
125
- fiveHourPct: 0,
126
- sevenDayPct: 0,
127
- fiveHourResetAt: NOW.getTime() - 60_000,
128
- sevenDayResetAt: undefined,
129
- }),
130
- ],
131
- NOW,
132
- )
133
- // Past-reset timestamps return "" from formatNearestAccountResetSuffix,
134
- // so the line should not contain "resets in" at all.
135
- expect(out.join('\n')).not.toContain('resets in')
136
- })
137
- })
@@ -1,145 +0,0 @@
1
- /**
2
- * Tests for the structured card-event logger — the audit trail for the
3
- * pinned progress card lifecycle. Mirrors `pin-event-log.test.ts`.
4
- */
5
-
6
- import { describe, it, expect, beforeEach, afterEach } from 'vitest'
7
- import { mkdtempSync, readFileSync, existsSync, rmSync } from 'fs'
8
- import { tmpdir } from 'os'
9
- import { join } from 'path'
10
- import {
11
- logCardEvent,
12
- emitCardEvent,
13
- resolveCardEventPath,
14
- _resetForTests,
15
- type CardEvent,
16
- } from '../card-event-log.js'
17
-
18
- let tmpDir: string
19
- const prevStateDir = process.env.STATE_DIR
20
-
21
- beforeEach(() => {
22
- tmpDir = mkdtempSync(join(tmpdir(), 'card-event-log-'))
23
- _resetForTests()
24
- })
25
-
26
- afterEach(() => {
27
- _resetForTests()
28
- if (prevStateDir === undefined) delete process.env.STATE_DIR
29
- else process.env.STATE_DIR = prevStateDir
30
- try { rmSync(tmpDir, { recursive: true, force: true }) } catch { /* ignore */ }
31
- })
32
-
33
- describe('logCardEvent (injected writer)', () => {
34
- it('writes one JSON line per call', () => {
35
- const lines: string[] = []
36
- const ev: CardEvent = {
37
- ts: 1700000000000,
38
- agent: 'klanker',
39
- chatId: '100',
40
- turnKey: '100::1',
41
- cardMessageId: 4242,
42
- event: 'rendered',
43
- htmlHash: 'abc123def456',
44
- }
45
- logCardEvent(ev, (l) => lines.push(l))
46
- expect(lines).toHaveLength(1)
47
- expect(lines[0].endsWith('\n')).toBe(true)
48
- const payload = JSON.parse(lines[0].trimEnd())
49
- expect(payload).toEqual(ev)
50
- })
51
-
52
- it('omits undefined optional fields cleanly', () => {
53
- const lines: string[] = []
54
- logCardEvent(
55
- {
56
- ts: 1,
57
- agent: 'a',
58
- chatId: 'c',
59
- turnKey: 'c::1',
60
- event: 'finalized',
61
- },
62
- (l) => lines.push(l),
63
- )
64
- const raw = lines[0].trimEnd()
65
- expect(raw).not.toContain('undefined')
66
- const payload = JSON.parse(raw)
67
- expect(payload.cardMessageId).toBeUndefined()
68
- expect(payload.reason).toBeUndefined()
69
- expect(payload.subagents).toBeUndefined()
70
- })
71
-
72
- it('preserves subagents array and durationMs', () => {
73
- const lines: string[] = []
74
- logCardEvent(
75
- {
76
- ts: 2,
77
- agent: 'a',
78
- chatId: 'c',
79
- turnKey: 'c::1',
80
- event: 'deferred',
81
- reason: 'in-flight-sub-agents',
82
- subagents: ['agent-1', 'agent-2'],
83
- durationMs: 12345,
84
- },
85
- (l) => lines.push(l),
86
- )
87
- const payload = JSON.parse(lines[0].trimEnd())
88
- expect(payload.subagents).toEqual(['agent-1', 'agent-2'])
89
- expect(payload.durationMs).toBe(12345)
90
- expect(payload.reason).toBe('in-flight-sub-agents')
91
- })
92
- })
93
-
94
- describe('emitCardEvent', () => {
95
- it('fills ts when omitted', () => {
96
- const lines: string[] = []
97
- const before = Date.now()
98
- emitCardEvent(
99
- { agent: 'a', chatId: 'c', turnKey: 'c::1', event: 'edited' },
100
- (l) => lines.push(l),
101
- )
102
- const after = Date.now()
103
- const payload = JSON.parse(lines[0].trimEnd())
104
- expect(payload.ts).toBeGreaterThanOrEqual(before)
105
- expect(payload.ts).toBeLessThanOrEqual(after)
106
- })
107
-
108
- it('respects an explicit ts', () => {
109
- const lines: string[] = []
110
- emitCardEvent(
111
- { ts: 999, agent: 'a', chatId: 'c', turnKey: 'c::1', event: 'edited' },
112
- (l) => lines.push(l),
113
- )
114
- expect(JSON.parse(lines[0].trimEnd()).ts).toBe(999)
115
- })
116
- })
117
-
118
- describe('resolveCardEventPath', () => {
119
- it('returns <STATE_DIR>/card-events.jsonl when STATE_DIR is set', () => {
120
- expect(resolveCardEventPath({ STATE_DIR: '/tmp/x' })).toBe('/tmp/x/card-events.jsonl')
121
- })
122
-
123
- it('returns null when STATE_DIR is unset', () => {
124
- expect(resolveCardEventPath({})).toBeNull()
125
- })
126
-
127
- it('returns null when STATE_DIR is empty', () => {
128
- expect(resolveCardEventPath({ STATE_DIR: '' })).toBeNull()
129
- })
130
- })
131
-
132
- describe('default writer (filesystem)', () => {
133
- it('appends to <STATE_DIR>/card-events.jsonl when STATE_DIR is set', () => {
134
- process.env.STATE_DIR = tmpDir
135
- _resetForTests()
136
- emitCardEvent({ agent: 'a', chatId: 'c', turnKey: 'c::1', event: 'rendered' })
137
- emitCardEvent({ agent: 'a', chatId: 'c', turnKey: 'c::1', event: 'finalized' })
138
- const target = join(tmpDir, 'card-events.jsonl')
139
- expect(existsSync(target)).toBe(true)
140
- const contents = readFileSync(target, 'utf8').trimEnd().split('\n')
141
- expect(contents).toHaveLength(2)
142
- expect(JSON.parse(contents[0]).event).toBe('rendered')
143
- expect(JSON.parse(contents[1]).event).toBe('finalized')
144
- })
145
- })