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,607 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// mixdog statusline wrapper — v0.1.45 (Bun/Node port of statusline.sh)
|
|
3
|
+
// Functional 1:1 parity with the bash original. Replaces bash startup (~50-100ms
|
|
4
|
+
// MSYS overhead) with a single Bun/Node process launch (~5-15ms).
|
|
5
|
+
// Node-builtins only: fs, os, node:http — no third-party deps.
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
import http from 'node:http';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
// ── ANSI palette (identical to bash original) ────────────────────────────────
|
|
13
|
+
const R = '\x1b[0m';
|
|
14
|
+
const B = '\x1b[1m';
|
|
15
|
+
const D = '\x1b[2m';
|
|
16
|
+
const RED = '\x1b[31m';
|
|
17
|
+
const GRN = '\x1b[32m';
|
|
18
|
+
const YLW = '\x1b[33m';
|
|
19
|
+
const CYN = '\x1b[36m';
|
|
20
|
+
const GREY = '\x1b[90m';
|
|
21
|
+
|
|
22
|
+
// ── Terminal width ────────────────────────────────────────────────────────────
|
|
23
|
+
let COLS = parseInt(process.env.COLUMNS || '120', 10);
|
|
24
|
+
if (!Number.isFinite(COLS) || COLS <= 0) COLS = 120;
|
|
25
|
+
|
|
26
|
+
// ── Read CC stdin JSON synchronously ─────────────────────────────────────────
|
|
27
|
+
let CC_JSON = '';
|
|
28
|
+
try { CC_JSON = fs.readFileSync(0, 'utf8'); } catch { CC_JSON = ''; }
|
|
29
|
+
|
|
30
|
+
if (process.env.MIXDOG_STATUSLINE_TRACE && CC_JSON && process.env.CLAUDE_PLUGIN_DATA) {
|
|
31
|
+
try {
|
|
32
|
+
fs.writeFileSync(
|
|
33
|
+
path.join(process.env.CLAUDE_PLUGIN_DATA, 'statusline-stdin.json'),
|
|
34
|
+
CC_JSON
|
|
35
|
+
);
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── One-shot log rotation at entry (5 MB threshold) ──────────────────────────
|
|
40
|
+
if (process.env.MIXDOG_STATUSLINE_TRACE) {
|
|
41
|
+
try {
|
|
42
|
+
const traceFile = path.join(
|
|
43
|
+
os.homedir(), '.claude', 'plugins', 'data', 'mixdog-trib-plugin', 'statusline-trace.log'
|
|
44
|
+
);
|
|
45
|
+
const st = fs.statSync(traceFile);
|
|
46
|
+
if (st.size > 5 * 1024 * 1024) fs.writeFileSync(traceFile, '');
|
|
47
|
+
} catch {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── JSON field extraction helpers (regex, matching bash anchoring) ────────────
|
|
51
|
+
function extract(json, re) {
|
|
52
|
+
const m = re.exec(json);
|
|
53
|
+
return m ? m[1] : '';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Slice a substring from after `key` up to (but not including) `stopKey`.
|
|
57
|
+
// Mirrors bash's `${CC_JSON#*"key"}` / `${var%%"stopKey"*}`.
|
|
58
|
+
function slice(json, key, stopKey) {
|
|
59
|
+
const idx = json.indexOf(key);
|
|
60
|
+
if (idx < 0) return null;
|
|
61
|
+
const tail = json.slice(idx + key.length);
|
|
62
|
+
if (stopKey) {
|
|
63
|
+
const stop = tail.indexOf(stopKey);
|
|
64
|
+
return stop >= 0 ? tail.slice(0, stop) : tail;
|
|
65
|
+
}
|
|
66
|
+
return tail;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Extract CC fields ─────────────────────────────────────────────────────────
|
|
70
|
+
let CC_MODEL = extract(CC_JSON, /"display_name"\s*:\s*"([^"]+)"/);
|
|
71
|
+
let CC_CTX_USED = '';
|
|
72
|
+
let CC_RL_5H = '';
|
|
73
|
+
let CC_RL_7D = '';
|
|
74
|
+
let CC_RL_5H_RESET = '';
|
|
75
|
+
|
|
76
|
+
// context_window scope — cap at "rate_limits" sibling so cold-start payloads
|
|
77
|
+
// don't accidentally pick up the five_hour used_percentage.
|
|
78
|
+
const ctxTail = slice(CC_JSON, '"context_window"', '"rate_limits"');
|
|
79
|
+
if (ctxTail !== null) {
|
|
80
|
+
CC_CTX_USED = extract(ctxTail, /"used_percentage"\s*:\s*([0-9.]+)/);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const fiveTail = slice(CC_JSON, '"five_hour"', '"seven_day"');
|
|
84
|
+
if (fiveTail !== null) {
|
|
85
|
+
CC_RL_5H = extract(fiveTail, /"used_percentage"\s*:\s*([0-9.]+)/);
|
|
86
|
+
CC_RL_5H_RESET = extract(fiveTail, /"resets_at"\s*:\s*([0-9]+)/);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const sevenTail = slice(CC_JSON, '"seven_day"', null);
|
|
90
|
+
if (sevenTail !== null) {
|
|
91
|
+
CC_RL_7D = extract(sevenTail, /"used_percentage"\s*:\s*([0-9.]+)/);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Extract effort level ──────────────────────────────────────────────────────
|
|
95
|
+
// Priority: CC_JSON effort.level → env CLAUDE_CODE_EFFORT_LEVEL → settings.json
|
|
96
|
+
let CC_EFFORT = extract(CC_JSON, /"effort"\s*:\s*\{[^}]*"level"\s*:\s*"([^"]+)"/);
|
|
97
|
+
if (!CC_EFFORT) CC_EFFORT = process.env.CLAUDE_CODE_EFFORT_LEVEL || '';
|
|
98
|
+
if (!CC_EFFORT) {
|
|
99
|
+
try {
|
|
100
|
+
const settingsRaw = fs.readFileSync(
|
|
101
|
+
path.join(os.homedir(), '.claude', 'settings.json'), 'utf8'
|
|
102
|
+
);
|
|
103
|
+
CC_EFFORT = extract(settingsRaw, /"effortLevel"\s*:\s*"([^"]+)"/);
|
|
104
|
+
} catch {}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── Extract CC session_id ─────────────────────────────────────────────────────
|
|
108
|
+
const CC_SESSION_ID = extract(CC_JSON, /"session_id"\s*:\s*"([^"]+)"/);
|
|
109
|
+
const STATUS_ARGS = (() => {
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(CC_JSON);
|
|
112
|
+
return Array.isArray(parsed?._args) ? parsed._args.map(String) : [];
|
|
113
|
+
} catch { return []; }
|
|
114
|
+
})();
|
|
115
|
+
function statusArg(prefix) {
|
|
116
|
+
return STATUS_ARGS.find(arg => arg.startsWith(prefix))?.slice(prefix.length) || '';
|
|
117
|
+
}
|
|
118
|
+
function positiveInt(value) {
|
|
119
|
+
const n = parseInt(String(value || ''), 10);
|
|
120
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
121
|
+
}
|
|
122
|
+
const CLIENT_HOST_PID = positiveInt(statusArg('--client-host-pid=')) || positiveInt(process.ppid);
|
|
123
|
+
// Bash-jobs scope pid: ONLY the explicitly passed --client-host-pid (the
|
|
124
|
+
// shim-provided claude.exe pid). No process.ppid fallback here — under a
|
|
125
|
+
// no-shim invocation ppid is the renderer's parent (the daemon/launcher),
|
|
126
|
+
// NOT claude.exe, so falling back would count jobs that merely match that
|
|
127
|
+
// unrelated pid. Absent ⇒ 0 ⇒ the segment attributes nothing.
|
|
128
|
+
const CLIENT_HOST_PID_JOBS = positiveInt(statusArg('--client-host-pid='));
|
|
129
|
+
function advertPidAlive(content) {
|
|
130
|
+
const pid = positiveInt(extract(content, /"pid"\s*:\s*([0-9]+)/));
|
|
131
|
+
if (!pid) return false;
|
|
132
|
+
try { process.kill(pid, 0); return true; } catch { return false; }
|
|
133
|
+
}
|
|
134
|
+
function advertCcMatches(content) {
|
|
135
|
+
return !!(CC_SESSION_ID && content.includes('"cc_session_id"') && content.includes(`"${CC_SESSION_ID}"`));
|
|
136
|
+
}
|
|
137
|
+
function advertClaimed(content) {
|
|
138
|
+
return content.includes('"cc_session_id"');
|
|
139
|
+
}
|
|
140
|
+
function advertClientHostPid(content) {
|
|
141
|
+
return positiveInt(extract(content, /"clientHostPid"\s*:\s*([0-9]+)/))
|
|
142
|
+
|| positiveInt(extract(content, /"client_host_pid"\s*:\s*([0-9]+)/));
|
|
143
|
+
}
|
|
144
|
+
function advertHostMatches(content, { allowUnclaimed = false } = {}) {
|
|
145
|
+
if (!CLIENT_HOST_PID) return true;
|
|
146
|
+
const clientHostPid = advertClientHostPid(content);
|
|
147
|
+
if (clientHostPid) return clientHostPid === CLIENT_HOST_PID;
|
|
148
|
+
if (allowUnclaimed && !advertClaimed(content)) return true;
|
|
149
|
+
const ownerHostPid = positiveInt(extract(content, /"ownerHostPid"\s*:\s*([0-9]+)/));
|
|
150
|
+
return ownerHostPid === CLIENT_HOST_PID;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Advert routing ────────────────────────────────────────────────────────────
|
|
154
|
+
// Fast path: read `.cc-<sessionId>.path` mapping (written after a successful
|
|
155
|
+
// claim on an earlier tick) and resolve straight to the cached advert.
|
|
156
|
+
// Verified by pid liveness + cc_session_id match in the advert body — any
|
|
157
|
+
// mismatch falls through to the O(N) scan and re-writes the mapping at the
|
|
158
|
+
// end. With a CC session_id, never bind to an advert already claimed by a
|
|
159
|
+
// different CC session: that leaks another terminal's workers into this
|
|
160
|
+
// statusline.
|
|
161
|
+
let statusAdvert = '';
|
|
162
|
+
let needClaim = false;
|
|
163
|
+
const advertDir = path.join(os.homedir(), '.claude', 'mixdog-status');
|
|
164
|
+
const mappingPath = CC_SESSION_ID
|
|
165
|
+
? path.join(advertDir, `.cc-${CC_SESSION_ID}.path`)
|
|
166
|
+
: '';
|
|
167
|
+
|
|
168
|
+
if (mappingPath) {
|
|
169
|
+
try {
|
|
170
|
+
const cached = fs.readFileSync(mappingPath, 'utf8').trim();
|
|
171
|
+
if (cached) {
|
|
172
|
+
const cachedAdvert = path.isAbsolute(cached) ? cached : path.join(advertDir, cached);
|
|
173
|
+
const advertContent = fs.readFileSync(cachedAdvert, 'utf8');
|
|
174
|
+
if (advertPidAlive(advertContent) && advertCcMatches(advertContent) && advertHostMatches(advertContent)) {
|
|
175
|
+
statusAdvert = cachedAdvert;
|
|
176
|
+
needClaim = false;
|
|
177
|
+
} else {
|
|
178
|
+
try { fs.unlinkSync(mappingPath); } catch {}
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
try { fs.unlinkSync(mappingPath); } catch {}
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
try { fs.unlinkSync(mappingPath); } catch {}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!statusAdvert) {
|
|
189
|
+
try {
|
|
190
|
+
const files = fs.readdirSync(advertDir)
|
|
191
|
+
.filter(f => f.endsWith('.json'))
|
|
192
|
+
.map(f => path.join(advertDir, f));
|
|
193
|
+
|
|
194
|
+
for (const f of files) {
|
|
195
|
+
let content;
|
|
196
|
+
try { content = fs.readFileSync(f, 'utf8'); } catch { continue; }
|
|
197
|
+
if (!advertPidAlive(content)) continue;
|
|
198
|
+
|
|
199
|
+
// Step 1: paired — cc_session_id matches
|
|
200
|
+
if (advertCcMatches(content)) {
|
|
201
|
+
if (!advertHostMatches(content)) continue;
|
|
202
|
+
statusAdvert = f;
|
|
203
|
+
needClaim = false;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
// Step 2: first unclaimed same-host advert. Never fetch an advert already
|
|
207
|
+
// claimed by a different CC session; that would leak another terminal's
|
|
208
|
+
// bridge workers into this statusline for at least one tick.
|
|
209
|
+
if (!statusAdvert && CC_SESSION_ID && !advertClaimed(content) && advertHostMatches(content, { allowUnclaimed: true })) {
|
|
210
|
+
statusAdvert = f;
|
|
211
|
+
needClaim = true;
|
|
212
|
+
// keep scanning — a paired advert may appear later
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Legacy/no-session fallback only. A statusline with session_id must not
|
|
217
|
+
// attach to another CC session's claimed advert.
|
|
218
|
+
if (!statusAdvert && !CC_SESSION_ID) {
|
|
219
|
+
for (const f of files) {
|
|
220
|
+
try { fs.readFileSync(f, 'utf8'); statusAdvert = f; break; } catch {}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
} catch {}
|
|
224
|
+
|
|
225
|
+
// Persist mapping for the next tick — fast path will reuse it. Skip when
|
|
226
|
+
// we still need to claim (the next tick will validate and write itself).
|
|
227
|
+
if (statusAdvert && mappingPath && !needClaim) {
|
|
228
|
+
try { fs.writeFileSync(mappingPath, statusAdvert); } catch {}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Legacy single-file fallback.
|
|
233
|
+
if (!statusAdvert && !CC_SESSION_ID) {
|
|
234
|
+
statusAdvert = path.join(os.homedir(), '.claude', 'mixdog-status.json');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ── Read port from advert ─────────────────────────────────────────────────────
|
|
238
|
+
let statusPort = '';
|
|
239
|
+
try {
|
|
240
|
+
const advertContent = fs.readFileSync(statusAdvert, 'utf8');
|
|
241
|
+
statusPort = extract(advertContent, /"port"\s*:\s*([0-9]+)/);
|
|
242
|
+
} catch {}
|
|
243
|
+
|
|
244
|
+
// ── Fire-and-forget claim POST ────────────────────────────────────────────────
|
|
245
|
+
// Response MUST be consumed (res.resume()) — Node http keeps the socket
|
|
246
|
+
// open until the body is read or destroyed, so an unread 200 leaks one
|
|
247
|
+
// socket per tick on a 1-5 s refresh cadence. setTimeout < the GET
|
|
248
|
+
// budget below so a stuck POST never overlaps the next statusline spawn.
|
|
249
|
+
if (needClaim && CC_SESSION_ID && statusPort) {
|
|
250
|
+
const claimPayload = { cc_session_id: CC_SESSION_ID };
|
|
251
|
+
if (CLIENT_HOST_PID) claimPayload.client_host_pid = CLIENT_HOST_PID;
|
|
252
|
+
const body = JSON.stringify(claimPayload);
|
|
253
|
+
try {
|
|
254
|
+
const req = http.request({
|
|
255
|
+
hostname: '127.0.0.1',
|
|
256
|
+
port: parseInt(statusPort, 10),
|
|
257
|
+
path: '/register-cc-session',
|
|
258
|
+
method: 'POST',
|
|
259
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
|
|
260
|
+
}, (res) => { res.resume(); });
|
|
261
|
+
req.on('error', () => {});
|
|
262
|
+
req.setTimeout(800, () => { try { req.destroy(); } catch {} });
|
|
263
|
+
req.write(body);
|
|
264
|
+
req.end();
|
|
265
|
+
} catch {}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ── Fetch /bridge/status with 1s timeout ─────────────────────────────────────
|
|
269
|
+
let bridgeJson = '';
|
|
270
|
+
if (statusPort) {
|
|
271
|
+
bridgeJson = await new Promise(resolve => {
|
|
272
|
+
try {
|
|
273
|
+
const statusUrl = `http://127.0.0.1:${statusPort}/bridge/status?format=statusline-json`
|
|
274
|
+
+ (CLIENT_HOST_PID ? `&clientHostPid=${CLIENT_HOST_PID}` : '');
|
|
275
|
+
const req = http.get(
|
|
276
|
+
statusUrl,
|
|
277
|
+
{ timeout: 500 },
|
|
278
|
+
res => {
|
|
279
|
+
let data = '';
|
|
280
|
+
res.on('data', chunk => { data += chunk; });
|
|
281
|
+
res.on('end', () => resolve(data));
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
req.on('error', () => resolve(''));
|
|
285
|
+
req.on('timeout', () => { req.destroy(); resolve(''); });
|
|
286
|
+
} catch { resolve(''); }
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (!bridgeJson.startsWith('{')) bridgeJson = '';
|
|
290
|
+
|
|
291
|
+
// ── NOBRIDGE trace ────────────────────────────────────────────────────────────
|
|
292
|
+
if (!bridgeJson && process.env.MIXDOG_STATUSLINE_TRACE) {
|
|
293
|
+
try {
|
|
294
|
+
const traceDir = path.join(os.homedir(), '.claude', 'plugins', 'data', 'mixdog-trib-plugin');
|
|
295
|
+
if (fs.existsSync(traceDir)) {
|
|
296
|
+
const advertPresent = (() => { try { fs.accessSync(statusAdvert); return 'present'; } catch { return 'missing'; } })();
|
|
297
|
+
const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
298
|
+
fs.appendFileSync(
|
|
299
|
+
path.join(traceDir, 'statusline-trace.log'),
|
|
300
|
+
`${ts} NOBRIDGE port=${statusPort || '?'} advert=${advertPresent}\n`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
} catch {}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ── Extract bridge fields ─────────────────────────────────────────────────────
|
|
307
|
+
let bSessRoles = '';
|
|
308
|
+
let bSchedNextAt = '';
|
|
309
|
+
let bSchedNextName = '';
|
|
310
|
+
// Worker list carrying running/idle status alongside each tag, surfaced by
|
|
311
|
+
// the aggregator's sessions.workers segment. Falls back to roles (running-
|
|
312
|
+
// only) when an older aggregator payload lacks the workers array.
|
|
313
|
+
let bWorkers = [];
|
|
314
|
+
|
|
315
|
+
if (bridgeJson) {
|
|
316
|
+
const sessRaw = extract(bridgeJson, /"sessions"\s*:\s*\{[^}]*"roles"\s*:\s*\[([^\]]*)\]/);
|
|
317
|
+
if (sessRaw) bSessRoles = sessRaw.replace(/"/g, '').replace(/\s/g, '');
|
|
318
|
+
const workersRaw = extract(bridgeJson, /"workers"\s*:\s*\[([^\]]*)\]/);
|
|
319
|
+
if (workersRaw) {
|
|
320
|
+
// Parse [{"tag":"x","status":"running"},...] without a full JSON.parse
|
|
321
|
+
// of the whole payload (matches the existing regex-extract approach).
|
|
322
|
+
const re = /\{[^}]*?"tag"\s*:\s*"([^"]*)"[^}]*?"status"\s*:\s*"([^"]*)"[^}]*?\}/g;
|
|
323
|
+
let m;
|
|
324
|
+
while ((m = re.exec(workersRaw)) !== null) {
|
|
325
|
+
bWorkers.push({ tag: m[1], status: m[2] === 'idle' ? 'idle' : 'running' });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
bSchedNextAt = extract(bridgeJson, /"next"\s*:\s*\{[^}]*"fireAt"\s*:\s*([0-9]+)/);
|
|
330
|
+
bSchedNextName = extract(bridgeJson, /"next"\s*:\s*\{[^}]*"name"\s*:\s*"([^"]*)"/);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ── Format helpers ────────────────────────────────────────────────────────────
|
|
334
|
+
|
|
335
|
+
// Model short form — collapse "(1M context)" → "(1M)", prefix with family name.
|
|
336
|
+
let modelStr = '';
|
|
337
|
+
if (CC_MODEL) {
|
|
338
|
+
let raw = CC_MODEL.replace('(1M context)', '(1M)');
|
|
339
|
+
if (raw.includes('Opus')) modelStr = 'Opus' + raw.slice(raw.indexOf('Opus') + 4);
|
|
340
|
+
else if (raw.includes('Sonnet')) modelStr = 'Sonnet' + raw.slice(raw.indexOf('Sonnet') + 6);
|
|
341
|
+
else if (raw.includes('Haiku')) modelStr = 'Haiku' + raw.slice(raw.indexOf('Haiku') + 5);
|
|
342
|
+
else modelStr = raw;
|
|
343
|
+
}
|
|
344
|
+
const modelShort = modelStr.split(' ')[0];
|
|
345
|
+
const effortStr = CC_EFFORT ? CC_EFFORT.toUpperCase() : '';
|
|
346
|
+
|
|
347
|
+
// Round float string to nearest integer (replicates printf "%.0f").
|
|
348
|
+
function roundPct(s) {
|
|
349
|
+
const n = parseFloat(s);
|
|
350
|
+
return Number.isFinite(n) ? Math.round(n) : null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const ctxInt = roundPct(CC_CTX_USED);
|
|
354
|
+
const rl5hInt = roundPct(CC_RL_5H);
|
|
355
|
+
const rl7dInt = roundPct(CC_RL_7D);
|
|
356
|
+
|
|
357
|
+
// HH:MM from ms epoch (local time).
|
|
358
|
+
function epochMsToHHMM(ms) {
|
|
359
|
+
const d = new Date(parseInt(ms, 10));
|
|
360
|
+
if (isNaN(d.getTime())) return '';
|
|
361
|
+
return d.toLocaleTimeString('sv-SE', { hour: '2-digit', minute: '2-digit', hour12: false });
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const resetStr = CC_RL_5H_RESET ? epochMsToHHMM(CC_RL_5H_RESET * 1000) : '';
|
|
365
|
+
|
|
366
|
+
// Schedule next HH:MM — fireAt is ms epoch directly.
|
|
367
|
+
const schedNextHHMM = bSchedNextAt ? epochMsToHHMM(parseInt(bSchedNextAt, 10)) : '';
|
|
368
|
+
|
|
369
|
+
// Colour a percentage integer string.
|
|
370
|
+
function colourPct(p) {
|
|
371
|
+
if (p >= 90) return `${RED}${p}%${R}`;
|
|
372
|
+
if (p >= 70) return `${YLW}${p}%${R}`;
|
|
373
|
+
return `${GRN}${p}%${R}`;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Build bar: ▓ for filled, ░ for empty.
|
|
377
|
+
function makeBar(pct, cells) {
|
|
378
|
+
if (pct === null || cells <= 0) return '';
|
|
379
|
+
let filled = Math.floor(pct * cells / 100);
|
|
380
|
+
if (filled < 0) filled = 0;
|
|
381
|
+
if (filled > cells) filled = cells;
|
|
382
|
+
if (pct > 0 && filled === 0) filled = 1;
|
|
383
|
+
return '▓'.repeat(filled) + '░'.repeat(cells - filled);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ── Build L1 ──────────────────────────────────────────────────────────────────
|
|
387
|
+
const SEP = `${D}│${R}`;
|
|
388
|
+
const l1Parts = [];
|
|
389
|
+
function addL1(seg) { if (seg) l1Parts.push(seg); }
|
|
390
|
+
|
|
391
|
+
// Model + effort
|
|
392
|
+
if (modelStr) {
|
|
393
|
+
const m = COLS >= 120 ? modelStr : modelShort;
|
|
394
|
+
if (effortStr) {
|
|
395
|
+
addL1(`${CYN}◆${R} ${B}${m}${R} ${D}·${R} ${B}${effortStr}${R}`);
|
|
396
|
+
} else {
|
|
397
|
+
addL1(`${CYN}◆${R} ${B}${m}${R}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Context bar
|
|
402
|
+
if (ctxInt !== null) {
|
|
403
|
+
const fill = ctxInt >= 90 ? RED : ctxInt >= 70 ? YLW : GRN;
|
|
404
|
+
let barOut = '';
|
|
405
|
+
if (COLS >= 120) barOut = makeBar(ctxInt, 14);
|
|
406
|
+
else if (COLS >= 80) barOut = makeBar(ctxInt, 8);
|
|
407
|
+
|
|
408
|
+
if (barOut) {
|
|
409
|
+
const filledPart = barOut.replace(/░/g, '');
|
|
410
|
+
const emptyPart = barOut.replace(/▓/g, '');
|
|
411
|
+
const bar = `${fill}${filledPart}${R}${D}${emptyPart}${R}`;
|
|
412
|
+
addL1(`${bar} ${ctxInt}%`);
|
|
413
|
+
} else {
|
|
414
|
+
addL1(`${fill}${ctxInt}%${R}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Rate limits + reset
|
|
419
|
+
if (rl5hInt !== null) {
|
|
420
|
+
addL1(`${D}5H${R} ${colourPct(rl5hInt)}`);
|
|
421
|
+
}
|
|
422
|
+
if (COLS >= 80) {
|
|
423
|
+
if (rl7dInt !== null) {
|
|
424
|
+
addL1(`${D}7D${R} ${colourPct(rl7dInt)}`);
|
|
425
|
+
}
|
|
426
|
+
if (resetStr) {
|
|
427
|
+
addL1(`${D}↻ ${resetStr}${R}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ── Role classification ───────────────────────────────────────────────────────
|
|
432
|
+
let workCount = 0;
|
|
433
|
+
let workOrder = '';
|
|
434
|
+
let hasCycle1 = false;
|
|
435
|
+
let hasCycle2 = false;
|
|
436
|
+
let hasCycle3 = false;
|
|
437
|
+
let hasSched = false;
|
|
438
|
+
let hasWebhook = false;
|
|
439
|
+
let hasExplorer = false;
|
|
440
|
+
|
|
441
|
+
function classifyMaint(role) {
|
|
442
|
+
switch (role) {
|
|
443
|
+
case 'cycle1-agent': hasCycle1 = true; return true;
|
|
444
|
+
case 'cycle2-agent': hasCycle2 = true; return true;
|
|
445
|
+
case 'cycle3-agent': hasCycle3 = true; return true;
|
|
446
|
+
case 'scheduler-task': hasSched = true; return true;
|
|
447
|
+
case 'webhook-handler': hasWebhook = true; return true;
|
|
448
|
+
case 'explorer': hasExplorer = true; return true;
|
|
449
|
+
default: return false;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Idle worker tags (greyed in L2), threaded from the aggregator's
|
|
454
|
+
// sessions.workers[].status. Running user-workers still feed workCount /
|
|
455
|
+
// workOrder so the existing "N Running (tags)" badge is unchanged.
|
|
456
|
+
const idleWorkers = [];
|
|
457
|
+
if (bWorkers.length) {
|
|
458
|
+
// Preferred path: per-worker running/idle status from the aggregator.
|
|
459
|
+
for (const w of bWorkers) {
|
|
460
|
+
if (classifyMaint(w.tag)) continue; // maintenance → L1, not the worker badge
|
|
461
|
+
if (w.status === 'idle') {
|
|
462
|
+
idleWorkers.push(w.tag);
|
|
463
|
+
} else {
|
|
464
|
+
workCount++;
|
|
465
|
+
workOrder = workOrder ? `${workOrder}, ${w.tag}` : w.tag;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
} else if (bSessRoles) {
|
|
469
|
+
// Fallback: legacy roles array (running-only, no idle/status info).
|
|
470
|
+
for (const role of bSessRoles.split(',')) {
|
|
471
|
+
if (!role) continue;
|
|
472
|
+
if (classifyMaint(role)) continue;
|
|
473
|
+
workCount++;
|
|
474
|
+
workOrder = workOrder ? `${workOrder}, ${role}` : role;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Maint/system/retrieval badges → single L1 segment
|
|
479
|
+
const maintParts = [];
|
|
480
|
+
if (hasCycle1) maintParts.push(`${GRN}↻${R} ${B}cycle1${R}`);
|
|
481
|
+
if (hasCycle2) maintParts.push(`${GRN}↻${R} ${B}cycle2${R}`);
|
|
482
|
+
if (hasCycle3) maintParts.push(`${GRN}↻${R} ${B}cycle3${R}`);
|
|
483
|
+
if (hasSched) maintParts.push(`${GRN}↻${R} ${B}scheduler${R}`);
|
|
484
|
+
if (hasWebhook) maintParts.push(`${GRN}↻${R} ${B}webhook${R}`);
|
|
485
|
+
if (hasExplorer) maintParts.push(`${GRN}↻${R} ${B}explorer${R}`);
|
|
486
|
+
if (maintParts.length) addL1(maintParts.join(' '));
|
|
487
|
+
|
|
488
|
+
// Background bash jobs: a running job is a `<jobId>.json` whose sibling
|
|
489
|
+
// `<jobId>.done` (written on exit) is absent. Surviving candidates are then
|
|
490
|
+
// liveness-filtered — the `.json` carries the wrapper `pid`; an orphaned job
|
|
491
|
+
// (wrapper crashed, `.done` never written, pid dead for days) is skipped via
|
|
492
|
+
// process.kill(pid, 0). Bounded per tick: candidates are ordered newest-first
|
|
493
|
+
// (the jobId embeds its spawn Date.now()) and at most JOB_SCAN_CAP of them are
|
|
494
|
+
// read — keeping each render O(cap), not O(total on-disk jobs). When the scan
|
|
495
|
+
// is truncated a trailing `+` overflow marker is appended to the count. One
|
|
496
|
+
// readFileSync/statSync per scanned job for the oldest startedAt (the .json is
|
|
497
|
+
// written at job start); tolerate the dir being missing and never throw.
|
|
498
|
+
const JOB_SCAN_CAP = 30;
|
|
499
|
+
const bashJobsSeg = (() => {
|
|
500
|
+
try {
|
|
501
|
+
const dir = path.join(os.homedir(), '.claude', 'plugins', 'data', 'mixdog-trib-plugin', 'shell-jobs');
|
|
502
|
+
const names = fs.readdirSync(dir);
|
|
503
|
+
const done = new Set();
|
|
504
|
+
const jobs = [];
|
|
505
|
+
const ownerByJob = new Map();
|
|
506
|
+
for (const n of names) {
|
|
507
|
+
if (n.endsWith('.done')) done.add(n.slice(0, -5));
|
|
508
|
+
else if (n.endsWith('.json')) jobs.push(n.slice(0, -5));
|
|
509
|
+
else {
|
|
510
|
+
// Owner sidecar `<jobId>.owner-<pid>` — a zero-byte marker whose NAME
|
|
511
|
+
// carries the owning CC host pid, written next to the .json at spawn.
|
|
512
|
+
const i = n.lastIndexOf('.owner-');
|
|
513
|
+
if (i > 0) { const pid = positiveInt(n.slice(i + 7)); if (pid) ownerByJob.set(n.slice(0, i), pid); }
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
// Owner-filter BEFORE the scan cap, from the directory listing alone: each
|
|
517
|
+
// job's owning claude.exe pid is read from its `.owner-<pid>` marker name
|
|
518
|
+
// (no JSON read), so another session's newer jobs can never evict ours at
|
|
519
|
+
// the cap. Only jobs whose marker pid equals THIS statusline's
|
|
520
|
+
// --client-host-pid survive; legacy jobs without a marker — and every job
|
|
521
|
+
// when no host pid was passed (CLIENT_HOST_PID_JOBS absent) — are excluded.
|
|
522
|
+
// Then ordered newest-first by the spawn timestamp embedded in
|
|
523
|
+
// `job_<ms>_<rand>`, so truncation drops only this session's oldest tail.
|
|
524
|
+
const jobStampMs = (id) => { const m = /^job_(\d+)/.exec(id); return m ? Number(m[1]) : 0; };
|
|
525
|
+
const candidates = jobs
|
|
526
|
+
.filter((id) => !done.has(id) && CLIENT_HOST_PID_JOBS && ownerByJob.get(id) === CLIENT_HOST_PID_JOBS)
|
|
527
|
+
.sort((a, b) => jobStampMs(b) - jobStampMs(a));
|
|
528
|
+
if (candidates.length === 0) return '';
|
|
529
|
+
const scan = candidates.slice(0, JOB_SCAN_CAP);
|
|
530
|
+
const truncated = candidates.length > JOB_SCAN_CAP;
|
|
531
|
+
let count = 0;
|
|
532
|
+
let oldestMs = Infinity;
|
|
533
|
+
for (const id of scan) {
|
|
534
|
+
const p = path.join(dir, `${id}.json`);
|
|
535
|
+
let pid, tmo, enforced;
|
|
536
|
+
try {
|
|
537
|
+
const d = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
538
|
+
pid = d.pid; tmo = Number(d.timeoutMs);
|
|
539
|
+
// Runtime enforcement proof: PS records timeoutEnforced:true; the
|
|
540
|
+
// posix wrapper touches <id>.enforced iff its `timeout` branch ran.
|
|
541
|
+
enforced = d.timeoutEnforced === true || fs.existsSync(path.join(dir, `${id}.enforced`));
|
|
542
|
+
}
|
|
543
|
+
catch { continue; } // unreadable/unparseable → skip
|
|
544
|
+
let st;
|
|
545
|
+
try { st = fs.statSync(p); }
|
|
546
|
+
catch { continue; }
|
|
547
|
+
// Deadline: the wrapper force-kills at timeoutMs, so a job older than
|
|
548
|
+
// timeoutMs + grace is dead even when its pid was recycled by an
|
|
549
|
+
// unrelated live process (pid-reuse-proof, mirrors the sweep). Trusted
|
|
550
|
+
// only when the record proves in-wrapper enforcement (timeoutEnforced).
|
|
551
|
+
if (enforced && Number.isFinite(tmo) && tmo > 0 && (Date.now() - st.mtimeMs) > tmo + 30 * 60_000) continue;
|
|
552
|
+
// kill(0, 0) probes the whole process group and "succeeds" — a
|
|
553
|
+
// malformed pid (0, "", []) must be rejected before the probe.
|
|
554
|
+
pid = Number(pid);
|
|
555
|
+
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
556
|
+
// Liveness: process.kill(pid, 0) succeeds or throws EPERM → alive;
|
|
557
|
+
// ESRCH/invalid pid → dead → skip.
|
|
558
|
+
let alive = false;
|
|
559
|
+
try { process.kill(pid, 0); alive = true; }
|
|
560
|
+
catch (e) { alive = e && e.code === 'EPERM'; }
|
|
561
|
+
if (!alive) continue;
|
|
562
|
+
count++;
|
|
563
|
+
if (st.mtimeMs < oldestMs) oldestMs = st.mtimeMs;
|
|
564
|
+
}
|
|
565
|
+
if (count === 0) return '';
|
|
566
|
+
let elapsed = '';
|
|
567
|
+
if (Number.isFinite(oldestMs)) {
|
|
568
|
+
const secs = Math.max(0, Math.floor((Date.now() - oldestMs) / 1000));
|
|
569
|
+
elapsed = secs < 60 ? ` ${secs}s` : ` ${Math.floor(secs / 60)}m`;
|
|
570
|
+
}
|
|
571
|
+
// Overflow marker: the scan was capped, so jobs may exist beyond what was
|
|
572
|
+
// counted — surface a trailing `+` so the figure reads as a lower bound.
|
|
573
|
+
const overflow = truncated ? '+' : '';
|
|
574
|
+
return `${GREY}⚙ bash:${count}${overflow}${elapsed}${R}`;
|
|
575
|
+
} catch { return ''; }
|
|
576
|
+
})();
|
|
577
|
+
if (bashJobsSeg) addL1(bashJobsSeg);
|
|
578
|
+
|
|
579
|
+
// ── Build L2 ──────────────────────────────────────────────────────────────────
|
|
580
|
+
const l2Parts = [];
|
|
581
|
+
function addL2(seg) { if (seg) l2Parts.push(seg); }
|
|
582
|
+
|
|
583
|
+
// Work sessions
|
|
584
|
+
if (workCount > 0 && workOrder) {
|
|
585
|
+
addL2(`${GRN}●${R} ${B}${workCount} Running${R} ${D}(${R}${CYN}${workOrder}${R}${D})${R}`);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (idleWorkers.length) {
|
|
589
|
+
// Idle workers: filled grey dot (●) + explicit 'idle' marker + tag list.
|
|
590
|
+
// Filled (not hollow ○) so the glyph matches the running ● weight; only
|
|
591
|
+
// the colour (grey vs green) distinguishes idle from running.
|
|
592
|
+
const idleTags = idleWorkers.join(', ');
|
|
593
|
+
addL2(`${GREY}● ${idleWorkers.length} idle (${idleTags})${R}`);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Schedule next
|
|
597
|
+
if (bSchedNextName && schedNextHHMM) {
|
|
598
|
+
addL2(`${YLW}⏰${R} ${B}${bSchedNextName}${R} ${D}${schedNextHHMM}${R}`);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Suppress "Idle" fallback (matches bash: if L2 = "Idle" → suppress)
|
|
602
|
+
const l1 = l1Parts.join(` ${SEP} `) || 'mixdog';
|
|
603
|
+
let l2 = l2Parts.join(` ${SEP} `);
|
|
604
|
+
if (l2 === 'Idle') l2 = '';
|
|
605
|
+
|
|
606
|
+
process.stdout.write(l1 + '\n');
|
|
607
|
+
if (l2) process.stdout.write(l2 + '\n');
|