@openparachute/agent 0.1.1 → 0.2.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 (598) hide show
  1. package/.parachute/module.json +124 -8
  2. package/LICENSE +2 -16
  3. package/README.md +118 -166
  4. package/package.json +32 -43
  5. package/scripts/spawn-agent.ts +371 -0
  6. package/src/_parked/interactive-spawn.test.ts +324 -0
  7. package/src/_parked/interactive-spawn.ts +701 -0
  8. package/src/agent-defs.test.ts +1504 -0
  9. package/src/agent-defs.ts +1702 -0
  10. package/src/agent-mcp-config.test.ts +115 -0
  11. package/src/agent-mcp-config.ts +115 -0
  12. package/src/agents.test.ts +360 -0
  13. package/src/agents.ts +379 -0
  14. package/src/auth.test.ts +46 -0
  15. package/src/auth.ts +140 -0
  16. package/src/backends/attached-queue.test.ts +376 -0
  17. package/src/backends/attached-queue.ts +372 -0
  18. package/src/backends/programmatic.test.ts +1715 -0
  19. package/src/backends/programmatic.ts +927 -0
  20. package/src/backends/registry.test.ts +1494 -0
  21. package/src/backends/registry.ts +1202 -0
  22. package/src/backends/stream-json.test.ts +570 -0
  23. package/src/backends/stream-json.ts +392 -0
  24. package/src/backends/types.ts +223 -0
  25. package/src/bridge.ts +417 -0
  26. package/src/channel-backend-wiring.test.ts +237 -0
  27. package/src/credentials.test.ts +274 -0
  28. package/src/credentials.ts +380 -0
  29. package/src/cron.test.ts +342 -0
  30. package/src/cron.ts +380 -0
  31. package/src/daemon-agent-def-api.test.ts +166 -0
  32. package/src/daemon-agent-defs-api.test.ts +953 -0
  33. package/src/daemon-agent-env-api.test.ts +338 -0
  34. package/src/daemon-attached-queue-store.test.ts +65 -0
  35. package/src/daemon-config-api.test.ts +962 -0
  36. package/src/daemon-jobs-api.test.ts +271 -0
  37. package/src/daemon-vault-chat.test.ts +250 -0
  38. package/src/daemon.test.ts +746 -0
  39. package/src/daemon.ts +3314 -0
  40. package/src/def-vaults.test.ts +136 -0
  41. package/src/def-vaults.ts +165 -0
  42. package/src/delivery-state.test.ts +110 -0
  43. package/src/delivery-state.ts +154 -0
  44. package/src/effective-env.test.ts +114 -0
  45. package/src/effective-env.ts +184 -0
  46. package/src/env-compat.ts +39 -0
  47. package/src/grants.test.ts +638 -0
  48. package/src/grants.ts +675 -0
  49. package/src/hub-jwt.test.ts +161 -0
  50. package/src/hub-jwt.ts +182 -0
  51. package/src/jobs.test.ts +245 -0
  52. package/src/jobs.ts +266 -0
  53. package/src/mcp-http.test.ts +265 -0
  54. package/src/mcp-http.ts +771 -0
  55. package/src/mint-token.test.ts +152 -0
  56. package/src/mint-token.ts +139 -0
  57. package/src/module-manifest.test.ts +158 -0
  58. package/src/oauth-discovery.ts +134 -0
  59. package/src/programmatic-wiring.test.ts +838 -0
  60. package/src/registry.test.ts +227 -0
  61. package/src/registry.ts +228 -0
  62. package/src/resolve-port.test.ts +64 -0
  63. package/src/routing.test.ts +184 -0
  64. package/src/routing.ts +76 -0
  65. package/src/runner.test.ts +506 -0
  66. package/src/runner.ts +255 -0
  67. package/src/sandbox/config.test.ts +150 -0
  68. package/src/sandbox/config.ts +102 -0
  69. package/src/sandbox/egress.test.ts +113 -0
  70. package/src/sandbox/egress.ts +123 -0
  71. package/src/sandbox/index.ts +180 -0
  72. package/src/sandbox/live-seatbelt.test.ts +277 -0
  73. package/src/sandbox/mounts.test.ts +154 -0
  74. package/src/sandbox/mounts.ts +133 -0
  75. package/src/sandbox/sandbox.test.ts +168 -0
  76. package/src/sandbox/types.ts +382 -0
  77. package/src/services-manifest.test.ts +106 -0
  78. package/src/services-manifest.ts +95 -0
  79. package/src/spa-serve.test.ts +116 -0
  80. package/src/spa-serve.ts +116 -0
  81. package/src/spawn-agent-cli.test.ts +172 -0
  82. package/src/spawn-agent.test.ts +1218 -0
  83. package/src/spawn-agent.ts +569 -0
  84. package/src/spawn-deps.test.ts +54 -0
  85. package/src/spawn-deps.ts +166 -0
  86. package/src/telegram/api.ts +153 -0
  87. package/src/terminal-assets.test.ts +50 -0
  88. package/src/terminal-assets.ts +79 -0
  89. package/src/terminal-ui.ts +305 -0
  90. package/src/terminal.test.ts +530 -0
  91. package/src/terminal.ts +458 -0
  92. package/src/transport.ts +270 -0
  93. package/src/transports/http-ui.test.ts +455 -0
  94. package/src/transports/http-ui.ts +201 -0
  95. package/src/transports/telegram.test.ts +174 -0
  96. package/src/transports/telegram.ts +426 -0
  97. package/src/transports/vault.test.ts +2011 -0
  98. package/src/transports/vault.ts +1790 -0
  99. package/src/ui-kit.test.ts +178 -0
  100. package/src/ui-kit.ts +402 -0
  101. package/tsconfig.json +8 -14
  102. package/web/ui/tsconfig.json +2 -1
  103. package/.claude/scheduled_tasks.lock +0 -1
  104. package/.claude/settings.json +0 -5
  105. package/.claude/skills/add-atomic-chat-tool/SKILL.md +0 -243
  106. package/.claude/skills/add-atomic-chat-tool/atomic-chat-mcp-stdio.ts +0 -229
  107. package/.claude/skills/add-codex/SKILL.md +0 -161
  108. package/.claude/skills/add-dashboard/SKILL.md +0 -138
  109. package/.claude/skills/add-dashboard/resources/dashboard-pusher.ts +0 -495
  110. package/.claude/skills/add-emacs/SKILL.md +0 -296
  111. package/.claude/skills/add-gcal-tool/SKILL.md +0 -210
  112. package/.claude/skills/add-gchat/REMOVE.md +0 -6
  113. package/.claude/skills/add-gchat/SKILL.md +0 -92
  114. package/.claude/skills/add-gchat/VERIFY.md +0 -3
  115. package/.claude/skills/add-github/REMOVE.md +0 -6
  116. package/.claude/skills/add-github/SKILL.md +0 -148
  117. package/.claude/skills/add-github/VERIFY.md +0 -3
  118. package/.claude/skills/add-gmail-tool/SKILL.md +0 -229
  119. package/.claude/skills/add-imessage/REMOVE.md +0 -6
  120. package/.claude/skills/add-imessage/SKILL.md +0 -113
  121. package/.claude/skills/add-imessage/VERIFY.md +0 -3
  122. package/.claude/skills/add-karpathy-llm-wiki/SKILL.md +0 -110
  123. package/.claude/skills/add-karpathy-llm-wiki/llm-wiki.md +0 -75
  124. package/.claude/skills/add-linear/REMOVE.md +0 -6
  125. package/.claude/skills/add-linear/SKILL.md +0 -168
  126. package/.claude/skills/add-linear/VERIFY.md +0 -3
  127. package/.claude/skills/add-macos-statusbar/SKILL.md +0 -133
  128. package/.claude/skills/add-macos-statusbar/add/src/statusbar.swift +0 -147
  129. package/.claude/skills/add-matrix/REMOVE.md +0 -6
  130. package/.claude/skills/add-matrix/SKILL.md +0 -148
  131. package/.claude/skills/add-matrix/VERIFY.md +0 -3
  132. package/.claude/skills/add-ollama-provider/SKILL.md +0 -179
  133. package/.claude/skills/add-ollama-tool/SKILL.md +0 -193
  134. package/.claude/skills/add-opencode/SKILL.md +0 -229
  135. package/.claude/skills/add-parallel/SKILL.md +0 -290
  136. package/.claude/skills/add-resend/REMOVE.md +0 -6
  137. package/.claude/skills/add-resend/SKILL.md +0 -93
  138. package/.claude/skills/add-resend/VERIFY.md +0 -3
  139. package/.claude/skills/add-signal/REMOVE.md +0 -13
  140. package/.claude/skills/add-signal/SKILL.md +0 -318
  141. package/.claude/skills/add-signal/VERIFY.md +0 -5
  142. package/.claude/skills/add-slack/REMOVE.md +0 -6
  143. package/.claude/skills/add-slack/SKILL.md +0 -112
  144. package/.claude/skills/add-slack/VERIFY.md +0 -3
  145. package/.claude/skills/add-teams/REMOVE.md +0 -6
  146. package/.claude/skills/add-teams/SKILL.md +0 -207
  147. package/.claude/skills/add-teams/VERIFY.md +0 -3
  148. package/.claude/skills/add-vercel/SKILL.md +0 -147
  149. package/.claude/skills/add-vercel/container-skills/vercel-cli/SKILL.md +0 -103
  150. package/.claude/skills/add-webex/REMOVE.md +0 -6
  151. package/.claude/skills/add-webex/SKILL.md +0 -88
  152. package/.claude/skills/add-webex/VERIFY.md +0 -3
  153. package/.claude/skills/add-wechat/REMOVE.md +0 -49
  154. package/.claude/skills/add-wechat/SKILL.md +0 -170
  155. package/.claude/skills/add-wechat/scripts/wire-dm.ts +0 -172
  156. package/.claude/skills/add-whatsapp/SKILL.md +0 -264
  157. package/.claude/skills/add-whatsapp-cloud/REMOVE.md +0 -6
  158. package/.claude/skills/add-whatsapp-cloud/SKILL.md +0 -95
  159. package/.claude/skills/add-whatsapp-cloud/VERIFY.md +0 -3
  160. package/.claude/skills/claw/SKILL.md +0 -131
  161. package/.claude/skills/claw/scripts/claw +0 -374
  162. package/.claude/skills/convert-to-apple-container/SKILL.md +0 -212
  163. package/.claude/skills/customize/SKILL.md +0 -110
  164. package/.claude/skills/debug/SKILL.md +0 -349
  165. package/.claude/skills/get-qodo-rules/SKILL.md +0 -122
  166. package/.claude/skills/get-qodo-rules/references/output-format.md +0 -41
  167. package/.claude/skills/get-qodo-rules/references/pagination.md +0 -33
  168. package/.claude/skills/get-qodo-rules/references/repository-scope.md +0 -26
  169. package/.claude/skills/init-first-agent/SKILL.md +0 -120
  170. package/.claude/skills/init-onecli/SKILL.md +0 -270
  171. package/.claude/skills/manage-channels/SKILL.md +0 -87
  172. package/.claude/skills/manage-mounts/SKILL.md +0 -47
  173. package/.claude/skills/migrate-from-openclaw/MIGRATE_CRONS.md +0 -100
  174. package/.claude/skills/migrate-from-openclaw/SKILL.md +0 -447
  175. package/.claude/skills/migrate-from-openclaw/scripts/discover-openclaw.ts +0 -734
  176. package/.claude/skills/migrate-from-openclaw/scripts/extract-channel-credentials.ts +0 -476
  177. package/.claude/skills/migrate-nanoclaw/SKILL.md +0 -484
  178. package/.claude/skills/migrate-nanoclaw/diagnostics.md +0 -51
  179. package/.claude/skills/qodo-pr-resolver/SKILL.md +0 -326
  180. package/.claude/skills/qodo-pr-resolver/resources/providers.md +0 -329
  181. package/.claude/skills/update-nanoclaw/SKILL.md +0 -243
  182. package/.claude/skills/update-nanoclaw/diagnostics.md +0 -48
  183. package/.claude/skills/update-skills/SKILL.md +0 -130
  184. package/.claude/skills/use-native-credential-proxy/SKILL.md +0 -167
  185. package/.claude/skills/x-integration/SKILL.md +0 -417
  186. package/.claude/skills/x-integration/agent.ts +0 -243
  187. package/.claude/skills/x-integration/host.ts +0 -155
  188. package/.claude/skills/x-integration/lib/browser.ts +0 -148
  189. package/.claude/skills/x-integration/lib/config.ts +0 -62
  190. package/.claude/skills/x-integration/scripts/like.ts +0 -56
  191. package/.claude/skills/x-integration/scripts/post.ts +0 -66
  192. package/.claude/skills/x-integration/scripts/quote.ts +0 -80
  193. package/.claude/skills/x-integration/scripts/reply.ts +0 -74
  194. package/.claude/skills/x-integration/scripts/retweet.ts +0 -62
  195. package/.claude/skills/x-integration/scripts/setup.ts +0 -87
  196. package/.github/CODEOWNERS +0 -10
  197. package/.github/PULL_REQUEST_TEMPLATE.md +0 -18
  198. package/.github/workflows/bump-version.yml +0 -35
  199. package/.github/workflows/ci.yml +0 -39
  200. package/.github/workflows/label-pr.yml +0 -40
  201. package/.github/workflows/update-tokens.yml +0 -43
  202. package/.husky/pre-commit +0 -1
  203. package/.mcp.json +0 -3
  204. package/.nvmrc +0 -1
  205. package/.prettierrc +0 -4
  206. package/CHANGELOG.md +0 -221
  207. package/CLAUDE.md +0 -307
  208. package/CODE_OF_CONDUCT.md +0 -128
  209. package/CONTRIBUTING.md +0 -159
  210. package/CONTRIBUTORS.md +0 -26
  211. package/LICENSE-NANOCLAW-MIT +0 -21
  212. package/README_ja.md +0 -194
  213. package/README_zh.md +0 -194
  214. package/assets/nanoclaw-favicon.png +0 -0
  215. package/assets/nanoclaw-icon.png +0 -0
  216. package/assets/nanoclaw-logo-dark.png +0 -0
  217. package/assets/nanoclaw-logo.png +0 -0
  218. package/assets/nanoclaw-profile.jpeg +0 -0
  219. package/assets/nanoclaw-sales.png +0 -0
  220. package/assets/social-preview.jpg +0 -0
  221. package/config-examples/mount-allowlist.json +0 -25
  222. package/container/.dockerignore +0 -2
  223. package/container/CLAUDE.md +0 -21
  224. package/container/Dockerfile +0 -121
  225. package/container/agent-runner/bun.lock +0 -243
  226. package/container/agent-runner/package.json +0 -22
  227. package/container/agent-runner/scripts/sdk-signal-probe.ts +0 -169
  228. package/container/agent-runner/src/config.ts +0 -55
  229. package/container/agent-runner/src/db/connection.ts +0 -267
  230. package/container/agent-runner/src/db/index.ts +0 -20
  231. package/container/agent-runner/src/db/messages-in.ts +0 -138
  232. package/container/agent-runner/src/db/messages-out.ts +0 -143
  233. package/container/agent-runner/src/db/session-routing.ts +0 -30
  234. package/container/agent-runner/src/db/session-state.test.ts +0 -100
  235. package/container/agent-runner/src/db/session-state.ts +0 -79
  236. package/container/agent-runner/src/destinations.ts +0 -135
  237. package/container/agent-runner/src/formatter.test.ts +0 -167
  238. package/container/agent-runner/src/formatter.ts +0 -260
  239. package/container/agent-runner/src/index.ts +0 -110
  240. package/container/agent-runner/src/integration.test.ts +0 -121
  241. package/container/agent-runner/src/mcp-tools/agents.instructions.md +0 -26
  242. package/container/agent-runner/src/mcp-tools/agents.ts +0 -66
  243. package/container/agent-runner/src/mcp-tools/core.instructions.md +0 -27
  244. package/container/agent-runner/src/mcp-tools/core.ts +0 -262
  245. package/container/agent-runner/src/mcp-tools/index.ts +0 -22
  246. package/container/agent-runner/src/mcp-tools/interactive.instructions.md +0 -22
  247. package/container/agent-runner/src/mcp-tools/interactive.ts +0 -169
  248. package/container/agent-runner/src/mcp-tools/scheduling.instructions.md +0 -40
  249. package/container/agent-runner/src/mcp-tools/scheduling.ts +0 -299
  250. package/container/agent-runner/src/mcp-tools/self-mod.instructions.md +0 -25
  251. package/container/agent-runner/src/mcp-tools/self-mod.ts +0 -120
  252. package/container/agent-runner/src/mcp-tools/server.ts +0 -54
  253. package/container/agent-runner/src/mcp-tools/types.ts +0 -6
  254. package/container/agent-runner/src/poll-loop.test.ts +0 -248
  255. package/container/agent-runner/src/poll-loop.ts +0 -437
  256. package/container/agent-runner/src/providers/claude.ts +0 -379
  257. package/container/agent-runner/src/providers/factory.test.ts +0 -19
  258. package/container/agent-runner/src/providers/factory.ts +0 -13
  259. package/container/agent-runner/src/providers/index.ts +0 -6
  260. package/container/agent-runner/src/providers/mock.ts +0 -77
  261. package/container/agent-runner/src/providers/provider-registry.ts +0 -33
  262. package/container/agent-runner/src/providers/types.ts +0 -82
  263. package/container/agent-runner/src/scheduling/task-script.ts +0 -121
  264. package/container/agent-runner/src/timezone.test.ts +0 -93
  265. package/container/agent-runner/src/timezone.ts +0 -107
  266. package/container/agent-runner/tsconfig.json +0 -14
  267. package/container/build.sh +0 -48
  268. package/container/entrypoint.sh +0 -16
  269. package/container/skills/agent-browser/SKILL.md +0 -159
  270. package/container/skills/frontend-engineer/SKILL.md +0 -157
  271. package/container/skills/self-customize/SKILL.md +0 -87
  272. package/container/skills/slack-formatting/SKILL.md +0 -94
  273. package/container/skills/vercel-cli/SKILL.md +0 -111
  274. package/container/skills/welcome/SKILL.md +0 -85
  275. package/docs/APPLE-CONTAINER-NETWORKING.md +0 -90
  276. package/docs/BRANCH-FORK-MAINTENANCE.md +0 -81
  277. package/docs/README.md +0 -25
  278. package/docs/SDK_DEEP_DIVE.md +0 -643
  279. package/docs/SECURITY.md +0 -162
  280. package/docs/agent-runner-details.md +0 -749
  281. package/docs/api-details.md +0 -365
  282. package/docs/architecture-diagram.html +0 -422
  283. package/docs/architecture-diagram.md +0 -215
  284. package/docs/architecture.md +0 -751
  285. package/docs/audit/2026-04-30-channel-endpoint-audit.md +0 -36
  286. package/docs/build-and-runtime.md +0 -80
  287. package/docs/cross-mount-stress/README.md +0 -112
  288. package/docs/cross-mount-stress/container-writer-retry.mjs +0 -55
  289. package/docs/cross-mount-stress/container-writer-slow.mjs +0 -42
  290. package/docs/cross-mount-stress/container-writer.mjs +0 -47
  291. package/docs/cross-mount-stress/host-writer-retry.mjs +0 -55
  292. package/docs/cross-mount-stress/host-writer-slow.mjs +0 -43
  293. package/docs/cross-mount-stress/host-writer.mjs +0 -47
  294. package/docs/db-central.md +0 -316
  295. package/docs/db-session.md +0 -183
  296. package/docs/db.md +0 -119
  297. package/docs/design/2026-04-29-vault-management-ui.md +0 -231
  298. package/docs/design/2026-04-30-channel-wiring-rework.md +0 -234
  299. package/docs/design/2026-05-01-channel-wiring-approvals-deep-dive.md +0 -272
  300. package/docs/design/2026-05-02-channel-policy-and-approval-routing.md +0 -250
  301. package/docs/docker-sandboxes.md +0 -359
  302. package/docs/isolation-model.md +0 -88
  303. package/docs/ollama.md +0 -79
  304. package/docs/parachute-integration.md +0 -109
  305. package/docs/post-night-rebirth-reflections.md +0 -151
  306. package/eslint.config.js +0 -32
  307. package/pnpm-workspace.yaml +0 -8
  308. package/repo-tokens/README.md +0 -113
  309. package/repo-tokens/action.yml +0 -186
  310. package/repo-tokens/badge.svg +0 -23
  311. package/repo-tokens/examples/green.svg +0 -14
  312. package/repo-tokens/examples/red.svg +0 -14
  313. package/repo-tokens/examples/yellow-green.svg +0 -14
  314. package/repo-tokens/examples/yellow.svg +0 -14
  315. package/scripts/chat.ts +0 -101
  316. package/scripts/cleanup-sessions.sh +0 -150
  317. package/scripts/init-cli-agent.ts +0 -171
  318. package/scripts/init-first-agent.ts +0 -377
  319. package/scripts/parachute.ts +0 -158
  320. package/scripts/run-migrations.ts +0 -105
  321. package/scripts/sanity-live-poll.ts +0 -95
  322. package/scripts/seed-discord.ts +0 -79
  323. package/scripts/test-v2-agent.ts +0 -106
  324. package/scripts/test-v2-channel-e2e.ts +0 -265
  325. package/scripts/test-v2-host.ts +0 -184
  326. package/src/channels/adapter.ts +0 -214
  327. package/src/channels/ask-question.ts +0 -46
  328. package/src/channels/channel-registry.test.ts +0 -421
  329. package/src/channels/channel-registry.ts +0 -313
  330. package/src/channels/chat-sdk-bridge.test.ts +0 -84
  331. package/src/channels/chat-sdk-bridge.ts +0 -652
  332. package/src/channels/cli.ts +0 -276
  333. package/src/channels/discord.ts +0 -90
  334. package/src/channels/index.ts +0 -17
  335. package/src/channels/telegram-markdown-sanitize.test.ts +0 -78
  336. package/src/channels/telegram-markdown-sanitize.ts +0 -55
  337. package/src/channels/telegram-pairing.test.ts +0 -254
  338. package/src/channels/telegram-pairing.ts +0 -339
  339. package/src/channels/telegram.ts +0 -279
  340. package/src/channels/trust-hint.test.ts +0 -48
  341. package/src/channels/trust-hint.ts +0 -75
  342. package/src/claude-md-compose.migrate.test.ts +0 -64
  343. package/src/claude-md-compose.ts +0 -205
  344. package/src/command-gate.ts +0 -63
  345. package/src/config.test.ts +0 -93
  346. package/src/config.ts +0 -108
  347. package/src/container-config.ts +0 -167
  348. package/src/container-runner.test.ts +0 -32
  349. package/src/container-runner.ts +0 -576
  350. package/src/container-runtime.test.ts +0 -169
  351. package/src/container-runtime.ts +0 -92
  352. package/src/db/_bun-sqlite-shim.ts +0 -88
  353. package/src/db/agent-activity.test.ts +0 -155
  354. package/src/db/agent-activity.ts +0 -121
  355. package/src/db/agent-groups.ts +0 -77
  356. package/src/db/connection.migrate.test.ts +0 -143
  357. package/src/db/connection.ts +0 -224
  358. package/src/db/db-v2.test.ts +0 -440
  359. package/src/db/dropped-messages.ts +0 -44
  360. package/src/db/index.ts +0 -40
  361. package/src/db/messaging-groups.ts +0 -252
  362. package/src/db/migrations/001-initial.ts +0 -112
  363. package/src/db/migrations/002-chat-sdk-state.ts +0 -36
  364. package/src/db/migrations/008-dropped-messages.ts +0 -27
  365. package/src/db/migrations/009-drop-pending-credentials.ts +0 -13
  366. package/src/db/migrations/010-engage-modes.ts +0 -103
  367. package/src/db/migrations/011-pending-sender-approvals.ts +0 -40
  368. package/src/db/migrations/012-channel-registration.ts +0 -48
  369. package/src/db/migrations/013-approval-render-metadata.ts +0 -27
  370. package/src/db/migrations/014-secrets.ts +0 -44
  371. package/src/db/migrations/015-secrets-drop-host-pattern.ts +0 -18
  372. package/src/db/migrations/016-secret-assignments.ts +0 -30
  373. package/src/db/migrations/017-agent-activity.ts +0 -40
  374. package/src/db/migrations/018-oauth-app-configs.ts +0 -34
  375. package/src/db/migrations/019-oauth-app-connections.ts +0 -48
  376. package/src/db/migrations/020-agent-app-connections.ts +0 -28
  377. package/src/db/migrations/021-pending-oauth-states.ts +0 -35
  378. package/src/db/migrations/022-app-connections-provider.ts +0 -25
  379. package/src/db/migrations/023-agent-group-secret-mode.test.ts +0 -124
  380. package/src/db/migrations/023-agent-group-secret-mode.ts +0 -65
  381. package/src/db/migrations/024-collapse-approvals.test.ts +0 -249
  382. package/src/db/migrations/024-collapse-approvals.ts +0 -182
  383. package/src/db/migrations/025-secret-mode-check.test.ts +0 -155
  384. package/src/db/migrations/025-secret-mode-check.ts +0 -49
  385. package/src/db/migrations/026-user-dms-bot-id.test.ts +0 -116
  386. package/src/db/migrations/026-user-dms-bot-id.ts +0 -54
  387. package/src/db/migrations/027-provider-credentials.ts +0 -41
  388. package/src/db/migrations/_test-helpers.ts +0 -41
  389. package/src/db/migrations/index.ts +0 -127
  390. package/src/db/migrations/module-agent-to-agent-destinations.ts +0 -84
  391. package/src/db/migrations/module-approvals-pending-approvals.ts +0 -42
  392. package/src/db/migrations/module-approvals-title-options.ts +0 -40
  393. package/src/db/schema.ts +0 -258
  394. package/src/db/session-db.test.ts +0 -93
  395. package/src/db/session-db.ts +0 -325
  396. package/src/db/sessions.ts +0 -241
  397. package/src/delivery.test.ts +0 -148
  398. package/src/delivery.ts +0 -445
  399. package/src/env.ts +0 -74
  400. package/src/group-folder.test.ts +0 -35
  401. package/src/group-folder.ts +0 -44
  402. package/src/group-init.ts +0 -92
  403. package/src/host-core.test.ts +0 -456
  404. package/src/host-sweep.test.ts +0 -146
  405. package/src/host-sweep.ts +0 -287
  406. package/src/index.ts +0 -227
  407. package/src/install-slug.ts +0 -33
  408. package/src/log.test.ts +0 -81
  409. package/src/log.ts +0 -117
  410. package/src/mcp/http.ts +0 -72
  411. package/src/mcp/server.ts +0 -92
  412. package/src/mcp/stdio.ts +0 -51
  413. package/src/mcp/tools/activity.ts +0 -88
  414. package/src/mcp/tools/agent-groups.ts +0 -183
  415. package/src/mcp/tools/approvals.ts +0 -122
  416. package/src/mcp/tools/channels.ts +0 -199
  417. package/src/mcp/tools/index.ts +0 -27
  418. package/src/mcp/tools/oauth.ts +0 -48
  419. package/src/mcp/tools/secrets.ts +0 -169
  420. package/src/mcp/tools/sessions.ts +0 -135
  421. package/src/mcp/types.ts +0 -51
  422. package/src/modules/agent-to-agent/agent-route.test.ts +0 -46
  423. package/src/modules/agent-to-agent/agent-route.ts +0 -223
  424. package/src/modules/agent-to-agent/create-agent.ts +0 -127
  425. package/src/modules/agent-to-agent/db/agent-destinations.ts +0 -135
  426. package/src/modules/agent-to-agent/index.ts +0 -22
  427. package/src/modules/agent-to-agent/write-destinations.ts +0 -59
  428. package/src/modules/approvals/agent.md +0 -45
  429. package/src/modules/approvals/index.ts +0 -21
  430. package/src/modules/approvals/picks.test.ts +0 -291
  431. package/src/modules/approvals/primitive.ts +0 -279
  432. package/src/modules/approvals/project.md +0 -27
  433. package/src/modules/approvals/response-handler.ts +0 -87
  434. package/src/modules/index.ts +0 -24
  435. package/src/modules/interactive/agent.md +0 -21
  436. package/src/modules/interactive/index.ts +0 -69
  437. package/src/modules/interactive/project.md +0 -12
  438. package/src/modules/mount-security/index.ts +0 -448
  439. package/src/modules/mount-security/migrate.test.ts +0 -91
  440. package/src/modules/permissions/access.ts +0 -28
  441. package/src/modules/permissions/channel-approval.test.ts +0 -389
  442. package/src/modules/permissions/channel-approval.ts +0 -188
  443. package/src/modules/permissions/db/agent-group-members.ts +0 -44
  444. package/src/modules/permissions/db/pending-channel-approvals.test.ts +0 -86
  445. package/src/modules/permissions/db/pending-channel-approvals.ts +0 -66
  446. package/src/modules/permissions/db/pending-sender-approvals.ts +0 -60
  447. package/src/modules/permissions/db/user-dms.ts +0 -58
  448. package/src/modules/permissions/db/user-roles.ts +0 -85
  449. package/src/modules/permissions/db/users.ts +0 -38
  450. package/src/modules/permissions/index.ts +0 -421
  451. package/src/modules/permissions/permissions.test.ts +0 -358
  452. package/src/modules/permissions/sender-approval.test.ts +0 -470
  453. package/src/modules/permissions/sender-approval.ts +0 -165
  454. package/src/modules/permissions/user-dm.ts +0 -200
  455. package/src/modules/provider-credentials/db.ts +0 -121
  456. package/src/modules/provider-credentials/index.ts +0 -12
  457. package/src/modules/provider-credentials/spawn.test.ts +0 -206
  458. package/src/modules/provider-credentials/spawn.ts +0 -114
  459. package/src/modules/scheduling/actions.ts +0 -113
  460. package/src/modules/scheduling/db.test.ts +0 -282
  461. package/src/modules/scheduling/db.ts +0 -148
  462. package/src/modules/scheduling/index.ts +0 -34
  463. package/src/modules/scheduling/recurrence.test.ts +0 -98
  464. package/src/modules/scheduling/recurrence.ts +0 -54
  465. package/src/modules/self-mod/agent.md +0 -30
  466. package/src/modules/self-mod/apply.ts +0 -85
  467. package/src/modules/self-mod/index.ts +0 -30
  468. package/src/modules/self-mod/project.md +0 -39
  469. package/src/modules/self-mod/request.ts +0 -91
  470. package/src/modules/typing/index.ts +0 -165
  471. package/src/oauth/agent-app-connections.ts +0 -103
  472. package/src/oauth/app-configs.test.ts +0 -64
  473. package/src/oauth/app-configs.ts +0 -114
  474. package/src/oauth/app-connections.test.ts +0 -109
  475. package/src/oauth/app-connections.ts +0 -178
  476. package/src/oauth/crypto.ts +0 -56
  477. package/src/oauth/flow.ts +0 -104
  478. package/src/oauth/providers/google.test.ts +0 -38
  479. package/src/oauth/providers/google.ts +0 -46
  480. package/src/oauth/providers/index.ts +0 -48
  481. package/src/oauth/state-store.test.ts +0 -54
  482. package/src/oauth/state-store.ts +0 -93
  483. package/src/parachute/README.md +0 -27
  484. package/src/parachute/create-agent.test.ts +0 -83
  485. package/src/parachute/create-agent.ts +0 -122
  486. package/src/parachute/group-status.test.ts +0 -165
  487. package/src/parachute/group-status.ts +0 -136
  488. package/src/parachute/types.ts +0 -41
  489. package/src/parachute/vault-mcp.test.ts +0 -251
  490. package/src/parachute/vault-mcp.ts +0 -232
  491. package/src/platform-id.test.ts +0 -104
  492. package/src/platform-id.ts +0 -109
  493. package/src/providers/index.ts +0 -6
  494. package/src/providers/provider-container-registry.ts +0 -58
  495. package/src/response-registry.ts +0 -45
  496. package/src/router.ts +0 -530
  497. package/src/secrets/crypto.test.ts +0 -45
  498. package/src/secrets/crypto.ts +0 -55
  499. package/src/secrets/index.ts +0 -355
  500. package/src/secrets/master-key.ts +0 -70
  501. package/src/secrets/secrets.test.ts +0 -354
  502. package/src/session-manager.migrate.test.ts +0 -59
  503. package/src/session-manager.ts +0 -433
  504. package/src/startup-bootstrap.test.ts +0 -226
  505. package/src/startup-bootstrap.ts +0 -207
  506. package/src/state-sqlite.ts +0 -182
  507. package/src/timezone.test.ts +0 -64
  508. package/src/timezone.ts +0 -37
  509. package/src/types.ts +0 -230
  510. package/src/web/auth.test.ts +0 -335
  511. package/src/web/auth.ts +0 -214
  512. package/src/web/discord-validate.test.ts +0 -77
  513. package/src/web/discord-validate.ts +0 -88
  514. package/src/web/hub-discovery.test.ts +0 -98
  515. package/src/web/hub-discovery.ts +0 -69
  516. package/src/web/routes/activity.ts +0 -106
  517. package/src/web/routes/agent-provider.test.ts +0 -282
  518. package/src/web/routes/agent-provider.ts +0 -309
  519. package/src/web/routes/approvals.ts +0 -185
  520. package/src/web/routes/apps.ts +0 -434
  521. package/src/web/routes/channels-mg-detail.test.ts +0 -324
  522. package/src/web/routes/channels-mga-detail.test.ts +0 -425
  523. package/src/web/routes/channels.ts +0 -489
  524. package/src/web/routes/oauth-providers.ts +0 -42
  525. package/src/web/routes/secrets.test.ts +0 -175
  526. package/src/web/routes/secrets.ts +0 -282
  527. package/src/web/routes/sessions.ts +0 -123
  528. package/src/web/routes/settings.test.ts +0 -106
  529. package/src/web/routes/settings.ts +0 -247
  530. package/src/web/routes/setup-status.ts +0 -205
  531. package/src/web/routes/vaults.test.ts +0 -389
  532. package/src/web/routes/vaults.ts +0 -225
  533. package/src/web/server-version.test.ts +0 -16
  534. package/src/web/server.ts +0 -1003
  535. package/src/web/services-manifest.test.ts +0 -120
  536. package/src/web/services-manifest.ts +0 -61
  537. package/src/web/static-serve.test.ts +0 -255
  538. package/src/web/static-serve.ts +0 -104
  539. package/src/web/telegram-validate.test.ts +0 -116
  540. package/src/web/telegram-validate.ts +0 -107
  541. package/src/web/vault-proxy.test.ts +0 -214
  542. package/src/web/vault-proxy.ts +0 -120
  543. package/src/web/wire-channel.ts +0 -181
  544. package/src/webhook-server.ts +0 -134
  545. package/vitest.config.ts +0 -18
  546. package/web/README.md +0 -63
  547. package/web/ui/index.html +0 -13
  548. package/web/ui/package.json +0 -35
  549. package/web/ui/pnpm-lock.yaml +0 -2164
  550. package/web/ui/scripts/verify-base.mjs +0 -31
  551. package/web/ui/src/App.tsx +0 -88
  552. package/web/ui/src/components/ActivityFeed.tsx +0 -444
  553. package/web/ui/src/components/AgentGroupPicker.tsx +0 -263
  554. package/web/ui/src/components/AgentProviderCards.tsx +0 -220
  555. package/web/ui/src/components/CredentialForm.tsx +0 -214
  556. package/web/ui/src/components/ScopeGrants.tsx +0 -74
  557. package/web/ui/src/components/StatusDot.tsx +0 -43
  558. package/web/ui/src/components/VaultPicker.tsx +0 -127
  559. package/web/ui/src/components/setup/AdapterInstallStep.tsx +0 -178
  560. package/web/ui/src/components/setup/AgentGroupStep.tsx +0 -43
  561. package/web/ui/src/components/setup/ChannelPickStep.tsx +0 -74
  562. package/web/ui/src/components/setup/DoneStep.tsx +0 -49
  563. package/web/ui/src/components/setup/PrereqStep.tsx +0 -129
  564. package/web/ui/src/components/setup/TestConnectionStep.tsx +0 -108
  565. package/web/ui/src/components/setup/TestMessageStep.tsx +0 -104
  566. package/web/ui/src/components/setup/WireChannelStep.tsx +0 -166
  567. package/web/ui/src/components/setup/types.ts +0 -105
  568. package/web/ui/src/lib/api.test.ts +0 -410
  569. package/web/ui/src/lib/api.ts +0 -1210
  570. package/web/ui/src/lib/auth.test.ts +0 -139
  571. package/web/ui/src/lib/auth.ts +0 -348
  572. package/web/ui/src/lib/channel-adapters.ts +0 -136
  573. package/web/ui/src/main.tsx +0 -19
  574. package/web/ui/src/routes/ApprovalsList.tsx +0 -294
  575. package/web/ui/src/routes/Apps.tsx +0 -613
  576. package/web/ui/src/routes/ChannelWireDetail.test.tsx +0 -233
  577. package/web/ui/src/routes/ChannelWireDetail.tsx +0 -403
  578. package/web/ui/src/routes/ChannelsList.tsx +0 -158
  579. package/web/ui/src/routes/GroupDetail.tsx +0 -755
  580. package/web/ui/src/routes/GroupList.tsx +0 -187
  581. package/web/ui/src/routes/MessagingGroupDetail.test.tsx +0 -233
  582. package/web/ui/src/routes/MessagingGroupDetail.tsx +0 -306
  583. package/web/ui/src/routes/NewGroupWizard.tsx +0 -390
  584. package/web/ui/src/routes/OAuthCallback.tsx +0 -56
  585. package/web/ui/src/routes/SecretsList.tsx +0 -921
  586. package/web/ui/src/routes/SessionsList.tsx +0 -220
  587. package/web/ui/src/routes/SettingsAgentProvider.tsx +0 -109
  588. package/web/ui/src/routes/SettingsApprovals.tsx +0 -234
  589. package/web/ui/src/routes/SetupWizard.tsx +0 -219
  590. package/web/ui/src/routes/VaultDetail.test.tsx +0 -361
  591. package/web/ui/src/routes/VaultDetail.tsx +0 -960
  592. package/web/ui/src/routes/VaultsList.tsx +0 -295
  593. package/web/ui/src/routes/WireChannelPage.tsx +0 -413
  594. package/web/ui/src/styles.css +0 -608
  595. package/web/ui/src/test/setup.ts +0 -23
  596. package/web/ui/src/vite-env.d.ts +0 -10
  597. package/web/ui/vite.config.ts +0 -34
  598. package/web/ui/vitest.config.ts +0 -25
@@ -1,231 +0,0 @@
1
- # Vault management UI
2
-
3
- **Status:** Design proposal · 2026-04-29 · paraclaw#38
4
-
5
- Aaron, while testing vault attach/detach (paraclaw#36/#37/#38): *"vault UI feels like a high priority. when we give people a host instance, we don't want them to have to use cli at all."*
6
-
7
- This doc proposes a first-class `/claw/vaults` admin surface that replaces "use the CLI" as the answer to: "what vaults do I have? which token is attached where? how do I rotate? how do I detach + revoke?"
8
-
9
- ## Goals
10
-
11
- - **Discover** — see every vault registered with the hub, in one place, without opening a terminal.
12
- - **Inspect** — for any vault, see all minted tokens (label, scopes, attached-to, last-used), without ever exposing plaintext.
13
- - **Mint** — create a new token with a chosen scope set, copy plaintext exactly once, save the assignment.
14
- - **Rotate / revoke** — revoke any token by id; revocation is one-way and obvious.
15
- - **Attach / detach** — attach a vault token to an agent group, detach with explicit choice between "keep token" (re-attach later) and "detach + revoke" (security default for retired groups).
16
- - **Refresh** — bypass the 30s discovery cache when the operator just installed a vault.
17
-
18
- ## Non-goals
19
-
20
- - Vault-side admin (creating vaults, configuring webhooks, importing Obsidian) — that stays in `parachute-vault` CLI / vault config UI. The paraclaw page is **agent-group-facing**: which vaults can my agents reach, with what permissions.
21
- - Migrating away from the `pvt_*` token model. Hub-issued JWT scope-narrowing (#234, just merged) is the future, but pvt_* tokens remain the storage shape for direct-attached vaults; the UI accepts both.
22
- - Backups, snapshotting, or vault data inspection (that's the vault's web UI, not paraclaw's).
23
-
24
- ## What lives where
25
-
26
- ### `/claw/vaults` — index page
27
-
28
- Table, one row per vault from `<hubOrigin>/.well-known/parachute.json`:
29
-
30
- | Vault name | URL | Version | Tokens | Attached groups | Actions |
31
- |---|---|---|---|---|---|
32
- | `default` | `https://hub.tail.../vault/default` | `0.4.7` | `3` | `2` | `▸ Manage` |
33
- | `work` | `https://hub.tail.../vault/work` | `0.4.7` | `1` | `1` | `▸ Manage` |
34
-
35
- Header controls: `[Refresh from hub]` button (clears `clearHubDiscoveryCache()` + re-fetches). Empty state when zero vaults: link to "How to install a vault" (parachute-vault README).
36
-
37
- ### `/claw/vaults/<name>` — detail page
38
-
39
- Three sections:
40
-
41
- **1. Tokens** — table from `GET /vault/<name>/tokens` (vault REST, admin-gated):
42
-
43
- | Label | Scopes | Attached to | Created | Last used | Actions |
44
- |---|---|---|---|---|---|
45
- | `claw-personal` | `vault:read vault:write` | `personal` | 2026-04-15 | 2026-04-29 | `Revoke` |
46
- | `claw-research` | `vault:read` | `research` | 2026-04-20 | 2026-04-28 | `Revoke` |
47
- | `claw-orphan` | `vault:admin` | *(none)* | 2026-04-10 | *(never)* | `Revoke` |
48
-
49
- Plaintext is **never** rendered after mint — the table shows label + scopes + group attachments + activity timestamps only. "Attached to" is computed paraclaw-side by walking each agent group's `parachute.json` and matching `tokenLabel` → token row.
50
-
51
- `Revoke` is a confirm-modal action. Confirmation copy explicitly says revocation is one-way.
52
-
53
- **2. Attached groups** — table of agent groups currently using this vault:
54
-
55
- | Group | Scope | Token label | Detach |
56
- |---|---|---|---|
57
- | `personal` | `vault:write` | `claw-personal` | `Detach…` |
58
- | `research` | `vault:read` | `claw-research` | `Detach…` |
59
-
60
- `Detach…` opens a modal with two buttons: `Keep token` (current behavior — `parachute.json` removed, `container.json` MCP entry removed, token stays live in vault) or `Detach + revoke` (the above + DELETE on the vault token endpoint). Secondary action button order is deliberate: **keep token is the default-cursor button**, since silent revoke can wedge unrelated callers; revoke is opt-in but one click away. (See § Detach revoke default below.)
61
-
62
- **3. Mint new token** — form:
63
-
64
- - **Label** — text input. Default `claw-<group-or-purpose>`. Validation: alphanumeric + dashes, 64 char max.
65
- - **Scopes** — multiselect with three rows:
66
- - `vault:read` (read notes, search, follow links)
67
- - `vault:write` (read + create / update / delete notes)
68
- - `vault:admin` (write + token mgmt + vault config)
69
- - …or one row per **named scope** if the vault is hub-routed: `vault:<name>:<verb>` (see § Scope formats below).
70
- - **Expires** — date picker, optional. Default never.
71
- - Submit → POST mint → server displays plaintext **once** in a copy-with-confirmation card, with a button to assign-to-group inline (skips a round-trip back to `/claw/groups/<id>/vault`).
72
-
73
- ## Admin auth model
74
-
75
- paraclaw mints, lists, and revokes vault tokens by calling vault HTTP endpoints directly — no CLI shell-out. Those endpoints are admin-gated (`vault:<name>:admin` scope), so paraclaw needs a credential that carries that scope at request time.
76
-
77
- **Chosen approach for v1: forward the operator's hub-issued session JWT.**
78
-
79
- Flow:
80
-
81
- 1. Operator hits `/claw/vaults` in their browser. Their session JWT was issued by the hub at portal sign-in.
82
- 2. The first time the operator navigates to a vault management surface, paraclaw checks the JWT for `vault:<name>:admin` on the targeted vault. If absent, paraclaw redirects to `/oauth/authorize?scope=vault:<name>:admin claw:admin …` and the hub prompts the operator to consent. The new JWT comes back via the existing OAuth callback.
83
- 3. Subsequent paraclaw → vault calls (mint / list / revoke) attach `Authorization: Bearer <operator-jwt>`. The vault validates against the hub's JWKS (same path it already uses for hub-issued JWTs per parachute-vault#234).
84
-
85
- Why this over the alternatives Aaron raised:
86
-
87
- - **Pasted setup-time admin token (Option A)** — adds a manual onboarding step ("paste this token") and creates a long-lived secret in paraclaw's secret store. Friction at install; recoverability problem if it leaks (rotate everything that uses it).
88
- - **paraclaw client_credentials grant (Option B)** — paraclaw acquires a JWT via DCR-issued credentials. More plumbing (paraclaw must register a hub client, store its client_secret, refresh JWTs). Audit trail loses the operator identity — actions show as "paraclaw" not "alice@example.com". Worth doing if a non-UI surface ever needs vault admin (cron job, webhook, etc.); not needed for v1, where every admin call originates from an operator click.
89
- - **Hub-issued JWT scoped to the operator's session (Option C, chosen)** — reuses the existing OAuth flow, attributes actions to the operator, expires automatically with the session, revoking the session revokes vault admin access. The one cost is the consent prompt the first time per vault; that's a feature for the security-conscious operator.
90
-
91
- Migration path: if multi-tenant cloud later needs a non-operator-bound caller (Aaron's planned cloud Tier 1/2 split), we add Option B alongside C — both auth modes can coexist on the vault side because they're both hub-issued JWTs differentiated only by the `sub` claim.
92
-
93
- ## Refresh story
94
-
95
- The 30s in-process cache in `src/web/hub-discovery.ts:36` is the right default for the picker (saves a hub round-trip on every group-detail page load), but the UI must be able to bypass it on demand:
96
-
97
- - **Explicit refresh button** on `/claw/vaults` calls a new endpoint `POST /api/vaults/refresh` that runs `clearHubDiscoveryCache()` + `fetchHubVaults()` + responds with the fresh list. Auth: `claw:read`.
98
- - **Implicit refresh** after a successful mint / revoke — the affected vault's tokens table re-fetches; nothing else needs invalidation since cache is keyed at vault-list level.
99
-
100
- That covers paraclaw#37's third suspect root (now closed; root was vault-side `cmdCreate` not calling `upsertService`, fixed in vault#208 — but the refresh button is good UX regardless).
101
-
102
- ## Token mint flow
103
-
104
- 1. Operator clicks `Mint` on `/claw/vaults/<name>`.
105
- 2. UI shows form (label + scopes + expires).
106
- 3. Submit → `POST /api/vaults/<name>/tokens` (new paraclaw endpoint, see § API surface) → paraclaw forwards the operator's hub JWT (see § Admin auth model) and POSTs `<vaultUrl>/tokens` directly. No CLI shell-out.
107
- 4. Response from vault: `{ token: "pvt_…", label, scopes, id, created_at }`. paraclaw passes plaintext through to the browser unmodified.
108
- 5. UI renders a one-time copy-card. Plaintext is held in component state, never persisted in a state store, never echoed back to a server log.
109
- 6. Operator either copies-and-closes (token saved server-side via the mint, plaintext never touches the disk) or clicks `Attach to group…` which lets them pick a target group inline; this triggers `POST /api/groups/<folder>/attach-vault` with the freshly-minted token (saving the round-trip).
110
-
111
- If the operator dismisses without copying: there is no recovery. The token is stored hashed in the vault's token store; plaintext is gone. UI surfaces a yellow banner: *"Token minted but not copied. Revoke it now and mint a new one if you need access."*
112
-
113
- ## Token rendering rules
114
-
115
- Inviolable across every surface:
116
-
117
- | Field | Shown? | Notes |
118
- |---|---|---|
119
- | `pvt_…` plaintext | Once, on mint | Held in component state, copy-button only |
120
- | Token id (`t_…`) | Yes, always | Used for revoke-by-id |
121
- | Label | Yes, always | Operator-chosen identifier |
122
- | Scopes | Yes, always | Resolved + sorted; legacy `permission=full` displayed as `vault:read vault:write` (legacy bridge per `parachute-vault/src/scopes.ts:24`) |
123
- | `created_at` / `last_used_at` | Yes, always | If vault exposes them; relative time (`2 days ago`) |
124
- | Attached-to group | Yes, derived | Walk every group's `parachute.json`, match `tokenLabel` |
125
-
126
- Ban list: never log plaintext, never send plaintext to telemetry/sentry, never include plaintext in 4xx error bodies, never round-trip through localStorage / sessionStorage.
127
-
128
- ## Relationship to existing `/groups/<id>/vault`
129
-
130
- Keep it. The per-group attach/detach UI lives at `/claw/groups/<folder>` (`src/web/server.ts:478` → `/attach-vault`, `:562` → `/detach-vault`) and answers a *different* question: "I'm configuring this group; what vault should it talk to?" That's the right entry point when adding a new group.
131
-
132
- The new `/claw/vaults` page answers the inverse: "I'm auditing/managing my vaults; what's attached where?" Cross-link both ways:
133
-
134
- - Group detail page → "Manage vault" link to `/claw/vaults/<name>` (when attached).
135
- - Vault detail page → group rows → click → `/claw/groups/<folder>`.
136
-
137
- The detach modal on the vault page offers the same operation as the group page's detach button, plus the explicit "+ revoke" option that the group page doesn't currently surface (see § Detach revoke default).
138
-
139
- ## Scope formats
140
-
141
- Per the just-merged scope work (`parachute-vault/src/scopes.ts`), two shapes coexist:
142
-
143
- - **Broad** `vault:<verb>` — used by `pvt_*` tokens (vault-pinned by storage). Three values: `vault:read`, `vault:write`, `vault:admin`.
144
- - **Narrowed** `vault:<name>:<verb>` — used by hub-issued JWTs. Required (broad scopes on JWTs are rejected). The vault name is the same name shown in `/claw/vaults`.
145
-
146
- UI treatment:
147
-
148
- - **pvt_* tokens** (the case paraclaw mints today): the scope picker is the broad shape — three checkboxes.
149
- - **JWT-bound attachments** (hub-routed grants from the OAuth flow, when that lands for vault — currently it's `pvt_*` only): the picker presents narrowed-shape scopes, defaulted to the vault the form is on. Other-vault scopes are not selectable from this surface.
150
- - **Legacy back-compat**: tokens minted before scope landing have `permission` instead of `scopes`. We display them via the `legacyPermissionToScopes` mapping (`vault:read` for read; `vault:read vault:write` for full). Clearly badge them as "Legacy — re-mint at your earliest convenience" since the legacy shim is documented as one-release-only.
151
-
152
- ## API surface paraclaw needs
153
-
154
- ### Already in place
155
-
156
- | Endpoint | Purpose | Source |
157
- |---|---|---|
158
- | `GET /api/vaults` | List vaults from hub well-known | `src/web/server.ts:357` |
159
- | `POST /api/groups/:folder/attach-vault` | Attach vault to group (mint or use existing token) | `:478` |
160
- | `POST /api/groups/:folder/detach-vault` | Detach (no revoke) | `:562` |
161
- | `clearHubDiscoveryCache()` | Cache buster | `src/web/hub-discovery.ts:46` |
162
-
163
- The existing `mintVaultToken` shell-out at `src/web/server.ts:144` will be deleted once the new HTTP-based mint endpoint lands; the new flow replaces it everywhere.
164
-
165
- ### Vault-side, already in place
166
-
167
- | Endpoint | Purpose | Source |
168
- |---|---|---|
169
- | `POST /vault/<name>/tokens` | Mint pvt_* token, returns plaintext once | `parachute-vault/src/tokens-routes.ts` |
170
- | `GET /vault/<name>/tokens` | List tokens (metadata only) | same |
171
- | `DELETE /vault/<name>/tokens/<id>` | Revoke; idempotent (200 even if id unknown — no enumeration leak) | same |
172
-
173
- All three are admin-gated (`vault:<name>:admin` scope on the bearer JWT). paraclaw calls them with the operator's session JWT in the `Authorization: Bearer …` header.
174
-
175
- ### New paraclaw endpoints to add
176
-
177
- Each endpoint validates the operator's session JWT for `claw:*` scope at the paraclaw boundary, then forwards the same JWT to the vault. The vault validates `vault:<name>:admin` independently — paraclaw doesn't downgrade or re-issue.
178
-
179
- | Endpoint | paraclaw-side scope | Forwards to vault? | Purpose |
180
- |---|---|---|---|
181
- | `POST /api/vaults/refresh` | `claw:read` | No | Clear discovery cache + re-fetch |
182
- | `GET /api/vaults/:name` | `claw:read` | No | Detail = listing entry + attached-groups computation |
183
- | `GET /api/vaults/:name/tokens` | `claw:admin` | `GET /vault/:name/tokens` | Proxy + merge attached-to-group from `parachute.json` walk |
184
- | `POST /api/vaults/:name/tokens` | `claw:admin` | `POST /vault/:name/tokens` | Mint, return plaintext once |
185
- | `DELETE /api/vaults/:name/tokens/:id` | `claw:admin` | `DELETE /vault/:name/tokens/:id` | Revoke; warn UI if id is currently attached |
186
- | `POST /api/groups/:folder/detach-vault` (extend) | `claw:write` (+ `vault:<name>:admin` if revoke=true) | Conditional DELETE on revoke=true | Add `revokeToken: boolean` body param |
187
-
188
- The vault name in the path is the canonical routing key end-to-end — browser → paraclaw → vault. No additional plumbing needed to pick the vault.
189
-
190
- ### Vault-side gaps (none blocking)
191
-
192
- - No `last_used_at` on tokens today. Nice-to-have for the table; optional. Tracked separately if Aaron wants it.
193
- - No bulk-revoke endpoint. Not needed for v1 — revoke-then-rotate is per-token.
194
-
195
- ## Phasing
196
-
197
- Single PR for the doc (this one). Implementation likely splits into 3 PRs:
198
-
199
- 1. **Backend** (~150 LOC): the five new paraclaw endpoints (refresh + detail + tokens proxy GET/POST/DELETE) + `detach-vault revokeToken` param + JWT-forwarding helper. Delete the old `mintVaultToken` shell-out and its callers. Tests for the proxy paths + the attached-to derivation + the consent-prompt redirect on missing `vault:<name>:admin` scope.
200
- 2. **Frontend index** (~250 LOC): `/claw/vaults` route, vault list table, refresh button.
201
- 3. **Frontend detail + mint flow** (~400 LOC): `/claw/vaults/<name>` route, tokens table, attached-groups table, mint form + one-time copy card, detach-modal with revoke option, group-page cross-links.
202
-
203
- Order: 1 → 2 → 3 (frontend can stub the backend in dev mode but ships need real endpoints).
204
-
205
- ## Detach revoke default
206
-
207
- Aaron landed on **status-quo + nearby revoke** (paraclaw#36 closed today): keep current `Detach (keep token)` as the default behavior, but surface revoke prominently in this new page. The detach-modal's two-button shape (Keep / Detach + revoke) realizes that — the operator sees both options at decision time, with neither hidden behind a CLI command.
208
-
209
- This avoids the "silent revoke wedges unrelated callers" footgun while making the secure default reachable in one click.
210
-
211
- ## Open questions — resolved
212
-
213
- All five Aaron weighed in on; recording the calls here for future-me.
214
-
215
- 1. **~~Vault HTTP vs CLI shell-out.~~** **Decided: HTTP.** See § Admin auth model. paraclaw forwards the operator's hub JWT to the vault directly; the existing `mintVaultToken` shell-out gets deleted once the new endpoints land.
216
-
217
- 2. **Token attachment derivation accuracy.** O(groups) walk per page load. **Punt to a follow-up issue if profiling shows it.** Confirmed.
218
-
219
- 3. **~~Multi-vault mint defaults.~~** **Dissolves under HTTP.** The vault name lives in the URL path (`/api/vaults/:name/tokens`) and is forwarded one-to-one to `<vaultUrl>/tokens`. No "default vault" disambiguation needed.
220
-
221
- 4. **Hub-issued JWT bindings.** Out of scope for v1. Confirmed.
222
-
223
- 5. **Refresh button auth.** `claw:read`. Confirmed.
224
-
225
- ## Dependencies
226
-
227
- **hub#141 — `buildWellKnown` emitted one vault per service entry instead of one per path.** Tracked on the hub side; the multi-vault picker and the `/claw/vaults` index page rely on the well-known returning multiple vaults. Phase 1 backend work can land independently (the new paraclaw endpoints don't depend on the well-known shape — vault name comes via URL path), but phase 2 should verify hub#141 has shipped before relying on multi-vault discovery.
228
-
229
- ---
230
-
231
- Ready for review. Once approved, I'll scope the three implementation PRs and start with backend.
@@ -1,234 +0,0 @@
1
- # Channel-wiring rework
2
-
3
- **Status:** Design proposal · 2026-04-30 · paraclaw#67
4
-
5
- Aaron, during 2026-04-30 evening claw+techne testing: adding a *second* Telegram bot to an already-set-up paraclaw drops the operator into the full first-run setup wizard. Wrong shape — the operator's mental model is "wire another bot," not "set paraclaw up from scratch."
6
-
7
- This proposes a focused `/claw/channels/new` surface for routine channel-add ops, leaving `/claw/setup` reserved for true first-run.
8
-
9
- ## Goals
10
-
11
- - **Routine channel-add is short.** Three concrete actions max: identify the bot, pick the agent group, wire. Skip everything that's already true.
12
- - **Adapter parity.** Telegram is the immediate need, but the same surface handles Discord, Slack, WhatsApp, etc. — we add adapters by registering descriptors, not by re-shaping the page.
13
- - **Setup wizard stays for first-run.** Initial install (no master key, no vault attached, no agent groups) still walks the full 8-step wizard. Once paraclaw is "ready," the wizard is reachable at `/claw/setup` but isn't where the "+ Wire a new channel" CTA points.
14
- - **Resilient mid-flow recovery.** Bad token, name collision, idempotent re-wire, adapter-missing — every failure is recoverable in-place without restarting from step one.
15
-
16
- ## Non-goals
17
-
18
- - Rewriting `/claw/setup`. The first-run wizard is a separate surface and stays as-is. (Phase-3 nice-to-have: refactor it to use the same descriptor-driven adapter components, but not required by this rework.)
19
- - Replacing the `/claw/channels` index. The list-and-edit page stays; we just point its `+ Wire a new channel` button at the new surface.
20
- - Moving credential capture out of the wizard's `TestConnectionStep` model. We reuse the same `/api/channels/<adapter>/test` validators and `/api/secrets` write path — this is a flow rework, not a credentials-store rework.
21
- - Channel adapter installation UX. Installing a *new* adapter remains the skill-driven path (`/add-discord`, `/add-telegram`); the new surface only handles *wiring* once an adapter is installed (see § Adapter not installed).
22
-
23
- ## Current flow walk-through
24
-
25
- When the operator clicks `+ Wire a new channel` on `/claw/channels`, the link points at `/setup` (see `web/ui/src/routes/ChannelsList.tsx:130-132`). They land in `SetupWizard` and walk:
26
-
27
- | # | Step | What it does | Friction for routine ops |
28
- |---|---|---|---|
29
- | 1 | **Prerequisites** | Polls `/api/setup/status` — checks master key, hub reachability, vault attachment | **Wholly redundant.** All three are guaranteed true if paraclaw is running. |
30
- | 2 | **Pick channel** | Card grid: Telegram / Discord (Slack/WhatsApp render disabled) | Relevant — needed even on re-wire. |
31
- | 3 | **Install adapter** | Idempotent. If already installed (status check), shows "already installed" empty state with a `Next` button | One extra click. Not painful but unmotivated. |
32
- | 4 | **Test connection** | Operator pastes token, server hits `/getMe` (or Discord `/users/@me`), captures `botUserId` + `botUsername` | Relevant — captures bot identity for the wire. |
33
- | 5 | **Agent group** | Pick existing OR create new (inline form) | Relevant — needs to choose where this bot routes. |
34
- | 6 | **Wire channel** | `POST /api/groups/:folder/wire-channel` — synthesizes `discord:@me:<id>` or `telegram:<id>` and inserts `messaging_groups` + `messaging_group_agents` | Relevant — the load-bearing action. |
35
- | 7 | **Test message** | Polls `/api/groups/:folder` for `lastMessageInAt > baseline`, advances when a real DM arrives | Optional but useful for confirming the wire works. |
36
- | 8 | **Done** | Static "you're done" page with links | Pure ceremony. |
37
-
38
- Friction summary: **steps 1, 3, and 8 add nothing for routine ops.** Step 2 is one click. The actual work is steps 4 → 5 → 6 (+ optional 7) — three concrete decisions. The wizard's framing copy ("Set up paraclaw / Fresh install? Walk these steps to land your first agent") is also wrong context — the operator already has paraclaw set up.
39
-
40
- Secondary friction sources:
41
-
42
- - **localStorage state collision.** The wizard's `SETUP_STORAGE_KEY = 'paraclaw.setupWizard.v1'` is a single global slot. If the operator's paused mid-first-install with `currentStep: 'wire-channel'`, then comes back later to wire a *new* bot, the wizard resumes mid-state with stale `botUserId` from the old bot. Reset is manual (button at the bottom).
43
- - **Step indicator visual weight.** "1 / 8 — Prerequisites" is loud for a 3-decision task.
44
- - **No surface for "I already have a Telegram bot, I just want to add it as a second wiring."** Today the only path is the wizard.
45
-
46
- ## Target flow
47
-
48
- A single page at **`/claw/channels/new`**, reached from the `+ Wire a new channel` button on `/claw/channels`. One form, three sections, progressive disclosure (later sections gate on earlier ones being valid):
49
-
50
- ```
51
- ┌─ Wire a new channel ──────────────────────────────────┐
52
- │ │
53
- │ 1. Channel adapter │
54
- │ [○ Telegram (installed)] [● Discord (installed)] │
55
- │ [+ Install another adapter →] (drops to /setup │
56
- │ on install step) │
57
- │ │
58
- │ 2. Bot identity │
59
- │ Bot token: [paste here] [Validate] │
60
- │ ✓ Bot identified: @your_bot (id 7654321) │
61
- │ [✓] Save token to /secrets as `discord-bot-2` │
62
- │ │
63
- │ (Telegram-only): Your Telegram user id │
64
- │ [123456789] (DM @userinfobot to find this) │
65
- │ │
66
- │ 3. Agent group │
67
- │ [● Forge ] [○ Research ] [○ + Create new] │
68
- │ │
69
- │ Preview: discord:@me:7654321 → Forge │
70
- │ [Cancel] [ Wire channel ] │
71
- └───────────────────────────────────────────────────────┘
72
- ```
73
-
74
- Behavior:
75
-
76
- 1. **Section 1 — Channel adapter.** Lists every *installed* adapter as a primary card. A secondary "+ Install another adapter →" link drops the operator into `/setup?step=install` (the existing install step, narrowed). Adapter list is data-driven from `/api/setup/status`'s `channels.<name>.installed` plus a UI-side descriptor table.
77
- 2. **Section 2 — Bot identity.** Per-adapter credential fields (token always, plus per-adapter extras like Telegram's operator user id). `[Validate]` calls `POST /api/channels/<adapter>/test` to confirm the token works and capture `{id, username}`. On success, an opt-in "save to /secrets" checkbox appears (default checked, with a sensible name like `<adapter>-bot-<botUsername>`).
78
- 3. **Section 3 — Agent group.** Same picker the wizard uses (existing groups + "create new" inline form). Shape lifted from `AgentGroupStep.tsx`'s `pick`/`create` modes — extracted into a shared component.
79
- 4. **Wire button.** Calls `POST /api/groups/:folder/wire-channel` with the right id (bot snowflake for Discord, operator user id for Telegram). On success, shows the canonical platform_id + a "wire complete" confirmation with two CTAs: `Send a test message` (links to a test-message panel) and `View on /channels` (back to the index).
80
-
81
- No localStorage state. The page is stateless across reloads — if the operator refreshes mid-flow, they re-paste the token (deliberately; tokens are write-only by design). Form-internal state is React component state, scoped to the page lifetime.
82
-
83
- The page is reachable directly via URL — operators can deep-link `/claw/channels/new?adapter=telegram` from docs or scripts.
84
-
85
- ## Adapter parity (descriptor-driven)
86
-
87
- We introduce a per-adapter descriptor so the page doesn't fork by channel type:
88
-
89
- ```ts
90
- // web/ui/src/lib/channel-adapters.ts (new)
91
- export interface ChannelAdapterDescriptor {
92
- /** key matching server side (`discord`, `telegram`, `slack`, …) */
93
- key: ChannelKind;
94
- /** human label */
95
- label: string;
96
- /** label hint shown under card; doc-style */
97
- blurb: string;
98
- /**
99
- * Credential fields the operator must paste. The first field is always the
100
- * token (used by /api/channels/<key>/test). Additional fields are
101
- * adapter-specific (e.g. Telegram operator user id, Slack signing secret).
102
- */
103
- credentials: ChannelAdapterField[];
104
- /**
105
- * Which captured field becomes the wire's `botUserId` payload. Discord uses
106
- * the bot's getMe id; Telegram uses the operator's user id (chat-routed DMs).
107
- * Slack will be the workspace id, etc.
108
- */
109
- wireIdSource: 'validatedIdentityId' | 'operatorUserId' | { fromField: string };
110
- /** Default "save to /secrets" name template. */
111
- secretNameTemplate: (validated: ValidatedIdentity) => string;
112
- }
113
-
114
- interface ChannelAdapterField {
115
- name: string;
116
- label: string;
117
- hint?: string;
118
- /** secret=true → password input + autoComplete=off + obscure-on-success */
119
- secret: boolean;
120
- /** numeric=true → inputMode=numeric, pattern=[0-9]+ */
121
- numeric?: boolean;
122
- }
123
- ```
124
-
125
- The page renders `descriptor.credentials` as a generic field list, calls the descriptor's validator (always `POST /api/channels/<key>/test`), and synthesizes the wire body from `wireIdSource`. Adding Slack means adding a Slack descriptor — no new page logic.
126
-
127
- Today's two descriptors (sketch):
128
-
129
- ```ts
130
- const TELEGRAM: ChannelAdapterDescriptor = {
131
- key: 'telegram',
132
- label: 'Telegram',
133
- blurb: 'Easiest first run — BotFather + @userinfobot, ~1 minute.',
134
- credentials: [
135
- { name: 'token', label: 'Bot token', secret: true },
136
- { name: 'operatorUserId', label: 'Your Telegram user id', numeric: true,
137
- hint: 'DM @userinfobot to find this. Telegram routes DMs by chat id (= your user id).' },
138
- ],
139
- wireIdSource: { fromField: 'operatorUserId' },
140
- secretNameTemplate: (v) => `telegram-bot-${v.username}`,
141
- };
142
-
143
- const DISCORD: ChannelAdapterDescriptor = {
144
- key: 'discord',
145
- label: 'Discord',
146
- blurb: 'DM your bot or @-mention it in a server.',
147
- credentials: [{ name: 'token', label: 'Bot token', secret: true }],
148
- wireIdSource: 'validatedIdentityId',
149
- secretNameTemplate: (v) => `discord-bot-${v.username}`,
150
- };
151
- ```
152
-
153
- Phase 2 ships only the `WireChannel` page reusing today's two adapters. Phase 3 (later, separate PR) extracts the wizard's `ChannelPickStep` + `TestConnectionStep` + `WireChannelStep` to read from the same descriptor table — that's churn we don't need for Aaron's blocker.
154
-
155
- ## Setup wizard scope
156
-
157
- Setup wizard runs only when the install isn't ready yet. Trigger condition: `/api/setup/status` returns `ready: false` (i.e., master key missing, hub unreachable, OR no agent group has a vault attached).
158
-
159
- Concretely:
160
-
161
- - `/claw/setup` remains a real route, accessible by direct URL anytime (operator can re-walk it for diagnostic purposes).
162
- - The CLI / hub-side dispatcher that lands a fresh operator on `/claw/setup` checks `ready` and redirects to `/claw/` (groups index) when ready=true, leaving the wizard as an opt-in deep link.
163
- - The `+ Wire a new channel` button on `/claw/channels` always points at `/claw/channels/new`, never `/setup`.
164
- - The `+ Install another adapter →` affordance on `/claw/channels/new` deep-links into `/setup?step=install` — re-entering the wizard *just* on the install step. (Setup wizard already supports `goto(step)`, so an opening URL param can route directly.) This is the one path back into the wizard for routine ops, and it's only needed when the adapter binary isn't on disk.
165
-
166
- The SetupWizard's localStorage key gets bumped (`paraclaw.setupWizard.v2`) so prior stale state from the v1-era flow doesn't haunt operators returning months later.
167
-
168
- ## Credentials capture model
169
-
170
- Reuse the existing pieces, narrowly:
171
-
172
- - **Validate token:** `POST /api/channels/<adapter>/test` (already used by `TestConnectionStep`). Returns `{ identity: { id, username, ... } }`.
173
- - **Save secret:** `POST /api/secrets` with `{ name, value, assigned_mode: 'all' }` (paraclaw#201 repurposed `CredentialFormStep` toward this; we just call the API directly). Default-checked checkbox in section 2 — operator opts out only if they already have the token in /secrets and don't want a duplicate. Naming defaults from `secretNameTemplate(validated)`.
174
- - **Pull secret name into the wire?** No — the wiring layer doesn't reference the secret by name. The adapter at runtime looks up its token via the standard secret-injection path (`assigned_mode: 'all'` → injected into every group's container). This means: the saved secret is what makes the bot actually *talk*; the wire row makes the bot's messages route to the right agent group. Both must succeed for the new bot to work end-to-end. This dual-write is documented in the success state.
175
-
176
- We deliberately do NOT reintroduce the wizard's old `CredentialFormStep` as a sub-component. Its three-step internal navigation would fight the single-page model. Instead, section 2 is a stateless inline form whose pieces (token input + validate button + result strip + save-to-secrets checkbox) are scoped to this surface.
177
-
178
- ## Resilience
179
-
180
- Failure modes the page handles in-place, no restart:
181
-
182
- | Failure | Surface | Recovery |
183
- |---|---|---|
184
- | **Bad token** | `POST /channels/<adapter>/test` returns 401/400 with provider message ("Unauthorized" from Discord, "Not Found" from Telegram for malformed token) | Inline error under the token field. Operator pastes a different token, clicks Validate again. No state lost. |
185
- | **Network glitch on Validate** | fetch throws / 5xx | Inline error with retry button. Token field stays populated. |
186
- | **Token validates but `/api/secrets` POST fails** (e.g. duplicate secret name) | 409 from secrets API | Inline warning under the checkbox: "A secret named `X` already exists. [Use a different name] [Skip saving]." Wire still proceeds without saving the secret. |
187
- | **Agent group doesn't exist on wire** (operator deletes it in another tab between picking and wiring) | 404 from `wire-channel` | Inline error in section 3: "Group X no longer exists. [Refresh group list]." |
188
- | **Wire fails because rows already exist** | `wire-channel` is idempotent; returns `created.wiring=false` | NOT a failure — page shows "Already wired — kept existing rows" success state with the existing platform_id. (Same as today's wizard step 7.) |
189
- | **Wire fails for unexpected DB reason** | 500 from `wire-channel` | Inline error with retry. Operator can retry — second call is idempotent. |
190
- | **Adapter not installed** | `/api/setup/status`'s `channels.<key>.installed=false` | The card for that adapter is rendered with "Install required" affordance instead of being clickable. CTA: "Install via /add-`<key>` skill" (links to docs) OR "Open install step" (deep-links into `/setup?step=install&adapter=<key>`). |
191
- | **Two operators wiring same bot in two tabs** | Both run idempotent wire; first wins, second returns same `messagingGroupId`/`messagingGroupAgentId` with `created=false` | No conflict — idempotency by (channel_type, platform_id) protects this. |
192
-
193
- Mid-flow recovery is structurally easier than the wizard because there are no ordered steps with localStorage that could go stale: each section reads its prerequisite data on mount, validates on demand, and degrades gracefully.
194
-
195
- ## API surface changes
196
-
197
- **New:**
198
- - *None server-side.* The page is pure UI + reuses existing endpoints (`/api/setup/status`, `/api/channels/<adapter>/test`, `/api/groups`, `POST /api/groups/:folder/wire-channel`, `POST /api/secrets`).
199
-
200
- **Modified UI behavior:**
201
- - `web/ui/src/routes/ChannelsList.tsx:130` — `+ Wire a new channel` Link's `to` changes from `/setup` to `/channels/new`.
202
- - `web/ui/src/routes/ChannelsList.tsx:147` — empty-state copy changes from `Run /setup` to `Wire your first channel`.
203
- - New route `/channels/new` registered in the router (next to `/groups/new` shape).
204
-
205
- **Wizard preserved:** `SetupWizard` and its 8 steps stay reachable at `/setup`, untouched. The `+ Install another adapter →` link from `/claw/channels/new` lands at `/setup?step=install` to reuse the install pipeline; no new install code.
206
-
207
- ## Files (Phase 2 implementation sketch)
208
-
209
- - **New:** `web/ui/src/routes/WireChannelPage.tsx` — the new single-page form (~250 lines).
210
- - **New:** `web/ui/src/lib/channel-adapters.ts` — descriptor table (DISCORD, TELEGRAM); ~70 lines.
211
- - **New:** `web/ui/src/components/AgentGroupPicker.tsx` — extracted from `AgentGroupStep.tsx`'s `pick`/`create` modes; ~150 lines. (Reused by the new page; the wizard step is refactored to thin-wrap this component to avoid duplication.)
212
- - **Modified:** `web/ui/src/App.tsx` (or wherever the router is) — register the new route.
213
- - **Modified:** `web/ui/src/routes/ChannelsList.tsx` — re-point the CTA + empty-state copy.
214
- - **Modified:** `web/ui/src/components/setup/AgentGroupStep.tsx` — thin-wrap `AgentGroupPicker`.
215
- - **Modified:** `web/ui/src/components/setup/types.ts` — bump `SETUP_STORAGE_KEY` to `paraclaw.setupWizard.v2`.
216
-
217
- No host-side changes. No DB migration. No new endpoints. Clean.
218
-
219
- ## Test plan (Phase 2)
220
-
221
- - **Vitest unit tests** (`web/ui/`): descriptor-driven field rendering for both adapters; validate-button success/error paths with mocked fetch; wire-button payload shape per adapter; idempotent re-wire renders the "already wired" state.
222
- - **Manual smoke** on Aaron's local install:
223
- 1. With paraclaw set up + 1 Telegram bot wired, navigate `/claw/channels` → click `+ Wire a new channel` → land on `/claw/channels/new`. Pick Telegram → paste second bot's token → Validate (`@second_bot` shows up) → enter operator user id → pick existing agent group → Wire. Confirm row appears on `/claw/channels` index.
224
- 2. Repeat for Discord (no operator-user-id field shown).
225
- 3. With second bot wired, send DM to it from Telegram → confirm message lands in the agent group's session (round-trip works = wire is real).
226
- 4. Visit `/claw/setup` directly → wizard still works (regression check).
227
- 5. Wipe agent groups / vault attachment → reload `/claw/` → confirm dispatcher routes back into setup wizard (first-run-still-works check).
228
-
229
- ## Open questions for Aaron
230
-
231
- 1. **Should the "Install another adapter →" link drop into the existing wizard's install step, or should we build a narrower `/claw/channels/install` micro-page?** Proposed: reuse the wizard step (saves code, install flow is rare and the wizard chrome isn't friction here). Decline if you'd rather fully isolate this surface from the wizard.
232
- 2. **Default for "save token to /secrets" checkbox: checked or unchecked?** Proposed: checked. Unchecked means the bot won't actually deliver — the operator would have a routed wire but no token in the secrets store, so the adapter wouldn't authenticate. Defaulting unchecked invites that footgun. Counter-argument: operators who already saved the token via `/claw/secrets` shouldn't get duplicates pushed at them.
233
- 3. **Should this surface also detect "you already have a wire for this exact (adapter, identity) pair" and short-circuit?** E.g., re-wiring the same Telegram operator id to the same group is a no-op (idempotency handles it), but re-wiring to a *different* group when an existing wire exists for the same operator id is ambiguous. Proposed: leave it to the operator (the route to a different group is their explicit pick), but show a warning ("this Telegram identity is already wired to group Y — this will add a second wire on top") under the wire button when the case matches. Decline if you'd rather treat that as an error.
234
- 4. **Phase 2 scope: does the new page also include a "test message" affordance (the wizard's step 7), or is "wired successfully" enough?** Proposed: include a simple `[Send test message]` button in the post-wire success state that opens an inline test-message panel mirroring the wizard step. It's the cheapest thing to leave behind for confidence-checking.