@vellumai/assistant 0.7.2 → 0.7.3
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 +16 -1
- package/docs/architecture/memory.md +5 -2
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
- 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 +449 -22
- package/package.json +1 -1
- package/src/__tests__/app-control-flow.test.ts +21 -11
- 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 +62 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -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__/config-loader-backfill.test.ts +379 -0
- 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 +3 -7
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop.test.ts +454 -5
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-process-callsite.test.ts +43 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -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-app-control.test.ts +15 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
- 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 +3 -4
- 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 +18 -6
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- 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__/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 +15 -4
- 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__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/twilio-validation.test.ts +2 -2
- 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-safe-storage-limits-release.test.ts +90 -0
- package/src/approvals/guardian-decision-primitive.ts +13 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -17
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-conversation-messages.ts +46 -10
- 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 +7 -6
- package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
- package/src/cli/commands/oauth/connect.ts +127 -1
- 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/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 +27 -3
- package/src/config/loader.ts +127 -8
- 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 +75 -11
- 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-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 +6 -0
- package/src/daemon/conversation-agent-loop.ts +170 -33
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +1 -3
- 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 +195 -15
- package/src/daemon/conversation-tool-setup.ts +57 -14
- package/src/daemon/conversation.ts +17 -22
- 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 +0 -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 +46 -21
- package/src/daemon/host-cu-proxy.ts +49 -3
- package/src/daemon/host-file-proxy.ts +43 -7
- package/src/daemon/host-transfer-proxy.ts +95 -4
- package/src/daemon/lifecycle.ts +79 -28
- 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 +14 -4
- 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 +3 -0
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/tool-setup-types.ts +2 -2
- package/src/documents/document-store.ts +85 -0
- package/src/filing/filing-service.ts +30 -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 +56 -2
- package/src/ipc/gateway-client.ts +37 -3
- 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 +52 -22
- 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 +5 -4
- package/src/memory/context-search/sources/memory.ts +0 -1
- 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 +32 -21
- package/src/memory/graph/conversation-graph-memory.ts +42 -54
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +10 -67
- package/src/memory/graph/graph-search.ts +1 -20
- package/src/memory/graph/retriever.test.ts +6 -0
- package/src/memory/graph/retriever.ts +6 -10
- package/src/memory/indexer.ts +54 -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-store.ts +48 -0
- package/src/memory/jobs-worker.ts +81 -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 +6 -0
- package/src/memory/qdrant-client.ts +0 -13
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/search/semantic.ts +6 -67
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +311 -250
- package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
- package/src/memory/v2/__tests__/injection.test.ts +157 -167
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +5 -199
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/static-context.test.ts +76 -1
- package/src/memory/v2/activation.ts +149 -156
- package/src/memory/v2/consolidation-job.ts +62 -12
- package/src/memory/v2/injection.ts +47 -60
- package/src/memory/v2/prompts/consolidation.ts +36 -1
- package/src/memory/v2/qdrant.ts +99 -0
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +10 -84
- 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 +22 -0
- package/src/memory/v2/types.ts +10 -10
- package/src/notifications/copy-composer.ts +13 -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/plugins/defaults/injectors.ts +35 -2
- 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 +867 -0
- package/src/proactive-artifact/job.ts +352 -0
- package/src/proactive-artifact/message-copy.ts +41 -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/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 +14 -1
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/guardian-reply-router.ts +10 -0
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/pending-interactions.ts +8 -0
- 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/client-routes.ts +20 -2
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-routes.ts +35 -26
- package/src/runtime/routes/debug-bash-routes.ts +163 -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/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/inbound-stages/guardian-reply-intercept.ts +3 -0
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/memory-item-routes.test.ts +41 -15
- package/src/runtime/routes/memory-v2-routes.ts +33 -0
- 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/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/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 +12 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +31 -6
- package/src/tools/types.ts +24 -2
- 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 +72 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/registry.ts +8 -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
|
@@ -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",
|
|
@@ -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:
|
|
@@ -7,8 +7,18 @@
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
|
|
9
9
|
import { HostFileProxy } from "../../daemon/host-file-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
|
-
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
|
// ---------------------------------------------------------------------------
|
|
@@ -44,16 +54,33 @@ function handleHostFileResult({ body, headers }: RouteHandlerArgs) {
|
|
|
44
54
|
|
|
45
55
|
// Validate submitting client matches the targeted client (if any).
|
|
46
56
|
if (peeked.targetClientId != null) {
|
|
47
|
-
const
|
|
48
|
-
const submittingClientId =
|
|
57
|
+
const headerMap = (headers as Record<string, string | undefined>) ?? {};
|
|
58
|
+
const submittingClientId =
|
|
59
|
+
headerMap["x-vellum-client-id"]?.trim() || undefined;
|
|
49
60
|
if (!submittingClientId) {
|
|
50
|
-
throw new BadRequestError(
|
|
61
|
+
throw new BadRequestError(
|
|
62
|
+
"x-vellum-client-id header is missing for a targeted host file request.",
|
|
63
|
+
);
|
|
51
64
|
}
|
|
52
65
|
if (submittingClientId !== peeked.targetClientId) {
|
|
53
66
|
throw new ForbiddenError(
|
|
54
67
|
`Client "${submittingClientId}" is not the target for this request (expected "${peeked.targetClientId}"). The targeted client must submit the result.`,
|
|
55
68
|
);
|
|
56
69
|
}
|
|
70
|
+
|
|
71
|
+
// Defense-in-depth: also require the submitting actor's principal id to
|
|
72
|
+
// match the actor that opened the target client's SSE stream. This blocks
|
|
73
|
+
// cross-user submissions even if a different user somehow obtains the
|
|
74
|
+
// target client id.
|
|
75
|
+
const submittingActorPrincipalId = resolveActorPrincipalIdForLocalGuardian(
|
|
76
|
+
headerMap["x-vellum-actor-principal-id"]?.trim() || undefined,
|
|
77
|
+
);
|
|
78
|
+
enforceSameActorOrThrow({
|
|
79
|
+
sourceActorPrincipalId: submittingActorPrincipalId,
|
|
80
|
+
targetActorPrincipalId: peeked.targetActorPrincipalId,
|
|
81
|
+
targetClientId: peeked.targetClientId,
|
|
82
|
+
op: "host_file",
|
|
83
|
+
});
|
|
57
84
|
}
|
|
58
85
|
|
|
59
86
|
HostFileProxy.instance.resolve(requestId, {
|
|
@@ -102,8 +129,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
102
129
|
"x-vellum-client-id header is missing for a targeted host file request.",
|
|
103
130
|
},
|
|
104
131
|
"403": {
|
|
105
|
-
description:
|
|
106
|
-
"Submitting client does not match the targeted client for this request.",
|
|
132
|
+
description: SAME_ACTOR_FORBIDDEN_DESCRIPTION,
|
|
107
133
|
},
|
|
108
134
|
"404": {
|
|
109
135
|
description: "No pending interaction found for the given requestId.",
|
|
@@ -8,8 +8,18 @@
|
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
10
|
import { HostTransferProxy } from "../../daemon/host-transfer-proxy.js";
|
|
11
|
+
import {
|
|
12
|
+
enforceSameActorOrThrow,
|
|
13
|
+
SAME_ACTOR_FORBIDDEN_DESCRIPTION,
|
|
14
|
+
} from "../auth/same-actor.js";
|
|
15
|
+
import { resolveActorPrincipalIdForLocalGuardian } from "../local-actor-identity.js";
|
|
11
16
|
import * as pendingInteractions from "../pending-interactions.js";
|
|
12
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
BadRequestError,
|
|
19
|
+
ConflictError,
|
|
20
|
+
ForbiddenError,
|
|
21
|
+
NotFoundError,
|
|
22
|
+
} from "./errors.js";
|
|
13
23
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
14
24
|
|
|
15
25
|
/**
|
|
@@ -44,9 +54,31 @@ function handleTransferContentGet({
|
|
|
44
54
|
|
|
45
55
|
const targetClientId = match.proxy.getTargetClientIdForTransfer(transferId);
|
|
46
56
|
if (targetClientId != null) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
const headerMap = headers as Record<string, string | undefined>;
|
|
58
|
+
const submittingClientId =
|
|
59
|
+
headerMap["x-vellum-client-id"]?.trim() || undefined;
|
|
60
|
+
if (!submittingClientId)
|
|
61
|
+
throw new BadRequestError(
|
|
62
|
+
"x-vellum-client-id header required for targeted transfer",
|
|
63
|
+
);
|
|
64
|
+
if (submittingClientId !== targetClientId)
|
|
65
|
+
throw new ForbiddenError(
|
|
66
|
+
`Client "${submittingClientId}" is not the owner of this transfer`,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Defense-in-depth: the submitting actor's principal must match the
|
|
70
|
+
// actor that opened the target client's SSE stream. Compare against
|
|
71
|
+
// the value persisted at registration time so a brief reconnect does
|
|
72
|
+
// not 403 a legitimate fetch.
|
|
73
|
+
enforceSameActorOrThrow({
|
|
74
|
+
sourceActorPrincipalId: resolveActorPrincipalIdForLocalGuardian(
|
|
75
|
+
headerMap["x-vellum-actor-principal-id"]?.trim() || undefined,
|
|
76
|
+
),
|
|
77
|
+
targetActorPrincipalId:
|
|
78
|
+
match.proxy.getTargetActorPrincipalIdForTransfer(transferId),
|
|
79
|
+
targetClientId,
|
|
80
|
+
op: "host_transfer",
|
|
81
|
+
});
|
|
50
82
|
}
|
|
51
83
|
|
|
52
84
|
const content = match.proxy.getTransferContent(transferId);
|
|
@@ -105,9 +137,27 @@ async function handleTransferContentPut({
|
|
|
105
137
|
|
|
106
138
|
const targetClientId = match.proxy.getTargetClientIdForTransfer(transferId);
|
|
107
139
|
if (targetClientId != null) {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
140
|
+
const headerMap = headers as Record<string, string | undefined>;
|
|
141
|
+
const submittingClientId =
|
|
142
|
+
headerMap["x-vellum-client-id"]?.trim() || undefined;
|
|
143
|
+
if (!submittingClientId)
|
|
144
|
+
throw new BadRequestError(
|
|
145
|
+
"x-vellum-client-id header required for targeted transfer",
|
|
146
|
+
);
|
|
147
|
+
if (submittingClientId !== targetClientId)
|
|
148
|
+
throw new ForbiddenError(
|
|
149
|
+
`Client "${submittingClientId}" is not the owner of this transfer`,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
enforceSameActorOrThrow({
|
|
153
|
+
sourceActorPrincipalId: resolveActorPrincipalIdForLocalGuardian(
|
|
154
|
+
headerMap["x-vellum-actor-principal-id"]?.trim() || undefined,
|
|
155
|
+
),
|
|
156
|
+
targetActorPrincipalId:
|
|
157
|
+
match.proxy.getTargetActorPrincipalIdForTransfer(transferId),
|
|
158
|
+
targetClientId,
|
|
159
|
+
op: "host_transfer",
|
|
160
|
+
});
|
|
111
161
|
}
|
|
112
162
|
|
|
113
163
|
const data = rawBody ? Buffer.from(rawBody) : Buffer.alloc(0);
|
|
@@ -158,10 +208,26 @@ function handleTransferResult({ body, headers }: RouteHandlerArgs) {
|
|
|
158
208
|
}
|
|
159
209
|
|
|
160
210
|
if (peeked.targetClientId != null) {
|
|
161
|
-
const
|
|
211
|
+
const headerMap = (headers as Record<string, string | undefined>) ?? {};
|
|
212
|
+
const rawClientId = headerMap["x-vellum-client-id"];
|
|
162
213
|
const submittingClientId = rawClientId?.trim() || undefined;
|
|
163
|
-
if (!submittingClientId)
|
|
164
|
-
|
|
214
|
+
if (!submittingClientId)
|
|
215
|
+
throw new BadRequestError(
|
|
216
|
+
"x-vellum-client-id header is missing for a targeted host transfer request.",
|
|
217
|
+
);
|
|
218
|
+
if (submittingClientId !== peeked.targetClientId)
|
|
219
|
+
throw new ForbiddenError(
|
|
220
|
+
`Client "${submittingClientId}" is not the target for this request (expected "${peeked.targetClientId}").`,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
enforceSameActorOrThrow({
|
|
224
|
+
sourceActorPrincipalId: resolveActorPrincipalIdForLocalGuardian(
|
|
225
|
+
headerMap["x-vellum-actor-principal-id"]?.trim() || undefined,
|
|
226
|
+
),
|
|
227
|
+
targetActorPrincipalId: peeked.targetActorPrincipalId,
|
|
228
|
+
targetClientId: peeked.targetClientId,
|
|
229
|
+
op: "host_transfer",
|
|
230
|
+
});
|
|
165
231
|
}
|
|
166
232
|
|
|
167
233
|
HostTransferProxy.instance.resolveTransferResult(requestId, {
|
|
@@ -195,8 +261,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
195
261
|
"x-vellum-client-id header is missing for a targeted transfer.",
|
|
196
262
|
},
|
|
197
263
|
"403": {
|
|
198
|
-
description:
|
|
199
|
-
"Submitting client does not match the targeted client for this transfer.",
|
|
264
|
+
description: SAME_ACTOR_FORBIDDEN_DESCRIPTION,
|
|
200
265
|
},
|
|
201
266
|
},
|
|
202
267
|
handler: handleTransferContentGet,
|
|
@@ -217,8 +282,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
217
282
|
"x-vellum-client-id header is missing for a targeted transfer.",
|
|
218
283
|
},
|
|
219
284
|
"403": {
|
|
220
|
-
description:
|
|
221
|
-
"Submitting client does not match the targeted client for this transfer.",
|
|
285
|
+
description: SAME_ACTOR_FORBIDDEN_DESCRIPTION,
|
|
222
286
|
},
|
|
223
287
|
},
|
|
224
288
|
handler: handleTransferContentPut,
|
|
@@ -247,8 +311,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
247
311
|
"x-vellum-client-id header is missing for a targeted host transfer request.",
|
|
248
312
|
},
|
|
249
313
|
"403": {
|
|
250
|
-
description:
|
|
251
|
-
"Submitting client does not match the targeted client for this transfer.",
|
|
314
|
+
description: SAME_ACTOR_FORBIDDEN_DESCRIPTION,
|
|
252
315
|
},
|
|
253
316
|
},
|
|
254
317
|
handler: handleTransferResult,
|
|
@@ -2,24 +2,20 @@
|
|
|
2
2
|
* Identity and health endpoint handlers.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import { existsSync, readFileSync, statfsSync } from "node:fs";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
6
|
import { availableParallelism, cpus, totalmem } from "node:os";
|
|
8
7
|
|
|
9
8
|
import { z } from "zod";
|
|
10
9
|
|
|
11
|
-
import {
|
|
12
|
-
getCpuLimit,
|
|
13
|
-
getIsPlatform,
|
|
14
|
-
getMinikubeStorageSize,
|
|
15
|
-
} from "../../config/env-registry.js";
|
|
10
|
+
import { getCpuLimit, getIsPlatform } from "../../config/env-registry.js";
|
|
16
11
|
import { parseIdentityFields } from "../../daemon/handlers/identity.js";
|
|
17
12
|
import { getProfilerRuntimeStatus } from "../../daemon/profiler-run-store.js";
|
|
18
13
|
import { getMaxMigrationVersion } from "../../memory/migrations/registry.js";
|
|
19
14
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from "../../util/
|
|
15
|
+
getDiskUsageInfo,
|
|
16
|
+
parseK8sMemoryBytes,
|
|
17
|
+
} from "../../util/disk-usage.js";
|
|
18
|
+
import { getWorkspacePromptPath } from "../../util/platform.js";
|
|
23
19
|
import { APP_VERSION } from "../../version.js";
|
|
24
20
|
import { resolveHatchedAtReadOnly } from "../../workspace/hatched-date.js";
|
|
25
21
|
import { WORKSPACE_MIGRATIONS } from "../../workspace/migrations/registry.js";
|
|
@@ -28,138 +24,11 @@ import { NotFoundError } from "./errors.js";
|
|
|
28
24
|
import { getCachedIntro } from "./identity-intro-cache.js";
|
|
29
25
|
import type { RouteDefinition } from "./types.js";
|
|
30
26
|
|
|
31
|
-
interface DiskSpaceInfo {
|
|
32
|
-
path: string;
|
|
33
|
-
totalMb: number;
|
|
34
|
-
usedMb: number;
|
|
35
|
-
freeMb: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Measure the on-disk usage of one or more directory paths using `du -sb`.
|
|
40
|
-
* Returns the sum of all paths in bytes, or null on failure.
|
|
41
|
-
*/
|
|
42
|
-
function getDirectorySizeBytes(paths: string[]): number | null {
|
|
43
|
-
try {
|
|
44
|
-
const existing = paths.filter((p) => existsSync(p));
|
|
45
|
-
if (existing.length === 0) return null;
|
|
46
|
-
const result = spawnSync("du", ["-sb", ...existing], {
|
|
47
|
-
encoding: "utf-8",
|
|
48
|
-
timeout: 30_000,
|
|
49
|
-
});
|
|
50
|
-
if (result.status !== 0) return null;
|
|
51
|
-
let total = 0;
|
|
52
|
-
for (const line of result.stdout.trim().split("\n")) {
|
|
53
|
-
const size = parseInt(line.split("\t")[0], 10);
|
|
54
|
-
if (!isNaN(size) && size > 0) total += size;
|
|
55
|
-
}
|
|
56
|
-
return total > 0 ? total : null;
|
|
57
|
-
} catch {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const DU_CACHE_TTL_MS = 60_000;
|
|
63
|
-
let duCacheValue: number | null = null;
|
|
64
|
-
let duCacheTime = 0;
|
|
65
|
-
let duCachePaths: string | null = null;
|
|
66
|
-
|
|
67
|
-
function getCachedDirectorySizeBytes(paths: string[]): number | null {
|
|
68
|
-
const key = paths.join("\0");
|
|
69
|
-
const now = Date.now();
|
|
70
|
-
if (duCachePaths === key && now - duCacheTime < DU_CACHE_TTL_MS) {
|
|
71
|
-
return duCacheValue;
|
|
72
|
-
}
|
|
73
|
-
duCacheValue = getDirectorySizeBytes(paths);
|
|
74
|
-
duCacheTime = now;
|
|
75
|
-
duCachePaths = key;
|
|
76
|
-
return duCacheValue;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function getDiskSpaceInfo(): DiskSpaceInfo | null {
|
|
80
|
-
try {
|
|
81
|
-
const wsDir = getWorkspaceDir();
|
|
82
|
-
const diskPath = existsSync(wsDir) ? wsDir : "/";
|
|
83
|
-
const stats = statfsSync(diskPath);
|
|
84
|
-
const fsTotalBytes = stats.bsize * stats.blocks;
|
|
85
|
-
const fsFreeBytes = stats.bsize * stats.bavail;
|
|
86
|
-
const bytesToMb = (b: number) =>
|
|
87
|
-
Math.round((b / (1024 * 1024)) * 100) / 100;
|
|
88
|
-
|
|
89
|
-
// Minikube mode: the platform passes the PVC storage size so we can
|
|
90
|
-
// report accurate capacity. On hostPath-backed PVCs statfsSync reports
|
|
91
|
-
// the host's entire filesystem rather than the PVC. Detect this by
|
|
92
|
-
// comparing filesystem size against PVC size — if the filesystem is
|
|
93
|
-
// larger, measure actual directory usage with `du` instead.
|
|
94
|
-
const storageSizeRaw = getMinikubeStorageSize();
|
|
95
|
-
if (storageSizeRaw) {
|
|
96
|
-
const pvcTotalBytes = parseK8sMemoryBytes(storageSizeRaw);
|
|
97
|
-
if (pvcTotalBytes !== null && fsTotalBytes > pvcTotalBytes * 1.1) {
|
|
98
|
-
const volumePaths = [diskPath];
|
|
99
|
-
if (diskPath !== "/data" && existsSync("/data")) {
|
|
100
|
-
volumePaths.push("/data");
|
|
101
|
-
}
|
|
102
|
-
const usedBytes = getCachedDirectorySizeBytes(volumePaths);
|
|
103
|
-
if (usedBytes !== null) {
|
|
104
|
-
return {
|
|
105
|
-
path: diskPath,
|
|
106
|
-
totalMb: bytesToMb(pvcTotalBytes),
|
|
107
|
-
usedMb: bytesToMb(usedBytes),
|
|
108
|
-
freeMb: bytesToMb(Math.max(0, pvcTotalBytes - usedBytes)),
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
path: diskPath,
|
|
116
|
-
totalMb: bytesToMb(fsTotalBytes),
|
|
117
|
-
usedMb: bytesToMb(fsTotalBytes - fsFreeBytes),
|
|
118
|
-
freeMb: bytesToMb(fsFreeBytes),
|
|
119
|
-
};
|
|
120
|
-
} catch {
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
27
|
interface MemoryInfo {
|
|
126
28
|
currentMb: number;
|
|
127
29
|
maxMb: number;
|
|
128
30
|
}
|
|
129
31
|
|
|
130
|
-
/**
|
|
131
|
-
* Parse a Kubernetes-style memory string (e.g. "3Gi", "512Mi", "1G") into bytes.
|
|
132
|
-
* Returns null if the value is not a recognized format.
|
|
133
|
-
*/
|
|
134
|
-
function parseK8sMemoryBytes(value: string): number | null {
|
|
135
|
-
const match = value
|
|
136
|
-
.trim()
|
|
137
|
-
.match(/^(\d+(?:\.\d+)?)\s*(Ki|Mi|Gi|Ti|Pi|Ei|k|M|G|T|P|E|m)?$/);
|
|
138
|
-
if (!match) return null;
|
|
139
|
-
const num = parseFloat(match[1]);
|
|
140
|
-
const unit = match[2] ?? "";
|
|
141
|
-
const multipliers: Record<string, number> = {
|
|
142
|
-
"": 1,
|
|
143
|
-
m: 1e-3,
|
|
144
|
-
k: 1e3,
|
|
145
|
-
M: 1e6,
|
|
146
|
-
G: 1e9,
|
|
147
|
-
T: 1e12,
|
|
148
|
-
P: 1e15,
|
|
149
|
-
E: 1e18,
|
|
150
|
-
Ki: 1024,
|
|
151
|
-
Mi: 1024 ** 2,
|
|
152
|
-
Gi: 1024 ** 3,
|
|
153
|
-
Ti: 1024 ** 4,
|
|
154
|
-
Pi: 1024 ** 5,
|
|
155
|
-
Ei: 1024 ** 6,
|
|
156
|
-
};
|
|
157
|
-
const mult = multipliers[unit];
|
|
158
|
-
if (mult === undefined) return null;
|
|
159
|
-
const bytes = Math.round(num * mult);
|
|
160
|
-
return bytes > 0 ? bytes : null;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
32
|
/**
|
|
164
33
|
* Read the memory limit from the VELLUM_MEMORY_LIMIT env var (K8s resource format),
|
|
165
34
|
* then fall back to cgroups, then to os.totalmem().
|
|
@@ -458,7 +327,7 @@ function getDetailedHealth() {
|
|
|
458
327
|
status: "healthy",
|
|
459
328
|
timestamp: new Date().toISOString(),
|
|
460
329
|
version: APP_VERSION,
|
|
461
|
-
disk:
|
|
330
|
+
disk: getDiskUsageInfo(),
|
|
462
331
|
memory: getMemoryInfo(),
|
|
463
332
|
cpu: getCpuInfo(),
|
|
464
333
|
migrations: {
|