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,112 @@
|
|
|
1
|
+
import { recallReadQuery } from './memory-recall-read-query.mjs'
|
|
2
|
+
|
|
3
|
+
const VALID_CATEGORIES_SET = new Set([
|
|
4
|
+
'rule', 'constraint', 'decision', 'fact', 'goal', 'preference', 'task', 'issue',
|
|
5
|
+
])
|
|
6
|
+
const VALID_STATUS_SET = new Set(['pending', 'active', 'archived'])
|
|
7
|
+
|
|
8
|
+
export async function retrieveEntries(db, filters = {}) {
|
|
9
|
+
const where = []
|
|
10
|
+
const params = []
|
|
11
|
+
|
|
12
|
+
// is_root filter (default: true)
|
|
13
|
+
const isRoot = filters.is_root === undefined ? true : Boolean(filters.is_root)
|
|
14
|
+
where.push(`is_root = $${params.length + 1}`)
|
|
15
|
+
params.push(isRoot ? 1 : 0)
|
|
16
|
+
|
|
17
|
+
if (filters.session_id != null) {
|
|
18
|
+
const sid = String(filters.session_id).trim()
|
|
19
|
+
if (sid) { where.push(`session_id = $${params.length + 1}`); params.push(sid) }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// projectScope filter: 'common' → project_id IS NULL only;
|
|
23
|
+
// specific slug → project_id IS NULL OR project_id = slug;
|
|
24
|
+
// 'all' or undefined → no filter (full pool).
|
|
25
|
+
if (filters.projectScope === 'common') {
|
|
26
|
+
where.push(`project_id IS NULL`)
|
|
27
|
+
} else if (typeof filters.projectScope === 'string' && filters.projectScope && filters.projectScope !== 'all') {
|
|
28
|
+
where.push(`(project_id IS NULL OR project_id = $${params.length + 1})`)
|
|
29
|
+
params.push(filters.projectScope)
|
|
30
|
+
}
|
|
31
|
+
// projectScope === 'all' or undefined → no filter
|
|
32
|
+
|
|
33
|
+
const tsFrom = Number(filters.ts_from)
|
|
34
|
+
if (Number.isFinite(tsFrom)) { where.push(`ts >= $${params.length + 1}`); params.push(tsFrom) }
|
|
35
|
+
const tsTo = Number(filters.ts_to)
|
|
36
|
+
if (Number.isFinite(tsTo)) { where.push(`ts <= $${params.length + 1}`); params.push(tsTo) }
|
|
37
|
+
|
|
38
|
+
if (filters.category != null) {
|
|
39
|
+
const cats = (Array.isArray(filters.category) ? filters.category : [filters.category])
|
|
40
|
+
.map(c => String(c).trim().toLowerCase())
|
|
41
|
+
.filter(c => VALID_CATEGORIES_SET.has(c))
|
|
42
|
+
if (cats.length > 0) {
|
|
43
|
+
const ph = cats.map((_, i) => `$${params.length + 1 + i}`).join(',')
|
|
44
|
+
where.push(`category IN (${ph})`)
|
|
45
|
+
params.push(...cats)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (filters.status != null) {
|
|
50
|
+
const statusVal = String(filters.status).trim().toLowerCase()
|
|
51
|
+
if (VALID_STATUS_SET.has(statusVal)) {
|
|
52
|
+
where.push(`status = $${params.length + 1}`)
|
|
53
|
+
params.push(statusVal)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// R11 reviewer H2: exclude archived leakage in temporal augment paths.
|
|
58
|
+
if (Array.isArray(filters.excludeStatuses) && filters.excludeStatuses.length > 0) {
|
|
59
|
+
const exc = filters.excludeStatuses
|
|
60
|
+
.map(s => String(s).trim().toLowerCase())
|
|
61
|
+
.filter(s => VALID_STATUS_SET.has(s))
|
|
62
|
+
if (exc.length > 0) {
|
|
63
|
+
const ph = exc.map((_, i) => `$${params.length + 1 + i}`).join(',')
|
|
64
|
+
where.push(`(status IS NULL OR status NOT IN (${ph}))`)
|
|
65
|
+
params.push(...exc)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// R11 reviewer M3: orphan raw chunks (chunk_root IS NULL) for narrow-window
|
|
70
|
+
// raw merging — prevents classified-member chunks from duplicating their root.
|
|
71
|
+
if (filters.chunkRootNull === true) {
|
|
72
|
+
where.push(`chunk_root IS NULL`)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const limit = Math.max(1, Math.min(500, Number(filters.limit ?? 50)))
|
|
76
|
+
const offset = Math.max(0, Number(filters.offset ?? 0))
|
|
77
|
+
const sort = String(filters.sort ?? 'importance').trim().toLowerCase()
|
|
78
|
+
const orderBy = sort === 'date'
|
|
79
|
+
? 'ts DESC, id DESC'
|
|
80
|
+
: 'score DESC NULLS LAST, ts DESC, id DESC'
|
|
81
|
+
|
|
82
|
+
params.push(limit, offset)
|
|
83
|
+
const sql = `SELECT id, ts, role, content, source_ref, session_id, source_turn,
|
|
84
|
+
chunk_root, is_root, element, category, summary, project_id,
|
|
85
|
+
status, score, last_seen_at
|
|
86
|
+
FROM entries
|
|
87
|
+
WHERE ${where.join(' AND ')}
|
|
88
|
+
ORDER BY ${orderBy}
|
|
89
|
+
LIMIT $${params.length - 1} OFFSET $${params.length}`
|
|
90
|
+
|
|
91
|
+
const rows = (await recallReadQuery(db, sql, params)).rows
|
|
92
|
+
|
|
93
|
+
if (filters.includeMembers && rows.length > 0) {
|
|
94
|
+
const rootIds = rows.map(r => r.id)
|
|
95
|
+
const memRes = (await recallReadQuery(
|
|
96
|
+
db,
|
|
97
|
+
`SELECT id, ts, role, content, session_id, source_turn, project_id, chunk_root
|
|
98
|
+
FROM entries WHERE chunk_root = ANY($1::bigint[]) AND is_root = 0
|
|
99
|
+
ORDER BY chunk_root, ts ASC, id ASC`,
|
|
100
|
+
[rootIds],
|
|
101
|
+
)).rows
|
|
102
|
+
const byRoot = new Map()
|
|
103
|
+
for (const m of memRes) {
|
|
104
|
+
const rid = Number(m.chunk_root)
|
|
105
|
+
if (!byRoot.has(rid)) byRoot.set(rid, [])
|
|
106
|
+
byRoot.get(rid).push({ id: m.id, ts: m.ts, role: m.role, content: m.content, session_id: m.session_id, source_turn: m.source_turn, project_id: m.project_id })
|
|
107
|
+
}
|
|
108
|
+
for (const r of rows) r.members = byRoot.get(Number(r.id)) || []
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return rows
|
|
112
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Per-category grade (base score ceiling) and decay rate.
|
|
2
|
+
//
|
|
3
|
+
// grade = baseline ceiling for the category (durability of knowledge type).
|
|
4
|
+
// decay = how fast score drops with age. 0 means immune to age-decay
|
|
5
|
+
// (rules never decay), higher means faster drop.
|
|
6
|
+
//
|
|
7
|
+
// Pairing: rules/constraints have high grade + low/zero decay (long-term),
|
|
8
|
+
// issues/tasks have low grade + high decay (transient by nature).
|
|
9
|
+
//
|
|
10
|
+
// score = grade * 1 / (1 + ageDays*rate/30)^0.3
|
|
11
|
+
//
|
|
12
|
+
// At ageDays=0: score = grade.
|
|
13
|
+
// As ageDays → ∞: score → 0.
|
|
14
|
+
// rate=0 disables decay entirely → score stays at grade forever.
|
|
15
|
+
export const CATEGORY_GRADE = {
|
|
16
|
+
rule: 2.0,
|
|
17
|
+
constraint: 1.9,
|
|
18
|
+
decision: 1.8,
|
|
19
|
+
fact: 1.6,
|
|
20
|
+
goal: 1.5,
|
|
21
|
+
preference: 1.4,
|
|
22
|
+
task: 1.1,
|
|
23
|
+
issue: 1.0,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const CATEGORY_DECAY = {
|
|
27
|
+
rule: 0.0,
|
|
28
|
+
constraint: 0.06,
|
|
29
|
+
decision: 0.15,
|
|
30
|
+
fact: 0.25,
|
|
31
|
+
goal: 0.30,
|
|
32
|
+
preference: 0.35,
|
|
33
|
+
task: 0.45,
|
|
34
|
+
issue: 0.50,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Smooth exponential freshness factor — used by hybrid retrieval ranking
|
|
38
|
+
// (separate from computeEntryScore, which is the persisted column score).
|
|
39
|
+
// Returns value in [0.50, 1.60].
|
|
40
|
+
// ageH=0 → 1.60, ageH=6 → ~1.39, ageH=24 → ~1.08, ageH=72 → ~0.85,
|
|
41
|
+
// ageH=168 → ~0.68, ageH=720 → ~0.50.
|
|
42
|
+
export function freshnessFactor(ts, nowMs = Date.now()) {
|
|
43
|
+
const ts_ = Number(ts ?? 0)
|
|
44
|
+
if (!Number.isFinite(ts_) || ts_ <= 0) return 0.85
|
|
45
|
+
const ageH = Math.max(0, (nowMs - ts_) / 3_600_000)
|
|
46
|
+
const raw = 0.50 + 1.10 * Math.exp(-ageH / 55)
|
|
47
|
+
return Math.max(0.50, Math.min(1.60, raw))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Persisted entry score = grade * decay-curve(ageDays, category-specific rate).
|
|
52
|
+
*
|
|
53
|
+
* Returns null on unknown category or non-finite timestamps.
|
|
54
|
+
* rate=0 (rule) yields score = grade with no time component.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} category
|
|
57
|
+
* @param {number|string} lastSeenAt — ms timestamp
|
|
58
|
+
* @param {number} nowMs
|
|
59
|
+
* @returns {number|null}
|
|
60
|
+
*/
|
|
61
|
+
export function computeEntryScore(category, lastSeenAt, nowMs) {
|
|
62
|
+
const grade = CATEGORY_GRADE[String(category ?? '').toLowerCase()]
|
|
63
|
+
const rate = CATEGORY_DECAY[String(category ?? '').toLowerCase()]
|
|
64
|
+
if (grade == null || rate == null) return null
|
|
65
|
+
if (!Number.isFinite(Number(nowMs))) return null
|
|
66
|
+
const anchor = Number.isFinite(Number(lastSeenAt)) ? Number(lastSeenAt) : Number(nowMs)
|
|
67
|
+
const ageDays = Math.max(0, (Number(nowMs) - anchor) / 86_400_000)
|
|
68
|
+
const adjustedAge = ageDays * rate
|
|
69
|
+
const decay = 1 / Math.pow(1 + adjustedAge / 30, 0.3)
|
|
70
|
+
return Math.min(grade, grade * decay)
|
|
71
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cleanMemoryText,
|
|
3
|
+
} from './memory-extraction.mjs'
|
|
4
|
+
|
|
5
|
+
const MEMORY_TOKEN_STOPWORDS = new Set([
|
|
6
|
+
'a', 'an', 'and', 'are', 'as', 'at', 'be', 'but', 'by', 'did', 'do', 'does', 'for', 'from',
|
|
7
|
+
'how', 'i', 'if', 'in', 'is', 'it', 'me', 'my', 'of', 'on', 'or', 'our', 'so', 'that', 'the',
|
|
8
|
+
'their', 'them', 'they', 'this', 'to', 'was', 'we', 'were', 'what', 'when', 'who', 'why', 'you',
|
|
9
|
+
'your', 'unless', 'with',
|
|
10
|
+
'user', 'assistant', 'requested', 'request', 'asked', 'ask', 'stated', 'state', 'reported', 'report',
|
|
11
|
+
'mentioned', 'mention', 'clarified', 'clarify', 'explicitly', 'currently',
|
|
12
|
+
'사용자', '유저', '요청', '질문', '답변', '언급', '말씀', '설명', '보고', '무슨', '뭐야', '했지', 'user', 'asks', 'asked', 'request', 'requested', 'question', 'answer', 'reply', 'said', 'mentioned', 'explained', 'reported', 'what', 'huh',
|
|
13
|
+
])
|
|
14
|
+
|
|
15
|
+
export function normalizeMemoryToken(token) {
|
|
16
|
+
let normalized = String(token ?? '').trim().toLowerCase()
|
|
17
|
+
if (!normalized) return ''
|
|
18
|
+
|
|
19
|
+
// Korean suffix stripping: basic particles + compound endings
|
|
20
|
+
if (/[\uAC00-\uD7AF]/.test(normalized) && normalized.length > 2) {
|
|
21
|
+
const stripped = normalized
|
|
22
|
+
.replace(/(했었지|했더라|됐었나|됐던가|했는지|였는지|인건가|하려면|에서는|이라서|였더라|에서도|이었지|으로도|거였지|한건지|이었나)$/u, '')
|
|
23
|
+
.replace(/(했던|했지|됐던|됐지|하게|되던|이라|에서|으로|하는|없는|있는|었던|하자|않게|할때|인지|인데|인건|이고|보다|처럼|까지|부터|마다|밖에|없이)$/u, '')
|
|
24
|
+
.replace(/(은|는|이|가|을|를|랑|과|와|도|에|의|로|만|며|나|고|서|자|요)$/u, '')
|
|
25
|
+
if (stripped.length >= 2) normalized = stripped
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (/^[a-z][a-z0-9_-]+$/i.test(normalized)) {
|
|
29
|
+
if (normalized.length > 5 && normalized.endsWith('ing')) normalized = normalized.slice(0, -3)
|
|
30
|
+
else if (normalized.length > 4 && normalized.endsWith('ed')) normalized = normalized.slice(0, -2)
|
|
31
|
+
else if (normalized.length > 4 && normalized.endsWith('es')) normalized = normalized.slice(0, -2)
|
|
32
|
+
else if (normalized.length > 3 && normalized.endsWith('s')) normalized = normalized.slice(0, -1)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return normalized
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function tokenizeMemoryText(text) {
|
|
39
|
+
return cleanMemoryText(text)
|
|
40
|
+
.toLowerCase()
|
|
41
|
+
.split(/[^\p{L}\p{N}_]+/u)
|
|
42
|
+
.map(token => normalizeMemoryToken(token))
|
|
43
|
+
.filter(token => token.length >= 2)
|
|
44
|
+
.filter(token => !MEMORY_TOKEN_STOPWORDS.has(token))
|
|
45
|
+
.slice(0, 24)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function buildFtsQuery(text) {
|
|
49
|
+
const tokens = tokenizeMemoryText(text)
|
|
50
|
+
if (tokens.length === 0) return ''
|
|
51
|
+
// Include 2-char Korean tokens (they carry meaning unlike 2-char English)
|
|
52
|
+
const ftsTokens = [...new Set(tokens)].filter(t => t.length >= 3 || (t.length === 2 && /[\uAC00-\uD7AF]/.test(t)))
|
|
53
|
+
if (ftsTokens.length === 0) return ''
|
|
54
|
+
// websearch_to_tsquery handles tokenization + OR/AND/quoting itself; pass plain tokens space-joined.
|
|
55
|
+
return ftsTokens.map(t => t.replace(/["']/g, '')).filter(t => t.length > 0).join(' ')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
// Native-PG-backed memory store. Schema, helpers, and lifecycle.
|
|
2
|
+
|
|
3
|
+
import { ensurePgInstance, closePgInstance, withSchemaBootstrapLock } from './pg/adapter.mjs'
|
|
4
|
+
import { mkdirSync } from 'fs'
|
|
5
|
+
import { resolve } from 'path'
|
|
6
|
+
import { cleanMemoryText } from './memory-extraction.mjs'
|
|
7
|
+
|
|
8
|
+
const dbs = new Map()
|
|
9
|
+
const opening = new Map()
|
|
10
|
+
|
|
11
|
+
export { cleanMemoryText }
|
|
12
|
+
|
|
13
|
+
export const VALID_CATEGORY = new Set([
|
|
14
|
+
'rule', 'constraint', 'decision', 'fact', 'goal', 'preference', 'task', 'issue',
|
|
15
|
+
])
|
|
16
|
+
|
|
17
|
+
export async function init(db, dims) {
|
|
18
|
+
const dimCount = Number(dims)
|
|
19
|
+
if (!Number.isInteger(dimCount) || dimCount <= 0) {
|
|
20
|
+
throw new Error(`init: dims must be a positive integer, got ${dims}`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Extensions are created once by pg-adapter.bootstrapInstance; skip here.
|
|
24
|
+
|
|
25
|
+
// Status as a real ENUM type — DB-level enforcement, B-tree friendly.
|
|
26
|
+
// PG has no CREATE TYPE IF NOT EXISTS; guard via pg_type lookup so a partial
|
|
27
|
+
// bootstrap (crash after CREATE TYPE but before boot.schema_bootstrap_complete)
|
|
28
|
+
// can re-run init() on the next boot without colliding on the existing type.
|
|
29
|
+
await db.exec(`
|
|
30
|
+
DO $$
|
|
31
|
+
BEGIN
|
|
32
|
+
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'entry_status') THEN
|
|
33
|
+
CREATE TYPE entry_status AS ENUM ('pending', 'active', 'archived');
|
|
34
|
+
END IF;
|
|
35
|
+
END
|
|
36
|
+
$$
|
|
37
|
+
`)
|
|
38
|
+
|
|
39
|
+
// Per-category score parameters (lookup table for the score function).
|
|
40
|
+
await db.exec(`
|
|
41
|
+
CREATE TABLE IF NOT EXISTS category_score_params (
|
|
42
|
+
category TEXT PRIMARY KEY,
|
|
43
|
+
grade REAL NOT NULL,
|
|
44
|
+
decay REAL NOT NULL
|
|
45
|
+
)
|
|
46
|
+
`)
|
|
47
|
+
await db.query(`
|
|
48
|
+
INSERT INTO category_score_params(category, grade, decay) VALUES
|
|
49
|
+
('rule', 2.0, 0.0),
|
|
50
|
+
('constraint', 1.9, 0.06),
|
|
51
|
+
('decision', 1.8, 0.15),
|
|
52
|
+
('fact', 1.6, 0.25),
|
|
53
|
+
('goal', 1.5, 0.30),
|
|
54
|
+
('preference', 1.4, 0.35),
|
|
55
|
+
('task', 1.1, 0.45),
|
|
56
|
+
('issue', 1.0, 0.50)
|
|
57
|
+
ON CONFLICT (category) DO NOTHING
|
|
58
|
+
`)
|
|
59
|
+
|
|
60
|
+
// SQL function mirrors src/memory/lib/memory-score.mjs computeEntryScore.
|
|
61
|
+
// STABLE (not IMMUTABLE) because the function reads category_score_params.
|
|
62
|
+
// IMMUTABLE would let the planner cache results across rows where params
|
|
63
|
+
// could legitimately differ if the table is updated.
|
|
64
|
+
await db.exec(`
|
|
65
|
+
CREATE OR REPLACE FUNCTION compute_entry_score(
|
|
66
|
+
category_p TEXT,
|
|
67
|
+
last_seen_at_p BIGINT,
|
|
68
|
+
now_ms_p BIGINT
|
|
69
|
+
) RETURNS REAL LANGUAGE sql STABLE AS $$
|
|
70
|
+
SELECT CASE
|
|
71
|
+
WHEN p.grade IS NULL OR last_seen_at_p IS NULL OR now_ms_p IS NULL THEN NULL::REAL
|
|
72
|
+
WHEN p.decay = 0 THEN p.grade
|
|
73
|
+
ELSE LEAST(
|
|
74
|
+
p.grade,
|
|
75
|
+
p.grade / POWER(
|
|
76
|
+
1 + (GREATEST(0, (now_ms_p - last_seen_at_p)) / 86400000.0) * p.decay / 30,
|
|
77
|
+
0.3
|
|
78
|
+
)
|
|
79
|
+
)::REAL
|
|
80
|
+
END
|
|
81
|
+
FROM category_score_params p
|
|
82
|
+
WHERE p.category = category_p
|
|
83
|
+
$$
|
|
84
|
+
`)
|
|
85
|
+
|
|
86
|
+
await db.exec(`
|
|
87
|
+
CREATE TABLE IF NOT EXISTS entries (
|
|
88
|
+
id BIGSERIAL PRIMARY KEY,
|
|
89
|
+
ts BIGINT NOT NULL,
|
|
90
|
+
role TEXT NOT NULL,
|
|
91
|
+
content TEXT NOT NULL,
|
|
92
|
+
source_ref TEXT NOT NULL UNIQUE,
|
|
93
|
+
session_id TEXT,
|
|
94
|
+
project_id TEXT,
|
|
95
|
+
source_turn INTEGER,
|
|
96
|
+
chunk_root BIGINT REFERENCES entries(id) ON DELETE SET NULL,
|
|
97
|
+
is_root SMALLINT NOT NULL DEFAULT 0,
|
|
98
|
+
element TEXT,
|
|
99
|
+
category TEXT,
|
|
100
|
+
summary TEXT,
|
|
101
|
+
core_summary TEXT,
|
|
102
|
+
status entry_status,
|
|
103
|
+
score REAL,
|
|
104
|
+
last_seen_at BIGINT,
|
|
105
|
+
reviewed_at BIGINT,
|
|
106
|
+
promoted_at BIGINT,
|
|
107
|
+
error_count INTEGER NOT NULL DEFAULT 0,
|
|
108
|
+
embedding halfvec(${dimCount}),
|
|
109
|
+
summary_hash TEXT,
|
|
110
|
+
search_tsv tsvector GENERATED ALWAYS AS (
|
|
111
|
+
setweight(to_tsvector('simple', coalesce(element, '')), 'A') ||
|
|
112
|
+
setweight(to_tsvector('simple', coalesce(summary, '')), 'B') ||
|
|
113
|
+
setweight(to_tsvector('simple', coalesce(content, '')), 'C') ||
|
|
114
|
+
setweight(to_tsvector('english', coalesce(element, '')), 'A') ||
|
|
115
|
+
setweight(to_tsvector('english', coalesce(summary, '')), 'B') ||
|
|
116
|
+
setweight(to_tsvector('english', coalesce(content, '')), 'C')
|
|
117
|
+
) STORED
|
|
118
|
+
)
|
|
119
|
+
`)
|
|
120
|
+
|
|
121
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_entries_chunk_root ON entries(chunk_root) WHERE chunk_root IS NOT NULL`)
|
|
122
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_entries_ts_desc ON entries(ts DESC)`)
|
|
123
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_entries_session_ts ON entries(session_id, ts DESC) WHERE session_id IS NOT NULL`)
|
|
124
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_entries_root_status_score ON entries(status, score DESC) WHERE is_root = 1`)
|
|
125
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_entries_root_category ON entries(category, status) WHERE is_root = 1`)
|
|
126
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_entries_pending ON entries(ts DESC, id DESC) WHERE chunk_root IS NULL AND session_id IS NOT NULL`)
|
|
127
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_roots_active ON entries(status, last_seen_at ASC, score DESC) WHERE is_root = 1 AND status = 'active'`)
|
|
128
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_entries_project ON entries(project_id) WHERE project_id IS NOT NULL`)
|
|
129
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_entries_reviewed_at ON entries(reviewed_at ASC) WHERE is_root = 1`)
|
|
130
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_entries_phase_sweep ON entries(status, is_root, error_count, reviewed_at, id)`)
|
|
131
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_entries_promoted_at ON entries(promoted_at) WHERE promoted_at IS NOT NULL`)
|
|
132
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_entries_tsv ON entries USING GIN (search_tsv)`)
|
|
133
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_entries_content_trgm ON entries USING GIN (content gin_trgm_ops) WHERE is_root = 1`)
|
|
134
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_entries_element_trgm ON entries USING GIN (element gin_trgm_ops) WHERE is_root = 1 AND element IS NOT NULL`)
|
|
135
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_entries_embedding_hnsw ON entries USING hnsw (embedding halfvec_cosine_ops) WHERE is_root = 1 AND embedding IS NOT NULL`)
|
|
136
|
+
|
|
137
|
+
// BEFORE INSERT/UPDATE trigger keeps score in sync with category + last_seen_at
|
|
138
|
+
// automatically; cycle code no longer needs to UPDATE entries SET score = ...
|
|
139
|
+
await db.exec(`
|
|
140
|
+
CREATE OR REPLACE FUNCTION trg_entry_score_recalc() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
141
|
+
BEGIN
|
|
142
|
+
IF NEW.is_root = 1 AND NEW.category IS NOT NULL THEN
|
|
143
|
+
-- NOW()-to-ms conversion is intentional schema-level work; the
|
|
144
|
+
-- "no EXTRACT(EPOCH …)" rule applies to ms-stored BIGINT timestamp
|
|
145
|
+
-- COLUMNS, not to the trigger reading the current wall clock.
|
|
146
|
+
NEW.score := compute_entry_score(
|
|
147
|
+
NEW.category,
|
|
148
|
+
COALESCE(NEW.last_seen_at, NEW.ts),
|
|
149
|
+
(EXTRACT(EPOCH FROM NOW()) * 1000)::BIGINT
|
|
150
|
+
);
|
|
151
|
+
END IF;
|
|
152
|
+
RETURN NEW;
|
|
153
|
+
END;
|
|
154
|
+
$$
|
|
155
|
+
`)
|
|
156
|
+
await db.exec(`DROP TRIGGER IF EXISTS trg_entries_score ON entries`)
|
|
157
|
+
await db.exec(`
|
|
158
|
+
CREATE TRIGGER trg_entries_score
|
|
159
|
+
BEFORE INSERT OR UPDATE OF category, last_seen_at, promoted_at, is_root ON entries
|
|
160
|
+
FOR EACH ROW
|
|
161
|
+
EXECUTE FUNCTION trg_entry_score_recalc()
|
|
162
|
+
`)
|
|
163
|
+
|
|
164
|
+
await db.exec(`
|
|
165
|
+
CREATE OR REPLACE FUNCTION trg_entry_embedding_invalidate() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
166
|
+
BEGIN
|
|
167
|
+
IF NEW.is_root = 1 AND (
|
|
168
|
+
NEW.content IS DISTINCT FROM OLD.content OR
|
|
169
|
+
NEW.summary IS DISTINCT FROM OLD.summary OR
|
|
170
|
+
NEW.element IS DISTINCT FROM OLD.element
|
|
171
|
+
) THEN
|
|
172
|
+
NEW.embedding := NULL;
|
|
173
|
+
NEW.summary_hash := NULL;
|
|
174
|
+
END IF;
|
|
175
|
+
RETURN NEW;
|
|
176
|
+
END;
|
|
177
|
+
$$
|
|
178
|
+
`)
|
|
179
|
+
await db.exec(`DROP TRIGGER IF EXISTS trg_entries_embedding_invalidate ON entries`)
|
|
180
|
+
await db.exec(`
|
|
181
|
+
CREATE TRIGGER trg_entries_embedding_invalidate
|
|
182
|
+
BEFORE UPDATE OF content, summary, element ON entries
|
|
183
|
+
FOR EACH ROW EXECUTE FUNCTION trg_entry_embedding_invalidate()
|
|
184
|
+
`)
|
|
185
|
+
|
|
186
|
+
await db.exec(`
|
|
187
|
+
CREATE TABLE IF NOT EXISTS core_entries (
|
|
188
|
+
id BIGSERIAL PRIMARY KEY,
|
|
189
|
+
element TEXT NOT NULL,
|
|
190
|
+
summary TEXT NOT NULL,
|
|
191
|
+
category TEXT NOT NULL,
|
|
192
|
+
project_id TEXT,
|
|
193
|
+
embedding halfvec(${dimCount}),
|
|
194
|
+
created_at BIGINT NOT NULL,
|
|
195
|
+
updated_at BIGINT NOT NULL
|
|
196
|
+
)
|
|
197
|
+
`)
|
|
198
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS core_entries_project_idx ON core_entries(project_id)`)
|
|
199
|
+
await db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS core_entries_unique_proj_elem ON core_entries (project_id, element) NULLS NOT DISTINCT`)
|
|
200
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS core_entries_embedding_hnsw ON core_entries USING hnsw (embedding halfvec_cosine_ops) WHERE embedding IS NOT NULL`)
|
|
201
|
+
|
|
202
|
+
await db.exec(`
|
|
203
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
204
|
+
key TEXT PRIMARY KEY,
|
|
205
|
+
value JSONB NOT NULL
|
|
206
|
+
)
|
|
207
|
+
`)
|
|
208
|
+
|
|
209
|
+
// Operational view — used by /health and dashboards. One round-trip,
|
|
210
|
+
// covers the metrics that previously needed 6+ COUNT queries.
|
|
211
|
+
await db.exec(`
|
|
212
|
+
CREATE OR REPLACE VIEW v_cycle_state AS
|
|
213
|
+
SELECT
|
|
214
|
+
COUNT(*) FILTER (WHERE is_root = 1) AS roots,
|
|
215
|
+
COUNT(*) FILTER (WHERE is_root = 1 AND status = 'pending') AS pending,
|
|
216
|
+
COUNT(*) FILTER (WHERE is_root = 1 AND status = 'active') AS active,
|
|
217
|
+
COUNT(*) FILTER (WHERE is_root = 1 AND status = 'archived') AS archived,
|
|
218
|
+
COUNT(*) FILTER (WHERE chunk_root IS NULL) AS unclassified,
|
|
219
|
+
COUNT(*) AS total
|
|
220
|
+
FROM entries
|
|
221
|
+
`)
|
|
222
|
+
|
|
223
|
+
// Hot active set — recall hot path uses the materialized copy. Refresh hook
|
|
224
|
+
// is owned by cycle2 (after promotion/archival). Created WITH NO DATA so
|
|
225
|
+
// bootstrap is fast; first refresh happens on the first cycle2 run.
|
|
226
|
+
await db.exec(`
|
|
227
|
+
CREATE MATERIALIZED VIEW IF NOT EXISTS mv_hot_active AS
|
|
228
|
+
SELECT id, element, summary, category, status, score, last_seen_at, promoted_at,
|
|
229
|
+
project_id, embedding, search_tsv
|
|
230
|
+
FROM entries
|
|
231
|
+
WHERE is_root = 1 AND status = 'active' AND embedding IS NOT NULL
|
|
232
|
+
WITH NO DATA
|
|
233
|
+
`)
|
|
234
|
+
await db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS mv_hot_active_id ON mv_hot_active(id)`)
|
|
235
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS mv_hot_active_hnsw ON mv_hot_active USING hnsw (embedding halfvec_cosine_ops)`)
|
|
236
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS mv_hot_active_tsv ON mv_hot_active USING GIN (search_tsv)`)
|
|
237
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS mv_hot_active_score ON mv_hot_active(score DESC)`)
|
|
238
|
+
|
|
239
|
+
await db.query(
|
|
240
|
+
`INSERT INTO meta(key, value) VALUES ($1, $2::jsonb)
|
|
241
|
+
ON CONFLICT(key) DO UPDATE SET value = EXCLUDED.value`,
|
|
242
|
+
['embedding.current_dims', JSON.stringify(dimCount)],
|
|
243
|
+
)
|
|
244
|
+
await db.query(
|
|
245
|
+
`INSERT INTO meta(key, value) VALUES ($1, $2::jsonb)
|
|
246
|
+
ON CONFLICT(key) DO UPDATE SET value = EXCLUDED.value`,
|
|
247
|
+
['boot.schema_bootstrap_complete', JSON.stringify('1')],
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Validate that the halfvec column dimension stored in the DB matches
|
|
252
|
+
// dimCount from the current model config. Call after schema is confirmed
|
|
253
|
+
// complete and before any embedding operations.
|
|
254
|
+
export async function validateEmbeddingDims(db, dimCount) {
|
|
255
|
+
const r = await db.query(`
|
|
256
|
+
SELECT atttypmod
|
|
257
|
+
FROM pg_attribute a
|
|
258
|
+
JOIN pg_class c ON c.oid = a.attrelid
|
|
259
|
+
WHERE c.relname = 'entries'
|
|
260
|
+
AND a.attname = 'embedding'
|
|
261
|
+
AND a.attnum > 0
|
|
262
|
+
AND NOT a.attisdropped
|
|
263
|
+
`)
|
|
264
|
+
const row = r.rows[0]
|
|
265
|
+
if (!row) return // column absent — pre-schema DB; bootstrapSchema will handle
|
|
266
|
+
// pgvector halfvec stores dimension as atttypmod directly (unlike varchar which uses dims+4).
|
|
267
|
+
const colDims = row.atttypmod
|
|
268
|
+
if (colDims !== dimCount) {
|
|
269
|
+
throw new Error(
|
|
270
|
+
`Embedding dimension mismatch: DB column halfvec(${colDims}) vs model config ${dimCount} dims. ` +
|
|
271
|
+
`Reconfigure the embedding model or rebuild the memory store before booting.`
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export async function ensureCurrentSchemaExtensions(db, dims) {
|
|
277
|
+
// core_entries gained an embedding column for cross-table semantic dedup
|
|
278
|
+
// between user-curated rows and cycle2-promoted entries. ALTER + index are
|
|
279
|
+
// idempotent and define the current runtime schema.
|
|
280
|
+
if (Number.isInteger(dims) && dims > 0) {
|
|
281
|
+
await db.exec(`ALTER TABLE core_entries ADD COLUMN IF NOT EXISTS embedding halfvec(${dims})`)
|
|
282
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS core_entries_embedding_hnsw ON core_entries USING hnsw (embedding halfvec_cosine_ops) WHERE embedding IS NOT NULL`)
|
|
283
|
+
}
|
|
284
|
+
await db.exec(`ALTER TABLE entries ADD COLUMN IF NOT EXISTS core_summary text`)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
// Dedupe core_entries before creating the unique index — keeps the row with
|
|
289
|
+
// the most recent updated_at (id breaks ties), drops the rest.
|
|
290
|
+
const dedupe = await db.query(`
|
|
291
|
+
WITH ranked AS (
|
|
292
|
+
SELECT id,
|
|
293
|
+
row_number() OVER (
|
|
294
|
+
PARTITION BY project_id, element
|
|
295
|
+
ORDER BY updated_at DESC NULLS LAST, id DESC
|
|
296
|
+
) AS rn
|
|
297
|
+
FROM core_entries
|
|
298
|
+
), deleted AS (
|
|
299
|
+
DELETE FROM core_entries c
|
|
300
|
+
USING ranked r
|
|
301
|
+
WHERE c.id = r.id AND r.rn > 1
|
|
302
|
+
RETURNING c.id
|
|
303
|
+
)
|
|
304
|
+
SELECT count(*)::int AS n FROM deleted
|
|
305
|
+
`)
|
|
306
|
+
const deduped = Number(dedupe.rows?.[0]?.n ?? 0)
|
|
307
|
+
if (deduped > 0) {
|
|
308
|
+
process.stderr.write(`[memory] ensureCurrentSchemaExtensions: removed ${deduped} duplicate core_entries before unique index creation\n`)
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
await db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS core_entries_unique_proj_elem ON core_entries (project_id, element) NULLS NOT DISTINCT`)
|
|
312
|
+
} catch (err) {
|
|
313
|
+
process.stderr.write(`[memory] ensureCurrentSchemaExtensions: core_entries_unique_proj_elem creation failed — duplicate rows must be deduplicated before this index can be created: ${err?.message || err}\n`)
|
|
314
|
+
throw err
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export async function openDatabase(dataDir, dims) {
|
|
319
|
+
const key = resolve(dataDir)
|
|
320
|
+
|
|
321
|
+
// Fast path — already resolved.
|
|
322
|
+
if (dbs.get(key)) return dbs.get(key)
|
|
323
|
+
|
|
324
|
+
// Dedupe concurrent callers — return the in-flight Promise if one exists.
|
|
325
|
+
if (opening.has(key)) return opening.get(key)
|
|
326
|
+
|
|
327
|
+
const promise = (async () => {
|
|
328
|
+
mkdirSync(key, { recursive: true })
|
|
329
|
+
|
|
330
|
+
const { db, pool } = await ensurePgInstance(dataDir, { schema: 'memory' })
|
|
331
|
+
|
|
332
|
+
if (!(await isBootstrapComplete(db))) {
|
|
333
|
+
// Serialize the schema/CREATE TYPE bootstrap across concurrent first-boot
|
|
334
|
+
// processes with a cluster-global advisory lock. Re-check completion once
|
|
335
|
+
// the lock is held (double-checked locking) so a worker that lost the
|
|
336
|
+
// race skips the redundant DDL instead of re-running init().
|
|
337
|
+
await withSchemaBootstrapLock(pool, async () => {
|
|
338
|
+
if (!(await isBootstrapComplete(db))) {
|
|
339
|
+
await init(db, dims)
|
|
340
|
+
}
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
await ensureCurrentSchemaExtensions(db, Number(dims))
|
|
344
|
+
await validateEmbeddingDims(db, Number(dims))
|
|
345
|
+
|
|
346
|
+
dbs.set(key, db)
|
|
347
|
+
return db
|
|
348
|
+
})()
|
|
349
|
+
|
|
350
|
+
opening.set(key, promise)
|
|
351
|
+
try {
|
|
352
|
+
return await promise
|
|
353
|
+
} finally {
|
|
354
|
+
opening.delete(key)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function getDatabase(dataDir) {
|
|
359
|
+
if (!dataDir) return null
|
|
360
|
+
const key = resolve(dataDir)
|
|
361
|
+
return dbs.get(key) ?? null
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export async function closeDatabase(dataDir) {
|
|
365
|
+
const key = resolve(dataDir)
|
|
366
|
+
const db = dbs.get(key)
|
|
367
|
+
if (!db) return
|
|
368
|
+
try { await db.close() } catch {}
|
|
369
|
+
dbs.delete(key)
|
|
370
|
+
// Evict pg-adapter's instance cache too: db.close() ends the pool, but the
|
|
371
|
+
// adapter still holds `instances.get(key)` pointing at the ended pool. A
|
|
372
|
+
// same-process reopen would then return the dead handle. closePgInstance
|
|
373
|
+
// drops the cache entry (and re-ends the pool, which is a safe no-op on
|
|
374
|
+
// an already-ended pool) so the next ensurePgInstance rebuilds fresh.
|
|
375
|
+
try { await closePgInstance(dataDir, { schema: 'memory' }) } catch {}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export async function isBootstrapComplete(db) {
|
|
379
|
+
try {
|
|
380
|
+
const r = await db.query(`SELECT 1 FROM meta WHERE key = 'boot.schema_bootstrap_complete'`)
|
|
381
|
+
return r.rows.length > 0
|
|
382
|
+
} catch {
|
|
383
|
+
return false
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Returns the raw JSON-encoded string stored in meta.value. Callers JSON.parse
|
|
388
|
+
// it themselves; preserves API parity with the prior TEXT column.
|
|
389
|
+
export async function getMetaValue(db, key, fallback = null) {
|
|
390
|
+
try {
|
|
391
|
+
const r = await db.query(`SELECT value::text AS v FROM meta WHERE key = $1`, [key])
|
|
392
|
+
if (r.rows.length === 0) return fallback
|
|
393
|
+
return r.rows[0].v ?? fallback
|
|
394
|
+
} catch {
|
|
395
|
+
return fallback
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Caller passes a JSON-encoded string (e.g. JSON.stringify(obj) or a quoted
|
|
400
|
+
// scalar like '"v1"'). Stored verbatim into the JSONB column.
|
|
401
|
+
export async function setMetaValue(db, key, value) {
|
|
402
|
+
await db.query(
|
|
403
|
+
`INSERT INTO meta(key, value) VALUES ($1, $2::jsonb)
|
|
404
|
+
ON CONFLICT(key) DO UPDATE SET value = EXCLUDED.value`,
|
|
405
|
+
[key, value == null ? 'null' : String(value)],
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function embeddingToSql(arr) {
|
|
410
|
+
if (!arr || !Array.isArray(arr)) return null
|
|
411
|
+
return `[${arr.map((n) => Number(n).toFixed(6)).join(',')}]`
|
|
412
|
+
}
|