@vellumai/assistant 0.6.1 → 0.6.3

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 (463) hide show
  1. package/bun.lock +40 -40
  2. package/bunfig.toml +3 -0
  3. package/docker-entrypoint.sh +12 -2
  4. package/docs/architecture/memory.md +1 -1
  5. package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
  6. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  7. package/openapi.yaml +184 -69
  8. package/package.json +41 -41
  9. package/scripts/generate-openapi.ts +1 -2
  10. package/src/__tests__/acp-session.test.ts +43 -0
  11. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  12. package/src/__tests__/app-executors.test.ts +1 -0
  13. package/src/__tests__/app-source-watcher.test.ts +37 -11
  14. package/src/__tests__/approval-routes-http.test.ts +178 -1
  15. package/src/__tests__/assistant-event-hub.test.ts +30 -0
  16. package/src/__tests__/browser-fill-credential.test.ts +229 -94
  17. package/src/__tests__/browser-manager.test.ts +40 -27
  18. package/src/__tests__/catalog-files.test.ts +862 -0
  19. package/src/__tests__/channel-approvals.test.ts +53 -0
  20. package/src/__tests__/checker.test.ts +104 -170
  21. package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
  22. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  23. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  24. package/src/__tests__/config-schema.test.ts +125 -48
  25. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  26. package/src/__tests__/context-overflow-approval.test.ts +21 -6
  27. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  28. package/src/__tests__/conversation-agent-loop.test.ts +1 -1
  29. package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
  30. package/src/__tests__/conversation-attachments.test.ts +80 -4
  31. package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
  32. package/src/__tests__/conversation-directories-parse.test.ts +105 -0
  33. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  34. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  35. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  36. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  37. package/src/__tests__/conversation-queue.test.ts +45 -2
  38. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
  39. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  40. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  41. package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
  42. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  43. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  44. package/src/__tests__/conversation-store.test.ts +195 -0
  45. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  46. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -3
  47. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  48. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  49. package/src/__tests__/credential-vault.test.ts +152 -13
  50. package/src/__tests__/credentials-cli.test.ts +2 -2
  51. package/src/__tests__/date-context.test.ts +4 -4
  52. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  53. package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
  54. package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
  55. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  56. package/src/__tests__/gemini-provider.test.ts +2 -2
  57. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  58. package/src/__tests__/headless-browser-interactions.test.ts +707 -371
  59. package/src/__tests__/headless-browser-navigate.test.ts +389 -47
  60. package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
  61. package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
  62. package/src/__tests__/host-bash-proxy.test.ts +150 -1
  63. package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
  64. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  65. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  66. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  67. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  68. package/src/__tests__/host-browser-routes.test.ts +198 -0
  69. package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
  70. package/src/__tests__/host-cu-proxy.test.ts +171 -1
  71. package/src/__tests__/host-file-proxy.test.ts +185 -1
  72. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  73. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  74. package/src/__tests__/host-shell-tool.test.ts +1 -11
  75. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  76. package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
  77. package/src/__tests__/inline-command-runner.test.ts +7 -5
  78. package/src/__tests__/integration-status.test.ts +6 -7
  79. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  80. package/src/__tests__/log-export-workspace.test.ts +190 -0
  81. package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
  82. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  83. package/src/__tests__/mcp-health-check.test.ts +10 -3
  84. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  85. package/src/__tests__/migration-export-http.test.ts +61 -2
  86. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  87. package/src/__tests__/migration-import-commit-http.test.ts +101 -1
  88. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  89. package/src/__tests__/navigate-settings-tab.test.ts +14 -1
  90. package/src/__tests__/notification-broadcaster.test.ts +65 -0
  91. package/src/__tests__/oauth-apps-routes.test.ts +17 -12
  92. package/src/__tests__/oauth-cli.test.ts +707 -60
  93. package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
  94. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  95. package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
  96. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  97. package/src/__tests__/oauth-providers-routes.test.ts +50 -14
  98. package/src/__tests__/oauth-store.test.ts +1386 -182
  99. package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
  100. package/src/__tests__/onboarding-template-contract.test.ts +74 -55
  101. package/src/__tests__/openai-provider.test.ts +2 -2
  102. package/src/__tests__/outlook-categories.test.ts +1 -1
  103. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  104. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  105. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  106. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  107. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  108. package/src/__tests__/outlook-trash.test.ts +1 -1
  109. package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
  110. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  111. package/src/__tests__/permission-mode.test.ts +28 -56
  112. package/src/__tests__/pkb-autoinject.test.ts +96 -0
  113. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  114. package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
  115. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  116. package/src/__tests__/require-fresh-approval.test.ts +40 -3
  117. package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
  118. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  119. package/src/__tests__/schedule-routes.test.ts +162 -0
  120. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  121. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  123. package/src/__tests__/set-permission-mode.test.ts +13 -250
  124. package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
  125. package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
  126. package/src/__tests__/slack-channel-config.test.ts +12 -15
  127. package/src/__tests__/subagent-detail.test.ts +44 -2
  128. package/src/__tests__/subagent-disposal.test.ts +1 -0
  129. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  130. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  131. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  132. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  133. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  134. package/src/__tests__/subagent-tools.test.ts +1 -0
  135. package/src/__tests__/subagent-types.test.ts +1 -0
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  137. package/src/__tests__/system-prompt.test.ts +72 -1
  138. package/src/__tests__/task-scheduler.test.ts +32 -6
  139. package/src/__tests__/telegram-config.test.ts +10 -13
  140. package/src/__tests__/terminal-sandbox.test.ts +1 -1
  141. package/src/__tests__/terminal-tools.test.ts +11 -5
  142. package/src/__tests__/test-preload.ts +14 -0
  143. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  144. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
  145. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
  146. package/src/__tests__/tool-executor.test.ts +0 -1
  147. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  148. package/src/__tests__/top-level-renderer.test.ts +73 -1
  149. package/src/__tests__/transport-hints-queue.test.ts +62 -0
  150. package/src/__tests__/trust-store.test.ts +4 -4
  151. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  152. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  153. package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
  154. package/src/__tests__/workspace-policy.test.ts +2 -7
  155. package/src/acp/client-handler.ts +30 -4
  156. package/src/agent/loop.ts +12 -35
  157. package/src/approvals/guardian-request-resolvers.ts +21 -15
  158. package/src/browser-session/__tests__/manager.test.ts +297 -0
  159. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  160. package/src/browser-session/backends/extension.ts +26 -0
  161. package/src/browser-session/backends/local.ts +24 -0
  162. package/src/browser-session/events.ts +164 -0
  163. package/src/browser-session/index.ts +27 -0
  164. package/src/browser-session/manager.ts +159 -0
  165. package/src/browser-session/types.ts +28 -0
  166. package/src/channels/__tests__/types.test.ts +134 -0
  167. package/src/channels/types.ts +55 -0
  168. package/src/cli/__tests__/run-assistant-command.ts +34 -7
  169. package/src/cli/__tests__/unknown-command.test.ts +33 -0
  170. package/src/cli/commands/browser-relay.ts +339 -409
  171. package/src/cli/commands/credentials.ts +3 -3
  172. package/src/cli/commands/default-action.ts +68 -1
  173. package/src/cli/commands/email.ts +18 -13
  174. package/src/cli/commands/mcp.ts +16 -4
  175. package/src/cli/commands/oauth/__tests__/connect.test.ts +68 -41
  176. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  177. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  178. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  179. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
  180. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
  181. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
  182. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  183. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  184. package/src/cli/commands/oauth/apps.ts +7 -4
  185. package/src/cli/commands/oauth/connect.ts +16 -2
  186. package/src/cli/commands/oauth/disconnect.ts +1 -1
  187. package/src/cli/commands/oauth/providers.ts +200 -36
  188. package/src/cli/commands/oauth/shared.ts +5 -5
  189. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
  190. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  191. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  192. package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
  193. package/src/cli/commands/platform/index.ts +107 -10
  194. package/src/cli/commands/usage.ts +10 -9
  195. package/src/cli/lib/daemon-credential-client.ts +4 -0
  196. package/src/cli/program.ts +10 -3
  197. package/src/config/assistant-feature-flags.ts +59 -55
  198. package/src/config/bundled-skills/app-builder/SKILL.md +33 -173
  199. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
  200. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  201. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  202. package/src/config/bundled-skills/contacts/SKILL.md +3 -0
  203. package/src/config/bundled-skills/document/SKILL.md +4 -0
  204. package/src/config/bundled-skills/gmail/SKILL.md +12 -7
  205. package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
  206. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
  207. package/src/config/bundled-skills/outlook/SKILL.md +7 -0
  208. package/src/config/bundled-skills/settings/TOOLS.json +1 -1
  209. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
  210. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  211. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  212. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  213. package/src/config/env-registry.ts +14 -0
  214. package/src/config/env.ts +21 -0
  215. package/src/config/feature-flag-registry.json +46 -7
  216. package/src/config/loader.ts +56 -1
  217. package/src/config/sanitize-for-transfer.ts +47 -0
  218. package/src/config/schema.ts +46 -5
  219. package/src/config/schemas/host-browser.ts +66 -0
  220. package/src/config/schemas/memory-lifecycle.ts +1 -1
  221. package/src/config/schemas/memory-retrieval.ts +103 -0
  222. package/src/config/schemas/security.ts +0 -6
  223. package/src/config/schemas/services.ts +16 -0
  224. package/src/config/types.ts +0 -1
  225. package/src/context/post-turn-tool-result-truncation.ts +176 -0
  226. package/src/context/window-manager.ts +19 -1
  227. package/src/credential-execution/approval-bridge.ts +49 -16
  228. package/src/credential-execution/managed-catalog.ts +3 -7
  229. package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
  230. package/src/daemon/app-source-watcher.ts +35 -0
  231. package/src/daemon/config-watcher.ts +6 -2
  232. package/src/daemon/context-overflow-approval.ts +5 -1
  233. package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
  234. package/src/daemon/conversation-agent-loop.ts +74 -19
  235. package/src/daemon/conversation-attachments.ts +40 -1
  236. package/src/daemon/conversation-messaging.ts +3 -0
  237. package/src/daemon/conversation-process.ts +66 -3
  238. package/src/daemon/conversation-queue-manager.ts +8 -0
  239. package/src/daemon/conversation-runtime-assembly.ts +159 -20
  240. package/src/daemon/conversation-surfaces.ts +78 -12
  241. package/src/daemon/conversation-tool-setup.ts +74 -11
  242. package/src/daemon/conversation-workspace.ts +12 -0
  243. package/src/daemon/conversation.ts +227 -11
  244. package/src/daemon/date-context.ts +10 -10
  245. package/src/daemon/first-greeting.ts +3 -2
  246. package/src/daemon/handlers/conversations.ts +9 -139
  247. package/src/daemon/handlers/shared.ts +65 -0
  248. package/src/daemon/handlers/skills.ts +232 -37
  249. package/src/daemon/host-bash-proxy.ts +48 -13
  250. package/src/daemon/host-browser-proxy.ts +191 -0
  251. package/src/daemon/host-cu-proxy.ts +36 -11
  252. package/src/daemon/host-file-proxy.ts +57 -9
  253. package/src/daemon/lifecycle.ts +86 -12
  254. package/src/daemon/message-protocol.ts +7 -0
  255. package/src/daemon/message-types/conversations.ts +59 -13
  256. package/src/daemon/message-types/host-browser.ts +100 -0
  257. package/src/daemon/message-types/messages.ts +5 -6
  258. package/src/daemon/message-types/notifications.ts +12 -0
  259. package/src/daemon/message-types/settings.ts +12 -0
  260. package/src/daemon/message-types/skills.ts +10 -0
  261. package/src/daemon/message-types/subagents.ts +2 -0
  262. package/src/daemon/server.ts +112 -35
  263. package/src/daemon/tool-side-effects.ts +6 -0
  264. package/src/daemon/transport-hints.ts +14 -0
  265. package/src/inbound/platform-callback-registration.ts +18 -17
  266. package/src/index.ts +1 -1
  267. package/src/mcp/client.ts +59 -24
  268. package/src/memory/app-store.ts +31 -1
  269. package/src/memory/conversation-crud.ts +38 -10
  270. package/src/memory/conversation-directories.ts +39 -0
  271. package/src/memory/conversation-group-migration.ts +65 -5
  272. package/src/memory/conversation-starters-cadence.ts +76 -0
  273. package/src/memory/conversation-title-service.ts +5 -2
  274. package/src/memory/db-init.ts +12 -0
  275. package/src/memory/embedding-backend.test.ts +75 -0
  276. package/src/memory/embedding-backend.ts +131 -5
  277. package/src/memory/embedding-gemini.test.ts +54 -0
  278. package/src/memory/embedding-gemini.ts +20 -9
  279. package/src/memory/embedding-local.ts +177 -18
  280. package/src/memory/graph/capability-seed.ts +3 -5
  281. package/src/memory/graph/consolidation.ts +10 -23
  282. package/src/memory/graph/extraction-job.ts +15 -0
  283. package/src/memory/graph/retriever.ts +40 -22
  284. package/src/memory/graph/store.test.ts +7 -3
  285. package/src/memory/graph/store.ts +47 -12
  286. package/src/memory/group-crud.ts +25 -9
  287. package/src/memory/llm-usage-store.ts +45 -4
  288. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  289. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  290. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  291. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  292. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  293. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  294. package/src/memory/migrations/index.ts +6 -0
  295. package/src/memory/migrations/registry.ts +8 -0
  296. package/src/memory/schema/conversations.ts +1 -0
  297. package/src/memory/schema/oauth.ts +18 -13
  298. package/src/messaging/provider.ts +1 -1
  299. package/src/notifications/broadcaster.ts +6 -0
  300. package/src/notifications/conversation-pairing.ts +12 -4
  301. package/src/notifications/emit-signal.ts +14 -0
  302. package/src/notifications/signal.ts +11 -0
  303. package/src/oauth/AGENTS.md +76 -0
  304. package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
  305. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  306. package/src/oauth/byo-connection.test.ts +8 -8
  307. package/src/oauth/byo-connection.ts +7 -7
  308. package/src/oauth/connect-orchestrator.ts +23 -21
  309. package/src/oauth/connect-types.ts +3 -3
  310. package/src/oauth/connection-resolver.test.ts +17 -4
  311. package/src/oauth/connection-resolver.ts +16 -16
  312. package/src/oauth/connection.ts +1 -1
  313. package/src/oauth/manual-token-connection.ts +13 -13
  314. package/src/oauth/oauth-store.ts +214 -100
  315. package/src/oauth/platform-connection.test.ts +5 -5
  316. package/src/oauth/platform-connection.ts +4 -4
  317. package/src/oauth/provider-serializer.ts +31 -5
  318. package/src/oauth/revoke.ts +76 -0
  319. package/src/oauth/seed-providers.ts +127 -87
  320. package/src/oauth/token-persistence.ts +1 -1
  321. package/src/permissions/checker.ts +3 -3
  322. package/src/permissions/defaults.ts +7 -8
  323. package/src/permissions/permission-mode.ts +4 -11
  324. package/src/permissions/prompter.ts +13 -3
  325. package/src/permissions/v2-consent-policy.ts +87 -0
  326. package/src/platform/client.ts +1 -1
  327. package/src/prompts/system-prompt.ts +18 -21
  328. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  329. package/src/prompts/templates/BOOTSTRAP.md +59 -96
  330. package/src/prompts/templates/SOUL.md +11 -11
  331. package/src/providers/anthropic/client.ts +1 -0
  332. package/src/providers/types.ts +1 -1
  333. package/src/runtime/AGENTS.md +23 -0
  334. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  335. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  336. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  337. package/src/runtime/assistant-event-hub.ts +24 -2
  338. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  339. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  340. package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
  341. package/src/runtime/auth/middleware.ts +98 -0
  342. package/src/runtime/auth/route-policy.ts +6 -7
  343. package/src/runtime/auth/token-service.ts +8 -0
  344. package/src/runtime/capability-tokens.ts +414 -0
  345. package/src/runtime/channel-approvals.ts +18 -5
  346. package/src/runtime/chrome-extension-registry.ts +332 -0
  347. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  348. package/src/runtime/guardian-decision-types.ts +7 -0
  349. package/src/runtime/http-server.ts +425 -70
  350. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  351. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  352. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
  353. package/src/runtime/migrations/migration-transport.ts +6 -0
  354. package/src/runtime/migrations/migration-wizard.ts +22 -2
  355. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  356. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  357. package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
  358. package/src/runtime/migrations/vbundle-importer.ts +55 -5
  359. package/src/runtime/pending-interactions.ts +29 -13
  360. package/src/runtime/routes/approval-routes.ts +90 -16
  361. package/src/runtime/routes/browser-cdp-routes.ts +229 -0
  362. package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
  363. package/src/runtime/routes/conversation-analysis-routes.ts +18 -5
  364. package/src/runtime/routes/conversation-management-routes.ts +108 -0
  365. package/src/runtime/routes/conversation-routes.ts +308 -28
  366. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  367. package/src/runtime/routes/group-routes.ts +22 -8
  368. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  369. package/src/runtime/routes/host-browser-routes.ts +279 -0
  370. package/src/runtime/routes/host-file-routes.ts +9 -1
  371. package/src/runtime/routes/identity-routes.ts +259 -16
  372. package/src/runtime/routes/log-export/AGENTS.md +104 -0
  373. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
  374. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
  375. package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
  376. package/src/runtime/routes/log-export-routes.ts +60 -25
  377. package/src/runtime/routes/memory-item-routes.ts +1 -7
  378. package/src/runtime/routes/migration-routes.ts +87 -2
  379. package/src/runtime/routes/oauth-apps.ts +15 -17
  380. package/src/runtime/routes/oauth-providers.ts +4 -0
  381. package/src/runtime/routes/schedule-routes.ts +24 -11
  382. package/src/runtime/routes/settings-routes.ts +9 -97
  383. package/src/runtime/routes/skills-routes.ts +52 -2
  384. package/src/runtime/routes/subagents-routes.ts +14 -10
  385. package/src/runtime/routes/usage-routes.ts +8 -7
  386. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  387. package/src/runtime/routes/workspace-routes.ts +8 -1
  388. package/src/runtime/routes/workspace-utils.ts +2 -0
  389. package/src/schedule/scheduler.ts +7 -5
  390. package/src/security/ces-credential-client.ts +20 -0
  391. package/src/security/ces-rpc-credential-backend.ts +17 -0
  392. package/src/security/credential-backend.ts +5 -0
  393. package/src/security/oauth2.ts +42 -25
  394. package/src/security/secure-keys.ts +118 -25
  395. package/src/security/token-manager.ts +23 -10
  396. package/src/skills/catalog-files.ts +492 -0
  397. package/src/skills/inline-command-runner.ts +12 -14
  398. package/src/subagent/manager.ts +131 -26
  399. package/src/subagent/types.ts +19 -0
  400. package/src/tools/apps/executors.ts +11 -2
  401. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  402. package/src/tools/browser/auth-detector.ts +43 -12
  403. package/src/tools/browser/browser-execution.ts +645 -340
  404. package/src/tools/browser/browser-manager.ts +36 -12
  405. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  406. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  407. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
  408. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
  409. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
  410. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  411. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  412. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  413. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  414. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  415. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  416. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
  417. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  418. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
  419. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  420. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
  421. package/src/tools/browser/cdp-client/errors.ts +34 -0
  422. package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
  423. package/src/tools/browser/cdp-client/factory.ts +204 -0
  424. package/src/tools/browser/cdp-client/index.ts +14 -0
  425. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  426. package/src/tools/browser/cdp-client/types.ts +52 -0
  427. package/src/tools/filesystem/edit.ts +1 -1
  428. package/src/tools/filesystem/list.ts +1 -1
  429. package/src/tools/filesystem/read.ts +1 -1
  430. package/src/tools/filesystem/write.ts +2 -1
  431. package/src/tools/host-filesystem/edit.ts +1 -1
  432. package/src/tools/host-filesystem/read.ts +12 -15
  433. package/src/tools/host-filesystem/write.ts +1 -1
  434. package/src/tools/host-terminal/host-shell.ts +21 -16
  435. package/src/tools/permission-checker.ts +77 -100
  436. package/src/tools/registry.ts +0 -2
  437. package/src/tools/secret-detection-handler.ts +34 -1
  438. package/src/tools/shared/filesystem/image-read.ts +61 -40
  439. package/src/tools/skills/sandbox-runner.ts +3 -6
  440. package/src/tools/subagent/spawn.ts +47 -3
  441. package/src/tools/subagent/status.ts +2 -0
  442. package/src/tools/system/register.ts +2 -16
  443. package/src/tools/terminal/safe-env.ts +7 -0
  444. package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
  445. package/src/tools/terminal/sandbox.ts +4 -1
  446. package/src/tools/terminal/shell.ts +24 -21
  447. package/src/tools/tool-approval-handler.ts +48 -2
  448. package/src/tools/types.ts +2 -3
  449. package/src/util/platform.ts +14 -19
  450. package/src/watcher/provider-types.ts +1 -1
  451. package/src/workspace/migrations/029-seed-pkb.ts +1 -0
  452. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
  453. package/src/workspace/migrations/registry.ts +2 -0
  454. package/src/workspace/top-level-renderer.ts +19 -1
  455. package/src/__tests__/chrome-cdp.test.ts +0 -419
  456. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  457. package/src/__tests__/permission-mode-store.test.ts +0 -277
  458. package/src/browser-extension-relay/protocol.ts +0 -63
  459. package/src/browser-extension-relay/server.ts +0 -203
  460. package/src/config/schemas/sandbox.ts +0 -14
  461. package/src/permissions/permission-mode-store.ts +0 -180
  462. package/src/tools/browser/chrome-cdp.ts +0 -239
  463. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -1,15 +1,13 @@
1
1
  /**
2
- * Platform callback route registration for platform-managed deployments.
2
+ * Platform callback route registration.
3
3
  *
4
- * When the assistant daemon runs as a platform-managed instance (IS_PLATFORM=true)
5
- * with a configured VELLUM_PLATFORM_URL and PLATFORM_ASSISTANT_ID, external
6
- * service callbacks (Twilio webhooks, OAuth redirects, Telegram webhooks, etc.)
7
- * must route through the platform's gateway proxy instead of hitting the
8
- * assistant directly.
4
+ * Both platform-managed (IS_PLATFORM=true) and self-hosted assistants can
5
+ * register callback routes with the platform so inbound provider webhooks
6
+ * (Telegram, Twilio, email, OAuth) are forwarded correctly.
9
7
  *
10
- * This module registers callback routes with the platform's internal
11
- * gateway endpoint so the platform knows how to forward inbound provider
12
- * webhooks to the correct platform-managed assistant instance.
8
+ * Platform-managed assistants pick up context from environment variables.
9
+ * Self-hosted assistants use stored credentials (from `assistant platform
10
+ * connect` or the ensure-registration bootstrap).
13
11
  *
14
12
  * The platform endpoint is:
15
13
  * POST {VELLUM_PLATFORM_URL}/v1/internal/gateway/callback-routes/register/
@@ -41,17 +39,18 @@ export interface PlatformCallbackRegistrationContext {
41
39
  }
42
40
 
43
41
  /**
44
- * Whether the daemon should register callback routes with the platform.
42
+ * Whether the **runtime** should automatically register callback routes.
45
43
  * True when IS_PLATFORM, VELLUM_PLATFORM_URL, and PLATFORM_ASSISTANT_ID
46
- * are all set. Intentionally does **not** require the managed proxy API key
47
- * so that callback-only flows (OAuth transport, Telegram/Twilio callback
48
- * registration) work during partial bootstrap before the key is injected.
44
+ * are all set i.e. this is a platform-managed deployment.
45
+ *
46
+ * This is intentionally stricter than `context.enabled` (which also covers
47
+ * self-hosted assistants with stored credentials). Runtime auto-registration
48
+ * only applies to managed deployments; self-hosted assistants register
49
+ * explicitly via the CLI or gateway startup hooks.
49
50
  */
50
51
  export function shouldUsePlatformCallbacks(): boolean {
51
52
  return (
52
- getIsPlatform() &&
53
- !!getPlatformBaseUrl() &&
54
- !!getPlatformAssistantId()
53
+ getIsPlatform() && !!getPlatformBaseUrl() && !!getPlatformAssistantId()
55
54
  );
56
55
  }
57
56
 
@@ -86,8 +85,10 @@ export async function resolvePlatformCallbackRegistrationContext(): Promise<Plat
86
85
  hasInternalApiKey: internalApiKey.length > 0,
87
86
  hasAssistantApiKey: assistantApiKey.length > 0,
88
87
  authHeader,
88
+ // Enabled when we have enough context to register callback routes.
89
+ // Does NOT require IS_PLATFORM — self-hosted assistants with stored
90
+ // credentials can also register routes.
89
91
  enabled:
90
- platform &&
91
92
  platformBaseUrl.length > 0 &&
92
93
  assistantId.length > 0 &&
93
94
  authHeader !== null,
package/src/index.ts CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  import { buildCliProgram } from "./cli/program.js";
4
4
 
5
- buildCliProgram().parse();
5
+ (await buildCliProgram()).parse();
package/src/mcp/client.ts CHANGED
@@ -35,6 +35,12 @@ export class McpClient {
35
35
  | null = null;
36
36
  private connected = false;
37
37
  private oauthProvider: McpOAuthProvider | null = null;
38
+ private _lastError: Error | null = null;
39
+
40
+ /** The last connection error, if any. Null when connected or not yet attempted. */
41
+ get lastError(): Error | null {
42
+ return this._lastError;
43
+ }
38
44
 
39
45
  get isConnected(): boolean {
40
46
  return this.connected;
@@ -46,6 +52,16 @@ export class McpClient {
46
52
  name: "vellum-assistant",
47
53
  version: "1.0.0",
48
54
  });
55
+
56
+ // Prevent SDK-internal transport errors (e.g. SSE reconnection auth
57
+ // failures) from surfacing as unhandled rejections that crash the daemon
58
+ // via the global unhandledRejection → shutdown handler.
59
+ this.client.onerror = (error) => {
60
+ log.warn(
61
+ { serverId: this.serverId, err: error },
62
+ "MCP SDK transport error (non-fatal)",
63
+ );
64
+ };
49
65
  }
50
66
 
51
67
  async connect(transportConfig: McpTransport): Promise<void> {
@@ -102,34 +118,24 @@ export class McpClient {
102
118
  }
103
119
  this.transport = null;
104
120
 
105
- if (isHttpTransport) {
106
- const isAuthError =
107
- err instanceof UnauthorizedError ||
108
- (err instanceof Error &&
109
- /\b(401|403|unauthorized|forbidden)\b/i.test(err.message)) ||
110
- (err != null &&
111
- typeof err === "object" &&
112
- "code" in err &&
113
- (err.code === 401 || err.code === 403));
114
-
115
- if (isAuthError) {
116
- // Auth-related — user can run `assistant mcp auth <name>` to authenticate.
117
- log.info(
118
- { serverId: this.serverId, err },
119
- "MCP server requires authentication",
120
- );
121
- return;
122
- }
123
-
124
- // Non-auth error (DNS, TLS, timeout, etc.) — log and re-throw
125
- log.error(
121
+ if (isHttpTransport && isAuthRelatedError(err)) {
122
+ // Auth-related — user can run `assistant mcp auth <name>` to authenticate.
123
+ log.info(
126
124
  { serverId: this.serverId, err },
127
- "MCP server connection failed",
125
+ "MCP server requires authentication",
128
126
  );
129
- throw err;
127
+ return;
130
128
  }
131
129
 
132
- throw err;
130
+ // Non-auth error (DNS, TLS, timeout, etc.) — log but never propagate
131
+ // an MCP connection failure to the caller. The daemon must keep
132
+ // running even when individual MCP servers are unreachable.
133
+ this._lastError = err instanceof Error ? err : new Error(String(err));
134
+ log.error(
135
+ { serverId: this.serverId, err },
136
+ "MCP server connection failed",
137
+ );
138
+ return;
133
139
  }
134
140
 
135
141
  this.connected = true;
@@ -258,3 +264,32 @@ export class McpClient {
258
264
  }
259
265
  }
260
266
  }
267
+
268
+ /**
269
+ * Returns true when `err` looks like an authentication / authorization failure
270
+ * from the MCP SDK or the remote server. Used to distinguish "needs auth"
271
+ * from genuine transport failures so we can log guidance instead of crashing.
272
+ */
273
+ function isAuthRelatedError(err: unknown): boolean {
274
+ if (err instanceof UnauthorizedError) return true;
275
+
276
+ if (
277
+ err instanceof Error &&
278
+ /\b(401|403|unauthorized|forbidden|authorizationCode is required|prepareTokenRequest)\b/i.test(
279
+ err.message,
280
+ )
281
+ ) {
282
+ return true;
283
+ }
284
+
285
+ if (
286
+ err != null &&
287
+ typeof err === "object" &&
288
+ "code" in err &&
289
+ (err.code === 401 || err.code === 403)
290
+ ) {
291
+ return true;
292
+ }
293
+
294
+ return false;
295
+ }
@@ -64,6 +64,23 @@ export function isMultifileApp(app: AppDefinition): boolean {
64
64
  return app.formatVersion === 2;
65
65
  }
66
66
 
67
+ /**
68
+ * Resolve the effective HTML for an app. For single-file apps this is
69
+ * `htmlDefinition` (the root index.html). For multifile apps it reads the
70
+ * compiled `dist/index.html` and inlines JS/CSS assets so the result is a
71
+ * self-contained HTML string suitable for `loadHTMLString`.
72
+ */
73
+ export function resolveEffectiveAppHtml(app: AppDefinition): string {
74
+ if (!isMultifileApp(app)) return app.htmlDefinition;
75
+
76
+ const appDir = getAppDirPath(app.id);
77
+ const distIndex = join(appDir, "dist", "index.html");
78
+ if (existsSync(distIndex)) {
79
+ return inlineDistAssets(appDir, readFileSync(distIndex, "utf-8"));
80
+ }
81
+ return app.htmlDefinition;
82
+ }
83
+
67
84
  /**
68
85
  * Inline dist assets (main.js, main.css) into the compiled HTML so it can be
69
86
  * delivered as a self-contained string via loadHTMLString/SSE without needing
@@ -75,7 +92,10 @@ export function inlineDistAssets(appDir: string, html: string): string {
75
92
  // Inline main.js
76
93
  const jsPath = join(distDir, "main.js");
77
94
  if (existsSync(jsPath)) {
78
- const js = readFileSync(jsPath, "utf-8").replace(/<\/script>/g, "<\\/script>");
95
+ const js = readFileSync(jsPath, "utf-8").replace(
96
+ /<\/script>/g,
97
+ "<\\/script>",
98
+ );
79
99
  html = html.replace(
80
100
  /<script\s+type="module"\s+src="main\.js"\s*><\/script>/,
81
101
  () => `<script type="module">${js}</script>`,
@@ -818,6 +838,16 @@ export function listAppFiles(appId: string): string[] {
818
838
  return results.sort();
819
839
  }
820
840
 
841
+ /**
842
+ * Check whether a file exists in the app directory.
843
+ * Path is validated to prevent traversal.
844
+ */
845
+ export function appFileExists(appId: string, path: string): boolean {
846
+ validateId(appId);
847
+ const resolved = validateFilePath(appId, path);
848
+ return existsSync(resolved);
849
+ }
850
+
821
851
  /**
822
852
  * Read a file from the app directory.
823
853
  * Path is validated to prevent traversal.
@@ -170,6 +170,7 @@ export interface ConversationRow {
170
170
  originInterface: string | null;
171
171
  forkParentConversationId: string | null;
172
172
  forkParentMessageId: string | null;
173
+ hostAccess: number;
173
174
  isAutoTitle: number;
174
175
  scheduleJobId: string | null;
175
176
  lastMessageAt: number | null;
@@ -196,6 +197,7 @@ export const parseConversation = createRowMapper<
196
197
  originInterface: "originInterface",
197
198
  forkParentConversationId: "forkParentConversationId",
198
199
  forkParentMessageId: "forkParentMessageId",
200
+ hostAccess: "hostAccess",
199
201
  isAutoTitle: "isAutoTitle",
200
202
  scheduleJobId: "scheduleJobId",
201
203
  lastMessageAt: "lastMessageAt",
@@ -245,6 +247,7 @@ export function createConversation(
245
247
  source?: string;
246
248
  scheduleJobId?: string;
247
249
  groupId?: string;
250
+ hostAccess?: boolean;
248
251
  },
249
252
  ) {
250
253
  const db = getDb();
@@ -276,6 +279,7 @@ export function createConversation(
276
279
  contextSummary: null as string | null,
277
280
  contextCompactedMessageCount: 0,
278
281
  contextCompactedAt: null as number | null,
282
+ hostAccess: opts.hostAccess ? 1 : 0,
279
283
  conversationType,
280
284
  source,
281
285
  memoryScopeId,
@@ -314,12 +318,15 @@ export function createConversation(
314
318
 
315
319
  // group_id is NOT in the Drizzle schema (raw-query-only pattern).
316
320
  // Set via raw SQL after the INSERT succeeds.
317
- if (groupId) {
321
+ // Always set group_id — default to "system:all" when none provided.
322
+ {
323
+ const effectiveGroupId = groupId ?? "system:all";
318
324
  for (let attempt = 0; ; attempt++) {
319
325
  try {
320
326
  rawRun(
321
- "UPDATE conversations SET group_id = ? WHERE id = ?",
322
- groupId,
327
+ "UPDATE conversations SET group_id = ?, is_pinned = ? WHERE id = ?",
328
+ effectiveGroupId,
329
+ effectiveGroupId === "system:pinned" ? 1 : 0,
323
330
  id,
324
331
  );
325
332
  break;
@@ -385,6 +392,11 @@ export function getConversationMemoryScopeId(conversationId: string): string {
385
392
  return conv?.memoryScopeId ?? "default";
386
393
  }
387
394
 
395
+ export function getConversationHostAccess(conversationId: string): boolean {
396
+ const conv = getConversation(conversationId);
397
+ return conv?.hostAccess === 1;
398
+ }
399
+
388
400
  /**
389
401
  * Fetch group_id for a conversation via raw SQL. group_id is NOT in the
390
402
  * Drizzle schema (raw-query-only pattern), so ConversationRow doesn't
@@ -469,7 +481,7 @@ export function forkConversation(params: {
469
481
  const fc = createConversation({
470
482
  title: forkTitle,
471
483
  conversationType: "standard",
472
- groupId: parentGroupId ?? undefined,
484
+ groupId: parentGroupId ?? "system:all",
473
485
  });
474
486
 
475
487
  db.update(conversations)
@@ -1120,6 +1132,20 @@ export function updateConversationContextWindow(
1120
1132
  .run();
1121
1133
  }
1122
1134
 
1135
+ export function updateConversationHostAccess(
1136
+ id: string,
1137
+ hostAccess: boolean,
1138
+ ): void {
1139
+ const db = getDb();
1140
+ db.update(conversations)
1141
+ .set({
1142
+ hostAccess: hostAccess ? 1 : 0,
1143
+ updatedAt: Date.now(),
1144
+ })
1145
+ .where(eq(conversations.id, id))
1146
+ .run();
1147
+ }
1148
+
1123
1149
  /**
1124
1150
  * Delete all conversations, messages, and related data (tool invocations,
1125
1151
  * memory segments, etc.) from the daemon database.
@@ -1547,17 +1573,19 @@ export function batchSetDisplayOrders(
1547
1573
  if (update.groupId !== undefined) {
1548
1574
  // New client: groupId is authoritative.
1549
1575
  // Derive is_pinned from groupId.
1550
- // Sanitize: if groupId references a deleted/unknown group, fall back
1551
- // to NULL to avoid FK violation that would roll back the entire batch.
1576
+ // Sanitize: if groupId is null or references a deleted/unknown group,
1577
+ // fall back to "system:all" to avoid FK violation that would roll back
1578
+ // the entire batch.
1552
1579
  let safeGroupId = update.groupId;
1553
- if (
1554
- safeGroupId !== null &&
1580
+ if (safeGroupId === null) {
1581
+ safeGroupId = "system:all";
1582
+ } else if (
1555
1583
  !rawGet<{ id: string }>(
1556
1584
  "SELECT id FROM conversation_groups WHERE id = ?",
1557
1585
  safeGroupId,
1558
1586
  )
1559
1587
  ) {
1560
- safeGroupId = null;
1588
+ safeGroupId = "system:all";
1561
1589
  }
1562
1590
  rawRun(
1563
1591
  "UPDATE conversations SET display_order = ?, is_pinned = ?, group_id = ? WHERE id = ?",
@@ -1587,7 +1615,7 @@ export function batchSetDisplayOrders(
1587
1615
  WHEN source IN ('schedule', 'reminder') THEN 'system:scheduled'
1588
1616
  WHEN source IN ('heartbeat', 'task') THEN 'system:background'
1589
1617
  WHEN conversation_type = 'background' AND COALESCE(source, '') != 'notification' THEN 'system:background'
1590
- ELSE NULL
1618
+ ELSE 'system:all'
1591
1619
  END
1592
1620
  ELSE group_id END
1593
1621
  WHERE id = ?`,
@@ -26,6 +26,45 @@ export function getConversationDirName(
26
26
  return `${getConversationDirTimestamp(createdAtMs)}_${id}`;
27
27
  }
28
28
 
29
+ /**
30
+ * Parse a canonical conversation directory name (`<ISO-with-dashes>_<id>`)
31
+ * back into its components. Returns null for names that don't match the
32
+ * canonical format — callers should treat null as "skip this entry"
33
+ * rather than as an error.
34
+ *
35
+ * Inverse of {@link getConversationDirName}. Does NOT parse the legacy
36
+ * `<id>_<ISO-with-dashes>` format.
37
+ */
38
+ export function parseConversationDirName(
39
+ name: string,
40
+ ): { conversationId: string; createdAtMs: number } | null {
41
+ // Canonical format: YYYY-MM-DDTHH-MM-SS.sssZ_<uuid>
42
+ // The timestamp portion has 4 hyphens (date + time-component separators)
43
+ // and ends in `Z`. Anchor the regex to enforce that.
44
+ const match = name.match(
45
+ /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2}\.\d{1,9}Z)_(.+)$/,
46
+ );
47
+ if (!match) return null;
48
+ const [, datePart, hh, mm, ssAndMs, conversationId] = match;
49
+ const iso = `${datePart}T${hh}:${mm}:${ssAndMs}`;
50
+ const ms = Date.parse(iso);
51
+ if (Number.isNaN(ms)) return null;
52
+ if (!conversationId) return null;
53
+ // Reject conversation IDs that are path-traversal-shaped or contain
54
+ // path separators. These are never valid conversation IDs and would
55
+ // be a defense-in-depth concern if parsed.conversationId is later used
56
+ // to construct filesystem paths.
57
+ if (
58
+ conversationId === "." ||
59
+ conversationId === ".." ||
60
+ conversationId.includes("/") ||
61
+ conversationId.includes("\\")
62
+ ) {
63
+ return null;
64
+ }
65
+ return { conversationId, createdAtMs: ms };
66
+ }
67
+
29
68
  /**
30
69
  * Return the absolute path to a conversation's timestamp-first disk-view
31
70
  * directory.
@@ -57,15 +57,45 @@ export function ensureGroupMigration(): void {
57
57
  }
58
58
  }
59
59
 
60
- // 3. Seed system groups (three: pinned, scheduled, background)
60
+ // 3. Seed system groups (four: pinned, scheduled, background, all)
61
+ const now = Math.floor(Date.now() / 1000);
61
62
  rawExec(`
62
- INSERT OR IGNORE INTO conversation_groups (id, name, sort_position, is_system_group)
63
+ INSERT OR IGNORE INTO conversation_groups (id, name, sort_position, is_system_group, created_at, updated_at)
63
64
  VALUES
64
- ('system:pinned', 'Pinned', 0, TRUE),
65
- ('system:scheduled', 'Scheduled', 1, TRUE),
66
- ('system:background', 'Background', 2, TRUE)
65
+ ('system:pinned', 'Pinned', 0, TRUE, ${now}, ${now}),
66
+ ('system:scheduled', 'Scheduled', 1, TRUE, ${now}, ${now}),
67
+ ('system:background', 'Background', 2, TRUE, ${now}, ${now}),
68
+ ('system:all', 'Recents', 3, TRUE, ${now}, ${now})
67
69
  `);
68
70
 
71
+ // One-time migration: move system:all to sortPosition 3 (from 999999).
72
+ // Bump custom groups at position 3+ up by 1 to make room. Wrapped in a
73
+ // transaction so a crash between the shift and the sentinel can't cause
74
+ // repeated drift on restart.
75
+ const sortShiftDone = rawGet<{ id: string }>(
76
+ "SELECT id FROM conversation_groups WHERE id = '_sort_shift_complete'",
77
+ );
78
+ if (!sortShiftDone) {
79
+ try {
80
+ rawExec("BEGIN");
81
+ rawRun(
82
+ "UPDATE conversation_groups SET sort_position = sort_position + 1 WHERE is_system_group = 0 AND sort_position >= 3",
83
+ );
84
+ rawRun(
85
+ "UPDATE conversation_groups SET sort_position = 3 WHERE id = 'system:all' AND sort_position != 3",
86
+ );
87
+ rawRun(
88
+ `INSERT OR IGNORE INTO conversation_groups (id, name, sort_position, is_system_group, created_at, updated_at)
89
+ VALUES ('_sort_shift_complete', '_sort_shift_complete', -1, TRUE, ${now}, ${now})`,
90
+ );
91
+ rawExec("COMMIT");
92
+ } catch (err) {
93
+ rawExec("ROLLBACK");
94
+ log.error({ err }, "Sort-position shift transaction failed, rolled back");
95
+ throw err;
96
+ }
97
+ }
98
+
69
99
  // 4. One-time backfill (guard: persistent marker prevents re-running on restart)
70
100
  //
71
101
  // The backfill sets group_id on existing conversations based on their attributes.
@@ -153,5 +183,35 @@ export function ensureGroupMigration(): void {
153
183
  }
154
184
  }
155
185
 
186
+ // 5. One-time backfill: assign all ungrouped conversations to system:all
187
+ //
188
+ // Separate from the initial backfill above because system:all is added later.
189
+ // Uses its own sentinel so it runs exactly once, even on existing installations
190
+ // where the original backfill already completed.
191
+ const allBackfillDone = rawGet<{ id: string }>(
192
+ "SELECT id FROM conversation_groups WHERE id = '_backfill_all_complete'",
193
+ );
194
+
195
+ if (!allBackfillDone) {
196
+ try {
197
+ rawExec("BEGIN");
198
+
199
+ rawExec(`
200
+ UPDATE conversations SET group_id = 'system:all' WHERE group_id IS NULL
201
+ `);
202
+
203
+ rawExec(`
204
+ INSERT OR IGNORE INTO conversation_groups (id, name, sort_position, is_system_group)
205
+ VALUES ('_backfill_all_complete', '_backfill_all_complete', -1, TRUE)
206
+ `);
207
+
208
+ rawExec("COMMIT");
209
+ } catch (err) {
210
+ rawExec("ROLLBACK");
211
+ log.error({ err }, "system:all backfill transaction failed, rolled back");
212
+ throw err;
213
+ }
214
+ }
215
+
156
216
  migrated = true;
157
217
  }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Cadence logic for conversation starters generation.
3
+ *
4
+ * Decides whether a new generation job should be enqueued based on how many
5
+ * active memory items have accumulated since the last generation.
6
+ */
7
+
8
+ import { and, eq, inArray, sql } from "drizzle-orm";
9
+
10
+ import { getLogger } from "../util/logger.js";
11
+ import { getDb } from "./db.js";
12
+ import { enqueueMemoryJob } from "./jobs-store.js";
13
+ import { rawGet } from "./raw-query.js";
14
+ import { memoryCheckpoints, memoryJobs } from "./schema.js";
15
+
16
+ const log = getLogger("conversation-starters-cadence");
17
+
18
+ /**
19
+ * Check whether enough new memory items have accumulated to justify
20
+ * generating a fresh batch of conversation starters.
21
+ */
22
+ export function maybeEnqueueConversationStartersJob(scopeId: string): void {
23
+ const db = getDb();
24
+
25
+ // Count total active memory items
26
+ const countRow = rawGet<{ c: number }>(
27
+ `SELECT COUNT(*) AS c FROM memory_graph_nodes WHERE fidelity != 'gone' AND scope_id = ?`,
28
+ scopeId,
29
+ );
30
+ const totalActive = countRow?.c ?? 0;
31
+ if (totalActive === 0) return;
32
+
33
+ // Read checkpoint: item count at last generation (scoped so each scope tracks independently)
34
+ const checkpointKey = `conversation_starters:item_count_at_last_gen:${scopeId}`;
35
+ const checkpoint = db
36
+ .select({ value: memoryCheckpoints.value })
37
+ .from(memoryCheckpoints)
38
+ .where(eq(memoryCheckpoints.key, checkpointKey))
39
+ .get();
40
+ const parsedLastCount = checkpoint ? parseInt(checkpoint.value, 10) : 0;
41
+ const lastCount = Number.isFinite(parsedLastCount) ? parsedLastCount : 0;
42
+
43
+ // Cadence formula
44
+ let threshold: number;
45
+ if (totalActive <= 10) {
46
+ threshold = 1;
47
+ } else if (totalActive <= 50) {
48
+ threshold = 5;
49
+ } else {
50
+ threshold = 10;
51
+ }
52
+
53
+ const checkpointAhead = totalActive < lastCount;
54
+ const delta = Math.max(0, totalActive - lastCount);
55
+ if (!checkpointAhead && delta < threshold) return;
56
+
57
+ // Dedup: don't enqueue if a pending/running job for this scope already exists
58
+ const existing = db
59
+ .select({ id: memoryJobs.id })
60
+ .from(memoryJobs)
61
+ .where(
62
+ and(
63
+ eq(memoryJobs.type, "generate_conversation_starters"),
64
+ inArray(memoryJobs.status, ["pending", "running"]),
65
+ sql`json_extract(${memoryJobs.payload}, '$.scopeId') = ${scopeId}`,
66
+ ),
67
+ )
68
+ .get();
69
+ if (existing) return;
70
+
71
+ enqueueMemoryJob("generate_conversation_starters", { scopeId });
72
+ log.info(
73
+ { totalActive, lastCount, delta, threshold, scopeId, checkpointAhead },
74
+ "Enqueued conversation starters generation job",
75
+ );
76
+ }
@@ -284,10 +284,13 @@ export function queueRegenerateConversationTitle(
284
284
  */
285
285
  function buildTitleSystemPrompt(): string {
286
286
  return [
287
- "You generate short conversation titles. Output ONLY the title text — no explanation, no quotes, no markdown, no preamble.",
287
+ "You generate ultra-concise conversation titles. Output ONLY the title text — no explanation, no quotes, no markdown, no preamble.",
288
288
  "",
289
289
  "Rules:",
290
- "- Summarize the TOPIC the user is asking about",
290
+ "- 2–6 words. Titles longer than 6 words are unacceptable — ruthlessly compress",
291
+ "- Summarize the TOPIC, not the request or instructions",
292
+ "- Noun phrases are ideal (e.g. 'Auth Middleware Rewrite', 'Docker Volume Mounts')",
293
+ "- Do NOT echo back what the user asked you to do",
291
294
  "- Do NOT respond to the conversation content",
292
295
  "- Do NOT assess feasibility or comment on capabilities",
293
296
  ].join("\n");
@@ -58,6 +58,7 @@ import {
58
58
  migrateContactsRolePrincipal,
59
59
  migrateContactsUserFileColumn,
60
60
  migrateConversationForkLineage,
61
+ migrateConversationHostAccess,
61
62
  migrateConversationsLastMessageAt,
62
63
  migrateConversationsThreadTypeIndex,
63
64
  migrateCreateConversationGraphMemoryState,
@@ -110,9 +111,14 @@ import {
110
111
  migrateOAuthProvidersBehaviorColumns,
111
112
  migrateOAuthProvidersDisplayMetadata,
112
113
  migrateOAuthProvidersFeatureFlag,
114
+ migrateOAuthProvidersLogoUrl,
113
115
  migrateOAuthProvidersManagedServiceConfigKey,
114
116
  migrateOAuthProvidersPingConfig,
115
117
  migrateOAuthProvidersPingUrl,
118
+ migrateOAuthProvidersRefreshUrl,
119
+ migrateOAuthProvidersRevoke,
120
+ migrateOAuthProvidersScopeSeparator,
121
+ migrateOAuthProvidersTokenAuthMethodDefault,
116
122
  migrateReminderRoutingIntent,
117
123
  migrateRemindersToSchedules,
118
124
  migrateRenameConversationTypeColumn,
@@ -352,6 +358,12 @@ export function initializeDb(): void {
352
358
  migrateScheduleReuseConversation,
353
359
  migrateMemoryRecallLogsQueryContext,
354
360
  migrateLlmRequestLogsCreatedAtIndex,
361
+ migrateOAuthProvidersScopeSeparator,
362
+ migrateOAuthProvidersRefreshUrl,
363
+ migrateOAuthProvidersRevoke,
364
+ migrateOAuthProvidersTokenAuthMethodDefault,
365
+ migrateConversationHostAccess,
366
+ migrateOAuthProvidersLogoUrl,
355
367
  ];
356
368
 
357
369
  // Run each migration step, catching and logging individual failures so one