@linghun/tools 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/dist/chunk-2U4BZXAI.js +6 -0
- package/dist/chunk-3RP665TH.js +6 -0
- package/dist/chunk-4567YAXM.js +13 -0
- package/dist/chunk-4EZHIDZ6.js +13 -0
- package/dist/chunk-4UHQWBFA.js +167 -0
- package/dist/chunk-7ZOADACP.js +13 -0
- package/dist/chunk-AZCQL24J.js +13 -0
- package/dist/chunk-BRG64AEC.js +6 -0
- package/dist/chunk-DN3KJZRV.js +13 -0
- package/dist/chunk-E75PYM7V.js +6 -0
- package/dist/chunk-EPIB4FSE.js +151 -0
- package/dist/chunk-GAWMEMXB.js +6 -0
- package/dist/chunk-H5OOD2OS.js +6 -0
- package/dist/chunk-HNIVNZIS.js +6 -0
- package/dist/chunk-HUIZOB4A.js +6 -0
- package/dist/chunk-I3Z4OKMS.js +13 -0
- package/dist/chunk-IOC3BSPT.js +6 -0
- package/dist/chunk-IQP3XE32.js +6 -0
- package/dist/chunk-ITBZHMZQ.js +6 -0
- package/dist/chunk-K3YDFDLP.js +6 -0
- package/dist/chunk-KGDJAST5.js +52 -0
- package/dist/chunk-LGZOG7IQ.js +13 -0
- package/dist/chunk-MK3YP2VR.js +6 -0
- package/dist/chunk-MMJX44V6.js +29 -0
- package/dist/chunk-NKWRH36V.js +52 -0
- package/dist/chunk-NPKLGVDQ.js +6 -0
- package/dist/chunk-PS2ZOFZ6.js +6 -0
- package/dist/chunk-RBHQ5WNR.js +13 -0
- package/dist/chunk-SQP6SJRP.js +6 -0
- package/dist/chunk-SWK6XRZQ.js +6 -0
- package/dist/chunk-SZFZPQGL.js +6 -0
- package/dist/chunk-VAFW24RN.js +6 -0
- package/dist/chunk-VJDLOFCF.js +13 -0
- package/dist/chunk-VZWMPUSS.js +13 -0
- package/dist/chunk-WLQ7DGMG.js +13 -0
- package/dist/chunk-XZWSO5CM.js +6 -0
- package/dist/chunk-YR4HWOR2.js +6 -0
- package/dist/chunk-ZYAEUABC.js +6 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1753 -0
- package/dist/tool-runtime.d.ts +49 -0
- package/dist/tool-runtime.d.ts.map +1 -0
- package/dist/tool-runtime.js +6 -0
- package/dist/tools/Bash/BashTool.d.ts +6 -0
- package/dist/tools/Bash/BashTool.d.ts.map +1 -0
- package/dist/tools/Bash/BashTool.js +8 -0
- package/dist/tools/Bash/UI.d.ts +2 -0
- package/dist/tools/Bash/UI.d.ts.map +1 -0
- package/dist/tools/Bash/UI.js +6 -0
- package/dist/tools/Bash/prompt.d.ts +2 -0
- package/dist/tools/Bash/prompt.d.ts.map +1 -0
- package/dist/tools/Bash/prompt.js +6 -0
- package/dist/tools/Diff/DiffTool.d.ts +6 -0
- package/dist/tools/Diff/DiffTool.d.ts.map +1 -0
- package/dist/tools/Diff/DiffTool.js +8 -0
- package/dist/tools/Diff/UI.d.ts +2 -0
- package/dist/tools/Diff/UI.d.ts.map +1 -0
- package/dist/tools/Diff/UI.js +6 -0
- package/dist/tools/Diff/prompt.d.ts +2 -0
- package/dist/tools/Diff/prompt.d.ts.map +1 -0
- package/dist/tools/Diff/prompt.js +6 -0
- package/dist/tools/Edit/EditTool.d.ts +6 -0
- package/dist/tools/Edit/EditTool.d.ts.map +1 -0
- package/dist/tools/Edit/EditTool.js +8 -0
- package/dist/tools/Edit/UI.d.ts +2 -0
- package/dist/tools/Edit/UI.d.ts.map +1 -0
- package/dist/tools/Edit/UI.js +6 -0
- package/dist/tools/Edit/prompt.d.ts +2 -0
- package/dist/tools/Edit/prompt.d.ts.map +1 -0
- package/dist/tools/Edit/prompt.js +6 -0
- package/dist/tools/Glob/GlobTool.d.ts +6 -0
- package/dist/tools/Glob/GlobTool.d.ts.map +1 -0
- package/dist/tools/Glob/GlobTool.js +8 -0
- package/dist/tools/Glob/UI.d.ts +2 -0
- package/dist/tools/Glob/UI.d.ts.map +1 -0
- package/dist/tools/Glob/UI.js +6 -0
- package/dist/tools/Glob/prompt.d.ts +2 -0
- package/dist/tools/Glob/prompt.d.ts.map +1 -0
- package/dist/tools/Glob/prompt.js +6 -0
- package/dist/tools/Grep/GrepTool.d.ts +6 -0
- package/dist/tools/Grep/GrepTool.d.ts.map +1 -0
- package/dist/tools/Grep/GrepTool.js +8 -0
- package/dist/tools/Grep/UI.d.ts +2 -0
- package/dist/tools/Grep/UI.d.ts.map +1 -0
- package/dist/tools/Grep/UI.js +6 -0
- package/dist/tools/Grep/prompt.d.ts +2 -0
- package/dist/tools/Grep/prompt.d.ts.map +1 -0
- package/dist/tools/Grep/prompt.js +6 -0
- package/dist/tools/MultiEdit/MultiEditTool.d.ts +6 -0
- package/dist/tools/MultiEdit/MultiEditTool.d.ts.map +1 -0
- package/dist/tools/MultiEdit/MultiEditTool.js +8 -0
- package/dist/tools/MultiEdit/UI.d.ts +2 -0
- package/dist/tools/MultiEdit/UI.d.ts.map +1 -0
- package/dist/tools/MultiEdit/UI.js +6 -0
- package/dist/tools/MultiEdit/prompt.d.ts +2 -0
- package/dist/tools/MultiEdit/prompt.d.ts.map +1 -0
- package/dist/tools/MultiEdit/prompt.js +6 -0
- package/dist/tools/Read/ReadTool.d.ts +6 -0
- package/dist/tools/Read/ReadTool.d.ts.map +1 -0
- package/dist/tools/Read/ReadTool.js +8 -0
- package/dist/tools/Read/UI.d.ts +2 -0
- package/dist/tools/Read/UI.d.ts.map +1 -0
- package/dist/tools/Read/UI.js +6 -0
- package/dist/tools/Read/prompt.d.ts +2 -0
- package/dist/tools/Read/prompt.d.ts.map +1 -0
- package/dist/tools/Read/prompt.js +6 -0
- package/dist/tools/Todo/TodoTool.d.ts +6 -0
- package/dist/tools/Todo/TodoTool.d.ts.map +1 -0
- package/dist/tools/Todo/TodoTool.js +8 -0
- package/dist/tools/Todo/UI.d.ts +2 -0
- package/dist/tools/Todo/UI.d.ts.map +1 -0
- package/dist/tools/Todo/UI.js +6 -0
- package/dist/tools/Todo/prompt.d.ts +2 -0
- package/dist/tools/Todo/prompt.d.ts.map +1 -0
- package/dist/tools/Todo/prompt.js +6 -0
- package/dist/tools/WebFetch/UI.d.ts +2 -0
- package/dist/tools/WebFetch/UI.d.ts.map +1 -0
- package/dist/tools/WebFetch/UI.js +6 -0
- package/dist/tools/WebFetch/WebFetchTool.d.ts +6 -0
- package/dist/tools/WebFetch/WebFetchTool.d.ts.map +1 -0
- package/dist/tools/WebFetch/WebFetchTool.js +8 -0
- package/dist/tools/WebFetch/prompt.d.ts +2 -0
- package/dist/tools/WebFetch/prompt.d.ts.map +1 -0
- package/dist/tools/WebFetch/prompt.js +6 -0
- package/dist/tools/WebFetch/web-fetch.d.ts +21 -0
- package/dist/tools/WebFetch/web-fetch.d.ts.map +1 -0
- package/dist/tools/WebFetch/web-fetch.js +8 -0
- package/dist/tools/WebSearch/UI.d.ts +2 -0
- package/dist/tools/WebSearch/UI.d.ts.map +1 -0
- package/dist/tools/WebSearch/UI.js +6 -0
- package/dist/tools/WebSearch/WebSearchTool.d.ts +6 -0
- package/dist/tools/WebSearch/WebSearchTool.d.ts.map +1 -0
- package/dist/tools/WebSearch/WebSearchTool.js +8 -0
- package/dist/tools/WebSearch/bing-scraper.d.ts +30 -0
- package/dist/tools/WebSearch/bing-scraper.d.ts.map +1 -0
- package/dist/tools/WebSearch/bing-scraper.js +10 -0
- package/dist/tools/WebSearch/prompt.d.ts +2 -0
- package/dist/tools/WebSearch/prompt.d.ts.map +1 -0
- package/dist/tools/WebSearch/prompt.js +6 -0
- package/dist/tools/Write/UI.d.ts +2 -0
- package/dist/tools/Write/UI.d.ts.map +1 -0
- package/dist/tools/Write/UI.js +6 -0
- package/dist/tools/Write/WriteTool.d.ts +6 -0
- package/dist/tools/Write/WriteTool.d.ts.map +1 -0
- package/dist/tools/Write/WriteTool.js +8 -0
- package/dist/tools/Write/prompt.d.ts +2 -0
- package/dist/tools/Write/prompt.d.ts.map +1 -0
- package/dist/tools/Write/prompt.js +6 -0
- package/dist/tools/prompts.d.ts +3 -0
- package/dist/tools/prompts.d.ts.map +1 -0
- package/dist/tools/prompts.js +39 -0
- package/dist/tools/ui.d.ts +3 -0
- package/dist/tools/ui.d.ts.map +1 -0
- package/dist/tools/ui.js +39 -0
- package/package.json +26 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1753 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyDomainFilter,
|
|
3
|
+
bingSearch,
|
|
4
|
+
formatSearchOutput
|
|
5
|
+
} from "./chunk-EPIB4FSE.js";
|
|
6
|
+
import {
|
|
7
|
+
formatFetchOutput,
|
|
8
|
+
webFetch
|
|
9
|
+
} from "./chunk-4UHQWBFA.js";
|
|
10
|
+
import {
|
|
11
|
+
createTool
|
|
12
|
+
} from "./chunk-MMJX44V6.js";
|
|
13
|
+
import {
|
|
14
|
+
toolPrompts
|
|
15
|
+
} from "./chunk-KGDJAST5.js";
|
|
16
|
+
import {
|
|
17
|
+
toolUserFacingNames
|
|
18
|
+
} from "./chunk-NKWRH36V.js";
|
|
19
|
+
import "./chunk-4EZHIDZ6.js";
|
|
20
|
+
import "./chunk-YR4HWOR2.js";
|
|
21
|
+
import "./chunk-VAFW24RN.js";
|
|
22
|
+
import "./chunk-I3Z4OKMS.js";
|
|
23
|
+
import "./chunk-PS2ZOFZ6.js";
|
|
24
|
+
import "./chunk-NPKLGVDQ.js";
|
|
25
|
+
import "./chunk-VJDLOFCF.js";
|
|
26
|
+
import "./chunk-3RP665TH.js";
|
|
27
|
+
import "./chunk-IOC3BSPT.js";
|
|
28
|
+
import "./chunk-LGZOG7IQ.js";
|
|
29
|
+
import "./chunk-ZYAEUABC.js";
|
|
30
|
+
import "./chunk-SWK6XRZQ.js";
|
|
31
|
+
import "./chunk-AZCQL24J.js";
|
|
32
|
+
import "./chunk-HUIZOB4A.js";
|
|
33
|
+
import "./chunk-MK3YP2VR.js";
|
|
34
|
+
import "./chunk-RBHQ5WNR.js";
|
|
35
|
+
import "./chunk-HNIVNZIS.js";
|
|
36
|
+
import "./chunk-K3YDFDLP.js";
|
|
37
|
+
import "./chunk-4567YAXM.js";
|
|
38
|
+
import "./chunk-XZWSO5CM.js";
|
|
39
|
+
import "./chunk-SZFZPQGL.js";
|
|
40
|
+
import "./chunk-DN3KJZRV.js";
|
|
41
|
+
import "./chunk-2U4BZXAI.js";
|
|
42
|
+
import "./chunk-ITBZHMZQ.js";
|
|
43
|
+
import "./chunk-WLQ7DGMG.js";
|
|
44
|
+
import "./chunk-SQP6SJRP.js";
|
|
45
|
+
import "./chunk-BRG64AEC.js";
|
|
46
|
+
import "./chunk-VZWMPUSS.js";
|
|
47
|
+
import "./chunk-E75PYM7V.js";
|
|
48
|
+
import "./chunk-GAWMEMXB.js";
|
|
49
|
+
import "./chunk-7ZOADACP.js";
|
|
50
|
+
import "./chunk-H5OOD2OS.js";
|
|
51
|
+
import "./chunk-IQP3XE32.js";
|
|
52
|
+
|
|
53
|
+
// src/index.ts
|
|
54
|
+
import { spawn } from "child_process";
|
|
55
|
+
import { createHash, randomUUID } from "crypto";
|
|
56
|
+
import { mkdir, readFile, readdir, stat, writeFile } from "fs/promises";
|
|
57
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "path";
|
|
58
|
+
var toolRegistryStatus = "ready";
|
|
59
|
+
var DEFAULT_LIMIT = 200;
|
|
60
|
+
var DEFAULT_SEARCH_LIMIT = 100;
|
|
61
|
+
var DEFAULT_TOOL_TEXT_LIMIT = 5e4;
|
|
62
|
+
var BASH_PREVIEW_LIMIT = 3e4;
|
|
63
|
+
var BASH_TIMEOUT_MS = 12e4;
|
|
64
|
+
var MAX_TODO_ITEMS = 100;
|
|
65
|
+
var SEARCH_EXCLUDED_DIR_NAMES = ["node_modules", "dist", ".git", ".codebase-memory"];
|
|
66
|
+
var SEARCH_EXCLUDED_PATH_PREFIXES = [".linghun/logs", ".linghun/agent-runs", ".linghun/failures"];
|
|
67
|
+
var SEARCH_EXCLUDED_FILE_SUFFIXES = [".tsbuildinfo"];
|
|
68
|
+
var RG_TIMEOUT_MS = 3e4;
|
|
69
|
+
function createToolContext(workspaceRoot = process.cwd()) {
|
|
70
|
+
return {
|
|
71
|
+
workspaceRoot: resolve(workspaceRoot),
|
|
72
|
+
changedFiles: [],
|
|
73
|
+
todos: [],
|
|
74
|
+
readSnapshots: {},
|
|
75
|
+
patchSummaries: {}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function runTool(name, input, context) {
|
|
79
|
+
const tool = builtInTools[name];
|
|
80
|
+
if (!tool) {
|
|
81
|
+
throw new Error(`\u672A\u77E5\u5DE5\u5177\uFF1A${name}`);
|
|
82
|
+
}
|
|
83
|
+
if (!tool.lifecycle.enabled) {
|
|
84
|
+
throw new Error(`\u5DE5\u5177\u5DF2\u7981\u7528\uFF1A${name}\u3002\u5EFA\u8BAE\uFF1A\u8FD0\u884C /features \u6216 /doctor \u67E5\u770B\u5F53\u524D\u80FD\u529B\u8FB9\u754C\u3002`);
|
|
85
|
+
}
|
|
86
|
+
const validatedInput = tool.validateInput(input);
|
|
87
|
+
const output = await tool.call(validatedInput, context);
|
|
88
|
+
return {
|
|
89
|
+
id: randomUUID(),
|
|
90
|
+
name,
|
|
91
|
+
input: validatedInput,
|
|
92
|
+
output: normalizeToolOutput(output, tool)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function defineTool(definition) {
|
|
96
|
+
return createTool(definition);
|
|
97
|
+
}
|
|
98
|
+
var toolDefinitions = {
|
|
99
|
+
Read: defineTool({
|
|
100
|
+
name: "Read",
|
|
101
|
+
title: "\u8BFB\u53D6\u6587\u4EF6",
|
|
102
|
+
description: "\u8BFB\u53D6\u5DE5\u4F5C\u533A\u6587\u4EF6\u5185\u5BB9\u3002",
|
|
103
|
+
permission: {
|
|
104
|
+
risk: "low",
|
|
105
|
+
scope: "workspace",
|
|
106
|
+
reason: "\u53EA\u8BFB\u67E5\u770B\u6587\u4EF6\u5185\u5BB9\u3002",
|
|
107
|
+
phase06Mode: "metadata-only"
|
|
108
|
+
},
|
|
109
|
+
isReadOnly: true,
|
|
110
|
+
isConcurrencySafe: true,
|
|
111
|
+
lifecycle: readOnlyLifecycle(),
|
|
112
|
+
validateInput: validateReadInput,
|
|
113
|
+
call: readTool,
|
|
114
|
+
prompt: () => toolPrompts.Read,
|
|
115
|
+
userFacingName: () => toolUserFacingNames.Read,
|
|
116
|
+
getToolUseSummary: (input) => `Read ${input.path}`,
|
|
117
|
+
getActivityDescription: (input) => `Reading ${input.path}`
|
|
118
|
+
}),
|
|
119
|
+
Write: defineTool({
|
|
120
|
+
name: "Write",
|
|
121
|
+
title: "\u5199\u5165\u6587\u4EF6",
|
|
122
|
+
description: "\u5728\u5DE5\u4F5C\u533A\u5185\u5199\u5165\u5B8C\u6574\u6587\u4EF6\u5185\u5BB9\u3002",
|
|
123
|
+
permission: {
|
|
124
|
+
risk: "medium",
|
|
125
|
+
scope: "workspace",
|
|
126
|
+
reason: "\u4F1A\u4FEE\u6539\u5DE5\u4F5C\u533A\u6587\u4EF6\uFF0CPhase 06 \u5C06\u63A5\u5165\u6743\u9650\u5BA1\u6279\u3002",
|
|
127
|
+
phase06Mode: "metadata-only"
|
|
128
|
+
},
|
|
129
|
+
isReadOnly: false,
|
|
130
|
+
isConcurrencySafe: false,
|
|
131
|
+
lifecycle: writeLifecycle(),
|
|
132
|
+
validateInput: validateWriteInput,
|
|
133
|
+
call: writeTool,
|
|
134
|
+
prompt: () => toolPrompts.Write,
|
|
135
|
+
userFacingName: () => toolUserFacingNames.Write,
|
|
136
|
+
getToolUseSummary: (input) => `Write ${input.path}`,
|
|
137
|
+
getActivityDescription: (input) => `Writing ${input.path}`
|
|
138
|
+
}),
|
|
139
|
+
Edit: defineTool({
|
|
140
|
+
name: "Edit",
|
|
141
|
+
title: "\u7F16\u8F91\u6587\u4EF6",
|
|
142
|
+
description: "\u5728\u5DE5\u4F5C\u533A\u5185\u505A\u552F\u4E00\u5B57\u7B26\u4E32\u66FF\u6362\u3002",
|
|
143
|
+
permission: {
|
|
144
|
+
risk: "low",
|
|
145
|
+
scope: "workspace",
|
|
146
|
+
reason: "\u4F1A\u5728\u5DE5\u4F5C\u533A\u5185\u505A\u5355\u6587\u4EF6\u552F\u4E00\u5B57\u7B26\u4E32\u66FF\u6362\uFF0C\u5C5E\u4E8E\u4F4E\u98CE\u9669\u7F16\u8F91\u3002",
|
|
147
|
+
phase06Mode: "metadata-only"
|
|
148
|
+
},
|
|
149
|
+
isReadOnly: false,
|
|
150
|
+
isConcurrencySafe: false,
|
|
151
|
+
lifecycle: writeLifecycle(),
|
|
152
|
+
validateInput: validateEditInput,
|
|
153
|
+
call: editTool,
|
|
154
|
+
prompt: () => toolPrompts.Edit,
|
|
155
|
+
userFacingName: () => toolUserFacingNames.Edit,
|
|
156
|
+
getToolUseSummary: (input) => `Edit ${input.path}`,
|
|
157
|
+
getActivityDescription: (input) => `Editing ${input.path}`
|
|
158
|
+
}),
|
|
159
|
+
MultiEdit: defineTool({
|
|
160
|
+
name: "MultiEdit",
|
|
161
|
+
title: "\u6279\u91CF\u7F16\u8F91\u6587\u4EF6",
|
|
162
|
+
description: "\u5728\u540C\u4E00\u6587\u4EF6\u5185\u6309\u987A\u5E8F\u505A\u591A\u4E2A\u552F\u4E00\u5B57\u7B26\u4E32\u66FF\u6362\u3002",
|
|
163
|
+
permission: {
|
|
164
|
+
risk: "medium",
|
|
165
|
+
scope: "workspace",
|
|
166
|
+
reason: "\u4F1A\u4FEE\u6539\u5DE5\u4F5C\u533A\u6587\u4EF6\uFF0C\u5E76\u9010\u9879\u8981\u6C42 oldText \u552F\u4E00\u3002",
|
|
167
|
+
phase06Mode: "metadata-only"
|
|
168
|
+
},
|
|
169
|
+
isReadOnly: false,
|
|
170
|
+
isConcurrencySafe: false,
|
|
171
|
+
lifecycle: writeLifecycle(),
|
|
172
|
+
validateInput: validateMultiEditInput,
|
|
173
|
+
call: multiEditTool,
|
|
174
|
+
prompt: () => toolPrompts.MultiEdit,
|
|
175
|
+
userFacingName: () => toolUserFacingNames.MultiEdit,
|
|
176
|
+
getToolUseSummary: (input) => `MultiEdit ${input.path} (${input.edits.length} edits)`,
|
|
177
|
+
getActivityDescription: (input) => `Editing ${input.path}`
|
|
178
|
+
}),
|
|
179
|
+
Grep: defineTool({
|
|
180
|
+
name: "Grep",
|
|
181
|
+
title: "\u641C\u7D22\u6587\u672C",
|
|
182
|
+
description: "\u5728\u5DE5\u4F5C\u533A\u5185\u6309\u6B63\u5219\u641C\u7D22\u6587\u672C\u3002",
|
|
183
|
+
permission: {
|
|
184
|
+
risk: "low",
|
|
185
|
+
scope: "workspace",
|
|
186
|
+
reason: "\u53EA\u8BFB\u641C\u7D22\u6587\u4EF6\u5185\u5BB9\u3002",
|
|
187
|
+
phase06Mode: "metadata-only"
|
|
188
|
+
},
|
|
189
|
+
isReadOnly: true,
|
|
190
|
+
isConcurrencySafe: true,
|
|
191
|
+
lifecycle: readOnlyLifecycle(),
|
|
192
|
+
validateInput: validateGrepInput,
|
|
193
|
+
call: grepTool,
|
|
194
|
+
prompt: () => toolPrompts.Grep,
|
|
195
|
+
userFacingName: () => toolUserFacingNames.Grep,
|
|
196
|
+
getToolUseSummary: (input) => `Grep ${input.pattern}`,
|
|
197
|
+
getActivityDescription: (input) => `Searching ${input.path ?? "."}`
|
|
198
|
+
}),
|
|
199
|
+
Glob: defineTool({
|
|
200
|
+
name: "Glob",
|
|
201
|
+
title: "\u5339\u914D\u6587\u4EF6",
|
|
202
|
+
description: "\u5728\u5DE5\u4F5C\u533A\u5185\u6309 glob \u6A21\u5F0F\u5339\u914D\u6587\u4EF6\u3002",
|
|
203
|
+
permission: {
|
|
204
|
+
risk: "low",
|
|
205
|
+
scope: "workspace",
|
|
206
|
+
reason: "\u53EA\u8BFB\u5217\u51FA\u6587\u4EF6\u8DEF\u5F84\u3002",
|
|
207
|
+
phase06Mode: "metadata-only"
|
|
208
|
+
},
|
|
209
|
+
isReadOnly: true,
|
|
210
|
+
isConcurrencySafe: true,
|
|
211
|
+
lifecycle: readOnlyLifecycle(),
|
|
212
|
+
validateInput: validateGlobInput,
|
|
213
|
+
call: globTool,
|
|
214
|
+
prompt: () => toolPrompts.Glob,
|
|
215
|
+
userFacingName: () => toolUserFacingNames.Glob,
|
|
216
|
+
getToolUseSummary: (input) => `Glob ${input.pattern}`,
|
|
217
|
+
getActivityDescription: (input) => `Matching ${input.path ?? "."}`
|
|
218
|
+
}),
|
|
219
|
+
Bash: defineTool({
|
|
220
|
+
name: "Bash",
|
|
221
|
+
title: "\u6267\u884C\u547D\u4EE4",
|
|
222
|
+
description: "\u5728\u5DE5\u4F5C\u533A\u5185\u6267\u884C shell \u547D\u4EE4\u5E76\u4FDD\u5B58\u5B8C\u6574\u65E5\u5FD7\u3002",
|
|
223
|
+
permission: {
|
|
224
|
+
risk: "high",
|
|
225
|
+
scope: "command",
|
|
226
|
+
reason: "\u4F1A\u6267\u884C\u672C\u5730\u547D\u4EE4\uFF1BPhase 05 \u4EC5\u58F0\u660E\u98CE\u9669\uFF0CPhase 06 \u63A5\u5165\u5BA1\u6279\u3002",
|
|
227
|
+
phase06Mode: "metadata-only"
|
|
228
|
+
},
|
|
229
|
+
isReadOnly: false,
|
|
230
|
+
isConcurrencySafe: false,
|
|
231
|
+
isLongRunning: true,
|
|
232
|
+
lifecycle: bashLifecycle(),
|
|
233
|
+
validateInput: validateBashInput,
|
|
234
|
+
call: bashTool,
|
|
235
|
+
prompt: () => toolPrompts.Bash,
|
|
236
|
+
userFacingName: () => toolUserFacingNames.Bash,
|
|
237
|
+
getToolUseSummary: (input) => `Bash ${input.command}`,
|
|
238
|
+
getActivityDescription: () => "Running command"
|
|
239
|
+
}),
|
|
240
|
+
Todo: defineTool({
|
|
241
|
+
name: "Todo",
|
|
242
|
+
title: "\u4EFB\u52A1\u5217\u8868",
|
|
243
|
+
description: "\u7EF4\u62A4\u5F53\u524D\u4F1A\u8BDD\u4EFB\u52A1\u3001\u5B8C\u6210\u9879\u548C\u963B\u585E\u9879\u3002",
|
|
244
|
+
permission: {
|
|
245
|
+
risk: "low",
|
|
246
|
+
scope: "session",
|
|
247
|
+
reason: "\u53EA\u4FEE\u6539\u5F53\u524D\u4F1A\u8BDD\u5185\u4EFB\u52A1\u72B6\u6001\u3002",
|
|
248
|
+
phase06Mode: "metadata-only"
|
|
249
|
+
},
|
|
250
|
+
isReadOnly: false,
|
|
251
|
+
isConcurrencySafe: false,
|
|
252
|
+
lifecycle: sessionLifecycle(),
|
|
253
|
+
validateInput: validateTodoInput,
|
|
254
|
+
call: todoTool,
|
|
255
|
+
prompt: () => toolPrompts.Todo,
|
|
256
|
+
userFacingName: () => toolUserFacingNames.Todo,
|
|
257
|
+
getToolUseSummary: (input) => `Todo ${input.action}`,
|
|
258
|
+
getActivityDescription: (input) => `Updating todo ${input.action}`
|
|
259
|
+
}),
|
|
260
|
+
Diff: defineTool({
|
|
261
|
+
name: "Diff",
|
|
262
|
+
title: "\u6539\u52A8\u6458\u8981",
|
|
263
|
+
description: "\u8F93\u51FA\u672C\u8F6E\u5DE5\u5177\u6539\u52A8\u6587\u4EF6\u5217\u8868\u548C\u6458\u8981\u3002",
|
|
264
|
+
permission: {
|
|
265
|
+
risk: "low",
|
|
266
|
+
scope: "workspace",
|
|
267
|
+
reason: "\u53EA\u8BFB\u6C47\u603B\u672C\u8F6E changedFiles\u3002",
|
|
268
|
+
phase06Mode: "metadata-only"
|
|
269
|
+
},
|
|
270
|
+
isReadOnly: true,
|
|
271
|
+
isConcurrencySafe: true,
|
|
272
|
+
lifecycle: readOnlyLifecycle(),
|
|
273
|
+
validateInput: validateDiffInput,
|
|
274
|
+
call: diffTool,
|
|
275
|
+
prompt: () => toolPrompts.Diff,
|
|
276
|
+
userFacingName: () => toolUserFacingNames.Diff,
|
|
277
|
+
getToolUseSummary: () => "Diff summary",
|
|
278
|
+
getActivityDescription: () => "Summarizing changes"
|
|
279
|
+
}),
|
|
280
|
+
WebSearch: defineTool({
|
|
281
|
+
name: "WebSearch",
|
|
282
|
+
title: "\u641C\u7D22\u7F51\u9875",
|
|
283
|
+
description: "\u901A\u8FC7 Bing \u641C\u7D22\u4E92\u8054\u7F51\uFF0C\u8FD4\u56DE\u6807\u9898\u3001\u94FE\u63A5\u548C\u6458\u8981\u3002\u96F6 API Key\uFF0C\u56FD\u5185\u76F4\u8FDE\u3002",
|
|
284
|
+
permission: {
|
|
285
|
+
risk: "low",
|
|
286
|
+
scope: "session",
|
|
287
|
+
reason: "\u53EA\u8BFB\u641C\u7D22\u516C\u5F00\u7F51\u9875\u4FE1\u606F\uFF0C\u4E0D\u6D89\u53CA\u5DE5\u4F5C\u533A\u6587\u4EF6\u3002",
|
|
288
|
+
phase06Mode: "metadata-only"
|
|
289
|
+
},
|
|
290
|
+
isReadOnly: true,
|
|
291
|
+
isConcurrencySafe: true,
|
|
292
|
+
lifecycle: {
|
|
293
|
+
enabled: true,
|
|
294
|
+
destructive: false,
|
|
295
|
+
interruptBehavior: "abortable",
|
|
296
|
+
maxResultSizeChars: 2e4
|
|
297
|
+
},
|
|
298
|
+
validateInput: validateWebSearchInput,
|
|
299
|
+
call: webSearchTool,
|
|
300
|
+
prompt: () => toolPrompts.WebSearch,
|
|
301
|
+
userFacingName: () => toolUserFacingNames.WebSearch,
|
|
302
|
+
getToolUseSummary: (input) => `\u641C\u7D22 "${input.query}"`,
|
|
303
|
+
getActivityDescription: (input) => `\u641C\u7D22\u7F51\u9875: ${input.query}`
|
|
304
|
+
}),
|
|
305
|
+
WebFetch: defineTool({
|
|
306
|
+
name: "WebFetch",
|
|
307
|
+
title: "\u6293\u53D6\u7F51\u9875",
|
|
308
|
+
description: "\u6293\u53D6\u6307\u5B9A URL \u7684\u7F51\u9875\u5185\u5BB9\u5E76\u8F6C\u4E3A\u7EAF\u6587\u672C\u3002\u96F6 API Key\u3002",
|
|
309
|
+
permission: {
|
|
310
|
+
risk: "low",
|
|
311
|
+
scope: "session",
|
|
312
|
+
reason: "\u53EA\u8BFB\u6293\u53D6\u516C\u5F00\u7F51\u9875\u5185\u5BB9\uFF0C\u5185\u7F51\u5730\u5740\u5DF2\u62E6\u622A\u3002",
|
|
313
|
+
phase06Mode: "metadata-only"
|
|
314
|
+
},
|
|
315
|
+
isReadOnly: true,
|
|
316
|
+
isConcurrencySafe: true,
|
|
317
|
+
lifecycle: {
|
|
318
|
+
enabled: true,
|
|
319
|
+
destructive: false,
|
|
320
|
+
interruptBehavior: "abortable",
|
|
321
|
+
maxResultSizeChars: 6e4
|
|
322
|
+
},
|
|
323
|
+
validateInput: validateWebFetchInput,
|
|
324
|
+
call: webFetchTool,
|
|
325
|
+
prompt: () => toolPrompts.WebFetch,
|
|
326
|
+
userFacingName: () => toolUserFacingNames.WebFetch,
|
|
327
|
+
getToolUseSummary: (input) => `Fetch ${input.url}`,
|
|
328
|
+
getActivityDescription: (input) => `\u6293\u53D6\u7F51\u9875: ${input.url}`
|
|
329
|
+
})
|
|
330
|
+
};
|
|
331
|
+
var builtInTools = toolDefinitions;
|
|
332
|
+
function readOnlyLifecycle() {
|
|
333
|
+
return {
|
|
334
|
+
enabled: true,
|
|
335
|
+
destructive: false,
|
|
336
|
+
interruptBehavior: "abortable",
|
|
337
|
+
maxResultSizeChars: DEFAULT_TOOL_TEXT_LIMIT
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function writeLifecycle() {
|
|
341
|
+
return {
|
|
342
|
+
enabled: true,
|
|
343
|
+
destructive: true,
|
|
344
|
+
interruptBehavior: "best-effort",
|
|
345
|
+
maxResultSizeChars: DEFAULT_TOOL_TEXT_LIMIT
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function bashLifecycle() {
|
|
349
|
+
return {
|
|
350
|
+
enabled: true,
|
|
351
|
+
destructive: true,
|
|
352
|
+
interruptBehavior: "abortable",
|
|
353
|
+
maxResultSizeChars: BASH_PREVIEW_LIMIT
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
function sessionLifecycle() {
|
|
357
|
+
return {
|
|
358
|
+
enabled: true,
|
|
359
|
+
destructive: false,
|
|
360
|
+
interruptBehavior: "not-supported",
|
|
361
|
+
maxResultSizeChars: DEFAULT_TOOL_TEXT_LIMIT
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function normalizeToolOutput(output, tool) {
|
|
365
|
+
if (output.text.length <= tool.lifecycle.maxResultSizeChars) {
|
|
366
|
+
return output;
|
|
367
|
+
}
|
|
368
|
+
const preview = `${output.text.slice(0, tool.lifecycle.maxResultSizeChars)}
|
|
369
|
+
...\uFF08\u8F93\u51FA\u5DF2\u622A\u65AD\uFF0C\u5B8C\u6574\u5185\u5BB9\u89C1 details/fullOutputPath \u6216 transcript\uFF09`;
|
|
370
|
+
return {
|
|
371
|
+
...output,
|
|
372
|
+
text: preview,
|
|
373
|
+
preview: output.preview ?? preview,
|
|
374
|
+
details: output.details ?? output.text,
|
|
375
|
+
truncated: true
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function validateRecord(input, toolName) {
|
|
379
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
380
|
+
throw new Error(`${toolName} \u8F93\u5165\u5FC5\u987B\u662F\u5BF9\u8C61\u3002\u5EFA\u8BAE\uFF1A\u6309\u5DE5\u5177 schema \u4F20\u5165 JSON object\u3002`);
|
|
381
|
+
}
|
|
382
|
+
return input;
|
|
383
|
+
}
|
|
384
|
+
function readString(record, key, toolName) {
|
|
385
|
+
const value = record[key];
|
|
386
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
387
|
+
throw new Error(`${toolName}.${key} \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u3002`);
|
|
388
|
+
}
|
|
389
|
+
return value;
|
|
390
|
+
}
|
|
391
|
+
function readOptionalString(record, key, toolName) {
|
|
392
|
+
const value = record[key];
|
|
393
|
+
if (value === void 0) {
|
|
394
|
+
return void 0;
|
|
395
|
+
}
|
|
396
|
+
if (typeof value !== "string") {
|
|
397
|
+
throw new Error(`${toolName}.${key} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u3002`);
|
|
398
|
+
}
|
|
399
|
+
return value;
|
|
400
|
+
}
|
|
401
|
+
function normalizeOptionalWorkspacePath(path) {
|
|
402
|
+
if (path === void 0) return void 0;
|
|
403
|
+
return path.trim().length === 0 ? void 0 : path;
|
|
404
|
+
}
|
|
405
|
+
function readOptionalPositiveInteger(record, key, toolName) {
|
|
406
|
+
const value = record[key];
|
|
407
|
+
if (value === void 0) {
|
|
408
|
+
return void 0;
|
|
409
|
+
}
|
|
410
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
411
|
+
throw new Error(`${toolName}.${key} \u5FC5\u987B\u662F\u975E\u8D1F\u6574\u6570\u3002`);
|
|
412
|
+
}
|
|
413
|
+
return value;
|
|
414
|
+
}
|
|
415
|
+
function validateReadInput(input) {
|
|
416
|
+
const record = validateRecord(input, "Read");
|
|
417
|
+
return {
|
|
418
|
+
path: readString(record, "path", "Read"),
|
|
419
|
+
offset: readOptionalPositiveInteger(record, "offset", "Read"),
|
|
420
|
+
limit: readOptionalPositiveInteger(record, "limit", "Read")
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function validateWriteInput(input) {
|
|
424
|
+
const record = validateRecord(input, "Write");
|
|
425
|
+
return {
|
|
426
|
+
path: readString(record, "path", "Write"),
|
|
427
|
+
content: readString(record, "content", "Write"),
|
|
428
|
+
expectedHash: readOptionalString(record, "expectedHash", "Write")
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
function validateEditInput(input) {
|
|
432
|
+
const record = validateRecord(input, "Edit");
|
|
433
|
+
return {
|
|
434
|
+
path: readString(record, "path", "Edit"),
|
|
435
|
+
oldText: readString(record, "oldText", "Edit"),
|
|
436
|
+
newText: readString(record, "newText", "Edit"),
|
|
437
|
+
expectedHash: readOptionalString(record, "expectedHash", "Edit")
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function validateMultiEditInput(input) {
|
|
441
|
+
const record = validateRecord(input, "MultiEdit");
|
|
442
|
+
const edits = record.edits;
|
|
443
|
+
if (!Array.isArray(edits) || edits.length === 0) {
|
|
444
|
+
throw new Error("MultiEdit.edits \u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4\u3002");
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
path: readString(record, "path", "MultiEdit"),
|
|
448
|
+
expectedHash: readOptionalString(record, "expectedHash", "MultiEdit"),
|
|
449
|
+
edits: edits.map((item) => {
|
|
450
|
+
const edit = validateRecord(item, "MultiEdit");
|
|
451
|
+
return {
|
|
452
|
+
oldText: readString(edit, "oldText", "MultiEdit"),
|
|
453
|
+
newText: readString(edit, "newText", "MultiEdit")
|
|
454
|
+
};
|
|
455
|
+
})
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
function validateGrepInput(input) {
|
|
459
|
+
const record = validateRecord(input, "Grep");
|
|
460
|
+
return {
|
|
461
|
+
pattern: readString(record, "pattern", "Grep"),
|
|
462
|
+
path: normalizeOptionalWorkspacePath(readOptionalString(record, "path", "Grep")),
|
|
463
|
+
limit: readOptionalPositiveInteger(record, "limit", "Grep")
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function validateGlobInput(input) {
|
|
467
|
+
const record = validateRecord(input, "Glob");
|
|
468
|
+
return {
|
|
469
|
+
pattern: readString(record, "pattern", "Glob"),
|
|
470
|
+
path: normalizeOptionalWorkspacePath(readOptionalString(record, "path", "Glob")),
|
|
471
|
+
limit: readOptionalPositiveInteger(record, "limit", "Glob")
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function validateBashInput(input) {
|
|
475
|
+
const record = validateRecord(input, "Bash");
|
|
476
|
+
return {
|
|
477
|
+
command: readString(record, "command", "Bash"),
|
|
478
|
+
timeoutMs: readOptionalPositiveInteger(record, "timeoutMs", "Bash")
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function validateTodoInput(input) {
|
|
482
|
+
const record = validateRecord(input, "Todo");
|
|
483
|
+
const action = readString(record, "action", "Todo");
|
|
484
|
+
if (action === "list") {
|
|
485
|
+
return { action };
|
|
486
|
+
}
|
|
487
|
+
if (action === "add") {
|
|
488
|
+
return { action, content: readString(record, "content", "Todo") };
|
|
489
|
+
}
|
|
490
|
+
if (action === "start" || action === "done" || action === "block") {
|
|
491
|
+
return {
|
|
492
|
+
action,
|
|
493
|
+
id: readString(record, "id", "Todo"),
|
|
494
|
+
content: readOptionalString(record, "content", "Todo"),
|
|
495
|
+
evidence: readOptionalString(record, "evidence", "Todo")
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
throw new Error("Todo.action \u5FC5\u987B\u662F list/add/start/done/block\u3002");
|
|
499
|
+
}
|
|
500
|
+
function validateDiffInput(input) {
|
|
501
|
+
const record = validateRecord(input ?? {}, "Diff");
|
|
502
|
+
const files = record.files;
|
|
503
|
+
if (files === void 0) {
|
|
504
|
+
return {};
|
|
505
|
+
}
|
|
506
|
+
if (!Array.isArray(files) || !files.every((item) => typeof item === "string")) {
|
|
507
|
+
throw new Error("Diff.files \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4\u3002");
|
|
508
|
+
}
|
|
509
|
+
return { files };
|
|
510
|
+
}
|
|
511
|
+
function validateWebSearchInput(input) {
|
|
512
|
+
const record = validateRecord(input, "WebSearch");
|
|
513
|
+
const query = readString(record, "query", "WebSearch");
|
|
514
|
+
const num_results = readOptionalPositiveInteger(record, "num_results", "WebSearch") ?? 8;
|
|
515
|
+
const rawAllowedDomains = readOptionalStringArray(record, "allowed_domains", "WebSearch");
|
|
516
|
+
const rawBlockedDomains = readOptionalStringArray(record, "blocked_domains", "WebSearch");
|
|
517
|
+
const allowed_domains = rawAllowedDomains && rawAllowedDomains.length > 0 ? rawAllowedDomains : void 0;
|
|
518
|
+
const blocked_domains = rawBlockedDomains && rawBlockedDomains.length > 0 ? rawBlockedDomains : void 0;
|
|
519
|
+
if (allowed_domains && blocked_domains) {
|
|
520
|
+
throw new Error("WebSearch: allowed_domains \u548C blocked_domains \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528\u3002");
|
|
521
|
+
}
|
|
522
|
+
return { query, num_results, allowed_domains, blocked_domains };
|
|
523
|
+
}
|
|
524
|
+
function validateWebFetchInput(input) {
|
|
525
|
+
const record = validateRecord(input, "WebFetch");
|
|
526
|
+
const url = readString(record, "url", "WebFetch");
|
|
527
|
+
const prompt = readOptionalString(record, "prompt", "WebFetch");
|
|
528
|
+
return { url, prompt };
|
|
529
|
+
}
|
|
530
|
+
function readOptionalStringArray(record, key, toolName) {
|
|
531
|
+
const value = record[key];
|
|
532
|
+
if (value === void 0) return void 0;
|
|
533
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
|
|
534
|
+
throw new Error(`${toolName}.${key} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4\u6216 undefined\u3002`);
|
|
535
|
+
}
|
|
536
|
+
return value;
|
|
537
|
+
}
|
|
538
|
+
async function readTool(input, context) {
|
|
539
|
+
const filePath = resolveWorkspacePath(context.workspaceRoot, input.path);
|
|
540
|
+
const content = await readFile(filePath, "utf8");
|
|
541
|
+
const info = await stat(filePath);
|
|
542
|
+
rememberReadSnapshot(context, filePath, content, info);
|
|
543
|
+
const lines = splitContentLines(content);
|
|
544
|
+
const offset = Math.max(input.offset ?? 0, 0);
|
|
545
|
+
const limit = Math.max(input.limit ?? DEFAULT_LIMIT, 1);
|
|
546
|
+
const selected = lines.slice(offset, offset + limit);
|
|
547
|
+
const truncated = offset > 0 || offset + limit < lines.length;
|
|
548
|
+
const textLines = selected.map((line, index) => `${offset + index + 1} ${line}`);
|
|
549
|
+
if (truncated) {
|
|
550
|
+
textLines.push(
|
|
551
|
+
`...\uFF08\u53EA\u663E\u793A\u8BFB\u53D6\u7A97\u53E3\uFF1A\u9009\u4E2D ${selected.length} \u884C / \u5168\u6587 ${lines.length} \u884C\uFF1B\u4E0D\u662F\u5B8C\u6574\u6587\u4EF6\uFF09`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
text: textLines.join("\n"),
|
|
556
|
+
data: {
|
|
557
|
+
path: relativePath(context.workspaceRoot, filePath),
|
|
558
|
+
lines: selected.length,
|
|
559
|
+
selectedLines: selected.length,
|
|
560
|
+
windowLines: selected.length,
|
|
561
|
+
totalLines: lines.length,
|
|
562
|
+
contentLines: lines.length,
|
|
563
|
+
hash: hashText(content),
|
|
564
|
+
newline: detectNewlineStyle(content)
|
|
565
|
+
},
|
|
566
|
+
truncated
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
function splitContentLines(content) {
|
|
570
|
+
if (content.length === 0) {
|
|
571
|
+
return [];
|
|
572
|
+
}
|
|
573
|
+
return content.replace(/\r?\n$/, "").split(/\r?\n/);
|
|
574
|
+
}
|
|
575
|
+
async function writeTool(input, context) {
|
|
576
|
+
const filePath = resolveWorkspacePath(context.workspaceRoot, input.path);
|
|
577
|
+
const before = await readExistingFile(filePath);
|
|
578
|
+
const readGuard = ensureReadBeforeEdit(context, filePath, before, input.expectedHash);
|
|
579
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
580
|
+
await writeFile(filePath, input.content, "utf8");
|
|
581
|
+
const afterInfo = await stat(filePath);
|
|
582
|
+
rememberReadSnapshot(context, filePath, input.content, afterInfo);
|
|
583
|
+
recordChangedFile(context, filePath);
|
|
584
|
+
return createEditOutput(
|
|
585
|
+
context,
|
|
586
|
+
"Write",
|
|
587
|
+
filePath,
|
|
588
|
+
before?.content ?? "",
|
|
589
|
+
input.content,
|
|
590
|
+
readGuard
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
async function editTool(input, context) {
|
|
594
|
+
const filePath = resolveWorkspacePath(context.workspaceRoot, input.path);
|
|
595
|
+
const before = await readExistingFile(filePath);
|
|
596
|
+
if (!before) {
|
|
597
|
+
throw new Error(
|
|
598
|
+
`\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${relativePath(context.workspaceRoot, filePath)}\u3002\u5EFA\u8BAE\uFF1A\u786E\u8BA4\u8DEF\u5F84\u6216\u6539\u7528 Write \u521B\u5EFA\u65B0\u6587\u4EF6\u3002`
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
const readGuard = ensureReadBeforeEdit(context, filePath, before, input.expectedHash);
|
|
602
|
+
try {
|
|
603
|
+
ensureUnique(before.content, input.oldText);
|
|
604
|
+
} catch (error) {
|
|
605
|
+
const message = error instanceof Error ? error.message : "\u552F\u4E00\u6027\u68C0\u67E5\u5931\u8D25\u3002";
|
|
606
|
+
throw new Error(`${message} \u5EFA\u8BAE\uFF1A\u91CD\u65B0 Read \u6587\u4EF6\uFF0C\u786E\u8BA4\u6700\u65B0\u5185\u5BB9\u540E\u518D\u63D0\u4EA4\u53EF\u552F\u4E00\u5339\u914D\u7684\u7F16\u8F91\u3002`);
|
|
607
|
+
}
|
|
608
|
+
const next = before.content.replace(input.oldText, input.newText);
|
|
609
|
+
await writeFile(filePath, next, "utf8");
|
|
610
|
+
const afterInfo = await stat(filePath);
|
|
611
|
+
rememberReadSnapshot(context, filePath, next, afterInfo);
|
|
612
|
+
recordChangedFile(context, filePath);
|
|
613
|
+
return createEditOutput(context, "Edit", filePath, before.content, next, readGuard);
|
|
614
|
+
}
|
|
615
|
+
async function multiEditTool(input, context) {
|
|
616
|
+
const filePath = resolveWorkspacePath(context.workspaceRoot, input.path);
|
|
617
|
+
if (!Array.isArray(input.edits) || input.edits.length === 0) {
|
|
618
|
+
throw new Error("MultiEdit \u9700\u8981\u81F3\u5C11 1 \u4E2A edits \u9879\u3002\u5EFA\u8BAE\uFF1A\u4F20\u5165 edits=[{oldText,newText}]\u3002");
|
|
619
|
+
}
|
|
620
|
+
const before = await readExistingFile(filePath);
|
|
621
|
+
if (!before) {
|
|
622
|
+
throw new Error(
|
|
623
|
+
`\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${relativePath(context.workspaceRoot, filePath)}\u3002\u5EFA\u8BAE\uFF1A\u786E\u8BA4\u8DEF\u5F84\u6216\u6539\u7528 Write \u521B\u5EFA\u65B0\u6587\u4EF6\u3002`
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
const readGuard = ensureReadBeforeEdit(context, filePath, before, input.expectedHash);
|
|
627
|
+
let content = before.content;
|
|
628
|
+
for (const [index, edit] of input.edits.entries()) {
|
|
629
|
+
try {
|
|
630
|
+
ensureUnique(content, edit.oldText);
|
|
631
|
+
} catch (error) {
|
|
632
|
+
const message = error instanceof Error ? error.message : "\u552F\u4E00\u6027\u68C0\u67E5\u5931\u8D25\u3002";
|
|
633
|
+
throw new Error(
|
|
634
|
+
`\u7B2C ${index + 1} \u4E2A\u7F16\u8F91\u5931\u8D25\uFF1A${message} \u5EFA\u8BAE\uFF1A\u91CD\u65B0 Read \u6587\u4EF6\uFF0C\u786E\u8BA4\u6700\u65B0\u5185\u5BB9\u540E\u518D\u6309\u987A\u5E8F\u63D0\u4EA4\u552F\u4E00\u5339\u914D\u7247\u6BB5\u3002`
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
content = content.replace(edit.oldText, edit.newText);
|
|
638
|
+
}
|
|
639
|
+
await writeFile(filePath, content, "utf8");
|
|
640
|
+
const afterInfo = await stat(filePath);
|
|
641
|
+
rememberReadSnapshot(context, filePath, content, afterInfo);
|
|
642
|
+
recordChangedFile(context, filePath);
|
|
643
|
+
return createEditOutput(
|
|
644
|
+
context,
|
|
645
|
+
"MultiEdit",
|
|
646
|
+
filePath,
|
|
647
|
+
before.content,
|
|
648
|
+
content,
|
|
649
|
+
readGuard,
|
|
650
|
+
input.edits.length
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
async function grepTool(input, context) {
|
|
654
|
+
const root = resolveWorkspacePath(context.workspaceRoot, input.path ?? ".");
|
|
655
|
+
const expression = createGrepExpression(input.pattern);
|
|
656
|
+
const limit = input.limit ?? DEFAULT_SEARCH_LIMIT;
|
|
657
|
+
const rgMatches = await tryRipgrepSearch(input, context, root, limit);
|
|
658
|
+
if (rgMatches) {
|
|
659
|
+
return {
|
|
660
|
+
text: rgMatches.matches.length === 0 ? "\u672A\u627E\u5230\u5339\u914D\u5185\u5BB9\u3002" : rgMatches.matches.join("\n"),
|
|
661
|
+
data: { count: rgMatches.matches.length, backend: "rg" },
|
|
662
|
+
truncated: rgMatches.truncated
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
const matches = [];
|
|
666
|
+
for await (const filePath of listFiles(root, () => matches.length >= limit)) {
|
|
667
|
+
const content = await safeReadText(filePath, context);
|
|
668
|
+
if (content === null) {
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
const lines = content.split(/\r?\n/);
|
|
672
|
+
for (const [index, line] of lines.entries()) {
|
|
673
|
+
if (expression.test(line)) {
|
|
674
|
+
matches.push(`${relativePath(context.workspaceRoot, filePath)}:${index + 1}: ${line}`);
|
|
675
|
+
if (matches.length >= limit) {
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return {
|
|
682
|
+
text: matches.length === 0 ? "\u672A\u627E\u5230\u5339\u914D\u5185\u5BB9\u3002" : matches.join("\n"),
|
|
683
|
+
data: { count: matches.length },
|
|
684
|
+
truncated: matches.length >= limit
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function createGrepExpression(pattern) {
|
|
688
|
+
if (pattern.startsWith("(?i)")) {
|
|
689
|
+
return new RegExp(pattern.slice(4), "i");
|
|
690
|
+
}
|
|
691
|
+
return new RegExp(pattern);
|
|
692
|
+
}
|
|
693
|
+
async function globTool(input, context) {
|
|
694
|
+
const root = resolveWorkspacePath(context.workspaceRoot, input.path ?? ".");
|
|
695
|
+
const limit = input.limit ?? DEFAULT_SEARCH_LIMIT;
|
|
696
|
+
const rgMatches = await tryRipgrepFiles(input, context, root, limit);
|
|
697
|
+
if (rgMatches) {
|
|
698
|
+
return {
|
|
699
|
+
text: rgMatches.matches.length === 0 ? "\u672A\u627E\u5230\u5339\u914D\u6587\u4EF6\u3002" : rgMatches.matches.join("\n"),
|
|
700
|
+
data: { count: rgMatches.matches.length, backend: "rg" },
|
|
701
|
+
truncated: rgMatches.truncated
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
const matcher = globToRegExp(input.pattern);
|
|
705
|
+
const matches = [];
|
|
706
|
+
for await (const filePath of listFiles(root, () => matches.length >= limit)) {
|
|
707
|
+
const rel = relativePath(context.workspaceRoot, filePath);
|
|
708
|
+
if (matcher.test(rel) || matcher.test(basename(filePath))) {
|
|
709
|
+
matches.push(rel);
|
|
710
|
+
}
|
|
711
|
+
if (matches.length >= limit) {
|
|
712
|
+
break;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return {
|
|
716
|
+
text: matches.length === 0 ? "\u672A\u627E\u5230\u5339\u914D\u6587\u4EF6\u3002" : matches.join("\n"),
|
|
717
|
+
data: { count: matches.length },
|
|
718
|
+
truncated: matches.length >= limit
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
var SECRET_PATTERNS = [
|
|
722
|
+
[/(?:-H\s+|--header\s+)?["']?\s*[Aa]uthorization\s*:\s*Bearer\s+\S+/g, "Authorization: Bearer [REDACTED]"],
|
|
723
|
+
[/(?:-H\s+|--header\s+)?["']?\s*[Aa]uthorization\s*:\s*Basic\s+\S+/g, "Authorization: Basic [REDACTED]"],
|
|
724
|
+
[/(?:-H\s+|--header\s+)?["']?\s*[Xx]-(?:[Aa]pi|[Ff]unctions|[Aa]uth|[Tt]oken)[- ]?[Kk]ey\s*:\s*\S+/g, (m) => `${m.split(":")[0]}: [REDACTED_HEADER]`],
|
|
725
|
+
[/--(?:token|api-key|api[_\-]?key|secret|access[_\-]?token|pat)\s+["']?\s*\S+/g, (m) => `${m.split(/\s+/)[0] ?? m} [REDACTED]`],
|
|
726
|
+
[/(?:AWS_SECRET_ACCESS_KEY|AWS_ACCESS_KEY_ID|AZURE_OPENAI_API_KEY|GITHUB_TOKEN|GITLAB_TOKEN|NPM_TOKEN|DOCKER_PASSWORD|OPENAI_API_KEY|ANTHROPIC_API_KEY|GOOGLE_API_KEY|COHERE_API_KEY|HF_TOKEN|HUGGINGFACE_TOKEN|DEEPSEEK_API_KEY)\s*[=:]\s*\S+/gi, (m) => `${m.split(/[=:]/)[0] ?? m}=[REDACTED_ENV]`],
|
|
727
|
+
[/(?:SECRET|TOKEN|KEY|PASSWORD|PASSWD)\s*[=:]\s*\S{8,}/gi, (m) => `${m.split(/[=:]/)[0] ?? m}=[REDACTED]`]
|
|
728
|
+
];
|
|
729
|
+
function sanitizeSecrets(text) {
|
|
730
|
+
let sanitized = text;
|
|
731
|
+
for (const [pattern, replacement] of SECRET_PATTERNS) {
|
|
732
|
+
sanitized = typeof replacement === "string" ? sanitized.replace(pattern, replacement) : sanitized.replace(pattern, replacement);
|
|
733
|
+
}
|
|
734
|
+
return sanitized;
|
|
735
|
+
}
|
|
736
|
+
async function bashTool(input, context) {
|
|
737
|
+
const logRoot = context.logRoot ?? join(context.workspaceRoot, ".linghun", "logs", "tools");
|
|
738
|
+
await mkdir(logRoot, { recursive: true });
|
|
739
|
+
const fullOutputPath = join(logRoot, `bash-${Date.now()}-${randomUUID()}.log`);
|
|
740
|
+
const timeoutMs = input.timeoutMs ?? BASH_TIMEOUT_MS;
|
|
741
|
+
const adapted = adaptShellCommand(input.command);
|
|
742
|
+
const result = await runShell(
|
|
743
|
+
adapted.command,
|
|
744
|
+
context.workspaceRoot,
|
|
745
|
+
timeoutMs,
|
|
746
|
+
context.abortSignal,
|
|
747
|
+
(stream, text) => void context.onProgress?.({ toolName: "Bash", stream, text })
|
|
748
|
+
);
|
|
749
|
+
const adapterLines = adapted.command === input.command && adapted.adapter === "native" ? [] : [
|
|
750
|
+
`adapter ${adapted.adapter}`,
|
|
751
|
+
`original command ${summarizeOriginalShellCommand(input.command)}`
|
|
752
|
+
];
|
|
753
|
+
const commandForLog = adapted.logCommand ?? adapted.command;
|
|
754
|
+
const fullText = [
|
|
755
|
+
`$ ${sanitizeSecrets(commandForLog)}`,
|
|
756
|
+
...adapterLines,
|
|
757
|
+
`exit code ${result.exitCode}`,
|
|
758
|
+
`outcome ${result.outcome}`,
|
|
759
|
+
"",
|
|
760
|
+
sanitizeSecrets(result.output)
|
|
761
|
+
].join("\n");
|
|
762
|
+
await writeFile(fullOutputPath, fullText, "utf8");
|
|
763
|
+
const truncated = fullText.length > BASH_PREVIEW_LIMIT;
|
|
764
|
+
const preview = truncated ? `${fullText.slice(0, BASH_PREVIEW_LIMIT)}
|
|
765
|
+
...\uFF08\u8F93\u51FA\u5DF2\u622A\u65AD\uFF0C\u5B8C\u6574\u65E5\u5FD7\u89C1 fullOutputPath\uFF09` : fullText;
|
|
766
|
+
return {
|
|
767
|
+
text: preview,
|
|
768
|
+
data: adapted.adapter === "native" ? { exitCode: result.exitCode, outcome: result.outcome } : { exitCode: result.exitCode, outcome: result.outcome, adapter: adapted.adapter },
|
|
769
|
+
truncated,
|
|
770
|
+
fullOutputPath
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function adaptShellCommand(command) {
|
|
774
|
+
return adaptShellCommandForPlatform(command, process.platform);
|
|
775
|
+
}
|
|
776
|
+
function adaptShellCommandForPlatform(command, platform) {
|
|
777
|
+
if (platform !== "win32") return { command, adapter: "native" };
|
|
778
|
+
const heredoc = convertNodeHereDocForPowerShell(command);
|
|
779
|
+
if (heredoc) return heredoc;
|
|
780
|
+
if (looksLikeUnsupportedUnixMultiline(command)) {
|
|
781
|
+
return createBlockedPowerShellAdapter(
|
|
782
|
+
"Unsupported multi-line Unix shell syntax on Windows PowerShell; use PowerShell-safe commands or Node one-liners."
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
const converted = convertUnixPipelineForPowerShell(command);
|
|
786
|
+
if (converted) return { command: converted, adapter: "powershell-adapted" };
|
|
787
|
+
const readOnlyCommand = convertUnixReadOnlyCommandForPowerShell(command);
|
|
788
|
+
if (readOnlyCommand) return readOnlyCommand;
|
|
789
|
+
if (looksLikeUnsupportedUnixPipeline(command)) {
|
|
790
|
+
const message = "Unsupported Unix pipeline on Windows PowerShell; use PowerShell-safe commands or Node one-liners.";
|
|
791
|
+
return createBlockedPowerShellAdapter(message);
|
|
792
|
+
}
|
|
793
|
+
const unsupportedReadOnlyCommand = blockUnsupportedUnixReadOnlyCommand(command);
|
|
794
|
+
if (unsupportedReadOnlyCommand) return unsupportedReadOnlyCommand;
|
|
795
|
+
return { command, adapter: "native" };
|
|
796
|
+
}
|
|
797
|
+
function convertNodeHereDocForPowerShell(command) {
|
|
798
|
+
const normalized = command.replace(/\r\n/gu, "\n");
|
|
799
|
+
const match = normalized.match(
|
|
800
|
+
/^node\s+(-)?\s*<<\s*(['"]?)([A-Za-z_][\w.-]*)\2\s*\n([\s\S]*)\n\3\s*$/u
|
|
801
|
+
);
|
|
802
|
+
if (!match) return void 0;
|
|
803
|
+
const mode = match[1] === "-" ? "stdin" : "script";
|
|
804
|
+
const body = match[4] ?? "";
|
|
805
|
+
if (!body.trim()) {
|
|
806
|
+
return createBlockedPowerShellAdapter("Unsupported empty node here-doc on Windows PowerShell.");
|
|
807
|
+
}
|
|
808
|
+
const extension = ".cjs";
|
|
809
|
+
const bodyBase64 = Buffer.from(body, "utf8").toString("base64");
|
|
810
|
+
const script = [
|
|
811
|
+
"$ErrorActionPreference='Stop'",
|
|
812
|
+
"$dir = Join-Path ([System.IO.Path]::GetTempPath()) ('linghun-node-heredoc-' + [guid]::NewGuid().ToString('N'))",
|
|
813
|
+
"[System.IO.Directory]::CreateDirectory($dir) | Out-Null",
|
|
814
|
+
`$script = Join-Path $dir ('script${extension}')`,
|
|
815
|
+
`$body = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(${quotePowerShellString(bodyBase64)}))`,
|
|
816
|
+
"[System.IO.File]::WriteAllText($script, $body, [System.Text.UTF8Encoding]::new($false))",
|
|
817
|
+
"try {",
|
|
818
|
+
" & node $script",
|
|
819
|
+
" exit $LASTEXITCODE",
|
|
820
|
+
"} finally {",
|
|
821
|
+
" Remove-Item -LiteralPath $dir -Recurse -Force -ErrorAction SilentlyContinue",
|
|
822
|
+
"}"
|
|
823
|
+
].join("; ");
|
|
824
|
+
return {
|
|
825
|
+
command: ["powershell.exe -NoProfile -NonInteractive -Command", quoteCmdArg(script)].join(" "),
|
|
826
|
+
adapter: "powershell-adapted",
|
|
827
|
+
logCommand: `powershell.exe -NoProfile -NonInteractive -Command <node here-doc adapter: ${mode}>`
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function createBlockedPowerShellAdapter(message) {
|
|
831
|
+
return {
|
|
832
|
+
command: `powershell.exe -NoProfile -NonInteractive -Command "Write-Error ${quotePowerShellString(message)}; exit 1"`,
|
|
833
|
+
adapter: "blocked",
|
|
834
|
+
logCommand: `powershell.exe -NoProfile -NonInteractive -Command <blocked: ${message}>`
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
function convertUnixPipelineForPowerShell(command) {
|
|
838
|
+
const normalized = command.trim();
|
|
839
|
+
const findSed = normalized.match(
|
|
840
|
+
/^find\s+(.+?)\s+-type\s+f\s*\|\s*sed\s+-n\s+['"]?1,(\d+)p['"]?$/iu
|
|
841
|
+
);
|
|
842
|
+
if (findSed) {
|
|
843
|
+
const root = stripShellQuotes(findSed[1] ?? ".");
|
|
844
|
+
const first = Number.parseInt(findSed[2] ?? "0", 10);
|
|
845
|
+
if (Number.isFinite(first) && first > 0) {
|
|
846
|
+
return [
|
|
847
|
+
"powershell.exe -NoProfile -NonInteractive -Command",
|
|
848
|
+
quoteCmdArg(
|
|
849
|
+
`$ErrorActionPreference='Stop'; Get-ChildItem -LiteralPath ${quotePowerShellString(root)} -File -Recurse | Select-Object -First ${first} | ForEach-Object { $_.FullName }`
|
|
850
|
+
)
|
|
851
|
+
].join(" ");
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const findHead = normalized.match(
|
|
855
|
+
/^find\s+((?:"[^"]+"|'[^']+'|[^\s|]+))(?:\s+-type\s+f)?\s*\|\s*head\s+-n\s+(\d+)$/iu
|
|
856
|
+
);
|
|
857
|
+
if (findHead) {
|
|
858
|
+
const root = stripShellQuotes(findHead[1] ?? ".");
|
|
859
|
+
const first = Number.parseInt(findHead[2] ?? "0", 10);
|
|
860
|
+
if (Number.isFinite(first) && first > 0) {
|
|
861
|
+
return [
|
|
862
|
+
"powershell.exe -NoProfile -NonInteractive -Command",
|
|
863
|
+
quoteCmdArg(
|
|
864
|
+
`$ErrorActionPreference='Stop'; Get-ChildItem -LiteralPath ${quotePowerShellString(root)} -File -Recurse | Select-Object -First ${first} | ForEach-Object { $_.FullName }`
|
|
865
|
+
)
|
|
866
|
+
].join(" ");
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
const sedHead = normalized.match(/^sed\s+-n\s+['"]?1,(\d+)p['"]?\s+(.+)$/iu);
|
|
870
|
+
if (sedHead) {
|
|
871
|
+
const first = Number.parseInt(sedHead[1] ?? "0", 10);
|
|
872
|
+
const file = stripShellQuotes(sedHead[2] ?? "");
|
|
873
|
+
if (Number.isFinite(first) && first > 0 && file) {
|
|
874
|
+
return [
|
|
875
|
+
"powershell.exe -NoProfile -NonInteractive -Command",
|
|
876
|
+
quoteCmdArg(
|
|
877
|
+
`$ErrorActionPreference='Stop'; Get-Content -LiteralPath ${quotePowerShellString(file)} -TotalCount ${first}`
|
|
878
|
+
)
|
|
879
|
+
].join(" ");
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
const headFile = normalized.match(/^head\s+-n\s+(\d+)\s+(.+)$/iu);
|
|
883
|
+
if (headFile) {
|
|
884
|
+
const first = Number.parseInt(headFile[1] ?? "0", 10);
|
|
885
|
+
const file = stripShellQuotes(headFile[2] ?? "");
|
|
886
|
+
if (Number.isFinite(first) && first > 0 && file) {
|
|
887
|
+
return [
|
|
888
|
+
"powershell.exe -NoProfile -NonInteractive -Command",
|
|
889
|
+
quoteCmdArg(
|
|
890
|
+
`$ErrorActionPreference='Stop'; Get-Content -LiteralPath ${quotePowerShellString(file)} -TotalCount ${first}`
|
|
891
|
+
)
|
|
892
|
+
].join(" ");
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return void 0;
|
|
896
|
+
}
|
|
897
|
+
function convertUnixReadOnlyCommandForPowerShell(command) {
|
|
898
|
+
const tokens = tokenizeSimpleShellCommand(command);
|
|
899
|
+
if (!tokens) return void 0;
|
|
900
|
+
const [program = "", ...args] = tokens;
|
|
901
|
+
const lowerProgram = program.toLowerCase();
|
|
902
|
+
let script;
|
|
903
|
+
if (lowerProgram === "cat") {
|
|
904
|
+
if (args.length === 1) {
|
|
905
|
+
script = `Get-Content -LiteralPath ${quotePowerShellString(args[0] ?? "")}`;
|
|
906
|
+
}
|
|
907
|
+
} else if (lowerProgram === "ls") {
|
|
908
|
+
script = convertLsForPowerShell(args);
|
|
909
|
+
} else if (lowerProgram === "grep") {
|
|
910
|
+
script = convertGrepForPowerShell(args);
|
|
911
|
+
} else if (lowerProgram === "pwd") {
|
|
912
|
+
if (args.length === 0) script = "Get-Location | ForEach-Object { $_.Path }";
|
|
913
|
+
} else if (lowerProgram === "which") {
|
|
914
|
+
if (args.length === 1) {
|
|
915
|
+
script = [
|
|
916
|
+
`$cmd = Get-Command -ErrorAction Stop ${quotePowerShellString(args[0] ?? "")}`,
|
|
917
|
+
"if ($cmd.Source) { $cmd.Source } elseif ($cmd.Path) { $cmd.Path } else { $cmd.Name }"
|
|
918
|
+
].join("; ");
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (!script) return void 0;
|
|
922
|
+
return {
|
|
923
|
+
command: [
|
|
924
|
+
"powershell.exe -NoProfile -NonInteractive -Command",
|
|
925
|
+
quoteCmdArg(`$ErrorActionPreference='Stop'; ${script}`)
|
|
926
|
+
].join(" "),
|
|
927
|
+
adapter: "powershell-adapted"
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
function convertLsForPowerShell(args) {
|
|
931
|
+
let force = false;
|
|
932
|
+
let path = ".";
|
|
933
|
+
for (const arg of args) {
|
|
934
|
+
if (arg === "-a" || arg === "-la" || arg === "-al") {
|
|
935
|
+
force = true;
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
938
|
+
if (arg === "-l") {
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
if (arg.startsWith("-")) return void 0;
|
|
942
|
+
if (path !== ".") return void 0;
|
|
943
|
+
path = arg;
|
|
944
|
+
}
|
|
945
|
+
const forceArg = force ? " -Force" : "";
|
|
946
|
+
return `Get-ChildItem -LiteralPath ${quotePowerShellString(path)}${forceArg}`;
|
|
947
|
+
}
|
|
948
|
+
function convertGrepForPowerShell(args) {
|
|
949
|
+
const recursive = args[0] === "-R" || args[0] === "-r";
|
|
950
|
+
const offset = recursive ? 1 : 0;
|
|
951
|
+
const pattern = args[offset];
|
|
952
|
+
const target = args[offset + 1];
|
|
953
|
+
if (!pattern || !target || args.length !== offset + 2) return void 0;
|
|
954
|
+
if (recursive) {
|
|
955
|
+
return [
|
|
956
|
+
`Get-ChildItem -LiteralPath ${quotePowerShellString(target)} -File -Recurse`,
|
|
957
|
+
`Select-String -Pattern ${quotePowerShellString(pattern)}`
|
|
958
|
+
].join(" | ");
|
|
959
|
+
}
|
|
960
|
+
return `Select-String -Pattern ${quotePowerShellString(pattern)} -LiteralPath ${quotePowerShellString(target)}`;
|
|
961
|
+
}
|
|
962
|
+
function looksLikeUnsupportedUnixPipeline(command) {
|
|
963
|
+
return /\bfind\s+.+\|\s*(?:sed|head)\b/iu.test(command) || /\b(?:cat|grep|ls|find|sed|head)\b.+\|\s*(?:sed|head|grep|awk|xargs)\b/isu.test(command) || /\bsed\s+-n\b/iu.test(command) || /\bhead\s+-n\b/iu.test(command);
|
|
964
|
+
}
|
|
965
|
+
function looksLikeUnsupportedUnixMultiline(command) {
|
|
966
|
+
const normalized = command.replace(/\r\n/gu, "\n");
|
|
967
|
+
if (!/\n/u.test(normalized)) return false;
|
|
968
|
+
if (/<<\s*['"]?[A-Za-z_][\w.-]*['"]?/u.test(normalized)) return true;
|
|
969
|
+
return normalized.split("\n").some((line) => /^\s*(?:cat|ls|grep|find|sed|head|which|pwd)\b/iu.test(line));
|
|
970
|
+
}
|
|
971
|
+
function blockUnsupportedUnixReadOnlyCommand(command) {
|
|
972
|
+
const tokens = tokenizeSimpleShellCommand(command);
|
|
973
|
+
const program = tokens?.[0]?.toLowerCase() ?? command.trim().match(/^(\w+)/u)?.[1]?.toLowerCase();
|
|
974
|
+
if (!program || !["cat", "ls", "grep", "pwd", "which"].includes(program)) return void 0;
|
|
975
|
+
return createBlockedPowerShellAdapter(
|
|
976
|
+
`Unsupported ${program} form on Windows PowerShell; use a simple read-only form or a PowerShell-safe command.`
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
function tokenizeSimpleShellCommand(command) {
|
|
980
|
+
const trimmed = command.trim();
|
|
981
|
+
if (!trimmed || /[\r\n|;&<>]/u.test(trimmed)) return void 0;
|
|
982
|
+
const tokens = [];
|
|
983
|
+
let current = "";
|
|
984
|
+
let quote;
|
|
985
|
+
for (let index = 0; index < trimmed.length; index += 1) {
|
|
986
|
+
const char = trimmed[index];
|
|
987
|
+
if (!char) continue;
|
|
988
|
+
if (quote) {
|
|
989
|
+
if (char === quote) {
|
|
990
|
+
quote = void 0;
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
current += char;
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
if (char === "'" || char === '"') {
|
|
997
|
+
quote = char;
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
if (/\s/u.test(char)) {
|
|
1001
|
+
if (current) {
|
|
1002
|
+
tokens.push(current);
|
|
1003
|
+
current = "";
|
|
1004
|
+
}
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
current += char;
|
|
1008
|
+
}
|
|
1009
|
+
if (quote) return void 0;
|
|
1010
|
+
if (current) tokens.push(current);
|
|
1011
|
+
return tokens.length > 0 ? tokens : void 0;
|
|
1012
|
+
}
|
|
1013
|
+
function stripShellQuotes(value) {
|
|
1014
|
+
const trimmed = value.trim();
|
|
1015
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
1016
|
+
return trimmed.slice(1, -1);
|
|
1017
|
+
}
|
|
1018
|
+
return trimmed;
|
|
1019
|
+
}
|
|
1020
|
+
function quotePowerShellString(value) {
|
|
1021
|
+
return `'${value.replace(/'/gu, "''")}'`;
|
|
1022
|
+
}
|
|
1023
|
+
function quoteCmdArg(value) {
|
|
1024
|
+
return `"${value.replace(/"/gu, '\\"')}"`;
|
|
1025
|
+
}
|
|
1026
|
+
function summarizeOriginalShellCommand(command) {
|
|
1027
|
+
if (!/\n/u.test(command)) return command;
|
|
1028
|
+
const lines = command.split(/\r?\n/u);
|
|
1029
|
+
const firstLine = lines[0]?.trim() || "multi-line command";
|
|
1030
|
+
return `${firstLine} <${lines.length} lines>`;
|
|
1031
|
+
}
|
|
1032
|
+
async function todoTool(input, context) {
|
|
1033
|
+
if (input.action === "add") {
|
|
1034
|
+
if (context.todos.length >= MAX_TODO_ITEMS) {
|
|
1035
|
+
throw new Error(`Todo \u5DF2\u8FBE\u5230\u4E0A\u9650 ${MAX_TODO_ITEMS} \u6761\u3002\u5EFA\u8BAE\uFF1A\u5148\u5B8C\u6210\u6216\u6E05\u7406\u65E7 Todo\u3002`);
|
|
1036
|
+
}
|
|
1037
|
+
const id = String(context.todos.length + 1);
|
|
1038
|
+
context.todos.push({
|
|
1039
|
+
id,
|
|
1040
|
+
content: input.content,
|
|
1041
|
+
status: "pending"
|
|
1042
|
+
});
|
|
1043
|
+
return {
|
|
1044
|
+
text: `Todo created: id=${id}
|
|
1045
|
+
${formatTodos(context.todos)}`,
|
|
1046
|
+
data: { items: context.todos, createdId: id }
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
if (input.action === "start") {
|
|
1050
|
+
const item = findTodo(context.todos, input.id, input.content);
|
|
1051
|
+
for (const todo of context.todos) {
|
|
1052
|
+
if (todo.status === "in_progress") {
|
|
1053
|
+
todo.status = "pending";
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
item.status = "in_progress";
|
|
1057
|
+
item.evidence = input.evidence ?? item.evidence;
|
|
1058
|
+
}
|
|
1059
|
+
if (input.action === "done") {
|
|
1060
|
+
const item = findTodo(context.todos, input.id, input.content);
|
|
1061
|
+
item.status = "completed";
|
|
1062
|
+
item.evidence = input.evidence ?? item.evidence;
|
|
1063
|
+
}
|
|
1064
|
+
if (input.action === "block") {
|
|
1065
|
+
const item = findTodo(context.todos, input.id, input.content);
|
|
1066
|
+
item.status = "blocked";
|
|
1067
|
+
item.evidence = input.evidence ?? item.evidence;
|
|
1068
|
+
}
|
|
1069
|
+
return {
|
|
1070
|
+
text: formatTodos(context.todos),
|
|
1071
|
+
data: { items: context.todos }
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
async function diffTool(input, context) {
|
|
1075
|
+
const changedFiles = unique(input.files ?? context.changedFiles);
|
|
1076
|
+
const patchSummaries = context.patchSummaries ?? {};
|
|
1077
|
+
const addedLines = changedFiles.reduce(
|
|
1078
|
+
(total, file) => total + (patchSummaries[file]?.addedLines ?? 0),
|
|
1079
|
+
0
|
|
1080
|
+
);
|
|
1081
|
+
const removedLines = changedFiles.reduce(
|
|
1082
|
+
(total, file) => total + (patchSummaries[file]?.removedLines ?? 0),
|
|
1083
|
+
0
|
|
1084
|
+
);
|
|
1085
|
+
const riskyFiles = changedFiles.filter(
|
|
1086
|
+
(file) => file.includes(".env") || file.startsWith(".git/")
|
|
1087
|
+
);
|
|
1088
|
+
const summary = {
|
|
1089
|
+
changedFiles,
|
|
1090
|
+
addedLines,
|
|
1091
|
+
removedLines,
|
|
1092
|
+
summary: changedFiles.length === 0 ? "\u672C\u8F6E\u6682\u65E0\u5DE5\u5177\u5199\u5165\u6539\u52A8\u3002" : `\u672C\u8F6E\u5DE5\u5177\u6539\u52A8 ${changedFiles.length} \u4E2A\u6587\u4EF6\uFF0C+${addedLines} -${removedLines}\u3002`,
|
|
1093
|
+
riskyFiles
|
|
1094
|
+
};
|
|
1095
|
+
return {
|
|
1096
|
+
text: `${summary.summary}
|
|
1097
|
+
${changedFiles.map((file) => `- ${file}`).join("\n")}`.trim(),
|
|
1098
|
+
data: summary
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
async function webSearchTool(input, _context) {
|
|
1102
|
+
const start = Date.now();
|
|
1103
|
+
const result = await bingSearch(input);
|
|
1104
|
+
if (!result.ok) {
|
|
1105
|
+
return {
|
|
1106
|
+
text: `WebSearch failed: ${result.error}`,
|
|
1107
|
+
data: {
|
|
1108
|
+
query: input.query,
|
|
1109
|
+
results: [],
|
|
1110
|
+
searches: 1,
|
|
1111
|
+
count: 0,
|
|
1112
|
+
durationMs: Date.now() - start,
|
|
1113
|
+
error: result.error
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
const filtered = applyDomainFilter(
|
|
1118
|
+
result.results,
|
|
1119
|
+
input.allowed_domains,
|
|
1120
|
+
input.blocked_domains
|
|
1121
|
+
);
|
|
1122
|
+
return formatSearchOutput(input.query, filtered, Date.now() - start);
|
|
1123
|
+
}
|
|
1124
|
+
async function webFetchTool(input, _context) {
|
|
1125
|
+
const start = Date.now();
|
|
1126
|
+
const result = await webFetch(input);
|
|
1127
|
+
return formatFetchOutput(input.url, result, Date.now() - start);
|
|
1128
|
+
}
|
|
1129
|
+
function resolveWorkspacePath(workspaceRoot, inputPath) {
|
|
1130
|
+
if (!inputPath) {
|
|
1131
|
+
throw new Error("\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A\u3002\u5EFA\u8BAE\uFF1A\u4F20\u5165\u5DE5\u4F5C\u533A\u5185\u76F8\u5BF9\u8DEF\u5F84\u3002");
|
|
1132
|
+
}
|
|
1133
|
+
const target = resolve(workspaceRoot, inputPath);
|
|
1134
|
+
const rel = relative(workspaceRoot, target);
|
|
1135
|
+
if (rel.startsWith("..") || isAbsolute(rel) || rel === "" && target !== resolve(workspaceRoot)) {
|
|
1136
|
+
throw new Error(`\u8DEF\u5F84\u8D8A\u754C\uFF1A${inputPath}\u3002\u5EFA\u8BAE\uFF1A\u53EA\u64CD\u4F5C\u5F53\u524D\u5DE5\u4F5C\u533A\u5185\u6587\u4EF6\u3002`);
|
|
1137
|
+
}
|
|
1138
|
+
return target;
|
|
1139
|
+
}
|
|
1140
|
+
function relativePath(workspaceRoot, filePath) {
|
|
1141
|
+
return relative(workspaceRoot, filePath).replaceAll("\\", "/") || ".";
|
|
1142
|
+
}
|
|
1143
|
+
function ensureUnique(content, oldText) {
|
|
1144
|
+
if (!oldText) {
|
|
1145
|
+
throw new Error("oldText \u4E0D\u80FD\u4E3A\u7A7A\u3002\u5EFA\u8BAE\uFF1A\u63D0\u4F9B\u8DB3\u591F\u4E0A\u4E0B\u6587\u7684\u552F\u4E00\u7247\u6BB5\u3002");
|
|
1146
|
+
}
|
|
1147
|
+
const count = content.split(oldText).length - 1;
|
|
1148
|
+
if (count === 0) {
|
|
1149
|
+
throw new Error("\u672A\u627E\u5230 oldText\u3002\u5EFA\u8BAE\uFF1A\u5148 Read \u6587\u4EF6\u786E\u8BA4\u6700\u65B0\u5185\u5BB9\u3002");
|
|
1150
|
+
}
|
|
1151
|
+
if (count > 1) {
|
|
1152
|
+
throw new Error(`oldText \u51FA\u73B0 ${count} \u6B21\uFF0C\u4E0D\u552F\u4E00\u3002\u5EFA\u8BAE\uFF1A\u589E\u52A0\u4E0A\u4E0B\u6587\u540E\u91CD\u8BD5\u3002`);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
function recordChangedFile(context, filePath) {
|
|
1156
|
+
context.changedFiles = unique([
|
|
1157
|
+
...context.changedFiles,
|
|
1158
|
+
relativePath(context.workspaceRoot, filePath)
|
|
1159
|
+
]);
|
|
1160
|
+
}
|
|
1161
|
+
async function readExistingFile(filePath) {
|
|
1162
|
+
try {
|
|
1163
|
+
const [content, info] = await Promise.all([readFile(filePath, "utf8"), stat(filePath)]);
|
|
1164
|
+
return { content, hash: hashText(content), mtimeMs: info.mtimeMs, size: info.size };
|
|
1165
|
+
} catch (error) {
|
|
1166
|
+
const code = typeof error === "object" && error !== null ? error.code : void 0;
|
|
1167
|
+
if (code === "ENOENT") {
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
throw error;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
function ensureReadBeforeEdit(context, filePath, before, expectedHash) {
|
|
1174
|
+
if (!before) {
|
|
1175
|
+
return { source: "new-file" };
|
|
1176
|
+
}
|
|
1177
|
+
const rel = relativePath(context.workspaceRoot, filePath);
|
|
1178
|
+
if (expectedHash) {
|
|
1179
|
+
if (expectedHash !== before.hash) {
|
|
1180
|
+
throw new Error(
|
|
1181
|
+
`\u6587\u4EF6\u5DF2\u53D8\u5316\uFF1A${rel} \u5F53\u524D hash \u4E0E expectedHash \u4E0D\u4E00\u81F4\u3002\u5EFA\u8BAE\uFF1A\u91CD\u65B0 Read \u6587\u4EF6\u540E\u6309\u6700\u65B0\u5185\u5BB9\u91CD\u8BD5\uFF0C\u907F\u514D\u8986\u76D6\u5916\u90E8\u4FEE\u6539\u3002`
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
return { source: "expectedHash", beforeHash: before.hash };
|
|
1185
|
+
}
|
|
1186
|
+
const snapshot = context.readSnapshots?.[rel];
|
|
1187
|
+
if (!snapshot) {
|
|
1188
|
+
throw new Error(
|
|
1189
|
+
`\u7F16\u8F91\u524D\u672A\u8BFB\u53D6\uFF1A${rel}\u3002\u5EFA\u8BAE\uFF1A\u5148\u8FD0\u884C Read \u83B7\u53D6\u6700\u65B0\u5185\u5BB9\uFF0C\u6216\u4F20\u5165 expectedHash \u540E\u518D\u6267\u884C\u5199\u5165\u3002`
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
if (snapshot.hash !== before.hash || snapshot.mtimeMs !== before.mtimeMs || snapshot.size !== before.size) {
|
|
1193
|
+
throw new Error(
|
|
1194
|
+
`\u6587\u4EF6\u5DF2\u53D8\u5316\uFF1A${rel} \u81EA\u4E0A\u6B21 Read \u540E\u88AB\u4FEE\u6539\u3002\u5EFA\u8BAE\uFF1A\u91CD\u65B0 Read \u6587\u4EF6\u540E\u6309\u6700\u65B0\u5185\u5BB9\u91CD\u8BD5\uFF0C\u907F\u514D\u9759\u9ED8\u8986\u76D6\u3002`
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
return { source: "read-snapshot", beforeHash: before.hash };
|
|
1198
|
+
}
|
|
1199
|
+
function rememberReadSnapshot(context, filePath, content, info) {
|
|
1200
|
+
const rel = relativePath(context.workspaceRoot, filePath);
|
|
1201
|
+
context.readSnapshots = {
|
|
1202
|
+
...context.readSnapshots ?? {},
|
|
1203
|
+
[rel]: { path: rel, hash: hashText(content), mtimeMs: info.mtimeMs, size: info.size }
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
function createEditOutput(context, operation, filePath, before, after, readGuard, editCount = 1) {
|
|
1207
|
+
const rel = relativePath(context.workspaceRoot, filePath);
|
|
1208
|
+
const summary = createPatchSummary([rel], before, after);
|
|
1209
|
+
context.patchSummaries = { ...context.patchSummaries ?? {}, [rel]: summary };
|
|
1210
|
+
const newlineBefore = detectNewlineStyle(before);
|
|
1211
|
+
const newlineAfter = detectNewlineStyle(after);
|
|
1212
|
+
const text = [
|
|
1213
|
+
`${operation} \u5DF2\u5B8C\u6210\uFF1A${rel}`,
|
|
1214
|
+
`- \u8865\u4E01\uFF1A+${summary.addedLines} -${summary.removedLines}`,
|
|
1215
|
+
`- \u6539\u52A8\u6587\u4EF6\uFF1A${rel}`,
|
|
1216
|
+
"- \u8BFB\u53D6\u4FDD\u62A4\uFF1A\u5DF2\u542F\u7528",
|
|
1217
|
+
`- \u6362\u884C\uFF1A${newlineBefore} -> ${newlineAfter}`,
|
|
1218
|
+
"- \u4E0B\u4E00\u6B65\uFF1A\u7528 Diff \u6216 /details \u67E5\u770B\u8865\u4E01\u6458\u8981\uFF1B\u9700\u8981\u7EE7\u7EED\u7F16\u8F91\u8BF7\u57FA\u4E8E\u6700\u65B0\u5185\u5BB9\u3002"
|
|
1219
|
+
].join("\n");
|
|
1220
|
+
return {
|
|
1221
|
+
text,
|
|
1222
|
+
summary: `${operation} ${rel}: +${summary.addedLines} -${summary.removedLines}; changed files 1`,
|
|
1223
|
+
preview: text,
|
|
1224
|
+
details: createPatchDetails(operation, rel, before, after, readGuard, editCount),
|
|
1225
|
+
data: {
|
|
1226
|
+
...summary,
|
|
1227
|
+
operation,
|
|
1228
|
+
editCount,
|
|
1229
|
+
readGuard: readGuard.source,
|
|
1230
|
+
beforeHash: readGuard.beforeHash,
|
|
1231
|
+
afterHash: hashText(after),
|
|
1232
|
+
newlineBefore,
|
|
1233
|
+
newlineAfter,
|
|
1234
|
+
encoding: "utf8"
|
|
1235
|
+
},
|
|
1236
|
+
changedFiles: [rel]
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
function createPatchSummary(changedFiles, before, after) {
|
|
1240
|
+
const beforeLines = before.length > 0 ? before.split(/\r?\n/u) : [];
|
|
1241
|
+
const afterLines = after.length > 0 ? after.split(/\r?\n/u) : [];
|
|
1242
|
+
const common = /* @__PURE__ */ new Map();
|
|
1243
|
+
for (const line of beforeLines) {
|
|
1244
|
+
common.set(line, (common.get(line) ?? 0) + 1);
|
|
1245
|
+
}
|
|
1246
|
+
let unchanged = 0;
|
|
1247
|
+
for (const line of afterLines) {
|
|
1248
|
+
const count = common.get(line) ?? 0;
|
|
1249
|
+
if (count > 0) {
|
|
1250
|
+
unchanged += 1;
|
|
1251
|
+
common.set(line, count - 1);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
const addedLines = Math.max(afterLines.length - unchanged, 0);
|
|
1255
|
+
const removedLines = Math.max(beforeLines.length - unchanged, 0);
|
|
1256
|
+
return {
|
|
1257
|
+
changedFiles,
|
|
1258
|
+
addedLines,
|
|
1259
|
+
removedLines,
|
|
1260
|
+
summary: `\u7F16\u8F91\u6539\u52A8 ${changedFiles.length} \u4E2A\u6587\u4EF6\uFF0C+${addedLines} -${removedLines}\u3002`,
|
|
1261
|
+
riskyFiles: changedFiles.filter((file) => file.includes(".env") || file.startsWith(".git/"))
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
function createPatchDetails(operation, rel, before, after, readGuard, editCount) {
|
|
1265
|
+
const beforeLines = before.split(/\r?\n/u);
|
|
1266
|
+
const afterLines = after.split(/\r?\n/u);
|
|
1267
|
+
return [
|
|
1268
|
+
`operation: ${operation}`,
|
|
1269
|
+
`file: ${rel}`,
|
|
1270
|
+
`edit count: ${editCount}`,
|
|
1271
|
+
"read protection: enabled",
|
|
1272
|
+
"encoding: utf8",
|
|
1273
|
+
`newline: ${detectNewlineStyle(before)} -> ${detectNewlineStyle(after)}`,
|
|
1274
|
+
"--- before (first changed context)",
|
|
1275
|
+
...previewChangedLines(beforeLines, afterLines, "-"),
|
|
1276
|
+
"+++ after (first changed context)",
|
|
1277
|
+
...previewChangedLines(afterLines, beforeLines, "+")
|
|
1278
|
+
].join("\n");
|
|
1279
|
+
}
|
|
1280
|
+
function previewChangedLines(primary, other, marker) {
|
|
1281
|
+
const limit = 12;
|
|
1282
|
+
const changed = primary.filter((line, index) => other[index] !== line).slice(0, limit);
|
|
1283
|
+
if (changed.length === 0) {
|
|
1284
|
+
return [`${marker} <no line-level preview; content hash changed or unchanged>`];
|
|
1285
|
+
}
|
|
1286
|
+
return changed.map((line) => `${marker} ${line}`);
|
|
1287
|
+
}
|
|
1288
|
+
function hashText(content) {
|
|
1289
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
1290
|
+
}
|
|
1291
|
+
function detectNewlineStyle(content) {
|
|
1292
|
+
const crlf = (content.match(/\r\n/gu) ?? []).length;
|
|
1293
|
+
const lf = (content.match(/(?<!\r)\n/gu) ?? []).length;
|
|
1294
|
+
if (crlf > 0 && lf > 0) return "mixed";
|
|
1295
|
+
if (crlf > 0) return "crlf";
|
|
1296
|
+
if (lf > 0) return "lf";
|
|
1297
|
+
return "none";
|
|
1298
|
+
}
|
|
1299
|
+
async function* listFiles(root, shouldStop, searchRoot = root) {
|
|
1300
|
+
if (shouldStop?.()) return;
|
|
1301
|
+
const current = await stat(root);
|
|
1302
|
+
if (current.isFile()) {
|
|
1303
|
+
yield root;
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
1307
|
+
for (const entry of entries) {
|
|
1308
|
+
if (shouldStop?.()) return;
|
|
1309
|
+
const entryPath = join(root, entry.name);
|
|
1310
|
+
if (isDefaultSearchExcludedPath(entryPath, searchRoot, entry)) {
|
|
1311
|
+
continue;
|
|
1312
|
+
}
|
|
1313
|
+
if (entry.isDirectory()) {
|
|
1314
|
+
yield* listFiles(entryPath, shouldStop, searchRoot);
|
|
1315
|
+
continue;
|
|
1316
|
+
}
|
|
1317
|
+
if (entry.isFile()) {
|
|
1318
|
+
yield entryPath;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
function isDefaultSearchExcludedPath(entryPath, searchRoot, entry) {
|
|
1323
|
+
if (entry.isDirectory() && SEARCH_EXCLUDED_DIR_NAMES.includes(entry.name)) {
|
|
1324
|
+
return true;
|
|
1325
|
+
}
|
|
1326
|
+
if (entry.isFile() && SEARCH_EXCLUDED_FILE_SUFFIXES.some((suffix) => entry.name.endsWith(suffix))) {
|
|
1327
|
+
return true;
|
|
1328
|
+
}
|
|
1329
|
+
const rel = stripCurrentDirectoryPrefix(relative(searchRoot, entryPath).replaceAll("\\", "/"));
|
|
1330
|
+
return SEARCH_EXCLUDED_PATH_PREFIXES.some(
|
|
1331
|
+
(prefix) => rel === prefix || rel.startsWith(`${prefix}/`)
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
async function safeReadText(filePath, context) {
|
|
1335
|
+
try {
|
|
1336
|
+
return await readFile(filePath, "utf8");
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
await context.onProgress?.({
|
|
1339
|
+
toolName: "Grep",
|
|
1340
|
+
stream: "system",
|
|
1341
|
+
text: `read skipped: ${relativePath(context.workspaceRoot, filePath)} (${formatDiagnosticError(error)})`
|
|
1342
|
+
});
|
|
1343
|
+
return null;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
function formatDiagnosticError(error) {
|
|
1347
|
+
return error instanceof Error ? error.message.replace(/\s+/g, " ").trim() : String(error);
|
|
1348
|
+
}
|
|
1349
|
+
async function tryRipgrepSearch(input, context, root, limit) {
|
|
1350
|
+
const args = [
|
|
1351
|
+
"--line-number",
|
|
1352
|
+
"--no-heading",
|
|
1353
|
+
"--color",
|
|
1354
|
+
"never",
|
|
1355
|
+
"--hidden",
|
|
1356
|
+
"--no-ignore",
|
|
1357
|
+
"--max-columns",
|
|
1358
|
+
"500",
|
|
1359
|
+
...createRgExcludeArgs(root, context.workspaceRoot)
|
|
1360
|
+
];
|
|
1361
|
+
const pattern = input.pattern.startsWith("(?i)") ? input.pattern.slice(4) : input.pattern;
|
|
1362
|
+
if (input.pattern.startsWith("(?i)")) {
|
|
1363
|
+
args.push("-i");
|
|
1364
|
+
}
|
|
1365
|
+
args.push("-e", pattern, relativePath(context.workspaceRoot, root));
|
|
1366
|
+
const result = await runRipgrep(
|
|
1367
|
+
args,
|
|
1368
|
+
context,
|
|
1369
|
+
limit,
|
|
1370
|
+
(line) => normalizeRgGrepLine(line, context.workspaceRoot)
|
|
1371
|
+
);
|
|
1372
|
+
if (!result) return null;
|
|
1373
|
+
const matches = result.lines.slice(0, limit);
|
|
1374
|
+
return { matches, truncated: result.truncated };
|
|
1375
|
+
}
|
|
1376
|
+
async function tryRipgrepFiles(input, context, root, limit) {
|
|
1377
|
+
const args = [
|
|
1378
|
+
"--files",
|
|
1379
|
+
"--color",
|
|
1380
|
+
"never",
|
|
1381
|
+
"--hidden",
|
|
1382
|
+
"--no-ignore",
|
|
1383
|
+
...createRgExcludeArgs(root, context.workspaceRoot)
|
|
1384
|
+
];
|
|
1385
|
+
args.push(relativePath(context.workspaceRoot, root));
|
|
1386
|
+
const matcher = globToRegExp(input.pattern);
|
|
1387
|
+
const result = await runRipgrep(args, context, limit, (line) => {
|
|
1388
|
+
const normalized = normalizeRgPath(line, context.workspaceRoot, root);
|
|
1389
|
+
return matcher.test(normalized) || matcher.test(basename(normalized)) ? normalized : null;
|
|
1390
|
+
});
|
|
1391
|
+
if (!result) return null;
|
|
1392
|
+
return { matches: result.lines.slice(0, limit), truncated: result.truncated };
|
|
1393
|
+
}
|
|
1394
|
+
function createRgExcludeArgs(root, workspaceRoot) {
|
|
1395
|
+
const searchRootRel = stripCurrentDirectoryPrefix(
|
|
1396
|
+
relative(workspaceRoot, root).replaceAll("\\", "/")
|
|
1397
|
+
);
|
|
1398
|
+
return [
|
|
1399
|
+
...SEARCH_EXCLUDED_DIR_NAMES.filter(
|
|
1400
|
+
(dir) => !isExplicitExcludedDirRoot(searchRootRel, dir)
|
|
1401
|
+
).map((dir) => `!**/${dir}/**`),
|
|
1402
|
+
...SEARCH_EXCLUDED_PATH_PREFIXES.filter((path) => !isSameOrInside(searchRootRel, path)).map(
|
|
1403
|
+
(path) => `!**/${path}/**`
|
|
1404
|
+
),
|
|
1405
|
+
...SEARCH_EXCLUDED_FILE_SUFFIXES.filter((suffix) => !searchRootRel.endsWith(suffix)).map(
|
|
1406
|
+
(suffix) => `!**/*${suffix}`
|
|
1407
|
+
)
|
|
1408
|
+
].flatMap((pattern) => ["--glob", pattern]);
|
|
1409
|
+
}
|
|
1410
|
+
function isExplicitExcludedDirRoot(searchRootRel, dir) {
|
|
1411
|
+
return searchRootRel === dir || searchRootRel.endsWith(`/${dir}`);
|
|
1412
|
+
}
|
|
1413
|
+
function isSameOrInside(value, parent) {
|
|
1414
|
+
return value === parent || value.startsWith(`${parent}/`);
|
|
1415
|
+
}
|
|
1416
|
+
function normalizeRgPath(line, workspaceRoot, root) {
|
|
1417
|
+
const filePath = resolve(workspaceRoot, line);
|
|
1418
|
+
const rel = relative(root, filePath);
|
|
1419
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
1420
|
+
return stripCurrentDirectoryPrefix(line.replaceAll("\\", "/"));
|
|
1421
|
+
}
|
|
1422
|
+
return stripCurrentDirectoryPrefix(rel.replaceAll("\\", "/"));
|
|
1423
|
+
}
|
|
1424
|
+
function normalizeRgGrepLine(line, workspaceRoot) {
|
|
1425
|
+
const firstColon = line.indexOf(":");
|
|
1426
|
+
if (firstColon < 0) return normalizeRgPath(line, workspaceRoot, workspaceRoot);
|
|
1427
|
+
const secondColon = line.indexOf(":", firstColon + 1);
|
|
1428
|
+
if (secondColon < 0) return normalizeRgPath(line, workspaceRoot, workspaceRoot);
|
|
1429
|
+
const file = normalizeRgPath(line.slice(0, firstColon), workspaceRoot, workspaceRoot);
|
|
1430
|
+
return `${file}${line.slice(firstColon)}`;
|
|
1431
|
+
}
|
|
1432
|
+
function stripCurrentDirectoryPrefix(value) {
|
|
1433
|
+
return value.replace(/^\.\/+/u, "");
|
|
1434
|
+
}
|
|
1435
|
+
async function runRipgrep(args, context, limit, mapLine = (line) => line) {
|
|
1436
|
+
return new Promise((resolvePromise) => {
|
|
1437
|
+
const child = spawn("rg", args, {
|
|
1438
|
+
cwd: context.workspaceRoot,
|
|
1439
|
+
shell: false,
|
|
1440
|
+
windowsHide: true
|
|
1441
|
+
});
|
|
1442
|
+
const lines = [];
|
|
1443
|
+
let pending = "";
|
|
1444
|
+
let stderr = "";
|
|
1445
|
+
let resolved = false;
|
|
1446
|
+
let killedForLimit = false;
|
|
1447
|
+
const timeout = setTimeout(() => {
|
|
1448
|
+
if (!resolved) {
|
|
1449
|
+
child.kill();
|
|
1450
|
+
}
|
|
1451
|
+
}, RG_TIMEOUT_MS);
|
|
1452
|
+
const abort = () => child.kill();
|
|
1453
|
+
context.abortSignal?.addEventListener("abort", abort, { once: true });
|
|
1454
|
+
const finish = (value) => {
|
|
1455
|
+
if (resolved) return;
|
|
1456
|
+
resolved = true;
|
|
1457
|
+
clearTimeout(timeout);
|
|
1458
|
+
context.abortSignal?.removeEventListener("abort", abort);
|
|
1459
|
+
resolvePromise(value);
|
|
1460
|
+
};
|
|
1461
|
+
child.stdout.setEncoding("utf8");
|
|
1462
|
+
child.stdout.on("data", (chunk) => {
|
|
1463
|
+
pending += chunk;
|
|
1464
|
+
const parts = pending.split(/\r?\n/u);
|
|
1465
|
+
pending = parts.pop() ?? "";
|
|
1466
|
+
for (const line of parts) {
|
|
1467
|
+
if (!line) continue;
|
|
1468
|
+
const mapped = mapLine(line);
|
|
1469
|
+
if (!mapped) continue;
|
|
1470
|
+
lines.push(mapped);
|
|
1471
|
+
if (lines.length >= limit) {
|
|
1472
|
+
killedForLimit = true;
|
|
1473
|
+
child.kill();
|
|
1474
|
+
break;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
});
|
|
1478
|
+
child.stderr.setEncoding("utf8");
|
|
1479
|
+
child.stderr.on("data", (chunk) => {
|
|
1480
|
+
stderr += chunk;
|
|
1481
|
+
});
|
|
1482
|
+
child.on("error", () => finish(null));
|
|
1483
|
+
child.on("close", (code) => {
|
|
1484
|
+
if (pending && lines.length < limit) {
|
|
1485
|
+
const mapped = mapLine(pending);
|
|
1486
|
+
if (mapped) {
|
|
1487
|
+
lines.push(mapped);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
if (killedForLimit) {
|
|
1491
|
+
finish({ lines, truncated: true });
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
if (code === 0 || code === 1) {
|
|
1495
|
+
finish({ lines, truncated: false });
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
if (stderr) {
|
|
1499
|
+
finish(null);
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
finish(null);
|
|
1503
|
+
});
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
function globToRegExp(pattern) {
|
|
1507
|
+
const startsWithDoubleStarSlash = pattern.startsWith("**/");
|
|
1508
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "::DOUBLE_STAR::").replace(/\*/g, "[^/]*").replace(/::DOUBLE_STAR::/g, ".*").replace(/\?/g, ".");
|
|
1509
|
+
let result = `^${escaped}$`;
|
|
1510
|
+
if (startsWithDoubleStarSlash) {
|
|
1511
|
+
result = result.replace(/^\^\.\*\//u, "^(?:.*/)?");
|
|
1512
|
+
}
|
|
1513
|
+
return new RegExp(result);
|
|
1514
|
+
}
|
|
1515
|
+
function runShell(command, cwd, timeoutMs, signal, onProgress) {
|
|
1516
|
+
return new Promise((resolvePromise) => {
|
|
1517
|
+
const child = spawn(command, { cwd, shell: true, windowsHide: true });
|
|
1518
|
+
let output = "";
|
|
1519
|
+
let settled = false;
|
|
1520
|
+
let forcedKillTimer;
|
|
1521
|
+
let stoppingOutcome;
|
|
1522
|
+
let childClosed = false;
|
|
1523
|
+
const finish = (exitCode, nextOutput = output, outcome = "completed") => {
|
|
1524
|
+
if (settled) {
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
settled = true;
|
|
1528
|
+
clearTimeout(timer);
|
|
1529
|
+
if (forcedKillTimer && outcome === "completed") {
|
|
1530
|
+
clearTimeout(forcedKillTimer);
|
|
1531
|
+
}
|
|
1532
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1533
|
+
resolvePromise({ exitCode, output: nextOutput, outcome });
|
|
1534
|
+
};
|
|
1535
|
+
const waitForChildClose = () => new Promise((resolveClose) => {
|
|
1536
|
+
if (childClosed || child.exitCode !== null || child.signalCode !== null) {
|
|
1537
|
+
resolveClose();
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
child.once("close", () => resolveClose());
|
|
1541
|
+
});
|
|
1542
|
+
const requestStop = async (force) => {
|
|
1543
|
+
if (process.platform === "win32" && child.pid) {
|
|
1544
|
+
await stopWindowsProcessTree(child.pid, cwd);
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
child.kill(force ? "SIGKILL" : "SIGTERM");
|
|
1548
|
+
if (force) {
|
|
1549
|
+
await waitForChildClose();
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
const scheduleForceStop = () => {
|
|
1553
|
+
forcedKillTimer = setTimeout(() => {
|
|
1554
|
+
void requestStop(true);
|
|
1555
|
+
}, 1e3);
|
|
1556
|
+
};
|
|
1557
|
+
const onAbort = async () => {
|
|
1558
|
+
const message = "\n\u5DE5\u5177\u8C03\u7528\u5DF2\u53D6\u6D88\uFF0C\u6B63\u5728\u7EC8\u6B62\u5B50\u8FDB\u7A0B\u3002";
|
|
1559
|
+
output += message;
|
|
1560
|
+
onProgress?.("system", `${message}
|
|
1561
|
+
`);
|
|
1562
|
+
stoppingOutcome = "cancelled";
|
|
1563
|
+
if (process.platform === "win32") {
|
|
1564
|
+
await requestStop(true);
|
|
1565
|
+
} else {
|
|
1566
|
+
void requestStop(false);
|
|
1567
|
+
scheduleForceStop();
|
|
1568
|
+
}
|
|
1569
|
+
finish(1, output, "cancelled");
|
|
1570
|
+
};
|
|
1571
|
+
const timer = setTimeout(() => {
|
|
1572
|
+
void handleTimeout();
|
|
1573
|
+
}, timeoutMs);
|
|
1574
|
+
const handleTimeout = async () => {
|
|
1575
|
+
const message = `
|
|
1576
|
+
\u547D\u4EE4\u8D85\u65F6\uFF1A\u8D85\u8FC7 ${timeoutMs}ms\uFF0C\u5DF2\u5C1D\u8BD5\u7EC8\u6B62\u5B50\u8FDB\u7A0B\u3002`;
|
|
1577
|
+
output += message;
|
|
1578
|
+
onProgress?.("system", `${message}
|
|
1579
|
+
`);
|
|
1580
|
+
stoppingOutcome = "timeout";
|
|
1581
|
+
if (process.platform === "win32") {
|
|
1582
|
+
await requestStop(true);
|
|
1583
|
+
} else {
|
|
1584
|
+
void requestStop(false);
|
|
1585
|
+
scheduleForceStop();
|
|
1586
|
+
}
|
|
1587
|
+
finish(1, output, "timeout");
|
|
1588
|
+
};
|
|
1589
|
+
if (signal?.aborted) {
|
|
1590
|
+
onAbort();
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1594
|
+
child.stdout.on("data", (chunk) => {
|
|
1595
|
+
const text = decodeShellChunk(chunk);
|
|
1596
|
+
output += text;
|
|
1597
|
+
onProgress?.("stdout", text);
|
|
1598
|
+
});
|
|
1599
|
+
child.stderr.on("data", (chunk) => {
|
|
1600
|
+
const text = decodeShellChunk(chunk);
|
|
1601
|
+
output += text;
|
|
1602
|
+
onProgress?.("stderr", text);
|
|
1603
|
+
});
|
|
1604
|
+
child.on("close", (code) => {
|
|
1605
|
+
childClosed = true;
|
|
1606
|
+
if (stoppingOutcome) {
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
finish(code ?? 1);
|
|
1610
|
+
});
|
|
1611
|
+
child.on("error", (error) => {
|
|
1612
|
+
finish(1, `\u547D\u4EE4\u6267\u884C\u5931\u8D25\uFF1A${error.message}`);
|
|
1613
|
+
});
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
function decodeShellChunk(chunk) {
|
|
1617
|
+
const utf8 = chunk.toString("utf8");
|
|
1618
|
+
if (process.platform !== "win32") return utf8;
|
|
1619
|
+
if (!utf8.includes("\uFFFD")) return utf8;
|
|
1620
|
+
try {
|
|
1621
|
+
const decoder = new TextDecoder("gb18030", { fatal: false });
|
|
1622
|
+
const gbk = decoder.decode(chunk);
|
|
1623
|
+
const utf8Errors = utf8.split("\uFFFD").length - 1;
|
|
1624
|
+
const gbkErrors = gbk.split("\uFFFD").length - 1;
|
|
1625
|
+
if (gbkErrors < utf8Errors) return gbk;
|
|
1626
|
+
} catch {
|
|
1627
|
+
}
|
|
1628
|
+
return utf8;
|
|
1629
|
+
}
|
|
1630
|
+
async function stopWindowsProcessTree(rootPid, cwd) {
|
|
1631
|
+
const deadline = Date.now() + 900;
|
|
1632
|
+
await taskkillWindowsPids([rootPid]);
|
|
1633
|
+
while (Date.now() < deadline) {
|
|
1634
|
+
await stopWindowsWorkspaceProcesses(rootPid, cwd);
|
|
1635
|
+
await sleep(50);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
function sleep(ms) {
|
|
1639
|
+
return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
|
|
1640
|
+
}
|
|
1641
|
+
function taskkillWindowsPids(pids) {
|
|
1642
|
+
return new Promise((resolveStop) => {
|
|
1643
|
+
const uniquePids = [...new Set(pids.filter((pid) => Number.isInteger(pid) && pid > 0))];
|
|
1644
|
+
if (uniquePids.length === 0) {
|
|
1645
|
+
resolveStop();
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
const args = [...uniquePids.flatMap((pid) => ["/pid", String(pid)]), "/t", "/f"];
|
|
1649
|
+
const killer = spawn("taskkill", args, { windowsHide: true });
|
|
1650
|
+
const stopTimeout = setTimeout(() => {
|
|
1651
|
+
killer.kill("SIGKILL");
|
|
1652
|
+
resolveStop();
|
|
1653
|
+
}, 750);
|
|
1654
|
+
const finishStop = () => {
|
|
1655
|
+
clearTimeout(stopTimeout);
|
|
1656
|
+
resolveStop();
|
|
1657
|
+
};
|
|
1658
|
+
killer.on("error", finishStop);
|
|
1659
|
+
killer.on("close", finishStop);
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
function stopWindowsWorkspaceProcesses(rootPid, cwd) {
|
|
1663
|
+
return new Promise((resolveStop) => {
|
|
1664
|
+
const script = `
|
|
1665
|
+
$rootPid = ${rootPid}
|
|
1666
|
+
$cwd = ${JSON.stringify(cwd)}
|
|
1667
|
+
$rows = @(Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine)
|
|
1668
|
+
$children = @{}
|
|
1669
|
+
foreach ($row in $rows) {
|
|
1670
|
+
$parent = [int]$row.ParentProcessId
|
|
1671
|
+
if (-not $children.ContainsKey($parent)) { $children[$parent] = @() }
|
|
1672
|
+
$children[$parent] += [int]$row.ProcessId
|
|
1673
|
+
}
|
|
1674
|
+
$queue = New-Object System.Collections.Generic.Queue[int]
|
|
1675
|
+
$seen = New-Object 'System.Collections.Generic.HashSet[int]'
|
|
1676
|
+
$queue.Enqueue($rootPid)
|
|
1677
|
+
$pids = @()
|
|
1678
|
+
while ($queue.Count -gt 0) {
|
|
1679
|
+
$pid = $queue.Dequeue()
|
|
1680
|
+
if (-not $seen.Add($pid)) { continue }
|
|
1681
|
+
$pids += $pid
|
|
1682
|
+
if ($children.ContainsKey($pid)) {
|
|
1683
|
+
foreach ($childPid in $children[$pid]) { $queue.Enqueue($childPid) }
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
$cwdLower = $cwd.ToLowerInvariant()
|
|
1687
|
+
foreach ($row in $rows) {
|
|
1688
|
+
$cmd = [string]$row.CommandLine
|
|
1689
|
+
if ($cmd.ToLowerInvariant().Contains($cwdLower)) { $pids += [int]$row.ProcessId }
|
|
1690
|
+
}
|
|
1691
|
+
$pids = $pids | Sort-Object -Unique
|
|
1692
|
+
foreach ($targetPid in $pids) {
|
|
1693
|
+
if ($targetPid -gt 0 -and $targetPid -ne $PID) {
|
|
1694
|
+
Stop-Process -Id $targetPid -Force -ErrorAction SilentlyContinue
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
`;
|
|
1698
|
+
const killer = spawn("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
1699
|
+
windowsHide: true
|
|
1700
|
+
});
|
|
1701
|
+
const stopTimeout = setTimeout(() => {
|
|
1702
|
+
killer.kill("SIGKILL");
|
|
1703
|
+
resolveStop();
|
|
1704
|
+
}, 900);
|
|
1705
|
+
const finishStop = () => {
|
|
1706
|
+
clearTimeout(stopTimeout);
|
|
1707
|
+
resolveStop();
|
|
1708
|
+
};
|
|
1709
|
+
killer.on("error", finishStop);
|
|
1710
|
+
killer.on("close", finishStop);
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
function findTodo(items, id, content) {
|
|
1714
|
+
const item = items.find((todo) => todo.id === id);
|
|
1715
|
+
if (item) return item;
|
|
1716
|
+
const normalizedContent = normalizeTodoContent(content);
|
|
1717
|
+
if (normalizedContent) {
|
|
1718
|
+
const matches = items.filter((todo) => normalizeTodoContent(todo.content) === normalizedContent);
|
|
1719
|
+
if (matches.length === 1) return matches[0];
|
|
1720
|
+
if (matches.length > 1) {
|
|
1721
|
+
throw new Error(`\u672A\u627E\u5230\u552F\u4E00 Todo\uFF1A${id}\u3002content \u5339\u914D\u5230 ${matches.length} \u6761\uFF1B\u5EFA\u8BAE\uFF1A\u5148\u8FD0\u884C /todo \u67E5\u770B\u5F53\u524D\u4EFB\u52A1\u3002`);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
throw new Error(`\u672A\u627E\u5230 Todo\uFF1A${id}\u3002\u5EFA\u8BAE\uFF1A\u5148\u8FD0\u884C /todo \u67E5\u770B\u5F53\u524D\u4EFB\u52A1\u3002`);
|
|
1725
|
+
}
|
|
1726
|
+
function normalizeTodoContent(content) {
|
|
1727
|
+
return (content ?? "").trim().replace(/\s+/gu, " ");
|
|
1728
|
+
}
|
|
1729
|
+
function formatTodos(items) {
|
|
1730
|
+
if (items.length === 0) {
|
|
1731
|
+
return "\u5F53\u524D\u6CA1\u6709 Todo\u3002";
|
|
1732
|
+
}
|
|
1733
|
+
return items.map((item) => {
|
|
1734
|
+
const evidence = item.evidence ? `\uFF08${item.evidence}\uFF09` : "";
|
|
1735
|
+
return `${item.id}. [${item.status}] ${item.content}${evidence}`;
|
|
1736
|
+
}).join("\n");
|
|
1737
|
+
}
|
|
1738
|
+
function unique(items) {
|
|
1739
|
+
return [...new Set(items)];
|
|
1740
|
+
}
|
|
1741
|
+
var __testGlobToRegExp = globToRegExp;
|
|
1742
|
+
var __testDecodeShellChunk = decodeShellChunk;
|
|
1743
|
+
export {
|
|
1744
|
+
__testDecodeShellChunk,
|
|
1745
|
+
__testGlobToRegExp,
|
|
1746
|
+
adaptShellCommand,
|
|
1747
|
+
adaptShellCommandForPlatform,
|
|
1748
|
+
builtInTools,
|
|
1749
|
+
createTool,
|
|
1750
|
+
createToolContext,
|
|
1751
|
+
runTool,
|
|
1752
|
+
toolRegistryStatus
|
|
1753
|
+
};
|