@vellumai/assistant 0.5.13 → 0.5.14
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/.env.example +1 -6
- package/AGENTS.md +4 -0
- package/ARCHITECTURE.md +0 -1
- package/bunfig.toml +1 -0
- package/docs/architecture/memory.md +3 -3
- package/openapi.yaml +127 -22
- package/package.json +1 -1
- package/src/__tests__/access-request-decision.test.ts +2 -32
- package/src/__tests__/actor-token-service.test.ts +1 -31
- package/src/__tests__/anthropic-provider.test.ts +53 -40
- package/src/__tests__/app-git-history.test.ts +9 -17
- package/src/__tests__/app-git-service.test.ts +14 -20
- package/src/__tests__/app-store-dir-names.test.ts +10 -20
- package/src/__tests__/approval-cascade.test.ts +2 -19
- package/src/__tests__/approval-primitive.test.ts +2 -27
- package/src/__tests__/approval-routes-http.test.ts +2 -30
- package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -28
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -45
- package/src/__tests__/attachments-store.test.ts +5 -32
- package/src/__tests__/audit-log-rotation.test.ts +5 -36
- package/src/__tests__/avatar-e2e.test.ts +1 -9
- package/src/__tests__/avatar-generator.test.ts +1 -7
- package/src/__tests__/browser-fill-credential.test.ts +0 -4
- package/src/__tests__/browser-manager.test.ts +0 -6
- package/src/__tests__/call-controller.test.ts +1 -22
- package/src/__tests__/call-conversation-messages.test.ts +0 -21
- package/src/__tests__/call-domain.test.ts +0 -25
- package/src/__tests__/call-pointer-messages.test.ts +0 -21
- package/src/__tests__/call-recovery.test.ts +0 -22
- package/src/__tests__/call-routes-http.test.ts +0 -24
- package/src/__tests__/call-store.test.ts +0 -21
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -24
- package/src/__tests__/canonical-guardian-store.test.ts +48 -21
- package/src/__tests__/channel-approval-routes.test.ts +6 -26
- package/src/__tests__/channel-approvals.test.ts +1 -38
- package/src/__tests__/channel-delivery-store.test.ts +0 -21
- package/src/__tests__/channel-guardian.test.ts +0 -26
- package/src/__tests__/channel-reply-delivery.test.ts +5 -0
- package/src/__tests__/channel-retry-sweep.test.ts +0 -21
- package/src/__tests__/checker.test.ts +26 -61
- package/src/__tests__/clawhub.test.ts +9 -25
- package/src/__tests__/cli-command-risk-guard.test.ts +0 -18
- package/src/__tests__/config-loader-backfill.test.ts +9 -28
- package/src/__tests__/config-schema-cmd.test.ts +5 -25
- package/src/__tests__/config-schema.test.ts +21 -40
- package/src/__tests__/config-watcher.test.ts +4 -91
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -21
- package/src/__tests__/contacts-tools.test.ts +0 -21
- package/src/__tests__/context-memory-e2e.test.ts +0 -21
- package/src/__tests__/context-window-manager.test.ts +130 -3
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -4
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -4
- package/src/__tests__/conversation-agent-loop.test.ts +0 -4
- package/src/__tests__/conversation-attachments.test.ts +1 -24
- package/src/__tests__/conversation-attention-store.test.ts +0 -21
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -22
- package/src/__tests__/conversation-clear-safety.test.ts +0 -22
- package/src/__tests__/conversation-confirmation-signals.test.ts +2 -21
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -24
- package/src/__tests__/conversation-disk-view-integration.test.ts +1 -23
- package/src/__tests__/conversation-disk-view.test.ts +5 -27
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +1 -33
- package/src/__tests__/conversation-fork-route.test.ts +0 -27
- package/src/__tests__/conversation-history-web-search.test.ts +23 -16
- package/src/__tests__/conversation-init.benchmark.test.ts +22 -43
- package/src/__tests__/conversation-key-store-disk-view.test.ts +8 -34
- package/src/__tests__/conversation-load-history-repair.test.ts +0 -4
- package/src/__tests__/conversation-pre-run-repair.test.ts +0 -4
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -4
- package/src/__tests__/conversation-queue.test.ts +8 -8
- package/src/__tests__/conversation-routes-disk-view.test.ts +13 -51
- package/src/__tests__/conversation-runtime-assembly.test.ts +64 -38
- package/src/__tests__/conversation-slash-commands.test.ts +5 -0
- package/src/__tests__/conversation-slash-queue.test.ts +0 -4
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -4
- package/src/__tests__/conversation-speed-override.test.ts +326 -0
- package/src/__tests__/conversation-starter-routes.test.ts +0 -23
- package/src/__tests__/conversation-store.test.ts +0 -21
- package/src/__tests__/conversation-unread-route.test.ts +0 -24
- package/src/__tests__/conversation-usage.test.ts +56 -21
- package/src/__tests__/conversation-wipe.test.ts +0 -21
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -4
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -4
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -4
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +8 -5
- package/src/__tests__/credential-vault-unit.test.ts +9 -428
- package/src/__tests__/credentials-cli.test.ts +10 -10
- package/src/__tests__/daemon-assistant-events.test.ts +0 -19
- package/src/__tests__/date-context.test.ts +77 -97
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +7 -24
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +29 -42
- package/src/__tests__/delete-managed-skill-tool.test.ts +2 -10
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -26
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +61 -15
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -36
- package/src/__tests__/email-cli.test.ts +6 -6
- package/src/__tests__/ephemeral-permissions.test.ts +5 -17
- package/src/__tests__/first-greeting.test.ts +4 -32
- package/src/__tests__/followup-tools.test.ts +0 -21
- package/src/__tests__/gateway-only-enforcement.test.ts +0 -20
- package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -23
- package/src/__tests__/guardian-action-followup-executor.test.ts +0 -23
- package/src/__tests__/guardian-action-followup-store.test.ts +0 -21
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -21
- package/src/__tests__/guardian-action-late-reply.test.ts +0 -21
- package/src/__tests__/guardian-action-store.test.ts +0 -21
- package/src/__tests__/guardian-action-sweep.test.ts +0 -21
- package/src/__tests__/guardian-binding-drift-heal.test.ts +0 -23
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +172 -22
- package/src/__tests__/guardian-dispatch.test.ts +0 -21
- package/src/__tests__/guardian-grant-minting.test.ts +0 -22
- package/src/__tests__/guardian-outbound-http.test.ts +0 -22
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -23
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -22
- package/src/__tests__/guardian-routing-state.test.ts +0 -22
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -24
- package/src/__tests__/headless-browser-interactions.test.ts +0 -4
- package/src/__tests__/headless-browser-navigate.test.ts +0 -4
- package/src/__tests__/headless-browser-read-tools.test.ts +0 -4
- package/src/__tests__/headless-browser-snapshot.test.ts +0 -4
- package/src/__tests__/heartbeat-service.test.ts +99 -26
- package/src/__tests__/hooks-blocking.test.ts +3 -3
- package/src/__tests__/hooks-config.test.ts +7 -7
- package/src/__tests__/hooks-discovery.test.ts +3 -3
- package/src/__tests__/hooks-integration.test.ts +5 -5
- package/src/__tests__/hooks-manager.test.ts +3 -3
- package/src/__tests__/hooks-runner.test.ts +5 -23
- package/src/__tests__/hooks-settings.test.ts +3 -3
- package/src/__tests__/hooks-templates.test.ts +3 -3
- package/src/__tests__/http-conversation-lineage.test.ts +0 -27
- package/src/__tests__/identity-intro-cache.test.ts +0 -4
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -22
- package/src/__tests__/inline-skill-load-permissions.test.ts +5 -16
- package/src/__tests__/intent-routing.test.ts +2 -55
- package/src/__tests__/invite-redemption-service.test.ts +0 -21
- package/src/__tests__/invite-routes-http.test.ts +0 -21
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +0 -17
- package/src/__tests__/journal-context.test.ts +8 -75
- package/src/__tests__/list-messages-attachments.test.ts +0 -22
- package/src/__tests__/llm-context-route-provider.test.ts +0 -21
- package/src/__tests__/llm-request-log-turn-query.test.ts +46 -28
- package/src/__tests__/llm-usage-store.test.ts +0 -21
- package/src/__tests__/log-export-workspace.test.ts +1 -1
- package/src/__tests__/managed-skill-lifecycle.test.ts +1 -1
- package/src/__tests__/managed-store.test.ts +1 -1
- package/src/__tests__/mcp-cli.test.ts +7 -10
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -21
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +0 -11
- package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -21
- package/src/__tests__/memory-recall-log-store.test.ts +0 -27
- package/src/__tests__/memory-recall-quality.test.ts +0 -21
- package/src/__tests__/memory-regressions.experimental.test.ts +31 -30
- package/src/__tests__/memory-regressions.test.ts +282 -70
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -21
- package/src/__tests__/memory-upsert-concurrency.test.ts +0 -21
- package/src/__tests__/messaging-send-tool.test.ts +201 -0
- package/src/__tests__/migration-cross-version-compatibility.test.ts +18 -13
- package/src/__tests__/migration-export-http.test.ts +7 -1
- package/src/__tests__/migration-import-commit-http.test.ts +16 -14
- package/src/__tests__/migration-import-preflight-http.test.ts +27 -44
- package/src/__tests__/migration-validate-http.test.ts +1 -28
- package/src/__tests__/native-web-search.test.ts +25 -22
- package/src/__tests__/non-member-access-request.test.ts +0 -22
- package/src/__tests__/notification-guardian-path.test.ts +0 -21
- package/src/__tests__/notification-schedule-dedup.test.ts +1 -25
- package/src/__tests__/oauth-apps-routes.test.ts +103 -2
- package/src/__tests__/oauth-cli.test.ts +52 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +0 -16
- package/src/__tests__/oauth-provider-serializer.test.ts +232 -0
- package/src/__tests__/oauth-providers-routes.test.ts +257 -0
- package/src/__tests__/oauth-store.test.ts +0 -21
- package/src/__tests__/onboarding-template-contract.test.ts +2 -2
- package/src/__tests__/openai-provider.test.ts +261 -0
- package/src/__tests__/pairing-concurrent.test.ts +6 -6
- package/src/__tests__/pairing-routes.test.ts +7 -1
- package/src/__tests__/path-policy.test.ts +1 -1
- package/src/__tests__/platform.test.ts +64 -88
- package/src/__tests__/playbook-execution.test.ts +0 -21
- package/src/__tests__/playbook-tools.test.ts +0 -21
- package/src/__tests__/pricing.test.ts +100 -0
- package/src/__tests__/relay-server.test.ts +1 -25
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -24
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -24
- package/src/__tests__/runtime-events-sse.test.ts +0 -24
- package/src/__tests__/sandbox-diagnostics.test.ts +2 -1
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -1
- package/src/__tests__/schedule-store.test.ts +0 -21
- package/src/__tests__/schedule-tools.test.ts +0 -21
- package/src/__tests__/scheduler-recurrence.test.ts +0 -21
- package/src/__tests__/scoped-approval-grants.test.ts +0 -21
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -21
- package/src/__tests__/secret-allowlist.test.ts +1 -1
- package/src/__tests__/secret-ingress-channel.test.ts +0 -5
- package/src/__tests__/secret-ingress-cli.test.ts +0 -6
- package/src/__tests__/secret-ingress-http.test.ts +0 -5
- package/src/__tests__/secret-ingress.test.ts +0 -5
- package/src/__tests__/send-endpoint-busy.test.ts +0 -24
- package/src/__tests__/sequence-store.test.ts +0 -21
- package/src/__tests__/server-history-render.test.ts +0 -24
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -4
- package/src/__tests__/skill-load-inline-command.test.ts +9 -0
- package/src/__tests__/skill-load-inline-includes.test.ts +9 -0
- package/src/__tests__/skill-load-tool.test.ts +11 -0
- package/src/__tests__/skills-uninstall.test.ts +10 -8
- package/src/__tests__/skills.test.ts +1 -1
- package/src/__tests__/slack-channel-config.test.ts +1 -1
- package/src/__tests__/slack-inbound-verification.test.ts +0 -22
- package/src/__tests__/starter-bundle.test.ts +4 -1
- package/src/__tests__/suggestion-routes.test.ts +2 -0
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +1 -1
- package/src/__tests__/test-preload.ts +31 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +0 -20
- package/src/__tests__/tool-input-summary.test.ts +124 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -1
- package/src/__tests__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -1
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +1 -1
- package/src/__tests__/trusted-contact-multichannel.test.ts +1 -1
- package/src/__tests__/trusted-contact-verification.test.ts +1 -1
- package/src/__tests__/turn-boundary-resolution.test.ts +1 -1
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/update-bulletin.test.ts +1 -1
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +1 -1
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -1
- package/src/__tests__/voice-session-bridge.test.ts +1 -1
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -4
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -1
- package/src/__tests__/workspace-migration-down-functions.test.ts +15 -3
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +40 -4
- package/src/agent/loop.ts +6 -9
- package/src/approvals/guardian-decision-primitive.ts +46 -18
- package/src/approvals/guardian-request-resolvers.ts +19 -2
- package/src/calls/active-call-lease.ts +2 -2
- package/src/cli/AGENTS.md +1 -1
- package/src/cli/commands/doctor.ts +9 -9
- package/src/cli/commands/memory.ts +142 -0
- package/src/cli/commands/oauth/__tests__/connect.test.ts +13 -11
- package/src/cli/commands/oauth/__tests__/ping.test.ts +1 -1
- package/src/cli/commands/oauth/connect.ts +13 -12
- package/src/cli/commands/oauth/index.ts +1 -1
- package/src/cli/commands/oauth/providers.ts +47 -62
- package/src/cli/commands/platform/__tests__/connect.test.ts +72 -46
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +54 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +36 -0
- package/src/cli/commands/platform/connect.ts +17 -7
- package/src/cli/commands/platform/disconnect.ts +28 -3
- package/src/cli/commands/platform/index.ts +3 -3
- package/src/cli.ts +1 -299
- package/src/config/assistant-feature-flags.ts +23 -15
- package/src/config/bundled-skills/app-builder/TOOLS.json +16 -0
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +4 -0
- package/src/config/bundled-skills/app-builder/tools/app-delete.ts +5 -1
- package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +9 -1
- package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +5 -1
- package/src/config/bundled-skills/contacts/TOOLS.json +8 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +10 -1
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +16 -2
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +1 -0
- package/src/config/bundled-skills/messaging/SKILL.md +7 -7
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +37 -0
- package/src/config/bundled-skills/slack/SKILL.md +18 -0
- package/src/config/env-registry.ts +15 -11
- package/src/config/env.ts +1 -11
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/schema.ts +4 -0
- package/src/config/schemas/heartbeat.ts +6 -1
- package/src/config/schemas/inference.ts +14 -3
- package/src/config/schemas/memory-processing.ts +16 -8
- package/src/config/schemas/memory-retrieval.ts +3 -3
- package/src/config/skills.ts +1 -1
- package/src/context/window-manager.ts +174 -51
- package/src/credential-execution/executable-discovery.ts +2 -2
- package/src/daemon/approved-devices-store.ts +2 -2
- package/src/daemon/assistant-attachments.ts +2 -0
- package/src/daemon/config-watcher.ts +4 -50
- package/src/daemon/conversation-agent-loop-handlers.ts +9 -1
- package/src/daemon/conversation-agent-loop.ts +12 -0
- package/src/daemon/conversation-error.ts +3 -5
- package/src/daemon/conversation-history.ts +7 -3
- package/src/daemon/conversation-lifecycle.ts +16 -0
- package/src/daemon/conversation-messaging.ts +1 -0
- package/src/daemon/conversation-notifiers.ts +67 -30
- package/src/daemon/conversation-process.ts +161 -2
- package/src/daemon/conversation-queue-manager.ts +2 -0
- package/src/daemon/conversation-runtime-assembly.ts +33 -11
- package/src/daemon/conversation-slash.ts +14 -3
- package/src/daemon/conversation-tool-setup.ts +2 -0
- package/src/daemon/conversation-usage.ts +32 -4
- package/src/daemon/conversation.ts +33 -1
- package/src/daemon/daemon-control.ts +32 -16
- package/src/daemon/date-context.ts +47 -45
- package/src/daemon/dictation-profile-store.ts +2 -2
- package/src/daemon/handlers/conversations.ts +19 -0
- package/src/daemon/handlers/shared.ts +14 -21
- package/src/daemon/lifecycle.ts +5 -7
- package/src/daemon/message-types/conversations.ts +2 -0
- package/src/daemon/message-types/guardian-actions.ts +3 -17
- package/src/daemon/message-types/integrations.ts +11 -1
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/pairing-store.ts +2 -79
- package/src/daemon/server.ts +154 -8
- package/src/daemon/watch-handler.ts +65 -21
- package/src/email/guardrails.ts +3 -3
- package/src/heartbeat/heartbeat-service.ts +14 -7
- package/src/hooks/cli.ts +2 -2
- package/src/hooks/config.ts +2 -2
- package/src/hooks/discovery.ts +2 -2
- package/src/hooks/manager.ts +2 -2
- package/src/hooks/runner.ts +5 -2
- package/src/hooks/templates.ts +2 -2
- package/src/memory/admin.ts +181 -2
- package/src/memory/app-git-service.ts +61 -4
- package/src/memory/attachments-store.ts +2 -0
- package/src/memory/canonical-guardian-store.ts +16 -0
- package/src/memory/db-init.ts +8 -0
- package/src/memory/embedding-local.ts +5 -2
- package/src/memory/indexer.ts +44 -26
- package/src/memory/items-extractor.ts +34 -82
- package/src/memory/job-handlers/batch-extraction.ts +741 -0
- package/src/memory/job-handlers/journal-carry-forward.test.ts +383 -0
- package/src/memory/job-handlers/journal-carry-forward.ts +255 -0
- package/src/memory/jobs-store.ts +28 -0
- package/src/memory/jobs-worker.ts +56 -9
- package/src/memory/lifecycle-events-store.ts +4 -2
- package/src/memory/llm-request-log-store.ts +40 -2
- package/src/memory/llm-usage-store.ts +4 -3
- package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +71 -0
- package/src/memory/migrations/200-usage-llm-call-count.ts +20 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/query-expansion.ts +83 -0
- package/src/memory/retriever.test.ts +119 -0
- package/src/memory/retriever.ts +513 -105
- package/src/memory/schema/guardian.ts +4 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/search/formatting.test.ts +140 -0
- package/src/memory/search/formatting.ts +143 -198
- package/src/memory/search/mmr.ts +136 -0
- package/src/memory/search/staleness.ts +0 -15
- package/src/memory/search/tier-classifier.ts +10 -21
- package/src/memory/search/types.ts +17 -0
- package/src/messaging/providers/slack/adapter.ts +51 -5
- package/src/notifications/broadcaster.ts +13 -0
- package/src/notifications/copy-composer.ts +8 -0
- package/src/oauth/connect-orchestrator.ts +1 -1
- package/src/oauth/connection-resolver.ts +2 -2
- package/src/oauth/provider-serializer.ts +116 -0
- package/src/permissions/trust-store.ts +24 -7
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +5 -0
- package/src/prompts/journal-context.ts +50 -35
- package/src/prompts/persona-resolver.ts +1 -1
- package/src/prompts/system-prompt.ts +27 -28
- package/src/prompts/templates/BOOTSTRAP.md +14 -1
- package/src/prompts/templates/HEARTBEAT.md +10 -0
- package/src/prompts/templates/NOW.md +19 -25
- package/src/prompts/templates/SOUL.md +13 -1
- package/src/prompts/templates/UPDATES.md +12 -0
- package/src/prompts/update-bulletin.ts +1 -1
- package/src/providers/anthropic/client.ts +89 -18
- package/src/providers/model-catalog.ts +22 -2
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/client.ts +40 -1
- package/src/providers/retry.ts +23 -4
- package/src/providers/types.ts +2 -0
- package/src/runtime/assistant-scope.ts +1 -1
- package/src/runtime/auth/__tests__/credential-service.test.ts +1 -0
- package/src/runtime/auth/route-policy.ts +1 -0
- package/src/runtime/auth/token-service.ts +51 -29
- package/src/runtime/confirmation-request-guardian-bridge.ts +3 -1
- package/src/runtime/guardian-decision-types.ts +16 -10
- package/src/runtime/http-server.ts +3 -14
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +7 -4
- package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -4
- package/src/runtime/migrations/vbundle-importer.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +40 -8
- package/src/runtime/routes/conversation-routes.ts +125 -3
- package/src/runtime/routes/guardian-action-routes.ts +9 -3
- package/src/runtime/routes/identity-routes.ts +25 -4
- package/src/runtime/routes/llm-context-normalization.ts +1 -0
- package/src/runtime/routes/log-export-routes.ts +34 -12
- package/src/runtime/routes/migration-routes.ts +6 -10
- package/src/runtime/routes/oauth-apps.ts +2 -9
- package/src/runtime/routes/oauth-providers.ts +60 -0
- package/src/runtime/routes/pairing-routes.ts +0 -8
- package/src/runtime/routes/settings-routes.ts +0 -1
- package/src/runtime/routes/telemetry-routes.ts +16 -4
- package/src/security/encrypted-store.ts +2 -2
- package/src/security/secret-allowlist.ts +3 -3
- package/src/signals/emit-event.ts +42 -0
- package/src/signals/user-message.ts +37 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +83 -19
- package/src/telemetry/usage-telemetry-reporter.ts +23 -17
- package/src/tools/browser/runtime-check.ts +2 -2
- package/src/tools/credentials/vault.ts +2 -249
- package/src/tools/memory/definitions.ts +1 -1
- package/src/tools/memory/handlers.test.ts +50 -8
- package/src/tools/memory/handlers.ts +3 -1
- package/src/tools/side-effects.ts +1 -6
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +11 -14
- package/src/tools/tool-approval-handler.ts +20 -1
- package/src/tools/tool-input-summary.ts +66 -0
- package/src/tools/types.ts +4 -0
- package/src/usage/types.ts +4 -0
- package/src/util/device-id.ts +10 -10
- package/src/util/platform.ts +71 -33
- package/src/util/pricing.ts +19 -6
- package/src/util/strip-comment-lines.ts +28 -0
- package/src/workspace/git-service.ts +8 -18
- package/src/workspace/migrations/003-seed-device-id.ts +6 -4
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +7 -1
- package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -4
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +84 -0
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +94 -0
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +86 -0
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +126 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +3 -6
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/signals/confirm.ts +0 -82
- package/src/signals/trust-rule.ts +0 -174
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getConfig } from "../config/loader.js";
|
|
2
2
|
import type { AssistantConfig } from "../config/types.js";
|
|
3
3
|
import { getLogger } from "../util/logger.js";
|
|
4
|
-
import { rawRun } from "./db.js";
|
|
4
|
+
import { rawAll, rawRun } from "./db.js";
|
|
5
5
|
import { backfillJob } from "./job-handlers/backfill.js";
|
|
6
|
+
import { batchExtractJob } from "./job-handlers/batch-extraction.js";
|
|
6
7
|
import {
|
|
7
8
|
cleanupStaleSupersededItemsJob,
|
|
8
9
|
pruneOldConversationsJob,
|
|
@@ -21,8 +22,8 @@ import {
|
|
|
21
22
|
deleteQdrantVectorsJob,
|
|
22
23
|
rebuildIndexJob,
|
|
23
24
|
} from "./job-handlers/index-maintenance.js";
|
|
25
|
+
import { journalCarryForwardJob } from "./job-handlers/journal-carry-forward.js";
|
|
24
26
|
import { mediaProcessingJob } from "./job-handlers/media-processing.js";
|
|
25
|
-
import { buildConversationSummaryJob } from "./job-handlers/summarization.js";
|
|
26
27
|
import {
|
|
27
28
|
BackendUnavailableError,
|
|
28
29
|
classifyError,
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
completeMemoryJob,
|
|
35
36
|
deferMemoryJob,
|
|
36
37
|
enqueueCleanupStaleSupersededItemsJob,
|
|
38
|
+
enqueueMemoryJob,
|
|
37
39
|
enqueuePruneOldConversationsJob,
|
|
38
40
|
failMemoryJob,
|
|
39
41
|
failStalledJobs,
|
|
@@ -58,6 +60,31 @@ export function startMemoryJobsWorker(): MemoryJobsWorker {
|
|
|
58
60
|
log.info({ recovered }, "Recovered stale running memory jobs");
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
// Startup recovery: enqueue batch_extract for conversations with pending
|
|
64
|
+
// unextracted messages (e.g. after a crash mid-conversation).
|
|
65
|
+
try {
|
|
66
|
+
const pendingRows = rawAll<{ key: string; value: string }>(
|
|
67
|
+
`SELECT key, value FROM memory_checkpoints WHERE key LIKE 'batch_extract:%:pending_count' AND CAST(value AS INTEGER) > 0`,
|
|
68
|
+
);
|
|
69
|
+
for (const row of pendingRows) {
|
|
70
|
+
// Extract conversationId from key: "batch_extract:<conversationId>:pending_count"
|
|
71
|
+
const parts = row.key.split(":");
|
|
72
|
+
if (parts.length >= 3) {
|
|
73
|
+
const conversationId = parts.slice(1, -1).join(":");
|
|
74
|
+
enqueueMemoryJob("batch_extract", { conversationId });
|
|
75
|
+
log.info(
|
|
76
|
+
{ conversationId, pendingCount: row.value },
|
|
77
|
+
"Recovered pending batch extraction on startup",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch (err) {
|
|
82
|
+
log.warn(
|
|
83
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
84
|
+
"Failed to recover pending batch extractions on startup",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
61
88
|
let stopped = false;
|
|
62
89
|
let tickRunning = false;
|
|
63
90
|
let timer: ReturnType<typeof setTimeout>;
|
|
@@ -137,20 +164,29 @@ export async function runMemoryJobsOnce(
|
|
|
137
164
|
return 0;
|
|
138
165
|
}
|
|
139
166
|
|
|
140
|
-
// Group jobs
|
|
141
|
-
//
|
|
142
|
-
|
|
167
|
+
// Group jobs so they can run concurrently across independent work units.
|
|
168
|
+
// Jobs targeting different conversations (via payload.conversationId) are
|
|
169
|
+
// placed in separate groups and can run in parallel. Jobs targeting the
|
|
170
|
+
// same conversation, or global jobs without a conversationId (backfill,
|
|
171
|
+
// cleanup, rebuild_index), are grouped together and run sequentially to
|
|
172
|
+
// prevent checkpoint races.
|
|
173
|
+
const jobGroups = new Map<string, MemoryJob[]>();
|
|
143
174
|
for (const job of jobs) {
|
|
144
|
-
|
|
175
|
+
const convId =
|
|
176
|
+
typeof job.payload.conversationId === "string"
|
|
177
|
+
? job.payload.conversationId
|
|
178
|
+
: null;
|
|
179
|
+
const groupKey = convId ? `${job.type}:${convId}` : job.type;
|
|
180
|
+
let group = jobGroups.get(groupKey);
|
|
145
181
|
if (!group) {
|
|
146
182
|
group = [];
|
|
147
|
-
|
|
183
|
+
jobGroups.set(groupKey, group);
|
|
148
184
|
}
|
|
149
185
|
group.push(job);
|
|
150
186
|
}
|
|
151
187
|
|
|
152
188
|
let processed = 0;
|
|
153
|
-
const typeGroups = [...
|
|
189
|
+
const typeGroups = [...jobGroups.values()];
|
|
154
190
|
|
|
155
191
|
// Run type groups concurrently using a task pool (up to workerConcurrency
|
|
156
192
|
// active at a time). Unlike the old wave approach, a new group starts as
|
|
@@ -294,6 +330,9 @@ async function processJob(
|
|
|
294
330
|
case "extract_items":
|
|
295
331
|
await extractItemsJob(job);
|
|
296
332
|
return;
|
|
333
|
+
case "batch_extract":
|
|
334
|
+
await batchExtractJob(job);
|
|
335
|
+
return;
|
|
297
336
|
case "extract_entities":
|
|
298
337
|
// Entity extraction has been removed — silently drop legacy jobs
|
|
299
338
|
return;
|
|
@@ -304,7 +343,12 @@ async function processJob(
|
|
|
304
343
|
pruneOldConversationsJob(job, config);
|
|
305
344
|
return;
|
|
306
345
|
case "build_conversation_summary":
|
|
307
|
-
|
|
346
|
+
// Deprecated: conversation summaries are now produced as a side-effect
|
|
347
|
+
// of batch extraction. Silently skip legacy jobs.
|
|
348
|
+
log.debug(
|
|
349
|
+
{ jobId: job.id },
|
|
350
|
+
"Skipping deprecated build_conversation_summary job — handled by batch extraction",
|
|
351
|
+
);
|
|
308
352
|
return;
|
|
309
353
|
case "backfill":
|
|
310
354
|
await backfillJob(job, config);
|
|
@@ -331,6 +375,9 @@ async function processJob(
|
|
|
331
375
|
case "embed_attachment":
|
|
332
376
|
await embedAttachmentJob(job, config);
|
|
333
377
|
return;
|
|
378
|
+
case "journal_carry_forward":
|
|
379
|
+
await journalCarryForwardJob(job);
|
|
380
|
+
return;
|
|
334
381
|
case "generate_conversation_starters":
|
|
335
382
|
await generateConversationStartersJob(job);
|
|
336
383
|
return;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { and, asc, eq, gt, or } from "drizzle-orm";
|
|
2
2
|
import { v4 as uuid } from "uuid";
|
|
3
3
|
|
|
4
|
+
import { getConfig } from "../config/loader.js";
|
|
4
5
|
import { getDb } from "./db.js";
|
|
5
6
|
import { lifecycleEvents } from "./schema.js";
|
|
6
7
|
|
|
@@ -10,8 +11,9 @@ export interface LifecycleEvent {
|
|
|
10
11
|
createdAt: number;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
/** Record a lifecycle event (e.g. app_open, hatch). */
|
|
14
|
-
export function recordLifecycleEvent(eventName: string): LifecycleEvent {
|
|
14
|
+
/** Record a lifecycle event (e.g. app_open, hatch). Returns null when usage data collection is disabled. */
|
|
15
|
+
export function recordLifecycleEvent(eventName: string): LifecycleEvent | null {
|
|
16
|
+
if (!getConfig().collectUsageData) return null;
|
|
15
17
|
const db = getDb();
|
|
16
18
|
const event: LifecycleEvent = {
|
|
17
19
|
id: uuid(),
|
|
@@ -26,11 +26,12 @@ export function recordRequestLog(
|
|
|
26
26
|
responsePayload: string,
|
|
27
27
|
messageId?: string,
|
|
28
28
|
provider?: string,
|
|
29
|
-
):
|
|
29
|
+
): string {
|
|
30
30
|
const db = getDb();
|
|
31
|
+
const id = uuid();
|
|
31
32
|
db.insert(llmRequestLogs)
|
|
32
33
|
.values({
|
|
33
|
-
id
|
|
34
|
+
id,
|
|
34
35
|
conversationId,
|
|
35
36
|
messageId: messageId ?? null,
|
|
36
37
|
provider: provider ?? null,
|
|
@@ -39,6 +40,7 @@ export function recordRequestLog(
|
|
|
39
40
|
createdAt: Date.now(),
|
|
40
41
|
})
|
|
41
42
|
.run();
|
|
43
|
+
return id;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
export function queryRequestLogs(
|
|
@@ -83,6 +85,42 @@ export function backfillMessageIdOnLogs(
|
|
|
83
85
|
.run();
|
|
84
86
|
}
|
|
85
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Set `messageId` on specific log rows identified by their IDs.
|
|
90
|
+
* Unlike `backfillMessageIdOnLogs` (which updates all null-messageId rows
|
|
91
|
+
* for a conversation), this targets only the given log rows — safe for
|
|
92
|
+
* concurrent watch/assistant turns.
|
|
93
|
+
*/
|
|
94
|
+
export function setMessageIdOnLogs(
|
|
95
|
+
logIds: string[],
|
|
96
|
+
messageId: string,
|
|
97
|
+
): void {
|
|
98
|
+
if (logIds.length === 0) return;
|
|
99
|
+
const db = getDb();
|
|
100
|
+
db.update(llmRequestLogs)
|
|
101
|
+
.set({ messageId })
|
|
102
|
+
.where(inArray(llmRequestLogs.id, logIds))
|
|
103
|
+
.run();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Re-link LLM request logs from a set of source message IDs to a target
|
|
108
|
+
* message. Used during message consolidation so logs from deleted
|
|
109
|
+
* intermediate messages survive and remain queryable via the consolidated
|
|
110
|
+
* message.
|
|
111
|
+
*/
|
|
112
|
+
export function relinkLlmRequestLogs(
|
|
113
|
+
fromMessageIds: string[],
|
|
114
|
+
toMessageId: string,
|
|
115
|
+
): void {
|
|
116
|
+
if (fromMessageIds.length === 0) return;
|
|
117
|
+
const db = getDb();
|
|
118
|
+
db.update(llmRequestLogs)
|
|
119
|
+
.set({ messageId: toMessageId })
|
|
120
|
+
.where(inArray(llmRequestLogs.messageId, fromMessageIds))
|
|
121
|
+
.run();
|
|
122
|
+
}
|
|
123
|
+
|
|
86
124
|
/**
|
|
87
125
|
* Internal helper: query `llm_request_logs` for rows matching any of the
|
|
88
126
|
* given message IDs, ordered by `createdAt ASC`. Uses the existing
|
|
@@ -42,6 +42,7 @@ export function recordUsageEvent(
|
|
|
42
42
|
cacheReadInputTokens: event.cacheReadInputTokens,
|
|
43
43
|
estimatedCostUsd: event.estimatedCostUsd,
|
|
44
44
|
pricingStatus: event.pricingStatus,
|
|
45
|
+
llmCallCount: event.llmCallCount ?? 1,
|
|
45
46
|
metadataJson: null,
|
|
46
47
|
})
|
|
47
48
|
.run();
|
|
@@ -213,7 +214,7 @@ export function getUsageTotals(range: UsageTimeRange): UsageTotals {
|
|
|
213
214
|
COALESCE(SUM(cache_creation_input_tokens), 0) AS total_cache_creation_tokens,
|
|
214
215
|
COALESCE(SUM(cache_read_input_tokens), 0) AS total_cache_read_tokens,
|
|
215
216
|
COALESCE(SUM(estimated_cost_usd), 0) AS total_estimated_cost_usd,
|
|
216
|
-
|
|
217
|
+
COALESCE(SUM(COALESCE(llm_call_count, 1)), 0) AS event_count,
|
|
217
218
|
COUNT(CASE WHEN pricing_status = 'priced' THEN 1 END) AS priced_event_count,
|
|
218
219
|
COUNT(CASE WHEN pricing_status = 'unpriced' THEN 1 END) AS unpriced_event_count
|
|
219
220
|
FROM llm_usage_events
|
|
@@ -249,7 +250,7 @@ export function getUsageDayBuckets(range: UsageTimeRange): UsageDayBucket[] {
|
|
|
249
250
|
COALESCE(SUM(input_tokens), 0) AS total_input_tokens,
|
|
250
251
|
COALESCE(SUM(output_tokens), 0) AS total_output_tokens,
|
|
251
252
|
COALESCE(SUM(estimated_cost_usd), 0) AS total_estimated_cost_usd,
|
|
252
|
-
|
|
253
|
+
COALESCE(SUM(COALESCE(llm_call_count, 1)), 0) AS event_count
|
|
253
254
|
FROM llm_usage_events
|
|
254
255
|
WHERE created_at >= ?1 AND created_at <= ?2
|
|
255
256
|
GROUP BY date
|
|
@@ -292,7 +293,7 @@ export function getUsageGroupBreakdown(
|
|
|
292
293
|
COALESCE(SUM(cache_creation_input_tokens), 0) AS total_cache_creation_tokens,
|
|
293
294
|
COALESCE(SUM(cache_read_input_tokens), 0) AS total_cache_read_tokens,
|
|
294
295
|
COALESCE(SUM(estimated_cost_usd), 0) AS total_estimated_cost_usd,
|
|
295
|
-
|
|
296
|
+
COALESCE(SUM(COALESCE(llm_call_count, 1)), 0) AS event_count
|
|
296
297
|
FROM llm_usage_events
|
|
297
298
|
WHERE created_at >= ?1 AND created_at <= ?2
|
|
298
299
|
GROUP BY ${column}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
import { tableHasColumn } from "./schema-introspection.js";
|
|
4
|
+
import { withCrashRecovery } from "./validate-migration-state.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Add enrichment columns to canonical_guardian_requests for guardian
|
|
8
|
+
* approval UX:
|
|
9
|
+
*
|
|
10
|
+
* - command_preview: truncated command/input preview
|
|
11
|
+
* - risk_level: "low", "medium", "high"
|
|
12
|
+
* - activity_text: LLM's explanation of why it's calling the tool
|
|
13
|
+
* - execution_target: "sandbox" or "host"
|
|
14
|
+
*
|
|
15
|
+
* All columns are nullable TEXT — existing rows default to NULL.
|
|
16
|
+
*/
|
|
17
|
+
export function migrateGuardianRequestEnrichmentColumns(
|
|
18
|
+
database: DrizzleDb,
|
|
19
|
+
): void {
|
|
20
|
+
withCrashRecovery(
|
|
21
|
+
database,
|
|
22
|
+
"migration_guardian_request_enrichment_columns_v1",
|
|
23
|
+
() => {
|
|
24
|
+
const raw = getSqliteFrom(database);
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
!tableHasColumn(
|
|
28
|
+
database,
|
|
29
|
+
"canonical_guardian_requests",
|
|
30
|
+
"command_preview",
|
|
31
|
+
)
|
|
32
|
+
) {
|
|
33
|
+
raw.exec(
|
|
34
|
+
/*sql*/ `ALTER TABLE canonical_guardian_requests ADD COLUMN command_preview TEXT`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (
|
|
39
|
+
!tableHasColumn(database, "canonical_guardian_requests", "risk_level")
|
|
40
|
+
) {
|
|
41
|
+
raw.exec(
|
|
42
|
+
/*sql*/ `ALTER TABLE canonical_guardian_requests ADD COLUMN risk_level TEXT`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (
|
|
47
|
+
!tableHasColumn(
|
|
48
|
+
database,
|
|
49
|
+
"canonical_guardian_requests",
|
|
50
|
+
"activity_text",
|
|
51
|
+
)
|
|
52
|
+
) {
|
|
53
|
+
raw.exec(
|
|
54
|
+
/*sql*/ `ALTER TABLE canonical_guardian_requests ADD COLUMN activity_text TEXT`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (
|
|
59
|
+
!tableHasColumn(
|
|
60
|
+
database,
|
|
61
|
+
"canonical_guardian_requests",
|
|
62
|
+
"execution_target",
|
|
63
|
+
)
|
|
64
|
+
) {
|
|
65
|
+
raw.exec(
|
|
66
|
+
/*sql*/ `ALTER TABLE canonical_guardian_requests ADD COLUMN execution_target TEXT`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
import { tableHasColumn } from "./schema-introspection.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Add llm_call_count column to llm_usage_events so each row records
|
|
7
|
+
* how many actual LLM API calls it represents (an exchange/turn may
|
|
8
|
+
* contain multiple tool-use iterations, each making a separate API call).
|
|
9
|
+
*
|
|
10
|
+
* Nullable INTEGER — existing rows default to NULL and are treated as 1
|
|
11
|
+
* by the aggregation queries via COALESCE.
|
|
12
|
+
*/
|
|
13
|
+
export function migrateUsageLlmCallCount(database: DrizzleDb): void {
|
|
14
|
+
const raw = getSqliteFrom(database);
|
|
15
|
+
if (!tableHasColumn(database, "llm_usage_events", "llm_call_count")) {
|
|
16
|
+
raw.exec(
|
|
17
|
+
/*sql*/ `ALTER TABLE llm_usage_events ADD COLUMN llm_call_count INTEGER`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -138,6 +138,8 @@ export { migrateMessagesConversationCreatedAtIndex } from "./196-messages-conver
|
|
|
138
138
|
export { migrateStripIntegrationPrefixFromProviderKeys } from "./196-strip-integration-prefix-from-provider-keys.js";
|
|
139
139
|
export { migrateOAuthProvidersBehaviorColumns } from "./197-oauth-providers-behavior-columns.js";
|
|
140
140
|
export { migrateDropSetupSkillIdColumn } from "./198-drop-setup-skill-id-column.js";
|
|
141
|
+
export { migrateGuardianRequestEnrichmentColumns } from "./199-guardian-request-enrichment-columns.js";
|
|
142
|
+
export { migrateUsageLlmCallCount } from "./200-usage-llm-call-count.js";
|
|
141
143
|
export {
|
|
142
144
|
MIGRATION_REGISTRY,
|
|
143
145
|
type MigrationRegistryEntry,
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HyDE (Hypothetical Document Embeddings) query expansion for memory retrieval.
|
|
3
|
+
*
|
|
4
|
+
* Generates hypothetical memory documents that bridge the semantic gap between
|
|
5
|
+
* how users query ("moments that changed everything") and how memories are
|
|
6
|
+
* actually stored ("Sidd gave Velissa a collar on March 24"). The expanded
|
|
7
|
+
* queries are embedded alongside the original query to improve recall.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { AssistantConfig } from "../config/types.js";
|
|
11
|
+
import {
|
|
12
|
+
extractText,
|
|
13
|
+
getConfiguredProvider,
|
|
14
|
+
userMessage,
|
|
15
|
+
} from "../providers/provider-send-message.js";
|
|
16
|
+
import { getLogger } from "../util/logger.js";
|
|
17
|
+
|
|
18
|
+
const log = getLogger("memory-query-expansion");
|
|
19
|
+
|
|
20
|
+
const SYSTEM_PROMPT = `Generate 3 short hypothetical memory entries that would match this search query. Each should describe what a stored memory about this topic would contain — specific details, emotional context, relationship dynamics. Write from the perspective of stored memory items, not the query itself. Keep each under 80 words. Separate entries with ---`;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generate hypothetical memory documents for a query using HyDE.
|
|
24
|
+
*
|
|
25
|
+
* Returns 1-3 hypothetical document strings that can be embedded alongside
|
|
26
|
+
* the original query to improve semantic recall. Returns `[]` on any error
|
|
27
|
+
* (provider unavailable, LLM failure, empty response) — the caller should
|
|
28
|
+
* fall back to the raw query only.
|
|
29
|
+
*
|
|
30
|
+
* The raw query is NOT included in the returned array; the caller handles
|
|
31
|
+
* that separately.
|
|
32
|
+
*/
|
|
33
|
+
export async function expandQueryWithHyDE(
|
|
34
|
+
query: string,
|
|
35
|
+
_config: AssistantConfig,
|
|
36
|
+
signal?: AbortSignal,
|
|
37
|
+
): Promise<string[]> {
|
|
38
|
+
try {
|
|
39
|
+
const provider = await getConfiguredProvider();
|
|
40
|
+
if (!provider) {
|
|
41
|
+
log.warn("No provider available for HyDE query expansion");
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const response = await provider.sendMessage(
|
|
46
|
+
[userMessage(query)],
|
|
47
|
+
undefined,
|
|
48
|
+
SYSTEM_PROMPT,
|
|
49
|
+
{
|
|
50
|
+
config: {
|
|
51
|
+
modelIntent: "latency-optimized" as const,
|
|
52
|
+
},
|
|
53
|
+
signal,
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const text = extractText(response);
|
|
58
|
+
if (!text) {
|
|
59
|
+
log.warn("Empty response from HyDE query expansion");
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const entries = text
|
|
64
|
+
.split("---")
|
|
65
|
+
.map((entry) => entry.trim())
|
|
66
|
+
.filter((entry) => entry.length > 0)
|
|
67
|
+
.slice(0, 3);
|
|
68
|
+
|
|
69
|
+
if (entries.length === 0) {
|
|
70
|
+
log.warn("No entries parsed from HyDE query expansion response");
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return entries;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if (err instanceof DOMException && err.name === "AbortError") throw err;
|
|
77
|
+
log.warn(
|
|
78
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
79
|
+
"HyDE query expansion failed",
|
|
80
|
+
);
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -1468,4 +1468,123 @@ describe("Memory Retriever Pipeline", () => {
|
|
|
1468
1468
|
expect(result.mergedCount).toBe(0);
|
|
1469
1469
|
});
|
|
1470
1470
|
});
|
|
1471
|
+
|
|
1472
|
+
// -----------------------------------------------------------------------
|
|
1473
|
+
// Serendipity layer
|
|
1474
|
+
// -----------------------------------------------------------------------
|
|
1475
|
+
|
|
1476
|
+
describe("serendipity sampling", () => {
|
|
1477
|
+
test("samples random active items and renders them in <echoes>", async () => {
|
|
1478
|
+
const db = getDb();
|
|
1479
|
+
const now = Date.now();
|
|
1480
|
+
const convId = "conv-serendipity";
|
|
1481
|
+
|
|
1482
|
+
insertConversation(db, convId, now - 60_000);
|
|
1483
|
+
insertMessage(db, "msg-s-1", convId, "user", "hello", now - 50_000);
|
|
1484
|
+
|
|
1485
|
+
// Items sourced from a different conversation so in-context filtering
|
|
1486
|
+
// doesn't remove them (serendipity is cross-conversation recall).
|
|
1487
|
+
const otherConvId = "conv-serendipity-other";
|
|
1488
|
+
insertConversation(db, otherConvId, now - 120_000);
|
|
1489
|
+
insertMessage(db, "msg-s-other", otherConvId, "user", "other", now - 110_000);
|
|
1490
|
+
|
|
1491
|
+
// Insert several active items that are NOT returned by Qdrant
|
|
1492
|
+
for (let i = 1; i <= 5; i++) {
|
|
1493
|
+
insertItem(db, {
|
|
1494
|
+
id: `serendipity-item-${i}`,
|
|
1495
|
+
kind: "fact",
|
|
1496
|
+
subject: `topic ${i}`,
|
|
1497
|
+
statement: `Serendipity fact number ${i}`,
|
|
1498
|
+
importance: i * 0.15, // 0.15..0.75
|
|
1499
|
+
firstSeenAt: now - i * 10_000,
|
|
1500
|
+
});
|
|
1501
|
+
insertItemSource(db, `serendipity-item-${i}`, "msg-s-other", now - i * 10_000);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
// Qdrant returns nothing — no recalled candidates
|
|
1505
|
+
mockQdrantResults.length = 0;
|
|
1506
|
+
|
|
1507
|
+
const result = await buildMemoryRecall(
|
|
1508
|
+
"unrelated query",
|
|
1509
|
+
convId,
|
|
1510
|
+
TEST_CONFIG,
|
|
1511
|
+
);
|
|
1512
|
+
|
|
1513
|
+
expect(result.enabled).toBe(true);
|
|
1514
|
+
// No semantic hits, so no recalled candidates
|
|
1515
|
+
expect(result.mergedCount).toBe(0);
|
|
1516
|
+
// But serendipity items should appear in the injection
|
|
1517
|
+
expect(result.injectedText).toContain("<echoes>");
|
|
1518
|
+
expect(result.injectedText).toContain("</echoes>");
|
|
1519
|
+
// At most 3 serendipity items
|
|
1520
|
+
const itemMatches = result.injectedText.match(/<item /g);
|
|
1521
|
+
expect(itemMatches).toBeTruthy();
|
|
1522
|
+
expect(itemMatches!.length).toBeLessThanOrEqual(3);
|
|
1523
|
+
expect(itemMatches!.length).toBeGreaterThanOrEqual(1);
|
|
1524
|
+
// selectedCount includes serendipity items
|
|
1525
|
+
expect(result.selectedCount).toBeGreaterThan(0);
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
test("excludes items already in the candidate pool from serendipity", async () => {
|
|
1529
|
+
const db = getDb();
|
|
1530
|
+
const now = Date.now();
|
|
1531
|
+
const convId = "conv-serendipity-excl";
|
|
1532
|
+
|
|
1533
|
+
insertConversation(db, convId, now - 60_000);
|
|
1534
|
+
insertMessage(db, "msg-se-1", convId, "user", "query about X", now - 50_000);
|
|
1535
|
+
|
|
1536
|
+
// This item will be returned by Qdrant as a recalled candidate
|
|
1537
|
+
insertItem(db, {
|
|
1538
|
+
id: "recalled-item",
|
|
1539
|
+
kind: "fact",
|
|
1540
|
+
subject: "X",
|
|
1541
|
+
statement: "Recalled fact about X",
|
|
1542
|
+
importance: 0.9,
|
|
1543
|
+
firstSeenAt: now - 30_000,
|
|
1544
|
+
});
|
|
1545
|
+
insertItemSource(db, "recalled-item", "msg-se-1", now - 30_000);
|
|
1546
|
+
|
|
1547
|
+
// Qdrant returns the recalled item
|
|
1548
|
+
mockQdrantResults.push({
|
|
1549
|
+
id: "qdrant-recalled",
|
|
1550
|
+
score: 0.9,
|
|
1551
|
+
payload: {
|
|
1552
|
+
target_type: "item",
|
|
1553
|
+
target_id: "recalled-item",
|
|
1554
|
+
text: "X: Recalled fact about X",
|
|
1555
|
+
created_at: now - 30_000,
|
|
1556
|
+
},
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
const result = await buildMemoryRecall(
|
|
1560
|
+
"query about X",
|
|
1561
|
+
convId,
|
|
1562
|
+
TEST_CONFIG,
|
|
1563
|
+
);
|
|
1564
|
+
|
|
1565
|
+
expect(result.enabled).toBe(true);
|
|
1566
|
+
// The recalled item is in <recalled>, not in <echoes>
|
|
1567
|
+
if (result.injectedText.includes("<echoes>")) {
|
|
1568
|
+
// If echoes exists, the recalled item should NOT be duplicated there
|
|
1569
|
+
const echoesMatch = result.injectedText.match(
|
|
1570
|
+
/<echoes>([\s\S]*?)<\/echoes>/,
|
|
1571
|
+
);
|
|
1572
|
+
if (echoesMatch) {
|
|
1573
|
+
expect(echoesMatch[1]).not.toContain("recalled-item");
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
|
|
1578
|
+
test("no <echoes> section when no active items exist", async () => {
|
|
1579
|
+
// No items seeded at all
|
|
1580
|
+
const result = await buildMemoryRecall(
|
|
1581
|
+
"anything",
|
|
1582
|
+
"conv-empty-seren",
|
|
1583
|
+
TEST_CONFIG,
|
|
1584
|
+
);
|
|
1585
|
+
|
|
1586
|
+
expect(result.enabled).toBe(true);
|
|
1587
|
+
expect(result.injectedText).not.toContain("<echoes>");
|
|
1588
|
+
});
|
|
1589
|
+
});
|
|
1471
1590
|
});
|