@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,89 @@
1
+ import { describe, expect, mock, test } from "bun:test";
2
+
3
+ // ─── Mocks ─────────────────────────────────────────────────────────────
4
+
5
+ let mockConnectedProviders = new Set<string>();
6
+
7
+ mock.module("../../oauth/oauth-store.js", () => ({
8
+ isProviderConnected: async (provider: string) =>
9
+ mockConnectedProviders.has(provider),
10
+ listProviders: () => [
11
+ { provider: "google" },
12
+ { provider: "slack" },
13
+ { provider: "notion" },
14
+ { provider: "linear" },
15
+ { provider: "github" },
16
+ ],
17
+ }));
18
+
19
+ mock.module("../../util/logger.js", () => ({
20
+ getLogger: () =>
21
+ new Proxy({} as Record<string, unknown>, {
22
+ get: () => () => {},
23
+ }),
24
+ }));
25
+
26
+ const { getSuggestedPrompts } = await import("../suggested-prompts.js");
27
+
28
+ // ─── Tests ─────────────────────────────────────────────────────────────
29
+
30
+ describe("getSuggestedPrompts", () => {
31
+ test("shows 'Connect X' prompts when providers are disconnected", async () => {
32
+ mockConnectedProviders = new Set();
33
+
34
+ const prompts = await getSuggestedPrompts();
35
+ const ids = prompts.map((p) => p.id);
36
+
37
+ expect(ids).toContain("connect-google");
38
+ expect(ids).toContain("connect-slack");
39
+ expect(prompts.find((p) => p.id === "connect-google")!.label).toBe(
40
+ "Connect Gmail",
41
+ );
42
+ });
43
+
44
+ test("shows email management prompts when Google is connected", async () => {
45
+ mockConnectedProviders = new Set(["google"]);
46
+
47
+ const prompts = await getSuggestedPrompts();
48
+ const ids = prompts.map((p) => p.id);
49
+
50
+ // Should NOT show "Connect Gmail"
51
+ expect(ids).not.toContain("connect-google");
52
+
53
+ // Should show management prompts
54
+ expect(ids).toContain("manage-google-triage-my-inbox");
55
+ expect(ids).toContain("manage-google-summarize-today's-emails");
56
+
57
+ const triage = prompts.find(
58
+ (p) => p.id === "manage-google-triage-my-inbox",
59
+ );
60
+ expect(triage).toBeDefined();
61
+ expect(triage!.label).toBe("Triage my inbox");
62
+ expect(triage!.icon).toBe("mail");
63
+ expect(triage!.source).toBe("deterministic");
64
+ });
65
+
66
+ test("still shows Connect prompts for disconnected providers alongside management prompts", async () => {
67
+ mockConnectedProviders = new Set(["google"]);
68
+
69
+ const prompts = await getSuggestedPrompts();
70
+ const ids = prompts.map((p) => p.id);
71
+
72
+ // Gmail management prompts
73
+ expect(ids).toContain("manage-google-triage-my-inbox");
74
+ // Slack still disconnected
75
+ expect(ids).toContain("connect-slack");
76
+ });
77
+
78
+ test("providers without connectedPrompts show nothing when connected", async () => {
79
+ mockConnectedProviders = new Set(["slack"]);
80
+
81
+ const prompts = await getSuggestedPrompts();
82
+ const ids = prompts.map((p) => p.id);
83
+
84
+ // No connect prompt since connected
85
+ expect(ids).not.toContain("connect-slack");
86
+ // No management prompts since Slack doesn't define any
87
+ expect(ids.filter((id) => id.startsWith("manage-slack"))).toHaveLength(0);
88
+ });
89
+ });
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Post-connection feed nudge.
3
+ *
4
+ * Emits a one-time nudge feed item when the user successfully connects
5
+ * an email-capable OAuth provider. The nudge highlights ongoing email
6
+ * management capabilities (inbox triage, daily digests) so the user
7
+ * discovers what they can do beyond the initial setup.
8
+ *
9
+ * Uses a deterministic id (`connect-nudge:<service>`) so reconnecting
10
+ * the same provider replaces the existing nudge in place rather than
11
+ * appending a duplicate.
12
+ */
13
+
14
+ import type { FeedItem } from "./feed-types.js";
15
+ import { appendFeedItem } from "./feed-writer.js";
16
+
17
+ /**
18
+ * Services that should trigger an email management nudge on connection.
19
+ * Only providers with real email integration are listed — see
20
+ * `relationship-state-writer.ts` for the same "only Gmail is real" note.
21
+ */
22
+ const EMAIL_SERVICES = new Set(["google"]);
23
+
24
+ /**
25
+ * Emit a feed nudge for a newly connected email provider.
26
+ *
27
+ * No-ops silently when the service is not email-capable. Never throws —
28
+ * the feed writer's warn-log contract absorbs persistence failures.
29
+ */
30
+ export async function emitPostConnectNudge(service: string): Promise<void> {
31
+ if (!EMAIL_SERVICES.has(service)) return;
32
+
33
+ const now = new Date();
34
+ const expiresAt = new Date(
35
+ now.getTime() + 7 * 24 * 60 * 60 * 1000,
36
+ ).toISOString();
37
+
38
+ const item: FeedItem = {
39
+ id: `connect-nudge:${service}`,
40
+ type: "nudge",
41
+ priority: 70,
42
+ title: "Gmail connected — want ongoing help?",
43
+ summary:
44
+ "I can triage your inbox, summarize new emails, or draft replies to important threads.",
45
+ source: "gmail",
46
+ timestamp: now.toISOString(),
47
+ status: "new",
48
+ expiresAt,
49
+ author: "platform",
50
+ createdAt: now.toISOString(),
51
+ actions: [
52
+ {
53
+ id: "inbox-triage",
54
+ label: "Triage my inbox",
55
+ prompt:
56
+ "Help me triage my inbox — summarize what's unread and flag anything that needs a reply",
57
+ },
58
+ {
59
+ id: "daily-digest",
60
+ label: "Set up daily digest",
61
+ prompt:
62
+ "Set up a daily email digest that summarizes my unread messages each morning",
63
+ },
64
+ ],
65
+ };
66
+
67
+ await appendFeedItem(item);
68
+ }
@@ -18,13 +18,7 @@
18
18
  * a missing or unreadable file degrades gracefully to an empty string.
19
19
  */
20
20
 
21
- import {
22
- existsSync,
23
- mkdirSync,
24
- readFileSync,
25
- statSync,
26
- writeFileSync,
27
- } from "node:fs";
21
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
28
22
  import { join } from "node:path";
29
23
 
30
24
  import { countConversations as countConversationsDb } from "../memory/conversation-queries.js";
@@ -39,6 +33,7 @@ import {
39
33
  getWorkspaceDir,
40
34
  getWorkspacePromptPath,
41
35
  } from "../util/platform.js";
36
+ import { resolveAndPersistHatchedAt } from "../workspace/hatched-date.js";
42
37
  import { computeProgressPercent, computeTier } from "./progress-formula.js";
43
38
  import {
44
39
  type Capability,
@@ -149,11 +144,12 @@ function readOnboardingSidecar(): OnboardingContext | null {
149
144
  * Reads USER.md / SOUL.md / IDENTITY.md, queries the oauth connection
150
145
  * store, and counts conversations via the DB-authoritative helper.
151
146
  *
152
- * Side effect: on the very first call when IDENTITY.md cannot provide
153
- * a hatched date, `resolveFallbackHatchedDate` persists a one-time
154
- * `data/hatched.json` sidecar so subsequent calls return a monotonic
155
- * timestamp. All other paths are read-only. Callers that want to
156
- * persist the full snapshot should use `writeRelationshipState()`.
147
+ * Side effect: on the very first call without an explicit Hatched
148
+ * bullet or existing hatched sidecar, the shared resolver persists a
149
+ * one-time `data/hatched.json` value seeded from IDENTITY.md metadata
150
+ * or a real current timestamp. All other paths are read-only. Callers
151
+ * that want to persist the full snapshot should use
152
+ * `writeRelationshipState()`.
157
153
  */
158
154
  export async function computeRelationshipState(): Promise<RelationshipState> {
159
155
  // Persona source-of-truth:
@@ -682,74 +678,17 @@ function countConversations(): number {
682
678
  }
683
679
  }
684
680
 
685
- /**
686
- * Filename for the hatched-date sidecar, used as a stable fallback
687
- * when IDENTITY.md is missing / unreadable / has no explicit hatched
688
- * bullet and file stat is unavailable. Lives under the workspace
689
- * data dir alongside `relationship-state.json`.
690
- */
691
- const HATCHED_SIDECAR_FILENAME = "hatched.json";
692
-
693
- function getHatchedSidecarPath(): string {
694
- return join(getDataDir(), HATCHED_SIDECAR_FILENAME);
695
- }
696
-
697
- /**
698
- * Resolve a stable hatched-date fallback timestamp.
699
- *
700
- * The Swift client, OpenAPI schema, and UI have no special handling
701
- * for a Unix-epoch sentinel — they'll render "1/1/1970" to the user.
702
- * Instead, we use `new Date().toISOString()` the first time a
703
- * fallback is needed and persist it to a small sidecar file
704
- * (`data/hatched.json`). Subsequent calls read the sidecar first, so
705
- * the returned timestamp is monotonic across writes and the
706
- * `hatchedDate` field never drifts once initialized.
707
- *
708
- * Never throws — a sidecar read/write failure still yields a valid
709
- * (though non-stable) `now` timestamp, which is still far better than
710
- * the epoch sentinel.
711
- */
712
- function resolveFallbackHatchedDate(): string {
713
- const path = getHatchedSidecarPath();
714
- try {
715
- if (existsSync(path)) {
716
- const parsed = JSON.parse(readFileSync(path, "utf-8")) as {
717
- hatchedAt?: string;
718
- };
719
- if (parsed.hatchedAt && !isNaN(Date.parse(parsed.hatchedAt))) {
720
- return parsed.hatchedAt;
721
- }
722
- }
723
- } catch {
724
- // Fall through to write a fresh sidecar.
725
- }
726
- const now = new Date().toISOString();
727
- try {
728
- mkdirSync(getDataDir(), { recursive: true });
729
- writeFileSync(path, JSON.stringify({ hatchedAt: now }, null, 2), "utf-8");
730
- } catch {
731
- // If even the sidecar write fails, return `now` anyway — the
732
- // caller will just get a fresh-looking date on every call. Not
733
- // ideal, but far better than the epoch sentinel.
734
- }
735
- return now;
736
- }
737
-
738
681
  /**
739
682
  * Pull `assistantName` and `hatchedDate` from IDENTITY.md.
740
683
  *
741
684
  * IDENTITY.md is a freeform markdown file, so for the name we scan
742
685
  * bullet lines for any recognizable `name` label (`Name`,
743
686
  * `Assistant Name`, `Preferred Name`, etc.). For the hatched date we
744
- * prefer any explicit `hatched:` / `birth:` bullet, then fall back to
745
- * the file's `stat.birthtime` (matching the pattern already
746
- * established by `identity-routes.ts`), and finally to `stat.mtime`
747
- * if birthtime is unavailable. We never fall back to `new Date()`
748
- * directly — `writeRelationshipState()` is called on every turn
749
- * boundary, so a raw `Date.now()` fallback would cause `hatchedDate`
750
- * to drift forward on every write. Instead, when nothing else is
751
- * readable we resolve via `resolveFallbackHatchedDate()` which
752
- * persists a stable timestamp to a sidecar file on first use.
687
+ * prefer any explicit `hatched:` / `birth:` bullet, then use the
688
+ * shared hatched-date resolver. That resolver reads an existing
689
+ * `data/hatched.json` sidecar first, otherwise seeds it from valid
690
+ * IDENTITY.md birthtime/mtime or a real current timestamp. This keeps
691
+ * `hatchedDate` stable without writing from read-only HTTP handlers.
753
692
  */
754
693
  function parseIdentity(identityPath: string): {
755
694
  assistantName: string;
@@ -792,24 +731,10 @@ function parseIdentity(identityPath: string): {
792
731
  return { assistantName, hatchedDate: explicitHatched };
793
732
  }
794
733
 
795
- // Stable fallback: use the IDENTITY.md file birth time so the
796
- // relationship start date is monotonic across turns. Matches the
797
- // approach used by `identity-routes.ts` for the Settings UI.
798
- try {
799
- const stats = statSync(identityPath);
800
- const candidate =
801
- stats.birthtime.getTime() > 0 ? stats.birthtime : stats.mtime;
802
- if (candidate.getTime() > 0) {
803
- return { assistantName, hatchedDate: candidate.toISOString() };
804
- }
805
- } catch {
806
- // File missing or unreadable — fall through to the sidecar.
807
- }
808
-
809
- // Last-ditch fallback: `resolveFallbackHatchedDate` returns a stable,
810
- // real timestamp (persisted to `data/hatched.json` on first call) so
811
- // the wire contract always carries a valid ISO date.
812
- return { assistantName, hatchedDate: resolveFallbackHatchedDate() };
734
+ return {
735
+ assistantName,
736
+ hatchedDate: resolveAndPersistHatchedAt(identityPath),
737
+ };
813
738
  }
814
739
 
815
740
  /**
@@ -21,14 +21,34 @@ const log = getLogger("suggested-prompts");
21
21
  * listed here produce deterministic "Connect X" prompts when disconnected.
22
22
  * The icon values are VIcon case names rendered by the macOS client.
23
23
  */
24
+ interface PromptEntry {
25
+ label: string;
26
+ prompt: string;
27
+ icon: string;
28
+ }
29
+
24
30
  const CONNECT_PROMPT_META: Record<
25
31
  string,
26
- { label: string; prompt: string; icon: string }
32
+ PromptEntry & { connectedPrompts?: PromptEntry[] }
27
33
  > = {
28
34
  google: {
29
35
  label: "Connect Gmail",
30
36
  prompt: "Help me connect my Gmail account",
31
37
  icon: "mail",
38
+ connectedPrompts: [
39
+ {
40
+ label: "Triage my inbox",
41
+ prompt:
42
+ "Help me triage my inbox — summarize what's unread and flag anything that needs a reply",
43
+ icon: "mail",
44
+ },
45
+ {
46
+ label: "Summarize today's emails",
47
+ prompt:
48
+ "Summarize the emails I received today and highlight anything important",
49
+ icon: "mail",
50
+ },
51
+ ],
32
52
  },
33
53
  slack: {
34
54
  label: "Connect Slack",
@@ -75,7 +95,9 @@ export async function getSuggestedPrompts(): Promise<SuggestedPrompt[]> {
75
95
 
76
96
  /**
77
97
  * Check which well-known OAuth providers are not connected and return
78
- * a "Connect X" prompt for each.
98
+ * a "Connect X" prompt for each. For connected providers that have
99
+ * `connectedPrompts`, return those instead so users discover ongoing
100
+ * management capabilities.
79
101
  */
80
102
  async function getDeterministicPrompts(): Promise<SuggestedPrompt[]> {
81
103
  const providers = listProviders();
@@ -86,15 +108,29 @@ async function getDeterministicPrompts(): Promise<SuggestedPrompt[]> {
86
108
  if (!meta) continue;
87
109
 
88
110
  const connected = await isProviderConnected(provider.provider);
89
- if (connected) continue;
90
111
 
91
- prompts.push({
92
- id: `connect-${provider.provider}`,
93
- label: meta.label,
94
- icon: meta.icon,
95
- prompt: meta.prompt,
96
- source: "deterministic",
97
- });
112
+ if (!connected) {
113
+ prompts.push({
114
+ id: `connect-${provider.provider}`,
115
+ label: meta.label,
116
+ icon: meta.icon,
117
+ prompt: meta.prompt,
118
+ source: "deterministic",
119
+ });
120
+ continue;
121
+ }
122
+
123
+ if (meta.connectedPrompts) {
124
+ for (const cp of meta.connectedPrompts) {
125
+ prompts.push({
126
+ id: `manage-${provider.provider}-${cp.label.toLowerCase().replace(/\s+/g, "-")}`,
127
+ label: cp.label,
128
+ icon: cp.icon,
129
+ prompt: cp.prompt,
130
+ source: "deterministic",
131
+ });
132
+ }
133
+ }
98
134
  }
99
135
 
100
136
  return prompts;
@@ -27,17 +27,30 @@
27
27
  * All public-facing ingress URL construction is centralized here.
28
28
  */
29
29
 
30
+ import {
31
+ buildTwilioConnectActionUrl,
32
+ buildTwilioMediaStreamUrl,
33
+ buildTwilioRelayUrl,
34
+ buildTwilioStatusWebhookUrl,
35
+ buildTwilioVoiceWebhookUrl,
36
+ normalizePublicBaseUrl,
37
+ } from "@vellumai/service-contracts/twilio-ingress";
38
+
30
39
  import { getIngressPublicBaseUrl } from "../config/env.js";
31
40
 
32
41
  export interface IngressConfig {
33
- ingress?: { enabled?: boolean; publicBaseUrl?: string };
42
+ ingress?: {
43
+ enabled?: boolean;
44
+ publicBaseUrl?: string;
45
+ };
34
46
  }
35
47
 
36
- /**
37
- * Trim whitespace and strip trailing slashes from a URL string.
38
- */
39
- function normalizeUrl(url: string): string {
40
- return url.trim().replace(/\/+$/, "");
48
+ function assertPublicIngressEnabled(config: IngressConfig): void {
49
+ if (config.ingress?.enabled === false) {
50
+ throw new Error(
51
+ "Public ingress is disabled. Ask the assistant to enable it, or update it from the Settings page.",
52
+ );
53
+ }
41
54
  }
42
55
 
43
56
  /**
@@ -51,23 +64,15 @@ function normalizeUrl(url: string): string {
51
64
  * Throws if no source provides a non-empty value or if ingress is disabled.
52
65
  */
53
66
  export function getPublicBaseUrl(config: IngressConfig): string {
54
- if (config.ingress?.enabled === false) {
55
- throw new Error(
56
- "Public ingress is disabled. Ask the assistant to enable it, or update it from the Settings page.",
57
- );
58
- }
67
+ assertPublicIngressEnabled(config);
59
68
 
60
69
  const ingressValue = config.ingress?.publicBaseUrl;
61
- if (ingressValue) {
62
- const normalized = normalizeUrl(ingressValue);
63
- if (normalized) return normalized;
64
- }
70
+ const normalizedIngressValue = normalizePublicBaseUrl(ingressValue);
71
+ if (normalizedIngressValue) return normalizedIngressValue;
65
72
 
66
73
  const ingressEnvValue = getIngressPublicBaseUrl();
67
- if (ingressEnvValue) {
68
- const normalized = normalizeUrl(ingressEnvValue);
69
- if (normalized) return normalized;
70
- }
74
+ const normalizedIngressEnvValue = normalizePublicBaseUrl(ingressEnvValue);
75
+ if (normalizedIngressEnvValue) return normalizedIngressEnvValue;
71
76
 
72
77
  throw new Error(
73
78
  "No public base URL configured. Set ingress.publicBaseUrl in config.",
@@ -87,27 +92,24 @@ export function getTwilioVoiceWebhookUrl(
87
92
  config: IngressConfig,
88
93
  callSessionId?: string,
89
94
  ): string {
90
- const base = getPublicBaseUrl(config);
91
- if (callSessionId) {
92
- return `${base}/webhooks/twilio/voice?callSessionId=${callSessionId}`;
93
- }
94
- return `${base}/webhooks/twilio/voice`;
95
+ return buildTwilioVoiceWebhookUrl(
96
+ getPublicBaseUrl(config),
97
+ callSessionId,
98
+ );
95
99
  }
96
100
 
97
101
  /**
98
102
  * Build the Twilio status callback URL.
99
103
  */
100
104
  export function getTwilioStatusCallbackUrl(config: IngressConfig): string {
101
- const base = getPublicBaseUrl(config);
102
- return `${base}/webhooks/twilio/status`;
105
+ return buildTwilioStatusWebhookUrl(getPublicBaseUrl(config));
103
106
  }
104
107
 
105
108
  /**
106
109
  * Build the Twilio connect-action callback URL.
107
110
  */
108
111
  export function getTwilioConnectActionUrl(config: IngressConfig): string {
109
- const base = getPublicBaseUrl(config);
110
- return `${base}/webhooks/twilio/connect-action`;
112
+ return buildTwilioConnectActionUrl(getPublicBaseUrl(config));
111
113
  }
112
114
 
113
115
  /**
@@ -115,9 +117,7 @@ export function getTwilioConnectActionUrl(config: IngressConfig): string {
115
117
  * Converts http:// → ws:// and https:// → wss://.
116
118
  */
117
119
  export function getTwilioRelayUrl(config: IngressConfig): string {
118
- const base = getPublicBaseUrl(config);
119
- const wsBase = base.replace(/^http(s?)/, "ws$1");
120
- return `${wsBase}/webhooks/twilio/relay`;
120
+ return buildTwilioRelayUrl(getPublicBaseUrl(config));
121
121
  }
122
122
 
123
123
  /**
@@ -127,9 +127,7 @@ export function getTwilioRelayUrl(config: IngressConfig): string {
127
127
  * Converts http:// → ws:// and https:// → wss://.
128
128
  */
129
129
  export function getTwilioMediaStreamUrl(config: IngressConfig): string {
130
- const base = getPublicBaseUrl(config);
131
- const wsBase = base.replace(/^http(s?)/, "ws$1");
132
- return `${wsBase}/webhooks/twilio/media-stream`;
130
+ return buildTwilioMediaStreamUrl(getPublicBaseUrl(config));
133
131
  }
134
132
 
135
133
  /**
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Unit tests for the IPC error envelope built from `RouteError` instances.
3
+ *
4
+ * Asserts that `AssistantIpcServer.buildErrorResponse` forwards the full
5
+ * `RouteError` shape — including the `details` field — into the IPC response
6
+ * envelope so IPC clients (e.g. gateway→daemon) receive the same structured
7
+ * payload as HTTP clients (e.g. `version_incompatible` migration imports).
8
+ */
9
+
10
+ import { describe, expect, test } from "bun:test";
11
+
12
+ import {
13
+ RouteError,
14
+ UnprocessableEntityError,
15
+ } from "../../runtime/routes/errors.js";
16
+ import { AssistantIpcServer, type IpcResponse } from "../assistant-server.js";
17
+
18
+ /**
19
+ * `buildErrorResponse` is private; access it through an interface cast so the
20
+ * test exercises the actual production code path without exporting a
21
+ * test-only API on the server class.
22
+ */
23
+ type PrivateApi = {
24
+ buildErrorResponse(id: string, err: unknown): IpcResponse;
25
+ };
26
+
27
+ function buildErrorResponse(err: unknown): IpcResponse {
28
+ const server = new AssistantIpcServer() as unknown as PrivateApi;
29
+ return server.buildErrorResponse("req-1", err);
30
+ }
31
+
32
+ describe("AssistantIpcServer error envelope", () => {
33
+ test("forwards RouteError message, statusCode, and code", () => {
34
+ const err = new RouteError("boom", "BOOM", 418);
35
+ const response = buildErrorResponse(err);
36
+
37
+ expect(response.id).toBe("req-1");
38
+ expect(response.error).toBe("boom");
39
+ expect(response.statusCode).toBe(418);
40
+ expect(response.errorCode).toBe("BOOM");
41
+ expect(response.errorDetails).toBeUndefined();
42
+ });
43
+
44
+ test("forwards RouteError.details into errorDetails when present", () => {
45
+ const details = {
46
+ reason: "version_incompatible" as const,
47
+ bundle_compat: { engineMin: "0.7.0" },
48
+ runtime_version: "0.6.0",
49
+ };
50
+ const err = new UnprocessableEntityError(
51
+ "incompatible bundle version",
52
+ details,
53
+ );
54
+
55
+ const response = buildErrorResponse(err);
56
+
57
+ expect(response.errorCode).toBe("UNPROCESSABLE_ENTITY");
58
+ expect(response.statusCode).toBe(422);
59
+ expect(response.errorDetails).toEqual(details);
60
+ });
61
+
62
+ test("omits errorDetails when RouteError has no details", () => {
63
+ const err = new UnprocessableEntityError("plain validation failure");
64
+
65
+ const response = buildErrorResponse(err);
66
+
67
+ expect(response.errorCode).toBe("UNPROCESSABLE_ENTITY");
68
+ // `errorDetails` must be omitted entirely (not `undefined` value) so the
69
+ // serialized JSON envelope stays minimal.
70
+ expect("errorDetails" in response).toBe(false);
71
+ });
72
+
73
+ test("non-RouteError errors are stringified into `error`", () => {
74
+ const response = buildErrorResponse(new Error("raw"));
75
+
76
+ expect(response.error).toBe("Error: raw");
77
+ expect(response.errorCode).toBeUndefined();
78
+ expect(response.errorDetails).toBeUndefined();
79
+ });
80
+ });
@@ -70,6 +70,14 @@ export type IpcResponse = {
70
70
  statusCode?: number;
71
71
  /** Machine-readable error code (e.g. "NOT_FOUND") for RouteError instances. */
72
72
  errorCode?: string;
73
+ /**
74
+ * Structured error payload mirroring `RouteError.details` — present only
75
+ * when the originating `RouteError` carries a `details` field (e.g.
76
+ * `version_incompatible` migration imports). Mirrors the HTTP adapter's
77
+ * `error.details` envelope so IPC clients can recover the same
78
+ * machine-readable context as HTTP clients.
79
+ */
80
+ errorDetails?: unknown;
73
81
  headers?: Record<string, string>;
74
82
  };
75
83
 
@@ -136,6 +144,7 @@ export class AssistantIpcServer {
136
144
  }
137
145
 
138
146
  // ⚠️ TEMPORARY — gateway→assistant DB proxy (see ipc/routes/db-proxy.ts).
147
+ // This is the ONLY route defined directly here; all other routes go in ROUTES.
139
148
  // Remove once contacts/guardian-binding logic is fully migrated to the
140
149
  // gateway's own database.
141
150
  this.methods.set("db_proxy", (params) =>
@@ -273,12 +282,16 @@ export class AssistantIpcServer {
273
282
 
274
283
  private buildErrorResponse(id: string, err: unknown): IpcResponse {
275
284
  if (err instanceof RouteError) {
276
- return {
285
+ const response: IpcResponse = {
277
286
  id,
278
287
  error: err.message,
279
288
  statusCode: err.statusCode,
280
289
  errorCode: err.code,
281
290
  };
291
+ if (err.details !== undefined) {
292
+ response.errorDetails = err.details;
293
+ }
294
+ return response;
282
295
  }
283
296
  return { id, error: String(err) };
284
297
  }