@vellumai/assistant 0.8.5 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (544) hide show
  1. package/AGENTS.md +33 -1
  2. package/ARCHITECTURE.md +1 -1
  3. package/bunfig.toml +6 -1
  4. package/docs/credential-execution-service.md +6 -6
  5. package/docs/plugins.md +4 -3
  6. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
  7. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
  8. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
  9. package/openapi.yaml +1900 -166
  10. package/package.json +1 -1
  11. package/src/__tests__/actor-token-service.test.ts +3 -2
  12. package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
  13. package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
  14. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  15. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  16. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  17. package/src/__tests__/annotate-risk-options.test.ts +1 -0
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/approval-routes-http.test.ts +9 -13
  20. package/src/__tests__/assert-not-live-db.ts +79 -0
  21. package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
  22. package/src/__tests__/audit-log-rotation.test.ts +2 -2
  23. package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
  24. package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
  25. package/src/__tests__/browser-skill-endstate.test.ts +3 -3
  26. package/src/__tests__/btw-routes.test.ts +3 -2
  27. package/src/__tests__/call-controller.test.ts +3 -2
  28. package/src/__tests__/channel-approval-routes.test.ts +3 -2
  29. package/src/__tests__/channel-guardian.test.ts +3 -2
  30. package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
  31. package/src/__tests__/channel-reply-delivery.test.ts +35 -0
  32. package/src/__tests__/channel-retry-sweep.test.ts +320 -3
  33. package/src/__tests__/checker.test.ts +12 -12
  34. package/src/__tests__/compaction-events.test.ts +1 -0
  35. package/src/__tests__/compaction-trail-store.test.ts +264 -0
  36. package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
  37. package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
  38. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
  39. package/src/__tests__/computer-use-tools.test.ts +12 -14
  40. package/src/__tests__/config-loader-backfill.test.ts +13 -28
  41. package/src/__tests__/config-loader-corrupt.test.ts +5 -5
  42. package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
  43. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
  44. package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
  45. package/src/__tests__/config-schema.test.ts +10 -10
  46. package/src/__tests__/connection-model-compat.test.ts +83 -0
  47. package/src/__tests__/contacts-tools.test.ts +3 -2
  48. package/src/__tests__/context-token-estimator.test.ts +22 -0
  49. package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
  50. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
  52. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  53. package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
  54. package/src/__tests__/conversation-agent-loop.test.ts +488 -2
  55. package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
  56. package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
  57. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
  58. package/src/__tests__/conversation-attention-store.test.ts +101 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  61. package/src/__tests__/conversation-error.test.ts +30 -0
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -8
  63. package/src/__tests__/conversation-fork-route.test.ts +3 -2
  64. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  65. package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
  66. package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
  67. package/src/__tests__/conversation-lifecycle.test.ts +1 -0
  68. package/src/__tests__/conversation-list-source.test.ts +3 -2
  69. package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
  70. package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
  71. package/src/__tests__/conversation-pairing.test.ts +53 -0
  72. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
  73. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  74. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
  75. package/src/__tests__/conversation-queue.test.ts +333 -291
  76. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
  77. package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
  78. package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
  79. package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
  80. package/src/__tests__/conversation-skill-tools.test.ts +38 -142
  81. package/src/__tests__/conversation-slash-queue.test.ts +84 -32
  82. package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
  83. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
  85. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
  90. package/src/__tests__/conversation-sync-tags.test.ts +128 -12
  91. package/src/__tests__/conversation-title-service.test.ts +1 -0
  92. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
  93. package/src/__tests__/conversation-usage.test.ts +1 -0
  94. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  95. package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
  96. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
  97. package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
  98. package/src/__tests__/credential-broker-server-use.test.ts +5 -5
  99. package/src/__tests__/credential-execution-client.test.ts +72 -1
  100. package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
  101. package/src/__tests__/credential-health-service.test.ts +252 -3
  102. package/src/__tests__/credential-security-invariants.test.ts +5 -5
  103. package/src/__tests__/credential-vault-unit.test.ts +19 -19
  104. package/src/__tests__/credential-vault.test.ts +5 -5
  105. package/src/__tests__/cross-provider-web-search.test.ts +56 -2
  106. package/src/__tests__/db-connection-isolation.test.ts +7 -6
  107. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
  108. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
  109. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
  110. package/src/__tests__/db-test-helpers.ts +58 -0
  111. package/src/__tests__/disk-pressure-guard.test.ts +58 -41
  112. package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
  113. package/src/__tests__/disk-pressure-routes.test.ts +0 -33
  114. package/src/__tests__/disk-pressure-tools.test.ts +0 -4
  115. package/src/__tests__/dm-persistence.test.ts +26 -40
  116. package/src/__tests__/document-create-dedupe.test.ts +189 -0
  117. package/src/__tests__/document-find-replace.test.ts +3 -2
  118. package/src/__tests__/document-tool-security.test.ts +81 -2
  119. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
  120. package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
  121. package/src/__tests__/encrypted-store.test.ts +11 -9
  122. package/src/__tests__/feature-flag-test-helpers.ts +53 -0
  123. package/src/__tests__/filing-service.test.ts +1 -0
  124. package/src/__tests__/first-greeting.test.ts +62 -12
  125. package/src/__tests__/gateway-flag-listener.test.ts +0 -1
  126. package/src/__tests__/gemini-provider.test.ts +26 -0
  127. package/src/__tests__/guardian-action-sweep.test.ts +3 -2
  128. package/src/__tests__/guardian-outbound-http.test.ts +3 -2
  129. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
  130. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
  131. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  132. package/src/__tests__/heartbeat-service.test.ts +1 -0
  133. package/src/__tests__/helpers/mock-logger.ts +26 -0
  134. package/src/__tests__/host-bash-routes.test.ts +1 -0
  135. package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
  136. package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
  137. package/src/__tests__/host-shell-tool.test.ts +5 -4
  138. package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
  139. package/src/__tests__/http-conversation-lineage.test.ts +3 -2
  140. package/src/__tests__/http-user-message-parity.test.ts +29 -7
  141. package/src/__tests__/identity-intro-cache.test.ts +133 -22
  142. package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
  143. package/src/__tests__/inference-profile-reaper.test.ts +3 -2
  144. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
  145. package/src/__tests__/injector-disk-pressure.test.ts +3 -17
  146. package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
  147. package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
  148. package/src/__tests__/llm-context-normalization.test.ts +42 -0
  149. package/src/__tests__/llm-resolver.test.ts +331 -0
  150. package/src/__tests__/llm-schema.test.ts +1 -1
  151. package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
  152. package/src/__tests__/mcp-abort-signal.test.ts +14 -0
  153. package/src/__tests__/mcp-client-auth.test.ts +14 -0
  154. package/src/__tests__/messaging-send-tool.test.ts +1 -0
  155. package/src/__tests__/migration-import-from-url.test.ts +3 -3
  156. package/src/__tests__/mock-gateway-ipc.ts +18 -2
  157. package/src/__tests__/model-intents.test.ts +3 -3
  158. package/src/__tests__/native-web-search.test.ts +30 -2
  159. package/src/__tests__/notification-deep-link.test.ts +62 -0
  160. package/src/__tests__/oauth-commands-routes.test.ts +37 -0
  161. package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
  162. package/src/__tests__/oauth-store.test.ts +3 -2
  163. package/src/__tests__/onboarding-template-contract.test.ts +3 -2
  164. package/src/__tests__/openai-provider.test.ts +8 -9
  165. package/src/__tests__/openai-responses-provider.test.ts +70 -10
  166. package/src/__tests__/openrouter-provider-only.test.ts +27 -5
  167. package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
  168. package/src/__tests__/persistence-pipeline.test.ts +139 -1
  169. package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
  170. package/src/__tests__/plugin-bootstrap.test.ts +9 -11
  171. package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
  172. package/src/__tests__/process-message-background-slack.test.ts +21 -16
  173. package/src/__tests__/process-message-display-content.test.ts +19 -22
  174. package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
  175. package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
  176. package/src/__tests__/provider-registry-ollama.test.ts +45 -22
  177. package/src/__tests__/recording-handler.test.ts +1 -0
  178. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  179. package/src/__tests__/registry.test.ts +82 -76
  180. package/src/__tests__/relay-server.test.ts +10 -10
  181. package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
  182. package/src/__tests__/schedule-store.test.ts +16 -1
  183. package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
  184. package/src/__tests__/secret-ingress-http.test.ts +5 -1
  185. package/src/__tests__/secure-keys.test.ts +3 -3
  186. package/src/__tests__/send-endpoint-busy.test.ts +81 -42
  187. package/src/__tests__/server-history-render.test.ts +4 -1
  188. package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
  189. package/src/__tests__/skill-feature-flags.test.ts +14 -16
  190. package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
  191. package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
  192. package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
  193. package/src/__tests__/skill-tool-factory.test.ts +96 -95
  194. package/src/__tests__/slack-channel-config.test.ts +3 -3
  195. package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
  196. package/src/__tests__/subagent-disposal.test.ts +27 -8
  197. package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
  198. package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
  199. package/src/__tests__/subagent-manager-notify.test.ts +20 -8
  200. package/src/__tests__/subagent-notify-parent.test.ts +5 -4
  201. package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
  202. package/src/__tests__/subagent-tools.test.ts +2 -1
  203. package/src/__tests__/suggestion-routes.test.ts +1 -0
  204. package/src/__tests__/system-prompt.test.ts +38 -0
  205. package/src/__tests__/test-preload-verifier.ts +68 -0
  206. package/src/__tests__/test-preload.ts +32 -39
  207. package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
  208. package/src/__tests__/tool-executor.test.ts +55 -10
  209. package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
  210. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  211. package/src/__tests__/twilio-routes.test.ts +3 -2
  212. package/src/__tests__/validate-input.test.ts +381 -0
  213. package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
  214. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
  215. package/src/__tests__/voice-session-bridge.test.ts +37 -28
  216. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
  217. package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
  218. package/src/acp/session-manager.ts +5 -6
  219. package/src/agent/loop.ts +80 -0
  220. package/src/api/README.md +124 -2
  221. package/src/api/constants/call-sites.ts +27 -0
  222. package/src/api/events/assistant-outbound-attachment.ts +51 -0
  223. package/src/api/events/assistant-text-delta.ts +32 -0
  224. package/src/api/events/assistant-turn-start.ts +33 -0
  225. package/src/api/events/document-comment-created.ts +48 -0
  226. package/src/api/events/document-comment-deleted.ts +24 -0
  227. package/src/api/events/document-comment-reopened.ts +25 -0
  228. package/src/api/events/document-comment-resolved.ts +27 -0
  229. package/src/api/events/generation-cancelled.ts +24 -0
  230. package/src/api/events/generation-handoff.ts +41 -0
  231. package/src/api/events/message-complete.ts +42 -0
  232. package/src/api/events/open-url.ts +30 -0
  233. package/src/{events → api/events}/relationship-state-updated.ts +3 -3
  234. package/src/api/events/tool-use-start.ts +32 -0
  235. package/src/api/index.ts +128 -3
  236. package/src/api/responses/llm-context-response.ts +39 -0
  237. package/src/api/responses/llm-request-log-entry.ts +93 -0
  238. package/src/api/responses/memory-recall-log.ts +65 -0
  239. package/src/api/responses/memory-v2-activation-log.ts +78 -0
  240. package/src/background-wake/background-wake-routes.test.ts +687 -52
  241. package/src/background-wake/platform-client.test.ts +308 -0
  242. package/src/background-wake/platform-client.ts +167 -0
  243. package/src/background-wake/publisher.ts +91 -0
  244. package/src/background-wake/runtime-registry.ts +2 -2
  245. package/src/background-wake/wake-intent-hooks.test.ts +282 -0
  246. package/src/calls/guardian-dispatch.ts +1 -0
  247. package/src/calls/voice-session-bridge.ts +4 -4
  248. package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
  249. package/src/cli/commands/__tests__/notifications.test.ts +184 -40
  250. package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
  251. package/src/cli/commands/channels/index.ts +229 -0
  252. package/src/cli/commands/memory-v3-render.ts +147 -0
  253. package/src/cli/commands/memory-v3.ts +255 -4
  254. package/src/cli/commands/notifications.ts +365 -55
  255. package/src/cli/lib/open-browser.ts +7 -2
  256. package/src/cli/program.ts +2 -0
  257. package/src/config/assistant-feature-flags.ts +23 -42
  258. package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
  259. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  260. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  261. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
  262. package/src/config/call-site-defaults.ts +1 -1
  263. package/src/config/feature-flag-cache.ts +86 -0
  264. package/src/config/feature-flag-registry.json +17 -17
  265. package/src/config/llm-context-resolution.ts +10 -1
  266. package/src/config/llm-resolver.ts +121 -15
  267. package/src/config/loader.ts +4 -5
  268. package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
  269. package/src/config/schemas/heartbeat.ts +1 -1
  270. package/src/config/schemas/llm.ts +90 -1
  271. package/src/config/schemas/memory-v2.ts +26 -0
  272. package/src/config/schemas/services.ts +6 -2
  273. package/src/config/seed-inference-profiles.ts +36 -16
  274. package/src/context/token-estimator.ts +10 -5
  275. package/src/credential-execution/executable-discovery.ts +40 -0
  276. package/src/credential-execution/process-manager.ts +6 -2
  277. package/src/credential-health/credential-health-service.ts +125 -40
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
  279. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
  280. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
  281. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
  282. package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
  283. package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
  284. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
  285. package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
  286. package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
  287. package/src/daemon/conversation-agent-loop.ts +132 -28
  288. package/src/daemon/conversation-error.ts +33 -5
  289. package/src/daemon/conversation-messaging.ts +84 -43
  290. package/src/daemon/conversation-process.ts +74 -37
  291. package/src/daemon/conversation-runtime-assembly.ts +29 -9
  292. package/src/daemon/conversation-skill-tools.ts +14 -30
  293. package/src/daemon/conversation-surfaces.ts +69 -34
  294. package/src/daemon/conversation-tool-setup.ts +33 -48
  295. package/src/daemon/conversation.ts +26 -46
  296. package/src/daemon/daemon-control.ts +1 -1
  297. package/src/daemon/daemon-skill-host.ts +9 -2
  298. package/src/daemon/disk-pressure-guard.ts +27 -29
  299. package/src/daemon/first-greeting.ts +31 -13
  300. package/src/daemon/handlers/shared.ts +6 -1
  301. package/src/daemon/lifecycle.ts +12 -12
  302. package/src/daemon/mcp-reload-service.ts +1 -1
  303. package/src/daemon/meet-manifest-loader.ts +10 -17
  304. package/src/daemon/message-types/conversations.ts +20 -22
  305. package/src/daemon/message-types/document-comments.ts +8 -44
  306. package/src/daemon/message-types/home.ts +2 -2
  307. package/src/daemon/message-types/integrations.ts +2 -7
  308. package/src/daemon/message-types/messages.ts +23 -38
  309. package/src/daemon/message-types/subagents.ts +6 -0
  310. package/src/daemon/process-message.ts +9 -9
  311. package/src/daemon/providers-setup.ts +1 -1
  312. package/src/daemon/server.ts +16 -0
  313. package/src/daemon/switch-inference-profile-tool.ts +13 -3
  314. package/src/daemon/tool-setup-types.ts +0 -6
  315. package/src/daemon/wake-target-adapter.ts +10 -0
  316. package/src/documents/document-store.ts +38 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +1 -0
  318. package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
  319. package/src/heartbeat/heartbeat-service.ts +63 -0
  320. package/src/home/__tests__/feed-writer.test.ts +161 -0
  321. package/src/home/__tests__/post-connect-feed.test.ts +1 -0
  322. package/src/home/__tests__/suggested-prompts.test.ts +55 -59
  323. package/src/home/feed-writer.ts +146 -7
  324. package/src/home/suggested-prompts.ts +27 -145
  325. package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
  326. package/src/ipc/gateway-client.test.ts +4 -1
  327. package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
  328. package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
  329. package/src/ipc/skill-routes/memory.ts +4 -3
  330. package/src/ipc/skill-routes/registries.ts +28 -29
  331. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
  332. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
  333. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
  334. package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
  335. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
  336. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
  337. package/src/memory/conversation-attention-store.ts +17 -3
  338. package/src/memory/conversation-crud.ts +352 -112
  339. package/src/memory/db-connection.ts +29 -19
  340. package/src/memory/db-init.ts +4 -0
  341. package/src/memory/db-singleton.ts +77 -0
  342. package/src/memory/delivery-channels.ts +82 -0
  343. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
  344. package/src/memory/graph/retriever.test.ts +3 -3
  345. package/src/memory/job-handlers/embedding.test.ts +3 -2
  346. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
  347. package/src/memory/jobs-worker.ts +12 -1
  348. package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
  349. package/src/memory/llm-request-log-source-local.ts +24 -0
  350. package/src/memory/llm-request-log-source.ts +31 -0
  351. package/src/memory/llm-request-log-store.ts +188 -3
  352. package/src/memory/memory-v2-activation-log-store.ts +95 -1
  353. package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
  354. package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
  355. package/src/memory/migrations/index.ts +2 -0
  356. package/src/memory/schema/conversations.ts +9 -1
  357. package/src/memory/schema/inference.ts +0 -1
  358. package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
  359. package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
  360. package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
  361. package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
  362. package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
  363. package/src/memory/v2/harness/metrics.ts +5 -1
  364. package/src/memory/v2/harness/replay-input.ts +19 -3
  365. package/src/memory/v2/harness/runner.ts +6 -0
  366. package/src/memory/v2/harness/trace.ts +6 -0
  367. package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
  368. package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
  369. package/src/memory/v3/__tests__/edges.test.ts +144 -1
  370. package/src/memory/v3/__tests__/filter.test.ts +48 -0
  371. package/src/memory/v3/__tests__/gate.test.ts +96 -33
  372. package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
  373. package/src/memory/v3/__tests__/loop.test.ts +250 -5
  374. package/src/memory/v3/__tests__/scouts.test.ts +49 -0
  375. package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
  376. package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
  377. package/src/memory/v3/__tests__/traversal.test.ts +39 -0
  378. package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
  379. package/src/memory/v3/__tests__/validate.test.ts +32 -0
  380. package/src/memory/v3/coretrieval-seed.ts +240 -0
  381. package/src/memory/v3/edges.ts +58 -21
  382. package/src/memory/v3/filter.ts +27 -22
  383. package/src/memory/v3/gate.ts +51 -36
  384. package/src/memory/v3/index-composition.ts +18 -5
  385. package/src/memory/v3/loop.ts +65 -17
  386. package/src/memory/v3/scouts.ts +15 -4
  387. package/src/memory/v3/shadow-diff.ts +287 -0
  388. package/src/memory/v3/shadow-middleware.ts +44 -2
  389. package/src/memory/v3/traversal.ts +6 -1
  390. package/src/memory/v3/tree-walk.ts +6 -1
  391. package/src/memory/v3/validate.ts +56 -33
  392. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  393. package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
  394. package/src/notifications/adapters/slack.ts +45 -11
  395. package/src/notifications/broadcaster.ts +114 -63
  396. package/src/notifications/conversation-pairing.ts +23 -3
  397. package/src/notifications/decisions-store.ts +32 -1
  398. package/src/notifications/deliveries-store.ts +45 -0
  399. package/src/notifications/edit-notification.ts +201 -0
  400. package/src/notifications/emit-signal.ts +11 -1
  401. package/src/notifications/signal.ts +10 -0
  402. package/src/notifications/types.ts +37 -0
  403. package/src/oauth/byo-connection.test.ts +67 -3
  404. package/src/oauth/byo-connection.ts +32 -5
  405. package/src/oauth/connect-orchestrator.ts +9 -0
  406. package/src/oauth/connection-resolver.test.ts +76 -0
  407. package/src/oauth/connection-resolver.ts +49 -10
  408. package/src/oauth/manual-token-connection.ts +51 -3
  409. package/src/oauth/seed-providers.ts +3 -0
  410. package/src/permissions/approval-policy.test.ts +19 -5
  411. package/src/permissions/approval-policy.ts +14 -3
  412. package/src/permissions/checker.ts +21 -8
  413. package/src/platform/client.test.ts +24 -1
  414. package/src/platform/client.ts +8 -0
  415. package/src/platform/feature-gate.ts +15 -0
  416. package/src/plugins/defaults/injectors.ts +2 -8
  417. package/src/plugins/defaults/persistence.ts +25 -6
  418. package/src/plugins/types.ts +57 -13
  419. package/src/proactive-artifact/job.test.ts +1 -0
  420. package/src/prompts/__tests__/system-prompt.test.ts +4 -4
  421. package/src/prompts/system-prompt.ts +38 -40
  422. package/src/prompts/template-detection.ts +10 -4
  423. package/src/prompts/templates/BOOTSTRAP.md +7 -11
  424. package/src/prompts/templates/IDENTITY.md +0 -2
  425. package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
  426. package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
  427. package/src/providers/call-site-routing.ts +33 -9
  428. package/src/providers/connection-model-compat.ts +23 -0
  429. package/src/providers/connection-resolution.ts +39 -20
  430. package/src/providers/fireworks/client.ts +1 -0
  431. package/src/providers/gemini/client.ts +24 -3
  432. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
  433. package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
  434. package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
  435. package/src/providers/inference/auth.ts +0 -8
  436. package/src/providers/inference/connections.ts +3 -66
  437. package/src/providers/inference/resolve-auth.ts +2 -3
  438. package/src/providers/model-catalog.ts +35 -1
  439. package/src/providers/model-intents.ts +3 -3
  440. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  441. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
  442. package/src/providers/openai/chat-completions-provider.ts +110 -12
  443. package/src/providers/openai/codex-models.ts +2 -0
  444. package/src/providers/openai/responses-provider.ts +53 -53
  445. package/src/providers/openrouter/client.ts +13 -8
  446. package/src/providers/provider-send-message.ts +18 -9
  447. package/src/providers/registry.ts +48 -8
  448. package/src/providers/retry.ts +16 -4
  449. package/src/providers/search-provider-catalog.ts +17 -9
  450. package/src/providers/types.ts +9 -0
  451. package/src/runtime/__tests__/agent-wake.test.ts +1 -0
  452. package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
  453. package/src/runtime/access-request-helper.ts +1 -0
  454. package/src/runtime/auth/route-policy.ts +10 -0
  455. package/src/runtime/channel-readiness-service.ts +68 -0
  456. package/src/runtime/channel-reply-delivery.ts +23 -0
  457. package/src/runtime/channel-retry-sweep.ts +47 -14
  458. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  459. package/src/runtime/migrations/vbundle-builder.ts +3 -2
  460. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
  461. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
  462. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  463. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  464. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
  465. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
  466. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
  467. package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
  468. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
  469. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
  470. package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
  471. package/src/runtime/routes/acp-routes-list.test.ts +3 -0
  472. package/src/runtime/routes/app-management-routes.ts +111 -4
  473. package/src/runtime/routes/background-wake-routes.ts +188 -20
  474. package/src/runtime/routes/btw-routes.ts +4 -4
  475. package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
  476. package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
  477. package/src/runtime/routes/conversation-list-routes.ts +147 -0
  478. package/src/runtime/routes/conversation-management-routes.ts +39 -14
  479. package/src/runtime/routes/conversation-query-routes.ts +60 -10
  480. package/src/runtime/routes/conversation-routes.ts +186 -140
  481. package/src/runtime/routes/conversations-import-routes.ts +19 -6
  482. package/src/runtime/routes/documents-routes.ts +10 -1
  483. package/src/runtime/routes/group-routes.ts +11 -0
  484. package/src/runtime/routes/home-feed-routes.ts +129 -0
  485. package/src/runtime/routes/identity-intro-cache.ts +61 -16
  486. package/src/runtime/routes/identity-routes.ts +30 -9
  487. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
  488. package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
  489. package/src/runtime/routes/index.ts +2 -0
  490. package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
  491. package/src/runtime/routes/integrations/vercel.ts +15 -0
  492. package/src/runtime/routes/llm-context-normalization.ts +7 -2
  493. package/src/runtime/routes/memory-v3-routes.ts +160 -2
  494. package/src/runtime/routes/migration-routes.ts +20 -13
  495. package/src/runtime/routes/notification-routes.ts +63 -1
  496. package/src/runtime/routes/oauth-commands-routes.ts +6 -1
  497. package/src/runtime/routes/surface-action-routes.ts +1 -38
  498. package/src/runtime/routes/surface-content-routes.ts +12 -5
  499. package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
  500. package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
  501. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
  502. package/src/runtime/slack-dm-text-delivery.ts +177 -0
  503. package/src/runtime/sync/resource-sync-events.ts +1 -1
  504. package/src/runtime/tool-grant-request-helper.ts +1 -0
  505. package/src/schedule/schedule-store.ts +8 -1
  506. package/src/schedule/scheduler.ts +111 -15
  507. package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
  508. package/src/security/encrypted-store.ts +7 -16
  509. package/src/security/store-path-override.ts +61 -0
  510. package/src/signals/user-message.ts +5 -8
  511. package/src/skills/validate-input.ts +177 -0
  512. package/src/subagent/manager.ts +13 -13
  513. package/src/subagent/types.ts +6 -0
  514. package/src/tasks/tool-sanitizer.ts +2 -2
  515. package/src/tools/apps/definitions.ts +35 -21
  516. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
  517. package/src/tools/computer-use/definitions.ts +268 -266
  518. package/src/tools/document/document-tool.ts +131 -8
  519. package/src/tools/execution-target.ts +2 -5
  520. package/src/tools/executor.ts +18 -55
  521. package/src/tools/host-filesystem/edit.test.ts +1 -0
  522. package/src/tools/host-filesystem/read.test.ts +1 -0
  523. package/src/tools/host-filesystem/transfer.test.ts +31 -6
  524. package/src/tools/host-filesystem/write.test.ts +1 -0
  525. package/src/tools/mcp/mcp-tool-factory.ts +0 -2
  526. package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
  527. package/src/tools/network/__tests__/web-search.test.ts +211 -3
  528. package/src/tools/network/managed-search-proxy.ts +183 -0
  529. package/src/tools/network/web-search.ts +199 -44
  530. package/src/tools/policy-context.ts +3 -1
  531. package/src/tools/registry.ts +146 -103
  532. package/src/tools/schedule/create.ts +1 -1
  533. package/src/tools/skills/skill-tool-factory.ts +17 -36
  534. package/src/tools/subagent/spawn.ts +3 -0
  535. package/src/tools/tool-approval-handler.ts +10 -4
  536. package/src/tools/tool-name-aliases.ts +72 -14
  537. package/src/tools/types.ts +17 -15
  538. package/src/tools/ui-surface/definitions.ts +98 -86
  539. package/src/types/onboarding-context.ts +6 -0
  540. package/src/usage/attribution.ts +32 -1
  541. package/src/util/browser.ts +7 -2
  542. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
  543. package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
  544. package/src/workspace/migrations/registry.ts +4 -0
@@ -2,87 +2,25 @@
2
2
  * Suggested prompt producer for the Home feed.
3
3
  *
4
4
  * Returns an array of `SuggestedPrompt` items shown at the top of the
5
- * Home page as conversation starters (e.g. "Connect Gmail", "Add Slack").
6
- *
7
- * Two sources of prompts:
8
- * - **Deterministic** derived from missing OAuth connections.
9
- * Computed inline (read-only, safe for GET).
10
- * - **Assistant-generated** — contextual suggestions from the LLM
11
- * based on what's relevant to the user. Read from an in-memory
12
- * cache in the GET path; generation runs in the background via
13
- * `refreshAssistantSuggestedPrompts`.
5
+ * Home page as conversation starters. All prompts are generated by the
6
+ * assistant based on the user's connected services and context —
7
+ * read from an in-memory cache in the GET path; generation runs in
8
+ * the background via `refreshAssistantSuggestedPrompts`.
14
9
  */
15
10
 
16
11
  import { resolveCallSiteConfig } from "../config/llm-resolver.js";
17
12
  import { getConfig } from "../config/loader.js";
18
- import { listProviders } from "../oauth/oauth-store.js";
19
13
  import { buildSystemPrompt } from "../prompts/system-prompt.js";
20
14
  import { getConfiguredProvider } from "../providers/provider-send-message.js";
21
15
  import { buildAssistantEvent } from "../runtime/assistant-event.js";
22
16
  import { assistantEventHub } from "../runtime/assistant-event-hub.js";
23
17
  import { runBtwSidechain } from "../runtime/btw-sidechain.js";
24
- import { isOAuthProviderConnected } from "../schedule/integration-status.js";
18
+ import { formatIntegrationSummary } from "../schedule/integration-status.js";
25
19
  import { getLogger } from "../util/logger.js";
26
20
  import type { SuggestedPrompt } from "./feed-types.js";
27
21
 
28
22
  const log = getLogger("suggested-prompts");
29
23
 
30
- /**
31
- * Map of provider keys to their suggested-prompt metadata. Only providers
32
- * listed here produce deterministic "Connect X" prompts when disconnected.
33
- * The icon values are VIcon case names rendered by the macOS client.
34
- */
35
- interface PromptEntry {
36
- label: string;
37
- prompt: string;
38
- icon: string;
39
- }
40
-
41
- const CONNECT_PROMPT_META: Record<
42
- string,
43
- PromptEntry & { connectedPrompts?: PromptEntry[] }
44
- > = {
45
- google: {
46
- label: "Connect Gmail",
47
- prompt: "Help me connect my Gmail account",
48
- icon: "mail",
49
- connectedPrompts: [
50
- {
51
- label: "Triage my inbox",
52
- prompt:
53
- "Help me triage my inbox — summarize what's unread and flag anything that needs a reply",
54
- icon: "mail",
55
- },
56
- {
57
- label: "Summarize today's emails",
58
- prompt:
59
- "Summarize the emails I received today and highlight anything important",
60
- icon: "mail",
61
- },
62
- ],
63
- },
64
- slack: {
65
- label: "Connect Slack",
66
- prompt: "Help me connect my Slack workspace",
67
- icon: "hash",
68
- },
69
- notion: {
70
- label: "Connect Notion",
71
- prompt: "Help me connect my Notion workspace",
72
- icon: "fileText",
73
- },
74
- linear: {
75
- label: "Connect Linear",
76
- prompt: "Help me connect my Linear workspace",
77
- icon: "clipboardList",
78
- },
79
- github: {
80
- label: "Connect GitHub",
81
- prompt: "Help me connect my GitHub account",
82
- icon: "terminal",
83
- },
84
- };
85
-
86
24
  const LLM_SUGGESTIONS_TIMEOUT_MS = 5_000;
87
25
  const LLM_CACHE_TTL_MS = 30 * 60 * 1000; // 30 minutes
88
26
 
@@ -94,37 +32,23 @@ let cachedLLMPrompts: SuggestedPrompt[] = [];
94
32
  let cachedLLMPromptsAt = 0;
95
33
 
96
34
  /**
97
- * Produce suggested prompts from both deterministic and cached LLM sources.
98
- * Deterministic prompts always come first; cached LLM-generated prompts are
99
- * appended when available. No LLM calls happen in this path — safe for GET.
35
+ * Return cached assistant-generated prompts. No LLM calls happen in
36
+ * this path safe for GET. Returns an empty array until the first
37
+ * background refresh populates the cache.
100
38
  */
101
39
  export async function getSuggestedPrompts(): Promise<SuggestedPrompt[]> {
102
- const prompts: SuggestedPrompt[] = [];
103
-
104
- let deterministicPrompts: SuggestedPrompt[] = [];
105
- try {
106
- deterministicPrompts = await getDeterministicPrompts();
107
- prompts.push(...deterministicPrompts);
108
- } catch (err) {
109
- log.warn({ err }, "Failed to compute deterministic suggested prompts");
110
- }
111
-
112
40
  if (Date.now() - cachedLLMPromptsAt < LLM_CACHE_TTL_MS) {
113
- prompts.push(...cachedLLMPrompts);
41
+ return [...cachedLLMPrompts];
114
42
  }
115
-
116
- return prompts;
43
+ return [];
117
44
  }
118
45
 
119
46
  /**
120
- * Drops the in-memory LLM suggestion cache so the next call to
121
- * `getSuggestedPrompts()` returns only the fresh deterministic list (and
122
- * a follow-up background refresh repopulates the LLM half).
47
+ * Drops the in-memory suggestion cache so the next background refresh
48
+ * regenerates prompts with current integration state.
123
49
  *
124
- * Called from OAuth connect/disconnect paths so a freshly-connected
125
- * provider stops surfacing as a "Connect X" pill within one reload — the
126
- * 30-minute TTL would otherwise pin a stale suggestion until the next
127
- * periodic refresh.
50
+ * Called from OAuth connect/disconnect paths so suggestions reflect the
51
+ * new state within one reload instead of waiting for the 30-minute TTL.
128
52
  */
129
53
  export function invalidateAssistantSuggestedPromptsCache(): void {
130
54
  cachedLLMPrompts = [];
@@ -156,8 +80,7 @@ export async function refreshAssistantSuggestedPrompts(): Promise<void> {
156
80
  }
157
81
 
158
82
  try {
159
- const deterministicPrompts = await getDeterministicPrompts();
160
- const llmPrompts = await generateAssistantPrompts(deterministicPrompts);
83
+ const llmPrompts = await generateAssistantPrompts();
161
84
  cachedLLMPrompts = llmPrompts;
162
85
  cachedLLMPromptsAt = Date.now();
163
86
  } catch (err) {
@@ -165,49 +88,6 @@ export async function refreshAssistantSuggestedPrompts(): Promise<void> {
165
88
  }
166
89
  }
167
90
 
168
- /**
169
- * Check which well-known OAuth providers are not connected and return
170
- * a "Connect X" prompt for each. For connected providers that have
171
- * `connectedPrompts`, return those instead so users discover ongoing
172
- * management capabilities.
173
- */
174
- async function getDeterministicPrompts(): Promise<SuggestedPrompt[]> {
175
- const providers = listProviders();
176
- const prompts: SuggestedPrompt[] = [];
177
-
178
- for (const provider of providers) {
179
- const meta = CONNECT_PROMPT_META[provider.provider];
180
- if (!meta) continue;
181
-
182
- const connected = await isOAuthProviderConnected(provider.provider);
183
-
184
- if (!connected) {
185
- prompts.push({
186
- id: `connect-${provider.provider}`,
187
- label: meta.label,
188
- icon: meta.icon,
189
- prompt: meta.prompt,
190
- source: "deterministic",
191
- });
192
- continue;
193
- }
194
-
195
- if (meta.connectedPrompts) {
196
- for (const cp of meta.connectedPrompts) {
197
- prompts.push({
198
- id: `manage-${provider.provider}-${cp.label.toLowerCase().replace(/\s+/g, "-")}`,
199
- label: cp.label,
200
- icon: cp.icon,
201
- prompt: cp.prompt,
202
- source: "deterministic",
203
- });
204
- }
205
- }
206
- }
207
-
208
- return prompts;
209
- }
210
-
211
91
  // ---------------------------------------------------------------------------
212
92
  // LLM-generated suggestions
213
93
  // ---------------------------------------------------------------------------
@@ -220,11 +100,9 @@ interface LLMSuggestion {
220
100
  /**
221
101
  * Ask the LLM to generate contextual conversation-starter suggestions
222
102
  * based on the assistant's persona and the user's connected services.
223
- * Returns an empty array on failure so deterministic prompts still show.
103
+ * Returns an empty array on failure.
224
104
  */
225
- async function generateAssistantPrompts(
226
- deterministicPrompts: SuggestedPrompt[],
227
- ): Promise<SuggestedPrompt[]> {
105
+ async function generateAssistantPrompts(): Promise<SuggestedPrompt[]> {
228
106
  const config = getConfig();
229
107
  const resolved = resolveCallSiteConfig("homeSuggestedPrompts", config.llm);
230
108
 
@@ -238,17 +116,21 @@ async function generateAssistantPrompts(
238
116
  excludeCustomPrefix: true,
239
117
  });
240
118
 
241
- const existingLabels = deterministicPrompts.map((p) => p.label).join(", ");
242
- const contextNote = existingLabels
243
- ? `The user already has these suggestions: ${existingLabels}. Do NOT duplicate them.`
244
- : "";
119
+ let integrationContext = "";
120
+ try {
121
+ integrationContext = `\nConnected integrations: ${await formatIntegrationSummary()}`;
122
+ } catch {
123
+ // Best-effort — continue without integration info
124
+ }
245
125
 
246
126
  const result = await runBtwSidechain({
247
127
  content:
248
128
  "Suggest 2-3 short, actionable conversation starters for the home page. " +
249
129
  "Each should be something specific and helpful you can do for the user right now. " +
250
- `${contextNote} ` +
251
- 'Return ONLY a JSON array of objects with "label" (max 5 words) and "prompt" (the full message to send). ' +
130
+ "Focus on things the user's connected services enable — don't suggest connecting services they already have. " +
131
+ "You may suggest connecting a service only if it's not yet connected and would be genuinely useful." +
132
+ integrationContext +
133
+ ' Return ONLY a JSON array of objects with "label" (max 5 words) and "prompt" (the full message to send). ' +
252
134
  "No markdown fences, no explanation.",
253
135
  provider,
254
136
  systemPrompt,
@@ -37,6 +37,7 @@ mock.module("../../runtime/agent-wake.js", () => ({
37
37
 
38
38
  mock.module("../../memory/conversation-crud.js", () => ({
39
39
  getConversation: (id: string) => ({ id, createdAt: Date.now() }),
40
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
40
41
  }));
41
42
 
42
43
  // ---------------------------------------------------------------------------
@@ -108,7 +108,10 @@ describe("ipcGetFeatureFlags", () => {
108
108
  });
109
109
 
110
110
  test("returns empty record when IPC returns undefined", async () => {
111
- // No mock configured ipcCall returns undefined
111
+ // Explicitly suppress the mock's default `get_feature_flags` sentinel
112
+ // (which exists to short-circuit `initFeatureFlagOverrides()`'s retry
113
+ // loop) so this test exercises the underlying-undefined path.
114
+ mockGatewayIpc(null, { results: { get_feature_flags: undefined } });
112
115
  const flags = await ipcGetFeatureFlags();
113
116
  expect(flags).toEqual({});
114
117
  });
@@ -24,6 +24,7 @@ const addMessageSpy = mock(
24
24
  );
25
25
  mock.module("../../../memory/conversation-crud.js", () => ({
26
26
  addMessage: addMessageSpy,
27
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
27
28
  }));
28
29
 
29
30
  const wakeAgentSpy = mock(
@@ -19,6 +19,7 @@ import {
19
19
  __clearExternalToolProvidersForTesting,
20
20
  __clearRegistryForTesting,
21
21
  getTool,
22
+ getToolOwner,
22
23
  } from "../../../tools/registry.js";
23
24
  import {
24
25
  __getActiveSessionCountForTesting,
@@ -55,7 +56,12 @@ afterEach(() => {
55
56
 
56
57
  describe("host.registries.register_tools", () => {
57
58
  test("installs proxy tools into the daemon's external tool registry", async () => {
59
+ // `skillId` lives at the top of the params object (one frame = one
60
+ // skill's batch). The per-tool manifest schema no longer carries an
61
+ // `owner` field — ownership flows in through this top-level key and
62
+ // is recorded in the registry's `ownersByName` map.
58
63
  const result = (await registerToolsRoute.handler({
64
+ skillId: "demo-skill",
59
65
  tools: [
60
66
  {
61
67
  name: "skill_demo_tool",
@@ -64,7 +70,6 @@ describe("host.registries.register_tools", () => {
64
70
  defaultRiskLevel: "low",
65
71
  category: "skill",
66
72
  executionTarget: "sandbox",
67
- ownerSkillId: "demo-skill",
68
73
  },
69
74
  ],
70
75
  })) as { registered: string[] };
@@ -72,13 +77,17 @@ describe("host.registries.register_tools", () => {
72
77
  expect(result.registered).toEqual(["skill_demo_tool"]);
73
78
  const installed = getTool("skill_demo_tool");
74
79
  expect(installed).toBeDefined();
75
- expect(installed!.origin).toBe("skill");
76
- expect(installed!.ownerSkillId).toBe("demo-skill");
77
- expect(installed!.executionMode).toBe("proxy");
80
+ // Ownership lives on the registry — getToolOwner is the single source of
81
+ // truth. The Tool object itself no longer carries the kind.
82
+ expect(getToolOwner("skill_demo_tool")).toEqual({
83
+ kind: "skill",
84
+ id: "demo-skill",
85
+ });
78
86
  });
79
87
 
80
88
  test("proxy execute throws when no supervisor is attached", async () => {
81
89
  await registerToolsRoute.handler({
90
+ skillId: "stub-skill",
82
91
  tools: [
83
92
  {
84
93
  name: "skill_stub_tool",
@@ -86,7 +95,6 @@ describe("host.registries.register_tools", () => {
86
95
  input_schema: { type: "object" },
87
96
  defaultRiskLevel: "medium",
88
97
  category: "skill",
89
- ownerSkillId: "stub-skill",
90
98
  },
91
99
  ],
92
100
  });
@@ -106,16 +114,37 @@ describe("host.registries.register_tools", () => {
106
114
  });
107
115
 
108
116
  test("rejects empty tool list", async () => {
109
- await expect(registerToolsRoute.handler({ tools: [] })).rejects.toThrow();
117
+ await expect(
118
+ registerToolsRoute.handler({ skillId: "any-skill", tools: [] }),
119
+ ).rejects.toThrow();
110
120
  });
111
121
 
112
122
  test("rejects missing required fields", async () => {
113
123
  await expect(
114
124
  registerToolsRoute.handler({
125
+ skillId: "any-skill",
115
126
  tools: [{ name: "missing_rest" }],
116
127
  }),
117
128
  ).rejects.toThrow();
118
129
  });
130
+
131
+ test("rejects missing skillId", async () => {
132
+ // skillId is the only place ownership flows in over IPC — without it
133
+ // the registry can't claim the tools, so the handler must reject.
134
+ await expect(
135
+ registerToolsRoute.handler({
136
+ tools: [
137
+ {
138
+ name: "skill_orphan_tool",
139
+ description: "no owner",
140
+ input_schema: { type: "object" },
141
+ defaultRiskLevel: "low",
142
+ category: "skill",
143
+ },
144
+ ],
145
+ }),
146
+ ).rejects.toThrow();
147
+ });
119
148
  });
120
149
 
121
150
  // ---------------------------------------------------------------------------
@@ -302,6 +331,7 @@ describe("lazy-external short-circuit", () => {
302
331
 
303
332
  const result = (await registerToolsRoute.handler(
304
333
  {
334
+ skillId: "demo-skill",
305
335
  tools: [
306
336
  {
307
337
  name: "skill_demo_tool",
@@ -309,7 +339,6 @@ describe("lazy-external short-circuit", () => {
309
339
  input_schema: {},
310
340
  defaultRiskLevel: "low",
311
341
  category: "skill",
312
- ownerSkillId: "demo-skill",
313
342
  },
314
343
  ],
315
344
  },
@@ -17,13 +17,14 @@ import type { SkillIpcRoute } from "../skill-ipc-types.js";
17
17
 
18
18
  /**
19
19
  * Shape mirrors the daemon's `addMessage()` positional signature:
20
- * `(conversationId, role, content, metadata?, opts?)`. Metadata is a
21
- * free-form record (validated downstream by `messageMetadataSchema` with a
20
+ * `(conversationId, role, content, metadata?, opts?)`. `role` is
21
+ * constrained to the `MessageRole` union. Metadata is a free-form
22
+ * record (validated downstream by `messageMetadataSchema` with a
22
23
  * warn-and-store fallback). Only `skipIndexing` is recognised in `opts`.
23
24
  */
24
25
  const MemoryAddMessageParams = z.object({
25
26
  conversationId: z.string().min(1),
26
- role: z.string().min(1),
27
+ role: z.enum(["user", "assistant", "system"]),
27
28
  content: z.string(),
28
29
  metadata: z.record(z.string(), z.unknown()).optional(),
29
30
  opts: z
@@ -48,18 +48,19 @@ const ToolManifestSchema = z.object({
48
48
  defaultRiskLevel: z.enum(["low", "medium", "high"]),
49
49
  category: z.string().min(1),
50
50
  executionTarget: z.enum(["sandbox", "host"]).optional(),
51
- executionMode: z.enum(["local", "proxy"]).optional(),
52
- // Required so disconnect can decrement the tool-registry refcount: a
53
- // tool registered without an owner has no ref-counted entry to drop and
54
- // would leak into the global registry on socket close.
55
- ownerSkillId: z.string().min(1),
56
- ownerSkillBundled: z.boolean().optional(),
57
- ownerSkillVersionHash: z.string().optional(),
58
51
  });
59
52
 
60
53
  export type ToolManifest = z.infer<typeof ToolManifestSchema>;
61
54
 
55
+ // `skillId` lives at the params level rather than per-tool: a single
56
+ // `register_tools` IPC frame is always one skill's batch, ownership flows
57
+ // through `registerSkillTools(skillId, tools)` into the registry's
58
+ // `ownersByName` map, and the tools themselves stay free of owner
59
+ // metadata so callers cannot spoof ownership by forging a field on the
60
+ // manifest. Only skill-owned tools cross IPC — plugin and MCP tools live
61
+ // in-process on the assistant side.
62
62
  const RegisterToolsParams = z.object({
63
+ skillId: z.string().min(1),
63
64
  tools: z.array(ToolManifestSchema).min(1),
64
65
  });
65
66
 
@@ -187,16 +188,10 @@ function buildProxyTool(manifest: ToolManifest): Tool {
187
188
  input_schema: manifest.input_schema as object,
188
189
  category: manifest.category,
189
190
  defaultRiskLevel: manifest.defaultRiskLevel as RiskLevel,
190
- executionMode: manifest.executionMode ?? "proxy",
191
191
  executionTarget: resolveExecutionTarget({
192
192
  name: manifest.name,
193
193
  executionTarget: manifest.executionTarget as ExecutionTarget | undefined,
194
- executionMode: manifest.executionMode ?? "proxy",
195
194
  }),
196
- origin: "skill",
197
- ownerSkillId: manifest.ownerSkillId,
198
- ownerSkillBundled: manifest.ownerSkillBundled,
199
- ownerSkillVersionHash: manifest.ownerSkillVersionHash,
200
195
  execute: async () => {
201
196
  // Only reached when no supervisor is attached (tests/boot race);
202
197
  // the supervisor short-circuit above replaces this with the
@@ -214,7 +209,7 @@ async function handleRegisterTools(
214
209
  params: Record<string, unknown> | undefined,
215
210
  connection?: unknown,
216
211
  ): Promise<{ registered: string[] }> {
217
- const { tools } = RegisterToolsParams.parse(params);
212
+ const { skillId, tools } = RegisterToolsParams.parse(params);
218
213
  const conn = connection as SkillIpcConnection | undefined;
219
214
 
220
215
  // Supervisor short-circuit: when a supervisor is registered, the
@@ -226,7 +221,11 @@ async function handleRegisterTools(
226
221
  if (sessionSupervisor) {
227
222
  if (conn) sessionSupervisor.setActiveConnection(conn);
228
223
  log.info(
229
- { count: tools.length, names: tools.map((t) => t.name) },
224
+ {
225
+ count: tools.length,
226
+ names: tools.map((t) => t.name),
227
+ ownerSkillId: skillId,
228
+ },
230
229
  "Supervisor active: skipping in-memory tool re-registration; manifest proxies serve dispatches",
231
230
  );
232
231
  return { registered: tools.map((t) => t.name) };
@@ -236,23 +235,23 @@ async function handleRegisterTools(
236
235
  // `registerExternalTools` is only consumed inside `initializeTools()` at
237
236
  // daemon boot; IPC children connect after boot, so route through
238
237
  // `registerSkillTools` into the live registry the agent-loop reads from.
239
- const accepted = registerSkillTools(proxies);
240
-
241
- // `registerSkillTools` increments the registry refcount once per unique
242
- // ownerSkillId in the batch; mirror that on the connection so disconnect
243
- // issues exactly the matching number of decrements.
244
- if (conn) {
245
- const ownerIds = new Set<string>();
246
- for (const tool of accepted) {
247
- if (tool.ownerSkillId) ownerIds.add(tool.ownerSkillId);
248
- }
249
- for (const skillId of ownerIds) {
250
- conn.addSkillToolsOwner(skillId);
251
- }
238
+ // Owner is stamped registry-side from `skillId`; the proxy `Tool` objects
239
+ // carry no owner field.
240
+ const accepted = registerSkillTools(skillId, proxies);
241
+
242
+ // `registerSkillTools` increments the registry refcount once per call;
243
+ // mirror that on the connection so disconnect issues exactly one matching
244
+ // decrement.
245
+ if (conn && accepted.length > 0) {
246
+ conn.addSkillToolsOwner(skillId);
252
247
  }
253
248
 
254
249
  log.info(
255
- { count: accepted.length, names: accepted.map((t) => t.name) },
250
+ {
251
+ count: accepted.length,
252
+ names: accepted.map((t) => t.name),
253
+ ownerSkillId: skillId,
254
+ },
256
255
  "Registered skill proxy tools via IPC",
257
256
  );
258
257
  return { registered: accepted.map((t) => t.name) };
@@ -57,6 +57,7 @@ mock.module("../../runtime/actor-trust-resolver.js", () => ({
57
57
  // retrospective and auto-analysis paths fall through to the enqueue.
58
58
  mock.module("../conversation-crud.js", () => ({
59
59
  getConversationSource: () => null,
60
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
60
61
  }));
61
62
  mock.module("../auto-analysis-guard.js", () => ({
62
63
  isAutoAnalysisConversation: () => false,
@@ -34,11 +34,9 @@ import {
34
34
 
35
35
  import { eq } from "drizzle-orm";
36
36
 
37
- import { makeMockLogger } from "../../__tests__/helpers/mock-logger.js";
37
+ import { createMockLoggerModule } from "../../__tests__/helpers/mock-logger.js";
38
38
 
39
- mock.module("../../util/logger.js", () => ({
40
- getLogger: () => makeMockLogger(),
41
- }));
39
+ mock.module("../../util/logger.js", () => createMockLoggerModule());
42
40
 
43
41
  // Workspace pin must precede the `db` import below — the DB singleton
44
42
  // resolves its path at first call, so we need the env var set before
@@ -279,10 +277,33 @@ describe("maybeEnqueueGraphMaintenanceJobs — buffer-size trigger", () => {
279
277
  maybeEnqueueGraphMaintenanceJobs(config, now);
280
278
 
281
279
  expect(countPendingJobs("memory_v2_consolidate")).toBe(1);
282
- // Checkpoint refreshed so the next tick doesn't immediately re-fire.
280
+ // Checkpoint refreshed so the time-based branch doesn't re-fire.
283
281
  expect(getMemoryCheckpoint(CONSOLIDATE_CHECKPOINT_KEY)).toBe(String(now));
284
282
  });
285
283
 
284
+ test("does not re-fire on every tick while buffer stays over threshold", () => {
285
+ const config = buildConfig({
286
+ v2Enabled: true,
287
+ intervalHours: 1,
288
+ maxBufferLines: 5,
289
+ });
290
+
291
+ const now = Date.now();
292
+ // Recent checkpoint so the time-based branch never fires across ticks —
293
+ // the only thing that could re-enqueue is the size branch.
294
+ setMemoryCheckpoint(CONSOLIDATE_CHECKPOINT_KEY, String(now - 60_000));
295
+ writeBuffer(10);
296
+
297
+ // Simulate several worker ticks with the buffer still over threshold and
298
+ // the first-tick job still pending (nothing has drained it yet).
299
+ maybeEnqueueGraphMaintenanceJobs(config, now);
300
+ maybeEnqueueGraphMaintenanceJobs(config, now + 1_000);
301
+ maybeEnqueueGraphMaintenanceJobs(config, now + 2_000);
302
+
303
+ // A pending consolidate job dedupes the later ticks — only one enqueue.
304
+ expect(countPendingJobs("memory_v2_consolidate")).toBe(1);
305
+ });
306
+
286
307
  test("does not enqueue when buffer is under the threshold", () => {
287
308
  const config = buildConfig({
288
309
  v2Enabled: true,
@@ -19,6 +19,7 @@ const upsertCalls: Array<{
19
19
 
20
20
  mock.module("../conversation-crud.js", () => ({
21
21
  getConversationSource: (_id: string) => sourceTag,
22
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
22
23
  }));
23
24
 
24
25
  mock.module("../jobs-store.js", () => ({
@@ -144,6 +144,7 @@ mock.module("../conversation-crud.js", () => ({
144
144
  deleteConversation: (id: string) => {
145
145
  deletedConversationIds.push(id);
146
146
  },
147
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
147
148
  }));
148
149
 
149
150
  mock.module("../../config/assistant-feature-flags.js", () => ({
@@ -108,6 +108,7 @@ mock.module("../conversation-crud.js", () => ({
108
108
  deletedIds.push(id);
109
109
  mockConversations = mockConversations.filter((c) => c.id !== id);
110
110
  },
111
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
111
112
  }));
112
113
 
113
114
  import { sweepOrphanMemoryRetrospectiveConversations } from "../memory-retrospective-startup-cleanup.js";
@@ -187,4 +187,35 @@ describe("memory-v2-activation-log-store", () => {
187
187
  expect(byTurn.get(2)!.messageId).toBe("msg-a");
188
188
  expect(byTurn.get(3)!.messageId).toBe("msg-b");
189
189
  });
190
+
191
+ test("backfill skips v3_shadow rows, leaving their messageId null", () => {
192
+ const conversationId = "conv-shadow-backfill";
193
+
194
+ // A live router row (null messageId) and a detached v3_shadow row (null
195
+ // messageId) coexist in the same conversation.
196
+ recordMemoryV2ActivationLog({
197
+ conversationId,
198
+ turn: 5,
199
+ mode: "router",
200
+ concepts: sampleConcepts,
201
+ config: sampleConfig,
202
+ });
203
+ recordMemoryV2ActivationLog({
204
+ conversationId,
205
+ turn: 5,
206
+ mode: "v3_shadow",
207
+ concepts: sampleConcepts,
208
+ config: sampleConfig,
209
+ });
210
+
211
+ backfillMemoryV2ActivationMessageId(conversationId, "msg-live");
212
+
213
+ const db = getDb();
214
+ const rows = db.select().from(memoryV2ActivationLogs).all();
215
+ const byMode = new Map(rows.map((r) => [r.mode, r]));
216
+ // The live router row got stamped; the shadow row stayed null (not
217
+ // mis-attributed to the live message).
218
+ expect(byMode.get("router")!.messageId).toBe("msg-live");
219
+ expect(byMode.get("v3_shadow")!.messageId).toBeNull();
220
+ });
190
221
  });