@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
@@ -93,7 +93,10 @@ function collectSectionIds(workspaceDir: string): string[] {
93
93
  if (name.endsWith(".md")) ids.add(name.slice(0, -".md".length));
94
94
  }
95
95
  } catch (err) {
96
- log.warn({ err, workspaceDir }, "Failed to list workspace system prompt dir");
96
+ log.warn(
97
+ { err, workspaceDir },
98
+ "Failed to list workspace system prompt dir",
99
+ );
97
100
  }
98
101
  }
99
102
  return [...ids].sort();
@@ -102,6 +105,7 @@ function collectSectionIds(workspaceDir: string): string[] {
102
105
  interface ResolvedSection {
103
106
  enabled: string | boolean | undefined;
104
107
  body: string;
108
+ transform?: BundledSection["transform"];
105
109
  }
106
110
 
107
111
  function resolveSection(
@@ -114,12 +118,20 @@ function resolveSection(
114
118
  try {
115
119
  raw = readFileSync(workspacePath, "utf-8");
116
120
  } catch (err) {
117
- log.warn({ err, workspacePath }, "Failed to read workspace section override");
121
+ log.warn(
122
+ { err, workspacePath },
123
+ "Failed to read workspace section override",
124
+ );
118
125
  return null;
119
126
  }
120
127
  const parsed = parseFrontmatterFields(raw);
121
128
  const fields = parsed?.fields ?? {};
122
129
  const body = parsed?.body ?? raw;
130
+ // Workspace override skips the bundled transform: when the user has
131
+ // written their own `prompts/system/<id>.md` they've taken full
132
+ // control of the body shape, and re-running the bundled transform
133
+ // (e.g. unmodified-template detection on IDENTITY.md) would
134
+ // misclassify their override.
123
135
  return { enabled: fields["enabled"] as string | boolean | undefined, body };
124
136
  }
125
137
  const bundled = BUNDLED_SYSTEM_SECTIONS.find((s) => s.id === id);
@@ -128,7 +140,8 @@ function resolveSection(
128
140
  // A bundled section may delegate its body to a workspace file outside
129
141
  // the section override directory (e.g. `SOUL.md` at the workspace
130
142
  // root). Read it now; missing/empty files yield "", which
131
- // `renderSection` then gates off via its empty-body check.
143
+ // `renderSection` then gates off via its empty-body check (or via the
144
+ // section's `transform`, if set).
132
145
  if (bundled.workspacePath) {
133
146
  const filePath = getWorkspacePromptPath(bundled.workspacePath);
134
147
  let body = "";
@@ -136,16 +149,17 @@ function resolveSection(
136
149
  try {
137
150
  body = readFileSync(filePath, "utf-8");
138
151
  } catch (err) {
139
- log.warn(
140
- { err, filePath, id },
141
- "Failed to read section workspacePath",
142
- );
152
+ log.warn({ err, filePath, id }, "Failed to read section workspacePath");
143
153
  }
144
154
  }
145
- return { enabled: bundled.enabled, body };
155
+ return { enabled: bundled.enabled, body, transform: bundled.transform };
146
156
  }
147
157
 
148
- return { enabled: bundled.enabled, body: bundled.body };
158
+ return {
159
+ enabled: bundled.enabled,
160
+ body: bundled.body,
161
+ transform: bundled.transform,
162
+ };
149
163
  }
150
164
 
151
165
  function renderSection(
@@ -158,7 +172,14 @@ function renderSection(
158
172
 
159
173
  if (!isEnabled(section.enabled, ctx)) return null;
160
174
 
161
- const stripped = stripCommentLines(section.body).trim();
175
+ let body = section.body;
176
+ if (section.transform) {
177
+ const transformed = section.transform(body, ctx);
178
+ if (transformed === null) return null;
179
+ body = transformed;
180
+ }
181
+
182
+ const stripped = stripCommentLines(body).trim();
162
183
  if (stripped.length === 0) return null;
163
184
  return interpolateVariables(stripped, ctx);
164
185
  }
@@ -190,10 +211,7 @@ const IDENT_REGEX = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
190
211
  * typo on a `{{key}}` substitution surfaces at the warn log rather than
191
212
  * inlining the string `"undefined"`).
192
213
  */
193
- function interpolateVariables(
194
- body: string,
195
- ctx: SectionRenderContext,
196
- ): string {
214
+ function interpolateVariables(body: string, ctx: SectionRenderContext): string {
197
215
  // Collapse standalone tag lines so multiline section templates render
198
216
  // without phantom blank lines from the layout markers.
199
217
  const collapsed = body.replace(STANDALONE_TAG_LINE, "$1");
@@ -4,6 +4,7 @@ import {
4
4
  mkdirSync,
5
5
  readdirSync,
6
6
  readFileSync,
7
+ writeFileSync,
7
8
  } from "node:fs";
8
9
  import { join } from "node:path";
9
10
 
@@ -23,6 +24,9 @@ import { cleanupBootstrapFiles } from "./bootstrap-cleanup.js";
23
24
  import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "./cache-boundary.js";
24
25
  import { normalizeOnboardingContext } from "./normalize-onboarding.js";
25
26
  import { renderWorkspaceSections } from "./sections.js";
27
+ import { isTemplateContent } from "./template-detection.js";
28
+
29
+ export { isTemplateContent };
26
30
 
27
31
  export { SYSTEM_PROMPT_CACHE_BOUNDARY };
28
32
 
@@ -36,9 +40,19 @@ const BOOTSTRAP_VOICE_BLOCKS: Record<string, string> = {
36
40
  "## Voice\nThoughtful and unhurried. Notice things. Word choice matters. Don't rush to close — sometimes the observation is the value.",
37
41
  };
38
42
 
43
+ /**
44
+ * Maps onboarding cohort identifiers to their cohort-specific bootstrap
45
+ * template filenames. When a cohort key is present in OnboardingContext,
46
+ * `maybeReseedBootstrapForCohort` swaps the generic BOOTSTRAP.md with the
47
+ * cohort-specific variant — but only if the workspace file is still pristine.
48
+ */
49
+ const COHORT_BOOTSTRAP_TEMPLATES: Record<string, string> = {
50
+ "content-automation": "BOOTSTRAP-CONTENT-AUTOMATION.md",
51
+ };
52
+
39
53
  const log = getLogger("system-prompt");
40
54
 
41
- const PROMPT_FILES = ["SOUL.md", "IDENTITY.md"] as const;
55
+ const PROMPT_FILES = ["IDENTITY.md", "SOUL.md"] as const;
42
56
 
43
57
  function hasPopulatedUsersDir(): boolean {
44
58
  try {
@@ -209,13 +223,67 @@ export function ensurePromptFiles(): void {
209
223
  }
210
224
  }
211
225
 
226
+ /**
227
+ * One-shot swap: if the workspace BOOTSTRAP.md is still the unmodified generic
228
+ * template AND a cohort-specific template exists, overwrite the workspace file
229
+ * with the cohort variant. No-op when BOOTSTRAP.md has been deleted, modified,
230
+ * or the cohort has no mapped template.
231
+ */
232
+ export function maybeReseedBootstrapForCohort(cohort: string): void {
233
+ const templateFileName = COHORT_BOOTSTRAP_TEMPLATES[cohort];
234
+ if (!templateFileName) return;
235
+
236
+ const bootstrapPath = getWorkspacePromptPath("BOOTSTRAP.md");
237
+ if (!existsSync(bootstrapPath)) return;
238
+
239
+ const currentContent = readPromptFile(bootstrapPath);
240
+ // Compare against the GENERIC "BOOTSTRAP.md" template, not the cohort-
241
+ // specific one. After the swap, the workspace content no longer matches
242
+ // the generic template, so this guard returns false on subsequent calls —
243
+ // making the swap idempotent. Do NOT change the comparison target to the
244
+ // cohort template filename; that would re-swap on every prompt build.
245
+ if (!isTemplateContent(currentContent, "BOOTSTRAP.md")) return;
246
+
247
+ const templatesDir = resolveBundledDir(
248
+ import.meta.dirname ?? __dirname,
249
+ "templates",
250
+ "templates",
251
+ );
252
+ const cohortTemplatePath = join(templatesDir, templateFileName);
253
+ if (!existsSync(cohortTemplatePath)) {
254
+ log.warn(
255
+ { cohort, templateFileName },
256
+ "Cohort bootstrap template not found, keeping generic BOOTSTRAP.md",
257
+ );
258
+ return;
259
+ }
260
+
261
+ try {
262
+ const cohortContent = readFileSync(cohortTemplatePath, "utf-8");
263
+ writeFileSync(bootstrapPath, cohortContent, "utf-8");
264
+ log.info(
265
+ { cohort, templateFileName },
266
+ "Replaced generic BOOTSTRAP.md with cohort-specific template",
267
+ );
268
+ } catch (err) {
269
+ log.warn(
270
+ { err, cohort, templateFileName },
271
+ "Failed to reseed BOOTSTRAP.md for cohort",
272
+ );
273
+ }
274
+ }
275
+
212
276
  /**
213
277
  * Build the system prompt from ~/.vellum prompt files.
214
278
  *
215
279
  * Composition:
216
- * 1. Base prompt: IDENTITY.md + SOUL.md (guaranteed to exist after ensurePromptFiles)
217
- * 2. Append the resolved user persona from users/<slug>.md (via options.userPersona)
218
- * 3. If BOOTSTRAP.md exists, append first-run ritual instructions
280
+ * 1. Bundled static sections (`renderWorkspaceSections`), in id-sort
281
+ * order. This includes `08-identity` (IDENTITY.md) and `09-soul`
282
+ * (SOUL.md), both backed by workspace files.
283
+ * 2. User and channel persona (via `options.userPersona` /
284
+ * `options.channelPersona`) and accumulated VOICE.md, after the
285
+ * cache boundary.
286
+ * 3. If BOOTSTRAP.md exists, the first-run ritual block.
219
287
  */
220
288
  export interface BuildSystemPromptOptions {
221
289
  hasNoClient?: boolean;
@@ -225,14 +293,6 @@ export interface BuildSystemPromptOptions {
225
293
  channelPersona?: string | null;
226
294
  userSlug?: string | null;
227
295
  onboardingContext?: OnboardingContext;
228
- /**
229
- * When true, append the Background Conversation guidance instructing the
230
- * model to invoke the `notifications` skill for progress, blockers, and
231
- * completion. Set by callers when running a non-interactive
232
- * background/scheduled conversation. Interactive conversations leave this
233
- * unset so they pay zero token cost.
234
- */
235
- isBackgroundConversation?: boolean;
236
296
  }
237
297
 
238
298
  /**
@@ -244,6 +304,19 @@ export interface BuildSystemPromptOptions {
244
304
  * files change between turns.
245
305
  */
246
306
  export function buildSystemPrompt(options?: BuildSystemPromptOptions): string {
307
+ // One-shot cohort swap: if the user has a cohort and BOOTSTRAP.md is still
308
+ // the generic template, replace it with the cohort-specific variant before
309
+ // the prompt reads the file.
310
+ if (options?.onboardingContext?.cohort) {
311
+ maybeReseedBootstrapForCohort(options.onboardingContext.cohort);
312
+ }
313
+
314
+ // Read BOOTSTRAP.md up front so `includeBootstrap` is on `ctx` for the
315
+ // `08-identity` section transform, which gates the unmodified IDENTITY.md
316
+ // template behind bootstrap presence.
317
+ const bootstrap = readPromptFile(getWorkspacePromptPath("BOOTSTRAP.md"));
318
+ const includeBootstrap = !!bootstrap && !options?.excludeBootstrap;
319
+
247
320
  // Section render context. Workspace section frontmatter `enabled:`
248
321
  // predicates and `{{key}}` / `{{#flag}}...{{/flag}}` body interpolation
249
322
  // both resolve against this map, so anything the renderer needs to see
@@ -256,53 +329,29 @@ export function buildSystemPrompt(options?: BuildSystemPromptOptions): string {
256
329
  ...options,
257
330
  isContainerized: getIsContainerized(),
258
331
  workspaceDir: getWorkspaceDir(),
332
+ includeBootstrap,
259
333
  };
260
334
 
261
335
  // Single array. Everything pushed before `dynamicStart` lands in the
262
336
  // static (cached) prefix; everything after lands in the dynamic suffix.
263
337
  // The two halves are joined around `SYSTEM_PROMPT_CACHE_BOUNDARY` so the
264
338
  // Anthropic provider can key its prompt cache on the prefix.
339
+ //
340
+ // IDENTITY.md and SOUL.md both render via workspace-backed bundled
341
+ // sections (`08-identity` / `09-soul`) inside `renderWorkspaceSections`,
342
+ // so they sit in the static prefix in that order.
265
343
  const systemParts: string[] = [...renderWorkspaceSections(ctx)];
266
344
  const dynamicStart = systemParts.length;
267
345
 
268
- // SOUL.md is rendered by the `09-soul` workspace-backed section
269
- // (see templates/system-sections.ts) — no inline read needed here.
270
- const identityPath = getWorkspacePromptPath("IDENTITY.md");
271
- const bootstrapPath = getWorkspacePromptPath("BOOTSTRAP.md");
272
-
273
- const identity = readPromptFile(identityPath);
274
- const bootstrap = readPromptFile(bootstrapPath);
275
-
276
- const includeBootstrap = !!bootstrap && !options?.excludeBootstrap;
277
-
278
- // Template prompt files contain placeholder fields and meta-instructions
279
- // meant for the assistant to fill in during onboarding. When included
280
- // verbatim in the system prompt, the model can leak internal details and
281
- // narrate its own setup process instead of following the BOOTSTRAP.md
282
- // ritual. Detect unmodified templates by comparing against the bundled
283
- // source and skip them — SOUL.md provides sufficient personality defaults
284
- // until onboarding completes.
285
- const identityIsTemplate = isTemplateContent(identity, "IDENTITY.md");
286
-
287
- if (identity && (!identityIsTemplate || includeBootstrap)) {
288
- if (identityIsTemplate) {
289
- // During bootstrap the model needs to see the template structure
290
- // so it can produce a valid file_write with the right fields.
291
- systemParts.push(identity);
292
- } else {
293
- // Strip placeholder lines (e.g. "- **Name:** _(not yet chosen)_") so
294
- // the model doesn't treat unresolved fields as prompts to ask the user.
295
- const cleanedIdentity = identity
296
- .split("\n")
297
- .filter((line) => !/_\(not yet (?:chosen|established)\)_/.test(line))
298
- .join("\n");
299
- if (cleanedIdentity.trim()) {
300
- systemParts.push(cleanedIdentity);
301
- }
302
- }
303
- }
304
346
  if (options?.userPersona) systemParts.push(options.userPersona);
305
347
  if (options?.channelPersona) systemParts.push(options.channelPersona);
348
+
349
+ // Surface accumulated voice markers when VOICE.md has content.
350
+ const voiceContent = readPromptFile(getWorkspacePromptPath("VOICE.md"));
351
+ if (voiceContent) {
352
+ systemParts.push("# Voice Profile\n\n" + voiceContent);
353
+ }
354
+
306
355
  if (includeBootstrap) {
307
356
  const userSlug = options?.userSlug ?? "default";
308
357
  const bootstrapWithSlug = bootstrap.replaceAll(
@@ -339,11 +388,19 @@ export function buildSystemPrompt(options?: BuildSystemPromptOptions): string {
339
388
  if (n.assistantName)
340
389
  lines.push(`- Chosen assistant name: ${n.assistantName}`);
341
390
  if (n.tone) lines.push(`- Preferred initial voice: ${n.tone}`);
391
+ if (n.cohort) lines.push(`- Cohort: ${n.cohort}`);
392
+ if (n.websiteUrl) lines.push(`- Website URL: ${n.websiteUrl}`);
393
+ if (n.contentSourceUrl)
394
+ lines.push(`- Content source URL: ${n.contentSourceUrl}`);
342
395
  if (n.googleConnected && n.googleServices?.length) {
343
396
  lines.push(
344
397
  `- Google connected: yes (${n.googleServices.join(", ")} access granted)`,
345
398
  );
346
399
  }
400
+ if (n.priorAssistants?.length)
401
+ lines.push(
402
+ `- Prior AI assistants used: ${n.priorAssistants.join(", ")}`,
403
+ );
347
404
  lines.push(
348
405
  "",
349
406
  "Apply this context quietly. Do not recap it as a list unless the user asks.",
@@ -408,34 +465,6 @@ function buildIntegrationSection(): string {
408
465
  // Re-export from shared util so existing importers don't break.
409
466
  export { stripCommentLines } from "../util/strip-comment-lines.js";
410
467
 
411
- /**
412
- * Returns true when the prompt file content is still the unmodified template
413
- * shipped with the daemon. Compares the stripped workspace content against
414
- * the stripped bundled template source so the check stays accurate even if
415
- * templates are edited in future releases.
416
- */
417
- export function isTemplateContent(
418
- content: string | null,
419
- templateFileName: string,
420
- ): boolean {
421
- if (content == null) return false;
422
- const templatesDir = resolveBundledDir(
423
- import.meta.dirname ?? __dirname,
424
- "templates",
425
- "templates",
426
- );
427
- const templatePath = join(templatesDir, templateFileName);
428
- if (!existsSync(templatePath)) return false;
429
- try {
430
- const templateContent = stripCommentLines(
431
- readFileSync(templatePath, "utf-8"),
432
- );
433
- return content === templateContent;
434
- } catch {
435
- return false;
436
- }
437
- }
438
-
439
468
  export function readPromptFile(path: string): string | null {
440
469
  if (!existsSync(path)) return null;
441
470
 
@@ -0,0 +1,37 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import { resolveBundledDir } from "../util/bundled-asset.js";
5
+ import { stripCommentLines } from "../util/strip-comment-lines.js";
6
+
7
+ /**
8
+ * Returns true when the prompt file content is still the unmodified template
9
+ * shipped with the daemon. Compares the stripped workspace content against
10
+ * the stripped bundled template source so the check stays accurate even if
11
+ * templates are edited in future releases.
12
+ *
13
+ * Kept in a leaf module so the bundled section registry can depend on it
14
+ * without forming a cycle through `system-prompt.ts → sections.ts →
15
+ * templates/system-sections.ts`.
16
+ */
17
+ export function isTemplateContent(
18
+ content: string | null,
19
+ templateFileName: string,
20
+ ): boolean {
21
+ if (content == null) return false;
22
+ const templatesDir = resolveBundledDir(
23
+ import.meta.dirname ?? __dirname,
24
+ "templates",
25
+ "templates",
26
+ );
27
+ const templatePath = join(templatesDir, templateFileName);
28
+ if (!existsSync(templatePath)) return false;
29
+ try {
30
+ const templateContent = stripCommentLines(
31
+ readFileSync(templatePath, "utf-8"),
32
+ );
33
+ return content === templateContent;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
@@ -0,0 +1,141 @@
1
+ _ Lines starting with _ are comments. They won't appear in the system prompt.
2
+ _ This template replaces BOOTSTRAP.md for users entering through the content-automation cohort
3
+ _ (utm_campaign=content-automation). Skill-first onboarding: load the geo-writing skill, ask one
4
+ _ question, ship a draft, learn voice from edits.
5
+
6
+ # BOOTSTRAP-CONTENT-AUTOMATION.md — Skill-First Onboarding (GEO)
7
+
8
+ You're here to help this person write GEO-optimized articles that get AI engines to cite their brand. The skill you load is the entry point, not a prerequisite. Multi-turn flow. The first article is the start of a loop, not the end of a conversation.
9
+
10
+ ## What you know at hatch
11
+
12
+ You know this person came in through a GEO marketing campaign. They saw a landing page that promised help writing better GEO posts. They signed up and hatched on web, which means they were willing to trade an email and a sign-up flow for the promise. That's already a filter: they believe content output is a bottleneck and are looking for leverage.
13
+
14
+ You do not yet know their name, their company, their brand, or their voice. You have no pre-chat context, no scraped site, no CMS content. Your job is to get it, fast, with minimal friction. But you know the frame: they self-identified as someone who wants to write GEO content that ranks.
15
+
16
+ ## First turn
17
+
18
+ The first message in your conversation context is a system trigger. Don't reference it as if the user said it.
19
+
20
+ Acknowledge their intent in one sentence. Then immediately load the `geo-writing` skill and the `document-editor` skill. Both are needed from the start: geo-writing drives research and writing, document-editor provides the WYSIWYG surface for output and comments.
21
+
22
+ After loading the skills, fork geo-writing to the workspace if no workspace copy exists yet: check whether `skills/geo-writing/SKILL.md` already exists in the workspace root. If it does, skip the copy — the existing workspace version contains learned edits from previous conversations. If it doesn't exist, copy the skill's SKILL.md and its `references/` directory to `skills/geo-writing/`. This creates a workspace override that you can edit freely across conversations. The bundled copy is read-only — all future reads and edits target the workspace copy.
23
+
24
+ After loading the skills, ask one question to open the collaboration: "What's a topic you've been wanting to write about?" This is your first and only ask. Everything else you get from their answer or from doing the work.
25
+
26
+ ## If they don't have a topic
27
+
28
+ If they say they're not sure what to write about, or they want ideas, do not ask more questions. Suggest two proven starting formats and offer a quick angle:
29
+
30
+ "Two formats work well for GEO: a listicle comparing tools in your category — your brand ranks #1 — or a head-to-head against your biggest competitor. What category are you in? I can suggest a specific angle."
31
+
32
+ Get the category, suggest one listicle and one head-to-head angle, and let them pick. Then proceed.
33
+
34
+ ## First article
35
+
36
+ Once you have a topic and format, run the research phase from the skill. Fetch their brand info. Research competitors. Find trends. Score tools if it's a listicle. Write the full article.
37
+
38
+ Do not ask permission to write. Do not preview the structure. Do not ask "should I include X?" Ship the draft. The work is the response.
39
+
40
+ Present the article in the document editor. Call `document_create` with the article title and write the content in chunks via `document_update` with `mode: "append"`. The article must open in the WYSIWYG editor — not inline in chat. This gives the user a real editing surface with comment capability, which is what the entire edit loop depends on. This supersedes the skill's PHASE 5 file-write instruction — output goes to the document editor, not to `Articles/Articles/`.
41
+
42
+ Lead with the angle, not the throat-clearing. Mirror voice from what you learn — sentence length, headers or no headers, lowercase or title case, words they use, words they don't. If you have no voice signal yet, write clean, direct, confident prose and let their edits teach you.
43
+
44
+ After the user's edits on the first article, this is your first signal for skill improvement. Start tracking patterns but don't edit the skill yet — wait for the second article to confirm.
45
+
46
+ ## Voice capture
47
+
48
+ You need writing samples to learn their voice. After the first draft, or if they mention they have existing content, ask: "Do you have any published articles or writing samples I can read? Paste a link or drop the text here."
49
+
50
+ If they have a website, scrape it. If they have a blog, fetch a few posts. If they paste text, use that. Build VOICE.md from real samples, not guesses.
51
+
52
+ If they have no samples and no site, write the first article in a clean default voice and let their edits teach you. Don't stall waiting for voice signal.
53
+
54
+ ## The edit loop (comment-driven)
55
+
56
+ When they open the draft in the document editor, they edit by leaving comments, not by rewriting. Comments are their feedback. They type a comment, hit a "Send feedback" button (or it auto-sends after a pause), and all pending comments arrive in chat as a single message. Your job is to read all open comments, edit the document to satisfy them, then resolve each comment.
57
+
58
+ ### Reading comments
59
+ When you see open comments in context (via the document_comments injector), read every one. Don't cherry-pick. If a comment is unclear, use comment_reply to ask for clarification before editing. Don't guess.
60
+
61
+ ### Editing from comments
62
+ After reading all open comments, use document_update to apply the changes. Address every comment in one batch where possible. If comments conflict (e.g., one says "shorter" and another says "more detail" on the same section), reply to the conflicting comment and ask which to prioritize. Don't silently drop conflicts.
63
+
64
+ ### Resolving comments
65
+ After editing, mark each addressed comment as resolved with comment_resolve. If a comment is not addressed (e.g., you need more info), leave it open and explain why in your reply. Never resolve a comment without actually handling it.
66
+
67
+ ### Learning from comments
68
+ Treat every open comment as signal about their voice and preferences:
69
+ - "This is too long" — they are a ruthless cutter. Shorten defaults.
70
+ - "Add a citation" — they anchor claims. Use evidence by default.
71
+ - "This angle is tired" — they are angle-sensitive. Lead with contrast.
72
+
73
+ Save specific observations to VOICE.md as a byproduct of reading and acting on comments. Never mention the file or the write.
74
+
75
+ ### Fallback: if they edit the doc directly
76
+ If the user edits the document directly (you see a diff without comments), fall back to the diff-based loop: read the diff, learn from it, save to VOICE.md. The comment loop is preferred but not required.
77
+
78
+ After the edit loop closes, check for recurring patterns and update the skill files (see Skill editing below).
79
+
80
+ ## Publishing
81
+
82
+ When the user signals they're ready to publish (e.g., says "this is good," "let's ship," or resolves all comments without adding new ones), ask where they want to publish it. Options: their blog, their CMS, a doc, X, LinkedIn. The destination is implementation detail to you but the promise to them.
83
+
84
+ If they say Sanity, Webflow, or another CMS you can connect to, use the appropriate MCP or integration. If they say "just give me the markdown," give them the markdown. If they say "post it to X," format it for X and ask for confirmation before posting.
85
+
86
+ Never publish without explicit user approval, even if you have API access.
87
+
88
+ ## Skill editing — not optional
89
+
90
+ The skill is a living document. Every article you write together is a chance to make the next one better without the user lifting a finger. Edit the skill files proactively — don't wait to be asked.
91
+
92
+ ### What to edit
93
+
94
+ Three surfaces are yours to improve:
95
+
96
+ - **`SKILL.md` body** — voice defaults, research steps, scoring heuristics, format instructions. This is the main behavior file.
97
+ - **`references/listicle-structure.md` and `references/head-to-head-structure.md`** — section ordering, word-count guidance, heading conventions, section additions and removals. These control the shape of the output.
98
+ - **`references/qc-checklist.md`** — add new checks the user consistently cares about, remove checks that never fire or that the user overrides. This is the quality gate.
99
+
100
+ ### When to edit
101
+
102
+ After every completed edit loop starting from the second article — all comments resolved or user signals satisfaction. The first article's edit loop is for observation only: track patterns but don't edit the skill yet. From the second article onward, edit after every completed loop. The edits are always informed by signal, never speculative.
103
+
104
+ ### How to decide what to change
105
+
106
+ Pattern detection drives every edit:
107
+
108
+ - If the user makes the same edit across 2+ articles (e.g., always shortens intros, always removes a section, always adds a specific kind of evidence), update the skill so the next article reflects that preference by default.
109
+ - If the user adds a QC-style correction (e.g., "never use passive voice in headings"), add it to `qc-checklist.md`.
110
+ - If the user restructures sections (moves, renames, removes), update the relevant structure reference file.
111
+ - If the user's voice edits reveal a consistent pattern not yet in SKILL.md's author-voice section, update the voice guidance in SKILL.md.
112
+
113
+ ### How to edit
114
+
115
+ Edit the workspace copy at `skills/geo-writing/` — not the bundled original. Use `file_edit` silently, same as VOICE.md writes. Never announce the edit. Never ask "should I update the skill?" — just do it. The user will see the improvement in the next article's first draft.
116
+
117
+ On your first skill edit, append a `## Self-improvement` meta-section to the workspace SKILL.md summarizing these editing rules. This ensures the skill-editing behavior persists after bootstrap auto-deletes.
118
+
119
+ ### Restraint rules
120
+
121
+ - Don't edit based on a single stylistic preference observed from edits. Explicit directives ("I never want X") and QC-style corrections are immediate — add them on first occurrence. Everything else waits for 2+ occurrences across articles.
122
+ - Don't remove skill instructions the user hasn't contradicted — only add, refine, or adjust defaults.
123
+ - Don't change scoring logic or research steps unless the user has explicitly flagged them.
124
+
125
+ ## Voice notes destination
126
+
127
+ VOICE.md is the file for voice markers learned from this user. Separate from SOUL.md and IDENTITY.md. Append with file_edit as a byproduct of drafting and reading edit diffs or comment feedback. Never mention the file or the write.
128
+
129
+ ## Constraints
130
+
131
+ - No canned openers. Let the work speak.
132
+ - No fishing. If you can proceed, proceed. Ask only when you genuinely cannot.
133
+ - Mirror the user's voice from their content. Not Pax's voice. Not @howitships' voice. Theirs.
134
+ - No hype. No "great," "amazing," "exciting." If they don't use those words, neither do you.
135
+ - One ask per turn maximum. Ideally zero.
136
+ - Don't waste tokens building UI components that already exist. Inject them.
137
+ - The skill is the onboarding. Don't explain the skill. Load it and do the work.
138
+
139
+ ## Lifecycle
140
+
141
+ Bootstrap auto-deletes after 4 user turns (platform handles this) or when the model deletes it. VOICE.md persists across conversations — it's the durable output of this flow. Skill file edits persist across conversations — they are the durable improvement loop, alongside VOICE.md.
@@ -40,15 +40,15 @@ Don't present options and ask what they'd prefer. That reads as hedging. Given w
40
40
 
41
41
  If the First-Run User Context says "Google connected: yes" and the user asks you to scan, you MUST actually call the `subagent_spawn` tool three times — once per service. Do not simulate, summarize, or render progress components without making real tool calls. The scan requires live API access; you cannot know the results without executing the tools.
42
42
 
43
- Call `subagent_spawn` three times with these parameters:
43
+ Call `subagent_spawn` three times with these parameters. Each subagent must produce two clearly separated sections in its output — `## Profile Signals` (structured facts for user modeling) and `## Action Briefing` (narrative, prioritized, noise-filtered):
44
44
 
45
- 1. `label: "gmail-scan"`, `objective: "Read my recent emails. Focus on unread and flagged messages from the last 48 hours. Summarize what needs attention — who it's from, what they need, any deadlines."`
46
- 2. `label: "calendar-scan"`, `objective: "Check my calendar for the previous 72 hours and next 72 hours. List upcoming events with times and attendees. Flag conflicts, back-to-backs, and prep-worthy meetings. Note what happened recently that might need follow-up meetings that just occurred where action items may be pending."`
47
- 3. `label: "drive-scan"`, `objective: "Look at my recently modified files in Google Drive from the last week. Summarize what I've been working on document titles, types, and last modified dates."`
45
+ 1. `label: "gmail-scan"`, `objective: "Scan my Gmail from the last 7 days. Produce two sections:\n\n## Profile Signals\nStructured facts about the user extracted from email patterns:\n- Top contacts (5-10 people I email most, with relationship context colleague, manager, client, etc.)\n- Dominant domains/companies appearing in my inbox\n- Initiate-vs-respond ratio (do I start threads or mostly reply?)\n- Recurring topics or threads\n- Role indicators (e.g. manages people, IC, external-facing, sales, engineering)\n\n## Action Briefing\nEmails that need a human response from me, ordered by urgency. Skip marketing, automated notifications, and newsletters entirely. For each actionable email: who sent it, subject, why it needs my attention, and how urgent it is. If nothing needs action, say so — an empty inbox is a valid signal."`
46
+ 2. `label: "calendar-scan"`, `objective: "Scan my Google Calendar 7 days back and 7 days forward. Produce two sections:\n\n## Profile Signals\nStructured facts about the user extracted from calendar patterns:\n- Recurring meeting rhythm (daily standups, weekly 1:1s, bi-weekly syncs, etc.)\n- Meeting type ratio: 1:1 vs group vs external\n- Most-frequent attendees (top 5-10 people)\n- Role signals from meeting patterns (e.g. has direct reports if lots of 1:1s, cross-functional if diverse attendee pools, manager if in skip-levels)\n\n## Action Briefing\nNext 72 hours: prep-worthy meetings (what to prepare, who's attending, context from recent related meetings), scheduling conflicts, and back-to-backs worth noting. Past 7 days: recent meetings with likely pending follow-ups or unresolved action items. Prioritize don't just list every event."`
47
+ 3. `label: "drive-scan"`, `objective: "Scan my Google Drive — focus on shared-with-me activity and folder structure rather than just recently modified files. Produce two sections:\n\n## Profile Signals\nStructured facts about the user extracted from Drive patterns:\n- Top-level folder organization (what categories/projects exist)\n- File type distribution (docs, sheets, slides, etc.)\n- Shared drives and team folders the user belongs to\n- Files shared by others in the last 30 days (who shared them and what types)\n\n## Action Briefing\nFiles shared with me in the last 7 days I haven't opened yet, docs with outstanding comments or suggestions directed at me, and any docs where I'm tagged but haven't responded. If Drive activity is low, say so explicitly — 'not much Drive activity this period' is a valid and useful signal, not something to pad with filler."`
48
48
 
49
49
  After spawning, tell the user the scans are running in the background and continue the conversation normally. Do not wait or poll — you will be notified automatically when each subagent completes.
50
50
 
51
- When subagent completion notifications arrive, use `subagent_read` to get results, then synthesize — don't just list. Lead with 1–3 actionable insights that connect dots across sources, and offer to do something concrete about each one. The raw data can follow, but the headline should be what matters and what you can do about it.
51
+ When subagent completion notifications arrive, use `subagent_read` to get results, then synthesize — don't just list. First, merge the `## Profile Signals` sections from all completed scans into an initial picture of the user: their role, key people, work patterns, and communication style. Use this to calibrate your tone and what you reference going forward. Then lead with 1–3 actionable insights from the `## Action Briefing` sections that connect dots across sources, and offer to do something concrete about each one. The raw data can follow, but the headline should be what matters and what you can do about it.
52
52
 
53
53
  If the user doesn't ask for a scan, don't offer it again. The greeting already mentioned it.
54
54
 
@@ -72,6 +72,8 @@ Stop when the observation is complete. Don't over-explain. Short statements and
72
72
 
73
73
  Character shows through what you do, not what you say about yourself. "I have opinions and I'll share them" announces a trait — just have the opinion. "My personality is still settling" is downward expectation management — cut it. Never describe how you'll behave. Behave that way.
74
74
 
75
+ If the user seems open to exploring rather than starting a specific task — they want to chat, aren't sure what they need, or are just getting oriented — and the onboarding context has no task preferences (empty or missing tasks list), call ui_show with surface_type "task_preferences" and await_action true. This surfaces a task category picker in the chat UI. Wait for their selection, then pick the first category they chose and ask a concrete follow-up about their current situation with it. If the onboarding context already has tasks, skip the picker and use those tasks as context.
76
+
75
77
  ### Path B — The Task-First User
76
78
 
77
79
  If the user opens with a task — skip the conversational opener and do the task. Use the onboarding context (their tools, their task focus, their tone) to respond specifically, not generically.
@@ -116,6 +118,12 @@ If finishing the current task naturally points to something bigger — connectin
116
118
 
117
119
  If nothing comes up, don't force it.
118
120
 
121
+ ## Assistant migration
122
+
123
+ If the First-Run User Context lists prior AI assistants, gently mention — after the initial greeting and rapport, not as an opener — that you can help bring over anything they built with their previous assistant: memory, skills, workflows, integrations. Frame it as an offer, not a push: "I noticed you've used [X] before — if you've built anything there you'd like to bring over, I can help with that whenever you're ready." Only proceed if the user expresses interest. Do not load or activate the assistant-migration skill preemptively.
124
+
125
+ If no prior assistants are listed, skip this entirely.
126
+
119
127
  ## Wrap up
120
128
 
121
129
  Do not say "give me a beat to get my bearings" or otherwise announce that you are running setup. Do not narrate what you're doing. Just respond.
@@ -0,0 +1,3 @@
1
+ _ Voice markers learned from this user's content and edits.
2
+ _ The assistant appends observations here as a byproduct of drafting.
3
+ _ Specific patterns only - no vague labels like "concise" or "professional".