@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
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Agent-spec + Sandbox contract types.
3
+ *
4
+ * The **agent spec** (design §4.1) is the single declaration of everything an
5
+ * arm may reach: its MCP surface (channels + vault), its network egress, and its
6
+ * filesystem view. Scope and isolation are both read off this one object, so
7
+ * there is exactly one place that says "what is this arm allowed to touch."
8
+ *
9
+ * design/2026-06-14-sandboxed-agent-sessions.md §4.1, §4.4, §4.5
10
+ *
11
+ * The Sandbox **contract** (design §3.1) is held constant; the **mechanism**
12
+ * varies by platform (Seatbelt on macOS, bubblewrap on Linux) behind it. v1
13
+ * ships one backend (Anthropic's sandbox-runtime); the escalation rung (§3.4 —
14
+ * gVisor / full VM) is a second backend added later without touching callers.
15
+ */
16
+
17
+ /** Read/write mode for a declared mount. */
18
+ export type MountMode = "ro" | "rw";
19
+
20
+ /**
21
+ * A filesystem bind beyond the implicit workspace + runtime/config. Each entry
22
+ * binds a host path to a mount path at `ro` or `rw` (design §4.5).
23
+ *
24
+ * On macOS the sandbox profile is glob-capable, so `hostPath` may contain glob
25
+ * patterns; on Linux it must be a literal path (the runtime does not glob there).
26
+ * For v1 we bind the host path directly (`mountPath` is recorded for the future
27
+ * bubblewrap bind-remap and for the spec→mandate seam, §4.6 — it does not change
28
+ * the v1 Seatbelt path, which has no path-remapping layer).
29
+ */
30
+ export interface AgentMount {
31
+ /** Path on the host to expose into the session. */
32
+ hostPath: string;
33
+ /** Path the session sees it at. Recorded for the future bind-remap; see note above. */
34
+ mountPath: string;
35
+ /** Read-only or read-write. */
36
+ mode: MountMode;
37
+ /**
38
+ * Opt-in cross-session share by name (design §4.5). A deliberate hole in
39
+ * session-to-session isolation — honored here, but the trust caveat (prefer
40
+ * shared-`ro` from the producer, never shared-`rw` across a trust boundary) is
41
+ * doc-level for v1. Plumbed through so a future use is a considered decision.
42
+ */
43
+ shared?: string;
44
+ }
45
+
46
+ /** The vault binding for an arm: which vault, what access, optionally tag-scoped. */
47
+ export interface AgentVaultSpec {
48
+ /** Vault instance name (e.g. "default"). */
49
+ name: string;
50
+ /** Access verb minted into the vault token. */
51
+ access: "read" | "write" | "admin";
52
+ /**
53
+ * Optional tag scope — narrows the minted vault token to these tags via the
54
+ * `permissions.scoped_tags` claim (e.g. `["#agent/message"]`). Omitted = the
55
+ * verb's full scope across the vault.
56
+ */
57
+ tags?: string[];
58
+ }
59
+
60
+ /** An additional MCP server to wire in, by URL (design §4.1 `otherMcps`). */
61
+ export interface OtherMcpSpec {
62
+ /** Entry key in the generated `mcpServers` object. */
63
+ name: string;
64
+ /** Streamable-HTTP MCP URL. */
65
+ url: string;
66
+ /**
67
+ * Scope to mint a token for, if this MCP is hub-gated. Omitted = no token
68
+ * (an unauthenticated / externally-authenticated MCP).
69
+ */
70
+ scope?: string;
71
+ /** Audience to mint the token under. Defaults to inferred from scope by the hub. */
72
+ audience?: string;
73
+ }
74
+
75
+ /**
76
+ * An agent spec — the complete least-privilege envelope for one launched arm
77
+ * (design §4.1). `egress` and `mounts` are additive to a non-removable base; the
78
+ * spec only ever *adds*.
79
+ */
80
+ /**
81
+ * A channel binding for an arm. A bare string is shorthand for `{ name, access:
82
+ * "write" }` (back-compat — the common read+write single-threaded session); the object
83
+ * form scopes a channel read-only so an arm that only *watches* a channel mints
84
+ * `agent:read` and never `agent:write` (the "scope an arm to channel X
85
+ * read-only" use case).
86
+ */
87
+ export interface AgentChannelSpec {
88
+ /** Channel name (the `/mcp/<channel>` segment). */
89
+ name: string;
90
+ /**
91
+ * Channel access. `"write"` (default) mints `agent:read agent:write`;
92
+ * `"read"` mints `agent:read` only — the arm can be woken + read the channel
93
+ * but cannot reply.
94
+ */
95
+ access?: "read" | "write";
96
+ }
97
+
98
+ /** A channel entry: a bare name (= write access) or the scoped object form. */
99
+ export type AgentChannel = string | AgentChannelSpec;
100
+
101
+ /**
102
+ * Which backend drives the agent (design 2026-06-16-pluggable-agent-backend.md +
103
+ * 2026-06-18-channel-backend.md). There are exactly TWO — the `interactive` (tmux)
104
+ * backend was RETIRED 2026-06-19 (design 2026-06-19-retire-interactive-backend.md);
105
+ * `channel` is what it was reaching for, done right:
106
+ *
107
+ * - `"programmatic"` (the DEFAULT): NO resident process. An inbound message becomes
108
+ * one on-demand `claude -p --resume <sid>` turn ({@link AgentBackend}); the reply
109
+ * is posted back as an outbound `#agent/message/outbound` note. No idle session →
110
+ * nothing to go deaf, no reconnect, no replay, no consent gate. The reliable
111
+ * primary path; best for clean per-message "do a task, report back" turns.
112
+ * - `"attached"` (design 2026-06-18-channel-backend.md; the backend value was named
113
+ * `"channel"` before this rename — see the BACK-COMPAT note below): the turn is
114
+ * delivered over a channel to a Claude Code session the OPERATOR runs themselves
115
+ * (their machine, their env/creds, unsandboxed) and has connected (is "attached") to
116
+ * the channel's MCP endpoint. The daemon runs NO `claude -p`; the inbound
117
+ * `#agent/message/inbound` notes accumulate as a durable queue (the vault IS the
118
+ * queue). The connected session PULLs the next message (an MCP tool), works, and
119
+ * REPLYs (an MCP tool) — the daemon writes the outbound note + marks the inbound
120
+ * handled. Routed to the {@link AttachedQueueRegistry}, entirely bypassing the
121
+ * programmatic serial worker (the daemon routing fork). Claim state lives on the
122
+ * inbound note's `status` (`pending | in-flight | handled`), so it's restart-safe.
123
+ *
124
+ * BACK-COMPAT (backend VALUE rename `"channel"` → `"attached"`): an already-persisted
125
+ * def (or `spec.json`) whose `backend` is the legacy `"channel"` is DUAL-READ —
126
+ * normalized to `"attached"` on read (`parseAgentDef` in agent-defs.ts; the daemon
127
+ * routing fork also accepts a legacy un-normalized value defensively). Only the new
128
+ * `"attached"` value is ever WRITTEN. (Note: the ROUTING KEY `channel` — the agent's
129
+ * address, the `/mcp/<channel>` URL segment, `metadata.channel` on notes — is a
130
+ * SEPARATE concept and is deliberately unchanged.)
131
+ *
132
+ * BACK-COMPAT (retired `interactive`): a persisted `spec.json` that carries the retired
133
+ * `backend:"interactive"` value (or omits `backend` entirely — pre-field specs were
134
+ * interactive) is no longer re-registered on boot (the boot re-register reads
135
+ * `spec.backend === "programmatic"` exactly; anything else is skipped — see daemon.ts),
136
+ * so a stale interactive spec on disk is inert, never migrated and never launched.
137
+ */
138
+ export type AgentBackendKind = "programmatic" | "attached";
139
+
140
+ /**
141
+ * How a channel's {@link AgentSpec.systemPrompt} composes with Claude Code's own
142
+ * default system prompt (design 2026-06-16-channel-system-prompt.md):
143
+ *
144
+ * - `"append"` (DEFAULT): KEEP Claude Code's capable default system prompt
145
+ * (~2.4k tokens of tool/agentic instruction) and ADD the channel's role on top
146
+ * — `claude -p --append-system-prompt(-file) <X>`. The right default: the
147
+ * channel gets strong specificity without losing CC's base competence.
148
+ * - `"replace"`: REPLACE the default entirely with the channel's prompt —
149
+ * `claude -p --system-prompt(-file) <X>`. A fully-custom persona for an
150
+ * operator who wants total control of the system layer (and leanness on the
151
+ * subscription — fewer base tokens per turn).
152
+ *
153
+ * Either mode is orthogonal to CLAUDE.md, which is a SEPARATE context layer
154
+ * unaffected by both flags (only `--bare` would drop it — and `--bare` is
155
+ * deliberately NOT implemented; see the design note: it is API-key-only by design
156
+ * and would force metered billing off the subscription).
157
+ */
158
+ export type SystemPromptMode = "append" | "replace";
159
+
160
+ /**
161
+ * The agent's EXECUTION-LIFECYCLE mode — how a turn relates to the agent's
162
+ * conversation thread (the architecture synthesis, Phase 3 prerequisite). The UNIFIED
163
+ * model is `definition -> thread -> message`: EVERYTHING is a thread, and BOTH modes
164
+ * materialize a `#agent/thread` note (the structural unification — a "run" was always a
165
+ * thread with one turn). An agent is either SINGLE-THREADED or MULTI-THREADED; the
166
+ * distinction is defined entirely by `claude -p` session-id semantics + the thread's
167
+ * identity:
168
+ *
169
+ * - `"single-threaded"` (DEFAULT; = today's behavior): ONE persistent session id
170
+ * per channel. Each turn `--resume`s the stored id and persists the returned id
171
+ * after — the channel transcript IS the thread. It materializes exactly ONE
172
+ * `#agent/thread` note per channel, named after the definition, UPSERTED in place
173
+ * each turn; the note body holds a rolling SUMMARY of the conversation (turn_count +
174
+ * cumulative usage roll up). A scheduled runner job for a single-threaded def is a
175
+ * synthetic inbound that RESUMES that one thread (continuing the chat). This is
176
+ * exactly what every agent does today (plus the now-materialized thread note).
177
+ *
178
+ * - `"multi-threaded"`: turns are THREAD-KEYED. TODAY — because no inbound carries
179
+ * a thread id yet — every fire mints a FRESH thread: do NOT read the prior session
180
+ * id (no `--resume`) and do NOT persist the returned id to the channel store, so
181
+ * each fire is a clean, independent invocation with no conversation continuity. It
182
+ * materializes ONE `#agent/thread` note per FIRE (the per-fire record: input + reply
183
+ * + status + timing). This is what an operator reaches for when a scheduled job
184
+ * should be a clean task run, NOT a silent continuation of the chat thread.
185
+ *
186
+ * ("one-shot" was the prior name for this mode — it was only ever the DEGENERATE
187
+ * FIRST-TURN of a multi-threaded agent, so the term retires. Continuation-by-
188
+ * thread-id — resuming a SPECIFIC prior thread — is a DEFERRED increment: it needs
189
+ * thread-id routing on the inbound, a thread-keyed session store, per-thread drain
190
+ * serialization, and recording the minted session/thread id into the thread note so
191
+ * a thread becomes resumable. When it lands, the SAME mode simply gains continuation
192
+ * with NO operator-facing change and NO migration; the fresh-per-fire shape that ships
193
+ * now is its degenerate case.)
194
+ */
195
+ export type AgentMode = "single-threaded" | "multi-threaded";
196
+
197
+ export interface AgentSpec {
198
+ /** Human-readable arm name; used as the tmux session + workspace slug. */
199
+ name: string;
200
+ /**
201
+ * Channels to attach (one MCP entry each). Each entry is a bare name (read+write,
202
+ * back-compat) or `{ name, access: "read" }` to scope a channel read-only.
203
+ */
204
+ channels: AgentChannel[];
205
+ /**
206
+ * WORKING DIRECTORY — a real host path the agent operates from (design
207
+ * 2026-06-16-agent-filesystem-and-sharing.md, the working-directory axis). When
208
+ * set, this absolute path becomes the agent's CWD and an rw working-root in the
209
+ * sandbox (it's bound rw + readable, exactly like an `rw` mount that is also the
210
+ * cwd). It is SHAREABLE — two agents (or a runner job, or a plain script) can
211
+ * point at the same dir (shared-rw concurrency is a known, deferred caveat; the
212
+ * agents step on each other like humans in a repo without git discipline).
213
+ *
214
+ * CRITICAL — the working dir is DECOUPLED from the agent's PRIVATE RUNTIME HOME.
215
+ * The seeded `CLAUDE_CONFIG_DIR` (`seedAgentHome`), `tmp`, `spec.json`,
216
+ * `system-prompt.txt`, and ESPECIALLY `.mcp.json` (which inlines the scoped
217
+ * vault/channel tokens — secrets) STAY in the per-agent private `sessions/<name>/`
218
+ * dir, 0600, NEVER written into this shared `workspace`. The governing principle
219
+ * (the design note's "line"): capability/credential/state is per-agent private;
220
+ * the working dir is shareable. `--mcp-config` / `--system-prompt-file` point at
221
+ * the private dir by ABSOLUTE path, so they're unaffected by the cwd change.
222
+ *
223
+ * Unset → today's behavior EXACTLY: the cwd is the private `sessions/<name>` dir
224
+ * (which is also the synthetic workspace).
225
+ */
226
+ workspace?: string;
227
+ /** Optional vault binding. */
228
+ vault?: AgentVaultSpec;
229
+ /** Additional MCP servers, by URL. */
230
+ otherMcps?: OtherMcpSpec[];
231
+ /**
232
+ * Filesystem READ scope — ONE of Anthropic's two containment boundaries
233
+ * (https://www.anthropic.com/engineering/claude-code-sandboxing). Orthogonal to
234
+ * {@link network} — the agent's reach into the local disk and its reach onto the
235
+ * network are independent controls, deliberately NOT bundled.
236
+ *
237
+ * - `"workspace"` (DEFAULT): SCOPED reads. The home tree (`/Users` on macOS,
238
+ * `/home` on Linux) is DENIED, then re-allowed ONLY for the per-session
239
+ * workspace + the claude runtime + declared mounts. The agent literally
240
+ * cannot read the operator's secrets — `~/.parachute/operator.token` (the
241
+ * hub bearer), SSH keys, other projects — even though the network is open.
242
+ * This is the security-correct default: scoped disk, exfiltration surface
243
+ * removed at the source.
244
+ *
245
+ * - `"full"`: BROAD reads (the runtime default — the whole filesystem is
246
+ * readable). An explicit, deliberate escape hatch for the rare agent that
247
+ * genuinely needs to read across the operator's disk AND is trusted not to
248
+ * leak it. Combined with `network: "open"` this is the maximum-reach posture
249
+ * — only choose it knowingly.
250
+ *
251
+ * WRITES are confined to the per-session workspace + rw mounts in BOTH cases
252
+ * (the agent can never corrupt the operator's files or escape its workspace),
253
+ * exactly per Anthropic's "read/write the cwd, block outside" model.
254
+ */
255
+ filesystem?: "workspace" | "full";
256
+ /**
257
+ * Network egress — the SECOND containment boundary, orthogonal to
258
+ * {@link filesystem}:
259
+ *
260
+ * - `"open"` (DEFAULT): full internet, no restriction. The right default for
261
+ * an owner-operated agent on a trusted box (claude needs the network to be
262
+ * useful) — SAFE because the `"workspace"` filesystem default already keeps
263
+ * local secrets unreadable, so "open network" can't exfiltrate what the
264
+ * agent can't see.
265
+ *
266
+ * - `"restricted"`: egress confined to a non-removable base
267
+ * (`{ Anthropic API, hub/vault }`) UNIONed with {@link egress}. For an agent
268
+ * fed FOREIGN/untrusted input, where you want to bound where it can reach
269
+ * even for data it legitimately holds.
270
+ */
271
+ network?: "open" | "restricted";
272
+ /**
273
+ * Network egress hosts ADDITIVE to the non-removable base — only meaningful
274
+ * under `network: "restricted"` (when the network is `"open"` this is ignored).
275
+ * A restricted code-building agent opens exactly the package/source hosts it
276
+ * needs here.
277
+ */
278
+ egress?: string[];
279
+ /**
280
+ * Filesystem mounts — ADDITIVE to the default private per-session workspace
281
+ * (rw) + the implicit runtime/claude-config (ro). Under `filesystem:
282
+ * "workspace"` (the default) these are the ONLY host paths re-allowed for
283
+ * reads beyond the workspace — mount the project you want the agent to work on.
284
+ */
285
+ mounts?: AgentMount[];
286
+ /**
287
+ * Which stored Claude credential to inject (design §6). Default = "operator".
288
+ * This stream injects the credential as a passed-in param/placeholder; Stream 3
289
+ * builds the real per-channel secret store.
290
+ */
291
+ credentialRef?: string;
292
+ /**
293
+ * Which backend drives the agent (design 2026-06-16-pluggable-agent-backend.md +
294
+ * 2026-06-18-channel-backend.md) — `"programmatic"` (the default; on-demand
295
+ * `claude -p` turns, no resident process) or `"attached"` (handled by a Claude Code
296
+ * session the operator connects — "attaches" — to the channel's MCP endpoint; the
297
+ * value was named `"channel"` before the rename, dual-read on load). The `interactive`
298
+ * (tmux) backend was retired 2026-06-19. See {@link AgentBackendKind}. Persisted in
299
+ * spec.json so a daemon restart re-registers a programmatic agent on boot.
300
+ */
301
+ backend?: AgentBackendKind;
302
+ /**
303
+ * Which model the PROGRAMMATIC backend runs the turn on — passed verbatim to
304
+ * `claude -p --model <value>`. Accepts a Claude Code alias (`opus` / `sonnet` /
305
+ * `haiku`) or a full model id (e.g. `claude-opus-4-8`). Unset → no `--model`
306
+ * flag, so the turn inherits Claude Code's own default (Sonnet today). Only the
307
+ * programmatic backend reads this (a `channel`-backend turn runs in the
308
+ * operator's own session, whose model the operator controls). Set from the def's
309
+ * `metadata.model`; persisted in spec.json. NOT shell-interpolated — it's a
310
+ * discrete argv element, so an arbitrary string can't inject.
311
+ */
312
+ model?: string;
313
+ /**
314
+ * Per-channel system prompt — the operator gives the channel a specific role,
315
+ * backend-visible, so the agent gets strong specificity decoupled from the
316
+ * workspace + CLAUDE.md (design 2026-06-16-channel-system-prompt.md). Set when
317
+ * creating/configuring the channel; written to a per-session file and passed on
318
+ * EVERY `claude -p` turn (the flags are per-invocation, not persistent — a
319
+ * `--resume` turn re-passes it too). Unset → today's behavior (CC's default
320
+ * prompt, untouched). Persisted in spec.json.
321
+ */
322
+ systemPrompt?: string;
323
+ /**
324
+ * How {@link systemPrompt} composes with Claude Code's default system prompt —
325
+ * `"append"` (DEFAULT, keep CC's base + add the role) or `"replace"` (full
326
+ * custom persona). Only meaningful when `systemPrompt` is set. See
327
+ * {@link SystemPromptMode}.
328
+ */
329
+ systemPromptMode?: SystemPromptMode;
330
+ /**
331
+ * The execution-lifecycle mode (the Phase-3 prerequisite). `"single-threaded"`
332
+ * (DEFAULT, = today): one persistent session per channel, `--resume`d + persisted
333
+ * each turn, the channel transcript is the thread. `"multi-threaded"`: thread-keyed —
334
+ * today (no inbound thread id yet) every fire mints a fresh thread (no `--resume`, the
335
+ * returned session id is NOT persisted to the channel store). BOTH modes now materialize
336
+ * an `#agent/thread` note (the unified model `definition -> thread -> message`): a
337
+ * single-threaded agent upserts ONE thread note per channel (named after the def, rolling
338
+ * summary); a multi-threaded agent writes one thread note per fire. Read at the
339
+ * session-handling chokepoint (the programmatic backend's `deliver` resume block) +
340
+ * governs the thread note's identity (one-per-channel upsert vs one-per-fire). Persisted
341
+ * in spec.json (set from the def's `metadata.mode`). See {@link AgentMode}.
342
+ */
343
+ mode?: AgentMode;
344
+ /**
345
+ * The `#agent/definition` note id this agent was instantiated from — the
346
+ * provenance carried into the `#agent/thread` note a turn materializes (so a thread
347
+ * record links back to its def; BOTH modes). A plain id STRING for now (interim — typed
348
+ * link fields are a future vault feature). Set by {@link parseAgentDef} from the note
349
+ * id; unset for a spec not sourced from a def note (then the thread note carries no
350
+ * definition link).
351
+ */
352
+ definition?: string;
353
+ }
354
+
355
+ /**
356
+ * The default workspace + runtime binds the contract always grants, independent
357
+ * of any spec. The caller supplies the concrete paths (resolved against the
358
+ * session's state dir + the runtime/config location) — keeping this module
359
+ * free of filesystem-layout assumptions and test-sandboxable.
360
+ */
361
+ export interface BaseBinds {
362
+ /** Private per-session workspace (rw). */
363
+ workspace: string;
364
+ /**
365
+ * Read-only runtime/claude-config paths the session needs to run `claude`
366
+ * (e.g. the claude config dir). Always bound `ro`.
367
+ */
368
+ runtimeReadOnly: string[];
369
+ }
370
+
371
+ /** Platform the Sandbox config is being built for. */
372
+ export type SandboxPlatform = "darwin" | "linux";
373
+
374
+ /**
375
+ * Normalize a channel entry (bare name or object) to `{ name, access }`. A bare
376
+ * string defaults to `write` (back-compat); the object form defaults to `write`
377
+ * when `access` is omitted.
378
+ */
379
+ export function normalizeChannel(ch: AgentChannel): { name: string; access: "read" | "write" } {
380
+ if (typeof ch === "string") return { name: ch, access: "write" };
381
+ return { name: ch.name, access: ch.access ?? "write" };
382
+ }
@@ -0,0 +1,106 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { mkdtempSync, readFileSync, writeFileSync, existsSync } from "fs";
3
+ import { tmpdir } from "os";
4
+ import { join } from "path";
5
+ import { resolveManifestPath, upsertService, listVaultNames, type ServiceEntry } from "./services-manifest.ts";
6
+
7
+ function tmp(): string {
8
+ return join(mkdtempSync(join(tmpdir(), "pc-manifest-")), "services.json");
9
+ }
10
+
11
+ const AGENT: ServiceEntry = {
12
+ name: "parachute-agent",
13
+ port: 1941,
14
+ paths: ["/agent"],
15
+ health: "/health",
16
+ version: "0.1.0",
17
+ displayName: "Agent",
18
+ stripPrefix: true,
19
+ };
20
+
21
+ describe("resolveManifestPath", () => {
22
+ test("honors PARACHUTE_HOME (sandbox for tests/e2e)", () => {
23
+ expect(resolveManifestPath({ PARACHUTE_HOME: "/tmp/sandbox" })).toBe("/tmp/sandbox/services.json");
24
+ });
25
+ test("falls back to HOME/.parachute", () => {
26
+ expect(resolveManifestPath({ HOME: "/home/x" })).toBe("/home/x/.parachute/services.json");
27
+ });
28
+ });
29
+
30
+ describe("upsertService", () => {
31
+ test("creates the manifest with the entry on first write", () => {
32
+ const path = tmp();
33
+ upsertService(AGENT, path);
34
+ const m = JSON.parse(readFileSync(path, "utf8"));
35
+ expect(m.services).toHaveLength(1);
36
+ expect(m.services[0].name).toBe("parachute-agent");
37
+ expect(m.services[0].paths).toEqual(["/agent"]);
38
+ expect(m.services[0].stripPrefix).toBe(true);
39
+ });
40
+
41
+ test("carries startCmd so the hub supervisor can start/restart/adopt the module (agent#34)", () => {
42
+ const path = tmp();
43
+ upsertService({ ...AGENT, startCmd: ["parachute-agent"] }, path);
44
+ const m = JSON.parse(readFileSync(path, "utf8"));
45
+ expect(m.services[0].startCmd).toEqual(["parachute-agent"]);
46
+ });
47
+
48
+ test("is idempotent — re-registering the same name does not duplicate", () => {
49
+ const path = tmp();
50
+ upsertService(AGENT, path);
51
+ upsertService({ ...AGENT, version: "0.1.1" }, path);
52
+ const m = JSON.parse(readFileSync(path, "utf8"));
53
+ expect(m.services).toHaveLength(1);
54
+ expect(m.services[0].version).toBe("0.1.1"); // module wins for fields it owns
55
+ });
56
+
57
+ test("merges — preserves hub-stamped fields the module doesn't author", () => {
58
+ const path = tmp();
59
+ // hub stamped installDir onto the row; module re-registers without it.
60
+ writeFileSync(path, JSON.stringify({ services: [{ ...AGENT, installDir: "/hub/stamped" }] }));
61
+ upsertService(AGENT, path);
62
+ const m = JSON.parse(readFileSync(path, "utf8"));
63
+ expect(m.services[0].installDir).toBe("/hub/stamped");
64
+ });
65
+
66
+ test("preserves other modules' entries", () => {
67
+ const path = tmp();
68
+ writeFileSync(path, JSON.stringify({ services: [{ name: "parachute-vault", port: 1940, paths: ["/vault/default"], health: "/vault/default/health", version: "0.5.2" }] }));
69
+ upsertService(AGENT, path);
70
+ const m = JSON.parse(readFileSync(path, "utf8"));
71
+ expect(m.services).toHaveLength(2);
72
+ expect(m.services.map((s: ServiceEntry) => s.name).sort()).toEqual(["parachute-agent", "parachute-vault"]);
73
+ });
74
+
75
+ test("throws on a malformed manifest rather than clobbering it", () => {
76
+ const path = tmp();
77
+ writeFileSync(path, JSON.stringify({ not_services: true }));
78
+ expect(() => upsertService(AGENT, path)).toThrow(/malformed/);
79
+ expect(existsSync(path)).toBe(true); // original left intact
80
+ expect(JSON.parse(readFileSync(path, "utf8"))).toEqual({ not_services: true }); // content untouched
81
+ });
82
+ });
83
+
84
+ describe("listVaultNames", () => {
85
+ test("extracts vault names from the vault module's /vault/<name> paths, default first", () => {
86
+ const path = tmp();
87
+ writeFileSync(path, JSON.stringify({ services: [
88
+ { name: "parachute-vault", port: 1940, paths: ["/vault/boulder", "/vault/default", "/vault/techne"], health: "x", version: "1" },
89
+ { name: "parachute-agent", port: 1941, paths: ["/agent"], health: "x", version: "1" },
90
+ ] }));
91
+ expect(listVaultNames(path)).toEqual(["default", "boulder", "techne"]);
92
+ });
93
+
94
+ test("dedupes across services and ignores non-vault paths", () => {
95
+ const path = tmp();
96
+ writeFileSync(path, JSON.stringify({ services: [
97
+ { name: "a", port: 1, paths: ["/vault/x", "/other"], health: "x", version: "1" },
98
+ { name: "b", port: 2, paths: ["/vault/x", "/vault/y"], health: "x", version: "1" },
99
+ ] }));
100
+ expect(listVaultNames(path).sort()).toEqual(["x", "y"]);
101
+ });
102
+
103
+ test("returns [] when the manifest is absent or unreadable", () => {
104
+ expect(listVaultNames(join(tmpdir(), "does-not-exist-" + Math.floor(performance.now()), "services.json"))).toEqual([]);
105
+ });
106
+ });
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Self-registration into `~/.parachute/services.json` on daemon boot.
3
+ *
4
+ * Mirrors `parachute-scribe/src/services-manifest.ts` deliberately — the file
5
+ * shape is the contract between every Parachute module and the hub
6
+ * (`parachute-hub/src/services-manifest.ts` is the canonical reader). Hub reads
7
+ * this to know the module's port, the paths it should reverse-proxy
8
+ * (`/agent/*` over the expose → this daemon on loopback), and the version.
9
+ *
10
+ * Best-effort: any write error is logged + swallowed by the caller. The daemon
11
+ * still serves locally even if registration fails. Honors `PARACHUTE_HOME` so
12
+ * sandboxed/test/e2e daemons never touch the operator's real services.json.
13
+ */
14
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
15
+ import { homedir } from "node:os";
16
+ import { dirname, join } from "node:path";
17
+
18
+ export interface ServiceEntry {
19
+ name: string;
20
+ port: number;
21
+ paths: string[];
22
+ health: string;
23
+ version: string;
24
+ displayName?: string;
25
+ tagline?: string;
26
+ installDir?: string;
27
+ stripPrefix?: boolean;
28
+ /** Hub-stamped fields (e.g. installDir) ride along; the upsert merges. */
29
+ [key: string]: unknown;
30
+ }
31
+
32
+ interface ServicesManifest {
33
+ services: ServiceEntry[];
34
+ }
35
+
36
+ /** Canonical services.json path. Honors PARACHUTE_HOME for sandbox/test runs. */
37
+ export function resolveManifestPath(env: Record<string, string | undefined> = process.env): string {
38
+ const base = env.PARACHUTE_HOME ?? join(env.HOME ?? homedir(), ".parachute");
39
+ return join(base, "services.json");
40
+ }
41
+
42
+ function readManifest(path: string): ServicesManifest {
43
+ if (!existsSync(path)) return { services: [] };
44
+ const raw = JSON.parse(readFileSync(path, "utf8"));
45
+ if (!raw || typeof raw !== "object" || !Array.isArray((raw as { services?: unknown }).services)) {
46
+ throw new Error(`services manifest at ${path} is malformed (missing "services" array)`);
47
+ }
48
+ return raw as ServicesManifest;
49
+ }
50
+
51
+ /**
52
+ * List the vault instance names installed on this host, from the vault module's
53
+ * registered `paths` (`/vault/<name>` → `<name>`). Used by the agents page's vault
54
+ * picker so an operator chooses from real vaults instead of typing a name blind.
55
+ * Best-effort: returns `[]` if the manifest is absent/unreadable (the picker then
56
+ * falls back to free text). Deduped + sorted; `default` floated first if present.
57
+ */
58
+ export function listVaultNames(path: string = resolveManifestPath()): string[] {
59
+ let manifest: ServicesManifest;
60
+ try {
61
+ manifest = readManifest(path);
62
+ } catch {
63
+ return [];
64
+ }
65
+ const names = new Set<string>();
66
+ for (const svc of manifest.services) {
67
+ for (const p of svc.paths ?? []) {
68
+ // `paths` are operator-registered route prefixes, not URLs — take the literal
69
+ // segment (no decodeURIComponent: a stray %2F could synthesize a slash-bearing
70
+ // vault name, and real vault names are plain slugs).
71
+ const m = /^\/vault\/([^/]+)/.exec(p);
72
+ if (m && m[1]) names.add(m[1]);
73
+ }
74
+ }
75
+ const sorted = [...names].sort((a, b) => a.localeCompare(b));
76
+ // Float "default" to the front — it's the conventional primary vault.
77
+ return sorted.sort((a, b) => (a === "default" ? -1 : b === "default" ? 1 : 0));
78
+ }
79
+
80
+ /**
81
+ * Idempotent upsert of a service entry. Merges into any existing row rather
82
+ * than replacing it — preserves hub-stamped fields the module doesn't own.
83
+ * Atomic write: stages to a tmp file, then renames over the target so a crash
84
+ * mid-write leaves the prior file intact.
85
+ */
86
+ export function upsertService(entry: ServiceEntry, path: string = resolveManifestPath()): void {
87
+ mkdirSync(dirname(path), { recursive: true });
88
+ const manifest = readManifest(path);
89
+ const idx = manifest.services.findIndex((s) => s.name === entry.name);
90
+ if (idx >= 0) manifest.services[idx] = { ...manifest.services[idx], ...entry };
91
+ else manifest.services.push(entry);
92
+ const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
93
+ writeFileSync(tmp, `${JSON.stringify(manifest, null, 2)}\n`);
94
+ renameSync(tmp, path);
95
+ }