@vellumai/assistant 0.8.5 → 0.8.6

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 (544) hide show
  1. package/AGENTS.md +33 -1
  2. package/ARCHITECTURE.md +1 -1
  3. package/bunfig.toml +6 -1
  4. package/docs/credential-execution-service.md +6 -6
  5. package/docs/plugins.md +4 -3
  6. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
  7. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
  8. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
  9. package/openapi.yaml +1900 -166
  10. package/package.json +1 -1
  11. package/src/__tests__/actor-token-service.test.ts +3 -2
  12. package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
  13. package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
  14. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  15. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  16. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  17. package/src/__tests__/annotate-risk-options.test.ts +1 -0
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/approval-routes-http.test.ts +9 -13
  20. package/src/__tests__/assert-not-live-db.ts +79 -0
  21. package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
  22. package/src/__tests__/audit-log-rotation.test.ts +2 -2
  23. package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
  24. package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
  25. package/src/__tests__/browser-skill-endstate.test.ts +3 -3
  26. package/src/__tests__/btw-routes.test.ts +3 -2
  27. package/src/__tests__/call-controller.test.ts +3 -2
  28. package/src/__tests__/channel-approval-routes.test.ts +3 -2
  29. package/src/__tests__/channel-guardian.test.ts +3 -2
  30. package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
  31. package/src/__tests__/channel-reply-delivery.test.ts +35 -0
  32. package/src/__tests__/channel-retry-sweep.test.ts +320 -3
  33. package/src/__tests__/checker.test.ts +12 -12
  34. package/src/__tests__/compaction-events.test.ts +1 -0
  35. package/src/__tests__/compaction-trail-store.test.ts +264 -0
  36. package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
  37. package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
  38. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
  39. package/src/__tests__/computer-use-tools.test.ts +12 -14
  40. package/src/__tests__/config-loader-backfill.test.ts +13 -28
  41. package/src/__tests__/config-loader-corrupt.test.ts +5 -5
  42. package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
  43. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
  44. package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
  45. package/src/__tests__/config-schema.test.ts +10 -10
  46. package/src/__tests__/connection-model-compat.test.ts +83 -0
  47. package/src/__tests__/contacts-tools.test.ts +3 -2
  48. package/src/__tests__/context-token-estimator.test.ts +22 -0
  49. package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
  50. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
  52. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  53. package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
  54. package/src/__tests__/conversation-agent-loop.test.ts +488 -2
  55. package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
  56. package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
  57. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
  58. package/src/__tests__/conversation-attention-store.test.ts +101 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  61. package/src/__tests__/conversation-error.test.ts +30 -0
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -8
  63. package/src/__tests__/conversation-fork-route.test.ts +3 -2
  64. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  65. package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
  66. package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
  67. package/src/__tests__/conversation-lifecycle.test.ts +1 -0
  68. package/src/__tests__/conversation-list-source.test.ts +3 -2
  69. package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
  70. package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
  71. package/src/__tests__/conversation-pairing.test.ts +53 -0
  72. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
  73. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  74. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
  75. package/src/__tests__/conversation-queue.test.ts +333 -291
  76. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
  77. package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
  78. package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
  79. package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
  80. package/src/__tests__/conversation-skill-tools.test.ts +38 -142
  81. package/src/__tests__/conversation-slash-queue.test.ts +84 -32
  82. package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
  83. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
  85. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
  90. package/src/__tests__/conversation-sync-tags.test.ts +128 -12
  91. package/src/__tests__/conversation-title-service.test.ts +1 -0
  92. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
  93. package/src/__tests__/conversation-usage.test.ts +1 -0
  94. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  95. package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
  96. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
  97. package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
  98. package/src/__tests__/credential-broker-server-use.test.ts +5 -5
  99. package/src/__tests__/credential-execution-client.test.ts +72 -1
  100. package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
  101. package/src/__tests__/credential-health-service.test.ts +252 -3
  102. package/src/__tests__/credential-security-invariants.test.ts +5 -5
  103. package/src/__tests__/credential-vault-unit.test.ts +19 -19
  104. package/src/__tests__/credential-vault.test.ts +5 -5
  105. package/src/__tests__/cross-provider-web-search.test.ts +56 -2
  106. package/src/__tests__/db-connection-isolation.test.ts +7 -6
  107. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
  108. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
  109. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
  110. package/src/__tests__/db-test-helpers.ts +58 -0
  111. package/src/__tests__/disk-pressure-guard.test.ts +58 -41
  112. package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
  113. package/src/__tests__/disk-pressure-routes.test.ts +0 -33
  114. package/src/__tests__/disk-pressure-tools.test.ts +0 -4
  115. package/src/__tests__/dm-persistence.test.ts +26 -40
  116. package/src/__tests__/document-create-dedupe.test.ts +189 -0
  117. package/src/__tests__/document-find-replace.test.ts +3 -2
  118. package/src/__tests__/document-tool-security.test.ts +81 -2
  119. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
  120. package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
  121. package/src/__tests__/encrypted-store.test.ts +11 -9
  122. package/src/__tests__/feature-flag-test-helpers.ts +53 -0
  123. package/src/__tests__/filing-service.test.ts +1 -0
  124. package/src/__tests__/first-greeting.test.ts +62 -12
  125. package/src/__tests__/gateway-flag-listener.test.ts +0 -1
  126. package/src/__tests__/gemini-provider.test.ts +26 -0
  127. package/src/__tests__/guardian-action-sweep.test.ts +3 -2
  128. package/src/__tests__/guardian-outbound-http.test.ts +3 -2
  129. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
  130. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
  131. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  132. package/src/__tests__/heartbeat-service.test.ts +1 -0
  133. package/src/__tests__/helpers/mock-logger.ts +26 -0
  134. package/src/__tests__/host-bash-routes.test.ts +1 -0
  135. package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
  136. package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
  137. package/src/__tests__/host-shell-tool.test.ts +5 -4
  138. package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
  139. package/src/__tests__/http-conversation-lineage.test.ts +3 -2
  140. package/src/__tests__/http-user-message-parity.test.ts +29 -7
  141. package/src/__tests__/identity-intro-cache.test.ts +133 -22
  142. package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
  143. package/src/__tests__/inference-profile-reaper.test.ts +3 -2
  144. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
  145. package/src/__tests__/injector-disk-pressure.test.ts +3 -17
  146. package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
  147. package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
  148. package/src/__tests__/llm-context-normalization.test.ts +42 -0
  149. package/src/__tests__/llm-resolver.test.ts +331 -0
  150. package/src/__tests__/llm-schema.test.ts +1 -1
  151. package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
  152. package/src/__tests__/mcp-abort-signal.test.ts +14 -0
  153. package/src/__tests__/mcp-client-auth.test.ts +14 -0
  154. package/src/__tests__/messaging-send-tool.test.ts +1 -0
  155. package/src/__tests__/migration-import-from-url.test.ts +3 -3
  156. package/src/__tests__/mock-gateway-ipc.ts +18 -2
  157. package/src/__tests__/model-intents.test.ts +3 -3
  158. package/src/__tests__/native-web-search.test.ts +30 -2
  159. package/src/__tests__/notification-deep-link.test.ts +62 -0
  160. package/src/__tests__/oauth-commands-routes.test.ts +37 -0
  161. package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
  162. package/src/__tests__/oauth-store.test.ts +3 -2
  163. package/src/__tests__/onboarding-template-contract.test.ts +3 -2
  164. package/src/__tests__/openai-provider.test.ts +8 -9
  165. package/src/__tests__/openai-responses-provider.test.ts +70 -10
  166. package/src/__tests__/openrouter-provider-only.test.ts +27 -5
  167. package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
  168. package/src/__tests__/persistence-pipeline.test.ts +139 -1
  169. package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
  170. package/src/__tests__/plugin-bootstrap.test.ts +9 -11
  171. package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
  172. package/src/__tests__/process-message-background-slack.test.ts +21 -16
  173. package/src/__tests__/process-message-display-content.test.ts +19 -22
  174. package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
  175. package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
  176. package/src/__tests__/provider-registry-ollama.test.ts +45 -22
  177. package/src/__tests__/recording-handler.test.ts +1 -0
  178. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  179. package/src/__tests__/registry.test.ts +82 -76
  180. package/src/__tests__/relay-server.test.ts +10 -10
  181. package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
  182. package/src/__tests__/schedule-store.test.ts +16 -1
  183. package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
  184. package/src/__tests__/secret-ingress-http.test.ts +5 -1
  185. package/src/__tests__/secure-keys.test.ts +3 -3
  186. package/src/__tests__/send-endpoint-busy.test.ts +81 -42
  187. package/src/__tests__/server-history-render.test.ts +4 -1
  188. package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
  189. package/src/__tests__/skill-feature-flags.test.ts +14 -16
  190. package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
  191. package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
  192. package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
  193. package/src/__tests__/skill-tool-factory.test.ts +96 -95
  194. package/src/__tests__/slack-channel-config.test.ts +3 -3
  195. package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
  196. package/src/__tests__/subagent-disposal.test.ts +27 -8
  197. package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
  198. package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
  199. package/src/__tests__/subagent-manager-notify.test.ts +20 -8
  200. package/src/__tests__/subagent-notify-parent.test.ts +5 -4
  201. package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
  202. package/src/__tests__/subagent-tools.test.ts +2 -1
  203. package/src/__tests__/suggestion-routes.test.ts +1 -0
  204. package/src/__tests__/system-prompt.test.ts +38 -0
  205. package/src/__tests__/test-preload-verifier.ts +68 -0
  206. package/src/__tests__/test-preload.ts +32 -39
  207. package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
  208. package/src/__tests__/tool-executor.test.ts +55 -10
  209. package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
  210. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  211. package/src/__tests__/twilio-routes.test.ts +3 -2
  212. package/src/__tests__/validate-input.test.ts +381 -0
  213. package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
  214. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
  215. package/src/__tests__/voice-session-bridge.test.ts +37 -28
  216. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
  217. package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
  218. package/src/acp/session-manager.ts +5 -6
  219. package/src/agent/loop.ts +80 -0
  220. package/src/api/README.md +124 -2
  221. package/src/api/constants/call-sites.ts +27 -0
  222. package/src/api/events/assistant-outbound-attachment.ts +51 -0
  223. package/src/api/events/assistant-text-delta.ts +32 -0
  224. package/src/api/events/assistant-turn-start.ts +33 -0
  225. package/src/api/events/document-comment-created.ts +48 -0
  226. package/src/api/events/document-comment-deleted.ts +24 -0
  227. package/src/api/events/document-comment-reopened.ts +25 -0
  228. package/src/api/events/document-comment-resolved.ts +27 -0
  229. package/src/api/events/generation-cancelled.ts +24 -0
  230. package/src/api/events/generation-handoff.ts +41 -0
  231. package/src/api/events/message-complete.ts +42 -0
  232. package/src/api/events/open-url.ts +30 -0
  233. package/src/{events → api/events}/relationship-state-updated.ts +3 -3
  234. package/src/api/events/tool-use-start.ts +32 -0
  235. package/src/api/index.ts +128 -3
  236. package/src/api/responses/llm-context-response.ts +39 -0
  237. package/src/api/responses/llm-request-log-entry.ts +93 -0
  238. package/src/api/responses/memory-recall-log.ts +65 -0
  239. package/src/api/responses/memory-v2-activation-log.ts +78 -0
  240. package/src/background-wake/background-wake-routes.test.ts +687 -52
  241. package/src/background-wake/platform-client.test.ts +308 -0
  242. package/src/background-wake/platform-client.ts +167 -0
  243. package/src/background-wake/publisher.ts +91 -0
  244. package/src/background-wake/runtime-registry.ts +2 -2
  245. package/src/background-wake/wake-intent-hooks.test.ts +282 -0
  246. package/src/calls/guardian-dispatch.ts +1 -0
  247. package/src/calls/voice-session-bridge.ts +4 -4
  248. package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
  249. package/src/cli/commands/__tests__/notifications.test.ts +184 -40
  250. package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
  251. package/src/cli/commands/channels/index.ts +229 -0
  252. package/src/cli/commands/memory-v3-render.ts +147 -0
  253. package/src/cli/commands/memory-v3.ts +255 -4
  254. package/src/cli/commands/notifications.ts +365 -55
  255. package/src/cli/lib/open-browser.ts +7 -2
  256. package/src/cli/program.ts +2 -0
  257. package/src/config/assistant-feature-flags.ts +23 -42
  258. package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
  259. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  260. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  261. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
  262. package/src/config/call-site-defaults.ts +1 -1
  263. package/src/config/feature-flag-cache.ts +86 -0
  264. package/src/config/feature-flag-registry.json +17 -17
  265. package/src/config/llm-context-resolution.ts +10 -1
  266. package/src/config/llm-resolver.ts +121 -15
  267. package/src/config/loader.ts +4 -5
  268. package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
  269. package/src/config/schemas/heartbeat.ts +1 -1
  270. package/src/config/schemas/llm.ts +90 -1
  271. package/src/config/schemas/memory-v2.ts +26 -0
  272. package/src/config/schemas/services.ts +6 -2
  273. package/src/config/seed-inference-profiles.ts +36 -16
  274. package/src/context/token-estimator.ts +10 -5
  275. package/src/credential-execution/executable-discovery.ts +40 -0
  276. package/src/credential-execution/process-manager.ts +6 -2
  277. package/src/credential-health/credential-health-service.ts +125 -40
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
  279. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
  280. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
  281. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
  282. package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
  283. package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
  284. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
  285. package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
  286. package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
  287. package/src/daemon/conversation-agent-loop.ts +132 -28
  288. package/src/daemon/conversation-error.ts +33 -5
  289. package/src/daemon/conversation-messaging.ts +84 -43
  290. package/src/daemon/conversation-process.ts +74 -37
  291. package/src/daemon/conversation-runtime-assembly.ts +29 -9
  292. package/src/daemon/conversation-skill-tools.ts +14 -30
  293. package/src/daemon/conversation-surfaces.ts +69 -34
  294. package/src/daemon/conversation-tool-setup.ts +33 -48
  295. package/src/daemon/conversation.ts +26 -46
  296. package/src/daemon/daemon-control.ts +1 -1
  297. package/src/daemon/daemon-skill-host.ts +9 -2
  298. package/src/daemon/disk-pressure-guard.ts +27 -29
  299. package/src/daemon/first-greeting.ts +31 -13
  300. package/src/daemon/handlers/shared.ts +6 -1
  301. package/src/daemon/lifecycle.ts +12 -12
  302. package/src/daemon/mcp-reload-service.ts +1 -1
  303. package/src/daemon/meet-manifest-loader.ts +10 -17
  304. package/src/daemon/message-types/conversations.ts +20 -22
  305. package/src/daemon/message-types/document-comments.ts +8 -44
  306. package/src/daemon/message-types/home.ts +2 -2
  307. package/src/daemon/message-types/integrations.ts +2 -7
  308. package/src/daemon/message-types/messages.ts +23 -38
  309. package/src/daemon/message-types/subagents.ts +6 -0
  310. package/src/daemon/process-message.ts +9 -9
  311. package/src/daemon/providers-setup.ts +1 -1
  312. package/src/daemon/server.ts +16 -0
  313. package/src/daemon/switch-inference-profile-tool.ts +13 -3
  314. package/src/daemon/tool-setup-types.ts +0 -6
  315. package/src/daemon/wake-target-adapter.ts +10 -0
  316. package/src/documents/document-store.ts +38 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +1 -0
  318. package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
  319. package/src/heartbeat/heartbeat-service.ts +63 -0
  320. package/src/home/__tests__/feed-writer.test.ts +161 -0
  321. package/src/home/__tests__/post-connect-feed.test.ts +1 -0
  322. package/src/home/__tests__/suggested-prompts.test.ts +55 -59
  323. package/src/home/feed-writer.ts +146 -7
  324. package/src/home/suggested-prompts.ts +27 -145
  325. package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
  326. package/src/ipc/gateway-client.test.ts +4 -1
  327. package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
  328. package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
  329. package/src/ipc/skill-routes/memory.ts +4 -3
  330. package/src/ipc/skill-routes/registries.ts +28 -29
  331. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
  332. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
  333. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
  334. package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
  335. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
  336. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
  337. package/src/memory/conversation-attention-store.ts +17 -3
  338. package/src/memory/conversation-crud.ts +352 -112
  339. package/src/memory/db-connection.ts +29 -19
  340. package/src/memory/db-init.ts +4 -0
  341. package/src/memory/db-singleton.ts +77 -0
  342. package/src/memory/delivery-channels.ts +82 -0
  343. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
  344. package/src/memory/graph/retriever.test.ts +3 -3
  345. package/src/memory/job-handlers/embedding.test.ts +3 -2
  346. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
  347. package/src/memory/jobs-worker.ts +12 -1
  348. package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
  349. package/src/memory/llm-request-log-source-local.ts +24 -0
  350. package/src/memory/llm-request-log-source.ts +31 -0
  351. package/src/memory/llm-request-log-store.ts +188 -3
  352. package/src/memory/memory-v2-activation-log-store.ts +95 -1
  353. package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
  354. package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
  355. package/src/memory/migrations/index.ts +2 -0
  356. package/src/memory/schema/conversations.ts +9 -1
  357. package/src/memory/schema/inference.ts +0 -1
  358. package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
  359. package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
  360. package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
  361. package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
  362. package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
  363. package/src/memory/v2/harness/metrics.ts +5 -1
  364. package/src/memory/v2/harness/replay-input.ts +19 -3
  365. package/src/memory/v2/harness/runner.ts +6 -0
  366. package/src/memory/v2/harness/trace.ts +6 -0
  367. package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
  368. package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
  369. package/src/memory/v3/__tests__/edges.test.ts +144 -1
  370. package/src/memory/v3/__tests__/filter.test.ts +48 -0
  371. package/src/memory/v3/__tests__/gate.test.ts +96 -33
  372. package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
  373. package/src/memory/v3/__tests__/loop.test.ts +250 -5
  374. package/src/memory/v3/__tests__/scouts.test.ts +49 -0
  375. package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
  376. package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
  377. package/src/memory/v3/__tests__/traversal.test.ts +39 -0
  378. package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
  379. package/src/memory/v3/__tests__/validate.test.ts +32 -0
  380. package/src/memory/v3/coretrieval-seed.ts +240 -0
  381. package/src/memory/v3/edges.ts +58 -21
  382. package/src/memory/v3/filter.ts +27 -22
  383. package/src/memory/v3/gate.ts +51 -36
  384. package/src/memory/v3/index-composition.ts +18 -5
  385. package/src/memory/v3/loop.ts +65 -17
  386. package/src/memory/v3/scouts.ts +15 -4
  387. package/src/memory/v3/shadow-diff.ts +287 -0
  388. package/src/memory/v3/shadow-middleware.ts +44 -2
  389. package/src/memory/v3/traversal.ts +6 -1
  390. package/src/memory/v3/tree-walk.ts +6 -1
  391. package/src/memory/v3/validate.ts +56 -33
  392. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  393. package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
  394. package/src/notifications/adapters/slack.ts +45 -11
  395. package/src/notifications/broadcaster.ts +114 -63
  396. package/src/notifications/conversation-pairing.ts +23 -3
  397. package/src/notifications/decisions-store.ts +32 -1
  398. package/src/notifications/deliveries-store.ts +45 -0
  399. package/src/notifications/edit-notification.ts +201 -0
  400. package/src/notifications/emit-signal.ts +11 -1
  401. package/src/notifications/signal.ts +10 -0
  402. package/src/notifications/types.ts +37 -0
  403. package/src/oauth/byo-connection.test.ts +67 -3
  404. package/src/oauth/byo-connection.ts +32 -5
  405. package/src/oauth/connect-orchestrator.ts +9 -0
  406. package/src/oauth/connection-resolver.test.ts +76 -0
  407. package/src/oauth/connection-resolver.ts +49 -10
  408. package/src/oauth/manual-token-connection.ts +51 -3
  409. package/src/oauth/seed-providers.ts +3 -0
  410. package/src/permissions/approval-policy.test.ts +19 -5
  411. package/src/permissions/approval-policy.ts +14 -3
  412. package/src/permissions/checker.ts +21 -8
  413. package/src/platform/client.test.ts +24 -1
  414. package/src/platform/client.ts +8 -0
  415. package/src/platform/feature-gate.ts +15 -0
  416. package/src/plugins/defaults/injectors.ts +2 -8
  417. package/src/plugins/defaults/persistence.ts +25 -6
  418. package/src/plugins/types.ts +57 -13
  419. package/src/proactive-artifact/job.test.ts +1 -0
  420. package/src/prompts/__tests__/system-prompt.test.ts +4 -4
  421. package/src/prompts/system-prompt.ts +38 -40
  422. package/src/prompts/template-detection.ts +10 -4
  423. package/src/prompts/templates/BOOTSTRAP.md +7 -11
  424. package/src/prompts/templates/IDENTITY.md +0 -2
  425. package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
  426. package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
  427. package/src/providers/call-site-routing.ts +33 -9
  428. package/src/providers/connection-model-compat.ts +23 -0
  429. package/src/providers/connection-resolution.ts +39 -20
  430. package/src/providers/fireworks/client.ts +1 -0
  431. package/src/providers/gemini/client.ts +24 -3
  432. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
  433. package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
  434. package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
  435. package/src/providers/inference/auth.ts +0 -8
  436. package/src/providers/inference/connections.ts +3 -66
  437. package/src/providers/inference/resolve-auth.ts +2 -3
  438. package/src/providers/model-catalog.ts +35 -1
  439. package/src/providers/model-intents.ts +3 -3
  440. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  441. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
  442. package/src/providers/openai/chat-completions-provider.ts +110 -12
  443. package/src/providers/openai/codex-models.ts +2 -0
  444. package/src/providers/openai/responses-provider.ts +53 -53
  445. package/src/providers/openrouter/client.ts +13 -8
  446. package/src/providers/provider-send-message.ts +18 -9
  447. package/src/providers/registry.ts +48 -8
  448. package/src/providers/retry.ts +16 -4
  449. package/src/providers/search-provider-catalog.ts +17 -9
  450. package/src/providers/types.ts +9 -0
  451. package/src/runtime/__tests__/agent-wake.test.ts +1 -0
  452. package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
  453. package/src/runtime/access-request-helper.ts +1 -0
  454. package/src/runtime/auth/route-policy.ts +10 -0
  455. package/src/runtime/channel-readiness-service.ts +68 -0
  456. package/src/runtime/channel-reply-delivery.ts +23 -0
  457. package/src/runtime/channel-retry-sweep.ts +47 -14
  458. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  459. package/src/runtime/migrations/vbundle-builder.ts +3 -2
  460. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
  461. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
  462. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  463. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  464. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
  465. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
  466. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
  467. package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
  468. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
  469. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
  470. package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
  471. package/src/runtime/routes/acp-routes-list.test.ts +3 -0
  472. package/src/runtime/routes/app-management-routes.ts +111 -4
  473. package/src/runtime/routes/background-wake-routes.ts +188 -20
  474. package/src/runtime/routes/btw-routes.ts +4 -4
  475. package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
  476. package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
  477. package/src/runtime/routes/conversation-list-routes.ts +147 -0
  478. package/src/runtime/routes/conversation-management-routes.ts +39 -14
  479. package/src/runtime/routes/conversation-query-routes.ts +60 -10
  480. package/src/runtime/routes/conversation-routes.ts +186 -140
  481. package/src/runtime/routes/conversations-import-routes.ts +19 -6
  482. package/src/runtime/routes/documents-routes.ts +10 -1
  483. package/src/runtime/routes/group-routes.ts +11 -0
  484. package/src/runtime/routes/home-feed-routes.ts +129 -0
  485. package/src/runtime/routes/identity-intro-cache.ts +61 -16
  486. package/src/runtime/routes/identity-routes.ts +30 -9
  487. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
  488. package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
  489. package/src/runtime/routes/index.ts +2 -0
  490. package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
  491. package/src/runtime/routes/integrations/vercel.ts +15 -0
  492. package/src/runtime/routes/llm-context-normalization.ts +7 -2
  493. package/src/runtime/routes/memory-v3-routes.ts +160 -2
  494. package/src/runtime/routes/migration-routes.ts +20 -13
  495. package/src/runtime/routes/notification-routes.ts +63 -1
  496. package/src/runtime/routes/oauth-commands-routes.ts +6 -1
  497. package/src/runtime/routes/surface-action-routes.ts +1 -38
  498. package/src/runtime/routes/surface-content-routes.ts +12 -5
  499. package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
  500. package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
  501. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
  502. package/src/runtime/slack-dm-text-delivery.ts +177 -0
  503. package/src/runtime/sync/resource-sync-events.ts +1 -1
  504. package/src/runtime/tool-grant-request-helper.ts +1 -0
  505. package/src/schedule/schedule-store.ts +8 -1
  506. package/src/schedule/scheduler.ts +111 -15
  507. package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
  508. package/src/security/encrypted-store.ts +7 -16
  509. package/src/security/store-path-override.ts +61 -0
  510. package/src/signals/user-message.ts +5 -8
  511. package/src/skills/validate-input.ts +177 -0
  512. package/src/subagent/manager.ts +13 -13
  513. package/src/subagent/types.ts +6 -0
  514. package/src/tasks/tool-sanitizer.ts +2 -2
  515. package/src/tools/apps/definitions.ts +35 -21
  516. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
  517. package/src/tools/computer-use/definitions.ts +268 -266
  518. package/src/tools/document/document-tool.ts +131 -8
  519. package/src/tools/execution-target.ts +2 -5
  520. package/src/tools/executor.ts +18 -55
  521. package/src/tools/host-filesystem/edit.test.ts +1 -0
  522. package/src/tools/host-filesystem/read.test.ts +1 -0
  523. package/src/tools/host-filesystem/transfer.test.ts +31 -6
  524. package/src/tools/host-filesystem/write.test.ts +1 -0
  525. package/src/tools/mcp/mcp-tool-factory.ts +0 -2
  526. package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
  527. package/src/tools/network/__tests__/web-search.test.ts +211 -3
  528. package/src/tools/network/managed-search-proxy.ts +183 -0
  529. package/src/tools/network/web-search.ts +199 -44
  530. package/src/tools/policy-context.ts +3 -1
  531. package/src/tools/registry.ts +146 -103
  532. package/src/tools/schedule/create.ts +1 -1
  533. package/src/tools/skills/skill-tool-factory.ts +17 -36
  534. package/src/tools/subagent/spawn.ts +3 -0
  535. package/src/tools/tool-approval-handler.ts +10 -4
  536. package/src/tools/tool-name-aliases.ts +72 -14
  537. package/src/tools/types.ts +17 -15
  538. package/src/tools/ui-surface/definitions.ts +98 -86
  539. package/src/types/onboarding-context.ts +6 -0
  540. package/src/usage/attribution.ts +32 -1
  541. package/src/util/browser.ts +7 -2
  542. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
  543. package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
  544. package/src/workspace/migrations/registry.ts +4 -0
@@ -1,5 +1,6 @@
1
1
  import type { Command } from "commander";
2
2
 
3
+ import type { FeedItem } from "../../home/feed-types.js";
3
4
  import {
4
5
  cliIpcCall,
5
6
  exitCodeFromIpcResult,
@@ -10,6 +11,66 @@ import { log } from "../logger.js";
10
11
  import { shouldOutputJson, writeOutput } from "../output.js";
11
12
  import { tryResolveConversationId } from "../utils/conversation-id.js";
12
13
 
14
+ // ---------------------------------------------------------------------------
15
+ // Local types & helpers
16
+ // ---------------------------------------------------------------------------
17
+
18
+ interface ListHomeFeedPayload {
19
+ items: FeedItem[];
20
+ total: number;
21
+ returned: number;
22
+ hasMore: boolean;
23
+ updatedAt: string;
24
+ }
25
+
26
+ function parseBoundedInt(
27
+ raw: string | undefined,
28
+ label: string,
29
+ bounds: { min: number; max?: number },
30
+ ): { value?: number; error?: string } {
31
+ if (raw === undefined) return {};
32
+ const n = Number(raw);
33
+ const upper = bounds.max ?? Infinity;
34
+ if (
35
+ !Number.isFinite(n) ||
36
+ !Number.isInteger(n) ||
37
+ n < bounds.min ||
38
+ n > upper
39
+ ) {
40
+ const range =
41
+ bounds.max !== undefined
42
+ ? `[${bounds.min}, ${bounds.max}]`
43
+ : `>= ${bounds.min}`;
44
+ return {
45
+ error: `Invalid ${label} "${raw}". Must be an integer ${range}`,
46
+ };
47
+ }
48
+ return { value: n };
49
+ }
50
+
51
+ function renderFeedItemsHuman(payload: ListHomeFeedPayload): void {
52
+ if (payload.items.length === 0) {
53
+ log.info("No notifications match the filters.");
54
+ return;
55
+ }
56
+ log.info(`${payload.returned} of ${payload.total} notifications:\n`);
57
+ for (const item of payload.items) {
58
+ const idShort = item.id.slice(0, 8);
59
+ const status = item.status.toUpperCase().padEnd(10);
60
+ const urgency = (item.urgency ?? "").padEnd(8);
61
+ const headline = item.title ?? item.summary;
62
+ const convoTag = item.conversationId
63
+ ? ` (conv: ${item.conversationId.slice(0, 8)})`
64
+ : "";
65
+ log.info(
66
+ ` ${idShort} ${item.createdAt} ${status} ${urgency} ${headline}${convoTag}`,
67
+ );
68
+ }
69
+ if (payload.hasMore) {
70
+ log.info("\n(more results available; bump --offset to paginate)");
71
+ }
72
+ }
73
+
13
74
  // ---------------------------------------------------------------------------
14
75
  // Command registration
15
76
  // ---------------------------------------------------------------------------
@@ -52,7 +113,7 @@ Examples:
52
113
  )
53
114
  .requiredOption(
54
115
  "--message <message>",
55
- "Notification message the user should receive",
116
+ "Notification body. Markdown (GFM) renders in the detail panel; the OS banner shows plain text.",
56
117
  )
57
118
  .option(
58
119
  "--urgent",
@@ -67,7 +128,10 @@ Examples:
67
128
  "--source-event-name <name>",
68
129
  "Event name for audit, routing, and dedupe grouping (default: assistant.share)",
69
130
  )
70
- .option("--title <title>", "Optional notification title")
131
+ .option(
132
+ "--title <title>",
133
+ "Short headline (≤ 8 words). Always provide one — the auto-derived fallback just truncates --message.",
134
+ )
71
135
  .option(
72
136
  "--urgency <urgency>",
73
137
  "Urgency hint: low, medium, high, critical (default: low; use --urgent for critical)",
@@ -353,84 +417,189 @@ Examples:
353
417
  // list
354
418
  // -------------------------------------------------------------------------
355
419
 
420
+ // Commander's `.exitOverride()` (used in tests) swallows thrown errors
421
+ // from collector functions, so we append values here and validate the
422
+ // accumulated array inside the action handler instead.
423
+ const collectFlag = (
424
+ value: string,
425
+ prev: string[] | undefined,
426
+ ): string[] => [...(prev ?? []), value];
427
+
428
+ function validateEnumFlag(
429
+ values: string[] | undefined,
430
+ label: string,
431
+ allowed: readonly string[],
432
+ ): { error?: string } {
433
+ if (!values) return {};
434
+ for (const v of values) {
435
+ if (!allowed.includes(v)) {
436
+ return {
437
+ error: `Invalid ${label} "${v}". Must be one of: ${allowed.join(", ")}`,
438
+ };
439
+ }
440
+ }
441
+ return {};
442
+ }
443
+
356
444
  notifications
357
445
  .command("list")
358
446
  .description(
359
- "List recent notification events from the local event store",
447
+ "List notifications surfaced to the user via the home feed. Excludes dismissed items by default.",
448
+ )
449
+ .option(
450
+ "--all",
451
+ "Include dismissed items (default: only new/seen/acted_on)",
452
+ false,
453
+ )
454
+ .option(
455
+ "--status <status>",
456
+ "Filter by status (new|seen|acted_on|dismissed); repeatable. Overrides --all default behavior.",
457
+ collectFlag,
360
458
  )
459
+ .option(
460
+ "--before <iso>",
461
+ "Only items with createdAt strictly before this ISO-8601 timestamp",
462
+ )
463
+ .option(
464
+ "--after <iso>",
465
+ "Only items with createdAt strictly after this ISO-8601 timestamp",
466
+ )
467
+ .option(
468
+ "--urgency <urgency>",
469
+ "Filter by urgency (low|medium|high|critical); repeatable",
470
+ collectFlag,
471
+ )
472
+ .option(
473
+ "--category <category>",
474
+ "Filter by category (security|scheduling|background|email|system); repeatable",
475
+ collectFlag,
476
+ )
477
+ .option(
478
+ "--conversation-id <id>",
479
+ "Only items tied to this conversation id",
480
+ )
481
+ .option(
482
+ "--from-assistant",
483
+ "Only items emitted by the assistant",
484
+ false,
485
+ )
486
+ .option("--noteworthy", "Only items flagged as noteworthy", false)
361
487
  .option(
362
488
  "--limit <n>",
363
- "Maximum number of events to return (default: 20)",
489
+ "Maximum number of items to return (default: 20, max: 200)",
364
490
  )
365
- .option("--source-event-name <name>", "Filter by source event name")
491
+ .option("--offset <n>", "Pagination offset (default: 0)")
366
492
  .addHelpText(
367
493
  "after",
368
494
  `
369
- Reads from the local notification events store, ordered by creation time
370
- (newest first). Each event represents a signal that was emitted through the
371
- notification pipeline.
495
+ Reads the home feed at ~/.vellum/workspace/data/home-feed.json the
496
+ user's notification inbox. Items are ordered by priority then recency,
497
+ matching what the user sees in the macOS Home page. The home feed covers
498
+ background/async notifications mirrored from the unified pipeline;
499
+ real-time chat pushes that did not mirror to the feed will not appear.
372
500
 
373
501
  Examples:
374
502
  $ assistant notifications list
375
- $ assistant notifications list --limit 5
376
- $ assistant notifications list --source-event-name schedule.notify
377
- $ assistant notifications list --source-event-name schedule.notify --limit 10 --json`,
503
+ $ assistant notifications list --all
504
+ $ assistant notifications list --status new --status seen
505
+ $ assistant notifications list --after 2026-05-28T00:00:00Z --urgency high
506
+ $ assistant notifications list --conversation-id 7fab234c --json
507
+ $ assistant notifications list --limit 5 --offset 5`,
378
508
  )
379
509
  .action(
380
510
  async (
381
511
  opts: {
512
+ all?: boolean;
513
+ status?: string[];
514
+ before?: string;
515
+ after?: string;
516
+ urgency?: string[];
517
+ category?: string[];
518
+ conversationId?: string;
519
+ fromAssistant?: boolean;
520
+ noteworthy?: boolean;
382
521
  limit?: string;
383
- sourceEventName?: string;
522
+ offset?: string;
384
523
  },
385
524
  cmd: Command,
386
525
  ) => {
387
526
  try {
388
- // Validate --source-event-name (accept any non-empty string; custom
389
- // event names are valid since skills can emit arbitrary names)
390
- if (
391
- opts.sourceEventName != null &&
392
- opts.sourceEventName.trim().length === 0
393
- ) {
394
- writeOutput(cmd, {
395
- ok: false,
396
- error: "Source event name must be a non-empty string",
397
- });
527
+ const enumChecks: Array<{ error?: string }> = [
528
+ validateEnumFlag(opts.status, "status", [
529
+ "new",
530
+ "seen",
531
+ "acted_on",
532
+ "dismissed",
533
+ ]),
534
+ validateEnumFlag(opts.urgency, "urgency", [
535
+ "low",
536
+ "medium",
537
+ "high",
538
+ "critical",
539
+ ]),
540
+ validateEnumFlag(opts.category, "category", [
541
+ "security",
542
+ "scheduling",
543
+ "background",
544
+ "email",
545
+ "system",
546
+ ]),
547
+ ];
548
+ const enumError = enumChecks.find((c) => c.error);
549
+ if (enumError) {
550
+ writeOutput(cmd, { ok: false, error: enumError.error });
398
551
  process.exitCode = 1;
399
552
  return;
400
553
  }
401
554
 
402
- // Parse and validate --limit
403
- let limit = 20;
404
- if (opts.limit != null) {
405
- const parsed = Number(opts.limit);
406
- if (
407
- !Number.isFinite(parsed) ||
408
- !Number.isInteger(parsed) ||
409
- parsed < 1
410
- ) {
555
+ const limit = parseBoundedInt(opts.limit, "limit", {
556
+ min: 1,
557
+ max: 200,
558
+ });
559
+ if (limit.error) {
560
+ writeOutput(cmd, { ok: false, error: limit.error });
561
+ process.exitCode = 1;
562
+ return;
563
+ }
564
+ const offset = parseBoundedInt(opts.offset, "offset", {
565
+ min: 0,
566
+ });
567
+ if (offset.error) {
568
+ writeOutput(cmd, { ok: false, error: offset.error });
569
+ process.exitCode = 1;
570
+ return;
571
+ }
572
+
573
+ if (opts.conversationId != null) {
574
+ const trimmed = opts.conversationId.trim();
575
+ if (trimmed.length === 0) {
411
576
  writeOutput(cmd, {
412
577
  ok: false,
413
- error: `Invalid limit "${opts.limit}". Must be a positive integer`,
578
+ error: "Conversation ID must be a non-empty string",
414
579
  });
415
580
  process.exitCode = 1;
416
581
  return;
417
582
  }
418
- limit = parsed;
419
583
  }
420
584
 
421
- const result = await cliIpcCall<
422
- Array<{
423
- id: string;
424
- sourceEventName: string;
425
- sourceChannel: string;
426
- sourceContextId: string;
427
- urgency: string;
428
- dedupeKey: string | null;
429
- createdAt: string;
430
- }>
431
- >("list_notification_events", {
432
- body: { limit, sourceEventName: opts.sourceEventName },
433
- });
585
+ const body: Record<string, unknown> = {};
586
+ if (opts.all) body.includeDismissed = true;
587
+ if (opts.status?.length) body.statuses = opts.status;
588
+ if (opts.before) body.before = opts.before;
589
+ if (opts.after) body.after = opts.after;
590
+ if (opts.urgency?.length) body.urgencies = opts.urgency;
591
+ if (opts.category?.length) body.categories = opts.category;
592
+ if (opts.conversationId)
593
+ body.conversationId = opts.conversationId.trim();
594
+ if (opts.fromAssistant) body.fromAssistant = true;
595
+ if (opts.noteworthy) body.noteworthy = true;
596
+ if (limit.value !== undefined) body.limit = limit.value;
597
+ if (offset.value !== undefined) body.offset = offset.value;
598
+
599
+ const result = await cliIpcCall<ListHomeFeedPayload>(
600
+ "list_home_feed",
601
+ { body },
602
+ );
434
603
 
435
604
  if (!result.ok) {
436
605
  writeOutput(cmd, { ok: false, error: result.error });
@@ -438,19 +607,160 @@ Examples:
438
607
  return;
439
608
  }
440
609
 
441
- const events = result.result!;
610
+ const payload = result.result!;
611
+ writeOutput(cmd, { ok: true, ...payload });
612
+
613
+ if (!shouldOutputJson(cmd)) {
614
+ renderFeedItemsHuman(payload);
615
+ }
616
+ } catch (err) {
617
+ const message = err instanceof Error ? err.message : String(err);
618
+ writeOutput(cmd, { ok: false, error: message });
619
+ process.exitCode = 1;
620
+ }
621
+ },
622
+ );
623
+
624
+ // -------------------------------------------------------------------------
625
+ // edit
626
+ // -------------------------------------------------------------------------
627
+
628
+ notifications
629
+ .command("edit")
630
+ .description(
631
+ "Edit an already-sent notification. Patches the home-feed entry and updates the delivered channel message in place where supported (Slack today).",
632
+ )
633
+ .requiredOption(
634
+ "--id <id>",
635
+ "Feed item id (notif:<uuid>) from `notifications list --json`. Bare uuids without the `notif:` prefix are also accepted.",
636
+ )
637
+ .option(
638
+ "--message <message>",
639
+ "New notification body. Updates the home-feed summary and the delivered channel message text where supported.",
640
+ )
641
+ .option("--title <title>", "New short headline (≤ 8 words).")
642
+ .option(
643
+ "--urgency <urgency>",
644
+ "Set urgency (low|medium|high|critical). Feed-only — does not re-push channel messages.",
645
+ )
646
+ .option(
647
+ "--status <status>",
648
+ "Set lifecycle status (new|seen|acted_on|dismissed). Feed-only.",
649
+ )
650
+ .addHelpText(
651
+ "after",
652
+ `
653
+ At least one of --message, --title, --urgency, or --status must be
654
+ supplied. --urgency and --status only update the home-feed entry —
655
+ they never re-push channel messages.
656
+
657
+ Channel updates are best-effort: Slack messages get updated via
658
+ chat.update; other channels (push, email, SMS) cannot be edited and
659
+ are reported as "unsupported".
660
+
661
+ Examples:
662
+ $ assistant notifications edit --id notif:abc12345-... --message "Fixed body"
663
+ $ assistant notifications edit --id abc12345-... --title "Backup complete"
664
+ $ assistant notifications edit --id notif:abc12345-... --urgency low
665
+ $ assistant notifications edit --id notif:abc12345-... --status dismissed`,
666
+ )
667
+ .action(
668
+ async (
669
+ opts: {
670
+ id: string;
671
+ message?: string;
672
+ title?: string;
673
+ urgency?: string;
674
+ status?: string;
675
+ },
676
+ cmd: Command,
677
+ ) => {
678
+ try {
679
+ const id = opts.id.trim();
680
+ if (!id) {
681
+ writeOutput(cmd, {
682
+ ok: false,
683
+ error: "--id must be a non-empty string",
684
+ });
685
+ process.exitCode = 1;
686
+ return;
687
+ }
688
+
689
+ if (
690
+ opts.message === undefined &&
691
+ opts.title === undefined &&
692
+ opts.urgency === undefined &&
693
+ opts.status === undefined
694
+ ) {
695
+ writeOutput(cmd, {
696
+ ok: false,
697
+ error:
698
+ "At least one of --message, --title, --urgency, or --status must be supplied",
699
+ });
700
+ process.exitCode = 1;
701
+ return;
702
+ }
703
+
704
+ if (
705
+ opts.urgency != null &&
706
+ !["low", "medium", "high", "critical"].includes(opts.urgency)
707
+ ) {
708
+ writeOutput(cmd, {
709
+ ok: false,
710
+ error: `Invalid urgency "${opts.urgency}". Must be one of: low, medium, high, critical`,
711
+ });
712
+ process.exitCode = 1;
713
+ return;
714
+ }
715
+ if (
716
+ opts.status != null &&
717
+ !["new", "seen", "acted_on", "dismissed"].includes(opts.status)
718
+ ) {
719
+ writeOutput(cmd, {
720
+ ok: false,
721
+ error: `Invalid status "${opts.status}". Must be one of: new, seen, acted_on, dismissed`,
722
+ });
723
+ process.exitCode = 1;
724
+ return;
725
+ }
726
+
727
+ const body: Record<string, unknown> = { id };
728
+ if (opts.message !== undefined) body.body = opts.message;
729
+ if (opts.title !== undefined) body.title = opts.title;
730
+ if (opts.urgency !== undefined) body.urgency = opts.urgency;
731
+ if (opts.status !== undefined) body.status = opts.status;
732
+
733
+ const result = await cliIpcCall<{
734
+ feedItem: FeedItem;
735
+ channels: Array<{
736
+ channel: string;
737
+ deliveryId: string;
738
+ outcome: "updated" | "unsupported" | "skipped" | "failed";
739
+ reason?: string;
740
+ }>;
741
+ }>("edit_notification", { body });
742
+
743
+ if (!result.ok) {
744
+ writeOutput(cmd, { ok: false, error: result.error });
745
+ process.exitCode = exitCodeFromIpcResult(result);
746
+ return;
747
+ }
442
748
 
443
- writeOutput(cmd, { ok: true, events });
749
+ const payload = result.result!;
750
+ writeOutput(cmd, { ok: true, ...payload });
444
751
 
445
752
  if (!shouldOutputJson(cmd)) {
446
- if (events.length === 0) {
447
- log.info("No notification events found");
753
+ const item = payload.feedItem;
754
+ log.info(`Updated ${item.id}`);
755
+ const headline = item.title ?? item.summary;
756
+ log.info(` ${headline}`);
757
+ if (payload.channels.length === 0) {
758
+ log.info(" No channel deliveries to update.");
448
759
  } else {
449
- log.info(`${events.length} event(s):\n`);
450
- for (const event of events) {
451
- log.info(
452
- ` ${event.createdAt} ${event.sourceEventName} ${event.urgency} ${event.sourceChannel}`,
453
- );
760
+ log.info(" Channels:");
761
+ for (const ch of payload.channels) {
762
+ const reason = ch.reason ? ` — ${ch.reason}` : "";
763
+ log.info(` ${ch.channel}: ${ch.outcome}${reason}`);
454
764
  }
455
765
  }
456
766
  }
@@ -2,8 +2,13 @@
2
2
  * CLI-side helper that opens a URL on the user's host machine.
3
3
  *
4
4
  * Writes an `open_url` event to the `signals/emit-event` file so that the
5
- * daemon's ConfigWatcher picks it up and publishes it to connected clients
6
- * (e.g. the Swift macOS app) via the assistant event hub.
5
+ * assistant's ConfigWatcher picks it up and publishes it to connected
6
+ * clients (e.g. the Swift macOS app) via the assistant event hub.
7
+ *
8
+ * CLI-initiated emit — no conversation context available, so the inner
9
+ * message has no `conversationId`. That's fine: `OpenUrlEventSchema`
10
+ * declares `conversationId` as optional, so this payload parses
11
+ * cleanly on the web side as well as in the Swift macOS app.
7
12
  *
8
13
  * Uses only `node:` imports so it's safe for `ipc`-tagged CLI commands.
9
14
  */
@@ -18,6 +18,7 @@ import { registerBrowserCommand } from "./commands/browser.js";
18
18
  import { registerCacheCommand } from "./commands/cache.js";
19
19
  import { registerChangelogCommand } from "./commands/changelog.js";
20
20
  import { registerChannelVerificationSessionsCommand } from "./commands/channel-verification-sessions.js";
21
+ import { registerChannelsCommand } from "./commands/channels/index.js";
21
22
  import { registerClientsCommand } from "./commands/clients.js";
22
23
  import { registerCompletionsCommand } from "./commands/completions.js";
23
24
  import { registerConfigCommand } from "./commands/config.js";
@@ -113,6 +114,7 @@ Examples:
113
114
  registerCacheCommand(program);
114
115
  registerChangelogCommand(program);
115
116
  registerChannelVerificationSessionsCommand(program);
117
+ registerChannelsCommand(program);
116
118
  registerClientsCommand(program);
117
119
  registerCompletionsCommand(program);
118
120
  registerConfigCommand(program);
@@ -18,6 +18,12 @@ import { dirname, join } from "node:path";
18
18
 
19
19
  import { ipcGetFeatureFlags } from "../ipc/gateway-client.js";
20
20
  import { getLogger } from "../util/logger.js";
21
+ import {
22
+ clearCachedOverrides,
23
+ getCachedOverrides,
24
+ isCachedFromGateway,
25
+ setCachedOverrides,
26
+ } from "./feature-flag-cache.js";
21
27
  import type { AssistantConfig } from "./schema.js";
22
28
 
23
29
  const log = getLogger("assistant-feature-flags");
@@ -112,22 +118,10 @@ function parseRegistryToDefaults(parsed: unknown): FeatureFlagDefaultsRegistry {
112
118
  // ---------------------------------------------------------------------------
113
119
  // Override loading — reads from gateway IPC socket
114
120
  // ---------------------------------------------------------------------------
115
-
116
- /**
117
- * Module-level cache of feature flag override values. Populated by
118
- * `initFeatureFlagOverrides()` at startup, invalidated by
119
- * `clearFeatureFlagOverridesCache()`.
120
- */
121
- let cachedOverrides: Record<string, boolean> | null = null;
122
-
123
- /**
124
- * True when `cachedOverrides` was populated by the gateway IPC fetch or
125
- * preseeded by a test via `_setOverridesForTesting()`. Guards
126
- * `initFeatureFlagOverrides()` from clobbering an existing populated cache
127
- * when called a second time (e.g. by a CLI entry point after the daemon
128
- * has already initialized).
129
- */
130
- let cachedOverridesFromGateway = false;
121
+ //
122
+ // The override cache lives in `feature-flag-cache.ts` (stdlib-only) so test
123
+ // helpers can seed it without dragging the pino logger + gateway IPC client
124
+ // transitively through their import chain. See that file's block comment.
131
125
 
132
126
  /**
133
127
  * Fetch override values from the gateway via IPC (Unix domain socket).
@@ -181,8 +175,9 @@ const DEFAULT_INIT_RETRY_BACKOFFS_MS: readonly number[] = [
181
175
  *
182
176
  * No-ops when the cache is already populated — callers that want to
183
177
  * refresh must call `clearFeatureFlagOverridesCache()` first. This lets
184
- * tests preseed flag state via `_setOverridesForTesting()` without the
185
- * gateway IPC call clobbering their setup.
178
+ * tests preseed flag state via `setOverridesForTesting()` (in
179
+ * `__tests__/feature-flag-test-helpers.ts`) without the gateway IPC call
180
+ * clobbering their setup.
186
181
  */
187
182
  export async function initFeatureFlagOverrides(options?: {
188
183
  retryBackoffsMs?: readonly number[];
@@ -194,7 +189,7 @@ export async function initFeatureFlagOverrides(options?: {
194
189
  */
195
190
  callTimeoutMs?: number;
196
191
  }): Promise<void> {
197
- if (cachedOverridesFromGateway) return;
192
+ if (isCachedFromGateway()) return;
198
193
 
199
194
  const backoffs = options?.retryBackoffsMs ?? DEFAULT_INIT_RETRY_BACKOFFS_MS;
200
195
  const callTimeoutMs = options?.callTimeoutMs;
@@ -208,15 +203,14 @@ export async function initFeatureFlagOverrides(options?: {
208
203
  const delay = backoffs[attempt - 1]!;
209
204
  await new Promise((resolve) => setTimeout(resolve, delay));
210
205
  // Re-check after the wait: a concurrent caller (e.g. a test using
211
- // `_setOverridesForTesting`) may have populated the cache while we
206
+ // `setOverridesForTesting`) may have populated the cache while we
212
207
  // were sleeping. Bail out so we don't clobber their setup.
213
- if (cachedOverridesFromGateway) return;
208
+ if (isCachedFromGateway()) return;
214
209
  }
215
210
 
216
211
  const gatewayOverrides = await fetchOverridesFromGateway(callTimeoutMs);
217
212
  if (Object.keys(gatewayOverrides).length > 0) {
218
- cachedOverrides = gatewayOverrides;
219
- cachedOverridesFromGateway = true;
213
+ setCachedOverrides(gatewayOverrides, { fromGateway: true });
220
214
  if (attempt > 0) {
221
215
  log.info(
222
216
  { attempt: attempt + 1 },
@@ -245,18 +239,20 @@ export async function initFeatureFlagOverrides(options?: {
245
239
  * called at startup, or an empty record otherwise.
246
240
  */
247
241
  function loadOverrides(): Record<string, boolean> {
248
- return cachedOverrides ?? {};
242
+ return getCachedOverrides() ?? {};
249
243
  }
250
244
 
251
245
  /**
252
246
  * Invalidate the cached overrides so the next call to
253
247
  * `isAssistantFeatureFlagEnabled` re-reads from the gateway.
254
248
  *
255
- * Used by tests between cases to reset module state.
249
+ * Called by `refreshOverridesFromGateway()` when the gateway pushes a
250
+ * `feature_flags_changed` event, and by tests between cases to reset
251
+ * module state. (Tests typically call `setOverridesForTesting()` from
252
+ * `__tests__/feature-flag-test-helpers.ts`, which combines clear + seed.)
256
253
  */
257
254
  export function clearFeatureFlagOverridesCache(): void {
258
- cachedOverrides = null;
259
- cachedOverridesFromGateway = false;
255
+ clearCachedOverrides();
260
256
  }
261
257
 
262
258
  /**
@@ -272,21 +268,6 @@ export async function refreshOverridesFromGateway(): Promise<void> {
272
268
  await initFeatureFlagOverrides({ retryBackoffsMs: [] });
273
269
  }
274
270
 
275
- /**
276
- * Directly inject override values into the module-level cache.
277
- *
278
- * **Test-only** — bypasses the gateway IPC fetch so unit tests can control
279
- * flag state without standing up a real gateway. Production code should
280
- * never call this; use `clearFeatureFlagOverridesCache()` instead and let
281
- * the resolver re-read from the gateway.
282
- */
283
- export function _setOverridesForTesting(
284
- overrides: Record<string, boolean>,
285
- ): void {
286
- cachedOverrides = { ...overrides };
287
- cachedOverridesFromGateway = true;
288
- }
289
-
290
271
  // ---------------------------------------------------------------------------
291
272
  // Public API
292
273
  // ---------------------------------------------------------------------------
@@ -39,10 +39,14 @@ When the user asks to see, open, or pull up a document:
39
39
 
40
40
  ## Creating a new document
41
41
 
42
- 1. **Create the document**: Call `document_create` with a title (inferred from the request). Call the tool immediately, not after conversational preamble.
42
+ 1. **Create the document**: Call `document_create` with a title (inferred from the request). Call the tool immediately, not after conversational preamble. Capture the `surface_id` from the response — every subsequent `document_update` call must reference it.
43
43
  2. **Write content in Markdown**: Use proper structure (`#` for titles, `##` for sections), **bold**, _italic_, code blocks, tables, lists, blockquotes as appropriate.
44
44
  3. **CRITICAL - Stream content in chunks**: Call `document_update` MULTIPLE times, not just once. Break content into logical chunks (paragraphs, sections, or every 200-300 words). Call `document_update` with `mode: "append"` for EACH chunk separately. The user experiences real-time content appearing as you write.
45
45
 
46
+ ### Recovering from a failed update
47
+
48
+ If a `document_update` call fails with an `Invalid input` error (for example because `surface_id` was missing), do NOT call `document_create` again. The `surface_id` you need is in the tool result of the most recent `document_create` call in this turn. Retry `document_update` with that `surface_id` and the same content. Creating a second document with the same title produces a duplicate for the user.
49
+
46
50
  ## Editing an existing document
47
51
 
48
52
  When the user requests changes to a document: