dskcode 0.1.18 → 0.1.19

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/index.js CHANGED
@@ -773,7 +773,7 @@ async function getAllSkills(cwd) {
773
773
  }
774
774
 
775
775
  // src/cli/index.tsx
776
- import { readFile as readFile8 } from "fs/promises";
776
+ import { readFile as readFile10 } from "fs/promises";
777
777
  import { join as join9 } from "path";
778
778
 
779
779
  // src/ui/RenderScope.tsx
@@ -1168,7 +1168,7 @@ function ToolCallBlock({ call, showPendingHint = true }) {
1168
1168
  ] })
1169
1169
  ] }),
1170
1170
  /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsx3(Text4, { color: "#888888", children: argsDisplay }) }),
1171
- showPendingHint && /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text4, { color: "yellow", children: "\u23F3 \u7B49\u5F85\u6267\u884C\uFF08\u5DE5\u5177\u7CFB\u7EDF\u5C06\u5728\u7B2C08\u7AE0\u5B9E\u73B0\uFF09" }) })
1171
+ showPendingHint && /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text4, { color: "yellow", children: "\u23F3 \u7B49\u5F85\u6267\u884C" }) })
1172
1172
  ] });
1173
1173
  }
1174
1174
 
@@ -1306,12 +1306,12 @@ function DiffPreview({ diff }) {
1306
1306
  ")"
1307
1307
  ] })
1308
1308
  ] }),
1309
- /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginLeft: 2, children: lines.map((line, i) => /* @__PURE__ */ jsx5(DiffLine, { line }, i)) })
1309
+ /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginLeft: 2, children: lines.filter((line) => !line.startsWith("---") && !line.startsWith("+++")).map((line, i) => /* @__PURE__ */ jsx5(DiffLine, { line }, i)) })
1310
1310
  ] });
1311
1311
  }
1312
1312
  function DiffLine({ line }) {
1313
1313
  if (line.startsWith("---") || line.startsWith("+++")) {
1314
- return /* @__PURE__ */ jsx5(Text6, { color: "#00cccc", bold: true, children: line });
1314
+ return null;
1315
1315
  }
1316
1316
  if (line.startsWith("@@")) {
1317
1317
  return /* @__PURE__ */ jsx5(Text6, { color: "#00cccc", children: line });
@@ -1628,6 +1628,13 @@ var ToolRegistry = class {
1628
1628
  }
1629
1629
  };
1630
1630
 
1631
+ // src/tool/types.ts
1632
+ var AlwaysAllowGate = class {
1633
+ check(_toolName, _args) {
1634
+ return true;
1635
+ }
1636
+ };
1637
+
1631
1638
  // src/agent/index.ts
1632
1639
  var Session = class {
1633
1640
  #messages = [];
@@ -1636,6 +1643,8 @@ var Session = class {
1636
1643
  #costTracker;
1637
1644
  #options;
1638
1645
  #abortController = new AbortController();
1646
+ // 风暴检测:记录每轮的工具调用错误
1647
+ #stormRecords = [];
1639
1648
  constructor(provider, tools = [], costTracker, options) {
1640
1649
  this.#provider = provider;
1641
1650
  if (tools instanceof ToolRegistry) {
@@ -1650,7 +1659,9 @@ var Session = class {
1650
1659
  maxToolRounds: options?.maxToolRounds ?? 20,
1651
1660
  reservedForOutput: options?.reservedForOutput ?? 4096,
1652
1661
  preserveRecentRounds: options?.preserveRecentRounds ?? 10,
1653
- projectContext: options?.projectContext
1662
+ projectContext: options?.projectContext,
1663
+ gate: options?.gate ?? new AlwaysAllowGate(),
1664
+ writeRoots: options?.writeRoots ?? [options?.cwd ?? process.cwd()]
1654
1665
  };
1655
1666
  }
1656
1667
  // -------------------------------------------------------------------------
@@ -1686,20 +1697,6 @@ var Session = class {
1686
1697
  * c. 如果有工具调用 → 执行工具 → 追加结果 → 继续循环
1687
1698
  * d. 如果没有工具调用 → 退出循环
1688
1699
  * 3. yield done 事件
1689
- *
1690
- * 调用方式:
1691
- * ```ts
1692
- * for await (const event of session.chat("你好")) {
1693
- * switch (event.type) {
1694
- * case "text_delta": // 追加文本
1695
- * case "tool_calls": // 展示工具调用
1696
- * case "tool_result": // 工具执行结果
1697
- * case "usage": // 记录使用量
1698
- * case "done": // 本轮完成
1699
- * case "error": // 处理错误
1700
- * }
1701
- * }
1702
- * ```
1703
1700
  */
1704
1701
  async *chat(userInput) {
1705
1702
  this.#messages.push({ role: "user", content: userInput });
@@ -1757,30 +1754,31 @@ var Session = class {
1757
1754
  this.#messages.push(assistantMsg);
1758
1755
  if (lastToolCalls && lastToolCalls.length > 0) {
1759
1756
  yield { type: "tool_calls", calls: lastToolCalls };
1760
- const toolCtx = {
1761
- cwd: this.#options.cwd,
1762
- signal: this.#abortController.signal
1763
- };
1764
- for (const tc of lastToolCalls) {
1765
- let toolArgs;
1766
- try {
1767
- toolArgs = tc.arguments ? JSON.parse(tc.arguments) : {};
1768
- } catch {
1769
- toolArgs = {};
1770
- }
1771
- const result = await this.#toolRegistry.execute(tc.name, toolArgs, toolCtx);
1772
- yield { type: "tool_result", name: tc.name, result };
1773
- let toolContent = result.data;
1774
- if (result.diff && result.diff.patch) {
1757
+ const stormBroken = this.#checkStormBreak(lastToolCalls);
1758
+ if (stormBroken) {
1759
+ const stormMsg = "\n\u26A0\uFE0F \u540C\u4E00\u5DE5\u5177\u91CD\u590D\u51FA\u9519\uFF0C\u5DF2\u5F3A\u5236\u5207\u6362\u7B56\u7565\n";
1760
+ yield { type: "text_delta", content: stormMsg };
1761
+ assistantMsg.toolCalls = void 0;
1762
+ assistantMsg.content += stormMsg;
1763
+ this.#stormRecords = [];
1764
+ toolRounds++;
1765
+ continue;
1766
+ }
1767
+ const results = await this.#executeBatch(lastToolCalls);
1768
+ this.#stormRecords = results.records;
1769
+ for (const item of results.items) {
1770
+ yield { type: "tool_result", name: item.name, result: item.result };
1771
+ let toolContent = item.result.data;
1772
+ if (item.result.diff && item.result.diff.patch) {
1775
1773
  toolContent += `
1776
1774
 
1777
- ${result.diff.patch}`;
1775
+ ${item.result.diff.patch}`;
1778
1776
  }
1779
1777
  this.#messages.push({
1780
1778
  role: "tool",
1781
1779
  content: toolContent,
1782
- toolCallId: tc.id,
1783
- name: tc.name
1780
+ toolCallId: item.callId,
1781
+ name: item.name
1784
1782
  });
1785
1783
  }
1786
1784
  toolRounds++;
@@ -1805,6 +1803,123 @@ ${result.diff.patch}`;
1805
1803
  yield { type: "done", elapsed };
1806
1804
  }
1807
1805
  // -------------------------------------------------------------------------
1806
+ // 工具执行 — 批量、并行/串行、Gate、风暴检测
1807
+ // -------------------------------------------------------------------------
1808
+ /**
1809
+ * 执行一批工具调用。
1810
+ *
1811
+ * 并行策略:
1812
+ * - 如果这批工具全部是 ReadOnly 的,并行执行(最多 8 并发)
1813
+ * - 否则按顺序串行执行,保证写/读顺序
1814
+ */
1815
+ async #executeBatch(calls) {
1816
+ const toolCtx = {
1817
+ cwd: this.#options.cwd,
1818
+ signal: this.#abortController.signal
1819
+ };
1820
+ const allReadOnly = calls.every((tc) => {
1821
+ const tool = this.#toolRegistry.get(tc.name);
1822
+ return tool?.readOnly === true;
1823
+ });
1824
+ if (allReadOnly && calls.length > 1) {
1825
+ const MAX_PARALLEL = 8;
1826
+ const items2 = [];
1827
+ const records2 = [];
1828
+ for (let i = 0; i < calls.length; i += MAX_PARALLEL) {
1829
+ const batch = calls.slice(i, i + MAX_PARALLEL);
1830
+ const promises = batch.map((tc) => this.#executeOne(tc, toolCtx));
1831
+ const batchResults = await Promise.all(promises);
1832
+ for (const r of batchResults) {
1833
+ items2.push(r.item);
1834
+ records2.push(r.record);
1835
+ }
1836
+ }
1837
+ return { items: items2, records: records2 };
1838
+ }
1839
+ const items = [];
1840
+ const records = [];
1841
+ for (const tc of calls) {
1842
+ const r = await this.#executeOne(tc, toolCtx);
1843
+ items.push(r.item);
1844
+ records.push(r.record);
1845
+ }
1846
+ return { items, records };
1847
+ }
1848
+ /**
1849
+ * 执行单个工具调用,包含 Gate 检查和预览。
1850
+ */
1851
+ async #executeOne(tc, ctx) {
1852
+ const toolName = tc.name;
1853
+ const timestamp = Date.now();
1854
+ const tool = this.#toolRegistry.get(toolName);
1855
+ if (!tool) {
1856
+ const errMsg = `\u5DE5\u5177 "${toolName}" \u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u7981\u7528`;
1857
+ return {
1858
+ item: { name: toolName, callId: tc.id, result: { success: false, data: errMsg, error: "TOOL_NOT_FOUND" } },
1859
+ record: { name: toolName, success: false, error: "TOOL_NOT_FOUND", timestamp }
1860
+ };
1861
+ }
1862
+ let toolArgs;
1863
+ try {
1864
+ toolArgs = tc.arguments ? JSON.parse(tc.arguments) : {};
1865
+ } catch {
1866
+ toolArgs = {};
1867
+ }
1868
+ const gateResult = await this.#options.gate.check(toolName, toolArgs);
1869
+ if (!gateResult) {
1870
+ const errMsg = `\u5DE5\u5177 "${toolName}" \u88AB\u6743\u9650\u95E8\u62D2\u7EDD`;
1871
+ return {
1872
+ item: { name: toolName, callId: tc.id, result: { success: false, data: errMsg, error: "GATE_DENIED" } },
1873
+ record: { name: toolName, success: false, error: "GATE_DENIED", timestamp }
1874
+ };
1875
+ }
1876
+ if (!tool.readOnly) {
1877
+ const maybePreviewer = tool;
1878
+ if (typeof maybePreviewer.preview === "function") {
1879
+ try {
1880
+ await maybePreviewer.preview(toolArgs, ctx);
1881
+ } catch {
1882
+ }
1883
+ }
1884
+ }
1885
+ try {
1886
+ const result = await tool.execute(toolArgs, ctx);
1887
+ return {
1888
+ item: { name: toolName, callId: tc.id, result },
1889
+ record: {
1890
+ name: toolName,
1891
+ success: result.success,
1892
+ error: result.error,
1893
+ timestamp
1894
+ }
1895
+ };
1896
+ } catch (err) {
1897
+ const message = err instanceof Error ? err.message : String(err);
1898
+ const errorResult = { success: false, data: `\u5DE5\u5177 "${toolName}" \u6267\u884C\u5F02\u5E38\uFF1A${message}`, error: "EXECUTION_ERROR" };
1899
+ return {
1900
+ item: { name: toolName, callId: tc.id, result: errorResult },
1901
+ record: { name: toolName, success: false, error: "EXECUTION_ERROR", timestamp }
1902
+ };
1903
+ }
1904
+ }
1905
+ // -------------------------------------------------------------------------
1906
+ // 风暴检测 — 同一工具同一错误连续 3 次 → 强制换策略
1907
+ // -------------------------------------------------------------------------
1908
+ /**
1909
+ * 连续 3 次同一工具同一错误 → 触发风暴中断。
1910
+ */
1911
+ #checkStormBreak(currentCalls) {
1912
+ if (this.#stormRecords.length < 3) return false;
1913
+ const recentErrors = this.#stormRecords.slice(-3);
1914
+ if (recentErrors.length < 3) return false;
1915
+ const first = recentErrors[0];
1916
+ const allSame = recentErrors.every(
1917
+ (r) => r.name === first.name && r.error === first.error && !r.success
1918
+ );
1919
+ if (!allSame) return false;
1920
+ return currentCalls.some((tc) => tc.name === first.name);
1921
+ }
1922
+ // -------------------------------------------------------------------------
1808
1923
  // 会话管理
1809
1924
  // -------------------------------------------------------------------------
1810
1925
  /** 取消正在进行的流式请求 */
@@ -1815,6 +1930,7 @@ ${result.diff.patch}`;
1815
1930
  reset() {
1816
1931
  this.#messages.length = 0;
1817
1932
  this.#costTracker.resetSession();
1933
+ this.#stormRecords = [];
1818
1934
  }
1819
1935
  // -------------------------------------------------------------------------
1820
1936
  // 内部方法
@@ -1850,6 +1966,7 @@ ${result.diff.patch}`;
1850
1966
 
1851
1967
  // src/tool/sandbox.ts
1852
1968
  import { resolve, relative, isAbsolute } from "path";
1969
+ import { realpath as realpath2 } from "fs/promises";
1853
1970
  import { spawn } from "child_process";
1854
1971
  import process2 from "process";
1855
1972
  var DEFAULT_TIMEOUT_MS = 3e4;
@@ -1858,18 +1975,14 @@ var DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
1858
1975
  var isWindows = process2.platform === "win32";
1859
1976
  function resolvePath(inputPath, cwd) {
1860
1977
  const resolved = isAbsolute(inputPath) ? inputPath : resolve(cwd, inputPath);
1861
- const normalized = resolve(resolved);
1862
- const rel = relative(cwd, normalized);
1863
- if (rel.startsWith("..") || normalized !== resolve(cwd) && !rel) {
1864
- }
1865
- return normalized;
1978
+ return resolve(resolved);
1866
1979
  }
1867
1980
  function truncateOutput(content, maxLength = DEFAULT_MAX_OUTPUT_LENGTH) {
1868
1981
  if (content.length <= maxLength) return content;
1869
1982
  const truncated = content.slice(0, maxLength);
1870
1983
  return `${truncated}
1871
1984
 
1872
- ... [\u8F93\u51FA\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF0C\u5171 ${content.length} \u5B57\u7B26]`;
1985
+ ... [\u8F93\u51FA\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF0C\u5171 ${String(content.length)} \u5B57\u7B26]`;
1873
1986
  }
1874
1987
  function getDefaultTimeout() {
1875
1988
  return DEFAULT_TIMEOUT_MS;
@@ -1930,97 +2043,119 @@ async function execCommand(command, args, cwd, timeoutMs = DEFAULT_TIMEOUT_MS, s
1930
2043
  }
1931
2044
 
1932
2045
  // src/tool/diff.ts
1933
- function computeFileDiff(oldContent, newContent, filePath) {
1934
- if (oldContent === newContent) {
1935
- return {
1936
- filePath,
1937
- patch: "",
1938
- existedBefore: oldContent.length > 0,
1939
- additions: 0,
1940
- deletions: 0
1941
- };
1942
- }
1943
- if (oldContent.length === 0) {
1944
- const newLines2 = splitLines(newContent);
1945
- const patch2 = formatNewFileDiff(newLines2, filePath);
1946
- return {
1947
- filePath,
1948
- patch: patch2,
1949
- existedBefore: false,
1950
- additions: newLines2.length,
1951
- deletions: 0
1952
- };
1953
- }
1954
- if (newContent.length === 0) {
1955
- const oldLines2 = splitLines(oldContent);
1956
- const patch2 = formatDeletedFileDiff(oldLines2, filePath);
1957
- return {
1958
- filePath,
1959
- patch: patch2,
1960
- existedBefore: true,
1961
- additions: 0,
1962
- deletions: oldLines2.length
1963
- };
1964
- }
1965
- const oldLines = splitLines(oldContent);
1966
- const newLines = splitLines(newContent);
1967
- const diffLines = computeLineDiff(oldLines, newLines);
1968
- let additions = 0;
1969
- let deletions = 0;
1970
- for (const line of diffLines) {
1971
- if (line.op === "add") additions++;
1972
- if (line.op === "remove") deletions++;
1973
- }
1974
- const hunks = groupIntoHunks(diffLines);
1975
- const patch = formatUnifiedDiff(hunks, filePath);
1976
- return {
1977
- filePath,
1978
- patch,
1979
- existedBefore: true,
1980
- additions,
1981
- deletions
1982
- };
1983
- }
1984
2046
  function splitLines(text) {
1985
- const raw = text.split("\n");
1986
- if (text.endsWith("\n") && raw.length > 0 && raw[raw.length - 1] === "") {
1987
- return raw.slice(0, -1);
2047
+ if (text.length === 0) {
2048
+ return { lines: [], eol: "\n", eofStyle: "no-eol" };
2049
+ }
2050
+ const crlfIdx = text.indexOf("\r\n");
2051
+ const lfIdx = text.indexOf("\n");
2052
+ let eol;
2053
+ if (crlfIdx !== -1 && (lfIdx === -1 || crlfIdx <= lfIdx)) {
2054
+ eol = "\r\n";
2055
+ } else if (lfIdx !== -1) {
2056
+ eol = "\n";
2057
+ } else {
2058
+ eol = "\n";
2059
+ }
2060
+ const lines = text.split(eol);
2061
+ let eofStyle;
2062
+ if (text.endsWith(eol)) {
2063
+ if (lines.length > 0 && lines[lines.length - 1] === "") {
2064
+ lines.pop();
2065
+ }
2066
+ eofStyle = "lf";
2067
+ } else {
2068
+ eofStyle = "no-eol";
1988
2069
  }
1989
- return raw;
2070
+ return { lines, eol, eofStyle };
1990
2071
  }
1991
2072
  function computeLineDiff(oldLines, newLines) {
1992
2073
  const N = oldLines.length;
1993
2074
  const M = newLines.length;
1994
- const dp = [];
1995
- for (let i2 = 0; i2 <= N; i2++) {
1996
- dp[i2] = new Array(M + 1).fill(0);
1997
- }
1998
- for (let i2 = 1; i2 <= N; i2++) {
1999
- for (let j2 = 1; j2 <= M; j2++) {
2000
- if (oldLines[i2 - 1] === newLines[j2 - 1]) {
2001
- dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
2002
- } else {
2003
- dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
2075
+ if (N === 0 && M === 0) return [];
2076
+ if (N === 0) return newLines.map((line) => ({ op: "add", line }));
2077
+ if (M === 0) return oldLines.map((line) => ({ op: "remove", line }));
2078
+ const trace = [];
2079
+ const maxD = N + M;
2080
+ const V = new Array(2 * maxD + 2).fill(-1);
2081
+ const offset = maxD;
2082
+ V[offset + 1] = 0;
2083
+ outer:
2084
+ for (let d = 0; d <= maxD; d++) {
2085
+ const snapshot = /* @__PURE__ */ new Map();
2086
+ const kStart = -d;
2087
+ const kEnd = d;
2088
+ for (let k = kStart; k <= kEnd; k += 2) {
2089
+ const ki = k + offset;
2090
+ let x;
2091
+ if (k === -d || k !== d && V[ki - 1] < V[ki + 1]) {
2092
+ x = V[ki + 1];
2093
+ } else {
2094
+ x = V[ki - 1] + 1;
2095
+ }
2096
+ let y = x - k;
2097
+ while (x < N && y < M && oldLines[x] === newLines[y]) {
2098
+ x++;
2099
+ y++;
2100
+ }
2101
+ V[ki] = x;
2102
+ snapshot.set(k, x);
2103
+ if (x >= N && y >= M) {
2104
+ trace.push(snapshot);
2105
+ return backtrack(trace, N, M, oldLines, newLines, offset);
2106
+ }
2004
2107
  }
2108
+ trace.push(snapshot);
2005
2109
  }
2006
- }
2110
+ return oldLines.map((line) => ({ op: "remove", line })).concat(newLines.map((line) => ({ op: "add", line })));
2111
+ }
2112
+ function backtrack(trace, N, M, oldLines, newLines, offset) {
2007
2113
  const result = [];
2008
- let i = N;
2009
- let j = M;
2010
- while (i > 0 || j > 0) {
2011
- const oldLine = i > 0 ? oldLines[i - 1] : void 0;
2012
- const newLine = j > 0 ? newLines[j - 1] : void 0;
2013
- if (i > 0 && j > 0 && oldLine !== void 0 && newLine !== void 0 && oldLine === newLine) {
2014
- result.unshift({ op: "equal", line: oldLine });
2015
- i--;
2016
- j--;
2017
- } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
2018
- result.unshift({ op: "add", line: newLines[j - 1] });
2019
- j--;
2114
+ let x = N;
2115
+ let y = M;
2116
+ for (let d = trace.length - 1; d >= 1; d--) {
2117
+ const k = x - y;
2118
+ const prevSnapshot = d > 0 ? trace[d - 1] : /* @__PURE__ */ new Map();
2119
+ const prevKDown = prevSnapshot.get(k - 1);
2120
+ const prevKUp = prevSnapshot.get(k + 1);
2121
+ let prevK;
2122
+ if (k === -d) {
2123
+ prevK = k + 1;
2124
+ } else if (k === d) {
2125
+ prevK = k - 1;
2126
+ } else if ((prevKDown ?? -1) < (prevKUp ?? -1)) {
2127
+ prevK = k + 1;
2020
2128
  } else {
2021
- result.unshift({ op: "remove", line: oldLines[i - 1] });
2022
- i--;
2129
+ prevK = k - 1;
2023
2130
  }
2131
+ const prevX = prevSnapshot.get(prevK) ?? 0;
2132
+ const prevY = prevX - prevK;
2133
+ if (prevK === k - 1) {
2134
+ while (x > prevX + 1 && y > prevY) {
2135
+ x--;
2136
+ y--;
2137
+ result.unshift({ op: "equal", line: oldLines[x] });
2138
+ }
2139
+ if (x > prevX) {
2140
+ x--;
2141
+ result.unshift({ op: "remove", line: oldLines[x] });
2142
+ }
2143
+ } else {
2144
+ while (x > prevX && y > prevY + 1) {
2145
+ x--;
2146
+ y--;
2147
+ result.unshift({ op: "equal", line: oldLines[x] });
2148
+ }
2149
+ if (y > prevY) {
2150
+ y--;
2151
+ result.unshift({ op: "add", line: newLines[y] });
2152
+ }
2153
+ }
2154
+ }
2155
+ while (x > 0 && y > 0) {
2156
+ x--;
2157
+ y--;
2158
+ result.unshift({ op: "equal", line: oldLines[x] });
2024
2159
  }
2025
2160
  return result;
2026
2161
  }
@@ -2029,21 +2164,24 @@ function groupIntoHunks(diffLines) {
2029
2164
  if (diffLines.length === 0) return [];
2030
2165
  const changeIndices = [];
2031
2166
  for (let i = 0; i < diffLines.length; i++) {
2032
- if (diffLines[i].op !== "equal") {
2167
+ const line = diffLines[i];
2168
+ if (line.op !== "equal") {
2033
2169
  changeIndices.push(i);
2034
2170
  }
2035
2171
  }
2036
2172
  if (changeIndices.length === 0) return [];
2037
2173
  const groups = [[changeIndices[0]]];
2038
2174
  for (let idx = 1; idx < changeIndices.length; idx++) {
2039
- const prevIdx = groups[groups.length - 1][groups[groups.length - 1].length - 1];
2175
+ const lastGroup = groups[groups.length - 1];
2176
+ const prevIdx = lastGroup[lastGroup.length - 1];
2040
2177
  const currIdx = changeIndices[idx];
2041
2178
  let gap = 0;
2042
2179
  for (let k = prevIdx + 1; k < currIdx; k++) {
2043
- if (diffLines[k].op === "equal") gap++;
2180
+ const line = diffLines[k];
2181
+ if (line.op === "equal") gap++;
2044
2182
  }
2045
2183
  if (gap <= 2 * CONTEXT_LINES) {
2046
- groups[groups.length - 1].push(currIdx);
2184
+ lastGroup.push(currIdx);
2047
2185
  } else {
2048
2186
  groups.push([currIdx]);
2049
2187
  }
@@ -2058,15 +2196,15 @@ function groupIntoHunks(diffLines) {
2058
2196
  let oldLine = 0;
2059
2197
  let newLine = 0;
2060
2198
  for (let i = 0; i < startIdx; i++) {
2061
- const op = diffLines[i].op;
2062
- if (op === "equal" || op === "remove") oldLine++;
2063
- if (op === "equal" || op === "add") newLine++;
2199
+ const line = diffLines[i];
2200
+ if (line.op === "equal" || line.op === "remove") oldLine++;
2201
+ if (line.op === "equal" || line.op === "add") newLine++;
2064
2202
  }
2065
2203
  let oldCount = 0;
2066
2204
  let newCount = 0;
2067
- for (const line of lines) {
2068
- if (line.op === "equal" || line.op === "remove") oldCount++;
2069
- if (line.op === "equal" || line.op === "add") newCount++;
2205
+ for (const diffLine of lines) {
2206
+ if (diffLine.op === "equal" || diffLine.op === "remove") oldCount++;
2207
+ if (diffLine.op === "equal" || diffLine.op === "add") newCount++;
2070
2208
  }
2071
2209
  hunks.push({
2072
2210
  oldStart: oldLine + 1,
@@ -2085,17 +2223,19 @@ function formatUnifiedDiff(hunks, filePath) {
2085
2223
  parts.push(`--- a/${fileName}`);
2086
2224
  parts.push(`+++ b/${fileName}`);
2087
2225
  for (const hunk of hunks) {
2088
- parts.push(`@@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@`);
2089
- for (const line of hunk.lines) {
2090
- switch (line.op) {
2226
+ parts.push(
2227
+ `@@ -${String(hunk.oldStart)},${String(hunk.oldCount)} +${String(hunk.newStart)},${String(hunk.newCount)} @@`
2228
+ );
2229
+ for (const diffLine of hunk.lines) {
2230
+ switch (diffLine.op) {
2091
2231
  case "equal":
2092
- parts.push(` ${line.line}`);
2232
+ parts.push(` ${diffLine.line}`);
2093
2233
  break;
2094
2234
  case "remove":
2095
- parts.push(`-${line.line}`);
2235
+ parts.push(`-${diffLine.line}`);
2096
2236
  break;
2097
2237
  case "add":
2098
- parts.push(`+${line.line}`);
2238
+ parts.push(`+${diffLine.line}`);
2099
2239
  break;
2100
2240
  }
2101
2241
  }
@@ -2107,7 +2247,7 @@ function formatNewFileDiff(lines, filePath) {
2107
2247
  const parts = [];
2108
2248
  parts.push(`--- /dev/null`);
2109
2249
  parts.push(`+++ b/${fileName}`);
2110
- parts.push(`@@ -0,0 +1,${lines.length} @@`);
2250
+ parts.push(`@@ -0,0 +1,${String(lines.length)} @@`);
2111
2251
  for (const line of lines) {
2112
2252
  parts.push(`+${line}`);
2113
2253
  }
@@ -2118,7 +2258,7 @@ function formatDeletedFileDiff(lines, filePath) {
2118
2258
  const parts = [];
2119
2259
  parts.push(`--- a/${fileName}`);
2120
2260
  parts.push(`+++ /dev/null`);
2121
- parts.push(`@@ -1,${lines.length} +0,0 @@`);
2261
+ parts.push(`@@ -1,${String(lines.length)} +0,0 @@`);
2122
2262
  for (const line of lines) {
2123
2263
  parts.push(`-${line}`);
2124
2264
  }
@@ -2128,10 +2268,62 @@ function extractFileName(filePath) {
2128
2268
  const normalized = filePath.replace(/\\/g, "/");
2129
2269
  return normalized.split("/").pop() ?? filePath;
2130
2270
  }
2271
+ function computeFileDiff(oldContent, newContent, filePath) {
2272
+ if (oldContent === newContent) {
2273
+ return {
2274
+ filePath,
2275
+ patch: "",
2276
+ existedBefore: oldContent.length > 0,
2277
+ additions: 0,
2278
+ deletions: 0
2279
+ };
2280
+ }
2281
+ if (oldContent.length === 0) {
2282
+ const split = splitLines(newContent);
2283
+ const patch2 = formatNewFileDiff(split.lines, filePath);
2284
+ return {
2285
+ filePath,
2286
+ patch: patch2,
2287
+ existedBefore: false,
2288
+ additions: split.lines.length,
2289
+ deletions: 0
2290
+ };
2291
+ }
2292
+ if (newContent.length === 0) {
2293
+ const split = splitLines(oldContent);
2294
+ const patch2 = formatDeletedFileDiff(split.lines, filePath);
2295
+ return {
2296
+ filePath,
2297
+ patch: patch2,
2298
+ existedBefore: true,
2299
+ additions: 0,
2300
+ deletions: split.lines.length
2301
+ };
2302
+ }
2303
+ const oldSplit = splitLines(oldContent);
2304
+ const newSplit = splitLines(newContent);
2305
+ const diffLines = computeLineDiff(oldSplit.lines, newSplit.lines);
2306
+ let additions = 0;
2307
+ let deletions = 0;
2308
+ for (const line of diffLines) {
2309
+ if (line.op === "add") additions++;
2310
+ if (line.op === "remove") deletions++;
2311
+ }
2312
+ const hunks = groupIntoHunks(diffLines);
2313
+ const patch = formatUnifiedDiff(hunks, filePath);
2314
+ return {
2315
+ filePath,
2316
+ patch,
2317
+ existedBefore: true,
2318
+ additions,
2319
+ deletions
2320
+ };
2321
+ }
2131
2322
 
2132
2323
  // src/tool/builtins/read-file.ts
2133
- import { readFile as readFile4 } from "fs/promises";
2134
- import { stat } from "fs/promises";
2324
+ import { readFile as readFile4, stat } from "fs/promises";
2325
+ import { open } from "fs/promises";
2326
+ import { relative as relative2 } from "path";
2135
2327
  var readFileSchema = {
2136
2328
  type: "object",
2137
2329
  properties: {
@@ -2139,11 +2331,11 @@ var readFileSchema = {
2139
2331
  type: "string",
2140
2332
  description: "\u6587\u4EF6\u8DEF\u5F84\uFF08\u76F8\u5BF9\u4E8E\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u6216\u7EDD\u5BF9\u8DEF\u5F84\uFF09"
2141
2333
  },
2142
- start_line: {
2334
+ startLine: {
2143
2335
  type: "number",
2144
2336
  description: "\u8D77\u59CB\u884C\u53F7\uFF08\u4ECE 1 \u5F00\u59CB\uFF09\uFF0C\u9ED8\u8BA4\u4E3A 1"
2145
2337
  },
2146
- end_line: {
2338
+ endLine: {
2147
2339
  type: "number",
2148
2340
  description: "\u7ED3\u675F\u884C\u53F7\uFF08\u5305\u542B\uFF09\uFF0C\u9ED8\u8BA4\u5230\u6587\u4EF6\u672B\u5C3E"
2149
2341
  }
@@ -2151,10 +2343,21 @@ var readFileSchema = {
2151
2343
  required: ["path"],
2152
2344
  additionalProperties: false
2153
2345
  };
2346
+ async function checkBinary(filePath) {
2347
+ const fileHandle = await open(filePath, "r");
2348
+ try {
2349
+ const buffer = Buffer.alloc(8192);
2350
+ const { bytesRead } = await fileHandle.read(buffer, 0, 8192, 0);
2351
+ return buffer.subarray(0, bytesRead).includes(0);
2352
+ } finally {
2353
+ await fileHandle.close();
2354
+ }
2355
+ }
2154
2356
  var readFileTool = {
2155
2357
  name: "read_file",
2156
- description: "\u8BFB\u53D6\u6307\u5B9A\u8DEF\u5F84\u7684\u6587\u4EF6\u5185\u5BB9\u3002\u652F\u6301\u884C\u53F7\u8303\u56F4\u9009\u62E9\uFF0C\u8F93\u51FA\u5E26\u884C\u53F7\u3002\u9002\u7528\u4E8E\u67E5\u770B\u6E90\u4EE3\u7801\u3001\u914D\u7F6E\u6587\u4EF6\u7B49\u6587\u672C\u6587\u4EF6\u3002",
2358
+ description: "\u8BFB\u53D6\u6307\u5B9A\u8DEF\u5F84\u7684\u6587\u4EF6\u5185\u5BB9\u3002\u652F\u6301\u884C\u53F7\u8303\u56F4\u9009\u62E9\uFF0C\u8F93\u51FA\u5E26\u884C\u53F7\u3002\u9002\u7528\u4E8E\u67E5\u770B\u6E90\u4EE3\u7801\u3001\u914D\u7F6E\u6587\u4EF6\u7B49\u6587\u672C\u6587\u4EF6\u3002\u81EA\u52A8\u62D2\u7EDD\u4E8C\u8FDB\u5236\u6587\u4EF6\u3002",
2157
2359
  parameters: readFileSchema,
2360
+ readOnly: true,
2158
2361
  async execute(args, ctx) {
2159
2362
  const params = args;
2160
2363
  if (!params?.path || typeof params.path !== "string") {
@@ -2171,19 +2374,46 @@ var readFileTool = {
2171
2374
  error: "FILE_TOO_LARGE"
2172
2375
  };
2173
2376
  }
2377
+ if (fileStat.isDirectory()) {
2378
+ return {
2379
+ success: false,
2380
+ data: `"${filePath}" \u662F\u4E00\u4E2A\u76EE\u5F55\uFF0C\u8BF7\u4F7F\u7528 ls \u5DE5\u5177\u67E5\u770B\u76EE\u5F55\u5185\u5BB9`,
2381
+ error: "IS_DIRECTORY"
2382
+ };
2383
+ }
2384
+ if (fileStat.size > 0) {
2385
+ const isBin = await checkBinary(filePath);
2386
+ if (isBin) {
2387
+ return {
2388
+ success: false,
2389
+ data: `"${filePath}" \u770B\u8D77\u6765\u662F\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u4E0D\u652F\u6301\u8BFB\u53D6`,
2390
+ error: "BINARY_FILE"
2391
+ };
2392
+ }
2393
+ }
2174
2394
  const content = await readFile4(filePath, "utf-8");
2175
2395
  const lines = content.split("\n");
2176
- const startLine = Math.max(1, params.start_line ?? 1) - 1;
2177
- const endLine = params.end_line ? Math.min(params.end_line, lines.length) : lines.length;
2396
+ if (lines.length > 0 && lines[lines.length - 1] === "" && content.endsWith("\n")) {
2397
+ lines.pop();
2398
+ }
2399
+ const startLine = Math.max(1, params.startLine ?? 1) - 1;
2400
+ const endLine = params.endLine ? Math.min(params.endLine, lines.length) : lines.length;
2178
2401
  const selectedLines = lines.slice(startLine, endLine);
2179
2402
  const lineNumWidth = String(endLine).length;
2180
2403
  const result = selectedLines.map((line, i) => {
2181
2404
  const lineNum = String(startLine + i + 1).padStart(lineNumWidth, " ");
2182
- return `${lineNum} | ${line}`;
2405
+ return `${lineNum}\u2192${line}`;
2183
2406
  }).join("\n");
2407
+ const remaining = lines.length - endLine;
2408
+ const tailHint = remaining > 0 ? `
2409
+
2410
+ [\u8FD8\u6709 ${remaining} \u884C\uFF1B\u4F7F\u7528 startLine=${endLine + 1} \u7EE7\u7EED\u67E5\u770B]` : "";
2411
+ const relPath = relative2(ctx.cwd, filePath).replace(/\\/g, "/");
2412
+ const rangeLabel = startLine > 0 || endLine < lines.length ? `\u7B2C ${startLine + 1}-${endLine} \u884C` : `${lines.length} \u884C`;
2184
2413
  return {
2185
2414
  success: true,
2186
- data: truncateOutput(result)
2415
+ data: truncateOutput(result) + tailHint,
2416
+ summary: `\u{1F4D6} ${relPath}\uFF08${rangeLabel}\uFF09`
2187
2417
  };
2188
2418
  } catch (err) {
2189
2419
  const message = err instanceof Error ? err.message : String(err);
@@ -2198,7 +2428,7 @@ var readFileTool = {
2198
2428
 
2199
2429
  // src/tool/builtins/write-file.ts
2200
2430
  import { writeFile as writeFile3, mkdir as mkdir4, readFile as readFile5 } from "fs/promises";
2201
- import { dirname } from "path";
2431
+ import { dirname, relative as relative3, basename } from "path";
2202
2432
  var writeFileSchema = {
2203
2433
  type: "object",
2204
2434
  properties: {
@@ -2216,8 +2446,9 @@ var writeFileSchema = {
2216
2446
  };
2217
2447
  var writeFileTool = {
2218
2448
  name: "write_file",
2219
- description: "\u521B\u5EFA\u6216\u8986\u76D6\u6587\u4EF6\u3002\u5982\u679C\u7236\u76EE\u5F55\u4E0D\u5B58\u5728\u4F1A\u81EA\u52A8\u521B\u5EFA\u3002\u9002\u7528\u4E8E\u521B\u5EFA\u65B0\u6587\u4EF6\u6216\u5B8C\u5168\u66FF\u6362\u6587\u4EF6\u5185\u5BB9\u3002",
2449
+ description: "\u521B\u5EFA\u6216\u8986\u76D6\u6587\u4EF6\u3002\u5982\u679C\u7236\u76EE\u5F55\u4E0D\u5B58\u5728\u4F1A\u81EA\u52A8\u521B\u5EFA\u3002\u9002\u7528\u4E8E\u521B\u5EFA\u65B0\u6587\u4EF6\u6216\u5B8C\u5168\u66FF\u6362\u6587\u4EF6\u5185\u5BB9\u3002\u8BF7\u52FF\u7528\u6B64\u5DE5\u5177\u521B\u5EFA _temp_\u3001_debug_ \u7B49\u7528\u4E8E\u8BCA\u65AD/\u8C03\u8BD5\u7684\u4E34\u65F6\u6587\u4EF6\u2014\u2014\u5982\u9700\u8BCA\u65AD\u8BF7\u6539\u7528 bash \u5DE5\u5177\u5185\u8054\u811A\u672C\uFF08node -e\uFF09\u3002",
2220
2450
  parameters: writeFileSchema,
2451
+ readOnly: false,
2221
2452
  async execute(args, ctx) {
2222
2453
  const params = args;
2223
2454
  if (!params?.path || typeof params.path !== "string") {
@@ -2244,9 +2475,12 @@ var writeFileTool = {
2244
2475
  const byteSize = Buffer.byteLength(content, "utf-8");
2245
2476
  const action = existedBefore ? "\u5DF2\u4FEE\u6539" : "\u5DF2\u521B\u5EFA";
2246
2477
  const diffSummary = existedBefore ? `\uFF0C+${diff.additions} -${diff.deletions}` : `\uFF0C+${diff.additions} \u884C\uFF08\u65B0\u5EFA\uFF09`;
2478
+ const fileName = basename(filePath);
2479
+ const summary = existedBefore ? `\u{1F4DD} \u4FEE\u6539: ${fileName} (+${diff.additions} -${diff.deletions})` : `\u{1F4DD} \u65B0\u5EFA: ${fileName} (+${diff.additions} \u884C)`;
2247
2480
  return {
2248
2481
  success: true,
2249
- data: `\u6587\u4EF6${action}\uFF1A${filePath}\uFF08${lineCount} \u884C\uFF0C${byteSize} \u5B57\u8282${diffSummary}\uFF09`,
2482
+ data: `\u6587\u4EF6${action}\uFF1A${relative3(ctx.cwd, filePath).replace(/\\/g, "/")}\uFF08${lineCount} \u884C\uFF0C${byteSize} \u5B57\u8282${diffSummary}\uFF09`,
2483
+ summary,
2250
2484
  diff
2251
2485
  };
2252
2486
  } catch (err) {
@@ -2262,6 +2496,7 @@ var writeFileTool = {
2262
2496
 
2263
2497
  // src/tool/builtins/edit-file.ts
2264
2498
  import { readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
2499
+ import { basename as basename2 } from "path";
2265
2500
  var editFileSchema = {
2266
2501
  type: "object",
2267
2502
  properties: {
@@ -2285,6 +2520,7 @@ var editFileTool = {
2285
2520
  name: "edit_file",
2286
2521
  description: "\u5BF9\u6587\u4EF6\u8FDB\u884C\u7CBE\u786E\u5B57\u7B26\u4E32\u66FF\u6362\u3002\u67E5\u627E\u6587\u4EF6\u4E2D\u7684 old_text \u5E76\u66FF\u6362\u4E3A new_text\u3002\u5982\u679C old_text \u51FA\u73B0\u591A\u6B21\u6216\u672A\u627E\u5230\u5219\u62A5\u9519\u3002\u9002\u7528\u4E8E\u5C0F\u8303\u56F4\u7CBE\u786E\u4FEE\u6539\u3002",
2287
2522
  parameters: editFileSchema,
2523
+ readOnly: false,
2288
2524
  async execute(args, ctx) {
2289
2525
  const params = args;
2290
2526
  if (!params?.path || typeof params.path !== "string") {
@@ -2324,12 +2560,14 @@ var editFileTool = {
2324
2560
  const oldLines = params.old_text.split("\n").length;
2325
2561
  const newLines = params.new_text.split("\n").length;
2326
2562
  const diffSummary = `+${diff.additions} -${diff.deletions}`;
2563
+ const summary = `\u{1F4DD} \u4FEE\u6539: ${basename2(filePath)} (+${diff.additions} -${diff.deletions})`;
2327
2564
  return {
2328
2565
  success: true,
2329
2566
  data: `\u6587\u4EF6\u5DF2\u7F16\u8F91\uFF1A${filePath}
2330
2567
  \u66FF\u6362\u4F4D\u7F6E\uFF1A\u7B2C ${startLine} \u884C
2331
2568
  ${oldLines} \u884C \u2192 ${newLines} \u884C
2332
2569
  \u53D8\u66F4\uFF1A${diffSummary}`,
2570
+ summary,
2333
2571
  diff
2334
2572
  };
2335
2573
  } catch (err) {
@@ -2343,6 +2581,236 @@ ${oldLines} \u884C \u2192 ${newLines} \u884C
2343
2581
  }
2344
2582
  };
2345
2583
 
2584
+ // src/tool/builtins/multi-edit.ts
2585
+ import { readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
2586
+ import { basename as basename3 } from "path";
2587
+ var multiEditSchema = {
2588
+ type: "object",
2589
+ properties: {
2590
+ path: {
2591
+ type: "string",
2592
+ description: "\u6587\u4EF6\u8DEF\u5F84\uFF08\u76F8\u5BF9\u4E8E\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u6216\u7EDD\u5BF9\u8DEF\u5F84\uFF09"
2593
+ },
2594
+ edits: {
2595
+ type: "array",
2596
+ description: "\u6709\u5E8F\u7684\u7F16\u8F91\u6B65\u9AA4\u5217\u8868\u3002\u6BCF\u4E2A\u6B65\u9AA4\u5305\u542B oldText\uFF08\u7CBE\u786E\u5339\u914D\uFF09\u3001newText\uFF08\u66FF\u6362\u5185\u5BB9\uFF09\u548C\u53EF\u9009\u7684 replaceAll\uFF08\u662F\u5426\u66FF\u6362\u6240\u6709\u5339\u914D\u9879\uFF09",
2597
+ items: {
2598
+ type: "object",
2599
+ properties: {
2600
+ oldText: { type: "string", description: "\u8981\u67E5\u627E\u7684\u539F\u59CB\u6587\u672C\uFF08\u7CBE\u786E\u5339\u914D\uFF09" },
2601
+ newText: { type: "string", description: "\u66FF\u6362\u540E\u7684\u65B0\u6587\u672C" },
2602
+ replaceAll: { type: "boolean", description: "\u662F\u5426\u66FF\u6362\u6240\u6709\u5339\u914D\u9879\uFF0C\u9ED8\u8BA4 false" }
2603
+ },
2604
+ required: ["oldText", "newText"],
2605
+ additionalProperties: false
2606
+ }
2607
+ }
2608
+ },
2609
+ required: ["path", "edits"],
2610
+ additionalProperties: false
2611
+ };
2612
+ var multiEditTool = {
2613
+ name: "multi_edit",
2614
+ description: "\u5BF9\u6587\u4EF6\u8FDB\u884C\u539F\u5B50\u6279\u91CF\u66FF\u6362\u7F16\u8F91\u3002\u5728\u4E00\u4E2A\u8BF7\u6C42\u4E2D\u6267\u884C\u591A\u4E2A\u7CBE\u786E\u66FF\u6362\uFF0C\u4EFB\u4E00\u5931\u8D25\u5219\u5168\u90E8\u56DE\u6EDA\u3002\u652F\u6301 replaceAll \u53C2\u6570\u505A\u5168\u5C40\u66FF\u6362\u3002\u9002\u7528\u4E8E\u9700\u8981\u540C\u65F6\u4FEE\u6539\u6587\u4EF6\u591A\u5904\u7684\u573A\u666F\u3002",
2615
+ parameters: multiEditSchema,
2616
+ readOnly: false,
2617
+ async execute(args, ctx) {
2618
+ const params = args;
2619
+ if (!params?.path || typeof params.path !== "string") {
2620
+ return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 path", error: "INVALID_ARGS" };
2621
+ }
2622
+ if (!Array.isArray(params.edits) || params.edits.length === 0) {
2623
+ return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 edits\uFF08\u975E\u7A7A\u6570\u7EC4\uFF09", error: "INVALID_ARGS" };
2624
+ }
2625
+ const filePath = resolvePath(params.path, ctx.cwd);
2626
+ try {
2627
+ const originalContent = await readFile7(filePath, "utf-8");
2628
+ let currentContent = originalContent;
2629
+ for (let idx = 0; idx < params.edits.length; idx++) {
2630
+ const step = params.edits[idx];
2631
+ if (typeof step.oldText !== "string") {
2632
+ return {
2633
+ success: false,
2634
+ data: `\u7B2C ${idx + 1} \u6B65\uFF1A\u7F3A\u5C11\u6709\u6548\u7684 oldText`,
2635
+ error: "INVALID_STEP_ARGS"
2636
+ };
2637
+ }
2638
+ if (step.replaceAll) {
2639
+ let count = 0;
2640
+ let pos = -1;
2641
+ while ((pos = currentContent.indexOf(step.oldText, pos + 1)) !== -1) {
2642
+ count++;
2643
+ }
2644
+ if (count === 0) {
2645
+ return {
2646
+ success: false,
2647
+ data: `\u7B2C ${idx + 1} \u6B65\uFF1A\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u6587\u672C\uFF0C\u8BF7\u786E\u8BA4 oldText \u4E0E\u6587\u4EF6\u5185\u5BB9\u5B8C\u5168\u4E00\u81F4`,
2648
+ error: "TEXT_NOT_FOUND"
2649
+ };
2650
+ }
2651
+ currentContent = currentContent.split(step.oldText).join(step.newText);
2652
+ } else {
2653
+ const firstIdx = currentContent.indexOf(step.oldText);
2654
+ if (firstIdx === -1) {
2655
+ return {
2656
+ success: false,
2657
+ data: `\u7B2C ${idx + 1} \u6B65\uFF1A\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u6587\u672C\u3002\u8BF7\u786E\u8BA4 oldText \u4E0E\u6587\u4EF6\u5185\u5BB9\u5B8C\u5168\u4E00\u81F4\uFF08\u5305\u62EC\u7F29\u8FDB\u548C\u7A7A\u683C\uFF09\u3002`,
2658
+ error: "TEXT_NOT_FOUND"
2659
+ };
2660
+ }
2661
+ const secondIdx = currentContent.indexOf(step.oldText, firstIdx + 1);
2662
+ if (secondIdx !== -1) {
2663
+ return {
2664
+ success: false,
2665
+ data: `\u7B2C ${idx + 1} \u6B65\uFF1A\u8981\u66FF\u6362\u7684\u6587\u672C\u5728\u6587\u4EF6\u4E2D\u51FA\u73B0\u591A\u6B21\uFF0C\u8BF7\u63D0\u4F9B\u66F4\u591A\u4E0A\u4E0B\u6587\u4EE5\u7CBE\u786E\u5B9A\u4F4D\uFF0C\u6216\u8BBE\u7F6E replaceAll=true`,
2666
+ error: "TEXT_MULTIPLE_MATCHES"
2667
+ };
2668
+ }
2669
+ currentContent = currentContent.replace(step.oldText, step.newText);
2670
+ }
2671
+ }
2672
+ await writeFile5(filePath, currentContent, "utf-8");
2673
+ const diff = computeFileDiff(originalContent, currentContent, filePath);
2674
+ diff.existedBefore = true;
2675
+ return {
2676
+ success: true,
2677
+ data: `\u6587\u4EF6\u5DF2\u7F16\u8F91\uFF1A${filePath}
2678
+ \u5171\u6267\u884C ${params.edits.length} \u6B65\u66FF\u6362
2679
+ \u53D8\u66F4\uFF1A+${diff.additions} -${diff.deletions}`,
2680
+ summary: `\u{1F4DD} \u4FEE\u6539: ${basename3(filePath)} (${params.edits.length} \u6B65, +${diff.additions} -${diff.deletions})`,
2681
+ diff
2682
+ };
2683
+ } catch (err) {
2684
+ const message = err instanceof Error ? err.message : String(err);
2685
+ return {
2686
+ success: false,
2687
+ data: `\u6279\u91CF\u7F16\u8F91\u5931\u8D25\uFF1A${message}`,
2688
+ error: "MULTI_EDIT_ERROR"
2689
+ };
2690
+ }
2691
+ }
2692
+ };
2693
+
2694
+ // src/tool/builtins/delete-range.ts
2695
+ import { readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
2696
+ import { basename as basename4 } from "path";
2697
+ var deleteRangeSchema = {
2698
+ type: "object",
2699
+ properties: {
2700
+ path: {
2701
+ type: "string",
2702
+ description: "\u6587\u4EF6\u8DEF\u5F84\uFF08\u76F8\u5BF9\u4E8E\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u6216\u7EDD\u5BF9\u8DEF\u5F84\uFF09"
2703
+ },
2704
+ startAnchor: {
2705
+ type: "string",
2706
+ description: "\u5220\u9664\u8303\u56F4\u7684\u8D77\u59CB\u6807\u8BB0\u884C\u5185\u5BB9\uFF08\u5FC5\u987B\u5728\u6587\u4EF6\u4E2D\u552F\u4E00\uFF0C\u7CBE\u786E\u5339\u914D\u6574\u884C\uFF09"
2707
+ },
2708
+ endAnchor: {
2709
+ type: "string",
2710
+ description: "\u5220\u9664\u8303\u56F4\u7684\u7ED3\u675F\u6807\u8BB0\u884C\u5185\u5BB9\uFF08\u5FC5\u987B\u5728\u6587\u4EF6\u4E2D\u552F\u4E00\uFF0C\u7CBE\u786E\u5339\u914D\u6574\u884C\uFF0C\u987B\u5728 start_anchor \u4E4B\u540E\uFF09"
2711
+ },
2712
+ inclusive: {
2713
+ type: "boolean",
2714
+ description: "\u662F\u5426\u5305\u542B\u951A\u70B9\u884C\u672C\u8EAB\uFF0C\u9ED8\u8BA4 false\uFF08\u53EA\u5220\u9664\u4E24\u884C\u4E4B\u95F4\u7684\u5185\u5BB9\uFF09"
2715
+ }
2716
+ },
2717
+ required: ["path", "startAnchor", "endAnchor"],
2718
+ additionalProperties: false
2719
+ };
2720
+ function findUniqueLine(lines, anchor, label) {
2721
+ const matches = [];
2722
+ for (let i = 0; i < lines.length; i++) {
2723
+ if (lines[i] === anchor) {
2724
+ matches.push(i);
2725
+ }
2726
+ }
2727
+ if (matches.length === 0) {
2728
+ return { error: `\u672A\u627E\u5230 "${label}" \u951A\u70B9\u884C\uFF0C\u8BF7\u786E\u8BA4\u5185\u5BB9\u5B8C\u5168\u4E00\u81F4` };
2729
+ }
2730
+ if (matches.length > 1) {
2731
+ return { error: `"${label}" \u951A\u70B9\u884C\u5728\u6587\u4EF6\u4E2D\u51FA\u73B0 ${matches.length} \u6B21\uFF0C\u8BF7\u4F7F\u7528\u66F4\u552F\u4E00\u7684\u884C\u5185\u5BB9` };
2732
+ }
2733
+ return { line: matches[0] };
2734
+ }
2735
+ var deleteRangeTool = {
2736
+ name: "delete_range",
2737
+ description: "\u5220\u9664\u6587\u4EF6\u4E2D\u4E24\u4E2A\u951A\u70B9\u884C\u4E4B\u95F4\u7684\u5185\u5BB9\u3002\u901A\u8FC7\u4E24\u4E2A\u552F\u4E00\u7684\u884C\u5185\u5BB9\u7CBE\u786E\u5B9A\u4F4D\u8303\u56F4\u3002\u9002\u7528\u4E8E\u5220\u9664\u65B9\u6CD5\u3001\u4EE3\u7801\u5757\u3001\u914D\u7F6E\u6BB5\u7B49\u3002",
2738
+ parameters: deleteRangeSchema,
2739
+ readOnly: false,
2740
+ async execute(args, ctx) {
2741
+ const params = args;
2742
+ if (!params?.path || typeof params.path !== "string") {
2743
+ return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 path", error: "INVALID_ARGS" };
2744
+ }
2745
+ if (typeof params.startAnchor !== "string") {
2746
+ return { success: false, data: "\u7F3A\u5C11\u6709\u6548\u53C2\u6570 startAnchor", error: "INVALID_ARGS" };
2747
+ }
2748
+ if (typeof params.endAnchor !== "string") {
2749
+ return { success: false, data: "\u7F3A\u5C11\u6709\u6548\u53C2\u6570 endAnchor", error: "INVALID_ARGS" };
2750
+ }
2751
+ const filePath = resolvePath(params.path, ctx.cwd);
2752
+ const inclusive = params.inclusive ?? false;
2753
+ try {
2754
+ const content = await readFile8(filePath, "utf-8");
2755
+ const lines = content.split("\n");
2756
+ const startResult = findUniqueLine(lines, params.startAnchor, "start_anchor");
2757
+ if ("error" in startResult) {
2758
+ return { success: false, data: startResult.error, error: "ANCHOR_NOT_FOUND" };
2759
+ }
2760
+ const endResult = findUniqueLine(lines, params.endAnchor, "end_anchor");
2761
+ if ("error" in endResult) {
2762
+ return { success: false, data: endResult.error, error: "ANCHOR_NOT_FOUND" };
2763
+ }
2764
+ const startLine = startResult.line;
2765
+ const endLine = endResult.line;
2766
+ if (startLine >= endLine) {
2767
+ return {
2768
+ success: false,
2769
+ data: "end_anchor \u5FC5\u987B\u5728 start_anchor \u4E4B\u540E",
2770
+ error: "ANCHOR_ORDER_ERROR"
2771
+ };
2772
+ }
2773
+ const rangeStart = inclusive ? startLine : startLine + 1;
2774
+ const rangeEnd = inclusive ? endLine : endLine - 1;
2775
+ if (rangeStart > rangeEnd) {
2776
+ return {
2777
+ success: false,
2778
+ data: "\u5220\u9664\u8303\u56F4\u4E3A\u7A7A\uFF08\u4E24\u884C\u951A\u70B9\u76F8\u90BB\u4E14 inclusive=false\uFF09",
2779
+ error: "EMPTY_RANGE"
2780
+ };
2781
+ }
2782
+ const newLines = [...lines.slice(0, rangeStart), ...lines.slice(rangeEnd + 1)];
2783
+ const newContent = newLines.join("\n");
2784
+ let writeContent = newContent;
2785
+ if (content.endsWith("\n") && !newContent.endsWith("\n")) {
2786
+ writeContent = newContent + "\n";
2787
+ } else if (content.endsWith("\r\n") && !newContent.endsWith("\r\n") && !newContent.endsWith("\n")) {
2788
+ writeContent = newContent + "\r\n";
2789
+ }
2790
+ await writeFile6(filePath, writeContent, "utf-8");
2791
+ const diff = computeFileDiff(content, writeContent, filePath);
2792
+ diff.existedBefore = true;
2793
+ const deletedLines = rangeEnd - rangeStart + 1;
2794
+ const summary = `\u{1F4DD} \u4FEE\u6539: ${basename4(filePath)} (\u5220 ${deletedLines} \u884C, +${diff.additions} -${diff.deletions})`;
2795
+ return {
2796
+ success: true,
2797
+ data: `\u6587\u4EF6\u5DF2\u7F16\u8F91\uFF1A${filePath}
2798
+ \u5220\u9664\u884C\u8303\u56F4\uFF1A\u7B2C ${rangeStart + 1} \u884C ~ \u7B2C ${rangeEnd + 1} \u884C\uFF08\u5171 ${deletedLines} \u884C\uFF09
2799
+ \u53D8\u66F4\uFF1A+${diff.additions} -${diff.deletions}`,
2800
+ summary,
2801
+ diff
2802
+ };
2803
+ } catch (err) {
2804
+ const message = err instanceof Error ? err.message : String(err);
2805
+ return {
2806
+ success: false,
2807
+ data: `\u5220\u9664\u8303\u56F4\u5931\u8D25\uFF1A${message}`,
2808
+ error: "DELETE_RANGE_ERROR"
2809
+ };
2810
+ }
2811
+ }
2812
+ };
2813
+
2346
2814
  // src/tool/builtins/bash.ts
2347
2815
  import process3 from "process";
2348
2816
  var isWindows2 = process3.platform === "win32";
@@ -2365,6 +2833,8 @@ var bashTool = {
2365
2833
  name: "bash",
2366
2834
  description: "\u5728 shell \u4E2D\u6267\u884C\u547D\u4EE4\u3002\u8FD4\u56DE\u6807\u51C6\u8F93\u51FA\u3001\u6807\u51C6\u9519\u8BEF\u548C\u9000\u51FA\u7801\u3002\u652F\u6301\u8D85\u65F6\u63A7\u5236\u548C\u4FE1\u53F7\u4E2D\u6B62\u3002\u9002\u7528\u4E8E\u8FD0\u884C\u6784\u5EFA\u3001\u6D4B\u8BD5\u3001Git \u64CD\u4F5C\u7B49\u547D\u4EE4\u3002",
2367
2835
  parameters: bashSchema,
2836
+ readOnly: false,
2837
+ // bash 有副作用,不能并行执行
2368
2838
  async execute(args, ctx) {
2369
2839
  const params = args;
2370
2840
  if (!params?.command || typeof params.command !== "string") {
@@ -2393,10 +2863,13 @@ ${truncateOutput(result.stderr)}`);
2393
2863
  }
2394
2864
  const success = result.exitCode === 0;
2395
2865
  const output = parts.length > 0 ? parts.join("\n") : "(\u65E0\u8F93\u51FA)";
2866
+ const cmdPreview = params.command.length > 60 ? params.command.slice(0, 57) + "..." : params.command;
2867
+ const summary = `\u{1F527} $ ${cmdPreview}\uFF08exit ${result.exitCode ?? "\u672A\u77E5"}\uFF09`;
2396
2868
  return {
2397
2869
  success,
2398
2870
  data: `${output}
2399
2871
  [\u9000\u51FA\u7801: ${result.exitCode ?? "\u672A\u77E5"}]`,
2872
+ summary,
2400
2873
  error: success ? void 0 : `EXIT_CODE_${result.exitCode ?? "UNKNOWN"}`
2401
2874
  };
2402
2875
  } catch (err) {
@@ -2412,7 +2885,7 @@ ${truncateOutput(result.stderr)}`);
2412
2885
 
2413
2886
  // src/tool/builtins/glob.ts
2414
2887
  import { readdir as readdir2, stat as stat2 } from "fs/promises";
2415
- import { join as join4, relative as relative2, isAbsolute as isAbsolute2 } from "path";
2888
+ import { join as join4, relative as relative4, isAbsolute as isAbsolute2 } from "path";
2416
2889
  var globSchema = {
2417
2890
  type: "object",
2418
2891
  properties: {
@@ -2453,7 +2926,7 @@ async function walkDir(dir, baseDir) {
2453
2926
  continue;
2454
2927
  }
2455
2928
  const fullPath = join4(dir, entry.name);
2456
- const relPath = relative2(baseDir, fullPath);
2929
+ const relPath = relative4(baseDir, fullPath);
2457
2930
  if (entry.isDirectory()) {
2458
2931
  results.push(...await walkDir(fullPath, baseDir));
2459
2932
  } else {
@@ -2466,6 +2939,7 @@ var globTool = {
2466
2939
  name: "glob",
2467
2940
  description: "\u6309\u6A21\u5F0F\u641C\u7D22\u6587\u4EF6\u8DEF\u5F84\u3002\u652F\u6301 *\uFF08\u5339\u914D\u6587\u4EF6\u540D\u90E8\u5206\uFF09\u548C **\uFF08\u5339\u914D\u591A\u5C42\u76EE\u5F55\uFF09\u901A\u914D\u7B26\u3002\u81EA\u52A8\u8DF3\u8FC7 node_modules \u548C .git \u76EE\u5F55\u3002",
2468
2941
  parameters: globSchema,
2942
+ readOnly: true,
2469
2943
  async execute(args, ctx) {
2470
2944
  const params = args;
2471
2945
  if (!params?.pattern || typeof params.pattern !== "string") {
@@ -2483,7 +2957,8 @@ var globTool = {
2483
2957
  if (matched.length === 0) {
2484
2958
  return {
2485
2959
  success: true,
2486
- data: `\u672A\u627E\u5230\u5339\u914D "${params.pattern}" \u7684\u6587\u4EF6`
2960
+ data: `\u672A\u627E\u5230\u5339\u914D "${params.pattern}" \u7684\u6587\u4EF6`,
2961
+ summary: `${params.pattern} \u2192 0 \u4E2A\u6587\u4EF6`
2487
2962
  };
2488
2963
  }
2489
2964
  const limit = 200;
@@ -2494,7 +2969,8 @@ var globTool = {
2494
2969
  ... \u5171 ${matched.length} \u4E2A\u6587\u4EF6\uFF0C\u53EA\u663E\u793A\u524D ${limit} \u4E2A` : "";
2495
2970
  return {
2496
2971
  success: true,
2497
- data: truncateOutput(output + suffix)
2972
+ data: truncateOutput(output + suffix),
2973
+ summary: `${params.pattern} \u2192 ${matched.length} \u4E2A\u6587\u4EF6`
2498
2974
  };
2499
2975
  } catch (err) {
2500
2976
  const message = err instanceof Error ? err.message : String(err);
@@ -2508,8 +2984,8 @@ var globTool = {
2508
2984
  };
2509
2985
 
2510
2986
  // src/tool/builtins/grep.ts
2511
- import { readdir as readdir3, readFile as readFile7, stat as stat3 } from "fs/promises";
2512
- import { join as join5, relative as relative3, isAbsolute as isAbsolute3 } from "path";
2987
+ import { readdir as readdir3, readFile as readFile9, stat as stat3 } from "fs/promises";
2988
+ import { join as join5, relative as relative5, isAbsolute as isAbsolute3 } from "path";
2513
2989
  var grepSchema = {
2514
2990
  type: "object",
2515
2991
  properties: {
@@ -2566,6 +3042,7 @@ var grepTool = {
2566
3042
  name: "grep",
2567
3043
  description: "\u5728\u6587\u4EF6\u5185\u5BB9\u4E2D\u641C\u7D22\u6B63\u5219\u8868\u8FBE\u5F0F\u3002\u8FD4\u56DE\u5339\u914D\u884C\u7684\u6587\u4EF6\u8DEF\u5F84\u3001\u884C\u53F7\u548C\u5185\u5BB9\u3002\u652F\u6301\u5927\u5C0F\u5199\u654F\u611F\u3001\u6587\u4EF6\u6269\u5C55\u540D\u8FC7\u6EE4\u3002",
2568
3044
  parameters: grepSchema,
3045
+ readOnly: true,
2569
3046
  async execute(args, ctx) {
2570
3047
  const params = args;
2571
3048
  if (!params?.pattern || typeof params.pattern !== "string") {
@@ -2584,9 +3061,9 @@ var grepTool = {
2584
3061
  const matches = [];
2585
3062
  for (const filePath of files) {
2586
3063
  try {
2587
- const content = await readFile7(filePath, "utf-8");
3064
+ const content = await readFile9(filePath, "utf-8");
2588
3065
  const lines = content.split("\n");
2589
- const relPath = relative3(searchDir, filePath);
3066
+ const relPath = relative5(searchDir, filePath);
2590
3067
  for (let i = 0; i < lines.length; i++) {
2591
3068
  if (regex.test(lines[i])) {
2592
3069
  regex.lastIndex = 0;
@@ -2606,13 +3083,17 @@ var grepTool = {
2606
3083
  if (matches.length === 0) {
2607
3084
  return {
2608
3085
  success: true,
2609
- data: `\u672A\u627E\u5230\u5339\u914D "${params.pattern}" \u7684\u5185\u5BB9`
3086
+ data: `\u672A\u627E\u5230\u5339\u914D "${params.pattern}" \u7684\u5185\u5BB9`,
3087
+ summary: `\u{1F50D} "${params.pattern}" \u2192 0 \u6761\u547D\u4E2D`
2610
3088
  };
2611
3089
  }
2612
3090
  const output = matches.map((m) => `${m.file}:${m.line}: ${m.content}`).join("\n");
3091
+ const fileSet = new Set(matches.map((m) => m.file));
3092
+ const summary = `\u{1F50D} "${params.pattern}" \u2192 ${matches.length} \u6761\u547D\u4E2D / ${fileSet.size} \u4E2A\u6587\u4EF6`;
2613
3093
  return {
2614
3094
  success: true,
2615
- data: truncateOutput(output)
3095
+ data: truncateOutput(output),
3096
+ summary
2616
3097
  };
2617
3098
  } catch (err) {
2618
3099
  const message = err instanceof Error ? err.message : String(err);
@@ -2627,7 +3108,7 @@ var grepTool = {
2627
3108
 
2628
3109
  // src/tool/builtins/ls.ts
2629
3110
  import { readdir as readdir4, stat as stat4 } from "fs/promises";
2630
- import { join as join6 } from "path";
3111
+ import { join as join6, relative as relative6 } from "path";
2631
3112
  var lsSchema = {
2632
3113
  type: "object",
2633
3114
  properties: {
@@ -2647,6 +3128,7 @@ var lsTool = {
2647
3128
  name: "ls",
2648
3129
  description: "\u5217\u51FA\u76EE\u5F55\u5185\u5BB9\u3002\u663E\u793A\u6761\u76EE\u7C7B\u578B\uFF08\u6587\u4EF6/\u76EE\u5F55/\u94FE\u63A5\uFF09\u548C\u5927\u5C0F\u3002\u53EF\u9009\u62E9\u662F\u5426\u663E\u793A\u9690\u85CF\u6587\u4EF6\u3002",
2649
3130
  parameters: lsSchema,
3131
+ readOnly: true,
2650
3132
  async execute(args, ctx) {
2651
3133
  const params = args ?? {};
2652
3134
  const dirPath = params.path ? resolvePath(params.path, ctx.cwd) : ctx.cwd;
@@ -2682,12 +3164,14 @@ var lsTool = {
2682
3164
  lines.push(`${typeLabel} ${entry.name}${sizeStr ? ` (${sizeStr})` : ""}`);
2683
3165
  }
2684
3166
  if (lines.length === 0) {
2685
- return { success: true, data: "\u76EE\u5F55\u4E3A\u7A7A" };
3167
+ return { success: true, data: "\u76EE\u5F55\u4E3A\u7A7A", summary: `\u{1F4C2} ${relative6(ctx.cwd, dirPath).replace(/\\/g, "/")}\uFF08\u7A7A\uFF09` };
2686
3168
  }
3169
+ const relPath = relative6(ctx.cwd, dirPath).replace(/\\/g, "/");
2687
3170
  return {
2688
3171
  success: true,
2689
3172
  data: truncateOutput(`\u76EE\u5F55\uFF1A${dirPath}
2690
- ${lines.join("\n")}`)
3173
+ ${lines.join("\n")}`),
3174
+ summary: `\u{1F4C2} ${relPath}\uFF08${lines.length} \u9879\uFF09`
2691
3175
  };
2692
3176
  } catch (err) {
2693
3177
  const message = err instanceof Error ? err.message : String(err);
@@ -2733,6 +3217,7 @@ var fetchTool = {
2733
3217
  name: "fetch",
2734
3218
  description: "\u53D1\u8D77 HTTP \u8BF7\u6C42\u5E76\u8FD4\u56DE\u54CD\u5E94\u5185\u5BB9\u3002\u652F\u6301\u81EA\u5B9A\u4E49\u65B9\u6CD5\u548C\u8BF7\u6C42\u5934\u3002\u9002\u7528\u4E8E\u83B7\u53D6\u7F51\u9875\u5185\u5BB9\u3001API \u8C03\u7528\u7B49\u573A\u666F\u3002",
2735
3219
  parameters: fetchSchema,
3220
+ readOnly: true,
2736
3221
  async execute(args, ctx) {
2737
3222
  const params = args;
2738
3223
  if (!params?.url || typeof params.url !== "string") {
@@ -2764,9 +3249,12 @@ var fetchTool = {
2764
3249
  const header = `\u72B6\u6001: ${statusText}
2765
3250
  \u5185\u5BB9\u7C7B\u578B: ${contentType}`;
2766
3251
  const separator = body.length > 0 ? "\n---\n" : "";
3252
+ const urlPreview = params.url.length > 60 ? params.url.slice(0, 57) + "..." : params.url;
3253
+ const summary = `\u{1F310} ${method} ${urlPreview} \u2192 ${response.status}`;
2767
3254
  return {
2768
3255
  success: response.ok,
2769
3256
  data: `${header}${separator}${truncatedBody}`,
3257
+ summary,
2770
3258
  error: response.ok ? void 0 : `HTTP_${response.status}`
2771
3259
  };
2772
3260
  } catch (err) {
@@ -2791,6 +3279,8 @@ var builtinTools = [
2791
3279
  readFileTool,
2792
3280
  writeFileTool,
2793
3281
  editFileTool,
3282
+ multiEditTool,
3283
+ deleteRangeTool,
2794
3284
  bashTool,
2795
3285
  globTool,
2796
3286
  grepTool,
@@ -3314,12 +3804,14 @@ function ChatSession({
3314
3804
  currentContentRef.current = "";
3315
3805
  setCurrentToolCalls([]);
3316
3806
  currentToolCallsRef.current = [];
3807
+ const r = event.result;
3808
+ const line = r.success ? r.summary ?? `\u2705 ${event.name}: ${r.data.slice(0, 500)}${r.data.length > 500 ? "..." : ""}` : `\u274C ${event.name}: ${r.error ?? "\u6267\u884C\u5931\u8D25"}`;
3317
3809
  setDisplayMessages((prev) => [
3318
3810
  ...prev,
3319
3811
  {
3320
3812
  role: "tool",
3321
- content: event.result.success ? `\u2705 ${event.name}: ${event.result.data.slice(0, 500)}${event.result.data.length > 500 ? "..." : ""}` : `\u274C ${event.name}: ${event.result.error ?? "\u6267\u884C\u5931\u8D25"}`,
3322
- diff: event.result.diff
3813
+ content: line,
3814
+ diff: r.diff
3323
3815
  }
3324
3816
  ]);
3325
3817
  break;
@@ -4735,7 +5227,7 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
4735
5227
 
4736
5228
  // src/utils/scan-files.ts
4737
5229
  import { readdir as readdir5 } from "fs/promises";
4738
- import { join as join8, relative as relative4 } from "path";
5230
+ import { join as join8, relative as relative7 } from "path";
4739
5231
  var IGNORE_DIRS = /* @__PURE__ */ new Set([
4740
5232
  "node_modules",
4741
5233
  ".git",
@@ -4800,7 +5292,7 @@ async function scanProjectFiles(baseDir, dir) {
4800
5292
  } else if (entry.isFile()) {
4801
5293
  const ext = name.slice(name.lastIndexOf(".")).toLowerCase();
4802
5294
  if (SOURCE_EXTS.has(ext)) {
4803
- files.push(relative4(baseDir, join8(currentDir, name)));
5295
+ files.push(relative7(baseDir, join8(currentDir, name)));
4804
5296
  }
4805
5297
  }
4806
5298
  }
@@ -4969,7 +5461,7 @@ compdef _dskcode_completion dskcode`);
4969
5461
  const globalConfigPath = join9(home, ".dskcode", "settings.json");
4970
5462
  let globalConfigHasStock = false;
4971
5463
  try {
4972
- const raw = await readFile8(globalConfigPath, "utf-8");
5464
+ const raw = await readFile10(globalConfigPath, "utf-8");
4973
5465
  const parsed = JSON.parse(raw);
4974
5466
  const stock = parsed.stock;
4975
5467
  globalConfigHasStock = Array.isArray(stock?.symbols) && stock.symbols.length > 0;