@vellumai/assistant 0.6.5 → 0.6.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 (443) hide show
  1. package/AGENTS.md +9 -1
  2. package/ARCHITECTURE.md +15 -17
  3. package/Dockerfile +6 -4
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +283 -0
  5. package/docs/architecture/integrations.md +32 -39
  6. package/docs/architecture/memory.md +25 -30
  7. package/docs/architecture/security.md +7 -6
  8. package/docs/browser-use-architecture-phase2.md +63 -20
  9. package/docs/plugins.md +761 -0
  10. package/examples/plugins/echo/README.md +132 -0
  11. package/examples/plugins/echo/package.json +17 -0
  12. package/examples/plugins/echo/register.ts +187 -0
  13. package/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
  14. package/openapi.yaml +212 -68
  15. package/package.json +1 -1
  16. package/src/__tests__/app-compiler.test.ts +57 -0
  17. package/src/__tests__/approval-cascade.test.ts +7 -2
  18. package/src/__tests__/auto-analysis-end-to-end.test.ts +1 -0
  19. package/src/__tests__/avatar-generator.test.ts +4 -2
  20. package/src/__tests__/bundled-asset.test.ts +6 -6
  21. package/src/__tests__/catalog-cache.test.ts +69 -0
  22. package/src/__tests__/checker.test.ts +459 -171
  23. package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
  24. package/src/__tests__/compaction-events.test.ts +501 -0
  25. package/src/__tests__/compaction-pipeline.test.ts +210 -0
  26. package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
  27. package/src/__tests__/compaction-timeout-recovery.test.ts +262 -0
  28. package/src/__tests__/config-model-image-provider.test.ts +110 -0
  29. package/src/__tests__/config-schema.test.ts +22 -9
  30. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
  31. package/src/__tests__/contacts-tools.test.ts +26 -0
  32. package/src/__tests__/context-overflow-policy.test.ts +7 -7
  33. package/src/__tests__/context-window-manager.test.ts +355 -4
  34. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  35. package/src/__tests__/conversation-agent-loop-overflow.test.ts +26 -30
  36. package/src/__tests__/conversation-agent-loop.test.ts +30 -141
  37. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -1
  38. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  39. package/src/__tests__/conversation-init.benchmark.test.ts +2 -16
  40. package/src/__tests__/conversation-pairing.test.ts +174 -10
  41. package/src/__tests__/conversation-pre-run-repair.test.ts +4 -1
  42. package/src/__tests__/conversation-process-callsite.test.ts +3 -0
  43. package/src/__tests__/conversation-provider-retry-repair.test.ts +16 -7
  44. package/src/__tests__/conversation-queue.test.ts +29 -14
  45. package/src/__tests__/conversation-routes-disk-view.test.ts +7 -6
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +155 -110
  47. package/src/__tests__/conversation-runtime-workspace.test.ts +23 -38
  48. package/src/__tests__/conversation-seed-composer.test.ts +2 -2
  49. package/src/__tests__/conversation-slash-queue.test.ts +7 -2
  50. package/src/__tests__/conversation-slash-unknown.test.ts +25 -2
  51. package/src/__tests__/conversation-speed-override.test.ts +6 -1
  52. package/src/__tests__/conversation-title-service.test.ts +116 -0
  53. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +41 -2
  54. package/src/__tests__/conversation-usage.test.ts +1 -1
  55. package/src/__tests__/conversation-workspace-cache-state.test.ts +4 -1
  56. package/src/__tests__/conversation-workspace-injection.test.ts +3 -0
  57. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -1
  58. package/src/__tests__/credential-health-service.test.ts +78 -9
  59. package/src/__tests__/credential-security-invariants.test.ts +2 -2
  60. package/src/__tests__/db-schedule-syntax-migration.test.ts +1 -0
  61. package/src/__tests__/empty-response-pipeline.test.ts +305 -0
  62. package/src/__tests__/extension-id-sync-guard.test.ts +3 -3
  63. package/src/__tests__/first-greeting.test.ts +247 -5
  64. package/src/__tests__/headless-browser-mode.test.ts +57 -0
  65. package/src/__tests__/history-repair-pipeline.test.ts +399 -0
  66. package/src/__tests__/host-browser-e2e-cloud.test.ts +307 -0
  67. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +3 -3
  68. package/src/__tests__/host-proxy-interface.test.ts +36 -2
  69. package/src/__tests__/image-credentials.test.ts +137 -0
  70. package/src/__tests__/image-service-dispatcher.test.ts +186 -0
  71. package/src/__tests__/injector-chain.test.ts +526 -0
  72. package/src/__tests__/intent-routing.test.ts +0 -26
  73. package/src/__tests__/llm-call-pipeline.test.ts +285 -0
  74. package/src/__tests__/llm-schema.test.ts +1 -1
  75. package/src/__tests__/media-generate-image.test.ts +119 -13
  76. package/src/__tests__/memory-retrieval-pipeline.test.ts +401 -0
  77. package/src/__tests__/memory-upsert-concurrency.test.ts +1 -0
  78. package/src/__tests__/migration-import-from-url.test.ts +5 -68
  79. package/src/__tests__/model-intents.test.ts +4 -2
  80. package/src/__tests__/notification-broadcaster.test.ts +3 -3
  81. package/src/__tests__/notification-decision-strategy.test.ts +0 -11
  82. package/src/__tests__/notification-schedule-notify-dedup.test.ts +108 -0
  83. package/src/__tests__/oauth-apps-routes.test.ts +1 -1
  84. package/src/__tests__/oauth-cli.test.ts +14 -12
  85. package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
  86. package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
  87. package/src/__tests__/oauth-provider-visibility.test.ts +3 -5
  88. package/src/__tests__/oauth-providers-routes.test.ts +3 -2
  89. package/src/__tests__/oauth-store.test.ts +41 -76
  90. package/src/__tests__/onboarding-template-contract.test.ts +16 -64
  91. package/src/__tests__/openai-image-service.test.ts +368 -0
  92. package/src/__tests__/overflow-reduce-pipeline.test.ts +676 -0
  93. package/src/__tests__/permission-checker-host-gate.test.ts +0 -24
  94. package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
  95. package/src/__tests__/persistence-pipeline.test.ts +377 -0
  96. package/src/__tests__/pipeline-runner.test.ts +565 -0
  97. package/src/__tests__/platform.test.ts +5 -2
  98. package/src/__tests__/plugin-bootstrap.test.ts +483 -0
  99. package/src/__tests__/plugin-registry.test.ts +273 -0
  100. package/src/__tests__/plugin-route-contribution.test.ts +288 -0
  101. package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
  102. package/src/__tests__/plugin-tool-contribution.test.ts +286 -0
  103. package/src/__tests__/plugin-types.test.ts +320 -0
  104. package/src/__tests__/pricing.test.ts +44 -12
  105. package/src/__tests__/proxy-approval-callback.test.ts +69 -8
  106. package/src/__tests__/reaction-persistence.test.ts +1 -0
  107. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  108. package/src/__tests__/registry.test.ts +0 -2
  109. package/src/__tests__/schedule-routes.test.ts +131 -1
  110. package/src/__tests__/scheduler-recurrence.test.ts +14 -70
  111. package/src/__tests__/scheduler-reuse-conversation.test.ts +10 -50
  112. package/src/__tests__/secret-detection-handler.test.ts +0 -10
  113. package/src/__tests__/shell-identity.test.ts +0 -134
  114. package/src/__tests__/suggestion-routes.test.ts +103 -4
  115. package/src/__tests__/task-memory-cleanup.test.ts +1 -0
  116. package/src/__tests__/task-scheduler.test.ts +3 -15
  117. package/src/__tests__/test-preload.ts +11 -0
  118. package/src/__tests__/title-generate-pipeline.test.ts +224 -0
  119. package/src/__tests__/token-estimate-pipeline.test.ts +431 -0
  120. package/src/__tests__/tool-error-pipeline.test.ts +244 -0
  121. package/src/__tests__/tool-execute-pipeline.test.ts +431 -0
  122. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -6
  123. package/src/__tests__/tool-executor-shell-integration.test.ts +7 -10
  124. package/src/__tests__/tool-executor.test.ts +141 -0
  125. package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
  126. package/src/__tests__/tool-result-truncation.test.ts +0 -110
  127. package/src/__tests__/user-plugin-loader.test.ts +191 -0
  128. package/src/__tests__/workspace-migration-046-seed-conversation-starters-callsite.test.ts +185 -0
  129. package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
  130. package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
  131. package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
  132. package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
  133. package/src/__tests__/workspace-policy.test.ts +21 -3
  134. package/src/agent/loop.ts +340 -102
  135. package/src/approvals/__tests__/guardian-feed-event.test.ts +304 -0
  136. package/src/approvals/guardian-request-resolvers.ts +80 -0
  137. package/src/backup/__tests__/backup-worker.test.ts +2 -13
  138. package/src/backup/backup-worker.ts +3 -15
  139. package/src/bundler/app-compiler.ts +84 -1
  140. package/src/calls/call-state.ts +2 -2
  141. package/src/channels/__tests__/types.test.ts +3 -3
  142. package/src/channels/types.ts +6 -4
  143. package/src/cli/__tests__/notifications.test.ts +87 -211
  144. package/src/cli/commands/__tests__/backup.test.ts +1 -1
  145. package/src/cli/commands/__tests__/image-generation.test.ts +255 -35
  146. package/src/cli/commands/__tests__/inference-send.test.ts +12 -0
  147. package/src/cli/commands/__tests__/tts-synthesize.test.ts +12 -0
  148. package/src/cli/commands/backup.ts +2 -2
  149. package/src/cli/commands/clients.ts +138 -0
  150. package/src/cli/commands/completions.ts +2 -9
  151. package/src/cli/commands/conversations.ts +55 -7
  152. package/src/cli/commands/image-generation.ts +33 -34
  153. package/src/cli/commands/notifications.ts +68 -103
  154. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
  155. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
  156. package/src/cli/commands/oauth/connect.ts +2 -2
  157. package/src/cli/commands/oauth/providers.ts +176 -8
  158. package/src/cli/commands/oauth/status.ts +46 -36
  159. package/src/cli/commands/skills.ts +3 -4
  160. package/src/cli/program.ts +25 -29
  161. package/src/config/__tests__/backup-schema.test.ts +7 -2
  162. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  163. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
  164. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
  165. package/src/config/bundled-skills/contacts/tools/contact-search.ts +28 -51
  166. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +22 -40
  167. package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
  168. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
  169. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
  170. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  171. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
  172. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +12 -0
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +58 -0
  174. package/src/config/bundled-skills/schedule/SKILL.md +8 -3
  175. package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
  176. package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
  177. package/src/config/bundled-tool-registry.ts +0 -15
  178. package/src/config/feature-flag-registry.json +17 -1
  179. package/src/config/schema.ts +19 -0
  180. package/src/config/schemas/backup.ts +1 -1
  181. package/src/config/schemas/conversations.ts +16 -0
  182. package/src/config/schemas/llm.ts +2 -3
  183. package/src/config/schemas/security.ts +6 -6
  184. package/src/config/schemas/tts.ts +11 -0
  185. package/src/config/skill-state.ts +6 -2
  186. package/src/config/skills.ts +94 -5
  187. package/src/context/__tests__/compact-prompt.test.ts +27 -9
  188. package/src/context/prompts/compact.md +26 -12
  189. package/src/context/tool-result-truncation.ts +3 -63
  190. package/src/context/window-manager.ts +190 -16
  191. package/src/credential-health/credential-health-service.ts +19 -6
  192. package/src/daemon/__tests__/conversation-feed-event.test.ts +317 -0
  193. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
  194. package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
  195. package/src/daemon/config-watcher.ts +0 -2
  196. package/src/daemon/context-overflow-policy.ts +4 -13
  197. package/src/daemon/conversation-agent-loop-handlers.ts +83 -22
  198. package/src/daemon/conversation-agent-loop.ts +984 -683
  199. package/src/daemon/conversation-history.ts +10 -19
  200. package/src/daemon/conversation-lifecycle.ts +37 -19
  201. package/src/daemon/conversation-notifiers.ts +2 -110
  202. package/src/daemon/conversation-process.ts +14 -7
  203. package/src/daemon/conversation-runtime-assembly.ts +532 -411
  204. package/src/daemon/conversation-tool-setup.ts +41 -4
  205. package/src/daemon/conversation.ts +80 -35
  206. package/src/daemon/external-plugins-bootstrap.ts +478 -0
  207. package/src/daemon/first-greeting.ts +191 -14
  208. package/src/daemon/handlers/config-model.ts +11 -0
  209. package/src/daemon/handlers/skills.ts +5 -1
  210. package/src/daemon/lifecycle.ts +33 -68
  211. package/src/daemon/message-types/computer-use.ts +2 -34
  212. package/src/daemon/message-types/conversations.ts +49 -0
  213. package/src/daemon/message-types/messages.ts +12 -0
  214. package/src/daemon/server.ts +5 -3
  215. package/src/daemon/shutdown-handlers.ts +2 -12
  216. package/src/daemon/tool-side-effects.ts +14 -56
  217. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +160 -0
  218. package/src/heartbeat/heartbeat-service.ts +24 -1
  219. package/src/home/__tests__/feed-population-integration.test.ts +312 -0
  220. package/src/home/emit-feed-event.ts +7 -0
  221. package/src/home/feed-types.ts +41 -2
  222. package/src/home/rewrite-command-preview.ts +66 -0
  223. package/src/ipc/__tests__/socket-path.test.ts +11 -50
  224. package/src/ipc/cli-client.ts +1 -1
  225. package/src/ipc/cli-server.ts +3 -3
  226. package/src/ipc/gateway-client.ts +4 -1
  227. package/src/ipc/routes/browser-context.ts +2 -0
  228. package/src/ipc/routes/browser.ts +1 -0
  229. package/src/ipc/routes/get-contact.ts +16 -0
  230. package/src/ipc/routes/index.ts +14 -0
  231. package/src/ipc/routes/list-clients.ts +31 -0
  232. package/src/ipc/routes/merge-contacts.ts +17 -0
  233. package/src/ipc/routes/notification.ts +133 -0
  234. package/src/ipc/routes/rename-conversation.ts +59 -0
  235. package/src/ipc/routes/search-contacts.ts +19 -0
  236. package/src/ipc/routes/upsert-contact.ts +25 -0
  237. package/src/ipc/socket-path.ts +14 -38
  238. package/src/media/app-icon-generator.ts +23 -46
  239. package/src/media/avatar-router.ts +26 -41
  240. package/src/media/gemini-image-service.ts +8 -41
  241. package/src/media/image-credentials.ts +73 -0
  242. package/src/media/image-service.ts +85 -0
  243. package/src/media/openai-image-service.ts +131 -0
  244. package/src/media/types.ts +46 -0
  245. package/src/memory/conversation-crud.ts +48 -18
  246. package/src/memory/conversation-queries.ts +57 -4
  247. package/src/memory/conversation-title-service.ts +25 -0
  248. package/src/memory/db-init.ts +8 -0
  249. package/src/memory/embedding-gemini.test.ts +41 -2
  250. package/src/memory/embedding-gemini.ts +6 -1
  251. package/src/memory/graph/bootstrap.test.ts +282 -0
  252. package/src/memory/graph/bootstrap.ts +8 -5
  253. package/src/memory/graph/extraction.ts +10 -2
  254. package/src/memory/graph/graph-search.test.ts +1 -0
  255. package/src/memory/graph/inspect.ts +2 -2
  256. package/src/memory/graph/retriever.ts +10 -3
  257. package/src/memory/migrations/041-approval-prompt-ts-tracker.ts +26 -0
  258. package/src/memory/migrations/149-oauth-tables.ts +1 -0
  259. package/src/memory/migrations/223-schedule-script-column.ts +11 -0
  260. package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
  261. package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
  262. package/src/memory/migrations/index.ts +4 -0
  263. package/src/memory/pkb/pkb-index.test.ts +1 -0
  264. package/src/memory/pkb/pkb-reconcile.test.ts +1 -0
  265. package/src/memory/pkb/pkb-search.test.ts +65 -4
  266. package/src/memory/pkb/pkb-search.ts +40 -18
  267. package/src/memory/qdrant-client.test.ts +60 -0
  268. package/src/memory/qdrant-client.ts +25 -0
  269. package/src/memory/schema/infrastructure.ts +1 -0
  270. package/src/memory/schema/oauth.ts +4 -1
  271. package/src/messaging/providers/slack/render-transcript.test.ts +77 -29
  272. package/src/messaging/providers/slack/render-transcript.ts +58 -0
  273. package/src/notifications/conversation-pairing.ts +78 -19
  274. package/src/notifications/copy-composer.ts +0 -5
  275. package/src/notifications/emit-signal.ts +1 -1
  276. package/src/notifications/signal.ts +1 -2
  277. package/src/oauth/AGENTS.md +1 -1
  278. package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
  279. package/src/oauth/connect-orchestrator.ts +8 -34
  280. package/src/oauth/connect-types.ts +6 -10
  281. package/src/oauth/manual-token-connection.ts +23 -0
  282. package/src/oauth/oauth-store.ts +30 -14
  283. package/src/oauth/provider-serializer.ts +6 -1
  284. package/src/oauth/seed-providers.ts +56 -108
  285. package/src/outbound-proxy/http-forwarder.ts +9 -0
  286. package/src/permissions/approval-policy.test.ts +293 -18
  287. package/src/permissions/approval-policy.ts +110 -58
  288. package/src/permissions/arg-parser.test.ts +161 -0
  289. package/src/permissions/arg-parser.ts +141 -0
  290. package/src/permissions/bash-risk-classifier.test.ts +414 -2
  291. package/src/permissions/bash-risk-classifier.ts +303 -60
  292. package/src/permissions/checker.ts +157 -29
  293. package/src/permissions/command-registry.test.ts +239 -0
  294. package/src/permissions/command-registry.ts +234 -54
  295. package/src/permissions/defaults.ts +5 -4
  296. package/src/permissions/gateway-threshold-reader.ts +196 -0
  297. package/src/permissions/prompter.ts +4 -0
  298. package/src/permissions/risk-types.ts +61 -4
  299. package/src/permissions/schedule-risk-classifier.test.ts +129 -0
  300. package/src/permissions/schedule-risk-classifier.ts +85 -0
  301. package/src/permissions/shell-identity.ts +2 -42
  302. package/src/permissions/types.ts +2 -0
  303. package/src/permissions/workspace-policy.ts +8 -3
  304. package/src/plugins/defaults/circuit-breaker.ts +146 -0
  305. package/src/plugins/defaults/compaction.ts +145 -0
  306. package/src/plugins/defaults/empty-response.ts +126 -0
  307. package/src/plugins/defaults/history-repair.ts +85 -0
  308. package/src/plugins/defaults/index.ts +116 -0
  309. package/src/plugins/defaults/injectors.ts +491 -0
  310. package/src/plugins/defaults/llm-call.ts +82 -0
  311. package/src/plugins/defaults/memory-retrieval.ts +226 -0
  312. package/src/plugins/defaults/overflow-reduce.ts +181 -0
  313. package/src/plugins/defaults/persistence.ts +129 -0
  314. package/src/plugins/defaults/title-generate.ts +95 -0
  315. package/src/plugins/defaults/token-estimate.ts +104 -0
  316. package/src/plugins/defaults/tool-error.ts +126 -0
  317. package/src/plugins/defaults/tool-execute.ts +89 -0
  318. package/src/plugins/defaults/tool-result-truncate.ts +88 -0
  319. package/src/plugins/pipeline.ts +316 -0
  320. package/src/plugins/plugin-skill-contributions.ts +292 -0
  321. package/src/plugins/registry.ts +241 -0
  322. package/src/plugins/types.ts +1134 -0
  323. package/src/plugins/user-loader.ts +177 -0
  324. package/src/prompts/templates/BOOTSTRAP.md +27 -77
  325. package/src/providers/model-catalog.ts +52 -29
  326. package/src/providers/model-intents.ts +1 -1
  327. package/src/providers/openrouter/client.ts +5 -1
  328. package/src/providers/speech-to-text/deepgram-realtime.test.ts +61 -0
  329. package/src/providers/speech-to-text/deepgram-realtime.ts +57 -0
  330. package/src/providers/speech-to-text/xai-realtime.test.ts +72 -4
  331. package/src/providers/speech-to-text/xai-realtime.ts +39 -14
  332. package/src/runtime/AGENTS.md +25 -16
  333. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +3 -3
  334. package/src/runtime/__tests__/client-registry.test.ts +293 -0
  335. package/src/runtime/client-registry.ts +261 -0
  336. package/src/runtime/http-server.ts +77 -8
  337. package/src/runtime/http-types.ts +0 -2
  338. package/src/runtime/migrations/vbundle-builder.ts +1 -22
  339. package/src/runtime/routes/approval-prompt-ts-tracker.ts +51 -31
  340. package/src/runtime/routes/approval-routes.ts +17 -0
  341. package/src/runtime/routes/browser-extension-pair-routes.ts +27 -8
  342. package/src/runtime/routes/conversation-routes.ts +223 -116
  343. package/src/runtime/routes/inbound-message-handler.ts +88 -13
  344. package/src/runtime/routes/memory-item-routes.test.ts +1 -0
  345. package/src/runtime/routes/migration-routes.ts +0 -3
  346. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +284 -0
  347. package/src/runtime/routes/playground/__tests__/guard.test.ts +80 -0
  348. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +294 -0
  349. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +271 -0
  350. package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +202 -0
  351. package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +309 -0
  352. package/src/runtime/routes/playground/__tests__/state.test.ts +224 -0
  353. package/src/runtime/routes/playground/conversation-not-found.ts +29 -0
  354. package/src/runtime/routes/playground/deps.ts +56 -0
  355. package/src/runtime/routes/playground/force-compact.ts +73 -0
  356. package/src/runtime/routes/playground/guard.ts +37 -0
  357. package/src/runtime/routes/playground/index.ts +28 -0
  358. package/src/runtime/routes/playground/inject-failures.ts +159 -0
  359. package/src/runtime/routes/playground/reset-circuit.ts +115 -0
  360. package/src/runtime/routes/playground/seed-conversation.ts +139 -0
  361. package/src/runtime/routes/playground/seeded-conversations.ts +78 -0
  362. package/src/runtime/routes/playground/state.ts +78 -0
  363. package/src/runtime/routes/schedule-routes.ts +89 -8
  364. package/src/runtime/skill-route-registry.ts +75 -15
  365. package/src/schedule/run-script.ts +68 -0
  366. package/src/schedule/schedule-store.ts +7 -1
  367. package/src/schedule/scheduler.ts +48 -8
  368. package/src/skills/catalog-cache.ts +12 -5
  369. package/src/tools/browser/__tests__/browser-status.test.ts +189 -0
  370. package/src/tools/browser/browser-execution.ts +88 -19
  371. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
  372. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
  373. package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
  374. package/src/tools/browser/cdp-client/factory.ts +15 -4
  375. package/src/tools/executor.ts +126 -74
  376. package/src/tools/network/script-proxy/session-manager.ts +37 -1
  377. package/src/tools/permission-checker.ts +98 -49
  378. package/src/tools/policy-context.ts +4 -0
  379. package/src/tools/registry.ts +140 -3
  380. package/src/tools/schedule/create.ts +23 -8
  381. package/src/tools/schedule/update.ts +3 -1
  382. package/src/tools/secret-detection-handler.ts +0 -51
  383. package/src/tools/system/avatar-generator.ts +6 -2
  384. package/src/tools/types.ts +28 -2
  385. package/src/util/platform.ts +7 -2
  386. package/src/util/pricing.ts +26 -3
  387. package/src/workspace/migrations/006-services-config.ts +2 -4
  388. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
  389. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +3 -4
  390. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +108 -0
  391. package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
  392. package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
  393. package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
  394. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
  395. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
  396. package/src/workspace/migrations/registry.ts +12 -0
  397. package/tsconfig.json +1 -1
  398. package/hook-templates/debug-prompt-logger/hook.json +0 -7
  399. package/hook-templates/debug-prompt-logger/run.sh +0 -66
  400. package/src/__tests__/compaction-circuit-breaker.test.ts +0 -336
  401. package/src/__tests__/context-overflow-approval.test.ts +0 -156
  402. package/src/__tests__/hooks-blocking.test.ts +0 -178
  403. package/src/__tests__/hooks-cli.test.ts +0 -182
  404. package/src/__tests__/hooks-config.test.ts +0 -108
  405. package/src/__tests__/hooks-discovery.test.ts +0 -211
  406. package/src/__tests__/hooks-integration.test.ts +0 -196
  407. package/src/__tests__/hooks-manager.test.ts +0 -226
  408. package/src/__tests__/hooks-runner.test.ts +0 -175
  409. package/src/__tests__/hooks-settings.test.ts +0 -160
  410. package/src/__tests__/hooks-templates.test.ts +0 -169
  411. package/src/__tests__/hooks-ts-runner.test.ts +0 -170
  412. package/src/__tests__/hooks-watch.test.ts +0 -112
  413. package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
  414. package/src/__tests__/oauth-scope-policy.test.ts +0 -180
  415. package/src/__tests__/send-notification-tool.test.ts +0 -83
  416. package/src/cli/commands/shotgun.ts +0 -266
  417. package/src/config/bundled-skills/conversations/SKILL.md +0 -20
  418. package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
  419. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -88
  420. package/src/config/bundled-skills/heartbeat/SKILL.md +0 -43
  421. package/src/config/bundled-skills/notifications/SKILL.md +0 -40
  422. package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
  423. package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
  424. package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
  425. package/src/config/bundled-skills/screen-watch/SKILL.md +0 -27
  426. package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
  427. package/src/config/bundled-skills/screen-watch/tools/start-screen-watch.ts +0 -12
  428. package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
  429. package/src/daemon/context-overflow-approval.ts +0 -52
  430. package/src/daemon/watch-handler.ts +0 -399
  431. package/src/hooks/cli.ts +0 -253
  432. package/src/hooks/config.ts +0 -100
  433. package/src/hooks/discovery.ts +0 -135
  434. package/src/hooks/manager.ts +0 -179
  435. package/src/hooks/runner.ts +0 -117
  436. package/src/hooks/templates.ts +0 -77
  437. package/src/hooks/types.ts +0 -75
  438. package/src/oauth/scope-policy.ts +0 -89
  439. package/src/runtime/gateway-internal-client.ts +0 -94
  440. package/src/runtime/routes/watch-routes.ts +0 -156
  441. package/src/signals/shotgun.ts +0 -203
  442. package/src/tools/watch/screen-watch.ts +0 -144
  443. package/src/tools/watch/watch-state.ts +0 -142
@@ -0,0 +1,138 @@
1
+ import type { Command } from "commander";
2
+
3
+ import { cliIpcCall } from "../../ipc/cli-client.js";
4
+ import type { ClientEntryJSON } from "../../runtime/client-registry.js";
5
+ import { log } from "../logger.js";
6
+ import { writeOutput } from "../output.js";
7
+
8
+ interface ListClientsResponse {
9
+ clients: ClientEntryJSON[];
10
+ }
11
+
12
+ export function registerClientsCommand(program: Command): void {
13
+ const clients = program
14
+ .command("clients")
15
+ .description("Discover connected clients and their capabilities");
16
+
17
+ clients.addHelpText(
18
+ "after",
19
+ `
20
+ Clients are the applications currently connected to the assistant —
21
+ macOS desktop, iOS, web, Chrome extension, or CLI. Each client has a
22
+ set of capabilities (e.g. host_bash, host_file) that determine which
23
+ tools the assistant can route through it.
24
+
25
+ Examples:
26
+ $ assistant clients list List all connected clients
27
+ $ assistant clients list --json Machine-readable JSON output
28
+ $ assistant clients list --capability host_bash Show only clients that can run host commands`,
29
+ );
30
+
31
+ clients
32
+ .command("list")
33
+ .description("List all currently connected clients")
34
+ .option("--json", "Machine-readable compact JSON output")
35
+ .option(
36
+ "--capability <name>",
37
+ "Filter to clients supporting this capability (e.g. host_bash, host_file, host_cu, host_browser)",
38
+ )
39
+ .addHelpText(
40
+ "after",
41
+ `
42
+ Options:
43
+ --json Output as compact JSON instead of a table.
44
+ --capability <name> Only show clients that support the named capability.
45
+ Valid values: host_bash, host_file, host_cu, host_browser.
46
+
47
+ The table shows each client's ID, interface type, capabilities,
48
+ connection timestamps, and host environment (when available).
49
+ Clients are sorted by most recently active first.
50
+
51
+ Examples:
52
+ $ assistant clients list
53
+ $ assistant clients list --capability host_bash
54
+ $ assistant clients list --json | jq '.clients[0].capabilities'`,
55
+ )
56
+ .action(
57
+ async (opts: { json?: boolean; capability?: string }, cmd: Command) => {
58
+ const params: Record<string, unknown> = {};
59
+ if (opts.capability) {
60
+ params.capability = opts.capability;
61
+ }
62
+
63
+ const result = await cliIpcCall<ListClientsResponse>(
64
+ "list_clients",
65
+ Object.keys(params).length > 0 ? params : undefined,
66
+ );
67
+
68
+ if (!result.ok) {
69
+ log.error(result.error ?? "Failed to list clients");
70
+ process.exitCode = 1;
71
+ return;
72
+ }
73
+
74
+ const response = result.result!;
75
+ const { clients: entries } = response;
76
+
77
+ if (opts.json) {
78
+ writeOutput(cmd, response);
79
+ return;
80
+ }
81
+
82
+ if (entries.length === 0) {
83
+ log.info("No clients connected.");
84
+ return;
85
+ }
86
+
87
+ // Table output
88
+ const header = [
89
+ "CLIENT ID",
90
+ "INTERFACE",
91
+ "CAPABILITIES",
92
+ "CONNECTED",
93
+ "LAST ACTIVE",
94
+ "HOST",
95
+ ];
96
+ const rows: string[][] = entries.map((e: ClientEntryJSON) => [
97
+ e.clientId.length > 20 ? `${e.clientId.slice(0, 17)}...` : e.clientId,
98
+ e.interfaceId,
99
+ e.capabilities.length > 0 ? e.capabilities.join(", ") : "—",
100
+ formatRelativeTime(e.connectedAt),
101
+ formatRelativeTime(e.lastActiveAt),
102
+ e.hostUsername
103
+ ? `${e.hostUsername}${e.hostHomeDir ? ` (${e.hostHomeDir})` : ""}`
104
+ : "—",
105
+ ]);
106
+
107
+ // Calculate column widths
108
+ const colWidths = header.map((h: string, i: number) =>
109
+ Math.max(h.length, ...rows.map((r: string[]) => r[i].length)),
110
+ );
111
+
112
+ const pad = (s: string, w: number) => s.padEnd(w);
113
+ const line = header
114
+ .map((h: string, i: number) => pad(h, colWidths[i]))
115
+ .join(" ");
116
+ log.info(line);
117
+ log.info(colWidths.map((w: number) => "─".repeat(w)).join(" "));
118
+ for (const row of rows) {
119
+ log.info(
120
+ row.map((c: string, i: number) => pad(c, colWidths[i])).join(" "),
121
+ );
122
+ }
123
+ },
124
+ );
125
+ }
126
+
127
+ function formatRelativeTime(iso: string): string {
128
+ const ms = Date.now() - new Date(iso).getTime();
129
+ if (ms < 0) return "just now";
130
+ const sec = Math.floor(ms / 1000);
131
+ if (sec < 60) return `${sec}s ago`;
132
+ const min = Math.floor(sec / 60);
133
+ if (min < 60) return `${min}m ago`;
134
+ const hr = Math.floor(min / 60);
135
+ if (hr < 24) return `${hr}h ago`;
136
+ const days = Math.floor(hr / 24);
137
+ return `${days}d ago`;
138
+ }
@@ -40,7 +40,6 @@ Examples:
40
40
  keys: ["list", "set", "delete"],
41
41
  trust: ["list", "remove", "clear"],
42
42
  memory: ["status", "backfill", "cleanup", "query", "rebuild-index"],
43
- hooks: ["list", "enable", "disable", "install", "remove"],
44
43
  contacts: ["list", "invites", "get", "merge"],
45
44
  autonomy: ["get", "set"],
46
45
  };
@@ -50,7 +49,6 @@ Examples:
50
49
  "keys",
51
50
  "trust",
52
51
  "memory",
53
- "hooks",
54
52
  "contacts",
55
53
  "autonomy",
56
54
  "audit",
@@ -63,7 +61,7 @@ Examples:
63
61
  process.stdout.write(generateBashCompletion(topLevel, subcommands));
64
62
  break;
65
63
  case "zsh":
66
- process.stdout.write(generateZshCompletion(topLevel, subcommands));
64
+ process.stdout.write(generateZshCompletion(subcommands));
67
65
  break;
68
66
  case "fish":
69
67
  process.stdout.write(generateFishCompletion(topLevel, subcommands));
@@ -113,10 +111,7 @@ complete -F _assistant_completions assistant
113
111
  `;
114
112
  }
115
113
 
116
- function generateZshCompletion(
117
- topLevel: string[],
118
- subcommands: Record<string, string[]>,
119
- ): string {
114
+ function generateZshCompletion(subcommands: Record<string, string[]>): string {
120
115
  const subcmdCases = Object.entries(subcommands)
121
116
  .map(([cmd, subs]) => ` ${cmd}) compadd ${subs.join(" ")} ;;`)
122
117
  .join("\n");
@@ -132,7 +127,6 @@ _assistant() {
132
127
  'keys:Manage API keys in secure storage'
133
128
  'trust:Manage trust rules'
134
129
  'memory:Manage long-term memory'
135
- 'hooks:Manage hooks'
136
130
  'contacts:Manage the contact graph'
137
131
  'autonomy:View and configure autonomy tiers'
138
132
  'audit:Show recent tool invocations'
@@ -172,7 +166,6 @@ function generateFishCompletion(
172
166
  keys: "Manage API keys in secure storage",
173
167
  trust: "Manage trust rules",
174
168
  memory: "Manage long-term memory",
175
- hooks: "Manage hooks",
176
169
  contacts: "Manage the contact graph",
177
170
  autonomy: "View and configure autonomy tiers",
178
171
  audit: "Show recent tool invocations",
@@ -1,10 +1,6 @@
1
1
  import type { Command } from "commander";
2
2
 
3
- import {
4
- getQdrantUrlEnv,
5
- getRuntimeHttpHost,
6
- getRuntimeHttpPort,
7
- } from "../../config/env.js";
3
+ import { getRuntimeHttpHost, getRuntimeHttpPort } from "../../config/env.js";
8
4
  import { getConfig } from "../../config/loader.js";
9
5
  import { shouldAutoStartDaemon } from "../../daemon/connection-policy.js";
10
6
  import { healthCheckHost, isHttpHealthy } from "../../daemon/daemon-control.js";
@@ -25,7 +21,10 @@ import {
25
21
  SPARSE_EMBEDDING_VERSION,
26
22
  } from "../../memory/embedding-backend.js";
27
23
  import { enqueueMemoryJob } from "../../memory/jobs-store.js";
28
- import { initQdrantClient } from "../../memory/qdrant-client.js";
24
+ import {
25
+ initQdrantClient,
26
+ resolveQdrantUrl,
27
+ } from "../../memory/qdrant-client.js";
29
28
  import {
30
29
  initAuthSigningKey,
31
30
  loadOrCreateSigningKey,
@@ -113,6 +112,55 @@ Examples:
113
112
  );
114
113
  });
115
114
 
115
+ conversations
116
+ .command("rename <conversationId> <title>")
117
+ .description("Rename a conversation")
118
+ .addHelpText(
119
+ "after",
120
+ `
121
+ Arguments:
122
+ conversationId Conversation ID (or unique prefix). Supports prefix matching.
123
+ Run 'assistant conversations list' to find IDs.
124
+ title The new title for the conversation. Should be concise (under
125
+ 60 characters) and descriptive of the current topic.
126
+
127
+ Renames the conversation to the given title and marks it as a manual rename
128
+ (auto-generated titles will not overwrite it).
129
+
130
+ Examples:
131
+ $ assistant conversations rename abc123 "Project planning"
132
+ $ assistant conversations rename abc123 "Bug triage 2026-04-22"`,
133
+ )
134
+ .action(async (conversationId: string, title: string) => {
135
+ const trimmedTitle = title.trim();
136
+ if (!trimmedTitle) {
137
+ log.error("Error: title must be a non-empty string");
138
+ process.exit(1);
139
+ }
140
+
141
+ const ipcResult = await cliIpcCall<{ ok: boolean; error?: string }>(
142
+ "rename_conversation",
143
+ { conversationId, title: trimmedTitle },
144
+ );
145
+
146
+ if (!ipcResult.ok) {
147
+ log.error(
148
+ `Rename failed: ${ipcResult.error}. Run 'assistant conversations list' to verify the conversation exists.`,
149
+ );
150
+ process.exit(1);
151
+ }
152
+
153
+ const result = ipcResult.result!;
154
+ if (!result.ok) {
155
+ log.error(
156
+ `Rename failed: ${result.error}. Run 'assistant conversations list' to see available conversations.`,
157
+ );
158
+ process.exit(1);
159
+ }
160
+
161
+ log.info(`Renamed conversation to "${trimmedTitle}" (${conversationId})`);
162
+ });
163
+
116
164
  conversations
117
165
  .command("export [conversationId]")
118
166
  .description("Export a conversation as markdown or JSON")
@@ -247,7 +295,7 @@ Examples:
247
295
  );
248
296
 
249
297
  const config = getConfig();
250
- const qdrantUrl = getQdrantUrlEnv() || config.memory.qdrant.url;
298
+ const qdrantUrl = resolveQdrantUrl(config);
251
299
  const embeddingSelection = await selectEmbeddingBackend(config);
252
300
  const embeddingModel = embeddingSelection.backend
253
301
  ? `${embeddingSelection.backend.provider}:${embeddingSelection.backend.model}:sparse-v${SPARSE_EMBEDDING_VERSION}`
@@ -5,16 +5,13 @@ import { join } from "node:path";
5
5
  import { Command } from "commander";
6
6
 
7
7
  import { getConfig } from "../../config/loader.js";
8
+ import { resolveImageGenCredentials } from "../../media/image-credentials.js";
8
9
  import {
9
10
  generateImage,
10
11
  type ImageGenCredentials,
11
- mapGeminiError,
12
- } from "../../media/gemini-image-service.js";
13
- import {
14
- buildManagedBaseUrl,
15
- resolveManagedProxyContext,
16
- } from "../../providers/managed-proxy/context.js";
17
- import { getProviderKeyAsync } from "../../security/secure-keys.js";
12
+ mapImageGenError,
13
+ providerForModel,
14
+ } from "../../media/image-service.js";
18
15
  import { log } from "../logger.js";
19
16
 
20
17
  // ---------------------------------------------------------------------------
@@ -72,11 +69,12 @@ export function registerImageGenerationCommand(program: Command): void {
72
69
  `
73
70
  Modes:
74
71
  managed — Uses platform-managed credentials (requires login to Vellum).
75
- your-own — Uses your own Gemini API key configured in settings.
72
+ your-own — Uses your own Gemini or OpenAI API key depending on the configured model.
76
73
 
77
74
  Supported models:
78
75
  gemini-3.1-flash-image-preview (default)
79
76
  gemini-3-pro-image-preview
77
+ gpt-image-2
80
78
 
81
79
  Examples:
82
80
  $ assistant image-generation generate --prompt "A sunset over the ocean"
@@ -114,12 +112,14 @@ Notes:
114
112
  Edit mode (--mode edit) requires at least one --source image file.
115
113
  Output files are named image-1.png, image-2.png, etc. (extension matches MIME type).
116
114
  Default output directory is the system temp directory.
115
+ Uses your own Gemini or OpenAI API key depending on the configured model.
117
116
 
118
117
  Examples:
119
118
  $ assistant image-generation generate --prompt "A mountain landscape at dawn"
120
119
  $ assistant image-generation generate --prompt "Make it darker" --mode edit --source input.png
121
120
  $ assistant image-generation generate --prompt "Logo variations" --variants 4 --output-dir ./logos
122
- $ assistant image-generation generate --prompt "A robot" --model gemini-3-pro-image-preview --json`,
121
+ $ assistant image-generation generate --prompt "A robot" --model gemini-3-pro-image-preview --json
122
+ $ assistant image-generation generate --prompt "A robot" --model gpt-image-2 --json`,
123
123
  );
124
124
 
125
125
  generate.action(async (opts) => {
@@ -149,33 +149,30 @@ Examples:
149
149
 
150
150
  // --- Resolve credentials ---
151
151
  const config = getConfig();
152
- const imageGenMode = config.services["image-generation"].mode;
152
+ const svc = config.services["image-generation"];
153
153
 
154
- let credentials: ImageGenCredentials | undefined;
154
+ // Derive provider from the explicit `--model` override when supplied so
155
+ // that `--model gpt-image-2` routes to OpenAI even when config.provider
156
+ // is `gemini` (and vice-versa). Without this, the Gemini service would
157
+ // silently downgrade unknown models to its default.
158
+ const provider = providerForModel(modelOverride, svc.provider);
155
159
 
156
- if (imageGenMode === "managed") {
157
- const managedBaseUrl = await buildManagedBaseUrl("gemini");
158
- if (managedBaseUrl) {
159
- const ctx = await resolveManagedProxyContext();
160
- credentials = {
161
- type: "managed-proxy",
162
- assistantApiKey: ctx.assistantApiKey,
163
- baseUrl: managedBaseUrl,
164
- };
165
- }
166
- } else {
167
- const apiKey = await getProviderKeyAsync("gemini");
168
- if (apiKey) {
169
- credentials = { type: "direct", apiKey };
170
- }
171
- }
160
+ const { credentials, errorHint } = await resolveImageGenCredentials({
161
+ provider,
162
+ mode: svc.mode,
163
+ });
172
164
 
173
165
  if (!credentials) {
166
+ const baseHint =
167
+ errorHint ?? "No credentials available for image generation.";
168
+ // The shared hint in image-credentials.ts is UI-neutral (it points at
169
+ // "Settings > Models & Services"), which is correct for tool surfaces
170
+ // but drops the actionable CLI command. Prepend the exact command for
171
+ // each mode so CLI users see a next-step they can copy-paste.
174
172
  const hint =
175
- imageGenMode === "managed"
176
- ? "Managed proxy is not available. Please log in to Vellum or switch to your-own mode:\n Run 'assistant auth login' to authenticate, or set services.image-generation.mode to 'your-own' in config."
177
- : "No Gemini API key configured. Add your Gemini API key:\n Run 'assistant keys set gemini' or configure it in Settings > Models & Services.";
178
-
173
+ svc.mode === "managed"
174
+ ? `${baseHint}\n Run 'assistant auth login' to authenticate, or set services.image-generation.mode to 'your-own' in config.`
175
+ : `Run: assistant keys set ${provider} <key>.\n${baseHint}`;
179
176
  if (jsonOutput) {
180
177
  process.stdout.write(JSON.stringify({ ok: false, error: hint }) + "\n");
181
178
  } else {
@@ -185,6 +182,8 @@ Examples:
185
182
  return;
186
183
  }
187
184
 
185
+ const resolvedCredentials: ImageGenCredentials = credentials;
186
+
188
187
  // --- Read source images for edit mode ---
189
188
  let sourceImages:
190
189
  | Array<{ mimeType: string; dataBase64: string }>
@@ -231,11 +230,11 @@ Examples:
231
230
  }
232
231
 
233
232
  // --- Resolve model ---
234
- const model = modelOverride ?? config.services["image-generation"].model;
233
+ const model = modelOverride ?? svc.model;
235
234
 
236
235
  // --- Generate image ---
237
236
  try {
238
- const result = await generateImage(credentials, {
237
+ const result = await generateImage(provider, resolvedCredentials, {
239
238
  prompt,
240
239
  mode,
241
240
  sourceImages,
@@ -286,7 +285,7 @@ Examples:
286
285
  }
287
286
  }
288
287
  } catch (error) {
289
- const errorMsg = mapGeminiError(error);
288
+ const errorMsg = mapImageGenError(provider, error);
290
289
  if (jsonOutput) {
291
290
  process.stdout.write(
292
291
  JSON.stringify({ ok: false, error: errorMsg }) + "\n",
@@ -1,41 +1,9 @@
1
1
  import type { Command } from "commander";
2
2
 
3
- import { getDeliverableChannels } from "../../channels/config.js";
4
- import { emitNotificationSignal } from "../../notifications/emit-signal.js";
5
- import { listEvents } from "../../notifications/events-store.js";
6
- import {
7
- isNotificationSourceChannel,
8
- isNotificationSourceEventName,
9
- NOTIFICATION_SOURCE_CHANNELS,
10
- NOTIFICATION_SOURCE_EVENT_NAMES,
11
- } from "../../notifications/signal.js";
12
- import type { NotificationChannel } from "../../notifications/types.js";
13
- import {
14
- initAuthSigningKey,
15
- resolveSigningKey,
16
- } from "../../runtime/auth/token-service.js";
17
- import { initializeDb } from "../db.js";
3
+ import { cliIpcCall } from "../../ipc/cli-client.js";
18
4
  import { log } from "../logger.js";
19
5
  import { shouldOutputJson, writeOutput } from "../output.js";
20
6
 
21
- // ---------------------------------------------------------------------------
22
- // Help text builders
23
- // ---------------------------------------------------------------------------
24
-
25
- function buildSourceChannelsHelpBlock(): string {
26
- const lines = NOTIFICATION_SOURCE_CHANNELS.map(
27
- (c) => ` ${c.id.padEnd(20)} ${c.description}`,
28
- );
29
- return `\nSource channels:\n${lines.join("\n")}`;
30
- }
31
-
32
- function buildSourceEventNamesHelpBlock(): string {
33
- const lines = NOTIFICATION_SOURCE_EVENT_NAMES.map(
34
- (e) => ` ${e.id.padEnd(50)} ${e.description}`,
35
- );
36
- return `\nSource event names:\n${lines.join("\n")}`;
37
- }
38
-
39
7
  // ---------------------------------------------------------------------------
40
8
  // Command registration
41
9
  // ---------------------------------------------------------------------------
@@ -55,8 +23,6 @@ Notifications flow through a unified pipeline: a signal is emitted with a
55
23
  source channel, event name, and attention hints. The decision engine evaluates
56
24
  whether and where to deliver the notification based on connected channels,
57
25
  urgency, and user preferences.
58
- ${buildSourceChannelsHelpBlock()}
59
- ${buildSourceEventNamesHelpBlock()}
60
26
 
61
27
  Examples:
62
28
  $ assistant notifications send --source-channel assistant_tool --source-event-name user.send_notification --message "Build finished"
@@ -129,6 +95,10 @@ Examples:
129
95
  "--dedupe-key <key>",
130
96
  "Optional dedupe key to suppress duplicate notifications",
131
97
  )
98
+ .option(
99
+ "--deep-link-metadata <json>",
100
+ "Optional JSON metadata clients can use for deep linking",
101
+ )
132
102
  .addHelpText(
133
103
  "after",
134
104
  `
@@ -165,37 +135,12 @@ Examples:
165
135
  preferredChannels?: string;
166
136
  sessionId?: string;
167
137
  dedupeKey?: string;
138
+ deepLinkMetadata?: string;
168
139
  },
169
140
  cmd: Command,
170
141
  ) => {
171
142
  try {
172
- // Validate --source-channel
173
- if (!isNotificationSourceChannel(opts.sourceChannel)) {
174
- const validChannels = NOTIFICATION_SOURCE_CHANNELS.map(
175
- (c) => c.id,
176
- ).join(", ");
177
- writeOutput(cmd, {
178
- ok: false,
179
- error: `Invalid source channel "${opts.sourceChannel}". Valid values: ${validChannels}`,
180
- });
181
- process.exitCode = 1;
182
- return;
183
- }
184
-
185
- // Validate --source-event-name
186
- if (!isNotificationSourceEventName(opts.sourceEventName)) {
187
- const validEvents = NOTIFICATION_SOURCE_EVENT_NAMES.map(
188
- (e) => e.id,
189
- ).join(", ");
190
- writeOutput(cmd, {
191
- ok: false,
192
- error: `Invalid source event name "${opts.sourceEventName}". Valid values: ${validEvents}`,
193
- });
194
- process.exitCode = 1;
195
- return;
196
- }
197
-
198
- // Validate --message
143
+ // Validate --message (keep basic validation for immediate CLI feedback)
199
144
  const message = opts.message.trim();
200
145
  if (message.length === 0) {
201
146
  writeOutput(cmd, {
@@ -217,7 +162,7 @@ Examples:
217
162
  return;
218
163
  }
219
164
 
220
- // Validate --deadline-at
165
+ // Parse --deadline-at
221
166
  let deadlineAt: number | undefined;
222
167
  if (opts.deadlineAt != null) {
223
168
  const parsed = Number(opts.deadlineAt);
@@ -232,36 +177,43 @@ Examples:
232
177
  deadlineAt = parsed;
233
178
  }
234
179
 
235
- // Validate --preferred-channels
236
- let preferredChannels: NotificationChannel[] | undefined;
180
+ // Parse --preferred-channels
181
+ let preferredChannels: string[] | undefined;
237
182
  if (opts.preferredChannels) {
238
- const deliverable = getDeliverableChannels();
239
- const requested = opts.preferredChannels
183
+ preferredChannels = opts.preferredChannels
240
184
  .split(",")
241
185
  .map((ch) => ch.trim())
242
186
  .filter((ch) => ch.length > 0);
187
+ }
243
188
 
244
- for (const ch of requested) {
245
- if (!deliverable.includes(ch as NotificationChannel)) {
246
- writeOutput(cmd, {
247
- ok: false,
248
- error: `Invalid preferred channel "${ch}". Valid deliverable channels: ${deliverable.join(", ")}`,
249
- });
250
- process.exitCode = 1;
251
- return;
252
- }
189
+ // Parse --deep-link-metadata
190
+ let deepLinkMetadata: Record<string, unknown> | undefined;
191
+ if (opts.deepLinkMetadata != null) {
192
+ try {
193
+ deepLinkMetadata = JSON.parse(opts.deepLinkMetadata) as Record<
194
+ string,
195
+ unknown
196
+ >;
197
+ } catch {
198
+ writeOutput(cmd, {
199
+ ok: false,
200
+ error: `Invalid deep-link-metadata: must be a valid JSON string`,
201
+ });
202
+ process.exitCode = 1;
203
+ return;
253
204
  }
254
- preferredChannels = requested as NotificationChannel[];
255
205
  }
256
206
 
257
- initializeDb();
258
- initAuthSigningKey(resolveSigningKey());
259
-
260
207
  const sourceContextId = opts.sessionId ?? `cli-${Date.now()}`;
261
208
 
262
- const result = await emitNotificationSignal({
263
- sourceEventName: opts.sourceEventName,
209
+ const result = await cliIpcCall<{
210
+ signalId: string;
211
+ dispatched: boolean;
212
+ deduplicated: boolean;
213
+ reason: string;
214
+ }>("emit_notification_signal", {
264
215
  sourceChannel: opts.sourceChannel,
216
+ sourceEventName: opts.sourceEventName,
265
217
  sourceContextId,
266
218
  attentionHints: {
267
219
  requiresAction: opts.requiresAction ?? true,
@@ -275,24 +227,33 @@ Examples:
275
227
  requestedBySource: opts.sourceChannel,
276
228
  ...(opts.title ? { requestedTitle: opts.title } : {}),
277
229
  ...(preferredChannels?.length ? { preferredChannels } : {}),
230
+ ...(deepLinkMetadata ? { deepLinkMetadata } : {}),
278
231
  },
279
232
  ...(opts.dedupeKey ? { dedupeKey: opts.dedupeKey } : {}),
280
233
  throwOnError: true,
281
234
  });
282
235
 
236
+ if (!result.ok) {
237
+ writeOutput(cmd, { ok: false, error: result.error });
238
+ process.exitCode = 1;
239
+ return;
240
+ }
241
+
242
+ const signal = result.result!;
243
+
283
244
  writeOutput(cmd, {
284
245
  ok: true,
285
- signalId: result.signalId,
286
- dispatched: result.dispatched,
287
- reason: result.reason,
246
+ signalId: signal.signalId,
247
+ dispatched: signal.dispatched,
248
+ reason: signal.reason,
288
249
  });
289
250
 
290
251
  if (!shouldOutputJson(cmd)) {
291
252
  log.info(
292
- `Signal ${result.signalId} emitted (dispatched: ${result.dispatched})`,
253
+ `Signal ${signal.signalId} emitted (dispatched: ${signal.dispatched})`,
293
254
  );
294
- if (result.reason) {
295
- log.info(` Reason: ${result.reason}`);
255
+ if (signal.reason) {
256
+ log.info(` Reason: ${signal.reason}`);
296
257
  }
297
258
  }
298
259
  } catch (err) {
@@ -318,7 +279,6 @@ Examples:
318
279
  Reads from the local notification events store, ordered by creation time
319
280
  (newest first). Each event represents a signal that was emitted through the
320
281
  notification pipeline.
321
- ${buildSourceEventNamesHelpBlock()}
322
282
 
323
283
  Examples:
324
284
  $ assistant notifications list
@@ -327,7 +287,7 @@ Examples:
327
287
  $ assistant notifications list --source-event-name schedule.notify --limit 10 --json`,
328
288
  )
329
289
  .action(
330
- (
290
+ async (
331
291
  opts: {
332
292
  limit?: string;
333
293
  sourceEventName?: string;
@@ -368,23 +328,28 @@ Examples:
368
328
  limit = parsed;
369
329
  }
370
330
 
371
- initializeDb();
372
-
373
- const rows = listEvents({
331
+ const result = await cliIpcCall<
332
+ Array<{
333
+ id: string;
334
+ sourceEventName: string;
335
+ sourceChannel: string;
336
+ sourceContextId: string;
337
+ urgency: string;
338
+ dedupeKey: string | null;
339
+ createdAt: string;
340
+ }>
341
+ >("list_notification_events", {
374
342
  limit,
375
343
  sourceEventName: opts.sourceEventName,
376
344
  });
377
345
 
378
- const events = rows.map((row) => ({
379
- id: row.id,
380
- sourceEventName: row.sourceEventName,
381
- sourceChannel: row.sourceChannel,
382
- sourceContextId: row.sourceContextId,
383
- urgency: (JSON.parse(row.attentionHintsJson) as { urgency: string })
384
- .urgency,
385
- dedupeKey: row.dedupeKey,
386
- createdAt: new Date(row.createdAt).toISOString(),
387
- }));
346
+ if (!result.ok) {
347
+ writeOutput(cmd, { ok: false, error: result.error });
348
+ process.exitCode = 1;
349
+ return;
350
+ }
351
+
352
+ const events = result.result!;
388
353
 
389
354
  writeOutput(cmd, { ok: true, events });
390
355