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,116 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* settings-loader.cjs
|
|
4
|
+
* Loads and merges Claude Code settings from three tiers (lowest → highest):
|
|
5
|
+
* 1. User global: ~/.claude/settings.json
|
|
6
|
+
* 2. Project: $CLAUDE_PROJECT_DIR/.claude/settings.json (or cwd/.claude/settings.json)
|
|
7
|
+
* 3. Project local: $CLAUDE_PROJECT_DIR/.claude/settings.local.json
|
|
8
|
+
*
|
|
9
|
+
* Only `permissions` sub-tree is returned; the rest is ignored.
|
|
10
|
+
* Pure fs/path — no external deps.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
|
|
17
|
+
// ── In-process mtime-driven cache ────────────────────────────────────────────
|
|
18
|
+
// Keyed by resolved file path. Each entry: { mtime: number|null, data: any }.
|
|
19
|
+
// Invalidated per-tier independently when mtime changes.
|
|
20
|
+
// TTL: never-expire by default (mtime-driven only).
|
|
21
|
+
const _fileCache = new Map();
|
|
22
|
+
|
|
23
|
+
function readJsonCached(filePath) {
|
|
24
|
+
let entry = _fileCache.get(filePath);
|
|
25
|
+
let mtime = null;
|
|
26
|
+
try { mtime = fs.statSync(filePath).mtimeMs; } catch { /* file absent */ }
|
|
27
|
+
if (entry && entry.mtime === mtime) return entry.data;
|
|
28
|
+
let data = null;
|
|
29
|
+
if (mtime !== null) {
|
|
30
|
+
try { data = JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { data = null; }
|
|
31
|
+
}
|
|
32
|
+
_fileCache.set(filePath, { mtime, data });
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Clear the entire settings cache (for test isolation). */
|
|
37
|
+
function clearSettingsCache() {
|
|
38
|
+
_fileCache.clear();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readJson(filePath) {
|
|
42
|
+
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
|
|
43
|
+
catch { return null; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Merge two permissions objects (base then overlay).
|
|
48
|
+
* Arrays are concatenated and de-duped; scalar fields are overwritten.
|
|
49
|
+
*/
|
|
50
|
+
function mergePermissions(base, overlay) {
|
|
51
|
+
if (!overlay) return base || {};
|
|
52
|
+
if (!base) return overlay || {};
|
|
53
|
+
|
|
54
|
+
const merged = Object.assign({}, base);
|
|
55
|
+
|
|
56
|
+
for (const key of ['allow', 'deny', 'ask']) {
|
|
57
|
+
const b = Array.isArray(base[key]) ? base[key] : [];
|
|
58
|
+
const o = Array.isArray(overlay[key]) ? overlay[key] : [];
|
|
59
|
+
const combined = [...b, ...o];
|
|
60
|
+
if (combined.length) merged[key] = [...new Set(combined)];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// scalar: overlay wins
|
|
64
|
+
if (overlay.defaultMode !== undefined) merged.defaultMode = overlay.defaultMode;
|
|
65
|
+
|
|
66
|
+
return merged;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Load and merge settings from all three tiers.
|
|
71
|
+
* Returns `{ allow: string[], deny: string[], ask: string[], defaultMode: string }`.
|
|
72
|
+
*/
|
|
73
|
+
function loadPermissions(projectDir) {
|
|
74
|
+
const home = os.homedir();
|
|
75
|
+
const projDir = projectDir ||
|
|
76
|
+
process.env.CLAUDE_PROJECT_DIR ||
|
|
77
|
+
process.cwd();
|
|
78
|
+
|
|
79
|
+
const userSettings = readJsonCached(path.join(home, '.claude', 'settings.json'));
|
|
80
|
+
const projectSettings = readJsonCached(path.join(projDir, '.claude', 'settings.json'));
|
|
81
|
+
const localSettings = readJsonCached(path.join(projDir, '.claude', 'settings.local.json'));
|
|
82
|
+
|
|
83
|
+
// Accept permissions from `settings.permissions` (canonical) OR top-level
|
|
84
|
+
// `allow`/`deny`/`ask`/`defaultMode` fields (common user shorthand).
|
|
85
|
+
function extractPerms(s) {
|
|
86
|
+
if (!s) return {};
|
|
87
|
+
const nested = (s.permissions && typeof s.permissions === 'object') ? s.permissions : {};
|
|
88
|
+
const topLevel = {};
|
|
89
|
+
for (const key of ['allow', 'deny', 'ask']) {
|
|
90
|
+
if (Array.isArray(s[key])) topLevel[key] = s[key];
|
|
91
|
+
}
|
|
92
|
+
if (typeof s.defaultMode === 'string') topLevel.defaultMode = s.defaultMode;
|
|
93
|
+
return mergePermissions(nested, topLevel);
|
|
94
|
+
}
|
|
95
|
+
const userPerms = extractPerms(userSettings);
|
|
96
|
+
const projectPerms = extractPerms(projectSettings);
|
|
97
|
+
const localPerms = extractPerms(localSettings);
|
|
98
|
+
|
|
99
|
+
let merged = mergePermissions({}, userPerms);
|
|
100
|
+
merged = mergePermissions(merged, projectPerms);
|
|
101
|
+
merged = mergePermissions(merged, localPerms);
|
|
102
|
+
|
|
103
|
+
const VALID_MODES = new Set(['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions', 'auto']);
|
|
104
|
+
const rawMode = merged.defaultMode;
|
|
105
|
+
const resolvedMode = (typeof rawMode === 'string' && VALID_MODES.has(rawMode))
|
|
106
|
+
? rawMode
|
|
107
|
+
: 'default';
|
|
108
|
+
return {
|
|
109
|
+
allow: Array.isArray(merged.allow) ? merged.allow : [],
|
|
110
|
+
deny: Array.isArray(merged.deny) ? merged.deny : [],
|
|
111
|
+
ask: Array.isArray(merged.ask) ? merged.ask : [],
|
|
112
|
+
defaultMode: resolvedMode,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = { loadPermissions, clearSettingsCache };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mixdog PostToolUse hook
|
|
3
|
+
*
|
|
4
|
+
* After any tool execution, drop a `tool-exec-{ts}-{rand}.signal` file into
|
|
5
|
+
* RUNTIME_ROOT containing { toolName, ts }. Consumers:
|
|
6
|
+
* - channels worker (main-session permission flow): fs.watch matches oldest
|
|
7
|
+
* pendingPermRequest by toolName → edits Discord message to
|
|
8
|
+
* "Allowed (terminal)".
|
|
9
|
+
* - pre-tool-subagent.cjs (sub-agent permission flow): polls for signal
|
|
10
|
+
* files with matching toolName created after the hook started →
|
|
11
|
+
* treats as terminal approval and exits.
|
|
12
|
+
*
|
|
13
|
+
* Consumers only unlink the signal file when they claim it; stale files are
|
|
14
|
+
* cleaned up by the channels worker's periodic sweep.
|
|
15
|
+
*/
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const os = require('os');
|
|
19
|
+
const crypto = require('crypto');
|
|
20
|
+
|
|
21
|
+
const RUNTIME_ROOT = path.join(os.tmpdir(), 'mixdog');
|
|
22
|
+
const SIGNAL_CONSUMER_MARKER = path.join(RUNTIME_ROOT, '.tool-exec-consumer');
|
|
23
|
+
const SUBAGENT_SIGNAL_CONSUMER_MARKER = path.join(RUNTIME_ROOT, '.tool-exec-subagent-consumer');
|
|
24
|
+
const SIGNAL_RE = /^tool-exec-\d+-[0-9a-f]+\.signal$/;
|
|
25
|
+
const SWEEP_MARKER = path.join(RUNTIME_ROOT, '.tool-exec-sweep');
|
|
26
|
+
const SWEEP_INTERVAL_MS = 30_000;
|
|
27
|
+
const SIGNAL_TTL_MS = 60_000;
|
|
28
|
+
|
|
29
|
+
function sweepStaleSignalsThrottled(now = Date.now()) {
|
|
30
|
+
try {
|
|
31
|
+
let lastSweep = 0;
|
|
32
|
+
try { lastSweep = fs.statSync(SWEEP_MARKER).mtimeMs; } catch {}
|
|
33
|
+
if (now - lastSweep < SWEEP_INTERVAL_MS) return;
|
|
34
|
+
try { fs.writeFileSync(SWEEP_MARKER, String(now)); } catch {}
|
|
35
|
+
const entries = fs.readdirSync(RUNTIME_ROOT);
|
|
36
|
+
for (const name of entries) {
|
|
37
|
+
if (!SIGNAL_RE.test(name)) continue;
|
|
38
|
+
const p = path.join(RUNTIME_ROOT, name);
|
|
39
|
+
try {
|
|
40
|
+
const st = fs.statSync(p);
|
|
41
|
+
if (now - st.mtimeMs > SIGNAL_TTL_MS) fs.unlinkSync(p);
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
44
|
+
} catch {}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let input = '';
|
|
48
|
+
process.stdin.on('data', d => { input += d; });
|
|
49
|
+
process.stdin.on('end', () => {
|
|
50
|
+
let toolName = '';
|
|
51
|
+
let filePath = '';
|
|
52
|
+
let toolUseId = '';
|
|
53
|
+
try {
|
|
54
|
+
if (input) {
|
|
55
|
+
const payload = JSON.parse(input);
|
|
56
|
+
toolName = payload?.tool_name || payload?.toolName || '';
|
|
57
|
+
filePath = payload?.tool_input?.file_path || payload?.toolInput?.file_path || '';
|
|
58
|
+
toolUseId = payload?.tool_use_id || payload?.toolUseId || '';
|
|
59
|
+
}
|
|
60
|
+
} catch { /* ignore parse errors */ }
|
|
61
|
+
|
|
62
|
+
if (!toolName) { process.exit(0); return; }
|
|
63
|
+
if (!fs.existsSync(SIGNAL_CONSUMER_MARKER) && !fs.existsSync(SUBAGENT_SIGNAL_CONSUMER_MARKER)) {
|
|
64
|
+
process.exit(0); return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
if (!fs.existsSync(RUNTIME_ROOT)) fs.mkdirSync(RUNTIME_ROOT, { recursive: true });
|
|
69
|
+
} catch { /* best-effort */ }
|
|
70
|
+
|
|
71
|
+
sweepStaleSignalsThrottled();
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// Channels worker also sweeps these files. The throttled local sweep above
|
|
75
|
+
// covers channels-disabled/degraded sessions without a per-tool readdir.
|
|
76
|
+
const rand = crypto.randomBytes(4).toString('hex');
|
|
77
|
+
const signalFile = path.join(RUNTIME_ROOT, `tool-exec-${Date.now()}-${rand}.signal`);
|
|
78
|
+
fs.writeFileSync(signalFile, JSON.stringify({ toolName, filePath, toolUseId, ts: Date.now() }));
|
|
79
|
+
} catch (err) {
|
|
80
|
+
process.stderr.write(`[post-tool-use] Failed to write signal file: ${err.message}\n`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
process.exit(0);
|
|
84
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* mixdog PreToolUse hook — MCP sandbox approval prompt (v2)
|
|
4
|
+
*
|
|
5
|
+
* Permission priority: deny > ask > allow > mode-default
|
|
6
|
+
*
|
|
7
|
+
* Settings are loaded from three tiers (project-local > project > user).
|
|
8
|
+
* Permission lists are evaluated in deny → ask → allow order.
|
|
9
|
+
* Mode default applies when no list matches:
|
|
10
|
+
* bypassPermissions → exit 0 (no interference)
|
|
11
|
+
* acceptEdits → readOnly tools + edit/write/apply_patch → exit 0;
|
|
12
|
+
* other tools outside cwd → ask
|
|
13
|
+
* plan → readOnly tools → exit 0; others → ask
|
|
14
|
+
* dontAsk → deny (no list match = explicit deny)
|
|
15
|
+
* default / unknown → outside-cwd → ask; updatedInput.cwd set to deepest existing ancestor
|
|
16
|
+
*
|
|
17
|
+
* On parse / unexpected error → exit 0 + stderr warn.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const os = require('os');
|
|
23
|
+
|
|
24
|
+
const { evaluatePermission } = require('./lib/permission-evaluator.cjs');
|
|
25
|
+
|
|
26
|
+
// ── constants ─────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const MCP_PREFIXES = [
|
|
29
|
+
'mcp__plugin_mixdog_mixdog__',
|
|
30
|
+
'mcp__plugin_mixdog_trib-plugin__',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// ── output helpers ────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
function emitDecision(decision, reason, updatedInput) {
|
|
36
|
+
const out = {
|
|
37
|
+
hookSpecificOutput: {
|
|
38
|
+
hookEventName: 'PreToolUse',
|
|
39
|
+
permissionDecision: decision,
|
|
40
|
+
permissionDecisionReason: reason,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
if (updatedInput !== undefined) {
|
|
44
|
+
out.hookSpecificOutput.updatedInput = updatedInput;
|
|
45
|
+
}
|
|
46
|
+
process.stdout.write(JSON.stringify(out));
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── main ──────────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
let input = '';
|
|
53
|
+
process.stdin.on('data', d => { input += d; });
|
|
54
|
+
process.stdin.on('end', () => {
|
|
55
|
+
try {
|
|
56
|
+
main();
|
|
57
|
+
} catch (err) {
|
|
58
|
+
process.stderr.write(`[pre-mcp-sandbox] Unexpected error: ${err.message}\n`);
|
|
59
|
+
process.exit(0); // default allow on unexpected error
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
function main() {
|
|
64
|
+
// 1. Parse payload
|
|
65
|
+
let payload;
|
|
66
|
+
try { payload = JSON.parse(input); }
|
|
67
|
+
catch {
|
|
68
|
+
process.exit(0); // malformed → default allow
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const toolName = payload?.tool_name || payload?.toolName || '';
|
|
73
|
+
const toolInput = payload?.tool_input ?? payload?.toolInput ?? {};
|
|
74
|
+
|
|
75
|
+
// 2. Only handle this plugin's MCP tools. Marketplace installs have used
|
|
76
|
+
// both server-name shapes; keep the standalone hook aligned with
|
|
77
|
+
// hook-pipe-server.mjs.
|
|
78
|
+
if (!MCP_PREFIXES.some(p => toolName.startsWith(p))) {
|
|
79
|
+
process.exit(0);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 3. Resolve user cwd
|
|
84
|
+
let userCwdRaw = payload?.cwd || '';
|
|
85
|
+
if (!userCwdRaw) {
|
|
86
|
+
const dataDir = process.env.CLAUDE_PLUGIN_DATA || '';
|
|
87
|
+
if (dataDir) {
|
|
88
|
+
try { userCwdRaw = fs.readFileSync(path.join(dataDir, 'user-cwd.txt'), 'utf8').trim(); } catch { /* ok */ }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!userCwdRaw) userCwdRaw = process.cwd();
|
|
92
|
+
|
|
93
|
+
const normCwdRaw = (userCwdRaw && path.sep === '\\')
|
|
94
|
+
? (() => { try { return path.normalize(userCwdRaw); } catch { return userCwdRaw; } })()
|
|
95
|
+
: userCwdRaw;
|
|
96
|
+
const userCwd = path.isAbsolute(normCwdRaw)
|
|
97
|
+
? path.normalize(normCwdRaw)
|
|
98
|
+
: path.resolve(normCwdRaw);
|
|
99
|
+
|
|
100
|
+
const projectDir = payload?.projectDir || payload?.project_dir ||
|
|
101
|
+
process.env.CLAUDE_PROJECT_DIR || userCwd;
|
|
102
|
+
const permissionMode = payload?.permissionMode || payload?.permission_mode || undefined;
|
|
103
|
+
|
|
104
|
+
const { loadPermissions } = require('./lib/settings-loader.cjs');
|
|
105
|
+
const settingsPerms = loadPermissions(projectDir);
|
|
106
|
+
const effectiveMode = permissionMode || settingsPerms.defaultMode;
|
|
107
|
+
|
|
108
|
+
// Bypass fast-path: bypassPermissions/auto are full-allow modes by design.
|
|
109
|
+
// When the user configured no deny rules, skip the evaluator entirely
|
|
110
|
+
// (~10-20ms/call). The owner has opted into full bypass; mixdog does not
|
|
111
|
+
// override it with its own hard-deny in this mode.
|
|
112
|
+
if ((effectiveMode === 'bypassPermissions' || effectiveMode === 'auto') &&
|
|
113
|
+
(!settingsPerms.deny || settingsPerms.deny.length === 0)) {
|
|
114
|
+
process.exit(0);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 4. Delegate to shared evaluator.
|
|
119
|
+
// bypass/auto modes still run the evaluator so that hard-deny rules
|
|
120
|
+
// (UNC paths, dangerous system paths) are enforced. Only 'ask' decisions
|
|
121
|
+
// are auto-approved under bypass; 'deny' is always respected.
|
|
122
|
+
const evalResult = evaluatePermission({
|
|
123
|
+
toolName,
|
|
124
|
+
toolInput,
|
|
125
|
+
permissionMode,
|
|
126
|
+
projectDir,
|
|
127
|
+
userCwd,
|
|
128
|
+
permissions: settingsPerms,
|
|
129
|
+
});
|
|
130
|
+
const { decision, reason, updatedInput } = evalResult;
|
|
131
|
+
|
|
132
|
+
// Fast-path for bypass/auto mode AFTER evaluator (deny already returned above
|
|
133
|
+
// if hard-deny matched; safe to skip ask/allow handling here).
|
|
134
|
+
if (effectiveMode === 'bypassPermissions' || effectiveMode === 'auto') {
|
|
135
|
+
if (decision === 'deny') {
|
|
136
|
+
emitDecision('deny', reason);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// ask/allow → auto-approve under bypass
|
|
140
|
+
process.exit(0);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (decision === 'allow') {
|
|
145
|
+
process.exit(0);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// For ask decisions in default mode, inject cwd into updatedInput so Claude
|
|
150
|
+
// Code can show the resolved sandbox root in the approval dialog.
|
|
151
|
+
// evaluatePermission returns updatedInput.cwd when firstOutsideResolved is set.
|
|
152
|
+
if (decision === 'ask') {
|
|
153
|
+
emitDecision('ask', reason, updatedInput);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
emitDecision('deny', reason);
|
|
158
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* PreToolUse hook — Discord permission flow for sub-agents.
|
|
4
|
+
*
|
|
5
|
+
* PermissionRequest hooks don't fire for sub-agents (Claude Code bug #23983).
|
|
6
|
+
* This hook intercepts sub-agent Edit/Write calls to protected paths
|
|
7
|
+
* (anywhere inside $HOME but outside the session's cwd — see
|
|
8
|
+
* isProtectedPath below) and runs the Discord approval flow directly.
|
|
9
|
+
*
|
|
10
|
+
* Unified signal-based flow (matches main session):
|
|
11
|
+
* 1. POST Discord prompt with perm-{uuid}-{action} buttons.
|
|
12
|
+
* 2. Poll runtime for:
|
|
13
|
+
* a. `perm-{instance}-{uuid}.result` — button click path (channels
|
|
14
|
+
* backend.onInteraction writes it on Discord interaction).
|
|
15
|
+
* b. `tool-exec-{ts}-{rand}.signal` with matching toolName created
|
|
16
|
+
* after this hook started — terminal approval path (post-tool-use
|
|
17
|
+
* writes it when the user approves in terminal).
|
|
18
|
+
* 3. Any `.signal` match/claim UNLINKs the signal (so the main channels
|
|
19
|
+
* watcher doesn't also process it). Unmatched signals are left for the
|
|
20
|
+
* main watcher; stale ones are swept by the channels worker.
|
|
21
|
+
* 4. On resolve/timeout, PATCH Discord to remove buttons.
|
|
22
|
+
*
|
|
23
|
+
* Main session / non-protected sub-agent: exit 0 (other paths handle it).
|
|
24
|
+
*/
|
|
25
|
+
if (process.env.MIXDOG_CHANNELS_NO_CONNECT) process.exit(0);
|
|
26
|
+
|
|
27
|
+
const https = require('https');
|
|
28
|
+
const fs = require('fs');
|
|
29
|
+
const path = require('path');
|
|
30
|
+
const os = require('os');
|
|
31
|
+
const crypto = require('crypto');
|
|
32
|
+
const { shouldRoutePermissionToDiscord } = require('./lib/permission-route.cjs');
|
|
33
|
+
const { getPermissionInstanceId, readActiveInstance } = require('./lib/active-instance.cjs');
|
|
34
|
+
|
|
35
|
+
const DATA_DIR = process.env.CLAUDE_PLUGIN_DATA;
|
|
36
|
+
if (!DATA_DIR) process.exit(0);
|
|
37
|
+
|
|
38
|
+
const RUNTIME_ROOT = path.join(os.tmpdir(), 'mixdog');
|
|
39
|
+
const SIGNAL_RE = /^tool-exec-(\d+)-[0-9a-f]+\.signal$/;
|
|
40
|
+
|
|
41
|
+
function sanitize(value) {
|
|
42
|
+
return String(value).replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readDiscordConfig() {
|
|
46
|
+
try { const { readSection } = require('../lib/config-cjs.cjs'); return readSection('channels'); } catch { return {}; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const POLL_INTERVAL = 2000;
|
|
50
|
+
// Discord-approval polling cap. Was 15 min (900000) — too long for an
|
|
51
|
+
// interactive review where the subagent's caller has already moved on.
|
|
52
|
+
// Capped at 2 min; un-replied prompt auto-denies and the user can resend.
|
|
53
|
+
const TIMEOUT = 120000;
|
|
54
|
+
|
|
55
|
+
function discordApi(method, apiPath, token, body) {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const data = body ? JSON.stringify(body) : '';
|
|
58
|
+
const headers = { 'Authorization': 'Bot ' + token, 'Content-Type': 'application/json' };
|
|
59
|
+
if (data) headers['Content-Length'] = Buffer.byteLength(data);
|
|
60
|
+
const req = https.request({ hostname: 'discord.com', path: apiPath, method, headers },
|
|
61
|
+
res => { let out = ''; res.on('data', d => { out += d; }); res.on('end', () => { try { resolve(JSON.parse(out)); } catch { resolve({}); } }); });
|
|
62
|
+
req.setTimeout(10_000, () => req.destroy());
|
|
63
|
+
req.on('error', reject);
|
|
64
|
+
if (data) req.write(data);
|
|
65
|
+
req.end();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// B2: protected-path expansion. Previously only `/.claude/` triggered
|
|
70
|
+
// Discord approval; now any Edit/Write landing inside HOME but OUTSIDE
|
|
71
|
+
// the session's cwd is treated as protected. Matches the central path
|
|
72
|
+
// This hook applies the same HOME-outside-cwd policy to sub-agents by routing
|
|
73
|
+
// any such target through Discord. Extended policy: any path outside cwd —
|
|
74
|
+
// not just HOME — is protected (covers /etc, C:\Windows, and other system paths).
|
|
75
|
+
function isProtectedPath(filePath, cwd) {
|
|
76
|
+
if (!filePath) return false;
|
|
77
|
+
const norm = path.resolve(filePath).replace(/\\/g, '/').toLowerCase();
|
|
78
|
+
const cwdNorm = (cwd || process.cwd()).replace(/\\/g, '/').toLowerCase();
|
|
79
|
+
// Any path outside cwd is protected (requires Discord approval).
|
|
80
|
+
// This covers HOME-outside-cwd (original policy) plus system paths like
|
|
81
|
+
// /etc, C:\Windows, and any other location outside the session sandbox.
|
|
82
|
+
const insideCwd = cwdNorm && (norm === cwdNorm || norm.startsWith(cwdNorm.endsWith('/') ? cwdNorm : cwdNorm + '/'));
|
|
83
|
+
return !insideCwd;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Scan RUNTIME_ROOT for a signal file whose payload (toolName, filePath,
|
|
87
|
+
// toolUseId) matches and whose name timestamp is >= hookStartedAt. Returns
|
|
88
|
+
// the claimed file path (after unlink) or null. Only the first matching
|
|
89
|
+
// signal is consumed. toolUseId is optional: when falsy, matching falls
|
|
90
|
+
// back to (toolName, filePath).
|
|
91
|
+
function findAndClaimSignal(toolName, filePath, toolUseId, hookStartedAt) {
|
|
92
|
+
let entries;
|
|
93
|
+
try { entries = fs.readdirSync(RUNTIME_ROOT); } catch { return null; }
|
|
94
|
+
for (const name of entries) {
|
|
95
|
+
const m = SIGNAL_RE.exec(name);
|
|
96
|
+
if (!m) continue;
|
|
97
|
+
const ts = Number(m[1]);
|
|
98
|
+
if (!Number.isFinite(ts) || ts < hookStartedAt) continue;
|
|
99
|
+
const p = path.join(RUNTIME_ROOT, name);
|
|
100
|
+
let raw;
|
|
101
|
+
try { raw = fs.readFileSync(p, 'utf8'); } catch { continue; }
|
|
102
|
+
let payload;
|
|
103
|
+
try { payload = JSON.parse(raw); } catch { continue; }
|
|
104
|
+
if (payload?.toolName !== toolName) continue;
|
|
105
|
+
if (payload?.filePath !== filePath) continue;
|
|
106
|
+
if (toolUseId && payload?.toolUseId !== toolUseId) continue;
|
|
107
|
+
try { fs.unlinkSync(p); } catch {}
|
|
108
|
+
return p;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let input = '';
|
|
114
|
+
process.stdin.on('data', d => { input += d; });
|
|
115
|
+
process.stdin.on('end', async () => {
|
|
116
|
+
try {
|
|
117
|
+
const data = JSON.parse(input);
|
|
118
|
+
|
|
119
|
+
const isSidechain = data.isSidechain ?? data.is_sidechain;
|
|
120
|
+
const agentIdRaw = data.agentId ?? data.agent_id;
|
|
121
|
+
const toolInput = data.tool_input ?? data.toolInput ?? {};
|
|
122
|
+
|
|
123
|
+
const isSubagent = isSidechain === true || Boolean(agentIdRaw);
|
|
124
|
+
if (!isSubagent) process.exit(0);
|
|
125
|
+
|
|
126
|
+
const toolName = data.tool_name || '';
|
|
127
|
+
if (toolName !== 'Edit' && toolName !== 'Write' && toolName !== 'MultiEdit') process.exit(0);
|
|
128
|
+
|
|
129
|
+
const mode = data.permissionMode || data.permission_mode || data.mode;
|
|
130
|
+
if (mode === 'bypassPermissions' || mode === 'acceptEdits' || mode === 'auto') process.exit(0);
|
|
131
|
+
|
|
132
|
+
const hookCwd = data.cwd || toolInput.cwd || process.cwd();
|
|
133
|
+
const filePath = toolInput.file_path || '';
|
|
134
|
+
const resolvedPath = filePath ? path.resolve(hookCwd, filePath) : '';
|
|
135
|
+
if (!isProtectedPath(resolvedPath, hookCwd)) process.exit(0);
|
|
136
|
+
|
|
137
|
+
try { fs.mkdirSync(RUNTIME_ROOT, { recursive: true }); } catch {}
|
|
138
|
+
const toolUseId = data.tool_use_id ?? data.toolUseId ?? '';
|
|
139
|
+
|
|
140
|
+
const route = shouldRoutePermissionToDiscord();
|
|
141
|
+
if (route.route !== 'discord') {
|
|
142
|
+
try { process.stderr.write(`[pre-tool-subagent] discord-route=off agent=${agentIdRaw || 'unknown'} tool=${toolName} reason=${route.reason || 'inactive'}\n`); } catch {}
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
try { process.stderr.write(`[pre-tool-subagent] discord-route=on agent=${agentIdRaw || 'unknown'} tool=${toolName} timeout_ms=${TIMEOUT}\n`); } catch {}
|
|
146
|
+
|
|
147
|
+
const { getDiscordToken } = require('../lib/config-cjs.cjs');
|
|
148
|
+
const cfg = readDiscordConfig();
|
|
149
|
+
const token = getDiscordToken();
|
|
150
|
+
if (!token) process.exit(0);
|
|
151
|
+
const agentId = agentIdRaw || 'unknown';
|
|
152
|
+
const mainCh = cfg && cfg.channelsConfig && cfg.channelsConfig.main;
|
|
153
|
+
const channelId = mainCh && (typeof mainCh === 'string' ? null : mainCh.channelId);
|
|
154
|
+
if (!channelId) process.exit(0);
|
|
155
|
+
|
|
156
|
+
const uuid = crypto.randomBytes(16).toString('hex');
|
|
157
|
+
const active = route.active || readActiveInstance();
|
|
158
|
+
const permissionInstanceIds = route.permissionInstanceIds || [getPermissionInstanceId(active)].filter(Boolean);
|
|
159
|
+
if (!permissionInstanceIds.length) process.exit(0);
|
|
160
|
+
const resultFiles = permissionInstanceIds.map((id) => path.join(RUNTIME_ROOT, `perm-${sanitize(id)}-${uuid}.result`));
|
|
161
|
+
|
|
162
|
+
let detail = '';
|
|
163
|
+
if (toolName === 'Edit') {
|
|
164
|
+
detail = filePath + '\n' + (toolInput.old_string || '').substring(0, 200);
|
|
165
|
+
} else {
|
|
166
|
+
detail = filePath;
|
|
167
|
+
}
|
|
168
|
+
const content = `🔐 **Sub-agent Permission**\nAgent: \`${agentId}\`\nTool: \`${toolName}\`\n\`\`\`\n${detail}\n\`\`\``;
|
|
169
|
+
|
|
170
|
+
const body = {
|
|
171
|
+
content,
|
|
172
|
+
components: [{
|
|
173
|
+
type: 1,
|
|
174
|
+
components: [
|
|
175
|
+
{ type: 2, style: 3, label: 'Allow', custom_id: 'perm-' + uuid + '-allow' },
|
|
176
|
+
{ type: 2, style: 1, label: 'Session Allow', custom_id: 'perm-' + uuid + '-session' },
|
|
177
|
+
{ type: 2, style: 4, label: 'Deny', custom_id: 'perm-' + uuid + '-deny' },
|
|
178
|
+
]
|
|
179
|
+
}]
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const msgResult = await discordApi('POST', '/api/v10/channels/' + channelId + '/messages', token, body);
|
|
183
|
+
const messageId = msgResult.id;
|
|
184
|
+
if (!messageId) process.exit(0);
|
|
185
|
+
|
|
186
|
+
const hookStartedAt = Date.now();
|
|
187
|
+
|
|
188
|
+
const patchAndExit = async (suffix, decisionJson) => {
|
|
189
|
+
if (messageId) {
|
|
190
|
+
await discordApi('PATCH', '/api/v10/channels/' + channelId + '/messages/' + messageId, token, {
|
|
191
|
+
content: content + suffix,
|
|
192
|
+
components: []
|
|
193
|
+
}).catch(() => {});
|
|
194
|
+
}
|
|
195
|
+
for (const resultFile of resultFiles) {
|
|
196
|
+
try { fs.unlinkSync(resultFile); } catch {}
|
|
197
|
+
}
|
|
198
|
+
if (decisionJson) process.stdout.write(JSON.stringify(decisionJson));
|
|
199
|
+
process.exit(0);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
process.on('SIGTERM', () => {
|
|
203
|
+
// Best-effort PATCH; don't await since the process may be killed hard.
|
|
204
|
+
discordApi('PATCH', '/api/v10/channels/' + channelId + '/messages/' + messageId, token, {
|
|
205
|
+
content: content + '\n\n↩️ Resolved from terminal.',
|
|
206
|
+
components: []
|
|
207
|
+
}).catch(() => {});
|
|
208
|
+
for (const resultFile of resultFiles) {
|
|
209
|
+
try { fs.unlinkSync(resultFile); } catch {}
|
|
210
|
+
}
|
|
211
|
+
process.exit(0);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const startTime = Date.now();
|
|
215
|
+
while (Date.now() - startTime < TIMEOUT) {
|
|
216
|
+
await new Promise(r => setTimeout(r, POLL_INTERVAL));
|
|
217
|
+
|
|
218
|
+
// Button-click path
|
|
219
|
+
const resultFile = resultFiles.find((file) => fs.existsSync(file));
|
|
220
|
+
if (resultFile) {
|
|
221
|
+
let decision;
|
|
222
|
+
try {
|
|
223
|
+
const result = fs.readFileSync(resultFile, 'utf8').trim();
|
|
224
|
+
if (result === 'allow' || result === 'session') {
|
|
225
|
+
decision = { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' } };
|
|
226
|
+
} else {
|
|
227
|
+
decision = { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: 'Denied from Discord' } };
|
|
228
|
+
}
|
|
229
|
+
} catch {
|
|
230
|
+
decision = { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: 'Failed to read result' } };
|
|
231
|
+
}
|
|
232
|
+
await patchAndExit('', decision);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Terminal-approval path: signal file from post-tool-use with matching
|
|
237
|
+
// (toolName, filePath, toolUseId) — toolUseId optional.
|
|
238
|
+
const claimed = findAndClaimSignal(toolName, filePath, toolUseId, hookStartedAt);
|
|
239
|
+
if (claimed) {
|
|
240
|
+
await patchAndExit('\n\n↩️ Resolved from terminal.', null);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Timeout → deny
|
|
246
|
+
await patchAndExit(
|
|
247
|
+
'\n\n⚠️ Auto-denied due to timeout.',
|
|
248
|
+
{ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: 'Timeout' } }
|
|
249
|
+
);
|
|
250
|
+
} catch {
|
|
251
|
+
process.exit(0);
|
|
252
|
+
}
|
|
253
|
+
});
|