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 +663 -171
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
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:
|
|
1783
|
-
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
|
-
|
|
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
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
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
|
|
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
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
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
|
|
2009
|
-
let
|
|
2010
|
-
|
|
2011
|
-
const
|
|
2012
|
-
const
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2180
|
+
const line = diffLines[k];
|
|
2181
|
+
if (line.op === "equal") gap++;
|
|
2044
2182
|
}
|
|
2045
2183
|
if (gap <= 2 * CONTEXT_LINES) {
|
|
2046
|
-
|
|
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
|
|
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
|
|
2068
|
-
if (
|
|
2069
|
-
if (
|
|
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(
|
|
2089
|
-
|
|
2090
|
-
|
|
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(` ${
|
|
2232
|
+
parts.push(` ${diffLine.line}`);
|
|
2093
2233
|
break;
|
|
2094
2234
|
case "remove":
|
|
2095
|
-
parts.push(`-${
|
|
2235
|
+
parts.push(`-${diffLine.line}`);
|
|
2096
2236
|
break;
|
|
2097
2237
|
case "add":
|
|
2098
|
-
parts.push(`+${
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2177
|
-
|
|
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}
|
|
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
|
|
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 =
|
|
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
|
|
2512
|
-
import { join as join5, relative as
|
|
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
|
|
3064
|
+
const content = await readFile9(filePath, "utf-8");
|
|
2588
3065
|
const lines = content.split("\n");
|
|
2589
|
-
const relPath =
|
|
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:
|
|
3322
|
-
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
|
|
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(
|
|
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
|
|
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;
|