@m4ike1/ion-agent-core 0.1.0
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/README.md +622 -0
- package/dist/agent-loop.d.ts +24 -0
- package/dist/agent-loop.d.ts.map +1 -0
- package/dist/agent-loop.js +560 -0
- package/dist/agent-loop.js.map +1 -0
- package/dist/agent.d.ts +122 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +422 -0
- package/dist/agent.js.map +1 -0
- package/dist/harness/agent-harness.d.ts +716 -0
- package/dist/harness/agent-harness.d.ts.map +1 -0
- package/dist/harness/agent-harness.js +6 -0
- package/dist/harness/agent-harness.js.map +1 -0
- package/dist/harness/compaction/branch-summarization.d.ts +68 -0
- package/dist/harness/compaction/branch-summarization.d.ts.map +1 -0
- package/dist/harness/compaction/branch-summarization.js +176 -0
- package/dist/harness/compaction/branch-summarization.js.map +1 -0
- package/dist/harness/compaction/compaction.d.ts +126 -0
- package/dist/harness/compaction/compaction.d.ts.map +1 -0
- package/dist/harness/compaction/compaction.js +560 -0
- package/dist/harness/compaction/compaction.js.map +1 -0
- package/dist/harness/compaction/utils.d.ts +25 -0
- package/dist/harness/compaction/utils.d.ts.map +1 -0
- package/dist/harness/compaction/utils.js +120 -0
- package/dist/harness/compaction/utils.js.map +1 -0
- package/dist/harness/config.d.ts +9 -0
- package/dist/harness/config.d.ts.map +1 -0
- package/dist/harness/config.js +35 -0
- package/dist/harness/config.js.map +1 -0
- package/dist/harness/context.d.ts +9 -0
- package/dist/harness/context.d.ts.map +1 -0
- package/dist/harness/context.js +13 -0
- package/dist/harness/context.js.map +1 -0
- package/dist/harness/env/nodejs.d.ts +43 -0
- package/dist/harness/env/nodejs.d.ts.map +1 -0
- package/dist/harness/env/nodejs.js +865 -0
- package/dist/harness/env/nodejs.js.map +1 -0
- package/dist/harness/events.d.ts +25 -0
- package/dist/harness/events.d.ts.map +1 -0
- package/dist/harness/events.js +250 -0
- package/dist/harness/events.js.map +1 -0
- package/dist/harness/execution/assistant.d.ts +42 -0
- package/dist/harness/execution/assistant.d.ts.map +1 -0
- package/dist/harness/execution/assistant.js +82 -0
- package/dist/harness/execution/assistant.js.map +1 -0
- package/dist/harness/execution/effect-gate.d.ts +22 -0
- package/dist/harness/execution/effect-gate.d.ts.map +1 -0
- package/dist/harness/execution/effect-gate.js +49 -0
- package/dist/harness/execution/effect-gate.js.map +1 -0
- package/dist/harness/execution/tools.d.ts +67 -0
- package/dist/harness/execution/tools.d.ts.map +1 -0
- package/dist/harness/execution/tools.js +121 -0
- package/dist/harness/execution/tools.js.map +1 -0
- package/dist/harness/hooks.d.ts +38 -0
- package/dist/harness/hooks.d.ts.map +1 -0
- package/dist/harness/hooks.js +406 -0
- package/dist/harness/hooks.js.map +1 -0
- package/dist/harness/messages.d.ts +51 -0
- package/dist/harness/messages.d.ts.map +1 -0
- package/dist/harness/messages.js +102 -0
- package/dist/harness/messages.js.map +1 -0
- package/dist/harness/prompt-templates.d.ts +49 -0
- package/dist/harness/prompt-templates.d.ts.map +1 -0
- package/dist/harness/prompt-templates.js +225 -0
- package/dist/harness/prompt-templates.js.map +1 -0
- package/dist/harness/result.d.ts +133 -0
- package/dist/harness/result.d.ts.map +1 -0
- package/dist/harness/result.js +81 -0
- package/dist/harness/result.js.map +1 -0
- package/dist/harness/runtime/drive/boundary.d.ts +28 -0
- package/dist/harness/runtime/drive/boundary.d.ts.map +1 -0
- package/dist/harness/runtime/drive/boundary.js +173 -0
- package/dist/harness/runtime/drive/boundary.js.map +1 -0
- package/dist/harness/runtime/drive/checkpoint.d.ts +8 -0
- package/dist/harness/runtime/drive/checkpoint.d.ts.map +1 -0
- package/dist/harness/runtime/drive/checkpoint.js +133 -0
- package/dist/harness/runtime/drive/checkpoint.js.map +1 -0
- package/dist/harness/runtime/drive/deferred.d.ts +15 -0
- package/dist/harness/runtime/drive/deferred.d.ts.map +1 -0
- package/dist/harness/runtime/drive/deferred.js +179 -0
- package/dist/harness/runtime/drive/deferred.js.map +1 -0
- package/dist/harness/runtime/drive/generation.d.ts +8 -0
- package/dist/harness/runtime/drive/generation.d.ts.map +1 -0
- package/dist/harness/runtime/drive/generation.js +205 -0
- package/dist/harness/runtime/drive/generation.js.map +1 -0
- package/dist/harness/runtime/drive/reconcile.d.ts +5 -0
- package/dist/harness/runtime/drive/reconcile.d.ts.map +1 -0
- package/dist/harness/runtime/drive/reconcile.js +140 -0
- package/dist/harness/runtime/drive/reconcile.js.map +1 -0
- package/dist/harness/runtime/drive/recovery.d.ts +8 -0
- package/dist/harness/runtime/drive/recovery.d.ts.map +1 -0
- package/dist/harness/runtime/drive/recovery.js +85 -0
- package/dist/harness/runtime/drive/recovery.js.map +1 -0
- package/dist/harness/runtime/drive/response.d.ts +23 -0
- package/dist/harness/runtime/drive/response.d.ts.map +1 -0
- package/dist/harness/runtime/drive/response.js +394 -0
- package/dist/harness/runtime/drive/response.js.map +1 -0
- package/dist/harness/runtime/drive/retry.d.ts +4 -0
- package/dist/harness/runtime/drive/retry.d.ts.map +1 -0
- package/dist/harness/runtime/drive/retry.js +34 -0
- package/dist/harness/runtime/drive/retry.js.map +1 -0
- package/dist/harness/runtime/drive/structural.d.ts +32 -0
- package/dist/harness/runtime/drive/structural.d.ts.map +1 -0
- package/dist/harness/runtime/drive/structural.js +929 -0
- package/dist/harness/runtime/drive/structural.js.map +1 -0
- package/dist/harness/runtime/drive/terminal.d.ts +7 -0
- package/dist/harness/runtime/drive/terminal.d.ts.map +1 -0
- package/dist/harness/runtime/drive/terminal.js +49 -0
- package/dist/harness/runtime/drive/terminal.js.map +1 -0
- package/dist/harness/runtime/drive/tool-placement.d.ts +14 -0
- package/dist/harness/runtime/drive/tool-placement.d.ts.map +1 -0
- package/dist/harness/runtime/drive/tool-placement.js +244 -0
- package/dist/harness/runtime/drive/tool-placement.js.map +1 -0
- package/dist/harness/runtime/drive/tools.d.ts +6 -0
- package/dist/harness/runtime/drive/tools.d.ts.map +1 -0
- package/dist/harness/runtime/drive/tools.js +449 -0
- package/dist/harness/runtime/drive/tools.js.map +1 -0
- package/dist/harness/runtime/drive.d.ts +6 -0
- package/dist/harness/runtime/drive.d.ts.map +1 -0
- package/dist/harness/runtime/drive.js +93 -0
- package/dist/harness/runtime/drive.js.map +1 -0
- package/dist/harness/runtime/harness.d.ts +59 -0
- package/dist/harness/runtime/harness.d.ts.map +1 -0
- package/dist/harness/runtime/harness.js +311 -0
- package/dist/harness/runtime/harness.js.map +1 -0
- package/dist/harness/runtime/index.d.ts +2 -0
- package/dist/harness/runtime/index.d.ts.map +1 -0
- package/dist/harness/runtime/index.js +2 -0
- package/dist/harness/runtime/index.js.map +1 -0
- package/dist/harness/runtime/lane.d.ts +101 -0
- package/dist/harness/runtime/lane.d.ts.map +1 -0
- package/dist/harness/runtime/lane.js +1575 -0
- package/dist/harness/runtime/lane.js.map +1 -0
- package/dist/harness/runtime/progress.d.ts +15 -0
- package/dist/harness/runtime/progress.d.ts.map +1 -0
- package/dist/harness/runtime/progress.js +66 -0
- package/dist/harness/runtime/progress.js.map +1 -0
- package/dist/harness/runtime/reducer.d.ts +5 -0
- package/dist/harness/runtime/reducer.d.ts.map +1 -0
- package/dist/harness/runtime/reducer.js +240 -0
- package/dist/harness/runtime/reducer.js.map +1 -0
- package/dist/harness/runtime/restore.d.ts +24 -0
- package/dist/harness/runtime/restore.d.ts.map +1 -0
- package/dist/harness/runtime/restore.js +117 -0
- package/dist/harness/runtime/restore.js.map +1 -0
- package/dist/harness/runtime/transcript.d.ts +21 -0
- package/dist/harness/runtime/transcript.d.ts.map +1 -0
- package/dist/harness/runtime/transcript.js +72 -0
- package/dist/harness/runtime/transcript.js.map +1 -0
- package/dist/harness/runtime/types.d.ts +105 -0
- package/dist/harness/runtime/types.d.ts.map +1 -0
- package/dist/harness/runtime/types.js +61 -0
- package/dist/harness/runtime/types.js.map +1 -0
- package/dist/harness/session/commit.d.ts +53 -0
- package/dist/harness/session/commit.d.ts.map +1 -0
- package/dist/harness/session/commit.js +57 -0
- package/dist/harness/session/commit.js.map +1 -0
- package/dist/harness/session/context.d.ts +10 -0
- package/dist/harness/session/context.d.ts.map +1 -0
- package/dist/harness/session/context.js +49 -0
- package/dist/harness/session/context.js.map +1 -0
- package/dist/harness/session/fork-policy.d.ts +21 -0
- package/dist/harness/session/fork-policy.d.ts.map +1 -0
- package/dist/harness/session/fork-policy.js +55 -0
- package/dist/harness/session/fork-policy.js.map +1 -0
- package/dist/harness/session/fork.d.ts +16 -0
- package/dist/harness/session/fork.d.ts.map +1 -0
- package/dist/harness/session/fork.js +79 -0
- package/dist/harness/session/fork.js.map +1 -0
- package/dist/harness/session/in-memory-storage-state.d.ts +39 -0
- package/dist/harness/session/in-memory-storage-state.d.ts.map +1 -0
- package/dist/harness/session/in-memory-storage-state.js +276 -0
- package/dist/harness/session/in-memory-storage-state.js.map +1 -0
- package/dist/harness/session/index.d.ts +11 -0
- package/dist/harness/session/index.d.ts.map +1 -0
- package/dist/harness/session/index.js +9 -0
- package/dist/harness/session/index.js.map +1 -0
- package/dist/harness/session/jsonl/codec.d.ts +21 -0
- package/dist/harness/session/jsonl/codec.d.ts.map +1 -0
- package/dist/harness/session/jsonl/codec.js +45 -0
- package/dist/harness/session/jsonl/codec.js.map +1 -0
- package/dist/harness/session/jsonl/fork.d.ts +38 -0
- package/dist/harness/session/jsonl/fork.d.ts.map +1 -0
- package/dist/harness/session/jsonl/fork.js +264 -0
- package/dist/harness/session/jsonl/fork.js.map +1 -0
- package/dist/harness/session/jsonl/index.d.ts +4 -0
- package/dist/harness/session/jsonl/index.d.ts.map +1 -0
- package/dist/harness/session/jsonl/index.js +4 -0
- package/dist/harness/session/jsonl/index.js.map +1 -0
- package/dist/harness/session/jsonl/io.d.ts +14 -0
- package/dist/harness/session/jsonl/io.d.ts.map +1 -0
- package/dist/harness/session/jsonl/io.js +86 -0
- package/dist/harness/session/jsonl/io.js.map +1 -0
- package/dist/harness/session/jsonl/legacy-v3.d.ts +43 -0
- package/dist/harness/session/jsonl/legacy-v3.d.ts.map +1 -0
- package/dist/harness/session/jsonl/legacy-v3.js +462 -0
- package/dist/harness/session/jsonl/legacy-v3.js.map +1 -0
- package/dist/harness/session/jsonl/repo.d.ts +35 -0
- package/dist/harness/session/jsonl/repo.d.ts.map +1 -0
- package/dist/harness/session/jsonl/repo.js +301 -0
- package/dist/harness/session/jsonl/repo.js.map +1 -0
- package/dist/harness/session/jsonl/storage.d.ts +40 -0
- package/dist/harness/session/jsonl/storage.d.ts.map +1 -0
- package/dist/harness/session/jsonl/storage.js +200 -0
- package/dist/harness/session/jsonl/storage.js.map +1 -0
- package/dist/harness/session/jsonl/types.d.ts +39 -0
- package/dist/harness/session/jsonl/types.d.ts.map +1 -0
- package/dist/harness/session/jsonl/types.js +3 -0
- package/dist/harness/session/jsonl/types.js.map +1 -0
- package/dist/harness/session/memory.d.ts +48 -0
- package/dist/harness/session/memory.d.ts.map +1 -0
- package/dist/harness/session/memory.js +369 -0
- package/dist/harness/session/memory.js.map +1 -0
- package/dist/harness/session/mutation-line.d.ts +8 -0
- package/dist/harness/session/mutation-line.d.ts.map +1 -0
- package/dist/harness/session/mutation-line.js +21 -0
- package/dist/harness/session/mutation-line.js.map +1 -0
- package/dist/harness/session/session.d.ts +82 -0
- package/dist/harness/session/session.d.ts.map +1 -0
- package/dist/harness/session/session.js +361 -0
- package/dist/harness/session/session.js.map +1 -0
- package/dist/harness/session/testing/benchmark/datasets.d.ts +12 -0
- package/dist/harness/session/testing/benchmark/datasets.d.ts.map +1 -0
- package/dist/harness/session/testing/benchmark/datasets.js +24 -0
- package/dist/harness/session/testing/benchmark/datasets.js.map +1 -0
- package/dist/harness/session/testing/benchmark/session-repo.d.ts +42 -0
- package/dist/harness/session/testing/benchmark/session-repo.d.ts.map +1 -0
- package/dist/harness/session/testing/benchmark/session-repo.js +127 -0
- package/dist/harness/session/testing/benchmark/session-repo.js.map +1 -0
- package/dist/harness/session/testing/benchmark/storage.d.ts +25 -0
- package/dist/harness/session/testing/benchmark/storage.d.ts.map +1 -0
- package/dist/harness/session/testing/benchmark/storage.js +114 -0
- package/dist/harness/session/testing/benchmark/storage.js.map +1 -0
- package/dist/harness/session/testing/conformance/session-repo.d.ts +23 -0
- package/dist/harness/session/testing/conformance/session-repo.d.ts.map +1 -0
- package/dist/harness/session/testing/conformance/session-repo.js +809 -0
- package/dist/harness/session/testing/conformance/session-repo.js.map +1 -0
- package/dist/harness/session/testing/conformance/storage.d.ts +4 -0
- package/dist/harness/session/testing/conformance/storage.d.ts.map +1 -0
- package/dist/harness/session/testing/conformance/storage.js +600 -0
- package/dist/harness/session/testing/conformance/storage.js.map +1 -0
- package/dist/harness/session/testing/gating-storage.d.ts +26 -0
- package/dist/harness/session/testing/gating-storage.d.ts.map +1 -0
- package/dist/harness/session/testing/gating-storage.js +100 -0
- package/dist/harness/session/testing/gating-storage.js.map +1 -0
- package/dist/harness/session/testing/index.d.ts +10 -0
- package/dist/harness/session/testing/index.d.ts.map +1 -0
- package/dist/harness/session/testing/index.js +9 -0
- package/dist/harness/session/testing/index.js.map +1 -0
- package/dist/harness/session/testing/instrumented-storage.d.ts +11 -0
- package/dist/harness/session/testing/instrumented-storage.d.ts.map +1 -0
- package/dist/harness/session/testing/instrumented-storage.js +16 -0
- package/dist/harness/session/testing/instrumented-storage.js.map +1 -0
- package/dist/harness/session/testing/storage-decorator.d.ts +20 -0
- package/dist/harness/session/testing/storage-decorator.d.ts.map +1 -0
- package/dist/harness/session/testing/storage-decorator.js +41 -0
- package/dist/harness/session/testing/storage-decorator.js.map +1 -0
- package/dist/harness/session/testing/types.d.ts +12 -0
- package/dist/harness/session/testing/types.d.ts.map +1 -0
- package/dist/harness/session/testing/types.js +2 -0
- package/dist/harness/session/testing/types.js.map +1 -0
- package/dist/harness/session/types.d.ts +512 -0
- package/dist/harness/session/types.d.ts.map +1 -0
- package/dist/harness/session/types.js +9 -0
- package/dist/harness/session/types.js.map +1 -0
- package/dist/harness/session/values.d.ts +95 -0
- package/dist/harness/session/values.d.ts.map +1 -0
- package/dist/harness/session/values.js +81 -0
- package/dist/harness/session/values.js.map +1 -0
- package/dist/harness/skills.d.ts +46 -0
- package/dist/harness/skills.d.ts.map +1 -0
- package/dist/harness/skills.js +326 -0
- package/dist/harness/skills.js.map +1 -0
- package/dist/harness/system-prompt.d.ts +3 -0
- package/dist/harness/system-prompt.d.ts.map +1 -0
- package/dist/harness/system-prompt.js +30 -0
- package/dist/harness/system-prompt.js.map +1 -0
- package/dist/harness/telemetry.d.ts +1242 -0
- package/dist/harness/telemetry.d.ts.map +1 -0
- package/dist/harness/telemetry.js +527 -0
- package/dist/harness/telemetry.js.map +1 -0
- package/dist/harness/tools/bash.d.ts +27 -0
- package/dist/harness/tools/bash.d.ts.map +1 -0
- package/dist/harness/tools/bash.js +105 -0
- package/dist/harness/tools/bash.js.map +1 -0
- package/dist/harness/tools/edit-diff.d.ts +89 -0
- package/dist/harness/tools/edit-diff.d.ts.map +1 -0
- package/dist/harness/tools/edit-diff.js +386 -0
- package/dist/harness/tools/edit-diff.js.map +1 -0
- package/dist/harness/tools/edit.d.ts +19 -0
- package/dist/harness/tools/edit.d.ts.map +1 -0
- package/dist/harness/tools/edit.js +108 -0
- package/dist/harness/tools/edit.js.map +1 -0
- package/dist/harness/tools/file-mutation-queue.d.ts +5 -0
- package/dist/harness/tools/file-mutation-queue.d.ts.map +1 -0
- package/dist/harness/tools/file-mutation-queue.js +46 -0
- package/dist/harness/tools/file-mutation-queue.js.map +1 -0
- package/dist/harness/tools/image.d.ts +3 -0
- package/dist/harness/tools/image.d.ts.map +1 -0
- package/dist/harness/tools/image.js +106 -0
- package/dist/harness/tools/image.js.map +1 -0
- package/dist/harness/tools/index.d.ts +6 -0
- package/dist/harness/tools/index.d.ts.map +1 -0
- package/dist/harness/tools/index.js +5 -0
- package/dist/harness/tools/index.js.map +1 -0
- package/dist/harness/tools/path-utils.d.ts +5 -0
- package/dist/harness/tools/path-utils.d.ts.map +1 -0
- package/dist/harness/tools/path-utils.js +26 -0
- package/dist/harness/tools/path-utils.js.map +1 -0
- package/dist/harness/tools/read.d.ts +35 -0
- package/dist/harness/tools/read.d.ts.map +1 -0
- package/dist/harness/tools/read.js +108 -0
- package/dist/harness/tools/read.js.map +1 -0
- package/dist/harness/tools/tool-context.d.ts +6 -0
- package/dist/harness/tools/tool-context.d.ts.map +1 -0
- package/dist/harness/tools/tool-context.js +2 -0
- package/dist/harness/tools/tool-context.js.map +1 -0
- package/dist/harness/tools/write.d.ts +11 -0
- package/dist/harness/tools/write.d.ts.map +1 -0
- package/dist/harness/tools/write.js +31 -0
- package/dist/harness/tools/write.js.map +1 -0
- package/dist/harness/types.d.ts +305 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +75 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/harness/utils/adaptive-publisher.d.ts +24 -0
- package/dist/harness/utils/adaptive-publisher.d.ts.map +1 -0
- package/dist/harness/utils/adaptive-publisher.js +79 -0
- package/dist/harness/utils/adaptive-publisher.js.map +1 -0
- package/dist/harness/utils/output-capture.d.ts +31 -0
- package/dist/harness/utils/output-capture.d.ts.map +1 -0
- package/dist/harness/utils/output-capture.js +217 -0
- package/dist/harness/utils/output-capture.js.map +1 -0
- package/dist/harness/utils/shell-output.d.ts +29 -0
- package/dist/harness/utils/shell-output.d.ts.map +1 -0
- package/dist/harness/utils/shell-output.js +71 -0
- package/dist/harness/utils/shell-output.js.map +1 -0
- package/dist/harness/utils/truncate.d.ts +71 -0
- package/dist/harness/utils/truncate.d.ts.map +1 -0
- package/dist/harness/utils/truncate.js +296 -0
- package/dist/harness/utils/truncate.js.map +1 -0
- package/dist/harness/utils/usage.d.ts +4 -0
- package/dist/harness/utils/usage.d.ts.map +1 -0
- package/dist/harness/utils/usage.js +33 -0
- package/dist/harness/utils/usage.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/node.d.ts +3 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +3 -0
- package/dist/node.js.map +1 -0
- package/dist/proxy.d.ts +72 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +310 -0
- package/dist/proxy.js.map +1 -0
- package/dist/search/index.d.ts +29 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +2 -0
- package/dist/search/index.js.map +1 -0
- package/dist/stream-fn.d.ts +10 -0
- package/dist/stream-fn.d.ts.map +1 -0
- package/dist/stream-fn.js +17 -0
- package/dist/stream-fn.js.map +1 -0
- package/dist/types.d.ts +416 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { constants, createWriteStream } from "node:fs";
|
|
4
|
+
import { access, appendFile, lstat, mkdir, mkdtemp, open as openFile, readdir, readFile, realpath, rename, rm, writeFile, } from "node:fs/promises";
|
|
5
|
+
import { homedir, constants as osConstants, tmpdir } from "node:os";
|
|
6
|
+
import { basename, isAbsolute, join, resolve } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { ExecutionError, err, FileError, ok, toError, } from "../types.js";
|
|
9
|
+
import { OutputCapture } from "../utils/output-capture.js";
|
|
10
|
+
const MAX_TIMEOUT_MS = 2_147_483_647;
|
|
11
|
+
const MAX_TIMEOUT_SECONDS = MAX_TIMEOUT_MS / 1000;
|
|
12
|
+
const EXIT_STDIO_GRACE_MS = 100;
|
|
13
|
+
const SPILL_HIGH_WATER_MARK = 8 * 1024 * 1024;
|
|
14
|
+
function resolveTimeoutMs(timeout) {
|
|
15
|
+
if (timeout === undefined)
|
|
16
|
+
return ok(undefined);
|
|
17
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
18
|
+
return err(new ExecutionError("timeout", "Invalid timeout: must be a finite number of seconds"));
|
|
19
|
+
}
|
|
20
|
+
const timeoutMs = timeout * 1000;
|
|
21
|
+
if (timeoutMs > MAX_TIMEOUT_MS) {
|
|
22
|
+
return err(new ExecutionError("timeout", `Invalid timeout: maximum is ${MAX_TIMEOUT_SECONDS} seconds`));
|
|
23
|
+
}
|
|
24
|
+
return ok(timeoutMs);
|
|
25
|
+
}
|
|
26
|
+
function resolvePath(cwd, path) {
|
|
27
|
+
let normalized = path;
|
|
28
|
+
if (normalized === "~") {
|
|
29
|
+
normalized = homedir();
|
|
30
|
+
}
|
|
31
|
+
else if (normalized.startsWith("~/") || (process.platform === "win32" && normalized.startsWith("~\\"))) {
|
|
32
|
+
normalized = join(homedir(), normalized.slice(2));
|
|
33
|
+
}
|
|
34
|
+
else if (normalized.startsWith("file://")) {
|
|
35
|
+
try {
|
|
36
|
+
normalized = fileURLToPath(normalized);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Keep malformed URLs as ordinary paths so filesystem methods preserve their non-throwing contract.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return isAbsolute(normalized) ? resolve(normalized) : resolve(cwd, normalized);
|
|
43
|
+
}
|
|
44
|
+
function fileKindFromStats(stats) {
|
|
45
|
+
if (stats.isFile())
|
|
46
|
+
return "file";
|
|
47
|
+
if (stats.isDirectory())
|
|
48
|
+
return "directory";
|
|
49
|
+
if (stats.isSymbolicLink())
|
|
50
|
+
return "symlink";
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
function fileInfoFromStats(path, stats) {
|
|
54
|
+
const kind = fileKindFromStats(stats);
|
|
55
|
+
if (!kind)
|
|
56
|
+
return err(new FileError("invalid", "Unsupported file type", path));
|
|
57
|
+
return ok({
|
|
58
|
+
name: basename(path),
|
|
59
|
+
path,
|
|
60
|
+
kind,
|
|
61
|
+
size: stats.size,
|
|
62
|
+
mtimeMs: stats.mtimeMs,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function isNodeError(error) {
|
|
66
|
+
return error instanceof Error && "code" in error;
|
|
67
|
+
}
|
|
68
|
+
function toFileError(error, fallbackPath) {
|
|
69
|
+
if (error instanceof FileError)
|
|
70
|
+
return error;
|
|
71
|
+
const cause = toError(error);
|
|
72
|
+
const nodeError = isNodeError(error) ? error : undefined;
|
|
73
|
+
const path = typeof nodeError?.path === "string" ? nodeError.path : fallbackPath;
|
|
74
|
+
if (nodeError) {
|
|
75
|
+
const message = nodeError.message;
|
|
76
|
+
switch (nodeError.code) {
|
|
77
|
+
case "ABORT_ERR":
|
|
78
|
+
return new FileError("aborted", message, path, cause);
|
|
79
|
+
case "ENOENT":
|
|
80
|
+
return new FileError("not_found", message, path, cause);
|
|
81
|
+
case "EACCES":
|
|
82
|
+
case "EPERM":
|
|
83
|
+
return new FileError("permission_denied", message, path, cause);
|
|
84
|
+
case "ENOTDIR":
|
|
85
|
+
return new FileError("not_directory", message, path, cause);
|
|
86
|
+
case "EISDIR":
|
|
87
|
+
return new FileError("is_directory", message, path, cause);
|
|
88
|
+
case "EINVAL":
|
|
89
|
+
return new FileError("invalid", message, path, cause);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return new FileError("unknown", cause.message, path, cause);
|
|
93
|
+
}
|
|
94
|
+
function abortResult(signal, path) {
|
|
95
|
+
return signal?.aborted ? err(new FileError("aborted", "aborted", path)) : undefined;
|
|
96
|
+
}
|
|
97
|
+
async function pathExists(path) {
|
|
98
|
+
try {
|
|
99
|
+
await access(path, constants.F_OK);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function runCommand(command, args, timeoutMs) {
|
|
107
|
+
return await new Promise((resolve) => {
|
|
108
|
+
let stdout = "";
|
|
109
|
+
let child;
|
|
110
|
+
try {
|
|
111
|
+
child = spawn(command, args, {
|
|
112
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
113
|
+
windowsHide: true,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
resolve({ stdout: "", status: null });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const timeout = setTimeout(() => {
|
|
121
|
+
if (child.pid)
|
|
122
|
+
killProcessTree(child.pid);
|
|
123
|
+
}, timeoutMs);
|
|
124
|
+
child.stdout?.setEncoding("utf8");
|
|
125
|
+
child.stdout?.on("data", (chunk) => {
|
|
126
|
+
stdout += chunk;
|
|
127
|
+
});
|
|
128
|
+
child.on("error", () => {
|
|
129
|
+
clearTimeout(timeout);
|
|
130
|
+
resolve({ stdout: "", status: null });
|
|
131
|
+
});
|
|
132
|
+
child.on("close", (status) => {
|
|
133
|
+
clearTimeout(timeout);
|
|
134
|
+
resolve({ stdout, status });
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async function findBashOnPath() {
|
|
139
|
+
const result = process.platform === "win32"
|
|
140
|
+
? await runCommand("where", ["bash.exe"], 5000)
|
|
141
|
+
: await runCommand("which", ["bash"], 5000);
|
|
142
|
+
if (result.status !== 0 || !result.stdout)
|
|
143
|
+
return null;
|
|
144
|
+
const firstMatch = result.stdout.trim().split(/\r?\n/)[0];
|
|
145
|
+
return firstMatch && (await pathExists(firstMatch)) ? firstMatch : null;
|
|
146
|
+
}
|
|
147
|
+
function isLegacyWslBashPath(path) {
|
|
148
|
+
const normalized = path.replace(/\//g, "\\").toLowerCase();
|
|
149
|
+
return /^[a-z]:\\windows\\(?:system32|sysnative)\\bash\.exe$/.test(normalized);
|
|
150
|
+
}
|
|
151
|
+
function getBashShellConfig(shell) {
|
|
152
|
+
return isLegacyWslBashPath(shell) ? { shell, args: ["-s"], commandTransport: "stdin" } : { shell, args: ["-c"] };
|
|
153
|
+
}
|
|
154
|
+
async function getShellConfig(customShellPath) {
|
|
155
|
+
if (customShellPath) {
|
|
156
|
+
if (await pathExists(customShellPath)) {
|
|
157
|
+
return ok(getBashShellConfig(customShellPath));
|
|
158
|
+
}
|
|
159
|
+
return err(new ExecutionError("shell_unavailable", `Custom shell path not found: ${customShellPath}`));
|
|
160
|
+
}
|
|
161
|
+
if (process.platform === "win32") {
|
|
162
|
+
const candidates = [];
|
|
163
|
+
const programFiles = process.env.ProgramFiles;
|
|
164
|
+
if (programFiles)
|
|
165
|
+
candidates.push(`${programFiles}\\Git\\bin\\bash.exe`);
|
|
166
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"];
|
|
167
|
+
if (programFilesX86)
|
|
168
|
+
candidates.push(`${programFilesX86}\\Git\\bin\\bash.exe`);
|
|
169
|
+
for (const candidate of candidates) {
|
|
170
|
+
if (await pathExists(candidate)) {
|
|
171
|
+
return ok(getBashShellConfig(candidate));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const bashOnPath = await findBashOnPath();
|
|
175
|
+
if (bashOnPath) {
|
|
176
|
+
return ok(getBashShellConfig(bashOnPath));
|
|
177
|
+
}
|
|
178
|
+
return err(new ExecutionError("shell_unavailable", `No bash shell found. Options:\n` +
|
|
179
|
+
` 1. Install Git for Windows: https://git-scm.com/download/win\n` +
|
|
180
|
+
` 2. Add your bash to PATH (Cygwin, MSYS2, etc.)\n` +
|
|
181
|
+
" 3. Configure an explicit shellPath\n\n" +
|
|
182
|
+
`Searched Git Bash in:\n${candidates.map((path) => ` ${path}`).join("\n")}`));
|
|
183
|
+
}
|
|
184
|
+
if (await pathExists("/bin/bash")) {
|
|
185
|
+
return ok(getBashShellConfig("/bin/bash"));
|
|
186
|
+
}
|
|
187
|
+
const bashOnPath = await findBashOnPath();
|
|
188
|
+
if (bashOnPath) {
|
|
189
|
+
return ok(getBashShellConfig(bashOnPath));
|
|
190
|
+
}
|
|
191
|
+
return ok({ shell: "sh", args: ["-c"] });
|
|
192
|
+
}
|
|
193
|
+
function getShellEnv(baseEnv, extraEnv, inheritEnv = true) {
|
|
194
|
+
if (!inheritEnv)
|
|
195
|
+
return { ...extraEnv };
|
|
196
|
+
return {
|
|
197
|
+
...process.env,
|
|
198
|
+
...baseEnv,
|
|
199
|
+
...extraEnv,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function killProcessTree(pid) {
|
|
203
|
+
if (process.platform === "win32") {
|
|
204
|
+
try {
|
|
205
|
+
const child = spawn(join(process.env.SystemRoot ?? "C:\\Windows", "System32", "taskkill.exe"), ["/F", "/T", "/PID", String(pid)], {
|
|
206
|
+
stdio: "ignore",
|
|
207
|
+
detached: true,
|
|
208
|
+
windowsHide: true,
|
|
209
|
+
});
|
|
210
|
+
// A failed spawn emits "error" asynchronously; consume it to avoid crashing Node.
|
|
211
|
+
child.once("error", () => { });
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// Ignore errors.
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
process.kill(-pid, "SIGKILL");
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
try {
|
|
223
|
+
process.kill(pid, "SIGKILL");
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// Process already dead.
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function waitForChildProcess(child, spillIsDraining) {
|
|
231
|
+
return new Promise((resolvePromise, reject) => {
|
|
232
|
+
let settled = false;
|
|
233
|
+
let exited = false;
|
|
234
|
+
let exitCode = null;
|
|
235
|
+
let exitSignal = null;
|
|
236
|
+
let postExitTimer;
|
|
237
|
+
let stdoutEnded = child.stdout === null;
|
|
238
|
+
let stderrEnded = child.stderr === null;
|
|
239
|
+
const cleanup = () => {
|
|
240
|
+
if (postExitTimer)
|
|
241
|
+
clearTimeout(postExitTimer);
|
|
242
|
+
child.removeListener("error", onError);
|
|
243
|
+
child.removeListener("exit", onExit);
|
|
244
|
+
child.removeListener("close", onClose);
|
|
245
|
+
child.stdout?.removeListener("end", onStdoutEnd);
|
|
246
|
+
child.stderr?.removeListener("end", onStderrEnd);
|
|
247
|
+
child.stdout?.removeListener("data", onData);
|
|
248
|
+
child.stderr?.removeListener("data", onData);
|
|
249
|
+
};
|
|
250
|
+
const finalize = () => {
|
|
251
|
+
if (settled)
|
|
252
|
+
return;
|
|
253
|
+
settled = true;
|
|
254
|
+
cleanup();
|
|
255
|
+
child.stdout?.destroy();
|
|
256
|
+
child.stderr?.destroy();
|
|
257
|
+
resolvePromise({ code: exitCode, signal: exitSignal });
|
|
258
|
+
};
|
|
259
|
+
const maybeFinalizeAfterExit = () => {
|
|
260
|
+
if (exited && stdoutEnded && stderrEnded)
|
|
261
|
+
finalize();
|
|
262
|
+
};
|
|
263
|
+
const armIdleTimer = () => {
|
|
264
|
+
if (postExitTimer)
|
|
265
|
+
clearTimeout(postExitTimer);
|
|
266
|
+
postExitTimer = setTimeout(() => {
|
|
267
|
+
if (spillIsDraining())
|
|
268
|
+
armIdleTimer();
|
|
269
|
+
else
|
|
270
|
+
finalize();
|
|
271
|
+
}, EXIT_STDIO_GRACE_MS);
|
|
272
|
+
};
|
|
273
|
+
const onData = () => {
|
|
274
|
+
if (exited && !settled)
|
|
275
|
+
armIdleTimer();
|
|
276
|
+
};
|
|
277
|
+
const onStdoutEnd = () => {
|
|
278
|
+
stdoutEnded = true;
|
|
279
|
+
maybeFinalizeAfterExit();
|
|
280
|
+
};
|
|
281
|
+
const onStderrEnd = () => {
|
|
282
|
+
stderrEnded = true;
|
|
283
|
+
maybeFinalizeAfterExit();
|
|
284
|
+
};
|
|
285
|
+
const onError = (error) => {
|
|
286
|
+
if (settled)
|
|
287
|
+
return;
|
|
288
|
+
settled = true;
|
|
289
|
+
cleanup();
|
|
290
|
+
reject(error);
|
|
291
|
+
};
|
|
292
|
+
const onExit = (code, signal) => {
|
|
293
|
+
exited = true;
|
|
294
|
+
exitCode = code;
|
|
295
|
+
exitSignal = signal;
|
|
296
|
+
maybeFinalizeAfterExit();
|
|
297
|
+
if (!settled)
|
|
298
|
+
armIdleTimer();
|
|
299
|
+
};
|
|
300
|
+
const onClose = (code, signal) => {
|
|
301
|
+
exitCode = code;
|
|
302
|
+
exitSignal = signal;
|
|
303
|
+
finalize();
|
|
304
|
+
};
|
|
305
|
+
child.stdout?.once("end", onStdoutEnd);
|
|
306
|
+
child.stderr?.once("end", onStderrEnd);
|
|
307
|
+
child.stdout?.on("data", onData);
|
|
308
|
+
child.stderr?.on("data", onData);
|
|
309
|
+
child.once("error", onError);
|
|
310
|
+
child.once("exit", onExit);
|
|
311
|
+
child.once("close", onClose);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/** Strict LF reader; Node readline does not report whether its final line was newline-terminated. */
|
|
315
|
+
class NodeTextLineReader {
|
|
316
|
+
file;
|
|
317
|
+
path;
|
|
318
|
+
decoder = new TextDecoder();
|
|
319
|
+
chunk = new Uint8Array(64 * 1024);
|
|
320
|
+
byteOffset = 0;
|
|
321
|
+
buffered = "";
|
|
322
|
+
ended = false;
|
|
323
|
+
closed = false;
|
|
324
|
+
constructor(file, path) {
|
|
325
|
+
this.file = file;
|
|
326
|
+
this.path = path;
|
|
327
|
+
}
|
|
328
|
+
async readLine(context) {
|
|
329
|
+
const aborted = abortResult(context.abortSignal, this.path);
|
|
330
|
+
if (aborted)
|
|
331
|
+
return aborted;
|
|
332
|
+
if (this.closed)
|
|
333
|
+
return err(new FileError("invalid", "Text line reader is closed", this.path));
|
|
334
|
+
try {
|
|
335
|
+
while (true) {
|
|
336
|
+
const newline = this.buffered.indexOf("\n");
|
|
337
|
+
if (newline !== -1) {
|
|
338
|
+
const text = this.buffered.slice(0, newline);
|
|
339
|
+
this.buffered = this.buffered.slice(newline + 1);
|
|
340
|
+
return ok({ text, terminated: true });
|
|
341
|
+
}
|
|
342
|
+
if (this.ended) {
|
|
343
|
+
if (this.buffered.length === 0)
|
|
344
|
+
return ok(undefined);
|
|
345
|
+
const text = this.buffered;
|
|
346
|
+
this.buffered = "";
|
|
347
|
+
return ok({ text, terminated: false });
|
|
348
|
+
}
|
|
349
|
+
// Explicit positions allow an aborted read to be retried without skipping bytes.
|
|
350
|
+
const { bytesRead } = await this.file.read(this.chunk, 0, this.chunk.length, this.byteOffset);
|
|
351
|
+
const afterReadAbort = abortResult(context.abortSignal, this.path);
|
|
352
|
+
if (afterReadAbort)
|
|
353
|
+
return afterReadAbort;
|
|
354
|
+
this.byteOffset += bytesRead;
|
|
355
|
+
if (bytesRead === 0) {
|
|
356
|
+
this.buffered += this.decoder.decode();
|
|
357
|
+
this.ended = true;
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
this.buffered += this.decoder.decode(this.chunk.subarray(0, bytesRead), { stream: true });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
return err(toFileError(error, this.path));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async close(_context) {
|
|
369
|
+
if (this.closed)
|
|
370
|
+
return;
|
|
371
|
+
this.closed = true;
|
|
372
|
+
this.buffered = "";
|
|
373
|
+
try {
|
|
374
|
+
await this.file.close();
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
// Closing is best-effort, including after cancellation or an earlier I/O failure.
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
export class NodeExecutionEnv {
|
|
382
|
+
cwd;
|
|
383
|
+
shellPath;
|
|
384
|
+
shellEnv;
|
|
385
|
+
activeChildPids = new Set();
|
|
386
|
+
constructor(options) {
|
|
387
|
+
this.cwd = options.cwd;
|
|
388
|
+
this.shellPath = options.shellPath;
|
|
389
|
+
this.shellEnv = options.shellEnv;
|
|
390
|
+
}
|
|
391
|
+
async absolutePath(path, _context) {
|
|
392
|
+
return ok(resolvePath(this.cwd, path));
|
|
393
|
+
}
|
|
394
|
+
async joinPath(parts, _context) {
|
|
395
|
+
return ok(join(...parts));
|
|
396
|
+
}
|
|
397
|
+
async exec(command, options, context) {
|
|
398
|
+
const signal = context.abortSignal;
|
|
399
|
+
if (signal?.aborted)
|
|
400
|
+
return err(new ExecutionError("aborted", "aborted"));
|
|
401
|
+
const timeoutMsResult = resolveTimeoutMs(options?.timeout);
|
|
402
|
+
if (!timeoutMsResult.ok)
|
|
403
|
+
return err(timeoutMsResult.error);
|
|
404
|
+
const timeoutMs = timeoutMsResult.value;
|
|
405
|
+
const cwd = options?.cwd ? resolvePath(this.cwd, options.cwd) : this.cwd;
|
|
406
|
+
const shellConfig = await getShellConfig(this.shellPath);
|
|
407
|
+
if (!shellConfig.ok)
|
|
408
|
+
return shellConfig;
|
|
409
|
+
try {
|
|
410
|
+
await access(cwd, constants.F_OK);
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
const cause = toError(error);
|
|
414
|
+
return err(new ExecutionError("spawn_error", `Working directory does not exist: ${cwd}\nCannot execute bash commands.`, cause));
|
|
415
|
+
}
|
|
416
|
+
return await new Promise((resolvePromise) => {
|
|
417
|
+
let settled = false;
|
|
418
|
+
let timedOut = false;
|
|
419
|
+
let callbackError;
|
|
420
|
+
let spillError;
|
|
421
|
+
let child;
|
|
422
|
+
let timeoutId;
|
|
423
|
+
const spillPrefix = [];
|
|
424
|
+
let spillPath;
|
|
425
|
+
const spillQueue = [];
|
|
426
|
+
let spillStart;
|
|
427
|
+
let spillStream;
|
|
428
|
+
let spillBackpressured = false;
|
|
429
|
+
const onAbort = () => {
|
|
430
|
+
if (child?.pid)
|
|
431
|
+
killProcessTree(child.pid);
|
|
432
|
+
};
|
|
433
|
+
const failCallback = (error) => {
|
|
434
|
+
if (callbackError !== undefined)
|
|
435
|
+
return;
|
|
436
|
+
const cause = toError(error);
|
|
437
|
+
callbackError = new ExecutionError("callback_error", cause.message, cause);
|
|
438
|
+
onAbort();
|
|
439
|
+
};
|
|
440
|
+
let capture;
|
|
441
|
+
try {
|
|
442
|
+
capture = new OutputCapture(options?.capture, context, {
|
|
443
|
+
onUpdate: options?.onUpdate,
|
|
444
|
+
onError: failCallback,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
const cause = toError(error);
|
|
449
|
+
resolvePromise(err(new ExecutionError("unknown", cause.message, cause)));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const settle = (result) => {
|
|
453
|
+
if (settled)
|
|
454
|
+
return;
|
|
455
|
+
settled = true;
|
|
456
|
+
if (timeoutId)
|
|
457
|
+
clearTimeout(timeoutId);
|
|
458
|
+
if (signal)
|
|
459
|
+
signal.removeEventListener("abort", onAbort);
|
|
460
|
+
if (child?.pid)
|
|
461
|
+
this.activeChildPids.delete(child.pid);
|
|
462
|
+
capture.dispose();
|
|
463
|
+
resolvePromise(result);
|
|
464
|
+
};
|
|
465
|
+
const pauseOutput = () => {
|
|
466
|
+
child?.stdout?.pause();
|
|
467
|
+
child?.stderr?.pause();
|
|
468
|
+
};
|
|
469
|
+
const resumeOutput = () => {
|
|
470
|
+
if (callbackError || spillError || timedOut || signal?.aborted || spillBackpressured)
|
|
471
|
+
return;
|
|
472
|
+
child?.stdout?.resume();
|
|
473
|
+
child?.stderr?.resume();
|
|
474
|
+
};
|
|
475
|
+
const failSpill = (error) => {
|
|
476
|
+
if (spillError !== undefined)
|
|
477
|
+
return;
|
|
478
|
+
const cause = toError(error);
|
|
479
|
+
spillError = new ExecutionError("unknown", `Failed to preserve complete shell output: ${cause.message}`, cause);
|
|
480
|
+
spillBackpressured = false;
|
|
481
|
+
onAbort();
|
|
482
|
+
};
|
|
483
|
+
const writeSpill = (chunk) => {
|
|
484
|
+
if (spillStream === undefined || chunk.length === 0)
|
|
485
|
+
return;
|
|
486
|
+
if (spillStream.write(chunk) || spillBackpressured)
|
|
487
|
+
return;
|
|
488
|
+
spillBackpressured = true;
|
|
489
|
+
pauseOutput();
|
|
490
|
+
spillStream.once("drain", () => {
|
|
491
|
+
spillBackpressured = false;
|
|
492
|
+
resumeOutput();
|
|
493
|
+
});
|
|
494
|
+
};
|
|
495
|
+
const startSpill = (chunk) => {
|
|
496
|
+
if (spillStream !== undefined) {
|
|
497
|
+
writeSpill(chunk);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
spillQueue.push(chunk);
|
|
501
|
+
if (spillStart !== undefined)
|
|
502
|
+
return;
|
|
503
|
+
pauseOutput();
|
|
504
|
+
spillStart = (async () => {
|
|
505
|
+
const created = await this.createTempFile({ prefix: "ion-output-", suffix: ".log" }, context);
|
|
506
|
+
if (!created.ok)
|
|
507
|
+
throw created.error;
|
|
508
|
+
spillPath = created.value;
|
|
509
|
+
capture.setSpillPath(spillPath);
|
|
510
|
+
spillStream = createWriteStream(spillPath, { flags: "a", highWaterMark: SPILL_HIGH_WATER_MARK });
|
|
511
|
+
spillStream.on("error", failSpill);
|
|
512
|
+
for (const queued of spillQueue)
|
|
513
|
+
writeSpill(queued);
|
|
514
|
+
spillQueue.length = 0;
|
|
515
|
+
})()
|
|
516
|
+
.catch(failSpill)
|
|
517
|
+
.finally(resumeOutput);
|
|
518
|
+
};
|
|
519
|
+
const finishSpill = async () => {
|
|
520
|
+
await spillStart;
|
|
521
|
+
const stream = spillStream;
|
|
522
|
+
if (stream === undefined || spillError !== undefined || stream.destroyed)
|
|
523
|
+
return;
|
|
524
|
+
await new Promise((resolveFinish) => {
|
|
525
|
+
stream.once("error", () => resolveFinish());
|
|
526
|
+
stream.once("finish", resolveFinish);
|
|
527
|
+
stream.end();
|
|
528
|
+
});
|
|
529
|
+
};
|
|
530
|
+
try {
|
|
531
|
+
const commandFromStdin = shellConfig.value.commandTransport === "stdin";
|
|
532
|
+
child = spawn(shellConfig.value.shell, commandFromStdin ? shellConfig.value.args : [...shellConfig.value.args, command], {
|
|
533
|
+
cwd,
|
|
534
|
+
detached: process.platform !== "win32",
|
|
535
|
+
env: getShellEnv(this.shellEnv, options?.env, options?.inheritEnv),
|
|
536
|
+
stdio: [commandFromStdin ? "pipe" : "ignore", "pipe", "pipe"],
|
|
537
|
+
windowsHide: true,
|
|
538
|
+
});
|
|
539
|
+
if (child.pid)
|
|
540
|
+
this.activeChildPids.add(child.pid);
|
|
541
|
+
if (commandFromStdin) {
|
|
542
|
+
child.stdin?.on("error", () => { });
|
|
543
|
+
child.stdin?.end(command);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
const cause = toError(error);
|
|
548
|
+
settle(err(new ExecutionError("spawn_error", cause.message, cause)));
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
timeoutId =
|
|
552
|
+
timeoutMs === undefined
|
|
553
|
+
? undefined
|
|
554
|
+
: setTimeout(() => {
|
|
555
|
+
timedOut = true;
|
|
556
|
+
onAbort();
|
|
557
|
+
}, timeoutMs);
|
|
558
|
+
if (signal) {
|
|
559
|
+
if (signal.aborted)
|
|
560
|
+
onAbort();
|
|
561
|
+
else
|
|
562
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
563
|
+
}
|
|
564
|
+
const feed = (chunk) => {
|
|
565
|
+
try {
|
|
566
|
+
const wasTruncated = capture.truncated;
|
|
567
|
+
capture.push(chunk);
|
|
568
|
+
if (!options?.capture?.spill || chunk.length === 0)
|
|
569
|
+
return;
|
|
570
|
+
if (spillPath !== undefined || wasTruncated) {
|
|
571
|
+
startSpill(chunk);
|
|
572
|
+
}
|
|
573
|
+
else if (capture.truncated) {
|
|
574
|
+
for (const prefix of spillPrefix)
|
|
575
|
+
startSpill(prefix);
|
|
576
|
+
spillPrefix.length = 0;
|
|
577
|
+
startSpill(chunk);
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
spillPrefix.push(chunk);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
catch (error) {
|
|
584
|
+
failCallback(error);
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
child.stdout?.on("data", feed);
|
|
588
|
+
child.stderr?.on("data", feed);
|
|
589
|
+
void waitForChildProcess(child, () => spillError === undefined &&
|
|
590
|
+
spillStart !== undefined &&
|
|
591
|
+
(spillStream === undefined || spillBackpressured)).then(async ({ code, signal: exitSignal }) => {
|
|
592
|
+
await finishSpill();
|
|
593
|
+
try {
|
|
594
|
+
capture.finish();
|
|
595
|
+
capture.flush();
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
failCallback(error);
|
|
599
|
+
}
|
|
600
|
+
if (callbackError) {
|
|
601
|
+
settle(err(callbackError));
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (timedOut) {
|
|
605
|
+
settle(err(new ExecutionError("timeout", `timeout:${options?.timeout}`)));
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (signal?.aborted) {
|
|
609
|
+
settle(err(new ExecutionError("aborted", "aborted")));
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
if (spillError) {
|
|
613
|
+
settle(err(spillError));
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const output = capture.snapshot();
|
|
617
|
+
// A process killed by a signal (e.g. OOM killer) has no exit code; map it
|
|
618
|
+
// to the conventional 128 + signal number so callers do not mistake it
|
|
619
|
+
// for a successful exit.
|
|
620
|
+
const exitCode = code ?? (exitSignal ? 128 + (osConstants.signals[exitSignal] ?? 0) : 1);
|
|
621
|
+
settle(ok({
|
|
622
|
+
exitCode,
|
|
623
|
+
truncation: output.truncation,
|
|
624
|
+
...(output.spillPath === undefined ? {} : { spillPath: output.spillPath }),
|
|
625
|
+
...(output.lastLineBytes === undefined ? {} : { lastLineBytes: output.lastLineBytes }),
|
|
626
|
+
}));
|
|
627
|
+
}, (error) => settle(err(new ExecutionError("spawn_error", error.message, error))));
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
async openTextLineReader(path, context) {
|
|
631
|
+
const resolved = resolvePath(this.cwd, path);
|
|
632
|
+
const aborted = abortResult(context.abortSignal, resolved);
|
|
633
|
+
if (aborted)
|
|
634
|
+
return aborted;
|
|
635
|
+
try {
|
|
636
|
+
const file = await openFile(resolved, "r");
|
|
637
|
+
const afterOpenAbort = abortResult(context.abortSignal, resolved);
|
|
638
|
+
if (afterOpenAbort) {
|
|
639
|
+
await file.close().catch(() => undefined);
|
|
640
|
+
return afterOpenAbort;
|
|
641
|
+
}
|
|
642
|
+
return ok(new NodeTextLineReader(file, resolved));
|
|
643
|
+
}
|
|
644
|
+
catch (error) {
|
|
645
|
+
return err(toFileError(error, resolved));
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
async readTextFile(path, context) {
|
|
649
|
+
const resolved = resolvePath(this.cwd, path);
|
|
650
|
+
const signal = context.abortSignal;
|
|
651
|
+
const aborted = abortResult(signal, resolved);
|
|
652
|
+
if (aborted)
|
|
653
|
+
return aborted;
|
|
654
|
+
try {
|
|
655
|
+
return ok(await readFile(resolved, { encoding: "utf8", signal }));
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
return err(toFileError(error, resolved));
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
async readTextLines(path, options, context) {
|
|
662
|
+
if (options?.maxLines !== undefined && options.maxLines <= 0)
|
|
663
|
+
return ok([]);
|
|
664
|
+
const opened = await this.openTextLineReader(path, context);
|
|
665
|
+
if (!opened.ok)
|
|
666
|
+
return opened;
|
|
667
|
+
const lines = [];
|
|
668
|
+
try {
|
|
669
|
+
while (options?.maxLines === undefined || lines.length < options.maxLines) {
|
|
670
|
+
const line = await opened.value.readLine(context);
|
|
671
|
+
if (!line.ok)
|
|
672
|
+
return line;
|
|
673
|
+
if (line.value === undefined)
|
|
674
|
+
break;
|
|
675
|
+
lines.push(line.value.text);
|
|
676
|
+
}
|
|
677
|
+
return ok(lines);
|
|
678
|
+
}
|
|
679
|
+
finally {
|
|
680
|
+
await opened.value.close(context);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
async readBinaryFile(path, context) {
|
|
684
|
+
const resolved = resolvePath(this.cwd, path);
|
|
685
|
+
const signal = context.abortSignal;
|
|
686
|
+
const aborted = abortResult(signal, resolved);
|
|
687
|
+
if (aborted)
|
|
688
|
+
return aborted;
|
|
689
|
+
try {
|
|
690
|
+
return ok(await readFile(resolved, { signal }));
|
|
691
|
+
}
|
|
692
|
+
catch (error) {
|
|
693
|
+
return err(toFileError(error, resolved));
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async writeFile(path, content, context) {
|
|
697
|
+
const resolved = resolvePath(this.cwd, path);
|
|
698
|
+
const signal = context.abortSignal;
|
|
699
|
+
const aborted = abortResult(signal, resolved);
|
|
700
|
+
if (aborted)
|
|
701
|
+
return aborted;
|
|
702
|
+
try {
|
|
703
|
+
await mkdir(resolve(resolved, ".."), { recursive: true });
|
|
704
|
+
const afterMkdirAbort = abortResult(signal, resolved);
|
|
705
|
+
if (afterMkdirAbort)
|
|
706
|
+
return afterMkdirAbort;
|
|
707
|
+
await writeFile(resolved, content, { signal });
|
|
708
|
+
return ok(undefined);
|
|
709
|
+
}
|
|
710
|
+
catch (error) {
|
|
711
|
+
return err(toFileError(error, resolved));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
async appendFile(path, content, context) {
|
|
715
|
+
const resolved = resolvePath(this.cwd, path);
|
|
716
|
+
const signal = context.abortSignal;
|
|
717
|
+
const aborted = abortResult(signal, resolved);
|
|
718
|
+
if (aborted)
|
|
719
|
+
return aborted;
|
|
720
|
+
try {
|
|
721
|
+
await mkdir(resolve(resolved, ".."), { recursive: true });
|
|
722
|
+
const afterMkdirAbort = abortResult(signal, resolved);
|
|
723
|
+
if (afterMkdirAbort)
|
|
724
|
+
return afterMkdirAbort;
|
|
725
|
+
await appendFile(resolved, content);
|
|
726
|
+
const afterAppendAbort = abortResult(signal, resolved);
|
|
727
|
+
return afterAppendAbort ?? ok(undefined);
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
return err(toFileError(error, resolved));
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
async renameFile(sourcePath, destinationPath, context) {
|
|
734
|
+
const source = resolvePath(this.cwd, sourcePath);
|
|
735
|
+
const destination = resolvePath(this.cwd, destinationPath);
|
|
736
|
+
const aborted = abortResult(context.abortSignal, destination);
|
|
737
|
+
if (aborted)
|
|
738
|
+
return aborted;
|
|
739
|
+
try {
|
|
740
|
+
await rename(source, destination);
|
|
741
|
+
return ok(undefined);
|
|
742
|
+
}
|
|
743
|
+
catch (error) {
|
|
744
|
+
return err(toFileError(error, source));
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
async fileInfo(path, context) {
|
|
748
|
+
const resolved = resolvePath(this.cwd, path);
|
|
749
|
+
const aborted = abortResult(context.abortSignal, resolved);
|
|
750
|
+
if (aborted)
|
|
751
|
+
return aborted;
|
|
752
|
+
try {
|
|
753
|
+
return fileInfoFromStats(resolved, await lstat(resolved));
|
|
754
|
+
}
|
|
755
|
+
catch (error) {
|
|
756
|
+
return err(toFileError(error, resolved));
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
async listDir(path, context) {
|
|
760
|
+
const resolved = resolvePath(this.cwd, path);
|
|
761
|
+
const signal = context.abortSignal;
|
|
762
|
+
const aborted = abortResult(signal, resolved);
|
|
763
|
+
if (aborted)
|
|
764
|
+
return aborted;
|
|
765
|
+
try {
|
|
766
|
+
const entries = await readdir(resolved, { withFileTypes: true });
|
|
767
|
+
const infos = [];
|
|
768
|
+
for (const entry of entries) {
|
|
769
|
+
const loopAbort = abortResult(signal, resolved);
|
|
770
|
+
if (loopAbort)
|
|
771
|
+
return loopAbort;
|
|
772
|
+
const entryPath = resolve(resolved, entry.name);
|
|
773
|
+
try {
|
|
774
|
+
const info = fileInfoFromStats(entryPath, await lstat(entryPath));
|
|
775
|
+
if (info.ok)
|
|
776
|
+
infos.push(info.value);
|
|
777
|
+
}
|
|
778
|
+
catch (error) {
|
|
779
|
+
return err(toFileError(error, entryPath));
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
return ok(infos);
|
|
783
|
+
}
|
|
784
|
+
catch (error) {
|
|
785
|
+
return err(toFileError(error, resolved));
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
async canonicalPath(path, context) {
|
|
789
|
+
const resolved = resolvePath(this.cwd, path);
|
|
790
|
+
const aborted = abortResult(context.abortSignal, resolved);
|
|
791
|
+
if (aborted)
|
|
792
|
+
return aborted;
|
|
793
|
+
try {
|
|
794
|
+
return ok(await realpath(resolved));
|
|
795
|
+
}
|
|
796
|
+
catch (error) {
|
|
797
|
+
return err(toFileError(error, resolved));
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
async exists(path, context) {
|
|
801
|
+
const result = await this.fileInfo(path, context);
|
|
802
|
+
if (result.ok)
|
|
803
|
+
return ok(true);
|
|
804
|
+
if (result.error.code === "not_found")
|
|
805
|
+
return ok(false);
|
|
806
|
+
return err(result.error);
|
|
807
|
+
}
|
|
808
|
+
async createDir(path, options, context) {
|
|
809
|
+
const resolved = resolvePath(this.cwd, path);
|
|
810
|
+
const aborted = abortResult(context.abortSignal, resolved);
|
|
811
|
+
if (aborted)
|
|
812
|
+
return aborted;
|
|
813
|
+
try {
|
|
814
|
+
await mkdir(resolved, { recursive: options?.recursive ?? true });
|
|
815
|
+
return ok(undefined);
|
|
816
|
+
}
|
|
817
|
+
catch (error) {
|
|
818
|
+
return err(toFileError(error, resolved));
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
async remove(path, options, context) {
|
|
822
|
+
const resolved = resolvePath(this.cwd, path);
|
|
823
|
+
const aborted = abortResult(context.abortSignal, resolved);
|
|
824
|
+
if (aborted)
|
|
825
|
+
return aborted;
|
|
826
|
+
try {
|
|
827
|
+
await rm(resolved, { recursive: options?.recursive ?? false, force: options?.force ?? false });
|
|
828
|
+
return ok(undefined);
|
|
829
|
+
}
|
|
830
|
+
catch (error) {
|
|
831
|
+
return err(toFileError(error, resolved));
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
async createTempDir(prefix, context) {
|
|
835
|
+
const aborted = abortResult(context.abortSignal);
|
|
836
|
+
if (aborted)
|
|
837
|
+
return aborted;
|
|
838
|
+
try {
|
|
839
|
+
prefix ??= "tmp-";
|
|
840
|
+
return ok(await mkdtemp(join(tmpdir(), prefix)));
|
|
841
|
+
}
|
|
842
|
+
catch (error) {
|
|
843
|
+
return err(toFileError(error));
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
async createTempFile(options, context) {
|
|
847
|
+
const dir = await this.createTempDir("tmp-", context);
|
|
848
|
+
if (!dir.ok)
|
|
849
|
+
return dir;
|
|
850
|
+
const filePath = join(dir.value, `${options?.prefix ?? ""}${randomUUID()}${options?.suffix ?? ""}`);
|
|
851
|
+
try {
|
|
852
|
+
await writeFile(filePath, "");
|
|
853
|
+
return ok(filePath);
|
|
854
|
+
}
|
|
855
|
+
catch (error) {
|
|
856
|
+
return err(toFileError(error, filePath));
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
async cleanup(_context) {
|
|
860
|
+
for (const pid of this.activeChildPids)
|
|
861
|
+
killProcessTree(pid);
|
|
862
|
+
this.activeChildPids.clear();
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
//# sourceMappingURL=nodejs.js.map
|