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,345 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, existsSync as fsExistsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { DATA_DIR } from "./config.mjs";
|
|
4
|
+
import { ensureDir } from "./state-file.mjs";
|
|
5
|
+
import { logEvent } from "./executor.mjs";
|
|
6
|
+
import { renameWithRetrySync, writeJsonAtomicSync } from "../../shared/atomic-file.mjs";
|
|
7
|
+
const QUEUE_DIR = join(DATA_DIR, "events", "queue");
|
|
8
|
+
const IN_PROGRESS_DIR = join(DATA_DIR, "events", "in-progress");
|
|
9
|
+
const PROCESSED_DIR = join(DATA_DIR, "events", "processed");
|
|
10
|
+
function finiteInt(value, { min, max, def }) {
|
|
11
|
+
const n = Number(value);
|
|
12
|
+
if (!Number.isFinite(n)) return def;
|
|
13
|
+
const i = Math.trunc(n);
|
|
14
|
+
if (i < min) return min;
|
|
15
|
+
if (i > max) return max;
|
|
16
|
+
return i;
|
|
17
|
+
}
|
|
18
|
+
class EventQueue {
|
|
19
|
+
config;
|
|
20
|
+
channelsConfig;
|
|
21
|
+
tickTimer = null;
|
|
22
|
+
batchTimer = null;
|
|
23
|
+
_processQueueRunning = false;
|
|
24
|
+
_processBatchRunning = false;
|
|
25
|
+
injectFn = null;
|
|
26
|
+
ownerGetter = null;
|
|
27
|
+
ownerSkipLogged = false;
|
|
28
|
+
// Monotonic enqueue counter — prevents same-millisecond enqueue ordering
|
|
29
|
+
// from being decided by Math.random() (lex sort on the random suffix).
|
|
30
|
+
// Counter is 8-digit zero-padded so lex sort matches numeric order.
|
|
31
|
+
enqueueSeq = 0;
|
|
32
|
+
// track files already notified during active state
|
|
33
|
+
constructor(config, channelsConfig) {
|
|
34
|
+
this.config = config ?? {};
|
|
35
|
+
this.channelsConfig = channelsConfig ?? null;
|
|
36
|
+
}
|
|
37
|
+
setInjectHandler(fn) {
|
|
38
|
+
this.injectFn = fn;
|
|
39
|
+
}
|
|
40
|
+
setOwnerGetter(fn) {
|
|
41
|
+
this.ownerGetter = fn;
|
|
42
|
+
}
|
|
43
|
+
// ── Lifecycle ─────────────────────────────────────────────────────
|
|
44
|
+
start() {
|
|
45
|
+
if (this.tickTimer) return;
|
|
46
|
+
ensureDir(QUEUE_DIR);
|
|
47
|
+
ensureDir(IN_PROGRESS_DIR);
|
|
48
|
+
ensureDir(PROCESSED_DIR);
|
|
49
|
+
// Recover events that were claimed (renamed into in-progress/) but never
|
|
50
|
+
// executed because the process crashed between claim and execute. The
|
|
51
|
+
// tick/batch loops only ever scan queue/, so without this they would be
|
|
52
|
+
// stranded forever. Move them back to queue/ so the next tick re-claims.
|
|
53
|
+
this.requeueStrandedInProgress();
|
|
54
|
+
const tickSec = finiteInt(this.config.tickInterval, { min: 1, max: 3600, def: 2 });
|
|
55
|
+
const tickMs = tickSec * 1e3;
|
|
56
|
+
this.tickTimer = setInterval(() => this.processQueue(), tickMs);
|
|
57
|
+
this.initialTickTimer = setTimeout(() => {
|
|
58
|
+
this.initialTickTimer = null;
|
|
59
|
+
this.processQueue();
|
|
60
|
+
}, 3e3);
|
|
61
|
+
const batchMin = finiteInt(this.config.batchInterval, { min: 1, max: 1440, def: 30 });
|
|
62
|
+
const batchMs = batchMin * 6e4;
|
|
63
|
+
this.batchTimer = setInterval(() => this.processBatch(), batchMs);
|
|
64
|
+
logEvent("queue started");
|
|
65
|
+
}
|
|
66
|
+
stop() {
|
|
67
|
+
if (this.tickTimer) {
|
|
68
|
+
clearInterval(this.tickTimer);
|
|
69
|
+
this.tickTimer = null;
|
|
70
|
+
}
|
|
71
|
+
if (this.batchTimer) {
|
|
72
|
+
clearInterval(this.batchTimer);
|
|
73
|
+
this.batchTimer = null;
|
|
74
|
+
}
|
|
75
|
+
if (this.initialTickTimer) {
|
|
76
|
+
clearTimeout(this.initialTickTimer);
|
|
77
|
+
this.initialTickTimer = null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
reloadConfig(config, channelsConfig) {
|
|
81
|
+
this.stop();
|
|
82
|
+
this.config = config ?? {};
|
|
83
|
+
this.channelsConfig = channelsConfig ?? null;
|
|
84
|
+
this.start();
|
|
85
|
+
}
|
|
86
|
+
// ── Enqueue ───────────────────────────────────────────────────────
|
|
87
|
+
enqueue(item) {
|
|
88
|
+
ensureDir(QUEUE_DIR);
|
|
89
|
+
const seq = String(this.enqueueSeq++).padStart(8, "0");
|
|
90
|
+
const id = `${Date.now()}-${seq}-${Math.random().toString(36).slice(2, 6)}`;
|
|
91
|
+
// Filename is plain <id>.json — ordering is by enqueue sequence (monotonic).
|
|
92
|
+
// Priority is stored inside the item JSON; callers sort/filter by item.priority.
|
|
93
|
+
const filename = `${id}.json`;
|
|
94
|
+
const finalPath = join(QUEUE_DIR, filename);
|
|
95
|
+
writeJsonAtomicSync(finalPath, item, { fsyncDir: true });
|
|
96
|
+
logEvent(`${item.name}: enqueued (${item.priority})`);
|
|
97
|
+
if (item.priority === "high") {
|
|
98
|
+
setImmediate(() => this.processQueue());
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// ── Process queue ─────────────────────────────────────────────────
|
|
102
|
+
processQueue() {
|
|
103
|
+
if (this._processQueueRunning) return;
|
|
104
|
+
this._processQueueRunning = true;
|
|
105
|
+
try {
|
|
106
|
+
// Belt-and-suspenders ownership guard: if this process is not the
|
|
107
|
+
// active owner, do nothing. The runtime should only have started this
|
|
108
|
+
// queue on the owner path, but an ownership hand-off can briefly leave
|
|
109
|
+
// two processes both ticking — this short-circuits that window.
|
|
110
|
+
// This is multi-process active-owner gating, not HTTP authentication.
|
|
111
|
+
if (this.ownerGetter) {
|
|
112
|
+
// Fail closed: a probe exception must NOT be treated as ownership.
|
|
113
|
+
// Duplicate-owner queue ticks are exactly what this guard prevents.
|
|
114
|
+
let isOwner = false;
|
|
115
|
+
try { isOwner = !!this.ownerGetter(); } catch { isOwner = false; }
|
|
116
|
+
if (!isOwner) {
|
|
117
|
+
if (!this.ownerSkipLogged) {
|
|
118
|
+
logEvent("queue: skipping tick — not owner");
|
|
119
|
+
this.ownerSkipLogged = true;
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this.ownerSkipLogged = false;
|
|
124
|
+
}
|
|
125
|
+
const files = this.readQueueFiles();
|
|
126
|
+
if (files.length === 0) return;
|
|
127
|
+
for (const file of files) {
|
|
128
|
+
const item = this.readItem(file);
|
|
129
|
+
if (!item) continue;
|
|
130
|
+
// Low-priority items are coalesced by processBatch() on its own
|
|
131
|
+
// interval; the per-tick loop must skip them or it would dispatch
|
|
132
|
+
// each low item individually and starve the batching feature.
|
|
133
|
+
if (item.priority === "low") continue;
|
|
134
|
+
// Atomic claim: rename into in-progress/ before executing. If the
|
|
135
|
+
// rename fails (another tick / cleanup raced, or file vanished),
|
|
136
|
+
// skip this handle.
|
|
137
|
+
const claimed = this.claimFile(file);
|
|
138
|
+
if (!claimed) continue;
|
|
139
|
+
this.executeItem(item, claimed);
|
|
140
|
+
}
|
|
141
|
+
} finally {
|
|
142
|
+
this._processQueueRunning = false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
processBatch() {
|
|
146
|
+
// Belt-and-suspenders ownership guard: if this process is not the
|
|
147
|
+
// active owner, do nothing. The runtime should only have started this
|
|
148
|
+
// queue on the owner path, but an ownership hand-off can briefly leave
|
|
149
|
+
// two processes both ticking — this short-circuits that window.
|
|
150
|
+
// This is multi-process active-owner gating, not HTTP authentication.
|
|
151
|
+
if (this.ownerGetter) {
|
|
152
|
+
// Fail closed: a probe exception must NOT be treated as ownership.
|
|
153
|
+
let isOwner = false;
|
|
154
|
+
try { isOwner = !!this.ownerGetter(); } catch { isOwner = false; }
|
|
155
|
+
if (!isOwner) {
|
|
156
|
+
if (!this.ownerSkipLogged) {
|
|
157
|
+
logEvent("queue: skipping batch tick — not owner");
|
|
158
|
+
this.ownerSkipLogged = true;
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this.ownerSkipLogged = false;
|
|
163
|
+
}
|
|
164
|
+
const files = this.readQueueFiles();
|
|
165
|
+
const lowFiles = files.filter((f) => {
|
|
166
|
+
const item = this.readItem(f);
|
|
167
|
+
return item?.priority === "low";
|
|
168
|
+
});
|
|
169
|
+
if (lowFiles.length === 0) return;
|
|
170
|
+
const groups = /* @__PURE__ */ new Map();
|
|
171
|
+
for (const file of lowFiles) {
|
|
172
|
+
const item = this.readItem(file);
|
|
173
|
+
if (!item) continue;
|
|
174
|
+
const group = groups.get(item.name) ?? { items: [], files: [] };
|
|
175
|
+
group.items.push(item);
|
|
176
|
+
group.files.push(file);
|
|
177
|
+
groups.set(item.name, group);
|
|
178
|
+
}
|
|
179
|
+
for (const [name, group] of groups) {
|
|
180
|
+
// Claim all batch files atomically BEFORE building the combined
|
|
181
|
+
// prompt so overlapping batch ticks don't double-process. Files
|
|
182
|
+
// that fail to claim are dropped from this batch, and their
|
|
183
|
+
// corresponding items are excluded from the prompt.
|
|
184
|
+
const claimedPairs = [];
|
|
185
|
+
for (let i = 0; i < group.files.length; i++) {
|
|
186
|
+
const claimed = this.claimFile(group.files[i]);
|
|
187
|
+
if (claimed) claimedPairs.push({ file: claimed, item: group.items[i] });
|
|
188
|
+
}
|
|
189
|
+
if (claimedPairs.length === 0) continue;
|
|
190
|
+
const combined = claimedPairs.length === 1 ? claimedPairs[0].item.prompt : `Batch of ${claimedPairs.length} events:
|
|
191
|
+
|
|
192
|
+
${claimedPairs.map((p, i) => `--- Event ${i + 1} ---
|
|
193
|
+
${p.item.prompt}`).join("\n\n")}`;
|
|
194
|
+
const batchItem = {
|
|
195
|
+
...claimedPairs[0].item,
|
|
196
|
+
prompt: combined
|
|
197
|
+
};
|
|
198
|
+
logEvent(`${name}: processing batch of ${claimedPairs.length}`);
|
|
199
|
+
this.executeItem(batchItem, null);
|
|
200
|
+
for (const { file: claimedPath } of claimedPairs) {
|
|
201
|
+
this.moveInProgressToProcessed(claimedPath, "batched");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// ── Execute ───────────────────────────────────────────────────────
|
|
206
|
+
// Only interactive items pass through the queue now. Delegate-mode
|
|
207
|
+
// webhooks invoke bridge directly and never enqueue here; the legacy
|
|
208
|
+
// non-interactive / script exec branches were removed.
|
|
209
|
+
executeItem(item, file) {
|
|
210
|
+
if (this.injectFn) {
|
|
211
|
+
const opts = { type: "webhook" };
|
|
212
|
+
const chatId = this.resolveChannel(item.channel) || "";
|
|
213
|
+
if (item.instruction) {
|
|
214
|
+
opts.instruction = `${item.instruction}\n\n${item.prompt}`;
|
|
215
|
+
} else {
|
|
216
|
+
opts.instruction = item.prompt;
|
|
217
|
+
}
|
|
218
|
+
this.injectFn(chatId, `event:${item.name}`, " ", opts);
|
|
219
|
+
}
|
|
220
|
+
if (file) this.moveToProcessed(file, "injected");
|
|
221
|
+
}
|
|
222
|
+
// ── Helpers ───────────────────────────────────────────────────────
|
|
223
|
+
readQueueFiles() {
|
|
224
|
+
try {
|
|
225
|
+
return readdirSync(QUEUE_DIR).filter((f) => f.endsWith(".json")).sort();
|
|
226
|
+
} catch {
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
readItem(file) {
|
|
231
|
+
try {
|
|
232
|
+
return JSON.parse(readFileSync(join(QUEUE_DIR, file), "utf8"));
|
|
233
|
+
} catch (err) {
|
|
234
|
+
if (err.code !== "ENOENT") {
|
|
235
|
+
logEvent(`queue: corrupt file ${file}`);
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// Atomically rename from queue/ to in-progress/. Returns the new
|
|
241
|
+
// filename on success, or null if another worker already claimed it /
|
|
242
|
+
// the file vanished. Same-volume rename stays atomic; Windows transient
|
|
243
|
+
// EPERM/EACCES/EBUSY gets a short retry window.
|
|
244
|
+
claimFile(file) {
|
|
245
|
+
try {
|
|
246
|
+
ensureDir(IN_PROGRESS_DIR);
|
|
247
|
+
const claimed = `in-progress-${Date.now()}-${file}`;
|
|
248
|
+
renameWithRetrySync(join(QUEUE_DIR, file), join(IN_PROGRESS_DIR, claimed));
|
|
249
|
+
return claimed;
|
|
250
|
+
} catch (err) {
|
|
251
|
+
// ENOENT: another tick grabbed it; EEXIST: target collision (very rare).
|
|
252
|
+
if (err && err.code && err.code !== "ENOENT" && err.code !== "EEXIST") {
|
|
253
|
+
logEvent(`queue: claim failed for ${file}: ${err.message ?? err}`);
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Move any leftover in-progress/ handles back to queue/ so a crash between
|
|
259
|
+
// claim and execute does not strand the event. Claim names are
|
|
260
|
+
// `in-progress-<ts>-<originalFile>`; strip the prefix to restore the
|
|
261
|
+
// original queue filename (which preserves enqueue-sequence ordering).
|
|
262
|
+
requeueStrandedInProgress() {
|
|
263
|
+
let entries;
|
|
264
|
+
try {
|
|
265
|
+
entries = readdirSync(IN_PROGRESS_DIR).filter((f) => f.endsWith(".json"));
|
|
266
|
+
} catch {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
let count = 0;
|
|
270
|
+
for (const claimed of entries) {
|
|
271
|
+
const match = /^in-progress-\d+-(.+)$/.exec(claimed);
|
|
272
|
+
const original = match ? match[1] : claimed;
|
|
273
|
+
try {
|
|
274
|
+
renameWithRetrySync(join(IN_PROGRESS_DIR, claimed), join(QUEUE_DIR, original));
|
|
275
|
+
count++;
|
|
276
|
+
} catch (err) {
|
|
277
|
+
if (err && err.code && err.code !== "ENOENT") {
|
|
278
|
+
logEvent(`queue: requeue failed for ${claimed}: ${err.message ?? err}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (count > 0) logEvent(`queue: requeued ${count} stranded in-progress events`);
|
|
283
|
+
}
|
|
284
|
+
moveToProcessed(file, status) {
|
|
285
|
+
// `file` may already live under in-progress/ (claimed) or still in
|
|
286
|
+
// queue/ (batch paths). Try both locations.
|
|
287
|
+
try {
|
|
288
|
+
ensureDir(PROCESSED_DIR);
|
|
289
|
+
const fromInProgress = join(IN_PROGRESS_DIR, file);
|
|
290
|
+
const fromQueue = join(QUEUE_DIR, file);
|
|
291
|
+
const src = this.existsSync(fromInProgress) ? fromInProgress : fromQueue;
|
|
292
|
+
renameWithRetrySync(src, join(PROCESSED_DIR, `${status}-${file}`));
|
|
293
|
+
} catch {
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
moveInProgressToProcessed(file, status) {
|
|
297
|
+
try {
|
|
298
|
+
ensureDir(PROCESSED_DIR);
|
|
299
|
+
renameWithRetrySync(join(IN_PROGRESS_DIR, file), join(PROCESSED_DIR, `${status}-${file}`));
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
existsSync(p) {
|
|
304
|
+
try {
|
|
305
|
+
// readdirSync-free existence check via rename would be destructive —
|
|
306
|
+
// fall back to a cheap stat via readFileSync on non-content probe.
|
|
307
|
+
// Use fs.existsSync semantics without importing it twice.
|
|
308
|
+
return fsExistsSync(p);
|
|
309
|
+
} catch {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
resolveChannel(label) {
|
|
314
|
+
if (!label || !this.channelsConfig) return "";
|
|
315
|
+
const entry = this.channelsConfig?.[label];
|
|
316
|
+
return entry?.channelId ?? label;
|
|
317
|
+
}
|
|
318
|
+
/** Remove items from queue — after processing, dismissal, or any resolution */
|
|
319
|
+
resolveItems(name, status = "done") {
|
|
320
|
+
const files = this.readQueueFiles();
|
|
321
|
+
let count = 0;
|
|
322
|
+
for (const file of files) {
|
|
323
|
+
const item = this.readItem(file);
|
|
324
|
+
if (!item) continue;
|
|
325
|
+
if (item.name === name || name === "*") {
|
|
326
|
+
this.moveToProcessed(file, status);
|
|
327
|
+
count++;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (count > 0) logEvent(`queue: resolved ${count} items (name=${name}, status=${status})`);
|
|
331
|
+
return count;
|
|
332
|
+
}
|
|
333
|
+
/** Get queue status */
|
|
334
|
+
getStatus() {
|
|
335
|
+
const pending = this.readQueueFiles().length;
|
|
336
|
+
return { pending, running: 0 };
|
|
337
|
+
}
|
|
338
|
+
/** List pending interactive items */
|
|
339
|
+
getPendingInteractive() {
|
|
340
|
+
return this.readQueueFiles().map((f) => this.readItem(f)).filter((item) => item !== null && item.exec === "interactive");
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
export {
|
|
344
|
+
EventQueue
|
|
345
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { existsSync, mkdirSync, appendFileSync, appendFile as _appendFileAsync } from "fs";
|
|
3
|
+
import { join, normalize, extname, sep } from "path";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import { DATA_DIR } from "./config.mjs";
|
|
6
|
+
const SCRIPTS_DIR = join(DATA_DIR, "scripts");
|
|
7
|
+
const NOPLUGIN_DIR = join(tmpdir(), "mixdog-noplugin");
|
|
8
|
+
const EVENT_LOG = join(DATA_DIR, "event.log");
|
|
9
|
+
// Buffered async logger — coalesces per-line appends into batched writes.
|
|
10
|
+
let _eventLogBuf = [];
|
|
11
|
+
let _eventLogTimer = null;
|
|
12
|
+
function _flushEventLog() {
|
|
13
|
+
_eventLogTimer = null;
|
|
14
|
+
if (_eventLogBuf.length === 0) return;
|
|
15
|
+
const lines = _eventLogBuf.join("");
|
|
16
|
+
_eventLogBuf = [];
|
|
17
|
+
_appendFileAsync(EVENT_LOG, lines, () => {});
|
|
18
|
+
}
|
|
19
|
+
function _flushEventLogSync() {
|
|
20
|
+
if (_eventLogBuf.length === 0) return;
|
|
21
|
+
const lines = _eventLogBuf.join("");
|
|
22
|
+
_eventLogBuf = [];
|
|
23
|
+
try { appendFileSync(EVENT_LOG, lines); } catch {}
|
|
24
|
+
}
|
|
25
|
+
process.on('exit', _flushEventLogSync);
|
|
26
|
+
function logEvent(msg) {
|
|
27
|
+
try { process.stderr.write(`mixdog event: ${msg}\n`); } catch {}
|
|
28
|
+
_eventLogBuf.push(`[${new Date().toISOString()}] ${msg}\n`);
|
|
29
|
+
if (!_eventLogTimer) _eventLogTimer = setTimeout(_flushEventLog, 2000);
|
|
30
|
+
}
|
|
31
|
+
function parseGithub(body, headers) {
|
|
32
|
+
const event = headers["x-github-event"] || "";
|
|
33
|
+
const action = body.action || "";
|
|
34
|
+
const pr = body.pull_request || body.issue || {};
|
|
35
|
+
return {
|
|
36
|
+
event,
|
|
37
|
+
action,
|
|
38
|
+
title: pr.title || body.head_commit?.message || "",
|
|
39
|
+
author: pr.user?.login || body.sender?.login || "",
|
|
40
|
+
repo: body.repository?.full_name || "",
|
|
41
|
+
url: pr.html_url || body.compare || "",
|
|
42
|
+
branch: body.ref || pr.head?.ref || "",
|
|
43
|
+
message: body.head_commit?.message || ""
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function parseSentry(body) {
|
|
47
|
+
const data = body.data || {};
|
|
48
|
+
const evt = data.event || data.issue || {};
|
|
49
|
+
return {
|
|
50
|
+
title: evt.title || body.message || "",
|
|
51
|
+
level: evt.level || body.level || "",
|
|
52
|
+
project: body.project_name || body.project || "",
|
|
53
|
+
url: evt.web_url || body.url || ""
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function parseGeneric(body) {
|
|
57
|
+
const result = {};
|
|
58
|
+
const keys = Object.keys(body).slice(0, 5);
|
|
59
|
+
for (const k of keys) {
|
|
60
|
+
result[k] = typeof body[k] === "string" ? body[k] : JSON.stringify(body[k]);
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
function applyParser(parser, body, headers) {
|
|
65
|
+
switch (parser) {
|
|
66
|
+
case "github":
|
|
67
|
+
return parseGithub(body, headers);
|
|
68
|
+
case "sentry":
|
|
69
|
+
return parseSentry(body);
|
|
70
|
+
case "generic":
|
|
71
|
+
return parseGeneric(body);
|
|
72
|
+
default:
|
|
73
|
+
return { raw: JSON.stringify(body) };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function evaluateFilter(expr, data) {
|
|
77
|
+
const orParts = expr.split("||").map((s) => s.trim());
|
|
78
|
+
for (const orPart of orParts) {
|
|
79
|
+
const andParts = orPart.split("&&").map((s) => s.trim());
|
|
80
|
+
let andResult = true;
|
|
81
|
+
for (const condition of andParts) {
|
|
82
|
+
const match = condition.match(/^(\w+)\s*==\s*['"](.*)['"]$/);
|
|
83
|
+
if (!match) {
|
|
84
|
+
const neqMatch = condition.match(/^(\w+)\s*!=\s*['"](.*)['"]$/);
|
|
85
|
+
if (neqMatch) {
|
|
86
|
+
const [, field2, value2] = neqMatch;
|
|
87
|
+
if ((data[field2] ?? "") === value2) {
|
|
88
|
+
andResult = false;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
andResult = false;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const [, field, value] = match;
|
|
98
|
+
if ((data[field] ?? "") !== value) {
|
|
99
|
+
andResult = false;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (andResult) return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
function applyTemplate(template, data) {
|
|
108
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => data[key] ?? "");
|
|
109
|
+
}
|
|
110
|
+
function ensureNopluginDir() {
|
|
111
|
+
mkdirSync(NOPLUGIN_DIR, { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
function runScript(name, scriptName, onResult) {
|
|
114
|
+
if (!existsSync(SCRIPTS_DIR)) {
|
|
115
|
+
mkdirSync(SCRIPTS_DIR, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
const scriptPath = normalize(join(SCRIPTS_DIR, scriptName));
|
|
118
|
+
// Boundary-correct containment: startsWith(SCRIPTS_DIR) by itself accepts
|
|
119
|
+
// sibling roots like `<SCRIPTS_DIR>2/...`. Require either an exact match
|
|
120
|
+
// OR the path-separator-prefixed form.
|
|
121
|
+
const SCRIPTS_PREFIX = SCRIPTS_DIR.endsWith(sep) ? SCRIPTS_DIR : SCRIPTS_DIR + sep;
|
|
122
|
+
if (scriptPath !== SCRIPTS_DIR && !scriptPath.startsWith(SCRIPTS_PREFIX)) {
|
|
123
|
+
logEvent(`${name}: script path escapes directory: ${scriptName}`);
|
|
124
|
+
onResult("", null);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (!existsSync(scriptPath)) {
|
|
128
|
+
logEvent(`${name}: script not found: ${scriptPath}`);
|
|
129
|
+
onResult("", null);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const ext = extname(scriptName).toLowerCase();
|
|
133
|
+
const cmd = ext === ".py" ? "python3" : "node";
|
|
134
|
+
const proc = spawn(cmd, [scriptPath], {
|
|
135
|
+
timeout: 3e4,
|
|
136
|
+
env: { ...process.env },
|
|
137
|
+
windowsHide: true
|
|
138
|
+
});
|
|
139
|
+
let stdout = "";
|
|
140
|
+
let stderr = "";
|
|
141
|
+
if (proc.stdout) proc.stdout.on("data", (d) => {
|
|
142
|
+
stdout += d;
|
|
143
|
+
});
|
|
144
|
+
if (proc.stderr) proc.stderr.on("data", (d) => {
|
|
145
|
+
stderr += d;
|
|
146
|
+
});
|
|
147
|
+
proc.on("close", (code) => {
|
|
148
|
+
if (code !== 0) {
|
|
149
|
+
logEvent(`${name}: script exited ${code}: ${stderr.substring(0, 500)}`);
|
|
150
|
+
}
|
|
151
|
+
onResult(stdout.substring(0, 2e3), code);
|
|
152
|
+
});
|
|
153
|
+
proc.on("error", (err) => {
|
|
154
|
+
logEvent(`${name}: script spawn error: ${err.message}`);
|
|
155
|
+
onResult("", null);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
export {
|
|
159
|
+
applyParser,
|
|
160
|
+
applyTemplate,
|
|
161
|
+
ensureNopluginDir,
|
|
162
|
+
evaluateFilter,
|
|
163
|
+
logEvent,
|
|
164
|
+
parseGeneric,
|
|
165
|
+
parseGithub,
|
|
166
|
+
parseSentry,
|
|
167
|
+
runScript
|
|
168
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
function getDisplayWidth(str) {
|
|
2
|
+
let width = 0;
|
|
3
|
+
for (const ch of str) {
|
|
4
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
5
|
+
if (code >= 4352 && code <= 4447 || code >= 11904 && code <= 12350 || code >= 12352 && code <= 13247 || code >= 13312 && code <= 19903 || code >= 19968 && code <= 40959 || code >= 44032 && code <= 55215 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65103 || code >= 65280 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 131072 && code <= 195103 || code >= 127744 && code <= 129535) {
|
|
6
|
+
width += 2;
|
|
7
|
+
} else {
|
|
8
|
+
width += 1;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return width;
|
|
12
|
+
}
|
|
13
|
+
function replaceEmojiInCodeBlock(text) {
|
|
14
|
+
return text.replace(/✅/g, "[O]").replace(/❌/g, "[X]").replace(/⭕/g, "[O]").replace(/🔴/g, "[X]");
|
|
15
|
+
}
|
|
16
|
+
function parseTableCells(line) {
|
|
17
|
+
return line.replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
|
|
18
|
+
}
|
|
19
|
+
// Render a parsed table as a monospace ASCII block wrapped in a code fence.
|
|
20
|
+
// allRows[0] is the header row; the rest are data rows.
|
|
21
|
+
function renderTableAsCode(allRows) {
|
|
22
|
+
const colCount = allRows[0].length;
|
|
23
|
+
const widths = [];
|
|
24
|
+
for (let c = 0; c < colCount; c++) {
|
|
25
|
+
let max = 2;
|
|
26
|
+
for (const row of allRows) {
|
|
27
|
+
const cellLen = row[c] ? getDisplayWidth(row[c]) : 0;
|
|
28
|
+
if (cellLen > max) max = cellLen;
|
|
29
|
+
}
|
|
30
|
+
widths.push(max);
|
|
31
|
+
}
|
|
32
|
+
const padCell = (str, w) => {
|
|
33
|
+
const visLen = getDisplayWidth(str || "");
|
|
34
|
+
return (str || "") + " ".repeat(Math.max(0, w - visLen));
|
|
35
|
+
};
|
|
36
|
+
const outLines = [];
|
|
37
|
+
outLines.push(allRows[0].map((c, ci) => padCell(c, widths[ci])).join(" "));
|
|
38
|
+
outLines.push(widths.map((w) => "-".repeat(w)).join(" "));
|
|
39
|
+
for (let r = 1; r < allRows.length; r++) {
|
|
40
|
+
outLines.push(allRows[r].map((c, ci) => padCell(c, widths[ci])).join(" "));
|
|
41
|
+
}
|
|
42
|
+
const tableText = replaceEmojiInCodeBlock(outLines.join("\n"));
|
|
43
|
+
return "```\n" + tableText + "\n```";
|
|
44
|
+
}
|
|
45
|
+
// Render a parsed table as a bullet list (mobile-safe, never wraps/breaks).
|
|
46
|
+
// Multi-column tables use the first cell as a bold row label and emit
|
|
47
|
+
// `• Header: Value` bullets for the remaining columns; empty values are
|
|
48
|
+
// skipped. Single-column tables fall back to a flat bullet list.
|
|
49
|
+
function renderTableAsBullets(allRows) {
|
|
50
|
+
const headers = allRows[0] ?? [];
|
|
51
|
+
const dataRows = allRows.slice(1);
|
|
52
|
+
if (headers.length === 0 && dataRows.length === 0) return "";
|
|
53
|
+
const useFirstColAsLabel = headers.length > 1 && dataRows.length > 0;
|
|
54
|
+
const out = [];
|
|
55
|
+
if (useFirstColAsLabel) {
|
|
56
|
+
for (const row of dataRows) {
|
|
57
|
+
if (row.length === 0) continue;
|
|
58
|
+
const rowLabel = row[0];
|
|
59
|
+
if (rowLabel) out.push("**" + rowLabel + "**");
|
|
60
|
+
for (let i = 1; i < row.length; i++) {
|
|
61
|
+
const value = row[i];
|
|
62
|
+
if (!value) continue;
|
|
63
|
+
const header = headers[i];
|
|
64
|
+
out.push(header ? "• " + header + ": " + value : "• Column " + i + ": " + value);
|
|
65
|
+
}
|
|
66
|
+
out.push("");
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
for (const row of dataRows) {
|
|
70
|
+
for (let i = 0; i < row.length; i++) {
|
|
71
|
+
const value = row[i];
|
|
72
|
+
if (!value) continue;
|
|
73
|
+
const header = headers[i];
|
|
74
|
+
if (header) out.push("• " + header + ": " + value);
|
|
75
|
+
}
|
|
76
|
+
out.push("");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return out.join("\n").replace(/\n+$/, "");
|
|
80
|
+
}
|
|
81
|
+
// Convert GitHub-flavored Markdown tables for Discord delivery.
|
|
82
|
+
// mode: "bullets" (default, mobile-safe) | "code" (monospace block) | "off".
|
|
83
|
+
function isTableSeparator(line) {
|
|
84
|
+
return /^\|[\s-:]+(\|[\s-:]+)*\|?\s*$/.test(line);
|
|
85
|
+
}
|
|
86
|
+
function convertMarkdownTables(text, mode = "bullets") {
|
|
87
|
+
if (!text || mode === "off") return text;
|
|
88
|
+
const lines = text.split("\n");
|
|
89
|
+
const result = [];
|
|
90
|
+
let i = 0;
|
|
91
|
+
while (i < lines.length) {
|
|
92
|
+
// A separator row turns the previously pushed header line into a table.
|
|
93
|
+
// We pop that header off `result` and push the rendered block in its place,
|
|
94
|
+
// so collapsing a multi-line table never desyncs array indices across
|
|
95
|
+
// subsequent tables.
|
|
96
|
+
if (i > 0 && isTableSeparator(lines[i]) && result.length > 0 && /\|/.test(lines[i - 1])) {
|
|
97
|
+
const headerLine = lines[i - 1];
|
|
98
|
+
const tableLines = [headerLine];
|
|
99
|
+
let j = i + 1;
|
|
100
|
+
while (j < lines.length && /^\|/.test(lines[j]) && !isTableSeparator(lines[j])) {
|
|
101
|
+
tableLines.push(lines[j]);
|
|
102
|
+
j++;
|
|
103
|
+
}
|
|
104
|
+
const allRows = tableLines.map(parseTableCells);
|
|
105
|
+
result.pop();
|
|
106
|
+
result.push(mode === "code" ? renderTableAsCode(allRows) : renderTableAsBullets(allRows));
|
|
107
|
+
i = j;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
result.push(lines[i]);
|
|
111
|
+
i++;
|
|
112
|
+
}
|
|
113
|
+
return result.join("\n");
|
|
114
|
+
}
|
|
115
|
+
function escapeNestedCodeBlocks(text) {
|
|
116
|
+
let fenceLen = 0;
|
|
117
|
+
const lines = text.split("\n");
|
|
118
|
+
return lines.map((line) => {
|
|
119
|
+
const match = line.match(/^(`{3,})/);
|
|
120
|
+
if (match) {
|
|
121
|
+
if (fenceLen === 0) {
|
|
122
|
+
fenceLen = match[1].length;
|
|
123
|
+
} else if (match[1].length >= fenceLen) {
|
|
124
|
+
fenceLen = 0;
|
|
125
|
+
}
|
|
126
|
+
return line;
|
|
127
|
+
}
|
|
128
|
+
if (fenceLen > 0 && line.includes("```")) {
|
|
129
|
+
return line.replace(/```/g, "```");
|
|
130
|
+
}
|
|
131
|
+
return line;
|
|
132
|
+
}).join("\n");
|
|
133
|
+
}
|
|
134
|
+
// opts.tables selects the table rendering mode: "bullets" | "code" | "off".
|
|
135
|
+
function formatForDiscord(text, opts = {}) {
|
|
136
|
+
const mode = opts.tables ?? "bullets";
|
|
137
|
+
return escapeNestedCodeBlocks(convertMarkdownTables(text, mode));
|
|
138
|
+
}
|
|
139
|
+
function safeCodeBlock(content, lang = "") {
|
|
140
|
+
const escaped = content.replace(/```/g, "```");
|
|
141
|
+
return "```" + lang + "\n" + escaped + "\n```";
|
|
142
|
+
}
|
|
143
|
+
function chunk(text, limit = 2e3) {
|
|
144
|
+
if (text.length <= limit) return [text];
|
|
145
|
+
const out = [];
|
|
146
|
+
let rest = text;
|
|
147
|
+
while (rest.length > limit) {
|
|
148
|
+
let cut = -1;
|
|
149
|
+
const cbEnd1 = rest.lastIndexOf("\n```\n", limit);
|
|
150
|
+
const cbEnd2 = rest.lastIndexOf("\n```", limit);
|
|
151
|
+
if (cbEnd1 > limit / 2) {
|
|
152
|
+
cut = cbEnd1 + 4;
|
|
153
|
+
} else if (cbEnd2 > limit / 2) {
|
|
154
|
+
cut = cbEnd2 + 4;
|
|
155
|
+
}
|
|
156
|
+
if (cut <= 0 || cut > limit) {
|
|
157
|
+
const para = rest.lastIndexOf("\n\n", limit);
|
|
158
|
+
const line = rest.lastIndexOf("\n", limit);
|
|
159
|
+
const space = rest.lastIndexOf(" ", limit);
|
|
160
|
+
cut = para > limit / 2 ? para : line > limit / 2 ? line : space > 0 ? space : limit;
|
|
161
|
+
}
|
|
162
|
+
let part = rest.slice(0, cut);
|
|
163
|
+
rest = rest.slice(cut).replace(/^\n+/, "");
|
|
164
|
+
const backtickCount = (part.match(/```/g) || []).length;
|
|
165
|
+
if (backtickCount % 2 === 1) {
|
|
166
|
+
const langMatch = part.match(/```(\w+)/);
|
|
167
|
+
const lang = langMatch ? langMatch[1] : "";
|
|
168
|
+
const closing = "\n```";
|
|
169
|
+
if (part.length + closing.length > limit) {
|
|
170
|
+
const overflow = part.length + closing.length - limit;
|
|
171
|
+
const moved = part.slice(part.length - overflow);
|
|
172
|
+
part = part.slice(0, part.length - overflow) + closing;
|
|
173
|
+
rest = "```" + lang + "\n" + moved + rest;
|
|
174
|
+
} else {
|
|
175
|
+
part += closing;
|
|
176
|
+
rest = "```" + lang + "\n" + rest;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
out.push(part);
|
|
180
|
+
}
|
|
181
|
+
if (rest) out.push(rest);
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
export {
|
|
185
|
+
chunk,
|
|
186
|
+
formatForDiscord,
|
|
187
|
+
safeCodeBlock
|
|
188
|
+
};
|