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":"task-ledger-facts.js","names":[],"sources":["../../services/task-ledger-facts.ts"],"sourcesContent":["/**\n * Facts-backed active task ledger (category:project).\n * Aligns hybrid-mem active-tasks with memory_store / memory_recall workflows.\n */\n\nimport { mkdir, readFile, rm, stat, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { execFile } from \"../utils/process-runner.js\";\nimport { mergeFactProvenanceJson } from \"../backends/facts-db/provenance-json.js\";\nimport type { FactsDB } from \"../backends/facts-db.js\";\nimport type { VectorDB } from \"../backends/vector-db.js\";\nimport type { ActiveTaskProjectionConfig, MemoryCategory } from \"../config.js\";\nimport type { MemoryEntry, ScopeFilter } from \"../types/memory.js\";\nimport { CLI_STORE_IMPORTANCE } from \"../utils/constants.js\";\nimport { getEnv } from \"../utils/env-manager.js\";\nimport {\n type ActiveTaskEntry,\n type ActiveTaskStatus,\n type PendingTaskSignal,\n OMITTED_CAP_NOTE,\n UNKNOWN_ACTIVE_TASK_TIME,\n completeTask,\n deleteSignal,\n detectStaleTasks,\n flushCompletedTaskToMemory,\n readPendingSignals,\n serializeActiveTaskFile,\n serializeTaskEntry,\n upsertTask,\n} from \"./active-task.js\";\nimport type { EmbeddingProvider } from \"./embeddings.js\";\nimport { isOpenClawSessionLikelyPresent, looksLikeOpenClawSessionRef } from \"./openclaw-session-artifact.js\";\nimport { fetchLivePrBlockerStatus, type PrBlockerStatus } from \"./task-hygiene.js\";\nimport {\n activeTaskProvenance,\n canonicalLabel,\n displayStatusToFact,\n factCanonicalLabel,\n factStatusToDisplay,\n groupProjectFactsByEntity,\n isTerminalFactStatus,\n readCanonicalLabelFromFact,\n} from \"./task-ledger/canonical.js\";\n\nconst execFileAsync = promisify(execFile);\n\nexport {\n canonicalLabel,\n displayStatusToFact,\n factStatusToDisplay,\n groupProjectFactsByEntity,\n} from \"./task-ledger/canonical.js\";\nexport const TASK_LEDGER_CATEGORY = \"project\" as MemoryCategory;\n\nconst GENERIC_TASK_TITLE_NORMALIZED = new Set([\"project task\", \"task\", \"active task\"]);\nconst PROJECTION_STALE_MARKER_SUFFIX = \".stale.json\";\nconst ACTIVE_TASK_PROJECTION_GLOBAL_SCOPE_FILTER: ScopeFilter = { agentId: \"__active_task_projection_global__\" };\n\ntype ActiveTaskProjectionStaleMarker = {\n staleAt: string;\n reason: string;\n source?: string;\n factId?: string;\n};\n\nexport type ActiveTaskProjectionStatus = {\n filePath: string;\n markerPath: string;\n exists: boolean;\n stale: boolean;\n staleReasons: string[];\n latestProjectFactAt: string | null;\n renderedAt: string | null;\n marker: ActiveTaskProjectionStaleMarker | null;\n};\n\nexport type ActiveTaskLedgerSelectionMetrics = {\n projectRowsFetched: number;\n rowsDroppedMissingEntityOrCanonical: number;\n groupedEntityCount: number;\n duplicateRowsCollapsed: number;\n activeCandidates: number;\n completedCandidates: number;\n entitiesSkippedWithoutStatus: number;\n elapsedMs: number;\n};\n\nexport type ActiveTaskCanonicalBackfillOptions = {\n dryRun?: boolean;\n limit?: number;\n};\n\nfunction rowToRecord(row: Map<string, MemoryEntry>): Record<string, string> {\n const o: Record<string, string> = {};\n for (const [k, e] of row) {\n const key = k === \"_body\" ? \"description\" : k;\n o[key] = e.value ?? e.text ?? \"\";\n }\n return o;\n}\n\nfunction parseIsoFromFactField(value: string | undefined): string | undefined {\n if (!value?.trim()) return undefined;\n const ms = Date.parse(value.trim());\n if (Number.isNaN(ms)) return undefined;\n return new Date(ms).toISOString();\n}\n\nfunction createdBoundsFromKeyMap(keyMap: Map<string, MemoryEntry>): { minSec: number; maxSec: number } | null {\n let minSec = Number.POSITIVE_INFINITY;\n let maxSec = Number.NEGATIVE_INFINITY;\n for (const e of keyMap.values()) {\n const c = e.createdAt;\n if (typeof c === \"number\" && Number.isFinite(c)) {\n minSec = Math.min(minSec, c);\n maxSec = Math.max(maxSec, c);\n }\n }\n if (minSec === Number.POSITIVE_INFINITY) return null;\n return { minSec, maxSec };\n}\n\nfunction resolveTaskStarted(f: Record<string, string>, bounds: ReturnType<typeof createdBoundsFromKeyMap>): string {\n return (\n parseIsoFromFactField(f.started) ??\n parseIsoFromFactField(f.task_started) ??\n parseIsoFromFactField(f.created_at) ??\n (bounds ? new Date(bounds.minSec * 1000).toISOString() : undefined) ??\n UNKNOWN_ACTIVE_TASK_TIME\n );\n}\n\nfunction resolveTaskUpdated(f: Record<string, string>, bounds: ReturnType<typeof createdBoundsFromKeyMap>): string {\n return (\n parseIsoFromFactField(f.task_updated) ??\n parseIsoFromFactField(f.updated) ??\n parseIsoFromFactField(f.updated_at) ??\n (bounds ? new Date(bounds.maxSec * 1000).toISOString() : undefined) ??\n UNKNOWN_ACTIVE_TASK_TIME\n );\n}\n\nfunction titleFromFacts(f: Record<string, string>): string {\n return f.title?.trim() || f.description?.trim() || f.summary?.trim() || \"Project task\";\n}\n\n/**\n * Build active + completed task entries from grouped project facts.\n */\nexport function buildTaskEntriesFromGroupedFacts(byEntity: Map<string, Map<string, MemoryEntry>>): {\n active: ActiveTaskEntry[];\n completed: ActiveTaskEntry[];\n} {\n const active: ActiveTaskEntry[] = [];\n const completed: ActiveTaskEntry[] = [];\n const sorted = [...byEntity.entries()].sort((a, b) => a[0].localeCompare(b[0]));\n\n for (const [entity, keyMap] of sorted) {\n const f = rowToRecord(keyMap);\n const bounds = createdBoundsFromKeyMap(keyMap);\n // Entities with no explicit `status` key must not default to in-progress.\n const statusRaw = f.status?.trim();\n if (!statusRaw) continue;\n const disp = factStatusToDisplay(statusRaw);\n const started = resolveTaskStarted(f, bounds);\n const updated = resolveTaskUpdated(f, bounds);\n let handoff: ActiveTaskEntry[\"handoff\"];\n if (f.handoff?.trim()) {\n try {\n handoff = JSON.parse(f.handoff.trim());\n } catch {\n // Ignore parse errors\n }\n }\n const entry: ActiveTaskEntry = {\n label: entity,\n description: titleFromFacts(f),\n status: disp,\n branch: f.branch?.trim() || undefined,\n stashCommit: f.stash_commit?.trim() || undefined,\n subagent: f.related_session?.trim() || undefined,\n next: f.next?.trim() || undefined,\n relatedGoal: f.related_goal?.trim() || f.goal_id?.trim() || undefined,\n started,\n updated,\n handoff,\n };\n if (isTerminalFactStatus(statusRaw) || disp === \"Done\") {\n completed.push({ ...entry, status: \"Done\" });\n } else {\n active.push(entry);\n }\n }\n\n return { active, completed };\n}\n\nexport function loadTaskLedgerFromFacts(\n factsDb: FactsDB,\n factLimit = 8000,\n scopeFilter?: ScopeFilter | null,\n): {\n active: ActiveTaskEntry[];\n completed: ActiveTaskEntry[];\n} {\n const { active, completed } = loadTaskLedgerFromFactsWithMetrics(factsDb, factLimit, scopeFilter);\n return { active, completed };\n}\n\nexport function loadTaskLedgerFromFactsWithMetrics(\n factsDb: FactsDB,\n factLimit = 8000,\n scopeFilter?: ScopeFilter | null,\n): {\n active: ActiveTaskEntry[];\n completed: ActiveTaskEntry[];\n metrics: ActiveTaskLedgerSelectionMetrics;\n} {\n const startedAtMs = Date.now();\n // Use targeted query instead of loading all facts then filtering by category (#1553)\n const facts = factsDb.getProjectFacts(factLimit, scopeFilter);\n // groupProjectFactsByEntity silently drops rows with no entity or no canonical label;\n // count those separately so duplicateRowsCollapsed only reflects true duplicates.\n const grouped = groupProjectFactsByEntity(facts);\n const { active, completed } = buildTaskEntriesFromGroupedFacts(grouped);\n let groupedFactRows = 0;\n for (const keyMap of grouped.values()) {\n groupedFactRows += keyMap.size;\n }\n // The rows fed to groupProjectFactsByEntity = facts that passed entity/canonical guard.\n // We infer dropped rows = facts.length - sum of sizes of all keyMaps.\n // But keyMaps only count unique (entity,key) pairs — so true duplicates are also in groupedFactRows.\n // Instead: track rows fed to grouping = only rows with entity AND canonicalLabel.\n // Use canonicalLabel directly to determine eligibility.\n const eligibleRows = facts.filter((f) => f.entity?.trim() && canonicalLabel(f.entity.trim())).length;\n const metrics: ActiveTaskLedgerSelectionMetrics = {\n projectRowsFetched: facts.length,\n rowsDroppedMissingEntityOrCanonical: facts.length - eligibleRows,\n groupedEntityCount: grouped.size,\n duplicateRowsCollapsed: Math.max(0, eligibleRows - groupedFactRows),\n activeCandidates: active.length,\n completedCandidates: completed.length,\n entitiesSkippedWithoutStatus: Math.max(0, grouped.size - active.length - completed.length),\n elapsedMs: Date.now() - startedAtMs,\n };\n return { active, completed, metrics };\n}\n\nexport function readActiveTaskRowsFromFacts(\n factsDb: FactsDB,\n staleMinutes: number,\n scopeFilter?: ScopeFilter | null,\n): { active: ActiveTaskEntry[]; completed: ActiveTaskEntry[]; latestProjectFactSec: number | null } {\n const { active, completed } = loadTaskLedgerFromFacts(factsDb, 8000, scopeFilter);\n const staleActive = detectStaleTasks(active, staleMinutes);\n const latestProjectFactSec = getLatestProjectFactCreatedAtSec(factsDb, scopeFilter);\n return { active: staleActive, completed, latestProjectFactSec };\n}\n\nexport function getActiveTaskProjectionStaleMarkerPath(filePath: string): string {\n return `${filePath}${PROJECTION_STALE_MARKER_SUFFIX}`;\n}\n\nfunction parseProjectionMarker(raw: string): ActiveTaskProjectionStaleMarker | null {\n try {\n const parsed = JSON.parse(raw) as Partial<ActiveTaskProjectionStaleMarker>;\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n typeof parsed.staleAt === \"string\" &&\n parsed.staleAt.trim().length > 0 &&\n typeof parsed.reason === \"string\" &&\n parsed.reason.trim().length > 0\n ) {\n return {\n staleAt: parsed.staleAt.trim(),\n reason: parsed.reason.trim(),\n ...(typeof parsed.source === \"string\" && parsed.source.trim().length > 0\n ? { source: parsed.source.trim() }\n : {}),\n ...(typeof parsed.factId === \"string\" && parsed.factId.trim().length > 0\n ? { factId: parsed.factId.trim() }\n : {}),\n };\n }\n } catch {\n // ignore malformed marker JSON\n }\n return null;\n}\n\nasync function readActiveTaskProjectionStaleMarker(filePath: string): Promise<ActiveTaskProjectionStaleMarker | null> {\n const markerPath = getActiveTaskProjectionStaleMarkerPath(filePath);\n try {\n const raw = await readFile(markerPath, \"utf-8\");\n return parseProjectionMarker(raw);\n } catch {\n return null;\n }\n}\n\nexport async function markActiveTaskProjectionStale(\n filePath: string,\n reason: string,\n meta: { source?: string; factId?: string } = {},\n): Promise<void> {\n const markerPath = getActiveTaskProjectionStaleMarkerPath(filePath);\n const marker: ActiveTaskProjectionStaleMarker = {\n staleAt: new Date().toISOString(),\n reason: reason.trim().length > 0 ? reason.trim() : \"projection marked stale\",\n ...(meta.source ? { source: meta.source } : {}),\n ...(meta.factId ? { factId: meta.factId } : {}),\n };\n await mkdir(dirname(markerPath), { recursive: true });\n await writeFile(markerPath, `${JSON.stringify(marker, null, 2)}\\n`, \"utf-8\");\n}\n\nexport async function clearActiveTaskProjectionStale(filePath: string): Promise<void> {\n await rm(getActiveTaskProjectionStaleMarkerPath(filePath), { force: true });\n}\n\nexport function getLatestProjectFactCreatedAtSec(factsDb: FactsDB, scopeFilter?: ScopeFilter | null): number | null {\n // Query all project facts (any source) to detect staleness from any project fact updates.\n // Note: getProjectFacts filters by source='active-task', but we need to detect updates\n // from all sources (e.g., memory_store) to properly mark projections as stale.\n const projectFacts = factsDb\n .getAll({ scopeFilter })\n .filter((fact) => fact.category === TASK_LEDGER_CATEGORY)\n .slice(0, 8000);\n if (projectFacts.length === 0) return null;\n let maxSec = Number.NEGATIVE_INFINITY;\n for (const fact of projectFacts) {\n if (typeof fact.createdAt === \"number\" && Number.isFinite(fact.createdAt)) {\n maxSec = Math.max(maxSec, fact.createdAt);\n }\n }\n return maxSec === Number.NEGATIVE_INFINITY ? null : maxSec;\n}\n\nfunction toIsoOrNull(unixSeconds: number | null): string | null {\n if (unixSeconds === null) return null;\n return new Date(unixSeconds * 1000).toISOString();\n}\n\nexport async function getActiveTaskProjectionStatus(\n factsDb: FactsDB,\n filePath: string,\n opts: { scopeFilter?: ScopeFilter | null; latestProjectFactSec?: number | null } = {},\n): Promise<ActiveTaskProjectionStatus> {\n const markerPath = getActiveTaskProjectionStaleMarkerPath(filePath);\n const marker = await readActiveTaskProjectionStaleMarker(filePath);\n const latestProjectFactSec = opts.latestProjectFactSec ?? getLatestProjectFactCreatedAtSec(factsDb, opts.scopeFilter);\n const latestProjectFactAt = toIsoOrNull(latestProjectFactSec);\n\n let exists = false;\n let renderedAt: string | null = null;\n let renderedMs: number | null = null;\n try {\n const fileStat = await stat(filePath);\n exists = fileStat.isFile();\n if (exists) {\n renderedMs = fileStat.mtimeMs;\n renderedAt = new Date(fileStat.mtimeMs).toISOString();\n }\n } catch {\n // no projection file\n }\n\n const staleReasons = new Set<string>();\n if (latestProjectFactSec !== null && !exists) {\n staleReasons.add(\"projection_missing\");\n }\n if (latestProjectFactSec !== null && renderedMs !== null && latestProjectFactSec * 1000 > renderedMs + 1000) {\n staleReasons.add(\"project_facts_newer_than_projection\");\n }\n if (marker) {\n staleReasons.add(\"projection_marked_stale\");\n }\n\n return {\n filePath,\n markerPath,\n exists,\n stale: staleReasons.size > 0,\n staleReasons: [...staleReasons],\n latestProjectFactAt,\n renderedAt,\n marker,\n };\n}\n\nexport async function refreshActiveTaskProjectionBestEffort(opts: {\n factsDb: FactsDB;\n staleMinutes: number;\n filePath: string;\n projection: ActiveTaskProjectionConfig;\n reason: string;\n source?: string;\n factId?: string;\n logger?: { warn?: (m: string) => void };\n}): Promise<{ rendered: boolean; staleMarked: boolean; error?: string }> {\n try {\n await renderActiveTaskMarkdownFile(opts.factsDb, opts.staleMinutes, opts.filePath, opts.projection);\n return { rendered: true, staleMarked: false };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n let staleMarked = false;\n try {\n await markActiveTaskProjectionStale(opts.filePath, opts.reason, { source: opts.source, factId: opts.factId });\n staleMarked = true;\n } catch {\n // Ignore stale marker write failures (best-effort)\n }\n opts.logger?.warn?.(`memory-hybrid: active-task projection refresh failed: ${message}`);\n return { rendered: false, staleMarked, error: message };\n }\n}\n\n/** Normalize description for `dedupeBy: normalizedTitle`. */\nfunction normalizeActiveTaskTitleForDedupe(description: string): string {\n return description.trim().toLowerCase().replace(/\\s+/g, \" \");\n}\n\n/** Filter/dedupe rows for facts markdown projection (`readable` mode). */\nexport function applyActiveTaskProjectionFilters(\n entries: ActiveTaskEntry[],\n config: ActiveTaskProjectionConfig,\n): ActiveTaskEntry[] {\n if (config.mode === \"full\") {\n return entries;\n }\n let out = entries;\n if (config.excludeGenericTitle) {\n out = out.filter((e) => e.description.trim() !== \"Project task\");\n }\n if (config.titleMinChars > 0) {\n out = out.filter((e) => e.description.trim().length >= config.titleMinChars);\n }\n if (config.dedupeBy === \"none\") {\n return out;\n }\n const seen = new Set<string>();\n const result: ActiveTaskEntry[] = [];\n for (const e of out) {\n const key =\n config.dedupeBy === \"label\" ? e.label.trim().toLowerCase() : normalizeActiveTaskTitleForDedupe(e.description);\n if (seen.has(key)) continue;\n seen.add(key);\n result.push(e);\n }\n return result;\n}\n\nexport interface ActiveTaskHygieneDuplicateGroup {\n normalized: string;\n canonicalLabel: string;\n labels: string[];\n}\n\nexport interface ActiveTaskHygieneStaleCandidate {\n label: string;\n status: ActiveTaskStatus;\n updated: string;\n hoursStale: number | \"?\";\n reason: string;\n}\n\nexport type ActiveTaskHygieneActionKind = \"dead-session\" | \"stale-failed\" | \"superseded-duplicate\" | \"pr-live-blocker\";\n\nexport interface ActiveTaskHygieneAction {\n label: string;\n kind: ActiveTaskHygieneActionKind;\n toStatus: \"abandoned\" | \"superseded\" | \"stage-4-feedback\";\n reason: string;\n canonicalLabel?: string;\n /** Set when kind is \"pr-live-blocker\" — the actual GitHub-classified blocker */\n prBlockerStatus?: string;\n}\n\nexport interface ActiveTaskHygienePlan {\n olderThanMinutes: number;\n duplicates: ActiveTaskHygieneDuplicateGroup[];\n stale: ActiveTaskHygieneStaleCandidate[];\n actions: ActiveTaskHygieneAction[];\n}\n\nfunction parseTaskUpdatedMs(updated: string): number | null {\n if (!updated || updated === UNKNOWN_ACTIVE_TASK_TIME) return null;\n const ms = Date.parse(updated);\n if (Number.isNaN(ms)) return null;\n return ms;\n}\n\nfunction normalizeTaskString(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/[_\\-.:/]+/g, \" \")\n .replace(/[^a-z0-9\\s]/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nfunction isGenericTaskTitle(value: string): boolean {\n const normalized = normalizeTaskString(value);\n if (!normalized) return true;\n return GENERIC_TASK_TITLE_NORMALIZED.has(normalized);\n}\n\nfunction staleHoursFromUpdated(updated: string, nowMs: number): number | \"?\" {\n const ms = parseTaskUpdatedMs(updated);\n if (ms == null) return \"?\";\n return Math.floor((nowMs - ms) / (60 * 60 * 1000));\n}\n\nfunction statusRankForCanonical(status: ActiveTaskStatus): number {\n switch (status) {\n case \"In progress\":\n return 0;\n case \"Waiting\":\n return 1;\n case \"Stalled\":\n return 2;\n case \"Failed\":\n return 3;\n default:\n return 4;\n }\n}\n\nfunction buildDuplicateComponents(tasks: ActiveTaskEntry[]): number[][] {\n const byLabel = new Map<string, number[]>();\n const byTitle = new Map<string, number[]>();\n for (let i = 0; i < tasks.length; i++) {\n const labelKey = normalizeTaskString(tasks[i].label);\n const titleKey = normalizeTaskString(tasks[i].description);\n if (labelKey) {\n const arr = byLabel.get(labelKey) ?? [];\n arr.push(i);\n byLabel.set(labelKey, arr);\n }\n if (titleKey && !isGenericTaskTitle(tasks[i].description)) {\n const arr = byTitle.get(titleKey) ?? [];\n arr.push(i);\n byTitle.set(titleKey, arr);\n }\n }\n\n const edges = new Map<number, Set<number>>();\n const linkGroup = (indices: number[]): void => {\n if (indices.length < 2) return;\n const root = indices[0];\n let rootSet = edges.get(root);\n if (!rootSet) {\n rootSet = new Set<number>();\n edges.set(root, rootSet);\n }\n for (let i = 1; i < indices.length; i++) {\n const idx = indices[i];\n rootSet.add(idx);\n let setI = edges.get(idx);\n if (!setI) {\n setI = new Set<number>();\n edges.set(idx, setI);\n }\n setI.add(root);\n }\n };\n\n for (const indices of byLabel.values()) linkGroup(indices);\n for (const indices of byTitle.values()) linkGroup(indices);\n\n const components: number[][] = [];\n const seen = new Set<number>();\n for (const idx of edges.keys()) {\n if (seen.has(idx)) continue;\n const stack = [idx];\n const comp: number[] = [];\n while (stack.length > 0) {\n const cur = stack.pop() as number;\n if (seen.has(cur)) continue;\n seen.add(cur);\n comp.push(cur);\n for (const n of edges.get(cur) ?? []) {\n if (!seen.has(n)) stack.push(n);\n }\n }\n if (comp.length > 1) components.push(comp);\n }\n return components;\n}\n\nexport async function planActiveTaskHygiene(\n tasks: ActiveTaskEntry[],\n opts: {\n olderThanMinutes: number;\n nowMs?: number;\n openclawHome?: string;\n checkSessionPresent?: (sessionRef: string) => Promise<boolean>;\n /** When true, fetch live PR state for tasks whose labels contain an \"owner/repo#N\" PR reference. */\n checkPrLiveBlocker?: boolean;\n },\n): Promise<ActiveTaskHygienePlan> {\n const nowMs = opts.nowMs ?? Date.now();\n const olderThanMinutes = Math.max(1, Math.floor(opts.olderThanMinutes));\n const olderThanMs = olderThanMinutes * 60 * 1000;\n const classifyAge = (\n task: ActiveTaskEntry,\n ): {\n stale: boolean;\n reasonQualifier: string;\n } => {\n const updatedMs = parseTaskUpdatedMs(task.updated);\n if (updatedMs == null) {\n return {\n stale: true,\n reasonQualifier: \"missing/unknown updated timestamp\",\n };\n }\n return {\n stale: nowMs - updatedMs > olderThanMs,\n reasonQualifier: `older than ${olderThanMinutes}m`,\n };\n };\n const checkSessionPresent =\n opts.checkSessionPresent ??\n ((sessionRef: string): Promise<boolean> => isOpenClawSessionLikelyPresent(sessionRef, opts.openclawHome));\n\n const stale: ActiveTaskHygieneStaleCandidate[] = [];\n const actionsByLabel = new Map<string, ActiveTaskHygieneAction>();\n\n for (const task of tasks) {\n if (task.status !== \"Failed\") continue;\n const age = classifyAge(task);\n if (!age.stale) continue;\n const reason = `Failed task ${age.reasonQualifier}; marking abandoned.`;\n stale.push({\n label: task.label,\n status: task.status,\n updated: task.updated,\n hoursStale: staleHoursFromUpdated(task.updated, nowMs),\n reason,\n });\n actionsByLabel.set(task.label, {\n label: task.label,\n kind: \"stale-failed\",\n toStatus: \"abandoned\",\n reason,\n });\n }\n\n const staleSessionCandidates = tasks\n .filter((task) => task.status === \"In progress\")\n .map((task) => ({ task, sessionRef: task.subagent?.trim() ?? \"\" }))\n .filter(({ sessionRef }) => sessionRef.length > 0 && looksLikeOpenClawSessionRef(sessionRef))\n .map(({ task, sessionRef }) => ({ task, sessionRef, age: classifyAge(task) }))\n .filter(({ age }) => age.stale);\n const uniqueSessionRefs = [...new Set(staleSessionCandidates.map((c) => c.sessionRef))];\n const sessionPresentByRef = new Map<string, boolean>();\n await Promise.all(\n uniqueSessionRefs.map(async (sessionRef) => {\n const present = await checkSessionPresent(sessionRef);\n sessionPresentByRef.set(sessionRef, present);\n }),\n );\n for (const { task, sessionRef, age } of staleSessionCandidates) {\n const present = sessionPresentByRef.get(sessionRef) ?? false;\n if (present) continue;\n const reason = `In-progress task has missing session transcript (${sessionRef}) and is ${age.reasonQualifier}; marking abandoned.`;\n stale.push({\n label: task.label,\n status: task.status,\n updated: task.updated,\n hoursStale: staleHoursFromUpdated(task.updated, nowMs),\n reason,\n });\n actionsByLabel.set(task.label, {\n label: task.label,\n kind: \"dead-session\",\n toStatus: \"abandoned\",\n reason,\n });\n }\n\n // -----------------------------------------------------------------------------------------\n // PR live blocker check: verify tasks with \"In progress\" / \"Waiting\" / \"Stalled\" status\n // actually have a live GitHub blocker before classifying them as blocked/waiting.\n // This prevents hygiene from incorrectly marking PR-backed tasks as stalled when\n // mergeStateStatus says BLOCKED but unresolved review threads exist.\n // -----------------------------------------------------------------------------------------\n if (opts.checkPrLiveBlocker === true) {\n // Collect tasks that reference a PR (owner/repo#N pattern in label or description)\n const PR_REF_RE = /(?:^|[\\s/])([a-zA-Z0-9_.-]+\\/[a-zA-Z0-9_.-]+)#(\\d+)/;\n const prCandidateTasks = tasks.filter((t) => {\n if (t.status !== \"In progress\" && t.status !== \"Waiting\" && t.status !== \"Stalled\") return false;\n const text = `${t.label} ${t.description}`;\n return PR_REF_RE.test(text);\n });\n\n if (prCandidateTasks.length > 0) {\n // Group by owner/repo to avoid duplicate API calls for the same repo\n const prKeys = new Map<string, { tasks: ActiveTaskEntry[]; owner: string; repo: string; number: number }>();\n for (const task of prCandidateTasks) {\n const m = PR_REF_RE.exec(`${task.label} ${task.description}`);\n if (!m) continue;\n const key = `${m[1]}#${m[2]}`;\n const existing = prKeys.get(key);\n if (existing) {\n existing.tasks.push(task);\n } else {\n const [owner, repo] = m[1].split(\"/\");\n prKeys.set(key, { tasks: [task], owner, repo, number: Number(m[2]) });\n }\n }\n\n // Fetch live blocker status for each unique PR (parallel, deduped)\n const prStatuses = await Promise.all(\n [...prKeys.values()].map(async ({ owner, repo, number }) => {\n const status = await fetchLivePrBlockerStatus(owner, repo, number);\n return { owner, repo, number, status };\n }),\n );\n const statusByKey = new Map(prStatuses.map((p) => [`${p.owner}/${p.repo}#${p.number}`, p]));\n\n for (const { tasks, owner, repo, number } of prKeys.values()) {\n const blockerStatus = statusByKey.get(`${owner}/${repo}#${number}`);\n if (!blockerStatus) continue;\n\n if (blockerStatus.status !== \"no_live_blocker\") {\n const key = `${owner}/${repo}`;\n const reason = `[PR hygiene #${number}] Live GitHub state: ${blockerStatus.status} — task updated to reflect actual PR state.`;\n\n // Apply action to ALL tasks referencing this PR\n for (const task of tasks) {\n // Skip if already handled by a dead-session or stale-failed action\n if (actionsByLabel.has(task.label)) continue;\n\n stale.push({\n label: task.label,\n status: task.status,\n updated: task.updated,\n hoursStale: staleHoursFromUpdated(task.updated, nowMs),\n reason,\n });\n actionsByLabel.set(task.label, {\n label: task.label,\n kind: \"pr-live-blocker\",\n toStatus: \"stage-4-feedback\",\n reason,\n // Store actual blocker classification, plus PR coordinates for parsing\n prBlockerStatus: `${blockerStatus.status}|${owner}:${repo}:${number}`,\n });\n }\n }\n }\n }\n }\n\n const duplicates: ActiveTaskHygieneDuplicateGroup[] = [];\n const components = buildDuplicateComponents(tasks);\n for (const comp of components) {\n const members = comp.map((idx) => tasks[idx]);\n const ranked = [...members].sort((a, b) => {\n const actionA = actionsByLabel.has(a.label) ? 1 : 0;\n const actionB = actionsByLabel.has(b.label) ? 1 : 0;\n if (actionA !== actionB) return actionA - actionB;\n const rankDiff = statusRankForCanonical(a.status) - statusRankForCanonical(b.status);\n if (rankDiff !== 0) return rankDiff;\n const updA = parseTaskUpdatedMs(a.updated) ?? Number.NEGATIVE_INFINITY;\n const updB = parseTaskUpdatedMs(b.updated) ?? Number.NEGATIVE_INFINITY;\n if (updA !== updB) return updB - updA;\n if (a.label.length !== b.label.length) return a.label.length - b.label.length;\n return a.label.localeCompare(b.label);\n });\n const canonical = ranked[0];\n const normalized = normalizeTaskString(canonical.description) || normalizeTaskString(canonical.label);\n const labels = ranked.map((m) => m.label);\n duplicates.push({\n normalized,\n canonicalLabel: canonical.label,\n labels,\n });\n for (const row of ranked.slice(1)) {\n if (actionsByLabel.has(row.label)) continue;\n actionsByLabel.set(row.label, {\n label: row.label,\n kind: \"superseded-duplicate\",\n toStatus: \"superseded\",\n canonicalLabel: canonical.label,\n reason: `Superseded duplicate of [${canonical.label}] after normalization.`,\n });\n }\n }\n\n stale.sort((a, b) => a.label.localeCompare(b.label));\n duplicates.sort((a, b) => a.canonicalLabel.localeCompare(b.canonicalLabel));\n const actions = [...actionsByLabel.values()].sort((a, b) => a.label.localeCompare(b.label));\n return {\n olderThanMinutes,\n duplicates,\n stale,\n actions,\n };\n}\n\nfunction capRows<T extends ActiveTaskEntry>(rows: T[], max?: number): { rows: T[]; omitted: number } {\n if (max === undefined || rows.length <= max) return { rows, omitted: 0 };\n return { rows: rows.slice(0, max), omitted: rows.length - max };\n}\n\n/** Sectioned markdown for facts projection (## Active / ## Stale — revisit / ## Completed). */\nexport function buildFactsSectionedMarkdownBody(\n hot: ActiveTaskEntry[],\n stale: ActiveTaskEntry[],\n completed: ActiveTaskEntry[],\n omitted: { active: number; stale: number; completed: number },\n): string {\n if (\n hot.length === 0 &&\n stale.length === 0 &&\n completed.length === 0 &&\n omitted.active === 0 &&\n omitted.stale === 0 &&\n omitted.completed === 0\n ) {\n return \"# ACTIVE-TASKS.md — Working Memory\\n\\n## Active\\n\\n_No active tasks._\\n\";\n }\n\n const parts: string[] = [\"# ACTIVE-TASKS.md — Working Memory\\n\"];\n\n if (hot.length > 0 || omitted.active > 0) {\n parts.push(\"## Active\\n\");\n if (hot.length === 0 && omitted.active === 0) {\n parts.push(\"_None._\\n\");\n } else {\n for (const entry of hot) {\n parts.push(serializeTaskEntry(entry));\n parts.push(\"\");\n }\n }\n if (omitted.active > 0) {\n parts.push(`> ${omitted.active} ${OMITTED_CAP_NOTE}\\n\\n`);\n } else if (hot.length > 0) {\n parts.push(\"\");\n }\n }\n\n if (stale.length > 0 || omitted.stale > 0) {\n parts.push(\"## Stale — revisit\\n\");\n if (stale.length === 0 && omitted.stale === 0) {\n parts.push(\"_None._\\n\");\n } else {\n for (const entry of stale) {\n parts.push(serializeTaskEntry(entry));\n parts.push(\"\");\n }\n }\n if (omitted.stale > 0) {\n parts.push(`> ${omitted.stale} ${OMITTED_CAP_NOTE}\\n\\n`);\n } else if (stale.length > 0) {\n parts.push(\"\");\n }\n }\n\n if (completed.length > 0 || omitted.completed > 0) {\n parts.push(\"## Completed\\n\");\n for (const entry of completed) {\n parts.push(serializeTaskEntry(entry));\n parts.push(\"\");\n }\n if (omitted.completed > 0) {\n parts.push(`> ${omitted.completed} ${OMITTED_CAP_NOTE}\\n`);\n }\n }\n\n return parts.join(\"\\n\");\n}\n\nexport function taskEntityKey(entity: string, key: string): string {\n return `${canonicalLabel(entity)}\\u0000${key.trim()}`;\n}\n\nexport async function upsertProjectTaskKey(\n factsDb: FactsDB,\n vectorDb: VectorDB,\n embeddings: EmbeddingProvider,\n entity: string,\n key: string,\n value: string,\n log?: { warn?: (m: string) => void },\n opts?: { latestByEntityKey?: Map<string, MemoryEntry> },\n): Promise<void> {\n // Normalise entity labels on write (trim + lowercase) to prevent case-variant\n // collisions (e.g. \"Humanizer\" and \"humanizer\" stored as separate entities).\n const normalizedEntity = entity.trim().toLowerCase();\n const canonical = canonicalLabel(normalizedEntity);\n const cacheKey = taskEntityKey(normalizedEntity, key);\n let previous: MemoryEntry | undefined;\n const cached = opts?.latestByEntityKey?.get(cacheKey);\n // Keep supersession scoped to active-task ledger rows only; cached\n // memory_store rows must never be retired by active-task checkpoints.\n if (cached?.source === \"active-task\") {\n previous = cached;\n } else if (!opts?.latestByEntityKey || cached || key === \"status\") {\n // Query database if: (a) no cache was provided, or (b) cache had a non-active-task\n // entry (which we must not supersede, but an active-task row may still exist), or\n // (c) this is a status write and cache missed (status must not become append-only).\n const facts = factsDb.listFactsByCategory(TASK_LEDGER_CATEGORY, 8000);\n const nowSec = Math.floor(Date.now() / 1000);\n // Supersede by canonical label to match grouping logic (strips suffixes, normalizes separators).\n // Only supersede facts from the active-task ledger (source:\"active-task\"), not memory_store.\n // Exclude expired facts to match projection logic (groupProjectFactsByEntity).\n const same = facts.filter(\n (f) =>\n f.source === \"active-task\" &&\n canonicalLabel(f.entity ?? \"\") === canonical &&\n (f.key ?? \"\") === key &&\n !(typeof f.expiresAt === \"number\" && Number.isFinite(f.expiresAt) && f.expiresAt <= nowSec),\n );\n same.sort((a, b) => b.createdAt - a.createdAt);\n previous = same[0];\n }\n const text = `Task [${normalizedEntity}] ${key}: ${value}`;\n const entry = factsDb.store({\n text,\n category: TASK_LEDGER_CATEGORY,\n importance: CLI_STORE_IMPORTANCE,\n entity: normalizedEntity,\n key,\n value,\n source: \"active-task\",\n decayClass: \"permanent\",\n provenanceJson: activeTaskProvenance(canonical),\n });\n if (previous) {\n factsDb.supersede(previous.id, entry.id);\n }\n opts?.latestByEntityKey?.set(cacheKey, entry);\n try {\n const vector = await embeddings.embed(text);\n factsDb.setEmbeddingModel(entry.id, embeddings.modelName);\n if (!(await vectorDb.hasDuplicate(vector))) {\n await vectorDb.store({\n text,\n vector,\n importance: CLI_STORE_IMPORTANCE,\n category: TASK_LEDGER_CATEGORY,\n id: entry.id,\n });\n }\n } catch (err) {\n log?.warn?.(`memory-hybrid: active-task ledger vector store failed: ${err}`);\n }\n}\n\n/** Persist one task row to project facts (multi-key upsert). */\nexport async function syncActiveTaskEntryToFacts(\n factsDb: FactsDB,\n vectorDb: VectorDB,\n embeddings: EmbeddingProvider,\n entry: ActiveTaskEntry,\n log?: { warn?: (m: string) => void },\n opts?: {\n statusOverride?: string;\n latestByEntityKey?: Map<string, MemoryEntry>;\n },\n): Promise<void> {\n const entity = entry.label;\n const upsertOpts = { latestByEntityKey: opts?.latestByEntityKey };\n await upsertProjectTaskKey(factsDb, vectorDb, embeddings, entity, \"title\", entry.description, log, upsertOpts);\n const statusValue = opts?.statusOverride?.trim() || displayStatusToFact(entry.status);\n await upsertProjectTaskKey(factsDb, vectorDb, embeddings, entity, \"status\", statusValue, log, upsertOpts);\n await upsertProjectTaskKey(factsDb, vectorDb, embeddings, entity, \"next\", entry.next?.trim() || \"\", log, upsertOpts);\n await upsertProjectTaskKey(\n factsDb,\n vectorDb,\n embeddings,\n entity,\n \"related_session\",\n entry.subagent?.trim() || \"\",\n log,\n upsertOpts,\n );\n await upsertProjectTaskKey(factsDb, vectorDb, embeddings, entity, \"task_updated\", entry.updated, log, upsertOpts);\n await upsertProjectTaskKey(factsDb, vectorDb, embeddings, entity, \"started\", entry.started, log, upsertOpts);\n await upsertProjectTaskKey(\n factsDb,\n vectorDb,\n embeddings,\n entity,\n \"branch\",\n entry.branch?.trim() || \"\",\n log,\n upsertOpts,\n );\n await upsertProjectTaskKey(\n factsDb,\n vectorDb,\n embeddings,\n entity,\n \"stash_commit\",\n entry.stashCommit?.trim() || \"\",\n log,\n upsertOpts,\n );\n await upsertProjectTaskKey(\n factsDb,\n vectorDb,\n embeddings,\n entity,\n \"handoff\",\n entry.handoff ? JSON.stringify(entry.handoff) : \"\",\n log,\n upsertOpts,\n );\n}\n\nexport interface ActiveTaskHygieneApplyResult {\n appliedCount: number;\n auditFactId?: string;\n /** Tasks whose live PR state has an actionable blocker (unresolved threads, red CI, etc.) */\n prBlockerTasks?: Array<{\n label: string;\n owner: string;\n repo: string;\n number: number;\n blockerStatus: string;\n reason: string;\n }>;\n}\n\nexport interface ActiveTaskCanonicalBackfillResult {\n scannedFacts: number;\n canonicalLabelsUpdated: number;\n supersededFacts: number;\n duplicateGroups: Array<{ canonicalLabel: string; facts: number; activeBefore: number; superseded: number }>;\n}\n\nexport function backfillActiveTaskCanonicalLabels(\n factsDb: FactsDB,\n opts: ActiveTaskCanonicalBackfillOptions = {},\n): ActiveTaskCanonicalBackfillResult {\n const facts = factsDb.listFactsByCategory(TASK_LEDGER_CATEGORY, opts.limit ?? 50_000);\n const groups = new Map<string, MemoryEntry[]>();\n let canonicalLabelsUpdated = 0;\n\n const verifiedLookup = (() => {\n try {\n return factsDb.getRawDb().prepare(\"SELECT 1 FROM verified_facts WHERE fact_id = ? LIMIT 1\");\n } catch {\n return null;\n }\n })();\n\n for (const fact of facts) {\n if (!fact.entity?.trim()) continue;\n const canonical = factCanonicalLabel(fact);\n const existing = readCanonicalLabelFromFact(fact);\n if (existing !== canonical) {\n canonicalLabelsUpdated++;\n if (!opts.dryRun) {\n const rawDb = (\n factsDb as unknown as {\n getRawDb?: () => { prepare: (sql: string) => { run: (...args: unknown[]) => unknown } };\n }\n ).getRawDb?.();\n rawDb\n ?.prepare(\"UPDATE facts SET provenance_json = ? WHERE id = ?\")\n .run(activeTaskProvenance(canonical, fact.provenanceJson), fact.id);\n }\n }\n const arr = groups.get(canonical) ?? [];\n arr.push(fact);\n groups.set(canonical, arr);\n }\n\n const duplicateGroups: ActiveTaskCanonicalBackfillResult[\"duplicateGroups\"] = [];\n let supersededFacts = 0;\n const nowSec = Math.floor(Date.now() / 1000);\n for (const [canonical, rows] of groups) {\n const activeBefore = rows.filter((f) => !f.supersededAt).length;\n let groupSuperseded = 0;\n const byKey = new Map<string, MemoryEntry[]>();\n for (const row of rows) {\n const key = (row.key ?? \"\").trim() || \"_body\";\n const arr = byKey.get(key) ?? [];\n arr.push(row);\n byKey.set(key, arr);\n }\n let terminalStatus: MemoryEntry | undefined;\n const statusRows = byKey.get(\"status\") ?? [];\n for (const row of statusRows) {\n const isExpired = row.expiresAt !== null && row.expiresAt <= nowSec;\n if (\n !isExpired &&\n !row.supersededAt &&\n isTerminalFactStatus(row.value ?? row.text ?? \"\") &&\n (!terminalStatus || row.createdAt > terminalStatus.createdAt)\n ) {\n terminalStatus = row;\n }\n }\n const supersede = (oldId: string, newId: string | null): void => {\n if (verifiedLookup?.get(oldId)) {\n return;\n }\n supersededFacts++;\n groupSuperseded++;\n if (!opts.dryRun) factsDb.supersede(oldId, newId);\n };\n if (terminalStatus) {\n for (const [key, sameKey] of byKey) {\n const ranked = [...sameKey].sort((a, b) => b.createdAt - a.createdAt);\n const keeper = key === \"status\" ? terminalStatus : ranked[0];\n for (const row of ranked) {\n if (row.id !== keeper.id && !row.supersededAt) supersede(row.id, keeper.id);\n }\n }\n } else {\n for (const sameKey of byKey.values()) {\n const ranked = [...sameKey].sort((a, b) => b.createdAt - a.createdAt);\n const keeper = ranked[0];\n for (const row of ranked.slice(1)) {\n if (!row.supersededAt) supersede(row.id, keeper.id);\n }\n }\n }\n if (activeBefore > 1 || groupSuperseded > 0) {\n duplicateGroups.push({\n canonicalLabel: canonical,\n facts: rows.length,\n activeBefore,\n superseded: groupSuperseded,\n });\n }\n }\n\n if (!opts.dryRun) {\n (factsDb as unknown as { invalidateSupersededTextsCache?: () => void }).invalidateSupersededTextsCache?.();\n }\n\n return {\n scannedFacts: facts.length,\n canonicalLabelsUpdated,\n supersededFacts,\n duplicateGroups: duplicateGroups.sort((a, b) => a.canonicalLabel.localeCompare(b.canonicalLabel)),\n };\n}\n\nexport async function applyActiveTaskHygieneFacts(\n factsDb: FactsDB,\n vectorDb: VectorDB,\n embeddings: EmbeddingProvider,\n plan: ActiveTaskHygienePlan,\n opts: {\n flushOnComplete?: boolean;\n memoryDir?: string;\n log?: { warn?: (m: string) => void };\n } = {},\n): Promise<ActiveTaskHygieneApplyResult> {\n if (plan.actions.length === 0) {\n return { appliedCount: 0 };\n }\n\n const runAt = new Date().toISOString();\n const { active } = loadTaskLedgerFromFacts(factsDb);\n const byLabel = new Map(active.map((task) => [task.label, task] as const));\n const latestByEntityKey = new Map<string, MemoryEntry>();\n const nowSec = Math.floor(Date.now() / 1000);\n // Only index active-task ledger facts, not memory_store project facts.\n // Exclude expired facts to match projection logic (groupProjectFactsByEntity).\n for (const fact of factsDb.listFactsByCategory(TASK_LEDGER_CATEGORY, 8000)) {\n if (fact.source !== \"active-task\") continue;\n if (typeof fact.expiresAt === \"number\" && Number.isFinite(fact.expiresAt) && fact.expiresAt <= nowSec) continue;\n const entity = fact.entity?.trim().toLowerCase();\n if (!entity) continue;\n const key = (fact.key ?? \"\").trim();\n const cacheKey = taskEntityKey(entity, key);\n const prev = latestByEntityKey.get(cacheKey);\n if (!prev || fact.createdAt > prev.createdAt) {\n latestByEntityKey.set(cacheKey, fact);\n }\n }\n let appliedCount = 0;\n const prBlockerTasks: ActiveTaskHygieneApplyResult[\"prBlockerTasks\"] = [];\n\n for (const action of plan.actions) {\n const task = byLabel.get(action.label);\n if (!task) continue;\n\n if (action.kind === \"pr-live-blocker\") {\n // Update task in-place: set status to \"Waiting\" and record the live blocker reason.\n // Do NOT complete the task — it is actively blocked and needs Forge attention.\n const updatedEntry: ActiveTaskEntry = {\n ...task,\n status: \"Waiting\",\n updated: runAt,\n next: action.reason,\n // Keep subagent so we know who is working on it\n subagent: task.subagent,\n };\n await syncActiveTaskEntryToFacts(factsDb, vectorDb, embeddings, updatedEntry, opts.log, {\n statusOverride: displayStatusToFact(\"Waiting\"),\n latestByEntityKey,\n });\n // Record so caller can dispatch Forge for these tasks\n const parsed = action.prBlockerStatus ?? \"\";\n const [actualStatus, prRef] = parsed.includes(\"|\") ? parsed.split(\"|\", 2) : [\"\", parsed];\n const prParts = prRef.split(\":\");\n prBlockerTasks.push({\n label: task.label,\n owner: prParts[0] ?? \"\",\n repo: prParts[1] ?? \"\",\n number: Number(prParts[2] ?? 0) || 0,\n blockerStatus: actualStatus,\n reason: action.reason,\n });\n appliedCount++;\n continue;\n }\n\n const doneEntry: ActiveTaskEntry = {\n ...task,\n status: \"Done\",\n updated: runAt,\n next: action.reason,\n subagent: action.kind === \"dead-session\" ? undefined : task.subagent,\n };\n await syncActiveTaskEntryToFacts(factsDb, vectorDb, embeddings, doneEntry, opts.log, {\n statusOverride: action.toStatus,\n latestByEntityKey,\n });\n if (action.kind === \"superseded-duplicate\" && action.canonicalLabel) {\n await upsertProjectTaskKey(\n factsDb,\n vectorDb,\n embeddings,\n doneEntry.label,\n \"superseded_by\",\n action.canonicalLabel,\n opts.log,\n { latestByEntityKey },\n );\n }\n if (opts.flushOnComplete && opts.memoryDir) {\n await flushCompletedTaskToMemory(doneEntry, opts.memoryDir).catch(() => {});\n }\n appliedCount++;\n }\n\n const audit = {\n runAt,\n olderThanMinutes: plan.olderThanMinutes,\n duplicates: plan.duplicates,\n stale: plan.stale,\n actions: plan.actions,\n appliedCount,\n };\n const auditFact = factsDb.store({\n text: `Active-task hygiene audit ${runAt}: ${plan.actions.length} action(s), ${plan.duplicates.length} duplicate group(s), ${appliedCount} applied.`,\n category: \"episode\",\n importance: CLI_STORE_IMPORTANCE,\n source: \"active-task-hygiene\",\n decayClass: \"permanent\",\n entity: `active-task-hygiene:${runAt}`,\n key: \"report\",\n value: JSON.stringify(audit),\n });\n\n return { appliedCount, auditFactId: auditFact.id, prBlockerTasks };\n}\n\nexport async function renderActiveTaskMarkdownFile(\n factsDb: FactsDB,\n staleMinutes: number,\n filePath: string,\n projection: ActiveTaskProjectionConfig,\n logger?: { debug?: (message: string) => void },\n opts: { includeCompleted?: boolean } = {},\n): Promise<void> {\n const renderStartMs = Date.now();\n const includeCompleted = opts.includeCompleted === true;\n let { active, completed, metrics } = loadTaskLedgerFromFactsWithMetrics(\n factsDb,\n 8000,\n ACTIVE_TASK_PROJECTION_GLOBAL_SCOPE_FILTER,\n );\n active = applyActiveTaskProjectionFilters(active, projection);\n completed = includeCompleted ? applyActiveTaskProjectionFilters(completed, projection) : [];\n active = detectStaleTasks(active, staleMinutes);\n\n const hotRaw = active.filter((t) => !t.stale);\n const staleRaw = active.filter((t) => t.stale);\n\n let capAct: { rows: ActiveTaskEntry[]; omitted: number };\n let capStale: { rows: ActiveTaskEntry[]; omitted: number };\n let capDone: { rows: ActiveTaskEntry[]; omitted: number };\n let combinedActiveOmitted = 0;\n\n if (projection.sectioned) {\n capAct = capRows(hotRaw, projection.maxRowsPerSection);\n capStale = capRows(staleRaw, projection.maxRowsPerSection);\n capDone = capRows(completed, projection.maxRowsPerSection);\n } else {\n const combinedActive = [...hotRaw, ...staleRaw];\n const cappedCombined = capRows(combinedActive, projection.maxRowsPerSection);\n capAct = { rows: cappedCombined.rows.filter((t) => !t.stale), omitted: 0 };\n capStale = { rows: cappedCombined.rows.filter((t) => t.stale), omitted: 0 };\n combinedActiveOmitted = cappedCombined.omitted;\n capDone = capRows(completed, projection.maxRowsPerSection);\n }\n\n const body = projection.sectioned\n ? buildFactsSectionedMarkdownBody(capAct.rows, capStale.rows, capDone.rows, {\n active: capAct.omitted,\n stale: capStale.omitted,\n completed: capDone.omitted,\n })\n : serializeActiveTaskFile([...capAct.rows, ...capStale.rows], capDone.rows, undefined, {\n active: combinedActiveOmitted,\n completed: capDone.omitted,\n });\n\n const lines = body.split(\"\\n\");\n lines.splice(\n 1,\n 0,\n \"\",\n \"> **Projection** of hybrid-memory `category:project` facts (`activeTask.ledger: facts`). Regenerate via `hybrid-mem active-tasks render`.\",\n includeCompleted\n ? \"> **History mode:** includes terminal rows (`--include-completed`) for audit context.\"\n : \"> **Default mode:** hides terminal rows; `ACTIVE-TASKS.md` is for active/stale work only. Use `active-tasks render --include-completed` for audit/history.\",\n \"> **Timestamps:** **Started** / **Updated** use stored fact fields (`started`, `task_started`, `task_updated`, …) or SQLite row times (min / max `createdAt` per task). The render clock is not used. Missing values show as **Unknown** and count as stale under `staleThreshold`.\",\n \"> **Operators:** Update or close tasks via `memory_store` / project facts; run `hybrid-mem active-tasks reconcile` when session rows are obsolete; then `active-tasks render`. See `docs/ACTIVE-TASKS-PROJECTION.md`.\",\n \"\",\n );\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, lines.join(\"\\n\"), \"utf-8\");\n try {\n await clearActiveTaskProjectionStale(filePath);\n } catch {\n // Keep render success best-effort even when marker cleanup fails.\n }\n logger?.debug?.(\n `memory-hybrid: active-task projection rowsFetched=${metrics.projectRowsFetched} groupedEntities=${metrics.groupedEntityCount} duplicatesCollapsed=${metrics.duplicateRowsCollapsed} activeCandidates=${metrics.activeCandidates} staleBucketed=${staleRaw.length} renderedActive=${capAct.rows.length} renderedStale=${capStale.rows.length} renderedCompleted=${capDone.rows.length} elapsedMs=${Date.now() - renderStartMs}`,\n );\n}\n\n/**\n * Apply pending sub-agent task signals to the facts ledger (no markdown).\n */\nexport async function consumePendingTaskSignalsFacts(\n workspaceRoot: string,\n staleMinutes: number,\n flushOnComplete: boolean,\n factsDb: FactsDB,\n vectorDb: VectorDB,\n embeddings: EmbeddingProvider,\n logger?: { info?: (msg: string) => void; warn?: (msg: string) => void },\n): Promise<void> {\n const memoryDir = join(workspaceRoot, \"memory\");\n let signals: PendingTaskSignal[];\n try {\n signals = await readPendingSignals(memoryDir);\n } catch (err) {\n logger?.warn?.(`memory-hybrid: failed to read pending task signals: ${err}`);\n return;\n }\n if (signals.length === 0) return;\n\n const signalTtlMs = Math.max(staleMinutes * 60 * 1000, 24 * 60 * 60 * 1000);\n const nowMs = Date.now();\n const isSignalExpired = (signal: PendingTaskSignal): boolean => {\n const parsed = Date.parse(signal.timestamp);\n if (Number.isNaN(parsed)) return true;\n return nowMs - parsed > signalTtlMs;\n };\n\n signals = [...signals].sort((a, b) => {\n const at = Date.parse(a.timestamp);\n const bt = Date.parse(b.timestamp);\n if (Number.isNaN(at) || Number.isNaN(bt)) return a._filePath.localeCompare(b._filePath);\n return at === bt ? a._filePath.localeCompare(b._filePath) : at - bt;\n });\n\n const { active: rawActive, completed: rawCompleted } = loadTaskLedgerFromFacts(factsDb);\n const active = detectStaleTasks(rawActive, staleMinutes);\n\n const findMatchingTask = (activeEntries: ActiveTaskEntry[], signal: PendingTaskSignal): ActiveTaskEntry | null => {\n const normalizedTaskRef = signal.taskRef.trim().toLowerCase();\n const byLabel = activeEntries.filter((t) => t.label.toLowerCase() === normalizedTaskRef);\n if (byLabel.length === 1) return byLabel[0];\n if (byLabel.length > 1) {\n logger?.warn?.(`memory-hybrid: multiple active tasks share label ${signal.taskRef}; leaving signal pending`);\n return null;\n }\n const byDescription = activeEntries.filter((t) => t.description === signal.taskRef);\n if (byDescription.length === 1) {\n logger?.warn?.(\n `memory-hybrid: matched signal for \"${signal.taskRef}\" by description (not label); sub-agents should use the exact task label in taskRef for reliable matching`,\n );\n return byDescription[0];\n }\n if (byDescription.length > 1) {\n logger?.warn?.(\n `memory-hybrid: multiple active tasks match description ${signal.taskRef}; leaving signal pending`,\n );\n return null;\n }\n return null;\n };\n\n const touched = new Set<string>();\n let updatedActive = [...active];\n const updatedCompleted = [...rawCompleted];\n const processedSignals: PendingTaskSignal[] = [];\n const expiredSignals: PendingTaskSignal[] = [];\n const completedToFlush: ActiveTaskEntry[] = [];\n\n for (const signal of signals) {\n try {\n const updatedTimestamp = (() => {\n const t = Date.parse(signal.timestamp);\n return Number.isNaN(t) ? new Date().toISOString() : signal.timestamp;\n })();\n\n const existing = findMatchingTask(updatedActive, signal);\n if (!existing) {\n if (isSignalExpired(signal)) expiredSignals.push(signal);\n else logger?.warn?.(`memory-hybrid: no matching active task for signal ${signal.taskRef}; leaving pending`);\n continue;\n }\n\n if (signal.signal === \"completed\") {\n const { updated, completed } = completeTask(updatedActive, existing.label);\n if (completed) {\n updatedActive = updated;\n updatedCompleted.push({\n ...completed,\n updated: updatedTimestamp,\n handoff: signal._handoff ?? completed.handoff,\n });\n processedSignals.push(signal);\n completedToFlush.push({\n ...completed,\n updated: updatedTimestamp,\n handoff: signal._handoff ?? completed.handoff,\n });\n touched.add(existing.label);\n }\n continue;\n }\n\n if (signal.signal !== \"blocked\" && signal.signal !== \"escalate\" && signal.signal !== \"update\") {\n if (isSignalExpired(signal)) expiredSignals.push(signal);\n else\n logger?.warn?.(\n `memory-hybrid: unhandled task signal \"${signal.signal}\" for ${signal.taskRef}; leaving pending`,\n );\n continue;\n }\n\n const newStatus: ActiveTaskEntry[\"status\"] =\n signal.signal === \"blocked\" ? \"Stalled\" : signal.signal === \"escalate\" ? \"Waiting\" : existing.status;\n const updatedEntry: ActiveTaskEntry = {\n ...existing,\n status: newStatus,\n next: signal.summary ? `[Signal: ${signal.signal}] ${signal.summary}` : existing.next,\n updated: updatedTimestamp,\n handoff: signal._handoff ?? existing.handoff,\n };\n updatedActive = upsertTask(updatedActive, updatedEntry, true);\n processedSignals.push(signal);\n touched.add(existing.label);\n } catch (err) {\n logger?.warn?.(`memory-hybrid: failed to process signal from ${signal._filePath}: ${err}`);\n }\n }\n\n if (processedSignals.length === 0) {\n if (expiredSignals.length > 0) {\n for (const signal of expiredSignals) await deleteSignal(signal._filePath).catch(() => {});\n logger?.info?.(`memory-hybrid: pruned ${expiredSignals.length} expired task signal(s) (facts ledger)`);\n }\n return;\n }\n\n try {\n for (const label of touched) {\n const a = updatedActive.find((t) => t.label === label);\n const c = updatedCompleted.find((t) => t.label === label);\n const entry = a ?? c;\n if (entry) {\n await syncActiveTaskEntryToFacts(factsDb, vectorDb, embeddings, entry, logger);\n }\n }\n } catch (err) {\n logger?.warn?.(`memory-hybrid: failed to persist facts after task signals: ${err}`);\n return;\n }\n\n for (const signal of processedSignals) await deleteSignal(signal._filePath).catch(() => {});\n for (const signal of expiredSignals) await deleteSignal(signal._filePath).catch(() => {});\n if (flushOnComplete && completedToFlush.length > 0) {\n for (const completed of completedToFlush) {\n await flushCompletedTaskToMemory(completed, memoryDir).catch(() => {});\n }\n }\n logger?.info?.(`memory-hybrid: consumed ${processedSignals.length} pending task signal(s) into facts ledger`);\n}\n\nexport interface FactsReconcileResult {\n reconciledLabels: string[];\n wrote: boolean;\n}\n\nexport async function reconcileActiveTaskInProgressSessionsFacts(\n factsDb: FactsDB,\n vectorDb: VectorDB,\n embeddings: EmbeddingProvider,\n staleMinutes: number,\n opts: {\n openclawHome?: string;\n flushOnComplete?: boolean;\n memoryDir?: string;\n dryRun?: boolean;\n log?: { warn?: (m: string) => void };\n } = {},\n): Promise<FactsReconcileResult> {\n const { active } = readActiveTaskRowsFromFacts(factsDb, staleMinutes);\n const reconciledLabels: string[] = [];\n const toFlush: ActiveTaskEntry[] = [];\n const openclawHome = opts.openclawHome;\n\n for (const task of active) {\n if (task.status !== \"In progress\") {\n continue;\n }\n const ref = task.subagent?.trim();\n if (!ref || !looksLikeOpenClawSessionRef(ref)) {\n continue;\n }\n const present = await isOpenClawSessionLikelyPresent(ref, openclawHome);\n if (present) {\n continue;\n }\n const now = new Date().toISOString();\n const doneEntry: ActiveTaskEntry = {\n ...task,\n status: \"Done\",\n updated: now,\n next: `Auto-reconciled: session transcript not found for ${ref} (subagent bookkeeping cleanup).`,\n subagent: undefined,\n };\n reconciledLabels.push(task.label);\n toFlush.push(doneEntry);\n }\n\n if (reconciledLabels.length === 0 || opts.dryRun) {\n return { reconciledLabels, wrote: false };\n }\n\n for (const entry of toFlush) {\n await syncActiveTaskEntryToFacts(factsDb, vectorDb, embeddings, entry, opts.log);\n }\n\n if (opts.flushOnComplete && opts.memoryDir) {\n for (const entry of toFlush) {\n await flushCompletedTaskToMemory(entry, opts.memoryDir).catch(() => {});\n }\n }\n\n return { reconciledLabels, wrote: true };\n}\n\n// -----------------------------------------------------------------------------------------\n// Issue #1625: live-state reconciliation\n// Fetches live GitHub issue/PR state for active tasks that reference external resources\n// and checkpoints terminal live state into the task ledger so agents don't repeat audits.\n// -----------------------------------------------------------------------------------------\n\nexport interface ReconcileLiveStateOptions {\n /** Maximum GitHub API calls per invocation (budget guard). */\n maxRequests?: number;\n /** AbortSignal for cancellation. */\n signal?: AbortSignal;\n log?: {\n debug?: (m: string) => void;\n warn?: (m: string) => void;\n info?: (m: string) => void;\n };\n}\n\ninterface LiveStateRef {\n owner: string;\n repo: string;\n number: number;\n /** \"pr\" or \"issue\" */\n kind: \"pr\" | \"issue\";\n}\n\ntype LiveStateFetchStatus = \"merged\" | \"closed\" | \"open\" | \"unknown\" | \"unavailable\";\n\nconst LIVE_STATE_GH_TIMEOUT_MS = 20_000;\nlet liveStateBudgetCursor = 0;\n\nfunction hasGitHubToken(): boolean {\n return (getEnv(\"GITHUB_TOKEN\") ?? getEnv(\"GH_TOKEN\") ?? \"\").trim().length > 0;\n}\n\nasync function runGhJsonCommand(args: string[], signal?: AbortSignal): Promise<unknown> {\n const ac = new AbortController();\n const timeout = setTimeout(() => ac.abort(), LIVE_STATE_GH_TIMEOUT_MS);\n const onAbort = (): void => ac.abort();\n if (signal) {\n if (signal.aborted) ac.abort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n try {\n const { stdout } = await execFileAsync(\"gh\", args, {\n encoding: \"utf-8\",\n signal: ac.signal,\n timeout: LIVE_STATE_GH_TIMEOUT_MS,\n });\n const raw = typeof stdout === \"string\" ? stdout.trim() : \"\";\n if (!raw) return null;\n return JSON.parse(raw);\n } finally {\n clearTimeout(timeout);\n if (signal) signal.removeEventListener(\"abort\", onAbort);\n }\n}\n\nfunction selectRoundRobinEntries<T>(entries: T[], maxRequests: number): T[] {\n if (entries.length === 0 || maxRequests <= 0) return [];\n if (entries.length <= maxRequests) {\n liveStateBudgetCursor = 0;\n return entries;\n }\n const start = ((liveStateBudgetCursor % entries.length) + entries.length) % entries.length;\n const selected: T[] = [];\n for (let i = 0; i < maxRequests; i++) {\n selected.push(entries[(start + i) % entries.length]);\n }\n liveStateBudgetCursor = (start + maxRequests) % entries.length;\n return selected;\n}\n\n/** Extract \"owner/repo#N\" references from a task label or description.\n * Handles: \"owner/repo#123\", \"owner/repo pull request #123\" (case-insensitive).\n * Returns one ref per task (first match wins). */\nfunction extractLiveStateRef(task: ActiveTaskEntry): LiveStateRef | null {\n const text = `${task.label} ${task.description}`;\n // Pattern: \"owner/repo issue #123\" or \"owner/repo pull request #123\" (GitHub's own URL form)\n // Check this first to avoid ambiguity with the bare owner/repo#N pattern\n const issueMatch = text.match(/\\b([a-zA-Z0-9_.-]+)\\/([a-zA-Z0-9_.-]+)\\s+(issue|pull\\s+request|pr)\\s*#(\\d+)\\b/i);\n if (issueMatch) {\n const number = Number(issueMatch[4]);\n if (!Number.isFinite(number) || number < 1) return null;\n const kindString = issueMatch[3];\n const kind: \"pr\" | \"issue\" = /pull\\s*request|pr/i.test(kindString) ? \"pr\" : \"issue\";\n return { owner: issueMatch[1], repo: issueMatch[2], number, kind };\n }\n // Pattern: owner/repo#number (most common - defaults to issue as per GitHub convention)\n const refMatch = text.match(/\\b([a-zA-Z0-9_.-]+)\\/([a-zA-Z0-9_.-]+)\\s*#(\\d+)\\b/i);\n if (refMatch) {\n const number = Number(refMatch[3]);\n if (!Number.isFinite(number) || number < 1) return null;\n return { owner: refMatch[1], repo: refMatch[2], number, kind: \"issue\" };\n }\n return null;\n}\n\n/** Fetch live state for a GitHub PR via `gh pr view`.\n * Returns \"merged\", \"closed\", \"open\", \"unknown\", or \"unavailable\". */\nasync function fetchLivePrState(\n owner: string,\n repo: string,\n prNumber: number,\n signal?: AbortSignal,\n): Promise<LiveStateFetchStatus> {\n if (!hasGitHubToken()) return \"unavailable\";\n try {\n const payload = await runGhJsonCommand(\n [\"pr\", \"view\", String(prNumber), \"--repo\", `${owner}/${repo}`, \"--json\", \"state,mergedAt,closedAt\"],\n signal,\n );\n const data = payload as { state?: unknown; mergedAt?: unknown; closedAt?: unknown } | null;\n const state = typeof data?.state === \"string\" ? data.state.trim().toUpperCase() : \"\";\n const mergedAt = typeof data?.mergedAt === \"string\" && data.mergedAt.trim().length > 0;\n const closedAt = typeof data?.closedAt === \"string\" && data.closedAt.trim().length > 0;\n if (state === \"MERGED\" || mergedAt) return \"merged\";\n if (state === \"CLOSED\" || (closedAt && !mergedAt)) return \"closed\";\n if (state === \"OPEN\") return \"open\";\n return \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\n/** Fetch live state for a GitHub issue.\n * Returns \"closed\" when the issue is closed, \"open\" when still open, \"unknown\" when\n * state cannot be determined, or \"unavailable\" when the token/network is missing. */\nasync function fetchLiveIssueState(\n owner: string,\n repo: string,\n issueNumber: number,\n signal?: AbortSignal,\n): Promise<LiveStateFetchStatus> {\n if (!hasGitHubToken()) return \"unavailable\";\n try {\n const payload = await runGhJsonCommand(\n [\"issue\", \"view\", String(issueNumber), \"--repo\", `${owner}/${repo}`, \"--json\", \"state\"],\n signal,\n );\n const data = payload as { state?: unknown } | null;\n const state = typeof data?.state === \"string\" ? data.state.trim().toUpperCase() : \"\";\n if (state === \"CLOSED\") return \"closed\";\n if (state === \"OPEN\") return \"open\";\n return \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\n/** Reconcile active tasks with live GitHub state.\n *\n * For each active task that references an external GitHub issue or PR:\n * - Fetch the current live state from GitHub.\n * - If the live state is terminal (issue closed, PR merged/closed), checkpoint the\n * task as \"Done\" in the facts ledger with evidence in `next` and `task_updated`.\n * - If live state is non-terminal/unknown, leave the task unchanged.\n *\n * This is intended to run during normal reconcile/hygiene/render cycles when enabled.\n * The function is safe to call repeatedly: it only checkpoints terminal\n * live-state transitions (merged/closed) and degrades gracefully when GitHub is unavailable.\n *\n * Returns the number of tasks that were updated (for telemetry).\n */\nexport async function reconcileActiveTaskLiveState(\n factsDb: FactsDB,\n vectorDb: VectorDB,\n embeddings: EmbeddingProvider,\n opts: ReconcileLiveStateOptions = {},\n): Promise<{ updatedCount: number; checkedCount: number; skippedCount: number }> {\n const maxRequests = Math.max(1, Math.min(opts.maxRequests ?? 20, 50));\n const signal = opts.signal;\n\n const { active } = loadTaskLedgerFromFacts(factsDb);\n let updatedCount = 0;\n let skippedCount = 0;\n\n // Collect all tasks with refs (allow multiple tasks per ref)\n const refMap = new Map<string, { tasks: ActiveTaskEntry[]; ref: LiveStateRef }>();\n for (const task of active) {\n if (task.status === \"Done\" || task.status === \"Failed\") continue;\n const ref = extractLiveStateRef(task);\n if (!ref) {\n skippedCount++;\n continue;\n }\n const key = `${ref.kind}:${ref.owner}/${ref.repo}#${ref.number}`;\n const existing = refMap.get(key);\n if (existing) {\n existing.tasks.push(task);\n } else {\n refMap.set(key, { tasks: [task], ref });\n }\n }\n\n const entries = [...refMap.entries()];\n const toFetch = selectRoundRobinEntries(entries, maxRequests);\n skippedCount += Math.max(0, entries.length - toFetch.length);\n\n // Fetch live state in parallel, up to budget\n let fetchedResults: Array<{ key: string; ref: LiveStateRef; state: LiveStateFetchStatus }> = [];\n try {\n const settled = await Promise.allSettled(\n toFetch.map(async ([key, { ref }]) => {\n const state: LiveStateFetchStatus =\n ref.kind === \"pr\"\n ? await fetchLivePrState(ref.owner, ref.repo, ref.number, signal)\n : await fetchLiveIssueState(ref.owner, ref.repo, ref.number, signal);\n return { key, ref, state };\n }),\n );\n fetchedResults = settled\n .filter(\n (result): result is PromiseFulfilledResult<{ key: string; ref: LiveStateRef; state: LiveStateFetchStatus }> =>\n result.status === \"fulfilled\",\n )\n .map((result) => result.value);\n } catch {\n opts.log?.debug?.(\"memory-hybrid: live-state reconcile fetch failed, skipping update\");\n return { updatedCount, checkedCount: fetchedResults.length, skippedCount };\n }\n const checkedCount = fetchedResults.length;\n\n const terminalRefKeys = new Set<string>();\n for (const { key, state } of fetchedResults) {\n const isTerminal = state === \"merged\" || state === \"closed\";\n if (isTerminal) terminalRefKeys.add(key);\n }\n\n // Apply updates: for each terminal ref, update ALL tasks that point to it\n for (const [key, { tasks }] of entries) {\n if (!terminalRefKeys.has(key)) continue;\n const { ref } = refMap.get(key)!;\n const state = fetchedResults.find((r) => r.key === key)?.state ?? \"unknown\";\n const isTerminal = state === \"merged\" || state === \"closed\";\n if (!isTerminal) continue;\n\n const now = new Date().toISOString();\n const terminalState = state === \"merged\" ? \"merged\" : \"closed\";\n const next = `Auto-reconciled from live GitHub (${ref.owner}/${ref.repo} ${ref.kind} #${ref.number}): ${terminalState}.`;\n\n for (const task of tasks) {\n const doneEntry: ActiveTaskEntry = {\n ...task,\n status: \"Done\",\n updated: now,\n next,\n };\n await syncActiveTaskEntryToFacts(factsDb, vectorDb, embeddings, doneEntry, opts.log);\n updatedCount++;\n opts.log?.info?.(`memory-hybrid: live-state reconcile marked \"${task.label}\" done (${terminalState})`);\n }\n }\n\n return { updatedCount, checkedCount, skippedCount };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"task-ledger-facts.js","names":[],"sources":["../../services/task-ledger-facts.ts"],"sourcesContent":["/**\n * Facts-backed active task ledger (category:project).\n * Aligns hybrid-mem active-tasks with memory_store / memory_recall workflows.\n */\n\nimport { mkdir, readFile, rm, stat, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { execFile } from \"../utils/process-runner.js\";\nimport { mergeFactProvenanceJson } from \"../backends/facts-db/provenance-json.js\";\nimport type { FactsDB } from \"../backends/facts-db.js\";\nimport type { VectorDB } from \"../backends/vector-db.js\";\nimport type { ActiveTaskProjectionConfig, MemoryCategory } from \"../config.js\";\nimport type { MemoryEntry, ScopeFilter } from \"../types/memory.js\";\nimport { CLI_STORE_IMPORTANCE } from \"../utils/constants.js\";\nimport { getEnv } from \"../utils/env-manager.js\";\nimport {\n type ActiveTaskEntry,\n type ActiveTaskStatus,\n type PendingTaskSignal,\n OMITTED_CAP_NOTE,\n UNKNOWN_ACTIVE_TASK_TIME,\n completeTask,\n deleteSignal,\n detectStaleTasks,\n flushCompletedTaskToMemory,\n readPendingSignals,\n serializeActiveTaskFile,\n serializeTaskEntry,\n upsertTask,\n} from \"./active-task.js\";\nimport type { EmbeddingProvider } from \"./embeddings.js\";\nimport { isOpenClawSessionLikelyPresent, looksLikeOpenClawSessionRef } from \"./openclaw-session-artifact.js\";\nimport { fetchLivePrBlockerStatus, type PrBlockerStatus } from \"./task-hygiene.js\";\nimport {\n activeTaskProvenance,\n canonicalLabel,\n displayStatusToFact,\n factCanonicalLabel,\n factStatusToDisplay,\n groupProjectFactsByEntity,\n isTerminalFactStatus,\n readCanonicalLabelFromFact,\n} from \"./task-ledger/canonical.js\";\n\nconst execFileAsync = promisify(execFile);\n\nexport {\n canonicalLabel,\n displayStatusToFact,\n factStatusToDisplay,\n groupProjectFactsByEntity,\n} from \"./task-ledger/canonical.js\";\nexport const TASK_LEDGER_CATEGORY = \"project\" as MemoryCategory;\n\nconst GENERIC_TASK_TITLE_NORMALIZED = new Set([\"project task\", \"task\", \"active task\"]);\nconst PROJECTION_STALE_MARKER_SUFFIX = \".stale.json\";\nconst ACTIVE_TASK_PROJECTION_GLOBAL_SCOPE_FILTER: ScopeFilter = { agentId: \"__active_task_projection_global__\" };\n\ntype ActiveTaskProjectionStaleMarker = {\n staleAt: string;\n reason: string;\n source?: string;\n factId?: string;\n};\n\nexport type ActiveTaskProjectionStatus = {\n filePath: string;\n markerPath: string;\n exists: boolean;\n stale: boolean;\n staleReasons: string[];\n latestProjectFactAt: string | null;\n renderedAt: string | null;\n marker: ActiveTaskProjectionStaleMarker | null;\n};\n\nexport type ActiveTaskLedgerSelectionMetrics = {\n projectRowsFetched: number;\n rowsDroppedMissingEntityOrCanonical: number;\n groupedEntityCount: number;\n duplicateRowsCollapsed: number;\n activeCandidates: number;\n completedCandidates: number;\n entitiesSkippedWithoutStatus: number;\n elapsedMs: number;\n};\n\nexport type ActiveTaskCanonicalBackfillOptions = {\n dryRun?: boolean;\n limit?: number;\n};\n\nfunction rowToRecord(row: Map<string, MemoryEntry>): Record<string, string> {\n const o: Record<string, string> = {};\n for (const [k, e] of row) {\n const key = k === \"_body\" ? \"description\" : k;\n o[key] = e.value ?? e.text ?? \"\";\n }\n return o;\n}\n\nfunction parseIsoFromFactField(value: string | undefined): string | undefined {\n if (!value?.trim()) return undefined;\n const ms = Date.parse(value.trim());\n if (Number.isNaN(ms)) return undefined;\n return new Date(ms).toISOString();\n}\n\nfunction createdBoundsFromKeyMap(keyMap: Map<string, MemoryEntry>): { minSec: number; maxSec: number } | null {\n let minSec = Number.POSITIVE_INFINITY;\n let maxSec = Number.NEGATIVE_INFINITY;\n for (const e of keyMap.values()) {\n const c = e.createdAt;\n if (typeof c === \"number\" && Number.isFinite(c)) {\n minSec = Math.min(minSec, c);\n maxSec = Math.max(maxSec, c);\n }\n }\n if (minSec === Number.POSITIVE_INFINITY) return null;\n return { minSec, maxSec };\n}\n\nfunction resolveTaskStarted(f: Record<string, string>, bounds: ReturnType<typeof createdBoundsFromKeyMap>): string {\n return (\n parseIsoFromFactField(f.started) ??\n parseIsoFromFactField(f.task_started) ??\n parseIsoFromFactField(f.created_at) ??\n (bounds ? new Date(bounds.minSec * 1000).toISOString() : undefined) ??\n UNKNOWN_ACTIVE_TASK_TIME\n );\n}\n\nfunction resolveTaskUpdated(f: Record<string, string>, bounds: ReturnType<typeof createdBoundsFromKeyMap>): string {\n return (\n parseIsoFromFactField(f.task_updated) ??\n parseIsoFromFactField(f.updated) ??\n parseIsoFromFactField(f.updated_at) ??\n (bounds ? new Date(bounds.maxSec * 1000).toISOString() : undefined) ??\n UNKNOWN_ACTIVE_TASK_TIME\n );\n}\n\nfunction titleFromFacts(f: Record<string, string>): string {\n return f.title?.trim() || f.description?.trim() || f.summary?.trim() || \"Project task\";\n}\n\n/**\n * Build active + completed task entries from grouped project facts.\n */\nexport function buildTaskEntriesFromGroupedFacts(byEntity: Map<string, Map<string, MemoryEntry>>): {\n active: ActiveTaskEntry[];\n completed: ActiveTaskEntry[];\n} {\n const active: ActiveTaskEntry[] = [];\n const completed: ActiveTaskEntry[] = [];\n const sorted = [...byEntity.entries()].sort((a, b) => a[0].localeCompare(b[0]));\n\n for (const [entity, keyMap] of sorted) {\n const f = rowToRecord(keyMap);\n const bounds = createdBoundsFromKeyMap(keyMap);\n // Entities with no explicit `status` key must not default to in-progress.\n const statusRaw = f.status?.trim();\n if (!statusRaw) continue;\n const disp = factStatusToDisplay(statusRaw);\n const started = resolveTaskStarted(f, bounds);\n const updated = resolveTaskUpdated(f, bounds);\n let handoff: ActiveTaskEntry[\"handoff\"];\n if (f.handoff?.trim()) {\n try {\n handoff = JSON.parse(f.handoff.trim());\n } catch {\n // Ignore parse errors\n }\n }\n const entry: ActiveTaskEntry = {\n label: entity,\n description: titleFromFacts(f),\n status: disp,\n branch: f.branch?.trim() || undefined,\n stashCommit: f.stash_commit?.trim() || undefined,\n subagent: f.related_session?.trim() || undefined,\n next: f.next?.trim() || undefined,\n relatedGoal: f.related_goal?.trim() || f.goal_id?.trim() || undefined,\n started,\n updated,\n handoff,\n };\n if (isTerminalFactStatus(statusRaw) || disp === \"Done\") {\n completed.push({ ...entry, status: \"Done\" });\n } else {\n active.push(entry);\n }\n }\n\n return { active, completed };\n}\n\nexport function loadTaskLedgerFromFacts(\n factsDb: FactsDB,\n factLimit = 8000,\n scopeFilter?: ScopeFilter | null,\n): {\n active: ActiveTaskEntry[];\n completed: ActiveTaskEntry[];\n} {\n const { active, completed } = loadTaskLedgerFromFactsWithMetrics(factsDb, factLimit, scopeFilter);\n return { active, completed };\n}\n\nexport function loadTaskLedgerFromFactsWithMetrics(\n factsDb: FactsDB,\n factLimit = 8000,\n scopeFilter?: ScopeFilter | null,\n): {\n active: ActiveTaskEntry[];\n completed: ActiveTaskEntry[];\n metrics: ActiveTaskLedgerSelectionMetrics;\n} {\n const startedAtMs = Date.now();\n // Use targeted query instead of loading all facts then filtering by category (#1553)\n const facts = factsDb.getProjectFacts(factLimit, scopeFilter);\n // groupProjectFactsByEntity silently drops rows with no entity or no canonical label;\n // count those separately so duplicateRowsCollapsed only reflects true duplicates.\n const grouped = groupProjectFactsByEntity(facts);\n const { active, completed } = buildTaskEntriesFromGroupedFacts(grouped);\n let groupedFactRows = 0;\n for (const keyMap of grouped.values()) {\n groupedFactRows += keyMap.size;\n }\n // The rows fed to groupProjectFactsByEntity = facts that passed entity/canonical guard.\n // We infer dropped rows = facts.length - sum of sizes of all keyMaps.\n // But keyMaps only count unique (entity,key) pairs — so true duplicates are also in groupedFactRows.\n // Instead: track rows fed to grouping = only rows with entity AND canonicalLabel.\n // Use canonicalLabel directly to determine eligibility.\n const eligibleRows = facts.filter((f) => f.entity?.trim() && canonicalLabel(f.entity.trim())).length;\n const metrics: ActiveTaskLedgerSelectionMetrics = {\n projectRowsFetched: facts.length,\n rowsDroppedMissingEntityOrCanonical: facts.length - eligibleRows,\n groupedEntityCount: grouped.size,\n duplicateRowsCollapsed: Math.max(0, eligibleRows - groupedFactRows),\n activeCandidates: active.length,\n completedCandidates: completed.length,\n entitiesSkippedWithoutStatus: Math.max(0, grouped.size - active.length - completed.length),\n elapsedMs: Date.now() - startedAtMs,\n };\n return { active, completed, metrics };\n}\n\nexport function readActiveTaskRowsFromFacts(\n factsDb: FactsDB,\n staleMinutes: number,\n scopeFilter?: ScopeFilter | null,\n): { active: ActiveTaskEntry[]; completed: ActiveTaskEntry[]; latestProjectFactSec: number | null } {\n const { active, completed } = loadTaskLedgerFromFacts(factsDb, 8000, scopeFilter);\n const staleActive = detectStaleTasks(active, staleMinutes);\n const latestProjectFactSec = getLatestProjectFactCreatedAtSec(factsDb, scopeFilter);\n return { active: staleActive, completed, latestProjectFactSec };\n}\n\nexport function getActiveTaskProjectionStaleMarkerPath(filePath: string): string {\n return `${filePath}${PROJECTION_STALE_MARKER_SUFFIX}`;\n}\n\nfunction parseProjectionMarker(raw: string): ActiveTaskProjectionStaleMarker | null {\n try {\n const parsed = JSON.parse(raw) as Partial<ActiveTaskProjectionStaleMarker>;\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n typeof parsed.staleAt === \"string\" &&\n parsed.staleAt.trim().length > 0 &&\n typeof parsed.reason === \"string\" &&\n parsed.reason.trim().length > 0\n ) {\n return {\n staleAt: parsed.staleAt.trim(),\n reason: parsed.reason.trim(),\n ...(typeof parsed.source === \"string\" && parsed.source.trim().length > 0\n ? { source: parsed.source.trim() }\n : {}),\n ...(typeof parsed.factId === \"string\" && parsed.factId.trim().length > 0\n ? { factId: parsed.factId.trim() }\n : {}),\n };\n }\n } catch {\n // ignore malformed marker JSON\n }\n return null;\n}\n\nasync function readActiveTaskProjectionStaleMarker(filePath: string): Promise<ActiveTaskProjectionStaleMarker | null> {\n const markerPath = getActiveTaskProjectionStaleMarkerPath(filePath);\n try {\n const raw = await readFile(markerPath, \"utf-8\");\n return parseProjectionMarker(raw);\n } catch {\n return null;\n }\n}\n\nexport async function markActiveTaskProjectionStale(\n filePath: string,\n reason: string,\n meta: { source?: string; factId?: string } = {},\n): Promise<void> {\n const markerPath = getActiveTaskProjectionStaleMarkerPath(filePath);\n const marker: ActiveTaskProjectionStaleMarker = {\n staleAt: new Date().toISOString(),\n reason: reason.trim().length > 0 ? reason.trim() : \"projection marked stale\",\n ...(meta.source ? { source: meta.source } : {}),\n ...(meta.factId ? { factId: meta.factId } : {}),\n };\n await mkdir(dirname(markerPath), { recursive: true });\n await writeFile(markerPath, `${JSON.stringify(marker, null, 2)}\\n`, \"utf-8\");\n}\n\nexport async function clearActiveTaskProjectionStale(filePath: string): Promise<void> {\n await rm(getActiveTaskProjectionStaleMarkerPath(filePath), { force: true });\n}\n\nexport function getLatestProjectFactCreatedAtSec(factsDb: FactsDB, scopeFilter?: ScopeFilter | null): number | null {\n // Query all project facts (any source) to detect staleness from any project fact updates.\n // Note: getProjectFacts filters by source='active-task', but we need to detect updates\n // from all sources (e.g., memory_store) to properly mark projections as stale.\n const projectFacts = factsDb\n .getAll({ scopeFilter })\n .filter((fact) => fact.category === TASK_LEDGER_CATEGORY)\n .slice(0, 8000);\n if (projectFacts.length === 0) return null;\n let maxSec = Number.NEGATIVE_INFINITY;\n for (const fact of projectFacts) {\n if (typeof fact.createdAt === \"number\" && Number.isFinite(fact.createdAt)) {\n maxSec = Math.max(maxSec, fact.createdAt);\n }\n }\n return maxSec === Number.NEGATIVE_INFINITY ? null : maxSec;\n}\n\nfunction toIsoOrNull(unixSeconds: number | null): string | null {\n if (unixSeconds === null) return null;\n return new Date(unixSeconds * 1000).toISOString();\n}\n\nexport async function getActiveTaskProjectionStatus(\n factsDb: FactsDB,\n filePath: string,\n opts: { scopeFilter?: ScopeFilter | null; latestProjectFactSec?: number | null } = {},\n): Promise<ActiveTaskProjectionStatus> {\n const markerPath = getActiveTaskProjectionStaleMarkerPath(filePath);\n const marker = await readActiveTaskProjectionStaleMarker(filePath);\n const latestProjectFactSec = opts.latestProjectFactSec ?? getLatestProjectFactCreatedAtSec(factsDb, opts.scopeFilter);\n const latestProjectFactAt = toIsoOrNull(latestProjectFactSec);\n\n let exists = false;\n let renderedAt: string | null = null;\n let renderedMs: number | null = null;\n try {\n const fileStat = await stat(filePath);\n exists = fileStat.isFile();\n if (exists) {\n renderedMs = fileStat.mtimeMs;\n renderedAt = new Date(fileStat.mtimeMs).toISOString();\n }\n } catch {\n // no projection file\n }\n\n const staleReasons = new Set<string>();\n if (latestProjectFactSec !== null && !exists) {\n staleReasons.add(\"projection_missing\");\n }\n if (latestProjectFactSec !== null && renderedMs !== null && latestProjectFactSec * 1000 > renderedMs + 1000) {\n staleReasons.add(\"project_facts_newer_than_projection\");\n }\n if (marker) {\n staleReasons.add(\"projection_marked_stale\");\n }\n\n return {\n filePath,\n markerPath,\n exists,\n stale: staleReasons.size > 0,\n staleReasons: [...staleReasons],\n latestProjectFactAt,\n renderedAt,\n marker,\n };\n}\n\nexport async function refreshActiveTaskProjectionBestEffort(opts: {\n factsDb: FactsDB;\n staleMinutes: number;\n filePath: string;\n projection: ActiveTaskProjectionConfig;\n reason: string;\n source?: string;\n factId?: string;\n logger?: { warn?: (m: string) => void };\n}): Promise<{ rendered: boolean; staleMarked: boolean; error?: string }> {\n try {\n await renderActiveTaskMarkdownFile(opts.factsDb, opts.staleMinutes, opts.filePath, opts.projection);\n return { rendered: true, staleMarked: false };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n let staleMarked = false;\n try {\n await markActiveTaskProjectionStale(opts.filePath, opts.reason, { source: opts.source, factId: opts.factId });\n staleMarked = true;\n } catch {\n // Ignore stale marker write failures (best-effort)\n }\n opts.logger?.warn?.(`memory-hybrid: active-task projection refresh failed: ${message}`);\n return { rendered: false, staleMarked, error: message };\n }\n}\n\n/** Normalize description for `dedupeBy: normalizedTitle`. */\nfunction normalizeActiveTaskTitleForDedupe(description: string): string {\n return description.trim().toLowerCase().replace(/\\s+/g, \" \");\n}\n\n/** Filter/dedupe rows for facts markdown projection (`readable` mode). */\nexport function applyActiveTaskProjectionFilters(\n entries: ActiveTaskEntry[],\n config: ActiveTaskProjectionConfig,\n): ActiveTaskEntry[] {\n if (config.mode === \"full\") {\n return entries;\n }\n let out = entries;\n if (config.excludeGenericTitle) {\n out = out.filter((e) => e.description.trim() !== \"Project task\");\n }\n if (config.titleMinChars > 0) {\n out = out.filter((e) => e.description.trim().length >= config.titleMinChars);\n }\n if (config.dedupeBy === \"none\") {\n return out;\n }\n const seen = new Set<string>();\n const result: ActiveTaskEntry[] = [];\n for (const e of out) {\n const key =\n config.dedupeBy === \"label\" ? e.label.trim().toLowerCase() : normalizeActiveTaskTitleForDedupe(e.description);\n if (seen.has(key)) continue;\n seen.add(key);\n result.push(e);\n }\n return result;\n}\n\nexport interface ActiveTaskHygieneDuplicateGroup {\n normalized: string;\n canonicalLabel: string;\n labels: string[];\n}\n\nexport interface ActiveTaskHygieneStaleCandidate {\n label: string;\n status: ActiveTaskStatus;\n updated: string;\n hoursStale: number | \"?\";\n reason: string;\n}\n\nexport type ActiveTaskHygieneActionKind = \"dead-session\" | \"stale-failed\" | \"superseded-duplicate\" | \"pr-live-blocker\";\n\nexport interface ActiveTaskHygieneAction {\n label: string;\n kind: ActiveTaskHygieneActionKind;\n toStatus: \"abandoned\" | \"superseded\" | \"stage-4-feedback\";\n reason: string;\n canonicalLabel?: string;\n /** Set when kind is \"pr-live-blocker\" — the actual GitHub-classified blocker */\n prBlockerStatus?: string;\n}\n\nexport interface ActiveTaskHygienePlan {\n olderThanMinutes: number;\n duplicates: ActiveTaskHygieneDuplicateGroup[];\n stale: ActiveTaskHygieneStaleCandidate[];\n actions: ActiveTaskHygieneAction[];\n}\n\nfunction parseTaskUpdatedMs(updated: string): number | null {\n if (!updated || updated === UNKNOWN_ACTIVE_TASK_TIME) return null;\n const ms = Date.parse(updated);\n if (Number.isNaN(ms)) return null;\n return ms;\n}\n\nfunction normalizeTaskString(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/[_\\-.:/]+/g, \" \")\n .replace(/[^a-z0-9\\s]/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nfunction isGenericTaskTitle(value: string): boolean {\n const normalized = normalizeTaskString(value);\n if (!normalized) return true;\n return GENERIC_TASK_TITLE_NORMALIZED.has(normalized);\n}\n\nfunction staleHoursFromUpdated(updated: string, nowMs: number): number | \"?\" {\n const ms = parseTaskUpdatedMs(updated);\n if (ms == null) return \"?\";\n return Math.floor((nowMs - ms) / (60 * 60 * 1000));\n}\n\nfunction statusRankForCanonical(status: ActiveTaskStatus): number {\n switch (status) {\n case \"In progress\":\n return 0;\n case \"Waiting\":\n return 1;\n case \"Stalled\":\n return 2;\n case \"Failed\":\n return 3;\n default:\n return 4;\n }\n}\n\nfunction buildDuplicateComponents(tasks: ActiveTaskEntry[]): number[][] {\n const byLabel = new Map<string, number[]>();\n const byTitle = new Map<string, number[]>();\n for (let i = 0; i < tasks.length; i++) {\n const labelKey = normalizeTaskString(tasks[i].label);\n const titleKey = normalizeTaskString(tasks[i].description);\n if (labelKey) {\n const arr = byLabel.get(labelKey) ?? [];\n arr.push(i);\n byLabel.set(labelKey, arr);\n }\n if (titleKey && !isGenericTaskTitle(tasks[i].description)) {\n const arr = byTitle.get(titleKey) ?? [];\n arr.push(i);\n byTitle.set(titleKey, arr);\n }\n }\n\n const edges = new Map<number, Set<number>>();\n const linkGroup = (indices: number[]): void => {\n if (indices.length < 2) return;\n const root = indices[0];\n let rootSet = edges.get(root);\n if (!rootSet) {\n rootSet = new Set<number>();\n edges.set(root, rootSet);\n }\n for (let i = 1; i < indices.length; i++) {\n const idx = indices[i];\n rootSet.add(idx);\n let setI = edges.get(idx);\n if (!setI) {\n setI = new Set<number>();\n edges.set(idx, setI);\n }\n setI.add(root);\n }\n };\n\n for (const indices of byLabel.values()) linkGroup(indices);\n for (const indices of byTitle.values()) linkGroup(indices);\n\n const components: number[][] = [];\n const seen = new Set<number>();\n for (const idx of edges.keys()) {\n if (seen.has(idx)) continue;\n const stack = [idx];\n const comp: number[] = [];\n while (stack.length > 0) {\n const cur = stack.pop() as number;\n if (seen.has(cur)) continue;\n seen.add(cur);\n comp.push(cur);\n for (const n of edges.get(cur) ?? []) {\n if (!seen.has(n)) stack.push(n);\n }\n }\n if (comp.length > 1) components.push(comp);\n }\n return components;\n}\n\nexport async function planActiveTaskHygiene(\n tasks: ActiveTaskEntry[],\n opts: {\n olderThanMinutes: number;\n nowMs?: number;\n openclawHome?: string;\n checkSessionPresent?: (sessionRef: string) => Promise<boolean>;\n /** When true, fetch live PR state for tasks whose labels contain an \"owner/repo#N\" PR reference. */\n checkPrLiveBlocker?: boolean;\n },\n): Promise<ActiveTaskHygienePlan> {\n const nowMs = opts.nowMs ?? Date.now();\n const olderThanMinutes = Math.max(1, Math.floor(opts.olderThanMinutes));\n const olderThanMs = olderThanMinutes * 60 * 1000;\n const classifyAge = (\n task: ActiveTaskEntry,\n ): {\n stale: boolean;\n reasonQualifier: string;\n } => {\n const updatedMs = parseTaskUpdatedMs(task.updated);\n if (updatedMs == null) {\n return {\n stale: true,\n reasonQualifier: \"missing/unknown updated timestamp\",\n };\n }\n return {\n stale: nowMs - updatedMs > olderThanMs,\n reasonQualifier: `older than ${olderThanMinutes}m`,\n };\n };\n const checkSessionPresent =\n opts.checkSessionPresent ??\n ((sessionRef: string): Promise<boolean> => isOpenClawSessionLikelyPresent(sessionRef, opts.openclawHome));\n\n const stale: ActiveTaskHygieneStaleCandidate[] = [];\n const actionsByLabel = new Map<string, ActiveTaskHygieneAction>();\n\n for (const task of tasks) {\n if (task.status !== \"Failed\") continue;\n const age = classifyAge(task);\n if (!age.stale) continue;\n const reason = `Failed task ${age.reasonQualifier}; marking abandoned.`;\n stale.push({\n label: task.label,\n status: task.status,\n updated: task.updated,\n hoursStale: staleHoursFromUpdated(task.updated, nowMs),\n reason,\n });\n actionsByLabel.set(task.label, {\n label: task.label,\n kind: \"stale-failed\",\n toStatus: \"abandoned\",\n reason,\n });\n }\n\n const staleSessionCandidates = tasks\n .filter((task) => task.status === \"In progress\")\n .map((task) => ({ task, sessionRef: task.subagent?.trim() ?? \"\" }))\n .filter(({ sessionRef }) => sessionRef.length > 0 && looksLikeOpenClawSessionRef(sessionRef))\n .map(({ task, sessionRef }) => ({ task, sessionRef, age: classifyAge(task) }))\n .filter(({ age }) => age.stale);\n const uniqueSessionRefs = [...new Set(staleSessionCandidates.map((c) => c.sessionRef))];\n const sessionPresentByRef = new Map<string, boolean>();\n await Promise.all(\n uniqueSessionRefs.map(async (sessionRef) => {\n const present = await checkSessionPresent(sessionRef);\n sessionPresentByRef.set(sessionRef, present);\n }),\n );\n for (const { task, sessionRef, age } of staleSessionCandidates) {\n const present = sessionPresentByRef.get(sessionRef) ?? false;\n if (present) continue;\n const reason = `In-progress task has missing session transcript (${sessionRef}) and is ${age.reasonQualifier}; marking abandoned.`;\n stale.push({\n label: task.label,\n status: task.status,\n updated: task.updated,\n hoursStale: staleHoursFromUpdated(task.updated, nowMs),\n reason,\n });\n actionsByLabel.set(task.label, {\n label: task.label,\n kind: \"dead-session\",\n toStatus: \"abandoned\",\n reason,\n });\n }\n\n // -----------------------------------------------------------------------------------------\n // PR live blocker check: verify tasks with \"In progress\" / \"Waiting\" / \"Stalled\" status\n // actually have a live GitHub blocker before classifying them as blocked/waiting.\n // This prevents hygiene from incorrectly marking PR-backed tasks as stalled when\n // mergeStateStatus says BLOCKED but unresolved review threads exist.\n // -----------------------------------------------------------------------------------------\n if (opts.checkPrLiveBlocker === true) {\n // Collect tasks that reference a PR (owner/repo#N pattern in label or description)\n const PR_REF_RE = /(?:^|[\\s/])([a-zA-Z0-9_.-]+\\/[a-zA-Z0-9_.-]+)#(\\d+)/;\n const prCandidateTasks = tasks.filter((t) => {\n if (t.status !== \"In progress\" && t.status !== \"Waiting\" && t.status !== \"Stalled\") return false;\n const text = `${t.label} ${t.description}`;\n return PR_REF_RE.test(text);\n });\n\n if (prCandidateTasks.length > 0) {\n // Group by owner/repo to avoid duplicate API calls for the same repo\n const prKeys = new Map<string, { tasks: ActiveTaskEntry[]; owner: string; repo: string; number: number }>();\n for (const task of prCandidateTasks) {\n const m = PR_REF_RE.exec(`${task.label} ${task.description}`);\n if (!m) continue;\n const key = `${m[1]}#${m[2]}`;\n const existing = prKeys.get(key);\n if (existing) {\n existing.tasks.push(task);\n } else {\n const [owner, repo] = m[1].split(\"/\");\n prKeys.set(key, { tasks: [task], owner, repo, number: Number(m[2]) });\n }\n }\n\n // Fetch live blocker status for each unique PR (parallel, deduped)\n const prStatuses = await Promise.all(\n [...prKeys.values()].map(async ({ owner, repo, number }) => {\n const status = await fetchLivePrBlockerStatus(owner, repo, number);\n return { owner, repo, number, status };\n }),\n );\n const statusByKey = new Map(prStatuses.map((p) => [`${p.owner}/${p.repo}#${p.number}`, p]));\n\n for (const { tasks, owner, repo, number } of prKeys.values()) {\n const blockerStatus = statusByKey.get(`${owner}/${repo}#${number}`);\n if (!blockerStatus) continue;\n\n if (blockerStatus.status !== \"no_live_blocker\") {\n const key = `${owner}/${repo}`;\n const reason = `[PR hygiene #${number}] Live GitHub state: ${blockerStatus.status} — task updated to reflect actual PR state.`;\n\n // Apply action to ALL tasks referencing this PR\n for (const task of tasks) {\n // Skip if already handled by a dead-session or stale-failed action\n if (actionsByLabel.has(task.label)) continue;\n\n stale.push({\n label: task.label,\n status: task.status,\n updated: task.updated,\n hoursStale: staleHoursFromUpdated(task.updated, nowMs),\n reason,\n });\n actionsByLabel.set(task.label, {\n label: task.label,\n kind: \"pr-live-blocker\",\n toStatus: \"stage-4-feedback\",\n reason,\n // Store actual blocker classification, plus PR coordinates for parsing\n prBlockerStatus: `${blockerStatus.status}|${owner}:${repo}:${number}`,\n });\n }\n }\n }\n }\n }\n\n const duplicates: ActiveTaskHygieneDuplicateGroup[] = [];\n const components = buildDuplicateComponents(tasks);\n for (const comp of components) {\n const members = comp.map((idx) => tasks[idx]);\n const ranked = [...members].sort((a, b) => {\n const actionA = actionsByLabel.has(a.label) ? 1 : 0;\n const actionB = actionsByLabel.has(b.label) ? 1 : 0;\n if (actionA !== actionB) return actionA - actionB;\n const rankDiff = statusRankForCanonical(a.status) - statusRankForCanonical(b.status);\n if (rankDiff !== 0) return rankDiff;\n const updA = parseTaskUpdatedMs(a.updated) ?? Number.NEGATIVE_INFINITY;\n const updB = parseTaskUpdatedMs(b.updated) ?? Number.NEGATIVE_INFINITY;\n if (updA !== updB) return updB - updA;\n if (a.label.length !== b.label.length) return a.label.length - b.label.length;\n return a.label.localeCompare(b.label);\n });\n const canonical = ranked[0];\n const normalized = normalizeTaskString(canonical.description) || normalizeTaskString(canonical.label);\n const labels = ranked.map((m) => m.label);\n duplicates.push({\n normalized,\n canonicalLabel: canonical.label,\n labels,\n });\n for (const row of ranked.slice(1)) {\n if (actionsByLabel.has(row.label)) continue;\n actionsByLabel.set(row.label, {\n label: row.label,\n kind: \"superseded-duplicate\",\n toStatus: \"superseded\",\n canonicalLabel: canonical.label,\n reason: `Superseded duplicate of [${canonical.label}] after normalization.`,\n });\n }\n }\n\n stale.sort((a, b) => a.label.localeCompare(b.label));\n duplicates.sort((a, b) => a.canonicalLabel.localeCompare(b.canonicalLabel));\n const actions = [...actionsByLabel.values()].sort((a, b) => a.label.localeCompare(b.label));\n return {\n olderThanMinutes,\n duplicates,\n stale,\n actions,\n };\n}\n\nfunction capRows<T extends ActiveTaskEntry>(rows: T[], max?: number): { rows: T[]; omitted: number } {\n if (max === undefined || rows.length <= max) return { rows, omitted: 0 };\n return { rows: rows.slice(0, max), omitted: rows.length - max };\n}\n\n/** Sectioned markdown for facts projection (## Active / ## Stale — revisit / ## Completed). */\nexport function buildFactsSectionedMarkdownBody(\n hot: ActiveTaskEntry[],\n stale: ActiveTaskEntry[],\n completed: ActiveTaskEntry[],\n omitted: { active: number; stale: number; completed: number },\n): string {\n if (\n hot.length === 0 &&\n stale.length === 0 &&\n completed.length === 0 &&\n omitted.active === 0 &&\n omitted.stale === 0 &&\n omitted.completed === 0\n ) {\n return \"# ACTIVE-TASKS.md — Working Memory\\n\\n## Active\\n\\n_No active tasks._\\n\";\n }\n\n const parts: string[] = [\"# ACTIVE-TASKS.md — Working Memory\\n\"];\n\n if (hot.length > 0 || omitted.active > 0) {\n parts.push(\"## Active\\n\");\n if (hot.length === 0 && omitted.active === 0) {\n parts.push(\"_None._\\n\");\n } else {\n for (const entry of hot) {\n parts.push(serializeTaskEntry(entry));\n parts.push(\"\");\n }\n }\n if (omitted.active > 0) {\n parts.push(`> ${omitted.active} ${OMITTED_CAP_NOTE}\\n\\n`);\n } else if (hot.length > 0) {\n parts.push(\"\");\n }\n }\n\n if (stale.length > 0 || omitted.stale > 0) {\n parts.push(\"## Stale — revisit\\n\");\n if (stale.length === 0 && omitted.stale === 0) {\n parts.push(\"_None._\\n\");\n } else {\n for (const entry of stale) {\n parts.push(serializeTaskEntry(entry));\n parts.push(\"\");\n }\n }\n if (omitted.stale > 0) {\n parts.push(`> ${omitted.stale} ${OMITTED_CAP_NOTE}\\n\\n`);\n } else if (stale.length > 0) {\n parts.push(\"\");\n }\n }\n\n if (completed.length > 0 || omitted.completed > 0) {\n parts.push(\"## Completed\\n\");\n for (const entry of completed) {\n parts.push(serializeTaskEntry(entry));\n parts.push(\"\");\n }\n if (omitted.completed > 0) {\n parts.push(`> ${omitted.completed} ${OMITTED_CAP_NOTE}\\n`);\n }\n }\n\n return parts.join(\"\\n\");\n}\n\nexport function taskEntityKey(entity: string, key: string): string {\n return `${canonicalLabel(entity)}\\u0000${key.trim()}`;\n}\n\nexport async function upsertProjectTaskKey(\n factsDb: FactsDB,\n vectorDb: VectorDB,\n embeddings: EmbeddingProvider,\n entity: string,\n key: string,\n value: string,\n log?: { warn?: (m: string) => void },\n opts?: { latestByEntityKey?: Map<string, MemoryEntry> },\n): Promise<void> {\n // Normalise entity labels on write (trim + lowercase) to prevent case-variant\n // collisions (e.g. \"Humanizer\" and \"humanizer\" stored as separate entities).\n const normalizedEntity = entity.trim().toLowerCase();\n const canonical = canonicalLabel(normalizedEntity);\n const cacheKey = taskEntityKey(normalizedEntity, key);\n let previous: MemoryEntry | undefined;\n const cached = opts?.latestByEntityKey?.get(cacheKey);\n // Keep supersession scoped to active-task ledger rows only; cached\n // memory_store rows must never be retired by active-task checkpoints.\n if (cached?.source === \"active-task\") {\n previous = cached;\n } else if (!opts?.latestByEntityKey || cached || key === \"status\") {\n // Query database if: (a) no cache was provided, or (b) cache had a non-active-task\n // entry (which we must not supersede, but an active-task row may still exist), or\n // (c) this is a status write and cache missed (status must not become append-only).\n const facts = factsDb.listFactsByCategory(TASK_LEDGER_CATEGORY, 8000);\n const nowSec = Math.floor(Date.now() / 1000);\n // Supersede by canonical label to match grouping logic (strips suffixes, normalizes separators).\n // Only supersede facts from the active-task ledger (source:\"active-task\"), not memory_store.\n // Exclude expired facts to match projection logic (groupProjectFactsByEntity).\n const same = facts.filter(\n (f) =>\n f.source === \"active-task\" &&\n canonicalLabel(f.entity ?? \"\") === canonical &&\n (f.key ?? \"\") === key &&\n !(typeof f.expiresAt === \"number\" && Number.isFinite(f.expiresAt) && f.expiresAt <= nowSec),\n );\n same.sort((a, b) => b.createdAt - a.createdAt);\n previous = same[0];\n }\n const text = `Task [${normalizedEntity}] ${key}: ${value}`;\n const entry = factsDb.store({\n text,\n category: TASK_LEDGER_CATEGORY,\n importance: CLI_STORE_IMPORTANCE,\n entity: normalizedEntity,\n key,\n value,\n source: \"active-task\",\n decayClass: \"permanent\",\n provenanceJson: activeTaskProvenance(canonical),\n });\n if (previous) {\n factsDb.supersede(previous.id, entry.id);\n }\n opts?.latestByEntityKey?.set(cacheKey, entry);\n try {\n const vector = await embeddings.embed(text);\n factsDb.setEmbeddingModel(entry.id, embeddings.modelName);\n if (!(await vectorDb.hasDuplicate(vector))) {\n await vectorDb.store({\n text,\n vector,\n importance: CLI_STORE_IMPORTANCE,\n category: TASK_LEDGER_CATEGORY,\n id: entry.id,\n });\n }\n } catch (err) {\n log?.warn?.(`memory-hybrid: active-task ledger vector store failed: ${err}`);\n }\n}\n\n/** Persist one task row to project facts (multi-key upsert). */\nexport async function syncActiveTaskEntryToFacts(\n factsDb: FactsDB,\n vectorDb: VectorDB,\n embeddings: EmbeddingProvider,\n entry: ActiveTaskEntry,\n log?: { warn?: (m: string) => void },\n opts?: {\n statusOverride?: string;\n latestByEntityKey?: Map<string, MemoryEntry>;\n },\n): Promise<void> {\n const entity = entry.label;\n const upsertOpts = { latestByEntityKey: opts?.latestByEntityKey };\n await upsertProjectTaskKey(factsDb, vectorDb, embeddings, entity, \"title\", entry.description, log, upsertOpts);\n const statusValue = opts?.statusOverride?.trim() || displayStatusToFact(entry.status);\n await upsertProjectTaskKey(factsDb, vectorDb, embeddings, entity, \"status\", statusValue, log, upsertOpts);\n await upsertProjectTaskKey(factsDb, vectorDb, embeddings, entity, \"next\", entry.next?.trim() || \"\", log, upsertOpts);\n await upsertProjectTaskKey(\n factsDb,\n vectorDb,\n embeddings,\n entity,\n \"related_session\",\n entry.subagent?.trim() || \"\",\n log,\n upsertOpts,\n );\n await upsertProjectTaskKey(factsDb, vectorDb, embeddings, entity, \"task_updated\", entry.updated, log, upsertOpts);\n await upsertProjectTaskKey(factsDb, vectorDb, embeddings, entity, \"started\", entry.started, log, upsertOpts);\n await upsertProjectTaskKey(\n factsDb,\n vectorDb,\n embeddings,\n entity,\n \"branch\",\n entry.branch?.trim() || \"\",\n log,\n upsertOpts,\n );\n await upsertProjectTaskKey(\n factsDb,\n vectorDb,\n embeddings,\n entity,\n \"stash_commit\",\n entry.stashCommit?.trim() || \"\",\n log,\n upsertOpts,\n );\n await upsertProjectTaskKey(\n factsDb,\n vectorDb,\n embeddings,\n entity,\n \"handoff\",\n entry.handoff ? JSON.stringify(entry.handoff) : \"\",\n log,\n upsertOpts,\n );\n}\n\nexport interface ActiveTaskHygieneApplyResult {\n appliedCount: number;\n auditFactId?: string;\n /** Tasks whose live PR state has an actionable blocker (unresolved threads, red CI, etc.) */\n prBlockerTasks?: Array<{\n label: string;\n owner: string;\n repo: string;\n number: number;\n blockerStatus: string;\n reason: string;\n }>;\n}\n\nexport interface ActiveTaskCanonicalBackfillResult {\n scannedFacts: number;\n canonicalLabelsUpdated: number;\n supersededFacts: number;\n duplicateGroups: Array<{ canonicalLabel: string; facts: number; activeBefore: number; superseded: number }>;\n}\n\nexport function backfillActiveTaskCanonicalLabels(\n factsDb: FactsDB,\n opts: ActiveTaskCanonicalBackfillOptions = {},\n): ActiveTaskCanonicalBackfillResult {\n const facts = factsDb.listFactsByCategory(TASK_LEDGER_CATEGORY, opts.limit ?? 50_000);\n const groups = new Map<string, MemoryEntry[]>();\n let canonicalLabelsUpdated = 0;\n\n const verifiedLookup = (() => {\n try {\n return factsDb.getRawDb().prepare(\"SELECT 1 FROM verified_facts WHERE fact_id = ? LIMIT 1\");\n } catch {\n return null;\n }\n })();\n\n for (const fact of facts) {\n if (!fact.entity?.trim()) continue;\n const canonical = factCanonicalLabel(fact);\n const existing = readCanonicalLabelFromFact(fact);\n if (existing !== canonical) {\n canonicalLabelsUpdated++;\n if (!opts.dryRun) {\n const rawDb = (\n factsDb as unknown as {\n getRawDb?: () => { prepare: (sql: string) => { run: (...args: unknown[]) => unknown } };\n }\n ).getRawDb?.();\n rawDb\n ?.prepare(\"UPDATE facts SET provenance_json = ? WHERE id = ?\")\n .run(activeTaskProvenance(canonical, fact.provenanceJson), fact.id);\n }\n }\n const arr = groups.get(canonical) ?? [];\n arr.push(fact);\n groups.set(canonical, arr);\n }\n\n const duplicateGroups: ActiveTaskCanonicalBackfillResult[\"duplicateGroups\"] = [];\n let supersededFacts = 0;\n const nowSec = Math.floor(Date.now() / 1000);\n for (const [canonical, rows] of groups) {\n const activeBefore = rows.filter((f) => !f.supersededAt).length;\n let groupSuperseded = 0;\n const byKey = new Map<string, MemoryEntry[]>();\n for (const row of rows) {\n const key = (row.key ?? \"\").trim() || \"_body\";\n const arr = byKey.get(key) ?? [];\n arr.push(row);\n byKey.set(key, arr);\n }\n let terminalStatus: MemoryEntry | undefined;\n const statusRows = byKey.get(\"status\") ?? [];\n for (const row of statusRows) {\n const isExpired = row.expiresAt !== null && row.expiresAt <= nowSec;\n if (\n !isExpired &&\n !row.supersededAt &&\n isTerminalFactStatus(row.value ?? row.text ?? \"\") &&\n (!terminalStatus || row.createdAt > terminalStatus.createdAt)\n ) {\n terminalStatus = row;\n }\n }\n const supersede = (oldId: string, newId: string | null): void => {\n if (verifiedLookup?.get(oldId)) {\n return;\n }\n supersededFacts++;\n groupSuperseded++;\n if (!opts.dryRun) factsDb.supersede(oldId, newId);\n };\n if (terminalStatus) {\n for (const [key, sameKey] of byKey) {\n const ranked = [...sameKey].sort((a, b) => b.createdAt - a.createdAt);\n const keeper = key === \"status\" ? terminalStatus : ranked[0];\n for (const row of ranked) {\n if (row.id !== keeper.id && !row.supersededAt) supersede(row.id, keeper.id);\n }\n }\n } else {\n for (const sameKey of byKey.values()) {\n const ranked = [...sameKey].sort((a, b) => b.createdAt - a.createdAt);\n const keeper = ranked[0];\n for (const row of ranked.slice(1)) {\n if (!row.supersededAt) supersede(row.id, keeper.id);\n }\n }\n }\n if (activeBefore > 1 || groupSuperseded > 0) {\n duplicateGroups.push({\n canonicalLabel: canonical,\n facts: rows.length,\n activeBefore,\n superseded: groupSuperseded,\n });\n }\n }\n\n if (!opts.dryRun) {\n (factsDb as unknown as { invalidateSupersededTextsCache?: () => void }).invalidateSupersededTextsCache?.();\n }\n\n return {\n scannedFacts: facts.length,\n canonicalLabelsUpdated,\n supersededFacts,\n duplicateGroups: duplicateGroups.sort((a, b) => a.canonicalLabel.localeCompare(b.canonicalLabel)),\n };\n}\n\nexport async function applyActiveTaskHygieneFacts(\n factsDb: FactsDB,\n vectorDb: VectorDB,\n embeddings: EmbeddingProvider,\n plan: ActiveTaskHygienePlan,\n opts: {\n flushOnComplete?: boolean;\n memoryDir?: string;\n log?: { warn?: (m: string) => void };\n } = {},\n): Promise<ActiveTaskHygieneApplyResult> {\n if (plan.actions.length === 0) {\n return { appliedCount: 0 };\n }\n\n const runAt = new Date().toISOString();\n const { active } = loadTaskLedgerFromFacts(factsDb);\n const byLabel = new Map(active.map((task) => [task.label, task] as const));\n const latestByEntityKey = new Map<string, MemoryEntry>();\n const nowSec = Math.floor(Date.now() / 1000);\n // Only index active-task ledger facts, not memory_store project facts.\n // Exclude expired facts to match projection logic (groupProjectFactsByEntity).\n for (const fact of factsDb.listFactsByCategory(TASK_LEDGER_CATEGORY, 8000)) {\n if (fact.source !== \"active-task\") continue;\n if (typeof fact.expiresAt === \"number\" && Number.isFinite(fact.expiresAt) && fact.expiresAt <= nowSec) continue;\n const entity = fact.entity?.trim().toLowerCase();\n if (!entity) continue;\n const key = (fact.key ?? \"\").trim();\n const cacheKey = taskEntityKey(entity, key);\n const prev = latestByEntityKey.get(cacheKey);\n if (!prev || fact.createdAt > prev.createdAt) {\n latestByEntityKey.set(cacheKey, fact);\n }\n }\n let appliedCount = 0;\n const prBlockerTasks: ActiveTaskHygieneApplyResult[\"prBlockerTasks\"] = [];\n\n for (const action of plan.actions) {\n const task = byLabel.get(action.label);\n if (!task) continue;\n\n if (action.kind === \"pr-live-blocker\") {\n // Update task in-place: set status to \"Waiting\" and record the live blocker reason.\n // Do NOT complete the task — it is actively blocked and needs Forge attention.\n const updatedEntry: ActiveTaskEntry = {\n ...task,\n status: \"Waiting\",\n updated: runAt,\n next: action.reason,\n // Keep subagent so we know who is working on it\n subagent: task.subagent,\n };\n await syncActiveTaskEntryToFacts(factsDb, vectorDb, embeddings, updatedEntry, opts.log, {\n statusOverride: displayStatusToFact(\"Waiting\"),\n latestByEntityKey,\n });\n // Record so caller can dispatch Forge for these tasks\n const parsed = action.prBlockerStatus ?? \"\";\n const [actualStatus, prRef] = parsed.includes(\"|\") ? parsed.split(\"|\", 2) : [\"\", parsed];\n const prParts = prRef.split(\":\");\n prBlockerTasks.push({\n label: task.label,\n owner: prParts[0] ?? \"\",\n repo: prParts[1] ?? \"\",\n number: Number(prParts[2] ?? 0) || 0,\n blockerStatus: actualStatus,\n reason: action.reason,\n });\n appliedCount++;\n continue;\n }\n\n const doneEntry: ActiveTaskEntry = {\n ...task,\n status: \"Done\",\n updated: runAt,\n next: action.reason,\n subagent: action.kind === \"dead-session\" ? undefined : task.subagent,\n };\n await syncActiveTaskEntryToFacts(factsDb, vectorDb, embeddings, doneEntry, opts.log, {\n statusOverride: action.toStatus,\n latestByEntityKey,\n });\n if (action.kind === \"superseded-duplicate\" && action.canonicalLabel) {\n await upsertProjectTaskKey(\n factsDb,\n vectorDb,\n embeddings,\n doneEntry.label,\n \"superseded_by\",\n action.canonicalLabel,\n opts.log,\n { latestByEntityKey },\n );\n }\n if (opts.flushOnComplete && opts.memoryDir) {\n await flushCompletedTaskToMemory(doneEntry, opts.memoryDir).catch(() => {});\n }\n appliedCount++;\n }\n\n const audit = {\n runAt,\n olderThanMinutes: plan.olderThanMinutes,\n duplicates: plan.duplicates,\n stale: plan.stale,\n actions: plan.actions,\n appliedCount,\n };\n const auditFact = factsDb.store({\n text: `Active-task hygiene audit ${runAt}: ${plan.actions.length} action(s), ${plan.duplicates.length} duplicate group(s), ${appliedCount} applied.`,\n category: \"episode\",\n importance: CLI_STORE_IMPORTANCE,\n source: \"active-task-hygiene\",\n decayClass: \"permanent\",\n entity: `active-task-hygiene:${runAt}`,\n key: \"report\",\n value: JSON.stringify(audit),\n });\n\n return { appliedCount, auditFactId: auditFact.id, prBlockerTasks };\n}\n\nexport async function renderActiveTaskMarkdownFile(\n factsDb: FactsDB,\n staleMinutes: number,\n filePath: string,\n projection: ActiveTaskProjectionConfig,\n logger?: { debug?: (message: string) => void },\n opts: { includeCompleted?: boolean } = {},\n): Promise<void> {\n const renderStartMs = Date.now();\n const includeCompleted = opts.includeCompleted === true;\n let { active, completed, metrics } = loadTaskLedgerFromFactsWithMetrics(\n factsDb,\n 8000,\n ACTIVE_TASK_PROJECTION_GLOBAL_SCOPE_FILTER,\n );\n active = applyActiveTaskProjectionFilters(active, projection);\n completed = includeCompleted ? applyActiveTaskProjectionFilters(completed, projection) : [];\n active = detectStaleTasks(active, staleMinutes);\n\n const hotRaw = active.filter((t) => !t.stale);\n const staleRaw = active.filter((t) => t.stale);\n\n let capAct: { rows: ActiveTaskEntry[]; omitted: number };\n let capStale: { rows: ActiveTaskEntry[]; omitted: number };\n let capDone: { rows: ActiveTaskEntry[]; omitted: number };\n let combinedActiveOmitted = 0;\n\n if (projection.sectioned) {\n capAct = capRows(hotRaw, projection.maxRowsPerSection);\n capStale = capRows(staleRaw, projection.maxRowsPerSection);\n capDone = capRows(completed, projection.maxRowsPerSection);\n } else {\n const combinedActive = [...hotRaw, ...staleRaw];\n const cappedCombined = capRows(combinedActive, projection.maxRowsPerSection);\n capAct = { rows: cappedCombined.rows.filter((t) => !t.stale), omitted: 0 };\n capStale = { rows: cappedCombined.rows.filter((t) => t.stale), omitted: 0 };\n combinedActiveOmitted = cappedCombined.omitted;\n capDone = capRows(completed, projection.maxRowsPerSection);\n }\n\n const body = projection.sectioned\n ? buildFactsSectionedMarkdownBody(capAct.rows, capStale.rows, capDone.rows, {\n active: capAct.omitted,\n stale: capStale.omitted,\n completed: capDone.omitted,\n })\n : serializeActiveTaskFile([...capAct.rows, ...capStale.rows], capDone.rows, undefined, {\n active: combinedActiveOmitted,\n completed: capDone.omitted,\n });\n\n const lines = body.split(\"\\n\");\n lines.splice(\n 1,\n 0,\n \"\",\n \"> **Projection** of hybrid-memory `category:project` facts (`activeTask.ledger: facts`). Regenerate via `hybrid-mem active-tasks render`.\",\n includeCompleted\n ? \"> **History mode:** includes terminal rows (`--include-completed`) for audit context.\"\n : \"> **Default mode:** hides terminal rows; `ACTIVE-TASKS.md` is for active/stale work only. Use `active-tasks render --include-completed` for audit/history.\",\n \"> **Timestamps:** **Started** / **Updated** use stored fact fields (`started`, `task_started`, `task_updated`, …) or SQLite row times (min / max `createdAt` per task). The render clock is not used. Missing values show as **Unknown** and count as stale under `staleThreshold`.\",\n \"> **Operators:** Update or close tasks via `memory_store` / project facts; run `hybrid-mem active-tasks reconcile` when session rows are obsolete; then `active-tasks render`. See `docs/ACTIVE-TASKS-PROJECTION.md`.\",\n \"\",\n );\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, lines.join(\"\\n\"), \"utf-8\");\n try {\n await clearActiveTaskProjectionStale(filePath);\n } catch {\n // Keep render success best-effort even when marker cleanup fails.\n }\n logger?.debug?.(\n `memory-hybrid: active-task projection rowsFetched=${metrics.projectRowsFetched} groupedEntities=${metrics.groupedEntityCount} duplicatesCollapsed=${metrics.duplicateRowsCollapsed} activeCandidates=${metrics.activeCandidates} staleBucketed=${staleRaw.length} renderedActive=${capAct.rows.length} renderedStale=${capStale.rows.length} renderedCompleted=${capDone.rows.length} elapsedMs=${Date.now() - renderStartMs}`,\n );\n}\n\n/**\n * Apply pending sub-agent task signals to the facts ledger (no markdown).\n */\nexport async function consumePendingTaskSignalsFacts(\n workspaceRoot: string,\n staleMinutes: number,\n flushOnComplete: boolean,\n factsDb: FactsDB,\n vectorDb: VectorDB,\n embeddings: EmbeddingProvider,\n logger?: { info?: (msg: string) => void; warn?: (msg: string) => void },\n): Promise<void> {\n const memoryDir = join(workspaceRoot, \"memory\");\n let signals: PendingTaskSignal[];\n try {\n signals = await readPendingSignals(memoryDir);\n } catch (err) {\n logger?.warn?.(`memory-hybrid: failed to read pending task signals: ${err}`);\n return;\n }\n if (signals.length === 0) return;\n\n const signalTtlMs = Math.max(staleMinutes * 60 * 1000, 24 * 60 * 60 * 1000);\n const nowMs = Date.now();\n const isSignalExpired = (signal: PendingTaskSignal): boolean => {\n const parsed = Date.parse(signal.timestamp);\n if (Number.isNaN(parsed)) return true;\n return nowMs - parsed > signalTtlMs;\n };\n\n signals = [...signals].sort((a, b) => {\n const at = Date.parse(a.timestamp);\n const bt = Date.parse(b.timestamp);\n if (Number.isNaN(at) || Number.isNaN(bt)) return a._filePath.localeCompare(b._filePath);\n return at === bt ? a._filePath.localeCompare(b._filePath) : at - bt;\n });\n\n const { active: rawActive, completed: rawCompleted } = loadTaskLedgerFromFacts(factsDb);\n const active = detectStaleTasks(rawActive, staleMinutes);\n\n const findMatchingTask = (activeEntries: ActiveTaskEntry[], signal: PendingTaskSignal): ActiveTaskEntry | null => {\n const normalizedTaskRef = signal.taskRef.trim().toLowerCase();\n const byLabel = activeEntries.filter((t) => t.label.toLowerCase() === normalizedTaskRef);\n if (byLabel.length === 1) return byLabel[0];\n if (byLabel.length > 1) {\n logger?.warn?.(`memory-hybrid: multiple active tasks share label ${signal.taskRef}; leaving signal pending`);\n return null;\n }\n const byDescription = activeEntries.filter((t) => t.description === signal.taskRef);\n if (byDescription.length === 1) {\n logger?.warn?.(\n `memory-hybrid: matched signal for \"${signal.taskRef}\" by description (not label); sub-agents should use the exact task label in taskRef for reliable matching`,\n );\n return byDescription[0];\n }\n if (byDescription.length > 1) {\n logger?.warn?.(\n `memory-hybrid: multiple active tasks match description ${signal.taskRef}; leaving signal pending`,\n );\n return null;\n }\n return null;\n };\n\n const touched = new Set<string>();\n let updatedActive = [...active];\n const updatedCompleted = [...rawCompleted];\n const processedSignals: PendingTaskSignal[] = [];\n const expiredSignals: PendingTaskSignal[] = [];\n const completedToFlush: ActiveTaskEntry[] = [];\n\n for (const signal of signals) {\n try {\n const updatedTimestamp = (() => {\n const t = Date.parse(signal.timestamp);\n return Number.isNaN(t) ? new Date().toISOString() : signal.timestamp;\n })();\n\n const existing = findMatchingTask(updatedActive, signal);\n if (!existing) {\n if (isSignalExpired(signal)) expiredSignals.push(signal);\n else logger?.warn?.(`memory-hybrid: no matching active task for signal ${signal.taskRef}; leaving pending`);\n continue;\n }\n\n if (signal.signal === \"completed\") {\n const { updated, completed } = completeTask(updatedActive, existing.label);\n if (completed) {\n updatedActive = updated;\n updatedCompleted.push({\n ...completed,\n updated: updatedTimestamp,\n handoff: signal._handoff ?? completed.handoff,\n });\n processedSignals.push(signal);\n completedToFlush.push({\n ...completed,\n updated: updatedTimestamp,\n handoff: signal._handoff ?? completed.handoff,\n });\n touched.add(existing.label);\n }\n continue;\n }\n\n if (signal.signal !== \"blocked\" && signal.signal !== \"escalate\" && signal.signal !== \"update\") {\n if (isSignalExpired(signal)) expiredSignals.push(signal);\n else\n logger?.warn?.(\n `memory-hybrid: unhandled task signal \"${signal.signal}\" for ${signal.taskRef}; leaving pending`,\n );\n continue;\n }\n\n const newStatus: ActiveTaskEntry[\"status\"] =\n signal.signal === \"blocked\" ? \"Stalled\" : signal.signal === \"escalate\" ? \"Waiting\" : existing.status;\n const updatedEntry: ActiveTaskEntry = {\n ...existing,\n status: newStatus,\n next: signal.summary ? `[Signal: ${signal.signal}] ${signal.summary}` : existing.next,\n updated: updatedTimestamp,\n handoff: signal._handoff ?? existing.handoff,\n };\n updatedActive = upsertTask(updatedActive, updatedEntry, true);\n processedSignals.push(signal);\n touched.add(existing.label);\n } catch (err) {\n logger?.warn?.(`memory-hybrid: failed to process signal from ${signal._filePath}: ${err}`);\n }\n }\n\n if (processedSignals.length === 0) {\n if (expiredSignals.length > 0) {\n for (const signal of expiredSignals) await deleteSignal(signal._filePath).catch(() => {});\n logger?.info?.(`memory-hybrid: pruned ${expiredSignals.length} expired task signal(s) (facts ledger)`);\n }\n return;\n }\n\n try {\n for (const label of touched) {\n const a = updatedActive.find((t) => t.label === label);\n const c = updatedCompleted.find((t) => t.label === label);\n const entry = a ?? c;\n if (entry) {\n await syncActiveTaskEntryToFacts(factsDb, vectorDb, embeddings, entry, logger);\n }\n }\n } catch (err) {\n logger?.warn?.(`memory-hybrid: failed to persist facts after task signals: ${err}`);\n return;\n }\n\n for (const signal of processedSignals) await deleteSignal(signal._filePath).catch(() => {});\n for (const signal of expiredSignals) await deleteSignal(signal._filePath).catch(() => {});\n if (flushOnComplete && completedToFlush.length > 0) {\n for (const completed of completedToFlush) {\n await flushCompletedTaskToMemory(completed, memoryDir).catch(() => {});\n }\n }\n logger?.info?.(`memory-hybrid: consumed ${processedSignals.length} pending task signal(s) into facts ledger`);\n}\n\nexport interface FactsReconcileResult {\n reconciledLabels: string[];\n wrote: boolean;\n}\n\nexport async function reconcileActiveTaskInProgressSessionsFacts(\n factsDb: FactsDB,\n vectorDb: VectorDB,\n embeddings: EmbeddingProvider,\n staleMinutes: number,\n opts: {\n openclawHome?: string;\n flushOnComplete?: boolean;\n memoryDir?: string;\n dryRun?: boolean;\n log?: { warn?: (m: string) => void };\n } = {},\n): Promise<FactsReconcileResult> {\n const { active } = readActiveTaskRowsFromFacts(factsDb, staleMinutes);\n const reconciledLabels: string[] = [];\n const toFlush: ActiveTaskEntry[] = [];\n const openclawHome = opts.openclawHome;\n\n for (const task of active) {\n if (task.status !== \"In progress\") {\n continue;\n }\n const ref = task.subagent?.trim();\n if (!ref || !looksLikeOpenClawSessionRef(ref)) {\n continue;\n }\n const present = await isOpenClawSessionLikelyPresent(ref, openclawHome);\n if (present) {\n continue;\n }\n const now = new Date().toISOString();\n const doneEntry: ActiveTaskEntry = {\n ...task,\n status: \"Done\",\n updated: now,\n next: `Auto-reconciled: session transcript not found for ${ref} (subagent bookkeeping cleanup).`,\n subagent: undefined,\n };\n reconciledLabels.push(task.label);\n toFlush.push(doneEntry);\n }\n\n if (reconciledLabels.length === 0 || opts.dryRun) {\n return { reconciledLabels, wrote: false };\n }\n\n for (const entry of toFlush) {\n await syncActiveTaskEntryToFacts(factsDb, vectorDb, embeddings, entry, opts.log);\n }\n\n if (opts.flushOnComplete && opts.memoryDir) {\n for (const entry of toFlush) {\n await flushCompletedTaskToMemory(entry, opts.memoryDir).catch(() => {});\n }\n }\n\n return { reconciledLabels, wrote: true };\n}\n\n// -----------------------------------------------------------------------------------------\n// Issue #1625: live-state reconciliation\n// Fetches live GitHub issue/PR state for active tasks that reference external resources\n// and checkpoints terminal live state into the task ledger so agents don't repeat audits.\n// -----------------------------------------------------------------------------------------\n\nexport interface ReconcileLiveStateOptions {\n /** Maximum GitHub API calls per invocation (budget guard). */\n maxRequests?: number;\n /** AbortSignal for cancellation. */\n signal?: AbortSignal;\n log?: {\n debug?: (m: string) => void;\n warn?: (m: string) => void;\n info?: (m: string) => void;\n };\n}\n\ninterface LiveStateRef {\n owner: string;\n repo: string;\n number: number;\n /** \"pr\" or \"issue\" */\n kind: \"pr\" | \"issue\";\n}\n\ntype LiveStateFetchStatus = \"merged\" | \"closed\" | \"open\" | \"unknown\" | \"unavailable\";\n\nconst LIVE_STATE_GH_TIMEOUT_MS = 20_000;\nlet liveStateBudgetCursor = 0;\n\nfunction hasGitHubToken(): boolean {\n return (getEnv(\"GITHUB_TOKEN\") ?? getEnv(\"GH_TOKEN\") ?? \"\").trim().length > 0;\n}\n\nasync function runGhJsonCommand(args: string[], signal?: AbortSignal): Promise<unknown> {\n const ac = new AbortController();\n const timeout = setTimeout(() => ac.abort(), LIVE_STATE_GH_TIMEOUT_MS);\n const onAbort = (): void => ac.abort();\n if (signal) {\n if (signal.aborted) ac.abort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n try {\n const { stdout } = await execFileAsync(\"gh\", args, {\n encoding: \"utf-8\",\n signal: ac.signal,\n timeout: LIVE_STATE_GH_TIMEOUT_MS,\n });\n const raw = typeof stdout === \"string\" ? stdout.trim() : \"\";\n if (!raw) return null;\n return JSON.parse(raw);\n } finally {\n clearTimeout(timeout);\n if (signal) signal.removeEventListener(\"abort\", onAbort);\n }\n}\n\nfunction selectRoundRobinEntries<T>(entries: T[], maxRequests: number): T[] {\n if (entries.length === 0 || maxRequests <= 0) return [];\n if (entries.length <= maxRequests) {\n liveStateBudgetCursor = 0;\n return entries;\n }\n const start = ((liveStateBudgetCursor % entries.length) + entries.length) % entries.length;\n const selected: T[] = [];\n for (let i = 0; i < maxRequests; i++) {\n selected.push(entries[(start + i) % entries.length]);\n }\n liveStateBudgetCursor = (start + maxRequests) % entries.length;\n return selected;\n}\n\n/** Extract \"owner/repo#N\" references from a task label or description.\n * Handles: \"owner/repo#123\", \"owner/repo pull request #123\" (case-insensitive).\n * Returns one ref per task (first match wins). */\nfunction extractLiveStateRef(task: ActiveTaskEntry): LiveStateRef | null {\n const text = `${task.label} ${task.description}`;\n // Pattern: \"owner/repo issue #123\" or \"owner/repo pull request #123\" (GitHub's own URL form)\n // Check this first to avoid ambiguity with the bare owner/repo#N pattern\n const issueMatch = text.match(/\\b([a-zA-Z0-9_.-]+)\\/([a-zA-Z0-9_.-]+)\\s+(issue|pull\\s+request|pr)\\s*#(\\d+)\\b/i);\n if (issueMatch) {\n const number = Number(issueMatch[4]);\n if (!Number.isFinite(number) || number < 1) return null;\n const kindString = issueMatch[3];\n const kind: \"pr\" | \"issue\" = /pull\\s*request|pr/i.test(kindString) ? \"pr\" : \"issue\";\n return { owner: issueMatch[1], repo: issueMatch[2], number, kind };\n }\n // Pattern: owner/repo#number (most common - defaults to issue as per GitHub convention)\n const refMatch = text.match(/\\b([a-zA-Z0-9_.-]+)\\/([a-zA-Z0-9_.-]+)\\s*#(\\d+)\\b/i);\n if (refMatch) {\n const number = Number(refMatch[3]);\n if (!Number.isFinite(number) || number < 1) return null;\n return { owner: refMatch[1], repo: refMatch[2], number, kind: \"issue\" };\n }\n return null;\n}\n\n/** Fetch live state for a GitHub PR via `gh pr view`.\n * Returns \"merged\", \"closed\", \"open\", \"unknown\", or \"unavailable\". */\nasync function fetchLivePrState(\n owner: string,\n repo: string,\n prNumber: number,\n signal?: AbortSignal,\n): Promise<LiveStateFetchStatus> {\n if (!hasGitHubToken()) return \"unavailable\";\n try {\n const payload = await runGhJsonCommand(\n [\"pr\", \"view\", String(prNumber), \"--repo\", `${owner}/${repo}`, \"--json\", \"state,mergedAt,closedAt\"],\n signal,\n );\n const data = payload as { state?: unknown; mergedAt?: unknown; closedAt?: unknown } | null;\n const state = typeof data?.state === \"string\" ? data.state.trim().toUpperCase() : \"\";\n const mergedAt = typeof data?.mergedAt === \"string\" && data.mergedAt.trim().length > 0;\n const closedAt = typeof data?.closedAt === \"string\" && data.closedAt.trim().length > 0;\n if (state === \"MERGED\" || mergedAt) return \"merged\";\n if (state === \"CLOSED\" || (closedAt && !mergedAt)) return \"closed\";\n if (state === \"OPEN\") return \"open\";\n return \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\n/** Fetch live state for a GitHub issue.\n * Returns \"closed\" when the issue is closed, \"open\" when still open, \"unknown\" when\n * state cannot be determined, or \"unavailable\" when the token/network is missing. */\nasync function fetchLiveIssueState(\n owner: string,\n repo: string,\n issueNumber: number,\n signal?: AbortSignal,\n): Promise<LiveStateFetchStatus> {\n if (!hasGitHubToken()) return \"unavailable\";\n try {\n const payload = await runGhJsonCommand(\n [\"issue\", \"view\", String(issueNumber), \"--repo\", `${owner}/${repo}`, \"--json\", \"state\"],\n signal,\n );\n const data = payload as { state?: unknown } | null;\n const state = typeof data?.state === \"string\" ? data.state.trim().toUpperCase() : \"\";\n if (state === \"CLOSED\") return \"closed\";\n if (state === \"OPEN\") return \"open\";\n return \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\n/** Reconcile active tasks with live GitHub state.\n *\n * For each active task that references an external GitHub issue or PR:\n * - Fetch the current live state from GitHub.\n * - If the live state is terminal (issue closed, PR merged/closed), checkpoint the\n * task as \"Done\" in the facts ledger with evidence in `next` and `task_updated`.\n * - If live state is non-terminal/unknown, leave the task unchanged.\n *\n * This is intended to run during normal reconcile/hygiene/render cycles when enabled.\n * The function is safe to call repeatedly: it only checkpoints terminal\n * live-state transitions (merged/closed) and degrades gracefully when GitHub is unavailable.\n *\n * Returns the number of tasks that were updated (for telemetry).\n */\nexport async function reconcileActiveTaskLiveState(\n factsDb: FactsDB,\n vectorDb: VectorDB,\n embeddings: EmbeddingProvider,\n opts: ReconcileLiveStateOptions = {},\n): Promise<{ updatedCount: number; checkedCount: number; skippedCount: number }> {\n const maxRequests = Math.max(1, Math.min(opts.maxRequests ?? 20, 50));\n const signal = opts.signal;\n\n const { active } = loadTaskLedgerFromFacts(factsDb);\n let updatedCount = 0;\n let skippedCount = 0;\n\n // Collect all tasks with refs (allow multiple tasks per ref)\n const refMap = new Map<string, { tasks: ActiveTaskEntry[]; ref: LiveStateRef }>();\n for (const task of active) {\n if (task.status === \"Done\" || task.status === \"Failed\") continue;\n const ref = extractLiveStateRef(task);\n if (!ref) {\n skippedCount++;\n continue;\n }\n const key = `${ref.kind}:${ref.owner}/${ref.repo}#${ref.number}`;\n const existing = refMap.get(key);\n if (existing) {\n existing.tasks.push(task);\n } else {\n refMap.set(key, { tasks: [task], ref });\n }\n }\n\n const entries = [...refMap.entries()];\n const toFetch = selectRoundRobinEntries(entries, maxRequests);\n skippedCount += Math.max(0, entries.length - toFetch.length);\n\n // Fetch live state in parallel, up to budget\n let fetchedResults: Array<{ key: string; ref: LiveStateRef; state: LiveStateFetchStatus }> = [];\n try {\n const settled = await Promise.allSettled(\n toFetch.map(async ([key, { ref }]) => {\n const state: LiveStateFetchStatus =\n ref.kind === \"pr\"\n ? await fetchLivePrState(ref.owner, ref.repo, ref.number, signal)\n : await fetchLiveIssueState(ref.owner, ref.repo, ref.number, signal);\n return { key, ref, state };\n }),\n );\n fetchedResults = settled\n .filter(\n (result): result is PromiseFulfilledResult<{ key: string; ref: LiveStateRef; state: LiveStateFetchStatus }> =>\n result.status === \"fulfilled\",\n )\n .map((result) => result.value);\n } catch {\n opts.log?.debug?.(\"memory-hybrid: live-state reconcile fetch failed, skipping update\");\n return { updatedCount, checkedCount: fetchedResults.length, skippedCount };\n }\n const checkedCount = fetchedResults.length;\n\n const terminalRefKeys = new Set<string>();\n for (const { key, state } of fetchedResults) {\n const isTerminal = state === \"merged\" || state === \"closed\";\n if (isTerminal) terminalRefKeys.add(key);\n }\n\n // Apply updates: for each terminal ref, update ALL tasks that point to it\n for (const [key, { tasks }] of entries) {\n if (!terminalRefKeys.has(key)) continue;\n const { ref } = refMap.get(key)!;\n const state = fetchedResults.find((r) => r.key === key)?.state ?? \"unknown\";\n const isTerminal = state === \"merged\" || state === \"closed\";\n if (!isTerminal) continue;\n\n const now = new Date().toISOString();\n const terminalState = state === \"merged\" ? \"merged\" : \"closed\";\n const next = `Auto-reconciled from live GitHub (${ref.owner}/${ref.repo} ${ref.kind} #${ref.number}): ${terminalState}.`;\n\n for (const task of tasks) {\n const doneEntry: ActiveTaskEntry = {\n ...task,\n status: \"Done\",\n updated: now,\n next,\n };\n await syncActiveTaskEntryToFacts(factsDb, vectorDb, embeddings, doneEntry, opts.log);\n updatedCount++;\n opts.log?.info?.(`memory-hybrid: live-state reconcile marked \"${task.label}\" done (${terminalState})`);\n }\n }\n\n return { updatedCount, checkedCount, skippedCount };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-queue-leases.js","names":[],"sources":["../../services/task-queue-leases.ts"],"sourcesContent":["/**\n * Task Queue Dispatch Leases — Issue #634\n *\n * Durable, local idempotency guard for autonomous queue dispatches.\n *\n * - Persists leases under `<stateDir>/dispatch-leases.json`\n * - Uses a lock file to make cross-process acquire/update atomic\n * - Separates queue authority from eventual GitHub branch visibility\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { mkdir, open, readFile, rename, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nconst LEASES_FILE = \"dispatch-leases.json\";\nconst LOCK_FILE = \"dispatch-leases.lock\";\nconst LEASES_SCHEMA_VERSION = 1;\nconst DEFAULT_LEASE_TTL_MS = 30 * 60 * 1000; // 30 minutes\nconst DEFAULT_COMPLETED_VISIBILITY_WINDOW_MS = 10 * 60 * 1000; // 10 minutes\nconst DEFAULT_LOCK_TIMEOUT_MS = 5000;\nconst LOCK_POLL_MS = 50;\nconst STALE_LOCK_MS = 2 * 60 * 1000;\n\ntype DispatchLeaseState = \"leased\" | \"running\" | \"completed\" | \"failed\" | \"lease-expired\";\n\ninterface DispatchLeaseRecord {\n issue: number;\n token: string;\n state: DispatchLeaseState;\n branch?: string;\n pid?: number;\n runId?: string;\n attempt: number;\n leasedAt: string;\n expiresAt?: string;\n completedAt?: string;\n reason?: string;\n updatedAt: string;\n}\n\ninterface DispatchLeaseEvent {\n issue: number;\n token: string;\n state: DispatchLeaseState;\n at: string;\n reason?: string;\n}\n\ninterface DispatchLeaseRegistry {\n version: number;\n leases: Record<string, DispatchLeaseRecord>;\n events: DispatchLeaseEvent[];\n}\n\ninterface AcquireDispatchLeaseInput {\n stateDir: string;\n issue: number;\n branch?: string;\n runId?: string;\n leaseTtlMs?: number;\n now?: Date;\n}\n\ninterface AcquireDispatchLeaseResult {\n acquired: boolean;\n lease?: DispatchLeaseRecord;\n existing?: DispatchLeaseRecord;\n reason?: string;\n}\n\ninterface TransitionDispatchLeaseInput {\n stateDir: string;\n issue: number;\n token?: string;\n toState: DispatchLeaseState;\n pid?: number;\n reason?: string;\n now?: Date;\n}\n\nfunction issueKey(issue: number): string {\n return String(issue);\n}\n\nfunction makeNowIso(now?: Date): string {\n return (now ?? new Date()).toISOString();\n}\n\nfunction parseIsoMs(iso?: string): number {\n if (!iso) return Number.NaN;\n return new Date(iso).getTime();\n}\n\nfunction isActiveState(state: DispatchLeaseState): boolean {\n return state === \"leased\" || state === \"running\";\n}\n\nfunction blocksAcquire(lease: DispatchLeaseRecord, now: Date): boolean {\n if (isActiveState(lease.state)) {\n return true;\n }\n\n if (lease.state !== \"completed\") {\n return false;\n }\n\n let expiresAtMs = parseIsoMs(lease.expiresAt);\n\n // For legacy or partially written records that lack expiresAt, fall back to\n // completedAt + DEFAULT_COMPLETED_VISIBILITY_WINDOW_MS to enforce cooldown.\n if (!Number.isFinite(expiresAtMs)) {\n const completedAtMs = parseIsoMs(lease.completedAt);\n if (Number.isFinite(completedAtMs)) {\n expiresAtMs = completedAtMs + DEFAULT_COMPLETED_VISIBILITY_WINDOW_MS;\n }\n }\n\n return Number.isFinite(expiresAtMs) && now.getTime() < expiresAtMs;\n}\n\nfunction formatAcquireBlockReason(issue: number, lease: DispatchLeaseRecord): string {\n if (isActiveState(lease.state)) {\n return `issue #${issue} already has active lease (${lease.state})`;\n }\n\n return `issue #${issue} is cooling down after completed dispatch (${lease.state})`;\n}\n\nfunction emptyRegistry(): DispatchLeaseRegistry {\n return {\n version: LEASES_SCHEMA_VERSION,\n leases: Object.create(null) as Record<string, DispatchLeaseRecord>,\n events: [],\n };\n}\n\nfunction normalizeRegistry(raw: unknown): DispatchLeaseRegistry {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n return emptyRegistry();\n }\n\n const obj = raw as Partial<DispatchLeaseRegistry>;\n const leases: Record<string, DispatchLeaseRecord> = Object.create(null) as Record<string, DispatchLeaseRecord>;\n const rawLeases = obj.leases;\n if (rawLeases && typeof rawLeases === \"object\" && !Array.isArray(rawLeases)) {\n for (const [key, value] of Object.entries(rawLeases)) {\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\") continue;\n if (!value || typeof value !== \"object\" || Array.isArray(value)) continue;\n const lease = value as Partial<DispatchLeaseRecord>;\n if (typeof lease.issue !== \"number\" || !Number.isInteger(lease.issue)) continue;\n if (typeof lease.token !== \"string\" || lease.token.length === 0) continue;\n if (\n lease.state !== \"leased\" &&\n lease.state !== \"running\" &&\n lease.state !== \"completed\" &&\n lease.state !== \"failed\" &&\n lease.state !== \"lease-expired\"\n ) {\n continue;\n }\n if (typeof lease.attempt !== \"number\" || !Number.isInteger(lease.attempt) || lease.attempt <= 0) continue;\n if (typeof lease.leasedAt !== \"string\" || typeof lease.updatedAt !== \"string\") continue;\n\n leases[key] = {\n issue: lease.issue,\n token: lease.token,\n state: lease.state,\n branch: typeof lease.branch === \"string\" ? lease.branch : undefined,\n pid: typeof lease.pid === \"number\" ? lease.pid : undefined,\n runId: typeof lease.runId === \"string\" ? lease.runId : undefined,\n attempt: lease.attempt,\n leasedAt: lease.leasedAt,\n expiresAt: typeof lease.expiresAt === \"string\" ? lease.expiresAt : undefined,\n completedAt: typeof lease.completedAt === \"string\" ? lease.completedAt : undefined,\n reason: typeof lease.reason === \"string\" ? lease.reason : undefined,\n updatedAt: lease.updatedAt,\n };\n }\n }\n\n const events = Array.isArray(obj.events)\n ? obj.events.filter(\n (evt): evt is DispatchLeaseEvent =>\n !!evt &&\n typeof evt === \"object\" &&\n !Array.isArray(evt) &&\n typeof (evt as DispatchLeaseEvent).issue === \"number\" &&\n typeof (evt as DispatchLeaseEvent).token === \"string\" &&\n typeof (evt as DispatchLeaseEvent).state === \"string\" &&\n typeof (evt as DispatchLeaseEvent).at === \"string\",\n )\n : [];\n\n return {\n version: LEASES_SCHEMA_VERSION,\n leases,\n events,\n };\n}\n\nasync function loadRegistry(filePath: string): Promise<DispatchLeaseRegistry> {\n try {\n const raw = await readFile(filePath, \"utf-8\");\n return normalizeRegistry(JSON.parse(raw));\n } catch {\n return emptyRegistry();\n }\n}\n\nasync function writeRegistry(filePath: string, registry: DispatchLeaseRegistry): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;\n await writeFile(tmpPath, `${JSON.stringify(registry, null, 2)}\\n`, \"utf-8\");\n await rename(tmpPath, filePath);\n}\n\nasync function tryAcquireLock(lockPath: string): Promise<boolean> {\n let fh: Awaited<ReturnType<typeof open>> | undefined;\n try {\n fh = await open(lockPath, \"wx\");\n await fh.writeFile(`${process.pid}\\n${new Date().toISOString()}\\n`, \"utf-8\");\n return true;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") {\n throw err;\n }\n return false;\n } finally {\n if (fh) {\n try {\n await fh.close();\n } catch {\n // Ignore close errors to avoid masking earlier failures.\n }\n }\n }\n}\n\nasync function isLockStale(lockPath: string, nowMs: number): Promise<boolean> {\n try {\n const lockStat = await stat(lockPath);\n return nowMs - lockStat.mtimeMs > STALE_LOCK_MS;\n } catch {\n return false;\n }\n}\n\nasync function withRegistryLock<T>(stateDir: string, fn: (registryPath: string) => Promise<T>): Promise<T> {\n await mkdir(stateDir, { recursive: true });\n const registryPath = join(stateDir, LEASES_FILE);\n const lockPath = join(stateDir, LOCK_FILE);\n\n const start = Date.now();\n while (true) {\n const acquired = await tryAcquireLock(lockPath);\n if (acquired) break;\n\n const nowMs = Date.now();\n if (await isLockStale(lockPath, nowMs)) {\n try {\n await unlink(lockPath);\n // Re-check staleness after unlinking to avoid removing a fresh lock\n // that another process acquired between our stale check and unlink.\n if (await isLockStale(lockPath, Date.now())) {\n // Lock is still stale (or gone), safe to retry acquisition.\n continue;\n }\n } catch {\n // Another process may have cleaned it; keep retrying.\n }\n continue;\n }\n\n if (nowMs - start > DEFAULT_LOCK_TIMEOUT_MS) {\n throw new Error(\"Timed out waiting for task queue lease lock\");\n }\n\n await new Promise<void>((resolve) => {\n setTimeout(resolve, LOCK_POLL_MS);\n });\n }\n\n try {\n return await fn(registryPath);\n } finally {\n try {\n await unlink(lockPath);\n } catch {\n // Ignore lock cleanup errors.\n }\n }\n}\n\nfunction pushEvent(registry: DispatchLeaseRegistry, lease: DispatchLeaseRecord, reason?: string): void {\n registry.events.push({\n issue: lease.issue,\n token: lease.token,\n state: lease.state,\n at: lease.updatedAt,\n reason,\n });\n\n // Keep recent events bounded.\n if (registry.events.length > 500) {\n registry.events.splice(0, registry.events.length - 500);\n }\n}\n\nfunction expireActiveLeases(registry: DispatchLeaseRegistry, now: Date): boolean {\n const nowMs = now.getTime();\n const nowIso = now.toISOString();\n let changed = false;\n\n for (const lease of Object.values(registry.leases)) {\n if (!isActiveState(lease.state)) continue;\n const expiresAtMs = parseIsoMs(lease.expiresAt);\n if (!Number.isFinite(expiresAtMs) || nowMs <= expiresAtMs) continue;\n\n lease.state = \"lease-expired\";\n lease.reason = `lease expired at ${lease.expiresAt}`;\n lease.completedAt = nowIso;\n lease.updatedAt = nowIso;\n pushEvent(registry, lease, lease.reason);\n changed = true;\n }\n\n return changed;\n}\n\nexport async function getDispatchLease(stateDir: string, issue: number): Promise<DispatchLeaseRecord | null> {\n const registryPath = join(stateDir, LEASES_FILE);\n if (!existsSync(registryPath)) return null;\n const registry = await loadRegistry(registryPath);\n return registry.leases[issueKey(issue)] ?? null;\n}\n\nexport async function acquireDispatchLease(input: AcquireDispatchLeaseInput): Promise<AcquireDispatchLeaseResult> {\n const now = input.now ?? new Date();\n const nowIso = makeNowIso(now);\n\n return withRegistryLock(input.stateDir, async (registryPath) => {\n const registry = await loadRegistry(registryPath);\n const changedByExpiry = expireActiveLeases(registry, now);\n\n const key = issueKey(input.issue);\n const existing = registry.leases[key];\n if (existing && blocksAcquire(existing, now)) {\n if (changedByExpiry) {\n await writeRegistry(registryPath, registry);\n }\n return {\n acquired: false,\n existing,\n reason: formatAcquireBlockReason(input.issue, existing),\n };\n }\n\n const attempt = existing ? existing.attempt + 1 : 1;\n const ttlMs = input.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;\n\n const lease: DispatchLeaseRecord = {\n issue: input.issue,\n token: randomUUID(),\n state: \"leased\",\n branch: input.branch,\n runId: input.runId,\n attempt,\n leasedAt: nowIso,\n expiresAt: new Date(now.getTime() + ttlMs).toISOString(),\n updatedAt: nowIso,\n reason: undefined,\n };\n\n registry.leases[key] = lease;\n pushEvent(registry, lease, \"lease acquired\");\n await writeRegistry(registryPath, registry);\n\n return {\n acquired: true,\n lease,\n };\n });\n}\n\nexport async function transitionDispatchLease(input: TransitionDispatchLeaseInput): Promise<boolean> {\n const now = input.now ?? new Date();\n const nowIso = now.toISOString();\n\n return withRegistryLock(input.stateDir, async (registryPath) => {\n const registry = await loadRegistry(registryPath);\n const changedByExpiry = expireActiveLeases(registry, now);\n\n const key = issueKey(input.issue);\n const lease = registry.leases[key];\n if (!lease) {\n if (changedByExpiry) {\n await writeRegistry(registryPath, registry);\n }\n return false;\n }\n\n if (input.token && lease.token !== input.token) {\n if (changedByExpiry) {\n await writeRegistry(registryPath, registry);\n }\n return false;\n }\n\n if (input.toState === \"leased\") {\n // Explicitly disallow transitioning back to leased from the API.\n if (changedByExpiry) {\n await writeRegistry(registryPath, registry);\n }\n return false;\n }\n\n // Prevent transitions from terminal states to preserve audit trail.\n if (!isActiveState(lease.state)) {\n if (changedByExpiry) {\n await writeRegistry(registryPath, registry);\n }\n return false;\n }\n\n lease.state = input.toState;\n lease.updatedAt = nowIso;\n\n if (input.pid != null) {\n lease.pid = input.pid;\n }\n\n if (input.toState === \"running\") {\n // Refresh expiry while work is active.\n lease.expiresAt = undefined;\n } else if (input.toState === \"completed\") {\n lease.completedAt = nowIso;\n // NOTE: `expiresAt` is overloaded:\n // - for active/leased states, it represents the lease TTL;\n // - for the `completed` state, it acts as a visibility cooldown / acquire-block-until timestamp.\n lease.expiresAt = new Date(now.getTime() + DEFAULT_COMPLETED_VISIBILITY_WINDOW_MS).toISOString();\n } else {\n // Other terminal states clear `expiresAt`; callers must not assume it is set for all non-active states.\n lease.completedAt = nowIso;\n lease.expiresAt = undefined;\n }\n\n if (input.reason) {\n lease.reason = input.reason;\n }\n\n pushEvent(registry, lease, input.reason);\n await writeRegistry(registryPath, registry);\n return true;\n });\n}\n\nexport async function expireDispatchLeases(stateDir: string, now?: Date): Promise<number> {\n return withRegistryLock(stateDir, async (registryPath) => {\n const registry = await loadRegistry(registryPath);\n const before = Object.values(registry.leases).filter((l) => l.state === \"lease-expired\").length;\n const changed = expireActiveLeases(registry, now ?? new Date());\n if (!changed) return 0;\n const after = Object.values(registry.leases).filter((l) => l.state === \"lease-expired\").length;\n await writeRegistry(registryPath, registry);\n return Math.max(0, after - before);\n });\n}\n\nexport async function readDispatchLeaseRegistry(stateDir: string): Promise<DispatchLeaseRegistry> {\n const registryPath = join(stateDir, LEASES_FILE);\n return loadRegistry(registryPath);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"task-queue-leases.js","names":[],"sources":["../../services/task-queue-leases.ts"],"sourcesContent":["/**\n * Task Queue Dispatch Leases — Issue #634\n *\n * Durable, local idempotency guard for autonomous queue dispatches.\n *\n * - Persists leases under `<stateDir>/dispatch-leases.json`\n * - Uses a lock file to make cross-process acquire/update atomic\n * - Separates queue authority from eventual GitHub branch visibility\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { mkdir, open, readFile, rename, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nconst LEASES_FILE = \"dispatch-leases.json\";\nconst LOCK_FILE = \"dispatch-leases.lock\";\nconst LEASES_SCHEMA_VERSION = 1;\nconst DEFAULT_LEASE_TTL_MS = 30 * 60 * 1000; // 30 minutes\nconst DEFAULT_COMPLETED_VISIBILITY_WINDOW_MS = 10 * 60 * 1000; // 10 minutes\nconst DEFAULT_LOCK_TIMEOUT_MS = 5000;\nconst LOCK_POLL_MS = 50;\nconst STALE_LOCK_MS = 2 * 60 * 1000;\n\ntype DispatchLeaseState = \"leased\" | \"running\" | \"completed\" | \"failed\" | \"lease-expired\";\n\ninterface DispatchLeaseRecord {\n issue: number;\n token: string;\n state: DispatchLeaseState;\n branch?: string;\n pid?: number;\n runId?: string;\n attempt: number;\n leasedAt: string;\n expiresAt?: string;\n completedAt?: string;\n reason?: string;\n updatedAt: string;\n}\n\ninterface DispatchLeaseEvent {\n issue: number;\n token: string;\n state: DispatchLeaseState;\n at: string;\n reason?: string;\n}\n\ninterface DispatchLeaseRegistry {\n version: number;\n leases: Record<string, DispatchLeaseRecord>;\n events: DispatchLeaseEvent[];\n}\n\ninterface AcquireDispatchLeaseInput {\n stateDir: string;\n issue: number;\n branch?: string;\n runId?: string;\n leaseTtlMs?: number;\n now?: Date;\n}\n\ninterface AcquireDispatchLeaseResult {\n acquired: boolean;\n lease?: DispatchLeaseRecord;\n existing?: DispatchLeaseRecord;\n reason?: string;\n}\n\ninterface TransitionDispatchLeaseInput {\n stateDir: string;\n issue: number;\n token?: string;\n toState: DispatchLeaseState;\n pid?: number;\n reason?: string;\n now?: Date;\n}\n\nfunction issueKey(issue: number): string {\n return String(issue);\n}\n\nfunction makeNowIso(now?: Date): string {\n return (now ?? new Date()).toISOString();\n}\n\nfunction parseIsoMs(iso?: string): number {\n if (!iso) return Number.NaN;\n return new Date(iso).getTime();\n}\n\nfunction isActiveState(state: DispatchLeaseState): boolean {\n return state === \"leased\" || state === \"running\";\n}\n\nfunction blocksAcquire(lease: DispatchLeaseRecord, now: Date): boolean {\n if (isActiveState(lease.state)) {\n return true;\n }\n\n if (lease.state !== \"completed\") {\n return false;\n }\n\n let expiresAtMs = parseIsoMs(lease.expiresAt);\n\n // For legacy or partially written records that lack expiresAt, fall back to\n // completedAt + DEFAULT_COMPLETED_VISIBILITY_WINDOW_MS to enforce cooldown.\n if (!Number.isFinite(expiresAtMs)) {\n const completedAtMs = parseIsoMs(lease.completedAt);\n if (Number.isFinite(completedAtMs)) {\n expiresAtMs = completedAtMs + DEFAULT_COMPLETED_VISIBILITY_WINDOW_MS;\n }\n }\n\n return Number.isFinite(expiresAtMs) && now.getTime() < expiresAtMs;\n}\n\nfunction formatAcquireBlockReason(issue: number, lease: DispatchLeaseRecord): string {\n if (isActiveState(lease.state)) {\n return `issue #${issue} already has active lease (${lease.state})`;\n }\n\n return `issue #${issue} is cooling down after completed dispatch (${lease.state})`;\n}\n\nfunction emptyRegistry(): DispatchLeaseRegistry {\n return {\n version: LEASES_SCHEMA_VERSION,\n leases: Object.create(null) as Record<string, DispatchLeaseRecord>,\n events: [],\n };\n}\n\nfunction normalizeRegistry(raw: unknown): DispatchLeaseRegistry {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n return emptyRegistry();\n }\n\n const obj = raw as Partial<DispatchLeaseRegistry>;\n const leases: Record<string, DispatchLeaseRecord> = Object.create(null) as Record<string, DispatchLeaseRecord>;\n const rawLeases = obj.leases;\n if (rawLeases && typeof rawLeases === \"object\" && !Array.isArray(rawLeases)) {\n for (const [key, value] of Object.entries(rawLeases)) {\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\") continue;\n if (!value || typeof value !== \"object\" || Array.isArray(value)) continue;\n const lease = value as Partial<DispatchLeaseRecord>;\n if (typeof lease.issue !== \"number\" || !Number.isInteger(lease.issue)) continue;\n if (typeof lease.token !== \"string\" || lease.token.length === 0) continue;\n if (\n lease.state !== \"leased\" &&\n lease.state !== \"running\" &&\n lease.state !== \"completed\" &&\n lease.state !== \"failed\" &&\n lease.state !== \"lease-expired\"\n ) {\n continue;\n }\n if (typeof lease.attempt !== \"number\" || !Number.isInteger(lease.attempt) || lease.attempt <= 0) continue;\n if (typeof lease.leasedAt !== \"string\" || typeof lease.updatedAt !== \"string\") continue;\n\n leases[key] = {\n issue: lease.issue,\n token: lease.token,\n state: lease.state,\n branch: typeof lease.branch === \"string\" ? lease.branch : undefined,\n pid: typeof lease.pid === \"number\" ? lease.pid : undefined,\n runId: typeof lease.runId === \"string\" ? lease.runId : undefined,\n attempt: lease.attempt,\n leasedAt: lease.leasedAt,\n expiresAt: typeof lease.expiresAt === \"string\" ? lease.expiresAt : undefined,\n completedAt: typeof lease.completedAt === \"string\" ? lease.completedAt : undefined,\n reason: typeof lease.reason === \"string\" ? lease.reason : undefined,\n updatedAt: lease.updatedAt,\n };\n }\n }\n\n const events = Array.isArray(obj.events)\n ? obj.events.filter(\n (evt): evt is DispatchLeaseEvent =>\n !!evt &&\n typeof evt === \"object\" &&\n !Array.isArray(evt) &&\n typeof (evt as DispatchLeaseEvent).issue === \"number\" &&\n typeof (evt as DispatchLeaseEvent).token === \"string\" &&\n typeof (evt as DispatchLeaseEvent).state === \"string\" &&\n typeof (evt as DispatchLeaseEvent).at === \"string\",\n )\n : [];\n\n return {\n version: LEASES_SCHEMA_VERSION,\n leases,\n events,\n };\n}\n\nasync function loadRegistry(filePath: string): Promise<DispatchLeaseRegistry> {\n try {\n const raw = await readFile(filePath, \"utf-8\");\n return normalizeRegistry(JSON.parse(raw));\n } catch {\n return emptyRegistry();\n }\n}\n\nasync function writeRegistry(filePath: string, registry: DispatchLeaseRegistry): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;\n await writeFile(tmpPath, `${JSON.stringify(registry, null, 2)}\\n`, \"utf-8\");\n await rename(tmpPath, filePath);\n}\n\nasync function tryAcquireLock(lockPath: string): Promise<boolean> {\n let fh: Awaited<ReturnType<typeof open>> | undefined;\n try {\n fh = await open(lockPath, \"wx\");\n await fh.writeFile(`${process.pid}\\n${new Date().toISOString()}\\n`, \"utf-8\");\n return true;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") {\n throw err;\n }\n return false;\n } finally {\n if (fh) {\n try {\n await fh.close();\n } catch {\n // Ignore close errors to avoid masking earlier failures.\n }\n }\n }\n}\n\nasync function isLockStale(lockPath: string, nowMs: number): Promise<boolean> {\n try {\n const lockStat = await stat(lockPath);\n return nowMs - lockStat.mtimeMs > STALE_LOCK_MS;\n } catch {\n return false;\n }\n}\n\nasync function withRegistryLock<T>(stateDir: string, fn: (registryPath: string) => Promise<T>): Promise<T> {\n await mkdir(stateDir, { recursive: true });\n const registryPath = join(stateDir, LEASES_FILE);\n const lockPath = join(stateDir, LOCK_FILE);\n\n const start = Date.now();\n while (true) {\n const acquired = await tryAcquireLock(lockPath);\n if (acquired) break;\n\n const nowMs = Date.now();\n if (await isLockStale(lockPath, nowMs)) {\n try {\n await unlink(lockPath);\n // Re-check staleness after unlinking to avoid removing a fresh lock\n // that another process acquired between our stale check and unlink.\n if (await isLockStale(lockPath, Date.now())) {\n // Lock is still stale (or gone), safe to retry acquisition.\n continue;\n }\n } catch {\n // Another process may have cleaned it; keep retrying.\n }\n continue;\n }\n\n if (nowMs - start > DEFAULT_LOCK_TIMEOUT_MS) {\n throw new Error(\"Timed out waiting for task queue lease lock\");\n }\n\n await new Promise<void>((resolve) => {\n setTimeout(resolve, LOCK_POLL_MS);\n });\n }\n\n try {\n return await fn(registryPath);\n } finally {\n try {\n await unlink(lockPath);\n } catch {\n // Ignore lock cleanup errors.\n }\n }\n}\n\nfunction pushEvent(registry: DispatchLeaseRegistry, lease: DispatchLeaseRecord, reason?: string): void {\n registry.events.push({\n issue: lease.issue,\n token: lease.token,\n state: lease.state,\n at: lease.updatedAt,\n reason,\n });\n\n // Keep recent events bounded.\n if (registry.events.length > 500) {\n registry.events.splice(0, registry.events.length - 500);\n }\n}\n\nfunction expireActiveLeases(registry: DispatchLeaseRegistry, now: Date): boolean {\n const nowMs = now.getTime();\n const nowIso = now.toISOString();\n let changed = false;\n\n for (const lease of Object.values(registry.leases)) {\n if (!isActiveState(lease.state)) continue;\n const expiresAtMs = parseIsoMs(lease.expiresAt);\n if (!Number.isFinite(expiresAtMs) || nowMs <= expiresAtMs) continue;\n\n lease.state = \"lease-expired\";\n lease.reason = `lease expired at ${lease.expiresAt}`;\n lease.completedAt = nowIso;\n lease.updatedAt = nowIso;\n pushEvent(registry, lease, lease.reason);\n changed = true;\n }\n\n return changed;\n}\n\nexport async function getDispatchLease(stateDir: string, issue: number): Promise<DispatchLeaseRecord | null> {\n const registryPath = join(stateDir, LEASES_FILE);\n if (!existsSync(registryPath)) return null;\n const registry = await loadRegistry(registryPath);\n return registry.leases[issueKey(issue)] ?? null;\n}\n\nexport async function acquireDispatchLease(input: AcquireDispatchLeaseInput): Promise<AcquireDispatchLeaseResult> {\n const now = input.now ?? new Date();\n const nowIso = makeNowIso(now);\n\n return withRegistryLock(input.stateDir, async (registryPath) => {\n const registry = await loadRegistry(registryPath);\n const changedByExpiry = expireActiveLeases(registry, now);\n\n const key = issueKey(input.issue);\n const existing = registry.leases[key];\n if (existing && blocksAcquire(existing, now)) {\n if (changedByExpiry) {\n await writeRegistry(registryPath, registry);\n }\n return {\n acquired: false,\n existing,\n reason: formatAcquireBlockReason(input.issue, existing),\n };\n }\n\n const attempt = existing ? existing.attempt + 1 : 1;\n const ttlMs = input.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;\n\n const lease: DispatchLeaseRecord = {\n issue: input.issue,\n token: randomUUID(),\n state: \"leased\",\n branch: input.branch,\n runId: input.runId,\n attempt,\n leasedAt: nowIso,\n expiresAt: new Date(now.getTime() + ttlMs).toISOString(),\n updatedAt: nowIso,\n reason: undefined,\n };\n\n registry.leases[key] = lease;\n pushEvent(registry, lease, \"lease acquired\");\n await writeRegistry(registryPath, registry);\n\n return {\n acquired: true,\n lease,\n };\n });\n}\n\nexport async function transitionDispatchLease(input: TransitionDispatchLeaseInput): Promise<boolean> {\n const now = input.now ?? new Date();\n const nowIso = now.toISOString();\n\n return withRegistryLock(input.stateDir, async (registryPath) => {\n const registry = await loadRegistry(registryPath);\n const changedByExpiry = expireActiveLeases(registry, now);\n\n const key = issueKey(input.issue);\n const lease = registry.leases[key];\n if (!lease) {\n if (changedByExpiry) {\n await writeRegistry(registryPath, registry);\n }\n return false;\n }\n\n if (input.token && lease.token !== input.token) {\n if (changedByExpiry) {\n await writeRegistry(registryPath, registry);\n }\n return false;\n }\n\n if (input.toState === \"leased\") {\n // Explicitly disallow transitioning back to leased from the API.\n if (changedByExpiry) {\n await writeRegistry(registryPath, registry);\n }\n return false;\n }\n\n // Prevent transitions from terminal states to preserve audit trail.\n if (!isActiveState(lease.state)) {\n if (changedByExpiry) {\n await writeRegistry(registryPath, registry);\n }\n return false;\n }\n\n lease.state = input.toState;\n lease.updatedAt = nowIso;\n\n if (input.pid != null) {\n lease.pid = input.pid;\n }\n\n if (input.toState === \"running\") {\n // Refresh expiry while work is active.\n lease.expiresAt = undefined;\n } else if (input.toState === \"completed\") {\n lease.completedAt = nowIso;\n // NOTE: `expiresAt` is overloaded:\n // - for active/leased states, it represents the lease TTL;\n // - for the `completed` state, it acts as a visibility cooldown / acquire-block-until timestamp.\n lease.expiresAt = new Date(now.getTime() + DEFAULT_COMPLETED_VISIBILITY_WINDOW_MS).toISOString();\n } else {\n // Other terminal states clear `expiresAt`; callers must not assume it is set for all non-active states.\n lease.completedAt = nowIso;\n lease.expiresAt = undefined;\n }\n\n if (input.reason) {\n lease.reason = input.reason;\n }\n\n pushEvent(registry, lease, input.reason);\n await writeRegistry(registryPath, registry);\n return true;\n });\n}\n\nexport async function expireDispatchLeases(stateDir: string, now?: Date): Promise<number> {\n return withRegistryLock(stateDir, async (registryPath) => {\n const registry = await loadRegistry(registryPath);\n const before = Object.values(registry.leases).filter((l) => l.state === \"lease-expired\").length;\n const changed = expireActiveLeases(registry, now ?? new Date());\n if (!changed) return 0;\n const after = Object.values(registry.leases).filter((l) => l.state === \"lease-expired\").length;\n await writeRegistry(registryPath, registry);\n return Math.max(0, after - before);\n });\n}\n\nexport async function readDispatchLeaseRegistry(stateDir: string): Promise<DispatchLeaseRegistry> {\n const registryPath = join(stateDir, LEASES_FILE);\n return loadRegistry(registryPath);\n}\n"],"mappings":"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"}
|