@vellumai/assistant 0.12.1 → 0.12.2-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 (577) hide show
  1. package/AGENTS.md +4 -0
  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/node_modules/@vellumai/slack-text/src/index.ts +13 -8
  6. package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
  7. package/openapi.yaml +360 -51
  8. package/package.json +3 -1
  9. package/scripts/smoke-desktop-window-theme.ts +186 -0
  10. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  11. package/src/__tests__/anthropic-provider.test.ts +0 -63
  12. package/src/__tests__/app-compiler.test.ts +6 -0
  13. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  14. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  15. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  16. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  17. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  18. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  19. package/src/__tests__/computer-use-tools.test.ts +19 -7
  20. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  21. package/src/__tests__/config-schema.test.ts +28 -29
  22. package/src/__tests__/config-watcher.test.ts +23 -8
  23. package/src/__tests__/connection-model-compat.test.ts +2 -2
  24. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  25. package/src/__tests__/conversation-pairing.test.ts +26 -0
  26. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  27. package/src/__tests__/conversation-queue.test.ts +48 -207
  28. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  29. package/src/__tests__/conversation-runtime-assembly.test.ts +28 -0
  30. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  31. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  32. package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
  33. package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
  34. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  35. package/src/__tests__/conversation-title-service.test.ts +14 -10
  36. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  37. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  38. package/src/__tests__/credential-record-retire.test.ts +171 -1
  39. package/src/__tests__/credential-routes.test.ts +250 -3
  40. package/src/__tests__/credential-security-invariants.test.ts +5 -3
  41. package/src/__tests__/cu-unified-flow.test.ts +14 -4
  42. package/src/__tests__/document-sync-tags.test.ts +25 -0
  43. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  44. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  45. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  46. package/src/__tests__/git-porcelain.test.ts +47 -0
  47. package/src/__tests__/heartbeat-service.test.ts +56 -40
  48. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  49. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  50. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  51. package/src/__tests__/host-cu-proxy.test.ts +596 -0
  52. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  53. package/src/__tests__/host-file-proxy.test.ts +11 -23
  54. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  55. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  56. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  57. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  58. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  59. package/src/__tests__/mcp-health-check.test.ts +8 -11
  60. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  61. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  62. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  63. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  64. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  65. package/src/__tests__/model-intents.test.ts +3 -0
  66. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  67. package/src/__tests__/oauth-commands-routes.test.ts +168 -0
  68. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  69. package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
  70. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  71. package/src/__tests__/openai-provider.test.ts +1 -0
  72. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  73. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  74. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  75. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  76. package/src/__tests__/qdrant-manager.test.ts +132 -0
  77. package/src/__tests__/resolve-app.test.ts +135 -0
  78. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  79. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  80. package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
  81. package/src/__tests__/service-account-token.test.ts +247 -0
  82. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  83. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  84. package/src/__tests__/skills.test.ts +9 -4
  85. package/src/__tests__/source-fingerprint.test.ts +184 -0
  86. package/src/__tests__/subagent-tool-gate-mode.test.ts +70 -3
  87. package/src/__tests__/subagent-tools.test.ts +3 -2
  88. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  89. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  90. package/src/__tests__/ui-channel-variants.test.ts +1 -56
  91. package/src/__tests__/unicode.test.ts +13 -0
  92. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  93. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  94. package/src/__tests__/workspace-git-service.test.ts +26 -0
  95. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  96. package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
  97. package/src/acp/__tests__/acp-credentials.test.ts +13 -0
  98. package/src/acp/__tests__/agent-process.test.ts +258 -4
  99. package/src/acp/__tests__/auth-required.test.ts +96 -0
  100. package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
  101. package/src/acp/__tests__/client-handler.test.ts +90 -2
  102. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  103. package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
  104. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  105. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  106. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  107. package/src/acp/__tests__/session-manager.test.ts +895 -4
  108. package/src/acp/acp-claude-oauth.ts +328 -14
  109. package/src/acp/acp-credentials.ts +19 -0
  110. package/src/acp/agent-process.test.ts +4 -1
  111. package/src/acp/agent-process.ts +96 -10
  112. package/src/acp/auth-required.ts +50 -1
  113. package/src/acp/auto-install.test.ts +9 -7
  114. package/src/acp/claude-token-refresh.ts +150 -0
  115. package/src/acp/client-handler.ts +16 -3
  116. package/src/acp/model-config.test.ts +166 -0
  117. package/src/acp/model-config.ts +138 -0
  118. package/src/acp/prepare-agent-env.ts +21 -6
  119. package/src/acp/resolve-agent.test.ts +220 -6
  120. package/src/acp/resolve-agent.ts +88 -23
  121. package/src/acp/session-manager.test.ts +196 -3
  122. package/src/acp/session-manager.ts +441 -18
  123. package/src/acp/types.ts +50 -5
  124. package/src/agent/loop.ts +1 -0
  125. package/src/api/events/acp-session-model-update.test.ts +88 -0
  126. package/src/api/events/acp-session-model-update.ts +46 -0
  127. package/src/api/events/app-files-changed.ts +4 -3
  128. package/src/api/events/notification-intent.ts +2 -0
  129. package/src/api/events/user-message-echo.ts +7 -1
  130. package/src/api/index.ts +6 -0
  131. package/src/api/responses/conversation-message.ts +5 -0
  132. package/src/apps/app-store.ts +1 -1
  133. package/src/apps/resolve-app.ts +81 -0
  134. package/src/apps/source-fingerprint.ts +266 -0
  135. package/src/avatar/identity-avatar.ts +7 -2
  136. package/src/bundler/app-compiler.ts +15 -0
  137. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  138. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  139. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  140. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  141. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  142. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  143. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  144. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  145. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  146. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  147. package/src/calls/__tests__/voice-control-protocol.test.ts +120 -25
  148. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  149. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  150. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  151. package/src/calls/__tests__/voice-session-bridge.test.ts +56 -1
  152. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  153. package/src/calls/barge-in-guard.ts +118 -0
  154. package/src/calls/call-constants.ts +24 -4
  155. package/src/calls/call-controller.ts +499 -199
  156. package/src/calls/call-transport.ts +16 -0
  157. package/src/calls/media-stream-output.ts +32 -0
  158. package/src/calls/media-stream-server.ts +82 -30
  159. package/src/calls/media-stream-stt-session.ts +15 -0
  160. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  161. package/src/calls/resolve-call-tts-provider.ts +33 -2
  162. package/src/calls/tts-call-strategy.ts +9 -5
  163. package/src/calls/types.ts +2 -0
  164. package/src/calls/voice-control-protocol.ts +155 -21
  165. package/src/calls/voice-leg-coordinator.ts +189 -0
  166. package/src/calls/voice-progress-cadence.ts +423 -0
  167. package/src/calls/voice-session-bridge.ts +92 -72
  168. package/src/calls/voice-triage-escalate.ts +178 -59
  169. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  170. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  171. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  172. package/src/cli/commands/apps.help.ts +67 -13
  173. package/src/cli/commands/apps.ts +183 -0
  174. package/src/cli/commands/bash.help.ts +8 -13
  175. package/src/cli/commands/bash.ts +7 -25
  176. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  177. package/src/cli/commands/channels/index.help.ts +70 -2
  178. package/src/cli/commands/channels/index.ts +16 -3
  179. package/src/cli/commands/channels/send.ts +128 -0
  180. package/src/cli/commands/credentials.help.ts +6 -2
  181. package/src/cli/commands/credentials.ts +2 -2
  182. package/src/cli/commands/mcp.help.ts +8 -8
  183. package/src/cli/commands/mcp.ts +1 -1
  184. package/src/cli/commands/oauth/status.ts +5 -0
  185. package/src/cli/commands/roadmap.help.ts +5 -1
  186. package/src/cli/commands/roadmap.ts +1 -1
  187. package/src/cli/lib/bundled-marketplace.json +103 -90
  188. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  189. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  190. package/src/config/__tests__/platform-identity.test.ts +255 -0
  191. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  192. package/src/config/acp-agent-write.ts +43 -0
  193. package/src/config/acp-defaults.test.ts +13 -3
  194. package/src/config/acp-defaults.ts +23 -6
  195. package/src/config/acp-schema.test.ts +81 -0
  196. package/src/config/acp-schema.ts +30 -2
  197. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  198. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  199. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  200. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  201. package/src/config/bundled-skills/computer-use/SKILL.md +29 -2
  202. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  203. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  204. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  205. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  206. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  207. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  208. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  209. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  210. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  211. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  212. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  213. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  214. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  215. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  216. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  217. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  218. package/src/config/default-profile-catalog.ts +1 -1
  219. package/src/config/env.ts +2 -2
  220. package/src/config/feature-flag-registry.json +33 -13
  221. package/src/config/loader.ts +2 -0
  222. package/src/config/memory-v3-gate.test.ts +31 -0
  223. package/src/config/memory-v3-gate.ts +20 -16
  224. package/src/config/platform-identity.ts +201 -0
  225. package/src/config/platform-rehydration.ts +11 -27
  226. package/src/config/schema.ts +2 -2
  227. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  228. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  229. package/src/config/schemas/calls.ts +1 -28
  230. package/src/config/schemas/live-voice.ts +1 -173
  231. package/src/config/schemas/mcp.ts +1 -1
  232. package/src/config/schemas/memory-retrospective.ts +10 -0
  233. package/src/config/schemas/services.ts +18 -0
  234. package/src/config/schemas/skills.ts +0 -14
  235. package/src/config/schemas/voice.ts +150 -0
  236. package/src/credential-execution/process-manager.test.ts +18 -19
  237. package/src/credential-health/credential-health-service.ts +11 -17
  238. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  239. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  240. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  241. package/src/daemon/app-change-notify.ts +45 -0
  242. package/src/daemon/config-watcher.ts +13 -194
  243. package/src/daemon/conversation-error.ts +3 -2
  244. package/src/daemon/conversation-media-retry.ts +2 -1
  245. package/src/daemon/conversation-process.ts +35 -64
  246. package/src/daemon/conversation-runtime-assembly.ts +20 -7
  247. package/src/daemon/conversation-skill-tools.ts +13 -31
  248. package/src/daemon/conversation-store.ts +1 -1
  249. package/src/daemon/conversation-surfaces.ts +21 -10
  250. package/src/daemon/conversation-tool-setup.ts +45 -59
  251. package/src/daemon/conversation.ts +0 -3
  252. package/src/daemon/dictation-profile-store.ts +2 -1
  253. package/src/daemon/handlers/shared.ts +2 -1
  254. package/src/daemon/handlers/skills.ts +6 -3
  255. package/src/daemon/host-bash-proxy.ts +8 -12
  256. package/src/daemon/host-browser-proxy.ts +15 -1
  257. package/src/daemon/host-cu-proxy.ts +224 -19
  258. package/src/daemon/host-file-proxy.ts +8 -10
  259. package/src/daemon/host-proxy-preactivation.ts +2 -1
  260. package/src/daemon/host-transfer-proxy.ts +14 -18
  261. package/src/daemon/lifecycle.ts +0 -5
  262. package/src/daemon/mcp-reload-service.ts +5 -5
  263. package/src/daemon/message-types/sync.ts +1 -0
  264. package/src/daemon/orphan-reaper.ts +1 -1
  265. package/src/daemon/providers-setup.ts +1 -1
  266. package/src/daemon/reaction-history-render.ts +2 -1
  267. package/src/daemon/shutdown-handlers.ts +1 -9
  268. package/src/daemon/skill-memory-refresh.ts +64 -0
  269. package/src/daemon/tool-side-effects.ts +7 -18
  270. package/src/desktop/__tests__/fake-desktop.ts +122 -0
  271. package/src/desktop/desktop-chrome-policy.test.ts +79 -0
  272. package/src/desktop/desktop-chrome-policy.ts +47 -0
  273. package/src/desktop/desktop-chrome-session.ts +37 -0
  274. package/src/desktop/desktop-dependencies.test.ts +102 -0
  275. package/src/desktop/desktop-dependencies.ts +308 -0
  276. package/src/desktop/desktop-feature.test.ts +18 -0
  277. package/src/desktop/desktop-feature.ts +19 -0
  278. package/src/desktop/desktop-panel-config.test.ts +135 -0
  279. package/src/desktop/desktop-panel-config.ts +149 -0
  280. package/src/desktop/desktop-session-manager.test.ts +730 -0
  281. package/src/desktop/desktop-session-manager.ts +850 -0
  282. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  283. package/src/desktop/desktop-stream-bridge.ts +214 -0
  284. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  285. package/src/desktop/desktop-wallpaper.ts +113 -0
  286. package/src/desktop/desktop-window-drag.ts +177 -0
  287. package/src/desktop/desktop-window-manager-config.test.ts +230 -0
  288. package/src/desktop/desktop-window-manager-config.ts +238 -0
  289. package/src/desktop/desktop-window-theme.ts +170 -0
  290. package/src/heartbeat/heartbeat-service.ts +21 -3
  291. package/src/home/__tests__/feed-writer.test.ts +22 -0
  292. package/src/home/feed-writer.ts +11 -2
  293. package/src/hooks/types.ts +33 -0
  294. package/src/i18n/AGENTS.md +22 -0
  295. package/src/i18n/__tests__/i18n.test.ts +108 -0
  296. package/src/i18n/index.ts +16 -0
  297. package/src/i18n/locales.ts +93 -0
  298. package/src/i18n/messages.ts +61 -0
  299. package/src/i18n/title.ts +25 -0
  300. package/src/inbound/platform-callback-registration.ts +4 -5
  301. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +121 -0
  302. package/src/live-voice/__tests__/live-voice-events.test.ts +8 -7
  303. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  304. package/src/live-voice/__tests__/live-voice-progress.test.ts +94 -15
  305. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  306. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  307. package/src/live-voice/__tests__/protocol.test.ts +39 -0
  308. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  309. package/src/live-voice/__tests__/session-controls.test.ts +79 -0
  310. package/src/live-voice/live-voice-photo.ts +25 -6
  311. package/src/live-voice/live-voice-session.ts +297 -699
  312. package/src/live-voice/protocol.ts +60 -2
  313. package/src/live-voice/session-controls.ts +111 -0
  314. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  315. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  316. package/src/mcp/effective-config.ts +3 -3
  317. package/src/mcp/mcp-header-store.ts +29 -45
  318. package/src/mcp/spec-schema.ts +111 -0
  319. package/src/mcp/startup.ts +8 -4
  320. package/src/mcp/workspace-mcp-config.ts +188 -0
  321. package/src/messaging/provider.ts +10 -3
  322. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  323. package/src/messaging/providers/callback-routing.ts +18 -0
  324. package/src/messaging/providers/channel-transport.ts +56 -0
  325. package/src/messaging/providers/discord/transport.ts +17 -0
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  327. package/src/messaging/providers/slack/adapter.ts +7 -44
  328. package/src/messaging/providers/slack/transport.ts +16 -0
  329. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  330. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  331. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  332. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  333. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  334. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  335. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  336. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  337. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  338. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  339. package/src/notifications/adapters/macos.ts +3 -0
  340. package/src/notifications/assistant-reply-producer.ts +8 -3
  341. package/src/notifications/conversation-pairing.ts +36 -16
  342. package/src/notifications/delivered-post-record.ts +11 -1
  343. package/src/notifications/guardian-feed-projection.ts +125 -5
  344. package/src/notifications/home-feed-side-effect.ts +55 -7
  345. package/src/notifications/schedule-result-producer.ts +66 -22
  346. package/src/oauth/AGENTS.md +2 -0
  347. package/src/oauth/__tests__/seed-providers-managed.test.ts +149 -0
  348. package/src/oauth/byo-connection.test.ts +105 -0
  349. package/src/oauth/byo-connection.ts +36 -6
  350. package/src/oauth/connection-resolver.test.ts +100 -0
  351. package/src/oauth/connection-resolver.ts +120 -3
  352. package/src/oauth/provider-serializer.ts +9 -0
  353. package/src/oauth/scope-utils.ts +24 -0
  354. package/src/oauth/seed-providers.ts +314 -27
  355. package/src/outbound-proxy/certs.ts +3 -5
  356. package/src/permissions/checker.test.ts +11 -6
  357. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  358. package/src/persistence/conversation-crud.ts +71 -63
  359. package/src/persistence/conversation-lineage.ts +12 -4
  360. package/src/persistence/conversation-queries.ts +2 -2
  361. package/src/persistence/conversation-title-placeholders.ts +23 -0
  362. package/src/persistence/conversation-title-service.ts +9 -26
  363. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  364. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  365. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  366. package/src/persistence/message-cursor.ts +78 -0
  367. package/src/persistence/message-reads.ts +30 -0
  368. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  369. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  370. package/src/persistence/schema/memory-core.ts +5 -0
  371. package/src/platform/client.test.ts +4 -2
  372. package/src/platform/client.ts +2 -10
  373. package/src/plugin-api/constants.ts +2 -0
  374. package/src/plugin-api/index.ts +1 -0
  375. package/src/plugin-api/types.ts +2 -0
  376. package/src/plugins/defaults/index.ts +2 -0
  377. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  378. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  379. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  380. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  381. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  382. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  383. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  384. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  385. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  386. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  387. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  388. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  389. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  390. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  391. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  392. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  393. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  394. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  395. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  396. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  397. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  398. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  399. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  400. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  401. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  402. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  403. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  404. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  405. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  406. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  407. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  408. package/src/plugins/defaults/memory/startup.ts +28 -2
  409. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  410. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  411. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  412. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  413. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  414. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  415. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  416. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  417. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  418. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  419. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  420. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  421. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  422. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  423. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  424. package/src/plugins/defaults/memory/worker.ts +6 -0
  425. package/src/plugins/mcp-servers.ts +15 -75
  426. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  427. package/src/providers/__tests__/retry-callsite.test.ts +41 -0
  428. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  429. package/src/providers/anthropic/client.ts +0 -47
  430. package/src/providers/gemini/client.ts +9 -23
  431. package/src/providers/gemini-thought-signature.test.ts +63 -0
  432. package/src/providers/gemini-thought-signature.ts +44 -0
  433. package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
  434. package/src/providers/inference/auth.ts +2 -3
  435. package/src/providers/inference/connection-availability.ts +27 -8
  436. package/src/providers/inference/endpoint-probe.ts +10 -1
  437. package/src/providers/inference/resolve-auth.ts +22 -4
  438. package/src/providers/inference/service-account-token.ts +248 -0
  439. package/src/providers/model-catalog.ts +34 -0
  440. package/src/providers/model-intents.ts +10 -5
  441. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  442. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  443. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  444. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  445. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  446. package/src/providers/openai/chat-completions-provider.ts +212 -79
  447. package/src/providers/openai/codex-models.ts +0 -4
  448. package/src/providers/openai/google-thought-signature.ts +320 -0
  449. package/src/providers/opencode/client.test.ts +73 -4
  450. package/src/providers/opencode/client.ts +60 -3
  451. package/src/providers/openrouter/client.ts +20 -0
  452. package/src/providers/outbound-request-sanitize.ts +96 -0
  453. package/src/providers/retry.ts +30 -19
  454. package/src/providers/unparseable-tool-args.ts +5 -1
  455. package/src/runtime/AGENTS.md +3 -1
  456. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  457. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  458. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  459. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  460. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  461. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  462. package/src/runtime/agent-wake.ts +2 -1
  463. package/src/runtime/assistant-event-hub.ts +21 -3
  464. package/src/runtime/auth/same-actor.ts +38 -3
  465. package/src/runtime/channel-send.ts +316 -0
  466. package/src/runtime/host-observe.ts +11 -1
  467. package/src/runtime/http-server.ts +160 -151
  468. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
  469. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  470. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +275 -0
  471. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  472. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  473. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  474. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  475. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  476. package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
  477. package/src/runtime/routes/acp-routes.test.ts +15 -0
  478. package/src/runtime/routes/acp-routes.ts +72 -9
  479. package/src/runtime/routes/app-management-routes.ts +90 -2
  480. package/src/runtime/routes/app-routes.ts +1 -1
  481. package/src/runtime/routes/channel-send-routes.ts +85 -0
  482. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  483. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  484. package/src/runtime/routes/conversation-routes.ts +16 -3
  485. package/src/runtime/routes/credential-routes.ts +20 -14
  486. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  487. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  488. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  489. package/src/runtime/routes/events-routes.ts +4 -0
  490. package/src/runtime/routes/host-cu-routes.ts +6 -0
  491. package/src/runtime/routes/index.ts +4 -2
  492. package/src/runtime/routes/log-export-routes.ts +2 -0
  493. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  494. package/src/runtime/routes/migration-routes.ts +6 -20
  495. package/src/runtime/routes/oauth-commands-routes.ts +50 -0
  496. package/src/runtime/routes/oauth-providers.ts +7 -0
  497. package/src/runtime/routes/platform-routes.ts +18 -15
  498. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  499. package/src/runtime/routes/workspace-routes.ts +46 -63
  500. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  501. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  502. package/src/runtime/routes/workspace-utils.ts +17 -0
  503. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  504. package/src/runtime/services/conversation-serializer.ts +16 -3
  505. package/src/runtime/sync/resource-sync-events.ts +2 -1
  506. package/src/schedule/run-script.ts +2 -1
  507. package/src/schedule/schedule-store.ts +9 -2
  508. package/src/schedule/worker.ts +1 -1
  509. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  510. package/src/security/ces-http-record-backend.ts +120 -0
  511. package/src/security/ces-rpc-record-backend.ts +7 -0
  512. package/src/security/untrusted-content.ts +3 -1
  513. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  514. package/src/skills/workspace-skill-md-signature.ts +50 -0
  515. package/src/subagent/consult-context.ts +12 -25
  516. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  517. package/src/tools/acp/list-agents.test.ts +3 -1
  518. package/src/tools/acp/spawn.test.ts +146 -11
  519. package/src/tools/acp/spawn.ts +21 -3
  520. package/src/tools/acp/status.test.ts +94 -0
  521. package/src/tools/acp/status.ts +40 -3
  522. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  523. package/src/tools/browser/browser-execution.ts +1 -1
  524. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  525. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  526. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  527. package/src/tools/browser/network-recorder.ts +2 -1
  528. package/src/tools/capability-offer.test.ts +22 -2
  529. package/src/tools/capability-offer.ts +16 -1
  530. package/src/tools/computer-use/definitions.ts +121 -2
  531. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  532. package/src/tools/credentials/metadata-store.ts +100 -0
  533. package/src/tools/filesystem/search.ts +2 -1
  534. package/src/tools/host-filesystem/edit.test.ts +2 -1
  535. package/src/tools/host-filesystem/edit.ts +2 -2
  536. package/src/tools/host-filesystem/read.test.ts +2 -1
  537. package/src/tools/host-filesystem/read.ts +2 -2
  538. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  539. package/src/tools/host-filesystem/transfer.ts +2 -2
  540. package/src/tools/host-filesystem/write.test.ts +2 -1
  541. package/src/tools/host-filesystem/write.ts +2 -2
  542. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  543. package/src/tools/host-terminal/host-shell.ts +4 -20
  544. package/src/tools/network/web-search.ts +6 -1
  545. package/src/tools/policy-context.ts +6 -7
  546. package/src/tools/skills/scaffold-managed.ts +61 -3
  547. package/src/tools/subagent/spawn.ts +5 -8
  548. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  549. package/src/tools/terminal/sanitized-bash.ts +105 -0
  550. package/src/tools/ui-surface/channel-variants.ts +10 -59
  551. package/src/tools/watch/watch-retro-report.ts +0 -8
  552. package/src/types/host-capabilities.ts +5 -3
  553. package/src/util/__tests__/language-subtag.test.ts +23 -1
  554. package/src/util/ca-bundle.ts +13 -0
  555. package/src/util/language-subtag.ts +16 -0
  556. package/src/util/platform.ts +8 -0
  557. package/src/util/unicode.ts +15 -1
  558. package/src/watch/watch-retro.ts +6 -8
  559. package/src/watch/watch-timeline.ts +2 -2
  560. package/src/workflows/run-manager.ts +2 -1
  561. package/src/workspace/git-porcelain.ts +66 -0
  562. package/src/workspace/git-service.ts +6 -55
  563. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  564. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  565. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  566. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  567. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  568. package/src/workspace/migrations/registry.ts +6 -0
  569. package/src/__tests__/app-source-watcher.test.ts +0 -218
  570. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  571. package/src/__tests__/speaker-identification.test.ts +0 -56
  572. package/src/calls/speaker-identification.ts +0 -233
  573. package/src/daemon/app-source-watcher.ts +0 -218
  574. package/src/messaging/providers/whatsapp/client.ts +0 -29
  575. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  576. package/src/util/fs-watcher-error.ts +0 -36
  577. /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,177 @@
1
+ /** Restore maximized windows when clients request a title-bar drag. */
2
+ export const DESKTOP_WINDOW_DRAG_SCRIPT = String.raw`import ctypes as C
3
+ import select
4
+ import signal
5
+ import subprocess
6
+ import sys
7
+ import time
8
+
9
+
10
+ class MessageData(C.Union):
11
+ _fields_ = [('bytes', C.c_char * 20), ('shorts', C.c_short * 10), ('longs', C.c_long * 5)]
12
+
13
+
14
+ class ClientMessage(C.Structure):
15
+ _fields_ = [('type', C.c_int), ('serial', C.c_ulong), ('sent', C.c_int),
16
+ ('display', C.c_void_p), ('window', C.c_ulong), ('atom', C.c_ulong),
17
+ ('format', C.c_int), ('data', MessageData)]
18
+
19
+
20
+ class Event(C.Union):
21
+ _fields_ = [('message', ClientMessage), ('padding', C.c_long * 24)]
22
+
23
+
24
+ X = C.CDLL('libX11.so.6')
25
+ P = C.POINTER
26
+ U = C.c_ulong
27
+ I = C.c_int
28
+ D = C.c_void_p
29
+
30
+
31
+ def bind(name, result, *args):
32
+ function = getattr(X, name)
33
+ function.restype = result
34
+ function.argtypes = args
35
+ return function
36
+
37
+
38
+ open_display = bind('XOpenDisplay', D, C.c_char_p)
39
+ close_display = bind('XCloseDisplay', I, D)
40
+ root_window = bind('XDefaultRootWindow', U, D)
41
+ intern = bind('XInternAtom', U, D, C.c_char_p, I)
42
+ select_input = bind('XSelectInput', I, D, U, C.c_long)
43
+ flush = bind('XFlush', I, D)
44
+ pending = bind('XPending', I, D)
45
+ next_event = bind('XNextEvent', I, D, P(Event))
46
+ connection = bind('XConnectionNumber', I, D)
47
+ send_event = bind('XSendEvent', I, D, U, I, C.c_long, P(Event))
48
+ get_property = bind('XGetWindowProperty', I, D, U, U, C.c_long, C.c_long, I, U,
49
+ P(U), P(I), P(U), P(U), P(P(C.c_ubyte)))
50
+ free = bind('XFree', I, D)
51
+ get_geometry = bind('XGetGeometry', I, D, U, P(U), P(I), P(I), P(C.c_uint),
52
+ P(C.c_uint), P(C.c_uint), P(C.c_uint))
53
+ translate = bind('XTranslateCoordinates', I, D, U, U, I, I, P(I), P(I), P(U))
54
+ query_pointer = bind('XQueryPointer', I, D, U, P(U), P(U), P(I), P(I), P(I), P(I), P(C.c_uint))
55
+
56
+ # Windows can close between receiving a move request and reading their state.
57
+ error_handler_type = C.CFUNCTYPE(I, D, D)
58
+ error_handler = error_handler_type(lambda display, error: 0)
59
+ bind('XSetErrorHandler', D, error_handler_type)(error_handler)
60
+ display = open_display(None)
61
+ if not display:
62
+ raise RuntimeError('Cannot connect to the desktop display')
63
+ root = root_window(display)
64
+ move_atom = intern(display, b'_NET_WM_MOVERESIZE', 0)
65
+ position_atom = intern(display, b'_NET_MOVERESIZE_WINDOW', 0)
66
+ state_atom = intern(display, b'_NET_WM_STATE', 0)
67
+ max_h = intern(display, b'_NET_WM_STATE_MAXIMIZED_HORZ', 0)
68
+ max_v = intern(display, b'_NET_WM_STATE_MAXIMIZED_VERT', 0)
69
+ notify_mask = 1 << 19
70
+ redirect_mask = 1 << 20
71
+
72
+
73
+ def maximized(window):
74
+ actual, count, remaining = U(), U(), U()
75
+ format = I()
76
+ data = P(C.c_ubyte)()
77
+ status = get_property(display, window, state_atom, 0, 64, 0, 0,
78
+ C.byref(actual), C.byref(format), C.byref(count),
79
+ C.byref(remaining), C.byref(data))
80
+ try:
81
+ if status or format.value != 32 or not data:
82
+ return False
83
+ values = C.cast(data, P(U))
84
+ return any(values[i] in (max_h, max_v) for i in range(count.value))
85
+ finally:
86
+ if data:
87
+ free(data)
88
+
89
+
90
+ def geometry(window):
91
+ parent, child = U(), U()
92
+ x, y = I(), I()
93
+ width, height, border, depth = (C.c_uint() for _ in range(4))
94
+ if not get_geometry(display, window, C.byref(parent), C.byref(x), C.byref(y),
95
+ C.byref(width), C.byref(height), C.byref(border), C.byref(depth)):
96
+ return None
97
+ if not translate(display, window, root, 0, 0, C.byref(x), C.byref(y), C.byref(child)):
98
+ return None
99
+ return x.value, y.value, width.value, height.value
100
+
101
+
102
+ def send(window, atom, values):
103
+ event = Event()
104
+ event.message.type = 33
105
+ event.message.display = display
106
+ event.message.window = window
107
+ event.message.atom = atom
108
+ event.message.format = 32
109
+ event.message.data.longs[:] = values
110
+ send_event(display, root, 0, notify_mask | redirect_mask, C.byref(event))
111
+ flush(display)
112
+
113
+
114
+ def restore_drag(message):
115
+ values = message.data.longs
116
+ # Chrome leaves the button unspecified; source 2 marks our continuation.
117
+ if message.format != 32 or values[2] != 8 or values[3] not in (0, 1) or values[4] == 2:
118
+ return
119
+ window = message.window
120
+ if not maximized(window):
121
+ return
122
+ before = geometry(window)
123
+ if not before or not before[2]:
124
+ return
125
+ fraction = max(0, min(1, (values[0] - before[0]) / before[2]))
126
+ offset_y = values[1] - before[1]
127
+ send(window, move_atom, [values[0], values[1], 11, 0, 2])
128
+ send(window, state_atom, [0, max_h, max_v, 2, 0])
129
+ deadline = time.monotonic() + 0.25
130
+ while maximized(window):
131
+ if time.monotonic() >= deadline:
132
+ return
133
+ time.sleep(0.005)
134
+ after = geometry(window)
135
+ if not after:
136
+ return
137
+ parent, child = U(), U()
138
+ x, y, local_x, local_y = (I() for _ in range(4))
139
+ buttons = C.c_uint()
140
+ if not query_pointer(display, root, C.byref(parent), C.byref(child), C.byref(x),
141
+ C.byref(y), C.byref(local_x), C.byref(local_y), C.byref(buttons)):
142
+ return
143
+ send(window, position_atom, [(2 << 12) | (1 << 8) | (1 << 9) | 1,
144
+ round(x.value - fraction * after[2]), y.value - offset_y, 0, 0])
145
+ if buttons.value & (1 << 8):
146
+ send(window, move_atom, [x.value, y.value, 8, 1, 2])
147
+
148
+
149
+ select_input(display, root, notify_mask)
150
+ flush(display)
151
+ manager = subprocess.Popen(sys.argv[1:])
152
+ running = True
153
+
154
+
155
+ def stop(signum, frame):
156
+ global running
157
+ running = False
158
+
159
+
160
+ signal.signal(signal.SIGTERM, stop)
161
+ signal.signal(signal.SIGINT, stop)
162
+ try:
163
+ while running and manager.poll() is None:
164
+ if not pending(display):
165
+ select.select([connection(display)], [], [], 0.1)
166
+ continue
167
+ event = Event()
168
+ next_event(display, C.byref(event))
169
+ if event.message.type == 33 and event.message.atom == move_atom:
170
+ restore_drag(event.message)
171
+ finally:
172
+ close_display(display)
173
+ if manager.poll() is None:
174
+ manager.terminate()
175
+ manager.wait()
176
+ sys.exit(manager.returncode)
177
+ `;