@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
package/src/router.ts DELETED
@@ -1,530 +0,0 @@
1
- /**
2
- * Inbound message routing.
3
- *
4
- * Channel adapter event → resolve messaging group → sender resolver →
5
- * resolve/pick agent → access gate → resolve/create session → write
6
- * messages_in → wake container.
7
- *
8
- * Two module hooks (registered by the permissions module):
9
- * - `setSenderResolver` runs BEFORE agent resolution so user rows get
10
- * upserted even if the message ends up dropped by agent wiring.
11
- * Without the module, userId is null and downstream code tolerates it.
12
- * - `setAccessGate` runs AFTER agent resolution so policy decisions can
13
- * branch on the target agent group. Without the module, access is
14
- * allow-all.
15
- *
16
- * `dropped_messages` is core audit infra. Core writes rows for structural
17
- * drops (no agent wired, no trigger match); the access gate writes rows
18
- * for policy refusals.
19
- */
20
- import { getChannelAdapter } from './channels/channel-registry.js';
21
- import { consumeTrustHint } from './channels/trust-hint.js';
22
- import { gateCommand } from './command-gate.js';
23
- import { getAgentGroup, getAllAgentGroups } from './db/agent-groups.js';
24
- import { recordDroppedMessage } from './db/dropped-messages.js';
25
- import {
26
- createMessagingGroup,
27
- getMessagingGroupAgents,
28
- getMessagingGroupWithAgentCount,
29
- updateMessagingGroup,
30
- } from './db/messaging-groups.js';
31
- import { decodePlatformIdAs } from './platform-id.js';
32
- import { wireDmToAgent } from './web/wire-channel.js';
33
- import { findSessionForAgent } from './db/sessions.js';
34
- import { startTypingRefresh } from './modules/typing/index.js';
35
- import { log } from './log.js';
36
- import { resolveSession, writeSessionMessage, writeOutboundDirect } from './session-manager.js';
37
- import { wakeContainer } from './container-runner.js';
38
- import { getSession } from './db/sessions.js';
39
- import type { AgentGroup, MessagingGroup, MessagingGroupAgent } from './types.js';
40
- import type { InboundEvent } from './channels/adapter.js';
41
-
42
- function generateId(): string {
43
- return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
44
- }
45
-
46
- /**
47
- * Sender-resolver hook. Runs before agent resolution.
48
- *
49
- * The permissions module registers this to extract the sender's namespaced
50
- * user id and upsert the users row. Returns null when the payload doesn't
51
- * carry enough info to identify a sender. Without the hook, every message
52
- * arrives at the gate with userId=null.
53
- */
54
- export type SenderResolverFn = (event: InboundEvent) => string | null;
55
-
56
- let senderResolver: SenderResolverFn | null = null;
57
-
58
- export function setSenderResolver(fn: SenderResolverFn): void {
59
- if (senderResolver) {
60
- log.warn('Sender resolver overwritten');
61
- }
62
- senderResolver = fn;
63
- }
64
-
65
- /**
66
- * Access-gate hook. Runs after agent resolution.
67
- *
68
- * The permissions module registers this; without it, core defaults to
69
- * allow-all. The gate receives the raw event so it can extract the sender
70
- * name for audit-trail purposes, and it is responsible for recording its
71
- * own `dropped_messages` row on refusal (structural drops are already
72
- * recorded by core before the gate runs).
73
- */
74
- export type AccessGateResult = { allowed: true } | { allowed: false; reason: string };
75
-
76
- export type AccessGateFn = (
77
- event: InboundEvent,
78
- userId: string | null,
79
- mg: MessagingGroup,
80
- agentGroupId: string,
81
- ) => AccessGateResult;
82
-
83
- let accessGate: AccessGateFn | null = null;
84
-
85
- export function setAccessGate(fn: AccessGateFn): void {
86
- if (accessGate) {
87
- log.warn('Access gate overwritten');
88
- }
89
- accessGate = fn;
90
- }
91
-
92
- /**
93
- * Per-wiring sender-scope hook. Runs alongside the access gate for each
94
- * agent that would otherwise engage — lets the permissions module enforce
95
- * `sender_scope='known'` on wirings that are stricter than the messaging
96
- * group's `unknown_sender_policy`. When the hook isn't registered (module
97
- * not installed), sender_scope is a no-op.
98
- */
99
- export type SenderScopeGateFn = (
100
- event: InboundEvent,
101
- userId: string | null,
102
- mg: MessagingGroup,
103
- agent: MessagingGroupAgent,
104
- ) => AccessGateResult;
105
-
106
- let senderScopeGate: SenderScopeGateFn | null = null;
107
-
108
- export function setSenderScopeGate(fn: SenderScopeGateFn): void {
109
- if (senderScopeGate) {
110
- log.warn('Sender-scope gate overwritten');
111
- }
112
- senderScopeGate = fn;
113
- }
114
-
115
- /**
116
- * Channel-registration hook. Runs when the router sees a mention/DM on a
117
- * messaging group that has no wirings AND hasn't been denied. The hook is
118
- * expected to escalate to an owner (card, etc.) and arrange for future
119
- * replay via routeInbound after approval. Fire-and-forget from the
120
- * router's perspective.
121
- *
122
- * Registered by the permissions module. Without the module the router
123
- * silently records the drop with reason='no_agent_wired' and moves on.
124
- */
125
- export type ChannelRequestGateFn = (mg: MessagingGroup, event: InboundEvent) => Promise<void>;
126
-
127
- let channelRequestGate: ChannelRequestGateFn | null = null;
128
-
129
- export function setChannelRequestGate(fn: ChannelRequestGateFn): void {
130
- if (channelRequestGate) {
131
- log.warn('Channel-request gate overwritten');
132
- }
133
- channelRequestGate = fn;
134
- }
135
-
136
- function safeParseContent(raw: string): { text?: string; sender?: string; senderId?: string } {
137
- try {
138
- return JSON.parse(raw);
139
- } catch {
140
- return { text: raw };
141
- }
142
- }
143
-
144
- /**
145
- * Route an inbound message from a channel adapter to the correct session.
146
- * Creates messaging group + session if they don't exist yet.
147
- */
148
- export async function routeInbound(event: InboundEvent): Promise<void> {
149
- // 0. Apply the adapter's thread policy. Non-threaded adapters (Telegram,
150
- // WhatsApp, iMessage, email) collapse threads to the channel.
151
- // By-channel-type (not by-bot) lookup is correct here: we only read
152
- // `supportsThreads`, which is a property of the channel itself, not of
153
- // a specific bot identity. Per-bot resolution (`getChannelAdapterForPlatformId`)
154
- // is reserved for delivery, where the outbound adapter must match the
155
- // bot dimension encoded in the v2 platform_id.
156
- const adapter = getChannelAdapter(event.channelType);
157
- if (adapter && !adapter.supportsThreads) {
158
- event = { ...event, threadId: null };
159
- }
160
-
161
- const isMention = event.message.isMention === true;
162
-
163
- // 1. Combined lookup: messaging_group row + count of wired agents in a
164
- // single query. Cheap short-circuit for the common "unwired channel"
165
- // case — one DB read and we're out, no auto-create, no sender
166
- // resolution, no log spam.
167
- const found = getMessagingGroupWithAgentCount(event.channelType, event.platformId);
168
-
169
- let mg: MessagingGroup;
170
- let agentCount: number;
171
- if (!found) {
172
- // No messaging_groups row. Auto-create only when the message warrants
173
- // attention (the bot was addressed — @mention or DM). Plain chatter in
174
- // channels we merely sit in stays silent — no row, no DB writes.
175
- if (!isMention) return;
176
- const mgId = `mg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
177
- mg = {
178
- id: mgId,
179
- channel_type: event.channelType,
180
- platform_id: event.platformId,
181
- name: null,
182
- is_group: event.message.isGroup ? 1 : 0,
183
- unknown_sender_policy: 'request_approval',
184
- denied_at: null,
185
- created_at: new Date().toISOString(),
186
- };
187
- createMessagingGroup(mg);
188
- log.info('Auto-created messaging group', {
189
- id: mgId,
190
- channelType: event.channelType,
191
- platformId: event.platformId,
192
- });
193
- agentCount = 0;
194
- } else {
195
- mg = found.mg;
196
- agentCount = found.agentCount;
197
- }
198
-
199
- // 1b. No wirings — either silent drop (plain chatter / denied channel) or
200
- // escalate to owner for channel-registration approval.
201
- if (agentCount === 0) {
202
- if (!isMention) return;
203
- if (mg.denied_at) {
204
- log.debug('Message dropped — channel was denied by owner', {
205
- messagingGroupId: mg.id,
206
- deniedAt: mg.denied_at,
207
- });
208
- return;
209
- }
210
-
211
- const parsedUnwired = safeParseContent(event.message.content);
212
-
213
- // Trust hint: if the operator just wired this bot at /channels/new and
214
- // is now DM'ing it, treat the message as trusted self-traffic instead
215
- // of escalating through the channel-registration approval flow. The
216
- // hint is single-use and bound to (channelType, botId, operatorUserId)
217
- // — Discord wires record no hint (no operator user id captured), so
218
- // this branch only fires for Telegram self-DMs in the trust window.
219
- const decoded = decodePlatformIdAs(event.platformId, 'v2');
220
- const senderId = parsedUnwired.senderId;
221
- if (decoded.botId && senderId && consumeTrustHint(event.channelType, decoded.botId, senderId)) {
222
- const targetGroups = getAllAgentGroups();
223
- if (targetGroups.length === 0) {
224
- // Hint already consumed (single-use) but there's no agent group to
225
- // wire to. Falls through to the approval cascade below; surface a
226
- // warn so an operator who hits this can correlate it with their
227
- // missing agent-group setup.
228
- log.warn('Trust hint consumed but no agent groups — operator message dropped', {
229
- messagingGroupId: mg.id,
230
- channelType: event.channelType,
231
- botId: decoded.botId,
232
- });
233
- }
234
- if (targetGroups.length > 0) {
235
- // Multi-agent-group installs: this picks the first group by DB
236
- // insert order. Good enough for the trust-hint use case (operator
237
- // just wired a bot and is DM'ing it now — usually the install only
238
- // has one group anyway), but a richer "wire to the most recently
239
- // wired group" or operator-prompted pick is a follow-up if needed.
240
- const target = targetGroups[0]!;
241
- log.info('Channel inbound auto-wired via operator trust hint', {
242
- messagingGroupId: mg.id,
243
- channelType: event.channelType,
244
- botId: decoded.botId,
245
- targetAgentGroupId: target.id,
246
- });
247
- // Drop the auto-created request_approval row in favor of a fresh
248
- // wire built with the trusted defaults (strict policy, all-senders
249
- // MGA). wireDmToAgent is idempotent — if we beat it to a wire that
250
- // already exists, it returns the existing rows.
251
- updateMessagingGroup(mg.id, { unknown_sender_policy: 'strict' });
252
- wireDmToAgent({
253
- channelType: event.channelType as 'discord' | 'telegram',
254
- agentGroup: target,
255
- botId: decoded.botId,
256
- botUserId: decoded.native,
257
- });
258
- // Re-run the standard route from the now-wired path so engage
259
- // checks, sender resolution, and session creation behave exactly
260
- // as if this were a normal message on a pre-wired channel.
261
- await routeInbound(event);
262
- return;
263
- }
264
- }
265
-
266
- recordDroppedMessage({
267
- channel_type: event.channelType,
268
- platform_id: event.platformId,
269
- user_id: null,
270
- sender_name: parsedUnwired.sender ?? null,
271
- reason: 'no_agent_wired',
272
- messaging_group_id: mg.id,
273
- agent_group_id: null,
274
- });
275
-
276
- if (channelRequestGate) {
277
- // Fire-and-forget escalation. The gate is expected to build a card,
278
- // persist pending_channel_approvals, and replay the event via
279
- // routeInbound after approval. Errors are logged internally — the
280
- // user's message still stays dropped here either way.
281
- void channelRequestGate(mg, event).catch((err) =>
282
- log.error('Channel-request gate threw', { messagingGroupId: mg.id, err }),
283
- );
284
- } else {
285
- log.warn('MESSAGE DROPPED — no agent groups wired and no channel-request gate registered', {
286
- messagingGroupId: mg.id,
287
- channelType: event.channelType,
288
- platformId: event.platformId,
289
- });
290
- }
291
- return;
292
- }
293
-
294
- // 2. Sender resolution (permissions module upserts the users row as a
295
- // side effect so later role/access lookups find a real record).
296
- // Without the module, userId is null — downstream tolerates it.
297
- const userId: string | null = senderResolver ? senderResolver(event) : null;
298
-
299
- // 3. Fetch wired agents in full (we already know the count is > 0; now
300
- // we need their actual rows for fan-out).
301
- const agents = getMessagingGroupAgents(mg.id);
302
-
303
- // 4. Fan-out: evaluate each wired agent independently against engage_mode,
304
- // sender_scope, and access gate. An agent that engages gets its own
305
- // session and container wake. An agent that declines but has
306
- // ignored_message_policy='accumulate' still gets the message stored in
307
- // its session (trigger=0) so the context is available when it does
308
- // engage later. Drop policy = skip silently.
309
- //
310
- // Subscribe (for mention-sticky wirings on threaded platforms) fires
311
- // once per message from this loop — the first engaging mention-sticky
312
- // wiring triggers adapter.subscribe(...); subsequent wirings don't
313
- // re-subscribe (chat.subscribe is idempotent anyway, but the flag
314
- // avoids the extra await).
315
- const parsed = safeParseContent(event.message.content);
316
- const messageText = parsed.text ?? '';
317
-
318
- let engagedCount = 0;
319
- let accumulatedCount = 0;
320
- let subscribed = false;
321
-
322
- for (const agent of agents) {
323
- const agentGroup = getAgentGroup(agent.agent_group_id);
324
- if (!agentGroup) continue;
325
-
326
- const engages = evaluateEngage(agent, messageText, isMention, mg, event.threadId);
327
-
328
- const accessOk = engages && (!accessGate || accessGate(event, userId, mg, agent.agent_group_id).allowed);
329
- const scopeOk = engages && (!senderScopeGate || senderScopeGate(event, userId, mg, agent).allowed);
330
-
331
- if (engages && accessOk && scopeOk) {
332
- await deliverToAgent(agent, agentGroup, mg, event, userId, adapter?.supportsThreads === true, true);
333
- engagedCount++;
334
-
335
- // Mention-sticky: ask the adapter to subscribe the thread so the
336
- // platform's subscribed-message path carries follow-ups without
337
- // requiring another @mention. Threaded-adapter only; DMs and
338
- // non-threaded platforms skip.
339
- if (
340
- !subscribed &&
341
- agent.engage_mode === 'mention-sticky' &&
342
- adapter?.supportsThreads &&
343
- adapter.subscribe &&
344
- event.threadId !== null &&
345
- mg.is_group !== 0
346
- ) {
347
- subscribed = true;
348
- // Fire-and-forget — subscribe is platform-side bookkeeping and
349
- // shouldn't block message routing. Errors are logged inside the
350
- // adapter (or by the promise rejection handler below).
351
- void adapter.subscribe(event.platformId, event.threadId).catch((err) => {
352
- log.warn('adapter.subscribe failed', { channelType: event.channelType, threadId: event.threadId, err });
353
- });
354
- }
355
- } else if (agent.ignored_message_policy === 'accumulate') {
356
- await deliverToAgent(agent, agentGroup, mg, event, userId, adapter?.supportsThreads === true, false);
357
- accumulatedCount++;
358
- } else {
359
- log.debug('Message not engaged for agent (drop policy)', {
360
- agentGroupId: agent.agent_group_id,
361
- engage_mode: agent.engage_mode,
362
- engages,
363
- accessOk,
364
- scopeOk,
365
- });
366
- }
367
- }
368
-
369
- if (engagedCount + accumulatedCount === 0) {
370
- recordDroppedMessage({
371
- channel_type: event.channelType,
372
- platform_id: event.platformId,
373
- user_id: userId,
374
- sender_name: parsed.sender ?? null,
375
- reason: 'no_agent_engaged',
376
- messaging_group_id: mg.id,
377
- agent_group_id: null,
378
- });
379
- }
380
- }
381
-
382
- /**
383
- * Decide whether a given wired agent should engage on this message.
384
- *
385
- * 'pattern' — regex test on text; '.' = always
386
- * 'mention' — bot must be mentioned on the platform. Resolved by
387
- * the adapter (SDK-level) and forwarded as
388
- * `event.message.isMention`. Agent display name
389
- * (`agent_group.name`) is irrelevant — users address
390
- * the bot via its platform username (@botname on
391
- * Telegram, user-id mention on Slack/Discord), not
392
- * via the agent's Paraclaw-side display name. If a
393
- * user wants to disambiguate between multiple agents
394
- * wired to one chat, use engage_mode='pattern' with
395
- * the disambiguator as the regex.
396
- * 'mention-sticky' — platform mention OR an active per-thread session
397
- * already exists for this (agent, mg, thread). The
398
- * session existence IS our subscription state; once
399
- * a thread has engaged us once, follow-ups arrive
400
- * with no mention and should still fire.
401
- */
402
- function evaluateEngage(
403
- agent: MessagingGroupAgent,
404
- text: string,
405
- isMention: boolean,
406
- mg: MessagingGroup,
407
- threadId: string | null,
408
- ): boolean {
409
- switch (agent.engage_mode) {
410
- case 'pattern': {
411
- const pat = agent.engage_pattern ?? '.';
412
- if (pat === '.') return true;
413
- try {
414
- return new RegExp(pat).test(text);
415
- } catch {
416
- // Bad regex: fail open so admin sees the agent responding + can fix.
417
- return true;
418
- }
419
- }
420
- case 'mention':
421
- return isMention;
422
- case 'mention-sticky': {
423
- if (isMention) return true;
424
- // Sticky follow-up: session already exists for this (agent, mg, thread)
425
- // — the thread was activated before, keep firing.
426
- if (mg.is_group === 0) return false; // DMs never use mention-sticky sensibly
427
- const existing = findSessionForAgent(agent.agent_group_id, mg.id, threadId);
428
- return existing !== undefined;
429
- }
430
- default:
431
- return false;
432
- }
433
- }
434
-
435
- async function deliverToAgent(
436
- agent: MessagingGroupAgent,
437
- agentGroup: AgentGroup,
438
- mg: MessagingGroup,
439
- event: InboundEvent,
440
- userId: string | null,
441
- adapterSupportsThreads: boolean,
442
- wake: boolean,
443
- ): Promise<void> {
444
- // Apply the adapter thread policy: threaded adapter in a group chat →
445
- // per-thread session regardless of wiring. agent-shared preserved (it's
446
- // a cross-channel directive the adapter doesn't know about). DMs collapse
447
- // sub-threads to one session (is_group=0 short-circuit).
448
- let effectiveSessionMode = agent.session_mode;
449
- if (adapterSupportsThreads && effectiveSessionMode !== 'agent-shared' && mg.is_group !== 0) {
450
- effectiveSessionMode = 'per-thread';
451
- }
452
-
453
- const { session, created } = resolveSession(agent.agent_group_id, mg.id, event.threadId, effectiveSessionMode);
454
-
455
- // The inbound row's (channel_type, platform_id, thread_id) is the address
456
- // the agent's reply will be delivered to. Normally it mirrors the source
457
- // (stamped from the event). When the caller supplied `replyTo` (CLI admin
458
- // transport acting on operator intent), the reply is redirected there.
459
- const deliveryAddr = event.replyTo ?? {
460
- channelType: event.channelType,
461
- platformId: event.platformId,
462
- threadId: event.threadId,
463
- };
464
-
465
- // Command gate: classify slash commands before they reach the container.
466
- // Filtered commands are dropped silently. Denied admin commands get a
467
- // permission-denied response written directly to messages_out.
468
- if (event.message.kind === 'chat' || event.message.kind === 'chat-sdk') {
469
- const gate = gateCommand(event.message.content, userId, agent.agent_group_id);
470
- if (gate.action === 'filter') {
471
- log.debug('Filtered command dropped by gate', { agentGroupId: agent.agent_group_id });
472
- return;
473
- }
474
- if (gate.action === 'deny') {
475
- writeOutboundDirect(session.agent_group_id, session.id, {
476
- id: `deny-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
477
- kind: 'chat',
478
- platformId: deliveryAddr.platformId,
479
- channelType: deliveryAddr.channelType,
480
- threadId: deliveryAddr.threadId,
481
- content: JSON.stringify({ text: `Permission denied: ${gate.command} requires admin access.` }),
482
- });
483
- log.info('Admin command denied by gate', { command: gate.command, userId, agentGroupId: agent.agent_group_id });
484
- return;
485
- }
486
- }
487
-
488
- writeSessionMessage(session.agent_group_id, session.id, {
489
- id: messageIdForAgent(event.message.id, agent.agent_group_id),
490
- kind: event.message.kind,
491
- timestamp: event.message.timestamp,
492
- platformId: deliveryAddr.platformId,
493
- channelType: deliveryAddr.channelType,
494
- threadId: deliveryAddr.threadId,
495
- content: event.message.content,
496
- trigger: wake ? 1 : 0,
497
- });
498
-
499
- log.info('Message routed', {
500
- sessionId: session.id,
501
- agentGroup: agent.agent_group_id,
502
- engage_mode: agent.engage_mode,
503
- kind: event.message.kind,
504
- userId,
505
- wake,
506
- created,
507
- agentGroupName: agentGroup.name,
508
- });
509
-
510
- if (wake) {
511
- // Typing indicator + wake are only for the engaged branch; accumulated
512
- // messages sit silently until a real trigger fires.
513
- startTypingRefresh(session.id, session.agent_group_id, event.channelType, event.platformId, event.threadId);
514
- const freshSession = getSession(session.id);
515
- if (freshSession) {
516
- await wakeContainer(freshSession);
517
- }
518
- }
519
- }
520
-
521
- /**
522
- * When fanning out, the same inbound message lands in multiple per-agent
523
- * session DBs. messages_in.id is PRIMARY KEY, so reuse of the raw id would
524
- * collide across sessions (or, more subtly, within one session if re-routed
525
- * after a retry). Namespace by agent_group_id to keep ids unique per session.
526
- */
527
- function messageIdForAgent(baseId: string | undefined, agentGroupId: string): string {
528
- const id = baseId && baseId.length > 0 ? baseId : generateId();
529
- return `${id}:${agentGroupId}`;
530
- }
@@ -1,45 +0,0 @@
1
- import crypto from 'crypto';
2
- import { describe, expect, it } from 'vitest';
3
-
4
- import { decryptSecret, encryptSecret } from './crypto.js';
5
-
6
- describe('secret crypto', () => {
7
- const key = crypto.randomBytes(32);
8
-
9
- it('round-trips a plaintext value', () => {
10
- const ct = encryptSecret('xoxb-1234-secret', key);
11
- expect(decryptSecret(ct, key)).toBe('xoxb-1234-secret');
12
- });
13
-
14
- it('produces a different ciphertext each call (random IV)', () => {
15
- const a = encryptSecret('same-value', key);
16
- const b = encryptSecret('same-value', key);
17
- expect(a).not.toBe(b);
18
- expect(decryptSecret(a, key)).toBe('same-value');
19
- expect(decryptSecret(b, key)).toBe('same-value');
20
- });
21
-
22
- it('rejects tampered ciphertext', () => {
23
- const ct = encryptSecret('original', key);
24
- const buf = Buffer.from(ct, 'base64');
25
- buf[buf.length - 1] ^= 0x01;
26
- const tampered = buf.toString('base64');
27
- expect(() => decryptSecret(tampered, key)).toThrow();
28
- });
29
-
30
- it('rejects ciphertext encrypted under a different key', () => {
31
- const otherKey = crypto.randomBytes(32);
32
- const ct = encryptSecret('only-original-key', key);
33
- expect(() => decryptSecret(ct, otherKey)).toThrow();
34
- });
35
-
36
- it('handles empty strings', () => {
37
- const ct = encryptSecret('', key);
38
- expect(decryptSecret(ct, key)).toBe('');
39
- });
40
-
41
- it('handles unicode', () => {
42
- const v = '🔐 résumé — 秘密';
43
- expect(decryptSecret(encryptSecret(v, key), key)).toBe(v);
44
- });
45
- });
@@ -1,55 +0,0 @@
1
- /**
2
- * AES-256-GCM encryption for secret values.
3
- *
4
- * Wire format (base64-encoded):
5
- * 12-byte IV || ciphertext || 16-byte auth tag
6
- *
7
- * Each call generates a fresh random IV — never reuse an IV with the same
8
- * key (catastrophic for GCM). The auth tag is appended so decryption fails
9
- * loudly on tampering.
10
- *
11
- * Domain separation: encryptSecret/decryptSecret accept a 32-byte key. Callers
12
- * MUST NOT pass the raw master key — they pass a per-domain HKDF derivation
13
- * (see `deriveKey` below). That way if a future subsystem (e.g. an outbox
14
- * cookie signer) needs symmetric crypto from the same master, its key is
15
- * cryptographically separated and a bug in one domain can't decrypt the other.
16
- */
17
- import crypto from 'crypto';
18
-
19
- const ALGO = 'aes-256-gcm';
20
- const IV_LEN = 12;
21
- const TAG_LEN = 16;
22
- const KEY_LEN = 32;
23
-
24
- /**
25
- * HKDF-SHA256 with an empty salt and a domain-specific `info` string. The
26
- * empty salt is fine — the master key is already 256 bits of CSPRNG output,
27
- * so HKDF degenerates to HKDF-Expand and the domain-tag in `info` does the
28
- * real work. Use `paraclaw.<subsystem>.v<n>`; bumping `v` is a key rotation
29
- * for that subsystem only.
30
- */
31
- export function deriveKey(masterKey: Buffer, info: string): Buffer {
32
- if (masterKey.length !== KEY_LEN) {
33
- throw new Error(`master key must be ${KEY_LEN} bytes, got ${masterKey.length}`);
34
- }
35
- return Buffer.from(crypto.hkdfSync('sha256', masterKey, Buffer.alloc(0), info, KEY_LEN));
36
- }
37
-
38
- export function encryptSecret(plaintext: string, key: Buffer): string {
39
- const iv = crypto.randomBytes(IV_LEN);
40
- const cipher = crypto.createCipheriv(ALGO, key, iv);
41
- const ct = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
42
- const tag = cipher.getAuthTag();
43
- return Buffer.concat([iv, ct, tag]).toString('base64');
44
- }
45
-
46
- export function decryptSecret(encoded: string, key: Buffer): string {
47
- const buf = Buffer.from(encoded, 'base64');
48
- if (buf.length < IV_LEN + TAG_LEN) throw new Error('ciphertext too short');
49
- const iv = buf.subarray(0, IV_LEN);
50
- const tag = buf.subarray(buf.length - TAG_LEN);
51
- const ct = buf.subarray(IV_LEN, buf.length - TAG_LEN);
52
- const decipher = crypto.createDecipheriv(ALGO, key, iv);
53
- decipher.setAuthTag(tag);
54
- return Buffer.concat([decipher.update(ct), decipher.final()]).toString('utf8');
55
- }