@vellumai/assistant 0.11.2 → 0.11.3-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (720) hide show
  1. package/ARCHITECTURE.md +58 -3
  2. package/docs/trusted-contact-access.md +9 -5
  3. package/docs/vellum-doctor.md +195 -0
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  7. package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
  8. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +19 -2
  9. package/node_modules/@vellumai/environments/package.json +2 -1
  10. package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
  11. package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
  12. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
  13. package/node_modules/@vellumai/environments/src/index.ts +17 -6
  14. package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
  15. package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
  16. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  19. package/node_modules/@vellumai/gateway-client/src/index.ts +4 -0
  20. package/node_modules/@vellumai/gateway-client/src/verification-session-contract.ts +91 -6
  21. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
  22. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
  23. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +2 -0
  24. package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
  25. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +15 -1
  26. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +17 -2
  27. package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  28. package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  29. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  30. package/node_modules/@vellumai/slack-text/src/index.ts +16 -2
  31. package/openapi.yaml +589 -30
  32. package/package.json +1 -1
  33. package/src/__tests__/agent-image-optimize.test.ts +4 -4
  34. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +113 -66
  35. package/src/__tests__/agent-loop-override-profile.test.ts +38 -48
  36. package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +166 -0
  37. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  38. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  39. package/src/__tests__/answered-question-persistence.test.ts +240 -0
  40. package/src/__tests__/anthropic-provider.test.ts +165 -30
  41. package/src/__tests__/app-control-flow.test.ts +9 -8
  42. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  43. package/src/__tests__/attachments-store-heic-normalize.test.ts +16 -12
  44. package/src/__tests__/attachments-store.test.ts +130 -68
  45. package/src/__tests__/attachments.test.ts +36 -36
  46. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  47. package/src/__tests__/call-start-guardian-guard.test.ts +18 -4
  48. package/src/__tests__/canned-reply-release.test.ts +86 -0
  49. package/src/__tests__/channel-guardian.test.ts +59 -29
  50. package/src/__tests__/channel-readiness-telegram-remote.test.ts +154 -0
  51. package/src/__tests__/channel-setup-panel-ack.test.ts +6 -5
  52. package/src/__tests__/client-os-metadata-persistence.test.ts +103 -4
  53. package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
  54. package/src/__tests__/consult-deadline.test.ts +65 -0
  55. package/src/__tests__/conversation-agent-loop.test.ts +438 -2
  56. package/src/__tests__/conversation-disk-view-integration.test.ts +5 -1
  57. package/src/__tests__/conversation-disk-view.test.ts +4 -4
  58. package/src/__tests__/conversation-error.test.ts +22 -3
  59. package/src/__tests__/conversation-fork-crud.test.ts +5 -1
  60. package/src/__tests__/conversation-fork-referential.test.ts +352 -0
  61. package/src/__tests__/conversation-fork-retrospective.test.ts +5 -1
  62. package/src/__tests__/conversation-group-tools.test.ts +32 -2
  63. package/src/__tests__/conversation-inference-profile-route.test.ts +27 -1
  64. package/src/__tests__/conversation-initial-prompt.test.ts +51 -22
  65. package/src/__tests__/conversation-key-store-bootstrap-cleanup.test.ts +119 -0
  66. package/src/__tests__/conversation-key-store-origin.test.ts +105 -0
  67. package/src/__tests__/conversation-list-recency-ordering.test.ts +104 -0
  68. package/src/__tests__/conversation-origin-at-creation.test.ts +153 -0
  69. package/src/__tests__/conversation-origin-channel-filter.test.ts +95 -0
  70. package/src/__tests__/conversation-placement-promotion.test.ts +172 -0
  71. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +123 -0
  72. package/src/__tests__/conversation-queue.test.ts +433 -1
  73. package/src/__tests__/conversation-retry-route.test.ts +3 -1
  74. package/src/__tests__/conversation-routes-hidden-queue.test.ts +64 -1
  75. package/src/__tests__/conversation-store-ephemeral.test.ts +4 -0
  76. package/src/__tests__/conversation-store.test.ts +26 -10
  77. package/src/__tests__/conversation-summarize-route.test.ts +1 -1
  78. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +15 -6
  79. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -5
  80. package/src/__tests__/conversation-surfaces-app-control.test.ts +12 -11
  81. package/src/__tests__/conversation-surfaces-app-open.test.ts +11 -5
  82. package/src/__tests__/conversation-surfaces-data-persist.test.ts +5 -4
  83. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +967 -0
  84. package/src/__tests__/conversation-surfaces-persisted-info.test.ts +312 -0
  85. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +6 -5
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -8
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +21 -11
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +7 -11
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -6
  90. package/src/__tests__/conversation-surfaces-task-progress.test.ts +11 -6
  91. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -6
  92. package/src/__tests__/conversation-tool-setup-attribution.test.ts +70 -7
  93. package/src/__tests__/conversation-wait-for-idle.test.ts +6 -11
  94. package/src/__tests__/credential-prompt-route.test.ts +57 -7
  95. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  96. package/src/__tests__/cu-unified-flow.test.ts +89 -28
  97. package/src/__tests__/custom-group-listing-visibility.test.ts +1 -1
  98. package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
  99. package/src/__tests__/discord-access-request-privacy.test.ts +366 -0
  100. package/src/__tests__/discord-requester-notice-privacy.test.ts +87 -0
  101. package/src/__tests__/disk-pressure-guard.test.ts +55 -55
  102. package/src/__tests__/disk-pressure-routes.test.ts +10 -10
  103. package/src/__tests__/disk-pressure-tools.test.ts +2 -2
  104. package/src/__tests__/disk-usage.test.ts +35 -20
  105. package/src/__tests__/document-sync-tags.test.ts +379 -0
  106. package/src/__tests__/document-update-default-surface.test.ts +28 -0
  107. package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
  108. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
  109. package/src/__tests__/ensure-conversation-exists.test.ts +8 -5
  110. package/src/__tests__/events-dev-bypass-actor.test.ts +183 -1
  111. package/src/__tests__/evict-conversations-for-reload.test.ts +90 -0
  112. package/src/__tests__/file-list-tool.test.ts +12 -10
  113. package/src/__tests__/file-ops-service.test.ts +151 -44
  114. package/src/__tests__/file-read-container-boundary.test.ts +137 -0
  115. package/src/__tests__/file-read-tool.test.ts +21 -9
  116. package/src/__tests__/file-url-path-guard.test.ts +83 -0
  117. package/src/__tests__/filesystem-tools.test.ts +82 -66
  118. package/src/__tests__/guardian-outbound-http.test.ts +8 -8
  119. package/src/__tests__/helpers/gateway-verification-sessions-stub.ts +14 -1
  120. package/src/__tests__/helpers/hub-clients.ts +42 -0
  121. package/src/__tests__/helpers/mock-conversation.ts +46 -0
  122. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +61 -57
  123. package/src/__tests__/history-repair.test.ts +112 -12
  124. package/src/__tests__/host-bash-proxy.test.ts +10 -5
  125. package/src/__tests__/host-bash-routes.test.ts +67 -0
  126. package/src/__tests__/host-browser-proxy.test.ts +2 -2
  127. package/src/__tests__/host-cu-proxy.test.ts +3 -7
  128. package/src/__tests__/host-file-proxy.test.ts +3 -7
  129. package/src/__tests__/host-file-read-tool.test.ts +5 -1
  130. package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
  131. package/src/__tests__/http-conversation-lineage.test.ts +2 -5
  132. package/src/__tests__/http-user-message-parity.test.ts +44 -0
  133. package/src/__tests__/identity-routes.test.ts +13 -13
  134. package/src/__tests__/image-conversion.test.ts +29 -26
  135. package/src/__tests__/image-source-path-reinject.test.ts +2 -2
  136. package/src/__tests__/inference-profile-session-handler.test.ts +95 -0
  137. package/src/__tests__/inference-profile-session-ipc.test.ts +25 -0
  138. package/src/__tests__/introduction-card-resolver.test.ts +6 -1
  139. package/src/__tests__/list-messages-attachments.test.ts +56 -32
  140. package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
  141. package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
  142. package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
  143. package/src/__tests__/list-messages-page-latest.test.ts +61 -61
  144. package/src/__tests__/list-messages-provider-error.test.ts +6 -6
  145. package/src/__tests__/list-messages-queued.test.ts +49 -6
  146. package/src/__tests__/list-messages-system-card.test.ts +4 -4
  147. package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
  148. package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
  149. package/src/__tests__/llm-usage-store.test.ts +55 -0
  150. package/src/__tests__/manual-token-reconciliation.test.ts +45 -0
  151. package/src/__tests__/mcp-tool-annotations-risk.test.ts +133 -0
  152. package/src/__tests__/media-stream-server-integration.test.ts +36 -0
  153. package/src/__tests__/migration-export-http.test.ts +1 -1
  154. package/src/__tests__/migration-import-commit-http.test.ts +1 -1
  155. package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
  156. package/src/__tests__/migration-validate-http.test.ts +1 -1
  157. package/src/__tests__/mtime-cache.test.ts +65 -3
  158. package/src/__tests__/notification-platform-adapter.test.ts +104 -0
  159. package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
  160. package/src/__tests__/oauth-provider-profiles.test.ts +4 -2
  161. package/src/__tests__/oauth-provider-seed-logos.test.ts +65 -0
  162. package/src/__tests__/oauth-provider-serializer.test.ts +17 -0
  163. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  164. package/src/__tests__/openai-responses-prompt-cache.test.ts +5 -2
  165. package/src/__tests__/path-policy.test.ts +164 -1
  166. package/src/__tests__/persist-media-references.test.ts +2 -2
  167. package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
  168. package/src/__tests__/persist-unsendable-image.test.ts +23 -23
  169. package/src/__tests__/persona-resolver.test.ts +154 -0
  170. package/src/__tests__/plugin-api-webhook-url.test.ts +170 -0
  171. package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
  172. package/src/__tests__/processing-flag-persist-failure.test.ts +15 -10
  173. package/src/__tests__/profile-availability-incomplete.test.ts +113 -0
  174. package/src/__tests__/profiler-routes.test.ts +13 -13
  175. package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +593 -0
  176. package/src/__tests__/proxy-approval-callback.test.ts +3 -3
  177. package/src/__tests__/queued-message-cancel-and-steer.test.ts +189 -0
  178. package/src/__tests__/queued-message-steer-actor-scoping.test.ts +164 -0
  179. package/src/__tests__/resolve-trust-class.test.ts +20 -0
  180. package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -1
  181. package/src/__tests__/run-due-schedules.test.ts +174 -0
  182. package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
  183. package/src/__tests__/scaffold-managed-skill-tool.test.ts +166 -0
  184. package/src/__tests__/schedule-routes.test.ts +730 -8
  185. package/src/__tests__/schedule-store.test.ts +540 -0
  186. package/src/__tests__/schedule-tools.test.ts +103 -0
  187. package/src/__tests__/scheduler-wake.test.ts +5 -0
  188. package/src/__tests__/secret-routes-platform-proxy.test.ts +29 -1
  189. package/src/__tests__/secure-keys.test.ts +91 -0
  190. package/src/__tests__/server-history-render.test.ts +97 -7
  191. package/src/__tests__/size-guard.test.ts +8 -8
  192. package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
  193. package/src/__tests__/skill-tool-factory.test.ts +247 -0
  194. package/src/__tests__/skills-install-staging.test.ts +15 -6
  195. package/src/__tests__/slack-edit-ordering-characterization.test.ts +103 -0
  196. package/src/__tests__/slack-inbound-verification.test.ts +4 -1
  197. package/src/__tests__/slack-mention-binding-invariance.test.ts +103 -0
  198. package/src/__tests__/slack-mention-provider-leak-audit.test.ts +135 -0
  199. package/src/__tests__/starter-task-flow.test.ts +5 -4
  200. package/src/__tests__/steer-on-enqueue-question.test.ts +3 -2
  201. package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
  202. package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
  203. package/src/__tests__/subagent-cron-run-attribution.test.ts +301 -0
  204. package/src/__tests__/subagent-disposal.test.ts +10 -0
  205. package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
  206. package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
  207. package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
  208. package/src/__tests__/subagent-manager-notify.test.ts +148 -0
  209. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  210. package/src/__tests__/subagent-persistence.test.ts +141 -0
  211. package/src/__tests__/subagent-role-registry.test.ts +172 -63
  212. package/src/__tests__/subagent-role-resolution.test.ts +88 -0
  213. package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
  214. package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
  215. package/src/__tests__/subagent-terminal-message.test.ts +88 -1
  216. package/src/__tests__/subagent-tool-gate-mode.test.ts +225 -10
  217. package/src/__tests__/subagent-tools.test.ts +1901 -108
  218. package/src/__tests__/surface-completion-compaction-boundary.test.ts +124 -0
  219. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +298 -0
  220. package/src/__tests__/system-prompt.test.ts +82 -7
  221. package/src/__tests__/tool-grant-request-escalation.test.ts +40 -1
  222. package/src/__tests__/tool-result-spool.test.ts +74 -18
  223. package/src/__tests__/tool-side-effects-documents.test.ts +174 -0
  224. package/src/__tests__/tool-side-effects-hook-failures.test.ts +129 -0
  225. package/src/__tests__/tools-audio-read.test.ts +10 -10
  226. package/src/__tests__/tools-get-route.test.ts +85 -12
  227. package/src/__tests__/tools-list-cli.test.ts +171 -0
  228. package/src/__tests__/top-level-renderer.test.ts +54 -1
  229. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +68 -1
  230. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -1
  231. package/src/__tests__/turn-tail-chain.test.ts +135 -0
  232. package/src/__tests__/ui-choice-copy-surfaces.test.ts +6 -4
  233. package/src/__tests__/ui-visual-surface.test.ts +5 -4
  234. package/src/__tests__/ui-work-result-surface.test.ts +5 -4
  235. package/src/__tests__/unified-turn-context-visible-app.test.ts +7 -8
  236. package/src/__tests__/usage-routes.test.ts +62 -0
  237. package/src/__tests__/verification-outbound-delivery.test.ts +176 -0
  238. package/src/__tests__/visible-app-context.test.ts +20 -4
  239. package/src/__tests__/web-search-history.test.ts +96 -0
  240. package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +6 -6
  241. package/src/__tests__/workspace-migration-140-repair-seed-pinned-memory-v3-live.test.ts +330 -0
  242. package/src/acp/failure-error.ts +3 -5
  243. package/src/agent/attachments.ts +26 -24
  244. package/src/agent/history-repair/history-repair.ts +27 -26
  245. package/src/agent/image-optimize.ts +6 -6
  246. package/src/agent/loop.ts +40 -22
  247. package/src/agent/message-types.ts +3 -3
  248. package/src/api/constants/document-tools.ts +55 -0
  249. package/src/api/events/question-answered.ts +53 -0
  250. package/src/api/events/tool-result.ts +7 -0
  251. package/src/api/index.ts +13 -0
  252. package/src/api/responses/conversation-message.ts +11 -0
  253. package/src/api/surfaces-oauth-connect.test.ts +96 -0
  254. package/src/api/surfaces.ts +36 -8
  255. package/src/approvals/guardian-channel-delivery.ts +83 -0
  256. package/src/approvals/guardian-expiry-notifier.ts +13 -11
  257. package/src/approvals/guardian-request-resolvers.ts +222 -135
  258. package/src/calls/__tests__/voice-session-bridge.test.ts +50 -0
  259. package/src/calls/media-stream-server.ts +9 -1
  260. package/src/calls/voice-session-bridge.ts +38 -0
  261. package/src/channels/__tests__/gateway-verification-sessions.test.ts +3 -3
  262. package/src/channels/config.ts +7 -2
  263. package/src/channels/gateway-verification-sessions.ts +7 -2
  264. package/src/channels/types.ts +7 -1
  265. package/src/cli/commands/__tests__/cache.test.ts +7 -4
  266. package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
  267. package/src/cli/commands/__tests__/conversations-wake.test.ts +51 -6
  268. package/src/cli/commands/__tests__/inference-models.test.ts +14 -0
  269. package/src/cli/commands/__tests__/inference-profiles.test.ts +44 -1
  270. package/src/cli/commands/__tests__/inference-providers.test.ts +7 -0
  271. package/src/cli/commands/__tests__/plugins.test.ts +692 -0
  272. package/src/cli/commands/__tests__/schedules.test.ts +136 -37
  273. package/src/cli/commands/conversations.ts +7 -3
  274. package/src/cli/commands/credentials.help.ts +7 -0
  275. package/src/cli/commands/inference-profiles.ts +9 -3
  276. package/src/cli/commands/inference.help.ts +16 -2
  277. package/src/cli/commands/mcp.help.ts +4 -2
  278. package/src/cli/commands/memory/memory-retrospective.ts +18 -0
  279. package/src/cli/commands/notifications.ts +3 -6
  280. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +16 -0
  281. package/src/cli/commands/oauth/connect-surface-guidance.ts +25 -7
  282. package/src/cli/commands/oauth/connect.test.ts +26 -1
  283. package/src/cli/commands/oauth/connect.ts +4 -1
  284. package/src/cli/commands/oauth/index.help.ts +3 -1
  285. package/src/cli/commands/oauth/providers.ts +3 -0
  286. package/src/cli/commands/plugins.help.ts +25 -2
  287. package/src/cli/commands/plugins.ts +289 -10
  288. package/src/cli/commands/schedules.help.ts +14 -12
  289. package/src/cli/commands/schedules.ts +19 -5
  290. package/src/cli/commands/tools.help.ts +4 -2
  291. package/src/cli/commands/tools.ts +38 -0
  292. package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
  293. package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
  294. package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
  295. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
  296. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
  297. package/src/cli/lib/install-from-github.ts +61 -0
  298. package/src/cli/lib/install-from-platform.ts +13 -2
  299. package/src/cli/lib/plugin-surfaces.ts +124 -4
  300. package/src/cli/lib/upgrade-plugin.ts +18 -0
  301. package/src/cli.ts +1 -1
  302. package/src/config/__tests__/deployment-context-defaults.test.ts +73 -0
  303. package/src/config/__tests__/memory-retrospective-schema.test.ts +40 -0
  304. package/src/config/__tests__/plugin-updates-schema.test.ts +43 -0
  305. package/src/config/__tests__/profile-tool-support.test.ts +131 -0
  306. package/src/config/bundled-skills/AGENTS.md +1 -1
  307. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  308. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +3 -3
  309. package/src/config/bundled-skills/schedule/SKILL.md +5 -1
  310. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  311. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  312. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
  313. package/src/config/bundled-skills/subagent/SKILL.md +53 -22
  314. package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
  315. package/src/config/bundled-skills/visualize/SKILL.md +1 -1
  316. package/src/config/default-profile-catalog.ts +6 -2
  317. package/src/config/feature-flag-registry.json +25 -17
  318. package/src/config/llm-resolver.ts +45 -15
  319. package/src/config/loader.ts +70 -1
  320. package/src/config/profile-tool-support.ts +110 -0
  321. package/src/config/sanitize-for-transfer.ts +11 -0
  322. package/src/config/schema.ts +4 -0
  323. package/src/config/schemas/__tests__/stt.test.ts +20 -5
  324. package/src/config/schemas/channels.ts +13 -0
  325. package/src/config/schemas/memory-retrospective.ts +25 -0
  326. package/src/config/schemas/plugin-updates.ts +55 -0
  327. package/src/config/schemas/services.ts +8 -4
  328. package/src/config/schemas/stt.ts +17 -5
  329. package/src/config/schemas/timeouts.ts +1 -1
  330. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +16 -0
  331. package/src/contacts/guardian-delivery-reader.ts +29 -2
  332. package/src/context/compactor.ts +4 -4
  333. package/src/context/post-turn-tool-result-truncation.ts +32 -16
  334. package/src/context/strip-injections.ts +42 -0
  335. package/src/context/tool-result-spool.ts +32 -18
  336. package/src/credential-execution/ces-runtime.ts +3 -0
  337. package/src/credential-execution/client.ts +22 -1
  338. package/src/credential-execution/process-manager.test.ts +99 -0
  339. package/src/credential-execution/process-manager.ts +62 -16
  340. package/src/daemon/__tests__/abort-null-controller.test.ts +141 -5
  341. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +8 -12
  342. package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
  343. package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +2 -4
  344. package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +20 -29
  345. package/src/daemon/channel-ui-capability.ts +4 -4
  346. package/src/daemon/conversation-agent-loop-handlers.ts +117 -61
  347. package/src/daemon/conversation-agent-loop.ts +318 -148
  348. package/src/daemon/conversation-attachments.ts +1 -1
  349. package/src/daemon/conversation-error.ts +8 -1
  350. package/src/daemon/conversation-initial-prompt.ts +16 -38
  351. package/src/daemon/conversation-lifecycle.ts +154 -32
  352. package/src/daemon/conversation-messaging.ts +106 -43
  353. package/src/daemon/conversation-process.ts +131 -33
  354. package/src/daemon/conversation-queue-manager.ts +10 -0
  355. package/src/daemon/conversation-runtime-assembly.ts +9 -4
  356. package/src/daemon/conversation-store.ts +52 -3
  357. package/src/daemon/conversation-surface-state.ts +9 -0
  358. package/src/daemon/conversation-surfaces.ts +403 -210
  359. package/src/daemon/conversation-tool-setup.ts +56 -7
  360. package/src/daemon/conversation-turn-finalize.ts +134 -89
  361. package/src/daemon/conversation.ts +111 -27
  362. package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
  363. package/src/daemon/disk-pressure-guard.ts +3 -3
  364. package/src/daemon/doordash-steps.ts +5 -5
  365. package/src/daemon/external-plugins-bootstrap.ts +7 -1
  366. package/src/daemon/handlers/config-channels.ts +48 -5
  367. package/src/daemon/handlers/conversation-history.ts +1 -1
  368. package/src/daemon/handlers/conversations.ts +62 -17
  369. package/src/daemon/handlers/shared.ts +12 -0
  370. package/src/daemon/handlers/skills.ts +11 -1
  371. package/src/daemon/identity-helpers.ts +38 -10
  372. package/src/daemon/install-assistant-command.ts +322 -0
  373. package/src/daemon/lifecycle.ts +14 -3
  374. package/src/daemon/message-types/conversations.ts +6 -7
  375. package/src/daemon/message-types/sync.ts +1 -0
  376. package/src/daemon/persist-media-references.ts +23 -14
  377. package/src/daemon/process-message.ts +6 -6
  378. package/src/daemon/tool-setup-types.ts +19 -122
  379. package/src/daemon/tool-side-effects.ts +17 -94
  380. package/src/daemon/trust-context-types.ts +39 -0
  381. package/src/daemon/trust-context.ts +14 -6
  382. package/src/daemon/turn-tail-chain.ts +73 -0
  383. package/src/daemon/web-search-history.ts +47 -33
  384. package/src/heartbeat/heartbeat-service.ts +6 -6
  385. package/src/ipc/__tests__/socket-path.test.ts +21 -0
  386. package/src/ipc/assistant-server.ts +17 -10
  387. package/src/ipc/cli-client.ts +10 -1
  388. package/src/ipc/routes/__tests__/documents-sync-ipc-routes.test.ts +56 -0
  389. package/src/ipc/routes/documents-sync-ipc-routes.ts +42 -0
  390. package/src/ipc/socket-cleanup.ts +9 -7
  391. package/src/ipc/socket-path.ts +4 -96
  392. package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
  393. package/src/live-voice/__tests__/live-voice-attach-image.test.ts +209 -0
  394. package/src/live-voice/__tests__/live-voice-connection.test.ts +20 -0
  395. package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
  396. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +265 -0
  397. package/src/live-voice/__tests__/live-voice-stt.test.ts +6 -1
  398. package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
  399. package/src/live-voice/__tests__/protocol.test.ts +52 -0
  400. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +3 -0
  401. package/src/live-voice/live-voice-archive.ts +13 -13
  402. package/src/live-voice/live-voice-connection.ts +7 -1
  403. package/src/live-voice/live-voice-photo.ts +169 -0
  404. package/src/live-voice/live-voice-session.ts +108 -4
  405. package/src/live-voice/protocol.ts +89 -1
  406. package/src/mcp/client.ts +10 -0
  407. package/src/messaging/providers/__tests__/callback-routing.test.ts +5 -1
  408. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +66 -4
  409. package/src/messaging/providers/callback-routing.ts +1 -0
  410. package/src/messaging/providers/discord/api.ts +312 -0
  411. package/src/messaging/providers/discord/dm-delivery.test.ts +204 -0
  412. package/src/messaging/providers/discord/render.test.ts +204 -0
  413. package/src/messaging/providers/discord/render.ts +195 -0
  414. package/src/messaging/providers/discord/send.test.ts +179 -0
  415. package/src/messaging/providers/discord/send.ts +212 -0
  416. package/src/messaging/providers/discord/transport.ts +81 -0
  417. package/src/messaging/providers/index.ts +3 -1
  418. package/src/messaging/providers/retry-policy.test.ts +247 -0
  419. package/src/messaging/providers/retry-policy.ts +206 -0
  420. package/src/messaging/providers/slack/mention-source.test.ts +285 -0
  421. package/src/messaging/providers/slack/mention-source.ts +287 -0
  422. package/src/messaging/providers/telegram-bot/api.ts +47 -130
  423. package/src/messaging/providers/whatsapp/api.ts +47 -136
  424. package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
  425. package/src/monitoring/__tests__/plugin-auto-update.test.ts +332 -0
  426. package/src/monitoring/db-integrity-sample.ts +6 -1
  427. package/src/monitoring/plugin-auto-update.ts +359 -0
  428. package/src/monitoring/resource-sampler.ts +21 -5
  429. package/src/monitoring/worker.ts +13 -0
  430. package/src/notifications/__tests__/assistant-reply-producer.test.ts +375 -2
  431. package/src/notifications/__tests__/broadcaster.test.ts +37 -1
  432. package/src/notifications/__tests__/copy-composer.test.ts +165 -0
  433. package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
  434. package/src/notifications/__tests__/notification-utils.test.ts +249 -0
  435. package/src/notifications/adapters/platform.ts +31 -2
  436. package/src/notifications/assistant-reply-producer.ts +79 -32
  437. package/src/notifications/broadcaster.ts +30 -10
  438. package/src/notifications/copy-composer.ts +69 -0
  439. package/src/notifications/emit-signal.ts +82 -15
  440. package/src/notifications/events-store.ts +10 -2
  441. package/src/notifications/home-feed-side-effect.ts +6 -3
  442. package/src/notifications/notification-utils.ts +173 -5
  443. package/src/notifications/signal.ts +12 -0
  444. package/src/oauth/AGENTS.md +4 -2
  445. package/src/oauth/byo-connection.ts +6 -1
  446. package/src/oauth/credential-token-resolver.ts +3 -10
  447. package/src/oauth/manual-token-connection.ts +29 -64
  448. package/src/oauth/manual-token-providers.ts +62 -0
  449. package/src/oauth/provider-serializer.ts +13 -0
  450. package/src/oauth/seed-providers.ts +62 -11
  451. package/src/onboarding/onboarding-events-store.ts +91 -1
  452. package/src/permissions/question-prompter.test.ts +4 -0
  453. package/src/permissions/question-prompter.ts +21 -4
  454. package/src/persistence/__tests__/conversation-lineage.test.ts +248 -0
  455. package/src/persistence/attachments-store.ts +15 -15
  456. package/src/persistence/conversation-attention-store.ts +30 -10
  457. package/src/persistence/conversation-bootstrap.ts +17 -0
  458. package/src/persistence/conversation-crud.ts +363 -97
  459. package/src/persistence/conversation-group-migration.ts +1 -1
  460. package/src/persistence/conversation-key-store.ts +38 -10
  461. package/src/persistence/conversation-lineage.ts +226 -0
  462. package/src/persistence/conversation-plugin-facade.ts +28 -3
  463. package/src/persistence/conversation-queries.ts +207 -33
  464. package/src/persistence/conversation-types.ts +107 -1
  465. package/src/persistence/jobs-store.ts +32 -8
  466. package/src/persistence/llm-usage-store.ts +22 -0
  467. package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +168 -0
  468. package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +76 -0
  469. package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
  470. package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
  471. package/src/persistence/migrations/363-backfill-schedule-inference-profile.test.ts +229 -0
  472. package/src/persistence/migrations/363-backfill-schedule-inference-profile.ts +127 -0
  473. package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
  474. package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
  475. package/src/persistence/migrations/365-add-conversation-fork-strategy.ts +27 -0
  476. package/src/persistence/schema/conversations.ts +32 -0
  477. package/src/persistence/schema/infrastructure.ts +3 -0
  478. package/src/persistence/steps.ts +30 -0
  479. package/src/persistence/subagent-store.ts +148 -6
  480. package/src/platform/managed-speech.test.ts +20 -0
  481. package/src/platform/managed-speech.ts +9 -0
  482. package/src/plugin-api/index.ts +8 -0
  483. package/src/plugin-api/webhook-url.ts +151 -0
  484. package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
  485. package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
  486. package/src/plugins/collect-source-versions.ts +4 -27
  487. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
  488. package/src/plugins/defaults/exploration-drift/package.json +1 -1
  489. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
  490. package/src/plugins/defaults/image-recovery/recover.ts +67 -57
  491. package/src/plugins/defaults/index.ts +1 -1
  492. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +64 -18
  493. package/src/plugins/defaults/memory/__tests__/jobs-worker-outcome-contract.test.ts +242 -0
  494. package/src/plugins/defaults/memory/__tests__/jobs-worker-retrospective-sweep.test.ts +24 -1
  495. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +48 -3
  496. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +511 -19
  497. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +39 -3
  498. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +493 -0
  499. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +5 -4
  500. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -1
  501. package/src/plugins/defaults/memory/job-handlers.ts +87 -5
  502. package/src/plugins/defaults/memory/jobs-worker.ts +60 -6
  503. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +11 -0
  504. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +29 -0
  505. package/src/plugins/defaults/memory/memory-retrospective-job.ts +342 -60
  506. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +23 -3
  507. package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +9 -4
  508. package/src/plugins/defaults/memory/substrate/sweep-job.ts +7 -0
  509. package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
  510. package/src/plugins/defaults/memory/v2/router.ts +8 -0
  511. package/src/plugins/defaults/turn-context/unified-turn-context.ts +1 -2
  512. package/src/plugins/external-plugin-loader.ts +17 -8
  513. package/src/plugins/installed-plugin-dirs.ts +101 -0
  514. package/src/plugins/mtime-cache.ts +34 -22
  515. package/src/plugins/types.ts +69 -8
  516. package/src/prompts/persona-resolver.ts +6 -2
  517. package/src/prompts/system-prompt.ts +72 -17
  518. package/src/providers/__tests__/inference.test.ts +40 -0
  519. package/src/providers/__tests__/preflight-resolved-config.test.ts +58 -1
  520. package/src/providers/__tests__/retry-callsite.test.ts +67 -0
  521. package/src/providers/__tests__/vellum-connection-routing.test.ts +42 -11
  522. package/src/providers/anthropic/client.ts +112 -95
  523. package/src/providers/connection-resolution.ts +35 -1
  524. package/src/providers/gemini/client.ts +5 -5
  525. package/src/providers/inference/adapter-factory.ts +16 -10
  526. package/src/providers/inference/auth.ts +16 -0
  527. package/src/providers/inference/connection-availability.ts +164 -12
  528. package/src/providers/inference/connections.ts +25 -1
  529. package/src/providers/media-resolve.ts +21 -11
  530. package/src/providers/model-catalog.ts +15 -0
  531. package/src/providers/openai/chat-completions-provider.ts +4 -4
  532. package/src/providers/openai/responses-provider.ts +12 -25
  533. package/src/providers/registry.ts +9 -4
  534. package/src/providers/retry.ts +25 -0
  535. package/src/providers/server-tool-pairing.ts +134 -0
  536. package/src/providers/speech-to-text/__tests__/resolve.test.ts +187 -6
  537. package/src/providers/speech-to-text/deepgram.ts +1 -0
  538. package/src/providers/speech-to-text/resolve.ts +80 -10
  539. package/src/providers/speech-to-text/vellum-managed.ts +7 -1
  540. package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
  541. package/src/providers/types.ts +26 -6
  542. package/src/providers/vellum-model-routing.ts +6 -7
  543. package/src/runtime/AGENTS.md +2 -0
  544. package/src/runtime/__tests__/agent-wake.test.ts +16 -16
  545. package/src/runtime/__tests__/background-job-runner.test.ts +0 -144
  546. package/src/runtime/__tests__/desktop-presence.test.ts +162 -0
  547. package/src/runtime/agent-wake.ts +63 -24
  548. package/src/runtime/assistant-event-hub.ts +87 -12
  549. package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
  550. package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
  551. package/src/runtime/auth/same-actor.ts +56 -8
  552. package/src/runtime/auth/scopes.ts +12 -0
  553. package/src/runtime/auth/types.ts +2 -0
  554. package/src/runtime/background-job-runner.ts +5 -27
  555. package/src/runtime/channel-readiness-service.ts +59 -3
  556. package/src/runtime/channel-readiness-types.ts +18 -0
  557. package/src/runtime/desktop-presence.ts +69 -0
  558. package/src/runtime/http-server.ts +12 -0
  559. package/src/runtime/local-actor-identity.ts +11 -9
  560. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  561. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  562. package/src/runtime/routes/__tests__/client-routes.test.ts +186 -14
  563. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +547 -1
  564. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +162 -7
  565. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
  566. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +340 -3
  567. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +28 -1
  568. package/src/runtime/routes/__tests__/plugins-routes.test.ts +173 -1
  569. package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
  570. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +20 -0
  571. package/src/runtime/routes/__tests__/skill-history-route.test.ts +116 -0
  572. package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +60 -2
  573. package/src/runtime/routes/archive-utils.ts +16 -12
  574. package/src/runtime/routes/attachment-routes.ts +13 -6
  575. package/src/runtime/routes/canned-reply-release.ts +54 -0
  576. package/src/runtime/routes/channel-verification-routes.ts +2 -27
  577. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +5 -0
  578. package/src/runtime/routes/client-routes.ts +114 -2
  579. package/src/runtime/routes/conversation-cli-routes.ts +10 -12
  580. package/src/runtime/routes/conversation-list-routes.ts +59 -10
  581. package/src/runtime/routes/conversation-management-routes.ts +47 -18
  582. package/src/runtime/routes/conversation-query-routes.ts +32 -53
  583. package/src/runtime/routes/conversation-routes.ts +360 -293
  584. package/src/runtime/routes/credential-prompt-routes.ts +23 -2
  585. package/src/runtime/routes/debug-routes.ts +4 -1
  586. package/src/runtime/routes/default-provider-routes.ts +7 -11
  587. package/src/runtime/routes/documents-routes.ts +44 -1
  588. package/src/runtime/routes/events-routes.ts +28 -1
  589. package/src/runtime/routes/host-app-control-routes.ts +2 -0
  590. package/src/runtime/routes/host-bash-routes.ts +2 -0
  591. package/src/runtime/routes/host-browser-routes.ts +2 -0
  592. package/src/runtime/routes/host-cu-routes.ts +2 -0
  593. package/src/runtime/routes/host-file-routes.ts +2 -0
  594. package/src/runtime/routes/host-transfer-routes.ts +4 -0
  595. package/src/runtime/routes/identity-routes.ts +6 -6
  596. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  597. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -3
  598. package/src/runtime/routes/inbound-stages/background-dispatch.ts +14 -0
  599. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -2
  600. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +14 -11
  601. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -17
  602. package/src/runtime/routes/inference-profile-availability-guard.ts +171 -0
  603. package/src/runtime/routes/inference-profile-session-handler.ts +26 -2
  604. package/src/runtime/routes/inference-profile-session-routes.ts +2 -1
  605. package/src/runtime/routes/inference-profiles-routes.ts +152 -73
  606. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -16
  607. package/src/runtime/routes/llm-call-sites-routes.ts +13 -0
  608. package/src/runtime/routes/log-export-routes.ts +1 -1
  609. package/src/runtime/routes/notification-routes.ts +0 -14
  610. package/src/runtime/routes/oauth-providers.ts +1 -0
  611. package/src/runtime/routes/plugins-routes.ts +106 -1
  612. package/src/runtime/routes/profiler-routes.ts +4 -2
  613. package/src/runtime/routes/question-routes.ts +28 -10
  614. package/src/runtime/routes/retrospective-routes.ts +9 -3
  615. package/src/runtime/routes/schedule-routes.ts +210 -12
  616. package/src/runtime/routes/secret-routes.ts +40 -23
  617. package/src/runtime/routes/settings-routes.ts +41 -13
  618. package/src/runtime/routes/skills-routes.ts +79 -0
  619. package/src/runtime/routes/ui-snapshot-routes.ts +1 -0
  620. package/src/runtime/routes/wake-conversation-routes.ts +23 -0
  621. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  622. package/src/runtime/services/conversation-serializer.ts +24 -9
  623. package/src/runtime/sync/documents-sidecar-publish.test.ts +101 -0
  624. package/src/runtime/sync/resource-sync-events.ts +79 -1
  625. package/src/runtime/sync/worker-daemon-notify.test.ts +34 -0
  626. package/src/runtime/sync/worker-daemon-notify.ts +44 -6
  627. package/src/runtime/verification-outbound-actions.ts +250 -222
  628. package/src/runtime/verification-templates.ts +41 -30
  629. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
  630. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
  631. package/src/schedule/inference-profile.ts +64 -3
  632. package/src/schedule/plugin-schedule-declarations.ts +518 -0
  633. package/src/schedule/plugin-schedule-reconciler.ts +605 -0
  634. package/src/schedule/plugin-schedules-gate.ts +23 -0
  635. package/src/schedule/schedule-store.ts +520 -26
  636. package/src/schedule/scheduler.ts +38 -0
  637. package/src/security/credential-backend.ts +51 -4
  638. package/src/security/encrypted-store.ts +64 -42
  639. package/src/security/secure-keys.ts +55 -7
  640. package/src/skills/catalog-install.ts +20 -7
  641. package/src/skills/inline-command-runner.ts +2 -7
  642. package/src/skills/skill-history.test.ts +269 -0
  643. package/src/skills/skill-history.ts +250 -0
  644. package/src/skills/skillssh-registry.ts +1 -1
  645. package/src/stt/daemon-batch-transcriber.ts +8 -6
  646. package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
  647. package/src/subagent/consult-prompt.ts +3 -1
  648. package/src/subagent/consult-transcript.ts +1 -1
  649. package/src/subagent/manager.ts +304 -44
  650. package/src/subagent/progress-events.ts +35 -0
  651. package/src/subagent/role-resolution.ts +88 -0
  652. package/src/subagent/types.ts +302 -67
  653. package/src/subagent/validate-allowlists.ts +3 -4
  654. package/src/telegram/__tests__/webhook-health.test.ts +73 -1
  655. package/src/telegram/webhook-health.ts +101 -3
  656. package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
  657. package/src/telemetry/activation-funnel.ts +6 -1
  658. package/src/telemetry/live-voice-funnel.ts +75 -0
  659. package/src/telemetry/telemetry-event-sources.ts +6 -0
  660. package/src/telemetry/telemetry-wire-source.json +1 -1
  661. package/src/telemetry/telemetry-wire.generated.ts +2 -0
  662. package/src/telemetry/types.ts +23 -4
  663. package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
  664. package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
  665. package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
  666. package/src/tools/ask-question/ask-question-tool.ts +56 -2
  667. package/src/tools/browser/browser-execution.ts +1 -1
  668. package/src/tools/calls/call-start.ts +8 -5
  669. package/src/tools/conversation-groups/move_to_group.ts +10 -12
  670. package/src/tools/document/document-tool.ts +11 -4
  671. package/src/tools/execution-timeout.ts +9 -2
  672. package/src/tools/executor.ts +7 -6
  673. package/src/tools/filesystem/edit.ts +1 -1
  674. package/src/tools/filesystem/list.ts +1 -1
  675. package/src/tools/filesystem/read.ts +7 -13
  676. package/src/tools/filesystem/search.ts +23 -21
  677. package/src/tools/filesystem/write.ts +4 -1
  678. package/src/tools/host-filesystem/edit.ts +7 -2
  679. package/src/tools/host-filesystem/read.ts +21 -8
  680. package/src/tools/host-filesystem/write.ts +10 -2
  681. package/src/tools/host-terminal/host-shell.ts +2 -2
  682. package/src/tools/mcp/mcp-tool-factory.ts +25 -1
  683. package/src/tools/permission-checker.ts +8 -3
  684. package/src/tools/schedule/delete.ts +13 -1
  685. package/src/tools/schedule/list.ts +14 -3
  686. package/src/tools/schedule/update.ts +56 -33
  687. package/src/tools/shared/filesystem/audio-read.ts +6 -4
  688. package/src/tools/shared/filesystem/file-ops-service.ts +176 -116
  689. package/src/tools/shared/filesystem/image-read.ts +12 -12
  690. package/src/tools/shared/filesystem/path-policy.ts +70 -5
  691. package/src/tools/shared/filesystem/size-guard.ts +6 -6
  692. package/src/tools/shared/input-misuse.ts +72 -0
  693. package/src/tools/skills/scaffold-managed.ts +82 -0
  694. package/src/tools/skills/skill-tool-factory.ts +14 -1
  695. package/src/tools/subagent/consult-deadline.ts +12 -7
  696. package/src/tools/subagent/message.ts +3 -1
  697. package/src/tools/subagent/read.ts +66 -4
  698. package/src/tools/subagent/spawn.ts +500 -78
  699. package/src/tools/tool-approval-handler.ts +45 -4
  700. package/src/tools/types.ts +22 -3
  701. package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
  702. package/src/tools/ui-surface/visual-validation.ts +2 -0
  703. package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
  704. package/src/usage/subagent-attribution.ts +108 -0
  705. package/src/util/__tests__/sqlite-retry.test.ts +112 -0
  706. package/src/util/abort-reasons.ts +16 -0
  707. package/src/util/ansi.ts +44 -0
  708. package/src/util/disk-usage.ts +17 -13
  709. package/src/util/image-conversion.ts +20 -13
  710. package/src/util/schedule-source-key.ts +23 -0
  711. package/src/util/worker-process.ts +7 -1
  712. package/src/workspace/git-service.ts +33 -9
  713. package/src/workspace/migrations/140-repair-seed-pinned-memory-v3-live.ts +260 -0
  714. package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +111 -0
  715. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +126 -0
  716. package/src/workspace/migrations/registry.ts +4 -0
  717. package/src/workspace/top-level-renderer.ts +15 -2
  718. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -357
  719. package/src/daemon/install-symlink.ts +0 -207
  720. package/src/notifications/deferred-emit.ts +0 -147
@@ -29,7 +29,6 @@ export interface UnifiedTurnContextOptions {
29
29
  name: string;
30
30
  /** Directory stem: the handle a human would recognize the app by. */
31
31
  slug: string;
32
- sourceDir: string;
33
32
  /** Set when the app is bundled by an installed plugin rather than built here. */
34
33
  pluginName?: string;
35
34
  } | null;
@@ -127,7 +126,7 @@ export function buildUnifiedTurnContextBlock(
127
126
  ? ` It is bundled by the "${sanitizeInlineContextValue(app.pluginName)}" plugin, not built in the sandbox.`
128
127
  : "";
129
128
  lines.push(
130
- `visible_app: "${sanitizeInlineContextValue(app.name)}" (app_id: "${sanitizeInlineContextValue(app.appId)}", slug: "${sanitizeInlineContextValue(app.slug)}", source: "${sanitizeInlineContextValue(app.sourceDir)}"). The user has this app open on screen; unqualified references to "the app" mean this one. The app tools take the app_id.${provenance}`,
129
+ `visible_app: "${sanitizeInlineContextValue(app.name)}" (app_id: "${sanitizeInlineContextValue(app.appId)}", slug: "${sanitizeInlineContextValue(app.slug)}"). The user has this app open on screen; unqualified references to "the app" mean this one.${provenance}`,
131
130
  );
132
131
  }
133
132
 
@@ -461,9 +461,14 @@ export async function loadExternalPlugin(
461
461
  * Exported so the mtime cache can discover plugin identity without going
462
462
  * through the full `buildExternalPlugin` path. The identity mirrors
463
463
  * {@link buildPluginFromDir}: the directory name, not `package.json` `name`.
464
+ *
465
+ * `quiet` suppresses the failure logs, for callers that poll on a timer and
466
+ * surface the failure through their own channel (e.g. the schedule
467
+ * reconciler's per-day deduped notification).
464
468
  */
465
469
  export async function parsePluginManifest(
466
470
  pluginDir: string,
471
+ opts: { quiet?: boolean } = {},
467
472
  ): Promise<
468
473
  Pick<PluginManifest, "name" | "version" | "credentialKeyPatterns"> | undefined
469
474
  > {
@@ -473,18 +478,22 @@ export async function parsePluginManifest(
473
478
  rawPkg = JSON.parse(await readFile(pkgPath, "utf8"));
474
479
  } catch (err) {
475
480
  const reason = err instanceof Error ? err.message : String(err);
476
- log.error(
477
- { err, pluginDir },
478
- `package.json at ${pluginDir} could not be read or parsed: ${reason}`,
479
- );
481
+ if (!opts.quiet) {
482
+ log.error(
483
+ { err, pluginDir },
484
+ `package.json at ${pluginDir} could not be read or parsed: ${reason}`,
485
+ );
486
+ }
480
487
  return undefined;
481
488
  }
482
489
  const parsed = PluginPackageJsonSchema.safeParse(rawPkg);
483
490
  if (!parsed.success) {
484
- log.error(
485
- { err: parsed.error, pluginDir },
486
- `package.json at ${pluginDir} failed schema validation: ${parsed.error.message}`,
487
- );
491
+ if (!opts.quiet) {
492
+ log.error(
493
+ { err: parsed.error, pluginDir },
494
+ `package.json at ${pluginDir} failed schema validation: ${parsed.error.message}`,
495
+ );
496
+ }
488
497
  return undefined;
489
498
  }
490
499
  const pkg: PluginPackageJson = parsed.data;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Enumerate installed plugin directories under the workspace plugins dir.
3
+ *
4
+ * An installed plugin is a non-hidden directory carrying a `package.json`,
5
+ * whose realpath stays under the realpath of the plugins directory. This walk
6
+ * is shared by the plugin source-version collector
7
+ * (`./collect-source-versions.ts`) and the schedule reconciler
8
+ * (`../schedule/plugin-schedule-reconciler.ts`) so both agree on what counts
9
+ * as an installed plugin. Disabled-state and manifest validation are caller
10
+ * concerns: the source collector fingerprints disabled plugins too, while the
11
+ * reconciler skips them and additionally gates on `parsePluginManifest`.
12
+ *
13
+ * The containment half of that definition is exported on its own
14
+ * ({@link isInsidePluginRoot}) for the plugin loader, which applies the same
15
+ * boundary at boot discovery and before it imports a plugin directory, and for
16
+ * the schedule fire-time probe, which applies it before answering that a
17
+ * declaration is available to run.
18
+ */
19
+
20
+ import { existsSync, readdirSync, realpathSync, statSync } from "node:fs";
21
+ import { join, sep } from "node:path";
22
+
23
+ import { getWorkspacePluginsDir } from "../util/platform.js";
24
+
25
+ export interface InstalledPluginDir {
26
+ /** Directory basename: the plugin's install identity. */
27
+ readonly name: string;
28
+ /** Absolute path to the plugin directory. */
29
+ readonly dir: string;
30
+ }
31
+
32
+ /**
33
+ * True when `dir` resolves to a location strictly inside `root`, where `root`
34
+ * is the tree the caller requires `dir` to stay in: an allowed plugin root
35
+ * (the workspace plugins directory, or the standalone workspace hooks
36
+ * directory) for a plugin directory, or a plugin directory for something
37
+ * installed under it.
38
+ *
39
+ * Both sides are resolved before the comparison. Resolving the candidate is
40
+ * what judges a symlinked entry by where it points rather than by where it
41
+ * sits; resolving the root is what keeps a root that itself sits behind a
42
+ * symlinked path component (macOS `/tmp` to `/private/tmp`, a workspace under
43
+ * a linked home) matching its own children. A resolved candidate compared
44
+ * against an unresolved root reports every entry as outside.
45
+ *
46
+ * This is the one containment boundary the plugin loader applies: enumeration
47
+ * here, boot discovery, and the pre-import check before a plugin directory is
48
+ * dynamic-imported (`scanPlugins` and `isAllowedPluginDir` in
49
+ * `./mtime-cache.ts`) all go through it, so a root the loader refuses to
50
+ * activate is never reported as installed either. A link pointing at the root
51
+ * itself is outside the boundary: it aliases the root, not a plugin. The
52
+ * schedule fire-time probe
53
+ * (`../schedule/plugin-schedule-declarations.ts`) applies the same boundary,
54
+ * so such a root cannot be run by a declared schedule either.
55
+ */
56
+ export function isInsidePluginRoot(dir: string, root: string): boolean {
57
+ try {
58
+ return realpathSync(dir).startsWith(realpathSync(root) + sep);
59
+ } catch {
60
+ // Unresolvable (dangling link, races with an uninstall, unreadable): not
61
+ // provably contained, so it is not inside.
62
+ return false;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * List every installed plugin directory, sorted by the underlying readdir
68
+ * order. A missing plugins directory yields `[]`.
69
+ */
70
+ export function listInstalledPluginDirs(): InstalledPluginDir[] {
71
+ const pluginsDir = getWorkspacePluginsDir();
72
+ let entries: string[] = [];
73
+ try {
74
+ entries = readdirSync(pluginsDir);
75
+ } catch {
76
+ // No plugins directory yet, so nothing installed.
77
+ return [];
78
+ }
79
+ const out: InstalledPluginDir[] = [];
80
+ for (const entry of entries) {
81
+ if (entry.startsWith(".")) {
82
+ continue;
83
+ }
84
+ const dir = join(pluginsDir, entry);
85
+ try {
86
+ if (!statSync(dir).isDirectory()) {
87
+ continue;
88
+ }
89
+ } catch {
90
+ continue;
91
+ }
92
+ if (!isInsidePluginRoot(dir, pluginsDir)) {
93
+ continue;
94
+ }
95
+ if (!existsSync(join(dir, "package.json"))) {
96
+ continue;
97
+ }
98
+ out.push({ name: entry, dir });
99
+ }
100
+ return out;
101
+ }
@@ -31,13 +31,7 @@
31
31
  * each hook through the hook loader on demand.
32
32
  */
33
33
 
34
- import {
35
- existsSync,
36
- readdirSync,
37
- readFileSync,
38
- realpathSync,
39
- statSync,
40
- } from "node:fs";
34
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
41
35
  import { join } from "node:path";
42
36
 
43
37
  import type { Logger } from "pino";
@@ -72,6 +66,7 @@ import {
72
66
  listSurfaceDir,
73
67
  parsePluginManifest,
74
68
  } from "./external-plugin-loader.js";
69
+ import { isInsidePluginRoot } from "./installed-plugin-dirs.js";
75
70
  import { snapshotPluginSource } from "./source-fingerprint.js";
76
71
  import type { PluginSourceVersion } from "./source-versions.js";
77
72
  import {
@@ -293,6 +288,18 @@ export async function reconcilePluginSourcesNow(): Promise<void> {
293
288
  } catch (err) {
294
289
  log.error({ err }, "imperative plugin reconcile failed");
295
290
  }
291
+ // Converge plugin-declared schedules against the plugin set this apply
292
+ // just settled, so install/uninstall/upgrade arm and disarm rows in the
293
+ // same poke. Imported lazily to keep the notification pipeline out of
294
+ // this module's static graph (sidecar workers import it for hook reads).
295
+ // Self-contained: never throws and checks DB readiness itself.
296
+ try {
297
+ const { reconcilePluginSchedules } =
298
+ await import("../schedule/plugin-schedule-reconciler.js");
299
+ await reconcilePluginSchedules();
300
+ } catch (err) {
301
+ log.error({ err }, "plugin schedule reconcile failed");
302
+ }
296
303
  })().finally(() => {
297
304
  reconcileInFlight = null;
298
305
  });
@@ -306,32 +313,26 @@ export async function reconcilePluginSourcesNow(): Promise<void> {
306
313
  * never dynamically imports code from outside the designated plugin roots,
307
314
  * regardless of what the collector walked.
308
315
  *
309
- * Uses `realpathSync` to resolve symlinks before the prefix check, so a
310
- * symlinked path that looks like it's under the plugins dir but points
311
- * elsewhere is rejected.
316
+ * Containment runs through {@link isInsidePluginRoot}, which resolves the
317
+ * candidate and the root, so a symlinked path that looks like it's under the
318
+ * plugins dir but points elsewhere is rejected while a plugins dir reached
319
+ * through a symlinked path component still accepts its own children.
312
320
  */
313
321
  function isAllowedPluginDir(
314
322
  dir: string,
315
323
  pluginsDir: string,
316
324
  hooksDir: string,
317
325
  ): boolean {
318
- let resolved: string;
319
- try {
320
- resolved = realpathSync(dir);
321
- } catch {
322
- // Directory doesn't exist or is inaccessible — allow it through so
323
- // bringUpPlugin/parsePluginManifest can log the normal failure. The
326
+ if (!existsSync(dir)) {
327
+ // Directory doesn't exist or is inaccessible: allow it through so
328
+ // bringUpPlugin/parsePluginManifest can log the normal failure, and so a
329
+ // directory that just went away still reaches its teardown branch. The
324
330
  // danger is importing code from an unexpected location, not a missing
325
331
  // directory.
326
332
  return true;
327
333
  }
328
- const normalizedPluginsDir = pluginsDir + "/";
329
- const normalizedHooksDir = hooksDir + "/";
330
334
  return (
331
- resolved === pluginsDir ||
332
- resolved.startsWith(normalizedPluginsDir) ||
333
- resolved === hooksDir ||
334
- resolved.startsWith(normalizedHooksDir)
335
+ isInsidePluginRoot(dir, pluginsDir) || isInsidePluginRoot(dir, hooksDir)
335
336
  );
336
337
  }
337
338
 
@@ -754,6 +755,17 @@ async function scanPlugins(): Promise<void> {
754
755
  } catch {
755
756
  continue;
756
757
  }
758
+ // Judge the entry by where it resolves. A symlinked plugin root pointing
759
+ // out of the plugins directory is not an install, and activating one at
760
+ // boot would run code that enumeration and the reload path both refuse to
761
+ // touch.
762
+ if (!isInsidePluginRoot(pluginDir, pluginsDir)) {
763
+ log.warn(
764
+ { pluginDir },
765
+ "plugin root resolves outside the plugins directory, skipping",
766
+ );
767
+ continue;
768
+ }
757
769
  if (!existsSync(join(pluginDir, "package.json"))) {
758
770
  continue;
759
771
  }
@@ -147,9 +147,9 @@ export interface TurnContext {
147
147
  /** Human-readable interface label (e.g. "vellum", "telegram"). */
148
148
  readonly interfaceName?: string;
149
149
  /**
150
- * Client OS surface ("web" | "ios" | "macos"), reported independently of
151
- * the transport interface. Rendered as the `client_os:` line so the model
152
- * knows the platform even though the web/iOS/macOS apps share one `"web"`
150
+ * Client OS surface, reported independently of the transport interface.
151
+ * Rendered as the `client_os:` line so the model knows the platform even
152
+ * though browser, mobile, and desktop apps share one `"web"`
153
153
  * transport interface.
154
154
  */
155
155
  readonly clientOs?: string;
@@ -163,10 +163,13 @@ export interface TurnContext {
163
163
  /** Canonical id the app tools key off (an opaque UUID for workspace apps). */
164
164
  appId: string;
165
165
  name: string;
166
- /** Directory stem: the readable handle for an otherwise opaque id. */
166
+ /**
167
+ * Directory stem: the readable handle for an otherwise opaque id, and the
168
+ * piece the model joins with the workspace root and the app-builder skill's
169
+ * `data/apps/<slug>/` layout to reach the app's files. The resolved
170
+ * directory itself is not sent, being derivable from those three.
171
+ */
167
172
  slug: string;
168
- /** Absolute path of the app's source directory. */
169
- sourceDir: string;
170
173
  /** Owning plugin, when the app is plugin-bundled rather than sandbox-built. */
171
174
  pluginName?: string;
172
175
  } | null;
@@ -350,10 +353,68 @@ export interface Injector {
350
353
  // surfaces (e.g. `routes/`) are served from their workspace location rather
351
354
  // than wired through the loader, so they carry no `Plugin` contribution slot.
352
355
 
356
+ /**
357
+ * How the worker resolves a claimed job row after its handler returns
358
+ * without throwing. Handlers that report failure through a returned domain
359
+ * outcome (rather than a throw) translate that outcome into one of these at
360
+ * the registration site, so the persisted `memory_jobs.status` reflects what
361
+ * actually happened instead of unconditionally reading `completed`.
362
+ *
363
+ * - `completed`: the work succeeded (or was a legitimate no-op).
364
+ * - `failed`: terminal failure; the row is dead-lettered with
365
+ * `errorMessage` as `last_error`. Use when retry is owned elsewhere
366
+ * (event-driven re-enqueue, durable backoff checkpoints).
367
+ * - `retryable`: transient failure; the row re-enters the queue's
368
+ * attempt-budgeted retry machinery with `errorMessage` recorded.
369
+ * - `deferred`: the work could not run yet for a reason external to the
370
+ * job (e.g. its source conversation is mid-turn); the row goes back to
371
+ * pending on the deferral counter's backoff curve and dead-letters with
372
+ * `deferralExhaustedMessage` once the deferral budget is spent.
373
+ */
374
+ export interface JobQueueResolution {
375
+ queueResolution: "completed" | "failed" | "retryable" | "deferred";
376
+ /** Persisted to `memory_jobs.last_error` for `failed` / `retryable`. */
377
+ errorMessage?: string;
378
+ /** Retry delay override for `retryable`. */
379
+ retryDelayMs?: number;
380
+ /** Terminal `last_error` when a `deferred` job exhausts its budget. */
381
+ deferralExhaustedMessage?: string;
382
+ }
383
+
384
+ const QUEUE_RESOLUTIONS = new Set([
385
+ "completed",
386
+ "failed",
387
+ "retryable",
388
+ "deferred",
389
+ ]);
390
+
391
+ /**
392
+ * Narrow an arbitrary handler return value to a {@link JobQueueResolution}.
393
+ * Anything else (including `undefined` and legacy domain outcomes) resolves
394
+ * as `completed`, preserving the historical contract for handlers that only
395
+ * signal failure by throwing.
396
+ */
397
+ export function isJobQueueResolution(
398
+ value: unknown,
399
+ ): value is JobQueueResolution {
400
+ return (
401
+ typeof value === "object" &&
402
+ value !== null &&
403
+ "queueResolution" in value &&
404
+ typeof (value as { queueResolution: unknown }).queueResolution ===
405
+ "string" &&
406
+ QUEUE_RESOLUTIONS.has(
407
+ (value as { queueResolution: string }).queueResolution,
408
+ )
409
+ );
410
+ }
411
+
353
412
  /**
354
413
  * Processes one claimed background job: receives the claimed job row and the
355
- * live assistant config. The return value is ignored a handler signals
356
- * failure by throwing, which the worker records against the job.
414
+ * live assistant config. A handler signals failure by throwing (recorded
415
+ * against the job's attempt budget) or by returning a
416
+ * {@link JobQueueResolution}; any other return value resolves the row as
417
+ * `completed`.
357
418
  */
358
419
  export type JobHandler = (job: MemoryJob, config: AssistantConfig) => unknown;
359
420
 
@@ -82,7 +82,10 @@ function readPersonaFile(filePath: string): string | null {
82
82
  * (`guardianExternalUserId`): the info read is keyed on that address so it
83
83
  * matches the gateway's guardian binding. When the context carries no guardian
84
84
  * address (desktop / native turns), fall back to the channel's guardian
85
- * contact. Returns `"guardian.md"` when the resolved guardian has no userFile.
85
+ * contact, then any guardian: the guardian may be bound on a channel other
86
+ * than the turn's source, and the construction-time warm populates both the
87
+ * channel-filtered and unfiltered cache keys. Returns `"guardian.md"` when the
88
+ * resolved guardian has no userFile.
86
89
  */
87
90
  function resolveGuardianUserFile(trustContext: TrustContext): string | null {
88
91
  if (trustContext.guardianExternalUserId) {
@@ -94,7 +97,8 @@ function resolveGuardianUserFile(trustContext: TrustContext): string | null {
94
97
  return guardianContact.userFile ?? "guardian.md";
95
98
  }
96
99
  }
97
- const guardian = peekGuardianForChannel(trustContext.sourceChannel);
100
+ const guardian =
101
+ peekGuardianForChannel(trustContext.sourceChannel) ?? peekAnyGuardian();
98
102
  return guardian
99
103
  ? (guardianDeliveryUserFile(guardian) ?? "guardian.md")
100
104
  : null;
@@ -48,13 +48,61 @@ function hasPopulatedUsersDir(): boolean {
48
48
  }
49
49
  }
50
50
 
51
- function hasExistingConversations(): boolean {
51
+ /**
52
+ * Conversation types that are internal side channels rather than threads the
53
+ * user opens: onboarding research, personality/identity rewrites, heartbeat
54
+ * and scheduled runs, plus legacy private rows.
55
+ */
56
+ const HIDDEN_CONVERSATION_TYPES = new Set([
57
+ "background",
58
+ "scheduled",
59
+ "private",
60
+ ]);
61
+
62
+ /**
63
+ * Whether the workspace carries at least one standard (user-visible)
64
+ * conversation.
65
+ *
66
+ * Classifies each disk-view directory by the `type` in its `meta.json` rather
67
+ * than counting raw directory entries, because hidden side threads get a
68
+ * disk-view directory too and must not be mistaken for the user's own history.
69
+ * The type read here is the same value `countConversations("standard")` filters
70
+ * on, but the answer comes off disk rather than out of the database:
71
+ * `ensurePromptFiles()` runs before `initializeDb()` settles in
72
+ * `daemon/lifecycle.ts`, so a query here would hit a partially-migrated schema.
73
+ *
74
+ * Only directories are classified. The layout is browsable, so stray files such
75
+ * as `.DS_Store` sit beside the conversations, and a `meta.json` read under one
76
+ * fails indistinguishably from an unreadable conversation. Directory names are
77
+ * not filtered further, because both the canonical `<ISO>_<id>` layout and the
78
+ * legacy `<id>_<ISO>` one name real conversations.
79
+ *
80
+ * A directory whose `meta.json` is missing or unreadable counts as standard: a
81
+ * directory the daemon cannot classify is far more likely to be legacy history
82
+ * than a side thread, and over-counting only keeps onboarding from re-running.
83
+ */
84
+ function hasStandardConversations(): boolean {
52
85
  try {
53
86
  const convDir = getConversationsDir();
54
87
  if (!existsSync(convDir)) {
55
88
  return false;
56
89
  }
57
- return readdirSync(convDir).length > 0;
90
+ return readdirSync(convDir, { withFileTypes: true }).some((entry) => {
91
+ if (!entry.isDirectory()) {
92
+ return false;
93
+ }
94
+ try {
95
+ const meta = JSON.parse(
96
+ readFileSync(join(convDir, entry.name, "meta.json"), "utf-8"),
97
+ ) as { type?: unknown };
98
+ return (
99
+ typeof meta.type !== "string" ||
100
+ !HIDDEN_CONVERSATION_TYPES.has(meta.type)
101
+ );
102
+ } catch {
103
+ return true;
104
+ }
105
+ });
58
106
  } catch {
59
107
  return false;
60
108
  }
@@ -78,14 +126,14 @@ export function ensurePromptFiles(): void {
78
126
 
79
127
  // Track whether this is a fresh workspace. A workspace counts as fresh
80
128
  // only when none of these signals are present: core prompt files, a
81
- // populated `users/` directory, or existing conversations. Upgraded
82
- // workspaces that dropped USER.md but still carry personas or history
83
- // would otherwise be mistaken for fresh installs and re-trigger
129
+ // populated `users/` directory, or existing standard conversations.
130
+ // Upgraded workspaces that dropped USER.md but still carry personas or
131
+ // history would otherwise be mistaken for fresh installs and re-trigger
84
132
  // onboarding.
85
133
  const isFirstRun =
86
134
  PROMPT_FILES.every((file) => !existsSync(getWorkspacePromptPath(file))) &&
87
135
  !hasPopulatedUsersDir() &&
88
- !hasExistingConversations();
136
+ !hasStandardConversations();
89
137
 
90
138
  for (const file of PROMPT_FILES) {
91
139
  const dest = getWorkspacePromptPath(file);
@@ -152,18 +200,25 @@ export function ensurePromptFiles(): void {
152
200
  // Auto-delete stale BOOTSTRAP.md at startup. The model is instructed to
153
201
  // delete it at the end of the first conversation, but if the user closes
154
202
  // the app or starts a new thread before the model gets another turn, it
155
- // never gets the chance. If BOOTSTRAP.md still exists but prior
156
- // conversations are present, the onboarding window has passed clean up.
203
+ // never gets the chance. If BOOTSTRAP.md still exists but prior standard
204
+ // conversations are present, the onboarding window has passed, so clean up.
205
+ //
206
+ // Only standard conversations count. Onboarding mints hidden side threads
207
+ // before the user's first visible chat, so counting those would delete
208
+ // BOOTSTRAP.md out from under that chat on the next daemon restart.
209
+ //
210
+ // This gate is one conversation stricter than the in-process gate in
211
+ // `persistence/conversation-key-store.ts`, which keeps BOOTSTRAP.md through
212
+ // the whole first standard conversation and deletes it when a second one is
213
+ // created. A restart while the user is still inside that first conversation
214
+ // deletes BOOTSTRAP.md from under it.
157
215
  const bootstrapCleanup = getWorkspacePromptPath("BOOTSTRAP.md");
158
- if (!isFirstRun && existsSync(bootstrapCleanup)) {
159
- const convDir = getConversationsDir();
160
- try {
161
- if (existsSync(convDir) && readdirSync(convDir).length > 0) {
162
- cleanupBootstrapFiles("prior conversations exist");
163
- }
164
- } catch (err) {
165
- log.warn({ err }, "Failed to auto-delete stale BOOTSTRAP.md");
166
- }
216
+ if (
217
+ !isFirstRun &&
218
+ existsSync(bootstrapCleanup) &&
219
+ hasStandardConversations()
220
+ ) {
221
+ cleanupBootstrapFiles("prior conversations exist");
167
222
  }
168
223
 
169
224
  // Seed HEARTBEAT.md — always created if missing so the heartbeat service
@@ -181,6 +181,46 @@ describe("Connection CRUD", () => {
181
181
  expect(fetched?.auth.type).toBe("api_key");
182
182
  });
183
183
 
184
+ test("updateConnection: provider correction rides an auth rewrite", () => {
185
+ // The ChatGPT sign-in flow stamps provider "openai" when it writes
186
+ // subscription auth, so a claiming row with another provider cannot
187
+ // strand the fresh token behind derived platform auth.
188
+ const { db } = setupDb();
189
+ createConnection(db, {
190
+ name: "chatgpt-subscription",
191
+ provider: "vellum",
192
+ auth: { type: "platform" },
193
+ });
194
+ const result = updateConnection(db, "chatgpt-subscription", {
195
+ auth: {
196
+ type: "oauth_subscription",
197
+ credential: "credential/chatgpt/access_token",
198
+ },
199
+ provider: "openai",
200
+ });
201
+ expect(result.ok).toBe(true);
202
+ const fetched = getConnection(db, "chatgpt-subscription");
203
+ expect(fetched?.provider).toBe("openai");
204
+ expect(fetched?.auth.type).toBe("oauth_subscription");
205
+ });
206
+
207
+ test("updateConnection: rejects an unknown provider", () => {
208
+ const { db } = setupDb();
209
+ createConnection(db, {
210
+ name: "updatable-provider",
211
+ provider: "anthropic",
212
+ auth: { type: "api_key", credential: "credential/anthropic/api_key" },
213
+ });
214
+ const result = updateConnection(db, "updatable-provider", {
215
+ auth: { type: "api_key", credential: "credential/anthropic/api_key" },
216
+ provider: "not-a-provider",
217
+ });
218
+ expect(result.ok).toBe(false);
219
+ if (!result.ok) {
220
+ expect(result.error.code).toBe("invalid_provider");
221
+ }
222
+ });
223
+
184
224
  test("updateConnection — rejects unknown name", () => {
185
225
  const { db } = setupDb();
186
226
  const result = updateConnection(db, "ghost", { auth: { type: "none" } });
@@ -1,4 +1,4 @@
1
- import { beforeEach, describe, expect, mock, test } from "bun:test";
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  let connectionsByName: Record<string, unknown> = {};
4
4
  let secureKeys: Record<string, string | undefined> = {};
@@ -74,6 +74,11 @@ beforeEach(() => {
74
74
  secureKeys = { "credential/anthropic/api_key": "sk-ant" };
75
75
  cesUnreachable = false;
76
76
  platformLoggedIn = false;
77
+ delete process.env.IS_PLATFORM;
78
+ });
79
+
80
+ afterEach(() => {
81
+ delete process.env.IS_PLATFORM;
77
82
  });
78
83
 
79
84
  describe("preflightResolvedConfig", () => {
@@ -300,3 +305,55 @@ describe("preflightResolvedConfig", () => {
300
305
  expect(await preflightError(resolved())).toBeUndefined();
301
306
  });
302
307
  });
308
+
309
+ describe("preflightResolvedConfig platform-managed credential messaging", () => {
310
+ const platformManaged = () => {
311
+ connectionsByName = {
312
+ vellum: {
313
+ name: "vellum",
314
+ provider: "vellum",
315
+ auth: { type: "platform" },
316
+ },
317
+ };
318
+ process.env.IS_PLATFORM = "true";
319
+ };
320
+ const managedRoute = () => resolved({ provider_connection: "vellum" });
321
+
322
+ test("store unreachable surfaces a retriable message with no login text", async () => {
323
+ platformManaged();
324
+ cesUnreachable = true;
325
+ const err = await preflightError(managedRoute());
326
+ expect(err?.reason).toBe("platform_unauthenticated");
327
+ expect(err?.message).toContain("temporarily unavailable");
328
+ expect(err?.message).not.toContain("log in");
329
+ });
330
+
331
+ test("credential absent asks for platform re-provisioning, not a login", async () => {
332
+ platformManaged();
333
+ // Logged out with a reachable store means the credential is genuinely absent.
334
+ const err = await preflightError(managedRoute());
335
+ expect(err?.reason).toBe("platform_unauthenticated");
336
+ expect(err?.message).toContain("must be re-provisioned");
337
+ expect(err?.message).not.toContain("log in");
338
+ });
339
+
340
+ test("a healthy platform login passes silently", async () => {
341
+ platformManaged();
342
+ platformLoggedIn = true;
343
+ expect(await preflightError(managedRoute())).toBeUndefined();
344
+ });
345
+
346
+ test("non-platform install keeps the log-in-or-switch wording", async () => {
347
+ connectionsByName = {
348
+ vellum: {
349
+ name: "vellum",
350
+ provider: "vellum",
351
+ auth: { type: "platform" },
352
+ },
353
+ };
354
+ // IS_PLATFORM left unset covers both local and self-hosted Docker installs.
355
+ const err = await preflightError(managedRoute());
356
+ expect(err?.reason).toBe("platform_unauthenticated");
357
+ expect(err?.message).toContain("log in or pick a different provider");
358
+ });
359
+ });