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":"active-task.js","names":[],"sources":["../../services/active-task.ts"],"sourcesContent":["/**\n * Active Task Working Memory Service\n *\n * Parses, reads, and writes ACTIVE-TASKS.md — a structured working memory file\n * that persists in-progress task state across session restarts and context compaction.\n *\n * File format:\n * ```markdown\n * ## Active Tasks\n * ### [label]: [description]\n * - **Branch:** fix/something\n * - **Status:** In progress\n * - **Subagent:** session-key\n * - **Next:** what to do next\n * - **Started:** ISO timestamp\n * - **Updated:** ISO timestamp\n *\n * ## Completed\n * ...\n * ```\n */\n\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { chmod, mkdir, readdir, readFile, realpath, rename, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, isAbsolute, join, relative } from \"node:path\";\nimport { formatDuration } from \"../utils/duration.js\";\nimport { pluginLogger } from \"../utils/logger.js\";\nimport { stableStringify } from \"../utils/stable-stringify.js\";\nimport { isOpenClawSessionLikelyPresent, looksLikeOpenClawSessionRef } from \"./openclaw-session-artifact.js\";\n\n/** Legacy filename before default became ACTIVE-TASKS.md; still read if the new file is missing. */\nconst LEGACY_ACTIVE_TASK_BASENAME = \"ACTIVE-TASK.md\";\n\n/** Prefer `ACTIVE-TASKS.md`; if missing, read legacy `ACTIVE-TASK.md` in the same directory. */\nexport function resolveActiveTaskReadPath(filePath: string): string | null {\n if (existsSync(filePath)) return filePath;\n if (basename(filePath) === \"ACTIVE-TASKS.md\") {\n const legacyPath = join(dirname(filePath), LEGACY_ACTIVE_TASK_BASENAME);\n if (existsSync(legacyPath)) return legacyPath;\n }\n return null;\n}\n\n/**\n * Unparseable or invalid signal files (and abandoned atomic-write temp files) older than this are\n * deleted to prevent unbounded accumulation (issue #812). Exported for tests.\n */\nexport const STALE_CORRUPT_SIGNAL_MS = 5 * 60 * 1000;\nconst MAX_TASK_SIGNAL_BYTES = 256 * 1024;\n\nasync function tryDeleteStaleCorruptSignalFile(filePath: string): Promise<void> {\n try {\n const s = await stat(filePath);\n if (Date.now() - s.mtimeMs <= STALE_CORRUPT_SIGNAL_MS) return;\n await unlink(filePath);\n } catch {\n // ENOENT or other — ignore\n }\n}\n\n/**\n * Sentinel for rows with no trustworthy Started/Updated value (facts projection and parsing).\n * Not an ISO-8601 string — do not parse as a date.\n */\nexport const UNKNOWN_ACTIVE_TASK_TIME = \"Unknown\";\n\n/** Message shown when rows are omitted due to projection cap. */\nexport const OMITTED_CAP_NOTE =\n \"more not shown (projection cap). Adjust `activeTask.projection.maxRowsPerSection`, set `activeTask.projection.mode` to `full`, or query `category:project` facts.\";\n\n/** Valid task statuses */\nexport const ACTIVE_TASK_STATUSES = [\"In progress\", \"Waiting\", \"Stalled\", \"Failed\", \"Done\"] as const;\nexport type ActiveTaskStatus = (typeof ACTIVE_TASK_STATUSES)[number];\n\n/** Non-terminal statuses (still active) */\nconst ACTIVE_STATUSES: Set<ActiveTaskStatus> = new Set([\"In progress\", \"Waiting\", \"Stalled\", \"Failed\"]);\n\n/** Structured task entry */\nexport interface ActiveTaskEntry {\n /** Short unique identifier (e.g. \"forge-99\", \"deploy-prod\") */\n label: string;\n /** Human-readable description */\n description: string;\n /** Git branch if applicable */\n branch?: string;\n /** Current status */\n status: ActiveTaskStatus;\n /** Stash or commit reference */\n stashCommit?: string;\n /** Subagent session key if applicable */\n subagent?: string;\n /** What to do next */\n next?: string;\n /** ISO-8601 when known; {@link UNKNOWN_ACTIVE_TASK_TIME} when missing (facts projection). */\n started: string;\n /** ISO-8601 when known; {@link UNKNOWN_ACTIVE_TASK_TIME} when missing (facts projection). */\n updated: string;\n /** Optional goal id from project facts (`related_goal` / `goal_id`) for projection hints. */\n relatedGoal?: string;\n /** Whether task is flagged as stale (not updated within staleThreshold) */\n stale?: boolean;\n /** Structured handoff metadata from latest sub-agent signal */\n handoff?: ActiveTaskHandoffRef;\n}\n\n/** Structured handoff reference persisted in ACTIVE-TASKS.md */\nexport interface ActiveTaskHandoffRef {\n /** OCTAVE schema identifier */\n schema: string;\n /** Unique artifact id */\n artifactId: string;\n /** Signal type represented by the artifact */\n signal: TaskSignalType;\n /** Agent that emitted the signal */\n agent: string;\n /** ISO-8601 timestamp of the handoff */\n timestamp: string;\n /** SHA-256 checksum of the artifact canonical payload */\n checksum: string;\n}\n\n/** Result of parsing ACTIVE-TASKS.md */\ninterface ActiveTaskFile {\n /** Active (non-Done) tasks */\n active: ActiveTaskEntry[];\n /** Completed tasks (in ## Completed section) */\n completed: ActiveTaskEntry[];\n /** Raw content of the file */\n raw: string;\n}\n\n// ---------------------------------------------------------------------------\n// Parsing\n// ---------------------------------------------------------------------------\n\n/** Parse a single field line: `- **FieldName:** value` */\nfunction parseFieldLine(line: string): { field: string; value: string } | null {\n const match = line.match(/^-\\s+\\*\\*([^:]+):\\*\\*\\s*(.*)/);\n if (!match) return null;\n return { field: match[1].trim(), value: match[2].trim() };\n}\n\n/** Parse task block lines into an ActiveTaskEntry */\nfunction parseTaskBlock(header: string, lines: string[]): ActiveTaskEntry | null {\n // Header format: `### [label]: description` or `### label: description`\n const headerMatch = header.match(/^###\\s+\\[?([^\\]:\\n]+)\\]?:\\s*(.+)/);\n if (!headerMatch) return null;\n\n const label = headerMatch[1].trim();\n const description = headerMatch[2].trim();\n\n const entry: Partial<ActiveTaskEntry> & { label: string; description: string } = {\n label,\n description,\n status: \"In progress\",\n started: UNKNOWN_ACTIVE_TASK_TIME,\n updated: UNKNOWN_ACTIVE_TASK_TIME,\n };\n\n for (const line of lines) {\n const parsed = parseFieldLine(line);\n if (!parsed) continue;\n const { field, value } = parsed;\n switch (field.toLowerCase()) {\n case \"branch\":\n entry.branch = value || undefined;\n break;\n case \"status\":\n if (ACTIVE_TASK_STATUSES.includes(value as ActiveTaskStatus)) {\n entry.status = value as ActiveTaskStatus;\n }\n break;\n case \"stash/commit\":\n case \"stash\":\n case \"commit\":\n entry.stashCommit = value || undefined;\n break;\n case \"subagent\":\n entry.subagent = value || undefined;\n break;\n case \"session\":\n if (!entry.subagent?.trim()) {\n entry.subagent = value || undefined;\n }\n break;\n case \"next\":\n entry.next = value || undefined;\n break;\n case \"started\":\n entry.started = value || UNKNOWN_ACTIVE_TASK_TIME;\n break;\n case \"updated\":\n entry.updated = value || UNKNOWN_ACTIVE_TASK_TIME;\n break;\n case \"related goal\":\n entry.relatedGoal = value || undefined;\n break;\n case \"handoff\":\n entry.handoff = parseHandoffRef(value) ?? undefined;\n break;\n }\n }\n\n return entry as ActiveTaskEntry;\n}\n\nfunction parseHandoffRef(value: string): ActiveTaskHandoffRef | null {\n if (!value) return null;\n try {\n const parsed = JSON.parse(value) as unknown;\n if (!isHandoffRef(parsed)) return null;\n return parsed;\n } catch {\n return null;\n }\n}\n\nfunction isHandoffRef(value: unknown): value is ActiveTaskHandoffRef {\n if (!value || typeof value !== \"object\") return false;\n const v = value as Partial<ActiveTaskHandoffRef>;\n return (\n typeof v.schema === \"string\" &&\n typeof v.artifactId === \"string\" &&\n typeof v.signal === \"string\" &&\n typeof v.agent === \"string\" &&\n typeof v.timestamp === \"string\" &&\n typeof v.checksum === \"string\"\n );\n}\n\n/**\n * Extract the \"## Active Goals\" section from the raw file content.\n * Returns the section content (without the header) or undefined if not present.\n */\nfunction extractGoalsMirrorSection(content: string): string | undefined {\n const lines = content.split(\"\\n\");\n let inGoalsSection = false;\n const goalLines: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n if (trimmed === \"## Active Goals\") {\n inGoalsSection = true;\n continue;\n }\n\n // Stop at any other h2 header\n if (inGoalsSection && trimmed.startsWith(\"## \")) {\n break;\n }\n\n if (inGoalsSection) {\n goalLines.push(line);\n }\n }\n\n if (goalLines.length === 0) return undefined;\n\n // Remove leading/trailing empty lines\n while (goalLines.length > 0 && goalLines[0].trim() === \"\") {\n goalLines.shift();\n }\n while (goalLines.length > 0 && goalLines[goalLines.length - 1].trim() === \"\") {\n goalLines.pop();\n }\n\n return goalLines.length > 0 ? goalLines.join(\"\\n\") : undefined;\n}\n\n/** Parse a full ACTIVE-TASKS.md file content */\nexport function parseActiveTaskFile(content: string): ActiveTaskFile {\n const lines = content.split(\"\\n\");\n const active: ActiveTaskEntry[] = [];\n const completed: ActiveTaskEntry[] = [];\n\n let inSection: \"active\" | \"completed\" | \"other\" = \"other\";\n let currentHeader: string | null = null;\n let currentLines: string[] = [];\n\n function flushTask(): void {\n if (!currentHeader) return;\n const entry = parseTaskBlock(currentHeader, currentLines);\n if (!entry) return;\n if (inSection === \"completed\" || entry.status === \"Done\") {\n completed.push(entry);\n } else {\n active.push(entry);\n }\n currentHeader = null;\n currentLines = [];\n }\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Section headers\n if (trimmed === \"## Active Tasks\") {\n flushTask();\n inSection = \"active\";\n continue;\n }\n if (trimmed === \"## Completed\") {\n flushTask();\n inSection = \"completed\";\n continue;\n }\n // Other h2 headers reset section\n if (trimmed.startsWith(\"## \") && trimmed !== \"## Active Tasks\" && trimmed !== \"## Completed\") {\n flushTask();\n inSection = \"other\";\n continue;\n }\n\n // Task headers (h3) — only within Active Tasks / Completed sections\n if (trimmed.startsWith(\"### \")) {\n flushTask();\n if (inSection !== \"other\") {\n currentHeader = trimmed;\n currentLines = [];\n }\n continue;\n }\n\n // Accumulate field lines under current task\n if (currentHeader && (trimmed.startsWith(\"- **\") || trimmed === \"\")) {\n currentLines.push(trimmed);\n }\n }\n\n // Don't forget the last task\n flushTask();\n\n return { active, completed, raw: content };\n}\n\n// ---------------------------------------------------------------------------\n// Serialization\n// ---------------------------------------------------------------------------\n\n/** Serialize a single ActiveTaskEntry to markdown block */\nexport function serializeTaskEntry(entry: ActiveTaskEntry): string {\n const lines: string[] = [`### [${entry.label}]: ${entry.description}`];\n if (entry.branch) lines.push(`- **Branch:** ${entry.branch}`);\n lines.push(`- **Status:** ${entry.status}`);\n if (entry.stashCommit) lines.push(`- **Stash/Commit:** ${entry.stashCommit}`);\n if (entry.subagent) lines.push(`- **Subagent:** ${entry.subagent}`);\n if (entry.next) lines.push(`- **Next:** ${entry.next}`);\n if (entry.relatedGoal?.trim()) lines.push(`- **Related goal:** ${entry.relatedGoal.trim()}`);\n if (entry.handoff) lines.push(`- **Handoff:** ${stableStringify(entry.handoff)}`);\n lines.push(`- **Started:** ${entry.started}`);\n lines.push(`- **Updated:** ${entry.updated}`);\n return lines.join(\"\\n\");\n}\n\n/** Serialize all tasks back to full ACTIVE-TASKS.md content */\nexport function serializeActiveTaskFile(\n active: ActiveTaskEntry[],\n completed: ActiveTaskEntry[],\n goalsMirrorMarkdown?: string,\n omitted?: { active: number; completed: number },\n): string {\n const parts: string[] = [\"# ACTIVE-TASKS.md — Working Memory\\n\"];\n\n parts.push(\"## Active Tasks\\n\");\n if (active.length === 0) {\n parts.push(\"_No active tasks._\\n\");\n } else {\n for (const entry of active) {\n parts.push(serializeTaskEntry(entry));\n parts.push(\"\");\n }\n }\n\n if (omitted?.active && omitted.active > 0) {\n parts.push(`> ${omitted.active} ${OMITTED_CAP_NOTE}\\n\\n`);\n }\n\n if (goalsMirrorMarkdown !== undefined) {\n parts.push(\"## Active Goals\\n\");\n parts.push(\"_Mirror from goal registry — do not edit by hand; refreshed on heartbeat._\\n\\n\");\n parts.push(goalsMirrorMarkdown);\n if (!goalsMirrorMarkdown.endsWith(\"\\n\")) parts.push(\"\\n\");\n }\n\n if (completed.length > 0 || (omitted?.completed && 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 && omitted.completed > 0) {\n parts.push(`> ${omitted.completed} ${OMITTED_CAP_NOTE}\\n`);\n }\n }\n\n return parts.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Stale detection\n// ---------------------------------------------------------------------------\n\n/**\n * Flag tasks that have not been updated in more than `staleMinutes` minutes.\n * Returns new array with `stale` property set.\n */\nexport function detectStaleTasks(tasks: ActiveTaskEntry[], staleMinutes: number): ActiveTaskEntry[] {\n const now = Date.now();\n const staleMs = staleMinutes * 60 * 1000;\n return tasks.map((t) => {\n const updatedMs = new Date(t.updated).getTime();\n const hasValidUpdated =\n t.updated !== UNKNOWN_ACTIVE_TASK_TIME && Number.isFinite(updatedMs) && !Number.isNaN(updatedMs);\n const isStale = !hasValidUpdated || now - updatedMs > staleMs;\n return { ...t, stale: isStale };\n });\n}\n\n// ---------------------------------------------------------------------------\n// File I/O\n// ---------------------------------------------------------------------------\n\n/** Same as {@link readActiveTaskFile}, plus the resolved path actually read (canonical or legacy). */\nexport async function readActiveTaskFileWithResolvedPath(\n filePath: string,\n staleMinutes = 1440,\n): Promise<(ActiveTaskFile & { readFrom: string }) | null> {\n const pathToRead = resolveActiveTaskReadPath(filePath);\n if (!pathToRead) return null;\n try {\n const content = await readFile(pathToRead, \"utf-8\");\n const parsed = parseActiveTaskFile(content);\n parsed.active = detectStaleTasks(parsed.active, staleMinutes);\n return { ...parsed, readFrom: pathToRead };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n}\n\n/**\n * Read and parse ACTIVE-TASKS.md from disk. Returns null if file doesn't exist.\n *\n * @param filePath - Absolute path to ACTIVE-TASKS.md\n * @param staleMinutes - Minutes before a task is considered stale (default: 1440 = 24h)\n */\nexport async function readActiveTaskFile(filePath: string, staleMinutes = 1440): Promise<ActiveTaskFile | null> {\n const r = await readActiveTaskFileWithResolvedPath(filePath, staleMinutes);\n if (!r) return null;\n const { readFrom: _readFrom, ...file } = r;\n return file;\n}\n\n/** Write tasks back to ACTIVE-TASKS.md. Creates parent directories as needed. */\nexport async function writeActiveTaskFile(\n filePath: string,\n active: ActiveTaskEntry[],\n completed: ActiveTaskEntry[],\n): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n\n // Preserve existing goals mirror section if present\n let goalsMirror: string | undefined;\n const pathToRead = resolveActiveTaskReadPath(filePath);\n if (pathToRead) {\n try {\n const existing = await readFile(pathToRead, \"utf-8\");\n goalsMirror = extractGoalsMirrorSection(existing);\n } catch {\n // Ignore read errors; write without goals section\n }\n }\n\n const content = serializeActiveTaskFile(active, completed, goalsMirror);\n const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}-${randomUUID().slice(0, 8)}`;\n let existingMode: number | null = null;\n try {\n existingMode = (await stat(filePath)).mode & 0o777;\n } catch {\n existingMode = null;\n }\n try {\n await writeFile(tmpPath, content, \"utf-8\");\n if (existingMode !== null) await chmod(tmpPath, existingMode);\n await rename(tmpPath, filePath);\n } catch (err) {\n await unlink(tmpPath).catch(() => {});\n throw err;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Task mutations\n// ---------------------------------------------------------------------------\n\n/**\n * Add or update a task entry. If an entry with the same label exists, update it.\n * Otherwise, append a new entry.\n *\n * @param active Active task entries\n * @param entry Entry to upsert\n * @param preserveUpdated If true, use entry.updated as-is; otherwise set to current time\n */\nexport function upsertTask(\n active: ActiveTaskEntry[],\n entry: ActiveTaskEntry,\n preserveUpdated = false,\n): ActiveTaskEntry[] {\n const idx = active.findIndex((t) => t.label === entry.label);\n const updatedTimestamp = preserveUpdated ? entry.updated : new Date().toISOString();\n if (idx >= 0) {\n const updated = [...active];\n updated[idx] = { ...active[idx], ...entry, updated: updatedTimestamp };\n return updated;\n }\n return [...active, { ...entry, updated: updatedTimestamp }];\n}\n\n/**\n * Mark a task as Done, remove from active list, return entry (for flush to memory).\n */\nexport function completeTask(\n active: ActiveTaskEntry[],\n label: string,\n): { updated: ActiveTaskEntry[]; completed: ActiveTaskEntry | null } {\n const idx = active.findIndex((t) => t.label === label);\n if (idx < 0) return { updated: active, completed: null };\n const task: ActiveTaskEntry = {\n ...active[idx],\n status: \"Done\",\n updated: new Date().toISOString(),\n };\n const updated = active.filter((_, i) => i !== idx);\n return { updated, completed: task };\n}\n\n// ---------------------------------------------------------------------------\n// Injection summary builder\n// ---------------------------------------------------------------------------\n\nexport type ActiveTaskInjectionBuildResult = {\n text: string;\n injectedCount: number;\n};\n\n/**\n * Build a compact injection block for the active task working memory.\n * Budget-capped to `maxTokens` or `opts.maxChars` (approximate — 4 chars ≈ 1 token).\n */\nexport function buildActiveTaskInjection(\n tasks: ActiveTaskEntry[],\n maxTokens: number,\n opts?: { maxChars?: number; excludeStale?: boolean },\n): ActiveTaskInjectionBuildResult {\n const activeTasks = tasks.filter((t) => ACTIVE_STATUSES.has(t.status) && (!opts?.excludeStale || !t.stale));\n if (activeTasks.length === 0) return { text: \"\", injectedCount: 0 };\n\n const lines: string[] = [\"<active-tasks>\", \"In-progress tasks from ACTIVE-TASKS.md:\"];\n\n const charBudget = typeof opts?.maxChars === \"number\" && opts.maxChars > 0 ? opts.maxChars - 60 : maxTokens * 4 - 60;\n let used = 0;\n let injectedCount = 0;\n\n for (const task of activeTasks) {\n const summary = [`- [${task.label}] ${task.description} (${task.status})`];\n if (task.next) summary.push(` Next: ${task.next}`);\n if (task.subagent) summary.push(` Subagent: ${task.subagent}`);\n const block = summary.join(\"\\n\");\n if (used + block.length > charBudget) break;\n lines.push(block);\n used += block.length + 1;\n injectedCount++;\n }\n\n if (lines.length === 2) return { text: \"\", injectedCount: 0 };\n\n lines.push(\"</active-tasks>\");\n return { text: lines.join(\"\\n\"), injectedCount };\n}\n\n// ---------------------------------------------------------------------------\n// Stale warning injection builder\n// ---------------------------------------------------------------------------\n\n/**\n * Build a stale-task warning block to prepend to the agent context.\n *\n * Surfaces two kinds of information:\n * 1. Tasks where `stale === true` (flagged by `detectStaleTasks`) — shown with elapsed time.\n * 2. Any \"In progress\" task that has a `subagent` field — hints the agent to verify the subagent\n * is still running via `subagents list`.\n *\n * Returns an empty string when there is nothing to report, so the caller can\n * skip injection entirely.\n *\n * @param tasks Active tasks (must have `stale` already computed by `detectStaleTasks`).\n * @param staleMinutes Threshold used for the warning label (e.g. 1440 → shows \">24h\").\n * @param maxChars Optional character budget cap (approximate — 4 chars ≈ 1 token). If provided, truncates output.\n * @returns Object with the rendered text and the count of tasks actually rendered.\n */\nexport function buildStaleWarningInjection(\n tasks: ActiveTaskEntry[],\n staleMinutes: number,\n maxChars?: number,\n): { text: string; renderedCount: number } {\n const staleTasks = tasks.filter((t) => t.stale);\n // Hint for any \"In progress\" task with a subagent — regardless of staleness.\n const inProgressWithSubagent = tasks.filter((t) => t.status === \"In progress\" && t.subagent);\n\n if (staleTasks.length === 0 && inProgressWithSubagent.length === 0) return { text: \"\", renderedCount: 0 };\n\n const lines: string[] = [];\n const thresholdDisplay = formatDuration(staleMinutes);\n let usedChars = 0;\n let renderedCount = 0;\n\n // ── Stale task warnings ──────────────────────────────────────────────────\n if (staleTasks.length > 0) {\n const header = `⚠️ STALE ACTIVE TASKS (not updated in >${thresholdDisplay}):`;\n if (maxChars != null && usedChars + header.length > maxChars) return { text: \"\", renderedCount: 0 };\n lines.push(header);\n usedChars += header.length + 1;\n\n const now = Date.now();\n for (const task of staleTasks) {\n const updatedMs = new Date(task.updated).getTime();\n const hoursAgo = Number.isNaN(updatedMs) ? \"?\" : Math.floor((now - updatedMs) / (60 * 60 * 1000));\n const line1 = `- [${task.label}]: ${task.description} — last updated ${task.updated} (${hoursAgo}h ago)`;\n const nextPart = task.next ? `, Next: ${task.next}` : \"\";\n const line2 = ` Status: ${task.status}${nextPart}`;\n const blockSize = line1.length + line2.length + 2;\n if (maxChars != null && usedChars + blockSize > maxChars) break;\n lines.push(line1);\n lines.push(line2);\n usedChars += blockSize;\n renderedCount++;\n }\n const footer = \"Consider: check subagent status, resume, or mark complete.\";\n if (maxChars == null || usedChars + footer.length <= maxChars) {\n lines.push(footer);\n usedChars += footer.length + 1;\n }\n }\n\n // ── Subagent hint ────────────────────────────────────────────────────────\n if (inProgressWithSubagent.length > 0) {\n const separator = lines.length > 0 ? \"\\n\" : \"\";\n const header = \"💡 In-progress tasks with subagents — verify they are still running:\";\n const headerSize = separator.length + header.length + 1;\n if (maxChars != null && usedChars + headerSize > maxChars) {\n return { text: lines.join(\"\\n\"), renderedCount };\n }\n if (lines.length > 0) lines.push(\"\");\n lines.push(header);\n usedChars += headerSize;\n\n for (const task of inProgressWithSubagent) {\n const line = `- [${task.label}]: ${task.description} (subagent: ${task.subagent})`;\n if (maxChars != null && usedChars + line.length + 1 > maxChars) break;\n lines.push(line);\n usedChars += line.length + 1;\n renderedCount++;\n }\n const footer = \"Hint: use `subagents list` to check if these subagents are still active.\";\n if (maxChars == null || usedChars + footer.length <= maxChars) {\n lines.push(footer);\n }\n }\n\n return { text: lines.join(\"\\n\"), renderedCount };\n}\n\n// ---------------------------------------------------------------------------\n// Flush to daily memory log\n// ---------------------------------------------------------------------------\n\n/**\n * Append completed task summary to `memory/YYYY-MM-DD.md`.\n * Creates the file if it doesn't exist.\n */\nexport async function flushCompletedTaskToMemory(task: ActiveTaskEntry, memoryDir: string): Promise<string> {\n const date = new Date().toISOString().slice(0, 10);\n const filePath = join(memoryDir, `${date}.md`);\n\n await mkdir(memoryDir, { recursive: true });\n\n let existing = \"\";\n try {\n existing = await readFile(filePath, \"utf-8\");\n } catch (err) {\n // Only swallow \"file not found\" — rethrow permission errors, malformed reads, etc.\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n existing = \"\";\n } else {\n throw err;\n }\n }\n\n const summary = [\n `## Completed Task: [${task.label}] ${task.description}`,\n \"- **Status:** Done\",\n `- **Started:** ${task.started}`,\n `- **Completed:** ${task.updated}`,\n ...(task.branch ? [`- **Branch:** ${task.branch}`] : []),\n ...(task.subagent ? [`- **Subagent:** ${task.subagent}`] : []),\n ...(task.handoff ? [`- **Handoff:** ${stableStringify(task.handoff)}`] : []),\n \"\",\n ].join(\"\\n\");\n\n const newContent = existing ? `${existing.trimEnd()}\\n\\n${summary}` : `# Memory Log — ${date}\\n\\n${summary}`;\n\n await writeFile(filePath, newContent, \"utf-8\");\n return filePath;\n}\n\n// ---------------------------------------------------------------------------\n// Sub-agent detection\n// ---------------------------------------------------------------------------\n\n/**\n * Returns true if the given session key belongs to a sub-agent.\n * Sub-agents have \"subagent:\" somewhere in their session key.\n *\n * Examples:\n * \"agent:forge:subagent:f3d14066\" → true\n * \"agent:main:main\" → false\n * undefined → false\n */\nexport function isSubagentSession(sessionKey?: string): boolean {\n if (!sessionKey) return false;\n return sessionKey.includes(\"subagent:\");\n}\n\n// ---------------------------------------------------------------------------\n// Task signal types (file-based sub-agent → orchestrator signalling)\n// ---------------------------------------------------------------------------\n\n/** Signal types that a sub-agent can emit. */\nexport type TaskSignalType = \"completed\" | \"blocked\" | \"escalate\" | \"update\";\n\n/**\n * Structured status signal that a sub-agent writes to\n * `memory/task-signals/<label>-<timestamp>-<suffix>.json` for the orchestrator to consume.\n */\nexport interface TaskSignal {\n /** Identifier of the emitting agent (e.g. \"reaver-mqtt-auth\") */\n agent: string;\n /** Human-readable task reference (e.g. \"Yarbo RE: MQTT Auth\") */\n taskRef: string;\n /** ISO-8601 timestamp of when the signal was emitted */\n timestamp: string;\n /** Signal type */\n signal: TaskSignalType;\n /** Short human-readable summary of what changed */\n summary: string;\n /** Optional: from/to status transition */\n statusChange?: { from: string; to: string };\n /** Optional: key findings or data points to surface */\n findings?: string[];\n}\n\n/** OCTAVE-style artifact schema identifier for task handoff files. */\nexport const OCTAVE_TASK_HANDOFF_SCHEMA = \"octave/task-handoff@v1\";\n\n/** Typed OCTAVE-style handoff artifact persisted to disk. */\ninterface OctaveTaskHandoffArtifact {\n /** Schema identifier */\n schema: typeof OCTAVE_TASK_HANDOFF_SCHEMA;\n /** Artifact type */\n artifactType: \"task_handoff\";\n /** Schema version */\n version: 1;\n /** Unique artifact id */\n artifactId: string;\n /** Deterministic canonical JSON of payload used for checksum and diffing */\n canonical: string;\n /** SHA-256 checksum of canonical payload */\n checksum: string;\n /** Structured payload */\n payload: TaskSignal;\n /** Append-only audit trail for artifact lifecycle */\n auditTrail: Array<{\n at: string;\n by: string;\n action: \"created\" | \"validated\";\n note?: string;\n }>;\n}\n\n/**\n * A TaskSignal enriched with the path of the file it was read from,\n * so the orchestrator can delete/archive it after processing.\n */\nexport interface PendingTaskSignal extends TaskSignal {\n /** Absolute path of the signal file on disk */\n _filePath: string;\n /** Structured OCTAVE handoff metadata if source file used the new schema */\n _handoff?: ActiveTaskHandoffRef;\n}\n\nfunction isTaskSignal(signal: unknown): signal is TaskSignal {\n if (!signal || typeof signal !== \"object\") return false;\n const s = signal as Partial<TaskSignal>;\n return (\n typeof s.agent === \"string\" &&\n typeof s.taskRef === \"string\" &&\n typeof s.timestamp === \"string\" &&\n typeof s.signal === \"string\" &&\n typeof s.summary === \"string\"\n );\n}\n\nfunction buildCanonicalPayload(signal: TaskSignal): string {\n return stableStringify(signal);\n}\n\nfunction buildChecksum(canonical: string): string {\n return createHash(\"sha256\").update(canonical).digest(\"hex\");\n}\n\nfunction toHandoffRefFromArtifact(artifact: OctaveTaskHandoffArtifact): ActiveTaskHandoffRef {\n return {\n schema: artifact.schema,\n artifactId: artifact.artifactId,\n signal: artifact.payload.signal,\n agent: artifact.payload.agent,\n timestamp: artifact.payload.timestamp,\n checksum: artifact.checksum,\n };\n}\n\nexport function createOctaveTaskHandoffArtifact(signal: TaskSignal): OctaveTaskHandoffArtifact {\n const canonical = buildCanonicalPayload(signal);\n const checksum = buildChecksum(canonical);\n return {\n schema: OCTAVE_TASK_HANDOFF_SCHEMA,\n artifactType: \"task_handoff\",\n version: 1,\n artifactId: randomUUID(),\n canonical,\n checksum,\n payload: signal,\n auditTrail: [\n {\n at: new Date().toISOString(),\n by: signal.agent,\n action: \"created\",\n },\n ],\n };\n}\n\nexport function validateOctaveTaskHandoffArtifact(\n value: unknown,\n): { valid: true; artifact: OctaveTaskHandoffArtifact } | { valid: false; reason: string } {\n if (!value || typeof value !== \"object\") return { valid: false, reason: \"artifact is not an object\" };\n const artifact = value as Partial<OctaveTaskHandoffArtifact>;\n if (artifact.schema !== OCTAVE_TASK_HANDOFF_SCHEMA) return { valid: false, reason: \"schema mismatch\" };\n if (artifact.artifactType !== \"task_handoff\") return { valid: false, reason: \"artifact type mismatch\" };\n if (artifact.version !== 1) return { valid: false, reason: \"unsupported artifact version\" };\n if (typeof artifact.artifactId !== \"string\" || artifact.artifactId.length === 0) {\n return { valid: false, reason: \"missing artifact id\" };\n }\n if (typeof artifact.canonical !== \"string\" || artifact.canonical.length === 0) {\n return { valid: false, reason: \"missing canonical payload\" };\n }\n if (typeof artifact.checksum !== \"string\" || artifact.checksum.length === 0) {\n return { valid: false, reason: \"missing checksum\" };\n }\n if (!Array.isArray(artifact.auditTrail)) return { valid: false, reason: \"missing audit trail\" };\n if (!isTaskSignal(artifact.payload)) return { valid: false, reason: \"invalid payload\" };\n const expectedCanonical = buildCanonicalPayload(artifact.payload);\n if (artifact.canonical !== expectedCanonical) return { valid: false, reason: \"canonical mismatch\" };\n const expectedChecksum = buildChecksum(artifact.canonical);\n if (artifact.checksum !== expectedChecksum) return { valid: false, reason: \"checksum mismatch\" };\n return { valid: true, artifact: artifact as OctaveTaskHandoffArtifact };\n}\n\n/**\n * Emit a structured signal file for the orchestrator to consume.\n * Should only be called by sub-agents (but is not restricted).\n *\n * @param label Short identifier for this signal (used in filename, e.g. \"forge-108\")\n * @param signal The signal payload to write\n * @param memoryDir Absolute path to the memory directory\n * @returns Absolute path of the written signal file\n */\nexport async function writeTaskSignal(label: string, signal: TaskSignal, memoryDir: string): Promise<string> {\n const signalsDir = join(memoryDir, \"task-signals\");\n await mkdir(signalsDir, { recursive: true });\n // Sanitise label to be filesystem-safe\n const safeLabel = label.replace(/[^a-zA-Z0-9_-]/g, \"-\");\n // Add timestamp + random suffix to prevent collisions (sanitized labels or same-millisecond writes)\n const timestamp = Date.now();\n const suffix = randomUUID().replace(/-/g, \"\").slice(0, 8);\n const filePath = join(signalsDir, `${safeLabel}-${timestamp}-${suffix}.json`);\n const artifact = createOctaveTaskHandoffArtifact(signal);\n const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;\n try {\n await writeFile(tmpPath, JSON.stringify(artifact, null, 2), \"utf-8\");\n await rename(tmpPath, filePath);\n } catch (err) {\n await unlink(tmpPath).catch(() => {});\n throw err;\n }\n return filePath;\n}\n\n/**\n * Read all pending signal files from `memory/task-signals/*.json`.\n * Returns an array of signals enriched with their file paths so the\n * orchestrator can delete/archive them after processing.\n *\n * @param memoryDir Absolute path to the memory directory\n */\nexport async function readPendingSignals(memoryDir: string): Promise<PendingTaskSignal[]> {\n const signalsDir = join(memoryDir, \"task-signals\");\n if (!existsSync(signalsDir)) return [];\n\n let files: string[];\n try {\n files = await readdir(signalsDir);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw err;\n }\n\n const resolvedSignalsDir = await realpath(signalsDir);\n const signals: PendingTaskSignal[] = [];\n\n // Orphaned atomic-write temps (crash after writeFile, before rename) — same age rule as corrupt JSON.\n for (const file of files) {\n if (!file.includes(\".json.tmp-\")) continue;\n await tryDeleteStaleCorruptSignalFile(join(signalsDir, file));\n }\n\n for (const file of files) {\n if (!file.endsWith(\".json\")) continue;\n const filePath = join(signalsDir, file);\n let resolvedFilePath: string;\n try {\n resolvedFilePath = await realpath(filePath);\n } catch {\n continue;\n }\n const rel = relative(resolvedSignalsDir, resolvedFilePath);\n if (rel.startsWith(\"..\") || isAbsolute(rel)) {\n continue;\n }\n try {\n let signalStat: Awaited<ReturnType<typeof stat>>;\n try {\n signalStat = await stat(resolvedFilePath);\n } catch {\n continue;\n }\n if (signalStat.size > MAX_TASK_SIGNAL_BYTES) {\n await deleteSignal(resolvedFilePath).catch(() => {});\n continue;\n }\n const raw = await readFile(resolvedFilePath, \"utf-8\");\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n await tryDeleteStaleCorruptSignalFile(resolvedFilePath);\n continue;\n }\n const validatedArtifact = validateOctaveTaskHandoffArtifact(parsed);\n if (validatedArtifact.valid) {\n const handoff = toHandoffRefFromArtifact(validatedArtifact.artifact);\n signals.push({\n ...validatedArtifact.artifact.payload,\n _filePath: resolvedFilePath,\n _handoff: handoff,\n });\n continue;\n }\n await deleteSignal(resolvedFilePath).catch(() => {});\n } catch {\n // Ignore transient read errors (race with delete, etc.)\n }\n }\n\n return signals;\n}\n\n/**\n * Delete a processed signal file.\n * The orchestrator calls this after applying the signal to ACTIVE-TASKS.md.\n *\n * @param filePath Absolute path of the signal file to delete (from `_filePath` field)\n */\nexport async function deleteSignal(filePath: string): Promise<void> {\n try {\n await unlink(filePath);\n } catch (err) {\n // Ignore ENOENT — already deleted (idempotent)\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Mtime-aware file I/O (optimistic concurrency)\n// ---------------------------------------------------------------------------\n\n/** ActiveTaskFile extended with the file's mtime in milliseconds. */\ninterface ActiveTaskFileWithMtime extends ActiveTaskFile {\n /** File modification time in milliseconds since epoch (from fs.stat) */\n mtime: number;\n}\n\n/**\n * Read and parse ACTIVE-TASKS.md, also capturing the file's mtime.\n * Use this when you intend to write the file back and need to detect\n * concurrent modifications (optimistic concurrency).\n *\n * @param filePath Absolute path to ACTIVE-TASKS.md\n * @param staleMinutes Minutes before a task is considered stale (default: 1440)\n */\nexport async function readActiveTaskFileWithMtime(\n filePath: string,\n staleMinutes = 1440,\n): Promise<ActiveTaskFileWithMtime | null> {\n const pathToRead = resolveActiveTaskReadPath(filePath);\n if (!pathToRead) return null;\n try {\n const [content, fileStat] = await Promise.all([readFile(pathToRead, \"utf-8\"), stat(pathToRead)]);\n const parsed = parseActiveTaskFile(content);\n parsed.active = detectStaleTasks(parsed.active, staleMinutes);\n return { ...parsed, mtime: fileStat.mtimeMs };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n}\n\n/**\n * Write ACTIVE-TASKS.md with optimistic concurrency protection.\n *\n * Before writing, re-reads the file and checks whether its mtime has changed\n * since `knownMtime` was recorded. If the file was modified concurrently, the\n * caller-supplied `merge` callback is invoked with the freshly-read file so it\n * can re-apply its changes on top of the latest state. Up to `maxRetries`\n * attempts are made; if conflicts persist, the write is aborted to avoid clobbering\n * concurrent changes.\n *\n * @param filePath Absolute path to ACTIVE-TASKS.md\n * @param active Active task entries to write\n * @param completed Completed task entries to write\n * @param knownMtime The mtime observed at the last read (milliseconds)\n * @param merge Called when a conflict is detected; receives the fresh\n * file state and must return the [active, completed] arrays\n * to write. Return null to abort the write.\n * @param maxRetries Maximum number of retry attempts (default: 3)\n * @param staleMinutes Minutes before a task is considered stale (default: 1440)\n * @returns True if a write occurred; false if merge aborted or conflicts persisted\n */\nexport async function writeActiveTaskFileOptimistic(\n filePath: string,\n active: ActiveTaskEntry[],\n completed: ActiveTaskEntry[],\n knownMtime: number,\n merge: (fresh: ActiveTaskFileWithMtime) => Promise<[ActiveTaskEntry[], ActiveTaskEntry[]] | null>,\n maxRetries = 3,\n staleMinutes = 1440,\n): Promise<boolean> {\n let currentActive = active;\n let currentCompleted = completed;\n let knownMtimeMutable = knownMtime;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n // Single read for mtime + content so the resolved path and mtime stay aligned across\n // legacy ACTIVE-TASK.md → ACTIVE-TASKS.md migration (avoids stat vs read path drift).\n const snapshot = await readActiveTaskFileWithMtime(filePath, staleMinutes);\n if (snapshot === null) {\n await writeActiveTaskFile(filePath, currentActive, currentCompleted);\n return true;\n }\n\n if (snapshot.mtime !== knownMtimeMutable) {\n const merged = await merge(snapshot);\n if (merged === null) return false;\n [currentActive, currentCompleted] = merged;\n knownMtimeMutable = snapshot.mtime;\n continue;\n }\n\n await writeActiveTaskFile(filePath, currentActive, currentCompleted);\n return true;\n }\n\n // Exhausted retries — fail safely to avoid clobbering concurrent updates.\n pluginLogger.warn(\n `memory-hybrid: writeActiveTaskFileOptimistic exhausted ${maxRetries} retries for ${filePath}; aborting write`,\n );\n return false;\n}\n\n/**\n * Write ACTIVE-TASKS.md, but refuse to write if the session is a sub-agent.\n * Sub-agents should use `writeTaskSignal` to communicate status changes back\n * to the orchestrator instead of writing ACTIVE-TASKS.md directly.\n *\n * @param filePath Absolute path to ACTIVE-TASKS.md\n * @param active Active task entries to write\n * @param completed Completed task entries to write\n * @param sessionKey The current session key (used to detect sub-agent mode)\n * @returns Object indicating whether the write was skipped\n */\nexport async function writeActiveTaskFileGuarded(\n filePath: string,\n active: ActiveTaskEntry[],\n completed: ActiveTaskEntry[],\n sessionKey?: string,\n): Promise<{ skipped: boolean; reason?: string }> {\n if (isSubagentSession(sessionKey)) {\n return {\n skipped: true,\n reason: \"sub-agent sessions are read-only for ACTIVE-TASKS.md; use writeTaskSignal instead\",\n };\n }\n await writeActiveTaskFile(filePath, active, completed);\n return { skipped: false };\n}\n\n/** Result of reconciling in-progress tasks whose backing session transcript is gone (#978, #981). */\nexport interface ActiveTaskSessionReconcileResult {\n reconciledLabels: string[];\n /** True when the file was written (skipped when dryRun or nothing to do). */\n wrote: boolean;\n}\n\n/**\n * For each \"In progress\" task with an OpenClaw-shaped session reference, if no session JSONL\n * exists under ~/.openclaw/agents (per-agent sessions folders), move the task to the Completed\n * section as Done with a note (unknown outcome / bookkeeping cleanup).\n */\nexport async function reconcileActiveTaskInProgressSessions(\n filePath: string,\n staleMinutes: number,\n opts: {\n openclawHome?: string;\n flushOnComplete?: boolean;\n memoryDir?: string;\n dryRun?: boolean;\n } = {},\n): Promise<ActiveTaskSessionReconcileResult> {\n const taskFile = await readActiveTaskFile(filePath, staleMinutes);\n if (!taskFile) {\n return { reconciledLabels: [], wrote: false };\n }\n\n const openclawHome = opts.openclawHome;\n const newActive: ActiveTaskEntry[] = [];\n const newCompleted = [...taskFile.completed];\n const reconciledLabels: string[] = [];\n const toFlush: ActiveTaskEntry[] = [];\n\n for (const task of taskFile.active) {\n if (task.status !== \"In progress\") {\n newActive.push(task);\n continue;\n }\n const ref = task.subagent?.trim();\n if (!ref || !looksLikeOpenClawSessionRef(ref)) {\n newActive.push(task);\n continue;\n }\n const present = await isOpenClawSessionLikelyPresent(ref, openclawHome);\n if (present) {\n newActive.push(task);\n continue;\n }\n\n const now = new Date().toISOString();\n const completedEntry: ActiveTaskEntry = {\n ...task,\n status: \"Done\",\n updated: now,\n next: `Auto-reconciled: session transcript not found for ${ref} (subagent bookkeeping cleanup).`,\n };\n newCompleted.push(completedEntry);\n reconciledLabels.push(task.label);\n toFlush.push(completedEntry);\n }\n\n if (reconciledLabels.length === 0) {\n return { reconciledLabels, wrote: false };\n }\n if (opts.dryRun) {\n return { reconciledLabels, wrote: false };\n }\n\n await writeActiveTaskFile(filePath, newActive, newCompleted);\n\n if (opts.flushOnComplete && opts.memoryDir) {\n for (const entry of toFlush) {\n try {\n await flushCompletedTaskToMemory(entry, opts.memoryDir);\n } catch {\n // Non-fatal\n }\n }\n }\n\n return { reconciledLabels, wrote: true };\n}\n\n// ---------------------------------------------------------------------------\n// Re-exports of types used in other modules\n// ---------------------------------------------------------------------------\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"active-task.js","names":[],"sources":["../../services/active-task.ts"],"sourcesContent":["/**\n * Active Task Working Memory Service\n *\n * Parses, reads, and writes ACTIVE-TASKS.md — a structured working memory file\n * that persists in-progress task state across session restarts and context compaction.\n *\n * File format:\n * ```markdown\n * ## Active Tasks\n * ### [label]: [description]\n * - **Branch:** fix/something\n * - **Status:** In progress\n * - **Subagent:** session-key\n * - **Next:** what to do next\n * - **Started:** ISO timestamp\n * - **Updated:** ISO timestamp\n *\n * ## Completed\n * ...\n * ```\n */\n\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { chmod, mkdir, readdir, readFile, realpath, rename, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, isAbsolute, join, relative } from \"node:path\";\nimport { formatDuration } from \"../utils/duration.js\";\nimport { pluginLogger } from \"../utils/logger.js\";\nimport { stableStringify } from \"../utils/stable-stringify.js\";\nimport { isOpenClawSessionLikelyPresent, looksLikeOpenClawSessionRef } from \"./openclaw-session-artifact.js\";\n\n/** Legacy filename before default became ACTIVE-TASKS.md; still read if the new file is missing. */\nconst LEGACY_ACTIVE_TASK_BASENAME = \"ACTIVE-TASK.md\";\n\n/** Prefer `ACTIVE-TASKS.md`; if missing, read legacy `ACTIVE-TASK.md` in the same directory. */\nexport function resolveActiveTaskReadPath(filePath: string): string | null {\n if (existsSync(filePath)) return filePath;\n if (basename(filePath) === \"ACTIVE-TASKS.md\") {\n const legacyPath = join(dirname(filePath), LEGACY_ACTIVE_TASK_BASENAME);\n if (existsSync(legacyPath)) return legacyPath;\n }\n return null;\n}\n\n/**\n * Unparseable or invalid signal files (and abandoned atomic-write temp files) older than this are\n * deleted to prevent unbounded accumulation (issue #812). Exported for tests.\n */\nexport const STALE_CORRUPT_SIGNAL_MS = 5 * 60 * 1000;\nconst MAX_TASK_SIGNAL_BYTES = 256 * 1024;\n\nasync function tryDeleteStaleCorruptSignalFile(filePath: string): Promise<void> {\n try {\n const s = await stat(filePath);\n if (Date.now() - s.mtimeMs <= STALE_CORRUPT_SIGNAL_MS) return;\n await unlink(filePath);\n } catch {\n // ENOENT or other — ignore\n }\n}\n\n/**\n * Sentinel for rows with no trustworthy Started/Updated value (facts projection and parsing).\n * Not an ISO-8601 string — do not parse as a date.\n */\nexport const UNKNOWN_ACTIVE_TASK_TIME = \"Unknown\";\n\n/** Message shown when rows are omitted due to projection cap. */\nexport const OMITTED_CAP_NOTE =\n \"more not shown (projection cap). Adjust `activeTask.projection.maxRowsPerSection`, set `activeTask.projection.mode` to `full`, or query `category:project` facts.\";\n\n/** Valid task statuses */\nexport const ACTIVE_TASK_STATUSES = [\"In progress\", \"Waiting\", \"Stalled\", \"Failed\", \"Done\"] as const;\nexport type ActiveTaskStatus = (typeof ACTIVE_TASK_STATUSES)[number];\n\n/** Non-terminal statuses (still active) */\nconst ACTIVE_STATUSES: Set<ActiveTaskStatus> = new Set([\"In progress\", \"Waiting\", \"Stalled\", \"Failed\"]);\n\n/** Structured task entry */\nexport interface ActiveTaskEntry {\n /** Short unique identifier (e.g. \"forge-99\", \"deploy-prod\") */\n label: string;\n /** Human-readable description */\n description: string;\n /** Git branch if applicable */\n branch?: string;\n /** Current status */\n status: ActiveTaskStatus;\n /** Stash or commit reference */\n stashCommit?: string;\n /** Subagent session key if applicable */\n subagent?: string;\n /** What to do next */\n next?: string;\n /** ISO-8601 when known; {@link UNKNOWN_ACTIVE_TASK_TIME} when missing (facts projection). */\n started: string;\n /** ISO-8601 when known; {@link UNKNOWN_ACTIVE_TASK_TIME} when missing (facts projection). */\n updated: string;\n /** Optional goal id from project facts (`related_goal` / `goal_id`) for projection hints. */\n relatedGoal?: string;\n /** Whether task is flagged as stale (not updated within staleThreshold) */\n stale?: boolean;\n /** Structured handoff metadata from latest sub-agent signal */\n handoff?: ActiveTaskHandoffRef;\n}\n\n/** Structured handoff reference persisted in ACTIVE-TASKS.md */\nexport interface ActiveTaskHandoffRef {\n /** OCTAVE schema identifier */\n schema: string;\n /** Unique artifact id */\n artifactId: string;\n /** Signal type represented by the artifact */\n signal: TaskSignalType;\n /** Agent that emitted the signal */\n agent: string;\n /** ISO-8601 timestamp of the handoff */\n timestamp: string;\n /** SHA-256 checksum of the artifact canonical payload */\n checksum: string;\n}\n\n/** Result of parsing ACTIVE-TASKS.md */\ninterface ActiveTaskFile {\n /** Active (non-Done) tasks */\n active: ActiveTaskEntry[];\n /** Completed tasks (in ## Completed section) */\n completed: ActiveTaskEntry[];\n /** Raw content of the file */\n raw: string;\n}\n\n// ---------------------------------------------------------------------------\n// Parsing\n// ---------------------------------------------------------------------------\n\n/** Parse a single field line: `- **FieldName:** value` */\nfunction parseFieldLine(line: string): { field: string; value: string } | null {\n const match = line.match(/^-\\s+\\*\\*([^:]+):\\*\\*\\s*(.*)/);\n if (!match) return null;\n return { field: match[1].trim(), value: match[2].trim() };\n}\n\n/** Parse task block lines into an ActiveTaskEntry */\nfunction parseTaskBlock(header: string, lines: string[]): ActiveTaskEntry | null {\n // Header format: `### [label]: description` or `### label: description`\n const headerMatch = header.match(/^###\\s+\\[?([^\\]:\\n]+)\\]?:\\s*(.+)/);\n if (!headerMatch) return null;\n\n const label = headerMatch[1].trim();\n const description = headerMatch[2].trim();\n\n const entry: Partial<ActiveTaskEntry> & { label: string; description: string } = {\n label,\n description,\n status: \"In progress\",\n started: UNKNOWN_ACTIVE_TASK_TIME,\n updated: UNKNOWN_ACTIVE_TASK_TIME,\n };\n\n for (const line of lines) {\n const parsed = parseFieldLine(line);\n if (!parsed) continue;\n const { field, value } = parsed;\n switch (field.toLowerCase()) {\n case \"branch\":\n entry.branch = value || undefined;\n break;\n case \"status\":\n if (ACTIVE_TASK_STATUSES.includes(value as ActiveTaskStatus)) {\n entry.status = value as ActiveTaskStatus;\n }\n break;\n case \"stash/commit\":\n case \"stash\":\n case \"commit\":\n entry.stashCommit = value || undefined;\n break;\n case \"subagent\":\n entry.subagent = value || undefined;\n break;\n case \"session\":\n if (!entry.subagent?.trim()) {\n entry.subagent = value || undefined;\n }\n break;\n case \"next\":\n entry.next = value || undefined;\n break;\n case \"started\":\n entry.started = value || UNKNOWN_ACTIVE_TASK_TIME;\n break;\n case \"updated\":\n entry.updated = value || UNKNOWN_ACTIVE_TASK_TIME;\n break;\n case \"related goal\":\n entry.relatedGoal = value || undefined;\n break;\n case \"handoff\":\n entry.handoff = parseHandoffRef(value) ?? undefined;\n break;\n }\n }\n\n return entry as ActiveTaskEntry;\n}\n\nfunction parseHandoffRef(value: string): ActiveTaskHandoffRef | null {\n if (!value) return null;\n try {\n const parsed = JSON.parse(value) as unknown;\n if (!isHandoffRef(parsed)) return null;\n return parsed;\n } catch {\n return null;\n }\n}\n\nfunction isHandoffRef(value: unknown): value is ActiveTaskHandoffRef {\n if (!value || typeof value !== \"object\") return false;\n const v = value as Partial<ActiveTaskHandoffRef>;\n return (\n typeof v.schema === \"string\" &&\n typeof v.artifactId === \"string\" &&\n typeof v.signal === \"string\" &&\n typeof v.agent === \"string\" &&\n typeof v.timestamp === \"string\" &&\n typeof v.checksum === \"string\"\n );\n}\n\n/**\n * Extract the \"## Active Goals\" section from the raw file content.\n * Returns the section content (without the header) or undefined if not present.\n */\nfunction extractGoalsMirrorSection(content: string): string | undefined {\n const lines = content.split(\"\\n\");\n let inGoalsSection = false;\n const goalLines: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n if (trimmed === \"## Active Goals\") {\n inGoalsSection = true;\n continue;\n }\n\n // Stop at any other h2 header\n if (inGoalsSection && trimmed.startsWith(\"## \")) {\n break;\n }\n\n if (inGoalsSection) {\n goalLines.push(line);\n }\n }\n\n if (goalLines.length === 0) return undefined;\n\n // Remove leading/trailing empty lines\n while (goalLines.length > 0 && goalLines[0].trim() === \"\") {\n goalLines.shift();\n }\n while (goalLines.length > 0 && goalLines[goalLines.length - 1].trim() === \"\") {\n goalLines.pop();\n }\n\n return goalLines.length > 0 ? goalLines.join(\"\\n\") : undefined;\n}\n\n/** Parse a full ACTIVE-TASKS.md file content */\nexport function parseActiveTaskFile(content: string): ActiveTaskFile {\n const lines = content.split(\"\\n\");\n const active: ActiveTaskEntry[] = [];\n const completed: ActiveTaskEntry[] = [];\n\n let inSection: \"active\" | \"completed\" | \"other\" = \"other\";\n let currentHeader: string | null = null;\n let currentLines: string[] = [];\n\n function flushTask(): void {\n if (!currentHeader) return;\n const entry = parseTaskBlock(currentHeader, currentLines);\n if (!entry) return;\n if (inSection === \"completed\" || entry.status === \"Done\") {\n completed.push(entry);\n } else {\n active.push(entry);\n }\n currentHeader = null;\n currentLines = [];\n }\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Section headers\n if (trimmed === \"## Active Tasks\") {\n flushTask();\n inSection = \"active\";\n continue;\n }\n if (trimmed === \"## Completed\") {\n flushTask();\n inSection = \"completed\";\n continue;\n }\n // Other h2 headers reset section\n if (trimmed.startsWith(\"## \") && trimmed !== \"## Active Tasks\" && trimmed !== \"## Completed\") {\n flushTask();\n inSection = \"other\";\n continue;\n }\n\n // Task headers (h3) — only within Active Tasks / Completed sections\n if (trimmed.startsWith(\"### \")) {\n flushTask();\n if (inSection !== \"other\") {\n currentHeader = trimmed;\n currentLines = [];\n }\n continue;\n }\n\n // Accumulate field lines under current task\n if (currentHeader && (trimmed.startsWith(\"- **\") || trimmed === \"\")) {\n currentLines.push(trimmed);\n }\n }\n\n // Don't forget the last task\n flushTask();\n\n return { active, completed, raw: content };\n}\n\n// ---------------------------------------------------------------------------\n// Serialization\n// ---------------------------------------------------------------------------\n\n/** Serialize a single ActiveTaskEntry to markdown block */\nexport function serializeTaskEntry(entry: ActiveTaskEntry): string {\n const lines: string[] = [`### [${entry.label}]: ${entry.description}`];\n if (entry.branch) lines.push(`- **Branch:** ${entry.branch}`);\n lines.push(`- **Status:** ${entry.status}`);\n if (entry.stashCommit) lines.push(`- **Stash/Commit:** ${entry.stashCommit}`);\n if (entry.subagent) lines.push(`- **Subagent:** ${entry.subagent}`);\n if (entry.next) lines.push(`- **Next:** ${entry.next}`);\n if (entry.relatedGoal?.trim()) lines.push(`- **Related goal:** ${entry.relatedGoal.trim()}`);\n if (entry.handoff) lines.push(`- **Handoff:** ${stableStringify(entry.handoff)}`);\n lines.push(`- **Started:** ${entry.started}`);\n lines.push(`- **Updated:** ${entry.updated}`);\n return lines.join(\"\\n\");\n}\n\n/** Serialize all tasks back to full ACTIVE-TASKS.md content */\nexport function serializeActiveTaskFile(\n active: ActiveTaskEntry[],\n completed: ActiveTaskEntry[],\n goalsMirrorMarkdown?: string,\n omitted?: { active: number; completed: number },\n): string {\n const parts: string[] = [\"# ACTIVE-TASKS.md — Working Memory\\n\"];\n\n parts.push(\"## Active Tasks\\n\");\n if (active.length === 0) {\n parts.push(\"_No active tasks._\\n\");\n } else {\n for (const entry of active) {\n parts.push(serializeTaskEntry(entry));\n parts.push(\"\");\n }\n }\n\n if (omitted?.active && omitted.active > 0) {\n parts.push(`> ${omitted.active} ${OMITTED_CAP_NOTE}\\n\\n`);\n }\n\n if (goalsMirrorMarkdown !== undefined) {\n parts.push(\"## Active Goals\\n\");\n parts.push(\"_Mirror from goal registry — do not edit by hand; refreshed on heartbeat._\\n\\n\");\n parts.push(goalsMirrorMarkdown);\n if (!goalsMirrorMarkdown.endsWith(\"\\n\")) parts.push(\"\\n\");\n }\n\n if (completed.length > 0 || (omitted?.completed && 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 && omitted.completed > 0) {\n parts.push(`> ${omitted.completed} ${OMITTED_CAP_NOTE}\\n`);\n }\n }\n\n return parts.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Stale detection\n// ---------------------------------------------------------------------------\n\n/**\n * Flag tasks that have not been updated in more than `staleMinutes` minutes.\n * Returns new array with `stale` property set.\n */\nexport function detectStaleTasks(tasks: ActiveTaskEntry[], staleMinutes: number): ActiveTaskEntry[] {\n const now = Date.now();\n const staleMs = staleMinutes * 60 * 1000;\n return tasks.map((t) => {\n const updatedMs = new Date(t.updated).getTime();\n const hasValidUpdated =\n t.updated !== UNKNOWN_ACTIVE_TASK_TIME && Number.isFinite(updatedMs) && !Number.isNaN(updatedMs);\n const isStale = !hasValidUpdated || now - updatedMs > staleMs;\n return { ...t, stale: isStale };\n });\n}\n\n// ---------------------------------------------------------------------------\n// File I/O\n// ---------------------------------------------------------------------------\n\n/** Same as {@link readActiveTaskFile}, plus the resolved path actually read (canonical or legacy). */\nexport async function readActiveTaskFileWithResolvedPath(\n filePath: string,\n staleMinutes = 1440,\n): Promise<(ActiveTaskFile & { readFrom: string }) | null> {\n const pathToRead = resolveActiveTaskReadPath(filePath);\n if (!pathToRead) return null;\n try {\n const content = await readFile(pathToRead, \"utf-8\");\n const parsed = parseActiveTaskFile(content);\n parsed.active = detectStaleTasks(parsed.active, staleMinutes);\n return { ...parsed, readFrom: pathToRead };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n}\n\n/**\n * Read and parse ACTIVE-TASKS.md from disk. Returns null if file doesn't exist.\n *\n * @param filePath - Absolute path to ACTIVE-TASKS.md\n * @param staleMinutes - Minutes before a task is considered stale (default: 1440 = 24h)\n */\nexport async function readActiveTaskFile(filePath: string, staleMinutes = 1440): Promise<ActiveTaskFile | null> {\n const r = await readActiveTaskFileWithResolvedPath(filePath, staleMinutes);\n if (!r) return null;\n const { readFrom: _readFrom, ...file } = r;\n return file;\n}\n\n/** Write tasks back to ACTIVE-TASKS.md. Creates parent directories as needed. */\nexport async function writeActiveTaskFile(\n filePath: string,\n active: ActiveTaskEntry[],\n completed: ActiveTaskEntry[],\n): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n\n // Preserve existing goals mirror section if present\n let goalsMirror: string | undefined;\n const pathToRead = resolveActiveTaskReadPath(filePath);\n if (pathToRead) {\n try {\n const existing = await readFile(pathToRead, \"utf-8\");\n goalsMirror = extractGoalsMirrorSection(existing);\n } catch {\n // Ignore read errors; write without goals section\n }\n }\n\n const content = serializeActiveTaskFile(active, completed, goalsMirror);\n const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}-${randomUUID().slice(0, 8)}`;\n let existingMode: number | null = null;\n try {\n existingMode = (await stat(filePath)).mode & 0o777;\n } catch {\n existingMode = null;\n }\n try {\n await writeFile(tmpPath, content, \"utf-8\");\n if (existingMode !== null) await chmod(tmpPath, existingMode);\n await rename(tmpPath, filePath);\n } catch (err) {\n await unlink(tmpPath).catch(() => {});\n throw err;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Task mutations\n// ---------------------------------------------------------------------------\n\n/**\n * Add or update a task entry. If an entry with the same label exists, update it.\n * Otherwise, append a new entry.\n *\n * @param active Active task entries\n * @param entry Entry to upsert\n * @param preserveUpdated If true, use entry.updated as-is; otherwise set to current time\n */\nexport function upsertTask(\n active: ActiveTaskEntry[],\n entry: ActiveTaskEntry,\n preserveUpdated = false,\n): ActiveTaskEntry[] {\n const idx = active.findIndex((t) => t.label === entry.label);\n const updatedTimestamp = preserveUpdated ? entry.updated : new Date().toISOString();\n if (idx >= 0) {\n const updated = [...active];\n updated[idx] = { ...active[idx], ...entry, updated: updatedTimestamp };\n return updated;\n }\n return [...active, { ...entry, updated: updatedTimestamp }];\n}\n\n/**\n * Mark a task as Done, remove from active list, return entry (for flush to memory).\n */\nexport function completeTask(\n active: ActiveTaskEntry[],\n label: string,\n): { updated: ActiveTaskEntry[]; completed: ActiveTaskEntry | null } {\n const idx = active.findIndex((t) => t.label === label);\n if (idx < 0) return { updated: active, completed: null };\n const task: ActiveTaskEntry = {\n ...active[idx],\n status: \"Done\",\n updated: new Date().toISOString(),\n };\n const updated = active.filter((_, i) => i !== idx);\n return { updated, completed: task };\n}\n\n// ---------------------------------------------------------------------------\n// Injection summary builder\n// ---------------------------------------------------------------------------\n\nexport type ActiveTaskInjectionBuildResult = {\n text: string;\n injectedCount: number;\n};\n\n/**\n * Build a compact injection block for the active task working memory.\n * Budget-capped to `maxTokens` or `opts.maxChars` (approximate — 4 chars ≈ 1 token).\n */\nexport function buildActiveTaskInjection(\n tasks: ActiveTaskEntry[],\n maxTokens: number,\n opts?: { maxChars?: number; excludeStale?: boolean },\n): ActiveTaskInjectionBuildResult {\n const activeTasks = tasks.filter((t) => ACTIVE_STATUSES.has(t.status) && (!opts?.excludeStale || !t.stale));\n if (activeTasks.length === 0) return { text: \"\", injectedCount: 0 };\n\n const lines: string[] = [\"<active-tasks>\", \"In-progress tasks from ACTIVE-TASKS.md:\"];\n\n const charBudget = typeof opts?.maxChars === \"number\" && opts.maxChars > 0 ? opts.maxChars - 60 : maxTokens * 4 - 60;\n let used = 0;\n let injectedCount = 0;\n\n for (const task of activeTasks) {\n const summary = [`- [${task.label}] ${task.description} (${task.status})`];\n if (task.next) summary.push(` Next: ${task.next}`);\n if (task.subagent) summary.push(` Subagent: ${task.subagent}`);\n const block = summary.join(\"\\n\");\n if (used + block.length > charBudget) break;\n lines.push(block);\n used += block.length + 1;\n injectedCount++;\n }\n\n if (lines.length === 2) return { text: \"\", injectedCount: 0 };\n\n lines.push(\"</active-tasks>\");\n return { text: lines.join(\"\\n\"), injectedCount };\n}\n\n// ---------------------------------------------------------------------------\n// Stale warning injection builder\n// ---------------------------------------------------------------------------\n\n/**\n * Build a stale-task warning block to prepend to the agent context.\n *\n * Surfaces two kinds of information:\n * 1. Tasks where `stale === true` (flagged by `detectStaleTasks`) — shown with elapsed time.\n * 2. Any \"In progress\" task that has a `subagent` field — hints the agent to verify the subagent\n * is still running via `subagents list`.\n *\n * Returns an empty string when there is nothing to report, so the caller can\n * skip injection entirely.\n *\n * @param tasks Active tasks (must have `stale` already computed by `detectStaleTasks`).\n * @param staleMinutes Threshold used for the warning label (e.g. 1440 → shows \">24h\").\n * @param maxChars Optional character budget cap (approximate — 4 chars ≈ 1 token). If provided, truncates output.\n * @returns Object with the rendered text and the count of tasks actually rendered.\n */\nexport function buildStaleWarningInjection(\n tasks: ActiveTaskEntry[],\n staleMinutes: number,\n maxChars?: number,\n): { text: string; renderedCount: number } {\n const staleTasks = tasks.filter((t) => t.stale);\n // Hint for any \"In progress\" task with a subagent — regardless of staleness.\n const inProgressWithSubagent = tasks.filter((t) => t.status === \"In progress\" && t.subagent);\n\n if (staleTasks.length === 0 && inProgressWithSubagent.length === 0) return { text: \"\", renderedCount: 0 };\n\n const lines: string[] = [];\n const thresholdDisplay = formatDuration(staleMinutes);\n let usedChars = 0;\n let renderedCount = 0;\n\n // ── Stale task warnings ──────────────────────────────────────────────────\n if (staleTasks.length > 0) {\n const header = `⚠️ STALE ACTIVE TASKS (not updated in >${thresholdDisplay}):`;\n if (maxChars != null && usedChars + header.length > maxChars) return { text: \"\", renderedCount: 0 };\n lines.push(header);\n usedChars += header.length + 1;\n\n const now = Date.now();\n for (const task of staleTasks) {\n const updatedMs = new Date(task.updated).getTime();\n const hoursAgo = Number.isNaN(updatedMs) ? \"?\" : Math.floor((now - updatedMs) / (60 * 60 * 1000));\n const line1 = `- [${task.label}]: ${task.description} — last updated ${task.updated} (${hoursAgo}h ago)`;\n const nextPart = task.next ? `, Next: ${task.next}` : \"\";\n const line2 = ` Status: ${task.status}${nextPart}`;\n const blockSize = line1.length + line2.length + 2;\n if (maxChars != null && usedChars + blockSize > maxChars) break;\n lines.push(line1);\n lines.push(line2);\n usedChars += blockSize;\n renderedCount++;\n }\n const footer = \"Consider: check subagent status, resume, or mark complete.\";\n if (maxChars == null || usedChars + footer.length <= maxChars) {\n lines.push(footer);\n usedChars += footer.length + 1;\n }\n }\n\n // ── Subagent hint ────────────────────────────────────────────────────────\n if (inProgressWithSubagent.length > 0) {\n const separator = lines.length > 0 ? \"\\n\" : \"\";\n const header = \"💡 In-progress tasks with subagents — verify they are still running:\";\n const headerSize = separator.length + header.length + 1;\n if (maxChars != null && usedChars + headerSize > maxChars) {\n return { text: lines.join(\"\\n\"), renderedCount };\n }\n if (lines.length > 0) lines.push(\"\");\n lines.push(header);\n usedChars += headerSize;\n\n for (const task of inProgressWithSubagent) {\n const line = `- [${task.label}]: ${task.description} (subagent: ${task.subagent})`;\n if (maxChars != null && usedChars + line.length + 1 > maxChars) break;\n lines.push(line);\n usedChars += line.length + 1;\n renderedCount++;\n }\n const footer = \"Hint: use `subagents list` to check if these subagents are still active.\";\n if (maxChars == null || usedChars + footer.length <= maxChars) {\n lines.push(footer);\n }\n }\n\n return { text: lines.join(\"\\n\"), renderedCount };\n}\n\n// ---------------------------------------------------------------------------\n// Flush to daily memory log\n// ---------------------------------------------------------------------------\n\n/**\n * Append completed task summary to `memory/YYYY-MM-DD.md`.\n * Creates the file if it doesn't exist.\n */\nexport async function flushCompletedTaskToMemory(task: ActiveTaskEntry, memoryDir: string): Promise<string> {\n const date = new Date().toISOString().slice(0, 10);\n const filePath = join(memoryDir, `${date}.md`);\n\n await mkdir(memoryDir, { recursive: true });\n\n let existing = \"\";\n try {\n existing = await readFile(filePath, \"utf-8\");\n } catch (err) {\n // Only swallow \"file not found\" — rethrow permission errors, malformed reads, etc.\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n existing = \"\";\n } else {\n throw err;\n }\n }\n\n const summary = [\n `## Completed Task: [${task.label}] ${task.description}`,\n \"- **Status:** Done\",\n `- **Started:** ${task.started}`,\n `- **Completed:** ${task.updated}`,\n ...(task.branch ? [`- **Branch:** ${task.branch}`] : []),\n ...(task.subagent ? [`- **Subagent:** ${task.subagent}`] : []),\n ...(task.handoff ? [`- **Handoff:** ${stableStringify(task.handoff)}`] : []),\n \"\",\n ].join(\"\\n\");\n\n const newContent = existing ? `${existing.trimEnd()}\\n\\n${summary}` : `# Memory Log — ${date}\\n\\n${summary}`;\n\n await writeFile(filePath, newContent, \"utf-8\");\n return filePath;\n}\n\n// ---------------------------------------------------------------------------\n// Sub-agent detection\n// ---------------------------------------------------------------------------\n\n/**\n * Returns true if the given session key belongs to a sub-agent.\n * Sub-agents have \"subagent:\" somewhere in their session key.\n *\n * Examples:\n * \"agent:forge:subagent:f3d14066\" → true\n * \"agent:main:main\" → false\n * undefined → false\n */\nexport function isSubagentSession(sessionKey?: string): boolean {\n if (!sessionKey) return false;\n return sessionKey.includes(\"subagent:\");\n}\n\n// ---------------------------------------------------------------------------\n// Task signal types (file-based sub-agent → orchestrator signalling)\n// ---------------------------------------------------------------------------\n\n/** Signal types that a sub-agent can emit. */\nexport type TaskSignalType = \"completed\" | \"blocked\" | \"escalate\" | \"update\";\n\n/**\n * Structured status signal that a sub-agent writes to\n * `memory/task-signals/<label>-<timestamp>-<suffix>.json` for the orchestrator to consume.\n */\nexport interface TaskSignal {\n /** Identifier of the emitting agent (e.g. \"reaver-mqtt-auth\") */\n agent: string;\n /** Human-readable task reference (e.g. \"Yarbo RE: MQTT Auth\") */\n taskRef: string;\n /** ISO-8601 timestamp of when the signal was emitted */\n timestamp: string;\n /** Signal type */\n signal: TaskSignalType;\n /** Short human-readable summary of what changed */\n summary: string;\n /** Optional: from/to status transition */\n statusChange?: { from: string; to: string };\n /** Optional: key findings or data points to surface */\n findings?: string[];\n}\n\n/** OCTAVE-style artifact schema identifier for task handoff files. */\nexport const OCTAVE_TASK_HANDOFF_SCHEMA = \"octave/task-handoff@v1\";\n\n/** Typed OCTAVE-style handoff artifact persisted to disk. */\ninterface OctaveTaskHandoffArtifact {\n /** Schema identifier */\n schema: typeof OCTAVE_TASK_HANDOFF_SCHEMA;\n /** Artifact type */\n artifactType: \"task_handoff\";\n /** Schema version */\n version: 1;\n /** Unique artifact id */\n artifactId: string;\n /** Deterministic canonical JSON of payload used for checksum and diffing */\n canonical: string;\n /** SHA-256 checksum of canonical payload */\n checksum: string;\n /** Structured payload */\n payload: TaskSignal;\n /** Append-only audit trail for artifact lifecycle */\n auditTrail: Array<{\n at: string;\n by: string;\n action: \"created\" | \"validated\";\n note?: string;\n }>;\n}\n\n/**\n * A TaskSignal enriched with the path of the file it was read from,\n * so the orchestrator can delete/archive it after processing.\n */\nexport interface PendingTaskSignal extends TaskSignal {\n /** Absolute path of the signal file on disk */\n _filePath: string;\n /** Structured OCTAVE handoff metadata if source file used the new schema */\n _handoff?: ActiveTaskHandoffRef;\n}\n\nfunction isTaskSignal(signal: unknown): signal is TaskSignal {\n if (!signal || typeof signal !== \"object\") return false;\n const s = signal as Partial<TaskSignal>;\n return (\n typeof s.agent === \"string\" &&\n typeof s.taskRef === \"string\" &&\n typeof s.timestamp === \"string\" &&\n typeof s.signal === \"string\" &&\n typeof s.summary === \"string\"\n );\n}\n\nfunction buildCanonicalPayload(signal: TaskSignal): string {\n return stableStringify(signal);\n}\n\nfunction buildChecksum(canonical: string): string {\n return createHash(\"sha256\").update(canonical).digest(\"hex\");\n}\n\nfunction toHandoffRefFromArtifact(artifact: OctaveTaskHandoffArtifact): ActiveTaskHandoffRef {\n return {\n schema: artifact.schema,\n artifactId: artifact.artifactId,\n signal: artifact.payload.signal,\n agent: artifact.payload.agent,\n timestamp: artifact.payload.timestamp,\n checksum: artifact.checksum,\n };\n}\n\nexport function createOctaveTaskHandoffArtifact(signal: TaskSignal): OctaveTaskHandoffArtifact {\n const canonical = buildCanonicalPayload(signal);\n const checksum = buildChecksum(canonical);\n return {\n schema: OCTAVE_TASK_HANDOFF_SCHEMA,\n artifactType: \"task_handoff\",\n version: 1,\n artifactId: randomUUID(),\n canonical,\n checksum,\n payload: signal,\n auditTrail: [\n {\n at: new Date().toISOString(),\n by: signal.agent,\n action: \"created\",\n },\n ],\n };\n}\n\nexport function validateOctaveTaskHandoffArtifact(\n value: unknown,\n): { valid: true; artifact: OctaveTaskHandoffArtifact } | { valid: false; reason: string } {\n if (!value || typeof value !== \"object\") return { valid: false, reason: \"artifact is not an object\" };\n const artifact = value as Partial<OctaveTaskHandoffArtifact>;\n if (artifact.schema !== OCTAVE_TASK_HANDOFF_SCHEMA) return { valid: false, reason: \"schema mismatch\" };\n if (artifact.artifactType !== \"task_handoff\") return { valid: false, reason: \"artifact type mismatch\" };\n if (artifact.version !== 1) return { valid: false, reason: \"unsupported artifact version\" };\n if (typeof artifact.artifactId !== \"string\" || artifact.artifactId.length === 0) {\n return { valid: false, reason: \"missing artifact id\" };\n }\n if (typeof artifact.canonical !== \"string\" || artifact.canonical.length === 0) {\n return { valid: false, reason: \"missing canonical payload\" };\n }\n if (typeof artifact.checksum !== \"string\" || artifact.checksum.length === 0) {\n return { valid: false, reason: \"missing checksum\" };\n }\n if (!Array.isArray(artifact.auditTrail)) return { valid: false, reason: \"missing audit trail\" };\n if (!isTaskSignal(artifact.payload)) return { valid: false, reason: \"invalid payload\" };\n const expectedCanonical = buildCanonicalPayload(artifact.payload);\n if (artifact.canonical !== expectedCanonical) return { valid: false, reason: \"canonical mismatch\" };\n const expectedChecksum = buildChecksum(artifact.canonical);\n if (artifact.checksum !== expectedChecksum) return { valid: false, reason: \"checksum mismatch\" };\n return { valid: true, artifact: artifact as OctaveTaskHandoffArtifact };\n}\n\n/**\n * Emit a structured signal file for the orchestrator to consume.\n * Should only be called by sub-agents (but is not restricted).\n *\n * @param label Short identifier for this signal (used in filename, e.g. \"forge-108\")\n * @param signal The signal payload to write\n * @param memoryDir Absolute path to the memory directory\n * @returns Absolute path of the written signal file\n */\nexport async function writeTaskSignal(label: string, signal: TaskSignal, memoryDir: string): Promise<string> {\n const signalsDir = join(memoryDir, \"task-signals\");\n await mkdir(signalsDir, { recursive: true });\n // Sanitise label to be filesystem-safe\n const safeLabel = label.replace(/[^a-zA-Z0-9_-]/g, \"-\");\n // Add timestamp + random suffix to prevent collisions (sanitized labels or same-millisecond writes)\n const timestamp = Date.now();\n const suffix = randomUUID().replace(/-/g, \"\").slice(0, 8);\n const filePath = join(signalsDir, `${safeLabel}-${timestamp}-${suffix}.json`);\n const artifact = createOctaveTaskHandoffArtifact(signal);\n const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;\n try {\n await writeFile(tmpPath, JSON.stringify(artifact, null, 2), \"utf-8\");\n await rename(tmpPath, filePath);\n } catch (err) {\n await unlink(tmpPath).catch(() => {});\n throw err;\n }\n return filePath;\n}\n\n/**\n * Read all pending signal files from `memory/task-signals/*.json`.\n * Returns an array of signals enriched with their file paths so the\n * orchestrator can delete/archive them after processing.\n *\n * @param memoryDir Absolute path to the memory directory\n */\nexport async function readPendingSignals(memoryDir: string): Promise<PendingTaskSignal[]> {\n const signalsDir = join(memoryDir, \"task-signals\");\n if (!existsSync(signalsDir)) return [];\n\n let files: string[];\n try {\n files = await readdir(signalsDir);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw err;\n }\n\n const resolvedSignalsDir = await realpath(signalsDir);\n const signals: PendingTaskSignal[] = [];\n\n // Orphaned atomic-write temps (crash after writeFile, before rename) — same age rule as corrupt JSON.\n for (const file of files) {\n if (!file.includes(\".json.tmp-\")) continue;\n await tryDeleteStaleCorruptSignalFile(join(signalsDir, file));\n }\n\n for (const file of files) {\n if (!file.endsWith(\".json\")) continue;\n const filePath = join(signalsDir, file);\n let resolvedFilePath: string;\n try {\n resolvedFilePath = await realpath(filePath);\n } catch {\n continue;\n }\n const rel = relative(resolvedSignalsDir, resolvedFilePath);\n if (rel.startsWith(\"..\") || isAbsolute(rel)) {\n continue;\n }\n try {\n let signalStat: Awaited<ReturnType<typeof stat>>;\n try {\n signalStat = await stat(resolvedFilePath);\n } catch {\n continue;\n }\n if (signalStat.size > MAX_TASK_SIGNAL_BYTES) {\n await deleteSignal(resolvedFilePath).catch(() => {});\n continue;\n }\n const raw = await readFile(resolvedFilePath, \"utf-8\");\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n await tryDeleteStaleCorruptSignalFile(resolvedFilePath);\n continue;\n }\n const validatedArtifact = validateOctaveTaskHandoffArtifact(parsed);\n if (validatedArtifact.valid) {\n const handoff = toHandoffRefFromArtifact(validatedArtifact.artifact);\n signals.push({\n ...validatedArtifact.artifact.payload,\n _filePath: resolvedFilePath,\n _handoff: handoff,\n });\n continue;\n }\n await deleteSignal(resolvedFilePath).catch(() => {});\n } catch {\n // Ignore transient read errors (race with delete, etc.)\n }\n }\n\n return signals;\n}\n\n/**\n * Delete a processed signal file.\n * The orchestrator calls this after applying the signal to ACTIVE-TASKS.md.\n *\n * @param filePath Absolute path of the signal file to delete (from `_filePath` field)\n */\nexport async function deleteSignal(filePath: string): Promise<void> {\n try {\n await unlink(filePath);\n } catch (err) {\n // Ignore ENOENT — already deleted (idempotent)\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Mtime-aware file I/O (optimistic concurrency)\n// ---------------------------------------------------------------------------\n\n/** ActiveTaskFile extended with the file's mtime in milliseconds. */\ninterface ActiveTaskFileWithMtime extends ActiveTaskFile {\n /** File modification time in milliseconds since epoch (from fs.stat) */\n mtime: number;\n}\n\n/**\n * Read and parse ACTIVE-TASKS.md, also capturing the file's mtime.\n * Use this when you intend to write the file back and need to detect\n * concurrent modifications (optimistic concurrency).\n *\n * @param filePath Absolute path to ACTIVE-TASKS.md\n * @param staleMinutes Minutes before a task is considered stale (default: 1440)\n */\nexport async function readActiveTaskFileWithMtime(\n filePath: string,\n staleMinutes = 1440,\n): Promise<ActiveTaskFileWithMtime | null> {\n const pathToRead = resolveActiveTaskReadPath(filePath);\n if (!pathToRead) return null;\n try {\n const [content, fileStat] = await Promise.all([readFile(pathToRead, \"utf-8\"), stat(pathToRead)]);\n const parsed = parseActiveTaskFile(content);\n parsed.active = detectStaleTasks(parsed.active, staleMinutes);\n return { ...parsed, mtime: fileStat.mtimeMs };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n}\n\n/**\n * Write ACTIVE-TASKS.md with optimistic concurrency protection.\n *\n * Before writing, re-reads the file and checks whether its mtime has changed\n * since `knownMtime` was recorded. If the file was modified concurrently, the\n * caller-supplied `merge` callback is invoked with the freshly-read file so it\n * can re-apply its changes on top of the latest state. Up to `maxRetries`\n * attempts are made; if conflicts persist, the write is aborted to avoid clobbering\n * concurrent changes.\n *\n * @param filePath Absolute path to ACTIVE-TASKS.md\n * @param active Active task entries to write\n * @param completed Completed task entries to write\n * @param knownMtime The mtime observed at the last read (milliseconds)\n * @param merge Called when a conflict is detected; receives the fresh\n * file state and must return the [active, completed] arrays\n * to write. Return null to abort the write.\n * @param maxRetries Maximum number of retry attempts (default: 3)\n * @param staleMinutes Minutes before a task is considered stale (default: 1440)\n * @returns True if a write occurred; false if merge aborted or conflicts persisted\n */\nexport async function writeActiveTaskFileOptimistic(\n filePath: string,\n active: ActiveTaskEntry[],\n completed: ActiveTaskEntry[],\n knownMtime: number,\n merge: (fresh: ActiveTaskFileWithMtime) => Promise<[ActiveTaskEntry[], ActiveTaskEntry[]] | null>,\n maxRetries = 3,\n staleMinutes = 1440,\n): Promise<boolean> {\n let currentActive = active;\n let currentCompleted = completed;\n let knownMtimeMutable = knownMtime;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n // Single read for mtime + content so the resolved path and mtime stay aligned across\n // legacy ACTIVE-TASK.md → ACTIVE-TASKS.md migration (avoids stat vs read path drift).\n const snapshot = await readActiveTaskFileWithMtime(filePath, staleMinutes);\n if (snapshot === null) {\n await writeActiveTaskFile(filePath, currentActive, currentCompleted);\n return true;\n }\n\n if (snapshot.mtime !== knownMtimeMutable) {\n const merged = await merge(snapshot);\n if (merged === null) return false;\n [currentActive, currentCompleted] = merged;\n knownMtimeMutable = snapshot.mtime;\n continue;\n }\n\n await writeActiveTaskFile(filePath, currentActive, currentCompleted);\n return true;\n }\n\n // Exhausted retries — fail safely to avoid clobbering concurrent updates.\n pluginLogger.warn(\n `memory-hybrid: writeActiveTaskFileOptimistic exhausted ${maxRetries} retries for ${filePath}; aborting write`,\n );\n return false;\n}\n\n/**\n * Write ACTIVE-TASKS.md, but refuse to write if the session is a sub-agent.\n * Sub-agents should use `writeTaskSignal` to communicate status changes back\n * to the orchestrator instead of writing ACTIVE-TASKS.md directly.\n *\n * @param filePath Absolute path to ACTIVE-TASKS.md\n * @param active Active task entries to write\n * @param completed Completed task entries to write\n * @param sessionKey The current session key (used to detect sub-agent mode)\n * @returns Object indicating whether the write was skipped\n */\nexport async function writeActiveTaskFileGuarded(\n filePath: string,\n active: ActiveTaskEntry[],\n completed: ActiveTaskEntry[],\n sessionKey?: string,\n): Promise<{ skipped: boolean; reason?: string }> {\n if (isSubagentSession(sessionKey)) {\n return {\n skipped: true,\n reason: \"sub-agent sessions are read-only for ACTIVE-TASKS.md; use writeTaskSignal instead\",\n };\n }\n await writeActiveTaskFile(filePath, active, completed);\n return { skipped: false };\n}\n\n/** Result of reconciling in-progress tasks whose backing session transcript is gone (#978, #981). */\nexport interface ActiveTaskSessionReconcileResult {\n reconciledLabels: string[];\n /** True when the file was written (skipped when dryRun or nothing to do). */\n wrote: boolean;\n}\n\n/**\n * For each \"In progress\" task with an OpenClaw-shaped session reference, if no session JSONL\n * exists under ~/.openclaw/agents (per-agent sessions folders), move the task to the Completed\n * section as Done with a note (unknown outcome / bookkeeping cleanup).\n */\nexport async function reconcileActiveTaskInProgressSessions(\n filePath: string,\n staleMinutes: number,\n opts: {\n openclawHome?: string;\n flushOnComplete?: boolean;\n memoryDir?: string;\n dryRun?: boolean;\n } = {},\n): Promise<ActiveTaskSessionReconcileResult> {\n const taskFile = await readActiveTaskFile(filePath, staleMinutes);\n if (!taskFile) {\n return { reconciledLabels: [], wrote: false };\n }\n\n const openclawHome = opts.openclawHome;\n const newActive: ActiveTaskEntry[] = [];\n const newCompleted = [...taskFile.completed];\n const reconciledLabels: string[] = [];\n const toFlush: ActiveTaskEntry[] = [];\n\n for (const task of taskFile.active) {\n if (task.status !== \"In progress\") {\n newActive.push(task);\n continue;\n }\n const ref = task.subagent?.trim();\n if (!ref || !looksLikeOpenClawSessionRef(ref)) {\n newActive.push(task);\n continue;\n }\n const present = await isOpenClawSessionLikelyPresent(ref, openclawHome);\n if (present) {\n newActive.push(task);\n continue;\n }\n\n const now = new Date().toISOString();\n const completedEntry: ActiveTaskEntry = {\n ...task,\n status: \"Done\",\n updated: now,\n next: `Auto-reconciled: session transcript not found for ${ref} (subagent bookkeeping cleanup).`,\n };\n newCompleted.push(completedEntry);\n reconciledLabels.push(task.label);\n toFlush.push(completedEntry);\n }\n\n if (reconciledLabels.length === 0) {\n return { reconciledLabels, wrote: false };\n }\n if (opts.dryRun) {\n return { reconciledLabels, wrote: false };\n }\n\n await writeActiveTaskFile(filePath, newActive, newCompleted);\n\n if (opts.flushOnComplete && opts.memoryDir) {\n for (const entry of toFlush) {\n try {\n await flushCompletedTaskToMemory(entry, opts.memoryDir);\n } catch {\n // Non-fatal\n }\n }\n }\n\n return { reconciledLabels, wrote: true };\n}\n\n// ---------------------------------------------------------------------------\n// Re-exports of types used in other modules\n// ---------------------------------------------------------------------------\n"],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region services/adaptive-catch-up-pacing.ts
|
|
2
|
+
/**
|
|
3
|
+
* Adaptive catch-up pressure backoff: provider Retry-After is a hard floor and is not
|
|
4
|
+
* reduced by the normal adaptive max-delay cap (used only when no Retry-After hint).
|
|
5
|
+
*/
|
|
6
|
+
function computeAdaptivePressureDelayMs(opts) {
|
|
7
|
+
const retryAfterFloor = opts.batchRetryAfterMs ?? 0;
|
|
8
|
+
const retryAfterDelay = Math.max(retryAfterFloor, opts.currentDelayMs);
|
|
9
|
+
const scaledDelay = Math.ceil(retryAfterDelay * 1.5);
|
|
10
|
+
const computed = Math.max(opts.backoffMinDelayMs, scaledDelay, retryAfterFloor);
|
|
11
|
+
if (retryAfterFloor > 0) return computed;
|
|
12
|
+
return Math.min(opts.maxAdaptiveDelayMs, computed);
|
|
13
|
+
}
|
|
14
|
+
/** Cap inter-batch sleep so a configured time budget is not exceeded by pacing delay. */
|
|
15
|
+
function capDelayMsByDeadline(delayMs, deadlineMs, nowMs = Date.now()) {
|
|
16
|
+
if (delayMs <= 0) return 0;
|
|
17
|
+
if (deadlineMs == null) return delayMs;
|
|
18
|
+
const remainingMs = deadlineMs - nowMs;
|
|
19
|
+
if (remainingMs <= 0) return 0;
|
|
20
|
+
return Math.min(delayMs, remainingMs);
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { capDelayMsByDeadline, computeAdaptivePressureDelayMs };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=adaptive-catch-up-pacing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adaptive-catch-up-pacing.js","names":[],"sources":["../../services/adaptive-catch-up-pacing.ts"],"sourcesContent":["/**\n * Adaptive catch-up pressure backoff: provider Retry-After is a hard floor and is not\n * reduced by the normal adaptive max-delay cap (used only when no Retry-After hint).\n */\nexport function computeAdaptivePressureDelayMs(opts: {\n currentDelayMs: number;\n batchRetryAfterMs?: number;\n maxAdaptiveDelayMs: number;\n backoffMinDelayMs: number;\n}): number {\n const retryAfterFloor = opts.batchRetryAfterMs ?? 0;\n const retryAfterDelay = Math.max(retryAfterFloor, opts.currentDelayMs);\n const scaledDelay = Math.ceil(retryAfterDelay * 1.5);\n const computed = Math.max(opts.backoffMinDelayMs, scaledDelay, retryAfterFloor);\n if (retryAfterFloor > 0) {\n return computed;\n }\n return Math.min(opts.maxAdaptiveDelayMs, computed);\n}\n\n/** Cap inter-batch sleep so a configured time budget is not exceeded by pacing delay. */\nexport function capDelayMsByDeadline(delayMs: number, deadlineMs: number | undefined, nowMs = Date.now()): number {\n if (delayMs <= 0) return 0;\n if (deadlineMs == null) return delayMs;\n const remainingMs = deadlineMs - nowMs;\n if (remainingMs <= 0) return 0;\n return Math.min(delayMs, remainingMs);\n}\n"],"mappings":";;;;;AAIA,SAAgB,+BAA+B,MAKpC;CACT,MAAM,kBAAkB,KAAK,qBAAqB;CAClD,MAAM,kBAAkB,KAAK,IAAI,iBAAiB,KAAK,cAAc;CACrE,MAAM,cAAc,KAAK,KAAK,kBAAkB,GAAG;CACnD,MAAM,WAAW,KAAK,IAAI,KAAK,mBAAmB,aAAa,eAAe;CAC9E,IAAI,kBAAkB,GACpB,OAAO;CAET,OAAO,KAAK,IAAI,KAAK,oBAAoB,QAAQ;AACnD;;AAGA,SAAgB,qBAAqB,SAAiB,YAAgC,QAAQ,KAAK,IAAI,GAAW;CAChH,IAAI,WAAW,GAAG,OAAO;CACzB,IAAI,cAAc,MAAM,OAAO;CAC/B,MAAM,cAAc,aAAa;CACjC,IAAI,eAAe,GAAG,OAAO;CAC7B,OAAO,KAAK,IAAI,SAAS,WAAW;AACtC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adaptive-maintenance-llm.js","names":[],"sources":["../../services/adaptive-maintenance-llm.ts"],"sourcesContent":["import type OpenAI from \"openai\";\nimport { getEnv } from \"../utils/env-manager.js\";\nimport { estimateTokens } from \"../utils/text.js\";\nimport {\n type AdaptiveFailureKind,\n type AdaptiveModelLimitsStateV1,\n ADAPTIVE_MODEL_LIMITS_VERSION,\n getEffectiveModelLimits,\n loadAdaptiveModelLimits,\n recordAdaptiveFailure,\n recordAdaptiveSuccess,\n saveAdaptiveModelLimits,\n} from \"./adaptive-model-limits.js\";\nimport {\n chatCompleteWithRetryDetailed,\n distillBatchTokenLimit,\n distillMaxOutputTokens,\n is403QuotaOrRateLimitLike,\n is429OrWrapped,\n isContextLengthError,\n type ChatCompleteWithRetryDetails,\n} from \"./chat.js\";\nimport type { CostFeatureId } from \"./cost-feature-labels.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\n\nexport type MaintenanceLlmLogger = {\n info?: (msg: string) => void;\n warn?: (msg: string) => void;\n};\n\nexport type AdaptiveMaintenanceLlmOptions = {\n model: string;\n modelSource?: string;\n fallbackModels?: string[];\n content: string;\n temperature: number;\n maxTokens: number;\n openai: OpenAI;\n label: string;\n feature: CostFeatureId;\n logger: MaintenanceLlmLogger;\n adaptiveStatePath?: string;\n enabled?: boolean;\n};\n\nfunction emptyState(): AdaptiveModelLimitsStateV1 {\n return { version: ADAPTIVE_MODEL_LIMITS_VERSION, models: {} };\n}\n\nfunction classifyAdaptiveFailure(err: Error): AdaptiveFailureKind {\n if (isContextLengthError(err)) return \"context_length\";\n if (is429OrWrapped(err)) return \"rate_limit\";\n if (is403QuotaOrRateLimitLike(err)) return \"quota\";\n if (/timed out|llm request timeout|request was aborted|Request was aborted/i.test(err.message)) return \"timeout\";\n return \"other\";\n}\n\nfunction compatibleFallbacks(\n content: string,\n fallbackModels: string[],\n logger: MaintenanceLlmLogger,\n label: string,\n): string[] {\n const inputTokens = estimateTokens(content);\n const kept: string[] = [];\n const skipped: string[] = [];\n for (const fb of fallbackModels) {\n if (inputTokens <= distillBatchTokenLimit(fb)) kept.push(fb);\n else skipped.push(fb);\n }\n if (skipped.length > 0) {\n logger.info?.(`${label}: skipping context-incompatible fallbacks: ${skipped.join(\", \")}`);\n }\n return kept;\n}\n\nexport async function chatCompleteWithAdaptiveMaintenanceRetry(\n opts: AdaptiveMaintenanceLlmOptions,\n): Promise<ChatCompleteWithRetryDetails> {\n const envAdaptiveOn = (getEnv(\"OPENCLAW_HYBRID_MEM_ADAPTIVE_DISTILL\") ?? \"\").trim() !== \"0\";\n const enabled = opts.enabled !== undefined ? opts.enabled : envAdaptiveOn;\n const envAdaptiveState = (getEnv(\"OPENCLAW_HYBRID_MEM_ADAPTIVE_DISTILL_STATE\") ?? \"\").trim();\n const adaptiveStatePath = envAdaptiveState || opts.adaptiveStatePath;\n const state = enabled && adaptiveStatePath ? loadAdaptiveModelLimits(adaptiveStatePath) : emptyState();\n const catalogBatchTokenLimit = distillBatchTokenLimit(opts.model);\n const catalogMaxOutputTokens = distillMaxOutputTokens(opts.model);\n const inputTokens = estimateTokens(opts.content);\n const effective = enabled\n ? getEffectiveModelLimits({\n state,\n model: opts.model,\n catalogBatchTokenLimit,\n catalogMaxOutputTokens,\n minBatchTokenLimit: Math.max(1024, inputTokens + 128),\n minMaxOutputTokens: Math.min(128, opts.maxTokens),\n })\n : {\n batchTokenLimit: catalogBatchTokenLimit,\n maxOutputTokens: catalogMaxOutputTokens,\n source: \"catalog\" as const,\n };\n // Completion budget: honor caller (e.g. self-correction TOOLS rewrite) up to catalog cap only.\n // Do not apply adaptive shrunk maxOutputTokens — shared state would truncate long outputs after unrelated failures.\n const maxTokens = Math.max(128, Math.min(opts.maxTokens, catalogMaxOutputTokens));\n const fallbackModels = compatibleFallbacks(opts.content, opts.fallbackModels ?? [], opts.logger, opts.label);\n\n opts.logger.info?.(\n `${opts.label}: starting with model ${opts.model} (source=${opts.modelSource ?? \"configured\"}; adaptive=${enabled ? effective.source : \"disabled\"}; inputTokens≈${inputTokens}; maxTokens=${maxTokens})`,\n );\n opts.logger.info?.(`${opts.label}: fallback chain = [${fallbackModels.length > 0 ? fallbackModels.join(\", \") : \"\"}]`);\n\n try {\n const detail = await chatCompleteWithRetryDetailed({\n model: opts.model,\n content: opts.content,\n temperature: opts.temperature,\n maxTokens,\n openai: opts.openai,\n fallbackModels,\n label: opts.label,\n feature: opts.feature,\n });\n if (enabled) {\n const usedModel = detail.modelUsed;\n const usedCatalogBatch = distillBatchTokenLimit(usedModel);\n const usedCatalogOutput = distillMaxOutputTokens(usedModel);\n recordAdaptiveSuccess({\n state,\n model: usedModel,\n catalogBatchTokenLimit: usedCatalogBatch,\n catalogMaxOutputTokens: usedCatalogOutput,\n usedBatchTokenLimit: Math.min(effective.batchTokenLimit, usedCatalogBatch),\n usedMaxOutputTokens: Math.min(maxTokens, usedCatalogOutput),\n });\n if (adaptiveStatePath) {\n try {\n saveAdaptiveModelLimits(adaptiveStatePath, state);\n } catch (saveErr) {\n capturePluginError(saveErr instanceof Error ? saveErr : new Error(String(saveErr)), {\n subsystem: \"cli\",\n operation: \"adaptive-maintenance-llm-save\",\n });\n }\n }\n }\n if (detail.modelUsed !== opts.model) {\n opts.logger.info?.(`${opts.label}: succeeded with fallback model ${detail.modelUsed}`);\n }\n return detail;\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (enabled) {\n const kind = classifyAdaptiveFailure(error);\n const failureModel =\n typeof (error as Error & { lastAttemptedModel?: string }).lastAttemptedModel === \"string\"\n ? (error as Error & { lastAttemptedModel: string }).lastAttemptedModel\n : opts.model;\n const failureCatalogBatch = distillBatchTokenLimit(failureModel);\n const failureCatalogMaxOut = distillMaxOutputTokens(failureModel);\n const failureEffective = getEffectiveModelLimits({\n state,\n model: failureModel,\n catalogBatchTokenLimit: failureCatalogBatch,\n catalogMaxOutputTokens: failureCatalogMaxOut,\n minBatchTokenLimit: Math.max(1024, inputTokens + 128),\n minMaxOutputTokens: Math.min(128, opts.maxTokens),\n });\n recordAdaptiveFailure({\n state,\n model: failureModel,\n kind,\n catalogBatchTokenLimit: failureCatalogBatch,\n catalogMaxOutputTokens: failureCatalogMaxOut,\n usedBatchTokenLimit: failureEffective.batchTokenLimit,\n usedMaxOutputTokens: Math.min(maxTokens, failureCatalogMaxOut),\n });\n if (adaptiveStatePath) {\n try {\n saveAdaptiveModelLimits(adaptiveStatePath, state);\n } catch (saveErr) {\n capturePluginError(saveErr instanceof Error ? saveErr : new Error(String(saveErr)), {\n subsystem: \"cli\",\n operation: \"adaptive-maintenance-llm-save\",\n });\n }\n }\n opts.logger.info?.(`${opts.label}: recorded adaptive ${kind} failure for ${failureModel}`);\n }\n throw error;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"adaptive-maintenance-llm.js","names":[],"sources":["../../services/adaptive-maintenance-llm.ts"],"sourcesContent":["import type OpenAI from \"openai\";\nimport { getEnv } from \"../utils/env-manager.js\";\nimport { estimateTokens } from \"../utils/text.js\";\nimport {\n type AdaptiveFailureKind,\n type AdaptiveModelLimitsStateV1,\n ADAPTIVE_MODEL_LIMITS_VERSION,\n getEffectiveModelLimits,\n loadAdaptiveModelLimits,\n recordAdaptiveFailure,\n recordAdaptiveSuccess,\n saveAdaptiveModelLimits,\n} from \"./adaptive-model-limits.js\";\nimport {\n chatCompleteWithRetryDetailed,\n distillBatchTokenLimit,\n distillMaxOutputTokens,\n is403QuotaOrRateLimitLike,\n is429OrWrapped,\n isContextLengthError,\n type ChatCompleteWithRetryDetails,\n} from \"./chat.js\";\nimport type { CostFeatureId } from \"./cost-feature-labels.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\n\nexport type MaintenanceLlmLogger = {\n info?: (msg: string) => void;\n warn?: (msg: string) => void;\n};\n\nexport type AdaptiveMaintenanceLlmOptions = {\n model: string;\n modelSource?: string;\n fallbackModels?: string[];\n content: string;\n temperature: number;\n maxTokens: number;\n openai: OpenAI;\n label: string;\n feature: CostFeatureId;\n logger: MaintenanceLlmLogger;\n adaptiveStatePath?: string;\n enabled?: boolean;\n};\n\nfunction emptyState(): AdaptiveModelLimitsStateV1 {\n return { version: ADAPTIVE_MODEL_LIMITS_VERSION, models: {} };\n}\n\nfunction classifyAdaptiveFailure(err: Error): AdaptiveFailureKind {\n if (isContextLengthError(err)) return \"context_length\";\n if (is429OrWrapped(err)) return \"rate_limit\";\n if (is403QuotaOrRateLimitLike(err)) return \"quota\";\n if (/timed out|llm request timeout|request was aborted|Request was aborted/i.test(err.message)) return \"timeout\";\n return \"other\";\n}\n\nfunction compatibleFallbacks(\n content: string,\n fallbackModels: string[],\n logger: MaintenanceLlmLogger,\n label: string,\n): string[] {\n const inputTokens = estimateTokens(content);\n const kept: string[] = [];\n const skipped: string[] = [];\n for (const fb of fallbackModels) {\n if (inputTokens <= distillBatchTokenLimit(fb)) kept.push(fb);\n else skipped.push(fb);\n }\n if (skipped.length > 0) {\n logger.info?.(`${label}: skipping context-incompatible fallbacks: ${skipped.join(\", \")}`);\n }\n return kept;\n}\n\nexport async function chatCompleteWithAdaptiveMaintenanceRetry(\n opts: AdaptiveMaintenanceLlmOptions,\n): Promise<ChatCompleteWithRetryDetails> {\n const envAdaptiveOn = (getEnv(\"OPENCLAW_HYBRID_MEM_ADAPTIVE_DISTILL\") ?? \"\").trim() !== \"0\";\n const enabled = opts.enabled !== undefined ? opts.enabled : envAdaptiveOn;\n const envAdaptiveState = (getEnv(\"OPENCLAW_HYBRID_MEM_ADAPTIVE_DISTILL_STATE\") ?? \"\").trim();\n const adaptiveStatePath = envAdaptiveState || opts.adaptiveStatePath;\n const state = enabled && adaptiveStatePath ? loadAdaptiveModelLimits(adaptiveStatePath) : emptyState();\n const catalogBatchTokenLimit = distillBatchTokenLimit(opts.model);\n const catalogMaxOutputTokens = distillMaxOutputTokens(opts.model);\n const inputTokens = estimateTokens(opts.content);\n const effective = enabled\n ? getEffectiveModelLimits({\n state,\n model: opts.model,\n catalogBatchTokenLimit,\n catalogMaxOutputTokens,\n minBatchTokenLimit: Math.max(1024, inputTokens + 128),\n minMaxOutputTokens: Math.min(128, opts.maxTokens),\n })\n : {\n batchTokenLimit: catalogBatchTokenLimit,\n maxOutputTokens: catalogMaxOutputTokens,\n source: \"catalog\" as const,\n };\n // Completion budget: honor caller (e.g. self-correction TOOLS rewrite) up to catalog cap only.\n // Do not apply adaptive shrunk maxOutputTokens — shared state would truncate long outputs after unrelated failures.\n const maxTokens = Math.max(128, Math.min(opts.maxTokens, catalogMaxOutputTokens));\n const fallbackModels = compatibleFallbacks(opts.content, opts.fallbackModels ?? [], opts.logger, opts.label);\n\n opts.logger.info?.(\n `${opts.label}: starting with model ${opts.model} (source=${opts.modelSource ?? \"configured\"}; adaptive=${enabled ? effective.source : \"disabled\"}; inputTokens≈${inputTokens}; maxTokens=${maxTokens})`,\n );\n opts.logger.info?.(`${opts.label}: fallback chain = [${fallbackModels.length > 0 ? fallbackModels.join(\", \") : \"\"}]`);\n\n try {\n const detail = await chatCompleteWithRetryDetailed({\n model: opts.model,\n content: opts.content,\n temperature: opts.temperature,\n maxTokens,\n openai: opts.openai,\n fallbackModels,\n label: opts.label,\n feature: opts.feature,\n });\n if (enabled) {\n const usedModel = detail.modelUsed;\n const usedCatalogBatch = distillBatchTokenLimit(usedModel);\n const usedCatalogOutput = distillMaxOutputTokens(usedModel);\n recordAdaptiveSuccess({\n state,\n model: usedModel,\n catalogBatchTokenLimit: usedCatalogBatch,\n catalogMaxOutputTokens: usedCatalogOutput,\n usedBatchTokenLimit: Math.min(effective.batchTokenLimit, usedCatalogBatch),\n usedMaxOutputTokens: Math.min(maxTokens, usedCatalogOutput),\n });\n if (adaptiveStatePath) {\n try {\n saveAdaptiveModelLimits(adaptiveStatePath, state);\n } catch (saveErr) {\n capturePluginError(saveErr instanceof Error ? saveErr : new Error(String(saveErr)), {\n subsystem: \"cli\",\n operation: \"adaptive-maintenance-llm-save\",\n });\n }\n }\n }\n if (detail.modelUsed !== opts.model) {\n opts.logger.info?.(`${opts.label}: succeeded with fallback model ${detail.modelUsed}`);\n }\n return detail;\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (enabled) {\n const kind = classifyAdaptiveFailure(error);\n const failureModel =\n typeof (error as Error & { lastAttemptedModel?: string }).lastAttemptedModel === \"string\"\n ? (error as Error & { lastAttemptedModel: string }).lastAttemptedModel\n : opts.model;\n const failureCatalogBatch = distillBatchTokenLimit(failureModel);\n const failureCatalogMaxOut = distillMaxOutputTokens(failureModel);\n const failureEffective = getEffectiveModelLimits({\n state,\n model: failureModel,\n catalogBatchTokenLimit: failureCatalogBatch,\n catalogMaxOutputTokens: failureCatalogMaxOut,\n minBatchTokenLimit: Math.max(1024, inputTokens + 128),\n minMaxOutputTokens: Math.min(128, opts.maxTokens),\n });\n recordAdaptiveFailure({\n state,\n model: failureModel,\n kind,\n catalogBatchTokenLimit: failureCatalogBatch,\n catalogMaxOutputTokens: failureCatalogMaxOut,\n usedBatchTokenLimit: failureEffective.batchTokenLimit,\n usedMaxOutputTokens: Math.min(maxTokens, failureCatalogMaxOut),\n });\n if (adaptiveStatePath) {\n try {\n saveAdaptiveModelLimits(adaptiveStatePath, state);\n } catch (saveErr) {\n capturePluginError(saveErr instanceof Error ? saveErr : new Error(String(saveErr)), {\n subsystem: \"cli\",\n operation: \"adaptive-maintenance-llm-save\",\n });\n }\n }\n opts.logger.info?.(`${opts.label}: recorded adaptive ${kind} failure for ${failureModel}`);\n }\n throw error;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adaptive-model-limits.js","names":[],"sources":["../../services/adaptive-model-limits.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nexport type AdaptiveFailureKind = \"timeout\" | \"rate_limit\" | \"quota\" | \"context_length\" | \"other\";\n\nexport type AdaptiveModelLimitEntryV1 = {\n safeBatchTokenLimit: number;\n safeMaxOutputTokens: number;\n updatedAtMs: number;\n lastSuccessAtMs?: number;\n lastFailureAtMs?: number;\n lastFailureKind?: AdaptiveFailureKind;\n consecutiveFailures?: number;\n};\n\nexport type AdaptiveModelLimitsStateV1 = {\n version: 1;\n models: Record<string, AdaptiveModelLimitEntryV1>;\n};\n\nexport const ADAPTIVE_MODEL_LIMITS_VERSION = 1 as const;\n\nfunction clampInt(n: number, min: number, max: number): number {\n if (!Number.isFinite(n)) return min;\n return Math.max(min, Math.min(max, Math.floor(n)));\n}\n\nfunction defaultState(): AdaptiveModelLimitsStateV1 {\n return { version: ADAPTIVE_MODEL_LIMITS_VERSION, models: {} };\n}\n\nexport function loadAdaptiveModelLimits(filePath: string): AdaptiveModelLimitsStateV1 {\n try {\n const raw = readFileSync(filePath, \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<AdaptiveModelLimitsStateV1> | null;\n if (\n !parsed ||\n parsed.version !== ADAPTIVE_MODEL_LIMITS_VERSION ||\n !parsed.models ||\n typeof parsed.models !== \"object\"\n )\n return defaultState();\n return {\n version: ADAPTIVE_MODEL_LIMITS_VERSION,\n models: parsed.models as Record<string, AdaptiveModelLimitEntryV1>,\n };\n } catch {\n return defaultState();\n }\n}\n\nexport function saveAdaptiveModelLimits(filePath: string, state: AdaptiveModelLimitsStateV1): void {\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\\n`, \"utf-8\");\n}\n\nexport type EffectiveModelLimits = {\n batchTokenLimit: number;\n maxOutputTokens: number;\n source: \"catalog\" | \"adaptive\";\n};\n\nexport function getEffectiveModelLimits(opts: {\n state: AdaptiveModelLimitsStateV1;\n model: string;\n catalogBatchTokenLimit: number;\n catalogMaxOutputTokens: number;\n minBatchTokenLimit?: number;\n minMaxOutputTokens?: number;\n}): EffectiveModelLimits {\n const { state, model, catalogBatchTokenLimit, catalogMaxOutputTokens } = opts;\n const minBatch = opts.minBatchTokenLimit ?? 1024;\n const minOut = opts.minMaxOutputTokens ?? 128;\n const entry = state.models[model];\n if (!entry) {\n // Start at 50% of catalog limits to reduce initial failure rate (cost optimization)\n const conservativeBatch = Math.floor(catalogBatchTokenLimit * 0.5);\n const conservativeOut = Math.floor(catalogMaxOutputTokens * 0.5);\n return {\n batchTokenLimit: clampInt(conservativeBatch, minBatch, catalogBatchTokenLimit),\n maxOutputTokens: clampInt(conservativeOut, minOut, catalogMaxOutputTokens),\n source: \"catalog\",\n };\n }\n return {\n batchTokenLimit: clampInt(entry.safeBatchTokenLimit, minBatch, catalogBatchTokenLimit),\n maxOutputTokens: clampInt(entry.safeMaxOutputTokens, minOut, catalogMaxOutputTokens),\n source: \"adaptive\",\n };\n}\n\nexport function recordAdaptiveSuccess(opts: {\n state: AdaptiveModelLimitsStateV1;\n model: string;\n catalogBatchTokenLimit: number;\n catalogMaxOutputTokens: number;\n usedBatchTokenLimit: number;\n usedMaxOutputTokens: number;\n /** How fast to grow on success (default 1.05 = +5%). */\n growFactor?: number;\n}): void {\n const {\n state,\n model,\n catalogBatchTokenLimit,\n catalogMaxOutputTokens,\n usedBatchTokenLimit,\n usedMaxOutputTokens,\n growFactor = 1.05,\n } = opts;\n const now = Date.now();\n const cur = state.models[model];\n const nextBatch = clampInt(Math.ceil(usedBatchTokenLimit * growFactor), 1024, catalogBatchTokenLimit);\n const nextOut = clampInt(Math.ceil(usedMaxOutputTokens * growFactor), 128, catalogMaxOutputTokens);\n state.models[model] = {\n safeBatchTokenLimit: nextBatch,\n safeMaxOutputTokens: nextOut,\n updatedAtMs: now,\n lastSuccessAtMs: now,\n consecutiveFailures: 0,\n ...(cur?.lastFailureAtMs != null ? { lastFailureAtMs: cur.lastFailureAtMs } : {}),\n ...(cur?.lastFailureKind != null ? { lastFailureKind: cur.lastFailureKind } : {}),\n };\n}\n\nfunction shrinkFactor(kind: AdaptiveFailureKind): number {\n if (kind === \"context_length\") return 0.6;\n if (kind === \"timeout\") return 0.75;\n if (kind === \"rate_limit\" || kind === \"quota\") return 0.85;\n return 0.8;\n}\n\n/** Matches {@link recordAdaptiveFailure} so non-adaptive distill retries shrink batches the same way. */\nexport function adaptiveFailureShrinkRatios(kind: AdaptiveFailureKind): { batch: number; out: number } {\n const batch = shrinkFactor(kind);\n const out = kind === \"timeout\" || kind === \"context_length\" ? Math.min(0.9, batch) : 0.95;\n return { batch, out };\n}\n\nexport function recordAdaptiveFailure(opts: {\n state: AdaptiveModelLimitsStateV1;\n model: string;\n kind: AdaptiveFailureKind;\n catalogBatchTokenLimit: number;\n catalogMaxOutputTokens: number;\n usedBatchTokenLimit: number;\n usedMaxOutputTokens: number;\n}): void {\n const {\n state,\n model,\n kind,\n catalogBatchTokenLimit,\n catalogMaxOutputTokens,\n usedBatchTokenLimit,\n usedMaxOutputTokens,\n } = opts;\n const now = Date.now();\n const cur = state.models[model];\n const factor = shrinkFactor(kind);\n const nextBatch = clampInt(Math.floor(usedBatchTokenLimit * factor), 1024, catalogBatchTokenLimit);\n // Output limits: only shrink meaningfully on timeouts/context-length; keep modest adjustments otherwise.\n const outFactor = kind === \"timeout\" || kind === \"context_length\" ? Math.min(0.9, factor) : 0.95;\n const nextOut = clampInt(Math.floor(usedMaxOutputTokens * outFactor), 128, catalogMaxOutputTokens);\n const consecutiveFailures = (cur?.consecutiveFailures ?? 0) + 1;\n state.models[model] = {\n safeBatchTokenLimit: nextBatch,\n safeMaxOutputTokens: nextOut,\n updatedAtMs: now,\n lastFailureAtMs: now,\n lastFailureKind: kind,\n consecutiveFailures,\n ...(cur?.lastSuccessAtMs != null ? { lastSuccessAtMs: cur.lastSuccessAtMs } : {}),\n };\n}\n"],"mappings":";;AAsBA,SAAS,SAAS,GAAW,KAAa,KAAqB;CAC7D,IAAI,CAAC,OAAO,SAAS,
|
|
1
|
+
{"version":3,"file":"adaptive-model-limits.js","names":[],"sources":["../../services/adaptive-model-limits.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nexport type AdaptiveFailureKind = \"timeout\" | \"rate_limit\" | \"quota\" | \"context_length\" | \"other\";\n\nexport type AdaptiveModelLimitEntryV1 = {\n safeBatchTokenLimit: number;\n safeMaxOutputTokens: number;\n updatedAtMs: number;\n lastSuccessAtMs?: number;\n lastFailureAtMs?: number;\n lastFailureKind?: AdaptiveFailureKind;\n consecutiveFailures?: number;\n};\n\nexport type AdaptiveModelLimitsStateV1 = {\n version: 1;\n models: Record<string, AdaptiveModelLimitEntryV1>;\n};\n\nexport const ADAPTIVE_MODEL_LIMITS_VERSION = 1 as const;\n\nfunction clampInt(n: number, min: number, max: number): number {\n if (!Number.isFinite(n)) return min;\n return Math.max(min, Math.min(max, Math.floor(n)));\n}\n\nfunction defaultState(): AdaptiveModelLimitsStateV1 {\n return { version: ADAPTIVE_MODEL_LIMITS_VERSION, models: {} };\n}\n\nexport function loadAdaptiveModelLimits(filePath: string): AdaptiveModelLimitsStateV1 {\n try {\n const raw = readFileSync(filePath, \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<AdaptiveModelLimitsStateV1> | null;\n if (\n !parsed ||\n parsed.version !== ADAPTIVE_MODEL_LIMITS_VERSION ||\n !parsed.models ||\n typeof parsed.models !== \"object\"\n )\n return defaultState();\n return {\n version: ADAPTIVE_MODEL_LIMITS_VERSION,\n models: parsed.models as Record<string, AdaptiveModelLimitEntryV1>,\n };\n } catch {\n return defaultState();\n }\n}\n\nexport function saveAdaptiveModelLimits(filePath: string, state: AdaptiveModelLimitsStateV1): void {\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\\n`, \"utf-8\");\n}\n\nexport type EffectiveModelLimits = {\n batchTokenLimit: number;\n maxOutputTokens: number;\n source: \"catalog\" | \"adaptive\";\n};\n\nexport function getEffectiveModelLimits(opts: {\n state: AdaptiveModelLimitsStateV1;\n model: string;\n catalogBatchTokenLimit: number;\n catalogMaxOutputTokens: number;\n minBatchTokenLimit?: number;\n minMaxOutputTokens?: number;\n}): EffectiveModelLimits {\n const { state, model, catalogBatchTokenLimit, catalogMaxOutputTokens } = opts;\n const minBatch = opts.minBatchTokenLimit ?? 1024;\n const minOut = opts.minMaxOutputTokens ?? 128;\n const entry = state.models[model];\n if (!entry) {\n // Start at 50% of catalog limits to reduce initial failure rate (cost optimization)\n const conservativeBatch = Math.floor(catalogBatchTokenLimit * 0.5);\n const conservativeOut = Math.floor(catalogMaxOutputTokens * 0.5);\n return {\n batchTokenLimit: clampInt(conservativeBatch, minBatch, catalogBatchTokenLimit),\n maxOutputTokens: clampInt(conservativeOut, minOut, catalogMaxOutputTokens),\n source: \"catalog\",\n };\n }\n return {\n batchTokenLimit: clampInt(entry.safeBatchTokenLimit, minBatch, catalogBatchTokenLimit),\n maxOutputTokens: clampInt(entry.safeMaxOutputTokens, minOut, catalogMaxOutputTokens),\n source: \"adaptive\",\n };\n}\n\nexport function recordAdaptiveSuccess(opts: {\n state: AdaptiveModelLimitsStateV1;\n model: string;\n catalogBatchTokenLimit: number;\n catalogMaxOutputTokens: number;\n usedBatchTokenLimit: number;\n usedMaxOutputTokens: number;\n /** How fast to grow on success (default 1.05 = +5%). */\n growFactor?: number;\n}): void {\n const {\n state,\n model,\n catalogBatchTokenLimit,\n catalogMaxOutputTokens,\n usedBatchTokenLimit,\n usedMaxOutputTokens,\n growFactor = 1.05,\n } = opts;\n const now = Date.now();\n const cur = state.models[model];\n const nextBatch = clampInt(Math.ceil(usedBatchTokenLimit * growFactor), 1024, catalogBatchTokenLimit);\n const nextOut = clampInt(Math.ceil(usedMaxOutputTokens * growFactor), 128, catalogMaxOutputTokens);\n state.models[model] = {\n safeBatchTokenLimit: nextBatch,\n safeMaxOutputTokens: nextOut,\n updatedAtMs: now,\n lastSuccessAtMs: now,\n consecutiveFailures: 0,\n ...(cur?.lastFailureAtMs != null ? { lastFailureAtMs: cur.lastFailureAtMs } : {}),\n ...(cur?.lastFailureKind != null ? { lastFailureKind: cur.lastFailureKind } : {}),\n };\n}\n\nfunction shrinkFactor(kind: AdaptiveFailureKind): number {\n if (kind === \"context_length\") return 0.6;\n if (kind === \"timeout\") return 0.75;\n if (kind === \"rate_limit\" || kind === \"quota\") return 0.85;\n return 0.8;\n}\n\n/** Matches {@link recordAdaptiveFailure} so non-adaptive distill retries shrink batches the same way. */\nexport function adaptiveFailureShrinkRatios(kind: AdaptiveFailureKind): { batch: number; out: number } {\n const batch = shrinkFactor(kind);\n const out = kind === \"timeout\" || kind === \"context_length\" ? Math.min(0.9, batch) : 0.95;\n return { batch, out };\n}\n\nexport function recordAdaptiveFailure(opts: {\n state: AdaptiveModelLimitsStateV1;\n model: string;\n kind: AdaptiveFailureKind;\n catalogBatchTokenLimit: number;\n catalogMaxOutputTokens: number;\n usedBatchTokenLimit: number;\n usedMaxOutputTokens: number;\n}): void {\n const {\n state,\n model,\n kind,\n catalogBatchTokenLimit,\n catalogMaxOutputTokens,\n usedBatchTokenLimit,\n usedMaxOutputTokens,\n } = opts;\n const now = Date.now();\n const cur = state.models[model];\n const factor = shrinkFactor(kind);\n const nextBatch = clampInt(Math.floor(usedBatchTokenLimit * factor), 1024, catalogBatchTokenLimit);\n // Output limits: only shrink meaningfully on timeouts/context-length; keep modest adjustments otherwise.\n const outFactor = kind === \"timeout\" || kind === \"context_length\" ? Math.min(0.9, factor) : 0.95;\n const nextOut = clampInt(Math.floor(usedMaxOutputTokens * outFactor), 128, catalogMaxOutputTokens);\n const consecutiveFailures = (cur?.consecutiveFailures ?? 0) + 1;\n state.models[model] = {\n safeBatchTokenLimit: nextBatch,\n safeMaxOutputTokens: nextOut,\n updatedAtMs: now,\n lastFailureAtMs: now,\n lastFailureKind: kind,\n consecutiveFailures,\n ...(cur?.lastSuccessAtMs != null ? { lastSuccessAtMs: cur.lastSuccessAtMs } : {}),\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ambient-retrieval.js","names":[],"sources":["../../services/ambient-retrieval.ts"],"sourcesContent":["/**\n * Ambient Retrieval Engine — Issue #156.\n *\n * Generates multiple implicit queries from conversation context to improve\n * ambient (auto-recall) memory injection quality.\n *\n * Query types:\n * 1. \"message\" — Direct semantic match from the incoming message (current behaviour).\n * 2. \"entity\" — Entity-neighbourhood lookups for entities mentioned in the message.\n * 3. \"temporal\" — Time-aware queries (\"action items from last 48h\", \"upcoming deadlines\").\n * 4. \"context\" — User/channel context queries (\"recent topics with [user]\").\n */\n\nimport type { IssueStore } from \"../backends/issue-store.js\";\nimport type { AmbientConfig } from \"../config.js\";\nimport type { Issue } from \"../types/issue-types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type AmbientQueryType = \"message\" | \"entity\" | \"temporal\" | \"context\";\n\nexport interface AmbientQuery {\n /** The query text to run against the retrieval pipeline. */\n text: string;\n /** What generated this query. */\n type: AmbientQueryType;\n /** If type=entity, which entity triggered this query. */\n entity?: string;\n}\n\ninterface AmbientContext {\n /** User/author identifier (e.g. userId). */\n userId?: string;\n /** Channel or conversation identifier. */\n channelId?: string;\n /** Current time in milliseconds (default: Date.now()). */\n nowMs?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Cosine similarity & topic-shift detection\n// ---------------------------------------------------------------------------\n\n/**\n * Compute cosine similarity between two equal-length numeric vectors.\n * This is the general-purpose version that handles arbitrary (non-normalized) vectors\n * by computing magnitudes and normalizing internally.\n *\n * Returns a value in [-1, 1], where 1 = identical direction, -1 = opposite.\n * Returns 0 when either vector has zero magnitude or when lengths differ.\n *\n * NOTE: For pre-normalized vectors, reflection.ts has an optimized version\n * that skips magnitude computation (just computes dot product).\n */\nexport function cosineSimilarity(a: number[], b: number[]): number {\n if (a.length === 0 || a.length !== b.length) return 0;\n let dot = 0;\n let magA = 0;\n let magB = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n magA += a[i] * a[i];\n magB += b[i] * b[i];\n }\n const denom = Math.sqrt(magA) * Math.sqrt(magB);\n if (denom === 0) return 0;\n return dot / denom;\n}\n\n/**\n * Compute cosine distance between two embeddings (1 − cosine_similarity).\n * Returns a value in [0, 2], where 0 = identical, 2 = opposite directions.\n */\nexport function cosineDistance(a: number[], b: number[]): number {\n return 1 - cosineSimilarity(a, b);\n}\n\n/**\n * Detect whether the topic has shifted significantly between two consecutive\n * message embeddings, based on a configurable cosine distance threshold.\n *\n * @param prev - Embedding of the previous message.\n * @param next - Embedding of the current message.\n * @param threshold - Cosine distance threshold (default: 0.4). Higher = less sensitive.\n * @returns true when the distance exceeds the threshold (topic shifted).\n */\nexport function detectTopicShift(prev: number[], next: number[], threshold = 0.4): boolean {\n if (prev.length === 0 || next.length === 0) return false;\n const distance = cosineDistance(prev, next);\n return distance > threshold;\n}\n\n// ---------------------------------------------------------------------------\n// Entity extraction (rule-based, cheap)\n// ---------------------------------------------------------------------------\n\n/** Common English stop-words / sentence starters to exclude from capitalised-word extraction. */\nconst COMMON_STOP_WORDS = new Set([\n \"the\",\n \"this\",\n \"that\",\n \"when\",\n \"what\",\n \"which\",\n \"where\",\n \"who\",\n \"how\",\n \"and\",\n \"but\",\n \"for\",\n \"now\",\n \"then\",\n \"are\",\n \"was\",\n \"has\",\n \"have\",\n \"had\",\n \"not\",\n \"can\",\n \"does\",\n \"did\",\n \"will\",\n \"may\",\n \"use\",\n \"get\",\n \"set\",\n \"run\",\n \"all\",\n \"any\",\n \"each\",\n \"one\",\n \"two\",\n \"new\",\n \"old\",\n \"my\",\n \"your\",\n \"our\",\n \"his\",\n \"her\",\n \"its\",\n \"they\",\n \"them\",\n \"with\",\n \"from\",\n \"into\",\n \"over\",\n \"also\",\n \"just\",\n \"more\",\n \"most\",\n \"very\",\n \"some\",\n \"such\",\n \"true\",\n \"false\",\n]);\n\nconst ENTITY_PREFIX_LEN = 3;\nconst entityCache = new Map<string, { prefixMap: Map<string, string[]>; timestamp: number }>();\nconst ENTITY_CACHE_TTL_MS = 5 * 60 * 1000;\n/** Issue #463: Maximum entity cache entries to prevent unbounded growth. */\nconst ENTITY_CACHE_MAX_SIZE = 50;\n\nfunction getEntityPrefixMap(knownEntities: string[]): Map<string, string[]> {\n const cacheKey = knownEntities.join(\"\\x00\");\n const now = Date.now();\n const cached = entityCache.get(cacheKey);\n if (cached && now - cached.timestamp < ENTITY_CACHE_TTL_MS) {\n return cached.prefixMap;\n }\n const prefixMap = new Map<string, string[]>();\n const seen = new Set<string>();\n for (const entity of knownEntities) {\n const lower = entity.toLowerCase().trim();\n if (lower.length < 2 || seen.has(lower)) continue;\n seen.add(lower);\n const firstWordMatch = lower.match(/^[a-z0-9][a-z0-9_-]*/);\n if (firstWordMatch) {\n const word = firstWordMatch[0];\n for (let len = Math.min(word.length, ENTITY_PREFIX_LEN); len >= 2; len--) {\n const prefix = word.slice(0, len);\n const list = prefixMap.get(prefix);\n if (list) list.push(lower);\n else prefixMap.set(prefix, [lower]);\n }\n } else {\n const prefix = lower.slice(0, ENTITY_PREFIX_LEN);\n const list = prefixMap.get(prefix);\n if (list) list.push(lower);\n else prefixMap.set(prefix, [lower]);\n }\n }\n entityCache.set(cacheKey, { prefixMap, timestamp: now });\n // Evict stale entries to prevent unbounded growth when entity lists change over time.\n for (const [k, v] of entityCache) {\n if (now - v.timestamp >= ENTITY_CACHE_TTL_MS) entityCache.delete(k);\n }\n // Issue #463: Hard cap on cache size to prevent unbounded growth\n if (entityCache.size > ENTITY_CACHE_MAX_SIZE) {\n const excess = entityCache.size - ENTITY_CACHE_MAX_SIZE;\n const keys = entityCache.keys();\n for (let i = 0; i < excess; i++) {\n const { value } = keys.next();\n if (value) entityCache.delete(value);\n }\n }\n return prefixMap;\n}\n\n/**\n * Extract entity candidates from a message using lightweight heuristics:\n * - Known entities (case-insensitive substring match)\n * - @mentions and #tags\n * - IPv4 addresses\n * - Capitalised words (PascalCase / ALL_CAPS, 3+ chars, not stop-words)\n * - Quoted strings\n *\n * Returns distinct lowercase entity strings (max 10).\n */\nexport function extractEntitiesFromMessage(text: string, knownEntities: string[] = []): string[] {\n const candidates = new Set<string>();\n const lower = text.toLowerCase();\n\n // 1. Known entities (case-insensitive)\n if (knownEntities.length > 0) {\n const prefixMap = getEntityPrefixMap(knownEntities);\n const seenPrefixes = new Set<string>();\n for (const m of lower.matchAll(/\\b[a-z0-9][a-z0-9_-]{1,}\\b/g)) {\n const token = m[0];\n for (let len = Math.min(token.length, ENTITY_PREFIX_LEN); len >= 2; len--) {\n seenPrefixes.add(token.slice(0, len));\n }\n }\n for (const prefix of seenPrefixes) {\n const list = prefixMap.get(prefix);\n if (!list) continue;\n for (const entity of list) {\n if (lower.includes(entity)) candidates.add(entity);\n }\n }\n }\n\n // 2. @mentions\n for (const m of text.matchAll(/@([\\w.-]+)/g)) {\n if (m[1] && m[1].length >= 2) candidates.add(m[1].toLowerCase());\n }\n\n // 3. #tags\n for (const m of text.matchAll(/#([\\w-]+)/g)) {\n if (m[1] && m[1].length >= 2) candidates.add(m[1].toLowerCase());\n }\n\n // 4. IPv4 addresses\n for (const m of text.matchAll(/\\b(\\d{1,3}(?:\\.\\d{1,3}){3})\\b/g)) {\n candidates.add(m[1]);\n }\n\n // 5. Capitalised words (PascalCase or ALL_CAPS, 3+ chars)\n for (const m of text.matchAll(/\\b([A-Z][a-zA-Z0-9_]{2,}|[A-Z]{3,})\\b/g)) {\n const word = m[1];\n if (!COMMON_STOP_WORDS.has(word.toLowerCase())) {\n candidates.add(word.toLowerCase());\n }\n }\n\n // 6. Quoted strings (single or double), 3–40 chars\n for (const m of text.matchAll(/[\"']([^\"']{3,40})[\"']/g)) {\n const val = m[1].toLowerCase().trim();\n if (val.length > 0) candidates.add(val);\n }\n\n return [...candidates].slice(0, 10);\n}\n\n// ---------------------------------------------------------------------------\n// Temporal query generation\n// ---------------------------------------------------------------------------\n\n/**\n * Generate time-aware retrieval queries based on the current hour.\n * Returns up to 2 query strings.\n */\nexport function generateTemporalQueries(nowMs: number = Date.now()): string[] {\n const hour = new Date(nowMs).getHours();\n\n if (hour >= 6 && hour < 12) {\n // Morning: today's plan and pending items\n return [\"action items and tasks for today\", \"pending decisions and upcoming deadlines\"];\n }\n if (hour >= 12 && hour < 18) {\n // Afternoon: recent progress and blockers\n return [\"action items from last 48 hours\", \"upcoming deadlines and commitments\"];\n }\n // Evening/night: unresolved issues and follow-ups\n return [\"unresolved issues and follow-ups\", \"action items from last 48 hours\"];\n}\n\n// ---------------------------------------------------------------------------\n// Session-seen facts tracking\n// ---------------------------------------------------------------------------\n\n/**\n * Tracks which fact IDs have already been injected during this session.\n * Used to prevent re-injecting the same facts after a topic shift.\n */\nexport class SessionSeenFacts {\n private readonly _seen = new Set<string>();\n\n /** Mark a list of fact IDs as seen/injected. */\n markSeen(factIds: string[]): void {\n for (const id of factIds) {\n this._seen.add(id);\n }\n }\n\n /** Return true when a fact ID has already been injected this session. */\n hasBeenSeen(factId: string): boolean {\n return this._seen.has(factId);\n }\n\n /** Filter a list of fact IDs, returning only those not yet seen. */\n filterUnseen(factIds: string[]): string[] {\n return factIds.filter((id) => !this._seen.has(id));\n }\n\n /** Number of unique fact IDs seen so far. */\n get size(): number {\n return this._seen.size;\n }\n\n /** Clear all seen state (e.g. on session end or reset). */\n clear(): void {\n this._seen.clear();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Multi-query generation\n// ---------------------------------------------------------------------------\n\n/**\n * Generate 2–4 implicit ambient retrieval queries from conversation context.\n *\n * Query types:\n * 1. message — Direct semantic match (current behaviour, always included).\n * 2. entity — One query per distinct entity detected in the message.\n * 3. temporal — Time-aware queries (action items, upcoming deadlines).\n * 4. context — User/channel context when userId or channelId is available.\n *\n * Total queries are capped by `config.maxQueriesPerTrigger` (default 4).\n * When `config.multiQuery` is false, only the direct message query is returned.\n */\nexport function generateAmbientQueries(\n message: string,\n config: Pick<AmbientConfig, \"maxQueriesPerTrigger\" | \"multiQuery\">,\n context: AmbientContext = {},\n knownEntities: string[] = [],\n): AmbientQuery[] {\n const queries: AmbientQuery[] = [];\n const trimmed = message.trim();\n\n // 1. Message-based query (always included — current behaviour)\n if (trimmed.length > 0) {\n queries.push({ text: trimmed, type: \"message\" });\n }\n\n if (!config.multiQuery) {\n return queries;\n }\n\n const max = Math.max(1, Math.min(4, config.maxQueriesPerTrigger ?? 4));\n\n // 2. Entity-based queries\n const entities = extractEntitiesFromMessage(trimmed, knownEntities);\n for (const entity of entities) {\n if (queries.length >= max) break;\n queries.push({ text: entity, type: \"entity\", entity });\n }\n\n // 3. Temporal context queries\n if (queries.length < max) {\n const nowMs = context.nowMs ?? Date.now();\n const temporal = generateTemporalQueries(nowMs);\n for (const t of temporal) {\n if (queries.length >= max) break;\n queries.push({ text: t, type: \"temporal\" });\n }\n }\n\n // 4. User/channel context query\n if (queries.length < max && (context.userId || context.channelId)) {\n const parts: string[] = [];\n if (context.userId) parts.push(`recent topics with ${context.userId}`);\n if (context.channelId) parts.push(`discussion in ${context.channelId}`);\n if (parts.length > 0) {\n queries.push({ text: parts.join(\" and \"), type: \"context\" });\n }\n }\n\n return queries.slice(0, max);\n}\n\n// ---------------------------------------------------------------------------\n// Deduplication helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Deduplicate results from multiple query runs by an arbitrary ID field.\n * Generic helper compatible with SearchResult-like objects.\n *\n * @param resultSets - Arrays of results to deduplicate.\n * @param getId - Function that extracts the unique ID from a result.\n * @returns Flat deduplicated list, first occurrence wins.\n */\nexport function deduplicateResultsById<T>(resultSets: T[][], getId: (item: T) => string): T[] {\n const seen = new Set<string>();\n const deduped: T[] = [];\n for (const results of resultSets) {\n for (const r of results) {\n const id = getId(r);\n if (!seen.has(id)) {\n seen.add(id);\n deduped.push(r);\n }\n }\n }\n return deduped;\n}\n\n// ---------------------------------------------------------------------------\n// Issue-aware ambient context (Issue #137)\n// ---------------------------------------------------------------------------\n\n/** Keywords that suggest the conversation involves an active problem or error. */\nconst ERROR_KEYWORDS = [\n \"error\",\n \"failed\",\n \"crash\",\n \"timeout\",\n \"broken\",\n \"not working\",\n \"exception\",\n \"bug\",\n \"issue\",\n \"problem\",\n \"fault\",\n \"failure\",\n \"down\",\n];\n\n/**\n * Detect whether a message text contains error-like keywords that suggest\n * the user is dealing with an active problem.\n */\nfunction hasErrorKeywords(text: string): boolean {\n const lower = text.toLowerCase();\n return ERROR_KEYWORDS.some((kw) => lower.includes(kw));\n}\n\ninterface IssueAmbientResult {\n /** Open issues that may be relevant to the current message. */\n openIssues: Issue[];\n /** Resolved/verified issues that may contain relevant resolution context. */\n resolvedIssues: Issue[];\n}\n\n/**\n * Search the issue store for issues relevant to the current conversation message.\n * Called when the message contains error-like keywords.\n *\n * - Searches open issues so the user knows about known active problems.\n * - Searches resolved/verified issues to surface past resolutions that may help.\n *\n * Returns at most 3 open + 3 resolved issues.\n */\nexport function searchAmbientIssues(message: string, issueStore: IssueStore): IssueAmbientResult {\n if (!hasErrorKeywords(message)) {\n return { openIssues: [], resolvedIssues: [] };\n }\n\n const lower = message.toLowerCase();\n const keywords: string[] = [];\n for (const kw of ERROR_KEYWORDS) {\n if (lower.includes(kw)) {\n keywords.push(kw);\n }\n }\n const words = message.match(/\\b[a-zA-Z0-9_-]{3,}\\b/g) || [];\n for (const word of words.slice(0, 5)) {\n if (!ERROR_KEYWORDS.includes(word.toLowerCase())) {\n keywords.push(word);\n }\n }\n\n const allMatches = new Map<string, Issue>();\n for (const keyword of keywords.slice(0, 3)) {\n const matches = issueStore.search(keyword);\n for (const match of matches) {\n allMatches.set(match.id, match);\n }\n }\n\n const matchList = Array.from(allMatches.values());\n const openIssues = matchList\n .filter((i) => i.status === \"open\" || i.status === \"diagnosed\" || i.status === \"fix-attempted\")\n .slice(0, 3);\n\n const resolvedIssues = matchList.filter((i) => i.status === \"resolved\" || i.status === \"verified\").slice(0, 3);\n\n return { openIssues, resolvedIssues };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ambient-retrieval.js","names":[],"sources":["../../services/ambient-retrieval.ts"],"sourcesContent":["/**\n * Ambient Retrieval Engine — Issue #156.\n *\n * Generates multiple implicit queries from conversation context to improve\n * ambient (auto-recall) memory injection quality.\n *\n * Query types:\n * 1. \"message\" — Direct semantic match from the incoming message (current behaviour).\n * 2. \"entity\" — Entity-neighbourhood lookups for entities mentioned in the message.\n * 3. \"temporal\" — Time-aware queries (\"action items from last 48h\", \"upcoming deadlines\").\n * 4. \"context\" — User/channel context queries (\"recent topics with [user]\").\n */\n\nimport type { IssueStore } from \"../backends/issue-store.js\";\nimport type { AmbientConfig } from \"../config.js\";\nimport type { Issue } from \"../types/issue-types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type AmbientQueryType = \"message\" | \"entity\" | \"temporal\" | \"context\";\n\nexport interface AmbientQuery {\n /** The query text to run against the retrieval pipeline. */\n text: string;\n /** What generated this query. */\n type: AmbientQueryType;\n /** If type=entity, which entity triggered this query. */\n entity?: string;\n}\n\ninterface AmbientContext {\n /** User/author identifier (e.g. userId). */\n userId?: string;\n /** Channel or conversation identifier. */\n channelId?: string;\n /** Current time in milliseconds (default: Date.now()). */\n nowMs?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Cosine similarity & topic-shift detection\n// ---------------------------------------------------------------------------\n\n/**\n * Compute cosine similarity between two equal-length numeric vectors.\n * This is the general-purpose version that handles arbitrary (non-normalized) vectors\n * by computing magnitudes and normalizing internally.\n *\n * Returns a value in [-1, 1], where 1 = identical direction, -1 = opposite.\n * Returns 0 when either vector has zero magnitude or when lengths differ.\n *\n * NOTE: For pre-normalized vectors, reflection.ts has an optimized version\n * that skips magnitude computation (just computes dot product).\n */\nexport function cosineSimilarity(a: number[], b: number[]): number {\n if (a.length === 0 || a.length !== b.length) return 0;\n let dot = 0;\n let magA = 0;\n let magB = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n magA += a[i] * a[i];\n magB += b[i] * b[i];\n }\n const denom = Math.sqrt(magA) * Math.sqrt(magB);\n if (denom === 0) return 0;\n return dot / denom;\n}\n\n/**\n * Compute cosine distance between two embeddings (1 − cosine_similarity).\n * Returns a value in [0, 2], where 0 = identical, 2 = opposite directions.\n */\nexport function cosineDistance(a: number[], b: number[]): number {\n return 1 - cosineSimilarity(a, b);\n}\n\n/**\n * Detect whether the topic has shifted significantly between two consecutive\n * message embeddings, based on a configurable cosine distance threshold.\n *\n * @param prev - Embedding of the previous message.\n * @param next - Embedding of the current message.\n * @param threshold - Cosine distance threshold (default: 0.4). Higher = less sensitive.\n * @returns true when the distance exceeds the threshold (topic shifted).\n */\nexport function detectTopicShift(prev: number[], next: number[], threshold = 0.4): boolean {\n if (prev.length === 0 || next.length === 0) return false;\n const distance = cosineDistance(prev, next);\n return distance > threshold;\n}\n\n// ---------------------------------------------------------------------------\n// Entity extraction (rule-based, cheap)\n// ---------------------------------------------------------------------------\n\n/** Common English stop-words / sentence starters to exclude from capitalised-word extraction. */\nconst COMMON_STOP_WORDS = new Set([\n \"the\",\n \"this\",\n \"that\",\n \"when\",\n \"what\",\n \"which\",\n \"where\",\n \"who\",\n \"how\",\n \"and\",\n \"but\",\n \"for\",\n \"now\",\n \"then\",\n \"are\",\n \"was\",\n \"has\",\n \"have\",\n \"had\",\n \"not\",\n \"can\",\n \"does\",\n \"did\",\n \"will\",\n \"may\",\n \"use\",\n \"get\",\n \"set\",\n \"run\",\n \"all\",\n \"any\",\n \"each\",\n \"one\",\n \"two\",\n \"new\",\n \"old\",\n \"my\",\n \"your\",\n \"our\",\n \"his\",\n \"her\",\n \"its\",\n \"they\",\n \"them\",\n \"with\",\n \"from\",\n \"into\",\n \"over\",\n \"also\",\n \"just\",\n \"more\",\n \"most\",\n \"very\",\n \"some\",\n \"such\",\n \"true\",\n \"false\",\n]);\n\nconst ENTITY_PREFIX_LEN = 3;\nconst entityCache = new Map<string, { prefixMap: Map<string, string[]>; timestamp: number }>();\nconst ENTITY_CACHE_TTL_MS = 5 * 60 * 1000;\n/** Issue #463: Maximum entity cache entries to prevent unbounded growth. */\nconst ENTITY_CACHE_MAX_SIZE = 50;\n\nfunction getEntityPrefixMap(knownEntities: string[]): Map<string, string[]> {\n const cacheKey = knownEntities.join(\"\\x00\");\n const now = Date.now();\n const cached = entityCache.get(cacheKey);\n if (cached && now - cached.timestamp < ENTITY_CACHE_TTL_MS) {\n return cached.prefixMap;\n }\n const prefixMap = new Map<string, string[]>();\n const seen = new Set<string>();\n for (const entity of knownEntities) {\n const lower = entity.toLowerCase().trim();\n if (lower.length < 2 || seen.has(lower)) continue;\n seen.add(lower);\n const firstWordMatch = lower.match(/^[a-z0-9][a-z0-9_-]*/);\n if (firstWordMatch) {\n const word = firstWordMatch[0];\n for (let len = Math.min(word.length, ENTITY_PREFIX_LEN); len >= 2; len--) {\n const prefix = word.slice(0, len);\n const list = prefixMap.get(prefix);\n if (list) list.push(lower);\n else prefixMap.set(prefix, [lower]);\n }\n } else {\n const prefix = lower.slice(0, ENTITY_PREFIX_LEN);\n const list = prefixMap.get(prefix);\n if (list) list.push(lower);\n else prefixMap.set(prefix, [lower]);\n }\n }\n entityCache.set(cacheKey, { prefixMap, timestamp: now });\n // Evict stale entries to prevent unbounded growth when entity lists change over time.\n for (const [k, v] of entityCache) {\n if (now - v.timestamp >= ENTITY_CACHE_TTL_MS) entityCache.delete(k);\n }\n // Issue #463: Hard cap on cache size to prevent unbounded growth\n if (entityCache.size > ENTITY_CACHE_MAX_SIZE) {\n const excess = entityCache.size - ENTITY_CACHE_MAX_SIZE;\n const keys = entityCache.keys();\n for (let i = 0; i < excess; i++) {\n const { value } = keys.next();\n if (value) entityCache.delete(value);\n }\n }\n return prefixMap;\n}\n\n/**\n * Extract entity candidates from a message using lightweight heuristics:\n * - Known entities (case-insensitive substring match)\n * - @mentions and #tags\n * - IPv4 addresses\n * - Capitalised words (PascalCase / ALL_CAPS, 3+ chars, not stop-words)\n * - Quoted strings\n *\n * Returns distinct lowercase entity strings (max 10).\n */\nexport function extractEntitiesFromMessage(text: string, knownEntities: string[] = []): string[] {\n const candidates = new Set<string>();\n const lower = text.toLowerCase();\n\n // 1. Known entities (case-insensitive)\n if (knownEntities.length > 0) {\n const prefixMap = getEntityPrefixMap(knownEntities);\n const seenPrefixes = new Set<string>();\n for (const m of lower.matchAll(/\\b[a-z0-9][a-z0-9_-]{1,}\\b/g)) {\n const token = m[0];\n for (let len = Math.min(token.length, ENTITY_PREFIX_LEN); len >= 2; len--) {\n seenPrefixes.add(token.slice(0, len));\n }\n }\n for (const prefix of seenPrefixes) {\n const list = prefixMap.get(prefix);\n if (!list) continue;\n for (const entity of list) {\n if (lower.includes(entity)) candidates.add(entity);\n }\n }\n }\n\n // 2. @mentions\n for (const m of text.matchAll(/@([\\w.-]+)/g)) {\n if (m[1] && m[1].length >= 2) candidates.add(m[1].toLowerCase());\n }\n\n // 3. #tags\n for (const m of text.matchAll(/#([\\w-]+)/g)) {\n if (m[1] && m[1].length >= 2) candidates.add(m[1].toLowerCase());\n }\n\n // 4. IPv4 addresses\n for (const m of text.matchAll(/\\b(\\d{1,3}(?:\\.\\d{1,3}){3})\\b/g)) {\n candidates.add(m[1]);\n }\n\n // 5. Capitalised words (PascalCase or ALL_CAPS, 3+ chars)\n for (const m of text.matchAll(/\\b([A-Z][a-zA-Z0-9_]{2,}|[A-Z]{3,})\\b/g)) {\n const word = m[1];\n if (!COMMON_STOP_WORDS.has(word.toLowerCase())) {\n candidates.add(word.toLowerCase());\n }\n }\n\n // 6. Quoted strings (single or double), 3–40 chars\n for (const m of text.matchAll(/[\"']([^\"']{3,40})[\"']/g)) {\n const val = m[1].toLowerCase().trim();\n if (val.length > 0) candidates.add(val);\n }\n\n return [...candidates].slice(0, 10);\n}\n\n// ---------------------------------------------------------------------------\n// Temporal query generation\n// ---------------------------------------------------------------------------\n\n/**\n * Generate time-aware retrieval queries based on the current hour.\n * Returns up to 2 query strings.\n */\nexport function generateTemporalQueries(nowMs: number = Date.now()): string[] {\n const hour = new Date(nowMs).getHours();\n\n if (hour >= 6 && hour < 12) {\n // Morning: today's plan and pending items\n return [\"action items and tasks for today\", \"pending decisions and upcoming deadlines\"];\n }\n if (hour >= 12 && hour < 18) {\n // Afternoon: recent progress and blockers\n return [\"action items from last 48 hours\", \"upcoming deadlines and commitments\"];\n }\n // Evening/night: unresolved issues and follow-ups\n return [\"unresolved issues and follow-ups\", \"action items from last 48 hours\"];\n}\n\n// ---------------------------------------------------------------------------\n// Session-seen facts tracking\n// ---------------------------------------------------------------------------\n\n/**\n * Tracks which fact IDs have already been injected during this session.\n * Used to prevent re-injecting the same facts after a topic shift.\n */\nexport class SessionSeenFacts {\n private readonly _seen = new Set<string>();\n\n /** Mark a list of fact IDs as seen/injected. */\n markSeen(factIds: string[]): void {\n for (const id of factIds) {\n this._seen.add(id);\n }\n }\n\n /** Return true when a fact ID has already been injected this session. */\n hasBeenSeen(factId: string): boolean {\n return this._seen.has(factId);\n }\n\n /** Filter a list of fact IDs, returning only those not yet seen. */\n filterUnseen(factIds: string[]): string[] {\n return factIds.filter((id) => !this._seen.has(id));\n }\n\n /** Number of unique fact IDs seen so far. */\n get size(): number {\n return this._seen.size;\n }\n\n /** Clear all seen state (e.g. on session end or reset). */\n clear(): void {\n this._seen.clear();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Multi-query generation\n// ---------------------------------------------------------------------------\n\n/**\n * Generate 2–4 implicit ambient retrieval queries from conversation context.\n *\n * Query types:\n * 1. message — Direct semantic match (current behaviour, always included).\n * 2. entity — One query per distinct entity detected in the message.\n * 3. temporal — Time-aware queries (action items, upcoming deadlines).\n * 4. context — User/channel context when userId or channelId is available.\n *\n * Total queries are capped by `config.maxQueriesPerTrigger` (default 4).\n * When `config.multiQuery` is false, only the direct message query is returned.\n */\nexport function generateAmbientQueries(\n message: string,\n config: Pick<AmbientConfig, \"maxQueriesPerTrigger\" | \"multiQuery\">,\n context: AmbientContext = {},\n knownEntities: string[] = [],\n): AmbientQuery[] {\n const queries: AmbientQuery[] = [];\n const trimmed = message.trim();\n\n // 1. Message-based query (always included — current behaviour)\n if (trimmed.length > 0) {\n queries.push({ text: trimmed, type: \"message\" });\n }\n\n if (!config.multiQuery) {\n return queries;\n }\n\n const max = Math.max(1, Math.min(4, config.maxQueriesPerTrigger ?? 4));\n\n // 2. Entity-based queries\n const entities = extractEntitiesFromMessage(trimmed, knownEntities);\n for (const entity of entities) {\n if (queries.length >= max) break;\n queries.push({ text: entity, type: \"entity\", entity });\n }\n\n // 3. Temporal context queries\n if (queries.length < max) {\n const nowMs = context.nowMs ?? Date.now();\n const temporal = generateTemporalQueries(nowMs);\n for (const t of temporal) {\n if (queries.length >= max) break;\n queries.push({ text: t, type: \"temporal\" });\n }\n }\n\n // 4. User/channel context query\n if (queries.length < max && (context.userId || context.channelId)) {\n const parts: string[] = [];\n if (context.userId) parts.push(`recent topics with ${context.userId}`);\n if (context.channelId) parts.push(`discussion in ${context.channelId}`);\n if (parts.length > 0) {\n queries.push({ text: parts.join(\" and \"), type: \"context\" });\n }\n }\n\n return queries.slice(0, max);\n}\n\n// ---------------------------------------------------------------------------\n// Deduplication helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Deduplicate results from multiple query runs by an arbitrary ID field.\n * Generic helper compatible with SearchResult-like objects.\n *\n * @param resultSets - Arrays of results to deduplicate.\n * @param getId - Function that extracts the unique ID from a result.\n * @returns Flat deduplicated list, first occurrence wins.\n */\nexport function deduplicateResultsById<T>(resultSets: T[][], getId: (item: T) => string): T[] {\n const seen = new Set<string>();\n const deduped: T[] = [];\n for (const results of resultSets) {\n for (const r of results) {\n const id = getId(r);\n if (!seen.has(id)) {\n seen.add(id);\n deduped.push(r);\n }\n }\n }\n return deduped;\n}\n\n// ---------------------------------------------------------------------------\n// Issue-aware ambient context (Issue #137)\n// ---------------------------------------------------------------------------\n\n/** Keywords that suggest the conversation involves an active problem or error. */\nconst ERROR_KEYWORDS = [\n \"error\",\n \"failed\",\n \"crash\",\n \"timeout\",\n \"broken\",\n \"not working\",\n \"exception\",\n \"bug\",\n \"issue\",\n \"problem\",\n \"fault\",\n \"failure\",\n \"down\",\n];\n\n/**\n * Detect whether a message text contains error-like keywords that suggest\n * the user is dealing with an active problem.\n */\nfunction hasErrorKeywords(text: string): boolean {\n const lower = text.toLowerCase();\n return ERROR_KEYWORDS.some((kw) => lower.includes(kw));\n}\n\ninterface IssueAmbientResult {\n /** Open issues that may be relevant to the current message. */\n openIssues: Issue[];\n /** Resolved/verified issues that may contain relevant resolution context. */\n resolvedIssues: Issue[];\n}\n\n/**\n * Search the issue store for issues relevant to the current conversation message.\n * Called when the message contains error-like keywords.\n *\n * - Searches open issues so the user knows about known active problems.\n * - Searches resolved/verified issues to surface past resolutions that may help.\n *\n * Returns at most 3 open + 3 resolved issues.\n */\nexport function searchAmbientIssues(message: string, issueStore: IssueStore): IssueAmbientResult {\n if (!hasErrorKeywords(message)) {\n return { openIssues: [], resolvedIssues: [] };\n }\n\n const lower = message.toLowerCase();\n const keywords: string[] = [];\n for (const kw of ERROR_KEYWORDS) {\n if (lower.includes(kw)) {\n keywords.push(kw);\n }\n }\n const words = message.match(/\\b[a-zA-Z0-9_-]{3,}\\b/g) || [];\n for (const word of words.slice(0, 5)) {\n if (!ERROR_KEYWORDS.includes(word.toLowerCase())) {\n keywords.push(word);\n }\n }\n\n const allMatches = new Map<string, Issue>();\n for (const keyword of keywords.slice(0, 3)) {\n const matches = issueStore.search(keyword);\n for (const match of matches) {\n allMatches.set(match.id, match);\n }\n }\n\n const matchList = Array.from(allMatches.values());\n const openIssues = matchList\n .filter((i) => i.status === \"open\" || i.status === \"diagnosed\" || i.status === \"fix-attempted\")\n .slice(0, 3);\n\n const resolvedIssues = matchList.filter((i) => i.status === \"resolved\" || i.status === \"verified\").slice(0, 3);\n\n return { openIssues, resolvedIssues };\n}\n"],"mappings":"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"}
|