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,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-ipc.mjs — IPC client for bridge LLM calls from the memory worker.
|
|
3
|
+
*
|
|
4
|
+
* The memory worker runs in its own fork, while the bridge / provider
|
|
5
|
+
* registry lives in-process in the parent server. cycle1 / cycle2 can't
|
|
6
|
+
* call makeBridgeLlm() locally (provider map is empty here), so we route
|
|
7
|
+
* every LLM call over IPC:
|
|
8
|
+
*
|
|
9
|
+
* memory → parent : { type: 'agent_ipc_request', callId, tool, params }
|
|
10
|
+
* parent → memory : { type: 'agent_ipc_response', callId, ok, result | error }
|
|
11
|
+
*
|
|
12
|
+
* Uses a module-level pending map keyed by callId. Parent-side handler
|
|
13
|
+
* lives in server.mjs spawnWorker's message listener.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const pending = new Map()
|
|
17
|
+
let listenerInstalled = false
|
|
18
|
+
let _idSeq = 0
|
|
19
|
+
|
|
20
|
+
function installListener() {
|
|
21
|
+
if (listenerInstalled) return
|
|
22
|
+
listenerInstalled = true
|
|
23
|
+
process.on('message', (msg) => {
|
|
24
|
+
if (!msg || msg.type !== 'agent_ipc_response' || !msg.callId) return
|
|
25
|
+
const entry = pending.get(msg.callId)
|
|
26
|
+
if (!entry) return
|
|
27
|
+
pending.delete(msg.callId)
|
|
28
|
+
if (msg.ok) entry.resolve(msg.result)
|
|
29
|
+
else entry.reject(new Error(msg.error || 'agent_ipc_response error'))
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function nextCallId() {
|
|
34
|
+
_idSeq += 1
|
|
35
|
+
return `mem-${process.pid}-${Date.now()}-${_idSeq}`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Send an agent-bridge LLM request to the parent. Throws if IPC is
|
|
40
|
+
* unavailable (worker not forked) or the parent reports an error.
|
|
41
|
+
*
|
|
42
|
+
* @param {object} opts bridge-llm construction options
|
|
43
|
+
* @param {string} [opts.role]
|
|
44
|
+
* @param {string} [opts.taskType]
|
|
45
|
+
* @param {string} [opts.mode]
|
|
46
|
+
* @param {string} [opts.preset] preset id/name (passed at call time)
|
|
47
|
+
* @param {number} [opts.timeout] ms, defaults 600000
|
|
48
|
+
* @param {string} [opts.cwd]
|
|
49
|
+
* @param {string} prompt user message
|
|
50
|
+
* @returns {Promise<string>} raw assistant content
|
|
51
|
+
*/
|
|
52
|
+
export function callBridgeLlm(opts = {}, prompt) {
|
|
53
|
+
if (!process.send) {
|
|
54
|
+
return Promise.reject(new Error('agent-ipc: process.send unavailable (not running as worker)'))
|
|
55
|
+
}
|
|
56
|
+
installListener()
|
|
57
|
+
const callId = nextCallId()
|
|
58
|
+
const timeoutMs = Math.max(1000, Number(opts.timeout ?? 600000))
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const timer = setTimeout(() => {
|
|
61
|
+
if (!pending.has(callId)) return
|
|
62
|
+
pending.delete(callId)
|
|
63
|
+
try {
|
|
64
|
+
process.send({ type: 'agent_ipc_cancel', callId })
|
|
65
|
+
} catch {}
|
|
66
|
+
reject(new Error(`agent-ipc: timed out after ${timeoutMs}ms`))
|
|
67
|
+
}, timeoutMs)
|
|
68
|
+
pending.set(callId, {
|
|
69
|
+
resolve: (v) => { clearTimeout(timer); resolve(v) },
|
|
70
|
+
reject: (e) => { clearTimeout(timer); reject(e) },
|
|
71
|
+
})
|
|
72
|
+
try {
|
|
73
|
+
process.send({
|
|
74
|
+
type: 'agent_ipc_request',
|
|
75
|
+
callId,
|
|
76
|
+
tool: 'bridge_llm',
|
|
77
|
+
params: {
|
|
78
|
+
role: opts.role || null,
|
|
79
|
+
taskType: opts.taskType || null,
|
|
80
|
+
mode: opts.mode || null,
|
|
81
|
+
preset: opts.preset || null,
|
|
82
|
+
cwd: opts.cwd || null,
|
|
83
|
+
prompt: String(prompt ?? ''),
|
|
84
|
+
timeout: timeoutMs,
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
} catch (e) {
|
|
88
|
+
pending.delete(callId)
|
|
89
|
+
clearTimeout(timer)
|
|
90
|
+
reject(e)
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// bridge-trace-queries.mjs — SQL helpers for the bridge analytic tables.
|
|
2
|
+
// All functions accept a `db` handle from openTraceDatabase() and return
|
|
3
|
+
// plain row arrays. Query parameters are always $N — no interpolation.
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// topSessionsByIteration(db, hours, limit)
|
|
7
|
+
// Sessions with the highest max_iteration seen in the last N hours.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
export async function topSessionsByIteration(db, hours = 24, limit = 20) {
|
|
10
|
+
const { rows } = await db.query(`
|
|
11
|
+
SELECT s.session_id,
|
|
12
|
+
s.role,
|
|
13
|
+
s.model,
|
|
14
|
+
s.max_iteration,
|
|
15
|
+
s.tool_calls,
|
|
16
|
+
s.llm_calls,
|
|
17
|
+
s.total_input_tokens,
|
|
18
|
+
s.total_output_tokens,
|
|
19
|
+
s.started_at,
|
|
20
|
+
s.last_seen_at
|
|
21
|
+
FROM bridge_sessions s
|
|
22
|
+
WHERE s.last_seen_at >= now() - ($1 || ' hours')::interval
|
|
23
|
+
ORDER BY s.max_iteration DESC, s.tool_calls DESC
|
|
24
|
+
LIMIT $2
|
|
25
|
+
`, [String(hours), limit])
|
|
26
|
+
return rows
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// repeatToolCalls(db, sessionId)
|
|
31
|
+
// Tool calls whose (tool_name, md5(tool_args::text)) combo appears ≥3 times
|
|
32
|
+
// in a single session — strong signal for a tool-loop.
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
export async function repeatToolCalls(db, sessionId) {
|
|
35
|
+
const { rows } = await db.query(`
|
|
36
|
+
SELECT tool_name,
|
|
37
|
+
md5(tool_args::text) AS args_hash,
|
|
38
|
+
COUNT(*) AS call_count,
|
|
39
|
+
MIN(ts) AS first_at,
|
|
40
|
+
MAX(ts) AS last_at,
|
|
41
|
+
(array_agg(tool_args ORDER BY ts))[1] AS sample_args
|
|
42
|
+
FROM bridge_calls
|
|
43
|
+
WHERE session_id = $1
|
|
44
|
+
AND tool_name IS NOT NULL
|
|
45
|
+
GROUP BY tool_name, md5(tool_args::text)
|
|
46
|
+
HAVING COUNT(*) >= 3
|
|
47
|
+
ORDER BY call_count DESC, tool_name
|
|
48
|
+
`, [sessionId])
|
|
49
|
+
return rows
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// mixedToolPattern(db, sessionId)
|
|
54
|
+
// Sliding 3-tool window where grep and read alternate AND share a path token.
|
|
55
|
+
// Returns the centre-row of each such window.
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
export async function mixedToolPattern(db, sessionId) {
|
|
58
|
+
const { rows } = await db.query(`
|
|
59
|
+
WITH ordered AS (
|
|
60
|
+
SELECT id, ts, tool_name,
|
|
61
|
+
tool_args->>'path' AS path,
|
|
62
|
+
-- basename: last segment after splitting on '/'
|
|
63
|
+
regexp_replace(tool_args->>'path', '^.*/', '') AS basename,
|
|
64
|
+
LAG (tool_name, 1) OVER (ORDER BY ts, id) AS prev1,
|
|
65
|
+
LAG (tool_name, 2) OVER (ORDER BY ts, id) AS prev2,
|
|
66
|
+
LEAD(tool_name, 1) OVER (ORDER BY ts, id) AS next1,
|
|
67
|
+
LAG (tool_args->>'path', 1) OVER (ORDER BY ts, id) AS prev_path,
|
|
68
|
+
LEAD(tool_args->>'path', 1) OVER (ORDER BY ts, id) AS next_path,
|
|
69
|
+
-- basenames of neighbours
|
|
70
|
+
regexp_replace(LAG (tool_args->>'path', 1) OVER (ORDER BY ts, id), '^.*/', '') AS prev_base,
|
|
71
|
+
regexp_replace(LEAD(tool_args->>'path', 1) OVER (ORDER BY ts, id), '^.*/', '') AS next_base
|
|
72
|
+
FROM bridge_calls
|
|
73
|
+
WHERE session_id = $1
|
|
74
|
+
AND tool_name IN ('grep','read')
|
|
75
|
+
)
|
|
76
|
+
SELECT id, ts, tool_name, path
|
|
77
|
+
FROM ordered
|
|
78
|
+
WHERE -- true 3-tool alternation: centre plus BOTH flanking neighbours (window size = 3)
|
|
79
|
+
(
|
|
80
|
+
(tool_name = 'read' AND prev1 = 'grep' AND next1 = 'grep')
|
|
81
|
+
OR (tool_name = 'grep' AND prev1 = 'read' AND next1 = 'read')
|
|
82
|
+
)
|
|
83
|
+
-- path-token sharing: basename or exact path matches across all 3 slots
|
|
84
|
+
AND basename IS NOT NULL
|
|
85
|
+
AND (
|
|
86
|
+
-- exact path shared with both neighbours
|
|
87
|
+
(path = prev_path AND path = next_path)
|
|
88
|
+
-- or basename shared with both neighbours
|
|
89
|
+
OR (basename = prev_base AND basename = next_base)
|
|
90
|
+
-- or mixed: exact on one side, basename on other
|
|
91
|
+
OR (path = prev_path AND basename = next_base)
|
|
92
|
+
OR (path = next_path AND basename = prev_base)
|
|
93
|
+
)
|
|
94
|
+
ORDER BY ts, id
|
|
95
|
+
`, [sessionId])
|
|
96
|
+
return rows
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// tokenUsageByRole(db, hours)
|
|
101
|
+
// Sum of input/output tokens grouped by role over the last N hours.
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
export async function tokenUsageByRole(db, hours = 24) {
|
|
104
|
+
const { rows } = await db.query(`
|
|
105
|
+
SELECT s.role,
|
|
106
|
+
COUNT(DISTINCT l.session_id) AS sessions,
|
|
107
|
+
SUM(l.input_tokens) AS total_input,
|
|
108
|
+
SUM(l.output_tokens) AS total_output,
|
|
109
|
+
SUM(l.cached_tokens) AS total_cached,
|
|
110
|
+
SUM(l.cache_write_tokens) AS total_cache_write,
|
|
111
|
+
AVG(l.input_tokens)::int AS avg_input_per_call,
|
|
112
|
+
AVG(l.output_tokens)::int AS avg_output_per_call
|
|
113
|
+
FROM bridge_llm l
|
|
114
|
+
JOIN bridge_sessions s USING (session_id)
|
|
115
|
+
WHERE l.ts >= now() - ($1 || ' hours')::interval
|
|
116
|
+
GROUP BY s.role
|
|
117
|
+
ORDER BY total_input DESC NULLS LAST
|
|
118
|
+
`, [String(hours)])
|
|
119
|
+
return rows
|
|
120
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
// User-curated core memory store — native PG-backed via core_entries table.
|
|
2
|
+
// Per-project entries distinguished by project_id column (NULL = COMMON).
|
|
3
|
+
// addCore / editCore generate an embedding for each row and run a cosine-sim
|
|
4
|
+
// lookup against existing rows in the same project pool: candidates at or
|
|
5
|
+
// above SIM_RECALL go through an LLM "merge or distinct" judge — only the
|
|
6
|
+
// LLM's verdict, not the embedding score, decides whether the prior row is
|
|
7
|
+
// superseded in place. Below the threshold the row is INSERTed fresh.
|
|
8
|
+
// cycle2 reads core_entries via the {{USER_CORE}} prompt slot to avoid
|
|
9
|
+
// re-promoting entries that already overlap a user-curated row.
|
|
10
|
+
|
|
11
|
+
import { getDatabase, embeddingToSql } from './memory.mjs'
|
|
12
|
+
import { cachedEmbedTextBatch } from './memory-embed.mjs'
|
|
13
|
+
import { callBridgeLlm } from './agent-ipc.mjs'
|
|
14
|
+
import { resolveMaintenancePreset } from '../../shared/llm/index.mjs'
|
|
15
|
+
import { checkedConnect } from './pg/adapter.mjs'
|
|
16
|
+
|
|
17
|
+
const VALID_CAT = new Set([
|
|
18
|
+
'rule', 'constraint', 'decision', 'fact', 'goal', 'preference', 'task', 'issue',
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
// Embedding sim threshold for surfacing a candidate to the LLM judge. Wider
|
|
22
|
+
// than cycle2's tier-1 (0.78) on purpose: LLM verdict is authoritative so
|
|
23
|
+
// the recall side can afford broader recall.
|
|
24
|
+
const SIM_RECALL = 0.65
|
|
25
|
+
const CORE_DEDUP_TOP_K = 5
|
|
26
|
+
|
|
27
|
+
export const CORE_SUMMARY_MAX = 120
|
|
28
|
+
|
|
29
|
+
function trimOrNull(v) {
|
|
30
|
+
if (v == null) return null
|
|
31
|
+
const s = String(v).trim()
|
|
32
|
+
return s === '' ? null : s
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function _getDb(dataDir) {
|
|
36
|
+
if (!dataDir) throw new Error('core-memory: dataDir required')
|
|
37
|
+
const db = getDatabase(dataDir)
|
|
38
|
+
if (!db) throw new Error('core-memory: database not open — call openDatabase first')
|
|
39
|
+
return db
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function _embedFor(db, element, summary) {
|
|
43
|
+
const text = `${element}\n${summary || ''}`.trim()
|
|
44
|
+
if (!text) return null
|
|
45
|
+
const [vec] = await cachedEmbedTextBatch(db, [text])
|
|
46
|
+
return Array.isArray(vec) ? vec : null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Lazy repair of NULL embeddings on existing rows. Runs once per boot or
|
|
50
|
+
// whenever a NULL slips back in via direct SQL. SELECT WHERE embedding IS NULL
|
|
51
|
+
// returns 0 rows on a fully-populated table, so this is a fast no-op.
|
|
52
|
+
function throwIfAborted(signal) {
|
|
53
|
+
if (signal?.aborted) throw signal.reason ?? new Error('aborted')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function _backfillNullEmbeddings(db, options = {}) {
|
|
57
|
+
const signal = options?.signal
|
|
58
|
+
throwIfAborted(signal)
|
|
59
|
+
const r = await db.query(`SELECT id, element, summary FROM core_entries WHERE embedding IS NULL`)
|
|
60
|
+
if (r.rows.length === 0) return 0
|
|
61
|
+
let filled = 0
|
|
62
|
+
for (const row of r.rows) {
|
|
63
|
+
throwIfAborted(signal)
|
|
64
|
+
const vec = await _embedFor(db, row.element, row.summary)
|
|
65
|
+
throwIfAborted(signal)
|
|
66
|
+
if (!vec) continue
|
|
67
|
+
await db.query(
|
|
68
|
+
`UPDATE core_entries SET embedding = $1::halfvec WHERE id = $2 AND embedding IS NULL`,
|
|
69
|
+
[embeddingToSql(vec), row.id],
|
|
70
|
+
)
|
|
71
|
+
filled++
|
|
72
|
+
}
|
|
73
|
+
if (filled > 0) {
|
|
74
|
+
process.stderr.write(`[core-memory] backfilled ${filled} NULL embedding(s) on core_entries\n`)
|
|
75
|
+
}
|
|
76
|
+
return filled
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function backfillCoreEmbeddings(dataDir, options = {}) {
|
|
80
|
+
const db = _getDb(dataDir)
|
|
81
|
+
return await _backfillNullEmbeddings(db, options)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Boot-time invariant restoration: core_entries.id must always be the
|
|
85
|
+
// contiguous sequence 1..N. SERIAL only ever increments, so deleting a row
|
|
86
|
+
// leaves a permanent gap (e.g. 1,2,4,5 after deleting 3). This closes those
|
|
87
|
+
// gaps by resequencing every row globally (across all project_id pools) to
|
|
88
|
+
// 1..N in id order, then realigns the serial so the next INSERT continues from
|
|
89
|
+
// N+1. Deterministic invariant restore — no heuristic, no fallback branch.
|
|
90
|
+
export async function compactCoreIds(dataDir) {
|
|
91
|
+
const db = _getDb(dataDir)
|
|
92
|
+
// Fast no-op guard: a contiguous 1..N table has COUNT == MAX(id). This also
|
|
93
|
+
// covers the empty table (n=0, mx=0) — return before any write.
|
|
94
|
+
const g = await db.query(`SELECT COUNT(*) AS n, COALESCE(MAX(id),0) AS mx FROM core_entries`)
|
|
95
|
+
const n = Number(g.rows[0].n)
|
|
96
|
+
const mx = Number(g.rows[0].mx)
|
|
97
|
+
if (n === mx) return 0
|
|
98
|
+
|
|
99
|
+
// BEGIN transaction on ONE checked-out client — the pool wrapper db.query
|
|
100
|
+
// releases the client per call, so BEGIN/COMMIT must run on one client
|
|
101
|
+
// (same pattern as addCore).
|
|
102
|
+
const client = await checkedConnect(db._pool, 'memory')
|
|
103
|
+
try {
|
|
104
|
+
await client.query('BEGIN')
|
|
105
|
+
// (a) Vacate the low range so shifted ids can't collide with the 1..N
|
|
106
|
+
// target. Offset by current MAX(id): every id becomes id+mx, which is
|
|
107
|
+
// strictly greater than N (N <= mx), so the resequence below is safe.
|
|
108
|
+
await client.query(`UPDATE core_entries SET id = id + $1`, [mx])
|
|
109
|
+
// (b) Resequence ALL rows globally to 1..N preserving id order.
|
|
110
|
+
await client.query(`
|
|
111
|
+
WITH ordered AS (
|
|
112
|
+
SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM core_entries
|
|
113
|
+
)
|
|
114
|
+
UPDATE core_entries c SET id = o.rn FROM ordered o WHERE c.id = o.id`)
|
|
115
|
+
// (c) Realign the serial so the next INSERT continues from N+1. Guarded by
|
|
116
|
+
// the n===mx return above, so MAX(id) is always > 0 here — setval(...,0,true)
|
|
117
|
+
// is invalid and is never reached.
|
|
118
|
+
await client.query(
|
|
119
|
+
`SELECT setval(pg_get_serial_sequence('core_entries','id'),
|
|
120
|
+
(SELECT COALESCE(MAX(id),0) FROM core_entries), true)`)
|
|
121
|
+
await client.query('COMMIT')
|
|
122
|
+
} catch (err) {
|
|
123
|
+
try { await client.query('ROLLBACK') } catch {}
|
|
124
|
+
throw err
|
|
125
|
+
} finally {
|
|
126
|
+
client.release()
|
|
127
|
+
}
|
|
128
|
+
process.stderr.write(`[core-memory] compacted ${n} core id(s)\n`)
|
|
129
|
+
return n
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function _findTopKCore(db, projectId, embedding, excludeId, { forUpdate = false } = {}) {
|
|
133
|
+
if (!embedding) return []
|
|
134
|
+
const exclusion = excludeId == null ? '' : 'AND id != $3'
|
|
135
|
+
const sql = `
|
|
136
|
+
SELECT id, element, summary, category, 1 - (embedding <=> $1::halfvec) AS sim
|
|
137
|
+
FROM core_entries
|
|
138
|
+
WHERE embedding IS NOT NULL
|
|
139
|
+
AND project_id IS NOT DISTINCT FROM $2
|
|
140
|
+
${exclusion}
|
|
141
|
+
ORDER BY embedding <=> $1::halfvec
|
|
142
|
+
LIMIT ${CORE_DEDUP_TOP_K}${forUpdate ? ' FOR UPDATE' : ''}`
|
|
143
|
+
const params = excludeId == null
|
|
144
|
+
? [embeddingToSql(embedding), projectId]
|
|
145
|
+
: [embeddingToSql(embedding), projectId, excludeId]
|
|
146
|
+
const r = await db.query(sql, params)
|
|
147
|
+
return r.rows.filter(row => Number(row.sim) >= SIM_RECALL)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function _resolveMergeTarget(candidates, incoming) {
|
|
151
|
+
for (const c of candidates) {
|
|
152
|
+
if (await _llmJudgeMerge(c, incoming)) return c
|
|
153
|
+
}
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// LLM judge for "is this incoming entry a restatement of the existing one?"
|
|
158
|
+
// One-word reply: merge | distinct. Errors fall back to distinct so a flaky
|
|
159
|
+
// LLM never silently absorbs a fresh registration into an unrelated row.
|
|
160
|
+
async function _llmJudgeMerge(existing, incoming) {
|
|
161
|
+
const prompt =
|
|
162
|
+
`Two user-curated core memory entries below. Are they restating the same rule, fact, or preference (just different wording)? Reply ONE WORD: merge or distinct.\n\n` +
|
|
163
|
+
`EXISTING: ${existing.element} — ${String(existing.summary || '')}\n` +
|
|
164
|
+
`INCOMING: ${incoming.element} — ${String(incoming.summary || '')}`
|
|
165
|
+
try {
|
|
166
|
+
const raw = await callBridgeLlm({
|
|
167
|
+
role: 'cycle2-agent',
|
|
168
|
+
taskType: 'maintenance',
|
|
169
|
+
mode: 'core-merge-judge',
|
|
170
|
+
preset: resolveMaintenancePreset('cycle2'),
|
|
171
|
+
timeout: 30_000,
|
|
172
|
+
cwd: null,
|
|
173
|
+
}, prompt)
|
|
174
|
+
return String(raw ?? '').trim().toLowerCase().startsWith('merge')
|
|
175
|
+
} catch (err) {
|
|
176
|
+
process.stderr.write(`[core-memory] LLM merge judge failed: ${err.message}\n`)
|
|
177
|
+
return false
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export async function listCore(dataDir, projectId = null) {
|
|
182
|
+
const db = _getDb(dataDir)
|
|
183
|
+
const cols = `id, element, summary, category, project_id, created_at, updated_at`
|
|
184
|
+
if (projectId === '*') {
|
|
185
|
+
const r = await db.query(`SELECT ${cols} FROM core_entries ORDER BY project_id NULLS FIRST, id ASC`)
|
|
186
|
+
return r.rows
|
|
187
|
+
}
|
|
188
|
+
if (projectId === null) {
|
|
189
|
+
const r = await db.query(`SELECT ${cols} FROM core_entries WHERE project_id IS NULL ORDER BY id ASC`)
|
|
190
|
+
return r.rows
|
|
191
|
+
}
|
|
192
|
+
const r = await db.query(`SELECT ${cols} FROM core_entries WHERE project_id = $1 ORDER BY id ASC`, [projectId])
|
|
193
|
+
return r.rows
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function addCore(dataDir, { element, summary, category }, projectId) {
|
|
197
|
+
if (projectId === undefined) throw new Error('addCore: projectId required — pass null for COMMON pool, or slug string for scoped pool')
|
|
198
|
+
const el = trimOrNull(element)
|
|
199
|
+
const sm = trimOrNull(summary) ?? el
|
|
200
|
+
if (!el || !sm) throw new Error('add requires element and summary')
|
|
201
|
+
if (sm.length > CORE_SUMMARY_MAX) {
|
|
202
|
+
throw new Error(`core summary too long (${sm.length} chars, max ${CORE_SUMMARY_MAX}) — core memory must be 1 fact in 1-2 sentences; procedures, multi-step, or code belong in recap or docs. Compress and retry.`)
|
|
203
|
+
}
|
|
204
|
+
const cat = (trimOrNull(category) ?? 'fact').toLowerCase()
|
|
205
|
+
if (!VALID_CAT.has(cat)) {
|
|
206
|
+
throw new Error(`invalid category "${cat}". Valid: ${[...VALID_CAT].join(', ')}`)
|
|
207
|
+
}
|
|
208
|
+
const db = _getDb(dataDir)
|
|
209
|
+
const now = Date.now()
|
|
210
|
+
await _backfillNullEmbeddings(db)
|
|
211
|
+
const embedding = await _embedFor(db, el, sm)
|
|
212
|
+
|
|
213
|
+
const client = await checkedConnect(db._pool, 'memory')
|
|
214
|
+
try {
|
|
215
|
+
await client.query('BEGIN')
|
|
216
|
+
await client.query(`SET LOCAL lock_timeout = '5s'`)
|
|
217
|
+
const poolKey = `core:${projectId == null ? 'COMMON' : projectId}`
|
|
218
|
+
await client.query(`SELECT pg_advisory_xact_lock(hashtext($1))`, [poolKey])
|
|
219
|
+
const candidates = await _findTopKCore(client, projectId, embedding, null, { forUpdate: true })
|
|
220
|
+
const mergeTarget = await _resolveMergeTarget(candidates, { element: el, summary: sm })
|
|
221
|
+
if (mergeTarget) {
|
|
222
|
+
const r = await client.query(
|
|
223
|
+
`UPDATE core_entries
|
|
224
|
+
SET element = $1, summary = $2, category = $3, embedding = $4::halfvec, updated_at = $5
|
|
225
|
+
WHERE id = $6
|
|
226
|
+
RETURNING id, element, summary, category, project_id, created_at, updated_at`,
|
|
227
|
+
[el, sm, cat, embedding ? embeddingToSql(embedding) : null, now, mergeTarget.id],
|
|
228
|
+
)
|
|
229
|
+
await client.query('COMMIT')
|
|
230
|
+
const row = r.rows[0]
|
|
231
|
+
return { ...row, merged_with: mergeTarget.id, sim: Number(mergeTarget.sim).toFixed(3) }
|
|
232
|
+
}
|
|
233
|
+
let r
|
|
234
|
+
try {
|
|
235
|
+
r = await client.query(
|
|
236
|
+
`INSERT INTO core_entries(element, summary, category, project_id, embedding, created_at, updated_at)
|
|
237
|
+
VALUES ($1, $2, $3, $4, $5::halfvec, $6, $7)
|
|
238
|
+
RETURNING id, element, summary, category, project_id, created_at, updated_at`,
|
|
239
|
+
[el, sm, cat, projectId, embedding ? embeddingToSql(embedding) : null, now, now],
|
|
240
|
+
)
|
|
241
|
+
} catch (err) {
|
|
242
|
+
if (err.code === '23505') {
|
|
243
|
+
throw new Error(`core entry already exists: project=${projectId ?? 'COMMON'} element=${JSON.stringify(el.slice(0, 200))}`)
|
|
244
|
+
}
|
|
245
|
+
throw err
|
|
246
|
+
}
|
|
247
|
+
await client.query('COMMIT')
|
|
248
|
+
return r.rows[0]
|
|
249
|
+
} catch (err) {
|
|
250
|
+
try { await client.query('ROLLBACK') } catch {}
|
|
251
|
+
throw err
|
|
252
|
+
} finally {
|
|
253
|
+
client.release()
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function editCore(dataDir, id, patch) {
|
|
258
|
+
const numId = Number(id)
|
|
259
|
+
if (!Number.isInteger(numId) || numId <= 0) throw new Error('integer id > 0 required')
|
|
260
|
+
const db = _getDb(dataDir)
|
|
261
|
+
const cur = (await db.query(`SELECT * FROM core_entries WHERE id = $1`, [numId])).rows[0]
|
|
262
|
+
if (!cur) throw new Error(`no entry with id=${numId}`)
|
|
263
|
+
const newElement = trimOrNull(patch.element) ?? cur.element
|
|
264
|
+
const newSummary = trimOrNull(patch.summary) ?? cur.summary
|
|
265
|
+
const newCategoryRaw = trimOrNull(patch.category)
|
|
266
|
+
const newCategory = newCategoryRaw ? newCategoryRaw.toLowerCase() : cur.category
|
|
267
|
+
if (!VALID_CAT.has(newCategory)) {
|
|
268
|
+
throw new Error(`invalid category "${newCategory}". Valid: ${[...VALID_CAT].join(', ')}`)
|
|
269
|
+
}
|
|
270
|
+
if (newElement === cur.element && newSummary === cur.summary && newCategory === cur.category) {
|
|
271
|
+
throw new Error('no change')
|
|
272
|
+
}
|
|
273
|
+
if (newSummary && newSummary.length > CORE_SUMMARY_MAX) {
|
|
274
|
+
throw new Error(`core summary too long (${newSummary.length} chars, max ${CORE_SUMMARY_MAX}) — core memory must be 1 fact in 1-2 sentences; procedures, multi-step, or code belong in recap or docs. Compress and retry.`)
|
|
275
|
+
}
|
|
276
|
+
const now = Date.now()
|
|
277
|
+
const textChanged = newElement !== cur.element || newSummary !== cur.summary
|
|
278
|
+
if (!textChanged) {
|
|
279
|
+
await db.query(
|
|
280
|
+
`UPDATE core_entries SET category = $1, updated_at = $2 WHERE id = $3`,
|
|
281
|
+
[newCategory, now, numId],
|
|
282
|
+
)
|
|
283
|
+
return { ...cur, element: newElement, summary: newSummary, category: newCategory, updated_at: now }
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
await _backfillNullEmbeddings(db)
|
|
287
|
+
const embedding = await _embedFor(db, newElement, newSummary)
|
|
288
|
+
const client = await checkedConnect(db._pool, 'memory')
|
|
289
|
+
try {
|
|
290
|
+
await client.query('BEGIN')
|
|
291
|
+
await client.query(`SET LOCAL lock_timeout = '5s'`)
|
|
292
|
+
const poolKey = `core:${cur.project_id == null ? 'COMMON' : cur.project_id}`
|
|
293
|
+
await client.query(`SELECT pg_advisory_xact_lock(hashtext($1))`, [poolKey])
|
|
294
|
+
const candidates = await _findTopKCore(client, cur.project_id, embedding, numId, { forUpdate: true })
|
|
295
|
+
const mergeTarget = await _resolveMergeTarget(candidates, { element: newElement, summary: newSummary })
|
|
296
|
+
if (mergeTarget) {
|
|
297
|
+
const r = await client.query(
|
|
298
|
+
`UPDATE core_entries
|
|
299
|
+
SET element = $1, summary = $2, category = $3, embedding = $4::halfvec, updated_at = $5
|
|
300
|
+
WHERE id = $6
|
|
301
|
+
RETURNING id, element, summary, category, project_id, created_at, updated_at`,
|
|
302
|
+
[newElement, newSummary, newCategory, embedding ? embeddingToSql(embedding) : null, now, mergeTarget.id],
|
|
303
|
+
)
|
|
304
|
+
await client.query(`DELETE FROM core_entries WHERE id = $1`, [numId])
|
|
305
|
+
await client.query('COMMIT')
|
|
306
|
+
const row = r.rows[0]
|
|
307
|
+
return { ...row, merged_from: numId, merged_with: mergeTarget.id, sim: Number(mergeTarget.sim).toFixed(3) }
|
|
308
|
+
}
|
|
309
|
+
await client.query(
|
|
310
|
+
`UPDATE core_entries SET element = $1, summary = $2, category = $3, embedding = $4::halfvec, updated_at = $5 WHERE id = $6`,
|
|
311
|
+
[newElement, newSummary, newCategory, embedding ? embeddingToSql(embedding) : null, now, numId],
|
|
312
|
+
)
|
|
313
|
+
await client.query('COMMIT')
|
|
314
|
+
return { ...cur, element: newElement, summary: newSummary, category: newCategory, updated_at: now }
|
|
315
|
+
} catch (err) {
|
|
316
|
+
try { await client.query('ROLLBACK') } catch {}
|
|
317
|
+
throw err
|
|
318
|
+
} finally {
|
|
319
|
+
client.release()
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export async function deleteCore(dataDir, id) {
|
|
324
|
+
const numId = Number(id)
|
|
325
|
+
if (!Number.isInteger(numId) || numId <= 0) throw new Error('integer id > 0 required')
|
|
326
|
+
const db = _getDb(dataDir)
|
|
327
|
+
const r = await db.query(`DELETE FROM core_entries WHERE id = $1 RETURNING *`, [numId])
|
|
328
|
+
if (r.rows.length === 0) throw new Error(`no entry with id=${numId}`)
|
|
329
|
+
return r.rows[0]
|
|
330
|
+
}
|