@vellumai/assistant 0.8.1 → 0.8.2

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 (506) hide show
  1. package/ARCHITECTURE.md +2 -7
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +5 -0
  5. package/docker-init-apt-root.sh +94 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +325 -3
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  21. package/src/__tests__/anthropic-provider.test.ts +45 -0
  22. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  23. package/src/__tests__/app-executors.test.ts +220 -4
  24. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  25. package/src/__tests__/bundled-asset.test.ts +6 -6
  26. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  27. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  28. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  29. package/src/__tests__/clawhub.test.ts +75 -16
  30. package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
  31. package/src/__tests__/config-schema.test.ts +21 -0
  32. package/src/__tests__/config-set-route.test.ts +80 -0
  33. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  34. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  35. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  36. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  37. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  38. package/src/__tests__/context-token-estimator.test.ts +1 -0
  39. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  42. package/src/__tests__/conversation-agent-loop.test.ts +2 -0
  43. package/src/__tests__/conversation-error.test.ts +42 -3
  44. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  45. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  46. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  47. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  48. package/src/__tests__/conversation-pairing.test.ts +54 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  51. package/src/__tests__/conversation-queue.test.ts +4 -1
  52. package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
  53. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  54. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  55. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  56. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  59. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  60. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  61. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  62. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  63. package/src/__tests__/dm-backfill.test.ts +121 -10
  64. package/src/__tests__/document-tool-security.test.ts +258 -0
  65. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  66. package/src/__tests__/edit-propagation.test.ts +33 -0
  67. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  68. package/src/__tests__/external-plugin-loader.test.ts +60 -36
  69. package/src/__tests__/filing-service.test.ts +140 -0
  70. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  71. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  72. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  73. package/src/__tests__/helpers/wait-for.ts +21 -0
  74. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  75. package/src/__tests__/history-repair.test.ts +73 -0
  76. package/src/__tests__/host-app-control-proxy.test.ts +266 -10
  77. package/src/__tests__/image-credentials.test.ts +1 -1
  78. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  79. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  80. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  81. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  82. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  83. package/src/__tests__/injector-chain.test.ts +10 -8
  84. package/src/__tests__/install-skill-routing.test.ts +155 -37
  85. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
  86. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  87. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  88. package/src/__tests__/llm-catalog-parity.test.ts +55 -13
  89. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
  90. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  91. package/src/__tests__/llm-usage-store.test.ts +114 -0
  92. package/src/__tests__/managed-profile-guard.test.ts +31 -29
  93. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  94. package/src/__tests__/managed-store.test.ts +84 -192
  95. package/src/__tests__/media-generate-image.test.ts +1 -1
  96. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  97. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  98. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  99. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  100. package/src/__tests__/openai-provider.test.ts +24 -0
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  102. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  103. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  104. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
  105. package/src/__tests__/platform.test.ts +2 -0
  106. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  107. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  108. package/src/__tests__/plugin-external-api.test.ts +68 -0
  109. package/src/__tests__/plugin-registry.test.ts +0 -77
  110. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  111. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  112. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  113. package/src/__tests__/plugin-types.test.ts +3 -13
  114. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  115. package/src/__tests__/process-message-display-content.test.ts +421 -0
  116. package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
  117. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  118. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
  119. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  120. package/src/__tests__/schedule-routes.test.ts +50 -3
  121. package/src/__tests__/schedule-store.test.ts +94 -0
  122. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  123. package/src/__tests__/schema-transforms.test.ts +20 -0
  124. package/src/__tests__/search-skills-unified.test.ts +0 -5
  125. package/src/__tests__/server-history-render.test.ts +43 -0
  126. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  127. package/src/__tests__/skill-load-tool.test.ts +27 -89
  128. package/src/__tests__/skill-memory.test.ts +23 -3
  129. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  130. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  131. package/src/__tests__/skills-install-extract.test.ts +49 -38
  132. package/src/__tests__/skills-install-staging.test.ts +159 -0
  133. package/src/__tests__/skills-uninstall.test.ts +9 -41
  134. package/src/__tests__/skills.test.ts +51 -58
  135. package/src/__tests__/slack-channel-config.test.ts +9 -0
  136. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  137. package/src/__tests__/system-prompt.test.ts +737 -63
  138. package/src/__tests__/terminal-tools.test.ts +28 -1
  139. package/src/__tests__/thread-backfill.test.ts +557 -27
  140. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  141. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  142. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  143. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  145. package/src/__tests__/tool-executor.test.ts +16 -4
  146. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  147. package/src/__tests__/turn-events-store.test.ts +256 -0
  148. package/src/__tests__/twilio-routes.test.ts +4 -0
  149. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  150. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  151. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  152. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  153. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  154. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  155. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  156. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  157. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  158. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  159. package/src/acp/resolve-agent.ts +1 -1
  160. package/src/agent/image-optimize.ts +13 -5
  161. package/src/calls/voice-session-bridge.ts +61 -42
  162. package/src/channels/types.ts +108 -0
  163. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  164. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  165. package/src/cli/commands/__tests__/schedules.test.ts +491 -0
  166. package/src/cli/commands/changelog.ts +106 -42
  167. package/src/cli/commands/conversations.ts +102 -17
  168. package/src/cli/commands/default-action.ts +10 -53
  169. package/src/cli/commands/notifications.ts +329 -317
  170. package/src/cli/commands/plugins.ts +185 -0
  171. package/src/cli/commands/schedules.ts +391 -0
  172. package/src/cli/commands/telemetry.ts +40 -0
  173. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  174. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  177. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  178. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  179. package/src/cli/lib/cli-colors.ts +12 -0
  180. package/src/cli/lib/confirm-prompt.ts +79 -0
  181. package/src/cli/lib/install-from-github.ts +304 -0
  182. package/src/cli/lib/list-installed-plugins.ts +137 -0
  183. package/src/cli/lib/uninstall-plugin.ts +82 -0
  184. package/src/cli/lib/unknown-command.ts +111 -0
  185. package/src/cli/program.ts +38 -2
  186. package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
  187. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  188. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  189. package/src/config/bundled-skills/document/SKILL.md +23 -3
  190. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  191. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  192. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  193. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  194. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  195. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  196. package/src/config/bundled-tool-registry.ts +6 -0
  197. package/src/config/feature-flag-registry.json +41 -1
  198. package/src/config/loader.ts +64 -38
  199. package/src/config/schema.ts +7 -10
  200. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  201. package/src/config/schemas/channels.ts +8 -0
  202. package/src/config/schemas/compaction.ts +28 -0
  203. package/src/config/schemas/heartbeat.ts +9 -0
  204. package/src/config/schemas/llm-request-logs.ts +31 -7
  205. package/src/config/schemas/llm.ts +3 -0
  206. package/src/config/schemas/memory-retrieval.ts +18 -0
  207. package/src/config/schemas/tools.ts +14 -0
  208. package/src/config/skills.ts +3 -96
  209. package/src/context/compactor.ts +1047 -0
  210. package/src/context/token-estimator.ts +2 -2
  211. package/src/context/window-manager.ts +197 -1520
  212. package/src/credential-execution/managed-catalog.ts +37 -0
  213. package/src/credential-health/credential-health-service.ts +280 -19
  214. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
  215. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  216. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  217. package/src/daemon/approval-generators.ts +8 -6
  218. package/src/daemon/config-watcher.ts +94 -31
  219. package/src/daemon/conversation-agent-loop.ts +169 -9
  220. package/src/daemon/conversation-error.ts +171 -37
  221. package/src/daemon/conversation-lifecycle.ts +53 -40
  222. package/src/daemon/conversation-messaging.ts +25 -6
  223. package/src/daemon/conversation-process.ts +49 -12
  224. package/src/daemon/conversation-runtime-assembly.ts +16 -1
  225. package/src/daemon/conversation-slash.ts +12 -5
  226. package/src/daemon/conversation-store.ts +11 -4
  227. package/src/daemon/conversation-tool-setup.ts +39 -7
  228. package/src/daemon/conversation.ts +33 -1
  229. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  230. package/src/daemon/first-greeting.ts +22 -2
  231. package/src/daemon/handlers/config-model.ts +6 -5
  232. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  233. package/src/daemon/handlers/shared.ts +14 -5
  234. package/src/daemon/handlers/skills.ts +111 -108
  235. package/src/daemon/history-repair.ts +28 -1
  236. package/src/daemon/host-app-control-proxy.ts +98 -23
  237. package/src/daemon/lifecycle.ts +45 -35
  238. package/src/daemon/meet-host-supervisor.ts +5 -4
  239. package/src/daemon/memory-v2-startup.ts +49 -0
  240. package/src/daemon/message-protocol.ts +1 -0
  241. package/src/daemon/message-types/conversations.ts +25 -0
  242. package/src/daemon/message-types/messages.ts +61 -0
  243. package/src/daemon/message-types/subagents.ts +1 -0
  244. package/src/daemon/message-types/sync.ts +1 -0
  245. package/src/daemon/pkb-reminder-builder.test.ts +1 -1
  246. package/src/daemon/pkb-reminder-builder.ts +1 -1
  247. package/src/daemon/plugin-source-watcher.ts +146 -0
  248. package/src/daemon/process-message.ts +21 -3
  249. package/src/daemon/server.ts +11 -2
  250. package/src/daemon/skill-memory-refresh.ts +29 -0
  251. package/src/documents/document-store.ts +221 -3
  252. package/src/embedded/plugin-api.ts +40 -0
  253. package/src/filing/filing-service.ts +39 -0
  254. package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
  255. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  256. package/src/heartbeat/heartbeat-service.ts +41 -0
  257. package/src/home/__tests__/feed-types.test.ts +40 -0
  258. package/src/home/feed-types.ts +22 -0
  259. package/src/home/post-connect-feed.ts +1 -0
  260. package/src/index.ts +18 -1
  261. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  262. package/src/mcp/client.ts +20 -4
  263. package/src/media/image-credentials.ts +3 -3
  264. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  265. package/src/memory/__tests__/conversation-queries.test.ts +263 -0
  266. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  267. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  268. package/src/memory/__tests__/message-content.test.ts +35 -0
  269. package/src/memory/bookmark-crud.ts +42 -10
  270. package/src/memory/context-search/sources/conversations.ts +62 -2
  271. package/src/memory/context-search/sources/workspace.ts +4 -0
  272. package/src/memory/conversation-crud.ts +63 -19
  273. package/src/memory/conversation-queries.ts +110 -10
  274. package/src/memory/db-init.ts +6 -0
  275. package/src/memory/delivery-crud.ts +152 -5
  276. package/src/memory/embedding-backend.ts +4 -4
  277. package/src/memory/external-conversation-store.ts +66 -5
  278. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
  279. package/src/memory/graph/conversation-graph-memory.ts +31 -15
  280. package/src/memory/graph/tools.ts +3 -3
  281. package/src/memory/indexer.ts +34 -29
  282. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  283. package/src/memory/jobs/embed-concept-page.ts +20 -11
  284. package/src/memory/jobs-worker.ts +6 -1
  285. package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
  286. package/src/memory/llm-request-log-source.ts +19 -52
  287. package/src/memory/llm-usage-store.ts +125 -5
  288. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  289. package/src/memory/message-content.ts +1 -1
  290. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  291. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  292. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  293. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  294. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  295. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  296. package/src/memory/migrations/index.ts +6 -0
  297. package/src/memory/migrations/registry.ts +8 -0
  298. package/src/memory/onboarding-events-store.ts +106 -0
  299. package/src/memory/schema/bookmarks.ts +0 -2
  300. package/src/memory/schema/calls.ts +1 -0
  301. package/src/memory/schema/inference.ts +1 -3
  302. package/src/memory/schema/infrastructure.ts +12 -0
  303. package/src/memory/turn-events-store.ts +127 -2
  304. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  305. package/src/memory/v2/__tests__/injection.test.ts +98 -8
  306. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  307. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  308. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  309. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  310. package/src/memory/v2/__tests__/router.test.ts +15 -0
  311. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  312. package/src/memory/v2/injection.ts +32 -6
  313. package/src/memory/v2/migration.ts +49 -19
  314. package/src/memory/v2/page-index.ts +35 -5
  315. package/src/memory/v2/prompts/router.ts +11 -8
  316. package/src/memory/v2/prompts/sweep.ts +2 -2
  317. package/src/memory/v2/qdrant.ts +135 -7
  318. package/src/memory/v2/router.ts +9 -8
  319. package/src/memory/v2/skill-store.ts +120 -35
  320. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  321. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  322. package/src/messaging/providers/slack/adapter.ts +43 -5
  323. package/src/messaging/providers/slack/client.ts +27 -0
  324. package/src/messaging/providers/slack/deep-link.ts +65 -0
  325. package/src/messaging/providers/slack/download.ts +104 -0
  326. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  327. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  328. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  329. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  330. package/src/messaging/providers/slack/types.ts +20 -1
  331. package/src/notifications/conversation-pairing.ts +2 -1
  332. package/src/notifications/decision-engine.ts +2 -1
  333. package/src/notifications/emit-signal.ts +20 -1
  334. package/src/notifications/home-feed-side-effect.ts +54 -0
  335. package/src/notifications/signal.ts +3 -1
  336. package/src/oauth/connection-resolver.ts +8 -4
  337. package/src/oauth/platform-connection.ts +6 -2
  338. package/src/oauth/seed-providers.ts +10 -1
  339. package/src/permissions/checker.ts +2 -0
  340. package/src/permissions/ipc-risk-types.ts +1 -0
  341. package/src/permissions/question-prompter.test.ts +416 -0
  342. package/src/permissions/question-prompter.ts +294 -0
  343. package/src/platform/client.test.ts +1 -1
  344. package/src/platform/client.ts +1 -1
  345. package/src/plugin-api/constants.ts +26 -0
  346. package/src/plugin-api/index.ts +34 -1
  347. package/src/plugin-api/types.ts +104 -22
  348. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  349. package/src/plugins/defaults/compaction.ts +0 -4
  350. package/src/plugins/defaults/empty-response.ts +0 -2
  351. package/src/plugins/defaults/history-repair.ts +0 -2
  352. package/src/plugins/defaults/injectors.ts +36 -3
  353. package/src/plugins/defaults/llm-call.ts +0 -2
  354. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  355. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  356. package/src/plugins/defaults/persistence.ts +0 -2
  357. package/src/plugins/defaults/title-generate.ts +0 -5
  358. package/src/plugins/defaults/token-estimate.ts +0 -2
  359. package/src/plugins/defaults/tool-error.ts +0 -7
  360. package/src/plugins/defaults/tool-execute.ts +0 -2
  361. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  362. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  363. package/src/plugins/external-api.ts +104 -0
  364. package/src/plugins/external-plugin-loader.ts +105 -32
  365. package/src/plugins/feature-gate.ts +22 -0
  366. package/src/plugins/pipeline.ts +37 -0
  367. package/src/plugins/registry.ts +48 -80
  368. package/src/plugins/types.ts +31 -26
  369. package/src/plugins/user-loader.ts +21 -2
  370. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  371. package/src/proactive-artifact/job.test.ts +37 -5
  372. package/src/prompts/__tests__/system-prompt.test.ts +12 -0
  373. package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
  374. package/src/prompts/normalize-onboarding.ts +27 -0
  375. package/src/prompts/sections.ts +302 -0
  376. package/src/prompts/system-prompt.ts +63 -166
  377. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  378. package/src/prompts/templates/system-sections.ts +173 -0
  379. package/src/providers/__tests__/inference.test.ts +22 -7
  380. package/src/providers/anthropic/client.ts +28 -28
  381. package/src/providers/connection-resolution.ts +7 -0
  382. package/src/providers/inference/adapter-factory.ts +41 -4
  383. package/src/providers/inference/connections.ts +74 -29
  384. package/src/providers/inference/resolve-auth.ts +12 -4
  385. package/src/providers/model-catalog.ts +294 -12
  386. package/src/providers/openai/chat-completions-provider.ts +10 -2
  387. package/src/providers/openrouter/client.ts +7 -0
  388. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
  389. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  390. package/src/providers/provider-availability.ts +17 -2
  391. package/src/providers/provider-catalog-visibility.ts +36 -0
  392. package/src/providers/registry.ts +22 -14
  393. package/src/providers/retry.ts +47 -1
  394. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  395. package/src/runtime/agent-wake.ts +42 -14
  396. package/src/runtime/auth/route-policy.ts +8 -1
  397. package/src/runtime/btw-sidechain.ts +2 -0
  398. package/src/runtime/http-types.ts +19 -0
  399. package/src/runtime/migrations/origin-mode.ts +1 -1
  400. package/src/runtime/pending-interactions.ts +1 -0
  401. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  402. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  403. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
  404. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  405. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  406. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  407. package/src/runtime/routes/acp-routes.ts +5 -3
  408. package/src/runtime/routes/auth-routes.ts +1 -1
  409. package/src/runtime/routes/bookmark-routes.ts +5 -3
  410. package/src/runtime/routes/btw-routes.ts +5 -1
  411. package/src/runtime/routes/channel-availability-routes.ts +121 -0
  412. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  413. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  414. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  415. package/src/runtime/routes/conversation-query-routes.ts +40 -35
  416. package/src/runtime/routes/conversation-routes.ts +90 -11
  417. package/src/runtime/routes/documents-routes.ts +25 -86
  418. package/src/runtime/routes/group-routes.ts +5 -0
  419. package/src/runtime/routes/inbound-conversation.ts +28 -8
  420. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  421. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  422. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  423. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  424. package/src/runtime/routes/index.ts +6 -0
  425. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  426. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  427. package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
  428. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  429. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  430. package/src/runtime/routes/integrations/twilio.ts +6 -13
  431. package/src/runtime/routes/notification-routes.ts +1 -1
  432. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  433. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  434. package/src/runtime/routes/question-routes.ts +259 -0
  435. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  436. package/src/runtime/routes/schedule-routes.ts +4 -7
  437. package/src/runtime/routes/subagents-routes.ts +57 -18
  438. package/src/runtime/routes/telemetry-routes.ts +27 -0
  439. package/src/runtime/routes/tts-routes.ts +27 -2
  440. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  441. package/src/runtime/routes/workspace-routes.ts +28 -0
  442. package/src/runtime/services/conversation-serializer.ts +39 -7
  443. package/src/runtime/sync/resource-sync-events.ts +93 -1
  444. package/src/schedule/schedule-store.ts +27 -2
  445. package/src/schedule/scheduler.ts +9 -1
  446. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  447. package/src/security/untrusted-content.ts +93 -8
  448. package/src/skills/catalog-files.ts +1 -1
  449. package/src/skills/catalog-install.ts +233 -116
  450. package/src/skills/clawhub.ts +70 -13
  451. package/src/skills/managed-store.ts +4 -119
  452. package/src/skills/skillssh-registry.ts +27 -48
  453. package/src/subagent/manager.ts +15 -7
  454. package/src/telemetry/types.ts +113 -1
  455. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  456. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  457. package/src/tools/apps/executors.ts +58 -7
  458. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  459. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  460. package/src/tools/browser/browser-execution.ts +15 -11
  461. package/src/tools/computer-use/definitions.ts +3 -3
  462. package/src/tools/credentials/vault.ts +1 -1
  463. package/src/tools/document/document-tool.ts +124 -1
  464. package/src/tools/filesystem/edit.ts +1 -1
  465. package/src/tools/filesystem/list.ts +1 -1
  466. package/src/tools/filesystem/read.ts +1 -1
  467. package/src/tools/filesystem/write.ts +5 -2
  468. package/src/tools/host-filesystem/transfer.ts +1 -1
  469. package/src/tools/host-terminal/host-shell.ts +1 -1
  470. package/src/tools/permission-checker.ts +1 -1
  471. package/src/tools/registry.ts +17 -7
  472. package/src/tools/schedule/create.ts +2 -2
  473. package/src/tools/schema-transforms.ts +7 -2
  474. package/src/tools/side-effects.ts +1 -0
  475. package/src/tools/skills/delete-managed.ts +4 -4
  476. package/src/tools/skills/execute.ts +1 -1
  477. package/src/tools/skills/scaffold-managed.ts +3 -2
  478. package/src/tools/subagent/notify-parent.ts +1 -1
  479. package/src/tools/system/request-permission.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +60 -1
  481. package/src/tools/tool-manifest.ts +2 -0
  482. package/src/tools/types.ts +72 -21
  483. package/src/tools/ui-surface/definitions.ts +6 -5
  484. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  485. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  486. package/src/types/onboarding-context.ts +2 -0
  487. package/src/util/errors.ts +17 -0
  488. package/src/util/platform.ts +10 -0
  489. package/src/watcher/__tests__/engine.test.ts +22 -0
  490. package/src/watcher/engine.ts +6 -2
  491. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  492. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  493. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  494. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  495. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  496. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  497. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  498. package/src/workspace/migrations/registry.ts +8 -0
  499. package/src/workspace/migrations/runner.ts +39 -9
  500. package/src/workspace/migrations/types.ts +4 -0
  501. package/examples/plugins/echo/bun.lock +0 -25
  502. package/src/__tests__/context-window-manager.test.ts +0 -2481
  503. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  504. package/src/context/prompts/compact.md +0 -26
  505. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  506. /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
@@ -29,15 +29,22 @@
29
29
  * through untouched).
30
30
  * 6. Creates `<workspaceDir>/plugins-data/<plugin>/` on demand for per-plugin
31
31
  * writable state and exposes it via {@link PluginInitContext.pluginStorageDir}.
32
- * 7. Awaits `plugin.init(ctx)` sequentially. One init failure surfaces as a
32
+ * 7. For each surviving plugin, registers its contributed tools, routes,
33
+ * and skills into their global registries via
34
+ * {@link registerPluginTools}, {@link registerSkillRoute}, and
35
+ * {@link registerPluginSkills}. Contributions land BEFORE `init()` so
36
+ * the plugin's hook can observe a registry where its own model-visible
37
+ * surface is already wired — useful for plugins that want to attach
38
+ * metadata, warm caches, or otherwise interact with their own
39
+ * contributions during initialization.
40
+ * 8. Awaits `plugin.init(ctx)` sequentially. An init failure surfaces as a
33
41
  * {@link PluginExecutionError} naming the offending plugin and aborts
34
42
  * bootstrap — later plugins' `init()` never runs and the daemon fails
35
- * startup cleanly rather than coming up in a half-wired state.
36
- * 8. After a plugin's `init()` succeeds, registers any tools declared on
37
- * `plugin.tools` with the global tool registry via
38
- * {@link registerPluginTools}. Tool contributions land after `init()` so
39
- * a plugin that fails mid-init never leaves partial tool registrations
40
- * behind.
43
+ * startup cleanly rather than coming up in a half-wired state. The
44
+ * failing plugin's already-registered tools, routes, and skills are
45
+ * rolled back before the error propagates so the registry never
46
+ * carries state contributed by a plugin that never finished
47
+ * initializing.
41
48
  *
42
49
  * A single shutdown hook is registered via
43
50
  * {@link registerShutdownHook} that walks the plugin list in **reverse
@@ -54,21 +61,24 @@ import { mkdirSync } from "node:fs";
54
61
  import { join } from "node:path";
55
62
 
56
63
  import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
64
+ import { getConfig } from "../config/loader.js";
57
65
  import type { AssistantConfig } from "../config/schema.js";
66
+ import { HOOKS } from "../plugin-api/constants.js";
58
67
  import { registerDefaultPlugins } from "../plugins/defaults/index.js";
68
+ import { buildExternalPlugin } from "../plugins/external-plugin-loader.js";
59
69
  import {
60
70
  registerPluginSkills,
61
71
  unregisterPluginSkills,
62
72
  } from "../plugins/plugin-skill-contributions.js";
63
73
  import {
64
- ASSISTANT_API_VERSIONS,
74
+ getRegisteredPlugin,
65
75
  getRegisteredPlugins,
76
+ setRegisteredPlugin,
66
77
  unregisterPlugin,
67
78
  } from "../plugins/registry.js";
68
79
  import {
69
80
  type Plugin,
70
81
  PluginExecutionError,
71
- type PluginInitContext,
72
82
  type PluginShutdownContext,
73
83
  type PluginSkillRegistration,
74
84
  } from "../plugins/types.js";
@@ -84,6 +94,7 @@ import {
84
94
  } from "../tools/registry.js";
85
95
  import { getLogger } from "../util/logger.js";
86
96
  import { getWorkspaceDir } from "../util/platform.js";
97
+ import { APP_VERSION } from "../version.js";
87
98
  import { registerShutdownHook } from "./shutdown-registry.js";
88
99
 
89
100
  const log = getLogger("plugins-bootstrap");
@@ -170,6 +181,16 @@ function ensurePluginStorageDir(pluginName: string): string {
170
181
  return dir;
171
182
  }
172
183
 
184
+ function getDisabledPluginFlag(
185
+ plugin: Plugin,
186
+ config: AssistantConfig,
187
+ ): string | undefined {
188
+ for (const flagKey of plugin.manifest.requiresFlag ?? []) {
189
+ if (!isAssistantFeatureFlagEnabled(flagKey, config)) return flagKey;
190
+ }
191
+ return undefined;
192
+ }
193
+
173
194
  /**
174
195
  * Run every registered plugin's `init()` hook sequentially and install a
175
196
  * reverse-order shutdown hook. See the module docstring for full semantics.
@@ -245,187 +266,20 @@ export async function bootstrapPlugins(ctx: DaemonContext): Promise<void> {
245
266
 
246
267
  for (const plugin of plugins) {
247
268
  const name = plugin.manifest.name;
248
-
249
- // Feature-flag gating — if any key in `manifest.requiresFlag` is
250
- // disabled, skip this plugin entirely. Skipping means: no `init()`, no
251
- // tool / route / skill contributions, and no shutdown hook entry. A
252
- // later boot with the flag flipped ON picks up the plugin cleanly.
253
- const requiredFlags = plugin.manifest.requiresFlag ?? [];
254
- let disabledFlag: string | undefined;
255
- for (const flagKey of requiredFlags) {
256
- if (!isAssistantFeatureFlagEnabled(flagKey, ctx.config)) {
257
- disabledFlag = flagKey;
258
- break;
259
- }
260
- }
269
+ const disabledFlag = getDisabledPluginFlag(plugin, ctx.config);
261
270
  if (disabledFlag !== undefined) {
262
271
  log.info(
263
272
  { plugin: name, flag: disabledFlag },
264
273
  `skipping plugin ${name}: feature flag ${disabledFlag} is disabled`,
265
274
  );
266
- // Drop the plugin from the registry too. `registerPlugin()` added it at
267
- // import time, and `getMiddlewaresFor()` / `getInjectors()` iterate over
268
- // every registered entry — without this call, the gated plugin's
269
- // middleware and injectors would still participate in every pipeline
270
- // run and system-prompt assembly despite `init()` never firing.
271
275
  unregisterPlugin(name);
272
276
  continue;
273
277
  }
274
278
 
275
- // Collected as routes are accepted so the catch block can revoke exactly
276
- // the routes this plugin contributed if a later contribution step throws.
277
- // Hoisted above the try so it's in scope for the error path.
278
- const routeHandles: SkillRouteHandle[] = [];
279
-
280
- // Tracks whether `plugin.init()` ran to completion (or the plugin has no
281
- // init at all). The catch block consults this to decide whether the
282
- // currently-failing plugin's `onShutdown()` may run: onShutdown is paired
283
- // with init, so a plugin that never completed init never set up the state
284
- // onShutdown is meant to tear down. Calling onShutdown in that case would
285
- // surprise plugin authors (their teardown runs against an uninitialized
286
- // self) and breaks the documented lifecycle contract.
287
- let initCompleted = false;
288
-
289
279
  try {
290
- // Credential resolution — gather every entry in `requiresCredential`
291
- // before calling `init()` so the plugin receives a fully-populated map.
292
- const credentials: Record<string, string> = {};
293
- const required = plugin.manifest.requiresCredential ?? [];
294
- for (const key of required) {
295
- credentials[key] = await resolveCredentialOrThrow(name, key);
296
- }
297
-
298
- // Per-plugin config block, validated against the manifest's parser-like
299
- // validator when one is declared.
300
- const rawConfig = getPluginConfigRaw(ctx.config, name);
301
- const config = validatePluginConfig(
302
- name,
303
- plugin.manifest.config,
304
- rawConfig,
305
- );
306
-
307
- // Per-plugin writable data directory. Created lazily during bootstrap
308
- // rather than at registration time so the side effect is isolated to
309
- // the boot path.
310
- const pluginStorageDir = ensurePluginStorageDir(name);
311
-
312
- const initContext: PluginInitContext = {
313
- config,
314
- credentials,
315
- logger: log.child({ plugin: name }),
316
- pluginStorageDir,
317
- assistantVersion: ctx.assistantVersion,
318
- apiVersions: ASSISTANT_API_VERSIONS,
319
- };
320
-
321
- if (plugin.hooks?.init) {
322
- try {
323
- await plugin.hooks.init(initContext);
324
- } catch (err) {
325
- throw new PluginExecutionError(
326
- `plugin ${name} init() failed: ${
327
- err instanceof Error ? err.message : String(err)
328
- }`,
329
- name,
330
- { cause: err },
331
- );
332
- }
333
- }
334
- // Reached when init() succeeded or the plugin has no init hook. The
335
- // catch block reads this to decide whether onShutdown may run.
336
- initCompleted = true;
337
-
338
- // After init succeeds, wire in the plugin's model-visible capabilities.
339
- // Tool contributions (PR 31) register only after `init()` succeeds so a
340
- // plugin that fails mid-init never leaves partially-wired tools behind.
341
- // Tool registration failures are wrapped in a PluginExecutionError so
342
- // the offending plugin name surfaces in the abort — matching the
343
- // strict-fail semantics of `init()` errors.
344
- if (plugin.tools && plugin.tools.length > 0) {
345
- try {
346
- const accepted = registerPluginTools(name, plugin.tools);
347
- log.info(
348
- { plugin: name, count: accepted.length },
349
- "plugin tools registered",
350
- );
351
- } catch (err) {
352
- throw new PluginExecutionError(
353
- `plugin ${name} tool registration failed: ${
354
- err instanceof Error ? err.message : String(err)
355
- }`,
356
- name,
357
- { cause: err },
358
- );
359
- }
360
- }
361
-
362
- // Route contributions (PR 32) — registered after init() succeeds so a
363
- // plugin that fails to initialize never exposes a half-wired HTTP
364
- // surface. Mirrors the skill-route registry shape; see
365
- // {@link PluginRouteRegistration}. Retain every returned handle so the
366
- // teardown path unregisters by identity rather than pattern text — two
367
- // plugins (or a plugin and a skill) that happen to register the same
368
- // regex must not evict each other's routes during shutdown.
369
- if (plugin.routes && plugin.routes.length > 0) {
370
- for (const route of plugin.routes) {
371
- routeHandles.push(registerSkillRoute(route));
372
- }
373
- log.info(
374
- { plugin: name, count: plugin.routes.length },
375
- "plugin routes registered",
376
- );
377
- }
378
-
379
- // Skills register into the in-memory plugin-skill catalog so
380
- // `skill_load` / `skill_execute` can resolve them alongside filesystem
381
- // skills. A registration failure aborts bootstrap with the plugin named
382
- // — same strict-fail posture as init() throws.
383
- if (plugin.skills && plugin.skills.length > 0) {
384
- try {
385
- // `plugin.skills` is typed as `PluginSkillRegistration[]` at the
386
- // Plugin interface — the type assertion here is a narrowing from
387
- // that generic slot into the concrete shape the registry expects.
388
- registerPluginSkills(
389
- name,
390
- plugin.skills as readonly PluginSkillRegistration[],
391
- );
392
- } catch (err) {
393
- throw new PluginExecutionError(
394
- `plugin ${name} skill registration failed: ${
395
- err instanceof Error ? err.message : String(err)
396
- }`,
397
- name,
398
- { cause: err },
399
- );
400
- }
401
- }
402
-
403
- activePlugins.push({ plugin, routeHandles });
404
-
405
- log.info({ plugin: name }, "plugin initialized");
280
+ activePlugins.push(await initializePlugin(plugin, ctx));
406
281
  } catch (err) {
407
- // Roll back the currently-failing plugin first — it is not in
408
- // `activePlugins` yet (that push happens only after every contribution
409
- // step succeeds), so `teardownPartialInit()` alone would leave its
410
- // already-registered tools, routes, and skills live.
411
- //
412
- // Branching on `initCompleted` keeps the init/onShutdown pairing
413
- // intact. When init succeeded but a later contribution step (tools,
414
- // routes, skills) threw, the plugin has live init-side state that
415
- // `onShutdown()` is responsible for cleaning up, so the full
416
- // rollbackPlugin() path runs. When init itself failed (or a step
417
- // before init — credential resolution, config validation — threw),
418
- // onShutdown must not run: calling it would invoke the plugin's
419
- // teardown against an uninitialized self, violating the lifecycle
420
- // contract documented on `Plugin.onShutdown`. In the init-failed case
421
- // there is also nothing to unregister — tools, routes, and skills are
422
- // all registered after init — so just drop the plugin from the
423
- // registry (idempotent if already removed).
424
- if (initCompleted) {
425
- await rollbackPlugin({ plugin, routeHandles });
426
- } else {
427
- unregisterPlugin(name);
428
- }
282
+ unregisterPlugin(name);
429
283
  await teardownPartialInit();
430
284
  throw err;
431
285
  }
@@ -470,6 +324,114 @@ interface ActivePlugin {
470
324
  readonly routeHandles: readonly SkillRouteHandle[];
471
325
  }
472
326
 
327
+ async function initializePlugin(
328
+ plugin: Plugin,
329
+ ctx: DaemonContext,
330
+ ): Promise<ActivePlugin> {
331
+ const name = plugin.manifest.name;
332
+ const routeHandles: SkillRouteHandle[] = [];
333
+ let initCompleted = false;
334
+
335
+ try {
336
+ const credentials: Record<string, string> = {};
337
+ for (const key of plugin.manifest.requiresCredential ?? []) {
338
+ credentials[key] = await resolveCredentialOrThrow(name, key);
339
+ }
340
+
341
+ const config = validatePluginConfig(
342
+ name,
343
+ plugin.manifest.config,
344
+ getPluginConfigRaw(ctx.config, name),
345
+ );
346
+
347
+ const initContext = {
348
+ config,
349
+ credentials,
350
+ logger: log.child({ plugin: name }),
351
+ pluginStorageDir: ensurePluginStorageDir(name),
352
+ assistantVersion: ctx.assistantVersion,
353
+ };
354
+
355
+ if (plugin.tools && plugin.tools.length > 0) {
356
+ try {
357
+ const accepted = registerPluginTools(name, plugin.tools);
358
+ log.info(
359
+ { plugin: name, count: accepted.length },
360
+ "plugin tools registered",
361
+ );
362
+ } catch (err) {
363
+ throw new PluginExecutionError(
364
+ `plugin ${name} tool registration failed: ${
365
+ err instanceof Error ? err.message : String(err)
366
+ }`,
367
+ name,
368
+ { cause: err },
369
+ );
370
+ }
371
+ }
372
+
373
+ if (plugin.routes && plugin.routes.length > 0) {
374
+ for (const route of plugin.routes) {
375
+ routeHandles.push(registerSkillRoute(route));
376
+ }
377
+ log.info(
378
+ { plugin: name, count: plugin.routes.length },
379
+ "plugin routes registered",
380
+ );
381
+ }
382
+
383
+ if (plugin.skills && plugin.skills.length > 0) {
384
+ try {
385
+ registerPluginSkills(
386
+ name,
387
+ plugin.skills as readonly PluginSkillRegistration[],
388
+ );
389
+ } catch (err) {
390
+ throw new PluginExecutionError(
391
+ `plugin ${name} skill registration failed: ${
392
+ err instanceof Error ? err.message : String(err)
393
+ }`,
394
+ name,
395
+ { cause: err },
396
+ );
397
+ }
398
+ }
399
+
400
+ if (plugin.hooks?.[HOOKS.INIT]) {
401
+ try {
402
+ await plugin.hooks[HOOKS.INIT](initContext);
403
+ } catch (err) {
404
+ throw new PluginExecutionError(
405
+ `plugin ${name} init() failed: ${
406
+ err instanceof Error ? err.message : String(err)
407
+ }`,
408
+ name,
409
+ { cause: err },
410
+ );
411
+ }
412
+ }
413
+ initCompleted = true;
414
+
415
+ log.info({ plugin: name }, "plugin initialized");
416
+ return { plugin, routeHandles };
417
+ } catch (err) {
418
+ if (initCompleted) {
419
+ await teardownPlugin({ plugin, routeHandles }, "bootstrap-failed", {
420
+ assistantVersion: ctx.assistantVersion,
421
+ });
422
+ } else {
423
+ for (const handle of routeHandles) {
424
+ unregisterSkillRoute(handle);
425
+ }
426
+ unregisterPluginTools(name);
427
+ if (plugin.skills && plugin.skills.length > 0) {
428
+ unregisterPluginSkills(name);
429
+ }
430
+ }
431
+ throw err;
432
+ }
433
+ }
434
+
473
435
  /**
474
436
  * Tear down a single fully-initialized plugin: unregister routes, unregister
475
437
  * tools, invoke `onShutdown()` if present, then unregister skills. Every step
@@ -514,9 +476,9 @@ async function teardownPlugin(
514
476
  );
515
477
  }
516
478
 
517
- if (plugin.hooks?.shutdown) {
479
+ if (plugin.hooks?.[HOOKS.SHUTDOWN]) {
518
480
  try {
519
- await plugin.hooks.shutdown(shutdownContext);
481
+ await plugin.hooks[HOOKS.SHUTDOWN](shutdownContext);
520
482
  } catch (err) {
521
483
  // Swallow — we want every plugin's shutdown to get a chance to run
522
484
  // even when an earlier one throws. The outer runShutdownHooks already
@@ -540,3 +502,77 @@ async function teardownPlugin(
540
502
  }
541
503
  }
542
504
  }
505
+
506
+ /** Rebuild a changed external plugin and swap it into the live registry. */
507
+ export async function reregisterExternalPlugin(
508
+ pluginName: string,
509
+ ): Promise<void> {
510
+ const pluginDir = join(getWorkspaceDir(), "plugins", pluginName);
511
+ const plugin = await buildExternalPlugin(pluginDir);
512
+ if (plugin === undefined) return;
513
+
514
+ if (plugin.manifest.name !== pluginName) {
515
+ log.warn(
516
+ { plugin: pluginName, manifestName: plugin.manifest.name, pluginDir },
517
+ `external plugin reload skipped: directory name "${pluginName}" does not match manifest.name "${plugin.manifest.name}"`,
518
+ );
519
+ return;
520
+ }
521
+
522
+ const ctx: DaemonContext = {
523
+ config: getConfig(),
524
+ assistantVersion: APP_VERSION,
525
+ };
526
+ const disabledFlag = getDisabledPluginFlag(plugin, ctx.config);
527
+ if (disabledFlag !== undefined) {
528
+ log.info(
529
+ { plugin: pluginName, flag: disabledFlag },
530
+ `external plugin reload skipped: feature flag ${disabledFlag} is disabled`,
531
+ );
532
+ return;
533
+ }
534
+
535
+ const existing = getRegisteredPlugin(pluginName);
536
+ if (existing === undefined) {
537
+ try {
538
+ await initializePlugin(plugin, ctx);
539
+ setRegisteredPlugin(plugin);
540
+ log.info({ plugin: pluginName }, "external plugin registered post-boot");
541
+ } catch (err) {
542
+ log.error(
543
+ { err, plugin: pluginName },
544
+ "external plugin post-boot registration failed",
545
+ );
546
+ }
547
+ return;
548
+ }
549
+
550
+ try {
551
+ unregisterPluginTools(pluginName);
552
+ } catch (err) {
553
+ log.warn(
554
+ { err, plugin: pluginName },
555
+ "external plugin reload: tool unregister failed (continuing)",
556
+ );
557
+ }
558
+
559
+ setRegisteredPlugin(plugin);
560
+
561
+ if (plugin.tools && plugin.tools.length > 0) {
562
+ try {
563
+ const accepted = registerPluginTools(pluginName, plugin.tools);
564
+ log.info(
565
+ { plugin: pluginName, count: accepted.length },
566
+ "external plugin reloaded",
567
+ );
568
+ } catch (err) {
569
+ log.error(
570
+ { err, plugin: pluginName },
571
+ "external plugin reload: tool registration failed",
572
+ );
573
+ }
574
+ return;
575
+ }
576
+
577
+ log.info({ plugin: pluginName }, "external plugin reloaded");
578
+ }
@@ -11,6 +11,7 @@ export interface OnboardingGreetingContext {
11
11
  tone: string;
12
12
  userName?: string;
13
13
  assistantName?: string;
14
+ googleConnected?: boolean;
14
15
  }
15
16
 
16
17
  export const CANNED_FIRST_GREETING = [
@@ -76,6 +77,16 @@ const TONE_INVITE: Record<Tone, string> = {
76
77
  "We can start with whatever's in front of you, or just talk for a bit first. Either way.",
77
78
  };
78
79
 
80
+ const TONE_GOOGLE_SCAN: Record<Tone, string> = {
81
+ grounded:
82
+ "I can scan your email, calendar, and drive in the background while we talk — just say the word.",
83
+ warm: "Also — I can scan your email, calendar, and drive in the background while we chat, if you'd like. Just let me know.",
84
+ energetic:
85
+ "Oh, and I can scan your email, calendar, and drive in the background right now — want me to?",
86
+ poetic:
87
+ "I can also look through your email, calendar, and drive quietly in the background — say the word.",
88
+ };
89
+
79
90
  function buildInvite(tone: Tone = "grounded"): string {
80
91
  return TONE_INVITE[tone];
81
92
  }
@@ -96,11 +107,20 @@ function buildPersonalizedGreeting(ctx: OnboardingGreetingContext): string {
96
107
  const assistant = ctx.assistantName?.trim();
97
108
  const tone = resolveTone(ctx.tone);
98
109
 
99
- if (!name && !assistant && !VALID_TONES.has(ctx.tone)) {
110
+ if (
111
+ !name &&
112
+ !assistant &&
113
+ !VALID_TONES.has(ctx.tone) &&
114
+ !ctx.googleConnected
115
+ ) {
100
116
  return CANNED_FIRST_GREETING;
101
117
  }
102
118
 
103
119
  const intro = buildIntroLine(name, assistant, tone);
104
120
  const invite = buildInvite(tone);
105
- return [intro, "", invite].join("\n");
121
+ const parts = [intro, "", invite];
122
+ if (ctx.googleConnected) {
123
+ parts.push("", TONE_GOOGLE_SCAN[tone]);
124
+ }
125
+ return parts.join("\n");
106
126
  }
@@ -7,8 +7,8 @@ import {
7
7
  import { setServiceField } from "../../config/raw-config-utils.js";
8
8
  import { providerForImageModelPrefix } from "../../media/types.js";
9
9
  import type { ProviderCatalogEntry } from "../../providers/model-catalog.js";
10
- import { PROVIDER_CATALOG } from "../../providers/model-catalog.js";
11
10
  import { getConfiguredProviders } from "../../providers/provider-availability.js";
11
+ import { getVisibleProviderCatalog } from "../../providers/provider-catalog-visibility.js";
12
12
  import { CONFIG_RELOAD_DEBOUNCE_MS, log } from "./shared.js";
13
13
 
14
14
  // ---------------------------------------------------------------------------
@@ -65,15 +65,16 @@ export async function getModelInfo(): Promise<ModelInfo> {
65
65
  const config = getConfig();
66
66
  const resolved = resolveCallSiteConfig("mainAgent", config.llm);
67
67
  const provider = resolved.provider;
68
+ const visibleCatalog = getVisibleProviderCatalog(config);
68
69
 
69
70
  return {
70
71
  model: resolved.model,
71
72
  provider,
72
73
  configuredProviders: await getConfiguredProviders(),
73
- availableModels: PROVIDER_CATALOG.find(
74
- (p) => p.id === provider,
75
- )?.models?.map((m) => ({ id: m.id, displayName: m.displayName })),
76
- allProviders: PROVIDER_CATALOG.map(projectProviderForWire),
74
+ availableModels: visibleCatalog
75
+ .find((p) => p.id === provider)
76
+ ?.models?.map((m) => ({ id: m.id, displayName: m.displayName })),
77
+ allProviders: visibleCatalog.map(projectProviderForWire),
77
78
  };
78
79
  }
79
80
 
@@ -34,6 +34,7 @@ export interface SlackChannelConfigResult {
34
34
  connected: boolean;
35
35
  teamId?: string;
36
36
  teamName?: string;
37
+ teamUrl?: string;
37
38
  botUserId?: string;
38
39
  botUsername?: string;
39
40
  error?: string;
@@ -93,7 +94,8 @@ export function backfillSlackInjectionTemplates(): void {
93
94
  // -- Business logic --
94
95
 
95
96
  export async function getSlackChannelConfig(): Promise<SlackChannelConfigResult> {
96
- const { teamId, teamName, botUserId, botUsername } = getConfig().slack;
97
+ const { teamId, teamName, teamUrl, botUserId, botUsername } =
98
+ getConfig().slack;
97
99
  const accountInfo = teamName
98
100
  ? `${teamName}${botUsername ? ` (@${botUsername})` : ""}`
99
101
  : undefined;
@@ -129,6 +131,7 @@ export async function getSlackChannelConfig(): Promise<SlackChannelConfigResult>
129
131
  connected,
130
132
  ...(teamId ? { teamId } : {}),
131
133
  ...(teamName ? { teamName } : {}),
134
+ ...(teamUrl ? { teamUrl } : {}),
132
135
  ...(botUserId ? { botUserId } : {}),
133
136
  ...(botUsername ? { botUsername } : {}),
134
137
  };
@@ -169,6 +172,7 @@ export async function setSlackChannelConfig(
169
172
  let metadata: {
170
173
  teamId?: string;
171
174
  teamName?: string;
175
+ teamUrl?: string;
172
176
  botUserId?: string;
173
177
  botUsername?: string;
174
178
  } = {};
@@ -187,6 +191,7 @@ export async function setSlackChannelConfig(
187
191
  error?: string;
188
192
  team_id?: string;
189
193
  team?: string;
194
+ url?: string;
190
195
  user_id?: string;
191
196
  user?: string;
192
197
  };
@@ -198,6 +203,7 @@ export async function setSlackChannelConfig(
198
203
  metadata = {
199
204
  teamId: data.team_id,
200
205
  teamName: data.team,
206
+ teamUrl: data.url,
201
207
  botUserId: data.user_id,
202
208
  botUsername: data.user,
203
209
  };
@@ -221,6 +227,7 @@ export async function setSlackChannelConfig(
221
227
  const raw = loadRawConfig();
222
228
  setNestedValue(raw, "slack.teamId", metadata.teamId ?? "");
223
229
  setNestedValue(raw, "slack.teamName", metadata.teamName ?? "");
230
+ setNestedValue(raw, "slack.teamUrl", metadata.teamUrl ?? "");
224
231
  setNestedValue(raw, "slack.botUserId", metadata.botUserId ?? "");
225
232
  setNestedValue(raw, "slack.botUsername", metadata.botUsername ?? "");
226
233
  saveRawConfig(raw);
@@ -293,10 +300,12 @@ export async function setSlackChannelConfig(
293
300
  }
294
301
  } else {
295
302
  // Use existing metadata from config if no new bot token provided
296
- const { teamId, teamName, botUserId, botUsername } = getConfig().slack;
303
+ const { teamId, teamName, teamUrl, botUserId, botUsername } =
304
+ getConfig().slack;
297
305
  metadata = {
298
306
  ...(teamId ? { teamId } : {}),
299
307
  ...(teamName ? { teamName } : {}),
308
+ ...(teamUrl ? { teamUrl } : {}),
300
309
  ...(botUserId ? { botUserId } : {}),
301
310
  ...(botUsername ? { botUsername } : {}),
302
311
  };
@@ -468,7 +477,8 @@ export async function clearSlackUserToken(): Promise<SlackChannelConfigResult> {
468
477
  credentialKey("slack_channel", "app_token"),
469
478
  ));
470
479
  const conn = getConnectionByProvider("slack_channel");
471
- const { teamId, teamName, botUserId, botUsername } = getConfig().slack;
480
+ const { teamId, teamName, teamUrl, botUserId, botUsername } =
481
+ getConfig().slack;
472
482
 
473
483
  return {
474
484
  success: true,
@@ -479,6 +489,7 @@ export async function clearSlackUserToken(): Promise<SlackChannelConfigResult> {
479
489
  !!(conn && conn.status === "active") && hasBotToken && hasAppToken,
480
490
  ...(teamId ? { teamId } : {}),
481
491
  ...(teamName ? { teamName } : {}),
492
+ ...(teamUrl ? { teamUrl } : {}),
482
493
  ...(botUserId ? { botUserId } : {}),
483
494
  ...(botUsername ? { botUsername } : {}),
484
495
  };
@@ -528,6 +539,7 @@ export async function clearSlackChannelConfig(): Promise<SlackChannelConfigResul
528
539
  const raw = loadRawConfig();
529
540
  setNestedValue(raw, "slack.teamId", "");
530
541
  setNestedValue(raw, "slack.teamName", "");
542
+ setNestedValue(raw, "slack.teamUrl", "");
531
543
  setNestedValue(raw, "slack.botUserId", "");
532
544
  setNestedValue(raw, "slack.botUsername", "");
533
545
  saveRawConfig(raw);