@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
@@ -7,7 +7,12 @@
7
7
 
8
8
  import { readFileSync } from "node:fs";
9
9
 
10
- import { getConfig, loadRawConfig, saveRawConfig, setNestedValue } from "../../config/loader.js";
10
+ import {
11
+ getConfig,
12
+ loadRawConfig,
13
+ saveRawConfig,
14
+ setNestedValue,
15
+ } from "../../config/loader.js";
11
16
  import {
12
17
  getServiceMode,
13
18
  type Services,
@@ -26,9 +31,11 @@ import {
26
31
  getProvider,
27
32
  listActiveConnectionsByProvider,
28
33
  listConnections,
34
+ type OAuthProviderRow,
29
35
  } from "../../oauth/oauth-store.js";
30
36
  import { VellumPlatformClient } from "../../platform/client.js";
31
37
  import { withValidToken } from "../../security/token-manager.js";
38
+ import { matchHostPattern } from "../../tools/credentials/host-pattern-match.js";
32
39
  import { getLogger } from "../../util/logger.js";
33
40
  import { BadRequestError, InternalError, NotFoundError } from "./errors.js";
34
41
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
@@ -121,6 +128,82 @@ async function countManagedConnections(provider: string): Promise<number> {
121
128
  }
122
129
  }
123
130
 
131
+ function parseUrl(value: string | null | undefined): URL | undefined {
132
+ if (!value) return undefined;
133
+ try {
134
+ return new URL(value);
135
+ } catch {
136
+ return undefined;
137
+ }
138
+ }
139
+
140
+ function getAllowedRequestHostPatterns(
141
+ providerRow: OAuthProviderRow,
142
+ ): string[] {
143
+ const patterns: string[] = [];
144
+
145
+ if (providerRow.injectionTemplates) {
146
+ try {
147
+ const parsed = JSON.parse(providerRow.injectionTemplates) as unknown;
148
+ if (Array.isArray(parsed)) {
149
+ for (const entry of parsed) {
150
+ if (
151
+ entry &&
152
+ typeof entry === "object" &&
153
+ typeof (entry as { hostPattern?: unknown }).hostPattern === "string"
154
+ ) {
155
+ const hostPattern = (
156
+ entry as { hostPattern: string }
157
+ ).hostPattern.trim();
158
+ if (hostPattern) patterns.push(hostPattern);
159
+ }
160
+ }
161
+ }
162
+ } catch {
163
+ // Fall back to the provider's base URL host below.
164
+ }
165
+ }
166
+
167
+ if (patterns.length === 0) {
168
+ const baseUrl = parseUrl(providerRow.baseUrl);
169
+ if (baseUrl) patterns.push(baseUrl.hostname);
170
+ }
171
+
172
+ return [...new Set(patterns)];
173
+ }
174
+
175
+ function assertOAuthRequestUrlAllowed(
176
+ providerRow: OAuthProviderRow,
177
+ parsedUrl: URL,
178
+ ): void {
179
+ const providerBaseUrl = parseUrl(providerRow.baseUrl);
180
+ const allowedProtocol = providerBaseUrl?.protocol ?? "https:";
181
+ if (parsedUrl.protocol !== allowedProtocol) {
182
+ throw new BadRequestError(
183
+ `OAuth request URL for "${providerRow.provider}" must use ${allowedProtocol.replace(/:$/, "")}.`,
184
+ );
185
+ }
186
+
187
+ const allowedHostPatterns = getAllowedRequestHostPatterns(providerRow);
188
+ if (allowedHostPatterns.length === 0) {
189
+ throw new BadRequestError(
190
+ `OAuth provider "${providerRow.provider}" does not define an allowed request host.`,
191
+ );
192
+ }
193
+
194
+ const allowed = allowedHostPatterns.some(
195
+ (pattern) =>
196
+ matchHostPattern(parsedUrl.hostname, pattern, {
197
+ includeApexForWildcard: true,
198
+ }) !== "none",
199
+ );
200
+ if (!allowed) {
201
+ throw new BadRequestError(
202
+ `OAuth request URL host "${parsedUrl.hostname}" is not allowed for "${providerRow.provider}". Allowed hosts: ${allowedHostPatterns.join(", ")}.`,
203
+ );
204
+ }
205
+ }
206
+
124
207
  // ---------------------------------------------------------------------------
125
208
  // Disconnect handler
126
209
  // ---------------------------------------------------------------------------
@@ -176,7 +259,9 @@ async function handleDisconnect({ body = {} }: RouteHandlerArgs) {
176
259
  accountLabel = match.account_label;
177
260
  } else {
178
261
  if (entries.length === 0) {
179
- throw new NotFoundError(`No active connections found for "${b.provider}".`);
262
+ throw new NotFoundError(
263
+ `No active connections found for "${b.provider}".`,
264
+ );
180
265
  }
181
266
  if (entries.length > 1) {
182
267
  throw new BadRequestError(
@@ -235,7 +320,9 @@ async function handleDisconnect({ body = {} }: RouteHandlerArgs) {
235
320
  } else {
236
321
  const active = listActiveConnectionsByProvider(b.provider);
237
322
  if (active.length === 0) {
238
- throw new NotFoundError(`No active connections found for "${b.provider}".`);
323
+ throw new NotFoundError(
324
+ `No active connections found for "${b.provider}".`,
325
+ );
239
326
  }
240
327
  if (active.length > 1) {
241
328
  throw new BadRequestError(
@@ -582,11 +669,7 @@ async function handleToken({ body = {} }: RouteHandlerArgs) {
582
669
  tokenOpts = { connectionId: conn.id };
583
670
  }
584
671
 
585
- const token = await withValidToken(
586
- b.provider,
587
- async (t) => t,
588
- tokenOpts,
589
- );
672
+ const token = await withValidToken(b.provider, async (t) => t, tokenOpts);
590
673
 
591
674
  return { ok: true, token };
592
675
  }
@@ -666,6 +749,7 @@ async function handleRequest({ body = {} }: RouteHandlerArgs) {
666
749
 
667
750
  if (b.url.startsWith("http://") || b.url.startsWith("https://")) {
668
751
  const parsed = new URL(b.url);
752
+ assertOAuthRequestUrlAllowed(providerRow, parsed);
669
753
  baseUrl = `${parsed.protocol}//${parsed.host}`;
670
754
  requestPath = parsed.pathname;
671
755
  for (const [key, value] of parsed.searchParams.entries()) {
@@ -717,7 +801,12 @@ async function handleRequest({ body = {} }: RouteHandlerArgs) {
717
801
  const query: Record<string, string | string[]> = { ...queryFromUrl };
718
802
 
719
803
  // Use pre-parsed data from CLI, or fall back to raw data string for direct API callers
720
- const resolvedData = b.parsed_data !== undefined ? b.parsed_data : b.data !== undefined ? readBodyData(b.data) : undefined;
804
+ const resolvedData =
805
+ b.parsed_data !== undefined
806
+ ? b.parsed_data
807
+ : b.data !== undefined
808
+ ? readBodyData(b.data)
809
+ : undefined;
721
810
 
722
811
  if (resolvedData !== undefined) {
723
812
  const rawBody = resolvedData;
@@ -855,7 +944,9 @@ async function handleManagedConnect({ body = {} }: RouteHandlerArgs) {
855
944
  return { ok: true, connect_url: result.connect_url };
856
945
  }
857
946
 
858
- async function handleManagedConnectPoll({ queryParams = {} }: RouteHandlerArgs) {
947
+ async function handleManagedConnectPoll({
948
+ queryParams = {},
949
+ }: RouteHandlerArgs) {
859
950
  const provider = queryParams.provider;
860
951
  if (!provider) throw new BadRequestError("provider query param is required");
861
952
 
@@ -894,7 +985,8 @@ export const ROUTES: RouteDefinition[] = [
894
985
  endpoint: "oauth/mode",
895
986
  method: "GET",
896
987
  summary: "Get OAuth mode",
897
- description: "Get the current OAuth mode (managed or your-own) for a provider.",
988
+ description:
989
+ "Get the current OAuth mode (managed or your-own) for a provider.",
898
990
  tags: ["oauth"],
899
991
  requirePolicyEnforcement: true,
900
992
  queryParams: [
@@ -913,8 +1005,7 @@ export const ROUTES: RouteDefinition[] = [
913
1005
  method: "POST",
914
1006
  policyKey: "oauth/mode.set",
915
1007
  summary: "Set OAuth mode",
916
- description:
917
- "Set the OAuth mode (managed or your-own) for a provider.",
1008
+ description: "Set the OAuth mode (managed or your-own) for a provider.",
918
1009
  tags: ["oauth"],
919
1010
  requirePolicyEnforcement: true,
920
1011
  handler: handleModeSet,
@@ -955,8 +1046,7 @@ export const ROUTES: RouteDefinition[] = [
955
1046
  method: "POST",
956
1047
  policyKey: "oauth/token",
957
1048
  summary: "Get OAuth token",
958
- description:
959
- "Retrieve a valid OAuth access token for a BYO-mode provider.",
1049
+ description: "Retrieve a valid OAuth access token for a BYO-mode provider.",
960
1050
  tags: ["oauth"],
961
1051
  requirePolicyEnforcement: true,
962
1052
  handler: handleToken,
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Transport-agnostic routes for OAuth lifecycle events.
3
+ *
4
+ * Allows CLI commands to notify the daemon when OAuth state changes
5
+ * (e.g. after `assistant oauth connect`) so the daemon can refresh its
6
+ * cached config and credential state.
7
+ */
8
+
9
+ import { z } from "zod";
10
+
11
+ import { getConfig, invalidateConfigCache } from "../../config/loader.js";
12
+ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
13
+
14
+ // ── Handlers ──────────────────────────────────────────────────────────
15
+
16
+ function handleOAuthConnectionChanged(_args: RouteHandlerArgs): {
17
+ refreshed: boolean;
18
+ } {
19
+ invalidateConfigCache();
20
+ // Force re-read from disk so subsequent resolveOAuthConnection() calls
21
+ // in this process see the current mode setting.
22
+ getConfig();
23
+ return { refreshed: true };
24
+ }
25
+
26
+ // ── Routes ────────────────────────────────────────────────────────────
27
+
28
+ export const ROUTES: RouteDefinition[] = [
29
+ {
30
+ operationId: "oauth_connection_changed",
31
+ endpoint: "oauth/connection-changed",
32
+ method: "POST",
33
+ policyKey: "oauth/connection-changed",
34
+ handler: handleOAuthConnectionChanged,
35
+ summary: "Notify the assistant that an OAuth connection changed",
36
+ description:
37
+ "Invalidates the config cache so the assistant picks up mode and credential changes immediately.",
38
+ tags: ["oauth"],
39
+ responseBody: z.object({
40
+ refreshed: z.boolean(),
41
+ }),
42
+ },
43
+ ];
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Route handler for resolving pending question prompts.
3
+ *
4
+ * POST /v1/question-response — a client (UI or remote channel) submits the
5
+ * user's selection for a pending ask-question interaction registered by
6
+ * {@link QuestionPrompter}. Two top-level shapes are accepted:
7
+ *
8
+ * - `kind: "submit"` carries a `responses` array — one entry per question in
9
+ * the original batch. The web client builds this locally and POSTs it once
10
+ * the user is done revising the card.
11
+ * - `kind: "close"` records that the user dismissed the card without
12
+ * answering; every entry is reported as `skipped`.
13
+ *
14
+ * For backwards-compat we also accept the prior single-question shape
15
+ * (`{ kind: "option" | "free_text", ... }`) as syntactic sugar for a
16
+ * one-element batch. That branch only succeeds against a single-question
17
+ * batch — multi-question batches reject it with a helpful error.
18
+ *
19
+ * Cross-talk safety: pending interactions of other kinds (`confirmation`,
20
+ * `secret`, host_*, etc.) return 404 here rather than being mis-resolved.
21
+ */
22
+ import { z } from "zod";
23
+
24
+ import {
25
+ buildBatchEntries,
26
+ type QuestionBatchMetadata,
27
+ type QuestionBatchSubmission,
28
+ QuestionBatchValidationError,
29
+ type QuestionPromptResult,
30
+ } from "../../permissions/question-prompter.js";
31
+ import { getLogger } from "../../util/logger.js";
32
+ import * as pendingInteractions from "../pending-interactions.js";
33
+ import { BadRequestError, NotFoundError } from "./errors.js";
34
+ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
35
+
36
+ const log = getLogger("question-routes");
37
+
38
+ // ── Batched (current) body shape ────────────────────────────────────
39
+
40
+ const SubmitEntry = z.discriminatedUnion("kind", [
41
+ z.object({
42
+ questionId: z.string(),
43
+ kind: z.literal("option"),
44
+ optionId: z.string(),
45
+ }),
46
+ z.object({
47
+ questionId: z.string(),
48
+ kind: z.literal("free_text"),
49
+ text: z.string(),
50
+ }),
51
+ z.object({
52
+ questionId: z.string(),
53
+ kind: z.literal("skip"),
54
+ }),
55
+ ]);
56
+
57
+ const SubmitBody = z.object({
58
+ requestId: z.string(),
59
+ kind: z.literal("submit"),
60
+ responses: z.array(SubmitEntry).min(1),
61
+ });
62
+
63
+ const CloseBody = z.object({
64
+ requestId: z.string(),
65
+ kind: z.literal("close"),
66
+ });
67
+
68
+ // ── Legacy single-question body shape (sugar for one-element batch) ──
69
+
70
+ const LegacyOptionBody = z.object({
71
+ requestId: z.string(),
72
+ kind: z.literal("option"),
73
+ optionId: z.string(),
74
+ });
75
+
76
+ const LegacyFreeTextBody = z.object({
77
+ requestId: z.string(),
78
+ kind: z.literal("free_text"),
79
+ text: z.string(),
80
+ });
81
+
82
+ // All four variants are mutually exclusive by their `kind` literal, so use
83
+ // `discriminatedUnion` rather than plain `union`. The generated OpenAPI then
84
+ // emits `oneOf` for the body (matching the pre-batched-shape spec) instead
85
+ // of the looser `anyOf` that `z.union` produces.
86
+ const QuestionResponseBody = z.discriminatedUnion("kind", [
87
+ SubmitBody,
88
+ CloseBody,
89
+ LegacyOptionBody,
90
+ LegacyFreeTextBody,
91
+ ]);
92
+
93
+ type SubmitBody = z.infer<typeof SubmitBody>;
94
+ type CloseBody = z.infer<typeof CloseBody>;
95
+ type LegacyOptionBody = z.infer<typeof LegacyOptionBody>;
96
+ type LegacyFreeTextBody = z.infer<typeof LegacyFreeTextBody>;
97
+ type QuestionResponseBody = z.infer<typeof QuestionResponseBody>;
98
+
99
+ /**
100
+ * POST /v1/question-response — resolve a pending ask-question interaction.
101
+ */
102
+ function handleQuestionResponse({ body }: RouteHandlerArgs) {
103
+ const parsed = QuestionResponseBody.safeParse(body);
104
+ if (!parsed.success) {
105
+ throw new BadRequestError(
106
+ `Invalid question response body: ${parsed.error.message}`,
107
+ );
108
+ }
109
+
110
+ const response: QuestionResponseBody = parsed.data;
111
+ const { requestId } = response;
112
+
113
+ const interaction = pendingInteractions.get(requestId);
114
+ if (!interaction || interaction.kind !== "question") {
115
+ log.warn(
116
+ { requestId, foundKind: interaction?.kind },
117
+ "Question response for unknown or wrong-kind requestId",
118
+ );
119
+ throw new NotFoundError(
120
+ "No pending question interaction found for this requestId",
121
+ );
122
+ }
123
+
124
+ // Build + validate the result BEFORE touching `pendingInteractions`, so a
125
+ // bad payload leaves the pending interaction (and its timer) intact and the
126
+ // user gets another chance to submit a correct batch.
127
+ let result: QuestionPromptResult;
128
+ try {
129
+ if (response.kind === "close") {
130
+ const { orderedIds } = readBatchMetadata(interaction);
131
+ result = {
132
+ entries: orderedIds.map((id) => ({
133
+ questionId: id,
134
+ decision: "skipped" as const,
135
+ })),
136
+ overall: "closed",
137
+ };
138
+ } else {
139
+ const submissions = buildSubmissions(response, interaction);
140
+ result = buildCompletedResult(submissions, interaction);
141
+ }
142
+ } catch (err) {
143
+ if (err instanceof QuestionBatchValidationError) {
144
+ throw new BadRequestError(err.message);
145
+ }
146
+ throw err;
147
+ }
148
+
149
+ // Validation passed — deregister now to clear the prompter timer, then
150
+ // hand the result to the prompter's caller via rpcResolve.
151
+ pendingInteractions.resolve(requestId);
152
+
153
+ log.info(
154
+ {
155
+ requestId,
156
+ overall: result.overall,
157
+ conversationId: interaction.conversationId,
158
+ },
159
+ "Question resolved",
160
+ );
161
+
162
+ (interaction.rpcResolve as
163
+ | ((value: QuestionPromptResult) => void)
164
+ | undefined)?.(result);
165
+
166
+ return { success: true };
167
+ }
168
+
169
+ /**
170
+ * Normalize the incoming body to a `QuestionBatchSubmission[]` for the
171
+ * submit/legacy paths. Returns `null` for the `close` path (no submissions).
172
+ */
173
+ function buildSubmissions(
174
+ body: SubmitBody | LegacyOptionBody | LegacyFreeTextBody,
175
+ interaction: ReturnType<typeof pendingInteractions.get>,
176
+ ): QuestionBatchSubmission[] {
177
+ if (body.kind === "submit") return body.responses;
178
+
179
+ // Legacy single-question shim: synthesize a one-element batch. The
180
+ // prompter stashed the ordered ids on the interaction metadata so we can
181
+ // pick the (single) target questionId here.
182
+ const { orderedIds } = readBatchMetadata(interaction);
183
+ if (orderedIds.length === 0) {
184
+ throw new QuestionBatchValidationError(
185
+ "Legacy single-question payload requires a registered batch with at least one question",
186
+ );
187
+ }
188
+ if (orderedIds.length > 1) {
189
+ throw new QuestionBatchValidationError(
190
+ 'Legacy single-question payload cannot answer a multi-question batch; submit `{ kind: "submit", responses: [...] }` covering every question instead.',
191
+ );
192
+ }
193
+ const questionId = orderedIds[0]!;
194
+ if (body.kind === "option") {
195
+ return [{ questionId, kind: "option", optionId: body.optionId }];
196
+ }
197
+ return [{ questionId, kind: "free_text", text: body.text }];
198
+ }
199
+
200
+ /**
201
+ * Build a `completed` QuestionPromptResult from a batched submission and the
202
+ * per-question metadata the prompter stashed on the interaction. Delegates
203
+ * the validation + ordering loop to {@link buildBatchEntries} so the
204
+ * prompter and the route share a single implementation.
205
+ */
206
+ function buildCompletedResult(
207
+ submissions: QuestionBatchSubmission[],
208
+ interaction: ReturnType<typeof pendingInteractions.get>,
209
+ ): QuestionPromptResult {
210
+ const { orderedIds, optionsById } = readBatchMetadata(interaction);
211
+ if (orderedIds.length === 0) {
212
+ throw new QuestionBatchValidationError(
213
+ "No registered question ids for this batch",
214
+ );
215
+ }
216
+ const entries = buildBatchEntries(
217
+ orderedIds,
218
+ (qid, oid) => (optionsById[qid] ?? []).includes(oid),
219
+ new Set(Object.keys(optionsById)),
220
+ submissions,
221
+ );
222
+ return { entries, overall: "completed" };
223
+ }
224
+
225
+ /**
226
+ * Pull the prompter-stashed batch bookkeeping off a pending interaction.
227
+ * Returns empty defaults if the metadata is absent.
228
+ */
229
+ function readBatchMetadata(
230
+ interaction: ReturnType<typeof pendingInteractions.get>,
231
+ ): QuestionBatchMetadata {
232
+ const meta = interaction?.metadata as Partial<QuestionBatchMetadata> | undefined;
233
+ return {
234
+ orderedIds: meta?.orderedIds ?? [],
235
+ optionsById: meta?.optionsById ?? {},
236
+ };
237
+ }
238
+
239
+ // ---------------------------------------------------------------------------
240
+ // Route definitions
241
+ // ---------------------------------------------------------------------------
242
+
243
+ export const ROUTES: RouteDefinition[] = [
244
+ {
245
+ operationId: "question_response",
246
+ endpoint: "question-response",
247
+ method: "POST",
248
+ handler: handleQuestionResponse,
249
+ requireGuardian: true,
250
+ summary: "Resolve a pending ask-question prompt",
251
+ description:
252
+ "Submit the user's batched response (or close the card) for a pending question prompt by requestId. Legacy single-question payloads remain accepted as syntactic sugar for a one-element batch.",
253
+ tags: ["approvals"],
254
+ requestBody: QuestionResponseBody,
255
+ responseBody: z.object({
256
+ success: z.boolean(),
257
+ }),
258
+ },
259
+ ];
@@ -13,9 +13,7 @@ import {
13
13
  getConversation,
14
14
  updateConversationTitle,
15
15
  } from "../../memory/conversation-crud.js";
16
- import { getLogger } from "../../util/logger.js";
17
- import { buildAssistantEvent } from "../assistant-event.js";
18
- import { assistantEventHub } from "../assistant-event-hub.js";
16
+ import { publishConversationTitleChanged } from "../sync/resource-sync-events.js";
19
17
  import { BadRequestError, NotFoundError } from "./errors.js";
20
18
  import type { RouteDefinition } from "./types.js";
21
19
 
@@ -24,8 +22,6 @@ const RenameConversationBody = z.object({
24
22
  title: z.string().min(1),
25
23
  });
26
24
 
27
- const log = getLogger("rename-conversation-routes");
28
-
29
25
  export const ROUTES: RouteDefinition[] = [
30
26
  {
31
27
  operationId: "rename_conversation",
@@ -50,34 +46,7 @@ export const ROUTES: RouteDefinition[] = [
50
46
 
51
47
  updateConversationTitle(conversationId, title, 0);
52
48
 
53
- assistantEventHub
54
- .publish(
55
- buildAssistantEvent(
56
- {
57
- type: "conversation_title_updated",
58
- conversationId,
59
- title,
60
- },
61
- conversationId,
62
- ),
63
- )
64
- .catch((err) => {
65
- log.warn({ err }, "Failed to publish conversation_title_updated");
66
- });
67
-
68
- assistantEventHub
69
- .publish(
70
- buildAssistantEvent({
71
- type: "conversation_list_invalidated",
72
- reason: "renamed",
73
- }),
74
- )
75
- .catch((err) => {
76
- log.warn(
77
- { err },
78
- "Failed to publish conversation_list_invalidated for rename",
79
- );
80
- });
49
+ publishConversationTitleChanged(conversationId, title);
81
50
 
82
51
  return { ok: true };
83
52
  },
@@ -78,7 +78,8 @@ function handleCreateSchedule(body: Record<string, unknown>) {
78
78
  const expression =
79
79
  typeof body.expression === "string" ? body.expression.trim() : "";
80
80
  const message = typeof body.message === "string" ? body.message : "";
81
- const timezoneRaw = typeof body.timezone === "string" ? body.timezone.trim() : "";
81
+ const timezoneRaw =
82
+ typeof body.timezone === "string" ? body.timezone.trim() : "";
82
83
  const timezone = timezoneRaw === "" ? null : timezoneRaw;
83
84
  const enabled = body.enabled !== false;
84
85
  const mode = (body.mode as string | undefined) ?? "execute";
@@ -298,16 +299,12 @@ export const ROUTES: RouteDefinition[] = [
298
299
  .boolean()
299
300
  .describe("Whether the schedule starts active (default true)")
300
301
  .optional(),
301
- mode: z
302
- .string()
303
- .describe("Currently must be 'execute'")
304
- .optional(),
302
+ mode: z.string().describe("Currently must be 'execute'").optional(),
305
303
  }),
306
304
  responseBody: z.object({
307
305
  schedules: z.array(z.unknown()).describe("Updated schedule list"),
308
306
  }),
309
- handler: ({ body }: RouteHandlerArgs) =>
310
- handleCreateSchedule(body ?? {}),
307
+ handler: ({ body }: RouteHandlerArgs) => handleCreateSchedule(body ?? {}),
311
308
  },
312
309
  {
313
310
  operationId: "listScheduleRuns",