@vellumai/assistant 0.6.5 → 0.6.6

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 (443) hide show
  1. package/AGENTS.md +9 -1
  2. package/ARCHITECTURE.md +15 -17
  3. package/Dockerfile +6 -4
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +283 -0
  5. package/docs/architecture/integrations.md +32 -39
  6. package/docs/architecture/memory.md +25 -30
  7. package/docs/architecture/security.md +7 -6
  8. package/docs/browser-use-architecture-phase2.md +63 -20
  9. package/docs/plugins.md +761 -0
  10. package/examples/plugins/echo/README.md +132 -0
  11. package/examples/plugins/echo/package.json +17 -0
  12. package/examples/plugins/echo/register.ts +187 -0
  13. package/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
  14. package/openapi.yaml +212 -68
  15. package/package.json +1 -1
  16. package/src/__tests__/app-compiler.test.ts +57 -0
  17. package/src/__tests__/approval-cascade.test.ts +7 -2
  18. package/src/__tests__/auto-analysis-end-to-end.test.ts +1 -0
  19. package/src/__tests__/avatar-generator.test.ts +4 -2
  20. package/src/__tests__/bundled-asset.test.ts +6 -6
  21. package/src/__tests__/catalog-cache.test.ts +69 -0
  22. package/src/__tests__/checker.test.ts +459 -171
  23. package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
  24. package/src/__tests__/compaction-events.test.ts +501 -0
  25. package/src/__tests__/compaction-pipeline.test.ts +210 -0
  26. package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
  27. package/src/__tests__/compaction-timeout-recovery.test.ts +262 -0
  28. package/src/__tests__/config-model-image-provider.test.ts +110 -0
  29. package/src/__tests__/config-schema.test.ts +22 -9
  30. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
  31. package/src/__tests__/contacts-tools.test.ts +26 -0
  32. package/src/__tests__/context-overflow-policy.test.ts +7 -7
  33. package/src/__tests__/context-window-manager.test.ts +355 -4
  34. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  35. package/src/__tests__/conversation-agent-loop-overflow.test.ts +26 -30
  36. package/src/__tests__/conversation-agent-loop.test.ts +30 -141
  37. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -1
  38. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  39. package/src/__tests__/conversation-init.benchmark.test.ts +2 -16
  40. package/src/__tests__/conversation-pairing.test.ts +174 -10
  41. package/src/__tests__/conversation-pre-run-repair.test.ts +4 -1
  42. package/src/__tests__/conversation-process-callsite.test.ts +3 -0
  43. package/src/__tests__/conversation-provider-retry-repair.test.ts +16 -7
  44. package/src/__tests__/conversation-queue.test.ts +29 -14
  45. package/src/__tests__/conversation-routes-disk-view.test.ts +7 -6
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +155 -110
  47. package/src/__tests__/conversation-runtime-workspace.test.ts +23 -38
  48. package/src/__tests__/conversation-seed-composer.test.ts +2 -2
  49. package/src/__tests__/conversation-slash-queue.test.ts +7 -2
  50. package/src/__tests__/conversation-slash-unknown.test.ts +25 -2
  51. package/src/__tests__/conversation-speed-override.test.ts +6 -1
  52. package/src/__tests__/conversation-title-service.test.ts +116 -0
  53. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +41 -2
  54. package/src/__tests__/conversation-usage.test.ts +1 -1
  55. package/src/__tests__/conversation-workspace-cache-state.test.ts +4 -1
  56. package/src/__tests__/conversation-workspace-injection.test.ts +3 -0
  57. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -1
  58. package/src/__tests__/credential-health-service.test.ts +78 -9
  59. package/src/__tests__/credential-security-invariants.test.ts +2 -2
  60. package/src/__tests__/db-schedule-syntax-migration.test.ts +1 -0
  61. package/src/__tests__/empty-response-pipeline.test.ts +305 -0
  62. package/src/__tests__/extension-id-sync-guard.test.ts +3 -3
  63. package/src/__tests__/first-greeting.test.ts +247 -5
  64. package/src/__tests__/headless-browser-mode.test.ts +57 -0
  65. package/src/__tests__/history-repair-pipeline.test.ts +399 -0
  66. package/src/__tests__/host-browser-e2e-cloud.test.ts +307 -0
  67. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +3 -3
  68. package/src/__tests__/host-proxy-interface.test.ts +36 -2
  69. package/src/__tests__/image-credentials.test.ts +137 -0
  70. package/src/__tests__/image-service-dispatcher.test.ts +186 -0
  71. package/src/__tests__/injector-chain.test.ts +526 -0
  72. package/src/__tests__/intent-routing.test.ts +0 -26
  73. package/src/__tests__/llm-call-pipeline.test.ts +285 -0
  74. package/src/__tests__/llm-schema.test.ts +1 -1
  75. package/src/__tests__/media-generate-image.test.ts +119 -13
  76. package/src/__tests__/memory-retrieval-pipeline.test.ts +401 -0
  77. package/src/__tests__/memory-upsert-concurrency.test.ts +1 -0
  78. package/src/__tests__/migration-import-from-url.test.ts +5 -68
  79. package/src/__tests__/model-intents.test.ts +4 -2
  80. package/src/__tests__/notification-broadcaster.test.ts +3 -3
  81. package/src/__tests__/notification-decision-strategy.test.ts +0 -11
  82. package/src/__tests__/notification-schedule-notify-dedup.test.ts +108 -0
  83. package/src/__tests__/oauth-apps-routes.test.ts +1 -1
  84. package/src/__tests__/oauth-cli.test.ts +14 -12
  85. package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
  86. package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
  87. package/src/__tests__/oauth-provider-visibility.test.ts +3 -5
  88. package/src/__tests__/oauth-providers-routes.test.ts +3 -2
  89. package/src/__tests__/oauth-store.test.ts +41 -76
  90. package/src/__tests__/onboarding-template-contract.test.ts +16 -64
  91. package/src/__tests__/openai-image-service.test.ts +368 -0
  92. package/src/__tests__/overflow-reduce-pipeline.test.ts +676 -0
  93. package/src/__tests__/permission-checker-host-gate.test.ts +0 -24
  94. package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
  95. package/src/__tests__/persistence-pipeline.test.ts +377 -0
  96. package/src/__tests__/pipeline-runner.test.ts +565 -0
  97. package/src/__tests__/platform.test.ts +5 -2
  98. package/src/__tests__/plugin-bootstrap.test.ts +483 -0
  99. package/src/__tests__/plugin-registry.test.ts +273 -0
  100. package/src/__tests__/plugin-route-contribution.test.ts +288 -0
  101. package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
  102. package/src/__tests__/plugin-tool-contribution.test.ts +286 -0
  103. package/src/__tests__/plugin-types.test.ts +320 -0
  104. package/src/__tests__/pricing.test.ts +44 -12
  105. package/src/__tests__/proxy-approval-callback.test.ts +69 -8
  106. package/src/__tests__/reaction-persistence.test.ts +1 -0
  107. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  108. package/src/__tests__/registry.test.ts +0 -2
  109. package/src/__tests__/schedule-routes.test.ts +131 -1
  110. package/src/__tests__/scheduler-recurrence.test.ts +14 -70
  111. package/src/__tests__/scheduler-reuse-conversation.test.ts +10 -50
  112. package/src/__tests__/secret-detection-handler.test.ts +0 -10
  113. package/src/__tests__/shell-identity.test.ts +0 -134
  114. package/src/__tests__/suggestion-routes.test.ts +103 -4
  115. package/src/__tests__/task-memory-cleanup.test.ts +1 -0
  116. package/src/__tests__/task-scheduler.test.ts +3 -15
  117. package/src/__tests__/test-preload.ts +11 -0
  118. package/src/__tests__/title-generate-pipeline.test.ts +224 -0
  119. package/src/__tests__/token-estimate-pipeline.test.ts +431 -0
  120. package/src/__tests__/tool-error-pipeline.test.ts +244 -0
  121. package/src/__tests__/tool-execute-pipeline.test.ts +431 -0
  122. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -6
  123. package/src/__tests__/tool-executor-shell-integration.test.ts +7 -10
  124. package/src/__tests__/tool-executor.test.ts +141 -0
  125. package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
  126. package/src/__tests__/tool-result-truncation.test.ts +0 -110
  127. package/src/__tests__/user-plugin-loader.test.ts +191 -0
  128. package/src/__tests__/workspace-migration-046-seed-conversation-starters-callsite.test.ts +185 -0
  129. package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
  130. package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
  131. package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
  132. package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
  133. package/src/__tests__/workspace-policy.test.ts +21 -3
  134. package/src/agent/loop.ts +340 -102
  135. package/src/approvals/__tests__/guardian-feed-event.test.ts +304 -0
  136. package/src/approvals/guardian-request-resolvers.ts +80 -0
  137. package/src/backup/__tests__/backup-worker.test.ts +2 -13
  138. package/src/backup/backup-worker.ts +3 -15
  139. package/src/bundler/app-compiler.ts +84 -1
  140. package/src/calls/call-state.ts +2 -2
  141. package/src/channels/__tests__/types.test.ts +3 -3
  142. package/src/channels/types.ts +6 -4
  143. package/src/cli/__tests__/notifications.test.ts +87 -211
  144. package/src/cli/commands/__tests__/backup.test.ts +1 -1
  145. package/src/cli/commands/__tests__/image-generation.test.ts +255 -35
  146. package/src/cli/commands/__tests__/inference-send.test.ts +12 -0
  147. package/src/cli/commands/__tests__/tts-synthesize.test.ts +12 -0
  148. package/src/cli/commands/backup.ts +2 -2
  149. package/src/cli/commands/clients.ts +138 -0
  150. package/src/cli/commands/completions.ts +2 -9
  151. package/src/cli/commands/conversations.ts +55 -7
  152. package/src/cli/commands/image-generation.ts +33 -34
  153. package/src/cli/commands/notifications.ts +68 -103
  154. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
  155. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
  156. package/src/cli/commands/oauth/connect.ts +2 -2
  157. package/src/cli/commands/oauth/providers.ts +176 -8
  158. package/src/cli/commands/oauth/status.ts +46 -36
  159. package/src/cli/commands/skills.ts +3 -4
  160. package/src/cli/program.ts +25 -29
  161. package/src/config/__tests__/backup-schema.test.ts +7 -2
  162. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  163. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
  164. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
  165. package/src/config/bundled-skills/contacts/tools/contact-search.ts +28 -51
  166. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +22 -40
  167. package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
  168. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
  169. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
  170. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  171. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
  172. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +12 -0
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +58 -0
  174. package/src/config/bundled-skills/schedule/SKILL.md +8 -3
  175. package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
  176. package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
  177. package/src/config/bundled-tool-registry.ts +0 -15
  178. package/src/config/feature-flag-registry.json +17 -1
  179. package/src/config/schema.ts +19 -0
  180. package/src/config/schemas/backup.ts +1 -1
  181. package/src/config/schemas/conversations.ts +16 -0
  182. package/src/config/schemas/llm.ts +2 -3
  183. package/src/config/schemas/security.ts +6 -6
  184. package/src/config/schemas/tts.ts +11 -0
  185. package/src/config/skill-state.ts +6 -2
  186. package/src/config/skills.ts +94 -5
  187. package/src/context/__tests__/compact-prompt.test.ts +27 -9
  188. package/src/context/prompts/compact.md +26 -12
  189. package/src/context/tool-result-truncation.ts +3 -63
  190. package/src/context/window-manager.ts +190 -16
  191. package/src/credential-health/credential-health-service.ts +19 -6
  192. package/src/daemon/__tests__/conversation-feed-event.test.ts +317 -0
  193. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
  194. package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
  195. package/src/daemon/config-watcher.ts +0 -2
  196. package/src/daemon/context-overflow-policy.ts +4 -13
  197. package/src/daemon/conversation-agent-loop-handlers.ts +83 -22
  198. package/src/daemon/conversation-agent-loop.ts +984 -683
  199. package/src/daemon/conversation-history.ts +10 -19
  200. package/src/daemon/conversation-lifecycle.ts +37 -19
  201. package/src/daemon/conversation-notifiers.ts +2 -110
  202. package/src/daemon/conversation-process.ts +14 -7
  203. package/src/daemon/conversation-runtime-assembly.ts +532 -411
  204. package/src/daemon/conversation-tool-setup.ts +41 -4
  205. package/src/daemon/conversation.ts +80 -35
  206. package/src/daemon/external-plugins-bootstrap.ts +478 -0
  207. package/src/daemon/first-greeting.ts +191 -14
  208. package/src/daemon/handlers/config-model.ts +11 -0
  209. package/src/daemon/handlers/skills.ts +5 -1
  210. package/src/daemon/lifecycle.ts +33 -68
  211. package/src/daemon/message-types/computer-use.ts +2 -34
  212. package/src/daemon/message-types/conversations.ts +49 -0
  213. package/src/daemon/message-types/messages.ts +12 -0
  214. package/src/daemon/server.ts +5 -3
  215. package/src/daemon/shutdown-handlers.ts +2 -12
  216. package/src/daemon/tool-side-effects.ts +14 -56
  217. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +160 -0
  218. package/src/heartbeat/heartbeat-service.ts +24 -1
  219. package/src/home/__tests__/feed-population-integration.test.ts +312 -0
  220. package/src/home/emit-feed-event.ts +7 -0
  221. package/src/home/feed-types.ts +41 -2
  222. package/src/home/rewrite-command-preview.ts +66 -0
  223. package/src/ipc/__tests__/socket-path.test.ts +11 -50
  224. package/src/ipc/cli-client.ts +1 -1
  225. package/src/ipc/cli-server.ts +3 -3
  226. package/src/ipc/gateway-client.ts +4 -1
  227. package/src/ipc/routes/browser-context.ts +2 -0
  228. package/src/ipc/routes/browser.ts +1 -0
  229. package/src/ipc/routes/get-contact.ts +16 -0
  230. package/src/ipc/routes/index.ts +14 -0
  231. package/src/ipc/routes/list-clients.ts +31 -0
  232. package/src/ipc/routes/merge-contacts.ts +17 -0
  233. package/src/ipc/routes/notification.ts +133 -0
  234. package/src/ipc/routes/rename-conversation.ts +59 -0
  235. package/src/ipc/routes/search-contacts.ts +19 -0
  236. package/src/ipc/routes/upsert-contact.ts +25 -0
  237. package/src/ipc/socket-path.ts +14 -38
  238. package/src/media/app-icon-generator.ts +23 -46
  239. package/src/media/avatar-router.ts +26 -41
  240. package/src/media/gemini-image-service.ts +8 -41
  241. package/src/media/image-credentials.ts +73 -0
  242. package/src/media/image-service.ts +85 -0
  243. package/src/media/openai-image-service.ts +131 -0
  244. package/src/media/types.ts +46 -0
  245. package/src/memory/conversation-crud.ts +48 -18
  246. package/src/memory/conversation-queries.ts +57 -4
  247. package/src/memory/conversation-title-service.ts +25 -0
  248. package/src/memory/db-init.ts +8 -0
  249. package/src/memory/embedding-gemini.test.ts +41 -2
  250. package/src/memory/embedding-gemini.ts +6 -1
  251. package/src/memory/graph/bootstrap.test.ts +282 -0
  252. package/src/memory/graph/bootstrap.ts +8 -5
  253. package/src/memory/graph/extraction.ts +10 -2
  254. package/src/memory/graph/graph-search.test.ts +1 -0
  255. package/src/memory/graph/inspect.ts +2 -2
  256. package/src/memory/graph/retriever.ts +10 -3
  257. package/src/memory/migrations/041-approval-prompt-ts-tracker.ts +26 -0
  258. package/src/memory/migrations/149-oauth-tables.ts +1 -0
  259. package/src/memory/migrations/223-schedule-script-column.ts +11 -0
  260. package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
  261. package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
  262. package/src/memory/migrations/index.ts +4 -0
  263. package/src/memory/pkb/pkb-index.test.ts +1 -0
  264. package/src/memory/pkb/pkb-reconcile.test.ts +1 -0
  265. package/src/memory/pkb/pkb-search.test.ts +65 -4
  266. package/src/memory/pkb/pkb-search.ts +40 -18
  267. package/src/memory/qdrant-client.test.ts +60 -0
  268. package/src/memory/qdrant-client.ts +25 -0
  269. package/src/memory/schema/infrastructure.ts +1 -0
  270. package/src/memory/schema/oauth.ts +4 -1
  271. package/src/messaging/providers/slack/render-transcript.test.ts +77 -29
  272. package/src/messaging/providers/slack/render-transcript.ts +58 -0
  273. package/src/notifications/conversation-pairing.ts +78 -19
  274. package/src/notifications/copy-composer.ts +0 -5
  275. package/src/notifications/emit-signal.ts +1 -1
  276. package/src/notifications/signal.ts +1 -2
  277. package/src/oauth/AGENTS.md +1 -1
  278. package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
  279. package/src/oauth/connect-orchestrator.ts +8 -34
  280. package/src/oauth/connect-types.ts +6 -10
  281. package/src/oauth/manual-token-connection.ts +23 -0
  282. package/src/oauth/oauth-store.ts +30 -14
  283. package/src/oauth/provider-serializer.ts +6 -1
  284. package/src/oauth/seed-providers.ts +56 -108
  285. package/src/outbound-proxy/http-forwarder.ts +9 -0
  286. package/src/permissions/approval-policy.test.ts +293 -18
  287. package/src/permissions/approval-policy.ts +110 -58
  288. package/src/permissions/arg-parser.test.ts +161 -0
  289. package/src/permissions/arg-parser.ts +141 -0
  290. package/src/permissions/bash-risk-classifier.test.ts +414 -2
  291. package/src/permissions/bash-risk-classifier.ts +303 -60
  292. package/src/permissions/checker.ts +157 -29
  293. package/src/permissions/command-registry.test.ts +239 -0
  294. package/src/permissions/command-registry.ts +234 -54
  295. package/src/permissions/defaults.ts +5 -4
  296. package/src/permissions/gateway-threshold-reader.ts +196 -0
  297. package/src/permissions/prompter.ts +4 -0
  298. package/src/permissions/risk-types.ts +61 -4
  299. package/src/permissions/schedule-risk-classifier.test.ts +129 -0
  300. package/src/permissions/schedule-risk-classifier.ts +85 -0
  301. package/src/permissions/shell-identity.ts +2 -42
  302. package/src/permissions/types.ts +2 -0
  303. package/src/permissions/workspace-policy.ts +8 -3
  304. package/src/plugins/defaults/circuit-breaker.ts +146 -0
  305. package/src/plugins/defaults/compaction.ts +145 -0
  306. package/src/plugins/defaults/empty-response.ts +126 -0
  307. package/src/plugins/defaults/history-repair.ts +85 -0
  308. package/src/plugins/defaults/index.ts +116 -0
  309. package/src/plugins/defaults/injectors.ts +491 -0
  310. package/src/plugins/defaults/llm-call.ts +82 -0
  311. package/src/plugins/defaults/memory-retrieval.ts +226 -0
  312. package/src/plugins/defaults/overflow-reduce.ts +181 -0
  313. package/src/plugins/defaults/persistence.ts +129 -0
  314. package/src/plugins/defaults/title-generate.ts +95 -0
  315. package/src/plugins/defaults/token-estimate.ts +104 -0
  316. package/src/plugins/defaults/tool-error.ts +126 -0
  317. package/src/plugins/defaults/tool-execute.ts +89 -0
  318. package/src/plugins/defaults/tool-result-truncate.ts +88 -0
  319. package/src/plugins/pipeline.ts +316 -0
  320. package/src/plugins/plugin-skill-contributions.ts +292 -0
  321. package/src/plugins/registry.ts +241 -0
  322. package/src/plugins/types.ts +1134 -0
  323. package/src/plugins/user-loader.ts +177 -0
  324. package/src/prompts/templates/BOOTSTRAP.md +27 -77
  325. package/src/providers/model-catalog.ts +52 -29
  326. package/src/providers/model-intents.ts +1 -1
  327. package/src/providers/openrouter/client.ts +5 -1
  328. package/src/providers/speech-to-text/deepgram-realtime.test.ts +61 -0
  329. package/src/providers/speech-to-text/deepgram-realtime.ts +57 -0
  330. package/src/providers/speech-to-text/xai-realtime.test.ts +72 -4
  331. package/src/providers/speech-to-text/xai-realtime.ts +39 -14
  332. package/src/runtime/AGENTS.md +25 -16
  333. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +3 -3
  334. package/src/runtime/__tests__/client-registry.test.ts +293 -0
  335. package/src/runtime/client-registry.ts +261 -0
  336. package/src/runtime/http-server.ts +77 -8
  337. package/src/runtime/http-types.ts +0 -2
  338. package/src/runtime/migrations/vbundle-builder.ts +1 -22
  339. package/src/runtime/routes/approval-prompt-ts-tracker.ts +51 -31
  340. package/src/runtime/routes/approval-routes.ts +17 -0
  341. package/src/runtime/routes/browser-extension-pair-routes.ts +27 -8
  342. package/src/runtime/routes/conversation-routes.ts +223 -116
  343. package/src/runtime/routes/inbound-message-handler.ts +88 -13
  344. package/src/runtime/routes/memory-item-routes.test.ts +1 -0
  345. package/src/runtime/routes/migration-routes.ts +0 -3
  346. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +284 -0
  347. package/src/runtime/routes/playground/__tests__/guard.test.ts +80 -0
  348. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +294 -0
  349. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +271 -0
  350. package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +202 -0
  351. package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +309 -0
  352. package/src/runtime/routes/playground/__tests__/state.test.ts +224 -0
  353. package/src/runtime/routes/playground/conversation-not-found.ts +29 -0
  354. package/src/runtime/routes/playground/deps.ts +56 -0
  355. package/src/runtime/routes/playground/force-compact.ts +73 -0
  356. package/src/runtime/routes/playground/guard.ts +37 -0
  357. package/src/runtime/routes/playground/index.ts +28 -0
  358. package/src/runtime/routes/playground/inject-failures.ts +159 -0
  359. package/src/runtime/routes/playground/reset-circuit.ts +115 -0
  360. package/src/runtime/routes/playground/seed-conversation.ts +139 -0
  361. package/src/runtime/routes/playground/seeded-conversations.ts +78 -0
  362. package/src/runtime/routes/playground/state.ts +78 -0
  363. package/src/runtime/routes/schedule-routes.ts +89 -8
  364. package/src/runtime/skill-route-registry.ts +75 -15
  365. package/src/schedule/run-script.ts +68 -0
  366. package/src/schedule/schedule-store.ts +7 -1
  367. package/src/schedule/scheduler.ts +48 -8
  368. package/src/skills/catalog-cache.ts +12 -5
  369. package/src/tools/browser/__tests__/browser-status.test.ts +189 -0
  370. package/src/tools/browser/browser-execution.ts +88 -19
  371. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
  372. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
  373. package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
  374. package/src/tools/browser/cdp-client/factory.ts +15 -4
  375. package/src/tools/executor.ts +126 -74
  376. package/src/tools/network/script-proxy/session-manager.ts +37 -1
  377. package/src/tools/permission-checker.ts +98 -49
  378. package/src/tools/policy-context.ts +4 -0
  379. package/src/tools/registry.ts +140 -3
  380. package/src/tools/schedule/create.ts +23 -8
  381. package/src/tools/schedule/update.ts +3 -1
  382. package/src/tools/secret-detection-handler.ts +0 -51
  383. package/src/tools/system/avatar-generator.ts +6 -2
  384. package/src/tools/types.ts +28 -2
  385. package/src/util/platform.ts +7 -2
  386. package/src/util/pricing.ts +26 -3
  387. package/src/workspace/migrations/006-services-config.ts +2 -4
  388. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
  389. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +3 -4
  390. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +108 -0
  391. package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
  392. package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
  393. package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
  394. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
  395. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
  396. package/src/workspace/migrations/registry.ts +12 -0
  397. package/tsconfig.json +1 -1
  398. package/hook-templates/debug-prompt-logger/hook.json +0 -7
  399. package/hook-templates/debug-prompt-logger/run.sh +0 -66
  400. package/src/__tests__/compaction-circuit-breaker.test.ts +0 -336
  401. package/src/__tests__/context-overflow-approval.test.ts +0 -156
  402. package/src/__tests__/hooks-blocking.test.ts +0 -178
  403. package/src/__tests__/hooks-cli.test.ts +0 -182
  404. package/src/__tests__/hooks-config.test.ts +0 -108
  405. package/src/__tests__/hooks-discovery.test.ts +0 -211
  406. package/src/__tests__/hooks-integration.test.ts +0 -196
  407. package/src/__tests__/hooks-manager.test.ts +0 -226
  408. package/src/__tests__/hooks-runner.test.ts +0 -175
  409. package/src/__tests__/hooks-settings.test.ts +0 -160
  410. package/src/__tests__/hooks-templates.test.ts +0 -169
  411. package/src/__tests__/hooks-ts-runner.test.ts +0 -170
  412. package/src/__tests__/hooks-watch.test.ts +0 -112
  413. package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
  414. package/src/__tests__/oauth-scope-policy.test.ts +0 -180
  415. package/src/__tests__/send-notification-tool.test.ts +0 -83
  416. package/src/cli/commands/shotgun.ts +0 -266
  417. package/src/config/bundled-skills/conversations/SKILL.md +0 -20
  418. package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
  419. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -88
  420. package/src/config/bundled-skills/heartbeat/SKILL.md +0 -43
  421. package/src/config/bundled-skills/notifications/SKILL.md +0 -40
  422. package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
  423. package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
  424. package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
  425. package/src/config/bundled-skills/screen-watch/SKILL.md +0 -27
  426. package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
  427. package/src/config/bundled-skills/screen-watch/tools/start-screen-watch.ts +0 -12
  428. package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
  429. package/src/daemon/context-overflow-approval.ts +0 -52
  430. package/src/daemon/watch-handler.ts +0 -399
  431. package/src/hooks/cli.ts +0 -253
  432. package/src/hooks/config.ts +0 -100
  433. package/src/hooks/discovery.ts +0 -135
  434. package/src/hooks/manager.ts +0 -179
  435. package/src/hooks/runner.ts +0 -117
  436. package/src/hooks/templates.ts +0 -77
  437. package/src/hooks/types.ts +0 -75
  438. package/src/oauth/scope-policy.ts +0 -89
  439. package/src/runtime/gateway-internal-client.ts +0 -94
  440. package/src/runtime/routes/watch-routes.ts +0 -156
  441. package/src/signals/shotgun.ts +0 -203
  442. package/src/tools/watch/screen-watch.ts +0 -144
  443. package/src/tools/watch/watch-state.ts +0 -142
@@ -0,0 +1,526 @@
1
+ /**
2
+ * Tests for the plugin-driven runtime-injection chain (PR 21 of the
3
+ * `agent-plugin-system` plan).
4
+ *
5
+ * Covers:
6
+ *
7
+ * 1. The eight default injectors registered by `defaultInjectorsPlugin` come
8
+ * back from `getInjectors()` in the documented order (workspace-context →
9
+ * unified-turn-context → pkb-context → pkb-reminder → now-md →
10
+ * subagent-status → slack-messages → thread-focus).
11
+ * 2. A third-party-registered injector at `order: 25` slots between
12
+ * `unified-turn-context` (order 20) and `pkb` (order 30), proving the
13
+ * extensibility contract.
14
+ * 3. `composeInjectorChain` concatenates non-null blocks with a blank-line
15
+ * separator and yields an empty string when every injector opts out — the
16
+ * latter matches pre-PR behavior for the golden-path conversation state
17
+ * (all defaults return `null` in this PR).
18
+ * 4. `applyRuntimeInjections` with an empty `turnContext` chain leaves
19
+ * `blocks.injectorChainBlock` undefined, preserving the existing snapshot
20
+ * for conversations that don't opt into the chain.
21
+ * 5. `applyRuntimeInjections` surfaces the composed chain output on
22
+ * `blocks.injectorChainBlock` when a third-party injector contributes
23
+ * content.
24
+ */
25
+
26
+ import { beforeEach, describe, expect, test } from "bun:test";
27
+
28
+ import {
29
+ applyRuntimeInjections,
30
+ composeInjectorChain,
31
+ } from "../daemon/conversation-runtime-assembly.js";
32
+ import {
33
+ DEFAULT_INJECTOR_ORDER,
34
+ defaultInjectorsPlugin,
35
+ } from "../plugins/defaults/injectors.js";
36
+ import {
37
+ getInjectors,
38
+ registerPlugin,
39
+ resetPluginRegistryForTests,
40
+ } from "../plugins/registry.js";
41
+ import type {
42
+ InjectionBlock,
43
+ Injector,
44
+ Plugin,
45
+ TurnContext,
46
+ } from "../plugins/types.js";
47
+ import type { Message } from "../providers/types.js";
48
+
49
+ /** A fake TurnContext sufficient for driving `composeInjectorChain`. */
50
+ function makeTurnContext(): TurnContext {
51
+ return {
52
+ requestId: "req-test-1",
53
+ conversationId: "conv-test-1",
54
+ turnIndex: 0,
55
+ trust: {
56
+ sourceChannel: "vellum",
57
+ trustClass: "guardian",
58
+ },
59
+ };
60
+ }
61
+
62
+ /** Build a tiny valid plugin wrapping an array of injectors. */
63
+ function wrapInPlugin(name: string, injectors: Injector[]): Plugin {
64
+ return {
65
+ manifest: {
66
+ name,
67
+ version: "0.0.1",
68
+ requires: { pluginRuntime: "v1" },
69
+ },
70
+ injectors,
71
+ };
72
+ }
73
+
74
+ describe("injector chain", () => {
75
+ beforeEach(() => {
76
+ resetPluginRegistryForTests();
77
+ });
78
+
79
+ test("defaultInjectorsPlugin registers the eight defaults in the documented order", () => {
80
+ registerPlugin(defaultInjectorsPlugin);
81
+
82
+ const names = getInjectors().map((i) => i.name);
83
+ expect(names).toEqual([
84
+ "workspace-context",
85
+ "unified-turn-context",
86
+ "pkb-context",
87
+ "pkb-reminder",
88
+ "now-md",
89
+ "subagent-status",
90
+ "slack-messages",
91
+ "thread-focus",
92
+ ]);
93
+ });
94
+
95
+ test("default injector order constants match the registered order values", () => {
96
+ registerPlugin(defaultInjectorsPlugin);
97
+
98
+ const byName = new Map(getInjectors().map((i) => [i.name, i.order]));
99
+ expect(byName.get("workspace-context")).toBe(
100
+ DEFAULT_INJECTOR_ORDER.workspaceContext,
101
+ );
102
+ expect(byName.get("unified-turn-context")).toBe(
103
+ DEFAULT_INJECTOR_ORDER.unifiedTurnContext,
104
+ );
105
+ expect(byName.get("pkb-context")).toBe(DEFAULT_INJECTOR_ORDER.pkbContext);
106
+ expect(byName.get("pkb-reminder")).toBe(DEFAULT_INJECTOR_ORDER.pkbReminder);
107
+ expect(byName.get("now-md")).toBe(DEFAULT_INJECTOR_ORDER.nowMd);
108
+ expect(byName.get("subagent-status")).toBe(
109
+ DEFAULT_INJECTOR_ORDER.subagentStatus,
110
+ );
111
+ expect(byName.get("slack-messages")).toBe(
112
+ DEFAULT_INJECTOR_ORDER.slackMessages,
113
+ );
114
+ expect(byName.get("thread-focus")).toBe(DEFAULT_INJECTOR_ORDER.threadFocus);
115
+ });
116
+
117
+ test("a third-party injector at order 25 slots between unified-turn-context (20) and pkb-context (30)", () => {
118
+ registerPlugin(defaultInjectorsPlugin);
119
+
120
+ const middleInjector: Injector = {
121
+ name: "plugin-25",
122
+ order: 25,
123
+ async produce() {
124
+ return null;
125
+ },
126
+ };
127
+ registerPlugin(wrapInPlugin("third-party", [middleInjector]));
128
+
129
+ const names = getInjectors().map((i) => i.name);
130
+ expect(names).toEqual([
131
+ "workspace-context", // 10
132
+ "unified-turn-context", // 20
133
+ "plugin-25", // 25 — slots in
134
+ "pkb-context", // 30
135
+ "pkb-reminder", // 35
136
+ "now-md", // 40
137
+ "subagent-status", // 50
138
+ "slack-messages", // 60
139
+ "thread-focus", // 70
140
+ ]);
141
+ });
142
+
143
+ test("composeInjectorChain returns empty string when every injector opts out", async () => {
144
+ // The default chain is the golden-path: all seven defaults return `null`
145
+ // in this PR, so the composed block is an empty string, matching the
146
+ // pre-PR behavior where no chain existed at all.
147
+ registerPlugin(defaultInjectorsPlugin);
148
+
149
+ const composed = await composeInjectorChain(makeTurnContext());
150
+ expect(composed).toBe("");
151
+ });
152
+
153
+ test("composeInjectorChain returns empty string when registry is empty", async () => {
154
+ // No plugins registered — the chain is a no-op and must return an empty
155
+ // string (not throw, not undefined). Callers rely on this to treat the
156
+ // chain as purely additive.
157
+ const composed = await composeInjectorChain(makeTurnContext());
158
+ expect(composed).toBe("");
159
+ });
160
+
161
+ test("composeInjectorChain concatenates non-null blocks in order with blank-line separators", async () => {
162
+ const first: Injector = {
163
+ name: "a",
164
+ order: 5,
165
+ async produce(): Promise<InjectionBlock> {
166
+ return { id: "a", text: "BLOCK_A" };
167
+ },
168
+ };
169
+ const second: Injector = {
170
+ name: "b",
171
+ order: 15,
172
+ async produce(): Promise<InjectionBlock> {
173
+ return { id: "b", text: "BLOCK_B" };
174
+ },
175
+ };
176
+ const skipped: Injector = {
177
+ name: "c",
178
+ order: 25,
179
+ async produce() {
180
+ return null;
181
+ },
182
+ };
183
+ // Register the higher-order one first to prove the chain sorts by `order`
184
+ // rather than registration order.
185
+ registerPlugin(wrapInPlugin("higher", [second]));
186
+ registerPlugin(wrapInPlugin("lower", [first]));
187
+ registerPlugin(wrapInPlugin("opts-out", [skipped]));
188
+
189
+ const composed = await composeInjectorChain(makeTurnContext());
190
+ expect(composed).toBe("BLOCK_A\n\nBLOCK_B");
191
+ });
192
+
193
+ test("composeInjectorChain skips blocks with empty text", async () => {
194
+ const emitEmpty: Injector = {
195
+ name: "empty",
196
+ order: 10,
197
+ async produce(): Promise<InjectionBlock> {
198
+ return { id: "empty", text: "" };
199
+ },
200
+ };
201
+ const emitReal: Injector = {
202
+ name: "real",
203
+ order: 20,
204
+ async produce(): Promise<InjectionBlock> {
205
+ return { id: "real", text: "CONTENT" };
206
+ },
207
+ };
208
+ registerPlugin(wrapInPlugin("plugin", [emitEmpty, emitReal]));
209
+
210
+ const composed = await composeInjectorChain(makeTurnContext());
211
+ expect(composed).toBe("CONTENT");
212
+ });
213
+
214
+ test("applyRuntimeInjections leaves injectorChainBlock undefined when defaults opt out", async () => {
215
+ // Golden-path snapshot: with only default injectors (all returning
216
+ // `null`), `applyRuntimeInjections` reports no chain output, so the
217
+ // historical `blocks` shape is preserved byte-for-byte for any
218
+ // conversation that doesn't involve third-party injectors.
219
+ registerPlugin(defaultInjectorsPlugin);
220
+
221
+ const runMessages: Message[] = [
222
+ { role: "user", content: [{ type: "text", text: "hello" }] },
223
+ ];
224
+
225
+ const result = await applyRuntimeInjections(runMessages, {
226
+ turnContext: makeTurnContext(),
227
+ });
228
+
229
+ expect(result.blocks.injectorChainBlock).toBeUndefined();
230
+ // Sanity: the message array is untouched when no options fire (no
231
+ // hardcoded branches apply, and the chain contributed nothing).
232
+ expect(result.messages).toEqual(runMessages);
233
+ });
234
+
235
+ test("applyRuntimeInjections surfaces third-party injector output on blocks.injectorChainBlock", async () => {
236
+ registerPlugin(defaultInjectorsPlugin);
237
+ registerPlugin(
238
+ wrapInPlugin("third-party-25", [
239
+ {
240
+ name: "plugin-25",
241
+ order: 25,
242
+ async produce(): Promise<InjectionBlock> {
243
+ return { id: "plugin-25", text: "THIRD_PARTY_BLOCK" };
244
+ },
245
+ },
246
+ ]),
247
+ );
248
+
249
+ const runMessages: Message[] = [
250
+ { role: "user", content: [{ type: "text", text: "hi" }] },
251
+ ];
252
+
253
+ const result = await applyRuntimeInjections(runMessages, {
254
+ turnContext: makeTurnContext(),
255
+ });
256
+
257
+ expect(result.blocks.injectorChainBlock).toBe("THIRD_PARTY_BLOCK");
258
+ });
259
+
260
+ test("applyRuntimeInjections without turnContext still runs the chain under a synthesized context", async () => {
261
+ // Post-G2.1 semantics: the default chain is the canonical injection
262
+ // path, so `applyRuntimeInjections` must drive it even when the caller
263
+ // doesn't pass a `turnContext`. Test/legacy call sites that rely on
264
+ // option fields to opt into injections continue to work because the
265
+ // synthesized fallback exposes `injectionInputs` built from `options`.
266
+ registerPlugin(defaultInjectorsPlugin);
267
+ registerPlugin(
268
+ wrapInPlugin("third-party-25", [
269
+ {
270
+ name: "plugin-25",
271
+ order: 25,
272
+ async produce(): Promise<InjectionBlock> {
273
+ return { id: "plugin-25", text: "THIRD_PARTY_BLOCK" };
274
+ },
275
+ },
276
+ ]),
277
+ );
278
+
279
+ const runMessages: Message[] = [
280
+ { role: "user", content: [{ type: "text", text: "hi" }] },
281
+ ];
282
+
283
+ const result = await applyRuntimeInjections(runMessages, {});
284
+
285
+ // Third-party injector runs even without a caller-supplied turnContext.
286
+ expect(result.blocks.injectorChainBlock).toBe("THIRD_PARTY_BLOCK");
287
+ });
288
+
289
+ // ── G2.1 migration integration tests ────────────────────────────────
290
+ //
291
+ // These assertions exercise the real per-turn injection pipeline with
292
+ // the default chain active, verifying that each ported injector emits
293
+ // byte-identical content to the pre-migration output and that a
294
+ // third-party injector registered at a fractional `order` slots into
295
+ // the correct position in the final user-tail content.
296
+
297
+ test("golden-path: default chain injects workspace + unified-turn + PKB + NOW + subagent in the correct positions", async () => {
298
+ // Canonical golden-path conversation state: full mode, non-Slack
299
+ // channel, workspace context + unified-turn + PKB + NOW + subagent
300
+ // all active. The expected final tail content ordering is:
301
+ //
302
+ // [workspace] ← prepend order 10 (topmost)
303
+ // [unified-turn] ← prepend order 20
304
+ // [now-md] ← after-memory-prefix order 40 (highest order, closest to memory)
305
+ // [pkb-reminder] ← after-memory-prefix order 35 (skipped when pkbActive=false)
306
+ // [pkb-context] ← after-memory-prefix order 30
307
+ // [user text]
308
+ // [subagent] ← append order 50
309
+ //
310
+ // No memory prefix blocks in this scenario, so after-memory-prefix
311
+ // lands right at the head of the user-text cluster.
312
+ registerPlugin(defaultInjectorsPlugin);
313
+
314
+ const runMessages: Message[] = [
315
+ { role: "user", content: [{ type: "text", text: "What next?" }] },
316
+ ];
317
+
318
+ const workspaceText =
319
+ "<workspace>\nRoot: /sandbox\nDirectories: src, lib\n</workspace>";
320
+ const unifiedTurn =
321
+ "<turn_context>\ncurrent_time: 2026-04-22\ninterface: macos\n</turn_context>";
322
+ const pkbContent = "essentials of the project";
323
+ const nowContent = "Current focus: shipping G2.1";
324
+ const subagentBlock =
325
+ '<active_subagents>\n- [running] "worker" (sub-1) | elapsed: 5s\n</active_subagents>';
326
+
327
+ const result = await applyRuntimeInjections(runMessages, {
328
+ turnContext: makeTurnContext(),
329
+ workspaceTopLevelContext: workspaceText,
330
+ unifiedTurnContext: unifiedTurn,
331
+ pkbContext: pkbContent,
332
+ pkbActive: false, // disable reminder-branch to keep the snapshot small
333
+ nowScratchpad: nowContent,
334
+ subagentStatusBlock: subagentBlock,
335
+ });
336
+
337
+ // Extract the tail user message content as a list of text strings.
338
+ const tail = result.messages[result.messages.length - 1];
339
+ expect(tail.role).toBe("user");
340
+ const texts = tail.content
341
+ .filter((b): b is { type: "text"; text: string } => b.type === "text")
342
+ .map((b) => b.text);
343
+
344
+ // Positional assertions — each block lands where the injector's
345
+ // placement says it does.
346
+ expect(texts[0]).toBe(workspaceText); // prepend order 10
347
+ expect(texts[1]).toBe(unifiedTurn); // prepend order 20
348
+ // NOW and PKB are both after-memory-prefix; NOW runs later so sits above PKB.
349
+ expect(texts[2]).toBe(
350
+ `<NOW.md Always keep this up to date; keep under 10 lines>\n${nowContent}\n</NOW.md>`,
351
+ );
352
+ expect(texts[3]).toBe(`<knowledge_base>\n${pkbContent}\n</knowledge_base>`);
353
+ expect(texts[4]).toBe("What next?"); // user's typed text
354
+ expect(texts[5]).toBe(subagentBlock); // append order 50
355
+ expect(texts).toHaveLength(6);
356
+
357
+ // Block metadata captures for DB persistence — one field per default
358
+ // injector whose output the loader rehydrates from message metadata.
359
+ expect(result.blocks.workspaceBlock).toBe(workspaceText);
360
+ expect(result.blocks.unifiedTurnContext).toBe(unifiedTurn);
361
+ expect(result.blocks.nowScratchpadBlock).toBe(
362
+ `<NOW.md Always keep this up to date; keep under 10 lines>\n${nowContent}\n</NOW.md>`,
363
+ );
364
+ expect(result.blocks.pkbContextBlock).toBe(
365
+ `<knowledge_base>\n${pkbContent}\n</knowledge_base>`,
366
+ );
367
+ });
368
+
369
+ test("third-party injector at order 25 lands between unified-turn-context (20) and pkb-context (30) in the final message", async () => {
370
+ // Proves the extensibility contract end-to-end: a plugin-registered
371
+ // injector at `order: 25` with `placement: "prepend-user-tail"` slots
372
+ // between the unified-turn prepend (order 20, executes just before
373
+ // workspace) and the PKB after-memory splice (order 30). Because it
374
+ // uses prepend-user-tail placement it becomes a third prepend,
375
+ // landing between workspace (topmost) and unified-turn.
376
+ registerPlugin(defaultInjectorsPlugin);
377
+ registerPlugin(
378
+ wrapInPlugin("third-party-25-prepend", [
379
+ {
380
+ name: "plugin-25",
381
+ order: 15, // between workspace (10) and unified-turn (20)
382
+ async produce(): Promise<InjectionBlock> {
383
+ return {
384
+ id: "plugin-25",
385
+ text: "<plugin_block_15/>",
386
+ placement: "prepend-user-tail",
387
+ };
388
+ },
389
+ },
390
+ ]),
391
+ );
392
+
393
+ const runMessages: Message[] = [
394
+ { role: "user", content: [{ type: "text", text: "hi" }] },
395
+ ];
396
+
397
+ const workspaceText = "<workspace>\nRoot: /sandbox\n</workspace>";
398
+ const unifiedTurn =
399
+ "<turn_context>\ncurrent_time: 2026-04-22\n</turn_context>";
400
+
401
+ const result = await applyRuntimeInjections(runMessages, {
402
+ turnContext: makeTurnContext(),
403
+ workspaceTopLevelContext: workspaceText,
404
+ unifiedTurnContext: unifiedTurn,
405
+ });
406
+
407
+ const tail = result.messages[result.messages.length - 1];
408
+ expect(tail.role).toBe("user");
409
+ const texts = tail.content
410
+ .filter((b): b is { type: "text"; text: string } => b.type === "text")
411
+ .map((b) => b.text);
412
+
413
+ // Descending-order application for prepends puts the lowest-`order`
414
+ // injector topmost, so order 10 (workspace) ends up on top, then
415
+ // plugin@15 below it, then unified-turn (order 20) below that.
416
+ expect(texts[0]).toBe(workspaceText);
417
+ expect(texts[1]).toBe("<plugin_block_15/>");
418
+ expect(texts[2]).toBe(unifiedTurn);
419
+ expect(texts[3]).toBe("hi");
420
+ });
421
+
422
+ test("slack-messages injector replaces runMessages when a chronological transcript is provided", async () => {
423
+ // End-to-end verification for the `replace-run-messages` placement:
424
+ // a Slack channel turn with a pre-rendered chronological transcript
425
+ // swaps the incoming `runMessages` for the transcript before the
426
+ // after-memory/append placements run. Memory-prefix blocks from the
427
+ // original tail are re-prepended onto the new tail so PKB / NOW
428
+ // splices still find them.
429
+ registerPlugin(defaultInjectorsPlugin);
430
+
431
+ const originalRun: Message[] = [
432
+ {
433
+ role: "user",
434
+ content: [
435
+ // A memory prefix block that must be carried over to the Slack
436
+ // transcript's tail so after-memory splices still fire.
437
+ {
438
+ type: "text",
439
+ text: "<memory __injected>\nrecalled fact\n</memory>",
440
+ },
441
+ { type: "text", text: "What's happening?" },
442
+ ],
443
+ },
444
+ ];
445
+ const slackTranscript: Message[] = [
446
+ {
447
+ role: "user",
448
+ content: [{ type: "text", text: "[12:00 alice]: kickoff" }],
449
+ },
450
+ {
451
+ role: "user",
452
+ content: [{ type: "text", text: "[12:05 @user]: What's happening?" }],
453
+ },
454
+ ];
455
+
456
+ const result = await applyRuntimeInjections(originalRun, {
457
+ turnContext: makeTurnContext(),
458
+ channelCapabilities: {
459
+ channel: "slack",
460
+ dashboardCapable: false,
461
+ supportsDynamicUi: false,
462
+ supportsVoiceInput: false,
463
+ chatType: "channel",
464
+ },
465
+ slackChronologicalMessages: slackTranscript,
466
+ });
467
+
468
+ // The swap replaced the run-messages wholesale but preserved the
469
+ // memory-prefix blocks onto the new tail user message.
470
+ expect(result.messages).toHaveLength(2);
471
+ const slackTail = result.messages[result.messages.length - 1];
472
+ expect(slackTail.role).toBe("user");
473
+ const texts = slackTail.content
474
+ .filter((b): b is { type: "text"; text: string } => b.type === "text")
475
+ .map((b) => b.text);
476
+ // Hardcoded channelCapabilities injection prepends first (Slack is a
477
+ // constrained channel), then the carried memory-prefix blocks, then
478
+ // the slack transcript's original user text.
479
+ expect(texts.some((t) => t.startsWith("<channel_capabilities>"))).toBe(
480
+ true,
481
+ );
482
+ expect(texts).toContain("<memory __injected>\nrecalled fact\n</memory>");
483
+ expect(texts[texts.length - 1]).toBe("[12:05 @user]: What's happening?");
484
+ });
485
+
486
+ test("minimal mode: only unified-turn-context survives; workspace/PKB/NOW/subagent are skipped", async () => {
487
+ // Validates the `minimal` injection-mode gating. Every default
488
+ // injector except `unified-turn-context` checks `mode === "full"` and
489
+ // opts out in minimal mode, so the tail should carry only the turn
490
+ // context prepend plus any non-injector hardcoded content (none
491
+ // here).
492
+ registerPlugin(defaultInjectorsPlugin);
493
+
494
+ const result = await applyRuntimeInjections(
495
+ [
496
+ {
497
+ role: "user",
498
+ content: [{ type: "text", text: "hi" }],
499
+ },
500
+ ],
501
+ {
502
+ turnContext: makeTurnContext(),
503
+ mode: "minimal",
504
+ workspaceTopLevelContext: "<workspace>...</workspace>",
505
+ unifiedTurnContext: "<turn_context>...</turn_context>",
506
+ pkbContext: "kbody",
507
+ pkbActive: true,
508
+ nowScratchpad: "nowbody",
509
+ subagentStatusBlock: "<active_subagents>...</active_subagents>",
510
+ },
511
+ );
512
+
513
+ const tail = result.messages[result.messages.length - 1];
514
+ const texts = tail.content
515
+ .filter((b): b is { type: "text"; text: string } => b.type === "text")
516
+ .map((b) => b.text);
517
+
518
+ expect(texts).toEqual(["<turn_context>...</turn_context>", "hi"]);
519
+ expect(result.blocks.unifiedTurnContext).toBe(
520
+ "<turn_context>...</turn_context>",
521
+ );
522
+ expect(result.blocks.workspaceBlock).toBeUndefined();
523
+ expect(result.blocks.pkbContextBlock).toBeUndefined();
524
+ expect(result.blocks.nowScratchpadBlock).toBeUndefined();
525
+ });
526
+ });
@@ -57,20 +57,6 @@ const scheduleCreateDef = scheduleToolsJson.tools.find(
57
57
  (t: { name: string }) => t.name === "schedule_create",
58
58
  );
59
59
 
60
- // Load send_notification description from the bundled skill TOOLS.json
61
- const notifToolsJson = JSON.parse(
62
- readFileSync(
63
- join(
64
- import.meta.dirname,
65
- "../config/bundled-skills/notifications/TOOLS.json",
66
- ),
67
- "utf-8",
68
- ),
69
- );
70
- const sendNotificationDef = notifToolsJson.tools.find(
71
- (t: { name: string }) => t.name === "send_notification",
72
- );
73
-
74
60
  // =====================================================================
75
61
  // 1. Routing section removed from system prompt — guidance in tool descriptions
76
62
  // =====================================================================
@@ -114,18 +100,6 @@ describe("schedule_create tool description", () => {
114
100
  });
115
101
  });
116
102
 
117
- describe("send_notification tool description", () => {
118
- test("states it fires immediately with no delay", () => {
119
- expect(sendNotificationDef).toBeDefined();
120
- expect(sendNotificationDef.description).toContain("immediate");
121
- expect(sendNotificationDef.description).toContain("no delay");
122
- });
123
-
124
- test("redirects to schedule_create for future alerts", () => {
125
- expect(sendNotificationDef.description).toContain("schedule_create");
126
- });
127
- });
128
-
129
103
  // =====================================================================
130
104
  // 3. Cross-tool consistency: schedule and task tools agree on routing boundaries
131
105
  // =====================================================================