@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,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Same-actor (same-user) binding check used by host proxies and result
|
|
3
|
+
* routes.
|
|
4
|
+
*
|
|
5
|
+
* Verifies that the submitting (source) actor's principal id matches the
|
|
6
|
+
* actor principal id captured for the target client at SSE subscription
|
|
7
|
+
* time. This is the authoritative gate that prevents cross-user
|
|
8
|
+
* execution and cross-user result submission across all three host-proxy
|
|
9
|
+
* capabilities (host_bash, host_file, host_cu).
|
|
10
|
+
*
|
|
11
|
+
* Two entry points map onto the two control-flow styles in the codebase:
|
|
12
|
+
* - {@link enforceSameActorOrErrorResult} for proxies — returns a
|
|
13
|
+
* tool-execution error result on rejection, `null` on success.
|
|
14
|
+
* - {@link enforceSameActorOrThrow} for HTTP/IPC route handlers —
|
|
15
|
+
* throws {@link ForbiddenError} on rejection so the route adapter
|
|
16
|
+
* maps it to HTTP 403.
|
|
17
|
+
*
|
|
18
|
+
* Both paths log a single structured warn line on rejection with the
|
|
19
|
+
* shape `{ sourceActorPrincipalId, targetClientId, targetActorPrincipalId,
|
|
20
|
+
* op, reason }` so that bash, file, and CU rejections render identically
|
|
21
|
+
* in the audit log.
|
|
22
|
+
*/
|
|
23
|
+
import type { HostProxyCapability } from "../../channels/types.js";
|
|
24
|
+
import { getLogger } from "../../util/logger.js";
|
|
25
|
+
import type { AssistantEventHub } from "../assistant-event-hub.js";
|
|
26
|
+
import { ForbiddenError } from "../routes/errors.js";
|
|
27
|
+
|
|
28
|
+
const log = getLogger("same-actor");
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Canonical user-facing rejection message. Used by both the proxy and
|
|
32
|
+
* route paths so operators and auditors see identical wording regardless
|
|
33
|
+
* of whether the failure surfaced as a tool-execution result or an HTTP
|
|
34
|
+
* 403.
|
|
35
|
+
*/
|
|
36
|
+
const REJECTION_MESSAGE =
|
|
37
|
+
"Submitting actor does not match the target client's actor for this request. The targeted client's authenticated user must submit the result.";
|
|
38
|
+
|
|
39
|
+
/** OpenAPI 403 description for `*-result` endpoints, kept identical. */
|
|
40
|
+
export const SAME_ACTOR_FORBIDDEN_DESCRIPTION =
|
|
41
|
+
"Submitting client does not match the targeted client, or the submitting actor's principal does not match the target client's actor.";
|
|
42
|
+
|
|
43
|
+
/** Per-capability scope for the structured warn log entry. */
|
|
44
|
+
export type SameActorOp =
|
|
45
|
+
| "host_bash"
|
|
46
|
+
| "host_file"
|
|
47
|
+
| "host_cu"
|
|
48
|
+
| "host_transfer";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Args for the live-lookup variant: caller supplies the hub + target client
|
|
52
|
+
* id, and the helper looks up the target's actor principal in real time.
|
|
53
|
+
* Used at proxy request time (registration), where the SSE subscription is
|
|
54
|
+
* present by definition.
|
|
55
|
+
*/
|
|
56
|
+
export interface SameActorLiveArgs {
|
|
57
|
+
hub: Pick<AssistantEventHub, "getActorPrincipalIdForClient">;
|
|
58
|
+
sourceActorPrincipalId: string | undefined;
|
|
59
|
+
targetClientId: string;
|
|
60
|
+
op: SameActorOp;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Args for the persisted-value variant: caller supplies a target actor
|
|
65
|
+
* principal id captured at registration time. Used at result-submission
|
|
66
|
+
* time, where the SSE subscription may have briefly disconnected and the
|
|
67
|
+
* live hub lookup would falsely 403 a legitimate result.
|
|
68
|
+
*/
|
|
69
|
+
export interface SameActorPersistedArgs {
|
|
70
|
+
sourceActorPrincipalId: string | undefined;
|
|
71
|
+
targetActorPrincipalId: string | undefined;
|
|
72
|
+
targetClientId: string;
|
|
73
|
+
op: SameActorOp;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type SameActorArgs = SameActorLiveArgs;
|
|
77
|
+
|
|
78
|
+
type RejectionReason = "missing_source" | "missing_target" | "mismatch";
|
|
79
|
+
|
|
80
|
+
function isLive(
|
|
81
|
+
args: SameActorLiveArgs | SameActorPersistedArgs,
|
|
82
|
+
): args is SameActorLiveArgs {
|
|
83
|
+
return (args as SameActorLiveArgs).hub != null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Internal: returns the rejection reason or `undefined` when the source
|
|
88
|
+
* matches the target. Always logs on rejection so all callers share the
|
|
89
|
+
* same audit shape.
|
|
90
|
+
*/
|
|
91
|
+
function detectRejection(
|
|
92
|
+
args: SameActorLiveArgs | SameActorPersistedArgs,
|
|
93
|
+
): RejectionReason | undefined {
|
|
94
|
+
const { sourceActorPrincipalId, targetClientId, op } = args;
|
|
95
|
+
const targetActorPrincipalId = isLive(args)
|
|
96
|
+
? args.hub.getActorPrincipalIdForClient(targetClientId)
|
|
97
|
+
: args.targetActorPrincipalId;
|
|
98
|
+
|
|
99
|
+
let reason: RejectionReason | undefined;
|
|
100
|
+
if (sourceActorPrincipalId == null) {
|
|
101
|
+
reason = "missing_source";
|
|
102
|
+
} else if (targetActorPrincipalId == null) {
|
|
103
|
+
reason = "missing_target";
|
|
104
|
+
} else if (sourceActorPrincipalId !== targetActorPrincipalId) {
|
|
105
|
+
reason = "mismatch";
|
|
106
|
+
}
|
|
107
|
+
if (reason == null) return undefined;
|
|
108
|
+
|
|
109
|
+
log.warn(
|
|
110
|
+
{
|
|
111
|
+
sourceActorPrincipalId,
|
|
112
|
+
targetClientId,
|
|
113
|
+
targetActorPrincipalId,
|
|
114
|
+
op,
|
|
115
|
+
reason,
|
|
116
|
+
},
|
|
117
|
+
"Rejecting cross-user host proxy request",
|
|
118
|
+
);
|
|
119
|
+
return reason;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Route-flavored variant: throws {@link ForbiddenError} on rejection so
|
|
124
|
+
* the existing route adapter maps it to HTTP 403. Returns void on
|
|
125
|
+
* success.
|
|
126
|
+
*
|
|
127
|
+
* Accepts EITHER {@link SameActorLiveArgs} (live hub lookup, used at
|
|
128
|
+
* proxy registration time) OR {@link SameActorPersistedArgs} (compare
|
|
129
|
+
* against a value captured earlier, used at result-submission time so a
|
|
130
|
+
* brief SSE reconnect doesn't 403 a legitimate result).
|
|
131
|
+
*/
|
|
132
|
+
export function enforceSameActorOrThrow(
|
|
133
|
+
args: SameActorLiveArgs | SameActorPersistedArgs,
|
|
134
|
+
): void {
|
|
135
|
+
if (detectRejection(args) != null) {
|
|
136
|
+
throw new ForbiddenError(REJECTION_MESSAGE);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Proxy-flavored variant: returns a tool-execution-shaped error result
|
|
142
|
+
* on rejection (so the proxy can pass it directly back to the agent),
|
|
143
|
+
* or `null` on success. Always uses the live hub lookup — proxy
|
|
144
|
+
* registration runs while the target SSE subscription is active.
|
|
145
|
+
*/
|
|
146
|
+
export function enforceSameActorOrErrorResult(
|
|
147
|
+
args: SameActorLiveArgs,
|
|
148
|
+
): { content: string; isError: true } | null {
|
|
149
|
+
if (detectRejection(args) == null) return null;
|
|
150
|
+
return { content: REJECTION_MESSAGE, isError: true };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Result of attempting to auto-resolve a single same-user target client.
|
|
155
|
+
*
|
|
156
|
+
* - `match`: exactly one same-user client supports the capability. Use the
|
|
157
|
+
* returned clientId.
|
|
158
|
+
* - `none`: no same-user client supports the capability. Caller's choice
|
|
159
|
+
* how to handle (typically: fall through to no-target, which broadcasts
|
|
160
|
+
* to nobody when no clients are connected).
|
|
161
|
+
* - `ambiguous`: more than one same-user client supports the capability.
|
|
162
|
+
* Caller MUST refuse to silently broadcast across them; instead surface
|
|
163
|
+
* an error asking the caller to specify `target_client_id`.
|
|
164
|
+
*/
|
|
165
|
+
export type AutoResolveResult =
|
|
166
|
+
| { kind: "match"; clientId: string }
|
|
167
|
+
| { kind: "none" }
|
|
168
|
+
| { kind: "ambiguous" };
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Filter capable clients by `actorPrincipalId === sourcePrincipalId` and
|
|
172
|
+
* report whether exactly one matched, zero matched, or more than one
|
|
173
|
+
* matched.
|
|
174
|
+
*
|
|
175
|
+
* Used by host proxies to auto-resolve a target client when the caller
|
|
176
|
+
* did not specify one. Skipping when the caller has no principal keeps
|
|
177
|
+
* the same-user binding closed: an unauthenticated caller cannot
|
|
178
|
+
* piggyback on a connected user's session.
|
|
179
|
+
*
|
|
180
|
+
* Why three outcomes (vs. just `string | undefined`)? Earlier revisions
|
|
181
|
+
* collapsed `none` and `ambiguous` into `undefined`, which caused the
|
|
182
|
+
* proxy to fall through to an untargeted broadcast — fanning a single
|
|
183
|
+
* targeted-style request out across every same-user machine. Surfacing
|
|
184
|
+
* `ambiguous` separately lets the proxy reject with a clear "specify
|
|
185
|
+
* target_client_id" error instead.
|
|
186
|
+
*/
|
|
187
|
+
export function pickSameUserAutoResolve(args: {
|
|
188
|
+
hub: Pick<AssistantEventHub, "listClientsByCapability">;
|
|
189
|
+
capability: HostProxyCapability;
|
|
190
|
+
sourceActorPrincipalId: string | undefined;
|
|
191
|
+
}): AutoResolveResult {
|
|
192
|
+
const { hub, capability, sourceActorPrincipalId } = args;
|
|
193
|
+
if (sourceActorPrincipalId == null) return { kind: "none" };
|
|
194
|
+
const sameUser = hub
|
|
195
|
+
.listClientsByCapability(capability)
|
|
196
|
+
.filter((c) => c.actorPrincipalId === sourceActorPrincipalId);
|
|
197
|
+
if (sameUser.length === 0) return { kind: "none" };
|
|
198
|
+
if (sameUser.length === 1) {
|
|
199
|
+
return { kind: "match", clientId: sameUser[0].clientId };
|
|
200
|
+
}
|
|
201
|
+
return { kind: "ambiguous" };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Standard error result for proxies when {@link pickSameUserAutoResolve}
|
|
206
|
+
* returns `ambiguous`. Asks the caller to specify `target_client_id`.
|
|
207
|
+
*/
|
|
208
|
+
export function ambiguousSameUserError(capability: HostProxyCapability): {
|
|
209
|
+
content: string;
|
|
210
|
+
isError: true;
|
|
211
|
+
} {
|
|
212
|
+
return {
|
|
213
|
+
content: `Multiple ${capability} clients are connected for this user. Specify target_client_id to disambiguate. Run \`assistant clients list --capability ${capability}\` to see client IDs.`,
|
|
214
|
+
isError: true,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
@@ -160,8 +160,9 @@ export function handleChannelDecision(
|
|
|
160
160
|
: pending[0];
|
|
161
161
|
if (!info) return { applied: false };
|
|
162
162
|
|
|
163
|
-
//
|
|
164
|
-
|
|
163
|
+
// Peek (not consume) — resolveConfirmation() owns deregistration and
|
|
164
|
+
// must fire the promptResolve callback stored in the interaction.
|
|
165
|
+
const resolved = pendingInteractions.get(info.requestId);
|
|
165
166
|
if (!resolved) return { applied: false };
|
|
166
167
|
|
|
167
168
|
// Map channel-level action to the permission system's UserDecision type.
|
|
@@ -7,9 +7,11 @@ import {
|
|
|
7
7
|
parseChannelId,
|
|
8
8
|
parseInterfaceId,
|
|
9
9
|
} from "../channels/types.js";
|
|
10
|
+
import { getDiskPressureStatus } from "../daemon/disk-pressure-guard.js";
|
|
11
|
+
import { classifyDiskPressureTurnPolicy } from "../daemon/disk-pressure-policy.js";
|
|
10
12
|
import type { TrustContext } from "../daemon/trust-context.js";
|
|
11
13
|
import { updateDeliveredSegmentCount } from "../memory/delivery-channels.js";
|
|
12
|
-
import { linkMessage } from "../memory/delivery-crud.js";
|
|
14
|
+
import { clearPayload, linkMessage } from "../memory/delivery-crud.js";
|
|
13
15
|
import {
|
|
14
16
|
getRetryableEvents,
|
|
15
17
|
markProcessed,
|
|
@@ -18,10 +20,13 @@ import {
|
|
|
18
20
|
} from "../memory/delivery-status.js";
|
|
19
21
|
import { getLogger } from "../util/logger.js";
|
|
20
22
|
import { deliverReplyViaCallback } from "./channel-reply-delivery.js";
|
|
23
|
+
import { deliverChannelReply } from "./gateway-client.js";
|
|
21
24
|
import type { MessageProcessor } from "./http-types.js";
|
|
22
25
|
import { resolveRoutingStateFromRuntime } from "./trust-context-resolver.js";
|
|
23
26
|
|
|
24
27
|
const log = getLogger("runtime-http");
|
|
28
|
+
const DISK_PRESSURE_REMOTE_BLOCK_REPLY =
|
|
29
|
+
"Storage is critically low, so remote messages are ignored until the guardian frees enough space. Please try again later.";
|
|
25
30
|
|
|
26
31
|
function parseTrustRuntimeContext(value: unknown): TrustContext | undefined {
|
|
27
32
|
if (!value || typeof value !== "object") return undefined;
|
|
@@ -163,6 +168,65 @@ export async function sweepFailedEvents(
|
|
|
163
168
|
trustClass: "unknown",
|
|
164
169
|
};
|
|
165
170
|
|
|
171
|
+
const diskPressureDecision = classifyDiskPressureTurnPolicy(
|
|
172
|
+
getDiskPressureStatus(),
|
|
173
|
+
{
|
|
174
|
+
sourceChannel,
|
|
175
|
+
sourceInterface,
|
|
176
|
+
trustContext: {
|
|
177
|
+
sourceChannel: trustContext.sourceChannel,
|
|
178
|
+
trustClass: trustContext.trustClass,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
);
|
|
182
|
+
if (diskPressureDecision.action === "block") {
|
|
183
|
+
clearPayload(event.id);
|
|
184
|
+
markProcessed(event.id);
|
|
185
|
+
log.info(
|
|
186
|
+
{
|
|
187
|
+
eventId: event.id,
|
|
188
|
+
conversationId: event.conversationId,
|
|
189
|
+
reason: diskPressureDecision.reason,
|
|
190
|
+
trustClass: trustContext.trustClass,
|
|
191
|
+
},
|
|
192
|
+
"Skipped channel retry during disk pressure cleanup mode",
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const replyCallbackUrl =
|
|
196
|
+
typeof payload.replyCallbackUrl === "string"
|
|
197
|
+
? payload.replyCallbackUrl
|
|
198
|
+
: undefined;
|
|
199
|
+
const externalChatId =
|
|
200
|
+
typeof payload.externalChatId === "string"
|
|
201
|
+
? payload.externalChatId
|
|
202
|
+
: undefined;
|
|
203
|
+
if (replyCallbackUrl && externalChatId) {
|
|
204
|
+
const requesterExternalUserId =
|
|
205
|
+
trustContext.requesterExternalUserId ??
|
|
206
|
+
(typeof payload.senderExternalUserId === "string"
|
|
207
|
+
? payload.senderExternalUserId
|
|
208
|
+
: undefined);
|
|
209
|
+
const replyPayload: Parameters<typeof deliverChannelReply>[1] = {
|
|
210
|
+
chatId: externalChatId,
|
|
211
|
+
text: DISK_PRESSURE_REMOTE_BLOCK_REPLY,
|
|
212
|
+
assistantId,
|
|
213
|
+
};
|
|
214
|
+
if (sourceChannel === "slack" && requesterExternalUserId) {
|
|
215
|
+
replyPayload.ephemeral = true;
|
|
216
|
+
replyPayload.user = requesterExternalUserId;
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
await deliverChannelReply(replyCallbackUrl, replyPayload);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
log.warn(
|
|
222
|
+
{ err, eventId: event.id, conversationId: event.conversationId },
|
|
223
|
+
"Failed to deliver disk pressure retry block reply",
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
|
|
166
230
|
const metadataHintsRaw = sourceMetadata?.hints;
|
|
167
231
|
const metadataHints = Array.isArray(metadataHintsRaw)
|
|
168
232
|
? metadataHintsRaw.filter(
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { ChannelId } from "../channels/types.js";
|
|
15
|
+
import { isHttpAuthDisabled } from "../config/env.js";
|
|
15
16
|
import { findGuardianForChannel } from "../contacts/contact-store.js";
|
|
16
17
|
import type { TrustContext } from "../daemon/trust-context.js";
|
|
17
18
|
import { getLogger } from "../util/logger.js";
|
|
@@ -43,6 +44,52 @@ export function buildLocalAuthContext(conversationId: string): AuthContext {
|
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Look up the local vellum guardian's principalId from the contacts table.
|
|
49
|
+
*
|
|
50
|
+
* Returns `undefined` when no vellum guardian binding exists (e.g. fresh
|
|
51
|
+
* install before bootstrap). Callers should treat that case as
|
|
52
|
+
* "not yet available" and either fall back or proceed without a principalId.
|
|
53
|
+
*/
|
|
54
|
+
export function findLocalGuardianPrincipalId(): string | undefined {
|
|
55
|
+
return findGuardianForChannel("vellum")?.contact.principalId ?? undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Translate the synthetic dev-bypass actor principal to the real local
|
|
60
|
+
* guardian's principalId when running in `DISABLE_HTTP_AUTH=true` mode.
|
|
61
|
+
*
|
|
62
|
+
* The dev-bypass `AuthContext` (`runtime/auth/middleware.ts`) injects
|
|
63
|
+
* `"dev-bypass"` as the actor principal id for every request, but tool-side
|
|
64
|
+
* trust resolution (`resolveLocalTrustContext`) and SSE registration both
|
|
65
|
+
* carry the real local guardian principalId. Without this translation, every
|
|
66
|
+
* targeted host_bash/host_file/host_cu/host_transfer result POST mismatches
|
|
67
|
+
* the same-user check and is rejected with 403, and conversation/surface/
|
|
68
|
+
* guardian-action routes resolve trust against the wrong principal.
|
|
69
|
+
*
|
|
70
|
+
* Returns the input unchanged when:
|
|
71
|
+
* - HTTP auth is enabled (production / non-dev-bypass deployments), OR
|
|
72
|
+
* - the input is not literally `"dev-bypass"` (e.g. service tokens).
|
|
73
|
+
*
|
|
74
|
+
* Returns the local guardian principalId when both gates are true. Returns
|
|
75
|
+
* `undefined` when dev-bypass is set but no guardian binding has been created
|
|
76
|
+
* yet (e.g. fresh install before bootstrap); callers must treat this the
|
|
77
|
+
* same as a missing principal.
|
|
78
|
+
*/
|
|
79
|
+
export function resolveActorPrincipalIdForLocalGuardian(
|
|
80
|
+
rawHeader: string | undefined,
|
|
81
|
+
): string | undefined {
|
|
82
|
+
if (rawHeader !== "dev-bypass" || !isHttpAuthDisabled()) return rawHeader;
|
|
83
|
+
|
|
84
|
+
const guardianPrincipalId = findLocalGuardianPrincipalId();
|
|
85
|
+
if (guardianPrincipalId) return guardianPrincipalId;
|
|
86
|
+
|
|
87
|
+
log.warn(
|
|
88
|
+
"dev-bypass actor principal received but no vellum guardian binding found; returning undefined",
|
|
89
|
+
);
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
46
93
|
/**
|
|
47
94
|
* Resolve the guardian runtime context for a local connection.
|
|
48
95
|
*
|
|
@@ -60,10 +107,8 @@ export function resolveLocalTrustContext(
|
|
|
60
107
|
): TrustContext {
|
|
61
108
|
const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
|
|
62
109
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (guardianResult && guardianResult.contact.principalId) {
|
|
66
|
-
const guardianPrincipalId = guardianResult.contact.principalId;
|
|
110
|
+
const guardianPrincipalId = findLocalGuardianPrincipalId();
|
|
111
|
+
if (guardianPrincipalId) {
|
|
67
112
|
const trustCtx = resolveTrustContext({
|
|
68
113
|
assistantId,
|
|
69
114
|
sourceChannel: "vellum",
|
|
@@ -97,13 +142,9 @@ export function resolveLocalTrustContext(
|
|
|
97
142
|
export function resolveLocalAuthContext(conversationId: string): AuthContext {
|
|
98
143
|
const authContext = buildLocalAuthContext(conversationId);
|
|
99
144
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
...authContext,
|
|
105
|
-
actorPrincipalId: guardianResult.contact.principalId,
|
|
106
|
-
};
|
|
145
|
+
const guardianPrincipalId = findLocalGuardianPrincipalId();
|
|
146
|
+
if (guardianPrincipalId) {
|
|
147
|
+
return { ...authContext, actorPrincipalId: guardianPrincipalId };
|
|
107
148
|
}
|
|
108
149
|
|
|
109
150
|
log.warn(
|
|
@@ -3,22 +3,22 @@
|
|
|
3
3
|
* confirmation, secret, host_bash, host_file, host_cu, host_browser, and
|
|
4
4
|
* host_transfer interactions.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* All request types self-register with their full RPC lifecycle state
|
|
7
|
+
* (resolve/reject callbacks, timer, abort detach):
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* host_transfer)
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* - Host proxies (host_bash, host_file, host_cu, host_browser,
|
|
10
|
+
* host_app_control, host_transfer): register in request(), using
|
|
11
|
+
* rpcResolve/rpcReject/timer/detachAbort/metadata.
|
|
12
|
+
*
|
|
13
|
+
* - Prompters (PermissionPrompter, SecretPrompter): register in prompt(),
|
|
14
|
+
* using promptResolve/promptReject/timer/toolUseId.
|
|
13
15
|
*
|
|
14
16
|
* Standalone HTTP endpoints (/v1/confirm, /v1/secret, /v1/trust-rules,
|
|
15
|
-
* /v1/host-bash-result,
|
|
16
|
-
* /v1/host-browser-result) look up the conversation from this tracker to
|
|
17
|
+
* /v1/host-bash-result, etc.) look up the conversation from this tracker to
|
|
17
18
|
* resolve the interaction.
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
import type { UserDecision } from "../permissions/types.js";
|
|
21
|
-
import type { ToolExecutionResult } from "../tools/types.js";
|
|
22
22
|
|
|
23
23
|
export interface ConfirmationDetails {
|
|
24
24
|
toolName: string;
|
|
@@ -59,19 +59,29 @@ export interface PendingInteraction {
|
|
|
59
59
|
directResolve?: (decision: UserDecision) => void;
|
|
60
60
|
/** When set, the host_bash request should be routed to this specific client. */
|
|
61
61
|
targetClientId?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Snapshot of `targetClientId`'s `actorPrincipalId` taken at registration
|
|
64
|
+
* time. Persisted so the result-route same-actor check compares against
|
|
65
|
+
* a stable value rather than the live hub — the target client's SSE
|
|
66
|
+
* subscription may have briefly disconnected between dispatch and result
|
|
67
|
+
* submission, which would otherwise 403 a legitimate result.
|
|
68
|
+
*/
|
|
69
|
+
targetActorPrincipalId?: string;
|
|
62
70
|
|
|
63
|
-
// -- RPC lifecycle (
|
|
71
|
+
// -- RPC lifecycle (all interaction types) --
|
|
64
72
|
|
|
65
|
-
/** Resolve the caller's Promise
|
|
66
|
-
rpcResolve?: (
|
|
73
|
+
/** Resolve the caller's Promise. Typed as unknown; callers cast at use sites. */
|
|
74
|
+
rpcResolve?: (value: unknown) => void;
|
|
67
75
|
/** Reject the caller's Promise with an error. */
|
|
68
76
|
rpcReject?: (err: Error) => void;
|
|
69
|
-
/**
|
|
77
|
+
/** Timeout timer. Cleared automatically on resolve(). */
|
|
70
78
|
timer?: ReturnType<typeof setTimeout>;
|
|
71
79
|
/** Detach the abort listener from the caller's signal. No-op when no signal was passed. */
|
|
72
80
|
detachAbort?: () => void;
|
|
73
81
|
/** Proxy-specific metadata (e.g. timeoutSec for bash, operation/path for file). */
|
|
74
82
|
metadata?: Record<string, unknown>;
|
|
83
|
+
/** toolUseId associated with a confirmation_request (PermissionPrompter). */
|
|
84
|
+
toolUseId?: string;
|
|
75
85
|
}
|
|
76
86
|
|
|
77
87
|
const pending = new Map<string, PendingInteraction>();
|
|
@@ -136,7 +146,8 @@ export function getByConversation(
|
|
|
136
146
|
* proxy timer would fire with a spurious timeout error.
|
|
137
147
|
*/
|
|
138
148
|
export function removeByConversation(conversationId: string): void {
|
|
139
|
-
|
|
149
|
+
// Snapshot keys to avoid mutation-during-iteration.
|
|
150
|
+
for (const [requestId, interaction] of [...pending]) {
|
|
140
151
|
if (
|
|
141
152
|
interaction.conversationId === conversationId &&
|
|
142
153
|
interaction.kind !== "host_bash" &&
|
|
@@ -147,7 +158,8 @@ export function removeByConversation(conversationId: string): void {
|
|
|
147
158
|
interaction.kind !== "host_transfer" &&
|
|
148
159
|
interaction.kind !== "acp_confirmation"
|
|
149
160
|
) {
|
|
150
|
-
|
|
161
|
+
// resolve() clears the stored timer and detaches abort listeners.
|
|
162
|
+
resolve(requestId);
|
|
151
163
|
}
|
|
152
164
|
}
|
|
153
165
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the GET /v1/clients (list_clients) route.
|
|
3
|
+
*
|
|
4
|
+
* Validates the same-user filter applied to client listings:
|
|
5
|
+
* - Caller sees only clients owned by their `actorPrincipalId`.
|
|
6
|
+
* - Clients with no stored `actorPrincipalId` are filtered out (fail-closed).
|
|
7
|
+
* - Dev-bypass mode (`isHttpAuthDisabled()`) returns all clients.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
// ── Module mocks (must be set up before importing the route) ──────────────
|
|
13
|
+
|
|
14
|
+
let fakeHttpAuthDisabled = false;
|
|
15
|
+
|
|
16
|
+
mock.module("../../../config/env.js", () => ({
|
|
17
|
+
isHttpAuthDisabled: () => fakeHttpAuthDisabled,
|
|
18
|
+
hasUngatedHttpAuthDisabled: () => false,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
mock.module("../../../util/logger.js", () => ({
|
|
22
|
+
getLogger: () =>
|
|
23
|
+
new Proxy({} as Record<string, unknown>, {
|
|
24
|
+
get: () => () => {},
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
// ── Real imports (after mocks) ────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
import { assistantEventHub } from "../../assistant-event-hub.js";
|
|
31
|
+
import { ROUTES } from "../client-routes.js";
|
|
32
|
+
import type { RouteDefinition } from "../types.js";
|
|
33
|
+
|
|
34
|
+
afterAll(() => {
|
|
35
|
+
mock.restore();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// ── Test helpers ──────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
function findHandler(operationId: string): RouteDefinition["handler"] {
|
|
41
|
+
const route = ROUTES.find((r) => r.operationId === operationId);
|
|
42
|
+
if (!route) throw new Error(`Route ${operationId} not found`);
|
|
43
|
+
return route.handler;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type ListClientsResponse = {
|
|
47
|
+
clients: Array<{
|
|
48
|
+
clientId: string;
|
|
49
|
+
interfaceId: string;
|
|
50
|
+
capabilities: string[];
|
|
51
|
+
machineName?: string;
|
|
52
|
+
connectedAt: string;
|
|
53
|
+
lastActiveAt: string;
|
|
54
|
+
}>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function registerClient(args: {
|
|
58
|
+
clientId: string;
|
|
59
|
+
actorPrincipalId?: string;
|
|
60
|
+
}): void {
|
|
61
|
+
assistantEventHub.subscribe({
|
|
62
|
+
type: "client",
|
|
63
|
+
clientId: args.clientId,
|
|
64
|
+
interfaceId: "macos",
|
|
65
|
+
capabilities: ["host_bash", "host_file", "host_cu"],
|
|
66
|
+
actorPrincipalId: args.actorPrincipalId,
|
|
67
|
+
callback: () => {},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function clearHub(): void {
|
|
72
|
+
const ids = assistantEventHub.listClients().map((c) => c.clientId);
|
|
73
|
+
for (const id of ids) {
|
|
74
|
+
assistantEventHub.disposeClient(id);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── Tests ────────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
describe("list_clients route — same-user filter", () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
fakeHttpAuthDisabled = false;
|
|
83
|
+
clearHub();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("returns only clients owned by the calling actor", () => {
|
|
87
|
+
registerClient({ clientId: "client-A1", actorPrincipalId: "user-A" });
|
|
88
|
+
registerClient({ clientId: "client-A2", actorPrincipalId: "user-A" });
|
|
89
|
+
registerClient({ clientId: "client-B1", actorPrincipalId: "user-B" });
|
|
90
|
+
|
|
91
|
+
const handler = findHandler("list_clients");
|
|
92
|
+
const result = handler({
|
|
93
|
+
headers: { "x-vellum-actor-principal-id": "user-A" },
|
|
94
|
+
}) as ListClientsResponse;
|
|
95
|
+
|
|
96
|
+
const ids = result.clients.map((c) => c.clientId).sort();
|
|
97
|
+
expect(ids).toEqual(["client-A1", "client-A2"]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("filters out cross-user clients when listing as a different user", () => {
|
|
101
|
+
registerClient({ clientId: "client-A1", actorPrincipalId: "user-A" });
|
|
102
|
+
registerClient({ clientId: "client-B1", actorPrincipalId: "user-B" });
|
|
103
|
+
|
|
104
|
+
const handler = findHandler("list_clients");
|
|
105
|
+
const result = handler({
|
|
106
|
+
headers: { "x-vellum-actor-principal-id": "user-B" },
|
|
107
|
+
}) as ListClientsResponse;
|
|
108
|
+
|
|
109
|
+
const ids = result.clients.map((c) => c.clientId);
|
|
110
|
+
expect(ids).toEqual(["client-B1"]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("filters out clients with no stored actorPrincipalId (fail-closed)", () => {
|
|
114
|
+
registerClient({
|
|
115
|
+
clientId: "client-noprincipal",
|
|
116
|
+
actorPrincipalId: undefined,
|
|
117
|
+
});
|
|
118
|
+
registerClient({ clientId: "client-A1", actorPrincipalId: "user-A" });
|
|
119
|
+
|
|
120
|
+
const handler = findHandler("list_clients");
|
|
121
|
+
const result = handler({
|
|
122
|
+
headers: { "x-vellum-actor-principal-id": "user-A" },
|
|
123
|
+
}) as ListClientsResponse;
|
|
124
|
+
|
|
125
|
+
const ids = result.clients.map((c) => c.clientId);
|
|
126
|
+
expect(ids).toEqual(["client-A1"]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("filters out all clients when caller has no actorPrincipalId header (fail-closed)", () => {
|
|
130
|
+
registerClient({ clientId: "client-A1", actorPrincipalId: "user-A" });
|
|
131
|
+
|
|
132
|
+
const handler = findHandler("list_clients");
|
|
133
|
+
const result = handler({}) as ListClientsResponse;
|
|
134
|
+
|
|
135
|
+
expect(result.clients).toEqual([]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("dev-bypass mode returns all clients regardless of actor", () => {
|
|
139
|
+
fakeHttpAuthDisabled = true;
|
|
140
|
+
registerClient({ clientId: "client-A1", actorPrincipalId: "user-A" });
|
|
141
|
+
registerClient({ clientId: "client-B1", actorPrincipalId: "user-B" });
|
|
142
|
+
registerClient({
|
|
143
|
+
clientId: "client-noprincipal",
|
|
144
|
+
actorPrincipalId: undefined,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const handler = findHandler("list_clients");
|
|
148
|
+
const result = handler({
|
|
149
|
+
headers: { "x-vellum-actor-principal-id": "user-A" },
|
|
150
|
+
}) as ListClientsResponse;
|
|
151
|
+
|
|
152
|
+
const ids = result.clients.map((c) => c.clientId).sort();
|
|
153
|
+
expect(ids).toEqual(["client-A1", "client-B1", "client-noprincipal"]);
|
|
154
|
+
});
|
|
155
|
+
});
|