@vellumai/assistant 0.12.3 → 0.12.4-dev.202609231620.a15f321

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 (209) hide show
  1. package/Dockerfile +7 -0
  2. package/README.md +12 -12
  3. package/docker/mousepad/90-vellum.gschema.override +4 -0
  4. package/node_modules/@vellumai/avatar-manifest/src/__tests__/notification-avatar.test.ts +48 -4
  5. package/node_modules/@vellumai/avatar-manifest/src/notification-avatar.ts +12 -8
  6. package/openapi.yaml +97 -4
  7. package/package.json +1 -1
  8. package/src/__tests__/approval-routes-http.test.ts +4 -2
  9. package/src/__tests__/call-routes-http.test.ts +4 -2
  10. package/src/__tests__/channel-readiness-discord.test.ts +8 -0
  11. package/src/__tests__/channel-readiness-slack-remote.test.ts +8 -0
  12. package/src/__tests__/config-loader-backfill.test.ts +1 -0
  13. package/src/__tests__/conversation-pairing.test.ts +10 -0
  14. package/src/__tests__/conversation-resurface-done.test.ts +289 -0
  15. package/src/__tests__/default-profile-catalog-fallback.test.ts +1 -0
  16. package/src/__tests__/gemini-provider.test.ts +31 -0
  17. package/src/__tests__/inference-profile-validation-jev.test.ts +17 -0
  18. package/src/__tests__/job-handler-registration-guard.test.ts +1 -0
  19. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  20. package/src/__tests__/llm-resolver-override-or-default.test.ts +43 -0
  21. package/src/__tests__/openai-responses-prompt-cache.test.ts +3 -1
  22. package/src/__tests__/platform-callback-registration.test.ts +46 -1
  23. package/src/__tests__/plugin-api-model-profiles.test.ts +7 -0
  24. package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
  25. package/src/__tests__/pricing.test.ts +33 -0
  26. package/src/__tests__/provider-error-scenarios.test.ts +46 -0
  27. package/src/__tests__/provider-platform-proxy-integration.test.ts +45 -15
  28. package/src/__tests__/retry-fallback-escalation.test.ts +22 -0
  29. package/src/__tests__/runtime-attachment-metadata.test.ts +4 -2
  30. package/src/__tests__/scaffold-managed-skill-tool.test.ts +116 -66
  31. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -0
  32. package/src/__tests__/send-endpoint-busy.test.ts +4 -2
  33. package/src/__tests__/skill-load-inline-command.test.ts +35 -0
  34. package/src/__tests__/skill-load-tool.test.ts +42 -11
  35. package/src/__tests__/skill-tool-factory.test.ts +50 -2
  36. package/src/__tests__/steer-on-enqueue-question.test.ts +265 -34
  37. package/src/__tests__/validate-input.test.ts +73 -0
  38. package/src/__tests__/workspace-migration-159-rename-colliding-jev-profile-name.test.ts +197 -0
  39. package/src/api/ask-question-tool.ts +50 -0
  40. package/src/api/events/tool-result.ts +11 -0
  41. package/src/api/index.ts +7 -0
  42. package/src/api/requests/dictation.ts +2 -2
  43. package/src/api/responses/home.ts +27 -0
  44. package/src/avatar/notification-avatar.test.ts +42 -34
  45. package/src/avatar/notification-avatar.ts +1 -0
  46. package/src/calls/__tests__/voice-session-bridge.test.ts +63 -1
  47. package/src/calls/voice-escalation-judge.ts +6 -5
  48. package/src/calls/voice-screen-annotation.ts +44 -0
  49. package/src/calls/voice-session-bridge.ts +22 -0
  50. package/src/cli/commands/__tests__/plugins.test.ts +29 -1
  51. package/src/cli/commands/channel-verification-sessions.help.ts +12 -2
  52. package/src/cli/commands/platform/index.help.ts +2 -1
  53. package/src/cli/commands/platform/index.ts +8 -2
  54. package/src/cli/commands/plugins.ts +16 -0
  55. package/src/cli/lib/__tests__/inspect-plugin.test.ts +29 -0
  56. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +92 -2
  57. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +15 -0
  58. package/src/cli/lib/__tests__/plugin-catalog-platform.test.ts +66 -2
  59. package/src/cli/lib/__tests__/plugin-catalog-resolve.test.ts +21 -0
  60. package/src/cli/lib/__tests__/plugin-catalog-visibility.test.ts +65 -0
  61. package/src/cli/lib/__tests__/plugin-details.test.ts +43 -0
  62. package/src/cli/lib/__tests__/plugins-install-offline.test.ts +26 -0
  63. package/src/cli/lib/bundled-marketplace.json +4 -4
  64. package/src/cli/lib/inspect-plugin.ts +5 -0
  65. package/src/cli/lib/plugin-catalog-cache.ts +41 -4
  66. package/src/cli/lib/plugin-catalog-local.ts +9 -0
  67. package/src/cli/lib/plugin-catalog-platform.ts +48 -9
  68. package/src/cli/lib/plugin-catalog-visibility.ts +41 -0
  69. package/src/cli/lib/plugin-details.ts +10 -7
  70. package/src/cli/lib/search-plugins.ts +2 -0
  71. package/src/config/__tests__/default-profile-catalog.test.ts +61 -1
  72. package/src/config/__tests__/jev-managed-profile.test.ts +82 -0
  73. package/src/config/__tests__/sync-gated-profiles.test.ts +71 -0
  74. package/src/config/bundled-skills/screen-annotation/SKILL.md +6 -3
  75. package/src/config/call-site-defaults.ts +17 -11
  76. package/src/config/default-profile-catalog.ts +118 -19
  77. package/src/config/default-profile-names.ts +69 -0
  78. package/src/config/feature-flag-registry.json +25 -9
  79. package/src/config/inference-profile-validation.ts +7 -6
  80. package/src/config/llm-resolver.ts +16 -0
  81. package/src/config/profile-text-generation.test.ts +9 -0
  82. package/src/config/schemas/call-site-catalog.ts +8 -1
  83. package/src/config/schemas/llm.ts +70 -9
  84. package/src/config/sidebar-done-gate.ts +27 -0
  85. package/src/config/sync-gated-profiles.ts +96 -44
  86. package/src/daemon/__tests__/auto-profile-router.test.ts +228 -0
  87. package/src/daemon/auto-profile-router.ts +220 -0
  88. package/src/daemon/conversation-agent-loop.ts +77 -4
  89. package/src/daemon/conversation-slash.ts +2 -2
  90. package/src/daemon/conversation-turn-finalize.ts +12 -0
  91. package/src/daemon/dictation-text-processing.ts +4 -4
  92. package/src/daemon/handlers/__tests__/conversation-history-search.test.ts +61 -0
  93. package/src/daemon/handlers/config-channels.ts +3 -1
  94. package/src/daemon/handlers/conversation-history.ts +11 -1
  95. package/src/daemon/handlers/conversations.ts +118 -10
  96. package/src/daemon/pkb-reminder-builder.test.ts +10 -3
  97. package/src/daemon/pkb-reminder-builder.ts +1 -1
  98. package/src/desktop/desktop-browser-endpoint.ts +1 -0
  99. package/src/home/feed-types.ts +2 -0
  100. package/src/inbound/platform-callback-registration.ts +19 -6
  101. package/src/ipc/routes/__tests__/conversation-sync-ipc-routes.test.ts +49 -1
  102. package/src/ipc/routes/conversation-sync-ipc-routes.ts +38 -2
  103. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +27 -0
  104. package/src/live-voice/__tests__/protocol.test.ts +22 -0
  105. package/src/live-voice/continuation-judge.ts +1 -1
  106. package/src/live-voice/live-voice-session.ts +7 -6
  107. package/src/live-voice/protocol.ts +16 -5
  108. package/src/messaging/providers/gmail/client.ts +4 -0
  109. package/src/notifications/README.md +11 -3
  110. package/src/notifications/__tests__/home-feed-side-effect.test.ts +87 -3
  111. package/src/notifications/conversation-pairing.ts +23 -8
  112. package/src/notifications/home-feed-mirror.ts +39 -0
  113. package/src/notifications/home-feed-side-effect.ts +46 -19
  114. package/src/oauth/AGENTS.md +7 -1
  115. package/src/permissions/question-prompter.ts +14 -4
  116. package/src/persistence/conversation-crud.ts +114 -3
  117. package/src/persistence/conversation-queries.ts +48 -3
  118. package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +228 -32
  119. package/src/persistence/embeddings/embedding-backend-shutdown.test.ts +47 -0
  120. package/src/persistence/embeddings/embedding-backend.ts +23 -41
  121. package/src/persistence/embeddings/embedding-gemini.test.ts +17 -0
  122. package/src/persistence/embeddings/embedding-gemini.ts +3 -1
  123. package/src/persistence/embeddings/embedding-local.ts +72 -17
  124. package/src/persistence/embeddings/embedding-types.ts +0 -19
  125. package/src/persistence/jobs-store.ts +229 -0
  126. package/src/persistence/migrations/383-normalize-opencode-host-connections.ts +44 -0
  127. package/src/persistence/migrations/__tests__/383-normalize-opencode-host-connections.test.ts +102 -0
  128. package/src/persistence/steps.ts +12 -0
  129. package/src/platform/sync-avatar.test.ts +29 -0
  130. package/src/plugin-api/model-profiles.ts +5 -3
  131. package/src/plugins/defaults/memory/AGENTS.md +5 -3
  132. package/src/plugins/defaults/memory/__tests__/memory-retrospective-skill-card.test.ts +2 -0
  133. package/src/plugins/defaults/memory/__tests__/skill-update-receipt-boundary-guard.test.ts +45 -0
  134. package/src/plugins/defaults/memory/__tests__/skill-update-receipt-job.test.ts +580 -0
  135. package/src/plugins/defaults/memory/graph/tools.ts +9 -5
  136. package/src/plugins/defaults/memory/hooks/conversation-deleted.ts +4 -0
  137. package/src/plugins/defaults/memory/job-handlers.ts +8 -2
  138. package/src/plugins/defaults/memory/memory-retrospective-skill-card.ts +5 -0
  139. package/src/plugins/defaults/memory/skill-update-receipt-job.ts +334 -0
  140. package/src/plugins/defaults/memory/substrate/skill-content.ts +1 -1
  141. package/src/plugins/defaults/memory/tools.test.ts +17 -0
  142. package/src/plugins/defaults/memory/v3/__tests__/render-injection.test.ts +3 -0
  143. package/src/providers/__tests__/connection-model-compat.test.ts +2 -0
  144. package/src/providers/__tests__/search-provider-catalog.test.ts +8 -0
  145. package/src/providers/anthropic/client.ts +2 -2
  146. package/src/providers/gemini/client.ts +17 -1
  147. package/src/providers/inference/auth.ts +25 -0
  148. package/src/providers/jev/ask.test.ts +66 -0
  149. package/src/{calls/typesafe-noul.ts → providers/jev/ask.ts} +75 -36
  150. package/src/providers/malformed-tool-call.ts +55 -0
  151. package/src/providers/model-catalog.ts +257 -4
  152. package/src/providers/openai/__tests__/chat-completions-malformed-tool-call.test.ts +105 -0
  153. package/src/providers/openai/chat-completions-provider.ts +30 -0
  154. package/src/providers/openai/codex-models.ts +2 -0
  155. package/src/providers/platform-proxy/constants.ts +5 -0
  156. package/src/providers/retry.ts +68 -36
  157. package/src/providers/search-provider-catalog.ts +10 -0
  158. package/src/providers/vellum-model-routing.test.ts +1 -0
  159. package/src/runtime/__tests__/agent-wake-archived-gate.test.ts +90 -0
  160. package/src/runtime/__tests__/question-resolution.test.ts +32 -0
  161. package/src/runtime/agent-wake.ts +9 -2
  162. package/src/runtime/auth/__tests__/middleware.test.ts +138 -0
  163. package/src/runtime/auth/middleware.ts +15 -11
  164. package/src/runtime/middleware/auth.ts +7 -1
  165. package/src/runtime/pending-interactions.ts +8 -8
  166. package/src/runtime/question-resolution.ts +54 -8
  167. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +146 -0
  168. package/src/runtime/routes/__tests__/dictation-processing.test.ts +174 -0
  169. package/src/runtime/routes/__tests__/global-search-routes.test.ts +74 -1
  170. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +60 -0
  171. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +16 -1
  172. package/src/runtime/routes/__tests__/plugins-routes.test.ts +8 -0
  173. package/src/runtime/routes/conversation-list-routes.ts +16 -14
  174. package/src/runtime/routes/conversation-query-routes.ts +4 -0
  175. package/src/runtime/routes/conversation-routes.ts +4 -2
  176. package/src/runtime/routes/diagnostics-routes.ts +66 -229
  177. package/src/runtime/routes/global-search-routes.ts +22 -7
  178. package/src/runtime/routes/inference-provider-connection-routes.ts +25 -8
  179. package/src/runtime/routes/llm-call-sites-routes.ts +19 -10
  180. package/src/runtime/routes/platform-routes.ts +6 -1
  181. package/src/runtime/routes/plugins-routes.ts +5 -2
  182. package/src/runtime/sync/documents-sidecar-publish.test.ts +50 -4
  183. package/src/runtime/sync/resource-sync-events.ts +11 -0
  184. package/src/runtime/sync/worker-daemon-notify.test.ts +30 -0
  185. package/src/runtime/sync/worker-daemon-notify.ts +44 -0
  186. package/src/runtime/verification-outbound-actions.ts +2 -2
  187. package/src/skills/validate-input.ts +75 -11
  188. package/src/telegram/__tests__/webhook-health.test.ts +8 -0
  189. package/src/tools/ask-question/ask-question-tool.test.ts +38 -0
  190. package/src/tools/ask-question/ask-question-tool.ts +12 -1
  191. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +28 -1
  192. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +34 -0
  193. package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +35 -1
  194. package/src/tools/network/__tests__/web-search.test.ts +96 -0
  195. package/src/tools/network/web-fetch.ts +30 -9
  196. package/src/tools/network/web-search.ts +235 -4
  197. package/src/tools/skills/execute.ts +1 -1
  198. package/src/tools/skills/format-input-schema.ts +15 -0
  199. package/src/tools/skills/load.ts +15 -43
  200. package/src/tools/skills/scaffold-managed.ts +86 -71
  201. package/src/tools/skills/skill-tool-factory.ts +2 -1
  202. package/src/tools/workflows/manage-workflows.ts +4 -2
  203. package/src/util/worker-pipe.test.ts +146 -0
  204. package/src/util/worker-pipe.ts +49 -0
  205. package/src/workspace/migrations/158-repoint-opencode-host-provider-fragments.ts +163 -0
  206. package/src/workspace/migrations/159-rename-colliding-jev-profile-name.ts +344 -0
  207. package/src/workspace/migrations/__tests__/158-repoint-opencode-host-provider-fragments.test.ts +188 -0
  208. package/src/workspace/migrations/registry.ts +4 -0
  209. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
package/Dockerfile CHANGED
@@ -102,6 +102,7 @@ RUN apt-get update && apt-get install -y \
102
102
  && rm -rf /var/lib/apt/lists/*
103
103
 
104
104
  # Desktop system packages persist through container replacement as image layers.
105
+ COPY assistant/docker/mousepad/90-vellum.gschema.override /usr/share/glib-2.0/schemas/90-vellum.gschema.override
105
106
  RUN set -eu; \
106
107
  wezterm_version=20240203-110809-5046fc22; \
107
108
  case "$(dpkg --print-architecture)" in \
@@ -129,6 +130,7 @@ RUN set -eu; \
129
130
  libgl1-mesa-dri \
130
131
  libgtk-3-0 \
131
132
  libvulkan1 \
133
+ mousepad \
132
134
  openbox \
133
135
  plank \
134
136
  python3 \
@@ -144,6 +146,11 @@ RUN set -eu; \
144
146
  xfonts-base \
145
147
  xterm \
146
148
  /tmp/wezterm.deb; \
149
+ /usr/lib/*/glib-2.0/glib-compile-schemas --strict /usr/share/glib-2.0/schemas; \
150
+ mkdir -p /etc/xdg; \
151
+ awk -v desktop_id=org.xfce.mousepad.desktop \
152
+ 'BEGIN { print "[Default Applications]" } /^MimeType=/ { sub(/^MimeType=/, ""); count = split($0, types, ";"); for (i = 1; i <= count; i++) { if (types[i] != "") { print types[i] "=" desktop_id ";" } } }' \
153
+ /usr/share/applications/org.xfce.mousepad.desktop > /etc/xdg/mimeapps.list; \
147
154
  wezterm --version; \
148
155
  rm -rf /tmp/wezterm.deb /var/lib/apt/lists/* /var/cache/apt/archives/*.deb
149
156
 
package/README.md CHANGED
@@ -70,16 +70,16 @@ bun run src/index.ts # interactive CLI session
70
70
 
71
71
  ### CLI commands
72
72
 
73
- | Command | Description |
74
- | -------------------------------------------------- | ------------------------------------------------ |
75
- | `vellum wake` | Start assistant + gateway from current checkout |
76
- | `vellum sleep` | Stop assistant + gateway processes |
77
- | `vellum ps` | List assistants and per-assistant process status |
78
- | `assistant` | Launch interactive CLI session |
73
+ | Command | Description |
74
+ | ---------------------------------------------------------- | ------------------------------------------------ |
75
+ | `vellum wake` | Start assistant + gateway from current checkout |
76
+ | `vellum sleep` | Stop assistant + gateway processes |
77
+ | `vellum ps` | List assistants and per-assistant process status |
78
+ | `assistant` | Launch interactive CLI session |
79
79
  | `assistant conversations list\|search\|new\|export\|clear` | Manage conversations |
80
- | `assistant config set\|get\|list` | Manage configuration |
81
- | `assistant keys set\|list\|delete` | Manage API keys in secure storage |
82
- | `assistant trust list\|add\|update\|remove` | Manage trust rules |
80
+ | `assistant config set\|get\|list` | Manage configuration |
81
+ | `assistant keys set\|list\|delete` | Manage API keys in secure storage |
82
+ | `assistant trust list\|add\|update\|remove` | Manage trust rules |
83
83
 
84
84
  ## Project Structure
85
85
 
@@ -183,7 +183,7 @@ Internal forwarding routes (`/v1/internal/twilio/*`) are unaffected — these ac
183
183
  The `/channels/inbound` endpoint requires a JWT with the `svc_gateway` principal type and `ingress.write` scope to prove the request originated from the gateway. This ensures channel messages can only arrive via the gateway (which performs webhook-level verification) and not via direct HTTP calls that bypass signature checks.
184
184
 
185
185
  - **JWT-based enforcement:** The route policy in `route-policy.ts` restricts `/channels/inbound` to the `svc_gateway` principal type with `ingress.write` scope. Actor and local principals are rejected with 403.
186
- - **Auth bypass:** When `DISABLE_HTTP_AUTH=true` is set (platform-managed deployments), JWT verification is skipped and a synthetic context is used.
186
+ - **Auth bypass:** When `DISABLE_HTTP_AUTH=true` is set (platform-managed deployments), a request that supplies no `Authorization` header gets a synthetic context. A request that supplies one is verified normally, and a token that fails verification is rejected.
187
187
 
188
188
  ## Twilio Setup Primitive
189
189
 
@@ -244,8 +244,8 @@ The channel guardian service generates verification challenge instructions with
244
244
  ### Operator Notes
245
245
 
246
246
  - **Verification input format:** Channel verification accepts a bare code reply only (6-digit numeric for identity-bound sessions; 64-char hex for unbound inbound/bootstrap compatibility).
247
- - **Rebind requirement:** Creating a new guardian challenge when a binding already exists requires `rebind: true` in the HTTP request. Without it, the assistant returns `already_bound`. This prevents accidental guardian replacement.
248
- - **Takeover prevention:** Verification is rejected when an active binding exists for a different external user. Same-user re-verification is allowed.
247
+ - **Rebind requirement:** Creating a new guardian challenge when a binding already exists requires `rebind: true` in the HTTP request. Without it, the assistant returns `already_bound`. On text channels the resulting code can only be redeemed by the account already bound, which is how the same guardian verifies again; to move the guardian to a different account, revoke the binding first and verify again. An outbound phone verification is the exception: its code replaces the bound number.
248
+ - **Takeover prevention:** On text channels, verification is rejected when an active binding exists for a different external user: the code is spent, the reply is the generic "invalid or expired", and the sender is made neither guardian nor contact. Same-user re-verification is allowed, including for a guardian who revoked their binding and is verifying the same account again. Phone differs: an outbound verification with `rebind: true` replaces the bound number once its code is entered, and an inbound call from a different number is not bound.
249
249
 
250
250
  ### Vellum Guardian Identity (Actor Tokens)
251
251
 
@@ -0,0 +1,4 @@
1
+ [org.xfce.mousepad.preferences.view]
2
+ font-name='FreeSans 12'
3
+ use-default-monospace-font=false
4
+ word-wrap=true
@@ -67,8 +67,31 @@ describe("notificationAvatarDiscHex", () => {
67
67
  });
68
68
 
69
69
  describe("notificationAvatarSvg", () => {
70
+ test("fills the entire circular crop with a custom image", () => {
71
+ const svg = notificationAvatarSvg({
72
+ kind: "image",
73
+ innerPngBase64: PNG_BASE64,
74
+ accentHex: "#E9642F",
75
+ });
76
+
77
+ expect(attributes("image", svg)).toMatchObject({
78
+ x: "0",
79
+ y: "0",
80
+ width: "256",
81
+ height: "256",
82
+ preserveAspectRatio: "xMidYMid slice",
83
+ "clip-path": "url(#notification-avatar-disc)",
84
+ });
85
+ expect(allAttributes("circle", svg)[1]).toEqual({
86
+ cx: "128",
87
+ cy: "128",
88
+ r: "128",
89
+ });
90
+ });
91
+
70
92
  test("draws one disc and one inset avatar at the default size", () => {
71
93
  const svg = notificationAvatarSvg({
94
+ kind: "character",
72
95
  innerPngBase64: PNG_BASE64,
73
96
  accentHex: "#E9642F",
74
97
  });
@@ -100,6 +123,7 @@ describe("notificationAvatarSvg", () => {
100
123
  // upload fill the square instead of letterboxing is the pair of attributes
101
124
  // asserted here.
102
125
  const svg = notificationAvatarSvg({
126
+ kind: "character",
103
127
  innerPngBase64: PNG_BASE64,
104
128
  accentHex: "#E9642F",
105
129
  });
@@ -121,6 +145,7 @@ describe("notificationAvatarSvg", () => {
121
145
 
122
146
  test("scales the geometry to a custom size", () => {
123
147
  const svg = notificationAvatarSvg({
148
+ kind: "character",
124
149
  innerPngBase64: PNG_BASE64,
125
150
  accentHex: "#E9642F",
126
151
  size: 100,
@@ -142,6 +167,7 @@ describe("notificationAvatarSvg", () => {
142
167
 
143
168
  test("carries a non-PNG inner raster with its own media type", () => {
144
169
  const svg = notificationAvatarSvg({
170
+ kind: "character",
145
171
  innerPngBase64: PNG_BASE64,
146
172
  innerMediaType: "image/jpeg",
147
173
  accentHex: "#E9642F",
@@ -154,6 +180,7 @@ describe("notificationAvatarSvg", () => {
154
180
 
155
181
  test("paints the fallback disc when there is no accent", () => {
156
182
  const svg = notificationAvatarSvg({
183
+ kind: "character",
157
184
  innerPngBase64: PNG_BASE64,
158
185
  accentHex: null,
159
186
  });
@@ -164,8 +191,21 @@ describe("notificationAvatarSvg", () => {
164
191
  });
165
192
 
166
193
  describe("notificationAvatarGeometry", () => {
194
+ test("custom images reach the disc edge at any output size", () => {
195
+ expect(notificationAvatarGeometry("image")).toEqual({
196
+ radius: 128,
197
+ offset: 0,
198
+ inner: 256,
199
+ });
200
+ expect(notificationAvatarGeometry("image", 100)).toEqual({
201
+ radius: 50,
202
+ offset: 0,
203
+ inner: 100,
204
+ });
205
+ });
206
+
167
207
  test("derives the disc, the border and the avatar edge from the size", () => {
168
- expect(notificationAvatarGeometry(100)).toEqual({
208
+ expect(notificationAvatarGeometry("character", 100)).toEqual({
169
209
  radius: 50,
170
210
  offset: 11,
171
211
  inner: 78,
@@ -173,15 +213,19 @@ describe("notificationAvatarGeometry", () => {
173
213
  });
174
214
 
175
215
  test("measures the default size when given none", () => {
176
- expect(notificationAvatarGeometry()).toEqual(
177
- notificationAvatarGeometry(NOTIFICATION_AVATAR_SIZE),
216
+ expect(notificationAvatarGeometry("character")).toEqual(
217
+ notificationAvatarGeometry("character", NOTIFICATION_AVATAR_SIZE),
178
218
  );
179
219
  });
180
220
 
181
221
  test("is the geometry the SVG is drawn with", () => {
182
222
  const size = 100;
183
- const { radius, offset, inner } = notificationAvatarGeometry(size);
223
+ const { radius, offset, inner } = notificationAvatarGeometry(
224
+ "character",
225
+ size,
226
+ );
184
227
  const svg = notificationAvatarSvg({
228
+ kind: "character",
185
229
  innerPngBase64: PNG_BASE64,
186
230
  accentHex: null,
187
231
  size,
@@ -15,11 +15,12 @@
15
15
  */
16
16
 
17
17
  import { isAvatarAccentHex } from "./accent.js";
18
+ import type { AvatarKind } from "./manifest.js";
18
19
 
19
20
  /** Output edge in pixels: large enough for an iOS notification thumbnail. */
20
21
  export const NOTIFICATION_AVATAR_SIZE = 256;
21
22
 
22
- /** Free disc on each side, as a fraction of the edge; the avatar gets the rest. */
23
+ /** Free disc on each side of a character avatar, as a fraction of the edge. */
23
24
  export const NOTIFICATION_AVATAR_INSET = 0.11;
24
25
 
25
26
  /** The disc when the assistant has no accent. */
@@ -48,7 +49,7 @@ export const NOTIFICATION_AVATAR_MAX_LOCAL_BYTES = 512 * 1024;
48
49
  * Bumped whenever the drawing above changes, so a sync keyed on it re-uploads
49
50
  * a disc rendered by an older spec even when the avatar itself is unchanged.
50
51
  */
51
- export const NOTIFICATION_AVATAR_SPEC_VERSION = 1;
52
+ export const NOTIFICATION_AVATAR_SPEC_VERSION = 2;
52
53
 
53
54
  /** The id the inner raster's clip path is referenced by inside the document. */
54
55
  const CLIP_ID = "notification-avatar-disc";
@@ -74,24 +75,26 @@ export function notificationAvatarDiscHex(accentHex: string | null): string {
74
75
 
75
76
  /** Raster formats an `<image>` href carries here; what resvg and a canvas both decode. */
76
77
  export type NotificationAvatarMediaType =
77
- | "image/png"
78
- | "image/jpeg"
79
- | "image/gif";
78
+ "image/png" | "image/jpeg" | "image/gif";
80
79
 
81
80
  /**
82
81
  * The three measurements the drawing derives from the edge: the disc's radius,
83
82
  * which is also its centre; the free border around the avatar; and the
84
83
  * avatar's own edge. Every rasterizer reads them from here, so the daemon's
85
84
  * SVG and the desktop canvas cannot drift.
85
+ * Custom images fill the disc; character avatars retain space around their
86
+ * silhouette.
86
87
  */
87
88
  export function notificationAvatarGeometry(
89
+ kind: Exclude<AvatarKind, "none">,
88
90
  size: number = NOTIFICATION_AVATAR_SIZE,
89
91
  ): { radius: number; offset: number; inner: number } {
90
- const offset = size * NOTIFICATION_AVATAR_INSET;
92
+ const offset = kind === "character" ? size * NOTIFICATION_AVATAR_INSET : 0;
91
93
  return { radius: size / 2, offset, inner: size - 2 * offset };
92
94
  }
93
95
 
94
96
  export interface NotificationAvatarSvgOptions {
97
+ kind: Exclude<AvatarKind, "none">;
95
98
  /** The avatar raster to draw inside the disc, base64 with no data prefix. */
96
99
  innerPngBase64: string;
97
100
  /** What `innerPngBase64` holds; PNG unless an upload arrived as a JPEG or a GIF. */
@@ -107,7 +110,7 @@ function px(value: number): string {
107
110
 
108
111
  /**
109
112
  * The notification avatar as an SVG document: a filled disc with the avatar
110
- * drawn inset into it.
113
+ * filling it for custom images or inset for character avatars.
111
114
  *
112
115
  * The inner raster is cover-cropped (`xMidYMid slice`), matching what the web
113
116
  * canvas does, so a portrait or landscape upload fills the square instead of
@@ -119,12 +122,13 @@ function px(value: number): string {
119
122
  * rasterizer is known to render it.
120
123
  */
121
124
  export function notificationAvatarSvg({
125
+ kind,
122
126
  innerPngBase64,
123
127
  innerMediaType = "image/png",
124
128
  accentHex,
125
129
  size = NOTIFICATION_AVATAR_SIZE,
126
130
  }: NotificationAvatarSvgOptions): string {
127
- const { radius, offset, inner } = notificationAvatarGeometry(size);
131
+ const { radius, offset, inner } = notificationAvatarGeometry(kind, size);
128
132
  const href = `data:${innerMediaType};base64,${innerPngBase64}`;
129
133
  const disc = `cx="${px(radius)}" cy="${px(radius)}" r="${px(radius)}"`;
130
134
  return (
package/openapi.yaml CHANGED
@@ -3,7 +3,7 @@
3
3
  openapi: 3.1.0
4
4
  info:
5
5
  title: Vellum Assistant API
6
- version: 0.12.3
6
+ version: 0.12.4
7
7
  description: Auto-generated OpenAPI specification for the Vellum Assistant runtime HTTP server.
8
8
  servers:
9
9
  - url: http://127.0.0.1:7821
@@ -7985,9 +7985,13 @@ paths:
7985
7985
  enum:
7986
7986
  - background
7987
7987
  - scheduled
7988
+ - all
7988
7989
  description:
7989
7990
  Filter by conversation type. Pass "background" to list background and scheduled conversations together (the
7990
- back-compat umbrella), or "scheduled" to list only scheduled conversations.
7991
+ back-compat umbrella), "scheduled" to list only scheduled conversations, or "all" for the union of the
7992
+ standard listing and that umbrella, so one paginated cursor spans every type. Combine "all" with
7993
+ archiveStatus=all for a whole-history read; rows are recency-ordered (COALESCE(last_message_at, updated_at)
7994
+ descending) like every other list read, and pinned rows are never appended to it.
7991
7995
  - name: archiveStatus
7992
7996
  in: query
7993
7997
  required: false
@@ -12056,7 +12060,9 @@ paths:
12056
12060
  post:
12057
12061
  operationId: dictation_post
12058
12062
  summary: Process dictation
12059
- description: Classify voice input as dictation or action, clean up text, and apply user style preferences.
12063
+ description:
12064
+ Preserve dictated words with explicit dictionary and snippet replacements, classify actions, or edit
12065
+ selected text when instructed.
12060
12066
  tags:
12061
12067
  - diagnostics
12062
12068
  requestBody:
@@ -15073,6 +15079,24 @@ paths:
15073
15079
  - intent
15074
15080
  - status
15075
15081
  additionalProperties: false
15082
+ updates:
15083
+ type: array
15084
+ items:
15085
+ type: object
15086
+ properties:
15087
+ skillId:
15088
+ type: string
15089
+ name:
15090
+ type: string
15091
+ summary:
15092
+ type: string
15093
+ conversationId:
15094
+ type: string
15095
+ required:
15096
+ - skillId
15097
+ - name
15098
+ - summary
15099
+ additionalProperties: false
15076
15100
  category:
15077
15101
  type: string
15078
15102
  enum:
@@ -15308,6 +15332,24 @@ paths:
15308
15332
  - intent
15309
15333
  - status
15310
15334
  additionalProperties: false
15335
+ updates:
15336
+ type: array
15337
+ items:
15338
+ type: object
15339
+ properties:
15340
+ skillId:
15341
+ type: string
15342
+ name:
15343
+ type: string
15344
+ summary:
15345
+ type: string
15346
+ conversationId:
15347
+ type: string
15348
+ required:
15349
+ - skillId
15350
+ - name
15351
+ - summary
15352
+ additionalProperties: false
15311
15353
  category:
15312
15354
  type: string
15313
15355
  enum:
@@ -15637,6 +15679,24 @@ paths:
15637
15679
  - intent
15638
15680
  - status
15639
15681
  additionalProperties: false
15682
+ updates:
15683
+ type: array
15684
+ items:
15685
+ type: object
15686
+ properties:
15687
+ skillId:
15688
+ type: string
15689
+ name:
15690
+ type: string
15691
+ summary:
15692
+ type: string
15693
+ conversationId:
15694
+ type: string
15695
+ required:
15696
+ - skillId
15697
+ - name
15698
+ - summary
15699
+ additionalProperties: false
15640
15700
  category:
15641
15701
  type: string
15642
15702
  enum:
@@ -21274,6 +21334,7 @@ paths:
21274
21334
  - fastcrw
21275
21335
  - searxng
21276
21336
  - tinyfish
21337
+ - exa
21277
21338
  resultCount:
21278
21339
  type: number
21279
21340
  durationMs:
@@ -21352,6 +21413,10 @@ paths:
21352
21413
  type: string
21353
21414
  mayRequireJavaScript:
21354
21415
  type: boolean
21416
+ startIndexPastEnd:
21417
+ type: boolean
21418
+ providerWarning:
21419
+ type: string
21355
21420
  required:
21356
21421
  - url
21357
21422
  - finalUrl
@@ -21888,6 +21953,7 @@ paths:
21888
21953
  - fastcrw
21889
21954
  - searxng
21890
21955
  - tinyfish
21956
+ - exa
21891
21957
  resultCount:
21892
21958
  type: number
21893
21959
  durationMs:
@@ -21966,6 +22032,10 @@ paths:
21966
22032
  type: string
21967
22033
  mayRequireJavaScript:
21968
22034
  type: boolean
22035
+ startIndexPastEnd:
22036
+ type: boolean
22037
+ providerWarning:
22038
+ type: string
21969
22039
  required:
21970
22040
  - url
21971
22041
  - finalUrl
@@ -24788,6 +24858,24 @@ paths:
24788
24858
  - intent
24789
24859
  - status
24790
24860
  additionalProperties: false
24861
+ updates:
24862
+ type: array
24863
+ items:
24864
+ type: object
24865
+ properties:
24866
+ skillId:
24867
+ type: string
24868
+ name:
24869
+ type: string
24870
+ summary:
24871
+ type: string
24872
+ conversationId:
24873
+ type: string
24874
+ required:
24875
+ - skillId
24876
+ - name
24877
+ - summary
24878
+ additionalProperties: false
24791
24879
  category:
24792
24880
  type: string
24793
24881
  enum:
@@ -26885,7 +26973,12 @@ paths:
26885
26973
  assistantId:
26886
26974
  type: string
26887
26975
  hasAssistantApiKey:
26888
- type: boolean
26976
+ anyOf:
26977
+ - type: boolean
26978
+ - type: "null"
26979
+ description:
26980
+ Whether a managed assistant API key is stored. Null when the credential store could not be read, which is
26981
+ not evidence that no key is stored.
26889
26982
  hasWebhookSecret:
26890
26983
  type: boolean
26891
26984
  clientInstallationId:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.12.3",
3
+ "version": "0.12.4-dev.202609231620.a15f321",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -241,8 +241,10 @@ function makeConfirmationEmittingSession(opts?: {
241
241
  // Tests
242
242
  // ---------------------------------------------------------------------------
243
243
 
244
- const TEST_TOKEN = "test-bearer-token-approvals";
245
- const AUTH_HEADERS = { Authorization: `Bearer ${TEST_TOKEN}` };
244
+ // This suite mocks isHttpAuthDisabled() to true, so a request carrying no
245
+ // bearer authenticates through the dev bypass. A bearer that is present is
246
+ // verified against the signing key, which these tests do not set up.
247
+ const AUTH_HEADERS: Record<string, string> = {};
246
248
 
247
249
  describe("standalone approval endpoints — HTTP layer", () => {
248
250
  let server: RuntimeHttpServer;
@@ -82,8 +82,10 @@ await initializeDb();
82
82
  // Helpers
83
83
  // ---------------------------------------------------------------------------
84
84
 
85
- const TEST_TOKEN = "test-bearer-token-calls";
86
- const AUTH_HEADERS = { Authorization: `Bearer ${TEST_TOKEN}` };
85
+ // This suite mocks isHttpAuthDisabled() to true, so a request carrying no
86
+ // bearer authenticates through the dev bypass. A bearer that is present is
87
+ // verified against the signing key, which these tests do not set up.
88
+ const AUTH_HEADERS: Record<string, string> = {};
87
89
 
88
90
  let ensuredConvIds = new Set<string>();
89
91
 
@@ -4,8 +4,16 @@ import { credentialKey } from "../security/credential-key.js";
4
4
 
5
5
  let mockSecureKeys: Record<string, string>;
6
6
 
7
+ // Spread the real module: mocks are shared across files in a combined run,
8
+ // and a wholesale replacement drops the exports peer modules import.
9
+ const actualSecureKeys = await import("../security/secure-keys.js");
7
10
  mock.module("../security/secure-keys.js", () => ({
11
+ ...actualSecureKeys,
8
12
  getSecureKeyAsync: async (key: string) => mockSecureKeys[key] ?? null,
13
+ getSecureKeyResultAsync: async (key: string) => ({
14
+ value: mockSecureKeys[key] ?? undefined,
15
+ unreachable: false,
16
+ }),
9
17
  setSecureKeyAsync: async (key: string, value: string) => {
10
18
  mockSecureKeys[key] = value;
11
19
  return true;
@@ -5,8 +5,16 @@ import { setConfig } from "./helpers/set-config.js";
5
5
 
6
6
  let mockSecureKeys: Record<string, string>;
7
7
 
8
+ // Spread the real module: mocks are shared across files in a combined run,
9
+ // and a wholesale replacement drops the exports peer modules import.
10
+ const actualSecureKeys = await import("../security/secure-keys.js");
8
11
  mock.module("../security/secure-keys.js", () => ({
12
+ ...actualSecureKeys,
9
13
  getSecureKeyAsync: async (key: string) => mockSecureKeys[key] ?? null,
14
+ getSecureKeyResultAsync: async (key: string) => ({
15
+ value: mockSecureKeys[key] ?? undefined,
16
+ unreachable: false,
17
+ }),
10
18
  setSecureKeyAsync: async (key: string, value: string) => {
11
19
  mockSecureKeys[key] = value;
12
20
  return true;
@@ -1869,6 +1869,7 @@ describe("seedInferenceProfiles BYOK-mode default profiles", () => {
1869
1869
  "quality-optimized-backup",
1870
1870
  "cost-optimized-backup",
1871
1871
  "latency-optimized-backup",
1872
+ "jev-managed",
1872
1873
  "my-custom",
1873
1874
  ]);
1874
1875
  });
@@ -996,6 +996,12 @@ describe("pairDeliveryWithConversation", () => {
996
996
  expect(createConversationMock).not.toHaveBeenCalled();
997
997
  expect(addMessageMock).toHaveBeenCalledTimes(1);
998
998
  expect(messagesInvalidated).toContain("conv-foreground");
999
+ // The transcript is this notification's only home, so a chat marked Done
1000
+ // comes back for it.
1001
+ const options = addMessageMock.mock.calls[0]![3] as {
1002
+ skipResurface?: boolean;
1003
+ };
1004
+ expect(options.skipResurface).toBe(false);
999
1005
  });
1000
1006
 
1001
1007
  test("appended notification body skips indexing", async () => {
@@ -1017,8 +1023,12 @@ describe("pairDeliveryWithConversation", () => {
1017
1023
 
1018
1024
  const options = addMessageMock.mock.calls[0]![3] as {
1019
1025
  skipIndexing?: boolean;
1026
+ skipResurface?: boolean;
1020
1027
  };
1021
1028
  expect(options.skipIndexing).toBe(true);
1029
+ // The bell carries this one (async-background), so the append is
1030
+ // bookkeeping and never resurfaces a chat the user marked Done.
1031
+ expect(options.skipResurface).toBe(true);
1022
1032
  });
1023
1033
 
1024
1034
  test("passive vellum signal appends regardless of the producing conversation's source", async () => {