@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
@@ -1,6 +1,6 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
- import { MANAGED_PROVIDER_META } from "../providers/managed-proxy/constants.js";
3
+ import { PLATFORM_PROVIDER_META } from "../providers/platform-proxy/constants.js";
4
4
  import { credentialKey } from "../security/credential-key.js";
5
5
 
6
6
  // ---------------------------------------------------------------------------
@@ -436,7 +436,7 @@ describe("managed proxy integration — ollama exclusion", () => {
436
436
  });
437
437
 
438
438
  test("ollama metadata is marked as non-managed", () => {
439
- const meta = MANAGED_PROVIDER_META.ollama;
439
+ const meta = PLATFORM_PROVIDER_META.ollama;
440
440
  expect(meta).toBeDefined();
441
441
  expect(meta.managed).toBe(false);
442
442
  expect(meta.proxyPath).toBeUndefined();
@@ -478,7 +478,7 @@ describe("config mode flip → provider reinit", () => {
478
478
  describe("managed proxy integration — constants integrity", () => {
479
479
  test("anthropic, openai, and gemini have metadata with managed=true and a proxyPath", () => {
480
480
  for (const provider of ["anthropic", "openai", "gemini"]) {
481
- const meta = MANAGED_PROVIDER_META[provider];
481
+ const meta = PLATFORM_PROVIDER_META[provider];
482
482
  expect(meta).toBeDefined();
483
483
  expect(meta.managed).toBe(true);
484
484
  expect(meta.proxyPath).toBeTruthy();
@@ -487,27 +487,27 @@ describe("managed proxy integration — constants integrity", () => {
487
487
  });
488
488
 
489
489
  test("anthropic routes through anthropic proxy path", () => {
490
- expect(MANAGED_PROVIDER_META.anthropic.proxyPath).toBe(
490
+ expect(PLATFORM_PROVIDER_META.anthropic.proxyPath).toBe(
491
491
  "/v1/runtime-proxy/anthropic",
492
492
  );
493
493
  });
494
494
 
495
495
  test("gemini routes through gemini proxy path", () => {
496
- expect(MANAGED_PROVIDER_META.gemini.proxyPath).toBe(
496
+ expect(PLATFORM_PROVIDER_META.gemini.proxyPath).toBe(
497
497
  "/v1/runtime-proxy/gemini",
498
498
  );
499
499
  });
500
500
 
501
501
  test("openai routes through openai proxy path", () => {
502
- expect(MANAGED_PROVIDER_META.openai.proxyPath).toBe(
502
+ expect(PLATFORM_PROVIDER_META.openai.proxyPath).toBe(
503
503
  "/v1/runtime-proxy/openai",
504
504
  );
505
505
  });
506
506
 
507
507
  test("fireworks and openrouter are not managed proxy capable", () => {
508
508
  for (const provider of ["fireworks", "openrouter"]) {
509
- expect(MANAGED_PROVIDER_META[provider].managed).toBe(false);
510
- expect(MANAGED_PROVIDER_META[provider].proxyPath).toBeUndefined();
509
+ expect(PLATFORM_PROVIDER_META[provider].managed).toBe(false);
510
+ expect(PLATFORM_PROVIDER_META[provider].proxyPath).toBeUndefined();
511
511
  }
512
512
  });
513
513
  });
@@ -3,6 +3,7 @@ import { join } from "node:path";
3
3
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
4
4
 
5
5
  const TEST_DIR = process.env.VELLUM_WORKSPACE_DIR!;
6
+ const mockRefreshSkillCapabilityMemories = mock(() => {});
6
7
 
7
8
  mock.module("../util/logger.js", () => ({
8
9
  getLogger: () =>
@@ -11,6 +12,11 @@ mock.module("../util/logger.js", () => ({
11
12
  }),
12
13
  }));
13
14
 
15
+ mock.module("../daemon/skill-memory-refresh.js", () => ({
16
+ refreshSkillCapabilityMemories: mockRefreshSkillCapabilityMemories,
17
+ }));
18
+
19
+ import { loadSkillCatalog } from "../config/skills.js";
14
20
  import { executeScaffoldManagedSkill } from "../tools/skills/scaffold-managed.js";
15
21
  import type { ToolContext } from "../tools/types.js";
16
22
 
@@ -24,6 +30,7 @@ function makeContext(): ToolContext {
24
30
 
25
31
  beforeEach(() => {
26
32
  mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
33
+ mockRefreshSkillCapabilityMemories.mockClear();
27
34
  });
28
35
 
29
36
  afterEach(() => {
@@ -31,7 +38,29 @@ afterEach(() => {
31
38
  });
32
39
 
33
40
  describe("scaffold_managed_skill tool", () => {
34
- test("creates a valid skill and index entry", async () => {
41
+ test("keeps legacy index control as a deprecated no-op schema field", () => {
42
+ const tools = JSON.parse(
43
+ readFileSync(
44
+ join(
45
+ import.meta.dirname,
46
+ "../config/bundled-skills/skill-management/TOOLS.json",
47
+ ),
48
+ "utf-8",
49
+ ),
50
+ );
51
+ const scaffoldTool = tools.tools.find(
52
+ (tool: { name: string }) => tool.name === "scaffold_managed_skill",
53
+ );
54
+
55
+ expect(scaffoldTool).toBeDefined();
56
+ expect(scaffoldTool.input_schema.properties.add_to_index).toEqual({
57
+ type: "boolean",
58
+ description:
59
+ "Deprecated no-op compatibility field. Skills are discovered from top-level SKILL.md files.",
60
+ });
61
+ });
62
+
63
+ test("creates a valid skill discovered from its SKILL.md directory", async () => {
35
64
  const result = await executeScaffoldManagedSkill(
36
65
  {
37
66
  skill_id: "test-skill",
@@ -46,18 +75,40 @@ describe("scaffold_managed_skill tool", () => {
46
75
  const parsed = JSON.parse(result.content);
47
76
  expect(parsed.created).toBe(true);
48
77
  expect(parsed.skill_id).toBe("test-skill");
49
- expect(parsed.index_updated).toBe(true);
78
+ expect(parsed).not.toHaveProperty("index_updated");
50
79
 
51
80
  const skillFile = join(TEST_DIR, "skills", "test-skill", "SKILL.md");
52
81
  expect(existsSync(skillFile)).toBe(true);
53
82
  const content = readFileSync(skillFile, "utf-8");
54
83
  expect(content).toContain('name: "Test Skill"');
55
84
 
56
- const indexContent = readFileSync(
57
- join(TEST_DIR, "skills", "SKILLS.md"),
58
- "utf-8",
85
+ expect(existsSync(join(TEST_DIR, "skills", "SKILLS.md"))).toBe(false);
86
+
87
+ const catalog = loadSkillCatalog();
88
+ const skill = catalog.find((s) => s.id === "test-skill");
89
+ expect(skill).toBeDefined();
90
+ expect(skill!.name).toBe("Test Skill");
91
+ expect(mockRefreshSkillCapabilityMemories).toHaveBeenCalledTimes(1);
92
+ });
93
+
94
+ test("accepts legacy add_to_index input without returning index metadata", async () => {
95
+ const result = await executeScaffoldManagedSkill(
96
+ {
97
+ skill_id: "legacy-input",
98
+ name: "Legacy Input",
99
+ description: "A test skill",
100
+ body_markdown: "Do the thing.",
101
+ add_to_index: true,
102
+ },
103
+ makeContext(),
59
104
  );
60
- expect(indexContent).toContain("- test-skill");
105
+
106
+ expect(result.isError).toBe(false);
107
+ const parsed = JSON.parse(result.content);
108
+ expect(parsed.created).toBe(true);
109
+ expect(parsed).not.toHaveProperty("index_updated");
110
+ expect(existsSync(join(TEST_DIR, "skills", "SKILLS.md"))).toBe(false);
111
+ expect(mockRefreshSkillCapabilityMemories).toHaveBeenCalledTimes(1);
61
112
  });
62
113
 
63
114
  test("rejects duplicate unless overwrite=true", async () => {
@@ -258,7 +309,7 @@ describe("scaffold_managed_skill tool", () => {
258
309
  expect(result.content).toContain("traversal");
259
310
  });
260
311
 
261
- test("e2e: scaffold child then parent with includes, verify files and index", async () => {
312
+ test("e2e: scaffold child then parent with includes, verify file discovery", async () => {
262
313
  const childResult = await executeScaffoldManagedSkill(
263
314
  {
264
315
  skill_id: "e2e-child",
@@ -288,11 +339,12 @@ describe("scaffold_managed_skill tool", () => {
288
339
  expect(parentContent).toContain(" includes:");
289
340
  expect(parentContent).toContain(" - e2e-child");
290
341
 
291
- const indexContent = readFileSync(
292
- join(TEST_DIR, "skills", "SKILLS.md"),
293
- "utf-8",
294
- );
295
- expect(indexContent).toContain("- e2e-child");
296
- expect(indexContent).toContain("- e2e-parent");
342
+ expect(existsSync(join(TEST_DIR, "skills", "SKILLS.md"))).toBe(false);
343
+
344
+ const catalog = loadSkillCatalog();
345
+ expect(catalog.find((s) => s.id === "e2e-child")).toBeDefined();
346
+ const parent = catalog.find((s) => s.id === "e2e-parent");
347
+ expect(parent).toBeDefined();
348
+ expect(parent!.includes).toEqual(["e2e-child"]);
297
349
  });
298
350
  });
@@ -39,8 +39,11 @@ mock.module("../daemon/conversation-store.js", () => ({
39
39
  },
40
40
  }));
41
41
 
42
+ import { SYNC_TAGS } from "../daemon/message-types/sync.js";
42
43
  import { getDb } from "../memory/db-connection.js";
43
44
  import { initializeDb } from "../memory/db-init.js";
45
+ import type { AssistantEvent } from "../runtime/assistant-event.js";
46
+ import { assistantEventHub } from "../runtime/assistant-event-hub.js";
44
47
  import { ROUTES } from "../runtime/routes/schedule-routes.js";
45
48
  import type { RouteDefinition } from "../runtime/routes/types.js";
46
49
  import {
@@ -71,6 +74,15 @@ function findRoute(endpoint: string, method: string): RouteDefinition {
71
74
  return route;
72
75
  }
73
76
 
77
+ async function waitFor(predicate: () => boolean): Promise<void> {
78
+ const deadline = Date.now() + 500;
79
+ while (Date.now() < deadline) {
80
+ if (predicate()) return;
81
+ await new Promise((resolve) => setTimeout(resolve, 5));
82
+ }
83
+ throw new Error("Timed out waiting for schedule route event");
84
+ }
85
+
74
86
  describe("schedule run-now trust propagation", () => {
75
87
  beforeEach(() => {
76
88
  clearTables();
@@ -218,6 +230,41 @@ describe("GET /schedules — default defer exclusion", () => {
218
230
  expect(result.schedules).toHaveLength(1);
219
231
  expect(result.schedules[0].id).toBe(agent.id);
220
232
  });
233
+
234
+ test("mutation routes emit schedule sync invalidation", async () => {
235
+ const received: AssistantEvent[] = [];
236
+ const subscription = assistantEventHub.subscribe({
237
+ type: "process",
238
+ callback: (event) => {
239
+ received.push(event);
240
+ },
241
+ });
242
+
243
+ try {
244
+ const agent = createSchedule({
245
+ name: "Agent schedule",
246
+ cronExpression: "* * * * *",
247
+ message: "hello",
248
+ syntax: "cron",
249
+ });
250
+ await waitFor(() => received.length >= 1);
251
+ received.length = 0;
252
+
253
+ const route = findRoute("schedules/:id/toggle", "POST");
254
+ route.handler({
255
+ pathParams: { id: agent.id },
256
+ body: { enabled: false },
257
+ });
258
+
259
+ await waitFor(() => received.length >= 1);
260
+ expect(received[0].message).toEqual({
261
+ type: "sync_changed",
262
+ tags: [SYNC_TAGS.assistantSchedules],
263
+ });
264
+ } finally {
265
+ subscription.dispose();
266
+ }
267
+ });
221
268
  });
222
269
 
223
270
  // ── schedules/:id/runs limit handling ─────────────────────────────────────
@@ -388,9 +435,9 @@ describe("POST /schedules — create", () => {
388
435
  });
389
436
 
390
437
  test("rejects missing required fields", () => {
391
- expect(() => postCreate({ expression: "* * * * *", message: "hi" })).toThrow(
392
- "name is required",
393
- );
438
+ expect(() =>
439
+ postCreate({ expression: "* * * * *", message: "hi" }),
440
+ ).toThrow("name is required");
394
441
  expect(() => postCreate({ name: "x", message: "hi" })).toThrow(
395
442
  "expression is required",
396
443
  );
@@ -9,13 +9,19 @@ mock.module("../util/logger.js", () => ({
9
9
  truncateForLog: (value: string) => value,
10
10
  }));
11
11
 
12
+ import { SYNC_TAGS } from "../daemon/message-types/sync.js";
12
13
  import { getDb } from "../memory/db-connection.js";
13
14
  import { initializeDb } from "../memory/db-init.js";
15
+ import type { AssistantEvent } from "../runtime/assistant-event.js";
16
+ import { assistantEventHub } from "../runtime/assistant-event-hub.js";
14
17
  import {
15
18
  cancelSchedule,
16
19
  claimDueSchedules,
17
20
  completeOneShot,
21
+ completeScheduleRun,
18
22
  createSchedule,
23
+ createScheduleRun,
24
+ deleteSchedule,
19
25
  describeCronExpression,
20
26
  failOneShot,
21
27
  getSchedule,
@@ -31,6 +37,94 @@ function getRawDb(): import("bun:sqlite").Database {
31
37
  .$client;
32
38
  }
33
39
 
40
+ async function waitFor(predicate: () => boolean): Promise<void> {
41
+ const deadline = Date.now() + 500;
42
+ while (Date.now() < deadline) {
43
+ if (predicate()) return;
44
+ await new Promise((resolve) => setTimeout(resolve, 5));
45
+ }
46
+ throw new Error("Timed out waiting for schedule-store event");
47
+ }
48
+
49
+ async function expectScheduleSyncEvent(
50
+ received: AssistantEvent[],
51
+ ): Promise<void> {
52
+ await waitFor(() =>
53
+ received.some(
54
+ (event) =>
55
+ event.message.type === "sync_changed" &&
56
+ event.message.tags.includes(SYNC_TAGS.assistantSchedules),
57
+ ),
58
+ );
59
+ const syncEvent = received.find(
60
+ (event) =>
61
+ event.message.type === "sync_changed" &&
62
+ event.message.tags.includes(SYNC_TAGS.assistantSchedules),
63
+ );
64
+ expect(syncEvent?.message).toEqual({
65
+ type: "sync_changed",
66
+ tags: [SYNC_TAGS.assistantSchedules],
67
+ });
68
+ received.length = 0;
69
+ }
70
+
71
+ describe("schedule sync invalidation", () => {
72
+ beforeEach(() => {
73
+ const db = getDb();
74
+ db.run("DELETE FROM cron_runs");
75
+ db.run("DELETE FROM cron_jobs");
76
+ });
77
+
78
+ test("store-level schedule mutations emit schedule sync invalidation", async () => {
79
+ const received: AssistantEvent[] = [];
80
+ const subscription = assistantEventHub.subscribe({
81
+ type: "process",
82
+ callback: (event) => {
83
+ received.push(event);
84
+ },
85
+ });
86
+
87
+ try {
88
+ const job = createSchedule({
89
+ name: "Sync test",
90
+ cronExpression: "* * * * *",
91
+ message: "sync me",
92
+ syntax: "cron",
93
+ });
94
+ await expectScheduleSyncEvent(received);
95
+
96
+ updateSchedule(job.id, { name: "Updated sync test" });
97
+ await expectScheduleSyncEvent(received);
98
+
99
+ getRawDb().run("UPDATE cron_jobs SET next_run_at = ? WHERE id = ?", [
100
+ Date.now() - 1000,
101
+ job.id,
102
+ ]);
103
+ expect(claimDueSchedules(Date.now())).toHaveLength(1);
104
+ await expectScheduleSyncEvent(received);
105
+
106
+ const runId = createScheduleRun(job.id, "conv-123");
107
+ completeScheduleRun(runId, { status: "ok" });
108
+ await expectScheduleSyncEvent(received);
109
+
110
+ const oneShot = createSchedule({
111
+ name: "One-shot sync test",
112
+ message: "cancel me",
113
+ nextRunAt: Date.now() + 60_000,
114
+ });
115
+ await expectScheduleSyncEvent(received);
116
+
117
+ expect(cancelSchedule(oneShot.id)).toBe(true);
118
+ await expectScheduleSyncEvent(received);
119
+
120
+ expect(deleteSchedule(job.id)).toBe(true);
121
+ await expectScheduleSyncEvent(received);
122
+ } finally {
123
+ subscription.dispose();
124
+ }
125
+ });
126
+ });
127
+
34
128
  // ── Cron schedules ──────────────────────────────────────────────────
35
129
 
36
130
  describe("createSchedule (cron)", () => {
@@ -31,6 +31,7 @@ const runBackgroundJobOptions: Array<{
31
31
  onConversationCreated?: (id: string) => void;
32
32
  }> = [];
33
33
  let runBackgroundJobShouldFail = false;
34
+ let runBackgroundJobBootstrapFails = false;
34
35
  mock.module("../runtime/background-job-runner.js", () => ({
35
36
  runBackgroundJob: async (opts: {
36
37
  prompt: string;
@@ -40,6 +41,25 @@ mock.module("../runtime/background-job-runner.js", () => ({
40
41
  suppressFailureNotifications?: boolean;
41
42
  onConversationCreated?: (id: string) => void;
42
43
  }) => {
44
+ runBackgroundJobOptions.push({
45
+ conversationType: opts.conversationType,
46
+ scheduleJobId: opts.scheduleJobId,
47
+ groupId: opts.groupId,
48
+ suppressFailureNotifications: opts.suppressFailureNotifications,
49
+ onConversationCreated: opts.onConversationCreated,
50
+ });
51
+ // Bootstrap-failure path: the real runner returns conversationId: ""
52
+ // when `bootstrapConversation` throws before assignment. Skip the
53
+ // callback (it was never reached) and surface the empty id to the
54
+ // scheduler so it can exercise the sentinel guard.
55
+ if (runBackgroundJobBootstrapFails) {
56
+ return {
57
+ conversationId: "",
58
+ ok: false,
59
+ error: new Error("Bootstrap failure"),
60
+ errorKind: "exception" as const,
61
+ };
62
+ }
43
63
  const { createConversation } =
44
64
  await import("../memory/conversation-crud.js");
45
65
  const conv = createConversation({
@@ -49,13 +69,6 @@ mock.module("../runtime/background-job-runner.js", () => ({
49
69
  ...(opts.groupId ? { groupId: opts.groupId } : {}),
50
70
  ...(opts.scheduleJobId ? { scheduleJobId: opts.scheduleJobId } : {}),
51
71
  });
52
- runBackgroundJobOptions.push({
53
- conversationType: opts.conversationType,
54
- scheduleJobId: opts.scheduleJobId,
55
- groupId: opts.groupId,
56
- suppressFailureNotifications: opts.suppressFailureNotifications,
57
- onConversationCreated: opts.onConversationCreated,
58
- });
59
72
  // Mirror the real runner's contract: fire the SSE callback synchronously
60
73
  // BEFORE the job's processMessage finishes, with the bootstrap-returned
61
74
  // conversation id.
@@ -137,6 +150,7 @@ describe("scheduler conversation reuse", () => {
137
150
  processedMessages.length = 0;
138
151
  runBackgroundJobOptions.length = 0;
139
152
  runBackgroundJobShouldFail = false;
153
+ runBackgroundJobBootstrapFails = false;
140
154
  });
141
155
 
142
156
  test("recurring schedule with reuseConversation=true reuses conversation across runs", async () => {
@@ -393,6 +407,7 @@ describe("scheduler talk-mode runner option propagation", () => {
393
407
  processedMessages.length = 0;
394
408
  runBackgroundJobOptions.length = 0;
395
409
  runBackgroundJobShouldFail = false;
410
+ runBackgroundJobBootstrapFails = false;
396
411
  });
397
412
 
398
413
  test("talk-mode propagates conversationType=scheduled, scheduleJobId, and quiet=>suppressFailureNotifications", async () => {
@@ -443,6 +458,38 @@ describe("scheduler talk-mode runner option propagation", () => {
443
458
  );
444
459
  });
445
460
 
461
+ test("talk-mode bootstrap failure writes sentinel conversationId, not empty string", async () => {
462
+ /**
463
+ * Regression: `runBackgroundJob` returns `{ conversationId: "", ok: false }`
464
+ * when bootstrap throws before the conversation row is assigned. The
465
+ * scheduler previously stored that empty string in the cron_runs DB row.
466
+ * Guard ensures we substitute a recognizable sentinel.
467
+ */
468
+ const rruleExpr = buildEveryMinuteRrule();
469
+ const schedule = createSchedule({
470
+ name: "Bootstrap Failure",
471
+ cronExpression: rruleExpr,
472
+ message: "x",
473
+ syntax: "rrule",
474
+ expression: rruleExpr,
475
+ });
476
+ forceScheduleDue(schedule.id);
477
+ runBackgroundJobBootstrapFails = true;
478
+
479
+ const processMessage = async () => {};
480
+ const scheduler = startScheduler(processMessage, () => {});
481
+ await new Promise((resolve) => setTimeout(resolve, 500));
482
+ scheduler.stop();
483
+
484
+ const runs = getScheduleRuns(schedule.id);
485
+ expect(runs.length).toBe(1);
486
+ expect(runs[0].status).toBe("error");
487
+ // Critical: must NOT be empty — the DB column should carry a marker that
488
+ // identifies this run as a bootstrap-failure case.
489
+ expect(runs[0].conversationId).not.toBe("");
490
+ expect(runs[0].conversationId).toBe(`bootstrap-error:${schedule.id}`);
491
+ });
492
+
446
493
  test("talk-mode fires onScheduleConversationCreated synchronously via runner callback (BEFORE the runner returns)", async () => {
447
494
  const rruleExpr = buildEveryMinuteRrule();
448
495
  const schedule = createSchedule({
@@ -157,6 +157,26 @@ describe("injectActivityField", () => {
157
157
  expect(Object.is(result[0], defs[0])).toBe(true);
158
158
  });
159
159
 
160
+ test("passes through undefined/null input_schema unchanged (no crash)", () => {
161
+ // Repro for the crash where an MCP server returned a tool with missing
162
+ // inputSchema and `injectActivityField` threw on `schema.type` access.
163
+ const defs: ToolDefinition[] = [
164
+ {
165
+ name: "missing",
166
+ description: "x",
167
+ input_schema: undefined as unknown as object,
168
+ },
169
+ {
170
+ name: "nullish",
171
+ description: "x",
172
+ input_schema: null as unknown as object,
173
+ },
174
+ ];
175
+ const result = injectActivityField(defs);
176
+ expect(Object.is(result[0], defs[0])).toBe(true);
177
+ expect(Object.is(result[1], defs[1])).toBe(true);
178
+ });
179
+
160
180
  test("does NOT add activity to top-level required when only in oneOf branch", () => {
161
181
  const defs = [
162
182
  makeDef("my_tool", {
@@ -124,9 +124,6 @@ mock.module("../providers/provider-send-message.js", () => ({
124
124
  getConfiguredProvider: async () => null,
125
125
  userMessage: () => ({}),
126
126
  }));
127
- mock.module("../runtime/routes/workspace-utils.js", () => ({
128
- isTextMimeType: () => true,
129
- }));
130
127
  mock.module("../skills/catalog-cache.js", () => ({
131
128
  getCatalog: async () => [],
132
129
  }));
@@ -136,7 +133,6 @@ mock.module("../skills/catalog-install.js", () => ({
136
133
  mock.module("../skills/managed-store.js", () => ({
137
134
  createManagedSkill: () => ({ created: true }),
138
135
  deleteManagedSkill: () => ({ deleted: true }),
139
- removeSkillsIndexEntry: () => {},
140
136
  validateManagedSkillId: () => null,
141
137
  }));
142
138
  mock.module("../memory/graph/capability-seed.js", () => ({
@@ -165,7 +161,6 @@ import { searchSkills } from "../daemon/handlers/skills.js";
165
161
  // Helpers
166
162
  // ---------------------------------------------------------------------------
167
163
 
168
-
169
164
  // ---------------------------------------------------------------------------
170
165
  // Tests
171
166
  // ---------------------------------------------------------------------------
@@ -91,6 +91,49 @@ describe("renderHistoryContent", () => {
91
91
  expect(renderHistoryContent(42).text).toBe("42");
92
92
  });
93
93
 
94
+ test("unwraps complete legacy external_content envelopes for plain string content", () => {
95
+ const output = renderHistoryContent(
96
+ '<external_content source="slack">\nVisible Slack text\n</external_content>',
97
+ );
98
+
99
+ expect(output.text).toBe("Visible Slack text");
100
+ expect(output.textSegments).toEqual(["Visible Slack text"]);
101
+ expect(output.contentOrder).toEqual(["text:0"]);
102
+ });
103
+
104
+ test("unwraps complete legacy external_content envelopes in text blocks", () => {
105
+ const output = renderHistoryContent([
106
+ {
107
+ type: "text",
108
+ text: '<external_content source="slack">\nVisible block text\n</external_content>',
109
+ },
110
+ { type: "text", text: "Plain follow-up." },
111
+ ]);
112
+
113
+ expect(output.text).toBe("Visible block text Plain follow-up.");
114
+ expect(output.textSegments).toEqual([
115
+ "Visible block text Plain follow-up.",
116
+ ]);
117
+ expect(output.contentOrder).toEqual(["text:0"]);
118
+ });
119
+
120
+ test("leaves malformed or mixed external_content text unchanged", () => {
121
+ const malformed =
122
+ '<external_content source="slack">Visible text</external_content>';
123
+ const mixed =
124
+ 'prefix <external_content source="slack">\nVisible text\n</external_content>';
125
+
126
+ const malformedOutput = renderHistoryContent([
127
+ { type: "text", text: malformed },
128
+ ]);
129
+ expect(malformedOutput.text).toBe(malformed);
130
+ expect(malformedOutput.textSegments).toEqual([malformed]);
131
+
132
+ const mixedOutput = renderHistoryContent(mixed);
133
+ expect(mixedOutput.text).toBe(mixed);
134
+ expect(mixedOutput.textSegments).toEqual([mixed]);
135
+ });
136
+
94
137
  test("preserves JSON object content as JSON string", () => {
95
138
  expect(renderHistoryContent({ foo: "bar" }).text).toBe('{"foo":"bar"}');
96
139
  });
@@ -103,10 +103,6 @@ describe("skill_load feature flag enforcement", () => {
103
103
  "Toggle email channel behavior",
104
104
  "Use the feature.",
105
105
  );
106
- writeFileSync(
107
- join(TEST_DIR, "skills", "SKILLS.md"),
108
- `- ${DECLARED_SKILL_ID}\n`,
109
- );
110
106
 
111
107
  _setOverridesForTesting({ [DECLARED_FLAG_KEY]: false });
112
108
 
@@ -124,10 +120,6 @@ describe("skill_load feature flag enforcement", () => {
124
120
  "Toggle email channel behavior",
125
121
  "Use the feature.",
126
122
  );
127
- writeFileSync(
128
- join(TEST_DIR, "skills", "SKILLS.md"),
129
- `- ${DECLARED_SKILL_ID}\n`,
130
- );
131
123
 
132
124
  _setOverridesForTesting({ [DECLARED_FLAG_KEY]: true });
133
125
 
@@ -144,10 +136,6 @@ describe("skill_load feature flag enforcement", () => {
144
136
  "Toggle email channel behavior",
145
137
  "Use the feature.",
146
138
  );
147
- writeFileSync(
148
- join(TEST_DIR, "skills", "SKILLS.md"),
149
- `- ${DECLARED_SKILL_ID}\n`,
150
- );
151
139
 
152
140
  // No overrides — uses registry defaults
153
141