@vellumai/assistant 0.5.15 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +3 -3
- package/Dockerfile +0 -3
- package/docs/architecture/integrations.md +15 -14
- package/knip.json +4 -1
- package/openapi.yaml +670 -122
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +68 -0
- package/src/__tests__/agent-loop.test.ts +0 -32
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
- package/src/__tests__/anthropic-provider.test.ts +57 -3
- package/src/__tests__/app-compiler.test.ts +120 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +5 -377
- package/src/__tests__/call-conversation-messages.test.ts +2 -6
- package/src/__tests__/call-domain.test.ts +2 -6
- package/src/__tests__/call-pointer-messages.test.ts +2 -14
- package/src/__tests__/call-recovery.test.ts +2 -6
- package/src/__tests__/call-routes-http.test.ts +2 -6
- package/src/__tests__/call-store.test.ts +2 -6
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
- package/src/__tests__/canonical-guardian-store.test.ts +2 -6
- package/src/__tests__/ces-rpc-credential-backend.test.ts +4 -1
- package/src/__tests__/channel-delivery-store.test.ts +2 -6
- package/src/__tests__/channel-retry-sweep.test.ts +2 -6
- package/src/__tests__/checker.test.ts +84 -3
- package/src/__tests__/clawhub.test.ts +54 -24
- package/src/__tests__/cli-command-risk-guard.test.ts +108 -6
- package/src/__tests__/cli-memory.test.ts +377 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +12 -2
- package/src/__tests__/config-schema.test.ts +1 -3
- package/src/__tests__/config-set-platform-guard.test.ts +302 -0
- package/src/__tests__/config-watcher-feature-flags.test.ts +211 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
- package/src/__tests__/contacts-tools.test.ts +31 -0
- package/src/__tests__/context-overflow-reducer.test.ts +86 -0
- package/src/__tests__/context-token-estimator.test.ts +175 -10
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
- package/src/__tests__/conversation-agent-loop.test.ts +9 -0
- package/src/__tests__/conversation-attachments.test.ts +2 -6
- package/src/__tests__/conversation-attention-store.test.ts +2 -6
- package/src/__tests__/conversation-clear-safety.test.ts +2 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
- package/src/__tests__/conversation-disk-view.test.ts +2 -6
- package/src/__tests__/conversation-error.test.ts +33 -2
- package/src/__tests__/conversation-fork-crud.test.ts +2 -6
- package/src/__tests__/conversation-history-web-search.test.ts +5 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
- package/src/__tests__/conversation-media-retry.test.ts +91 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +7 -4
- package/src/__tests__/conversation-slash-commands.test.ts +2 -6
- package/src/__tests__/conversation-starter-routes.test.ts +20 -11
- package/src/__tests__/conversation-store.test.ts +2 -6
- package/src/__tests__/conversation-usage.test.ts +3 -6
- package/src/__tests__/conversation-wipe.test.ts +11 -408
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
- package/src/__tests__/credential-security-e2e.test.ts +6 -1
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +7 -73
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -7
- package/src/__tests__/followup-tools.test.ts +2 -6
- package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
- package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
- package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
- package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
- package/src/__tests__/guardian-action-store.test.ts +2 -6
- package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
- package/src/__tests__/guardian-dispatch.test.ts +2 -6
- package/src/__tests__/guardian-grant-minting.test.ts +2 -14
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
- package/src/__tests__/guardian-routing-invariants.test.ts +343 -6
- package/src/__tests__/guardian-routing-state.test.ts +2 -6
- package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
- package/src/__tests__/heartbeat-service.test.ts +1 -3
- package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
- package/src/__tests__/injection-block.test.ts +154 -0
- package/src/__tests__/install-meta.test.ts +506 -0
- package/src/__tests__/install-skill-routing.test.ts +292 -0
- package/src/__tests__/intent-routing.test.ts +6 -18
- package/src/__tests__/invite-redemption-service.test.ts +2 -6
- package/src/__tests__/invite-routes-http.test.ts +2 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
- package/src/__tests__/list-messages-attachments.test.ts +2 -6
- package/src/__tests__/llm-context-route-provider.test.ts +2 -6
- package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
- package/src/__tests__/llm-usage-store.test.ts +2 -6
- package/src/__tests__/log-export-workspace.test.ts +4 -34
- package/src/__tests__/managed-skill-lifecycle.test.ts +7 -37
- package/src/__tests__/managed-store.test.ts +40 -21
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
- package/src/__tests__/memory-recall-log-store.test.ts +2 -6
- package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
- package/src/__tests__/messaging-send-tool.test.ts +6 -6
- package/src/__tests__/migration-cross-version-compatibility.test.ts +1 -29
- package/src/__tests__/migration-export-http.test.ts +3 -34
- package/src/__tests__/migration-import-commit-http.test.ts +1 -29
- package/src/__tests__/migration-import-preflight-http.test.ts +3 -34
- package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +2 -1
- package/src/__tests__/non-member-access-request.test.ts +2 -6
- package/src/__tests__/notification-guardian-path.test.ts +2 -6
- package/src/__tests__/oauth-apps-routes.test.ts +120 -10
- package/src/__tests__/oauth-cli.test.ts +364 -2
- package/src/__tests__/oauth-connect-orchestrator.test.ts +709 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +2 -1
- package/src/__tests__/oauth-provider-visibility.test.ts +149 -0
- package/src/__tests__/oauth-providers-routes.test.ts +5 -2
- package/src/__tests__/oauth-store.test.ts +0 -5
- package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
- package/src/__tests__/outlook-attachments.test.ts +301 -0
- package/src/__tests__/outlook-automation-tools.test.ts +425 -0
- package/src/__tests__/outlook-categories.test.ts +212 -0
- package/src/__tests__/outlook-client-automation.test.ts +246 -0
- package/src/__tests__/outlook-compose-tools.test.ts +325 -0
- package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
- package/src/__tests__/outlook-email-watcher.test.ts +322 -0
- package/src/__tests__/outlook-follow-up.test.ts +196 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +1071 -0
- package/src/__tests__/outlook-trash.test.ts +77 -0
- package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
- package/src/__tests__/path-policy.test.ts +2 -17
- package/src/__tests__/permission-types.test.ts +0 -1
- package/src/__tests__/platform-callback-registration.test.ts +7 -11
- package/src/__tests__/playbook-execution.test.ts +76 -80
- package/src/__tests__/playbook-tools.test.ts +5 -7
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
- package/src/__tests__/provider-error-scenarios.test.ts +21 -2
- package/src/__tests__/qdrant-manager.test.ts +68 -21
- package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/require-fresh-approval.test.ts +64 -3
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
- package/src/__tests__/runtime-events-sse.test.ts +2 -6
- package/src/__tests__/sandbox-diagnostics.test.ts +20 -29
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +2 -10
- package/src/__tests__/schedule-store.test.ts +2 -6
- package/src/__tests__/schedule-tools.test.ts +2 -6
- package/src/__tests__/scheduler-recurrence.test.ts +1 -5
- package/src/__tests__/scoped-approval-grants.test.ts +2 -6
- package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
- package/src/__tests__/search-skills-unified.test.ts +421 -0
- package/src/__tests__/secret-allowlist.test.ts +20 -35
- package/src/__tests__/secret-onetime-send.test.ts +2 -0
- package/src/__tests__/send-endpoint-busy.test.ts +2 -6
- package/src/__tests__/sequence-store.test.ts +2 -6
- package/src/__tests__/server-history-render.test.ts +2 -6
- package/src/__tests__/shell-credential-ref.test.ts +0 -5
- package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
- package/src/__tests__/skill-feature-flags.test.ts +6 -6
- package/src/__tests__/skill-load-feature-flag.test.ts +13 -54
- package/src/__tests__/skill-load-inline-command.test.ts +3 -65
- package/src/__tests__/skill-load-inline-includes.test.ts +3 -65
- package/src/__tests__/skill-load-tool.test.ts +3 -67
- package/src/__tests__/skill-memory.test.ts +480 -195
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/skills.test.ts +23 -50
- package/src/__tests__/slack-channel-config.test.ts +2 -21
- package/src/__tests__/slack-inbound-verification.test.ts +2 -6
- package/src/__tests__/starter-bundle.test.ts +2 -8
- package/src/__tests__/stt-hints.test.ts +7 -2
- package/src/__tests__/system-prompt.test.ts +25 -45
- package/src/__tests__/task-compiler.test.ts +2 -27
- package/src/__tests__/task-management-tools.test.ts +2 -27
- package/src/__tests__/task-memory-cleanup.test.ts +173 -250
- package/src/__tests__/task-runner.test.ts +2 -27
- package/src/__tests__/task-scheduler.test.ts +2 -27
- package/src/__tests__/terminal-tools.test.ts +1 -17
- package/src/__tests__/test-preload.ts +3 -0
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +0 -79
- package/src/__tests__/tool-approval-handler.test.ts +4 -27
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +2 -11
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -25
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +4 -27
- package/src/__tests__/tool-preview-lifecycle.test.ts +0 -20
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
- package/src/__tests__/trust-store.test.ts +10 -42
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -30
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +3 -27
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -28
- package/src/__tests__/trusted-contact-multichannel.test.ts +2 -28
- package/src/__tests__/trusted-contact-verification.test.ts +2 -28
- package/src/__tests__/turn-boundary-resolution.test.ts +2 -34
- package/src/__tests__/twilio-provider.test.ts +0 -16
- package/src/__tests__/twilio-routes-twiml.test.ts +7 -12
- package/src/__tests__/twilio-routes.test.ts +0 -24
- package/src/__tests__/update-bulletin.test.ts +17 -89
- package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -26
- package/src/__tests__/usage-routes.test.ts +2 -27
- package/src/__tests__/user-reference.test.ts +1 -5
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +4 -34
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +2 -53
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-invite-redemption.test.ts +2 -27
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -30
- package/src/__tests__/voice-session-bridge.test.ts +2 -27
- package/src/__tests__/volume-security-guard.test.ts +2 -0
- package/src/__tests__/workspace-lifecycle.test.ts +29 -1
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -29
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +2 -2
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +4 -29
- package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +0 -6
- package/src/__tests__/workspace-policy.test.ts +1 -1
- package/src/acp/client-handler.ts +1 -2
- package/src/agent/attachments.ts +7 -2
- package/src/agent/image-optimize.ts +165 -0
- package/src/agent/loop.ts +1 -15
- package/src/bundler/app-compiler.ts +179 -2
- package/src/bundler/package-resolver.ts +3 -5
- package/src/cli/__tests__/notifications.test.ts +1 -24
- package/src/cli/cli-memory.ts +179 -0
- package/src/cli/commands/avatar.ts +3 -3
- package/src/cli/commands/config.ts +26 -13
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/memory.ts +41 -55
- package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
- package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
- package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
- package/src/cli/commands/oauth/connect.ts +26 -6
- package/src/cli/commands/oauth/mode.ts +7 -0
- package/src/cli/commands/oauth/providers.ts +49 -42
- package/src/cli/commands/oauth/shared.ts +39 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +3 -49
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +3 -49
- package/src/cli/commands/platform/__tests__/status.test.ts +5 -55
- package/src/cli/commands/platform/index.ts +16 -16
- package/src/cli/commands/skills.ts +88 -16
- package/src/cli/commands/trust.ts +2 -2
- package/src/cli/lib/daemon-credential-client.ts +2 -3
- package/src/config/bundled-skills/acp/TOOLS.json +1 -1
- package/src/config/bundled-skills/computer-use/TOOLS.json +7 -7
- package/src/config/bundled-skills/contacts/SKILL.md +0 -1
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
- package/src/config/bundled-skills/gmail/SKILL.md +2 -10
- package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
- package/src/config/bundled-skills/messaging/SKILL.md +26 -19
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
- package/src/config/bundled-skills/outlook/SKILL.md +189 -0
- package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
- package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
- package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/slack/SKILL.md +1 -7
- package/src/config/bundled-tool-registry.ts +56 -4
- package/src/config/env-registry.ts +15 -8
- package/src/config/feature-flag-registry.json +29 -116
- package/src/config/loader.ts +4 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +8 -0
- package/src/config/schemas/timeouts.ts +1 -1
- package/src/config/skills.ts +18 -7
- package/src/context/token-estimator.ts +25 -18
- package/src/context/window-manager.ts +32 -9
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/process-manager.ts +3 -1
- package/src/daemon/config-watcher.ts +51 -0
- package/src/daemon/context-overflow-reducer.ts +46 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
- package/src/daemon/conversation-agent-loop.ts +99 -63
- package/src/daemon/conversation-error.ts +31 -8
- package/src/daemon/conversation-lifecycle.ts +33 -0
- package/src/daemon/conversation-media-retry.ts +85 -7
- package/src/daemon/conversation-notifiers.ts +4 -1
- package/src/daemon/conversation-process.ts +1 -0
- package/src/daemon/conversation-runtime-assembly.ts +5 -0
- package/src/daemon/conversation-usage.ts +1 -0
- package/src/daemon/conversation.ts +41 -2
- package/src/daemon/daemon-control.ts +8 -2
- package/src/daemon/handlers/shared.ts +22 -12
- package/src/daemon/handlers/skills.ts +423 -201
- package/src/daemon/lifecycle.ts +52 -4
- package/src/daemon/main.ts +5 -1
- package/src/daemon/message-types/conversations.ts +5 -1
- package/src/daemon/message-types/messages.ts +3 -1
- package/src/daemon/message-types/skills.ts +97 -36
- package/src/daemon/providers-setup.ts +7 -0
- package/src/daemon/server.ts +35 -22
- package/src/daemon/tool-side-effects.ts +27 -5
- package/src/events/domain-events.ts +1 -2
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/hooks/cli.ts +2 -2
- package/src/hooks/runner.ts +15 -38
- package/src/inbound/platform-callback-registration.ts +14 -14
- package/src/memory/admin.ts +11 -45
- package/src/memory/conversation-bootstrap.ts +2 -0
- package/src/memory/conversation-crud.ts +242 -348
- package/src/memory/conversation-group-migration.ts +157 -0
- package/src/memory/conversation-queries.ts +4 -2
- package/src/memory/db-init.ts +39 -3
- package/src/memory/embed.ts +73 -0
- package/src/memory/embedding-backend.ts +8 -14
- package/src/memory/embedding-runtime-manager.ts +12 -114
- package/src/memory/fingerprint.ts +2 -2
- package/src/memory/graph/bootstrap.ts +512 -0
- package/src/memory/graph/capability-seed.ts +297 -0
- package/src/memory/graph/consolidation.ts +691 -0
- package/src/memory/graph/conversation-graph-memory.ts +630 -0
- package/src/memory/graph/decay.test.ts +208 -0
- package/src/memory/graph/decay.ts +195 -0
- package/src/memory/graph/extraction-job.ts +69 -0
- package/src/memory/graph/extraction.test.ts +936 -0
- package/src/memory/graph/extraction.ts +1254 -0
- package/src/memory/graph/graph-search.ts +266 -0
- package/src/memory/graph/image-ref-utils.ts +29 -0
- package/src/memory/graph/injection.test.ts +513 -0
- package/src/memory/graph/injection.ts +439 -0
- package/src/memory/graph/inspect.ts +534 -0
- package/src/memory/graph/narrative.ts +267 -0
- package/src/memory/graph/pattern-scan.ts +269 -0
- package/src/memory/graph/retriever.ts +1008 -0
- package/src/memory/graph/scoring.test.ts +548 -0
- package/src/memory/graph/scoring.ts +232 -0
- package/src/memory/graph/serendipity.ts +65 -0
- package/src/memory/graph/store.test.ts +1050 -0
- package/src/memory/graph/store.ts +699 -0
- package/src/memory/graph/tool-handlers.ts +426 -0
- package/src/memory/graph/tools.ts +141 -0
- package/src/memory/graph/triggers.test.ts +487 -0
- package/src/memory/graph/triggers.ts +223 -0
- package/src/memory/graph/types.ts +271 -0
- package/src/memory/group-crud.ts +191 -0
- package/src/memory/indexer.ts +37 -19
- package/src/memory/job-handlers/cleanup.ts +0 -53
- package/src/memory/job-handlers/conversation-starters.ts +91 -53
- package/src/memory/job-handlers/embedding.test.ts +3 -27
- package/src/memory/job-handlers/embedding.ts +5 -31
- package/src/memory/job-handlers/index-maintenance.ts +23 -11
- package/src/memory/job-handlers/summarization.ts +32 -17
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +50 -70
- package/src/memory/jobs-worker.ts +147 -112
- package/src/memory/llm-usage-store.ts +35 -2
- package/src/memory/message-content.ts +1 -0
- package/src/memory/migrations/201-oauth-providers-feature-flag.ts +11 -0
- package/src/memory/migrations/202-drop-callback-transport-column.ts +13 -0
- package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
- package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
- package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
- package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/qdrant-client.ts +44 -17
- package/src/memory/qdrant-manager.ts +26 -5
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/memory-graph.ts +139 -0
- package/src/memory/schema/oauth.ts +1 -1
- package/src/memory/search/semantic.ts +47 -91
- package/src/memory/slack-thread-store.ts +17 -0
- package/src/memory/task-memory-cleanup.ts +28 -50
- package/src/messaging/providers/outlook/adapter.ts +200 -0
- package/src/messaging/providers/outlook/client.ts +610 -0
- package/src/messaging/providers/outlook/types.ts +201 -0
- package/src/notifications/adapters/macos.ts +1 -0
- package/src/notifications/adapters/slack.ts +1 -1
- package/src/notifications/copy-composer.ts +9 -0
- package/src/notifications/signal.ts +16 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +1 -1
- package/src/oauth/connect-orchestrator.ts +10 -3
- package/src/oauth/oauth-store.ts +10 -11
- package/src/oauth/provider-serializer.ts +3 -0
- package/src/oauth/provider-visibility.ts +16 -0
- package/src/oauth/seed-providers.ts +50 -17
- package/src/permissions/checker.ts +62 -9
- package/src/permissions/defaults.ts +4 -4
- package/src/permissions/types.ts +2 -4
- package/src/permissions/workspace-policy.ts +1 -1
- package/src/playbooks/playbook-compiler.ts +19 -18
- package/src/playbooks/types.ts +4 -3
- package/src/prompts/system-prompt.ts +6 -93
- package/src/prompts/templates/UPDATES.md +6 -0
- package/src/providers/anthropic/client.ts +47 -19
- package/src/providers/gemini/client.ts +1 -1
- package/src/providers/openai/client.ts +1 -1
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +19 -3
- package/src/runtime/actor-trust-resolver.ts +5 -1
- package/src/runtime/auth/__tests__/credential-service.test.ts +1 -27
- package/src/runtime/auth/__tests__/token-service.test.ts +1 -25
- package/src/runtime/auth/route-policy.ts +7 -4
- package/src/runtime/guardian-reply-router.ts +10 -2
- package/src/runtime/http-server.ts +23 -3
- package/src/runtime/middleware/auth.ts +20 -0
- package/src/runtime/routes/attachment-routes.test.ts +106 -0
- package/src/runtime/routes/attachment-routes.ts +106 -16
- package/src/runtime/routes/brain-graph-routes.ts +21 -22
- package/src/runtime/routes/btw-routes.ts +8 -0
- package/src/runtime/routes/conversation-management-routes.ts +2 -0
- package/src/runtime/routes/conversation-query-routes.ts +2 -58
- package/src/runtime/routes/conversation-starter-routes.ts +2 -2
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/global-search-routes.ts +21 -19
- package/src/runtime/routes/group-routes.ts +207 -0
- package/src/runtime/routes/guardian-action-routes.ts +21 -10
- package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
- package/src/runtime/routes/inbound-message-handler.ts +19 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +43 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
- package/src/runtime/routes/memory-item-routes.test.ts +2 -31
- package/src/runtime/routes/memory-item-routes.ts +385 -341
- package/src/runtime/routes/oauth-apps.ts +18 -1
- package/src/runtime/routes/oauth-providers.ts +13 -1
- package/src/runtime/routes/schedule-routes.ts +2 -0
- package/src/runtime/routes/settings-routes.ts +1 -0
- package/src/runtime/routes/skills-routes.ts +103 -37
- package/src/runtime/routes/usage-routes.ts +19 -2
- package/src/runtime/routes/work-items-routes.test.ts +2 -27
- package/src/runtime/routes/workspace-routes.test.ts +3 -27
- package/src/schedule/scheduler.ts +8 -1
- package/src/security/oauth2.ts +1 -1
- package/src/security/secret-allowlist.ts +4 -4
- package/src/security/secure-keys.ts +4 -8
- package/src/shared/provider-env-vars.ts +19 -0
- package/src/skills/catalog-cache.ts +5 -0
- package/src/skills/catalog-install.ts +15 -14
- package/src/skills/clawhub.ts +134 -154
- package/src/skills/install-meta.ts +208 -0
- package/src/skills/managed-store.ts +27 -16
- package/src/skills/skill-memory.ts +210 -96
- package/src/skills/skillssh-registry.ts +19 -17
- package/src/tasks/task-runner.ts +3 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
- package/src/tools/browser/runtime-check.ts +3 -1
- package/src/tools/memory/register.ts +63 -46
- package/src/tools/permission-checker.ts +7 -19
- package/src/tools/shared/filesystem/image-read.ts +22 -85
- package/src/tools/skills/skill-script-runner.ts +1 -1
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/tool-manifest.ts +3 -3
- package/src/util/browser.ts +25 -10
- package/src/util/bun-runtime.ts +172 -0
- package/src/util/device-id.ts +3 -65
- package/src/watcher/providers/outlook-calendar.ts +343 -0
- package/src/watcher/providers/outlook.ts +198 -0
- package/src/workspace/git-service.ts +27 -6
- package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
- package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
- package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/context-memory-e2e.test.ts +0 -415
- package/src/__tests__/journal-context.test.ts +0 -268
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
- package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
- package/src/__tests__/memory-query-builder.test.ts +0 -59
- package/src/__tests__/memory-recall-quality.test.ts +0 -1046
- package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
- package/src/__tests__/memory-regressions.test.ts +0 -3696
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
- package/src/daemon/conversation-memory.ts +0 -207
- package/src/memory/conversation-starters-cadence.ts +0 -74
- package/src/memory/items-extractor.ts +0 -860
- package/src/memory/job-handlers/batch-extraction.ts +0 -741
- package/src/memory/job-handlers/extraction.ts +0 -40
- package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -383
- package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
- package/src/memory/journal-memory.ts +0 -224
- package/src/memory/query-builder.ts +0 -47
- package/src/memory/query-expansion.ts +0 -83
- package/src/memory/retriever.test.ts +0 -1590
- package/src/memory/retriever.ts +0 -1323
- package/src/memory/search/formatting.test.ts +0 -140
- package/src/memory/search/formatting.ts +0 -262
- package/src/memory/search/mmr.ts +0 -136
- package/src/memory/search/ranking.ts +0 -15
- package/src/memory/search/staleness.ts +0 -40
- package/src/memory/search/tier-classifier.ts +0 -18
- package/src/memory/search/types.ts +0 -121
- package/src/prompts/journal-context.ts +0 -156
- package/src/tools/memory/definitions.ts +0 -69
- package/src/tools/memory/handlers.test.ts +0 -590
- package/src/tools/memory/handlers.ts +0 -434
|
@@ -1,25 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
1
|
+
import { rmSync } from "node:fs";
|
|
4
2
|
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
5
3
|
|
|
6
|
-
import { eq } from "drizzle-orm";
|
|
7
|
-
|
|
8
|
-
const testDir = mkdtempSync(join(tmpdir(), "skill-memory-"));
|
|
9
|
-
|
|
10
|
-
mock.module("../util/platform.js", () => ({
|
|
11
|
-
getDataDir: () => testDir,
|
|
12
|
-
isMacOS: () => process.platform === "darwin",
|
|
13
|
-
isLinux: () => process.platform === "linux",
|
|
14
|
-
isWindows: () => process.platform === "win32",
|
|
15
|
-
getPidPath: () => join(testDir, "test.pid"),
|
|
16
|
-
getDbPath: () => join(testDir, "test.db"),
|
|
17
|
-
getLogPath: () => join(testDir, "test.log"),
|
|
18
|
-
ensureDataDir: () => {},
|
|
19
|
-
getWorkspaceSkillsDir: () => join(testDir, "skills"),
|
|
20
|
-
getWorkspaceConfigPath: () => join(testDir, "config.json"),
|
|
21
|
-
readPlatformToken: () => undefined,
|
|
22
|
-
}));
|
|
4
|
+
import { eq, like } from "drizzle-orm";
|
|
23
5
|
|
|
24
6
|
mock.module("../util/logger.js", () => ({
|
|
25
7
|
getLogger: () =>
|
|
@@ -38,16 +20,25 @@ mock.module("../memory/qdrant-client.js", () => ({
|
|
|
38
20
|
initQdrantClient: () => {},
|
|
39
21
|
}));
|
|
40
22
|
|
|
41
|
-
// Controllable mock for
|
|
42
|
-
let
|
|
43
|
-
|
|
44
|
-
|
|
23
|
+
// Controllable mock for loadSkillCatalog used by seedCatalogSkillMemories
|
|
24
|
+
let mockLoadSkillCatalog: () => import("../config/skills.js").SkillSummary[] =
|
|
25
|
+
() => [];
|
|
26
|
+
|
|
27
|
+
mock.module("../config/skills.js", () => ({
|
|
28
|
+
loadSkillCatalog: (..._args: unknown[]) => mockLoadSkillCatalog(),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
// Controllable mock for getCachedCatalogSync used by seedCatalogSkillMemories
|
|
32
|
+
let mockGetCachedCatalogSync: () => import("../skills/catalog-install.js").CatalogSkill[] =
|
|
33
|
+
() => [];
|
|
45
34
|
|
|
46
|
-
mock.module("../skills/catalog-
|
|
47
|
-
|
|
35
|
+
mock.module("../skills/catalog-cache.js", () => ({
|
|
36
|
+
getCachedCatalogSync: (..._args: unknown[]) => mockGetCachedCatalogSync(),
|
|
37
|
+
getCatalog: async () => mockGetCachedCatalogSync(),
|
|
38
|
+
invalidateCatalogCache: () => {},
|
|
48
39
|
}));
|
|
49
40
|
|
|
50
|
-
// Controllable mock for isAssistantFeatureFlagEnabled used by
|
|
41
|
+
// Controllable mock for isAssistantFeatureFlagEnabled used by resolveSkillStates
|
|
51
42
|
let mockIsFeatureFlagEnabled: (key: string) => boolean = () => true;
|
|
52
43
|
|
|
53
44
|
mock.module("../config/assistant-feature-flags.js", () => ({
|
|
@@ -78,40 +69,44 @@ mock.module("../config/loader.js", () => ({
|
|
|
78
69
|
invalidateConfigCache: () => {},
|
|
79
70
|
}));
|
|
80
71
|
|
|
72
|
+
import type { SkillSummary } from "../config/skills.js";
|
|
81
73
|
import { getDb, initializeDb, resetDb } from "../memory/db.js";
|
|
82
|
-
import {
|
|
83
|
-
import type { CatalogSkill } from "../skills/catalog-install.js";
|
|
74
|
+
import { memoryGraphNodes, memoryJobs } from "../memory/schema.js";
|
|
84
75
|
import {
|
|
85
76
|
buildCapabilityStatement,
|
|
86
77
|
deleteSkillCapabilityMemory,
|
|
78
|
+
fromSkillSummary,
|
|
87
79
|
seedCatalogSkillMemories,
|
|
80
|
+
type SkillCapabilityInput,
|
|
88
81
|
upsertSkillCapabilityMemory,
|
|
89
82
|
} from "../skills/skill-memory.js";
|
|
83
|
+
import { ensureDataDir, getDbPath } from "../util/platform.js";
|
|
90
84
|
|
|
85
|
+
ensureDataDir();
|
|
91
86
|
initializeDb();
|
|
92
87
|
|
|
93
88
|
afterAll(() => {
|
|
94
89
|
resetDb();
|
|
95
|
-
try {
|
|
96
|
-
rmSync(testDir, { recursive: true });
|
|
97
|
-
} catch {
|
|
98
|
-
// best effort cleanup
|
|
99
|
-
}
|
|
100
90
|
});
|
|
101
91
|
|
|
102
92
|
function resetTables() {
|
|
103
93
|
const db = getDb();
|
|
104
|
-
db.run("DELETE FROM memory_item_sources");
|
|
105
94
|
db.run("DELETE FROM memory_embeddings");
|
|
106
|
-
db.run("DELETE FROM
|
|
95
|
+
db.run("DELETE FROM memory_graph_nodes");
|
|
107
96
|
db.run("DELETE FROM memory_jobs");
|
|
108
97
|
}
|
|
109
98
|
|
|
110
|
-
function
|
|
99
|
+
function makeSkillSummary(
|
|
100
|
+
overrides: Partial<SkillSummary> = {},
|
|
101
|
+
): SkillSummary {
|
|
111
102
|
return {
|
|
112
103
|
id: "test-skill",
|
|
113
|
-
name: "
|
|
104
|
+
name: "test-skill",
|
|
105
|
+
displayName: "Test Skill",
|
|
114
106
|
description: "A skill for testing",
|
|
107
|
+
directoryPath: "/skills/test-skill",
|
|
108
|
+
skillFilePath: "/skills/test-skill/SKILL.md",
|
|
109
|
+
source: "managed",
|
|
115
110
|
...overrides,
|
|
116
111
|
};
|
|
117
112
|
}
|
|
@@ -120,33 +115,63 @@ function makeSkill(overrides: Partial<CatalogSkill> = {}): CatalogSkill {
|
|
|
120
115
|
|
|
121
116
|
describe("buildCapabilityStatement", () => {
|
|
122
117
|
test("includes display name, id, and description", () => {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
const input: SkillCapabilityInput = {
|
|
119
|
+
id: "test-skill",
|
|
120
|
+
displayName: "My Skill",
|
|
121
|
+
description: "A skill for testing",
|
|
122
|
+
};
|
|
123
|
+
const result = buildCapabilityStatement(input);
|
|
127
124
|
expect(result).toContain('"My Skill"');
|
|
128
125
|
expect(result).toContain("(test-skill)");
|
|
129
126
|
expect(result).toContain("A skill for testing");
|
|
130
127
|
});
|
|
131
128
|
|
|
132
129
|
test("includes activation hints when present", () => {
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
});
|
|
141
|
-
const result = buildCapabilityStatement(entry);
|
|
130
|
+
const input: SkillCapabilityInput = {
|
|
131
|
+
id: "test-skill",
|
|
132
|
+
displayName: "My Skill",
|
|
133
|
+
description: "A skill for testing",
|
|
134
|
+
activationHints: ["user asks to search", "needs web data"],
|
|
135
|
+
};
|
|
136
|
+
const result = buildCapabilityStatement(input);
|
|
142
137
|
expect(result).toContain("Use when:");
|
|
143
138
|
expect(result).toContain("user asks to search");
|
|
144
139
|
expect(result).toContain("needs web data");
|
|
145
140
|
});
|
|
146
141
|
|
|
147
|
-
test("
|
|
148
|
-
const
|
|
149
|
-
|
|
142
|
+
test("includes avoidWhen routing cues when present", () => {
|
|
143
|
+
const input: SkillCapabilityInput = {
|
|
144
|
+
id: "test-skill",
|
|
145
|
+
displayName: "My Skill",
|
|
146
|
+
description: "A skill for testing",
|
|
147
|
+
avoidWhen: ["user wants local files only", "offline mode"],
|
|
148
|
+
};
|
|
149
|
+
const result = buildCapabilityStatement(input);
|
|
150
|
+
expect(result).toContain("Avoid when:");
|
|
151
|
+
expect(result).toContain("user wants local files only");
|
|
152
|
+
expect(result).toContain("offline mode");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("includes both activationHints and avoidWhen when present", () => {
|
|
156
|
+
const input: SkillCapabilityInput = {
|
|
157
|
+
id: "test-skill",
|
|
158
|
+
displayName: "My Skill",
|
|
159
|
+
description: "A skill for testing",
|
|
160
|
+
activationHints: ["user asks to search"],
|
|
161
|
+
avoidWhen: ["offline mode"],
|
|
162
|
+
};
|
|
163
|
+
const result = buildCapabilityStatement(input);
|
|
164
|
+
expect(result).toContain("Use when: user asks to search.");
|
|
165
|
+
expect(result).toContain("Avoid when: offline mode.");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("works with just name as displayName", () => {
|
|
169
|
+
const input: SkillCapabilityInput = {
|
|
170
|
+
id: "test-skill",
|
|
171
|
+
displayName: "Test Skill",
|
|
172
|
+
description: "A skill for testing",
|
|
173
|
+
};
|
|
174
|
+
const result = buildCapabilityStatement(input);
|
|
150
175
|
expect(result).toContain('"Test Skill"');
|
|
151
176
|
expect(result).toContain("(test-skill)");
|
|
152
177
|
expect(result).toContain("A skill for testing");
|
|
@@ -154,77 +179,131 @@ describe("buildCapabilityStatement", () => {
|
|
|
154
179
|
|
|
155
180
|
test("truncates long statements to 500 chars", () => {
|
|
156
181
|
const longDesc = "x".repeat(600);
|
|
157
|
-
const
|
|
158
|
-
|
|
182
|
+
const input: SkillCapabilityInput = {
|
|
183
|
+
id: "test-skill",
|
|
184
|
+
displayName: "Test Skill",
|
|
185
|
+
description: longDesc,
|
|
186
|
+
};
|
|
187
|
+
const result = buildCapabilityStatement(input);
|
|
159
188
|
expect(result.length).toBe(500);
|
|
160
189
|
});
|
|
161
190
|
});
|
|
162
191
|
|
|
192
|
+
// ─── fromSkillSummary ────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
describe("fromSkillSummary", () => {
|
|
195
|
+
test("maps displayName from SkillSummary", () => {
|
|
196
|
+
const entry = makeSkillSummary({ displayName: "Pretty Name" });
|
|
197
|
+
const input = fromSkillSummary(entry);
|
|
198
|
+
expect(input.displayName).toBe("Pretty Name");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("maps activationHints from SkillSummary", () => {
|
|
202
|
+
const hints = ["user asks to search", "needs web data"];
|
|
203
|
+
const entry = makeSkillSummary({ activationHints: hints });
|
|
204
|
+
const input = fromSkillSummary(entry);
|
|
205
|
+
expect(input.activationHints).toEqual(hints);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("leaves activationHints undefined when not present", () => {
|
|
209
|
+
const entry = makeSkillSummary({ activationHints: undefined });
|
|
210
|
+
const input = fromSkillSummary(entry);
|
|
211
|
+
expect(input.activationHints).toBeUndefined();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("maps avoidWhen from SkillSummary", () => {
|
|
215
|
+
const cues = ["offline mode", "user wants local files only"];
|
|
216
|
+
const entry = makeSkillSummary({ avoidWhen: cues });
|
|
217
|
+
const input = fromSkillSummary(entry);
|
|
218
|
+
expect(input.avoidWhen).toEqual(cues);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("leaves avoidWhen undefined when not present", () => {
|
|
222
|
+
const entry = makeSkillSummary({ avoidWhen: undefined });
|
|
223
|
+
const input = fromSkillSummary(entry);
|
|
224
|
+
expect(input.avoidWhen).toBeUndefined();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("copies id and description directly", () => {
|
|
228
|
+
const entry = makeSkillSummary({
|
|
229
|
+
id: "my-id",
|
|
230
|
+
description: "Does amazing things",
|
|
231
|
+
});
|
|
232
|
+
const input = fromSkillSummary(entry);
|
|
233
|
+
expect(input.id).toBe("my-id");
|
|
234
|
+
expect(input.description).toBe("Does amazing things");
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
163
238
|
// ─── upsertSkillCapabilityMemory ─────────────────────────────────────────────
|
|
164
239
|
|
|
165
240
|
describe("upsertSkillCapabilityMemory", () => {
|
|
166
241
|
beforeEach(resetTables);
|
|
167
242
|
|
|
168
|
-
test("inserts with correct
|
|
169
|
-
const
|
|
170
|
-
upsertSkillCapabilityMemory("test-skill",
|
|
243
|
+
test("inserts with correct type, content, confidence, significance", () => {
|
|
244
|
+
const input = fromSkillSummary(makeSkillSummary());
|
|
245
|
+
upsertSkillCapabilityMemory("test-skill", input);
|
|
171
246
|
|
|
172
247
|
const db = getDb();
|
|
173
|
-
const items = db.select().from(
|
|
248
|
+
const items = db.select().from(memoryGraphNodes).all();
|
|
174
249
|
expect(items).toHaveLength(1);
|
|
175
|
-
expect(items[0].
|
|
176
|
-
expect(items[0].
|
|
250
|
+
expect(items[0].type).toBe("procedural");
|
|
251
|
+
expect(items[0].content).toMatch(/^skill:test-skill\n/);
|
|
177
252
|
expect(items[0].confidence).toBe(1.0);
|
|
178
|
-
expect(items[0].
|
|
179
|
-
expect(items[0].
|
|
253
|
+
expect(items[0].significance).toBe(0.7);
|
|
254
|
+
expect(items[0].fidelity).toBe("vivid");
|
|
180
255
|
expect(items[0].scopeId).toBe("default");
|
|
181
256
|
|
|
182
|
-
// Should also enqueue an
|
|
257
|
+
// Should also enqueue an embed_graph_node job
|
|
183
258
|
const jobs = db.select().from(memoryJobs).all();
|
|
184
259
|
expect(jobs).toHaveLength(1);
|
|
185
|
-
expect(jobs[0].type).toBe("
|
|
260
|
+
expect(jobs[0].type).toBe("embed_graph_node");
|
|
186
261
|
});
|
|
187
262
|
|
|
188
|
-
test("is idempotent (same entry only touches
|
|
189
|
-
const
|
|
190
|
-
upsertSkillCapabilityMemory("test-skill",
|
|
263
|
+
test("is idempotent (same entry only touches lastAccessed)", () => {
|
|
264
|
+
const input = fromSkillSummary(makeSkillSummary());
|
|
265
|
+
upsertSkillCapabilityMemory("test-skill", input);
|
|
191
266
|
|
|
192
267
|
const db = getDb();
|
|
193
|
-
const before = db.select().from(
|
|
268
|
+
const before = db.select().from(memoryGraphNodes).all();
|
|
194
269
|
expect(before).toHaveLength(1);
|
|
195
|
-
const
|
|
270
|
+
const originalLastAccessed = before[0].lastAccessed;
|
|
196
271
|
|
|
197
272
|
// Upsert again
|
|
198
|
-
upsertSkillCapabilityMemory("test-skill",
|
|
273
|
+
upsertSkillCapabilityMemory("test-skill", input);
|
|
199
274
|
|
|
200
|
-
const after = db.select().from(
|
|
275
|
+
const after = db.select().from(memoryGraphNodes).all();
|
|
201
276
|
expect(after).toHaveLength(1);
|
|
202
|
-
//
|
|
203
|
-
expect(after[0].
|
|
204
|
-
expect(after[0].
|
|
277
|
+
// Same content, so only lastAccessed changes
|
|
278
|
+
expect(after[0].content).toBe(before[0].content);
|
|
279
|
+
expect(after[0].lastAccessed).toBeGreaterThanOrEqual(originalLastAccessed);
|
|
205
280
|
|
|
206
281
|
// Should NOT enqueue a second embed job (only 1 from initial insert)
|
|
207
282
|
const jobs = db.select().from(memoryJobs).all();
|
|
208
283
|
expect(jobs).toHaveLength(1);
|
|
209
284
|
});
|
|
210
285
|
|
|
211
|
-
test("updates
|
|
212
|
-
const
|
|
213
|
-
|
|
286
|
+
test("updates content when description changes", () => {
|
|
287
|
+
const input = fromSkillSummary(
|
|
288
|
+
makeSkillSummary({ description: "Original description" }),
|
|
289
|
+
);
|
|
290
|
+
upsertSkillCapabilityMemory("test-skill", input);
|
|
214
291
|
|
|
215
292
|
const db = getDb();
|
|
216
|
-
const before = db.select().from(
|
|
293
|
+
const before = db.select().from(memoryGraphNodes).all();
|
|
217
294
|
expect(before).toHaveLength(1);
|
|
218
|
-
expect(before[0].
|
|
295
|
+
expect(before[0].content).toContain("Original description");
|
|
219
296
|
|
|
220
297
|
// Change description
|
|
221
|
-
const
|
|
222
|
-
|
|
298
|
+
const updatedInput = fromSkillSummary(
|
|
299
|
+
makeSkillSummary({ description: "Updated description" }),
|
|
300
|
+
);
|
|
301
|
+
upsertSkillCapabilityMemory("test-skill", updatedInput);
|
|
223
302
|
|
|
224
|
-
const after = db.select().from(
|
|
303
|
+
const after = db.select().from(memoryGraphNodes).all();
|
|
225
304
|
expect(after).toHaveLength(1);
|
|
226
|
-
expect(after[0].
|
|
227
|
-
expect(after[0].
|
|
305
|
+
expect(after[0].content).toContain("Updated description");
|
|
306
|
+
expect(after[0].content).not.toBe(before[0].content);
|
|
228
307
|
|
|
229
308
|
// Should enqueue a second embed job
|
|
230
309
|
const jobs = db.select().from(memoryJobs).all();
|
|
@@ -232,33 +311,33 @@ describe("upsertSkillCapabilityMemory", () => {
|
|
|
232
311
|
});
|
|
233
312
|
|
|
234
313
|
test("reactivates soft-deleted items", () => {
|
|
235
|
-
const
|
|
236
|
-
upsertSkillCapabilityMemory("test-skill",
|
|
314
|
+
const input = fromSkillSummary(makeSkillSummary());
|
|
315
|
+
upsertSkillCapabilityMemory("test-skill", input);
|
|
237
316
|
|
|
238
317
|
const db = getDb();
|
|
239
318
|
// Soft-delete the item
|
|
240
|
-
db.update(
|
|
241
|
-
.set({
|
|
242
|
-
.where(
|
|
319
|
+
db.update(memoryGraphNodes)
|
|
320
|
+
.set({ fidelity: "gone" })
|
|
321
|
+
.where(like(memoryGraphNodes.content, "skill:test-skill\n%"))
|
|
243
322
|
.run();
|
|
244
323
|
|
|
245
|
-
const deleted = db.select().from(
|
|
246
|
-
expect(deleted[0].
|
|
324
|
+
const deleted = db.select().from(memoryGraphNodes).all();
|
|
325
|
+
expect(deleted[0].fidelity).toBe("gone");
|
|
247
326
|
|
|
248
327
|
// Clear jobs from initial insert
|
|
249
328
|
db.run("DELETE FROM memory_jobs");
|
|
250
329
|
|
|
251
330
|
// Upsert again — should reactivate
|
|
252
|
-
upsertSkillCapabilityMemory("test-skill",
|
|
331
|
+
upsertSkillCapabilityMemory("test-skill", input);
|
|
253
332
|
|
|
254
|
-
const reactivated = db.select().from(
|
|
333
|
+
const reactivated = db.select().from(memoryGraphNodes).all();
|
|
255
334
|
expect(reactivated).toHaveLength(1);
|
|
256
|
-
expect(reactivated[0].
|
|
335
|
+
expect(reactivated[0].fidelity).toBe("vivid");
|
|
257
336
|
|
|
258
337
|
// Should enqueue embed job for reactivated item
|
|
259
338
|
const jobs = db.select().from(memoryJobs).all();
|
|
260
339
|
expect(jobs).toHaveLength(1);
|
|
261
|
-
expect(jobs[0].type).toBe("
|
|
340
|
+
expect(jobs[0].type).toBe("embed_graph_node");
|
|
262
341
|
});
|
|
263
342
|
|
|
264
343
|
test("does not throw on DB error", () => {
|
|
@@ -268,12 +347,15 @@ describe("upsertSkillCapabilityMemory", () => {
|
|
|
268
347
|
// dropping the table it reads from. Use a fresh DB without initialization.
|
|
269
348
|
// Instead, verify the try/catch by closing and reopening:
|
|
270
349
|
// resetDb closes the connection; getDb lazily reconnects.
|
|
271
|
-
// We drop the
|
|
350
|
+
// We drop the memory_graph_nodes table to force an error on the next query.
|
|
272
351
|
const db = getDb();
|
|
273
|
-
db.run("DROP TABLE IF EXISTS
|
|
352
|
+
db.run("DROP TABLE IF EXISTS memory_graph_nodes");
|
|
274
353
|
|
|
275
354
|
expect(() => {
|
|
276
|
-
upsertSkillCapabilityMemory(
|
|
355
|
+
upsertSkillCapabilityMemory(
|
|
356
|
+
"test-skill",
|
|
357
|
+
fromSkillSummary(makeSkillSummary()),
|
|
358
|
+
);
|
|
277
359
|
}).not.toThrow();
|
|
278
360
|
|
|
279
361
|
// Restore DB state for subsequent tests.
|
|
@@ -281,8 +363,9 @@ describe("upsertSkillCapabilityMemory", () => {
|
|
|
281
363
|
// resetting the connection leaves stale migration checkpoints that skip
|
|
282
364
|
// checkpoint-guarded ALTER TABLE migrations (e.g. source_type column).
|
|
283
365
|
resetDb();
|
|
366
|
+
const dbPath = getDbPath();
|
|
284
367
|
for (const ext of ["", "-wal", "-shm"]) {
|
|
285
|
-
rmSync(
|
|
368
|
+
rmSync(`${dbPath}${ext}`, { force: true });
|
|
286
369
|
}
|
|
287
370
|
initializeDb();
|
|
288
371
|
});
|
|
@@ -294,19 +377,19 @@ describe("deleteSkillCapabilityMemory", () => {
|
|
|
294
377
|
beforeEach(resetTables);
|
|
295
378
|
|
|
296
379
|
test("soft-deletes matching item", () => {
|
|
297
|
-
const
|
|
298
|
-
upsertSkillCapabilityMemory("test-skill",
|
|
380
|
+
const input = fromSkillSummary(makeSkillSummary());
|
|
381
|
+
upsertSkillCapabilityMemory("test-skill", input);
|
|
299
382
|
|
|
300
383
|
const db = getDb();
|
|
301
|
-
const before = db.select().from(
|
|
384
|
+
const before = db.select().from(memoryGraphNodes).all();
|
|
302
385
|
expect(before).toHaveLength(1);
|
|
303
|
-
expect(before[0].
|
|
386
|
+
expect(before[0].fidelity).toBe("vivid");
|
|
304
387
|
|
|
305
388
|
deleteSkillCapabilityMemory("test-skill");
|
|
306
389
|
|
|
307
|
-
const after = db.select().from(
|
|
390
|
+
const after = db.select().from(memoryGraphNodes).all();
|
|
308
391
|
expect(after).toHaveLength(1);
|
|
309
|
-
expect(after[0].
|
|
392
|
+
expect(after[0].fidelity).toBe("gone");
|
|
310
393
|
});
|
|
311
394
|
|
|
312
395
|
test("is no-op for missing item", () => {
|
|
@@ -316,7 +399,7 @@ describe("deleteSkillCapabilityMemory", () => {
|
|
|
316
399
|
}).not.toThrow();
|
|
317
400
|
|
|
318
401
|
const db = getDb();
|
|
319
|
-
const items = db.select().from(
|
|
402
|
+
const items = db.select().from(memoryGraphNodes).all();
|
|
320
403
|
expect(items).toHaveLength(0);
|
|
321
404
|
});
|
|
322
405
|
|
|
@@ -324,7 +407,7 @@ describe("deleteSkillCapabilityMemory", () => {
|
|
|
324
407
|
// Close and reopen DB, then drop the table to force a query error
|
|
325
408
|
resetDb();
|
|
326
409
|
const db = getDb();
|
|
327
|
-
db.run("DROP TABLE IF EXISTS
|
|
410
|
+
db.run("DROP TABLE IF EXISTS memory_graph_nodes");
|
|
328
411
|
|
|
329
412
|
expect(() => {
|
|
330
413
|
deleteSkillCapabilityMemory("test-skill");
|
|
@@ -333,8 +416,9 @@ describe("deleteSkillCapabilityMemory", () => {
|
|
|
333
416
|
// Restore DB state for subsequent tests (see upsert "does not throw" test
|
|
334
417
|
// for rationale on why we delete the DB file).
|
|
335
418
|
resetDb();
|
|
419
|
+
const dbPath = getDbPath();
|
|
336
420
|
for (const ext of ["", "-wal", "-shm"]) {
|
|
337
|
-
rmSync(
|
|
421
|
+
rmSync(`${dbPath}${ext}`, { force: true });
|
|
338
422
|
}
|
|
339
423
|
initializeDb();
|
|
340
424
|
});
|
|
@@ -346,215 +430,416 @@ describe("seedCatalogSkillMemories", () => {
|
|
|
346
430
|
beforeEach(() => {
|
|
347
431
|
resetTables();
|
|
348
432
|
// Reset mocks to defaults
|
|
349
|
-
|
|
433
|
+
mockLoadSkillCatalog = () => [];
|
|
350
434
|
mockIsFeatureFlagEnabled = () => true;
|
|
435
|
+
// Default: non-empty cache so pruning is allowed
|
|
436
|
+
mockGetCachedCatalogSync = () => [
|
|
437
|
+
{ id: "_sentinel", name: "_sentinel", description: "" },
|
|
438
|
+
];
|
|
351
439
|
});
|
|
352
440
|
|
|
353
|
-
test("upserts capability memories for all
|
|
354
|
-
const skills:
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
441
|
+
test("upserts capability memories for all enabled skills", () => {
|
|
442
|
+
const skills: SkillSummary[] = [
|
|
443
|
+
makeSkillSummary({
|
|
444
|
+
id: "skill-a",
|
|
445
|
+
displayName: "Skill A",
|
|
446
|
+
description: "Does A",
|
|
447
|
+
}),
|
|
448
|
+
makeSkillSummary({
|
|
449
|
+
id: "skill-b",
|
|
450
|
+
displayName: "Skill B",
|
|
451
|
+
description: "Does B",
|
|
452
|
+
}),
|
|
453
|
+
makeSkillSummary({
|
|
454
|
+
id: "skill-c",
|
|
455
|
+
displayName: "Skill C",
|
|
456
|
+
description: "Does C",
|
|
457
|
+
}),
|
|
358
458
|
];
|
|
359
|
-
|
|
459
|
+
mockLoadSkillCatalog = () => skills;
|
|
360
460
|
|
|
361
|
-
|
|
461
|
+
seedCatalogSkillMemories();
|
|
362
462
|
|
|
363
463
|
const db = getDb();
|
|
364
464
|
const items = db
|
|
365
465
|
.select()
|
|
366
|
-
.from(
|
|
367
|
-
.where(eq(
|
|
466
|
+
.from(memoryGraphNodes)
|
|
467
|
+
.where(eq(memoryGraphNodes.type, "procedural"))
|
|
368
468
|
.all();
|
|
369
469
|
expect(items).toHaveLength(3);
|
|
370
470
|
|
|
371
|
-
const
|
|
372
|
-
expect(
|
|
471
|
+
const contentPrefixes = items.map((i) => i.content.split("\n")[0]).sort();
|
|
472
|
+
expect(contentPrefixes).toEqual([
|
|
373
473
|
"skill:skill-a",
|
|
374
474
|
"skill:skill-b",
|
|
375
475
|
"skill:skill-c",
|
|
376
476
|
]);
|
|
377
477
|
|
|
378
|
-
// All should be
|
|
478
|
+
// All should be vivid
|
|
379
479
|
for (const item of items) {
|
|
380
|
-
expect(item.
|
|
480
|
+
expect(item.fidelity).toBe("vivid");
|
|
381
481
|
}
|
|
382
482
|
});
|
|
383
483
|
|
|
384
|
-
test("
|
|
484
|
+
test("includes bundled skills in seeded memories", () => {
|
|
485
|
+
const skills: SkillSummary[] = [
|
|
486
|
+
makeSkillSummary({
|
|
487
|
+
id: "managed-skill",
|
|
488
|
+
displayName: "Managed",
|
|
489
|
+
description: "A managed skill",
|
|
490
|
+
source: "managed",
|
|
491
|
+
}),
|
|
492
|
+
makeSkillSummary({
|
|
493
|
+
id: "bundled-skill",
|
|
494
|
+
displayName: "Bundled",
|
|
495
|
+
description: "A bundled skill",
|
|
496
|
+
source: "bundled",
|
|
497
|
+
bundled: true,
|
|
498
|
+
}),
|
|
499
|
+
];
|
|
500
|
+
mockLoadSkillCatalog = () => skills;
|
|
501
|
+
|
|
502
|
+
seedCatalogSkillMemories();
|
|
503
|
+
|
|
504
|
+
const db = getDb();
|
|
505
|
+
const items = db
|
|
506
|
+
.select()
|
|
507
|
+
.from(memoryGraphNodes)
|
|
508
|
+
.where(eq(memoryGraphNodes.type, "procedural"))
|
|
509
|
+
.all();
|
|
510
|
+
expect(items).toHaveLength(2);
|
|
511
|
+
|
|
512
|
+
const contentPrefixes = items.map((i) => i.content.split("\n")[0]).sort();
|
|
513
|
+
expect(contentPrefixes).toEqual(["skill:bundled-skill", "skill:managed-skill"]);
|
|
514
|
+
|
|
515
|
+
for (const item of items) {
|
|
516
|
+
expect(item.fidelity).toBe("vivid");
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test("excludes bundled skills filtered by allowBundled config", () => {
|
|
521
|
+
const skills: SkillSummary[] = [
|
|
522
|
+
makeSkillSummary({
|
|
523
|
+
id: "allowed-bundled",
|
|
524
|
+
displayName: "Allowed Bundled",
|
|
525
|
+
description: "This bundled skill is allowed",
|
|
526
|
+
source: "bundled",
|
|
527
|
+
bundled: true,
|
|
528
|
+
}),
|
|
529
|
+
makeSkillSummary({
|
|
530
|
+
id: "blocked-bundled",
|
|
531
|
+
displayName: "Blocked Bundled",
|
|
532
|
+
description: "This bundled skill is not in allowBundled",
|
|
533
|
+
source: "bundled",
|
|
534
|
+
bundled: true,
|
|
535
|
+
}),
|
|
536
|
+
makeSkillSummary({
|
|
537
|
+
id: "managed-skill",
|
|
538
|
+
displayName: "Managed",
|
|
539
|
+
description: "A managed skill",
|
|
540
|
+
source: "managed",
|
|
541
|
+
}),
|
|
542
|
+
];
|
|
543
|
+
mockLoadSkillCatalog = () => skills;
|
|
544
|
+
|
|
545
|
+
// Override config to set allowBundled to only allow one bundled skill
|
|
546
|
+
const configWithAllowBundled = {
|
|
547
|
+
...TEST_CONFIG,
|
|
548
|
+
skills: {
|
|
549
|
+
...TEST_CONFIG.skills,
|
|
550
|
+
allowBundled: ["allowed-bundled"],
|
|
551
|
+
},
|
|
552
|
+
};
|
|
553
|
+
mock.module("../config/loader.js", () => ({
|
|
554
|
+
loadConfig: () => configWithAllowBundled,
|
|
555
|
+
getConfig: () => configWithAllowBundled,
|
|
556
|
+
loadRawConfig: () => ({}),
|
|
557
|
+
saveRawConfig: () => {},
|
|
558
|
+
invalidateConfigCache: () => {},
|
|
559
|
+
}));
|
|
560
|
+
|
|
561
|
+
seedCatalogSkillMemories();
|
|
562
|
+
|
|
563
|
+
const db = getDb();
|
|
564
|
+
const items = db
|
|
565
|
+
.select()
|
|
566
|
+
.from(memoryGraphNodes)
|
|
567
|
+
.where(eq(memoryGraphNodes.type, "procedural"))
|
|
568
|
+
.all();
|
|
569
|
+
|
|
570
|
+
// Only allowed-bundled and managed-skill should be seeded
|
|
571
|
+
expect(items).toHaveLength(2);
|
|
572
|
+
const contentPrefixes = items.map((i) => i.content.split("\n")[0]).sort();
|
|
573
|
+
expect(contentPrefixes).toEqual(["skill:allowed-bundled", "skill:managed-skill"]);
|
|
574
|
+
|
|
575
|
+
// Restore default config mock
|
|
576
|
+
mock.module("../config/loader.js", () => ({
|
|
577
|
+
loadConfig: () => TEST_CONFIG,
|
|
578
|
+
getConfig: () => TEST_CONFIG,
|
|
579
|
+
loadRawConfig: () => ({}),
|
|
580
|
+
saveRawConfig: () => {},
|
|
581
|
+
invalidateConfigCache: () => {},
|
|
582
|
+
}));
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
test("prunes stale capabilities for skills no longer enabled", () => {
|
|
385
586
|
// First seed with three skills
|
|
386
|
-
const initialSkills:
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
587
|
+
const initialSkills: SkillSummary[] = [
|
|
588
|
+
makeSkillSummary({
|
|
589
|
+
id: "skill-a",
|
|
590
|
+
displayName: "Skill A",
|
|
591
|
+
description: "Does A",
|
|
592
|
+
}),
|
|
593
|
+
makeSkillSummary({
|
|
594
|
+
id: "skill-b",
|
|
595
|
+
displayName: "Skill B",
|
|
596
|
+
description: "Does B",
|
|
597
|
+
}),
|
|
598
|
+
makeSkillSummary({
|
|
599
|
+
id: "skill-c",
|
|
600
|
+
displayName: "Skill C",
|
|
601
|
+
description: "Does C",
|
|
602
|
+
}),
|
|
390
603
|
];
|
|
391
|
-
|
|
392
|
-
|
|
604
|
+
mockLoadSkillCatalog = () => initialSkills;
|
|
605
|
+
seedCatalogSkillMemories();
|
|
393
606
|
|
|
394
607
|
const db = getDb();
|
|
395
608
|
const beforeItems = db
|
|
396
609
|
.select()
|
|
397
|
-
.from(
|
|
398
|
-
.where(eq(
|
|
610
|
+
.from(memoryGraphNodes)
|
|
611
|
+
.where(eq(memoryGraphNodes.type, "procedural"))
|
|
399
612
|
.all();
|
|
400
613
|
expect(beforeItems).toHaveLength(3);
|
|
401
|
-
expect(beforeItems.every((i) => i.
|
|
614
|
+
expect(beforeItems.every((i) => i.fidelity === "vivid")).toBe(true);
|
|
402
615
|
|
|
403
616
|
// Now seed with only skill-a — skill-b and skill-c should be pruned
|
|
404
|
-
|
|
405
|
-
|
|
617
|
+
mockLoadSkillCatalog = () => [
|
|
618
|
+
makeSkillSummary({
|
|
619
|
+
id: "skill-a",
|
|
620
|
+
displayName: "Skill A",
|
|
621
|
+
description: "Does A",
|
|
622
|
+
}),
|
|
406
623
|
];
|
|
407
|
-
|
|
624
|
+
seedCatalogSkillMemories();
|
|
408
625
|
|
|
409
626
|
const afterItems = db
|
|
410
627
|
.select()
|
|
411
|
-
.from(
|
|
412
|
-
.where(eq(
|
|
628
|
+
.from(memoryGraphNodes)
|
|
629
|
+
.where(eq(memoryGraphNodes.type, "procedural"))
|
|
413
630
|
.all();
|
|
414
631
|
expect(afterItems).toHaveLength(3); // still 3 rows, but 2 are soft-deleted
|
|
415
632
|
|
|
416
|
-
const active = afterItems.filter((i) => i.
|
|
417
|
-
const deleted = afterItems.filter((i) => i.
|
|
633
|
+
const active = afterItems.filter((i) => i.fidelity === "vivid");
|
|
634
|
+
const deleted = afterItems.filter((i) => i.fidelity === "gone");
|
|
418
635
|
|
|
419
636
|
expect(active).toHaveLength(1);
|
|
420
|
-
expect(active[0].
|
|
637
|
+
expect(active[0].content).toMatch(/^skill:skill-a\n/);
|
|
421
638
|
|
|
422
639
|
expect(deleted).toHaveLength(2);
|
|
423
|
-
const
|
|
424
|
-
expect(
|
|
640
|
+
const deletedPrefixes = deleted.map((i) => i.content.split("\n")[0]).sort();
|
|
641
|
+
expect(deletedPrefixes).toEqual(["skill:skill-b", "skill:skill-c"]);
|
|
425
642
|
});
|
|
426
643
|
|
|
427
|
-
test("handles empty catalog without errors",
|
|
644
|
+
test("handles empty catalog without errors", () => {
|
|
428
645
|
// Pre-populate a skill so we can verify it gets pruned
|
|
429
646
|
upsertSkillCapabilityMemory(
|
|
430
647
|
"existing-skill",
|
|
431
|
-
|
|
648
|
+
fromSkillSummary(makeSkillSummary({ id: "existing-skill" })),
|
|
432
649
|
);
|
|
433
650
|
|
|
434
651
|
const db = getDb();
|
|
435
|
-
const beforeItems = db.select().from(
|
|
652
|
+
const beforeItems = db.select().from(memoryGraphNodes).all();
|
|
436
653
|
expect(beforeItems).toHaveLength(1);
|
|
437
|
-
expect(beforeItems[0].
|
|
654
|
+
expect(beforeItems[0].fidelity).toBe("vivid");
|
|
438
655
|
|
|
439
656
|
// Seed with empty catalog
|
|
440
|
-
|
|
441
|
-
|
|
657
|
+
mockLoadSkillCatalog = () => [];
|
|
658
|
+
seedCatalogSkillMemories();
|
|
442
659
|
|
|
443
660
|
// The existing skill should be pruned (soft-deleted)
|
|
444
|
-
const afterItems = db.select().from(
|
|
661
|
+
const afterItems = db.select().from(memoryGraphNodes).all();
|
|
662
|
+
expect(afterItems).toHaveLength(1);
|
|
663
|
+
expect(afterItems[0].fidelity).toBe("gone");
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
test("does not prune when catalog cache is empty", () => {
|
|
667
|
+
// Pre-populate a skill
|
|
668
|
+
upsertSkillCapabilityMemory(
|
|
669
|
+
"existing-skill",
|
|
670
|
+
fromSkillSummary(makeSkillSummary({ id: "existing-skill" })),
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
const db = getDb();
|
|
674
|
+
const beforeItems = db.select().from(memoryGraphNodes).all();
|
|
675
|
+
expect(beforeItems).toHaveLength(1);
|
|
676
|
+
expect(beforeItems[0].fidelity).toBe("vivid");
|
|
677
|
+
|
|
678
|
+
// Seed with empty catalog AND empty cache — pruning guard should skip
|
|
679
|
+
mockLoadSkillCatalog = () => [];
|
|
680
|
+
mockGetCachedCatalogSync = () => [];
|
|
681
|
+
seedCatalogSkillMemories();
|
|
682
|
+
|
|
683
|
+
// The existing skill should NOT be pruned because the cache is empty
|
|
684
|
+
const afterItems = db.select().from(memoryGraphNodes).all();
|
|
445
685
|
expect(afterItems).toHaveLength(1);
|
|
446
|
-
expect(afterItems[0].
|
|
686
|
+
expect(afterItems[0].fidelity).toBe("vivid");
|
|
447
687
|
});
|
|
448
688
|
|
|
449
|
-
test("does not
|
|
450
|
-
|
|
451
|
-
|
|
689
|
+
test("does not prune non-skill capability memories", () => {
|
|
690
|
+
// Pre-insert a non-skill capability memory directly into the DB
|
|
691
|
+
const db = getDb();
|
|
692
|
+
const now = Date.now();
|
|
693
|
+
db.insert(memoryGraphNodes)
|
|
694
|
+
.values({
|
|
695
|
+
id: "cli-doctor-item",
|
|
696
|
+
type: "procedural",
|
|
697
|
+
content: "cli:doctor\nThe doctor command diagnoses issues.",
|
|
698
|
+
fidelity: "vivid",
|
|
699
|
+
confidence: 1.0,
|
|
700
|
+
significance: 0.7,
|
|
701
|
+
sourceType: "inferred",
|
|
702
|
+
scopeId: "default",
|
|
703
|
+
created: now,
|
|
704
|
+
lastAccessed: now,
|
|
705
|
+
lastConsolidated: now,
|
|
706
|
+
emotionalCharge: '{"valence":0,"intensity":0.1,"decayCurve":"linear","decayRate":0.05,"originalIntensity":0.1}',
|
|
707
|
+
stability: 14,
|
|
708
|
+
reinforcementCount: 0,
|
|
709
|
+
lastReinforced: now,
|
|
710
|
+
sourceConversations: "[]",
|
|
711
|
+
narrativeRole: null,
|
|
712
|
+
partOfStory: null,
|
|
713
|
+
})
|
|
714
|
+
.run();
|
|
715
|
+
|
|
716
|
+
// Seed with empty catalog — skill pruner runs but should skip cli:* items
|
|
717
|
+
mockLoadSkillCatalog = () => [];
|
|
718
|
+
seedCatalogSkillMemories();
|
|
719
|
+
|
|
720
|
+
const item = db
|
|
721
|
+
.select()
|
|
722
|
+
.from(memoryGraphNodes)
|
|
723
|
+
.where(like(memoryGraphNodes.content, "cli:doctor\n%"))
|
|
724
|
+
.get();
|
|
725
|
+
expect(item).toBeDefined();
|
|
726
|
+
expect(item!.fidelity).toBe("vivid");
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
test("does not throw when loadSkillCatalog throws", () => {
|
|
730
|
+
mockLoadSkillCatalog = () => {
|
|
731
|
+
throw new Error("Catalog load failure");
|
|
452
732
|
};
|
|
453
733
|
|
|
454
734
|
// Best-effort: should not propagate the error
|
|
455
|
-
|
|
735
|
+
expect(() => seedCatalogSkillMemories()).not.toThrow();
|
|
456
736
|
});
|
|
457
737
|
|
|
458
|
-
test("skips skills whose feature flag is disabled",
|
|
459
|
-
const skills:
|
|
460
|
-
|
|
738
|
+
test("skips skills whose feature flag is disabled", () => {
|
|
739
|
+
const skills: SkillSummary[] = [
|
|
740
|
+
makeSkillSummary({
|
|
461
741
|
id: "unflagged-skill",
|
|
462
|
-
|
|
742
|
+
displayName: "Unflagged",
|
|
463
743
|
description: "No flag",
|
|
464
744
|
}),
|
|
465
|
-
|
|
745
|
+
makeSkillSummary({
|
|
466
746
|
id: "flagged-skill",
|
|
467
|
-
|
|
747
|
+
displayName: "Flagged",
|
|
468
748
|
description: "Has flag",
|
|
469
|
-
|
|
749
|
+
featureFlag: "my_gated_feature",
|
|
470
750
|
}),
|
|
471
751
|
];
|
|
472
|
-
|
|
752
|
+
mockLoadSkillCatalog = () => skills;
|
|
473
753
|
|
|
474
754
|
// Disable the feature flag for the flagged skill
|
|
475
755
|
mockIsFeatureFlagEnabled = (key: string) => key !== "my_gated_feature";
|
|
476
756
|
|
|
477
|
-
|
|
757
|
+
seedCatalogSkillMemories();
|
|
478
758
|
|
|
479
759
|
const db = getDb();
|
|
480
760
|
const items = db
|
|
481
761
|
.select()
|
|
482
|
-
.from(
|
|
483
|
-
.where(eq(
|
|
762
|
+
.from(memoryGraphNodes)
|
|
763
|
+
.where(eq(memoryGraphNodes.type, "procedural"))
|
|
484
764
|
.all();
|
|
485
765
|
|
|
486
766
|
// Only the unflagged skill should have a capability row
|
|
487
767
|
expect(items).toHaveLength(1);
|
|
488
|
-
expect(items[0].
|
|
489
|
-
expect(items[0].
|
|
768
|
+
expect(items[0].content).toMatch(/^skill:unflagged-skill\n/);
|
|
769
|
+
expect(items[0].fidelity).toBe("vivid");
|
|
490
770
|
});
|
|
491
771
|
|
|
492
|
-
test("prunes pre-existing capability for a skill whose flag becomes disabled",
|
|
772
|
+
test("prunes pre-existing capability for a skill whose flag becomes disabled", () => {
|
|
493
773
|
// First seed with both skills, all flags enabled
|
|
494
|
-
const skills:
|
|
495
|
-
|
|
774
|
+
const skills: SkillSummary[] = [
|
|
775
|
+
makeSkillSummary({
|
|
496
776
|
id: "unflagged-skill",
|
|
497
|
-
|
|
777
|
+
displayName: "Unflagged",
|
|
498
778
|
description: "No flag",
|
|
499
779
|
}),
|
|
500
|
-
|
|
780
|
+
makeSkillSummary({
|
|
501
781
|
id: "flagged-skill",
|
|
502
|
-
|
|
782
|
+
displayName: "Flagged",
|
|
503
783
|
description: "Has flag",
|
|
504
|
-
|
|
784
|
+
featureFlag: "my_gated_feature",
|
|
505
785
|
}),
|
|
506
786
|
];
|
|
507
|
-
|
|
787
|
+
mockLoadSkillCatalog = () => skills;
|
|
508
788
|
mockIsFeatureFlagEnabled = () => true;
|
|
509
|
-
|
|
789
|
+
seedCatalogSkillMemories();
|
|
510
790
|
|
|
511
791
|
const db = getDb();
|
|
512
792
|
const beforeItems = db
|
|
513
793
|
.select()
|
|
514
|
-
.from(
|
|
515
|
-
.where(eq(
|
|
794
|
+
.from(memoryGraphNodes)
|
|
795
|
+
.where(eq(memoryGraphNodes.type, "procedural"))
|
|
516
796
|
.all();
|
|
517
797
|
expect(beforeItems).toHaveLength(2);
|
|
518
|
-
expect(beforeItems.every((i) => i.
|
|
798
|
+
expect(beforeItems.every((i) => i.fidelity === "vivid")).toBe(true);
|
|
519
799
|
|
|
520
800
|
// Now disable the flag — the flagged skill should be pruned
|
|
521
801
|
mockIsFeatureFlagEnabled = (key: string) => key !== "my_gated_feature";
|
|
522
|
-
|
|
802
|
+
seedCatalogSkillMemories();
|
|
523
803
|
|
|
524
804
|
const afterItems = db
|
|
525
805
|
.select()
|
|
526
|
-
.from(
|
|
527
|
-
.where(eq(
|
|
806
|
+
.from(memoryGraphNodes)
|
|
807
|
+
.where(eq(memoryGraphNodes.type, "procedural"))
|
|
528
808
|
.all();
|
|
529
809
|
expect(afterItems).toHaveLength(2); // still 2 rows, but one soft-deleted
|
|
530
810
|
|
|
531
|
-
const active = afterItems.filter((i) => i.
|
|
532
|
-
const deleted = afterItems.filter((i) => i.
|
|
811
|
+
const active = afterItems.filter((i) => i.fidelity === "vivid");
|
|
812
|
+
const deleted = afterItems.filter((i) => i.fidelity === "gone");
|
|
533
813
|
|
|
534
814
|
expect(active).toHaveLength(1);
|
|
535
|
-
expect(active[0].
|
|
815
|
+
expect(active[0].content).toMatch(/^skill:unflagged-skill\n/);
|
|
536
816
|
|
|
537
817
|
expect(deleted).toHaveLength(1);
|
|
538
|
-
expect(deleted[0].
|
|
818
|
+
expect(deleted[0].content).toMatch(/^skill:flagged-skill\n/);
|
|
539
819
|
});
|
|
540
820
|
|
|
541
|
-
test("does not throw on DB error during pruning",
|
|
542
|
-
|
|
543
|
-
|
|
821
|
+
test("does not throw on DB error during pruning", () => {
|
|
822
|
+
mockLoadSkillCatalog = () => [
|
|
823
|
+
makeSkillSummary({
|
|
824
|
+
id: "skill-a",
|
|
825
|
+
displayName: "Skill A",
|
|
826
|
+
description: "Does A",
|
|
827
|
+
}),
|
|
544
828
|
];
|
|
545
829
|
|
|
546
|
-
// Drop
|
|
830
|
+
// Drop memory_graph_nodes to force a DB error during the prune phase
|
|
547
831
|
resetDb();
|
|
548
832
|
const db = getDb();
|
|
549
|
-
db.run("DROP TABLE IF EXISTS
|
|
833
|
+
db.run("DROP TABLE IF EXISTS memory_graph_nodes");
|
|
550
834
|
|
|
551
|
-
|
|
835
|
+
expect(() => seedCatalogSkillMemories()).not.toThrow();
|
|
552
836
|
|
|
553
837
|
// Restore DB state for subsequent tests (see upsert "does not throw" test
|
|
554
838
|
// for rationale on why we delete the DB file).
|
|
555
839
|
resetDb();
|
|
840
|
+
const dbPath = getDbPath();
|
|
556
841
|
for (const ext of ["", "-wal", "-shm"]) {
|
|
557
|
-
rmSync(
|
|
842
|
+
rmSync(`${dbPath}${ext}`, { force: true });
|
|
558
843
|
}
|
|
559
844
|
initializeDb();
|
|
560
845
|
});
|