@vellumai/assistant 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (503) hide show
  1. package/ARCHITECTURE.md +11 -12
  2. package/docker-entrypoint.sh +13 -2
  3. package/docker-init-apt-root.sh +79 -6
  4. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  5. package/openapi.yaml +945 -36
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
  8. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  9. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +272 -0
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  14. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  15. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  16. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  17. package/src/__tests__/compaction-events.test.ts +1 -1
  18. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  19. package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
  20. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  22. package/src/__tests__/config-watcher.test.ts +1 -1
  23. package/src/__tests__/context-token-estimator.test.ts +112 -57
  24. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  27. package/src/__tests__/conversation-agent-loop.test.ts +77 -3
  28. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  29. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  30. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  32. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  33. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  34. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  35. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  36. package/src/__tests__/conversation-pairing.test.ts +2 -2
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  39. package/src/__tests__/conversation-queue.test.ts +1 -1
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
  41. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  42. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  43. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  44. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  45. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  46. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  47. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  48. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  49. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  50. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  51. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  52. package/src/__tests__/date-context.test.ts +45 -0
  53. package/src/__tests__/dm-backfill.test.ts +64 -0
  54. package/src/__tests__/dm-persistence.test.ts +33 -0
  55. package/src/__tests__/document-find-replace.test.ts +501 -0
  56. package/src/__tests__/external-plugin-loader.test.ts +91 -19
  57. package/src/__tests__/first-greeting.test.ts +23 -2
  58. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  59. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  60. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  61. package/src/__tests__/heartbeat-service.test.ts +24 -164
  62. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  63. package/src/__tests__/host-app-control-proxy.test.ts +241 -0
  64. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  66. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  67. package/src/__tests__/host-file-proxy.test.ts +8 -1
  68. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  69. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  70. package/src/__tests__/identity-routes.test.ts +57 -0
  71. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  72. package/src/__tests__/injector-background-turn.test.ts +153 -0
  73. package/src/__tests__/injector-chain.test.ts +7 -0
  74. package/src/__tests__/injector-document-comments.test.ts +378 -0
  75. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  76. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
  77. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  78. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  79. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  80. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  81. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  82. package/src/__tests__/llm-catalog-parity.test.ts +3 -0
  83. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  84. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  85. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  86. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
  87. package/src/__tests__/llm-resolver.test.ts +340 -3
  88. package/src/__tests__/log-export-routes.test.ts +99 -2
  89. package/src/__tests__/managed-profile-guard.test.ts +10 -0
  90. package/src/__tests__/message-queue-steer.test.ts +114 -0
  91. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  92. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  93. package/src/__tests__/notification-deep-link.test.ts +15 -0
  94. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  95. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  96. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  97. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  98. package/src/__tests__/openai-provider.test.ts +323 -3
  99. package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
  100. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  101. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  102. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  103. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  104. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  105. package/src/__tests__/platform-proxy-context.test.ts +6 -1
  106. package/src/__tests__/platform.test.ts +0 -3
  107. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  108. package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
  109. package/src/__tests__/plugin-types.test.ts +2 -2
  110. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  111. package/src/__tests__/process-message-display-content.test.ts +21 -16
  112. package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
  113. package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  115. package/src/__tests__/server-history-render.test.ts +83 -4
  116. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  117. package/src/__tests__/system-prompt.test.ts +57 -101
  118. package/src/__tests__/terminal-tools.test.ts +11 -1
  119. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  120. package/src/__tests__/thread-backfill.test.ts +370 -22
  121. package/src/__tests__/tool-executor.test.ts +90 -1
  122. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  123. package/src/__tests__/twilio-routes.test.ts +1 -1
  124. package/src/__tests__/web-fetch.test.ts +2 -2
  125. package/src/__tests__/workspace-git-service.test.ts +88 -5
  126. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  127. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  128. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  129. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  130. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  131. package/src/a2a/__tests__/task-store.test.ts +246 -0
  132. package/src/a2a/agent-card.ts +58 -0
  133. package/src/a2a/feature-gate.ts +8 -0
  134. package/src/a2a/protocol-constants.ts +21 -0
  135. package/src/a2a/protocol-errors.ts +50 -0
  136. package/src/a2a/protocol-types.ts +162 -0
  137. package/src/a2a/task-store.ts +168 -0
  138. package/src/agent/attachments.ts +1 -0
  139. package/src/agent/loop.ts +208 -22
  140. package/src/background-wake/next-wake.test.ts +289 -0
  141. package/src/background-wake/next-wake.ts +172 -0
  142. package/src/browser/operations.ts +15 -0
  143. package/src/channels/config.ts +9 -0
  144. package/src/channels/types.ts +14 -0
  145. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  146. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  147. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  148. package/src/cli/commands/__tests__/schedules.test.ts +469 -0
  149. package/src/cli/commands/conversations.ts +128 -1
  150. package/src/cli/commands/inference-providers.ts +147 -1
  151. package/src/cli/commands/memory-v2.ts +308 -0
  152. package/src/cli/commands/notifications.ts +89 -37
  153. package/src/cli/commands/plugins.ts +67 -0
  154. package/src/cli/commands/schedules.ts +297 -5
  155. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  156. package/src/cli/lib/install-from-github.ts +8 -9
  157. package/src/cli/lib/search-plugins.ts +163 -0
  158. package/src/cli/program.ts +14 -0
  159. package/src/cli/utils/conversation-id.ts +17 -5
  160. package/src/config/assistant-feature-flags.ts +24 -54
  161. package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
  162. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  163. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  164. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  165. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  166. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  167. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  168. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  169. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  170. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  171. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  172. package/src/config/bundled-tool-registry.ts +22 -12
  173. package/src/config/call-site-defaults.ts +124 -0
  174. package/src/config/feature-flag-registry.json +111 -23
  175. package/src/config/llm-resolver.ts +66 -1
  176. package/src/config/schema.ts +2 -0
  177. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
  178. package/src/config/schemas/call-site-catalog.ts +21 -0
  179. package/src/config/schemas/channels.ts +9 -0
  180. package/src/config/schemas/conversations.ts +10 -0
  181. package/src/config/schemas/heartbeat.ts +14 -0
  182. package/src/config/schemas/llm.ts +4 -3
  183. package/src/config/schemas/memory-retrospective.ts +1 -1
  184. package/src/config/schemas/memory-v2.ts +51 -4
  185. package/src/config/schemas/memory.ts +3 -1
  186. package/src/config/seed-inference-profiles.ts +99 -29
  187. package/src/context/compactor.ts +80 -13
  188. package/src/context/token-estimator.ts +72 -31
  189. package/src/context/window-manager.ts +25 -0
  190. package/src/credential-health/credential-health-service.ts +34 -19
  191. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
  192. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  193. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  194. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  195. package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
  196. package/src/daemon/conversation-agent-loop.ts +252 -56
  197. package/src/daemon/conversation-lifecycle.ts +142 -116
  198. package/src/daemon/conversation-messaging.ts +3 -0
  199. package/src/daemon/conversation-process.ts +273 -0
  200. package/src/daemon/conversation-queue-manager.ts +14 -0
  201. package/src/daemon/conversation-runtime-assembly.ts +144 -75
  202. package/src/daemon/conversation-slash.ts +37 -5
  203. package/src/daemon/conversation-surfaces.ts +45 -2
  204. package/src/daemon/conversation-tool-setup.ts +7 -0
  205. package/src/daemon/conversation.ts +42 -12
  206. package/src/daemon/date-context.ts +40 -0
  207. package/src/daemon/first-greeting.ts +10 -0
  208. package/src/daemon/guardian-action-generators.ts +1 -125
  209. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  210. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  211. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  212. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  213. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  214. package/src/daemon/handlers/config-a2a.ts +449 -0
  215. package/src/daemon/handlers/config-model.test.ts +1 -0
  216. package/src/daemon/handlers/conversations.ts +80 -0
  217. package/src/daemon/handlers/shared.ts +92 -29
  218. package/src/daemon/host-app-control-proxy.ts +69 -18
  219. package/src/daemon/host-bash-proxy.ts +1 -1
  220. package/src/daemon/host-cu-proxy.ts +1 -1
  221. package/src/daemon/host-file-proxy.ts +1 -1
  222. package/src/daemon/host-proxy-preactivation.ts +85 -18
  223. package/src/daemon/host-transfer-proxy.ts +1 -1
  224. package/src/daemon/lifecycle.ts +67 -65
  225. package/src/daemon/memory-v2-startup.ts +49 -13
  226. package/src/daemon/message-protocol.ts +4 -0
  227. package/src/daemon/message-types/conversations.ts +8 -0
  228. package/src/daemon/message-types/document-comments.ts +50 -0
  229. package/src/daemon/message-types/messages.ts +68 -1
  230. package/src/daemon/message-types/notifications.ts +21 -0
  231. package/src/daemon/message-types/surfaces.ts +3 -1
  232. package/src/daemon/message-types/web-activity.ts +57 -0
  233. package/src/daemon/pkb-reminder-builder.test.ts +10 -53
  234. package/src/daemon/pkb-reminder-builder.ts +4 -19
  235. package/src/daemon/plugin-source-watcher.ts +135 -3
  236. package/src/daemon/process-message.ts +72 -12
  237. package/src/daemon/query-complexity-router.ts +75 -0
  238. package/src/daemon/skill-memory-refresh.ts +5 -1
  239. package/src/daemon/trust-context.ts +6 -0
  240. package/src/daemon/wake-target-adapter.ts +2 -0
  241. package/src/documents/document-comments-store.test.ts +338 -0
  242. package/src/documents/document-comments-store.ts +237 -0
  243. package/src/documents/document-store.ts +202 -0
  244. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  245. package/src/export/transcript-formatter.ts +54 -20
  246. package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
  247. package/src/heartbeat/heartbeat-service.ts +35 -191
  248. package/src/home/__tests__/feed-types.test.ts +40 -0
  249. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  250. package/src/home/feed-types.ts +20 -3
  251. package/src/home/home-content-refresh.ts +52 -0
  252. package/src/home/home-greeting-cache.ts +69 -0
  253. package/src/home/home-greeting.ts +94 -0
  254. package/src/home/suggested-prompts.ts +177 -9
  255. package/src/ipc/cli-client.ts +147 -45
  256. package/src/memory/__tests__/conversation-queries.test.ts +220 -0
  257. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  258. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  259. package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
  260. package/src/memory/conversation-crud.ts +133 -43
  261. package/src/memory/conversation-queries.ts +87 -1
  262. package/src/memory/conversation-title-service.ts +26 -4
  263. package/src/memory/db-init.ts +22 -0
  264. package/src/memory/delivery-crud.ts +41 -0
  265. package/src/memory/delivery-status.ts +141 -15
  266. package/src/memory/external-conversation-store.ts +32 -1
  267. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
  268. package/src/memory/graph/conversation-graph-memory.ts +18 -6
  269. package/src/memory/graph/tools.ts +6 -37
  270. package/src/memory/invite-store.ts +53 -0
  271. package/src/memory/jobs-worker.ts +21 -1
  272. package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
  273. package/src/memory/llm-request-log-store.ts +92 -1
  274. package/src/memory/memory-retrospective-constants.ts +28 -0
  275. package/src/memory/memory-retrospective-enqueue.ts +4 -22
  276. package/src/memory/memory-retrospective-job.ts +438 -21
  277. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  278. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  279. package/src/memory/migrations/100-core-tables.ts +1 -0
  280. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  281. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  282. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  283. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  284. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  285. package/src/memory/migrations/253-document-comments.ts +47 -0
  286. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  287. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  288. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  289. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  290. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  291. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  292. package/src/memory/migrations/index.ts +20 -0
  293. package/src/memory/migrations/registry.ts +33 -0
  294. package/src/memory/onboarding-events-store.ts +7 -0
  295. package/src/memory/schema/a2a.ts +15 -0
  296. package/src/memory/schema/calls.ts +1 -0
  297. package/src/memory/schema/conversations.ts +3 -0
  298. package/src/memory/schema/index.ts +1 -0
  299. package/src/memory/schema/inference.ts +2 -0
  300. package/src/memory/schema/infrastructure.ts +2 -0
  301. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  302. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  303. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  304. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  305. package/src/memory/v2/__tests__/injection.test.ts +221 -17
  306. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  307. package/src/memory/v2/__tests__/router.test.ts +489 -1
  308. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  309. package/src/memory/v2/activation-store.ts +14 -16
  310. package/src/memory/v2/cli-command-content.ts +19 -0
  311. package/src/memory/v2/cli-command-store.ts +304 -0
  312. package/src/memory/v2/consolidation-job.ts +14 -0
  313. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  314. package/src/memory/v2/injection-events.ts +101 -0
  315. package/src/memory/v2/injection.ts +69 -29
  316. package/src/memory/v2/page-index.ts +246 -19
  317. package/src/memory/v2/page-store.ts +18 -0
  318. package/src/memory/v2/router.ts +209 -55
  319. package/src/memory/v2/static-context.ts +4 -4
  320. package/src/memory/v2/types.ts +23 -0
  321. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  322. package/src/messaging/providers/a2a/deliver.ts +156 -0
  323. package/src/messaging/providers/gmail/client.ts +9 -2
  324. package/src/messaging/providers/index.ts +18 -3
  325. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  327. package/src/messaging/providers/slack/adapter.ts +178 -25
  328. package/src/messaging/providers/slack/api.test.ts +54 -0
  329. package/src/messaging/providers/slack/api.ts +119 -3
  330. package/src/messaging/providers/slack/client.ts +12 -0
  331. package/src/messaging/providers/slack/deep-link.ts +20 -1
  332. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  333. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  334. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  335. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  336. package/src/messaging/providers/slack/send.test.ts +77 -0
  337. package/src/messaging/providers/slack/send.ts +8 -2
  338. package/src/messaging/providers/slack/types.ts +14 -0
  339. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  340. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  341. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  342. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
  343. package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
  344. package/src/notifications/adapters/macos.ts +12 -2
  345. package/src/notifications/broadcaster.ts +29 -4
  346. package/src/notifications/conversation-seed-composer.ts +14 -2
  347. package/src/notifications/copy-composer.ts +17 -64
  348. package/src/notifications/decision-engine.ts +111 -44
  349. package/src/notifications/deferred-emit.ts +135 -0
  350. package/src/notifications/deterministic-checks.ts +96 -0
  351. package/src/notifications/emit-signal.ts +10 -1
  352. package/src/notifications/home-feed-side-effect.ts +136 -27
  353. package/src/notifications/signal.ts +0 -4
  354. package/src/notifications/types.ts +8 -0
  355. package/src/oauth/connect-orchestrator.ts +3 -0
  356. package/src/oauth/credential-token-resolver.ts +2 -0
  357. package/src/oauth/manual-token-connection.ts +19 -0
  358. package/src/oauth/oauth-store.ts +12 -0
  359. package/src/oauth/platform-connection.test.ts +43 -3
  360. package/src/oauth/platform-connection.ts +13 -4
  361. package/src/oauth/seed-providers.ts +22 -0
  362. package/src/permissions/prompter.ts +5 -2
  363. package/src/permissions/secret-prompter.ts +4 -1
  364. package/src/plugins/defaults/injectors.ts +118 -26
  365. package/src/plugins/external-plugin-loader.ts +82 -10
  366. package/src/plugins/types.ts +16 -7
  367. package/src/prompts/__tests__/system-prompt.test.ts +44 -45
  368. package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
  369. package/src/prompts/normalize-onboarding.ts +40 -0
  370. package/src/prompts/sections.ts +32 -14
  371. package/src/prompts/system-prompt.ts +105 -76
  372. package/src/prompts/template-detection.ts +37 -0
  373. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  374. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  375. package/src/prompts/templates/VOICE.md +3 -0
  376. package/src/prompts/templates/system-sections.ts +51 -10
  377. package/src/providers/__tests__/inference.test.ts +2 -0
  378. package/src/providers/anthropic/client.ts +132 -5
  379. package/src/providers/call-site-routing.ts +24 -6
  380. package/src/providers/connection-resolution.ts +63 -13
  381. package/src/providers/fireworks/client.ts +20 -2
  382. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  383. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  384. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  385. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  386. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  387. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  388. package/src/providers/inference/adapter-factory.ts +24 -21
  389. package/src/providers/inference/auth.ts +15 -3
  390. package/src/providers/inference/backfill.ts +14 -1
  391. package/src/providers/inference/codex-token-refresh.ts +128 -0
  392. package/src/providers/inference/connections.ts +85 -5
  393. package/src/providers/inference/resolve-auth.ts +50 -5
  394. package/src/providers/model-catalog.ts +244 -242
  395. package/src/providers/model-intents.ts +3 -3
  396. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  397. package/src/providers/openai/chat-completions-provider.ts +215 -25
  398. package/src/providers/openai/responses-provider.ts +9 -3
  399. package/src/providers/openrouter/client.ts +46 -4
  400. package/src/providers/platform-proxy/constants.ts +3 -4
  401. package/src/providers/provider-catalog-visibility.ts +3 -1
  402. package/src/providers/provider-send-message.ts +27 -12
  403. package/src/providers/registry.ts +30 -1
  404. package/src/providers/types.ts +25 -0
  405. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  406. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  407. package/src/runtime/agent-wake.ts +212 -57
  408. package/src/runtime/auth/route-policy.ts +20 -3
  409. package/src/runtime/background-job-runner.ts +26 -0
  410. package/src/runtime/channel-reply-delivery.ts +182 -47
  411. package/src/runtime/channel-retry-sweep.ts +141 -16
  412. package/src/runtime/http-server.ts +7 -16
  413. package/src/runtime/http-types.ts +7 -51
  414. package/src/runtime/pending-interactions.ts +51 -8
  415. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  416. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  417. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
  418. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  419. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  420. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  421. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  422. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  423. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  424. package/src/runtime/routes/approval-routes.ts +4 -1
  425. package/src/runtime/routes/channel-availability-routes.ts +5 -0
  426. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  427. package/src/runtime/routes/consolidation-routes.ts +100 -0
  428. package/src/runtime/routes/content-source-routes.ts +78 -0
  429. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  430. package/src/runtime/routes/conversation-query-routes.ts +130 -12
  431. package/src/runtime/routes/conversation-routes.ts +288 -76
  432. package/src/runtime/routes/document-comments-routes.ts +287 -0
  433. package/src/runtime/routes/documents-routes.ts +33 -0
  434. package/src/runtime/routes/home-feed-routes.ts +6 -3
  435. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  436. package/src/runtime/routes/host-browser-routes.ts +8 -1
  437. package/src/runtime/routes/identity-routes.ts +21 -0
  438. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  439. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  440. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  441. package/src/runtime/routes/index.ts +14 -4
  442. package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
  443. package/src/runtime/routes/integrations/a2a.ts +294 -0
  444. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  445. package/src/runtime/routes/log-export-routes.ts +39 -0
  446. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  447. package/src/runtime/routes/notification-routes.ts +19 -2
  448. package/src/runtime/routes/question-routes.ts +4 -1
  449. package/src/runtime/routes/sanity-routes.ts +159 -0
  450. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  451. package/src/runtime/routes/subagents-routes.ts +41 -0
  452. package/src/runtime/services/conversation-serializer.ts +30 -4
  453. package/src/schedule/integration-status.ts +3 -1
  454. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  455. package/src/security/oauth2-device-code.ts +307 -0
  456. package/src/security/oauth2.ts +26 -9
  457. package/src/security/secure-keys.ts +5 -0
  458. package/src/skills/catalog-install.ts +6 -2
  459. package/src/subagent/manager.ts +2 -0
  460. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  461. package/src/tools/browser/browser-execution.ts +93 -0
  462. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  463. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  464. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  465. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  466. package/src/tools/browser/cdp-client/factory.ts +87 -3
  467. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  468. package/src/tools/browser/cdp-client/types.ts +36 -0
  469. package/src/tools/browser/pinned-tabs.ts +90 -0
  470. package/src/tools/document/document-comment-tool.test.ts +379 -0
  471. package/src/tools/document/document-comment-tool.ts +156 -0
  472. package/src/tools/document/document-tool.ts +128 -2
  473. package/src/tools/memory/register.ts +1 -9
  474. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  475. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  476. package/src/tools/network/domain-normalize.ts +17 -0
  477. package/src/tools/network/web-fetch.ts +213 -64
  478. package/src/tools/network/web-search.ts +191 -66
  479. package/src/tools/registry.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +3 -2
  481. package/src/tools/tool-approval-handler.ts +19 -12
  482. package/src/tools/types.ts +41 -2
  483. package/src/tools/ui-surface/definitions.ts +3 -1
  484. package/src/types/onboarding-context.ts +4 -0
  485. package/src/util/__tests__/favicon.test.ts +84 -0
  486. package/src/util/favicon.ts +40 -0
  487. package/src/util/platform.ts +0 -5
  488. package/src/workspace/git-service.ts +75 -4
  489. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  490. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  491. package/src/workspace/migrations/registry.ts +4 -0
  492. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  493. package/src/config/bundled-skills/document/SKILL.md +0 -54
  494. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  495. package/src/daemon/seed-files.ts +0 -18
  496. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  497. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
  498. package/src/runtime/routes/interface-routes.ts +0 -43
  499. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  500. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  501. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  502. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  503. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -1,8 +1,13 @@
1
1
  import { getConfig } from "../../config/loader.js";
2
+ import type {
3
+ WebSearchMetadata,
4
+ WebSearchResultItem,
5
+ } from "../../daemon/message-types/web-activity.js";
2
6
  import { RiskLevel } from "../../permissions/types.js";
3
7
  import type { ToolDefinition } from "../../providers/types.js";
4
8
  import { getProviderKeyAsync } from "../../security/secure-keys.js";
5
9
  import { wrapUntrustedContent } from "../../security/untrusted-content.js";
10
+ import { faviconUrlForDomain } from "../../util/favicon.js";
6
11
  import { getLogger } from "../../util/logger.js";
7
12
  import {
8
13
  DEFAULT_BASE_DELAY_MS,
@@ -12,6 +17,7 @@ import {
12
17
  } from "../../util/retry.js";
13
18
  import { registerTool } from "../registry.js";
14
19
  import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
20
+ import { extractDomain } from "./domain-normalize.js";
15
21
 
16
22
  const log = getLogger("web-search");
17
23
 
@@ -201,6 +207,85 @@ function formatTavilyResults(
201
207
  return lines.join("\n");
202
208
  }
203
209
 
210
+ function buildBraveMetadata(
211
+ results: BraveSearchResult[],
212
+ query: string,
213
+ durationMs: number,
214
+ ): WebSearchMetadata {
215
+ const items: WebSearchResultItem[] = results.map((r, i) => {
216
+ const domain = extractDomain(r.url);
217
+ return {
218
+ rank: i + 1,
219
+ title: r.title,
220
+ url: r.url,
221
+ domain,
222
+ faviconUrl: faviconUrlForDomain(domain),
223
+ snippet: r.description,
224
+ age: r.age,
225
+ };
226
+ });
227
+ return {
228
+ query,
229
+ provider: "brave",
230
+ resultCount: items.length,
231
+ durationMs,
232
+ results: items,
233
+ };
234
+ }
235
+
236
+ function buildPerplexityMetadata(
237
+ data: PerplexityResponse,
238
+ query: string,
239
+ durationMs: number,
240
+ ): WebSearchMetadata {
241
+ const citations = data.citations ?? [];
242
+ const items: WebSearchResultItem[] = citations.map((url, i) => {
243
+ const domain = extractDomain(url);
244
+ return {
245
+ rank: i + 1,
246
+ title: "",
247
+ url,
248
+ domain,
249
+ faviconUrl: faviconUrlForDomain(domain),
250
+ };
251
+ });
252
+ return {
253
+ query,
254
+ provider: "perplexity",
255
+ resultCount: items.length,
256
+ durationMs,
257
+ results: items,
258
+ };
259
+ }
260
+
261
+ function buildTavilyMetadata(
262
+ data: TavilySearchResponse,
263
+ query: string,
264
+ durationMs: number,
265
+ ): WebSearchMetadata {
266
+ const results = data.results ?? [];
267
+ const items: WebSearchResultItem[] = results.map((r, i) => {
268
+ const url = r.url ?? "";
269
+ const domain = extractDomain(url);
270
+ return {
271
+ rank: i + 1,
272
+ title: r.title?.trim() || url.trim() || "Untitled result",
273
+ url,
274
+ domain,
275
+ faviconUrl: r.favicon ?? faviconUrlForDomain(domain),
276
+ snippet: r.content,
277
+ score: r.score,
278
+ };
279
+ });
280
+ return {
281
+ query,
282
+ provider: "tavily",
283
+ resultCount: items.length,
284
+ durationMs,
285
+ results: items,
286
+ };
287
+ }
288
+
204
289
  function tavilyTimeRangeForFreshness(
205
290
  freshness: string | undefined,
206
291
  ): "day" | "week" | "month" | "year" | undefined {
@@ -218,6 +303,28 @@ function tavilyTimeRangeForFreshness(
218
303
  }
219
304
  }
220
305
 
306
+ function errorResult(
307
+ query: string,
308
+ provider: WebSearchProvider,
309
+ startedAt: number,
310
+ errorMessage: string,
311
+ ): ToolExecutionResult {
312
+ return {
313
+ content: `Error: ${errorMessage}`,
314
+ isError: true,
315
+ activityMetadata: {
316
+ webSearch: {
317
+ query,
318
+ provider,
319
+ resultCount: 0,
320
+ durationMs: Date.now() - startedAt,
321
+ results: [],
322
+ errorMessage,
323
+ },
324
+ },
325
+ };
326
+ }
327
+
221
328
  async function executeBraveSearch(
222
329
  query: string,
223
330
  count: number,
@@ -238,6 +345,7 @@ async function executeBraveSearch(
238
345
  }
239
346
 
240
347
  const url = `${BRAVE_API_URL}?${params.toString()}`;
348
+ const startedAt = Date.now();
241
349
 
242
350
  for (let attempt = 0; attempt <= DEFAULT_MAX_RETRIES; attempt++) {
243
351
  const response = await fetch(url, {
@@ -252,6 +360,7 @@ async function executeBraveSearch(
252
360
  if (response.ok) {
253
361
  const data = (await response.json()) as BraveSearchResponse;
254
362
  const results = data.web?.results ?? [];
363
+ const durationMs = Date.now() - startedAt;
255
364
  return {
256
365
  content:
257
366
  wrapUntrustedContent(formatBraveResults(results, query), {
@@ -259,16 +368,21 @@ async function executeBraveSearch(
259
368
  sourceDetail: "brave",
260
369
  }) + CITATION_INSTRUCTION,
261
370
  isError: false,
371
+ activityMetadata: {
372
+ webSearch: buildBraveMetadata(results, query, durationMs),
373
+ },
262
374
  };
263
375
  }
264
376
 
265
377
  await response.text();
266
378
 
267
379
  if (response.status === 401 || response.status === 403) {
268
- return {
269
- content: "Error: Invalid or expired Brave Search API key",
270
- isError: true,
271
- };
380
+ return errorResult(
381
+ query,
382
+ "brave",
383
+ startedAt,
384
+ "Invalid or expired Brave Search API key",
385
+ );
272
386
  }
273
387
 
274
388
  if (response.status === 429 && attempt < DEFAULT_MAX_RETRIES) {
@@ -286,24 +400,22 @@ async function executeBraveSearch(
286
400
  }
287
401
 
288
402
  log.warn({ status: response.status }, "Brave Search API error");
289
- if (response.status === 429) {
290
- return {
291
- content:
292
- "Error: Brave Search rate limit exceeded after retries. Try again shortly.",
293
- isError: true,
294
- };
295
- }
296
- return {
297
- content: `Error: Brave Search API returned status ${response.status}`,
298
- isError: true,
299
- };
403
+ return errorResult(
404
+ query,
405
+ "brave",
406
+ startedAt,
407
+ response.status === 429
408
+ ? "Brave Search rate limit exceeded after retries. Try again shortly."
409
+ : `Brave Search API returned status ${response.status}`,
410
+ );
300
411
  }
301
412
 
302
- return {
303
- content:
304
- "Error: Brave Search rate limit exceeded after retries. Try again shortly.",
305
- isError: true,
306
- };
413
+ return errorResult(
414
+ query,
415
+ "brave",
416
+ startedAt,
417
+ "Brave Search rate limit exceeded after retries. Try again shortly.",
418
+ );
307
419
  }
308
420
 
309
421
  async function executePerplexitySearch(
@@ -311,6 +423,7 @@ async function executePerplexitySearch(
311
423
  apiKey: string,
312
424
  signal?: AbortSignal,
313
425
  ): Promise<ToolExecutionResult> {
426
+ const startedAt = Date.now();
314
427
  for (let attempt = 0; attempt <= DEFAULT_MAX_RETRIES; attempt++) {
315
428
  const response = await fetch(PERPLEXITY_API_URL, {
316
429
  method: "POST",
@@ -327,6 +440,7 @@ async function executePerplexitySearch(
327
440
 
328
441
  if (response.ok) {
329
442
  const data = (await response.json()) as PerplexityResponse;
443
+ const durationMs = Date.now() - startedAt;
330
444
  return {
331
445
  content:
332
446
  wrapUntrustedContent(formatPerplexityResults(data, query), {
@@ -334,16 +448,21 @@ async function executePerplexitySearch(
334
448
  sourceDetail: "perplexity",
335
449
  }) + CITATION_INSTRUCTION,
336
450
  isError: false,
451
+ activityMetadata: {
452
+ webSearch: buildPerplexityMetadata(data, query, durationMs),
453
+ },
337
454
  };
338
455
  }
339
456
 
340
457
  await response.text();
341
458
 
342
459
  if (response.status === 401 || response.status === 403) {
343
- return {
344
- content: "Error: Invalid or expired Perplexity API key",
345
- isError: true,
346
- };
460
+ return errorResult(
461
+ query,
462
+ "perplexity",
463
+ startedAt,
464
+ "Invalid or expired Perplexity API key",
465
+ );
347
466
  }
348
467
 
349
468
  if (response.status === 429 && attempt < DEFAULT_MAX_RETRIES) {
@@ -361,24 +480,22 @@ async function executePerplexitySearch(
361
480
  }
362
481
 
363
482
  log.warn({ status: response.status }, "Perplexity API error");
364
- if (response.status === 429) {
365
- return {
366
- content:
367
- "Error: Perplexity rate limit exceeded after retries. Try again shortly.",
368
- isError: true,
369
- };
370
- }
371
- return {
372
- content: `Error: Perplexity API returned status ${response.status}`,
373
- isError: true,
374
- };
483
+ return errorResult(
484
+ query,
485
+ "perplexity",
486
+ startedAt,
487
+ response.status === 429
488
+ ? "Perplexity rate limit exceeded after retries. Try again shortly."
489
+ : `Perplexity API returned status ${response.status}`,
490
+ );
375
491
  }
376
492
 
377
- return {
378
- content:
379
- "Error: Perplexity rate limit exceeded after retries. Try again shortly.",
380
- isError: true,
381
- };
493
+ return errorResult(
494
+ query,
495
+ "perplexity",
496
+ startedAt,
497
+ "Perplexity rate limit exceeded after retries. Try again shortly.",
498
+ );
382
499
  }
383
500
 
384
501
  async function executeTavilySearch(
@@ -398,6 +515,8 @@ async function executeTavilySearch(
398
515
  body.time_range = timeRange;
399
516
  }
400
517
 
518
+ const startedAt = Date.now();
519
+
401
520
  for (let attempt = 0; attempt <= DEFAULT_MAX_RETRIES; attempt++) {
402
521
  const response = await fetch(TAVILY_API_URL, {
403
522
  method: "POST",
@@ -412,6 +531,7 @@ async function executeTavilySearch(
412
531
 
413
532
  if (response.ok) {
414
533
  const data = (await response.json()) as TavilySearchResponse;
534
+ const durationMs = Date.now() - startedAt;
415
535
  return {
416
536
  content:
417
537
  wrapUntrustedContent(formatTavilyResults(data, query), {
@@ -419,16 +539,21 @@ async function executeTavilySearch(
419
539
  sourceDetail: "tavily",
420
540
  }) + CITATION_INSTRUCTION,
421
541
  isError: false,
542
+ activityMetadata: {
543
+ webSearch: buildTavilyMetadata(data, query, durationMs),
544
+ },
422
545
  };
423
546
  }
424
547
 
425
548
  await response.text();
426
549
 
427
550
  if (response.status === 401 || response.status === 403) {
428
- return {
429
- content: "Error: Invalid or expired Tavily API key",
430
- isError: true,
431
- };
551
+ return errorResult(
552
+ query,
553
+ "tavily",
554
+ startedAt,
555
+ "Invalid or expired Tavily API key",
556
+ );
432
557
  }
433
558
 
434
559
  if (response.status === 429 && attempt < DEFAULT_MAX_RETRIES) {
@@ -446,24 +571,22 @@ async function executeTavilySearch(
446
571
  }
447
572
 
448
573
  log.warn({ status: response.status }, "Tavily Search API error");
449
- if (response.status === 429) {
450
- return {
451
- content:
452
- "Error: Tavily Search rate limit exceeded after retries. Try again shortly.",
453
- isError: true,
454
- };
455
- }
456
- return {
457
- content: `Error: Tavily Search API returned status ${response.status}`,
458
- isError: true,
459
- };
574
+ return errorResult(
575
+ query,
576
+ "tavily",
577
+ startedAt,
578
+ response.status === 429
579
+ ? "Tavily Search rate limit exceeded after retries. Try again shortly."
580
+ : `Tavily Search API returned status ${response.status}`,
581
+ );
460
582
  }
461
583
 
462
- return {
463
- content:
464
- "Error: Tavily Search rate limit exceeded after retries. Try again shortly.",
465
- isError: true,
466
- };
584
+ return errorResult(
585
+ query,
586
+ "tavily",
587
+ startedAt,
588
+ "Tavily Search rate limit exceeded after retries. Try again shortly.",
589
+ );
467
590
  }
468
591
 
469
592
  // ----------------------------------------------------------------------------
@@ -573,6 +696,7 @@ class WebSearchTool implements Tool {
573
696
  };
574
697
  }
575
698
 
699
+ const startedAt = Date.now();
576
700
  let provider = getWebSearchProvider();
577
701
  let apiKey = await getApiKey(provider);
578
702
 
@@ -594,11 +718,12 @@ class WebSearchTool implements Tool {
594
718
  }
595
719
 
596
720
  if (!apiKey) {
597
- return {
598
- content:
599
- "Error: No web search API key configured. Set it via `keys set perplexity <key>`, `keys set brave <key>`, or `keys set tavily <key>`, or configure it from the Settings page under API Keys.",
600
- isError: true,
601
- };
721
+ return errorResult(
722
+ query,
723
+ provider,
724
+ startedAt,
725
+ "No web search API key configured. Set it via `keys set perplexity <key>`, `keys set brave <key>`, or `keys set tavily <key>`, or configure it from the Settings page under API Keys.",
726
+ );
602
727
  }
603
728
  }
604
729
 
@@ -627,7 +752,7 @@ class WebSearchTool implements Tool {
627
752
  } catch (err) {
628
753
  const msg = err instanceof Error ? err.message : String(err);
629
754
  log.error({ err }, "Web search failed");
630
- return { content: `Error: Web search failed: ${msg}`, isError: true };
755
+ return errorResult(query, provider, startedAt, `Web search failed: ${msg}`);
631
756
  }
632
757
  }
633
758
  }
@@ -10,7 +10,7 @@ import { hostFileWriteTool } from "./host-filesystem/write.js";
10
10
  import { hostShellTool } from "./host-terminal/host-shell.js";
11
11
  import { toProviderSafeToolName } from "./provider-tool-name.js";
12
12
  import { registerSystemTools } from "./system/register.js";
13
- import type { PluginTool, Tool } from "./types.js";
13
+ import type { LoadedPluginTool, Tool } from "./types.js";
14
14
  import { allUiSurfaceTools } from "./ui-surface/definitions.js";
15
15
  import { registerUiSurfaceTools } from "./ui-surface/registry.js";
16
16
 
@@ -193,7 +193,7 @@ export function registerSkillTools(newTools: Tool[]): Tool[] {
193
193
  */
194
194
  export function registerPluginTools(
195
195
  pluginName: string,
196
- newTools: PluginTool[],
196
+ newTools: LoadedPluginTool[],
197
197
  ): Tool[] {
198
198
  const stamped: Tool[] = newTools.map((pluginTool) => {
199
199
  const { input_schema, ...rest } = pluginTool;
@@ -61,12 +61,13 @@ export const SAFE_ENV_VARS = [
61
61
  ] as const;
62
62
 
63
63
  export const KATA_SAFE_ENV_VARS = [
64
- "LD_LIBRARY_PATH",
65
64
  "VELLUM_APT_DATA_ROOT",
66
65
  "VELLUM_APT_DATA_SUITE",
67
66
  "VELLUM_APT_DATA_MIRROR",
68
67
  ] as const;
69
68
 
69
+ export const KATA_INJECTED_ENV_VARS = ["LD_LIBRARY_PATH"] as const;
70
+
70
71
  const KATA_APT_DATA_ROOT = "/data/system";
71
72
 
72
73
  function kataAptPaths(dataRoot: string): string[] {
@@ -132,7 +133,7 @@ export function buildSanitizedEnv(): Record<string, string> {
132
133
  env.VELLUM_APT_DATA_ROOT = kataAptDataRoot;
133
134
  env.PATH = appendUniquePathEntries(env.PATH, kataAptPaths(kataAptDataRoot));
134
135
  env.LD_LIBRARY_PATH = appendUniquePathEntries(
135
- env.LD_LIBRARY_PATH,
136
+ undefined,
136
137
  kataAptLibraryPaths(kataAptDataRoot),
137
138
  );
138
139
  }
@@ -334,9 +334,19 @@ export class ToolApprovalHandler {
334
334
  return { allowed: false, result: { content: msg, isError: true } };
335
335
  }
336
336
 
337
- // Gate tools not active for the current turn
338
- if (context.allowedToolNames && !context.allowedToolNames.has(name)) {
339
- const msg = `Tool "${name}" is not currently active. Load the skill that provides this tool first.`;
337
+ // Look up the tool before the allowedToolNames gate so a name no skill
338
+ // provides surfaces as "Unknown tool" (with the real list) instead of
339
+ // the misleading "load the skill" hint.
340
+ const tool = getTool(name);
341
+ if (!tool) {
342
+ const allowedToolNames = context.allowedToolNames;
343
+ const available = getAllTools()
344
+ .filter((t) => t.executionMode !== "proxy" || context.proxyToolResolver)
345
+ .map((t) => t.name)
346
+ .filter((n) => !allowedToolNames || allowedToolNames.has(n))
347
+ .sort()
348
+ .join(", ");
349
+ const msg = `Unknown tool: ${name}. Available tools: ${available}`;
340
350
  const durationMs = Date.now() - startTime;
341
351
  emitLifecycleEvent({
342
352
  type: "error",
@@ -356,15 +366,12 @@ export class ToolApprovalHandler {
356
366
  return { allowed: false, result: { content: msg, isError: true } };
357
367
  }
358
368
 
359
- // Resolve the tool from the registry
360
- const tool = getTool(name);
361
- if (!tool) {
362
- const available = getAllTools()
363
- .filter((t) => t.executionMode !== "proxy" || context.proxyToolResolver)
364
- .map((t) => t.name)
365
- .sort()
366
- .join(", ");
367
- const msg = `Unknown tool: ${name}. Available tools: ${available}`;
369
+ // Gate tools not active for the current turn
370
+ if (context.allowedToolNames && !context.allowedToolNames.has(name)) {
371
+ const loadHint = tool.ownerSkillId
372
+ ? `Load the "${tool.ownerSkillId}" skill that provides this tool first.`
373
+ : `Load the skill that provides this tool first.`;
374
+ const msg = `Tool "${name}" is not currently active. ${loadHint}`;
368
375
  const durationMs = Date.now() - startTime;
369
376
  emitLifecycleEvent({
370
377
  type: "error",
@@ -14,6 +14,7 @@ import type {
14
14
 
15
15
  import type { InterfaceId } from "../channels/types.js";
16
16
  import type { CesClient } from "../credential-execution/client.js";
17
+ import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
17
18
  import type { SecretPromptResult } from "../permissions/secret-prompter.js";
18
19
  import type { ContentBlock } from "../providers/types.js";
19
20
  import type { TrustClass } from "../runtime/actor-trust-resolver.js";
@@ -163,6 +164,9 @@ export interface ToolExecutionResult extends PluginToolExecutionResult {
163
164
  * approval flow transparently.
164
165
  */
165
166
  cesApprovalRequired?: ApprovalRequired;
167
+ /** Structured activity metadata for client rendering (web search, web fetch, etc).
168
+ * Populated by daemon-internal tools; plugins must not set this. */
169
+ activityMetadata?: ToolActivityMetadata;
166
170
  }
167
171
 
168
172
  export type ProxyToolResolver = (
@@ -376,16 +380,51 @@ export interface Tool {
376
380
 
377
381
  /**
378
382
  * Plugin-facing tool shape. The narrow surface plugin authors implement;
379
- * differs from {@link Tool} in three ways:
383
+ * differs from {@link Tool} in four ways:
380
384
  * - Plugins declare `input_schema` as a top-level field instead of
381
385
  * implementing `getDefinition()`. The registration boundary synthesizes
382
386
  * `getDefinition()` from `{name, description, input_schema}` before the
383
387
  * tool enters the internal registry.
388
+ * - `name` is derived from the tool file's basename by the external plugin
389
+ * loader.
384
390
  * - `category` is registry-owned and stamped to `"plugin"` when the tool is
385
391
  * registered.
386
392
  * - All ownership stamps (`origin`, `ownerPluginId`, etc.) are set
387
393
  * authoritatively by the bootstrap; plugin authors leave them blank.
394
+ *
395
+ * Every author-visible field is optional. The loader fills the four
396
+ * normally-required slots (`description`, `defaultRiskLevel`,
397
+ * `input_schema`, `execute`) with documented defaults when a plugin omits
398
+ * them — see `applyPluginToolDefaults` in `external-plugin-loader.ts`.
399
+ * A nameless, body-less `export default {}` is a valid (if useless) tool;
400
+ * misconfigured tools surface at call time rather than blocking plugin
401
+ * load.
402
+ */
403
+ export type PluginTool = Omit<
404
+ Tool,
405
+ "category" | "getDefinition" | "name" | "description" | "defaultRiskLevel"
406
+ > & {
407
+ description?: string;
408
+ defaultRiskLevel?: RiskLevel;
409
+ input_schema?: object;
410
+ execute?: (
411
+ input: Record<string, unknown>,
412
+ context: ToolContext,
413
+ ) => Promise<ToolExecutionResult>;
414
+ };
415
+
416
+ /**
417
+ * Plugin tool after the external loader has derived its registry name and
418
+ * filled defaults for any author-omitted fields. All four normally-required
419
+ * slots are guaranteed present.
388
420
  */
389
- export type PluginTool = Omit<Tool, "category" | "getDefinition"> & {
421
+ export type LoadedPluginTool = PluginTool & {
422
+ name: string;
423
+ description: string;
424
+ defaultRiskLevel: RiskLevel;
390
425
  input_schema: object;
426
+ execute: (
427
+ input: Record<string, unknown>,
428
+ context: ToolContext,
429
+ ) => Promise<ToolExecutionResult>;
391
430
  };
@@ -36,7 +36,8 @@ export const uiShowTool: Tool = {
36
36
  '- list: { items: [{ id, title, subtitle?, icon?, selected? }], selectionMode: "single"|"multiple"|"none" }\n' +
37
37
  "- confirmation: { message, detail?, confirmLabel?, confirmedLabel?, cancelLabel?, destructive? }\n" +
38
38
  "- dynamic_page: { html, width?, height?, preview?: { title, subtitle?, description?, icon?, metrics?: [{ label, value }] } }\n" +
39
- "- file_upload: { prompt, acceptedTypes?, maxFiles? }\n\n" +
39
+ "- file_upload: { prompt, acceptedTypes?, maxFiles? }\n" +
40
+ "- task_preferences: {} (no data needed — categories are rendered client-side)\n\n" +
40
41
  "Proactively show a task_progress card before multi-step or long-running work (web searches, file operations, research). Show it before your first tool call, then update steps as work progresses.",
41
42
  category: "ui-surface",
42
43
  defaultRiskLevel: RiskLevel.Low,
@@ -59,6 +60,7 @@ export const uiShowTool: Tool = {
59
60
  "confirmation",
60
61
  "dynamic_page",
61
62
  "file_upload",
63
+ "task_preferences",
62
64
  ],
63
65
  description: "The type of surface to display",
64
66
  },
@@ -4,6 +4,10 @@ export interface OnboardingContext {
4
4
  tone: string;
5
5
  userName?: string;
6
6
  assistantName?: string;
7
+ priorAssistants?: string[];
7
8
  googleConnected?: boolean;
8
9
  googleScopes?: string[];
10
+ cohort?: string;
11
+ websiteUrl?: string;
12
+ contentSourceUrl?: string;
9
13
  }
@@ -0,0 +1,84 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { faviconUrlForDomain } from "../favicon.js";
4
+
5
+ describe("faviconUrlForDomain", () => {
6
+ test("returns a properly encoded s2 URL for a valid host", () => {
7
+ expect(faviconUrlForDomain("example.com")).toBe(
8
+ "https://www.google.com/s2/favicons?domain=example.com&sz=64",
9
+ );
10
+ });
11
+
12
+ test("lowercases mixed-case hosts before encoding", () => {
13
+ expect(faviconUrlForDomain("Example.COM")).toBe(
14
+ "https://www.google.com/s2/favicons?domain=example.com&sz=64",
15
+ );
16
+ });
17
+
18
+ test("returns undefined for empty input", () => {
19
+ expect(faviconUrlForDomain("")).toBeUndefined();
20
+ });
21
+
22
+ test("returns undefined for whitespace-only input", () => {
23
+ expect(faviconUrlForDomain(" ")).toBeUndefined();
24
+ });
25
+
26
+ test("returns undefined for hosts containing a slash", () => {
27
+ expect(faviconUrlForDomain("example.com/path")).toBeUndefined();
28
+ });
29
+
30
+ test("returns undefined for hosts containing a space", () => {
31
+ expect(faviconUrlForDomain("example .com")).toBeUndefined();
32
+ });
33
+
34
+ test("URL-encodes unicode/IDN hosts", () => {
35
+ // Punycode-style raw unicode host should be percent-encoded by encodeURIComponent.
36
+ const result = faviconUrlForDomain("münchen.de");
37
+ expect(result).toBe(
38
+ `https://www.google.com/s2/favicons?domain=${encodeURIComponent("münchen.de")}&sz=64`,
39
+ );
40
+ // Sanity check: the encoded form contains percent-encoded bytes for "ü".
41
+ expect(result).toContain("m%C3%BCnchen.de");
42
+ });
43
+
44
+ test("returns undefined for localhost", () => {
45
+ expect(faviconUrlForDomain("localhost")).toBeUndefined();
46
+ });
47
+
48
+ test("returns undefined for private IPv4 hosts", () => {
49
+ expect(faviconUrlForDomain("127.0.0.1")).toBeUndefined();
50
+ expect(faviconUrlForDomain("10.0.0.5")).toBeUndefined();
51
+ expect(faviconUrlForDomain("192.168.1.1")).toBeUndefined();
52
+ });
53
+
54
+ test("returns undefined for any raw IPv4 literal — private or public", () => {
55
+ // We can't tell from the host alone whether an IP belongs to a routable
56
+ // public host or an internal one, so all IP literals are rejected to avoid
57
+ // leaking the address to Google when the client renders the icon.
58
+ expect(faviconUrlForDomain("172.16.0.1")).toBeUndefined();
59
+ expect(faviconUrlForDomain("8.8.8.8")).toBeUndefined();
60
+ expect(faviconUrlForDomain("1.1.1.1")).toBeUndefined();
61
+ });
62
+
63
+ test("returns undefined for raw IPv6 literals (bracketed or bare)", () => {
64
+ expect(faviconUrlForDomain("2001:db8::1")).toBeUndefined();
65
+ expect(faviconUrlForDomain("[2001:db8::1]")).toBeUndefined();
66
+ });
67
+
68
+ test("strips :port before the private-host check (host:port → undefined for private)", () => {
69
+ expect(faviconUrlForDomain("localhost:3000")).toBeUndefined();
70
+ expect(faviconUrlForDomain("127.0.0.1:8080")).toBeUndefined();
71
+ expect(faviconUrlForDomain("192.168.1.1:443")).toBeUndefined();
72
+ });
73
+
74
+ test("strips :port and encodes only the host (public host with port)", () => {
75
+ const result = faviconUrlForDomain("example.com:8080");
76
+ expect(result).toBe(
77
+ "https://www.google.com/s2/favicons?domain=example.com&sz=64",
78
+ );
79
+ });
80
+
81
+ test("handles bracketed IPv6 hosts with ports", () => {
82
+ expect(faviconUrlForDomain("[::1]:8080")).toBeUndefined();
83
+ });
84
+ });