@openparachute/agent 0.1.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 (501) hide show
  1. package/.claude/scheduled_tasks.lock +1 -0
  2. package/.claude/settings.json +5 -0
  3. package/.claude/skills/add-atomic-chat-tool/SKILL.md +243 -0
  4. package/.claude/skills/add-atomic-chat-tool/atomic-chat-mcp-stdio.ts +229 -0
  5. package/.claude/skills/add-codex/SKILL.md +161 -0
  6. package/.claude/skills/add-dashboard/SKILL.md +138 -0
  7. package/.claude/skills/add-dashboard/resources/dashboard-pusher.ts +495 -0
  8. package/.claude/skills/add-emacs/SKILL.md +296 -0
  9. package/.claude/skills/add-gcal-tool/SKILL.md +210 -0
  10. package/.claude/skills/add-gchat/REMOVE.md +6 -0
  11. package/.claude/skills/add-gchat/SKILL.md +92 -0
  12. package/.claude/skills/add-gchat/VERIFY.md +3 -0
  13. package/.claude/skills/add-github/REMOVE.md +6 -0
  14. package/.claude/skills/add-github/SKILL.md +148 -0
  15. package/.claude/skills/add-github/VERIFY.md +3 -0
  16. package/.claude/skills/add-gmail-tool/SKILL.md +229 -0
  17. package/.claude/skills/add-imessage/REMOVE.md +6 -0
  18. package/.claude/skills/add-imessage/SKILL.md +113 -0
  19. package/.claude/skills/add-imessage/VERIFY.md +3 -0
  20. package/.claude/skills/add-karpathy-llm-wiki/SKILL.md +110 -0
  21. package/.claude/skills/add-karpathy-llm-wiki/llm-wiki.md +75 -0
  22. package/.claude/skills/add-linear/REMOVE.md +6 -0
  23. package/.claude/skills/add-linear/SKILL.md +168 -0
  24. package/.claude/skills/add-linear/VERIFY.md +3 -0
  25. package/.claude/skills/add-macos-statusbar/SKILL.md +133 -0
  26. package/.claude/skills/add-macos-statusbar/add/src/statusbar.swift +147 -0
  27. package/.claude/skills/add-matrix/REMOVE.md +6 -0
  28. package/.claude/skills/add-matrix/SKILL.md +148 -0
  29. package/.claude/skills/add-matrix/VERIFY.md +3 -0
  30. package/.claude/skills/add-ollama-provider/SKILL.md +179 -0
  31. package/.claude/skills/add-ollama-tool/SKILL.md +193 -0
  32. package/.claude/skills/add-opencode/SKILL.md +229 -0
  33. package/.claude/skills/add-parallel/SKILL.md +290 -0
  34. package/.claude/skills/add-resend/REMOVE.md +6 -0
  35. package/.claude/skills/add-resend/SKILL.md +93 -0
  36. package/.claude/skills/add-resend/VERIFY.md +3 -0
  37. package/.claude/skills/add-signal/REMOVE.md +13 -0
  38. package/.claude/skills/add-signal/SKILL.md +318 -0
  39. package/.claude/skills/add-signal/VERIFY.md +5 -0
  40. package/.claude/skills/add-slack/REMOVE.md +6 -0
  41. package/.claude/skills/add-slack/SKILL.md +112 -0
  42. package/.claude/skills/add-slack/VERIFY.md +3 -0
  43. package/.claude/skills/add-teams/REMOVE.md +6 -0
  44. package/.claude/skills/add-teams/SKILL.md +207 -0
  45. package/.claude/skills/add-teams/VERIFY.md +3 -0
  46. package/.claude/skills/add-vercel/SKILL.md +147 -0
  47. package/.claude/skills/add-vercel/container-skills/vercel-cli/SKILL.md +103 -0
  48. package/.claude/skills/add-webex/REMOVE.md +6 -0
  49. package/.claude/skills/add-webex/SKILL.md +88 -0
  50. package/.claude/skills/add-webex/VERIFY.md +3 -0
  51. package/.claude/skills/add-wechat/REMOVE.md +49 -0
  52. package/.claude/skills/add-wechat/SKILL.md +170 -0
  53. package/.claude/skills/add-wechat/scripts/wire-dm.ts +172 -0
  54. package/.claude/skills/add-whatsapp/SKILL.md +264 -0
  55. package/.claude/skills/add-whatsapp-cloud/REMOVE.md +6 -0
  56. package/.claude/skills/add-whatsapp-cloud/SKILL.md +95 -0
  57. package/.claude/skills/add-whatsapp-cloud/VERIFY.md +3 -0
  58. package/.claude/skills/claw/SKILL.md +131 -0
  59. package/.claude/skills/claw/scripts/claw +374 -0
  60. package/.claude/skills/convert-to-apple-container/SKILL.md +212 -0
  61. package/.claude/skills/customize/SKILL.md +110 -0
  62. package/.claude/skills/debug/SKILL.md +349 -0
  63. package/.claude/skills/get-qodo-rules/SKILL.md +122 -0
  64. package/.claude/skills/get-qodo-rules/references/output-format.md +41 -0
  65. package/.claude/skills/get-qodo-rules/references/pagination.md +33 -0
  66. package/.claude/skills/get-qodo-rules/references/repository-scope.md +26 -0
  67. package/.claude/skills/init-first-agent/SKILL.md +120 -0
  68. package/.claude/skills/init-onecli/SKILL.md +270 -0
  69. package/.claude/skills/manage-channels/SKILL.md +87 -0
  70. package/.claude/skills/manage-mounts/SKILL.md +47 -0
  71. package/.claude/skills/migrate-from-openclaw/MIGRATE_CRONS.md +100 -0
  72. package/.claude/skills/migrate-from-openclaw/SKILL.md +447 -0
  73. package/.claude/skills/migrate-from-openclaw/scripts/discover-openclaw.ts +734 -0
  74. package/.claude/skills/migrate-from-openclaw/scripts/extract-channel-credentials.ts +476 -0
  75. package/.claude/skills/migrate-nanoclaw/SKILL.md +484 -0
  76. package/.claude/skills/migrate-nanoclaw/diagnostics.md +51 -0
  77. package/.claude/skills/qodo-pr-resolver/SKILL.md +326 -0
  78. package/.claude/skills/qodo-pr-resolver/resources/providers.md +329 -0
  79. package/.claude/skills/update-nanoclaw/SKILL.md +243 -0
  80. package/.claude/skills/update-nanoclaw/diagnostics.md +48 -0
  81. package/.claude/skills/update-skills/SKILL.md +130 -0
  82. package/.claude/skills/use-native-credential-proxy/SKILL.md +167 -0
  83. package/.claude/skills/x-integration/SKILL.md +417 -0
  84. package/.claude/skills/x-integration/agent.ts +243 -0
  85. package/.claude/skills/x-integration/host.ts +155 -0
  86. package/.claude/skills/x-integration/lib/browser.ts +148 -0
  87. package/.claude/skills/x-integration/lib/config.ts +62 -0
  88. package/.claude/skills/x-integration/scripts/like.ts +56 -0
  89. package/.claude/skills/x-integration/scripts/post.ts +66 -0
  90. package/.claude/skills/x-integration/scripts/quote.ts +80 -0
  91. package/.claude/skills/x-integration/scripts/reply.ts +74 -0
  92. package/.claude/skills/x-integration/scripts/retweet.ts +62 -0
  93. package/.claude/skills/x-integration/scripts/setup.ts +87 -0
  94. package/.github/CODEOWNERS +10 -0
  95. package/.github/PULL_REQUEST_TEMPLATE.md +18 -0
  96. package/.github/workflows/bump-version.yml +35 -0
  97. package/.github/workflows/ci.yml +39 -0
  98. package/.github/workflows/label-pr.yml +40 -0
  99. package/.github/workflows/update-tokens.yml +43 -0
  100. package/.husky/pre-commit +1 -0
  101. package/.mcp.json +3 -0
  102. package/.nvmrc +1 -0
  103. package/.parachute/module.json +14 -0
  104. package/.prettierrc +4 -0
  105. package/CHANGELOG.md +215 -0
  106. package/CLAUDE.md +307 -0
  107. package/CODE_OF_CONDUCT.md +128 -0
  108. package/CONTRIBUTING.md +159 -0
  109. package/CONTRIBUTORS.md +26 -0
  110. package/LICENSE +21 -0
  111. package/README.md +190 -0
  112. package/README_ja.md +194 -0
  113. package/README_zh.md +194 -0
  114. package/assets/nanoclaw-favicon.png +0 -0
  115. package/assets/nanoclaw-icon.png +0 -0
  116. package/assets/nanoclaw-logo-dark.png +0 -0
  117. package/assets/nanoclaw-logo.png +0 -0
  118. package/assets/nanoclaw-profile.jpeg +0 -0
  119. package/assets/nanoclaw-sales.png +0 -0
  120. package/assets/social-preview.jpg +0 -0
  121. package/config-examples/mount-allowlist.json +25 -0
  122. package/container/.dockerignore +2 -0
  123. package/container/CLAUDE.md +21 -0
  124. package/container/Dockerfile +121 -0
  125. package/container/agent-runner/bun.lock +243 -0
  126. package/container/agent-runner/package.json +22 -0
  127. package/container/agent-runner/scripts/sdk-signal-probe.ts +169 -0
  128. package/container/agent-runner/src/config.ts +55 -0
  129. package/container/agent-runner/src/db/connection.ts +267 -0
  130. package/container/agent-runner/src/db/index.ts +20 -0
  131. package/container/agent-runner/src/db/messages-in.ts +138 -0
  132. package/container/agent-runner/src/db/messages-out.ts +143 -0
  133. package/container/agent-runner/src/db/session-routing.ts +30 -0
  134. package/container/agent-runner/src/db/session-state.test.ts +100 -0
  135. package/container/agent-runner/src/db/session-state.ts +79 -0
  136. package/container/agent-runner/src/destinations.ts +135 -0
  137. package/container/agent-runner/src/formatter.test.ts +167 -0
  138. package/container/agent-runner/src/formatter.ts +260 -0
  139. package/container/agent-runner/src/index.ts +110 -0
  140. package/container/agent-runner/src/integration.test.ts +121 -0
  141. package/container/agent-runner/src/mcp-tools/agents.instructions.md +26 -0
  142. package/container/agent-runner/src/mcp-tools/agents.ts +66 -0
  143. package/container/agent-runner/src/mcp-tools/core.instructions.md +27 -0
  144. package/container/agent-runner/src/mcp-tools/core.ts +262 -0
  145. package/container/agent-runner/src/mcp-tools/index.ts +22 -0
  146. package/container/agent-runner/src/mcp-tools/interactive.instructions.md +22 -0
  147. package/container/agent-runner/src/mcp-tools/interactive.ts +169 -0
  148. package/container/agent-runner/src/mcp-tools/scheduling.instructions.md +40 -0
  149. package/container/agent-runner/src/mcp-tools/scheduling.ts +299 -0
  150. package/container/agent-runner/src/mcp-tools/self-mod.instructions.md +25 -0
  151. package/container/agent-runner/src/mcp-tools/self-mod.ts +120 -0
  152. package/container/agent-runner/src/mcp-tools/server.ts +54 -0
  153. package/container/agent-runner/src/mcp-tools/types.ts +6 -0
  154. package/container/agent-runner/src/poll-loop.test.ts +248 -0
  155. package/container/agent-runner/src/poll-loop.ts +437 -0
  156. package/container/agent-runner/src/providers/claude.ts +379 -0
  157. package/container/agent-runner/src/providers/factory.test.ts +19 -0
  158. package/container/agent-runner/src/providers/factory.ts +13 -0
  159. package/container/agent-runner/src/providers/index.ts +6 -0
  160. package/container/agent-runner/src/providers/mock.ts +77 -0
  161. package/container/agent-runner/src/providers/provider-registry.ts +33 -0
  162. package/container/agent-runner/src/providers/types.ts +82 -0
  163. package/container/agent-runner/src/scheduling/task-script.ts +121 -0
  164. package/container/agent-runner/src/timezone.test.ts +93 -0
  165. package/container/agent-runner/src/timezone.ts +107 -0
  166. package/container/agent-runner/tsconfig.json +14 -0
  167. package/container/build.sh +48 -0
  168. package/container/entrypoint.sh +16 -0
  169. package/container/skills/agent-browser/SKILL.md +159 -0
  170. package/container/skills/frontend-engineer/SKILL.md +157 -0
  171. package/container/skills/self-customize/SKILL.md +87 -0
  172. package/container/skills/slack-formatting/SKILL.md +94 -0
  173. package/container/skills/vercel-cli/SKILL.md +111 -0
  174. package/container/skills/welcome/SKILL.md +85 -0
  175. package/docs/APPLE-CONTAINER-NETWORKING.md +90 -0
  176. package/docs/BRANCH-FORK-MAINTENANCE.md +81 -0
  177. package/docs/README.md +25 -0
  178. package/docs/SDK_DEEP_DIVE.md +643 -0
  179. package/docs/SECURITY.md +162 -0
  180. package/docs/agent-runner-details.md +749 -0
  181. package/docs/api-details.md +365 -0
  182. package/docs/architecture-diagram.html +422 -0
  183. package/docs/architecture-diagram.md +215 -0
  184. package/docs/architecture.md +751 -0
  185. package/docs/audit/2026-04-30-channel-endpoint-audit.md +36 -0
  186. package/docs/build-and-runtime.md +80 -0
  187. package/docs/cross-mount-stress/README.md +112 -0
  188. package/docs/cross-mount-stress/container-writer-retry.mjs +55 -0
  189. package/docs/cross-mount-stress/container-writer-slow.mjs +42 -0
  190. package/docs/cross-mount-stress/container-writer.mjs +47 -0
  191. package/docs/cross-mount-stress/host-writer-retry.mjs +55 -0
  192. package/docs/cross-mount-stress/host-writer-slow.mjs +43 -0
  193. package/docs/cross-mount-stress/host-writer.mjs +47 -0
  194. package/docs/db-central.md +316 -0
  195. package/docs/db-session.md +183 -0
  196. package/docs/db.md +119 -0
  197. package/docs/design/2026-04-29-vault-management-ui.md +231 -0
  198. package/docs/design/2026-04-30-channel-wiring-rework.md +234 -0
  199. package/docs/design/2026-05-01-channel-wiring-approvals-deep-dive.md +272 -0
  200. package/docs/design/2026-05-02-channel-policy-and-approval-routing.md +250 -0
  201. package/docs/docker-sandboxes.md +359 -0
  202. package/docs/isolation-model.md +88 -0
  203. package/docs/ollama.md +79 -0
  204. package/docs/parachute-integration.md +109 -0
  205. package/docs/post-night-rebirth-reflections.md +151 -0
  206. package/eslint.config.js +32 -0
  207. package/package.json +54 -0
  208. package/pnpm-workspace.yaml +8 -0
  209. package/repo-tokens/README.md +113 -0
  210. package/repo-tokens/action.yml +186 -0
  211. package/repo-tokens/badge.svg +23 -0
  212. package/repo-tokens/examples/green.svg +14 -0
  213. package/repo-tokens/examples/red.svg +14 -0
  214. package/repo-tokens/examples/yellow-green.svg +14 -0
  215. package/repo-tokens/examples/yellow.svg +14 -0
  216. package/scripts/chat.ts +101 -0
  217. package/scripts/cleanup-sessions.sh +150 -0
  218. package/scripts/init-cli-agent.ts +171 -0
  219. package/scripts/init-first-agent.ts +377 -0
  220. package/scripts/parachute.ts +158 -0
  221. package/scripts/run-migrations.ts +105 -0
  222. package/scripts/sanity-live-poll.ts +95 -0
  223. package/scripts/seed-discord.ts +79 -0
  224. package/scripts/test-v2-agent.ts +106 -0
  225. package/scripts/test-v2-channel-e2e.ts +265 -0
  226. package/scripts/test-v2-host.ts +184 -0
  227. package/src/channels/adapter.ts +214 -0
  228. package/src/channels/ask-question.ts +46 -0
  229. package/src/channels/channel-registry.test.ts +421 -0
  230. package/src/channels/channel-registry.ts +313 -0
  231. package/src/channels/chat-sdk-bridge.test.ts +84 -0
  232. package/src/channels/chat-sdk-bridge.ts +652 -0
  233. package/src/channels/cli.ts +276 -0
  234. package/src/channels/discord.ts +90 -0
  235. package/src/channels/index.ts +17 -0
  236. package/src/channels/telegram-markdown-sanitize.test.ts +78 -0
  237. package/src/channels/telegram-markdown-sanitize.ts +55 -0
  238. package/src/channels/telegram-pairing.test.ts +254 -0
  239. package/src/channels/telegram-pairing.ts +339 -0
  240. package/src/channels/telegram.ts +279 -0
  241. package/src/channels/trust-hint.test.ts +48 -0
  242. package/src/channels/trust-hint.ts +75 -0
  243. package/src/claude-md-compose.migrate.test.ts +64 -0
  244. package/src/claude-md-compose.ts +205 -0
  245. package/src/command-gate.ts +63 -0
  246. package/src/config.test.ts +93 -0
  247. package/src/config.ts +108 -0
  248. package/src/container-config.ts +167 -0
  249. package/src/container-runner.test.ts +32 -0
  250. package/src/container-runner.ts +576 -0
  251. package/src/container-runtime.test.ts +169 -0
  252. package/src/container-runtime.ts +92 -0
  253. package/src/db/_bun-sqlite-shim.ts +88 -0
  254. package/src/db/agent-activity.test.ts +155 -0
  255. package/src/db/agent-activity.ts +121 -0
  256. package/src/db/agent-groups.ts +77 -0
  257. package/src/db/connection.migrate.test.ts +143 -0
  258. package/src/db/connection.ts +224 -0
  259. package/src/db/db-v2.test.ts +440 -0
  260. package/src/db/dropped-messages.ts +44 -0
  261. package/src/db/index.ts +40 -0
  262. package/src/db/messaging-groups.ts +252 -0
  263. package/src/db/migrations/001-initial.ts +112 -0
  264. package/src/db/migrations/002-chat-sdk-state.ts +36 -0
  265. package/src/db/migrations/008-dropped-messages.ts +27 -0
  266. package/src/db/migrations/009-drop-pending-credentials.ts +13 -0
  267. package/src/db/migrations/010-engage-modes.ts +103 -0
  268. package/src/db/migrations/011-pending-sender-approvals.ts +40 -0
  269. package/src/db/migrations/012-channel-registration.ts +48 -0
  270. package/src/db/migrations/013-approval-render-metadata.ts +27 -0
  271. package/src/db/migrations/014-secrets.ts +44 -0
  272. package/src/db/migrations/015-secrets-drop-host-pattern.ts +18 -0
  273. package/src/db/migrations/016-secret-assignments.ts +30 -0
  274. package/src/db/migrations/017-agent-activity.ts +40 -0
  275. package/src/db/migrations/018-oauth-app-configs.ts +34 -0
  276. package/src/db/migrations/019-oauth-app-connections.ts +48 -0
  277. package/src/db/migrations/020-agent-app-connections.ts +28 -0
  278. package/src/db/migrations/021-pending-oauth-states.ts +35 -0
  279. package/src/db/migrations/022-app-connections-provider.ts +25 -0
  280. package/src/db/migrations/023-agent-group-secret-mode.test.ts +124 -0
  281. package/src/db/migrations/023-agent-group-secret-mode.ts +65 -0
  282. package/src/db/migrations/024-collapse-approvals.test.ts +249 -0
  283. package/src/db/migrations/024-collapse-approvals.ts +182 -0
  284. package/src/db/migrations/025-secret-mode-check.test.ts +155 -0
  285. package/src/db/migrations/025-secret-mode-check.ts +49 -0
  286. package/src/db/migrations/026-user-dms-bot-id.test.ts +116 -0
  287. package/src/db/migrations/026-user-dms-bot-id.ts +54 -0
  288. package/src/db/migrations/027-provider-credentials.ts +41 -0
  289. package/src/db/migrations/_test-helpers.ts +41 -0
  290. package/src/db/migrations/index.ts +127 -0
  291. package/src/db/migrations/module-agent-to-agent-destinations.ts +84 -0
  292. package/src/db/migrations/module-approvals-pending-approvals.ts +42 -0
  293. package/src/db/migrations/module-approvals-title-options.ts +40 -0
  294. package/src/db/schema.ts +258 -0
  295. package/src/db/session-db.test.ts +93 -0
  296. package/src/db/session-db.ts +325 -0
  297. package/src/db/sessions.ts +241 -0
  298. package/src/delivery.test.ts +148 -0
  299. package/src/delivery.ts +445 -0
  300. package/src/env.ts +74 -0
  301. package/src/group-folder.test.ts +35 -0
  302. package/src/group-folder.ts +44 -0
  303. package/src/group-init.ts +92 -0
  304. package/src/host-core.test.ts +456 -0
  305. package/src/host-sweep.test.ts +146 -0
  306. package/src/host-sweep.ts +287 -0
  307. package/src/index.ts +227 -0
  308. package/src/install-slug.ts +33 -0
  309. package/src/log.test.ts +81 -0
  310. package/src/log.ts +117 -0
  311. package/src/mcp/http.ts +72 -0
  312. package/src/mcp/server.ts +92 -0
  313. package/src/mcp/stdio.ts +51 -0
  314. package/src/mcp/tools/activity.ts +88 -0
  315. package/src/mcp/tools/agent-groups.ts +183 -0
  316. package/src/mcp/tools/approvals.ts +122 -0
  317. package/src/mcp/tools/channels.ts +199 -0
  318. package/src/mcp/tools/index.ts +27 -0
  319. package/src/mcp/tools/oauth.ts +48 -0
  320. package/src/mcp/tools/secrets.ts +169 -0
  321. package/src/mcp/tools/sessions.ts +135 -0
  322. package/src/mcp/types.ts +51 -0
  323. package/src/modules/agent-to-agent/agent-route.test.ts +46 -0
  324. package/src/modules/agent-to-agent/agent-route.ts +223 -0
  325. package/src/modules/agent-to-agent/create-agent.ts +127 -0
  326. package/src/modules/agent-to-agent/db/agent-destinations.ts +135 -0
  327. package/src/modules/agent-to-agent/index.ts +22 -0
  328. package/src/modules/agent-to-agent/write-destinations.ts +59 -0
  329. package/src/modules/approvals/agent.md +45 -0
  330. package/src/modules/approvals/index.ts +21 -0
  331. package/src/modules/approvals/picks.test.ts +291 -0
  332. package/src/modules/approvals/primitive.ts +279 -0
  333. package/src/modules/approvals/project.md +27 -0
  334. package/src/modules/approvals/response-handler.ts +87 -0
  335. package/src/modules/index.ts +24 -0
  336. package/src/modules/interactive/agent.md +21 -0
  337. package/src/modules/interactive/index.ts +69 -0
  338. package/src/modules/interactive/project.md +12 -0
  339. package/src/modules/mount-security/index.ts +448 -0
  340. package/src/modules/mount-security/migrate.test.ts +91 -0
  341. package/src/modules/permissions/access.ts +28 -0
  342. package/src/modules/permissions/channel-approval.test.ts +389 -0
  343. package/src/modules/permissions/channel-approval.ts +188 -0
  344. package/src/modules/permissions/db/agent-group-members.ts +44 -0
  345. package/src/modules/permissions/db/pending-channel-approvals.test.ts +86 -0
  346. package/src/modules/permissions/db/pending-channel-approvals.ts +66 -0
  347. package/src/modules/permissions/db/pending-sender-approvals.ts +60 -0
  348. package/src/modules/permissions/db/user-dms.ts +58 -0
  349. package/src/modules/permissions/db/user-roles.ts +85 -0
  350. package/src/modules/permissions/db/users.ts +38 -0
  351. package/src/modules/permissions/index.ts +421 -0
  352. package/src/modules/permissions/permissions.test.ts +358 -0
  353. package/src/modules/permissions/sender-approval.test.ts +470 -0
  354. package/src/modules/permissions/sender-approval.ts +165 -0
  355. package/src/modules/permissions/user-dm.ts +200 -0
  356. package/src/modules/provider-credentials/db.ts +121 -0
  357. package/src/modules/provider-credentials/index.ts +12 -0
  358. package/src/modules/provider-credentials/spawn.test.ts +206 -0
  359. package/src/modules/provider-credentials/spawn.ts +114 -0
  360. package/src/modules/scheduling/actions.ts +113 -0
  361. package/src/modules/scheduling/db.test.ts +282 -0
  362. package/src/modules/scheduling/db.ts +148 -0
  363. package/src/modules/scheduling/index.ts +34 -0
  364. package/src/modules/scheduling/recurrence.test.ts +98 -0
  365. package/src/modules/scheduling/recurrence.ts +54 -0
  366. package/src/modules/self-mod/agent.md +30 -0
  367. package/src/modules/self-mod/apply.ts +85 -0
  368. package/src/modules/self-mod/index.ts +30 -0
  369. package/src/modules/self-mod/project.md +39 -0
  370. package/src/modules/self-mod/request.ts +91 -0
  371. package/src/modules/typing/index.ts +165 -0
  372. package/src/oauth/agent-app-connections.ts +103 -0
  373. package/src/oauth/app-configs.test.ts +64 -0
  374. package/src/oauth/app-configs.ts +114 -0
  375. package/src/oauth/app-connections.test.ts +109 -0
  376. package/src/oauth/app-connections.ts +178 -0
  377. package/src/oauth/crypto.ts +56 -0
  378. package/src/oauth/flow.ts +104 -0
  379. package/src/oauth/providers/google.test.ts +38 -0
  380. package/src/oauth/providers/google.ts +46 -0
  381. package/src/oauth/providers/index.ts +48 -0
  382. package/src/oauth/state-store.test.ts +54 -0
  383. package/src/oauth/state-store.ts +93 -0
  384. package/src/parachute/README.md +27 -0
  385. package/src/parachute/create-agent.test.ts +83 -0
  386. package/src/parachute/create-agent.ts +122 -0
  387. package/src/parachute/group-status.test.ts +165 -0
  388. package/src/parachute/group-status.ts +136 -0
  389. package/src/parachute/types.ts +41 -0
  390. package/src/parachute/vault-mcp.test.ts +251 -0
  391. package/src/parachute/vault-mcp.ts +232 -0
  392. package/src/platform-id.test.ts +104 -0
  393. package/src/platform-id.ts +109 -0
  394. package/src/providers/index.ts +6 -0
  395. package/src/providers/provider-container-registry.ts +58 -0
  396. package/src/response-registry.ts +45 -0
  397. package/src/router.ts +530 -0
  398. package/src/secrets/crypto.test.ts +45 -0
  399. package/src/secrets/crypto.ts +55 -0
  400. package/src/secrets/index.ts +355 -0
  401. package/src/secrets/master-key.ts +70 -0
  402. package/src/secrets/secrets.test.ts +354 -0
  403. package/src/session-manager.migrate.test.ts +59 -0
  404. package/src/session-manager.ts +433 -0
  405. package/src/startup-bootstrap.test.ts +226 -0
  406. package/src/startup-bootstrap.ts +207 -0
  407. package/src/state-sqlite.ts +182 -0
  408. package/src/timezone.test.ts +64 -0
  409. package/src/timezone.ts +37 -0
  410. package/src/types.ts +230 -0
  411. package/src/web/auth.test.ts +335 -0
  412. package/src/web/auth.ts +214 -0
  413. package/src/web/discord-validate.test.ts +77 -0
  414. package/src/web/discord-validate.ts +88 -0
  415. package/src/web/hub-discovery.test.ts +98 -0
  416. package/src/web/hub-discovery.ts +69 -0
  417. package/src/web/routes/activity.ts +106 -0
  418. package/src/web/routes/agent-provider.test.ts +282 -0
  419. package/src/web/routes/agent-provider.ts +309 -0
  420. package/src/web/routes/approvals.ts +185 -0
  421. package/src/web/routes/apps.ts +434 -0
  422. package/src/web/routes/channels-mg-detail.test.ts +324 -0
  423. package/src/web/routes/channels-mga-detail.test.ts +425 -0
  424. package/src/web/routes/channels.ts +489 -0
  425. package/src/web/routes/oauth-providers.ts +42 -0
  426. package/src/web/routes/secrets.test.ts +175 -0
  427. package/src/web/routes/secrets.ts +282 -0
  428. package/src/web/routes/sessions.ts +123 -0
  429. package/src/web/routes/settings.test.ts +106 -0
  430. package/src/web/routes/settings.ts +247 -0
  431. package/src/web/routes/setup-status.ts +205 -0
  432. package/src/web/routes/vaults.test.ts +389 -0
  433. package/src/web/routes/vaults.ts +225 -0
  434. package/src/web/server-version.test.ts +16 -0
  435. package/src/web/server.ts +1003 -0
  436. package/src/web/services-manifest.test.ts +120 -0
  437. package/src/web/services-manifest.ts +61 -0
  438. package/src/web/static-serve.test.ts +255 -0
  439. package/src/web/static-serve.ts +104 -0
  440. package/src/web/telegram-validate.test.ts +116 -0
  441. package/src/web/telegram-validate.ts +107 -0
  442. package/src/web/vault-proxy.test.ts +214 -0
  443. package/src/web/vault-proxy.ts +120 -0
  444. package/src/web/wire-channel.ts +181 -0
  445. package/src/webhook-server.ts +134 -0
  446. package/tsconfig.json +21 -0
  447. package/vitest.config.ts +18 -0
  448. package/web/README.md +63 -0
  449. package/web/ui/index.html +13 -0
  450. package/web/ui/package.json +35 -0
  451. package/web/ui/pnpm-lock.yaml +2164 -0
  452. package/web/ui/scripts/verify-base.mjs +31 -0
  453. package/web/ui/src/App.tsx +88 -0
  454. package/web/ui/src/components/ActivityFeed.tsx +444 -0
  455. package/web/ui/src/components/AgentGroupPicker.tsx +263 -0
  456. package/web/ui/src/components/AgentProviderCards.tsx +220 -0
  457. package/web/ui/src/components/CredentialForm.tsx +214 -0
  458. package/web/ui/src/components/ScopeGrants.tsx +74 -0
  459. package/web/ui/src/components/StatusDot.tsx +43 -0
  460. package/web/ui/src/components/VaultPicker.tsx +127 -0
  461. package/web/ui/src/components/setup/AdapterInstallStep.tsx +178 -0
  462. package/web/ui/src/components/setup/AgentGroupStep.tsx +43 -0
  463. package/web/ui/src/components/setup/ChannelPickStep.tsx +74 -0
  464. package/web/ui/src/components/setup/DoneStep.tsx +49 -0
  465. package/web/ui/src/components/setup/PrereqStep.tsx +129 -0
  466. package/web/ui/src/components/setup/TestConnectionStep.tsx +108 -0
  467. package/web/ui/src/components/setup/TestMessageStep.tsx +104 -0
  468. package/web/ui/src/components/setup/WireChannelStep.tsx +166 -0
  469. package/web/ui/src/components/setup/types.ts +105 -0
  470. package/web/ui/src/lib/api.test.ts +410 -0
  471. package/web/ui/src/lib/api.ts +1210 -0
  472. package/web/ui/src/lib/auth.test.ts +139 -0
  473. package/web/ui/src/lib/auth.ts +348 -0
  474. package/web/ui/src/lib/channel-adapters.ts +136 -0
  475. package/web/ui/src/main.tsx +19 -0
  476. package/web/ui/src/routes/ApprovalsList.tsx +294 -0
  477. package/web/ui/src/routes/Apps.tsx +613 -0
  478. package/web/ui/src/routes/ChannelWireDetail.test.tsx +233 -0
  479. package/web/ui/src/routes/ChannelWireDetail.tsx +403 -0
  480. package/web/ui/src/routes/ChannelsList.tsx +158 -0
  481. package/web/ui/src/routes/GroupDetail.tsx +755 -0
  482. package/web/ui/src/routes/GroupList.tsx +187 -0
  483. package/web/ui/src/routes/MessagingGroupDetail.test.tsx +233 -0
  484. package/web/ui/src/routes/MessagingGroupDetail.tsx +306 -0
  485. package/web/ui/src/routes/NewGroupWizard.tsx +390 -0
  486. package/web/ui/src/routes/OAuthCallback.tsx +56 -0
  487. package/web/ui/src/routes/SecretsList.tsx +921 -0
  488. package/web/ui/src/routes/SessionsList.tsx +220 -0
  489. package/web/ui/src/routes/SettingsAgentProvider.tsx +109 -0
  490. package/web/ui/src/routes/SettingsApprovals.tsx +234 -0
  491. package/web/ui/src/routes/SetupWizard.tsx +219 -0
  492. package/web/ui/src/routes/VaultDetail.test.tsx +361 -0
  493. package/web/ui/src/routes/VaultDetail.tsx +960 -0
  494. package/web/ui/src/routes/VaultsList.tsx +295 -0
  495. package/web/ui/src/routes/WireChannelPage.tsx +413 -0
  496. package/web/ui/src/styles.css +608 -0
  497. package/web/ui/src/test/setup.ts +23 -0
  498. package/web/ui/src/vite-env.d.ts +10 -0
  499. package/web/ui/tsconfig.json +20 -0
  500. package/web/ui/vite.config.ts +34 -0
  501. package/web/ui/vitest.config.ts +25 -0
@@ -0,0 +1,74 @@
1
+ import type { VaultScope } from "../lib/api.ts";
2
+
3
+ const READ_TOOLS = ["query-notes", "list-tags", "find-path", "vault-info"] as const;
4
+ const WRITE_TOOLS = ["create-note", "update-note", "delete-note", "update-tag", "delete-tag"] as const;
5
+
6
+ export const SCOPE_OPTIONS: { value: VaultScope; label: string }[] = [
7
+ { value: "vault:read", label: "vault:read — query, can't modify" },
8
+ { value: "vault:write", label: "vault:write — capture and update notes" },
9
+ { value: "vault:admin", label: "vault:admin — full access (use sparingly)" },
10
+ ];
11
+
12
+ export function scopeGrants(scope: VaultScope): {
13
+ summary: string;
14
+ granted: readonly string[];
15
+ withheld: readonly string[];
16
+ adminNote?: string;
17
+ } {
18
+ if (scope === "vault:read") {
19
+ return {
20
+ summary: "Read-only access. The agent can query the vault but cannot create, modify, or delete anything.",
21
+ granted: READ_TOOLS,
22
+ withheld: WRITE_TOOLS,
23
+ };
24
+ }
25
+ if (scope === "vault:write") {
26
+ return {
27
+ summary: "Read + write. The agent can capture and update notes and tags, but cannot manage vault config or tokens.",
28
+ granted: [...READ_TOOLS, ...WRITE_TOOLS],
29
+ withheld: [],
30
+ };
31
+ }
32
+ return {
33
+ summary: "Full access including vault config (/.parachute/config*) and token management. Use sparingly.",
34
+ granted: [...READ_TOOLS, ...WRITE_TOOLS],
35
+ withheld: [],
36
+ adminNote: "Admin tokens can read and write any path including vault config — only grant when the agent genuinely needs it.",
37
+ };
38
+ }
39
+
40
+ export function ScopeGrants({ scope }: { scope: VaultScope }) {
41
+ const grants = scopeGrants(scope);
42
+ return (
43
+ <div className="scope-grants">
44
+ <p className="scope-grants-summary">{grants.summary}</p>
45
+ <div className="scope-grants-tools">
46
+ <div>
47
+ <span className="scope-grants-label">Allows</span>
48
+ <ul>
49
+ {grants.granted.map((t) => (
50
+ <li key={t}>
51
+ <code>{t}</code>
52
+ </li>
53
+ ))}
54
+ </ul>
55
+ </div>
56
+ {grants.withheld.length > 0 && (
57
+ <div>
58
+ <span className="scope-grants-label">Blocks</span>
59
+ <ul>
60
+ {grants.withheld.map((t) => (
61
+ <li key={t} className="dim">
62
+ <code>{t}</code>
63
+ </li>
64
+ ))}
65
+ </ul>
66
+ </div>
67
+ )}
68
+ </div>
69
+ {grants.adminNote && (
70
+ <p className="scope-grants-warn">{grants.adminNote}</p>
71
+ )}
72
+ </div>
73
+ );
74
+ }
@@ -0,0 +1,43 @@
1
+ import type { GroupStatus } from "../lib/api.ts";
2
+
3
+ export function StatusDot({ status }: { status: GroupStatus | null }) {
4
+ if (!status) {
5
+ return <span className="status-dot status-dot-unknown" title="status unavailable" aria-label="status unavailable" />;
6
+ }
7
+ if (status.containerRunning) {
8
+ const lastActive = status.lastHeartbeatAt
9
+ ? `last heartbeat ${formatRelative(status.lastHeartbeatAt)}`
10
+ : "alive";
11
+ return (
12
+ <span
13
+ className="status-dot status-dot-alive"
14
+ title={`${status.activeSessionCount} session${status.activeSessionCount === 1 ? "" : "s"} alive — ${lastActive}`}
15
+ aria-label="alive"
16
+ />
17
+ );
18
+ }
19
+ const idleTitle = status.lastHeartbeatAt
20
+ ? `idle — last heartbeat ${formatRelative(status.lastHeartbeatAt)}`
21
+ : "idle — never started";
22
+ return (
23
+ <span
24
+ className="status-dot status-dot-idle"
25
+ title={idleTitle}
26
+ aria-label="idle"
27
+ />
28
+ );
29
+ }
30
+
31
+ export function formatRelative(iso: string, nowMs: number = Date.now()): string {
32
+ const t = Date.parse(iso);
33
+ if (Number.isNaN(t)) return iso;
34
+ const diffSec = Math.max(0, Math.floor((nowMs - t) / 1000));
35
+ if (diffSec < 5) return "just now";
36
+ if (diffSec < 60) return `${diffSec}s ago`;
37
+ const diffMin = Math.floor(diffSec / 60);
38
+ if (diffMin < 60) return `${diffMin}m ago`;
39
+ const diffHr = Math.floor(diffMin / 60);
40
+ if (diffHr < 24) return `${diffHr}h ago`;
41
+ const diffDay = Math.floor(diffHr / 24);
42
+ return `${diffDay}d ago`;
43
+ }
@@ -0,0 +1,127 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { listVaults, type VaultListing } from '../lib/api.ts';
3
+
4
+ interface VaultPickerProps {
5
+ value: string;
6
+ onChange: (next: string) => void;
7
+ disabled?: boolean;
8
+ inputId?: string;
9
+ /** Fires with the picked vault's display name (or null when "Other" is selected / no match). */
10
+ onPickedName?: (name: string | null) => void;
11
+ }
12
+
13
+ const OTHER_SENTINEL = '__other__';
14
+
15
+ export function VaultPicker({ value, onChange, disabled, inputId, onPickedName }: VaultPickerProps) {
16
+ const [vaults, setVaults] = useState<VaultListing[] | null>(null);
17
+ const [loadError, setLoadError] = useState<string | null>(null);
18
+ const [useCustomUrl, setUseCustomUrl] = useState(false);
19
+ const initializedRef = useRef(false);
20
+
21
+ useEffect(() => {
22
+ let cancelled = false;
23
+ listVaults()
24
+ .then((vs) => {
25
+ if (cancelled) return;
26
+ setVaults(vs);
27
+ if (initializedRef.current) return;
28
+ initializedRef.current = true;
29
+ if (vs.length === 0) {
30
+ setUseCustomUrl(true);
31
+ onPickedName?.(null);
32
+ return;
33
+ }
34
+ const match = vs.find((v) => v.url === value);
35
+ if (!match) onChange(vs[0].url);
36
+ })
37
+ .catch((e) => {
38
+ if (cancelled) return;
39
+ setLoadError(e instanceof Error ? e.message : String(e));
40
+ setUseCustomUrl(true);
41
+ initializedRef.current = true;
42
+ onPickedName?.(null);
43
+ });
44
+ return () => {
45
+ cancelled = true;
46
+ };
47
+ // eslint-disable-next-line react-hooks/exhaustive-deps
48
+ }, []);
49
+
50
+ // Notify parent when the picked-name changes (selection or value flip).
51
+ useEffect(() => {
52
+ if (!onPickedName) return;
53
+ if (useCustomUrl) {
54
+ onPickedName(null);
55
+ return;
56
+ }
57
+ if (!vaults) return;
58
+ const match = vaults.find((v) => v.url === value);
59
+ onPickedName(match ? match.name : null);
60
+ }, [vaults, value, useCustomUrl, onPickedName]);
61
+
62
+ const trimmed = value.replace(/\/+$/, '');
63
+ const reachLine = (
64
+ <p className="dim">
65
+ The agent will reach this at <code>{trimmed || '…'}/mcp</code>.
66
+ </p>
67
+ );
68
+
69
+ if (vaults === null && !loadError) {
70
+ return <p className="dim">Loading vaults…</p>;
71
+ }
72
+
73
+ const customInput = (
74
+ <input
75
+ id={vaults && vaults.length > 0 ? undefined : inputId}
76
+ type="text"
77
+ value={value}
78
+ onChange={(e) => onChange(e.target.value)}
79
+ disabled={disabled}
80
+ placeholder="https://parachute.example/vault/default"
81
+ style={vaults && vaults.length > 0 ? { marginTop: '0.5rem' } : undefined}
82
+ />
83
+ );
84
+
85
+ if (!vaults || vaults.length === 0) {
86
+ return (
87
+ <>
88
+ {customInput}
89
+ {reachLine}
90
+ {loadError && <p className="dim">(could not list registered vaults: {loadError})</p>}
91
+ </>
92
+ );
93
+ }
94
+
95
+ return (
96
+ <>
97
+ <select
98
+ id={inputId}
99
+ value={useCustomUrl ? OTHER_SENTINEL : value}
100
+ onChange={(e) => {
101
+ if (e.target.value === OTHER_SENTINEL) {
102
+ setUseCustomUrl(true);
103
+ } else {
104
+ setUseCustomUrl(false);
105
+ onChange(e.target.value);
106
+ }
107
+ }}
108
+ disabled={disabled}
109
+ >
110
+ {vaults.map((v) => (
111
+ <option key={v.url} value={v.url}>
112
+ {v.name} — {v.url}
113
+ </option>
114
+ ))}
115
+ <option value={OTHER_SENTINEL}>Other (paste URL)…</option>
116
+ </select>
117
+ {useCustomUrl && customInput}
118
+ {reachLine}
119
+ </>
120
+ );
121
+ }
122
+
123
+ export function vaultLabelForUrl(vaults: VaultListing[] | null, url: string): string | null {
124
+ if (!vaults) return null;
125
+ const match = vaults.find((v) => v.url === url.replace(/\/+$/, ''));
126
+ return match ? match.name : null;
127
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Step 4 — Adapter install.
3
+ *
4
+ * Triggers POST /setup/install-channel with the chosen adapter, then polls
5
+ * GET /tasks/:id every second until the task lands in a terminal state.
6
+ * Renders the per-step checklist live as the orchestrator advances.
7
+ *
8
+ * Per-channel step counts: discord = 5, telegram = 6 (extra
9
+ * register-setup-step that wires the pair-telegram setup script).
10
+ *
11
+ * Idempotency: if the chosen adapter is already installed (status check
12
+ * before dispatch shows channels.<adapter>.installed=true), we skip
13
+ * kicking off the task and let the user advance immediately. The
14
+ * dirty-tree 409 surfaces as an inline error with the offending file
15
+ * list — operator commits/stashes and re-clicks.
16
+ */
17
+ import { useCallback, useEffect, useRef, useState } from 'react';
18
+ import {
19
+ getSetupStatus,
20
+ getTask,
21
+ startInstallChannel,
22
+ type TaskRecord,
23
+ type SetupStatus,
24
+ } from '../../lib/api.ts';
25
+ import { ADAPTER_LABELS, type StepProps } from './types.ts';
26
+
27
+ const POLL_MS = 1000;
28
+
29
+ export function AdapterInstallStep({ state, patchState, next, back }: StepProps) {
30
+ const [status, setStatus] = useState<SetupStatus | null>(null);
31
+ const [task, setTask] = useState<TaskRecord | null>(null);
32
+ const [starting, setStarting] = useState(false);
33
+ const [error, setError] = useState<string | null>(null);
34
+ const [dirtyFiles, setDirtyFiles] = useState<string[] | null>(null);
35
+ const pollTimer = useRef<ReturnType<typeof setInterval> | null>(null);
36
+
37
+ const adapter = state.adapter;
38
+
39
+ useEffect(() => {
40
+ getSetupStatus()
41
+ .then(setStatus)
42
+ .catch((err) => setError(err instanceof Error ? err.message : String(err)));
43
+ }, []);
44
+
45
+ const startPolling = useCallback((taskId: string) => {
46
+ if (pollTimer.current) clearInterval(pollTimer.current);
47
+ pollTimer.current = setInterval(() => {
48
+ getTask(taskId)
49
+ .then((t) => {
50
+ setTask(t);
51
+ if (t.status === 'completed' || t.status === 'failed') {
52
+ if (pollTimer.current) clearInterval(pollTimer.current);
53
+ pollTimer.current = null;
54
+ }
55
+ })
56
+ .catch((err) => {
57
+ setError(err instanceof Error ? err.message : String(err));
58
+ if (pollTimer.current) clearInterval(pollTimer.current);
59
+ pollTimer.current = null;
60
+ });
61
+ }, POLL_MS);
62
+ }, []);
63
+
64
+ useEffect(() => {
65
+ if (state.installTaskId && !task) {
66
+ getTask(state.installTaskId).then(setTask).catch(() => {
67
+ patchState({ installTaskId: null });
68
+ });
69
+ startPolling(state.installTaskId);
70
+ }
71
+ return () => {
72
+ if (pollTimer.current) clearInterval(pollTimer.current);
73
+ };
74
+ }, [state.installTaskId, task, startPolling, patchState]);
75
+
76
+ if (!adapter) {
77
+ return (
78
+ <>
79
+ <h3>Install adapter</h3>
80
+ <div className="error-banner">No channel selected — go back to step 2.</div>
81
+ <div className="actions" style={{ marginTop: '1rem' }}>
82
+ <button className="secondary" onClick={back}>Back</button>
83
+ </div>
84
+ </>
85
+ );
86
+ }
87
+
88
+ const onStart = async () => {
89
+ setStarting(true);
90
+ setError(null);
91
+ setDirtyFiles(null);
92
+ try {
93
+ const res = await startInstallChannel(adapter);
94
+ patchState({ installTaskId: res.taskId });
95
+ startPolling(res.taskId);
96
+ } catch (err) {
97
+ const msg = err instanceof Error ? err.message : String(err);
98
+ if (msg.includes('uncommitted changes')) {
99
+ setDirtyFiles([msg]);
100
+ }
101
+ setError(msg);
102
+ } finally {
103
+ setStarting(false);
104
+ }
105
+ };
106
+
107
+ const installed = status?.channels[adapter]?.installed ?? false;
108
+ const taskTerminal = task?.status === 'completed' || task?.status === 'failed';
109
+ const adapterLabel = ADAPTER_LABELS[adapter];
110
+
111
+ return (
112
+ <>
113
+ <h3>Install {adapterLabel} adapter</h3>
114
+ {installed && !task && (
115
+ <div className="empty empty-rich" style={{ marginTop: '0.5rem' }}>
116
+ <p className="empty-headline" style={{ margin: 0 }}>{adapterLabel} adapter is already installed.</p>
117
+ <p className="muted" style={{ marginTop: '0.4rem' }}>
118
+ Move on to test the connection.
119
+ </p>
120
+ </div>
121
+ )}
122
+
123
+ {!installed && !task && (
124
+ <p className="muted">
125
+ {adapter === 'discord' ? (
126
+ <>
127
+ We'll fetch the adapter from the <code>channels</code> branch, copy it into <code>src/channels/discord.ts</code>,
128
+ register the import, run <code>pnpm install @chat-adapter/discord</code>, and rebuild. Takes ~1–3 min depending on cache.
129
+ </>
130
+ ) : (
131
+ <>
132
+ We'll fetch the adapter from the <code>channels</code> branch, copy 6 files into <code>src/channels/</code> + <code>setup/</code>,
133
+ register the import + setup step, run <code>pnpm install @chat-adapter/telegram</code>, and rebuild.
134
+ Takes ~1–3 min depending on cache.
135
+ </>
136
+ )}
137
+ </p>
138
+ )}
139
+
140
+ {task && (
141
+ <ol style={{ listStyle: 'none', padding: 0, marginTop: '0.5rem' }}>
142
+ {task.steps.map((s) => (
143
+ <li key={s.name} style={{ padding: '0.4rem 0', display: 'flex', gap: '0.5rem' }}>
144
+ <span aria-hidden style={{ width: '1.2rem' }}>
145
+ {s.status === 'completed' ? '✓' : s.status === 'running' ? '…' : s.status === 'failed' ? '✗' : '○'}
146
+ </span>
147
+ <code>{s.name}</code>
148
+ {s.error && <span className="dim" style={{ color: 'var(--error)' }}>— {s.error}</span>}
149
+ </li>
150
+ ))}
151
+ </ol>
152
+ )}
153
+
154
+ {error && (
155
+ <div className="error-banner" style={{ marginTop: '0.75rem' }}>
156
+ {error}
157
+ {dirtyFiles && (
158
+ <p style={{ marginTop: '0.4rem' }}>
159
+ Commit or stash the listed files, then click <strong>Retry</strong>.
160
+ </p>
161
+ )}
162
+ </div>
163
+ )}
164
+
165
+ <div className="actions" style={{ marginTop: '1rem' }}>
166
+ <button className="secondary" onClick={back}>Back</button>
167
+ {!installed && (
168
+ <button onClick={onStart} disabled={starting || (!!task && !taskTerminal)}>
169
+ {starting ? 'Starting…' : task && !taskTerminal ? 'Installing…' : task?.status === 'failed' ? 'Retry install' : 'Start install'}
170
+ </button>
171
+ )}
172
+ <button onClick={next} disabled={!installed && task?.status !== 'completed'}>
173
+ Next: test connection
174
+ </button>
175
+ </div>
176
+ </>
177
+ );
178
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Step 6 — Agent group.
3
+ *
4
+ * Two paths:
5
+ *
6
+ * (a) Pick an existing group. If the operator already has groups, list
7
+ * them and let them select one — the wire-channel step works the
8
+ * same way regardless of how the group was created.
9
+ *
10
+ * (b) Create a new group inline. Vault attach is offered on the group
11
+ * detail page after setup, not here, to keep the wizard fast.
12
+ *
13
+ * The picker UI is shared with /channels/new — both are thin wrappers
14
+ * around <AgentGroupPicker />. Once a group is picked / created, we
15
+ * stamp `agentGroupFolder` and `agentGroupName` on the wizard state and
16
+ * advance.
17
+ */
18
+ import { AgentGroupPicker, type PickedGroup } from '../AgentGroupPicker.tsx';
19
+ import type { StepProps } from './types.ts';
20
+
21
+ export function AgentGroupStep({ patchState, next, back }: StepProps) {
22
+ const onPicked = (g: PickedGroup) => {
23
+ patchState({ agentGroupFolder: g.folder, agentGroupName: g.name });
24
+ next();
25
+ };
26
+
27
+ return (
28
+ <>
29
+ <h3>Agent group</h3>
30
+ <p className="muted">
31
+ The agent group is the entity your bot represents. Pick an existing one or create a new one.
32
+ </p>
33
+
34
+ <AgentGroupPicker onPicked={onPicked} />
35
+
36
+ <div className="actions" style={{ marginTop: '1rem' }}>
37
+ <button className="secondary" onClick={back}>
38
+ Back
39
+ </button>
40
+ </div>
41
+ </>
42
+ );
43
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Step 2 — Channel pick.
3
+ *
4
+ * Phase 1 ships Discord + Telegram. Slack/WhatsApp/Teams render disabled
5
+ * with a "coming soon" badge so the operator sees the trajectory — when
6
+ * those phases land, the UI shape doesn't change, only the disabled flag
7
+ * flips.
8
+ */
9
+ import { useEffect } from 'react';
10
+ import type { ChannelAdapter, StepProps } from './types.ts';
11
+
12
+ interface AdapterCard {
13
+ key: ChannelAdapter | 'slack' | 'whatsapp';
14
+ name: string;
15
+ blurb: string;
16
+ available: boolean;
17
+ }
18
+
19
+ const ADAPTERS: AdapterCard[] = [
20
+ { key: 'telegram', name: 'Telegram', blurb: 'Easiest first run — BotFather + @userinfobot, ~1 minute.', available: true },
21
+ { key: 'discord', name: 'Discord', blurb: 'DM your bot or @-mention it in a server.', available: true },
22
+ { key: 'slack', name: 'Slack', blurb: 'Workspace bot.', available: false },
23
+ { key: 'whatsapp', name: 'WhatsApp', blurb: 'Cloud API.', available: false },
24
+ ];
25
+
26
+ export function ChannelPickStep({ state, patchState, next, back }: StepProps) {
27
+ // If the user re-enters this step without an adapter set, default to telegram
28
+ // (the lower-friction onboarding path).
29
+ useEffect(() => {
30
+ if (!state.adapter) patchState({ adapter: 'telegram' });
31
+ }, [state.adapter, patchState]);
32
+
33
+ return (
34
+ <>
35
+ <h3>Pick a channel</h3>
36
+ <p className="muted">Telegram is the lowest-friction setup; Discord is the production target. Slack + WhatsApp arrive in Phase 3.</p>
37
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: '0.75rem', marginTop: '0.75rem' }}>
38
+ {ADAPTERS.map((a) => {
39
+ const selected = state.adapter === a.key;
40
+ return (
41
+ <button
42
+ key={a.key}
43
+ type="button"
44
+ disabled={!a.available}
45
+ onClick={() => a.available && patchState({ adapter: a.key as ChannelAdapter })}
46
+ className="secondary"
47
+ style={{
48
+ textAlign: 'left',
49
+ padding: '1rem',
50
+ border: selected ? '2px solid var(--accent)' : '1px solid var(--border)',
51
+ background: selected ? 'var(--accent-soft)' : 'white',
52
+ opacity: a.available ? 1 : 0.5,
53
+ cursor: a.available ? 'pointer' : 'not-allowed',
54
+ }}
55
+ >
56
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
57
+ <strong>{a.name}</strong>
58
+ {!a.available && <span className="tag muted">coming soon</span>}
59
+ </div>
60
+ <p className="dim" style={{ margin: '0.4rem 0 0', fontSize: '0.85rem' }}>{a.blurb}</p>
61
+ </button>
62
+ );
63
+ })}
64
+ </div>
65
+
66
+ <div className="actions" style={{ marginTop: '1rem' }}>
67
+ <button className="secondary" onClick={back}>Back</button>
68
+ <button onClick={next} disabled={state.adapter !== 'discord' && state.adapter !== 'telegram'}>
69
+ Next: install adapter
70
+ </button>
71
+ </div>
72
+ </>
73
+ );
74
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Step 9 — Done.
3
+ *
4
+ * Terminal state. Links to the agent group's detail page so the operator
5
+ * can see the live session and inspect status. Does NOT clear the
6
+ * localStorage state — re-opening /setup just lands here, which is the
7
+ * desired behavior (you can revisit any step via the indicator).
8
+ */
9
+ import { Link } from 'react-router-dom';
10
+ import { ADAPTER_LABELS, SETUP_STORAGE_KEY, type StepProps } from './types.ts';
11
+
12
+ export function DoneStep({ state }: StepProps) {
13
+ const folder = state.agentGroupFolder;
14
+ const channel = state.adapter ? ADAPTER_LABELS[state.adapter] : 'your channel';
15
+ return (
16
+ <>
17
+ <h3>Done.</h3>
18
+ <p>
19
+ <code>{state.agentGroupName ?? folder}</code> is wired to {channel} and your first inbound has round-tripped.
20
+ </p>
21
+
22
+ <div className="actions" style={{ marginTop: '1rem' }}>
23
+ {folder && (
24
+ <Link to={`/groups/${encodeURIComponent(folder)}`}>
25
+ <button>Open group</button>
26
+ </Link>
27
+ )}
28
+ <Link to="/">
29
+ <button className="secondary">All groups</button>
30
+ </Link>
31
+ <button
32
+ className="secondary"
33
+ onClick={() => {
34
+ if (
35
+ confirm(
36
+ 'Clear local wizard state? Returning to /agent/setup later will restart from step 1 (your installed adapters / agent groups / wired channels are unaffected).',
37
+ )
38
+ ) {
39
+ localStorage.removeItem(SETUP_STORAGE_KEY);
40
+ window.location.href = '/';
41
+ }
42
+ }}
43
+ >
44
+ Clear wizard state
45
+ </button>
46
+ </div>
47
+ </>
48
+ );
49
+ }