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,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI OAuth (Codex backend) search backend.
|
|
3
|
+
*
|
|
4
|
+
* Reuses agent.providers.openai-oauth credentials (ChatGPT Pro bearer).
|
|
5
|
+
* Calls Codex WebSocket endpoint via sendViaWebSocket with web_search server
|
|
6
|
+
* tool. Model is config-driven (search.models.openai default 'gpt-5.4-mini').
|
|
7
|
+
*/
|
|
8
|
+
import { OpenAIOAuthProvider, ensureLatestCodexModel } from '../../../agent/orchestrator/providers/openai-oauth.mjs'
|
|
9
|
+
import {
|
|
10
|
+
OPENAI_SEARCH_SYSTEM_INSTRUCTIONS,
|
|
11
|
+
buildOpenAISearchPrompt,
|
|
12
|
+
buildOpenAIWebSearchTool,
|
|
13
|
+
citationsFromText,
|
|
14
|
+
citationsFromWebSearchCalls,
|
|
15
|
+
} from './openai-web-search.mjs'
|
|
16
|
+
|
|
17
|
+
const SEARCH_POOL_KEY = 'mixdog-search-openai-oauth'
|
|
18
|
+
const SEARCH_CACHE_KEY = 'mixdog-codex-search'
|
|
19
|
+
|
|
20
|
+
// Reuse one provider instance across searches so its in-memory token cache
|
|
21
|
+
// survives between calls — avoids re-loading credentials on every search.
|
|
22
|
+
// ensureAuth() still re-validates via mtime + expiry per use.
|
|
23
|
+
let _sharedProvider = null
|
|
24
|
+
function getSharedProvider() {
|
|
25
|
+
if (!_sharedProvider) _sharedProvider = new OpenAIOAuthProvider({})
|
|
26
|
+
return _sharedProvider
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function searchViaOpenAIOAuth({
|
|
30
|
+
query,
|
|
31
|
+
model,
|
|
32
|
+
effort,
|
|
33
|
+
fast = false,
|
|
34
|
+
site,
|
|
35
|
+
type = 'web',
|
|
36
|
+
maxResults = 5,
|
|
37
|
+
locale,
|
|
38
|
+
contextSize = 'low',
|
|
39
|
+
warnings = [],
|
|
40
|
+
signal,
|
|
41
|
+
}) {
|
|
42
|
+
const t0 = Date.now()
|
|
43
|
+
const provider = getSharedProvider()
|
|
44
|
+
const useModel = model || await ensureLatestCodexModel(provider)
|
|
45
|
+
await provider.ensureAuth({ reason: 'search' })
|
|
46
|
+
const tokens = provider.tokens
|
|
47
|
+
if (!tokens?.access_token) throw new Error('[search:openai-oauth] no access_token available')
|
|
48
|
+
|
|
49
|
+
// Match the bridge invariant: poolKey is the local socket bucket, cacheKey is
|
|
50
|
+
// the server-side prompt-cache shard. Keep them stable and distinct.
|
|
51
|
+
const poolKey = SEARCH_POOL_KEY
|
|
52
|
+
const cacheKey = SEARCH_CACHE_KEY
|
|
53
|
+
// Effort defaults to 'low' when caller doesn't specify — preserves the
|
|
54
|
+
// prior hard-coded behavior so older configs without modelOptions still
|
|
55
|
+
// route through the same low-latency path.
|
|
56
|
+
const body = {
|
|
57
|
+
model: useModel,
|
|
58
|
+
instructions: OPENAI_SEARCH_SYSTEM_INSTRUCTIONS,
|
|
59
|
+
input: [{ type: 'message', role: 'user', content: [{ type: 'input_text', text: buildOpenAISearchPrompt(query, maxResults) }] }],
|
|
60
|
+
store: false,
|
|
61
|
+
stream: true,
|
|
62
|
+
prompt_cache_key: cacheKey,
|
|
63
|
+
reasoning: { effort: effort || 'low' },
|
|
64
|
+
text: { verbosity: 'low' },
|
|
65
|
+
tool_choice: 'auto',
|
|
66
|
+
parallel_tool_calls: false,
|
|
67
|
+
tools: [buildOpenAIWebSearchTool({ site, type, locale, contextSize })],
|
|
68
|
+
}
|
|
69
|
+
if (fast === true) body.service_tier = 'priority'
|
|
70
|
+
// Route through provider.send() (not sendViaWebSocket directly) so the search
|
|
71
|
+
// request inherits the 401/403 force-refresh retry + HTTP/SSE fallback. A
|
|
72
|
+
// stale token or unhealthy WebSocket then recovers instead of hard-failing.
|
|
73
|
+
// _prebuiltBody bypasses buildRequestBody — the web_search server-tool body
|
|
74
|
+
// shape it can't express is shipped verbatim. poolKey/cacheKey map onto
|
|
75
|
+
// sessionId/providerCacheKey to preserve the prior socket/cache sharding.
|
|
76
|
+
const result = await provider.send(null, useModel, null, {
|
|
77
|
+
_prebuiltBody: body,
|
|
78
|
+
sessionId: poolKey,
|
|
79
|
+
providerCacheKey: cacheKey,
|
|
80
|
+
iteration: 0,
|
|
81
|
+
signal,
|
|
82
|
+
})
|
|
83
|
+
const answer = String(result?.content || '').trim()
|
|
84
|
+
let citations = Array.isArray(result?.citations) ? result.citations.slice(0, maxResults) : []
|
|
85
|
+
if (!citations.length) citations = citationsFromText(answer, maxResults, 'openai-oauth')
|
|
86
|
+
if (!citations.length) citations = citationsFromWebSearchCalls(result?.webSearchCalls, maxResults, 'openai-oauth')
|
|
87
|
+
return {
|
|
88
|
+
backend: 'openai-oauth',
|
|
89
|
+
model: useModel,
|
|
90
|
+
query,
|
|
91
|
+
answer,
|
|
92
|
+
citations,
|
|
93
|
+
durationMs: Date.now() - t0,
|
|
94
|
+
usage: result?.usage || null,
|
|
95
|
+
webSearchCalls: result?.webSearchCalls || [],
|
|
96
|
+
warnings,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const OPENAI_SEARCH_SYSTEM_INSTRUCTIONS = 'You can use the web_search server tool to look up live information. Reply concisely with the answer and citations.'
|
|
2
|
+
|
|
3
|
+
export function buildOpenAIWebSearchTool({ site, type = 'web', locale, contextSize = 'low' } = {}) {
|
|
4
|
+
const tool = {
|
|
5
|
+
type: 'web_search',
|
|
6
|
+
external_web_access: true,
|
|
7
|
+
search_context_size: contextSize || 'low',
|
|
8
|
+
}
|
|
9
|
+
if (site) tool.filters = { allowed_domains: [site] }
|
|
10
|
+
if (locale?.country || locale?.city || locale?.region || locale?.timezone) {
|
|
11
|
+
tool.user_location = {
|
|
12
|
+
type: 'approximate',
|
|
13
|
+
...(locale.country ? { country: locale.country } : {}),
|
|
14
|
+
...(locale.city ? { city: locale.city } : {}),
|
|
15
|
+
...(locale.region ? { region: locale.region } : {}),
|
|
16
|
+
...(locale.timezone ? { timezone: locale.timezone } : {}),
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (type === 'images') tool.search_content_types = ['text', 'image']
|
|
20
|
+
return tool
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function buildOpenAISearchPrompt(query, maxResults = 5) {
|
|
24
|
+
return [
|
|
25
|
+
String(query),
|
|
26
|
+
'',
|
|
27
|
+
`Return a concise answer and cite at most ${maxResults} source URLs.`,
|
|
28
|
+
'Put source URLs as plain full URLs if annotations are not available.',
|
|
29
|
+
].join('\n')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function citationsFromText(text, maxResults = 5, source = 'openai-oauth') {
|
|
33
|
+
const urls = String(text || '').match(/https?:\/\/[^\s<>()\[\]{}"`']+/g) || []
|
|
34
|
+
const seen = new Set()
|
|
35
|
+
const citations = []
|
|
36
|
+
for (const raw of urls) {
|
|
37
|
+
let parsed
|
|
38
|
+
try {
|
|
39
|
+
parsed = new URL(raw.replace(/[.,;:!?]+$/g, ''))
|
|
40
|
+
} catch {
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
const url = parsed.toString()
|
|
44
|
+
if (seen.has(url)) continue
|
|
45
|
+
seen.add(url)
|
|
46
|
+
citations.push({ title: parsed.hostname, url, snippet: '', source })
|
|
47
|
+
if (citations.length >= maxResults) break
|
|
48
|
+
}
|
|
49
|
+
return citations
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function citationsFromWebSearchCalls(calls, maxResults = 5, source = 'openai-oauth') {
|
|
53
|
+
const seen = new Set()
|
|
54
|
+
const citations = []
|
|
55
|
+
for (const call of Array.isArray(calls) ? calls : []) {
|
|
56
|
+
const action = call?.action || {}
|
|
57
|
+
const entries = [
|
|
58
|
+
...(action.url ? [action.url] : []),
|
|
59
|
+
...(Array.isArray(action.urls) ? action.urls : []),
|
|
60
|
+
]
|
|
61
|
+
for (const raw of entries) {
|
|
62
|
+
let parsed
|
|
63
|
+
try {
|
|
64
|
+
parsed = new URL(String(raw))
|
|
65
|
+
} catch {
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
const url = parsed.toString()
|
|
69
|
+
if (seen.has(url)) continue
|
|
70
|
+
seen.add(url)
|
|
71
|
+
citations.push({ title: parsed.hostname, url, snippet: '', source })
|
|
72
|
+
if (citations.length >= maxResults) return citations
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return citations
|
|
76
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tavily search backend.
|
|
3
|
+
*
|
|
4
|
+
* Uses search.credentials.tavily.apiKey. POST /search with include_answer:true
|
|
5
|
+
* for built-in AI synthesis. RAG-specialized index.
|
|
6
|
+
*/
|
|
7
|
+
import { providerHttpError } from '../state.mjs'
|
|
8
|
+
import { getSearchApiKey } from '../../../shared/config.mjs'
|
|
9
|
+
import { tavilyCountryName } from '../search-intent.mjs'
|
|
10
|
+
|
|
11
|
+
const URL = 'https://api.tavily.com/search'
|
|
12
|
+
|
|
13
|
+
export async function searchViaTavily({ query, limit = 5, site, type = 'web', locale, signal }) {
|
|
14
|
+
const t0 = Date.now()
|
|
15
|
+
const key = getSearchApiKey('tavily')
|
|
16
|
+
if (!key) throw new Error('[search:tavily] no api key — register via mixdog-search setup -> search-keys')
|
|
17
|
+
const topic = type === 'news' ? 'news' : 'general'
|
|
18
|
+
const body = {
|
|
19
|
+
api_key: key,
|
|
20
|
+
query: String(query),
|
|
21
|
+
topic,
|
|
22
|
+
search_depth: 'basic',
|
|
23
|
+
max_results: limit,
|
|
24
|
+
include_answer: true,
|
|
25
|
+
include_raw_content: false,
|
|
26
|
+
...(site ? { include_domains: [site] } : {}),
|
|
27
|
+
}
|
|
28
|
+
if (topic === 'general' && locale?.country) body.country = tavilyCountryName(locale.country)
|
|
29
|
+
|
|
30
|
+
const res = await fetch(URL, {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: { 'Content-Type': 'application/json' },
|
|
33
|
+
body: JSON.stringify(body),
|
|
34
|
+
signal,
|
|
35
|
+
})
|
|
36
|
+
if (res.status !== 200) {
|
|
37
|
+
const text = await res.text()
|
|
38
|
+
throw providerHttpError('tavily', res.status, text)
|
|
39
|
+
}
|
|
40
|
+
const j = await res.json()
|
|
41
|
+
const citations = (j?.results || []).slice(0, limit).map(h => ({
|
|
42
|
+
title: h.title || '',
|
|
43
|
+
url: h.url || '',
|
|
44
|
+
snippet: (h.content || '').slice(0, 240),
|
|
45
|
+
publishedDate: h.published_date || null,
|
|
46
|
+
source: 'tavily',
|
|
47
|
+
}))
|
|
48
|
+
return {
|
|
49
|
+
backend: 'tavily',
|
|
50
|
+
query,
|
|
51
|
+
answer: (j?.answer || '').trim(),
|
|
52
|
+
citations,
|
|
53
|
+
durationMs: Date.now() - t0,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xAI API key search backend.
|
|
3
|
+
*
|
|
4
|
+
* API key via getAgentApiKey('xai') (env/keychain/setup). Calls Responses API + web_search
|
|
5
|
+
* (Agent Tools API) — Live Search (Chat Completions) is deprecated.
|
|
6
|
+
*/
|
|
7
|
+
import { providerHttpError } from '../state.mjs'
|
|
8
|
+
import { getAgentApiKey } from '../../../shared/config.mjs'
|
|
9
|
+
import { resolveLatestGrokModel } from '../../../agent/orchestrator/providers/grok-oauth.mjs'
|
|
10
|
+
|
|
11
|
+
const URL = 'https://api.x.ai/v1/responses'
|
|
12
|
+
const MODELS_URL = 'https://api.x.ai/v1/models'
|
|
13
|
+
|
|
14
|
+
// Match resolveLatestGrokModel() in grok-oauth.mjs (do not write OAuth shared cache).
|
|
15
|
+
const NON_CHAT_MODEL_RE = /imagine|image|video/i
|
|
16
|
+
const PROXY_EXACT_MODELS = new Set(['grok-build'])
|
|
17
|
+
function _isProxyOnlyModel(model) {
|
|
18
|
+
const m = String(model || '')
|
|
19
|
+
return /^grok-composer/i.test(m) || PROXY_EXACT_MODELS.has(m)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function _normalizeGrokApiModel(m) {
|
|
23
|
+
const id = m?.id
|
|
24
|
+
if (!id) return null
|
|
25
|
+
return {
|
|
26
|
+
id,
|
|
27
|
+
name: id,
|
|
28
|
+
display: id,
|
|
29
|
+
provider: 'grok-oauth',
|
|
30
|
+
family: 'grok',
|
|
31
|
+
tier: 'version',
|
|
32
|
+
latest: false,
|
|
33
|
+
contextWindow: m?.context_window || 0,
|
|
34
|
+
created: typeof m?.created === 'number' ? m.created : null,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function _pickLatestChatModelId(models) {
|
|
39
|
+
let best = null
|
|
40
|
+
for (const m of models) {
|
|
41
|
+
if (!m?.id || NON_CHAT_MODEL_RE.test(m.id) || _isProxyOnlyModel(m.id) || !(Number(m.created) > 0)) continue
|
|
42
|
+
if (!best || Number(m.created) > Number(best.created)) best = m
|
|
43
|
+
}
|
|
44
|
+
return best?.id || null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function _fetchGrokCatalogModels(apiKey, signal) {
|
|
48
|
+
const res = await fetch(MODELS_URL, {
|
|
49
|
+
method: 'GET',
|
|
50
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
51
|
+
redirect: 'error',
|
|
52
|
+
signal,
|
|
53
|
+
})
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
const text = await res.text().catch(() => '')
|
|
56
|
+
throw new Error(`[search:xai-api] model catalog fetch failed (${res.status}${text ? `: ${text.slice(0, 200)}` : ''})`)
|
|
57
|
+
}
|
|
58
|
+
const data = await res.json()
|
|
59
|
+
if (!Array.isArray(data?.data)) {
|
|
60
|
+
throw new Error('[search:xai-api] unexpected /models response shape (no data[])')
|
|
61
|
+
}
|
|
62
|
+
return data.data.map(_normalizeGrokApiModel).filter(Boolean)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function ensureLatestGrokModelForApiKey(apiKey, signal) {
|
|
66
|
+
let m = resolveLatestGrokModel()
|
|
67
|
+
if (m) return m
|
|
68
|
+
const models = await _fetchGrokCatalogModels(apiKey, signal)
|
|
69
|
+
m = _pickLatestChatModelId(models)
|
|
70
|
+
if (m) return m
|
|
71
|
+
throw new Error(
|
|
72
|
+
'[search:xai-api] model catalog unavailable after warmup — set search.models.xai to an explicit model id',
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function searchViaXAIApi({ query, model, maxResults = 5, warnings = [], signal }) {
|
|
77
|
+
const t0 = Date.now()
|
|
78
|
+
const key = getAgentApiKey('xai')
|
|
79
|
+
if (!key) throw new Error('[search:xai-api] no api key — set XAI_API_KEY or the xAI provider key in setup')
|
|
80
|
+
const useModel = model || await ensureLatestGrokModelForApiKey(key, signal)
|
|
81
|
+
|
|
82
|
+
const res = await fetch(URL, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
85
|
+
body: JSON.stringify({
|
|
86
|
+
model: useModel,
|
|
87
|
+
input: String(query),
|
|
88
|
+
tools: [{ type: 'web_search' }],
|
|
89
|
+
}),
|
|
90
|
+
signal,
|
|
91
|
+
})
|
|
92
|
+
if (res.status !== 200) {
|
|
93
|
+
const text = await res.text()
|
|
94
|
+
throw providerHttpError('xai-api', res.status, text)
|
|
95
|
+
}
|
|
96
|
+
const j = await res.json()
|
|
97
|
+
const items = j?.output || []
|
|
98
|
+
const msg = items.find(it => it.type === 'message')?.content || []
|
|
99
|
+
const tb = msg.find(c => c.type === 'output_text')
|
|
100
|
+
const annotations = (tb?.annotations || [])
|
|
101
|
+
.filter(a => a?.url)
|
|
102
|
+
.map(a => ({ title: a.title || '', url: a.url || '', snippet: '', source: 'xai-api' }))
|
|
103
|
+
return {
|
|
104
|
+
backend: 'xai-api',
|
|
105
|
+
model: useModel,
|
|
106
|
+
query,
|
|
107
|
+
answer: (tb?.text || '').trim(),
|
|
108
|
+
citations: annotations.slice(0, maxResults),
|
|
109
|
+
durationMs: Date.now() - t0,
|
|
110
|
+
usage: j?.usage || null,
|
|
111
|
+
warnings,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { CACHE_PATH, readJson, writeJson } from './config.mjs'
|
|
3
|
+
|
|
4
|
+
const DEFAULT_CACHE_STATE = {
|
|
5
|
+
entries: {},
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const FLUSH_DELAY_MS = 5000
|
|
9
|
+
|
|
10
|
+
let cacheDirty = false
|
|
11
|
+
let cacheFlushTimer = null
|
|
12
|
+
let activeCacheState = null
|
|
13
|
+
let lastCacheFlushWarnAt = 0
|
|
14
|
+
|
|
15
|
+
function nowMs() {
|
|
16
|
+
return Date.now()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 5s debounce so a single search invocation that touches the cache multiple
|
|
20
|
+
// times (lookup + insert + prune) coalesces into one writeJson roundtrip.
|
|
21
|
+
// Without this, callers like crawl/batch that don't explicitly flush would
|
|
22
|
+
// either spam fsync (immediate write per mutation) or silently drop dirty
|
|
23
|
+
// state on crash (bare dirty flag).
|
|
24
|
+
function scheduleCacheFlush(state) {
|
|
25
|
+
cacheDirty = true
|
|
26
|
+
activeCacheState = state
|
|
27
|
+
if (cacheFlushTimer) return
|
|
28
|
+
cacheFlushTimer = setTimeout(() => {
|
|
29
|
+
cacheFlushTimer = null
|
|
30
|
+
flushCacheState()
|
|
31
|
+
}, FLUSH_DELAY_MS)
|
|
32
|
+
if (cacheFlushTimer.unref) cacheFlushTimer.unref()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function flushCacheState() {
|
|
36
|
+
if (cacheFlushTimer) {
|
|
37
|
+
clearTimeout(cacheFlushTimer)
|
|
38
|
+
cacheFlushTimer = null
|
|
39
|
+
}
|
|
40
|
+
if (cacheDirty && activeCacheState) {
|
|
41
|
+
try {
|
|
42
|
+
writeJson(CACHE_PATH, activeCacheState)
|
|
43
|
+
cacheDirty = false
|
|
44
|
+
} catch (err) {
|
|
45
|
+
// Cache state is best-effort. Windows AV/indexer can hold the
|
|
46
|
+
// destination open. Keep the dirty state and retry quietly.
|
|
47
|
+
const now = Date.now()
|
|
48
|
+
if (now - lastCacheFlushWarnAt > 60000) {
|
|
49
|
+
lastCacheFlushWarnAt = now
|
|
50
|
+
process.stderr.write(`[search-cache] flushCacheState delayed: ${err?.code || err?.message || err}\n`)
|
|
51
|
+
}
|
|
52
|
+
if (!cacheFlushTimer) {
|
|
53
|
+
cacheFlushTimer = setTimeout(() => {
|
|
54
|
+
cacheFlushTimer = null
|
|
55
|
+
flushCacheState()
|
|
56
|
+
}, FLUSH_DELAY_MS * 2)
|
|
57
|
+
if (cacheFlushTimer.unref) cacheFlushTimer.unref()
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
process.on('exit', flushCacheState)
|
|
64
|
+
|
|
65
|
+
export { flushCacheState }
|
|
66
|
+
|
|
67
|
+
let _instance = null
|
|
68
|
+
|
|
69
|
+
export function loadCacheState() {
|
|
70
|
+
if (_instance) return _instance
|
|
71
|
+
const state = readJson(CACHE_PATH, DEFAULT_CACHE_STATE)
|
|
72
|
+
if (!state.entries || typeof state.entries !== 'object') {
|
|
73
|
+
state.entries = {}
|
|
74
|
+
}
|
|
75
|
+
_instance = state
|
|
76
|
+
activeCacheState = state
|
|
77
|
+
pruneExpiredEntries(state)
|
|
78
|
+
return state
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function buildCacheKey(namespace, payload) {
|
|
82
|
+
const hash = crypto
|
|
83
|
+
.createHash('sha256')
|
|
84
|
+
.update(JSON.stringify(payload))
|
|
85
|
+
.digest('hex')
|
|
86
|
+
return `${namespace}:${hash}`
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getCachedEntry(state, key) {
|
|
90
|
+
const entry = state.entries[key]
|
|
91
|
+
if (!entry) return null
|
|
92
|
+
if (entry.expiresAt && entry.expiresAt <= nowMs()) {
|
|
93
|
+
delete state.entries[key]
|
|
94
|
+
scheduleCacheFlush(state)
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
return entry
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function setCachedEntry(state, key, payload, ttlMs) {
|
|
101
|
+
const cachedAt = nowMs()
|
|
102
|
+
state.entries[key] = {
|
|
103
|
+
cachedAt,
|
|
104
|
+
expiresAt: cachedAt + ttlMs,
|
|
105
|
+
payload,
|
|
106
|
+
}
|
|
107
|
+
scheduleCacheFlush(state)
|
|
108
|
+
return state.entries[key]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function buildCacheMeta(entry, hit) {
|
|
112
|
+
return {
|
|
113
|
+
hit,
|
|
114
|
+
cachedAt: entry ? new Date(entry.cachedAt).toISOString() : null,
|
|
115
|
+
expiresAt: entry ? new Date(entry.expiresAt).toISOString() : null,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function pruneExpiredEntries(state) {
|
|
120
|
+
const current = nowMs()
|
|
121
|
+
let dirty = false
|
|
122
|
+
for (const [key, entry] of Object.entries(state.entries)) {
|
|
123
|
+
if (entry?.expiresAt && entry.expiresAt <= current) {
|
|
124
|
+
delete state.entries[key]
|
|
125
|
+
dirty = true
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (dirty) {
|
|
129
|
+
scheduleCacheFlush(state)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
import { resolvePluginData } from '../../shared/plugin-paths.mjs'
|
|
5
|
+
import { renameWithRetrySync, writeJsonAtomicSync } from '../../shared/atomic-file.mjs'
|
|
6
|
+
import { readSection, updateSection, stripGeneratedMarker, CONFIG_PATH as MIXDOG_CONFIG_PATH, getSearchApiKey } from '../../shared/config.mjs'
|
|
7
|
+
|
|
8
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
// src/search/lib/config.mjs → plugin root is three levels up (src/search/lib → src/search → src → plugin root).
|
|
10
|
+
export const PLUGIN_ROOT = process.env.CLAUDE_PLUGIN_ROOT || path.resolve(currentDir, '..', '..', '..')
|
|
11
|
+
|
|
12
|
+
// Unified mode: search shares the plugin data dir with the rest of mixdog.
|
|
13
|
+
const SHARED_DATA_DIR = resolvePluginData()
|
|
14
|
+
if (!SHARED_DATA_DIR) throw new Error('[search-config] resolvePluginData() returned falsy — plugin data dir not configured')
|
|
15
|
+
export const DATA_DIR = SHARED_DATA_DIR
|
|
16
|
+
export const USAGE_PATH = path.join(DATA_DIR, 'usage.local.json')
|
|
17
|
+
export const CACHE_PATH = path.join(DATA_DIR, 'cache.local.json')
|
|
18
|
+
|
|
19
|
+
// Per-provider default models. Single source of truth for any site that
|
|
20
|
+
// needs a fallback when the user config lacks an explicit override.
|
|
21
|
+
export const DEFAULT_MODELS = {
|
|
22
|
+
openai: 'gpt-5.5',
|
|
23
|
+
gemini: 'gemini-3-flash-preview',
|
|
24
|
+
xai: 'grok-4.3',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const DEFAULT_CONFIG = {
|
|
28
|
+
// Single active search backend. Switching here changes which backend the
|
|
29
|
+
// `search` MCP tool calls — does not require credentials for the unused
|
|
30
|
+
// backends. No priority chain / fallback — the selected provider is the
|
|
31
|
+
// only one tried; on failure the call throws.
|
|
32
|
+
provider: 'anthropic-oauth',
|
|
33
|
+
// Per-provider model override (only used by providers that take a model arg).
|
|
34
|
+
// Anthropic OAuth is haiku-fixed (third-party policy + practical quota).
|
|
35
|
+
models: {},
|
|
36
|
+
rawSearch: {
|
|
37
|
+
maxResults: 10,
|
|
38
|
+
credentials: {
|
|
39
|
+
firecrawl: {
|
|
40
|
+
apiKey: '',
|
|
41
|
+
},
|
|
42
|
+
tavily: {
|
|
43
|
+
apiKey: '',
|
|
44
|
+
},
|
|
45
|
+
exa: {
|
|
46
|
+
apiKey: '',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
requestTimeoutMs: 120000,
|
|
51
|
+
crawl: {
|
|
52
|
+
maxPages: 10,
|
|
53
|
+
maxDepth: 2,
|
|
54
|
+
sameDomainOnly: true,
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function ensureDir(dirPath) {
|
|
59
|
+
fs.mkdirSync(dirPath, { recursive: true })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function ensureDataDir() {
|
|
63
|
+
ensureDir(DATA_DIR)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function readJson(filePath, fallback) {
|
|
67
|
+
try {
|
|
68
|
+
const raw = fs.readFileSync(filePath, 'utf8')
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(raw)
|
|
71
|
+
} catch (parseErr) {
|
|
72
|
+
try { renameWithRetrySync(filePath, filePath + '.corrupt.' + Date.now()) } catch {}
|
|
73
|
+
process.stderr.write(`[search-config] corrupt JSON backed up: ${filePath}\n`)
|
|
74
|
+
throw parseErr
|
|
75
|
+
}
|
|
76
|
+
} catch (err) {
|
|
77
|
+
if (err.code === 'ENOENT') return fallback
|
|
78
|
+
throw err
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function writeJson(filePath, value) {
|
|
83
|
+
ensureDir(path.dirname(filePath))
|
|
84
|
+
writeJsonAtomicSync(filePath, value, { lock: true, fsyncDir: true })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function hasKeys(value) {
|
|
88
|
+
return !!value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length > 0
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function saveConfig(config) {
|
|
92
|
+
updateSection('search', () => stripGeneratedMarker(config) || {})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function finiteInt(value, { min, max, def }) {
|
|
96
|
+
const n = typeof value === 'number' ? value : Number(value)
|
|
97
|
+
if (!Number.isFinite(n)) return def
|
|
98
|
+
const i = Math.trunc(n)
|
|
99
|
+
if (i < min) return min
|
|
100
|
+
if (i > max) return max
|
|
101
|
+
return i
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function strictBool(value, def) {
|
|
105
|
+
if (value === true) return true
|
|
106
|
+
if (value === false) return false
|
|
107
|
+
return def
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function loadConfig() {
|
|
111
|
+
ensureDataDir()
|
|
112
|
+
let config = readSection('search')
|
|
113
|
+
if (!hasKeys(config)) {
|
|
114
|
+
saveConfig(DEFAULT_CONFIG)
|
|
115
|
+
config = DEFAULT_CONFIG
|
|
116
|
+
process.stderr.write(
|
|
117
|
+
`mixdog-search: default config created in ${MIXDOG_CONFIG_PATH} (section: search)\n` +
|
|
118
|
+
' use /setup to change provider priority and crawl defaults.\n',
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
const merged = {
|
|
122
|
+
...DEFAULT_CONFIG,
|
|
123
|
+
...config,
|
|
124
|
+
rawSearch: {
|
|
125
|
+
...DEFAULT_CONFIG.rawSearch,
|
|
126
|
+
...(config?.rawSearch || {}),
|
|
127
|
+
credentials: {
|
|
128
|
+
...DEFAULT_CONFIG.rawSearch.credentials,
|
|
129
|
+
...(config?.rawSearch?.credentials || {}),
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
crawl: {
|
|
133
|
+
...DEFAULT_CONFIG.crawl,
|
|
134
|
+
...(config?.crawl || {}),
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
merged.requestTimeoutMs = finiteInt(merged.requestTimeoutMs, {
|
|
138
|
+
min: 1000,
|
|
139
|
+
max: 300000,
|
|
140
|
+
def: DEFAULT_CONFIG.requestTimeoutMs,
|
|
141
|
+
})
|
|
142
|
+
merged.crawl.maxPages = finiteInt(merged.crawl.maxPages, {
|
|
143
|
+
min: 1,
|
|
144
|
+
max: 200,
|
|
145
|
+
def: DEFAULT_CONFIG.crawl.maxPages,
|
|
146
|
+
})
|
|
147
|
+
merged.crawl.maxDepth = finiteInt(merged.crawl.maxDepth, {
|
|
148
|
+
min: 0,
|
|
149
|
+
max: 5,
|
|
150
|
+
def: DEFAULT_CONFIG.crawl.maxDepth,
|
|
151
|
+
})
|
|
152
|
+
merged.crawl.sameDomainOnly = strictBool(merged.crawl.sameDomainOnly, true)
|
|
153
|
+
return merged
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function getRawSearchMaxResults(config) {
|
|
157
|
+
return config.rawSearch?.maxResults || DEFAULT_CONFIG.rawSearch.maxResults
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function getRawProviderApiKey(_config, provider) {
|
|
161
|
+
return getSearchApiKey(provider)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function getRawProviderCredentialSource(config, provider, env = process.env) {
|
|
165
|
+
if (getRawProviderApiKey(config, provider)) {
|
|
166
|
+
return 'config'
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const envKeyByProvider = {
|
|
170
|
+
firecrawl: 'FIRECRAWL_API_KEY',
|
|
171
|
+
tavily: 'TAVILY_API_KEY',
|
|
172
|
+
'xai-api': ['XAI_API_KEY', 'GROK_API_KEY'],
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const envKey = envKeyByProvider[provider]
|
|
176
|
+
if (envKey) {
|
|
177
|
+
const keys = Array.isArray(envKey) ? envKey : [envKey]
|
|
178
|
+
if (keys.some(k => env?.[k])) {
|
|
179
|
+
return 'env'
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return null
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function getRequestTimeoutMs(config) {
|
|
187
|
+
return config.requestTimeoutMs || DEFAULT_CONFIG.requestTimeoutMs
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function getFirecrawlApiKey(config) {
|
|
191
|
+
return getRawProviderApiKey(config, 'firecrawl') || ''
|
|
192
|
+
}
|