@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
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
1
|
+
import { beforeEach, describe, expect, it, mock } from "bun:test";
|
|
2
|
+
|
|
3
|
+
let providerRoutingSources: Record<string, "user-key" | "managed-proxy"> = {};
|
|
4
|
+
|
|
5
|
+
mock.module("../providers/registry.js", () => ({
|
|
6
|
+
getProviderRoutingSource: (provider: string) =>
|
|
7
|
+
providerRoutingSources[provider],
|
|
8
|
+
}));
|
|
2
9
|
|
|
3
10
|
import type { ErrorContext } from "../daemon/conversation-error.js";
|
|
4
11
|
import {
|
|
@@ -63,6 +70,10 @@ describe("isUserCancellation", () => {
|
|
|
63
70
|
describe("classifyConversationError", () => {
|
|
64
71
|
const baseCtx: ErrorContext = { phase: "agent_loop" };
|
|
65
72
|
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
providerRoutingSources = {};
|
|
75
|
+
});
|
|
76
|
+
|
|
66
77
|
describe("network errors", () => {
|
|
67
78
|
const cases = [
|
|
68
79
|
"ECONNREFUSED",
|
|
@@ -105,6 +116,52 @@ describe("classifyConversationError", () => {
|
|
|
105
116
|
expect(result.errorCategory).toBe("rate_limit");
|
|
106
117
|
});
|
|
107
118
|
}
|
|
119
|
+
|
|
120
|
+
it("classifies managed-proxy daily quota responses as MANAGED_USAGE_LIMIT", () => {
|
|
121
|
+
const err = new ProviderError(
|
|
122
|
+
'Anthropic API error (429): 429 {"code":"daily_quota_exceeded","detail":"You\'ve reached your usage limit for today. You\'ve made 1000 requests, but your current plan allows 1000 per day.","provider":"anthropic"}',
|
|
123
|
+
"anthropic",
|
|
124
|
+
429,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const result = classifyConversationError(err, baseCtx);
|
|
128
|
+
|
|
129
|
+
expect(result.code).toBe("MANAGED_USAGE_LIMIT");
|
|
130
|
+
expect(result.retryable).toBe(true);
|
|
131
|
+
expect(result.userMessage).toContain("Vellum managed inference");
|
|
132
|
+
expect(result.userMessage).toContain("not an AI provider outage");
|
|
133
|
+
expect(result.errorCategory).toBe("managed_usage_limit");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("classifies managed-proxy routed 429s as MANAGED_USAGE_LIMIT", () => {
|
|
137
|
+
providerRoutingSources.anthropic = "managed-proxy";
|
|
138
|
+
const err = new ProviderError(
|
|
139
|
+
"Anthropic API error (429): Too many requests",
|
|
140
|
+
"anthropic",
|
|
141
|
+
429,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const result = classifyConversationError(err, baseCtx);
|
|
145
|
+
|
|
146
|
+
expect(result.code).toBe("MANAGED_USAGE_LIMIT");
|
|
147
|
+
expect(result.userMessage).toContain("Vellum managed inference");
|
|
148
|
+
expect(result.errorCategory).toBe("managed_usage_limit");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("keeps provider copy for direct provider 429s", () => {
|
|
152
|
+
providerRoutingSources.anthropic = "user-key";
|
|
153
|
+
const err = new ProviderError(
|
|
154
|
+
"Anthropic API error (429): Too many requests",
|
|
155
|
+
"anthropic",
|
|
156
|
+
429,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const result = classifyConversationError(err, baseCtx);
|
|
160
|
+
|
|
161
|
+
expect(result.code).toBe("PROVIDER_RATE_LIMIT");
|
|
162
|
+
expect(result.userMessage).toContain("AI provider");
|
|
163
|
+
expect(result.errorCategory).toBe("rate_limit");
|
|
164
|
+
});
|
|
108
165
|
});
|
|
109
166
|
|
|
110
167
|
describe("provider overloaded errors", () => {
|
|
@@ -509,11 +566,11 @@ describe("classifyConversationError", () => {
|
|
|
509
566
|
expect(result.errorCategory).toBe("provider_not_configured");
|
|
510
567
|
});
|
|
511
568
|
|
|
512
|
-
it("classifies ProviderError with 402 as
|
|
569
|
+
it("classifies direct ProviderError with 402 as provider_billing (non-retryable)", () => {
|
|
513
570
|
const err = new ProviderError("Payment Required", "anthropic", 402);
|
|
514
571
|
const result = classifyConversationError(err, baseCtx);
|
|
515
572
|
expect(result.code).toBe("PROVIDER_BILLING");
|
|
516
|
-
expect(result.errorCategory).toBe("
|
|
573
|
+
expect(result.errorCategory).toBe("provider_billing");
|
|
517
574
|
expect(result.retryable).toBe(false);
|
|
518
575
|
});
|
|
519
576
|
|
|
@@ -560,6 +617,96 @@ describe("classifyConversationError", () => {
|
|
|
560
617
|
});
|
|
561
618
|
});
|
|
562
619
|
|
|
620
|
+
describe("OpenRouter billing classification", () => {
|
|
621
|
+
it("keeps managed-proxy OpenRouter 402 responses as credits_exhausted", () => {
|
|
622
|
+
providerRoutingSources.openrouter = "managed-proxy";
|
|
623
|
+
const err = new ProviderError(
|
|
624
|
+
"OpenRouter API error (402): Payment Required",
|
|
625
|
+
"openrouter",
|
|
626
|
+
402,
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
const result = classifyConversationError(err, baseCtx);
|
|
630
|
+
|
|
631
|
+
expect(result.code).toBe("PROVIDER_BILLING");
|
|
632
|
+
expect(result.errorCategory).toBe("credits_exhausted");
|
|
633
|
+
expect(result.retryable).toBe(false);
|
|
634
|
+
expect(result.userMessage).toContain("Add funds");
|
|
635
|
+
expect(result.userMessage).toContain("assistant");
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
it("classifies direct Anthropic, OpenAI, and OpenRouter 402 responses as provider_billing", () => {
|
|
639
|
+
providerRoutingSources.anthropic = "user-key";
|
|
640
|
+
providerRoutingSources.openai = "user-key";
|
|
641
|
+
providerRoutingSources.openrouter = "user-key";
|
|
642
|
+
|
|
643
|
+
for (const provider of ["anthropic", "openai", "openrouter"]) {
|
|
644
|
+
const err = new ProviderError(
|
|
645
|
+
`${provider} API error (402): Payment Required`,
|
|
646
|
+
provider,
|
|
647
|
+
402,
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
const result = classifyConversationError(err, baseCtx);
|
|
651
|
+
|
|
652
|
+
expect(result.code).toBe("PROVIDER_BILLING");
|
|
653
|
+
expect(result.errorCategory).toBe("provider_billing");
|
|
654
|
+
expect(result.retryable).toBe(false);
|
|
655
|
+
expect(result.userMessage).toContain("provider");
|
|
656
|
+
expect(result.userMessage).toContain("Settings");
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it("classifies OpenRouter 400 credit-limit messages as provider_billing", () => {
|
|
661
|
+
const cases = [
|
|
662
|
+
"OpenRouter API error (400): This request requires more credits",
|
|
663
|
+
"OpenRouter API error (400): You can only afford 1000 tokens",
|
|
664
|
+
];
|
|
665
|
+
|
|
666
|
+
for (const message of cases) {
|
|
667
|
+
const err = new ProviderError(message, "openrouter", 400);
|
|
668
|
+
|
|
669
|
+
const result = classifyConversationError(err, baseCtx);
|
|
670
|
+
|
|
671
|
+
expect(result.code).toBe("PROVIDER_BILLING");
|
|
672
|
+
expect(result.errorCategory).toBe("provider_billing");
|
|
673
|
+
expect(result.retryable).toBe(false);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it("classifies managed-proxy OpenRouter insufficient_balance bodies as credits_exhausted", () => {
|
|
678
|
+
providerRoutingSources.openrouter = "managed-proxy";
|
|
679
|
+
const err = new ProviderError(
|
|
680
|
+
'OpenRouter API error (402): {"code":"insufficient_balance","detail":"Managed balance exhausted"}',
|
|
681
|
+
"openrouter",
|
|
682
|
+
402,
|
|
683
|
+
);
|
|
684
|
+
|
|
685
|
+
const result = classifyConversationError(err, baseCtx);
|
|
686
|
+
|
|
687
|
+
expect(result.code).toBe("PROVIDER_BILLING");
|
|
688
|
+
expect(result.errorCategory).toBe("credits_exhausted");
|
|
689
|
+
expect(result.retryable).toBe(false);
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
it("classifies direct OpenRouter insufficient_balance bodies as provider_billing", () => {
|
|
693
|
+
providerRoutingSources.openrouter = "user-key";
|
|
694
|
+
const err = new ProviderError(
|
|
695
|
+
'OpenRouter API error (402): {"code":"insufficient_balance","detail":"Provider account balance exhausted"}',
|
|
696
|
+
"openrouter",
|
|
697
|
+
402,
|
|
698
|
+
);
|
|
699
|
+
|
|
700
|
+
const result = classifyConversationError(err, baseCtx);
|
|
701
|
+
|
|
702
|
+
expect(result.code).toBe("PROVIDER_BILLING");
|
|
703
|
+
expect(result.errorCategory).toBe("provider_billing");
|
|
704
|
+
expect(result.retryable).toBe(false);
|
|
705
|
+
expect(result.userMessage).toContain("provider");
|
|
706
|
+
expect(result.userMessage).toContain("Settings");
|
|
707
|
+
});
|
|
708
|
+
});
|
|
709
|
+
|
|
563
710
|
describe("debug detail truncation", () => {
|
|
564
711
|
it("truncates debugDetails longer than 4000 chars", () => {
|
|
565
712
|
const longMsg = "x".repeat(5000);
|
|
@@ -465,7 +465,7 @@ describe("End-to-end session creation benchmark", () => {
|
|
|
465
465
|
timings.push(performance.now() - start);
|
|
466
466
|
|
|
467
467
|
if (i === 0) {
|
|
468
|
-
expect(session.eventBus.
|
|
468
|
+
expect(session.eventBus.anyListenerCount()).toBeGreaterThan(0);
|
|
469
469
|
}
|
|
470
470
|
session.dispose();
|
|
471
471
|
}
|
|
@@ -77,6 +77,7 @@ mock.module("../config/loader.js", () => ({
|
|
|
77
77
|
pricingOverrides: [],
|
|
78
78
|
},
|
|
79
79
|
rateLimit: { maxRequestsPerMinute: 0 },
|
|
80
|
+
memory: { v2: { enabled: false } },
|
|
80
81
|
daemon: {
|
|
81
82
|
startupSocketWaitMs: 5000,
|
|
82
83
|
stopTimeoutMs: 5000,
|
|
@@ -382,4 +383,41 @@ describe("processMessage callSite threading", () => {
|
|
|
382
383
|
expect(captured.resolvedMaxTokens).toBe(1234);
|
|
383
384
|
expect(captured.resolvedHasMaxTokens).toBe(true);
|
|
384
385
|
});
|
|
386
|
+
|
|
387
|
+
test("applies clientTimezone in the create and reuse transport metadata path", async () => {
|
|
388
|
+
mockConversation = {
|
|
389
|
+
id: "conv-store-client-timezone",
|
|
390
|
+
contextSummary: null,
|
|
391
|
+
contextCompactedMessageCount: 0,
|
|
392
|
+
totalInputTokens: 0,
|
|
393
|
+
totalOutputTokens: 0,
|
|
394
|
+
totalEstimatedCost: 0,
|
|
395
|
+
};
|
|
396
|
+
mockDbMessages = [];
|
|
397
|
+
clearCaptured();
|
|
398
|
+
clearAllActiveConversations();
|
|
399
|
+
|
|
400
|
+
const conversation = await getOrCreateConversation(
|
|
401
|
+
"conv-store-client-timezone",
|
|
402
|
+
{
|
|
403
|
+
transport: {
|
|
404
|
+
channelId: "vellum",
|
|
405
|
+
interfaceId: "macos",
|
|
406
|
+
clientTimezone: "america/new_york",
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
expect(conversation.clientTimezone).toBe("America/New_York");
|
|
412
|
+
|
|
413
|
+
await getOrCreateConversation("conv-store-client-timezone", {
|
|
414
|
+
transport: {
|
|
415
|
+
channelId: "vellum",
|
|
416
|
+
interfaceId: "ios",
|
|
417
|
+
clientTimezone: "europe/london",
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
expect(conversation.clientTimezone).toBe("Europe/London");
|
|
422
|
+
});
|
|
385
423
|
});
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
|
+
// This test exercises v1 PKB injection. `config.memory.v2.enabled` (default
|
|
4
|
+
// `true`) makes the PKB injector go silent — force it off here so the v1
|
|
5
|
+
// injection chain assertions stay meaningful.
|
|
6
|
+
const realLoaderForAssemblyTest = await import("../config/loader.js");
|
|
7
|
+
const realGetConfigForAssemblyTest = realLoaderForAssemblyTest.getConfig;
|
|
8
|
+
mock.module("../config/loader.js", () => ({
|
|
9
|
+
...realLoaderForAssemblyTest,
|
|
10
|
+
getConfig: () => {
|
|
11
|
+
const real = realGetConfigForAssemblyTest();
|
|
12
|
+
return {
|
|
13
|
+
...real,
|
|
14
|
+
memory: { ...real.memory, v2: { ...real.memory.v2, enabled: false } },
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
3
19
|
// PKB search is mocked so the reminder-hints tests can assert behavior
|
|
4
20
|
// without standing up Qdrant. The mock returns whatever is staged in
|
|
5
21
|
// `pkbSearchResults` / `pkbSearchThrows` for the enclosing test.
|
|
@@ -1430,6 +1446,64 @@ describe("buildUnifiedTurnContextBlock", () => {
|
|
|
1430
1446
|
expect(text).toContain("time_since_last_message: yesterday");
|
|
1431
1447
|
expect(text).toContain("canonical_actor_identity: user-1");
|
|
1432
1448
|
});
|
|
1449
|
+
|
|
1450
|
+
test("timezone mismatch: omits extra lines when there is no manual override", () => {
|
|
1451
|
+
const text = buildUnifiedTurnContextBlock({
|
|
1452
|
+
timestamp: "2026-04-02 (Thursday) 08:00:00 -04:00 (America/New_York)",
|
|
1453
|
+
clientTimezone: "America/New_York",
|
|
1454
|
+
detectedTimezone: "America/New_York",
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
expect(text).toContain(
|
|
1458
|
+
"current_time: 2026-04-02 (Thursday) 08:00:00 -04:00 (America/New_York)",
|
|
1459
|
+
);
|
|
1460
|
+
expect(text).not.toContain("configured_user_timezone:");
|
|
1461
|
+
expect(text).not.toContain("client_device_timezone:");
|
|
1462
|
+
expect(text).not.toContain("timezone_update_available:");
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
test("timezone mismatch: omits extra lines when configured and client timezone match", () => {
|
|
1466
|
+
const text = buildUnifiedTurnContextBlock({
|
|
1467
|
+
timestamp: "2026-04-02 (Thursday) 08:00:00 -04:00 (America/New_York)",
|
|
1468
|
+
configuredUserTimezone: "America/New_York",
|
|
1469
|
+
clientTimezone: "America/New_York",
|
|
1470
|
+
detectedTimezone: "America/New_York",
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
expect(text).not.toContain("configured_user_timezone:");
|
|
1474
|
+
expect(text).not.toContain("client_device_timezone:");
|
|
1475
|
+
expect(text).not.toContain("timezone_update_available:");
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
test("timezone mismatch: emits configured and client device timezone when they differ", () => {
|
|
1479
|
+
const text = buildUnifiedTurnContextBlock({
|
|
1480
|
+
timestamp: "2026-04-02 (Thursday) 08:00:00 -04:00 (America/New_York)",
|
|
1481
|
+
configuredUserTimezone: "America/New_York",
|
|
1482
|
+
clientTimezone: "America/Los_Angeles",
|
|
1483
|
+
detectedTimezone: "America/Los_Angeles",
|
|
1484
|
+
});
|
|
1485
|
+
|
|
1486
|
+
expect(text).toContain("configured_user_timezone: America/New_York");
|
|
1487
|
+
expect(text).toContain("client_device_timezone: America/Los_Angeles");
|
|
1488
|
+
});
|
|
1489
|
+
|
|
1490
|
+
test("timezone mismatch: emits CLI affordance only in mismatch case", () => {
|
|
1491
|
+
const mismatchText = buildUnifiedTurnContextBlock({
|
|
1492
|
+
timestamp: "2026-04-02 (Thursday) 08:00:00 -04:00 (America/New_York)",
|
|
1493
|
+
configuredUserTimezone: "America/New_York",
|
|
1494
|
+
clientTimezone: "America/Los_Angeles",
|
|
1495
|
+
});
|
|
1496
|
+
const matchingText = buildUnifiedTurnContextBlock({
|
|
1497
|
+
timestamp: "2026-04-02 (Thursday) 08:00:00 -04:00 (America/New_York)",
|
|
1498
|
+
configuredUserTimezone: "America/New_York",
|
|
1499
|
+
clientTimezone: "America/New_York",
|
|
1500
|
+
});
|
|
1501
|
+
|
|
1502
|
+
expect(mismatchText).toContain(
|
|
1503
|
+
'timezone_update_available: after explicit user confirmation, persist client_device_timezone with `assistant config set ui.userTimezone "America/Los_Angeles"`',
|
|
1504
|
+
);
|
|
1505
|
+
expect(matchingText).not.toContain("timezone_update_available:");
|
|
1506
|
+
});
|
|
1433
1507
|
});
|
|
1434
1508
|
|
|
1435
1509
|
// ---------------------------------------------------------------------------
|
|
@@ -294,7 +294,6 @@ describe("per-conversation speed override", () => {
|
|
|
294
294
|
4096,
|
|
295
295
|
makeSendToClient(),
|
|
296
296
|
"/tmp",
|
|
297
|
-
undefined, // memoryPolicy
|
|
298
297
|
undefined, // sharedCesClient
|
|
299
298
|
"standard", // speedOverride
|
|
300
299
|
);
|
|
@@ -315,7 +314,6 @@ describe("per-conversation speed override", () => {
|
|
|
315
314
|
4096,
|
|
316
315
|
makeSendToClient(),
|
|
317
316
|
"/tmp",
|
|
318
|
-
undefined, // memoryPolicy
|
|
319
317
|
undefined, // sharedCesClient
|
|
320
318
|
// no speedOverride — should fall back to global config "fast"
|
|
321
319
|
);
|
|
@@ -335,7 +333,6 @@ describe("per-conversation speed override", () => {
|
|
|
335
333
|
4096,
|
|
336
334
|
makeSendToClient(),
|
|
337
335
|
"/tmp",
|
|
338
|
-
undefined, // memoryPolicy
|
|
339
336
|
undefined, // sharedCesClient
|
|
340
337
|
"fast", // speedOverride
|
|
341
338
|
);
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
createConversation,
|
|
20
20
|
deleteLastExchange,
|
|
21
21
|
getConversation,
|
|
22
|
-
getConversationMemoryScopeId,
|
|
23
22
|
getMessages,
|
|
24
23
|
} from "../memory/conversation-crud.js";
|
|
25
24
|
import { isLastUserMessageToolResult } from "../memory/conversation-queries.js";
|
|
@@ -454,23 +453,6 @@ describe("createConversation with conversation type option", () => {
|
|
|
454
453
|
});
|
|
455
454
|
});
|
|
456
455
|
|
|
457
|
-
describe("conversation metadata read helpers", () => {
|
|
458
|
-
beforeEach(() => {
|
|
459
|
-
const db = getDb();
|
|
460
|
-
db.run(`DELETE FROM messages`);
|
|
461
|
-
db.run(`DELETE FROM conversations`);
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
test("getConversationMemoryScopeId returns default for standard conversation", () => {
|
|
465
|
-
const conv = createConversation("test");
|
|
466
|
-
expect(getConversationMemoryScopeId(conv.id)).toBe("default");
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
test("getConversationMemoryScopeId returns default for missing conversation", () => {
|
|
470
|
-
expect(getConversationMemoryScopeId("nonexistent-id")).toBe("default");
|
|
471
|
-
});
|
|
472
|
-
});
|
|
473
|
-
|
|
474
456
|
// ---------------------------------------------------------------------------
|
|
475
457
|
// Baseline: attachment reuse across conversations
|
|
476
458
|
// ---------------------------------------------------------------------------
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
4
|
+
|
|
5
|
+
let broadcastedMessages: ServerMessage[] = [];
|
|
6
|
+
const realEventHub = await import("../runtime/assistant-event-hub.js");
|
|
7
|
+
mock.module("../runtime/assistant-event-hub.js", () => ({
|
|
8
|
+
...realEventHub,
|
|
9
|
+
broadcastMessage: (msg: ServerMessage) => broadcastedMessages.push(msg),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
const { createSurfaceMutex, handleSurfaceAction, surfaceProxyResolver } =
|
|
13
|
+
await import("../daemon/conversation-surfaces.js");
|
|
14
|
+
|
|
15
|
+
import type { SurfaceConversationContext } from "../daemon/conversation-surfaces.js";
|
|
9
16
|
import type {
|
|
10
|
-
ServerMessage,
|
|
11
17
|
SurfaceData,
|
|
12
18
|
SurfaceType,
|
|
13
19
|
UiSurfaceShow,
|
|
@@ -81,6 +87,10 @@ function makeContext(sent: ServerMessage[] = []): SurfaceConversationContext & {
|
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
describe("surface action delivery to assistant", () => {
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
broadcastedMessages = [];
|
|
92
|
+
});
|
|
93
|
+
|
|
84
94
|
test("table action button click triggers processMessage with action content", async () => {
|
|
85
95
|
const sent: ServerMessage[] = [];
|
|
86
96
|
const ctx = makeContext(sent);
|
|
@@ -199,4 +209,155 @@ describe("surface action delivery to assistant", () => {
|
|
|
199
209
|
"[User action on app:",
|
|
200
210
|
);
|
|
201
211
|
});
|
|
212
|
+
|
|
213
|
+
test("confirmation surface broadcasts ui_surface_complete on action", async () => {
|
|
214
|
+
const sent: ServerMessage[] = [];
|
|
215
|
+
const ctx = makeContext(sent);
|
|
216
|
+
|
|
217
|
+
const showResult = await surfaceProxyResolver(ctx, "ui_show", {
|
|
218
|
+
surface_type: "confirmation",
|
|
219
|
+
title: "Delete files?",
|
|
220
|
+
data: {
|
|
221
|
+
message: "This will permanently delete 3 files.",
|
|
222
|
+
confirmLabel: "Delete",
|
|
223
|
+
cancelLabel: "Keep",
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
expect(showResult.isError).toBe(false);
|
|
228
|
+
expect(showResult.yieldToUser).toBe(true);
|
|
229
|
+
|
|
230
|
+
const showMessage = sent.find(
|
|
231
|
+
(msg): msg is UiSurfaceShow => msg.type === "ui_surface_show",
|
|
232
|
+
) as UiSurfaceShow;
|
|
233
|
+
const surfaceId = showMessage.surfaceId;
|
|
234
|
+
expect(ctx.pendingSurfaceActions.has(surfaceId)).toBe(true);
|
|
235
|
+
|
|
236
|
+
await handleSurfaceAction(ctx, surfaceId, "confirm", {});
|
|
237
|
+
|
|
238
|
+
const completeMsg = broadcastedMessages.find(
|
|
239
|
+
(m) =>
|
|
240
|
+
(m as unknown as Record<string, unknown>).type ===
|
|
241
|
+
"ui_surface_complete" &&
|
|
242
|
+
(m as unknown as Record<string, unknown>).surfaceId === surfaceId,
|
|
243
|
+
) as unknown as Record<string, unknown> | undefined;
|
|
244
|
+
expect(completeMsg).toBeDefined();
|
|
245
|
+
expect(completeMsg?.conversationId).toBe("conv-1");
|
|
246
|
+
expect(completeMsg?.summary).toContain("Delete");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("file_upload surface broadcasts ui_surface_complete on action", async () => {
|
|
250
|
+
const sent: ServerMessage[] = [];
|
|
251
|
+
const ctx = makeContext(sent);
|
|
252
|
+
|
|
253
|
+
const showResult = await surfaceProxyResolver(ctx, "ui_show", {
|
|
254
|
+
surface_type: "file_upload",
|
|
255
|
+
title: "Upload documents",
|
|
256
|
+
data: { accept: ".pdf,.docx", maxFiles: 5 },
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(showResult.isError).toBe(false);
|
|
260
|
+
expect(showResult.yieldToUser).toBe(true);
|
|
261
|
+
|
|
262
|
+
const showMessage = sent.find(
|
|
263
|
+
(msg): msg is UiSurfaceShow => msg.type === "ui_surface_show",
|
|
264
|
+
) as UiSurfaceShow;
|
|
265
|
+
const surfaceId = showMessage.surfaceId;
|
|
266
|
+
expect(ctx.pendingSurfaceActions.has(surfaceId)).toBe(true);
|
|
267
|
+
|
|
268
|
+
await handleSurfaceAction(ctx, surfaceId, "submit", {
|
|
269
|
+
files: [
|
|
270
|
+
{
|
|
271
|
+
filename: "doc.pdf",
|
|
272
|
+
mimeType: "application/pdf",
|
|
273
|
+
data: "base64encodedcontent",
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const completeMsg = broadcastedMessages.find(
|
|
279
|
+
(m) =>
|
|
280
|
+
(m as unknown as Record<string, unknown>).type ===
|
|
281
|
+
"ui_surface_complete" &&
|
|
282
|
+
(m as unknown as Record<string, unknown>).surfaceId === surfaceId,
|
|
283
|
+
) as unknown as Record<string, unknown> | undefined;
|
|
284
|
+
expect(completeMsg).toBeDefined();
|
|
285
|
+
expect(completeMsg?.conversationId).toBe("conv-1");
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("file_upload completion event does not include base64 file blobs", async () => {
|
|
289
|
+
const sent: ServerMessage[] = [];
|
|
290
|
+
const ctx = makeContext(sent);
|
|
291
|
+
|
|
292
|
+
await surfaceProxyResolver(ctx, "ui_show", {
|
|
293
|
+
surface_type: "file_upload",
|
|
294
|
+
title: "Upload",
|
|
295
|
+
data: { accept: "*" },
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const showMessage = sent.find(
|
|
299
|
+
(msg): msg is UiSurfaceShow => msg.type === "ui_surface_show",
|
|
300
|
+
) as UiSurfaceShow;
|
|
301
|
+
const surfaceId = showMessage.surfaceId;
|
|
302
|
+
|
|
303
|
+
const largeBase64 = "A".repeat(10_000);
|
|
304
|
+
await handleSurfaceAction(ctx, surfaceId, "submit", {
|
|
305
|
+
files: [
|
|
306
|
+
{
|
|
307
|
+
filename: "big.pdf",
|
|
308
|
+
mimeType: "application/pdf",
|
|
309
|
+
data: largeBase64,
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const completeMsg = broadcastedMessages.find(
|
|
315
|
+
(m) =>
|
|
316
|
+
(m as unknown as Record<string, unknown>).type ===
|
|
317
|
+
"ui_surface_complete" &&
|
|
318
|
+
(m as unknown as Record<string, unknown>).surfaceId === surfaceId,
|
|
319
|
+
) as unknown as Record<string, unknown> | undefined;
|
|
320
|
+
expect(completeMsg).toBeDefined();
|
|
321
|
+
|
|
322
|
+
const submittedData = completeMsg?.submittedData as
|
|
323
|
+
| Record<string, unknown>
|
|
324
|
+
| undefined;
|
|
325
|
+
// The files array with base64 blobs should be stripped from the
|
|
326
|
+
// completion event — only the sanitized payload (without files) is sent.
|
|
327
|
+
expect(submittedData?.files).toBeUndefined();
|
|
328
|
+
// The raw base64 content should not appear anywhere in the event
|
|
329
|
+
expect(JSON.stringify(completeMsg)).not.toContain(largeBase64);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test("table surface does NOT broadcast ui_surface_complete (not one-shot)", async () => {
|
|
333
|
+
const sent: ServerMessage[] = [];
|
|
334
|
+
const ctx = makeContext(sent);
|
|
335
|
+
|
|
336
|
+
await surfaceProxyResolver(ctx, "ui_show", {
|
|
337
|
+
surface_type: "table",
|
|
338
|
+
title: "Items",
|
|
339
|
+
data: {
|
|
340
|
+
columns: [{ id: "name", label: "Name" }],
|
|
341
|
+
rows: [{ id: "r1", cells: { name: "Item 1" } }],
|
|
342
|
+
},
|
|
343
|
+
actions: [{ id: "select", label: "Select" }],
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const showMessage = sent.find(
|
|
347
|
+
(msg): msg is UiSurfaceShow => msg.type === "ui_surface_show",
|
|
348
|
+
) as UiSurfaceShow;
|
|
349
|
+
const surfaceId = showMessage.surfaceId;
|
|
350
|
+
|
|
351
|
+
broadcastedMessages = [];
|
|
352
|
+
await handleSurfaceAction(ctx, surfaceId, "select", {
|
|
353
|
+
selectedIds: ["r1"],
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const completeMsg = broadcastedMessages.find(
|
|
357
|
+
(m) =>
|
|
358
|
+
(m as unknown as Record<string, unknown>).type ===
|
|
359
|
+
"ui_surface_complete",
|
|
360
|
+
);
|
|
361
|
+
expect(completeMsg).toBeUndefined();
|
|
362
|
+
});
|
|
202
363
|
});
|
|
@@ -33,8 +33,11 @@ mock.module("../runtime/pending-interactions.js", () => ({
|
|
|
33
33
|
|
|
34
34
|
const { surfaceProxyResolver } =
|
|
35
35
|
await import("../daemon/conversation-surfaces.js");
|
|
36
|
-
const {
|
|
37
|
-
|
|
36
|
+
const {
|
|
37
|
+
HostAppControlProxy,
|
|
38
|
+
_resetActiveAppControlSession,
|
|
39
|
+
_setActiveAppControlSession,
|
|
40
|
+
} = await import("../daemon/host-app-control-proxy.js");
|
|
38
41
|
type SurfaceConversationContext =
|
|
39
42
|
import("../daemon/conversation-surfaces.js").SurfaceConversationContext;
|
|
40
43
|
|
|
@@ -83,11 +86,11 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
|
|
|
83
86
|
beforeEach(() => {
|
|
84
87
|
sentMessages.length = 0;
|
|
85
88
|
mockHasClient = true;
|
|
86
|
-
|
|
89
|
+
_resetActiveAppControlSession();
|
|
87
90
|
});
|
|
88
91
|
|
|
89
92
|
afterEach(() => {
|
|
90
|
-
|
|
93
|
+
_resetActiveAppControlSession();
|
|
91
94
|
});
|
|
92
95
|
|
|
93
96
|
// -------------------------------------------------------------------------
|
|
@@ -148,6 +151,10 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
|
|
|
148
151
|
test("app_control_observe routes through proxy and returns observation", async () => {
|
|
149
152
|
const proxy = new HostAppControlProxy("conv-1");
|
|
150
153
|
const ctx = buildMockContext(proxy, "conv-1");
|
|
154
|
+
_setActiveAppControlSession({
|
|
155
|
+
conversationId: "conv-1",
|
|
156
|
+
app: "com.example.editor",
|
|
157
|
+
});
|
|
151
158
|
|
|
152
159
|
const resultPromise = surfaceProxyResolver(ctx, "app_control_observe", {
|
|
153
160
|
tool: "observe",
|
|
@@ -254,6 +261,10 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
|
|
|
254
261
|
test("injects `tool` derived from toolName when the agent input omits it", async () => {
|
|
255
262
|
const proxy = new HostAppControlProxy("conv-1");
|
|
256
263
|
const ctx = buildMockContext(proxy, "conv-1");
|
|
264
|
+
_setActiveAppControlSession({
|
|
265
|
+
conversationId: "conv-1",
|
|
266
|
+
app: "com.example.editor",
|
|
267
|
+
});
|
|
257
268
|
|
|
258
269
|
// Agent inputs do not carry the discriminator — the resolver has to
|
|
259
270
|
// synthesize it from `toolName` ("app_control_observe" → "observe")
|