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,260 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import {
3
- buildDashboardText,
4
- buildDashboardKeyboard,
5
- parseCallbackData,
6
- encodeCallbackData,
7
- isQuotaHot,
8
- buildRemoveConfirmKeyboard,
9
- QUOTA_HOT_THRESHOLD_PCT,
10
- type DashboardSlot,
11
- } from "../auth-dashboard";
12
-
13
- /**
14
- * Edge-case coverage for the /auth dashboard. Pair with
15
- * auth-dashboard.test.ts which covers the happy paths. This file
16
- * focuses on pathological input, security boundaries, and failure
17
- * modes we should not regress.
18
- */
19
-
20
- function slot(o: Partial<DashboardSlot> = {}): DashboardSlot {
21
- return { slot: "default", active: false, health: "healthy", quotaExhaustedUntil: null, fiveHourPct: null, sevenDayPct: null, ...o };
22
- }
23
-
24
- describe("callback payload — hostile inputs", () => {
25
- const cases: Array<[string, string]> = [
26
- ["shell expansion", "auth:reauth:$(whoami)"],
27
- ["backticks", "auth:reauth:`id`"],
28
- ["semicolons", "auth:reauth:clerk;ls"],
29
- ["pipes", "auth:reauth:clerk|nc attacker"],
30
- ["dot-segments", "auth:use:../../../etc/passwd:default"],
31
- ["null bytes", "auth:reauth:clerk\0xyz"],
32
- ["tabs", "auth:reauth:clerk\ttab"],
33
- ["newlines", "auth:reauth:clerk\nextra"],
34
- ["leading space", "auth:reauth: clerk"],
35
- ["trailing space", "auth:reauth:clerk "],
36
- ["unicode lookalike", "auth:reauth:\u202eevil"],
37
- ["empty agent", "auth:reauth:"],
38
- ["empty slot for use", "auth:use:clerk:"],
39
- ["slot too long (33 chars)", "auth:use:clerk:" + "a".repeat(33)],
40
- ["agent too long (65 chars)", "auth:reauth:" + "a".repeat(65)],
41
- ["slot contains dot", "auth:use:clerk:slot.name"],
42
- ["slot contains slash", "auth:use:clerk:slot/name"],
43
- ["slot contains space", "auth:use:clerk:slot name"],
44
- ];
45
-
46
- it.each(cases)("rejects %s as noop: %s", (_desc, data) => {
47
- expect(parseCallbackData(data)).toEqual({ kind: "noop" });
48
- });
49
-
50
- it("accepts legitimately hyphenated slot names", () => {
51
- expect(parseCallbackData("auth:use:clerk:backup-1")).toMatchObject({ kind: "use", slot: "backup-1" });
52
- });
53
-
54
- it("accepts underscored names", () => {
55
- expect(parseCallbackData("auth:use:clerk:work_personal")).toMatchObject({ kind: "use", slot: "work_personal" });
56
- });
57
- });
58
-
59
- describe("dashboard text — pathological slot states", () => {
60
- it("renders 10 slots without breaking layout", () => {
61
- const slots: DashboardSlot[] = Array.from({ length: 10 }, (_, i) => slot({
62
- slot: `slot-${i}`,
63
- active: i === 0,
64
- }));
65
- const text = buildDashboardText({ agent: "clerk", bankId: "assistant", plan: "max", slots, quotaHot: false });
66
- // All 10 slot names appear in the output
67
- for (let i = 0; i < 10; i++) {
68
- expect(text).toContain(`slot-${i}`);
69
- }
70
- });
71
-
72
- it("renders when ALL slots are quota-exhausted (fallback-impossible state)", () => {
73
- const slots = [
74
- slot({ slot: "default", active: true, health: "quota-exhausted", quotaExhaustedUntil: Date.now() + 60_000 }),
75
- slot({ slot: "backup", active: false, health: "quota-exhausted", quotaExhaustedUntil: Date.now() + 120_000 }),
76
- ];
77
- const text = buildDashboardText({ agent: "clerk", bankId: "assistant", slots, quotaHot: true });
78
- expect(text).toContain("default");
79
- expect(text).toContain("backup");
80
- expect(text.match(/quota-exhausted/g)?.length).toBe(2);
81
- });
82
-
83
- it("renders when a slot has zero quota data (no fiveHour/sevenDay pct)", () => {
84
- const text = buildDashboardText({
85
- agent: "clerk",
86
- bankId: "assistant",
87
- slots: [slot({ active: true, fiveHourPct: null, sevenDayPct: null })],
88
- quotaHot: false,
89
- });
90
- // Shouldn't contain '5h:' or '7d:' if no data
91
- expect(text).not.toContain("5h:");
92
- expect(text).not.toContain("7d:");
93
- });
94
-
95
- it("handles plan=null gracefully", () => {
96
- const text = buildDashboardText({
97
- agent: "clerk",
98
- bankId: "assistant",
99
- plan: null,
100
- slots: [slot({ active: true })],
101
- quotaHot: false,
102
- });
103
- expect(text).toContain("assistant");
104
- expect(text).not.toContain("Plan:");
105
- });
106
-
107
- it("handles 0% utilization (falsy but present)", () => {
108
- const text = buildDashboardText({
109
- agent: "clerk",
110
- bankId: "a",
111
- slots: [slot({ active: true, fiveHourPct: 0, sevenDayPct: 0 })],
112
- quotaHot: false,
113
- });
114
- expect(text).toContain("5h: 0%");
115
- expect(text).toContain("7d: 0%");
116
- });
117
-
118
- it("handles 100% utilization", () => {
119
- const text = buildDashboardText({
120
- agent: "clerk",
121
- bankId: "a",
122
- slots: [slot({ active: true, fiveHourPct: 100, sevenDayPct: 100 })],
123
- quotaHot: true,
124
- });
125
- expect(text).toContain("5h: 100%");
126
- expect(text).toContain("7d: 100%");
127
- });
128
-
129
- it("handles negative reset time (already expired)", () => {
130
- const text = buildDashboardText({
131
- agent: "clerk",
132
- bankId: "a",
133
- slots: [slot({ active: true, health: "quota-exhausted", quotaExhaustedUntil: Date.now() - 60_000 })],
134
- quotaHot: true,
135
- });
136
- // Should clamp to 0 rather than show negative minutes
137
- expect(text).toContain("resets in ~0m");
138
- });
139
-
140
- it("escapes very adversarial agent names", () => {
141
- const text = buildDashboardText({
142
- agent: '</b><script>alert("x")</script><b>',
143
- bankId: "a",
144
- slots: [slot({ active: true })],
145
- quotaHot: false,
146
- });
147
- expect(text).not.toContain("<script>");
148
- expect(text).toContain("&lt;script&gt;");
149
- });
150
- });
151
-
152
- describe("dashboard keyboard — edge cases", () => {
153
- it("caps non-active slot buttons at 3 (prevents runaway rows)", () => {
154
- const slots = [
155
- slot({ slot: "active", active: true }),
156
- slot({ slot: "s1" }),
157
- slot({ slot: "s2" }),
158
- slot({ slot: "s3" }),
159
- slot({ slot: "s4" }),
160
- slot({ slot: "s5" }),
161
- ];
162
- const kb = buildDashboardKeyboard({ agent: "clerk", bankId: "a", slots, quotaHot: false });
163
- const useButtons = kb.inline_keyboard.flat().filter((b) => b.text.startsWith("Use:"));
164
- expect(useButtons.length).toBeLessThanOrEqual(3);
165
- });
166
-
167
- it("no [Reauth active] button when no active slot", () => {
168
- const kb = buildDashboardKeyboard({ agent: "clerk", bankId: "a", slots: [slot({ slot: "a", active: false })], quotaHot: false });
169
- const reauthButton = kb.inline_keyboard.flat().find((b) => b.text.includes("Reauth"));
170
- // There IS still a Reauth button (agent-level, no slot) — expected behaviour
171
- expect(reauthButton).toBeDefined();
172
- // But it should NOT reference a specific slot name
173
- expect(reauthButton!.text).not.toMatch(/Reauth \S+$/);
174
- });
175
-
176
- it("no Use/Remove rows at all when only the active slot exists", () => {
177
- const kb = buildDashboardKeyboard({ agent: "clerk", bankId: "a", slots: [slot({ active: true })], quotaHot: false });
178
- const flat = kb.inline_keyboard.flat();
179
- expect(flat.some((b) => b.text.startsWith("Use:"))).toBe(false);
180
- expect(flat.some((b) => b.text.startsWith("🗑 Remove:"))).toBe(false);
181
- });
182
-
183
- it("empty slot set still shows Add + Refresh", () => {
184
- const kb = buildDashboardKeyboard({ agent: "clerk", bankId: "a", slots: [], quotaHot: false });
185
- const flat = kb.inline_keyboard.flat();
186
- expect(flat.some((b) => b.text.includes("Add slot"))).toBe(true);
187
- expect(flat.some((b) => b.text.includes("Refresh"))).toBe(true);
188
- });
189
-
190
- it("Refresh callback always targets the correct agent", () => {
191
- const kb = buildDashboardKeyboard({ agent: "specific-agent", bankId: "a", slots: [], quotaHot: false });
192
- const refreshBtn = kb.inline_keyboard.flat().find((b) => b.text.includes("Refresh"));
193
- if (refreshBtn && "callback_data" in refreshBtn) {
194
- expect(refreshBtn.callback_data).toBe("auth:refresh:specific-agent");
195
- }
196
- });
197
- });
198
-
199
- describe("isQuotaHot — boundary conditions", () => {
200
- it("99% 5h does NOT trip hot (cold, auto-fallback at 99.5%)", () => {
201
- expect(isQuotaHot([slot({ fiveHourPct: 89 })])).toBe(false);
202
- });
203
-
204
- it("exactly QUOTA_HOT_THRESHOLD_PCT (90%) trips hot", () => {
205
- expect(isQuotaHot([slot({ fiveHourPct: QUOTA_HOT_THRESHOLD_PCT })])).toBe(true);
206
- expect(isQuotaHot([slot({ sevenDayPct: QUOTA_HOT_THRESHOLD_PCT })])).toBe(true);
207
- });
208
-
209
- it("one hot slot among many cold → hot", () => {
210
- expect(isQuotaHot([
211
- slot({ fiveHourPct: 10 }),
212
- slot({ fiveHourPct: 20 }),
213
- slot({ fiveHourPct: 95 }),
214
- slot({ fiveHourPct: 0 }),
215
- ])).toBe(true);
216
- });
217
-
218
- it("quota-exhausted always hot, even with 0% pct", () => {
219
- expect(isQuotaHot([slot({ health: "quota-exhausted", fiveHourPct: 0, sevenDayPct: 0 })])).toBe(true);
220
- });
221
-
222
- it("null utilization doesn't trip", () => {
223
- expect(isQuotaHot([slot({ fiveHourPct: null, sevenDayPct: null })])).toBe(false);
224
- });
225
- });
226
-
227
- describe("remove confirm keyboard — edge cases", () => {
228
- it("cancel button refreshes back to the dashboard (doesn't leave orphan)", () => {
229
- const kb = buildRemoveConfirmKeyboard("clerk", "personal");
230
- const cancelBtn = kb.inline_keyboard.flat().find((b) => b.text.includes("Cancel"));
231
- if (cancelBtn && "callback_data" in cancelBtn) {
232
- expect(cancelBtn.callback_data).toBe("auth:refresh:clerk");
233
- }
234
- });
235
-
236
- it("handles slot names with hyphens/underscores in the confirm label", () => {
237
- const kb = buildRemoveConfirmKeyboard("clerk", "backup_slot-v2");
238
- const confirmBtn = kb.inline_keyboard.flat().find((b) => b.text.startsWith("⚠️"));
239
- expect(confirmBtn?.text).toContain("backup_slot-v2");
240
- });
241
- });
242
-
243
- describe("encode/parse round-trip — lots of identities", () => {
244
- const actions = [
245
- { kind: "refresh", agent: "a" },
246
- { kind: "reauth", agent: "a" },
247
- { kind: "reauth", agent: "a", slot: "b" },
248
- { kind: "add", agent: "a" },
249
- { kind: "use", agent: "a", slot: "b" },
250
- { kind: "rm", agent: "a", slot: "b" },
251
- { kind: "confirm-rm", agent: "a", slot: "b" },
252
- { kind: "fallback", agent: "a" },
253
- { kind: "usage", agent: "a" },
254
- ] as const;
255
-
256
- it.each(actions)("round-trips %s", (action) => {
257
- const encoded = encodeCallbackData(action);
258
- expect(parseCallbackData(encoded)).toEqual(action);
259
- });
260
- });
@@ -1,140 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import {
3
- buildDashboardText,
4
- buildDashboardKeyboard,
5
- encodeCallbackData,
6
- parseCallbackData,
7
- type DashboardState,
8
- type DashboardSlot,
9
- } from "../auth-dashboard";
10
-
11
- function slot(o: Partial<DashboardSlot> = {}): DashboardSlot {
12
- return { slot: "default", active: false, health: "healthy", quotaExhaustedUntil: null, fiveHourPct: null, sevenDayPct: null, ...o };
13
- }
14
-
15
- /**
16
- * PR C — [♻️ Restart flow] dashboard button.
17
- *
18
- * Pairs with PR B (automatic stale-session detection). When the user
19
- * wants to explicitly kill + restart a pending auth flow without
20
- * waiting for the PKCE challenge to drift, the dashboard surfaces a
21
- * button keyed by the pending slot name. Gateway routes it to
22
- * cancel + re-initiate the matching reauth/add.
23
- */
24
-
25
- describe("restart-flow callback — encode / parse round-trip", () => {
26
- it("encodes as auth:restart-flow:<agent>:<slot>", () => {
27
- expect(encodeCallbackData({ kind: "restart-flow", agent: "clerk", slot: "default" }))
28
- .toBe("auth:restart-flow:clerk:default");
29
- expect(encodeCallbackData({ kind: "restart-flow", agent: "lawgpt", slot: "slot-2" }))
30
- .toBe("auth:restart-flow:lawgpt:slot-2");
31
- });
32
-
33
- it("round-trips cleanly", () => {
34
- for (const action of [
35
- { kind: "restart-flow", agent: "clerk", slot: "default" },
36
- { kind: "restart-flow", agent: "lawgpt", slot: "slot-2" },
37
- { kind: "restart-flow", agent: "klanker", slot: "personal_v2" },
38
- ] as const) {
39
- expect(parseCallbackData(encodeCallbackData(action))).toEqual(action);
40
- }
41
- });
42
-
43
- it("rejects restart-flow without a slot (slot is required)", () => {
44
- // The `use`, `rm`, `confirm-rm`, and `restart-flow` verbs all
45
- // require a slot. Missing slot → noop (doesn't accidentally fire
46
- // a wider-scope reset).
47
- expect(parseCallbackData("auth:restart-flow:clerk")).toEqual({ kind: "noop" });
48
- expect(parseCallbackData("auth:restart-flow:clerk:")).toEqual({ kind: "noop" });
49
- });
50
-
51
- it("rejects unsafe slot names as noop (shell-injection guard)", () => {
52
- expect(parseCallbackData("auth:restart-flow:clerk:../etc")).toEqual({ kind: "noop" });
53
- expect(parseCallbackData("auth:restart-flow:clerk:bad slot")).toEqual({ kind: "noop" });
54
- expect(parseCallbackData("auth:restart-flow:clerk:slot;ls")).toEqual({ kind: "noop" });
55
- });
56
-
57
- it("encoded payload fits Telegram's 64-byte callback_data cap", () => {
58
- // Longest practical: auth:restart-flow:<agent32>:<slot32> = 17 + 32 + 1 + 32 = 82.
59
- // Agent + slot each capped at 16 chars realistic → well under 64.
60
- const realistic = encodeCallbackData({
61
- kind: "restart-flow",
62
- agent: "a".repeat(16),
63
- slot: "b".repeat(16),
64
- });
65
- expect(realistic.length).toBeLessThanOrEqual(64);
66
- });
67
- });
68
-
69
- describe("dashboard renders [Restart flow] button when pending session exists", () => {
70
- const base: DashboardState = {
71
- agent: "lawgpt",
72
- bankId: "lawgpt",
73
- plan: "max",
74
- slots: [slot({ slot: "default", active: true })],
75
- quotaHot: false,
76
- };
77
-
78
- it("omits the button when pendingSessionSlot is absent", () => {
79
- const kb = buildDashboardKeyboard(base);
80
- const flat = kb.inline_keyboard.flat();
81
- expect(flat.some((b) => b.text.includes("Restart"))).toBe(false);
82
- });
83
-
84
- it("renders the button when pendingSessionSlot is set", () => {
85
- const kb = buildDashboardKeyboard({ ...base, pendingSessionSlot: "default" });
86
- const flat = kb.inline_keyboard.flat();
87
- const btn = flat.find((b) => b.text.includes("Restart"));
88
- expect(btn).toBeDefined();
89
- expect(btn!.text).toContain("default");
90
- if (btn && "callback_data" in btn && btn.callback_data) {
91
- expect(btn.callback_data).toBe("auth:restart-flow:lawgpt:default");
92
- }
93
- });
94
-
95
- it("includes slot name in the button label for named-slot pending flows", () => {
96
- const kb = buildDashboardKeyboard({ ...base, pendingSessionSlot: "slot-2" });
97
- const flat = kb.inline_keyboard.flat();
98
- const btn = flat.find((b) => b.text.includes("Restart"));
99
- expect(btn!.text).toContain("slot-2");
100
- if (btn && "callback_data" in btn && btn.callback_data) {
101
- expect(btn.callback_data).toBe("auth:restart-flow:lawgpt:slot-2");
102
- }
103
- });
104
-
105
- it("pendingSessionSlot=null is treated as 'no pending' (no button)", () => {
106
- const kb = buildDashboardKeyboard({ ...base, pendingSessionSlot: null });
107
- const flat = kb.inline_keyboard.flat();
108
- expect(flat.some((b) => b.text.includes("Restart"))).toBe(false);
109
- });
110
- });
111
-
112
- describe("dashboard text includes the pending-flow notice", () => {
113
- const base: DashboardState = {
114
- agent: "lawgpt",
115
- bankId: "lawgpt",
116
- plan: "max",
117
- slots: [slot({ active: true })],
118
- quotaHot: false,
119
- };
120
-
121
- it("no notice when no pending", () => {
122
- const text = buildDashboardText(base);
123
- expect(text).not.toContain("Auth flow pending");
124
- expect(text).not.toContain("⏳");
125
- });
126
-
127
- it("shows a pending-flow notice when pendingSessionSlot is set", () => {
128
- const text = buildDashboardText({ ...base, pendingSessionSlot: "slot-2" });
129
- expect(text).toContain("Auth flow pending");
130
- expect(text).toContain("slot-2");
131
- expect(text).toContain("⏳");
132
- expect(text).toContain("♻️");
133
- });
134
-
135
- it("escapes HTML in pendingSessionSlot to guard against injection", () => {
136
- const text = buildDashboardText({ ...base, pendingSessionSlot: "<script>alert(1)</script>" });
137
- expect(text).not.toContain("<script>");
138
- expect(text).toContain("&lt;script&gt;");
139
- });
140
- });