@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,422 +1,399 @@
1
+ /**
2
+ * Tests for the email attachment subcommand.
3
+ *
4
+ * Uses IPC mocks (cliIpcCall / cliIpcCallStream) — no real daemon required.
5
+ */
6
+
1
7
  import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
2
8
  import { tmpdir } from "node:os";
3
9
  import { join } from "node:path";
4
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
5
-
6
- import {
7
- getMockFetchCalls,
8
- mockFetch,
9
- resetMockFetch,
10
- } from "../../../__tests__/mock-fetch.js";
11
- import { _setOverridesForTesting } from "../../../config/assistant-feature-flags.js";
12
- import { setPlatformAssistantId } from "../../../config/env.js";
13
- import { credentialKey } from "../../../security/credential-key.js";
14
- import {
15
- _resetBackend,
16
- deleteSecureKeyAsync,
17
- setSecureKeyAsync,
18
- } from "../../../security/secure-keys.js";
19
- import { runAssistantCommand } from "../../__tests__/run-assistant-command.js";
20
-
21
- const ASSISTANT_ID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
22
- const MESSAGE_ID = "msg-001";
23
- const ATT_ID_1 = "att-001";
24
- const ATT_ID_2 = "att-002";
25
- const API_KEY_CREDENTIAL = credentialKey("vellum", "assistant_api_key");
26
-
27
- const SAMPLE_ATTACHMENT_1 = {
28
- id: ATT_ID_1,
29
- filename: "invoice.pdf",
30
- content_type: "application/pdf",
31
- size_bytes: 245_000,
32
- content_id: "",
33
- created_at: "2026-04-05T12:00:00Z",
34
- };
35
-
36
- const SAMPLE_ATTACHMENT_2 = {
37
- id: ATT_ID_2,
38
- filename: "screenshot.png",
39
- content_type: "image/png",
40
- size_bytes: 1_200_000,
41
- content_id: "<img001@mail>",
42
- created_at: "2026-04-05T12:01:00Z",
43
- };
44
-
45
- function mockAttachmentList(
46
- attachments = [SAMPLE_ATTACHMENT_1, SAMPLE_ATTACHMENT_2],
47
- status = 200,
48
- ): void {
49
- mockFetch(
50
- "/attachments/",
51
- {},
52
- { body: { results: attachments }, status },
53
- );
54
- }
55
-
56
- function mockAttachmentDetail(
57
- att = SAMPLE_ATTACHMENT_1,
58
- status = 200,
59
- ): void {
60
- mockFetch(`/attachments/${att.id}/`, {}, { body: att, status });
61
- }
62
-
63
- function mockAttachmentDownload(
64
- attId: string,
65
- content: string,
66
- status = 200,
67
- ): void {
68
- const body = new TextEncoder().encode(content);
69
- const response = new Response(body, {
70
- status,
71
- headers: {
72
- "Content-Type": "application/octet-stream",
73
- "Content-Length": String(body.length),
10
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Mock state
14
+ // ---------------------------------------------------------------------------
15
+
16
+ let mockIpcCallFn: any = mock(() => Promise.resolve({ ok: true, result: { results: [] } }));
17
+ let mockIpcCallStreamFn: any = mock(() =>
18
+ Promise.resolve({ ok: false, error: "not used" }),
19
+ );
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Mocks — must be declared before importing the module under test
23
+ // ---------------------------------------------------------------------------
24
+
25
+ mock.module("../../../ipc/cli-client.js", () => ({
26
+ cliIpcCall: (...args: Parameters<typeof mockIpcCallFn>) => mockIpcCallFn(...args),
27
+ cliIpcCallStream: (...args: Parameters<typeof mockIpcCallStreamFn>) =>
28
+ mockIpcCallStreamFn(...args),
29
+ exitFromIpcResult: mock((r: { error?: string; statusCode?: number }) => {
30
+ process.stderr.write((r.error ?? "Unknown error") + "\n");
31
+ process.exitCode = 10;
32
+ }),
33
+ }));
34
+
35
+ mock.module("../../../util/logger.js", () => ({
36
+ getLogger: () => ({
37
+ info: () => {},
38
+ warn: () => {},
39
+ error: () => {},
40
+ debug: () => {},
41
+ }),
42
+ getCliLogger: () => ({
43
+ info: (...args: unknown[]) => {
44
+ captured.push(args.join(" "));
45
+ },
46
+ warn: () => {},
47
+ error: (...args: unknown[]) => {
48
+ captured.push("[error] " + args.join(" "));
49
+ },
50
+ debug: () => {},
51
+ }),
52
+ }));
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Capture output
56
+ // ---------------------------------------------------------------------------
57
+
58
+ const captured: string[] = [];
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Helpers
62
+ // ---------------------------------------------------------------------------
63
+
64
+ function makeMockStream(chunks: Uint8Array[]): {
65
+ ok: true;
66
+ headers: Record<string, string>;
67
+ body: ReadableStream<Uint8Array>;
68
+ abort: () => void;
69
+ } {
70
+ const body = new ReadableStream<Uint8Array>({
71
+ start(ctrl) {
72
+ for (const chunk of chunks) ctrl.enqueue(chunk);
73
+ ctrl.close();
74
74
  },
75
75
  });
76
- mockFetch(`/attachments/${attId}/download/`, {}, response);
76
+ return { ok: true, headers: { "x-filename": "test.pdf" }, body, abort: () => {} };
77
77
  }
78
78
 
79
- let savedCesUrl: string | undefined;
80
- let savedContainerized: string | undefined;
79
+ // ---------------------------------------------------------------------------
80
+ // Setup
81
+ // ---------------------------------------------------------------------------
82
+
81
83
  let tmpDir: string;
82
84
 
83
- beforeEach(async () => {
85
+ beforeEach(() => {
84
86
  process.exitCode = 0;
87
+ captured.length = 0;
85
88
 
86
- savedCesUrl = process.env.CES_CREDENTIAL_URL;
87
- savedContainerized = process.env.IS_CONTAINERIZED;
88
- delete process.env.CES_CREDENTIAL_URL;
89
- delete process.env.IS_CONTAINERIZED;
89
+ tmpDir = join(tmpdir(), `email-att-ipc-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
90
+ mkdirSync(tmpDir, { recursive: true });
90
91
 
91
- _resetBackend();
92
- resetMockFetch();
93
- _setOverridesForTesting({ "email-channel": true });
94
- setPlatformAssistantId(ASSISTANT_ID);
95
- await setSecureKeyAsync(API_KEY_CREDENTIAL, "test-api-key");
92
+ // Default: list returns two attachments
93
+ mockIpcCallFn = mock(() =>
94
+ Promise.resolve({
95
+ ok: true,
96
+ result: {
97
+ results: [
98
+ {
99
+ id: "att-001",
100
+ filename: "invoice.pdf",
101
+ content_type: "application/pdf",
102
+ size_bytes: 245_000,
103
+ content_id: "",
104
+ created_at: "2026-04-05T12:00:00Z",
105
+ },
106
+ {
107
+ id: "att-002",
108
+ filename: "screenshot.png",
109
+ content_type: "image/png",
110
+ size_bytes: 1_200_000,
111
+ content_id: "<img001@mail>",
112
+ created_at: "2026-04-05T12:01:00Z",
113
+ },
114
+ ],
115
+ },
116
+ }),
117
+ );
96
118
 
97
- tmpDir = join(tmpdir(), `email-attachment-test-${Date.now()}`);
98
- mkdirSync(tmpDir, { recursive: true });
119
+ mockIpcCallStreamFn = mock(() =>
120
+ Promise.resolve(
121
+ makeMockStream([new Uint8Array([1, 2, 3])]),
122
+ ),
123
+ );
99
124
  });
100
125
 
101
126
  afterEach(() => {
102
- resetMockFetch();
103
- _setOverridesForTesting({});
104
- setPlatformAssistantId(undefined);
105
- _resetBackend();
106
-
107
- if (savedCesUrl !== undefined) process.env.CES_CREDENTIAL_URL = savedCesUrl;
108
- else delete process.env.CES_CREDENTIAL_URL;
109
- if (savedContainerized !== undefined)
110
- process.env.IS_CONTAINERIZED = savedContainerized;
111
- else delete process.env.IS_CONTAINERIZED;
112
-
127
+ process.exitCode = 0;
128
+ captured.length = 0;
113
129
  if (existsSync(tmpDir)) rmSync(tmpDir, { recursive: true });
114
130
  });
115
131
 
116
- describe("assistant email attachment", () => {
117
- test("--list shows attachment metadata", async () => {
118
- mockAttachmentList();
132
+ // ---------------------------------------------------------------------------
133
+ // Helper: run the attachment subcommand
134
+ // ---------------------------------------------------------------------------
135
+
136
+ async function runAttachment(...args: string[]): Promise<string> {
137
+ const { registerEmailCommand } = await import("../email.js");
138
+
139
+ const capturedOutput: string[] = [];
140
+ const origWrite = process.stdout.write.bind(process.stdout);
141
+ process.stdout.write = (chunk: string | Buffer) => {
142
+ capturedOutput.push(typeof chunk === "string" ? chunk : chunk.toString());
143
+ return true;
144
+ };
145
+
146
+ try {
147
+ const { Command } = await import("commander");
148
+ const program = new Command();
149
+ program.exitOverride();
150
+ registerEmailCommand(program);
151
+ await program.parseAsync(["node", "assistant", "email", "attachment", ...args]);
152
+ } catch {
153
+ // exitOverride throws on --help etc; ignore
154
+ } finally {
155
+ process.stdout.write = origWrite;
156
+ }
157
+
158
+ return [...capturedOutput, ...captured].join("\n");
159
+ }
119
160
 
120
- const output = await runAssistantCommand(
121
- "email",
122
- "attachment",
123
- MESSAGE_ID,
124
- "--list",
125
- );
161
+ // ---------------------------------------------------------------------------
162
+ // Tests
163
+ // ---------------------------------------------------------------------------
126
164
 
127
- expect(output).toContain("invoice.pdf");
128
- expect(output).toContain("screenshot.png");
129
- expect(output).toContain("2 attachment(s)");
130
- expect(process.exitCode).toBe(0);
165
+ describe("email attachment (IPC)", () => {
166
+ test("--list calls email_attachment_list with messageId", async () => {
167
+ await runAttachment("msg_1", "--list");
168
+
169
+ expect(mockIpcCallFn.mock.calls.length).toBeGreaterThan(0);
170
+ const [method, params] = mockIpcCallFn.mock.calls[0] as unknown as [string, { queryParams: Record<string, unknown> }];
171
+ expect(method).toBe("email_attachment_list");
172
+ expect(params.queryParams.messageId).toBe("msg_1");
131
173
  });
132
174
 
133
- test("--list with no attachments", async () => {
134
- mockAttachmentList([]);
175
+ test("--list displays attachment metadata", async () => {
176
+ const out = await runAttachment("msg_1", "--list");
135
177
 
136
- const output = await runAssistantCommand(
137
- "email",
138
- "attachment",
139
- MESSAGE_ID,
140
- "--list",
141
- );
142
-
143
- expect(output).toContain("No attachments");
178
+ expect(out).toContain("invoice.pdf");
179
+ expect(out).toContain("screenshot.png");
180
+ expect(out).toContain("2 attachment(s)");
144
181
  expect(process.exitCode).toBe(0);
145
182
  });
146
183
 
147
- test("--list --json returns JSON", async () => {
148
- mockAttachmentList();
149
-
150
- const output = await runAssistantCommand(
151
- "email",
152
- "--json",
153
- "attachment",
154
- MESSAGE_ID,
155
- "--list",
184
+ test("--list with no attachments shows empty message", async () => {
185
+ mockIpcCallFn = mock(() =>
186
+ Promise.resolve({ ok: true, result: { results: [] } }),
156
187
  );
157
188
 
158
- const parsed = JSON.parse(output.trim());
189
+ const out = await runAttachment("msg_1", "--list");
190
+
191
+ expect(out).toContain("No attachments");
192
+ expect(process.exitCode).toBe(0);
193
+ });
194
+
195
+ test("--list --json outputs JSON", async () => {
196
+ let jsonOut = "";
197
+ const origWrite = process.stdout.write.bind(process.stdout);
198
+ process.stdout.write = (chunk: string | Buffer) => {
199
+ jsonOut += typeof chunk === "string" ? chunk : chunk.toString();
200
+ return true;
201
+ };
202
+
203
+ try {
204
+ const { registerEmailCommand } = await import("../email.js");
205
+ const { Command } = await import("commander");
206
+ const program = new Command();
207
+ program.exitOverride();
208
+ registerEmailCommand(program);
209
+ await program.parseAsync(["node", "assistant", "email", "--json", "attachment", "msg_1", "--list"]);
210
+ } finally {
211
+ process.stdout.write = origWrite;
212
+ }
213
+
214
+ const parsed = JSON.parse(jsonOut.trim());
159
215
  expect(parsed.results).toHaveLength(2);
160
216
  expect(parsed.results[0].filename).toBe("invoice.pdf");
161
217
  expect(process.exitCode).toBe(0);
162
218
  });
163
219
 
164
- test("download single attachment by ID", async () => {
165
- mockAttachmentDetail(SAMPLE_ATTACHMENT_1);
166
- mockAttachmentDownload(ATT_ID_1, "fake-pdf-content");
167
-
168
- const output = await runAssistantCommand(
169
- "email",
170
- "attachment",
171
- MESSAGE_ID,
172
- ATT_ID_1,
173
- "-o",
174
- tmpDir,
220
+ test("--list returns error on IPC failure", async () => {
221
+ mockIpcCallFn = mock(() =>
222
+ Promise.resolve({ ok: false, error: "daemon error", statusCode: 500 }),
175
223
  );
176
224
 
177
- expect(output).toContain("Downloaded invoice.pdf");
178
- expect(process.exitCode).toBe(0);
225
+ await runAttachment("msg_1", "--list");
179
226
 
180
- const filePath = join(tmpDir, "invoice.pdf");
181
- expect(existsSync(filePath)).toBe(true);
182
- expect(readFileSync(filePath, "utf-8")).toBe("fake-pdf-content");
227
+ // exitFromIpcResult sets exitCode to 10 in mock
228
+ expect(process.exitCode).not.toBe(0);
183
229
  });
184
230
 
185
- test("download single attachment --json output", async () => {
186
- mockAttachmentDetail(SAMPLE_ATTACHMENT_1);
187
- mockAttachmentDownload(ATT_ID_1, "fake-pdf-content");
188
-
189
- const output = await runAssistantCommand(
190
- "email",
191
- "--json",
192
- "attachment",
193
- MESSAGE_ID,
194
- ATT_ID_1,
195
- "-o",
196
- tmpDir,
197
- );
231
+ test("single download: calls list then stream with correct params", async () => {
232
+ await runAttachment("msg_1", "att-001", "-o", tmpDir);
198
233
 
199
- const parsed = JSON.parse(output.trim());
200
- expect(parsed.filename).toBe("invoice.pdf");
201
- expect(parsed.size_bytes).toBe(245_000);
202
- expect(parsed.saved).toContain("invoice.pdf");
203
- expect(process.exitCode).toBe(0);
204
- });
234
+ // The list call should come first
235
+ const listCall = mockIpcCallFn.mock.calls[0] as unknown as [string, { queryParams: Record<string, unknown> }];
236
+ expect(listCall[0]).toBe("email_attachment_list");
237
+ expect(listCall[1].queryParams.messageId).toBe("msg_1");
205
238
 
206
- test("--all downloads all attachments", async () => {
207
- mockAttachmentList();
208
- mockAttachmentDownload(ATT_ID_1, "pdf-bytes");
209
- mockAttachmentDownload(ATT_ID_2, "png-bytes");
210
-
211
- const output = await runAssistantCommand(
212
- "email",
213
- "attachment",
214
- MESSAGE_ID,
215
- "--all",
216
- "-o",
217
- tmpDir,
218
- );
239
+ // Stream call should use the correct attachmentId and messageId
240
+ const streamCall = mockIpcCallStreamFn.mock.calls[0] as unknown as [string, { queryParams: Record<string, unknown> }];
241
+ expect(streamCall[0]).toBe("email_attachment_get");
242
+ expect(streamCall[1].queryParams.attachmentId).toBe("att-001");
243
+ expect(streamCall[1].queryParams.messageId).toBe("msg_1");
219
244
 
220
- expect(output).toContain("Downloaded 2 attachment(s)");
221
- expect(output).toContain("invoice.pdf");
222
- expect(output).toContain("screenshot.png");
223
245
  expect(process.exitCode).toBe(0);
224
-
225
- expect(existsSync(join(tmpDir, "invoice.pdf"))).toBe(true);
226
- expect(existsSync(join(tmpDir, "screenshot.png"))).toBe(true);
227
- expect(readFileSync(join(tmpDir, "invoice.pdf"), "utf-8")).toBe("pdf-bytes");
228
- expect(readFileSync(join(tmpDir, "screenshot.png"), "utf-8")).toBe(
229
- "png-bytes",
230
- );
231
246
  });
232
247
 
233
- test("--all --json returns JSON", async () => {
234
- mockAttachmentList();
235
- mockAttachmentDownload(ATT_ID_1, "pdf-bytes");
236
- mockAttachmentDownload(ATT_ID_2, "png-bytes");
237
-
238
- const output = await runAssistantCommand(
239
- "email",
240
- "--json",
241
- "attachment",
242
- MESSAGE_ID,
243
- "--all",
244
- "-o",
245
- tmpDir,
248
+ test("single download: writes file to disk", async () => {
249
+ const content = new Uint8Array([104, 101, 108, 108, 111]); // "hello"
250
+ mockIpcCallStreamFn = mock(() =>
251
+ Promise.resolve(makeMockStream([content])),
246
252
  );
247
253
 
248
- const parsed = JSON.parse(output.trim());
249
- expect(parsed.downloaded).toBe(2);
250
- expect(parsed.files).toHaveLength(2);
254
+ await runAttachment("msg_1", "att-001", "-o", tmpDir);
255
+
256
+ const filePath = join(tmpDir, "invoice.pdf");
257
+ expect(existsSync(filePath)).toBe(true);
258
+ const written = readFileSync(filePath);
259
+ expect(written).toEqual(Buffer.from(content));
251
260
  expect(process.exitCode).toBe(0);
252
261
  });
253
262
 
254
- test("--all with no attachments returns error", async () => {
255
- mockAttachmentList([]);
256
-
257
- const output = await runAssistantCommand(
258
- "email",
259
- "--json",
260
- "attachment",
261
- MESSAGE_ID,
262
- "--all",
263
- "-o",
264
- tmpDir,
265
- );
263
+ test("single download: attachment not in list → exit code 2", async () => {
264
+ await runAttachment("msg_1", "att-999", "-o", tmpDir);
266
265
 
267
- expect(process.exitCode).toBe(1);
268
- const parsed = JSON.parse(output.trim());
269
- expect(parsed.error).toContain("No attachments");
266
+ expect(process.exitCode).toBe(2);
267
+ const out = captured.join("\n");
268
+ expect(out).toContain("Attachment not found");
270
269
  });
271
270
 
272
- test("no attachment-id and no --all returns error", async () => {
273
- const output = await runAssistantCommand(
274
- "email",
275
- "--json",
276
- "attachment",
277
- MESSAGE_ID,
271
+ test("single download: stream error throws", async () => {
272
+ mockIpcCallStreamFn = mock(() =>
273
+ Promise.resolve({ ok: false, error: "stream failed", statusCode: 500 }),
278
274
  );
279
275
 
280
- expect(process.exitCode).toBe(1);
281
- const parsed = JSON.parse(output.trim());
282
- expect(parsed.error).toContain("Specify an attachment ID");
276
+ // The thrown error from streamDownloadAttachment should propagate out
277
+ // (not caught — let the test framework see it unless the action catches it)
278
+ try {
279
+ await runAttachment("msg_1", "att-001", "-o", tmpDir);
280
+ } catch {
281
+ // acceptable — the stream failure throws
282
+ }
283
+ // Either throws or sets exitCode != 0
284
+ // We just verify the stream was attempted
285
+ expect(mockIpcCallStreamFn.mock.calls.length).toBeGreaterThan(0);
283
286
  });
284
287
 
285
- test("calls correct list URL", async () => {
286
- mockAttachmentList();
288
+ test("--all: calls list then streams each attachment", async () => {
289
+ await runAttachment("msg_1", "--all", "-o", tmpDir);
287
290
 
288
- await runAssistantCommand(
289
- "email",
290
- "attachment",
291
- MESSAGE_ID,
292
- "--list",
293
- );
294
-
295
- // Filter out the CLI bootstrap fetch to /v1/feature-flags so this test
296
- // focuses on the attachment-related calls it actually cares about.
297
- const calls = getMockFetchCalls().filter(
298
- (c) => !c.path.includes("/v1/feature-flags"),
299
- );
300
- expect(calls).toHaveLength(1);
301
- expect(calls[0].path).toContain(
302
- `/v1/assistants/${ASSISTANT_ID}/emails/${MESSAGE_ID}/attachments/`,
303
- );
304
- });
291
+ // First IPC call: list
292
+ const listCall = mockIpcCallFn.mock.calls[0] as unknown as [string, Record<string, unknown>];
293
+ expect(listCall[0]).toBe("email_attachment_list");
305
294
 
306
- test("calls correct detail + download URLs for single download", async () => {
307
- mockAttachmentDetail(SAMPLE_ATTACHMENT_1);
308
- mockAttachmentDownload(ATT_ID_1, "content");
309
-
310
- await runAssistantCommand(
311
- "email",
312
- "attachment",
313
- MESSAGE_ID,
314
- ATT_ID_1,
315
- "-o",
316
- tmpDir,
295
+ // Two stream calls one per attachment
296
+ expect(mockIpcCallStreamFn.mock.calls.length).toBe(2);
297
+ const ids = (mockIpcCallStreamFn.mock.calls as unknown as [string, { queryParams: Record<string, unknown> }][]).map(
298
+ ([, p]) => p.queryParams.attachmentId,
317
299
  );
300
+ expect(ids).toContain("att-001");
301
+ expect(ids).toContain("att-002");
318
302
 
319
- // Filter out the CLI bootstrap fetch to /v1/feature-flags so this test
320
- // focuses on the attachment-related calls it actually cares about.
321
- const calls = getMockFetchCalls().filter(
322
- (c) => !c.path.includes("/v1/feature-flags"),
323
- );
324
- expect(calls).toHaveLength(2);
325
- expect(calls[0].path).toContain(`/attachments/${ATT_ID_1}/`);
326
- expect(calls[1].path).toContain(`/attachments/${ATT_ID_1}/download/`);
303
+ expect(process.exitCode).toBe(0);
327
304
  });
328
305
 
329
- test("404 on detail returns error", async () => {
330
- mockFetch(
331
- `/attachments/${ATT_ID_1}/`,
332
- {},
333
- { body: { detail: "Not found." }, status: 404 },
334
- );
306
+ test("--all: writes files to disk", async () => {
307
+ const pdfBytes = new Uint8Array([37, 80, 68, 70]); // %PDF
308
+ const pngBytes = new Uint8Array([137, 80, 78, 71]); // PNG header
309
+ let callIdx = 0;
310
+ mockIpcCallStreamFn = mock(() => {
311
+ const chunk = callIdx++ === 0 ? pdfBytes : pngBytes;
312
+ return Promise.resolve(makeMockStream([chunk]));
313
+ });
335
314
 
336
- const output = await runAssistantCommand(
337
- "email",
338
- "--json",
339
- "attachment",
340
- MESSAGE_ID,
341
- ATT_ID_1,
342
- "-o",
343
- tmpDir,
344
- );
315
+ await runAttachment("msg_1", "--all", "-o", tmpDir);
345
316
 
346
- expect(process.exitCode).toBe(1);
347
- const parsed = JSON.parse(output.trim());
348
- expect(parsed.error).toContain("Not found");
317
+ expect(existsSync(join(tmpDir, "invoice.pdf"))).toBe(true);
318
+ expect(existsSync(join(tmpDir, "screenshot.png"))).toBe(true);
319
+ expect(process.exitCode).toBe(0);
349
320
  });
350
321
 
351
- test("missing platform credentials returns error", async () => {
352
- await deleteSecureKeyAsync(API_KEY_CREDENTIAL);
353
-
354
- const output = await runAssistantCommand(
355
- "email",
356
- "--json",
357
- "attachment",
358
- MESSAGE_ID,
359
- "--list",
322
+ test("--all with no attachments → exit code 1", async () => {
323
+ mockIpcCallFn = mock(() =>
324
+ Promise.resolve({ ok: true, result: { results: [] } }),
360
325
  );
361
326
 
327
+ await runAttachment("msg_1", "--all", "-o", tmpDir);
328
+
362
329
  expect(process.exitCode).toBe(1);
363
- const parsed = JSON.parse(output.trim());
364
- expect(parsed.error).toContain("Platform credentials not configured");
330
+ expect(captured.join("")).toContain("No attachments");
365
331
  });
366
332
 
367
- test("missing assistant ID returns error", async () => {
368
- setPlatformAssistantId("");
369
-
370
- const output = await runAssistantCommand(
371
- "email",
372
- "--json",
373
- "attachment",
374
- MESSAGE_ID,
375
- "--list",
376
- );
333
+ test("no attachment-id and no --all → exit code 1", async () => {
334
+ await runAttachment("msg_1");
377
335
 
378
336
  expect(process.exitCode).toBe(1);
379
- const parsed = JSON.parse(output.trim());
380
- expect(parsed.error).toContain("Assistant ID");
337
+ expect(captured.join("")).toContain("Specify an attachment ID");
381
338
  });
382
339
 
383
- test("formatBytes displays human-readable sizes", async () => {
384
- mockAttachmentList([
385
- { ...SAMPLE_ATTACHMENT_1, size_bytes: 500 },
386
- { ...SAMPLE_ATTACHMENT_2, size_bytes: 2_500_000 },
387
- ]);
388
-
389
- const output = await runAssistantCommand(
390
- "email",
391
- "attachment",
392
- MESSAGE_ID,
393
- "--list",
340
+ test("path traversal in filename is sanitized", async () => {
341
+ mockIpcCallFn = mock(() =>
342
+ Promise.resolve({
343
+ ok: true,
344
+ result: {
345
+ results: [
346
+ {
347
+ id: "att-001",
348
+ filename: "../../../etc/passwd",
349
+ content_type: "application/octet-stream",
350
+ size_bytes: 100,
351
+ content_id: "",
352
+ created_at: "2026-04-05T12:00:00Z",
353
+ },
354
+ ],
355
+ },
356
+ }),
394
357
  );
395
358
 
396
- expect(output).toContain("500 B");
397
- expect(output).toContain("2.4 MB");
359
+ await runAttachment("msg_1", "att-001", "-o", tmpDir);
360
+
361
+ // Should write to <tmpDir>/passwd, not traverse up
362
+ expect(existsSync(join(tmpDir, "passwd"))).toBe(true);
398
363
  expect(process.exitCode).toBe(0);
399
364
  });
400
365
 
401
- test("path traversal in filename is sanitized", async () => {
402
- mockAttachmentDetail({
403
- ...SAMPLE_ATTACHMENT_1,
404
- filename: "../../../etc/passwd",
405
- });
406
- mockAttachmentDownload(ATT_ID_1, "not-a-real-passwd");
407
-
408
- await runAssistantCommand(
409
- "email",
410
- "attachment",
411
- MESSAGE_ID,
412
- ATT_ID_1,
413
- "-o",
414
- tmpDir,
366
+ test("formatBytes displays human-readable sizes in --list", async () => {
367
+ mockIpcCallFn = mock(() =>
368
+ Promise.resolve({
369
+ ok: true,
370
+ result: {
371
+ results: [
372
+ {
373
+ id: "att-001",
374
+ filename: "tiny.txt",
375
+ content_type: "text/plain",
376
+ size_bytes: 500,
377
+ content_id: "",
378
+ created_at: "2026-04-05T12:00:00Z",
379
+ },
380
+ {
381
+ id: "att-002",
382
+ filename: "big.mp4",
383
+ content_type: "video/mp4",
384
+ size_bytes: 2_500_000,
385
+ content_id: "",
386
+ created_at: "2026-04-05T12:01:00Z",
387
+ },
388
+ ],
389
+ },
390
+ }),
415
391
  );
416
392
 
393
+ const out = await runAttachment("msg_1", "--list");
394
+
395
+ expect(out).toContain("500 B");
396
+ expect(out).toContain("2.4 MB");
417
397
  expect(process.exitCode).toBe(0);
418
- // Should NOT write to ../../../etc/passwd — should strip to just "passwd"
419
- expect(existsSync(join(tmpDir, "passwd"))).toBe(true);
420
- expect(existsSync("/etc/passwd-test")).toBe(false);
421
398
  });
422
- });
399
+ });