switchroom 0.8.1 → 0.11.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 (137) hide show
  1. package/README.md +54 -61
  2. package/bin/timezone-hook.sh +9 -7
  3. package/dist/agent-scheduler/index.js +285 -45
  4. package/dist/auth-broker/index.js +13932 -0
  5. package/dist/cli/drive-write-pretool.mjs +5418 -0
  6. package/dist/cli/switchroom.js +8890 -5560
  7. package/dist/host-control/main.js +582 -43
  8. package/dist/vault/approvals/kernel-server.js +276 -47
  9. package/dist/vault/broker/server.js +333 -69
  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 +6 -4
  16. package/profiles/_base/start.sh.hbs +3 -3
  17. package/profiles/_shared/agent-self-service.md.hbs +126 -0
  18. package/profiles/default/CLAUDE.md +10 -0
  19. package/profiles/default/CLAUDE.md.hbs +16 -0
  20. package/skills/buildkite-agent-infrastructure/SKILL.md +30 -11
  21. package/skills/buildkite-agent-runtime/SKILL.md +44 -11
  22. package/skills/buildkite-api/SKILL.md +31 -8
  23. package/skills/buildkite-cli/SKILL.md +27 -9
  24. package/skills/buildkite-migration/SKILL.md +22 -9
  25. package/skills/buildkite-pipelines/SKILL.md +26 -9
  26. package/skills/buildkite-secure-delivery/SKILL.md +23 -9
  27. package/skills/buildkite-test-engine/SKILL.md +25 -8
  28. package/skills/docx/SKILL.md +1 -1
  29. package/skills/file-bug/SKILL.md +34 -6
  30. package/skills/humanizer/SKILL.md +15 -0
  31. package/skills/humanizer-calibrate/SKILL.md +7 -1
  32. package/skills/mcp-builder/SKILL.md +1 -1
  33. package/skills/pdf/SKILL.md +1 -1
  34. package/skills/pptx/SKILL.md +1 -1
  35. package/skills/skill-creator/SKILL.md +21 -1
  36. package/skills/skill-creator/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  37. package/skills/skill-creator/scripts/__pycache__/generate_report.cpython-313.pyc +0 -0
  38. package/skills/skill-creator/scripts/__pycache__/improve_description.cpython-313.pyc +0 -0
  39. package/skills/skill-creator/scripts/__pycache__/run_eval.cpython-313.pyc +0 -0
  40. package/skills/skill-creator/scripts/__pycache__/run_loop.cpython-313.pyc +0 -0
  41. package/skills/skill-creator/scripts/__pycache__/utils.cpython-313.pyc +0 -0
  42. package/skills/switchroom-cli/SKILL.md +63 -64
  43. package/skills/switchroom-health/SKILL.md +23 -10
  44. package/skills/switchroom-install/SKILL.md +3 -3
  45. package/skills/switchroom-manage/SKILL.md +26 -19
  46. package/skills/switchroom-runtime/SKILL.md +67 -15
  47. package/skills/switchroom-status/SKILL.md +26 -1
  48. package/skills/telegram-test-harness/SKILL.md +3 -0
  49. package/skills/webapp-testing/SKILL.md +31 -1
  50. package/skills/xlsx/SKILL.md +1 -1
  51. package/telegram-plugin/admin-commands/dispatch.test.ts +1 -1
  52. package/telegram-plugin/admin-commands/index.ts +9 -5
  53. package/telegram-plugin/auth-snapshot-format.ts +612 -0
  54. package/telegram-plugin/auto-fallback-fleet.ts +215 -0
  55. package/telegram-plugin/auto-fallback.ts +28 -301
  56. package/telegram-plugin/dist/gateway/gateway.js +17453 -15100
  57. package/telegram-plugin/fleet-fallback-gate.ts +105 -0
  58. package/telegram-plugin/gateway/approval-callback.test.ts +104 -0
  59. package/telegram-plugin/gateway/approval-callback.ts +31 -3
  60. package/telegram-plugin/gateway/auth-add-flow.ts +326 -0
  61. package/telegram-plugin/gateway/auth-broker-client.ts +75 -0
  62. package/telegram-plugin/gateway/auth-command.ts +905 -0
  63. package/telegram-plugin/gateway/auth-line.ts +123 -0
  64. package/telegram-plugin/gateway/auth-status-adapter.ts +101 -0
  65. package/telegram-plugin/gateway/boot-card.ts +23 -37
  66. package/telegram-plugin/gateway/boot-probes.ts +9 -12
  67. package/telegram-plugin/gateway/diff-preview-card.test.ts +192 -0
  68. package/telegram-plugin/gateway/diff-preview-card.ts +170 -0
  69. package/telegram-plugin/gateway/drive-write-approval.test.ts +312 -0
  70. package/telegram-plugin/gateway/drive-write-approval.ts +243 -0
  71. package/telegram-plugin/gateway/folder-picker-handler.test.ts +314 -0
  72. package/telegram-plugin/gateway/folder-picker-handler.ts +348 -0
  73. package/telegram-plugin/gateway/gateway.ts +1156 -938
  74. package/telegram-plugin/gateway/hostd-dispatch.ts +244 -0
  75. package/telegram-plugin/gateway/ipc-protocol.ts +83 -2
  76. package/telegram-plugin/gateway/ipc-server.ts +69 -0
  77. package/telegram-plugin/hooks/sandbox-hint-posttool.mjs +103 -12
  78. package/telegram-plugin/hooks/tool-label-pretool.mjs +11 -0
  79. package/telegram-plugin/hooks/wedge-detect-posttool.mjs +303 -0
  80. package/telegram-plugin/model-unavailable.ts +28 -12
  81. package/telegram-plugin/permission-title.ts +56 -0
  82. package/telegram-plugin/quota-check.ts +19 -41
  83. package/telegram-plugin/scripts/build.mjs +0 -1
  84. package/telegram-plugin/shared/bot-runtime.ts +5 -4
  85. package/telegram-plugin/silence-poke.ts +153 -1
  86. package/telegram-plugin/tests/auth-add-flow.test.ts +559 -0
  87. package/telegram-plugin/tests/auth-code-redact.test.ts +8 -4
  88. package/telegram-plugin/tests/auth-command-format2.test.ts +156 -0
  89. package/telegram-plugin/tests/auth-command-vernacular.test.ts +531 -0
  90. package/telegram-plugin/tests/auth-snapshot-format.test.ts +429 -0
  91. package/telegram-plugin/tests/auth-status-adapter.test.ts +129 -0
  92. package/telegram-plugin/tests/auto-fallback-fleet.test.ts +211 -0
  93. package/telegram-plugin/tests/auto-fallback.test.ts +60 -358
  94. package/telegram-plugin/tests/boot-probes.test.ts +27 -22
  95. package/telegram-plugin/tests/fleet-fallback-gate.test.ts +197 -0
  96. package/telegram-plugin/tests/model-unavailable.test.ts +30 -5
  97. package/telegram-plugin/tests/permission-title.test.ts +31 -0
  98. package/telegram-plugin/tests/quota-check.test.ts +5 -35
  99. package/telegram-plugin/tests/sandbox-hint-posttool.test.ts +212 -2
  100. package/telegram-plugin/tests/silence-poke.test.ts +237 -0
  101. package/telegram-plugin/tests/turn-flush-safety.test.ts +112 -0
  102. package/telegram-plugin/turn-flush-safety.ts +55 -1
  103. package/telegram-plugin/uat/SETUP.md +35 -1
  104. package/telegram-plugin/uat/runners/agent-self-sufficiency.ts +457 -0
  105. package/telegram-plugin/uat/runners/paraphrases.ts +231 -0
  106. package/telegram-plugin/uat/runners/report.ts +150 -0
  107. package/telegram-plugin/uat/runners/run-agent-self-sufficiency.sh +50 -0
  108. package/telegram-plugin/uat/runners/scorer.test.ts +196 -0
  109. package/telegram-plugin/uat/runners/scorer.ts +106 -0
  110. package/telegram-plugin/uat/runners/skill-coverage.test.ts +100 -0
  111. package/telegram-plugin/uat/runners/skill-coverage.ts +620 -0
  112. package/telegram-plugin/uat/scenarios/jtbd-interrupt-marker-dm.test.ts +7 -1
  113. package/telegram-plugin/uat/scenarios/jtbd-rapid-followup-dm.test.ts +7 -1
  114. package/telegram-plugin/auth-dashboard.ts +0 -1104
  115. package/telegram-plugin/auth-slot-parser.ts +0 -497
  116. package/telegram-plugin/auto-fallback-dispatcher.ts +0 -68
  117. package/telegram-plugin/dist/foreman/foreman.js +0 -31358
  118. package/telegram-plugin/foreman/foreman-create-flow.ts +0 -202
  119. package/telegram-plugin/foreman/foreman-handlers.ts +0 -493
  120. package/telegram-plugin/foreman/foreman.ts +0 -1165
  121. package/telegram-plugin/foreman/setup-flow.ts +0 -345
  122. package/telegram-plugin/foreman/setup-state.ts +0 -239
  123. package/telegram-plugin/foreman/state.ts +0 -203
  124. package/telegram-plugin/tests/auth-account-identity-surface.test.ts +0 -118
  125. package/telegram-plugin/tests/auth-dashboard-edge-cases.test.ts +0 -260
  126. package/telegram-plugin/tests/auth-dashboard-restart-flow.test.ts +0 -140
  127. package/telegram-plugin/tests/auth-dashboard-v3b.test.ts +0 -559
  128. package/telegram-plugin/tests/auth-dashboard.test.ts +0 -1045
  129. package/telegram-plugin/tests/auth-slot-commands.test.ts +0 -640
  130. package/telegram-plugin/tests/auto-fallback-dispatcher.e2e.test.ts +0 -183
  131. package/telegram-plugin/tests/boot-card-account-quota.test.ts +0 -137
  132. package/telegram-plugin/tests/foreman-create-flow.test.ts +0 -359
  133. package/telegram-plugin/tests/foreman-handlers.test.ts +0 -347
  134. package/telegram-plugin/tests/foreman-state.test.ts +0 -164
  135. package/telegram-plugin/tests/foreman-write-ops.test.ts +0 -214
  136. package/telegram-plugin/tests/setup-flow.test.ts +0 -510
  137. package/telegram-plugin/tests/setup-state.test.ts +0 -146
@@ -1,214 +0,0 @@
1
- /**
2
- * Tests for Phase 3b foreman write operations:
3
- * - handleRestartCommand (mocked execFileSync)
4
- * - handleDeleteCommand + executeDeleteAgent (mocked exec + FS)
5
- * - handleUpdateCommand (mocked combined exec)
6
- */
7
-
8
- import { describe, it, expect, vi, beforeEach } from 'vitest'
9
- import {
10
- handleRestartCommand,
11
- handleDeleteCommand,
12
- executeDeleteAgent,
13
- handleUpdateCommand,
14
- type SwitchroomExecFn,
15
- } from '../foreman/foreman-handlers.js'
16
- import type { execFileSync } from 'child_process'
17
-
18
- // ─── /restart ─────────────────────────────────────────────────────────────
19
-
20
- describe('foreman: handleRestartCommand — input validation', () => {
21
- it('returns usage when no agent specified', () => {
22
- const result = handleRestartCommand('')
23
- expect(result.ok).toBe(false)
24
- expect(result.text).toContain('Usage')
25
- })
26
-
27
- it('rejects invalid agent name', () => {
28
- const execFile = vi.fn()
29
- // 'BadName' has uppercase — first token is invalid
30
- const result = handleRestartCommand('BadName', execFile as never)
31
- expect(result.ok).toBe(false)
32
- expect(result.text).toBe('Invalid agent name.')
33
- expect(execFile).not.toHaveBeenCalled()
34
- })
35
-
36
- it('rejects path traversal', () => {
37
- const execFile = vi.fn()
38
- const result = handleRestartCommand('../etc/passwd', execFile as never)
39
- expect(result.ok).toBe(false)
40
- expect(execFile).not.toHaveBeenCalled()
41
- })
42
-
43
- it('rejects agent name with colon', () => {
44
- const execFile = vi.fn()
45
- const result = handleRestartCommand('bad:name', execFile as never)
46
- expect(result.ok).toBe(false)
47
- expect(execFile).not.toHaveBeenCalled()
48
- })
49
- })
50
-
51
- describe('foreman: handleRestartCommand — execFileSync calls', () => {
52
- it('calls systemctl --user restart with correct unit name', () => {
53
- const execFile = vi.fn().mockReturnValue('')
54
- const result = handleRestartCommand('gymbro', execFile as never)
55
-
56
- expect(result.ok).toBe(true)
57
- expect(execFile).toHaveBeenCalledOnce()
58
- const [cmd, args] = execFile.mock.calls[0] as [string, string[]]
59
- expect(cmd).toBe('systemctl')
60
- expect(args).toContain('--user')
61
- expect(args).toContain('restart')
62
- expect(args).toContain('switchroom-gymbro')
63
- // Must NOT be a shell string — second arg must be an array
64
- expect(Array.isArray(args)).toBe(true)
65
- })
66
-
67
- it('uses execFileSync not execSync (no shell)', () => {
68
- const execFile = vi.fn().mockReturnValue('')
69
- handleRestartCommand('gymbro', execFile as never)
70
- // The function is called with 3 args (cmd, args[], opts) not a shell string
71
- const [, args] = execFile.mock.calls[0] as [string, string[], object]
72
- expect(Array.isArray(args)).toBe(true)
73
- })
74
-
75
- it('includes agent name in success reply', () => {
76
- const execFile = vi.fn().mockReturnValue('')
77
- const result = handleRestartCommand('gymbro', execFile as never)
78
- expect(result.ok).toBe(true)
79
- expect(result.text).toContain('gymbro')
80
- })
81
-
82
- it('returns error text when systemctl fails', () => {
83
- const execFile = vi.fn().mockImplementation(() => {
84
- throw Object.assign(new Error('unit not found'), { stderr: 'Unit switchroom-gymbro.service not found.' })
85
- })
86
- const result = handleRestartCommand('gymbro', execFile as never)
87
- expect(result.ok).toBe(false)
88
- expect(result.text).toContain('restart failed')
89
- expect(result.text).toContain('gymbro')
90
- })
91
-
92
- it('handles agent name with hyphen', () => {
93
- const execFile = vi.fn().mockReturnValue('')
94
- handleRestartCommand('my-agent', execFile as never)
95
- const [, args] = execFile.mock.calls[0] as [string, string[]]
96
- expect(args).toContain('switchroom-my-agent')
97
- })
98
-
99
- it('escapes HTML in error output', () => {
100
- const execFile = vi.fn().mockImplementation(() => {
101
- throw Object.assign(new Error('err'), { stderr: 'Error: <unit> not found' })
102
- })
103
- const result = handleRestartCommand('gymbro', execFile as never)
104
- expect(result.text).not.toContain('<unit>')
105
- })
106
- })
107
-
108
- // ─── /delete first step ───────────────────────────────────────────────────
109
-
110
- describe('foreman: handleDeleteCommand — prompt', () => {
111
- it('returns usage when no agent specified', () => {
112
- const result = handleDeleteCommand('')
113
- expect(result.replies[0].text).toContain('Usage')
114
- expect(result.needsConfirm).toBeFalsy()
115
- })
116
-
117
- it('rejects invalid agent name', () => {
118
- // 'BadAgent' has uppercase — invalid
119
- const result = handleDeleteCommand('BadAgent')
120
- expect(result.replies[0].text).toBe('Invalid agent name.')
121
- expect(result.needsConfirm).toBeFalsy()
122
- })
123
-
124
- it('returns confirmation prompt for valid agent', () => {
125
- const result = handleDeleteCommand('gymbro')
126
- expect(result.replies[0].text).toContain('gymbro')
127
- expect(result.replies[0].text).toContain('YES')
128
- expect(result.needsConfirm).toBe(true)
129
- expect(result.agentForConfirm).toBe('gymbro')
130
- })
131
-
132
- it('escapes HTML in agent name in prompt', () => {
133
- // valid name, but let's use one that could trip HTML — actually all valid names
134
- // are alphanumeric so no HTML risk; just verify the name appears
135
- const result = handleDeleteCommand('my-agent')
136
- expect(result.replies[0].text).toContain('my-agent')
137
- expect(result.replies[0].html).toBe(true)
138
- })
139
- })
140
-
141
- // ─── executeDeleteAgent ───────────────────────────────────────────────────
142
-
143
- describe('foreman: executeDeleteAgent — execution', () => {
144
- it('runs CLI destroy with --yes flag', () => {
145
- const switchroomExec: SwitchroomExecFn = vi.fn().mockReturnValue('Removed unit.')
146
- const execFile = vi.fn().mockReturnValue('')
147
- const tmpDir = '/tmp/fake-agents-dir'
148
-
149
- const result = executeDeleteAgent('gymbro', switchroomExec, execFile as never, tmpDir)
150
- expect(switchroomExec).toHaveBeenCalledWith(['agent', 'destroy', '--yes', 'gymbro'])
151
- expect(result.replies[0].text).toContain('gymbro')
152
- })
153
-
154
- it('reports success without archive when dir does not exist', () => {
155
- const switchroomExec: SwitchroomExecFn = vi.fn().mockReturnValue('done')
156
- const result = executeDeleteAgent('gymbro', switchroomExec, vi.fn() as never, '/nonexistent-agents-dir')
157
- // No archive reported (dir didn't exist)
158
- expect(result.replies[0].text).not.toContain('Archived')
159
- })
160
-
161
- it('rejects invalid agent name without calling CLI', () => {
162
- const switchroomExec: SwitchroomExecFn = vi.fn()
163
- const result = executeDeleteAgent('bad name!', switchroomExec, vi.fn() as never, '/tmp')
164
- expect(result.replies[0].text).toBe('Invalid agent name.')
165
- expect(switchroomExec).not.toHaveBeenCalled()
166
- })
167
-
168
- it('reports CLI error but notes archive succeeded', () => {
169
- const switchroomExec: SwitchroomExecFn = vi.fn().mockImplementation(() => {
170
- throw Object.assign(new Error('destroy failed'), { stderr: 'switchroom error' })
171
- })
172
- // Use a dir that definitely doesn't exist so no actual rename
173
- const result = executeDeleteAgent('gymbro', switchroomExec, vi.fn() as never, '/nonexistent-agents-12345')
174
- expect(result.replies[0].text).toContain('CLI destroy failed')
175
- })
176
- })
177
-
178
- // ─── /update ──────────────────────────────────────────────────────────────
179
-
180
- describe('foreman: handleUpdateCommand', () => {
181
- it('calls switchroomExec with ["update"]', () => {
182
- const exec: SwitchroomExecFn = vi.fn().mockReturnValue('Updated successfully.')
183
- handleUpdateCommand(exec)
184
- expect(exec).toHaveBeenCalledWith(['update'])
185
- })
186
-
187
- it('returns output in a pre block', () => {
188
- const exec: SwitchroomExecFn = vi.fn().mockReturnValue('Pulled abc123. Reconciled 2 agents.')
189
- const result = handleUpdateCommand(exec)
190
- expect(result.replies[0].text).toContain('Pulled abc123')
191
- expect(result.replies[0].html).toBe(true)
192
- })
193
-
194
- it('returns error message when CLI throws', () => {
195
- const exec: SwitchroomExecFn = vi.fn().mockImplementation(() => {
196
- throw Object.assign(new Error('not a git repo'), { stderr: 'fatal: not a git repository' })
197
- })
198
- const result = handleUpdateCommand(exec)
199
- expect(result.replies[0].text).toContain('update failed')
200
- })
201
-
202
- it('returns no-output message when CLI returns empty', () => {
203
- const exec: SwitchroomExecFn = vi.fn().mockReturnValue(' \n')
204
- const result = handleUpdateCommand(exec)
205
- expect(result.replies[0].text).toContain('Update complete')
206
- })
207
-
208
- it('paginates output > 3 KB', () => {
209
- const bigOutput = 'x'.repeat(4000)
210
- const exec: SwitchroomExecFn = vi.fn().mockReturnValue(bigOutput)
211
- const result = handleUpdateCommand(exec)
212
- expect(result.replies.length).toBeGreaterThan(1)
213
- })
214
- })