@vellumai/assistant 0.8.2 → 0.8.4

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 (503) hide show
  1. package/ARCHITECTURE.md +11 -12
  2. package/docker-entrypoint.sh +13 -2
  3. package/docker-init-apt-root.sh +79 -6
  4. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  5. package/openapi.yaml +945 -36
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
  8. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  9. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +272 -0
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  14. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  15. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  16. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  17. package/src/__tests__/compaction-events.test.ts +1 -1
  18. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  19. package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
  20. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  22. package/src/__tests__/config-watcher.test.ts +1 -1
  23. package/src/__tests__/context-token-estimator.test.ts +112 -57
  24. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  27. package/src/__tests__/conversation-agent-loop.test.ts +77 -3
  28. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  29. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  30. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  32. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  33. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  34. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  35. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  36. package/src/__tests__/conversation-pairing.test.ts +2 -2
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  39. package/src/__tests__/conversation-queue.test.ts +1 -1
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
  41. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  42. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  43. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  44. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  45. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  46. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  47. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  48. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  49. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  50. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  51. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  52. package/src/__tests__/date-context.test.ts +45 -0
  53. package/src/__tests__/dm-backfill.test.ts +64 -0
  54. package/src/__tests__/dm-persistence.test.ts +33 -0
  55. package/src/__tests__/document-find-replace.test.ts +501 -0
  56. package/src/__tests__/external-plugin-loader.test.ts +91 -19
  57. package/src/__tests__/first-greeting.test.ts +23 -2
  58. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  59. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  60. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  61. package/src/__tests__/heartbeat-service.test.ts +24 -164
  62. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  63. package/src/__tests__/host-app-control-proxy.test.ts +241 -0
  64. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  66. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  67. package/src/__tests__/host-file-proxy.test.ts +8 -1
  68. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  69. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  70. package/src/__tests__/identity-routes.test.ts +57 -0
  71. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  72. package/src/__tests__/injector-background-turn.test.ts +153 -0
  73. package/src/__tests__/injector-chain.test.ts +7 -0
  74. package/src/__tests__/injector-document-comments.test.ts +378 -0
  75. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  76. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
  77. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  78. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  79. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  80. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  81. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  82. package/src/__tests__/llm-catalog-parity.test.ts +3 -0
  83. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  84. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  85. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  86. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
  87. package/src/__tests__/llm-resolver.test.ts +340 -3
  88. package/src/__tests__/log-export-routes.test.ts +99 -2
  89. package/src/__tests__/managed-profile-guard.test.ts +10 -0
  90. package/src/__tests__/message-queue-steer.test.ts +114 -0
  91. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  92. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  93. package/src/__tests__/notification-deep-link.test.ts +15 -0
  94. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  95. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  96. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  97. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  98. package/src/__tests__/openai-provider.test.ts +323 -3
  99. package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
  100. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  101. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  102. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  103. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  104. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  105. package/src/__tests__/platform-proxy-context.test.ts +6 -1
  106. package/src/__tests__/platform.test.ts +0 -3
  107. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  108. package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
  109. package/src/__tests__/plugin-types.test.ts +2 -2
  110. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  111. package/src/__tests__/process-message-display-content.test.ts +21 -16
  112. package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
  113. package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  115. package/src/__tests__/server-history-render.test.ts +83 -4
  116. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  117. package/src/__tests__/system-prompt.test.ts +57 -101
  118. package/src/__tests__/terminal-tools.test.ts +11 -1
  119. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  120. package/src/__tests__/thread-backfill.test.ts +370 -22
  121. package/src/__tests__/tool-executor.test.ts +90 -1
  122. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  123. package/src/__tests__/twilio-routes.test.ts +1 -1
  124. package/src/__tests__/web-fetch.test.ts +2 -2
  125. package/src/__tests__/workspace-git-service.test.ts +88 -5
  126. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  127. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  128. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  129. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  130. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  131. package/src/a2a/__tests__/task-store.test.ts +246 -0
  132. package/src/a2a/agent-card.ts +58 -0
  133. package/src/a2a/feature-gate.ts +8 -0
  134. package/src/a2a/protocol-constants.ts +21 -0
  135. package/src/a2a/protocol-errors.ts +50 -0
  136. package/src/a2a/protocol-types.ts +162 -0
  137. package/src/a2a/task-store.ts +168 -0
  138. package/src/agent/attachments.ts +1 -0
  139. package/src/agent/loop.ts +208 -22
  140. package/src/background-wake/next-wake.test.ts +289 -0
  141. package/src/background-wake/next-wake.ts +172 -0
  142. package/src/browser/operations.ts +15 -0
  143. package/src/channels/config.ts +9 -0
  144. package/src/channels/types.ts +14 -0
  145. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  146. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  147. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  148. package/src/cli/commands/__tests__/schedules.test.ts +469 -0
  149. package/src/cli/commands/conversations.ts +128 -1
  150. package/src/cli/commands/inference-providers.ts +147 -1
  151. package/src/cli/commands/memory-v2.ts +308 -0
  152. package/src/cli/commands/notifications.ts +89 -37
  153. package/src/cli/commands/plugins.ts +67 -0
  154. package/src/cli/commands/schedules.ts +297 -5
  155. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  156. package/src/cli/lib/install-from-github.ts +8 -9
  157. package/src/cli/lib/search-plugins.ts +163 -0
  158. package/src/cli/program.ts +14 -0
  159. package/src/cli/utils/conversation-id.ts +17 -5
  160. package/src/config/assistant-feature-flags.ts +24 -54
  161. package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
  162. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  163. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  164. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  165. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  166. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  167. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  168. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  169. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  170. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  171. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  172. package/src/config/bundled-tool-registry.ts +22 -12
  173. package/src/config/call-site-defaults.ts +124 -0
  174. package/src/config/feature-flag-registry.json +111 -23
  175. package/src/config/llm-resolver.ts +66 -1
  176. package/src/config/schema.ts +2 -0
  177. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
  178. package/src/config/schemas/call-site-catalog.ts +21 -0
  179. package/src/config/schemas/channels.ts +9 -0
  180. package/src/config/schemas/conversations.ts +10 -0
  181. package/src/config/schemas/heartbeat.ts +14 -0
  182. package/src/config/schemas/llm.ts +4 -3
  183. package/src/config/schemas/memory-retrospective.ts +1 -1
  184. package/src/config/schemas/memory-v2.ts +51 -4
  185. package/src/config/schemas/memory.ts +3 -1
  186. package/src/config/seed-inference-profiles.ts +99 -29
  187. package/src/context/compactor.ts +80 -13
  188. package/src/context/token-estimator.ts +72 -31
  189. package/src/context/window-manager.ts +25 -0
  190. package/src/credential-health/credential-health-service.ts +34 -19
  191. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
  192. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  193. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  194. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  195. package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
  196. package/src/daemon/conversation-agent-loop.ts +252 -56
  197. package/src/daemon/conversation-lifecycle.ts +142 -116
  198. package/src/daemon/conversation-messaging.ts +3 -0
  199. package/src/daemon/conversation-process.ts +273 -0
  200. package/src/daemon/conversation-queue-manager.ts +14 -0
  201. package/src/daemon/conversation-runtime-assembly.ts +144 -75
  202. package/src/daemon/conversation-slash.ts +37 -5
  203. package/src/daemon/conversation-surfaces.ts +45 -2
  204. package/src/daemon/conversation-tool-setup.ts +7 -0
  205. package/src/daemon/conversation.ts +42 -12
  206. package/src/daemon/date-context.ts +40 -0
  207. package/src/daemon/first-greeting.ts +10 -0
  208. package/src/daemon/guardian-action-generators.ts +1 -125
  209. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  210. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  211. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  212. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  213. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  214. package/src/daemon/handlers/config-a2a.ts +449 -0
  215. package/src/daemon/handlers/config-model.test.ts +1 -0
  216. package/src/daemon/handlers/conversations.ts +80 -0
  217. package/src/daemon/handlers/shared.ts +92 -29
  218. package/src/daemon/host-app-control-proxy.ts +69 -18
  219. package/src/daemon/host-bash-proxy.ts +1 -1
  220. package/src/daemon/host-cu-proxy.ts +1 -1
  221. package/src/daemon/host-file-proxy.ts +1 -1
  222. package/src/daemon/host-proxy-preactivation.ts +85 -18
  223. package/src/daemon/host-transfer-proxy.ts +1 -1
  224. package/src/daemon/lifecycle.ts +67 -65
  225. package/src/daemon/memory-v2-startup.ts +49 -13
  226. package/src/daemon/message-protocol.ts +4 -0
  227. package/src/daemon/message-types/conversations.ts +8 -0
  228. package/src/daemon/message-types/document-comments.ts +50 -0
  229. package/src/daemon/message-types/messages.ts +68 -1
  230. package/src/daemon/message-types/notifications.ts +21 -0
  231. package/src/daemon/message-types/surfaces.ts +3 -1
  232. package/src/daemon/message-types/web-activity.ts +57 -0
  233. package/src/daemon/pkb-reminder-builder.test.ts +10 -53
  234. package/src/daemon/pkb-reminder-builder.ts +4 -19
  235. package/src/daemon/plugin-source-watcher.ts +135 -3
  236. package/src/daemon/process-message.ts +72 -12
  237. package/src/daemon/query-complexity-router.ts +75 -0
  238. package/src/daemon/skill-memory-refresh.ts +5 -1
  239. package/src/daemon/trust-context.ts +6 -0
  240. package/src/daemon/wake-target-adapter.ts +2 -0
  241. package/src/documents/document-comments-store.test.ts +338 -0
  242. package/src/documents/document-comments-store.ts +237 -0
  243. package/src/documents/document-store.ts +202 -0
  244. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  245. package/src/export/transcript-formatter.ts +54 -20
  246. package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
  247. package/src/heartbeat/heartbeat-service.ts +35 -191
  248. package/src/home/__tests__/feed-types.test.ts +40 -0
  249. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  250. package/src/home/feed-types.ts +20 -3
  251. package/src/home/home-content-refresh.ts +52 -0
  252. package/src/home/home-greeting-cache.ts +69 -0
  253. package/src/home/home-greeting.ts +94 -0
  254. package/src/home/suggested-prompts.ts +177 -9
  255. package/src/ipc/cli-client.ts +147 -45
  256. package/src/memory/__tests__/conversation-queries.test.ts +220 -0
  257. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  258. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  259. package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
  260. package/src/memory/conversation-crud.ts +133 -43
  261. package/src/memory/conversation-queries.ts +87 -1
  262. package/src/memory/conversation-title-service.ts +26 -4
  263. package/src/memory/db-init.ts +22 -0
  264. package/src/memory/delivery-crud.ts +41 -0
  265. package/src/memory/delivery-status.ts +141 -15
  266. package/src/memory/external-conversation-store.ts +32 -1
  267. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
  268. package/src/memory/graph/conversation-graph-memory.ts +18 -6
  269. package/src/memory/graph/tools.ts +6 -37
  270. package/src/memory/invite-store.ts +53 -0
  271. package/src/memory/jobs-worker.ts +21 -1
  272. package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
  273. package/src/memory/llm-request-log-store.ts +92 -1
  274. package/src/memory/memory-retrospective-constants.ts +28 -0
  275. package/src/memory/memory-retrospective-enqueue.ts +4 -22
  276. package/src/memory/memory-retrospective-job.ts +438 -21
  277. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  278. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  279. package/src/memory/migrations/100-core-tables.ts +1 -0
  280. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  281. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  282. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  283. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  284. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  285. package/src/memory/migrations/253-document-comments.ts +47 -0
  286. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  287. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  288. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  289. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  290. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  291. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  292. package/src/memory/migrations/index.ts +20 -0
  293. package/src/memory/migrations/registry.ts +33 -0
  294. package/src/memory/onboarding-events-store.ts +7 -0
  295. package/src/memory/schema/a2a.ts +15 -0
  296. package/src/memory/schema/calls.ts +1 -0
  297. package/src/memory/schema/conversations.ts +3 -0
  298. package/src/memory/schema/index.ts +1 -0
  299. package/src/memory/schema/inference.ts +2 -0
  300. package/src/memory/schema/infrastructure.ts +2 -0
  301. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  302. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  303. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  304. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  305. package/src/memory/v2/__tests__/injection.test.ts +221 -17
  306. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  307. package/src/memory/v2/__tests__/router.test.ts +489 -1
  308. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  309. package/src/memory/v2/activation-store.ts +14 -16
  310. package/src/memory/v2/cli-command-content.ts +19 -0
  311. package/src/memory/v2/cli-command-store.ts +304 -0
  312. package/src/memory/v2/consolidation-job.ts +14 -0
  313. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  314. package/src/memory/v2/injection-events.ts +101 -0
  315. package/src/memory/v2/injection.ts +69 -29
  316. package/src/memory/v2/page-index.ts +246 -19
  317. package/src/memory/v2/page-store.ts +18 -0
  318. package/src/memory/v2/router.ts +209 -55
  319. package/src/memory/v2/static-context.ts +4 -4
  320. package/src/memory/v2/types.ts +23 -0
  321. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  322. package/src/messaging/providers/a2a/deliver.ts +156 -0
  323. package/src/messaging/providers/gmail/client.ts +9 -2
  324. package/src/messaging/providers/index.ts +18 -3
  325. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  327. package/src/messaging/providers/slack/adapter.ts +178 -25
  328. package/src/messaging/providers/slack/api.test.ts +54 -0
  329. package/src/messaging/providers/slack/api.ts +119 -3
  330. package/src/messaging/providers/slack/client.ts +12 -0
  331. package/src/messaging/providers/slack/deep-link.ts +20 -1
  332. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  333. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  334. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  335. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  336. package/src/messaging/providers/slack/send.test.ts +77 -0
  337. package/src/messaging/providers/slack/send.ts +8 -2
  338. package/src/messaging/providers/slack/types.ts +14 -0
  339. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  340. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  341. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  342. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
  343. package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
  344. package/src/notifications/adapters/macos.ts +12 -2
  345. package/src/notifications/broadcaster.ts +29 -4
  346. package/src/notifications/conversation-seed-composer.ts +14 -2
  347. package/src/notifications/copy-composer.ts +17 -64
  348. package/src/notifications/decision-engine.ts +111 -44
  349. package/src/notifications/deferred-emit.ts +135 -0
  350. package/src/notifications/deterministic-checks.ts +96 -0
  351. package/src/notifications/emit-signal.ts +10 -1
  352. package/src/notifications/home-feed-side-effect.ts +136 -27
  353. package/src/notifications/signal.ts +0 -4
  354. package/src/notifications/types.ts +8 -0
  355. package/src/oauth/connect-orchestrator.ts +3 -0
  356. package/src/oauth/credential-token-resolver.ts +2 -0
  357. package/src/oauth/manual-token-connection.ts +19 -0
  358. package/src/oauth/oauth-store.ts +12 -0
  359. package/src/oauth/platform-connection.test.ts +43 -3
  360. package/src/oauth/platform-connection.ts +13 -4
  361. package/src/oauth/seed-providers.ts +22 -0
  362. package/src/permissions/prompter.ts +5 -2
  363. package/src/permissions/secret-prompter.ts +4 -1
  364. package/src/plugins/defaults/injectors.ts +118 -26
  365. package/src/plugins/external-plugin-loader.ts +82 -10
  366. package/src/plugins/types.ts +16 -7
  367. package/src/prompts/__tests__/system-prompt.test.ts +44 -45
  368. package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
  369. package/src/prompts/normalize-onboarding.ts +40 -0
  370. package/src/prompts/sections.ts +32 -14
  371. package/src/prompts/system-prompt.ts +105 -76
  372. package/src/prompts/template-detection.ts +37 -0
  373. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  374. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  375. package/src/prompts/templates/VOICE.md +3 -0
  376. package/src/prompts/templates/system-sections.ts +51 -10
  377. package/src/providers/__tests__/inference.test.ts +2 -0
  378. package/src/providers/anthropic/client.ts +132 -5
  379. package/src/providers/call-site-routing.ts +24 -6
  380. package/src/providers/connection-resolution.ts +63 -13
  381. package/src/providers/fireworks/client.ts +20 -2
  382. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  383. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  384. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  385. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  386. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  387. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  388. package/src/providers/inference/adapter-factory.ts +24 -21
  389. package/src/providers/inference/auth.ts +15 -3
  390. package/src/providers/inference/backfill.ts +14 -1
  391. package/src/providers/inference/codex-token-refresh.ts +128 -0
  392. package/src/providers/inference/connections.ts +85 -5
  393. package/src/providers/inference/resolve-auth.ts +50 -5
  394. package/src/providers/model-catalog.ts +244 -242
  395. package/src/providers/model-intents.ts +3 -3
  396. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  397. package/src/providers/openai/chat-completions-provider.ts +215 -25
  398. package/src/providers/openai/responses-provider.ts +9 -3
  399. package/src/providers/openrouter/client.ts +46 -4
  400. package/src/providers/platform-proxy/constants.ts +3 -4
  401. package/src/providers/provider-catalog-visibility.ts +3 -1
  402. package/src/providers/provider-send-message.ts +27 -12
  403. package/src/providers/registry.ts +30 -1
  404. package/src/providers/types.ts +25 -0
  405. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  406. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  407. package/src/runtime/agent-wake.ts +212 -57
  408. package/src/runtime/auth/route-policy.ts +20 -3
  409. package/src/runtime/background-job-runner.ts +26 -0
  410. package/src/runtime/channel-reply-delivery.ts +182 -47
  411. package/src/runtime/channel-retry-sweep.ts +141 -16
  412. package/src/runtime/http-server.ts +7 -16
  413. package/src/runtime/http-types.ts +7 -51
  414. package/src/runtime/pending-interactions.ts +51 -8
  415. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  416. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  417. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
  418. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  419. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  420. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  421. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  422. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  423. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  424. package/src/runtime/routes/approval-routes.ts +4 -1
  425. package/src/runtime/routes/channel-availability-routes.ts +5 -0
  426. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  427. package/src/runtime/routes/consolidation-routes.ts +100 -0
  428. package/src/runtime/routes/content-source-routes.ts +78 -0
  429. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  430. package/src/runtime/routes/conversation-query-routes.ts +130 -12
  431. package/src/runtime/routes/conversation-routes.ts +288 -76
  432. package/src/runtime/routes/document-comments-routes.ts +287 -0
  433. package/src/runtime/routes/documents-routes.ts +33 -0
  434. package/src/runtime/routes/home-feed-routes.ts +6 -3
  435. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  436. package/src/runtime/routes/host-browser-routes.ts +8 -1
  437. package/src/runtime/routes/identity-routes.ts +21 -0
  438. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  439. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  440. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  441. package/src/runtime/routes/index.ts +14 -4
  442. package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
  443. package/src/runtime/routes/integrations/a2a.ts +294 -0
  444. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  445. package/src/runtime/routes/log-export-routes.ts +39 -0
  446. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  447. package/src/runtime/routes/notification-routes.ts +19 -2
  448. package/src/runtime/routes/question-routes.ts +4 -1
  449. package/src/runtime/routes/sanity-routes.ts +159 -0
  450. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  451. package/src/runtime/routes/subagents-routes.ts +41 -0
  452. package/src/runtime/services/conversation-serializer.ts +30 -4
  453. package/src/schedule/integration-status.ts +3 -1
  454. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  455. package/src/security/oauth2-device-code.ts +307 -0
  456. package/src/security/oauth2.ts +26 -9
  457. package/src/security/secure-keys.ts +5 -0
  458. package/src/skills/catalog-install.ts +6 -2
  459. package/src/subagent/manager.ts +2 -0
  460. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  461. package/src/tools/browser/browser-execution.ts +93 -0
  462. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  463. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  464. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  465. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  466. package/src/tools/browser/cdp-client/factory.ts +87 -3
  467. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  468. package/src/tools/browser/cdp-client/types.ts +36 -0
  469. package/src/tools/browser/pinned-tabs.ts +90 -0
  470. package/src/tools/document/document-comment-tool.test.ts +379 -0
  471. package/src/tools/document/document-comment-tool.ts +156 -0
  472. package/src/tools/document/document-tool.ts +128 -2
  473. package/src/tools/memory/register.ts +1 -9
  474. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  475. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  476. package/src/tools/network/domain-normalize.ts +17 -0
  477. package/src/tools/network/web-fetch.ts +213 -64
  478. package/src/tools/network/web-search.ts +191 -66
  479. package/src/tools/registry.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +3 -2
  481. package/src/tools/tool-approval-handler.ts +19 -12
  482. package/src/tools/types.ts +41 -2
  483. package/src/tools/ui-surface/definitions.ts +3 -1
  484. package/src/types/onboarding-context.ts +4 -0
  485. package/src/util/__tests__/favicon.test.ts +84 -0
  486. package/src/util/favicon.ts +40 -0
  487. package/src/util/platform.ts +0 -5
  488. package/src/workspace/git-service.ts +75 -4
  489. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  490. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  491. package/src/workspace/migrations/registry.ts +4 -0
  492. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  493. package/src/config/bundled-skills/document/SKILL.md +0 -54
  494. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  495. package/src/daemon/seed-files.ts +0 -18
  496. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  497. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
  498. package/src/runtime/routes/interface-routes.ts +0 -43
  499. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  500. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  501. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  502. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  503. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -20,6 +20,7 @@
20
20
  * | `memory-v2-static` | 38 | after-memory-prefix |
21
21
  * | `now-md` | 40 | after-memory-prefix |
22
22
  * | `active-documents` | 45 | prepend-user-tail |
23
+ * | `document-comments` | 46 | prepend-user-tail |
23
24
  * | `subagent-status` | 50 | append-user-tail |
24
25
  * | `slack-messages` | 60 | replace-run-messages |
25
26
  * | `thread-focus` | 70 | append-user-tail |
@@ -51,6 +52,7 @@ import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-fl
51
52
  import { getConfig } from "../../config/loader.js";
52
53
  import { getInContextPkbPaths } from "../../daemon/pkb-context-tracker.js";
53
54
  import { buildPkbReminder } from "../../daemon/pkb-reminder-builder.js";
55
+ import { listComments } from "../../documents/document-comments-store.js";
54
56
  import { searchPkbFiles } from "../../memory/pkb/pkb-search.js";
55
57
  import { getLogger } from "../../util/logger.js";
56
58
  import { registerPlugin } from "../registry.js";
@@ -87,12 +89,14 @@ const PKB_HINT_ARCHIVE_THRESHOLD = 0.7;
87
89
  export const DEFAULT_INJECTOR_ORDER = {
88
90
  diskPressureWarning: 5,
89
91
  workspaceContext: 10,
92
+ backgroundTurn: 15,
90
93
  unifiedTurnContext: 20,
91
94
  pkbContext: 30,
92
95
  pkbReminder: 35,
93
96
  memoryV2Static: 38,
94
97
  nowMd: 40,
95
98
  activeDocuments: 45,
99
+ documentComments: 46,
96
100
  subagentStatus: 50,
97
101
  slackMessages: 60,
98
102
  threadFocus: 70,
@@ -132,12 +136,11 @@ const diskPressureWarningInjector: Injector = {
132
136
  };
133
137
 
134
138
  /**
135
- * v2 read-side cutover guard. The `pkb-context` injector silences itself
136
- * under v2 because the `<knowledge_base>` block surfaces PKB content the v2
137
- * activation block already covers. The `pkb-reminder` injector still fires
138
- * (its body is generic recall/remember guidance) but skips the hybrid-search
139
- * hints — those name PKB paths v2 is moving away from. NOW.md is workspace
140
- * state independent of PKB and fires unchanged.
139
+ * v2 read-side cutover guard. Under v2 both `pkb-context` and `pkb-reminder`
140
+ * silence themselves entirely the `<knowledge_base>` content and the
141
+ * generic recall/remember nudge are both supplanted by the v2 static
142
+ * `<memory>` block. NOW.md is workspace state independent of PKB and fires
143
+ * unchanged.
141
144
  */
142
145
  function isPkbInjectionSilencedByV2(): boolean {
143
146
  return getConfig().memory.v2.enabled;
@@ -171,6 +174,39 @@ const workspaceContextInjector: Injector = {
171
174
  },
172
175
  };
173
176
 
177
+ /**
178
+ * `background-turn` injector — order 15, prepend-user-tail.
179
+ *
180
+ * Wraps the tail user message with a `<background_turn>` block that tells
181
+ * the assistant the guardian isn't watching and that anything noteworthy
182
+ * should be surfaced via the `notifications` skill. Fires only when (a) the
183
+ * conversation's type is "background" or "scheduled" (see
184
+ * `isBackgroundConversationType`) AND (b) no client is currently connected
185
+ * (`isNonInteractive`). The second gate is what prevents the reminder from
186
+ * firing on a manual follow-up the guardian sends into a background thread
187
+ * — at that point the guardian IS watching, so the framing doesn't apply.
188
+ *
189
+ * The inner text is read from `config.conversations.backgroundInjection`, so
190
+ * operators can edit the reminder without a code change. Setting it to the
191
+ * empty string disables the injection entirely.
192
+ */
193
+ const backgroundTurnInjector: Injector = {
194
+ name: "background-turn",
195
+ order: DEFAULT_INJECTOR_ORDER.backgroundTurn,
196
+ async produce(ctx: TurnContext): Promise<InjectionBlock | null> {
197
+ const inputs = readInjectionInputs(ctx);
198
+ if (!inputs.isBackgroundConversation) return null;
199
+ if (!inputs.isNonInteractive) return null;
200
+ const inner = getConfig().conversations.backgroundInjection;
201
+ if (!inner) return null;
202
+ return {
203
+ id: "background-turn",
204
+ text: `<background_turn>\n${inner}\n</background_turn>`,
205
+ placement: "prepend-user-tail",
206
+ };
207
+ },
208
+ };
209
+
174
210
  /**
175
211
  * `unified-turn-context` injector — order 20, prepend-user-tail.
176
212
  *
@@ -253,24 +289,8 @@ const pkbReminderInjector: Injector = {
253
289
  const mode = inputs.mode ?? "full";
254
290
  if (mode !== "full") return null;
255
291
  if (!inputs.pkbActive) return null;
256
- // The `memory-retrospective` feature flag enables a focused background
257
- // retrospective pass that catches what the in-conversation `remember`
258
- // calls miss. When that backstop is on, the per-turn pressure to call
259
- // `remember` softens to a judgment framing. When it's off, the original
260
- // high-pressure BODY is used so users without the retrospective still
261
- // get aggressive capture in-conversation.
262
- let relaxed = false;
263
- try {
264
- relaxed = isAssistantFeatureFlagEnabled(
265
- "memory-retrospective",
266
- getConfig(),
267
- );
268
- } catch {
269
- // Best-effort — fall back to the default (non-relaxed) BODY.
270
- }
271
- const reminder = isPkbInjectionSilencedByV2()
272
- ? buildPkbReminder([], relaxed)
273
- : await buildPkbReminderWithHints(inputs, relaxed);
292
+ if (isPkbInjectionSilencedByV2()) return null;
293
+ const reminder = await buildPkbReminderWithHints(inputs);
274
294
  return {
275
295
  id: "pkb-reminder",
276
296
  text: reminder,
@@ -300,7 +320,6 @@ function buildPkbContextBlock(content: string): string {
300
320
  */
301
321
  async function buildPkbReminderWithHints(
302
322
  inputs: TurnInjectionInputs,
303
- relaxed: boolean,
304
323
  ): Promise<string> {
305
324
  let hints: string[] = [];
306
325
  const queryVector = inputs.pkbQueryVector;
@@ -361,7 +380,7 @@ async function buildPkbReminderWithHints(
361
380
  hints = [];
362
381
  }
363
382
  }
364
- return buildPkbReminder(hints, relaxed);
383
+ return buildPkbReminder(hints);
365
384
  }
366
385
 
367
386
  /**
@@ -473,6 +492,77 @@ const activeDocumentsInjector: Injector = {
473
492
  },
474
493
  };
475
494
 
495
+ /** Maximum open comments surfaced per document to limit context bloat. */
496
+ const DOCUMENT_COMMENTS_CAP = 10;
497
+
498
+ /**
499
+ * Escape closing `</document_comments>` inside user-controlled strings so
500
+ * they cannot break out of the XML wrapper — same pattern as
501
+ * {@link buildPkbContextBlock} and {@link buildMemoryV2StaticBlock}.
502
+ */
503
+ function escapeDocCommentTag(s: string): string {
504
+ return s.replace(/<\/document_comments\s*>/gi, "&lt;/document_comments&gt;");
505
+ }
506
+
507
+ /**
508
+ * `document-comments` injector — order 46, prepend-user-tail.
509
+ *
510
+ * Surfaces open top-level comments on active documents so the assistant
511
+ * knows what feedback to address. For each active document, queries the
512
+ * comment store for open top-level comments (capped at
513
+ * {@link DOCUMENT_COMMENTS_CAP} most recent per document). Inline comments
514
+ * include the quoted anchor text; doc-level comments are labelled as such.
515
+ *
516
+ * Gating:
517
+ * - `mode === "full"`.
518
+ * - `activeDocuments` has at least one entry.
519
+ * - At least one document has open comments (returns null otherwise).
520
+ */
521
+ const documentCommentsInjector: Injector = {
522
+ name: "document-comments",
523
+ order: DEFAULT_INJECTOR_ORDER.documentComments,
524
+ async produce(ctx: TurnContext): Promise<InjectionBlock | null> {
525
+ const inputs = readInjectionInputs(ctx);
526
+ const mode = inputs.mode ?? "full";
527
+ if (mode !== "full") return null;
528
+ const docs = inputs.activeDocuments;
529
+ if (!docs || docs.length === 0) return null;
530
+
531
+ const sections: string[] = [];
532
+ for (const doc of docs) {
533
+ const comments = listComments(doc.surfaceId, {
534
+ status: "open",
535
+ topLevelOnly: true,
536
+ }).slice(-DOCUMENT_COMMENTS_CAP);
537
+ if (comments.length === 0) continue;
538
+
539
+ const lines = comments.map((c) => {
540
+ const anchor =
541
+ c.anchorText != null ? escapeDocCommentTag(c.anchorText) : null;
542
+ const label =
543
+ anchor != null ? `inline, anchored to "${anchor}"` : "doc-level";
544
+ return `- Comment #${c.id} (${label}): "${escapeDocCommentTag(c.content)}"`;
545
+ });
546
+ sections.push(
547
+ `Document: "${escapeDocCommentTag(doc.title)}" (surface_id: "${doc.surfaceId}")\n${lines.join("\n")}`,
548
+ );
549
+ }
550
+
551
+ if (sections.length === 0) return null;
552
+
553
+ const text = `<document_comments>
554
+ Open comments on your documents. Address these by editing the document, then use comment_resolve to mark each resolved.
555
+
556
+ ${sections.join("\n\n")}
557
+ </document_comments>`;
558
+ return {
559
+ id: "document-comments",
560
+ text,
561
+ placement: "prepend-user-tail",
562
+ };
563
+ },
564
+ };
565
+
476
566
  /**
477
567
  * `subagent-status` injector — order 50, append-user-tail.
478
568
  *
@@ -601,12 +691,14 @@ export const defaultInjectorsPlugin: Plugin = {
601
691
  injectors: [
602
692
  diskPressureWarningInjector,
603
693
  workspaceContextInjector,
694
+ backgroundTurnInjector,
604
695
  unifiedTurnContextInjector,
605
696
  pkbContextInjector,
606
697
  pkbReminderInjector,
607
698
  memoryV2StaticInjector,
608
699
  nowMdInjector,
609
700
  activeDocumentsInjector,
701
+ documentCommentsInjector,
610
702
  subagentStatusInjector,
611
703
  slackMessagesInjector,
612
704
  threadFocusInjector,
@@ -23,7 +23,9 @@
23
23
  * other filenames sit in the map for
24
24
  * forward compatibility)
25
25
  * tools/
26
- * *.ts ← each file's default export → plugin.tools[]
26
+ * *.ts ← each default export → plugin.tools[];
27
+ * runtime name derives from the filename
28
+ * basename
27
29
  * src/ ← internal helpers, ignored by the loader
28
30
  *
29
31
  * Per-surface, `.js` is preferred over `.ts` (compiled-binary semantics).
@@ -47,6 +49,12 @@ import semver from "semver";
47
49
  import { z } from "zod";
48
50
 
49
51
  import assistantPkg from "../../package.json" with { type: "json" };
52
+ import type {
53
+ LoadedPluginTool,
54
+ PluginTool,
55
+ RiskLevel,
56
+ ToolExecutionResult,
57
+ } from "../tools/types.js";
50
58
  import { getLogger } from "../util/logger.js";
51
59
  import { registerPlugin } from "./registry.js";
52
60
  import type {
@@ -106,6 +114,72 @@ function stripScope(name: string): string {
106
114
  return match ? match[1]! : name;
107
115
  }
108
116
 
117
+ function toToolNameSegment(value: string): string {
118
+ return value.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "tool";
119
+ }
120
+
121
+ function deriveToolName(toolFileBaseName: string): string {
122
+ return toToolNameSegment(toolFileBaseName);
123
+ }
124
+
125
+ /**
126
+ * Defaults applied by {@link applyPluginToolDefaults} when a plugin tool
127
+ * omits one of the normally-required fields. Exported as a constant so
128
+ * tests and callers can reference the same source of truth.
129
+ *
130
+ * The default `execute` returns an error result so the model sees a clear
131
+ * "this tool isn't wired up" signal at call time. The plugin still loads
132
+ * cleanly — broken individual tools must never block daemon boot.
133
+ */
134
+ export const PLUGIN_TOOL_DEFAULTS = Object.freeze({
135
+ description: "",
136
+ defaultRiskLevel: "medium" as RiskLevel,
137
+ input_schema: Object.freeze({
138
+ type: "object",
139
+ properties: {},
140
+ additionalProperties: false,
141
+ }) as object,
142
+ });
143
+
144
+ /**
145
+ * Fill the four normally-required {@link PluginTool} fields with documented
146
+ * defaults when the author omitted them. Returns a {@link LoadedPluginTool}
147
+ * that is safe to register.
148
+ */
149
+ function applyPluginToolDefaults(
150
+ tool: PluginTool,
151
+ name: string,
152
+ ): LoadedPluginTool {
153
+ const description =
154
+ typeof tool.description === "string"
155
+ ? tool.description
156
+ : PLUGIN_TOOL_DEFAULTS.description;
157
+ const defaultRiskLevel =
158
+ typeof tool.defaultRiskLevel === "string"
159
+ ? tool.defaultRiskLevel
160
+ : PLUGIN_TOOL_DEFAULTS.defaultRiskLevel;
161
+ const input_schema =
162
+ tool.input_schema !== null &&
163
+ typeof tool.input_schema === "object"
164
+ ? tool.input_schema
165
+ : PLUGIN_TOOL_DEFAULTS.input_schema;
166
+ const execute =
167
+ typeof tool.execute === "function"
168
+ ? tool.execute
169
+ : async (): Promise<ToolExecutionResult> => ({
170
+ content: `plugin tool ${name} has no execute implementation`,
171
+ isError: true,
172
+ });
173
+ return {
174
+ ...tool,
175
+ name,
176
+ description,
177
+ defaultRiskLevel,
178
+ input_schema,
179
+ execute,
180
+ };
181
+ }
182
+
109
183
  /**
110
184
  * Dynamic-import `absolutePath` and return its default export. Throws when
111
185
  * the module has no default export — callers attribute the error.
@@ -266,18 +340,16 @@ async function buildPluginFromDir(pluginDir: string): Promise<Plugin> {
266
340
  if (hooks !== undefined) plugin.hooks = hooks;
267
341
 
268
342
  const tools: PluginToolRegistration[] = [];
269
- for (const { path: toolPath } of listSurfaceDir(join(pluginDir, "tools"))) {
270
- const tool = await importDefault<PluginToolRegistration>(toolPath);
271
- if (
272
- tool === null ||
273
- typeof tool !== "object" ||
274
- typeof (tool as { name?: unknown }).name !== "string"
275
- ) {
343
+ for (const { name: toolName, path: toolPath } of listSurfaceDir(
344
+ join(pluginDir, "tools"),
345
+ )) {
346
+ const tool = await importDefault<PluginTool>(toolPath);
347
+ if (tool === null || typeof tool !== "object") {
276
348
  throw new Error(
277
- `external plugin ${name}: ${toolPath} default export must be a Tool object with a string "name"`,
349
+ `external plugin ${name}: ${toolPath} default export must be an object`,
278
350
  );
279
351
  }
280
- tools.push(tool);
352
+ tools.push(applyPluginToolDefaults(tool, deriveToolName(toolName)));
281
353
  }
282
354
  if (tools.length > 0) plugin.tools = tools;
283
355
 
@@ -43,7 +43,7 @@ import type {
43
43
  } from "../providers/types.js";
44
44
  import type { SkillRoute } from "../runtime/skill-route-registry.js";
45
45
  import type {
46
- PluginTool,
46
+ LoadedPluginTool,
47
47
  ToolContext,
48
48
  ToolExecutionResult,
49
49
  } from "../tools/types.js";
@@ -839,6 +839,13 @@ export interface TurnInjectionInputs {
839
839
  * knows no human is present to answer clarification questions.
840
840
  */
841
841
  readonly isNonInteractive?: boolean;
842
+ /**
843
+ * True when the active conversation's type is "background" or "scheduled"
844
+ * (see `isBackgroundConversationType`). Read by the `background-turn`
845
+ * injector to wrap the tail user message with a contextual reminder when
846
+ * the turn is also non-interactive.
847
+ */
848
+ readonly isBackgroundConversation?: boolean;
842
849
  /**
843
850
  * Active documents open in this conversation — surfaced by the
844
851
  * `active-documents` injector so the assistant can target existing docs
@@ -1002,15 +1009,17 @@ export interface Injector {
1002
1009
 
1003
1010
  /**
1004
1011
  * Tool registration contributed by a plugin. Uses the narrow
1005
- * {@link PluginTool} shape plugin authors declare functional fields
1006
- * (`name`, `description`, `input_schema`, `execute`, etc.) and leave category
1007
- * / ownership metadata to the bootstrap, which stamps `category: "plugin"`,
1008
- * `origin: "plugin"`, and `ownerPluginId: <plugin.name>` before handing the
1009
- * batch to `registerPluginTools`. The registration boundary synthesizes
1012
+ * {@link LoadedPluginTool} shape. External plugin authors declare the
1013
+ * nameless `PluginTool` file shape; the loader derives `name` from the
1014
+ * `tools/<name>.ts` basename before storing it on `plugin.tools`. Authors
1015
+ * also leave category / ownership metadata to the bootstrap, which stamps
1016
+ * `category: "plugin"`, `origin: "plugin"`, and
1017
+ * `ownerPluginId: <plugin.name>` before handing the batch to
1018
+ * `registerPluginTools`. The registration boundary synthesizes
1010
1019
  * `getDefinition()` from `{name, description, input_schema}` so the canonical
1011
1020
  * {@link Tool} interface used by the internal registry stays unchanged.
1012
1021
  */
1013
- export type PluginToolRegistration = PluginTool;
1022
+ export type PluginToolRegistration = LoadedPluginTool;
1014
1023
  /**
1015
1024
  * HTTP route registration contributed by a plugin. Plugins express routes as
1016
1025
  * {@link SkillRoute} values — the same shape the skill-route registry
@@ -1,13 +1,13 @@
1
1
  /**
2
- * Tests for the Background Conversation gating in buildSystemPrompt.
3
- *
4
- * The Background Conversation guidance is gated on
5
- * `options.isBackgroundConversation === true`. Interactive (default)
6
- * conversations must pay zero token cost — the section must be entirely
7
- * absent unless the flag is explicitly true.
2
+ * Smoke tests for buildSystemPrompt covers tool-routing-guidance
3
+ * exclusions and other call-shape invariants. Background-conversation
4
+ * guidance is no longer rendered into the system prompt; see
5
+ * `__tests__/injector-background-turn.test.ts` for the per-turn
6
+ * user-message injection that replaced it.
8
7
  */
9
8
 
10
- import { mkdirSync } from "node:fs";
9
+ import { copyFileSync, mkdirSync, readFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
11
  import { beforeEach, describe, expect, mock, test } from "bun:test";
12
12
 
13
13
  const TEST_DIR = process.env.VELLUM_WORKSPACE_DIR!;
@@ -58,60 +58,59 @@ mock.module("../../config/loader.js", () => ({
58
58
  setNestedValue: () => {},
59
59
  }));
60
60
 
61
- const { buildSystemPrompt, SYSTEM_PROMPT_CACHE_BOUNDARY } =
61
+ const { buildSystemPrompt, maybeReseedBootstrapForCohort } =
62
62
  await import("../system-prompt.js");
63
63
 
64
- describe("buildSystemPrompt — Background Conversation gating", () => {
64
+ describe("buildSystemPrompt — tool routing guidance", () => {
65
65
  beforeEach(() => {
66
66
  mkdirSync(TEST_DIR, { recursive: true });
67
67
  });
68
68
 
69
- test("isBackgroundConversation: true appends the Background Conversation section", () => {
70
- const result = buildSystemPrompt({ isBackgroundConversation: true });
71
- expect(result).toContain("## Background Conversation");
72
- expect(result).toContain("`notifications` skill");
73
- expect(result).toContain("assistant notifications send");
69
+ test("does not include ask_question routing guidance", () => {
70
+ const result = buildSystemPrompt({});
71
+ expect(result).not.toContain("## Clarifying questions");
72
+ expect(result).not.toContain("ask_question");
74
73
  });
74
+ });
75
75
 
76
- test("isBackgroundConversation: false section is omitted", () => {
77
- const result = buildSystemPrompt({ isBackgroundConversation: false });
78
- expect(result).not.toContain("## Background Conversation");
79
- });
76
+ describe("maybeReseedBootstrapForCohortcontent-automation template", () => {
77
+ const templatesDir = join(import.meta.dirname!, "..", "templates");
80
78
 
81
- test("options undefined — section is omitted (interactive default)", () => {
82
- const result = buildSystemPrompt(undefined);
83
- expect(result).not.toContain("## Background Conversation");
79
+ beforeEach(() => {
80
+ mkdirSync(TEST_DIR, { recursive: true });
81
+ // Seed the workspace with the generic BOOTSTRAP.md so the cohort
82
+ // reseed detects it as an unmodified template and overwrites it.
83
+ copyFileSync(
84
+ join(templatesDir, "BOOTSTRAP.md"),
85
+ join(TEST_DIR, "BOOTSTRAP.md"),
86
+ );
84
87
  });
85
88
 
86
- test("options provided without the flag — section is omitted", () => {
87
- const result = buildSystemPrompt({});
88
- expect(result).not.toContain("## Background Conversation");
89
+ function reseedAndRead(): string {
90
+ maybeReseedBootstrapForCohort("content-automation");
91
+ return readFileSync(join(TEST_DIR, "BOOTSTRAP.md"), "utf-8");
92
+ }
93
+
94
+ test("loads the geo-writing skill on first turn", () => {
95
+ const content = reseedAndRead();
96
+ expect(content).toContain("geo-writing");
89
97
  });
90
98
 
91
- test("section lives in the static (cached) block, not the dynamic suffix", () => {
92
- // The section is deterministic for a given conversationType, so it
93
- // belongs in staticParts to share the cache block with other
94
- // call-time-stable instructions.
95
- const result = buildSystemPrompt({ isBackgroundConversation: true });
96
- const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
97
- expect(boundaryIdx).toBeGreaterThan(-1);
98
- const staticBlock = result.slice(0, boundaryIdx);
99
- const dynamicBlock = result.slice(
100
- boundaryIdx + SYSTEM_PROMPT_CACHE_BOUNDARY.length,
101
- );
102
- expect(staticBlock).toContain("## Background Conversation");
103
- expect(dynamicBlock).not.toContain("## Background Conversation");
99
+ test("uses skill-first onboarding approach", () => {
100
+ const content = reseedAndRead();
101
+ expect(content).toContain("Skill-First Onboarding");
102
+ expect(content).toContain("The skill is the onboarding");
104
103
  });
105
- });
106
104
 
107
- describe("buildSystemPrompt tool routing guidance", () => {
108
- beforeEach(() => {
109
- mkdirSync(TEST_DIR, { recursive: true });
105
+ test("includes comment-driven edit loop", () => {
106
+ const content = reseedAndRead();
107
+ expect(content).toContain("comment-driven");
108
+ expect(content).toContain("comment_resolve");
109
+ expect(content).toContain("document_update");
110
110
  });
111
111
 
112
- test("does not include ask_question routing guidance", () => {
113
- const result = buildSystemPrompt({});
114
- expect(result).not.toContain("## Clarifying questions");
115
- expect(result).not.toContain("ask_question");
112
+ test("references VOICE.md for voice capture", () => {
113
+ const content = reseedAndRead();
114
+ expect(content).toContain("VOICE.md");
116
115
  });
117
116
  });
@@ -74,18 +74,14 @@ describe("task_progress hint in parallel-tool-calls section", () => {
74
74
  });
75
75
 
76
76
  test("renders regardless of options passed", () => {
77
- const withBackground = buildSystemPrompt({
78
- isBackgroundConversation: true,
79
- });
80
- const withoutBackground = buildSystemPrompt({
81
- isBackgroundConversation: false,
82
- });
77
+ const withClientFlag = buildSystemPrompt({ hasNoClient: true });
78
+ const withoutClientFlag = buildSystemPrompt({ hasNoClient: false });
83
79
  const withExcludePrefix = buildSystemPrompt({
84
80
  excludeCustomPrefix: true,
85
81
  });
86
82
 
87
- expect(withBackground).toContain("task_progress");
88
- expect(withoutBackground).toContain("task_progress");
83
+ expect(withClientFlag).toContain("task_progress");
84
+ expect(withoutClientFlag).toContain("task_progress");
89
85
  expect(withExcludePrefix).toContain("task_progress");
90
86
  });
91
87
 
@@ -19,6 +19,20 @@ export const TOOL_DISPLAY_NAMES: Record<string, string> = {
19
19
  "apple-notes": "Apple Notes",
20
20
  };
21
21
 
22
+ /**
23
+ * Map of known prior-assistant IDs (from the client onboarding UI) to display names.
24
+ * Unknown IDs pass through with first-letter capitalization via `normalizePriorAssistants`.
25
+ */
26
+ export const PRIOR_ASSISTANT_DISPLAY_NAMES: Record<string, string> = {
27
+ chatgpt: "ChatGPT",
28
+ claude: "Claude",
29
+ openclaw: "OpenClaw",
30
+ hermes: "Hermes",
31
+ manus: "Manus",
32
+ gemini: "Gemini",
33
+ copilot: "Copilot",
34
+ };
35
+
22
36
  /**
23
37
  * Map of known task IDs to plain-language labels describing what the assistant
24
38
  * does for each task category.
@@ -56,14 +70,28 @@ export function normalizeTasks(tasks: string[]): string[] {
56
70
  return tasks.map((id) => TASK_DISPLAY_LABELS[id] ?? id);
57
71
  }
58
72
 
73
+ /**
74
+ * Maps each prior-assistant ID through `PRIOR_ASSISTANT_DISPLAY_NAMES`,
75
+ * falling back to first-letter capitalization for unknown IDs.
76
+ */
77
+ export function normalizePriorAssistants(assistants: string[]): string[] {
78
+ return assistants.map(
79
+ (id) => PRIOR_ASSISTANT_DISPLAY_NAMES[id] ?? capitalizeFirst(id),
80
+ );
81
+ }
82
+
59
83
  export interface NormalizedOnboarding {
60
84
  preferredName?: string;
61
85
  commonWork: string[];
62
86
  dailyTools: string[];
63
87
  tone?: string;
64
88
  assistantName?: string;
89
+ priorAssistants?: string[];
65
90
  googleConnected?: boolean;
66
91
  googleServices?: string[];
92
+ cohort?: string;
93
+ websiteUrl?: string;
94
+ contentSourceUrl?: string;
67
95
  }
68
96
 
69
97
  const SCOPE_SERVICE_MAP: Record<string, string> = {
@@ -103,5 +131,17 @@ export function normalizeOnboardingContext(
103
131
  googleServices: ctx.googleConnected
104
132
  ? deriveGoogleServices(ctx.googleScopes)
105
133
  : undefined,
134
+ priorAssistants: ctx.priorAssistants?.length
135
+ ? normalizePriorAssistants(ctx.priorAssistants)
136
+ : undefined,
137
+ cohort: ctx.cohort,
138
+ websiteUrl:
139
+ typeof ctx.websiteUrl === "string"
140
+ ? ctx.websiteUrl.trim().replace(/[\r\n\t]/g, "") || undefined
141
+ : undefined,
142
+ contentSourceUrl:
143
+ typeof ctx.contentSourceUrl === "string"
144
+ ? ctx.contentSourceUrl.trim().replace(/[\r\n\t]/g, "") || undefined
145
+ : undefined,
106
146
  };
107
147
  }