@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
|
@@ -3,11 +3,16 @@ import { join } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { getConfig } from "../config/loader.js";
|
|
5
5
|
import type { HeartbeatConfig } from "../config/schemas/heartbeat.js";
|
|
6
|
+
import {
|
|
7
|
+
checkDiskPressureBackgroundGate,
|
|
8
|
+
diskPressureBackgroundSkipLogFields,
|
|
9
|
+
shouldLogDiskPressureBackgroundSkip,
|
|
10
|
+
} from "../daemon/disk-pressure-background-gate.js";
|
|
6
11
|
import type { HeartbeatAlert } from "../daemon/message-protocol.js";
|
|
7
12
|
import { processMessage } from "../daemon/process-message.js";
|
|
8
13
|
import { emitFeedEvent } from "../home/emit-feed-event.js";
|
|
9
14
|
import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
|
|
10
|
-
import { getConversation } from "../memory/conversation-crud.js";
|
|
15
|
+
import { getConversation, getMessages } from "../memory/conversation-crud.js";
|
|
11
16
|
import { GENERATING_TITLE } from "../memory/conversation-title-service.js";
|
|
12
17
|
import {
|
|
13
18
|
GUARDIAN_PERSONA_TEMPLATE,
|
|
@@ -21,6 +26,7 @@ import { getWorkspaceDir, getWorkspacePromptPath } from "../util/platform.js";
|
|
|
21
26
|
import { stripCommentLines } from "../util/strip-comment-lines.js";
|
|
22
27
|
import {
|
|
23
28
|
completeHeartbeatRun,
|
|
29
|
+
countCompletedHeartbeatRuns,
|
|
24
30
|
insertPendingHeartbeatRun,
|
|
25
31
|
markStaleRunningAsError,
|
|
26
32
|
markStaleRunsAsMissed,
|
|
@@ -38,8 +44,12 @@ const DEFAULT_CHECKLIST = `- Check in with yourself. Read NOW.md. Is it still ac
|
|
|
38
44
|
- If you have a thought worth sharing, send it. A follow-up, a useful find, a check-in. Not every beat, but when it feels right.
|
|
39
45
|
- If something has happened since your last journal entry, write one. Even a few sentences. The journal is how future-you stays connected.`;
|
|
40
46
|
|
|
47
|
+
const EARLY_HEARTBEAT_THRESHOLD = 3;
|
|
41
48
|
const REENGAGEMENT_COOLDOWN_MS = 18 * 60 * 60 * 1000; // 18 hours
|
|
42
49
|
const HEARTBEAT_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
50
|
+
const HEARTBEAT_ALERT_MARKER = "HEARTBEAT_ALERT";
|
|
51
|
+
const HEARTBEAT_OK_MARKER = "HEARTBEAT_OK";
|
|
52
|
+
const HEARTBEAT_ALERT_SUMMARY_MAX_CHARS = 700;
|
|
43
53
|
|
|
44
54
|
// Stripped-comment form of the guardian persona scaffold. Computed
|
|
45
55
|
// once at module load because stripping comment lines is deterministic
|
|
@@ -92,6 +102,69 @@ function recordReengagementTimestamp(): void {
|
|
|
92
102
|
}
|
|
93
103
|
}
|
|
94
104
|
|
|
105
|
+
type HeartbeatDisposition = "alert" | "ok" | "unknown";
|
|
106
|
+
|
|
107
|
+
function parseHeartbeatDisposition(text: string | null): HeartbeatDisposition {
|
|
108
|
+
if (!text) return "unknown";
|
|
109
|
+
const lines = text
|
|
110
|
+
.trim()
|
|
111
|
+
.split(/\r?\n/)
|
|
112
|
+
.map((line) => line.trim())
|
|
113
|
+
.filter((line) => line.length > 0);
|
|
114
|
+
const lastLine = lines.at(-1);
|
|
115
|
+
if (lastLine === HEARTBEAT_ALERT_MARKER) return "alert";
|
|
116
|
+
if (lastLine === HEARTBEAT_OK_MARKER) return "ok";
|
|
117
|
+
return "unknown";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function stripHeartbeatDispositionMarkers(text: string): string {
|
|
121
|
+
return text
|
|
122
|
+
.replace(
|
|
123
|
+
new RegExp(
|
|
124
|
+
`(?:\\r?\\n)?\\s*(?:${HEARTBEAT_ALERT_MARKER}|${HEARTBEAT_OK_MARKER})\\s*$`,
|
|
125
|
+
),
|
|
126
|
+
"",
|
|
127
|
+
)
|
|
128
|
+
.trim();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function truncateSummary(text: string, maxChars: number): string {
|
|
132
|
+
if (text.length <= maxChars) return text;
|
|
133
|
+
return `${text.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function buildHeartbeatAlertSummary(text: string | null): string {
|
|
137
|
+
const summary = text ? stripHeartbeatDispositionMarkers(text) : "";
|
|
138
|
+
return truncateSummary(
|
|
139
|
+
summary || "Your assistant found something worth your attention.",
|
|
140
|
+
HEARTBEAT_ALERT_SUMMARY_MAX_CHARS,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function extractVisibleTextFromStoredMessageContent(raw: string): string {
|
|
145
|
+
try {
|
|
146
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
147
|
+
if (typeof parsed === "string") return parsed;
|
|
148
|
+
if (!Array.isArray(parsed)) return "";
|
|
149
|
+
const texts: string[] = [];
|
|
150
|
+
for (const block of parsed) {
|
|
151
|
+
if (
|
|
152
|
+
block != null &&
|
|
153
|
+
typeof block === "object" &&
|
|
154
|
+
"type" in block &&
|
|
155
|
+
block.type === "text" &&
|
|
156
|
+
"text" in block &&
|
|
157
|
+
typeof block.text === "string"
|
|
158
|
+
) {
|
|
159
|
+
texts.push(block.text);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return texts.join("\n").trim();
|
|
163
|
+
} catch {
|
|
164
|
+
return raw;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
95
168
|
export interface HeartbeatDeps {
|
|
96
169
|
alerter: (alert: HeartbeatAlert) => void;
|
|
97
170
|
onConversationCreated?: (info: {
|
|
@@ -168,18 +241,22 @@ export class HeartbeatService {
|
|
|
168
241
|
"Recovered stale heartbeat runs on startup",
|
|
169
242
|
);
|
|
170
243
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
244
|
+
if (!isDiskPressureBackgroundLocked("heartbeat-startup")) {
|
|
245
|
+
const total = this._startupMissedCount + this._startupCrashedCount;
|
|
246
|
+
const today = new Date().toISOString().split("T")[0];
|
|
247
|
+
void emitFeedEvent({
|
|
248
|
+
source: "assistant",
|
|
249
|
+
title: "Heartbeat Runs Missed",
|
|
250
|
+
summary: `${total} heartbeat run${
|
|
251
|
+
total > 1 ? "s were" : " was"
|
|
252
|
+
} missed while the assistant was offline.`,
|
|
253
|
+
dedupKey: `heartbeat:missed:${today}`,
|
|
254
|
+
priority: 55,
|
|
255
|
+
urgency: "high",
|
|
256
|
+
}).catch((err) => {
|
|
257
|
+
log.warn({ err }, "Failed to emit missed heartbeat feed event");
|
|
258
|
+
});
|
|
259
|
+
}
|
|
183
260
|
}
|
|
184
261
|
}
|
|
185
262
|
|
|
@@ -328,6 +405,10 @@ export class HeartbeatService {
|
|
|
328
405
|
async runOnce({ force = false }: { force?: boolean } = {}): Promise<boolean> {
|
|
329
406
|
const config = getConfig().heartbeat;
|
|
330
407
|
|
|
408
|
+
if (!force && isDiskPressureBackgroundLocked("heartbeat")) {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
|
|
331
412
|
let runId: string | null;
|
|
332
413
|
let scheduledFor: number;
|
|
333
414
|
if (force) {
|
|
@@ -579,6 +660,65 @@ export class HeartbeatService {
|
|
|
579
660
|
}
|
|
580
661
|
}
|
|
581
662
|
|
|
663
|
+
private getLatestAssistantMessage(
|
|
664
|
+
conversationId: string,
|
|
665
|
+
): { id: string; text: string } | null {
|
|
666
|
+
try {
|
|
667
|
+
const messages = getMessages(conversationId);
|
|
668
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
669
|
+
const message = messages[i]!;
|
|
670
|
+
if (message.role !== "assistant") continue;
|
|
671
|
+
return {
|
|
672
|
+
id: message.id,
|
|
673
|
+
text: extractVisibleTextFromStoredMessageContent(message.content),
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
} catch (err) {
|
|
677
|
+
log.warn(
|
|
678
|
+
{ err, conversationId },
|
|
679
|
+
"Failed to read heartbeat assistant message",
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
private async emitHeartbeatAlertNotification(params: {
|
|
686
|
+
runId: string;
|
|
687
|
+
conversationId: string;
|
|
688
|
+
messageId?: string;
|
|
689
|
+
conversationTitle: string;
|
|
690
|
+
summary: string;
|
|
691
|
+
}): Promise<void> {
|
|
692
|
+
const { emitNotificationSignal } =
|
|
693
|
+
await import("../notifications/emit-signal.js");
|
|
694
|
+
|
|
695
|
+
await emitNotificationSignal({
|
|
696
|
+
sourceEventName: "heartbeat.alert",
|
|
697
|
+
sourceChannel: "watcher",
|
|
698
|
+
sourceContextId: params.runId,
|
|
699
|
+
dedupeKey: `heartbeat:alert:${params.runId}`,
|
|
700
|
+
attentionHints: {
|
|
701
|
+
requiresAction: true,
|
|
702
|
+
urgency: "medium",
|
|
703
|
+
isAsyncBackground: true,
|
|
704
|
+
visibleInSourceNow: false,
|
|
705
|
+
},
|
|
706
|
+
contextPayload: {
|
|
707
|
+
title: "Heartbeat Alert",
|
|
708
|
+
summary: params.summary,
|
|
709
|
+
conversationTitle: params.conversationTitle,
|
|
710
|
+
conversationId: params.conversationId,
|
|
711
|
+
messageId: params.messageId,
|
|
712
|
+
},
|
|
713
|
+
routingIntent: "single_channel",
|
|
714
|
+
conversationAffinityHint: { vellum: params.conversationId },
|
|
715
|
+
conversationMetadata: {
|
|
716
|
+
source: "heartbeat",
|
|
717
|
+
groupId: "system:background",
|
|
718
|
+
},
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
582
722
|
private async executeRun(runId: string, scheduledFor: number): Promise<void> {
|
|
583
723
|
log.info("Running heartbeat");
|
|
584
724
|
|
|
@@ -595,9 +735,11 @@ export class HeartbeatService {
|
|
|
595
735
|
let conversationId: string | undefined;
|
|
596
736
|
try {
|
|
597
737
|
const checklist = this.readChecklist();
|
|
738
|
+
const completedRunCount = countCompletedHeartbeatRuns();
|
|
598
739
|
const { prompt, includedReengagement } = this.buildPrompt(
|
|
599
740
|
checklist,
|
|
600
741
|
unhealthyProviders,
|
|
742
|
+
completedRunCount,
|
|
601
743
|
);
|
|
602
744
|
|
|
603
745
|
const conversation = bootstrapConversation({
|
|
@@ -609,11 +751,6 @@ export class HeartbeatService {
|
|
|
609
751
|
});
|
|
610
752
|
conversationId = conversation.id;
|
|
611
753
|
|
|
612
|
-
this.deps.onConversationCreated?.({
|
|
613
|
-
conversationId: conversation.id,
|
|
614
|
-
title: "Heartbeat",
|
|
615
|
-
});
|
|
616
|
-
|
|
617
754
|
await processMessage(conversation.id, prompt, undefined, {
|
|
618
755
|
trustContext: {
|
|
619
756
|
sourceChannel: "vellum",
|
|
@@ -644,20 +781,32 @@ export class HeartbeatService {
|
|
|
644
781
|
// Best-effort; fall back to generic title.
|
|
645
782
|
}
|
|
646
783
|
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
784
|
+
const assistantMessage = this.getLatestAssistantMessage(
|
|
785
|
+
conversation.id,
|
|
786
|
+
);
|
|
787
|
+
const disposition = parseHeartbeatDisposition(
|
|
788
|
+
assistantMessage?.text ?? null,
|
|
789
|
+
);
|
|
790
|
+
if (disposition === "alert") {
|
|
791
|
+
this.deps.onConversationCreated?.({
|
|
792
|
+
conversationId: conversation.id,
|
|
793
|
+
title,
|
|
794
|
+
});
|
|
795
|
+
void this.emitHeartbeatAlertNotification({
|
|
796
|
+
runId,
|
|
797
|
+
conversationId: conversation.id,
|
|
798
|
+
messageId: assistantMessage?.id,
|
|
799
|
+
conversationTitle: title,
|
|
800
|
+
summary: buildHeartbeatAlertSummary(assistantMessage?.text ?? null),
|
|
801
|
+
}).catch((err) => {
|
|
802
|
+
log.warn(
|
|
803
|
+
{ err, conversationId: conversation.id },
|
|
804
|
+
"Failed to emit heartbeat alert notification",
|
|
805
|
+
);
|
|
806
|
+
});
|
|
807
|
+
}
|
|
660
808
|
|
|
809
|
+
const today = new Date().toISOString().split("T")[0];
|
|
661
810
|
if (latenessMs > LATE_THRESHOLD_MS) {
|
|
662
811
|
const lateMinutes = Math.round(latenessMs / 60_000);
|
|
663
812
|
void emitFeedEvent({
|
|
@@ -714,6 +863,7 @@ export class HeartbeatService {
|
|
|
714
863
|
buildPrompt(
|
|
715
864
|
checklist: string,
|
|
716
865
|
unhealthyProviders: string[] = [],
|
|
866
|
+
completedRunCount: number = Infinity,
|
|
717
867
|
): { prompt: string; includedReengagement: boolean } {
|
|
718
868
|
let prompt = `You are running a periodic heartbeat check. Review the following checklist and take any necessary actions.
|
|
719
869
|
|
|
@@ -730,11 +880,20 @@ Do NOT attempt to use tools for these providers — they will fail. Skip any che
|
|
|
730
880
|
}
|
|
731
881
|
|
|
732
882
|
prompt += `\n\n<heartbeat-disposition>
|
|
883
|
+
This heartbeat runs frequently. Do not manufacture a report just because it ran.
|
|
884
|
+
If there is nothing genuinely useful, actionable, or interesting to surface, keep the response brief and end with HEARTBEAT_OK.
|
|
885
|
+
If there is something worth interrupting the guardian for, write a concise guardian-facing note first: what happened, why it matters, and the recommended next step. Address the guardian directly as "you"; do not write instructions to yourself or another intermediary. Then end with HEARTBEAT_ALERT. That note may be used as notification copy.
|
|
733
886
|
After completing your review, end your response with one of:
|
|
734
887
|
- HEARTBEAT_OK — if everything looks good, no action needed
|
|
735
888
|
- HEARTBEAT_ALERT — if you found issues that need attention (describe them before this marker)
|
|
736
889
|
</heartbeat-disposition>`;
|
|
737
890
|
|
|
891
|
+
if (completedRunCount < EARLY_HEARTBEAT_THRESHOLD) {
|
|
892
|
+
prompt += `\n\n<early-heartbeat>
|
|
893
|
+
This is one of your first heartbeats. Your user hasn't heard from you yet and may not know you're here. Find something genuinely useful to share — a follow-up from a recent conversation, something you noticed, or a quick check-in. Lean toward HEARTBEAT_ALERT this time. First impressions matter.
|
|
894
|
+
</early-heartbeat>`;
|
|
895
|
+
}
|
|
896
|
+
|
|
738
897
|
let includedReengagement = false;
|
|
739
898
|
if (isShallowProfile() && isReengagementCooldownElapsed()) {
|
|
740
899
|
includedReengagement = true;
|
|
@@ -745,6 +904,21 @@ After completing your review, end your response with one of:
|
|
|
745
904
|
}
|
|
746
905
|
}
|
|
747
906
|
|
|
907
|
+
function isDiskPressureBackgroundLocked(logKey: string): boolean {
|
|
908
|
+
const diskPressureGate = checkDiskPressureBackgroundGate("background-work");
|
|
909
|
+
if (diskPressureGate.action === "allow") return false;
|
|
910
|
+
if (shouldLogDiskPressureBackgroundSkip(logKey)) {
|
|
911
|
+
log.warn(
|
|
912
|
+
{
|
|
913
|
+
source: "heartbeat",
|
|
914
|
+
...diskPressureBackgroundSkipLogFields(diskPressureGate),
|
|
915
|
+
},
|
|
916
|
+
"Heartbeat skipped during disk pressure cleanup mode",
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
return true;
|
|
920
|
+
}
|
|
921
|
+
|
|
748
922
|
/**
|
|
749
923
|
* Check if the given hour falls within the active window.
|
|
750
924
|
* Handles overnight windows (e.g. start=22, end=6).
|
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
* scheduler needing to touch the event hub.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
+
import {
|
|
32
|
+
checkDiskPressureBackgroundGate,
|
|
33
|
+
diskPressureBackgroundSkipLogFields,
|
|
34
|
+
shouldLogDiskPressureBackgroundSkip,
|
|
35
|
+
} from "../daemon/disk-pressure-background-gate.js";
|
|
31
36
|
import { getLogger } from "../util/logger.js";
|
|
32
37
|
import type { FeedItem } from "./feed-types.js";
|
|
33
38
|
import {
|
|
@@ -119,6 +124,19 @@ export function startFeedScheduler(
|
|
|
119
124
|
rollupRan: false,
|
|
120
125
|
};
|
|
121
126
|
if (stopped || tickRunning) return summary;
|
|
127
|
+
const diskPressureGate = checkDiskPressureBackgroundGate("background-work");
|
|
128
|
+
if (diskPressureGate.action === "skip") {
|
|
129
|
+
if (shouldLogDiskPressureBackgroundSkip("home-feed-scheduler")) {
|
|
130
|
+
log.warn(
|
|
131
|
+
{
|
|
132
|
+
source: "feed-scheduler",
|
|
133
|
+
...diskPressureBackgroundSkipLogFields(diskPressureGate),
|
|
134
|
+
},
|
|
135
|
+
"Home feed scheduler skipped during disk pressure cleanup mode",
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return summary;
|
|
139
|
+
}
|
|
122
140
|
tickRunning = true;
|
|
123
141
|
const nowMs = now.getTime();
|
|
124
142
|
try {
|
|
@@ -16,11 +16,7 @@
|
|
|
16
16
|
* callback_url that external services should use.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
20
|
-
getPlatformAssistantId,
|
|
21
|
-
getPlatformBaseUrl,
|
|
22
|
-
getPlatformInternalApiKey,
|
|
23
|
-
} from "../config/env.js";
|
|
19
|
+
import { getPlatformAssistantId, getPlatformBaseUrl } from "../config/env.js";
|
|
24
20
|
import { getIsPlatform } from "../config/env-registry.js";
|
|
25
21
|
import { credentialKey } from "../security/credential-key.js";
|
|
26
22
|
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
@@ -32,7 +28,6 @@ export interface PlatformCallbackRegistrationContext {
|
|
|
32
28
|
isPlatform: boolean;
|
|
33
29
|
platformBaseUrl: string;
|
|
34
30
|
assistantId: string;
|
|
35
|
-
hasInternalApiKey: boolean;
|
|
36
31
|
hasAssistantApiKey: boolean;
|
|
37
32
|
authHeader: string | null;
|
|
38
33
|
enabled: boolean;
|
|
@@ -54,20 +49,18 @@ export async function resolvePlatformCallbackRegistrationContext(): Promise<Plat
|
|
|
54
49
|
);
|
|
55
50
|
const assistantId =
|
|
56
51
|
getPlatformAssistantId().trim() || storedAssistantIdRaw?.trim() || "";
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
: null;
|
|
52
|
+
const envAssistantCredential = process.env.ASSISTANT_API_KEY?.trim();
|
|
53
|
+
const assistantCredential =
|
|
54
|
+
storedAssistantApiKeyRaw?.trim() || envAssistantCredential || undefined;
|
|
55
|
+
const authHeader = assistantCredential
|
|
56
|
+
? `Api-Key ${assistantCredential}`
|
|
57
|
+
: null;
|
|
64
58
|
|
|
65
59
|
return {
|
|
66
60
|
isPlatform: platform,
|
|
67
61
|
platformBaseUrl,
|
|
68
62
|
assistantId,
|
|
69
|
-
|
|
70
|
-
hasAssistantApiKey: assistantApiKey.length > 0,
|
|
63
|
+
hasAssistantApiKey: !!assistantCredential,
|
|
71
64
|
authHeader,
|
|
72
65
|
// Enabled when we have enough context to register callback routes.
|
|
73
66
|
// Does NOT require IS_PLATFORM — self-hosted assistants with stored
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end test for `assistant clients list` over IPC.
|
|
3
|
+
*
|
|
4
|
+
* Regression test for the gap where the same-user filter on
|
|
5
|
+
* `GET /v1/clients` (which reads `headers["x-vellum-actor-principal-id"]`)
|
|
6
|
+
* silently returned an empty list over IPC because the IPC adapter did
|
|
7
|
+
* not inject the synthetic actor-principal header that the HTTP adapter
|
|
8
|
+
* populates from the verified `AuthContext`.
|
|
9
|
+
*
|
|
10
|
+
* Asserts that in non-dev-bypass mode (`isHttpAuthDisabled() === false`),
|
|
11
|
+
* the CLI sees same-user clients via the IPC path because the IPC server
|
|
12
|
+
* fills in the header from the local guardian principal.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
afterAll,
|
|
17
|
+
afterEach,
|
|
18
|
+
beforeEach,
|
|
19
|
+
describe,
|
|
20
|
+
expect,
|
|
21
|
+
mock,
|
|
22
|
+
test,
|
|
23
|
+
} from "bun:test";
|
|
24
|
+
|
|
25
|
+
import { runAssistantCommandFull } from "../../cli/__tests__/run-assistant-command.js";
|
|
26
|
+
import { AssistantIpcServer } from "../assistant-server.js";
|
|
27
|
+
|
|
28
|
+
// ── Module mocks (must be set up before importing the route) ──────────────
|
|
29
|
+
|
|
30
|
+
let fakeHttpAuthDisabled = false;
|
|
31
|
+
let fakeLocalPrincipalId: string | undefined = "guardian-local";
|
|
32
|
+
|
|
33
|
+
mock.module("../../config/env.js", () => ({
|
|
34
|
+
isHttpAuthDisabled: () => fakeHttpAuthDisabled,
|
|
35
|
+
hasUngatedHttpAuthDisabled: () => false,
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
mock.module("../../runtime/local-actor-identity.js", () => ({
|
|
39
|
+
findLocalGuardianPrincipalId: () => fakeLocalPrincipalId,
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
// ── Real imports (after mocks) ────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
|
|
45
|
+
|
|
46
|
+
// ── Fixtures ──────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
let server: AssistantIpcServer | null = null;
|
|
49
|
+
|
|
50
|
+
function registerClient(args: {
|
|
51
|
+
clientId: string;
|
|
52
|
+
actorPrincipalId?: string;
|
|
53
|
+
}): void {
|
|
54
|
+
assistantEventHub.subscribe({
|
|
55
|
+
type: "client",
|
|
56
|
+
clientId: args.clientId,
|
|
57
|
+
interfaceId: "macos",
|
|
58
|
+
capabilities: ["host_bash", "host_file", "host_cu"],
|
|
59
|
+
actorPrincipalId: args.actorPrincipalId,
|
|
60
|
+
callback: () => {},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function clearHub(): void {
|
|
65
|
+
const ids = assistantEventHub.listClients().map((c) => c.clientId);
|
|
66
|
+
for (const id of ids) {
|
|
67
|
+
assistantEventHub.disposeClient(id);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function startServer(): Promise<void> {
|
|
72
|
+
server = new AssistantIpcServer();
|
|
73
|
+
await server.start();
|
|
74
|
+
// Allow the listener to be ready before the CLI tries to connect.
|
|
75
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
fakeHttpAuthDisabled = false;
|
|
80
|
+
fakeLocalPrincipalId = "guardian-local";
|
|
81
|
+
clearHub();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
afterEach(() => {
|
|
85
|
+
server?.stop();
|
|
86
|
+
server = null;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
afterAll(() => {
|
|
90
|
+
mock.restore();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ── Tests ────────────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
describe("assistant clients list over IPC — same-user filter", () => {
|
|
96
|
+
test("returns same-user clients in non-dev-bypass mode", async () => {
|
|
97
|
+
registerClient({
|
|
98
|
+
clientId: "client-self-1",
|
|
99
|
+
actorPrincipalId: "guardian-local",
|
|
100
|
+
});
|
|
101
|
+
registerClient({
|
|
102
|
+
clientId: "client-self-2",
|
|
103
|
+
actorPrincipalId: "guardian-local",
|
|
104
|
+
});
|
|
105
|
+
registerClient({
|
|
106
|
+
clientId: "client-other",
|
|
107
|
+
actorPrincipalId: "other-user",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await startServer();
|
|
111
|
+
|
|
112
|
+
const { stdout } = await runAssistantCommandFull(
|
|
113
|
+
"clients",
|
|
114
|
+
"list",
|
|
115
|
+
"--json",
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const parsed = JSON.parse(stdout.trim()) as {
|
|
119
|
+
clients: Array<{ clientId: string }>;
|
|
120
|
+
};
|
|
121
|
+
const ids = parsed.clients.map((c) => c.clientId).sort();
|
|
122
|
+
expect(ids).toEqual(["client-self-1", "client-self-2"]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("returns empty when no local guardian principal is bound (fail-closed)", async () => {
|
|
126
|
+
fakeLocalPrincipalId = undefined;
|
|
127
|
+
registerClient({
|
|
128
|
+
clientId: "client-self",
|
|
129
|
+
actorPrincipalId: "guardian-local",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await startServer();
|
|
133
|
+
|
|
134
|
+
const { stdout } = await runAssistantCommandFull(
|
|
135
|
+
"clients",
|
|
136
|
+
"list",
|
|
137
|
+
"--json",
|
|
138
|
+
);
|
|
139
|
+
const parsed = JSON.parse(stdout.trim()) as {
|
|
140
|
+
clients: Array<{ clientId: string }>;
|
|
141
|
+
};
|
|
142
|
+
expect(parsed.clients).toEqual([]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("dev-bypass mode returns all clients regardless of principal", async () => {
|
|
146
|
+
fakeHttpAuthDisabled = true;
|
|
147
|
+
registerClient({
|
|
148
|
+
clientId: "client-self",
|
|
149
|
+
actorPrincipalId: "guardian-local",
|
|
150
|
+
});
|
|
151
|
+
registerClient({
|
|
152
|
+
clientId: "client-other",
|
|
153
|
+
actorPrincipalId: "other-user",
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await startServer();
|
|
157
|
+
|
|
158
|
+
const { stdout } = await runAssistantCommandFull(
|
|
159
|
+
"clients",
|
|
160
|
+
"list",
|
|
161
|
+
"--json",
|
|
162
|
+
);
|
|
163
|
+
const parsed = JSON.parse(stdout.trim()) as {
|
|
164
|
+
clients: Array<{ clientId: string }>;
|
|
165
|
+
};
|
|
166
|
+
const ids = parsed.clients.map((c) => c.clientId).sort();
|
|
167
|
+
expect(ids).toEqual(["client-other", "client-self"]);
|
|
168
|
+
});
|
|
169
|
+
});
|