@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
@@ -84,6 +84,82 @@ describe("renderHistoryContent", () => {
84
84
  );
85
85
  });
86
86
 
87
+ test("emits attachment:N entries in contentOrder for interleaved file blocks", () => {
88
+ const output = renderHistoryContent([
89
+ { type: "text", text: "first paragraph" },
90
+ {
91
+ type: "file",
92
+ source: {
93
+ type: "base64",
94
+ media_type: "text/markdown",
95
+ filename: "dream-015.md",
96
+ data: Buffer.from("a").toString("base64"),
97
+ },
98
+ _attachmentId: "att-015",
99
+ },
100
+ {
101
+ type: "file",
102
+ source: {
103
+ type: "base64",
104
+ media_type: "text/markdown",
105
+ filename: "dream-016.md",
106
+ data: Buffer.from("b").toString("base64"),
107
+ },
108
+ _attachmentId: "att-016",
109
+ },
110
+ { type: "text", text: "trailing paragraph" },
111
+ ]);
112
+
113
+ expect(output.contentOrder).toEqual([
114
+ "text:0",
115
+ "attachment:0",
116
+ "attachment:1",
117
+ "text:1",
118
+ ]);
119
+ expect(output.attachments).toEqual([
120
+ {
121
+ attachmentId: "att-015",
122
+ filename: "dream-015.md",
123
+ mimeType: "text/markdown",
124
+ sizeBytes: 1,
125
+ },
126
+ {
127
+ attachmentId: "att-016",
128
+ filename: "dream-016.md",
129
+ mimeType: "text/markdown",
130
+ sizeBytes: 1,
131
+ },
132
+ ]);
133
+ // Trailing-segment summary is preserved for legacy channel-reply
134
+ // delivery (Slack/Telegram outbound text) and iOS rehydrate.
135
+ expect(output.textSegments[1]).toContain("[File attachment] dream-015.md");
136
+ });
137
+
138
+ test("omits attachmentId when file block has no _attachmentId", () => {
139
+ const output = renderHistoryContent([
140
+ {
141
+ type: "file",
142
+ source: {
143
+ type: "base64",
144
+ media_type: "application/pdf",
145
+ filename: "legacy.pdf",
146
+ data: Buffer.from("x").toString("base64"),
147
+ },
148
+ },
149
+ ]);
150
+
151
+ // attachment:0 marks the file's inline position; text:0 follows because
152
+ // the legacy summary append (for Slack/iOS) opens a trailing segment.
153
+ expect(output.contentOrder).toEqual(["attachment:0", "text:0"]);
154
+ expect(output.attachments).toEqual([
155
+ {
156
+ filename: "legacy.pdf",
157
+ mimeType: "application/pdf",
158
+ sizeBytes: 1,
159
+ },
160
+ ]);
161
+ });
162
+
87
163
  test("falls back to string conversion for non-array content", () => {
88
164
  expect(renderHistoryContent("raw string").text).toBe("raw string");
89
165
  expect(renderHistoryContent(null).text).toBe("");
@@ -320,14 +396,17 @@ describe("renderHistoryContent", () => {
320
396
  expect(output.toolCallsBeforeText).toBe(false);
321
397
  });
322
398
 
323
- test("handles orphan tool_result without matching tool_use", () => {
399
+ test("drops orphan tool_result without matching tool_use", () => {
324
400
  const output = renderHistoryContent([
325
401
  { type: "tool_result", tool_use_id: "missing", content: "some result" },
326
402
  ]);
327
403
 
328
- expect(output.toolCalls).toEqual([
329
- { name: "unknown", input: {}, result: "some result", isError: false },
330
- ]);
404
+ // Orphans are dropped — without the parent tool_use we can't tell the user
405
+ // what tool ran, so the result is meaningless. See shared.ts comment.
406
+ expect(output.toolCalls).toEqual([]);
407
+ expect(output.text).toBe("");
408
+ expect(output.textSegments).toEqual([]);
409
+ expect(output.contentOrder).toEqual([]);
331
410
  });
332
411
 
333
412
  test("produces textSegments for text-tool-text interleaving", () => {
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Tests for the tool-result repair logic that runs during queue drain
3
+ * after a steer-to-message abort interrupts an in-flight tool call.
4
+ *
5
+ * Uses the exported drainQueue indirectly by testing the repair helper's
6
+ * observable effects on conversation.messages. We import the
7
+ * repairPendingToolUseBlocks behavior through drainQueue's contract.
8
+ */
9
+
10
+ import { describe, expect, test } from "bun:test";
11
+
12
+ import type { Message } from "../providers/types.js";
13
+
14
+ /**
15
+ * Minimal reproduction of repairPendingToolUseBlocks logic for direct
16
+ * unit testing. The real implementation lives in conversation-process.ts.
17
+ * This mirrors it exactly to avoid needing the full ProcessConversationContext.
18
+ */
19
+ function repairPendingToolUseBlocks(messages: Message[]): Message[] {
20
+ if (messages.length === 0) return messages;
21
+
22
+ const resolvedToolUseIds = new Set<string>();
23
+ const pendingToolUseIds: string[] = [];
24
+ for (let i = messages.length - 1; i >= 0; i--) {
25
+ const msg = messages[i];
26
+ if (msg.role === "user") {
27
+ for (const block of msg.content) {
28
+ if (block.type === "tool_result") {
29
+ resolvedToolUseIds.add(block.tool_use_id);
30
+ }
31
+ }
32
+ } else if (msg.role === "assistant") {
33
+ for (const block of msg.content) {
34
+ if (block.type === "tool_use" && !resolvedToolUseIds.has(block.id)) {
35
+ pendingToolUseIds.push(block.id);
36
+ }
37
+ }
38
+ break;
39
+ }
40
+ }
41
+
42
+ if (pendingToolUseIds.length === 0) return messages;
43
+
44
+ const syntheticContent = pendingToolUseIds.map((toolUseId) => ({
45
+ type: "tool_result" as const,
46
+ tool_use_id: toolUseId,
47
+ content: "Tool execution was interrupted by user steering.",
48
+ is_error: true,
49
+ }));
50
+ messages.push({ role: "user", content: syntheticContent });
51
+ return messages;
52
+ }
53
+
54
+ describe("steer tool-result repair", () => {
55
+ test("no-op when messages are empty", () => {
56
+ const messages: Message[] = [];
57
+ repairPendingToolUseBlocks(messages);
58
+ expect(messages).toHaveLength(0);
59
+ });
60
+
61
+ test("no-op when last message is a user message", () => {
62
+ const messages: Message[] = [
63
+ {
64
+ role: "user",
65
+ content: [{ type: "text", text: "hello" }],
66
+ },
67
+ ];
68
+ repairPendingToolUseBlocks(messages);
69
+ expect(messages).toHaveLength(1);
70
+ });
71
+
72
+ test("no-op when last assistant message has no tool_use blocks", () => {
73
+ const messages: Message[] = [
74
+ {
75
+ role: "user",
76
+ content: [{ type: "text", text: "hello" }],
77
+ },
78
+ {
79
+ role: "assistant",
80
+ content: [{ type: "text", text: "hi there" }],
81
+ },
82
+ ];
83
+ repairPendingToolUseBlocks(messages);
84
+ expect(messages).toHaveLength(2);
85
+ });
86
+
87
+ test("no-op when tool_use has a matching tool_result", () => {
88
+ const messages: Message[] = [
89
+ {
90
+ role: "user",
91
+ content: [{ type: "text", text: "hello" }],
92
+ },
93
+ {
94
+ role: "assistant",
95
+ content: [
96
+ { type: "text", text: "let me run a command" },
97
+ {
98
+ type: "tool_use",
99
+ id: "tu_1",
100
+ name: "bash",
101
+ input: { command: "ls" },
102
+ },
103
+ ],
104
+ },
105
+ {
106
+ role: "user",
107
+ content: [
108
+ {
109
+ type: "tool_result",
110
+ tool_use_id: "tu_1",
111
+ content: "file1.txt\nfile2.txt",
112
+ },
113
+ ],
114
+ },
115
+ ];
116
+ repairPendingToolUseBlocks(messages);
117
+ // No synthetic message added
118
+ expect(messages).toHaveLength(3);
119
+ });
120
+
121
+ test("injects synthetic tool_result for a single pending tool_use", () => {
122
+ const messages: Message[] = [
123
+ {
124
+ role: "user",
125
+ content: [{ type: "text", text: "hello" }],
126
+ },
127
+ {
128
+ role: "assistant",
129
+ content: [
130
+ { type: "text", text: "let me run a command" },
131
+ {
132
+ type: "tool_use",
133
+ id: "tu_1",
134
+ name: "bash",
135
+ input: { command: "ls" },
136
+ },
137
+ ],
138
+ },
139
+ ];
140
+ repairPendingToolUseBlocks(messages);
141
+ expect(messages).toHaveLength(3);
142
+
143
+ const synthetic = messages[2];
144
+ expect(synthetic.role).toBe("user");
145
+ expect(synthetic.content).toHaveLength(1);
146
+ expect(synthetic.content[0].type).toBe("tool_result");
147
+ if (synthetic.content[0].type === "tool_result") {
148
+ expect(synthetic.content[0].tool_use_id).toBe("tu_1");
149
+ expect(synthetic.content[0].is_error).toBe(true);
150
+ expect(synthetic.content[0].content).toContain("interrupted");
151
+ }
152
+ });
153
+
154
+ test("injects synthetic tool_results for multiple pending tool_use blocks", () => {
155
+ const messages: Message[] = [
156
+ {
157
+ role: "user",
158
+ content: [{ type: "text", text: "hello" }],
159
+ },
160
+ {
161
+ role: "assistant",
162
+ content: [
163
+ {
164
+ type: "tool_use",
165
+ id: "tu_1",
166
+ name: "bash",
167
+ input: { command: "ls" },
168
+ },
169
+ {
170
+ type: "tool_use",
171
+ id: "tu_2",
172
+ name: "read_file",
173
+ input: { path: "/tmp/foo" },
174
+ },
175
+ ],
176
+ },
177
+ ];
178
+ repairPendingToolUseBlocks(messages);
179
+ expect(messages).toHaveLength(3);
180
+
181
+ const synthetic = messages[2];
182
+ expect(synthetic.role).toBe("user");
183
+ expect(synthetic.content).toHaveLength(2);
184
+
185
+ const ids = synthetic.content
186
+ .filter((b) => b.type === "tool_result")
187
+ .map((b) => (b as { tool_use_id: string }).tool_use_id);
188
+ expect(ids).toContain("tu_1");
189
+ expect(ids).toContain("tu_2");
190
+ });
191
+
192
+ test("handles partial resolution — one of two tool_use blocks resolved", () => {
193
+ const messages: Message[] = [
194
+ {
195
+ role: "user",
196
+ content: [{ type: "text", text: "hello" }],
197
+ },
198
+ {
199
+ role: "assistant",
200
+ content: [
201
+ {
202
+ type: "tool_use",
203
+ id: "tu_1",
204
+ name: "bash",
205
+ input: { command: "ls" },
206
+ },
207
+ {
208
+ type: "tool_use",
209
+ id: "tu_2",
210
+ name: "read_file",
211
+ input: { path: "/tmp/foo" },
212
+ },
213
+ ],
214
+ },
215
+ {
216
+ role: "user",
217
+ content: [
218
+ {
219
+ type: "tool_result",
220
+ tool_use_id: "tu_1",
221
+ content: "file1.txt",
222
+ },
223
+ ],
224
+ },
225
+ {
226
+ role: "assistant",
227
+ content: [
228
+ { type: "text", text: "now reading the file" },
229
+ {
230
+ type: "tool_use",
231
+ id: "tu_3",
232
+ name: "write_file",
233
+ input: { path: "/tmp/bar", content: "test" },
234
+ },
235
+ ],
236
+ },
237
+ ];
238
+ repairPendingToolUseBlocks(messages);
239
+ expect(messages).toHaveLength(5);
240
+
241
+ const synthetic = messages[4];
242
+ expect(synthetic.role).toBe("user");
243
+ expect(synthetic.content).toHaveLength(1);
244
+ if (synthetic.content[0].type === "tool_result") {
245
+ expect(synthetic.content[0].tool_use_id).toBe("tu_3");
246
+ expect(synthetic.content[0].is_error).toBe(true);
247
+ }
248
+ });
249
+ });
@@ -78,13 +78,14 @@ const {
78
78
  } = await import("../prompts/system-prompt.js");
79
79
 
80
80
  /**
81
- * Extract IDENTITY.md / BOOTSTRAP.md content + the user persona from the
82
- * dynamic block of the system prompt, stripping configuration, skills
83
- * catalog, and connected services.
81
+ * Extract BOOTSTRAP.md content + the user persona from the dynamic block
82
+ * of the system prompt, stripping configuration, skills catalog, and
83
+ * connected services.
84
84
  *
85
- * SOUL.md no longer flows through this helper — it renders as the
86
- * `09-soul` workspace-backed section in the static (cached) prefix.
87
- * Tests that assert on SOUL.md content slice the static block directly.
85
+ * Neither SOUL.md nor IDENTITY.md flows through this helper — they
86
+ * render as the `09-soul` and `08-identity` workspace-backed sections in
87
+ * the static (cached) prefix. Tests that assert on their content slice
88
+ * the static block directly.
88
89
  */
89
90
  function basePrompt(result: string): string {
90
91
  // The workspace files are in the dynamic block after the cache boundary.
@@ -154,18 +155,30 @@ describe("buildSystemPrompt", () => {
154
155
  "# My Identity\n\nI am Vellum.",
155
156
  );
156
157
  const result = buildSystemPrompt();
157
- expect(basePrompt(result)).toBe("# My Identity\n\nI am Vellum.");
158
+ // IDENTITY.md renders as the `08-identity` workspace-backed section
159
+ // in the static (cached) prefix before SYSTEM_PROMPT_CACHE_BOUNDARY.
160
+ const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
161
+ expect(boundaryIdx).toBeGreaterThan(-1);
162
+ expect(result.slice(0, boundaryIdx)).toContain(
163
+ "# My Identity\n\nI am Vellum.",
164
+ );
158
165
  });
159
166
 
160
167
  test("composes IDENTITY.md + SOUL.md when both exist", () => {
161
168
  writeFileSync(join(TEST_DIR, "IDENTITY.md"), "# Identity\n\nI am Vellum.");
162
169
  writeFileSync(join(TEST_DIR, "SOUL.md"), "# Soul\n\nBe thoughtful.");
163
170
  const result = buildSystemPrompt();
164
- // SOUL renders as the workspace-backed section in the static prefix;
165
- // IDENTITY renders in the dynamic suffix.
171
+ // Both render in the static (cached) prefix, IDENTITY before SOUL
172
+ // (sections `08-identity` and `09-soul`).
166
173
  const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
167
- expect(result.slice(0, boundaryIdx)).toContain("# Soul\n\nBe thoughtful.");
168
- expect(basePrompt(result)).toBe("# Identity\n\nI am Vellum.");
174
+ expect(boundaryIdx).toBeGreaterThan(-1);
175
+ const staticBlock = result.slice(0, boundaryIdx);
176
+ expect(staticBlock).toContain("# Identity\n\nI am Vellum.");
177
+ expect(staticBlock).toContain("# Soul\n\nBe thoughtful.");
178
+ const identityIdx = staticBlock.indexOf("# Identity\n\nI am Vellum.");
179
+ const soulIdx = staticBlock.indexOf("# Soul\n\nBe thoughtful.");
180
+ expect(identityIdx).toBeLessThan(soulIdx);
181
+ expect(basePrompt(result)).toBe("");
169
182
  });
170
183
 
171
184
  test("ignores empty SOUL.md", () => {
@@ -217,10 +230,9 @@ describe("buildSystemPrompt", () => {
217
230
  writeFileSync(join(TEST_DIR, "SOUL.md"), "Soul content");
218
231
 
219
232
  const result = buildSystemPrompt();
220
- // After SOUL.md became the `09-soul` workspace-backed section, it
221
- // renders in the static prefix while IDENTITY stays in the dynamic
222
- // suffix — the two are no longer adjacent. Verify both are present
223
- // and the skills catalog is still suppressed.
233
+ // Both files render in the static prefix via `08-identity` /
234
+ // `09-soul`. Verify both are present and the skills catalog is
235
+ // still suppressed.
224
236
  expect(result).toContain("Identity content");
225
237
  expect(result).toContain("Soul content");
226
238
  expect(result).not.toContain("## Available Skills");
@@ -262,11 +274,10 @@ describe("buildSystemPrompt", () => {
262
274
  writeFileSync(join(TEST_DIR, "IDENTITY.md"), "Identity");
263
275
  writeFileSync(join(TEST_DIR, "SOUL.md"), "Soul");
264
276
  const result = buildSystemPrompt();
265
- // SOUL.md now renders in the static (cached) prefix via the 09-soul
266
- // section, so it doesn't flow through basePrompt. Assert that
267
- // IDENTITY is the only dynamic content and that SOUL is still in the
268
- // full prompt.
269
- expect(basePrompt(result)).toBe("Identity");
277
+ // Both IDENTITY and SOUL render in the static (cached) prefix; the
278
+ // dynamic block sliced by basePrompt is empty here.
279
+ expect(basePrompt(result)).toBe("");
280
+ expect(result).toContain("Identity");
270
281
  expect(result).toContain("Soul");
271
282
  });
272
283
 
@@ -280,7 +291,8 @@ describe("buildSystemPrompt", () => {
280
291
  );
281
292
  const result = buildSystemPrompt();
282
293
  expect(result).not.toContain("stale user content");
283
- expect(basePrompt(result)).toBe("Identity");
294
+ expect(result).toContain("Identity");
295
+ expect(basePrompt(result)).toBe("");
284
296
  });
285
297
 
286
298
  test("uses options.userPersona instead of USER.md", () => {
@@ -289,11 +301,10 @@ describe("buildSystemPrompt", () => {
289
301
  const result = buildSystemPrompt({
290
302
  userPersona: "# User persona\n\nName: Alice",
291
303
  });
292
- // SOUL.md renders in the static (cached) prefix via the 09-soul section
293
- // and is no longer part of the dynamic block sliced by basePrompt.
294
- expect(basePrompt(result)).toBe(
295
- "Identity\n\n# User persona\n\nName: Alice",
296
- );
304
+ // IDENTITY and SOUL both render in the static (cached) prefix; only
305
+ // the user persona ends up in the dynamic block.
306
+ expect(basePrompt(result)).toBe("# User persona\n\nName: Alice");
307
+ expect(result).toContain("Identity");
297
308
  expect(result).toContain("Soul");
298
309
  });
299
310
 
@@ -480,7 +491,11 @@ describe("buildSystemPrompt", () => {
480
491
  "# Identity\n_ This is a comment\nI am Vellum.\n_ Another comment",
481
492
  );
482
493
  const result = buildSystemPrompt();
483
- expect(basePrompt(result)).toBe("# Identity\nI am Vellum.");
494
+ // IDENTITY.md renders in the static prefix via the 08-identity section,
495
+ // so we assert against the full prompt rather than basePrompt.
496
+ expect(result).toContain("# Identity\nI am Vellum.");
497
+ expect(result).not.toContain("_ This is a comment");
498
+ expect(result).not.toContain("_ Another comment");
484
499
  });
485
500
 
486
501
  test("collapses whitespace around stripped comment lines", () => {
@@ -610,7 +625,15 @@ describe("buildSystemPrompt", () => {
610
625
  writeFileSync(join(TEST_DIR, "IDENTITY.md"), "I am Vellum.");
611
626
  const result = buildSystemPrompt();
612
627
  expect(result.startsWith("Custom prefix")).toBe(true);
613
- expect(basePrompt(result)).toBe("I am Vellum.");
628
+ // IDENTITY.md renders via 08-identity in the static prefix after
629
+ // the 00-prefix slot.
630
+ const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
631
+ const staticBlock = result.slice(0, boundaryIdx);
632
+ const prefixIdx = staticBlock.indexOf("Custom prefix");
633
+ const identityIdx = staticBlock.indexOf("I am Vellum.");
634
+ expect(prefixIdx).toBeGreaterThan(-1);
635
+ expect(identityIdx).toBeGreaterThan(prefixIdx);
636
+ expect(basePrompt(result)).toBe("");
614
637
  });
615
638
 
616
639
  test("parallel tool calls section is sourced from workspace when present", () => {
@@ -675,10 +698,7 @@ describe("buildSystemPrompt", () => {
675
698
  // so the bundled body must not leak into the rendered output. This is
676
699
  // the explicit "user silenced this section" path.
677
700
  mkdirSync(SYSTEM_PROMPTS_DIR, { recursive: true });
678
- writeFileSync(
679
- PARALLEL_FILE,
680
- "---\nenabled: false\n---\nIgnored body.\n",
681
- );
701
+ writeFileSync(PARALLEL_FILE, "---\nenabled: false\n---\nIgnored body.\n");
682
702
  const result = buildSystemPrompt();
683
703
  expect(result).not.toContain("<use_parallel_tool_calls>");
684
704
  expect(result).not.toContain("Batch independent tool calls");
@@ -713,7 +733,10 @@ describe("buildSystemPrompt", () => {
713
733
  });
714
734
 
715
735
  describe("containerized section (slot 02)", () => {
716
- const CONTAINERIZED_FILE = join(SYSTEM_PROMPTS_DIR, "02-containerized.md");
736
+ const CONTAINERIZED_FILE = join(
737
+ SYSTEM_PROMPTS_DIR,
738
+ "02-containerized.md",
739
+ );
717
740
 
718
741
  // The runtime gate is `isContainerized` on the render context, sourced
719
742
  // from `getIsContainerized()` which reads `process.env.IS_CONTAINERIZED`.
@@ -1093,10 +1116,7 @@ describe("buildSystemPrompt", () => {
1093
1116
  });
1094
1117
 
1095
1118
  describe("external-content section (slot 07)", () => {
1096
- const EXTERNAL_FILE = join(
1097
- SYSTEM_PROMPTS_DIR,
1098
- "07-external-content.md",
1099
- );
1119
+ const EXTERNAL_FILE = join(SYSTEM_PROMPTS_DIR, "07-external-content.md");
1100
1120
 
1101
1121
  test("workspace external-content file is rendered into the static block", () => {
1102
1122
  mkdirSync(SYSTEM_PROMPTS_DIR, { recursive: true });
@@ -1139,69 +1159,6 @@ describe("buildSystemPrompt", () => {
1139
1159
  });
1140
1160
  });
1141
1161
 
1142
- describe("background-conversation section (slot 08)", () => {
1143
- const BACKGROUND_FILE = join(
1144
- SYSTEM_PROMPTS_DIR,
1145
- "08-background-conversation.md",
1146
- );
1147
-
1148
- test("bundled default renders when isBackgroundConversation is true", () => {
1149
- mkdirSync(SYSTEM_PROMPTS_DIR, { recursive: true });
1150
- const result = buildSystemPrompt({ isBackgroundConversation: true });
1151
- expect(result).toContain("## Background Conversation");
1152
- expect(result).toContain("non-interactive background job");
1153
- expect(result).toContain("`notifications` skill");
1154
- });
1155
-
1156
- test("bundled default is gated out when isBackgroundConversation is false", () => {
1157
- // Mustache `{{#isBackgroundConversation}}...{{/isBackgroundConversation}}`
1158
- // wraps the entire heading + body so the slot interpolates to empty
1159
- // in foreground conversations and the renderer drops it.
1160
- mkdirSync(SYSTEM_PROMPTS_DIR, { recursive: true });
1161
- const result = buildSystemPrompt({ isBackgroundConversation: false });
1162
- expect(result).not.toContain("## Background Conversation");
1163
- });
1164
-
1165
- test("bundled default is gated out when isBackgroundConversation is omitted", () => {
1166
- // `ctx.isBackgroundConversation` is normalized to `false` when the
1167
- // caller omits the flag, so the gated section drops out cleanly.
1168
- mkdirSync(SYSTEM_PROMPTS_DIR, { recursive: true });
1169
- const result = buildSystemPrompt();
1170
- expect(result).not.toContain("## Background Conversation");
1171
- });
1172
-
1173
- test("workspace override is also gated by isBackgroundConversation", () => {
1174
- // Workspace overrides flow through the same mustache interpolation,
1175
- // so authors can rely on the same `{{#isBackgroundConversation}}`
1176
- // gate in their custom body.
1177
- mkdirSync(SYSTEM_PROMPTS_DIR, { recursive: true });
1178
- writeFileSync(
1179
- BACKGROUND_FILE,
1180
- "{{#isBackgroundConversation}}## Background Conversation\n\nWorkspace override marker COMET_3K.\n{{/isBackgroundConversation}}\n",
1181
- );
1182
-
1183
- const offResult = buildSystemPrompt({ isBackgroundConversation: false });
1184
- expect(offResult).not.toContain("## Background Conversation");
1185
- expect(offResult).not.toContain("Workspace override marker COMET_3K.");
1186
-
1187
- const onResult = buildSystemPrompt({ isBackgroundConversation: true });
1188
- expect(onResult).toContain("## Background Conversation");
1189
- expect(onResult).toContain("Workspace override marker COMET_3K.");
1190
- });
1191
-
1192
- test("renders after the external-content section when both render", () => {
1193
- // Numeric prefix `08-` > `07-` so the background-conversation
1194
- // section trails the external-content section in the static block.
1195
- mkdirSync(SYSTEM_PROMPTS_DIR, { recursive: true });
1196
- const result = buildSystemPrompt({ isBackgroundConversation: true });
1197
- const externalIdx = result.indexOf("## External Content");
1198
- const backgroundIdx = result.indexOf("## Background Conversation");
1199
- expect(externalIdx).toBeGreaterThan(-1);
1200
- expect(backgroundIdx).toBeGreaterThan(-1);
1201
- expect(externalIdx).toBeLessThan(backgroundIdx);
1202
- });
1203
- });
1204
-
1205
1162
  test("unresolved {{variable}} is left as a literal in the body", () => {
1206
1163
  mkdirSync(SYSTEM_PROMPTS_DIR, { recursive: true });
1207
1164
  writeFileSync(
@@ -1211,7 +1168,6 @@ describe("buildSystemPrompt", () => {
1211
1168
  const result = buildSystemPrompt();
1212
1169
  expect(result).toContain("Has {{somethingMissing}} in body.");
1213
1170
  });
1214
-
1215
1171
  });
1216
1172
  });
1217
1173
 
@@ -67,6 +67,7 @@ mock.module("../tools/network/script-proxy/index.js", () => ({
67
67
  import {
68
68
  ALWAYS_INJECTED_ENV_VARS,
69
69
  buildSanitizedEnv,
70
+ KATA_INJECTED_ENV_VARS,
70
71
  KATA_SAFE_ENV_VARS,
71
72
  SAFE_ENV_VARS,
72
73
  } from "../tools/terminal/safe-env.js";
@@ -145,7 +146,7 @@ describe("buildSanitizedEnv", () => {
145
146
  process.env.VELLUM_SANDBOX_RUNTIME = "gvisor";
146
147
  process.env.PATH = "/usr/bin";
147
148
  process.env.VELLUM_APT_DATA_ROOT = "/data/system";
148
- process.env.LD_LIBRARY_PATH = "/data/system/usr/lib";
149
+ process.env.LD_LIBRARY_PATH = "/host/lib";
149
150
 
150
151
  let env = buildSanitizedEnv();
151
152
  expect(env.VELLUM_APT_DATA_ROOT).toBeUndefined();
@@ -161,6 +162,7 @@ describe("buildSanitizedEnv", () => {
161
162
  expect(env.LD_LIBRARY_PATH.split(":")).toContain(
162
163
  "/data/system/usr/local/lib",
163
164
  );
165
+ expect(env.LD_LIBRARY_PATH.split(":")).not.toContain("/host/lib");
164
166
  });
165
167
 
166
168
  test("defaults LANG and LC_ALL to UTF-8 when unset", () => {
@@ -181,12 +183,20 @@ describe("buildSanitizedEnv", () => {
181
183
  delete process.env.GATEWAY_PORT;
182
184
  });
183
185
 
186
+ test("Kata entrypoint initializes apt root without inheriting apt loader paths", () => {
187
+ const entrypoint = readFileSync("docker-entrypoint.sh", "utf8");
188
+
189
+ expect(entrypoint).toContain("/app/assistant/docker-init-apt-root.sh");
190
+ expect(entrypoint).not.toContain(". /app/assistant/docker-kata-apt-env.sh");
191
+ });
192
+
184
193
  test("result is a plain object with no prototype-inherited secrets", () => {
185
194
  const env = buildSanitizedEnv();
186
195
  const keys = Object.keys(env);
187
196
  const safeKeys: string[] = [
188
197
  ...SAFE_ENV_VARS,
189
198
  ...KATA_SAFE_ENV_VARS,
199
+ ...KATA_INJECTED_ENV_VARS,
190
200
  ...ALWAYS_INJECTED_ENV_VARS,
191
201
  ];
192
202
  for (const key of keys) {