@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.
- package/Dockerfile +7 -0
- package/README.md +12 -12
- package/docker/mousepad/90-vellum.gschema.override +4 -0
- package/node_modules/@vellumai/avatar-manifest/src/__tests__/notification-avatar.test.ts +48 -4
- package/node_modules/@vellumai/avatar-manifest/src/notification-avatar.ts +12 -8
- package/openapi.yaml +97 -4
- package/package.json +1 -1
- package/src/__tests__/approval-routes-http.test.ts +4 -2
- package/src/__tests__/call-routes-http.test.ts +4 -2
- package/src/__tests__/channel-readiness-discord.test.ts +8 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +8 -0
- package/src/__tests__/config-loader-backfill.test.ts +1 -0
- package/src/__tests__/conversation-pairing.test.ts +10 -0
- package/src/__tests__/conversation-resurface-done.test.ts +289 -0
- package/src/__tests__/default-profile-catalog-fallback.test.ts +1 -0
- package/src/__tests__/gemini-provider.test.ts +31 -0
- package/src/__tests__/inference-profile-validation-jev.test.ts +17 -0
- package/src/__tests__/job-handler-registration-guard.test.ts +1 -0
- package/src/__tests__/llm-catalog-parity.test.ts +4 -0
- package/src/__tests__/llm-resolver-override-or-default.test.ts +43 -0
- package/src/__tests__/openai-responses-prompt-cache.test.ts +3 -1
- package/src/__tests__/platform-callback-registration.test.ts +46 -1
- package/src/__tests__/plugin-api-model-profiles.test.ts +7 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
- package/src/__tests__/pricing.test.ts +33 -0
- package/src/__tests__/provider-error-scenarios.test.ts +46 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +45 -15
- package/src/__tests__/retry-fallback-escalation.test.ts +22 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +4 -2
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +116 -66
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +4 -2
- package/src/__tests__/skill-load-inline-command.test.ts +35 -0
- package/src/__tests__/skill-load-tool.test.ts +42 -11
- package/src/__tests__/skill-tool-factory.test.ts +50 -2
- package/src/__tests__/steer-on-enqueue-question.test.ts +265 -34
- package/src/__tests__/validate-input.test.ts +73 -0
- package/src/__tests__/workspace-migration-159-rename-colliding-jev-profile-name.test.ts +197 -0
- package/src/api/ask-question-tool.ts +50 -0
- package/src/api/events/tool-result.ts +11 -0
- package/src/api/index.ts +7 -0
- package/src/api/requests/dictation.ts +2 -2
- package/src/api/responses/home.ts +27 -0
- package/src/avatar/notification-avatar.test.ts +42 -34
- package/src/avatar/notification-avatar.ts +1 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +63 -1
- package/src/calls/voice-escalation-judge.ts +6 -5
- package/src/calls/voice-screen-annotation.ts +44 -0
- package/src/calls/voice-session-bridge.ts +22 -0
- package/src/cli/commands/__tests__/plugins.test.ts +29 -1
- package/src/cli/commands/channel-verification-sessions.help.ts +12 -2
- package/src/cli/commands/platform/index.help.ts +2 -1
- package/src/cli/commands/platform/index.ts +8 -2
- package/src/cli/commands/plugins.ts +16 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +29 -0
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +92 -2
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +15 -0
- package/src/cli/lib/__tests__/plugin-catalog-platform.test.ts +66 -2
- package/src/cli/lib/__tests__/plugin-catalog-resolve.test.ts +21 -0
- package/src/cli/lib/__tests__/plugin-catalog-visibility.test.ts +65 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +43 -0
- package/src/cli/lib/__tests__/plugins-install-offline.test.ts +26 -0
- package/src/cli/lib/bundled-marketplace.json +4 -4
- package/src/cli/lib/inspect-plugin.ts +5 -0
- package/src/cli/lib/plugin-catalog-cache.ts +41 -4
- package/src/cli/lib/plugin-catalog-local.ts +9 -0
- package/src/cli/lib/plugin-catalog-platform.ts +48 -9
- package/src/cli/lib/plugin-catalog-visibility.ts +41 -0
- package/src/cli/lib/plugin-details.ts +10 -7
- package/src/cli/lib/search-plugins.ts +2 -0
- package/src/config/__tests__/default-profile-catalog.test.ts +61 -1
- package/src/config/__tests__/jev-managed-profile.test.ts +82 -0
- package/src/config/__tests__/sync-gated-profiles.test.ts +71 -0
- package/src/config/bundled-skills/screen-annotation/SKILL.md +6 -3
- package/src/config/call-site-defaults.ts +17 -11
- package/src/config/default-profile-catalog.ts +118 -19
- package/src/config/default-profile-names.ts +69 -0
- package/src/config/feature-flag-registry.json +25 -9
- package/src/config/inference-profile-validation.ts +7 -6
- package/src/config/llm-resolver.ts +16 -0
- package/src/config/profile-text-generation.test.ts +9 -0
- package/src/config/schemas/call-site-catalog.ts +8 -1
- package/src/config/schemas/llm.ts +70 -9
- package/src/config/sidebar-done-gate.ts +27 -0
- package/src/config/sync-gated-profiles.ts +96 -44
- package/src/daemon/__tests__/auto-profile-router.test.ts +228 -0
- package/src/daemon/auto-profile-router.ts +220 -0
- package/src/daemon/conversation-agent-loop.ts +77 -4
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-turn-finalize.ts +12 -0
- package/src/daemon/dictation-text-processing.ts +4 -4
- package/src/daemon/handlers/__tests__/conversation-history-search.test.ts +61 -0
- package/src/daemon/handlers/config-channels.ts +3 -1
- package/src/daemon/handlers/conversation-history.ts +11 -1
- package/src/daemon/handlers/conversations.ts +118 -10
- package/src/daemon/pkb-reminder-builder.test.ts +10 -3
- package/src/daemon/pkb-reminder-builder.ts +1 -1
- package/src/desktop/desktop-browser-endpoint.ts +1 -0
- package/src/home/feed-types.ts +2 -0
- package/src/inbound/platform-callback-registration.ts +19 -6
- package/src/ipc/routes/__tests__/conversation-sync-ipc-routes.test.ts +49 -1
- package/src/ipc/routes/conversation-sync-ipc-routes.ts +38 -2
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +27 -0
- package/src/live-voice/__tests__/protocol.test.ts +22 -0
- package/src/live-voice/continuation-judge.ts +1 -1
- package/src/live-voice/live-voice-session.ts +7 -6
- package/src/live-voice/protocol.ts +16 -5
- package/src/messaging/providers/gmail/client.ts +4 -0
- package/src/notifications/README.md +11 -3
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +87 -3
- package/src/notifications/conversation-pairing.ts +23 -8
- package/src/notifications/home-feed-mirror.ts +39 -0
- package/src/notifications/home-feed-side-effect.ts +46 -19
- package/src/oauth/AGENTS.md +7 -1
- package/src/permissions/question-prompter.ts +14 -4
- package/src/persistence/conversation-crud.ts +114 -3
- package/src/persistence/conversation-queries.ts +48 -3
- package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +228 -32
- package/src/persistence/embeddings/embedding-backend-shutdown.test.ts +47 -0
- package/src/persistence/embeddings/embedding-backend.ts +23 -41
- package/src/persistence/embeddings/embedding-gemini.test.ts +17 -0
- package/src/persistence/embeddings/embedding-gemini.ts +3 -1
- package/src/persistence/embeddings/embedding-local.ts +72 -17
- package/src/persistence/embeddings/embedding-types.ts +0 -19
- package/src/persistence/jobs-store.ts +229 -0
- package/src/persistence/migrations/383-normalize-opencode-host-connections.ts +44 -0
- package/src/persistence/migrations/__tests__/383-normalize-opencode-host-connections.test.ts +102 -0
- package/src/persistence/steps.ts +12 -0
- package/src/platform/sync-avatar.test.ts +29 -0
- package/src/plugin-api/model-profiles.ts +5 -3
- package/src/plugins/defaults/memory/AGENTS.md +5 -3
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-skill-card.test.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/skill-update-receipt-boundary-guard.test.ts +45 -0
- package/src/plugins/defaults/memory/__tests__/skill-update-receipt-job.test.ts +580 -0
- package/src/plugins/defaults/memory/graph/tools.ts +9 -5
- package/src/plugins/defaults/memory/hooks/conversation-deleted.ts +4 -0
- package/src/plugins/defaults/memory/job-handlers.ts +8 -2
- package/src/plugins/defaults/memory/memory-retrospective-skill-card.ts +5 -0
- package/src/plugins/defaults/memory/skill-update-receipt-job.ts +334 -0
- package/src/plugins/defaults/memory/substrate/skill-content.ts +1 -1
- package/src/plugins/defaults/memory/tools.test.ts +17 -0
- package/src/plugins/defaults/memory/v3/__tests__/render-injection.test.ts +3 -0
- package/src/providers/__tests__/connection-model-compat.test.ts +2 -0
- package/src/providers/__tests__/search-provider-catalog.test.ts +8 -0
- package/src/providers/anthropic/client.ts +2 -2
- package/src/providers/gemini/client.ts +17 -1
- package/src/providers/inference/auth.ts +25 -0
- package/src/providers/jev/ask.test.ts +66 -0
- package/src/{calls/typesafe-noul.ts → providers/jev/ask.ts} +75 -36
- package/src/providers/malformed-tool-call.ts +55 -0
- package/src/providers/model-catalog.ts +257 -4
- package/src/providers/openai/__tests__/chat-completions-malformed-tool-call.test.ts +105 -0
- package/src/providers/openai/chat-completions-provider.ts +30 -0
- package/src/providers/openai/codex-models.ts +2 -0
- package/src/providers/platform-proxy/constants.ts +5 -0
- package/src/providers/retry.ts +68 -36
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/vellum-model-routing.test.ts +1 -0
- package/src/runtime/__tests__/agent-wake-archived-gate.test.ts +90 -0
- package/src/runtime/__tests__/question-resolution.test.ts +32 -0
- package/src/runtime/agent-wake.ts +9 -2
- package/src/runtime/auth/__tests__/middleware.test.ts +138 -0
- package/src/runtime/auth/middleware.ts +15 -11
- package/src/runtime/middleware/auth.ts +7 -1
- package/src/runtime/pending-interactions.ts +8 -8
- package/src/runtime/question-resolution.ts +54 -8
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +146 -0
- package/src/runtime/routes/__tests__/dictation-processing.test.ts +174 -0
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +74 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +60 -0
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +16 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +8 -0
- package/src/runtime/routes/conversation-list-routes.ts +16 -14
- package/src/runtime/routes/conversation-query-routes.ts +4 -0
- package/src/runtime/routes/conversation-routes.ts +4 -2
- package/src/runtime/routes/diagnostics-routes.ts +66 -229
- package/src/runtime/routes/global-search-routes.ts +22 -7
- package/src/runtime/routes/inference-provider-connection-routes.ts +25 -8
- package/src/runtime/routes/llm-call-sites-routes.ts +19 -10
- package/src/runtime/routes/platform-routes.ts +6 -1
- package/src/runtime/routes/plugins-routes.ts +5 -2
- package/src/runtime/sync/documents-sidecar-publish.test.ts +50 -4
- package/src/runtime/sync/resource-sync-events.ts +11 -0
- package/src/runtime/sync/worker-daemon-notify.test.ts +30 -0
- package/src/runtime/sync/worker-daemon-notify.ts +44 -0
- package/src/runtime/verification-outbound-actions.ts +2 -2
- package/src/skills/validate-input.ts +75 -11
- package/src/telegram/__tests__/webhook-health.test.ts +8 -0
- package/src/tools/ask-question/ask-question-tool.test.ts +38 -0
- package/src/tools/ask-question/ask-question-tool.ts +12 -1
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +28 -1
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +34 -0
- package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +35 -1
- package/src/tools/network/__tests__/web-search.test.ts +96 -0
- package/src/tools/network/web-fetch.ts +30 -9
- package/src/tools/network/web-search.ts +235 -4
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/format-input-schema.ts +15 -0
- package/src/tools/skills/load.ts +15 -43
- package/src/tools/skills/scaffold-managed.ts +86 -71
- package/src/tools/skills/skill-tool-factory.ts +2 -1
- package/src/tools/workflows/manage-workflows.ts +4 -2
- package/src/util/worker-pipe.test.ts +146 -0
- package/src/util/worker-pipe.ts +49 -0
- package/src/workspace/migrations/158-repoint-opencode-host-provider-fragments.ts +163 -0
- package/src/workspace/migrations/159-rename-colliding-jev-profile-name.ts +344 -0
- package/src/workspace/migrations/__tests__/158-repoint-opencode-host-provider-fragments.test.ts +188 -0
- package/src/workspace/migrations/registry.ts +4 -0
- 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
|
|
74
|
-
|
|
|
75
|
-
| `vellum wake`
|
|
76
|
-
| `vellum sleep`
|
|
77
|
-
| `vellum ps`
|
|
78
|
-
| `assistant`
|
|
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`
|
|
81
|
-
| `assistant keys set\|list\|delete`
|
|
82
|
-
| `assistant trust list\|add\|update\|remove`
|
|
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),
|
|
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`.
|
|
248
|
-
- **Takeover prevention:**
|
|
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
|
|
|
@@ -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(
|
|
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
|
|
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 =
|
|
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/
|
|
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
|
-
*
|
|
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.
|
|
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),
|
|
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:
|
|
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
|
-
|
|
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
|
@@ -241,8 +241,10 @@ function makeConfirmationEmittingSession(opts?: {
|
|
|
241
241
|
// Tests
|
|
242
242
|
// ---------------------------------------------------------------------------
|
|
243
243
|
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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;
|
|
@@ -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 () => {
|