@vellumai/assistant 0.12.5 → 0.12.6-dev.202609292019.a6f6d85
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/.env.example +2 -0
- package/ARCHITECTURE.md +23 -0
- package/docs/automatic-done.md +32 -0
- package/docs/desktop-browser-cli.md +3 -2
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
- package/node_modules/@vellumai/environments/src/shell.test.ts +13 -1
- package/node_modules/@vellumai/environments/src/shell.ts +22 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/status-contracts.test.ts +39 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +25 -0
- package/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
- package/openapi.yaml +270 -10
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +76 -0
- package/src/__tests__/anthropic-provider.test.ts +2 -0
- package/src/__tests__/byok-default-profile-ensure.test.ts +7 -0
- package/src/__tests__/config-get-vision-flag.test.ts +1 -1
- package/src/__tests__/config-loader-validation-reset-notice.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +160 -154
- package/src/__tests__/conversation-evictor.test.ts +54 -0
- package/src/__tests__/conversation-pairing.test.ts +112 -0
- package/src/__tests__/document-append-idempotency.test.ts +13 -0
- package/src/__tests__/document-create-dedupe.test.ts +13 -0
- package/src/__tests__/document-find-replace.test.ts +13 -0
- package/src/__tests__/document-revision-history.test.ts +390 -0
- package/src/__tests__/document-revision.test.ts +425 -0
- package/src/__tests__/document-tool-security.test.ts +13 -0
- package/src/__tests__/document-update-default-surface.test.ts +13 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +270 -0
- package/src/__tests__/gateway-flag-listener.test.ts +59 -6
- package/src/__tests__/headless-browser-navigate.test.ts +20 -30
- package/src/__tests__/host-cu-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +14 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -2
- package/src/__tests__/identity-routes.test.ts +4 -3
- package/src/__tests__/llm-catalog-parity.test.ts +4 -4
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/notification-platform-adapter.test.ts +83 -1
- package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
- package/src/__tests__/openai-provider.test.ts +11 -11
- package/src/__tests__/pricing.test.ts +11 -0
- package/src/__tests__/public-ingress-urls.test.ts +26 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +47 -0
- package/src/__tests__/usage-attribution.test.ts +31 -0
- package/src/__tests__/workspace-migration-133-collapse-provider-connections.test.ts +3 -3
- package/src/__tests__/workspace-migration-160-repair-retired-fireworks-model-ids.test.ts +262 -0
- package/src/__tests__/workspace-migration-161-repair-retired-fireworks-glm-5p2-model-id.test.ts +233 -0
- package/src/acp/agent-process.ts +6 -1
- package/src/acp/client-handler.ts +6 -1
- package/src/agent/loop.ts +29 -6
- package/src/api/events/document-editor-show.ts +5 -0
- package/src/api/events/document-editor-update.ts +10 -0
- package/src/api/events/tool-result.ts +1 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +54 -0
- package/src/calls/channel-admission-reader.ts +15 -9
- package/src/calls/twilio-provider.ts +14 -31
- package/src/calls/voice-session-bridge.ts +18 -1
- package/src/cli/commands/browser.ts +1 -2
- package/src/cli/lib/__tests__/plugin-artifact.test.ts +23 -0
- package/src/cli/lib/__tests__/plugin-catalog-visibility.test.ts +2 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +16 -0
- package/src/cli/lib/bundled-marketplace.json +41 -2
- package/src/cli/lib/inspect-plugin.ts +15 -15
- package/src/cli/lib/list-installed-plugins.ts +29 -28
- package/src/cli/lib/plugin-artifact.ts +74 -63
- package/src/cli/lib/plugin-catalog-visibility.ts +1 -0
- package/src/cli/lib/plugin-details.ts +27 -42
- package/src/cli/lib/plugin-marketplace.ts +6 -8
- package/src/cli/lib/plugin-surfaces.ts +8 -7
- package/src/cli.ts +1 -3
- package/src/config/__tests__/chat-settings.test.ts +121 -0
- package/src/config/assistant-feature-flags.ts +28 -23
- package/src/config/bundled-skills/computer-use/SKILL.md +20 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +11 -11
- package/src/config/chat-settings.ts +100 -0
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/feature-flag-cache.ts +7 -25
- package/src/config/feature-flag-registry.json +1 -9
- package/src/config/loader.ts +5 -1
- package/src/config/profile-text-generation.ts +25 -21
- package/src/config/schemas/conversations.ts +14 -0
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/notifications.ts +8 -1
- package/src/contacts/gateway-channel-read.ts +1 -1
- package/src/conversations/__tests__/auto-archive-activity.test.ts +136 -0
- package/src/conversations/__tests__/auto-archive.test.ts +304 -0
- package/src/conversations/auto-archive-activity.ts +61 -0
- package/src/conversations/auto-archive.ts +201 -0
- package/src/daemon/computer-use-observation.test.ts +81 -0
- package/src/daemon/computer-use-observation.ts +23 -0
- package/src/daemon/conversation-agent-loop.ts +11 -6
- package/src/daemon/conversation-evictor.ts +50 -16
- package/src/daemon/doordash-steps.ts +1 -3
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +43 -1
- package/src/daemon/handlers/__tests__/config-channels.test.ts +1 -1
- package/src/daemon/handlers/config-a2a.ts +75 -2
- package/src/daemon/host-cu-proxy.ts +34 -3
- package/src/daemon/lifecycle.ts +15 -2
- package/src/daemon/shutdown-handlers.ts +3 -0
- package/src/daemon/turn-finalization.ts +4 -0
- package/src/desktop/desktop-browser-endpoint.ts +11 -2
- package/src/desktop/desktop-chrome-policy.test.ts +1 -0
- package/src/desktop/desktop-chrome-policy.ts +7 -1
- package/src/desktop/desktop-computer-use.test.ts +14 -4
- package/src/desktop/desktop-session-manager.ts +5 -3
- package/src/documents/document-revisions-store.ts +153 -0
- package/src/documents/document-store.ts +471 -133
- package/src/followups/followup-store.ts +0 -4
- package/src/inbound/__tests__/oauth-callback-url.test.ts +25 -2
- package/src/inbound/oauth-callback-url.ts +30 -1
- package/src/inbound/public-ingress-urls.ts +12 -2
- package/src/ipc/gateway-client.test.ts +40 -0
- package/src/ipc/gateway-client.ts +12 -29
- package/src/ipc/gateway-flag-listener.ts +7 -10
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +71 -0
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +83 -0
- package/src/live-voice/__tests__/protocol.test.ts +77 -0
- package/src/live-voice/__tests__/share-targets.test.ts +163 -0
- package/src/live-voice/live-voice-session.ts +23 -6
- package/src/live-voice/protocol.ts +22 -0
- package/src/live-voice/share-targets.ts +225 -0
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +144 -3
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +2 -0
- package/src/mcp/client.ts +1 -19
- package/src/mcp/mcp-oauth-provider.ts +66 -49
- package/src/media/openrouter-image-service.ts +39 -22
- package/src/monitoring/__tests__/oom-inheritance-sweep.test.ts +86 -0
- package/src/monitoring/__tests__/oom-kill-reporter.test.ts +374 -0
- package/src/monitoring/control.ts +2 -1
- package/src/monitoring/oom-inheritance-sweep.ts +96 -0
- package/src/monitoring/oom-kill-reporter.ts +469 -0
- package/src/monitoring/resource-sampler.ts +30 -0
- package/src/notifications/README.md +53 -4
- package/src/notifications/__tests__/assistant-initiated-thread.test.ts +254 -0
- package/src/notifications/__tests__/broadcaster.test.ts +558 -6
- package/src/notifications/__tests__/completion-policy.test.ts +37 -0
- package/src/notifications/__tests__/from-me-alert-integration.test.ts +192 -0
- package/src/notifications/adapters/platform.ts +14 -0
- package/src/notifications/assistant-initiated-thread.ts +101 -0
- package/src/notifications/broadcaster.ts +94 -16
- package/src/notifications/chat-reply-policy.ts +11 -0
- package/src/notifications/completion-policy.ts +12 -2
- package/src/notifications/conversation-pairing.ts +15 -70
- package/src/notifications/emit-signal.ts +34 -3
- package/src/notifications/types.ts +2 -0
- package/src/oauth/connect-orchestrator.ts +1 -2
- package/src/persistence/__tests__/conversation-auto-archive.test.ts +330 -0
- package/src/persistence/conversation-auto-archive.ts +170 -0
- package/src/persistence/conversation-crud.ts +2 -1
- package/src/persistence/conversation-queries.ts +15 -8
- package/src/persistence/embeddings/embed.test.ts +96 -0
- package/src/persistence/embeddings/embed.ts +20 -2
- package/src/persistence/embeddings/embedding-gemini.test.ts +39 -3
- package/src/persistence/embeddings/embedding-gemini.ts +39 -1
- package/src/persistence/embeddings/embedding-local.ts +6 -0
- package/src/persistence/embeddings/qdrant-manager.ts +5 -0
- package/src/persistence/job-utils.ts +10 -0
- package/src/persistence/migrations/384-add-document-revision.test.ts +83 -0
- package/src/persistence/migrations/384-add-document-revision.ts +21 -0
- package/src/persistence/migrations/385-create-document-revisions.test.ts +67 -0
- package/src/persistence/migrations/385-create-document-revisions.ts +28 -0
- package/src/persistence/migrations/386-conversations-last-reopened-at.ts +10 -0
- package/src/persistence/migrations/__tests__/386-conversations-last-reopened-at.test.ts +32 -0
- package/src/persistence/schema/conversations.ts +1 -0
- package/src/persistence/schema/documents.ts +20 -0
- package/src/persistence/steps.ts +6 -0
- package/src/platform/managed-speech.ts +42 -46
- package/src/plugin-api/vision-support.test.ts +21 -21
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +11 -6
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +1 -1
- package/src/providers/__tests__/search-provider-catalog.test.ts +8 -0
- package/src/providers/anthropic/client.ts +1 -1
- package/src/providers/fireworks/client.test.ts +168 -0
- package/src/providers/fireworks/client.ts +24 -0
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/ionet/client.test.ts +143 -0
- package/src/providers/ionet/client.ts +37 -0
- package/src/providers/model-catalog.ts +141 -84
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/openai/chat-completions-provider.ts +23 -1
- package/src/providers/retry.ts +56 -4
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/types.ts +10 -2
- package/src/providers/usage-tracking.ts +5 -3
- package/src/runtime/assistant-event-hub.ts +23 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +0 -7
- package/src/runtime/auth/__tests__/route-policy.test.ts +1 -1
- package/src/runtime/channel-retry-sweep.ts +1 -2
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +35 -10
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +48 -0
- package/src/runtime/migrations/vbundle-builder.ts +19 -3
- package/src/runtime/migrations/vbundle-streaming-importer.ts +14 -19
- package/src/runtime/migrations/vbundle-streaming-validator.ts +14 -7
- package/src/runtime/migrations/vbundle-validator.ts +54 -1
- package/src/runtime/pending-interactions.ts +20 -2
- package/src/runtime/routes/__tests__/contact-routes.test.ts +5 -3
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +36 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +375 -2
- package/src/runtime/routes/__tests__/inference-send-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +12 -0
- package/src/runtime/routes/contact-routes.ts +2 -2
- package/src/runtime/routes/conversation-list-routes.ts +10 -2
- package/src/runtime/routes/conversation-query-routes.ts +48 -10
- package/src/runtime/routes/documents-routes.ts +185 -47
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -0
- package/src/runtime/routes/host-bash-routes.ts +1 -0
- package/src/runtime/routes/host-browser-routes.ts +15 -0
- package/src/runtime/routes/host-cu-routes.ts +4 -1
- package/src/runtime/routes/host-file-routes.ts +1 -0
- package/src/runtime/routes/host-proxy-result-binding.test.ts +83 -0
- package/src/runtime/routes/host-proxy-result-binding.ts +25 -0
- package/src/runtime/routes/host-transfer-routes.ts +1 -0
- package/src/runtime/routes/identity-routes.ts +3 -3
- package/src/runtime/routes/integrations/a2a.ts +4 -1
- package/src/runtime/routes/migration-routes.ts +3 -1
- package/src/runtime/routes/notification-routes.ts +1 -2
- package/src/runtime/routes/ui-snapshot-routes.test.ts +1 -0
- package/src/runtime/routes/user-route-dispatcher.ts +5 -1
- package/src/runtime/verification-templates.ts +1 -2
- package/src/sequence/analytics.ts +0 -4
- package/src/sequence/engine.ts +1 -11
- package/src/sequence/guardrails.ts +1 -6
- package/src/sequence/importer.ts +0 -8
- package/src/sequence/store.ts +0 -10
- package/src/signals/user-message.ts +0 -6
- package/src/skills/catalog-install.ts +52 -23
- package/src/tools/browser/browser-execution.ts +20 -23
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +1 -3
- package/src/tools/document/document-tool.ts +13 -11
- package/src/tools/executor.ts +1 -2
- package/src/tools/host-terminal/host-shell.ts +4 -2
- package/src/tools/network/__tests__/web-search.test.ts +105 -0
- package/src/tools/network/web-fetch.ts +2 -4
- package/src/tools/network/web-search.ts +239 -12
- package/src/tools/shared/filesystem/format-diff.ts +1 -2
- package/src/tools/shared/shell-output.test.ts +42 -0
- package/src/tools/shared/shell-output.ts +27 -4
- package/src/tools/terminal/shell.ts +3 -3
- package/src/tools/ui-surface/visual-validation.ts +1 -2
- package/src/usage/attribution.ts +18 -1
- package/src/util/__tests__/oom-priority.test.ts +55 -0
- package/src/util/__tests__/worker-memory.test.ts +16 -0
- package/src/util/debounce.ts +0 -4
- package/src/util/oom-priority.ts +79 -0
- package/src/util/platform.ts +0 -2
- package/src/util/plugin-manifest.ts +5 -7
- package/src/util/pricing.ts +25 -0
- package/src/util/worker-memory.ts +9 -3
- package/src/util/worker-process.ts +8 -0
- package/src/workspace/byok-default-profile-ensure.ts +11 -0
- package/src/workspace/hatched-date.ts +11 -4
- package/src/workspace/migrations/160-repair-retired-fireworks-model-ids.ts +209 -0
- package/src/workspace/migrations/161-repair-retired-fireworks-glm-5p2-model-id.ts +197 -0
- package/src/workspace/migrations/registry.ts +4 -0
package/.env.example
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
ANTHROPIC_API_KEY=sk-ant-...
|
|
3
3
|
OPENAI_API_KEY=sk-...
|
|
4
4
|
GEMINI_API_KEY=...
|
|
5
|
+
# Optional. IO Intelligence (io.net) open-models endpoint.
|
|
6
|
+
IONET_API_KEY=...
|
|
5
7
|
# Optional. TypeSafe / Jev System One decision model.
|
|
6
8
|
TYPESAFE_API_KEY=...
|
|
7
9
|
# Optional for authenticated/proxied Ollama deployments
|
package/ARCHITECTURE.md
CHANGED
|
@@ -45,6 +45,12 @@ Resource pressure monitoring reports sustained CPU/memory pressure on platform-h
|
|
|
45
45
|
|
|
46
46
|
**Lifecycle:** `src/daemon/resource-pressure-guard-lifecycle.ts` starts the guard at daemon boot with the first sample deferred onto a macrotask so it never blocks startup, and stops the guard (cancelling any pending deferred sample) on shutdown. The web chat banner built on this status is documented in the repo-level [`/ARCHITECTURE.md`](../ARCHITECTURE.md) "Resource Pressure Monitoring" section.
|
|
47
47
|
|
|
48
|
+
### OOM Priority
|
|
49
|
+
|
|
50
|
+
When the container hits its memory limit, the kernel kills the process with the highest `oom_score` (memory share plus `oom_score_adj`). `src/util/oom-priority.ts` steers that choice so the assistant is never the victim of a runaway tool: the daemon sets itself to -700 as the first thing in `runDaemon`, before any spawn. Children inherit the parent's value at fork, so every spawn path resets its child: shell children get +1000 from the Linux prefix in `buildShellInvocation` (`packages/environments`), desktop children and ACP agents and terminals get +1000 from `withOomScoreAdj`, an `sh -c ... exec` wrapper that keeps pid and process group, and workers, the embedding worker, and Qdrant get 0 from a parent-side write right after spawn (the resource monitor gets -500 so it outlives everything but the daemon and records the kill). The monitor also sweeps the daemon's descendants every few seconds (`src/monitoring/oom-inheritance-sweep.ts`) and writes 0 over any negative value it finds, logging the comm, so a spawn path that forgets the reset degrades to a few seconds of inherited protection instead of a protected runaway. Bundled plugins (the memory plugin's rerank worker, for one) rely on the sweep rather than importing the helper, since the plugin import boundary forbids reaching outside the plugin. Raising never needs a capability; lowering needs `CAP_SYS_RESOURCE`, which the platform grants and self-hosted Docker does not, where the daemon simply stays at 0. Nothing here caps a single process or acts early: it only decides who dies once the kernel has to kill.
|
|
51
|
+
|
|
52
|
+
**Verification telemetry:** the resource monitor reports kernel OOM kills in the container as a `watchdog` event with `check_name` `oom_kill` (`src/monitoring/oom-kill-reporter.ts`). It reads the kernel log through `dmesg` (the `/dev/kmsg` device is not mounted in the container; `dmesg` uses the syslog syscall, which the Kata profile permits) when the cgroup `memory.events` kill counter moves, on its first scan after start (the kill that took the daemon down restarts the monitor with it), and once a minute as a fallback. The log is shared by every container on that kernel, so a kill is attributed by the cgroup path in the kernel's `oom-kill:` header, matched against the container's own id, which it reads from the runtime-named mounts in `/proc/self/mountinfo` because `/proc/self/cgroup` reads `/` inside a cgroup namespace (`attribution: "cgroup"`). The kernel rate-limits that header in a burst, so header-less kills are attributed by the counter, re-read after the log: credit left after the header-attributed kills names header-less kills oldest first, header-less kills beyond the credit are another container's, and credit for a kill counted but not yet logged is spent on the next scan or reported unnamed (`attribution: "cgroup_counter"`). The first scan and hosts without the counter claim every new kill not assigned elsewhere by a header (`attribution: "kernel_log"`). The `detail` bag lists each victim's comm, `oom_score_adj` and resident size, the cgroup memory figures at the time, and `killed_daemon`, detected by the daemon's -700 score rather than a pid the restart has since replaced; where the log is unreadable a counter move still yields a victimless event. A cursor (kernel boot id plus last log timestamp) on the monitor data dir advances only after the event is queued, so a telemetry store outage delays a report rather than losing one, and resets when the kernel reboots.
|
|
53
|
+
|
|
48
54
|
### Single-Header JWT Auth Model
|
|
49
55
|
|
|
50
56
|
All HTTP API requests use a single `Authorization: Bearer <jwt>` header for authentication. The JWT carries identity, permissions, and policy versioning in a unified token.
|
|
@@ -902,6 +908,23 @@ graph LR
|
|
|
902
908
|
|
|
903
909
|
---
|
|
904
910
|
|
|
911
|
+
## Automatic Done
|
|
912
|
+
|
|
913
|
+
`src/conversations/auto-archive.ts` runs an opt-in automatic Done sweep after startup recovery, on assistant config or feature-flag invalidation, and hourly. It marks eligible inactive native chats through the existing `archived_at` state. Candidate selection and the conditional update share the same persisted eligibility predicates; runtime checks run synchronously with each conditional write. Explicit reopening records `last_reopened_at` to grant a full inactivity interval.
|
|
914
|
+
|
|
915
|
+
```mermaid
|
|
916
|
+
flowchart LR
|
|
917
|
+
TRIGGER[Startup recovery / config or feature-flag change / hourly timer] --> PAGE[Bounded candidate page]
|
|
918
|
+
PAGE --> GUARDIAN[Read pending guardian requests]
|
|
919
|
+
GUARDIAN --> CHECK[Recheck config and runtime activity]
|
|
920
|
+
CHECK --> CAS[Conditional SQLite Done update]
|
|
921
|
+
CAS --> SYNC[Conversation list and metadata invalidation]
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
Readiness, eligibility, restart behavior, and the gateway snapshot boundary are documented in [Automatic Done](docs/automatic-done.md).
|
|
925
|
+
|
|
926
|
+
---
|
|
927
|
+
|
|
905
928
|
## Plugin-Declared Schedules: Declaration → Reconciler → cron_jobs
|
|
906
929
|
|
|
907
930
|
Plugins contribute recurring schedules as a surface: directories under
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Automatic Done
|
|
2
|
+
|
|
3
|
+
Chats Settings exposes `conversations.autoArchive` through the assistant config. `enabled` defaults to `false`; `afterDays` defaults to `7` and accepts `1`, `7`, `14`, or `30`. The worker additionally requires the `sidebar-done` feature flag so the setting cannot activate legacy archive behavior.
|
|
4
|
+
|
|
5
|
+
Auto Archive marks eligible inactive chats **Done** by setting the existing `archived_at` field. The chat remains visible in All Chats and Done, retains its transcript, and can be reopened. No schedule is cancelled and no data is deleted. New visible activity uses the existing resurfacing path.
|
|
6
|
+
|
|
7
|
+
## Eligibility
|
|
8
|
+
|
|
9
|
+
Inactivity is elapsed wall time since the latest of the last message (or creation for an empty chat) and an explicit reopen. Title edits, read receipts, config edits and other `updated_at` writes do not count as conversation activity. Migration `386-conversations-last-reopened-at.ts` adds a nullable marker without backfilling or changing existing chats. Only an actual Done-to-open transition records that marker, giving a reopened chat a full configured interval.
|
|
10
|
+
|
|
11
|
+
Candidates must be unarchived standard native chats. Native notification-origin chats are eligible; an external channel binding takes precedence over the legacy origin field. Pinned, private, scheduled, background, system, consolidation and child/subagent conversations are excluded. Chats with unread assistant output, pending guardian input, a live ACP session, a running workflow, an active mode session or a nonterminal subagent are excluded.
|
|
12
|
+
|
|
13
|
+
The final runtime check also excludes accepted sends, queued/processing turns, voice residency, pending standalone surfaces, local interactions, background tool work, wake dispatches and unfinished turn finalization. ACP resume reservations are checked against their persisted parent before the resumed session registers as live.
|
|
14
|
+
|
|
15
|
+
## Lifecycle and concurrency
|
|
16
|
+
|
|
17
|
+
The assistant owns one worker, independent of client pages. Its first sweep waits for interrupted conversation recovery, database migration readiness and startup completion. Failed startup recovery leaves the worker paused until restart. Config or assistant feature-flag invalidation and an hourly timer trigger later sweeps; overlapping triggers coalesce. The gateway flag listener publishes invalidation only after its cache refresh succeeds, including reconnect catch-up, so consumers see the refreshed gate. A failed refresh emits no invalidation. Shutdown cancels retries, removes the subscription/timer and prevents a late recovery promise from starting work.
|
|
18
|
+
|
|
19
|
+
Each sweep scans bounded pages of 100 candidates with an activity/id keyset. For every page, it reads the exhaustive pending guardian-request list through the gateway. A failed read leaves that page untouched. Final eligibility and the write run synchronously inside each SQLite retry attempt. The SQL compare-and-set checks sampled message/reopen cursors and all persisted eligibility again, so activity, pinning, input-related processing, workflow/session changes, or reopening during the asynchronous gateway read cannot overwrite newer state.
|
|
20
|
+
|
|
21
|
+
Gateway-owned guardian requests live outside the assistant database. The page read is a snapshot, not a distributed transaction: a remote request created after that read cannot be atomically excluded by SQLite. Local interaction, processing and queue checks close the ordinary same-process window. The worker fails closed on an unavailable gateway or uncertain runtime state; the cross-service snapshot boundary remains explicit.
|
|
22
|
+
|
|
23
|
+
Successful updates publish the existing conversation-list and per-conversation metadata invalidations once per changed page, including updates committed before a later failure. There is no new transport event or persistent worker cursor. A restart safely scans eligible rows again, and already Done rows are excluded.
|
|
24
|
+
|
|
25
|
+
## Code and validation
|
|
26
|
+
|
|
27
|
+
- `src/conversations/auto-archive.ts`: scheduling, startup barrier, guardian snapshot and invalidation batching.
|
|
28
|
+
- `src/conversations/auto-archive-activity.ts`: synchronous runtime blockers.
|
|
29
|
+
- `src/persistence/conversation-auto-archive.ts`: shared SQL eligibility, paging and conditional writes.
|
|
30
|
+
- `src/persistence/conversation-crud.ts`: explicit reopen marker and existing activity resurfacing.
|
|
31
|
+
|
|
32
|
+
Focused tests cover eligibility and cursor races, reopen grace, paging, pending input, retries, lifecycle cancellation, coalescing and client invalidation. Migration tests verify existing data and idempotence.
|
|
@@ -53,8 +53,9 @@ Non-platform assistants keep their connected-computer behavior.
|
|
|
53
53
|
Explicit `target: "connected-computer"` or `target_client_id` selects a connected
|
|
54
54
|
computer on every client. The selected target never falls back to another
|
|
55
55
|
computer when unavailable. The virtual desktop uses a fixed 1440x810 display.
|
|
56
|
-
Observations return full-screen screenshots and mouse actions use
|
|
57
|
-
|
|
56
|
+
Observations return full-screen screenshots and mouse actions use screen
|
|
57
|
+
points. Screenshot metadata uses the transport-optimized image dimensions and
|
|
58
|
+
provides per-axis conversion from image pixels to those screen points.
|
|
58
59
|
Viewer resizing scales the canvas locally without changing the desktop layout.
|
|
59
60
|
|
|
60
61
|
Observe first and pass the returned `observation_id` with each native action.
|
package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json
CHANGED
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"./guardian-requests": "./src/guardian-requests.ts",
|
|
29
29
|
"./platform-credential": "./src/platform-credential.ts",
|
|
30
30
|
"./plan-credit": "./src/plan-credit.ts",
|
|
31
|
-
"./integration-categories": "./src/integration-categories.ts"
|
|
31
|
+
"./integration-categories": "./src/integration-categories.ts",
|
|
32
|
+
"./mcp-oauth": "./src/mcp-oauth.ts"
|
|
32
33
|
},
|
|
33
34
|
"scripts": {
|
|
34
35
|
"typecheck": "bunx tsc --noEmit",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildMcpOAuthCallbackUrl,
|
|
5
|
+
buildMcpOAuthClientMetadata,
|
|
6
|
+
buildMcpOAuthClientMetadataUrl,
|
|
7
|
+
} from "../mcp-oauth.js";
|
|
8
|
+
|
|
9
|
+
describe("MCP OAuth client metadata", () => {
|
|
10
|
+
test("builds the public client document", () => {
|
|
11
|
+
expect(
|
|
12
|
+
buildMcpOAuthClientMetadata({
|
|
13
|
+
clientId:
|
|
14
|
+
"https://velay.example/assistant-123/oauth/client-metadata.json",
|
|
15
|
+
redirectUris: [
|
|
16
|
+
"https://velay.example/assistant-123/webhooks/oauth/callback",
|
|
17
|
+
],
|
|
18
|
+
}),
|
|
19
|
+
).toEqual({
|
|
20
|
+
client_id:
|
|
21
|
+
"https://velay.example/assistant-123/oauth/client-metadata.json",
|
|
22
|
+
redirect_uris: [
|
|
23
|
+
"https://velay.example/assistant-123/webhooks/oauth/callback",
|
|
24
|
+
],
|
|
25
|
+
token_endpoint_auth_method: "none",
|
|
26
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
27
|
+
response_types: ["code"],
|
|
28
|
+
client_name: "Vellum Assistant",
|
|
29
|
+
logo_uri: "https://www.vellum.ai/favicon.svg",
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("builds callback and metadata URLs from one normalized base", () => {
|
|
34
|
+
const baseUrl = "https://velay.example/assistant-123///";
|
|
35
|
+
expect(buildMcpOAuthCallbackUrl(baseUrl)).toBe(
|
|
36
|
+
"https://velay.example/assistant-123/webhooks/oauth/callback",
|
|
37
|
+
);
|
|
38
|
+
expect(buildMcpOAuthClientMetadataUrl(baseUrl)).toBe(
|
|
39
|
+
"https://velay.example/assistant-123/oauth/client-metadata.json",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("canonicalizes equivalent public base URL spellings", () => {
|
|
44
|
+
const baseUrl = " https://VELAY.EXAMPLE:443/a/../assistant-123/// ";
|
|
45
|
+
|
|
46
|
+
expect(buildMcpOAuthCallbackUrl(baseUrl)).toBe(
|
|
47
|
+
"https://velay.example/assistant-123/webhooks/oauth/callback",
|
|
48
|
+
);
|
|
49
|
+
expect(buildMcpOAuthClientMetadataUrl(baseUrl)).toBe(
|
|
50
|
+
"https://velay.example/assistant-123/oauth/client-metadata.json",
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
});
|
package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ export * from "./ingress.js";
|
|
|
30
30
|
export * from "./no-response.js";
|
|
31
31
|
export * from "./plan-credit.js";
|
|
32
32
|
export * from "./integration-categories.js";
|
|
33
|
+
export * from "./mcp-oauth.js";
|
|
33
34
|
export * from "./platform-credential.js";
|
|
34
35
|
export * from "./remote-web-pairing.js";
|
|
35
36
|
export * from "./twilio-ingress.js";
|
package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { normalizeHttpPublicBaseUrlWithoutTrailingSlash } from "./ingress.js";
|
|
2
|
+
|
|
3
|
+
export const MCP_OAUTH_CALLBACK_PATH = "/webhooks/oauth/callback";
|
|
4
|
+
export const MCP_OAUTH_CLIENT_METADATA_PATH = "/oauth/client-metadata.json";
|
|
5
|
+
export const MCP_OAUTH_CLIENT_LOGO_URI = "https://www.vellum.ai/favicon.svg";
|
|
6
|
+
|
|
7
|
+
export interface McpOAuthClientMetadata {
|
|
8
|
+
client_id?: string;
|
|
9
|
+
redirect_uris: string[];
|
|
10
|
+
token_endpoint_auth_method: "none";
|
|
11
|
+
grant_types: ["authorization_code", "refresh_token"];
|
|
12
|
+
response_types: ["code"];
|
|
13
|
+
client_name: string;
|
|
14
|
+
logo_uri: string;
|
|
15
|
+
software_id?: string;
|
|
16
|
+
software_version?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BuildMcpOAuthClientMetadataOptions {
|
|
20
|
+
clientId?: string;
|
|
21
|
+
redirectUris: string[];
|
|
22
|
+
clientName?: string;
|
|
23
|
+
softwareId?: string;
|
|
24
|
+
softwareVersion?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function buildMcpOAuthClientMetadata(
|
|
28
|
+
options: BuildMcpOAuthClientMetadataOptions,
|
|
29
|
+
): McpOAuthClientMetadata {
|
|
30
|
+
return {
|
|
31
|
+
...(options.clientId && { client_id: options.clientId }),
|
|
32
|
+
redirect_uris: options.redirectUris,
|
|
33
|
+
token_endpoint_auth_method: "none",
|
|
34
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
35
|
+
response_types: ["code"],
|
|
36
|
+
client_name: options.clientName ?? "Vellum Assistant",
|
|
37
|
+
logo_uri: MCP_OAUTH_CLIENT_LOGO_URI,
|
|
38
|
+
...(options.softwareId && { software_id: options.softwareId }),
|
|
39
|
+
...(options.softwareVersion && {
|
|
40
|
+
software_version: options.softwareVersion,
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function buildMcpOAuthCallbackUrl(publicBaseUrl: string): string {
|
|
46
|
+
return `${normalizeBaseUrl(publicBaseUrl)}${MCP_OAUTH_CALLBACK_PATH}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function buildMcpOAuthClientMetadataUrl(publicBaseUrl: string): string {
|
|
50
|
+
return `${normalizeBaseUrl(publicBaseUrl)}${MCP_OAUTH_CLIENT_METADATA_PATH}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeBaseUrl(publicBaseUrl: string): string {
|
|
54
|
+
const normalized =
|
|
55
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash(publicBaseUrl);
|
|
56
|
+
if (!normalized) {
|
|
57
|
+
throw new Error("MCP OAuth public base URL must be an absolute HTTP URL");
|
|
58
|
+
}
|
|
59
|
+
return normalized;
|
|
60
|
+
}
|
|
@@ -3,18 +3,30 @@ import { describe, expect, test } from "bun:test";
|
|
|
3
3
|
import {
|
|
4
4
|
buildShellInvocation,
|
|
5
5
|
buildShellSpawnFlags,
|
|
6
|
+
CHILD_OOM_SCORE_ADJ,
|
|
6
7
|
pathListDelimiter,
|
|
7
8
|
prependUniquePathEntries,
|
|
8
9
|
} from "./shell.js";
|
|
9
10
|
|
|
10
11
|
describe("buildShellInvocation", () => {
|
|
11
12
|
test("uses Bash on POSIX hosts", () => {
|
|
12
|
-
expect(buildShellInvocation("printf hello", "
|
|
13
|
+
expect(buildShellInvocation("printf hello", "darwin")).toEqual({
|
|
13
14
|
command: "bash",
|
|
14
15
|
args: ["-c", "--", "printf hello"],
|
|
15
16
|
});
|
|
16
17
|
});
|
|
17
18
|
|
|
19
|
+
test("raises the shell's OOM-kill priority on Linux", () => {
|
|
20
|
+
expect(buildShellInvocation("printf hello", "linux")).toEqual({
|
|
21
|
+
command: "bash",
|
|
22
|
+
args: [
|
|
23
|
+
"-c",
|
|
24
|
+
"--",
|
|
25
|
+
`echo ${CHILD_OOM_SCORE_ADJ} 2>/dev/null >/proc/self/oom_score_adj; printf hello`,
|
|
26
|
+
],
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
18
30
|
test("uses encoded non-interactive PowerShell on Windows", () => {
|
|
19
31
|
const invocation = buildShellInvocation(
|
|
20
32
|
"Write-Output 'hello 世界'",
|
|
@@ -24,6 +24,23 @@ if (-not $__vellumCommandSucceeded) {
|
|
|
24
24
|
}
|
|
25
25
|
exit 0`;
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* `oom_score_adj` for assistant-owned tool processes. Any positive value
|
|
29
|
+
* outranks a 0-scored process; 1000 makes a tool the kernel's first choice
|
|
30
|
+
* whenever the container runs out of memory, so the assistant itself is never
|
|
31
|
+
* the victim of a runaway command.
|
|
32
|
+
*/
|
|
33
|
+
export const CHILD_OOM_SCORE_ADJ = 1000;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Prefix for a Bash `-c` script that raises the shell's own OOM-kill priority
|
|
37
|
+
* before the command runs. Every process the command forks inherits it. Bash
|
|
38
|
+
* performs a builtin's redirection itself, so `/proc/self` is the shell.
|
|
39
|
+
* Redirections apply left to right, so stderr goes to /dev/null before the
|
|
40
|
+
* procfs open can fail and complain about it.
|
|
41
|
+
*/
|
|
42
|
+
const LINUX_OOM_SCORE_ADJ_PREFIX = `echo ${CHILD_OOM_SCORE_ADJ} 2>/dev/null >/proc/self/oom_score_adj; `;
|
|
43
|
+
|
|
27
44
|
export function buildShellInvocation(
|
|
28
45
|
command: string,
|
|
29
46
|
hostPlatform: NodeJS.Platform = process.platform,
|
|
@@ -45,7 +62,11 @@ export function buildShellInvocation(
|
|
|
45
62
|
],
|
|
46
63
|
};
|
|
47
64
|
}
|
|
48
|
-
|
|
65
|
+
const script =
|
|
66
|
+
hostPlatform === "linux"
|
|
67
|
+
? `${LINUX_OOM_SCORE_ADJ_PREFIX}${command}`
|
|
68
|
+
: command;
|
|
69
|
+
return { command: "bash", args: ["-c", "--", script] };
|
|
49
70
|
}
|
|
50
71
|
|
|
51
72
|
/**
|
package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json
CHANGED
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"./guardian-requests": "./src/guardian-requests.ts",
|
|
29
29
|
"./platform-credential": "./src/platform-credential.ts",
|
|
30
30
|
"./plan-credit": "./src/plan-credit.ts",
|
|
31
|
-
"./integration-categories": "./src/integration-categories.ts"
|
|
31
|
+
"./integration-categories": "./src/integration-categories.ts",
|
|
32
|
+
"./mcp-oauth": "./src/mcp-oauth.ts"
|
|
32
33
|
},
|
|
33
34
|
"scripts": {
|
|
34
35
|
"typecheck": "bunx tsc --noEmit",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildMcpOAuthCallbackUrl,
|
|
5
|
+
buildMcpOAuthClientMetadata,
|
|
6
|
+
buildMcpOAuthClientMetadataUrl,
|
|
7
|
+
} from "../mcp-oauth.js";
|
|
8
|
+
|
|
9
|
+
describe("MCP OAuth client metadata", () => {
|
|
10
|
+
test("builds the public client document", () => {
|
|
11
|
+
expect(
|
|
12
|
+
buildMcpOAuthClientMetadata({
|
|
13
|
+
clientId:
|
|
14
|
+
"https://velay.example/assistant-123/oauth/client-metadata.json",
|
|
15
|
+
redirectUris: [
|
|
16
|
+
"https://velay.example/assistant-123/webhooks/oauth/callback",
|
|
17
|
+
],
|
|
18
|
+
}),
|
|
19
|
+
).toEqual({
|
|
20
|
+
client_id:
|
|
21
|
+
"https://velay.example/assistant-123/oauth/client-metadata.json",
|
|
22
|
+
redirect_uris: [
|
|
23
|
+
"https://velay.example/assistant-123/webhooks/oauth/callback",
|
|
24
|
+
],
|
|
25
|
+
token_endpoint_auth_method: "none",
|
|
26
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
27
|
+
response_types: ["code"],
|
|
28
|
+
client_name: "Vellum Assistant",
|
|
29
|
+
logo_uri: "https://www.vellum.ai/favicon.svg",
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("builds callback and metadata URLs from one normalized base", () => {
|
|
34
|
+
const baseUrl = "https://velay.example/assistant-123///";
|
|
35
|
+
expect(buildMcpOAuthCallbackUrl(baseUrl)).toBe(
|
|
36
|
+
"https://velay.example/assistant-123/webhooks/oauth/callback",
|
|
37
|
+
);
|
|
38
|
+
expect(buildMcpOAuthClientMetadataUrl(baseUrl)).toBe(
|
|
39
|
+
"https://velay.example/assistant-123/oauth/client-metadata.json",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("canonicalizes equivalent public base URL spellings", () => {
|
|
44
|
+
const baseUrl = " https://VELAY.EXAMPLE:443/a/../assistant-123/// ";
|
|
45
|
+
|
|
46
|
+
expect(buildMcpOAuthCallbackUrl(baseUrl)).toBe(
|
|
47
|
+
"https://velay.example/assistant-123/webhooks/oauth/callback",
|
|
48
|
+
);
|
|
49
|
+
expect(buildMcpOAuthClientMetadataUrl(baseUrl)).toBe(
|
|
50
|
+
"https://velay.example/assistant-123/oauth/client-metadata.json",
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
});
|
package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ export * from "./ingress.js";
|
|
|
30
30
|
export * from "./no-response.js";
|
|
31
31
|
export * from "./plan-credit.js";
|
|
32
32
|
export * from "./integration-categories.js";
|
|
33
|
+
export * from "./mcp-oauth.js";
|
|
33
34
|
export * from "./platform-credential.js";
|
|
34
35
|
export * from "./remote-web-pairing.js";
|
|
35
36
|
export * from "./twilio-ingress.js";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { normalizeHttpPublicBaseUrlWithoutTrailingSlash } from "./ingress.js";
|
|
2
|
+
|
|
3
|
+
export const MCP_OAUTH_CALLBACK_PATH = "/webhooks/oauth/callback";
|
|
4
|
+
export const MCP_OAUTH_CLIENT_METADATA_PATH = "/oauth/client-metadata.json";
|
|
5
|
+
export const MCP_OAUTH_CLIENT_LOGO_URI = "https://www.vellum.ai/favicon.svg";
|
|
6
|
+
|
|
7
|
+
export interface McpOAuthClientMetadata {
|
|
8
|
+
client_id?: string;
|
|
9
|
+
redirect_uris: string[];
|
|
10
|
+
token_endpoint_auth_method: "none";
|
|
11
|
+
grant_types: ["authorization_code", "refresh_token"];
|
|
12
|
+
response_types: ["code"];
|
|
13
|
+
client_name: string;
|
|
14
|
+
logo_uri: string;
|
|
15
|
+
software_id?: string;
|
|
16
|
+
software_version?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BuildMcpOAuthClientMetadataOptions {
|
|
20
|
+
clientId?: string;
|
|
21
|
+
redirectUris: string[];
|
|
22
|
+
clientName?: string;
|
|
23
|
+
softwareId?: string;
|
|
24
|
+
softwareVersion?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function buildMcpOAuthClientMetadata(
|
|
28
|
+
options: BuildMcpOAuthClientMetadataOptions,
|
|
29
|
+
): McpOAuthClientMetadata {
|
|
30
|
+
return {
|
|
31
|
+
...(options.clientId && { client_id: options.clientId }),
|
|
32
|
+
redirect_uris: options.redirectUris,
|
|
33
|
+
token_endpoint_auth_method: "none",
|
|
34
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
35
|
+
response_types: ["code"],
|
|
36
|
+
client_name: options.clientName ?? "Vellum Assistant",
|
|
37
|
+
logo_uri: MCP_OAUTH_CLIENT_LOGO_URI,
|
|
38
|
+
...(options.softwareId && { software_id: options.softwareId }),
|
|
39
|
+
...(options.softwareVersion && {
|
|
40
|
+
software_version: options.softwareVersion,
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function buildMcpOAuthCallbackUrl(publicBaseUrl: string): string {
|
|
46
|
+
return `${normalizeBaseUrl(publicBaseUrl)}${MCP_OAUTH_CALLBACK_PATH}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function buildMcpOAuthClientMetadataUrl(publicBaseUrl: string): string {
|
|
50
|
+
return `${normalizeBaseUrl(publicBaseUrl)}${MCP_OAUTH_CLIENT_METADATA_PATH}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeBaseUrl(publicBaseUrl: string): string {
|
|
54
|
+
const normalized =
|
|
55
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash(publicBaseUrl);
|
|
56
|
+
if (!normalized) {
|
|
57
|
+
throw new Error("MCP OAuth public base URL must be an absolute HTTP URL");
|
|
58
|
+
}
|
|
59
|
+
return normalized;
|
|
60
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
FeatureFlagsIpcResponseSchema,
|
|
5
|
+
VelayStatusIpcResponseSchema,
|
|
6
|
+
} from "../gateway-ipc-contracts.js";
|
|
7
|
+
|
|
8
|
+
describe("gateway status IPC contracts", () => {
|
|
9
|
+
test("filters unsupported feature flag values independently", () => {
|
|
10
|
+
expect(
|
|
11
|
+
FeatureFlagsIpcResponseSchema.parse({
|
|
12
|
+
enabled: true,
|
|
13
|
+
model: "balanced",
|
|
14
|
+
count: 3,
|
|
15
|
+
nested: { enabled: true },
|
|
16
|
+
}),
|
|
17
|
+
).toEqual({ enabled: true, model: "balanced" });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("rejects non-record feature flag responses", () => {
|
|
21
|
+
expect(FeatureFlagsIpcResponseSchema.safeParse([]).success).toBe(false);
|
|
22
|
+
expect(FeatureFlagsIpcResponseSchema.safeParse(null).success).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("normalizes a malformed Velay public URL to null", () => {
|
|
26
|
+
expect(
|
|
27
|
+
VelayStatusIpcResponseSchema.parse({
|
|
28
|
+
connected: false,
|
|
29
|
+
publicUrl: 42,
|
|
30
|
+
}),
|
|
31
|
+
).toEqual({ connected: false, publicUrl: null });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("requires a boolean Velay connection state", () => {
|
|
35
|
+
expect(
|
|
36
|
+
VelayStatusIpcResponseSchema.safeParse({ connected: "yes" }).success,
|
|
37
|
+
).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -17,6 +17,31 @@ export const GATEWAY_LOG_LEVEL_NAMES = [
|
|
|
17
17
|
] as const;
|
|
18
18
|
export type GatewayLogLevelName = (typeof GATEWAY_LOG_LEVEL_NAMES)[number];
|
|
19
19
|
|
|
20
|
+
export const FeatureFlagsIpcResponseSchema = z
|
|
21
|
+
.record(z.string(), z.unknown())
|
|
22
|
+
.transform((flags): Record<string, boolean | string> => {
|
|
23
|
+
const parsed: Record<string, boolean | string> = {};
|
|
24
|
+
for (const [key, value] of Object.entries(flags)) {
|
|
25
|
+
if (typeof value === "boolean" || typeof value === "string") {
|
|
26
|
+
parsed[key] = value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return parsed;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export type FeatureFlagsIpcResponse = z.infer<
|
|
33
|
+
typeof FeatureFlagsIpcResponseSchema
|
|
34
|
+
>;
|
|
35
|
+
|
|
36
|
+
export const VelayStatusIpcResponseSchema = z.object({
|
|
37
|
+
connected: z.boolean(),
|
|
38
|
+
publicUrl: z.string().nullable().catch(null),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export type VelayStatusIpcResponse = z.infer<
|
|
42
|
+
typeof VelayStatusIpcResponseSchema
|
|
43
|
+
>;
|
|
44
|
+
|
|
20
45
|
const GatewayLogsTailIpcParamsShape = {
|
|
21
46
|
n: z.number().int().min(1).max(1000).optional(),
|
|
22
47
|
level: z.enum(GATEWAY_LOG_LEVEL_NAMES).optional(),
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"./guardian-requests": "./src/guardian-requests.ts",
|
|
29
29
|
"./platform-credential": "./src/platform-credential.ts",
|
|
30
30
|
"./plan-credit": "./src/plan-credit.ts",
|
|
31
|
-
"./integration-categories": "./src/integration-categories.ts"
|
|
31
|
+
"./integration-categories": "./src/integration-categories.ts",
|
|
32
|
+
"./mcp-oauth": "./src/mcp-oauth.ts"
|
|
32
33
|
},
|
|
33
34
|
"scripts": {
|
|
34
35
|
"typecheck": "bunx tsc --noEmit",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildMcpOAuthCallbackUrl,
|
|
5
|
+
buildMcpOAuthClientMetadata,
|
|
6
|
+
buildMcpOAuthClientMetadataUrl,
|
|
7
|
+
} from "../mcp-oauth.js";
|
|
8
|
+
|
|
9
|
+
describe("MCP OAuth client metadata", () => {
|
|
10
|
+
test("builds the public client document", () => {
|
|
11
|
+
expect(
|
|
12
|
+
buildMcpOAuthClientMetadata({
|
|
13
|
+
clientId:
|
|
14
|
+
"https://velay.example/assistant-123/oauth/client-metadata.json",
|
|
15
|
+
redirectUris: [
|
|
16
|
+
"https://velay.example/assistant-123/webhooks/oauth/callback",
|
|
17
|
+
],
|
|
18
|
+
}),
|
|
19
|
+
).toEqual({
|
|
20
|
+
client_id:
|
|
21
|
+
"https://velay.example/assistant-123/oauth/client-metadata.json",
|
|
22
|
+
redirect_uris: [
|
|
23
|
+
"https://velay.example/assistant-123/webhooks/oauth/callback",
|
|
24
|
+
],
|
|
25
|
+
token_endpoint_auth_method: "none",
|
|
26
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
27
|
+
response_types: ["code"],
|
|
28
|
+
client_name: "Vellum Assistant",
|
|
29
|
+
logo_uri: "https://www.vellum.ai/favicon.svg",
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("builds callback and metadata URLs from one normalized base", () => {
|
|
34
|
+
const baseUrl = "https://velay.example/assistant-123///";
|
|
35
|
+
expect(buildMcpOAuthCallbackUrl(baseUrl)).toBe(
|
|
36
|
+
"https://velay.example/assistant-123/webhooks/oauth/callback",
|
|
37
|
+
);
|
|
38
|
+
expect(buildMcpOAuthClientMetadataUrl(baseUrl)).toBe(
|
|
39
|
+
"https://velay.example/assistant-123/oauth/client-metadata.json",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("canonicalizes equivalent public base URL spellings", () => {
|
|
44
|
+
const baseUrl = " https://VELAY.EXAMPLE:443/a/../assistant-123/// ";
|
|
45
|
+
|
|
46
|
+
expect(buildMcpOAuthCallbackUrl(baseUrl)).toBe(
|
|
47
|
+
"https://velay.example/assistant-123/webhooks/oauth/callback",
|
|
48
|
+
);
|
|
49
|
+
expect(buildMcpOAuthClientMetadataUrl(baseUrl)).toBe(
|
|
50
|
+
"https://velay.example/assistant-123/oauth/client-metadata.json",
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -30,6 +30,7 @@ export * from "./ingress.js";
|
|
|
30
30
|
export * from "./no-response.js";
|
|
31
31
|
export * from "./plan-credit.js";
|
|
32
32
|
export * from "./integration-categories.js";
|
|
33
|
+
export * from "./mcp-oauth.js";
|
|
33
34
|
export * from "./platform-credential.js";
|
|
34
35
|
export * from "./remote-web-pairing.js";
|
|
35
36
|
export * from "./twilio-ingress.js";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { normalizeHttpPublicBaseUrlWithoutTrailingSlash } from "./ingress.js";
|
|
2
|
+
|
|
3
|
+
export const MCP_OAUTH_CALLBACK_PATH = "/webhooks/oauth/callback";
|
|
4
|
+
export const MCP_OAUTH_CLIENT_METADATA_PATH = "/oauth/client-metadata.json";
|
|
5
|
+
export const MCP_OAUTH_CLIENT_LOGO_URI = "https://www.vellum.ai/favicon.svg";
|
|
6
|
+
|
|
7
|
+
export interface McpOAuthClientMetadata {
|
|
8
|
+
client_id?: string;
|
|
9
|
+
redirect_uris: string[];
|
|
10
|
+
token_endpoint_auth_method: "none";
|
|
11
|
+
grant_types: ["authorization_code", "refresh_token"];
|
|
12
|
+
response_types: ["code"];
|
|
13
|
+
client_name: string;
|
|
14
|
+
logo_uri: string;
|
|
15
|
+
software_id?: string;
|
|
16
|
+
software_version?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BuildMcpOAuthClientMetadataOptions {
|
|
20
|
+
clientId?: string;
|
|
21
|
+
redirectUris: string[];
|
|
22
|
+
clientName?: string;
|
|
23
|
+
softwareId?: string;
|
|
24
|
+
softwareVersion?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function buildMcpOAuthClientMetadata(
|
|
28
|
+
options: BuildMcpOAuthClientMetadataOptions,
|
|
29
|
+
): McpOAuthClientMetadata {
|
|
30
|
+
return {
|
|
31
|
+
...(options.clientId && { client_id: options.clientId }),
|
|
32
|
+
redirect_uris: options.redirectUris,
|
|
33
|
+
token_endpoint_auth_method: "none",
|
|
34
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
35
|
+
response_types: ["code"],
|
|
36
|
+
client_name: options.clientName ?? "Vellum Assistant",
|
|
37
|
+
logo_uri: MCP_OAUTH_CLIENT_LOGO_URI,
|
|
38
|
+
...(options.softwareId && { software_id: options.softwareId }),
|
|
39
|
+
...(options.softwareVersion && {
|
|
40
|
+
software_version: options.softwareVersion,
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function buildMcpOAuthCallbackUrl(publicBaseUrl: string): string {
|
|
46
|
+
return `${normalizeBaseUrl(publicBaseUrl)}${MCP_OAUTH_CALLBACK_PATH}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function buildMcpOAuthClientMetadataUrl(publicBaseUrl: string): string {
|
|
50
|
+
return `${normalizeBaseUrl(publicBaseUrl)}${MCP_OAUTH_CLIENT_METADATA_PATH}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeBaseUrl(publicBaseUrl: string): string {
|
|
54
|
+
const normalized =
|
|
55
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash(publicBaseUrl);
|
|
56
|
+
if (!normalized) {
|
|
57
|
+
throw new Error("MCP OAuth public base URL must be an absolute HTTP URL");
|
|
58
|
+
}
|
|
59
|
+
return normalized;
|
|
60
|
+
}
|