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,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Per-turn latency logger. Opt-in via MIXDOG_PERF=1.
|
|
4
|
+
// Usage: `node turn-timer.cjs start` on UserPromptSubmit,
|
|
5
|
+
// `node turn-timer.cjs stop` on Stop.
|
|
6
|
+
// Writes one JSON line per completed turn to ${CLAUDE_PLUGIN_DATA}/perf.log.
|
|
7
|
+
|
|
8
|
+
// Default ON; opt out with MIXDOG_PERF=0|false|no|off.
|
|
9
|
+
if (/^(0|false|no|off)$/i.test(String(process.env.MIXDOG_PERF || ''))) process.exit(0);
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
|
|
15
|
+
const HOOK_ENTERED_AT = Date.now();
|
|
16
|
+
|
|
17
|
+
let raw = '';
|
|
18
|
+
try { raw = fs.readFileSync(0, 'utf8'); } catch {}
|
|
19
|
+
let ev = {};
|
|
20
|
+
try { ev = raw ? JSON.parse(raw) : {}; } catch {}
|
|
21
|
+
|
|
22
|
+
if (ev.isSidechain) process.exit(0);
|
|
23
|
+
if (ev.is_sidechain) process.exit(0);
|
|
24
|
+
if (ev.agentId) process.exit(0);
|
|
25
|
+
if (ev.agent_id) process.exit(0);
|
|
26
|
+
|
|
27
|
+
const mode = process.argv[2] || '';
|
|
28
|
+
const DATA_DIR = process.env.CLAUDE_PLUGIN_DATA;
|
|
29
|
+
if (!DATA_DIR) process.exit(0);
|
|
30
|
+
|
|
31
|
+
const RUNTIME = path.join(os.tmpdir(), 'mixdog');
|
|
32
|
+
try { fs.mkdirSync(RUNTIME, { recursive: true }); } catch {}
|
|
33
|
+
try { fs.mkdirSync(DATA_DIR, { recursive: true }); } catch {}
|
|
34
|
+
|
|
35
|
+
const sessionId = ev.session_id || ev.sessionId;
|
|
36
|
+
if (!sessionId) process.exit(0);
|
|
37
|
+
const markFile = path.join(RUNTIME, `turn-${sessionId}.mark`);
|
|
38
|
+
const logFile = path.join(DATA_DIR, 'perf.log');
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
|
|
41
|
+
function appendLine(obj) {
|
|
42
|
+
try {
|
|
43
|
+
try {
|
|
44
|
+
const st = fs.statSync(logFile);
|
|
45
|
+
if (st.size > 5 * 1024 * 1024) fs.renameSync(logFile, logFile + '.1');
|
|
46
|
+
} catch {}
|
|
47
|
+
fs.appendFileSync(logFile, JSON.stringify(obj) + '\n');
|
|
48
|
+
} catch (e) {
|
|
49
|
+
process.stderr.write(`[turn-timer] append failed: ${e.message}\n`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (mode === 'start') {
|
|
54
|
+
try {
|
|
55
|
+
fs.writeFileSync(markFile, JSON.stringify({ t: now, session: sessionId, ppid: process.ppid }));
|
|
56
|
+
} catch (e) {
|
|
57
|
+
process.stderr.write(`[turn-timer] mark failed: ${e.message}\n`);
|
|
58
|
+
}
|
|
59
|
+
appendLine({
|
|
60
|
+
kind: 'start',
|
|
61
|
+
ts: new Date(now).toISOString(),
|
|
62
|
+
session: sessionId,
|
|
63
|
+
hook_ms: Date.now() - HOOK_ENTERED_AT,
|
|
64
|
+
});
|
|
65
|
+
} else if (mode === 'stop') {
|
|
66
|
+
let startTs = null;
|
|
67
|
+
try {
|
|
68
|
+
const m = JSON.parse(fs.readFileSync(markFile, 'utf8'));
|
|
69
|
+
startTs = m.t;
|
|
70
|
+
fs.unlinkSync(markFile);
|
|
71
|
+
} catch {}
|
|
72
|
+
appendLine({
|
|
73
|
+
kind: 'stop',
|
|
74
|
+
ts: new Date(now).toISOString(),
|
|
75
|
+
session: sessionId,
|
|
76
|
+
duration_ms: startTs ? now - startTs : null,
|
|
77
|
+
stop_reason: ev.stop_reason || null,
|
|
78
|
+
hook_ms: Date.now() - HOOK_ENTERED_AT,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
process.exit(0);
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* mixdog CLAUDE.md managed-block writer.
|
|
5
|
+
*
|
|
6
|
+
* Manages a single marker-delimited block inside a CLAUDE.md file.
|
|
7
|
+
* Normally only content *between* the markers is touched — anything the
|
|
8
|
+
* user has written outside the block is preserved verbatim. The sole
|
|
9
|
+
* exception is the FIRST takeover of a non-empty, marker-less file: the
|
|
10
|
+
* untouched original is backed up once to
|
|
11
|
+
* <backupRoot>/install-restore/claude-md-original.md (atomically) and the
|
|
12
|
+
* file is then REPLACED with just the managed block. If that backup
|
|
13
|
+
* cannot be written, the writer falls back to appending the block so user
|
|
14
|
+
* content is never destroyed without a confirmed backup.
|
|
15
|
+
*
|
|
16
|
+
* Every write also purges blocks tagged with legacy markers (see
|
|
17
|
+
* LEGACY_MARKERS below) so a plugin rename never leaves stale copies
|
|
18
|
+
* behind. Duplicate current-marker blocks (e.g. authored by hand) are
|
|
19
|
+
* collapsed to the first occurrence on every write.
|
|
20
|
+
*
|
|
21
|
+
* All writes are atomic (temp file + rename) to prevent partial writes
|
|
22
|
+
* from corrupting the target file.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
const os = require('os');
|
|
28
|
+
|
|
29
|
+
const MARKER_START = '<!-- BEGIN mixdog managed -->';
|
|
30
|
+
const MARKER_END = '<!-- END mixdog managed -->';
|
|
31
|
+
const RENAME_RETRY_CODES = new Set(['EPERM', 'EACCES', 'EBUSY', 'EEXIST']);
|
|
32
|
+
const RENAME_BACKOFFS_MS = [25, 50, 100, 200, 400, 800, 1200, 1600];
|
|
33
|
+
|
|
34
|
+
function sleepSync(ms) {
|
|
35
|
+
try {
|
|
36
|
+
const buf = new SharedArrayBuffer(4);
|
|
37
|
+
Atomics.wait(new Int32Array(buf), 0, 0, ms);
|
|
38
|
+
} catch {
|
|
39
|
+
const end = Date.now() + ms;
|
|
40
|
+
while (Date.now() < end) {}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function renameWithRetrySync(src, dst) {
|
|
45
|
+
let lastErr = null;
|
|
46
|
+
for (let attempt = 0; attempt <= RENAME_BACKOFFS_MS.length; attempt++) {
|
|
47
|
+
try {
|
|
48
|
+
fs.renameSync(src, dst);
|
|
49
|
+
return;
|
|
50
|
+
} catch (err) {
|
|
51
|
+
lastErr = err;
|
|
52
|
+
if (!RENAME_RETRY_CODES.has(err && err.code) || attempt >= RENAME_BACKOFFS_MS.length) break;
|
|
53
|
+
sleepSync(RENAME_BACKOFFS_MS[attempt] + Math.floor(Math.random() * 40));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
throw lastErr;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function withFileLockSync(lockPath, fn) {
|
|
60
|
+
const deadline = Date.now() + 3000;
|
|
61
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
62
|
+
while (Date.now() <= deadline) {
|
|
63
|
+
let fd = null;
|
|
64
|
+
try {
|
|
65
|
+
fd = fs.openSync(lockPath, 'wx');
|
|
66
|
+
try { fs.writeFileSync(fd, `${process.pid} ${Date.now()}\n`, 'utf8'); } catch {}
|
|
67
|
+
try { return fn(); }
|
|
68
|
+
finally {
|
|
69
|
+
try { if (fd !== null) fs.closeSync(fd); } catch {}
|
|
70
|
+
try { fs.unlinkSync(lockPath); } catch {}
|
|
71
|
+
}
|
|
72
|
+
} catch (err) {
|
|
73
|
+
try { if (fd !== null) fs.closeSync(fd); } catch {}
|
|
74
|
+
if (err && err.code !== 'EEXIST') throw err;
|
|
75
|
+
try {
|
|
76
|
+
const st = fs.statSync(lockPath);
|
|
77
|
+
if (Date.now() - st.mtimeMs > 30000) {
|
|
78
|
+
try { fs.unlinkSync(lockPath); } catch {}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
} catch {}
|
|
82
|
+
sleepSync(25 + Math.floor(Math.random() * 35));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Lock contention past the deadline: fail closed rather than executing
|
|
86
|
+
// fn() unlocked. Concurrent CLAUDE.md writers without a lock can
|
|
87
|
+
// overwrite each other's updates (last-writer-wins on an unrelated
|
|
88
|
+
// managed-block edit corrupts the file). Stale-lock removal already
|
|
89
|
+
// happens above via the 30s mtime check, so reaching here means a
|
|
90
|
+
// genuinely contested lock held by a live writer.
|
|
91
|
+
throw Object.assign(new Error(`[claude-md-writer] file lock not acquired within 3000ms: ${lockPath}`), { code: 'ELOCKED' });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Marker pairs from previous plugin names. Every write strips any block
|
|
95
|
+
// delimited by these so a rename never leaves the old block behind next
|
|
96
|
+
// to the new one. Append new entries in lockstep with MARKER_START/END
|
|
97
|
+
// whenever the plugin is renamed again.
|
|
98
|
+
const LEGACY_MARKERS = Object.freeze([
|
|
99
|
+
Object.freeze({
|
|
100
|
+
start: '<!-- BEGIN trib-plugin managed -->',
|
|
101
|
+
end: '<!-- END trib-plugin managed -->',
|
|
102
|
+
}),
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Expand a leading `~` to the current user's home directory.
|
|
107
|
+
* Any other path is returned unchanged.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} p
|
|
110
|
+
* @returns {string}
|
|
111
|
+
*/
|
|
112
|
+
function expandHome(p) {
|
|
113
|
+
if (typeof p !== 'string' || !p) return p;
|
|
114
|
+
if (p === '~') return os.homedir();
|
|
115
|
+
if (p.startsWith('~/') || p.startsWith('~\\')) {
|
|
116
|
+
return path.join(os.homedir(), p.slice(2));
|
|
117
|
+
}
|
|
118
|
+
return p;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Resolve the mixdog user-data backup root. Mirrors getBackupRoot() in
|
|
123
|
+
* src/shared/user-data-guard.mjs (env override, else ~/.claude/backups/
|
|
124
|
+
* mixdog-user-data) — kept as a tiny local resolver because this is a
|
|
125
|
+
* .cjs module that cannot import the ESM guard.
|
|
126
|
+
*/
|
|
127
|
+
function userDataBackupRoot() {
|
|
128
|
+
return process.env.MIXDOG_USER_DATA_BACKUP_ROOT
|
|
129
|
+
|| path.join(os.homedir(), '.claude', 'backups', 'mixdog-user-data');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* On the FIRST takeover of a user-authored CLAUDE.md (no managed markers
|
|
134
|
+
* yet, real content present) back up the untouched original so it can be
|
|
135
|
+
* restored later. Writes only when the backup does not already exist —
|
|
136
|
+
* the first takeover wins; later marker-less states must never clobber
|
|
137
|
+
* the true original. Returns true when a backup is in place (already
|
|
138
|
+
* existed or freshly written), false when it could not be written (the
|
|
139
|
+
* caller then falls back to append so user content is never destroyed).
|
|
140
|
+
*
|
|
141
|
+
* The backup is written atomically (temp file + rename, same pattern as
|
|
142
|
+
* the target file) so a crashed attempt can never leave a truncated
|
|
143
|
+
* backup. An existing dest is therefore trustworthy going forward and is
|
|
144
|
+
* treated as a completed first takeover.
|
|
145
|
+
*/
|
|
146
|
+
function ensureOriginalBackup(originalContent) {
|
|
147
|
+
const dest = path.join(userDataBackupRoot(), 'install-restore', 'claude-md-original.md');
|
|
148
|
+
try {
|
|
149
|
+
if (fs.existsSync(dest)) return true;
|
|
150
|
+
_atomicWriteUnlocked(dest, originalContent);
|
|
151
|
+
return true;
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Write `data` to `filePath` atomically via a sibling temp file.
|
|
159
|
+
* Creates parent directories as needed.
|
|
160
|
+
*/
|
|
161
|
+
function atomicWrite(filePath, data) {
|
|
162
|
+
return withFileLockSync(`${filePath}.lock`, () => _atomicWriteUnlocked(filePath, data));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Inner write helper used when the caller already holds `${filePath}.lock`.
|
|
166
|
+
// Splitting this out lets upsertManagedBlock / removeManagedBlock perform
|
|
167
|
+
// read+compute+rename under a SINGLE lock (RMW linearisable), mirroring
|
|
168
|
+
// the withConfigLock pattern in src/shared/config.mjs. Calling
|
|
169
|
+
// atomicWrite() from inside the same lock would deadlock on EEXIST.
|
|
170
|
+
function _atomicWriteUnlocked(filePath, data) {
|
|
171
|
+
const dir = path.dirname(filePath);
|
|
172
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
173
|
+
const tmp = path.join(
|
|
174
|
+
dir,
|
|
175
|
+
`.${path.basename(filePath)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`,
|
|
176
|
+
);
|
|
177
|
+
fs.writeFileSync(tmp, data, 'utf8');
|
|
178
|
+
try {
|
|
179
|
+
renameWithRetrySync(tmp, filePath);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
try { fs.unlinkSync(tmp); } catch {}
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Build the managed block string from its inner content.
|
|
188
|
+
*/
|
|
189
|
+
function wrapBlock(content) {
|
|
190
|
+
const body = typeof content === 'string' ? content : '';
|
|
191
|
+
return `${MARKER_START}\n${body}\n${MARKER_END}`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Strip every (start, end) block from `text`. Handles duplicates by
|
|
196
|
+
* looping until no more pairs remain. Invalid pairs (end before start,
|
|
197
|
+
* unmatched markers) are left alone.
|
|
198
|
+
*/
|
|
199
|
+
function stripAllBlocks(text, start, end) {
|
|
200
|
+
let out = text;
|
|
201
|
+
let cursor = 0;
|
|
202
|
+
while (cursor <= out.length) {
|
|
203
|
+
const si = out.indexOf(start, cursor);
|
|
204
|
+
if (si === -1) break;
|
|
205
|
+
const ei = out.indexOf(end, si + start.length);
|
|
206
|
+
if (ei === -1) break;
|
|
207
|
+
// If another start marker appears before the next end marker, the
|
|
208
|
+
// current `si` is an unmatched start and pairing it with `ei` would
|
|
209
|
+
// delete the later valid block too. Leave the orphan in place and
|
|
210
|
+
// continue scanning from the inner start (which may itself pair with
|
|
211
|
+
// `ei`). This honours the contract that unmatched markers are left
|
|
212
|
+
// alone.
|
|
213
|
+
const nextStart = out.indexOf(start, si + start.length);
|
|
214
|
+
if (nextStart !== -1 && nextStart < ei) {
|
|
215
|
+
cursor = nextStart;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
out = out.slice(0, si) + out.slice(ei + end.length);
|
|
219
|
+
cursor = si;
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Remove any legacy-marker blocks from `text`. No-op when none are
|
|
226
|
+
* present, so it is safe to call on every upsert.
|
|
227
|
+
*/
|
|
228
|
+
function stripLegacyBlocks(text) {
|
|
229
|
+
let out = text;
|
|
230
|
+
for (const m of LEGACY_MARKERS) {
|
|
231
|
+
out = stripAllBlocks(out, m.start, m.end);
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Insert or update the managed block inside `filePath`.
|
|
238
|
+
*
|
|
239
|
+
* Behavior:
|
|
240
|
+
* - `~` in filePath is expanded via os.homedir()
|
|
241
|
+
* - If the file does not exist, create it containing just the block
|
|
242
|
+
* - Legacy-marker blocks (see LEGACY_MARKERS) are always purged first
|
|
243
|
+
* so plugin renames never leave stale copies behind
|
|
244
|
+
* - The FIRST current-marker block is replaced in place; any extra
|
|
245
|
+
* duplicates elsewhere in the file are removed
|
|
246
|
+
* - If no current-marker block exists in a non-empty file (first
|
|
247
|
+
* takeover of user-authored content), back up the untouched original
|
|
248
|
+
* to <backupRoot>/install-restore/claude-md-original.md (written once,
|
|
249
|
+
* first takeover wins) and REPLACE the file with just the block. If
|
|
250
|
+
* the backup cannot be written, fall back to appending the block so
|
|
251
|
+
* user content is never destroyed without a confirmed backup.
|
|
252
|
+
* - An empty / whitespace-only file is simply replaced with the block
|
|
253
|
+
* - Blank-line runs introduced by the stripping are collapsed and the
|
|
254
|
+
* file always ends with exactly one trailing newline
|
|
255
|
+
*
|
|
256
|
+
* @param {string} filePath
|
|
257
|
+
* @param {string} content — raw inner content (no markers)
|
|
258
|
+
*/
|
|
259
|
+
function upsertManagedBlock(filePath, content) {
|
|
260
|
+
const resolved = expandHome(filePath);
|
|
261
|
+
const block = wrapBlock(content);
|
|
262
|
+
|
|
263
|
+
// Hold the file lock across the FULL read-modify-write window so a
|
|
264
|
+
// concurrent writer can't slip a different revision in between our
|
|
265
|
+
// read and our rename (last-writer-wins on managed-block edits).
|
|
266
|
+
// Mirrors the withConfigLock RMW pattern in src/shared/config.mjs.
|
|
267
|
+
withFileLockSync(`${resolved}.lock`, () => {
|
|
268
|
+
let existing = null;
|
|
269
|
+
try {
|
|
270
|
+
existing = fs.readFileSync(resolved, 'utf8');
|
|
271
|
+
} catch (err) {
|
|
272
|
+
if (err && err.code !== 'ENOENT') throw err;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (existing === null) {
|
|
276
|
+
// File does not exist — create with just the block (trailing newline).
|
|
277
|
+
_atomicWriteUnlocked(resolved, block + '\n');
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Purge legacy-marker blocks from any earlier plugin name. Only the
|
|
282
|
+
// gap left by legacy removal is normalised — user content outside the
|
|
283
|
+
// managed/legacy markers must be preserved verbatim per the header
|
|
284
|
+
// contract. Whole-file collapse / trailing-ws strip would silently
|
|
285
|
+
// rewrite the user's prose, code blocks, and intentional blank runs.
|
|
286
|
+
const cleaned = stripLegacyBlocks(existing);
|
|
287
|
+
|
|
288
|
+
const startIdx = cleaned.indexOf(MARKER_START);
|
|
289
|
+
const endIdx = cleaned.indexOf(MARKER_END, startIdx + MARKER_START.length);
|
|
290
|
+
|
|
291
|
+
let next;
|
|
292
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
293
|
+
// Replace the first current-marker block in place. Any additional
|
|
294
|
+
// duplicate blocks in the tail are stripped so only one remains.
|
|
295
|
+
const before = cleaned.slice(0, startIdx);
|
|
296
|
+
const afterRaw = cleaned.slice(endIdx + MARKER_END.length);
|
|
297
|
+
const after = stripAllBlocks(afterRaw, MARKER_START, MARKER_END);
|
|
298
|
+
next = before + block + after;
|
|
299
|
+
} else if (cleaned.trim().length === 0) {
|
|
300
|
+
// Empty / whitespace-only file: nothing to preserve, just replace.
|
|
301
|
+
next = block + '\n';
|
|
302
|
+
} else if (ensureOriginalBackup(existing)) {
|
|
303
|
+
// First takeover of user-authored content with a confirmed backup:
|
|
304
|
+
// replace the whole file with just the managed block.
|
|
305
|
+
next = block + '\n';
|
|
306
|
+
} else if (cleaned.endsWith('\n\n')) {
|
|
307
|
+
// Backup could not be written — fall back to append so user content
|
|
308
|
+
// is never destroyed without a confirmed backup.
|
|
309
|
+
next = cleaned + block + '\n';
|
|
310
|
+
} else if (cleaned.endsWith('\n')) {
|
|
311
|
+
next = cleaned + '\n' + block + '\n';
|
|
312
|
+
} else {
|
|
313
|
+
next = cleaned + '\n\n' + block + '\n';
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (next !== existing) _atomicWriteUnlocked(resolved, next);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Remove the managed block (markers inclusive) from `filePath`, plus
|
|
322
|
+
* any legacy-marker blocks.
|
|
323
|
+
*
|
|
324
|
+
* Behavior:
|
|
325
|
+
* - `~` in filePath is expanded via os.homedir()
|
|
326
|
+
* - No-op if the file does not exist
|
|
327
|
+
* - Surgical removal: only bytes between/including managed and
|
|
328
|
+
* legacy markers are touched; user content outside the markers
|
|
329
|
+
* is preserved verbatim (no blank-line collapse).
|
|
330
|
+
* - Atomic write
|
|
331
|
+
*
|
|
332
|
+
* @param {string} filePath
|
|
333
|
+
* @returns {boolean} true if an actual write happened, false if no-op
|
|
334
|
+
*/
|
|
335
|
+
function removeManagedBlock(filePath) {
|
|
336
|
+
const resolved = expandHome(filePath);
|
|
337
|
+
|
|
338
|
+
// Preserve the documented missing-file no-op before taking a lock:
|
|
339
|
+
// withFileLockSync creates the lock parent directory, which would be
|
|
340
|
+
// an observable side effect for a target that does not exist.
|
|
341
|
+
try {
|
|
342
|
+
fs.readFileSync(resolved, 'utf8');
|
|
343
|
+
} catch (err) {
|
|
344
|
+
if (err && err.code === 'ENOENT') return false;
|
|
345
|
+
throw err;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Single lock around read+compute+rename: a concurrent upsert from
|
|
349
|
+
// another process must not slip between our read and our write, or
|
|
350
|
+
// the removal can race a re-injection and either lose the write or
|
|
351
|
+
// resurrect a stale block. Mirrors upsertManagedBlock's single-lock
|
|
352
|
+
// RMW (and withConfigLock in src/shared/config.mjs).
|
|
353
|
+
return withFileLockSync(`${resolved}.lock`, () => {
|
|
354
|
+
let existing;
|
|
355
|
+
try {
|
|
356
|
+
existing = fs.readFileSync(resolved, 'utf8');
|
|
357
|
+
} catch (err) {
|
|
358
|
+
if (err && err.code === 'ENOENT') return false;
|
|
359
|
+
throw err;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Surgical removal: only bytes between/including the markers are
|
|
363
|
+
// touched. User content outside the managed/legacy markers is
|
|
364
|
+
// preserved verbatim per the header contract — whole-file collapse /
|
|
365
|
+
// trailing-ws strip would silently rewrite the user's prose, code
|
|
366
|
+
// blocks, and intentional blank runs (mirrors upsertManagedBlock's
|
|
367
|
+
// preservation guarantee on the file-existed path).
|
|
368
|
+
let next = existing;
|
|
369
|
+
next = stripAllBlocks(next, MARKER_START, MARKER_END);
|
|
370
|
+
next = stripLegacyBlocks(next);
|
|
371
|
+
|
|
372
|
+
if (next !== existing) {
|
|
373
|
+
_atomicWriteUnlocked(resolved, next);
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
return false;
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
module.exports = {
|
|
381
|
+
MARKER_START,
|
|
382
|
+
MARKER_END,
|
|
383
|
+
expandHome,
|
|
384
|
+
upsertManagedBlock,
|
|
385
|
+
removeManagedBlock,
|
|
386
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* CJS shim for shared config reading in hooks.
|
|
4
|
+
*
|
|
5
|
+
* Read-only mirror of src/shared/config.mjs's section accessor. Hook
|
|
6
|
+
* processes only read mixdog-config.json — never write, never rename.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { resolvePluginData } = require('./plugin-paths.cjs');
|
|
12
|
+
const { getSecret: _getSecret } = require('./keychain-cjs.cjs');
|
|
13
|
+
|
|
14
|
+
const DATA_DIR = resolvePluginData();
|
|
15
|
+
|
|
16
|
+
const GENERATED_KEY = '_generated';
|
|
17
|
+
|
|
18
|
+
function stripGeneratedMarker(data) {
|
|
19
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) return data;
|
|
20
|
+
if (!Object.prototype.hasOwnProperty.call(data, GENERATED_KEY)) return data;
|
|
21
|
+
const { [GENERATED_KEY]: _unused, ...rest } = data;
|
|
22
|
+
return rest;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readJsonFile(filePath) {
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readSection(section) {
|
|
34
|
+
const unified = readJsonFile(path.join(DATA_DIR, 'mixdog-config.json'));
|
|
35
|
+
if (!unified || typeof unified !== 'object') return {};
|
|
36
|
+
const raw = unified[section];
|
|
37
|
+
if (raw == null) return {};
|
|
38
|
+
return stripGeneratedMarker(raw) || {};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Secret-aware getters (CJS, for hooks) ────────────────────────────────────
|
|
42
|
+
// Read order: ENV MIXDOG_<UPPER_SNAKE> → OS keychain → null.
|
|
43
|
+
|
|
44
|
+
function _envKey(account) {
|
|
45
|
+
return 'MIXDOG_' + account.replace(/[.\s]+/g, '_').toUpperCase();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function _readSecret(account) {
|
|
49
|
+
const envVal = process.env[_envKey(account)];
|
|
50
|
+
if (envVal) return envVal;
|
|
51
|
+
try { return _getSecret(account); } catch { return null; }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getDiscordToken() {
|
|
55
|
+
return _readSecret('discord.token');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
readSection,
|
|
60
|
+
getDiscordToken,
|
|
61
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Single source for the hook IPC path. Centralizes an existing platform
|
|
4
|
+
// contract (not new heuristic). Rust mirror native/mixdog-shim/src/main.rs:34-43
|
|
5
|
+
// must stay in sync.
|
|
6
|
+
module.exports = function hookPipePath() {
|
|
7
|
+
return process.platform === 'win32'
|
|
8
|
+
? '\\\\.\\pipe\\mixdog-hooks'
|
|
9
|
+
: require('path').join(process.env.XDG_RUNTIME_DIR || '/tmp', 'mixdog-hooks.sock');
|
|
10
|
+
};
|