@openparachute/agent 0.1.2 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (608) hide show
  1. package/.parachute/module.json +124 -8
  2. package/LICENSE +2 -16
  3. package/README.md +118 -166
  4. package/package.json +35 -42
  5. package/scripts/spawn-agent.ts +371 -0
  6. package/src/_parked/interactive-spawn.test.ts +324 -0
  7. package/src/_parked/interactive-spawn.ts +701 -0
  8. package/src/agent-defs.test.ts +1504 -0
  9. package/src/agent-defs.ts +1702 -0
  10. package/src/agent-mcp-config.test.ts +115 -0
  11. package/src/agent-mcp-config.ts +115 -0
  12. package/src/agents.test.ts +360 -0
  13. package/src/agents.ts +379 -0
  14. package/src/auth.test.ts +46 -0
  15. package/src/auth.ts +140 -0
  16. package/src/backends/attached-queue.test.ts +376 -0
  17. package/src/backends/attached-queue.ts +372 -0
  18. package/src/backends/programmatic.test.ts +1715 -0
  19. package/src/backends/programmatic.ts +927 -0
  20. package/src/backends/registry.test.ts +1494 -0
  21. package/src/backends/registry.ts +1202 -0
  22. package/src/backends/stream-json.test.ts +570 -0
  23. package/src/backends/stream-json.ts +392 -0
  24. package/src/backends/types.ts +223 -0
  25. package/src/bridge.ts +417 -0
  26. package/src/channel-backend-wiring.test.ts +237 -0
  27. package/src/credentials.test.ts +274 -0
  28. package/src/credentials.ts +380 -0
  29. package/src/cron.test.ts +342 -0
  30. package/src/cron.ts +380 -0
  31. package/src/daemon-agent-def-api.test.ts +166 -0
  32. package/src/daemon-agent-defs-api.test.ts +953 -0
  33. package/src/daemon-agent-env-api.test.ts +338 -0
  34. package/src/daemon-attached-queue-store.test.ts +65 -0
  35. package/src/daemon-config-api.test.ts +962 -0
  36. package/src/daemon-jobs-api.test.ts +271 -0
  37. package/src/daemon-vault-chat.test.ts +250 -0
  38. package/src/daemon.test.ts +746 -0
  39. package/src/daemon.ts +3314 -0
  40. package/src/def-vaults.test.ts +136 -0
  41. package/src/def-vaults.ts +165 -0
  42. package/src/delivery-state.test.ts +110 -0
  43. package/src/delivery-state.ts +154 -0
  44. package/src/effective-env.test.ts +114 -0
  45. package/src/effective-env.ts +184 -0
  46. package/src/env-compat.ts +39 -0
  47. package/src/grants.test.ts +638 -0
  48. package/src/grants.ts +675 -0
  49. package/src/hub-jwt.test.ts +161 -0
  50. package/src/hub-jwt.ts +182 -0
  51. package/src/jobs.test.ts +245 -0
  52. package/src/jobs.ts +266 -0
  53. package/src/mcp-http.test.ts +265 -0
  54. package/src/mcp-http.ts +771 -0
  55. package/src/mint-token.test.ts +152 -0
  56. package/src/mint-token.ts +139 -0
  57. package/src/module-manifest.test.ts +158 -0
  58. package/src/oauth-discovery.ts +134 -0
  59. package/src/programmatic-wiring.test.ts +838 -0
  60. package/src/registry.test.ts +227 -0
  61. package/src/registry.ts +228 -0
  62. package/src/resolve-port.test.ts +64 -0
  63. package/src/routing.test.ts +184 -0
  64. package/src/routing.ts +76 -0
  65. package/src/runner.test.ts +506 -0
  66. package/src/runner.ts +255 -0
  67. package/src/sandbox/config.test.ts +150 -0
  68. package/src/sandbox/config.ts +102 -0
  69. package/src/sandbox/egress.test.ts +113 -0
  70. package/src/sandbox/egress.ts +123 -0
  71. package/src/sandbox/index.ts +180 -0
  72. package/src/sandbox/live-seatbelt.test.ts +277 -0
  73. package/src/sandbox/mounts.test.ts +154 -0
  74. package/src/sandbox/mounts.ts +133 -0
  75. package/src/sandbox/sandbox.test.ts +168 -0
  76. package/src/sandbox/types.ts +382 -0
  77. package/src/services-manifest.test.ts +106 -0
  78. package/src/services-manifest.ts +95 -0
  79. package/src/spa-serve.test.ts +116 -0
  80. package/src/spa-serve.ts +116 -0
  81. package/src/spawn-agent-cli.test.ts +172 -0
  82. package/src/spawn-agent.test.ts +1218 -0
  83. package/src/spawn-agent.ts +569 -0
  84. package/src/spawn-deps.test.ts +54 -0
  85. package/src/spawn-deps.ts +166 -0
  86. package/src/telegram/api.ts +153 -0
  87. package/src/terminal-assets.test.ts +50 -0
  88. package/src/terminal-assets.ts +79 -0
  89. package/src/terminal-ui.ts +305 -0
  90. package/src/terminal.test.ts +530 -0
  91. package/src/terminal.ts +458 -0
  92. package/src/transport.ts +270 -0
  93. package/src/transports/http-ui.test.ts +455 -0
  94. package/src/transports/http-ui.ts +201 -0
  95. package/src/transports/telegram.test.ts +174 -0
  96. package/src/transports/telegram.ts +426 -0
  97. package/src/transports/vault.test.ts +2011 -0
  98. package/src/transports/vault.ts +1790 -0
  99. package/src/ui-kit.test.ts +178 -0
  100. package/src/ui-kit.ts +402 -0
  101. package/tsconfig.json +8 -14
  102. package/web/ui/dist/assets/index-C-iWdFFV.css +1 -0
  103. package/web/ui/dist/assets/index-VFETBk0a.js +60 -0
  104. package/web/ui/dist/index.html +15 -0
  105. package/web/ui/tsconfig.json +2 -1
  106. package/.claude/scheduled_tasks.lock +0 -1
  107. package/.claude/settings.json +0 -5
  108. package/.claude/skills/add-atomic-chat-tool/SKILL.md +0 -243
  109. package/.claude/skills/add-atomic-chat-tool/atomic-chat-mcp-stdio.ts +0 -229
  110. package/.claude/skills/add-codex/SKILL.md +0 -161
  111. package/.claude/skills/add-dashboard/SKILL.md +0 -138
  112. package/.claude/skills/add-dashboard/resources/dashboard-pusher.ts +0 -495
  113. package/.claude/skills/add-emacs/SKILL.md +0 -296
  114. package/.claude/skills/add-gcal-tool/SKILL.md +0 -210
  115. package/.claude/skills/add-gchat/REMOVE.md +0 -6
  116. package/.claude/skills/add-gchat/SKILL.md +0 -92
  117. package/.claude/skills/add-gchat/VERIFY.md +0 -3
  118. package/.claude/skills/add-github/REMOVE.md +0 -6
  119. package/.claude/skills/add-github/SKILL.md +0 -148
  120. package/.claude/skills/add-github/VERIFY.md +0 -3
  121. package/.claude/skills/add-gmail-tool/SKILL.md +0 -229
  122. package/.claude/skills/add-imessage/REMOVE.md +0 -6
  123. package/.claude/skills/add-imessage/SKILL.md +0 -113
  124. package/.claude/skills/add-imessage/VERIFY.md +0 -3
  125. package/.claude/skills/add-karpathy-llm-wiki/SKILL.md +0 -110
  126. package/.claude/skills/add-karpathy-llm-wiki/llm-wiki.md +0 -75
  127. package/.claude/skills/add-linear/REMOVE.md +0 -6
  128. package/.claude/skills/add-linear/SKILL.md +0 -168
  129. package/.claude/skills/add-linear/VERIFY.md +0 -3
  130. package/.claude/skills/add-macos-statusbar/SKILL.md +0 -133
  131. package/.claude/skills/add-macos-statusbar/add/src/statusbar.swift +0 -147
  132. package/.claude/skills/add-matrix/REMOVE.md +0 -6
  133. package/.claude/skills/add-matrix/SKILL.md +0 -148
  134. package/.claude/skills/add-matrix/VERIFY.md +0 -3
  135. package/.claude/skills/add-ollama-provider/SKILL.md +0 -179
  136. package/.claude/skills/add-ollama-tool/SKILL.md +0 -193
  137. package/.claude/skills/add-opencode/SKILL.md +0 -229
  138. package/.claude/skills/add-parallel/SKILL.md +0 -290
  139. package/.claude/skills/add-resend/REMOVE.md +0 -6
  140. package/.claude/skills/add-resend/SKILL.md +0 -93
  141. package/.claude/skills/add-resend/VERIFY.md +0 -3
  142. package/.claude/skills/add-signal/REMOVE.md +0 -13
  143. package/.claude/skills/add-signal/SKILL.md +0 -318
  144. package/.claude/skills/add-signal/VERIFY.md +0 -5
  145. package/.claude/skills/add-slack/REMOVE.md +0 -6
  146. package/.claude/skills/add-slack/SKILL.md +0 -112
  147. package/.claude/skills/add-slack/VERIFY.md +0 -3
  148. package/.claude/skills/add-teams/REMOVE.md +0 -6
  149. package/.claude/skills/add-teams/SKILL.md +0 -207
  150. package/.claude/skills/add-teams/VERIFY.md +0 -3
  151. package/.claude/skills/add-vercel/SKILL.md +0 -147
  152. package/.claude/skills/add-vercel/container-skills/vercel-cli/SKILL.md +0 -103
  153. package/.claude/skills/add-webex/REMOVE.md +0 -6
  154. package/.claude/skills/add-webex/SKILL.md +0 -88
  155. package/.claude/skills/add-webex/VERIFY.md +0 -3
  156. package/.claude/skills/add-wechat/REMOVE.md +0 -49
  157. package/.claude/skills/add-wechat/SKILL.md +0 -170
  158. package/.claude/skills/add-wechat/scripts/wire-dm.ts +0 -172
  159. package/.claude/skills/add-whatsapp/SKILL.md +0 -264
  160. package/.claude/skills/add-whatsapp-cloud/REMOVE.md +0 -6
  161. package/.claude/skills/add-whatsapp-cloud/SKILL.md +0 -95
  162. package/.claude/skills/add-whatsapp-cloud/VERIFY.md +0 -3
  163. package/.claude/skills/claw/SKILL.md +0 -131
  164. package/.claude/skills/claw/scripts/claw +0 -374
  165. package/.claude/skills/convert-to-apple-container/SKILL.md +0 -212
  166. package/.claude/skills/customize/SKILL.md +0 -110
  167. package/.claude/skills/debug/SKILL.md +0 -349
  168. package/.claude/skills/get-qodo-rules/SKILL.md +0 -122
  169. package/.claude/skills/get-qodo-rules/references/output-format.md +0 -41
  170. package/.claude/skills/get-qodo-rules/references/pagination.md +0 -33
  171. package/.claude/skills/get-qodo-rules/references/repository-scope.md +0 -26
  172. package/.claude/skills/init-first-agent/SKILL.md +0 -120
  173. package/.claude/skills/init-onecli/SKILL.md +0 -270
  174. package/.claude/skills/manage-channels/SKILL.md +0 -87
  175. package/.claude/skills/manage-mounts/SKILL.md +0 -47
  176. package/.claude/skills/migrate-from-openclaw/MIGRATE_CRONS.md +0 -100
  177. package/.claude/skills/migrate-from-openclaw/SKILL.md +0 -447
  178. package/.claude/skills/migrate-from-openclaw/scripts/discover-openclaw.ts +0 -734
  179. package/.claude/skills/migrate-from-openclaw/scripts/extract-channel-credentials.ts +0 -476
  180. package/.claude/skills/migrate-nanoclaw/SKILL.md +0 -484
  181. package/.claude/skills/migrate-nanoclaw/diagnostics.md +0 -51
  182. package/.claude/skills/qodo-pr-resolver/SKILL.md +0 -326
  183. package/.claude/skills/qodo-pr-resolver/resources/providers.md +0 -329
  184. package/.claude/skills/update-nanoclaw/SKILL.md +0 -243
  185. package/.claude/skills/update-nanoclaw/diagnostics.md +0 -48
  186. package/.claude/skills/update-skills/SKILL.md +0 -130
  187. package/.claude/skills/use-native-credential-proxy/SKILL.md +0 -167
  188. package/.claude/skills/x-integration/SKILL.md +0 -417
  189. package/.claude/skills/x-integration/agent.ts +0 -243
  190. package/.claude/skills/x-integration/host.ts +0 -155
  191. package/.claude/skills/x-integration/lib/browser.ts +0 -148
  192. package/.claude/skills/x-integration/lib/config.ts +0 -62
  193. package/.claude/skills/x-integration/scripts/like.ts +0 -56
  194. package/.claude/skills/x-integration/scripts/post.ts +0 -66
  195. package/.claude/skills/x-integration/scripts/quote.ts +0 -80
  196. package/.claude/skills/x-integration/scripts/reply.ts +0 -74
  197. package/.claude/skills/x-integration/scripts/retweet.ts +0 -62
  198. package/.claude/skills/x-integration/scripts/setup.ts +0 -87
  199. package/.github/CODEOWNERS +0 -10
  200. package/.github/PULL_REQUEST_TEMPLATE.md +0 -18
  201. package/.github/workflows/bump-version.yml +0 -35
  202. package/.github/workflows/ci.yml +0 -39
  203. package/.github/workflows/label-pr.yml +0 -40
  204. package/.github/workflows/update-tokens.yml +0 -43
  205. package/.husky/pre-commit +0 -1
  206. package/.mcp.json +0 -3
  207. package/.nvmrc +0 -1
  208. package/.prettierrc +0 -4
  209. package/CHANGELOG.md +0 -263
  210. package/CLAUDE.md +0 -307
  211. package/CODE_OF_CONDUCT.md +0 -128
  212. package/CONTRIBUTING.md +0 -159
  213. package/CONTRIBUTORS.md +0 -26
  214. package/LICENSE-NANOCLAW-MIT +0 -21
  215. package/README_ja.md +0 -194
  216. package/README_zh.md +0 -194
  217. package/assets/nanoclaw-favicon.png +0 -0
  218. package/assets/nanoclaw-icon.png +0 -0
  219. package/assets/nanoclaw-logo-dark.png +0 -0
  220. package/assets/nanoclaw-logo.png +0 -0
  221. package/assets/nanoclaw-profile.jpeg +0 -0
  222. package/assets/nanoclaw-sales.png +0 -0
  223. package/assets/social-preview.jpg +0 -0
  224. package/config-examples/mount-allowlist.json +0 -25
  225. package/container/.dockerignore +0 -2
  226. package/container/CLAUDE.md +0 -21
  227. package/container/Dockerfile +0 -121
  228. package/container/agent-runner/bun.lock +0 -243
  229. package/container/agent-runner/package.json +0 -22
  230. package/container/agent-runner/scripts/sdk-signal-probe.ts +0 -169
  231. package/container/agent-runner/src/config.ts +0 -55
  232. package/container/agent-runner/src/db/connection.ts +0 -267
  233. package/container/agent-runner/src/db/index.ts +0 -20
  234. package/container/agent-runner/src/db/messages-in.ts +0 -138
  235. package/container/agent-runner/src/db/messages-out.ts +0 -143
  236. package/container/agent-runner/src/db/session-routing.ts +0 -30
  237. package/container/agent-runner/src/db/session-state.test.ts +0 -100
  238. package/container/agent-runner/src/db/session-state.ts +0 -79
  239. package/container/agent-runner/src/destinations.ts +0 -135
  240. package/container/agent-runner/src/formatter.test.ts +0 -167
  241. package/container/agent-runner/src/formatter.ts +0 -260
  242. package/container/agent-runner/src/index.ts +0 -110
  243. package/container/agent-runner/src/integration.test.ts +0 -121
  244. package/container/agent-runner/src/mcp-tools/agents.instructions.md +0 -26
  245. package/container/agent-runner/src/mcp-tools/agents.ts +0 -66
  246. package/container/agent-runner/src/mcp-tools/core.instructions.md +0 -27
  247. package/container/agent-runner/src/mcp-tools/core.ts +0 -262
  248. package/container/agent-runner/src/mcp-tools/index.ts +0 -22
  249. package/container/agent-runner/src/mcp-tools/interactive.instructions.md +0 -22
  250. package/container/agent-runner/src/mcp-tools/interactive.ts +0 -169
  251. package/container/agent-runner/src/mcp-tools/scheduling.instructions.md +0 -40
  252. package/container/agent-runner/src/mcp-tools/scheduling.ts +0 -299
  253. package/container/agent-runner/src/mcp-tools/self-mod.instructions.md +0 -25
  254. package/container/agent-runner/src/mcp-tools/self-mod.ts +0 -120
  255. package/container/agent-runner/src/mcp-tools/server.ts +0 -54
  256. package/container/agent-runner/src/mcp-tools/types.ts +0 -6
  257. package/container/agent-runner/src/poll-loop.test.ts +0 -248
  258. package/container/agent-runner/src/poll-loop.ts +0 -437
  259. package/container/agent-runner/src/providers/claude.ts +0 -379
  260. package/container/agent-runner/src/providers/factory.test.ts +0 -19
  261. package/container/agent-runner/src/providers/factory.ts +0 -13
  262. package/container/agent-runner/src/providers/index.ts +0 -6
  263. package/container/agent-runner/src/providers/mock.ts +0 -77
  264. package/container/agent-runner/src/providers/provider-registry.ts +0 -33
  265. package/container/agent-runner/src/providers/types.ts +0 -82
  266. package/container/agent-runner/src/scheduling/task-script.ts +0 -121
  267. package/container/agent-runner/src/timezone.test.ts +0 -93
  268. package/container/agent-runner/src/timezone.ts +0 -107
  269. package/container/agent-runner/tsconfig.json +0 -14
  270. package/container/build.sh +0 -48
  271. package/container/entrypoint.sh +0 -16
  272. package/container/skills/agent-browser/SKILL.md +0 -159
  273. package/container/skills/frontend-engineer/SKILL.md +0 -157
  274. package/container/skills/self-customize/SKILL.md +0 -87
  275. package/container/skills/slack-formatting/SKILL.md +0 -94
  276. package/container/skills/vercel-cli/SKILL.md +0 -111
  277. package/container/skills/welcome/SKILL.md +0 -85
  278. package/docs/APPLE-CONTAINER-NETWORKING.md +0 -90
  279. package/docs/BRANCH-FORK-MAINTENANCE.md +0 -81
  280. package/docs/README.md +0 -25
  281. package/docs/SDK_DEEP_DIVE.md +0 -643
  282. package/docs/SECURITY.md +0 -162
  283. package/docs/agent-runner-details.md +0 -749
  284. package/docs/api-details.md +0 -365
  285. package/docs/architecture-diagram.html +0 -422
  286. package/docs/architecture-diagram.md +0 -215
  287. package/docs/architecture.md +0 -751
  288. package/docs/audit/2026-04-30-channel-endpoint-audit.md +0 -36
  289. package/docs/build-and-runtime.md +0 -80
  290. package/docs/cross-mount-stress/README.md +0 -112
  291. package/docs/cross-mount-stress/container-writer-retry.mjs +0 -55
  292. package/docs/cross-mount-stress/container-writer-slow.mjs +0 -42
  293. package/docs/cross-mount-stress/container-writer.mjs +0 -47
  294. package/docs/cross-mount-stress/host-writer-retry.mjs +0 -55
  295. package/docs/cross-mount-stress/host-writer-slow.mjs +0 -43
  296. package/docs/cross-mount-stress/host-writer.mjs +0 -47
  297. package/docs/db-central.md +0 -316
  298. package/docs/db-session.md +0 -183
  299. package/docs/db.md +0 -119
  300. package/docs/design/2026-04-29-vault-management-ui.md +0 -231
  301. package/docs/design/2026-04-30-channel-wiring-rework.md +0 -234
  302. package/docs/design/2026-05-01-channel-wiring-approvals-deep-dive.md +0 -272
  303. package/docs/design/2026-05-02-channel-policy-and-approval-routing.md +0 -250
  304. package/docs/docker-sandboxes.md +0 -359
  305. package/docs/isolation-model.md +0 -88
  306. package/docs/ollama.md +0 -79
  307. package/docs/parachute-integration.md +0 -109
  308. package/docs/post-night-rebirth-reflections.md +0 -151
  309. package/eslint.config.js +0 -32
  310. package/pnpm-workspace.yaml +0 -8
  311. package/repo-tokens/README.md +0 -113
  312. package/repo-tokens/action.yml +0 -186
  313. package/repo-tokens/badge.svg +0 -23
  314. package/repo-tokens/examples/green.svg +0 -14
  315. package/repo-tokens/examples/red.svg +0 -14
  316. package/repo-tokens/examples/yellow-green.svg +0 -14
  317. package/repo-tokens/examples/yellow.svg +0 -14
  318. package/scripts/chat.ts +0 -101
  319. package/scripts/cleanup-sessions.sh +0 -150
  320. package/scripts/init-cli-agent.ts +0 -172
  321. package/scripts/init-first-agent.ts +0 -378
  322. package/scripts/parachute.ts +0 -158
  323. package/scripts/run-migrations.ts +0 -105
  324. package/scripts/sanity-live-poll.ts +0 -95
  325. package/scripts/seed-discord.ts +0 -80
  326. package/scripts/test-v2-agent.ts +0 -106
  327. package/scripts/test-v2-channel-e2e.ts +0 -265
  328. package/scripts/test-v2-host.ts +0 -184
  329. package/src/channels/adapter.ts +0 -214
  330. package/src/channels/api-translator.test.ts +0 -306
  331. package/src/channels/api-translator.ts +0 -214
  332. package/src/channels/ask-question.ts +0 -46
  333. package/src/channels/channel-registry.test.ts +0 -421
  334. package/src/channels/channel-registry.ts +0 -313
  335. package/src/channels/chat-sdk-bridge.test.ts +0 -84
  336. package/src/channels/chat-sdk-bridge.ts +0 -652
  337. package/src/channels/cli.ts +0 -276
  338. package/src/channels/discord.ts +0 -90
  339. package/src/channels/index.ts +0 -17
  340. package/src/channels/telegram-markdown-sanitize.test.ts +0 -78
  341. package/src/channels/telegram-markdown-sanitize.ts +0 -55
  342. package/src/channels/telegram-pairing.test.ts +0 -254
  343. package/src/channels/telegram-pairing.ts +0 -339
  344. package/src/channels/telegram.ts +0 -279
  345. package/src/channels/trust-hint.test.ts +0 -48
  346. package/src/channels/trust-hint.ts +0 -75
  347. package/src/claude-md-compose.migrate.test.ts +0 -64
  348. package/src/claude-md-compose.ts +0 -205
  349. package/src/command-gate.ts +0 -63
  350. package/src/config.test.ts +0 -93
  351. package/src/config.ts +0 -128
  352. package/src/container-config.ts +0 -167
  353. package/src/container-runner.test.ts +0 -32
  354. package/src/container-runner.ts +0 -576
  355. package/src/container-runtime.test.ts +0 -269
  356. package/src/container-runtime.ts +0 -167
  357. package/src/db/_bun-sqlite-shim.ts +0 -88
  358. package/src/db/agent-activity.test.ts +0 -155
  359. package/src/db/agent-activity.ts +0 -121
  360. package/src/db/agent-groups.ts +0 -77
  361. package/src/db/connection.migrate.test.ts +0 -176
  362. package/src/db/connection.ts +0 -259
  363. package/src/db/db-v2.test.ts +0 -440
  364. package/src/db/dropped-messages.ts +0 -44
  365. package/src/db/index.ts +0 -40
  366. package/src/db/messaging-groups.ts +0 -252
  367. package/src/db/migrations/001-initial.ts +0 -112
  368. package/src/db/migrations/002-chat-sdk-state.ts +0 -36
  369. package/src/db/migrations/008-dropped-messages.ts +0 -27
  370. package/src/db/migrations/009-drop-pending-credentials.ts +0 -13
  371. package/src/db/migrations/010-engage-modes.ts +0 -103
  372. package/src/db/migrations/011-pending-sender-approvals.ts +0 -40
  373. package/src/db/migrations/012-channel-registration.ts +0 -48
  374. package/src/db/migrations/013-approval-render-metadata.ts +0 -27
  375. package/src/db/migrations/014-secrets.ts +0 -44
  376. package/src/db/migrations/015-secrets-drop-host-pattern.ts +0 -18
  377. package/src/db/migrations/016-secret-assignments.ts +0 -30
  378. package/src/db/migrations/017-agent-activity.ts +0 -40
  379. package/src/db/migrations/018-oauth-app-configs.ts +0 -34
  380. package/src/db/migrations/019-oauth-app-connections.ts +0 -48
  381. package/src/db/migrations/020-agent-app-connections.ts +0 -28
  382. package/src/db/migrations/021-pending-oauth-states.ts +0 -35
  383. package/src/db/migrations/022-app-connections-provider.ts +0 -25
  384. package/src/db/migrations/023-agent-group-secret-mode.test.ts +0 -124
  385. package/src/db/migrations/023-agent-group-secret-mode.ts +0 -65
  386. package/src/db/migrations/024-collapse-approvals.test.ts +0 -249
  387. package/src/db/migrations/024-collapse-approvals.ts +0 -182
  388. package/src/db/migrations/025-secret-mode-check.test.ts +0 -155
  389. package/src/db/migrations/025-secret-mode-check.ts +0 -49
  390. package/src/db/migrations/026-user-dms-bot-id.test.ts +0 -116
  391. package/src/db/migrations/026-user-dms-bot-id.ts +0 -54
  392. package/src/db/migrations/027-provider-credentials.ts +0 -41
  393. package/src/db/migrations/_test-helpers.ts +0 -41
  394. package/src/db/migrations/index.ts +0 -127
  395. package/src/db/migrations/module-agent-to-agent-destinations.ts +0 -84
  396. package/src/db/migrations/module-approvals-pending-approvals.ts +0 -42
  397. package/src/db/migrations/module-approvals-title-options.ts +0 -40
  398. package/src/db/schema.ts +0 -258
  399. package/src/db/session-db.test.ts +0 -93
  400. package/src/db/session-db.ts +0 -325
  401. package/src/db/sessions.ts +0 -241
  402. package/src/delivery.test.ts +0 -148
  403. package/src/delivery.ts +0 -445
  404. package/src/env.ts +0 -74
  405. package/src/group-folder.test.ts +0 -35
  406. package/src/group-folder.ts +0 -44
  407. package/src/group-init.ts +0 -92
  408. package/src/host-core.test.ts +0 -456
  409. package/src/host-sweep.test.ts +0 -146
  410. package/src/host-sweep.ts +0 -287
  411. package/src/index.ts +0 -232
  412. package/src/install-slug.ts +0 -33
  413. package/src/log.test.ts +0 -81
  414. package/src/log.ts +0 -117
  415. package/src/mcp/http.ts +0 -72
  416. package/src/mcp/server.ts +0 -92
  417. package/src/mcp/stdio.ts +0 -51
  418. package/src/mcp/tools/activity.ts +0 -88
  419. package/src/mcp/tools/agent-groups.ts +0 -183
  420. package/src/mcp/tools/approvals.ts +0 -122
  421. package/src/mcp/tools/channels.test.ts +0 -126
  422. package/src/mcp/tools/channels.ts +0 -134
  423. package/src/mcp/tools/index.ts +0 -27
  424. package/src/mcp/tools/oauth.ts +0 -48
  425. package/src/mcp/tools/secrets.ts +0 -169
  426. package/src/mcp/tools/sessions.ts +0 -135
  427. package/src/mcp/types.ts +0 -51
  428. package/src/modules/agent-to-agent/agent-route.test.ts +0 -46
  429. package/src/modules/agent-to-agent/agent-route.ts +0 -223
  430. package/src/modules/agent-to-agent/create-agent.ts +0 -127
  431. package/src/modules/agent-to-agent/db/agent-destinations.ts +0 -135
  432. package/src/modules/agent-to-agent/index.ts +0 -22
  433. package/src/modules/agent-to-agent/write-destinations.ts +0 -59
  434. package/src/modules/approvals/agent.md +0 -45
  435. package/src/modules/approvals/index.ts +0 -21
  436. package/src/modules/approvals/picks.test.ts +0 -291
  437. package/src/modules/approvals/primitive.ts +0 -279
  438. package/src/modules/approvals/project.md +0 -27
  439. package/src/modules/approvals/response-handler.ts +0 -87
  440. package/src/modules/index.ts +0 -24
  441. package/src/modules/interactive/agent.md +0 -21
  442. package/src/modules/interactive/index.ts +0 -69
  443. package/src/modules/interactive/project.md +0 -12
  444. package/src/modules/mount-security/expand-path.test.ts +0 -82
  445. package/src/modules/mount-security/index.ts +0 -459
  446. package/src/modules/mount-security/migrate.test.ts +0 -91
  447. package/src/modules/permissions/access.ts +0 -28
  448. package/src/modules/permissions/channel-approval.test.ts +0 -389
  449. package/src/modules/permissions/channel-approval.ts +0 -188
  450. package/src/modules/permissions/db/agent-group-members.ts +0 -44
  451. package/src/modules/permissions/db/pending-channel-approvals.test.ts +0 -86
  452. package/src/modules/permissions/db/pending-channel-approvals.ts +0 -66
  453. package/src/modules/permissions/db/pending-sender-approvals.ts +0 -60
  454. package/src/modules/permissions/db/user-dms.ts +0 -58
  455. package/src/modules/permissions/db/user-roles.ts +0 -85
  456. package/src/modules/permissions/db/users.ts +0 -38
  457. package/src/modules/permissions/index.ts +0 -421
  458. package/src/modules/permissions/permissions.test.ts +0 -358
  459. package/src/modules/permissions/sender-approval.test.ts +0 -641
  460. package/src/modules/permissions/sender-approval.ts +0 -165
  461. package/src/modules/permissions/user-dm.ts +0 -200
  462. package/src/modules/provider-credentials/db.ts +0 -121
  463. package/src/modules/provider-credentials/index.ts +0 -12
  464. package/src/modules/provider-credentials/spawn.test.ts +0 -206
  465. package/src/modules/provider-credentials/spawn.ts +0 -114
  466. package/src/modules/scheduling/actions.ts +0 -113
  467. package/src/modules/scheduling/db.test.ts +0 -282
  468. package/src/modules/scheduling/db.ts +0 -148
  469. package/src/modules/scheduling/index.ts +0 -34
  470. package/src/modules/scheduling/recurrence.test.ts +0 -98
  471. package/src/modules/scheduling/recurrence.ts +0 -54
  472. package/src/modules/self-mod/agent.md +0 -30
  473. package/src/modules/self-mod/apply.ts +0 -85
  474. package/src/modules/self-mod/index.ts +0 -30
  475. package/src/modules/self-mod/project.md +0 -39
  476. package/src/modules/self-mod/request.ts +0 -91
  477. package/src/modules/typing/index.ts +0 -165
  478. package/src/oauth/agent-app-connections.ts +0 -103
  479. package/src/oauth/app-configs.test.ts +0 -64
  480. package/src/oauth/app-configs.ts +0 -114
  481. package/src/oauth/app-connections.test.ts +0 -109
  482. package/src/oauth/app-connections.ts +0 -178
  483. package/src/oauth/crypto.ts +0 -56
  484. package/src/oauth/flow.ts +0 -104
  485. package/src/oauth/providers/google.test.ts +0 -38
  486. package/src/oauth/providers/google.ts +0 -46
  487. package/src/oauth/providers/index.ts +0 -48
  488. package/src/oauth/state-store.test.ts +0 -54
  489. package/src/oauth/state-store.ts +0 -93
  490. package/src/parachute/README.md +0 -27
  491. package/src/parachute/create-agent.test.ts +0 -83
  492. package/src/parachute/create-agent.ts +0 -122
  493. package/src/parachute/group-status.test.ts +0 -165
  494. package/src/parachute/group-status.ts +0 -136
  495. package/src/parachute/types.ts +0 -41
  496. package/src/parachute/vault-mcp.test.ts +0 -251
  497. package/src/parachute/vault-mcp.ts +0 -232
  498. package/src/platform-id.test.ts +0 -104
  499. package/src/platform-id.ts +0 -109
  500. package/src/providers/index.ts +0 -6
  501. package/src/providers/provider-container-registry.ts +0 -58
  502. package/src/response-registry.ts +0 -45
  503. package/src/router.ts +0 -530
  504. package/src/secrets/crypto.test.ts +0 -45
  505. package/src/secrets/crypto.ts +0 -55
  506. package/src/secrets/index.ts +0 -461
  507. package/src/secrets/master-key.ts +0 -70
  508. package/src/secrets/secrets.test.ts +0 -651
  509. package/src/session-manager.attachments.test.ts +0 -171
  510. package/src/session-manager.dup-skip.test.ts +0 -173
  511. package/src/session-manager.migrate.test.ts +0 -59
  512. package/src/session-manager.ts +0 -451
  513. package/src/startup-bootstrap.test.ts +0 -226
  514. package/src/startup-bootstrap.ts +0 -207
  515. package/src/state-sqlite.ts +0 -182
  516. package/src/timezone.test.ts +0 -64
  517. package/src/timezone.ts +0 -37
  518. package/src/types.ts +0 -233
  519. package/src/web/auth.test.ts +0 -335
  520. package/src/web/auth.ts +0 -214
  521. package/src/web/discord-validate.test.ts +0 -77
  522. package/src/web/discord-validate.ts +0 -88
  523. package/src/web/hub-discovery.test.ts +0 -98
  524. package/src/web/hub-discovery.ts +0 -69
  525. package/src/web/routes/activity.ts +0 -106
  526. package/src/web/routes/agent-provider.test.ts +0 -282
  527. package/src/web/routes/agent-provider.ts +0 -309
  528. package/src/web/routes/approvals.ts +0 -185
  529. package/src/web/routes/apps.ts +0 -434
  530. package/src/web/routes/channels-mg-detail.test.ts +0 -324
  531. package/src/web/routes/channels-mga-detail.test.ts +0 -472
  532. package/src/web/routes/channels.ts +0 -311
  533. package/src/web/routes/oauth-providers.ts +0 -42
  534. package/src/web/routes/secrets.test.ts +0 -220
  535. package/src/web/routes/secrets.ts +0 -317
  536. package/src/web/routes/sessions.ts +0 -123
  537. package/src/web/routes/settings.test.ts +0 -106
  538. package/src/web/routes/settings.ts +0 -247
  539. package/src/web/routes/setup-status.ts +0 -205
  540. package/src/web/routes/vaults.test.ts +0 -389
  541. package/src/web/routes/vaults.ts +0 -225
  542. package/src/web/server-version.test.ts +0 -16
  543. package/src/web/server.ts +0 -1024
  544. package/src/web/services-manifest.test.ts +0 -148
  545. package/src/web/services-manifest.ts +0 -66
  546. package/src/web/static-serve.test.ts +0 -255
  547. package/src/web/static-serve.ts +0 -104
  548. package/src/web/telegram-validate.test.ts +0 -116
  549. package/src/web/telegram-validate.ts +0 -107
  550. package/src/web/vault-proxy.test.ts +0 -214
  551. package/src/web/vault-proxy.ts +0 -120
  552. package/src/web/wire-channel.ts +0 -181
  553. package/src/webhook-server.ts +0 -134
  554. package/vitest.config.ts +0 -18
  555. package/web/README.md +0 -63
  556. package/web/ui/index.html +0 -13
  557. package/web/ui/package.json +0 -35
  558. package/web/ui/pnpm-lock.yaml +0 -2164
  559. package/web/ui/scripts/verify-base.mjs +0 -31
  560. package/web/ui/src/App.tsx +0 -88
  561. package/web/ui/src/components/ActivityFeed.tsx +0 -444
  562. package/web/ui/src/components/AgentGroupPicker.tsx +0 -263
  563. package/web/ui/src/components/AgentProviderCards.tsx +0 -220
  564. package/web/ui/src/components/CredentialForm.tsx +0 -214
  565. package/web/ui/src/components/ScopeGrants.tsx +0 -74
  566. package/web/ui/src/components/StatusDot.tsx +0 -43
  567. package/web/ui/src/components/VaultPicker.tsx +0 -127
  568. package/web/ui/src/components/setup/AdapterInstallStep.tsx +0 -178
  569. package/web/ui/src/components/setup/AgentGroupStep.tsx +0 -43
  570. package/web/ui/src/components/setup/ChannelPickStep.tsx +0 -74
  571. package/web/ui/src/components/setup/DoneStep.tsx +0 -49
  572. package/web/ui/src/components/setup/PrereqStep.tsx +0 -129
  573. package/web/ui/src/components/setup/TestConnectionStep.tsx +0 -108
  574. package/web/ui/src/components/setup/TestMessageStep.tsx +0 -104
  575. package/web/ui/src/components/setup/WireChannelStep.tsx +0 -166
  576. package/web/ui/src/components/setup/types.ts +0 -105
  577. package/web/ui/src/lib/api.test.ts +0 -410
  578. package/web/ui/src/lib/api.ts +0 -1248
  579. package/web/ui/src/lib/auth.test.ts +0 -352
  580. package/web/ui/src/lib/auth.ts +0 -405
  581. package/web/ui/src/lib/channel-adapters.ts +0 -136
  582. package/web/ui/src/main.tsx +0 -19
  583. package/web/ui/src/routes/ApprovalsList.tsx +0 -294
  584. package/web/ui/src/routes/Apps.tsx +0 -613
  585. package/web/ui/src/routes/ChannelWireDetail.test.tsx +0 -233
  586. package/web/ui/src/routes/ChannelWireDetail.tsx +0 -403
  587. package/web/ui/src/routes/ChannelsList.tsx +0 -158
  588. package/web/ui/src/routes/GroupDetail.test.tsx +0 -206
  589. package/web/ui/src/routes/GroupDetail.tsx +0 -880
  590. package/web/ui/src/routes/GroupList.tsx +0 -187
  591. package/web/ui/src/routes/MessagingGroupDetail.test.tsx +0 -233
  592. package/web/ui/src/routes/MessagingGroupDetail.tsx +0 -306
  593. package/web/ui/src/routes/NewGroupWizard.tsx +0 -390
  594. package/web/ui/src/routes/OAuthCallback.tsx +0 -56
  595. package/web/ui/src/routes/SecretsList.tsx +0 -942
  596. package/web/ui/src/routes/SessionsList.tsx +0 -220
  597. package/web/ui/src/routes/SettingsAgentProvider.tsx +0 -109
  598. package/web/ui/src/routes/SettingsApprovals.tsx +0 -234
  599. package/web/ui/src/routes/SetupWizard.tsx +0 -219
  600. package/web/ui/src/routes/VaultDetail.test.tsx +0 -363
  601. package/web/ui/src/routes/VaultDetail.tsx +0 -960
  602. package/web/ui/src/routes/VaultsList.tsx +0 -295
  603. package/web/ui/src/routes/WireChannelPage.tsx +0 -413
  604. package/web/ui/src/styles.css +0 -608
  605. package/web/ui/src/test/setup.ts +0 -23
  606. package/web/ui/src/vite-env.d.ts +0 -10
  607. package/web/ui/vite.config.ts +0 -34
  608. package/web/ui/vitest.config.ts +0 -25
@@ -0,0 +1,372 @@
1
+ /**
2
+ * The ATTACHED-backend queue registry (design 2026-06-18-channel-backend.md, phase 1).
3
+ * (The backend VALUE was named `"channel"` before the rename; the ROUTING KEY `channel`
4
+ * — the agent's address / the `/mcp/<channel>` segment — is a separate concept and KEEPS
5
+ * its name, so `channel` still appears throughout as the routing-key identifier.)
6
+ *
7
+ * The PARALLEL to {@link ProgrammaticAgentRegistry}, NOT a reuse of it. A
8
+ * `backend: "attached"` agent runs NO `claude -p` and has NO drain worker: the turn
9
+ * is handled by a Claude Code session the OPERATOR runs and connects ("attaches") to the
10
+ * channel's MCP endpoint. The inbound `#agent/message/inbound` notes themselves ARE the
11
+ * queue (the vault is the queue + the source of truth), and their claim `status`
12
+ * (`pending | in-flight | handled`) lives on the note, so a claim survives a daemon
13
+ * restart and a handled message is never re-presented.
14
+ *
15
+ * ── Why a separate registry (the daemon routing fork) ────────────────────────────
16
+ * The programmatic registry's drain worker reads `deliver()`'s `reply` synchronously
17
+ * and OWNS the outbound write. An attached agent has no synchronous turn and its
18
+ * outbound is written by the MCP `reply` tool — reusing that worker would double-write
19
+ * (worker + tool) or drop the reply (worker sees an empty `deliver`). So the fork is
20
+ * at the daemon ROUTER: inbound for an `attached` agent routes HERE and is NOT enqueued
21
+ * to the programmatic worker. This registry exposes only queue operations the MCP
22
+ * surface calls — there is no in-process `deliver`-produces-reply.
23
+ *
24
+ * ── The queue operations (called by the channel MCP surface, phase 2) ────────────
25
+ * - `pending(channel)` → count + a peek (ids/previews) of `status:pending` inbound.
26
+ * - `claimNext(channel)` → oldest `pending` → set `in-flight` + `claimedAt`; returns
27
+ * { id, text, inReplyTo, systemPrompt }. Single-claim (two
28
+ * sessions don't double-handle). null when none pending.
29
+ * - `reply(channel, …)` → write the outbound note via the SAME vault-transport
30
+ * `reply()` the programmatic worker uses (durable, threads,
31
+ * shows in chat UI, tagged outbound so it can't re-trigger
32
+ * the inbound webhook), THEN set the inbound `handled`.
33
+ * - `release(channel,id)`→ `in-flight` → `pending` (the session is giving up).
34
+ * - `sweepExpired(now)` → `in-flight` notes claimed > TTL ago → `pending` (so a
35
+ * crashed session can't strand the queue). Wired into the
36
+ * daemon's periodic tick.
37
+ *
38
+ * CARDINALITY: one channel : one agent (the channel IS the agent's conduit). The
39
+ * surface deliberately doesn't bake "channel == agent" so deep that adding an optional
40
+ * agent filter to `claimNext` later would be a breaking change — the operations key on
41
+ * the channel name only, and the per-channel record carries the agent's spec.
42
+ */
43
+
44
+ import type { AgentSpec } from "../sandbox/types.ts";
45
+ import { InboundClaimConflictError } from "../transports/vault.ts";
46
+ import type { InboundQueueNote, InboundStatus } from "../transports/vault.ts";
47
+
48
+ /**
49
+ * The storage seam the registry operates a channel's queue through — the durable
50
+ * inbound-note store (the daemon wires this to the channel's VaultTransport; tests
51
+ * inject a fake). Mirrors the vault-transport methods 1:1 so the seam is thin.
52
+ */
53
+ export interface AttachedQueueStore {
54
+ /** List this channel's inbound queue notes, ascending by ts (oldest first). */
55
+ listInboundQueue(opts?: { limit?: number }): Promise<InboundQueueNote[]>;
56
+ /**
57
+ * Set an inbound note's claim status (+ optionally claimedAt; `null` clears it).
58
+ * When `ifUpdatedAt` is given, the write is a COMPARE-AND-SWAP (the claim only lands
59
+ * if the note hasn't changed since it was read) and throws {@link
60
+ * InboundClaimConflictError} when the race is lost (agent#101); omitting it is the
61
+ * prior last-write-wins behavior (release / handled / sweep).
62
+ */
63
+ setInboundStatus(
64
+ id: string,
65
+ status: InboundStatus,
66
+ claimedAt?: string | null,
67
+ ifUpdatedAt?: string,
68
+ ): Promise<void>;
69
+ /** Write an outbound reply (the SAME `#agent/message/outbound` path the worker uses). */
70
+ reply(args: { text: string; inReplyTo?: string }): Promise<{ sent: string[] }>;
71
+ }
72
+
73
+ /** Bound on the CAS re-list retries in {@link AttachedQueueRegistry.claimNext} — a
74
+ * safety net against a pathological all-contended queue (each pass claims/eliminates
75
+ * one note, so the loop is naturally bounded by the pending count anyway). */
76
+ const MAX_CLAIM_ATTEMPTS = 25;
77
+
78
+ /** The default in-flight claim TTL (design: 15 min comfortably covers an operator turn). */
79
+ export const DEFAULT_CLAIM_TTL_MS = 15 * 60 * 1000;
80
+
81
+ /** A peek at the pending queue — count + a bounded preview of the waiting items. */
82
+ export interface PendingView {
83
+ /** How many inbound messages are `pending` (unclaimed) on this channel. */
84
+ count: number;
85
+ /** A bounded preview (oldest-first) for "you have N messages waiting" affordances. */
86
+ items: Array<{ id: string; preview: string }>;
87
+ }
88
+
89
+ /** The claimed message `claimNext` returns — everything a session needs to be the agent. */
90
+ export interface ClaimedMessage {
91
+ /** The inbound note id — pass it back as `inReplyTo` on `reply`. */
92
+ id: string;
93
+ /** The message text to work on. */
94
+ text: string;
95
+ /** The note id this turn threads to (== `id`, surfaced explicitly for the reply call). */
96
+ inReplyTo: string;
97
+ /**
98
+ * The agent's system prompt (the `#agent/definition` body) — the session adopts the
99
+ * persona by treating this as its instructions for the reply. Adopting it is the
100
+ * SESSION's responsibility (MCP can't force a system prompt on the caller); the MCP
101
+ * server INSTRUCTIONS reinforce the convention. Empty string when the def has none.
102
+ */
103
+ systemPrompt: string;
104
+ }
105
+
106
+ /** One registered attached-backend agent: its spec + the store its queue lives in. */
107
+ interface AttachedRecord {
108
+ /** The agent slug (the spec name) == the wake channel (agent ≡ channel). */
109
+ name: string;
110
+ /** The channel the queue + MCP surface key on. */
111
+ channel: string;
112
+ /** The spec — carries the systemPrompt the session adopts on `next-message`. */
113
+ spec: AgentSpec;
114
+ /** The durable inbound-note store (the channel's VaultTransport, in production). */
115
+ store: AttachedQueueStore;
116
+ }
117
+
118
+ /** How many pending items the `pending` peek returns at most (a nudge, not a dump). */
119
+ const PENDING_PEEK_CAP = 20;
120
+ /** How long a pending preview snippet is (characters). */
121
+ const PREVIEW_LEN = 120;
122
+
123
+ /**
124
+ * The daemon's registry of ATTACHED-backend agents + their durable queues. Keyed by
125
+ * CHANNEL (the inbound-routing index + the MCP-surface lookup are both O(1)). One
126
+ * instance per daemon, constructed at boot; the store is injected per-agent so tests
127
+ * drive it with a fake store, no real vault.
128
+ */
129
+ export class AttachedQueueRegistry {
130
+ /** channel → record. */
131
+ private readonly byChannel = new Map<string, AttachedRecord>();
132
+ /** name → channel (the lifecycle index; an agent has exactly one channel). */
133
+ private readonly nameToChannel = new Map<string, string>();
134
+ private readonly claimTtlMs: number;
135
+
136
+ constructor(opts?: { claimTtlMs?: number }) {
137
+ this.claimTtlMs = opts?.claimTtlMs ?? DEFAULT_CLAIM_TTL_MS;
138
+ }
139
+
140
+ /**
141
+ * Register (or replace) a attached-backend agent. Lightweight: index the record by
142
+ * channel + name. Idempotent-replace by name (a reload / boot re-register swaps the
143
+ * spec + store in place). Throws if the spec declares no channel.
144
+ */
145
+ register(spec: AgentSpec, store: AttachedQueueStore): void {
146
+ if (spec.channels.length === 0) {
147
+ throw new Error(`attached-queue registry: spec "${spec.name}" declares no channels`);
148
+ }
149
+ const channel = channelOf(spec);
150
+ // If the name moved channels (rare), drop the stale channel index.
151
+ const priorChannel = this.nameToChannel.get(spec.name);
152
+ if (priorChannel !== undefined && priorChannel !== channel) {
153
+ this.byChannel.delete(priorChannel);
154
+ }
155
+ this.byChannel.set(channel, { name: spec.name, channel, spec, store });
156
+ this.nameToChannel.set(spec.name, channel);
157
+ }
158
+
159
+ /** Deregister a attached-backend agent by NAME — drop its indexes. The durable queue
160
+ * notes stay in the vault (deregistering an agent doesn't delete its history). */
161
+ deregister(name: string): boolean {
162
+ const channel = this.nameToChannel.get(name);
163
+ if (channel === undefined) return false;
164
+ this.byChannel.delete(channel);
165
+ this.nameToChannel.delete(name);
166
+ return true;
167
+ }
168
+
169
+ /** Is a attached-backend agent registered for this channel? (the routing-fork check) */
170
+ hasChannel(channel: string): boolean {
171
+ return this.byChannel.has(channel);
172
+ }
173
+
174
+ /** Is a attached-backend agent registered under this name? (the mutual-exclusion check) */
175
+ hasName(name: string): boolean {
176
+ return this.nameToChannel.has(name);
177
+ }
178
+
179
+ /** All registered channels (for /health + the sweep + tests). */
180
+ channels(): string[] {
181
+ return [...this.byChannel.keys()];
182
+ }
183
+
184
+ /**
185
+ * The registered attached-backend agents as plain records (name + channel + the
186
+ * spec's surfaceable, non-secret fields). The `GET /api/agents` list (#102) maps
187
+ * these into {@link AgentInfo}; tests assert the shape. Sorted by name for a stable
188
+ * list. NEVER carries a token/secret — the spec's vault binding is a name + access
189
+ * verb only. The live `pending` count is fetched separately (it's an async vault
190
+ * read; see {@link pending}) so this accessor stays synchronous + cheap.
191
+ */
192
+ list(): Array<{ name: string; channel: string; vault?: string; systemPrompt?: string }> {
193
+ return [...this.byChannel.values()]
194
+ .map((rec) => ({
195
+ name: rec.name,
196
+ channel: rec.channel,
197
+ ...(rec.spec.vault?.name ? { vault: rec.spec.vault.name } : {}),
198
+ ...(rec.spec.systemPrompt ? { systemPrompt: rec.spec.systemPrompt } : {}),
199
+ }))
200
+ .sort((a, b) => a.name.localeCompare(b.name));
201
+ }
202
+
203
+ /**
204
+ * Peek the pending queue for a channel: the count of `status:pending` inbound + a
205
+ * bounded oldest-first preview. A no-op shape `{ count: 0, items: [] }` for an
206
+ * unregistered (non-channel) channel — the MCP surface gates cleanly on a non-channel
207
+ * channel rather than erroring.
208
+ */
209
+ async pending(channel: string): Promise<PendingView> {
210
+ const rec = this.byChannel.get(channel);
211
+ if (!rec) return { count: 0, items: [] };
212
+ const notes = await rec.store.listInboundQueue();
213
+ const pendingNotes = notes.filter((n) => n.status === "pending");
214
+ const items = pendingNotes
215
+ .slice(0, PENDING_PEEK_CAP)
216
+ .map((n) => ({ id: n.id, preview: preview(n.text) }));
217
+ return { count: pendingNotes.length, items };
218
+ }
219
+
220
+ /**
221
+ * Claim the OLDEST `pending` inbound for a channel: set it `in-flight` + stamp
222
+ * `claimedAt = now`, then return it (+ the agent's system prompt). The status flip to
223
+ * `in-flight` (the vault is the source of truth) is what makes a SEQUENTIAL second
224
+ * `claimNext` skip it: each call re-reads the live queue, so once the first claim's
225
+ * PATCH lands the note is no longer `pending`. Returns null when none pending (or for
226
+ * an unregistered channel).
227
+ *
228
+ * SINGLE-CLAIM via COMPARE-AND-SWAP (agent#101). The claim PATCH carries
229
+ * `if_updated_at` (the note's last-seen `updated_at`), so it only lands if the note
230
+ * hasn't changed since this call read it. Two truly-concurrent `claimNext` calls read
231
+ * the SAME `updated_at`; the first claim advances it, so the second's precondition
232
+ * FAILS (the store throws {@link InboundClaimConflictError}) — we then RE-LIST and try
233
+ * the NEXT pending message, never double-claiming. The loop is bounded by the pending
234
+ * count (each pass either claims one or eliminates a now-contended one) plus a hard
235
+ * {@link MAX_CLAIM_ATTEMPTS} backstop. A note with no `updatedAt` (a vault that omitted
236
+ * it) falls back to the prior last-write-wins claim — the precondition is simply
237
+ * absent, so the narrow double-claim window remains only for that degenerate case.
238
+ * Returns null when none pending (or for an unregistered channel).
239
+ */
240
+ async claimNext(channel: string, now: () => Date = () => new Date()): Promise<ClaimedMessage | null> {
241
+ const rec = this.byChannel.get(channel);
242
+ if (!rec) return null;
243
+ for (let attempt = 0; attempt < MAX_CLAIM_ATTEMPTS; attempt++) {
244
+ // RE-LIST each attempt so a lost CAS race sees the queue as the winner left it
245
+ // (the contended note is now in-flight, so `find` skips past it to the next pending).
246
+ const notes = await rec.store.listInboundQueue();
247
+ const oldest = notes.find((n) => n.status === "pending"); // ascending by ts.
248
+ if (!oldest) return null;
249
+ try {
250
+ // Commit the claim (status → in-flight + claimedAt) BEFORE returning, guarded by
251
+ // the note's `updated_at` so a concurrent claimer can't also win it. A non-conflict
252
+ // PATCH failure propagates (the caller gets the error; the note stays pending).
253
+ await rec.store.setInboundStatus(
254
+ oldest.id,
255
+ "in-flight",
256
+ now().toISOString(),
257
+ oldest.updatedAt, // CAS precondition; undefined → store falls back to force.
258
+ );
259
+ } catch (err) {
260
+ if (err instanceof InboundClaimConflictError) {
261
+ // Another session claimed this note between our list and PATCH — re-list and
262
+ // try the next pending one (no double-claim).
263
+ continue;
264
+ }
265
+ throw err;
266
+ }
267
+ return {
268
+ id: oldest.id,
269
+ text: oldest.text,
270
+ inReplyTo: oldest.id,
271
+ systemPrompt: rec.spec.systemPrompt ?? "",
272
+ };
273
+ }
274
+ // Exhausted the retry budget under sustained contention — treat as "none claimable
275
+ // right now" (a connected session retries `next-message`). Non-corrupting.
276
+ return null;
277
+ }
278
+
279
+ /**
280
+ * Reply to an inbound on a channel: write the outbound `#agent/message/outbound`
281
+ * note via the SAME vault-transport `reply()` the programmatic worker uses (durable,
282
+ * threads `inReplyTo`, renders in the chat UI, tagged outbound so it can NEVER
283
+ * re-trigger the inbound webhook — loop-safe), THEN mark the inbound `handled` and
284
+ * clear its `claimedAt`. Order matters: the outbound is written FIRST so a failure to
285
+ * persist the reply leaves the inbound un-handled (the session can retry) rather than
286
+ * marking it done with no reply. Returns the outbound note id(s). Throws for an
287
+ * unregistered channel (the MCP surface maps it to a tool error).
288
+ */
289
+ async reply(channel: string, args: { inReplyTo?: string; text: string }): Promise<{ sent: string[] }> {
290
+ const rec = this.byChannel.get(channel);
291
+ if (!rec) throw new Error(`attached-queue registry: no attached-backend agent for "${channel}"`);
292
+ const sent = await rec.store.reply({
293
+ text: args.text,
294
+ ...(args.inReplyTo ? { inReplyTo: args.inReplyTo } : {}),
295
+ });
296
+ // Mark handled only AFTER the outbound is durably written. Clear claimedAt so a
297
+ // handled note doesn't linger with a stale claim timestamp.
298
+ if (args.inReplyTo) {
299
+ await rec.store.setInboundStatus(args.inReplyTo, "handled", null);
300
+ }
301
+ return sent;
302
+ }
303
+
304
+ /**
305
+ * Release an in-flight claim back to `pending` (the session is giving up). Clears
306
+ * `claimedAt`. Idempotent at the vault level (re-setting pending is harmless). Throws
307
+ * for an unregistered channel.
308
+ */
309
+ async release(channel: string, id: string): Promise<void> {
310
+ const rec = this.byChannel.get(channel);
311
+ if (!rec) throw new Error(`attached-queue registry: no attached-backend agent for "${channel}"`);
312
+ await rec.store.setInboundStatus(id, "pending", null);
313
+ }
314
+
315
+ /**
316
+ * TTL auto-release: across EVERY registered channel, reset to `pending` any
317
+ * `in-flight` note whose `claimedAt` is older than the claim TTL — so a crashed /
318
+ * abandoned session can't strand the queue. Best-effort + per-channel-isolated: one
319
+ * channel's store error is logged and never aborts the others. Returns the number of
320
+ * notes released. Wired into the daemon's periodic tick.
321
+ *
322
+ * An `in-flight` note with NO usable `claimedAt` is left alone (we can't judge its
323
+ * age) — defensive; in practice `claimNext` always stamps one.
324
+ */
325
+ async sweepExpired(now: Date = new Date()): Promise<number> {
326
+ let released = 0;
327
+ const cutoff = now.getTime() - this.claimTtlMs;
328
+ for (const rec of this.byChannel.values()) {
329
+ let notes: InboundQueueNote[];
330
+ try {
331
+ notes = await rec.store.listInboundQueue();
332
+ } catch (err) {
333
+ console.warn(
334
+ `attached-queue: sweep list for "${rec.channel}" failed (continuing): ${(err as Error).message}`,
335
+ );
336
+ continue;
337
+ }
338
+ for (const n of notes) {
339
+ if (n.status !== "in-flight") continue;
340
+ if (!n.claimedAt) continue; // can't judge age — leave it.
341
+ const claimedMs = new Date(n.claimedAt).getTime();
342
+ if (Number.isNaN(claimedMs) || claimedMs > cutoff) continue; // fresh enough.
343
+ try {
344
+ await rec.store.setInboundStatus(n.id, "pending", null);
345
+ released++;
346
+ console.log(
347
+ `attached-queue: auto-released stale in-flight note ${n.id} on "${rec.channel}" ` +
348
+ `(claimed ${n.claimedAt}, TTL ${this.claimTtlMs}ms).`,
349
+ );
350
+ } catch (err) {
351
+ console.warn(
352
+ `attached-queue: sweep release of ${n.id} on "${rec.channel}" failed (continuing): ` +
353
+ `${(err as Error).message}`,
354
+ );
355
+ }
356
+ }
357
+ }
358
+ return released;
359
+ }
360
+ }
361
+
362
+ /** The wake channel for a spec (its first channel) — agent ≡ channel for a channel def. */
363
+ function channelOf(spec: AgentSpec): string {
364
+ const first = spec.channels[0]!;
365
+ return typeof first === "string" ? first : first.name;
366
+ }
367
+
368
+ /** A bounded single-line preview of a message body (for the pending peek). */
369
+ function preview(text: string): string {
370
+ const oneLine = text.replace(/\s+/g, " ").trim();
371
+ return oneLine.length > PREVIEW_LEN ? `${oneLine.slice(0, PREVIEW_LEN - 1)}…` : oneLine;
372
+ }