@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
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { createServer, type Server, type Socket } from "node:net";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { afterEach,beforeEach, describe, expect, mock, test } from "bun:test";
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Isolated temp directory for the IPC socket
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const testRoot = mkdtempSync(join(tmpdir(), "flag-listener-test-"));
|
|
11
|
+
const socketPath = join(testRoot, "gateway.sock");
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Mock socket-path resolution to use our test socket
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
mock.module("../ipc/socket-path.js", () => ({
|
|
17
|
+
resolveIpcSocketPath: (_name: string) => ({
|
|
18
|
+
path: socketPath,
|
|
19
|
+
source: "workspace" as const,
|
|
20
|
+
}),
|
|
21
|
+
getAssistantSocketPath: () => join(testRoot, "assistant.sock"),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Track calls to refreshOverridesFromGateway
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
let refreshCallCount = 0;
|
|
28
|
+
|
|
29
|
+
mock.module("../config/assistant-feature-flags.js", () => ({
|
|
30
|
+
refreshOverridesFromGateway: async () => {
|
|
31
|
+
refreshCallCount++;
|
|
32
|
+
},
|
|
33
|
+
initFeatureFlagOverrides: async () => {},
|
|
34
|
+
clearFeatureFlagOverridesCache: () => {},
|
|
35
|
+
isAssistantFeatureFlagEnabled: () => true,
|
|
36
|
+
_setOverridesForTesting: () => {},
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Track calls to publishSyncInvalidation so we can assert the listener
|
|
41
|
+
// fans flag changes out onto the SSE hub.
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
let publishedTagSets: string[][] = [];
|
|
44
|
+
|
|
45
|
+
mock.module("../runtime/sync/sync-publisher.js", () => ({
|
|
46
|
+
publishSyncInvalidation: async (tags: string[]) => {
|
|
47
|
+
publishedTagSets.push([...tags]);
|
|
48
|
+
return { type: "sync_changed", tags };
|
|
49
|
+
},
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Dynamic imports (after mock.module)
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
const { startGatewayFlagListener, stopGatewayFlagListener } = await import(
|
|
56
|
+
"../ipc/gateway-flag-listener.js"
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Helpers
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
function createTestServer(): {
|
|
64
|
+
server: Server;
|
|
65
|
+
clients: Set<Socket>;
|
|
66
|
+
emit: (event: string, data?: unknown) => void;
|
|
67
|
+
waitForClient: () => Promise<Socket>;
|
|
68
|
+
} {
|
|
69
|
+
const clients = new Set<Socket>();
|
|
70
|
+
const clientWaiters: Array<(socket: Socket) => void> = [];
|
|
71
|
+
|
|
72
|
+
const server = createServer((socket) => {
|
|
73
|
+
clients.add(socket);
|
|
74
|
+
socket.on("close", () => clients.delete(socket));
|
|
75
|
+
const waiter = clientWaiters.shift();
|
|
76
|
+
if (waiter) waiter(socket);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
server,
|
|
81
|
+
clients,
|
|
82
|
+
emit: (event: string, data?: unknown) => {
|
|
83
|
+
const payload = JSON.stringify({ event, data }) + "\n";
|
|
84
|
+
for (const client of clients) {
|
|
85
|
+
if (!client.destroyed) client.write(payload);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
waitForClient: () =>
|
|
89
|
+
new Promise((resolve) => {
|
|
90
|
+
if (clients.size > 0) {
|
|
91
|
+
resolve(clients.values().next().value!);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
clientWaiters.push(resolve);
|
|
95
|
+
}),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Tests
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
describe("gateway-flag-listener", () => {
|
|
103
|
+
let testServer: ReturnType<typeof createTestServer>;
|
|
104
|
+
|
|
105
|
+
beforeEach(() => {
|
|
106
|
+
mkdirSync(testRoot, { recursive: true });
|
|
107
|
+
refreshCallCount = 0;
|
|
108
|
+
publishedTagSets = [];
|
|
109
|
+
testServer = createTestServer();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
afterEach(async () => {
|
|
113
|
+
stopGatewayFlagListener();
|
|
114
|
+
await new Promise<void>((resolve) => {
|
|
115
|
+
for (const client of testServer.clients) {
|
|
116
|
+
if (!client.destroyed) client.destroy();
|
|
117
|
+
}
|
|
118
|
+
testServer.server.close(() => resolve());
|
|
119
|
+
});
|
|
120
|
+
try {
|
|
121
|
+
rmSync(socketPath, { force: true });
|
|
122
|
+
} catch {
|
|
123
|
+
// best effort
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("refreshes flag cache on connect and on feature_flags_changed event", async () => {
|
|
128
|
+
await new Promise<void>((resolve) => {
|
|
129
|
+
testServer.server.listen(socketPath, resolve);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
startGatewayFlagListener();
|
|
133
|
+
await testServer.waitForClient();
|
|
134
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
135
|
+
|
|
136
|
+
expect(refreshCallCount).toBe(1);
|
|
137
|
+
|
|
138
|
+
testServer.emit("feature_flags_changed");
|
|
139
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
140
|
+
|
|
141
|
+
expect(refreshCallCount).toBe(2);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("broadcasts feature-flags sync_changed when flags change", async () => {
|
|
145
|
+
await new Promise<void>((resolve) => {
|
|
146
|
+
testServer.server.listen(socketPath, resolve);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
startGatewayFlagListener();
|
|
150
|
+
await testServer.waitForClient();
|
|
151
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
152
|
+
|
|
153
|
+
// Connect refresh should not broadcast — only an actual change does.
|
|
154
|
+
expect(publishedTagSets.length).toBe(0);
|
|
155
|
+
|
|
156
|
+
testServer.emit("feature_flags_changed");
|
|
157
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
158
|
+
|
|
159
|
+
expect(publishedTagSets.length).toBe(1);
|
|
160
|
+
expect(publishedTagSets[0]).toEqual([
|
|
161
|
+
"feature-flags:client",
|
|
162
|
+
"feature-flags:assistant",
|
|
163
|
+
]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("ignores non-flag events", async () => {
|
|
167
|
+
await new Promise<void>((resolve) => {
|
|
168
|
+
testServer.server.listen(socketPath, resolve);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
startGatewayFlagListener();
|
|
172
|
+
await testServer.waitForClient();
|
|
173
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
174
|
+
|
|
175
|
+
const countAfterConnect = refreshCallCount;
|
|
176
|
+
|
|
177
|
+
testServer.emit("some_other_event");
|
|
178
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
179
|
+
|
|
180
|
+
expect(refreshCallCount).toBe(countAfterConnect);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("reconnects on disconnect and handles events on new connection", async () => {
|
|
184
|
+
await new Promise<void>((resolve) => {
|
|
185
|
+
testServer.server.listen(socketPath, resolve);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
startGatewayFlagListener();
|
|
189
|
+
const firstClient = await testServer.waitForClient();
|
|
190
|
+
|
|
191
|
+
// Wait for the close event to propagate back to the server before
|
|
192
|
+
// setting up the next waitForClient — otherwise waitForClient might
|
|
193
|
+
// resolve with the old (now-destroyed) socket that is still in the set.
|
|
194
|
+
await new Promise<void>((resolve) => {
|
|
195
|
+
firstClient.on("close", resolve);
|
|
196
|
+
firstClient.destroy();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Wait for reconnect (initial backoff is 1s)
|
|
200
|
+
const secondClient = await Promise.race([
|
|
201
|
+
testServer.waitForClient(),
|
|
202
|
+
new Promise<null>((r) => setTimeout(() => r(null), 3000)),
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
expect(secondClient).not.toBeNull();
|
|
206
|
+
|
|
207
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
208
|
+
const countAfterReconnect = refreshCallCount;
|
|
209
|
+
expect(countAfterReconnect).toBeGreaterThan(0);
|
|
210
|
+
|
|
211
|
+
const payload =
|
|
212
|
+
JSON.stringify({ event: "feature_flags_changed" }) + "\n";
|
|
213
|
+
secondClient!.write(payload);
|
|
214
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
215
|
+
|
|
216
|
+
expect(refreshCallCount).toBe(countAfterReconnect + 1);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("stopGatewayFlagListener cleans up and does not reconnect", async () => {
|
|
220
|
+
await new Promise<void>((resolve) => {
|
|
221
|
+
testServer.server.listen(socketPath, resolve);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
startGatewayFlagListener();
|
|
225
|
+
await testServer.waitForClient();
|
|
226
|
+
|
|
227
|
+
stopGatewayFlagListener();
|
|
228
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
229
|
+
|
|
230
|
+
const initialClientCount = testServer.clients.size;
|
|
231
|
+
|
|
232
|
+
// Wait past reconnect backoff — should not reconnect
|
|
233
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
234
|
+
|
|
235
|
+
expect(testServer.clients.size).toBeLessThanOrEqual(initialClientCount);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
@@ -75,6 +75,12 @@ mock.module("@google/genai", () => ({
|
|
|
75
75
|
};
|
|
76
76
|
},
|
|
77
77
|
ApiError: FakeApiError,
|
|
78
|
+
ThinkingLevel: {
|
|
79
|
+
MINIMAL: "MINIMAL",
|
|
80
|
+
LOW: "LOW",
|
|
81
|
+
MEDIUM: "MEDIUM",
|
|
82
|
+
HIGH: "HIGH",
|
|
83
|
+
},
|
|
78
84
|
}));
|
|
79
85
|
|
|
80
86
|
// Import after mocking
|
|
@@ -223,6 +229,78 @@ describe("GeminiProvider", () => {
|
|
|
223
229
|
expect(config.systemInstruction).toBe("You are a helpful assistant.");
|
|
224
230
|
});
|
|
225
231
|
|
|
232
|
+
// -----------------------------------------------------------------------
|
|
233
|
+
// Thinking config
|
|
234
|
+
// -----------------------------------------------------------------------
|
|
235
|
+
test("omits thinkingConfig when no thinking config is supplied", async () => {
|
|
236
|
+
fakeChunks = [textChunk("OK"), finishChunk("STOP", 10, 2)];
|
|
237
|
+
|
|
238
|
+
await provider.sendMessage([
|
|
239
|
+
{ role: "user", content: [{ type: "text", text: "Hi" }] },
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
const config = lastStreamParams!.config as Record<string, unknown>;
|
|
243
|
+
expect(config.thinkingConfig).toBeUndefined();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("maps wire { type: 'adaptive', level, streamThinking } to Gemini thinkingConfig", async () => {
|
|
247
|
+
fakeChunks = [textChunk("OK"), finishChunk("STOP", 10, 2)];
|
|
248
|
+
|
|
249
|
+
await provider.sendMessage(
|
|
250
|
+
[{ role: "user", content: [{ type: "text", text: "Hi" }] }],
|
|
251
|
+
undefined,
|
|
252
|
+
undefined,
|
|
253
|
+
{
|
|
254
|
+
config: {
|
|
255
|
+
thinking: {
|
|
256
|
+
type: "adaptive",
|
|
257
|
+
level: "high",
|
|
258
|
+
streamThinking: false,
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const config = lastStreamParams!.config as Record<string, unknown>;
|
|
265
|
+
expect(config.thinkingConfig).toEqual({
|
|
266
|
+
thinkingLevel: "HIGH",
|
|
267
|
+
includeThoughts: false,
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("maps wire { type: 'disabled' } to MINIMAL thinking level", async () => {
|
|
272
|
+
fakeChunks = [textChunk("OK"), finishChunk("STOP", 10, 2)];
|
|
273
|
+
|
|
274
|
+
await provider.sendMessage(
|
|
275
|
+
[{ role: "user", content: [{ type: "text", text: "Hi" }] }],
|
|
276
|
+
undefined,
|
|
277
|
+
undefined,
|
|
278
|
+
{ config: { thinking: { type: "disabled" } } },
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
const config = lastStreamParams!.config as Record<string, unknown>;
|
|
282
|
+
expect(config.thinkingConfig).toEqual({
|
|
283
|
+
thinkingLevel: "MINIMAL",
|
|
284
|
+
includeThoughts: false,
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("omits thinkingConfig when wire shape is adaptive with no extras", async () => {
|
|
289
|
+
fakeChunks = [textChunk("OK"), finishChunk("STOP", 10, 2)];
|
|
290
|
+
|
|
291
|
+
await provider.sendMessage(
|
|
292
|
+
[{ role: "user", content: [{ type: "text", text: "Hi" }] }],
|
|
293
|
+
undefined,
|
|
294
|
+
undefined,
|
|
295
|
+
{ config: { thinking: { type: "adaptive" } } },
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const config = lastStreamParams!.config as Record<string, unknown>;
|
|
299
|
+
// No level/streamThinking → omit so Google's per-model default applies
|
|
300
|
+
// (Gemini 3.x defaults to "medium" with dynamic thinking).
|
|
301
|
+
expect(config.thinkingConfig).toBeUndefined();
|
|
302
|
+
});
|
|
303
|
+
|
|
226
304
|
// -----------------------------------------------------------------------
|
|
227
305
|
// Tool definitions
|
|
228
306
|
// -----------------------------------------------------------------------
|
|
@@ -280,11 +280,13 @@ describe("startOutbound", () => {
|
|
|
280
280
|
expect(voiceCallInitCalls.length).toBe(1);
|
|
281
281
|
});
|
|
282
282
|
|
|
283
|
-
test("
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
283
|
+
test("email channel creates outbound session", async () => {
|
|
284
|
+
const result = await startOutbound({
|
|
285
|
+
channel: "email",
|
|
286
|
+
destination: "user@example.com",
|
|
287
|
+
});
|
|
288
|
+
expect(result.success).toBe(true);
|
|
289
|
+
expect(result.channel).toBe("email");
|
|
288
290
|
});
|
|
289
291
|
});
|
|
290
292
|
|
|
@@ -193,6 +193,6 @@ describe("handleConfirmationResponse canonical status sync", () => {
|
|
|
193
193
|
// Canonical status sync is now handled inside Conversation.handleConfirmationResponse,
|
|
194
194
|
// which this test mocks out — so the handler itself no longer calls resolveCanonicalGuardianRequest.
|
|
195
195
|
expect(resolveCanonicalGuardianRequestMock).not.toHaveBeenCalled();
|
|
196
|
-
expect(resolveMock).toHaveBeenCalledWith("req-confirm-allow");
|
|
196
|
+
expect(resolveMock).toHaveBeenCalledWith("req-confirm-allow", "approved");
|
|
197
197
|
});
|
|
198
198
|
});
|
|
@@ -91,6 +91,10 @@ mock.module("../notifications/emit-signal.js", () => ({
|
|
|
91
91
|
mock.module("../prompts/persona-resolver.js", () => ({
|
|
92
92
|
GUARDIAN_PERSONA_TEMPLATE: "# User Profile\n",
|
|
93
93
|
resolveGuardianPersona: () => "# User Profile\n",
|
|
94
|
+
// buildSystemPrompt now uses resolveUserSlug (for ctx) instead of
|
|
95
|
+
// resolvePersonaContext — give the mock a noop so the import
|
|
96
|
+
// doesn't fail.
|
|
97
|
+
resolveUserSlug: () => null,
|
|
94
98
|
}));
|
|
95
99
|
|
|
96
100
|
mock.module("../memory/conversation-title-service.js", () => ({
|
|
@@ -112,6 +112,10 @@ let mockGuardianPersona: string | null = null;
|
|
|
112
112
|
mock.module("../prompts/persona-resolver.js", () => ({
|
|
113
113
|
GUARDIAN_PERSONA_TEMPLATE,
|
|
114
114
|
resolveGuardianPersona: () => mockGuardianPersona,
|
|
115
|
+
// buildSystemPrompt now uses resolveUserSlug (for ctx) instead of
|
|
116
|
+
// resolvePersonaContext — give the mock a noop so the import
|
|
117
|
+
// doesn't fail.
|
|
118
|
+
resolveUserSlug: () => null,
|
|
115
119
|
}));
|
|
116
120
|
|
|
117
121
|
// Mock conversation store
|
|
@@ -264,7 +264,7 @@ describe("host_bash — baseline: no sandbox isolation", () => {
|
|
|
264
264
|
// These tests lock that boundary so any accidental addition is caught.
|
|
265
265
|
|
|
266
266
|
describe("host_bash — regression: no proxied-mode additions", () => {
|
|
267
|
-
const definition = hostShellTool
|
|
267
|
+
const definition = hostShellTool;
|
|
268
268
|
const schemaProps = (definition.input_schema as Record<string, unknown>)
|
|
269
269
|
.properties as Record<string, unknown>;
|
|
270
270
|
|
|
@@ -47,9 +47,9 @@ describe("initFeatureFlagOverrides", () => {
|
|
|
47
47
|
// through the backoff schedule.
|
|
48
48
|
await initFeatureFlagOverrides({ retryBackoffsMs: [] });
|
|
49
49
|
|
|
50
|
-
// Without gateway data or file, undeclared flags default to
|
|
50
|
+
// Without gateway data or file, undeclared flags default to false
|
|
51
51
|
const config = {} as any;
|
|
52
|
-
expect(isAssistantFeatureFlagEnabled("foo-enabled", config)).toBe(
|
|
52
|
+
expect(isAssistantFeatureFlagEnabled("foo-enabled", config)).toBe(false);
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
it("respects false values from gateway IPC", async () => {
|
|
@@ -72,10 +72,9 @@ describe("initFeatureFlagOverrides", () => {
|
|
|
72
72
|
// through the production backoff schedule.
|
|
73
73
|
await initFeatureFlagOverrides({ retryBackoffsMs: [] });
|
|
74
74
|
|
|
75
|
-
// Undeclared flags without overrides default to
|
|
76
|
-
// a cached empty map)
|
|
75
|
+
// Undeclared flags without overrides default to false.
|
|
77
76
|
const config = {} as any;
|
|
78
|
-
expect(isAssistantFeatureFlagEnabled("foo-enabled", config)).toBe(
|
|
77
|
+
expect(isAssistantFeatureFlagEnabled("foo-enabled", config)).toBe(false);
|
|
79
78
|
});
|
|
80
79
|
|
|
81
80
|
it("retries empty gateway responses and picks up flags once they become available", async () => {
|
|
@@ -111,6 +110,6 @@ describe("initFeatureFlagOverrides", () => {
|
|
|
111
110
|
// first-call must still be true (from the cached first fetch)
|
|
112
111
|
expect(isAssistantFeatureFlagEnabled("first-call", config)).toBe(true);
|
|
113
112
|
// second-call should not be in the cache since init was a no-op
|
|
114
|
-
expect(isAssistantFeatureFlagEnabled("second-call", config)).toBe(
|
|
113
|
+
expect(isAssistantFeatureFlagEnabled("second-call", config)).toBe(false);
|
|
115
114
|
});
|
|
116
115
|
});
|
|
@@ -175,15 +175,13 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
175
175
|
expect(body.messages[2].content).toBe("how are you?");
|
|
176
176
|
});
|
|
177
177
|
|
|
178
|
-
test("tool_result at
|
|
178
|
+
test("orphan tool_result at pagination boundary is suppressed entirely", async () => {
|
|
179
179
|
const conv = createConversation();
|
|
180
|
-
// Orphan tool_result with no preceding assistant
|
|
181
|
-
// tool_use
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
// (mergeToolResultsIntoAssistantMessages keeps it to avoid data loss
|
|
186
|
-
// in case the matching tool_use lives on the previous page).
|
|
180
|
+
// Orphan tool_result with no preceding assistant in this page. The
|
|
181
|
+
// matching tool_use lives on the previous page, so renderHistoryContent
|
|
182
|
+
// would silently drop the orphan downstream and leave a blank user
|
|
183
|
+
// bubble. The merger now suppresses the user row outright at the
|
|
184
|
+
// boundary to prevent that blank bubble from reaching the client.
|
|
187
185
|
await addMessage(
|
|
188
186
|
conv.id,
|
|
189
187
|
"user",
|
|
@@ -204,13 +202,74 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
204
202
|
const response = handleListMessages(createTestArgs(conv.id));
|
|
205
203
|
const body = response as { messages: MessagePayload[] };
|
|
206
204
|
|
|
207
|
-
//
|
|
208
|
-
|
|
205
|
+
// User row dropped entirely; only the assistant survives.
|
|
206
|
+
expect(body.messages).toHaveLength(1);
|
|
207
|
+
expect(body.messages[0].role).toBe("assistant");
|
|
208
|
+
expect(body.messages[0].content).toBe("response");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("mixed content at pagination boundary keeps real user text", async () => {
|
|
212
|
+
const conv = createConversation();
|
|
213
|
+
// Row has both real user text AND an orphan tool_result. The orphan
|
|
214
|
+
// gets stripped (it'd be dropped by renderHistoryContent anyway), but
|
|
215
|
+
// the user text is preserved.
|
|
216
|
+
await addMessage(
|
|
217
|
+
conv.id,
|
|
218
|
+
"user",
|
|
219
|
+
JSON.stringify([
|
|
220
|
+
{
|
|
221
|
+
type: "tool_result",
|
|
222
|
+
tool_use_id: "tu_orphan",
|
|
223
|
+
content: "stale result",
|
|
224
|
+
},
|
|
225
|
+
{ type: "text", text: "what about this?" },
|
|
226
|
+
]),
|
|
227
|
+
);
|
|
228
|
+
await addMessage(
|
|
229
|
+
conv.id,
|
|
230
|
+
"assistant",
|
|
231
|
+
JSON.stringify([{ type: "text", text: "answering" }]),
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
235
|
+
const body = response as { messages: MessagePayload[] };
|
|
236
|
+
|
|
209
237
|
expect(body.messages).toHaveLength(2);
|
|
210
238
|
expect(body.messages[0].role).toBe("user");
|
|
239
|
+
expect(body.messages[0].content).toBe("what about this?");
|
|
211
240
|
expect(body.messages[0].toolCalls).toBeUndefined();
|
|
212
241
|
expect(body.messages[1].role).toBe("assistant");
|
|
213
|
-
expect(body.messages[1].content).toBe("
|
|
242
|
+
expect(body.messages[1].content).toBe("answering");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("orphan tool_result + system_notice at boundary is suppressed", async () => {
|
|
246
|
+
const conv = createConversation();
|
|
247
|
+
// System notices ride alongside tool_results in the agent loop. At a
|
|
248
|
+
// boundary they shouldn't keep the row alive on their own — the row
|
|
249
|
+
// is still semantically tool-result-only from the user's perspective.
|
|
250
|
+
await addMessage(
|
|
251
|
+
conv.id,
|
|
252
|
+
"user",
|
|
253
|
+
JSON.stringify([
|
|
254
|
+
{
|
|
255
|
+
type: "tool_result",
|
|
256
|
+
tool_use_id: "tu_orphan",
|
|
257
|
+
content: "stale",
|
|
258
|
+
},
|
|
259
|
+
{ type: "text", text: "<system_notice>internal</system_notice>" },
|
|
260
|
+
]),
|
|
261
|
+
);
|
|
262
|
+
await addMessage(
|
|
263
|
+
conv.id,
|
|
264
|
+
"assistant",
|
|
265
|
+
JSON.stringify([{ type: "text", text: "ok" }]),
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const response = handleListMessages(createTestArgs(conv.id));
|
|
269
|
+
const body = response as { messages: MessagePayload[] };
|
|
270
|
+
|
|
271
|
+
expect(body.messages).toHaveLength(1);
|
|
272
|
+
expect(body.messages[0].role).toBe("assistant");
|
|
214
273
|
});
|
|
215
274
|
|
|
216
275
|
test("multi-turn: each tool_result merges into correct assistant", async () => {
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the `call_site` column on `llm_request_logs`. The column is
|
|
3
|
+
* stamped by `recordRequestLog` at insertion time and surfaces in
|
|
4
|
+
* `LogRow.callSite`. Historical rows (pre-migration 264) stay NULL —
|
|
5
|
+
* "we don't know" rather than guessing `mainAgent`.
|
|
6
|
+
*/
|
|
7
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
8
|
+
|
|
9
|
+
mock.module("../util/logger.js", () => ({
|
|
10
|
+
getLogger: () =>
|
|
11
|
+
new Proxy({} as Record<string, unknown>, {
|
|
12
|
+
get: () => () => {},
|
|
13
|
+
}),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
mock.module("../config/loader.js", () => ({
|
|
17
|
+
getConfig: () => ({
|
|
18
|
+
ui: {},
|
|
19
|
+
model: "test",
|
|
20
|
+
provider: "test",
|
|
21
|
+
memory: { enabled: false },
|
|
22
|
+
rateLimit: { maxRequestsPerMinute: 0 },
|
|
23
|
+
secretDetection: { enabled: false },
|
|
24
|
+
}),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
import { getDb, getSqliteFrom } from "../memory/db-connection.js";
|
|
28
|
+
import { initializeDb } from "../memory/db-init.js";
|
|
29
|
+
import {
|
|
30
|
+
getRequestLogById,
|
|
31
|
+
recordRequestLog,
|
|
32
|
+
} from "../memory/llm-request-log-store.js";
|
|
33
|
+
import { migrateLlmRequestLogCallSite } from "../memory/migrations/264-llm-request-log-call-site.js";
|
|
34
|
+
import { llmRequestLogs } from "../memory/schema.js";
|
|
35
|
+
|
|
36
|
+
initializeDb();
|
|
37
|
+
|
|
38
|
+
function resetLogs(): void {
|
|
39
|
+
const db = getDb();
|
|
40
|
+
db.delete(llmRequestLogs).run();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("recordRequestLog call_site stamping", () => {
|
|
44
|
+
beforeEach(resetLogs);
|
|
45
|
+
|
|
46
|
+
test("stamps callSite when provided", () => {
|
|
47
|
+
const id = recordRequestLog(
|
|
48
|
+
"conv-1",
|
|
49
|
+
'{"req":1}',
|
|
50
|
+
'{"res":1}',
|
|
51
|
+
undefined,
|
|
52
|
+
"anthropic",
|
|
53
|
+
"mainAgent",
|
|
54
|
+
);
|
|
55
|
+
const row = getRequestLogById(id);
|
|
56
|
+
expect(row).not.toBeNull();
|
|
57
|
+
expect(row!.callSite).toBe("mainAgent");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("leaves callSite NULL when omitted (backward compat)", () => {
|
|
61
|
+
const id = recordRequestLog("conv-1", '{"req":1}', '{"res":1}');
|
|
62
|
+
const row = getRequestLogById(id);
|
|
63
|
+
expect(row).not.toBeNull();
|
|
64
|
+
expect(row!.callSite).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("supports the compactionAgent value", () => {
|
|
68
|
+
const id = recordRequestLog(
|
|
69
|
+
"conv-1",
|
|
70
|
+
'{"req":1}',
|
|
71
|
+
'{"res":1}',
|
|
72
|
+
undefined,
|
|
73
|
+
"anthropic",
|
|
74
|
+
"compactionAgent",
|
|
75
|
+
);
|
|
76
|
+
expect(getRequestLogById(id)?.callSite).toBe("compactionAgent");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("two rows in the same conversation can carry different callSites", () => {
|
|
80
|
+
const mainId = recordRequestLog(
|
|
81
|
+
"conv-1",
|
|
82
|
+
'{"req":1}',
|
|
83
|
+
'{"res":1}',
|
|
84
|
+
undefined,
|
|
85
|
+
"anthropic",
|
|
86
|
+
"mainAgent",
|
|
87
|
+
);
|
|
88
|
+
const compactId = recordRequestLog(
|
|
89
|
+
"conv-1",
|
|
90
|
+
'{"req":2}',
|
|
91
|
+
'{"res":2}',
|
|
92
|
+
undefined,
|
|
93
|
+
"anthropic",
|
|
94
|
+
"compactionAgent",
|
|
95
|
+
);
|
|
96
|
+
expect(getRequestLogById(mainId)?.callSite).toBe("mainAgent");
|
|
97
|
+
expect(getRequestLogById(compactId)?.callSite).toBe("compactionAgent");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("migrateLlmRequestLogCallSite", () => {
|
|
102
|
+
test("adds the call_site column when missing", () => {
|
|
103
|
+
const db = getDb();
|
|
104
|
+
const raw = getSqliteFrom(db);
|
|
105
|
+
|
|
106
|
+
// Drop the column if present (simulate pre-264 state). SQLite supports
|
|
107
|
+
// `DROP COLUMN` since 3.35 (June 2021) — bun-sqlite ships well past that.
|
|
108
|
+
const before = raw
|
|
109
|
+
.query(`PRAGMA table_info(llm_request_logs)`)
|
|
110
|
+
.all() as Array<{ name: string }>;
|
|
111
|
+
if (before.some((c) => c.name === "call_site")) {
|
|
112
|
+
raw.exec(`ALTER TABLE llm_request_logs DROP COLUMN call_site`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const without = raw
|
|
116
|
+
.query(`PRAGMA table_info(llm_request_logs)`)
|
|
117
|
+
.all() as Array<{ name: string }>;
|
|
118
|
+
expect(without.some((c) => c.name === "call_site")).toBe(false);
|
|
119
|
+
|
|
120
|
+
migrateLlmRequestLogCallSite(db);
|
|
121
|
+
|
|
122
|
+
const after = raw
|
|
123
|
+
.query(`PRAGMA table_info(llm_request_logs)`)
|
|
124
|
+
.all() as Array<{ name: string }>;
|
|
125
|
+
expect(after.some((c) => c.name === "call_site")).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("is idempotent — second run is a no-op", () => {
|
|
129
|
+
const db = getDb();
|
|
130
|
+
// First run (column may or may not exist depending on test order; either
|
|
131
|
+
// path is fine for the idempotency contract).
|
|
132
|
+
migrateLlmRequestLogCallSite(db);
|
|
133
|
+
// Second run must not throw.
|
|
134
|
+
expect(() => migrateLlmRequestLogCallSite(db)).not.toThrow();
|
|
135
|
+
});
|
|
136
|
+
});
|