@wrongstack/tools 0.318.0 → 0.319.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/_edit-match.d.ts +1 -3
- package/dist/_util.d.ts +20 -0
- package/dist/audit.js +4 -4
- package/dist/bash.js +2 -2
- package/dist/builtin.d.ts +10 -0
- package/dist/builtin.js +46 -44
- package/dist/{chunk-TKA4VEX2.js → chunk-2OXHS3KZ.js} +17 -5
- package/dist/{chunk-X5K3U6GI.js → chunk-3M7LLN7W.js} +71 -7
- package/dist/{chunk-4AEZIFOW.js → chunk-5EUJF3WC.js} +36 -8
- package/dist/{chunk-CVALEVGA.js → chunk-64VIJPFV.js} +34 -13
- package/dist/{chunk-ZE6KLBUO.js → chunk-6AAOT4PW.js} +58 -10
- package/dist/{chunk-A6BRWIXN.js → chunk-6IEBD27U.js} +20 -5
- package/dist/{chunk-HTYEO67J.js → chunk-AKHYMB4V.js} +36 -12
- package/dist/{chunk-4E42D7UF.js → chunk-ATSKPZUK.js} +2 -2
- package/dist/{chunk-H6AWAZ2Y.js → chunk-B4HTSV6Z.js} +57 -6
- package/dist/{chunk-5ZYEGP2P.js → chunk-BK732J7I.js} +72 -27
- package/dist/{chunk-WI3VDXHD.js → chunk-BSJSNVGA.js} +108 -19
- package/dist/{chunk-UKGMDCXT.js → chunk-C2DQKVLO.js} +61 -28
- package/dist/{chunk-CIFAIIHI.js → chunk-CFLJFDYV.js} +100 -58
- package/dist/{chunk-JJQB6R7Y.js → chunk-CMOUCKGC.js} +6 -6
- package/dist/{chunk-MX25MTDJ.js → chunk-DC4KMPJO.js} +2 -2
- package/dist/{chunk-VZLX2JZG.js → chunk-DP6CXMJZ.js} +3 -3
- package/dist/{chunk-3QQSBUXR.js → chunk-EADULOZV.js} +36 -12
- package/dist/{chunk-6RNIFLG4.js → chunk-ELRRTWNP.js} +38 -6
- package/dist/{chunk-CPSAF2AU.js → chunk-FQNXYA6W.js} +45 -6
- package/dist/{chunk-ZZT45EHF.js → chunk-GMZYW52L.js} +3 -3
- package/dist/{chunk-ZLTFLC25.js → chunk-IIF5HIVF.js} +2 -2
- package/dist/{chunk-EMRGN4KM.js → chunk-IU4DV2PA.js} +18 -8
- package/dist/{chunk-AGUSOFNI.js → chunk-IWS56OY7.js} +30 -7
- package/dist/{chunk-MRZOVQ5D.js → chunk-IYKXK356.js} +98 -11
- package/dist/{chunk-6GZPSWP3.js → chunk-JDPQOR7C.js} +14 -7
- package/dist/{chunk-BLCSYKNN.js → chunk-KY4PXYSO.js} +10 -15
- package/dist/{chunk-WJYXMB2R.js → chunk-LOBRWKCR.js} +63 -14
- package/dist/{chunk-J5SKD6LN.js → chunk-MNP3GE7H.js} +38 -27
- package/dist/{chunk-S2GBLQ5S.js → chunk-O44IMQCN.js} +31 -7
- package/dist/{chunk-P7ZQ2FAJ.js → chunk-OG2BEUBY.js} +69 -34
- package/dist/{chunk-K23JNFE3.js → chunk-OTINMOKK.js} +233 -45
- package/dist/{chunk-PQTONQ4P.js → chunk-QPNYDH5B.js} +19 -5
- package/dist/{chunk-UE3UBMCP.js → chunk-RLMX75EM.js} +19 -8
- package/dist/{chunk-3U4H6ZRF.js → chunk-RNWUCZ2U.js} +53 -25
- package/dist/{chunk-YF3D5INN.js → chunk-RTZWBNTY.js} +121 -91
- package/dist/{chunk-SOG7XIRF.js → chunk-TTZOK6IY.js} +2 -2
- package/dist/{chunk-6GVXZCQV.js → chunk-UDVKRX5R.js} +2 -2
- package/dist/{chunk-CPVLVQYT.js → chunk-UQQS3VNT.js} +43 -9
- package/dist/{chunk-VB3MJGCV.js → chunk-VD2LV5WO.js} +82 -13
- package/dist/{chunk-5WNDJHT6.js → chunk-VPALDFYM.js} +2 -2
- package/dist/{chunk-FJEQZHZN.js → chunk-W2CECGTG.js} +24 -14
- package/dist/{chunk-CISOLTOP.js → chunk-X5FOFGXX.js} +72 -33
- package/dist/{chunk-CWALNI6K.js → chunk-X6KXNHUO.js} +1 -1
- package/dist/{chunk-EEOFQDT6.js → chunk-XRNN44QY.js} +46 -2
- package/dist/{chunk-EOOFNYIT.js → chunk-YOKD4GWJ.js} +67 -27
- package/dist/{chunk-D33S4RQG.js → chunk-ZUFHAHTE.js} +18 -6
- package/dist/codebase-index/generic-parser.d.ts +8 -1
- package/dist/codebase-index/index.js +9 -9
- package/dist/codebase-index/parser-worker-script.js +3 -3
- package/dist/codebase-index/project-server.js +5 -5
- package/dist/codebase-index/worker.js +4 -4
- package/dist/codebase-index/writer-refs.d.ts +1 -1
- package/dist/design.d.ts +1 -1
- package/dist/design.js +1 -1
- package/dist/diff.js +3 -2
- package/dist/document.js +2 -2
- package/dist/e2e.js +2 -2
- package/dist/edit.js +8 -8
- package/dist/exec.js +2 -2
- package/dist/fetch.js +2 -2
- package/dist/format.js +4 -4
- package/dist/{generic-parser-VUXDIAOZ.js → generic-parser-WFMIRHBX.js} +2 -2
- package/dist/git.js +2 -2
- package/dist/glob.js +2 -2
- package/dist/grep.d.ts +2 -0
- package/dist/grep.js +4 -2
- package/dist/index.js +46 -46
- package/dist/install.js +4 -4
- package/dist/json.js +2 -2
- package/dist/kanban-tool-types.d.ts +2 -0
- package/dist/kanban.js +2 -2
- package/dist/languages/index.js +2 -2
- package/dist/{legacy-bridge-SFNXIRAP.js → legacy-bridge-3T7SR4SK.js} +4 -4
- package/dist/lint.js +4 -4
- package/dist/logs.js +2 -2
- package/dist/outdated.js +4 -2
- package/dist/pack.js +45 -45
- package/dist/{parser-dispatch-X62UWXJS.js → parser-dispatch-QGC4HSP5.js} +4 -4
- package/dist/patch.js +8 -8
- package/dist/plan.js +4 -4
- package/dist/{py-parser-TDXSQZBC.js → py-parser-RFWG2HYI.js} +3 -3
- package/dist/read.js +8 -8
- package/dist/replace.js +9 -8
- package/dist/scaffold.js +2 -2
- package/dist/search.js +1 -1
- package/dist/session-kanban.js +1 -1
- package/dist/task.js +4 -4
- package/dist/test.js +4 -4
- package/dist/todo.js +3 -3
- package/dist/tool-tier.js +46 -46
- package/dist/{tree-sitter-parser-AIERW3YI.js → tree-sitter-parser-GILYQLS4.js} +2 -2
- package/dist/tree.js +2 -2
- package/dist/typecheck.js +4 -4
- package/dist/write.js +8 -8
- package/package.json +5 -5
|
@@ -20,10 +20,11 @@ import {
|
|
|
20
20
|
COMMAND_OUTPUT_MAX_BYTES,
|
|
21
21
|
normalizeCommandOutput,
|
|
22
22
|
safeResolveReal
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-ZUFHAHTE.js";
|
|
24
24
|
|
|
25
25
|
// src/exec.ts
|
|
26
26
|
import { spawn } from "node:child_process";
|
|
27
|
+
import { StringDecoder } from "node:string_decoder";
|
|
27
28
|
import {
|
|
28
29
|
emitProcessCompleted,
|
|
29
30
|
emitProcessOutput,
|
|
@@ -32,13 +33,12 @@ import {
|
|
|
32
33
|
import { toErrorMessage } from "@wrongstack/core/utils/error";
|
|
33
34
|
|
|
34
35
|
// src/_danger-detect.ts
|
|
35
|
-
var argHas = (args, value) => args.includes(value);
|
|
36
36
|
var argMatches = (args, re) => args.some((a) => re.test(a));
|
|
37
37
|
var flagLetters = (args) => {
|
|
38
38
|
const seen = /* @__PURE__ */ new Set();
|
|
39
39
|
for (const a of args) {
|
|
40
40
|
if (/^-[a-zA-Z]+$/.test(a)) {
|
|
41
|
-
for (const ch of a.slice(1)) seen.add(ch);
|
|
41
|
+
for (const ch of a.slice(1)) seen.add(ch.toLowerCase());
|
|
42
42
|
} else if (a === "--recursive") {
|
|
43
43
|
seen.add("r");
|
|
44
44
|
} else if (a === "--force") {
|
|
@@ -54,13 +54,29 @@ var RULES = [
|
|
|
54
54
|
// want to do intentionally. We downgrade it to 'destructive' so the
|
|
55
55
|
// confirm prompt can approve. Short clusters, GNU long forms
|
|
56
56
|
// (`--recursive --force`), and mixed shapes all classify identically.
|
|
57
|
+
// The Windows cmd.exe shape classifies identically too: `rmdir /s /q`
|
|
58
|
+
// (and its `rd` alias) is the same operation — /s is the recursive half,
|
|
59
|
+
// /q the quiet (no per-directory prompt, i.e. the "force") half. /s alone
|
|
60
|
+
// still prompts in cmd, so it stays safe, mirroring the PowerShell rule's
|
|
61
|
+
// requirement for both -Recurse and -Force. `del`/`erase` (erase is a cmd
|
|
62
|
+
// alias of del) with /s is classified as well: `del /s` deletes matching
|
|
63
|
+
// files in the whole subtree WITHOUT any per-file prompt, so there /s
|
|
64
|
+
// alone is the recursive-force half (/f only overrides read-only, /q only
|
|
65
|
+
// mutes the global-wildcard "are you sure"). Non-recursive del/erase
|
|
66
|
+
// (single files, or a quiet single-directory wildcard) stays safe.
|
|
57
67
|
{
|
|
58
68
|
id: "rm-recursive",
|
|
59
69
|
level: "destructive",
|
|
60
70
|
test: (cmd, args) => {
|
|
61
|
-
if (cmd !== "rm" && cmd !== "rmdir")
|
|
71
|
+
if (cmd !== "rm" && cmd !== "rmdir" && cmd !== "rd" && cmd !== "del" && cmd !== "erase") {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
62
74
|
const letters = flagLetters(args);
|
|
63
|
-
|
|
75
|
+
if (letters.has("r") && letters.has("f")) return true;
|
|
76
|
+
const has = (flag) => args.some((a) => a.toLowerCase() === `/${flag}`);
|
|
77
|
+
if (cmd === "rmdir" || cmd === "rd") return has("s") && has("q");
|
|
78
|
+
if (cmd === "del" || cmd === "erase") return has("s");
|
|
79
|
+
return false;
|
|
64
80
|
},
|
|
65
81
|
reason: "recursive force-delete"
|
|
66
82
|
},
|
|
@@ -70,9 +86,9 @@ var RULES = [
|
|
|
70
86
|
level: "destructive",
|
|
71
87
|
test: (cmd, args) => {
|
|
72
88
|
if (cmd !== "powershell" && cmd !== "pwsh") return false;
|
|
73
|
-
const hasRecurse = argMatches(args, /^-(?:
|
|
74
|
-
const hasForce =
|
|
75
|
-
if (
|
|
89
|
+
const hasRecurse = argMatches(args, /^-(?:r|recurse)(?::\$true)?$/i);
|
|
90
|
+
const hasForce = argMatches(args, /^-(?:f|force)(?::\$true)?$/i);
|
|
91
|
+
if (argMatches(args, /^-whatif(?::\$true)?$/i)) return false;
|
|
76
92
|
return hasRecurse && hasForce;
|
|
77
93
|
},
|
|
78
94
|
reason: "Remove-Item with -Recurse -Force"
|
|
@@ -84,7 +100,9 @@ var RULES = [
|
|
|
84
100
|
test: (cmd, args) => {
|
|
85
101
|
if (cmd !== "powershell" && cmd !== "pwsh") return false;
|
|
86
102
|
return args.some(
|
|
87
|
-
(a) => /^(?:Format-Volume|Clear-Disk|Initialize-Disk|Remove-Partition|Clear-Volume)(?:\s|$)/i.test(
|
|
103
|
+
(a) => /^(?:Format-Volume|Clear-Disk|Initialize-Disk|Remove-Partition|Clear-Volume)(?:\s|$)/i.test(
|
|
104
|
+
a
|
|
105
|
+
)
|
|
88
106
|
);
|
|
89
107
|
},
|
|
90
108
|
reason: "PowerShell disk/volume partition destruction"
|
|
@@ -191,6 +209,15 @@ var RULES = [
|
|
|
191
209
|
// ----- VCS history rewrite (destructive) -----
|
|
192
210
|
// `git push --force` / `-f` rewrites remote history. `--force-with-lease`
|
|
193
211
|
// is the safer variant (checks remote hasn't moved) but still rewrites.
|
|
212
|
+
// A refspec prefixed with `+` (`git push origin +main`,
|
|
213
|
+
// `+HEAD:refs/heads/main`) is per-refspec force — documented git shorthand
|
|
214
|
+
// equivalent to `--force` for that ref — so it classifies identically. A
|
|
215
|
+
// `+` anywhere else in a refspec (branch `feature+fix`) is just a
|
|
216
|
+
// character, and a leading `^` EXCLUDES the ref (with --all/--mirror);
|
|
217
|
+
// neither is force syntax. `--dry-run` / `-n` rewrites nothing and is
|
|
218
|
+
// exempt, like the PowerShell -WhatIf carve-out above (combined short
|
|
219
|
+
// clusters like `-nf` count as dry-run too — git push's short flags are
|
|
220
|
+
// only -q -v -f -n -u -o, so an 'n' in a cluster means dry-run).
|
|
194
221
|
{
|
|
195
222
|
id: "git-push-force",
|
|
196
223
|
level: "destructive",
|
|
@@ -198,11 +225,18 @@ var RULES = [
|
|
|
198
225
|
if (cmd !== "git") return false;
|
|
199
226
|
const pushIdx = args.indexOf("push");
|
|
200
227
|
if (pushIdx < 0) return false;
|
|
228
|
+
if (args.slice(pushIdx + 1).some((a) => a === "--dry-run" || a === "-n" || /^-[a-z]*n[a-z]*$/i.test(a))) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
201
231
|
for (let i = pushIdx + 1; i < args.length; i++) {
|
|
202
232
|
const a = args[i];
|
|
203
233
|
if (a === "--force" || a === "-f" || a === "--force-with-lease") return true;
|
|
204
|
-
if (!a.startsWith("-") && !a.includes("="))
|
|
234
|
+
if (!a.startsWith("-") && !a.includes("=")) {
|
|
235
|
+
if (a.startsWith("+")) return true;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
205
238
|
if (a.startsWith("--force")) return true;
|
|
239
|
+
if (/^-[a-z]+$/i.test(a) && a.toLowerCase().includes("f")) return true;
|
|
206
240
|
}
|
|
207
241
|
return false;
|
|
208
242
|
},
|
|
@@ -1507,7 +1541,8 @@ var execTool = {
|
|
|
1507
1541
|
};
|
|
1508
1542
|
}
|
|
1509
1543
|
const args = (input.args ?? []).slice(0, MAX_ARGS);
|
|
1510
|
-
const
|
|
1544
|
+
const rawTimeout = typeof input.timeout === "number" && !Number.isNaN(input.timeout) ? input.timeout : DEFAULT_TIMEOUT_MS;
|
|
1545
|
+
const timeout = Math.max(1, Math.min(rawTimeout, MAX_TIMEOUT_MS));
|
|
1511
1546
|
const danger = detectDanger(cmd, args, dangerBypass);
|
|
1512
1547
|
const killCheck = await checkExecKillCommand(cmd, args);
|
|
1513
1548
|
if (killCheck.blocked) {
|
|
@@ -1552,7 +1587,30 @@ var execTool = {
|
|
|
1552
1587
|
};
|
|
1553
1588
|
}
|
|
1554
1589
|
const signal = opts?.signal ?? ctx.signal ?? new AbortController().signal;
|
|
1590
|
+
if (signal.aborted) {
|
|
1591
|
+
return {
|
|
1592
|
+
command: cmd,
|
|
1593
|
+
args,
|
|
1594
|
+
stdout: "",
|
|
1595
|
+
stderr: "Aborted",
|
|
1596
|
+
exitCode: 124,
|
|
1597
|
+
truncated: false,
|
|
1598
|
+
allowed: true,
|
|
1599
|
+
danger
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1555
1602
|
return runCommand(cmd, args, cwd, timeout, signal, ctx.session?.id, danger);
|
|
1603
|
+
},
|
|
1604
|
+
async cleanup(_input, ctx) {
|
|
1605
|
+
const registry = getProcessRegistry();
|
|
1606
|
+
const sessionId = ctx.session?.id;
|
|
1607
|
+
if (!sessionId) return;
|
|
1608
|
+
for (const entry of registry.bySession(sessionId)) {
|
|
1609
|
+
if (entry.name !== "exec") continue;
|
|
1610
|
+
if (entry.child && entry.child.exitCode !== null) continue;
|
|
1611
|
+
if (entry.protected) continue;
|
|
1612
|
+
registry.kill(entry.pid, { force: true });
|
|
1613
|
+
}
|
|
1556
1614
|
}
|
|
1557
1615
|
};
|
|
1558
1616
|
function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
@@ -1572,6 +1630,20 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
1572
1630
|
let telemetryCompleted = false;
|
|
1573
1631
|
let timedOut = false;
|
|
1574
1632
|
const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT });
|
|
1633
|
+
if (signal.aborted) {
|
|
1634
|
+
spool.finalize();
|
|
1635
|
+
finish({
|
|
1636
|
+
command: cmd,
|
|
1637
|
+
args,
|
|
1638
|
+
stdout: "",
|
|
1639
|
+
stderr: "Aborted",
|
|
1640
|
+
exitCode: 124,
|
|
1641
|
+
truncated: false,
|
|
1642
|
+
allowed: true,
|
|
1643
|
+
danger
|
|
1644
|
+
});
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1575
1647
|
const resolved = resolveWin32Command(cmd);
|
|
1576
1648
|
const needsShell = isWin2 && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
1577
1649
|
const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
|
|
@@ -1681,19 +1753,25 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
1681
1753
|
if (signal.aborted) onAbort();
|
|
1682
1754
|
else signal.addEventListener("abort", onAbort, { once: true });
|
|
1683
1755
|
}
|
|
1756
|
+
const stdoutDecoder = new StringDecoder("utf8");
|
|
1757
|
+
const stderrDecoder = new StringDecoder("utf8");
|
|
1684
1758
|
child.stdout?.on("data", (chunk) => {
|
|
1685
|
-
const text =
|
|
1759
|
+
const text = stdoutDecoder.write(chunk);
|
|
1686
1760
|
stdoutBytes += chunk.byteLength;
|
|
1687
1761
|
emitProcessOutput({ pid, stream: "stdout", chunk });
|
|
1688
|
-
if (
|
|
1689
|
-
|
|
1762
|
+
if (text.length > 0) {
|
|
1763
|
+
if (stdout.length < MAX_OUTPUT) stdout += text.slice(0, MAX_OUTPUT - stdout.length);
|
|
1764
|
+
spool.write(text);
|
|
1765
|
+
}
|
|
1690
1766
|
});
|
|
1691
1767
|
child.stderr?.on("data", (chunk) => {
|
|
1692
|
-
const text =
|
|
1768
|
+
const text = stderrDecoder.write(chunk);
|
|
1693
1769
|
stderrBytes += chunk.byteLength;
|
|
1694
1770
|
emitProcessOutput({ pid, stream: "stderr", chunk });
|
|
1695
|
-
if (
|
|
1696
|
-
|
|
1771
|
+
if (text.length > 0) {
|
|
1772
|
+
if (stderr.length < MAX_OUTPUT) stderr += text.slice(0, MAX_OUTPUT - stderr.length);
|
|
1773
|
+
spool.write(text);
|
|
1774
|
+
}
|
|
1697
1775
|
});
|
|
1698
1776
|
child.on("close", (code) => {
|
|
1699
1777
|
clearTimeout(timer);
|
|
@@ -1703,14 +1781,25 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
1703
1781
|
const exitCode = killed ? 124 : code ?? 1;
|
|
1704
1782
|
emitCompletedOnce(exitCode, pid);
|
|
1705
1783
|
registry.afterCall(durationMs, exitCode !== 0);
|
|
1784
|
+
const stdoutTail = stdoutDecoder.end();
|
|
1785
|
+
if (stdoutTail) {
|
|
1786
|
+
if (stdout.length < MAX_OUTPUT) stdout += stdoutTail.slice(0, MAX_OUTPUT - stdout.length);
|
|
1787
|
+
spool.write(stdoutTail);
|
|
1788
|
+
}
|
|
1789
|
+
const stderrTail = stderrDecoder.end();
|
|
1790
|
+
if (stderrTail) {
|
|
1791
|
+
if (stderr.length < MAX_OUTPUT) stderr += stderrTail.slice(0, MAX_OUTPUT - stderr.length);
|
|
1792
|
+
spool.write(stderrTail);
|
|
1793
|
+
}
|
|
1706
1794
|
const spooled = spool.finalize();
|
|
1795
|
+
const isTruncated = stdoutBytes > MAX_OUTPUT || stderrBytes > MAX_OUTPUT || Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES || Buffer.byteLength(stderr, "utf8") > COMMAND_OUTPUT_MAX_BYTES;
|
|
1707
1796
|
finish({
|
|
1708
1797
|
command: cmd,
|
|
1709
1798
|
args,
|
|
1710
1799
|
stdout: normalizeCommandOutput(stdout) + (spooled ? spoolNote(spooled) : ""),
|
|
1711
1800
|
stderr: normalizeCommandOutput(stderr),
|
|
1712
1801
|
exitCode,
|
|
1713
|
-
truncated:
|
|
1802
|
+
truncated: isTruncated,
|
|
1714
1803
|
allowed: true,
|
|
1715
1804
|
danger
|
|
1716
1805
|
});
|
|
@@ -1730,4 +1819,4 @@ export {
|
|
|
1730
1819
|
getExecAllowlist,
|
|
1731
1820
|
execTool
|
|
1732
1821
|
};
|
|
1733
|
-
//# sourceMappingURL=chunk-
|
|
1822
|
+
//# sourceMappingURL=chunk-BSJSNVGA.js.map
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-NBDZMMG2.js";
|
|
6
6
|
import {
|
|
7
7
|
truncateMiddle
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-ZUFHAHTE.js";
|
|
9
9
|
|
|
10
10
|
// src/fetch.ts
|
|
11
11
|
import { FetchError, ToolError, ToolValidationError } from "@wrongstack/core/types";
|
|
@@ -79,13 +79,29 @@ var fetchTool = {
|
|
|
79
79
|
return final;
|
|
80
80
|
},
|
|
81
81
|
async *executeStream(input, ctx, opts) {
|
|
82
|
-
if (!input?.url) {
|
|
82
|
+
if (!input?.url || typeof input.url !== "string" || !input.url.trim()) {
|
|
83
83
|
throw new ToolValidationError({
|
|
84
|
-
message: "fetch: url is required",
|
|
84
|
+
message: "fetch: url is required and cannot be empty",
|
|
85
85
|
field: "url"
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
|
-
const
|
|
88
|
+
const trimmedUrl = input.url.trim();
|
|
89
|
+
let u;
|
|
90
|
+
try {
|
|
91
|
+
u = new URL(trimmedUrl);
|
|
92
|
+
} catch {
|
|
93
|
+
throw new ToolValidationError({
|
|
94
|
+
message: `fetch: invalid URL "${input.url}"`,
|
|
95
|
+
field: "url"
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const ALLOWED_FORMATS = /* @__PURE__ */ new Set(["markdown", "text", "raw"]);
|
|
99
|
+
if (input.format !== void 0 && !ALLOWED_FORMATS.has(input.format)) {
|
|
100
|
+
throw new ToolValidationError({
|
|
101
|
+
message: `fetch: unsupported format "${input.format}". Allowed formats: markdown, text, raw`,
|
|
102
|
+
field: "format"
|
|
103
|
+
});
|
|
104
|
+
}
|
|
89
105
|
if (u.username || u.password) {
|
|
90
106
|
throw new ToolValidationError({
|
|
91
107
|
message: "fetch: URLs with embedded credentials (user:pass@host) are not allowed",
|
|
@@ -105,8 +121,9 @@ var fetchTool = {
|
|
|
105
121
|
});
|
|
106
122
|
}
|
|
107
123
|
await assertNotPrivate(u.hostname);
|
|
108
|
-
yield { type: "log", text: `GET ${
|
|
124
|
+
yield { type: "log", text: `GET ${trimmedUrl}` };
|
|
109
125
|
const signal = opts?.signal ?? ctx.signal ?? new AbortController().signal;
|
|
126
|
+
signal.throwIfAborted();
|
|
110
127
|
const ctrl = new AbortController();
|
|
111
128
|
const timer = setTimeout(
|
|
112
129
|
() => ctrl.abort(
|
|
@@ -122,10 +139,10 @@ var fetchTool = {
|
|
|
122
139
|
try {
|
|
123
140
|
let res;
|
|
124
141
|
try {
|
|
125
|
-
res = await guardedFetch(
|
|
142
|
+
res = await guardedFetch(trimmedUrl, 5, combined);
|
|
126
143
|
} catch (err) {
|
|
127
144
|
if (signal.aborted) throw err;
|
|
128
|
-
throw describeFetchError(err,
|
|
145
|
+
throw describeFetchError(err, trimmedUrl, ctrl.signal.aborted);
|
|
129
146
|
}
|
|
130
147
|
const ct = res.headers.get("content-type") ?? "application/octet-stream";
|
|
131
148
|
if (/^image\/|^audio\/|^video\/|application\/octet-stream/.test(ct)) {
|
|
@@ -146,31 +163,46 @@ var fetchTool = {
|
|
|
146
163
|
let pendingBytes = 0;
|
|
147
164
|
const FLUSH_AT = 4 * 1024;
|
|
148
165
|
if (reader) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
166
|
+
try {
|
|
167
|
+
for (; ; ) {
|
|
168
|
+
const { value, done } = await reader.read();
|
|
169
|
+
if (done) break;
|
|
170
|
+
if (!value) continue;
|
|
171
|
+
received += value.byteLength;
|
|
172
|
+
pendingBytes += value.byteLength;
|
|
173
|
+
chunks.push(value);
|
|
174
|
+
if (pendingBytes >= FLUSH_AT) {
|
|
175
|
+
const recent = Buffer.from(value.buffer, value.byteOffset, value.byteLength).toString(
|
|
176
|
+
"utf8"
|
|
177
|
+
);
|
|
178
|
+
yield {
|
|
179
|
+
type: "partial_output",
|
|
180
|
+
text: recent,
|
|
181
|
+
data: { received }
|
|
182
|
+
};
|
|
183
|
+
pendingBytes = 0;
|
|
184
|
+
}
|
|
185
|
+
if (received > MAX_BYTES) {
|
|
186
|
+
await reader.cancel().catch(() => {
|
|
187
|
+
});
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
164
190
|
}
|
|
165
|
-
|
|
191
|
+
} finally {
|
|
192
|
+
reader.releaseLock();
|
|
166
193
|
}
|
|
167
194
|
}
|
|
168
|
-
const text = Buffer.concat(chunks
|
|
169
|
-
const
|
|
195
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
196
|
+
const isHtml = ct.includes("text/html");
|
|
197
|
+
const isJson = /[\/+]json(;|$)/i.test(ct);
|
|
198
|
+
const format = input.format ?? (isHtml ? "markdown" : "text");
|
|
170
199
|
let content;
|
|
171
200
|
if (format === "raw") content = text;
|
|
172
|
-
else if (format === "markdown" &&
|
|
173
|
-
else if (
|
|
201
|
+
else if (format === "markdown" && isHtml) content = TD.turndown(text);
|
|
202
|
+
else if (format === "markdown" && isJson) content = `\`\`\`json
|
|
203
|
+
${prettyJson(text)}
|
|
204
|
+
\`\`\``;
|
|
205
|
+
else if (isJson) content = prettyJson(text);
|
|
174
206
|
else content = text;
|
|
175
207
|
yield {
|
|
176
208
|
type: "final",
|
|
@@ -226,6 +258,7 @@ function describeFetchError(err, url, timedOut) {
|
|
|
226
258
|
});
|
|
227
259
|
}
|
|
228
260
|
function prettyJson(s) {
|
|
261
|
+
if (!s || s.length > 5e5) return s;
|
|
229
262
|
try {
|
|
230
263
|
return JSON.stringify(JSON.parse(s), null, 2);
|
|
231
264
|
} catch {
|
|
@@ -236,4 +269,4 @@ function prettyJson(s) {
|
|
|
236
269
|
export {
|
|
237
270
|
fetchTool
|
|
238
271
|
};
|
|
239
|
-
//# sourceMappingURL=chunk-
|
|
272
|
+
//# sourceMappingURL=chunk-C2DQKVLO.js.map
|
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
} from "./chunk-FQZOHLG3.js";
|
|
4
4
|
import {
|
|
5
5
|
enqueueReindex
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-DP6CXMJZ.js";
|
|
7
7
|
import {
|
|
8
8
|
safeResolveReal,
|
|
9
9
|
sha256hex,
|
|
10
10
|
truncateDiffPayload
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-ZUFHAHTE.js";
|
|
12
12
|
|
|
13
13
|
// src/edit.ts
|
|
14
14
|
import * as fs from "node:fs/promises";
|
|
@@ -41,43 +41,50 @@ var FUZZY_MAX_INTERIOR_CHARS = 2e3;
|
|
|
41
41
|
function findLadderMatches(fileLf, oldLf) {
|
|
42
42
|
const exact = [];
|
|
43
43
|
let idx = fileLf.indexOf(oldLf);
|
|
44
|
+
let currentLine = 1;
|
|
45
|
+
let lastPos = 0;
|
|
44
46
|
while (idx !== -1) {
|
|
45
|
-
|
|
47
|
+
while (lastPos < idx) {
|
|
48
|
+
const nextNewline = fileLf.indexOf("\n", lastPos);
|
|
49
|
+
if (nextNewline === -1 || nextNewline >= idx) break;
|
|
50
|
+
currentLine++;
|
|
51
|
+
lastPos = nextNewline + 1;
|
|
52
|
+
}
|
|
53
|
+
exact.push({ start: idx, end: idx + oldLf.length, startLine: currentLine });
|
|
46
54
|
idx = fileLf.indexOf(oldLf, idx + 1);
|
|
47
55
|
}
|
|
48
56
|
if (exact.length > 0) return { tier: "exact", matches: exact };
|
|
49
57
|
const fileLines = fileLf.split("\n");
|
|
50
|
-
const
|
|
51
|
-
|
|
58
|
+
const needleEndsWithNewline = oldLf.endsWith("\n");
|
|
59
|
+
const rawNeedleLines = oldLf.split("\n");
|
|
60
|
+
const needleLines = needleEndsWithNewline && rawNeedleLines[rawNeedleLines.length - 1] === "" ? rawNeedleLines.slice(0, -1) : rawNeedleLines;
|
|
61
|
+
if (needleLines.length === 0 || needleLines.length > fileLines.length) return void 0;
|
|
52
62
|
const offsets = lineOffsets(fileLines);
|
|
53
63
|
const fileTrimEnd = fileLines.map((l) => l.trimEnd());
|
|
54
64
|
const needleTrimEnd = needleLines.map((l) => l.trimEnd());
|
|
55
|
-
const trailing = windowScan(
|
|
65
|
+
const trailing = windowScan(
|
|
66
|
+
fileTrimEnd,
|
|
67
|
+
needleTrimEnd,
|
|
68
|
+
offsets,
|
|
69
|
+
fileLines,
|
|
70
|
+
(a, b) => a === b,
|
|
71
|
+
needleEndsWithNewline
|
|
72
|
+
);
|
|
56
73
|
if (trailing.length > 0) return { tier: "trailing-whitespace", matches: trailing };
|
|
57
74
|
const normalizedLen = needleTrimEnd.reduce((n, l) => n + l.trimStart().length, 0);
|
|
58
75
|
if (normalizedLen < MIN_NORMALIZED_NEEDLE_CHARS) return void 0;
|
|
59
76
|
const fileTrimmed = fileTrimEnd.map((l) => l.trimStart());
|
|
60
77
|
const needleTrimmed = needleTrimEnd.map((l) => l.trimStart());
|
|
61
|
-
const normalized = windowScan(
|
|
78
|
+
const normalized = windowScan(
|
|
79
|
+
fileTrimmed,
|
|
80
|
+
needleTrimmed,
|
|
81
|
+
offsets,
|
|
82
|
+
fileLines,
|
|
83
|
+
(a, b) => a === b,
|
|
84
|
+
needleEndsWithNewline
|
|
85
|
+
);
|
|
62
86
|
if (normalized.length > 0) return { tier: "whitespace-normalized", matches: normalized };
|
|
63
|
-
return fuzzyScan(fileLines, needleLines, offsets);
|
|
64
|
-
}
|
|
65
|
-
function lineAt(text, pos) {
|
|
66
|
-
if (pos < 512) {
|
|
67
|
-
let line2 = 1;
|
|
68
|
-
for (let i = 0; i < pos; i++) {
|
|
69
|
-
if (text.charCodeAt(i) === 10) line2++;
|
|
70
|
-
}
|
|
71
|
-
return line2;
|
|
72
|
-
}
|
|
73
|
-
let line = 1;
|
|
74
|
-
let search = 0;
|
|
75
|
-
while (true) {
|
|
76
|
-
const idx = text.indexOf("\n", search);
|
|
77
|
-
if (idx === -1 || idx >= pos) return line;
|
|
78
|
-
search = idx + 1;
|
|
79
|
-
line++;
|
|
80
|
-
}
|
|
87
|
+
return fuzzyScan(fileLines, needleLines, offsets, needleEndsWithNewline);
|
|
81
88
|
}
|
|
82
89
|
function lineOffsets(lines) {
|
|
83
90
|
const out = new Array(lines.length);
|
|
@@ -88,15 +95,17 @@ function lineOffsets(lines) {
|
|
|
88
95
|
}
|
|
89
96
|
return out;
|
|
90
97
|
}
|
|
91
|
-
function windowToMatch(fileLines, offsets, start, windowLen) {
|
|
98
|
+
function windowToMatch(fileLines, offsets, start, windowLen, includeTrailingNewline = false) {
|
|
92
99
|
const lastLine = start + windowLen - 1;
|
|
100
|
+
const lineEnd = offsets[lastLine] + fileLines[lastLine].length;
|
|
101
|
+
const end = includeTrailingNewline && lastLine < fileLines.length - 1 ? lineEnd + 1 : lineEnd;
|
|
93
102
|
return {
|
|
94
103
|
start: offsets[start],
|
|
95
|
-
end
|
|
104
|
+
end,
|
|
96
105
|
startLine: start + 1
|
|
97
106
|
};
|
|
98
107
|
}
|
|
99
|
-
function windowScan(comparisonLines, needleLines, offsets, originalLines, eq) {
|
|
108
|
+
function windowScan(comparisonLines, needleLines, offsets, originalLines, eq, includeTrailingNewline = false) {
|
|
100
109
|
const n = needleLines.length;
|
|
101
110
|
const out = [];
|
|
102
111
|
for (let i = 0; i + n <= comparisonLines.length; i++) {
|
|
@@ -108,28 +117,32 @@ function windowScan(comparisonLines, needleLines, offsets, originalLines, eq) {
|
|
|
108
117
|
}
|
|
109
118
|
}
|
|
110
119
|
if (all) {
|
|
111
|
-
out.push(windowToMatch(originalLines, offsets, i, n));
|
|
120
|
+
out.push(windowToMatch(originalLines, offsets, i, n, includeTrailingNewline));
|
|
112
121
|
i += n - 1;
|
|
113
122
|
}
|
|
114
123
|
}
|
|
115
124
|
return out;
|
|
116
125
|
}
|
|
117
|
-
function fuzzyScan(fileLines, needleLines, offsets) {
|
|
126
|
+
function fuzzyScan(fileLines, needleLines, offsets, includeTrailingNewline = false) {
|
|
118
127
|
const n = needleLines.length;
|
|
119
128
|
if (n < 3) return void 0;
|
|
120
129
|
const firstNeedle = needleLines[0].trim();
|
|
121
130
|
const lastNeedle = needleLines[n - 1].trim();
|
|
122
131
|
const needleInterior = needleLines.slice(1, -1).map((l) => l.trim()).join("\n");
|
|
123
132
|
if (needleInterior.length > FUZZY_MAX_INTERIOR_CHARS) return void 0;
|
|
133
|
+
const fileTrimmed = fileLines.map((l) => l.trim());
|
|
124
134
|
const candidates = [];
|
|
125
135
|
for (let i = 0; i + n <= fileLines.length; i++) {
|
|
126
|
-
if (
|
|
127
|
-
if (
|
|
128
|
-
const windowInterior =
|
|
136
|
+
if (fileTrimmed[i] !== firstNeedle) continue;
|
|
137
|
+
if (fileTrimmed[i + n - 1] !== lastNeedle) continue;
|
|
138
|
+
const windowInterior = fileTrimmed.slice(i + 1, i + n - 1).join("\n");
|
|
129
139
|
if (windowInterior.length > FUZZY_MAX_INTERIOR_CHARS) continue;
|
|
130
140
|
const score = similarity(needleInterior, windowInterior);
|
|
131
141
|
if (score >= FUZZY_MIN_SIMILARITY) {
|
|
132
|
-
candidates.push({
|
|
142
|
+
candidates.push({
|
|
143
|
+
match: windowToMatch(fileLines, offsets, i, n, includeTrailingNewline),
|
|
144
|
+
score
|
|
145
|
+
});
|
|
133
146
|
}
|
|
134
147
|
}
|
|
135
148
|
if (candidates.length === 0) return void 0;
|
|
@@ -150,21 +163,29 @@ function similarity(a, b) {
|
|
|
150
163
|
if (a === b) return 1;
|
|
151
164
|
const max = Math.max(a.length, b.length);
|
|
152
165
|
if (max === 0) return 1;
|
|
153
|
-
|
|
154
|
-
|
|
166
|
+
const lenDiff = Math.abs(a.length - b.length);
|
|
167
|
+
if (lenDiff / max > 1 - FUZZY_MIN_SIMILARITY) {
|
|
168
|
+
return 1 - lenDiff / max;
|
|
155
169
|
}
|
|
156
170
|
return 1 - levenshtein(a, b) / max;
|
|
157
171
|
}
|
|
158
172
|
function levenshtein(a, b) {
|
|
159
173
|
if (a.length === 0) return b.length;
|
|
160
174
|
if (b.length === 0) return a.length;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
175
|
+
if (a.length < b.length) {
|
|
176
|
+
const tmp = a;
|
|
177
|
+
a = b;
|
|
178
|
+
b = tmp;
|
|
179
|
+
}
|
|
180
|
+
const bLen = b.length;
|
|
181
|
+
const aLen = a.length;
|
|
182
|
+
let prev = new Int32Array(bLen + 1);
|
|
183
|
+
let cur = new Int32Array(bLen + 1);
|
|
184
|
+
for (let j = 0; j <= bLen; j++) prev[j] = j;
|
|
185
|
+
for (let i = 1; i <= aLen; i++) {
|
|
165
186
|
cur[0] = i;
|
|
166
187
|
const ca = a.charCodeAt(i - 1);
|
|
167
|
-
for (let j = 1; j <=
|
|
188
|
+
for (let j = 1; j <= bLen; j++) {
|
|
168
189
|
const cost = ca === b.charCodeAt(j - 1) ? 0 : 1;
|
|
169
190
|
cur[j] = Math.min(
|
|
170
191
|
prev[j] + 1,
|
|
@@ -172,9 +193,11 @@ function levenshtein(a, b) {
|
|
|
172
193
|
prev[j - 1] + cost
|
|
173
194
|
);
|
|
174
195
|
}
|
|
175
|
-
|
|
196
|
+
const tmp = prev;
|
|
197
|
+
prev = cur;
|
|
198
|
+
cur = tmp;
|
|
176
199
|
}
|
|
177
|
-
return prev[
|
|
200
|
+
return prev[bLen];
|
|
178
201
|
}
|
|
179
202
|
function firstLineIndent(text) {
|
|
180
203
|
for (const line of text.split("\n")) {
|
|
@@ -189,14 +212,17 @@ function adjustIndent(newLf, fromIndent, toIndent) {
|
|
|
189
212
|
if (toIndent.startsWith(fromIndent)) {
|
|
190
213
|
const extra = toIndent.slice(fromIndent.length);
|
|
191
214
|
return {
|
|
192
|
-
text: lines.map((l) => l.trim() === "" ?
|
|
215
|
+
text: lines.map((l) => l.trim() === "" ? "" : extra + l).join("\n"),
|
|
193
216
|
adjusted: true
|
|
194
217
|
};
|
|
195
218
|
}
|
|
196
219
|
if (fromIndent.startsWith(toIndent)) {
|
|
197
220
|
const remove = fromIndent.slice(toIndent.length);
|
|
198
221
|
return {
|
|
199
|
-
text: lines.map((l) =>
|
|
222
|
+
text: lines.map((l) => {
|
|
223
|
+
if (l.trim() === "") return "";
|
|
224
|
+
return l.startsWith(remove) ? l.slice(remove.length) : l;
|
|
225
|
+
}).join("\n"),
|
|
200
226
|
adjusted: true
|
|
201
227
|
};
|
|
202
228
|
}
|
|
@@ -204,34 +230,50 @@ function adjustIndent(newLf, fromIndent, toIndent) {
|
|
|
204
230
|
}
|
|
205
231
|
function nearestMatchHint(fileLf, oldLf) {
|
|
206
232
|
const fileLines = fileLf.split("\n");
|
|
207
|
-
const
|
|
208
|
-
|
|
233
|
+
const rawNeedle = oldLf.split("\n");
|
|
234
|
+
const needleLines = oldLf.endsWith("\n") && rawNeedle[rawNeedle.length - 1] === "" ? rawNeedle.slice(0, -1) : rawNeedle;
|
|
235
|
+
const n = needleLines.length;
|
|
236
|
+
if (n === 0 || n > fileLines.length) return void 0;
|
|
209
237
|
const needleTrimmed = needleLines.map((l) => l.trim());
|
|
238
|
+
const fileTrimmed = fileLines.map((l) => l.trim());
|
|
210
239
|
let bestScore = 0;
|
|
211
240
|
let bestStart = -1;
|
|
212
|
-
|
|
241
|
+
const maxI = fileLines.length - n;
|
|
242
|
+
for (let i = 0; i <= maxI; i++) {
|
|
213
243
|
let sum = 0;
|
|
214
|
-
|
|
215
|
-
|
|
244
|
+
let possible = true;
|
|
245
|
+
for (let j = 0; j < n; j++) {
|
|
246
|
+
sum += lineSimilarity(needleTrimmed[j], fileTrimmed[i + j]);
|
|
247
|
+
if (sum + (n - 1 - j) <= bestScore * n) {
|
|
248
|
+
possible = false;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
216
251
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
bestScore
|
|
220
|
-
|
|
252
|
+
if (possible) {
|
|
253
|
+
const score = sum / n;
|
|
254
|
+
if (score > bestScore) {
|
|
255
|
+
bestScore = score;
|
|
256
|
+
bestStart = i;
|
|
257
|
+
}
|
|
221
258
|
}
|
|
222
259
|
}
|
|
223
260
|
if (bestStart === -1 || bestScore < 0.5) return void 0;
|
|
224
|
-
const snippet = fileLines.slice(bestStart, bestStart + Math.min(3,
|
|
261
|
+
const snippet = fileLines.slice(bestStart, bestStart + Math.min(3, n)).map((l) => l.length > 120 ? `${l.slice(0, 120)}\u2026` : l).join("\n");
|
|
225
262
|
return { line: bestStart + 1, snippet };
|
|
226
263
|
}
|
|
227
|
-
function
|
|
264
|
+
function lineSimilarity(a, b) {
|
|
228
265
|
if (a === b) return 1;
|
|
229
266
|
const max = Math.max(a.length, b.length);
|
|
230
267
|
if (max === 0) return 1;
|
|
268
|
+
if (a.includes(b) || b.includes(a)) {
|
|
269
|
+
return Math.min(a.length, b.length) / max;
|
|
270
|
+
}
|
|
231
271
|
let p = 0;
|
|
232
272
|
const lim = Math.min(a.length, b.length);
|
|
233
273
|
while (p < lim && a.charCodeAt(p) === b.charCodeAt(p)) p++;
|
|
234
|
-
|
|
274
|
+
let s = 0;
|
|
275
|
+
while (s < lim - p && a.charCodeAt(a.length - 1 - s) === b.charCodeAt(b.length - 1 - s)) s++;
|
|
276
|
+
return (p + s) / max;
|
|
235
277
|
}
|
|
236
278
|
|
|
237
279
|
// src/edit.ts
|
|
@@ -473,4 +515,4 @@ Compare this against your old_string and retry with the file's actual text.` : "
|
|
|
473
515
|
export {
|
|
474
516
|
editTool
|
|
475
517
|
};
|
|
476
|
-
//# sourceMappingURL=chunk-
|
|
518
|
+
//# sourceMappingURL=chunk-CFLJFDYV.js.map
|