@vellumai/assistant 0.8.4 → 0.8.5
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 +2 -2
- package/docs/browser-use-architecture-phase2.md +1 -1
- package/knip.json +2 -1
- package/openapi.yaml +809 -11
- package/package.json +1 -1
- package/src/__tests__/anthropic-provider.test.ts +34 -37
- package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
- package/src/__tests__/audit-log-rotation.test.ts +70 -16
- package/src/__tests__/background-workers-disk-pressure.test.ts +3 -3
- package/src/__tests__/btw-routes.test.ts +2 -3
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
- package/src/__tests__/channel-guardian.test.ts +3 -3
- package/src/__tests__/checker.test.ts +6 -15
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
- package/src/__tests__/computer-use-tools.test.ts +2 -4
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +197 -2
- package/src/__tests__/conversation-agent-loop.test.ts +163 -122
- package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
- package/src/__tests__/conversation-clear-safety.test.ts +25 -25
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
- package/src/__tests__/conversation-error.test.ts +31 -0
- package/src/__tests__/conversation-fork-crud.test.ts +178 -15
- package/src/__tests__/conversation-lifecycle.test.ts +52 -11
- package/src/__tests__/{conversation-load-cleaned-at.test.ts → conversation-load-history-stripped.test.ts} +13 -13
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
- package/src/__tests__/conversation-skill-tools.test.ts +2 -5
- package/src/__tests__/conversation-store.test.ts +1 -1
- package/src/__tests__/conversation-sync-tags.test.ts +99 -32
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
- package/src/__tests__/credential-execution-tools.test.ts +6 -6
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +2 -2
- package/src/__tests__/dynamic-page-surface.test.ts +2 -2
- package/src/__tests__/email-html-renderer.test.ts +12 -0
- package/src/__tests__/gateway-flag-listener.test.ts +237 -0
- package/src/__tests__/gemini-provider.test.ts +78 -0
- package/src/__tests__/guardian-dispatch.test.ts +0 -1
- package/src/__tests__/guardian-outbound-http.test.ts +7 -5
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
- package/src/__tests__/heartbeat-service.test.ts +4 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
- package/src/__tests__/list-messages-tool-merge.test.ts +70 -11
- package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
- package/src/__tests__/llm-resolver.test.ts +77 -9
- package/src/__tests__/llm-usage-store.test.ts +66 -0
- package/src/__tests__/logger.test.ts +89 -0
- package/src/__tests__/mcp-abort-signal.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +31 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
- package/src/__tests__/model-intents.test.ts +2 -4
- package/src/__tests__/notification-guardian-path.test.ts +0 -1
- package/src/__tests__/onboarding-template-contract.test.ts +1 -1
- package/src/__tests__/openai-provider.test.ts +46 -0
- package/src/__tests__/openai-responses-provider.test.ts +114 -12
- package/src/__tests__/pending-interactions-resolved-event.test.ts +0 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
- package/src/__tests__/platform.test.ts +2 -2
- package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
- package/src/__tests__/plugin-bootstrap.test.ts +2 -2
- package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
- package/src/__tests__/plugin-types.test.ts +3 -2
- package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
- package/src/__tests__/pricing.test.ts +12 -0
- package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
- package/src/__tests__/registry.test.ts +2 -8
- package/src/__tests__/require-fresh-approval.test.ts +2 -2
- package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
- package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
- package/src/__tests__/skill-tool-factory.test.ts +1 -1
- package/src/__tests__/subagent-notify-parent.test.ts +1 -1
- package/src/__tests__/suggestion-routes.test.ts +1 -0
- package/src/__tests__/sync-message-contract.test.ts +59 -0
- package/src/__tests__/system-prompt.test.ts +145 -131
- package/src/__tests__/terminal-tools.test.ts +1 -1
- package/src/__tests__/tool-approval-handler.test.ts +1 -5
- package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
- package/src/__tests__/tool-executor.test.ts +9 -62
- package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
- package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
- package/src/__tests__/usage-routes.test.ts +3 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +6 -5
- package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
- package/src/acp/prepare-agent-env.ts +78 -0
- package/src/acp/session-manager.ts +1 -1
- package/src/agent/loop.ts +8 -0
- package/src/api/README.md +5 -0
- package/src/api/index.ts +4 -0
- package/src/api/package.json +10 -0
- package/src/background-wake/background-wake-routes.test.ts +233 -0
- package/src/background-wake/runtime-registry.ts +24 -0
- package/src/cli/commands/__tests__/browser.test.ts +23 -5
- package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
- package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
- package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +1 -0
- package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
- package/src/cli/commands/browser.ts +247 -0
- package/src/cli/commands/domain.ts +91 -41
- package/src/cli/commands/inference.ts +93 -40
- package/src/cli/commands/memory-v2-compare-render.ts +115 -0
- package/src/cli/commands/memory-v2.ts +176 -1
- package/src/cli/commands/memory-v3-render.ts +344 -0
- package/src/cli/commands/memory-v3.ts +316 -0
- package/src/cli/program.ts +2 -0
- package/src/config/assistant-feature-flags.ts +21 -9
- package/src/config/bundled-skills/document-editor/SKILL.md +11 -2
- package/src/config/bundled-skills/document-editor/TOOLS.json +18 -0
- package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/call-site-defaults.ts +7 -6
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +213 -1
- package/src/config/schemas/call-site-catalog.ts +21 -7
- package/src/config/schemas/llm.ts +12 -1
- package/src/config/schemas/memory-v2.ts +246 -0
- package/src/config/schemas/memory.ts +2 -1
- package/src/context/compactor.ts +52 -0
- package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
- package/src/conversations/message-consolidation.ts +404 -0
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -13
- package/src/daemon/conversation-agent-loop.ts +126 -76
- package/src/daemon/conversation-error.ts +31 -1
- package/src/daemon/conversation-lifecycle.ts +27 -22
- package/src/daemon/conversation-runtime-assembly.ts +10 -9
- package/src/daemon/conversation-tool-setup.ts +63 -3
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +14 -29
- package/src/daemon/disk-pressure-guard.ts +14 -2
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +11 -3
- package/src/daemon/host-browser-proxy.ts +5 -5
- package/src/daemon/host-cu-proxy.ts +4 -4
- package/src/daemon/host-file-proxy.ts +4 -4
- package/src/daemon/host-proxy-base.ts +4 -4
- package/src/daemon/host-transfer-proxy.ts +10 -10
- package/src/daemon/lifecycle.ts +23 -20
- package/src/daemon/meet-manifest-loader.ts +1 -7
- package/src/daemon/message-types/conversations.ts +6 -9
- package/src/daemon/message-types/home.ts +1 -13
- package/src/daemon/message-types/messages.ts +6 -14
- package/src/daemon/message-types/sync.ts +14 -0
- package/src/daemon/shutdown-handlers.ts +24 -5
- package/src/daemon/switch-inference-profile-tool.ts +52 -0
- package/src/daemon/tool-setup-types.ts +13 -0
- package/src/events/relationship-state-updated.ts +25 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
- package/src/home/home-greeting.ts +0 -9
- package/src/home/suggested-prompts.ts +0 -9
- package/src/ipc/gateway-flag-listener.ts +123 -0
- package/src/ipc/skill-routes/registries.ts +8 -12
- package/src/memory/__tests__/db-async-query.test.ts +165 -0
- package/src/memory/__tests__/db-maintenance.test.ts +115 -0
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
- package/src/memory/__tests__/memory-retrospective-job.test.ts +7 -0
- package/src/memory/auto-analysis-enqueue.ts +5 -1
- package/src/memory/conversation-crud.ts +71 -70
- package/src/memory/conversation-starters-cadence.ts +3 -1
- package/src/memory/conversation-title-service.ts +19 -3
- package/src/memory/db-async-query.ts +214 -0
- package/src/memory/db-init.ts +10 -0
- package/src/memory/db-maintenance.ts +30 -21
- package/src/memory/graph/bootstrap.ts +8 -1
- package/src/memory/graph/capability-seed.ts +7 -3
- package/src/memory/graph/conversation-graph-memory.ts +100 -17
- package/src/memory/graph/extraction.ts +1 -5
- package/src/memory/graph/graph-search.ts +7 -1
- package/src/memory/indexer.ts +28 -18
- package/src/memory/job-handlers/cleanup.ts +76 -18
- package/src/memory/job-handlers/conversation-starters.ts +1 -4
- package/src/memory/jobs/embed-pkb-file.ts +6 -1
- package/src/memory/jobs-store.ts +14 -0
- package/src/memory/jobs-worker.ts +55 -22
- package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
- package/src/memory/llm-request-log-source-local.ts +7 -0
- package/src/memory/llm-request-log-source.ts +9 -2
- package/src/memory/llm-request-log-store.ts +43 -1
- package/src/memory/llm-usage-store.ts +24 -0
- package/src/memory/memory-retrospective-enqueue.ts +8 -1
- package/src/memory/memory-retrospective-job.ts +5 -0
- package/src/memory/memory-v2-activation-log-store.ts +15 -6
- package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
- package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
- package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
- package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
- package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
- package/src/memory/migrations/index.ts +17 -0
- package/src/memory/migrations/registry.ts +33 -0
- package/src/memory/schema/conversations.ts +1 -1
- package/src/memory/schema/infrastructure.ts +21 -0
- package/src/memory/tool-usage-store.ts +36 -8
- package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
- package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
- package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
- package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
- package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
- package/src/memory/v2/__tests__/injection.test.ts +127 -98
- package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
- package/src/memory/v2/__tests__/router.test.ts +171 -3
- package/src/memory/v2/harness/compare.ts +57 -0
- package/src/memory/v2/harness/metrics.ts +124 -0
- package/src/memory/v2/harness/oracle.ts +145 -0
- package/src/memory/v2/harness/replay-input.ts +224 -0
- package/src/memory/v2/harness/retriever.ts +74 -0
- package/src/memory/v2/harness/router-retriever.ts +43 -0
- package/src/memory/v2/harness/runner.ts +106 -0
- package/src/memory/v2/harness/trace.ts +58 -0
- package/src/memory/v2/injection.ts +21 -15
- package/src/memory/v2/prompts/router.ts +26 -1
- package/src/memory/v2/qdrant.ts +14 -2
- package/src/memory/v2/router.ts +171 -18
- package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
- package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
- package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
- package/src/memory/v3/__tests__/edges.test.ts +563 -0
- package/src/memory/v3/__tests__/filter.test.ts +512 -0
- package/src/memory/v3/__tests__/gate.test.ts +574 -0
- package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
- package/src/memory/v3/__tests__/loop.test.ts +530 -0
- package/src/memory/v3/__tests__/retriever.test.ts +226 -0
- package/src/memory/v3/__tests__/scouts.test.ts +440 -0
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
- package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
- package/src/memory/v3/__tests__/traversal.test.ts +469 -0
- package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
- package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
- package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
- package/src/memory/v3/__tests__/validate.test.ts +245 -0
- package/src/memory/v3/auto-edges.ts +223 -0
- package/src/memory/v3/coactivation-store.ts +124 -0
- package/src/memory/v3/consolidation-job.ts +323 -0
- package/src/memory/v3/edge-learning-job.ts +160 -0
- package/src/memory/v3/edges.ts +249 -0
- package/src/memory/v3/filter.ts +281 -0
- package/src/memory/v3/gate.ts +334 -0
- package/src/memory/v3/index-composition.ts +113 -0
- package/src/memory/v3/llm-capture.ts +46 -0
- package/src/memory/v3/loop.ts +382 -0
- package/src/memory/v3/maintenance.ts +144 -0
- package/src/memory/v3/prompt-context.ts +33 -0
- package/src/memory/v3/prompts/consolidation.ts +458 -0
- package/src/memory/v3/prompts/system-prompts.ts +196 -0
- package/src/memory/v3/retriever.ts +33 -0
- package/src/memory/v3/scouts.ts +420 -0
- package/src/memory/v3/shadow-middleware.ts +305 -0
- package/src/memory/v3/traversal.ts +206 -0
- package/src/memory/v3/tree-index.ts +237 -0
- package/src/memory/v3/tree-store.ts +394 -0
- package/src/memory/v3/tree-walk.ts +351 -0
- package/src/memory/v3/types.ts +65 -0
- package/src/memory/v3/validate.ts +300 -0
- package/src/notifications/adapters/macos.ts +18 -1
- package/src/notifications/adapters/platform.ts +1 -1
- package/src/notifications/decision-engine.ts +1 -4
- package/src/notifications/emit-signal.ts +29 -49
- package/src/permissions/prompter.ts +3 -3
- package/src/permissions/question-prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +2 -2
- package/src/plugin-api/index.ts +4 -0
- package/src/plugin-api/types.ts +7 -33
- package/src/plugins/defaults/index.ts +6 -0
- package/src/plugins/defaults/injectors.ts +18 -11
- package/src/plugins/external-plugin-loader.ts +5 -68
- package/src/plugins/types.ts +11 -16
- package/src/proactive-artifact/aux-message-injector.ts +17 -4
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
- package/src/prompts/persona-resolver.ts +36 -21
- package/src/prompts/sections.ts +39 -7
- package/src/prompts/system-prompt.ts +50 -185
- package/src/prompts/templates/BOOTSTRAP.md +2 -2
- package/src/prompts/templates/system-sections.ts +230 -8
- package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
- package/src/providers/__tests__/retry-callsite.test.ts +85 -5
- package/src/providers/anthropic/client.ts +32 -66
- package/src/providers/call-site-routing.ts +14 -2
- package/src/providers/connection-model-compat.ts +38 -0
- package/src/providers/connection-resolution.ts +16 -2
- package/src/providers/gemini/client.ts +49 -6
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/minimax/client.ts +106 -0
- package/src/providers/model-catalog.ts +43 -0
- package/src/providers/model-intents.ts +1 -1
- package/src/providers/openai/chat-completions-provider.ts +6 -3
- package/src/providers/openai/codex-models.ts +18 -0
- package/src/providers/openai/responses-provider.ts +78 -21
- package/src/providers/provider-send-message.ts +7 -1
- package/src/providers/retry.ts +34 -3
- package/src/providers/thinking-config.ts +26 -1
- package/src/providers/usage-tracking.ts +2 -0
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/agent-wake.ts +1 -0
- package/src/runtime/assistant-event-hub.ts +76 -6
- package/src/runtime/auth/route-policy.ts +36 -0
- package/src/runtime/btw-sidechain.ts +0 -6
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/migrations/vbundle-builder.ts +10 -3
- package/src/runtime/pending-interactions.ts +0 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +106 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +25 -6
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
- package/src/runtime/routes/acp-routes.test.ts +255 -6
- package/src/runtime/routes/acp-routes.ts +8 -1
- package/src/runtime/routes/avatar-routes.ts +10 -10
- package/src/runtime/routes/background-wake-routes.ts +188 -0
- package/src/runtime/routes/browser-tabs-routes.ts +200 -0
- package/src/runtime/routes/btw-routes.ts +0 -6
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +12 -4
- package/src/runtime/routes/conversation-management-routes.ts +77 -20
- package/src/runtime/routes/conversation-query-routes.ts +142 -36
- package/src/runtime/routes/conversation-routes.ts +252 -410
- package/src/runtime/routes/conversation-starter-routes.ts +6 -3
- package/src/runtime/routes/disk-pressure-routes.ts +1 -1
- package/src/runtime/routes/domain-routes.ts +60 -10
- package/src/runtime/routes/email-routes.ts +5 -2
- package/src/runtime/routes/events-routes.ts +54 -10
- package/src/runtime/routes/group-routes.ts +24 -8
- package/src/runtime/routes/host-browser-routes.ts +10 -2
- package/src/runtime/routes/host-cu-routes.ts +2 -2
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
- package/src/runtime/routes/index.ts +8 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
- package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
- package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
- package/src/runtime/routes/memory-item-routes.ts +8 -3
- package/src/runtime/routes/memory-v2-routes.ts +215 -5
- package/src/runtime/routes/memory-v3-routes.ts +316 -0
- package/src/runtime/routes/migration-routes.ts +21 -24
- package/src/runtime/routes/plugins-routes.ts +337 -0
- package/src/runtime/routes/rename-conversation-routes.ts +6 -2
- package/src/runtime/routes/secret-routes.ts +25 -5
- package/src/runtime/routes/settings-routes.ts +12 -11
- package/src/runtime/routes/slack-channel-routes.ts +5 -4
- package/src/runtime/routes/workspace-routes.ts +25 -10
- package/src/runtime/sync/resource-sync-events.ts +106 -38
- package/src/runtime/sync/sync-publisher.test.ts +49 -0
- package/src/runtime/sync/sync-publisher.ts +2 -1
- package/src/runtime/verification-outbound-actions.ts +73 -1
- package/src/telemetry/types.ts +12 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/acp/spawn.test.ts +119 -0
- package/src/tools/acp/spawn.ts +15 -2
- package/src/tools/apps/definitions.ts +2 -8
- package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
- package/src/tools/ask-question/ask-question-tool.ts +38 -45
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +70 -0
- package/src/tools/browser/browser-execution.ts +16 -3
- package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +3 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +12 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +27 -1
- package/src/tools/browser/cdp-client/factory.ts +100 -17
- package/src/tools/browser/cdp-client/local-cdp-client.ts +12 -0
- package/src/tools/browser/cdp-client/types.ts +65 -0
- package/src/tools/browser/pinned-tabs.ts +96 -40
- package/src/tools/computer-use/definitions.ts +22 -78
- package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
- package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
- package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
- package/src/tools/credentials/vault.ts +3 -9
- package/src/tools/document/document-tool.ts +59 -0
- package/src/tools/execution-target.ts +21 -23
- package/src/tools/executor.ts +6 -1
- package/src/tools/filesystem/edit.ts +3 -9
- package/src/tools/filesystem/list.ts +3 -9
- package/src/tools/filesystem/read.ts +3 -9
- package/src/tools/filesystem/write.ts +3 -9
- package/src/tools/host-filesystem/edit.ts +3 -9
- package/src/tools/host-filesystem/read.ts +3 -9
- package/src/tools/host-filesystem/transfer.ts +3 -9
- package/src/tools/host-filesystem/write.ts +3 -9
- package/src/tools/host-terminal/host-shell.ts +3 -9
- package/src/tools/mcp/mcp-tool-factory.ts +1 -8
- package/src/tools/memory/register.test.ts +1 -1
- package/src/tools/memory/register.ts +4 -9
- package/src/tools/network/web-fetch.ts +3 -9
- package/src/tools/network/web-search.ts +25 -32
- package/src/tools/registry.ts +7 -23
- package/src/tools/schema-transforms.ts +1 -1
- package/src/tools/skills/execute.ts +3 -9
- package/src/tools/skills/load.ts +3 -9
- package/src/tools/skills/skill-tool-factory.ts +1 -8
- package/src/tools/subagent/notify-parent.ts +3 -9
- package/src/tools/system/request-permission.ts +3 -9
- package/src/tools/terminal/shell.ts +3 -9
- package/src/tools/tool-defaults.ts +94 -0
- package/src/tools/types.ts +27 -98
- package/src/tools/ui-surface/definitions.ts +6 -22
- package/src/usage/pricing.ts +23 -0
- package/src/usage/types.ts +12 -0
- package/src/util/logger.ts +16 -7
- package/src/util/platform.ts +7 -2
- package/src/util/sqlite3-runtime.ts +65 -0
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
- package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
- package/src/__tests__/message-complete-display-id.test.ts +0 -175
- package/src/daemon/query-complexity-router.ts +0 -75
- package/src/prompts/cache-boundary.ts +0 -8
package/package.json
CHANGED
|
@@ -102,7 +102,6 @@ mock.module("@anthropic-ai/sdk", () => ({
|
|
|
102
102
|
}));
|
|
103
103
|
|
|
104
104
|
// Import after mocking
|
|
105
|
-
import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "../prompts/system-prompt.js";
|
|
106
105
|
import {
|
|
107
106
|
AnthropicProvider,
|
|
108
107
|
isPlaceholderSentinelText,
|
|
@@ -228,28 +227,8 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
|
|
|
228
227
|
expect(assistant?.content).toEqual([{ type: "text", text: "I can help." }]);
|
|
229
228
|
});
|
|
230
229
|
|
|
231
|
-
test("
|
|
232
|
-
const
|
|
233
|
-
const dynamicBlock = "User workspace files here.";
|
|
234
|
-
const prompt = staticBlock + SYSTEM_PROMPT_CACHE_BOUNDARY + dynamicBlock;
|
|
235
|
-
|
|
236
|
-
await provider.sendMessage([userMsg("Hi")], undefined, prompt);
|
|
237
|
-
|
|
238
|
-
const system = lastStreamParams!.system as Array<{
|
|
239
|
-
type: string;
|
|
240
|
-
text: string;
|
|
241
|
-
cache_control?: { type: string; ttl?: string };
|
|
242
|
-
}>;
|
|
243
|
-
expect(system).toHaveLength(2);
|
|
244
|
-
expect(system[0].text).toBe(staticBlock);
|
|
245
|
-
expect(system[0].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
|
|
246
|
-
expect(system[1].text).toBe(dynamicBlock);
|
|
247
|
-
expect(system[1].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
test("omits empty dynamic system block after cache boundary", async () => {
|
|
251
|
-
const staticBlock = "You are a helpful assistant.";
|
|
252
|
-
const prompt = staticBlock + SYSTEM_PROMPT_CACHE_BOUNDARY;
|
|
230
|
+
test("renders the system prompt as a single 1h-cached block", async () => {
|
|
231
|
+
const prompt = "You are a helpful assistant.";
|
|
253
232
|
|
|
254
233
|
await provider.sendMessage([userMsg("Hi")], undefined, prompt);
|
|
255
234
|
|
|
@@ -259,16 +238,14 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
|
|
|
259
238
|
cache_control?: { type: string; ttl?: string };
|
|
260
239
|
}>;
|
|
261
240
|
expect(system).toHaveLength(1);
|
|
262
|
-
expect(system[0].text).toBe(
|
|
241
|
+
expect(system[0].text).toBe(prompt);
|
|
263
242
|
expect(system[0].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
|
|
264
243
|
});
|
|
265
244
|
|
|
266
|
-
test("
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const prompt =
|
|
270
|
-
|
|
271
|
-
// Boundary (2 system) + tools (1) + turn-start (1) + tail (1) = 5 → must cap at 4
|
|
245
|
+
test("applies the standard 4-breakpoint cache layout in a tool-use loop", async () => {
|
|
246
|
+
// system(1) + tools(1) + turn-start(1) + tail(1) = 4, the Anthropic
|
|
247
|
+
// per-request cap. All four breakpoints should be present.
|
|
248
|
+
const prompt = "You are a helpful assistant.";
|
|
272
249
|
const messages: Message[] = [
|
|
273
250
|
userMsg("Do something"),
|
|
274
251
|
toolUseMsg("tu_1", "bash"),
|
|
@@ -281,13 +258,9 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
|
|
|
281
258
|
text: string;
|
|
282
259
|
cache_control?: { type: string; ttl?: string };
|
|
283
260
|
}>;
|
|
284
|
-
expect(system).toHaveLength(
|
|
285
|
-
|
|
286
|
-
expect(system[0].cache_control).toBeUndefined();
|
|
287
|
-
// Dynamic block keeps its cache_control
|
|
288
|
-
expect(system[1].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
|
|
261
|
+
expect(system).toHaveLength(1);
|
|
262
|
+
expect(system[0].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
|
|
289
263
|
|
|
290
|
-
// Tools breakpoint still present
|
|
291
264
|
const tools = lastStreamParams!.tools as Array<{
|
|
292
265
|
cache_control?: { type: string; ttl?: string };
|
|
293
266
|
}>;
|
|
@@ -296,7 +269,6 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
|
|
|
296
269
|
ttl: "1h",
|
|
297
270
|
});
|
|
298
271
|
|
|
299
|
-
// Turn-start + tail breakpoints still present
|
|
300
272
|
const sent = lastStreamParams!.messages as Array<{
|
|
301
273
|
role: string;
|
|
302
274
|
content: Array<{
|
|
@@ -432,6 +404,31 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
|
|
|
432
404
|
expect(lastBlock.cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
|
|
433
405
|
});
|
|
434
406
|
|
|
407
|
+
test("disableTurnStartCache suppresses the 1h breakpoint on the turn-starting user message", async () => {
|
|
408
|
+
// One-shot callers (e.g. the memory router) send a single user message
|
|
409
|
+
// per call with content that changes every time. Caching the turn-start
|
|
410
|
+
// block would create unused entries — `disableTurnStartCache: true`
|
|
411
|
+
// opts out.
|
|
412
|
+
await provider.sendMessage(
|
|
413
|
+
[userMsg("Pick relevant pages")],
|
|
414
|
+
undefined,
|
|
415
|
+
undefined,
|
|
416
|
+
{
|
|
417
|
+
config: { disableTurnStartCache: true },
|
|
418
|
+
},
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
const sent = lastStreamParams!.messages as Array<{
|
|
422
|
+
role: string;
|
|
423
|
+
content: Array<{
|
|
424
|
+
type: string;
|
|
425
|
+
cache_control?: { type: string; ttl?: string };
|
|
426
|
+
}>;
|
|
427
|
+
}>;
|
|
428
|
+
const lastBlock = sent[0].content[sent[0].content.length - 1];
|
|
429
|
+
expect(lastBlock.cache_control).toBeUndefined();
|
|
430
|
+
});
|
|
431
|
+
|
|
435
432
|
test("previous-turn anchor is NOT applied during a tool-use loop", async () => {
|
|
436
433
|
// When the request is mid tool-use (last msg is a tool_result), the
|
|
437
434
|
// turn-start anchor already covers the long prefix, so we must not
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for self-echo suppression in `AssistantEventHub`.
|
|
3
|
+
*
|
|
4
|
+
* Validates:
|
|
5
|
+
* - hub.publish(event, { excludeClientId }) skips the matching client
|
|
6
|
+
* subscriber and delivers to every other matching subscriber.
|
|
7
|
+
* - Suppression is unconditional: it applies whether the broadcast is
|
|
8
|
+
* untargeted, conversation-scoped, or capability-targeted.
|
|
9
|
+
* - `broadcastMessage(sync_changed { originClientId })` derives the
|
|
10
|
+
* exclusion from the message itself — no caller wiring needed.
|
|
11
|
+
* - `broadcastMessage(sync_changed)` without an originClientId fans out
|
|
12
|
+
* to every subscriber (the daemon-internal emit path).
|
|
13
|
+
* - Process-type subscribers are never excluded by a clientId match.
|
|
14
|
+
*/
|
|
15
|
+
import { describe, expect, test } from "bun:test";
|
|
16
|
+
|
|
17
|
+
import type { AssistantEvent } from "../runtime/assistant-event.js";
|
|
18
|
+
import {
|
|
19
|
+
AssistantEventHub,
|
|
20
|
+
assistantEventHub,
|
|
21
|
+
broadcastMessage,
|
|
22
|
+
} from "../runtime/assistant-event-hub.js";
|
|
23
|
+
|
|
24
|
+
function makeSyncChangedEvent(originClientId?: string): AssistantEvent {
|
|
25
|
+
return {
|
|
26
|
+
id: "evt_test_sync",
|
|
27
|
+
conversationId: undefined,
|
|
28
|
+
emittedAt: "2026-05-03T00:00:00.000Z",
|
|
29
|
+
message: {
|
|
30
|
+
type: "sync_changed",
|
|
31
|
+
tags: ["conversation:abc:messages"],
|
|
32
|
+
...(originClientId ? { originClientId } : {}),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe("AssistantEventHub — self-echo suppression (excludeClientId)", () => {
|
|
38
|
+
test("skips the named client and delivers to every other client", async () => {
|
|
39
|
+
const hub = new AssistantEventHub();
|
|
40
|
+
const receivedA: AssistantEvent[] = [];
|
|
41
|
+
const receivedB: AssistantEvent[] = [];
|
|
42
|
+
const receivedC: AssistantEvent[] = [];
|
|
43
|
+
|
|
44
|
+
hub.subscribe({
|
|
45
|
+
type: "client",
|
|
46
|
+
clientId: "client-a",
|
|
47
|
+
interfaceId: "web",
|
|
48
|
+
capabilities: [],
|
|
49
|
+
callback: (e) => {
|
|
50
|
+
receivedA.push(e);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
hub.subscribe({
|
|
55
|
+
type: "client",
|
|
56
|
+
clientId: "client-b",
|
|
57
|
+
interfaceId: "web",
|
|
58
|
+
capabilities: [],
|
|
59
|
+
callback: (e) => {
|
|
60
|
+
receivedB.push(e);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
hub.subscribe({
|
|
65
|
+
type: "client",
|
|
66
|
+
clientId: "client-c",
|
|
67
|
+
interfaceId: "macos",
|
|
68
|
+
capabilities: [],
|
|
69
|
+
callback: (e) => {
|
|
70
|
+
receivedC.push(e);
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await hub.publish(makeSyncChangedEvent("client-a"), {
|
|
75
|
+
excludeClientId: "client-a",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(receivedA).toHaveLength(0);
|
|
79
|
+
expect(receivedB).toHaveLength(1);
|
|
80
|
+
expect(receivedC).toHaveLength(1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("delivers to every subscriber when excludeClientId is omitted", async () => {
|
|
84
|
+
const hub = new AssistantEventHub();
|
|
85
|
+
const receivedA: AssistantEvent[] = [];
|
|
86
|
+
const receivedB: AssistantEvent[] = [];
|
|
87
|
+
|
|
88
|
+
hub.subscribe({
|
|
89
|
+
type: "client",
|
|
90
|
+
clientId: "client-a",
|
|
91
|
+
interfaceId: "web",
|
|
92
|
+
capabilities: [],
|
|
93
|
+
callback: (e) => {
|
|
94
|
+
receivedA.push(e);
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
hub.subscribe({
|
|
98
|
+
type: "client",
|
|
99
|
+
clientId: "client-b",
|
|
100
|
+
interfaceId: "web",
|
|
101
|
+
capabilities: [],
|
|
102
|
+
callback: (e) => {
|
|
103
|
+
receivedB.push(e);
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await hub.publish(makeSyncChangedEvent());
|
|
108
|
+
|
|
109
|
+
expect(receivedA).toHaveLength(1);
|
|
110
|
+
expect(receivedB).toHaveLength(1);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("excludeClientId does not match process-type subscribers", async () => {
|
|
114
|
+
const hub = new AssistantEventHub();
|
|
115
|
+
const receivedProcess: AssistantEvent[] = [];
|
|
116
|
+
|
|
117
|
+
// A process subscriber sits in the same hub as a client whose id
|
|
118
|
+
// matches `excludeClientId`. It must never be suppressed because
|
|
119
|
+
// `excludeClientId` only matches `type: "client"` entries — process
|
|
120
|
+
// entries have no `clientId` field at all.
|
|
121
|
+
hub.subscribe({
|
|
122
|
+
type: "process",
|
|
123
|
+
callback: (e) => {
|
|
124
|
+
receivedProcess.push(e);
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await hub.publish(makeSyncChangedEvent("client-a"), {
|
|
129
|
+
excludeClientId: "client-a",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(receivedProcess).toHaveLength(1);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("excludeClientId composes with conversation-scoped subscribers", async () => {
|
|
136
|
+
const hub = new AssistantEventHub();
|
|
137
|
+
const receivedA: AssistantEvent[] = [];
|
|
138
|
+
const receivedB: AssistantEvent[] = [];
|
|
139
|
+
|
|
140
|
+
hub.subscribe({
|
|
141
|
+
type: "client",
|
|
142
|
+
clientId: "client-a",
|
|
143
|
+
interfaceId: "web",
|
|
144
|
+
capabilities: [],
|
|
145
|
+
filter: { conversationId: "sess_x" },
|
|
146
|
+
callback: (e) => {
|
|
147
|
+
receivedA.push(e);
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
hub.subscribe({
|
|
151
|
+
type: "client",
|
|
152
|
+
clientId: "client-b",
|
|
153
|
+
interfaceId: "web",
|
|
154
|
+
capabilities: [],
|
|
155
|
+
filter: { conversationId: "sess_x" },
|
|
156
|
+
callback: (e) => {
|
|
157
|
+
receivedB.push(e);
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Scoped event for sess_x; both clients filter to sess_x; exclude A.
|
|
162
|
+
const event: AssistantEvent = {
|
|
163
|
+
id: "evt_scoped",
|
|
164
|
+
conversationId: "sess_x",
|
|
165
|
+
emittedAt: "2026-05-03T00:00:00.000Z",
|
|
166
|
+
message: {
|
|
167
|
+
type: "sync_changed",
|
|
168
|
+
tags: ["conversation:sess_x:messages"],
|
|
169
|
+
originClientId: "client-a",
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
await hub.publish(event, { excludeClientId: "client-a" });
|
|
174
|
+
|
|
175
|
+
expect(receivedA).toHaveLength(0);
|
|
176
|
+
expect(receivedB).toHaveLength(1);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("broadcastMessage — derives excludeClientId from sync_changed.originClientId", () => {
|
|
181
|
+
test("skips the originating client when originClientId is present", async () => {
|
|
182
|
+
const receivedA: AssistantEvent[] = [];
|
|
183
|
+
const receivedB: AssistantEvent[] = [];
|
|
184
|
+
|
|
185
|
+
const subA = assistantEventHub.subscribe({
|
|
186
|
+
type: "client",
|
|
187
|
+
clientId: "broadcast-a",
|
|
188
|
+
interfaceId: "web",
|
|
189
|
+
capabilities: [],
|
|
190
|
+
callback: (e) => {
|
|
191
|
+
receivedA.push(e);
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
const subB = assistantEventHub.subscribe({
|
|
195
|
+
type: "client",
|
|
196
|
+
clientId: "broadcast-b",
|
|
197
|
+
interfaceId: "web",
|
|
198
|
+
capabilities: [],
|
|
199
|
+
callback: (e) => {
|
|
200
|
+
receivedB.push(e);
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
broadcastMessage({
|
|
206
|
+
type: "sync_changed",
|
|
207
|
+
tags: ["resource:test"],
|
|
208
|
+
originClientId: "broadcast-a",
|
|
209
|
+
});
|
|
210
|
+
// broadcastMessage queues onto a microtask chain — yield until it
|
|
211
|
+
// drains. Two awaits cover the publish promise + its `.then` chain.
|
|
212
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
213
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
214
|
+
|
|
215
|
+
expect(receivedA).toHaveLength(0);
|
|
216
|
+
expect(receivedB).toHaveLength(1);
|
|
217
|
+
} finally {
|
|
218
|
+
subA.dispose();
|
|
219
|
+
subB.dispose();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("fans out to every client when originClientId is absent", async () => {
|
|
224
|
+
const receivedA: AssistantEvent[] = [];
|
|
225
|
+
const receivedB: AssistantEvent[] = [];
|
|
226
|
+
|
|
227
|
+
const subA = assistantEventHub.subscribe({
|
|
228
|
+
type: "client",
|
|
229
|
+
clientId: "broadcast-c",
|
|
230
|
+
interfaceId: "web",
|
|
231
|
+
capabilities: [],
|
|
232
|
+
callback: (e) => {
|
|
233
|
+
receivedA.push(e);
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
const subB = assistantEventHub.subscribe({
|
|
237
|
+
type: "client",
|
|
238
|
+
clientId: "broadcast-d",
|
|
239
|
+
interfaceId: "web",
|
|
240
|
+
capabilities: [],
|
|
241
|
+
callback: (e) => {
|
|
242
|
+
receivedB.push(e);
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
broadcastMessage({
|
|
248
|
+
type: "sync_changed",
|
|
249
|
+
tags: ["resource:test"],
|
|
250
|
+
});
|
|
251
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
252
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
253
|
+
|
|
254
|
+
expect(receivedA).toHaveLength(1);
|
|
255
|
+
expect(receivedB).toHaveLength(1);
|
|
256
|
+
} finally {
|
|
257
|
+
subA.dispose();
|
|
258
|
+
subB.dispose();
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("ignores an empty-string originClientId (treats as absent)", async () => {
|
|
263
|
+
const receivedA: AssistantEvent[] = [];
|
|
264
|
+
|
|
265
|
+
const subA = assistantEventHub.subscribe({
|
|
266
|
+
type: "client",
|
|
267
|
+
clientId: "broadcast-e",
|
|
268
|
+
interfaceId: "web",
|
|
269
|
+
capabilities: [],
|
|
270
|
+
callback: (e) => {
|
|
271
|
+
receivedA.push(e);
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
broadcastMessage({
|
|
277
|
+
type: "sync_changed",
|
|
278
|
+
tags: ["resource:test"],
|
|
279
|
+
// Exercise the length-zero guard. `originClientId?: string` accepts
|
|
280
|
+
// empty strings at the type level, but production code path can't
|
|
281
|
+
// produce one — `buildSyncChangedMessage` trims and drops empty
|
|
282
|
+
// values before calling `broadcastMessage`.
|
|
283
|
+
originClientId: "",
|
|
284
|
+
});
|
|
285
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
286
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
287
|
+
|
|
288
|
+
expect(receivedA).toHaveLength(1);
|
|
289
|
+
} finally {
|
|
290
|
+
subA.dispose();
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Covers:
|
|
5
5
|
* - Missing persisted value falls back to code default
|
|
6
6
|
* - Protected feature-flags.json is the sole override mechanism
|
|
7
|
-
* - Undeclared keys default to
|
|
7
|
+
* - Undeclared keys default to disabled
|
|
8
8
|
*/
|
|
9
9
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
10
10
|
|
|
@@ -80,10 +80,10 @@ describe("isAssistantFeatureFlagEnabled", () => {
|
|
|
80
80
|
).toBe(true);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
test("unknown flag defaults to
|
|
83
|
+
test("unknown flag defaults to false when no persisted override", () => {
|
|
84
84
|
const config = {} as any;
|
|
85
85
|
|
|
86
|
-
expect(isAssistantFeatureFlagEnabled("unknown-skill", config)).toBe(
|
|
86
|
+
expect(isAssistantFeatureFlagEnabled("unknown-skill", config)).toBe(false);
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
test("undeclared flag respects persisted override", () => {
|
|
@@ -69,72 +69,126 @@ describe("audit log rotation", () => {
|
|
|
69
69
|
clearTable();
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
-
test("returns 0 when retentionDays is 0 (retain forever)", () => {
|
|
72
|
+
test("returns 0 when retentionDays is 0 (retain forever)", async () => {
|
|
73
73
|
addInvocation(100 * ONE_DAY_MS); // 100 days old
|
|
74
|
-
const deleted = rotateToolInvocations(0);
|
|
74
|
+
const deleted = await rotateToolInvocations(0);
|
|
75
75
|
expect(deleted).toBe(0);
|
|
76
76
|
expect(getRecentInvocations(100).length).toBe(1);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
test("returns 0 when retentionDays is negative", () => {
|
|
79
|
+
test("returns 0 when retentionDays is negative", async () => {
|
|
80
80
|
addInvocation(100 * ONE_DAY_MS);
|
|
81
|
-
const deleted = rotateToolInvocations(-5);
|
|
81
|
+
const deleted = await rotateToolInvocations(-5);
|
|
82
82
|
expect(deleted).toBe(0);
|
|
83
83
|
expect(getRecentInvocations(100).length).toBe(1);
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
test("deletes records older than retentionDays", () => {
|
|
86
|
+
test("deletes records older than retentionDays", async () => {
|
|
87
87
|
addInvocation(10 * ONE_DAY_MS); // 10 days old — should be deleted with 7-day retention
|
|
88
88
|
addInvocation(3 * ONE_DAY_MS); // 3 days old — should be kept
|
|
89
89
|
addInvocation(1 * ONE_DAY_MS); // 1 day old — should be kept
|
|
90
90
|
|
|
91
|
-
const deleted = rotateToolInvocations(7);
|
|
91
|
+
const deleted = await rotateToolInvocations(7);
|
|
92
92
|
expect(deleted).toBe(1);
|
|
93
93
|
expect(getRecentInvocations(100).length).toBe(2);
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
-
test("keeps all records when none exceed retention", () => {
|
|
96
|
+
test("keeps all records when none exceed retention", async () => {
|
|
97
97
|
addInvocation(1 * ONE_DAY_MS);
|
|
98
98
|
addInvocation(2 * ONE_DAY_MS);
|
|
99
99
|
addInvocation(3 * ONE_DAY_MS);
|
|
100
100
|
|
|
101
|
-
const deleted = rotateToolInvocations(30);
|
|
101
|
+
const deleted = await rotateToolInvocations(30);
|
|
102
102
|
expect(deleted).toBe(0);
|
|
103
103
|
expect(getRecentInvocations(100).length).toBe(3);
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
test("deletes all records when all exceed retention", () => {
|
|
106
|
+
test("deletes all records when all exceed retention", async () => {
|
|
107
107
|
addInvocation(60 * ONE_DAY_MS);
|
|
108
108
|
addInvocation(90 * ONE_DAY_MS);
|
|
109
109
|
addInvocation(120 * ONE_DAY_MS);
|
|
110
110
|
|
|
111
|
-
const deleted = rotateToolInvocations(30);
|
|
111
|
+
const deleted = await rotateToolInvocations(30);
|
|
112
112
|
expect(deleted).toBe(3);
|
|
113
113
|
expect(getRecentInvocations(100).length).toBe(0);
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
test("returns 0 when table is empty", () => {
|
|
117
|
-
const deleted = rotateToolInvocations(7);
|
|
116
|
+
test("returns 0 when table is empty", async () => {
|
|
117
|
+
const deleted = await rotateToolInvocations(7);
|
|
118
118
|
expect(deleted).toBe(0);
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
-
test("handles 1-day retention (deletes everything older than 24h)", () => {
|
|
121
|
+
test("handles 1-day retention (deletes everything older than 24h)", async () => {
|
|
122
122
|
addInvocation(2 * ONE_DAY_MS); // 2 days old — delete
|
|
123
123
|
addInvocation(12 * 60 * 60 * 1000); // 12 hours old — keep
|
|
124
124
|
|
|
125
|
-
const deleted = rotateToolInvocations(1);
|
|
125
|
+
const deleted = await rotateToolInvocations(1);
|
|
126
126
|
expect(deleted).toBe(1);
|
|
127
127
|
expect(getRecentInvocations(100).length).toBe(1);
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
-
test("works with recordToolInvocation (via ORM)", () => {
|
|
130
|
+
test("works with recordToolInvocation (via ORM)", async () => {
|
|
131
131
|
// Use raw SQL to insert (avoids db singleton issues in parallel test runs)
|
|
132
132
|
// and verify the rotation/query functions work correctly with it
|
|
133
133
|
addInvocation(0); // just-created record
|
|
134
134
|
|
|
135
135
|
// This record was just created, so it should not be rotated
|
|
136
|
-
const deleted = rotateToolInvocations(1);
|
|
136
|
+
const deleted = await rotateToolInvocations(1);
|
|
137
137
|
expect(deleted).toBe(0);
|
|
138
138
|
expect(getRecentInvocations(100).length).toBe(1);
|
|
139
139
|
});
|
|
140
|
+
|
|
141
|
+
test(
|
|
142
|
+
"yields to the event loop while the purge is in flight (anti-block)",
|
|
143
|
+
async () => {
|
|
144
|
+
// Seed a few thousand rows so the DELETE has measurable
|
|
145
|
+
// subprocess work behind the scenes.
|
|
146
|
+
const ROW_COUNT = 2000;
|
|
147
|
+
const past = 100 * ONE_DAY_MS;
|
|
148
|
+
const sqlite = getSqlite();
|
|
149
|
+
sqlite.exec("BEGIN");
|
|
150
|
+
try {
|
|
151
|
+
for (let i = 0; i < ROW_COUNT; i++) {
|
|
152
|
+
addInvocation(past);
|
|
153
|
+
}
|
|
154
|
+
sqlite.exec("COMMIT");
|
|
155
|
+
} catch (err) {
|
|
156
|
+
sqlite.exec("ROLLBACK");
|
|
157
|
+
throw err;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Race the purge against a `setImmediate` ping. The ping
|
|
161
|
+
// resolves on the very next event-loop iteration after it is
|
|
162
|
+
// scheduled.
|
|
163
|
+
//
|
|
164
|
+
// If the purge is async (subprocess), `rotateToolInvocations`
|
|
165
|
+
// returns a pending Promise immediately; the ping wins the race
|
|
166
|
+
// while the subprocess is still running.
|
|
167
|
+
//
|
|
168
|
+
// If the purge is ever regressed back to a synchronous DELETE,
|
|
169
|
+
// `rotateToolInvocations` blocks the main thread for the full
|
|
170
|
+
// DELETE duration. `setImmediate` cannot fire until the main
|
|
171
|
+
// thread releases, so the purge's already-resolved Promise
|
|
172
|
+
// beats the ping through the microtask queue and "purge" wins.
|
|
173
|
+
//
|
|
174
|
+
// The signal is deterministic regardless of how fast the
|
|
175
|
+
// subprocess is or how busy the event loop is.
|
|
176
|
+
const ping = new Promise<"ping">((resolve) =>
|
|
177
|
+
setImmediate(() => resolve("ping")),
|
|
178
|
+
);
|
|
179
|
+
const purgePromise = rotateToolInvocations(7);
|
|
180
|
+
const winner = await Promise.race([
|
|
181
|
+
ping,
|
|
182
|
+
purgePromise.then(() => "purge" as const),
|
|
183
|
+
]);
|
|
184
|
+
|
|
185
|
+
expect(winner).toBe("ping");
|
|
186
|
+
|
|
187
|
+
// Drain the purge so the log + subprocess cleanup complete
|
|
188
|
+
// before the test exits.
|
|
189
|
+
const deleted = await purgePromise;
|
|
190
|
+
expect(deleted).toBe(ROW_COUNT);
|
|
191
|
+
},
|
|
192
|
+
60_000,
|
|
193
|
+
);
|
|
140
194
|
});
|
|
@@ -108,7 +108,6 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
108
108
|
addMessage: mock(() => ({ id: "msg-1" })),
|
|
109
109
|
archiveConversation: mock(() => true),
|
|
110
110
|
batchSetDisplayOrders: mock(() => {}),
|
|
111
|
-
clearStrippedInjectionMetadataForConversation: mock(() => {}),
|
|
112
111
|
createConversation: (opts: { conversationType: string }) => {
|
|
113
112
|
createdConversations.push(opts);
|
|
114
113
|
return { id: "conv-1", ...opts };
|
|
@@ -116,7 +115,7 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
116
115
|
countConversationsByScheduleJobId: mock(() => 0),
|
|
117
116
|
countMessagesAfter: mock(() => 0),
|
|
118
117
|
deleteMessageById: mock(() => {}),
|
|
119
|
-
clearAll: mock(() => ({ conversations: 0, messages: 0 })),
|
|
118
|
+
clearAll: mock(async () => ({ conversations: 0, messages: 0 })),
|
|
120
119
|
deleteConversation: mock(() => ({ memoryIds: [] })),
|
|
121
120
|
deleteLastExchange: mock(() => 0),
|
|
122
121
|
findAnalysisConversationFor: mock(() => null),
|
|
@@ -156,7 +155,7 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
156
155
|
updateConversationTitle: mock(() => {}),
|
|
157
156
|
updateConversationUsage: mock(() => {}),
|
|
158
157
|
setLastNotifiedInferenceProfile: mock(() => {}),
|
|
159
|
-
|
|
158
|
+
setConversationHistoryStrippedAt: mock(() => {}),
|
|
160
159
|
wipeConversation: mock(() => ({ memoryIds: [] })),
|
|
161
160
|
}));
|
|
162
161
|
|
|
@@ -182,6 +181,7 @@ mock.module("../memory/jobs-store.js", () => ({
|
|
|
182
181
|
failStalledJobs: mockFailStalledJobs,
|
|
183
182
|
getMemoryJobCounts: mock(() => ({})),
|
|
184
183
|
hasActiveJobOfType: mock(() => false),
|
|
184
|
+
isMemoryEnabled: () => true,
|
|
185
185
|
resetRunningJobsToPending: mock(() => 0),
|
|
186
186
|
SLOW_LLM_JOB_TYPES: [],
|
|
187
187
|
upsertAutoAnalysisJob: mock(() => "job-auto-analysis"),
|
|
@@ -317,12 +317,11 @@ describe("POST /v1/btw", () => {
|
|
|
317
317
|
|
|
318
318
|
expect(tools).toEqual(MOCK_TOOLS);
|
|
319
319
|
expect(systemPrompt).toBe(MOCK_SYSTEM_PROMPT);
|
|
320
|
+
// Persona is resolved internally now; btw-routes no longer plumbs
|
|
321
|
+
// it through to buildSystemPrompt.
|
|
320
322
|
expect(mockBuildSystemPrompt).toHaveBeenCalledWith({
|
|
321
|
-
channelPersona: null,
|
|
322
323
|
excludeBootstrap: true,
|
|
323
324
|
excludeCustomPrefix: true,
|
|
324
|
-
userPersona: null,
|
|
325
|
-
userSlug: null,
|
|
326
325
|
});
|
|
327
326
|
expect(options!.config!.tool_choice).toEqual({ type: "none" });
|
|
328
327
|
expect(options!.config!.callSite).toBe("identityIntro");
|
|
@@ -49,7 +49,7 @@ mock.module("../daemon/handlers/conversations.js", () => ({
|
|
|
49
49
|
return true;
|
|
50
50
|
},
|
|
51
51
|
switchConversation: async () => null,
|
|
52
|
-
clearAllConversations: () => 0,
|
|
52
|
+
clearAllConversations: async () => 0,
|
|
53
53
|
undoLastMessage: async () => null,
|
|
54
54
|
regenerateResponse: async () => null,
|
|
55
55
|
}));
|
|
@@ -2777,7 +2777,7 @@ describe("outbound voice verification", () => {
|
|
|
2777
2777
|
}
|
|
2778
2778
|
});
|
|
2779
2779
|
|
|
2780
|
-
test("start_outbound
|
|
2780
|
+
test("start_outbound succeeds for email channel", async () => {
|
|
2781
2781
|
const { lastResponse } = createResponseReader();
|
|
2782
2782
|
await handleChannelVerificationSession(
|
|
2783
2783
|
{
|
|
@@ -2790,8 +2790,8 @@ describe("outbound voice verification", () => {
|
|
|
2790
2790
|
|
|
2791
2791
|
const resp = lastResponse();
|
|
2792
2792
|
expect(resp).not.toBeNull();
|
|
2793
|
-
expect(resp!.success).toBe(
|
|
2794
|
-
expect(resp!.
|
|
2793
|
+
expect(resp!.success).toBe(true);
|
|
2794
|
+
expect(resp!.channel).toBe("email");
|
|
2795
2795
|
});
|
|
2796
2796
|
|
|
2797
2797
|
test("create_session without destination falls through to inbound challenge", async () => {
|