mixdog 1.2.5 → 1.2.6
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/package.json +1 -1
- package/scripts/lib/gate-scheduler.mjs +2 -2
- package/scripts/lib/run-node-tests.mjs +5 -14
- package/scripts/provider-pingpong-live.mjs +9 -9
- package/src/agents/advisor/AGENT.md +4 -4
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/reviewer/AGENT.md +2 -3
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +10 -0
- package/src/defaults/skills/browser-use/SKILL.md +2 -1
- package/src/defaults/skills/computer-use/SKILL.md +3 -1
- package/src/defaults/skills/visualize/SKILL.md +3 -2
- package/src/lib/rules-builder.cjs +1 -1
- package/src/output-styles/common.md +22 -6
- package/src/output-styles/detailed.md +7 -5
- package/src/output-styles/minimal.md +7 -3
- package/src/output-styles/simple.md +2 -3
- package/src/rules/agent/AGENT.md +3 -1
- package/src/rules/lead/LEAD.md +4 -3
- package/src/rules/shared/10-tool-workflow.md +2 -1
- package/src/rules/shared/60-verification.md +4 -5
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -1
- package/src/runtime/agent/orchestrator/config.mjs +6 -0
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-agents.mjs +10 -0
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +53 -60
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +2 -3
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +40 -19
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/catalog-deadline.mjs +163 -0
- package/src/runtime/agent/orchestrator/providers/catalog-failures.mjs +34 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +38 -14
- package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +100 -26
- package/src/runtime/agent/orchestrator/providers/lib/send-callbacks.mjs +24 -0
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +49 -4
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +0 -7
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +11 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +45 -16
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-connect.mjs +196 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +74 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-login.mjs +194 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-tokens.mjs +353 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +293 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +33 -100
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +23 -14
- package/src/runtime/agent/orchestrator/providers/registry.mjs +41 -27
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +2 -3
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +3 -8
- package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +74 -13
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +8 -2
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +39 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +9 -19
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +10 -7
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +62 -22
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +81 -34
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/request-fork.mjs +47 -0
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/side-question.mjs +5 -43
- package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +73 -6
- package/src/runtime/agent/orchestrator/session/store/load-session.mjs +40 -0
- package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-header.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +258 -43
- package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +306 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +70 -21
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +46 -30
- package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +144 -45
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +32 -13
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +364 -38
- package/src/runtime/agent/orchestrator/session/store.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +43 -12
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +23 -6
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -6
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +13 -7
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/benign-exit.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +40 -7
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +37 -34
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +0 -4
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +29 -27
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/find-files-tool.mjs +28 -11
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +12 -3
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +203 -101
- package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +90 -41
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-delivery.mjs +199 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +165 -39
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +25 -13
- package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +7 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +19 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-line-collector.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +25 -7
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-render.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +12 -5
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +61 -22
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +32 -8
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +193 -19
- package/src/runtime/agent/orchestrator/tools/builtin/shell-direct-exe.mjs +57 -7
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +119 -32
- package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +26 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +13 -28
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +90 -11
- package/src/runtime/agent/orchestrator/tools/builtin/wait-budgets.mjs +23 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +54 -10
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +22 -28
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +11 -7
- package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +126 -9
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +15 -6
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +40 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promotion-results.mjs +13 -3
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +35 -4
- package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +7 -2
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +39 -23
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
- package/src/runtime/attachments/office-extract.mjs +11 -9
- package/src/runtime/memory/lib/embedding-tokenizer.mjs +32 -18
- package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -1
- package/src/runtime/memory/lib/tool-call-handler.mjs +7 -5
- package/src/runtime/office/authoring/pptx-script-normalize.mjs +11 -8
- package/src/runtime/office/core/tabular.mjs +1 -1
- package/src/runtime/office/design/library/design-template-inspect.mjs +7 -4
- package/src/runtime/office/portable/docx-formatting.mjs +1 -1
- package/src/runtime/office/portable/docx-runs.mjs +1 -1
- package/src/runtime/office/portable/docx-tracked-edits.mjs +2 -2
- package/src/runtime/office/portable/portable-docx-edits.mjs +11 -10
- package/src/runtime/office/portable/portable-docx-operations.mjs +21 -17
- package/src/runtime/office/portable/portable-docx-parts.mjs +15 -9
- package/src/runtime/office/portable/portable-docx-styles.mjs +4 -1
- package/src/runtime/office/portable/portable-docx-xml.mjs +9 -9
- package/src/runtime/office/portable/portable-opc.mjs +27 -13
- package/src/runtime/office/portable/portable-pivot.mjs +3 -3
- package/src/runtime/office/portable/portable-pptx-chart.mjs +3 -3
- package/src/runtime/office/portable/portable-pptx-charts.mjs +24 -18
- package/src/runtime/office/portable/portable-pptx-core.mjs +5 -5
- package/src/runtime/office/portable/portable-pptx-deck.mjs +8 -4
- package/src/runtime/office/portable/portable-pptx-package.mjs +7 -4
- package/src/runtime/office/portable/portable-pptx-shapes.mjs +18 -9
- package/src/runtime/office/portable/portable-sheet-parts.mjs +9 -3
- package/src/runtime/office/portable/portable-sheet-xml.mjs +5 -5
- package/src/runtime/office/portable/portable-xlsx-charts.mjs +4 -1
- package/src/runtime/office/portable/portable-xlsx-drawings.mjs +10 -6
- package/src/runtime/office/portable/portable-xlsx-operations.mjs +8 -5
- package/src/runtime/office/portable/portable-xlsx-pivot-ops.mjs +1 -1
- package/src/runtime/office/portable/portable-xlsx-reference-shift.mjs +11 -7
- package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +6 -3
- package/src/runtime/office/portable/portable-xml.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +22 -13
- package/src/runtime/shared/background-tasks.mjs +60 -20
- package/src/runtime/shared/child-spawn-gate.mjs +26 -16
- package/src/runtime/shared/config.mjs +0 -10
- package/src/runtime/shared/file-lock.mjs +7 -1
- package/src/runtime/shared/file-permissions-worker.mjs +38 -0
- package/src/runtime/shared/file-permissions.d.mts +19 -4
- package/src/runtime/shared/file-permissions.mjs +310 -4
- package/src/runtime/shared/git-runner-worker.mjs +6 -78
- package/src/runtime/shared/llm/http-agent.mjs +7 -3
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +10 -0
- package/src/runtime/shared/llm/usage-accounting.mjs +2 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +32 -3
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +12 -5
- package/src/runtime/shared/llm/usage-ledger-worker.mjs +1 -1
- package/src/runtime/shared/llm/usage-ledger.mjs +20 -5
- package/src/runtime/shared/memory-snapshot.mjs +5 -1
- package/src/runtime/shared/notify-trace.mjs +8 -1
- package/src/runtime/shared/pristine-execution-contract.json +5 -1
- package/src/runtime/shared/process-runner-worker.mjs +7 -0
- package/src/runtime/shared/process-runner.mjs +38 -0
- package/src/runtime/shared/provider-accounts.mjs +2 -1
- package/src/runtime/shared/provider-api-key.mjs +0 -1
- package/src/runtime/shared/read-row-numbers.mjs +1 -1
- package/src/runtime/shared/resource-admission.mjs +60 -18
- package/src/runtime/shared/run-child.mjs +84 -0
- package/src/runtime/shared/session-activity-signal.mjs +23 -0
- package/src/runtime/shared/shell-output-summary.mjs +23 -0
- package/src/runtime/shared/singleton-owner.mjs +126 -42
- package/src/runtime/shared/tool-output-limit.mjs +4 -2
- package/src/runtime/shared/tool-output-marker.mjs +58 -0
- package/src/runtime/shared/user-data-guard.mjs +28 -9
- package/src/runtime/shared/worktrees/activity-gate.mjs +33 -8
- package/src/runtime/shared/worktrees/git-head.mjs +20 -2
- package/src/runtime/shared/worktrees/lock.mjs +3 -2
- package/src/runtime/terminal-bridge/tool-defs.mjs +1 -1
- package/src/runtime/tidy/runners/biome.mjs +3 -1
- package/src/runtime/tidy/shadow.mjs +2 -1
- package/src/runtime/web-search/index.mjs +5 -7
- package/src/runtime/web-search/lib/formatter.mjs +42 -6
- package/src/runtime/web-search/tool-defs.mjs +6 -1
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +4 -3
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -4
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +3 -3
- package/src/session-runtime/lifecycle/shared.mjs +19 -2
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +2 -0
- package/src/session-runtime/lifecycle/teardown.mjs +5 -2
- package/src/session-runtime/lifecycle-api.mjs +3 -1
- package/src/session-runtime/provider-auth/login.mjs +10 -6
- package/src/session-runtime/provider-auth/setup.mjs +19 -2
- package/src/session-runtime/provider-catalog-cache.mjs +101 -25
- package/src/session-runtime/provider-models/catalog-load.mjs +14 -3
- package/src/session-runtime/provider-models/row-hydration.mjs +4 -0
- package/src/session-runtime/provider-models.mjs +76 -10
- package/src/session-runtime/provider-readiness.mjs +4 -0
- package/src/session-runtime/services/agent-tool/close-flow.mjs +2 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +10 -2
- package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +13 -4
- package/src/session-runtime/services/agent-tool/notify.mjs +15 -4
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +134 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/deferred-spawn.mjs +2 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -1
- package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +8 -3
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +4 -0
- package/src/session-runtime/services/agent-tool/worker-index.mjs +44 -10
- package/src/session-runtime/services/agent-tool.mjs +20 -0
- package/src/session-runtime/services/provider-admin.mjs +92 -20
- package/src/session-runtime/services/usage-dashboard-model.mjs +2 -2
- package/src/session-runtime/services/usage-dashboard.mjs +27 -69
- package/src/session-runtime/turn/session-ops.mjs +2 -2
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/usage-stats-api.mjs +6 -1
- package/src/standalone/channel-transport/http-routes.mjs +10 -9
- package/src/standalone/daemon-desktop-runtime.mjs +20 -7
- package/src/standalone/daemon-owner.mjs +117 -25
- package/src/standalone/daemon.mjs +43 -13
- package/src/standalone/session-call-cache.mjs +6 -0
- package/src/standalone/session-protocol.mjs +1 -1
- package/src/standalone/session-service/retention/busy.mjs +7 -2
- package/src/standalone/session-transport/routes.mjs +7 -6
- package/src/tui/app/model-options.mjs +39 -2
- package/src/tui/app/model-picker.mjs +16 -2
- package/src/tui/app/provider-setup-picker.mjs +1 -2
- package/src/tui/dist/index.mjs +385 -104
- package/src/tui/session/notice-text.mjs +0 -1
- package/src/tui/session/session-api/intake/submission.mjs +7 -13
- package/src/tui/session/session-api/integrations.mjs +1 -5
- package/src/tui/session/session-flow/drain.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +46 -0
- package/src/tui/session/session-flow.mjs +2 -0
- package/src/tui/session/turn-stream.mjs +33 -0
- package/src/tui/session/turn.mjs +30 -3
- package/src/ui/statusline.mjs +1 -6
- package/src/ui/streaming-markdown-heal.mjs +24 -7
- package/src/vendor/statusline/bin/statusline-route.mjs +0 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +0 -1
- package/src/workflows/default/WORKFLOW.md +9 -13
|
@@ -4,7 +4,9 @@ import { isAbsolute, join, resolve } from 'node:path';
|
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { randomUUID } from 'node:crypto';
|
|
6
6
|
import { isBuiltin } from 'node:module';
|
|
7
|
+
import { Script } from 'node:vm';
|
|
7
8
|
import { cwdRelativePath, normalizeInputPath, normalizeOutputPath, resolveAgainstCwd } from './path-utils.mjs';
|
|
9
|
+
import { codeMask } from './bash-tool/bashism-rewrite.mjs';
|
|
8
10
|
|
|
9
11
|
// Hard-block patterns live exclusively in ../shell-policy.mjs (BLOCKED_PATTERNS /
|
|
10
12
|
// isBlockedCommand). This lighter detector only classifies cache invalidation
|
|
@@ -35,6 +37,14 @@ const SHELL_GLOBAL_MUTATORS = new Set([
|
|
|
35
37
|
'dd',
|
|
36
38
|
]);
|
|
37
39
|
|
|
40
|
+
// Whether a backslash escapes the character after it: anything outside quotes,
|
|
41
|
+
// only `"` or `\` inside double quotes, never inside single quotes (`'C:\'` is
|
|
42
|
+
// a closed string, not an escaped quote).
|
|
43
|
+
function startsEscape(quote, next) {
|
|
44
|
+
if (quote === "'") return false;
|
|
45
|
+
return quote === '"' ? next === '"' || next === '\\' : true;
|
|
46
|
+
}
|
|
47
|
+
|
|
38
48
|
export function shellSplitSegments(command) {
|
|
39
49
|
const parts = [];
|
|
40
50
|
let current = '';
|
|
@@ -47,7 +57,7 @@ export function shellSplitSegments(command) {
|
|
|
47
57
|
escaped = false;
|
|
48
58
|
continue;
|
|
49
59
|
}
|
|
50
|
-
if (ch === '\\') {
|
|
60
|
+
if (ch === '\\' && startsEscape(quote, command[i + 1])) {
|
|
51
61
|
current += ch;
|
|
52
62
|
escaped = true;
|
|
53
63
|
continue;
|
|
@@ -83,20 +93,24 @@ export function shellTokenize(segment) {
|
|
|
83
93
|
const tokens = [];
|
|
84
94
|
let current = '';
|
|
85
95
|
let quote = null;
|
|
86
|
-
let escaped = false;
|
|
87
96
|
const push = () => {
|
|
88
97
|
if (current !== '') tokens.push(current);
|
|
89
98
|
current = '';
|
|
90
99
|
};
|
|
91
100
|
for (let i = 0; i < segment.length; i++) {
|
|
92
101
|
const ch = segment[i];
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
if (ch === '\\' && quote !== "'") {
|
|
103
|
+
// A backslash escapes only what a shell would need it for. Before an
|
|
104
|
+
// ordinary character it is a path separator (`C:\Users\x`, `\\server\s`),
|
|
105
|
+
// which dropping would turn into a different, nonexistent path.
|
|
106
|
+
const next = segment[i + 1];
|
|
107
|
+
const escapes = quote === '"' ? next === '"' : next !== undefined && /[\s"'$`;&|<>()*?]/.test(next);
|
|
108
|
+
if (!escapes) {
|
|
109
|
+
current += ch;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
current += next;
|
|
113
|
+
i++;
|
|
100
114
|
continue;
|
|
101
115
|
}
|
|
102
116
|
if (quote) {
|
|
@@ -228,7 +242,7 @@ export function shellSplitPipelineSegments(segment) {
|
|
|
228
242
|
escaped = false;
|
|
229
243
|
continue;
|
|
230
244
|
}
|
|
231
|
-
if (ch === '\\') {
|
|
245
|
+
if (ch === '\\' && startsEscape(quote, segment[i + 1])) {
|
|
232
246
|
current += ch;
|
|
233
247
|
escaped = true;
|
|
234
248
|
continue;
|
|
@@ -518,6 +532,17 @@ const MSYS_ABS_PATH_RE = /(^|[\s"'=,(])\/([A-Za-z])\/([^\s"'`|&;<>()]*)/g;
|
|
|
518
532
|
// the drive-rewrite and bash-ism regexes against the mask so quoted literals /
|
|
519
533
|
// regex patterns are never treated as real path tokens or connectors.
|
|
520
534
|
function maskQuotedRegions(command) {
|
|
535
|
+
// The full PowerShell tokeniser first: it knows here-strings, comments and
|
|
536
|
+
// doubled quotes, which a bare quote-pair scan reads wrongly (an apostrophe
|
|
537
|
+
// inside `@'…'@` or a `# don't` comment flips every region after it). It
|
|
538
|
+
// declines on text it cannot tokenise with confidence; the pair scan below
|
|
539
|
+
// then keeps the previous behaviour.
|
|
540
|
+
const codeOnly = codeMask(command);
|
|
541
|
+
if (codeOnly) {
|
|
542
|
+
let masked = '';
|
|
543
|
+
for (let i = 0; i < command.length; i++) masked += codeOnly[i] ? command[i] : '\0';
|
|
544
|
+
return masked;
|
|
545
|
+
}
|
|
521
546
|
const FILL = '\0';
|
|
522
547
|
let out = '';
|
|
523
548
|
let quote = null;
|
|
@@ -655,6 +680,12 @@ export function hasPowerShellOnlySyntax(command) {
|
|
|
655
680
|
const INLINE_SCRIPT_RE =
|
|
656
681
|
/\b(node(?:\.exe)?|python3?|py)((?:\s+--?[\w-]+(?:=[^\s"']+)?)*)\s+(-e|--eval|-c)\s+"((?:[^"\\]|\\.)*)"/;
|
|
657
682
|
const INLINE_UNSAFE_BODY = /[$`\\]/;
|
|
683
|
+
// What may follow the closing quote for the run to be the same as a file run:
|
|
684
|
+
// end of command or a shell operator/newline. Anything else is either more
|
|
685
|
+
// argument text (`""` is an escaped quote in PowerShell, and adjacent quoted
|
|
686
|
+
// strings concatenate in bash, so the body did not end at that quote) or script
|
|
687
|
+
// arguments, which `-e` and a file run number differently in process.argv.
|
|
688
|
+
const INLINE_TRAILING_OK = /^[ \t]*(?:$|[\r\n|;&)<>]|\d+>)/;
|
|
658
689
|
const INLINE_FILE_RELATIVE =
|
|
659
690
|
/(?:require|import)\s*\(\s*['"]\.{1,2}\/|from\s+['"]\.{1,2}\/|import\.meta|__dirname|__filename|__file__|require\.main|module\.(?:filename|parent)|sys\.argv\s*\[\s*0\s*\]|sys\.path\s*\[\s*0\s*\]|argv\s*\[\s*1\s*\]/;
|
|
660
691
|
const WINDOWS_INLINE_COMMAND_FILE_THRESHOLD = 24_000;
|
|
@@ -695,21 +726,143 @@ function inlineScriptFileShape(exe, rawFlags) {
|
|
|
695
726
|
return { extension, keptFlags: flags.replace(/\s+--input-type=\w+/g, '') };
|
|
696
727
|
}
|
|
697
728
|
|
|
698
|
-
|
|
729
|
+
// `python -c` puts the working directory first on sys.path; a script file puts
|
|
730
|
+
// its own (temp) directory there. Only a body whose every import is a standard
|
|
731
|
+
// library module resolves identically from both, so any other import — a
|
|
732
|
+
// project-local module, a relative import, a dynamic import — keeps `-c`.
|
|
733
|
+
const PYTHON_STDLIB = new Set(
|
|
734
|
+
`__future__ abc argparse array ast asyncio atexit base64 bdb binascii bisect builtins bz2 cProfile calendar cmath cmd code codecs codeop collections colorsys compileall concurrent configparser contextlib contextvars copy copyreg csv ctypes curses dataclasses datetime dbm decimal difflib dis doctest email encodings ensurepip enum errno faulthandler fcntl filecmp fileinput fnmatch fractions ftplib functools gc genericpath getopt getpass gettext glob graphlib grp gzip hashlib heapq hmac html http imaplib inspect io ipaddress itertools json keyword linecache locale logging lzma mailbox marshal math mimetypes mmap modulefinder msvcrt multiprocessing netrc nt ntpath numbers opcode operator optparse os pathlib pdb pickle pickletools pkgutil platform plistlib poplib posix posixpath pprint profile pstats pty pwd py_compile pyclbr pydoc pyexpat queue quopri random re readline reprlib resource rlcompleter runpy sched secrets select selectors shelve shlex shutil signal site smtplib socket socketserver sqlite3 ssl stat statistics string stringprep struct subprocess symtable sys sysconfig syslog tabnanny tarfile tempfile termios textwrap threading time timeit tkinter token tokenize tomllib trace traceback tracemalloc tty turtle types typing unicodedata unittest urllib uuid venv warnings wave weakref webbrowser winreg winsound wsgiref xml xmlrpc zipapp zipfile zipimport zlib zoneinfo`.split(
|
|
735
|
+
' '
|
|
736
|
+
)
|
|
737
|
+
);
|
|
738
|
+
const PYTHON_DYNAMIC_IMPORT = /\b(?:__import__|importlib|exec|eval|compile|runpy)\b/;
|
|
739
|
+
function pythonImportsOutsideStdlib(body) {
|
|
740
|
+
if (PYTHON_DYNAMIC_IMPORT.test(body)) return true;
|
|
741
|
+
for (const [, module] of body.matchAll(/(?:^|[;:\n])[ \t]*from[ \t]+([.\w]+)[ \t]+import\b/g)) {
|
|
742
|
+
if (module.startsWith('.') || !PYTHON_STDLIB.has(module.split('.')[0])) return true;
|
|
743
|
+
}
|
|
744
|
+
for (const [, list] of body.matchAll(/(?:^|[;:\n])[ \t]*import[ \t]+([^;\n]+)/g)) {
|
|
745
|
+
for (const part of list.split(',')) {
|
|
746
|
+
const name = part
|
|
747
|
+
.trim()
|
|
748
|
+
.split(/\s+as\s+/)[0]
|
|
749
|
+
.trim()
|
|
750
|
+
.split('.')[0];
|
|
751
|
+
if (name && !PYTHON_STDLIB.has(name)) return true;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// `node -e` accepts ESM syntax and top-level `await` (Node's syntax detection);
|
|
758
|
+
// the `.cjs` file a hoist writes does not, so a body using either stays inline.
|
|
759
|
+
// An `await` is top-level exactly when the body no longer compiles as CommonJS.
|
|
760
|
+
const NODE_ESM_SYNTAX = /(?:^|[;{}\n])[ \t]*(?:import[ \t]*[\w$*{'"]|export[ \t])/;
|
|
761
|
+
function nodeBodyNeedsModuleSyntax(body) {
|
|
762
|
+
if (NODE_ESM_SYNTAX.test(body)) return true;
|
|
763
|
+
if (!/\bawait\b/.test(body)) return false;
|
|
764
|
+
try {
|
|
765
|
+
new Script(`(function (exports, require, module, __filename, __dirname) {${body}\n})`);
|
|
766
|
+
return false;
|
|
767
|
+
} catch {
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// PowerShell ends a double-quoted string at any typographic double quote and a
|
|
773
|
+
// single-quoted one at any typographic single quote.
|
|
774
|
+
const POWERSHELL_SMART_DOUBLE = /[\u201C\u201D\u201E\u201F]/;
|
|
775
|
+
const POWERSHELL_SMART_SINGLE = /[\u2018\u2019\u201A\u201B]/;
|
|
776
|
+
|
|
777
|
+
function defaultInlineShellType() {
|
|
778
|
+
return process.platform === 'win32' ? 'powershell' : 'posix';
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// True when `index` is in live shell code of a POSIX shell: not inside a
|
|
782
|
+
// quote, `$'…'`, comment, or a heredoc body (any heredoc earlier in the text
|
|
783
|
+
// declines, since its body cannot be bounded here).
|
|
784
|
+
function posixLiveAt(text, index) {
|
|
785
|
+
let i = 0;
|
|
786
|
+
const skipTo = (end) => {
|
|
787
|
+
if (end < 0 || end >= index) return false;
|
|
788
|
+
i = end + 1;
|
|
789
|
+
return true;
|
|
790
|
+
};
|
|
791
|
+
// The quote that closes a region whose backslashes escape (`"…"`, `$'…'`).
|
|
792
|
+
const closer = (from, quote) => {
|
|
793
|
+
let j = from;
|
|
794
|
+
while (j < text.length && text[j] !== quote) j += text[j] === '\\' ? 2 : 1;
|
|
795
|
+
return j < text.length ? j : -1;
|
|
796
|
+
};
|
|
797
|
+
while (i < index) {
|
|
798
|
+
const ch = text[i];
|
|
799
|
+
if (ch === '\\') i += 2;
|
|
800
|
+
else if (ch === "'") {
|
|
801
|
+
if (!skipTo(text.indexOf("'", i + 1))) return false;
|
|
802
|
+
} else if (ch === '$' && text[i + 1] === "'") {
|
|
803
|
+
if (!skipTo(closer(i + 2, "'"))) return false;
|
|
804
|
+
} else if (ch === '"') {
|
|
805
|
+
if (!skipTo(closer(i + 1, '"'))) return false;
|
|
806
|
+
} else if (ch === '#' && (i === 0 || /[\s;&|(]/.test(text[i - 1]))) {
|
|
807
|
+
const nl = text.indexOf('\n', i);
|
|
808
|
+
if (nl < 0 || nl >= index) return false;
|
|
809
|
+
i = nl;
|
|
810
|
+
} else if (ch === '<' && text[i + 1] === '<' && text[i + 2] !== '<') return false;
|
|
811
|
+
else i++;
|
|
812
|
+
}
|
|
813
|
+
return true;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// A node/python invocation may be turned into a file run only when the shell
|
|
817
|
+
// itself runs it: live code (not text inside a string, here-string, comment or
|
|
818
|
+
// heredoc) at command position (not an argument of docker/ssh/wsl/ts-node/…,
|
|
819
|
+
// which would run the file somewhere that cannot see it).
|
|
820
|
+
function isShellInvocationAt(text, index, shellType) {
|
|
821
|
+
if (shellType === 'powershell') {
|
|
822
|
+
if (!codeMask(text)?.[index]) return false;
|
|
823
|
+
} else if (!posixLiveAt(text, index)) {
|
|
824
|
+
return false;
|
|
825
|
+
}
|
|
826
|
+
let start = index;
|
|
827
|
+
while (start > 0 && /[^\s;|&(){}<>'"]/.test(text[start - 1])) start--;
|
|
828
|
+
if (start < index && !/[\\/]$/.test(text.slice(start, index))) return false;
|
|
829
|
+
const before = text.slice(0, start);
|
|
830
|
+
const assignments = /(?:^|[\s;|&(){}])[A-Za-z_]\w*=[^\s;|&(){}]*[ \t]+$/;
|
|
831
|
+
let head = before;
|
|
832
|
+
while (assignments.test(head)) head = head.replace(assignments, (m) => m.match(/^[\s;|&(){}]?/)[0]);
|
|
833
|
+
const trimmed = head.replace(/[ \t]+$/, '');
|
|
834
|
+
if (trimmed === '') return true;
|
|
835
|
+
const last = trimmed.at(-1);
|
|
836
|
+
if (!';|&({\n'.includes(last)) return false;
|
|
837
|
+
// A line continuation joins the next line onto this command's arguments.
|
|
838
|
+
return !(last === '\n' && /[\\`]\r?\n$/.test(trimmed));
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
export function planInlineScriptHoist(command, { shellType = defaultInlineShellType() } = {}) {
|
|
699
842
|
const text = String(command || '');
|
|
700
843
|
if (!text.trim()) return null;
|
|
701
844
|
const match = text.match(INLINE_SCRIPT_RE);
|
|
702
845
|
if (!match) return null;
|
|
703
846
|
const [whole, exe, rawFlags, , body] = match;
|
|
704
847
|
if (INLINE_UNSAFE_BODY.test(body)) return null;
|
|
848
|
+
if (!INLINE_TRAILING_OK.test(text.slice(match.index + whole.length))) return null;
|
|
705
849
|
if (INLINE_FILE_RELATIVE.test(body)) return null;
|
|
706
|
-
if (
|
|
850
|
+
if (shellType === 'powershell' && POWERSHELL_SMART_DOUBLE.test(body)) return null;
|
|
851
|
+
const isNode = /^node/i.test(exe);
|
|
852
|
+
if (isNode && importsPackage(body)) return null;
|
|
853
|
+
if (isNode && !/--input-type=module/.test(rawFlags) && nodeBodyNeedsModuleSyntax(body)) return null;
|
|
854
|
+
if (!isNode && pythonImportsOutsideStdlib(body)) return null;
|
|
855
|
+
if (!isShellInvocationAt(text, match.index, shellType)) return null;
|
|
707
856
|
const { extension, keptFlags } = inlineScriptFileShape(exe, rawFlags);
|
|
708
857
|
return {
|
|
709
858
|
exe,
|
|
710
859
|
extension,
|
|
711
860
|
body,
|
|
712
|
-
|
|
861
|
+
// Spliced at the match offset, not by searching for its text: an earlier
|
|
862
|
+
// look-alike (`xnode -e "…"`) must not be the one rewritten. Plain
|
|
863
|
+
// concatenation also keeps a path holding `$&` / `$1` from being expanded.
|
|
864
|
+
replace: (filePath) =>
|
|
865
|
+
`${text.slice(0, match.index)}${exe}${keptFlags} "${filePath}"${text.slice(match.index + whole.length)}`,
|
|
713
866
|
};
|
|
714
867
|
}
|
|
715
868
|
|
|
@@ -727,11 +880,26 @@ export function planLongInlineScriptFileTransport(
|
|
|
727
880
|
const match = text.match(LONG_INLINE_SCRIPT_RE);
|
|
728
881
|
if (!match) return null;
|
|
729
882
|
const [whole, exe, rawFlags, , doubleBody, singleBody] = match;
|
|
883
|
+
const powershell = String(shellType || '').toLowerCase() === 'powershell';
|
|
884
|
+
// The same refusals as the short hoist: text after the closing quote means
|
|
885
|
+
// the body did not end there (or the script has arguments), and the match
|
|
886
|
+
// must be a command the shell itself runs.
|
|
887
|
+
if (!INLINE_TRAILING_OK.test(text.slice(match.index + whole.length))) return null;
|
|
888
|
+
if (!isShellInvocationAt(text, match.index, powershell ? 'powershell' : 'posix')) return null;
|
|
730
889
|
const { extension, keptFlags } = inlineScriptFileShape(exe, rawFlags);
|
|
731
890
|
let body = doubleBody;
|
|
732
891
|
if (body === undefined) {
|
|
733
892
|
body = String(singleBody || '');
|
|
734
|
-
|
|
893
|
+
if (powershell && POWERSHELL_SMART_SINGLE.test(body)) return null;
|
|
894
|
+
body = powershell ? body.replace(/''/g, "'") : body.replace(/''/g, '');
|
|
895
|
+
} else if (
|
|
896
|
+
// Inside double quotes the shell rewrites `$…`, backtick and backslash
|
|
897
|
+
// escapes (and PowerShell ends the string at a typographic quote) before
|
|
898
|
+
// the child sees them, so the verbatim body would be a different script.
|
|
899
|
+
/[$`]|\\"/.test(body) ||
|
|
900
|
+
(powershell ? POWERSHELL_SMART_DOUBLE.test(body) : body.includes('\\\\'))
|
|
901
|
+
) {
|
|
902
|
+
return null;
|
|
735
903
|
}
|
|
736
904
|
return {
|
|
737
905
|
command: text,
|
|
@@ -739,7 +907,7 @@ export function planLongInlineScriptFileTransport(
|
|
|
739
907
|
body,
|
|
740
908
|
replace(filePath) {
|
|
741
909
|
const normalized = String(filePath || '').replace(/\\/g, '/');
|
|
742
|
-
return text.
|
|
910
|
+
return `${text.slice(0, match.index)}${exe}${keptFlags} "${normalized}"${text.slice(match.index + whole.length)}`;
|
|
743
911
|
},
|
|
744
912
|
};
|
|
745
913
|
}
|
|
@@ -769,7 +937,11 @@ export function planLongShellScriptFileTransport(
|
|
|
769
937
|
const escaped = String(filePath || '')
|
|
770
938
|
.replace(/\\/g, '/')
|
|
771
939
|
.replace(/'/g, "''");
|
|
772
|
-
|
|
940
|
+
// Evaluated in the host's own scope, not called as a script: `& file.ps1`
|
|
941
|
+
// turns `exit N` into exit code 1 and a failing last command into 0,
|
|
942
|
+
// where the command itself would have ended with N and 1. The trailing
|
|
943
|
+
// statement reads the last command's `$?` the way -Command does.
|
|
944
|
+
return `Invoke-Expression ([System.IO.File]::ReadAllText('${escaped}', [System.Text.Encoding]::UTF8) + "\`n" + '$global:__mixdogLastOk = $?'); if ($global:__mixdogLastOk -eq $false) { exit 1 }`;
|
|
773
945
|
},
|
|
774
946
|
};
|
|
775
947
|
}
|
|
@@ -937,9 +1109,8 @@ export async function analyzeShellCommandEffects(command, cwd) {
|
|
|
937
1109
|
const sub = String(tokens[1] || '').toLowerCase();
|
|
938
1110
|
if (['status', 'diff', 'show', 'log', 'rev-parse', 'branch', 'remote', 'ls-files'].includes(sub)) continue;
|
|
939
1111
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
}
|
|
1112
|
+
// A python run can write anywhere (alone it already classifies as global
|
|
1113
|
+
// below the read-only check), so it never narrows a chain's effects.
|
|
943
1114
|
global = true;
|
|
944
1115
|
continue;
|
|
945
1116
|
}
|
|
@@ -1054,6 +1225,9 @@ export function buildPowerShellFilterTeePlan(command) {
|
|
|
1054
1225
|
const trimmed = cmd.trimEnd();
|
|
1055
1226
|
if (!trimmed.endsWith(last)) return null;
|
|
1056
1227
|
if (!_groupingBalanced(last) || !_groupingBalanced(trimmed.slice(0, trimmed.length - last.length))) return null;
|
|
1228
|
+
// Behind `&&` / `||` the rewrite's leading assignment is not a pipeline (a
|
|
1229
|
+
// parse error), and it would run the producer even when `||` skips it.
|
|
1230
|
+
if (/(?:&&|\|\|)\s*$/.test(trimmed.slice(0, trimmed.length - last.length))) return null;
|
|
1057
1231
|
const stages = shellSplitPipelineSegments(last);
|
|
1058
1232
|
if (stages.length < 2) return null;
|
|
1059
1233
|
const tokenized = [];
|
|
@@ -23,6 +23,7 @@ const POWERSHELL_RESERVED_HEADS = new Set([
|
|
|
23
23
|
'dir',
|
|
24
24
|
'echo',
|
|
25
25
|
'erase',
|
|
26
|
+
'fc',
|
|
26
27
|
'foreach',
|
|
27
28
|
'help',
|
|
28
29
|
'history',
|
|
@@ -110,12 +111,19 @@ function _disabledByEnv(env) {
|
|
|
110
111
|
|
|
111
112
|
// Quote-aware argv split for native CreateProcess. Unlike shellTokenize this
|
|
112
113
|
// keeps Windows backslashes literal so `C:\foo\bar.exe` is one path token.
|
|
113
|
-
|
|
114
|
+
// `strict` is for callers that must reproduce PowerShell's own parse (the
|
|
115
|
+
// direct spawn): a form where PowerShell and this splitter could disagree —
|
|
116
|
+
// a `\"` that PowerShell does not treat as an escape, or text glued to the
|
|
117
|
+
// closing quote of a string that opened its argument (PowerShell starts a new
|
|
118
|
+
// argument there) — returns null so the command runs through the shell
|
|
119
|
+
// unchanged.
|
|
120
|
+
export function tokenizeDirectArgv(command, { strict = false } = {}) {
|
|
114
121
|
const text = String(command || '');
|
|
115
122
|
const tokens = [];
|
|
116
123
|
let current = '';
|
|
117
124
|
let quote = null;
|
|
118
125
|
let started = false;
|
|
126
|
+
let leading = false;
|
|
119
127
|
const push = () => {
|
|
120
128
|
if (started) tokens.push(current);
|
|
121
129
|
current = '';
|
|
@@ -125,12 +133,20 @@ export function tokenizeDirectArgv(command) {
|
|
|
125
133
|
const ch = text[i];
|
|
126
134
|
if (quote === '"') {
|
|
127
135
|
if (ch === '\\' && text[i + 1] === '"') {
|
|
136
|
+
if (strict) return null;
|
|
128
137
|
current += '"';
|
|
129
138
|
i += 1;
|
|
130
139
|
continue;
|
|
131
140
|
}
|
|
132
141
|
if (ch === '"') {
|
|
142
|
+
// PowerShell: a doubled quote inside a double-quoted string is a literal quote.
|
|
143
|
+
if (text[i + 1] === '"') {
|
|
144
|
+
current += '"';
|
|
145
|
+
i += 1;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
133
148
|
quote = null;
|
|
149
|
+
if (strict && leading && i + 1 < text.length && !/\s/.test(text[i + 1])) return null;
|
|
134
150
|
continue;
|
|
135
151
|
}
|
|
136
152
|
current += ch;
|
|
@@ -138,13 +154,24 @@ export function tokenizeDirectArgv(command) {
|
|
|
138
154
|
}
|
|
139
155
|
if (quote === "'") {
|
|
140
156
|
if (ch === "'") {
|
|
157
|
+
// PowerShell: a doubled quote inside a single-quoted string is a literal quote.
|
|
158
|
+
if (text[i + 1] === "'") {
|
|
159
|
+
current += "'";
|
|
160
|
+
i += 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
141
163
|
quote = null;
|
|
164
|
+
if (strict && leading && i + 1 < text.length && !/\s/.test(text[i + 1])) return null;
|
|
142
165
|
continue;
|
|
143
166
|
}
|
|
144
167
|
current += ch;
|
|
145
168
|
continue;
|
|
146
169
|
}
|
|
147
170
|
if (ch === "'" || ch === '"') {
|
|
171
|
+
// A string that opens an argument ends it: PowerShell starts a NEW
|
|
172
|
+
// argument at whatever is glued behind the closing quote. A string
|
|
173
|
+
// opened mid-argument (`a"b c"d`) joins its neighbours.
|
|
174
|
+
leading = !started;
|
|
148
175
|
quote = ch;
|
|
149
176
|
started = true;
|
|
150
177
|
continue;
|
|
@@ -185,6 +212,9 @@ export function commandNeedsShell(command) {
|
|
|
185
212
|
continue;
|
|
186
213
|
}
|
|
187
214
|
if ('|&;<>()\n\r{}'.includes(ch) || ch === '`') return true;
|
|
215
|
+
// A bare `~` expands to the home directory and `@name` splats, both before
|
|
216
|
+
// the native program sees the argument.
|
|
217
|
+
if ((ch === '~' || ch === '@') && (i === 0 || /\s/.test(text[i - 1]))) return true;
|
|
188
218
|
if ('*?[]'.includes(ch)) return true;
|
|
189
219
|
if (ch === '$') return true;
|
|
190
220
|
if (ch === '#' && (i === 0 || /\s/.test(text[i - 1]))) return true;
|
|
@@ -192,6 +222,14 @@ export function commandNeedsShell(command) {
|
|
|
192
222
|
return quote != null;
|
|
193
223
|
}
|
|
194
224
|
|
|
225
|
+
function _isFile(file) {
|
|
226
|
+
try {
|
|
227
|
+
return statSync(file).isFile();
|
|
228
|
+
} catch {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
195
233
|
function _isUsableExe(file) {
|
|
196
234
|
try {
|
|
197
235
|
if (!/\.exe$/i.test(file)) return false;
|
|
@@ -215,17 +253,29 @@ export function resolveDirectExe(
|
|
|
215
253
|
const resolved = isAbsolute(name) || /^[A-Za-z]:[\\/]/.test(name) ? name : pathResolve(baseCwd, name);
|
|
216
254
|
return _isUsableExe(resolved) ? resolved : null;
|
|
217
255
|
}
|
|
218
|
-
const
|
|
256
|
+
const explicitExe = /\.exe$/i.test(name);
|
|
219
257
|
const seen = new Set();
|
|
220
|
-
|
|
221
|
-
|
|
258
|
+
// PowerShell never looks in the current directory for a bare command name,
|
|
259
|
+
// so neither may this resolver: a project-local `tool.exe` is not `tool`.
|
|
260
|
+
for (const dir of String(pathValue || '').split(pathDelimiter)) {
|
|
222
261
|
if (!dir) continue;
|
|
223
262
|
const key = dir.toLowerCase();
|
|
224
263
|
if (seen.has(key)) continue;
|
|
225
264
|
seen.add(key);
|
|
226
|
-
if (seen.size > maxDirectories
|
|
227
|
-
|
|
265
|
+
if (seen.size > maxDirectories) break;
|
|
266
|
+
if (explicitExe) {
|
|
267
|
+
const candidate = pathJoin(dir, name);
|
|
268
|
+
if (_isUsableExe(candidate)) return candidate;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
// PowerShell takes the first directory holding ANY runnable form of the
|
|
272
|
+
// name, and a `.com`/`.ps1` there outranks the `.exe` while a `.cmd`/`.bat`
|
|
273
|
+
// shim in an earlier directory shadows a later `.exe`: all stay with the shell.
|
|
274
|
+
const has = (ext) => _isFile(pathJoin(dir, `${name}${ext}`));
|
|
275
|
+
if (has('.com') || has('.ps1')) return null;
|
|
276
|
+
const candidate = pathJoin(dir, `${name}.exe`);
|
|
228
277
|
if (_isUsableExe(candidate)) return candidate;
|
|
278
|
+
if (has('.bat') || has('.cmd')) return null;
|
|
229
279
|
}
|
|
230
280
|
return null;
|
|
231
281
|
}
|
|
@@ -244,7 +294,7 @@ export function planDirectExeSpawn(
|
|
|
244
294
|
if (_disabledByEnv(env)) return null;
|
|
245
295
|
const text = String(command || '').trim();
|
|
246
296
|
if (!text || commandNeedsShell(text) || hasPowerShellOnlySyntax(text)) return null;
|
|
247
|
-
const tokens = tokenizeDirectArgv(text);
|
|
297
|
+
const tokens = tokenizeDirectArgv(text, { strict: true });
|
|
248
298
|
if (!tokens || tokens.length === 0) return null;
|
|
249
299
|
if (tokens.includes('--%')) return null;
|
|
250
300
|
const head = tokens[0];
|