@vellumai/assistant 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +45 -29
- package/Dockerfile +1 -0
- package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
- package/bun.lock +3 -0
- package/docs/architecture/memory.md +5 -2
- package/knip.json +1 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
- package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
- package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
- package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
- package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
- package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
- package/openapi.yaml +470 -25
- package/package.json +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +291 -0
- package/src/__tests__/app-control-flow.test.ts +21 -11
- package/src/__tests__/approval-cascade.test.ts +8 -16
- package/src/__tests__/approval-routes-http.test.ts +6 -0
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/assistant-event.test.ts +0 -10
- package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +48 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
- package/src/__tests__/call-constants.test.ts +10 -1
- package/src/__tests__/call-controller.test.ts +127 -0
- package/src/__tests__/call-conversation-messages.test.ts +8 -2
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
- package/src/__tests__/channel-readiness-service.test.ts +4 -2
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
- package/src/__tests__/config-loader-backfill.test.ts +379 -0
- package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
- package/src/__tests__/config-watcher.test.ts +140 -69
- package/src/__tests__/context-search-agent-runner.test.ts +61 -3
- package/src/__tests__/context-search-conversations-source.test.ts +0 -24
- package/src/__tests__/context-search-fanout.test.ts +0 -1
- package/src/__tests__/context-search-memory-source.test.ts +6 -33
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +12 -7
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +457 -8
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
- package/src/__tests__/conversation-process-callsite.test.ts +38 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +74 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
- package/src/__tests__/conversation-speed-override.test.ts +0 -3
- package/src/__tests__/conversation-store.test.ts +0 -18
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
- package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +476 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +61 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/credentials-cli.test.ts +7 -0
- package/src/__tests__/cu-unified-flow.test.ts +176 -10
- package/src/__tests__/date-context.test.ts +164 -2
- package/src/__tests__/disk-pressure-guard.test.ts +262 -0
- package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
- package/src/__tests__/disk-pressure-policy.test.ts +241 -0
- package/src/__tests__/disk-pressure-routes.test.ts +379 -0
- package/src/__tests__/disk-pressure-tools.test.ts +277 -0
- package/src/__tests__/disk-usage.test.ts +150 -0
- package/src/__tests__/events-client-registration.test.ts +52 -0
- package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
- package/src/__tests__/file-write-tool.test.ts +4 -10
- package/src/__tests__/filing-service.test.ts +2 -20
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
- package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
- package/src/__tests__/heartbeat-service.test.ts +260 -11
- package/src/__tests__/host-app-control-proxy.test.ts +195 -25
- package/src/__tests__/host-bash-proxy.test.ts +227 -34
- package/src/__tests__/host-bash-routes.test.ts +178 -13
- package/src/__tests__/host-cu-proxy.test.ts +210 -3
- package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
- package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
- package/src/__tests__/host-file-proxy.test.ts +268 -6
- package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
- package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
- package/src/__tests__/http-user-message-parity.test.ts +107 -1
- package/src/__tests__/injector-chain.test.ts +36 -16
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
- package/src/__tests__/managed-profile-guard.test.ts +18 -0
- package/src/__tests__/mcp-abort-signal.test.ts +130 -0
- package/src/__tests__/memory-admin-recall.test.ts +3 -11
- package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
- package/src/__tests__/normalize-onboarding.test.ts +180 -0
- package/src/__tests__/notification-decision-fallback.test.ts +91 -0
- package/src/__tests__/notification-decision-strategy.test.ts +22 -0
- package/src/__tests__/oauth-cli.test.ts +121 -0
- package/src/__tests__/oauth-connect-routes.test.ts +316 -0
- package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
- package/src/__tests__/onboarding-persona-write.test.ts +308 -0
- package/src/__tests__/openai-provider.test.ts +45 -8
- package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
- package/src/__tests__/platform-callback-registration.test.ts +21 -4
- package/src/__tests__/platform.test.ts +2 -1
- package/src/__tests__/playbook-execution.test.ts +0 -43
- package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
- package/src/__tests__/provider-tool-name.test.ts +23 -0
- package/src/__tests__/relay-server.test.ts +60 -5
- package/src/__tests__/runtime-events-sse.test.ts +4 -8
- package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -1
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
- package/src/__tests__/secret-response-routing.test.ts +7 -5
- package/src/__tests__/server-history-render.test.ts +82 -0
- package/src/__tests__/skill-include-graph.test.ts +31 -0
- package/src/__tests__/skill-load-tool.test.ts +44 -16
- package/src/__tests__/skills.test.ts +39 -0
- package/src/__tests__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
- package/src/__tests__/tool-executor.test.ts +155 -0
- package/src/__tests__/twilio-validation.test.ts +2 -2
- package/src/__tests__/voice-session-bridge.test.ts +3 -0
- package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
- package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
- package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
- package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
- package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +78 -0
- package/src/agent/loop.ts +11 -0
- package/src/approvals/guardian-request-resolvers.ts +3 -32
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-constants.ts +5 -8
- package/src/calls/call-controller.ts +130 -67
- package/src/calls/call-conversation-messages.ts +46 -10
- package/src/calls/relay-server.ts +7 -1
- package/src/calls/voice-session-bridge.ts +1 -1
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
- package/src/cli/commands/bash.ts +35 -108
- package/src/cli/commands/contacts.ts +64 -25
- package/src/cli/commands/credentials.ts +56 -0
- package/src/cli/commands/memory-v2.ts +11 -10
- package/src/cli/commands/oauth/__tests__/connect.test.ts +401 -219
- package/src/cli/commands/oauth/connect.ts +124 -40
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
- package/src/cli/commands/platform/index.ts +16 -7
- package/src/cli/commands/status.ts +57 -0
- package/src/cli/program.ts +4 -2
- package/src/config/assistant-feature-flags.ts +13 -3
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
- package/src/config/env.ts +0 -8
- package/src/config/feature-flag-registry.json +13 -5
- package/src/config/loader.ts +199 -27
- package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -5
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +2 -0
- package/src/config/schemas/memory-lifecycle.ts +13 -0
- package/src/config/schemas/memory-v2.ts +76 -12
- package/src/config/schemas/platform.ts +43 -3
- package/src/config/schemas/services.ts +28 -0
- package/src/config/seed-inference-profiles.ts +230 -33
- package/src/contacts/contact-store.ts +0 -25
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
- package/src/daemon/assistant-attachments.ts +4 -4
- package/src/daemon/config-watcher.ts +85 -57
- package/src/daemon/conversation-agent-loop-handlers.ts +38 -0
- package/src/daemon/conversation-agent-loop.ts +183 -43
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +22 -10
- package/src/daemon/conversation-process.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +26 -0
- package/src/daemon/conversation-store.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +211 -29
- package/src/daemon/conversation-tool-setup.ts +66 -19
- package/src/daemon/conversation.ts +18 -23
- package/src/daemon/date-context.ts +71 -22
- package/src/daemon/disk-pressure-background-gate.ts +73 -0
- package/src/daemon/disk-pressure-guard.ts +343 -0
- package/src/daemon/disk-pressure-policy.ts +163 -0
- package/src/daemon/handlers/shared.ts +26 -1
- package/src/daemon/handlers/skills.ts +3 -4
- package/src/daemon/host-app-control-proxy.ts +137 -41
- package/src/daemon/host-bash-proxy.ts +47 -22
- package/src/daemon/host-browser-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +50 -4
- package/src/daemon/host-file-proxy.ts +44 -8
- package/src/daemon/host-transfer-proxy.ts +97 -6
- package/src/daemon/lifecycle.ts +167 -101
- package/src/daemon/meet-host-supervisor.ts +4 -4
- package/src/daemon/meet-manifest-loader.ts +0 -1
- package/src/daemon/memory-v2-startup.ts +66 -15
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +4 -0
- package/src/daemon/message-types/disk-pressure.ts +9 -0
- package/src/daemon/message-types/messages.ts +22 -1
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/tool-setup-types.ts +2 -2
- package/src/documents/document-store.ts +119 -0
- package/src/filing/filing-service.ts +29 -5
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
- package/src/heartbeat/heartbeat-run-store.ts +13 -0
- package/src/heartbeat/heartbeat-service.ts +205 -31
- package/src/home/feed-scheduler.ts +18 -0
- package/src/inbound/platform-callback-registration.ts +8 -15
- package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
- package/src/ipc/assistant-server.ts +149 -38
- package/src/ipc/gateway-client.ts +37 -3
- package/src/ipc/skill-server.ts +99 -42
- package/src/live-voice/live-voice-archive.ts +4 -4
- package/src/live-voice/protocol.ts +5 -7
- package/src/media/image-service.ts +1 -7
- package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +34 -51
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
- package/src/memory/admin.ts +5 -9
- package/src/memory/context-search/agent-runner.ts +19 -2
- package/src/memory/context-search/sources/conversations.ts +2 -11
- package/src/memory/context-search/sources/memory-v2.ts +1 -16
- package/src/memory/context-search/sources/memory.ts +2 -3
- package/src/memory/context-search/sources/pkb.ts +2 -3
- package/src/memory/context-search/types.ts +0 -1
- package/src/memory/conversation-crud.ts +4 -12
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-runtime-manager.ts +119 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +136 -82
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
- package/src/memory/graph/conversation-graph-memory.ts +72 -61
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +11 -67
- package/src/memory/graph/graph-search.ts +4 -24
- package/src/memory/graph/retriever.test.ts +12 -1
- package/src/memory/graph/retriever.ts +10 -15
- package/src/memory/graph/tool-handlers.ts +3 -4
- package/src/memory/graph/tools.ts +4 -4
- package/src/memory/indexer.ts +53 -45
- package/src/memory/job-handlers/backfill.ts +2 -11
- package/src/memory/job-handlers/cleanup.ts +43 -0
- package/src/memory/job-handlers/embedding.ts +6 -8
- package/src/memory/job-handlers/summarization.ts +2 -7
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
- package/src/memory/jobs/embed-concept-page.ts +223 -87
- package/src/memory/jobs-store.ts +48 -0
- package/src/memory/jobs-worker.ts +85 -43
- package/src/memory/memory-v2-activation-log-store.ts +32 -14
- package/src/memory/memory-v2-concept-frequency.ts +169 -0
- package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +7 -0
- package/src/memory/pkb/pkb-search.ts +4 -5
- package/src/memory/qdrant-client.ts +3 -13
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/search/semantic.ts +10 -72
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +346 -255
- package/src/memory/v2/__tests__/consolidation-job.test.ts +61 -40
- package/src/memory/v2/__tests__/injection.test.ts +297 -190
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +326 -9
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +113 -196
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/static-context.test.ts +77 -14
- package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
- package/src/memory/v2/activation.ts +149 -156
- package/src/memory/v2/consolidation-job.ts +69 -20
- package/src/memory/v2/injection.ts +75 -68
- package/src/memory/v2/page-store.ts +39 -0
- package/src/memory/v2/prompts/consolidation.ts +41 -1
- package/src/memory/v2/qdrant.ts +306 -46
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +77 -110
- package/src/memory/v2/skill-content.ts +4 -3
- package/src/memory/v2/skill-store.ts +82 -59
- package/src/memory/v2/static-context.ts +26 -8
- package/src/memory/v2/sweep-job.ts +5 -6
- package/src/memory/v2/types.ts +17 -10
- package/src/notifications/copy-composer.ts +47 -0
- package/src/notifications/decision-engine.ts +46 -0
- package/src/notifications/signal.ts +4 -0
- package/src/oauth/AGENTS.md +3 -1
- package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.test.ts +66 -1
- package/src/oauth/connection-resolver.ts +55 -1
- package/src/oauth/oauth-connect-state.ts +77 -0
- package/src/oauth/seed-providers.ts +58 -1
- package/src/permissions/gateway-threshold-reader.ts +116 -8
- package/src/permissions/prompter.ts +86 -96
- package/src/permissions/secret-prompter.ts +31 -31
- package/src/plugins/defaults/injectors.ts +36 -4
- package/src/plugins/defaults/memory-retrieval.ts +5 -6
- package/src/plugins/types.ts +7 -0
- package/src/proactive-artifact/aux-message-injector.ts +74 -0
- package/src/proactive-artifact/decision.test.ts +226 -0
- package/src/proactive-artifact/decision.ts +165 -0
- package/src/proactive-artifact/index.ts +7 -0
- package/src/proactive-artifact/job.test.ts +914 -0
- package/src/proactive-artifact/job.ts +366 -0
- package/src/proactive-artifact/message-copy.ts +58 -0
- package/src/proactive-artifact/trigger-state.test.ts +277 -0
- package/src/proactive-artifact/trigger-state.ts +119 -0
- package/src/prompts/normalize-onboarding.ts +80 -0
- package/src/prompts/persona-resolver.ts +101 -9
- package/src/prompts/system-prompt.ts +21 -7
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/prompts/templates/SOUL.md +13 -28
- package/src/providers/__tests__/retry-callsite.test.ts +222 -1
- package/src/providers/model-intents.ts +7 -0
- package/src/providers/openrouter/client.ts +8 -0
- package/src/providers/retry.ts +50 -0
- package/src/providers/types.ts +1 -0
- package/src/runtime/__tests__/agent-wake.test.ts +456 -3
- package/src/runtime/agent-wake.ts +238 -100
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/assistant-event.ts +0 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
- package/src/runtime/auth/route-policy.ts +15 -1
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/channel-approvals.ts +3 -2
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/pending-interactions.ts +27 -15
- package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
- package/src/runtime/routes/approval-routes.ts +7 -3
- package/src/runtime/routes/client-routes.ts +20 -2
- package/src/runtime/routes/consolidation-routes.ts +8 -9
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-query-routes.ts +44 -1
- package/src/runtime/routes/conversation-routes.ts +35 -26
- package/src/runtime/routes/debug-bash-routes.ts +165 -0
- package/src/runtime/routes/disk-pressure-routes.ts +121 -0
- package/src/runtime/routes/document-pdf-renderer.ts +6 -2
- package/src/runtime/routes/documents-routes.ts +2 -75
- package/src/runtime/routes/events-routes.ts +41 -9
- package/src/runtime/routes/filing-routes.ts +2 -3
- package/src/runtime/routes/host-bash-routes.ts +23 -3
- package/src/runtime/routes/host-cu-routes.ts +33 -6
- package/src/runtime/routes/host-file-routes.ts +32 -6
- package/src/runtime/routes/host-transfer-routes.ts +79 -16
- package/src/runtime/routes/identity-routes.ts +7 -138
- package/src/runtime/routes/inbound-message-handler.ts +77 -12
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/memory-item-routes.test.ts +37 -17
- package/src/runtime/routes/memory-item-routes.ts +5 -6
- package/src/runtime/routes/memory-v2-routes.ts +136 -17
- package/src/runtime/routes/oauth-connect-routes.ts +153 -0
- package/src/runtime/verification-outbound-actions.ts +4 -4
- package/src/schedule/run-script.ts +37 -5
- package/src/schedule/scheduler.ts +20 -1
- package/src/security/encrypted-store.ts +2 -0
- package/src/security/secure-keys.ts +55 -0
- package/src/skills/include-graph.ts +35 -13
- package/src/skills/remote-skill-policy.ts +4 -10
- package/src/subagent/index.ts +1 -7
- package/src/subagent/manager.ts +1 -15
- package/src/tasks/task-runner.ts +0 -1
- package/src/tasks/task-store.ts +0 -3
- package/src/tools/background-tool-registry.ts +17 -3
- package/src/tools/document/document-tool.ts +20 -0
- package/src/tools/executor.ts +18 -2
- package/src/tools/host-filesystem/edit.test.ts +151 -0
- package/src/tools/host-filesystem/edit.ts +43 -1
- package/src/tools/host-filesystem/read.test.ts +129 -0
- package/src/tools/host-filesystem/read.ts +43 -1
- package/src/tools/host-filesystem/transfer.test.ts +127 -2
- package/src/tools/host-filesystem/transfer.ts +56 -11
- package/src/tools/host-filesystem/write.test.ts +134 -0
- package/src/tools/host-filesystem/write.ts +43 -1
- package/src/tools/host-terminal/host-shell.ts +13 -6
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/register.test.ts +14 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/permission-checker.ts +15 -0
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/skills/load.ts +24 -20
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +31 -6
- package/src/tools/tool-name-aliases.ts +19 -0
- package/src/tools/types.ts +43 -3
- package/src/tts/provider-catalog.ts +3 -5
- package/src/util/disk-usage.ts +138 -0
- package/src/util/platform.ts +21 -11
- package/src/util/process-liveness.ts +26 -0
- package/src/workspace/heartbeat-service.ts +19 -0
- package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
- package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +14 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
- package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
- package/src/workspace/migrations/registry.ts +14 -0
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
- package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
- package/src/memory/v2/skill-qdrant.ts +0 -404
- package/src/signals/bash.ts +0 -198
|
@@ -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
|
+
});
|
|
@@ -95,6 +95,97 @@ describe("notification decision fallback copy", () => {
|
|
|
95
95
|
);
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
+
test("enforces guardian-facing popup copy for heartbeat alerts", async () => {
|
|
99
|
+
configuredProvider = { sendMessage: async () => ({}) };
|
|
100
|
+
extractedToolUse = {
|
|
101
|
+
input: {
|
|
102
|
+
shouldNotify: true,
|
|
103
|
+
selectedChannels: ["vellum"],
|
|
104
|
+
reasoningSummary: "Heartbeat found a useful follow-up.",
|
|
105
|
+
renderedCopy: {
|
|
106
|
+
vellum: {
|
|
107
|
+
title: "Heartbeat Follow-up",
|
|
108
|
+
body: "The daily tracker is ready; consider reminding the guardian to review it before the next check-in.",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
dedupeKey: "heartbeat:test",
|
|
112
|
+
confidence: 0.9,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const decision = await evaluateSignal(
|
|
117
|
+
makeSignal({
|
|
118
|
+
sourceEventName: "heartbeat.alert",
|
|
119
|
+
sourceChannel: "watcher",
|
|
120
|
+
contextPayload: {
|
|
121
|
+
summary:
|
|
122
|
+
"The daily tracker is ready; consider reminding the guardian to review it before the next check-in.",
|
|
123
|
+
conversationTitle: "Running Habit Tracking",
|
|
124
|
+
},
|
|
125
|
+
attentionHints: {
|
|
126
|
+
requiresAction: true,
|
|
127
|
+
urgency: "medium",
|
|
128
|
+
isAsyncBackground: true,
|
|
129
|
+
visibleInSourceNow: false,
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
132
|
+
["vellum"] as NotificationChannel[],
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
expect(decision.fallbackUsed).toBe(false);
|
|
136
|
+
expect(decision.renderedCopy.vellum?.title).toBe("Heartbeat Alert");
|
|
137
|
+
expect(decision.renderedCopy.vellum?.body).toBe(
|
|
138
|
+
"I found something worth your attention in a heartbeat check. Open the conversation for details.",
|
|
139
|
+
);
|
|
140
|
+
expect(decision.renderedCopy.vellum?.body).not.toContain(
|
|
141
|
+
"reminding the guardian",
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("keeps direct guardian-facing heartbeat copy", async () => {
|
|
146
|
+
configuredProvider = { sendMessage: async () => ({}) };
|
|
147
|
+
extractedToolUse = {
|
|
148
|
+
input: {
|
|
149
|
+
shouldNotify: true,
|
|
150
|
+
selectedChannels: ["vellum"],
|
|
151
|
+
reasoningSummary: "Heartbeat found a useful follow-up.",
|
|
152
|
+
renderedCopy: {
|
|
153
|
+
vellum: {
|
|
154
|
+
title: "Tracker Ready",
|
|
155
|
+
body: "Your daily tracker is ready. Review it before the next check-in.",
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
dedupeKey: "heartbeat:direct-copy-test",
|
|
159
|
+
confidence: 0.9,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const decision = await evaluateSignal(
|
|
164
|
+
makeSignal({
|
|
165
|
+
sourceEventName: "heartbeat.alert",
|
|
166
|
+
sourceChannel: "watcher",
|
|
167
|
+
contextPayload: {
|
|
168
|
+
summary:
|
|
169
|
+
"The daily tracker is ready; consider reminding the guardian to review it before the next check-in.",
|
|
170
|
+
conversationTitle: "Running Habit Tracking",
|
|
171
|
+
},
|
|
172
|
+
attentionHints: {
|
|
173
|
+
requiresAction: true,
|
|
174
|
+
urgency: "medium",
|
|
175
|
+
isAsyncBackground: true,
|
|
176
|
+
visibleInSourceNow: false,
|
|
177
|
+
},
|
|
178
|
+
}),
|
|
179
|
+
["vellum"] as NotificationChannel[],
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
expect(decision.fallbackUsed).toBe(false);
|
|
183
|
+
expect(decision.renderedCopy.vellum?.title).toBe("Tracker Ready");
|
|
184
|
+
expect(decision.renderedCopy.vellum?.body).toBe(
|
|
185
|
+
"Your daily tracker is ready. Review it before the next check-in.",
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
98
189
|
test("enforces free-text answer instructions for guardian.question when requestCode exists", async () => {
|
|
99
190
|
const signal = makeSignal({
|
|
100
191
|
contextPayload: {
|
|
@@ -373,6 +373,28 @@ describe("notification decision strategy", () => {
|
|
|
373
373
|
"A guardian question needs your attention",
|
|
374
374
|
);
|
|
375
375
|
});
|
|
376
|
+
|
|
377
|
+
test("heartbeat.alert fallback avoids intermediary-instruction popup copy", () => {
|
|
378
|
+
const signal = makeSignal({
|
|
379
|
+
sourceEventName: "heartbeat.alert",
|
|
380
|
+
contextPayload: {
|
|
381
|
+
summary:
|
|
382
|
+
"The daily tracker is ready; consider reminding the guardian to review it before the next check-in.",
|
|
383
|
+
conversationTitle: "Running Habit Tracking",
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const copy = composeFallbackCopy(signal, channels);
|
|
388
|
+
expect(copy.vellum).toBeDefined();
|
|
389
|
+
expect(copy.vellum!.title).toBe("Heartbeat Alert");
|
|
390
|
+
expect(copy.vellum!.body).toBe(
|
|
391
|
+
"I found something worth your attention in a heartbeat check. Open the conversation for details.",
|
|
392
|
+
);
|
|
393
|
+
expect(copy.vellum!.conversationSeedMessage).toContain(
|
|
394
|
+
"consider reminding the guardian",
|
|
395
|
+
);
|
|
396
|
+
expect(copy.telegram!.deliveryText).toBe(copy.vellum!.body);
|
|
397
|
+
});
|
|
376
398
|
});
|
|
377
399
|
|
|
378
400
|
// -- NotificationChannel type correctness ----------------------------------
|
|
@@ -62,6 +62,13 @@ let mockUpsertAppImpl:
|
|
|
62
62
|
let mockOrchestrateOAuthConnect: (
|
|
63
63
|
opts: Record<string, unknown>,
|
|
64
64
|
) => Promise<Record<string, unknown>>;
|
|
65
|
+
let mockCliIpcCall: (
|
|
66
|
+
operationId: string,
|
|
67
|
+
params?: Record<string, unknown>,
|
|
68
|
+
) => Promise<Record<string, unknown>> = async () => ({
|
|
69
|
+
ok: false,
|
|
70
|
+
error: "Could not connect to assistant daemon (test default)",
|
|
71
|
+
});
|
|
65
72
|
let mockGetAppByProviderAndClientId: (
|
|
66
73
|
provider: string,
|
|
67
74
|
clientId: string,
|
|
@@ -335,6 +342,15 @@ mock.module("../oauth/connect-orchestrator.js", () => ({
|
|
|
335
342
|
mockOrchestrateOAuthConnect(opts),
|
|
336
343
|
}));
|
|
337
344
|
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
// Mock cli-client (IPC) — used by `oauth connect` for daemon-orchestrated flow
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
|
|
349
|
+
mock.module("../ipc/cli-client.js", () => ({
|
|
350
|
+
cliIpcCall: (operationId: string, params?: Record<string, unknown>) =>
|
|
351
|
+
mockCliIpcCall(operationId, params),
|
|
352
|
+
}));
|
|
353
|
+
|
|
338
354
|
mock.module("../oauth/seed-providers.js", () => ({
|
|
339
355
|
SEEDED_PROVIDER_KEYS: new Set([
|
|
340
356
|
"google",
|
|
@@ -1220,6 +1236,111 @@ describe("assistant oauth connect managed mode — platform 401/403 errors", ()
|
|
|
1220
1236
|
});
|
|
1221
1237
|
});
|
|
1222
1238
|
|
|
1239
|
+
// ---------------------------------------------------------------------------
|
|
1240
|
+
// `assistant oauth connect <provider>` BYO mode — daemon-unreachable behavior.
|
|
1241
|
+
//
|
|
1242
|
+
// We deleted the in-process `orchestrateOAuthConnect` fallback (the same
|
|
1243
|
+
// pattern as the MCP CLI consolidation in #29484). When the daemon is
|
|
1244
|
+
// unreachable, the CLI must surface a clear error and exit 1 — never
|
|
1245
|
+
// silently fall back to in-process flow.
|
|
1246
|
+
// ---------------------------------------------------------------------------
|
|
1247
|
+
|
|
1248
|
+
describe("assistant oauth connect <provider> — daemon unreachable (BYO mode)", () => {
|
|
1249
|
+
beforeEach(() => {
|
|
1250
|
+
// BYO provider with a registered app and no managed-mode wiring.
|
|
1251
|
+
mockGetProvider = () => ({
|
|
1252
|
+
provider: "github",
|
|
1253
|
+
authorizeUrl: "https://github.com/login/oauth/authorize",
|
|
1254
|
+
tokenExchangeUrl: "https://github.com/login/oauth/access_token",
|
|
1255
|
+
defaultScopes: "[]",
|
|
1256
|
+
availableScopes: null,
|
|
1257
|
+
authorizeParams: null,
|
|
1258
|
+
managedServiceConfigKey: null,
|
|
1259
|
+
requiresClientSecret: false,
|
|
1260
|
+
createdAt: Date.now(),
|
|
1261
|
+
updatedAt: Date.now(),
|
|
1262
|
+
});
|
|
1263
|
+
mockGetMostRecentAppByProvider = () => ({
|
|
1264
|
+
provider: "github",
|
|
1265
|
+
clientId: "test-client-id",
|
|
1266
|
+
clientSecretCredentialPath: "oauth_app/github/test/client_secret",
|
|
1267
|
+
});
|
|
1268
|
+
mockGetSecureKey = () => "test-secret";
|
|
1269
|
+
mockGetConfig = () => ({ services: {} });
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
test("daemon connect-refused → exit 1 with 'Is the assistant running?'", async () => {
|
|
1273
|
+
mockCliIpcCall = async () => ({
|
|
1274
|
+
ok: false,
|
|
1275
|
+
error: "Could not connect to assistant daemon: ECONNREFUSED",
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
const { exitCode, stdout } = await runCli([
|
|
1279
|
+
"connect",
|
|
1280
|
+
"github",
|
|
1281
|
+
"--no-browser",
|
|
1282
|
+
"--json",
|
|
1283
|
+
]);
|
|
1284
|
+
|
|
1285
|
+
expect(exitCode).toBe(1);
|
|
1286
|
+
const parsed = JSON.parse(stdout);
|
|
1287
|
+
expect(parsed.ok).toBe(false);
|
|
1288
|
+
expect(parsed.error).toContain("Could not reach the assistant");
|
|
1289
|
+
expect(parsed.error).toContain("Is the assistant running?");
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
test("daemon route missing (Unknown method) → exit 1, never falls through to in-process", async () => {
|
|
1293
|
+
let orchestratorCalls = 0;
|
|
1294
|
+
mockOrchestrateOAuthConnect = async () => {
|
|
1295
|
+
orchestratorCalls++;
|
|
1296
|
+
return { success: true, deferred: false, grantedScopes: [] };
|
|
1297
|
+
};
|
|
1298
|
+
mockCliIpcCall = async () => ({
|
|
1299
|
+
ok: false,
|
|
1300
|
+
error: "Unknown method: internal_oauth_connect_start",
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
const { exitCode } = await runCli([
|
|
1304
|
+
"connect",
|
|
1305
|
+
"github",
|
|
1306
|
+
"--no-browser",
|
|
1307
|
+
"--json",
|
|
1308
|
+
]);
|
|
1309
|
+
|
|
1310
|
+
expect(exitCode).toBe(1);
|
|
1311
|
+
// Critical regression guard: the in-process `orchestrateOAuthConnect`
|
|
1312
|
+
// must NOT be invoked from the CLI. The daemon-orchestrated path is
|
|
1313
|
+
// the sole code path; this is the same invariant #29484 established
|
|
1314
|
+
// for the MCP CLI.
|
|
1315
|
+
expect(orchestratorCalls).toBe(0);
|
|
1316
|
+
});
|
|
1317
|
+
|
|
1318
|
+
test("daemon HTTP error (statusCode set) → surfaces error verbatim, no fallback", async () => {
|
|
1319
|
+
let orchestratorCalls = 0;
|
|
1320
|
+
mockOrchestrateOAuthConnect = async () => {
|
|
1321
|
+
orchestratorCalls++;
|
|
1322
|
+
return { success: true, deferred: false, grantedScopes: [] };
|
|
1323
|
+
};
|
|
1324
|
+
mockCliIpcCall = async () => ({
|
|
1325
|
+
ok: false,
|
|
1326
|
+
statusCode: 400,
|
|
1327
|
+
error: "service must be registered before connecting",
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
const { exitCode, stdout } = await runCli([
|
|
1331
|
+
"connect",
|
|
1332
|
+
"github",
|
|
1333
|
+
"--no-browser",
|
|
1334
|
+
"--json",
|
|
1335
|
+
]);
|
|
1336
|
+
|
|
1337
|
+
expect(exitCode).toBe(1);
|
|
1338
|
+
const parsed = JSON.parse(stdout);
|
|
1339
|
+
expect(parsed.error).toContain("service must be registered");
|
|
1340
|
+
expect(orchestratorCalls).toBe(0);
|
|
1341
|
+
});
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1223
1344
|
// ---------------------------------------------------------------------------
|
|
1224
1345
|
// requirePlatformClient — improved error messages
|
|
1225
1346
|
// ---------------------------------------------------------------------------
|