@vellumai/assistant 0.7.2 → 0.8.0
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/ARCHITECTURE.md +45 -29
- package/Dockerfile +1 -0
- package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
- package/bun.lock +3 -0
- package/docs/architecture/memory.md +5 -2
- package/knip.json +1 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
- package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
- package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
- package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
- package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
- package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
- package/openapi.yaml +470 -25
- package/package.json +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +291 -0
- package/src/__tests__/app-control-flow.test.ts +21 -11
- package/src/__tests__/approval-cascade.test.ts +8 -16
- package/src/__tests__/approval-routes-http.test.ts +6 -0
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/assistant-event.test.ts +0 -10
- package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +48 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
- package/src/__tests__/call-constants.test.ts +10 -1
- package/src/__tests__/call-controller.test.ts +127 -0
- package/src/__tests__/call-conversation-messages.test.ts +8 -2
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
- package/src/__tests__/channel-readiness-service.test.ts +4 -2
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
- package/src/__tests__/config-loader-backfill.test.ts +379 -0
- package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
- package/src/__tests__/config-watcher.test.ts +140 -69
- package/src/__tests__/context-search-agent-runner.test.ts +61 -3
- package/src/__tests__/context-search-conversations-source.test.ts +0 -24
- package/src/__tests__/context-search-fanout.test.ts +0 -1
- package/src/__tests__/context-search-memory-source.test.ts +6 -33
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +12 -7
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +457 -8
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
- package/src/__tests__/conversation-process-callsite.test.ts +38 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +74 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
- package/src/__tests__/conversation-speed-override.test.ts +0 -3
- package/src/__tests__/conversation-store.test.ts +0 -18
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
- package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +476 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +61 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/credentials-cli.test.ts +7 -0
- package/src/__tests__/cu-unified-flow.test.ts +176 -10
- package/src/__tests__/date-context.test.ts +164 -2
- package/src/__tests__/disk-pressure-guard.test.ts +262 -0
- package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
- package/src/__tests__/disk-pressure-policy.test.ts +241 -0
- package/src/__tests__/disk-pressure-routes.test.ts +379 -0
- package/src/__tests__/disk-pressure-tools.test.ts +277 -0
- package/src/__tests__/disk-usage.test.ts +150 -0
- package/src/__tests__/events-client-registration.test.ts +52 -0
- package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
- package/src/__tests__/file-write-tool.test.ts +4 -10
- package/src/__tests__/filing-service.test.ts +2 -20
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
- package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
- package/src/__tests__/heartbeat-service.test.ts +260 -11
- package/src/__tests__/host-app-control-proxy.test.ts +195 -25
- package/src/__tests__/host-bash-proxy.test.ts +227 -34
- package/src/__tests__/host-bash-routes.test.ts +178 -13
- package/src/__tests__/host-cu-proxy.test.ts +210 -3
- package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
- package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
- package/src/__tests__/host-file-proxy.test.ts +268 -6
- package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
- package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
- package/src/__tests__/http-user-message-parity.test.ts +107 -1
- package/src/__tests__/injector-chain.test.ts +36 -16
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
- package/src/__tests__/managed-profile-guard.test.ts +18 -0
- package/src/__tests__/mcp-abort-signal.test.ts +130 -0
- package/src/__tests__/memory-admin-recall.test.ts +3 -11
- package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
- package/src/__tests__/normalize-onboarding.test.ts +180 -0
- package/src/__tests__/notification-decision-fallback.test.ts +91 -0
- package/src/__tests__/notification-decision-strategy.test.ts +22 -0
- package/src/__tests__/oauth-cli.test.ts +121 -0
- package/src/__tests__/oauth-connect-routes.test.ts +316 -0
- package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
- package/src/__tests__/onboarding-persona-write.test.ts +308 -0
- package/src/__tests__/openai-provider.test.ts +45 -8
- package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
- package/src/__tests__/platform-callback-registration.test.ts +21 -4
- package/src/__tests__/platform.test.ts +2 -1
- package/src/__tests__/playbook-execution.test.ts +0 -43
- package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
- package/src/__tests__/provider-tool-name.test.ts +23 -0
- package/src/__tests__/relay-server.test.ts +60 -5
- package/src/__tests__/runtime-events-sse.test.ts +4 -8
- package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -1
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
- package/src/__tests__/secret-response-routing.test.ts +7 -5
- package/src/__tests__/server-history-render.test.ts +82 -0
- package/src/__tests__/skill-include-graph.test.ts +31 -0
- package/src/__tests__/skill-load-tool.test.ts +44 -16
- package/src/__tests__/skills.test.ts +39 -0
- package/src/__tests__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
- package/src/__tests__/tool-executor.test.ts +155 -0
- package/src/__tests__/twilio-validation.test.ts +2 -2
- package/src/__tests__/voice-session-bridge.test.ts +3 -0
- package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
- package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
- package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
- package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
- package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +78 -0
- package/src/agent/loop.ts +11 -0
- package/src/approvals/guardian-request-resolvers.ts +3 -32
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-constants.ts +5 -8
- package/src/calls/call-controller.ts +130 -67
- package/src/calls/call-conversation-messages.ts +46 -10
- package/src/calls/relay-server.ts +7 -1
- package/src/calls/voice-session-bridge.ts +1 -1
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
- package/src/cli/commands/bash.ts +35 -108
- package/src/cli/commands/contacts.ts +64 -25
- package/src/cli/commands/credentials.ts +56 -0
- package/src/cli/commands/memory-v2.ts +11 -10
- package/src/cli/commands/oauth/__tests__/connect.test.ts +401 -219
- package/src/cli/commands/oauth/connect.ts +124 -40
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
- package/src/cli/commands/platform/index.ts +16 -7
- package/src/cli/commands/status.ts +57 -0
- package/src/cli/program.ts +4 -2
- package/src/config/assistant-feature-flags.ts +13 -3
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
- package/src/config/env.ts +0 -8
- package/src/config/feature-flag-registry.json +13 -5
- package/src/config/loader.ts +199 -27
- package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -5
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +2 -0
- package/src/config/schemas/memory-lifecycle.ts +13 -0
- package/src/config/schemas/memory-v2.ts +76 -12
- package/src/config/schemas/platform.ts +43 -3
- package/src/config/schemas/services.ts +28 -0
- package/src/config/seed-inference-profiles.ts +230 -33
- package/src/contacts/contact-store.ts +0 -25
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
- package/src/daemon/assistant-attachments.ts +4 -4
- package/src/daemon/config-watcher.ts +85 -57
- package/src/daemon/conversation-agent-loop-handlers.ts +38 -0
- package/src/daemon/conversation-agent-loop.ts +183 -43
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +22 -10
- package/src/daemon/conversation-process.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +26 -0
- package/src/daemon/conversation-store.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +211 -29
- package/src/daemon/conversation-tool-setup.ts +66 -19
- package/src/daemon/conversation.ts +18 -23
- package/src/daemon/date-context.ts +71 -22
- package/src/daemon/disk-pressure-background-gate.ts +73 -0
- package/src/daemon/disk-pressure-guard.ts +343 -0
- package/src/daemon/disk-pressure-policy.ts +163 -0
- package/src/daemon/handlers/shared.ts +26 -1
- package/src/daemon/handlers/skills.ts +3 -4
- package/src/daemon/host-app-control-proxy.ts +137 -41
- package/src/daemon/host-bash-proxy.ts +47 -22
- package/src/daemon/host-browser-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +50 -4
- package/src/daemon/host-file-proxy.ts +44 -8
- package/src/daemon/host-transfer-proxy.ts +97 -6
- package/src/daemon/lifecycle.ts +167 -101
- package/src/daemon/meet-host-supervisor.ts +4 -4
- package/src/daemon/meet-manifest-loader.ts +0 -1
- package/src/daemon/memory-v2-startup.ts +66 -15
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +4 -0
- package/src/daemon/message-types/disk-pressure.ts +9 -0
- package/src/daemon/message-types/messages.ts +22 -1
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/tool-setup-types.ts +2 -2
- package/src/documents/document-store.ts +119 -0
- package/src/filing/filing-service.ts +29 -5
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
- package/src/heartbeat/heartbeat-run-store.ts +13 -0
- package/src/heartbeat/heartbeat-service.ts +205 -31
- package/src/home/feed-scheduler.ts +18 -0
- package/src/inbound/platform-callback-registration.ts +8 -15
- package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
- package/src/ipc/assistant-server.ts +149 -38
- package/src/ipc/gateway-client.ts +37 -3
- package/src/ipc/skill-server.ts +99 -42
- package/src/live-voice/live-voice-archive.ts +4 -4
- package/src/live-voice/protocol.ts +5 -7
- package/src/media/image-service.ts +1 -7
- package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +34 -51
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
- package/src/memory/admin.ts +5 -9
- package/src/memory/context-search/agent-runner.ts +19 -2
- package/src/memory/context-search/sources/conversations.ts +2 -11
- package/src/memory/context-search/sources/memory-v2.ts +1 -16
- package/src/memory/context-search/sources/memory.ts +2 -3
- package/src/memory/context-search/sources/pkb.ts +2 -3
- package/src/memory/context-search/types.ts +0 -1
- package/src/memory/conversation-crud.ts +4 -12
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-runtime-manager.ts +119 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +136 -82
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
- package/src/memory/graph/conversation-graph-memory.ts +72 -61
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +11 -67
- package/src/memory/graph/graph-search.ts +4 -24
- package/src/memory/graph/retriever.test.ts +12 -1
- package/src/memory/graph/retriever.ts +10 -15
- package/src/memory/graph/tool-handlers.ts +3 -4
- package/src/memory/graph/tools.ts +4 -4
- package/src/memory/indexer.ts +53 -45
- package/src/memory/job-handlers/backfill.ts +2 -11
- package/src/memory/job-handlers/cleanup.ts +43 -0
- package/src/memory/job-handlers/embedding.ts +6 -8
- package/src/memory/job-handlers/summarization.ts +2 -7
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
- package/src/memory/jobs/embed-concept-page.ts +223 -87
- package/src/memory/jobs-store.ts +48 -0
- package/src/memory/jobs-worker.ts +85 -43
- package/src/memory/memory-v2-activation-log-store.ts +32 -14
- package/src/memory/memory-v2-concept-frequency.ts +169 -0
- package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +7 -0
- package/src/memory/pkb/pkb-search.ts +4 -5
- package/src/memory/qdrant-client.ts +3 -13
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/search/semantic.ts +10 -72
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +346 -255
- package/src/memory/v2/__tests__/consolidation-job.test.ts +61 -40
- package/src/memory/v2/__tests__/injection.test.ts +297 -190
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +326 -9
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +113 -196
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/static-context.test.ts +77 -14
- package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
- package/src/memory/v2/activation.ts +149 -156
- package/src/memory/v2/consolidation-job.ts +69 -20
- package/src/memory/v2/injection.ts +75 -68
- package/src/memory/v2/page-store.ts +39 -0
- package/src/memory/v2/prompts/consolidation.ts +41 -1
- package/src/memory/v2/qdrant.ts +306 -46
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +77 -110
- package/src/memory/v2/skill-content.ts +4 -3
- package/src/memory/v2/skill-store.ts +82 -59
- package/src/memory/v2/static-context.ts +26 -8
- package/src/memory/v2/sweep-job.ts +5 -6
- package/src/memory/v2/types.ts +17 -10
- package/src/notifications/copy-composer.ts +47 -0
- package/src/notifications/decision-engine.ts +46 -0
- package/src/notifications/signal.ts +4 -0
- package/src/oauth/AGENTS.md +3 -1
- package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.test.ts +66 -1
- package/src/oauth/connection-resolver.ts +55 -1
- package/src/oauth/oauth-connect-state.ts +77 -0
- package/src/oauth/seed-providers.ts +58 -1
- package/src/permissions/gateway-threshold-reader.ts +116 -8
- package/src/permissions/prompter.ts +86 -96
- package/src/permissions/secret-prompter.ts +31 -31
- package/src/plugins/defaults/injectors.ts +36 -4
- package/src/plugins/defaults/memory-retrieval.ts +5 -6
- package/src/plugins/types.ts +7 -0
- package/src/proactive-artifact/aux-message-injector.ts +74 -0
- package/src/proactive-artifact/decision.test.ts +226 -0
- package/src/proactive-artifact/decision.ts +165 -0
- package/src/proactive-artifact/index.ts +7 -0
- package/src/proactive-artifact/job.test.ts +914 -0
- package/src/proactive-artifact/job.ts +366 -0
- package/src/proactive-artifact/message-copy.ts +58 -0
- package/src/proactive-artifact/trigger-state.test.ts +277 -0
- package/src/proactive-artifact/trigger-state.ts +119 -0
- package/src/prompts/normalize-onboarding.ts +80 -0
- package/src/prompts/persona-resolver.ts +101 -9
- package/src/prompts/system-prompt.ts +21 -7
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/prompts/templates/SOUL.md +13 -28
- package/src/providers/__tests__/retry-callsite.test.ts +222 -1
- package/src/providers/model-intents.ts +7 -0
- package/src/providers/openrouter/client.ts +8 -0
- package/src/providers/retry.ts +50 -0
- package/src/providers/types.ts +1 -0
- package/src/runtime/__tests__/agent-wake.test.ts +456 -3
- package/src/runtime/agent-wake.ts +238 -100
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/assistant-event.ts +0 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
- package/src/runtime/auth/route-policy.ts +15 -1
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/channel-approvals.ts +3 -2
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/pending-interactions.ts +27 -15
- package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
- package/src/runtime/routes/approval-routes.ts +7 -3
- package/src/runtime/routes/client-routes.ts +20 -2
- package/src/runtime/routes/consolidation-routes.ts +8 -9
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-query-routes.ts +44 -1
- package/src/runtime/routes/conversation-routes.ts +35 -26
- package/src/runtime/routes/debug-bash-routes.ts +165 -0
- package/src/runtime/routes/disk-pressure-routes.ts +121 -0
- package/src/runtime/routes/document-pdf-renderer.ts +6 -2
- package/src/runtime/routes/documents-routes.ts +2 -75
- package/src/runtime/routes/events-routes.ts +41 -9
- package/src/runtime/routes/filing-routes.ts +2 -3
- package/src/runtime/routes/host-bash-routes.ts +23 -3
- package/src/runtime/routes/host-cu-routes.ts +33 -6
- package/src/runtime/routes/host-file-routes.ts +32 -6
- package/src/runtime/routes/host-transfer-routes.ts +79 -16
- package/src/runtime/routes/identity-routes.ts +7 -138
- package/src/runtime/routes/inbound-message-handler.ts +77 -12
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/memory-item-routes.test.ts +37 -17
- package/src/runtime/routes/memory-item-routes.ts +5 -6
- package/src/runtime/routes/memory-v2-routes.ts +136 -17
- package/src/runtime/routes/oauth-connect-routes.ts +153 -0
- package/src/runtime/verification-outbound-actions.ts +4 -4
- package/src/schedule/run-script.ts +37 -5
- package/src/schedule/scheduler.ts +20 -1
- package/src/security/encrypted-store.ts +2 -0
- package/src/security/secure-keys.ts +55 -0
- package/src/skills/include-graph.ts +35 -13
- package/src/skills/remote-skill-policy.ts +4 -10
- package/src/subagent/index.ts +1 -7
- package/src/subagent/manager.ts +1 -15
- package/src/tasks/task-runner.ts +0 -1
- package/src/tasks/task-store.ts +0 -3
- package/src/tools/background-tool-registry.ts +17 -3
- package/src/tools/document/document-tool.ts +20 -0
- package/src/tools/executor.ts +18 -2
- package/src/tools/host-filesystem/edit.test.ts +151 -0
- package/src/tools/host-filesystem/edit.ts +43 -1
- package/src/tools/host-filesystem/read.test.ts +129 -0
- package/src/tools/host-filesystem/read.ts +43 -1
- package/src/tools/host-filesystem/transfer.test.ts +127 -2
- package/src/tools/host-filesystem/transfer.ts +56 -11
- package/src/tools/host-filesystem/write.test.ts +134 -0
- package/src/tools/host-filesystem/write.ts +43 -1
- package/src/tools/host-terminal/host-shell.ts +13 -6
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/register.test.ts +14 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/permission-checker.ts +15 -0
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/skills/load.ts +24 -20
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +31 -6
- package/src/tools/tool-name-aliases.ts +19 -0
- package/src/tools/types.ts +43 -3
- package/src/tts/provider-catalog.ts +3 -5
- package/src/util/disk-usage.ts +138 -0
- package/src/util/platform.ts +21 -11
- package/src/util/process-liveness.ts +26 -0
- package/src/workspace/heartbeat-service.ts +19 -0
- package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
- package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +14 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
- package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
- package/src/workspace/migrations/registry.ts +14 -0
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
- package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
- package/src/memory/v2/skill-qdrant.ts +0 -404
- package/src/signals/bash.ts +0 -198
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IPC route for executing shell commands through the assistant process.
|
|
3
|
+
*
|
|
4
|
+
* The CLI sends the command over the IPC socket and receives a single
|
|
5
|
+
* response containing stdout, stderr, and the exit code.
|
|
6
|
+
*
|
|
7
|
+
* **Security**: Gated behind VELLUM_DEBUG=1. When debug mode is off (the
|
|
8
|
+
* default), the handler returns an error immediately so the CLI surfaces a
|
|
9
|
+
* clear rejection instead of hanging. The assistant must be restarted with
|
|
10
|
+
* VELLUM_DEBUG=1 for this route to execute commands.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { spawn } from "node:child_process";
|
|
14
|
+
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
import { getIsContainerized } from "../../config/env-registry.js";
|
|
18
|
+
import { buildSanitizedEnv } from "../../tools/terminal/safe-env.js";
|
|
19
|
+
import { getWorkspaceDir } from "../../util/platform.js";
|
|
20
|
+
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
21
|
+
|
|
22
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
23
|
+
|
|
24
|
+
function isDebugMode(): boolean {
|
|
25
|
+
return (
|
|
26
|
+
process.env.VELLUM_DEBUG === "1" || process.env.VELLUM_DEBUG === "true"
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface DebugBashResult {
|
|
31
|
+
stdout: string;
|
|
32
|
+
stderr: string;
|
|
33
|
+
exitCode: number | null;
|
|
34
|
+
timedOut: boolean;
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function handleDebugBash({ body }: RouteHandlerArgs): Promise<DebugBashResult> {
|
|
39
|
+
if (getIsContainerized()) {
|
|
40
|
+
return Promise.resolve({
|
|
41
|
+
stdout: "",
|
|
42
|
+
stderr: "",
|
|
43
|
+
exitCode: null,
|
|
44
|
+
timedOut: false,
|
|
45
|
+
error: "debug bash is not available in containerized environments",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!isDebugMode()) {
|
|
50
|
+
return Promise.resolve({
|
|
51
|
+
stdout: "",
|
|
52
|
+
stderr: "",
|
|
53
|
+
exitCode: null,
|
|
54
|
+
timedOut: false,
|
|
55
|
+
error:
|
|
56
|
+
"Bash debug execution is disabled. The running assistant process must have been started with VELLUM_DEBUG=1 (setting it on the CLI command alone is not enough). Restart the assistant with: vellum sleep && VELLUM_DEBUG=1 vellum wake",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { command, timeoutMs } = body as {
|
|
61
|
+
command?: string;
|
|
62
|
+
timeoutMs?: number;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (!command || typeof command !== "string") {
|
|
66
|
+
return Promise.resolve({
|
|
67
|
+
stdout: "",
|
|
68
|
+
stderr: "",
|
|
69
|
+
exitCode: null,
|
|
70
|
+
timedOut: false,
|
|
71
|
+
error: "command is required",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const effectiveTimeout =
|
|
76
|
+
typeof timeoutMs === "number" && timeoutMs > 0
|
|
77
|
+
? timeoutMs
|
|
78
|
+
: DEFAULT_TIMEOUT_MS;
|
|
79
|
+
|
|
80
|
+
return new Promise<DebugBashResult>((resolve) => {
|
|
81
|
+
const stdoutChunks: Buffer[] = [];
|
|
82
|
+
const stderrChunks: Buffer[] = [];
|
|
83
|
+
let timedOut = false;
|
|
84
|
+
let settled = false;
|
|
85
|
+
|
|
86
|
+
const finish = (result: DebugBashResult) => {
|
|
87
|
+
if (settled) return;
|
|
88
|
+
settled = true;
|
|
89
|
+
resolve(result);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const child = spawn("bash", ["-c", command], {
|
|
93
|
+
cwd: getWorkspaceDir(),
|
|
94
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
95
|
+
detached: true,
|
|
96
|
+
env: buildSanitizedEnv(),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const timer = setTimeout(() => {
|
|
100
|
+
timedOut = true;
|
|
101
|
+
try {
|
|
102
|
+
process.kill(-child.pid!, "SIGKILL");
|
|
103
|
+
} catch {
|
|
104
|
+
// Process group may have already exited.
|
|
105
|
+
}
|
|
106
|
+
}, effectiveTimeout);
|
|
107
|
+
|
|
108
|
+
child.stdout.on("data", (data: Buffer) => {
|
|
109
|
+
stdoutChunks.push(data);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
child.stderr.on("data", (data: Buffer) => {
|
|
113
|
+
stderrChunks.push(data);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
child.on("close", (code) => {
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
finish({
|
|
119
|
+
stdout: Buffer.concat(stdoutChunks).toString(),
|
|
120
|
+
stderr: Buffer.concat(stderrChunks).toString(),
|
|
121
|
+
exitCode: code,
|
|
122
|
+
timedOut,
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
child.on("error", (err) => {
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
finish({
|
|
129
|
+
stdout: "",
|
|
130
|
+
stderr: "",
|
|
131
|
+
exitCode: null,
|
|
132
|
+
timedOut: false,
|
|
133
|
+
error: err.message,
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const ROUTES: RouteDefinition[] = [
|
|
140
|
+
{
|
|
141
|
+
operationId: "debug_bash",
|
|
142
|
+
endpoint: "debug/bash",
|
|
143
|
+
method: "POST",
|
|
144
|
+
requireGuardian: false,
|
|
145
|
+
summary: "Execute a shell command in the assistant process",
|
|
146
|
+
description:
|
|
147
|
+
"Developer debugging tool. Requires the assistant to be running with VELLUM_DEBUG=1.",
|
|
148
|
+
tags: ["debug"],
|
|
149
|
+
requestBody: z.object({
|
|
150
|
+
command: z.string().describe("Shell command to execute via bash -c"),
|
|
151
|
+
timeoutMs: z
|
|
152
|
+
.number()
|
|
153
|
+
.optional()
|
|
154
|
+
.describe("Execution timeout in milliseconds (default: 30000)"),
|
|
155
|
+
}),
|
|
156
|
+
responseBody: z.object({
|
|
157
|
+
stdout: z.string(),
|
|
158
|
+
stderr: z.string(),
|
|
159
|
+
exitCode: z.number().nullable(),
|
|
160
|
+
timedOut: z.boolean(),
|
|
161
|
+
error: z.string().optional(),
|
|
162
|
+
}),
|
|
163
|
+
handler: handleDebugBash,
|
|
164
|
+
},
|
|
165
|
+
];
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
acknowledgeDiskPressureLock,
|
|
5
|
+
DISK_PRESSURE_OVERRIDE_CONFIRMATION,
|
|
6
|
+
getDiskPressureStatus,
|
|
7
|
+
overrideDiskPressureLock,
|
|
8
|
+
} from "../../daemon/disk-pressure-guard.js";
|
|
9
|
+
import { RouteError } from "./errors.js";
|
|
10
|
+
import type { RouteDefinition } from "./types.js";
|
|
11
|
+
|
|
12
|
+
const DiskPressureStatusSchema = z.object({
|
|
13
|
+
enabled: z.boolean(),
|
|
14
|
+
state: z.enum(["disabled", "ok", "critical", "unknown"]),
|
|
15
|
+
locked: z.boolean(),
|
|
16
|
+
acknowledged: z.boolean(),
|
|
17
|
+
overrideActive: z.boolean(),
|
|
18
|
+
effectivelyLocked: z.boolean(),
|
|
19
|
+
lockId: z.string().nullable(),
|
|
20
|
+
usagePercent: z.number().nullable(),
|
|
21
|
+
thresholdPercent: z.number(),
|
|
22
|
+
path: z.string().nullable(),
|
|
23
|
+
lastCheckedAt: z.string().nullable(),
|
|
24
|
+
blockedCapabilities: z.array(
|
|
25
|
+
z.enum(["agent-turns", "background-work", "remote-ingress"]),
|
|
26
|
+
),
|
|
27
|
+
error: z.string().nullable(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const DiskPressureActionResponseSchema = z.object({
|
|
31
|
+
status: DiskPressureStatusSchema,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const OverrideRequestSchema = z.object({
|
|
35
|
+
confirmation: z.string(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function statusResponse() {
|
|
39
|
+
return { status: getDiskPressureStatus() };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function transitionErrorCode(
|
|
43
|
+
reason: "not_locked" | "already_acknowledged" | "already_overridden",
|
|
44
|
+
): string {
|
|
45
|
+
if (reason === "not_locked") return "NOT_LOCKED";
|
|
46
|
+
if (reason === "already_acknowledged") return "ALREADY_ACKNOWLEDGED";
|
|
47
|
+
return "ALREADY_OVERRIDDEN";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const ROUTES: RouteDefinition[] = [
|
|
51
|
+
{
|
|
52
|
+
operationId: "getDiskPressureStatus",
|
|
53
|
+
endpoint: "disk-pressure/status",
|
|
54
|
+
method: "GET",
|
|
55
|
+
policyKey: "disk-pressure/status",
|
|
56
|
+
requirePolicyEnforcement: true,
|
|
57
|
+
summary: "Get disk pressure status",
|
|
58
|
+
description:
|
|
59
|
+
"Return the current disk pressure status snapshot. When safe storage limits are disabled, returns a disabled status.",
|
|
60
|
+
tags: ["disk-pressure"],
|
|
61
|
+
responseBody: DiskPressureActionResponseSchema,
|
|
62
|
+
handler: () => statusResponse(),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
operationId: "acknowledgeDiskPressure",
|
|
66
|
+
endpoint: "disk-pressure/acknowledge",
|
|
67
|
+
method: "POST",
|
|
68
|
+
policyKey: "disk-pressure/acknowledge",
|
|
69
|
+
requirePolicyEnforcement: true,
|
|
70
|
+
summary: "Acknowledge disk pressure",
|
|
71
|
+
description:
|
|
72
|
+
"Acknowledge the current disk pressure lock and enter cleanup mode without overriding assistant protections.",
|
|
73
|
+
tags: ["disk-pressure"],
|
|
74
|
+
responseBody: DiskPressureActionResponseSchema,
|
|
75
|
+
additionalResponses: {
|
|
76
|
+
"409": { description: "No active lock or lock already acknowledged." },
|
|
77
|
+
},
|
|
78
|
+
handler: () => {
|
|
79
|
+
const result = acknowledgeDiskPressureLock();
|
|
80
|
+
if (result.ok) return { status: result.status };
|
|
81
|
+
if (result.reason === "invalid_confirmation") {
|
|
82
|
+
throw new RouteError(result.message, "INVALID_CONFIRMATION", 400);
|
|
83
|
+
}
|
|
84
|
+
throw new RouteError(
|
|
85
|
+
result.message,
|
|
86
|
+
transitionErrorCode(result.reason),
|
|
87
|
+
409,
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
operationId: "overrideDiskPressure",
|
|
93
|
+
endpoint: "disk-pressure/override",
|
|
94
|
+
method: "POST",
|
|
95
|
+
policyKey: "disk-pressure/override",
|
|
96
|
+
requirePolicyEnforcement: true,
|
|
97
|
+
summary: "Override disk pressure",
|
|
98
|
+
description: `Override the current disk pressure lock only after confirming "${DISK_PRESSURE_OVERRIDE_CONFIRMATION}".`,
|
|
99
|
+
tags: ["disk-pressure"],
|
|
100
|
+
requestBody: OverrideRequestSchema,
|
|
101
|
+
responseBody: DiskPressureActionResponseSchema,
|
|
102
|
+
additionalResponses: {
|
|
103
|
+
"400": { description: "Confirmation phrase is invalid." },
|
|
104
|
+
"409": { description: "No active lock or lock already overridden." },
|
|
105
|
+
},
|
|
106
|
+
handler: ({ body }) => {
|
|
107
|
+
const parsed = OverrideRequestSchema.safeParse(body);
|
|
108
|
+
const confirmation = parsed.success ? parsed.data.confirmation : "";
|
|
109
|
+
const result = overrideDiskPressureLock(confirmation);
|
|
110
|
+
if (result.ok) return { status: result.status };
|
|
111
|
+
if (result.reason === "invalid_confirmation") {
|
|
112
|
+
throw new RouteError(result.message, "INVALID_CONFIRMATION", 400);
|
|
113
|
+
}
|
|
114
|
+
throw new RouteError(
|
|
115
|
+
result.message,
|
|
116
|
+
transitionErrorCode(result.reason),
|
|
117
|
+
409,
|
|
118
|
+
);
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
];
|
|
@@ -146,8 +146,12 @@ export async function renderMarkdownToPDF(
|
|
|
146
146
|
const pw = await importPlaywright();
|
|
147
147
|
const browser = await pw.chromium.launch({ headless: true });
|
|
148
148
|
try {
|
|
149
|
-
const
|
|
150
|
-
|
|
149
|
+
const context = await browser.newContext({
|
|
150
|
+
javaScriptEnabled: false,
|
|
151
|
+
});
|
|
152
|
+
const page = await context.newPage();
|
|
153
|
+
await page.route("**/*", (route) => route.abort());
|
|
154
|
+
await page.setContent(fullHtml, { waitUntil: "domcontentloaded" });
|
|
151
155
|
const pdfBuffer = await page.pdf({
|
|
152
156
|
format: "A4",
|
|
153
157
|
margin: {
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { saveDocument } from "../../documents/document-store.js";
|
|
10
|
+
import { rawAll, rawGet } from "../../memory/raw-query.js";
|
|
10
11
|
import { getLogger } from "../../util/logger.js";
|
|
11
12
|
import { renderMarkdownToPDF } from "./document-pdf-renderer.js";
|
|
12
13
|
import { BadRequestError, InternalError, NotFoundError } from "./errors.js";
|
|
@@ -27,80 +28,6 @@ interface DocumentRow {
|
|
|
27
28
|
|
|
28
29
|
type DocumentListRow = Omit<DocumentRow, "content">;
|
|
29
30
|
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
// Junction table helper
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
|
|
34
|
-
/** Insert a document–conversation association (idempotent via INSERT OR IGNORE). */
|
|
35
|
-
function addDocumentConversation(
|
|
36
|
-
surfaceId: string,
|
|
37
|
-
conversationId: string,
|
|
38
|
-
): void {
|
|
39
|
-
rawRun(
|
|
40
|
-
/*sql*/ `INSERT OR IGNORE INTO document_conversations (surface_id, conversation_id, created_at) VALUES (?, ?, ?)`,
|
|
41
|
-
surfaceId,
|
|
42
|
-
conversationId,
|
|
43
|
-
Date.now(),
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
// Shared business logic (used by both message handlers and HTTP routes)
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
|
|
51
|
-
function saveDocument(params: {
|
|
52
|
-
surfaceId: string;
|
|
53
|
-
conversationId: string;
|
|
54
|
-
title: string;
|
|
55
|
-
content: string;
|
|
56
|
-
wordCount: number;
|
|
57
|
-
}): { success: true; surfaceId: string } | { success: false; error: string } {
|
|
58
|
-
try {
|
|
59
|
-
const now = Date.now();
|
|
60
|
-
rawRun(
|
|
61
|
-
`INSERT INTO documents (surface_id, conversation_id, title, content, word_count, created_at, updated_at)
|
|
62
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
63
|
-
ON CONFLICT(surface_id) DO UPDATE SET
|
|
64
|
-
title = excluded.title,
|
|
65
|
-
content = excluded.content,
|
|
66
|
-
word_count = excluded.word_count,
|
|
67
|
-
updated_at = excluded.updated_at`,
|
|
68
|
-
params.surfaceId,
|
|
69
|
-
params.conversationId,
|
|
70
|
-
params.title,
|
|
71
|
-
params.content,
|
|
72
|
-
params.wordCount,
|
|
73
|
-
now,
|
|
74
|
-
now,
|
|
75
|
-
);
|
|
76
|
-
log.info(
|
|
77
|
-
{ surfaceId: params.surfaceId, title: params.title },
|
|
78
|
-
"Saved document",
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
// Best-effort: associate the document with the conversation.
|
|
82
|
-
// Failures (e.g. migration not yet applied, table missing) must not
|
|
83
|
-
// cause the save response to report failure — the document itself is
|
|
84
|
-
// already persisted at this point.
|
|
85
|
-
try {
|
|
86
|
-
addDocumentConversation(params.surfaceId, params.conversationId);
|
|
87
|
-
} catch (err) {
|
|
88
|
-
log.warn(
|
|
89
|
-
{ err, surfaceId: params.surfaceId },
|
|
90
|
-
"Failed to record document–conversation association",
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return { success: true, surfaceId: params.surfaceId };
|
|
95
|
-
} catch (error) {
|
|
96
|
-
log.error({ err: error, surfaceId: params.surfaceId }, "Save error");
|
|
97
|
-
return {
|
|
98
|
-
success: false,
|
|
99
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
31
|
function loadDocument(surfaceId: string):
|
|
105
32
|
| {
|
|
106
33
|
success: true;
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
* handles registration, touch (heartbeat), and unregistration (dispose).
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
|
|
21
23
|
import type { HostProxyCapability } from "../../channels/types.js";
|
|
22
24
|
import { parseInterfaceId, supportsHostProxy } from "../../channels/types.js";
|
|
25
|
+
import { emitContactChange } from "../../contacts/contact-events.js";
|
|
23
26
|
import { getOrCreateConversation } from "../../memory/conversation-key-store.js";
|
|
24
27
|
import { getLogger } from "../../util/logger.js";
|
|
25
|
-
import {
|
|
26
|
-
formatSseFrame,
|
|
27
|
-
formatSseHeartbeatWithData,
|
|
28
|
-
} from "../assistant-event.js";
|
|
28
|
+
import { formatSseFrame, formatSseHeartbeat } from "../assistant-event.js";
|
|
29
29
|
import type {
|
|
30
30
|
AssistantEventCallback,
|
|
31
31
|
AssistantEventFilter,
|
|
@@ -35,13 +35,14 @@ import {
|
|
|
35
35
|
AssistantEventHub,
|
|
36
36
|
assistantEventHub,
|
|
37
37
|
} from "../assistant-event-hub.js";
|
|
38
|
+
import { resolveActorPrincipalIdForLocalGuardian } from "../local-actor-identity.js";
|
|
38
39
|
import { BadRequestError, ServiceUnavailableError } from "./errors.js";
|
|
39
40
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
40
41
|
|
|
41
42
|
const log = getLogger("events-routes");
|
|
42
43
|
|
|
43
|
-
/** Keep-alive comment sent to idle clients every
|
|
44
|
-
const DEFAULT_HEARTBEAT_INTERVAL_MS =
|
|
44
|
+
/** Keep-alive comment sent to idle clients every 7 s by default. */
|
|
45
|
+
const DEFAULT_HEARTBEAT_INTERVAL_MS = 7_000;
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
48
|
* Stream assistant events as Server-Sent Events.
|
|
@@ -61,7 +62,7 @@ const DEFAULT_HEARTBEAT_INTERVAL_MS = 5_000;
|
|
|
61
62
|
*
|
|
62
63
|
* Options (for testing):
|
|
63
64
|
* hub -- override the event hub (defaults to process singleton).
|
|
64
|
-
* heartbeatIntervalMs -- how often to emit keep-alive comments (default
|
|
65
|
+
* heartbeatIntervalMs -- how often to emit keep-alive comments (default 7 s).
|
|
65
66
|
*/
|
|
66
67
|
export function handleSubscribeAssistantEvents(
|
|
67
68
|
args: RouteHandlerArgs,
|
|
@@ -81,10 +82,18 @@ export function handleSubscribeAssistantEvents(
|
|
|
81
82
|
const rawClientId = headers?.["x-vellum-client-id"];
|
|
82
83
|
const rawInterfaceId = headers?.["x-vellum-interface-id"];
|
|
83
84
|
const rawMachineName = headers?.["x-vellum-machine-name"];
|
|
85
|
+
const rawActorPrincipalId = headers?.["x-vellum-actor-principal-id"];
|
|
84
86
|
const clientId = rawClientId?.trim() || null;
|
|
85
87
|
const interfaceId = clientId
|
|
86
88
|
? parseInterfaceId(rawInterfaceId?.trim())
|
|
87
89
|
: null;
|
|
90
|
+
// Verified by RuntimeHttpServer and forwarded by the http-adapter from the
|
|
91
|
+
// bearer token's AuthContext. May be absent for legacy / service-token
|
|
92
|
+
// connections that have no principal. See `resolveActorPrincipalId` for the
|
|
93
|
+
// dev-bypass translation rationale.
|
|
94
|
+
const actorPrincipalId = resolveActorPrincipalIdForLocalGuardian(
|
|
95
|
+
rawActorPrincipalId?.trim() || undefined,
|
|
96
|
+
);
|
|
88
97
|
|
|
89
98
|
if (clientId && !interfaceId) {
|
|
90
99
|
log.error(
|
|
@@ -171,6 +180,7 @@ export function handleSubscribeAssistantEvents(
|
|
|
171
180
|
supportsHostProxy(interfaceId, cap),
|
|
172
181
|
),
|
|
173
182
|
machineName: rawMachineName?.trim() || undefined,
|
|
183
|
+
actorPrincipalId,
|
|
174
184
|
})
|
|
175
185
|
: hub.subscribe({
|
|
176
186
|
...subscriberBase,
|
|
@@ -194,7 +204,7 @@ export function handleSubscribeAssistantEvents(
|
|
|
194
204
|
return;
|
|
195
205
|
}
|
|
196
206
|
|
|
197
|
-
controller.enqueue(encoder.encode(
|
|
207
|
+
controller.enqueue(encoder.encode(formatSseHeartbeat()));
|
|
198
208
|
|
|
199
209
|
heartbeatTimer = setInterval(() => {
|
|
200
210
|
try {
|
|
@@ -206,7 +216,7 @@ export function handleSubscribeAssistantEvents(
|
|
|
206
216
|
if (clientId) {
|
|
207
217
|
hub.touchClient(clientId);
|
|
208
218
|
}
|
|
209
|
-
controller.enqueue(encoder.encode(
|
|
219
|
+
controller.enqueue(encoder.encode(formatSseHeartbeat()));
|
|
210
220
|
} catch {
|
|
211
221
|
sub.dispose();
|
|
212
222
|
cleanup();
|
|
@@ -237,7 +247,29 @@ export function handleSubscribeAssistantEvents(
|
|
|
237
247
|
// Route definitions
|
|
238
248
|
// ---------------------------------------------------------------------------
|
|
239
249
|
|
|
250
|
+
const EmitEventBodySchema = z.object({
|
|
251
|
+
kind: z.enum(["contacts_changed"]),
|
|
252
|
+
});
|
|
253
|
+
|
|
240
254
|
export const ROUTES: RouteDefinition[] = [
|
|
255
|
+
{
|
|
256
|
+
operationId: "emit_event",
|
|
257
|
+
endpoint: "events/emit",
|
|
258
|
+
method: "POST",
|
|
259
|
+
summary: "Emit an assistant event",
|
|
260
|
+
description:
|
|
261
|
+
"Trigger an in-process assistant event by kind. Used by the gateway after owning a write that the assistant runtime would normally emit.",
|
|
262
|
+
tags: ["events"],
|
|
263
|
+
requestBody: EmitEventBodySchema,
|
|
264
|
+
responseStatus: "204",
|
|
265
|
+
handler: ({ body }) => {
|
|
266
|
+
const { kind } = EmitEventBodySchema.parse(body);
|
|
267
|
+
if (kind === "contacts_changed") {
|
|
268
|
+
emitContactChange();
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
271
|
+
},
|
|
272
|
+
},
|
|
241
273
|
{
|
|
242
274
|
operationId: "subscribe_assistant_events",
|
|
243
275
|
endpoint: "events",
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
* Route handlers for filing management.
|
|
3
3
|
*
|
|
4
4
|
* `available` reflects whether the filing service is the active background
|
|
5
|
-
* memory job for this instance. When
|
|
5
|
+
* memory job for this instance. When `config.memory.v2.enabled` is true,
|
|
6
6
|
* filing yields to the consolidation job (see consolidation-routes.ts) and
|
|
7
7
|
* returns `available: false` so the UI can hide the row.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
|
-
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
13
12
|
import { getConfig } from "../../config/loader.js";
|
|
14
13
|
import { FilingService } from "../../filing/filing-service.js";
|
|
15
14
|
import { getLogger } from "../../util/logger.js";
|
|
@@ -19,7 +18,7 @@ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
|
19
18
|
const log = getLogger("filing-routes");
|
|
20
19
|
|
|
21
20
|
function isFilingAvailable(): boolean {
|
|
22
|
-
return !
|
|
21
|
+
return !getConfig().memory.v2.enabled;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
// ---------------------------------------------------------------------------
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
|
|
9
9
|
import { HostBashProxy } from "../../daemon/host-bash-proxy.js";
|
|
10
|
+
import {
|
|
11
|
+
enforceSameActorOrThrow,
|
|
12
|
+
SAME_ACTOR_FORBIDDEN_DESCRIPTION,
|
|
13
|
+
} from "../auth/same-actor.js";
|
|
14
|
+
import { resolveActorPrincipalIdForLocalGuardian } from "../local-actor-identity.js";
|
|
10
15
|
import * as pendingInteractions from "../pending-interactions.js";
|
|
11
16
|
import {
|
|
12
17
|
BadRequestError,
|
|
@@ -37,7 +42,11 @@ function handleHostBashResult({ body, headers }: RouteHandlerArgs) {
|
|
|
37
42
|
throw new BadRequestError("requestId is required");
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
const submittingClientId =
|
|
45
|
+
const submittingClientId =
|
|
46
|
+
headers?.["x-vellum-client-id"]?.trim() || undefined;
|
|
47
|
+
const submittingActorPrincipalId = resolveActorPrincipalIdForLocalGuardian(
|
|
48
|
+
headers?.["x-vellum-actor-principal-id"]?.trim() || undefined,
|
|
49
|
+
);
|
|
41
50
|
|
|
42
51
|
const peeked = pendingInteractions.get(requestId);
|
|
43
52
|
if (!peeked) {
|
|
@@ -62,6 +71,18 @@ function handleHostBashResult({ body, headers }: RouteHandlerArgs) {
|
|
|
62
71
|
`Client "${submittingClientId}" is not the target for this request (expected "${targetClientId}"). The targeted client must submit the result.`,
|
|
63
72
|
);
|
|
64
73
|
}
|
|
74
|
+
|
|
75
|
+
// Defense-in-depth on top of the client-id header binding above: the
|
|
76
|
+
// submitting actor's principal must match the actor principal stored
|
|
77
|
+
// for the target client at SSE subscription time. This prevents a
|
|
78
|
+
// cross-user submission even when the attacker can guess or spoof the
|
|
79
|
+
// target's client ID.
|
|
80
|
+
enforceSameActorOrThrow({
|
|
81
|
+
sourceActorPrincipalId: submittingActorPrincipalId,
|
|
82
|
+
targetActorPrincipalId: peeked.targetActorPrincipalId,
|
|
83
|
+
targetClientId,
|
|
84
|
+
op: "host_bash",
|
|
85
|
+
});
|
|
65
86
|
}
|
|
66
87
|
|
|
67
88
|
HostBashProxy.instance.resolveResult(requestId, {
|
|
@@ -103,8 +124,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
103
124
|
"x-vellum-client-id header is missing for a targeted host bash request.",
|
|
104
125
|
},
|
|
105
126
|
"403": {
|
|
106
|
-
description:
|
|
107
|
-
"Submitting client does not match the targeted client for this request.",
|
|
127
|
+
description: SAME_ACTOR_FORBIDDEN_DESCRIPTION,
|
|
108
128
|
},
|
|
109
129
|
"404": {
|
|
110
130
|
description: "No pending interaction found for the given requestId.",
|
|
@@ -7,8 +7,18 @@
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
|
|
9
9
|
import { findConversation } from "../../daemon/conversation-store.js";
|
|
10
|
+
import {
|
|
11
|
+
enforceSameActorOrThrow,
|
|
12
|
+
SAME_ACTOR_FORBIDDEN_DESCRIPTION,
|
|
13
|
+
} from "../auth/same-actor.js";
|
|
14
|
+
import { resolveActorPrincipalIdForLocalGuardian } from "../local-actor-identity.js";
|
|
10
15
|
import * as pendingInteractions from "../pending-interactions.js";
|
|
11
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
BadRequestError,
|
|
18
|
+
ConflictError,
|
|
19
|
+
ForbiddenError,
|
|
20
|
+
NotFoundError,
|
|
21
|
+
} from "./errors.js";
|
|
12
22
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
13
23
|
|
|
14
24
|
// ---------------------------------------------------------------------------
|
|
@@ -65,16 +75,34 @@ function handleHostCuResult({ body, headers }: RouteHandlerArgs) {
|
|
|
65
75
|
|
|
66
76
|
// Validate submitting client matches the targeted client (if any).
|
|
67
77
|
if (peeked.targetClientId != null) {
|
|
68
|
-
const
|
|
69
|
-
const submittingClientId =
|
|
78
|
+
const headerMap = (headers as Record<string, string | undefined>) ?? {};
|
|
79
|
+
const submittingClientId =
|
|
80
|
+
headerMap["x-vellum-client-id"]?.trim() || undefined;
|
|
70
81
|
if (!submittingClientId) {
|
|
71
|
-
throw new BadRequestError(
|
|
82
|
+
throw new BadRequestError(
|
|
83
|
+
"x-vellum-client-id header is missing for a targeted host CU request.",
|
|
84
|
+
);
|
|
72
85
|
}
|
|
73
86
|
if (submittingClientId !== peeked.targetClientId) {
|
|
74
87
|
throw new ForbiddenError(
|
|
75
88
|
`Client "${submittingClientId}" is not the target for this request (expected "${peeked.targetClientId}"). The targeted client must submit the result.`,
|
|
76
89
|
);
|
|
77
90
|
}
|
|
91
|
+
|
|
92
|
+
// Defense-in-depth: require the submitting actor's principal id to match
|
|
93
|
+
// the actor principal id captured when the target client opened its SSE
|
|
94
|
+
// stream. This prevents a different authenticated user with knowledge of
|
|
95
|
+
// both the requestId and target clientId from submitting a result on
|
|
96
|
+
// behalf of the targeted client.
|
|
97
|
+
const submittingActorPrincipalId = resolveActorPrincipalIdForLocalGuardian(
|
|
98
|
+
headerMap["x-vellum-actor-principal-id"]?.trim() || undefined,
|
|
99
|
+
);
|
|
100
|
+
enforceSameActorOrThrow({
|
|
101
|
+
sourceActorPrincipalId: submittingActorPrincipalId,
|
|
102
|
+
targetActorPrincipalId: peeked.targetActorPrincipalId,
|
|
103
|
+
targetClientId: peeked.targetClientId,
|
|
104
|
+
op: "host_cu",
|
|
105
|
+
});
|
|
78
106
|
}
|
|
79
107
|
|
|
80
108
|
const conversation = findConversation(peeked.conversationId);
|
|
@@ -141,8 +169,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
141
169
|
"x-vellum-client-id header is missing for a targeted host CU request.",
|
|
142
170
|
},
|
|
143
171
|
"403": {
|
|
144
|
-
description:
|
|
145
|
-
"Submitting client does not match the targeted client for this request.",
|
|
172
|
+
description: SAME_ACTOR_FORBIDDEN_DESCRIPTION,
|
|
146
173
|
},
|
|
147
174
|
"404": {
|
|
148
175
|
description:
|