@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,120 +0,0 @@
1
- /**
2
- * Round-trip tests for the services.json upsert. Mirrors scribe's coverage
3
- * — the cross-service contract here is the on-disk shape, so we verify
4
- * exactly that: a fresh write produces the canonical schema, a second
5
- * upsert with the same name replaces in-place rather than duplicating,
6
- * and an upsert with a different name appends.
7
- */
8
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
9
- import { tmpdir } from 'node:os';
10
- import { join } from 'node:path';
11
-
12
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
13
-
14
- import { upsertService } from './services-manifest.js';
15
-
16
- let tmp: string;
17
- let path: string;
18
-
19
- beforeEach(() => {
20
- tmp = mkdtempSync(join(tmpdir(), 'paraclaw-services-'));
21
- path = join(tmp, 'services.json');
22
- });
23
-
24
- afterEach(() => {
25
- rmSync(tmp, { recursive: true, force: true });
26
- });
27
-
28
- describe('upsertService', () => {
29
- it('creates the file + writes the canonical entry shape', () => {
30
- upsertService(
31
- {
32
- name: 'agent',
33
- port: 1944,
34
- paths: ['/agent'],
35
- health: '/api/health',
36
- version: '0.0.6-rc.1',
37
- displayName: 'Parachute Agent',
38
- tagline: 'Manage your Parachute agent groups + vault attachments.',
39
- },
40
- path,
41
- );
42
- const raw = JSON.parse(readFileSync(path, 'utf8'));
43
- expect(raw).toEqual({
44
- services: [
45
- {
46
- name: 'agent',
47
- port: 1944,
48
- paths: ['/agent'],
49
- health: '/api/health',
50
- version: '0.0.6-rc.1',
51
- displayName: 'Parachute Agent',
52
- tagline: 'Manage your Parachute agent groups + vault attachments.',
53
- },
54
- ],
55
- });
56
- });
57
-
58
- it('replaces an existing entry with the same name in-place', () => {
59
- upsertService({ name: 'agent', port: 1944, paths: ['/agent'], health: '/api/health', version: 'a' }, path);
60
- upsertService({ name: 'agent', port: 1944, paths: ['/agent'], health: '/api/health', version: 'b' }, path);
61
- const raw = JSON.parse(readFileSync(path, 'utf8')) as { services: { version: string }[] };
62
- expect(raw.services).toHaveLength(1);
63
- expect(raw.services[0].version).toBe('b');
64
- });
65
-
66
- it('appends a different-name entry without disturbing existing rows', () => {
67
- upsertService({ name: 'vault', port: 1940, paths: ['/vault'], health: '/health', version: '0.3.0' }, path);
68
- upsertService({ name: 'agent', port: 1944, paths: ['/agent'], health: '/api/health', version: '0.0.6-rc.1' }, path);
69
- const raw = JSON.parse(readFileSync(path, 'utf8')) as {
70
- services: { name: string }[];
71
- };
72
- expect(raw.services.map((s) => s.name).sort()).toEqual(['agent', 'vault']);
73
- });
74
-
75
- it('preserves hub-stamped fields on the row (e.g. installDir from parachute-hub#84)', () => {
76
- // The hub stamps `installDir` onto the row at install time. Paraclaw's
77
- // self-registration row shape doesn't know about that field, but the
78
- // upsert must merge rather than replace so the hub-stamped value
79
- // survives the second write — otherwise `parachute start agent` after
80
- // an auto-start round-trip can't resolve installDir → "unknown service".
81
- writeFileSync(
82
- path,
83
- JSON.stringify({
84
- services: [
85
- {
86
- name: 'agent',
87
- port: 1944,
88
- paths: ['/agent'],
89
- health: '/api/health',
90
- version: '0.0.7-rc.1',
91
- installDir: '/Users/test/.parachute/agent',
92
- },
93
- ],
94
- }),
95
- );
96
- upsertService(
97
- {
98
- name: 'agent',
99
- port: 1944,
100
- paths: ['/agent'],
101
- health: '/api/health',
102
- version: '0.0.8-rc.1',
103
- },
104
- path,
105
- );
106
- const raw = JSON.parse(readFileSync(path, 'utf8')) as {
107
- services: { version: string; installDir?: string }[];
108
- };
109
- expect(raw.services).toHaveLength(1);
110
- expect(raw.services[0].version).toBe('0.0.8-rc.1');
111
- expect(raw.services[0].installDir).toBe('/Users/test/.parachute/agent');
112
- });
113
-
114
- it('throws on a malformed existing manifest (so we never silently overwrite)', () => {
115
- writeFileSync(path, '{"services": "not an array"}');
116
- expect(() =>
117
- upsertService({ name: 'agent', port: 1944, paths: ['/agent'], health: '/api/health', version: 'x' }, path),
118
- ).toThrow(/malformed/);
119
- });
120
- });
@@ -1,61 +0,0 @@
1
- /**
2
- * Self-registration into `~/.parachute/services.json` on server startup.
3
- *
4
- * Mirrors `parachute-scribe/src/services-manifest.ts` deliberately — the
5
- * shape is the contract between every Parachute service and the hub
6
- * (`parachute-hub/src/services-manifest.ts` is the canonical reader).
7
- * Once paraclaw earns a slot in the hub's vendored fallback, the
8
- * manifest read flips to `.parachute/module.json`-backed and this file
9
- * becomes the live state-side companion. Until then, this is what makes
10
- * `parachute status` / `parachute expose` see paraclaw at all.
11
- *
12
- * Failure mode: any write error is logged + swallowed. Self-registration
13
- * is best-effort — the server still serves locally even if the manifest
14
- * write fails (permissions, disk full, race with another writer).
15
- */
16
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
17
- import { dirname, join } from 'node:path';
18
-
19
- import { PARACHUTE_DIR } from '../config.js';
20
-
21
- export interface ServiceEntry {
22
- name: string;
23
- port: number;
24
- paths: string[];
25
- health: string;
26
- version: string;
27
- displayName?: string;
28
- tagline?: string;
29
- }
30
-
31
- interface ServicesManifest {
32
- services: ServiceEntry[];
33
- }
34
-
35
- export function resolveManifestPath(): string {
36
- return join(PARACHUTE_DIR, 'services.json');
37
- }
38
-
39
- function readManifest(path: string): ServicesManifest {
40
- if (!existsSync(path)) return { services: [] };
41
- const raw = JSON.parse(readFileSync(path, 'utf8'));
42
- if (!raw || typeof raw !== 'object' || !Array.isArray((raw as { services?: unknown }).services)) {
43
- throw new Error(`services manifest at ${path} is malformed (missing "services" array)`);
44
- }
45
- return raw as ServicesManifest;
46
- }
47
-
48
- export function upsertService(entry: ServiceEntry, path: string = resolveManifestPath()): void {
49
- mkdirSync(dirname(path), { recursive: true });
50
- const manifest = readManifest(path);
51
- const idx = manifest.services.findIndex((s) => s.name === entry.name);
52
- // Merge rather than replace so fields the hub stamps onto the row
53
- // (`installDir` from parachute-hub#84, etc.) survive a self-registration
54
- // pass. Paraclaw still wins for the fields it owns — port, paths,
55
- // version, health — because they spread last.
56
- if (idx >= 0) manifest.services[idx] = { ...manifest.services[idx], ...entry };
57
- else manifest.services.push(entry);
58
- const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
59
- writeFileSync(tmp, `${JSON.stringify(manifest, null, 2)}\n`);
60
- renameSync(tmp, path);
61
- }
@@ -1,255 +0,0 @@
1
- /**
2
- * Static-serve mount-strip tests. Mirrors the cases in paraclaw#13:
3
- * - mount="" : behavior unchanged from pre-strip implementation
4
- * - mount=/X : prefix stripped before resolving against dist/
5
- * - SPA fallback returns dist/index.html with text/html content-type
6
- * - Path traversal still 400s
7
- * - Absent dist/ → 503
8
- *
9
- * Uses a real http server bound to an ephemeral port + a real dist fixture
10
- * on disk. Faster than spinning up the full paraclaw server (no DB / auth)
11
- * but still exercises the actual fs + node http stack — same shape as
12
- * auth.test.ts.
13
- */
14
- import http from 'node:http';
15
- import type { AddressInfo } from 'node:net';
16
- import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
17
- import { tmpdir } from 'node:os';
18
- import { join } from 'node:path';
19
-
20
- import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
21
-
22
- import { makeServeStatic, normalizeMount } from './static-serve.js';
23
-
24
- interface FetchResult {
25
- status: number;
26
- contentType: string;
27
- body: string;
28
- }
29
-
30
- function fetchPath(baseUrl: string, urlPath: string): Promise<FetchResult> {
31
- // Use http.request with explicit path so callers can send raw paths like
32
- // `/../etc/passwd` without the URL parser normalizing `..` away before the
33
- // request goes on the wire — that matters for the path-traversal test.
34
- const url = new URL(baseUrl);
35
- return new Promise((resolve, reject) => {
36
- const req = http.request(
37
- {
38
- host: url.hostname,
39
- port: url.port,
40
- method: 'GET',
41
- path: urlPath,
42
- },
43
- (res) => {
44
- const chunks: Buffer[] = [];
45
- res.on('data', (c) => chunks.push(c as Buffer));
46
- res.on('end', () => {
47
- resolve({
48
- status: res.statusCode ?? 0,
49
- contentType: String(res.headers['content-type'] ?? ''),
50
- body: Buffer.concat(chunks).toString('utf8'),
51
- });
52
- });
53
- },
54
- );
55
- req.on('error', reject);
56
- req.end();
57
- });
58
- }
59
-
60
- function startServer(handler: (req: http.IncomingMessage, res: http.ServerResponse) => void): Promise<{
61
- server: http.Server;
62
- baseUrl: string;
63
- }> {
64
- return new Promise((resolve) => {
65
- const server = http.createServer(handler);
66
- server.listen(0, '127.0.0.1', () => {
67
- const port = (server.address() as AddressInfo).port;
68
- resolve({ server, baseUrl: `http://127.0.0.1:${port}` });
69
- });
70
- });
71
- }
72
-
73
- function stopServer(server: http.Server): Promise<void> {
74
- return new Promise((resolve) => server.close(() => resolve()));
75
- }
76
-
77
- describe('normalizeMount', () => {
78
- it('returns empty for empty + slash', () => {
79
- expect(normalizeMount('')).toBe('');
80
- expect(normalizeMount('/')).toBe('');
81
- });
82
-
83
- it('strips trailing slash', () => {
84
- expect(normalizeMount('/agent')).toBe('/agent');
85
- expect(normalizeMount('/agent/')).toBe('/agent');
86
- expect(normalizeMount('/agent///')).toBe('/agent');
87
- });
88
- });
89
-
90
- describe('makeServeStatic', () => {
91
- let distDir: string;
92
-
93
- beforeEach(() => {
94
- distDir = mkdtempSync(join(tmpdir(), 'parachute-agent-static-'));
95
- writeFileSync(join(distDir, 'index.html'), '<!doctype html><html><body>shell</body></html>');
96
- mkdirSync(join(distDir, 'assets'));
97
- writeFileSync(join(distDir, 'assets', 'index-X.js'), 'export const k = 1;');
98
- writeFileSync(join(distDir, 'assets', 'index-X.css'), 'body { color: red }');
99
- });
100
-
101
- afterEach(() => {
102
- rmSync(distDir, { recursive: true, force: true });
103
- });
104
-
105
- describe('mount=""', () => {
106
- let server: http.Server;
107
- let baseUrl: string;
108
-
109
- beforeAll(() => {});
110
-
111
- beforeEach(async () => {
112
- const handler = makeServeStatic({ distDir, mount: '' });
113
- const ctx = await startServer((req, res) => {
114
- const url = new URL(req.url ?? '/', `http://${req.headers.host}`);
115
- handler(req, res, url.pathname);
116
- });
117
- server = ctx.server;
118
- baseUrl = ctx.baseUrl;
119
- });
120
-
121
- afterEach(async () => {
122
- await stopServer(server);
123
- });
124
-
125
- it('GET / serves index.html as text/html', async () => {
126
- const r = await fetchPath(baseUrl, '/');
127
- expect(r.status).toBe(200);
128
- expect(r.contentType).toContain('text/html');
129
- expect(r.body).toContain('shell');
130
- });
131
-
132
- it('GET /assets/index-X.js serves the file as application/javascript', async () => {
133
- const r = await fetchPath(baseUrl, '/assets/index-X.js');
134
- expect(r.status).toBe(200);
135
- expect(r.contentType).toContain('application/javascript');
136
- expect(r.body).toBe('export const k = 1;');
137
- });
138
-
139
- it('GET /unknown/path SPA-falls-back to index.html', async () => {
140
- const r = await fetchPath(baseUrl, '/unknown/path');
141
- expect(r.status).toBe(200);
142
- expect(r.contentType).toContain('text/html');
143
- expect(r.body).toContain('shell');
144
- });
145
- });
146
-
147
- describe('mount="/agent"', () => {
148
- let server: http.Server;
149
- let baseUrl: string;
150
-
151
- beforeEach(async () => {
152
- const handler = makeServeStatic({ distDir, mount: '/agent' });
153
- const ctx = await startServer((req, res) => {
154
- const url = new URL(req.url ?? '/', `http://${req.headers.host}`);
155
- handler(req, res, url.pathname);
156
- });
157
- server = ctx.server;
158
- baseUrl = ctx.baseUrl;
159
- });
160
-
161
- afterEach(async () => {
162
- await stopServer(server);
163
- });
164
-
165
- it('GET /agent/ serves index.html as text/html (prefix stripped → /)', async () => {
166
- const r = await fetchPath(baseUrl, '/agent/');
167
- expect(r.status).toBe(200);
168
- expect(r.contentType).toContain('text/html');
169
- expect(r.body).toContain('shell');
170
- });
171
-
172
- it('GET /agent/assets/index-X.js serves the JS bundle correctly (the regression #13 fixed)', async () => {
173
- const r = await fetchPath(baseUrl, '/agent/assets/index-X.js');
174
- expect(r.status).toBe(200);
175
- expect(r.contentType).toContain('application/javascript');
176
- expect(r.body).toBe('export const k = 1;');
177
- });
178
-
179
- it('GET /agent/assets/index-X.css serves CSS correctly', async () => {
180
- const r = await fetchPath(baseUrl, '/agent/assets/index-X.css');
181
- expect(r.status).toBe(200);
182
- expect(r.contentType).toContain('text/css');
183
- expect(r.body).toBe('body { color: red }');
184
- });
185
-
186
- it('GET /agent/some/spa/route SPA-falls-back to index.html (BrowserRouter resolves)', async () => {
187
- const r = await fetchPath(baseUrl, '/agent/some/spa/route');
188
- expect(r.status).toBe(200);
189
- expect(r.contentType).toContain('text/html');
190
- expect(r.body).toContain('shell');
191
- });
192
-
193
- it('GET /assets/index-X.js (no prefix) still serves the file — matches notes-serve behavior', async () => {
194
- // Defense-in-depth: paths that don't start with the mount aren't 404'd.
195
- // The real frontend never makes these requests (Vite bakes /agent/ into
196
- // the bundle's asset URLs), but a direct test from a healthcheck or
197
- // local-dev curl shouldn't break — same shape as
198
- // parachute-hub/src/notes-serve.ts at paths-without-prefix.
199
- const r = await fetchPath(baseUrl, '/assets/index-X.js');
200
- expect(r.status).toBe(200);
201
- expect(r.contentType).toContain('application/javascript');
202
- expect(r.body).toBe('export const k = 1;');
203
- });
204
-
205
- it('GET /agent is a SPA route (no trailing slash) → SPA shell', async () => {
206
- const r = await fetchPath(baseUrl, '/agent');
207
- expect(r.status).toBe(200);
208
- expect(r.contentType).toContain('text/html');
209
- expect(r.body).toContain('shell');
210
- });
211
- });
212
-
213
- describe('safety', () => {
214
- // The URL constructor in server.ts (and in the http test fixture above)
215
- // normalizes `..` away before the handler ever sees the path, so the
216
- // `..` guard inside makeServeStatic is defense-in-depth — tested by
217
- // calling the handler directly with a synthetic url-path.
218
- it('handler called with a path containing `..` → 400 bad path', () => {
219
- const handler = makeServeStatic({ distDir, mount: '' });
220
- let status = 0;
221
- let body = '';
222
- const res = {
223
- writeHead(s: number) {
224
- status = s;
225
- },
226
- end(chunk?: string) {
227
- body = chunk ?? '';
228
- },
229
- } as unknown as http.ServerResponse;
230
- handler({} as http.IncomingMessage, res, '/../etc/passwd');
231
- expect(status).toBe(400);
232
- expect(body).toContain('bad path');
233
- });
234
- });
235
-
236
- describe('missing dist', () => {
237
- it('returns 503 with a build-instruction body', async () => {
238
- const handler = makeServeStatic({
239
- distDir: join(tmpdir(), 'paraclaw-does-not-exist-' + Date.now()),
240
- mount: '',
241
- });
242
- const ctx = await startServer((req, res) => {
243
- const url = new URL(req.url ?? '/', `http://${req.headers.host}`);
244
- handler(req, res, url.pathname);
245
- });
246
- try {
247
- const r = await fetchPath(ctx.baseUrl, '/');
248
- expect(r.status).toBe(503);
249
- expect(r.body).toContain('UI bundle not found');
250
- } finally {
251
- await stopServer(ctx.server);
252
- }
253
- });
254
- });
255
- });
@@ -1,104 +0,0 @@
1
- /**
2
- * Static-file handler for the built UI bundle, with optional mount-prefix
3
- * stripping.
4
- *
5
- * When parachute-agent is fronted by `parachute expose tailnet` at a path
6
- * prefix (e.g. `https://<host>/agent/`), tailscale serve forwards the
7
- * request with the prefix preserved. Combined with a Vite build that bakes
8
- * the prefix into asset URLs (`VITE_BASE_PATH=/agent/`), the browser asks
9
- * for `/agent/assets/index-X.js` — which a 1:1 path-to-dist map turns into
10
- * the non-existent `dist/agent/assets/index-X.js`, falls back to the SPA
11
- * shell, and ships `text/html` for what should be a JS module. Page never
12
- * boots.
13
- *
14
- * Mirrors `parachute-hub/src/notes-serve.ts:96-115` — strip the mount
15
- * prefix off the request path before resolving against `dist/`. Standalone
16
- * parachute-agent (`mount=""`) is unchanged: the strip is a no-op when no
17
- * prefix is configured.
18
- */
19
- import http from 'node:http';
20
- import fs from 'node:fs';
21
- import path from 'node:path';
22
-
23
- export const MIME_TYPES: Record<string, string> = {
24
- '.html': 'text/html; charset=utf-8',
25
- '.js': 'application/javascript; charset=utf-8',
26
- '.css': 'text/css; charset=utf-8',
27
- '.json': 'application/json; charset=utf-8',
28
- '.svg': 'image/svg+xml',
29
- '.png': 'image/png',
30
- '.ico': 'image/x-icon',
31
- '.txt': 'text/plain; charset=utf-8',
32
- };
33
-
34
- /**
35
- * Normalize a mount value to either `""` (no strip) or a prefix without a
36
- * trailing slash (e.g. `/agent`). `""` and `"/"` both mean "no strip" so
37
- * standalone deployments at the origin root don't accidentally strip a
38
- * leading `/`.
39
- */
40
- export function normalizeMount(raw: string): string {
41
- if (raw === '' || raw === '/') return '';
42
- return raw.replace(/\/+$/, '');
43
- }
44
-
45
- export interface StaticServeOpts {
46
- distDir: string;
47
- /** Mount prefix to strip before resolving against `distDir` (e.g. `/agent`). */
48
- mount: string;
49
- }
50
-
51
- /**
52
- * Build the static-serve handler closure. The returned function has the
53
- * same shape as the inline handler that previously lived in server.ts —
54
- * `(req, res, urlPath)` — but resolves through the configured mount + dist.
55
- *
56
- * SPA-fallback behavior matches the prior implementation: any path that
57
- * doesn't resolve to a real file under `dist/` returns `index.html` so
58
- * BrowserRouter routes work on hard-refresh. The mount-strip only changes
59
- * the lookup; missing-file fallback is unchanged.
60
- */
61
- export function makeServeStatic(opts: StaticServeOpts) {
62
- const { distDir, mount } = opts;
63
- return function serveStatic(_req: http.IncomingMessage, res: http.ServerResponse, urlPath: string): void {
64
- if (!fs.existsSync(distDir)) {
65
- res.writeHead(503, { 'content-type': 'text/plain; charset=utf-8' });
66
- res.end(
67
- 'UI bundle not found at ' +
68
- distDir +
69
- '\n\nIn dev: run `pnpm --filter @parachute-agent/web-ui dev` and open http://localhost:5173/.\n' +
70
- 'In prod: run `pnpm --filter @parachute-agent/web-ui build` first.',
71
- );
72
- return;
73
- }
74
-
75
- let pathname = urlPath;
76
- if (mount && (pathname === mount || pathname.startsWith(`${mount}/`))) {
77
- pathname = pathname.slice(mount.length) || '/';
78
- }
79
-
80
- let rel = pathname.replace(/^\/+/, '') || 'index.html';
81
-
82
- if (rel.includes('..')) {
83
- res.writeHead(400);
84
- res.end('bad path');
85
- return;
86
- }
87
-
88
- let abs = path.join(distDir, rel);
89
- if (!abs.startsWith(distDir)) {
90
- res.writeHead(403);
91
- res.end('forbidden');
92
- return;
93
- }
94
-
95
- if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) {
96
- abs = path.join(distDir, 'index.html');
97
- }
98
- const ext = path.extname(abs).toLowerCase();
99
- const mime = MIME_TYPES[ext] ?? 'application/octet-stream';
100
- const stream = fs.createReadStream(abs);
101
- res.writeHead(200, { 'content-type': mime });
102
- stream.pipe(res);
103
- };
104
- }
@@ -1,116 +0,0 @@
1
- /**
2
- * Token validation paths for Telegram. Mirror of discord-validate.test.ts so
3
- * the same wizard surface can render either result with one error shape.
4
- */
5
- import { describe, expect, it } from 'vitest';
6
-
7
- import { validateTelegramBotToken } from './telegram-validate.js';
8
-
9
- const VALID_TOKEN = '1234567890:ABCdefGhIJklmnopQRsTUvwxyz0123456789';
10
-
11
- function fakeFetch(impl: (url: string) => { status?: number; body: unknown }): typeof fetch {
12
- return (async (input: string | URL | Request) => {
13
- const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
14
- const { status = 200, body } = impl(url);
15
- return new Response(JSON.stringify(body), {
16
- status,
17
- headers: { 'content-type': 'application/json' },
18
- });
19
- }) as typeof fetch;
20
- }
21
-
22
- describe('validateTelegramBotToken', () => {
23
- it('rejects an empty token without fetching', async () => {
24
- const result = await validateTelegramBotToken(' ', (() => {
25
- throw new Error('fetch should not be called');
26
- }) as unknown as typeof fetch);
27
- expect(result.ok).toBe(false);
28
- if (!result.ok) {
29
- expect(result.status).toBe(400);
30
- expect(result.error).toMatch(/empty/);
31
- }
32
- });
33
-
34
- it('rejects a malformed token shape without fetching', async () => {
35
- const result = await validateTelegramBotToken('not-a-real-token', (() => {
36
- throw new Error('fetch should not be called');
37
- }) as unknown as typeof fetch);
38
- expect(result.ok).toBe(false);
39
- if (!result.ok) {
40
- expect(result.status).toBe(400);
41
- expect(result.error).toMatch(/format invalid/);
42
- }
43
- });
44
-
45
- it('returns identity on success', async () => {
46
- const fetchImpl = fakeFetch(() => ({
47
- status: 200,
48
- body: {
49
- ok: true,
50
- result: { id: 6037840640, username: 'andy_bot', first_name: 'Andy', is_bot: true },
51
- },
52
- }));
53
- const result = await validateTelegramBotToken(VALID_TOKEN, fetchImpl);
54
- expect(result.ok).toBe(true);
55
- if (result.ok) {
56
- expect(result.identity.id).toBe(6037840640);
57
- expect(result.identity.username).toBe('andy_bot');
58
- expect(result.identity.firstName).toBe('Andy');
59
- expect(result.identity.isBot).toBe(true);
60
- }
61
- });
62
-
63
- it('rejects when telegram returns ok:false', async () => {
64
- const fetchImpl = fakeFetch(() => ({
65
- status: 401,
66
- body: { ok: false, error_code: 401, description: 'Unauthorized' },
67
- }));
68
- const result = await validateTelegramBotToken(VALID_TOKEN, fetchImpl);
69
- expect(result.ok).toBe(false);
70
- if (!result.ok) {
71
- expect(result.status).toBe(401);
72
- expect(result.error).toMatch(/rejected token/i);
73
- }
74
- });
75
-
76
- it('rejects a non-bot account', async () => {
77
- const fetchImpl = fakeFetch(() => ({
78
- status: 200,
79
- body: {
80
- ok: true,
81
- result: { id: 1, username: 'human', first_name: 'Real Person', is_bot: false },
82
- },
83
- }));
84
- const result = await validateTelegramBotToken(VALID_TOKEN, fetchImpl);
85
- expect(result.ok).toBe(false);
86
- if (!result.ok) {
87
- expect(result.status).toBe(400);
88
- expect(result.error).toMatch(/not a bot/i);
89
- }
90
- });
91
-
92
- it('returns 502 when telegram is unreachable', async () => {
93
- const fetchImpl = (async () => {
94
- throw new TypeError('network down');
95
- }) as unknown as typeof fetch;
96
- const result = await validateTelegramBotToken(VALID_TOKEN, fetchImpl);
97
- expect(result.ok).toBe(false);
98
- if (!result.ok) {
99
- expect(result.status).toBe(502);
100
- expect(result.error).toMatch(/unreachable/);
101
- }
102
- });
103
-
104
- it('returns 502 on malformed result body', async () => {
105
- const fetchImpl = fakeFetch(() => ({
106
- status: 200,
107
- body: { ok: true, result: {} },
108
- }));
109
- const result = await validateTelegramBotToken(VALID_TOKEN, fetchImpl);
110
- expect(result.ok).toBe(false);
111
- if (!result.ok) {
112
- expect(result.status).toBe(502);
113
- expect(result.error).toMatch(/malformed/);
114
- }
115
- });
116
- });