reasonix 0.25.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +527 -102
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +479 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -47,8 +47,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
|
|
|
47
47
|
}
|
|
48
48
|
function sleep(ms, signal) {
|
|
49
49
|
if (ms <= 0) return Promise.resolve();
|
|
50
|
-
return new Promise((
|
|
51
|
-
const timer = setTimeout(
|
|
50
|
+
return new Promise((resolve10, reject) => {
|
|
51
|
+
const timer = setTimeout(resolve10, ms);
|
|
52
52
|
if (signal) {
|
|
53
53
|
const onAbort = () => {
|
|
54
54
|
clearTimeout(timer);
|
|
@@ -523,7 +523,7 @@ function matchesTool(hook, toolName) {
|
|
|
523
523
|
}
|
|
524
524
|
var HOOK_OUTPUT_CAP_BYTES = 256 * 1024;
|
|
525
525
|
function defaultSpawner(input) {
|
|
526
|
-
return new Promise((
|
|
526
|
+
return new Promise((resolve10) => {
|
|
527
527
|
const child = spawn(input.command, {
|
|
528
528
|
cwd: input.cwd,
|
|
529
529
|
shell: true,
|
|
@@ -568,7 +568,7 @@ function defaultSpawner(input) {
|
|
|
568
568
|
child.stderr.on("data", (chunk) => onChunk("stderr", chunk));
|
|
569
569
|
child.once("error", (err) => {
|
|
570
570
|
clearTimeout(timer);
|
|
571
|
-
|
|
571
|
+
resolve10({
|
|
572
572
|
exitCode: null,
|
|
573
573
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
574
574
|
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
@@ -579,7 +579,7 @@ function defaultSpawner(input) {
|
|
|
579
579
|
});
|
|
580
580
|
child.once("close", (code) => {
|
|
581
581
|
clearTimeout(timer);
|
|
582
|
-
|
|
582
|
+
resolve10({
|
|
583
583
|
exitCode: code,
|
|
584
584
|
stdout: Buffer.concat(stdoutChunks).toString("utf8").trim(),
|
|
585
585
|
stderr: Buffer.concat(stderrChunks).toString("utf8").trim(),
|
|
@@ -2310,8 +2310,8 @@ var CacheFirstLoop = class {
|
|
|
2310
2310
|
}
|
|
2311
2311
|
);
|
|
2312
2312
|
for (let k = 0; k < budget; k++) {
|
|
2313
|
-
const sample = queue.shift() ?? await new Promise((
|
|
2314
|
-
waiter =
|
|
2313
|
+
const sample = queue.shift() ?? await new Promise((resolve10) => {
|
|
2314
|
+
waiter = resolve10;
|
|
2315
2315
|
});
|
|
2316
2316
|
yield {
|
|
2317
2317
|
turn: this._turn,
|
|
@@ -5367,9 +5367,9 @@ function forkRegistryExcluding(parent, exclude) {
|
|
|
5367
5367
|
}
|
|
5368
5368
|
|
|
5369
5369
|
// src/tools/shell.ts
|
|
5370
|
-
import { spawn as
|
|
5370
|
+
import { spawn as spawn4, spawnSync } from "child_process";
|
|
5371
5371
|
import { existsSync as existsSync8, statSync as statSync4 } from "fs";
|
|
5372
|
-
import * as
|
|
5372
|
+
import * as pathMod4 from "path";
|
|
5373
5373
|
|
|
5374
5374
|
// src/tools/jobs.ts
|
|
5375
5375
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -5659,6 +5659,417 @@ function snapshot(job) {
|
|
|
5659
5659
|
};
|
|
5660
5660
|
}
|
|
5661
5661
|
|
|
5662
|
+
// src/tools/shell-chain.ts
|
|
5663
|
+
import { spawn as spawn3 } from "child_process";
|
|
5664
|
+
import { closeSync, openSync } from "fs";
|
|
5665
|
+
import * as pathMod3 from "path";
|
|
5666
|
+
var UnsupportedSyntaxError = class extends Error {
|
|
5667
|
+
constructor(detail) {
|
|
5668
|
+
super(`run_command: ${detail}`);
|
|
5669
|
+
this.name = "UnsupportedSyntaxError";
|
|
5670
|
+
}
|
|
5671
|
+
};
|
|
5672
|
+
function splitOnChainOps(cmd) {
|
|
5673
|
+
const segs = [];
|
|
5674
|
+
const ops = [];
|
|
5675
|
+
let segStart = 0;
|
|
5676
|
+
let i = 0;
|
|
5677
|
+
let quote = null;
|
|
5678
|
+
let atTokenStart = true;
|
|
5679
|
+
while (i < cmd.length) {
|
|
5680
|
+
const ch = cmd[i];
|
|
5681
|
+
if (quote) {
|
|
5682
|
+
if (ch === quote) quote = null;
|
|
5683
|
+
else if (ch === "\\" && quote === '"' && i + 1 < cmd.length) i++;
|
|
5684
|
+
i++;
|
|
5685
|
+
atTokenStart = false;
|
|
5686
|
+
continue;
|
|
5687
|
+
}
|
|
5688
|
+
if (ch === '"' || ch === "'") {
|
|
5689
|
+
quote = ch;
|
|
5690
|
+
i++;
|
|
5691
|
+
atTokenStart = false;
|
|
5692
|
+
continue;
|
|
5693
|
+
}
|
|
5694
|
+
if (ch === " " || ch === " ") {
|
|
5695
|
+
i++;
|
|
5696
|
+
atTokenStart = true;
|
|
5697
|
+
continue;
|
|
5698
|
+
}
|
|
5699
|
+
if (atTokenStart) {
|
|
5700
|
+
let op = null;
|
|
5701
|
+
let opLen = 0;
|
|
5702
|
+
const next = cmd[i + 1];
|
|
5703
|
+
if (ch === "|" && next === "|") {
|
|
5704
|
+
op = "||";
|
|
5705
|
+
opLen = 2;
|
|
5706
|
+
} else if (ch === "&" && next === "&") {
|
|
5707
|
+
op = "&&";
|
|
5708
|
+
opLen = 2;
|
|
5709
|
+
} else if (ch === "|") {
|
|
5710
|
+
op = "|";
|
|
5711
|
+
opLen = 1;
|
|
5712
|
+
} else if (ch === ";") {
|
|
5713
|
+
op = ";";
|
|
5714
|
+
opLen = 1;
|
|
5715
|
+
}
|
|
5716
|
+
if (op !== null) {
|
|
5717
|
+
segs.push(cmd.slice(segStart, i));
|
|
5718
|
+
ops.push(op);
|
|
5719
|
+
i += opLen;
|
|
5720
|
+
segStart = i;
|
|
5721
|
+
atTokenStart = true;
|
|
5722
|
+
continue;
|
|
5723
|
+
}
|
|
5724
|
+
}
|
|
5725
|
+
i++;
|
|
5726
|
+
atTokenStart = false;
|
|
5727
|
+
}
|
|
5728
|
+
segs.push(cmd.slice(segStart));
|
|
5729
|
+
return { segs, ops };
|
|
5730
|
+
}
|
|
5731
|
+
function parseSegment(segStr) {
|
|
5732
|
+
const argv = [];
|
|
5733
|
+
const redirects = [];
|
|
5734
|
+
let cur = "";
|
|
5735
|
+
let curHasContent = false;
|
|
5736
|
+
let pending = null;
|
|
5737
|
+
let quote = null;
|
|
5738
|
+
const flush = () => {
|
|
5739
|
+
if (!curHasContent && cur.length === 0) return;
|
|
5740
|
+
if (pending) {
|
|
5741
|
+
redirects.push({ kind: pending, target: cur });
|
|
5742
|
+
pending = null;
|
|
5743
|
+
} else {
|
|
5744
|
+
argv.push(cur);
|
|
5745
|
+
}
|
|
5746
|
+
cur = "";
|
|
5747
|
+
curHasContent = false;
|
|
5748
|
+
};
|
|
5749
|
+
let i = 0;
|
|
5750
|
+
while (i < segStr.length) {
|
|
5751
|
+
const ch = segStr[i];
|
|
5752
|
+
if (quote) {
|
|
5753
|
+
if (ch === quote) {
|
|
5754
|
+
quote = null;
|
|
5755
|
+
} else if (ch === "\\" && quote === '"' && i + 1 < segStr.length) {
|
|
5756
|
+
cur += segStr[++i] ?? "";
|
|
5757
|
+
curHasContent = true;
|
|
5758
|
+
} else {
|
|
5759
|
+
cur += ch;
|
|
5760
|
+
curHasContent = true;
|
|
5761
|
+
}
|
|
5762
|
+
i++;
|
|
5763
|
+
continue;
|
|
5764
|
+
}
|
|
5765
|
+
if (ch === '"' || ch === "'") {
|
|
5766
|
+
quote = ch;
|
|
5767
|
+
curHasContent = true;
|
|
5768
|
+
i++;
|
|
5769
|
+
continue;
|
|
5770
|
+
}
|
|
5771
|
+
if (ch === " " || ch === " ") {
|
|
5772
|
+
flush();
|
|
5773
|
+
i++;
|
|
5774
|
+
continue;
|
|
5775
|
+
}
|
|
5776
|
+
if (cur.length === 0 && !curHasContent) {
|
|
5777
|
+
const remaining = segStr.slice(i);
|
|
5778
|
+
let matched = null;
|
|
5779
|
+
if (remaining.startsWith("2>&1")) matched = { op: "2>&1", len: 4 };
|
|
5780
|
+
else if (remaining.startsWith("&>")) matched = { op: "&>", len: 2 };
|
|
5781
|
+
else if (remaining.startsWith("2>>")) matched = { op: "2>>", len: 3 };
|
|
5782
|
+
else if (remaining.startsWith("2>")) matched = { op: "2>", len: 2 };
|
|
5783
|
+
else if (remaining.startsWith(">>")) matched = { op: ">>", len: 2 };
|
|
5784
|
+
else if (remaining.startsWith(">")) matched = { op: ">", len: 1 };
|
|
5785
|
+
else if (remaining.startsWith("<<")) {
|
|
5786
|
+
throw new UnsupportedSyntaxError(
|
|
5787
|
+
`shell operator "<<" is not supported \u2014 heredoc / here-string is not implemented; pass input via a "<" file or the binary's --input flag`
|
|
5788
|
+
);
|
|
5789
|
+
} else if (remaining.startsWith("<")) matched = { op: "<", len: 1 };
|
|
5790
|
+
if (matched) {
|
|
5791
|
+
if (pending !== null) {
|
|
5792
|
+
throw new UnsupportedSyntaxError(
|
|
5793
|
+
`redirect "${pending}" is missing a target file before "${matched.op}"`
|
|
5794
|
+
);
|
|
5795
|
+
}
|
|
5796
|
+
if (matched.op === "2>&1") {
|
|
5797
|
+
redirects.push({ kind: "2>&1", target: "" });
|
|
5798
|
+
} else {
|
|
5799
|
+
pending = matched.op;
|
|
5800
|
+
}
|
|
5801
|
+
i += matched.len;
|
|
5802
|
+
continue;
|
|
5803
|
+
}
|
|
5804
|
+
if (ch === "&") {
|
|
5805
|
+
throw new UnsupportedSyntaxError(
|
|
5806
|
+
'shell operator "&" is not supported \u2014 background runs need run_background, not run_command. Wrap a literal `&` arg in quotes.'
|
|
5807
|
+
);
|
|
5808
|
+
}
|
|
5809
|
+
}
|
|
5810
|
+
cur += ch;
|
|
5811
|
+
curHasContent = true;
|
|
5812
|
+
i++;
|
|
5813
|
+
}
|
|
5814
|
+
if (quote) throw new Error(`unclosed ${quote} in command`);
|
|
5815
|
+
flush();
|
|
5816
|
+
if (pending) throw new UnsupportedSyntaxError(`redirect "${pending}" is missing a target file`);
|
|
5817
|
+
if (argv.length === 0 && redirects.length > 0) {
|
|
5818
|
+
throw new UnsupportedSyntaxError(
|
|
5819
|
+
"redirect without a command \u2014 segment must have at least one program argument"
|
|
5820
|
+
);
|
|
5821
|
+
}
|
|
5822
|
+
validateRedirectFds(redirects);
|
|
5823
|
+
return { argv, redirects };
|
|
5824
|
+
}
|
|
5825
|
+
function validateRedirectFds(redirects) {
|
|
5826
|
+
let stdin = 0;
|
|
5827
|
+
let stdout = 0;
|
|
5828
|
+
let stderr = 0;
|
|
5829
|
+
for (const r of redirects) {
|
|
5830
|
+
if (r.kind === "<") stdin++;
|
|
5831
|
+
else if (r.kind === ">" || r.kind === ">>") stdout++;
|
|
5832
|
+
else if (r.kind === "2>" || r.kind === "2>>" || r.kind === "2>&1") stderr++;
|
|
5833
|
+
else if (r.kind === "&>") {
|
|
5834
|
+
stdout++;
|
|
5835
|
+
stderr++;
|
|
5836
|
+
}
|
|
5837
|
+
}
|
|
5838
|
+
if (stdin > 1) throw new UnsupportedSyntaxError("multiple `<` stdin redirects in one segment");
|
|
5839
|
+
if (stdout > 1)
|
|
5840
|
+
throw new UnsupportedSyntaxError(
|
|
5841
|
+
"multiple stdout redirects in one segment (`>` / `>>` / `&>` conflict)"
|
|
5842
|
+
);
|
|
5843
|
+
if (stderr > 1)
|
|
5844
|
+
throw new UnsupportedSyntaxError(
|
|
5845
|
+
"multiple stderr redirects in one segment (`2>` / `2>>` / `&>` / `2>&1` conflict)"
|
|
5846
|
+
);
|
|
5847
|
+
}
|
|
5848
|
+
function parseCommandChain(cmd) {
|
|
5849
|
+
const { segs, ops } = splitOnChainOps(cmd);
|
|
5850
|
+
const segments = [];
|
|
5851
|
+
for (let i = 0; i < segs.length; i++) {
|
|
5852
|
+
const trimmed = segs[i].trim();
|
|
5853
|
+
if (trimmed.length === 0) {
|
|
5854
|
+
const op = i === 0 ? ops[0] : ops[i - 1];
|
|
5855
|
+
throw new UnsupportedSyntaxError(
|
|
5856
|
+
i === 0 ? `empty segment before "${op}"` : i === segs.length - 1 ? `chain ends with "${op}"` : `empty segment between "${ops[i - 1]}" and "${ops[i]}"`
|
|
5857
|
+
);
|
|
5858
|
+
}
|
|
5859
|
+
segments.push(parseSegment(trimmed));
|
|
5860
|
+
}
|
|
5861
|
+
if (ops.length === 0 && segments[0].redirects.length === 0) return null;
|
|
5862
|
+
return { segments, ops };
|
|
5863
|
+
}
|
|
5864
|
+
function chainAllowed(chain, isAllowed2) {
|
|
5865
|
+
for (const seg of chain.segments) {
|
|
5866
|
+
if (!isAllowed2(seg.argv.join(" "))) return false;
|
|
5867
|
+
}
|
|
5868
|
+
return true;
|
|
5869
|
+
}
|
|
5870
|
+
function groupChain(chain) {
|
|
5871
|
+
const groups = [{ segments: [chain.segments[0]], opBefore: null }];
|
|
5872
|
+
for (let i = 0; i < chain.ops.length; i++) {
|
|
5873
|
+
const op = chain.ops[i];
|
|
5874
|
+
const next = chain.segments[i + 1];
|
|
5875
|
+
if (op === "|") {
|
|
5876
|
+
groups[groups.length - 1].segments.push(next);
|
|
5877
|
+
} else {
|
|
5878
|
+
groups.push({ segments: [next], opBefore: op });
|
|
5879
|
+
}
|
|
5880
|
+
}
|
|
5881
|
+
return groups;
|
|
5882
|
+
}
|
|
5883
|
+
async function runChain(chain, opts) {
|
|
5884
|
+
const groups = groupChain(chain);
|
|
5885
|
+
const buf = new OutputBuffer(opts.maxOutputChars * 2 * 4);
|
|
5886
|
+
const deadline = Date.now() + opts.timeoutSec * 1e3;
|
|
5887
|
+
let lastExit = 0;
|
|
5888
|
+
let timedOut = false;
|
|
5889
|
+
for (const group of groups) {
|
|
5890
|
+
if (group.opBefore === "&&" && lastExit !== 0) continue;
|
|
5891
|
+
if (group.opBefore === "||" && lastExit === 0) continue;
|
|
5892
|
+
const remainingMs = deadline - Date.now();
|
|
5893
|
+
if (remainingMs <= 0) {
|
|
5894
|
+
timedOut = true;
|
|
5895
|
+
break;
|
|
5896
|
+
}
|
|
5897
|
+
const result = await runPipeGroup(group.segments, {
|
|
5898
|
+
cwd: opts.cwd,
|
|
5899
|
+
timeoutMs: remainingMs,
|
|
5900
|
+
buf,
|
|
5901
|
+
signal: opts.signal
|
|
5902
|
+
});
|
|
5903
|
+
lastExit = result.exitCode;
|
|
5904
|
+
if (result.timedOut) {
|
|
5905
|
+
timedOut = true;
|
|
5906
|
+
break;
|
|
5907
|
+
}
|
|
5908
|
+
if (opts.signal?.aborted) break;
|
|
5909
|
+
}
|
|
5910
|
+
const output = buf.toString();
|
|
5911
|
+
const truncated = output.length > opts.maxOutputChars ? `${output.slice(0, opts.maxOutputChars)}
|
|
5912
|
+
|
|
5913
|
+
[\u2026 truncated ${output.length - opts.maxOutputChars} chars \u2026]` : output;
|
|
5914
|
+
return { exitCode: lastExit, output: truncated, timedOut };
|
|
5915
|
+
}
|
|
5916
|
+
function openRedirects(redirects, cwd) {
|
|
5917
|
+
let stdinFd = null;
|
|
5918
|
+
let stdoutFd = null;
|
|
5919
|
+
let stderrFd = null;
|
|
5920
|
+
let mergeStderrToStdout = false;
|
|
5921
|
+
let bothFd = null;
|
|
5922
|
+
const toClose = [];
|
|
5923
|
+
const open = (target, flags) => {
|
|
5924
|
+
const resolved = pathMod3.resolve(cwd, target);
|
|
5925
|
+
const fd = openSync(resolved, flags);
|
|
5926
|
+
toClose.push(fd);
|
|
5927
|
+
return fd;
|
|
5928
|
+
};
|
|
5929
|
+
for (const r of redirects) {
|
|
5930
|
+
if (r.kind === "<") stdinFd = open(r.target, "r");
|
|
5931
|
+
else if (r.kind === ">") stdoutFd = open(r.target, "w");
|
|
5932
|
+
else if (r.kind === ">>") stdoutFd = open(r.target, "a");
|
|
5933
|
+
else if (r.kind === "2>") stderrFd = open(r.target, "w");
|
|
5934
|
+
else if (r.kind === "2>>") stderrFd = open(r.target, "a");
|
|
5935
|
+
else if (r.kind === "&>") {
|
|
5936
|
+
bothFd = open(r.target, "w");
|
|
5937
|
+
stdoutFd = bothFd;
|
|
5938
|
+
stderrFd = bothFd;
|
|
5939
|
+
} else if (r.kind === "2>&1") {
|
|
5940
|
+
mergeStderrToStdout = true;
|
|
5941
|
+
}
|
|
5942
|
+
}
|
|
5943
|
+
return { stdinFd, stdoutFd, stderrFd, mergeStderrToStdout, toClose };
|
|
5944
|
+
}
|
|
5945
|
+
async function runPipeGroup(segments, opts) {
|
|
5946
|
+
const env = { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" };
|
|
5947
|
+
const children = [];
|
|
5948
|
+
const allFds = [];
|
|
5949
|
+
let timedOut = false;
|
|
5950
|
+
const killAll = () => {
|
|
5951
|
+
for (const c of children) killProcessTree2(c);
|
|
5952
|
+
};
|
|
5953
|
+
const killTimer = setTimeout(() => {
|
|
5954
|
+
timedOut = true;
|
|
5955
|
+
killAll();
|
|
5956
|
+
}, opts.timeoutMs);
|
|
5957
|
+
const onAbort = () => killAll();
|
|
5958
|
+
if (opts.signal?.aborted) {
|
|
5959
|
+
onAbort();
|
|
5960
|
+
} else {
|
|
5961
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
5962
|
+
}
|
|
5963
|
+
try {
|
|
5964
|
+
for (let i = 0; i < segments.length; i++) {
|
|
5965
|
+
const isFirst = i === 0;
|
|
5966
|
+
const isLast = i === segments.length - 1;
|
|
5967
|
+
const seg = segments[i];
|
|
5968
|
+
const io = openRedirects(seg.redirects, opts.cwd);
|
|
5969
|
+
allFds.push(...io.toClose);
|
|
5970
|
+
const { bin, args, spawnOverrides } = prepareSpawn(seg.argv);
|
|
5971
|
+
const stdoutSpec = io.stdoutFd !== null ? io.stdoutFd : "pipe";
|
|
5972
|
+
const stderrSpec = io.stderrFd !== null ? io.stderrFd : io.mergeStderrToStdout ? stdoutSpec : "pipe";
|
|
5973
|
+
const stdinSpec = io.stdinFd !== null ? io.stdinFd : isFirst ? "ignore" : "pipe";
|
|
5974
|
+
const spawnOpts = {
|
|
5975
|
+
cwd: opts.cwd,
|
|
5976
|
+
shell: false,
|
|
5977
|
+
windowsHide: true,
|
|
5978
|
+
env,
|
|
5979
|
+
stdio: [stdinSpec, stdoutSpec, stderrSpec],
|
|
5980
|
+
...spawnOverrides
|
|
5981
|
+
};
|
|
5982
|
+
let child;
|
|
5983
|
+
try {
|
|
5984
|
+
child = spawn3(bin, args, spawnOpts);
|
|
5985
|
+
} catch (err) {
|
|
5986
|
+
for (const fd of allFds) tryClose(fd);
|
|
5987
|
+
killAll();
|
|
5988
|
+
clearTimeout(killTimer);
|
|
5989
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
5990
|
+
throw err;
|
|
5991
|
+
}
|
|
5992
|
+
children.push(child);
|
|
5993
|
+
if (!isFirst && io.stdinFd === null) {
|
|
5994
|
+
const prev = children[i - 1];
|
|
5995
|
+
prev.stdout?.on("error", () => {
|
|
5996
|
+
});
|
|
5997
|
+
child.stdin?.on("error", () => {
|
|
5998
|
+
});
|
|
5999
|
+
const prevMergesStderr = segments[i - 1].redirects.some((r) => r.kind === "2>&1") && !!prev.stderr;
|
|
6000
|
+
if (prevMergesStderr && prev.stderr) {
|
|
6001
|
+
prev.stderr.on("error", () => {
|
|
6002
|
+
});
|
|
6003
|
+
let openSources = 2;
|
|
6004
|
+
const closeIfDone = () => {
|
|
6005
|
+
if (--openSources === 0) child.stdin?.end();
|
|
6006
|
+
};
|
|
6007
|
+
prev.stdout?.pipe(child.stdin, { end: false });
|
|
6008
|
+
prev.stderr.pipe(child.stdin, { end: false });
|
|
6009
|
+
prev.stdout?.once("end", closeIfDone);
|
|
6010
|
+
prev.stderr.once("end", closeIfDone);
|
|
6011
|
+
} else {
|
|
6012
|
+
prev.stdout?.pipe(child.stdin);
|
|
6013
|
+
}
|
|
6014
|
+
}
|
|
6015
|
+
if (child.stderr && io.stderrFd === null && !(io.mergeStderrToStdout && !isLast)) {
|
|
6016
|
+
child.stderr.on("data", (chunk) => opts.buf.push(toBuf(chunk)));
|
|
6017
|
+
}
|
|
6018
|
+
if (isLast && child.stdout && io.stdoutFd === null) {
|
|
6019
|
+
child.stdout.on("data", (chunk) => opts.buf.push(toBuf(chunk)));
|
|
6020
|
+
if (io.mergeStderrToStdout && child.stderr && io.stderrFd === null) {
|
|
6021
|
+
child.stderr.removeAllListeners("data");
|
|
6022
|
+
child.stderr.on("data", (chunk) => opts.buf.push(toBuf(chunk)));
|
|
6023
|
+
}
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
const exits = await Promise.all(
|
|
6027
|
+
children.map(
|
|
6028
|
+
(c) => new Promise((resolve10) => {
|
|
6029
|
+
c.once("error", () => resolve10(null));
|
|
6030
|
+
c.once("close", (code) => resolve10(code));
|
|
6031
|
+
})
|
|
6032
|
+
)
|
|
6033
|
+
);
|
|
6034
|
+
return { exitCode: exits[exits.length - 1] ?? null, timedOut };
|
|
6035
|
+
} finally {
|
|
6036
|
+
for (const fd of allFds) tryClose(fd);
|
|
6037
|
+
clearTimeout(killTimer);
|
|
6038
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
6039
|
+
}
|
|
6040
|
+
}
|
|
6041
|
+
function tryClose(fd) {
|
|
6042
|
+
try {
|
|
6043
|
+
closeSync(fd);
|
|
6044
|
+
} catch {
|
|
6045
|
+
}
|
|
6046
|
+
}
|
|
6047
|
+
function toBuf(chunk) {
|
|
6048
|
+
return typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
6049
|
+
}
|
|
6050
|
+
var OutputBuffer = class {
|
|
6051
|
+
constructor(cap) {
|
|
6052
|
+
this.cap = cap;
|
|
6053
|
+
}
|
|
6054
|
+
cap;
|
|
6055
|
+
chunks = [];
|
|
6056
|
+
bytes = 0;
|
|
6057
|
+
push(b) {
|
|
6058
|
+
if (this.bytes >= this.cap) return;
|
|
6059
|
+
const remaining = this.cap - this.bytes;
|
|
6060
|
+
if (b.length > remaining) {
|
|
6061
|
+
this.chunks.push(b.subarray(0, remaining));
|
|
6062
|
+
this.bytes = this.cap;
|
|
6063
|
+
} else {
|
|
6064
|
+
this.chunks.push(b);
|
|
6065
|
+
this.bytes += b.length;
|
|
6066
|
+
}
|
|
6067
|
+
}
|
|
6068
|
+
toString() {
|
|
6069
|
+
return smartDecodeOutput(Buffer.concat(this.chunks));
|
|
6070
|
+
}
|
|
6071
|
+
};
|
|
6072
|
+
|
|
5662
6073
|
// src/tools/shell.ts
|
|
5663
6074
|
function killProcessTree2(child) {
|
|
5664
6075
|
if (!child.pid || child.killed) return;
|
|
@@ -5830,17 +6241,31 @@ function isAllowed(cmd, extra = []) {
|
|
|
5830
6241
|
}
|
|
5831
6242
|
return false;
|
|
5832
6243
|
}
|
|
6244
|
+
function isCommandAllowed(cmd, extra = []) {
|
|
6245
|
+
let chain;
|
|
6246
|
+
try {
|
|
6247
|
+
chain = parseCommandChain(cmd);
|
|
6248
|
+
} catch {
|
|
6249
|
+
return false;
|
|
6250
|
+
}
|
|
6251
|
+
if (chain === null) return isAllowed(cmd, extra);
|
|
6252
|
+
return chainAllowed(chain, (seg) => isAllowed(seg, extra));
|
|
6253
|
+
}
|
|
5833
6254
|
async function runCommand(cmd, opts) {
|
|
6255
|
+
const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
|
|
6256
|
+
const maxChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
5834
6257
|
const argv = tokenizeCommand(cmd);
|
|
5835
6258
|
if (argv.length === 0) throw new Error("run_command: empty command");
|
|
5836
|
-
const
|
|
5837
|
-
if (
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
6259
|
+
const chain = parseCommandChain(cmd);
|
|
6260
|
+
if (chain !== null) {
|
|
6261
|
+
return await runChain(chain, {
|
|
6262
|
+
cwd: opts.cwd,
|
|
6263
|
+
timeoutSec,
|
|
6264
|
+
maxOutputChars: maxChars,
|
|
6265
|
+
signal: opts.signal
|
|
6266
|
+
});
|
|
5841
6267
|
}
|
|
5842
|
-
const timeoutMs =
|
|
5843
|
-
const maxChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
6268
|
+
const timeoutMs = timeoutSec * 1e3;
|
|
5844
6269
|
const spawnOpts = {
|
|
5845
6270
|
cwd: opts.cwd,
|
|
5846
6271
|
shell: false,
|
|
@@ -5858,10 +6283,10 @@ async function runCommand(cmd, opts) {
|
|
|
5858
6283
|
};
|
|
5859
6284
|
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
5860
6285
|
const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
|
|
5861
|
-
return await new Promise((
|
|
6286
|
+
return await new Promise((resolve10, reject) => {
|
|
5862
6287
|
let child;
|
|
5863
6288
|
try {
|
|
5864
|
-
child =
|
|
6289
|
+
child = spawn4(bin, args, effectiveSpawnOpts);
|
|
5865
6290
|
} catch (err) {
|
|
5866
6291
|
reject(err);
|
|
5867
6292
|
return;
|
|
@@ -5912,7 +6337,7 @@ async function runCommand(cmd, opts) {
|
|
|
5912
6337
|
const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
|
|
5913
6338
|
|
|
5914
6339
|
[\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
|
|
5915
|
-
|
|
6340
|
+
resolve10({ exitCode: code, output, timedOut });
|
|
5916
6341
|
});
|
|
5917
6342
|
});
|
|
5918
6343
|
}
|
|
@@ -5934,16 +6359,16 @@ function resolveExecutable(cmd, opts = {}) {
|
|
|
5934
6359
|
const platform = opts.platform ?? process.platform;
|
|
5935
6360
|
if (platform !== "win32") return cmd;
|
|
5936
6361
|
if (!cmd) return cmd;
|
|
5937
|
-
if (cmd.includes("/") || cmd.includes("\\") ||
|
|
5938
|
-
if (
|
|
6362
|
+
if (cmd.includes("/") || cmd.includes("\\") || pathMod4.isAbsolute(cmd)) return cmd;
|
|
6363
|
+
if (pathMod4.extname(cmd)) return cmd;
|
|
5939
6364
|
const env = opts.env ?? process.env;
|
|
5940
6365
|
const pathExt = (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
|
|
5941
|
-
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" :
|
|
6366
|
+
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" : pathMod4.delimiter);
|
|
5942
6367
|
const pathDirs = (env.PATH ?? "").split(delimiter2).filter(Boolean);
|
|
5943
6368
|
const isFile = opts.isFile ?? defaultIsFile;
|
|
5944
6369
|
for (const dir of pathDirs) {
|
|
5945
6370
|
for (const ext of pathExt) {
|
|
5946
|
-
const full =
|
|
6371
|
+
const full = pathMod4.win32.join(dir, cmd + ext);
|
|
5947
6372
|
if (isFile(full)) return full;
|
|
5948
6373
|
}
|
|
5949
6374
|
}
|
|
@@ -6013,8 +6438,8 @@ function withUtf8Codepage(cmdline) {
|
|
|
6013
6438
|
function isBareWindowsName(s) {
|
|
6014
6439
|
if (!s) return false;
|
|
6015
6440
|
if (s.includes("/") || s.includes("\\")) return false;
|
|
6016
|
-
if (
|
|
6017
|
-
if (
|
|
6441
|
+
if (pathMod4.isAbsolute(s)) return false;
|
|
6442
|
+
if (pathMod4.extname(s)) return false;
|
|
6018
6443
|
return true;
|
|
6019
6444
|
}
|
|
6020
6445
|
function quoteForCmdExe(arg) {
|
|
@@ -6033,7 +6458,7 @@ var NeedsConfirmationError = class extends Error {
|
|
|
6033
6458
|
}
|
|
6034
6459
|
};
|
|
6035
6460
|
function registerShellTools(registry, opts) {
|
|
6036
|
-
const rootDir =
|
|
6461
|
+
const rootDir = pathMod4.resolve(opts.rootDir);
|
|
6037
6462
|
const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
|
|
6038
6463
|
const maxOutputChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
6039
6464
|
const jobs = opts.jobs ?? new JobRegistry();
|
|
@@ -6044,7 +6469,7 @@ function registerShellTools(registry, opts) {
|
|
|
6044
6469
|
const isAllowAll = typeof opts.allowAll === "function" ? opts.allowAll : () => opts.allowAll === true;
|
|
6045
6470
|
registry.register({
|
|
6046
6471
|
name: "run_command",
|
|
6047
|
-
description: "Run a shell command in the project root and return its combined stdout+stderr.\n\nConstraints (read these before the first call):\n\u2022
|
|
6472
|
+
description: "Run a shell command in the project root and return its combined stdout+stderr.\n\nConstraints (read these before the first call):\n\u2022 Chain operators `|`, `||`, `&&`, `;` ARE supported \u2014 parsed natively, no shell invoked, so semantics are identical on Windows / macOS / Linux. Each chain segment is allowlist-checked individually: `git status | grep main` runs if both halves are allowed.\n\u2022 File redirects ARE supported: `>` truncate, `>>` append, `<` stdin from file, `2>` / `2>>` stderr to file, `2>&1` merge stderr\u2192stdout, `&>` both to file. Targets resolve relative to the project root. At most one redirect per fd per segment.\n\u2022 Background `&`, heredoc `<<`, command substitution `$(\u2026)`, subshells `(\u2026)`, and process substitution `<(\u2026)` are NOT supported. Wrap a literal `&` arg in quotes; for input use a `<` file or the binary's own --input flag.\n\u2022 Env-var expansion `$VAR` is NOT performed \u2014 `$VAR` is passed as a literal string. Use the binary's own --env flag or substitute the value yourself.\n\u2022 `cd` DOES NOT PERSIST between calls \u2014 each call spawns a fresh process rooted at the project. If a tool needs a subdirectory, pass it via the tool's own flag (`npm --prefix`, `cargo -C`, `git -C`, `pytest tests/\u2026`), NOT via a preceding `cd`.\n\u2022 Glob patterns (`*.ts`) are passed through as literal arguments \u2014 no shell expansion. Use `grep -r`, `rg`, `find -name`, etc.\n\u2022 Avoid commands with unbounded output (`netstat -ano`, `find /`, etc.) \u2014 they waste tokens. Filter at source: `netstat -ano -p TCP`, `find src -name '*.ts'`, `grep -c`, `wc -l`.\n\nCommon read-only inspection and test/lint/typecheck commands run immediately; anything that could mutate state, install dependencies, or touch the network is refused until the user confirms it in the TUI. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.",
|
|
6048
6473
|
// Plan-mode gate: allow allowlisted commands through (git status,
|
|
6049
6474
|
// cargo check, ls, grep …) so the model can actually investigate
|
|
6050
6475
|
// during planning. Anything that would otherwise trigger a
|
|
@@ -6053,14 +6478,14 @@ function registerShellTools(registry, opts) {
|
|
|
6053
6478
|
if (isAllowAll()) return true;
|
|
6054
6479
|
const cmd = typeof args?.command === "string" ? args.command.trim() : "";
|
|
6055
6480
|
if (!cmd) return false;
|
|
6056
|
-
return
|
|
6481
|
+
return isCommandAllowed(cmd, getExtraAllowed());
|
|
6057
6482
|
},
|
|
6058
6483
|
parameters: {
|
|
6059
6484
|
type: "object",
|
|
6060
6485
|
properties: {
|
|
6061
6486
|
command: {
|
|
6062
6487
|
type: "string",
|
|
6063
|
-
description: 'Full command line.
|
|
6488
|
+
description: 'Full command line. POSIX-ish quoting. Chain operators `|`, `||`, `&&`, `;` and file redirects `>` / `>>` / `<` / `2>` / `2>>` / `2>&1` / `&>` work natively (no shell). Background `&`, heredoc `<<`, env-var expansion `$VAR`, and command substitution `$(\u2026)` are rejected (or passed through as literal in the case of `$VAR`). To pass an operator character as a literal argument (e.g. a regex), wrap it in quotes: `grep "a|b" file.txt`.'
|
|
6064
6489
|
},
|
|
6065
6490
|
timeoutSec: {
|
|
6066
6491
|
type: "integer",
|
|
@@ -6072,7 +6497,7 @@ function registerShellTools(registry, opts) {
|
|
|
6072
6497
|
fn: async (args, ctx) => {
|
|
6073
6498
|
const cmd = args.command.trim();
|
|
6074
6499
|
if (!cmd) throw new Error("run_command: empty command");
|
|
6075
|
-
if (!isAllowAll() && !
|
|
6500
|
+
if (!isAllowAll() && !isCommandAllowed(cmd, getExtraAllowed())) {
|
|
6076
6501
|
throw new NeedsConfirmationError(cmd);
|
|
6077
6502
|
}
|
|
6078
6503
|
const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
|
|
@@ -6436,11 +6861,11 @@ ${i + 1}. ${r.title}`);
|
|
|
6436
6861
|
|
|
6437
6862
|
// src/env.ts
|
|
6438
6863
|
import { readFileSync as readFileSync9 } from "fs";
|
|
6439
|
-
import { resolve as
|
|
6864
|
+
import { resolve as resolve8 } from "path";
|
|
6440
6865
|
function loadDotenv(path2 = ".env") {
|
|
6441
6866
|
let raw;
|
|
6442
6867
|
try {
|
|
6443
|
-
raw = readFileSync9(
|
|
6868
|
+
raw = readFileSync9(resolve8(process.cwd(), path2), "utf8");
|
|
6444
6869
|
} catch {
|
|
6445
6870
|
return;
|
|
6446
6871
|
}
|
|
@@ -7194,7 +7619,7 @@ var McpClient = class {
|
|
|
7194
7619
|
const id = this.nextId++;
|
|
7195
7620
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
7196
7621
|
let abortHandler = null;
|
|
7197
|
-
const promise = new Promise((
|
|
7622
|
+
const promise = new Promise((resolve10, reject) => {
|
|
7198
7623
|
const timeout = setTimeout(() => {
|
|
7199
7624
|
this.pending.delete(id);
|
|
7200
7625
|
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
@@ -7203,7 +7628,7 @@ var McpClient = class {
|
|
|
7203
7628
|
);
|
|
7204
7629
|
}, this.requestTimeoutMs);
|
|
7205
7630
|
this.pending.set(id, {
|
|
7206
|
-
resolve:
|
|
7631
|
+
resolve: resolve10,
|
|
7207
7632
|
reject,
|
|
7208
7633
|
timeout
|
|
7209
7634
|
});
|
|
@@ -7285,7 +7710,7 @@ var McpClient = class {
|
|
|
7285
7710
|
};
|
|
7286
7711
|
|
|
7287
7712
|
// src/mcp/stdio.ts
|
|
7288
|
-
import { spawn as
|
|
7713
|
+
import { spawn as spawn5 } from "child_process";
|
|
7289
7714
|
var StdioTransport = class {
|
|
7290
7715
|
child;
|
|
7291
7716
|
queue = [];
|
|
@@ -7300,14 +7725,14 @@ var StdioTransport = class {
|
|
|
7300
7725
|
opts.command,
|
|
7301
7726
|
...(opts.args ?? []).map((a) => quoteArg(a, process.platform === "win32"))
|
|
7302
7727
|
].join(" ");
|
|
7303
|
-
this.child =
|
|
7728
|
+
this.child = spawn5(line, [], {
|
|
7304
7729
|
env,
|
|
7305
7730
|
cwd: opts.cwd,
|
|
7306
7731
|
stdio: ["pipe", "pipe", "inherit"],
|
|
7307
7732
|
shell: true
|
|
7308
7733
|
});
|
|
7309
7734
|
} else {
|
|
7310
|
-
this.child =
|
|
7735
|
+
this.child = spawn5(opts.command, opts.args ?? [], {
|
|
7311
7736
|
env,
|
|
7312
7737
|
cwd: opts.cwd,
|
|
7313
7738
|
stdio: ["pipe", "pipe", "inherit"]
|
|
@@ -7326,12 +7751,12 @@ var StdioTransport = class {
|
|
|
7326
7751
|
}
|
|
7327
7752
|
async send(message) {
|
|
7328
7753
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
7329
|
-
return new Promise((
|
|
7754
|
+
return new Promise((resolve10, reject) => {
|
|
7330
7755
|
const line = `${JSON.stringify(message)}
|
|
7331
7756
|
`;
|
|
7332
7757
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
7333
7758
|
if (err) reject(err);
|
|
7334
|
-
else
|
|
7759
|
+
else resolve10();
|
|
7335
7760
|
});
|
|
7336
7761
|
});
|
|
7337
7762
|
}
|
|
@@ -7342,8 +7767,8 @@ var StdioTransport = class {
|
|
|
7342
7767
|
continue;
|
|
7343
7768
|
}
|
|
7344
7769
|
if (this.closed) return;
|
|
7345
|
-
const next = await new Promise((
|
|
7346
|
-
this.waiters.push(
|
|
7770
|
+
const next = await new Promise((resolve10) => {
|
|
7771
|
+
this.waiters.push(resolve10);
|
|
7347
7772
|
});
|
|
7348
7773
|
if (next === null) return;
|
|
7349
7774
|
yield next;
|
|
@@ -7412,8 +7837,8 @@ var SseTransport = class {
|
|
|
7412
7837
|
constructor(opts) {
|
|
7413
7838
|
this.url = opts.url;
|
|
7414
7839
|
this.headers = opts.headers ?? {};
|
|
7415
|
-
this.endpointReady = new Promise((
|
|
7416
|
-
this.resolveEndpoint =
|
|
7840
|
+
this.endpointReady = new Promise((resolve10, reject) => {
|
|
7841
|
+
this.resolveEndpoint = resolve10;
|
|
7417
7842
|
this.rejectEndpoint = reject;
|
|
7418
7843
|
});
|
|
7419
7844
|
this.endpointReady.catch(() => void 0);
|
|
@@ -7440,8 +7865,8 @@ var SseTransport = class {
|
|
|
7440
7865
|
continue;
|
|
7441
7866
|
}
|
|
7442
7867
|
if (this.closed) return;
|
|
7443
|
-
const next = await new Promise((
|
|
7444
|
-
this.waiters.push(
|
|
7868
|
+
const next = await new Promise((resolve10) => {
|
|
7869
|
+
this.waiters.push(resolve10);
|
|
7445
7870
|
});
|
|
7446
7871
|
if (next === null) return;
|
|
7447
7872
|
yield next;
|
|
@@ -7627,8 +8052,8 @@ var StreamableHttpTransport = class {
|
|
|
7627
8052
|
continue;
|
|
7628
8053
|
}
|
|
7629
8054
|
if (this.closed) return;
|
|
7630
|
-
const next = await new Promise((
|
|
7631
|
-
this.waiters.push(
|
|
8055
|
+
const next = await new Promise((resolve10) => {
|
|
8056
|
+
this.waiters.push(resolve10);
|
|
7632
8057
|
});
|
|
7633
8058
|
if (next === null) return;
|
|
7634
8059
|
yield next;
|
|
@@ -7799,7 +8224,7 @@ async function trySection(load) {
|
|
|
7799
8224
|
|
|
7800
8225
|
// src/code/edit-blocks.ts
|
|
7801
8226
|
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync12, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
7802
|
-
import { dirname as dirname5, resolve as
|
|
8227
|
+
import { dirname as dirname5, resolve as resolve9 } from "path";
|
|
7803
8228
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
7804
8229
|
function parseEditBlocks(text) {
|
|
7805
8230
|
const out = [];
|
|
@@ -7817,8 +8242,8 @@ function parseEditBlocks(text) {
|
|
|
7817
8242
|
return out;
|
|
7818
8243
|
}
|
|
7819
8244
|
function applyEditBlock(block, rootDir) {
|
|
7820
|
-
const absRoot =
|
|
7821
|
-
const absTarget =
|
|
8245
|
+
const absRoot = resolve9(rootDir);
|
|
8246
|
+
const absTarget = resolve9(absRoot, block.path);
|
|
7822
8247
|
if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
|
|
7823
8248
|
return {
|
|
7824
8249
|
path: block.path,
|
|
@@ -7871,13 +8296,13 @@ function applyEditBlocks(blocks, rootDir) {
|
|
|
7871
8296
|
return blocks.map((b) => applyEditBlock(b, rootDir));
|
|
7872
8297
|
}
|
|
7873
8298
|
function snapshotBeforeEdits(blocks, rootDir) {
|
|
7874
|
-
const absRoot =
|
|
8299
|
+
const absRoot = resolve9(rootDir);
|
|
7875
8300
|
const seen = /* @__PURE__ */ new Set();
|
|
7876
8301
|
const snapshots = [];
|
|
7877
8302
|
for (const b of blocks) {
|
|
7878
8303
|
if (seen.has(b.path)) continue;
|
|
7879
8304
|
seen.add(b.path);
|
|
7880
|
-
const abs =
|
|
8305
|
+
const abs = resolve9(absRoot, b.path);
|
|
7881
8306
|
if (!existsSync10(abs)) {
|
|
7882
8307
|
snapshots.push({ path: b.path, prevContent: null });
|
|
7883
8308
|
continue;
|
|
@@ -7891,9 +8316,9 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
7891
8316
|
return snapshots;
|
|
7892
8317
|
}
|
|
7893
8318
|
function restoreSnapshots(snapshots, rootDir) {
|
|
7894
|
-
const absRoot =
|
|
8319
|
+
const absRoot = resolve9(rootDir);
|
|
7895
8320
|
return snapshots.map((snap) => {
|
|
7896
|
-
const abs =
|
|
8321
|
+
const abs = resolve9(absRoot, snap.path);
|
|
7897
8322
|
if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
|
|
7898
8323
|
return {
|
|
7899
8324
|
path: snap.path,
|