@vellumai/assistant 0.8.1 → 0.8.2

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 (506) hide show
  1. package/ARCHITECTURE.md +2 -7
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +5 -0
  5. package/docker-init-apt-root.sh +94 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +325 -3
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  21. package/src/__tests__/anthropic-provider.test.ts +45 -0
  22. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  23. package/src/__tests__/app-executors.test.ts +220 -4
  24. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  25. package/src/__tests__/bundled-asset.test.ts +6 -6
  26. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  27. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  28. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  29. package/src/__tests__/clawhub.test.ts +75 -16
  30. package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
  31. package/src/__tests__/config-schema.test.ts +21 -0
  32. package/src/__tests__/config-set-route.test.ts +80 -0
  33. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  34. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  35. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  36. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  37. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  38. package/src/__tests__/context-token-estimator.test.ts +1 -0
  39. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  42. package/src/__tests__/conversation-agent-loop.test.ts +2 -0
  43. package/src/__tests__/conversation-error.test.ts +42 -3
  44. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  45. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  46. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  47. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  48. package/src/__tests__/conversation-pairing.test.ts +54 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  51. package/src/__tests__/conversation-queue.test.ts +4 -1
  52. package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
  53. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  54. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  55. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  56. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  59. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  60. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  61. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  62. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  63. package/src/__tests__/dm-backfill.test.ts +121 -10
  64. package/src/__tests__/document-tool-security.test.ts +258 -0
  65. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  66. package/src/__tests__/edit-propagation.test.ts +33 -0
  67. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  68. package/src/__tests__/external-plugin-loader.test.ts +60 -36
  69. package/src/__tests__/filing-service.test.ts +140 -0
  70. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  71. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  72. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  73. package/src/__tests__/helpers/wait-for.ts +21 -0
  74. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  75. package/src/__tests__/history-repair.test.ts +73 -0
  76. package/src/__tests__/host-app-control-proxy.test.ts +266 -10
  77. package/src/__tests__/image-credentials.test.ts +1 -1
  78. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  79. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  80. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  81. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  82. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  83. package/src/__tests__/injector-chain.test.ts +10 -8
  84. package/src/__tests__/install-skill-routing.test.ts +155 -37
  85. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
  86. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  87. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  88. package/src/__tests__/llm-catalog-parity.test.ts +55 -13
  89. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
  90. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  91. package/src/__tests__/llm-usage-store.test.ts +114 -0
  92. package/src/__tests__/managed-profile-guard.test.ts +31 -29
  93. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  94. package/src/__tests__/managed-store.test.ts +84 -192
  95. package/src/__tests__/media-generate-image.test.ts +1 -1
  96. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  97. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  98. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  99. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  100. package/src/__tests__/openai-provider.test.ts +24 -0
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  102. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  103. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  104. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
  105. package/src/__tests__/platform.test.ts +2 -0
  106. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  107. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  108. package/src/__tests__/plugin-external-api.test.ts +68 -0
  109. package/src/__tests__/plugin-registry.test.ts +0 -77
  110. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  111. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  112. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  113. package/src/__tests__/plugin-types.test.ts +3 -13
  114. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  115. package/src/__tests__/process-message-display-content.test.ts +421 -0
  116. package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
  117. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  118. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
  119. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  120. package/src/__tests__/schedule-routes.test.ts +50 -3
  121. package/src/__tests__/schedule-store.test.ts +94 -0
  122. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  123. package/src/__tests__/schema-transforms.test.ts +20 -0
  124. package/src/__tests__/search-skills-unified.test.ts +0 -5
  125. package/src/__tests__/server-history-render.test.ts +43 -0
  126. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  127. package/src/__tests__/skill-load-tool.test.ts +27 -89
  128. package/src/__tests__/skill-memory.test.ts +23 -3
  129. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  130. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  131. package/src/__tests__/skills-install-extract.test.ts +49 -38
  132. package/src/__tests__/skills-install-staging.test.ts +159 -0
  133. package/src/__tests__/skills-uninstall.test.ts +9 -41
  134. package/src/__tests__/skills.test.ts +51 -58
  135. package/src/__tests__/slack-channel-config.test.ts +9 -0
  136. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  137. package/src/__tests__/system-prompt.test.ts +737 -63
  138. package/src/__tests__/terminal-tools.test.ts +28 -1
  139. package/src/__tests__/thread-backfill.test.ts +557 -27
  140. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  141. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  142. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  143. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  145. package/src/__tests__/tool-executor.test.ts +16 -4
  146. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  147. package/src/__tests__/turn-events-store.test.ts +256 -0
  148. package/src/__tests__/twilio-routes.test.ts +4 -0
  149. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  150. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  151. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  152. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  153. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  154. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  155. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  156. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  157. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  158. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  159. package/src/acp/resolve-agent.ts +1 -1
  160. package/src/agent/image-optimize.ts +13 -5
  161. package/src/calls/voice-session-bridge.ts +61 -42
  162. package/src/channels/types.ts +108 -0
  163. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  164. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  165. package/src/cli/commands/__tests__/schedules.test.ts +491 -0
  166. package/src/cli/commands/changelog.ts +106 -42
  167. package/src/cli/commands/conversations.ts +102 -17
  168. package/src/cli/commands/default-action.ts +10 -53
  169. package/src/cli/commands/notifications.ts +329 -317
  170. package/src/cli/commands/plugins.ts +185 -0
  171. package/src/cli/commands/schedules.ts +391 -0
  172. package/src/cli/commands/telemetry.ts +40 -0
  173. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  174. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  177. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  178. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  179. package/src/cli/lib/cli-colors.ts +12 -0
  180. package/src/cli/lib/confirm-prompt.ts +79 -0
  181. package/src/cli/lib/install-from-github.ts +304 -0
  182. package/src/cli/lib/list-installed-plugins.ts +137 -0
  183. package/src/cli/lib/uninstall-plugin.ts +82 -0
  184. package/src/cli/lib/unknown-command.ts +111 -0
  185. package/src/cli/program.ts +38 -2
  186. package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
  187. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  188. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  189. package/src/config/bundled-skills/document/SKILL.md +23 -3
  190. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  191. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  192. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  193. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  194. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  195. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  196. package/src/config/bundled-tool-registry.ts +6 -0
  197. package/src/config/feature-flag-registry.json +41 -1
  198. package/src/config/loader.ts +64 -38
  199. package/src/config/schema.ts +7 -10
  200. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  201. package/src/config/schemas/channels.ts +8 -0
  202. package/src/config/schemas/compaction.ts +28 -0
  203. package/src/config/schemas/heartbeat.ts +9 -0
  204. package/src/config/schemas/llm-request-logs.ts +31 -7
  205. package/src/config/schemas/llm.ts +3 -0
  206. package/src/config/schemas/memory-retrieval.ts +18 -0
  207. package/src/config/schemas/tools.ts +14 -0
  208. package/src/config/skills.ts +3 -96
  209. package/src/context/compactor.ts +1047 -0
  210. package/src/context/token-estimator.ts +2 -2
  211. package/src/context/window-manager.ts +197 -1520
  212. package/src/credential-execution/managed-catalog.ts +37 -0
  213. package/src/credential-health/credential-health-service.ts +280 -19
  214. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
  215. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  216. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  217. package/src/daemon/approval-generators.ts +8 -6
  218. package/src/daemon/config-watcher.ts +94 -31
  219. package/src/daemon/conversation-agent-loop.ts +169 -9
  220. package/src/daemon/conversation-error.ts +171 -37
  221. package/src/daemon/conversation-lifecycle.ts +53 -40
  222. package/src/daemon/conversation-messaging.ts +25 -6
  223. package/src/daemon/conversation-process.ts +49 -12
  224. package/src/daemon/conversation-runtime-assembly.ts +16 -1
  225. package/src/daemon/conversation-slash.ts +12 -5
  226. package/src/daemon/conversation-store.ts +11 -4
  227. package/src/daemon/conversation-tool-setup.ts +39 -7
  228. package/src/daemon/conversation.ts +33 -1
  229. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  230. package/src/daemon/first-greeting.ts +22 -2
  231. package/src/daemon/handlers/config-model.ts +6 -5
  232. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  233. package/src/daemon/handlers/shared.ts +14 -5
  234. package/src/daemon/handlers/skills.ts +111 -108
  235. package/src/daemon/history-repair.ts +28 -1
  236. package/src/daemon/host-app-control-proxy.ts +98 -23
  237. package/src/daemon/lifecycle.ts +45 -35
  238. package/src/daemon/meet-host-supervisor.ts +5 -4
  239. package/src/daemon/memory-v2-startup.ts +49 -0
  240. package/src/daemon/message-protocol.ts +1 -0
  241. package/src/daemon/message-types/conversations.ts +25 -0
  242. package/src/daemon/message-types/messages.ts +61 -0
  243. package/src/daemon/message-types/subagents.ts +1 -0
  244. package/src/daemon/message-types/sync.ts +1 -0
  245. package/src/daemon/pkb-reminder-builder.test.ts +1 -1
  246. package/src/daemon/pkb-reminder-builder.ts +1 -1
  247. package/src/daemon/plugin-source-watcher.ts +146 -0
  248. package/src/daemon/process-message.ts +21 -3
  249. package/src/daemon/server.ts +11 -2
  250. package/src/daemon/skill-memory-refresh.ts +29 -0
  251. package/src/documents/document-store.ts +221 -3
  252. package/src/embedded/plugin-api.ts +40 -0
  253. package/src/filing/filing-service.ts +39 -0
  254. package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
  255. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  256. package/src/heartbeat/heartbeat-service.ts +41 -0
  257. package/src/home/__tests__/feed-types.test.ts +40 -0
  258. package/src/home/feed-types.ts +22 -0
  259. package/src/home/post-connect-feed.ts +1 -0
  260. package/src/index.ts +18 -1
  261. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  262. package/src/mcp/client.ts +20 -4
  263. package/src/media/image-credentials.ts +3 -3
  264. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  265. package/src/memory/__tests__/conversation-queries.test.ts +263 -0
  266. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  267. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  268. package/src/memory/__tests__/message-content.test.ts +35 -0
  269. package/src/memory/bookmark-crud.ts +42 -10
  270. package/src/memory/context-search/sources/conversations.ts +62 -2
  271. package/src/memory/context-search/sources/workspace.ts +4 -0
  272. package/src/memory/conversation-crud.ts +63 -19
  273. package/src/memory/conversation-queries.ts +110 -10
  274. package/src/memory/db-init.ts +6 -0
  275. package/src/memory/delivery-crud.ts +152 -5
  276. package/src/memory/embedding-backend.ts +4 -4
  277. package/src/memory/external-conversation-store.ts +66 -5
  278. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
  279. package/src/memory/graph/conversation-graph-memory.ts +31 -15
  280. package/src/memory/graph/tools.ts +3 -3
  281. package/src/memory/indexer.ts +34 -29
  282. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  283. package/src/memory/jobs/embed-concept-page.ts +20 -11
  284. package/src/memory/jobs-worker.ts +6 -1
  285. package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
  286. package/src/memory/llm-request-log-source.ts +19 -52
  287. package/src/memory/llm-usage-store.ts +125 -5
  288. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  289. package/src/memory/message-content.ts +1 -1
  290. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  291. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  292. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  293. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  294. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  295. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  296. package/src/memory/migrations/index.ts +6 -0
  297. package/src/memory/migrations/registry.ts +8 -0
  298. package/src/memory/onboarding-events-store.ts +106 -0
  299. package/src/memory/schema/bookmarks.ts +0 -2
  300. package/src/memory/schema/calls.ts +1 -0
  301. package/src/memory/schema/inference.ts +1 -3
  302. package/src/memory/schema/infrastructure.ts +12 -0
  303. package/src/memory/turn-events-store.ts +127 -2
  304. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  305. package/src/memory/v2/__tests__/injection.test.ts +98 -8
  306. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  307. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  308. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  309. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  310. package/src/memory/v2/__tests__/router.test.ts +15 -0
  311. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  312. package/src/memory/v2/injection.ts +32 -6
  313. package/src/memory/v2/migration.ts +49 -19
  314. package/src/memory/v2/page-index.ts +35 -5
  315. package/src/memory/v2/prompts/router.ts +11 -8
  316. package/src/memory/v2/prompts/sweep.ts +2 -2
  317. package/src/memory/v2/qdrant.ts +135 -7
  318. package/src/memory/v2/router.ts +9 -8
  319. package/src/memory/v2/skill-store.ts +120 -35
  320. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  321. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  322. package/src/messaging/providers/slack/adapter.ts +43 -5
  323. package/src/messaging/providers/slack/client.ts +27 -0
  324. package/src/messaging/providers/slack/deep-link.ts +65 -0
  325. package/src/messaging/providers/slack/download.ts +104 -0
  326. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  327. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  328. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  329. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  330. package/src/messaging/providers/slack/types.ts +20 -1
  331. package/src/notifications/conversation-pairing.ts +2 -1
  332. package/src/notifications/decision-engine.ts +2 -1
  333. package/src/notifications/emit-signal.ts +20 -1
  334. package/src/notifications/home-feed-side-effect.ts +54 -0
  335. package/src/notifications/signal.ts +3 -1
  336. package/src/oauth/connection-resolver.ts +8 -4
  337. package/src/oauth/platform-connection.ts +6 -2
  338. package/src/oauth/seed-providers.ts +10 -1
  339. package/src/permissions/checker.ts +2 -0
  340. package/src/permissions/ipc-risk-types.ts +1 -0
  341. package/src/permissions/question-prompter.test.ts +416 -0
  342. package/src/permissions/question-prompter.ts +294 -0
  343. package/src/platform/client.test.ts +1 -1
  344. package/src/platform/client.ts +1 -1
  345. package/src/plugin-api/constants.ts +26 -0
  346. package/src/plugin-api/index.ts +34 -1
  347. package/src/plugin-api/types.ts +104 -22
  348. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  349. package/src/plugins/defaults/compaction.ts +0 -4
  350. package/src/plugins/defaults/empty-response.ts +0 -2
  351. package/src/plugins/defaults/history-repair.ts +0 -2
  352. package/src/plugins/defaults/injectors.ts +36 -3
  353. package/src/plugins/defaults/llm-call.ts +0 -2
  354. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  355. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  356. package/src/plugins/defaults/persistence.ts +0 -2
  357. package/src/plugins/defaults/title-generate.ts +0 -5
  358. package/src/plugins/defaults/token-estimate.ts +0 -2
  359. package/src/plugins/defaults/tool-error.ts +0 -7
  360. package/src/plugins/defaults/tool-execute.ts +0 -2
  361. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  362. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  363. package/src/plugins/external-api.ts +104 -0
  364. package/src/plugins/external-plugin-loader.ts +105 -32
  365. package/src/plugins/feature-gate.ts +22 -0
  366. package/src/plugins/pipeline.ts +37 -0
  367. package/src/plugins/registry.ts +48 -80
  368. package/src/plugins/types.ts +31 -26
  369. package/src/plugins/user-loader.ts +21 -2
  370. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  371. package/src/proactive-artifact/job.test.ts +37 -5
  372. package/src/prompts/__tests__/system-prompt.test.ts +12 -0
  373. package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
  374. package/src/prompts/normalize-onboarding.ts +27 -0
  375. package/src/prompts/sections.ts +302 -0
  376. package/src/prompts/system-prompt.ts +63 -166
  377. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  378. package/src/prompts/templates/system-sections.ts +173 -0
  379. package/src/providers/__tests__/inference.test.ts +22 -7
  380. package/src/providers/anthropic/client.ts +28 -28
  381. package/src/providers/connection-resolution.ts +7 -0
  382. package/src/providers/inference/adapter-factory.ts +41 -4
  383. package/src/providers/inference/connections.ts +74 -29
  384. package/src/providers/inference/resolve-auth.ts +12 -4
  385. package/src/providers/model-catalog.ts +294 -12
  386. package/src/providers/openai/chat-completions-provider.ts +10 -2
  387. package/src/providers/openrouter/client.ts +7 -0
  388. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
  389. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  390. package/src/providers/provider-availability.ts +17 -2
  391. package/src/providers/provider-catalog-visibility.ts +36 -0
  392. package/src/providers/registry.ts +22 -14
  393. package/src/providers/retry.ts +47 -1
  394. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  395. package/src/runtime/agent-wake.ts +42 -14
  396. package/src/runtime/auth/route-policy.ts +8 -1
  397. package/src/runtime/btw-sidechain.ts +2 -0
  398. package/src/runtime/http-types.ts +19 -0
  399. package/src/runtime/migrations/origin-mode.ts +1 -1
  400. package/src/runtime/pending-interactions.ts +1 -0
  401. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  402. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  403. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
  404. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  405. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  406. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  407. package/src/runtime/routes/acp-routes.ts +5 -3
  408. package/src/runtime/routes/auth-routes.ts +1 -1
  409. package/src/runtime/routes/bookmark-routes.ts +5 -3
  410. package/src/runtime/routes/btw-routes.ts +5 -1
  411. package/src/runtime/routes/channel-availability-routes.ts +121 -0
  412. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  413. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  414. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  415. package/src/runtime/routes/conversation-query-routes.ts +40 -35
  416. package/src/runtime/routes/conversation-routes.ts +90 -11
  417. package/src/runtime/routes/documents-routes.ts +25 -86
  418. package/src/runtime/routes/group-routes.ts +5 -0
  419. package/src/runtime/routes/inbound-conversation.ts +28 -8
  420. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  421. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  422. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  423. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  424. package/src/runtime/routes/index.ts +6 -0
  425. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  426. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  427. package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
  428. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  429. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  430. package/src/runtime/routes/integrations/twilio.ts +6 -13
  431. package/src/runtime/routes/notification-routes.ts +1 -1
  432. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  433. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  434. package/src/runtime/routes/question-routes.ts +259 -0
  435. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  436. package/src/runtime/routes/schedule-routes.ts +4 -7
  437. package/src/runtime/routes/subagents-routes.ts +57 -18
  438. package/src/runtime/routes/telemetry-routes.ts +27 -0
  439. package/src/runtime/routes/tts-routes.ts +27 -2
  440. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  441. package/src/runtime/routes/workspace-routes.ts +28 -0
  442. package/src/runtime/services/conversation-serializer.ts +39 -7
  443. package/src/runtime/sync/resource-sync-events.ts +93 -1
  444. package/src/schedule/schedule-store.ts +27 -2
  445. package/src/schedule/scheduler.ts +9 -1
  446. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  447. package/src/security/untrusted-content.ts +93 -8
  448. package/src/skills/catalog-files.ts +1 -1
  449. package/src/skills/catalog-install.ts +233 -116
  450. package/src/skills/clawhub.ts +70 -13
  451. package/src/skills/managed-store.ts +4 -119
  452. package/src/skills/skillssh-registry.ts +27 -48
  453. package/src/subagent/manager.ts +15 -7
  454. package/src/telemetry/types.ts +113 -1
  455. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  456. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  457. package/src/tools/apps/executors.ts +58 -7
  458. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  459. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  460. package/src/tools/browser/browser-execution.ts +15 -11
  461. package/src/tools/computer-use/definitions.ts +3 -3
  462. package/src/tools/credentials/vault.ts +1 -1
  463. package/src/tools/document/document-tool.ts +124 -1
  464. package/src/tools/filesystem/edit.ts +1 -1
  465. package/src/tools/filesystem/list.ts +1 -1
  466. package/src/tools/filesystem/read.ts +1 -1
  467. package/src/tools/filesystem/write.ts +5 -2
  468. package/src/tools/host-filesystem/transfer.ts +1 -1
  469. package/src/tools/host-terminal/host-shell.ts +1 -1
  470. package/src/tools/permission-checker.ts +1 -1
  471. package/src/tools/registry.ts +17 -7
  472. package/src/tools/schedule/create.ts +2 -2
  473. package/src/tools/schema-transforms.ts +7 -2
  474. package/src/tools/side-effects.ts +1 -0
  475. package/src/tools/skills/delete-managed.ts +4 -4
  476. package/src/tools/skills/execute.ts +1 -1
  477. package/src/tools/skills/scaffold-managed.ts +3 -2
  478. package/src/tools/subagent/notify-parent.ts +1 -1
  479. package/src/tools/system/request-permission.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +60 -1
  481. package/src/tools/tool-manifest.ts +2 -0
  482. package/src/tools/types.ts +72 -21
  483. package/src/tools/ui-surface/definitions.ts +6 -5
  484. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  485. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  486. package/src/types/onboarding-context.ts +2 -0
  487. package/src/util/errors.ts +17 -0
  488. package/src/util/platform.ts +10 -0
  489. package/src/watcher/__tests__/engine.test.ts +22 -0
  490. package/src/watcher/engine.ts +6 -2
  491. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  492. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  493. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  494. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  495. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  496. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  497. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  498. package/src/workspace/migrations/registry.ts +8 -0
  499. package/src/workspace/migrations/runner.ts +39 -9
  500. package/src/workspace/migrations/types.ts +4 -0
  501. package/examples/plugins/echo/bun.lock +0 -25
  502. package/src/__tests__/context-window-manager.test.ts +0 -2481
  503. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  504. package/src/context/prompts/compact.md +0 -26
  505. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  506. /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
@@ -43,9 +43,20 @@ interface GitHubRelease {
43
43
  prerelease: boolean;
44
44
  }
45
45
 
46
+ /**
47
+ * Cache shape persisted under `<workspace>/data/changelog-cache.json`.
48
+ *
49
+ * - `recent` holds the most recent stable releases, capped at
50
+ * `CACHE_STABLE_LIMIT`. TTL-gated via `fetchedAt`.
51
+ * - `byTag` is a content-addressed slot for single-tag lookups. Release
52
+ * tags are immutable once published, so entries here are kept without a
53
+ * TTL — first `show <tag>` populates, subsequent calls short-circuit
54
+ * the fetch.
55
+ */
46
56
  interface CacheStore {
47
57
  fetchedAt: string;
48
- releases: GitHubRelease[];
58
+ recent: GitHubRelease[];
59
+ byTag: Record<string, GitHubRelease>;
49
60
  }
50
61
 
51
62
  // ── Config ───────────────────────────────────────────────────────────
@@ -54,6 +65,19 @@ const REPO = "vellum-ai/vellum-assistant";
54
65
  const LIST_TTL_MS = 60 * 60 * 1000;
55
66
  const DEFAULT_LIST_LIMIT = 30;
56
67
  const MAX_LIST_LIMIT = 100;
68
+ /**
69
+ * Maximum number of stable releases we persist in the rolling `recent` slot.
70
+ * Most callers only ever read the latest one or two; capping the cache keeps
71
+ * the file small and the network round-trip predictable.
72
+ */
73
+ const CACHE_STABLE_LIMIT = 5;
74
+ /**
75
+ * When fetching, request a page size that comfortably covers the caller's
76
+ * requested limit plus a small buffer to absorb the occasional draft or
77
+ * pre-release without forcing a second round-trip.
78
+ */
79
+ const STABLE_BUFFER = 5;
80
+ const MIN_FETCH_PAGE_SIZE = 30;
57
81
  const FETCH_TIMEOUT_MS = 10_000;
58
82
  const USER_AGENT = "vellum-assistant-cli";
59
83
 
@@ -72,7 +96,9 @@ function readCache(): CacheStore | null {
72
96
  ) as Partial<CacheStore>;
73
97
  if (
74
98
  typeof parsed.fetchedAt !== "string" ||
75
- !Array.isArray(parsed.releases)
99
+ !Array.isArray(parsed.recent) ||
100
+ typeof parsed.byTag !== "object" ||
101
+ parsed.byTag === null
76
102
  ) {
77
103
  return null;
78
104
  }
@@ -122,8 +148,20 @@ function describeGithubError(status: number, body: string): string {
122
148
  return `GitHub API error ${status}: ${body || "(no body)"}`;
123
149
  }
124
150
 
151
+ /**
152
+ * Compute the per-page size for a fetch that needs to cover `limit` stable
153
+ * releases. The buffer absorbs drafts/prereleases without paginating; we cap
154
+ * at `MAX_LIST_LIMIT` because that's GitHub's per-page maximum.
155
+ */
156
+ function pageSizeFor(limit: number): number {
157
+ return Math.min(
158
+ MAX_LIST_LIMIT,
159
+ Math.max(limit + STABLE_BUFFER, MIN_FETCH_PAGE_SIZE),
160
+ );
161
+ }
162
+
125
163
  async function fetchReleaseList(limit: number): Promise<GitHubRelease[]> {
126
- const url = `https://api.github.com/repos/${REPO}/releases?per_page=${limit}`;
164
+ const url = `https://api.github.com/repos/${REPO}/releases?per_page=${pageSizeFor(limit)}`;
127
165
  const res = await githubFetch(url);
128
166
  if (!res.ok) {
129
167
  const text = await res.text().catch(() => "");
@@ -150,28 +188,72 @@ interface LoadOpts {
150
188
  limit: number;
151
189
  }
152
190
 
191
+ /**
192
+ * Persist the rolling list of stable releases. Cap at `CACHE_STABLE_LIMIT`.
193
+ * Mirror each entry into `byTag` so single-tag lookups short-circuit fetches
194
+ * for any tag that appears in the rolling list. Preserves any existing
195
+ * tag-keyed entries (which never expire).
196
+ */
197
+ function persistRecent(stable: GitHubRelease[]): void {
198
+ const previous = readCache();
199
+ const recent = stable.slice(0, CACHE_STABLE_LIMIT);
200
+ const byTag: Record<string, GitHubRelease> = { ...(previous?.byTag ?? {}) };
201
+ for (const r of recent) {
202
+ byTag[r.tag_name] = r;
203
+ }
204
+ writeCache({ fetchedAt: new Date().toISOString(), recent, byTag });
205
+ }
206
+
207
+ /**
208
+ * Persist a single tag fetched via `show <tag>`. Tags are immutable; this
209
+ * entry survives subsequent list refreshes.
210
+ */
211
+ function persistByTag(release: GitHubRelease): void {
212
+ const previous = readCache();
213
+ writeCache({
214
+ fetchedAt: previous?.fetchedAt ?? new Date().toISOString(),
215
+ recent: previous?.recent ?? [],
216
+ byTag: { ...(previous?.byTag ?? {}), [release.tag_name]: release },
217
+ });
218
+ }
219
+
220
+ /**
221
+ * Returns up to `opts.limit` stable releases, newest first. Uses the cached
222
+ * rolling list when fresh and large enough; otherwise fetches a single page
223
+ * sized via `pageSizeFor` and filters stable. The cached rolling list is
224
+ * capped at `CACHE_STABLE_LIMIT` even if the caller asks for more.
225
+ */
153
226
  async function loadReleases(opts: LoadOpts): Promise<GitHubRelease[]> {
154
227
  if (!opts.noCache) {
155
228
  const cache = readCache();
156
- if (cache && !isStale(cache) && cache.releases.length >= opts.limit) {
157
- return cache.releases.slice(0, opts.limit);
229
+ if (cache && !isStale(cache) && cache.recent.length >= opts.limit) {
230
+ return cache.recent.slice(0, opts.limit);
158
231
  }
159
232
  }
160
- const releases = await fetchReleaseList(opts.limit);
161
- writeCache({ fetchedAt: new Date().toISOString(), releases });
162
- return releases;
233
+ const raw = await fetchReleaseList(opts.limit);
234
+ const stable = stableReleases(raw);
235
+ persistRecent(stable);
236
+ return stable.slice(0, opts.limit);
163
237
  }
164
238
 
239
+ /**
240
+ * Returns the release for a specific tag. Prefers the cached `byTag` slot
241
+ * (immutable, no TTL), then the rolling recent list, then the network.
242
+ * Persists fetched results into `byTag` so subsequent lookups short-circuit.
243
+ */
165
244
  async function loadReleaseByTag(
166
245
  tag: string,
167
246
  opts: { noCache: boolean },
168
247
  ): Promise<GitHubRelease | null> {
169
248
  if (!opts.noCache) {
170
249
  const cache = readCache();
171
- const hit = cache?.releases.find((r) => r.tag_name === tag);
250
+ const hit =
251
+ cache?.byTag[tag] ?? cache?.recent.find((r) => r.tag_name === tag);
172
252
  if (hit) return hit;
173
253
  }
174
- return fetchReleaseByTag(tag);
254
+ const release = await fetchReleaseByTag(tag);
255
+ if (release) persistByTag(release);
256
+ return release;
175
257
  }
176
258
 
177
259
  // ── Filtering / version utilities ────────────────────────────────────
@@ -264,11 +346,12 @@ interface DefaultOpts extends CommonOpts {
264
346
  async function runDefault(opts: DefaultOpts): Promise<void> {
265
347
  const noCache = opts.cache === false;
266
348
  const useJson = opts.json === true;
267
- const limit = parseLimit(opts.limit, DEFAULT_LIST_LIMIT);
268
349
 
269
350
  if (opts.since) {
351
+ // --since needs the full rolling list so we can filter by tag.
352
+ const limit = parseLimit(opts.limit, DEFAULT_LIST_LIMIT);
270
353
  const floor = normalizeTag(opts.since);
271
- const all = stableReleases(await loadReleases({ noCache, limit }));
354
+ const all = await loadReleases({ noCache, limit });
272
355
  const newer = all
273
356
  .filter((r) => compareTags(r.tag_name, floor) > 0)
274
357
  .sort((a, b) => compareTags(b.tag_name, a.tag_name));
@@ -285,7 +368,10 @@ async function runDefault(opts: DefaultOpts): Promise<void> {
285
368
  return;
286
369
  }
287
370
 
288
- const releases = stableReleases(await loadReleases({ noCache, limit }));
371
+ // Bare default action only needs the latest stable release. Asking for 1
372
+ // means a populated cache (even with a single entry) is enough to short-
373
+ // circuit the network round-trip.
374
+ const releases = await loadReleases({ noCache, limit: 1 });
289
375
  if (releases.length === 0) {
290
376
  emitError("No releases found.");
291
377
  }
@@ -310,10 +396,7 @@ async function runList(opts: CommonOpts): Promise<void> {
310
396
  const noCache = opts.cache === false;
311
397
  const useJson = opts.json === true;
312
398
  const limit = parseLimit(opts.limit, DEFAULT_LIST_LIMIT);
313
- const releases = stableReleases(await loadReleases({ noCache, limit })).slice(
314
- 0,
315
- limit,
316
- );
399
+ const releases = await loadReleases({ noCache, limit });
317
400
  if (useJson) {
318
401
  emit(JSON.stringify({ releases }, null, 2));
319
402
  return;
@@ -335,15 +418,17 @@ export function registerChangelogCommand(program: Command): void {
335
418
  registerCommand(program, {
336
419
  name: "changelog",
337
420
  transport: "local",
338
- description: "Show release notes (fetched on demand from GitHub Releases)",
421
+ description:
422
+ "Show release notes of the Vellum Assistant to see what new capabilities you have!",
339
423
  build: (cmd) => {
340
424
  cmd.addHelpText(
341
425
  "after",
342
426
  `
343
427
  Release notes are fetched on demand from the public GitHub Releases of
344
- ${REPO}. Results are cached locally for ${LIST_TTL_MS / 60_000} minutes;
345
- pass --no-cache to bypass the cache. Specific tags are cached without TTL
346
- because release tags are immutable.
428
+ ${REPO}. The most recent ${CACHE_STABLE_LIMIT} stable releases are cached
429
+ locally for ${LIST_TTL_MS / 60_000} minutes; pass --no-cache to bypass.
430
+ Specific tags are cached indefinitely once seen because release tags are
431
+ immutable.
347
432
 
348
433
  Examples:
349
434
  $ assistant changelog Show the latest release
@@ -391,24 +476,3 @@ Examples:
391
476
  },
392
477
  });
393
478
  }
394
-
395
- // ── Test-only exports ────────────────────────────────────────────────
396
-
397
- /**
398
- * Internal helpers exported for unit tests only. Not part of the public CLI
399
- * contract — do not import from outside `cli/commands/__tests__`.
400
- */
401
- export const __testing = {
402
- compareTags,
403
- normalizeTag,
404
- stableReleases,
405
- renderRelease,
406
- renderList,
407
- parseLimit,
408
- readCache,
409
- writeCache,
410
- isStale,
411
- loadReleases,
412
- loadReleaseByTag,
413
- getCachePath,
414
- };
@@ -1,3 +1,5 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+
1
3
  import type { Command } from "commander";
2
4
 
3
5
  import { cliIpcCall, exitFromIpcResult } from "../../ipc/cli-client.js";
@@ -7,6 +9,98 @@ import { log } from "../logger.js";
7
9
  import { registerConversationsDeferCommand } from "./conversations-defer.js";
8
10
  import { registerConversationsImportCommand } from "./conversations-import.js";
9
11
 
12
+ type ConversationSeedMessage = {
13
+ role: "user" | "assistant";
14
+ content: string;
15
+ };
16
+
17
+ function readSeedMessages(
18
+ contentFile?: string,
19
+ ): ConversationSeedMessage[] | undefined {
20
+ if (!contentFile) return undefined;
21
+ if (!existsSync(contentFile)) {
22
+ log.error(`Error: content file not found: ${contentFile}`);
23
+ process.exitCode = 1;
24
+ return undefined;
25
+ }
26
+
27
+ let parsed: unknown;
28
+ try {
29
+ parsed = JSON.parse(readFileSync(contentFile, "utf-8"));
30
+ } catch (err) {
31
+ log.error(
32
+ `Error: failed to read content file: ${err instanceof Error ? err.message : String(err)}`,
33
+ );
34
+ process.exitCode = 1;
35
+ return undefined;
36
+ }
37
+
38
+ if (!Array.isArray(parsed)) {
39
+ log.error("Error: content file must contain an array of messages");
40
+ process.exitCode = 1;
41
+ return undefined;
42
+ }
43
+
44
+ const messages: ConversationSeedMessage[] = [];
45
+ for (const [index, value] of parsed.entries()) {
46
+ if (
47
+ typeof value !== "object" ||
48
+ value === null ||
49
+ !("role" in value) ||
50
+ !("content" in value)
51
+ ) {
52
+ log.error(`Error: message ${index} must include role and content`);
53
+ process.exitCode = 1;
54
+ return undefined;
55
+ }
56
+ const role = (value as { role?: unknown }).role;
57
+ const content = (value as { content?: unknown }).content;
58
+ if (
59
+ (role !== "user" && role !== "assistant") ||
60
+ typeof content !== "string"
61
+ ) {
62
+ log.error(
63
+ `Error: message ${index} must have role user|assistant and string content`,
64
+ );
65
+ process.exitCode = 1;
66
+ return undefined;
67
+ }
68
+ messages.push({ role, content });
69
+ }
70
+
71
+ return messages;
72
+ }
73
+
74
+ async function createConversationCli(
75
+ title: string | undefined,
76
+ opts?: { contentFile?: string },
77
+ ): Promise<void> {
78
+ const messages = readSeedMessages(opts?.contentFile);
79
+ if (process.exitCode) return;
80
+
81
+ const result = await cliIpcCall<{
82
+ id: string;
83
+ title: string;
84
+ conversationKey: string;
85
+ messagesInserted: number;
86
+ }>("conversation_create_cli", {
87
+ body: {
88
+ title,
89
+ messages,
90
+ },
91
+ });
92
+
93
+ if (!result.ok) return exitFromIpcResult(result);
94
+
95
+ const conversation = result.result!;
96
+ const seedSuffix = conversation.messagesInserted
97
+ ? `, seeded ${conversation.messagesInserted} messages`
98
+ : "";
99
+ log.info(
100
+ `Created conversation: ${conversation.title} (${conversation.id}), conversation key: ${conversation.conversationKey}${seedSuffix}`,
101
+ );
102
+ }
103
+
10
104
  export function registerConversationsCommand(program: Command): void {
11
105
  registerCommand(program, {
12
106
  name: "conversations",
@@ -85,6 +179,7 @@ Examples:
85
179
  conversations
86
180
  .command("new [title]")
87
181
  .description("Create a new conversation")
182
+ .option("--content-file <path>", "Seed messages from a JSON file")
88
183
  .addHelpText(
89
184
  "after",
90
185
  `
@@ -92,26 +187,18 @@ Arguments:
92
187
  title Optional conversation title (string). If omitted, a default title is
93
188
  assigned by the assistant.
94
189
 
95
- Creates a new conversation and prints its title and ID.
190
+ The content file must be a JSON array of { role, content } messages.
191
+
192
+ Creates a new conversation and prints its title, ID, and generated conversation
193
+ key.
96
194
 
97
195
  Examples:
98
196
  $ assistant conversations new
99
197
  $ assistant conversations new "Project planning"
198
+ $ assistant conversations new --content-file /tmp/seed.json
100
199
  $ assistant conversations new "Bug triage 2026-03-05"`,
101
200
  )
102
- .action(async (title?: string) => {
103
- const result = await cliIpcCall<{ id: string; title: string }>(
104
- "conversation_create_cli",
105
- { body: { title } },
106
- );
107
-
108
- if (!result.ok) return exitFromIpcResult(result);
109
-
110
- const conversation = result.result!;
111
- log.info(
112
- `Created conversation: ${conversation.title} (${conversation.id})`,
113
- );
114
- });
201
+ .action(createConversationCli);
115
202
 
116
203
  // -------------------------------------------------------------------
117
204
  // rename
@@ -278,9 +365,7 @@ Examples:
278
365
 
279
366
  if (!result.ok) return exitFromIpcResult(result);
280
367
 
281
- log.info(
282
- `Cleared ${result.result!.cleared} conversations. Done.`,
283
- );
368
+ log.info(`Cleared ${result.result!.cleared} conversations. Done.`);
284
369
  });
285
370
 
286
371
  // -------------------------------------------------------------------
@@ -3,6 +3,10 @@ import type { Command } from "commander";
3
3
  import { startCli } from "../../cli.js";
4
4
  import { shouldAutoStartDaemon } from "../../daemon/connection-policy.js";
5
5
  import { ensureDaemonRunning } from "../../daemon/daemon-control.js";
6
+ import {
7
+ findClosestCommand,
8
+ formatUnknownCommandMessage,
9
+ } from "../lib/unknown-command.js";
6
10
 
7
11
  export function registerDefaultAction(program: Command): void {
8
12
  program.action(async (_options: unknown, cmd: Command) => {
@@ -10,16 +14,16 @@ export function registerDefaultAction(program: Command): void {
10
14
  // args instead of raising an error. Detect this case and fail with a
11
15
  // helpful message so users don't silently get the interactive CLI when
12
16
  // they mistype a command name.
17
+ //
18
+ // The `assistant <unknown> --help` path is intercepted earlier (see
19
+ // src/index.ts) because Commander's `--help` short-circuit fires before
20
+ // this action runs. This branch covers `assistant <unknown>` with no
21
+ // `--help` flag.
13
22
  if (cmd.args.length > 0) {
14
23
  const unknown = cmd.args[0];
15
24
  const available = cmd.commands.map((c) => c.name());
16
25
  const suggestion = findClosestCommand(unknown, available);
17
- const lines = [`unknown command '${unknown}'`];
18
- if (suggestion) {
19
- lines.push(`(Did you mean '${suggestion}'?)`);
20
- }
21
- lines.push(`Run 'assistant --help' to see a list of available commands.`);
22
- cmd.error(lines.join("\n"), {
26
+ cmd.error(formatUnknownCommandMessage({ token: unknown, suggestion }), {
23
27
  code: "commander.unknownCommand",
24
28
  exitCode: 1,
25
29
  });
@@ -32,50 +36,3 @@ export function registerDefaultAction(program: Command): void {
32
36
  await startCli();
33
37
  });
34
38
  }
35
-
36
- /**
37
- * Find the closest matching command name using Levenshtein distance.
38
- * Returns the best match if the distance is ≤ 40% of the longer string's
39
- * length, otherwise returns undefined.
40
- */
41
- function findClosestCommand(
42
- input: string,
43
- candidates: string[],
44
- ): string | undefined {
45
- let best: string | undefined;
46
- let bestDist = Infinity;
47
-
48
- for (const name of candidates) {
49
- const dist = levenshtein(input.toLowerCase(), name.toLowerCase());
50
- if (dist < bestDist) {
51
- bestDist = dist;
52
- best = name;
53
- }
54
- }
55
-
56
- // Only suggest if the edit distance is at most 40% of the longer string
57
- const maxLen = Math.max(input.length, best?.length ?? 0);
58
- if (best && bestDist <= Math.ceil(maxLen * 0.4)) {
59
- return best;
60
- }
61
- return undefined;
62
- }
63
-
64
- function levenshtein(a: string, b: string): number {
65
- const m = a.length;
66
- const n = b.length;
67
- const dp: number[][] = Array.from({ length: m + 1 }, () =>
68
- Array(n + 1).fill(0),
69
- );
70
- for (let i = 0; i <= m; i++) dp[i][0] = i;
71
- for (let j = 0; j <= n; j++) dp[0][j] = j;
72
- for (let i = 1; i <= m; i++) {
73
- for (let j = 1; j <= n; j++) {
74
- dp[i][j] =
75
- a[i - 1] === b[j - 1]
76
- ? dp[i - 1][j - 1]
77
- : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
78
- }
79
- }
80
- return dp[m][n];
81
- }