@vellumai/assistant 0.7.1 → 0.7.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 (535) hide show
  1. package/ARCHITECTURE.md +32 -49
  2. package/Dockerfile +1 -0
  3. package/README.md +1 -2
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +9 -3
  5. package/bun.lock +26 -26
  6. package/docs/architecture/security.md +20 -0
  7. package/docs/plugins.md +7 -9
  8. package/knip.json +1 -0
  9. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
  10. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +39 -1
  11. package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
  12. package/node_modules/@vellumai/service-contracts/package.json +2 -0
  13. package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
  14. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
  15. package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
  16. package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
  17. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
  18. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +9 -0
  19. package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
  20. package/node_modules/@vellumai/twilio-client/package.json +18 -0
  21. package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
  22. package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
  23. package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
  24. package/openapi.yaml +565 -12
  25. package/package.json +6 -3
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
  27. package/src/__tests__/app-bundler.test.ts +170 -1
  28. package/src/__tests__/app-control-flow.test.ts +374 -0
  29. package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
  30. package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
  31. package/src/__tests__/app-executors.test.ts +30 -43
  32. package/src/__tests__/approval-routes-http.test.ts +23 -6
  33. package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
  34. package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
  35. package/src/__tests__/assistant-event-hub.test.ts +109 -2
  36. package/src/__tests__/assistant-event.test.ts +10 -0
  37. package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -2
  38. package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -7
  39. package/src/__tests__/background-shell-host-bash.test.ts +14 -15
  40. package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
  41. package/src/__tests__/btw-routes.test.ts +13 -4
  42. package/src/__tests__/call-controller.test.ts +49 -1
  43. package/src/__tests__/call-domain.test.ts +0 -2
  44. package/src/__tests__/call-routes-http.test.ts +0 -2
  45. package/src/__tests__/channel-readiness-service.test.ts +59 -1
  46. package/src/__tests__/checker.test.ts +3 -4
  47. package/src/__tests__/config-loader-backfill.test.ts +90 -155
  48. package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
  49. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  50. package/src/__tests__/config-set-platform-guard.test.ts +48 -4
  51. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +2 -2
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
  54. package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
  55. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  56. package/src/__tests__/conversation-lifecycle.test.ts +36 -0
  57. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
  58. package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
  59. package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
  60. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  61. package/src/__tests__/conversation-slash-commands.test.ts +0 -4
  62. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +317 -0
  64. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
  65. package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
  66. package/src/__tests__/credentials-cli.test.ts +5 -12
  67. package/src/__tests__/cu-unified-flow.test.ts +185 -23
  68. package/src/__tests__/daemon-credential-client.test.ts +101 -19
  69. package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
  70. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  71. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  72. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
  73. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
  74. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
  75. package/src/__tests__/heartbeat-service.test.ts +718 -1
  76. package/src/__tests__/helpers/call-route-handler.ts +7 -1
  77. package/src/__tests__/host-app-control-proxy.test.ts +602 -0
  78. package/src/__tests__/host-app-control-routes.test.ts +263 -0
  79. package/src/__tests__/host-bash-proxy.test.ts +246 -47
  80. package/src/__tests__/host-bash-routes.test.ts +294 -0
  81. package/src/__tests__/host-browser-proxy.test.ts +24 -22
  82. package/src/__tests__/host-browser-routes.test.ts +39 -13
  83. package/src/__tests__/host-cu-proxy.test.ts +41 -52
  84. package/src/__tests__/host-cu-routes-targeted.test.ts +300 -0
  85. package/src/__tests__/host-file-edit-tool.test.ts +47 -1
  86. package/src/__tests__/host-file-proxy-targeted.test.ts +339 -0
  87. package/src/__tests__/host-file-proxy.test.ts +37 -43
  88. package/src/__tests__/host-file-read-tool.test.ts +17 -0
  89. package/src/__tests__/host-file-routes-targeted.test.ts +262 -0
  90. package/src/__tests__/host-file-write-tool.test.ts +42 -1
  91. package/src/__tests__/host-proxy-base.test.ts +312 -0
  92. package/src/__tests__/host-shell-tool.test.ts +22 -4
  93. package/src/__tests__/host-transfer-proxy-targeted.test.ts +583 -0
  94. package/src/__tests__/host-transfer-proxy.test.ts +121 -22
  95. package/src/__tests__/host-transfer-routes-targeted.test.ts +447 -0
  96. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  97. package/src/__tests__/identity-intro-cache.test.ts +29 -0
  98. package/src/__tests__/identity-routes.test.ts +103 -1
  99. package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
  100. package/src/__tests__/inline-command-runner.test.ts +0 -1
  101. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
  102. package/src/__tests__/integration-status.test.ts +85 -5
  103. package/src/__tests__/intent-routing.test.ts +0 -1
  104. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
  105. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
  106. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
  107. package/src/__tests__/mcp-auth-routes.test.ts +197 -0
  108. package/src/__tests__/mcp-cli.test.ts +338 -2
  109. package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
  110. package/src/__tests__/migration-import-commit-http.test.ts +108 -2
  111. package/src/__tests__/mock-gateway-ipc.ts +1 -0
  112. package/src/__tests__/oauth-cli.test.ts +0 -2
  113. package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
  114. package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
  115. package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
  116. package/src/__tests__/prechat-onboarding-contract.test.ts +3 -1
  117. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  118. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  119. package/src/__tests__/public-ingress-urls.test.ts +97 -0
  120. package/src/__tests__/require-fresh-approval.test.ts +0 -1
  121. package/src/__tests__/retry-backoff.test.ts +87 -0
  122. package/src/__tests__/runtime-events-sse.test.ts +10 -6
  123. package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
  124. package/src/__tests__/schedule-retry.test.ts +715 -0
  125. package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
  126. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  127. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  128. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
  129. package/src/__tests__/skill-feature-flags.test.ts +43 -41
  130. package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
  131. package/src/__tests__/skill-load-inline-command.test.ts +0 -51
  132. package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
  133. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  134. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
  135. package/src/__tests__/slack-channel-config.test.ts +9 -14
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
  137. package/src/__tests__/system-prompt.test.ts +0 -1
  138. package/src/__tests__/telegram-config.test.ts +0 -1
  139. package/src/__tests__/test-preload.ts +8 -0
  140. package/src/__tests__/tool-approval-handler.test.ts +3 -4
  141. package/src/__tests__/tool-audit-listener.test.ts +48 -0
  142. package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
  143. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  145. package/src/__tests__/tool-executor.test.ts +0 -1
  146. package/src/__tests__/twilio-config.test.ts +3 -16
  147. package/src/__tests__/twilio-routes.test.ts +3 -5
  148. package/src/__tests__/twilio-validation.test.ts +93 -0
  149. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
  150. package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
  151. package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
  152. package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
  153. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
  154. package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
  155. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
  156. package/src/backup/__tests__/paths.test.ts +0 -22
  157. package/src/backup/__tests__/restore.test.ts +51 -151
  158. package/src/backup/paths.ts +2 -18
  159. package/src/backup/restore.ts +107 -231
  160. package/src/bundler/app-bundler.ts +51 -3
  161. package/src/calls/relay-server.ts +4 -44
  162. package/src/calls/twilio-config.ts +2 -17
  163. package/src/calls/twilio-rest.ts +33 -105
  164. package/src/calls/twilio-routes.ts +11 -12
  165. package/src/channels/types.ts +8 -7
  166. package/src/cli/commands/__tests__/backup.test.ts +6 -277
  167. package/src/cli/commands/__tests__/gateway.test.ts +288 -0
  168. package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
  169. package/src/cli/commands/__tests__/webhooks.test.ts +0 -1
  170. package/src/cli/commands/backup.ts +6 -331
  171. package/src/cli/commands/clients.ts +36 -37
  172. package/src/cli/commands/contacts.ts +73 -0
  173. package/src/cli/commands/conversations.ts +2 -5
  174. package/src/cli/commands/credentials.ts +15 -7
  175. package/src/cli/commands/domain.ts +66 -15
  176. package/src/cli/commands/gateway.ts +183 -0
  177. package/src/cli/commands/keys.ts +9 -6
  178. package/src/cli/commands/mcp.ts +116 -156
  179. package/src/cli/commands/memory-v2.ts +296 -1
  180. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  181. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -2
  182. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -2
  183. package/src/cli/commands/platform/__tests__/status.test.ts +13 -15
  184. package/src/cli/commands/platform/disconnect.ts +5 -4
  185. package/src/cli/commands/platform/index.ts +0 -18
  186. package/src/cli/lib/daemon-credential-client.ts +110 -28
  187. package/src/cli/program.ts +2 -0
  188. package/src/config/assistant-feature-flags.ts +67 -10
  189. package/src/config/bundled-skills/acp/SKILL.md +6 -0
  190. package/src/config/bundled-skills/acp/TOOLS.json +1 -22
  191. package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
  192. package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
  193. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
  194. package/src/config/bundled-skills/app-control/SKILL.md +75 -0
  195. package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
  196. package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
  197. package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
  198. package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
  199. package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
  200. package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
  201. package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
  202. package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
  203. package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
  204. package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
  205. package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
  206. package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
  207. package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
  208. package/src/config/bundled-skills/document/TOOLS.json +0 -8
  209. package/src/config/bundled-skills/followups/TOOLS.json +0 -12
  210. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  211. package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
  212. package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
  213. package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
  214. package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
  215. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +19 -4
  216. package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
  217. package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
  218. package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
  219. package/src/config/bundled-skills/settings/SKILL.md +4 -0
  220. package/src/config/bundled-skills/settings/TOOLS.json +0 -12
  221. package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
  222. package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
  223. package/src/config/bundled-skills/subagent/SKILL.md +6 -2
  224. package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
  225. package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
  226. package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
  227. package/src/config/bundled-tool-registry.ts +21 -0
  228. package/src/config/env-registry.ts +0 -2
  229. package/src/config/env.ts +19 -12
  230. package/src/config/feature-flag-registry.json +21 -133
  231. package/src/config/loader.ts +73 -99
  232. package/src/config/sanitize-for-transfer.ts +2 -0
  233. package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
  234. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -4
  235. package/src/config/schemas/calls.ts +0 -9
  236. package/src/config/schemas/heartbeat.ts +63 -0
  237. package/src/config/schemas/ingress.ts +10 -6
  238. package/src/config/schemas/llm.ts +5 -10
  239. package/src/config/schemas/memory-lifecycle.ts +77 -24
  240. package/src/config/schemas/memory-v2.ts +48 -4
  241. package/src/config/schemas/platform.ts +6 -0
  242. package/src/config/schemas/services.ts +1 -15
  243. package/src/config/schemas/skills.ts +0 -6
  244. package/src/config/seed-inference-profiles.ts +1 -1
  245. package/src/contacts/contact-store.ts +0 -30
  246. package/src/contacts/contacts-write.ts +0 -27
  247. package/src/context/window-manager.ts +1 -2
  248. package/src/credential-execution/feature-gates.ts +10 -10
  249. package/src/credential-execution/process-manager.ts +12 -41
  250. package/src/daemon/__tests__/conversation-tool-setup.test.ts +126 -5
  251. package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
  252. package/src/daemon/config-watcher.ts +4 -3
  253. package/src/daemon/conversation-agent-loop-handlers.ts +21 -3
  254. package/src/daemon/conversation-agent-loop.ts +32 -28
  255. package/src/daemon/conversation-lifecycle.ts +8 -1
  256. package/src/daemon/conversation-process.ts +16 -11
  257. package/src/daemon/conversation-runtime-assembly.ts +2 -2
  258. package/src/daemon/conversation-surfaces.ts +125 -4
  259. package/src/daemon/conversation-tool-setup.ts +16 -55
  260. package/src/daemon/conversation.ts +21 -2
  261. package/src/daemon/doordash-steps.ts +1 -1
  262. package/src/daemon/handlers/shared.ts +4 -1
  263. package/src/daemon/host-app-control-proxy.ts +293 -0
  264. package/src/daemon/host-bash-proxy.ts +84 -74
  265. package/src/daemon/host-browser-proxy.ts +67 -82
  266. package/src/daemon/host-cu-proxy.ts +81 -86
  267. package/src/daemon/host-file-proxy.ts +93 -69
  268. package/src/daemon/host-proxy-base.ts +294 -0
  269. package/src/daemon/host-proxy-preactivation.ts +82 -0
  270. package/src/daemon/host-transfer-proxy.ts +247 -129
  271. package/src/daemon/lifecycle.ts +115 -117
  272. package/src/daemon/message-protocol.ts +3 -8
  273. package/src/daemon/message-types/contacts.ts +23 -1
  274. package/src/daemon/message-types/conversations.ts +11 -8
  275. package/src/daemon/message-types/host-app-control.ts +150 -0
  276. package/src/daemon/message-types/host-bash.ts +4 -0
  277. package/src/daemon/message-types/host-cu.ts +2 -0
  278. package/src/daemon/message-types/host-file.ts +4 -0
  279. package/src/daemon/message-types/host-transfer.ts +3 -0
  280. package/src/daemon/message-types/schedules.ts +8 -3
  281. package/src/daemon/message-types/skills.ts +2 -2
  282. package/src/daemon/process-message.ts +18 -1
  283. package/src/daemon/shutdown-handlers.ts +0 -3
  284. package/src/daemon/tool-setup-types.ts +51 -0
  285. package/src/daemon/tool-side-effects.ts +1 -1
  286. package/src/events/tool-audit-listener.ts +2 -1
  287. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +15 -7
  288. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +216 -0
  289. package/src/heartbeat/heartbeat-run-store.ts +236 -0
  290. package/src/heartbeat/heartbeat-service.ts +280 -49
  291. package/src/home/__tests__/post-connect-feed.test.ts +99 -0
  292. package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
  293. package/src/home/__tests__/suggested-prompts.test.ts +89 -0
  294. package/src/home/post-connect-feed.ts +68 -0
  295. package/src/home/relationship-state-writer.ts +17 -92
  296. package/src/home/suggested-prompts.ts +46 -10
  297. package/src/inbound/public-ingress-urls.ts +32 -34
  298. package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
  299. package/src/ipc/assistant-server.ts +14 -1
  300. package/src/ipc/cli-client.ts +32 -1
  301. package/src/live-voice/live-voice-metrics.ts +10 -10
  302. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
  303. package/src/mcp/mcp-auth-orchestrator.ts +213 -0
  304. package/src/mcp/mcp-auth-state.ts +133 -0
  305. package/src/mcp/mcp-oauth-provider.ts +19 -0
  306. package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
  307. package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
  308. package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
  309. package/src/memory/anisotropy.test.ts +247 -0
  310. package/src/memory/anisotropy.ts +443 -0
  311. package/src/memory/auto-analysis-constants.ts +17 -0
  312. package/src/memory/auto-analysis-guard.ts +5 -15
  313. package/src/memory/canonical-guardian-store.ts +7 -7
  314. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
  315. package/src/memory/context-search/agent-protocol.ts +6 -6
  316. package/src/memory/context-search/agent-runner.ts +32 -7
  317. package/src/memory/context-search/sources/memory-v2.ts +17 -5
  318. package/src/memory/conversation-crud.ts +1 -1
  319. package/src/memory/conversation-key-store.ts +2 -15
  320. package/src/memory/db-init.ts +4 -0
  321. package/src/memory/embedding-backend.ts +9 -21
  322. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +49 -4
  323. package/src/memory/graph/conversation-graph-memory.ts +1 -24
  324. package/src/memory/graph/graph-search.ts +8 -0
  325. package/src/memory/graph/retriever.ts +28 -0
  326. package/src/memory/graph/tools.ts +1 -1
  327. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
  328. package/src/memory/jobs/embed-concept-page.ts +28 -2
  329. package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
  330. package/src/memory/jobs-store.ts +66 -22
  331. package/src/memory/jobs-worker.ts +112 -63
  332. package/src/memory/memory-v2-activation-log-store.ts +1 -1
  333. package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
  334. package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
  335. package/src/memory/migrations/index.ts +5 -0
  336. package/src/memory/migrations/registry.ts +8 -0
  337. package/src/memory/pkb/pkb-search.ts +7 -0
  338. package/src/memory/qdrant-client.ts +50 -20
  339. package/src/memory/schema/infrastructure.ts +15 -0
  340. package/src/memory/search/semantic.ts +7 -0
  341. package/src/memory/sparse-tokenize.ts +49 -0
  342. package/src/memory/v2/__tests__/activation.test.ts +77 -95
  343. package/src/memory/v2/__tests__/injection.test.ts +43 -21
  344. package/src/memory/v2/__tests__/sim.test.ts +166 -6
  345. package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
  346. package/src/memory/v2/__tests__/static-context.test.ts +0 -1
  347. package/src/memory/v2/activation.ts +69 -88
  348. package/src/memory/v2/consolidation-job.ts +3 -5
  349. package/src/memory/v2/constants.ts +7 -0
  350. package/src/memory/v2/injection.ts +86 -53
  351. package/src/memory/v2/prompts/consolidation.ts +312 -91
  352. package/src/memory/v2/qdrant.ts +99 -1
  353. package/src/memory/v2/sim.ts +126 -16
  354. package/src/memory/v2/skill-qdrant.ts +12 -3
  355. package/src/memory/v2/skill-store.ts +16 -1
  356. package/src/memory/v2/sparse-bm25.ts +245 -0
  357. package/src/memory/v2/static-context.ts +6 -5
  358. package/src/messaging/providers/gmail/types.ts +0 -49
  359. package/src/messaging/providers/slack/adapter.ts +1 -31
  360. package/src/messaging/providers/slack/types.ts +0 -32
  361. package/src/notifications/README.md +10 -10
  362. package/src/notifications/broadcaster.ts +1 -1
  363. package/src/notifications/guardian-question-mode.ts +5 -5
  364. package/src/oauth/connect-orchestrator.ts +4 -0
  365. package/src/oauth/credential-token-resolver.ts +1 -3
  366. package/src/oauth/manual-token-connection.ts +0 -4
  367. package/src/outbound-proxy/index.ts +1 -37
  368. package/src/outbound-proxy/logging.ts +1 -1
  369. package/src/outbound-proxy/policy.ts +6 -5
  370. package/src/outbound-proxy/router.ts +2 -1
  371. package/src/permissions/approval-policy.test.ts +6 -275
  372. package/src/permissions/approval-policy.ts +0 -51
  373. package/src/permissions/checker.test.ts +0 -1
  374. package/src/permissions/checker.ts +3 -17
  375. package/src/permissions/gateway-threshold-reader.ts +2 -0
  376. package/src/permissions/prompter.ts +34 -1
  377. package/src/permissions/secret-prompter.ts +6 -2
  378. package/src/prompts/bootstrap-cleanup.ts +27 -0
  379. package/src/prompts/system-prompt.ts +3 -18
  380. package/src/prompts/templates/SOUL.md +13 -1
  381. package/src/providers/speech-to-text/provider-catalog.ts +7 -8
  382. package/src/runtime/assistant-event-hub.ts +118 -96
  383. package/src/runtime/assistant-event.ts +1 -0
  384. package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
  385. package/src/runtime/auth/middleware.ts +0 -96
  386. package/src/runtime/auth/route-policy.ts +19 -0
  387. package/src/runtime/btw-sidechain.ts +2 -3
  388. package/src/runtime/channel-invite-transport.ts +2 -48
  389. package/src/runtime/channel-invite-transports/email.ts +1 -1
  390. package/src/runtime/channel-invite-transports/slack.ts +1 -1
  391. package/src/runtime/channel-invite-transports/telegram.ts +1 -1
  392. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  393. package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
  394. package/src/runtime/channel-invite-types.ts +54 -0
  395. package/src/runtime/channel-readiness-service.ts +32 -13
  396. package/src/runtime/http-server.ts +3 -329
  397. package/src/runtime/http-types.ts +0 -5
  398. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
  399. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
  400. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
  401. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
  402. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
  403. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  404. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
  405. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
  406. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
  407. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
  408. package/src/runtime/migrations/migration-transport.ts +7 -7
  409. package/src/runtime/migrations/vbundle-builder.ts +327 -60
  410. package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
  411. package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
  412. package/src/runtime/migrations/vbundle-importer.ts +245 -68
  413. package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
  414. package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
  415. package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
  416. package/src/runtime/migrations/vbundle-validator.ts +114 -0
  417. package/src/runtime/pending-interactions.ts +35 -9
  418. package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
  419. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  420. package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
  421. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
  422. package/src/runtime/routes/approval-interception-types.ts +13 -0
  423. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
  424. package/src/runtime/routes/backup-routes.ts +15 -38
  425. package/src/runtime/routes/btw-routes.ts +14 -37
  426. package/src/runtime/routes/client-routes.ts +1 -0
  427. package/src/runtime/routes/contact-prompt-routes.ts +183 -0
  428. package/src/runtime/routes/conversation-query-routes.ts +36 -1
  429. package/src/runtime/routes/conversation-routes.ts +30 -13
  430. package/src/runtime/routes/document-pdf-renderer.ts +165 -0
  431. package/src/runtime/routes/documents-routes.ts +30 -0
  432. package/src/runtime/routes/errors.ts +19 -4
  433. package/src/runtime/routes/events-routes.ts +12 -6
  434. package/src/runtime/routes/gateway-log-routes.ts +79 -0
  435. package/src/runtime/routes/guardian-approval-interception.ts +2 -8
  436. package/src/runtime/routes/heartbeat-routes.ts +103 -38
  437. package/src/runtime/routes/host-app-control-routes.ts +134 -0
  438. package/src/runtime/routes/host-bash-routes.ts +36 -6
  439. package/src/runtime/routes/host-browser-routes.ts +108 -13
  440. package/src/runtime/routes/host-cu-routes.ts +44 -14
  441. package/src/runtime/routes/host-file-routes.ts +33 -10
  442. package/src/runtime/routes/host-transfer-routes.ts +64 -24
  443. package/src/runtime/routes/http-adapter.ts +1 -0
  444. package/src/runtime/routes/identity-intro-cache.ts +30 -0
  445. package/src/runtime/routes/identity-routes.ts +15 -43
  446. package/src/runtime/routes/inbound-message-handler.ts +1 -9
  447. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
  448. package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
  449. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
  450. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
  451. package/src/runtime/routes/index.ts +8 -0
  452. package/src/runtime/routes/mcp-auth-routes.ts +132 -0
  453. package/src/runtime/routes/memory-item-routes.ts +10 -12
  454. package/src/runtime/routes/memory-v2-routes.ts +441 -1
  455. package/src/runtime/routes/migration-routes.ts +96 -0
  456. package/src/runtime/routes/schedule-routes.ts +7 -0
  457. package/src/runtime/verification-templates.ts +4 -7
  458. package/src/schedule/integration-status.ts +66 -2
  459. package/src/schedule/recurrence-engine.ts +4 -1
  460. package/src/schedule/retry-backoff.ts +18 -0
  461. package/src/schedule/retry-policy.ts +82 -0
  462. package/src/schedule/schedule-recovery.ts +64 -0
  463. package/src/schedule/schedule-store.ts +106 -2
  464. package/src/schedule/scheduler-types.ts +25 -0
  465. package/src/schedule/scheduler.ts +63 -38
  466. package/src/security/oauth-callback-registry.ts +8 -0
  467. package/src/sequence/analytics.ts +5 -5
  468. package/src/sequence/engine.ts +1 -1
  469. package/src/skills/catalog-files.ts +2 -8
  470. package/src/skills/include-graph.ts +5 -5
  471. package/src/skills/remote-skill-policy.ts +5 -5
  472. package/src/skills/skill-file-provider.ts +1 -1
  473. package/src/skills/skill-file-types.ts +13 -0
  474. package/src/skills/skillssh-audit-types.ts +28 -0
  475. package/src/skills/skillssh-registry.ts +8 -21
  476. package/src/telemetry/types.ts +2 -0
  477. package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
  478. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  479. package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
  480. package/src/tools/apps/executors.ts +56 -69
  481. package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
  482. package/src/tools/browser/browser-execution.ts +2 -2
  483. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
  484. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
  485. package/src/tools/browser/cdp-client/factory.ts +23 -24
  486. package/src/tools/browser/cdp-client/index.ts +1 -14
  487. package/src/tools/computer-use/definitions.ts +42 -20
  488. package/src/tools/executor.ts +2 -0
  489. package/src/tools/host-filesystem/edit.ts +26 -0
  490. package/src/tools/host-filesystem/read.ts +26 -0
  491. package/src/tools/host-filesystem/transfer.ts +31 -1
  492. package/src/tools/host-filesystem/write.ts +26 -0
  493. package/src/tools/host-terminal/host-shell.ts +58 -0
  494. package/src/tools/schedule/create.ts +6 -0
  495. package/src/tools/schedule/list.ts +2 -0
  496. package/src/tools/schedule/update.ts +10 -0
  497. package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
  498. package/src/tools/shared/filesystem/path-policy.ts +25 -1
  499. package/src/tools/skills/load.ts +0 -32
  500. package/src/tools/tool-approval-handler.ts +1 -5
  501. package/src/tools/types.ts +4 -0
  502. package/src/usage/pricing.ts +1 -1
  503. package/src/workspace/hatched-date.ts +86 -0
  504. package/src/workspace/migrations/003-seed-device-id.ts +1 -1
  505. package/src/workspace/migrations/006-services-config.ts +8 -5
  506. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
  507. package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
  508. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
  509. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
  510. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
  511. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
  512. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
  513. package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
  514. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
  515. package/src/workspace/migrations/AGENTS.md +1 -1
  516. package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
  517. package/src/workspace/migrations/utils.ts +21 -0
  518. package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
  519. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
  520. package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
  521. package/src/__tests__/twilio-rest.test.ts +0 -34
  522. package/src/backup/__tests__/backup-key.test.ts +0 -152
  523. package/src/backup/__tests__/backup-worker.test.ts +0 -782
  524. package/src/backup/__tests__/offsite-writer.test.ts +0 -641
  525. package/src/backup/__tests__/stream-crypt.test.ts +0 -228
  526. package/src/backup/backup-key.ts +0 -137
  527. package/src/backup/backup-worker.ts +0 -472
  528. package/src/backup/offsite-writer.ts +0 -222
  529. package/src/backup/stream-crypt.ts +0 -263
  530. package/src/daemon/message-types/pairing.ts +0 -58
  531. package/src/outbound-proxy/config.ts +0 -20
  532. package/src/outbound-proxy/health.ts +0 -18
  533. package/src/outbound-proxy/types.ts +0 -150
  534. package/src/runtime/capability-tokens.ts +0 -190
  535. package/src/signals/mcp-reload.ts +0 -18
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Gateway log tail route — gateway HTTP proxy.
3
+ *
4
+ * The handler makes a single HTTP call to the gateway's log-tail API
5
+ * and surfaces the body's `.error` message on non-OK responses.
6
+ */
7
+ import { z } from "zod";
8
+
9
+ import { getGatewayInternalBaseUrl } from "../../config/env.js";
10
+ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
11
+
12
+ // ── Shared helper ───────────────────────────────────────────────────────
13
+
14
+ async function gatewayFetch(
15
+ path: string,
16
+ init?: RequestInit,
17
+ ): Promise<unknown> {
18
+ const base = getGatewayInternalBaseUrl();
19
+ const res = await fetch(`${base}${path}`, init);
20
+ if (!res.ok) {
21
+ let message = `Gateway request failed (${res.status})`;
22
+ try {
23
+ const body = (await res.json()) as { error?: unknown };
24
+ if (typeof body.error === "string") {
25
+ message = body.error;
26
+ }
27
+ } catch {
28
+ // ignore JSON parse failures
29
+ }
30
+ throw new Error(message);
31
+ }
32
+ return res.json();
33
+ }
34
+
35
+ // ── Schemas ─────────────────────────────────────────────────────────────
36
+
37
+ const LEVEL_NAMES = ["trace", "debug", "info", "warn", "error", "fatal"] as const;
38
+
39
+ const GatewayLogsTailParams = z
40
+ .object({
41
+ n: z.coerce.number().int().min(1).max(1000).optional(),
42
+ level: z.enum(LEVEL_NAMES).optional(),
43
+ module: z.string().optional(),
44
+ })
45
+ .strict();
46
+
47
+ // ── Handlers ────────────────────────────────────────────────────────────
48
+
49
+ async function handleGatewayLogsTail({ queryParams = {}, body = {} }: RouteHandlerArgs) {
50
+ // HTTP GET delivers filters via queryParams; CLI IPC puts them in body.
51
+ const source = Object.keys(queryParams).length > 0 ? queryParams : body;
52
+ const p = GatewayLogsTailParams.parse(source);
53
+ const qs = new URLSearchParams();
54
+ if (p.n !== undefined) qs.set("n", String(p.n));
55
+ if (p.level !== undefined) qs.set("level", p.level);
56
+ if (p.module !== undefined) qs.set("module", p.module);
57
+ const query = qs.toString();
58
+ return gatewayFetch(`/v1/logs/tail${query ? `?${query}` : ""}`);
59
+ }
60
+
61
+ // ── Route definitions ───────────────────────────────────────────────────
62
+
63
+ export const ROUTES: RouteDefinition[] = [
64
+ {
65
+ operationId: "gateway_logs_tail",
66
+ method: "GET",
67
+ endpoint: "gateway/logs/tail",
68
+ handler: handleGatewayLogsTail,
69
+ summary: "Tail gateway log entries",
70
+ description:
71
+ "Return the last N structured log entries from the gateway log files.",
72
+ tags: ["gateway-logs"],
73
+ queryParams: [
74
+ { name: "n", description: "Number of lines to return (1–1000, default: 10)" },
75
+ { name: "level", description: "Minimum pino level name (default: info)" },
76
+ { name: "module", description: "Filter to exact pino module name" },
77
+ ],
78
+ },
79
+ ];
@@ -71,14 +71,8 @@ export interface ApprovalInterceptionParams {
71
71
  approvalMessageTs?: string;
72
72
  }
73
73
 
74
- export interface ApprovalInterceptionResult {
75
- handled: boolean;
76
- type?:
77
- | "decision_applied"
78
- | "assistant_turn"
79
- | "guardian_decision_applied"
80
- | "stale_ignored";
81
- }
74
+ import type { ApprovalInterceptionResult } from "./approval-interception-types.js";
75
+ export type { ApprovalInterceptionResult } from "./approval-interception-types.js";
82
76
 
83
77
  /**
84
78
  * Check for pending approvals and handle inbound messages accordingly.
@@ -8,13 +8,16 @@
8
8
  import { mkdirSync, writeFileSync } from "node:fs";
9
9
  import { dirname } from "node:path";
10
10
 
11
- import { desc, eq } from "drizzle-orm";
12
11
  import { z } from "zod";
13
12
 
14
- import { getConfig, saveConfig } from "../../config/loader.js";
13
+ import {
14
+ getConfig,
15
+ invalidateConfigCache,
16
+ loadRawConfig,
17
+ saveRawConfig,
18
+ } from "../../config/loader.js";
19
+ import { listHeartbeatRuns } from "../../heartbeat/heartbeat-run-store.js";
15
20
  import { HeartbeatService } from "../../heartbeat/heartbeat-service.js";
16
- import { getDb } from "../../memory/db-connection.js";
17
- import { conversations } from "../../memory/schema/conversations.js";
18
21
  import { readTextFileSync } from "../../util/fs.js";
19
22
  import { getLogger } from "../../util/logger.js";
20
23
  import { getWorkspacePromptPath } from "../../util/platform.js";
@@ -32,25 +35,20 @@ function handleListRuns(queryParams: Record<string, string>) {
32
35
  const limit = Number.isFinite(rawLimit)
33
36
  ? Math.min(Math.max(Math.floor(rawLimit), 1), 100)
34
37
  : 20;
35
- const db = getDb();
36
- const rows = db
37
- .select({
38
- id: conversations.id,
39
- title: conversations.title,
40
- createdAt: conversations.createdAt,
41
- })
42
- .from(conversations)
43
- .where(eq(conversations.source, "heartbeat"))
44
- .orderBy(desc(conversations.createdAt))
45
- .limit(limit)
46
- .all();
47
38
 
39
+ const runs = listHeartbeatRuns(limit);
48
40
  return {
49
- runs: rows.map((r) => ({
41
+ runs: runs.map((r) => ({
50
42
  id: r.id,
51
- title: r.title ?? "Heartbeat",
43
+ scheduledFor: r.scheduledFor,
44
+ startedAt: r.startedAt,
45
+ finishedAt: r.finishedAt,
46
+ durationMs: r.durationMs,
47
+ status: r.status,
48
+ skipReason: r.skipReason,
49
+ error: r.error,
50
+ conversationId: r.conversationId,
52
51
  createdAt: r.createdAt,
53
- result: "ok",
54
52
  })),
55
53
  };
56
54
  }
@@ -102,7 +100,22 @@ export const ROUTES: RouteDefinition[] = [
102
100
  },
103
101
  ],
104
102
  responseBody: z.object({
105
- runs: z.array(z.unknown()).describe("Heartbeat run records"),
103
+ runs: z
104
+ .array(
105
+ z.object({
106
+ id: z.string(),
107
+ scheduledFor: z.number(),
108
+ startedAt: z.number().nullable(),
109
+ finishedAt: z.number().nullable(),
110
+ durationMs: z.number().nullable(),
111
+ status: z.string(),
112
+ skipReason: z.string().nullable(),
113
+ error: z.string().nullable(),
114
+ conversationId: z.string().nullable(),
115
+ createdAt: z.number(),
116
+ }),
117
+ )
118
+ .describe("Heartbeat run records"),
106
119
  }),
107
120
  handler: ({ queryParams }: RouteHandlerArgs) =>
108
121
  handleListRuns(queryParams ?? {}),
@@ -135,8 +148,7 @@ export const ROUTES: RouteDefinition[] = [
135
148
  responseBody: z.object({
136
149
  success: z.boolean(),
137
150
  }),
138
- handler: ({ body }: RouteHandlerArgs) =>
139
- handleWriteChecklist(body ?? {}),
151
+ handler: ({ body }: RouteHandlerArgs) => handleWriteChecklist(body ?? {}),
140
152
  },
141
153
  {
142
154
  operationId: "getHeartbeatConfig",
@@ -152,6 +164,8 @@ export const ROUTES: RouteDefinition[] = [
152
164
  intervalMs: z.number(),
153
165
  activeHoursStart: z.number().nullable(),
154
166
  activeHoursEnd: z.number().nullable(),
167
+ cronExpression: z.string().nullable(),
168
+ timezone: z.string().nullable(),
155
169
  nextRunAt: z.number().nullable(),
156
170
  lastRunAt: z.number().nullable(),
157
171
  success: z.boolean(),
@@ -164,6 +178,8 @@ export const ROUTES: RouteDefinition[] = [
164
178
  intervalMs: config.intervalMs,
165
179
  activeHoursStart: config.activeHoursStart ?? null,
166
180
  activeHoursEnd: config.activeHoursEnd ?? null,
181
+ cronExpression: config.cronExpression ?? null,
182
+ timezone: config.timezone ?? null,
167
183
  nextRunAt: svc?.nextRunAt ?? null,
168
184
  lastRunAt: svc?.lastRunAt ?? null,
169
185
  success: true,
@@ -180,46 +196,95 @@ export const ROUTES: RouteDefinition[] = [
180
196
  description: "Update the heartbeat schedule configuration.",
181
197
  tags: ["heartbeat"],
182
198
  requestBody: z.object({
183
- enabled: z.boolean().describe("Enable or disable heartbeat"),
184
- intervalMs: z.number().describe("Heartbeat interval in ms"),
185
- activeHoursStart: z.number().describe("Active hours start (0–23)"),
186
- activeHoursEnd: z.number().describe("Active hours end (0–23)"),
199
+ enabled: z.boolean().optional().describe("Enable or disable heartbeat"),
200
+ intervalMs: z.number().optional().describe("Heartbeat interval in ms"),
201
+ activeHoursStart: z
202
+ .number()
203
+ .nullable()
204
+ .optional()
205
+ .describe("Active hours start (0–23)"),
206
+ activeHoursEnd: z
207
+ .number()
208
+ .nullable()
209
+ .optional()
210
+ .describe("Active hours end (0–23)"),
211
+ cronExpression: z
212
+ .string()
213
+ .nullable()
214
+ .optional()
215
+ .describe(
216
+ "Cron expression for heartbeat timing, or null for fixed interval",
217
+ ),
218
+ timezone: z
219
+ .string()
220
+ .nullable()
221
+ .optional()
222
+ .describe("Timezone for cron evaluation"),
187
223
  }),
188
224
  responseBody: z.object({
189
225
  enabled: z.boolean(),
190
226
  intervalMs: z.number(),
191
227
  activeHoursStart: z.number().nullable(),
192
228
  activeHoursEnd: z.number().nullable(),
229
+ cronExpression: z.string().nullable(),
230
+ timezone: z.string().nullable(),
193
231
  nextRunAt: z.number().nullable(),
194
232
  lastRunAt: z.number().nullable(),
195
233
  success: z.boolean(),
196
234
  }),
197
235
  handler: async ({ body = {} }: RouteHandlerArgs) => {
198
- const config = getConfig();
199
- const heartbeat = { ...config.heartbeat };
200
-
201
- if (typeof body.enabled === "boolean") heartbeat.enabled = body.enabled;
202
- if (typeof body.intervalMs === "number")
203
- heartbeat.intervalMs = body.intervalMs;
204
- if (typeof body.activeHoursStart === "number")
205
- heartbeat.activeHoursStart = body.activeHoursStart;
206
- if (typeof body.activeHoursEnd === "number")
207
- heartbeat.activeHoursEnd = body.activeHoursEnd;
236
+ // Build a patch containing only the fields the caller actually set.
237
+ // Writing back the full Zod-defaulted heartbeat object would bake
238
+ // defaults onto disk, masking later schema changes from the user.
239
+ // Use "key in body" checks for nullable fields so explicit null clears them.
240
+ const heartbeatPatch: Record<string, unknown> = {};
241
+ if ("enabled" in body && typeof body.enabled === "boolean")
242
+ heartbeatPatch.enabled = body.enabled;
243
+ if ("intervalMs" in body && typeof body.intervalMs === "number")
244
+ heartbeatPatch.intervalMs = body.intervalMs;
245
+ if ("activeHoursStart" in body)
246
+ heartbeatPatch.activeHoursStart =
247
+ typeof body.activeHoursStart === "number"
248
+ ? body.activeHoursStart
249
+ : null;
250
+ if ("activeHoursEnd" in body)
251
+ heartbeatPatch.activeHoursEnd =
252
+ typeof body.activeHoursEnd === "number" ? body.activeHoursEnd : null;
253
+ if ("cronExpression" in body)
254
+ heartbeatPatch.cronExpression =
255
+ typeof body.cronExpression === "string" ? body.cronExpression : null;
256
+ if ("timezone" in body)
257
+ heartbeatPatch.timezone =
258
+ typeof body.timezone === "string" ? body.timezone : null;
208
259
 
209
260
  try {
210
- saveConfig({ ...config, heartbeat });
211
- log.info({ heartbeat }, "Heartbeat config updated");
261
+ const raw = loadRawConfig();
262
+ raw.heartbeat = {
263
+ ...((raw.heartbeat as Record<string, unknown>) ?? {}),
264
+ ...heartbeatPatch,
265
+ };
266
+ saveRawConfig(raw);
267
+ invalidateConfigCache();
268
+ log.info({ heartbeat: heartbeatPatch }, "Heartbeat config updated");
212
269
  } catch (err) {
213
270
  log.error({ err }, "Failed to save heartbeat config");
214
271
  throw new InternalError("Failed to save config");
215
272
  }
216
273
 
274
+ // Read effective values back through the schema-defaulting loader so
275
+ // callers that only set a subset of fields still see the resolved
276
+ // (post-default) shape in the response.
277
+ const heartbeat = getConfig().heartbeat;
217
278
  const svc = HeartbeatService.getInstance();
279
+ svc?.reconfigure();
280
+
218
281
  return {
219
282
  enabled: heartbeat.enabled,
220
283
  intervalMs: heartbeat.intervalMs,
221
284
  activeHoursStart: heartbeat.activeHoursStart ?? null,
222
285
  activeHoursEnd: heartbeat.activeHoursEnd ?? null,
286
+ cronExpression: heartbeat.cronExpression ?? null,
287
+ timezone: heartbeat.timezone ?? null,
223
288
  nextRunAt: svc?.nextRunAt ?? null,
224
289
  lastRunAt: svc?.lastRunAt ?? null,
225
290
  success: true,
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Route handler for host app-control result submissions.
3
+ *
4
+ * Resolves pending host app-control proxy requests by requestId when the
5
+ * desktop client returns observation/action results via HTTP. App-control
6
+ * sessions are per-conversation (not a singleton like host-browser), so we
7
+ * look up the owning conversation through the pending-interactions tracker
8
+ * and forward the payload to that conversation's `hostAppControlProxy`.
9
+ *
10
+ * Late-delivery tolerance: returns 200 even when no pending interaction
11
+ * matches (e.g. the conversation was disposed before the client reported
12
+ * back). The proxy is best-effort — there is no consumer to notify, so a
13
+ * 4xx would only confuse a client that already executed the action.
14
+ */
15
+ import { z } from "zod";
16
+
17
+ import { findConversation } from "../../daemon/conversation-store.js";
18
+ import type {
19
+ HostAppControlResultPayload,
20
+ HostAppControlState,
21
+ } from "../../daemon/message-types/host-app-control.js";
22
+ import * as pendingInteractions from "../pending-interactions.js";
23
+ import { BadRequestError } from "./errors.js";
24
+ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
25
+
26
+ const VALID_STATES: ReadonlySet<HostAppControlState> = new Set([
27
+ "running",
28
+ "missing",
29
+ "minimized",
30
+ ]);
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // POST /v1/host-app-control-result
34
+ // ---------------------------------------------------------------------------
35
+
36
+ function handleHostAppControlResult({ body }: RouteHandlerArgs) {
37
+ if (!body || typeof body !== "object") {
38
+ throw new BadRequestError("Request body is required");
39
+ }
40
+
41
+ const {
42
+ requestId,
43
+ state,
44
+ pngBase64,
45
+ windowBounds,
46
+ executionResult,
47
+ executionError,
48
+ } = body as {
49
+ requestId?: string;
50
+ state?: string;
51
+ pngBase64?: string;
52
+ windowBounds?: { x: number; y: number; width: number; height: number };
53
+ executionResult?: string;
54
+ executionError?: string;
55
+ };
56
+
57
+ if (!requestId || typeof requestId !== "string") {
58
+ throw new BadRequestError("requestId is required");
59
+ }
60
+
61
+ if (!state || !VALID_STATES.has(state as HostAppControlState)) {
62
+ throw new BadRequestError(
63
+ "state must be one of: running, missing, minimized",
64
+ );
65
+ }
66
+
67
+ // Late-delivery tolerance: if the pending interaction is already gone (the
68
+ // proxy timed out, the conversation was disposed, etc.), accept the post
69
+ // and move on. There is no consumer left to fail loudly to.
70
+ const peeked = pendingInteractions.get(requestId);
71
+ if (!peeked || peeked.kind !== "host_app_control") {
72
+ return { accepted: true };
73
+ }
74
+
75
+ const interaction = pendingInteractions.resolve(requestId)!;
76
+ const conversation = findConversation(interaction.conversationId);
77
+ if (!conversation) {
78
+ return { accepted: true };
79
+ }
80
+
81
+ const payload: HostAppControlResultPayload = {
82
+ requestId,
83
+ state: state as HostAppControlState,
84
+ ...(pngBase64 !== undefined ? { pngBase64 } : {}),
85
+ ...(windowBounds !== undefined ? { windowBounds } : {}),
86
+ ...(executionResult !== undefined ? { executionResult } : {}),
87
+ ...(executionError !== undefined ? { executionError } : {}),
88
+ };
89
+
90
+ conversation.hostAppControlProxy?.resolve(requestId, payload);
91
+
92
+ return { accepted: true };
93
+ }
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Route definitions (shared HTTP + IPC)
97
+ // ---------------------------------------------------------------------------
98
+
99
+ export const ROUTES: RouteDefinition[] = [
100
+ {
101
+ operationId: "host_app_control_result",
102
+ endpoint: "host-app-control-result",
103
+ method: "POST",
104
+ requireGuardian: true,
105
+ summary: "Submit host app-control result",
106
+ description:
107
+ "Resolve a pending host app-control request by requestId. Returns 200 even when no pending interaction matches (late delivery is tolerated).",
108
+ tags: ["host"],
109
+ requestBody: z.object({
110
+ requestId: z.string().describe("Pending app-control request ID"),
111
+ state: z
112
+ .enum(["running", "missing", "minimized"])
113
+ .describe("Lifecycle state of the targeted application"),
114
+ pngBase64: z
115
+ .string()
116
+ .describe("Base64 PNG screenshot of the targeted app window")
117
+ .optional(),
118
+ windowBounds: z
119
+ .object({
120
+ x: z.number(),
121
+ y: z.number(),
122
+ width: z.number(),
123
+ height: z.number(),
124
+ })
125
+ .optional(),
126
+ executionResult: z.string().optional(),
127
+ executionError: z.string().optional(),
128
+ }),
129
+ responseBody: z.object({
130
+ accepted: z.boolean(),
131
+ }),
132
+ handler: handleHostAppControlResult,
133
+ },
134
+ ];
@@ -11,6 +11,7 @@ import * as pendingInteractions from "../pending-interactions.js";
11
11
  import {
12
12
  BadRequestError,
13
13
  ConflictError,
14
+ ForbiddenError,
14
15
  NotFoundError,
15
16
  } from "./errors.js";
16
17
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
@@ -19,7 +20,7 @@ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
19
20
  // POST /v1/host-bash-result
20
21
  // ---------------------------------------------------------------------------
21
22
 
22
- function handleHostBashResult({ body }: RouteHandlerArgs) {
23
+ function handleHostBashResult({ body, headers }: RouteHandlerArgs) {
23
24
  if (!body || typeof body !== "object") {
24
25
  throw new BadRequestError("Request body is required");
25
26
  }
@@ -36,11 +37,11 @@ function handleHostBashResult({ body }: RouteHandlerArgs) {
36
37
  throw new BadRequestError("requestId is required");
37
38
  }
38
39
 
40
+ const submittingClientId = headers?.["x-vellum-client-id"]?.trim() || undefined;
41
+
39
42
  const peeked = pendingInteractions.get(requestId);
40
43
  if (!peeked) {
41
- throw new NotFoundError(
42
- "No pending interaction found for this requestId",
43
- );
44
+ throw new NotFoundError("No pending interaction found for this requestId");
44
45
  }
45
46
 
46
47
  if (peeked.kind !== "host_bash") {
@@ -49,9 +50,21 @@ function handleHostBashResult({ body }: RouteHandlerArgs) {
49
50
  );
50
51
  }
51
52
 
52
- pendingInteractions.resolve(requestId);
53
+ const { targetClientId } = peeked;
54
+ if (targetClientId) {
55
+ if (!submittingClientId) {
56
+ throw new BadRequestError(
57
+ "x-vellum-client-id header is required for targeted host bash requests",
58
+ );
59
+ }
60
+ if (submittingClientId !== targetClientId) {
61
+ throw new ForbiddenError(
62
+ `Client "${submittingClientId}" is not the target for this request (expected "${targetClientId}"). The targeted client must submit the result.`,
63
+ );
64
+ }
65
+ }
53
66
 
54
- HostBashProxy.instance.resolve(requestId, {
67
+ HostBashProxy.instance.resolveResult(requestId, {
55
68
  stdout: stdout ?? "",
56
69
  stderr: stderr ?? "",
57
70
  exitCode: exitCode ?? null,
@@ -84,6 +97,23 @@ export const ROUTES: RouteDefinition[] = [
84
97
  responseBody: z.object({
85
98
  accepted: z.boolean(),
86
99
  }),
100
+ additionalResponses: {
101
+ "400": {
102
+ description:
103
+ "x-vellum-client-id header is missing for a targeted host bash request.",
104
+ },
105
+ "403": {
106
+ description:
107
+ "Submitting client does not match the targeted client for this request.",
108
+ },
109
+ "404": {
110
+ description: "No pending interaction found for the given requestId.",
111
+ },
112
+ "409": {
113
+ description:
114
+ "Pending interaction exists but is of a different kind (e.g. host_file, host_cu).",
115
+ },
116
+ },
87
117
  handler: handleHostBashResult,
88
118
  },
89
119
  ];