@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
|
@@ -214,6 +214,115 @@ describe("forkConversation", () => {
|
|
|
214
214
|
]);
|
|
215
215
|
});
|
|
216
216
|
|
|
217
|
+
test("advances fork boundary through consecutive assistant rows after the requested message", async () => {
|
|
218
|
+
// When the read-path merges consecutive assistant DB rows into a single
|
|
219
|
+
// display row, the client only addresses the anchor id. Forking through
|
|
220
|
+
// the anchor must still include the merged tail rows that follow.
|
|
221
|
+
const source = createConversation("Multi-row turn thread");
|
|
222
|
+
await addMessage(source.id, "user", "Message 1", undefined, {
|
|
223
|
+
skipIndexing: true,
|
|
224
|
+
});
|
|
225
|
+
const anchor = await addMessage(
|
|
226
|
+
source.id,
|
|
227
|
+
"assistant",
|
|
228
|
+
"Assistant text segment",
|
|
229
|
+
undefined,
|
|
230
|
+
{ skipIndexing: true },
|
|
231
|
+
);
|
|
232
|
+
const toolRow = await addMessage(
|
|
233
|
+
source.id,
|
|
234
|
+
"assistant",
|
|
235
|
+
"Tool turn row",
|
|
236
|
+
undefined,
|
|
237
|
+
{ skipIndexing: true },
|
|
238
|
+
);
|
|
239
|
+
const tailRow = await addMessage(
|
|
240
|
+
source.id,
|
|
241
|
+
"assistant",
|
|
242
|
+
"Final assistant segment",
|
|
243
|
+
undefined,
|
|
244
|
+
{ skipIndexing: true },
|
|
245
|
+
);
|
|
246
|
+
await addMessage(source.id, "user", "Next user turn", undefined, {
|
|
247
|
+
skipIndexing: true,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const fork = forkConversation({
|
|
251
|
+
conversationId: source.id,
|
|
252
|
+
throughMessageId: anchor.id,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Boundary advances past the entire consecutive-assistant cluster, so the
|
|
256
|
+
// full turn is preserved in the fork — not just the anchor row.
|
|
257
|
+
expect(getMessages(fork.id).map((message) => message.content)).toEqual([
|
|
258
|
+
"Message 1",
|
|
259
|
+
"Assistant text segment",
|
|
260
|
+
"Tool turn row",
|
|
261
|
+
"Final assistant segment",
|
|
262
|
+
]);
|
|
263
|
+
expect(fork.forkParentMessageId).toBe(tailRow.id);
|
|
264
|
+
expect(toolRow.id).not.toBe(anchor.id);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("advances fork boundary across tool-result-only user rows between assistant rows", async () => {
|
|
268
|
+
// Read-path collapse folds tool-result-only user rows into the
|
|
269
|
+
// surrounding assistant turn (`mergeToolResultsIntoAssistantMessages`
|
|
270
|
+
// suppresses them). The client only sees a single display turn anchored
|
|
271
|
+
// at the first assistant row, so forking through the anchor must include
|
|
272
|
+
// both halves of the assistant turn plus the suppressed user row in
|
|
273
|
+
// between — otherwise the fork loses tool_use ↔ tool_result pairing
|
|
274
|
+
// and produces an invalid LLM history.
|
|
275
|
+
const source = createConversation("Tool-result gap thread");
|
|
276
|
+
await addMessage(source.id, "user", "Find the latest sales numbers", undefined, {
|
|
277
|
+
skipIndexing: true,
|
|
278
|
+
});
|
|
279
|
+
const anchor = await addMessage(
|
|
280
|
+
source.id,
|
|
281
|
+
"assistant",
|
|
282
|
+
JSON.stringify([
|
|
283
|
+
{ type: "text", text: "Looking up the data." },
|
|
284
|
+
{ type: "tool_use", id: "tool_1", name: "lookup", input: {} },
|
|
285
|
+
]),
|
|
286
|
+
undefined,
|
|
287
|
+
{ skipIndexing: true },
|
|
288
|
+
);
|
|
289
|
+
const toolResultUserRow = await addMessage(
|
|
290
|
+
source.id,
|
|
291
|
+
"user",
|
|
292
|
+
JSON.stringify([
|
|
293
|
+
{ type: "tool_result", tool_use_id: "tool_1", content: "data" },
|
|
294
|
+
]),
|
|
295
|
+
undefined,
|
|
296
|
+
{ skipIndexing: true },
|
|
297
|
+
);
|
|
298
|
+
const tailAssistantRow = await addMessage(
|
|
299
|
+
source.id,
|
|
300
|
+
"assistant",
|
|
301
|
+
"Here are the numbers.",
|
|
302
|
+
undefined,
|
|
303
|
+
{ skipIndexing: true },
|
|
304
|
+
);
|
|
305
|
+
await addMessage(source.id, "user", "Thanks", undefined, {
|
|
306
|
+
skipIndexing: true,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const fork = forkConversation({
|
|
310
|
+
conversationId: source.id,
|
|
311
|
+
throughMessageId: anchor.id,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// All three DB rows of the assistant display turn — including the
|
|
315
|
+
// suppressed tool-result user row in the middle — land in the fork.
|
|
316
|
+
const forkedContent = getMessages(fork.id).map((m) => m.content);
|
|
317
|
+
expect(forkedContent).toHaveLength(4);
|
|
318
|
+
expect(forkedContent[0]).toBe("Find the latest sales numbers");
|
|
319
|
+
expect(forkedContent[1]).toContain("tool_use");
|
|
320
|
+
expect(forkedContent[2]).toContain("tool_result");
|
|
321
|
+
expect(forkedContent[3]).toBe("Here are the numbers.");
|
|
322
|
+
expect(fork.forkParentMessageId).toBe(tailAssistantRow.id);
|
|
323
|
+
expect(toolResultUserRow.id).not.toBe(anchor.id);
|
|
324
|
+
});
|
|
325
|
+
|
|
217
326
|
test("preserves compacted context when forking from the visible window", async () => {
|
|
218
327
|
const source = createConversation("Compacted thread");
|
|
219
328
|
await addMessage(source.id, "user", "Message 1", undefined, {
|
|
@@ -297,7 +406,7 @@ describe("forkConversation", () => {
|
|
|
297
406
|
]);
|
|
298
407
|
});
|
|
299
408
|
|
|
300
|
-
test("inherits
|
|
409
|
+
test("inherits historyStrippedAt when forking past the clean event", async () => {
|
|
301
410
|
const source = createConversation("Clean thread");
|
|
302
411
|
await addMessage(source.id, "user", "Message 1", undefined, {
|
|
303
412
|
skipIndexing: true,
|
|
@@ -310,10 +419,10 @@ describe("forkConversation", () => {
|
|
|
310
419
|
{ skipIndexing: true },
|
|
311
420
|
);
|
|
312
421
|
|
|
313
|
-
const
|
|
422
|
+
const historyStrippedAt = preClean.createdAt + 1;
|
|
314
423
|
getDb()
|
|
315
424
|
.update(conversations)
|
|
316
|
-
.set({
|
|
425
|
+
.set({ historyStrippedAt })
|
|
317
426
|
.where(eq(conversations.id, source.id))
|
|
318
427
|
.run();
|
|
319
428
|
|
|
@@ -324,17 +433,17 @@ describe("forkConversation", () => {
|
|
|
324
433
|
undefined,
|
|
325
434
|
{ skipIndexing: true },
|
|
326
435
|
);
|
|
327
|
-
expect(postClean.createdAt).toBeGreaterThanOrEqual(
|
|
436
|
+
expect(postClean.createdAt).toBeGreaterThanOrEqual(historyStrippedAt);
|
|
328
437
|
|
|
329
438
|
const fork = forkConversation({
|
|
330
439
|
conversationId: source.id,
|
|
331
440
|
throughMessageId: postClean.id,
|
|
332
441
|
});
|
|
333
442
|
|
|
334
|
-
expect(fork.
|
|
443
|
+
expect(fork.historyStrippedAt).toBe(historyStrippedAt);
|
|
335
444
|
});
|
|
336
445
|
|
|
337
|
-
test("does not inherit
|
|
446
|
+
test("does not inherit historyStrippedAt when forking before the clean event", async () => {
|
|
338
447
|
const source = createConversation("Clean thread");
|
|
339
448
|
await addMessage(source.id, "user", "Message 1", undefined, {
|
|
340
449
|
skipIndexing: true,
|
|
@@ -347,10 +456,10 @@ describe("forkConversation", () => {
|
|
|
347
456
|
{ skipIndexing: true },
|
|
348
457
|
);
|
|
349
458
|
|
|
350
|
-
const
|
|
459
|
+
const historyStrippedAt = preClean.createdAt + 1;
|
|
351
460
|
getDb()
|
|
352
461
|
.update(conversations)
|
|
353
|
-
.set({
|
|
462
|
+
.set({ historyStrippedAt })
|
|
354
463
|
.where(eq(conversations.id, source.id))
|
|
355
464
|
.run();
|
|
356
465
|
|
|
@@ -363,10 +472,10 @@ describe("forkConversation", () => {
|
|
|
363
472
|
throughMessageId: preClean.id,
|
|
364
473
|
});
|
|
365
474
|
|
|
366
|
-
expect(fork.
|
|
475
|
+
expect(fork.historyStrippedAt).toBeNull();
|
|
367
476
|
});
|
|
368
477
|
|
|
369
|
-
test("inherits
|
|
478
|
+
test("inherits historyStrippedAt on a full-history fork", async () => {
|
|
370
479
|
const source = createConversation("Clean thread");
|
|
371
480
|
await addMessage(source.id, "user", "Message 1", undefined, {
|
|
372
481
|
skipIndexing: true,
|
|
@@ -379,19 +488,19 @@ describe("forkConversation", () => {
|
|
|
379
488
|
{ skipIndexing: true },
|
|
380
489
|
);
|
|
381
490
|
|
|
382
|
-
const
|
|
491
|
+
const historyStrippedAt = last.createdAt - 1;
|
|
383
492
|
getDb()
|
|
384
493
|
.update(conversations)
|
|
385
|
-
.set({
|
|
494
|
+
.set({ historyStrippedAt })
|
|
386
495
|
.where(eq(conversations.id, source.id))
|
|
387
496
|
.run();
|
|
388
497
|
|
|
389
498
|
const fork = forkConversation({ conversationId: source.id });
|
|
390
499
|
|
|
391
|
-
expect(fork.
|
|
500
|
+
expect(fork.historyStrippedAt).toBe(historyStrippedAt);
|
|
392
501
|
});
|
|
393
502
|
|
|
394
|
-
test("leaves
|
|
503
|
+
test("leaves historyStrippedAt null when the source has no clean event", async () => {
|
|
395
504
|
const source = createConversation("Unclean thread");
|
|
396
505
|
await addMessage(source.id, "user", "Message 1", undefined, {
|
|
397
506
|
skipIndexing: true,
|
|
@@ -402,7 +511,61 @@ describe("forkConversation", () => {
|
|
|
402
511
|
|
|
403
512
|
const fork = forkConversation({ conversationId: source.id });
|
|
404
513
|
|
|
405
|
-
expect(fork.
|
|
514
|
+
expect(fork.historyStrippedAt).toBeNull();
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test("fork from a pre-compaction message preserves historical injection metadata", async () => {
|
|
518
|
+
const source = createConversation("Compacted thread");
|
|
519
|
+
const m1 = await addMessage(
|
|
520
|
+
source.id,
|
|
521
|
+
"user",
|
|
522
|
+
"Historical question",
|
|
523
|
+
{
|
|
524
|
+
pkbContextBlock: "<knowledge_base>\nstale\n</knowledge_base>",
|
|
525
|
+
nowScratchpadBlock:
|
|
526
|
+
"<NOW.md Always keep this up to date>\nstale\n</NOW.md>",
|
|
527
|
+
},
|
|
528
|
+
{ skipIndexing: true },
|
|
529
|
+
);
|
|
530
|
+
await addMessage(source.id, "assistant", "Reply 1", undefined, {
|
|
531
|
+
skipIndexing: true,
|
|
532
|
+
});
|
|
533
|
+
await addMessage(source.id, "user", "Tail turn", undefined, {
|
|
534
|
+
skipIndexing: true,
|
|
535
|
+
});
|
|
536
|
+
const compactedAt = Date.now();
|
|
537
|
+
getDb()
|
|
538
|
+
.update(conversations)
|
|
539
|
+
.set({
|
|
540
|
+
contextSummary: "summary",
|
|
541
|
+
contextCompactedMessageCount: 2,
|
|
542
|
+
contextCompactedAt: compactedAt,
|
|
543
|
+
historyStrippedAt: compactedAt,
|
|
544
|
+
})
|
|
545
|
+
.where(eq(conversations.id, source.id))
|
|
546
|
+
.run();
|
|
547
|
+
|
|
548
|
+
const fork = forkConversation({
|
|
549
|
+
conversationId: source.id,
|
|
550
|
+
throughMessageId: m1.id,
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
expect(fork.historyStrippedAt).toBeNull();
|
|
554
|
+
expect(fork.contextSummary).toBeNull();
|
|
555
|
+
expect(fork.contextCompactedMessageCount).toBe(0);
|
|
556
|
+
|
|
557
|
+
const forkedMessages = getMessages(fork.id);
|
|
558
|
+
expect(forkedMessages).toHaveLength(1);
|
|
559
|
+
const meta = parseMetadata(forkedMessages[0].metadata) as Record<
|
|
560
|
+
string,
|
|
561
|
+
unknown
|
|
562
|
+
>;
|
|
563
|
+
expect(meta.pkbContextBlock).toBe(
|
|
564
|
+
"<knowledge_base>\nstale\n</knowledge_base>",
|
|
565
|
+
);
|
|
566
|
+
expect(meta.nowScratchpadBlock).toBe(
|
|
567
|
+
"<NOW.md Always keep this up to date>\nstale\n</NOW.md>",
|
|
568
|
+
);
|
|
406
569
|
});
|
|
407
570
|
|
|
408
571
|
test("rejects forks when the source conversation has no persisted messages", () => {
|
|
@@ -379,7 +379,7 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
379
379
|
metadata: JSON.stringify({
|
|
380
380
|
memoryInjectedBlock: "mem payload",
|
|
381
381
|
memoryV2StaticBlock:
|
|
382
|
-
"<
|
|
382
|
+
"<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
|
|
383
383
|
pkbSystemReminderBlock:
|
|
384
384
|
"<system_reminder>\npkb payload\n</system_reminder>",
|
|
385
385
|
}),
|
|
@@ -406,7 +406,7 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
406
406
|
{ type: "text", text: "<memory>\nmem payload\n</memory>" },
|
|
407
407
|
{
|
|
408
408
|
type: "text",
|
|
409
|
-
text: "<
|
|
409
|
+
text: "<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
|
|
410
410
|
},
|
|
411
411
|
{
|
|
412
412
|
type: "text",
|
|
@@ -416,6 +416,47 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
416
416
|
]);
|
|
417
417
|
});
|
|
418
418
|
|
|
419
|
+
test("legacy <memory>-wrapped memoryV2StaticBlock rehydrates verbatim", async () => {
|
|
420
|
+
// `meta.memoryV2StaticBlock` may carry either `<info>…</info>` or
|
|
421
|
+
// legacy `<memory>…</memory>` wrappers depending on when the row was
|
|
422
|
+
// persisted. The rehydrate path replays the stored text verbatim,
|
|
423
|
+
// so both wrappers must round-trip unchanged.
|
|
424
|
+
mockConversation = defaultConv();
|
|
425
|
+
mockDbMessages = [
|
|
426
|
+
{
|
|
427
|
+
id: "m1",
|
|
428
|
+
role: "user",
|
|
429
|
+
content: JSON.stringify([{ type: "text", text: "First turn" }]),
|
|
430
|
+
metadata: JSON.stringify({
|
|
431
|
+
memoryV2StaticBlock:
|
|
432
|
+
"<memory>\n## Essentials\n\nAlice prefers VS Code.\n</memory>",
|
|
433
|
+
}),
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
id: "m2",
|
|
437
|
+
role: "assistant",
|
|
438
|
+
content: JSON.stringify([{ type: "text", text: "Reply" }]),
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
id: "m3",
|
|
442
|
+
role: "user",
|
|
443
|
+
content: JSON.stringify([{ type: "text", text: "Tail turn" }]),
|
|
444
|
+
},
|
|
445
|
+
];
|
|
446
|
+
|
|
447
|
+
const conversation = makeConversation();
|
|
448
|
+
await conversation.loadFromDb();
|
|
449
|
+
const messages = conversation.getMessages();
|
|
450
|
+
|
|
451
|
+
expect(messages[0].content).toEqual([
|
|
452
|
+
{
|
|
453
|
+
type: "text",
|
|
454
|
+
text: "<memory>\n## Essentials\n\nAlice prefers VS Code.\n</memory>",
|
|
455
|
+
},
|
|
456
|
+
{ type: "text", text: "First turn" },
|
|
457
|
+
]);
|
|
458
|
+
});
|
|
459
|
+
|
|
419
460
|
test("tail user row skips memoryV2StaticBlock", async () => {
|
|
420
461
|
mockConversation = defaultConv();
|
|
421
462
|
mockDbMessages = [
|
|
@@ -434,7 +475,7 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
434
475
|
role: "user",
|
|
435
476
|
content: JSON.stringify([{ type: "text", text: "Tail" }]),
|
|
436
477
|
metadata: JSON.stringify({
|
|
437
|
-
memoryV2StaticBlock: "<
|
|
478
|
+
memoryV2StaticBlock: "<info>\n## Essentials\n\nleak\n</info>",
|
|
438
479
|
}),
|
|
439
480
|
},
|
|
440
481
|
];
|
|
@@ -471,7 +512,7 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
471
512
|
// survive the row-level filter for non-guardian views.
|
|
472
513
|
provenanceTrustClass: "trusted_contact",
|
|
473
514
|
memoryV2StaticBlock:
|
|
474
|
-
"<
|
|
515
|
+
"<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
|
|
475
516
|
}),
|
|
476
517
|
},
|
|
477
518
|
{
|
|
@@ -500,7 +541,7 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
500
541
|
expect(messages[0].content).toEqual([
|
|
501
542
|
{
|
|
502
543
|
type: "text",
|
|
503
|
-
text: "<
|
|
544
|
+
text: "<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
|
|
504
545
|
},
|
|
505
546
|
{ type: "text", text: "First" },
|
|
506
547
|
]);
|
|
@@ -510,7 +551,7 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
510
551
|
// Injection-time layout (per `applyRuntimeInjections` after-memory-
|
|
511
552
|
// prefix splicing in ascending injector order: pkb-context 30,
|
|
512
553
|
// pkb-reminder 35, memory-v2-static 38, now-md 40):
|
|
513
|
-
// [<memory
|
|
554
|
+
// [<memory>dynamic</memory>, <info>v2static</info>, <NOW.md>,
|
|
514
555
|
// <system_reminder>, <knowledge_base>, ...original]
|
|
515
556
|
// Rehydration must reproduce this exactly so Anthropic's prefix cache
|
|
516
557
|
// matches msg[0] across daemon restarts.
|
|
@@ -523,7 +564,7 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
523
564
|
metadata: JSON.stringify({
|
|
524
565
|
memoryInjectedBlock: "mem payload",
|
|
525
566
|
memoryV2StaticBlock:
|
|
526
|
-
"<
|
|
567
|
+
"<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
|
|
527
568
|
nowScratchpadBlock: "<NOW.md>\nnow body\n</NOW.md>",
|
|
528
569
|
pkbSystemReminderBlock:
|
|
529
570
|
"<system_reminder>\npkb reminder body\n</system_reminder>",
|
|
@@ -551,7 +592,7 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
551
592
|
{ type: "text", text: "<memory>\nmem payload\n</memory>" },
|
|
552
593
|
{
|
|
553
594
|
type: "text",
|
|
554
|
-
text: "<
|
|
595
|
+
text: "<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
|
|
555
596
|
},
|
|
556
597
|
{ type: "text", text: "<NOW.md>\nnow body\n</NOW.md>" },
|
|
557
598
|
{
|
|
@@ -575,7 +616,7 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
575
616
|
metadata: JSON.stringify({
|
|
576
617
|
provenanceTrustClass: "trusted_contact",
|
|
577
618
|
memoryV2StaticBlock:
|
|
578
|
-
"<
|
|
619
|
+
"<info>\n## Essentials\n\nprivate memory\n</info>",
|
|
579
620
|
}),
|
|
580
621
|
},
|
|
581
622
|
{
|
|
@@ -621,7 +662,7 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
621
662
|
metadata: JSON.stringify({
|
|
622
663
|
provenanceTrustClass: "trusted_contact",
|
|
623
664
|
memoryV2StaticBlock:
|
|
624
|
-
"<
|
|
665
|
+
"<info>\n## Essentials\n\nprivate memory\n</info>",
|
|
625
666
|
}),
|
|
626
667
|
},
|
|
627
668
|
{
|
|
@@ -649,7 +690,7 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
649
690
|
expect(conversation.getMessages()[0].content).toEqual([
|
|
650
691
|
{
|
|
651
692
|
type: "text",
|
|
652
|
-
text: "<
|
|
693
|
+
text: "<info>\n## Essentials\n\nprivate memory\n</info>",
|
|
653
694
|
},
|
|
654
695
|
{ type: "text", text: "First" },
|
|
655
696
|
]);
|
|
@@ -84,7 +84,7 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
84
84
|
getConversation: () => mockConversation,
|
|
85
85
|
createConversation: () => ({ id: "conv-1" }),
|
|
86
86
|
addMessage: async () => ({ id: "persisted" }),
|
|
87
|
-
|
|
87
|
+
setConversationHistoryStrippedAt: () => {},
|
|
88
88
|
setConversationOriginChannelIfUnset: () => {},
|
|
89
89
|
setConversationOriginInterfaceIfUnset: () => {},
|
|
90
90
|
}));
|
|
@@ -123,19 +123,19 @@ function textBlocks(content: ReadonlyArray<{ type: string; text?: string }>) {
|
|
|
123
123
|
.map((b) => b.text);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
describe("loadFromDb with
|
|
126
|
+
describe("loadFromDb with historyStrippedAt", () => {
|
|
127
127
|
beforeEach(() => {
|
|
128
128
|
mockDbMessages = [];
|
|
129
129
|
mockConversation = null;
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
test("strips injection prefixes from pre-
|
|
133
|
-
const
|
|
132
|
+
test("strips injection prefixes from pre-strip user content", async () => {
|
|
133
|
+
const historyStrippedAt = 1000;
|
|
134
134
|
mockConversation = {
|
|
135
135
|
id: "conv-1",
|
|
136
136
|
contextSummary: null,
|
|
137
137
|
contextCompactedMessageCount: 0,
|
|
138
|
-
|
|
138
|
+
historyStrippedAt,
|
|
139
139
|
totalInputTokens: 0,
|
|
140
140
|
totalOutputTokens: 0,
|
|
141
141
|
totalEstimatedCost: 0,
|
|
@@ -186,13 +186,13 @@ describe("loadFromDb with cleanedAt", () => {
|
|
|
186
186
|
]);
|
|
187
187
|
});
|
|
188
188
|
|
|
189
|
-
test("skips metadata rehydration for pre-
|
|
190
|
-
const
|
|
189
|
+
test("skips metadata rehydration for pre-strip messages", async () => {
|
|
190
|
+
const historyStrippedAt = 1000;
|
|
191
191
|
mockConversation = {
|
|
192
192
|
id: "conv-1",
|
|
193
193
|
contextSummary: null,
|
|
194
194
|
contextCompactedMessageCount: 0,
|
|
195
|
-
|
|
195
|
+
historyStrippedAt,
|
|
196
196
|
totalInputTokens: 0,
|
|
197
197
|
totalOutputTokens: 0,
|
|
198
198
|
totalEstimatedCost: 0,
|
|
@@ -201,7 +201,7 @@ describe("loadFromDb with cleanedAt", () => {
|
|
|
201
201
|
{
|
|
202
202
|
id: "m1",
|
|
203
203
|
role: "user",
|
|
204
|
-
content: JSON.stringify([{ type: "text", text: "Pre-
|
|
204
|
+
content: JSON.stringify([{ type: "text", text: "Pre-strip turn" }]),
|
|
205
205
|
createdAt: 500,
|
|
206
206
|
metadata: JSON.stringify({
|
|
207
207
|
pkbContextBlock: "<knowledge_base>stale</knowledge_base>",
|
|
@@ -217,7 +217,7 @@ describe("loadFromDb with cleanedAt", () => {
|
|
|
217
217
|
id: "m3",
|
|
218
218
|
role: "user",
|
|
219
219
|
content: JSON.stringify([
|
|
220
|
-
{ type: "text", text: "Mid post-
|
|
220
|
+
{ type: "text", text: "Mid post-strip turn" },
|
|
221
221
|
]),
|
|
222
222
|
createdAt: 1500,
|
|
223
223
|
metadata: JSON.stringify({
|
|
@@ -236,18 +236,18 @@ describe("loadFromDb with cleanedAt", () => {
|
|
|
236
236
|
await conversation.loadFromDb();
|
|
237
237
|
const messages = conversation.getMessages();
|
|
238
238
|
|
|
239
|
-
expect(textBlocks(messages[0].content)).toEqual(["Pre-
|
|
239
|
+
expect(textBlocks(messages[0].content)).toEqual(["Pre-strip turn"]);
|
|
240
240
|
expect(textBlocks(messages[2].content).join("\n")).toContain(
|
|
241
241
|
"<knowledge_base>kept</knowledge_base>",
|
|
242
242
|
);
|
|
243
243
|
});
|
|
244
244
|
|
|
245
|
-
test("leaves messages untouched when
|
|
245
|
+
test("leaves messages untouched when historyStrippedAt is null", async () => {
|
|
246
246
|
mockConversation = {
|
|
247
247
|
id: "conv-1",
|
|
248
248
|
contextSummary: null,
|
|
249
249
|
contextCompactedMessageCount: 0,
|
|
250
|
-
|
|
250
|
+
historyStrippedAt: null,
|
|
251
251
|
totalInputTokens: 0,
|
|
252
252
|
totalOutputTokens: 0,
|
|
253
253
|
totalEstimatedCost: 0,
|
|
@@ -179,6 +179,7 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
179
179
|
updateConversationUsage: () => {},
|
|
180
180
|
updateConversationTitle: () => {},
|
|
181
181
|
updateConversationContextWindow: () => {},
|
|
182
|
+
setConversationHistoryStrippedAt: () => {},
|
|
182
183
|
getConversationOriginChannel: () => null,
|
|
183
184
|
getConversationOriginInterface: () => null,
|
|
184
185
|
provenanceFromTrustContext: () => ({}),
|
|
@@ -498,6 +498,115 @@ describe("macOS browser backend fallback (no extension, no cdp-inspect)", () =>
|
|
|
498
498
|
});
|
|
499
499
|
});
|
|
500
500
|
|
|
501
|
+
describe("POST /v1/messages — body.conversationId direct id lookup", () => {
|
|
502
|
+
// The handler accepts two scope inputs with distinct semantics:
|
|
503
|
+
//
|
|
504
|
+
// - `body.conversationId` is the assistant-minted internal id and is
|
|
505
|
+
// looked up directly. A missing row is a 404 — clients must obtain
|
|
506
|
+
// the id from a prior daemon response.
|
|
507
|
+
// - `body.conversationKey` is an external key (non-vellum channels /
|
|
508
|
+
// web idempotency); resolved via the conversation_keys table and
|
|
509
|
+
// materialised on first use.
|
|
510
|
+
//
|
|
511
|
+
// When both are sent, `conversationId` wins and `conversationKey` is
|
|
512
|
+
// ignored. (Don't combine — fetch by one and then the other.)
|
|
513
|
+
|
|
514
|
+
async function sendMessage(
|
|
515
|
+
body: Record<string, unknown>,
|
|
516
|
+
successStatus = 202,
|
|
517
|
+
): Promise<Response> {
|
|
518
|
+
return callHandler(
|
|
519
|
+
(args) =>
|
|
520
|
+
handleSendMessage(args, {
|
|
521
|
+
sendMessageDeps: {
|
|
522
|
+
getOrCreateConversation: async (conversationId: string) =>
|
|
523
|
+
getOrCreateFakeConversation(conversationId),
|
|
524
|
+
assistantEventHub: new AssistantEventHub(),
|
|
525
|
+
resolveAttachments: () => [],
|
|
526
|
+
},
|
|
527
|
+
}),
|
|
528
|
+
new Request("http://localhost/v1/messages", {
|
|
529
|
+
method: "POST",
|
|
530
|
+
headers: {
|
|
531
|
+
"Content-Type": "application/json",
|
|
532
|
+
"x-vellum-principal-type": authContext.principalType,
|
|
533
|
+
},
|
|
534
|
+
body: JSON.stringify(body),
|
|
535
|
+
}),
|
|
536
|
+
undefined,
|
|
537
|
+
successStatus,
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
test("body.conversationId=<existing-id> scopes the send to that conversation", async () => {
|
|
542
|
+
// Pre-materialise a conversation via the key path, then send a message
|
|
543
|
+
// by its assistant-minted internal id.
|
|
544
|
+
const externalKey = `pre-materialised-${crypto.randomUUID()}`;
|
|
545
|
+
const seeded = getOrCreateConversationMapping(externalKey);
|
|
546
|
+
|
|
547
|
+
const response = await sendMessage({
|
|
548
|
+
conversationId: seeded.conversationId,
|
|
549
|
+
content: "Direct id lookup — should reuse the existing conversation.",
|
|
550
|
+
sourceChannel: "vellum",
|
|
551
|
+
interface: "macos",
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
expect(response.status).toBe(202);
|
|
555
|
+
const body = (await response.json()) as {
|
|
556
|
+
accepted: boolean;
|
|
557
|
+
conversationId: string;
|
|
558
|
+
};
|
|
559
|
+
expect(body.accepted).toBe(true);
|
|
560
|
+
expect(body.conversationId).toBe(seeded.conversationId);
|
|
561
|
+
|
|
562
|
+
// No new external-key row should be materialised under the internal id.
|
|
563
|
+
expect(getConversationByKey(seeded.conversationId)).toBeNull();
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
test("body.conversationId=<non-existent-id> returns 404", async () => {
|
|
567
|
+
const response = await sendMessage(
|
|
568
|
+
{
|
|
569
|
+
conversationId: `does-not-exist-${crypto.randomUUID()}`,
|
|
570
|
+
content: "Should 404 — unknown internal id.",
|
|
571
|
+
sourceChannel: "vellum",
|
|
572
|
+
interface: "macos",
|
|
573
|
+
},
|
|
574
|
+
404,
|
|
575
|
+
);
|
|
576
|
+
expect(response.status).toBe(404);
|
|
577
|
+
const body = (await response.json()) as {
|
|
578
|
+
error?: { code?: string; message?: string };
|
|
579
|
+
};
|
|
580
|
+
expect(body.error?.code).toBe("NOT_FOUND");
|
|
581
|
+
expect(body.error?.message).toMatch(/not found/i);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
test("body.conversationId is honored and body.conversationKey is ignored when both are sent", async () => {
|
|
585
|
+
// Seed a conversation for the id we'll send. Also seed a separate
|
|
586
|
+
// conversation under a key the client will pass alongside — but the
|
|
587
|
+
// handler must scope to the id, NOT the key.
|
|
588
|
+
const idSeed = getOrCreateConversationMapping(
|
|
589
|
+
`id-honored-${crypto.randomUUID()}`,
|
|
590
|
+
);
|
|
591
|
+
const keyValue = `key-ignored-${crypto.randomUUID()}`;
|
|
592
|
+
const keySeed = getOrCreateConversationMapping(keyValue);
|
|
593
|
+
expect(idSeed.conversationId).not.toBe(keySeed.conversationId);
|
|
594
|
+
|
|
595
|
+
const response = await sendMessage({
|
|
596
|
+
conversationId: idSeed.conversationId,
|
|
597
|
+
conversationKey: keyValue,
|
|
598
|
+
content: "Both fields sent — id should win.",
|
|
599
|
+
sourceChannel: "vellum",
|
|
600
|
+
interface: "macos",
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
expect(response.status).toBe(202);
|
|
604
|
+
const body = (await response.json()) as { conversationId: string };
|
|
605
|
+
expect(body.conversationId).toBe(idSeed.conversationId);
|
|
606
|
+
expect(body.conversationId).not.toBe(keySeed.conversationId);
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
501
610
|
describe("conversationKey send path disk-view regression", () => {
|
|
502
611
|
test("first send on a fresh conversationKey creates disk-view dir and writes user+assistant records", async () => {
|
|
503
612
|
const conversationKey = `fresh-conv-key-${crypto.randomUUID()}`;
|
|
@@ -370,6 +370,41 @@ describe("handleSendMessage slash command interception", () => {
|
|
|
370
370
|
expect(runAgentLoop).not.toHaveBeenCalled();
|
|
371
371
|
});
|
|
372
372
|
|
|
373
|
+
test("clears processing and drains the queue when /compact's initial persist fails", async () => {
|
|
374
|
+
const { conversation } = makeConversation();
|
|
375
|
+
const drainQueue = mock(async () => {});
|
|
376
|
+
(
|
|
377
|
+
conversation as unknown as { drainQueue: () => Promise<void> }
|
|
378
|
+
).drainQueue = drainQueue;
|
|
379
|
+
|
|
380
|
+
// Force the user-message persist (the first addMessage in the /compact
|
|
381
|
+
// branch, on the synchronous pre-202 path) to throw.
|
|
382
|
+
addMessageMock.mockImplementationOnce(async () => {
|
|
383
|
+
throw new Error("disk full");
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// The failure surfaces to the caller rather than silently 202-ing.
|
|
387
|
+
let caught: Error | undefined;
|
|
388
|
+
try {
|
|
389
|
+
await callHandler(
|
|
390
|
+
(args) => handleSendMessage(args, makeDeps(conversation)),
|
|
391
|
+
makeRequest("/compact"),
|
|
392
|
+
undefined,
|
|
393
|
+
202,
|
|
394
|
+
);
|
|
395
|
+
} catch (err) {
|
|
396
|
+
caught = err as Error;
|
|
397
|
+
}
|
|
398
|
+
expect(caught?.message).toBe("disk full");
|
|
399
|
+
|
|
400
|
+
// Regression: without the guard `processing` stays stuck true, leaving
|
|
401
|
+
// every later send queued forever; the queue must also be drained.
|
|
402
|
+
expect(
|
|
403
|
+
(conversation as unknown as { processing: boolean }).processing,
|
|
404
|
+
).toBe(false);
|
|
405
|
+
expect(drainQueue).toHaveBeenCalledTimes(1);
|
|
406
|
+
});
|
|
407
|
+
|
|
373
408
|
test("passes regular messages through to agent loop unchanged", async () => {
|
|
374
409
|
const {
|
|
375
410
|
conversation,
|