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,420 @@
|
|
|
1
|
+
import {
|
|
2
|
+
closeSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
fsyncSync,
|
|
5
|
+
linkSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
openSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
statSync,
|
|
11
|
+
unlinkSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from 'fs';
|
|
14
|
+
import { dirname, basename, join } from 'path';
|
|
15
|
+
import { randomBytes } from 'crypto';
|
|
16
|
+
import { execFileSync } from 'child_process';
|
|
17
|
+
|
|
18
|
+
const RETRY_CODES = new Set(['EPERM', 'EACCES', 'EBUSY', 'EEXIST']);
|
|
19
|
+
const LOCK_WAIT_CODES = new Set(['EEXIST', 'EPERM', 'EACCES', 'EBUSY']);
|
|
20
|
+
const DEFAULT_BACKOFFS_MS = Object.freeze([25, 50, 100, 200, 400, 800, 1200, 1600]);
|
|
21
|
+
const DEFAULT_LOCK_TIMEOUT_MS = 8000;
|
|
22
|
+
|
|
23
|
+
function sleepSync(ms) {
|
|
24
|
+
try {
|
|
25
|
+
const buf = new SharedArrayBuffer(4);
|
|
26
|
+
Atomics.wait(new Int32Array(buf), 0, 0, Math.max(1, Number(ms) || 1));
|
|
27
|
+
} catch {
|
|
28
|
+
const end = Date.now() + Math.max(1, Number(ms) || 1);
|
|
29
|
+
while (Date.now() < end) {}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function renameWithRetrySync(src, dst, opts = {}) {
|
|
34
|
+
const backoffs = Array.isArray(opts.backoffs) && opts.backoffs.length > 0
|
|
35
|
+
? opts.backoffs
|
|
36
|
+
: DEFAULT_BACKOFFS_MS;
|
|
37
|
+
let lastErr = null;
|
|
38
|
+
for (let attempt = 0; attempt <= backoffs.length; attempt++) {
|
|
39
|
+
try {
|
|
40
|
+
renameSync(src, dst);
|
|
41
|
+
return true;
|
|
42
|
+
} catch (err) {
|
|
43
|
+
lastErr = err;
|
|
44
|
+
if (!RETRY_CODES.has(err?.code) || attempt >= backoffs.length) break;
|
|
45
|
+
const jitter = Math.floor(Math.random() * Math.min(50, Math.max(1, backoffs[attempt])));
|
|
46
|
+
sleepSync(backoffs[attempt] + jitter);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
throw lastErr;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function withFileLockSync(lockPath, fn, opts = {}) {
|
|
53
|
+
const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : DEFAULT_LOCK_TIMEOUT_MS;
|
|
54
|
+
const staleMs = Number.isFinite(opts.staleMs) ? opts.staleMs : 30000;
|
|
55
|
+
const deadline = Date.now() + timeoutMs;
|
|
56
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
57
|
+
let attempt = 0;
|
|
58
|
+
let lastErr = null;
|
|
59
|
+
while (true) {
|
|
60
|
+
let fd;
|
|
61
|
+
try {
|
|
62
|
+
fd = openSync(lockPath, 'wx');
|
|
63
|
+
} catch (err) {
|
|
64
|
+
lastErr = err;
|
|
65
|
+
if (!LOCK_WAIT_CODES.has(err?.code)) throw err;
|
|
66
|
+
try {
|
|
67
|
+
const st = statSync(lockPath);
|
|
68
|
+
if (Date.now() - st.mtimeMs > staleMs) {
|
|
69
|
+
// Only steal a stale lock when we can prove the owner is
|
|
70
|
+
// gone. Reading the pid from the lock file and probing it
|
|
71
|
+
// with kill(pid, 0) protects a slow-but-live holder from
|
|
72
|
+
// having its lock yanked out from under it; the next
|
|
73
|
+
// process that acquires the lock would otherwise race
|
|
74
|
+
// against the original holder and clobber its write.
|
|
75
|
+
//
|
|
76
|
+
// Two-waiter stale-reclaim race: a pure rename "claim" is
|
|
77
|
+
// not enough. A waiter that proved pid D dead can stall;
|
|
78
|
+
// another waiter can then remove D and acquire a live lock,
|
|
79
|
+
// and the stalled waiter would rename that live lock away.
|
|
80
|
+
// Instead, serialize stale removal behind a tiny pid-stamped
|
|
81
|
+
// reclaim guard, then re-read the lock while the stale file
|
|
82
|
+
// is still present at lockPath. If the owner token no longer
|
|
83
|
+
// matches the pid we proved dead, treat the holder as live
|
|
84
|
+
// and back off. We never move or unlink a mismatched live-
|
|
85
|
+
// token file. Guard cleanup is only a best-effort unblocker:
|
|
86
|
+
// path unlink is not a portable unlink-if-token-still-matches
|
|
87
|
+
// primitive, so live-lock safety rests on the lockPath token
|
|
88
|
+
// reverify immediately before the lockPath unlink below.
|
|
89
|
+
const deadPid = _readLockOwnerPid(lockPath);
|
|
90
|
+
if (deadPid !== null && _pidIsDead(deadPid)) {
|
|
91
|
+
const reclaim = _tryAcquireReclaimGuard(lockPath, staleMs);
|
|
92
|
+
if (reclaim !== null) {
|
|
93
|
+
let reclaimed = false;
|
|
94
|
+
try {
|
|
95
|
+
const currentSt = statSync(lockPath);
|
|
96
|
+
if (Date.now() - currentSt.mtimeMs > staleMs) {
|
|
97
|
+
const currentPid = _readLockOwnerPid(lockPath);
|
|
98
|
+
if (currentPid === deadPid && _pidIsDead(currentPid)) {
|
|
99
|
+
try { unlinkSync(lockPath); reclaimed = true; } catch {}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} finally {
|
|
103
|
+
_releaseReclaimGuard(reclaim);
|
|
104
|
+
}
|
|
105
|
+
if (reclaimed) continue;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch {}
|
|
110
|
+
if (Date.now() >= deadline) break;
|
|
111
|
+
const base = DEFAULT_BACKOFFS_MS[Math.min(attempt, DEFAULT_BACKOFFS_MS.length - 1)];
|
|
112
|
+
const jitter = Math.floor(Math.random() * Math.min(75, Math.max(1, base)));
|
|
113
|
+
sleepSync(Math.min(Math.max(1, deadline - Date.now()), base + jitter));
|
|
114
|
+
attempt += 1;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// Stamp our pid into the lock so stale-lock recovery and the
|
|
118
|
+
// finally-unlink below can verify ownership instead of blindly
|
|
119
|
+
// deleting whatever lock file happens to be at this path.
|
|
120
|
+
try { writeFileSync(fd, `${process.pid} ${Date.now()}\n`, 'utf8'); } catch {}
|
|
121
|
+
// For secret-bearing critical sections, the lock file sits beside
|
|
122
|
+
// the secret in the same (shared-home) directory; clamp it owner-only
|
|
123
|
+
// too. Fail-closed: an unenforceable ACL aborts before fn() runs.
|
|
124
|
+
if (opts.secret === true) _enforceOwnerOnlyAclWin32(lockPath);
|
|
125
|
+
try { return fn(); }
|
|
126
|
+
finally {
|
|
127
|
+
try { closeSync(fd); } catch {}
|
|
128
|
+
// Only unlink if we still own the lock. If our hold exceeded
|
|
129
|
+
// staleMs and another process already stole + replaced the
|
|
130
|
+
// file, unconditionally unlinking here would destroy the new
|
|
131
|
+
// owner's lock and break mutual exclusion for whoever is
|
|
132
|
+
// queued behind it.
|
|
133
|
+
try {
|
|
134
|
+
if (_lockOwnedByPid(lockPath, process.pid)) unlinkSync(lockPath);
|
|
135
|
+
} catch {}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const timeoutErr = new Error(`atomic lock timeout after ${timeoutMs}ms: ${lockPath}`);
|
|
139
|
+
timeoutErr.code = 'ELOCKTIMEOUT';
|
|
140
|
+
timeoutErr.cause = lastErr;
|
|
141
|
+
throw timeoutErr;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Lock-ownership helpers. The lock file's first whitespace-separated
|
|
145
|
+
// token is the owner pid (see the writeFileSync above). Best-effort
|
|
146
|
+
// only: a parse failure or stat error is treated as "not ours" /
|
|
147
|
+
// "owner unknown" so we err on the side of NOT stealing.
|
|
148
|
+
function _readLockOwnerPid(lockPath) {
|
|
149
|
+
try {
|
|
150
|
+
const raw = readFileSync(lockPath, 'utf8');
|
|
151
|
+
const tok = String(raw).trim().split(/\s+/)[0];
|
|
152
|
+
const pid = Number.parseInt(tok, 10);
|
|
153
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
154
|
+
} catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function _pidIsDead(pid) {
|
|
160
|
+
// Unreadable / unparseable pid: be conservative — treat as alive so
|
|
161
|
+
// we wait instead of stealing.
|
|
162
|
+
if (pid === null) return false;
|
|
163
|
+
if (pid === process.pid) return false;
|
|
164
|
+
try {
|
|
165
|
+
process.kill(pid, 0);
|
|
166
|
+
return false; // signal delivered → process exists
|
|
167
|
+
} catch (err) {
|
|
168
|
+
// ESRCH = no such process → owner is gone, safe to steal.
|
|
169
|
+
// EPERM = process exists but we can't signal it → still alive,
|
|
170
|
+
// do NOT steal.
|
|
171
|
+
return err?.code === 'ESRCH';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function _lockOwnedByPid(lockPath, pid) {
|
|
176
|
+
return _readLockOwnerPid(lockPath) === pid;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function _reclaimGuardIsUnreadableAndStale(guardPath, staleMs) {
|
|
180
|
+
if (_readLockOwnerPid(guardPath) !== null) return false;
|
|
181
|
+
try {
|
|
182
|
+
const st = statSync(guardPath);
|
|
183
|
+
return Date.now() - st.mtimeMs > staleMs;
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function _reclaimGuardMatchesToken(guardPath, token) {
|
|
190
|
+
try {
|
|
191
|
+
return readFileSync(guardPath, 'utf8') === token;
|
|
192
|
+
} catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function _unlinkReclaimGuardIfPidMatches(guardPath, pid) {
|
|
198
|
+
if (_readLockOwnerPid(guardPath) !== pid) return false;
|
|
199
|
+
try { unlinkSync(guardPath); return true; } catch { return false; }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function _unlinkUnreadableStaleReclaimGuard(guardPath, staleMs) {
|
|
203
|
+
if (!_reclaimGuardIsUnreadableAndStale(guardPath, staleMs)) return false;
|
|
204
|
+
try { unlinkSync(guardPath); return true; } catch { return false; }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function _tryClearStaleReclaimGuard(guardPath, staleMs) {
|
|
208
|
+
const guardPid = _readLockOwnerPid(guardPath);
|
|
209
|
+
if (guardPid !== null) {
|
|
210
|
+
// Re-read the guard immediately before unlinking so a regenerated
|
|
211
|
+
// guard with a different owner is not removed by a stale decision.
|
|
212
|
+
// This is still not a serialized primitive: correctness comes from
|
|
213
|
+
// the lockPath owner-token reverify before unlinking lockPath, not
|
|
214
|
+
// from making guard cleanup itself authoritative.
|
|
215
|
+
if (_pidIsDead(guardPid)) _unlinkReclaimGuardIfPidMatches(guardPath, guardPid);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
// A crash between openSync('wx') and the pid write can leave an empty
|
|
219
|
+
// guard. There is no pid to prove dead, so clear only once the guard
|
|
220
|
+
// file itself is stale; any contender that proceeds must still prove
|
|
221
|
+
// the lockPath token dead before unlinking the lock.
|
|
222
|
+
_unlinkUnreadableStaleReclaimGuard(guardPath, staleMs);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function _tryAcquireReclaimGuard(lockPath, staleMs) {
|
|
226
|
+
const guardPath = `${lockPath}.reclaim`;
|
|
227
|
+
try {
|
|
228
|
+
const fd = openSync(guardPath, 'wx', 0o600);
|
|
229
|
+
const token = `${process.pid} ${Date.now()} ${randomBytes(8).toString('hex')}\n`;
|
|
230
|
+
try { writeFileSync(fd, token, 'utf8'); } catch {}
|
|
231
|
+
return { fd, guardPath, token };
|
|
232
|
+
} catch (err) {
|
|
233
|
+
if (LOCK_WAIT_CODES.has(err?.code)) {
|
|
234
|
+
_tryClearStaleReclaimGuard(guardPath, staleMs);
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
throw err;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function _releaseReclaimGuard(reclaim) {
|
|
242
|
+
try { closeSync(reclaim.fd); } catch {}
|
|
243
|
+
if (_reclaimGuardMatchesToken(reclaim.guardPath, reclaim.token)) {
|
|
244
|
+
try { unlinkSync(reclaim.guardPath); } catch {}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ── Windows owner-only ACL enforcement (fail-closed) ─────────────────
|
|
249
|
+
// POSIX 0o600/0o700 mode bits are advisory-at-best on Windows: NTFS
|
|
250
|
+
// ignores them and the file inherits the parent ACL, which in shared or
|
|
251
|
+
// roaming-profile setups can be world-readable. For secret-bearing
|
|
252
|
+
// writes we MUST lock the file/dir down to the current user only. This
|
|
253
|
+
// is invariant-based and fail-closed: if we cannot prove the ACL was
|
|
254
|
+
// tightened (icacls missing, SystemRoot unset, user unresolvable, or a
|
|
255
|
+
// non-zero exit), we throw so the caller never persists a secret that
|
|
256
|
+
// might be left readable by other accounts.
|
|
257
|
+
|
|
258
|
+
let _cachedUserSid = null;
|
|
259
|
+
|
|
260
|
+
function _resolveCurrentUserPrincipal() {
|
|
261
|
+
// Prefer the current-user SID to avoid localized account-name parsing
|
|
262
|
+
// (e.g. "Administrators"/"Users" differ per locale). Resolve via
|
|
263
|
+
// `whoami /user` using the deterministic System32 binary path. This is
|
|
264
|
+
// fail-closed: if the SID cannot be proven we throw immediately rather
|
|
265
|
+
// than recovering from a (spoofable) account name.
|
|
266
|
+
const systemRoot = process.env.SystemRoot || process.env.windir;
|
|
267
|
+
if (systemRoot) {
|
|
268
|
+
const whoami = join(systemRoot, 'System32', 'whoami.exe');
|
|
269
|
+
if (existsSync(whoami)) {
|
|
270
|
+
try {
|
|
271
|
+
const out = execFileSync(whoami, ['/user', '/fo', 'csv', '/nh'], {
|
|
272
|
+
encoding: 'utf8',
|
|
273
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
274
|
+
windowsHide: true,
|
|
275
|
+
});
|
|
276
|
+
const m = String(out).match(/S-1-5-[0-9-]+/);
|
|
277
|
+
if (m) return m[0];
|
|
278
|
+
} catch {}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const err = new Error('cannot resolve current Windows user for owner-only ACL enforcement');
|
|
282
|
+
err.code = 'EACLNOUSER';
|
|
283
|
+
throw err;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function _icaclsPrincipal(principal) {
|
|
287
|
+
// icacls takes a raw SID prefixed with '*'; account names pass through.
|
|
288
|
+
return /^S-1-/.test(principal) ? `*${principal}` : principal;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function _enforceOwnerOnlyAclWin32(targetPath) {
|
|
292
|
+
if (process.platform !== 'win32') return;
|
|
293
|
+
const systemRoot = process.env.SystemRoot || process.env.windir;
|
|
294
|
+
if (!systemRoot) {
|
|
295
|
+
const err = new Error('SystemRoot not set; cannot locate icacls.exe for owner-only ACL enforcement');
|
|
296
|
+
err.code = 'EACLNOROOT';
|
|
297
|
+
throw err;
|
|
298
|
+
}
|
|
299
|
+
// DETERMINISTIC binary path — never rely on PATH lookup for a
|
|
300
|
+
// security-critical tool (PATH could resolve a planted icacls.exe).
|
|
301
|
+
const icacls = join(systemRoot, 'System32', 'icacls.exe');
|
|
302
|
+
if (!existsSync(icacls)) {
|
|
303
|
+
const err = new Error(`icacls.exe not found at ${icacls}; refusing to leave secret world-readable`);
|
|
304
|
+
err.code = 'EACLNOICACLS';
|
|
305
|
+
throw err;
|
|
306
|
+
}
|
|
307
|
+
if (_cachedUserSid === null) _cachedUserSid = _resolveCurrentUserPrincipal();
|
|
308
|
+
const principal = _icaclsPrincipal(_cachedUserSid);
|
|
309
|
+
try {
|
|
310
|
+
// STEP 1 — /reset discards ALL pre-existing EXPLICIT ACEs on the
|
|
311
|
+
// target, restoring it to the parent's inherited-only DACL. Without
|
|
312
|
+
// this, overwriting a previously-permissive file/dir keeps any
|
|
313
|
+
// explicit non-owner ACEs (which /inheritance:r alone does NOT
|
|
314
|
+
// touch — that flag only strips INHERITED ACEs), leaving the secret
|
|
315
|
+
// readable by others. execFileSync throws on a non-zero exit, which
|
|
316
|
+
// is exactly the fail-closed behaviour we want.
|
|
317
|
+
execFileSync(icacls, [targetPath, '/reset'], {
|
|
318
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
319
|
+
windowsHide: true,
|
|
320
|
+
});
|
|
321
|
+
// STEP 2 — /inheritance:r strips the now-inherited ACEs left by the
|
|
322
|
+
// reset; /grant:r replaces the principal's ACE so the resulting DACL
|
|
323
|
+
// grants ONLY the current user full control. After both steps no
|
|
324
|
+
// explicit or inherited non-owner ACE survives.
|
|
325
|
+
execFileSync(icacls, [targetPath, '/inheritance:r', '/grant:r', `${principal}:(F)`], {
|
|
326
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
327
|
+
windowsHide: true,
|
|
328
|
+
});
|
|
329
|
+
} catch (err) {
|
|
330
|
+
const e = new Error(`icacls failed to apply owner-only ACL to ${targetPath}: ${err?.message || err}`);
|
|
331
|
+
e.code = 'EACLFAIL';
|
|
332
|
+
e.cause = err;
|
|
333
|
+
throw e;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function writeFileAtomicSync(filePath, data, opts = {}) {
|
|
338
|
+
const run = () => {
|
|
339
|
+
const dir = dirname(filePath);
|
|
340
|
+
mkdirSync(dir, { recursive: true });
|
|
341
|
+
const tmp = join(dir, `.${basename(filePath)}.${randomBytes(12).toString('hex')}.tmp`);
|
|
342
|
+
try {
|
|
343
|
+
const writeOpts = { encoding: opts.encoding || 'utf8', flag: 'wx', mode: opts.mode !== undefined ? opts.mode : 0o600 };
|
|
344
|
+
writeFileSync(tmp, data, writeOpts);
|
|
345
|
+
// Lock the temp down to the current user BEFORE it is published at
|
|
346
|
+
// filePath. On win32 an NTFS rename carries the file's explicit
|
|
347
|
+
// DACL, so tightening tmp here means the secret never appears at
|
|
348
|
+
// the final path with a permissive ACL. Fail-closed: if the ACL
|
|
349
|
+
// cannot be applied, the throw below unlinks tmp and aborts.
|
|
350
|
+
if (opts.secret === true) _enforceOwnerOnlyAclWin32(tmp);
|
|
351
|
+
if (opts.fsync !== false) {
|
|
352
|
+
let fd = null;
|
|
353
|
+
try {
|
|
354
|
+
fd = openSync(tmp, 'r');
|
|
355
|
+
fsyncSync(fd);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
if (!['EPERM', 'ENOTSUP', 'EINVAL'].includes(err?.code)) throw err;
|
|
358
|
+
} finally {
|
|
359
|
+
try { if (fd !== null) closeSync(fd); } catch {}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (opts.createOnly === true) {
|
|
363
|
+
// Atomic create-if-absent: linkSync fails with EEXIST if the
|
|
364
|
+
// target already exists at link time, so a concurrent
|
|
365
|
+
// non-locking writer (user/editor) that creates the file
|
|
366
|
+
// between any prior existsSync gate and this call still
|
|
367
|
+
// wins — we drop the temp and report `false` so the caller
|
|
368
|
+
// can record the path as skipped rather than overwriting.
|
|
369
|
+
try {
|
|
370
|
+
linkSync(tmp, filePath);
|
|
371
|
+
} catch (err) {
|
|
372
|
+
try { unlinkSync(tmp); } catch {}
|
|
373
|
+
if (err?.code === 'EEXIST') return false;
|
|
374
|
+
throw err;
|
|
375
|
+
}
|
|
376
|
+
try { unlinkSync(tmp); } catch {}
|
|
377
|
+
} else {
|
|
378
|
+
renameWithRetrySync(tmp, filePath, opts);
|
|
379
|
+
}
|
|
380
|
+
if (opts.secret === true) {
|
|
381
|
+
// Re-assert owner-only on the final path (createOnly's linkSync may
|
|
382
|
+
// not carry the temp DACL the same way a rename does). Throws
|
|
383
|
+
// fail-closed if the ACL cannot be enforced.
|
|
384
|
+
//
|
|
385
|
+
// NOTE: we deliberately do NOT clamp the parent dir. On win32
|
|
386
|
+
// `_enforceOwnerOnlyAclWin32` runs `icacls /inheritance:r /grant:r
|
|
387
|
+
// user:(F)` which is NOT the POSIX-0o700 analogue it looks like —
|
|
388
|
+
// on a directory it breaks inheritance and cascade-strips inherited
|
|
389
|
+
// ACEs from EVERY descendant, leaving the whole tree (e.g. ~/.claude
|
|
390
|
+
// when writing ~/.claude/.credentials.json) with empty DACLs and
|
|
391
|
+
// access-denied for everyone, owner included. The file ACL above
|
|
392
|
+
// already protects the secret's contents regardless of dir listing.
|
|
393
|
+
_enforceOwnerOnlyAclWin32(filePath);
|
|
394
|
+
}
|
|
395
|
+
if (opts.fsyncDir === true) {
|
|
396
|
+
let dfd = null;
|
|
397
|
+
try {
|
|
398
|
+
dfd = openSync(dir, 'r');
|
|
399
|
+
fsyncSync(dfd);
|
|
400
|
+
} catch (err) {
|
|
401
|
+
if (!['EPERM', 'ENOTSUP', 'EINVAL', 'EACCES'].includes(err?.code)) throw err;
|
|
402
|
+
} finally {
|
|
403
|
+
try { if (dfd !== null) closeSync(dfd); } catch {}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return true;
|
|
407
|
+
} catch (err) {
|
|
408
|
+
try { if (existsSync(tmp)) unlinkSync(tmp); } catch {}
|
|
409
|
+
throw err;
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
if (opts.lock === true) {
|
|
413
|
+
return withFileLockSync(`${filePath}.lock`, run, opts);
|
|
414
|
+
}
|
|
415
|
+
return run();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export function writeJsonAtomicSync(filePath, value, opts = {}) {
|
|
419
|
+
return writeFileAtomicSync(filePath, JSON.stringify(value, null, opts.compact ? 0 : 2) + '\n', opts);
|
|
420
|
+
}
|