mixdog 0.7.1
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/.claude-plugin/marketplace.json +31 -0
- package/.claude-plugin/plugin.json +20 -0
- package/.gitattributes +34 -0
- package/.mcp.json +14 -0
- package/ARCHITECTURE.md +77 -0
- package/CHANGELOG.md +7 -0
- package/CONTRIBUTING.md +45 -0
- package/DATA-FLOW.md +79 -0
- package/LICENSE +21 -0
- package/README.md +389 -0
- package/SECURITY.md +138 -0
- package/UNINSTALL.md +112 -0
- package/agents/maintenance.md +5 -0
- package/agents/memory-classification.md +30 -0
- package/agents/scheduler-task.md +18 -0
- package/agents/webhook-handler.md +27 -0
- package/agents/worker.md +24 -0
- package/bin/bridge +133 -0
- package/bin/statusline-launcher.mjs +78 -0
- package/bin/statusline-lib.mjs +550 -0
- package/bin/statusline.mjs +607 -0
- package/bun.lock +802 -0
- package/commands/config.md +16 -0
- package/commands/doctor.md +13 -0
- package/commands/setup.md +17 -0
- package/defaults/cycle3-review-prompt.md +90 -0
- package/defaults/hidden-roles.json +65 -0
- package/defaults/memory-chunk-prompt.md +63 -0
- package/defaults/memory-promote-prompt.md +135 -0
- package/defaults/mixdog-config.template.json +27 -0
- package/defaults/user-workflow.json +8 -0
- package/defaults/user-workflow.md +12 -0
- package/hooks/hooks.json +73 -0
- package/hooks/lib/active-instance.cjs +77 -0
- package/hooks/lib/permission-evaluator.cjs +411 -0
- package/hooks/lib/permission-route.cjs +63 -0
- package/hooks/lib/permission-rules.cjs +170 -0
- package/hooks/lib/settings-loader.cjs +116 -0
- package/hooks/post-tool-use.cjs +84 -0
- package/hooks/pre-mcp-sandbox.cjs +158 -0
- package/hooks/pre-tool-subagent.cjs +253 -0
- package/hooks/session-start.cjs +1372 -0
- package/hooks/turn-timer.cjs +82 -0
- package/lib/claude-md-writer.cjs +386 -0
- package/lib/config-cjs.cjs +61 -0
- package/lib/hook-pipe-path.cjs +10 -0
- package/lib/keychain-cjs.cjs +263 -0
- package/lib/plugin-paths.cjs +61 -0
- package/lib/rules-builder.cjs +241 -0
- package/lib/text-utils.cjs +61 -0
- package/native/README.md +117 -0
- package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
- package/package.json +107 -0
- package/prompts/code-review.txt +16 -0
- package/prompts/security-audit.txt +17 -0
- package/rules/bridge/00-common.md +39 -0
- package/rules/bridge/20-skip-protocol.md +18 -0
- package/rules/bridge/30-explorer.md +33 -0
- package/rules/bridge/40-cycle1-agent.md +52 -0
- package/rules/bridge/41-cycle2-agent.md +62 -0
- package/rules/bridge/42-cycle3-agent.md +44 -0
- package/rules/lead/00-tool-lead.md +61 -0
- package/rules/lead/01-general.md +23 -0
- package/rules/lead/02-channels.md +49 -0
- package/rules/lead/03-team.md +27 -0
- package/rules/lead/04-workflow.md +20 -0
- package/rules/shared/00-language.md +14 -0
- package/rules/shared/01-tool.md +138 -0
- package/scripts/bootstrap.mjs +184 -0
- package/scripts/bridge-unify-smoke.mjs +308 -0
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/builtin-utils-smoke.mjs +392 -0
- package/scripts/check-json.mjs +45 -0
- package/scripts/check-syntax-changed.mjs +102 -0
- package/scripts/check-syntax.mjs +58 -0
- package/scripts/code-graph-batch.test.mjs +33 -0
- package/scripts/config-preserve-smoke.mjs +180 -0
- package/scripts/doctor.mjs +484 -0
- package/scripts/edit-normalize-fuzz.mjs +130 -0
- package/scripts/edit-normalize-smoke.mjs +401 -0
- package/scripts/edit-operation-smoke.mjs +369 -0
- package/scripts/edit2-smoke.mjs +63 -0
- package/scripts/fuzzy-e2e.mjs +28 -0
- package/scripts/fuzzy-smoke.mjs +26 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/guard-smoke.mjs +66 -0
- package/scripts/hidden-role-schema-smoke.mjs +162 -0
- package/scripts/hook-routing-smoke.mjs +29 -0
- package/scripts/inject-input.ps1 +204 -0
- package/scripts/io-complex-smoke.mjs +667 -0
- package/scripts/io-explore-bench.mjs +424 -0
- package/scripts/io-guardrails-smoke.mjs +205 -0
- package/scripts/io-mini-bench-baseline.json +11 -0
- package/scripts/io-mini-bench.mjs +216 -0
- package/scripts/io-route-harness.mjs +933 -0
- package/scripts/io-telemetry-report.mjs +691 -0
- package/scripts/mutation-bench.mjs +564 -0
- package/scripts/mutation-io-smoke.mjs +1081 -0
- package/scripts/native-patch-bridge-smoke.mjs +288 -0
- package/scripts/native-patch-smoke.mjs +304 -0
- package/scripts/patch-interior-context-smoke.mjs +49 -0
- package/scripts/patch-newline-utf8-smoke.mjs +157 -0
- package/scripts/perf-hook-smoke.mjs +71 -0
- package/scripts/permission-eval-smoke.mjs +426 -0
- package/scripts/prep-patch.mjs +53 -0
- package/scripts/prep-shim.mjs +96 -0
- package/scripts/provider-cache-smoke.mjs +687 -0
- package/scripts/report-runtime-health.mjs +132 -0
- package/scripts/run-mcp.mjs +1547 -0
- package/scripts/salvage-v4a-shatter.test.mjs +58 -0
- package/scripts/scoped-cache-io-smoke.mjs +103 -0
- package/scripts/shell-policy-round3-smoke.mjs +46 -0
- package/scripts/smoke-runtime-negative.ps1 +100 -0
- package/scripts/smoke-runtime-negative.sh +95 -0
- package/scripts/stall-policy-smoke.mjs +50 -0
- package/scripts/start-memory-worker.mjs +23 -0
- package/scripts/statusline-launcher-smoke.mjs +82 -0
- package/scripts/stress-atomic-write.mjs +1028 -0
- package/scripts/test-config-rmw-restore.mjs +122 -0
- package/scripts/test-fault-inject.mjs +164 -0
- package/scripts/test-large-file.mjs +174 -0
- package/scripts/tool-edge-smoke.mjs +209 -0
- package/scripts/uninstall.mjs +201 -0
- package/scripts/webhook-selfheal-smoke.mjs +29 -0
- package/scripts/write-overwrite-guard-smoke.mjs +56 -0
- package/server-main.mjs +3055 -0
- package/server.mjs +468 -0
- package/setup/config-merge.mjs +254 -0
- package/setup/install.mjs +120 -0
- package/setup/launch-core.mjs +507 -0
- package/setup/launch.mjs +101 -0
- package/setup/setup-server.mjs +3206 -0
- package/setup/setup.html +3693 -0
- package/skills/retro-skill-proposer/SKILL.md +92 -0
- package/skills/schedule-add/SKILL.md +77 -0
- package/skills/setup/SKILL.md +346 -0
- package/skills/webhook-add/SKILL.md +81 -0
- package/src/agent/bridge-stall-watchdog.mjs +337 -0
- package/src/agent/index.mjs +2138 -0
- package/src/agent/orchestrator/activity-bus.mjs +38 -0
- package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +1010 -0
- package/src/agent/orchestrator/bridge-retry.mjs +220 -0
- package/src/agent/orchestrator/bridge-trace.mjs +583 -0
- package/src/agent/orchestrator/cache-mtime.mjs +58 -0
- package/src/agent/orchestrator/config.mjs +358 -0
- package/src/agent/orchestrator/context/collect.mjs +651 -0
- package/src/agent/orchestrator/dispatch-persist.mjs +549 -0
- package/src/agent/orchestrator/drain-registry.mjs +50 -0
- package/src/agent/orchestrator/explore-validator.mjs +8 -0
- package/src/agent/orchestrator/internal-roles.mjs +118 -0
- package/src/agent/orchestrator/internal-tools.mjs +88 -0
- package/src/agent/orchestrator/jobs.mjs +116 -0
- package/src/agent/orchestrator/mcp/client.mjs +364 -0
- package/src/agent/orchestrator/providers/anthropic-betas.mjs +21 -0
- package/src/agent/orchestrator/providers/anthropic-oauth.mjs +1745 -0
- package/src/agent/orchestrator/providers/anthropic.mjs +437 -0
- package/src/agent/orchestrator/providers/gemini.mjs +1175 -0
- package/src/agent/orchestrator/providers/grok-oauth.mjs +782 -0
- package/src/agent/orchestrator/providers/model-catalog.mjs +241 -0
- package/src/agent/orchestrator/providers/openai-compat.mjs +1467 -0
- package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +1890 -0
- package/src/agent/orchestrator/providers/openai-oauth.mjs +1307 -0
- package/src/agent/orchestrator/providers/openai-ws.mjs +104 -0
- package/src/agent/orchestrator/providers/registry.mjs +192 -0
- package/src/agent/orchestrator/providers/retry-classifier.mjs +325 -0
- package/src/agent/orchestrator/session/abort-lookup.mjs +13 -0
- package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
- package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
- package/src/agent/orchestrator/session/cache/read-cache.mjs +319 -0
- package/src/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +11 -0
- package/src/agent/orchestrator/session/cache/scoped-cache.mjs +361 -0
- package/src/agent/orchestrator/session/cache/util.mjs +49 -0
- package/src/agent/orchestrator/session/loop.mjs +1478 -0
- package/src/agent/orchestrator/session/manager.mjs +1975 -0
- package/src/agent/orchestrator/session/read-dedup.mjs +6 -0
- package/src/agent/orchestrator/session/result-classification.mjs +65 -0
- package/src/agent/orchestrator/session/save-session-worker.mjs +18 -0
- package/src/agent/orchestrator/session/store.mjs +624 -0
- package/src/agent/orchestrator/session/stream-watchdog.mjs +130 -0
- package/src/agent/orchestrator/session/tool-result-offload.mjs +166 -0
- package/src/agent/orchestrator/session/trim.mjs +491 -0
- package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +115 -0
- package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +327 -0
- package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +150 -0
- package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +228 -0
- package/src/agent/orchestrator/smart-bridge/index.mjs +215 -0
- package/src/agent/orchestrator/smart-bridge/profiles.mjs +37 -0
- package/src/agent/orchestrator/smart-bridge/registry.mjs +348 -0
- package/src/agent/orchestrator/smart-bridge/session-builder.mjs +116 -0
- package/src/agent/orchestrator/stall-policy.mjs +195 -0
- package/src/agent/orchestrator/tool-loop-guard.mjs +75 -0
- package/src/agent/orchestrator/tools/bash-policy-scan.mjs +77 -0
- package/src/agent/orchestrator/tools/bash-session.mjs +721 -0
- package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
- package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +455 -0
- package/src/agent/orchestrator/tools/builtin/atomic-write.mjs +236 -0
- package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +480 -0
- package/src/agent/orchestrator/tools/builtin/binary-file.mjs +76 -0
- package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +256 -0
- package/src/agent/orchestrator/tools/builtin/cache-layers.mjs +386 -0
- package/src/agent/orchestrator/tools/builtin/cwd-utils.mjs +37 -0
- package/src/agent/orchestrator/tools/builtin/device-paths.mjs +154 -0
- package/src/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +292 -0
- package/src/agent/orchestrator/tools/builtin/diff-utils.mjs +109 -0
- package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +58 -0
- package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +240 -0
- package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +113 -0
- package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +74 -0
- package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +242 -0
- package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +211 -0
- package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +1364 -0
- package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +126 -0
- package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +141 -0
- package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +194 -0
- package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +60 -0
- package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +168 -0
- package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +173 -0
- package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +48 -0
- package/src/agent/orchestrator/tools/builtin/fs-reachability.mjs +48 -0
- package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +99 -0
- package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +170 -0
- package/src/agent/orchestrator/tools/builtin/grep-formatting.mjs +113 -0
- package/src/agent/orchestrator/tools/builtin/hash-utils.mjs +6 -0
- package/src/agent/orchestrator/tools/builtin/list-formatting.mjs +7 -0
- package/src/agent/orchestrator/tools/builtin/list-tool.mjs +593 -0
- package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +89 -0
- package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +300 -0
- package/src/agent/orchestrator/tools/builtin/open-config-tool.mjs +26 -0
- package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +152 -0
- package/src/agent/orchestrator/tools/builtin/path-locks.mjs +35 -0
- package/src/agent/orchestrator/tools/builtin/path-utils.mjs +201 -0
- package/src/agent/orchestrator/tools/builtin/read-args.mjs +103 -0
- package/src/agent/orchestrator/tools/builtin/read-batch.mjs +172 -0
- package/src/agent/orchestrator/tools/builtin/read-constants.mjs +40 -0
- package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
- package/src/agent/orchestrator/tools/builtin/read-image-resize.mjs +189 -0
- package/src/agent/orchestrator/tools/builtin/read-image.mjs +88 -0
- package/src/agent/orchestrator/tools/builtin/read-lines.mjs +12 -0
- package/src/agent/orchestrator/tools/builtin/read-mode-tool.mjs +455 -0
- package/src/agent/orchestrator/tools/builtin/read-open.mjs +190 -0
- package/src/agent/orchestrator/tools/builtin/read-range-index.mjs +271 -0
- package/src/agent/orchestrator/tools/builtin/read-ranges.mjs +26 -0
- package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +728 -0
- package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +173 -0
- package/src/agent/orchestrator/tools/builtin/read-special-files.mjs +268 -0
- package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
- package/src/agent/orchestrator/tools/builtin/read-tool.mjs +530 -0
- package/src/agent/orchestrator/tools/builtin/read-windows.mjs +107 -0
- package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +196 -0
- package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +422 -0
- package/src/agent/orchestrator/tools/builtin/search-builders.mjs +158 -0
- package/src/agent/orchestrator/tools/builtin/search-tool.mjs +869 -0
- package/src/agent/orchestrator/tools/builtin/shell-analysis.mjs +653 -0
- package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +936 -0
- package/src/agent/orchestrator/tools/builtin/shell-output.mjs +36 -0
- package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +214 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +143 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +206 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-validation.mjs +98 -0
- package/src/agent/orchestrator/tools/builtin/text-stats.mjs +69 -0
- package/src/agent/orchestrator/tools/builtin/windows-roots.mjs +23 -0
- package/src/agent/orchestrator/tools/builtin/write-tool.mjs +401 -0
- package/src/agent/orchestrator/tools/builtin.mjs +500 -0
- package/src/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +39 -0
- package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
- package/src/agent/orchestrator/tools/code-graph.mjs +4095 -0
- package/src/agent/orchestrator/tools/cwd-tool.mjs +298 -0
- package/src/agent/orchestrator/tools/destructive-warning.mjs +323 -0
- package/src/agent/orchestrator/tools/edit-normalize.mjs +603 -0
- package/src/agent/orchestrator/tools/env-scrub.mjs +100 -0
- package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +144 -0
- package/src/agent/orchestrator/tools/graph-manifest.json +26 -0
- package/src/agent/orchestrator/tools/host-input.mjs +204 -0
- package/src/agent/orchestrator/tools/mutation-content-cache.mjs +67 -0
- package/src/agent/orchestrator/tools/mutation-planner.mjs +75 -0
- package/src/agent/orchestrator/tools/next-call-utils.mjs +48 -0
- package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +133 -0
- package/src/agent/orchestrator/tools/patch-manifest.json +26 -0
- package/src/agent/orchestrator/tools/patch-tool-defs.mjs +20 -0
- package/src/agent/orchestrator/tools/patch.mjs +2754 -0
- package/src/agent/orchestrator/tools/progress-message.mjs +118 -0
- package/src/agent/orchestrator/tools/result-compression.mjs +279 -0
- package/src/agent/orchestrator/tools/shell-command.mjs +865 -0
- package/src/agent/orchestrator/tools/shell-exec-policy.mjs +89 -0
- package/src/agent/orchestrator/tools/shell-policy-danger-target.mjs +27 -0
- package/src/agent/orchestrator/tools/shell-policy-imports.mjs +7 -0
- package/src/agent/orchestrator/tools/shell-policy.mjs +345 -0
- package/src/agent/orchestrator/tools/shell-snapshot.mjs +313 -0
- package/src/agent/orchestrator/workflow-store.mjs +93 -0
- package/src/agent/tool-defs.mjs +103 -0
- package/src/channels/backends/discord.mjs +784 -0
- package/src/channels/data/voice-runtime-manifest.json +138 -0
- package/src/channels/index.mjs +3229 -0
- package/src/channels/lib/cli-worker-host.mjs +12 -0
- package/src/channels/lib/config-lock.mjs +13 -0
- package/src/channels/lib/config.mjs +292 -0
- package/src/channels/lib/drop-trace.mjs +71 -0
- package/src/channels/lib/event-pipeline.mjs +81 -0
- package/src/channels/lib/event-queue.mjs +345 -0
- package/src/channels/lib/executor.mjs +168 -0
- package/src/channels/lib/format.mjs +188 -0
- package/src/channels/lib/holidays.mjs +138 -0
- package/src/channels/lib/hook-pipe-server.mjs +802 -0
- package/src/channels/lib/interaction-workflows.mjs +184 -0
- package/src/channels/lib/memory-client.mjs +149 -0
- package/src/channels/lib/output-forwarder.mjs +765 -0
- package/src/channels/lib/runtime-paths.mjs +479 -0
- package/src/channels/lib/scheduler.mjs +723 -0
- package/src/channels/lib/session-control.mjs +36 -0
- package/src/channels/lib/session-discovery.mjs +103 -0
- package/src/channels/lib/settings.mjs +11 -0
- package/src/channels/lib/state-file.mjs +68 -0
- package/src/channels/lib/status-snapshot.mjs +219 -0
- package/src/channels/lib/tool-format.mjs +140 -0
- package/src/channels/lib/transcript-discovery.mjs +195 -0
- package/src/channels/lib/voice-runtime-fetcher.mjs +734 -0
- package/src/channels/lib/webhook.mjs +1179 -0
- package/src/channels/lib/whisper-server.mjs +477 -0
- package/src/channels/tool-defs.mjs +170 -0
- package/src/daemon/host.mjs +118 -0
- package/src/daemon/mcp-transport.mjs +47 -0
- package/src/daemon/session.mjs +100 -0
- package/src/daemon/thin-client.mjs +71 -0
- package/src/daemon/transport.mjs +163 -0
- package/src/memory/data/runtime-manifest.json +40 -0
- package/src/memory/index.mjs +3305 -0
- package/src/memory/lib/agent-ipc.mjs +93 -0
- package/src/memory/lib/bridge-trace-queries.mjs +120 -0
- package/src/memory/lib/core-memory-store.mjs +330 -0
- package/src/memory/lib/embedding-provider.mjs +269 -0
- package/src/memory/lib/embedding-worker.mjs +323 -0
- package/src/memory/lib/llm-worker-host.mjs +17 -0
- package/src/memory/lib/memory-cycle.mjs +11 -0
- package/src/memory/lib/memory-cycle1.mjs +641 -0
- package/src/memory/lib/memory-cycle2.mjs +1284 -0
- package/src/memory/lib/memory-cycle3.mjs +540 -0
- package/src/memory/lib/memory-embed.mjs +299 -0
- package/src/memory/lib/memory-extraction.mjs +5 -0
- package/src/memory/lib/memory-maintenance-store.mjs +32 -0
- package/src/memory/lib/memory-ops-policy.mjs +190 -0
- package/src/memory/lib/memory-recall-id-patch.mjs +15 -0
- package/src/memory/lib/memory-recall-read-query.mjs +7 -0
- package/src/memory/lib/memory-recall-scope-filter.mjs +63 -0
- package/src/memory/lib/memory-recall-store.mjs +621 -0
- package/src/memory/lib/memory-retrievers.mjs +112 -0
- package/src/memory/lib/memory-score.mjs +71 -0
- package/src/memory/lib/memory-text-utils.mjs +58 -0
- package/src/memory/lib/memory.mjs +412 -0
- package/src/memory/lib/model-profile.mjs +85 -0
- package/src/memory/lib/pg/adapter.mjs +308 -0
- package/src/memory/lib/pg/process.mjs +360 -0
- package/src/memory/lib/pg/supervisor.mjs +396 -0
- package/src/memory/lib/project-id-resolver.mjs +86 -0
- package/src/memory/lib/runtime-fetcher.mjs +442 -0
- package/src/memory/lib/trace-store.mjs +728 -0
- package/src/memory/tool-defs.mjs +79 -0
- package/src/search/index.mjs +1173 -0
- package/src/search/lib/backends/anthropic-oauth.mjs +98 -0
- package/src/search/lib/backends/exa.mjs +50 -0
- package/src/search/lib/backends/firecrawl.mjs +61 -0
- package/src/search/lib/backends/gemini-api.mjs +83 -0
- package/src/search/lib/backends/grok-oauth.mjs +86 -0
- package/src/search/lib/backends/index.mjs +150 -0
- package/src/search/lib/backends/openai-api.mjs +144 -0
- package/src/search/lib/backends/openai-oauth.mjs +98 -0
- package/src/search/lib/backends/openai-web-search.mjs +76 -0
- package/src/search/lib/backends/tavily.mjs +55 -0
- package/src/search/lib/backends/xai-api.mjs +113 -0
- package/src/search/lib/cache.mjs +131 -0
- package/src/search/lib/config.mjs +192 -0
- package/src/search/lib/formatter.mjs +115 -0
- package/src/search/lib/provider-usage.mjs +67 -0
- package/src/search/lib/providers.mjs +47 -0
- package/src/search/lib/search-intent.mjs +109 -0
- package/src/search/lib/setup-handler.mjs +261 -0
- package/src/search/lib/state.mjs +201 -0
- package/src/search/lib/web-tools.mjs +1207 -0
- package/src/search/tool-defs.mjs +83 -0
- package/src/setup/defender-exclusion.mjs +183 -0
- package/src/shared/abort-controller.mjs +15 -0
- package/src/shared/atomic-file.mjs +420 -0
- package/src/shared/config.mjs +350 -0
- package/src/shared/daemon-recycle.mjs +108 -0
- package/src/shared/disable-claude-builtins.mjs +88 -0
- package/src/shared/err-text.mjs +12 -0
- package/src/shared/llm/cost.mjs +66 -0
- package/src/shared/llm/http-agent.mjs +123 -0
- package/src/shared/llm/index.mjs +41 -0
- package/src/shared/llm/pid-cleanup.mjs +27 -0
- package/src/shared/llm/usage-log.mjs +47 -0
- package/src/shared/plugin-paths.mjs +58 -0
- package/src/shared/schedules-store.mjs +70 -0
- package/src/shared/seed.mjs +119 -0
- package/src/shared/user-cwd.mjs +213 -0
- package/src/shared/user-data-guard.mjs +238 -0
- package/src/status/aggregator.mjs +584 -0
- package/src/status/server.mjs +413 -0
- package/tools.json +1653 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { embedText, getEmbeddingModelId } from './embedding-provider.mjs'
|
|
2
|
+
import { embeddingToSql } from './memory.mjs'
|
|
3
|
+
import { createHash } from 'crypto'
|
|
4
|
+
import os from 'os'
|
|
5
|
+
import fs from 'fs'
|
|
6
|
+
import path from 'path'
|
|
7
|
+
|
|
8
|
+
// Restart-survivable embedding dedup cache (DDL created on first flush).
|
|
9
|
+
// Keyed per-db handle so a second DB instance in the same process re-runs the
|
|
10
|
+
// IF NOT EXISTS DDL on its own connection rather than skipping based on a
|
|
11
|
+
// flag set by a different DB.
|
|
12
|
+
const _embCacheReady = new WeakSet()
|
|
13
|
+
|
|
14
|
+
export function inferChunkProjectId(members) {
|
|
15
|
+
const storedIds = new Set()
|
|
16
|
+
for (const m of members) {
|
|
17
|
+
if (m.project_id != null) storedIds.add(m.project_id)
|
|
18
|
+
}
|
|
19
|
+
if (storedIds.size === 1) return [...storedIds][0]
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const _flushInFlight = new WeakMap()
|
|
24
|
+
|
|
25
|
+
const _rawTimeout = Number(process.env.MIXDOG_EMBED_FLUSH_TIMEOUT_MS)
|
|
26
|
+
const EMBED_FLUSH_TIMEOUT_MS = (Number.isFinite(_rawTimeout) && _rawTimeout > 0) ? _rawTimeout : 30_000
|
|
27
|
+
|
|
28
|
+
const BATCH_SIZE = 32
|
|
29
|
+
|
|
30
|
+
function throwIfAborted(signal) {
|
|
31
|
+
if (signal?.aborted) throw signal.reason ?? new Error('aborted')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function ensureEmbCacheTable(db) {
|
|
35
|
+
if (_embCacheReady.has(db)) return
|
|
36
|
+
await db.query(`
|
|
37
|
+
CREATE TABLE IF NOT EXISTS memory.embedding_cache (
|
|
38
|
+
model_id text NOT NULL,
|
|
39
|
+
text_hash bytea NOT NULL,
|
|
40
|
+
vector halfvec NOT NULL,
|
|
41
|
+
PRIMARY KEY (model_id, text_hash)
|
|
42
|
+
)
|
|
43
|
+
`)
|
|
44
|
+
_embCacheReady.add(db)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Batch variant: resolve cache hits in one SELECT WHERE hash IN (...),
|
|
48
|
+
// embed misses in parallel, then bulk-INSERT new entries.
|
|
49
|
+
export async function cachedEmbedTextBatch(db, texts, options = {}) {
|
|
50
|
+
const signal = options?.signal
|
|
51
|
+
throwIfAborted(signal)
|
|
52
|
+
if (texts.length === 0) return []
|
|
53
|
+
await ensureEmbCacheTable(db)
|
|
54
|
+
throwIfAborted(signal)
|
|
55
|
+
// Key cache rows by the provider's real model id, not the env override —
|
|
56
|
+
// env may be unset while the provider still has a concrete model, and an
|
|
57
|
+
// env value that doesn't match the provider would tag vectors with a
|
|
58
|
+
// stale/wrong label across model/env changes.
|
|
59
|
+
const modelId = getEmbeddingModelId() || process.env.MIXDOG_EMBED_MODEL || 'default'
|
|
60
|
+
const entries = texts.map(t => ({
|
|
61
|
+
text: t,
|
|
62
|
+
hash: Buffer.from(createHash('sha256').update(t).digest()),
|
|
63
|
+
}))
|
|
64
|
+
|
|
65
|
+
// Single SELECT for all hashes
|
|
66
|
+
const hashBufs = entries.map(e => e.hash)
|
|
67
|
+
const hits = (await db.query(
|
|
68
|
+
`SELECT text_hash, vector FROM memory.embedding_cache WHERE model_id=$1 AND text_hash = ANY($2::bytea[])`,
|
|
69
|
+
[modelId, hashBufs],
|
|
70
|
+
)).rows
|
|
71
|
+
throwIfAborted(signal)
|
|
72
|
+
// Issue 6: pgvector/halfvec returns a text string like "[0.1,0.2,...]".
|
|
73
|
+
// Array.from on a string yields individual characters — parse to numeric array.
|
|
74
|
+
const hitMap = new Map(hits.map(r => [
|
|
75
|
+
r.text_hash.toString('hex'),
|
|
76
|
+
typeof r.vector === 'string'
|
|
77
|
+
? r.vector.replace(/^\[|\]$/g, '').split(',').map(Number)
|
|
78
|
+
: Array.from(r.vector),
|
|
79
|
+
]))
|
|
80
|
+
|
|
81
|
+
// Embed misses in parallel
|
|
82
|
+
const misses = entries.filter(e => !hitMap.has(e.hash.toString('hex')))
|
|
83
|
+
if (misses.length > 0) {
|
|
84
|
+
throwIfAborted(signal)
|
|
85
|
+
await Promise.all(misses.map(async (e) => {
|
|
86
|
+
e.vector = await embedText(e.text)
|
|
87
|
+
}))
|
|
88
|
+
throwIfAborted(signal)
|
|
89
|
+
// Bulk INSERT misses
|
|
90
|
+
await db.query(
|
|
91
|
+
`INSERT INTO memory.embedding_cache (model_id, text_hash, vector)
|
|
92
|
+
SELECT $1, unnest($2::bytea[]), unnest($3::text[])::halfvec
|
|
93
|
+
ON CONFLICT (model_id, text_hash) DO NOTHING`,
|
|
94
|
+
[
|
|
95
|
+
modelId,
|
|
96
|
+
misses.map(e => e.hash),
|
|
97
|
+
misses.map(e => embeddingToSql(e.vector)),
|
|
98
|
+
],
|
|
99
|
+
)
|
|
100
|
+
throwIfAborted(signal)
|
|
101
|
+
for (const e of misses) hitMap.set(e.hash.toString('hex'), e.vector)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return entries.map(e => hitMap.get(e.hash.toString('hex')) ?? null)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function flushEmbeddingDirty(db, options = {}) {
|
|
108
|
+
const signal = options?.signal
|
|
109
|
+
throwIfAborted(signal)
|
|
110
|
+
// Coalesce concurrent flush calls per db handle.
|
|
111
|
+
const inFlight = _flushInFlight.get(db)
|
|
112
|
+
if (inFlight) return inFlight
|
|
113
|
+
const p = (async () => {
|
|
114
|
+
let totalAttempted = 0
|
|
115
|
+
let totalSucceeded = 0
|
|
116
|
+
let timedOut = false
|
|
117
|
+
const allFailed = []
|
|
118
|
+
const deadline = Date.now() + EMBED_FLUSH_TIMEOUT_MS
|
|
119
|
+
let cursor = 0
|
|
120
|
+
await ensureEmbCacheTable(db)
|
|
121
|
+
throwIfAborted(signal)
|
|
122
|
+
|
|
123
|
+
while (true) {
|
|
124
|
+
throwIfAborted(signal)
|
|
125
|
+
if (Date.now() >= deadline) {
|
|
126
|
+
process.stderr.write(
|
|
127
|
+
`[embed] flush timed out after ${EMBED_FLUSH_TIMEOUT_MS / 1000}s; proceeding with partial state\n`,
|
|
128
|
+
)
|
|
129
|
+
timedOut = true
|
|
130
|
+
break
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Claim a disjoint batch via SKIP LOCKED on a dedicated connection.
|
|
134
|
+
// Hold the transaction open across the embedding write so another flush
|
|
135
|
+
// (different process / db handle) cannot re-claim the same ids mid-flight.
|
|
136
|
+
// The protected work routes through `client` so the UPDATE runs on the
|
|
137
|
+
// same connection that owns the row locks (a different pool connection
|
|
138
|
+
// would block on FOR UPDATE).
|
|
139
|
+
const client = await db._pool.connect()
|
|
140
|
+
let ids
|
|
141
|
+
let claimOk = false
|
|
142
|
+
try {
|
|
143
|
+
throwIfAborted(signal)
|
|
144
|
+
await client.query('BEGIN')
|
|
145
|
+
const res = await client.query(
|
|
146
|
+
`SELECT id FROM memory.entries
|
|
147
|
+
WHERE is_root = 1 AND embedding IS NULL
|
|
148
|
+
AND (element IS NOT NULL OR summary IS NOT NULL)
|
|
149
|
+
AND id > $2
|
|
150
|
+
ORDER BY id
|
|
151
|
+
LIMIT $1
|
|
152
|
+
FOR UPDATE SKIP LOCKED`,
|
|
153
|
+
[BATCH_SIZE, cursor],
|
|
154
|
+
)
|
|
155
|
+
ids = res.rows.map(r => Number(r.id))
|
|
156
|
+
claimOk = true
|
|
157
|
+
throwIfAborted(signal)
|
|
158
|
+
} catch (err) {
|
|
159
|
+
try { await client.query('ROLLBACK') } catch {}
|
|
160
|
+
client.release()
|
|
161
|
+
if (signal?.aborted) throw signal.reason ?? err
|
|
162
|
+
process.stderr.write(`[embed] flush SKIP LOCKED claim failed: ${err.message}\n`)
|
|
163
|
+
break
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (ids.length === 0) {
|
|
167
|
+
try { await client.query('COMMIT') } catch {}
|
|
168
|
+
client.release()
|
|
169
|
+
break
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
cursor = ids[ids.length - 1]
|
|
173
|
+
totalAttempted += ids.length
|
|
174
|
+
let batchDone = false
|
|
175
|
+
try {
|
|
176
|
+
// One embedding flush batch owns row locks until COMMIT/ROLLBACK;
|
|
177
|
+
// cancellation is checked before each batch and after its locked work.
|
|
178
|
+
throwIfAborted(signal)
|
|
179
|
+
const writtenIds = await syncBatchEmbeddings(client, ids, { signal })
|
|
180
|
+
totalSucceeded += writtenIds.length
|
|
181
|
+
if (writtenIds.length < ids.length) {
|
|
182
|
+
// Track per-id: only the ids that did NOT receive an embedding
|
|
183
|
+
// (no text, dim mismatch, stale write) count as failed.
|
|
184
|
+
const writtenSet = new Set(writtenIds)
|
|
185
|
+
for (const id of ids) {
|
|
186
|
+
if (!writtenSet.has(id)) allFailed.push(id)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
batchDone = true
|
|
190
|
+
} catch (err) {
|
|
191
|
+
if (signal?.aborted) throw signal.reason ?? err
|
|
192
|
+
process.stderr.write(`[embed] batch failed (ids=${ids[0]}..${ids[ids.length-1]}): ${err.message}\n`)
|
|
193
|
+
for (const id of ids) allFailed.push(id)
|
|
194
|
+
} finally {
|
|
195
|
+
try {
|
|
196
|
+
if (batchDone) await client.query('COMMIT')
|
|
197
|
+
else await client.query('ROLLBACK')
|
|
198
|
+
} catch {}
|
|
199
|
+
client.release()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (ids.length < BATCH_SIZE) break
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return { attempted: totalAttempted, succeeded: totalSucceeded, failed: allFailed, timedOut }
|
|
206
|
+
})()
|
|
207
|
+
_flushInFlight.set(db, p)
|
|
208
|
+
try {
|
|
209
|
+
return await p
|
|
210
|
+
} finally {
|
|
211
|
+
_flushInFlight.delete(db)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Batch-embed all ids in one DB round-trip for fetching, one API call per unique text,
|
|
216
|
+
// then one VALUES UPDATE for all results.
|
|
217
|
+
// Returns the list of ids that were actually UPDATEd with a fresh embedding.
|
|
218
|
+
// Empty result means no ids landed (e.g. all had empty text, dim mismatches,
|
|
219
|
+
// or text changed since read). Callers may compare result.length to ids.length
|
|
220
|
+
// for per-id success tracking.
|
|
221
|
+
async function syncBatchEmbeddings(db, ids, options = {}) {
|
|
222
|
+
const signal = options?.signal
|
|
223
|
+
throwIfAborted(signal)
|
|
224
|
+
// 1. Fetch element+summary for all ids in one query
|
|
225
|
+
const rows = (await db.query(
|
|
226
|
+
`SELECT id, element, summary FROM memory.entries WHERE id = ANY($1::bigint[]) AND is_root = 1`,
|
|
227
|
+
[ids],
|
|
228
|
+
)).rows
|
|
229
|
+
throwIfAborted(signal)
|
|
230
|
+
if (rows.length === 0) return []
|
|
231
|
+
|
|
232
|
+
// 2. Fetch dims from meta once
|
|
233
|
+
const dimsRow = (await db.query(`SELECT value FROM memory.meta WHERE key = 'embedding.current_dims'`)).rows[0]
|
|
234
|
+
throwIfAborted(signal)
|
|
235
|
+
const expected = Number(dimsRow?.value ?? 0)
|
|
236
|
+
|
|
237
|
+
// 3. Embed each row via batch cache lookup (one SELECT + one INSERT for misses)
|
|
238
|
+
const texts = rows.map(row => [row.element, row.summary].filter(Boolean).join(' — ').trim())
|
|
239
|
+
const vectors = await cachedEmbedTextBatch(db, texts.filter(Boolean), { signal })
|
|
240
|
+
throwIfAborted(signal)
|
|
241
|
+
// Re-map: only non-empty texts were passed to cachedEmbedTextBatch
|
|
242
|
+
let vecIdx = 0
|
|
243
|
+
const updates = [] // { id, element, summary, vector }
|
|
244
|
+
for (let i = 0; i < rows.length; i++) {
|
|
245
|
+
throwIfAborted(signal)
|
|
246
|
+
const row = rows[i]
|
|
247
|
+
if (!texts[i]) continue
|
|
248
|
+
const vector = vectors[vecIdx++]
|
|
249
|
+
if (!Array.isArray(vector) || vector.length === 0) continue
|
|
250
|
+
if (Number.isFinite(expected) && expected > 0 && vector.length !== expected) {
|
|
251
|
+
process.stderr.write(`[embed] dim mismatch (id=${row.id} got=${vector.length} expected=${expected})\n`)
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
254
|
+
updates.push({ id: Number(row.id), element: row.element, summary: row.summary, vector })
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (updates.length === 0) return []
|
|
258
|
+
|
|
259
|
+
// 4. Bulk UPDATE using VALUES list
|
|
260
|
+
// The VALUES update is one SQL batch; do not split it with an abort checkpoint.
|
|
261
|
+
const valClauses = updates.map((u, i) => {
|
|
262
|
+
const base = i * 4
|
|
263
|
+
return `($${base+1}::bigint, $${base+2}::halfvec, $${base+3}::text, $${base+4}::text)`
|
|
264
|
+
})
|
|
265
|
+
const params = []
|
|
266
|
+
for (const u of updates) {
|
|
267
|
+
params.push(u.id, embeddingToSql(u.vector), u.element, u.summary)
|
|
268
|
+
}
|
|
269
|
+
const res = await db.query(
|
|
270
|
+
`UPDATE memory.entries AS e
|
|
271
|
+
SET embedding = t.vector
|
|
272
|
+
FROM (VALUES ${valClauses.join(',')}) AS t(id, vector, element, summary)
|
|
273
|
+
WHERE e.id = t.id AND e.is_root = 1
|
|
274
|
+
AND e.embedding IS NULL
|
|
275
|
+
AND e.element IS NOT DISTINCT FROM t.element
|
|
276
|
+
AND e.summary IS NOT DISTINCT FROM t.summary
|
|
277
|
+
RETURNING e.id`,
|
|
278
|
+
params,
|
|
279
|
+
)
|
|
280
|
+
throwIfAborted(signal)
|
|
281
|
+
const writtenIds = (res.rows || []).map(r => Number(r.id))
|
|
282
|
+
if (writtenIds.length < updates.length) {
|
|
283
|
+
process.stderr.write(`[embed-sync] ${updates.length - writtenIds.length} stale-write(s) skipped — text changed since read\n`)
|
|
284
|
+
}
|
|
285
|
+
return writtenIds
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Kept for external callers that still import syncRootEmbedding directly.
|
|
289
|
+
export async function syncRootEmbedding(db, rootId, options = {}) {
|
|
290
|
+
const writtenIds = await syncBatchEmbeddings(db, [rootId], options)
|
|
291
|
+
return writtenIds.length > 0
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export async function deleteRootEmbedding(db, rootId) {
|
|
295
|
+
await db.transaction(async (tx) => {
|
|
296
|
+
await tx.query(`UPDATE entries SET embedding = NULL WHERE id = $1 AND is_root = 1`, [rootId])
|
|
297
|
+
})
|
|
298
|
+
return true
|
|
299
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Single source of truth: lib/text-utils.cjs (also required by hooks/session-start.cjs).
|
|
2
|
+
import { createRequire } from 'module'
|
|
3
|
+
const _require = createRequire(import.meta.url)
|
|
4
|
+
const { cleanMemoryText: _cleanMemoryText } = _require('../../../lib/text-utils.cjs')
|
|
5
|
+
export const cleanMemoryText = _cleanMemoryText
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export async function pruneOldEntries(db, maxAgeDays) {
|
|
2
|
+
const days = Number(maxAgeDays)
|
|
3
|
+
if (!Number.isFinite(days) || days <= 0) {
|
|
4
|
+
throw new Error(`pruneOldEntries: maxAgeDays must be positive, got ${maxAgeDays}`)
|
|
5
|
+
}
|
|
6
|
+
const cutoffMs = Date.now() - days * 86_400_000
|
|
7
|
+
const result = await db.query(
|
|
8
|
+
`DELETE FROM entries
|
|
9
|
+
WHERE ts < $1
|
|
10
|
+
AND (
|
|
11
|
+
chunk_root IS NULL
|
|
12
|
+
OR (is_root = 0 AND chunk_root = id AND status = 'archived')
|
|
13
|
+
)`,
|
|
14
|
+
[cutoffMs],
|
|
15
|
+
)
|
|
16
|
+
// Cross-schema FK is intentionally absent (trace_events is partitioned and
|
|
17
|
+
// self-FKs are fragile there). After deleting from memory.entries, NULL out
|
|
18
|
+
// the dangling entry_id references on trace_events so cross-schema joins
|
|
19
|
+
// (recall ↔ trace correlation) do not surface stale ids. Same PG instance
|
|
20
|
+
// → fully-qualified names work from either schema's connection.
|
|
21
|
+
let orphanedTraceRefs = 0
|
|
22
|
+
try {
|
|
23
|
+
const orphan = await db.query(
|
|
24
|
+
`UPDATE trace.trace_events SET entry_id = NULL
|
|
25
|
+
WHERE entry_id IS NOT NULL
|
|
26
|
+
AND NOT EXISTS (SELECT 1 FROM memory.entries WHERE id = trace.trace_events.entry_id)`,
|
|
27
|
+
[],
|
|
28
|
+
)
|
|
29
|
+
orphanedTraceRefs = Number(orphan.rowCount ?? 0)
|
|
30
|
+
} catch { /* trace schema may be absent in early boot — no-op */ }
|
|
31
|
+
return { deleted: Number(result.rowCount ?? 0), cutoffMs, orphanedTraceRefs }
|
|
32
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
const DEFAULT_OPS_POLICY = {
|
|
6
|
+
features: {
|
|
7
|
+
temporalParser: false,
|
|
8
|
+
},
|
|
9
|
+
startup: {
|
|
10
|
+
// Startup catch-up disabled by default: was running inline embeddings
|
|
11
|
+
// ~5s after server start, causing perceptible lag right after user typed.
|
|
12
|
+
// Pending work is still handled by the regular 5-min cycle1 interval.
|
|
13
|
+
cycle1CatchUp: {
|
|
14
|
+
mode: 'off',
|
|
15
|
+
delayMs: 5000,
|
|
16
|
+
minPendingCandidates: 8,
|
|
17
|
+
requireDue: false,
|
|
18
|
+
},
|
|
19
|
+
cycle2CatchUp: {
|
|
20
|
+
mode: 'off',
|
|
21
|
+
delayMs: 5000,
|
|
22
|
+
requireDue: true,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
scheduler: {
|
|
26
|
+
checkIntervalMs: 60_000,
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function coercePositiveInt(value, fallback) {
|
|
31
|
+
const parsed = Number(value)
|
|
32
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallback
|
|
33
|
+
return Math.floor(parsed)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeBackfillWindow(value) {
|
|
37
|
+
const normalized = String(value ?? 'all').trim().toLowerCase()
|
|
38
|
+
if (['none', 'off', 'disabled', '0'].includes(normalized)) return 'none'
|
|
39
|
+
if (['1d', '1day', '1-day', '1 day', 'day', 'today'].includes(normalized)) return '1d'
|
|
40
|
+
if (['3d', '3days', '3-day', '3 day'].includes(normalized)) return '3d'
|
|
41
|
+
if (['7d', '7days', '7-day', '7 day', 'week'].includes(normalized)) return '7d'
|
|
42
|
+
if (['30d', '30days', '30-day', '30 day', 'month'].includes(normalized)) return '30d'
|
|
43
|
+
return 'all'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeCatchUpMode(value, fallback = 'light') {
|
|
47
|
+
const normalized = String(value ?? fallback).trim().toLowerCase()
|
|
48
|
+
if (['off', 'none', 'disabled'].includes(normalized)) return 'off'
|
|
49
|
+
if (['full', 'all', 'aggressive'].includes(normalized)) return 'full'
|
|
50
|
+
return 'light'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeBackfillScope(value) {
|
|
54
|
+
const normalized = String(value ?? 'all').trim().toLowerCase()
|
|
55
|
+
if (['workspace', 'project', 'current'].includes(normalized)) return 'workspace'
|
|
56
|
+
return 'all'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function envFlag(value, fallback = false) {
|
|
60
|
+
if (value == null || value === '') return fallback
|
|
61
|
+
const normalized = String(value).trim().toLowerCase()
|
|
62
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true
|
|
63
|
+
if (['0', 'false', 'no', 'off'].includes(normalized)) return false
|
|
64
|
+
return fallback
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function resolveBackfillSinceMs(windowValue, now = Date.now()) {
|
|
68
|
+
const normalized = normalizeBackfillWindow(windowValue)
|
|
69
|
+
if (normalized === '1d') return now - (1 * 24 * 60 * 60 * 1000)
|
|
70
|
+
if (normalized === '3d') return now - (3 * 24 * 60 * 60 * 1000)
|
|
71
|
+
if (normalized === '7d') return now - (7 * 24 * 60 * 60 * 1000)
|
|
72
|
+
if (normalized === '30d') return now - (30 * 24 * 60 * 60 * 1000)
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function countUnclassified(db) {
|
|
77
|
+
if (!db) return 0
|
|
78
|
+
try {
|
|
79
|
+
const row = (await db.query(`SELECT COUNT(*) c FROM entries WHERE chunk_root IS NULL`, [])).rows[0]
|
|
80
|
+
return Number(row?.c ?? 0)
|
|
81
|
+
} catch {
|
|
82
|
+
return 0
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function selectBackfillTranscripts({ sinceMs = null, limit = null, projectsRoot = null } = {}) {
|
|
87
|
+
const root = projectsRoot || path.join(os.homedir(), '.claude', 'projects')
|
|
88
|
+
if (!fs.existsSync(root)) return []
|
|
89
|
+
const files = []
|
|
90
|
+
for (const d of fs.readdirSync(root)) {
|
|
91
|
+
if (d.includes('tmp') || d.includes('cache') || d.includes('plugins')) continue
|
|
92
|
+
const full = path.join(root, d)
|
|
93
|
+
try {
|
|
94
|
+
for (const f of fs.readdirSync(full)) {
|
|
95
|
+
if (!f.endsWith('.jsonl') || f.startsWith('agent-')) continue
|
|
96
|
+
const fp = path.join(full, f)
|
|
97
|
+
let mtime
|
|
98
|
+
try { mtime = fs.statSync(fp).mtimeMs } catch { continue }
|
|
99
|
+
if (sinceMs != null && mtime < sinceMs) continue
|
|
100
|
+
files.push({ path: fp, mtime })
|
|
101
|
+
}
|
|
102
|
+
} catch {}
|
|
103
|
+
}
|
|
104
|
+
files.sort((a, b) => b.mtime - a.mtime)
|
|
105
|
+
const capped = (limit != null && Number(limit) > 0) ? files.slice(0, Number(limit)) : files
|
|
106
|
+
return capped.map(f => f.path).reverse()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const FULL_BACKFILL_MAX_ITERS = 30
|
|
110
|
+
const BACKFILL_CONCURRENCY = 3
|
|
111
|
+
|
|
112
|
+
export async function runFullBackfill(db, {
|
|
113
|
+
window = '7d',
|
|
114
|
+
scope = 'all',
|
|
115
|
+
limit = null,
|
|
116
|
+
config = {},
|
|
117
|
+
dataDir = null,
|
|
118
|
+
ingestTranscriptFile,
|
|
119
|
+
cwdFromTranscriptPath,
|
|
120
|
+
runCycle1,
|
|
121
|
+
runCycle2,
|
|
122
|
+
now = Date.now(),
|
|
123
|
+
projectsRoot = null,
|
|
124
|
+
} = {}) {
|
|
125
|
+
if (typeof ingestTranscriptFile !== 'function') {
|
|
126
|
+
throw new Error('runFullBackfill: ingestTranscriptFile required')
|
|
127
|
+
}
|
|
128
|
+
if (typeof runCycle1 !== 'function' || typeof runCycle2 !== 'function') {
|
|
129
|
+
throw new Error('runFullBackfill: runCycle1/runCycle2 required')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const normalizedWindow = normalizeBackfillWindow(window)
|
|
133
|
+
const normalizedScope = normalizeBackfillScope(scope)
|
|
134
|
+
const sinceMs = resolveBackfillSinceMs(normalizedWindow, now)
|
|
135
|
+
const selected = selectBackfillTranscripts({ sinceMs, limit, projectsRoot })
|
|
136
|
+
|
|
137
|
+
let ingested = 0
|
|
138
|
+
let cursor = 0
|
|
139
|
+
const workers = Array.from({ length: BACKFILL_CONCURRENCY }, async () => {
|
|
140
|
+
while (cursor < selected.length) {
|
|
141
|
+
const idx = cursor++
|
|
142
|
+
const fp = selected[idx]
|
|
143
|
+
try {
|
|
144
|
+
const cwd = typeof cwdFromTranscriptPath === 'function' ? cwdFromTranscriptPath(fp) : undefined
|
|
145
|
+
const n = Number(await ingestTranscriptFile(fp, { cwd }) ?? 0)
|
|
146
|
+
ingested += n
|
|
147
|
+
} catch (err) {
|
|
148
|
+
process.stderr.write(`[backfill] ingest failed (${fp}): ${err.message}\n`)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
await Promise.all(workers)
|
|
153
|
+
|
|
154
|
+
let cycle1Iters = 0
|
|
155
|
+
let prevUnclassified = await countUnclassified(db)
|
|
156
|
+
while (prevUnclassified > 0 && cycle1Iters < FULL_BACKFILL_MAX_ITERS) {
|
|
157
|
+
let result
|
|
158
|
+
try {
|
|
159
|
+
result = await runCycle1(db, config?.cycle1 || {}, {}, dataDir)
|
|
160
|
+
} catch (err) {
|
|
161
|
+
process.stderr.write(`[backfill] cycle1 error (iter=${cycle1Iters}): ${err.message}\n`)
|
|
162
|
+
break
|
|
163
|
+
}
|
|
164
|
+
cycle1Iters += 1
|
|
165
|
+
if (Number(result?.processed ?? 0) === 0) break
|
|
166
|
+
const nextUnclassified = await countUnclassified(db)
|
|
167
|
+
if (nextUnclassified >= prevUnclassified) break
|
|
168
|
+
prevUnclassified = nextUnclassified
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let promoted = 0
|
|
172
|
+
try {
|
|
173
|
+
const c2 = await runCycle2(db, config?.cycle2 || {}, {}, dataDir)
|
|
174
|
+
promoted = Number(c2?.promoted ?? 0)
|
|
175
|
+
} catch (err) {
|
|
176
|
+
process.stderr.write(`[backfill] cycle2 error: ${err.message}\n`)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const unclassified = await countUnclassified(db)
|
|
180
|
+
return {
|
|
181
|
+
window: normalizedWindow,
|
|
182
|
+
scope: normalizedScope,
|
|
183
|
+
files: selected.length,
|
|
184
|
+
ingested,
|
|
185
|
+
cycle1_iters: cycle1Iters,
|
|
186
|
+
promoted,
|
|
187
|
+
unclassified,
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Helper: scoped id recall query (used from index handleSearch id mode).
|
|
2
|
+
import { buildRecallScopeFilter } from './memory-recall-scope-filter.mjs'
|
|
3
|
+
import { recallReadQuery } from './memory-recall-read-query.mjs'
|
|
4
|
+
|
|
5
|
+
export async function fetchEntriesByIdsScoped(db, ids, scopeOptions) {
|
|
6
|
+
const { clause, params } = buildRecallScopeFilter(2, scopeOptions)
|
|
7
|
+
const { rows } = await recallReadQuery(
|
|
8
|
+
db,
|
|
9
|
+
`SELECT id, ts, role, content, session_id, source_turn, chunk_root, is_root,
|
|
10
|
+
element, category, summary, project_id, status, score, last_seen_at
|
|
11
|
+
FROM entries WHERE id = ANY($1::bigint[]) ${clause}`,
|
|
12
|
+
[ids, ...params],
|
|
13
|
+
)
|
|
14
|
+
return rows
|
|
15
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Recall read path: bounded query time via SET LOCAL (requires a transaction).
|
|
2
|
+
export async function recallReadQuery(db, sql, params) {
|
|
3
|
+
return db.transaction(async (tx) => {
|
|
4
|
+
await tx.query(`SET LOCAL statement_timeout = '30s'`)
|
|
5
|
+
return tx.query(sql, params)
|
|
6
|
+
})
|
|
7
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { VALID_CATEGORY } from './memory.mjs'
|
|
2
|
+
|
|
3
|
+
export function buildCategoryFilterClause(offset, categories, { tableAlias = '' } = {}) {
|
|
4
|
+
const cats = (Array.isArray(categories) ? categories : [categories])
|
|
5
|
+
.map(c => String(c ?? '').trim().toLowerCase())
|
|
6
|
+
.filter(c => VALID_CATEGORY.has(c))
|
|
7
|
+
if (cats.length === 0) return { clause: '', params: [] }
|
|
8
|
+
const outerRef = tableAlias || 'entries'
|
|
9
|
+
const p = `${outerRef}.`
|
|
10
|
+
const ph = cats.map((_, i) => `$${offset + i}`).join(', ')
|
|
11
|
+
const inner = `(
|
|
12
|
+
(${p}is_root = 1 AND ${p}category IN (${ph}))
|
|
13
|
+
OR (${p}is_root = 0 AND ${p}chunk_root IS NOT NULL AND ${p}chunk_root <> ${p}id AND EXISTS (
|
|
14
|
+
SELECT 1 FROM entries r WHERE r.id = ${p}chunk_root AND r.is_root = 1 AND r.category IN (${ph})
|
|
15
|
+
))
|
|
16
|
+
OR (${p}is_root = 0 AND (${p}chunk_root IS NULL OR ${p}chunk_root = ${p}id) AND ${p}category IN (${ph}))
|
|
17
|
+
)`
|
|
18
|
+
return { clause: `AND (${inner})`, params: [...cats] }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function buildRecallScopeFilter(offset, options = {}, tableAlias = '') {
|
|
22
|
+
const outerRef = tableAlias || 'entries'
|
|
23
|
+
const p = `${outerRef}.`
|
|
24
|
+
const clauses = [
|
|
25
|
+
`NOT (${p}is_root = 0 AND ${p}chunk_root IS NOT DISTINCT FROM ${p}id AND ${p}status IS NOT DISTINCT FROM 'archived')`,
|
|
26
|
+
]
|
|
27
|
+
const params = []
|
|
28
|
+
let next = offset
|
|
29
|
+
const tsFrom = Number.isFinite(Number(options.ts_from)) ? Number(options.ts_from) : null
|
|
30
|
+
const tsTo = Number.isFinite(Number(options.ts_to)) ? Number(options.ts_to) : null
|
|
31
|
+
if (tsFrom != null) { clauses.push(`${p}ts >= $${next++}`); params.push(tsFrom) }
|
|
32
|
+
if (tsTo != null) { clauses.push(`${p}ts <= $${next++}`); params.push(tsTo) }
|
|
33
|
+
const excludeStatuses = Array.isArray(options.excludeStatuses)
|
|
34
|
+
? options.excludeStatuses.filter(s => typeof s === 'string' && s.trim()).map(s => s.trim().toLowerCase())
|
|
35
|
+
: []
|
|
36
|
+
if (excludeStatuses.length > 0) {
|
|
37
|
+
const ph = excludeStatuses.map(() => `$${next++}`).join(', ')
|
|
38
|
+
const statusPred = `(${p}status IS NULL OR ${p}status NOT IN (${ph}))`
|
|
39
|
+
clauses.push(`(
|
|
40
|
+
(${p}is_root = 1 AND ${statusPred})
|
|
41
|
+
OR (${p}is_root = 0 AND ${p}chunk_root IS NOT NULL AND ${p}chunk_root <> ${p}id AND EXISTS (
|
|
42
|
+
SELECT 1 FROM entries r WHERE r.id = ${p}chunk_root AND r.is_root = 1
|
|
43
|
+
AND (r.status IS NULL OR r.status NOT IN (${ph}))
|
|
44
|
+
))
|
|
45
|
+
OR (${p}is_root = 0 AND (${p}chunk_root IS NULL OR ${p}chunk_root = ${p}id) AND ${statusPred})
|
|
46
|
+
)`)
|
|
47
|
+
params.push(...excludeStatuses)
|
|
48
|
+
}
|
|
49
|
+
const categories = (Array.isArray(options.category) ? options.category : [options.category])
|
|
50
|
+
.map(c => String(c ?? '').trim().toLowerCase())
|
|
51
|
+
.filter(c => VALID_CATEGORY.has(c))
|
|
52
|
+
if (categories.length > 0) {
|
|
53
|
+
const { clause: catClause, params: catParams } = buildCategoryFilterClause(next, categories, { tableAlias })
|
|
54
|
+
if (catClause) { clauses.push(catClause.replace(/^AND /, '')); params.push(...catParams); next += catParams.length }
|
|
55
|
+
}
|
|
56
|
+
const projectScope = typeof options.projectScope === 'string' ? options.projectScope : null
|
|
57
|
+
if (projectScope === 'common') clauses.push(`${p}project_id IS NULL`)
|
|
58
|
+
else if (projectScope && projectScope !== 'all') {
|
|
59
|
+
clauses.push(`(${p}project_id IS NULL OR ${p}project_id = $${next++})`)
|
|
60
|
+
params.push(projectScope)
|
|
61
|
+
}
|
|
62
|
+
return { clause: clauses.length > 0 ? `AND ${clauses.join(' AND ')}` : '', params }
|
|
63
|
+
}
|