@openparachute/agent 0.1.2 → 0.2.2

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 (608) hide show
  1. package/.parachute/module.json +124 -8
  2. package/LICENSE +2 -16
  3. package/README.md +118 -166
  4. package/package.json +35 -42
  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/dist/assets/index-C-iWdFFV.css +1 -0
  103. package/web/ui/dist/assets/index-VFETBk0a.js +60 -0
  104. package/web/ui/dist/index.html +15 -0
  105. package/web/ui/tsconfig.json +2 -1
  106. package/.claude/scheduled_tasks.lock +0 -1
  107. package/.claude/settings.json +0 -5
  108. package/.claude/skills/add-atomic-chat-tool/SKILL.md +0 -243
  109. package/.claude/skills/add-atomic-chat-tool/atomic-chat-mcp-stdio.ts +0 -229
  110. package/.claude/skills/add-codex/SKILL.md +0 -161
  111. package/.claude/skills/add-dashboard/SKILL.md +0 -138
  112. package/.claude/skills/add-dashboard/resources/dashboard-pusher.ts +0 -495
  113. package/.claude/skills/add-emacs/SKILL.md +0 -296
  114. package/.claude/skills/add-gcal-tool/SKILL.md +0 -210
  115. package/.claude/skills/add-gchat/REMOVE.md +0 -6
  116. package/.claude/skills/add-gchat/SKILL.md +0 -92
  117. package/.claude/skills/add-gchat/VERIFY.md +0 -3
  118. package/.claude/skills/add-github/REMOVE.md +0 -6
  119. package/.claude/skills/add-github/SKILL.md +0 -148
  120. package/.claude/skills/add-github/VERIFY.md +0 -3
  121. package/.claude/skills/add-gmail-tool/SKILL.md +0 -229
  122. package/.claude/skills/add-imessage/REMOVE.md +0 -6
  123. package/.claude/skills/add-imessage/SKILL.md +0 -113
  124. package/.claude/skills/add-imessage/VERIFY.md +0 -3
  125. package/.claude/skills/add-karpathy-llm-wiki/SKILL.md +0 -110
  126. package/.claude/skills/add-karpathy-llm-wiki/llm-wiki.md +0 -75
  127. package/.claude/skills/add-linear/REMOVE.md +0 -6
  128. package/.claude/skills/add-linear/SKILL.md +0 -168
  129. package/.claude/skills/add-linear/VERIFY.md +0 -3
  130. package/.claude/skills/add-macos-statusbar/SKILL.md +0 -133
  131. package/.claude/skills/add-macos-statusbar/add/src/statusbar.swift +0 -147
  132. package/.claude/skills/add-matrix/REMOVE.md +0 -6
  133. package/.claude/skills/add-matrix/SKILL.md +0 -148
  134. package/.claude/skills/add-matrix/VERIFY.md +0 -3
  135. package/.claude/skills/add-ollama-provider/SKILL.md +0 -179
  136. package/.claude/skills/add-ollama-tool/SKILL.md +0 -193
  137. package/.claude/skills/add-opencode/SKILL.md +0 -229
  138. package/.claude/skills/add-parallel/SKILL.md +0 -290
  139. package/.claude/skills/add-resend/REMOVE.md +0 -6
  140. package/.claude/skills/add-resend/SKILL.md +0 -93
  141. package/.claude/skills/add-resend/VERIFY.md +0 -3
  142. package/.claude/skills/add-signal/REMOVE.md +0 -13
  143. package/.claude/skills/add-signal/SKILL.md +0 -318
  144. package/.claude/skills/add-signal/VERIFY.md +0 -5
  145. package/.claude/skills/add-slack/REMOVE.md +0 -6
  146. package/.claude/skills/add-slack/SKILL.md +0 -112
  147. package/.claude/skills/add-slack/VERIFY.md +0 -3
  148. package/.claude/skills/add-teams/REMOVE.md +0 -6
  149. package/.claude/skills/add-teams/SKILL.md +0 -207
  150. package/.claude/skills/add-teams/VERIFY.md +0 -3
  151. package/.claude/skills/add-vercel/SKILL.md +0 -147
  152. package/.claude/skills/add-vercel/container-skills/vercel-cli/SKILL.md +0 -103
  153. package/.claude/skills/add-webex/REMOVE.md +0 -6
  154. package/.claude/skills/add-webex/SKILL.md +0 -88
  155. package/.claude/skills/add-webex/VERIFY.md +0 -3
  156. package/.claude/skills/add-wechat/REMOVE.md +0 -49
  157. package/.claude/skills/add-wechat/SKILL.md +0 -170
  158. package/.claude/skills/add-wechat/scripts/wire-dm.ts +0 -172
  159. package/.claude/skills/add-whatsapp/SKILL.md +0 -264
  160. package/.claude/skills/add-whatsapp-cloud/REMOVE.md +0 -6
  161. package/.claude/skills/add-whatsapp-cloud/SKILL.md +0 -95
  162. package/.claude/skills/add-whatsapp-cloud/VERIFY.md +0 -3
  163. package/.claude/skills/claw/SKILL.md +0 -131
  164. package/.claude/skills/claw/scripts/claw +0 -374
  165. package/.claude/skills/convert-to-apple-container/SKILL.md +0 -212
  166. package/.claude/skills/customize/SKILL.md +0 -110
  167. package/.claude/skills/debug/SKILL.md +0 -349
  168. package/.claude/skills/get-qodo-rules/SKILL.md +0 -122
  169. package/.claude/skills/get-qodo-rules/references/output-format.md +0 -41
  170. package/.claude/skills/get-qodo-rules/references/pagination.md +0 -33
  171. package/.claude/skills/get-qodo-rules/references/repository-scope.md +0 -26
  172. package/.claude/skills/init-first-agent/SKILL.md +0 -120
  173. package/.claude/skills/init-onecli/SKILL.md +0 -270
  174. package/.claude/skills/manage-channels/SKILL.md +0 -87
  175. package/.claude/skills/manage-mounts/SKILL.md +0 -47
  176. package/.claude/skills/migrate-from-openclaw/MIGRATE_CRONS.md +0 -100
  177. package/.claude/skills/migrate-from-openclaw/SKILL.md +0 -447
  178. package/.claude/skills/migrate-from-openclaw/scripts/discover-openclaw.ts +0 -734
  179. package/.claude/skills/migrate-from-openclaw/scripts/extract-channel-credentials.ts +0 -476
  180. package/.claude/skills/migrate-nanoclaw/SKILL.md +0 -484
  181. package/.claude/skills/migrate-nanoclaw/diagnostics.md +0 -51
  182. package/.claude/skills/qodo-pr-resolver/SKILL.md +0 -326
  183. package/.claude/skills/qodo-pr-resolver/resources/providers.md +0 -329
  184. package/.claude/skills/update-nanoclaw/SKILL.md +0 -243
  185. package/.claude/skills/update-nanoclaw/diagnostics.md +0 -48
  186. package/.claude/skills/update-skills/SKILL.md +0 -130
  187. package/.claude/skills/use-native-credential-proxy/SKILL.md +0 -167
  188. package/.claude/skills/x-integration/SKILL.md +0 -417
  189. package/.claude/skills/x-integration/agent.ts +0 -243
  190. package/.claude/skills/x-integration/host.ts +0 -155
  191. package/.claude/skills/x-integration/lib/browser.ts +0 -148
  192. package/.claude/skills/x-integration/lib/config.ts +0 -62
  193. package/.claude/skills/x-integration/scripts/like.ts +0 -56
  194. package/.claude/skills/x-integration/scripts/post.ts +0 -66
  195. package/.claude/skills/x-integration/scripts/quote.ts +0 -80
  196. package/.claude/skills/x-integration/scripts/reply.ts +0 -74
  197. package/.claude/skills/x-integration/scripts/retweet.ts +0 -62
  198. package/.claude/skills/x-integration/scripts/setup.ts +0 -87
  199. package/.github/CODEOWNERS +0 -10
  200. package/.github/PULL_REQUEST_TEMPLATE.md +0 -18
  201. package/.github/workflows/bump-version.yml +0 -35
  202. package/.github/workflows/ci.yml +0 -39
  203. package/.github/workflows/label-pr.yml +0 -40
  204. package/.github/workflows/update-tokens.yml +0 -43
  205. package/.husky/pre-commit +0 -1
  206. package/.mcp.json +0 -3
  207. package/.nvmrc +0 -1
  208. package/.prettierrc +0 -4
  209. package/CHANGELOG.md +0 -263
  210. package/CLAUDE.md +0 -307
  211. package/CODE_OF_CONDUCT.md +0 -128
  212. package/CONTRIBUTING.md +0 -159
  213. package/CONTRIBUTORS.md +0 -26
  214. package/LICENSE-NANOCLAW-MIT +0 -21
  215. package/README_ja.md +0 -194
  216. package/README_zh.md +0 -194
  217. package/assets/nanoclaw-favicon.png +0 -0
  218. package/assets/nanoclaw-icon.png +0 -0
  219. package/assets/nanoclaw-logo-dark.png +0 -0
  220. package/assets/nanoclaw-logo.png +0 -0
  221. package/assets/nanoclaw-profile.jpeg +0 -0
  222. package/assets/nanoclaw-sales.png +0 -0
  223. package/assets/social-preview.jpg +0 -0
  224. package/config-examples/mount-allowlist.json +0 -25
  225. package/container/.dockerignore +0 -2
  226. package/container/CLAUDE.md +0 -21
  227. package/container/Dockerfile +0 -121
  228. package/container/agent-runner/bun.lock +0 -243
  229. package/container/agent-runner/package.json +0 -22
  230. package/container/agent-runner/scripts/sdk-signal-probe.ts +0 -169
  231. package/container/agent-runner/src/config.ts +0 -55
  232. package/container/agent-runner/src/db/connection.ts +0 -267
  233. package/container/agent-runner/src/db/index.ts +0 -20
  234. package/container/agent-runner/src/db/messages-in.ts +0 -138
  235. package/container/agent-runner/src/db/messages-out.ts +0 -143
  236. package/container/agent-runner/src/db/session-routing.ts +0 -30
  237. package/container/agent-runner/src/db/session-state.test.ts +0 -100
  238. package/container/agent-runner/src/db/session-state.ts +0 -79
  239. package/container/agent-runner/src/destinations.ts +0 -135
  240. package/container/agent-runner/src/formatter.test.ts +0 -167
  241. package/container/agent-runner/src/formatter.ts +0 -260
  242. package/container/agent-runner/src/index.ts +0 -110
  243. package/container/agent-runner/src/integration.test.ts +0 -121
  244. package/container/agent-runner/src/mcp-tools/agents.instructions.md +0 -26
  245. package/container/agent-runner/src/mcp-tools/agents.ts +0 -66
  246. package/container/agent-runner/src/mcp-tools/core.instructions.md +0 -27
  247. package/container/agent-runner/src/mcp-tools/core.ts +0 -262
  248. package/container/agent-runner/src/mcp-tools/index.ts +0 -22
  249. package/container/agent-runner/src/mcp-tools/interactive.instructions.md +0 -22
  250. package/container/agent-runner/src/mcp-tools/interactive.ts +0 -169
  251. package/container/agent-runner/src/mcp-tools/scheduling.instructions.md +0 -40
  252. package/container/agent-runner/src/mcp-tools/scheduling.ts +0 -299
  253. package/container/agent-runner/src/mcp-tools/self-mod.instructions.md +0 -25
  254. package/container/agent-runner/src/mcp-tools/self-mod.ts +0 -120
  255. package/container/agent-runner/src/mcp-tools/server.ts +0 -54
  256. package/container/agent-runner/src/mcp-tools/types.ts +0 -6
  257. package/container/agent-runner/src/poll-loop.test.ts +0 -248
  258. package/container/agent-runner/src/poll-loop.ts +0 -437
  259. package/container/agent-runner/src/providers/claude.ts +0 -379
  260. package/container/agent-runner/src/providers/factory.test.ts +0 -19
  261. package/container/agent-runner/src/providers/factory.ts +0 -13
  262. package/container/agent-runner/src/providers/index.ts +0 -6
  263. package/container/agent-runner/src/providers/mock.ts +0 -77
  264. package/container/agent-runner/src/providers/provider-registry.ts +0 -33
  265. package/container/agent-runner/src/providers/types.ts +0 -82
  266. package/container/agent-runner/src/scheduling/task-script.ts +0 -121
  267. package/container/agent-runner/src/timezone.test.ts +0 -93
  268. package/container/agent-runner/src/timezone.ts +0 -107
  269. package/container/agent-runner/tsconfig.json +0 -14
  270. package/container/build.sh +0 -48
  271. package/container/entrypoint.sh +0 -16
  272. package/container/skills/agent-browser/SKILL.md +0 -159
  273. package/container/skills/frontend-engineer/SKILL.md +0 -157
  274. package/container/skills/self-customize/SKILL.md +0 -87
  275. package/container/skills/slack-formatting/SKILL.md +0 -94
  276. package/container/skills/vercel-cli/SKILL.md +0 -111
  277. package/container/skills/welcome/SKILL.md +0 -85
  278. package/docs/APPLE-CONTAINER-NETWORKING.md +0 -90
  279. package/docs/BRANCH-FORK-MAINTENANCE.md +0 -81
  280. package/docs/README.md +0 -25
  281. package/docs/SDK_DEEP_DIVE.md +0 -643
  282. package/docs/SECURITY.md +0 -162
  283. package/docs/agent-runner-details.md +0 -749
  284. package/docs/api-details.md +0 -365
  285. package/docs/architecture-diagram.html +0 -422
  286. package/docs/architecture-diagram.md +0 -215
  287. package/docs/architecture.md +0 -751
  288. package/docs/audit/2026-04-30-channel-endpoint-audit.md +0 -36
  289. package/docs/build-and-runtime.md +0 -80
  290. package/docs/cross-mount-stress/README.md +0 -112
  291. package/docs/cross-mount-stress/container-writer-retry.mjs +0 -55
  292. package/docs/cross-mount-stress/container-writer-slow.mjs +0 -42
  293. package/docs/cross-mount-stress/container-writer.mjs +0 -47
  294. package/docs/cross-mount-stress/host-writer-retry.mjs +0 -55
  295. package/docs/cross-mount-stress/host-writer-slow.mjs +0 -43
  296. package/docs/cross-mount-stress/host-writer.mjs +0 -47
  297. package/docs/db-central.md +0 -316
  298. package/docs/db-session.md +0 -183
  299. package/docs/db.md +0 -119
  300. package/docs/design/2026-04-29-vault-management-ui.md +0 -231
  301. package/docs/design/2026-04-30-channel-wiring-rework.md +0 -234
  302. package/docs/design/2026-05-01-channel-wiring-approvals-deep-dive.md +0 -272
  303. package/docs/design/2026-05-02-channel-policy-and-approval-routing.md +0 -250
  304. package/docs/docker-sandboxes.md +0 -359
  305. package/docs/isolation-model.md +0 -88
  306. package/docs/ollama.md +0 -79
  307. package/docs/parachute-integration.md +0 -109
  308. package/docs/post-night-rebirth-reflections.md +0 -151
  309. package/eslint.config.js +0 -32
  310. package/pnpm-workspace.yaml +0 -8
  311. package/repo-tokens/README.md +0 -113
  312. package/repo-tokens/action.yml +0 -186
  313. package/repo-tokens/badge.svg +0 -23
  314. package/repo-tokens/examples/green.svg +0 -14
  315. package/repo-tokens/examples/red.svg +0 -14
  316. package/repo-tokens/examples/yellow-green.svg +0 -14
  317. package/repo-tokens/examples/yellow.svg +0 -14
  318. package/scripts/chat.ts +0 -101
  319. package/scripts/cleanup-sessions.sh +0 -150
  320. package/scripts/init-cli-agent.ts +0 -172
  321. package/scripts/init-first-agent.ts +0 -378
  322. package/scripts/parachute.ts +0 -158
  323. package/scripts/run-migrations.ts +0 -105
  324. package/scripts/sanity-live-poll.ts +0 -95
  325. package/scripts/seed-discord.ts +0 -80
  326. package/scripts/test-v2-agent.ts +0 -106
  327. package/scripts/test-v2-channel-e2e.ts +0 -265
  328. package/scripts/test-v2-host.ts +0 -184
  329. package/src/channels/adapter.ts +0 -214
  330. package/src/channels/api-translator.test.ts +0 -306
  331. package/src/channels/api-translator.ts +0 -214
  332. package/src/channels/ask-question.ts +0 -46
  333. package/src/channels/channel-registry.test.ts +0 -421
  334. package/src/channels/channel-registry.ts +0 -313
  335. package/src/channels/chat-sdk-bridge.test.ts +0 -84
  336. package/src/channels/chat-sdk-bridge.ts +0 -652
  337. package/src/channels/cli.ts +0 -276
  338. package/src/channels/discord.ts +0 -90
  339. package/src/channels/index.ts +0 -17
  340. package/src/channels/telegram-markdown-sanitize.test.ts +0 -78
  341. package/src/channels/telegram-markdown-sanitize.ts +0 -55
  342. package/src/channels/telegram-pairing.test.ts +0 -254
  343. package/src/channels/telegram-pairing.ts +0 -339
  344. package/src/channels/telegram.ts +0 -279
  345. package/src/channels/trust-hint.test.ts +0 -48
  346. package/src/channels/trust-hint.ts +0 -75
  347. package/src/claude-md-compose.migrate.test.ts +0 -64
  348. package/src/claude-md-compose.ts +0 -205
  349. package/src/command-gate.ts +0 -63
  350. package/src/config.test.ts +0 -93
  351. package/src/config.ts +0 -128
  352. package/src/container-config.ts +0 -167
  353. package/src/container-runner.test.ts +0 -32
  354. package/src/container-runner.ts +0 -576
  355. package/src/container-runtime.test.ts +0 -269
  356. package/src/container-runtime.ts +0 -167
  357. package/src/db/_bun-sqlite-shim.ts +0 -88
  358. package/src/db/agent-activity.test.ts +0 -155
  359. package/src/db/agent-activity.ts +0 -121
  360. package/src/db/agent-groups.ts +0 -77
  361. package/src/db/connection.migrate.test.ts +0 -176
  362. package/src/db/connection.ts +0 -259
  363. package/src/db/db-v2.test.ts +0 -440
  364. package/src/db/dropped-messages.ts +0 -44
  365. package/src/db/index.ts +0 -40
  366. package/src/db/messaging-groups.ts +0 -252
  367. package/src/db/migrations/001-initial.ts +0 -112
  368. package/src/db/migrations/002-chat-sdk-state.ts +0 -36
  369. package/src/db/migrations/008-dropped-messages.ts +0 -27
  370. package/src/db/migrations/009-drop-pending-credentials.ts +0 -13
  371. package/src/db/migrations/010-engage-modes.ts +0 -103
  372. package/src/db/migrations/011-pending-sender-approvals.ts +0 -40
  373. package/src/db/migrations/012-channel-registration.ts +0 -48
  374. package/src/db/migrations/013-approval-render-metadata.ts +0 -27
  375. package/src/db/migrations/014-secrets.ts +0 -44
  376. package/src/db/migrations/015-secrets-drop-host-pattern.ts +0 -18
  377. package/src/db/migrations/016-secret-assignments.ts +0 -30
  378. package/src/db/migrations/017-agent-activity.ts +0 -40
  379. package/src/db/migrations/018-oauth-app-configs.ts +0 -34
  380. package/src/db/migrations/019-oauth-app-connections.ts +0 -48
  381. package/src/db/migrations/020-agent-app-connections.ts +0 -28
  382. package/src/db/migrations/021-pending-oauth-states.ts +0 -35
  383. package/src/db/migrations/022-app-connections-provider.ts +0 -25
  384. package/src/db/migrations/023-agent-group-secret-mode.test.ts +0 -124
  385. package/src/db/migrations/023-agent-group-secret-mode.ts +0 -65
  386. package/src/db/migrations/024-collapse-approvals.test.ts +0 -249
  387. package/src/db/migrations/024-collapse-approvals.ts +0 -182
  388. package/src/db/migrations/025-secret-mode-check.test.ts +0 -155
  389. package/src/db/migrations/025-secret-mode-check.ts +0 -49
  390. package/src/db/migrations/026-user-dms-bot-id.test.ts +0 -116
  391. package/src/db/migrations/026-user-dms-bot-id.ts +0 -54
  392. package/src/db/migrations/027-provider-credentials.ts +0 -41
  393. package/src/db/migrations/_test-helpers.ts +0 -41
  394. package/src/db/migrations/index.ts +0 -127
  395. package/src/db/migrations/module-agent-to-agent-destinations.ts +0 -84
  396. package/src/db/migrations/module-approvals-pending-approvals.ts +0 -42
  397. package/src/db/migrations/module-approvals-title-options.ts +0 -40
  398. package/src/db/schema.ts +0 -258
  399. package/src/db/session-db.test.ts +0 -93
  400. package/src/db/session-db.ts +0 -325
  401. package/src/db/sessions.ts +0 -241
  402. package/src/delivery.test.ts +0 -148
  403. package/src/delivery.ts +0 -445
  404. package/src/env.ts +0 -74
  405. package/src/group-folder.test.ts +0 -35
  406. package/src/group-folder.ts +0 -44
  407. package/src/group-init.ts +0 -92
  408. package/src/host-core.test.ts +0 -456
  409. package/src/host-sweep.test.ts +0 -146
  410. package/src/host-sweep.ts +0 -287
  411. package/src/index.ts +0 -232
  412. package/src/install-slug.ts +0 -33
  413. package/src/log.test.ts +0 -81
  414. package/src/log.ts +0 -117
  415. package/src/mcp/http.ts +0 -72
  416. package/src/mcp/server.ts +0 -92
  417. package/src/mcp/stdio.ts +0 -51
  418. package/src/mcp/tools/activity.ts +0 -88
  419. package/src/mcp/tools/agent-groups.ts +0 -183
  420. package/src/mcp/tools/approvals.ts +0 -122
  421. package/src/mcp/tools/channels.test.ts +0 -126
  422. package/src/mcp/tools/channels.ts +0 -134
  423. package/src/mcp/tools/index.ts +0 -27
  424. package/src/mcp/tools/oauth.ts +0 -48
  425. package/src/mcp/tools/secrets.ts +0 -169
  426. package/src/mcp/tools/sessions.ts +0 -135
  427. package/src/mcp/types.ts +0 -51
  428. package/src/modules/agent-to-agent/agent-route.test.ts +0 -46
  429. package/src/modules/agent-to-agent/agent-route.ts +0 -223
  430. package/src/modules/agent-to-agent/create-agent.ts +0 -127
  431. package/src/modules/agent-to-agent/db/agent-destinations.ts +0 -135
  432. package/src/modules/agent-to-agent/index.ts +0 -22
  433. package/src/modules/agent-to-agent/write-destinations.ts +0 -59
  434. package/src/modules/approvals/agent.md +0 -45
  435. package/src/modules/approvals/index.ts +0 -21
  436. package/src/modules/approvals/picks.test.ts +0 -291
  437. package/src/modules/approvals/primitive.ts +0 -279
  438. package/src/modules/approvals/project.md +0 -27
  439. package/src/modules/approvals/response-handler.ts +0 -87
  440. package/src/modules/index.ts +0 -24
  441. package/src/modules/interactive/agent.md +0 -21
  442. package/src/modules/interactive/index.ts +0 -69
  443. package/src/modules/interactive/project.md +0 -12
  444. package/src/modules/mount-security/expand-path.test.ts +0 -82
  445. package/src/modules/mount-security/index.ts +0 -459
  446. package/src/modules/mount-security/migrate.test.ts +0 -91
  447. package/src/modules/permissions/access.ts +0 -28
  448. package/src/modules/permissions/channel-approval.test.ts +0 -389
  449. package/src/modules/permissions/channel-approval.ts +0 -188
  450. package/src/modules/permissions/db/agent-group-members.ts +0 -44
  451. package/src/modules/permissions/db/pending-channel-approvals.test.ts +0 -86
  452. package/src/modules/permissions/db/pending-channel-approvals.ts +0 -66
  453. package/src/modules/permissions/db/pending-sender-approvals.ts +0 -60
  454. package/src/modules/permissions/db/user-dms.ts +0 -58
  455. package/src/modules/permissions/db/user-roles.ts +0 -85
  456. package/src/modules/permissions/db/users.ts +0 -38
  457. package/src/modules/permissions/index.ts +0 -421
  458. package/src/modules/permissions/permissions.test.ts +0 -358
  459. package/src/modules/permissions/sender-approval.test.ts +0 -641
  460. package/src/modules/permissions/sender-approval.ts +0 -165
  461. package/src/modules/permissions/user-dm.ts +0 -200
  462. package/src/modules/provider-credentials/db.ts +0 -121
  463. package/src/modules/provider-credentials/index.ts +0 -12
  464. package/src/modules/provider-credentials/spawn.test.ts +0 -206
  465. package/src/modules/provider-credentials/spawn.ts +0 -114
  466. package/src/modules/scheduling/actions.ts +0 -113
  467. package/src/modules/scheduling/db.test.ts +0 -282
  468. package/src/modules/scheduling/db.ts +0 -148
  469. package/src/modules/scheduling/index.ts +0 -34
  470. package/src/modules/scheduling/recurrence.test.ts +0 -98
  471. package/src/modules/scheduling/recurrence.ts +0 -54
  472. package/src/modules/self-mod/agent.md +0 -30
  473. package/src/modules/self-mod/apply.ts +0 -85
  474. package/src/modules/self-mod/index.ts +0 -30
  475. package/src/modules/self-mod/project.md +0 -39
  476. package/src/modules/self-mod/request.ts +0 -91
  477. package/src/modules/typing/index.ts +0 -165
  478. package/src/oauth/agent-app-connections.ts +0 -103
  479. package/src/oauth/app-configs.test.ts +0 -64
  480. package/src/oauth/app-configs.ts +0 -114
  481. package/src/oauth/app-connections.test.ts +0 -109
  482. package/src/oauth/app-connections.ts +0 -178
  483. package/src/oauth/crypto.ts +0 -56
  484. package/src/oauth/flow.ts +0 -104
  485. package/src/oauth/providers/google.test.ts +0 -38
  486. package/src/oauth/providers/google.ts +0 -46
  487. package/src/oauth/providers/index.ts +0 -48
  488. package/src/oauth/state-store.test.ts +0 -54
  489. package/src/oauth/state-store.ts +0 -93
  490. package/src/parachute/README.md +0 -27
  491. package/src/parachute/create-agent.test.ts +0 -83
  492. package/src/parachute/create-agent.ts +0 -122
  493. package/src/parachute/group-status.test.ts +0 -165
  494. package/src/parachute/group-status.ts +0 -136
  495. package/src/parachute/types.ts +0 -41
  496. package/src/parachute/vault-mcp.test.ts +0 -251
  497. package/src/parachute/vault-mcp.ts +0 -232
  498. package/src/platform-id.test.ts +0 -104
  499. package/src/platform-id.ts +0 -109
  500. package/src/providers/index.ts +0 -6
  501. package/src/providers/provider-container-registry.ts +0 -58
  502. package/src/response-registry.ts +0 -45
  503. package/src/router.ts +0 -530
  504. package/src/secrets/crypto.test.ts +0 -45
  505. package/src/secrets/crypto.ts +0 -55
  506. package/src/secrets/index.ts +0 -461
  507. package/src/secrets/master-key.ts +0 -70
  508. package/src/secrets/secrets.test.ts +0 -651
  509. package/src/session-manager.attachments.test.ts +0 -171
  510. package/src/session-manager.dup-skip.test.ts +0 -173
  511. package/src/session-manager.migrate.test.ts +0 -59
  512. package/src/session-manager.ts +0 -451
  513. package/src/startup-bootstrap.test.ts +0 -226
  514. package/src/startup-bootstrap.ts +0 -207
  515. package/src/state-sqlite.ts +0 -182
  516. package/src/timezone.test.ts +0 -64
  517. package/src/timezone.ts +0 -37
  518. package/src/types.ts +0 -233
  519. package/src/web/auth.test.ts +0 -335
  520. package/src/web/auth.ts +0 -214
  521. package/src/web/discord-validate.test.ts +0 -77
  522. package/src/web/discord-validate.ts +0 -88
  523. package/src/web/hub-discovery.test.ts +0 -98
  524. package/src/web/hub-discovery.ts +0 -69
  525. package/src/web/routes/activity.ts +0 -106
  526. package/src/web/routes/agent-provider.test.ts +0 -282
  527. package/src/web/routes/agent-provider.ts +0 -309
  528. package/src/web/routes/approvals.ts +0 -185
  529. package/src/web/routes/apps.ts +0 -434
  530. package/src/web/routes/channels-mg-detail.test.ts +0 -324
  531. package/src/web/routes/channels-mga-detail.test.ts +0 -472
  532. package/src/web/routes/channels.ts +0 -311
  533. package/src/web/routes/oauth-providers.ts +0 -42
  534. package/src/web/routes/secrets.test.ts +0 -220
  535. package/src/web/routes/secrets.ts +0 -317
  536. package/src/web/routes/sessions.ts +0 -123
  537. package/src/web/routes/settings.test.ts +0 -106
  538. package/src/web/routes/settings.ts +0 -247
  539. package/src/web/routes/setup-status.ts +0 -205
  540. package/src/web/routes/vaults.test.ts +0 -389
  541. package/src/web/routes/vaults.ts +0 -225
  542. package/src/web/server-version.test.ts +0 -16
  543. package/src/web/server.ts +0 -1024
  544. package/src/web/services-manifest.test.ts +0 -148
  545. package/src/web/services-manifest.ts +0 -66
  546. package/src/web/static-serve.test.ts +0 -255
  547. package/src/web/static-serve.ts +0 -104
  548. package/src/web/telegram-validate.test.ts +0 -116
  549. package/src/web/telegram-validate.ts +0 -107
  550. package/src/web/vault-proxy.test.ts +0 -214
  551. package/src/web/vault-proxy.ts +0 -120
  552. package/src/web/wire-channel.ts +0 -181
  553. package/src/webhook-server.ts +0 -134
  554. package/vitest.config.ts +0 -18
  555. package/web/README.md +0 -63
  556. package/web/ui/index.html +0 -13
  557. package/web/ui/package.json +0 -35
  558. package/web/ui/pnpm-lock.yaml +0 -2164
  559. package/web/ui/scripts/verify-base.mjs +0 -31
  560. package/web/ui/src/App.tsx +0 -88
  561. package/web/ui/src/components/ActivityFeed.tsx +0 -444
  562. package/web/ui/src/components/AgentGroupPicker.tsx +0 -263
  563. package/web/ui/src/components/AgentProviderCards.tsx +0 -220
  564. package/web/ui/src/components/CredentialForm.tsx +0 -214
  565. package/web/ui/src/components/ScopeGrants.tsx +0 -74
  566. package/web/ui/src/components/StatusDot.tsx +0 -43
  567. package/web/ui/src/components/VaultPicker.tsx +0 -127
  568. package/web/ui/src/components/setup/AdapterInstallStep.tsx +0 -178
  569. package/web/ui/src/components/setup/AgentGroupStep.tsx +0 -43
  570. package/web/ui/src/components/setup/ChannelPickStep.tsx +0 -74
  571. package/web/ui/src/components/setup/DoneStep.tsx +0 -49
  572. package/web/ui/src/components/setup/PrereqStep.tsx +0 -129
  573. package/web/ui/src/components/setup/TestConnectionStep.tsx +0 -108
  574. package/web/ui/src/components/setup/TestMessageStep.tsx +0 -104
  575. package/web/ui/src/components/setup/WireChannelStep.tsx +0 -166
  576. package/web/ui/src/components/setup/types.ts +0 -105
  577. package/web/ui/src/lib/api.test.ts +0 -410
  578. package/web/ui/src/lib/api.ts +0 -1248
  579. package/web/ui/src/lib/auth.test.ts +0 -352
  580. package/web/ui/src/lib/auth.ts +0 -405
  581. package/web/ui/src/lib/channel-adapters.ts +0 -136
  582. package/web/ui/src/main.tsx +0 -19
  583. package/web/ui/src/routes/ApprovalsList.tsx +0 -294
  584. package/web/ui/src/routes/Apps.tsx +0 -613
  585. package/web/ui/src/routes/ChannelWireDetail.test.tsx +0 -233
  586. package/web/ui/src/routes/ChannelWireDetail.tsx +0 -403
  587. package/web/ui/src/routes/ChannelsList.tsx +0 -158
  588. package/web/ui/src/routes/GroupDetail.test.tsx +0 -206
  589. package/web/ui/src/routes/GroupDetail.tsx +0 -880
  590. package/web/ui/src/routes/GroupList.tsx +0 -187
  591. package/web/ui/src/routes/MessagingGroupDetail.test.tsx +0 -233
  592. package/web/ui/src/routes/MessagingGroupDetail.tsx +0 -306
  593. package/web/ui/src/routes/NewGroupWizard.tsx +0 -390
  594. package/web/ui/src/routes/OAuthCallback.tsx +0 -56
  595. package/web/ui/src/routes/SecretsList.tsx +0 -942
  596. package/web/ui/src/routes/SessionsList.tsx +0 -220
  597. package/web/ui/src/routes/SettingsAgentProvider.tsx +0 -109
  598. package/web/ui/src/routes/SettingsApprovals.tsx +0 -234
  599. package/web/ui/src/routes/SetupWizard.tsx +0 -219
  600. package/web/ui/src/routes/VaultDetail.test.tsx +0 -363
  601. package/web/ui/src/routes/VaultDetail.tsx +0 -960
  602. package/web/ui/src/routes/VaultsList.tsx +0 -295
  603. package/web/ui/src/routes/WireChannelPage.tsx +0 -413
  604. package/web/ui/src/styles.css +0 -608
  605. package/web/ui/src/test/setup.ts +0 -23
  606. package/web/ui/src/vite-env.d.ts +0 -10
  607. package/web/ui/vite.config.ts +0 -34
  608. package/web/ui/vitest.config.ts +0 -25
@@ -1,461 +0,0 @@
1
- /**
2
- * Public API for paraclaw's secret store. Values are AES-256-GCM encrypted
3
- * in-process before landing in the central DB; decrypted only when injected
4
- * into per-session containers (`src/container-runner.ts`).
5
- *
6
- * Naming: a secret is keyed by `(name, agent_group_id)`. A NULL agent_group_id
7
- * is global; a non-NULL agent_group_id scopes the secret to that group only.
8
- *
9
- * Resolution preference at injection time: agent-scoped secret with that
10
- * name beats the global one. The host walks both rows and the scoped wins.
11
- *
12
- * Injection policy lives on the recipient `agent_groups.secret_mode` row
13
- * (migration 023): `all` injects every in-scope secret; `selective` injects
14
- * only those with an explicit `secret_assignments` row pointing to the group.
15
- */
16
- import crypto from 'crypto';
17
-
18
- import { getDb } from '../db/connection.js';
19
- import type { Database } from '../db/connection.js';
20
- import { decryptSecret, deriveKey, encryptSecret } from './crypto.js';
21
- import { loadOrCreateMasterKey } from './master-key.js';
22
-
23
- // Domain tag for HKDF-derived secrets-store key. Bumping the version (v2…)
24
- // would force re-encryption of every row in this table. See crypto.ts.
25
- //
26
- // ⚠ The `paraclaw.` prefix is a cryptographic domain separator and must
27
- // stay frozen across the paraclaw → parachute-agent rename. Renaming it
28
- // changes the derived key and renders every existing ciphertext row
29
- // undecryptable. The brand-sweep documentation lives in commit messages
30
- // and CHANGELOG; the bytes here do not change.
31
- const SECRETS_INFO = 'paraclaw.secrets.v1';
32
-
33
- function secretsKey(): Buffer {
34
- return deriveKey(loadOrCreateMasterKey(), SECRETS_INFO);
35
- }
36
-
37
- export type SecretKind = 'channel-token' | 'api-key' | 'generic';
38
- export type AssignedMode = 'all' | 'selective';
39
-
40
- export interface SecretRow {
41
- id: string;
42
- name: string;
43
- kind: SecretKind;
44
- agent_group_id: string | null;
45
- created_at: string;
46
- updated_at: string;
47
- }
48
-
49
- export interface PutSecretOpts {
50
- kind?: SecretKind;
51
- agent_group_id?: string | null;
52
- }
53
-
54
- interface RawRow extends SecretRow {
55
- value_encrypted: string;
56
- }
57
-
58
- function db(): Database {
59
- return getDb();
60
- }
61
-
62
- function nowIso(): string {
63
- return new Date().toISOString();
64
- }
65
-
66
- /** Insert or update a secret. Returns the row's id. */
67
- export function putSecret(name: string, value: string, opts: PutSecretOpts = {}): string {
68
- const key = secretsKey();
69
- const ct = encryptSecret(value, key);
70
- const agentGroupId = opts.agent_group_id ?? null;
71
- const kind = opts.kind ?? 'generic';
72
-
73
- const existing = db()
74
- .prepare<{ id: string }>(`SELECT id FROM secrets WHERE name = @name AND agent_group_id IS @agent_group_id`)
75
- .get({ name, agent_group_id: agentGroupId });
76
-
77
- const now = nowIso();
78
- if (existing) {
79
- db()
80
- .prepare(
81
- `UPDATE secrets
82
- SET value_encrypted = @value_encrypted,
83
- kind = @kind,
84
- updated_at = @updated_at
85
- WHERE id = @id`,
86
- )
87
- .run({
88
- id: existing.id,
89
- value_encrypted: ct,
90
- kind,
91
- updated_at: now,
92
- });
93
- return existing.id;
94
- }
95
-
96
- const id = crypto.randomUUID();
97
- // Auto-seed the matching `secret_assignments` row when the secret is
98
- // scoped to a group (paraclaw#127). For a scoped secret the only valid
99
- // assignment-row pair is (id, owning_group); the resolver only injects
100
- // when `s.agent_group_id = g.id OR s.agent_group_id IS NULL`, so an
101
- // assignment elsewhere is meaningless. Without this seed, scoped
102
- // creates land orphaned under the default `selective` group mode and
103
- // are silently invisible to `resolveInjectableSecrets`. INSERT path
104
- // only — UPDATE/rotate leaves the existing assignment set alone.
105
- // ON CONFLICT DO NOTHING for idempotency-on-replay (the constraint
106
- // already exists in `replaceAssignments` for the same reason).
107
- db().transaction(() => {
108
- db()
109
- .prepare(
110
- `INSERT INTO secrets
111
- (id, name, value_encrypted, kind, agent_group_id, created_at, updated_at)
112
- VALUES
113
- (@id, @name, @value_encrypted, @kind, @agent_group_id, @created_at, @updated_at)`,
114
- )
115
- .run({
116
- id,
117
- name,
118
- value_encrypted: ct,
119
- kind,
120
- agent_group_id: agentGroupId,
121
- created_at: now,
122
- updated_at: now,
123
- });
124
- if (agentGroupId !== null) {
125
- db()
126
- .prepare(
127
- `INSERT INTO secret_assignments (secret_id, agent_group_id, created_at)
128
- VALUES (@secret_id, @agent_group_id, @created_at)
129
- ON CONFLICT (secret_id, agent_group_id) DO NOTHING`,
130
- )
131
- .run({ secret_id: id, agent_group_id: agentGroupId, created_at: now });
132
- }
133
- })();
134
- return id;
135
- }
136
-
137
- /**
138
- * Decrypt and return a secret's plaintext value. Returns undefined if the
139
- * named secret does not exist for the given scope. Resolution: an
140
- * agent-scoped secret beats a global one with the same name.
141
- */
142
- export function getSecret(name: string, agentGroupId?: string | null): string | undefined {
143
- const key = secretsKey();
144
- const scoped = agentGroupId
145
- ? db()
146
- .prepare<RawRow>(`SELECT * FROM secrets WHERE name = @name AND agent_group_id = @agent_group_id`)
147
- .get({ name, agent_group_id: agentGroupId })
148
- : undefined;
149
- const row =
150
- scoped ?? db().prepare<RawRow>(`SELECT * FROM secrets WHERE name = @name AND agent_group_id IS NULL`).get({ name });
151
- if (!row) return undefined;
152
- return decryptSecret(row.value_encrypted, key);
153
- }
154
-
155
- /** Names + metadata only — never decrypts. */
156
- export function listSecrets(agentGroupId?: string | null): SecretRow[] {
157
- if (agentGroupId === undefined) {
158
- return db()
159
- .prepare<SecretRow>(
160
- `SELECT id, name, kind, agent_group_id, created_at, updated_at
161
- FROM secrets ORDER BY name`,
162
- )
163
- .all();
164
- }
165
- if (agentGroupId === null) {
166
- return db()
167
- .prepare<SecretRow>(
168
- `SELECT id, name, kind, agent_group_id, created_at, updated_at
169
- FROM secrets WHERE agent_group_id IS NULL ORDER BY name`,
170
- )
171
- .all();
172
- }
173
- return db()
174
- .prepare<SecretRow>(
175
- `SELECT id, name, kind, agent_group_id, created_at, updated_at
176
- FROM secrets
177
- WHERE agent_group_id = @agent_group_id OR agent_group_id IS NULL
178
- ORDER BY name`,
179
- )
180
- .all({ agent_group_id: agentGroupId });
181
- }
182
-
183
- export function deleteSecret(id: string): boolean {
184
- const r = db().prepare(`DELETE FROM secrets WHERE id = @id`).run({ id });
185
- return r.changes > 0;
186
- }
187
-
188
- /**
189
- * Resolve the secrets that should be injected into a session for the given
190
- * agent group. Returns plaintext values; callers are expected to inject as
191
- * env vars and never log. Agent-scoped wins over global on name collision.
192
- *
193
- * Mode lives on the recipient `agent_groups.secret_mode`:
194
- * - `all` — inject every in-scope secret (scoped + globals).
195
- * - `selective` — inject only those with an explicit assignment row
196
- * pointing to this group. Lets operators stage credentials
197
- * in the store before any agent gets them and revoke per
198
- * agent without rotating the value.
199
- *
200
- * Unknown agent_group_id is treated as `selective` (the safe default) — the
201
- * group-level row is the source of truth, so a missing row means we err on
202
- * the side of withholding.
203
- */
204
- export function resolveInjectableSecrets(agentGroupId: string): Map<string, string> {
205
- const key = secretsKey();
206
- const rows = db()
207
- .prepare<RawRow>(
208
- `SELECT s.*
209
- FROM secrets s
210
- LEFT JOIN secret_assignments a
211
- ON a.secret_id = s.id
212
- AND a.agent_group_id = @agent_group_id
213
- LEFT JOIN agent_groups g
214
- ON g.id = @agent_group_id
215
- WHERE (s.agent_group_id = @agent_group_id OR s.agent_group_id IS NULL)
216
- AND (g.secret_mode = 'all' OR a.secret_id IS NOT NULL)
217
- ORDER BY s.agent_group_id IS NULL`,
218
- )
219
- .all({ agent_group_id: agentGroupId });
220
-
221
- const out = new Map<string, string>();
222
- for (const row of rows) {
223
- if (out.has(row.name)) continue;
224
- out.set(row.name, decryptSecret(row.value_encrypted, key));
225
- }
226
- return out;
227
- }
228
-
229
- // ── Assignments ──
230
-
231
- export interface SecretAssignment {
232
- secret_id: string;
233
- agent_group_id: string;
234
- created_at: string;
235
- }
236
-
237
- /** All agent_group_ids assigned to this secret (selective-mode allowlist). */
238
- export function listAssignments(secretId: string): string[] {
239
- const rows = db()
240
- .prepare<{ agent_group_id: string }>(
241
- `SELECT agent_group_id FROM secret_assignments
242
- WHERE secret_id = @secret_id
243
- ORDER BY agent_group_id`,
244
- )
245
- .all({ secret_id: secretId });
246
- return rows.map((r) => r.agent_group_id);
247
- }
248
-
249
- /**
250
- * Replace the assignment set atomically. Empty array = revoke everything.
251
- * Throws if the secret doesn't exist; FK ON DELETE CASCADE handles agent
252
- * groups that vanish. The whole replace runs inside one transaction so
253
- * the UI's "save" button is all-or-nothing.
254
- */
255
- export function replaceAssignments(secretId: string, agentGroupIds: string[]): void {
256
- const exists = db().prepare<{ id: string }>(`SELECT id FROM secrets WHERE id = @id`).get({ id: secretId });
257
- if (!exists) throw new Error(`secret not found: ${secretId}`);
258
- const now = nowIso();
259
- db().transaction(() => {
260
- db().prepare(`DELETE FROM secret_assignments WHERE secret_id = @secret_id`).run({ secret_id: secretId });
261
- const insert = db().prepare(
262
- `INSERT INTO secret_assignments (secret_id, agent_group_id, created_at)
263
- VALUES (@secret_id, @agent_group_id, @created_at)`,
264
- );
265
- for (const gid of agentGroupIds) {
266
- insert.run({ secret_id: secretId, agent_group_id: gid, created_at: now });
267
- }
268
- })();
269
- }
270
-
271
- /** Idempotent — re-adding an existing assignment is a no-op (composite PK). */
272
- export function addAssignment(secretId: string, agentGroupId: string): boolean {
273
- const r = db()
274
- .prepare(
275
- `INSERT INTO secret_assignments (secret_id, agent_group_id, created_at)
276
- VALUES (@secret_id, @agent_group_id, @created_at)
277
- ON CONFLICT (secret_id, agent_group_id) DO NOTHING`,
278
- )
279
- .run({ secret_id: secretId, agent_group_id: agentGroupId, created_at: nowIso() });
280
- return r.changes > 0;
281
- }
282
-
283
- export function removeAssignment(secretId: string, agentGroupId: string): boolean {
284
- const r = db()
285
- .prepare(`DELETE FROM secret_assignments WHERE secret_id = @secret_id AND agent_group_id = @agent_group_id`)
286
- .run({ secret_id: secretId, agent_group_id: agentGroupId });
287
- return r.changes > 0;
288
- }
289
-
290
- // ── Staleness detection (Bug B) ──
291
-
292
- export interface StaleSession {
293
- sessionId: string;
294
- agentGroupId: string;
295
- agentGroupName: string;
296
- agentGroupFolder: string;
297
- sessionCreatedAt: string;
298
- secretUpdatedAt: string;
299
- }
300
-
301
- /**
302
- * Sessions whose container was spawned BEFORE this secret was last updated
303
- * AND whose agent group would inject the secret. The injection predicate
304
- * mirrors `resolveInjectableSecrets` for the configurations the UI can
305
- * actually create:
306
- * - scoped secret → matches its parent group (`s.agent_group_id = g.id`)
307
- * - global secret → matches any group with `secret_mode='all'` OR an
308
- * explicit `secret_assignments` row
309
- *
310
- * Note on a subtle asymmetry: `resolveInjectableSecrets` additionally gates
311
- * scoped secrets through `(secret_mode='all' OR assignment row exists)` on
312
- * the recipient group. The SQL here accepts the scoped match unconditionally.
313
- * The asymmetry is benign — the only config where it would diverge (a scoped
314
- * secret in a `selective`-mode group with no assignment row) is structurally
315
- * unreachable from `putSecret`: paraclaw#127 made the INSERT path auto-seed
316
- * the (id, owning_group) assignment row in the same transaction. If a future
317
- * code path bypasses `putSecret` and writes the orphan state directly, tighten
318
- * the SQL to add the same gate.
319
- *
320
- * The host injects env vars at spawn time only — there is no in-process
321
- * update path. This helper powers the post-save banner that prompts the
322
- * operator to restart the specific sessions that need to see the change.
323
- *
324
- * Returns empty when the secret doesn't exist (caller handles 404).
325
- */
326
- export function findStaleSessionsForSecret(secretId: string): StaleSession[] {
327
- const rows = db()
328
- .prepare<{
329
- session_id: string;
330
- agent_group_id: string;
331
- agent_group_name: string;
332
- agent_group_folder: string;
333
- session_created_at: string;
334
- secret_updated_at: string;
335
- }>(
336
- `SELECT
337
- sess.id AS session_id,
338
- g.id AS agent_group_id,
339
- g.name AS agent_group_name,
340
- g.folder AS agent_group_folder,
341
- sess.created_at AS session_created_at,
342
- s.updated_at AS secret_updated_at
343
- FROM secrets s
344
- JOIN agent_groups g
345
- ON s.agent_group_id = g.id
346
- OR s.agent_group_id IS NULL
347
- LEFT JOIN secret_assignments a
348
- ON a.secret_id = s.id AND a.agent_group_id = g.id
349
- JOIN sessions sess
350
- ON sess.agent_group_id = g.id
351
- WHERE s.id = @secret_id
352
- AND sess.container_status = 'running'
353
- AND sess.created_at < s.updated_at
354
- AND (
355
- s.agent_group_id = g.id
356
- OR (s.agent_group_id IS NULL
357
- AND (g.secret_mode = 'all' OR a.secret_id IS NOT NULL))
358
- )
359
- ORDER BY sess.created_at DESC`,
360
- )
361
- .all({ secret_id: secretId });
362
- return rows.map((r) => ({
363
- sessionId: r.session_id,
364
- agentGroupId: r.agent_group_id,
365
- agentGroupName: r.agent_group_name,
366
- agentGroupFolder: r.agent_group_folder,
367
- sessionCreatedAt: r.session_created_at,
368
- secretUpdatedAt: r.secret_updated_at,
369
- }));
370
- }
371
-
372
- /** Metadata-only single-row read by id. Returns undefined if missing. */
373
- export function getSecretById(id: string): SecretRow | undefined {
374
- return db()
375
- .prepare<SecretRow>(`SELECT id, name, kind, agent_group_id, created_at, updated_at FROM secrets WHERE id = ?`)
376
- .get(id);
377
- }
378
-
379
- /**
380
- * Why a secret lands in a particular agent group's injectable set:
381
- * - `scoped` — secret is owned by this group (`s.agent_group_id = g.id`).
382
- * - `assigned` — global secret with an explicit `secret_assignments` row
383
- * pointing at this group.
384
- * - `global` — global secret with no assignment row, included only because
385
- * the recipient group is in `secret_mode='all'`.
386
- *
387
- * When a global has BOTH an assignment row AND `secret_mode='all'`, we report
388
- * `assigned` — the explicit row reflects deliberate operator intent, while
389
- * mode='all' is a blanket setting; surfacing the more-specific reason makes
390
- * the GroupDetail page actionable ("revoke this assignment" vs "flip to
391
- * selective"). See paraclaw#104.
392
- */
393
- export type SecretInclusionScope = 'global' | 'scoped' | 'assigned';
394
-
395
- export interface InjectableSecretView extends SecretRow {
396
- scope: SecretInclusionScope;
397
- }
398
-
399
- /**
400
- * Metadata-only mirror of `resolveInjectableSecrets` for the GroupDetail
401
- * "Secrets" panel. Returns the same row set (subject to the same SQL gate)
402
- * tagged with the inclusion reason — never decrypts. Caller is the read-only
403
- * `GET /api/groups/:folder/secrets` route.
404
- *
405
- * The SQL mirrors `resolveInjectableSecrets` (the `(s.agent_group_id = g.id
406
- * OR s.agent_group_id IS NULL)` row predicate gated by `(secret_mode='all'
407
- * OR assignment exists)`) so the panel cannot disagree with what the
408
- * container will actually receive at spawn time. Drift here would defeat
409
- * the entire point of #104 — keep them in lockstep. If you change either,
410
- * change both.
411
- *
412
- * `ORDER BY s.agent_group_id IS NULL` puts scoped rows first so the
413
- * dedupe-by-name loop honors the "scoped wins on collision" rule
414
- * `resolveInjectableSecrets` enforces.
415
- */
416
- export function listInjectableSecretsForGroup(agentGroupId: string): InjectableSecretView[] {
417
- const rows = db()
418
- .prepare<{
419
- id: string;
420
- name: string;
421
- kind: SecretKind;
422
- agent_group_id: string | null;
423
- created_at: string;
424
- updated_at: string;
425
- assignment_present: number;
426
- }>(
427
- `SELECT s.id, s.name, s.kind, s.agent_group_id, s.created_at, s.updated_at,
428
- CASE WHEN a.secret_id IS NULL THEN 0 ELSE 1 END AS assignment_present
429
- FROM secrets s
430
- LEFT JOIN secret_assignments a
431
- ON a.secret_id = s.id
432
- AND a.agent_group_id = @agent_group_id
433
- LEFT JOIN agent_groups g
434
- ON g.id = @agent_group_id
435
- WHERE (s.agent_group_id = @agent_group_id OR s.agent_group_id IS NULL)
436
- AND (g.secret_mode = 'all' OR a.secret_id IS NOT NULL)
437
- ORDER BY s.agent_group_id IS NULL, s.name`,
438
- )
439
- .all({ agent_group_id: agentGroupId });
440
-
441
- const out: InjectableSecretView[] = [];
442
- const seen = new Set<string>();
443
- for (const row of rows) {
444
- if (seen.has(row.name)) continue;
445
- seen.add(row.name);
446
- let scope: SecretInclusionScope;
447
- if (row.agent_group_id === agentGroupId) scope = 'scoped';
448
- else if (row.assignment_present === 1) scope = 'assigned';
449
- else scope = 'global';
450
- out.push({
451
- id: row.id,
452
- name: row.name,
453
- kind: row.kind,
454
- agent_group_id: row.agent_group_id,
455
- created_at: row.created_at,
456
- updated_at: row.updated_at,
457
- scope,
458
- });
459
- }
460
- return out;
461
- }
@@ -1,70 +0,0 @@
1
- /**
2
- * Master key bootstrap. Stores a 32-byte (256-bit) random key at
3
- * `~/.parachute/agent/master.key` with mode 0600. Generated on first start;
4
- * loaded from disk on subsequent starts.
5
- *
6
- * The key is never written to logs, never sent over the wire, never put in
7
- * env vars. Loss of the file = loss of every encrypted secret (no recovery
8
- * path); rotation requires re-encrypting every row.
9
- */
10
- import crypto from 'crypto';
11
- import fs from 'fs';
12
- import path from 'path';
13
-
14
- import { CENTRAL_DB_DIR } from '../config.js';
15
-
16
- const KEY_LEN = 32;
17
- // `master.key` lives next to the central DB so a single backup of
18
- // `<PARACHUTE_DIR>/agent/` captures both crypto material and DB state, and
19
- // so `PARACHUTE_HOME` overrides reroute both atoms together — sandboxes
20
- // that override the home dir get a fresh DB AND a fresh master.key.
21
- const KEY_DIR = CENTRAL_DB_DIR;
22
- const KEY_PATH = path.join(KEY_DIR, 'master.key');
23
-
24
- let cached: Buffer | null = null;
25
-
26
- export function getMasterKeyPath(): string {
27
- return KEY_PATH;
28
- }
29
-
30
- export function loadOrCreateMasterKey(): Buffer {
31
- if (cached) return cached;
32
-
33
- if (fs.existsSync(KEY_PATH)) {
34
- // Refuse to load a key file that's group/world readable. The file was
35
- // created with mode 0600; if something has loosened it (chmod, restore
36
- // from a backup tarball, etc.) we'd rather fail loud than silently keep
37
- // serving secrets out of a file anyone on the box can read.
38
- const stat = fs.statSync(KEY_PATH);
39
- const perm = stat.mode & 0o777;
40
- if ((stat.mode & 0o077) !== 0) {
41
- throw new Error(
42
- `Master key at ${KEY_PATH} has permissive mode 0${perm.toString(8).padStart(3, '0')}; ` +
43
- `expected 0600. Run: chmod 600 ${KEY_PATH}`,
44
- );
45
- }
46
- const buf = fs.readFileSync(KEY_PATH);
47
- if (buf.length !== KEY_LEN) {
48
- throw new Error(`Master key at ${KEY_PATH} is ${buf.length} bytes; expected ${KEY_LEN}`);
49
- }
50
- cached = buf;
51
- return buf;
52
- }
53
-
54
- fs.mkdirSync(KEY_DIR, { recursive: true, mode: 0o700 });
55
- const key = crypto.randomBytes(KEY_LEN);
56
- fs.writeFileSync(KEY_PATH, key, { mode: 0o600 });
57
- cached = key;
58
- return key;
59
- }
60
-
61
- /** Test-only: clear the cached key so a different one can be loaded. */
62
- export function _resetMasterKeyCache(): void {
63
- cached = null;
64
- }
65
-
66
- /** Test-only: install a key without touching disk. */
67
- export function _setMasterKeyForTest(key: Buffer): void {
68
- if (key.length !== KEY_LEN) throw new Error(`test key must be ${KEY_LEN} bytes`);
69
- cached = key;
70
- }