@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
|
@@ -47,11 +47,24 @@ mock.module("../daemon/host-transfer-proxy.js", () => ({
|
|
|
47
47
|
getTargetClientIdForTransfer(_transferId: string) {
|
|
48
48
|
return stubTargetClientId;
|
|
49
49
|
},
|
|
50
|
+
getTargetActorPrincipalIdForTransfer(_transferId: string) {
|
|
51
|
+
return stubTargetClientId
|
|
52
|
+
? clientActors.get(stubTargetClientId)
|
|
53
|
+
: undefined;
|
|
54
|
+
},
|
|
50
55
|
getTransferContent(transferId: string) {
|
|
51
56
|
getTransferContentCalls.push(transferId);
|
|
52
|
-
return {
|
|
57
|
+
return {
|
|
58
|
+
buffer: Buffer.from("data"),
|
|
59
|
+
sizeBytes: 4,
|
|
60
|
+
sha256: "abc123",
|
|
61
|
+
};
|
|
53
62
|
},
|
|
54
|
-
async receiveTransferContent(
|
|
63
|
+
async receiveTransferContent(
|
|
64
|
+
transferId: string,
|
|
65
|
+
_data: Buffer,
|
|
66
|
+
_sha256: string,
|
|
67
|
+
) {
|
|
55
68
|
receiveTransferContentCalls.push(transferId);
|
|
56
69
|
return { accepted: true };
|
|
57
70
|
},
|
|
@@ -63,12 +76,20 @@ mock.module("../daemon/host-transfer-proxy.js", () => ({
|
|
|
63
76
|
},
|
|
64
77
|
}));
|
|
65
78
|
|
|
79
|
+
// Stub event hub so tests control what actorPrincipalId is associated with
|
|
80
|
+
// each connected client.
|
|
81
|
+
const clientActors = new Map<string, string>();
|
|
82
|
+
|
|
83
|
+
mock.module("../runtime/assistant-event-hub.js", () => ({
|
|
84
|
+
assistantEventHub: {
|
|
85
|
+
getActorPrincipalIdForClient: (clientId: string) =>
|
|
86
|
+
clientActors.get(clientId),
|
|
87
|
+
},
|
|
88
|
+
}));
|
|
89
|
+
|
|
66
90
|
// ── Real imports (after mocks) ──────────────────────────────────────────────
|
|
67
91
|
|
|
68
|
-
import {
|
|
69
|
-
BadRequestError,
|
|
70
|
-
ForbiddenError,
|
|
71
|
-
} from "../runtime/routes/errors.js";
|
|
92
|
+
import { BadRequestError, ForbiddenError } from "../runtime/routes/errors.js";
|
|
72
93
|
import { ROUTES } from "../runtime/routes/host-transfer-routes.js";
|
|
73
94
|
|
|
74
95
|
afterAll(() => {
|
|
@@ -93,10 +114,19 @@ const TEST_TRANSFER_ID = "transfer-abc";
|
|
|
93
114
|
const TEST_REQUEST_ID = "req-1";
|
|
94
115
|
|
|
95
116
|
function registerPending(overrides: Partial<PendingInteraction> = {}): void {
|
|
117
|
+
// Mirror the production proxy: capture the target's actor principal at
|
|
118
|
+
// registration time so the result-route check compares against a stable
|
|
119
|
+
// value rather than the live hub.
|
|
120
|
+
const targetActorPrincipalId =
|
|
121
|
+
overrides.targetActorPrincipalId ??
|
|
122
|
+
(overrides.targetClientId
|
|
123
|
+
? clientActors.get(overrides.targetClientId)
|
|
124
|
+
: undefined);
|
|
96
125
|
pendingStore.set(TEST_REQUEST_ID, {
|
|
97
126
|
conversationId: "conv-1",
|
|
98
127
|
kind: "host_transfer",
|
|
99
128
|
...overrides,
|
|
129
|
+
targetActorPrincipalId,
|
|
100
130
|
});
|
|
101
131
|
}
|
|
102
132
|
|
|
@@ -107,6 +137,7 @@ describe("handleTransferContentGet — Phase 3 targetClientId guard", () => {
|
|
|
107
137
|
pendingStore.clear();
|
|
108
138
|
stubTargetClientId = null;
|
|
109
139
|
getTransferContentCalls.length = 0;
|
|
140
|
+
clientActors.clear();
|
|
110
141
|
});
|
|
111
142
|
|
|
112
143
|
// ── 1. Targeted + correct header → success ────────────────────────────────
|
|
@@ -114,9 +145,13 @@ describe("handleTransferContentGet — Phase 3 targetClientId guard", () => {
|
|
|
114
145
|
describe("targeted + correct x-vellum-client-id header", () => {
|
|
115
146
|
test("returns Uint8Array and calls getTransferContent", async () => {
|
|
116
147
|
stubTargetClientId = "client-A";
|
|
148
|
+
clientActors.set("client-A", "actor-1");
|
|
117
149
|
const result = await handleTransferContentGet({
|
|
118
150
|
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
119
|
-
headers: {
|
|
151
|
+
headers: {
|
|
152
|
+
"x-vellum-client-id": "client-A",
|
|
153
|
+
"x-vellum-actor-principal-id": "actor-1",
|
|
154
|
+
},
|
|
120
155
|
});
|
|
121
156
|
|
|
122
157
|
expect(result).toBeInstanceOf(Uint8Array);
|
|
@@ -125,9 +160,13 @@ describe("handleTransferContentGet — Phase 3 targetClientId guard", () => {
|
|
|
125
160
|
|
|
126
161
|
test("trims whitespace from header before comparing", async () => {
|
|
127
162
|
stubTargetClientId = "client-A";
|
|
163
|
+
clientActors.set("client-A", "actor-1");
|
|
128
164
|
const result = await handleTransferContentGet({
|
|
129
165
|
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
130
|
-
headers: {
|
|
166
|
+
headers: {
|
|
167
|
+
"x-vellum-client-id": " client-A ",
|
|
168
|
+
"x-vellum-actor-principal-id": " actor-1 ",
|
|
169
|
+
},
|
|
131
170
|
});
|
|
132
171
|
|
|
133
172
|
expect(result).toBeInstanceOf(Uint8Array);
|
|
@@ -199,6 +238,52 @@ describe("handleTransferContentGet — Phase 3 targetClientId guard", () => {
|
|
|
199
238
|
expect(getTransferContentCalls).toContain(TEST_TRANSFER_ID);
|
|
200
239
|
});
|
|
201
240
|
});
|
|
241
|
+
|
|
242
|
+
// ── 5. Same-user actor binding (defense-in-depth) ─────────────────────────
|
|
243
|
+
|
|
244
|
+
describe("targeted + actor principal binding", () => {
|
|
245
|
+
test("throws ForbiddenError when submitting actor does not match target client's actor", () => {
|
|
246
|
+
stubTargetClientId = "client-A";
|
|
247
|
+
clientActors.set("client-A", "actor-victim");
|
|
248
|
+
expect(() =>
|
|
249
|
+
handleTransferContentGet({
|
|
250
|
+
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
251
|
+
headers: {
|
|
252
|
+
"x-vellum-client-id": "client-A",
|
|
253
|
+
"x-vellum-actor-principal-id": "actor-attacker",
|
|
254
|
+
},
|
|
255
|
+
}),
|
|
256
|
+
).toThrow(ForbiddenError);
|
|
257
|
+
expect(getTransferContentCalls).toHaveLength(0);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("throws ForbiddenError when actor principal header is missing", () => {
|
|
261
|
+
stubTargetClientId = "client-A";
|
|
262
|
+
clientActors.set("client-A", "actor-victim");
|
|
263
|
+
expect(() =>
|
|
264
|
+
handleTransferContentGet({
|
|
265
|
+
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
266
|
+
headers: { "x-vellum-client-id": "client-A" },
|
|
267
|
+
}),
|
|
268
|
+
).toThrow(ForbiddenError);
|
|
269
|
+
expect(getTransferContentCalls).toHaveLength(0);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("throws ForbiddenError when target client has no stored actor", () => {
|
|
273
|
+
stubTargetClientId = "client-A";
|
|
274
|
+
// No entry in clientActors for "client-A"
|
|
275
|
+
expect(() =>
|
|
276
|
+
handleTransferContentGet({
|
|
277
|
+
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
278
|
+
headers: {
|
|
279
|
+
"x-vellum-client-id": "client-A",
|
|
280
|
+
"x-vellum-actor-principal-id": "actor-1",
|
|
281
|
+
},
|
|
282
|
+
}),
|
|
283
|
+
).toThrow(ForbiddenError);
|
|
284
|
+
expect(getTransferContentCalls).toHaveLength(0);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
202
287
|
});
|
|
203
288
|
|
|
204
289
|
describe("handleTransferContentPut — Phase 3 targetClientId guard", () => {
|
|
@@ -206,6 +291,7 @@ describe("handleTransferContentPut — Phase 3 targetClientId guard", () => {
|
|
|
206
291
|
pendingStore.clear();
|
|
207
292
|
stubTargetClientId = null;
|
|
208
293
|
receiveTransferContentCalls.length = 0;
|
|
294
|
+
clientActors.clear();
|
|
209
295
|
});
|
|
210
296
|
|
|
211
297
|
// ── 1. Targeted + correct header → success ────────────────────────────────
|
|
@@ -213,9 +299,14 @@ describe("handleTransferContentPut — Phase 3 targetClientId guard", () => {
|
|
|
213
299
|
describe("targeted + correct x-vellum-client-id header", () => {
|
|
214
300
|
test("returns { accepted: true } and calls receiveTransferContent", async () => {
|
|
215
301
|
stubTargetClientId = "client-A";
|
|
302
|
+
clientActors.set("client-A", "actor-1");
|
|
216
303
|
const result = await handleTransferContentPut({
|
|
217
304
|
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
218
|
-
headers: {
|
|
305
|
+
headers: {
|
|
306
|
+
"x-vellum-client-id": "client-A",
|
|
307
|
+
"x-vellum-actor-principal-id": "actor-1",
|
|
308
|
+
"x-transfer-sha256": "abc",
|
|
309
|
+
},
|
|
219
310
|
rawBody: new Uint8Array(Buffer.from("data")),
|
|
220
311
|
});
|
|
221
312
|
|
|
@@ -225,9 +316,14 @@ describe("handleTransferContentPut — Phase 3 targetClientId guard", () => {
|
|
|
225
316
|
|
|
226
317
|
test("trims whitespace from header before comparing", async () => {
|
|
227
318
|
stubTargetClientId = "client-A";
|
|
319
|
+
clientActors.set("client-A", "actor-1");
|
|
228
320
|
const result = await handleTransferContentPut({
|
|
229
321
|
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
230
|
-
headers: {
|
|
322
|
+
headers: {
|
|
323
|
+
"x-vellum-client-id": " client-A ",
|
|
324
|
+
"x-vellum-actor-principal-id": " actor-1 ",
|
|
325
|
+
"x-transfer-sha256": "abc",
|
|
326
|
+
},
|
|
231
327
|
rawBody: new Uint8Array(Buffer.from("data")),
|
|
232
328
|
});
|
|
233
329
|
|
|
@@ -272,7 +368,10 @@ describe("handleTransferContentPut — Phase 3 targetClientId guard", () => {
|
|
|
272
368
|
await expect(
|
|
273
369
|
handleTransferContentPut({
|
|
274
370
|
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
275
|
-
headers: {
|
|
371
|
+
headers: {
|
|
372
|
+
"x-vellum-client-id": "client-B",
|
|
373
|
+
"x-transfer-sha256": "abc",
|
|
374
|
+
},
|
|
276
375
|
rawBody: new Uint8Array(Buffer.from("data")),
|
|
277
376
|
}),
|
|
278
377
|
).rejects.toBeInstanceOf(ForbiddenError);
|
|
@@ -283,7 +382,10 @@ describe("handleTransferContentPut — Phase 3 targetClientId guard", () => {
|
|
|
283
382
|
try {
|
|
284
383
|
await handleTransferContentPut({
|
|
285
384
|
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
286
|
-
headers: {
|
|
385
|
+
headers: {
|
|
386
|
+
"x-vellum-client-id": "client-B",
|
|
387
|
+
"x-transfer-sha256": "abc",
|
|
388
|
+
},
|
|
287
389
|
rawBody: new Uint8Array(Buffer.from("data")),
|
|
288
390
|
});
|
|
289
391
|
} catch {
|
|
@@ -308,6 +410,60 @@ describe("handleTransferContentPut — Phase 3 targetClientId guard", () => {
|
|
|
308
410
|
expect(receiveTransferContentCalls).toContain(TEST_TRANSFER_ID);
|
|
309
411
|
});
|
|
310
412
|
});
|
|
413
|
+
|
|
414
|
+
// ── 5. Same-user actor binding (defense-in-depth) ─────────────────────────
|
|
415
|
+
|
|
416
|
+
describe("targeted + actor principal binding", () => {
|
|
417
|
+
test("rejects when submitting actor does not match target client's actor", async () => {
|
|
418
|
+
stubTargetClientId = "client-A";
|
|
419
|
+
clientActors.set("client-A", "actor-victim");
|
|
420
|
+
await expect(
|
|
421
|
+
handleTransferContentPut({
|
|
422
|
+
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
423
|
+
headers: {
|
|
424
|
+
"x-vellum-client-id": "client-A",
|
|
425
|
+
"x-vellum-actor-principal-id": "actor-attacker",
|
|
426
|
+
"x-transfer-sha256": "abc",
|
|
427
|
+
},
|
|
428
|
+
rawBody: new Uint8Array(Buffer.from("data")),
|
|
429
|
+
}),
|
|
430
|
+
).rejects.toBeInstanceOf(ForbiddenError);
|
|
431
|
+
expect(receiveTransferContentCalls).toHaveLength(0);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test("rejects when actor principal header is missing", async () => {
|
|
435
|
+
stubTargetClientId = "client-A";
|
|
436
|
+
clientActors.set("client-A", "actor-victim");
|
|
437
|
+
await expect(
|
|
438
|
+
handleTransferContentPut({
|
|
439
|
+
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
440
|
+
headers: {
|
|
441
|
+
"x-vellum-client-id": "client-A",
|
|
442
|
+
"x-transfer-sha256": "abc",
|
|
443
|
+
},
|
|
444
|
+
rawBody: new Uint8Array(Buffer.from("data")),
|
|
445
|
+
}),
|
|
446
|
+
).rejects.toBeInstanceOf(ForbiddenError);
|
|
447
|
+
expect(receiveTransferContentCalls).toHaveLength(0);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test("rejects when target client has no stored actor", async () => {
|
|
451
|
+
stubTargetClientId = "client-A";
|
|
452
|
+
// No entry in clientActors for "client-A"
|
|
453
|
+
await expect(
|
|
454
|
+
handleTransferContentPut({
|
|
455
|
+
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
456
|
+
headers: {
|
|
457
|
+
"x-vellum-client-id": "client-A",
|
|
458
|
+
"x-vellum-actor-principal-id": "actor-1",
|
|
459
|
+
"x-transfer-sha256": "abc",
|
|
460
|
+
},
|
|
461
|
+
rawBody: new Uint8Array(Buffer.from("data")),
|
|
462
|
+
}),
|
|
463
|
+
).rejects.toBeInstanceOf(ForbiddenError);
|
|
464
|
+
expect(receiveTransferContentCalls).toHaveLength(0);
|
|
465
|
+
});
|
|
466
|
+
});
|
|
311
467
|
});
|
|
312
468
|
|
|
313
469
|
describe("handleTransferResult — Phase 3 targetClientId guard", () => {
|
|
@@ -315,6 +471,7 @@ describe("handleTransferResult — Phase 3 targetClientId guard", () => {
|
|
|
315
471
|
pendingStore.clear();
|
|
316
472
|
stubTargetClientId = null;
|
|
317
473
|
resolveTransferResultCalls.length = 0;
|
|
474
|
+
clientActors.clear();
|
|
318
475
|
});
|
|
319
476
|
|
|
320
477
|
function registerHostTransferPending(targetClientId?: string): void {
|
|
@@ -329,10 +486,14 @@ describe("handleTransferResult — Phase 3 targetClientId guard", () => {
|
|
|
329
486
|
|
|
330
487
|
describe("targeted + correct x-vellum-client-id header", () => {
|
|
331
488
|
test("returns { accepted: true } and calls resolveTransferResult", async () => {
|
|
489
|
+
clientActors.set("client-A", "actor-1");
|
|
332
490
|
registerHostTransferPending("client-A");
|
|
333
491
|
const result = await handleTransferResult({
|
|
334
492
|
body: resultBody(),
|
|
335
|
-
headers: {
|
|
493
|
+
headers: {
|
|
494
|
+
"x-vellum-client-id": "client-A",
|
|
495
|
+
"x-vellum-actor-principal-id": "actor-1",
|
|
496
|
+
},
|
|
336
497
|
});
|
|
337
498
|
|
|
338
499
|
expect(result).toEqual({ accepted: true });
|
|
@@ -340,10 +501,14 @@ describe("handleTransferResult — Phase 3 targetClientId guard", () => {
|
|
|
340
501
|
});
|
|
341
502
|
|
|
342
503
|
test("trims whitespace from header before comparing", async () => {
|
|
504
|
+
clientActors.set("client-A", "actor-1");
|
|
343
505
|
registerHostTransferPending("client-A");
|
|
344
506
|
const result = await handleTransferResult({
|
|
345
507
|
body: resultBody(),
|
|
346
|
-
headers: {
|
|
508
|
+
headers: {
|
|
509
|
+
"x-vellum-client-id": " client-A ",
|
|
510
|
+
"x-vellum-actor-principal-id": " actor-1 ",
|
|
511
|
+
},
|
|
347
512
|
});
|
|
348
513
|
|
|
349
514
|
expect(result).toEqual({ accepted: true });
|
|
@@ -355,9 +520,9 @@ describe("handleTransferResult — Phase 3 targetClientId guard", () => {
|
|
|
355
520
|
describe("targeted + missing x-vellum-client-id header", () => {
|
|
356
521
|
test("throws BadRequestError when header is absent", () => {
|
|
357
522
|
registerHostTransferPending("client-A");
|
|
358
|
-
expect(() =>
|
|
359
|
-
|
|
360
|
-
)
|
|
523
|
+
expect(() => handleTransferResult({ body: resultBody() })).toThrow(
|
|
524
|
+
BadRequestError,
|
|
525
|
+
);
|
|
361
526
|
});
|
|
362
527
|
|
|
363
528
|
test("resolveTransferResult NOT called on 400", () => {
|
|
@@ -444,4 +609,54 @@ describe("handleTransferResult — Phase 3 targetClientId guard", () => {
|
|
|
444
609
|
expect(result).toEqual({ accepted: true });
|
|
445
610
|
});
|
|
446
611
|
});
|
|
612
|
+
|
|
613
|
+
// ── 5. Same-user actor binding (defense-in-depth) ─────────────────────────
|
|
614
|
+
|
|
615
|
+
describe("targeted + actor principal binding", () => {
|
|
616
|
+
test("throws ForbiddenError when submitting actor does not match target client's actor", () => {
|
|
617
|
+
registerHostTransferPending("client-A");
|
|
618
|
+
clientActors.set("client-A", "actor-victim");
|
|
619
|
+
expect(() =>
|
|
620
|
+
handleTransferResult({
|
|
621
|
+
body: resultBody(),
|
|
622
|
+
headers: {
|
|
623
|
+
"x-vellum-client-id": "client-A",
|
|
624
|
+
"x-vellum-actor-principal-id": "actor-attacker",
|
|
625
|
+
},
|
|
626
|
+
}),
|
|
627
|
+
).toThrow(ForbiddenError);
|
|
628
|
+
expect(resolveTransferResultCalls).toHaveLength(0);
|
|
629
|
+
// Pending interaction should still be present (not consumed on 403).
|
|
630
|
+
expect(pendingStore.has(TEST_REQUEST_ID)).toBe(true);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
test("throws ForbiddenError when actor principal header is missing", () => {
|
|
634
|
+
registerHostTransferPending("client-A");
|
|
635
|
+
clientActors.set("client-A", "actor-victim");
|
|
636
|
+
expect(() =>
|
|
637
|
+
handleTransferResult({
|
|
638
|
+
body: resultBody(),
|
|
639
|
+
headers: { "x-vellum-client-id": "client-A" },
|
|
640
|
+
}),
|
|
641
|
+
).toThrow(ForbiddenError);
|
|
642
|
+
expect(resolveTransferResultCalls).toHaveLength(0);
|
|
643
|
+
expect(pendingStore.has(TEST_REQUEST_ID)).toBe(true);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
test("throws ForbiddenError when target client has no stored actor", () => {
|
|
647
|
+
registerHostTransferPending("client-A");
|
|
648
|
+
// No entry in clientActors for "client-A"
|
|
649
|
+
expect(() =>
|
|
650
|
+
handleTransferResult({
|
|
651
|
+
body: resultBody(),
|
|
652
|
+
headers: {
|
|
653
|
+
"x-vellum-client-id": "client-A",
|
|
654
|
+
"x-vellum-actor-principal-id": "actor-1",
|
|
655
|
+
},
|
|
656
|
+
}),
|
|
657
|
+
).toThrow(ForbiddenError);
|
|
658
|
+
expect(resolveTransferResultCalls).toHaveLength(0);
|
|
659
|
+
expect(pendingStore.has(TEST_REQUEST_ID)).toBe(true);
|
|
660
|
+
});
|
|
661
|
+
});
|
|
447
662
|
});
|
|
@@ -252,12 +252,24 @@ async function sendMessage(
|
|
|
252
252
|
content: string,
|
|
253
253
|
conversationObj: import("../daemon/conversation.js").Conversation,
|
|
254
254
|
extra: Record<string, unknown> = {},
|
|
255
|
+
options: {
|
|
256
|
+
onGetOrCreateConversation?: (
|
|
257
|
+
conversationId: string,
|
|
258
|
+
opts?: Record<string, unknown>,
|
|
259
|
+
) => void;
|
|
260
|
+
} = {},
|
|
255
261
|
) {
|
|
256
262
|
return callHandler(
|
|
257
263
|
(args) =>
|
|
258
264
|
handleSendMessage(args, {
|
|
259
265
|
sendMessageDeps: {
|
|
260
|
-
getOrCreateConversation: async () =>
|
|
266
|
+
getOrCreateConversation: async (conversationId, opts) => {
|
|
267
|
+
options.onGetOrCreateConversation?.(
|
|
268
|
+
conversationId,
|
|
269
|
+
opts as Record<string, unknown> | undefined,
|
|
270
|
+
);
|
|
271
|
+
return conversationObj;
|
|
272
|
+
},
|
|
261
273
|
assistantEventHub: { publish: async () => {} } as any,
|
|
262
274
|
resolveAttachments: () => [],
|
|
263
275
|
},
|
|
@@ -326,3 +338,97 @@ describe("HTTP POST /v1/messages does not intercept recording intents (by design
|
|
|
326
338
|
expect(runAgentLoop).toHaveBeenCalledTimes(1);
|
|
327
339
|
});
|
|
328
340
|
});
|
|
341
|
+
|
|
342
|
+
// ============================================================================
|
|
343
|
+
// CLIENT TIMEZONE — optional HTTP metadata
|
|
344
|
+
// ============================================================================
|
|
345
|
+
describe("HTTP POST /v1/messages clientTimezone transport metadata", () => {
|
|
346
|
+
beforeEach(() => {
|
|
347
|
+
routeGuardianReplyMock.mockClear();
|
|
348
|
+
listPendingByDestinationMock.mockClear();
|
|
349
|
+
listCanonicalMock.mockClear();
|
|
350
|
+
addMessageMock.mockClear();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("passes canonical clientTimezone through host-proxy transport", async () => {
|
|
354
|
+
const persistUserMessage = mock(async () => "persisted-msg-id");
|
|
355
|
+
const runAgentLoop = mock(async () => undefined);
|
|
356
|
+
const conversation = makeConversation({ persistUserMessage, runAgentLoop });
|
|
357
|
+
let capturedOptions: Record<string, unknown> | undefined;
|
|
358
|
+
|
|
359
|
+
const res = await sendMessage(
|
|
360
|
+
"hello",
|
|
361
|
+
conversation,
|
|
362
|
+
{ clientTimezone: "america/new_york" },
|
|
363
|
+
{
|
|
364
|
+
onGetOrCreateConversation: (_conversationId, opts) => {
|
|
365
|
+
capturedOptions = opts;
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
expect(res.status).toBe(202);
|
|
371
|
+
expect(capturedOptions).toEqual({
|
|
372
|
+
transport: {
|
|
373
|
+
channelId: "vellum",
|
|
374
|
+
interfaceId: "macos",
|
|
375
|
+
clientTimezone: "America/New_York",
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("passes canonical clientTimezone through non-host-proxy transport", async () => {
|
|
381
|
+
const persistUserMessage = mock(async () => "persisted-msg-id");
|
|
382
|
+
const runAgentLoop = mock(async () => undefined);
|
|
383
|
+
const conversation = makeConversation({ persistUserMessage, runAgentLoop });
|
|
384
|
+
let capturedOptions: Record<string, unknown> | undefined;
|
|
385
|
+
|
|
386
|
+
const res = await sendMessage(
|
|
387
|
+
"hello",
|
|
388
|
+
conversation,
|
|
389
|
+
{ interface: "ios", clientTimezone: "europe/london" },
|
|
390
|
+
{
|
|
391
|
+
onGetOrCreateConversation: (_conversationId, opts) => {
|
|
392
|
+
capturedOptions = opts;
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
expect(res.status).toBe(202);
|
|
398
|
+
expect(capturedOptions).toEqual({
|
|
399
|
+
transport: {
|
|
400
|
+
channelId: "vellum",
|
|
401
|
+
interfaceId: "ios",
|
|
402
|
+
clientTimezone: "Europe/London",
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test("drops invalid clientTimezone without rejecting the message", async () => {
|
|
408
|
+
const persistUserMessage = mock(async () => "persisted-msg-id");
|
|
409
|
+
const runAgentLoop = mock(async () => undefined);
|
|
410
|
+
const conversation = makeConversation({ persistUserMessage, runAgentLoop });
|
|
411
|
+
let capturedOptions: Record<string, unknown> | undefined;
|
|
412
|
+
|
|
413
|
+
const res = await sendMessage(
|
|
414
|
+
"hello",
|
|
415
|
+
conversation,
|
|
416
|
+
{ clientTimezone: "not-a-timezone" },
|
|
417
|
+
{
|
|
418
|
+
onGetOrCreateConversation: (_conversationId, opts) => {
|
|
419
|
+
capturedOptions = opts;
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
expect(res.status).toBe(202);
|
|
425
|
+
expect(capturedOptions).toEqual({
|
|
426
|
+
transport: {
|
|
427
|
+
channelId: "vellum",
|
|
428
|
+
interfaceId: "macos",
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
expect(persistUserMessage).toHaveBeenCalledTimes(1);
|
|
432
|
+
expect(runAgentLoop).toHaveBeenCalledTimes(1);
|
|
433
|
+
});
|
|
434
|
+
});
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Covers:
|
|
6
6
|
*
|
|
7
|
-
* 1. The
|
|
8
|
-
* back from `getInjectors()` in the documented order
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* 1. The ten default injectors registered by `defaultInjectorsPlugin` come
|
|
8
|
+
* back from `getInjectors()` in the documented order
|
|
9
|
+
* (disk-pressure-warning → workspace-context → unified-turn-context →
|
|
10
|
+
* pkb-context → pkb-reminder → memory-v2-static → now-md →
|
|
11
|
+
* subagent-status → slack-messages → thread-focus).
|
|
11
12
|
* 2. A third-party-registered injector at `order: 25` slots between
|
|
12
13
|
* `unified-turn-context` (order 20) and `pkb` (order 30), proving the
|
|
13
14
|
* extensibility contract.
|
|
@@ -25,6 +26,7 @@
|
|
|
25
26
|
|
|
26
27
|
import { beforeEach, describe, expect, test } from "bun:test";
|
|
27
28
|
|
|
29
|
+
import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
|
|
28
30
|
import {
|
|
29
31
|
applyRuntimeInjections,
|
|
30
32
|
composeInjectorChain,
|
|
@@ -33,6 +35,11 @@ import {
|
|
|
33
35
|
DEFAULT_INJECTOR_ORDER,
|
|
34
36
|
defaultInjectorsPlugin,
|
|
35
37
|
} from "../plugins/defaults/injectors.js";
|
|
38
|
+
|
|
39
|
+
// This test exercises v1 PKB injection. The `memory-v2-enabled` flag
|
|
40
|
+
// (registry default `true`) makes the PKB injector go silent — disable it
|
|
41
|
+
// here so the v1 injection chain assertions stay meaningful.
|
|
42
|
+
_setOverridesForTesting({ "memory-v2-enabled": false });
|
|
36
43
|
import {
|
|
37
44
|
getInjectors,
|
|
38
45
|
registerPlugin,
|
|
@@ -76,11 +83,12 @@ describe("injector chain", () => {
|
|
|
76
83
|
resetPluginRegistryForTests();
|
|
77
84
|
});
|
|
78
85
|
|
|
79
|
-
test("defaultInjectorsPlugin registers the
|
|
86
|
+
test("defaultInjectorsPlugin registers the ten defaults in the documented order", () => {
|
|
80
87
|
registerPlugin(defaultInjectorsPlugin);
|
|
81
88
|
|
|
82
89
|
const names = getInjectors().map((i) => i.name);
|
|
83
90
|
expect(names).toEqual([
|
|
91
|
+
"disk-pressure-warning",
|
|
84
92
|
"workspace-context",
|
|
85
93
|
"unified-turn-context",
|
|
86
94
|
"pkb-context",
|
|
@@ -97,6 +105,9 @@ describe("injector chain", () => {
|
|
|
97
105
|
registerPlugin(defaultInjectorsPlugin);
|
|
98
106
|
|
|
99
107
|
const byName = new Map(getInjectors().map((i) => [i.name, i.order]));
|
|
108
|
+
expect(byName.get("disk-pressure-warning")).toBe(
|
|
109
|
+
DEFAULT_INJECTOR_ORDER.diskPressureWarning,
|
|
110
|
+
);
|
|
100
111
|
expect(byName.get("workspace-context")).toBe(
|
|
101
112
|
DEFAULT_INJECTOR_ORDER.workspaceContext,
|
|
102
113
|
);
|
|
@@ -132,6 +143,7 @@ describe("injector chain", () => {
|
|
|
132
143
|
|
|
133
144
|
const names = getInjectors().map((i) => i.name);
|
|
134
145
|
expect(names).toEqual([
|
|
146
|
+
"disk-pressure-warning", // 5
|
|
135
147
|
"workspace-context", // 10
|
|
136
148
|
"unified-turn-context", // 20
|
|
137
149
|
"plugin-25", // 25 — slots in
|
|
@@ -146,7 +158,7 @@ describe("injector chain", () => {
|
|
|
146
158
|
});
|
|
147
159
|
|
|
148
160
|
test("composeInjectorChain returns empty string when every injector opts out", async () => {
|
|
149
|
-
// The default chain is the golden-path: all
|
|
161
|
+
// The default chain is the golden-path: all ten defaults return `null`
|
|
150
162
|
// on an empty turn context, so the composed block is an empty string.
|
|
151
163
|
registerPlugin(defaultInjectorsPlugin);
|
|
152
164
|
|