switchroom 0.8.1 → 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 (105) hide show
  1. package/README.md +49 -57
  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/switchroom.js +15931 -12778
  6. package/dist/host-control/main.js +582 -43
  7. package/dist/vault/approvals/kernel-server.js +276 -47
  8. package/dist/vault/broker/server.js +333 -69
  9. package/examples/minimal.yaml +63 -0
  10. package/examples/personal-google-workspace-mcp/.env.example +34 -0
  11. package/examples/personal-google-workspace-mcp/README.md +194 -0
  12. package/examples/personal-google-workspace-mcp/compose.yaml +66 -0
  13. package/examples/switchroom.yaml +220 -0
  14. package/package.json +6 -4
  15. package/profiles/_base/start.sh.hbs +3 -3
  16. package/profiles/_shared/agent-self-service.md.hbs +126 -0
  17. package/profiles/default/CLAUDE.md +10 -0
  18. package/profiles/default/CLAUDE.md.hbs +16 -0
  19. package/skills/buildkite-agent-infrastructure/SKILL.md +30 -11
  20. package/skills/buildkite-agent-runtime/SKILL.md +44 -11
  21. package/skills/buildkite-api/SKILL.md +31 -8
  22. package/skills/buildkite-cli/SKILL.md +27 -9
  23. package/skills/buildkite-migration/SKILL.md +22 -9
  24. package/skills/buildkite-pipelines/SKILL.md +26 -9
  25. package/skills/buildkite-secure-delivery/SKILL.md +23 -9
  26. package/skills/buildkite-test-engine/SKILL.md +25 -8
  27. package/skills/docx/SKILL.md +1 -1
  28. package/skills/file-bug/SKILL.md +34 -6
  29. package/skills/humanizer/SKILL.md +15 -0
  30. package/skills/humanizer-calibrate/SKILL.md +7 -1
  31. package/skills/mcp-builder/SKILL.md +1 -1
  32. package/skills/pdf/SKILL.md +1 -1
  33. package/skills/pptx/SKILL.md +1 -1
  34. package/skills/skill-creator/SKILL.md +21 -1
  35. package/skills/skill-creator/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  36. package/skills/skill-creator/scripts/__pycache__/generate_report.cpython-313.pyc +0 -0
  37. package/skills/skill-creator/scripts/__pycache__/improve_description.cpython-313.pyc +0 -0
  38. package/skills/skill-creator/scripts/__pycache__/run_eval.cpython-313.pyc +0 -0
  39. package/skills/skill-creator/scripts/__pycache__/run_loop.cpython-313.pyc +0 -0
  40. package/skills/skill-creator/scripts/__pycache__/utils.cpython-313.pyc +0 -0
  41. package/skills/switchroom-cli/SKILL.md +63 -64
  42. package/skills/switchroom-health/SKILL.md +23 -10
  43. package/skills/switchroom-install/SKILL.md +3 -3
  44. package/skills/switchroom-manage/SKILL.md +26 -19
  45. package/skills/switchroom-runtime/SKILL.md +67 -15
  46. package/skills/switchroom-status/SKILL.md +26 -1
  47. package/skills/telegram-test-harness/SKILL.md +3 -0
  48. package/skills/webapp-testing/SKILL.md +31 -1
  49. package/skills/xlsx/SKILL.md +1 -1
  50. package/telegram-plugin/admin-commands/index.ts +7 -5
  51. package/telegram-plugin/dist/gateway/gateway.js +13042 -12844
  52. package/telegram-plugin/gateway/auth-add-flow.ts +326 -0
  53. package/telegram-plugin/gateway/auth-broker-client.ts +75 -0
  54. package/telegram-plugin/gateway/auth-command.ts +794 -0
  55. package/telegram-plugin/gateway/auth-line.ts +123 -0
  56. package/telegram-plugin/gateway/boot-card.ts +22 -36
  57. package/telegram-plugin/gateway/boot-probes.ts +3 -3
  58. package/telegram-plugin/gateway/gateway.ts +313 -798
  59. package/telegram-plugin/gateway/hostd-dispatch.ts +117 -0
  60. package/telegram-plugin/hooks/tool-label-pretool.mjs +11 -0
  61. package/telegram-plugin/hooks/wedge-detect-posttool.mjs +303 -0
  62. package/telegram-plugin/permission-title.ts +56 -0
  63. package/telegram-plugin/quota-check.ts +19 -41
  64. package/telegram-plugin/scripts/build.mjs +0 -1
  65. package/telegram-plugin/shared/bot-runtime.ts +5 -4
  66. package/telegram-plugin/tests/auth-add-flow.test.ts +559 -0
  67. package/telegram-plugin/tests/auth-code-redact.test.ts +8 -4
  68. package/telegram-plugin/tests/auth-command-vernacular.test.ts +531 -0
  69. package/telegram-plugin/tests/boot-probes.test.ts +11 -4
  70. package/telegram-plugin/tests/hostd-dispatch.test.ts +129 -0
  71. package/telegram-plugin/tests/permission-title.test.ts +31 -0
  72. package/telegram-plugin/tests/quota-check.test.ts +5 -35
  73. package/telegram-plugin/uat/SETUP.md +31 -1
  74. package/telegram-plugin/uat/runners/agent-self-sufficiency.ts +457 -0
  75. package/telegram-plugin/uat/runners/paraphrases.ts +231 -0
  76. package/telegram-plugin/uat/runners/report.ts +150 -0
  77. package/telegram-plugin/uat/runners/run-agent-self-sufficiency.sh +50 -0
  78. package/telegram-plugin/uat/runners/scorer.test.ts +196 -0
  79. package/telegram-plugin/uat/runners/scorer.ts +106 -0
  80. package/telegram-plugin/uat/runners/skill-coverage.test.ts +100 -0
  81. package/telegram-plugin/uat/runners/skill-coverage.ts +620 -0
  82. package/telegram-plugin/uat/scenarios/jtbd-interrupt-marker-dm.test.ts +7 -1
  83. package/telegram-plugin/uat/scenarios/jtbd-rapid-followup-dm.test.ts +7 -1
  84. package/telegram-plugin/auth-dashboard.ts +0 -1104
  85. package/telegram-plugin/auth-slot-parser.ts +0 -497
  86. package/telegram-plugin/dist/foreman/foreman.js +0 -31358
  87. package/telegram-plugin/foreman/foreman-create-flow.ts +0 -202
  88. package/telegram-plugin/foreman/foreman-handlers.ts +0 -493
  89. package/telegram-plugin/foreman/foreman.ts +0 -1165
  90. package/telegram-plugin/foreman/setup-flow.ts +0 -345
  91. package/telegram-plugin/foreman/setup-state.ts +0 -239
  92. package/telegram-plugin/foreman/state.ts +0 -203
  93. package/telegram-plugin/tests/auth-account-identity-surface.test.ts +0 -118
  94. package/telegram-plugin/tests/auth-dashboard-edge-cases.test.ts +0 -260
  95. package/telegram-plugin/tests/auth-dashboard-restart-flow.test.ts +0 -140
  96. package/telegram-plugin/tests/auth-dashboard-v3b.test.ts +0 -559
  97. package/telegram-plugin/tests/auth-dashboard.test.ts +0 -1045
  98. package/telegram-plugin/tests/auth-slot-commands.test.ts +0 -640
  99. package/telegram-plugin/tests/boot-card-account-quota.test.ts +0 -137
  100. package/telegram-plugin/tests/foreman-create-flow.test.ts +0 -359
  101. package/telegram-plugin/tests/foreman-handlers.test.ts +0 -347
  102. package/telegram-plugin/tests/foreman-state.test.ts +0 -164
  103. package/telegram-plugin/tests/foreman-write-ops.test.ts +0 -214
  104. package/telegram-plugin/tests/setup-flow.test.ts +0 -510
  105. package/telegram-plugin/tests/setup-state.test.ts +0 -146
@@ -0,0 +1,63 @@
1
+ # switchroom.yaml — Minimal single-agent setup.
2
+ # One bot, one agent, DM-only (no forum/topic routing).
3
+ #
4
+ # `vault:telegram-bot-token` is the canonical way to reference the bot
5
+ # token — `switchroom setup` stores your BotFather token in the vault
6
+ # under that key and the runtime resolves it via the vault broker.
7
+ # (For bootstrap before the vault exists, the wizard also accepts
8
+ # TELEGRAM_BOT_TOKEN as an env-var fallback.)
9
+ #
10
+ # `forum_chat_id: "0"` is the DM-only sentinel used by v0.7+ — kept in
11
+ # the schema for backwards compat with legacy forum-mode installs.
12
+
13
+ switchroom:
14
+ version: 1
15
+
16
+ telegram:
17
+ bot_token: "vault:telegram-bot-token"
18
+ forum_chat_id: "0"
19
+
20
+ memory:
21
+ backend: hindsight
22
+ shared_collection: shared
23
+
24
+ defaults:
25
+ model: claude-sonnet-4-6
26
+ tools:
27
+ allow: [all]
28
+ subagents:
29
+ worker:
30
+ description: "Handles implementation tasks: writing, editing, building, testing. Use for any task that takes more than a few seconds."
31
+ model: sonnet
32
+ background: true
33
+ isolation: worktree
34
+ maxTurns: 50
35
+ color: blue
36
+ prompt: |
37
+ You are a worker sub-agent. Implement the task described in your
38
+ prompt precisely and completely. Commit your work when done.
39
+ If requirements are ambiguous, state your assumptions rather
40
+ than guessing. Return a concise summary of what you did.
41
+ researcher:
42
+ description: "Explores codebases, reads documentation, searches for information. Use for any research or investigation task."
43
+ model: haiku
44
+ background: true
45
+ color: green
46
+ prompt: |
47
+ You are a research sub-agent. Investigate the topic described
48
+ in your prompt thoroughly. Return structured findings — not
49
+ raw file contents. Keep your report under 500 words unless
50
+ the parent explicitly asks for more detail.
51
+ reviewer:
52
+ description: "Reviews code, plans, documents for quality, correctness, and completeness. Use after a worker finishes or before shipping."
53
+ model: sonnet
54
+ color: purple
55
+ prompt: |
56
+ You are a review sub-agent. Examine the work described in
57
+ your prompt for correctness, completeness, security, and
58
+ quality. Report findings as a punch list: what's good, what
59
+ needs fixing, what's missing. Be rigorous but concise.
60
+
61
+ agents:
62
+ assistant:
63
+ topic_name: "General"
@@ -0,0 +1,34 @@
1
+ # Personal Google Workspace MCP — environment configuration.
2
+ #
3
+ # Copy to `.env` and fill in:
4
+ # GOOGLE_OAUTH_CLIENT_ID — from your Google Cloud Console OAuth client (Desktop app type)
5
+ # FASTMCP_SERVER_AUTH_GOOGLE_JWT_SIGNING_KEY — generate via: openssl rand -hex 32
6
+ #
7
+ # OAuth 2.1 PKCE flow — no client_secret needed (Desktop clients are
8
+ # public clients per OAuth 2.0 spec; PKCE replaces the secret's role).
9
+
10
+ # Required by upstream MCP for OAuth 2.1 mode.
11
+ MCP_ENABLE_OAUTH21=true
12
+
13
+ # Your OAuth client ID from Google Cloud Console.
14
+ # Create: APIs & Services → Credentials → Create OAuth client ID → Desktop app.
15
+ # Format: "12345678-abc...xyz.apps.googleusercontent.com"
16
+ GOOGLE_OAUTH_CLIENT_ID=REPLACE_WITH_YOUR_CLIENT_ID
17
+
18
+ # Port the MCP server listens on (must match compose.yaml's port mapping
19
+ # AND the URL in your .mcp.json). 8217 is arbitrary; pick anything free.
20
+ WORKSPACE_MCP_PORT=8217
21
+
22
+ # OAuth callback URL — must match WORKSPACE_MCP_PORT exactly.
23
+ # Loopback `localhost` is implicitly allowed for Desktop OAuth clients,
24
+ # no GCP-side redirect URI registration needed.
25
+ GOOGLE_OAUTH_REDIRECT_URI=http://localhost:8217/oauth2callback
26
+
27
+ # Required for the loopback OAuth flow over plain HTTP. Safe in this
28
+ # context — the OAuth handshake never leaves localhost.
29
+ OAUTHLIB_INSECURE_TRANSPORT=1
30
+
31
+ # JWT signing key for the MCP's session tokens. Generate ONCE per
32
+ # install: openssl rand -hex 32
33
+ # DO NOT commit the real value to source control.
34
+ FASTMCP_SERVER_AUTH_GOOGLE_JWT_SIGNING_KEY=REPLACE_WITH_HEX_FROM_OPENSSL_RAND
@@ -0,0 +1,194 @@
1
+ # Personal Google Workspace MCP
2
+
3
+ This example sets up [taylorwilsdon/google_workspace_mcp][upstream] as a
4
+ docker-compose service exposing Google Drive + Docs + Sheets + Calendar
5
+ tools to **your own Claude Code session on the host** (not to switchroom
6
+ agents).
7
+
8
+ It is intentionally **separate from the agent-side feature.** Agents get
9
+ Workspace access via `switchroom auth google connect <agent>` (RFC G
10
+ §4.5, available after Phase 3 lands). This example is for the
11
+ operator's pair-design loop with their own host-side `claude`.
12
+
13
+ > **Why two paths?** Agents run inside switchroom containers with
14
+ > approval-kernel-mediated tool access; the per-agent OAuth posture is
15
+ > load-bearing for the approval semantics (RFC D §2 + RFC G §4.4).
16
+ > Your own host-side Claude Code is a single-identity surface — none of
17
+ > that machinery applies. A shared HTTP MCP server is the right shape
18
+ > for the operator case and the wrong shape for the fleet.
19
+
20
+ [upstream]: https://github.com/taylorwilsdon/google_workspace_mcp
21
+
22
+ ## What you get
23
+
24
+ - A long-running docker container (`google-workspace-mcp`) listening on
25
+ loopback port 8217.
26
+ - 16 Workspace tools available to any Claude Code session that points
27
+ its `.mcp.json` at `http://127.0.0.1:8217/mcp`.
28
+ - OAuth 2.1 PKCE flow — no client_secret to manage.
29
+ - Refresh tokens persist in `./credentials/` so you re-auth only on
30
+ Google password change or 7-day Testing-mode expiry (see §5).
31
+
32
+ ## What you don't get
33
+
34
+ - Tools in unrelated Claude Code sessions. The `.mcp.json` is
35
+ project-scoped — Drive surface only appears when you `cd` to a
36
+ directory that has it.
37
+ - Any effect on switchroom agents.
38
+ - HTTPS or LAN reachability. Loopback only by design.
39
+
40
+ ## 1. GCP Console setup (~5 minutes, you do this)
41
+
42
+ 1. Go to <https://console.cloud.google.com> and create (or pick) a
43
+ project — name it `claude-workspace-mcp`.
44
+ 2. **APIs & Services → Library** — enable each of these (one at a time;
45
+ wait for "API enabled" before moving on):
46
+ - Google Drive API
47
+ - Google Docs API
48
+ - Google Sheets API
49
+ - Google Calendar API
50
+ 3. **APIs & Services → OAuth consent screen** → User Type **External**.
51
+ - Fill App name (`claude-workspace-mcp`), your email, dev contact email.
52
+ - Save and continue past the Scopes page (the server requests scopes
53
+ at runtime — don't add any here).
54
+ - **Add yourself as a Test User** under "Audience" (your gmail
55
+ address).
56
+ - Decide whether to **Publish app** — see §5 for the trade-off.
57
+ 4. **APIs & Services → Credentials → Create Credentials → OAuth client
58
+ ID** → Application type **Desktop app** → name it `claude-code-local`.
59
+ 5. Copy the Client ID from the modal (looks like
60
+ `12345-abc...xyz.apps.googleusercontent.com`). You can ignore the
61
+ client secret — PKCE doesn't use it.
62
+
63
+ ## 2. Set up this directory
64
+
65
+ ```sh
66
+ cd examples/personal-google-workspace-mcp/
67
+ cp .env.example .env
68
+
69
+ # Edit .env:
70
+ # GOOGLE_OAUTH_CLIENT_ID ← paste from step 5 above
71
+ # FASTMCP_SERVER_AUTH_GOOGLE_JWT_SIGNING_KEY ← generate via:
72
+
73
+ # (GNU sed — Linux)
74
+ sed -i "s|REPLACE_WITH_HEX_FROM_OPENSSL_RAND|$(openssl rand -hex 32)|" .env
75
+
76
+ # (BSD sed — macOS — use this instead)
77
+ # sed -i '' "s|REPLACE_WITH_HEX_FROM_OPENSSL_RAND|$(openssl rand -hex 32)|" .env
78
+
79
+ chmod 600 .env
80
+ ```
81
+
82
+ ## 3. Bring it up
83
+
84
+ ```sh
85
+ docker compose up -d
86
+ docker compose ps # should show "healthy" within ~30s
87
+ docker compose logs -f # ctrl-c when you see "Uvicorn running on http://0.0.0.0:8217"
88
+ ```
89
+
90
+ ## 4. Wire it into Claude Code
91
+
92
+ Choose where the MCP server should be reachable:
93
+
94
+ **Project-scoped (recommended)** — the Workspace tools only appear when
95
+ you start Claude Code in this specific project directory:
96
+
97
+ ```sh
98
+ cat > /path/to/your/project/.mcp.json <<'JSON'
99
+ {
100
+ "mcpServers": {
101
+ "google-workspace": {
102
+ "type": "http",
103
+ "url": "http://127.0.0.1:8217/mcp"
104
+ }
105
+ }
106
+ }
107
+ JSON
108
+ ```
109
+
110
+ Restart Claude Code in that directory. On first start it'll prompt to
111
+ approve the new MCP server — say yes.
112
+
113
+ **User-scoped** (every Claude Code session sees Workspace tools): use
114
+ `claude mcp add` instead per upstream README — but think hard before you
115
+ do this, because you're then carrying Workspace authorization into every
116
+ unrelated codebase.
117
+
118
+ ## 5. OAuth consent — Testing vs Production trade-off
119
+
120
+ When you set up the consent screen in §1.3, you chose between leaving
121
+ the app in **Testing** or clicking **Publish app**.
122
+
123
+ - **Testing** (default): refresh tokens expire **every 7 days** for
124
+ Google's "unverified app + sensitive scopes" policy. You re-auth
125
+ weekly via the inline OAuth prompt.
126
+ - **Production**: no 7-day expiry. The "Publish app" button prompts
127
+ scary copy ("your app will be available to any user with a Google
128
+ Account") but for a personal Desktop OAuth client with you as the
129
+ only user, this is effectively a no-op — there's nothing to find or
130
+ use without your specific Client ID.
131
+
132
+ Pick whichever your nerve allows.
133
+
134
+ ## 6. Tier choice
135
+
136
+ `compose.yaml` defaults to `--tool-tier core` (~16 tools). Change to:
137
+
138
+ - `extended` (~40 tools) — adds Slides, Forms, Tasks, Chat. Re-consent
139
+ needed (broader OAuth scopes). Procedure:
140
+ 1. Edit `compose.yaml` to bump `--tool-tier core` → `--tool-tier extended`.
141
+ 2. **Delete the existing token** so the upgraded scopes get requested
142
+ on the next OAuth flow: `rm -rf ./credentials/`.
143
+ 3. `docker compose up -d --force-recreate`.
144
+ 4. Trigger a tool call from Claude Code — the OAuth URL will appear
145
+ inline; tap to consent at the new scopes.
146
+ - `complete` (~60+ tools) — adds Gmail. **Not recommended yet** — Gmail's
147
+ per-thread approval shape is unsuitable for the broad OAuth scopes
148
+ this tier requests. Wait for a dedicated Gmail spec.
149
+
150
+ ## 7. Maintenance
151
+
152
+ - **Logs**: `docker compose logs -f workspace-mcp`
153
+ - **Restart**: `docker compose restart workspace-mcp`
154
+ - **Re-auth** (Google password change or weekly Testing expiry): the
155
+ next failed tool call surfaces a fresh OAuth URL inline; tap to
156
+ consent, done.
157
+ - **Upgrade**: bump the `workspace-mcp==X.Y.Z` pin in `compose.yaml`,
158
+ then `docker compose up -d --force-recreate`.
159
+ - **Tear down**: `docker compose down` (keeps credentials);
160
+ `docker compose down -v` (deletes credentials, forces fresh OAuth).
161
+ - **Inspect tokens**: `ls -la ./credentials/` — one JSON file per
162
+ authenticated Google account.
163
+
164
+ ## 8. Troubleshooting
165
+
166
+ - **Container "unhealthy" forever**: check `docker compose logs` for
167
+ Python tracebacks. Most common cause: `GOOGLE_OAUTH_CLIENT_ID` or
168
+ `FASTMCP_SERVER_AUTH_GOOGLE_JWT_SIGNING_KEY` still has the
169
+ `REPLACE_WITH_*` placeholder.
170
+ - **Port 8217 collision**: bump the port in `.env`
171
+ (`WORKSPACE_MCP_PORT` + `GOOGLE_OAUTH_REDIRECT_URI`), in
172
+ `compose.yaml` (the `ports:` mapping AND the healthcheck script),
173
+ and in your `.mcp.json` URL.
174
+ - **OAuth flow fails with `redirect_uri_mismatch`**: make sure
175
+ `GOOGLE_OAUTH_REDIRECT_URI` in `.env` exactly matches
176
+ `WORKSPACE_MCP_PORT`. Loopback URIs don't need GCP-side registration
177
+ for Desktop clients, but they do need to match what the server
178
+ presents.
179
+ - **Claude Code doesn't show the new MCP server after `cd` to project**:
180
+ exit and re-launch Claude Code (`/exit`, then `claude -c` to keep
181
+ the conversation). `.mcp.json` is loaded at startup, not hot-reloaded.
182
+
183
+ ## 9. Security notes
184
+
185
+ - `.env` is mode 600 — don't commit, don't share, don't snapshot.
186
+ - `./credentials/` directory holds refresh tokens — same restrictions.
187
+ Add to disk-encryption scope if your host has any.
188
+ - The Workspace MCP sends data to Google APIs only, on your behalf,
189
+ using your OAuth client. No telemetry, no third-party endpoints
190
+ (verified per upstream's `pyproject.toml` dependency tree).
191
+ - The OAuth client_secret in your `gcp-oauth.keys.json` (if Google
192
+ Cloud Console gave you one) is **not used** in PKCE flow. Treat it
193
+ as cosmetic; you can ignore it. If it's worried you, regenerate it
194
+ in GCP Console — won't break anything.
@@ -0,0 +1,66 @@
1
+ # Personal Google Workspace MCP — operator-host Claude Code surface.
2
+ #
3
+ # This is the docker-compose pattern for an operator who wants their own
4
+ # Claude Code on the host to have Drive + Docs + Sheets + Calendar tools.
5
+ # It does NOT affect switchroom agents — they get Workspace via
6
+ # `switchroom auth google connect <agent>` (RFC G §4.5, post-Phase-3).
7
+ #
8
+ # Setup walkthrough: see README.md.
9
+ #
10
+ # Compose project name (`name:`) is deliberately distinct from the
11
+ # `switchroom` project so `switchroom apply` / `switchroom update` /
12
+ # `docker compose -p switchroom down -v` won't touch this container.
13
+ # Same isolation pattern as the hostd compose project.
14
+
15
+ name: google-workspace-mcp
16
+
17
+ services:
18
+ workspace-mcp:
19
+ image: ghcr.io/astral-sh/uv:python3.12-bookworm-slim
20
+ container_name: google-workspace-mcp
21
+ restart: unless-stopped
22
+ env_file: .env
23
+ environment:
24
+ WORKSPACE_MCP_CREDENTIALS_DIR: /credentials
25
+ ports:
26
+ # Loopback only — never expose Workspace MCP to the LAN.
27
+ # Bump the host-side port if 8217 collides with something else
28
+ # (also update WORKSPACE_MCP_PORT + GOOGLE_OAUTH_REDIRECT_URI in .env
29
+ # and the URL in your .mcp.json).
30
+ - "127.0.0.1:8217:8217"
31
+ volumes:
32
+ # Refresh tokens persist here (chmod 700-equivalent inside the
33
+ # container). Survives container recreate; lost on `docker compose
34
+ # down -v`.
35
+ - ./credentials:/credentials
36
+ command:
37
+ - uvx
38
+ # Pinned: bumping is a deliberate operator decision, not a silent
39
+ # surprise. Bump in lockstep with retesting the OAuth flow.
40
+ - --from
41
+ - workspace-mcp==1.20.4
42
+ - workspace-mcp
43
+ # streamable-http (not stdio) is required for OAuth 2.1 PKCE per
44
+ # upstream README.
45
+ - --transport
46
+ - streamable-http
47
+ # Tier knob — see README §4 for tier choice. `core` = ~16 tools
48
+ # (Drive + Docs + Sheets + Calendar, the validated default).
49
+ - --tool-tier
50
+ - core
51
+ # Restrict to only the four core services; omits Gmail (which is
52
+ # in `complete` tier and warrants its own approval shape).
53
+ - --tools
54
+ - drive
55
+ - docs
56
+ - sheets
57
+ - calendar
58
+ healthcheck:
59
+ # TCP-connect probe — the server returns 401 on `/mcp` until OAuth
60
+ # completes, which would fail an HTTP healthcheck. TCP-connect just
61
+ # verifies the listener is bound.
62
+ test: ["CMD-SHELL", "python -c \"import socket; s=socket.socket(); s.settimeout(2); s.connect(('127.0.0.1',8217)); s.close()\""]
63
+ interval: 30s
64
+ timeout: 5s
65
+ retries: 3
66
+ start_period: 20s
@@ -0,0 +1,220 @@
1
+ # switchroom.yaml — Full example configuration
2
+ #
3
+ # Switchroom uses a three-layer cascade for agent config:
4
+ # 1. defaults: → global baseline for every agent
5
+ # 2. profiles: → named presets agents opt into via `extends:`
6
+ # 3. agents: → per-agent overrides (only express differences)
7
+ #
8
+ # Each agent gets its own Telegram topic in a forum group.
9
+ # Create bots via @BotFather: /newbot for each agent.
10
+
11
+ switchroom:
12
+ version: 1
13
+ agents_dir: ~/.switchroom/agents
14
+ skills_dir: ~/.switchroom/skills # shared skill pool (symlinked per agent)
15
+
16
+ telegram:
17
+ bot_token: "vault:telegram-bot-token"
18
+ # DM-only sentinel; v0.7+ defaults to per-agent DM-pair topology.
19
+ # Legacy forum-mode installs keep a real chat id here.
20
+ forum_chat_id: "0"
21
+
22
+ memory:
23
+ backend: hindsight
24
+ shared_collection: shared
25
+ config:
26
+ provider: ollama
27
+ model: nomic-embed-text
28
+
29
+ vault:
30
+ # v0.7.12+ canonical path is the parent-dir layout. v0.7.x installs
31
+ # auto-migrate from the legacy ~/.switchroom/vault.enc on `switchroom
32
+ # apply`; the legacy path becomes a symlink that's removed in v0.7.14.
33
+ # See docs/vault.md for the migration story.
34
+ path: ~/.switchroom/vault/vault.enc
35
+
36
+ # --- Global defaults (implicit bottom-of-cascade profile) ---
37
+ # Every agent inherits this. Per-agent fields win on conflict.
38
+ # tools/skills/schedule are unioned; scalars are overridden.
39
+ defaults:
40
+ model: claude-sonnet-4-6
41
+ # The switchroom-telegram fork is the default. Uncomment to fall back to
42
+ # the upstream marketplace plugin: channels.telegram.plugin: official
43
+ channels:
44
+ telegram:
45
+ format: html
46
+ tools:
47
+ allow: [all]
48
+ env:
49
+ SWITCHROOM_AUDIT_URL: "https://audit.example"
50
+ hooks:
51
+ PreToolUse:
52
+ - command: "/opt/switchroom-audit.sh"
53
+ timeout: 5
54
+ # Bundled skills that ship with switchroom. `humanizer` removes AI-writing
55
+ # patterns from agent replies before they reach Telegram (29 patterns from
56
+ # Wikipedia's "Signs of AI writing" guide). `humanizer-calibrate` is its
57
+ # companion that builds a personal voice template from your message history.
58
+ # Remove from this list to disable. Per-agent `skills:` is unioned with
59
+ # this default — don't repeat shared skills in each agent.
60
+ skills: [humanizer, humanizer-calibrate]
61
+ # Optional: point the humanizer at a voice template generated by
62
+ # `/humanizer-calibrate`. Without this, falls back to generic rules.
63
+ # humanizer_voice_file: ~/.switchroom/voice.md
64
+ system_prompt_append: |
65
+ Always respond concisely.
66
+
67
+ # --- Default sub-agents ---
68
+ # Every agent gets these workers. They run on cheaper/faster models
69
+ # in the background so the main agent stays available for new requests.
70
+ # Override per-agent or per-profile by redefining the same name.
71
+ subagents:
72
+ worker:
73
+ description: "Handles implementation tasks: writing, editing, building, testing. Use for any task that takes more than a few seconds."
74
+ model: sonnet
75
+ background: true
76
+ # NOTE: `isolation: worktree` used to live here as a global default
77
+ # but moved to the `coding` profile in switchroom 0.6.6 (#682). It
78
+ # hard-failed for any agent whose cwd was not a git repo — i.e. the
79
+ # majority of switchroom agents, which run from
80
+ # `~/.switchroom/agents/<name>` with no git init. Worktree isolation
81
+ # is opt-in via `extends: coding` now. See CHANGELOG and #682.
82
+ # 100 turns is a comfortable budget for most worker tasks; if a
83
+ # worker exhausts this mid-flow, it'll be reported as #423-style
84
+ # ceiling. Bump to 200 for genuinely long PR-shipping flows or
85
+ # remove the line entirely to inherit Claude Code's default.
86
+ # Origin: this used to be 50 and was the literal cause of #423
87
+ # ("worker sub-agents hit ~50 tool-use ceiling mid-flow before
88
+ # finishing"). Don't shrink it without good reason.
89
+ maxTurns: 100
90
+ color: blue
91
+ disallowedTools:
92
+ - "mcp__switchroom-telegram__*"
93
+ prompt: |
94
+ You are a worker sub-agent. Implement the task described in your
95
+ prompt precisely and completely. Commit your work when done.
96
+ If requirements are ambiguous, state your assumptions rather
97
+ than guessing. Return a concise summary of what you did.
98
+ researcher:
99
+ description: "Explores codebases, reads documentation, searches for information. Use for any research or investigation task."
100
+ model: haiku
101
+ background: true
102
+ color: green
103
+ disallowedTools:
104
+ - "mcp__switchroom-telegram__*"
105
+ prompt: |
106
+ You are a research sub-agent. Investigate the topic described
107
+ in your prompt thoroughly. Return structured findings — not
108
+ raw file contents. Keep your report under 500 words unless
109
+ the parent explicitly asks for more detail.
110
+ reviewer:
111
+ description: "Reviews code, plans, documents for quality, correctness, and completeness. Use after a worker finishes or before shipping."
112
+ model: sonnet
113
+ color: purple
114
+ disallowedTools:
115
+ - "mcp__switchroom-telegram__*"
116
+ prompt: |
117
+ You are a review sub-agent. Examine the work described in
118
+ your prompt for correctness, completeness, security, and
119
+ quality. Report findings as a punch list: what's good, what
120
+ needs fixing, what's missing. Be rigorous but concise.
121
+
122
+ # --- Named profiles (opt-in presets) ---
123
+ # Agents inherit from a profile via `extends: <name>`.
124
+ # Inline profiles here take priority over filesystem profiles/<name>/
125
+ # (which can additionally bundle CLAUDE.md.hbs + skills/).
126
+ profiles:
127
+ # The `coding` profile pairs with `profiles/coding/` on disk (which
128
+ # ships the CLAUDE.md template and skills). Its inline counterpart
129
+ # here carries the YAML-level defaults that the filesystem assets
130
+ # advertise — worktree-isolated workers chief among them. Agents
131
+ # whose cwd IS a git checkout (a real code repo) should
132
+ # `extends: coding` to pick this up. Agents whose cwd is the default
133
+ # `~/.switchroom/agents/<name>` (no git) should NOT — Claude Code
134
+ # cannot create a worktree off a non-repo and the worker will fail.
135
+ coding:
136
+ subagents:
137
+ worker:
138
+ isolation: worktree
139
+
140
+ coder:
141
+ tools:
142
+ allow: [Bash, Read, Write, Edit, Grep, Glob]
143
+ system_prompt_append: |
144
+ You write production-quality TypeScript. Prefer explicit types.
145
+ skills: [code-review, architecture]
146
+
147
+ advisor:
148
+ tools:
149
+ deny: [Bash, Edit, Write]
150
+ soul:
151
+ style: warm, empathetic, non-prescriptive
152
+ boundaries: not a licensed professional — always recommend consulting a qualified expert
153
+ system_prompt_append: |
154
+ Prioritize listening and asking clarifying questions.
155
+
156
+ # --- Agents ---
157
+ # Minimal per-agent declarations. Everything else inherited.
158
+ agents:
159
+ coach:
160
+ topic_name: "Fitness"
161
+ topic_emoji: "🏋️"
162
+ extends: advisor # inherits from inline profile above
163
+ soul:
164
+ name: Coach
165
+ style: motivational, direct # overrides advisor.soul.style
166
+ memory:
167
+ collection: fitness
168
+ schedule:
169
+ - cron: "0 8 * * *"
170
+ prompt: "Good morning check-in: ask about sleep, energy, and plans for today"
171
+ - cron: "0 20 * * 0"
172
+ prompt: "Weekly review: summarize this week's activity and progress"
173
+
174
+ dev:
175
+ topic_name: "Code"
176
+ topic_emoji: "💻"
177
+ extends: coder # inherits from inline profile above
178
+ model: claude-opus-4-7 # override defaults.model for this agent
179
+ memory:
180
+ collection: coding
181
+ cli_args: ["--effort", "high"] # escape hatch: extra exec claude flags
182
+
183
+ assistant:
184
+ topic_name: "General"
185
+ topic_emoji: "💬"
186
+ memory:
187
+ collection: general
188
+ # No `extends:` → uses the "default" filesystem profile (profiles/default/)
189
+ # No tool/model overrides → inherits everything from defaults:
190
+
191
+ exec:
192
+ topic_name: "Executive"
193
+ topic_emoji: "📋"
194
+ extends: advisor
195
+ soul:
196
+ name: Friday
197
+ style: efficient, proactive, anticipates needs
198
+ skills: [daily-briefing, meeting-prep]
199
+ memory:
200
+ collection: executive
201
+ schedule:
202
+ - cron: "0 7 * * 1-5"
203
+ prompt: "Daily briefing: summarize today's calendar, pending tasks, and priorities"
204
+
205
+ # Example admin agent — its gateway intercepts fleet-management slash
206
+ # commands (/agents, /restart, /update, /logs, etc.) and runs them
207
+ # locally via the switchroom CLI instead of forwarding to Claude. Per-
208
+ # agent commands like /auth list, /auth reauth, /interrupt, /new work
209
+ # on every agent regardless of admin status. See the three-tier
210
+ # command model in docs/architecture.md.
211
+ #
212
+ # Uncomment after creating a second BotFather bot and adding its
213
+ # token to the vault (`switchroom vault set telegram-admin-bot-token`).
214
+ # admin:
215
+ # topic_name: "Admin"
216
+ # topic_emoji: "🛠️"
217
+ # bot_token: "vault:telegram-admin-bot-token"
218
+ # admin: true
219
+ # system_prompt_append: |
220
+ # You are the fleet admin agent. Always respond concisely.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.8.1",
3
+ "version": "0.10.0",
4
4
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,6 +9,7 @@
9
9
  "main": "./dist/cli/switchroom.js",
10
10
  "files": [
11
11
  "dist",
12
+ "examples",
12
13
  "profiles",
13
14
  "skills",
14
15
  "telegram-plugin",
@@ -20,14 +21,15 @@
20
21
  "dev": "bun bin/switchroom.ts",
21
22
  "build": "node scripts/build.mjs",
22
23
  "build:cli": "node scripts/build.mjs && bun build --compile --target=bun-linux-x64 --minify bin/switchroom.ts --outfile switchroom-linux-amd64",
23
- "test": "vitest run && bun test telegram-plugin/tests/history.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/foreman-state.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/registry/api-registry.test.ts telegram-plugin/registry/turns-schema.test.ts telegram-plugin/tests/idle-footer-wiring.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts",
24
+ "test": "vitest run && bun test telegram-plugin/tests/history.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/registry/api-registry.test.ts telegram-plugin/registry/turns-schema.test.ts telegram-plugin/tests/idle-footer-wiring.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts",
24
25
  "test:vitest": "vitest run",
25
- "test:bun": "bun test src/watchdog/state.test.ts src/watchdog/policy.test.ts src/vault/grants.test.ts src/vault/write-grants.test.ts src/vault/broker/server-grants.test.ts src/vault/broker/server-write-grants.test.ts src/vault/broker/server-mint-grant-passphrase-attest.test.ts src/vault/broker/server-passphrase-attest.test.ts src/vault/broker/server-mint-grant-posture-attest.test.ts src/vault/broker/client-token.test.ts src/vault/broker/server-unlock.test.ts src/vault/broker/auto-unlock.test.ts src/vault/broker/drift-detection.test.ts tests/vault-broker-passphrase.test.ts src/cli/vault-get-broker.test.ts src/vault/resolver-via-broker.test.ts src/vault/broker/scope.test.ts src/vault/broker/server.test.ts src/drive/disconnect.test.ts src/drive/grants.test.ts src/drive/oauth.test.ts src/drive/onboarding.test.ts src/drive/reconciler.test.ts src/drive/vault-slots.test.ts src/drive/wrapper.test.ts src/vault/approvals/kernel.test.ts src/vault/approvals/schema-idempotent.test.ts src/vault/broker/server-approvals.test.ts telegram-plugin/tests/boot-probes.test.ts telegram-plugin/tests/boot-version-string.test.ts telegram-plugin/tests/setup-state.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/foreman-state.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/uat/load-env.test.ts",
26
+ "test:bun": "bun test src/watchdog/state.test.ts src/watchdog/policy.test.ts src/vault/grants.test.ts src/vault/write-grants.test.ts src/vault/broker/server-grants.test.ts src/vault/broker/server-write-grants.test.ts src/vault/broker/server-mint-grant-passphrase-attest.test.ts src/vault/broker/server-passphrase-attest.test.ts src/vault/broker/server-mint-grant-posture-attest.test.ts src/vault/broker/client-token.test.ts src/vault/broker/server-unlock.test.ts src/vault/broker/auto-unlock.test.ts src/vault/broker/drift-detection.test.ts tests/vault-broker-passphrase.test.ts src/cli/vault-get-broker.test.ts src/vault/resolver-via-broker.test.ts src/vault/broker/scope.test.ts src/vault/broker/server.test.ts src/drive/disconnect.test.ts src/drive/grants.test.ts src/drive/oauth.test.ts src/drive/onboarding.test.ts src/drive/reconciler.test.ts src/drive/vault-slots.test.ts src/drive/wrapper.test.ts src/vault/approvals/kernel.test.ts src/vault/approvals/schema-idempotent.test.ts src/vault/broker/server-approvals.test.ts telegram-plugin/tests/boot-probes.test.ts telegram-plugin/tests/boot-version-string.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/uat/load-env.test.ts",
26
27
  "test:watch": "vitest",
27
- "lint": "tsc --noEmit && node scripts/check-plugin-references.mjs && bash scripts/check-bot-api-wrapping.sh",
28
+ "lint": "tsc --noEmit && node scripts/check-plugin-references.mjs && bash scripts/check-bot-api-wrapping.sh && node scripts/check-bun-test-imports.mjs",
28
29
  "lint:tsc": "tsc --noEmit",
29
30
  "lint:plugin-references": "node scripts/check-plugin-references.mjs",
30
31
  "lint:bot-api-wrapping": "bash scripts/check-bot-api-wrapping.sh",
32
+ "lint:bun-test-imports": "node scripts/check-bun-test-imports.mjs",
31
33
  "prepublishOnly": "npm run build && npm run lint && npm test"
32
34
  },
33
35
  "dependencies": {
@@ -149,10 +149,10 @@ export PATH="$HOME/.bun/bin:$PATH"
149
149
  # /state/agent bind mount (HOME=/state/agent/home).
150
150
  export PATH="$HOME/.local/bin:$HOME/bin:$HOME/.npm-global/bin:$PATH"
151
151
  export CLAUDE_CONFIG_DIR="{{agentDir}}/.claude"
152
+ # CLAUDE_CODE_OAUTH_TOKEN injection was removed with RFC H (auth-broker).
153
+ # Claude reads .credentials.json directly; the broker is the sole writer
154
+ # of that file and updates it before claude's own refresh window opens.
152
155
  unset CLAUDE_CODE_OAUTH_TOKEN
153
- if [ -f "$CLAUDE_CONFIG_DIR/.oauth-token" ]; then
154
- export CLAUDE_CODE_OAUTH_TOKEN="$(tr -d '\r\n' < "$CLAUDE_CONFIG_DIR/.oauth-token")"
155
- fi
156
156
  export TELEGRAM_STATE_DIR="{{agentDir}}/telegram"
157
157
  # SWITCHROOM_AGENT_NAME is the canonical "which agent am I" identifier the
158
158
  # telegram-plugin reads to detect self-restart commands. We can't rely on