@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
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
|
|
4
|
+
import {
|
|
5
|
+
applyRuntimeInjections,
|
|
6
|
+
stripInjectionsForCompaction,
|
|
7
|
+
} from "../daemon/conversation-runtime-assembly.js";
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_INJECTOR_ORDER,
|
|
10
|
+
defaultInjectorsPlugin,
|
|
11
|
+
DISK_PRESSURE_WARNING_PROMPT,
|
|
12
|
+
} from "../plugins/defaults/injectors.js";
|
|
13
|
+
import {
|
|
14
|
+
registerPlugin,
|
|
15
|
+
resetPluginRegistryForTests,
|
|
16
|
+
} from "../plugins/registry.js";
|
|
17
|
+
import type { Injector, TurnContext } from "../plugins/types.js";
|
|
18
|
+
import type { Message } from "../providers/types.js";
|
|
19
|
+
|
|
20
|
+
function findInjector(name: string): Injector {
|
|
21
|
+
const injector = defaultInjectorsPlugin.injectors?.find(
|
|
22
|
+
(candidate) => candidate.name === name,
|
|
23
|
+
);
|
|
24
|
+
if (!injector) {
|
|
25
|
+
throw new Error(`injector '${name}' not registered`);
|
|
26
|
+
}
|
|
27
|
+
return injector;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function makeContext(overrides: Partial<TurnContext> = {}): TurnContext {
|
|
31
|
+
return {
|
|
32
|
+
requestId: "req-test",
|
|
33
|
+
conversationId: "conv-test",
|
|
34
|
+
turnIndex: 0,
|
|
35
|
+
trust: { sourceChannel: "vellum", trustClass: "guardian" },
|
|
36
|
+
...overrides,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function tailTexts(messages: Message[]): string[] {
|
|
41
|
+
const tail = messages[messages.length - 1];
|
|
42
|
+
if (!tail || tail.role !== "user") return [];
|
|
43
|
+
return tail.content
|
|
44
|
+
.filter((block): block is { type: "text"; text: string } => {
|
|
45
|
+
return block.type === "text";
|
|
46
|
+
})
|
|
47
|
+
.map((block) => block.text);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const diskPressureInjector = findInjector("disk-pressure-warning");
|
|
51
|
+
const cleanupContext = { cleanupModeActive: true };
|
|
52
|
+
|
|
53
|
+
describe("disk-pressure-warning injector", () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
resetPluginRegistryForTests();
|
|
56
|
+
registerPlugin(defaultInjectorsPlugin);
|
|
57
|
+
_setOverridesForTesting({ "safe-storage-limits": true });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("emits the exact cleanup prompt while safe storage limits are enabled", async () => {
|
|
61
|
+
const block = await diskPressureInjector.produce(
|
|
62
|
+
makeContext({
|
|
63
|
+
injectionInputs: { diskPressureContext: cleanupContext },
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(block).toEqual({
|
|
68
|
+
id: "disk-pressure-warning",
|
|
69
|
+
text: DISK_PRESSURE_WARNING_PROMPT,
|
|
70
|
+
placement: "prepend-user-tail",
|
|
71
|
+
});
|
|
72
|
+
expect(diskPressureInjector.order).toBe(
|
|
73
|
+
DEFAULT_INJECTOR_ORDER.diskPressureWarning,
|
|
74
|
+
);
|
|
75
|
+
expect(DISK_PRESSURE_WARNING_PROMPT).toBe(`<disk_pressure_warning>
|
|
76
|
+
Disk usage is critically low: this assistant is in storage cleanup mode because the workspace volume is at least 95% full.
|
|
77
|
+
|
|
78
|
+
In your first paragraph, warn the user that storage is critically low and that normal work is suspended until space is freed.
|
|
79
|
+
|
|
80
|
+
Then help the user clean up storage. Prefer safe inspection steps first, such as checking available space and finding large directories. Ask before deleting files or caches unless the user has already clearly approved the specific cleanup action.
|
|
81
|
+
|
|
82
|
+
Do not work on unrelated tasks until disk usage drops below the critical threshold or the user explicitly overrides the lock. Background processes and messages from trusted contacts are blocked while this cleanup mode is active.
|
|
83
|
+
</disk_pressure_warning>`);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("omits the prompt when cleanup context is null or inactive", async () => {
|
|
87
|
+
await expect(
|
|
88
|
+
diskPressureInjector.produce(
|
|
89
|
+
makeContext({ injectionInputs: { diskPressureContext: null } }),
|
|
90
|
+
),
|
|
91
|
+
).resolves.toBeNull();
|
|
92
|
+
|
|
93
|
+
await expect(
|
|
94
|
+
diskPressureInjector.produce(
|
|
95
|
+
makeContext({
|
|
96
|
+
injectionInputs: {
|
|
97
|
+
diskPressureContext: { cleanupModeActive: false },
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
),
|
|
101
|
+
).resolves.toBeNull();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("omits the prompt when safe storage limits are disabled", async () => {
|
|
105
|
+
_setOverridesForTesting({ "safe-storage-limits": false });
|
|
106
|
+
|
|
107
|
+
await expect(
|
|
108
|
+
diskPressureInjector.produce(
|
|
109
|
+
makeContext({
|
|
110
|
+
injectionInputs: { diskPressureContext: cleanupContext },
|
|
111
|
+
}),
|
|
112
|
+
),
|
|
113
|
+
).resolves.toBeNull();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("prepends ahead of workspace and unified turn context in full mode", async () => {
|
|
117
|
+
const runMessages: Message[] = [
|
|
118
|
+
{ role: "user", content: [{ type: "text", text: "clean up space" }] },
|
|
119
|
+
];
|
|
120
|
+
const workspace = "<workspace>\nRoot: /workspace\n</workspace>";
|
|
121
|
+
const turnContext = "<turn_context>\ninterface: macos\n</turn_context>";
|
|
122
|
+
|
|
123
|
+
const result = await applyRuntimeInjections(runMessages, {
|
|
124
|
+
turnContext: makeContext(),
|
|
125
|
+
diskPressureContext: cleanupContext,
|
|
126
|
+
workspaceTopLevelContext: workspace,
|
|
127
|
+
unifiedTurnContext: turnContext,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
expect(tailTexts(result.messages).slice(0, 4)).toEqual([
|
|
131
|
+
DISK_PRESSURE_WARNING_PROMPT,
|
|
132
|
+
workspace,
|
|
133
|
+
turnContext,
|
|
134
|
+
"clean up space",
|
|
135
|
+
]);
|
|
136
|
+
expect(
|
|
137
|
+
result.blocks.injectorChainBlock?.startsWith(
|
|
138
|
+
DISK_PRESSURE_WARNING_PROMPT,
|
|
139
|
+
),
|
|
140
|
+
).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("survives minimal mode as safety-critical context", async () => {
|
|
144
|
+
const result = await applyRuntimeInjections(
|
|
145
|
+
[{ role: "user", content: [{ type: "text", text: "status" }] }],
|
|
146
|
+
{
|
|
147
|
+
turnContext: makeContext(),
|
|
148
|
+
mode: "minimal",
|
|
149
|
+
diskPressureContext: cleanupContext,
|
|
150
|
+
workspaceTopLevelContext: "<workspace>...</workspace>",
|
|
151
|
+
unifiedTurnContext: "<turn_context>...</turn_context>",
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
expect(tailTexts(result.messages)).toEqual([
|
|
156
|
+
DISK_PRESSURE_WARNING_PROMPT,
|
|
157
|
+
"<turn_context>...</turn_context>",
|
|
158
|
+
"status",
|
|
159
|
+
]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("applies after Slack chronological transcript replacement", async () => {
|
|
163
|
+
const originalRun: Message[] = [
|
|
164
|
+
{
|
|
165
|
+
role: "user",
|
|
166
|
+
content: [{ type: "text", text: "latest raw user text" }],
|
|
167
|
+
},
|
|
168
|
+
];
|
|
169
|
+
const slackTranscript: Message[] = [
|
|
170
|
+
{
|
|
171
|
+
role: "user",
|
|
172
|
+
content: [{ type: "text", text: "[12:00 user]: earlier" }],
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
role: "user",
|
|
176
|
+
content: [{ type: "text", text: "[12:01 @assistant]: cleanup?" }],
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
const result = await applyRuntimeInjections(originalRun, {
|
|
181
|
+
turnContext: makeContext(),
|
|
182
|
+
diskPressureContext: cleanupContext,
|
|
183
|
+
channelCapabilities: {
|
|
184
|
+
channel: "slack",
|
|
185
|
+
dashboardCapable: false,
|
|
186
|
+
supportsDynamicUi: false,
|
|
187
|
+
supportsVoiceInput: false,
|
|
188
|
+
chatType: "channel",
|
|
189
|
+
},
|
|
190
|
+
slackChronologicalMessages: slackTranscript,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(result.messages).toHaveLength(2);
|
|
194
|
+
const texts = tailTexts(result.messages);
|
|
195
|
+
expect(texts[0]).toBe(DISK_PRESSURE_WARNING_PROMPT);
|
|
196
|
+
expect(
|
|
197
|
+
texts.some((text) => text.startsWith("<channel_capabilities>")),
|
|
198
|
+
).toBe(true);
|
|
199
|
+
expect(texts[texts.length - 1]).toBe("[12:01 @assistant]: cleanup?");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("compaction strip plus re-apply does not duplicate the warning", async () => {
|
|
203
|
+
const runMessages: Message[] = [
|
|
204
|
+
{ role: "user", content: [{ type: "text", text: "find large files" }] },
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
const first = await applyRuntimeInjections(runMessages, {
|
|
208
|
+
turnContext: makeContext(),
|
|
209
|
+
diskPressureContext: cleanupContext,
|
|
210
|
+
});
|
|
211
|
+
const stripped = stripInjectionsForCompaction(first.messages);
|
|
212
|
+
expect(tailTexts(stripped)).toEqual(["find large files"]);
|
|
213
|
+
|
|
214
|
+
const second = await applyRuntimeInjections(stripped, {
|
|
215
|
+
turnContext: makeContext(),
|
|
216
|
+
diskPressureContext: cleanupContext,
|
|
217
|
+
});
|
|
218
|
+
expect(
|
|
219
|
+
tailTexts(second.messages).filter(
|
|
220
|
+
(text) => text === DISK_PRESSURE_WARNING_PROMPT,
|
|
221
|
+
),
|
|
222
|
+
).toHaveLength(1);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
@@ -125,6 +125,16 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
|
|
|
125
125
|
).toThrow(BadRequestError);
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
+
test("allows edits to custom-balanced (user-owned)", () => {
|
|
129
|
+
savedRaw = null;
|
|
130
|
+
const result = replaceRoute.handler({
|
|
131
|
+
pathParams: { name: "custom-balanced" },
|
|
132
|
+
body: { provider: "openai", model: "gpt-4o" },
|
|
133
|
+
});
|
|
134
|
+
expect(result).toEqual({ ok: true });
|
|
135
|
+
expect(savedRaw).not.toBeNull();
|
|
136
|
+
});
|
|
137
|
+
|
|
128
138
|
test("allows edits to a user-defined profile", () => {
|
|
129
139
|
savedRaw = null;
|
|
130
140
|
const result = replaceRoute.handler({
|
|
@@ -165,6 +175,14 @@ describe("PATCH /v1/config — managed profile deletion guard", () => {
|
|
|
165
175
|
).rejects.toThrow(BadRequestError);
|
|
166
176
|
});
|
|
167
177
|
|
|
178
|
+
test("allows deletion of custom-balanced via null (user-owned)", async () => {
|
|
179
|
+
savedRaw = null;
|
|
180
|
+
const result = await patchRoute.handler({
|
|
181
|
+
body: { llm: { profiles: { "custom-balanced": null } } },
|
|
182
|
+
});
|
|
183
|
+
expect(result).toEqual({ ok: true });
|
|
184
|
+
});
|
|
185
|
+
|
|
168
186
|
test("allows deletion of a user-defined profile via null", async () => {
|
|
169
187
|
savedRaw = null;
|
|
170
188
|
const result = await patchRoute.handler({
|
|
@@ -122,6 +122,136 @@ describe("MCP AbortSignal threading", () => {
|
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
describe("createMcpTool execute", () => {
|
|
125
|
+
test("keeps safe MCP tool names unchanged", () => {
|
|
126
|
+
const fakeManager = { callTool: jest.fn() } as any;
|
|
127
|
+
|
|
128
|
+
const tool = createMcpTool(
|
|
129
|
+
{
|
|
130
|
+
name: "my-tool",
|
|
131
|
+
description: "A test tool",
|
|
132
|
+
inputSchema: { type: "object", properties: {} },
|
|
133
|
+
},
|
|
134
|
+
"test-server",
|
|
135
|
+
{
|
|
136
|
+
transport: { type: "stdio", command: "echo", args: [] },
|
|
137
|
+
enabled: true,
|
|
138
|
+
defaultRiskLevel: "high",
|
|
139
|
+
maxTools: 100,
|
|
140
|
+
},
|
|
141
|
+
fakeManager,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
expect(tool.name).toBe("mcp__test-server__my-tool");
|
|
145
|
+
expect(tool.getDefinition().name).toBe("mcp__test-server__my-tool");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("keeps MCP tool names with trailing whitespace distinct", () => {
|
|
149
|
+
const fakeManager = { callTool: jest.fn() } as any;
|
|
150
|
+
|
|
151
|
+
const plain = createMcpTool(
|
|
152
|
+
{
|
|
153
|
+
name: "deploy",
|
|
154
|
+
description: "Deploy",
|
|
155
|
+
inputSchema: { type: "object", properties: {} },
|
|
156
|
+
},
|
|
157
|
+
"test-server",
|
|
158
|
+
{
|
|
159
|
+
transport: { type: "stdio", command: "echo", args: [] },
|
|
160
|
+
enabled: true,
|
|
161
|
+
defaultRiskLevel: "high",
|
|
162
|
+
maxTools: 100,
|
|
163
|
+
},
|
|
164
|
+
fakeManager,
|
|
165
|
+
);
|
|
166
|
+
const padded = createMcpTool(
|
|
167
|
+
{
|
|
168
|
+
name: "deploy ",
|
|
169
|
+
description: "Deploy padded",
|
|
170
|
+
inputSchema: { type: "object", properties: {} },
|
|
171
|
+
},
|
|
172
|
+
"test-server",
|
|
173
|
+
{
|
|
174
|
+
transport: { type: "stdio", command: "echo", args: [] },
|
|
175
|
+
enabled: true,
|
|
176
|
+
defaultRiskLevel: "high",
|
|
177
|
+
maxTools: 100,
|
|
178
|
+
},
|
|
179
|
+
fakeManager,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
expect(plain.name).toBe("mcp__test-server__deploy");
|
|
183
|
+
expect(padded.name).toMatch(/^mcp__test-server__deploy__[a-f0-9]{12}$/);
|
|
184
|
+
expect(padded.name).not.toBe(plain.name);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("exposes provider-safe MCP names while preserving raw execution names", async () => {
|
|
188
|
+
const callToolSpy = jest.fn().mockResolvedValue({
|
|
189
|
+
content: "tool result",
|
|
190
|
+
isError: false,
|
|
191
|
+
});
|
|
192
|
+
const fakeManager = { callTool: callToolSpy } as any;
|
|
193
|
+
|
|
194
|
+
const tool = createMcpTool(
|
|
195
|
+
{
|
|
196
|
+
name: "create link",
|
|
197
|
+
description: "Create a Stripe Link CLI resource",
|
|
198
|
+
inputSchema: { type: "object", properties: {} },
|
|
199
|
+
},
|
|
200
|
+
"stripe.link-cli",
|
|
201
|
+
{
|
|
202
|
+
transport: { type: "stdio", command: "echo", args: [] },
|
|
203
|
+
enabled: true,
|
|
204
|
+
defaultRiskLevel: "high",
|
|
205
|
+
maxTools: 100,
|
|
206
|
+
},
|
|
207
|
+
fakeManager,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
expect(tool.name).toMatch(/^[a-zA-Z0-9_-]{1,64}$/);
|
|
211
|
+
expect(tool.name.startsWith("mcp__stripe_link-cli__create_link__")).toBe(
|
|
212
|
+
true,
|
|
213
|
+
);
|
|
214
|
+
expect(tool.getDefinition().name).toBe(tool.name);
|
|
215
|
+
|
|
216
|
+
await tool.execute(
|
|
217
|
+
{ someArg: "value" },
|
|
218
|
+
{
|
|
219
|
+
workingDir: "/tmp",
|
|
220
|
+
conversationId: "conv-1",
|
|
221
|
+
trustClass: "guardian",
|
|
222
|
+
},
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
expect(callToolSpy).toHaveBeenCalledWith(
|
|
226
|
+
"stripe.link-cli",
|
|
227
|
+
"create link",
|
|
228
|
+
{ someArg: "value" },
|
|
229
|
+
undefined,
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("caps long MCP names at the provider limit", () => {
|
|
234
|
+
const fakeManager = { callTool: jest.fn() } as any;
|
|
235
|
+
const tool = createMcpTool(
|
|
236
|
+
{
|
|
237
|
+
name: "x".repeat(180),
|
|
238
|
+
description: "A test tool",
|
|
239
|
+
inputSchema: { type: "object", properties: {} },
|
|
240
|
+
},
|
|
241
|
+
"server",
|
|
242
|
+
{
|
|
243
|
+
transport: { type: "stdio", command: "echo", args: [] },
|
|
244
|
+
enabled: true,
|
|
245
|
+
defaultRiskLevel: "high",
|
|
246
|
+
maxTools: 100,
|
|
247
|
+
},
|
|
248
|
+
fakeManager,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
expect(tool.name).toHaveLength(64);
|
|
252
|
+
expect(tool.name).toMatch(/^[a-zA-Z0-9_-]{1,64}$/);
|
|
253
|
+
});
|
|
254
|
+
|
|
125
255
|
test("threads context.signal through manager.callTool", async () => {
|
|
126
256
|
const callToolSpy = jest.fn().mockResolvedValue({
|
|
127
257
|
content: "tool result",
|
|
@@ -15,7 +15,6 @@ interface CapturedSearch {
|
|
|
15
15
|
|
|
16
16
|
const capturedSearches: CapturedSearch[] = [];
|
|
17
17
|
const getConfiguredProviderCalls: string[] = [];
|
|
18
|
-
const scopeByConversation = new Map<string, string | undefined>();
|
|
19
18
|
const testConfig = {} as AssistantConfig;
|
|
20
19
|
|
|
21
20
|
mock.module("../config/loader.js", () => ({
|
|
@@ -56,13 +55,11 @@ mock.module("../memory/embedding-backend.js", () => ({
|
|
|
56
55
|
|
|
57
56
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
58
57
|
addMessage: () => ({ id: "msg-1" }),
|
|
59
|
-
createConversation: () => ({ id: "conv-1"
|
|
58
|
+
createConversation: () => ({ id: "conv-1" }),
|
|
60
59
|
deleteConversation: () => true,
|
|
61
60
|
getAssistantMessageIdsInTurn: () => [],
|
|
62
61
|
getConversation: () => null,
|
|
63
62
|
getConversationHostAccess: () => false,
|
|
64
|
-
getConversationMemoryScopeId: (conversationId: string) =>
|
|
65
|
-
scopeByConversation.get(conversationId),
|
|
66
63
|
getConversationOverrideProfile: () => undefined,
|
|
67
64
|
getConversationSource: () => null,
|
|
68
65
|
getMessageById: () => null,
|
|
@@ -165,12 +162,9 @@ describe("memory admin recall", () => {
|
|
|
165
162
|
beforeEach(() => {
|
|
166
163
|
capturedSearches.length = 0;
|
|
167
164
|
getConfiguredProviderCalls.length = 0;
|
|
168
|
-
scopeByConversation.clear();
|
|
169
165
|
});
|
|
170
166
|
|
|
171
|
-
test("uses
|
|
172
|
-
scopeByConversation.set("conv-admin", "scope-admin");
|
|
173
|
-
|
|
167
|
+
test("uses safe admin sources", async () => {
|
|
174
168
|
const result = await queryMemory("launch notes", "conv-admin");
|
|
175
169
|
|
|
176
170
|
expect(capturedSearches).toHaveLength(1);
|
|
@@ -180,7 +174,6 @@ describe("memory admin recall", () => {
|
|
|
180
174
|
});
|
|
181
175
|
expect(capturedSearches[0].context).toMatchObject({
|
|
182
176
|
workingDir: getWorkspaceDir(),
|
|
183
|
-
memoryScopeId: "scope-admin",
|
|
184
177
|
conversationId: "conv-admin",
|
|
185
178
|
config: testConfig,
|
|
186
179
|
});
|
|
@@ -202,13 +195,12 @@ describe("memory admin recall", () => {
|
|
|
202
195
|
});
|
|
203
196
|
});
|
|
204
197
|
|
|
205
|
-
test("
|
|
198
|
+
test("does not invoke a provider for deterministic recall", async () => {
|
|
206
199
|
await queryMemory("offline recall", "missing-conversation");
|
|
207
200
|
|
|
208
201
|
expect(capturedSearches).toHaveLength(1);
|
|
209
202
|
expect(capturedSearches[0].context).toMatchObject({
|
|
210
203
|
workingDir: getWorkspaceDir(),
|
|
211
|
-
memoryScopeId: "default",
|
|
212
204
|
conversationId: "missing-conversation",
|
|
213
205
|
});
|
|
214
206
|
expect(capturedSearches[0].input.sources).toEqual([
|
|
@@ -158,6 +158,27 @@ describe("runDefaultMemoryRetrieval", () => {
|
|
|
158
158
|
expect(result.nowContent).toBe("now-default");
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
+
test("propagates errors from prepareMemory rather than swallowing them", async () => {
|
|
162
|
+
// Memory is critical — failures must surface to the caller (the agent
|
|
163
|
+
// loop) rather than silently degrading to an empty memory block.
|
|
164
|
+
const failingPrepare = mock(
|
|
165
|
+
(
|
|
166
|
+
_msgs: Message[],
|
|
167
|
+
_cfg: AssistantConfig,
|
|
168
|
+
_signal: AbortSignal,
|
|
169
|
+
_onEvent: (msg: ServerMessage) => void,
|
|
170
|
+
) => Promise.reject(new Error("retrieval failed")),
|
|
171
|
+
);
|
|
172
|
+
const graphMemory = {
|
|
173
|
+
prepareMemory: failingPrepare,
|
|
174
|
+
} as unknown as ConversationGraphMemory;
|
|
175
|
+
const deps = makeDeps({ graphMemory, isTrustedActor: true });
|
|
176
|
+
|
|
177
|
+
await expect(
|
|
178
|
+
runDefaultMemoryRetrieval(makeMemoryArgs(), deps),
|
|
179
|
+
).rejects.toThrow("retrieval failed");
|
|
180
|
+
});
|
|
181
|
+
|
|
161
182
|
test("passes through null PKB and NOW when the files are absent", async () => {
|
|
162
183
|
readPkbContextMock.mockImplementation(() => null);
|
|
163
184
|
readNowContextMock.mockImplementation(() => null);
|
|
@@ -314,7 +335,7 @@ describe("memoryRetrieval pipeline — default vs custom plugin", () => {
|
|
|
314
335
|
(innerArgs: MemoryArgs) => runDefaultMemoryRetrieval(innerArgs, deps),
|
|
315
336
|
args,
|
|
316
337
|
makeTurnCtx(),
|
|
317
|
-
30, // tiny budget
|
|
338
|
+
30, // tiny pipeline budget to keep the test fast
|
|
318
339
|
);
|
|
319
340
|
} catch (err) {
|
|
320
341
|
caught = err;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
normalizeOnboardingContext,
|
|
5
|
+
normalizeTasks,
|
|
6
|
+
normalizeTools,
|
|
7
|
+
TASK_DISPLAY_LABELS,
|
|
8
|
+
TOOL_DISPLAY_NAMES,
|
|
9
|
+
} from "../prompts/normalize-onboarding.js";
|
|
10
|
+
import type { OnboardingContext } from "../types/onboarding-context.js";
|
|
11
|
+
|
|
12
|
+
describe("normalizeTools", () => {
|
|
13
|
+
test("known tool IDs produce display labels", () => {
|
|
14
|
+
expect(normalizeTools(["github"])).toEqual(["GitHub"]);
|
|
15
|
+
expect(normalizeTools(["google-calendar"])).toEqual(["Google Calendar"]);
|
|
16
|
+
expect(normalizeTools(["slack"])).toEqual(["Slack"]);
|
|
17
|
+
expect(normalizeTools(["notion"])).toEqual(["Notion"]);
|
|
18
|
+
expect(normalizeTools(["linear"])).toEqual(["Linear"]);
|
|
19
|
+
expect(normalizeTools(["gmail"])).toEqual(["Gmail"]);
|
|
20
|
+
expect(normalizeTools(["google-drive"])).toEqual(["Google Drive"]);
|
|
21
|
+
expect(normalizeTools(["figma"])).toEqual(["Figma"]);
|
|
22
|
+
expect(normalizeTools(["jira"])).toEqual(["Jira"]);
|
|
23
|
+
expect(normalizeTools(["outlook"])).toEqual(["Outlook"]);
|
|
24
|
+
expect(normalizeTools(["excel"])).toEqual(["Excel"]);
|
|
25
|
+
expect(normalizeTools(["apple-notes"])).toEqual(["Apple Notes"]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("all known tool IDs from the client onboarding UI are mapped", () => {
|
|
29
|
+
const clientToolIds = [
|
|
30
|
+
"gmail",
|
|
31
|
+
"outlook",
|
|
32
|
+
"google-calendar",
|
|
33
|
+
"slack",
|
|
34
|
+
"notion",
|
|
35
|
+
"linear",
|
|
36
|
+
"jira",
|
|
37
|
+
"github",
|
|
38
|
+
"figma",
|
|
39
|
+
"google-drive",
|
|
40
|
+
"excel",
|
|
41
|
+
"apple-notes",
|
|
42
|
+
];
|
|
43
|
+
expect(Object.keys(TOOL_DISPLAY_NAMES)).toEqual(
|
|
44
|
+
expect.arrayContaining(clientToolIds),
|
|
45
|
+
);
|
|
46
|
+
expect(Object.keys(TOOL_DISPLAY_NAMES)).toHaveLength(clientToolIds.length);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("unknown/custom tool IDs pass through with first-letter capitalization", () => {
|
|
50
|
+
expect(normalizeTools(["trello"])).toEqual(["Trello"]);
|
|
51
|
+
expect(normalizeTools(["asana"])).toEqual(["Asana"]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("mixed known and unknown IDs normalize correctly", () => {
|
|
55
|
+
expect(normalizeTools(["github", "trello", "slack"])).toEqual([
|
|
56
|
+
"GitHub",
|
|
57
|
+
"Trello",
|
|
58
|
+
"Slack",
|
|
59
|
+
]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("empty array produces empty array", () => {
|
|
63
|
+
expect(normalizeTools([])).toEqual([]);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("normalizeTasks", () => {
|
|
68
|
+
test("known task IDs produce plain-language labels", () => {
|
|
69
|
+
expect(normalizeTasks(["code-building"])).toEqual([
|
|
70
|
+
"builds code, apps, or tools",
|
|
71
|
+
]);
|
|
72
|
+
expect(normalizeTasks(["writing"])).toEqual([
|
|
73
|
+
"writes docs, emails, or content",
|
|
74
|
+
]);
|
|
75
|
+
expect(normalizeTasks(["research"])).toEqual([
|
|
76
|
+
"does research and analysis",
|
|
77
|
+
]);
|
|
78
|
+
expect(normalizeTasks(["project-management"])).toEqual([
|
|
79
|
+
"plans and coordinates work",
|
|
80
|
+
]);
|
|
81
|
+
expect(normalizeTasks(["scheduling"])).toEqual([
|
|
82
|
+
"handles meetings, calendar, and logistics",
|
|
83
|
+
]);
|
|
84
|
+
expect(normalizeTasks(["personal"])).toEqual(["handles life admin"]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("all six known task IDs are mapped", () => {
|
|
88
|
+
const knownIds = [
|
|
89
|
+
"code-building",
|
|
90
|
+
"writing",
|
|
91
|
+
"research",
|
|
92
|
+
"project-management",
|
|
93
|
+
"scheduling",
|
|
94
|
+
"personal",
|
|
95
|
+
];
|
|
96
|
+
expect(Object.keys(TASK_DISPLAY_LABELS)).toEqual(
|
|
97
|
+
expect.arrayContaining(knownIds),
|
|
98
|
+
);
|
|
99
|
+
expect(Object.keys(TASK_DISPLAY_LABELS)).toHaveLength(knownIds.length);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("unknown/custom task IDs pass through unchanged", () => {
|
|
103
|
+
expect(normalizeTasks(["data-entry"])).toEqual(["data-entry"]);
|
|
104
|
+
expect(normalizeTasks(["custom-workflow"])).toEqual(["custom-workflow"]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("mixed known and unknown IDs normalize correctly", () => {
|
|
108
|
+
expect(normalizeTasks(["writing", "data-entry", "research"])).toEqual([
|
|
109
|
+
"writes docs, emails, or content",
|
|
110
|
+
"data-entry",
|
|
111
|
+
"does research and analysis",
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("empty array produces empty array", () => {
|
|
116
|
+
expect(normalizeTasks([])).toEqual([]);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("normalizeOnboardingContext", () => {
|
|
121
|
+
test("maps userName to preferredName", () => {
|
|
122
|
+
const ctx: OnboardingContext = {
|
|
123
|
+
tools: [],
|
|
124
|
+
tasks: [],
|
|
125
|
+
tone: "friendly",
|
|
126
|
+
userName: "Alice",
|
|
127
|
+
};
|
|
128
|
+
const result = normalizeOnboardingContext(ctx);
|
|
129
|
+
expect(result.preferredName).toBe("Alice");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("absent userName yields undefined preferredName", () => {
|
|
133
|
+
const ctx: OnboardingContext = {
|
|
134
|
+
tools: [],
|
|
135
|
+
tasks: [],
|
|
136
|
+
tone: "professional",
|
|
137
|
+
};
|
|
138
|
+
const result = normalizeOnboardingContext(ctx);
|
|
139
|
+
expect(result.preferredName).toBeUndefined();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("tone passes through", () => {
|
|
143
|
+
const ctx: OnboardingContext = {
|
|
144
|
+
tools: [],
|
|
145
|
+
tasks: [],
|
|
146
|
+
tone: "casual",
|
|
147
|
+
};
|
|
148
|
+
const result = normalizeOnboardingContext(ctx);
|
|
149
|
+
expect(result.tone).toBe("casual");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("assistantName passes through", () => {
|
|
153
|
+
const ctx: OnboardingContext = {
|
|
154
|
+
tools: [],
|
|
155
|
+
tasks: [],
|
|
156
|
+
tone: "friendly",
|
|
157
|
+
assistantName: "Jarvis",
|
|
158
|
+
};
|
|
159
|
+
const result = normalizeOnboardingContext(ctx);
|
|
160
|
+
expect(result.assistantName).toBe("Jarvis");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("normalizes tools and tasks together", () => {
|
|
164
|
+
const ctx: OnboardingContext = {
|
|
165
|
+
tools: ["github", "trello"],
|
|
166
|
+
tasks: ["code-building", "data-entry"],
|
|
167
|
+
tone: "professional",
|
|
168
|
+
userName: "Bob",
|
|
169
|
+
assistantName: "Friday",
|
|
170
|
+
};
|
|
171
|
+
const result = normalizeOnboardingContext(ctx);
|
|
172
|
+
expect(result).toEqual({
|
|
173
|
+
preferredName: "Bob",
|
|
174
|
+
commonWork: ["builds code, apps, or tools", "data-entry"],
|
|
175
|
+
dailyTools: ["GitHub", "Trello"],
|
|
176
|
+
tone: "professional",
|
|
177
|
+
assistantName: "Friday",
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|