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.
- package/README.md +54 -61
- package/bin/timezone-hook.sh +9 -7
- package/dist/agent-scheduler/index.js +285 -45
- package/dist/auth-broker/index.js +13932 -0
- package/dist/cli/drive-write-pretool.mjs +5418 -0
- package/dist/cli/switchroom.js +8890 -5560
- package/dist/host-control/main.js +582 -43
- package/dist/vault/approvals/kernel-server.js +276 -47
- package/dist/vault/broker/server.js +333 -69
- package/examples/minimal.yaml +63 -0
- package/examples/personal-google-workspace-mcp/.env.example +34 -0
- package/examples/personal-google-workspace-mcp/README.md +194 -0
- package/examples/personal-google-workspace-mcp/compose.yaml +66 -0
- package/examples/switchroom.yaml +220 -0
- package/package.json +6 -4
- package/profiles/_base/start.sh.hbs +3 -3
- package/profiles/_shared/agent-self-service.md.hbs +126 -0
- package/profiles/default/CLAUDE.md +10 -0
- package/profiles/default/CLAUDE.md.hbs +16 -0
- package/skills/buildkite-agent-infrastructure/SKILL.md +30 -11
- package/skills/buildkite-agent-runtime/SKILL.md +44 -11
- package/skills/buildkite-api/SKILL.md +31 -8
- package/skills/buildkite-cli/SKILL.md +27 -9
- package/skills/buildkite-migration/SKILL.md +22 -9
- package/skills/buildkite-pipelines/SKILL.md +26 -9
- package/skills/buildkite-secure-delivery/SKILL.md +23 -9
- package/skills/buildkite-test-engine/SKILL.md +25 -8
- package/skills/docx/SKILL.md +1 -1
- package/skills/file-bug/SKILL.md +34 -6
- package/skills/humanizer/SKILL.md +15 -0
- package/skills/humanizer-calibrate/SKILL.md +7 -1
- package/skills/mcp-builder/SKILL.md +1 -1
- package/skills/pdf/SKILL.md +1 -1
- package/skills/pptx/SKILL.md +1 -1
- package/skills/skill-creator/SKILL.md +21 -1
- package/skills/skill-creator/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/generate_report.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/improve_description.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_eval.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_loop.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/utils.cpython-313.pyc +0 -0
- package/skills/switchroom-cli/SKILL.md +63 -64
- package/skills/switchroom-health/SKILL.md +23 -10
- package/skills/switchroom-install/SKILL.md +3 -3
- package/skills/switchroom-manage/SKILL.md +26 -19
- package/skills/switchroom-runtime/SKILL.md +67 -15
- package/skills/switchroom-status/SKILL.md +26 -1
- package/skills/telegram-test-harness/SKILL.md +3 -0
- package/skills/webapp-testing/SKILL.md +31 -1
- package/skills/xlsx/SKILL.md +1 -1
- package/telegram-plugin/admin-commands/dispatch.test.ts +1 -1
- package/telegram-plugin/admin-commands/index.ts +9 -5
- package/telegram-plugin/auth-snapshot-format.ts +612 -0
- package/telegram-plugin/auto-fallback-fleet.ts +215 -0
- package/telegram-plugin/auto-fallback.ts +28 -301
- package/telegram-plugin/dist/gateway/gateway.js +17453 -15100
- package/telegram-plugin/fleet-fallback-gate.ts +105 -0
- package/telegram-plugin/gateway/approval-callback.test.ts +104 -0
- package/telegram-plugin/gateway/approval-callback.ts +31 -3
- package/telegram-plugin/gateway/auth-add-flow.ts +326 -0
- package/telegram-plugin/gateway/auth-broker-client.ts +75 -0
- package/telegram-plugin/gateway/auth-command.ts +905 -0
- package/telegram-plugin/gateway/auth-line.ts +123 -0
- package/telegram-plugin/gateway/auth-status-adapter.ts +101 -0
- package/telegram-plugin/gateway/boot-card.ts +23 -37
- package/telegram-plugin/gateway/boot-probes.ts +9 -12
- package/telegram-plugin/gateway/diff-preview-card.test.ts +192 -0
- package/telegram-plugin/gateway/diff-preview-card.ts +170 -0
- package/telegram-plugin/gateway/drive-write-approval.test.ts +312 -0
- package/telegram-plugin/gateway/drive-write-approval.ts +243 -0
- package/telegram-plugin/gateway/folder-picker-handler.test.ts +314 -0
- package/telegram-plugin/gateway/folder-picker-handler.ts +348 -0
- package/telegram-plugin/gateway/gateway.ts +1156 -938
- package/telegram-plugin/gateway/hostd-dispatch.ts +244 -0
- package/telegram-plugin/gateway/ipc-protocol.ts +83 -2
- package/telegram-plugin/gateway/ipc-server.ts +69 -0
- package/telegram-plugin/hooks/sandbox-hint-posttool.mjs +103 -12
- package/telegram-plugin/hooks/tool-label-pretool.mjs +11 -0
- package/telegram-plugin/hooks/wedge-detect-posttool.mjs +303 -0
- package/telegram-plugin/model-unavailable.ts +28 -12
- package/telegram-plugin/permission-title.ts +56 -0
- package/telegram-plugin/quota-check.ts +19 -41
- package/telegram-plugin/scripts/build.mjs +0 -1
- package/telegram-plugin/shared/bot-runtime.ts +5 -4
- package/telegram-plugin/silence-poke.ts +153 -1
- package/telegram-plugin/tests/auth-add-flow.test.ts +559 -0
- package/telegram-plugin/tests/auth-code-redact.test.ts +8 -4
- package/telegram-plugin/tests/auth-command-format2.test.ts +156 -0
- package/telegram-plugin/tests/auth-command-vernacular.test.ts +531 -0
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +429 -0
- package/telegram-plugin/tests/auth-status-adapter.test.ts +129 -0
- package/telegram-plugin/tests/auto-fallback-fleet.test.ts +211 -0
- package/telegram-plugin/tests/auto-fallback.test.ts +60 -358
- package/telegram-plugin/tests/boot-probes.test.ts +27 -22
- package/telegram-plugin/tests/fleet-fallback-gate.test.ts +197 -0
- package/telegram-plugin/tests/model-unavailable.test.ts +30 -5
- package/telegram-plugin/tests/permission-title.test.ts +31 -0
- package/telegram-plugin/tests/quota-check.test.ts +5 -35
- package/telegram-plugin/tests/sandbox-hint-posttool.test.ts +212 -2
- package/telegram-plugin/tests/silence-poke.test.ts +237 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +112 -0
- package/telegram-plugin/turn-flush-safety.ts +55 -1
- package/telegram-plugin/uat/SETUP.md +35 -1
- package/telegram-plugin/uat/runners/agent-self-sufficiency.ts +457 -0
- package/telegram-plugin/uat/runners/paraphrases.ts +231 -0
- package/telegram-plugin/uat/runners/report.ts +150 -0
- package/telegram-plugin/uat/runners/run-agent-self-sufficiency.sh +50 -0
- package/telegram-plugin/uat/runners/scorer.test.ts +196 -0
- package/telegram-plugin/uat/runners/scorer.ts +106 -0
- package/telegram-plugin/uat/runners/skill-coverage.test.ts +100 -0
- package/telegram-plugin/uat/runners/skill-coverage.ts +620 -0
- package/telegram-plugin/uat/scenarios/jtbd-interrupt-marker-dm.test.ts +7 -1
- package/telegram-plugin/uat/scenarios/jtbd-rapid-followup-dm.test.ts +7 -1
- package/telegram-plugin/auth-dashboard.ts +0 -1104
- package/telegram-plugin/auth-slot-parser.ts +0 -497
- package/telegram-plugin/auto-fallback-dispatcher.ts +0 -68
- package/telegram-plugin/dist/foreman/foreman.js +0 -31358
- package/telegram-plugin/foreman/foreman-create-flow.ts +0 -202
- package/telegram-plugin/foreman/foreman-handlers.ts +0 -493
- package/telegram-plugin/foreman/foreman.ts +0 -1165
- package/telegram-plugin/foreman/setup-flow.ts +0 -345
- package/telegram-plugin/foreman/setup-state.ts +0 -239
- package/telegram-plugin/foreman/state.ts +0 -203
- package/telegram-plugin/tests/auth-account-identity-surface.test.ts +0 -118
- package/telegram-plugin/tests/auth-dashboard-edge-cases.test.ts +0 -260
- package/telegram-plugin/tests/auth-dashboard-restart-flow.test.ts +0 -140
- package/telegram-plugin/tests/auth-dashboard-v3b.test.ts +0 -559
- package/telegram-plugin/tests/auth-dashboard.test.ts +0 -1045
- package/telegram-plugin/tests/auth-slot-commands.test.ts +0 -640
- package/telegram-plugin/tests/auto-fallback-dispatcher.e2e.test.ts +0 -183
- package/telegram-plugin/tests/boot-card-account-quota.test.ts +0 -137
- package/telegram-plugin/tests/foreman-create-flow.test.ts +0 -359
- package/telegram-plugin/tests/foreman-handlers.test.ts +0 -347
- package/telegram-plugin/tests/foreman-state.test.ts +0 -164
- package/telegram-plugin/tests/foreman-write-ops.test.ts +0 -214
- package/telegram-plugin/tests/setup-flow.test.ts +0 -510
- 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
|
-
})
|