@vellumai/assistant 0.8.0 → 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 (692) hide show
  1. package/AGENTS.md +11 -0
  2. package/Dockerfile +5 -4
  3. package/README.md +2 -2
  4. package/docker-entrypoint.sh +16 -0
  5. package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +420 -0
  6. package/eslint-rules/cli-no-daemon-internals.js +283 -0
  7. package/eslint.config.mjs +12 -0
  8. package/knip.json +2 -1
  9. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -1
  10. package/openapi.yaml +4847 -1698
  11. package/package.json +3 -1
  12. package/scripts/generate-openapi.ts +52 -4
  13. package/scripts/sync-llm-catalog.ts +165 -0
  14. package/scripts/sync-web-search-catalog.ts +107 -0
  15. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +169 -0
  16. package/src/__tests__/agent-loop-override-profile.test.ts +26 -1
  17. package/src/__tests__/anthropic-provider.test.ts +92 -2
  18. package/src/__tests__/app-control-flow.test.ts +7 -0
  19. package/src/__tests__/assistant-events-sse-shed.test.ts +232 -0
  20. package/src/__tests__/avatar-identity-sync.test.ts +87 -0
  21. package/src/__tests__/background-workers-disk-pressure.test.ts +11 -22
  22. package/src/__tests__/btw-routes.test.ts +1 -0
  23. package/src/__tests__/call-site-routing-provider.test.ts +172 -45
  24. package/src/__tests__/cancel-resolves-conversation-key.test.ts +44 -3
  25. package/src/__tests__/channel-policy.test.ts +12 -0
  26. package/src/__tests__/checker.test.ts +89 -0
  27. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +35 -7
  28. package/src/__tests__/compact-event-conversation-id-guard.test.ts +33 -5
  29. package/src/__tests__/compaction-strip-metadata-clear.test.ts +26 -1
  30. package/src/__tests__/config-loader-backfill.test.ts +526 -102
  31. package/src/__tests__/config-loader-corrupt.test.ts +68 -0
  32. package/src/__tests__/config-loader-platform-defaults.test.ts +77 -23
  33. package/src/__tests__/config-schema-cmd.test.ts +63 -29
  34. package/src/__tests__/config-schema.test.ts +14 -3
  35. package/src/__tests__/config-set-platform-guard.test.ts +75 -152
  36. package/src/__tests__/config-set-route.test.ts +198 -0
  37. package/src/__tests__/config-watcher.test.ts +6 -0
  38. package/src/__tests__/contacts-tools.test.ts +51 -199
  39. package/src/__tests__/context-search-agent-protocol.test.ts +21 -2
  40. package/src/__tests__/context-search-agent-runner.test.ts +22 -138
  41. package/src/__tests__/context-search-conversations-source.test.ts +42 -16
  42. package/src/__tests__/context-search-fanout.test.ts +20 -157
  43. package/src/__tests__/context-search-memory-v2-source.test.ts +3 -3
  44. package/src/__tests__/context-search-types.test.ts +7 -2
  45. package/src/__tests__/context-window-manager.test.ts +389 -1
  46. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  47. package/src/__tests__/conversation-crud-inference-profile.test.ts +100 -0
  48. package/src/__tests__/conversation-error.test.ts +38 -0
  49. package/src/__tests__/conversation-fork-crud.test.ts +241 -1
  50. package/src/__tests__/conversation-inference-profile-route.test.ts +14 -14
  51. package/src/__tests__/conversation-init.benchmark.test.ts +1 -0
  52. package/src/__tests__/conversation-lifecycle.test.ts +124 -0
  53. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +100 -1
  54. package/src/__tests__/conversation-process-callsite.test.ts +21 -1
  55. package/src/__tests__/conversation-runtime-assembly.test.ts +4 -4
  56. package/src/__tests__/conversation-slash-commands.test.ts +194 -2
  57. package/src/__tests__/conversation-surfaces-app-control.test.ts +323 -3
  58. package/src/__tests__/credential-security-invariants.test.ts +5 -6
  59. package/src/__tests__/daemon-credential-client.test.ts +56 -1
  60. package/src/__tests__/db-activation-state-fk-cascade.test.ts +132 -0
  61. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +37 -0
  62. package/src/__tests__/db-memory-graph-event-date-repair.test.ts +43 -20
  63. package/src/__tests__/db-proxy-transaction.test.ts +206 -0
  64. package/src/__tests__/external-plugin-loader.test.ts +458 -0
  65. package/src/__tests__/filing-service.test.ts +23 -3
  66. package/src/__tests__/fixtures/mock-chrome-extension.ts +5 -0
  67. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  68. package/src/__tests__/graph-extraction-event-date.test.ts +34 -0
  69. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -8
  70. package/src/__tests__/heartbeat-disk-pressure.test.ts +21 -8
  71. package/src/__tests__/heartbeat-service.test.ts +50 -233
  72. package/src/__tests__/history-repair.test.ts +89 -0
  73. package/src/__tests__/host-app-control-proxy.test.ts +109 -1
  74. package/src/__tests__/host-app-control-routes.test.ts +247 -1
  75. package/src/__tests__/host-browser-proxy.test.ts +416 -20
  76. package/src/__tests__/host-browser-routes.test.ts +325 -33
  77. package/src/__tests__/host-proxy-preactivation.test.ts +211 -0
  78. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +246 -0
  79. package/src/__tests__/inference-profile-reaper.test.ts +154 -0
  80. package/src/__tests__/inference-profile-session-handler.test.ts +398 -0
  81. package/src/__tests__/inference-profile-session-ipc.test.ts +236 -0
  82. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -1
  83. package/src/__tests__/install-skill-routing.test.ts +2 -2
  84. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +15 -0
  85. package/src/__tests__/llm-callsite-catalog.test.ts +20 -1
  86. package/src/__tests__/llm-catalog-parity.test.ts +146 -0
  87. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +188 -0
  88. package/src/__tests__/llm-request-log-source-factory.test.ts +124 -0
  89. package/src/__tests__/llm-resolver.test.ts +46 -0
  90. package/src/__tests__/managed-profile-guard.test.ts +131 -2
  91. package/src/__tests__/mcp-auth-routes.test.ts +1 -0
  92. package/src/__tests__/mcp-cli.test.ts +182 -220
  93. package/src/__tests__/mcp-health-check.test.ts +56 -27
  94. package/src/__tests__/memory-jobs-worker-lanes.test.ts +18 -11
  95. package/src/__tests__/message-complete-display-id.test.ts +175 -0
  96. package/src/__tests__/notification-platform-adapter.test.ts +229 -0
  97. package/src/__tests__/oauth-cli.test.ts +38 -2009
  98. package/src/__tests__/oauth-commands-routes.test.ts +711 -0
  99. package/src/__tests__/oauth-connect-routes.test.ts +174 -11
  100. package/src/__tests__/oauth-providers-routes.test.ts +14 -10
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +33 -12
  102. package/src/__tests__/openai-responses-provider.test.ts +17 -0
  103. package/src/__tests__/plugin-bootstrap.test.ts +31 -2
  104. package/src/__tests__/plugin-route-contribution.test.ts +31 -3
  105. package/src/__tests__/plugin-tool-contribution.test.ts +31 -3
  106. package/src/__tests__/plugin-types.test.ts +13 -11
  107. package/src/__tests__/process-message-background-slack.test.ts +46 -0
  108. package/src/__tests__/profile-entry-status.test.ts +43 -0
  109. package/src/__tests__/provider-managed-proxy-integration.test.ts +12 -4
  110. package/src/__tests__/provider-registry-ollama.test.ts +12 -4
  111. package/src/__tests__/provider-send-message-override-profile.test.ts +10 -4
  112. package/src/__tests__/relay-server.test.ts +118 -0
  113. package/src/__tests__/retry-thinking-tool-choice.test.ts +15 -0
  114. package/src/__tests__/schedule-retry.test.ts +56 -4
  115. package/src/__tests__/schedule-routes.test.ts +104 -0
  116. package/src/__tests__/scheduler-disk-pressure.test.ts +0 -4
  117. package/src/__tests__/scheduler-recurrence.test.ts +87 -34
  118. package/src/__tests__/scheduler-reuse-conversation.test.ts +161 -5
  119. package/src/__tests__/scheduler-wake.test.ts +0 -63
  120. package/src/__tests__/secret-allowlist.test.ts +1 -0
  121. package/src/__tests__/secret-routes-managed-proxy.test.ts +12 -4
  122. package/src/__tests__/shell-credential-ref.test.ts +95 -3
  123. package/src/__tests__/shell-tool-proxy-mode.test.ts +14 -0
  124. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  125. package/src/__tests__/skill-load-tool.test.ts +2 -4
  126. package/src/__tests__/subagent-call-site-routing.test.ts +78 -16
  127. package/src/__tests__/suggestion-routes.test.ts +3 -3
  128. package/src/__tests__/sync-message-contract.test.ts +63 -0
  129. package/src/__tests__/task-scheduler.test.ts +88 -23
  130. package/src/__tests__/update-bulletin-job.test.ts +96 -193
  131. package/src/__tests__/usage-cli.test.ts +11 -73
  132. package/src/__tests__/user-plugin-loader.test.ts +145 -0
  133. package/src/__tests__/vercel-config.test.ts +168 -0
  134. package/src/__tests__/web-search-catalog-parity.test.ts +86 -0
  135. package/src/__tests__/web-search.test.ts +303 -2
  136. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +1 -21
  137. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +58 -0
  138. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +53 -20
  139. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +191 -0
  140. package/src/__tests__/workspace-migration-076-drop-services-inference-mode.test.ts +211 -0
  141. package/src/__tests__/workspace-migration-077-seed-memory-router-callsite.test.ts +174 -0
  142. package/src/__tests__/workspace-migration-079-home-feed-notification-only.test.ts +323 -0
  143. package/src/__tests__/workspace-migration-080-restrict-vercel-api-token-metadata.test.ts +299 -0
  144. package/src/__tests__/workspace-migration-081-backfill-bash-allowed-tools.test.ts +410 -0
  145. package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +268 -0
  146. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +3 -3
  147. package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +115 -0
  148. package/src/acp/__tests__/helpers/which-stub.ts +4 -2
  149. package/src/acp/resolve-agent.test.ts +25 -0
  150. package/src/acp/resolve-agent.ts +13 -2
  151. package/src/acp/session-manager.ts +14 -0
  152. package/src/approvals/guardian-request-resolvers.ts +32 -87
  153. package/src/calls/relay-server.ts +35 -0
  154. package/src/calls/relay-setup-router.ts +36 -0
  155. package/src/calls/types.ts +1 -0
  156. package/src/calls/voice-session-bridge.ts +23 -4
  157. package/src/channels/config.ts +14 -1
  158. package/src/channels/types.ts +1 -0
  159. package/src/cli/AGENTS.md +164 -4
  160. package/src/cli/__tests__/notifications.test.ts +54 -0
  161. package/src/cli/commands/__tests__/avatar.test.ts +540 -0
  162. package/src/cli/commands/__tests__/backup.test.ts +236 -776
  163. package/src/cli/commands/__tests__/cache.test.ts +1 -1
  164. package/src/cli/commands/__tests__/changelog.test.ts +593 -0
  165. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +503 -0
  166. package/src/cli/commands/__tests__/conversations-import.test.ts +515 -0
  167. package/src/cli/commands/__tests__/domain-register.test.ts +140 -167
  168. package/src/cli/commands/__tests__/domain-status.test.ts +137 -76
  169. package/src/cli/commands/__tests__/email-attachment.test.ts +314 -337
  170. package/src/cli/commands/__tests__/email-core.test.ts +579 -0
  171. package/src/cli/commands/__tests__/image-generation.test.ts +87 -824
  172. package/src/cli/commands/__tests__/inference-send.test.ts +30 -266
  173. package/src/cli/commands/__tests__/inference-session.test.ts +423 -0
  174. package/src/cli/commands/__tests__/memory-v2.test.ts +81 -110
  175. package/src/cli/commands/__tests__/skills.test.ts +563 -0
  176. package/src/cli/commands/__tests__/status.test.ts +249 -0
  177. package/src/cli/commands/__tests__/stt.test.ts +320 -0
  178. package/src/cli/commands/__tests__/tts-synthesize.test.ts +4 -603
  179. package/src/cli/commands/__tests__/tts.test.ts +321 -0
  180. package/src/cli/commands/__tests__/webhooks.test.ts +86 -511
  181. package/src/cli/commands/attachment.ts +8 -3
  182. package/src/cli/commands/audit.ts +95 -64
  183. package/src/cli/commands/auth.ts +61 -58
  184. package/src/cli/commands/avatar.ts +276 -390
  185. package/src/cli/commands/backup.ts +409 -505
  186. package/src/cli/commands/bash.ts +9 -5
  187. package/src/cli/commands/browser.ts +28 -9
  188. package/src/cli/commands/cache.ts +9 -4
  189. package/src/cli/commands/changelog.ts +414 -0
  190. package/src/cli/commands/channel-verification-sessions.ts +238 -317
  191. package/src/cli/commands/clients.ts +8 -3
  192. package/src/cli/commands/completions.ts +9 -9
  193. package/src/cli/commands/config.ts +102 -72
  194. package/src/cli/commands/contacts.ts +575 -696
  195. package/src/cli/commands/conversations-defer.ts +17 -69
  196. package/src/cli/commands/conversations-import.ts +90 -253
  197. package/src/cli/commands/conversations.ts +346 -436
  198. package/src/cli/commands/credential-execution.ts +9 -6
  199. package/src/cli/commands/credentials.ts +456 -736
  200. package/src/cli/commands/domain.ts +128 -206
  201. package/src/cli/commands/email.ts +606 -794
  202. package/src/cli/commands/gateway.ts +8 -1
  203. package/src/cli/commands/image-generation.ts +157 -205
  204. package/src/cli/commands/inference-providers.ts +352 -0
  205. package/src/cli/commands/inference-session.ts +415 -0
  206. package/src/cli/commands/inference.ts +87 -65
  207. package/src/cli/commands/keys.ts +8 -3
  208. package/src/cli/commands/mcp.ts +103 -287
  209. package/src/cli/commands/memory-v2.ts +162 -516
  210. package/src/cli/commands/notifications.ts +33 -7
  211. package/src/cli/commands/oauth/apps.ts +292 -261
  212. package/src/cli/commands/oauth/connect.ts +176 -297
  213. package/src/cli/commands/oauth/disconnect.ts +16 -215
  214. package/src/cli/commands/oauth/index.ts +49 -45
  215. package/src/cli/commands/oauth/mode.ts +43 -199
  216. package/src/cli/commands/oauth/ping.ts +17 -125
  217. package/src/cli/commands/oauth/providers.ts +732 -921
  218. package/src/cli/commands/oauth/request.ts +60 -350
  219. package/src/cli/commands/oauth/shared.ts +11 -121
  220. package/src/cli/commands/oauth/status.ts +31 -121
  221. package/src/cli/commands/oauth/token.ts +13 -55
  222. package/src/cli/commands/pending.ts +19 -10
  223. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +133 -183
  224. package/src/cli/commands/platform/__tests__/connect.test.ts +66 -181
  225. package/src/cli/commands/platform/__tests__/disconnect.test.ts +71 -227
  226. package/src/cli/commands/platform/__tests__/status.test.ts +169 -287
  227. package/src/cli/commands/platform/connect.ts +16 -80
  228. package/src/cli/commands/platform/disconnect.ts +14 -112
  229. package/src/cli/commands/platform/index.ts +177 -246
  230. package/src/cli/commands/routes.ts +153 -336
  231. package/src/cli/commands/sequence.ts +316 -360
  232. package/src/cli/commands/skills.ts +449 -671
  233. package/src/cli/commands/status.ts +58 -37
  234. package/src/cli/commands/stt.ts +94 -262
  235. package/src/cli/commands/task.ts +14 -40
  236. package/src/cli/commands/trust.ts +8 -3
  237. package/src/cli/commands/tts.ts +162 -167
  238. package/src/cli/commands/ui.ts +35 -42
  239. package/src/cli/commands/usage.ts +188 -126
  240. package/src/cli/commands/watchers.ts +8 -3
  241. package/src/cli/commands/webhooks.ts +99 -193
  242. package/src/cli/lib/__tests__/register-command.test.ts +85 -0
  243. package/src/cli/lib/daemon-credential-client.ts +4 -5
  244. package/src/cli/lib/nested-value.ts +44 -0
  245. package/src/cli/lib/open-browser.ts +36 -0
  246. package/src/cli/lib/register-command.ts +19 -0
  247. package/src/cli/lib/time-ago.ts +34 -0
  248. package/src/cli/program.ts +2 -4
  249. package/src/cli/utils/__tests__/conversation-id.test.ts +66 -0
  250. package/src/cli/utils/__tests__/parse-duration.test.ts +49 -0
  251. package/src/cli/utils/conversation-id.ts +30 -0
  252. package/src/cli/utils/parse-duration.ts +41 -0
  253. package/src/config/acp-defaults.test.ts +5 -1
  254. package/src/config/acp-defaults.ts +11 -4
  255. package/src/config/bundled-skills/acp/TOOLS.json +2 -2
  256. package/src/config/bundled-skills/app-control/TOOLS.json +32 -0
  257. package/src/config/bundled-skills/contacts/SKILL.md +12 -45
  258. package/src/config/bundled-skills/contacts/TOOLS.json +0 -57
  259. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +0 -12
  260. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -58
  261. package/src/config/bundled-tool-registry.ts +0 -2
  262. package/src/config/feature-flag-registry.json +16 -0
  263. package/src/config/llm-resolver.ts +16 -1
  264. package/src/config/loader.ts +76 -14
  265. package/src/config/raw-config-utils.ts +2 -30
  266. package/src/config/schema.ts +4 -0
  267. package/src/config/schemas/__tests__/memory-v2.test.ts +49 -0
  268. package/src/config/schemas/call-site-catalog.ts +29 -7
  269. package/src/config/schemas/llm-request-logs.ts +57 -0
  270. package/src/config/schemas/llm.ts +52 -2
  271. package/src/config/schemas/memory-retrospective.ts +48 -0
  272. package/src/config/schemas/memory-v2.ts +32 -1
  273. package/src/config/schemas/memory.ts +4 -0
  274. package/src/config/schemas/services.ts +15 -12
  275. package/src/config/seed-inference-profiles.ts +195 -134
  276. package/src/contacts/contact-store.ts +0 -61
  277. package/src/context/window-manager.ts +191 -5
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +79 -0
  279. package/src/daemon/__tests__/conversation-tool-setup.test.ts +109 -4
  280. package/src/daemon/__tests__/daemon-skill-host.test.ts +10 -4
  281. package/src/daemon/approval-generators.ts +23 -29
  282. package/src/daemon/config-watcher.ts +2 -0
  283. package/src/daemon/conversation-agent-loop-handlers.ts +24 -0
  284. package/src/daemon/conversation-agent-loop.ts +127 -97
  285. package/src/daemon/conversation-error.ts +21 -0
  286. package/src/daemon/conversation-lifecycle.ts +46 -5
  287. package/src/daemon/conversation-process.ts +36 -19
  288. package/src/daemon/conversation-runtime-assembly.ts +14 -5
  289. package/src/daemon/conversation-slash.ts +175 -23
  290. package/src/daemon/conversation-store.ts +17 -10
  291. package/src/daemon/conversation-surfaces.ts +76 -12
  292. package/src/daemon/conversation-tool-setup.ts +24 -14
  293. package/src/daemon/conversation.ts +48 -9
  294. package/src/daemon/external-plugins-bootstrap.ts +18 -8
  295. package/src/daemon/guardian-action-generators.ts +7 -22
  296. package/src/daemon/handlers/config-model.ts +8 -126
  297. package/src/daemon/handlers/config-slack-channel.ts +10 -7
  298. package/src/daemon/handlers/config-vercel.ts +3 -1
  299. package/src/daemon/handlers/skills.ts +84 -5
  300. package/src/daemon/history-repair.ts +33 -6
  301. package/src/daemon/host-app-control-proxy.ts +44 -19
  302. package/src/daemon/host-bash-proxy.ts +85 -158
  303. package/src/daemon/host-browser-proxy.ts +96 -35
  304. package/src/daemon/host-proxy-base.ts +13 -1
  305. package/src/daemon/host-proxy-preactivation.ts +25 -1
  306. package/src/daemon/identity-helpers.ts +19 -0
  307. package/src/daemon/lifecycle.ts +42 -43
  308. package/src/daemon/meet-host-supervisor.ts +15 -15
  309. package/src/daemon/memory-v2-startup.ts +9 -2
  310. package/src/daemon/message-protocol.ts +6 -0
  311. package/src/daemon/message-types/bookmarks.ts +18 -0
  312. package/src/daemon/message-types/conversations.ts +12 -9
  313. package/src/daemon/message-types/messages.ts +9 -1
  314. package/src/daemon/message-types/sync.ts +60 -0
  315. package/src/daemon/pkb-reminder-builder.test.ts +54 -13
  316. package/src/daemon/pkb-reminder-builder.ts +21 -7
  317. package/src/daemon/process-message.ts +56 -23
  318. package/src/daemon/server.ts +23 -18
  319. package/src/daemon/shutdown-handlers.ts +0 -2
  320. package/src/daemon/tool-setup-types.ts +9 -0
  321. package/src/daemon/tool-side-effects.ts +6 -4
  322. package/src/daemon/wake-target-adapter.ts +11 -0
  323. package/src/export/transcript-formatter.ts +61 -2
  324. package/src/filing/filing-service.ts +40 -53
  325. package/src/heartbeat/__tests__/heartbeat-service.test.ts +359 -0
  326. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  327. package/src/heartbeat/heartbeat-service.ts +148 -127
  328. package/src/home/__tests__/feed-types.test.ts +63 -131
  329. package/src/home/__tests__/feed-writer.test.ts +77 -278
  330. package/src/home/__tests__/post-connect-feed.test.ts +9 -12
  331. package/src/home/feed-types.ts +19 -73
  332. package/src/home/feed-writer.ts +25 -156
  333. package/src/home/post-connect-feed.ts +1 -3
  334. package/src/ipc/__tests__/cli-ipc.test.ts +2 -0
  335. package/src/ipc/__tests__/email-ipc.test.ts +506 -0
  336. package/src/ipc/__tests__/exit-helper.test.ts +104 -0
  337. package/src/ipc/__tests__/streaming-client.test.ts +237 -0
  338. package/src/ipc/__tests__/streaming-framing.test.ts +142 -0
  339. package/src/ipc/assistant-server.ts +55 -6
  340. package/src/ipc/cli-client.ts +370 -50
  341. package/src/ipc/routes/db-proxy-transaction.ts +151 -0
  342. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +60 -0
  343. package/src/ipc/skill-routes/events.ts +30 -3
  344. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +46 -0
  345. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +1 -0
  346. package/src/live-voice/live-voice-session-manager.ts +11 -4
  347. package/src/live-voice/live-voice-session.ts +14 -6
  348. package/src/memory/__tests__/bookmark-crud.test.ts +258 -0
  349. package/src/memory/__tests__/bookmark-schema.test.ts +181 -0
  350. package/src/memory/__tests__/conversation-types.test.ts +36 -0
  351. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +130 -0
  352. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +177 -0
  353. package/src/memory/__tests__/memory-retrospective-job.test.ts +328 -0
  354. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +213 -0
  355. package/src/memory/__tests__/memory-retrospective-trigger-check.test.ts +90 -0
  356. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +69 -0
  357. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -0
  358. package/src/memory/bookmark-crud.ts +179 -0
  359. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +31 -9
  360. package/src/memory/context-search/agent-protocol.ts +5 -1
  361. package/src/memory/context-search/agent-runner.ts +60 -85
  362. package/src/memory/context-search/limits.ts +1 -4
  363. package/src/memory/context-search/search.ts +23 -113
  364. package/src/memory/context-search/sources/conversations.ts +18 -6
  365. package/src/memory/context-search/sources/memory-v2.ts +39 -14
  366. package/src/memory/context-search/sources/memory.ts +7 -0
  367. package/src/memory/context-search/sources/workspace.ts +13 -10
  368. package/src/memory/context-search/types.ts +1 -1
  369. package/src/memory/conversation-bootstrap.ts +11 -0
  370. package/src/memory/conversation-crud.ts +312 -10
  371. package/src/memory/conversation-queries.ts +9 -5
  372. package/src/memory/conversation-title-service.ts +1 -0
  373. package/src/memory/conversation-types.ts +16 -0
  374. package/src/memory/db-init.ts +14 -0
  375. package/src/memory/embedding-backend.ts +2 -1
  376. package/src/memory/embedding-runtime-manager.ts +1 -2
  377. package/src/memory/graph/__tests__/remember-description.test.ts +55 -0
  378. package/src/memory/graph/conversation-graph-memory.ts +76 -5
  379. package/src/memory/graph/extraction.ts +4 -0
  380. package/src/memory/graph/graph-memory-state-store.ts +16 -3
  381. package/src/memory/graph/tool-handlers.ts +17 -7
  382. package/src/memory/graph/tools.ts +44 -5
  383. package/src/memory/indexer.ts +17 -0
  384. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +13 -15
  385. package/src/memory/jobs/embed-concept-page.ts +45 -9
  386. package/src/memory/jobs-store.ts +51 -1
  387. package/src/memory/jobs-worker.ts +52 -3
  388. package/src/memory/llm-request-log-source-clickhouse.ts +317 -0
  389. package/src/memory/llm-request-log-source-local.ts +26 -0
  390. package/src/memory/llm-request-log-source.ts +97 -0
  391. package/src/memory/llm-request-log-store.ts +1 -1
  392. package/src/memory/memory-retrospective-constants.ts +13 -0
  393. package/src/memory/memory-retrospective-enqueue.ts +114 -0
  394. package/src/memory/memory-retrospective-job.ts +351 -0
  395. package/src/memory/memory-retrospective-startup-cleanup.ts +108 -0
  396. package/src/memory/memory-retrospective-state.ts +162 -0
  397. package/src/memory/memory-retrospective-trigger-check.ts +91 -0
  398. package/src/memory/memory-v2-activation-log-store.ts +49 -5
  399. package/src/memory/memory-v2-concept-frequency.ts +4 -0
  400. package/src/memory/message-content.ts +38 -1
  401. package/src/memory/migrations/227-add-conversation-inference-profile.ts +6 -1
  402. package/src/memory/migrations/228-rename-inference-profile-snake-case.ts +20 -7
  403. package/src/memory/migrations/229-delete-private-conversations.test.ts +70 -1
  404. package/src/memory/migrations/229-delete-private-conversations.ts +12 -0
  405. package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +16 -2
  406. package/src/memory/migrations/240-conversation-inference-profile-session.ts +25 -0
  407. package/src/memory/migrations/241-activation-state-fk-cascade.ts +50 -0
  408. package/src/memory/migrations/242-message-bookmarks.ts +38 -0
  409. package/src/memory/migrations/243-provider-connections.ts +68 -0
  410. package/src/memory/migrations/244-provider-connection-status-label.ts +23 -0
  411. package/src/memory/migrations/245-memory-retrospective-state.ts +36 -0
  412. package/src/memory/migrations/246-backfill-provider-connection-label.ts +81 -0
  413. package/src/memory/migrations/__tests__/244-provider-connection-status-label.test.ts +84 -0
  414. package/src/memory/migrations/__tests__/245-memory-retrospective-state.test.ts +125 -0
  415. package/src/memory/migrations/__tests__/246-backfill-provider-connection-label.test.ts +192 -0
  416. package/src/memory/migrations/index.ts +7 -0
  417. package/src/memory/published-pages-store.ts +16 -0
  418. package/src/memory/schema/bookmarks.ts +38 -0
  419. package/src/memory/schema/conversations.ts +2 -0
  420. package/src/memory/schema/index.ts +2 -0
  421. package/src/memory/schema/inference.ts +29 -0
  422. package/src/memory/schema/memory-core.ts +9 -0
  423. package/src/memory/search/semantic.ts +1 -4
  424. package/src/memory/v2/__tests__/__snapshots__/prompts-router.test.ts.snap +27 -0
  425. package/src/memory/v2/__tests__/activation-store.test.ts +5 -5
  426. package/src/memory/v2/__tests__/activation.test.ts +11 -4
  427. package/src/memory/v2/__tests__/backfill-jobs.test.ts +38 -21
  428. package/src/memory/v2/__tests__/consolidation-job.test.ts +123 -135
  429. package/src/memory/v2/__tests__/edge-index.test.ts +1 -1
  430. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +111 -0
  431. package/src/memory/v2/__tests__/injection.test.ts +628 -10
  432. package/src/memory/v2/__tests__/migration.test.ts +7 -3
  433. package/src/memory/v2/__tests__/page-index.test.ts +277 -0
  434. package/src/memory/v2/__tests__/page-store.test.ts +14 -1
  435. package/src/memory/v2/__tests__/prompts-router.test.ts +257 -0
  436. package/src/memory/v2/__tests__/qdrant.test.ts +72 -0
  437. package/src/memory/v2/__tests__/reranker.test.ts +4 -4
  438. package/src/memory/v2/__tests__/router.test.ts +516 -0
  439. package/src/memory/v2/__tests__/sim.test.ts +45 -1
  440. package/src/memory/v2/__tests__/skill-store.test.ts +58 -3
  441. package/src/memory/v2/__tests__/static-context.test.ts +7 -22
  442. package/src/memory/v2/__tests__/sweep-job.test.ts +95 -0
  443. package/src/memory/v2/activation-store.ts +34 -5
  444. package/src/memory/v2/activation.ts +40 -27
  445. package/src/memory/v2/backfill-jobs.ts +17 -84
  446. package/src/memory/v2/consolidation-job.ts +85 -78
  447. package/src/memory/v2/frontmatter-sweep.ts +91 -0
  448. package/src/memory/v2/injection.ts +440 -109
  449. package/src/memory/v2/migration.ts +117 -20
  450. package/src/memory/v2/page-index.ts +191 -0
  451. package/src/memory/v2/page-store.ts +3 -0
  452. package/src/memory/v2/prompts/consolidation.ts +9 -7
  453. package/src/memory/v2/prompts/router.ts +192 -0
  454. package/src/memory/v2/qdrant.ts +100 -87
  455. package/src/memory/v2/reranker.ts +14 -7
  456. package/src/memory/v2/router.ts +322 -0
  457. package/src/memory/v2/sim.ts +25 -12
  458. package/src/memory/v2/skill-store.ts +118 -29
  459. package/src/memory/v2/static-context.ts +16 -9
  460. package/src/memory/v2/sweep-job.ts +122 -96
  461. package/src/memory/v2/types.ts +10 -6
  462. package/src/memory/validation.ts +13 -0
  463. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +182 -0
  464. package/src/notifications/__tests__/home-feed-side-effect.test.ts +199 -0
  465. package/src/notifications/__tests__/signal-registry.test.ts +17 -0
  466. package/src/notifications/adapters/platform.ts +171 -0
  467. package/src/notifications/conversation-pairing.ts +2 -2
  468. package/src/notifications/copy-composer.ts +15 -0
  469. package/src/notifications/destination-resolver.ts +21 -0
  470. package/src/notifications/emit-signal.ts +28 -1
  471. package/src/notifications/home-feed-side-effect.ts +111 -0
  472. package/src/notifications/signal.ts +5 -0
  473. package/src/permissions/checker.ts +12 -0
  474. package/src/permissions/ipc-risk-types.ts +2 -0
  475. package/src/plugin-api/index.ts +13 -0
  476. package/src/plugin-api/package.json +12 -0
  477. package/src/plugin-api/types.ts +62 -0
  478. package/src/plugins/defaults/injectors.ts +19 -3
  479. package/src/plugins/external-plugin-loader.ts +294 -0
  480. package/src/plugins/types.ts +46 -30
  481. package/src/plugins/user-loader.ts +64 -41
  482. package/src/proactive-artifact/job.test.ts +12 -4
  483. package/src/proactive-artifact/job.ts +4 -0
  484. package/src/proactive-artifact/trigger-state.test.ts +9 -0
  485. package/src/proactive-artifact/trigger-state.ts +4 -0
  486. package/src/prompts/__tests__/system-prompt.test.ts +105 -0
  487. package/src/prompts/system-prompt.ts +22 -1
  488. package/src/prompts/update-bulletin-job.ts +61 -73
  489. package/src/providers/__tests__/dispatch-connection-routing.test.ts +279 -0
  490. package/src/providers/__tests__/inference.test.ts +288 -0
  491. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  492. package/src/providers/__tests__/provider-secret-catalog.test.ts +6 -0
  493. package/src/providers/__tests__/retry-callsite.test.ts +14 -32
  494. package/src/providers/__tests__/satellite-connection-routing.test.ts +510 -0
  495. package/src/providers/__tests__/search-provider-catalog.test.ts +80 -0
  496. package/src/providers/anthropic/client.ts +95 -26
  497. package/src/providers/call-site-routing.ts +94 -16
  498. package/src/providers/connection-resolution.ts +163 -0
  499. package/src/providers/inference/__tests__/connections-status-label.test.ts +250 -0
  500. package/src/providers/inference/adapter-factory.ts +173 -0
  501. package/src/providers/inference/auth.ts +112 -0
  502. package/src/providers/inference/backfill.ts +196 -0
  503. package/src/providers/inference/connections.ts +356 -0
  504. package/src/providers/inference/resolve-auth.ts +65 -0
  505. package/src/providers/model-catalog.ts +104 -6
  506. package/src/providers/openai/responses-provider.ts +4 -2
  507. package/src/providers/provider-env-vars.ts +17 -7
  508. package/src/providers/provider-secret-catalog.ts +49 -30
  509. package/src/providers/provider-send-message.ts +41 -20
  510. package/src/providers/registry.ts +143 -159
  511. package/src/providers/retry.ts +18 -10
  512. package/src/providers/search-provider-catalog.ts +121 -0
  513. package/src/runtime/AGENTS.md +18 -5
  514. package/src/runtime/__tests__/background-job-runner.test.ts +357 -0
  515. package/src/runtime/__tests__/pre-first-message-gate.test.ts +82 -0
  516. package/src/runtime/actor-trust-resolver.ts +32 -10
  517. package/src/runtime/agent-wake.ts +35 -6
  518. package/src/runtime/assistant-event-hub.ts +3 -85
  519. package/src/runtime/auth/route-policy.ts +303 -8
  520. package/src/runtime/auth/same-actor.ts +2 -0
  521. package/src/runtime/background-job-runner.ts +339 -0
  522. package/src/runtime/btw-sidechain.ts +1 -0
  523. package/src/runtime/http-router.ts +36 -1
  524. package/src/runtime/http-server.ts +31 -5
  525. package/src/runtime/http-types.ts +2 -0
  526. package/src/runtime/middleware/__tests__/request-logger.test.ts +162 -0
  527. package/src/runtime/middleware/request-logger.ts +62 -1
  528. package/src/runtime/pre-first-message-gate.ts +83 -0
  529. package/src/runtime/routes/__tests__/backup-routes.test.ts +8 -1
  530. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +251 -0
  531. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +142 -0
  532. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +315 -0
  533. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +189 -0
  534. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +15 -136
  535. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +736 -0
  536. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +4 -4
  537. package/src/runtime/routes/__tests__/stt-routes.test.ts +5 -1
  538. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +384 -0
  539. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  540. package/src/runtime/routes/acp-routes.ts +10 -8
  541. package/src/runtime/routes/app-management-routes.ts +228 -3
  542. package/src/runtime/routes/approval-routes.ts +0 -18
  543. package/src/runtime/routes/audit-routes.ts +43 -0
  544. package/src/runtime/routes/auth-routes.ts +72 -0
  545. package/src/runtime/routes/avatar-routes.ts +273 -20
  546. package/src/runtime/routes/backup-routes.ts +406 -2
  547. package/src/runtime/routes/bookmark-routes.ts +154 -0
  548. package/src/runtime/routes/channel-verification-routes.ts +2 -1
  549. package/src/runtime/routes/contact-routes.ts +0 -160
  550. package/src/runtime/routes/conversation-cli-routes.ts +192 -0
  551. package/src/runtime/routes/conversation-management-routes.ts +30 -43
  552. package/src/runtime/routes/conversation-query-routes.ts +334 -86
  553. package/src/runtime/routes/conversation-routes.ts +31 -10
  554. package/src/runtime/routes/conversations-import-routes.ts +229 -0
  555. package/src/runtime/routes/credential-routes.ts +540 -0
  556. package/src/runtime/routes/debug-routes.ts +2 -2
  557. package/src/runtime/routes/document-pdf-renderer.ts +5 -1
  558. package/src/runtime/routes/domain-routes.ts +167 -0
  559. package/src/runtime/routes/email-routes.ts +603 -0
  560. package/src/runtime/routes/errors.ts +2 -2
  561. package/src/runtime/routes/events-routes.ts +192 -0
  562. package/src/runtime/routes/home-feed-routes.ts +6 -78
  563. package/src/runtime/routes/host-app-control-routes.ts +44 -2
  564. package/src/runtime/routes/host-browser-routes.ts +103 -22
  565. package/src/runtime/routes/http-adapter.ts +2 -0
  566. package/src/runtime/routes/identity-routes.ts +5 -0
  567. package/src/runtime/routes/image-generation-routes.ts +99 -0
  568. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +137 -1
  569. package/src/runtime/routes/inbound-stages/background-dispatch.ts +87 -7
  570. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +156 -0
  571. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +22 -4
  572. package/src/runtime/routes/index.ts +36 -0
  573. package/src/runtime/routes/inference-profile-session-handler.ts +312 -0
  574. package/src/runtime/routes/inference-profile-session-reaper.ts +98 -0
  575. package/src/runtime/routes/inference-profile-session-routes.ts +146 -0
  576. package/src/runtime/routes/inference-provider-connection-routes.ts +317 -0
  577. package/src/runtime/routes/inference-send-routes.ts +115 -0
  578. package/src/runtime/routes/integrations/twilio.ts +1 -0
  579. package/src/runtime/routes/mcp-auth-routes.ts +283 -9
  580. package/src/runtime/routes/memory-v2-routes.ts +13 -398
  581. package/src/runtime/routes/notification-routes.ts +2 -0
  582. package/src/runtime/routes/oauth-apps.ts +112 -7
  583. package/src/runtime/routes/oauth-commands-routes.ts +1007 -0
  584. package/src/runtime/routes/oauth-connect-routes.ts +67 -5
  585. package/src/runtime/routes/oauth-providers.ts +298 -8
  586. package/src/runtime/routes/platform-routes.ts +336 -0
  587. package/src/runtime/routes/playground/inject-failures.ts +2 -1
  588. package/src/runtime/routes/playground/reset-circuit.ts +2 -1
  589. package/src/runtime/routes/playground/state.ts +2 -1
  590. package/src/runtime/routes/publish-routes.ts +221 -0
  591. package/src/runtime/routes/schedule-routes.ts +82 -0
  592. package/src/runtime/routes/sequence-routes.ts +291 -0
  593. package/src/runtime/routes/settings-routes.ts +2 -10
  594. package/src/runtime/routes/skills-routes.ts +31 -1
  595. package/src/runtime/routes/stt-routes.ts +240 -3
  596. package/src/runtime/routes/surface-action-routes.ts +43 -7
  597. package/src/runtime/routes/tts-routes.ts +67 -0
  598. package/src/runtime/routes/types.ts +32 -0
  599. package/src/runtime/routes/user-routes-cli.ts +243 -0
  600. package/src/runtime/routes/webhook-routes.ts +165 -0
  601. package/src/runtime/sync/resource-sync-events.ts +25 -0
  602. package/src/runtime/sync/sync-publisher.test.ts +105 -0
  603. package/src/runtime/sync/sync-publisher.ts +21 -0
  604. package/src/schedule/scheduler.ts +200 -123
  605. package/src/security/__tests__/provider-key-env-fallback.test.ts +12 -6
  606. package/src/security/secret-patterns.ts +3 -0
  607. package/src/sequence/engine.ts +38 -40
  608. package/src/subagent/manager.ts +20 -15
  609. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +206 -0
  610. package/src/tools/browser/browser-execution.ts +15 -4
  611. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +174 -0
  612. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +16 -13
  613. package/src/tools/browser/cdp-client/extension-cdp-client.ts +24 -1
  614. package/src/tools/browser/cdp-client/factory.ts +66 -5
  615. package/src/tools/browser/runtime-check.ts +77 -0
  616. package/src/tools/memory/register.test.ts +3 -3
  617. package/src/tools/memory/register.ts +9 -1
  618. package/src/tools/network/__tests__/web-search.test.ts +156 -0
  619. package/src/tools/network/web-search.ts +280 -37
  620. package/src/tools/permission-checker.ts +13 -5
  621. package/src/tools/subagent/spawn.ts +3 -3
  622. package/src/tools/terminal/shell.ts +44 -0
  623. package/src/usage/attribution.ts +3 -2
  624. package/src/util/pricing.ts +86 -160
  625. package/src/watcher/__tests__/engine.test.ts +301 -0
  626. package/src/watcher/constants.ts +7 -0
  627. package/src/watcher/engine.ts +90 -90
  628. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +6 -9
  629. package/src/workspace/migrations/054-seed-recall-callsite.ts +10 -1
  630. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +28 -4
  631. package/src/workspace/migrations/069-seed-onboarding-threads.ts +8 -2
  632. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +104 -0
  633. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +93 -0
  634. package/src/workspace/migrations/074-drop-deprecated-secret-detection-keys.ts +117 -0
  635. package/src/workspace/migrations/075-memory-v2-bm25-b-default-reembed.ts +61 -0
  636. package/src/workspace/migrations/076-drop-services-inference-mode.ts +62 -0
  637. package/src/workspace/migrations/077-seed-memory-router-callsite.ts +89 -0
  638. package/src/workspace/migrations/078-release-notes-tavily-web-search.ts +66 -0
  639. package/src/workspace/migrations/079-home-feed-notification-only.ts +197 -0
  640. package/src/workspace/migrations/080-restrict-vercel-api-token-metadata.ts +182 -0
  641. package/src/workspace/migrations/081-backfill-bash-allowed-tools-for-injection-credentials.ts +160 -0
  642. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +154 -0
  643. package/src/workspace/migrations/registry.ts +22 -0
  644. package/src/workspace/migrations/runner.ts +13 -2
  645. package/src/workspace/migrations/types.ts +13 -3
  646. package/src/workspace/provider-commit-message-generator.ts +3 -2
  647. package/src/__tests__/context-search-pkb-source.test.ts +0 -498
  648. package/src/__tests__/credentials-cli.test.ts +0 -1225
  649. package/src/__tests__/memory-admin-recall.test.ts +0 -213
  650. package/src/approvals/__tests__/guardian-feed-event.test.ts +0 -303
  651. package/src/cli/commands/__tests__/email-download.test.ts +0 -260
  652. package/src/cli/commands/__tests__/email-list.test.ts +0 -216
  653. package/src/cli/commands/__tests__/email-register.test.ts +0 -186
  654. package/src/cli/commands/__tests__/email-send.test.ts +0 -416
  655. package/src/cli/commands/__tests__/email-status.test.ts +0 -185
  656. package/src/cli/commands/__tests__/email-unregister.test.ts +0 -168
  657. package/src/cli/commands/__tests__/routes.test.ts +0 -562
  658. package/src/cli/commands/__tests__/stt-transcribe.test.ts +0 -454
  659. package/src/cli/commands/autonomy.ts +0 -365
  660. package/src/cli/commands/memory.ts +0 -424
  661. package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -947
  662. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +0 -686
  663. package/src/cli/commands/oauth/__tests__/mode.test.ts +0 -632
  664. package/src/cli/commands/oauth/__tests__/ping.test.ts +0 -631
  665. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +0 -573
  666. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +0 -330
  667. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +0 -521
  668. package/src/cli/commands/oauth/__tests__/status.test.ts +0 -551
  669. package/src/cli/commands/oauth/__tests__/token.test.ts +0 -420
  670. package/src/cli/lib/daemon-avatar-client.ts +0 -37
  671. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -87
  672. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +0 -207
  673. package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -304
  674. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +0 -233
  675. package/src/home/__tests__/assistant-feed-authoring.test.ts +0 -156
  676. package/src/home/__tests__/emit-feed-event.test.ts +0 -169
  677. package/src/home/__tests__/feed-population-integration.test.ts +0 -312
  678. package/src/home/__tests__/feed-scheduler.test.ts +0 -222
  679. package/src/home/__tests__/phase5-exit-criteria.test.ts +0 -229
  680. package/src/home/__tests__/platform-gmail-digest.test.ts +0 -222
  681. package/src/home/__tests__/rollup-producer.test.ts +0 -507
  682. package/src/home/assistant-feed-authoring.ts +0 -135
  683. package/src/home/emit-feed-event.ts +0 -169
  684. package/src/home/feed-scheduler.ts +0 -281
  685. package/src/home/platform-gmail-digest.ts +0 -163
  686. package/src/home/rewrite-command-preview.ts +0 -66
  687. package/src/home/rewrite-feed-title.ts +0 -58
  688. package/src/home/rollup-producer.ts +0 -426
  689. package/src/memory/admin.ts +0 -326
  690. package/src/memory/context-search/sources/pkb.ts +0 -476
  691. package/src/memory/graph/compaction.ts +0 -299
  692. /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
  });