@vellumai/assistant 0.7.3 → 0.8.1

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 (778) hide show
  1. package/AGENTS.md +11 -0
  2. package/ARCHITECTURE.md +29 -28
  3. package/Dockerfile +6 -4
  4. package/README.md +2 -2
  5. package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
  6. package/bun.lock +3 -0
  7. package/docker-entrypoint.sh +16 -0
  8. package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +420 -0
  9. package/eslint-rules/cli-no-daemon-internals.js +283 -0
  10. package/eslint.config.mjs +12 -0
  11. package/knip.json +3 -1
  12. package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
  13. package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
  14. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
  15. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
  16. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
  17. package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
  18. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -1
  19. package/openapi.yaml +4126 -959
  20. package/package.json +5 -1
  21. package/scripts/generate-openapi.ts +52 -4
  22. package/scripts/sync-llm-catalog.ts +165 -0
  23. package/scripts/sync-web-search-catalog.ts +107 -0
  24. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +169 -0
  25. package/src/__tests__/agent-loop-override-profile.test.ts +26 -1
  26. package/src/__tests__/annotate-risk-options.test.ts +291 -0
  27. package/src/__tests__/anthropic-provider.test.ts +92 -2
  28. package/src/__tests__/app-control-flow.test.ts +7 -0
  29. package/src/__tests__/approval-cascade.test.ts +8 -16
  30. package/src/__tests__/approval-routes-http.test.ts +6 -0
  31. package/src/__tests__/assistant-events-sse-shed.test.ts +232 -0
  32. package/src/__tests__/auto-analysis-end-to-end.test.ts +12 -25
  33. package/src/__tests__/avatar-identity-sync.test.ts +87 -0
  34. package/src/__tests__/background-workers-disk-pressure.test.ts +11 -22
  35. package/src/__tests__/btw-routes.test.ts +1 -0
  36. package/src/__tests__/call-constants.test.ts +10 -1
  37. package/src/__tests__/call-controller.test.ts +127 -0
  38. package/src/__tests__/call-site-routing-provider.test.ts +172 -45
  39. package/src/__tests__/cancel-resolves-conversation-key.test.ts +44 -3
  40. package/src/__tests__/channel-policy.test.ts +12 -0
  41. package/src/__tests__/checker.test.ts +89 -0
  42. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +88 -30
  43. package/src/__tests__/compact-event-conversation-id-guard.test.ts +33 -5
  44. package/src/__tests__/compaction-strip-metadata-clear.test.ts +26 -1
  45. package/src/__tests__/config-loader-backfill.test.ts +526 -102
  46. package/src/__tests__/config-loader-corrupt.test.ts +68 -0
  47. package/src/__tests__/config-loader-platform-defaults.test.ts +345 -8
  48. package/src/__tests__/config-schema-cmd.test.ts +63 -29
  49. package/src/__tests__/config-schema.test.ts +14 -3
  50. package/src/__tests__/config-set-platform-guard.test.ts +75 -152
  51. package/src/__tests__/config-set-route.test.ts +198 -0
  52. package/src/__tests__/config-watcher.test.ts +6 -0
  53. package/src/__tests__/contacts-tools.test.ts +51 -199
  54. package/src/__tests__/context-search-agent-protocol.test.ts +21 -2
  55. package/src/__tests__/context-search-agent-runner.test.ts +22 -138
  56. package/src/__tests__/context-search-conversations-source.test.ts +42 -16
  57. package/src/__tests__/context-search-fanout.test.ts +20 -157
  58. package/src/__tests__/context-search-memory-source.test.ts +3 -26
  59. package/src/__tests__/context-search-memory-v2-source.test.ts +3 -3
  60. package/src/__tests__/context-search-types.test.ts +7 -2
  61. package/src/__tests__/context-window-manager.test.ts +389 -1
  62. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -6
  63. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  64. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -1
  65. package/src/__tests__/conversation-agent-loop.test.ts +3 -3
  66. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
  67. package/src/__tests__/conversation-crud-inference-profile.test.ts +100 -0
  68. package/src/__tests__/conversation-error.test.ts +38 -0
  69. package/src/__tests__/conversation-fork-crud.test.ts +241 -1
  70. package/src/__tests__/conversation-inference-profile-route.test.ts +14 -14
  71. package/src/__tests__/conversation-init.benchmark.test.ts +2 -1
  72. package/src/__tests__/conversation-lifecycle.test.ts +124 -0
  73. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +100 -1
  74. package/src/__tests__/conversation-process-callsite.test.ts +22 -7
  75. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -6
  76. package/src/__tests__/conversation-runtime-assembly.test.ts +19 -10
  77. package/src/__tests__/conversation-slash-commands.test.ts +194 -2
  78. package/src/__tests__/conversation-slash-unknown.test.ts +1 -6
  79. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
  80. package/src/__tests__/conversation-surfaces-app-control.test.ts +323 -3
  81. package/src/__tests__/conversation-surfaces-data-persist.test.ts +73 -1
  82. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +59 -0
  83. package/src/__tests__/conversation-workspace-injection.test.ts +1 -7
  84. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -7
  85. package/src/__tests__/credential-security-invariants.test.ts +5 -6
  86. package/src/__tests__/daemon-credential-client.test.ts +56 -1
  87. package/src/__tests__/db-activation-state-fk-cascade.test.ts +132 -0
  88. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +37 -0
  89. package/src/__tests__/db-memory-graph-event-date-repair.test.ts +43 -20
  90. package/src/__tests__/db-proxy-transaction.test.ts +206 -0
  91. package/src/__tests__/external-plugin-loader.test.ts +458 -0
  92. package/src/__tests__/filing-service.test.ts +25 -22
  93. package/src/__tests__/fixtures/mock-chrome-extension.ts +5 -0
  94. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  95. package/src/__tests__/graph-extraction-event-date.test.ts +34 -0
  96. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -34
  97. package/src/__tests__/heartbeat-disk-pressure.test.ts +21 -8
  98. package/src/__tests__/heartbeat-service.test.ts +50 -233
  99. package/src/__tests__/history-repair.test.ts +89 -0
  100. package/src/__tests__/host-app-control-proxy.test.ts +109 -1
  101. package/src/__tests__/host-app-control-routes.test.ts +247 -1
  102. package/src/__tests__/host-browser-proxy.test.ts +416 -20
  103. package/src/__tests__/host-browser-routes.test.ts +325 -33
  104. package/src/__tests__/host-proxy-preactivation.test.ts +211 -0
  105. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +246 -0
  106. package/src/__tests__/inference-profile-reaper.test.ts +154 -0
  107. package/src/__tests__/inference-profile-session-handler.test.ts +398 -0
  108. package/src/__tests__/inference-profile-session-ipc.test.ts +236 -0
  109. package/src/__tests__/injector-chain.test.ts +24 -16
  110. package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
  111. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -1
  112. package/src/__tests__/install-skill-routing.test.ts +2 -2
  113. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +169 -67
  114. package/src/__tests__/llm-callsite-catalog.test.ts +20 -1
  115. package/src/__tests__/llm-catalog-parity.test.ts +146 -0
  116. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +188 -0
  117. package/src/__tests__/llm-request-log-source-factory.test.ts +124 -0
  118. package/src/__tests__/llm-resolver.test.ts +46 -0
  119. package/src/__tests__/managed-profile-guard.test.ts +131 -2
  120. package/src/__tests__/mcp-auth-routes.test.ts +1 -0
  121. package/src/__tests__/mcp-cli.test.ts +182 -220
  122. package/src/__tests__/mcp-health-check.test.ts +56 -27
  123. package/src/__tests__/memory-jobs-worker-lanes.test.ts +18 -11
  124. package/src/__tests__/message-complete-display-id.test.ts +175 -0
  125. package/src/__tests__/notification-decision-fallback.test.ts +91 -0
  126. package/src/__tests__/notification-decision-strategy.test.ts +22 -0
  127. package/src/__tests__/notification-platform-adapter.test.ts +229 -0
  128. package/src/__tests__/oauth-cli.test.ts +38 -1888
  129. package/src/__tests__/oauth-commands-routes.test.ts +711 -0
  130. package/src/__tests__/oauth-connect-routes.test.ts +174 -11
  131. package/src/__tests__/oauth-providers-routes.test.ts +14 -10
  132. package/src/__tests__/openai-responses-cutover-guard.test.ts +33 -12
  133. package/src/__tests__/openai-responses-provider.test.ts +17 -0
  134. package/src/__tests__/plugin-bootstrap.test.ts +31 -2
  135. package/src/__tests__/plugin-route-contribution.test.ts +31 -3
  136. package/src/__tests__/plugin-tool-contribution.test.ts +31 -3
  137. package/src/__tests__/plugin-types.test.ts +13 -11
  138. package/src/__tests__/process-message-background-slack.test.ts +46 -0
  139. package/src/__tests__/profile-entry-status.test.ts +43 -0
  140. package/src/__tests__/provider-managed-proxy-integration.test.ts +12 -4
  141. package/src/__tests__/provider-registry-ollama.test.ts +12 -4
  142. package/src/__tests__/provider-send-message-override-profile.test.ts +10 -4
  143. package/src/__tests__/relay-server.test.ts +164 -2
  144. package/src/__tests__/retry-thinking-tool-choice.test.ts +15 -0
  145. package/src/__tests__/schedule-retry.test.ts +56 -4
  146. package/src/__tests__/schedule-routes.test.ts +104 -0
  147. package/src/__tests__/scheduler-disk-pressure.test.ts +0 -4
  148. package/src/__tests__/scheduler-recurrence.test.ts +87 -34
  149. package/src/__tests__/scheduler-reuse-conversation.test.ts +161 -5
  150. package/src/__tests__/scheduler-wake.test.ts +0 -63
  151. package/src/__tests__/secret-allowlist.test.ts +1 -0
  152. package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
  153. package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
  154. package/src/__tests__/secret-response-routing.test.ts +7 -5
  155. package/src/__tests__/secret-routes-managed-proxy.test.ts +12 -4
  156. package/src/__tests__/server-history-render.test.ts +82 -0
  157. package/src/__tests__/shell-credential-ref.test.ts +95 -3
  158. package/src/__tests__/shell-tool-proxy-mode.test.ts +14 -0
  159. package/src/__tests__/skill-include-graph.test.ts +31 -0
  160. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  161. package/src/__tests__/skill-load-tool.test.ts +42 -16
  162. package/src/__tests__/skills.test.ts +39 -0
  163. package/src/__tests__/subagent-call-site-routing.test.ts +78 -16
  164. package/src/__tests__/suggestion-routes.test.ts +3 -3
  165. package/src/__tests__/sync-message-contract.test.ts +63 -0
  166. package/src/__tests__/task-scheduler.test.ts +88 -23
  167. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
  168. package/src/__tests__/tool-executor.test.ts +155 -0
  169. package/src/__tests__/update-bulletin-job.test.ts +96 -193
  170. package/src/__tests__/usage-cli.test.ts +11 -73
  171. package/src/__tests__/user-plugin-loader.test.ts +145 -0
  172. package/src/__tests__/vercel-config.test.ts +168 -0
  173. package/src/__tests__/voice-session-bridge.test.ts +3 -0
  174. package/src/__tests__/web-search-catalog-parity.test.ts +86 -0
  175. package/src/__tests__/web-search.test.ts +303 -2
  176. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +1 -21
  177. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +58 -0
  178. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +153 -0
  179. package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
  180. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +191 -0
  181. package/src/__tests__/workspace-migration-076-drop-services-inference-mode.test.ts +211 -0
  182. package/src/__tests__/workspace-migration-077-seed-memory-router-callsite.test.ts +174 -0
  183. package/src/__tests__/workspace-migration-079-home-feed-notification-only.test.ts +323 -0
  184. package/src/__tests__/workspace-migration-080-restrict-vercel-api-token-metadata.test.ts +299 -0
  185. package/src/__tests__/workspace-migration-081-backfill-bash-allowed-tools.test.ts +410 -0
  186. package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +268 -0
  187. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +15 -27
  188. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +3 -3
  189. package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +115 -0
  190. package/src/acp/__tests__/helpers/which-stub.ts +4 -2
  191. package/src/acp/resolve-agent.test.ts +25 -0
  192. package/src/acp/resolve-agent.ts +13 -2
  193. package/src/acp/session-manager.ts +14 -0
  194. package/src/agent/loop.ts +11 -0
  195. package/src/approvals/guardian-decision-primitive.ts +0 -13
  196. package/src/approvals/guardian-request-resolvers.ts +19 -102
  197. package/src/calls/call-constants.ts +5 -8
  198. package/src/calls/call-controller.ts +130 -67
  199. package/src/calls/relay-server.ts +42 -1
  200. package/src/calls/relay-setup-router.ts +36 -0
  201. package/src/calls/types.ts +1 -0
  202. package/src/calls/voice-session-bridge.ts +24 -5
  203. package/src/channels/config.ts +14 -1
  204. package/src/channels/types.ts +1 -0
  205. package/src/cli/AGENTS.md +164 -4
  206. package/src/cli/__tests__/notifications.test.ts +54 -0
  207. package/src/cli/commands/__tests__/avatar.test.ts +540 -0
  208. package/src/cli/commands/__tests__/backup.test.ts +236 -776
  209. package/src/cli/commands/__tests__/cache.test.ts +1 -1
  210. package/src/cli/commands/__tests__/changelog.test.ts +593 -0
  211. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +503 -0
  212. package/src/cli/commands/__tests__/conversations-import.test.ts +515 -0
  213. package/src/cli/commands/__tests__/domain-register.test.ts +140 -167
  214. package/src/cli/commands/__tests__/domain-status.test.ts +137 -76
  215. package/src/cli/commands/__tests__/email-attachment.test.ts +314 -337
  216. package/src/cli/commands/__tests__/email-core.test.ts +579 -0
  217. package/src/cli/commands/__tests__/image-generation.test.ts +87 -824
  218. package/src/cli/commands/__tests__/inference-send.test.ts +30 -266
  219. package/src/cli/commands/__tests__/inference-session.test.ts +423 -0
  220. package/src/cli/commands/__tests__/memory-v2.test.ts +81 -110
  221. package/src/cli/commands/__tests__/skills.test.ts +563 -0
  222. package/src/cli/commands/__tests__/status.test.ts +249 -0
  223. package/src/cli/commands/__tests__/stt.test.ts +320 -0
  224. package/src/cli/commands/__tests__/tts-synthesize.test.ts +4 -603
  225. package/src/cli/commands/__tests__/tts.test.ts +321 -0
  226. package/src/cli/commands/__tests__/webhooks.test.ts +86 -511
  227. package/src/cli/commands/attachment.ts +8 -3
  228. package/src/cli/commands/audit.ts +95 -64
  229. package/src/cli/commands/auth.ts +61 -58
  230. package/src/cli/commands/avatar.ts +276 -390
  231. package/src/cli/commands/backup.ts +409 -505
  232. package/src/cli/commands/bash.ts +9 -5
  233. package/src/cli/commands/browser.ts +28 -9
  234. package/src/cli/commands/cache.ts +9 -4
  235. package/src/cli/commands/changelog.ts +414 -0
  236. package/src/cli/commands/channel-verification-sessions.ts +238 -317
  237. package/src/cli/commands/clients.ts +8 -3
  238. package/src/cli/commands/completions.ts +9 -9
  239. package/src/cli/commands/config.ts +102 -72
  240. package/src/cli/commands/contacts.ts +575 -696
  241. package/src/cli/commands/conversations-defer.ts +17 -69
  242. package/src/cli/commands/conversations-import.ts +90 -253
  243. package/src/cli/commands/conversations.ts +346 -436
  244. package/src/cli/commands/credential-execution.ts +9 -6
  245. package/src/cli/commands/credentials.ts +456 -736
  246. package/src/cli/commands/domain.ts +128 -206
  247. package/src/cli/commands/email.ts +606 -794
  248. package/src/cli/commands/gateway.ts +8 -1
  249. package/src/cli/commands/image-generation.ts +157 -205
  250. package/src/cli/commands/inference-providers.ts +352 -0
  251. package/src/cli/commands/inference-session.ts +415 -0
  252. package/src/cli/commands/inference.ts +87 -65
  253. package/src/cli/commands/keys.ts +8 -3
  254. package/src/cli/commands/mcp.ts +103 -287
  255. package/src/cli/commands/memory-v2.ts +163 -517
  256. package/src/cli/commands/notifications.ts +33 -7
  257. package/src/cli/commands/oauth/apps.ts +292 -261
  258. package/src/cli/commands/oauth/connect.ts +182 -345
  259. package/src/cli/commands/oauth/disconnect.ts +16 -215
  260. package/src/cli/commands/oauth/index.ts +49 -45
  261. package/src/cli/commands/oauth/mode.ts +43 -199
  262. package/src/cli/commands/oauth/ping.ts +17 -125
  263. package/src/cli/commands/oauth/providers.ts +732 -921
  264. package/src/cli/commands/oauth/request.ts +60 -350
  265. package/src/cli/commands/oauth/shared.ts +11 -121
  266. package/src/cli/commands/oauth/status.ts +31 -121
  267. package/src/cli/commands/oauth/token.ts +13 -55
  268. package/src/cli/commands/pending.ts +19 -10
  269. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +133 -183
  270. package/src/cli/commands/platform/__tests__/connect.test.ts +66 -181
  271. package/src/cli/commands/platform/__tests__/disconnect.test.ts +71 -227
  272. package/src/cli/commands/platform/__tests__/status.test.ts +169 -287
  273. package/src/cli/commands/platform/connect.ts +16 -80
  274. package/src/cli/commands/platform/disconnect.ts +14 -112
  275. package/src/cli/commands/platform/index.ts +177 -246
  276. package/src/cli/commands/routes.ts +153 -336
  277. package/src/cli/commands/sequence.ts +316 -360
  278. package/src/cli/commands/skills.ts +449 -671
  279. package/src/cli/commands/status.ts +58 -37
  280. package/src/cli/commands/stt.ts +94 -262
  281. package/src/cli/commands/task.ts +14 -40
  282. package/src/cli/commands/trust.ts +8 -3
  283. package/src/cli/commands/tts.ts +162 -167
  284. package/src/cli/commands/ui.ts +35 -42
  285. package/src/cli/commands/usage.ts +188 -126
  286. package/src/cli/commands/watchers.ts +8 -3
  287. package/src/cli/commands/webhooks.ts +99 -193
  288. package/src/cli/lib/__tests__/register-command.test.ts +85 -0
  289. package/src/cli/lib/daemon-credential-client.ts +4 -5
  290. package/src/cli/lib/nested-value.ts +44 -0
  291. package/src/cli/lib/open-browser.ts +36 -0
  292. package/src/cli/lib/register-command.ts +19 -0
  293. package/src/cli/lib/time-ago.ts +34 -0
  294. package/src/cli/program.ts +2 -4
  295. package/src/cli/utils/__tests__/conversation-id.test.ts +66 -0
  296. package/src/cli/utils/__tests__/parse-duration.test.ts +49 -0
  297. package/src/cli/utils/conversation-id.ts +30 -0
  298. package/src/cli/utils/parse-duration.ts +41 -0
  299. package/src/config/acp-defaults.test.ts +5 -1
  300. package/src/config/acp-defaults.ts +11 -4
  301. package/src/config/bundled-skills/acp/TOOLS.json +2 -2
  302. package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
  303. package/src/config/bundled-skills/app-control/TOOLS.json +32 -0
  304. package/src/config/bundled-skills/contacts/SKILL.md +12 -45
  305. package/src/config/bundled-skills/contacts/TOOLS.json +0 -57
  306. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +0 -12
  307. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -58
  308. package/src/config/bundled-tool-registry.ts +0 -2
  309. package/src/config/feature-flag-registry.json +17 -17
  310. package/src/config/llm-resolver.ts +16 -1
  311. package/src/config/loader.ts +148 -33
  312. package/src/config/raw-config-utils.ts +2 -30
  313. package/src/config/schema.ts +4 -0
  314. package/src/config/schemas/__tests__/memory-v2.test.ts +49 -0
  315. package/src/config/schemas/call-site-catalog.ts +29 -7
  316. package/src/config/schemas/llm-request-logs.ts +57 -0
  317. package/src/config/schemas/llm.ts +52 -2
  318. package/src/config/schemas/memory-retrospective.ts +48 -0
  319. package/src/config/schemas/memory-v2.ts +33 -2
  320. package/src/config/schemas/memory.ts +4 -0
  321. package/src/config/schemas/services.ts +15 -12
  322. package/src/config/seed-inference-profiles.ts +195 -134
  323. package/src/contacts/contact-store.ts +0 -61
  324. package/src/context/window-manager.ts +191 -5
  325. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +111 -0
  326. package/src/daemon/__tests__/conversation-tool-setup.test.ts +109 -4
  327. package/src/daemon/__tests__/daemon-skill-host.test.ts +10 -4
  328. package/src/daemon/approval-generators.ts +23 -29
  329. package/src/daemon/config-watcher.ts +2 -0
  330. package/src/daemon/conversation-agent-loop-handlers.ts +56 -0
  331. package/src/daemon/conversation-agent-loop.ts +140 -107
  332. package/src/daemon/conversation-error.ts +21 -0
  333. package/src/daemon/conversation-lifecycle.ts +68 -13
  334. package/src/daemon/conversation-process.ts +36 -19
  335. package/src/daemon/conversation-runtime-assembly.ts +14 -5
  336. package/src/daemon/conversation-slash.ts +175 -23
  337. package/src/daemon/conversation-store.ts +17 -10
  338. package/src/daemon/conversation-surfaces.ts +92 -26
  339. package/src/daemon/conversation-tool-setup.ts +33 -19
  340. package/src/daemon/conversation.ts +49 -10
  341. package/src/daemon/external-plugins-bootstrap.ts +18 -8
  342. package/src/daemon/guardian-action-generators.ts +7 -22
  343. package/src/daemon/handlers/config-model.ts +8 -126
  344. package/src/daemon/handlers/config-slack-channel.ts +10 -7
  345. package/src/daemon/handlers/config-vercel.ts +3 -1
  346. package/src/daemon/handlers/shared.ts +26 -0
  347. package/src/daemon/handlers/skills.ts +84 -5
  348. package/src/daemon/history-repair.ts +33 -6
  349. package/src/daemon/host-app-control-proxy.ts +44 -19
  350. package/src/daemon/host-bash-proxy.ts +85 -158
  351. package/src/daemon/host-browser-proxy.ts +97 -36
  352. package/src/daemon/host-cu-proxy.ts +1 -1
  353. package/src/daemon/host-file-proxy.ts +1 -1
  354. package/src/daemon/host-proxy-base.ts +13 -1
  355. package/src/daemon/host-proxy-preactivation.ts +25 -1
  356. package/src/daemon/host-transfer-proxy.ts +2 -2
  357. package/src/daemon/identity-helpers.ts +19 -0
  358. package/src/daemon/lifecycle.ts +128 -114
  359. package/src/daemon/meet-host-supervisor.ts +15 -15
  360. package/src/daemon/memory-v2-startup.ts +62 -14
  361. package/src/daemon/message-protocol.ts +6 -0
  362. package/src/daemon/message-types/bookmarks.ts +18 -0
  363. package/src/daemon/message-types/conversations.ts +12 -9
  364. package/src/daemon/message-types/messages.ts +28 -2
  365. package/src/daemon/message-types/sync.ts +60 -0
  366. package/src/daemon/pkb-reminder-builder.test.ts +54 -13
  367. package/src/daemon/pkb-reminder-builder.ts +21 -7
  368. package/src/daemon/process-message.ts +56 -23
  369. package/src/daemon/server.ts +23 -18
  370. package/src/daemon/shutdown-handlers.ts +0 -2
  371. package/src/daemon/tool-setup-types.ts +9 -0
  372. package/src/daemon/tool-side-effects.ts +6 -4
  373. package/src/daemon/wake-target-adapter.ts +11 -0
  374. package/src/documents/document-store.ts +35 -1
  375. package/src/export/transcript-formatter.ts +61 -2
  376. package/src/filing/filing-service.ts +42 -56
  377. package/src/heartbeat/__tests__/heartbeat-service.test.ts +359 -0
  378. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  379. package/src/heartbeat/heartbeat-service.ts +149 -128
  380. package/src/home/__tests__/feed-types.test.ts +63 -131
  381. package/src/home/__tests__/feed-writer.test.ts +77 -278
  382. package/src/home/__tests__/post-connect-feed.test.ts +9 -12
  383. package/src/home/feed-types.ts +19 -73
  384. package/src/home/feed-writer.ts +25 -156
  385. package/src/home/post-connect-feed.ts +1 -3
  386. package/src/ipc/__tests__/cli-ipc.test.ts +2 -0
  387. package/src/ipc/__tests__/email-ipc.test.ts +506 -0
  388. package/src/ipc/__tests__/exit-helper.test.ts +104 -0
  389. package/src/ipc/__tests__/streaming-client.test.ts +237 -0
  390. package/src/ipc/__tests__/streaming-framing.test.ts +142 -0
  391. package/src/ipc/assistant-server.ts +148 -42
  392. package/src/ipc/cli-client.ts +370 -50
  393. package/src/ipc/routes/db-proxy-transaction.ts +151 -0
  394. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +60 -0
  395. package/src/ipc/skill-routes/events.ts +30 -3
  396. package/src/ipc/skill-server.ts +99 -42
  397. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +46 -0
  398. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +1 -0
  399. package/src/live-voice/live-voice-session-manager.ts +11 -4
  400. package/src/live-voice/live-voice-session.ts +14 -6
  401. package/src/memory/__tests__/bookmark-crud.test.ts +258 -0
  402. package/src/memory/__tests__/bookmark-schema.test.ts +181 -0
  403. package/src/memory/__tests__/conversation-types.test.ts +36 -0
  404. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +130 -0
  405. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +10 -57
  406. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +177 -0
  407. package/src/memory/__tests__/memory-retrospective-job.test.ts +328 -0
  408. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +213 -0
  409. package/src/memory/__tests__/memory-retrospective-trigger-check.test.ts +90 -0
  410. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +69 -0
  411. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -0
  412. package/src/memory/bookmark-crud.ts +179 -0
  413. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +31 -9
  414. package/src/memory/context-search/agent-protocol.ts +5 -1
  415. package/src/memory/context-search/agent-runner.ts +60 -85
  416. package/src/memory/context-search/limits.ts +1 -4
  417. package/src/memory/context-search/search.ts +23 -113
  418. package/src/memory/context-search/sources/conversations.ts +18 -6
  419. package/src/memory/context-search/sources/memory-v2.ts +40 -31
  420. package/src/memory/context-search/sources/memory.ts +9 -2
  421. package/src/memory/context-search/sources/workspace.ts +13 -10
  422. package/src/memory/context-search/types.ts +1 -1
  423. package/src/memory/conversation-bootstrap.ts +11 -0
  424. package/src/memory/conversation-crud.ts +312 -10
  425. package/src/memory/conversation-queries.ts +9 -5
  426. package/src/memory/conversation-title-service.ts +1 -0
  427. package/src/memory/conversation-types.ts +16 -0
  428. package/src/memory/db-init.ts +14 -0
  429. package/src/memory/embedding-backend.ts +2 -1
  430. package/src/memory/embedding-runtime-manager.ts +1 -2
  431. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +104 -61
  432. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
  433. package/src/memory/graph/__tests__/remember-description.test.ts +55 -0
  434. package/src/memory/graph/conversation-graph-memory.ts +108 -14
  435. package/src/memory/graph/extraction.ts +4 -0
  436. package/src/memory/graph/graph-memory-state-store.ts +16 -3
  437. package/src/memory/graph/graph-search.test.ts +6 -5
  438. package/src/memory/graph/graph-search.ts +3 -4
  439. package/src/memory/graph/retriever.test.ts +12 -7
  440. package/src/memory/graph/retriever.ts +4 -5
  441. package/src/memory/graph/tool-handlers.ts +20 -11
  442. package/src/memory/graph/tools.ts +48 -9
  443. package/src/memory/indexer.ts +18 -2
  444. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +120 -6
  445. package/src/memory/jobs/embed-concept-page.ts +261 -89
  446. package/src/memory/jobs-store.ts +51 -1
  447. package/src/memory/jobs-worker.ts +60 -7
  448. package/src/memory/llm-request-log-source-clickhouse.ts +317 -0
  449. package/src/memory/llm-request-log-source-local.ts +26 -0
  450. package/src/memory/llm-request-log-source.ts +97 -0
  451. package/src/memory/llm-request-log-store.ts +1 -1
  452. package/src/memory/memory-retrospective-constants.ts +13 -0
  453. package/src/memory/memory-retrospective-enqueue.ts +114 -0
  454. package/src/memory/memory-retrospective-job.ts +351 -0
  455. package/src/memory/memory-retrospective-startup-cleanup.ts +108 -0
  456. package/src/memory/memory-retrospective-state.ts +162 -0
  457. package/src/memory/memory-retrospective-trigger-check.ts +91 -0
  458. package/src/memory/memory-v2-activation-log-store.ts +49 -5
  459. package/src/memory/memory-v2-concept-frequency.ts +4 -0
  460. package/src/memory/message-content.ts +38 -1
  461. package/src/memory/migrations/227-add-conversation-inference-profile.ts +6 -1
  462. package/src/memory/migrations/228-rename-inference-profile-snake-case.ts +20 -7
  463. package/src/memory/migrations/229-delete-private-conversations.test.ts +70 -1
  464. package/src/memory/migrations/229-delete-private-conversations.ts +12 -0
  465. package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +16 -2
  466. package/src/memory/migrations/240-conversation-inference-profile-session.ts +25 -0
  467. package/src/memory/migrations/241-activation-state-fk-cascade.ts +50 -0
  468. package/src/memory/migrations/242-message-bookmarks.ts +38 -0
  469. package/src/memory/migrations/243-provider-connections.ts +68 -0
  470. package/src/memory/migrations/244-provider-connection-status-label.ts +23 -0
  471. package/src/memory/migrations/245-memory-retrospective-state.ts +36 -0
  472. package/src/memory/migrations/246-backfill-provider-connection-label.ts +81 -0
  473. package/src/memory/migrations/__tests__/244-provider-connection-status-label.test.ts +84 -0
  474. package/src/memory/migrations/__tests__/245-memory-retrospective-state.test.ts +125 -0
  475. package/src/memory/migrations/__tests__/246-backfill-provider-connection-label.test.ts +192 -0
  476. package/src/memory/migrations/index.ts +7 -0
  477. package/src/memory/pkb/pkb-search.test.ts +6 -5
  478. package/src/memory/pkb/pkb-search.ts +4 -5
  479. package/src/memory/published-pages-store.ts +16 -0
  480. package/src/memory/qdrant-client.ts +3 -0
  481. package/src/memory/schema/bookmarks.ts +38 -0
  482. package/src/memory/schema/conversations.ts +2 -0
  483. package/src/memory/schema/index.ts +2 -0
  484. package/src/memory/schema/inference.ts +29 -0
  485. package/src/memory/schema/memory-core.ts +9 -0
  486. package/src/memory/search/semantic.ts +5 -9
  487. package/src/memory/v2/__tests__/__snapshots__/prompts-router.test.ts.snap +27 -0
  488. package/src/memory/v2/__tests__/activation-store.test.ts +5 -5
  489. package/src/memory/v2/__tests__/activation.test.ts +46 -9
  490. package/src/memory/v2/__tests__/backfill-jobs.test.ts +38 -21
  491. package/src/memory/v2/__tests__/consolidation-job.test.ts +140 -163
  492. package/src/memory/v2/__tests__/edge-index.test.ts +1 -1
  493. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +111 -0
  494. package/src/memory/v2/__tests__/injection.test.ts +768 -33
  495. package/src/memory/v2/__tests__/migration.test.ts +7 -3
  496. package/src/memory/v2/__tests__/page-index.test.ts +277 -0
  497. package/src/memory/v2/__tests__/page-store.test.ts +14 -1
  498. package/src/memory/v2/__tests__/prompts-router.test.ts +257 -0
  499. package/src/memory/v2/__tests__/qdrant.test.ts +382 -9
  500. package/src/memory/v2/__tests__/reranker.test.ts +4 -4
  501. package/src/memory/v2/__tests__/router.test.ts +516 -0
  502. package/src/memory/v2/__tests__/sim.test.ts +163 -8
  503. package/src/memory/v2/__tests__/skill-store.test.ts +58 -3
  504. package/src/memory/v2/__tests__/static-context.test.ts +8 -35
  505. package/src/memory/v2/__tests__/sweep-job.test.ts +114 -33
  506. package/src/memory/v2/activation-store.ts +34 -5
  507. package/src/memory/v2/activation.ts +40 -27
  508. package/src/memory/v2/backfill-jobs.ts +17 -84
  509. package/src/memory/v2/consolidation-job.ts +92 -86
  510. package/src/memory/v2/frontmatter-sweep.ts +91 -0
  511. package/src/memory/v2/injection.ts +466 -115
  512. package/src/memory/v2/migration.ts +117 -20
  513. package/src/memory/v2/page-index.ts +191 -0
  514. package/src/memory/v2/page-store.ts +42 -0
  515. package/src/memory/v2/prompts/consolidation.ts +14 -7
  516. package/src/memory/v2/prompts/router.ts +192 -0
  517. package/src/memory/v2/qdrant.ts +307 -133
  518. package/src/memory/v2/reranker.ts +14 -7
  519. package/src/memory/v2/router.ts +322 -0
  520. package/src/memory/v2/sim.ts +88 -34
  521. package/src/memory/v2/skill-store.ts +118 -29
  522. package/src/memory/v2/static-context.ts +20 -17
  523. package/src/memory/v2/sweep-job.ts +127 -102
  524. package/src/memory/v2/types.ts +16 -5
  525. package/src/memory/validation.ts +13 -0
  526. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +182 -0
  527. package/src/notifications/__tests__/home-feed-side-effect.test.ts +199 -0
  528. package/src/notifications/__tests__/signal-registry.test.ts +17 -0
  529. package/src/notifications/adapters/platform.ts +171 -0
  530. package/src/notifications/conversation-pairing.ts +2 -2
  531. package/src/notifications/copy-composer.ts +61 -12
  532. package/src/notifications/decision-engine.ts +46 -0
  533. package/src/notifications/destination-resolver.ts +21 -0
  534. package/src/notifications/emit-signal.ts +28 -1
  535. package/src/notifications/home-feed-side-effect.ts +111 -0
  536. package/src/notifications/signal.ts +5 -0
  537. package/src/permissions/checker.ts +12 -0
  538. package/src/permissions/gateway-threshold-reader.ts +116 -8
  539. package/src/permissions/ipc-risk-types.ts +2 -0
  540. package/src/permissions/prompter.ts +86 -96
  541. package/src/permissions/secret-prompter.ts +31 -31
  542. package/src/plugin-api/index.ts +13 -0
  543. package/src/plugin-api/package.json +12 -0
  544. package/src/plugin-api/types.ts +62 -0
  545. package/src/plugins/defaults/injectors.ts +20 -5
  546. package/src/plugins/external-plugin-loader.ts +294 -0
  547. package/src/plugins/types.ts +46 -30
  548. package/src/plugins/user-loader.ts +64 -41
  549. package/src/proactive-artifact/job.test.ts +63 -8
  550. package/src/proactive-artifact/job.ts +20 -2
  551. package/src/proactive-artifact/message-copy.ts +18 -1
  552. package/src/proactive-artifact/trigger-state.test.ts +9 -0
  553. package/src/proactive-artifact/trigger-state.ts +4 -0
  554. package/src/prompts/__tests__/system-prompt.test.ts +105 -0
  555. package/src/prompts/system-prompt.ts +22 -1
  556. package/src/prompts/templates/SOUL.md +13 -28
  557. package/src/prompts/update-bulletin-job.ts +61 -73
  558. package/src/providers/__tests__/dispatch-connection-routing.test.ts +279 -0
  559. package/src/providers/__tests__/inference.test.ts +288 -0
  560. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  561. package/src/providers/__tests__/provider-secret-catalog.test.ts +6 -0
  562. package/src/providers/__tests__/retry-callsite.test.ts +14 -32
  563. package/src/providers/__tests__/satellite-connection-routing.test.ts +510 -0
  564. package/src/providers/__tests__/search-provider-catalog.test.ts +80 -0
  565. package/src/providers/anthropic/client.ts +95 -26
  566. package/src/providers/call-site-routing.ts +94 -16
  567. package/src/providers/connection-resolution.ts +163 -0
  568. package/src/providers/inference/__tests__/connections-status-label.test.ts +250 -0
  569. package/src/providers/inference/adapter-factory.ts +173 -0
  570. package/src/providers/inference/auth.ts +112 -0
  571. package/src/providers/inference/backfill.ts +196 -0
  572. package/src/providers/inference/connections.ts +356 -0
  573. package/src/providers/inference/resolve-auth.ts +65 -0
  574. package/src/providers/model-catalog.ts +104 -6
  575. package/src/providers/openai/responses-provider.ts +4 -2
  576. package/src/providers/provider-env-vars.ts +17 -7
  577. package/src/providers/provider-secret-catalog.ts +49 -30
  578. package/src/providers/provider-send-message.ts +41 -20
  579. package/src/providers/registry.ts +143 -159
  580. package/src/providers/retry.ts +18 -10
  581. package/src/providers/search-provider-catalog.ts +121 -0
  582. package/src/runtime/AGENTS.md +18 -5
  583. package/src/runtime/__tests__/background-job-runner.test.ts +357 -0
  584. package/src/runtime/__tests__/pre-first-message-gate.test.ts +82 -0
  585. package/src/runtime/actor-trust-resolver.ts +32 -10
  586. package/src/runtime/agent-wake.ts +35 -6
  587. package/src/runtime/assistant-event-hub.ts +3 -85
  588. package/src/runtime/auth/route-policy.ts +304 -8
  589. package/src/runtime/auth/same-actor.ts +2 -0
  590. package/src/runtime/background-job-runner.ts +339 -0
  591. package/src/runtime/btw-sidechain.ts +1 -0
  592. package/src/runtime/channel-approvals.ts +3 -2
  593. package/src/runtime/guardian-reply-router.ts +0 -10
  594. package/src/runtime/http-router.ts +36 -1
  595. package/src/runtime/http-server.ts +31 -5
  596. package/src/runtime/http-types.ts +2 -0
  597. package/src/runtime/middleware/__tests__/request-logger.test.ts +162 -0
  598. package/src/runtime/middleware/request-logger.ts +62 -1
  599. package/src/runtime/pending-interactions.ts +19 -15
  600. package/src/runtime/pre-first-message-gate.ts +83 -0
  601. package/src/runtime/routes/__tests__/backup-routes.test.ts +8 -1
  602. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +251 -0
  603. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +142 -0
  604. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +315 -0
  605. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +189 -0
  606. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +15 -136
  607. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +736 -0
  608. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
  609. package/src/runtime/routes/__tests__/stt-routes.test.ts +5 -1
  610. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +384 -0
  611. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  612. package/src/runtime/routes/acp-routes.ts +10 -8
  613. package/src/runtime/routes/app-management-routes.ts +228 -3
  614. package/src/runtime/routes/approval-routes.ts +7 -21
  615. package/src/runtime/routes/audit-routes.ts +43 -0
  616. package/src/runtime/routes/auth-routes.ts +72 -0
  617. package/src/runtime/routes/avatar-routes.ts +273 -20
  618. package/src/runtime/routes/backup-routes.ts +406 -2
  619. package/src/runtime/routes/bookmark-routes.ts +154 -0
  620. package/src/runtime/routes/channel-verification-routes.ts +2 -1
  621. package/src/runtime/routes/consolidation-routes.ts +8 -9
  622. package/src/runtime/routes/contact-routes.ts +0 -160
  623. package/src/runtime/routes/conversation-cli-routes.ts +192 -0
  624. package/src/runtime/routes/conversation-management-routes.ts +30 -43
  625. package/src/runtime/routes/conversation-query-routes.ts +373 -82
  626. package/src/runtime/routes/conversation-routes.ts +31 -10
  627. package/src/runtime/routes/conversations-import-routes.ts +229 -0
  628. package/src/runtime/routes/credential-routes.ts +540 -0
  629. package/src/runtime/routes/debug-bash-routes.ts +2 -0
  630. package/src/runtime/routes/debug-routes.ts +2 -2
  631. package/src/runtime/routes/document-pdf-renderer.ts +5 -1
  632. package/src/runtime/routes/domain-routes.ts +167 -0
  633. package/src/runtime/routes/email-routes.ts +603 -0
  634. package/src/runtime/routes/errors.ts +2 -2
  635. package/src/runtime/routes/events-routes.ts +192 -0
  636. package/src/runtime/routes/filing-routes.ts +2 -3
  637. package/src/runtime/routes/home-feed-routes.ts +6 -78
  638. package/src/runtime/routes/host-app-control-routes.ts +44 -2
  639. package/src/runtime/routes/host-browser-routes.ts +103 -22
  640. package/src/runtime/routes/http-adapter.ts +2 -0
  641. package/src/runtime/routes/identity-routes.ts +5 -0
  642. package/src/runtime/routes/image-generation-routes.ts +99 -0
  643. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +137 -1
  644. package/src/runtime/routes/inbound-stages/background-dispatch.ts +87 -7
  645. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +156 -0
  646. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +22 -7
  647. package/src/runtime/routes/index.ts +36 -0
  648. package/src/runtime/routes/inference-profile-session-handler.ts +312 -0
  649. package/src/runtime/routes/inference-profile-session-reaper.ts +98 -0
  650. package/src/runtime/routes/inference-profile-session-routes.ts +146 -0
  651. package/src/runtime/routes/inference-provider-connection-routes.ts +317 -0
  652. package/src/runtime/routes/inference-send-routes.ts +115 -0
  653. package/src/runtime/routes/integrations/twilio.ts +1 -0
  654. package/src/runtime/routes/mcp-auth-routes.ts +283 -9
  655. package/src/runtime/routes/memory-item-routes.test.ts +3 -9
  656. package/src/runtime/routes/memory-item-routes.ts +5 -6
  657. package/src/runtime/routes/memory-v2-routes.ts +105 -404
  658. package/src/runtime/routes/notification-routes.ts +2 -0
  659. package/src/runtime/routes/oauth-apps.ts +112 -7
  660. package/src/runtime/routes/oauth-commands-routes.ts +1007 -0
  661. package/src/runtime/routes/oauth-connect-routes.ts +67 -5
  662. package/src/runtime/routes/oauth-providers.ts +298 -8
  663. package/src/runtime/routes/platform-routes.ts +336 -0
  664. package/src/runtime/routes/playground/inject-failures.ts +2 -1
  665. package/src/runtime/routes/playground/reset-circuit.ts +2 -1
  666. package/src/runtime/routes/playground/state.ts +2 -1
  667. package/src/runtime/routes/publish-routes.ts +221 -0
  668. package/src/runtime/routes/schedule-routes.ts +82 -0
  669. package/src/runtime/routes/sequence-routes.ts +291 -0
  670. package/src/runtime/routes/settings-routes.ts +2 -10
  671. package/src/runtime/routes/skills-routes.ts +31 -1
  672. package/src/runtime/routes/stt-routes.ts +240 -3
  673. package/src/runtime/routes/surface-action-routes.ts +43 -7
  674. package/src/runtime/routes/tts-routes.ts +67 -0
  675. package/src/runtime/routes/types.ts +32 -0
  676. package/src/runtime/routes/user-routes-cli.ts +243 -0
  677. package/src/runtime/routes/webhook-routes.ts +165 -0
  678. package/src/runtime/sync/resource-sync-events.ts +25 -0
  679. package/src/runtime/sync/sync-publisher.test.ts +105 -0
  680. package/src/runtime/sync/sync-publisher.ts +21 -0
  681. package/src/schedule/scheduler.ts +200 -123
  682. package/src/security/__tests__/provider-key-env-fallback.test.ts +12 -6
  683. package/src/security/secret-patterns.ts +3 -0
  684. package/src/sequence/engine.ts +38 -40
  685. package/src/skills/include-graph.ts +35 -13
  686. package/src/subagent/manager.ts +20 -15
  687. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +206 -0
  688. package/src/tools/browser/browser-execution.ts +15 -4
  689. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +174 -0
  690. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +16 -13
  691. package/src/tools/browser/cdp-client/extension-cdp-client.ts +24 -1
  692. package/src/tools/browser/cdp-client/factory.ts +66 -5
  693. package/src/tools/browser/runtime-check.ts +77 -0
  694. package/src/tools/document/document-tool.ts +20 -0
  695. package/src/tools/executor.ts +18 -2
  696. package/src/tools/memory/register.test.ts +10 -8
  697. package/src/tools/memory/register.ts +9 -1
  698. package/src/tools/network/__tests__/web-search.test.ts +156 -0
  699. package/src/tools/network/web-search.ts +280 -37
  700. package/src/tools/permission-checker.ts +28 -5
  701. package/src/tools/skills/load.ts +24 -20
  702. package/src/tools/subagent/spawn.ts +3 -3
  703. package/src/tools/terminal/shell.ts +44 -0
  704. package/src/tools/tool-name-aliases.ts +19 -0
  705. package/src/tools/types.ts +19 -1
  706. package/src/usage/attribution.ts +3 -2
  707. package/src/util/pricing.ts +86 -160
  708. package/src/watcher/__tests__/engine.test.ts +301 -0
  709. package/src/watcher/constants.ts +7 -0
  710. package/src/watcher/engine.ts +90 -90
  711. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +6 -9
  712. package/src/workspace/migrations/054-seed-recall-callsite.ts +10 -1
  713. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +28 -4
  714. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +4 -62
  715. package/src/workspace/migrations/069-seed-onboarding-threads.ts +34 -0
  716. package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
  717. package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
  718. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +104 -0
  719. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +93 -0
  720. package/src/workspace/migrations/074-drop-deprecated-secret-detection-keys.ts +117 -0
  721. package/src/workspace/migrations/075-memory-v2-bm25-b-default-reembed.ts +61 -0
  722. package/src/workspace/migrations/076-drop-services-inference-mode.ts +62 -0
  723. package/src/workspace/migrations/077-seed-memory-router-callsite.ts +89 -0
  724. package/src/workspace/migrations/078-release-notes-tavily-web-search.ts +66 -0
  725. package/src/workspace/migrations/079-home-feed-notification-only.ts +197 -0
  726. package/src/workspace/migrations/080-restrict-vercel-api-token-metadata.ts +182 -0
  727. package/src/workspace/migrations/081-backfill-bash-allowed-tools-for-injection-credentials.ts +160 -0
  728. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +154 -0
  729. package/src/workspace/migrations/registry.ts +28 -0
  730. package/src/workspace/migrations/runner.ts +13 -2
  731. package/src/workspace/migrations/types.ts +13 -3
  732. package/src/workspace/provider-commit-message-generator.ts +3 -2
  733. package/src/__tests__/context-search-pkb-source.test.ts +0 -492
  734. package/src/__tests__/credentials-cli.test.ts +0 -1225
  735. package/src/__tests__/memory-admin-recall.test.ts +0 -213
  736. package/src/approvals/__tests__/guardian-feed-event.test.ts +0 -303
  737. package/src/cli/commands/__tests__/email-download.test.ts +0 -260
  738. package/src/cli/commands/__tests__/email-list.test.ts +0 -216
  739. package/src/cli/commands/__tests__/email-register.test.ts +0 -186
  740. package/src/cli/commands/__tests__/email-send.test.ts +0 -416
  741. package/src/cli/commands/__tests__/email-status.test.ts +0 -185
  742. package/src/cli/commands/__tests__/email-unregister.test.ts +0 -168
  743. package/src/cli/commands/__tests__/routes.test.ts +0 -562
  744. package/src/cli/commands/__tests__/stt-transcribe.test.ts +0 -454
  745. package/src/cli/commands/autonomy.ts +0 -365
  746. package/src/cli/commands/memory.ts +0 -424
  747. package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -1201
  748. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +0 -686
  749. package/src/cli/commands/oauth/__tests__/mode.test.ts +0 -632
  750. package/src/cli/commands/oauth/__tests__/ping.test.ts +0 -631
  751. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +0 -573
  752. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +0 -330
  753. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +0 -521
  754. package/src/cli/commands/oauth/__tests__/status.test.ts +0 -551
  755. package/src/cli/commands/oauth/__tests__/token.test.ts +0 -420
  756. package/src/cli/lib/daemon-avatar-client.ts +0 -37
  757. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -87
  758. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +0 -207
  759. package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -304
  760. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +0 -233
  761. package/src/home/__tests__/assistant-feed-authoring.test.ts +0 -156
  762. package/src/home/__tests__/emit-feed-event.test.ts +0 -169
  763. package/src/home/__tests__/feed-population-integration.test.ts +0 -312
  764. package/src/home/__tests__/feed-scheduler.test.ts +0 -222
  765. package/src/home/__tests__/phase5-exit-criteria.test.ts +0 -229
  766. package/src/home/__tests__/platform-gmail-digest.test.ts +0 -222
  767. package/src/home/__tests__/rollup-producer.test.ts +0 -507
  768. package/src/home/assistant-feed-authoring.ts +0 -135
  769. package/src/home/emit-feed-event.ts +0 -169
  770. package/src/home/feed-scheduler.ts +0 -281
  771. package/src/home/platform-gmail-digest.ts +0 -163
  772. package/src/home/rewrite-command-preview.ts +0 -66
  773. package/src/home/rewrite-feed-title.ts +0 -58
  774. package/src/home/rollup-producer.ts +0 -426
  775. package/src/memory/admin.ts +0 -326
  776. package/src/memory/context-search/sources/pkb.ts +0 -477
  777. package/src/memory/graph/compaction.ts +0 -299
  778. /package/src/cli/{commands → lib}/cache-fs.ts +0 -0
@@ -1,650 +1,285 @@
1
1
  /**
2
- * Tests for the `vellum backup` CLI command tree.
2
+ * Tests for the `assistant backup` CLI command tree.
3
3
  *
4
- * These tests mock out the config loader (so state lives in an in-memory
5
- * record), the backup/restore libraries (so we don't touch the filesystem or
6
- * rely on a real workspace), and the memory checkpoint store (so `status`
7
- * can be driven with a known last-run timestamp). Each test drives the
8
- * handlers either directly (for fine-grained assertions on persisted state)
9
- * or via a commander program (for end-to-end arg parsing).
4
+ * Uses the IPC mock pattern: cliIpcCall is stubbed out so tests assert the
5
+ * correct method + params are sent without touching the daemon or filesystem.
10
6
  */
11
7
 
12
- import {
13
- afterEach,
14
- beforeEach,
15
- describe,
16
- expect,
17
- mock,
18
- test,
19
- } from "bun:test";
8
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
20
9
 
21
10
  import { Command } from "commander";
22
11
 
23
- import type {
24
- BackupConfig,
25
- BackupDestination,
26
- } from "../../../config/schema.js";
27
-
28
12
  // ---------------------------------------------------------------------------
29
13
  // Mock state
30
14
  // ---------------------------------------------------------------------------
31
15
 
32
- /** The raw `config.json` record the command sees, shared across mocks. */
33
- let mockRawConfig: Record<string, unknown> = {};
34
-
35
- /** History of `saveRawConfig` calls so tests can assert persist-order. */
36
- let mockSaveRawConfigCalls: Array<Record<string, unknown>> = [];
37
-
38
- /** Memory checkpoint the mocked `getMemoryCheckpoint` should return. */
39
- let mockLastRunAt: string | null = null;
40
-
41
- /** Snapshot listings per directory, keyed by absolute path. */
42
- let mockSnapshots: Record<
43
- string,
44
- Array<{
45
- path: string;
46
- filename: string;
47
- createdAt: Date;
48
- sizeBytes: number;
49
- encrypted: boolean;
50
- }>
51
- > = {};
52
-
53
- /** Result returned by the stubbed `verifySnapshot`. */
54
- let mockVerifyResult: {
55
- valid: boolean;
56
- manifest?: {
57
- schema_version: string;
58
- created_at: string;
59
- source?: string;
60
- description?: string;
61
- files: unknown[];
62
- manifest_sha256: string;
63
- };
64
- error?: string;
65
- } = { valid: true };
66
-
67
- /** Result returned by the stubbed `restoreFromSnapshot`. */
68
- let mockRestoreResult: {
69
- manifest: {
70
- schema_version: string;
71
- created_at: string;
72
- source?: string;
73
- files: unknown[];
74
- manifest_sha256: string;
75
- };
76
- restoredFiles: number;
77
- } = {
78
- manifest: {
79
- schema_version: "1.0.0",
80
- created_at: "2026-04-11T09:30:00Z",
81
- source: "test",
82
- files: [],
83
- manifest_sha256: "abc",
84
- },
85
- restoredFiles: 42,
86
- };
16
+ /** The last `cliIpcCall` invocation captured for assertions. */
17
+ let lastIpcCall: { method: string; params?: any } | null = null;
87
18
 
88
- /** Result returned by the stubbed `createSnapshotNow`. */
89
- let mockCreateSnapshotResult: {
90
- local: {
91
- path: string;
92
- filename: string;
93
- createdAt: Date;
94
- sizeBytes: number;
95
- encrypted: boolean;
96
- };
97
- offsite: Array<{
98
- destination: BackupDestination;
99
- entry: {
100
- path: string;
101
- filename: string;
102
- createdAt: Date;
103
- sizeBytes: number;
104
- encrypted: boolean;
105
- } | null;
106
- skipped?: "parent-missing";
107
- error?: string;
108
- }>;
109
- durationMs: number;
110
- } = {
111
- local: {
112
- path: "/tmp/local/backup-20260411-093000.vbundle",
113
- filename: "backup-20260411-093000.vbundle",
114
- createdAt: new Date("2026-04-11T09:30:00Z"),
115
- sizeBytes: 1024,
116
- encrypted: false,
117
- },
118
- offsite: [],
119
- durationMs: 123,
19
+ /** The result that cliIpcCall will return. */
20
+ let mockIpcResult: { ok: boolean; result?: unknown; error?: string } = {
21
+ ok: true,
22
+ result: {},
120
23
  };
121
24
 
122
- /** Whether `createSnapshotNow` should throw concurrency error. */
123
- let mockCreateShouldThrow: Error | null = null;
124
-
125
- /** Whether the stubbed `isDaemonRunning` should report the assistant as alive. */
126
- let mockDaemonRunning = false;
127
-
128
- /** Sequence of recovery calls so tests can assert ordering. */
129
- const recoveryCallOrder: string[] = [];
130
-
131
- /** Number of times each recovery helper was invoked. */
132
- let _mockResetDbCalls = 0;
133
- let _mockInvalidateConfigCacheCalls = 0;
134
-
135
- /** Log calls captured by the mocked logger. */
136
- let mockLogInfo: string[] = [];
137
- let mockLogError: string[] = [];
25
+ /** Captured log output for assertion. */
26
+ let logOutput: string[] = [];
138
27
 
139
28
  // ---------------------------------------------------------------------------
140
- // Mocks (must be registered before importing the module under test)
29
+ // Mocks
141
30
  // ---------------------------------------------------------------------------
142
31
 
143
- mock.module("../../../config/loader.js", () => ({
144
- loadRawConfig: () => mockRawConfig,
145
- saveRawConfig: (config: Record<string, unknown>) => {
146
- mockRawConfig = structuredClone(config);
147
- mockSaveRawConfigCalls.push(structuredClone(config));
148
- },
149
- setNestedValue: (
150
- obj: Record<string, unknown>,
151
- path: string,
152
- value: unknown,
153
- ) => {
154
- const keys = path.split(".");
155
- let current: Record<string, unknown> = obj;
156
- for (let i = 0; i < keys.length - 1; i++) {
157
- const key = keys[i]!;
158
- if (current[key] == null || typeof current[key] !== "object") {
159
- current[key] = {};
160
- }
161
- current = current[key] as Record<string, unknown>;
162
- }
163
- current[keys[keys.length - 1]!] = value;
164
- },
165
- getConfig: () => ({
166
- backup: getComputedBackupConfig(),
167
- }),
168
- invalidateConfigCache: () => {
169
- _mockInvalidateConfigCacheCalls += 1;
170
- recoveryCallOrder.push("invalidateConfigCache");
171
- },
172
- }));
173
-
174
- mock.module("../../../daemon/daemon-control.js", () => ({
175
- isDaemonRunning: () => mockDaemonRunning,
176
- }));
177
-
178
- mock.module("../../../memory/db-connection.js", () => ({
179
- resetDb: () => {
180
- _mockResetDbCalls += 1;
181
- recoveryCallOrder.push("resetDb");
32
+ mock.module("../../../ipc/cli-client.js", () => ({
33
+ cliIpcCall: async (method: string, params?: any) => {
34
+ lastIpcCall = { method, params };
35
+ return mockIpcResult;
182
36
  },
183
- }));
184
-
185
- mock.module("../../../permissions/trust-store.js", () => ({
186
- clearCache: () => { },
187
- }));
188
-
189
- mock.module("../../../memory/checkpoints.js", () => ({
190
- getMemoryCheckpoint: (key: string) =>
191
- key === "backup:last_run_at" ? mockLastRunAt : null,
192
- }));
193
-
194
- mock.module("../../../backup/list-snapshots.js", () => ({
195
- listSnapshotsInDir: async (dir: string) => mockSnapshots[dir] ?? [],
196
- }));
197
-
198
- mock.module("../../../backup/paths.js", () => ({
199
- getLocalBackupsDir: (override?: string | null) =>
200
- override ?? "/tmp/local",
201
- getBackupKeyPath: () => "/tmp/backup.key",
202
- resolveOffsiteDestinations: (
203
- override?: BackupDestination[] | null,
204
- ): BackupDestination[] => {
205
- if (override == null) {
206
- return [{ path: "/icloud/default", encrypt: true }];
207
- }
208
- return override;
37
+ exitFromIpcResult: (r: any) => {
38
+ process.exitCode = 1;
39
+ throw new Error(r.error ?? "ipc error");
209
40
  },
210
- getDefaultOffsiteBackupsDir: () => "/icloud/default",
211
- formatBackupFilename: (
212
- date: Date,
213
- { encrypted }: { encrypted: boolean },
214
- ) => `backup-${date.toISOString()}${encrypted ? ".vbundle.enc" : ".vbundle"}`,
215
- parseBackupTimestamp: () => null,
216
41
  }));
217
42
 
218
- mock.module("../../../backup/backup-key.js", () => ({
219
- readBackupKey: async () => Buffer.alloc(32),
220
- ensureBackupKey: async () => Buffer.alloc(32),
221
- }));
222
-
223
- mock.module("../../../backup/restore.js", () => ({
224
- verifySnapshot: async () => mockVerifyResult,
225
- restoreFromSnapshot: async () => {
226
- recoveryCallOrder.push("restoreFromSnapshot");
227
- return mockRestoreResult;
228
- },
229
- }));
230
-
231
- mock.module("../../../backup/backup-worker.js", () => ({
232
- createSnapshotNow: async () => {
233
- if (mockCreateShouldThrow) throw mockCreateShouldThrow;
234
- return mockCreateSnapshotResult;
235
- },
236
- }));
237
-
238
- mock.module("../../../runtime/migrations/vbundle-import-analyzer.js", () => ({
239
- DefaultPathResolver: class {
240
- constructor(..._args: unknown[]) {}
241
- resolve(): null {
242
- return null;
243
- }
244
- },
245
- }));
246
-
247
- mock.module("../../../util/platform.js", () => ({
248
- getWorkspaceDir: () => "/tmp/workspace",
249
- getWorkspaceHooksDir: () => "/tmp/workspace/hooks",
250
- }));
251
-
252
- mock.module("../../../util/logger.js", () => ({
253
- getLogger: () => ({
254
- info: () => {},
255
- warn: () => {},
256
- error: () => {},
257
- debug: () => {},
258
- }),
259
- getCliLogger: () => ({
260
- info: (msg: string) => mockLogInfo.push(msg),
261
- warn: () => {},
262
- error: (msg: string) => mockLogError.push(msg),
263
- debug: () => {},
264
- }),
265
- }));
43
+ const capture = (...args: unknown[]) => {
44
+ logOutput.push(args.map(String).join(" "));
45
+ };
46
+ const fakeLogger = {
47
+ info: capture,
48
+ warn: capture,
49
+ error: capture,
50
+ debug: () => {},
51
+ };
266
52
 
267
53
  mock.module("../../logger.js", () => ({
268
- log: {
269
- info: (msg: string) => mockLogInfo.push(msg),
270
- warn: () => {},
271
- error: (msg: string) => mockLogError.push(msg),
272
- debug: () => {},
273
- },
274
- getCliLogger: () => ({
275
- info: (msg: string) => mockLogInfo.push(msg),
276
- warn: () => {},
277
- error: (msg: string) => mockLogError.push(msg),
278
- debug: () => {},
279
- }),
54
+ log: fakeLogger,
55
+ getCliLogger: () => fakeLogger,
280
56
  }));
281
57
 
282
58
  // ---------------------------------------------------------------------------
283
- // Helpers
59
+ // Import module under test (after mocks)
284
60
  // ---------------------------------------------------------------------------
285
61
 
286
- /**
287
- * Compute the "validated" backup config the command sees via `getConfig`.
288
- * Reads the shared `mockRawConfig` record and applies schema defaults for
289
- * any missing keys. We can't use the real Zod schema here because the
290
- * config/loader mock above intercepts the whole module.
291
- */
292
- function getComputedBackupConfig(): BackupConfig {
293
- const raw =
294
- (mockRawConfig.backup as Record<string, unknown> | undefined) ?? {};
295
- const offsite = (raw.offsite as Record<string, unknown> | undefined) ?? {};
296
- return {
297
- enabled: (raw.enabled as boolean | undefined) ?? false,
298
- intervalHours: (raw.intervalHours as number | undefined) ?? 6,
299
- retention: (raw.retention as number | undefined) ?? 3,
300
- offsite: {
301
- enabled: (offsite.enabled as boolean | undefined) ?? true,
302
- destinations:
303
- (offsite.destinations as BackupDestination[] | null | undefined) ??
304
- null,
305
- },
306
- localDirectory:
307
- (raw.localDirectory as string | null | undefined) ?? null,
308
- };
309
- }
62
+ const { registerBackupCommand } = await import("../backup.js");
310
63
 
311
64
  // ---------------------------------------------------------------------------
312
- // Import module under test (after mocks are registered)
65
+ // Test helpers
313
66
  // ---------------------------------------------------------------------------
314
67
 
315
- const backupMod = await import("../backup.js");
316
- const {
317
- handleEnable,
318
- handleDisable,
319
- handleDestinationsAdd,
320
- handleDestinationsRemove,
321
- handleDestinationsSetEncrypt,
322
- handleDestinationsList,
323
- handleStatus,
324
- handleList,
325
- registerBackupCommand,
326
- } = backupMod;
68
+ function buildProgram(): Command {
69
+ const program = new Command();
70
+ program.exitOverride();
71
+ program.configureOutput({
72
+ writeErr: () => {},
73
+ writeOut: () => {},
74
+ });
75
+ registerBackupCommand(program);
76
+ return program;
77
+ }
78
+
79
+ async function runCommand(args: string[]): Promise<{ exitCode: number }> {
80
+ process.exitCode = 0;
81
+ try {
82
+ const program = buildProgram();
83
+ await program.parseAsync(["node", "assistant", ...args]);
84
+ } catch {
85
+ if (process.exitCode === 0) process.exitCode = 1;
86
+ }
87
+ const exitCode = process.exitCode ?? 0;
88
+ process.exitCode = 0;
89
+ return { exitCode };
90
+ }
327
91
 
328
92
  // ---------------------------------------------------------------------------
329
- // Reset between tests
93
+ // Setup
330
94
  // ---------------------------------------------------------------------------
331
95
 
332
96
  beforeEach(() => {
333
- mockRawConfig = {};
334
- mockSaveRawConfigCalls = [];
335
- mockLastRunAt = null;
336
- mockSnapshots = {};
337
- mockLogInfo = [];
338
- mockLogError = [];
339
- mockCreateShouldThrow = null;
340
- mockDaemonRunning = false;
341
- _mockResetDbCalls = 0;
342
- _mockInvalidateConfigCacheCalls = 0;
343
- recoveryCallOrder.length = 0;
344
- process.exitCode = 0;
345
- mockVerifyResult = { valid: true };
346
- mockRestoreResult = {
347
- manifest: {
348
- schema_version: "1.0.0",
349
- created_at: "2026-04-11T09:30:00Z",
350
- source: "test",
351
- files: [],
352
- manifest_sha256: "abc",
353
- },
354
- restoredFiles: 42,
355
- };
356
- mockCreateSnapshotResult = {
357
- local: {
358
- path: "/tmp/local/backup-20260411-093000.vbundle",
359
- filename: "backup-20260411-093000.vbundle",
360
- createdAt: new Date("2026-04-11T09:30:00Z"),
361
- sizeBytes: 1024,
362
- encrypted: false,
363
- },
364
- offsite: [],
365
- durationMs: 123,
366
- };
367
- });
368
-
369
- afterEach(() => {
97
+ lastIpcCall = null;
98
+ mockIpcResult = { ok: true, result: {} };
99
+ logOutput = [];
370
100
  process.exitCode = 0;
371
101
  });
372
102
 
373
103
  // ---------------------------------------------------------------------------
374
- // enable / disable
104
+ // enable
375
105
  // ---------------------------------------------------------------------------
376
106
 
377
- describe("handleEnable", () => {
378
- test("persists backup.enabled=true and no other overrides", () => {
379
- handleEnable({});
380
- expect(mockSaveRawConfigCalls.length).toBe(1);
381
- const saved = mockSaveRawConfigCalls[0]!;
382
- expect(
383
- (saved.backup as Record<string, unknown>).enabled,
384
- ).toBe(true);
385
- expect(
386
- (saved.backup as Record<string, unknown>).intervalHours,
387
- ).toBeUndefined();
388
- });
389
-
390
- test("applies --interval and --retention overrides", () => {
391
- handleEnable({ interval: "12", retention: "14" });
392
- const saved = mockSaveRawConfigCalls[0]!;
393
- const cfg = saved.backup as Record<string, unknown>;
394
- expect(cfg.enabled).toBe(true);
395
- expect(cfg.intervalHours).toBe(12);
396
- expect(cfg.retention).toBe(14);
397
- });
398
-
399
- test("rejects non-numeric interval", () => {
400
- handleEnable({ interval: "abc" });
401
- expect(process.exitCode).toBe(1);
402
- expect(mockSaveRawConfigCalls.length).toBe(0);
403
- expect(mockLogError.some((m) => m.includes("--interval"))).toBe(true);
107
+ describe("backup enable", () => {
108
+ test("basic enable sends backup_enable with no extra params", async () => {
109
+ mockIpcResult = {
110
+ ok: true,
111
+ result: {
112
+ enabled: true,
113
+ intervalHours: 6,
114
+ retention: 3,
115
+ offsite: { enabled: true },
116
+ },
117
+ };
118
+ const { exitCode } = await runCommand(["backup", "enable"]);
119
+ expect(exitCode).toBe(0);
120
+ expect(lastIpcCall!.method).toBe("backup_enable");
121
+ // Should not have intervalHours or retention if not specified
122
+ expect((lastIpcCall!.params?.body as Record<string, unknown>)?.intervalHours).toBeUndefined();
123
+ expect((lastIpcCall!.params?.body as Record<string, unknown>)?.retention).toBeUndefined();
124
+ expect((lastIpcCall!.params?.body as Record<string, unknown>)?.offsiteEnabled).toBeUndefined();
404
125
  });
405
126
 
406
- test("rejects zero retention", () => {
407
- handleEnable({ retention: "0" });
408
- expect(process.exitCode).toBe(1);
409
- expect(mockSaveRawConfigCalls.length).toBe(0);
127
+ test("--interval 12 --retention 7 sends correct params", async () => {
128
+ mockIpcResult = {
129
+ ok: true,
130
+ result: {
131
+ enabled: true,
132
+ intervalHours: 12,
133
+ retention: 7,
134
+ offsite: { enabled: true },
135
+ },
136
+ };
137
+ const { exitCode } = await runCommand([
138
+ "backup",
139
+ "enable",
140
+ "--interval",
141
+ "12",
142
+ "--retention",
143
+ "7",
144
+ ]);
145
+ expect(exitCode).toBe(0);
146
+ expect(lastIpcCall!.method).toBe("backup_enable");
147
+ expect(lastIpcCall!.params).toEqual({ body: { intervalHours: 12, retention: 7 } });
410
148
  });
411
149
 
412
- test("--no-offsite sets offsite.enabled=false but leaves destinations untouched", () => {
413
- mockRawConfig = {
414
- backup: {
415
- offsite: {
416
- destinations: [{ path: "/tmp/x", encrypt: true }],
417
- },
150
+ test("--no-offsite sends offsiteEnabled: false", async () => {
151
+ mockIpcResult = {
152
+ ok: true,
153
+ result: {
154
+ enabled: true,
155
+ intervalHours: 6,
156
+ retention: 3,
157
+ offsite: { enabled: false },
418
158
  },
419
159
  };
420
- handleEnable({ offsite: false });
421
- const saved = mockSaveRawConfigCalls[0]!;
422
- const cfg = saved.backup as Record<string, unknown>;
423
- expect(cfg.enabled).toBe(true);
424
- expect(
425
- (cfg.offsite as Record<string, unknown>).enabled,
426
- ).toBe(false);
427
- // destinations preserved exactly as-is
428
- expect(
429
- (cfg.offsite as Record<string, unknown>).destinations,
430
- ).toEqual([{ path: "/tmp/x", encrypt: true }]);
160
+ const { exitCode } = await runCommand(["backup", "enable", "--no-offsite"]);
161
+ expect(exitCode).toBe(0);
162
+ expect(lastIpcCall!.method).toBe("backup_enable");
163
+ expect((lastIpcCall!.params?.body as Record<string, unknown>)?.offsiteEnabled).toBe(false);
431
164
  });
432
165
  });
433
166
 
434
- describe("handleDisable", () => {
435
- test("persists backup.enabled=false", () => {
436
- mockRawConfig = { backup: { enabled: true, intervalHours: 6 } };
437
- handleDisable();
438
- const saved = mockSaveRawConfigCalls[0]!;
439
- const cfg = saved.backup as Record<string, unknown>;
440
- expect(cfg.enabled).toBe(false);
441
- // other fields preserved
442
- expect(cfg.intervalHours).toBe(6);
167
+ // ---------------------------------------------------------------------------
168
+ // disable
169
+ // ---------------------------------------------------------------------------
170
+
171
+ describe("backup disable", () => {
172
+ test("sends backup_disable", async () => {
173
+ const { exitCode } = await runCommand(["backup", "disable"]);
174
+ expect(exitCode).toBe(0);
175
+ expect(lastIpcCall!.method).toBe("backup_disable");
443
176
  });
444
177
  });
445
178
 
446
179
  // ---------------------------------------------------------------------------
447
- // destinations add / remove / set-encrypt
180
+ // destinations list
448
181
  // ---------------------------------------------------------------------------
449
182
 
450
- describe("handleDestinationsAdd", () => {
451
- test("on null destinations: materializes iCloud default then appends", () => {
452
- // Start with completely empty config destinations resolves to iCloud default.
453
- handleDestinationsAdd("/tmp/x", {});
454
- const saved = mockSaveRawConfigCalls[0]!;
455
- const destinations = (
456
- (saved.backup as Record<string, unknown>).offsite as Record<
457
- string,
458
- unknown
459
- >
460
- ).destinations as BackupDestination[];
461
- expect(destinations).toHaveLength(2);
462
- expect(destinations[0]).toEqual({
463
- path: "/icloud/default",
464
- encrypt: true,
465
- });
466
- expect(destinations[1]).toEqual({ path: "/tmp/x", encrypt: true });
183
+ describe("backup destinations list", () => {
184
+ test("sends backup_destinations_list", async () => {
185
+ mockIpcResult = { ok: true, result: { destinations: [] } };
186
+ const { exitCode } = await runCommand(["backup", "destinations", "list"]);
187
+ expect(exitCode).toBe(0);
188
+ expect(lastIpcCall!.method).toBe("backup_destinations_list");
467
189
  });
190
+ });
468
191
 
469
- test("--plaintext stores encrypt: false", () => {
470
- handleDestinationsAdd("/tmp/x", { plaintext: true });
471
- const saved = mockSaveRawConfigCalls[0]!;
472
- const destinations = (
473
- (saved.backup as Record<string, unknown>).offsite as Record<
474
- string,
475
- unknown
476
- >
477
- ).destinations as BackupDestination[];
478
- // 2 entries: iCloud default + new plaintext /tmp/x
479
- const tmpEntry = destinations.find((d) => d.path === "/tmp/x")!;
480
- expect(tmpEntry).toEqual({ path: "/tmp/x", encrypt: false });
481
- });
192
+ // ---------------------------------------------------------------------------
193
+ // destinations add
194
+ // ---------------------------------------------------------------------------
482
195
 
483
- test("appends to existing explicit array without re-materializing default", () => {
484
- mockRawConfig = {
485
- backup: {
486
- offsite: {
487
- destinations: [{ path: "/existing", encrypt: true }],
488
- },
489
- },
196
+ describe("backup destinations add", () => {
197
+ test("sends backup_destinations_add with encrypt: true by default", async () => {
198
+ mockIpcResult = {
199
+ ok: true,
200
+ result: { destinations: [{ path: "/tmp/foo", encrypt: true }] },
490
201
  };
491
- handleDestinationsAdd("/new", { plaintext: true });
492
- const saved = mockSaveRawConfigCalls[0]!;
493
- const destinations = (
494
- (saved.backup as Record<string, unknown>).offsite as Record<
495
- string,
496
- unknown
497
- >
498
- ).destinations as BackupDestination[];
499
- expect(destinations).toEqual([
500
- { path: "/existing", encrypt: true },
501
- { path: "/new", encrypt: false },
202
+ const { exitCode } = await runCommand([
203
+ "backup",
204
+ "destinations",
205
+ "add",
206
+ "/tmp/foo",
502
207
  ]);
208
+ expect(exitCode).toBe(0);
209
+ expect(lastIpcCall!.method).toBe("backup_destinations_add");
210
+ expect(lastIpcCall!.params).toEqual({ body: { path: "/tmp/foo", encrypt: true } });
503
211
  });
504
212
 
505
- test("duplicate path errors", () => {
506
- mockRawConfig = {
507
- backup: {
508
- offsite: {
509
- destinations: [{ path: "/dup", encrypt: true }],
510
- },
511
- },
213
+ test("--plaintext sends encrypt: false", async () => {
214
+ mockIpcResult = {
215
+ ok: true,
216
+ result: { destinations: [{ path: "/tmp/foo", encrypt: false }] },
512
217
  };
513
- handleDestinationsAdd("/dup", {});
514
- expect(process.exitCode).toBe(1);
515
- expect(mockSaveRawConfigCalls.length).toBe(0);
218
+ const { exitCode } = await runCommand([
219
+ "backup",
220
+ "destinations",
221
+ "add",
222
+ "/tmp/foo",
223
+ "--plaintext",
224
+ ]);
225
+ expect(exitCode).toBe(0);
226
+ expect(lastIpcCall!.method).toBe("backup_destinations_add");
227
+ expect(lastIpcCall!.params).toEqual({ body: { path: "/tmp/foo", encrypt: false } });
516
228
  });
517
229
  });
518
230
 
519
- describe("handleDestinationsRemove", () => {
520
- test("removes matching entry", () => {
521
- mockRawConfig = {
522
- backup: {
523
- offsite: {
524
- destinations: [
525
- { path: "/a", encrypt: true },
526
- { path: "/b", encrypt: false },
527
- ],
528
- },
529
- },
530
- };
531
- handleDestinationsRemove("/a");
532
- const saved = mockSaveRawConfigCalls[0]!;
533
- const destinations = (
534
- (saved.backup as Record<string, unknown>).offsite as Record<
535
- string,
536
- unknown
537
- >
538
- ).destinations as BackupDestination[];
539
- expect(destinations).toEqual([{ path: "/b", encrypt: false }]);
540
- });
231
+ // ---------------------------------------------------------------------------
232
+ // destinations remove
233
+ // ---------------------------------------------------------------------------
541
234
 
542
- test("errors on nonexistent path", () => {
543
- mockRawConfig = {
544
- backup: {
545
- offsite: {
546
- destinations: [{ path: "/a", encrypt: true }],
547
- },
548
- },
549
- };
550
- handleDestinationsRemove("/nonexistent");
551
- expect(process.exitCode).toBe(1);
552
- expect(mockSaveRawConfigCalls.length).toBe(0);
553
- expect(mockLogError.some((m) => m.includes("not found"))).toBe(true);
235
+ describe("backup destinations remove", () => {
236
+ test("sends backup_destinations_remove with correct path", async () => {
237
+ mockIpcResult = { ok: true, result: { destinations: [] } };
238
+ const { exitCode } = await runCommand([
239
+ "backup",
240
+ "destinations",
241
+ "remove",
242
+ "/tmp/foo",
243
+ ]);
244
+ expect(exitCode).toBe(0);
245
+ expect(lastIpcCall!.method).toBe("backup_destinations_remove");
246
+ expect((lastIpcCall!.params?.body as Record<string, unknown>)?.path).toBe("/tmp/foo");
554
247
  });
555
248
  });
556
249
 
557
- describe("handleDestinationsSetEncrypt", () => {
558
- test("flips encrypt flag to false", () => {
559
- mockRawConfig = {
560
- backup: {
561
- offsite: {
562
- destinations: [{ path: "/x", encrypt: true }],
563
- },
564
- },
565
- };
566
- handleDestinationsSetEncrypt("/x", "false");
567
- const saved = mockSaveRawConfigCalls[0]!;
568
- const destinations = (
569
- (saved.backup as Record<string, unknown>).offsite as Record<
570
- string,
571
- unknown
572
- >
573
- ).destinations as BackupDestination[];
574
- expect(destinations).toEqual([{ path: "/x", encrypt: false }]);
575
- });
576
-
577
- test("flips encrypt flag to true", () => {
578
- mockRawConfig = {
579
- backup: {
580
- offsite: {
581
- destinations: [{ path: "/x", encrypt: false }],
582
- },
583
- },
584
- };
585
- handleDestinationsSetEncrypt("/x", "true");
586
- const saved = mockSaveRawConfigCalls[0]!;
587
- const destinations = (
588
- (saved.backup as Record<string, unknown>).offsite as Record<
589
- string,
590
- unknown
591
- >
592
- ).destinations as BackupDestination[];
593
- expect(destinations[0]!.encrypt).toBe(true);
594
- });
595
-
596
- test("rejects non-boolean value", () => {
597
- mockRawConfig = {
598
- backup: {
599
- offsite: {
600
- destinations: [{ path: "/x", encrypt: false }],
601
- },
602
- },
603
- };
604
- handleDestinationsSetEncrypt("/x", "yes");
605
- expect(process.exitCode).toBe(1);
606
- expect(mockSaveRawConfigCalls.length).toBe(0);
607
- });
608
-
609
- test("errors on nonexistent path", () => {
610
- handleDestinationsSetEncrypt("/missing", "true");
611
- expect(process.exitCode).toBe(1);
612
- expect(mockSaveRawConfigCalls.length).toBe(0);
613
- });
614
- });
250
+ // ---------------------------------------------------------------------------
251
+ // destinations set-encrypt
252
+ // ---------------------------------------------------------------------------
615
253
 
616
- describe("handleDestinationsList", () => {
617
- test("empty state shows friendly message", async () => {
618
- mockRawConfig = {
619
- backup: {
620
- offsite: {
621
- destinations: [],
622
- },
623
- },
254
+ describe("backup destinations set-encrypt", () => {
255
+ test("sends backup_destinations_set_encrypt with encrypt: true", async () => {
256
+ mockIpcResult = {
257
+ ok: true,
258
+ result: { destination: { path: "/tmp/foo", encrypt: true } },
624
259
  };
625
- await handleDestinationsList();
626
- expect(
627
- mockLogInfo.some((m) => m.includes("No offsite destinations")),
628
- ).toBe(true);
260
+ const { exitCode } = await runCommand([
261
+ "backup",
262
+ "destinations",
263
+ "set-encrypt",
264
+ "/tmp/foo",
265
+ "true",
266
+ ]);
267
+ expect(exitCode).toBe(0);
268
+ expect(lastIpcCall!.method).toBe("backup_destinations_set_encrypt");
269
+ expect(lastIpcCall!.params).toEqual({ body: { path: "/tmp/foo", encrypt: true } });
629
270
  });
630
271
 
631
- test("lists all destinations with encryption flag", async () => {
632
- mockRawConfig = {
633
- backup: {
634
- offsite: {
635
- destinations: [
636
- { path: "/a", encrypt: true },
637
- { path: "/b", encrypt: false },
638
- ],
639
- },
640
- },
641
- };
642
- await handleDestinationsList();
643
- const out = mockLogInfo.join("\n");
644
- expect(out).toContain("/a");
645
- expect(out).toContain("/b");
646
- expect(out).toContain("yes");
647
- expect(out).toContain("no");
272
+ test("invalid value exits with code 1 without calling IPC", async () => {
273
+ const { exitCode } = await runCommand([
274
+ "backup",
275
+ "destinations",
276
+ "set-encrypt",
277
+ "/tmp/foo",
278
+ "yes",
279
+ ]);
280
+ expect(exitCode).toBe(1);
281
+ // IPC should not have been called since we validated locally
282
+ expect(lastIpcCall).toBeNull();
648
283
  });
649
284
  });
650
285
 
@@ -652,55 +287,25 @@ describe("handleDestinationsList", () => {
652
287
  // status
653
288
  // ---------------------------------------------------------------------------
654
289
 
655
- describe("handleStatus", () => {
656
- test("disabled state renders header", async () => {
657
- await handleStatus();
658
- const out = mockLogInfo.join("\n");
659
- expect(out).toContain("Automatic backups: disabled");
660
- expect(out).toContain("Interval:");
661
- expect(out).toContain("Retention:");
662
- expect(out).toContain("Last run:");
663
- });
664
-
665
- test("enabled state with last-run checkpoint and mixed destinations", async () => {
666
- mockRawConfig = {
667
- backup: {
290
+ describe("backup status", () => {
291
+ test("successful status call exits with 0", async () => {
292
+ mockIpcResult = {
293
+ ok: true,
294
+ result: {
668
295
  enabled: true,
669
296
  intervalHours: 6,
670
- retention: 7,
671
- offsite: {
672
- enabled: true,
673
- destinations: [
674
- { path: "/reachable", encrypt: true },
675
- { path: "/unreachable/path", encrypt: false },
676
- ],
677
- },
678
- },
679
- };
680
- // Make the first destination reachable by putting a snapshot at its dir.
681
- // Our mocked list-snapshots returns whatever is in mockSnapshots — but
682
- // reachability is probed via fs/promises.stat(dirname(path)), which is
683
- // real. Instead, we rely on: both dirs won't exist → both "[unreachable]"
684
- // in production. For this test we just check the lines render.
685
- mockLastRunAt = String(Date.now() - 60 * 60 * 1000); // 1h ago
686
- await handleStatus();
687
- const out = mockLogInfo.join("\n");
688
- expect(out).toContain("Automatic backups: enabled");
689
- expect(out).toContain("Last run:");
690
- expect(out).toContain("/reachable");
691
- expect(out).toContain("/unreachable/path");
692
- });
693
-
694
- test("offsite disabled shows (disabled) line", async () => {
695
- mockRawConfig = {
696
- backup: {
697
- enabled: true,
698
- offsite: { enabled: false },
297
+ retention: 3,
298
+ lastRunAt: null,
299
+ nextRunAt: null,
300
+ localDir: "/tmp/local",
301
+ localSnapshotCount: 0,
302
+ offsiteEnabled: false,
303
+ offsite: [],
699
304
  },
700
305
  };
701
- await handleStatus();
702
- const out = mockLogInfo.join("\n");
703
- expect(out).toContain("(disabled)");
306
+ const { exitCode } = await runCommand(["backup", "status"]);
307
+ expect(exitCode).toBe(0);
308
+ expect(lastIpcCall!.method).toBe("backup_status");
704
309
  });
705
310
  });
706
311
 
@@ -708,176 +313,31 @@ describe("handleStatus", () => {
708
313
  // list
709
314
  // ---------------------------------------------------------------------------
710
315
 
711
- describe("handleList", () => {
712
- test("empty-state renders per-group '(none)'", async () => {
713
- await handleList();
714
- const out = mockLogInfo.join("\n");
715
- expect(out).toContain("Local:");
716
- // iCloud default is included when offsite.enabled=true by default
717
- expect(out).toContain("(none)");
718
- });
719
-
720
- test("populated local pool renders table rows", async () => {
721
- mockSnapshots["/tmp/local"] = [
722
- {
723
- path: "/tmp/local/backup-20260411-093000.vbundle",
724
- filename: "backup-20260411-093000.vbundle",
725
- createdAt: new Date("2026-04-11T09:30:00Z"),
726
- sizeBytes: 1024,
727
- encrypted: false,
728
- },
729
- ];
730
- await handleList();
731
- const out = mockLogInfo.join("\n");
732
- expect(out).toContain("backup-20260411-093000.vbundle");
733
- expect(out).toContain("Local:");
734
- expect(out).toContain("2026-04-11 09:30 UTC");
735
- });
736
-
737
- test("per-destination grouping with explicit offsite", async () => {
738
- mockRawConfig = {
739
- backup: {
740
- enabled: true,
741
- offsite: {
742
- enabled: true,
743
- destinations: [{ path: "/off1", encrypt: true }],
744
- },
316
+ describe("backup list", () => {
317
+ test("sends backups_list", async () => {
318
+ mockIpcResult = {
319
+ ok: true,
320
+ result: {
321
+ local: [],
322
+ offsite: [],
323
+ offsiteEnabled: false,
324
+ nextRunAt: null,
745
325
  },
746
326
  };
747
- mockSnapshots["/off1"] = [
748
- {
749
- path: "/off1/backup.vbundle.enc",
750
- filename: "backup.vbundle.enc",
751
- createdAt: new Date("2026-04-11T09:30:00Z"),
752
- sizeBytes: 2048,
753
- encrypted: true,
754
- },
755
- ];
756
- await handleList();
757
- const out = mockLogInfo.join("\n");
758
- expect(out).toContain("Offsite: /off1");
759
- expect(out).toContain("encrypted");
327
+ const { exitCode } = await runCommand(["backup", "list"]);
328
+ expect(exitCode).toBe(0);
329
+ expect(lastIpcCall!.method).toBe("backups_list");
760
330
  });
761
331
  });
762
332
 
763
333
  // ---------------------------------------------------------------------------
764
- // create
334
+ // IPC error path
765
335
  // ---------------------------------------------------------------------------
766
336
 
767
- async function runProgram(
768
- args: string[],
769
- ): Promise<{ exitCode: number }> {
770
- process.exitCode = 0;
771
- try {
772
- const program = new Command();
773
- program.exitOverride();
774
- program.configureOutput({
775
- writeErr: () => {},
776
- writeOut: () => {},
777
- });
778
- registerBackupCommand(program);
779
- await program.parseAsync(["node", "assistant", ...args]);
780
- } catch {
781
- if (process.exitCode === 0) process.exitCode = 1;
782
- }
783
- const exitCode = process.exitCode ?? 0;
784
- return { exitCode };
785
- }
786
-
787
- describe("registerBackupCommand (end-to-end)", () => {
788
- test("vellum backup enable persists enabled=true via commander", async () => {
789
- const { exitCode } = await runProgram(["backup", "enable"]);
790
- expect(exitCode).toBe(0);
791
- expect(
792
- mockSaveRawConfigCalls.length,
793
- ).toBeGreaterThanOrEqual(1);
794
- expect(
795
- (mockSaveRawConfigCalls.at(-1)!.backup as Record<string, unknown>)
796
- .enabled,
797
- ).toBe(true);
798
- });
799
-
800
- test("vellum backup destinations add on null field materializes iCloud default", async () => {
801
- const { exitCode } = await runProgram([
802
- "backup",
803
- "destinations",
804
- "add",
805
- "/tmp/x",
806
- ]);
807
- expect(exitCode).toBe(0);
808
- const saved = mockSaveRawConfigCalls.at(-1)!;
809
- const destinations = (
810
- (saved.backup as Record<string, unknown>).offsite as Record<
811
- string,
812
- unknown
813
- >
814
- ).destinations as BackupDestination[];
815
- expect(destinations).toHaveLength(2);
816
- expect(destinations[0]!.path).toBe("/icloud/default");
817
- expect(destinations[1]).toEqual({ path: "/tmp/x", encrypt: true });
818
- });
819
-
820
- test("vellum backup destinations add --plaintext stores encrypt=false", async () => {
821
- const { exitCode } = await runProgram([
822
- "backup",
823
- "destinations",
824
- "add",
825
- "/tmp/ssd",
826
- "--plaintext",
827
- ]);
828
- expect(exitCode).toBe(0);
829
- const saved = mockSaveRawConfigCalls.at(-1)!;
830
- const destinations = (
831
- (saved.backup as Record<string, unknown>).offsite as Record<
832
- string,
833
- unknown
834
- >
835
- ).destinations as BackupDestination[];
836
- const added = destinations.find((d) => d.path === "/tmp/ssd");
837
- expect(added).toEqual({ path: "/tmp/ssd", encrypt: false });
838
- });
839
-
840
- test("vellum backup destinations remove /nonexistent exits with error", async () => {
841
- mockRawConfig = {
842
- backup: {
843
- offsite: {
844
- destinations: [{ path: "/existing", encrypt: true }],
845
- },
846
- },
847
- };
848
- const { exitCode } = await runProgram([
849
- "backup",
850
- "destinations",
851
- "remove",
852
- "/nonexistent",
853
- ]);
337
+ describe("IPC error handling", () => {
338
+ test("IPC error causes process.exitCode = 1", async () => {
339
+ mockIpcResult = { ok: false, error: "daemon not running" };
340
+ const { exitCode } = await runCommand(["backup", "disable"]);
854
341
  expect(exitCode).toBe(1);
855
- expect(mockSaveRawConfigCalls.length).toBe(0);
856
- });
857
-
858
- test("vellum backup destinations set-encrypt flips flag", async () => {
859
- mockRawConfig = {
860
- backup: {
861
- offsite: {
862
- destinations: [{ path: "/tmp/x", encrypt: true }],
863
- },
864
- },
865
- };
866
- const { exitCode } = await runProgram([
867
- "backup",
868
- "destinations",
869
- "set-encrypt",
870
- "/tmp/x",
871
- "false",
872
- ]);
873
- expect(exitCode).toBe(0);
874
- const saved = mockSaveRawConfigCalls.at(-1)!;
875
- const destinations = (
876
- (saved.backup as Record<string, unknown>).offsite as Record<
877
- string,
878
- unknown
879
- >
880
- ).destinations as BackupDestination[];
881
- expect(destinations[0]!.encrypt).toBe(false);
882
342
  });
883
343
  });