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,640 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import {
3
- assertSafeSlotName,
4
- parseAuthSubCommand,
5
- checkRemoveSafety,
6
- formatSlotList,
7
- splitFlags,
8
- usageText,
9
- AUTH_VERBS,
10
- type SlotListingFromCli,
11
- type AuthIntent,
12
- } from "../auth-slot-parser";
13
-
14
- describe("assertSafeSlotName", () => {
15
- it("accepts valid slot names", () => {
16
- for (const name of ["default", "personal-1", "work_acct", "a", "A".repeat(32)]) {
17
- expect(() => assertSafeSlotName(name)).not.toThrow();
18
- }
19
- });
20
- it("rejects empty string", () => {
21
- expect(() => assertSafeSlotName("")).toThrow(/invalid slot name/);
22
- });
23
- it("rejects names > 32 chars", () => {
24
- expect(() => assertSafeSlotName("a".repeat(33))).toThrow(/invalid slot name/);
25
- });
26
- it("rejects shell metacharacters and spaces", () => {
27
- for (const bad of ["foo bar", "foo;ls", "foo/bar", "foo.bar", "foo$var", "foo|bar"]) {
28
- expect(() => assertSafeSlotName(bad)).toThrow(/invalid slot name/);
29
- }
30
- });
31
- });
32
-
33
- describe("parseAuthSubCommand — existing verbs pass-through", () => {
34
- it("defaults to status with no args", () => {
35
- const intent = parseAuthSubCommand([], "clerk");
36
- expect(intent.kind).toBe("status");
37
- if (intent.kind === "status") expect(intent.cliArgs).toEqual(["auth", "status"]);
38
- });
39
-
40
- it("login with no agent defaults to currentAgent", () => {
41
- const intent = parseAuthSubCommand(["login"], "clerk");
42
- expect(intent.kind).toBe("login");
43
- if (intent.kind === "login") {
44
- expect(intent.agent).toBe("clerk");
45
- expect(intent.cliArgs).toEqual(["auth", "login", "clerk"]);
46
- expect(intent.registerReauth).toBe(true);
47
- }
48
- });
49
-
50
- it("login with explicit agent", () => {
51
- const intent = parseAuthSubCommand(["login", "klanker"], "clerk");
52
- expect(intent.kind).toBe("login");
53
- if (intent.kind === "login") expect(intent.agent).toBe("klanker");
54
- });
55
-
56
- it("code requires a browser code", () => {
57
- const intent = parseAuthSubCommand(["code"], "clerk");
58
- expect(intent.kind).toBe("usage");
59
- });
60
-
61
- it("code treats 2 args as agent-is-current + code", () => {
62
- const intent = parseAuthSubCommand(["code", "ABC123"], "clerk");
63
- expect(intent.kind).toBe("code");
64
- if (intent.kind === "code") {
65
- expect(intent.agent).toBe("clerk");
66
- expect(intent.code).toBe("ABC123");
67
- }
68
- });
69
-
70
- it("code treats 3 args as agent + code", () => {
71
- const intent = parseAuthSubCommand(["code", "klanker", "ABC123"], "clerk");
72
- expect(intent.kind).toBe("code");
73
- if (intent.kind === "code") {
74
- expect(intent.agent).toBe("klanker");
75
- expect(intent.code).toBe("ABC123");
76
- }
77
- });
78
- });
79
-
80
- describe("parseAuthSubCommand — /auth add", () => {
81
- it("defaults agent to currentAgent", () => {
82
- const intent = parseAuthSubCommand(["add"], "clerk");
83
- expect(intent.kind).toBe("add");
84
- if (intent.kind === "add") {
85
- expect(intent.agent).toBe("clerk");
86
- expect(intent.slot).toBeUndefined();
87
- expect(intent.cliArgs).toEqual(["auth", "add", "clerk"]);
88
- }
89
- });
90
-
91
- it("takes explicit agent", () => {
92
- const intent = parseAuthSubCommand(["add", "klanker"], "clerk");
93
- expect(intent.kind).toBe("add");
94
- if (intent.kind === "add") {
95
- expect(intent.agent).toBe("klanker");
96
- expect(intent.cliArgs).toEqual(["auth", "add", "klanker"]);
97
- }
98
- });
99
-
100
- it("accepts --slot value", () => {
101
- const intent = parseAuthSubCommand(["add", "clerk", "--slot", "personal"], "clerk");
102
- expect(intent.kind).toBe("add");
103
- if (intent.kind === "add") {
104
- expect(intent.slot).toBe("personal");
105
- expect(intent.cliArgs).toEqual(["auth", "add", "clerk", "--slot", "personal"]);
106
- }
107
- });
108
-
109
- it("rejects invalid slot name", () => {
110
- const intent = parseAuthSubCommand(["add", "clerk", "--slot", "bad name"], "clerk");
111
- expect(intent.kind).toBe("error");
112
- if (intent.kind === "error") expect(intent.message).toMatch(/slot name/i);
113
- });
114
- });
115
-
116
- describe("parseAuthSubCommand — /auth use", () => {
117
- it("requires a slot arg", () => {
118
- const intent = parseAuthSubCommand(["use"], "clerk");
119
- expect(intent.kind).toBe("usage");
120
- });
121
-
122
- it("1 positional arg = slot (agent defaults)", () => {
123
- const intent = parseAuthSubCommand(["use", "personal"], "clerk");
124
- expect(intent.kind).toBe("use");
125
- if (intent.kind === "use") {
126
- expect(intent.agent).toBe("clerk");
127
- expect(intent.slot).toBe("personal");
128
- expect(intent.force).toBe(false);
129
- expect(intent.cliArgs).toEqual(["auth", "use", "clerk", "personal"]);
130
- expect(intent.restartAgentAfter).toBe(true);
131
- }
132
- });
133
-
134
- it("2 positional args = agent + slot", () => {
135
- const intent = parseAuthSubCommand(["use", "klanker", "personal"], "clerk");
136
- expect(intent.kind).toBe("use");
137
- if (intent.kind === "use") {
138
- expect(intent.agent).toBe("klanker");
139
- expect(intent.slot).toBe("personal");
140
- expect(intent.force).toBe(false);
141
- }
142
- });
143
-
144
- it("--force sets force=true (#421)", () => {
145
- const intent = parseAuthSubCommand(["use", "personal", "--force"], "clerk");
146
- expect(intent.kind).toBe("use");
147
- if (intent.kind === "use") {
148
- expect(intent.slot).toBe("personal");
149
- expect(intent.force).toBe(true);
150
- }
151
- });
152
-
153
- it("--force with explicit agent + slot", () => {
154
- const intent = parseAuthSubCommand(["use", "klanker", "personal", "--force"], "clerk");
155
- expect(intent.kind).toBe("use");
156
- if (intent.kind === "use") {
157
- expect(intent.agent).toBe("klanker");
158
- expect(intent.slot).toBe("personal");
159
- expect(intent.force).toBe(true);
160
- }
161
- });
162
-
163
- it("rejects invalid slot name with clear error", () => {
164
- const intent = parseAuthSubCommand(["use", "bad slot"], "clerk");
165
- expect(intent.kind).toBe("error");
166
- if (intent.kind === "error") expect(intent.message).toMatch(/slot name/i);
167
- });
168
-
169
- it("rejects invalid agent name", () => {
170
- const intent = parseAuthSubCommand(["use", "bad;agent", "slot"], "clerk");
171
- expect(intent.kind).toBe("error");
172
- if (intent.kind === "error") expect(intent.message).toMatch(/agent name/i);
173
- });
174
- });
175
-
176
- describe("parseAuthSubCommand — /auth list", () => {
177
- it("defaults agent to currentAgent, requests JSON from CLI", () => {
178
- const intent = parseAuthSubCommand(["list"], "clerk");
179
- expect(intent.kind).toBe("list");
180
- if (intent.kind === "list") {
181
- expect(intent.agent).toBe("clerk");
182
- expect(intent.cliArgs).toEqual(["auth", "list", "clerk", "--json"]);
183
- }
184
- });
185
-
186
- it("takes explicit agent", () => {
187
- const intent = parseAuthSubCommand(["list", "klanker"], "clerk");
188
- expect(intent.kind).toBe("list");
189
- if (intent.kind === "list") expect(intent.agent).toBe("klanker");
190
- });
191
- });
192
-
193
- describe("parseAuthSubCommand — /auth rm", () => {
194
- it("requires a slot", () => {
195
- const intent = parseAuthSubCommand(["rm"], "clerk");
196
- expect(intent.kind).toBe("usage");
197
- });
198
-
199
- it("1 positional arg = slot, no --force", () => {
200
- const intent = parseAuthSubCommand(["rm", "personal"], "clerk");
201
- expect(intent.kind).toBe("rm");
202
- if (intent.kind === "rm") {
203
- expect(intent.agent).toBe("clerk");
204
- expect(intent.slot).toBe("personal");
205
- expect(intent.force).toBe(false);
206
- expect(intent.cliArgs).toEqual(["auth", "rm", "clerk", "personal"]);
207
- }
208
- });
209
-
210
- it("--force sets force=true", () => {
211
- const intent = parseAuthSubCommand(["rm", "personal", "--force"], "clerk");
212
- expect(intent.kind).toBe("rm");
213
- if (intent.kind === "rm") expect(intent.force).toBe(true);
214
- });
215
-
216
- it("2 positional args = agent + slot", () => {
217
- const intent = parseAuthSubCommand(["rm", "klanker", "personal"], "clerk");
218
- expect(intent.kind).toBe("rm");
219
- if (intent.kind === "rm") {
220
- expect(intent.agent).toBe("klanker");
221
- expect(intent.slot).toBe("personal");
222
- }
223
- });
224
-
225
- it("rejects invalid slot name", () => {
226
- const intent = parseAuthSubCommand(["rm", "bad slot"], "clerk");
227
- expect(intent.kind).toBe("error");
228
- });
229
- });
230
-
231
- describe("parseAuthSubCommand — unknown verb", () => {
232
- it("returns usage with full verb list", () => {
233
- const intent = parseAuthSubCommand(["foo"], "clerk");
234
- expect(intent.kind).toBe("usage");
235
- if (intent.kind === "usage") {
236
- // Primary verbs should appear in the usage text.
237
- // ("link" is an alias of "login" and not listed separately;
238
- // "status" is the bare /auth with no args.)
239
- const visibleVerbs = AUTH_VERBS.filter(v => v !== "link" && v !== "status");
240
- for (const v of visibleVerbs) {
241
- expect(intent.message).toContain(`/auth ${v}`);
242
- }
243
- // sanity: the 4 new verbs definitely appear
244
- expect(intent.message).toContain("/auth add");
245
- expect(intent.message).toContain("/auth use");
246
- expect(intent.message).toContain("/auth list");
247
- expect(intent.message).toContain("/auth rm");
248
- }
249
- });
250
- });
251
-
252
- describe("checkRemoveSafety", () => {
253
- const baseListing: SlotListingFromCli = {
254
- agent: "clerk",
255
- slots: [
256
- { slot: "default", active: true, health: "healthy", expires_at: null, quota_exhausted_until: null },
257
- { slot: "personal", active: false, health: "healthy", expires_at: null, quota_exhausted_until: null },
258
- ],
259
- };
260
-
261
- it("allows removing inactive slot without force", () => {
262
- expect(checkRemoveSafety(baseListing, "personal", false)).toBeNull();
263
- });
264
-
265
- it("blocks removing active slot without force", () => {
266
- const err = checkRemoveSafety(baseListing, "default", false);
267
- expect(err).toMatch(/active slot/i);
268
- expect(err).toMatch(/--force/);
269
- });
270
-
271
- it("blocks removing only slot without force", () => {
272
- const only: SlotListingFromCli = {
273
- agent: "clerk",
274
- slots: [{ slot: "default", active: true, health: "healthy", expires_at: null, quota_exhausted_until: null }],
275
- };
276
- const err = checkRemoveSafety(only, "default", false);
277
- expect(err).toMatch(/only account slot/i);
278
- });
279
-
280
- it("--force bypasses all safety checks", () => {
281
- expect(checkRemoveSafety(baseListing, "default", true)).toBeNull();
282
- });
283
-
284
- it("returns null for unknown slot (CLI will produce its own error)", () => {
285
- expect(checkRemoveSafety(baseListing, "ghost", false)).toBeNull();
286
- });
287
- });
288
-
289
- describe("formatSlotList", () => {
290
- const now = Date.now();
291
-
292
- it("shows active marker on active slot", () => {
293
- const out = formatSlotList({
294
- agent: "clerk",
295
- slots: [
296
- { slot: "default", active: true, health: "healthy", expires_at: null, quota_exhausted_until: null },
297
- { slot: "personal", active: false, health: "healthy", expires_at: null, quota_exhausted_until: null },
298
- ],
299
- });
300
- expect(out).toContain("<b>Slots for clerk</b>");
301
- expect(out).toContain("●"); // active marker
302
- expect(out).toContain("<code>default</code>");
303
- expect(out).toContain("<code>personal</code>");
304
- });
305
-
306
- it("shows 'no slots' message when empty", () => {
307
- const out = formatSlotList({ agent: "clerk", slots: [] });
308
- expect(out).toMatch(/no slots/i);
309
- expect(out).toContain("/auth add");
310
- });
311
-
312
- it("surfaces quota-exhausted with resets-in tail", () => {
313
- const until = now + 30 * 60_000;
314
- const out = formatSlotList({
315
- agent: "clerk",
316
- slots: [{ slot: "default", active: true, health: "quota-exhausted", expires_at: null, quota_exhausted_until: until }],
317
- });
318
- expect(out).toMatch(/resets in ~\d+m/);
319
- expect(out).toContain("⚠️");
320
- });
321
-
322
- it("surfaces expired with reauth hint", () => {
323
- const out = formatSlotList({
324
- agent: "clerk",
325
- slots: [{ slot: "default", active: true, health: "expired", expires_at: null, quota_exhausted_until: null }],
326
- });
327
- expect(out).toMatch(/\/auth reauth/);
328
- expect(out).toContain("⌛");
329
- });
330
-
331
- it("escapes HTML in agent name", () => {
332
- const out = formatSlotList({ agent: "<evil>", slots: [] });
333
- expect(out).toContain("&lt;evil&gt;");
334
- expect(out).not.toContain("<evil>");
335
- });
336
- });
337
-
338
- describe("splitFlags", () => {
339
- it("extracts value flag + leaves positional", () => {
340
- const { flags, positional } = splitFlags(["agent", "--slot", "personal"], ["--slot"]);
341
- expect(flags).toEqual({ "--slot": "personal" });
342
- expect(positional).toEqual(["agent"]);
343
- });
344
-
345
- it("treats value flag with no value as boolean", () => {
346
- const { flags, positional } = splitFlags(["--slot"], ["--slot"]);
347
- expect(flags["--slot"]).toBe(true);
348
- expect(positional).toEqual([]);
349
- });
350
-
351
- it("bare --flag becomes boolean", () => {
352
- const { flags, positional } = splitFlags(["slot", "--force"], []);
353
- expect(flags["--force"]).toBe(true);
354
- expect(positional).toEqual(["slot"]);
355
- });
356
- });
357
-
358
- describe("usageText", () => {
359
- it("lists all ten public sub-verbs", () => {
360
- const u = usageText();
361
- for (const v of ["login", "reauth", "code", "cancel", "add", "use", "list", "rm"]) {
362
- expect(u).toContain(`/auth ${v}`);
363
- }
364
- });
365
-
366
- it("lists the new account-shaped verbs", () => {
367
- const u = usageText();
368
- expect(u).toContain("/auth account add");
369
- expect(u).toContain("/auth account list");
370
- expect(u).toContain("/auth account rm");
371
- expect(u).toContain("/auth enable");
372
- expect(u).toContain("/auth disable");
373
- });
374
- });
375
-
376
- describe("parseAuthSubCommand — account-shaped verbs", () => {
377
- it("/auth account add <label> defaults --from-agent to currentAgent", () => {
378
- const intent = parseAuthSubCommand(["account", "add", "work-pro"], "clerk");
379
- expect(intent.kind).toBe("account-add");
380
- if (intent.kind === "account-add") {
381
- expect(intent.account).toBe("work-pro");
382
- expect(intent.fromAgent).toBe("clerk");
383
- expect(intent.cliArgs).toEqual(["auth", "account", "add", "work-pro", "--from-agent", "clerk"]);
384
- }
385
- });
386
-
387
- it("/auth account add <label> --from-agent <name> uses explicit agent", () => {
388
- const intent = parseAuthSubCommand(
389
- ["account", "add", "work-pro", "--from-agent", "klanker"],
390
- "clerk",
391
- );
392
- expect(intent.kind).toBe("account-add");
393
- if (intent.kind === "account-add") {
394
- expect(intent.fromAgent).toBe("klanker");
395
- expect(intent.cliArgs).toContain("--from-agent");
396
- expect(intent.cliArgs).toContain("klanker");
397
- }
398
- });
399
-
400
- it("/auth account add without label is a usage error", () => {
401
- const intent = parseAuthSubCommand(["account", "add"], "clerk");
402
- expect(intent.kind).toBe("usage");
403
- });
404
-
405
- it("/auth account add rejects invalid labels", () => {
406
- const intent = parseAuthSubCommand(["account", "add", "../etc"], "clerk");
407
- expect(intent.kind).toBe("error");
408
- });
409
-
410
- it("/auth account add accepts dotted account labels (email-style)", () => {
411
- const intent = parseAuthSubCommand(
412
- ["account", "add", "ken.example.com"],
413
- "clerk",
414
- );
415
- expect(intent.kind).toBe("account-add");
416
- });
417
-
418
- it("/auth account add rejects --from-agent with shell metacharacters", () => {
419
- const intent = parseAuthSubCommand(
420
- ["account", "add", "work", "--from-agent", "foo;ls"],
421
- "clerk",
422
- );
423
- expect(intent.kind).toBe("error");
424
- });
425
-
426
- it("/auth account list maps to the CLI list verb", () => {
427
- const intent = parseAuthSubCommand(["account", "list"], "clerk");
428
- expect(intent.kind).toBe("account-list");
429
- if (intent.kind === "account-list") {
430
- expect(intent.cliArgs).toEqual(["auth", "account", "list"]);
431
- }
432
- });
433
-
434
- it("/auth account rm <label>", () => {
435
- const intent = parseAuthSubCommand(["account", "rm", "old-account"], "clerk");
436
- expect(intent.kind).toBe("account-rm");
437
- if (intent.kind === "account-rm") {
438
- expect(intent.account).toBe("old-account");
439
- expect(intent.cliArgs).toEqual(["auth", "account", "rm", "old-account"]);
440
- }
441
- });
442
-
443
- it("/auth account rm without label is a usage error", () => {
444
- const intent = parseAuthSubCommand(["account", "rm"], "clerk");
445
- expect(intent.kind).toBe("usage");
446
- });
447
-
448
- it("/auth account with no subverb defaults to list", () => {
449
- const intent = parseAuthSubCommand(["account"], "clerk");
450
- expect(intent.kind).toBe("account-list");
451
- });
452
-
453
- it("/auth account rename <old> <new> produces account-rename intent with both labels", () => {
454
- const intent = parseAuthSubCommand(
455
- ["account", "rename", "ken-pro", "work-pro"],
456
- "clerk",
457
- );
458
- expect(intent.kind).toBe("account-rename");
459
- if (intent.kind === "account-rename") {
460
- expect(intent.oldAccount).toBe("ken-pro");
461
- expect(intent.newAccount).toBe("work-pro");
462
- expect(intent.cliArgs).toEqual([
463
- "auth",
464
- "account",
465
- "rename",
466
- "ken-pro",
467
- "work-pro",
468
- ]);
469
- }
470
- });
471
-
472
- it("/auth account rename without both labels is a usage error", () => {
473
- expect(parseAuthSubCommand(["account", "rename"], "clerk").kind).toBe("usage");
474
- expect(parseAuthSubCommand(["account", "rename", "only-one"], "clerk").kind).toBe(
475
- "usage",
476
- );
477
- });
478
-
479
- it("/auth account rename rejects same-name no-op as an error (not silent)", () => {
480
- const intent = parseAuthSubCommand(
481
- ["account", "rename", "same", "same"],
482
- "clerk",
483
- );
484
- expect(intent.kind).toBe("error");
485
- });
486
-
487
- it("/auth account rename rejects invalid labels", () => {
488
- expect(
489
- parseAuthSubCommand(["account", "rename", "../etc", "ok"], "clerk").kind,
490
- ).toBe("error");
491
- expect(
492
- parseAuthSubCommand(["account", "rename", "ok", "foo bar"], "clerk").kind,
493
- ).toBe("error");
494
- });
495
- });
496
-
497
- describe("parseAuthSubCommand — enable / disable", () => {
498
- it("/auth enable <label> defaults agents to currentAgent", () => {
499
- const intent = parseAuthSubCommand(["enable", "work-pro"], "clerk");
500
- expect(intent.kind).toBe("enable");
501
- if (intent.kind === "enable") {
502
- expect(intent.account).toBe("work-pro");
503
- expect(intent.agents).toEqual(["clerk"]);
504
- expect(intent.cliArgs).toEqual(["auth", "enable", "work-pro", "clerk"]);
505
- expect(intent.restartAgentsAfter).toBe(true);
506
- }
507
- });
508
-
509
- it("/auth enable <label> <a> <b> wires multiple agents", () => {
510
- const intent = parseAuthSubCommand(
511
- ["enable", "work-pro", "foo", "bar"],
512
- "clerk",
513
- );
514
- expect(intent.kind).toBe("enable");
515
- if (intent.kind === "enable") {
516
- expect(intent.agents).toEqual(["foo", "bar"]);
517
- expect(intent.cliArgs).toEqual(["auth", "enable", "work-pro", "foo", "bar"]);
518
- }
519
- });
520
-
521
- it("/auth enable without label is a usage error", () => {
522
- const intent = parseAuthSubCommand(["enable"], "clerk");
523
- expect(intent.kind).toBe("usage");
524
- });
525
-
526
- it("/auth enable rejects invalid agent names", () => {
527
- const intent = parseAuthSubCommand(
528
- ["enable", "work-pro", "foo;ls"],
529
- "clerk",
530
- );
531
- expect(intent.kind).toBe("error");
532
- });
533
-
534
- it("/auth disable <label> mirrors enable shape", () => {
535
- const intent = parseAuthSubCommand(["disable", "work-pro"], "clerk");
536
- expect(intent.kind).toBe("disable");
537
- if (intent.kind === "disable") {
538
- expect(intent.account).toBe("work-pro");
539
- expect(intent.agents).toEqual(["clerk"]);
540
- expect(intent.cliArgs).toEqual(["auth", "disable", "work-pro", "clerk"]);
541
- }
542
- });
543
-
544
- it("/auth disable <label> <a> <b> for explicit agents", () => {
545
- const intent = parseAuthSubCommand(
546
- ["disable", "work-pro", "foo", "bar"],
547
- "clerk",
548
- );
549
- expect(intent.kind).toBe("disable");
550
- if (intent.kind === "disable") {
551
- expect(intent.agents).toEqual(["foo", "bar"]);
552
- }
553
- });
554
- });
555
-
556
- describe("AUTH_VERBS includes the new account-shaped verbs", () => {
557
- it("exports account / enable / disable in the verb list", () => {
558
- expect(AUTH_VERBS).toContain("account");
559
- expect(AUTH_VERBS).toContain("enable");
560
- expect(AUTH_VERBS).toContain("disable");
561
- });
562
-
563
- it("exports share in the verb list", () => {
564
- expect(AUTH_VERBS).toContain("share");
565
- });
566
- });
567
-
568
- describe("parseAuthSubCommand — `all` keyword for enable/disable", () => {
569
- it("/auth enable <label> all parses with agents=['all']", () => {
570
- const intent = parseAuthSubCommand(["enable", "work-pro", "all"], "clerk");
571
- expect(intent.kind).toBe("enable");
572
- if (intent.kind === "enable") {
573
- expect(intent.agents).toEqual(["all"]);
574
- expect(intent.cliArgs).toEqual(["auth", "enable", "work-pro", "all"]);
575
- expect(intent.restartAgentsAfter).toBe(true);
576
- }
577
- });
578
-
579
- it("/auth disable <label> all is symmetric", () => {
580
- const intent = parseAuthSubCommand(["disable", "work-pro", "all"], "clerk");
581
- expect(intent.kind).toBe("disable");
582
- if (intent.kind === "disable") {
583
- expect(intent.agents).toEqual(["all"]);
584
- expect(intent.cliArgs).toEqual(["auth", "disable", "work-pro", "all"]);
585
- }
586
- });
587
- });
588
-
589
- describe("parseAuthSubCommand — /auth share", () => {
590
- it("/auth share <label> defaults --from-agent to currentAgent", () => {
591
- const intent = parseAuthSubCommand(["share", "work-pro"], "clerk");
592
- expect(intent.kind).toBe("share");
593
- if (intent.kind === "share") {
594
- expect(intent.account).toBe("work-pro");
595
- expect(intent.fromAgent).toBe("clerk");
596
- expect(intent.cliArgs).toEqual(["auth", "share", "work-pro", "--from-agent", "clerk"]);
597
- expect(intent.restartAgentsAfter).toBe(true);
598
- }
599
- });
600
-
601
- it("/auth share <label> --from-agent <name> honors explicit value", () => {
602
- const intent = parseAuthSubCommand(
603
- ["share", "work-pro", "--from-agent", "klanker"],
604
- "clerk",
605
- );
606
- expect(intent.kind).toBe("share");
607
- if (intent.kind === "share") {
608
- expect(intent.fromAgent).toBe("klanker");
609
- expect(intent.cliArgs).toEqual([
610
- "auth", "share", "work-pro", "--from-agent", "klanker",
611
- ]);
612
- }
613
- });
614
-
615
- it("/auth share without label is a usage error", () => {
616
- const intent = parseAuthSubCommand(["share"], "clerk");
617
- expect(intent.kind).toBe("usage");
618
- });
619
-
620
- it("/auth share rejects invalid labels", () => {
621
- const intent = parseAuthSubCommand(["share", "../etc"], "clerk");
622
- expect(intent.kind).toBe("error");
623
- });
624
-
625
- it("/auth share rejects --from-agent with shell metacharacters", () => {
626
- const intent = parseAuthSubCommand(
627
- ["share", "work-pro", "--from-agent", "foo;ls"],
628
- "clerk",
629
- );
630
- expect(intent.kind).toBe("error");
631
- });
632
- });
633
-
634
- describe("usageText — `all` keyword and share verb", () => {
635
- it("documents the `all` keyword on enable/disable and `/auth share`", () => {
636
- const u = usageText();
637
- expect(u).toContain("/auth share");
638
- expect(u).toMatch(/all/);
639
- });
640
- });