@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,305 @@
1
+ /**
2
+ * Tests for the `emptyResponse` plugin pipeline (PR 18).
3
+ *
4
+ * Covers:
5
+ * - Default plugin decision matches the original inline loop logic for the
6
+ * canonical cases (empty-after-tools → nudge, visible-text → accept,
7
+ * tool-use-blocks-present → accept, retries-exhausted → accept,
8
+ * prior-visible-text-in-run → accept).
9
+ * - Swapping in a custom middleware that returns `action: "accept"` prevents
10
+ * the nudge and lets the loop fall through to history append.
11
+ * - Swapping in a custom middleware that returns `action: "error"` is
12
+ * propagated by the pipeline so the loop can surface a clear error.
13
+ *
14
+ * The loop's actual side-effects (history append, retry counter bump, log
15
+ * emission) live in `agent/loop.ts` and are covered by integration tests in
16
+ * `conversation-agent-loop.test.ts`. This file isolates the pipeline.
17
+ */
18
+
19
+ import { beforeEach, describe, expect, test } from "bun:test";
20
+
21
+ import type { TrustContext } from "../daemon/conversation-runtime-assembly.js";
22
+ import {
23
+ defaultEmptyResponsePlugin,
24
+ defaultEmptyResponseTerminal,
25
+ } from "../plugins/defaults/empty-response.js";
26
+ import { DEFAULT_TIMEOUTS, runPipeline } from "../plugins/pipeline.js";
27
+ import {
28
+ getMiddlewaresFor,
29
+ registerPlugin,
30
+ resetPluginRegistryForTests,
31
+ } from "../plugins/registry.js";
32
+ import type {
33
+ EmptyResponseArgs,
34
+ EmptyResponseDecision,
35
+ Middleware,
36
+ Plugin,
37
+ TurnContext,
38
+ } from "../plugins/types.js";
39
+ import type { ContentBlock } from "../providers/types.js";
40
+
41
+ // ─── Fixtures ────────────────────────────────────────────────────────────────
42
+
43
+ const trust: TrustContext = {
44
+ sourceChannel: "vellum",
45
+ trustClass: "guardian",
46
+ };
47
+
48
+ function makeCtx(): TurnContext {
49
+ return {
50
+ requestId: "req-empty-response",
51
+ conversationId: "conv-empty-response",
52
+ turnIndex: 2,
53
+ trust,
54
+ };
55
+ }
56
+
57
+ /**
58
+ * The nudge text has to match the loop's original inline string verbatim —
59
+ * clients (and the model) may match on this exact text.
60
+ */
61
+ const CANONICAL_NUDGE_TEXT =
62
+ "<system_notice>Your previous response was empty. You must respond to the user with a summary of what you found or did. Do not use any tools — just respond with text.</system_notice>";
63
+
64
+ const emptyTextBlock: ContentBlock = { type: "text", text: " " };
65
+
66
+ function makeArgs(
67
+ overrides: Partial<EmptyResponseArgs> = {},
68
+ ): EmptyResponseArgs {
69
+ return {
70
+ responseContent: [],
71
+ toolUseBlocksLength: 0,
72
+ toolUseTurns: 1,
73
+ emptyResponseRetries: 0,
74
+ maxEmptyResponseRetries: 1,
75
+ priorAssistantHadVisibleText: false,
76
+ ...overrides,
77
+ };
78
+ }
79
+
80
+ async function runEmpty(
81
+ args: EmptyResponseArgs,
82
+ ): Promise<EmptyResponseDecision> {
83
+ return runPipeline(
84
+ "emptyResponse",
85
+ getMiddlewaresFor("emptyResponse"),
86
+ async (a) => defaultEmptyResponseTerminal(a),
87
+ args,
88
+ makeCtx(),
89
+ DEFAULT_TIMEOUTS.emptyResponse,
90
+ );
91
+ }
92
+
93
+ // ─── Tests ───────────────────────────────────────────────────────────────────
94
+
95
+ describe("emptyResponse pipeline — default decisions", () => {
96
+ beforeEach(() => {
97
+ resetPluginRegistryForTests();
98
+ registerPlugin(defaultEmptyResponsePlugin);
99
+ });
100
+
101
+ test("empty turn after tool results → nudge with canonical text", async () => {
102
+ // Whitespace-only text counts as empty (matches inline `trim().length > 0`).
103
+ const decision = await runEmpty(
104
+ makeArgs({
105
+ responseContent: [emptyTextBlock],
106
+ toolUseBlocksLength: 0,
107
+ toolUseTurns: 2,
108
+ emptyResponseRetries: 0,
109
+ priorAssistantHadVisibleText: false,
110
+ }),
111
+ );
112
+ expect(decision.action).toBe("nudge");
113
+ expect(decision.nudgeText).toBe(CANONICAL_NUDGE_TEXT);
114
+ });
115
+
116
+ test("turn contains visible text → accept", async () => {
117
+ const decision = await runEmpty(
118
+ makeArgs({
119
+ responseContent: [{ type: "text", text: "here is a summary" }],
120
+ }),
121
+ );
122
+ expect(decision.action).toBe("accept");
123
+ });
124
+
125
+ test("turn contains tool_use blocks → accept (not empty)", async () => {
126
+ const decision = await runEmpty(
127
+ makeArgs({
128
+ responseContent: [
129
+ {
130
+ type: "tool_use",
131
+ id: "tu-1",
132
+ name: "read",
133
+ input: { path: "/tmp/x" },
134
+ } as ContentBlock,
135
+ ],
136
+ toolUseBlocksLength: 1,
137
+ }),
138
+ );
139
+ expect(decision.action).toBe("accept");
140
+ });
141
+
142
+ test("retries already exhausted → accept", async () => {
143
+ const decision = await runEmpty(
144
+ makeArgs({
145
+ responseContent: [],
146
+ toolUseTurns: 3,
147
+ emptyResponseRetries: 1,
148
+ maxEmptyResponseRetries: 1,
149
+ }),
150
+ );
151
+ expect(decision.action).toBe("accept");
152
+ });
153
+
154
+ test("prior assistant turn already delivered visible text → accept", async () => {
155
+ // Model said its piece earlier, ended with a side-effect tool, returned
156
+ // empty. Nudging would force a verbatim re-send of text the user already
157
+ // saw. Default must accept.
158
+ const decision = await runEmpty(
159
+ makeArgs({
160
+ responseContent: [],
161
+ toolUseTurns: 2,
162
+ priorAssistantHadVisibleText: true,
163
+ }),
164
+ );
165
+ expect(decision.action).toBe("accept");
166
+ });
167
+
168
+ test("no prior tool-use turn (toolUseTurns === 0) → accept", async () => {
169
+ // Empty first assistant response with no tools is not the pattern the
170
+ // nudge guards against. Default accepts.
171
+ const decision = await runEmpty(
172
+ makeArgs({
173
+ responseContent: [],
174
+ toolUseTurns: 0,
175
+ }),
176
+ );
177
+ expect(decision.action).toBe("accept");
178
+ });
179
+ });
180
+
181
+ describe("emptyResponse pipeline — custom middleware overrides", () => {
182
+ beforeEach(() => {
183
+ resetPluginRegistryForTests();
184
+ });
185
+
186
+ test("plugin returning action:accept suppresses the nudge", async () => {
187
+ // Build a plugin whose middleware short-circuits with accept. Register it
188
+ // as the ONLY plugin so its decision is authoritative. The loop-side
189
+ // effect (no nudge appended) is covered by integration tests; here we
190
+ // assert the pipeline returns what the plugin returned.
191
+ const acceptPlugin: Plugin = {
192
+ manifest: {
193
+ name: "force-accept",
194
+ version: "1.0.0",
195
+ requires: { pluginRuntime: "v1" },
196
+ },
197
+ middleware: {
198
+ emptyResponse: async () => ({ action: "accept" }),
199
+ },
200
+ };
201
+ registerPlugin(acceptPlugin);
202
+
203
+ const decision = await runEmpty(
204
+ makeArgs({
205
+ // Conditions the default would nudge on — but the custom plugin wins.
206
+ responseContent: [],
207
+ toolUseTurns: 2,
208
+ emptyResponseRetries: 0,
209
+ priorAssistantHadVisibleText: false,
210
+ }),
211
+ );
212
+ expect(decision.action).toBe("accept");
213
+ // `nudgeText` must not leak from the acceptance branch.
214
+ expect(decision.nudgeText).toBeUndefined();
215
+ });
216
+
217
+ test("plugin returning action:error is propagated to the caller", async () => {
218
+ const errorPlugin: Plugin = {
219
+ manifest: {
220
+ name: "force-error",
221
+ version: "1.0.0",
222
+ requires: { pluginRuntime: "v1" },
223
+ },
224
+ middleware: {
225
+ emptyResponse: async () => ({ action: "error" }),
226
+ },
227
+ };
228
+ registerPlugin(errorPlugin);
229
+
230
+ const decision = await runEmpty(makeArgs());
231
+ expect(decision.action).toBe("error");
232
+ });
233
+
234
+ test("plugin overriding default nudge text changes the returned text", async () => {
235
+ // Exercises the wrapping semantics: the custom plugin observes the
236
+ // default's decision via `next(args)` and rewrites only the text. This
237
+ // is the canonical "plugin wraps default" pattern.
238
+ const rewriterPlugin: Plugin = {
239
+ manifest: {
240
+ name: "rewrite-nudge",
241
+ version: "1.0.0",
242
+ requires: { pluginRuntime: "v1" },
243
+ },
244
+ middleware: {
245
+ emptyResponse: async (args, next, ctx) => {
246
+ const downstream = await next(args);
247
+ if (downstream.action !== "nudge") return downstream;
248
+ void ctx; // silence lint
249
+ return { action: "nudge", nudgeText: "ALTERED_NUDGE" };
250
+ },
251
+ },
252
+ };
253
+ // Register the custom plugin FIRST so it is the outermost middleware; the
254
+ // default registers second and acts as the inner decision maker.
255
+ registerPlugin(rewriterPlugin);
256
+ registerPlugin(defaultEmptyResponsePlugin);
257
+
258
+ const decision = await runEmpty(
259
+ makeArgs({
260
+ responseContent: [],
261
+ toolUseTurns: 2,
262
+ priorAssistantHadVisibleText: false,
263
+ }),
264
+ );
265
+ expect(decision.action).toBe("nudge");
266
+ expect(decision.nudgeText).toBe("ALTERED_NUDGE");
267
+ });
268
+
269
+ test("user plugin registered AFTER the default still runs (no shadowing)", async () => {
270
+ // Production registration order: defaults load first via the side-effect
271
+ // imports in `defaults/index.ts`, then user plugins register on top via
272
+ // `bootstrapPlugins()`. The user's middleware ends up at a deeper onion
273
+ // layer than the default. If the default's middleware were to bypass
274
+ // `next` and decide directly, the user middleware would never run — this
275
+ // test guards against that regression.
276
+ registerPlugin(defaultEmptyResponsePlugin);
277
+
278
+ let userMiddlewareRan = false;
279
+ const userMiddleware: Middleware<
280
+ EmptyResponseArgs,
281
+ EmptyResponseDecision
282
+ > = async (args, next) => {
283
+ userMiddlewareRan = true;
284
+ return next(args);
285
+ };
286
+ registerPlugin({
287
+ manifest: {
288
+ name: "late-user-empty-response",
289
+ version: "0.0.1",
290
+ requires: { pluginRuntime: "v1", emptyResponseApi: "v1" },
291
+ },
292
+ middleware: { emptyResponse: userMiddleware },
293
+ });
294
+
295
+ await runEmpty(
296
+ makeArgs({
297
+ responseContent: [],
298
+ toolUseTurns: 2,
299
+ priorAssistantHadVisibleText: false,
300
+ }),
301
+ );
302
+
303
+ expect(userMiddlewareRan).toBe(true);
304
+ });
305
+ });
@@ -16,7 +16,7 @@ import { homedir } from "node:os";
16
16
  import { join, resolve } from "node:path";
17
17
  import { describe, expect, test } from "bun:test";
18
18
 
19
- import { ALLOWED_EXTENSION_ORIGINS } from "../runtime/routes/browser-extension-pair-routes.js";
19
+ import { getAllowedExtensionOrigins } from "../runtime/routes/browser-extension-pair-routes.js";
20
20
 
21
21
  const repoRoot = resolve(__dirname, "..", "..", "..");
22
22
  const CANONICAL_CONFIG_REL_PATH =
@@ -176,7 +176,7 @@ describe("Chrome extension allowlist guard", () => {
176
176
  const config = parseCanonicalConfig();
177
177
  for (const id of config.allowedExtensionIds) {
178
178
  const origin = `chrome-extension://${id}/`;
179
- expect(ALLOWED_EXTENSION_ORIGINS.has(origin)).toBe(true);
179
+ expect(getAllowedExtensionOrigins().has(origin)).toBe(true);
180
180
  }
181
181
  });
182
182
 
@@ -198,7 +198,7 @@ describe("Chrome extension allowlist guard", () => {
198
198
  const expectedOrigins = new Set(
199
199
  config.allowedExtensionIds.map((id) => `chrome-extension://${id}/`),
200
200
  );
201
- expect(new Set(ALLOWED_EXTENSION_ORIGINS)).toEqual(expectedOrigins);
201
+ expect(new Set(getAllowedExtensionOrigins())).toEqual(expectedOrigins);
202
202
  });
203
203
 
204
204
  test("concrete extension IDs appear only in canonical config or CWS URLs", () => {
@@ -6,6 +6,7 @@ const tempDir = process.env.VELLUM_WORKSPACE_DIR!;
6
6
 
7
7
  const { isWakeUpGreeting, getCannedFirstGreeting, CANNED_FIRST_GREETING } =
8
8
  await import("../daemon/first-greeting.js");
9
+ import type { OnboardingGreetingContext } from "../daemon/first-greeting.js";
9
10
 
10
11
  describe("first-greeting", () => {
11
12
  describe("isWakeUpGreeting", () => {
@@ -48,12 +49,253 @@ describe("first-greeting", () => {
48
49
  });
49
50
  });
50
51
 
51
- describe("getCannedFirstGreeting", () => {
52
- it("returns the expected greeting string", () => {
53
- const greeting = getCannedFirstGreeting();
52
+ describe("no-onboarding branch", () => {
53
+ it("returns no-onboarding greeting when context is undefined", () => {
54
+ expect(getCannedFirstGreeting(undefined)).toBe(CANNED_FIRST_GREETING);
55
+ });
56
+
57
+ it("returns no-onboarding greeting when everything is empty", () => {
58
+ const greeting = getCannedFirstGreeting({
59
+ tools: [],
60
+ tasks: [],
61
+ tone: "",
62
+ });
54
63
  expect(greeting).toBe(CANNED_FIRST_GREETING);
55
- expect(greeting).toContain("brand new");
56
- expect(greeting).toContain("No name, no memories");
64
+ });
65
+
66
+ it("no-onboarding greeting uses two-paragraph structure", () => {
67
+ expect(CANNED_FIRST_GREETING).toContain("\n\n");
68
+ expect(CANNED_FIRST_GREETING).toContain("I can ask");
69
+ });
70
+ });
71
+
72
+ describe("personalized greeting", () => {
73
+ const base: OnboardingGreetingContext = {
74
+ tools: [],
75
+ tasks: [],
76
+ tone: "balanced",
77
+ };
78
+
79
+ it("full dev stack: GitHub + Linear, Building + PM", () => {
80
+ const greeting = getCannedFirstGreeting({
81
+ ...base,
82
+ tools: ["github", "linear"],
83
+ tasks: ["code-building", "project-management"],
84
+ userName: "Bob",
85
+ assistantName: "Pip",
86
+ });
87
+ expect(greeting).toContain("Hey Bob, I'm Pip.");
88
+ expect(greeting).toContain("You mentioned using GitHub and Linear");
89
+ expect(greeting).toContain(
90
+ "shipping code or figuring out what to ship next",
91
+ );
92
+ expect(greeting).toContain("\n\n");
93
+ });
94
+
95
+ it("PM + comms: Linear + Notion, PM + Writing", () => {
96
+ const greeting = getCannedFirstGreeting({
97
+ ...base,
98
+ tools: ["linear", "notion"],
99
+ tasks: ["project-management", "writing"],
100
+ userName: "Bob",
101
+ assistantName: "Pip",
102
+ });
103
+ expect(greeting).toContain("You mentioned using Notion and Linear");
104
+ expect(greeting).toContain("writing a spec or pushing something forward");
105
+ });
106
+
107
+ it("writer: Notion + Google Drive, Writing only", () => {
108
+ const greeting = getCannedFirstGreeting({
109
+ ...base,
110
+ tools: ["notion", "google-drive"],
111
+ tasks: ["writing"],
112
+ userName: "Bob",
113
+ assistantName: "Luna",
114
+ });
115
+ expect(greeting).toContain("You mentioned using Notion and Google Drive");
116
+ expect(greeting).toContain("drafting something or cleaning up docs");
117
+ });
118
+
119
+ it("single task no tools: Building only", () => {
120
+ const greeting = getCannedFirstGreeting({
121
+ ...base,
122
+ tasks: ["code-building"],
123
+ userName: "Bob",
124
+ assistantName: "Pip",
125
+ });
126
+ expect(greeting).toContain("Probably shipping something or debugging");
127
+ expect(greeting).not.toContain("You mentioned using");
128
+ });
129
+
130
+ it("single tool single task: GitHub + Building", () => {
131
+ const greeting = getCannedFirstGreeting({
132
+ ...base,
133
+ tools: ["github"],
134
+ tasks: ["code-building"],
135
+ userName: "Bob",
136
+ assistantName: "Pip",
137
+ });
138
+ expect(greeting).toContain("You mentioned using GitHub");
139
+ });
140
+
141
+ it("many hats: 4 selections", () => {
142
+ const greeting = getCannedFirstGreeting({
143
+ ...base,
144
+ tasks: ["code-building", "writing", "research", "project-management"],
145
+ userName: "Bob",
146
+ assistantName: "Pip",
147
+ });
148
+ expect(greeting).toContain("wear a lot of hats");
149
+ expect(greeting).toContain("Where should we start?");
150
+ });
151
+
152
+ it("many hats: 6 selections", () => {
153
+ const greeting = getCannedFirstGreeting({
154
+ ...base,
155
+ tasks: [
156
+ "code-building",
157
+ "writing",
158
+ "research",
159
+ "project-management",
160
+ "scheduling",
161
+ "personal",
162
+ ],
163
+ userName: "Bob",
164
+ assistantName: "Pip",
165
+ });
166
+ expect(greeting).toContain("wear a lot of hats");
167
+ });
168
+
169
+ it("no tasks with tools: no-signal branch", () => {
170
+ const greeting = getCannedFirstGreeting({
171
+ ...base,
172
+ tools: ["gmail", "linear"],
173
+ tasks: [],
174
+ userName: "Bob",
175
+ assistantName: "Pip",
176
+ });
177
+ expect(greeting).toContain("What's on your plate?");
178
+ expect(greeting).toContain("I can ask you a few questions");
179
+ });
180
+
181
+ it("missing name uses Hey comma opener", () => {
182
+ const greeting = getCannedFirstGreeting({
183
+ ...base,
184
+ tasks: ["code-building"],
185
+ assistantName: "Pip",
186
+ });
187
+ expect(greeting).toStartWith("Hey, I'm Pip.");
188
+ });
189
+
190
+ it("3-selection falls back to highest-priority single template", () => {
191
+ const greeting = getCannedFirstGreeting({
192
+ ...base,
193
+ tasks: ["writing", "research", "scheduling"],
194
+ userName: "Bob",
195
+ assistantName: "Pip",
196
+ });
197
+ expect(greeting).toContain("drafting something or cleaning up docs");
198
+ });
199
+
200
+ it("unlisted 2-combo falls back to highest-priority single", () => {
201
+ const greeting = getCannedFirstGreeting({
202
+ ...base,
203
+ tasks: ["research", "personal"],
204
+ userName: "Bob",
205
+ assistantName: "Pip",
206
+ });
207
+ expect(greeting).toContain(
208
+ "digging into a topic or making sense of something",
209
+ );
210
+ });
211
+
212
+ it("uses capital I throughout", () => {
213
+ const greeting = getCannedFirstGreeting({
214
+ ...base,
215
+ tasks: ["code-building"],
216
+ userName: "Bob",
217
+ assistantName: "Pip",
218
+ });
219
+ expect(greeting).toContain("I'm Pip");
220
+ expect(greeting).toContain("I'll get sharper");
221
+ expect(greeting).toContain("Am I on the right track");
222
+ });
223
+
224
+ it("two-paragraph structure with blank line", () => {
225
+ const greeting = getCannedFirstGreeting({
226
+ ...base,
227
+ tasks: ["code-building"],
228
+ userName: "Bob",
229
+ assistantName: "Pip",
230
+ });
231
+ const paragraphs = greeting.split("\n\n");
232
+ expect(paragraphs.length).toBe(2);
233
+ });
234
+
235
+ it("picks relevant tools for guess, not arbitrary selection order", () => {
236
+ const greeting = getCannedFirstGreeting({
237
+ ...base,
238
+ tools: [
239
+ "notion",
240
+ "linear",
241
+ "gmail",
242
+ "google-calendar",
243
+ "github",
244
+ "apple-notes",
245
+ ],
246
+ tasks: ["code-building", "project-management"],
247
+ userName: "Bob",
248
+ assistantName: "Pip",
249
+ });
250
+ expect(greeting).toContain("You mentioned using GitHub and Linear");
251
+ expect(greeting).not.toContain("Apple Notes");
252
+ expect(greeting).not.toContain("Notion");
253
+ });
254
+
255
+ it("falls back to no-tool prefix when no relevant tools match", () => {
256
+ const greeting = getCannedFirstGreeting({
257
+ ...base,
258
+ tools: ["notion", "apple-notes"],
259
+ tasks: ["code-building"],
260
+ userName: "Bob",
261
+ assistantName: "Pip",
262
+ });
263
+ expect(greeting).toContain("Probably shipping something or debugging");
264
+ expect(greeting).not.toContain("You mentioned using");
265
+ });
266
+
267
+ it("life admin guess uses verb phrase", () => {
268
+ const greeting = getCannedFirstGreeting({
269
+ ...base,
270
+ tools: ["gmail", "google-calendar"],
271
+ tasks: ["personal"],
272
+ userName: "Bob",
273
+ assistantName: "Pip",
274
+ });
275
+ expect(greeting).toContain(
276
+ "You mentioned using Gmail and Google Calendar",
277
+ );
278
+ expect(greeting).toContain("juggling travel, bills, or household stuff");
279
+ });
280
+
281
+ it("uses personalized greeting when assistantName present but no user name/tasks/tools", () => {
282
+ const greeting = getCannedFirstGreeting({
283
+ ...base,
284
+ assistantName: "Pip",
285
+ });
286
+ expect(greeting).toContain("I'm Pip");
287
+ expect(greeting).not.toContain("no name, no memories");
288
+ });
289
+
290
+ it("falls back to no-signal branch for unknown task types", () => {
291
+ const greeting = getCannedFirstGreeting({
292
+ ...base,
293
+ tasks: ["future-task-type"],
294
+ userName: "Bob",
295
+ assistantName: "Pip",
296
+ });
297
+ expect(greeting).toContain("What's on your plate?");
298
+ expect(greeting).not.toContain("\n\n\n");
57
299
  });
58
300
  });
59
301
  });
@@ -550,6 +550,63 @@ describe("browser_mode wiring through tool execution", () => {
550
550
  expect(result.content).toContain("Remediation:");
551
551
  });
552
552
 
553
+ // ── Transport-classified host-browser errors produce failover diagnostics ──
554
+
555
+ test("pinned extension with transport-classified host_browser error surfaces failover diagnostics", async () => {
556
+ // Simulate a structured transport error envelope from the host_browser
557
+ // dispatcher (e.g. { code: "unreachable", message: "..." }) that the
558
+ // extension-cdp-client now classifies as transport_error.
559
+ factoryThrowError = new CdpError(
560
+ "transport_error",
561
+ "Host browser not reachable",
562
+ {
563
+ attemptDiagnostics: [
564
+ {
565
+ candidateKind: "extension",
566
+ inclusionReason: "pinned mode: extension",
567
+ stage: "send",
568
+ errorCode: "transport_error",
569
+ errorMessage: "Host browser not reachable",
570
+ },
571
+ ],
572
+ },
573
+ );
574
+
575
+ const result = await executeBrowserNavigate(
576
+ { url: "https://example.com", browser_mode: "extension" },
577
+ ctx,
578
+ );
579
+ expect(result.isError).toBe(true);
580
+ expect(result.content).toContain('Browser mode "extension" failed');
581
+ expect(result.content).toContain("extension: FAILED at send");
582
+ expect(result.content).toContain("Reason: Host browser not reachable");
583
+ expect(result.content).toContain("Remediation:");
584
+ expect(result.content).toContain("extension is installed and enabled");
585
+ });
586
+
587
+ test("pinned extension with timeout transport error surfaces failover diagnostics in snapshot", async () => {
588
+ factoryThrowError = new CdpError("transport_error", "CDP call timed out", {
589
+ attemptDiagnostics: [
590
+ {
591
+ candidateKind: "extension",
592
+ inclusionReason: "pinned mode: extension",
593
+ stage: "send",
594
+ errorCode: "transport_error",
595
+ errorMessage: "CDP call timed out",
596
+ },
597
+ ],
598
+ });
599
+
600
+ const result = await executeBrowserSnapshot(
601
+ { browser_mode: "extension" },
602
+ ctx,
603
+ );
604
+ expect(result.isError).toBe(true);
605
+ expect(result.content).toContain('Browser mode "extension" failed');
606
+ expect(result.content).toContain("extension: FAILED at send");
607
+ expect(result.content).toContain("Remediation:");
608
+ });
609
+
553
610
  // ── Per-conversation sticky backend kind ─────────────────────────
554
611
 
555
612
  test("auto-mode call after an explicit pin sticks to the pinned kind", async () => {