openclaw-hybrid-memory 2026.5.310 → 2026.6.10
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/api/plugin-runtime.ts +2 -0
- package/backends/facts-db/contradictions.ts +1 -1
- package/cli/cmd-extract-directives.ts +225 -11
- package/cli/cmd-extract-proposals.ts +5 -6
- package/cli/cmd-extract-reinforcement.ts +71 -0
- package/cli/cmd-feedback.ts +15 -9
- package/cli/commands/manage/register-reflection-pipeline.ts +247 -13
- package/cli/commands/manage/register-storage-maintenance.ts +224 -15
- package/cli/commands/manage/storage-stats-helpers.ts +13 -2
- package/cli/context.ts +9 -19
- package/cli/distill.ts +31 -1
- package/cli/register.ts +28 -38
- package/dist/api/plugin-runtime.js.map +1 -1
- package/dist/backends/agent-health-store.js.map +1 -1
- package/dist/backends/apitap-store.js.map +1 -1
- package/dist/backends/audit-store.js.map +1 -1
- package/dist/backends/base-sqlite-store.js.map +1 -1
- package/dist/backends/cost-tracker.js.map +1 -1
- package/dist/backends/credentials-db.js +2 -3
- package/dist/backends/credentials-db.js.map +1 -1
- package/dist/backends/crystallization-store.js.map +1 -1
- package/dist/backends/edict-store.js.map +1 -1
- package/dist/backends/event-bus.js.map +1 -1
- package/dist/backends/event-log.js.map +1 -1
- package/dist/backends/facts-db/cache-manager.js.map +1 -1
- package/dist/backends/facts-db/clusters.js.map +1 -1
- package/dist/backends/facts-db/contradictions.js +1 -1
- package/dist/backends/facts-db/contradictions.js.map +1 -1
- package/dist/backends/facts-db/crud.js.map +1 -1
- package/dist/backends/facts-db/db-connection.js.map +1 -1
- package/dist/backends/facts-db/entity-autolink.js.map +1 -1
- package/dist/backends/facts-db/entity-layer.js.map +1 -1
- package/dist/backends/facts-db/episodes.js.map +1 -1
- package/dist/backends/facts-db/fact-queries.js.map +1 -1
- package/dist/backends/facts-db/fact-read-queries.js.map +1 -1
- package/dist/backends/facts-db/facts-db-layer1.js.map +1 -1
- package/dist/backends/facts-db/facts-db-layer2.js.map +1 -1
- package/dist/backends/facts-db/facts-db-layer3.js.map +1 -1
- package/dist/backends/facts-db/fts-text.js.map +1 -1
- package/dist/backends/facts-db/generated-skills/policy.js.map +1 -1
- package/dist/backends/facts-db/generated-skills.js.map +1 -1
- package/dist/backends/facts-db/housekeeping.js.map +1 -1
- package/dist/backends/facts-db/links.js.map +1 -1
- package/dist/backends/facts-db/maintenance.js.map +1 -1
- package/dist/backends/facts-db/procedures/crud.js.map +1 -1
- package/dist/backends/facts-db/procedures/internal.js.map +1 -1
- package/dist/backends/facts-db/procedures/promotion.js.map +1 -1
- package/dist/backends/facts-db/procedures/search.js.map +1 -1
- package/dist/backends/facts-db/procedures/stats.js.map +1 -1
- package/dist/backends/facts-db/reinforcement.js.map +1 -1
- package/dist/backends/facts-db/row-mapper.js.map +1 -1
- package/dist/backends/facts-db/scan-cursors.js.map +1 -1
- package/dist/backends/facts-db/schema-bootstrap.js.map +1 -1
- package/dist/backends/facts-db/scope-sql.js.map +1 -1
- package/dist/backends/facts-db/search.js.map +1 -1
- package/dist/backends/facts-db/stats.js.map +1 -1
- package/dist/backends/facts-db/types.js.map +1 -1
- package/dist/backends/facts-db/variants.js.map +1 -1
- package/dist/backends/identity-reflection-store.js.map +1 -1
- package/dist/backends/issue-store.js.map +1 -1
- package/dist/backends/learnings-db.js.map +1 -1
- package/dist/backends/migrations/facts-migrations.js.map +1 -1
- package/dist/backends/migrations/procedures.js.map +1 -1
- package/dist/backends/narratives-db.js.map +1 -1
- package/dist/backends/persona-state-store.js.map +1 -1
- package/dist/backends/proposals-db.js.map +1 -1
- package/dist/backends/scope-filter-sql.js.map +1 -1
- package/dist/backends/sqlite-schema-meta.js.map +1 -1
- package/dist/backends/tool-proposal-store.js.map +1 -1
- package/dist/backends/vector-db/constants.js.map +1 -1
- package/dist/backends/vector-db/path-utils.js.map +1 -1
- package/dist/backends/vector-db/runtime-locks.js.map +1 -1
- package/dist/backends/vector-db/vector-db-class.js.map +1 -1
- package/dist/backends/wal.js.map +1 -1
- package/dist/backends/workflow-store.js.map +1 -1
- package/dist/benchmark/shadow-eval.js.map +1 -1
- package/dist/cli/active-tasks.js.map +1 -1
- package/dist/cli/backup.js.map +1 -1
- package/dist/cli/benchmark.js.map +1 -1
- package/dist/cli/cmd-backfill.js.map +1 -1
- package/dist/cli/cmd-config.js.map +1 -1
- package/dist/cli/cmd-credentials.js.map +1 -1
- package/dist/cli/cmd-demo.js.map +1 -1
- package/dist/cli/cmd-distill.js.map +1 -1
- package/dist/cli/cmd-doctor.js.map +1 -1
- package/dist/cli/cmd-examples.js.map +1 -1
- package/dist/cli/cmd-extract-daily.js.map +1 -1
- package/dist/cli/cmd-extract-directives.js +141 -10
- package/dist/cli/cmd-extract-directives.js.map +1 -1
- package/dist/cli/cmd-extract-procedures.js.map +1 -1
- package/dist/cli/cmd-extract-proposals.js +3 -2
- package/dist/cli/cmd-extract-proposals.js.map +1 -1
- package/dist/cli/cmd-extract-reinforcement.js +39 -0
- package/dist/cli/cmd-extract-reinforcement.js.map +1 -1
- package/dist/cli/cmd-extract-sessions.js.map +1 -1
- package/dist/cli/cmd-feedback.js +9 -4
- package/dist/cli/cmd-feedback.js.map +1 -1
- package/dist/cli/cmd-health.js.map +1 -1
- package/dist/cli/cmd-providers.js.map +1 -1
- package/dist/cli/cmd-selfcorrection.js.map +1 -1
- package/dist/cli/cmd-setup.js.map +1 -1
- package/dist/cli/cmd-status.js.map +1 -1
- package/dist/cli/cmd-store.js.map +1 -1
- package/dist/cli/cmd-user-friendly.js.map +1 -1
- package/dist/cli/cmd-verify.js.map +1 -1
- package/dist/cli/commands/manage/bindings.js.map +1 -1
- package/dist/cli/commands/manage/dream-cycle-followup.js.map +1 -1
- package/dist/cli/commands/manage/maintenance-heartbeat.js.map +1 -1
- package/dist/cli/commands/manage/register-agents-audit-runall.js.map +1 -1
- package/dist/cli/commands/manage/register-analyze-maintenance-logs.js.map +1 -1
- package/dist/cli/commands/manage/register-budget-proposals.js.map +1 -1
- package/dist/cli/commands/manage/register-config-cli.js.map +1 -1
- package/dist/cli/commands/manage/register-corrections-and-pipeline.js.map +1 -1
- package/dist/cli/commands/manage/register-corrections.js.map +1 -1
- package/dist/cli/commands/manage/register-council.js.map +1 -1
- package/dist/cli/commands/manage/register-credentials-scope.js.map +1 -1
- package/dist/cli/commands/manage/register-digest.js.map +1 -1
- package/dist/cli/commands/manage/register-lifecycle.js.map +1 -1
- package/dist/cli/commands/manage/register-procedure-lifecycle.js.map +1 -1
- package/dist/cli/commands/manage/register-reconcile-cron-ledgers.js.map +1 -1
- package/dist/cli/commands/manage/register-reflection-pipeline.js +144 -7
- package/dist/cli/commands/manage/register-reflection-pipeline.js.map +1 -1
- package/dist/cli/commands/manage/register-self-correction-feedback.js.map +1 -1
- package/dist/cli/commands/manage/register-storage-and-stats.js.map +1 -1
- package/dist/cli/commands/manage/register-storage-entities-decay.js.map +1 -1
- package/dist/cli/commands/manage/register-storage-graph-audit.js.map +1 -1
- package/dist/cli/commands/manage/register-storage-maintenance.js +152 -9
- package/dist/cli/commands/manage/register-storage-maintenance.js.map +1 -1
- package/dist/cli/commands/manage/register-validate-cron-exit.js.map +1 -1
- package/dist/cli/commands/manage/storage-stats-helpers.js +10 -3
- package/dist/cli/commands/manage/storage-stats-helpers.js.map +1 -1
- package/dist/cli/commands/register-manage-commands.js.map +1 -1
- package/dist/cli/config-feature-summaries.js.map +1 -1
- package/dist/cli/config-output-sink.js.map +1 -1
- package/dist/cli/distill-session-jsonl.js.map +1 -1
- package/dist/cli/distill.js +10 -1
- package/dist/cli/distill.js.map +1 -1
- package/dist/cli/global-verbose.js.map +1 -1
- package/dist/cli/goals.js.map +1 -1
- package/dist/cli/hybrid-mem-commander-utils.js.map +1 -1
- package/dist/cli/install/config-merge.js.map +1 -1
- package/dist/cli/install/cron-jobs.js.map +1 -1
- package/dist/cli/install/embedding-detect.js.map +1 -1
- package/dist/cli/install/run-install.js.map +1 -1
- package/dist/cli/install/workspace.js.map +1 -1
- package/dist/cli/proposals.js.map +1 -1
- package/dist/cli/register.js.map +1 -1
- package/dist/cli/shared.js.map +1 -1
- package/dist/cli/skills.js.map +1 -1
- package/dist/cli/task-queue-status.js.map +1 -1
- package/dist/cli/verified.js.map +1 -1
- package/dist/cli/verify/fact-count.js.map +1 -1
- package/dist/cli/verify/openclaw-config.js.map +1 -1
- package/dist/cli/verify/plugin-config-credentials.js.map +1 -1
- package/dist/cli/verify/sections/config-cron.js.map +1 -1
- package/dist/cli/verify/sections/embeddings.js.map +1 -1
- package/dist/cli/verify/sections/infrastructure.js.map +1 -1
- package/dist/cli/verify/sections/llm-models.js.map +1 -1
- package/dist/cli/verify/sections/reconcile.js.map +1 -1
- package/dist/cli/verify/verify-run-state.js.map +1 -1
- package/dist/cli/verify-llm-azure-auth.js.map +1 -1
- package/dist/cli/verify.js.map +1 -1
- package/dist/config/hybrid-schema.js.map +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/maintenance-fallback-policy.js.map +1 -1
- package/dist/config/parsers/capture.js.map +1 -1
- package/dist/config/parsers/core.js.map +1 -1
- package/dist/config/parsers/features.js.map +1 -1
- package/dist/config/parsers/index.js.map +1 -1
- package/dist/config/parsers/maintenance.js.map +1 -1
- package/dist/config/parsers/retrieval.js.map +1 -1
- package/dist/config/parsers/sensors.js.map +1 -1
- package/dist/config/skill-sections.js.map +1 -1
- package/dist/config/skill-size-limits.js.map +1 -1
- package/dist/config/types/agents.js.map +1 -1
- package/dist/config/types/bootstrap.js.map +1 -1
- package/dist/config/types/core.js.map +1 -1
- package/dist/config/types/index.js.map +1 -1
- package/dist/config/utils.js.map +1 -1
- package/dist/index-help.js.map +1 -1
- package/dist/index-testing-exports.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lifecycle/hook-resolution-api.js.map +1 -1
- package/dist/lifecycle/hooks.js +0 -1
- package/dist/lifecycle/hooks.js.map +1 -1
- package/dist/lifecycle/resolve-agent-id.js.map +1 -1
- package/dist/lifecycle/session-state.js.map +1 -1
- package/dist/lifecycle/stage-active-task.js.map +1 -1
- package/dist/lifecycle/stage-auth-failure.js.map +1 -1
- package/dist/lifecycle/stage-capture/run-capture.js.map +1 -1
- package/dist/lifecycle/stage-capture.js.map +1 -1
- package/dist/lifecycle/stage-cleanup.js.map +1 -1
- package/dist/lifecycle/stage-credential-hint.js.map +1 -1
- package/dist/lifecycle/stage-frustration.js.map +1 -1
- package/dist/lifecycle/stage-goal-stewardship.js.map +1 -1
- package/dist/lifecycle/stage-goal-subagent.js.map +1 -1
- package/dist/lifecycle/stage-injection.js +1 -1
- package/dist/lifecycle/stage-injection.js.map +1 -1
- package/dist/lifecycle/stage-recall/run-recall.js.map +1 -1
- package/dist/lifecycle/stage-recall.js.map +1 -1
- package/dist/lifecycle/stage-setup.js.map +1 -1
- package/dist/routes/dashboard/collectors.js.map +1 -1
- package/dist/routes/dashboard/html.js.map +1 -1
- package/dist/routes/dashboard/server.js.map +1 -1
- package/dist/routes/dashboard-graph.js.map +1 -1
- package/dist/routes/graphql-resolvers.js.map +1 -1
- package/dist/routes/graphql-server.js.map +1 -1
- package/dist/services/active-task-checkpoint.js.map +1 -1
- package/dist/services/active-task-injection.js.map +1 -1
- package/dist/services/active-task.js.map +1 -1
- package/dist/services/adaptive-catch-up-pacing.js +25 -0
- package/dist/services/adaptive-catch-up-pacing.js.map +1 -0
- package/dist/services/adaptive-maintenance-llm.js.map +1 -1
- package/dist/services/adaptive-model-limits.js.map +1 -1
- package/dist/services/ambient-retrieval.js.map +1 -1
- package/dist/services/apitap-service.js.map +1 -1
- package/dist/services/audit-health-exit-info.js.map +1 -1
- package/dist/services/audit-health-json.js.map +1 -1
- package/dist/services/auth-failure-detect.js.map +1 -1
- package/dist/services/auto-capture.js.map +1 -1
- package/dist/services/auto-classifier.js.map +1 -1
- package/dist/services/auto-skills-audit.js.map +1 -1
- package/dist/services/bootstrap-optional.js.map +1 -1
- package/dist/services/bootstrap-priority.js.map +1 -1
- package/dist/services/bootstrap.js.map +1 -1
- package/dist/services/capture-provenance.js.map +1 -1
- package/dist/services/capture-utils.js.map +1 -1
- package/dist/services/chat.js +22 -3
- package/dist/services/chat.js.map +1 -1
- package/dist/services/classification-scope.js.map +1 -1
- package/dist/services/classification.js.map +1 -1
- package/dist/services/cli-sql-dump.js.map +1 -1
- package/dist/services/consolidation.js.map +1 -1
- package/dist/services/context-audit.js +1 -1
- package/dist/services/context-audit.js.map +1 -1
- package/dist/services/context-budget.js.map +1 -1
- package/dist/services/context-engine.js.map +1 -1
- package/dist/services/contextual-variants.js.map +1 -1
- package/dist/services/continuous-verifier.js.map +1 -1
- package/dist/services/contradiction-adjudicator.js.map +1 -1
- package/dist/services/cost-context.js.map +1 -1
- package/dist/services/cost-feature-labels.js.map +1 -1
- package/dist/services/credential-migration.js.map +1 -1
- package/dist/services/credential-scanner.js.map +1 -1
- package/dist/services/credential-validation.js.map +1 -1
- package/dist/services/cron-exit-validator.js.map +1 -1
- package/dist/services/cron-guard.js.map +1 -1
- package/dist/services/cron-job-bash-harness.js +52 -5
- package/dist/services/cron-job-bash-harness.js.map +1 -1
- package/dist/services/cron-maintenance-reconciler.js +1 -3
- package/dist/services/cron-maintenance-reconciler.js.map +1 -1
- package/dist/services/cross-agent-learning.js.map +1 -1
- package/dist/services/crystallization-proposer.js.map +1 -1
- package/dist/services/dedupe-policy.js.map +1 -1
- package/dist/services/deprecated-cron-commands.js.map +1 -1
- package/dist/services/directive-extract.js.map +1 -1
- package/dist/services/document-chunker.js.map +1 -1
- package/dist/services/document-grader.js.map +1 -1
- package/dist/services/dream-cycle.js.map +1 -1
- package/dist/services/embedding-migration.js.map +1 -1
- package/dist/services/embedding-registry.js.map +1 -1
- package/dist/services/embeddings/chain-provider.js.map +1 -1
- package/dist/services/embeddings/factory.js.map +1 -1
- package/dist/services/embeddings/fallback-provider.js.map +1 -1
- package/dist/services/embeddings/ollama-provider.js.map +1 -1
- package/dist/services/embeddings/onnx-provider.js.map +1 -1
- package/dist/services/embeddings/openai-provider.js.map +1 -1
- package/dist/services/embeddings/shared.js +3 -3
- package/dist/services/embeddings/shared.js.map +1 -1
- package/dist/services/embeddings/types.js.map +1 -1
- package/dist/services/entity-enrichment-adaptive.js +128 -0
- package/dist/services/entity-enrichment-adaptive.js.map +1 -0
- package/dist/services/entity-enrichment-cli.js +389 -42
- package/dist/services/entity-enrichment-cli.js.map +1 -1
- package/dist/services/entity-enrichment.js +31 -5
- package/dist/services/entity-enrichment.js.map +1 -1
- package/dist/services/error-reporter/noisy-errors.js.map +1 -1
- package/dist/services/error-reporter/sanitize.js.map +1 -1
- package/dist/services/error-reporter.js.map +1 -1
- package/dist/services/event-hub-repair.js.map +1 -1
- package/dist/services/export-memory.js.map +1 -1
- package/dist/services/fact-extraction.js.map +1 -1
- package/dist/services/feedback-effectiveness.js.map +1 -1
- package/dist/services/find-duplicates.js.map +1 -1
- package/dist/services/frustration-detector.js.map +1 -1
- package/dist/services/fts-search.js.map +1 -1
- package/dist/services/gap-detector.js.map +1 -1
- package/dist/services/generated-skill-lifecycle.js.map +1 -1
- package/dist/services/generated-skill-validation.js.map +1 -1
- package/dist/services/goal-active-task-mirror.js.map +1 -1
- package/dist/services/goal-circuit-breaker.js.map +1 -1
- package/dist/services/goal-health.js.map +1 -1
- package/dist/services/goal-registry.js.map +1 -1
- package/dist/services/goal-stewardship-heartbeat.js.map +1 -1
- package/dist/services/goal-stewardship-llm-triage.js.map +1 -1
- package/dist/services/goal-stewardship-verify-cron.js.map +1 -1
- package/dist/services/goal-stewardship.js.map +1 -1
- package/dist/services/goal-subagent.js.map +1 -1
- package/dist/services/graph-retrieval.js.map +1 -1
- package/dist/services/humanizer-score.js.map +1 -1
- package/dist/services/hybrid-mem-cron-default-job-steps.js.map +1 -1
- package/dist/services/hyde-helper.js.map +1 -1
- package/dist/services/identity-reflection.js.map +1 -1
- package/dist/services/implicit-feedback-extract.js.map +1 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/ingest-utils.js.map +1 -1
- package/dist/services/intent-template.js.map +1 -1
- package/dist/services/json-array-parser.js.map +1 -1
- package/dist/services/knowledge-gaps.js.map +1 -1
- package/dist/services/language-keywords-build.js.map +1 -1
- package/dist/services/lifecycle/github-adapter.js.map +1 -1
- package/dist/services/llm-rate-limit-headers.js +1 -2
- package/dist/services/llm-rate-limit-headers.js.map +1 -1
- package/dist/services/maintenance-auto-fix.js.map +1 -1
- package/dist/services/maintenance-log-analyzer.js +7 -1
- package/dist/services/maintenance-log-analyzer.js.map +1 -1
- package/dist/services/maintenance-timestamp.js.map +1 -1
- package/dist/services/memory-diagnostics.js.map +1 -1
- package/dist/services/memory-index.js.map +1 -1
- package/dist/services/merge-results.js.map +1 -1
- package/dist/services/model-capabilities.js.map +1 -1
- package/dist/services/model-pricing.js.map +1 -1
- package/dist/services/narrative-recall.js.map +1 -1
- package/dist/services/openclaw-session-artifact.js.map +1 -1
- package/dist/services/passive-observer.js.map +1 -1
- package/dist/services/pattern-detector-hash.js.map +1 -1
- package/dist/services/pattern-detector.js.map +1 -1
- package/dist/services/pending-autopilot/foundation.js.map +1 -1
- package/dist/services/pending-autopilot/redaction.js.map +1 -1
- package/dist/services/pending-autopilot/store.js.map +1 -1
- package/dist/services/pending-autopilot/types.js.map +1 -1
- package/dist/services/pending-digest-autopilot-cron.js.map +1 -1
- package/dist/services/pending-digest-autopilot.js.map +1 -1
- package/dist/services/pending-review-digest.js.map +1 -1
- package/dist/services/persona-proposal-triage.js.map +1 -1
- package/dist/services/persona-state-promotion.js.map +1 -1
- package/dist/services/post-compaction-recall.js.map +1 -1
- package/dist/services/pre-consolidation-flush.js.map +1 -1
- package/dist/services/pre-finalization-guard.js.map +1 -1
- package/dist/services/procedure-cluster.js.map +1 -1
- package/dist/services/procedure-extractor.js.map +1 -1
- package/dist/services/procedure-promotion/duplicate-skill-cache.js.map +1 -1
- package/dist/services/procedure-promotion-policy.js.map +1 -1
- package/dist/services/procedure-selection-metrics.js.map +1 -1
- package/dist/services/procedure-skill-eval.js.map +1 -1
- package/dist/services/procedure-skill-generator.js.map +1 -1
- package/dist/services/procedure-skill-recipe.js.map +1 -1
- package/dist/services/procedure-skill-shrink.js.map +1 -1
- package/dist/services/procedure-skill-workflow.js.map +1 -1
- package/dist/services/provenance.js.map +1 -1
- package/dist/services/public-export-bundle.js.map +1 -1
- package/dist/services/python-bridge.js.map +1 -1
- package/dist/services/query-expander.js.map +1 -1
- package/dist/services/query-validator.js.map +1 -1
- package/dist/services/recall-pipeline.js.map +1 -1
- package/dist/services/recall-timing.js.map +1 -1
- package/dist/services/recent-http-attempts.js.map +1 -1
- package/dist/services/reflection/shared.js.map +1 -1
- package/dist/services/reflection.js.map +1 -1
- package/dist/services/reinforcement-extract.js.map +1 -1
- package/dist/services/reranker.js.map +1 -1
- package/dist/services/responses-adapter.js.map +1 -1
- package/dist/services/retrieval-aliases.js.map +1 -1
- package/dist/services/retrieval-mode-policy.js.map +1 -1
- package/dist/services/retrieval-orchestrator/packing.js.map +1 -1
- package/dist/services/retrieval-orchestrator.d.ts +2 -3
- package/dist/services/retrieval-orchestrator.js.map +1 -1
- package/dist/services/rrf-fusion.js.map +1 -1
- package/dist/services/self-correction-extract.js.map +1 -1
- package/dist/services/session-observability.js.map +1 -1
- package/dist/services/session-pre-filter.js.map +1 -1
- package/dist/services/shortest-path.js.map +1 -1
- package/dist/services/skill-allowed-tools.js.map +1 -1
- package/dist/services/skill-creator-validator.js.map +1 -1
- package/dist/services/skill-crystallizer-helpers.js.map +1 -1
- package/dist/services/skill-crystallizer.js.map +1 -1
- package/dist/services/skill-description-builder.js.map +1 -1
- package/dist/services/skill-eval-synthesizer.js.map +1 -1
- package/dist/services/skill-examples-builder.js.map +1 -1
- package/dist/services/skill-frontmatter.js.map +1 -1
- package/dist/services/skill-name-validator.js.map +1 -1
- package/dist/services/skill-prompt-injection.js.map +1 -1
- package/dist/services/skill-reference-sidecar.js.map +1 -1
- package/dist/services/skill-script-bundler.js.map +1 -1
- package/dist/services/skill-validator.js.map +1 -1
- package/dist/services/startup-memory-attribution.js.map +1 -1
- package/dist/services/task-hygiene.js.map +1 -1
- package/dist/services/task-ledger/canonical.js.map +1 -1
- package/dist/services/task-ledger-facts.js.map +1 -1
- package/dist/services/task-queue-leases.js.map +1 -1
- package/dist/services/task-queue-watchdog.js.map +1 -1
- package/dist/services/tool-effectiveness.js.map +1 -1
- package/dist/services/tool-proposer.js.map +1 -1
- package/dist/services/tools-md-section.js.map +1 -1
- package/dist/services/topic-clusters.js.map +1 -1
- package/dist/services/trajectory-tracker.js.map +1 -1
- package/dist/services/vector-backend-observability.js.map +1 -1
- package/dist/services/vector-lifecycle-audit.js.map +1 -1
- package/dist/services/vector-maintenance.js.map +1 -1
- package/dist/services/vector-search.js.map +1 -1
- package/dist/services/verification-store.js.map +1 -1
- package/dist/services/verified-fact-triage.js.map +1 -1
- package/dist/services/wal-helpers.js.map +1 -1
- package/dist/services/workflow-tracker.js.map +1 -1
- package/dist/setup/bootstrap-databases.js.map +1 -1
- package/dist/setup/cli-context/cli-services.js.map +1 -1
- package/dist/setup/cli-context/help-text.js.map +1 -1
- package/dist/setup/cli-context/metadata.js.map +1 -1
- package/dist/setup/cli-context/register-cli-with-help.js.map +1 -1
- package/dist/setup/cli-context/register-full.js.map +1 -1
- package/dist/setup/cli-context/register-help.js.map +1 -1
- package/dist/setup/cost-instrumentation.js.map +1 -1
- package/dist/setup/hybrid-memory-generation-state.js.map +1 -1
- package/dist/setup/hybrid-memory-reload-coordinator.js +13 -13
- package/dist/setup/hybrid-memory-reload-coordinator.js.map +1 -1
- package/dist/setup/plugin-service.js.map +1 -1
- package/dist/setup/provider-router.js.map +1 -1
- package/dist/setup/register-context-engine.js.map +1 -1
- package/dist/setup/register-hooks.js.map +1 -1
- package/dist/setup/register-plugin.js +25 -21
- package/dist/setup/register-plugin.js.map +1 -1
- package/dist/setup/register-tools.js.map +1 -1
- package/dist/setup/reregister-policy.js +2 -2
- package/dist/setup/reregister-policy.js.map +1 -1
- package/dist/setup/tool-installers.js.map +1 -1
- package/dist/setup/workspace-bootstrap.js.map +1 -1
- package/dist/src/worker/narratives.js.map +1 -1
- package/dist/tools/apitap-tools.js.map +1 -1
- package/dist/tools/credential-tools.js.map +1 -1
- package/dist/tools/crystallization-tools.js.map +1 -1
- package/dist/tools/dashboard-routes.js.map +1 -1
- package/dist/tools/document-tools.js.map +1 -1
- package/dist/tools/goal-tools.js.map +1 -1
- package/dist/tools/graph-tools.js.map +1 -1
- package/dist/tools/issue-tools.js.map +1 -1
- package/dist/tools/memory/build-runtime.js.map +1 -1
- package/dist/tools/memory/helpers.js.map +1 -1
- package/dist/tools/memory/register-checkpoint-tools.js.map +1 -1
- package/dist/tools/memory/register-directory-tools.js.map +1 -1
- package/dist/tools/memory/register-edict-tools.js.map +1 -1
- package/dist/tools/memory/register-episode-tools.js.map +1 -1
- package/dist/tools/memory/register-recall-tools.js.map +1 -1
- package/dist/tools/memory/register-store-tools.js.map +1 -1
- package/dist/tools/memory-tools.js.map +1 -1
- package/dist/tools/persona-tools.js.map +1 -1
- package/dist/tools/provenance-tools.js.map +1 -1
- package/dist/tools/public-api-routes.js.map +1 -1
- package/dist/tools/safe-register-http-route.js.map +1 -1
- package/dist/tools/self-extension-tools.js.map +1 -1
- package/dist/tools/task-hygiene-tools.js.map +1 -1
- package/dist/tools/utility-tools.js.map +1 -1
- package/dist/tools/verification-tools.js.map +1 -1
- package/dist/tools/workflow-tools.js.map +1 -1
- package/dist/types/issue-types.js.map +1 -1
- package/dist/types/learnings-types.js.map +1 -1
- package/dist/types/memory.js.map +1 -1
- package/dist/utils/apim-gateway-fetch.js.map +1 -1
- package/dist/utils/atomic-write.js.map +1 -1
- package/dist/utils/auth-failover.js.map +1 -1
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/compaction-model-watchdog.js.map +1 -1
- package/dist/utils/consolidation-controls.js.map +1 -1
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/date-detector.js.map +1 -1
- package/dist/utils/dates.js.map +1 -1
- package/dist/utils/decay.js.map +1 -1
- package/dist/utils/duration.js.map +1 -1
- package/dist/utils/embed-call.js.map +1 -1
- package/dist/utils/entity-lookup-resolve.js.map +1 -1
- package/dist/utils/entity-mention-quality.js.map +1 -1
- package/dist/utils/entity-stopwords.js.map +1 -1
- package/dist/utils/env-manager.js.map +1 -1
- package/dist/utils/error-tracking.js.map +1 -1
- package/dist/utils/event-loop-yield.js.map +1 -1
- package/dist/utils/extract-last-user-message.js.map +1 -1
- package/dist/utils/extraction-from-template.js.map +1 -1
- package/dist/utils/fact-embeddings.js.map +1 -1
- package/dist/utils/file-snapshot.js.map +1 -1
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/fs.js.map +1 -1
- package/dist/utils/gh-repo-arg.js.map +1 -1
- package/dist/utils/hybrid-mem-json-cli.js.map +1 -1
- package/dist/utils/language-keywords.js.map +1 -1
- package/dist/utils/lifecycle-generation.js.map +1 -1
- package/dist/utils/llm-json-array.js.map +1 -1
- package/dist/utils/llm-selection.js.map +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/model-provider-family.js.map +1 -1
- package/dist/utils/model-tier.js.map +1 -1
- package/dist/utils/openclaw-agent-defaults.js.map +1 -1
- package/dist/utils/path.js.map +1 -1
- package/dist/utils/plugin-root.js.map +1 -1
- package/dist/utils/plugin-update-check.js.map +1 -1
- package/dist/utils/procedure-risk.js.map +1 -1
- package/dist/utils/progress-indicators.js.map +1 -1
- package/dist/utils/prompt-loader.js.map +1 -1
- package/dist/utils/provenance.js.map +1 -1
- package/dist/utils/provider-detection.js.map +1 -1
- package/dist/utils/registration-superseded.js.map +1 -1
- package/dist/utils/salience.js.map +1 -1
- package/dist/utils/sanitize-messages.js.map +1 -1
- package/dist/utils/scope-filter.js.map +1 -1
- package/dist/utils/skill-discovery.js.map +1 -1
- package/dist/utils/sqlite-file-perms.js.map +1 -1
- package/dist/utils/sqlite-outcome-compat.js.map +1 -1
- package/dist/utils/sqlite-transaction.js.map +1 -1
- package/dist/utils/stable-stringify.js.map +1 -1
- package/dist/utils/subagent-ended-utils.js.map +1 -1
- package/dist/utils/tags.js.map +1 -1
- package/dist/utils/text.js.map +1 -1
- package/dist/utils/timeout.js.map +1 -1
- package/dist/utils/typebox.js.map +1 -1
- package/dist/utils/version-check.js.map +1 -1
- package/dist/utils/wal-replay.js.map +1 -1
- package/dist/versionInfo.js.map +1 -1
- package/index.ts +2 -2
- package/lifecycle/hooks.ts +0 -1
- package/npm-shrinkwrap.json +487 -186
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
- package/services/adaptive-catch-up-pacing.ts +28 -0
- package/services/chat.ts +34 -1
- package/services/cron-job-bash-harness.ts +52 -5
- package/services/embeddings/shared.ts +5 -2
- package/services/entity-enrichment-adaptive.ts +245 -0
- package/services/entity-enrichment-cli.ts +553 -47
- package/services/entity-enrichment.ts +43 -2
- package/services/llm-rate-limit-headers.ts +1 -4
- package/services/maintenance-log-analyzer.ts +13 -9
- package/services/reinforcement-extract.ts +19 -0
- package/setup/hybrid-memory-reload-coordinator.ts +26 -0
- package/setup/register-plugin.ts +62 -32
- package/setup/reregister-policy.ts +10 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directive-extract.js","names":[],"sources":["../../services/directive-extract.ts"],"sourcesContent":["/**\n * Directive extraction: scan session JSONL for user messages\n * that contain directive phrases (10 categories: explicit memory requests,\n * future behavior changes, absolute rules, corrections, preferences, warnings,\n * procedural, implicit corrections, emotional emphasis, conditional rules).\n * Uses multi-language directive signals from .language-keywords.json.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { getDirectiveCategoryRegexes } from \"../utils/language-keywords.js\";\nimport { extractMessageText, timestampFromFilename, truncate } from \"../utils/text.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\n\n/** 10 directive categories (can overlap — a message may have multiple types). */\nexport const DIRECTIVE_CATEGORIES = [\n \"explicit_memory\",\n \"future_behavior\",\n \"absolute_rule\",\n \"correction\",\n \"preference\",\n \"warning\",\n \"procedural\",\n \"implicit_correction\",\n \"emotional_emphasis\",\n \"conditional_rule\",\n] as const;\n\nexport type DirectiveCategory = (typeof DIRECTIVE_CATEGORIES)[number];\n\nexport type DirectiveIncident = {\n userMessage: string;\n /** Which directive categories were detected (can be multiple). */\n categories: DirectiveCategory[];\n /** Extracted rule/instruction (what the agent should remember). */\n extractedRule: string;\n /** Preceding agent message (what triggered the directive). */\n precedingAssistant: string;\n /** Confidence score 0-1 (how certain this is a real directive vs conversational noise). */\n confidence: number;\n timestamp?: string;\n sessionFile: string;\n};\n\nexport type DirectiveExtractResult = {\n incidents: DirectiveIncident[];\n sessionsScanned: number;\n rejected?: number;\n};\n\nexport const DIRECTIVE_EXTRACTION_METHOD = \"directive-extract:regex-heuristic-v2\";\n\nconst MAX_USER_MSG = 800;\nconst MAX_ASSISTANT_MSG = 500;\n\n/**\n * Patterns that indicate a user message should be skipped (heartbeat, cron, system, etc.).\n *\n * NOTE (Issue #282 — heartbeat quiet mode): The plugin skips CAPTURE from heartbeat\n * messages here, but does NOT generate heartbeat output itself. The \"HEARTBEAT_OK\"\n * response and any health-report text are produced entirely by OpenClaw core (the agent\n * session runner), which is outside this plugin's scope. Verbosity-aware suppression of\n * \"all clear\" heartbeat output must therefore be implemented in OpenClaw core.\n *\n * What this plugin's `verbosity` config DOES control:\n * - Tool response text (memory_recall, memory_store, etc.)\n * - CLI command output (hybrid-mem status, backup, etc.)\n * - Pre-compaction flush log messages\n */\nconst SKIP_PATTERNS = [\n /heartbeat/i,\n /cron\\s+job|cronjob|schedule.*run|run\\s+the\\s+nightly|run\\s+the\\s+weekly/i,\n /compact|pre-compaction|compaction\\s+flush/i,\n /sub-?agent|subagent\\s+announce/i,\n /NO_REPLY|no\\s+reply\\s+needed/i,\n /^\\s*\\{.*\"schedule\"/m, // JSON cron definition\n];\n\nfunction shouldSkipUserMessage(text: string): boolean {\n if (!text || text.length < 25) return true;\n const t = text.trim();\n if (t.length < 25) return true;\n for (const re of SKIP_PATTERNS) {\n if (re.test(t)) return true;\n }\n return false;\n}\n\ntype DirectiveRejectionReason =\n | \"untrusted_metadata\"\n | \"json_envelope\"\n | \"chat_fragment\"\n | \"one_off_command\"\n | \"missing_durable_signal\";\n\nconst UNTRUSTED_METADATA_MARKERS = [/conversation info\\s*\\(untrusted metadata\\)/i, /sender\\s*\\(untrusted metadata\\)/i];\nconst UNTRUSTED_METADATA_KEYS_RE = /\\b(chat_id|message_id|sender_id|inbound_event_kind|conversation_id)\\b/i;\nconst UNTRUSTED_METADATA_ENVELOPE_BLOCK_RE =\n /(?:Conversation info|Sender)\\s*\\(untrusted metadata\\)\\s*:\\s*```(?:json)?[\\s\\S]*?```/gi;\nconst CODE_FENCE_RE = /```[\\s\\S]*?```/g;\nconst GITHUB_LINK_RE = /https?:\\/\\/github\\.com\\/[^\\s)]+/gi;\nconst GITHUB_ISSUE_PR_RE = /(?:^|\\s)#\\d+(?:\\s|$)|\\b(?:issue|issues|pr|pull request)\\b/i;\nconst SELECTED_CONTEXT_RE = /(?:^|\\s)#\\d+\\s+(?:mon|tue|wed|thu|fri|sat|sun)\\b/i;\nconst QUESTION_MARK_RE = /^\\s*\\?|[?]{2,}/;\nconst ONE_OFF_COMMAND_RE = /\\b(file|open|create|submit)\\s+(?:a\\s+)?(?:detailed\\s+)?(?:issue|pr|pull request)\\b/i;\nconst RULE_LIKE_CATEGORIES = new Set<DirectiveCategory>([\n \"explicit_memory\",\n \"future_behavior\",\n \"absolute_rule\",\n \"correction\",\n \"preference\",\n \"warning\",\n \"procedural\",\n \"implicit_correction\",\n \"conditional_rule\",\n]);\n\nfunction hasRuleLikeCategory(categories: readonly DirectiveCategory[]): boolean {\n return categories.some((category) => RULE_LIKE_CATEGORIES.has(category));\n}\n\nfunction hasLocalizedDurableSignal(categories: readonly DirectiveCategory[], text: string): boolean {\n if (hasRuleLikeCategory(categories)) return true;\n const detected = detectDirectiveCategories(text);\n return hasRuleLikeCategory(detected.categories);\n}\n\nfunction sanitizeDirectiveCandidate(rawRule: string): string {\n return rawRule\n .replace(UNTRUSTED_METADATA_ENVELOPE_BLOCK_RE, \" \")\n .replace(CODE_FENCE_RE, \" \")\n .replace(/(?:Conversation info|Sender)\\s*\\(untrusted metadata\\)\\s*:/gi, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nfunction classifyDirectiveCandidate(\n rawRule: string,\n userText: string,\n categories: readonly DirectiveCategory[],\n): { accepted: true; sanitizedRule: string } | { accepted: false; reason: DirectiveRejectionReason } {\n if (!rawRule.trim()) return { accepted: false, reason: \"missing_durable_signal\" };\n if (\n UNTRUSTED_METADATA_MARKERS.some((re) => re.test(rawRule) || re.test(userText)) ||\n UNTRUSTED_METADATA_KEYS_RE.test(rawRule) ||\n UNTRUSTED_METADATA_KEYS_RE.test(userText)\n ) {\n return { accepted: false, reason: \"untrusted_metadata\" };\n }\n\n const sanitizedRule = sanitizeDirectiveCandidate(rawRule);\n if (!sanitizedRule) return { accepted: false, reason: \"missing_durable_signal\" };\n if (UNTRUSTED_METADATA_KEYS_RE.test(sanitizedRule)) return { accepted: false, reason: \"untrusted_metadata\" };\n const looksLikeRawJsonEnvelope = /^\\s*```(?:json)?/i.test(rawRule);\n const looksLikeJsonDocument = /^\\s*[{[][\\s\\S]*[}\\]]\\s*$/m.test(sanitizedRule);\n if (looksLikeRawJsonEnvelope || looksLikeJsonDocument) {\n return { accepted: false, reason: \"json_envelope\" };\n }\n\n const githubLinks = sanitizedRule.match(GITHUB_LINK_RE) ?? [];\n const looksLikeUrlList = githubLinks.length >= 2;\n if (looksLikeUrlList || SELECTED_CONTEXT_RE.test(sanitizedRule) || QUESTION_MARK_RE.test(sanitizedRule)) {\n return { accepted: false, reason: \"chat_fragment\" };\n }\n const hasDurableSignal =\n hasLocalizedDurableSignal(categories, sanitizedRule) ||\n hasRuleLikeCategory(detectDirectiveCategories(userText).categories);\n if (ONE_OFF_COMMAND_RE.test(sanitizedRule) && GITHUB_ISSUE_PR_RE.test(sanitizedRule) && !hasDurableSignal) {\n return { accepted: false, reason: \"one_off_command\" };\n }\n if (!hasDurableSignal) {\n return { accepted: false, reason: \"missing_durable_signal\" };\n }\n\n return { accepted: true, sanitizedRule };\n}\n\nlet categoryRegexCache: Map<DirectiveCategory, RegExp> | null = null;\n\n/**\n * Clear the category regex cache (e.g., after keyword rebuild).\n */\nexport function clearDirectiveCategoryCache(): void {\n categoryRegexCache = null;\n}\n\n/**\n * Detect directive categories in a user message.\n * Returns array of categories (can be empty if no clear directive).\n * Confidence is based on signal strength and context.\n * Now uses multilingual keywords from language-keywords.ts.\n */\nfunction detectDirectiveCategories(text: string): { categories: DirectiveCategory[]; confidence: number } {\n if (!categoryRegexCache) {\n const regexes = getDirectiveCategoryRegexes();\n categoryRegexCache = new Map();\n categoryRegexCache.set(\"explicit_memory\", regexes.explicit_memory);\n categoryRegexCache.set(\"future_behavior\", regexes.future_behavior);\n categoryRegexCache.set(\"absolute_rule\", regexes.absolute_rule);\n categoryRegexCache.set(\"preference\", regexes.preference);\n categoryRegexCache.set(\"warning\", regexes.warning);\n categoryRegexCache.set(\"procedural\", regexes.procedural);\n categoryRegexCache.set(\"implicit_correction\", regexes.implicit_correction);\n categoryRegexCache.set(\"conditional_rule\", regexes.conditional_rule);\n categoryRegexCache.set(\"correction\", regexes.correction);\n }\n\n const categories: DirectiveCategory[] = [];\n\n // Test against each category regex\n for (const [category, regex] of categoryRegexCache) {\n if (regex.test(text)) {\n categories.push(category);\n }\n }\n\n // Emotional emphasis (ALL CAPS words, multiple !!!, frustrated emoji)\n const hasAllCaps = /\\b[A-Z]{4,}\\b/.test(text);\n const hasMultipleExclamation = /!{2,}/.test(text);\n const hasFrustratedEmoji = /[🤬😤😡]/u.test(text);\n if (hasAllCaps || hasMultipleExclamation || hasFrustratedEmoji) {\n categories.push(\"emotional_emphasis\");\n }\n\n // Confidence heuristic:\n // - 1+ explicit category: 0.7+\n // - 2+ categories: 0.8+\n // - Emotional emphasis boosts confidence\n // - Very short message (< 40 chars) reduces confidence\n let confidence = 0.5;\n if (categories.length >= 1) confidence = 0.7;\n if (categories.length >= 2) confidence = 0.8;\n if (categories.includes(\"emotional_emphasis\")) confidence = Math.min(1.0, confidence + 0.1);\n if (text.length < 25) confidence *= 0.8; // Reduced threshold from 40 to 25\n\n return { categories, confidence };\n}\n\n/**\n * Common URL/URI scheme names (IANA and common usage). Colons after these words\n * must not be treated as directive separators (e.g. \"Remember: ...\").\n */\nconst URI_SCHEMES = new Set([\n \"http\",\n \"https\",\n \"ftp\",\n \"file\",\n \"mailto\",\n \"tel\",\n \"ssh\",\n \"data\",\n \"ws\",\n \"wss\",\n \"irc\",\n \"imap\",\n \"nntp\",\n \"ldap\",\n \"sftp\",\n \"git\",\n \"svn\",\n \"jdbc\",\n \"redis\",\n \"mongodb\",\n]);\n\n/**\n * Extract a concise rule/instruction from the user message.\n * This is a simple heuristic; LLM-based extraction would be more accurate.\n * Improved: if colon exists (\"Remember: ...\"), take text after it.\n * Skips colons that are part of URL schemes, time formats, numbered lists, etc.\n */\nfunction extractRule(text: string): string {\n const trimmed = text.trim().replace(/\\s+/g, \" \");\n\n // Heuristic: If a colon exists after a word boundary (directive pattern), extract text after it.\n // This regex matches colons that follow a word, but excludes:\n // - URL/URI schemes (http:, https:, ftp:, mailto:, file:, ssh:, data:, etc.) via URI_SCHEMES set\n // - Time formats (14:30) - digit before colon (not matched by \\b[a-zA-Z]+)\n // - Numbered lists (Step 1:) - digit before colon\n // - Port numbers - negative lookahead (?!\\d) and (?!\\/\\/)\n const colonRegex = /\\b([a-zA-Z]+)\\s*:\\s*(?!\\/\\/)(?!\\d)(.+)/g;\n let colonMatch;\n while ((colonMatch = colonRegex.exec(trimmed)) !== null) {\n const wordBeforeColon = colonMatch[1].toLowerCase();\n if (URI_SCHEMES.has(wordBeforeColon)) {\n // Skip past the URI by finding the next whitespace after the colon\n // This prevents the greedy .+ from consuming directive colons that come after URIs\n const uriStart = colonMatch.index;\n const afterScheme = trimmed.substring(uriStart + colonMatch[1].length + 1);\n const nextSpace = afterScheme.search(/\\s/);\n if (nextSpace !== -1) {\n colonRegex.lastIndex = uriStart + colonMatch[1].length + 1 + nextSpace + 1;\n } else {\n colonRegex.lastIndex = trimmed.length;\n }\n continue;\n }\n const afterColon = colonMatch[2].trim();\n if (afterColon.length >= 10 && !afterColon.startsWith(\"//\")) {\n return afterColon.slice(0, 200);\n }\n }\n return fallbackExtract(trimmed);\n}\n\nfunction fallbackExtract(trimmed: string): string {\n if (trimmed.length <= 200) return trimmed;\n\n // Try to find a sentence with directive keywords\n const sentences = trimmed\n .split(/[.!?]+/)\n .map((s) => s.trim())\n .filter((s) => s.length > 10);\n for (const s of sentences) {\n if (/\\b(always|never|from now on|make sure|remember|prefer|avoid|when|if)\\b/i.test(s)) {\n return s.slice(0, 200);\n }\n }\n return trimmed.slice(0, 200);\n}\n\ntype RunDirectiveExtractOpts = {\n filePaths: string[];\n directiveRegex: RegExp;\n};\n\n/**\n * Scan session JSONL files for user messages matching directive signals.\n * Uses the provided regex (from getDirectiveSignalRegex() after setKeywordsPath)\n * so that all languages from .language-keywords.json are included.\n */\nexport function runDirectiveExtract(opts: RunDirectiveExtractOpts): DirectiveExtractResult {\n const { filePaths, directiveRegex } = opts;\n const incidents: DirectiveIncident[] = [];\n let rejected = 0;\n\n for (const filePath of filePaths) {\n let lines: string[];\n try {\n lines = readFileSync(filePath, \"utf-8\").split(\"\\n\");\n } catch (err) {\n capturePluginError(err as Error, {\n operation: \"read-session-file\",\n severity: \"info\",\n subsystem: \"directive-extract\",\n });\n continue;\n }\n\n const messages: Array<{ role: string; text: string }> = [];\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n const obj = JSON.parse(trimmed) as { type?: string; message?: { role?: string; content?: unknown } };\n if (obj.type !== \"message\" || !obj.message) continue;\n const msg = obj.message;\n const role = msg.role === \"user\" || msg.role === \"assistant\" ? msg.role : null;\n if (!role) continue;\n const text = extractMessageText(msg.content);\n messages.push({ role, text });\n } catch (err) {\n capturePluginError(err as Error, {\n operation: \"parse-session-line\",\n severity: \"info\",\n subsystem: \"directive-extract\",\n });\n // skip malformed lines\n }\n }\n\n const sessionName = basename(filePath);\n const ts = timestampFromFilename(sessionName);\n\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role !== \"user\") continue;\n const userText = messages[i].text;\n // Reset lastIndex to avoid statefulness with global/sticky regexes\n directiveRegex.lastIndex = 0;\n if (!directiveRegex.test(userText)) continue;\n if (shouldSkipUserMessage(userText)) continue;\n\n const { categories, confidence } = detectDirectiveCategories(userText);\n if (categories.length === 0) continue; // No clear directive\n\n const precedingAssistant = i > 0 && messages[i - 1].role === \"assistant\" ? messages[i - 1].text : \"\";\n const extractedRule = extractRule(userText);\n const candidate = classifyDirectiveCandidate(extractedRule, userText, categories);\n if (!candidate.accepted) {\n rejected++;\n continue;\n }\n\n incidents.push({\n userMessage: truncate(userText, MAX_USER_MSG),\n categories,\n extractedRule: candidate.sanitizedRule,\n precedingAssistant: truncate(precedingAssistant, MAX_ASSISTANT_MSG),\n confidence,\n timestamp: ts,\n sessionFile: sessionName,\n });\n }\n }\n\n return { incidents, sessionsScanned: filePaths.length, rejected };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"directive-extract.js","names":[],"sources":["../../services/directive-extract.ts"],"sourcesContent":["/**\n * Directive extraction: scan session JSONL for user messages\n * that contain directive phrases (10 categories: explicit memory requests,\n * future behavior changes, absolute rules, corrections, preferences, warnings,\n * procedural, implicit corrections, emotional emphasis, conditional rules).\n * Uses multi-language directive signals from .language-keywords.json.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { getDirectiveCategoryRegexes } from \"../utils/language-keywords.js\";\nimport { extractMessageText, timestampFromFilename, truncate } from \"../utils/text.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\n\n/** 10 directive categories (can overlap — a message may have multiple types). */\nexport const DIRECTIVE_CATEGORIES = [\n \"explicit_memory\",\n \"future_behavior\",\n \"absolute_rule\",\n \"correction\",\n \"preference\",\n \"warning\",\n \"procedural\",\n \"implicit_correction\",\n \"emotional_emphasis\",\n \"conditional_rule\",\n] as const;\n\nexport type DirectiveCategory = (typeof DIRECTIVE_CATEGORIES)[number];\n\nexport type DirectiveIncident = {\n userMessage: string;\n /** Which directive categories were detected (can be multiple). */\n categories: DirectiveCategory[];\n /** Extracted rule/instruction (what the agent should remember). */\n extractedRule: string;\n /** Preceding agent message (what triggered the directive). */\n precedingAssistant: string;\n /** Confidence score 0-1 (how certain this is a real directive vs conversational noise). */\n confidence: number;\n timestamp?: string;\n sessionFile: string;\n};\n\nexport type DirectiveExtractResult = {\n incidents: DirectiveIncident[];\n sessionsScanned: number;\n rejected?: number;\n};\n\nexport const DIRECTIVE_EXTRACTION_METHOD = \"directive-extract:regex-heuristic-v2\";\n\nconst MAX_USER_MSG = 800;\nconst MAX_ASSISTANT_MSG = 500;\n\n/**\n * Patterns that indicate a user message should be skipped (heartbeat, cron, system, etc.).\n *\n * NOTE (Issue #282 — heartbeat quiet mode): The plugin skips CAPTURE from heartbeat\n * messages here, but does NOT generate heartbeat output itself. The \"HEARTBEAT_OK\"\n * response and any health-report text are produced entirely by OpenClaw core (the agent\n * session runner), which is outside this plugin's scope. Verbosity-aware suppression of\n * \"all clear\" heartbeat output must therefore be implemented in OpenClaw core.\n *\n * What this plugin's `verbosity` config DOES control:\n * - Tool response text (memory_recall, memory_store, etc.)\n * - CLI command output (hybrid-mem status, backup, etc.)\n * - Pre-compaction flush log messages\n */\nconst SKIP_PATTERNS = [\n /heartbeat/i,\n /cron\\s+job|cronjob|schedule.*run|run\\s+the\\s+nightly|run\\s+the\\s+weekly/i,\n /compact|pre-compaction|compaction\\s+flush/i,\n /sub-?agent|subagent\\s+announce/i,\n /NO_REPLY|no\\s+reply\\s+needed/i,\n /^\\s*\\{.*\"schedule\"/m, // JSON cron definition\n];\n\nfunction shouldSkipUserMessage(text: string): boolean {\n if (!text || text.length < 25) return true;\n const t = text.trim();\n if (t.length < 25) return true;\n for (const re of SKIP_PATTERNS) {\n if (re.test(t)) return true;\n }\n return false;\n}\n\ntype DirectiveRejectionReason =\n | \"untrusted_metadata\"\n | \"json_envelope\"\n | \"chat_fragment\"\n | \"one_off_command\"\n | \"missing_durable_signal\";\n\nconst UNTRUSTED_METADATA_MARKERS = [/conversation info\\s*\\(untrusted metadata\\)/i, /sender\\s*\\(untrusted metadata\\)/i];\nconst UNTRUSTED_METADATA_KEYS_RE = /\\b(chat_id|message_id|sender_id|inbound_event_kind|conversation_id)\\b/i;\nconst UNTRUSTED_METADATA_ENVELOPE_BLOCK_RE =\n /(?:Conversation info|Sender)\\s*\\(untrusted metadata\\)\\s*:\\s*```(?:json)?[\\s\\S]*?```/gi;\nconst CODE_FENCE_RE = /```[\\s\\S]*?```/g;\nconst GITHUB_LINK_RE = /https?:\\/\\/github\\.com\\/[^\\s)]+/gi;\nconst GITHUB_ISSUE_PR_RE = /(?:^|\\s)#\\d+(?:\\s|$)|\\b(?:issue|issues|pr|pull request)\\b/i;\nconst SELECTED_CONTEXT_RE = /(?:^|\\s)#\\d+\\s+(?:mon|tue|wed|thu|fri|sat|sun)\\b/i;\nconst QUESTION_MARK_RE = /^\\s*\\?|[?]{2,}/;\nconst ONE_OFF_COMMAND_RE = /\\b(file|open|create|submit)\\s+(?:a\\s+)?(?:detailed\\s+)?(?:issue|pr|pull request)\\b/i;\nconst RULE_LIKE_CATEGORIES = new Set<DirectiveCategory>([\n \"explicit_memory\",\n \"future_behavior\",\n \"absolute_rule\",\n \"correction\",\n \"preference\",\n \"warning\",\n \"procedural\",\n \"implicit_correction\",\n \"conditional_rule\",\n]);\n\nfunction hasRuleLikeCategory(categories: readonly DirectiveCategory[]): boolean {\n return categories.some((category) => RULE_LIKE_CATEGORIES.has(category));\n}\n\nfunction hasLocalizedDurableSignal(categories: readonly DirectiveCategory[], text: string): boolean {\n if (hasRuleLikeCategory(categories)) return true;\n const detected = detectDirectiveCategories(text);\n return hasRuleLikeCategory(detected.categories);\n}\n\nfunction sanitizeDirectiveCandidate(rawRule: string): string {\n return rawRule\n .replace(UNTRUSTED_METADATA_ENVELOPE_BLOCK_RE, \" \")\n .replace(CODE_FENCE_RE, \" \")\n .replace(/(?:Conversation info|Sender)\\s*\\(untrusted metadata\\)\\s*:/gi, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nfunction classifyDirectiveCandidate(\n rawRule: string,\n userText: string,\n categories: readonly DirectiveCategory[],\n): { accepted: true; sanitizedRule: string } | { accepted: false; reason: DirectiveRejectionReason } {\n if (!rawRule.trim()) return { accepted: false, reason: \"missing_durable_signal\" };\n if (\n UNTRUSTED_METADATA_MARKERS.some((re) => re.test(rawRule) || re.test(userText)) ||\n UNTRUSTED_METADATA_KEYS_RE.test(rawRule) ||\n UNTRUSTED_METADATA_KEYS_RE.test(userText)\n ) {\n return { accepted: false, reason: \"untrusted_metadata\" };\n }\n\n const sanitizedRule = sanitizeDirectiveCandidate(rawRule);\n if (!sanitizedRule) return { accepted: false, reason: \"missing_durable_signal\" };\n if (UNTRUSTED_METADATA_KEYS_RE.test(sanitizedRule)) return { accepted: false, reason: \"untrusted_metadata\" };\n const looksLikeRawJsonEnvelope = /^\\s*```(?:json)?/i.test(rawRule);\n const looksLikeJsonDocument = /^\\s*[{[][\\s\\S]*[}\\]]\\s*$/m.test(sanitizedRule);\n if (looksLikeRawJsonEnvelope || looksLikeJsonDocument) {\n return { accepted: false, reason: \"json_envelope\" };\n }\n\n const githubLinks = sanitizedRule.match(GITHUB_LINK_RE) ?? [];\n const looksLikeUrlList = githubLinks.length >= 2;\n if (looksLikeUrlList || SELECTED_CONTEXT_RE.test(sanitizedRule) || QUESTION_MARK_RE.test(sanitizedRule)) {\n return { accepted: false, reason: \"chat_fragment\" };\n }\n const hasDurableSignal =\n hasLocalizedDurableSignal(categories, sanitizedRule) ||\n hasRuleLikeCategory(detectDirectiveCategories(userText).categories);\n if (ONE_OFF_COMMAND_RE.test(sanitizedRule) && GITHUB_ISSUE_PR_RE.test(sanitizedRule) && !hasDurableSignal) {\n return { accepted: false, reason: \"one_off_command\" };\n }\n if (!hasDurableSignal) {\n return { accepted: false, reason: \"missing_durable_signal\" };\n }\n\n return { accepted: true, sanitizedRule };\n}\n\nlet categoryRegexCache: Map<DirectiveCategory, RegExp> | null = null;\n\n/**\n * Clear the category regex cache (e.g., after keyword rebuild).\n */\nexport function clearDirectiveCategoryCache(): void {\n categoryRegexCache = null;\n}\n\n/**\n * Detect directive categories in a user message.\n * Returns array of categories (can be empty if no clear directive).\n * Confidence is based on signal strength and context.\n * Now uses multilingual keywords from language-keywords.ts.\n */\nfunction detectDirectiveCategories(text: string): { categories: DirectiveCategory[]; confidence: number } {\n if (!categoryRegexCache) {\n const regexes = getDirectiveCategoryRegexes();\n categoryRegexCache = new Map();\n categoryRegexCache.set(\"explicit_memory\", regexes.explicit_memory);\n categoryRegexCache.set(\"future_behavior\", regexes.future_behavior);\n categoryRegexCache.set(\"absolute_rule\", regexes.absolute_rule);\n categoryRegexCache.set(\"preference\", regexes.preference);\n categoryRegexCache.set(\"warning\", regexes.warning);\n categoryRegexCache.set(\"procedural\", regexes.procedural);\n categoryRegexCache.set(\"implicit_correction\", regexes.implicit_correction);\n categoryRegexCache.set(\"conditional_rule\", regexes.conditional_rule);\n categoryRegexCache.set(\"correction\", regexes.correction);\n }\n\n const categories: DirectiveCategory[] = [];\n\n // Test against each category regex\n for (const [category, regex] of categoryRegexCache) {\n if (regex.test(text)) {\n categories.push(category);\n }\n }\n\n // Emotional emphasis (ALL CAPS words, multiple !!!, frustrated emoji)\n const hasAllCaps = /\\b[A-Z]{4,}\\b/.test(text);\n const hasMultipleExclamation = /!{2,}/.test(text);\n const hasFrustratedEmoji = /[🤬😤😡]/u.test(text);\n if (hasAllCaps || hasMultipleExclamation || hasFrustratedEmoji) {\n categories.push(\"emotional_emphasis\");\n }\n\n // Confidence heuristic:\n // - 1+ explicit category: 0.7+\n // - 2+ categories: 0.8+\n // - Emotional emphasis boosts confidence\n // - Very short message (< 40 chars) reduces confidence\n let confidence = 0.5;\n if (categories.length >= 1) confidence = 0.7;\n if (categories.length >= 2) confidence = 0.8;\n if (categories.includes(\"emotional_emphasis\")) confidence = Math.min(1.0, confidence + 0.1);\n if (text.length < 25) confidence *= 0.8; // Reduced threshold from 40 to 25\n\n return { categories, confidence };\n}\n\n/**\n * Common URL/URI scheme names (IANA and common usage). Colons after these words\n * must not be treated as directive separators (e.g. \"Remember: ...\").\n */\nconst URI_SCHEMES = new Set([\n \"http\",\n \"https\",\n \"ftp\",\n \"file\",\n \"mailto\",\n \"tel\",\n \"ssh\",\n \"data\",\n \"ws\",\n \"wss\",\n \"irc\",\n \"imap\",\n \"nntp\",\n \"ldap\",\n \"sftp\",\n \"git\",\n \"svn\",\n \"jdbc\",\n \"redis\",\n \"mongodb\",\n]);\n\n/**\n * Extract a concise rule/instruction from the user message.\n * This is a simple heuristic; LLM-based extraction would be more accurate.\n * Improved: if colon exists (\"Remember: ...\"), take text after it.\n * Skips colons that are part of URL schemes, time formats, numbered lists, etc.\n */\nfunction extractRule(text: string): string {\n const trimmed = text.trim().replace(/\\s+/g, \" \");\n\n // Heuristic: If a colon exists after a word boundary (directive pattern), extract text after it.\n // This regex matches colons that follow a word, but excludes:\n // - URL/URI schemes (http:, https:, ftp:, mailto:, file:, ssh:, data:, etc.) via URI_SCHEMES set\n // - Time formats (14:30) - digit before colon (not matched by \\b[a-zA-Z]+)\n // - Numbered lists (Step 1:) - digit before colon\n // - Port numbers - negative lookahead (?!\\d) and (?!\\/\\/)\n const colonRegex = /\\b([a-zA-Z]+)\\s*:\\s*(?!\\/\\/)(?!\\d)(.+)/g;\n let colonMatch;\n while ((colonMatch = colonRegex.exec(trimmed)) !== null) {\n const wordBeforeColon = colonMatch[1].toLowerCase();\n if (URI_SCHEMES.has(wordBeforeColon)) {\n // Skip past the URI by finding the next whitespace after the colon\n // This prevents the greedy .+ from consuming directive colons that come after URIs\n const uriStart = colonMatch.index;\n const afterScheme = trimmed.substring(uriStart + colonMatch[1].length + 1);\n const nextSpace = afterScheme.search(/\\s/);\n if (nextSpace !== -1) {\n colonRegex.lastIndex = uriStart + colonMatch[1].length + 1 + nextSpace + 1;\n } else {\n colonRegex.lastIndex = trimmed.length;\n }\n continue;\n }\n const afterColon = colonMatch[2].trim();\n if (afterColon.length >= 10 && !afterColon.startsWith(\"//\")) {\n return afterColon.slice(0, 200);\n }\n }\n return fallbackExtract(trimmed);\n}\n\nfunction fallbackExtract(trimmed: string): string {\n if (trimmed.length <= 200) return trimmed;\n\n // Try to find a sentence with directive keywords\n const sentences = trimmed\n .split(/[.!?]+/)\n .map((s) => s.trim())\n .filter((s) => s.length > 10);\n for (const s of sentences) {\n if (/\\b(always|never|from now on|make sure|remember|prefer|avoid|when|if)\\b/i.test(s)) {\n return s.slice(0, 200);\n }\n }\n return trimmed.slice(0, 200);\n}\n\ntype RunDirectiveExtractOpts = {\n filePaths: string[];\n directiveRegex: RegExp;\n};\n\n/**\n * Scan session JSONL files for user messages matching directive signals.\n * Uses the provided regex (from getDirectiveSignalRegex() after setKeywordsPath)\n * so that all languages from .language-keywords.json are included.\n */\nexport function runDirectiveExtract(opts: RunDirectiveExtractOpts): DirectiveExtractResult {\n const { filePaths, directiveRegex } = opts;\n const incidents: DirectiveIncident[] = [];\n let rejected = 0;\n\n for (const filePath of filePaths) {\n let lines: string[];\n try {\n lines = readFileSync(filePath, \"utf-8\").split(\"\\n\");\n } catch (err) {\n capturePluginError(err as Error, {\n operation: \"read-session-file\",\n severity: \"info\",\n subsystem: \"directive-extract\",\n });\n continue;\n }\n\n const messages: Array<{ role: string; text: string }> = [];\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n const obj = JSON.parse(trimmed) as { type?: string; message?: { role?: string; content?: unknown } };\n if (obj.type !== \"message\" || !obj.message) continue;\n const msg = obj.message;\n const role = msg.role === \"user\" || msg.role === \"assistant\" ? msg.role : null;\n if (!role) continue;\n const text = extractMessageText(msg.content);\n messages.push({ role, text });\n } catch (err) {\n capturePluginError(err as Error, {\n operation: \"parse-session-line\",\n severity: \"info\",\n subsystem: \"directive-extract\",\n });\n // skip malformed lines\n }\n }\n\n const sessionName = basename(filePath);\n const ts = timestampFromFilename(sessionName);\n\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role !== \"user\") continue;\n const userText = messages[i].text;\n // Reset lastIndex to avoid statefulness with global/sticky regexes\n directiveRegex.lastIndex = 0;\n if (!directiveRegex.test(userText)) continue;\n if (shouldSkipUserMessage(userText)) continue;\n\n const { categories, confidence } = detectDirectiveCategories(userText);\n if (categories.length === 0) continue; // No clear directive\n\n const precedingAssistant = i > 0 && messages[i - 1].role === \"assistant\" ? messages[i - 1].text : \"\";\n const extractedRule = extractRule(userText);\n const candidate = classifyDirectiveCandidate(extractedRule, userText, categories);\n if (!candidate.accepted) {\n rejected++;\n continue;\n }\n\n incidents.push({\n userMessage: truncate(userText, MAX_USER_MSG),\n categories,\n extractedRule: candidate.sanitizedRule,\n precedingAssistant: truncate(precedingAssistant, MAX_ASSISTANT_MSG),\n confidence,\n timestamp: ts,\n sessionFile: sessionName,\n });\n }\n }\n\n return { incidents, sessionsScanned: filePaths.length, rejected };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document-chunker.js","names":[],"sources":["../../services/document-chunker.ts"],"sourcesContent":["/**\n * Document Chunker Service\n *\n * Splits a Markdown document into semantically meaningful chunks suitable\n * for storage as individual memory facts.\n *\n * Algorithm:\n * 1. Split by ## and ### headings (heading-aware)\n * 2. Each chunk carries the section heading as context\n * 3. Very long sections are further split by paragraphs\n * 4. If no headings exist, fall back to paragraph splitting\n */\n\ninterface DocumentChunk {\n text: string;\n sectionHeading: string | null;\n chunkIndex: number;\n totalChunks: number;\n}\n\ninterface ChunkerOptions {\n /** Max characters per chunk (default: 2000) */\n chunkSize?: number;\n /** Characters of heading context overlap (default: 200) */\n chunkOverlap?: number;\n}\n\nconst DEFAULT_CHUNK_SIZE = 2000;\nconst DEFAULT_OVERLAP = 200;\n\n/**\n * Split markdown by top-level section headings (## or ###).\n * Returns pairs of [headingText | null, sectionBody].\n */\nfunction splitByHeadings(markdown: string): Array<{ heading: string | null; body: string }> {\n // Match ## or ### headings (not deeper — # is document title, #### are detail)\n const headingRegex = /^(#{2,3})\\s+(.+)$/m;\n const lines = markdown.split(\"\\n\");\n const sections: Array<{ heading: string | null; body: string }> = [];\n let currentHeading: string | null = null;\n let currentLines: string[] = [];\n\n for (const line of lines) {\n const match = headingRegex.exec(line);\n if (match) {\n // Save previous section\n const body = currentLines.join(\"\\n\").trim();\n if (body.length > 0 || currentHeading !== null) {\n sections.push({ heading: currentHeading, body });\n }\n currentHeading = match[2].trim();\n currentLines = [];\n } else {\n currentLines.push(line);\n }\n }\n\n // Push final section\n const body = currentLines.join(\"\\n\").trim();\n if (body.length > 0 || currentHeading !== null) {\n sections.push({ heading: currentHeading, body });\n }\n\n return sections;\n}\n\n/**\n * Split a text block into chunks by paragraph boundaries, respecting maxSize.\n * Each chunk starts with the optional headingPrefix for context.\n */\nfunction splitByParagraphs(text: string, headingPrefix: string, maxSize: number): string[] {\n const paragraphs = text.split(/\\n{2,}/);\n const chunks: string[] = [];\n let current = headingPrefix;\n\n for (const para of paragraphs) {\n const trimmed = para.trim();\n if (!trimmed) continue;\n\n const candidate = current.length > 0 ? `${current}\\n\\n${trimmed}` : trimmed;\n if (candidate.length <= maxSize) {\n current = candidate;\n } else {\n // Save current chunk and start a new one\n if (current.trim().length > 0) {\n chunks.push(current.trim());\n }\n // If the paragraph alone is longer than maxSize, hard-split by characters\n if (`${headingPrefix}\\n\\n${trimmed}`.length > maxSize) {\n const parts = splitByChars(trimmed, headingPrefix, maxSize);\n // The last part becomes the new \"current\"\n for (let i = 0; i < parts.length - 1; i++) {\n chunks.push(parts[i]);\n }\n current = parts[parts.length - 1] ?? headingPrefix;\n } else {\n current = headingPrefix.length > 0 ? `${headingPrefix}\\n\\n${trimmed}` : trimmed;\n }\n }\n }\n\n if (\n current.trim().length > headingPrefix.trim().length ||\n (current.trim().length > 0 && headingPrefix.trim().length === 0)\n ) {\n chunks.push(current.trim());\n } else if (current.trim().length > 0 && current.trim() !== headingPrefix.trim()) {\n chunks.push(current.trim());\n }\n\n return chunks.filter((c) => c.trim().length > 0);\n}\n\n/** Hard-split text into character-bounded chunks, each prefixed with headingPrefix. */\nfunction splitByChars(text: string, headingPrefix: string, maxSize: number): string[] {\n const overhead = headingPrefix.length > 0 ? headingPrefix.length + 2 : 0;\n const effectiveMax = Math.max(100, maxSize - overhead);\n const chunks: string[] = [];\n let offset = 0;\n while (offset < text.length) {\n const slice = text.slice(offset, offset + effectiveMax);\n const prefix = headingPrefix.length > 0 ? `${headingPrefix}\\n\\n${slice}` : slice;\n chunks.push(prefix);\n offset += effectiveMax;\n }\n return chunks;\n}\n\n/**\n * Chunk a markdown document into DocumentChunk[] records.\n *\n * @param markdown - Full markdown text of the document\n * @param options - chunkSize and chunkOverlap settings\n */\nexport function chunkMarkdown(markdown: string, options: ChunkerOptions = {}): DocumentChunk[] {\n const maxSize = options.chunkSize ?? DEFAULT_CHUNK_SIZE;\n // chunkOverlap controls the heading prefix size included in each chunk\n const overlapSize = options.chunkOverlap ?? DEFAULT_OVERLAP;\n\n const sections = splitByHeadings(markdown);\n\n const rawChunks: Array<{ text: string; heading: string | null }> = [];\n\n if (sections.length === 0) {\n // No content at all\n return [];\n }\n\n // Check if there are any real headings\n const hasHeadings = sections.some((s) => s.heading !== null);\n\n if (!hasHeadings) {\n // Fall back to pure paragraph splitting\n const parts = splitByParagraphs(markdown.trim(), \"\", maxSize);\n for (const part of parts) {\n rawChunks.push({ text: part, heading: null });\n }\n } else {\n for (const section of sections) {\n const headingPrefix = section.heading !== null ? `## ${section.heading}`.slice(0, overlapSize) : \"\";\n\n if (!section.body.trim() && section.heading) {\n // Heading-only section — include just the heading\n rawChunks.push({ text: `## ${section.heading}`, heading: section.heading });\n continue;\n }\n\n // Combine heading + body; check if it fits in one chunk\n const full = headingPrefix.length > 0 ? `${headingPrefix}\\n\\n${section.body}`.trim() : section.body.trim();\n\n if (full.length <= maxSize) {\n rawChunks.push({ text: full, heading: section.heading });\n } else {\n // Split long section by paragraphs\n const parts = splitByParagraphs(section.body, headingPrefix, maxSize);\n for (const part of parts) {\n rawChunks.push({ text: part, heading: section.heading });\n }\n }\n }\n }\n\n const total = rawChunks.length;\n return rawChunks.map((c, i) => ({\n text: c.text,\n sectionHeading: c.heading,\n chunkIndex: i,\n totalChunks: total,\n }));\n}\n"],"mappings":";AA2BA,MAAM,qBAAqB;AAC3B,MAAM,kBAAkB;;;;;AAMxB,SAAS,gBAAgB,UAAmE;CAE1F,MAAM,eAAe;CACrB,MAAM,QAAQ,SAAS,MAAM,
|
|
1
|
+
{"version":3,"file":"document-chunker.js","names":[],"sources":["../../services/document-chunker.ts"],"sourcesContent":["/**\n * Document Chunker Service\n *\n * Splits a Markdown document into semantically meaningful chunks suitable\n * for storage as individual memory facts.\n *\n * Algorithm:\n * 1. Split by ## and ### headings (heading-aware)\n * 2. Each chunk carries the section heading as context\n * 3. Very long sections are further split by paragraphs\n * 4. If no headings exist, fall back to paragraph splitting\n */\n\ninterface DocumentChunk {\n text: string;\n sectionHeading: string | null;\n chunkIndex: number;\n totalChunks: number;\n}\n\ninterface ChunkerOptions {\n /** Max characters per chunk (default: 2000) */\n chunkSize?: number;\n /** Characters of heading context overlap (default: 200) */\n chunkOverlap?: number;\n}\n\nconst DEFAULT_CHUNK_SIZE = 2000;\nconst DEFAULT_OVERLAP = 200;\n\n/**\n * Split markdown by top-level section headings (## or ###).\n * Returns pairs of [headingText | null, sectionBody].\n */\nfunction splitByHeadings(markdown: string): Array<{ heading: string | null; body: string }> {\n // Match ## or ### headings (not deeper — # is document title, #### are detail)\n const headingRegex = /^(#{2,3})\\s+(.+)$/m;\n const lines = markdown.split(\"\\n\");\n const sections: Array<{ heading: string | null; body: string }> = [];\n let currentHeading: string | null = null;\n let currentLines: string[] = [];\n\n for (const line of lines) {\n const match = headingRegex.exec(line);\n if (match) {\n // Save previous section\n const body = currentLines.join(\"\\n\").trim();\n if (body.length > 0 || currentHeading !== null) {\n sections.push({ heading: currentHeading, body });\n }\n currentHeading = match[2].trim();\n currentLines = [];\n } else {\n currentLines.push(line);\n }\n }\n\n // Push final section\n const body = currentLines.join(\"\\n\").trim();\n if (body.length > 0 || currentHeading !== null) {\n sections.push({ heading: currentHeading, body });\n }\n\n return sections;\n}\n\n/**\n * Split a text block into chunks by paragraph boundaries, respecting maxSize.\n * Each chunk starts with the optional headingPrefix for context.\n */\nfunction splitByParagraphs(text: string, headingPrefix: string, maxSize: number): string[] {\n const paragraphs = text.split(/\\n{2,}/);\n const chunks: string[] = [];\n let current = headingPrefix;\n\n for (const para of paragraphs) {\n const trimmed = para.trim();\n if (!trimmed) continue;\n\n const candidate = current.length > 0 ? `${current}\\n\\n${trimmed}` : trimmed;\n if (candidate.length <= maxSize) {\n current = candidate;\n } else {\n // Save current chunk and start a new one\n if (current.trim().length > 0) {\n chunks.push(current.trim());\n }\n // If the paragraph alone is longer than maxSize, hard-split by characters\n if (`${headingPrefix}\\n\\n${trimmed}`.length > maxSize) {\n const parts = splitByChars(trimmed, headingPrefix, maxSize);\n // The last part becomes the new \"current\"\n for (let i = 0; i < parts.length - 1; i++) {\n chunks.push(parts[i]);\n }\n current = parts[parts.length - 1] ?? headingPrefix;\n } else {\n current = headingPrefix.length > 0 ? `${headingPrefix}\\n\\n${trimmed}` : trimmed;\n }\n }\n }\n\n if (\n current.trim().length > headingPrefix.trim().length ||\n (current.trim().length > 0 && headingPrefix.trim().length === 0)\n ) {\n chunks.push(current.trim());\n } else if (current.trim().length > 0 && current.trim() !== headingPrefix.trim()) {\n chunks.push(current.trim());\n }\n\n return chunks.filter((c) => c.trim().length > 0);\n}\n\n/** Hard-split text into character-bounded chunks, each prefixed with headingPrefix. */\nfunction splitByChars(text: string, headingPrefix: string, maxSize: number): string[] {\n const overhead = headingPrefix.length > 0 ? headingPrefix.length + 2 : 0;\n const effectiveMax = Math.max(100, maxSize - overhead);\n const chunks: string[] = [];\n let offset = 0;\n while (offset < text.length) {\n const slice = text.slice(offset, offset + effectiveMax);\n const prefix = headingPrefix.length > 0 ? `${headingPrefix}\\n\\n${slice}` : slice;\n chunks.push(prefix);\n offset += effectiveMax;\n }\n return chunks;\n}\n\n/**\n * Chunk a markdown document into DocumentChunk[] records.\n *\n * @param markdown - Full markdown text of the document\n * @param options - chunkSize and chunkOverlap settings\n */\nexport function chunkMarkdown(markdown: string, options: ChunkerOptions = {}): DocumentChunk[] {\n const maxSize = options.chunkSize ?? DEFAULT_CHUNK_SIZE;\n // chunkOverlap controls the heading prefix size included in each chunk\n const overlapSize = options.chunkOverlap ?? DEFAULT_OVERLAP;\n\n const sections = splitByHeadings(markdown);\n\n const rawChunks: Array<{ text: string; heading: string | null }> = [];\n\n if (sections.length === 0) {\n // No content at all\n return [];\n }\n\n // Check if there are any real headings\n const hasHeadings = sections.some((s) => s.heading !== null);\n\n if (!hasHeadings) {\n // Fall back to pure paragraph splitting\n const parts = splitByParagraphs(markdown.trim(), \"\", maxSize);\n for (const part of parts) {\n rawChunks.push({ text: part, heading: null });\n }\n } else {\n for (const section of sections) {\n const headingPrefix = section.heading !== null ? `## ${section.heading}`.slice(0, overlapSize) : \"\";\n\n if (!section.body.trim() && section.heading) {\n // Heading-only section — include just the heading\n rawChunks.push({ text: `## ${section.heading}`, heading: section.heading });\n continue;\n }\n\n // Combine heading + body; check if it fits in one chunk\n const full = headingPrefix.length > 0 ? `${headingPrefix}\\n\\n${section.body}`.trim() : section.body.trim();\n\n if (full.length <= maxSize) {\n rawChunks.push({ text: full, heading: section.heading });\n } else {\n // Split long section by paragraphs\n const parts = splitByParagraphs(section.body, headingPrefix, maxSize);\n for (const part of parts) {\n rawChunks.push({ text: part, heading: section.heading });\n }\n }\n }\n }\n\n const total = rawChunks.length;\n return rawChunks.map((c, i) => ({\n text: c.text,\n sectionHeading: c.heading,\n chunkIndex: i,\n totalChunks: total,\n }));\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document-grader.js","names":[],"sources":["../../services/document-grader.ts"],"sourcesContent":["import type OpenAI from \"openai\";\nimport { chatComplete } from \"./chat.js\";\nimport { CostFeature } from \"./cost-feature-labels.js\";\nimport { extractJsonArray } from \"./json-array-parser.js\";\n\ninterface GradeableDocument {\n factId: string;\n text: string;\n}\n\ninterface DocumentGrade {\n factId: string;\n answer: \"yes\" | \"no\";\n relevant: boolean;\n}\n\ninterface DocumentGraderConfig {\n model?: string;\n timeoutMs?: number;\n}\n\nconst DEFAULT_MODEL = \"openai/gpt-4.1-nano\";\n\nfunction buildGradePrompt(query: string, docs: GradeableDocument[]): string {\n const renderedDocs = docs\n .map((doc, index) => {\n const snippet = doc.text.length > 220 ? `${doc.text.slice(0, 217)}...` : doc.text;\n return `${index + 1}. ${snippet}`;\n })\n .join(\"\\n\\n\");\n\n return `You are grading retrieval relevance for a memory system. For each document, answer if it helps answer the query. Return ONLY a JSON array with one string per document: \"yes\" or \"no\".\\n\\nQuery: \"${query}\"\\n\\nDocuments:\\n${renderedDocs}`;\n}\n\nfunction parseGrades(response: string, count: number): Array<\"yes\" | \"no\"> {\n const parsed = extractJsonArray(response)\n .map((value) => (typeof value === \"string\" ? value.trim().toLowerCase() : \"\"))\n .filter((value): value is \"yes\" | \"no\" => value === \"yes\" || value === \"no\")\n .slice(0, count);\n\n if (parsed.length !== count) {\n return new Array(count).fill(\"yes\");\n }\n\n return parsed;\n}\n\nfunction buildRewritePrompt(query: string, previousQueries: string[]): string {\n const previous = previousQueries.length > 0 ? previousQueries.map((q) => `- ${q}`).join(\"\\n\") : \"- none\";\n return `Rewrite this memory lookup query so retrieval is more precise and grounded in likely stored facts. Prefer concise concrete nouns, entities, file names, settings, projects, dates, or error terms when helpful. Do not repeat any previous query exactly. Return ONLY the rewritten query text.\\n\\nOriginal query: ${query}\\nPrevious queries:\\n${previous}`;\n}\n\nexport class DocumentGrader {\n constructor(\n private readonly openai: OpenAI,\n private readonly config: DocumentGraderConfig = {},\n ) {}\n\n async gradeDocuments(query: string, docs: GradeableDocument[]): Promise<DocumentGrade[]> {\n if (docs.length === 0) return [];\n\n try {\n const response = await chatComplete({\n model: this.config.model ?? DEFAULT_MODEL,\n content: buildGradePrompt(query, docs),\n temperature: 0,\n maxTokens: 250,\n openai: this.openai,\n timeoutMs: this.config.timeoutMs,\n feature: CostFeature.documentGrader,\n });\n\n const answers = parseGrades(response, docs.length);\n return docs.map((doc, index) => ({\n factId: doc.factId,\n answer: answers[index],\n relevant: answers[index] === \"yes\",\n }));\n } catch {\n return docs.map((doc) => ({ factId: doc.factId, answer: \"yes\", relevant: true }));\n }\n }\n\n async rewriteQuery(query: string, previousQueries: string[]): Promise<string | null> {\n try {\n const response = await chatComplete({\n model: this.config.model ?? DEFAULT_MODEL,\n content: buildRewritePrompt(query, previousQueries),\n temperature: 0.2,\n maxTokens: 80,\n openai: this.openai,\n timeoutMs: this.config.timeoutMs,\n feature: CostFeature.documentGraderQueryRewrite,\n });\n\n const rewritten =\n response\n .replace(/^['\"`\\s]+|['\"`\\s]+$/g, \"\")\n .split(/\\r?\\n/, 1)[0]\n ?.trim() ?? \"\";\n if (!rewritten) return null;\n if (previousQueries.some((previous) => previous.toLowerCase() === rewritten.toLowerCase())) return null;\n return rewritten;\n } catch {\n return null;\n }\n }\n}\n"],"mappings":";;;;AAqBA,MAAM,gBAAgB;AAEtB,SAAS,iBAAiB,OAAe,MAAmC;CAQ1E,OAAO,qMAAqM,MAAM,mBAP7L,KAClB,KAAK,KAAK,UAAU;EACnB,MAAM,UAAU,IAAI,KAAK,SAAS,MAAM,GAAG,IAAI,KAAK,MAAM,GAAG,
|
|
1
|
+
{"version":3,"file":"document-grader.js","names":[],"sources":["../../services/document-grader.ts"],"sourcesContent":["import type OpenAI from \"openai\";\nimport { chatComplete } from \"./chat.js\";\nimport { CostFeature } from \"./cost-feature-labels.js\";\nimport { extractJsonArray } from \"./json-array-parser.js\";\n\ninterface GradeableDocument {\n factId: string;\n text: string;\n}\n\ninterface DocumentGrade {\n factId: string;\n answer: \"yes\" | \"no\";\n relevant: boolean;\n}\n\ninterface DocumentGraderConfig {\n model?: string;\n timeoutMs?: number;\n}\n\nconst DEFAULT_MODEL = \"openai/gpt-4.1-nano\";\n\nfunction buildGradePrompt(query: string, docs: GradeableDocument[]): string {\n const renderedDocs = docs\n .map((doc, index) => {\n const snippet = doc.text.length > 220 ? `${doc.text.slice(0, 217)}...` : doc.text;\n return `${index + 1}. ${snippet}`;\n })\n .join(\"\\n\\n\");\n\n return `You are grading retrieval relevance for a memory system. For each document, answer if it helps answer the query. Return ONLY a JSON array with one string per document: \"yes\" or \"no\".\\n\\nQuery: \"${query}\"\\n\\nDocuments:\\n${renderedDocs}`;\n}\n\nfunction parseGrades(response: string, count: number): Array<\"yes\" | \"no\"> {\n const parsed = extractJsonArray(response)\n .map((value) => (typeof value === \"string\" ? value.trim().toLowerCase() : \"\"))\n .filter((value): value is \"yes\" | \"no\" => value === \"yes\" || value === \"no\")\n .slice(0, count);\n\n if (parsed.length !== count) {\n return new Array(count).fill(\"yes\");\n }\n\n return parsed;\n}\n\nfunction buildRewritePrompt(query: string, previousQueries: string[]): string {\n const previous = previousQueries.length > 0 ? previousQueries.map((q) => `- ${q}`).join(\"\\n\") : \"- none\";\n return `Rewrite this memory lookup query so retrieval is more precise and grounded in likely stored facts. Prefer concise concrete nouns, entities, file names, settings, projects, dates, or error terms when helpful. Do not repeat any previous query exactly. Return ONLY the rewritten query text.\\n\\nOriginal query: ${query}\\nPrevious queries:\\n${previous}`;\n}\n\nexport class DocumentGrader {\n constructor(\n private readonly openai: OpenAI,\n private readonly config: DocumentGraderConfig = {},\n ) {}\n\n async gradeDocuments(query: string, docs: GradeableDocument[]): Promise<DocumentGrade[]> {\n if (docs.length === 0) return [];\n\n try {\n const response = await chatComplete({\n model: this.config.model ?? DEFAULT_MODEL,\n content: buildGradePrompt(query, docs),\n temperature: 0,\n maxTokens: 250,\n openai: this.openai,\n timeoutMs: this.config.timeoutMs,\n feature: CostFeature.documentGrader,\n });\n\n const answers = parseGrades(response, docs.length);\n return docs.map((doc, index) => ({\n factId: doc.factId,\n answer: answers[index],\n relevant: answers[index] === \"yes\",\n }));\n } catch {\n return docs.map((doc) => ({ factId: doc.factId, answer: \"yes\", relevant: true }));\n }\n }\n\n async rewriteQuery(query: string, previousQueries: string[]): Promise<string | null> {\n try {\n const response = await chatComplete({\n model: this.config.model ?? DEFAULT_MODEL,\n content: buildRewritePrompt(query, previousQueries),\n temperature: 0.2,\n maxTokens: 80,\n openai: this.openai,\n timeoutMs: this.config.timeoutMs,\n feature: CostFeature.documentGraderQueryRewrite,\n });\n\n const rewritten =\n response\n .replace(/^['\"`\\s]+|['\"`\\s]+$/g, \"\")\n .split(/\\r?\\n/, 1)[0]\n ?.trim() ?? \"\";\n if (!rewritten) return null;\n if (previousQueries.some((previous) => previous.toLowerCase() === rewritten.toLowerCase())) return null;\n return rewritten;\n } catch {\n return null;\n }\n }\n}\n"],"mappings":";;;;AAqBA,MAAM,gBAAgB;AAEtB,SAAS,iBAAiB,OAAe,MAAmC;CAQ1E,OAAO,qMAAqM,MAAM,mBAP7L,KAClB,KAAK,KAAK,UAAU;EACnB,MAAM,UAAU,IAAI,KAAK,SAAS,MAAM,GAAG,IAAI,KAAK,MAAM,GAAG,GAAG,EAAE,OAAO,IAAI;EAC7E,OAAO,GAAG,QAAQ,EAAE,IAAI;CAC1B,CAAC,EACA,KAAK,MAEwO;AAClP;AAEA,SAAS,YAAY,UAAkB,OAAoC;CACzE,MAAM,SAAS,iBAAiB,QAAQ,EACrC,KAAK,UAAW,OAAO,UAAU,WAAW,MAAM,KAAK,EAAE,YAAY,IAAI,EAAG,EAC5E,QAAQ,UAAiC,UAAU,SAAS,UAAU,IAAI,EAC1E,MAAM,GAAG,KAAK;CAEjB,IAAI,OAAO,WAAW,OACpB,OAAO,IAAI,MAAM,KAAK,EAAE,KAAK,KAAK;CAGpC,OAAO;AACT;AAEA,SAAS,mBAAmB,OAAe,iBAAmC;CAE5E,OAAO,sTAAsT,MAAM,uBADlT,gBAAgB,SAAS,IAAI,gBAAgB,KAAK,MAAM,KAAK,GAAG,EAAE,KAAK,IAAI,IAAI;AAElG;AAEA,IAAa,iBAAb,MAA4B;CAEP;CACA;CAFnB,YACE,QACA,SAAgD,CAAC,GACjD;EAFiB,KAAA,SAAA;EACA,KAAA,SAAA;CAChB;CAEH,MAAM,eAAe,OAAe,MAAqD;EACvF,IAAI,KAAK,WAAW,GAAG,OAAO,CAAC;EAE/B,IAAI;GAWF,MAAM,UAAU,YAAY,MAVL,aAAa;IAClC,OAAO,KAAK,OAAO,SAAS;IAC5B,SAAS,iBAAiB,OAAO,IAAI;IACrC,aAAa;IACb,WAAW;IACX,QAAQ,KAAK;IACb,WAAW,KAAK,OAAO;IACvB,SAAS,YAAY;GACvB,CAAC,GAEqC,KAAK,MAAM;GACjD,OAAO,KAAK,KAAK,KAAK,WAAW;IAC/B,QAAQ,IAAI;IACZ,QAAQ,QAAQ;IAChB,UAAU,QAAQ,WAAW;GAC/B,EAAE;EACJ,QAAQ;GACN,OAAO,KAAK,KAAK,SAAS;IAAE,QAAQ,IAAI;IAAQ,QAAQ;IAAO,UAAU;GAAK,EAAE;EAClF;CACF;CAEA,MAAM,aAAa,OAAe,iBAAmD;EACnF,IAAI;GAWF,MAAM,aACJ,MAXqB,aAAa;IAClC,OAAO,KAAK,OAAO,SAAS;IAC5B,SAAS,mBAAmB,OAAO,eAAe;IAClD,aAAa;IACb,WAAW;IACX,QAAQ,KAAK;IACb,WAAW,KAAK,OAAO;IACvB,SAAS,YAAY;GACvB,CAAC,GAII,QAAQ,wBAAwB,EAAE,EAClC,MAAM,SAAS,CAAC,EAAE,IACjB,KAAK,KAAK;GAChB,IAAI,CAAC,WAAW,OAAO;GACvB,IAAI,gBAAgB,MAAM,aAAa,SAAS,YAAY,MAAM,UAAU,YAAY,CAAC,GAAG,OAAO;GACnG,OAAO;EACT,QAAQ;GACN,OAAO;EACT;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dream-cycle.js","names":[],"sources":["../../services/dream-cycle.ts"],"sourcesContent":["/**\n * Dream Cycle Service — Automated nightly reflection + pruning pipeline (Issue #143).\n *\n * Sequence (verbose `step N:` numbers are assigned in runtime order so skipped optional phases do not leave gaps):\n * prune / decay / orphaned links → episodic consolidation + event log maintenance → reflection (patterns) →\n * optional reflect-rules (if enough patterns) → MEMORY_INDEX refresh → prune log tables → FTS5 optimize →\n * optional VACUUM → digest summary.\n *\n * Designed to be cheap ($0.003/night target) using a Flash-tier model.\n * Self-contained — does not require an active agent session.\n */\n\nimport type OpenAI from \"openai\";\nimport type { EventLog, EventLogEntry, EventType } from \"../backends/event-log.js\";\nimport type { FactsDB } from \"../backends/facts-db.js\";\nimport type { VectorDB } from \"../backends/vector-db.js\";\nimport type { MemoryCategory, MemoryEntry } from \"../types/memory.js\";\nimport { CONSOLIDATED_FACT_DECAY_CLASS } from \"../utils/consolidation-controls.js\";\nimport type { EmbeddingProvider } from \"./embeddings.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\nimport { writeMemoryIndex } from \"./memory-index.js\";\nimport type { ProvenanceService } from \"./provenance.js\";\nimport {\n countActivePatternFactsForMaintenance,\n type ReflectionConfig,\n runReflection,\n runReflectionRules,\n} from \"./reflection.js\";\nimport { cleanupEvictedVector, deleteVectorsForFactIds } from \"./vector-maintenance.js\";\n\n/** Prune modes for the dream cycle. */\nexport type DreamCyclePruneMode = \"expired\" | \"decay\" | \"both\";\n\n/** Configuration for the nightly dream cycle. */\nexport interface DreamCycleConfig {\n enabled: boolean;\n schedule: string;\n reflectWindowDays: number;\n pruneMode: DreamCyclePruneMode;\n model: string;\n /** Fallback models for reflection steps, in preference order. */\n fallbackModels?: string[];\n consolidateAfterDays: number;\n /** Archive consolidated event log entries older than this many days. */\n eventLogArchivalDays: number;\n /** Directory for compressed JSONL archives. */\n eventLogArchivePath: string;\n /** Delete unconsolidated event log entries older than this many days. */\n maxUnconsolidatedAgeDays: number;\n /** Maximum events to merge into one consolidated fact. Default: 200. */\n maxEventsPerConsolidation: number;\n /**\n * Retention window for log tables (recall_log, reinforcement_log, feedback_trajectories).\n * Rows older than this many days are deleted. 0 = disabled. Default: 30.\n */\n logRetentionDays: number;\n /**\n * When true, run wal_checkpoint(TRUNCATE) + VACUUM after the cycle to reclaim freed space.\n * Default: true.\n */\n vacuumOnCycle: boolean;\n /**\n * When true, run the source-/importance-aware decay reclassifier (#1189) as part of the\n * nightly cycle. Defaults to true; operators can opt out by setting it to false.\n */\n reclassifyDecayOnCycle?: boolean;\n /** Inactivity threshold in days for the recall-based demotion path. Default: 90. */\n reclassifyInactiveDays?: number;\n /** Recall count required to promote a fact to a longer-lived class. Default: 3. */\n reclassifyPromoteRecallCount?: number;\n /** When true, log episodic consolidation / reflection / memory-index detail (CLI `--verbose`). */\n verbose?: boolean;\n /**\n * Episodic consolidation filter on `event.eventType` (#1185). Built-in deny list always includes\n * session/heartbeat/transport lifecycle types unless removed via config.\n */\n episodicConsolidationEventTypes?: {\n /** When non-empty, only these event types are eligible (after deny). */\n allow?: string[];\n /** Extra types to treat as noise (merged with built-in deny list). */\n deny?: string[];\n };\n /**\n * When true, enable reflection rules generation during the dream cycle.\n * Cost optimization: Set to false to save 30-40% of dream cycle LLM cost.\n * Default: true (enabled for backward compatibility).\n */\n enableReflectionRules?: boolean;\n}\n\n/** Result returned by a single dream cycle run. */\nexport interface DreamCycleResult {\n /** Facts removed by pruneExpired(). */\n factsPruned: number;\n /** Facts whose confidence was decayed (confidence halved). */\n factsDecayed: number;\n /** Orphaned memory_links rows removed. */\n linksPruned: number;\n /** Episodic event log entries successfully consolidated. */\n eventsConsolidated: number;\n /** New consolidated facts created from episodic events. */\n factsCreated: number;\n /** New patterns stored by runReflection(). */\n patternsFound: number;\n /** New rules stored by runReflectionRules(). */\n rulesGenerated: number;\n /** Reflect-rules diagnostics for zero/partial outcomes. */\n reflectionRulesDiagnostics?: {\n modelResponseChars: number;\n parseSuccess: boolean;\n parsedCandidates: number;\n rejectedDuplicates: number;\n rejectedLowConfidence: number;\n stored: number;\n zeroRulesReason?: string;\n status: \"ok\" | \"partial\" | \"degraded\";\n };\n /** Log table rows deleted by pruneLogTables() (Issue #573). */\n logRowsPruned: number;\n /** True when VACUUM + checkpoint was executed (Issue #573). */\n vacuumRan: boolean;\n /** Number of facts re-tiered by the source-/importance-aware reclassifier (#1189). */\n decayReclassified: number;\n /** Number of orphaned LanceDB vector entries removed during reconciliation. */\n orphanVectorsRemoved: number;\n /** Human-readable summary of the cycle. */\n digestSummary: string;\n /** True when the cycle was skipped because nightlyCycle.enabled = false. */\n skipped: boolean;\n}\n\n// Minimum patterns stored in one cycle before we also run reflect-rules.\nconst MIN_PATTERNS_FOR_RULES = 3;\nexport const DEFAULT_MAX_EVENTS_PER_CONSOLIDATION = 200;\nconst DREAM_STAGE_HEARTBEAT_MS = 60_000;\nconst EPISODIC_PROGRESS_HEARTBEAT_MS = 60_000;\nconst EPISODIC_PROGRESS_GROUP_INTERVAL = 25;\nconst SKIP_CONSOLIDATION_TEXT_PATTERNS = new Set([\n \"heartbeat\",\n \"session_end\",\n \"session_start\",\n \"transport_connect\",\n \"transport_disconnect\",\n]);\n\n/** Default deny list for `event.eventType` during episodic consolidation (#1185). */\nexport const DEFAULT_EPISODIC_CONSOLIDATION_EVENT_TYPE_DENY: ReadonlySet<EventType> = new Set([\n \"session_start\",\n \"session_end\",\n \"heartbeat\",\n \"transport_connect\",\n \"transport_disconnect\",\n]);\n\nexport type EpisodicConsolidationEventTypeFilter = {\n allow?: string[];\n deny?: string[];\n};\n\nfunction isVectorBackendAvailable(vectorDb: Pick<VectorDB, \"isLanceDbAvailable\"> | unknown): boolean {\n const candidate = vectorDb as { isLanceDbAvailable?: () => boolean };\n if (typeof candidate.isLanceDbAvailable !== \"function\") return true;\n return candidate.isLanceDbAvailable();\n}\n\nfunction episodicDenySet(filter?: EpisodicConsolidationEventTypeFilter | null): Set<string> {\n const s = new Set<string>(DEFAULT_EPISODIC_CONSOLIDATION_EVENT_TYPE_DENY);\n for (const x of filter?.deny ?? []) {\n if (typeof x === \"string\" && x.trim().length > 0) s.add(x.trim());\n }\n return s;\n}\n\n/**\n * True when this event should not be merged into consolidated facts based on `eventType`\n * (allow/deny lists). Text-pattern skipping remains in {@link shouldSkipEpisodicConsolidation}.\n *\n * CRITICAL FIX (#5): Allow list now overrides deny list. If an event type is explicitly\n * in the allow list, it is accepted even if it's in the default deny set.\n */\nexport function shouldSkipEpisodicConsolidationByEventType(\n event: EventLogEntry,\n filter?: EpisodicConsolidationEventTypeFilter | null,\n): boolean {\n const allow = filter?.allow?.map((x) => x.trim()).filter((x) => x.length > 0);\n // If allow list is specified and contains this event type, accept it (override deny).\n if (allow && allow.length > 0) {\n if (allow.includes(event.eventType)) return false;\n // If allow list is specified but doesn't contain this event type, reject it.\n return true;\n }\n // No allow list specified, check deny list.\n const deny = episodicDenySet(filter);\n if (deny.has(event.eventType)) return true;\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Episodic consolidation helpers (exported for testing)\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the primary text content from an event log entry.\n * Checks common content field names in priority order.\n */\nexport function extractEventText(event: EventLogEntry): string {\n const c = event.content;\n if (typeof c.text === \"string\" && c.text.trim().length > 0) return c.text.trim();\n if (typeof c.decision === \"string\" && c.decision.trim().length > 0) return c.decision.trim();\n if (typeof c.summary === \"string\" && c.summary.trim().length > 0) return c.summary.trim();\n if (typeof c.action === \"string\" && c.action.trim().length > 0) return c.action.trim();\n if (typeof c.description === \"string\" && c.description.trim().length > 0) return c.description.trim();\n // Fall back to any string value in the content object\n for (const v of Object.values(c)) {\n if (typeof v === \"string\" && v.trim().length > 0) return v.trim();\n }\n return \"\";\n}\n\n/**\n * Group event log entries by their primary entity.\n * Events with no entities are grouped under the \"__default__\" key.\n * Callers should omit lifecycle/noise events (e.g. via `shouldSkipEpisodicConsolidation` and\n * `shouldSkipEpisodicConsolidationByEventType`) before calling.\n */\nexport function groupEventsByEntity(events: EventLogEntry[]): Map<string, EventLogEntry[]> {\n const groups = new Map<string, EventLogEntry[]>();\n for (const event of events) {\n const primaryEntity = event.entities?.[0] ?? \"__default__\";\n if (!groups.has(primaryEntity)) groups.set(primaryEntity, []);\n groups.get(primaryEntity)?.push(event);\n }\n return groups;\n}\n\nexport function shouldSkipEpisodicConsolidation(event: EventLogEntry): boolean {\n const text = extractEventText(event).toLowerCase();\n for (const pattern of SKIP_CONSOLIDATION_TEXT_PATTERNS) {\n if (text.includes(pattern)) return true;\n }\n return false;\n}\n\n/**\n * Build the digest summary string from cycle counts.\n * Exported so it can be tested independently.\n */\nexport function buildDigestSummary(counts: {\n factsPruned: number;\n factsDecayed: number;\n linksPruned?: number;\n eventsConsolidated: number;\n factsCreated: number;\n patternsFound: number;\n rulesGenerated: number;\n logRowsPruned?: number;\n vacuumRan?: boolean;\n decayReclassified?: number;\n orphanVectorsRemoved?: number;\n}): string {\n const parts: string[] = [];\n if (counts.factsPruned > 0) parts.push(`${counts.factsPruned} facts pruned`);\n if (counts.factsDecayed > 0) parts.push(`${counts.factsDecayed} facts decayed`);\n if (counts.decayReclassified && counts.decayReclassified > 0) {\n parts.push(`${counts.decayReclassified} facts re-tiered`);\n }\n if (counts.orphanVectorsRemoved && counts.orphanVectorsRemoved > 0) {\n parts.push(`${counts.orphanVectorsRemoved} orphaned vectors reconciled`);\n }\n if (counts.linksPruned && counts.linksPruned > 0) parts.push(`${counts.linksPruned} orphaned links removed`);\n if (counts.eventsConsolidated > 0) {\n parts.push(`${counts.eventsConsolidated} events consolidated into ${counts.factsCreated} facts`);\n }\n if (counts.patternsFound > 0) parts.push(`${counts.patternsFound} patterns extracted`);\n if (counts.rulesGenerated > 0) parts.push(`${counts.rulesGenerated} rules generated`);\n if (counts.logRowsPruned && counts.logRowsPruned > 0) parts.push(`${counts.logRowsPruned} log rows pruned`);\n if (counts.vacuumRan) parts.push(\"VACUUM ran\");\n if (parts.length === 0) return \"No changes.\";\n return `${parts.join(\", \")}.`;\n}\n\n// ---------------------------------------------------------------------------\n// Episodic consolidation\n// ---------------------------------------------------------------------------\n\n/**\n * Run episodic consolidation:\n * 1. Fetch unconsolidated event log entries older than consolidateAfterDays.\n * 2. Group by primary entity.\n * 3. For each group, create a consolidated fact with structured provenance_json.\n * 4. Mark all events as consolidated in the event log.\n * 5. Optionally embed and store consolidated facts in LanceDB so they are\n * discoverable by semantic search (non-fatal; requires vectorDb + embeddings).\n *\n * Historical versions created one DERIVED_FROM graph edge per source event; new\n * consolidations keep that lineage on the fact row to avoid provenance mega-hubs.\n */\nexport async function runEpisodicConsolidation(\n factsDb: FactsDB,\n eventLog: EventLog,\n consolidateAfterDays: number,\n logger: { info: (msg: string) => void; warn: (msg: string) => void },\n verbose?: boolean,\n maxEventsPerConsolidation = DEFAULT_MAX_EVENTS_PER_CONSOLIDATION,\n eventTypeFilter?: EpisodicConsolidationEventTypeFilter | null,\n vectorDb?: VectorDB | null,\n embeddings?: EmbeddingProvider | null,\n): Promise<{ eventsConsolidated: number; factsCreated: number }> {\n const events = eventLog.getUnconsolidated(consolidateAfterDays);\n if (events.length === 0) {\n return { eventsConsolidated: 0, factsCreated: 0 };\n }\n\n let eventsConsolidated = 0;\n const skippedTextEvents = events.filter(shouldSkipEpisodicConsolidation);\n if (skippedTextEvents.length > 0) {\n try {\n eventLog.markConsolidated(\n skippedTextEvents.map((e) => e.id),\n \"SKIP:lifecycle_event\",\n );\n eventsConsolidated += skippedTextEvents.length;\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — failed to mark lifecycle events as skipped: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-mark-lifecycle-skip\",\n subsystem: \"event-log\",\n });\n }\n }\n\n const afterTextSkip = events.filter((event) => !shouldSkipEpisodicConsolidation(event));\n const skippedTypeEvents = afterTextSkip.filter((e) => shouldSkipEpisodicConsolidationByEventType(e, eventTypeFilter));\n if (skippedTypeEvents.length > 0) {\n try {\n eventLog.markConsolidated(\n skippedTypeEvents.map((e) => e.id),\n \"SKIP:event_type_filter\",\n );\n eventsConsolidated += skippedTypeEvents.length;\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — failed to mark event-type-filtered events as skipped: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-mark-event-type-skip\",\n subsystem: \"event-log\",\n });\n }\n }\n\n const consolidatableEvents = afterTextSkip.filter(\n (event) => !shouldSkipEpisodicConsolidationByEventType(event, eventTypeFilter),\n );\n if (consolidatableEvents.length === 0) {\n return { eventsConsolidated, factsCreated: 0 };\n }\n\n const groups = groupEventsByEntity(consolidatableEvents);\n if (verbose) {\n logger.info(\n `memory-hybrid: dream-cycle — episodic consolidation: ${consolidatableEvents.length} consolidatable event(s) in ${groups.size} entity group(s) (≥${consolidateAfterDays}d); skipped ${skippedTextEvents.length} text-pattern noise + ${skippedTypeEvents.length} eventType-filtered`,\n );\n }\n let factsCreated = 0;\n const totalGroups = groups.size;\n let processedGroups = 0;\n const consolidationStartedAt = Date.now();\n let lastProgressLogAt = consolidationStartedAt;\n const maybeLogConsolidationProgress = (entity: string): void => {\n if (!verbose) return;\n const now = Date.now();\n const shouldLogByCount = processedGroups === 1 || processedGroups % EPISODIC_PROGRESS_GROUP_INTERVAL === 0;\n const shouldLogByTime = now - lastProgressLogAt >= EPISODIC_PROGRESS_HEARTBEAT_MS;\n if (!shouldLogByCount && !shouldLogByTime) return;\n lastProgressLogAt = now;\n const elapsedSec = Math.floor((now - consolidationStartedAt) / 1000);\n const safeEntity = entity === \"__default__\" ? \"unattributed\" : entity;\n logger.info(\n `memory-hybrid: dream-cycle — episodic consolidation progress ${processedGroups}/${totalGroups} group(s); eventsConsolidated=${eventsConsolidated}, factsCreated=${factsCreated}, currentEntity=\"${safeEntity}\"; still running after ${elapsedSec}s`,\n );\n };\n\n for (const [entity, groupEvents] of groups) {\n if (groupEvents.length === 0) continue;\n processedGroups++;\n maybeLogConsolidationProgress(entity);\n\n if (entity === \"__default__\" && groupEvents.length > maxEventsPerConsolidation) {\n try {\n eventLog.markConsolidated(\n groupEvents.map((e) => e.id),\n \"SKIP:default_group_cap\",\n );\n eventsConsolidated += groupEvents.length;\n logger.info(\n `memory-hybrid: dream-cycle — skipped ${groupEvents.length} unattributed event(s): default group exceeds maxEventsPerConsolidation=${maxEventsPerConsolidation}`,\n );\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — failed to mark capped default group as skipped: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-mark-default-cap-skip\",\n subsystem: \"event-log\",\n });\n }\n continue;\n }\n\n const cappedGroupEvents = groupEvents.slice(0, maxEventsPerConsolidation);\n const excessEvents = groupEvents.slice(maxEventsPerConsolidation);\n\n // Collect text from all events in this group\n const eventTexts = cappedGroupEvents.map((e) => extractEventText(e)).filter((t) => t.length >= 3);\n\n if (eventTexts.length === 0) {\n // Mark events as consolidated with a namespaced skip sentinel to prevent re-processing.\n // 'SKIP:no_text' is clearly not a real fact UUID — no UUID-based query will match it.\n try {\n eventLog.markConsolidated(\n cappedGroupEvents.map((e) => e.id),\n \"SKIP:no_text\",\n );\n eventsConsolidated += cappedGroupEvents.length;\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — failed to mark no-text events as consolidated: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-mark-skip\",\n subsystem: \"event-log\",\n });\n }\n continue;\n }\n\n // Build merged text for the consolidated fact\n const entityLabel = entity !== \"__default__\" ? entity : null;\n const mergedText =\n eventTexts.length === 1\n ? eventTexts[0]\n : `[consolidated from ${eventTexts.length} events${entityLabel ? ` about ${entityLabel}` : \"\"}] ${eventTexts.slice(0, 5).join(\"; \")}`;\n\n const consolidatedAt = Math.floor(Date.now() / 1000);\n const sourceEvents = cappedGroupEvents.map((event) => ({\n id: event.id,\n eventType: event.eventType,\n timestamp: event.timestamp,\n sessionId: event.sessionId,\n text: extractEventText(event).slice(0, 300),\n }));\n\n // Create the consolidated fact. Issue #1195: store dream-cycle provenance on\n // the fact row instead of creating one DERIVED_FROM graph edge per event.\n // Historical DERIVED_FROM rows are left untouched by this forward migration;\n // deleting legacy provenance blindly is riskier than stopping new hub growth.\n let consolidatedFact: MemoryEntry | null = null;\n try {\n const storeResult = factsDb.storeWithResult({\n text: mergedText.slice(0, 500),\n category: \"fact\" as MemoryCategory,\n importance: 0.5,\n entity: entityLabel,\n key: \"consolidated\",\n value: null,\n source: \"dream-cycle\",\n decayClass: CONSOLIDATED_FACT_DECAY_CLASS,\n tags: [\"dream-cycle\", \"consolidated\"],\n extractionMethod: \"consolidation\",\n provenanceJson: JSON.stringify({\n method: \"dream-cycle\",\n consolidatedAt,\n sourceEventIds: sourceEvents.map((event) => event.id),\n sourceEvents,\n }),\n });\n if (storeResult.skipped) {\n continue;\n }\n consolidatedFact = storeResult.entry;\n // CRITICAL FIX (#2): Delete vector for evicted fact to prevent orphaned vectors\n if (vectorDb) {\n await cleanupEvictedVector({\n vectorDb,\n evictedFactId: storeResult.evictedFactId,\n logger,\n context: \"dream-cycle\",\n });\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — failed to store consolidated fact for entity \"${entity}\": ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-consolidate\",\n subsystem: \"facts-db\",\n });\n continue;\n }\n\n // Mark all events in the group as consolidated into the new fact\n try {\n eventLog.markConsolidated(\n cappedGroupEvents.map((e) => e.id),\n consolidatedFact.id,\n );\n factsCreated++;\n eventsConsolidated += cappedGroupEvents.length;\n\n if (excessEvents.length > 0) {\n try {\n eventLog.markConsolidated(\n excessEvents.map((e) => e.id),\n \"SKIP:entity_group_cap\",\n );\n eventsConsolidated += excessEvents.length;\n logger.info(\n `memory-hybrid: dream-cycle — skipped ${excessEvents.length} excess event(s) for entity \"${entity}\": exceeds maxEventsPerConsolidation=${maxEventsPerConsolidation}`,\n );\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — failed to mark excess entity group events as skipped: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-mark-entity-cap-skip\",\n subsystem: \"event-log\",\n });\n }\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — failed to mark events as consolidated: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-mark-consolidated\",\n subsystem: \"event-log\",\n });\n try {\n if (consolidatedFact) factsDb.delete(consolidatedFact.id);\n } catch (cleanupErr) {\n logger.warn(\n `memory-hybrid: dream-cycle — failed to delete consolidated fact after mark failure: ${cleanupErr}`,\n );\n }\n continue;\n }\n\n logger.info(\n `memory-hybrid: dream-cycle — consolidated ${cappedGroupEvents.length} events${entityLabel ? ` for entity \"${entityLabel}\"` : \"\"} → fact ${consolidatedFact.id.slice(0, 8)}`,\n );\n\n // Embed and store the consolidated fact in LanceDB so it is discoverable by\n // semantic search. This is non-fatal: the fact is already safely in SQLite.\n // Use the same truncated text for both the embedding call and the store payload\n // so the embedding always matches the text that will be returned by search.\n if (vectorDb && embeddings) {\n const persistedText =\n typeof consolidatedFact.text === \"string\" && consolidatedFact.text.trim().length > 0\n ? consolidatedFact.text.slice(0, 500)\n : mergedText.slice(0, 500);\n try {\n const vector = await embeddings.embed(persistedText);\n await vectorDb.store({\n id: consolidatedFact.id,\n text: persistedText,\n vector,\n importance: consolidatedFact.importance,\n category: consolidatedFact.category,\n });\n } catch (embedErr) {\n logger.warn(\n `memory-hybrid: dream-cycle — failed to embed consolidated fact ${consolidatedFact.id.slice(0, 8)} into LanceDB (non-fatal): ${embedErr}`,\n );\n }\n }\n }\n\n if (verbose) {\n const elapsedSec = Math.floor((Date.now() - consolidationStartedAt) / 1000);\n logger.info(\n `memory-hybrid: dream-cycle — episodic consolidation complete in ${elapsedSec}s; groups=${processedGroups}/${totalGroups}, eventsConsolidated=${eventsConsolidated}, factsCreated=${factsCreated}`,\n );\n }\n\n return { eventsConsolidated, factsCreated };\n}\n\n// ---------------------------------------------------------------------------\n// Main dream cycle orchestration\n// ---------------------------------------------------------------------------\n\n/**\n * Run the full nightly dream cycle:\n * prune → consolidation → reflect → optional reflect-rules → memory index → log prune → FTS5 → optional VACUUM → digest.\n */\nexport async function runDreamCycle(\n factsDb: FactsDB,\n vectorDb: VectorDB,\n embeddings: EmbeddingProvider,\n openai: OpenAI,\n eventLog: EventLog | null,\n config: DreamCycleConfig,\n logger: { info: (msg: string) => void; warn: (msg: string) => void },\n provenanceService?: ProvenanceService | null,\n): Promise<DreamCycleResult> {\n if (!config.enabled) {\n return {\n factsPruned: 0,\n factsDecayed: 0,\n linksPruned: 0,\n eventsConsolidated: 0,\n factsCreated: 0,\n patternsFound: 0,\n rulesGenerated: 0,\n logRowsPruned: 0,\n vacuumRan: false,\n decayReclassified: 0,\n orphanVectorsRemoved: 0,\n digestSummary: \"Dream cycle disabled.\",\n skipped: true,\n };\n }\n\n logger.info(\"memory-hybrid: dream-cycle — starting nightly cycle\");\n const cycleStartedAt = Date.now();\n const v = !!config.verbose;\n let stageCounter = 0;\n const beginStage = (\n label: string,\n opts?: { heartbeat?: boolean; progressSupplier?: () => string | undefined },\n ): {\n complete: (summary?: string) => void;\n fail: (err: unknown) => void;\n } => {\n const stageNumber = ++stageCounter;\n const startedAt = Date.now();\n logger.info(`memory-hybrid: dream-cycle — stage ${stageNumber} start: ${label}`);\n let heartbeat: ReturnType<typeof setInterval> | undefined;\n if (v && opts?.heartbeat === true) {\n heartbeat = setInterval(() => {\n const elapsedSec = Math.floor((Date.now() - startedAt) / 1000);\n const progress = opts?.progressSupplier?.();\n logger.info(\n `memory-hybrid: dream-cycle — stage ${stageNumber} still running after ${elapsedSec}s: ${label}${progress ? `; ${progress}` : \"\"}`,\n );\n }, DREAM_STAGE_HEARTBEAT_MS);\n heartbeat.unref?.();\n }\n const clearHeartbeat = () => {\n if (heartbeat) clearInterval(heartbeat);\n };\n return {\n complete: (summary?: string) => {\n clearHeartbeat();\n const elapsedSec = Math.floor((Date.now() - startedAt) / 1000);\n logger.info(\n `memory-hybrid: dream-cycle — stage ${stageNumber} complete in ${elapsedSec}s: ${label}${summary ? `; ${summary}` : \"\"}`,\n );\n },\n fail: (err: unknown) => {\n clearHeartbeat();\n const elapsedSec = Math.floor((Date.now() - startedAt) / 1000);\n logger.warn(`memory-hybrid: dream-cycle — stage ${stageNumber} failed after ${elapsedSec}s: ${label}: ${err}`);\n },\n };\n };\n const beginSubstep = (label: string): { complete: (summary?: string) => void } => {\n const startedAt = Date.now();\n if (v) logger.info(`memory-hybrid: dream-cycle — substep start: ${label}`);\n return {\n complete: (summary?: string) => {\n if (!v) return;\n const elapsedSec = Math.floor((Date.now() - startedAt) / 1000);\n logger.info(\n `memory-hybrid: dream-cycle — substep complete in ${elapsedSec}s: ${label}${summary ? `; ${summary}` : \"\"}`,\n );\n },\n };\n };\n\n // ── Stage 1: Core pruning / decay / graph-vector maintenance ─────────────\n const stageCore = beginStage(\"core prune/decay/link/vector maintenance\");\n let factsPruned = 0;\n let factsDecayed = 0;\n if (config.pruneMode === \"expired\" || config.pruneMode === \"both\") {\n try {\n const expiredPrune = factsDb.pruneExpiredWithDetails();\n factsPruned = expiredPrune.factsPruned;\n const vectorCleanup = await deleteVectorsForFactIds(vectorDb, expiredPrune.deletedFactIds, {\n operation: \"dream-cycle-prune-expired\",\n logger,\n });\n logger.info(`memory-hybrid: dream-cycle — pruned ${factsPruned} expired facts`);\n if (vectorCleanup.failed > 0) {\n logger.warn(\n `memory-hybrid: dream-cycle — expired vector cleanup partial: deleted ${vectorCleanup.deleted}/${vectorCleanup.attempted}, failed ${vectorCleanup.failed}`,\n );\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — pruneExpired failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-prune-expired\",\n subsystem: \"facts-db\",\n });\n }\n }\n\n // ── Step 1a-pre: Reclassify decay classes BEFORE taking the decay snapshot ────\n // Running reclassification first ensures that listFactIdsToBeDeletedByDecayRun()\n // reflects up-to-date decay_class values. Previously, reclassification ran in\n // Step 1c (after the decay snapshot), which caused newly re-tiered facts to be\n // missed by the per-batch vector cleanup, leaving orphaned LanceDB vectors until\n // the full Step 1e reconciliation caught them on a later cycle.\n let decayReclassified = 0;\n if (config.reclassifyDecayOnCycle !== false) {\n const reclassifyStep = beginSubstep(\"decay reclassify (source/importance/recall)\");\n try {\n const report = factsDb.reclassifyDecayClasses({\n apply: true,\n inactiveDays: config.reclassifyInactiveDays ?? 90,\n promoteRecallCount: config.reclassifyPromoteRecallCount ?? 3,\n });\n decayReclassified = report.changed;\n if (decayReclassified > 0) {\n logger.info(\n `memory-hybrid: dream-cycle — decay reclassify: ${decayReclassified} facts re-tiered (scanned ${report.scanned}, stable+permanent ${(report.stablePermanentRatioBefore * 100).toFixed(1)}% → ${(report.stablePermanentRatioAfter * 100).toFixed(1)}%)`,\n );\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — reclassifyDecayClasses failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-reclassify-decay\",\n subsystem: \"facts-db\",\n });\n }\n reclassifyStep.complete(`changed=${decayReclassified}`);\n }\n\n if (config.pruneMode === \"decay\" || config.pruneMode === \"both\") {\n try {\n const decayNowSec = Math.floor(Date.now() / 1000);\n const decayRun = factsDb.decayConfidenceWithDetails(decayNowSec);\n factsDecayed = decayRun.factsDecayed;\n const vectorCleanup = await deleteVectorsForFactIds(vectorDb, decayRun.deletedFactIds, {\n operation: \"dream-cycle-decay\",\n logger,\n });\n logger.info(`memory-hybrid: dream-cycle — decayed ${factsDecayed} facts`);\n if (vectorCleanup.failed > 0) {\n logger.warn(\n `memory-hybrid: dream-cycle — decay vector cleanup partial: deleted ${vectorCleanup.deleted}/${vectorCleanup.attempted}, failed ${vectorCleanup.failed}`,\n );\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — decayConfidence failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-decay\",\n subsystem: \"facts-db\",\n });\n }\n }\n\n // ── Step 1b: Prune orphaned links ────────────────────────────────────────\n let linksPruned = 0;\n try {\n linksPruned = factsDb.pruneOrphanedLinks();\n if (linksPruned > 0) {\n logger.info(`memory-hybrid: dream-cycle — pruned ${linksPruned} orphaned link(s)`);\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — pruneOrphanedLinks failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-prune-orphaned-links\",\n subsystem: \"facts-db\",\n });\n }\n\n // ── Step 1d: Refresh denormalized graph degrees (#1192) ──────────────────\n // Without this, every BFS hop runs `COUNT(*) FROM memory_links` against the same node, which\n // is O(edges) per call. A single nightly refresh keeps reads cheap while still adapting to\n // graph changes within a day.\n if (typeof (factsDb as { refreshFactDegrees?: () => unknown }).refreshFactDegrees === \"function\") {\n const refreshDegreesStep = beginSubstep(\"refresh fact degrees\");\n try {\n const result = (factsDb as { refreshFactDegrees: () => { updated: number } }).refreshFactDegrees();\n if (v) {\n logger.info(`memory-hybrid: dream-cycle — refreshed fact degrees for ${result.updated} facts`);\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — refreshFactDegrees failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-refresh-degrees\",\n subsystem: \"facts-db\",\n });\n }\n refreshDegreesStep.complete();\n }\n\n // ── Step 1e: Reconcile LanceDB orphaned vectors ──────────────────────────\n // After SQLite pruning (pruneExpired, decayConfidence), any LanceDB vector whose\n // corresponding SQLite fact was hard-deleted is now an orphan. Orphaned vectors\n // pollute semantic search results with stale or deleted facts. We reconcile here\n // by deleting vector IDs that no longer have a corresponding SQLite fact.\n const vectorReconcileStep = beginSubstep(\"reconcile LanceDB orphaned vectors\");\n let orphanVectorsRemoved = 0;\n try {\n const sqliteIds = new Set(factsDb.getAllIds());\n const vectorIds = await vectorDb.getAllIds();\n const orphans = vectorIds.filter((id) => !sqliteIds.has(id));\n if (orphans.length > 0) {\n logger.info(`memory-hybrid: dream-cycle — reconciling ${orphans.length} orphaned vector(s)...`);\n const cleanup = await deleteVectorsForFactIds(vectorDb, orphans, {\n operation: \"dream-cycle-orphan-vector-reconcile\",\n logger,\n });\n orphanVectorsRemoved = cleanup.deleted;\n if (cleanup.deleted > 0) {\n logger.info(`memory-hybrid: dream-cycle — removed ${cleanup.deleted} orphaned vector(s) from LanceDB`);\n }\n if (cleanup.failed > 0) {\n logger.warn(\n `memory-hybrid: dream-cycle — orphan reconciliation partial: deleted ${cleanup.deleted}/${cleanup.attempted}, failed ${cleanup.failed}`,\n );\n }\n }\n if (isVectorBackendAvailable(vectorDb)) {\n const vectorlessAfterReconcile = factsDb.countVectorlessActiveFacts();\n if (vectorlessAfterReconcile > 0) {\n logger.warn(\n `memory-hybrid: dream-cycle — detected ${vectorlessAfterReconcile} active SQLite fact(s) without vectors after reconciliation; run 'hybrid-mem reembed-vectorless --apply' to restore semantic recall coverage`,\n );\n }\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — vector reconciliation failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-vector-reconcile\",\n subsystem: \"vector\",\n });\n }\n vectorReconcileStep.complete(`removed=${orphanVectorsRemoved}`);\n stageCore.complete(\n `factsPruned=${factsPruned}, factsDecayed=${factsDecayed}, linksPruned=${linksPruned}, decayReclassified=${decayReclassified}, orphanVectorsRemoved=${orphanVectorsRemoved}`,\n );\n\n // ── Stage 2: Episodic consolidation + event log maintenance ──────────────\n let eventsConsolidated = 0;\n let factsCreated = 0;\n let eventLogPhase = \"consolidation\";\n const stageEventLog = beginStage(\"episodic consolidation + event log maintenance\", {\n heartbeat: true,\n progressSupplier: () =>\n `phase=${eventLogPhase}; eventsConsolidated=${eventsConsolidated}; factsCreated=${factsCreated}`,\n });\n if (eventLog) {\n try {\n const consolidationResult = await runEpisodicConsolidation(\n factsDb,\n eventLog,\n config.consolidateAfterDays,\n logger,\n v,\n config.maxEventsPerConsolidation,\n config.episodicConsolidationEventTypes ?? null,\n vectorDb,\n embeddings,\n );\n eventsConsolidated = consolidationResult.eventsConsolidated;\n factsCreated = consolidationResult.factsCreated;\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — consolidation step failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-consolidation\",\n subsystem: \"event-log\",\n });\n }\n } else if (v) {\n logger.info(\"memory-hybrid: dream-cycle — no event log: skipping episodic consolidation\");\n }\n\n // ── Step 2b: Archive stale event log entries ─────────────────────────────\n if (eventLog && config.eventLogArchivalDays > 0) {\n eventLogPhase = \"archive-consolidated\";\n try {\n const result = await eventLog.archiveConsolidated(config.eventLogArchivalDays, config.eventLogArchivePath);\n if (result.archived > 0) {\n logger.info(\n `memory-hybrid: dream-cycle — archived ${result.archived} consolidated event log entries older than ${config.eventLogArchivalDays} days`,\n );\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — archiveConsolidated failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-archive\",\n subsystem: \"event-log\",\n });\n }\n }\n\n // ── Step 2c: Clean up old unconsolidated events ──────────────────────────\n if (eventLog && config.maxUnconsolidatedAgeDays > 0) {\n eventLogPhase = \"archive-old\";\n try {\n const deleted = eventLog.archiveOld(config.maxUnconsolidatedAgeDays, true);\n if (deleted > 0) {\n logger.info(\n `memory-hybrid: dream-cycle — deleted ${deleted} old event log entries (including unconsolidated) older than ${config.maxUnconsolidatedAgeDays} days`,\n );\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — archiveOld failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-archive-old\",\n subsystem: \"event-log\",\n });\n }\n }\n stageEventLog.complete(`eventsConsolidated=${eventsConsolidated}, factsCreated=${factsCreated}`);\n\n // ── Stage 3: Reflection (patterns) ────────────────────────────────────────\n let patternsFound = 0;\n const stageReflection = beginStage(\"reflection (patterns)\", {\n heartbeat: true,\n progressSupplier: () => `phase=extract-patterns; patternsStored=${patternsFound}`,\n });\n const reflectionConfig: ReflectionConfig = {\n enabled: true,\n defaultWindow: config.reflectWindowDays,\n minObservations: 2,\n };\n try {\n const reflectionResult = await runReflection(\n factsDb,\n vectorDb,\n embeddings,\n openai,\n reflectionConfig,\n {\n window: config.reflectWindowDays,\n dryRun: false,\n model: config.model,\n fallbackModels: config.fallbackModels ?? [],\n verbose: v,\n },\n logger,\n provenanceService,\n );\n patternsFound = reflectionResult.patternsStored;\n logger.info(`memory-hybrid: dream-cycle — reflection complete: ${patternsFound} patterns stored`);\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — reflection step failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-reflect\",\n subsystem: \"reflection\",\n });\n }\n stageReflection.complete(`patternsStored=${patternsFound}`);\n\n const livePatternCountForRules = countActivePatternFactsForMaintenance(factsDb);\n const patternGateForRules = Math.max(patternsFound, livePatternCountForRules);\n\n // ── Stage 4: Reflect-rules (optional) ─────────────────────────────────────\n let rulesGenerated = 0;\n let reflectionRulesDiagnostics: DreamCycleResult[\"reflectionRulesDiagnostics\"];\n const enableReflectionRules = config.enableReflectionRules !== false; // default: true\n if (enableReflectionRules && patternGateForRules >= MIN_PATTERNS_FOR_RULES) {\n const stageReflectRules = beginStage(\"reflect-rules\", {\n heartbeat: true,\n progressSupplier: () => `phase=extract-rules; rulesStored=${rulesGenerated}`,\n });\n try {\n const rulesResult = await runReflectionRules(\n factsDb,\n vectorDb,\n embeddings,\n openai,\n { dryRun: false, model: config.model, fallbackModels: config.fallbackModels ?? [], verbose: v },\n logger,\n provenanceService,\n );\n rulesGenerated = rulesResult.rulesStored;\n reflectionRulesDiagnostics = rulesResult.diagnostics;\n logger.info(\n `memory-hybrid: dream-cycle — reflect-rules complete: ${rulesGenerated} rules stored (status=${rulesResult.diagnostics.status}${rulesResult.diagnostics.zeroRulesReason ? `, zero_rules_reason=${rulesResult.diagnostics.zeroRulesReason}` : \"\"})`,\n );\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — reflect-rules step failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-reflect-rules\",\n subsystem: \"reflection\",\n });\n reflectionRulesDiagnostics = {\n modelResponseChars: 0,\n parseSuccess: false,\n parsedCandidates: 0,\n rejectedDuplicates: 0,\n rejectedLowConfidence: 0,\n stored: 0,\n zeroRulesReason: \"reflect_rules_threw\",\n status: \"degraded\",\n };\n }\n stageReflectRules.complete(`rulesStored=${rulesGenerated}`);\n } else if (!enableReflectionRules) {\n reflectionRulesDiagnostics = {\n modelResponseChars: 0,\n parseSuccess: false,\n parsedCandidates: 0,\n rejectedDuplicates: 0,\n rejectedLowConfidence: 0,\n stored: 0,\n zeroRulesReason: \"stage_disabled\",\n status: \"ok\",\n };\n if (v) {\n logger.info(\"memory-hybrid: dream-cycle — reflection rules disabled (nightlyCycle.enableReflectionRules=false)\");\n }\n } else {\n reflectionRulesDiagnostics = {\n modelResponseChars: 0,\n parseSuccess: false,\n parsedCandidates: 0,\n rejectedDuplicates: 0,\n rejectedLowConfidence: 0,\n stored: 0,\n zeroRulesReason: \"stage_skipped_pattern_gate\",\n status: \"ok\",\n };\n if (v) {\n logger.info(\n `memory-hybrid: dream-cycle — skipping reflect-rules (${patternsFound} stored this cycle, ${livePatternCountForRules} live patterns; need ≥${MIN_PATTERNS_FOR_RULES})`,\n );\n }\n }\n\n // ── Stage 5: Refresh memory awareness index ───────────────────────────────\n const stageMemoryIndex = beginStage(\"MEMORY_INDEX.md refresh\", {\n heartbeat: true,\n progressSupplier: () => \"phase=refresh-index\",\n });\n try {\n await writeMemoryIndex(\n factsDb,\n openai,\n {\n model: config.model,\n fallbackModels: config.fallbackModels ?? [],\n recentWindowDays: config.reflectWindowDays,\n verbose: v,\n },\n logger,\n );\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — memory index update failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-memory-index\",\n subsystem: \"reflection\",\n });\n }\n stageMemoryIndex.complete();\n\n // ── Stage 6: Operational table/index cleanup ──────────────────────────────\n let logRowsPruned = 0;\n let vacuumRan = false;\n let walCheckpointRan = false;\n const stageOperational = beginStage(\"prune operational logs + FTS optimize + optional vacuum\", {\n heartbeat: true,\n progressSupplier: () =>\n `phase=cleanup; logRowsPruned=${logRowsPruned}; vacuumRan=${vacuumRan ? \"yes\" : \"no\"}; walCheckpointRan=${walCheckpointRan ? \"yes\" : \"no\"}`,\n });\n if (config.logRetentionDays > 0) {\n try {\n logRowsPruned = factsDb.pruneLogTables(config.logRetentionDays);\n if (logRowsPruned > 0) {\n logger.info(\n `memory-hybrid: dream-cycle — pruned ${logRowsPruned} log rows older than ${config.logRetentionDays} days`,\n );\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — pruneLogTables failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-prune-log-tables\",\n subsystem: \"facts-db\",\n });\n }\n }\n\n // ── Stage 6b: FTS5 optimize ───────────────────────────────────────────────\n const optimizeFtsStep = beginSubstep(\"FTS5 optimize (facts search)\");\n try {\n factsDb.optimizeFts();\n logger.info(\"memory-hybrid: dream-cycle — FTS5 index optimized\");\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — optimizeFts failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-optimize-fts\",\n subsystem: \"facts-db\",\n });\n }\n optimizeFtsStep.complete();\n\n // ── Step 5c: VACUUM + WAL checkpoint ────────────────────────────────────\n if (config.vacuumOnCycle) {\n const vacuumStep = beginSubstep(\"VACUUM + WAL checkpoint\");\n try {\n // Cost optimization: Skip VACUUM when freed space < 10MB (low benefit, high I/O cost)\n const { freedMB } = factsDb.freelistSpaceStats();\n\n if (freedMB < 10) {\n // Preserve historical \"checkpoint every cycle\" behavior even when VACUUM is deferred.\n factsDb.checkpointWalTruncate();\n walCheckpointRan = true;\n logger.info(\n `memory-hybrid: dream-cycle — skipping VACUUM (only ${freedMB.toFixed(2)}MB freed space, threshold: 10MB); WAL checkpoint complete`,\n );\n } else {\n factsDb.vacuumAndCheckpoint();\n vacuumRan = true;\n walCheckpointRan = true;\n logger.info(\n `memory-hybrid: dream-cycle — VACUUM + WAL checkpoint complete (reclaimed ${freedMB.toFixed(2)}MB)`,\n );\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — vacuumAndCheckpoint failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-vacuum\",\n subsystem: \"facts-db\",\n });\n }\n vacuumStep.complete(`vacuumRan=${vacuumRan ? \"yes\" : \"no\"}, walCheckpointRan=${walCheckpointRan ? \"yes\" : \"no\"}`);\n }\n stageOperational.complete(\n `logRowsPruned=${logRowsPruned}, vacuumRan=${vacuumRan ? \"yes\" : \"no\"}, walCheckpointRan=${walCheckpointRan ? \"yes\" : \"no\"}`,\n );\n\n // ── Step 6: Digest summary ───────────────────────────────────────────────\n const digestSummary = buildDigestSummary({\n factsPruned,\n factsDecayed,\n linksPruned,\n eventsConsolidated,\n factsCreated,\n patternsFound,\n rulesGenerated,\n logRowsPruned,\n vacuumRan,\n decayReclassified,\n orphanVectorsRemoved,\n });\n\n const totalElapsedSec = Math.floor((Date.now() - cycleStartedAt) / 1000);\n logger.info(`memory-hybrid: dream-cycle — complete in ${totalElapsedSec}s. ${digestSummary}`);\n\n return {\n factsPruned,\n factsDecayed,\n linksPruned,\n eventsConsolidated,\n factsCreated,\n patternsFound,\n rulesGenerated,\n reflectionRulesDiagnostics,\n logRowsPruned,\n vacuumRan,\n decayReclassified,\n orphanVectorsRemoved,\n digestSummary,\n skipped: false,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dream-cycle.js","names":[],"sources":["../../services/dream-cycle.ts"],"sourcesContent":["/**\n * Dream Cycle Service — Automated nightly reflection + pruning pipeline (Issue #143).\n *\n * Sequence (verbose `step N:` numbers are assigned in runtime order so skipped optional phases do not leave gaps):\n * prune / decay / orphaned links → episodic consolidation + event log maintenance → reflection (patterns) →\n * optional reflect-rules (if enough patterns) → MEMORY_INDEX refresh → prune log tables → FTS5 optimize →\n * optional VACUUM → digest summary.\n *\n * Designed to be cheap ($0.003/night target) using a Flash-tier model.\n * Self-contained — does not require an active agent session.\n */\n\nimport type OpenAI from \"openai\";\nimport type { EventLog, EventLogEntry, EventType } from \"../backends/event-log.js\";\nimport type { FactsDB } from \"../backends/facts-db.js\";\nimport type { VectorDB } from \"../backends/vector-db.js\";\nimport type { MemoryCategory, MemoryEntry } from \"../types/memory.js\";\nimport { CONSOLIDATED_FACT_DECAY_CLASS } from \"../utils/consolidation-controls.js\";\nimport type { EmbeddingProvider } from \"./embeddings.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\nimport { writeMemoryIndex } from \"./memory-index.js\";\nimport type { ProvenanceService } from \"./provenance.js\";\nimport {\n countActivePatternFactsForMaintenance,\n type ReflectionConfig,\n runReflection,\n runReflectionRules,\n} from \"./reflection.js\";\nimport { cleanupEvictedVector, deleteVectorsForFactIds } from \"./vector-maintenance.js\";\n\n/** Prune modes for the dream cycle. */\nexport type DreamCyclePruneMode = \"expired\" | \"decay\" | \"both\";\n\n/** Configuration for the nightly dream cycle. */\nexport interface DreamCycleConfig {\n enabled: boolean;\n schedule: string;\n reflectWindowDays: number;\n pruneMode: DreamCyclePruneMode;\n model: string;\n /** Fallback models for reflection steps, in preference order. */\n fallbackModels?: string[];\n consolidateAfterDays: number;\n /** Archive consolidated event log entries older than this many days. */\n eventLogArchivalDays: number;\n /** Directory for compressed JSONL archives. */\n eventLogArchivePath: string;\n /** Delete unconsolidated event log entries older than this many days. */\n maxUnconsolidatedAgeDays: number;\n /** Maximum events to merge into one consolidated fact. Default: 200. */\n maxEventsPerConsolidation: number;\n /**\n * Retention window for log tables (recall_log, reinforcement_log, feedback_trajectories).\n * Rows older than this many days are deleted. 0 = disabled. Default: 30.\n */\n logRetentionDays: number;\n /**\n * When true, run wal_checkpoint(TRUNCATE) + VACUUM after the cycle to reclaim freed space.\n * Default: true.\n */\n vacuumOnCycle: boolean;\n /**\n * When true, run the source-/importance-aware decay reclassifier (#1189) as part of the\n * nightly cycle. Defaults to true; operators can opt out by setting it to false.\n */\n reclassifyDecayOnCycle?: boolean;\n /** Inactivity threshold in days for the recall-based demotion path. Default: 90. */\n reclassifyInactiveDays?: number;\n /** Recall count required to promote a fact to a longer-lived class. Default: 3. */\n reclassifyPromoteRecallCount?: number;\n /** When true, log episodic consolidation / reflection / memory-index detail (CLI `--verbose`). */\n verbose?: boolean;\n /**\n * Episodic consolidation filter on `event.eventType` (#1185). Built-in deny list always includes\n * session/heartbeat/transport lifecycle types unless removed via config.\n */\n episodicConsolidationEventTypes?: {\n /** When non-empty, only these event types are eligible (after deny). */\n allow?: string[];\n /** Extra types to treat as noise (merged with built-in deny list). */\n deny?: string[];\n };\n /**\n * When true, enable reflection rules generation during the dream cycle.\n * Cost optimization: Set to false to save 30-40% of dream cycle LLM cost.\n * Default: true (enabled for backward compatibility).\n */\n enableReflectionRules?: boolean;\n}\n\n/** Result returned by a single dream cycle run. */\nexport interface DreamCycleResult {\n /** Facts removed by pruneExpired(). */\n factsPruned: number;\n /** Facts whose confidence was decayed (confidence halved). */\n factsDecayed: number;\n /** Orphaned memory_links rows removed. */\n linksPruned: number;\n /** Episodic event log entries successfully consolidated. */\n eventsConsolidated: number;\n /** New consolidated facts created from episodic events. */\n factsCreated: number;\n /** New patterns stored by runReflection(). */\n patternsFound: number;\n /** New rules stored by runReflectionRules(). */\n rulesGenerated: number;\n /** Reflect-rules diagnostics for zero/partial outcomes. */\n reflectionRulesDiagnostics?: {\n modelResponseChars: number;\n parseSuccess: boolean;\n parsedCandidates: number;\n rejectedDuplicates: number;\n rejectedLowConfidence: number;\n stored: number;\n zeroRulesReason?: string;\n status: \"ok\" | \"partial\" | \"degraded\";\n };\n /** Log table rows deleted by pruneLogTables() (Issue #573). */\n logRowsPruned: number;\n /** True when VACUUM + checkpoint was executed (Issue #573). */\n vacuumRan: boolean;\n /** Number of facts re-tiered by the source-/importance-aware reclassifier (#1189). */\n decayReclassified: number;\n /** Number of orphaned LanceDB vector entries removed during reconciliation. */\n orphanVectorsRemoved: number;\n /** Human-readable summary of the cycle. */\n digestSummary: string;\n /** True when the cycle was skipped because nightlyCycle.enabled = false. */\n skipped: boolean;\n}\n\n// Minimum patterns stored in one cycle before we also run reflect-rules.\nconst MIN_PATTERNS_FOR_RULES = 3;\nexport const DEFAULT_MAX_EVENTS_PER_CONSOLIDATION = 200;\nconst DREAM_STAGE_HEARTBEAT_MS = 60_000;\nconst EPISODIC_PROGRESS_HEARTBEAT_MS = 60_000;\nconst EPISODIC_PROGRESS_GROUP_INTERVAL = 25;\nconst SKIP_CONSOLIDATION_TEXT_PATTERNS = new Set([\n \"heartbeat\",\n \"session_end\",\n \"session_start\",\n \"transport_connect\",\n \"transport_disconnect\",\n]);\n\n/** Default deny list for `event.eventType` during episodic consolidation (#1185). */\nexport const DEFAULT_EPISODIC_CONSOLIDATION_EVENT_TYPE_DENY: ReadonlySet<EventType> = new Set([\n \"session_start\",\n \"session_end\",\n \"heartbeat\",\n \"transport_connect\",\n \"transport_disconnect\",\n]);\n\nexport type EpisodicConsolidationEventTypeFilter = {\n allow?: string[];\n deny?: string[];\n};\n\nfunction isVectorBackendAvailable(vectorDb: Pick<VectorDB, \"isLanceDbAvailable\"> | unknown): boolean {\n const candidate = vectorDb as { isLanceDbAvailable?: () => boolean };\n if (typeof candidate.isLanceDbAvailable !== \"function\") return true;\n return candidate.isLanceDbAvailable();\n}\n\nfunction episodicDenySet(filter?: EpisodicConsolidationEventTypeFilter | null): Set<string> {\n const s = new Set<string>(DEFAULT_EPISODIC_CONSOLIDATION_EVENT_TYPE_DENY);\n for (const x of filter?.deny ?? []) {\n if (typeof x === \"string\" && x.trim().length > 0) s.add(x.trim());\n }\n return s;\n}\n\n/**\n * True when this event should not be merged into consolidated facts based on `eventType`\n * (allow/deny lists). Text-pattern skipping remains in {@link shouldSkipEpisodicConsolidation}.\n *\n * CRITICAL FIX (#5): Allow list now overrides deny list. If an event type is explicitly\n * in the allow list, it is accepted even if it's in the default deny set.\n */\nexport function shouldSkipEpisodicConsolidationByEventType(\n event: EventLogEntry,\n filter?: EpisodicConsolidationEventTypeFilter | null,\n): boolean {\n const allow = filter?.allow?.map((x) => x.trim()).filter((x) => x.length > 0);\n // If allow list is specified and contains this event type, accept it (override deny).\n if (allow && allow.length > 0) {\n if (allow.includes(event.eventType)) return false;\n // If allow list is specified but doesn't contain this event type, reject it.\n return true;\n }\n // No allow list specified, check deny list.\n const deny = episodicDenySet(filter);\n if (deny.has(event.eventType)) return true;\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Episodic consolidation helpers (exported for testing)\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the primary text content from an event log entry.\n * Checks common content field names in priority order.\n */\nexport function extractEventText(event: EventLogEntry): string {\n const c = event.content;\n if (typeof c.text === \"string\" && c.text.trim().length > 0) return c.text.trim();\n if (typeof c.decision === \"string\" && c.decision.trim().length > 0) return c.decision.trim();\n if (typeof c.summary === \"string\" && c.summary.trim().length > 0) return c.summary.trim();\n if (typeof c.action === \"string\" && c.action.trim().length > 0) return c.action.trim();\n if (typeof c.description === \"string\" && c.description.trim().length > 0) return c.description.trim();\n // Fall back to any string value in the content object\n for (const v of Object.values(c)) {\n if (typeof v === \"string\" && v.trim().length > 0) return v.trim();\n }\n return \"\";\n}\n\n/**\n * Group event log entries by their primary entity.\n * Events with no entities are grouped under the \"__default__\" key.\n * Callers should omit lifecycle/noise events (e.g. via `shouldSkipEpisodicConsolidation` and\n * `shouldSkipEpisodicConsolidationByEventType`) before calling.\n */\nexport function groupEventsByEntity(events: EventLogEntry[]): Map<string, EventLogEntry[]> {\n const groups = new Map<string, EventLogEntry[]>();\n for (const event of events) {\n const primaryEntity = event.entities?.[0] ?? \"__default__\";\n if (!groups.has(primaryEntity)) groups.set(primaryEntity, []);\n groups.get(primaryEntity)?.push(event);\n }\n return groups;\n}\n\nexport function shouldSkipEpisodicConsolidation(event: EventLogEntry): boolean {\n const text = extractEventText(event).toLowerCase();\n for (const pattern of SKIP_CONSOLIDATION_TEXT_PATTERNS) {\n if (text.includes(pattern)) return true;\n }\n return false;\n}\n\n/**\n * Build the digest summary string from cycle counts.\n * Exported so it can be tested independently.\n */\nexport function buildDigestSummary(counts: {\n factsPruned: number;\n factsDecayed: number;\n linksPruned?: number;\n eventsConsolidated: number;\n factsCreated: number;\n patternsFound: number;\n rulesGenerated: number;\n logRowsPruned?: number;\n vacuumRan?: boolean;\n decayReclassified?: number;\n orphanVectorsRemoved?: number;\n}): string {\n const parts: string[] = [];\n if (counts.factsPruned > 0) parts.push(`${counts.factsPruned} facts pruned`);\n if (counts.factsDecayed > 0) parts.push(`${counts.factsDecayed} facts decayed`);\n if (counts.decayReclassified && counts.decayReclassified > 0) {\n parts.push(`${counts.decayReclassified} facts re-tiered`);\n }\n if (counts.orphanVectorsRemoved && counts.orphanVectorsRemoved > 0) {\n parts.push(`${counts.orphanVectorsRemoved} orphaned vectors reconciled`);\n }\n if (counts.linksPruned && counts.linksPruned > 0) parts.push(`${counts.linksPruned} orphaned links removed`);\n if (counts.eventsConsolidated > 0) {\n parts.push(`${counts.eventsConsolidated} events consolidated into ${counts.factsCreated} facts`);\n }\n if (counts.patternsFound > 0) parts.push(`${counts.patternsFound} patterns extracted`);\n if (counts.rulesGenerated > 0) parts.push(`${counts.rulesGenerated} rules generated`);\n if (counts.logRowsPruned && counts.logRowsPruned > 0) parts.push(`${counts.logRowsPruned} log rows pruned`);\n if (counts.vacuumRan) parts.push(\"VACUUM ran\");\n if (parts.length === 0) return \"No changes.\";\n return `${parts.join(\", \")}.`;\n}\n\n// ---------------------------------------------------------------------------\n// Episodic consolidation\n// ---------------------------------------------------------------------------\n\n/**\n * Run episodic consolidation:\n * 1. Fetch unconsolidated event log entries older than consolidateAfterDays.\n * 2. Group by primary entity.\n * 3. For each group, create a consolidated fact with structured provenance_json.\n * 4. Mark all events as consolidated in the event log.\n * 5. Optionally embed and store consolidated facts in LanceDB so they are\n * discoverable by semantic search (non-fatal; requires vectorDb + embeddings).\n *\n * Historical versions created one DERIVED_FROM graph edge per source event; new\n * consolidations keep that lineage on the fact row to avoid provenance mega-hubs.\n */\nexport async function runEpisodicConsolidation(\n factsDb: FactsDB,\n eventLog: EventLog,\n consolidateAfterDays: number,\n logger: { info: (msg: string) => void; warn: (msg: string) => void },\n verbose?: boolean,\n maxEventsPerConsolidation = DEFAULT_MAX_EVENTS_PER_CONSOLIDATION,\n eventTypeFilter?: EpisodicConsolidationEventTypeFilter | null,\n vectorDb?: VectorDB | null,\n embeddings?: EmbeddingProvider | null,\n): Promise<{ eventsConsolidated: number; factsCreated: number }> {\n const events = eventLog.getUnconsolidated(consolidateAfterDays);\n if (events.length === 0) {\n return { eventsConsolidated: 0, factsCreated: 0 };\n }\n\n let eventsConsolidated = 0;\n const skippedTextEvents = events.filter(shouldSkipEpisodicConsolidation);\n if (skippedTextEvents.length > 0) {\n try {\n eventLog.markConsolidated(\n skippedTextEvents.map((e) => e.id),\n \"SKIP:lifecycle_event\",\n );\n eventsConsolidated += skippedTextEvents.length;\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — failed to mark lifecycle events as skipped: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-mark-lifecycle-skip\",\n subsystem: \"event-log\",\n });\n }\n }\n\n const afterTextSkip = events.filter((event) => !shouldSkipEpisodicConsolidation(event));\n const skippedTypeEvents = afterTextSkip.filter((e) => shouldSkipEpisodicConsolidationByEventType(e, eventTypeFilter));\n if (skippedTypeEvents.length > 0) {\n try {\n eventLog.markConsolidated(\n skippedTypeEvents.map((e) => e.id),\n \"SKIP:event_type_filter\",\n );\n eventsConsolidated += skippedTypeEvents.length;\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — failed to mark event-type-filtered events as skipped: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-mark-event-type-skip\",\n subsystem: \"event-log\",\n });\n }\n }\n\n const consolidatableEvents = afterTextSkip.filter(\n (event) => !shouldSkipEpisodicConsolidationByEventType(event, eventTypeFilter),\n );\n if (consolidatableEvents.length === 0) {\n return { eventsConsolidated, factsCreated: 0 };\n }\n\n const groups = groupEventsByEntity(consolidatableEvents);\n if (verbose) {\n logger.info(\n `memory-hybrid: dream-cycle — episodic consolidation: ${consolidatableEvents.length} consolidatable event(s) in ${groups.size} entity group(s) (≥${consolidateAfterDays}d); skipped ${skippedTextEvents.length} text-pattern noise + ${skippedTypeEvents.length} eventType-filtered`,\n );\n }\n let factsCreated = 0;\n const totalGroups = groups.size;\n let processedGroups = 0;\n const consolidationStartedAt = Date.now();\n let lastProgressLogAt = consolidationStartedAt;\n const maybeLogConsolidationProgress = (entity: string): void => {\n if (!verbose) return;\n const now = Date.now();\n const shouldLogByCount = processedGroups === 1 || processedGroups % EPISODIC_PROGRESS_GROUP_INTERVAL === 0;\n const shouldLogByTime = now - lastProgressLogAt >= EPISODIC_PROGRESS_HEARTBEAT_MS;\n if (!shouldLogByCount && !shouldLogByTime) return;\n lastProgressLogAt = now;\n const elapsedSec = Math.floor((now - consolidationStartedAt) / 1000);\n const safeEntity = entity === \"__default__\" ? \"unattributed\" : entity;\n logger.info(\n `memory-hybrid: dream-cycle — episodic consolidation progress ${processedGroups}/${totalGroups} group(s); eventsConsolidated=${eventsConsolidated}, factsCreated=${factsCreated}, currentEntity=\"${safeEntity}\"; still running after ${elapsedSec}s`,\n );\n };\n\n for (const [entity, groupEvents] of groups) {\n if (groupEvents.length === 0) continue;\n processedGroups++;\n maybeLogConsolidationProgress(entity);\n\n if (entity === \"__default__\" && groupEvents.length > maxEventsPerConsolidation) {\n try {\n eventLog.markConsolidated(\n groupEvents.map((e) => e.id),\n \"SKIP:default_group_cap\",\n );\n eventsConsolidated += groupEvents.length;\n logger.info(\n `memory-hybrid: dream-cycle — skipped ${groupEvents.length} unattributed event(s): default group exceeds maxEventsPerConsolidation=${maxEventsPerConsolidation}`,\n );\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — failed to mark capped default group as skipped: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-mark-default-cap-skip\",\n subsystem: \"event-log\",\n });\n }\n continue;\n }\n\n const cappedGroupEvents = groupEvents.slice(0, maxEventsPerConsolidation);\n const excessEvents = groupEvents.slice(maxEventsPerConsolidation);\n\n // Collect text from all events in this group\n const eventTexts = cappedGroupEvents.map((e) => extractEventText(e)).filter((t) => t.length >= 3);\n\n if (eventTexts.length === 0) {\n // Mark events as consolidated with a namespaced skip sentinel to prevent re-processing.\n // 'SKIP:no_text' is clearly not a real fact UUID — no UUID-based query will match it.\n try {\n eventLog.markConsolidated(\n cappedGroupEvents.map((e) => e.id),\n \"SKIP:no_text\",\n );\n eventsConsolidated += cappedGroupEvents.length;\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — failed to mark no-text events as consolidated: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-mark-skip\",\n subsystem: \"event-log\",\n });\n }\n continue;\n }\n\n // Build merged text for the consolidated fact\n const entityLabel = entity !== \"__default__\" ? entity : null;\n const mergedText =\n eventTexts.length === 1\n ? eventTexts[0]\n : `[consolidated from ${eventTexts.length} events${entityLabel ? ` about ${entityLabel}` : \"\"}] ${eventTexts.slice(0, 5).join(\"; \")}`;\n\n const consolidatedAt = Math.floor(Date.now() / 1000);\n const sourceEvents = cappedGroupEvents.map((event) => ({\n id: event.id,\n eventType: event.eventType,\n timestamp: event.timestamp,\n sessionId: event.sessionId,\n text: extractEventText(event).slice(0, 300),\n }));\n\n // Create the consolidated fact. Issue #1195: store dream-cycle provenance on\n // the fact row instead of creating one DERIVED_FROM graph edge per event.\n // Historical DERIVED_FROM rows are left untouched by this forward migration;\n // deleting legacy provenance blindly is riskier than stopping new hub growth.\n let consolidatedFact: MemoryEntry | null = null;\n try {\n const storeResult = factsDb.storeWithResult({\n text: mergedText.slice(0, 500),\n category: \"fact\" as MemoryCategory,\n importance: 0.5,\n entity: entityLabel,\n key: \"consolidated\",\n value: null,\n source: \"dream-cycle\",\n decayClass: CONSOLIDATED_FACT_DECAY_CLASS,\n tags: [\"dream-cycle\", \"consolidated\"],\n extractionMethod: \"consolidation\",\n provenanceJson: JSON.stringify({\n method: \"dream-cycle\",\n consolidatedAt,\n sourceEventIds: sourceEvents.map((event) => event.id),\n sourceEvents,\n }),\n });\n if (storeResult.skipped) {\n continue;\n }\n consolidatedFact = storeResult.entry;\n // CRITICAL FIX (#2): Delete vector for evicted fact to prevent orphaned vectors\n if (vectorDb) {\n await cleanupEvictedVector({\n vectorDb,\n evictedFactId: storeResult.evictedFactId,\n logger,\n context: \"dream-cycle\",\n });\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — failed to store consolidated fact for entity \"${entity}\": ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-consolidate\",\n subsystem: \"facts-db\",\n });\n continue;\n }\n\n // Mark all events in the group as consolidated into the new fact\n try {\n eventLog.markConsolidated(\n cappedGroupEvents.map((e) => e.id),\n consolidatedFact.id,\n );\n factsCreated++;\n eventsConsolidated += cappedGroupEvents.length;\n\n if (excessEvents.length > 0) {\n try {\n eventLog.markConsolidated(\n excessEvents.map((e) => e.id),\n \"SKIP:entity_group_cap\",\n );\n eventsConsolidated += excessEvents.length;\n logger.info(\n `memory-hybrid: dream-cycle — skipped ${excessEvents.length} excess event(s) for entity \"${entity}\": exceeds maxEventsPerConsolidation=${maxEventsPerConsolidation}`,\n );\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — failed to mark excess entity group events as skipped: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-mark-entity-cap-skip\",\n subsystem: \"event-log\",\n });\n }\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — failed to mark events as consolidated: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-mark-consolidated\",\n subsystem: \"event-log\",\n });\n try {\n if (consolidatedFact) factsDb.delete(consolidatedFact.id);\n } catch (cleanupErr) {\n logger.warn(\n `memory-hybrid: dream-cycle — failed to delete consolidated fact after mark failure: ${cleanupErr}`,\n );\n }\n continue;\n }\n\n logger.info(\n `memory-hybrid: dream-cycle — consolidated ${cappedGroupEvents.length} events${entityLabel ? ` for entity \"${entityLabel}\"` : \"\"} → fact ${consolidatedFact.id.slice(0, 8)}`,\n );\n\n // Embed and store the consolidated fact in LanceDB so it is discoverable by\n // semantic search. This is non-fatal: the fact is already safely in SQLite.\n // Use the same truncated text for both the embedding call and the store payload\n // so the embedding always matches the text that will be returned by search.\n if (vectorDb && embeddings) {\n const persistedText =\n typeof consolidatedFact.text === \"string\" && consolidatedFact.text.trim().length > 0\n ? consolidatedFact.text.slice(0, 500)\n : mergedText.slice(0, 500);\n try {\n const vector = await embeddings.embed(persistedText);\n await vectorDb.store({\n id: consolidatedFact.id,\n text: persistedText,\n vector,\n importance: consolidatedFact.importance,\n category: consolidatedFact.category,\n });\n } catch (embedErr) {\n logger.warn(\n `memory-hybrid: dream-cycle — failed to embed consolidated fact ${consolidatedFact.id.slice(0, 8)} into LanceDB (non-fatal): ${embedErr}`,\n );\n }\n }\n }\n\n if (verbose) {\n const elapsedSec = Math.floor((Date.now() - consolidationStartedAt) / 1000);\n logger.info(\n `memory-hybrid: dream-cycle — episodic consolidation complete in ${elapsedSec}s; groups=${processedGroups}/${totalGroups}, eventsConsolidated=${eventsConsolidated}, factsCreated=${factsCreated}`,\n );\n }\n\n return { eventsConsolidated, factsCreated };\n}\n\n// ---------------------------------------------------------------------------\n// Main dream cycle orchestration\n// ---------------------------------------------------------------------------\n\n/**\n * Run the full nightly dream cycle:\n * prune → consolidation → reflect → optional reflect-rules → memory index → log prune → FTS5 → optional VACUUM → digest.\n */\nexport async function runDreamCycle(\n factsDb: FactsDB,\n vectorDb: VectorDB,\n embeddings: EmbeddingProvider,\n openai: OpenAI,\n eventLog: EventLog | null,\n config: DreamCycleConfig,\n logger: { info: (msg: string) => void; warn: (msg: string) => void },\n provenanceService?: ProvenanceService | null,\n): Promise<DreamCycleResult> {\n if (!config.enabled) {\n return {\n factsPruned: 0,\n factsDecayed: 0,\n linksPruned: 0,\n eventsConsolidated: 0,\n factsCreated: 0,\n patternsFound: 0,\n rulesGenerated: 0,\n logRowsPruned: 0,\n vacuumRan: false,\n decayReclassified: 0,\n orphanVectorsRemoved: 0,\n digestSummary: \"Dream cycle disabled.\",\n skipped: true,\n };\n }\n\n logger.info(\"memory-hybrid: dream-cycle — starting nightly cycle\");\n const cycleStartedAt = Date.now();\n const v = !!config.verbose;\n let stageCounter = 0;\n const beginStage = (\n label: string,\n opts?: { heartbeat?: boolean; progressSupplier?: () => string | undefined },\n ): {\n complete: (summary?: string) => void;\n fail: (err: unknown) => void;\n } => {\n const stageNumber = ++stageCounter;\n const startedAt = Date.now();\n logger.info(`memory-hybrid: dream-cycle — stage ${stageNumber} start: ${label}`);\n let heartbeat: ReturnType<typeof setInterval> | undefined;\n if (v && opts?.heartbeat === true) {\n heartbeat = setInterval(() => {\n const elapsedSec = Math.floor((Date.now() - startedAt) / 1000);\n const progress = opts?.progressSupplier?.();\n logger.info(\n `memory-hybrid: dream-cycle — stage ${stageNumber} still running after ${elapsedSec}s: ${label}${progress ? `; ${progress}` : \"\"}`,\n );\n }, DREAM_STAGE_HEARTBEAT_MS);\n heartbeat.unref?.();\n }\n const clearHeartbeat = () => {\n if (heartbeat) clearInterval(heartbeat);\n };\n return {\n complete: (summary?: string) => {\n clearHeartbeat();\n const elapsedSec = Math.floor((Date.now() - startedAt) / 1000);\n logger.info(\n `memory-hybrid: dream-cycle — stage ${stageNumber} complete in ${elapsedSec}s: ${label}${summary ? `; ${summary}` : \"\"}`,\n );\n },\n fail: (err: unknown) => {\n clearHeartbeat();\n const elapsedSec = Math.floor((Date.now() - startedAt) / 1000);\n logger.warn(`memory-hybrid: dream-cycle — stage ${stageNumber} failed after ${elapsedSec}s: ${label}: ${err}`);\n },\n };\n };\n const beginSubstep = (label: string): { complete: (summary?: string) => void } => {\n const startedAt = Date.now();\n if (v) logger.info(`memory-hybrid: dream-cycle — substep start: ${label}`);\n return {\n complete: (summary?: string) => {\n if (!v) return;\n const elapsedSec = Math.floor((Date.now() - startedAt) / 1000);\n logger.info(\n `memory-hybrid: dream-cycle — substep complete in ${elapsedSec}s: ${label}${summary ? `; ${summary}` : \"\"}`,\n );\n },\n };\n };\n\n // ── Stage 1: Core pruning / decay / graph-vector maintenance ─────────────\n const stageCore = beginStage(\"core prune/decay/link/vector maintenance\");\n let factsPruned = 0;\n let factsDecayed = 0;\n if (config.pruneMode === \"expired\" || config.pruneMode === \"both\") {\n try {\n const expiredPrune = factsDb.pruneExpiredWithDetails();\n factsPruned = expiredPrune.factsPruned;\n const vectorCleanup = await deleteVectorsForFactIds(vectorDb, expiredPrune.deletedFactIds, {\n operation: \"dream-cycle-prune-expired\",\n logger,\n });\n logger.info(`memory-hybrid: dream-cycle — pruned ${factsPruned} expired facts`);\n if (vectorCleanup.failed > 0) {\n logger.warn(\n `memory-hybrid: dream-cycle — expired vector cleanup partial: deleted ${vectorCleanup.deleted}/${vectorCleanup.attempted}, failed ${vectorCleanup.failed}`,\n );\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — pruneExpired failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-prune-expired\",\n subsystem: \"facts-db\",\n });\n }\n }\n\n // ── Step 1a-pre: Reclassify decay classes BEFORE taking the decay snapshot ────\n // Running reclassification first ensures that listFactIdsToBeDeletedByDecayRun()\n // reflects up-to-date decay_class values. Previously, reclassification ran in\n // Step 1c (after the decay snapshot), which caused newly re-tiered facts to be\n // missed by the per-batch vector cleanup, leaving orphaned LanceDB vectors until\n // the full Step 1e reconciliation caught them on a later cycle.\n let decayReclassified = 0;\n if (config.reclassifyDecayOnCycle !== false) {\n const reclassifyStep = beginSubstep(\"decay reclassify (source/importance/recall)\");\n try {\n const report = factsDb.reclassifyDecayClasses({\n apply: true,\n inactiveDays: config.reclassifyInactiveDays ?? 90,\n promoteRecallCount: config.reclassifyPromoteRecallCount ?? 3,\n });\n decayReclassified = report.changed;\n if (decayReclassified > 0) {\n logger.info(\n `memory-hybrid: dream-cycle — decay reclassify: ${decayReclassified} facts re-tiered (scanned ${report.scanned}, stable+permanent ${(report.stablePermanentRatioBefore * 100).toFixed(1)}% → ${(report.stablePermanentRatioAfter * 100).toFixed(1)}%)`,\n );\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — reclassifyDecayClasses failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-reclassify-decay\",\n subsystem: \"facts-db\",\n });\n }\n reclassifyStep.complete(`changed=${decayReclassified}`);\n }\n\n if (config.pruneMode === \"decay\" || config.pruneMode === \"both\") {\n try {\n const decayNowSec = Math.floor(Date.now() / 1000);\n const decayRun = factsDb.decayConfidenceWithDetails(decayNowSec);\n factsDecayed = decayRun.factsDecayed;\n const vectorCleanup = await deleteVectorsForFactIds(vectorDb, decayRun.deletedFactIds, {\n operation: \"dream-cycle-decay\",\n logger,\n });\n logger.info(`memory-hybrid: dream-cycle — decayed ${factsDecayed} facts`);\n if (vectorCleanup.failed > 0) {\n logger.warn(\n `memory-hybrid: dream-cycle — decay vector cleanup partial: deleted ${vectorCleanup.deleted}/${vectorCleanup.attempted}, failed ${vectorCleanup.failed}`,\n );\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — decayConfidence failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-decay\",\n subsystem: \"facts-db\",\n });\n }\n }\n\n // ── Step 1b: Prune orphaned links ────────────────────────────────────────\n let linksPruned = 0;\n try {\n linksPruned = factsDb.pruneOrphanedLinks();\n if (linksPruned > 0) {\n logger.info(`memory-hybrid: dream-cycle — pruned ${linksPruned} orphaned link(s)`);\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — pruneOrphanedLinks failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-prune-orphaned-links\",\n subsystem: \"facts-db\",\n });\n }\n\n // ── Step 1d: Refresh denormalized graph degrees (#1192) ──────────────────\n // Without this, every BFS hop runs `COUNT(*) FROM memory_links` against the same node, which\n // is O(edges) per call. A single nightly refresh keeps reads cheap while still adapting to\n // graph changes within a day.\n if (typeof (factsDb as { refreshFactDegrees?: () => unknown }).refreshFactDegrees === \"function\") {\n const refreshDegreesStep = beginSubstep(\"refresh fact degrees\");\n try {\n const result = (factsDb as { refreshFactDegrees: () => { updated: number } }).refreshFactDegrees();\n if (v) {\n logger.info(`memory-hybrid: dream-cycle — refreshed fact degrees for ${result.updated} facts`);\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — refreshFactDegrees failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-refresh-degrees\",\n subsystem: \"facts-db\",\n });\n }\n refreshDegreesStep.complete();\n }\n\n // ── Step 1e: Reconcile LanceDB orphaned vectors ──────────────────────────\n // After SQLite pruning (pruneExpired, decayConfidence), any LanceDB vector whose\n // corresponding SQLite fact was hard-deleted is now an orphan. Orphaned vectors\n // pollute semantic search results with stale or deleted facts. We reconcile here\n // by deleting vector IDs that no longer have a corresponding SQLite fact.\n const vectorReconcileStep = beginSubstep(\"reconcile LanceDB orphaned vectors\");\n let orphanVectorsRemoved = 0;\n try {\n const sqliteIds = new Set(factsDb.getAllIds());\n const vectorIds = await vectorDb.getAllIds();\n const orphans = vectorIds.filter((id) => !sqliteIds.has(id));\n if (orphans.length > 0) {\n logger.info(`memory-hybrid: dream-cycle — reconciling ${orphans.length} orphaned vector(s)...`);\n const cleanup = await deleteVectorsForFactIds(vectorDb, orphans, {\n operation: \"dream-cycle-orphan-vector-reconcile\",\n logger,\n });\n orphanVectorsRemoved = cleanup.deleted;\n if (cleanup.deleted > 0) {\n logger.info(`memory-hybrid: dream-cycle — removed ${cleanup.deleted} orphaned vector(s) from LanceDB`);\n }\n if (cleanup.failed > 0) {\n logger.warn(\n `memory-hybrid: dream-cycle — orphan reconciliation partial: deleted ${cleanup.deleted}/${cleanup.attempted}, failed ${cleanup.failed}`,\n );\n }\n }\n if (isVectorBackendAvailable(vectorDb)) {\n const vectorlessAfterReconcile = factsDb.countVectorlessActiveFacts();\n if (vectorlessAfterReconcile > 0) {\n logger.warn(\n `memory-hybrid: dream-cycle — detected ${vectorlessAfterReconcile} active SQLite fact(s) without vectors after reconciliation; run 'hybrid-mem reembed-vectorless --apply' to restore semantic recall coverage`,\n );\n }\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — vector reconciliation failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-vector-reconcile\",\n subsystem: \"vector\",\n });\n }\n vectorReconcileStep.complete(`removed=${orphanVectorsRemoved}`);\n stageCore.complete(\n `factsPruned=${factsPruned}, factsDecayed=${factsDecayed}, linksPruned=${linksPruned}, decayReclassified=${decayReclassified}, orphanVectorsRemoved=${orphanVectorsRemoved}`,\n );\n\n // ── Stage 2: Episodic consolidation + event log maintenance ──────────────\n let eventsConsolidated = 0;\n let factsCreated = 0;\n let eventLogPhase = \"consolidation\";\n const stageEventLog = beginStage(\"episodic consolidation + event log maintenance\", {\n heartbeat: true,\n progressSupplier: () =>\n `phase=${eventLogPhase}; eventsConsolidated=${eventsConsolidated}; factsCreated=${factsCreated}`,\n });\n if (eventLog) {\n try {\n const consolidationResult = await runEpisodicConsolidation(\n factsDb,\n eventLog,\n config.consolidateAfterDays,\n logger,\n v,\n config.maxEventsPerConsolidation,\n config.episodicConsolidationEventTypes ?? null,\n vectorDb,\n embeddings,\n );\n eventsConsolidated = consolidationResult.eventsConsolidated;\n factsCreated = consolidationResult.factsCreated;\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — consolidation step failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-consolidation\",\n subsystem: \"event-log\",\n });\n }\n } else if (v) {\n logger.info(\"memory-hybrid: dream-cycle — no event log: skipping episodic consolidation\");\n }\n\n // ── Step 2b: Archive stale event log entries ─────────────────────────────\n if (eventLog && config.eventLogArchivalDays > 0) {\n eventLogPhase = \"archive-consolidated\";\n try {\n const result = await eventLog.archiveConsolidated(config.eventLogArchivalDays, config.eventLogArchivePath);\n if (result.archived > 0) {\n logger.info(\n `memory-hybrid: dream-cycle — archived ${result.archived} consolidated event log entries older than ${config.eventLogArchivalDays} days`,\n );\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — archiveConsolidated failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-archive\",\n subsystem: \"event-log\",\n });\n }\n }\n\n // ── Step 2c: Clean up old unconsolidated events ──────────────────────────\n if (eventLog && config.maxUnconsolidatedAgeDays > 0) {\n eventLogPhase = \"archive-old\";\n try {\n const deleted = eventLog.archiveOld(config.maxUnconsolidatedAgeDays, true);\n if (deleted > 0) {\n logger.info(\n `memory-hybrid: dream-cycle — deleted ${deleted} old event log entries (including unconsolidated) older than ${config.maxUnconsolidatedAgeDays} days`,\n );\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — archiveOld failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-archive-old\",\n subsystem: \"event-log\",\n });\n }\n }\n stageEventLog.complete(`eventsConsolidated=${eventsConsolidated}, factsCreated=${factsCreated}`);\n\n // ── Stage 3: Reflection (patterns) ────────────────────────────────────────\n let patternsFound = 0;\n const stageReflection = beginStage(\"reflection (patterns)\", {\n heartbeat: true,\n progressSupplier: () => `phase=extract-patterns; patternsStored=${patternsFound}`,\n });\n const reflectionConfig: ReflectionConfig = {\n enabled: true,\n defaultWindow: config.reflectWindowDays,\n minObservations: 2,\n };\n try {\n const reflectionResult = await runReflection(\n factsDb,\n vectorDb,\n embeddings,\n openai,\n reflectionConfig,\n {\n window: config.reflectWindowDays,\n dryRun: false,\n model: config.model,\n fallbackModels: config.fallbackModels ?? [],\n verbose: v,\n },\n logger,\n provenanceService,\n );\n patternsFound = reflectionResult.patternsStored;\n logger.info(`memory-hybrid: dream-cycle — reflection complete: ${patternsFound} patterns stored`);\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — reflection step failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-reflect\",\n subsystem: \"reflection\",\n });\n }\n stageReflection.complete(`patternsStored=${patternsFound}`);\n\n const livePatternCountForRules = countActivePatternFactsForMaintenance(factsDb);\n const patternGateForRules = Math.max(patternsFound, livePatternCountForRules);\n\n // ── Stage 4: Reflect-rules (optional) ─────────────────────────────────────\n let rulesGenerated = 0;\n let reflectionRulesDiagnostics: DreamCycleResult[\"reflectionRulesDiagnostics\"];\n const enableReflectionRules = config.enableReflectionRules !== false; // default: true\n if (enableReflectionRules && patternGateForRules >= MIN_PATTERNS_FOR_RULES) {\n const stageReflectRules = beginStage(\"reflect-rules\", {\n heartbeat: true,\n progressSupplier: () => `phase=extract-rules; rulesStored=${rulesGenerated}`,\n });\n try {\n const rulesResult = await runReflectionRules(\n factsDb,\n vectorDb,\n embeddings,\n openai,\n { dryRun: false, model: config.model, fallbackModels: config.fallbackModels ?? [], verbose: v },\n logger,\n provenanceService,\n );\n rulesGenerated = rulesResult.rulesStored;\n reflectionRulesDiagnostics = rulesResult.diagnostics;\n logger.info(\n `memory-hybrid: dream-cycle — reflect-rules complete: ${rulesGenerated} rules stored (status=${rulesResult.diagnostics.status}${rulesResult.diagnostics.zeroRulesReason ? `, zero_rules_reason=${rulesResult.diagnostics.zeroRulesReason}` : \"\"})`,\n );\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — reflect-rules step failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-reflect-rules\",\n subsystem: \"reflection\",\n });\n reflectionRulesDiagnostics = {\n modelResponseChars: 0,\n parseSuccess: false,\n parsedCandidates: 0,\n rejectedDuplicates: 0,\n rejectedLowConfidence: 0,\n stored: 0,\n zeroRulesReason: \"reflect_rules_threw\",\n status: \"degraded\",\n };\n }\n stageReflectRules.complete(`rulesStored=${rulesGenerated}`);\n } else if (!enableReflectionRules) {\n reflectionRulesDiagnostics = {\n modelResponseChars: 0,\n parseSuccess: false,\n parsedCandidates: 0,\n rejectedDuplicates: 0,\n rejectedLowConfidence: 0,\n stored: 0,\n zeroRulesReason: \"stage_disabled\",\n status: \"ok\",\n };\n if (v) {\n logger.info(\"memory-hybrid: dream-cycle — reflection rules disabled (nightlyCycle.enableReflectionRules=false)\");\n }\n } else {\n reflectionRulesDiagnostics = {\n modelResponseChars: 0,\n parseSuccess: false,\n parsedCandidates: 0,\n rejectedDuplicates: 0,\n rejectedLowConfidence: 0,\n stored: 0,\n zeroRulesReason: \"stage_skipped_pattern_gate\",\n status: \"ok\",\n };\n if (v) {\n logger.info(\n `memory-hybrid: dream-cycle — skipping reflect-rules (${patternsFound} stored this cycle, ${livePatternCountForRules} live patterns; need ≥${MIN_PATTERNS_FOR_RULES})`,\n );\n }\n }\n\n // ── Stage 5: Refresh memory awareness index ───────────────────────────────\n const stageMemoryIndex = beginStage(\"MEMORY_INDEX.md refresh\", {\n heartbeat: true,\n progressSupplier: () => \"phase=refresh-index\",\n });\n try {\n await writeMemoryIndex(\n factsDb,\n openai,\n {\n model: config.model,\n fallbackModels: config.fallbackModels ?? [],\n recentWindowDays: config.reflectWindowDays,\n verbose: v,\n },\n logger,\n );\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — memory index update failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-memory-index\",\n subsystem: \"reflection\",\n });\n }\n stageMemoryIndex.complete();\n\n // ── Stage 6: Operational table/index cleanup ──────────────────────────────\n let logRowsPruned = 0;\n let vacuumRan = false;\n let walCheckpointRan = false;\n const stageOperational = beginStage(\"prune operational logs + FTS optimize + optional vacuum\", {\n heartbeat: true,\n progressSupplier: () =>\n `phase=cleanup; logRowsPruned=${logRowsPruned}; vacuumRan=${vacuumRan ? \"yes\" : \"no\"}; walCheckpointRan=${walCheckpointRan ? \"yes\" : \"no\"}`,\n });\n if (config.logRetentionDays > 0) {\n try {\n logRowsPruned = factsDb.pruneLogTables(config.logRetentionDays);\n if (logRowsPruned > 0) {\n logger.info(\n `memory-hybrid: dream-cycle — pruned ${logRowsPruned} log rows older than ${config.logRetentionDays} days`,\n );\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — pruneLogTables failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-prune-log-tables\",\n subsystem: \"facts-db\",\n });\n }\n }\n\n // ── Stage 6b: FTS5 optimize ───────────────────────────────────────────────\n const optimizeFtsStep = beginSubstep(\"FTS5 optimize (facts search)\");\n try {\n factsDb.optimizeFts();\n logger.info(\"memory-hybrid: dream-cycle — FTS5 index optimized\");\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — optimizeFts failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-optimize-fts\",\n subsystem: \"facts-db\",\n });\n }\n optimizeFtsStep.complete();\n\n // ── Step 5c: VACUUM + WAL checkpoint ────────────────────────────────────\n if (config.vacuumOnCycle) {\n const vacuumStep = beginSubstep(\"VACUUM + WAL checkpoint\");\n try {\n // Cost optimization: Skip VACUUM when freed space < 10MB (low benefit, high I/O cost)\n const { freedMB } = factsDb.freelistSpaceStats();\n\n if (freedMB < 10) {\n // Preserve historical \"checkpoint every cycle\" behavior even when VACUUM is deferred.\n factsDb.checkpointWalTruncate();\n walCheckpointRan = true;\n logger.info(\n `memory-hybrid: dream-cycle — skipping VACUUM (only ${freedMB.toFixed(2)}MB freed space, threshold: 10MB); WAL checkpoint complete`,\n );\n } else {\n factsDb.vacuumAndCheckpoint();\n vacuumRan = true;\n walCheckpointRan = true;\n logger.info(\n `memory-hybrid: dream-cycle — VACUUM + WAL checkpoint complete (reclaimed ${freedMB.toFixed(2)}MB)`,\n );\n }\n } catch (err) {\n logger.warn(`memory-hybrid: dream-cycle — vacuumAndCheckpoint failed: ${err}`);\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"dream-cycle-vacuum\",\n subsystem: \"facts-db\",\n });\n }\n vacuumStep.complete(`vacuumRan=${vacuumRan ? \"yes\" : \"no\"}, walCheckpointRan=${walCheckpointRan ? \"yes\" : \"no\"}`);\n }\n stageOperational.complete(\n `logRowsPruned=${logRowsPruned}, vacuumRan=${vacuumRan ? \"yes\" : \"no\"}, walCheckpointRan=${walCheckpointRan ? \"yes\" : \"no\"}`,\n );\n\n // ── Step 6: Digest summary ───────────────────────────────────────────────\n const digestSummary = buildDigestSummary({\n factsPruned,\n factsDecayed,\n linksPruned,\n eventsConsolidated,\n factsCreated,\n patternsFound,\n rulesGenerated,\n logRowsPruned,\n vacuumRan,\n decayReclassified,\n orphanVectorsRemoved,\n });\n\n const totalElapsedSec = Math.floor((Date.now() - cycleStartedAt) / 1000);\n logger.info(`memory-hybrid: dream-cycle — complete in ${totalElapsedSec}s. ${digestSummary}`);\n\n return {\n factsPruned,\n factsDecayed,\n linksPruned,\n eventsConsolidated,\n factsCreated,\n patternsFound,\n rulesGenerated,\n reflectionRulesDiagnostics,\n logRowsPruned,\n vacuumRan,\n decayReclassified,\n orphanVectorsRemoved,\n digestSummary,\n skipped: false,\n };\n}\n"],"mappings":"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"}
|