@openparachute/agent 0.1.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 (501) hide show
  1. package/.claude/scheduled_tasks.lock +1 -0
  2. package/.claude/settings.json +5 -0
  3. package/.claude/skills/add-atomic-chat-tool/SKILL.md +243 -0
  4. package/.claude/skills/add-atomic-chat-tool/atomic-chat-mcp-stdio.ts +229 -0
  5. package/.claude/skills/add-codex/SKILL.md +161 -0
  6. package/.claude/skills/add-dashboard/SKILL.md +138 -0
  7. package/.claude/skills/add-dashboard/resources/dashboard-pusher.ts +495 -0
  8. package/.claude/skills/add-emacs/SKILL.md +296 -0
  9. package/.claude/skills/add-gcal-tool/SKILL.md +210 -0
  10. package/.claude/skills/add-gchat/REMOVE.md +6 -0
  11. package/.claude/skills/add-gchat/SKILL.md +92 -0
  12. package/.claude/skills/add-gchat/VERIFY.md +3 -0
  13. package/.claude/skills/add-github/REMOVE.md +6 -0
  14. package/.claude/skills/add-github/SKILL.md +148 -0
  15. package/.claude/skills/add-github/VERIFY.md +3 -0
  16. package/.claude/skills/add-gmail-tool/SKILL.md +229 -0
  17. package/.claude/skills/add-imessage/REMOVE.md +6 -0
  18. package/.claude/skills/add-imessage/SKILL.md +113 -0
  19. package/.claude/skills/add-imessage/VERIFY.md +3 -0
  20. package/.claude/skills/add-karpathy-llm-wiki/SKILL.md +110 -0
  21. package/.claude/skills/add-karpathy-llm-wiki/llm-wiki.md +75 -0
  22. package/.claude/skills/add-linear/REMOVE.md +6 -0
  23. package/.claude/skills/add-linear/SKILL.md +168 -0
  24. package/.claude/skills/add-linear/VERIFY.md +3 -0
  25. package/.claude/skills/add-macos-statusbar/SKILL.md +133 -0
  26. package/.claude/skills/add-macos-statusbar/add/src/statusbar.swift +147 -0
  27. package/.claude/skills/add-matrix/REMOVE.md +6 -0
  28. package/.claude/skills/add-matrix/SKILL.md +148 -0
  29. package/.claude/skills/add-matrix/VERIFY.md +3 -0
  30. package/.claude/skills/add-ollama-provider/SKILL.md +179 -0
  31. package/.claude/skills/add-ollama-tool/SKILL.md +193 -0
  32. package/.claude/skills/add-opencode/SKILL.md +229 -0
  33. package/.claude/skills/add-parallel/SKILL.md +290 -0
  34. package/.claude/skills/add-resend/REMOVE.md +6 -0
  35. package/.claude/skills/add-resend/SKILL.md +93 -0
  36. package/.claude/skills/add-resend/VERIFY.md +3 -0
  37. package/.claude/skills/add-signal/REMOVE.md +13 -0
  38. package/.claude/skills/add-signal/SKILL.md +318 -0
  39. package/.claude/skills/add-signal/VERIFY.md +5 -0
  40. package/.claude/skills/add-slack/REMOVE.md +6 -0
  41. package/.claude/skills/add-slack/SKILL.md +112 -0
  42. package/.claude/skills/add-slack/VERIFY.md +3 -0
  43. package/.claude/skills/add-teams/REMOVE.md +6 -0
  44. package/.claude/skills/add-teams/SKILL.md +207 -0
  45. package/.claude/skills/add-teams/VERIFY.md +3 -0
  46. package/.claude/skills/add-vercel/SKILL.md +147 -0
  47. package/.claude/skills/add-vercel/container-skills/vercel-cli/SKILL.md +103 -0
  48. package/.claude/skills/add-webex/REMOVE.md +6 -0
  49. package/.claude/skills/add-webex/SKILL.md +88 -0
  50. package/.claude/skills/add-webex/VERIFY.md +3 -0
  51. package/.claude/skills/add-wechat/REMOVE.md +49 -0
  52. package/.claude/skills/add-wechat/SKILL.md +170 -0
  53. package/.claude/skills/add-wechat/scripts/wire-dm.ts +172 -0
  54. package/.claude/skills/add-whatsapp/SKILL.md +264 -0
  55. package/.claude/skills/add-whatsapp-cloud/REMOVE.md +6 -0
  56. package/.claude/skills/add-whatsapp-cloud/SKILL.md +95 -0
  57. package/.claude/skills/add-whatsapp-cloud/VERIFY.md +3 -0
  58. package/.claude/skills/claw/SKILL.md +131 -0
  59. package/.claude/skills/claw/scripts/claw +374 -0
  60. package/.claude/skills/convert-to-apple-container/SKILL.md +212 -0
  61. package/.claude/skills/customize/SKILL.md +110 -0
  62. package/.claude/skills/debug/SKILL.md +349 -0
  63. package/.claude/skills/get-qodo-rules/SKILL.md +122 -0
  64. package/.claude/skills/get-qodo-rules/references/output-format.md +41 -0
  65. package/.claude/skills/get-qodo-rules/references/pagination.md +33 -0
  66. package/.claude/skills/get-qodo-rules/references/repository-scope.md +26 -0
  67. package/.claude/skills/init-first-agent/SKILL.md +120 -0
  68. package/.claude/skills/init-onecli/SKILL.md +270 -0
  69. package/.claude/skills/manage-channels/SKILL.md +87 -0
  70. package/.claude/skills/manage-mounts/SKILL.md +47 -0
  71. package/.claude/skills/migrate-from-openclaw/MIGRATE_CRONS.md +100 -0
  72. package/.claude/skills/migrate-from-openclaw/SKILL.md +447 -0
  73. package/.claude/skills/migrate-from-openclaw/scripts/discover-openclaw.ts +734 -0
  74. package/.claude/skills/migrate-from-openclaw/scripts/extract-channel-credentials.ts +476 -0
  75. package/.claude/skills/migrate-nanoclaw/SKILL.md +484 -0
  76. package/.claude/skills/migrate-nanoclaw/diagnostics.md +51 -0
  77. package/.claude/skills/qodo-pr-resolver/SKILL.md +326 -0
  78. package/.claude/skills/qodo-pr-resolver/resources/providers.md +329 -0
  79. package/.claude/skills/update-nanoclaw/SKILL.md +243 -0
  80. package/.claude/skills/update-nanoclaw/diagnostics.md +48 -0
  81. package/.claude/skills/update-skills/SKILL.md +130 -0
  82. package/.claude/skills/use-native-credential-proxy/SKILL.md +167 -0
  83. package/.claude/skills/x-integration/SKILL.md +417 -0
  84. package/.claude/skills/x-integration/agent.ts +243 -0
  85. package/.claude/skills/x-integration/host.ts +155 -0
  86. package/.claude/skills/x-integration/lib/browser.ts +148 -0
  87. package/.claude/skills/x-integration/lib/config.ts +62 -0
  88. package/.claude/skills/x-integration/scripts/like.ts +56 -0
  89. package/.claude/skills/x-integration/scripts/post.ts +66 -0
  90. package/.claude/skills/x-integration/scripts/quote.ts +80 -0
  91. package/.claude/skills/x-integration/scripts/reply.ts +74 -0
  92. package/.claude/skills/x-integration/scripts/retweet.ts +62 -0
  93. package/.claude/skills/x-integration/scripts/setup.ts +87 -0
  94. package/.github/CODEOWNERS +10 -0
  95. package/.github/PULL_REQUEST_TEMPLATE.md +18 -0
  96. package/.github/workflows/bump-version.yml +35 -0
  97. package/.github/workflows/ci.yml +39 -0
  98. package/.github/workflows/label-pr.yml +40 -0
  99. package/.github/workflows/update-tokens.yml +43 -0
  100. package/.husky/pre-commit +1 -0
  101. package/.mcp.json +3 -0
  102. package/.nvmrc +1 -0
  103. package/.parachute/module.json +14 -0
  104. package/.prettierrc +4 -0
  105. package/CHANGELOG.md +215 -0
  106. package/CLAUDE.md +307 -0
  107. package/CODE_OF_CONDUCT.md +128 -0
  108. package/CONTRIBUTING.md +159 -0
  109. package/CONTRIBUTORS.md +26 -0
  110. package/LICENSE +21 -0
  111. package/README.md +190 -0
  112. package/README_ja.md +194 -0
  113. package/README_zh.md +194 -0
  114. package/assets/nanoclaw-favicon.png +0 -0
  115. package/assets/nanoclaw-icon.png +0 -0
  116. package/assets/nanoclaw-logo-dark.png +0 -0
  117. package/assets/nanoclaw-logo.png +0 -0
  118. package/assets/nanoclaw-profile.jpeg +0 -0
  119. package/assets/nanoclaw-sales.png +0 -0
  120. package/assets/social-preview.jpg +0 -0
  121. package/config-examples/mount-allowlist.json +25 -0
  122. package/container/.dockerignore +2 -0
  123. package/container/CLAUDE.md +21 -0
  124. package/container/Dockerfile +121 -0
  125. package/container/agent-runner/bun.lock +243 -0
  126. package/container/agent-runner/package.json +22 -0
  127. package/container/agent-runner/scripts/sdk-signal-probe.ts +169 -0
  128. package/container/agent-runner/src/config.ts +55 -0
  129. package/container/agent-runner/src/db/connection.ts +267 -0
  130. package/container/agent-runner/src/db/index.ts +20 -0
  131. package/container/agent-runner/src/db/messages-in.ts +138 -0
  132. package/container/agent-runner/src/db/messages-out.ts +143 -0
  133. package/container/agent-runner/src/db/session-routing.ts +30 -0
  134. package/container/agent-runner/src/db/session-state.test.ts +100 -0
  135. package/container/agent-runner/src/db/session-state.ts +79 -0
  136. package/container/agent-runner/src/destinations.ts +135 -0
  137. package/container/agent-runner/src/formatter.test.ts +167 -0
  138. package/container/agent-runner/src/formatter.ts +260 -0
  139. package/container/agent-runner/src/index.ts +110 -0
  140. package/container/agent-runner/src/integration.test.ts +121 -0
  141. package/container/agent-runner/src/mcp-tools/agents.instructions.md +26 -0
  142. package/container/agent-runner/src/mcp-tools/agents.ts +66 -0
  143. package/container/agent-runner/src/mcp-tools/core.instructions.md +27 -0
  144. package/container/agent-runner/src/mcp-tools/core.ts +262 -0
  145. package/container/agent-runner/src/mcp-tools/index.ts +22 -0
  146. package/container/agent-runner/src/mcp-tools/interactive.instructions.md +22 -0
  147. package/container/agent-runner/src/mcp-tools/interactive.ts +169 -0
  148. package/container/agent-runner/src/mcp-tools/scheduling.instructions.md +40 -0
  149. package/container/agent-runner/src/mcp-tools/scheduling.ts +299 -0
  150. package/container/agent-runner/src/mcp-tools/self-mod.instructions.md +25 -0
  151. package/container/agent-runner/src/mcp-tools/self-mod.ts +120 -0
  152. package/container/agent-runner/src/mcp-tools/server.ts +54 -0
  153. package/container/agent-runner/src/mcp-tools/types.ts +6 -0
  154. package/container/agent-runner/src/poll-loop.test.ts +248 -0
  155. package/container/agent-runner/src/poll-loop.ts +437 -0
  156. package/container/agent-runner/src/providers/claude.ts +379 -0
  157. package/container/agent-runner/src/providers/factory.test.ts +19 -0
  158. package/container/agent-runner/src/providers/factory.ts +13 -0
  159. package/container/agent-runner/src/providers/index.ts +6 -0
  160. package/container/agent-runner/src/providers/mock.ts +77 -0
  161. package/container/agent-runner/src/providers/provider-registry.ts +33 -0
  162. package/container/agent-runner/src/providers/types.ts +82 -0
  163. package/container/agent-runner/src/scheduling/task-script.ts +121 -0
  164. package/container/agent-runner/src/timezone.test.ts +93 -0
  165. package/container/agent-runner/src/timezone.ts +107 -0
  166. package/container/agent-runner/tsconfig.json +14 -0
  167. package/container/build.sh +48 -0
  168. package/container/entrypoint.sh +16 -0
  169. package/container/skills/agent-browser/SKILL.md +159 -0
  170. package/container/skills/frontend-engineer/SKILL.md +157 -0
  171. package/container/skills/self-customize/SKILL.md +87 -0
  172. package/container/skills/slack-formatting/SKILL.md +94 -0
  173. package/container/skills/vercel-cli/SKILL.md +111 -0
  174. package/container/skills/welcome/SKILL.md +85 -0
  175. package/docs/APPLE-CONTAINER-NETWORKING.md +90 -0
  176. package/docs/BRANCH-FORK-MAINTENANCE.md +81 -0
  177. package/docs/README.md +25 -0
  178. package/docs/SDK_DEEP_DIVE.md +643 -0
  179. package/docs/SECURITY.md +162 -0
  180. package/docs/agent-runner-details.md +749 -0
  181. package/docs/api-details.md +365 -0
  182. package/docs/architecture-diagram.html +422 -0
  183. package/docs/architecture-diagram.md +215 -0
  184. package/docs/architecture.md +751 -0
  185. package/docs/audit/2026-04-30-channel-endpoint-audit.md +36 -0
  186. package/docs/build-and-runtime.md +80 -0
  187. package/docs/cross-mount-stress/README.md +112 -0
  188. package/docs/cross-mount-stress/container-writer-retry.mjs +55 -0
  189. package/docs/cross-mount-stress/container-writer-slow.mjs +42 -0
  190. package/docs/cross-mount-stress/container-writer.mjs +47 -0
  191. package/docs/cross-mount-stress/host-writer-retry.mjs +55 -0
  192. package/docs/cross-mount-stress/host-writer-slow.mjs +43 -0
  193. package/docs/cross-mount-stress/host-writer.mjs +47 -0
  194. package/docs/db-central.md +316 -0
  195. package/docs/db-session.md +183 -0
  196. package/docs/db.md +119 -0
  197. package/docs/design/2026-04-29-vault-management-ui.md +231 -0
  198. package/docs/design/2026-04-30-channel-wiring-rework.md +234 -0
  199. package/docs/design/2026-05-01-channel-wiring-approvals-deep-dive.md +272 -0
  200. package/docs/design/2026-05-02-channel-policy-and-approval-routing.md +250 -0
  201. package/docs/docker-sandboxes.md +359 -0
  202. package/docs/isolation-model.md +88 -0
  203. package/docs/ollama.md +79 -0
  204. package/docs/parachute-integration.md +109 -0
  205. package/docs/post-night-rebirth-reflections.md +151 -0
  206. package/eslint.config.js +32 -0
  207. package/package.json +54 -0
  208. package/pnpm-workspace.yaml +8 -0
  209. package/repo-tokens/README.md +113 -0
  210. package/repo-tokens/action.yml +186 -0
  211. package/repo-tokens/badge.svg +23 -0
  212. package/repo-tokens/examples/green.svg +14 -0
  213. package/repo-tokens/examples/red.svg +14 -0
  214. package/repo-tokens/examples/yellow-green.svg +14 -0
  215. package/repo-tokens/examples/yellow.svg +14 -0
  216. package/scripts/chat.ts +101 -0
  217. package/scripts/cleanup-sessions.sh +150 -0
  218. package/scripts/init-cli-agent.ts +171 -0
  219. package/scripts/init-first-agent.ts +377 -0
  220. package/scripts/parachute.ts +158 -0
  221. package/scripts/run-migrations.ts +105 -0
  222. package/scripts/sanity-live-poll.ts +95 -0
  223. package/scripts/seed-discord.ts +79 -0
  224. package/scripts/test-v2-agent.ts +106 -0
  225. package/scripts/test-v2-channel-e2e.ts +265 -0
  226. package/scripts/test-v2-host.ts +184 -0
  227. package/src/channels/adapter.ts +214 -0
  228. package/src/channels/ask-question.ts +46 -0
  229. package/src/channels/channel-registry.test.ts +421 -0
  230. package/src/channels/channel-registry.ts +313 -0
  231. package/src/channels/chat-sdk-bridge.test.ts +84 -0
  232. package/src/channels/chat-sdk-bridge.ts +652 -0
  233. package/src/channels/cli.ts +276 -0
  234. package/src/channels/discord.ts +90 -0
  235. package/src/channels/index.ts +17 -0
  236. package/src/channels/telegram-markdown-sanitize.test.ts +78 -0
  237. package/src/channels/telegram-markdown-sanitize.ts +55 -0
  238. package/src/channels/telegram-pairing.test.ts +254 -0
  239. package/src/channels/telegram-pairing.ts +339 -0
  240. package/src/channels/telegram.ts +279 -0
  241. package/src/channels/trust-hint.test.ts +48 -0
  242. package/src/channels/trust-hint.ts +75 -0
  243. package/src/claude-md-compose.migrate.test.ts +64 -0
  244. package/src/claude-md-compose.ts +205 -0
  245. package/src/command-gate.ts +63 -0
  246. package/src/config.test.ts +93 -0
  247. package/src/config.ts +108 -0
  248. package/src/container-config.ts +167 -0
  249. package/src/container-runner.test.ts +32 -0
  250. package/src/container-runner.ts +576 -0
  251. package/src/container-runtime.test.ts +169 -0
  252. package/src/container-runtime.ts +92 -0
  253. package/src/db/_bun-sqlite-shim.ts +88 -0
  254. package/src/db/agent-activity.test.ts +155 -0
  255. package/src/db/agent-activity.ts +121 -0
  256. package/src/db/agent-groups.ts +77 -0
  257. package/src/db/connection.migrate.test.ts +143 -0
  258. package/src/db/connection.ts +224 -0
  259. package/src/db/db-v2.test.ts +440 -0
  260. package/src/db/dropped-messages.ts +44 -0
  261. package/src/db/index.ts +40 -0
  262. package/src/db/messaging-groups.ts +252 -0
  263. package/src/db/migrations/001-initial.ts +112 -0
  264. package/src/db/migrations/002-chat-sdk-state.ts +36 -0
  265. package/src/db/migrations/008-dropped-messages.ts +27 -0
  266. package/src/db/migrations/009-drop-pending-credentials.ts +13 -0
  267. package/src/db/migrations/010-engage-modes.ts +103 -0
  268. package/src/db/migrations/011-pending-sender-approvals.ts +40 -0
  269. package/src/db/migrations/012-channel-registration.ts +48 -0
  270. package/src/db/migrations/013-approval-render-metadata.ts +27 -0
  271. package/src/db/migrations/014-secrets.ts +44 -0
  272. package/src/db/migrations/015-secrets-drop-host-pattern.ts +18 -0
  273. package/src/db/migrations/016-secret-assignments.ts +30 -0
  274. package/src/db/migrations/017-agent-activity.ts +40 -0
  275. package/src/db/migrations/018-oauth-app-configs.ts +34 -0
  276. package/src/db/migrations/019-oauth-app-connections.ts +48 -0
  277. package/src/db/migrations/020-agent-app-connections.ts +28 -0
  278. package/src/db/migrations/021-pending-oauth-states.ts +35 -0
  279. package/src/db/migrations/022-app-connections-provider.ts +25 -0
  280. package/src/db/migrations/023-agent-group-secret-mode.test.ts +124 -0
  281. package/src/db/migrations/023-agent-group-secret-mode.ts +65 -0
  282. package/src/db/migrations/024-collapse-approvals.test.ts +249 -0
  283. package/src/db/migrations/024-collapse-approvals.ts +182 -0
  284. package/src/db/migrations/025-secret-mode-check.test.ts +155 -0
  285. package/src/db/migrations/025-secret-mode-check.ts +49 -0
  286. package/src/db/migrations/026-user-dms-bot-id.test.ts +116 -0
  287. package/src/db/migrations/026-user-dms-bot-id.ts +54 -0
  288. package/src/db/migrations/027-provider-credentials.ts +41 -0
  289. package/src/db/migrations/_test-helpers.ts +41 -0
  290. package/src/db/migrations/index.ts +127 -0
  291. package/src/db/migrations/module-agent-to-agent-destinations.ts +84 -0
  292. package/src/db/migrations/module-approvals-pending-approvals.ts +42 -0
  293. package/src/db/migrations/module-approvals-title-options.ts +40 -0
  294. package/src/db/schema.ts +258 -0
  295. package/src/db/session-db.test.ts +93 -0
  296. package/src/db/session-db.ts +325 -0
  297. package/src/db/sessions.ts +241 -0
  298. package/src/delivery.test.ts +148 -0
  299. package/src/delivery.ts +445 -0
  300. package/src/env.ts +74 -0
  301. package/src/group-folder.test.ts +35 -0
  302. package/src/group-folder.ts +44 -0
  303. package/src/group-init.ts +92 -0
  304. package/src/host-core.test.ts +456 -0
  305. package/src/host-sweep.test.ts +146 -0
  306. package/src/host-sweep.ts +287 -0
  307. package/src/index.ts +227 -0
  308. package/src/install-slug.ts +33 -0
  309. package/src/log.test.ts +81 -0
  310. package/src/log.ts +117 -0
  311. package/src/mcp/http.ts +72 -0
  312. package/src/mcp/server.ts +92 -0
  313. package/src/mcp/stdio.ts +51 -0
  314. package/src/mcp/tools/activity.ts +88 -0
  315. package/src/mcp/tools/agent-groups.ts +183 -0
  316. package/src/mcp/tools/approvals.ts +122 -0
  317. package/src/mcp/tools/channels.ts +199 -0
  318. package/src/mcp/tools/index.ts +27 -0
  319. package/src/mcp/tools/oauth.ts +48 -0
  320. package/src/mcp/tools/secrets.ts +169 -0
  321. package/src/mcp/tools/sessions.ts +135 -0
  322. package/src/mcp/types.ts +51 -0
  323. package/src/modules/agent-to-agent/agent-route.test.ts +46 -0
  324. package/src/modules/agent-to-agent/agent-route.ts +223 -0
  325. package/src/modules/agent-to-agent/create-agent.ts +127 -0
  326. package/src/modules/agent-to-agent/db/agent-destinations.ts +135 -0
  327. package/src/modules/agent-to-agent/index.ts +22 -0
  328. package/src/modules/agent-to-agent/write-destinations.ts +59 -0
  329. package/src/modules/approvals/agent.md +45 -0
  330. package/src/modules/approvals/index.ts +21 -0
  331. package/src/modules/approvals/picks.test.ts +291 -0
  332. package/src/modules/approvals/primitive.ts +279 -0
  333. package/src/modules/approvals/project.md +27 -0
  334. package/src/modules/approvals/response-handler.ts +87 -0
  335. package/src/modules/index.ts +24 -0
  336. package/src/modules/interactive/agent.md +21 -0
  337. package/src/modules/interactive/index.ts +69 -0
  338. package/src/modules/interactive/project.md +12 -0
  339. package/src/modules/mount-security/index.ts +448 -0
  340. package/src/modules/mount-security/migrate.test.ts +91 -0
  341. package/src/modules/permissions/access.ts +28 -0
  342. package/src/modules/permissions/channel-approval.test.ts +389 -0
  343. package/src/modules/permissions/channel-approval.ts +188 -0
  344. package/src/modules/permissions/db/agent-group-members.ts +44 -0
  345. package/src/modules/permissions/db/pending-channel-approvals.test.ts +86 -0
  346. package/src/modules/permissions/db/pending-channel-approvals.ts +66 -0
  347. package/src/modules/permissions/db/pending-sender-approvals.ts +60 -0
  348. package/src/modules/permissions/db/user-dms.ts +58 -0
  349. package/src/modules/permissions/db/user-roles.ts +85 -0
  350. package/src/modules/permissions/db/users.ts +38 -0
  351. package/src/modules/permissions/index.ts +421 -0
  352. package/src/modules/permissions/permissions.test.ts +358 -0
  353. package/src/modules/permissions/sender-approval.test.ts +470 -0
  354. package/src/modules/permissions/sender-approval.ts +165 -0
  355. package/src/modules/permissions/user-dm.ts +200 -0
  356. package/src/modules/provider-credentials/db.ts +121 -0
  357. package/src/modules/provider-credentials/index.ts +12 -0
  358. package/src/modules/provider-credentials/spawn.test.ts +206 -0
  359. package/src/modules/provider-credentials/spawn.ts +114 -0
  360. package/src/modules/scheduling/actions.ts +113 -0
  361. package/src/modules/scheduling/db.test.ts +282 -0
  362. package/src/modules/scheduling/db.ts +148 -0
  363. package/src/modules/scheduling/index.ts +34 -0
  364. package/src/modules/scheduling/recurrence.test.ts +98 -0
  365. package/src/modules/scheduling/recurrence.ts +54 -0
  366. package/src/modules/self-mod/agent.md +30 -0
  367. package/src/modules/self-mod/apply.ts +85 -0
  368. package/src/modules/self-mod/index.ts +30 -0
  369. package/src/modules/self-mod/project.md +39 -0
  370. package/src/modules/self-mod/request.ts +91 -0
  371. package/src/modules/typing/index.ts +165 -0
  372. package/src/oauth/agent-app-connections.ts +103 -0
  373. package/src/oauth/app-configs.test.ts +64 -0
  374. package/src/oauth/app-configs.ts +114 -0
  375. package/src/oauth/app-connections.test.ts +109 -0
  376. package/src/oauth/app-connections.ts +178 -0
  377. package/src/oauth/crypto.ts +56 -0
  378. package/src/oauth/flow.ts +104 -0
  379. package/src/oauth/providers/google.test.ts +38 -0
  380. package/src/oauth/providers/google.ts +46 -0
  381. package/src/oauth/providers/index.ts +48 -0
  382. package/src/oauth/state-store.test.ts +54 -0
  383. package/src/oauth/state-store.ts +93 -0
  384. package/src/parachute/README.md +27 -0
  385. package/src/parachute/create-agent.test.ts +83 -0
  386. package/src/parachute/create-agent.ts +122 -0
  387. package/src/parachute/group-status.test.ts +165 -0
  388. package/src/parachute/group-status.ts +136 -0
  389. package/src/parachute/types.ts +41 -0
  390. package/src/parachute/vault-mcp.test.ts +251 -0
  391. package/src/parachute/vault-mcp.ts +232 -0
  392. package/src/platform-id.test.ts +104 -0
  393. package/src/platform-id.ts +109 -0
  394. package/src/providers/index.ts +6 -0
  395. package/src/providers/provider-container-registry.ts +58 -0
  396. package/src/response-registry.ts +45 -0
  397. package/src/router.ts +530 -0
  398. package/src/secrets/crypto.test.ts +45 -0
  399. package/src/secrets/crypto.ts +55 -0
  400. package/src/secrets/index.ts +355 -0
  401. package/src/secrets/master-key.ts +70 -0
  402. package/src/secrets/secrets.test.ts +354 -0
  403. package/src/session-manager.migrate.test.ts +59 -0
  404. package/src/session-manager.ts +433 -0
  405. package/src/startup-bootstrap.test.ts +226 -0
  406. package/src/startup-bootstrap.ts +207 -0
  407. package/src/state-sqlite.ts +182 -0
  408. package/src/timezone.test.ts +64 -0
  409. package/src/timezone.ts +37 -0
  410. package/src/types.ts +230 -0
  411. package/src/web/auth.test.ts +335 -0
  412. package/src/web/auth.ts +214 -0
  413. package/src/web/discord-validate.test.ts +77 -0
  414. package/src/web/discord-validate.ts +88 -0
  415. package/src/web/hub-discovery.test.ts +98 -0
  416. package/src/web/hub-discovery.ts +69 -0
  417. package/src/web/routes/activity.ts +106 -0
  418. package/src/web/routes/agent-provider.test.ts +282 -0
  419. package/src/web/routes/agent-provider.ts +309 -0
  420. package/src/web/routes/approvals.ts +185 -0
  421. package/src/web/routes/apps.ts +434 -0
  422. package/src/web/routes/channels-mg-detail.test.ts +324 -0
  423. package/src/web/routes/channels-mga-detail.test.ts +425 -0
  424. package/src/web/routes/channels.ts +489 -0
  425. package/src/web/routes/oauth-providers.ts +42 -0
  426. package/src/web/routes/secrets.test.ts +175 -0
  427. package/src/web/routes/secrets.ts +282 -0
  428. package/src/web/routes/sessions.ts +123 -0
  429. package/src/web/routes/settings.test.ts +106 -0
  430. package/src/web/routes/settings.ts +247 -0
  431. package/src/web/routes/setup-status.ts +205 -0
  432. package/src/web/routes/vaults.test.ts +389 -0
  433. package/src/web/routes/vaults.ts +225 -0
  434. package/src/web/server-version.test.ts +16 -0
  435. package/src/web/server.ts +1003 -0
  436. package/src/web/services-manifest.test.ts +120 -0
  437. package/src/web/services-manifest.ts +61 -0
  438. package/src/web/static-serve.test.ts +255 -0
  439. package/src/web/static-serve.ts +104 -0
  440. package/src/web/telegram-validate.test.ts +116 -0
  441. package/src/web/telegram-validate.ts +107 -0
  442. package/src/web/vault-proxy.test.ts +214 -0
  443. package/src/web/vault-proxy.ts +120 -0
  444. package/src/web/wire-channel.ts +181 -0
  445. package/src/webhook-server.ts +134 -0
  446. package/tsconfig.json +21 -0
  447. package/vitest.config.ts +18 -0
  448. package/web/README.md +63 -0
  449. package/web/ui/index.html +13 -0
  450. package/web/ui/package.json +35 -0
  451. package/web/ui/pnpm-lock.yaml +2164 -0
  452. package/web/ui/scripts/verify-base.mjs +31 -0
  453. package/web/ui/src/App.tsx +88 -0
  454. package/web/ui/src/components/ActivityFeed.tsx +444 -0
  455. package/web/ui/src/components/AgentGroupPicker.tsx +263 -0
  456. package/web/ui/src/components/AgentProviderCards.tsx +220 -0
  457. package/web/ui/src/components/CredentialForm.tsx +214 -0
  458. package/web/ui/src/components/ScopeGrants.tsx +74 -0
  459. package/web/ui/src/components/StatusDot.tsx +43 -0
  460. package/web/ui/src/components/VaultPicker.tsx +127 -0
  461. package/web/ui/src/components/setup/AdapterInstallStep.tsx +178 -0
  462. package/web/ui/src/components/setup/AgentGroupStep.tsx +43 -0
  463. package/web/ui/src/components/setup/ChannelPickStep.tsx +74 -0
  464. package/web/ui/src/components/setup/DoneStep.tsx +49 -0
  465. package/web/ui/src/components/setup/PrereqStep.tsx +129 -0
  466. package/web/ui/src/components/setup/TestConnectionStep.tsx +108 -0
  467. package/web/ui/src/components/setup/TestMessageStep.tsx +104 -0
  468. package/web/ui/src/components/setup/WireChannelStep.tsx +166 -0
  469. package/web/ui/src/components/setup/types.ts +105 -0
  470. package/web/ui/src/lib/api.test.ts +410 -0
  471. package/web/ui/src/lib/api.ts +1210 -0
  472. package/web/ui/src/lib/auth.test.ts +139 -0
  473. package/web/ui/src/lib/auth.ts +348 -0
  474. package/web/ui/src/lib/channel-adapters.ts +136 -0
  475. package/web/ui/src/main.tsx +19 -0
  476. package/web/ui/src/routes/ApprovalsList.tsx +294 -0
  477. package/web/ui/src/routes/Apps.tsx +613 -0
  478. package/web/ui/src/routes/ChannelWireDetail.test.tsx +233 -0
  479. package/web/ui/src/routes/ChannelWireDetail.tsx +403 -0
  480. package/web/ui/src/routes/ChannelsList.tsx +158 -0
  481. package/web/ui/src/routes/GroupDetail.tsx +755 -0
  482. package/web/ui/src/routes/GroupList.tsx +187 -0
  483. package/web/ui/src/routes/MessagingGroupDetail.test.tsx +233 -0
  484. package/web/ui/src/routes/MessagingGroupDetail.tsx +306 -0
  485. package/web/ui/src/routes/NewGroupWizard.tsx +390 -0
  486. package/web/ui/src/routes/OAuthCallback.tsx +56 -0
  487. package/web/ui/src/routes/SecretsList.tsx +921 -0
  488. package/web/ui/src/routes/SessionsList.tsx +220 -0
  489. package/web/ui/src/routes/SettingsAgentProvider.tsx +109 -0
  490. package/web/ui/src/routes/SettingsApprovals.tsx +234 -0
  491. package/web/ui/src/routes/SetupWizard.tsx +219 -0
  492. package/web/ui/src/routes/VaultDetail.test.tsx +361 -0
  493. package/web/ui/src/routes/VaultDetail.tsx +960 -0
  494. package/web/ui/src/routes/VaultsList.tsx +295 -0
  495. package/web/ui/src/routes/WireChannelPage.tsx +413 -0
  496. package/web/ui/src/styles.css +608 -0
  497. package/web/ui/src/test/setup.ts +23 -0
  498. package/web/ui/src/vite-env.d.ts +10 -0
  499. package/web/ui/tsconfig.json +20 -0
  500. package/web/ui/vite.config.ts +34 -0
  501. package/web/ui/vitest.config.ts +25 -0
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Core MCP tools: send_message, send_file, edit_message, add_reaction.
3
+ *
4
+ * All outbound tools resolve destinations via the local destination map
5
+ * (see destinations.ts). Agents reference destinations by name; the map
6
+ * translates name → routing tuple. Permission enforcement happens on
7
+ * the host side in delivery.ts via the agent_destinations table.
8
+ */
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+
12
+ import { findByName, getAllDestinations } from '../destinations.js';
13
+ import { getMessageIdBySeq, getRoutingBySeq, writeMessageOut } from '../db/messages-out.js';
14
+ import { getSessionRouting } from '../db/session-routing.js';
15
+ import { registerTools } from './server.js';
16
+ import type { McpToolDefinition } from './types.js';
17
+
18
+ function log(msg: string): void {
19
+ console.error(`[mcp-tools] ${msg}`);
20
+ }
21
+
22
+ function generateId(): string {
23
+ return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
24
+ }
25
+
26
+ function ok(text: string) {
27
+ return { content: [{ type: 'text' as const, text }] };
28
+ }
29
+
30
+ function err(text: string) {
31
+ return { content: [{ type: 'text' as const, text: `Error: ${text}` }], isError: true };
32
+ }
33
+
34
+ function destinationList(): string {
35
+ const all = getAllDestinations();
36
+ if (all.length === 0) return '(none)';
37
+ return all.map((d) => d.name).join(', ');
38
+ }
39
+
40
+ /**
41
+ * Resolve a destination name to routing fields.
42
+ *
43
+ * If `to` is omitted, use the session's default reply routing (channel +
44
+ * thread the conversation is in) — the agent replies in place.
45
+ *
46
+ * If `to` is specified, look up the named destination. If it resolves to
47
+ * the same channel the session is bound to, the session's thread_id is
48
+ * preserved so replies land in the correct thread. Otherwise thread_id
49
+ * is null (a cross-destination send starts a new conversation).
50
+ */
51
+ function resolveRouting(
52
+ to: string | undefined,
53
+ ):
54
+ | { channel_type: string; platform_id: string; thread_id: string | null; resolvedName: string }
55
+ | { error: string } {
56
+ if (!to) {
57
+ // Default: reply to whatever thread/channel this session is bound to.
58
+ const session = getSessionRouting();
59
+ if (session.channel_type && session.platform_id) {
60
+ return {
61
+ channel_type: session.channel_type,
62
+ platform_id: session.platform_id,
63
+ thread_id: session.thread_id,
64
+ resolvedName: '(current conversation)',
65
+ };
66
+ }
67
+ // No session routing (e.g., agent-shared or internal-only agent) —
68
+ // fall back to the legacy single-destination shortcut.
69
+ const all = getAllDestinations();
70
+ if (all.length === 0) return { error: 'No destinations configured.' };
71
+ if (all.length > 1) {
72
+ return {
73
+ error: `You have multiple destinations — specify "to". Options: ${all.map((d) => d.name).join(', ')}`,
74
+ };
75
+ }
76
+ to = all[0].name;
77
+ }
78
+ const dest = findByName(to);
79
+ if (!dest) return { error: `Unknown destination "${to}". Known: ${destinationList()}` };
80
+ if (dest.type === 'channel') {
81
+ // If the destination is the same channel the session is bound to,
82
+ // preserve the thread_id so replies land in the correct thread.
83
+ const session = getSessionRouting();
84
+ const threadId =
85
+ session.channel_type === dest.channelType && session.platform_id === dest.platformId
86
+ ? session.thread_id
87
+ : null;
88
+ return {
89
+ channel_type: dest.channelType!,
90
+ platform_id: dest.platformId!,
91
+ thread_id: threadId,
92
+ resolvedName: to,
93
+ };
94
+ }
95
+ return { channel_type: 'agent', platform_id: dest.agentGroupId!, thread_id: null, resolvedName: to };
96
+ }
97
+
98
+ export const sendMessage: McpToolDefinition = {
99
+ tool: {
100
+ name: 'send_message',
101
+ description:
102
+ 'Send a message to a named destination. If you have only one destination, you can omit `to`.',
103
+ inputSchema: {
104
+ type: 'object' as const,
105
+ properties: {
106
+ to: { type: 'string', description: 'Destination name (e.g., "family", "worker-1"). Optional if you have only one destination.' },
107
+ text: { type: 'string', description: 'Message content' },
108
+ },
109
+ required: ['text'],
110
+ },
111
+ },
112
+ async handler(args) {
113
+ const text = args.text as string;
114
+ if (!text) return err('text is required');
115
+
116
+ const routing = resolveRouting(args.to as string | undefined);
117
+ if ('error' in routing) return err(routing.error);
118
+
119
+ const id = generateId();
120
+ const seq = writeMessageOut({
121
+ id,
122
+ kind: 'chat',
123
+ platform_id: routing.platform_id,
124
+ channel_type: routing.channel_type,
125
+ thread_id: routing.thread_id,
126
+ content: JSON.stringify({ text }),
127
+ });
128
+
129
+ log(`send_message: #${seq} → ${routing.resolvedName}`);
130
+ return ok(`Message sent to ${routing.resolvedName} (id: ${seq})`);
131
+ },
132
+ };
133
+
134
+ export const sendFile: McpToolDefinition = {
135
+ tool: {
136
+ name: 'send_file',
137
+ description: 'Send a file to a named destination. If you have only one destination, you can omit `to`.',
138
+ inputSchema: {
139
+ type: 'object' as const,
140
+ properties: {
141
+ to: { type: 'string', description: 'Destination name. Optional if you have only one destination.' },
142
+ path: { type: 'string', description: 'File path (relative to /workspace/agent/ or absolute)' },
143
+ text: { type: 'string', description: 'Optional accompanying message' },
144
+ filename: { type: 'string', description: 'Display name (default: basename of path)' },
145
+ },
146
+ required: ['path'],
147
+ },
148
+ },
149
+ async handler(args) {
150
+ const filePath = args.path as string;
151
+ if (!filePath) return err('path is required');
152
+
153
+ const routing = resolveRouting(args.to as string | undefined);
154
+ if ('error' in routing) return err(routing.error);
155
+
156
+ const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve('/workspace/agent', filePath);
157
+ if (!fs.existsSync(resolvedPath)) return err(`File not found: ${filePath}`);
158
+
159
+ const id = generateId();
160
+ const filename = (args.filename as string) || path.basename(resolvedPath);
161
+
162
+ const outboxDir = path.join('/workspace/outbox', id);
163
+ fs.mkdirSync(outboxDir, { recursive: true });
164
+ fs.copyFileSync(resolvedPath, path.join(outboxDir, filename));
165
+
166
+ writeMessageOut({
167
+ id,
168
+ kind: 'chat',
169
+ platform_id: routing.platform_id,
170
+ channel_type: routing.channel_type,
171
+ thread_id: routing.thread_id,
172
+ content: JSON.stringify({ text: (args.text as string) || '', files: [filename] }),
173
+ });
174
+
175
+ log(`send_file: ${id} → ${routing.resolvedName} (${filename})`);
176
+ return ok(`File sent to ${routing.resolvedName} (id: ${id}, filename: ${filename})`);
177
+ },
178
+ };
179
+
180
+ export const editMessage: McpToolDefinition = {
181
+ tool: {
182
+ name: 'edit_message',
183
+ description: 'Edit a previously sent message. Targets the same destination the original message was sent to.',
184
+ inputSchema: {
185
+ type: 'object' as const,
186
+ properties: {
187
+ messageId: { type: 'integer', description: 'Message ID (the numeric id shown in messages)' },
188
+ text: { type: 'string', description: 'New message content' },
189
+ },
190
+ required: ['messageId', 'text'],
191
+ },
192
+ },
193
+ async handler(args) {
194
+ const seq = Number(args.messageId);
195
+ const text = args.text as string;
196
+ if (!seq || !text) return err('messageId and text are required');
197
+
198
+ const platformId = getMessageIdBySeq(seq);
199
+ if (!platformId) return err(`Message #${seq} not found`);
200
+
201
+ const routing = getRoutingBySeq(seq);
202
+ if (!routing || !routing.channel_type || !routing.platform_id) {
203
+ return err(`Cannot determine destination for message #${seq}`);
204
+ }
205
+
206
+ const id = generateId();
207
+ writeMessageOut({
208
+ id,
209
+ kind: 'chat',
210
+ platform_id: routing.platform_id,
211
+ channel_type: routing.channel_type,
212
+ thread_id: routing.thread_id,
213
+ content: JSON.stringify({ operation: 'edit', messageId: platformId, text }),
214
+ });
215
+
216
+ log(`edit_message: #${seq} → ${platformId}`);
217
+ return ok(`Message edit queued for #${seq}`);
218
+ },
219
+ };
220
+
221
+ export const addReaction: McpToolDefinition = {
222
+ tool: {
223
+ name: 'add_reaction',
224
+ description: 'Add an emoji reaction to a message.',
225
+ inputSchema: {
226
+ type: 'object' as const,
227
+ properties: {
228
+ messageId: { type: 'integer', description: 'Message ID (the numeric id shown in messages)' },
229
+ emoji: { type: 'string', description: 'Emoji name (e.g., thumbs_up, heart, check)' },
230
+ },
231
+ required: ['messageId', 'emoji'],
232
+ },
233
+ },
234
+ async handler(args) {
235
+ const seq = Number(args.messageId);
236
+ const emoji = args.emoji as string;
237
+ if (!seq || !emoji) return err('messageId and emoji are required');
238
+
239
+ const platformId = getMessageIdBySeq(seq);
240
+ if (!platformId) return err(`Message #${seq} not found`);
241
+
242
+ const routing = getRoutingBySeq(seq);
243
+ if (!routing || !routing.channel_type || !routing.platform_id) {
244
+ return err(`Cannot determine destination for message #${seq}`);
245
+ }
246
+
247
+ const id = generateId();
248
+ writeMessageOut({
249
+ id,
250
+ kind: 'chat',
251
+ platform_id: routing.platform_id,
252
+ channel_type: routing.channel_type,
253
+ thread_id: routing.thread_id,
254
+ content: JSON.stringify({ operation: 'reaction', messageId: platformId, emoji }),
255
+ });
256
+
257
+ log(`add_reaction: #${seq} → ${emoji} on ${platformId}`);
258
+ return ok(`Reaction queued for #${seq}`);
259
+ },
260
+ };
261
+
262
+ registerTools([sendMessage, sendFile, editMessage, addReaction]);
@@ -0,0 +1,22 @@
1
+ /**
2
+ * MCP tools barrel — imports each tool module for its side-effect
3
+ * `registerTools([...])` call, then starts the MCP server.
4
+ *
5
+ * Adding a new tool module: create the file, call `registerTools([...])`
6
+ * at module scope, and append the import here. No central list.
7
+ */
8
+ import './core.js';
9
+ import './scheduling.js';
10
+ import './interactive.js';
11
+ import './agents.js';
12
+ import './self-mod.js';
13
+ import { startMcpServer } from './server.js';
14
+
15
+ function log(msg: string): void {
16
+ console.error(`[mcp-tools] ${msg}`);
17
+ }
18
+
19
+ startMcpServer().catch((err) => {
20
+ log(`MCP server error: ${err instanceof Error ? err.message : String(err)}`);
21
+ process.exit(1);
22
+ });
@@ -0,0 +1,22 @@
1
+ ## Interactive prompts
2
+
3
+ The two tools here solve different problems: `ask_user_question` forces a decision and waits for it; `send_card` displays structured content and moves on.
4
+
5
+ ### Asking a multiple-choice question (`ask_user_question`)
6
+
7
+ `mcp__parachute_agent__ask_user_question({ title, question, options, timeout? })` presents the user with a set of choices and **blocks your turn** until they tap one or the timeout expires (default: 300 seconds). Returns their chosen value.
8
+
9
+ `options` can be plain strings or `{ label, selectedLabel?, value? }` objects:
10
+ - `label` — the button text shown before selection
11
+ - `selectedLabel` — the text shown on the button *after* selection (useful for confirmations, e.g. `"✓ Confirmed"`)
12
+ - `value` — the string returned to you when that option is chosen (defaults to `label`)
13
+
14
+ Use this when you genuinely cannot proceed without a decision. For free-text input, send a normal message and wait for their reply — don't reach for this tool.
15
+
16
+ ### Structured cards (`send_card`)
17
+
18
+ `mcp__parachute_agent__send_card({ card, fallbackText? })` renders a structured card and **returns immediately** — it does not pause your turn or collect a response.
19
+
20
+ `card` supports: `title`, `description`, `children` (nested text or content blocks), and `actions` (buttons). `fallbackText` is sent as a plain message on platforms without card support.
21
+
22
+ Use this for presenting information in a cleaner format than prose: summaries, options the user can read (but you're not waiting on), or results with contextual buttons. If you need the user to actually *choose* something and return a value, use `ask_user_question` instead.
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Interactive MCP tools: ask_user_question, send_card.
3
+ *
4
+ * ask_user_question is a blocking tool call — it writes a messages_out row
5
+ * with a question card, then polls messages_in for the response.
6
+ */
7
+ import { findQuestionResponse, markCompleted } from '../db/messages-in.js';
8
+ import { writeMessageOut } from '../db/messages-out.js';
9
+ import { getSessionRouting } from '../db/session-routing.js';
10
+ import { registerTools } from './server.js';
11
+ import type { McpToolDefinition } from './types.js';
12
+
13
+ function log(msg: string): void {
14
+ console.error(`[mcp-tools] ${msg}`);
15
+ }
16
+
17
+ function generateId(): string {
18
+ return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
19
+ }
20
+
21
+ function routing() {
22
+ return getSessionRouting();
23
+ }
24
+
25
+ function ok(text: string) {
26
+ return { content: [{ type: 'text' as const, text }] };
27
+ }
28
+
29
+ function err(text: string) {
30
+ return { content: [{ type: 'text' as const, text: `Error: ${text}` }], isError: true };
31
+ }
32
+
33
+ function sleep(ms: number): Promise<void> {
34
+ return new Promise((resolve) => setTimeout(resolve, ms));
35
+ }
36
+
37
+ export const askUserQuestion: McpToolDefinition = {
38
+ tool: {
39
+ name: 'ask_user_question',
40
+ description:
41
+ 'Ask the user a multiple-choice question and wait for their response. This is a blocking call — execution pauses until the user responds or the timeout expires. Provide a short card title (e.g. "Confirm deletion") and an array of options — each option may be a plain string (used as both button label and result value) or an object { label, selectedLabel?, value? } where selectedLabel is the text shown on the card after the user clicks.',
42
+ inputSchema: {
43
+ type: 'object' as const,
44
+ properties: {
45
+ title: { type: 'string', description: 'Short card title shown above the question' },
46
+ question: { type: 'string', description: 'The question to ask' },
47
+ options: {
48
+ type: 'array',
49
+ items: {
50
+ oneOf: [
51
+ { type: 'string' },
52
+ {
53
+ type: 'object',
54
+ properties: {
55
+ label: { type: 'string' },
56
+ selectedLabel: { type: 'string' },
57
+ value: { type: 'string' },
58
+ },
59
+ required: ['label'],
60
+ },
61
+ ],
62
+ },
63
+ description: 'Options for the user to choose from (string or {label, selectedLabel?, value?})',
64
+ },
65
+ timeout: { type: 'number', description: 'Timeout in seconds (default: 300)' },
66
+ },
67
+ required: ['title', 'question', 'options'],
68
+ },
69
+ },
70
+ async handler(args) {
71
+ const title = args.title as string;
72
+ const question = args.question as string;
73
+ const rawOptions = args.options as unknown[];
74
+ const timeout = ((args.timeout as number) || 300) * 1000;
75
+ if (!title || !question || !rawOptions?.length) {
76
+ return err('title, question, and options are required');
77
+ }
78
+
79
+ const options = rawOptions.map((o) => {
80
+ if (typeof o === 'string') return { label: o, selectedLabel: o, value: o };
81
+ const obj = o as { label: string; selectedLabel?: string; value?: string };
82
+ return {
83
+ label: obj.label,
84
+ selectedLabel: obj.selectedLabel ?? obj.label,
85
+ value: obj.value ?? obj.label,
86
+ };
87
+ });
88
+
89
+ const questionId = generateId();
90
+ const r = routing();
91
+
92
+ // Write question card to outbound.db
93
+ writeMessageOut({
94
+ id: questionId,
95
+ kind: 'chat-sdk',
96
+ platform_id: r.platform_id,
97
+ channel_type: r.channel_type,
98
+ thread_id: r.thread_id,
99
+ content: JSON.stringify({
100
+ type: 'ask_question',
101
+ questionId,
102
+ title,
103
+ question,
104
+ options,
105
+ }),
106
+ });
107
+
108
+ log(`ask_user_question: ${questionId} → "${question}" [${options.join(', ')}]`);
109
+
110
+ // Poll for response in inbound.db (host writes the response there)
111
+ const deadline = Date.now() + timeout;
112
+ while (Date.now() < deadline) {
113
+ const response = findQuestionResponse(questionId);
114
+
115
+ if (response) {
116
+ const parsed = JSON.parse(response.content);
117
+ // Mark the response as completed via processing_ack (outbound.db)
118
+ markCompleted([response.id]);
119
+
120
+ log(`ask_user_question response: ${questionId} → ${parsed.selectedOption}`);
121
+ return ok(parsed.selectedOption);
122
+ }
123
+
124
+ await sleep(1000);
125
+ }
126
+
127
+ log(`ask_user_question timeout: ${questionId}`);
128
+ return err(`Question timed out after ${timeout / 1000}s`);
129
+ },
130
+ };
131
+
132
+ export const sendCard: McpToolDefinition = {
133
+ tool: {
134
+ name: 'send_card',
135
+ description: 'Send a structured card (interactive or display-only) to the current conversation.',
136
+ inputSchema: {
137
+ type: 'object' as const,
138
+ properties: {
139
+ card: {
140
+ type: 'object',
141
+ description: 'Card structure with title, description, and optional children/actions',
142
+ },
143
+ fallbackText: { type: 'string', description: 'Text fallback for platforms without card support' },
144
+ },
145
+ required: ['card'],
146
+ },
147
+ },
148
+ async handler(args) {
149
+ const card = args.card as Record<string, unknown>;
150
+ if (!card) return err('card is required');
151
+
152
+ const id = generateId();
153
+ const r = routing();
154
+
155
+ writeMessageOut({
156
+ id,
157
+ kind: 'chat-sdk',
158
+ platform_id: r.platform_id,
159
+ channel_type: r.channel_type,
160
+ thread_id: r.thread_id,
161
+ content: JSON.stringify({ type: 'card', card, fallbackText: (args.fallbackText as string) || '' }),
162
+ });
163
+
164
+ log(`send_card: ${id}`);
165
+ return ok(`Card sent (id: ${id})`);
166
+ },
167
+ };
168
+
169
+ registerTools([askUserQuestion, sendCard]);
@@ -0,0 +1,40 @@
1
+ ## Task scheduling (`schedule_task`)
2
+
3
+ For any recurring task, use `schedule_task`. This is the scheduling path — tasks persist across sessions and restarts, and support the pre-task `script` hook described below.
4
+
5
+ To inspect or change existing tasks, use `list_tasks` (returns one row per series with the stable id) and `update_task` / `cancel_task` / `pause_task` / `resume_task`. Prefer `update_task` over cancel + reschedule.
6
+
7
+ Frequent recurring scheduled tasks — more than a few times a day — consume API credits and can risk account restrictions. You can add a `script` that runs first, and you will only be called when the check passes.
8
+
9
+ ### How it works
10
+
11
+ 1. Provide a bash `script` alongside the `prompt` when scheduling
12
+ 2. When the task fires, the script runs first
13
+ 3. Script returns: `{ "wakeAgent": true/false, "data": {...} }`
14
+ 4. If `wakeAgent: false` — nothing happens, task waits for next run
15
+ 5. If `wakeAgent: true` — claude receives the script's data + prompt and handles
16
+
17
+ ### Always test your script first
18
+
19
+ Before scheduling, run the script directly to verify it works:
20
+
21
+ ```bash
22
+ bash -c 'node --input-type=module -e "
23
+ const r = await fetch(\"https://api.github.com/repos/owner/repo/pulls?state=open\");
24
+ const prs = await r.json();
25
+ console.log(JSON.stringify({ wakeAgent: prs.length > 0, data: prs.slice(0, 5) }));
26
+ "'
27
+ ```
28
+
29
+ ### When NOT to use scripts
30
+
31
+ If a task requires your judgment every time (daily briefings, reminders, reports), skip the script — just use a regular prompt. Do not attempt to do things like sentiment analysis or advanced nlp in scripts.
32
+
33
+ ### Frequent task guidance
34
+
35
+ If a user wants a task to run more than a few times a day and a script can't be used:
36
+
37
+ - Explain that each time the task fires it uses API credits and risks rate limits
38
+ - Suggest adjusting the task requirements in a way that will allow you to use a script
39
+ - If the user needs an LLM to evaluate data, suggest using an API key with direct Anthropic API calls inside the script
40
+ - Help the user find the minimum viable frequency