@vellumai/assistant 0.12.0 → 0.12.1-dev.202609142113.d1590bc

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 (541) hide show
  1. package/AGENTS.md +5 -1
  2. package/ARCHITECTURE.md +20 -21
  3. package/docs/architecture/turn-actor.md +15 -8
  4. package/docs/guardian-request-flow.md +16 -0
  5. package/openapi.yaml +327 -52
  6. package/package.json +2 -1
  7. package/scripts/smoke-desktop-window-theme.ts +186 -0
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  9. package/src/__tests__/anthropic-provider.test.ts +0 -63
  10. package/src/__tests__/app-compiler.test.ts +6 -0
  11. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  13. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  14. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  15. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  16. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  17. package/src/__tests__/computer-use-tools.test.ts +19 -7
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +28 -29
  20. package/src/__tests__/config-watcher.test.ts +23 -8
  21. package/src/__tests__/connection-model-compat.test.ts +2 -2
  22. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  23. package/src/__tests__/conversation-pairing.test.ts +26 -0
  24. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  25. package/src/__tests__/conversation-queue.test.ts +48 -207
  26. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  27. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  28. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  29. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  30. package/src/__tests__/conversation-title-service.test.ts +14 -10
  31. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  32. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  33. package/src/__tests__/credential-record-retire.test.ts +171 -1
  34. package/src/__tests__/credential-routes.test.ts +212 -3
  35. package/src/__tests__/credential-security-invariants.test.ts +4 -2
  36. package/src/__tests__/cu-unified-flow.test.ts +8 -2
  37. package/src/__tests__/document-sync-tags.test.ts +25 -0
  38. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  39. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  40. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  41. package/src/__tests__/git-porcelain.test.ts +47 -0
  42. package/src/__tests__/heartbeat-service.test.ts +56 -40
  43. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  44. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  45. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  46. package/src/__tests__/host-cu-proxy.test.ts +539 -0
  47. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  48. package/src/__tests__/host-file-proxy.test.ts +11 -23
  49. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  50. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  51. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  52. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  53. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  54. package/src/__tests__/mcp-health-check.test.ts +8 -11
  55. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  56. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  57. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  58. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  59. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  60. package/src/__tests__/model-intents.test.ts +3 -0
  61. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  62. package/src/__tests__/oauth-commands-routes.test.ts +113 -0
  63. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  64. package/src/__tests__/openai-provider.test.ts +1 -0
  65. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  66. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  67. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  68. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  69. package/src/__tests__/qdrant-manager.test.ts +132 -0
  70. package/src/__tests__/resolve-app.test.ts +135 -0
  71. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  72. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  73. package/src/__tests__/service-account-token.test.ts +247 -0
  74. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  75. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  76. package/src/__tests__/skills.test.ts +9 -4
  77. package/src/__tests__/source-fingerprint.test.ts +184 -0
  78. package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
  79. package/src/__tests__/subagent-tools.test.ts +3 -2
  80. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  81. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  82. package/src/__tests__/unicode.test.ts +13 -0
  83. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  84. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  85. package/src/__tests__/workspace-git-service.test.ts +26 -0
  86. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  87. package/src/acp/__tests__/agent-process.test.ts +258 -4
  88. package/src/acp/__tests__/auth-required.test.ts +96 -0
  89. package/src/acp/__tests__/client-handler.test.ts +90 -2
  90. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  91. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  92. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  93. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  94. package/src/acp/__tests__/session-manager.test.ts +895 -4
  95. package/src/acp/agent-process.test.ts +4 -1
  96. package/src/acp/agent-process.ts +96 -10
  97. package/src/acp/auth-required.ts +50 -1
  98. package/src/acp/auto-install.test.ts +9 -7
  99. package/src/acp/client-handler.ts +16 -3
  100. package/src/acp/model-config.test.ts +166 -0
  101. package/src/acp/model-config.ts +138 -0
  102. package/src/acp/resolve-agent.test.ts +220 -6
  103. package/src/acp/resolve-agent.ts +88 -23
  104. package/src/acp/session-manager.test.ts +196 -3
  105. package/src/acp/session-manager.ts +441 -18
  106. package/src/acp/types.ts +50 -5
  107. package/src/agent/loop.ts +1 -0
  108. package/src/api/events/acp-session-model-update.test.ts +88 -0
  109. package/src/api/events/acp-session-model-update.ts +46 -0
  110. package/src/api/events/app-files-changed.ts +4 -3
  111. package/src/api/events/notification-intent.ts +2 -0
  112. package/src/api/events/user-message-echo.ts +7 -1
  113. package/src/api/index.ts +6 -0
  114. package/src/api/responses/conversation-message.ts +5 -0
  115. package/src/apps/app-store.ts +1 -1
  116. package/src/apps/resolve-app.ts +81 -0
  117. package/src/apps/source-fingerprint.ts +266 -0
  118. package/src/avatar/identity-avatar.ts +7 -2
  119. package/src/bundler/app-compiler.ts +15 -0
  120. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  121. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  122. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  123. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  124. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  125. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  126. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  127. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  128. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  129. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  130. package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
  131. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  132. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  133. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  134. package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
  135. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  136. package/src/calls/barge-in-guard.ts +118 -0
  137. package/src/calls/call-constants.ts +24 -4
  138. package/src/calls/call-controller.ts +499 -199
  139. package/src/calls/call-transport.ts +16 -0
  140. package/src/calls/media-stream-output.ts +32 -0
  141. package/src/calls/media-stream-server.ts +82 -30
  142. package/src/calls/media-stream-stt-session.ts +15 -0
  143. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  144. package/src/calls/resolve-call-tts-provider.ts +33 -2
  145. package/src/calls/tts-call-strategy.ts +9 -5
  146. package/src/calls/types.ts +2 -0
  147. package/src/calls/voice-control-protocol.ts +53 -21
  148. package/src/calls/voice-leg-coordinator.ts +189 -0
  149. package/src/calls/voice-progress-cadence.ts +423 -0
  150. package/src/calls/voice-session-bridge.ts +59 -46
  151. package/src/calls/voice-triage-escalate.ts +175 -56
  152. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  153. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  154. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  155. package/src/cli/commands/apps.help.ts +67 -13
  156. package/src/cli/commands/apps.ts +183 -0
  157. package/src/cli/commands/bash.help.ts +8 -13
  158. package/src/cli/commands/bash.ts +7 -25
  159. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  160. package/src/cli/commands/channels/index.help.ts +70 -2
  161. package/src/cli/commands/channels/index.ts +16 -3
  162. package/src/cli/commands/channels/send.ts +128 -0
  163. package/src/cli/commands/credentials.help.ts +6 -2
  164. package/src/cli/commands/credentials.ts +2 -2
  165. package/src/cli/commands/mcp.help.ts +8 -8
  166. package/src/cli/commands/mcp.ts +1 -1
  167. package/src/cli/commands/roadmap.help.ts +5 -1
  168. package/src/cli/commands/roadmap.ts +1 -1
  169. package/src/cli/lib/bundled-marketplace.json +103 -90
  170. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  171. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  172. package/src/config/__tests__/platform-identity.test.ts +255 -0
  173. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  174. package/src/config/acp-agent-write.ts +43 -0
  175. package/src/config/acp-defaults.test.ts +13 -3
  176. package/src/config/acp-defaults.ts +23 -6
  177. package/src/config/acp-schema.test.ts +81 -0
  178. package/src/config/acp-schema.ts +30 -2
  179. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  180. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  181. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  182. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  183. package/src/config/bundled-skills/computer-use/SKILL.md +20 -2
  184. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  185. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  186. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  187. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  188. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  189. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  190. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  191. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  192. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  193. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  194. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  195. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  196. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  197. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  198. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  199. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  200. package/src/config/default-profile-catalog.ts +1 -1
  201. package/src/config/env.ts +2 -2
  202. package/src/config/feature-flag-registry.json +24 -12
  203. package/src/config/loader.ts +1 -0
  204. package/src/config/memory-v3-gate.test.ts +31 -0
  205. package/src/config/memory-v3-gate.ts +20 -16
  206. package/src/config/platform-identity.ts +201 -0
  207. package/src/config/platform-rehydration.ts +11 -27
  208. package/src/config/schema.ts +2 -2
  209. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  210. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  211. package/src/config/schemas/calls.ts +1 -28
  212. package/src/config/schemas/live-voice.ts +1 -173
  213. package/src/config/schemas/mcp.ts +1 -1
  214. package/src/config/schemas/memory-retrospective.ts +10 -0
  215. package/src/config/schemas/services.ts +8 -0
  216. package/src/config/schemas/skills.ts +0 -14
  217. package/src/config/schemas/voice.ts +150 -0
  218. package/src/credential-execution/process-manager.test.ts +18 -19
  219. package/src/credential-health/credential-health-service.ts +11 -17
  220. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  221. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  222. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  223. package/src/daemon/app-change-notify.ts +45 -0
  224. package/src/daemon/config-watcher.ts +13 -194
  225. package/src/daemon/conversation-error.ts +3 -2
  226. package/src/daemon/conversation-media-retry.ts +2 -1
  227. package/src/daemon/conversation-process.ts +35 -64
  228. package/src/daemon/conversation-runtime-assembly.ts +2 -1
  229. package/src/daemon/conversation-skill-tools.ts +13 -31
  230. package/src/daemon/conversation-store.ts +1 -1
  231. package/src/daemon/conversation-surfaces.ts +15 -9
  232. package/src/daemon/conversation-tool-setup.ts +37 -43
  233. package/src/daemon/conversation.ts +0 -3
  234. package/src/daemon/dictation-profile-store.ts +2 -1
  235. package/src/daemon/handlers/shared.ts +2 -1
  236. package/src/daemon/handlers/skills.ts +6 -3
  237. package/src/daemon/host-bash-proxy.ts +8 -12
  238. package/src/daemon/host-browser-proxy.ts +15 -1
  239. package/src/daemon/host-cu-proxy.ts +186 -19
  240. package/src/daemon/host-file-proxy.ts +8 -10
  241. package/src/daemon/host-proxy-preactivation.ts +2 -1
  242. package/src/daemon/host-transfer-proxy.ts +14 -18
  243. package/src/daemon/lifecycle.ts +0 -5
  244. package/src/daemon/mcp-reload-service.ts +5 -5
  245. package/src/daemon/message-types/sync.ts +1 -0
  246. package/src/daemon/orphan-reaper.ts +1 -1
  247. package/src/daemon/providers-setup.ts +1 -1
  248. package/src/daemon/reaction-history-render.ts +2 -1
  249. package/src/daemon/shutdown-handlers.ts +1 -9
  250. package/src/daemon/skill-memory-refresh.ts +64 -0
  251. package/src/daemon/tool-side-effects.ts +7 -18
  252. package/src/desktop/__tests__/fake-desktop.ts +119 -0
  253. package/src/desktop/desktop-chrome-policy.test.ts +71 -0
  254. package/src/desktop/desktop-chrome-policy.ts +42 -0
  255. package/src/desktop/desktop-dependencies.test.ts +102 -0
  256. package/src/desktop/desktop-dependencies.ts +306 -0
  257. package/src/desktop/desktop-feature.test.ts +18 -0
  258. package/src/desktop/desktop-feature.ts +19 -0
  259. package/src/desktop/desktop-panel-config.test.ts +135 -0
  260. package/src/desktop/desktop-panel-config.ts +149 -0
  261. package/src/desktop/desktop-session-manager.test.ts +627 -0
  262. package/src/desktop/desktop-session-manager.ts +814 -0
  263. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  264. package/src/desktop/desktop-stream-bridge.ts +214 -0
  265. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  266. package/src/desktop/desktop-wallpaper.ts +113 -0
  267. package/src/desktop/desktop-window-theme.ts +170 -0
  268. package/src/heartbeat/heartbeat-service.ts +21 -3
  269. package/src/home/__tests__/feed-writer.test.ts +22 -0
  270. package/src/home/feed-writer.ts +11 -2
  271. package/src/hooks/types.ts +33 -0
  272. package/src/i18n/AGENTS.md +22 -0
  273. package/src/i18n/__tests__/i18n.test.ts +108 -0
  274. package/src/i18n/index.ts +16 -0
  275. package/src/i18n/locales.ts +93 -0
  276. package/src/i18n/messages.ts +61 -0
  277. package/src/i18n/title.ts +25 -0
  278. package/src/inbound/platform-callback-registration.ts +4 -5
  279. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  280. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  281. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  282. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  283. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  284. package/src/live-voice/live-voice-photo.ts +25 -6
  285. package/src/live-voice/live-voice-session.ts +213 -693
  286. package/src/live-voice/protocol.ts +0 -2
  287. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  288. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  289. package/src/mcp/effective-config.ts +3 -3
  290. package/src/mcp/mcp-header-store.ts +29 -45
  291. package/src/mcp/spec-schema.ts +111 -0
  292. package/src/mcp/startup.ts +8 -4
  293. package/src/mcp/workspace-mcp-config.ts +188 -0
  294. package/src/messaging/provider.ts +10 -3
  295. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  296. package/src/messaging/providers/callback-routing.ts +18 -0
  297. package/src/messaging/providers/channel-transport.ts +56 -0
  298. package/src/messaging/providers/discord/transport.ts +17 -0
  299. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  300. package/src/messaging/providers/slack/adapter.ts +7 -44
  301. package/src/messaging/providers/slack/transport.ts +16 -0
  302. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  303. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  304. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  305. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  306. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  307. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  308. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  309. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  310. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  311. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  312. package/src/notifications/adapters/macos.ts +3 -0
  313. package/src/notifications/assistant-reply-producer.ts +8 -3
  314. package/src/notifications/conversation-pairing.ts +36 -16
  315. package/src/notifications/delivered-post-record.ts +11 -1
  316. package/src/notifications/guardian-feed-projection.ts +125 -5
  317. package/src/notifications/home-feed-side-effect.ts +55 -7
  318. package/src/notifications/schedule-result-producer.ts +66 -22
  319. package/src/oauth/AGENTS.md +2 -0
  320. package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
  321. package/src/oauth/byo-connection.test.ts +105 -0
  322. package/src/oauth/byo-connection.ts +36 -6
  323. package/src/oauth/connection-resolver.test.ts +73 -0
  324. package/src/oauth/connection-resolver.ts +95 -2
  325. package/src/oauth/scope-utils.ts +24 -0
  326. package/src/oauth/seed-providers.ts +204 -25
  327. package/src/outbound-proxy/certs.ts +3 -5
  328. package/src/permissions/checker.test.ts +11 -6
  329. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  330. package/src/persistence/conversation-crud.ts +71 -63
  331. package/src/persistence/conversation-lineage.ts +12 -4
  332. package/src/persistence/conversation-queries.ts +2 -2
  333. package/src/persistence/conversation-title-placeholders.ts +23 -0
  334. package/src/persistence/conversation-title-service.ts +9 -26
  335. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  336. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  337. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  338. package/src/persistence/message-cursor.ts +78 -0
  339. package/src/persistence/message-reads.ts +30 -0
  340. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  341. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  342. package/src/persistence/schema/memory-core.ts +5 -0
  343. package/src/platform/client.test.ts +4 -2
  344. package/src/platform/client.ts +2 -10
  345. package/src/plugin-api/constants.ts +2 -0
  346. package/src/plugin-api/index.ts +1 -0
  347. package/src/plugin-api/types.ts +2 -0
  348. package/src/plugins/defaults/index.ts +2 -0
  349. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  350. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  351. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  352. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  353. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  354. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  355. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  356. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  357. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  358. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  359. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  360. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  361. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  362. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  363. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  364. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  365. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  366. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  367. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  368. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  369. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  370. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  371. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  372. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  373. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  374. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  375. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  376. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  377. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  378. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  379. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  380. package/src/plugins/defaults/memory/startup.ts +28 -2
  381. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  382. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  383. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  384. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  385. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  386. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  387. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  388. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  389. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  390. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  391. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  392. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  393. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  394. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  395. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  396. package/src/plugins/defaults/memory/worker.ts +6 -0
  397. package/src/plugins/mcp-servers.ts +15 -75
  398. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  399. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  400. package/src/providers/anthropic/client.ts +0 -47
  401. package/src/providers/gemini/client.ts +9 -23
  402. package/src/providers/gemini-thought-signature.test.ts +63 -0
  403. package/src/providers/gemini-thought-signature.ts +44 -0
  404. package/src/providers/inference/auth.ts +2 -3
  405. package/src/providers/inference/connection-availability.ts +27 -8
  406. package/src/providers/inference/resolve-auth.ts +22 -4
  407. package/src/providers/inference/service-account-token.ts +248 -0
  408. package/src/providers/model-catalog.ts +34 -0
  409. package/src/providers/model-intents.ts +10 -5
  410. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  411. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  412. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  413. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  414. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  415. package/src/providers/openai/chat-completions-provider.ts +212 -79
  416. package/src/providers/openai/codex-models.ts +0 -4
  417. package/src/providers/openai/google-thought-signature.ts +320 -0
  418. package/src/providers/openrouter/client.ts +20 -0
  419. package/src/providers/outbound-request-sanitize.ts +96 -0
  420. package/src/providers/retry.ts +24 -3
  421. package/src/providers/unparseable-tool-args.ts +5 -1
  422. package/src/runtime/AGENTS.md +3 -1
  423. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  424. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  425. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  426. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  427. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  428. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  429. package/src/runtime/agent-wake.ts +2 -1
  430. package/src/runtime/assistant-event-hub.ts +21 -3
  431. package/src/runtime/auth/same-actor.ts +38 -3
  432. package/src/runtime/channel-send.ts +316 -0
  433. package/src/runtime/host-observe.ts +11 -1
  434. package/src/runtime/http-server.ts +160 -151
  435. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  436. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
  437. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  438. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  439. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  440. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  441. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  442. package/src/runtime/routes/acp-routes.test.ts +15 -0
  443. package/src/runtime/routes/acp-routes.ts +72 -9
  444. package/src/runtime/routes/app-management-routes.ts +73 -0
  445. package/src/runtime/routes/app-routes.ts +1 -1
  446. package/src/runtime/routes/channel-send-routes.ts +85 -0
  447. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  448. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  449. package/src/runtime/routes/conversation-routes.ts +16 -3
  450. package/src/runtime/routes/credential-routes.ts +19 -10
  451. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  452. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  453. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  454. package/src/runtime/routes/events-routes.ts +4 -0
  455. package/src/runtime/routes/host-cu-routes.ts +6 -0
  456. package/src/runtime/routes/index.ts +4 -2
  457. package/src/runtime/routes/log-export-routes.ts +2 -0
  458. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  459. package/src/runtime/routes/migration-routes.ts +6 -20
  460. package/src/runtime/routes/oauth-commands-routes.ts +36 -0
  461. package/src/runtime/routes/platform-routes.ts +18 -15
  462. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  463. package/src/runtime/routes/workspace-routes.ts +46 -63
  464. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  465. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  466. package/src/runtime/routes/workspace-utils.ts +17 -0
  467. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  468. package/src/runtime/services/conversation-serializer.ts +16 -3
  469. package/src/runtime/sync/resource-sync-events.ts +2 -1
  470. package/src/schedule/run-script.ts +2 -1
  471. package/src/schedule/schedule-store.ts +9 -2
  472. package/src/schedule/worker.ts +1 -1
  473. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  474. package/src/security/ces-http-record-backend.ts +120 -0
  475. package/src/security/ces-rpc-record-backend.ts +7 -0
  476. package/src/security/untrusted-content.ts +3 -1
  477. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  478. package/src/skills/workspace-skill-md-signature.ts +50 -0
  479. package/src/subagent/consult-context.ts +12 -25
  480. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  481. package/src/tools/acp/list-agents.test.ts +3 -1
  482. package/src/tools/acp/spawn.test.ts +146 -11
  483. package/src/tools/acp/spawn.ts +21 -3
  484. package/src/tools/acp/status.test.ts +94 -0
  485. package/src/tools/acp/status.ts +40 -3
  486. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  487. package/src/tools/browser/browser-execution.ts +1 -1
  488. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  489. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  490. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  491. package/src/tools/browser/network-recorder.ts +2 -1
  492. package/src/tools/capability-offer.test.ts +22 -2
  493. package/src/tools/capability-offer.ts +16 -1
  494. package/src/tools/computer-use/definitions.ts +121 -2
  495. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  496. package/src/tools/credentials/metadata-store.ts +100 -0
  497. package/src/tools/filesystem/search.ts +2 -1
  498. package/src/tools/host-filesystem/edit.test.ts +2 -1
  499. package/src/tools/host-filesystem/edit.ts +2 -2
  500. package/src/tools/host-filesystem/read.test.ts +2 -1
  501. package/src/tools/host-filesystem/read.ts +2 -2
  502. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  503. package/src/tools/host-filesystem/transfer.ts +2 -2
  504. package/src/tools/host-filesystem/write.test.ts +2 -1
  505. package/src/tools/host-filesystem/write.ts +2 -2
  506. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  507. package/src/tools/host-terminal/host-shell.ts +4 -20
  508. package/src/tools/network/web-search.ts +6 -1
  509. package/src/tools/policy-context.ts +6 -7
  510. package/src/tools/skills/scaffold-managed.ts +61 -3
  511. package/src/tools/subagent/spawn.ts +5 -8
  512. package/src/tools/terminal/__tests__/safe-env.test.ts +6 -6
  513. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  514. package/src/tools/terminal/safe-env.ts +23 -3
  515. package/src/tools/terminal/sanitized-bash.ts +105 -0
  516. package/src/tools/watch/watch-retro-report.ts +0 -8
  517. package/src/types/host-capabilities.ts +5 -3
  518. package/src/util/__tests__/language-subtag.test.ts +23 -1
  519. package/src/util/ca-bundle.ts +13 -0
  520. package/src/util/language-subtag.ts +16 -0
  521. package/src/util/platform.ts +8 -0
  522. package/src/util/unicode.ts +15 -1
  523. package/src/watch/watch-timeline.ts +2 -2
  524. package/src/workflows/run-manager.ts +2 -1
  525. package/src/workspace/git-porcelain.ts +66 -0
  526. package/src/workspace/git-service.ts +6 -55
  527. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  528. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  529. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  530. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  531. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  532. package/src/workspace/migrations/registry.ts +6 -0
  533. package/src/__tests__/app-source-watcher.test.ts +0 -218
  534. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  535. package/src/__tests__/speaker-identification.test.ts +0 -56
  536. package/src/calls/speaker-identification.ts +0 -233
  537. package/src/daemon/app-source-watcher.ts +0 -218
  538. package/src/messaging/providers/whatsapp/client.ts +0 -29
  539. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  540. package/src/util/fs-watcher-error.ts +0 -36
  541. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -0,0 +1,191 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, expect, test } from "bun:test";
5
+
6
+ import sharp from "sharp";
7
+
8
+ import {
9
+ __resetResvgCacheForTests,
10
+ __setResvgCacheForTests,
11
+ } from "../avatar/resvg-lazy.js";
12
+ import {
13
+ renderCurrentDesktopWallpaper,
14
+ renderDesktopWallpaper,
15
+ } from "./desktop-wallpaper.js";
16
+
17
+ let workspace: string;
18
+ let previousWorkspace: string | undefined;
19
+ beforeEach(() => {
20
+ workspace = mkdtempSync(join(tmpdir(), "desktop-wallpaper-test-"));
21
+ previousWorkspace = process.env.VELLUM_WORKSPACE_DIR;
22
+ process.env.VELLUM_WORKSPACE_DIR = workspace;
23
+ mkdirSync(join(workspace, "data/avatar"), { recursive: true });
24
+ });
25
+ afterEach(() => {
26
+ __resetResvgCacheForTests();
27
+ if (previousWorkspace === undefined) {
28
+ delete process.env.VELLUM_WORKSPACE_DIR;
29
+ } else {
30
+ process.env.VELLUM_WORKSPACE_DIR = previousWorkspace;
31
+ }
32
+ rmSync(workspace, { recursive: true, force: true });
33
+ });
34
+
35
+ function manifest(state: Record<string, unknown>): void {
36
+ writeFileSync(
37
+ join(workspace, "data/avatar/avatar.json"),
38
+ JSON.stringify(state),
39
+ );
40
+ }
41
+
42
+ async function centerPixel(png: Buffer): Promise<number[]> {
43
+ const { data, info } = await sharp(png)
44
+ .ensureAlpha()
45
+ .raw()
46
+ .toBuffer({ resolveWithObject: true });
47
+ const offset =
48
+ (Math.round(info.height * 0.46) * info.width + info.width / 2) * 4;
49
+ return [...data.subarray(offset, offset + 4)];
50
+ }
51
+
52
+ test("renders the current uploaded avatar into an opaque desktop wallpaper", async () => {
53
+ const png = await sharp({
54
+ create: { width: 32, height: 32, channels: 4, background: "#ff0000" },
55
+ })
56
+ .png()
57
+ .toBuffer();
58
+ writeFileSync(join(workspace, "data/avatar/avatar-image.png"), png);
59
+ manifest({
60
+ kind: "image",
61
+ source: "upload",
62
+ traits: null,
63
+ image: { updatedAt: "2026-01-01T00:00:00Z", etag: "test" },
64
+ accent: null,
65
+ });
66
+ const result = (await renderCurrentDesktopWallpaper(480, 300))!;
67
+ expect(await centerPixel(result)).toEqual([255, 0, 0, 255]);
68
+ const metadata = await sharp(result).metadata();
69
+ expect([metadata.width, metadata.height]).toEqual([480, 300]);
70
+ expect((await sharp(result).stats()).isOpaque).toBe(true);
71
+ });
72
+
73
+ test("regenerates a character from current traits when its raster is missing", async () => {
74
+ manifest({
75
+ kind: "character",
76
+ source: "pool",
77
+ traits: { bodyShape: "blob", eyeStyle: "curious", color: "green" },
78
+ image: null,
79
+ accent: null,
80
+ });
81
+ const result = (await renderCurrentDesktopWallpaper(480, 300))!;
82
+ const empty = renderDesktopWallpaper(480, 300, null, null);
83
+ expect(await centerPixel(result)).not.toEqual(await centerPixel(empty));
84
+ });
85
+
86
+ test("missing and unreadable avatars produce the same neutral background", async () => {
87
+ const missing = await renderCurrentDesktopWallpaper(480, 300);
88
+ manifest({
89
+ kind: "image",
90
+ source: "upload",
91
+ traits: null,
92
+ image: { updatedAt: "2026-01-01T00:00:00Z", etag: "test" },
93
+ accent: null,
94
+ });
95
+ writeFileSync(
96
+ join(workspace, "data/avatar/avatar-image.png"),
97
+ "not an image",
98
+ );
99
+ expect(await renderCurrentDesktopWallpaper(480, 300)).toEqual(missing);
100
+ expect((await sharp(missing!).stats()).isOpaque).toBe(true);
101
+ });
102
+
103
+ test("accent changes tint the background and malformed accents fall back safely", async () => {
104
+ const neutral = renderDesktopWallpaper(480, 300, null, null);
105
+ expect(
106
+ renderDesktopWallpaper(480, 300, null, '\"/><image href="file:///test"/>'),
107
+ ).toEqual(neutral);
108
+ const red = await centerPixel(
109
+ renderDesktopWallpaper(480, 300, null, "#ff0000"),
110
+ );
111
+ const blue = await centerPixel(
112
+ renderDesktopWallpaper(480, 300, null, "#0000ff"),
113
+ );
114
+ expect(red[0]!).toBeGreaterThan(blue[0]!);
115
+ expect(blue[2]!).toBeGreaterThan(red[2]!);
116
+ });
117
+
118
+ test("a missing native renderer leaves the existing desktop background alone", async () => {
119
+ __setResvgCacheForTests({
120
+ available: false,
121
+ error: new Error("unavailable"),
122
+ });
123
+ expect(await renderCurrentDesktopWallpaper(480, 300)).toBeNull();
124
+ });
125
+
126
+ async function wordmarkPixels(
127
+ png: Buffer,
128
+ ): Promise<{ x: number; y: number }[]> {
129
+ const { data, info } = await sharp(png)
130
+ .removeAlpha()
131
+ .raw()
132
+ .toBuffer({ resolveWithObject: true });
133
+ const pixels: { x: number; y: number }[] = [];
134
+ for (let y = 0; y < info.height; y++) {
135
+ for (let x = 0; x < info.width; x++) {
136
+ const offset = (y * info.width + x) * info.channels;
137
+ if (
138
+ data[offset]! > 150 &&
139
+ data[offset + 1]! > 150 &&
140
+ data[offset + 2]! > 150
141
+ ) {
142
+ pixels.push({ x, y });
143
+ }
144
+ }
145
+ }
146
+ return pixels;
147
+ }
148
+
149
+ test("renders the current identity name and picks up a rename on the next refresh", async () => {
150
+ const identity = join(workspace, "IDENTITY.md");
151
+ writeFileSync(identity, "- **Name:** Alice\n");
152
+ const first = (await renderCurrentDesktopWallpaper(480, 300))!;
153
+ expect(first).toEqual(renderDesktopWallpaper(480, 300, null, null, "Alice"));
154
+ const pixels = await wordmarkPixels(first);
155
+ expect(pixels.length).toBeGreaterThan(100);
156
+ expect(pixels.every(({ y }) => y > 300 * 0.6 && y < 300 * 0.72)).toBe(true);
157
+
158
+ writeFileSync(identity, "- **Name:** Bob\n");
159
+ const renamed = await renderCurrentDesktopWallpaper(480, 300);
160
+ expect(renamed).not.toEqual(first);
161
+ expect(renamed).toEqual(renderDesktopWallpaper(480, 300, null, null, "Bob"));
162
+ });
163
+
164
+ test("an unset or template identity displays Vellum OS", async () => {
165
+ const fallback = renderDesktopWallpaper(480, 300, null, null, "Vellum");
166
+ expect(await renderCurrentDesktopWallpaper(480, 300)).toEqual(fallback);
167
+ writeFileSync(
168
+ join(workspace, "IDENTITY.md"),
169
+ "- **Name:** _(not yet chosen)_\n",
170
+ );
171
+ expect(await renderCurrentDesktopWallpaper(480, 300)).toEqual(fallback);
172
+ });
173
+
174
+ test("long names fit inside the wallpaper without losing the wordmark", async () => {
175
+ const png = renderDesktopWallpaper(480, 300, null, null, "W".repeat(32));
176
+ const pixels = await wordmarkPixels(png);
177
+ expect(pixels.length).toBeGreaterThan(100);
178
+ expect(pixels.every(({ x }) => x > 480 * 0.13 && x < 480 * 0.87)).toBe(true);
179
+ });
180
+
181
+ test("names containing XML are rendered as text without injecting SVG shapes", async () => {
182
+ const name =
183
+ 'Alice & </text><rect width="480" height="300" fill="red"/><text>';
184
+ const png = renderDesktopWallpaper(480, 300, null, null, name);
185
+ const fallback = renderDesktopWallpaper(480, 300, null, null);
186
+ const top = { left: 0, top: 0, width: 480, height: 150 };
187
+ expect(await sharp(png).extract(top).raw().toBuffer()).toEqual(
188
+ await sharp(fallback).extract(top).raw().toBuffer(),
189
+ );
190
+ expect((await wordmarkPixels(png)).length).toBeGreaterThan(0);
191
+ });
@@ -0,0 +1,113 @@
1
+ import { readAvatarState } from "../avatar/avatar-manifest.js";
2
+ import {
3
+ renderNotificationAvatarPng,
4
+ resolveNotificationAccentHex,
5
+ } from "../avatar/notification-avatar.js";
6
+ import { getResvg, isResvgAvailable } from "../avatar/resvg-lazy.js";
7
+ import { isTemplatePlaceholder } from "../daemon/handlers/identity.js";
8
+ import { getAssistantName } from "../daemon/identity-helpers.js";
9
+ import { escapeXmlContent } from "../util/xml.js";
10
+
11
+ function wallpaperWordmark(
12
+ width: number,
13
+ height: number,
14
+ name: string | null,
15
+ ): string {
16
+ const normalized = name?.replace(/\s+/g, " ").trim();
17
+ const label =
18
+ normalized && !isTemplatePlaceholder(normalized) ? normalized : "Vellum";
19
+ const text = `<text id="wordmark" font-family="Liberation Sans, Arial, sans-serif" font-size="64" font-weight="700" letter-spacing="1.5">${escapeXmlContent(`${label} OS`)}</text>`;
20
+ const Resvg = getResvg();
21
+ const bounds = new Resvg(
22
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">${text}</svg>`,
23
+ ).getBBox();
24
+ if (!bounds || bounds.width === 0) {
25
+ return "";
26
+ }
27
+ const depth = 6;
28
+ const scale = Math.min(
29
+ (height * 0.06) / 64,
30
+ (width * 0.72) / (bounds.width + depth),
31
+ );
32
+ const x = width / 2 - (bounds.x + (bounds.width + depth) / 2) * scale;
33
+ const layers = Array.from({ length: depth }, (_, index) => {
34
+ const offset = depth - index;
35
+ return `<use href="#wordmark" transform="translate(${offset} ${offset})" fill="#40535b"/>`;
36
+ }).join("");
37
+ return `<defs>
38
+ ${text}
39
+ <linearGradient id="letter-face" x2="0" y2="1">
40
+ <stop stop-color="#ffffff"/><stop offset=".48" stop-color="#e0e9e7"/>
41
+ <stop offset="1" stop-color="#9eb4ba"/>
42
+ </linearGradient>
43
+ </defs>
44
+ <g transform="translate(${x} ${height * 0.69}) scale(${scale})">
45
+ <use href="#wordmark" transform="translate(9 12)" fill="#080f14" opacity=".35"/>
46
+ ${layers}
47
+ <use href="#wordmark" fill="url(#letter-face)" stroke="#e8f1ed" stroke-opacity=".4" stroke-width=".5"/>
48
+ </g>`;
49
+ }
50
+
51
+ export function renderDesktopWallpaper(
52
+ width: number,
53
+ height: number,
54
+ avatar: Buffer | null,
55
+ accentHex: string | null,
56
+ assistantName: string | null = null,
57
+ ): Buffer {
58
+ const accent = /^#[\da-f]{6}$/i.test(accentHex ?? "")
59
+ ? accentHex!
60
+ : "#91a6b0";
61
+ const cx = width / 2;
62
+ const cy = height * 0.46;
63
+ const size = Math.min(width, height) * 0.24;
64
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
65
+ <defs>
66
+ <linearGradient id="base" x2="1" y2="1">
67
+ <stop stop-color="#202a30"/><stop offset="1" stop-color="#10171c"/>
68
+ </linearGradient>
69
+ <radialGradient id="glow">
70
+ <stop stop-color="${accent}" stop-opacity=".24"/>
71
+ <stop offset="1" stop-color="${accent}" stop-opacity="0"/>
72
+ </radialGradient>
73
+ <radialGradient id="shadow">
74
+ <stop stop-color="#000" stop-opacity=".25"/>
75
+ <stop offset="1" stop-color="#000" stop-opacity="0"/>
76
+ </radialGradient>
77
+ </defs>
78
+ <path fill="url(#base)" d="M0 0H${width}V${height}H0z"/>
79
+ <ellipse cx="${cx}" cy="${cy}" rx="${height * 0.7}" ry="${height * 0.6}" fill="url(#glow)"/>
80
+ <g fill="none" stroke="${accent}">
81
+ <circle cx="${cx}" cy="${cy}" r="${size * 0.76}" stroke-opacity=".16"/>
82
+ <circle cx="${cx}" cy="${cy}" r="${size * 1.25}" stroke-opacity=".09"/>
83
+ <circle cx="${cx}" cy="${cy}" r="${size * 1.9}" stroke-opacity=".05"/>
84
+ </g>
85
+ ${
86
+ avatar
87
+ ? `<ellipse cx="${cx}" cy="${cy + size * 0.48}" rx="${size * 0.7}" ry="${size * 0.16}" fill="url(#shadow)"/>
88
+ <image x="${cx - size / 2}" y="${cy - size / 2}" width="${size}" height="${size}" href="data:image/png;base64,${avatar.toString("base64")}"/>`
89
+ : ""
90
+ }
91
+ ${wallpaperWordmark(width, height, assistantName)}
92
+ </svg>`;
93
+ const Resvg = getResvg();
94
+ return Buffer.from(new Resvg(svg).render().asPng());
95
+ }
96
+
97
+ export async function renderCurrentDesktopWallpaper(
98
+ width: number,
99
+ height: number,
100
+ ): Promise<Buffer | null> {
101
+ if (!isResvgAvailable()) {
102
+ return null;
103
+ }
104
+ const state = readAvatarState();
105
+ const name = getAssistantName();
106
+ return renderDesktopWallpaper(
107
+ width,
108
+ height,
109
+ await renderNotificationAvatarPng(state),
110
+ resolveNotificationAccentHex(state),
111
+ name,
112
+ );
113
+ }
@@ -0,0 +1,170 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+
5
+ import { escapeXmlAttr, escapeXmlContent } from "../util/xml.js";
6
+
7
+ const BUTTON_MASKS = {
8
+ close: [0x201, 0x102, 0x084, 0x048, 0x030, 0x030, 0x048, 0x084, 0x102, 0x201],
9
+ max: [0x3ff, 0x201, 0x201, 0x201, 0x201, 0x201, 0x201, 0x201, 0x201, 0x3ff],
10
+ max_toggled: [
11
+ 0x3fc, 0x204, 0x27f, 0x241, 0x241, 0x241, 0x3c1, 0x041, 0x041, 0x07f,
12
+ ],
13
+ iconify: [0, 0, 0, 0, 0x3ff, 0x3ff, 0, 0, 0, 0],
14
+ };
15
+
16
+ function windowTheme(accentHex: string | null): string {
17
+ const tint = (base: string, amount: number): string => {
18
+ if (!accentHex || !/^#[\da-f]{6}$/i.test(accentHex)) {
19
+ return base;
20
+ }
21
+ return (
22
+ "#" +
23
+ [1, 3, 5]
24
+ .map((offset) => {
25
+ const from = parseInt(base.slice(offset, offset + 2), 16);
26
+ const to = parseInt(accentHex.slice(offset, offset + 2), 16);
27
+ return Math.round(from + (to - from) * amount)
28
+ .toString(16)
29
+ .padStart(2, "0");
30
+ })
31
+ .join("")
32
+ );
33
+ };
34
+ return `
35
+ border.width: 1
36
+ padding.width: 8
37
+ padding.height: 7
38
+ window.client.padding.width: 0
39
+ window.client.padding.height: 0
40
+ window.handle.width: 3
41
+ window.label.text.justify: Center
42
+ window.*.label.text.font: shadow=n
43
+ window.*.title.bg: Flat Solid
44
+ window.*.label.bg: Parentrelative
45
+ window.*.handle.bg: Flat Solid
46
+ window.*.grip.bg: Flat Solid
47
+ window.*.button.*.bg: Parentrelative
48
+ window.*.button.*.hover.bg: Flat Solid
49
+ window.*.button.*.pressed.bg: Flat Solid
50
+ window.active.title.bg: Flat Gradient Horizontal
51
+ window.active.title.bg.color: ${tint("#292930", 0.28)}
52
+ window.active.title.bg.colorTo: #292930
53
+ window.active.border.color: #4a4a54
54
+ window.active.title.separator.color: #202026
55
+ window.active.label.text.color: #f0f0f4
56
+ window.active.handle.bg.color: #292930
57
+ window.active.grip.bg.color: #292930
58
+ window.active.button.*.image.color: #d4d4dc
59
+ window.active.button.*.hover.bg.color: ${tint("#45454f", 0.22)}
60
+ window.active.button.*.pressed.bg.color: ${tint("#555561", 0.18)}
61
+ window.active.button.*.disabled.image.color: #62626e
62
+ window.active.button.close.hover.bg.color: #b64150
63
+ window.active.button.close.pressed.bg.color: #963442
64
+ window.inactive.title.bg.color: #232329
65
+ window.inactive.border.color: #36363f
66
+ window.inactive.title.separator.color: #202026
67
+ window.inactive.label.text.color: #a6a6b2
68
+ window.inactive.handle.bg.color: #232329
69
+ window.inactive.grip.bg.color: #232329
70
+ window.inactive.button.*.image.color: #858592
71
+ window.inactive.button.*.hover.bg.color: #3b3b45
72
+ window.inactive.button.*.pressed.bg.color: #4a4a54
73
+ window.inactive.button.*.disabled.image.color: #565660
74
+ menu.border.width: 1
75
+ menu.border.color: #4a4a54
76
+ menu.title.bg: Flat Solid
77
+ menu.title.bg.color: #292930
78
+ menu.title.text.color: #f0f0f4
79
+ menu.title.text.font: shadow=n
80
+ menu.items.bg: Flat Solid
81
+ menu.items.bg.color: #232329
82
+ menu.items.text.color: #e6e6ee
83
+ menu.items.disabled.text.color: #858592
84
+ menu.items.active.bg: Flat Solid
85
+ menu.items.active.bg.color: #45454f
86
+ menu.items.active.text.color: #ffffff
87
+ menu.items.active.disabled.text.color: #a6a6b2
88
+ menu.separator.color: #45454f
89
+ osd.bg: Flat Solid
90
+ osd.bg.color: #292930
91
+ osd.border.color: #4a4a54
92
+ osd.label.text.color: #f0f0f4
93
+ `;
94
+ }
95
+
96
+ /** Keep Openbox bindings and placement rules while styling its decorations. */
97
+ export function writeDesktopWindowTheme(
98
+ configDir: string,
99
+ home?: string,
100
+ accentHex: string | null = null,
101
+ ): string {
102
+ let source: string | undefined;
103
+ let sourcePath = "";
104
+ const candidates = [
105
+ ...(home ? [join(home, ".config", "openbox", "rc.xml")] : []),
106
+ "/etc/xdg/openbox/rc.xml",
107
+ ];
108
+ for (const path of candidates) {
109
+ try {
110
+ source = readFileSync(path, "utf8");
111
+ sourcePath = path;
112
+ break;
113
+ } catch (err) {
114
+ if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
115
+ throw err;
116
+ }
117
+ }
118
+ }
119
+ if (!source || !/<theme>[^]*?<\/theme>/.test(source)) {
120
+ throw new Error("Openbox theme configuration is unavailable");
121
+ }
122
+ const root = source.match(/<openbox_config\b[^>]*>/)?.[0];
123
+ if (!root || /\bxml:base\s*=/.test(root)) {
124
+ throw new Error("Openbox configuration requires its original base URI");
125
+ }
126
+ // Relative XIncludes keep resolving beside the source configuration.
127
+ source = source.replace(root, () =>
128
+ root.replace(
129
+ "<openbox_config",
130
+ `<openbox_config xml:base="${escapeXmlAttr(pathToFileURL(sourcePath).href)}"`,
131
+ ),
132
+ );
133
+ const themeDir = join(configDir, "window-theme");
134
+ mkdirSync(join(themeDir, "openbox-3"), { recursive: true });
135
+ writeFileSync(
136
+ join(themeDir, "openbox-3", "themerc"),
137
+ windowTheme(accentHex).trimStart(),
138
+ );
139
+ for (const [name, rows] of Object.entries(BUTTON_MASKS)) {
140
+ const bytes = rows.flatMap((row) => [row & 0xff, row >> 8]);
141
+ writeFileSync(
142
+ join(themeDir, "openbox-3", `${name}.xbm`),
143
+ `#define button_width 10\n#define button_height 10\nstatic unsigned char button_bits[] = {\n${bytes.map((byte) => `0x${byte.toString(16)}`).join(",")}};\n`,
144
+ );
145
+ }
146
+ const fonts = [
147
+ "ActiveWindow",
148
+ "InactiveWindow",
149
+ "MenuHeader",
150
+ "MenuItem",
151
+ "ActiveOnScreenDisplay",
152
+ "InactiveOnScreenDisplay",
153
+ ].map(
154
+ (place) =>
155
+ `<font place="${place}"><name>sans</name><size>10</size><weight>normal</weight><slant>normal</slant></font>`,
156
+ );
157
+ const theme = `<theme>
158
+ <name>${escapeXmlContent(themeDir)}</name>
159
+ <titleLayout>LIMC</titleLayout>
160
+ <keepBorder>yes</keepBorder>
161
+ <animateIconify>yes</animateIconify>
162
+ ${fonts.join("\n ")}
163
+ </theme>`;
164
+ const configPath = join(configDir, "window-manager.xml");
165
+ writeFileSync(
166
+ configPath,
167
+ source.replace(/<theme>[^]*?<\/theme>/, () => theme),
168
+ );
169
+ return configPath;
170
+ }
@@ -1,6 +1,8 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
+ import { channelForBotProvider } from "@vellumai/service-contracts/channels";
5
+
4
6
  import type { HeartbeatAlertEvent } from "../api/events/heartbeat-alert.js";
5
7
  import { getConfig } from "../config/loader.js";
6
8
  import type { HeartbeatConfig } from "../config/schemas/heartbeat.js";
@@ -55,6 +57,19 @@ const DEFAULT_CHECKLIST = `- Check in with yourself. Read NOW.md. Is it still ac
55
57
  const EARLY_HEARTBEAT_THRESHOLD = 3;
56
58
  const REENGAGEMENT_COOLDOWN_MS = 18 * 60 * 60 * 1000; // 18 hours
57
59
 
60
+ /**
61
+ * A provider key named by the identity it carries. The keys alone do not say
62
+ * which is which (`slack` is the integration acting as the connected person,
63
+ * `slack_channel` is the assistant's own bot), and a heartbeat told to avoid
64
+ * "slack" would otherwise stop posting through a bot whose credential is fine.
65
+ */
66
+ function describeUnhealthyProvider(providerKey: string): string {
67
+ const channel = channelForBotProvider(providerKey);
68
+ return channel
69
+ ? `${providerKey} (the ${channel} channel bot)`
70
+ : `${providerKey} (integration, acts as the connected person)`;
71
+ }
72
+
58
73
  // Stripped-comment form of the guardian persona scaffold. Computed
59
74
  // once at module load because stripping comment lines is deterministic
60
75
  // and the template itself is a compile-time constant.
@@ -945,10 +960,13 @@ ${checklist}
945
960
  </heartbeat-checklist>`;
946
961
 
947
962
  if (unhealthyProviders.length > 0) {
948
- const providers = unhealthyProviders.join(", ");
963
+ const providers = unhealthyProviders
964
+ .map(describeUnhealthyProvider)
965
+ .join(", ");
949
966
  prompt += `\n\n<credential-status>
950
- The following providers have broken or expired credentials: ${providers}.
951
- Do NOT attempt to use tools for these providers — they will fail. Skip any checklist items that depend on them and note the outage in your summary.
967
+ The following credentials are broken or expired: ${providers}.
968
+ Do NOT attempt to use tools for these providers, they will fail. Skip any checklist items that depend on them and note the outage in your summary.
969
+ A channel bot is the assistant's own identity on a channel and a separate credential from the integration of the same name; it is affected only when listed here as a channel bot.
952
970
  </credential-status>`;
953
971
  }
954
972
 
@@ -459,6 +459,28 @@ describe("feed-writer", () => {
459
459
  expect(result!.summary).toBe("Fresh summary");
460
460
  });
461
461
 
462
+ test("a status value replaces, and a status updater reads write-time state", async () => {
463
+ await appendFeedItem(makeItem({ id: "item-1" }));
464
+
465
+ const replaced = await patchFeedItemContent("item-1", {
466
+ status: "acted_on",
467
+ });
468
+ expect(replaced!.status).toBe("acted_on");
469
+
470
+ // The updater sees the status as of the write, so a conditional
471
+ // transition cannot overwrite what another writer just set.
472
+ const observed: string[] = [];
473
+ const conditional = await patchFeedItemContent("item-1", {
474
+ status: (existing) => {
475
+ observed.push(existing.status);
476
+ return existing.status === "new" ? "seen" : existing.status;
477
+ },
478
+ });
479
+ expect(observed).toEqual(["acted_on"]);
480
+ expect(conditional!.status).toBe("acted_on");
481
+ expect(readFileJson().items[0]!.status).toBe("acted_on");
482
+ });
483
+
462
484
  test("returns null for an unknown id", async () => {
463
485
  await appendFeedItem(makeItem({ id: "known" }));
464
486
  expect(await patchFeedItemContent("unknown", { title: "x" })).toBeNull();
@@ -185,7 +185,13 @@ export interface FeedItemContentPatch {
185
185
  title?: string;
186
186
  summary?: string;
187
187
  urgency?: FeedItemUrgency;
188
- status?: FeedItemStatus;
188
+ /**
189
+ * New status, or an updater applied inside the coalescing queue so it
190
+ * reads the item as of write time. Use the updater for a conditional
191
+ * transition: a plain value would overwrite a status another writer
192
+ * set while the patch sat in the queue.
193
+ */
194
+ status?: FeedItemStatus | ((existing: FeedItem) => FeedItemStatus);
189
195
  /**
190
196
  * Updater for the item's guardian projection, applied inside the
191
197
  * coalescing queue so it reads the projection as of write time (a
@@ -399,7 +405,10 @@ async function runWrite(): Promise<void> {
399
405
  updated.urgency = patch.urgency;
400
406
  }
401
407
  if (patch.status !== undefined) {
402
- updated.status = patch.status;
408
+ updated.status =
409
+ typeof patch.status === "function"
410
+ ? patch.status(existing)
411
+ : patch.status;
403
412
  }
404
413
  if (patch.guardianRequest !== undefined && existing.guardianRequest) {
405
414
  updated.guardianRequest = patch.guardianRequest(existing.guardianRequest);
@@ -679,3 +679,36 @@ export interface ConversationDeletedContext
679
679
  * {@link BaseHookContext}.
680
680
  */
681
681
  export type ConversationsClearedContext = BaseHookContext;
682
+
683
+ // ─── Message-deleted hook context ────────────────────────────────────────────
684
+
685
+ /**
686
+ * Context passed to the `message-deleted` hook. Fires once per deleted
687
+ * message row, from the shared single-message delete primitive, after the row
688
+ * is removed. Whole-conversation deletes dispatch `conversation-deleted`
689
+ * instead.
690
+ *
691
+ * Fire-and-forget like `conversation-deleted`: the primitive is synchronous
692
+ * and does not wait for the chain. The row is gone by the time a hook runs,
693
+ * so the context carries the row's `createdAt` alongside its id. Together
694
+ * they are the `(createdAt, id)` position the row held, which is what a hook
695
+ * keeping a cursor on that row needs to record once the row can no longer be
696
+ * looked up. The default memory plugin contributes a hook here that stamps
697
+ * its retrospective cursor.
698
+ */
699
+ export interface MessageDeletedInputContext {
700
+ /** ID of the conversation that owned the row. */
701
+ readonly conversationId: string;
702
+ /** ID of the deleted message row. */
703
+ readonly messageId: string;
704
+ /** The deleted row's `createdAt`. */
705
+ readonly createdAt: number;
706
+ }
707
+
708
+ /**
709
+ * The full `message-deleted` context a hook receives: the dispatching call
710
+ * site's {@link MessageDeletedInputContext} plus the pipeline-stamped
711
+ * {@link BaseHookContext} capabilities.
712
+ */
713
+ export interface MessageDeletedContext
714
+ extends MessageDeletedInputContext, BaseHookContext {}
@@ -0,0 +1,22 @@
1
+ # Daemon i18n
2
+
3
+ User-facing copy the assistant generates as a hardcoded constant goes
4
+ through `assistant/src/i18n/`. The web app has its own catalogs. This
5
+ module is for constants the daemon writes or emits.
6
+
7
+ ## Rules
8
+
9
+ 1. Add a key to `MESSAGE_KEYS` and to every locale in `MESSAGE_CATALOGS`.
10
+ 2. Persist the key when a column holds a system constant. Never persist a
11
+ translated string.
12
+ 3. Resolve with `t(key, locale)` or a typed helper (`resolveConversationTitle`)
13
+ at the edge that emits the string.
14
+ 4. Do not match stored display strings back to keys. A stored title may be
15
+ in any language on purpose.
16
+ 5. Locale comes from `Accept-Language` (`localeFromAcceptLanguage`) or an
17
+ explicit CLI flag. Default `en` lives in this module, not at each caller.
18
+
19
+ Conversation titles persist `conversation.title.generating` /
20
+ `conversation.title.untitled`. `resolveConversationTitle` turns those keys
21
+ into catalog copy. Legacy English placeholders stay replaceable via the
22
+ title service patterns and pass through on the wire until they are retitled.