hypercore-cli 1.1.1 → 1.3.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 +92 -21
- package/README.md +8 -1
- package/dist/App-YMX7FSXR.js +1 -0
- package/dist/api-Q2TX5JJL.js +1 -0
- package/dist/auth-X6CUT3DW.js +1 -0
- package/dist/background-ACODXSUG.js +1 -0
- package/dist/backlog-JD2IM336.js +1 -0
- package/dist/chunk-2QI2IU2V.js +1 -0
- package/dist/chunk-3KFRDIPQ.js +1 -0
- package/dist/chunk-42C5J7PN.js +1 -0
- package/dist/chunk-4D7XVJ7Q.js +1 -0
- package/dist/chunk-545IGTXV.js +1 -0
- package/dist/chunk-5KUSGQP2.js +1 -0
- package/dist/chunk-AUQ64BK2.js +1 -0
- package/dist/chunk-AV244H5C.js +1 -0
- package/dist/chunk-BQVBEFS4.js +1 -0
- package/dist/chunk-BYWQLFP2.js +1 -0
- package/dist/chunk-COITWWZJ.js +1 -0
- package/dist/chunk-CR7UUJVX.js +1 -0
- package/dist/chunk-E3MULLBX.js +1 -0
- package/dist/chunk-EWBV7YPP.js +1 -0
- package/dist/chunk-EZHYVJGQ.js +1 -0
- package/dist/chunk-FAKXBY7Q.js +1 -0
- package/dist/chunk-FHGATV5B.js +1 -0
- package/dist/chunk-I2G27Y5P.js +1 -0
- package/dist/chunk-IKF43TX2.js +1 -0
- package/dist/chunk-INSPHCBN.js +1 -0
- package/dist/chunk-LQMDUKIE.js +1 -0
- package/dist/chunk-M3MTKGA5.js +1 -0
- package/dist/chunk-MPO54FU3.js +1 -0
- package/dist/chunk-PVKCZI6A.js +1 -0
- package/dist/chunk-Q7KEPCYL.js +1 -0
- package/dist/chunk-R5XD3NT2.js +1 -0
- package/dist/chunk-ROBZ6PAL.js +1 -0
- package/dist/chunk-RXB5BS2N.js +1 -0
- package/dist/chunk-RZ3HNYMT.js +1 -0
- package/dist/chunk-UCGLRMTG.js +1 -0
- package/dist/chunk-UEHJVRKB.js +1 -0
- package/dist/chunk-UZYX5GGF.js +1 -0
- package/dist/chunk-XQJBB725.js +1 -0
- package/dist/chunk-ZB5ZQSXH.js +1 -0
- package/dist/claude-US2QPRBA.js +1 -0
- package/dist/commands-5TFN74MD.js +1 -0
- package/dist/commands-EKPWCB3T.js +1 -0
- package/dist/commands-QHJLREPM.js +1 -0
- package/dist/config-2OUL5FLS.js +1 -0
- package/dist/config-loader-N7IBWN2P.js +1 -0
- package/dist/diagnose-NLHN4SAJ.js +1 -0
- package/dist/display-TB5YACJV.js +1 -0
- package/dist/extractor-3KTM2IUL.js +1 -0
- package/dist/feature-flag-VVIF5FJG.js +1 -0
- package/dist/history-GVNDPXXQ.js +1 -0
- package/dist/index.js +1 -402
- package/dist/instance-registry-I5AIVJE2.js +1 -0
- package/dist/keybindings-RN3A7CRW.js +1 -0
- package/dist/loader-3IKPXP4R.js +1 -0
- package/dist/network-GI2F3IDE.js +1 -0
- package/dist/notify-O6FNVHC4.js +1 -0
- package/dist/openai-compat-IPCMINVF.js +1 -0
- package/dist/permissions-5O7KVAXU.js +1 -0
- package/dist/prompt-VWFPFM4N.js +1 -0
- package/dist/quality-GPQD25UL.js +1 -0
- package/dist/repl-YNXCDVU4.js +1 -0
- package/dist/roadmap-QRZODSNJ.js +1 -0
- package/dist/server-USQP4GC4.js +1 -0
- package/dist/session-5HDDQQP6.js +1 -0
- package/dist/skills-DXWSVJSU.js +1 -0
- package/dist/store-WXXTKTTL.js +1 -0
- package/dist/team-VTPJ3WRT.js +1 -0
- package/dist/telemetry-NT4UZLBS.js +1 -0
- package/dist/test-runner-F6B6RH3S.js +1 -0
- package/dist/theme-JJJ6ABR2.js +1 -0
- package/dist/upgrade-RUG3R7R5.js +1 -0
- package/dist/verify-6OGRY2PR.js +1 -0
- package/dist/version-DLROA5JN.js +1 -0
- package/dist/web/static/app.js +1 -562
- package/dist/web/static/index.html +114 -126
- package/dist/web/static/mirror.css +1 -1001
- package/dist/web/static/mirror.html +155 -178
- package/dist/web/static/mirror.js +1 -1125
- package/dist/web/static/onboard.css +1 -302
- package/dist/web/static/onboard.html +121 -145
- package/dist/web/static/onboard.js +1 -300
- package/dist/web/static/style.css +1 -602
- package/dist/web/static/utils.js +1 -0
- package/dist/web/static/workspace.css +1 -1568
- package/dist/web/static/workspace.html +369 -402
- package/dist/web/static/workspace.js +1 -1683
- package/dist/web-P5YUKEAU.js +1 -0
- package/package.json +25 -4
- package/dist/api-D4PUN5BN.js +0 -162
- package/dist/auth-UTR4I6QY.js +0 -21
- package/dist/background-2EGCAAQH.js +0 -14
- package/dist/backlog-Q2NZCLNY.js +0 -24
- package/dist/chunk-2CMSCWQW.js +0 -162
- package/dist/chunk-4DVYJAJL.js +0 -57
- package/dist/chunk-77FRUHTU.js +0 -271
- package/dist/chunk-7ZYMJFCA.js +0 -251
- package/dist/chunk-BE46C7JW.js +0 -46
- package/dist/chunk-CM423E2U.js +0 -133
- package/dist/chunk-E4NKO2KI.js +0 -263
- package/dist/chunk-GH7E2OJE.js +0 -223
- package/dist/chunk-GMLQ7GZ5.js +0 -134
- package/dist/chunk-GU2FZQ6A.js +0 -69
- package/dist/chunk-IOPKN5GD.js +0 -190
- package/dist/chunk-LWDNLX6B.js +0 -2025
- package/dist/chunk-MGLJ53QN.js +0 -219
- package/dist/chunk-NHPDLYEW.js +0 -139
- package/dist/chunk-OGQGKMDX.js +0 -173
- package/dist/chunk-OPZYEVYR.js +0 -150
- package/dist/chunk-OWAOKDIN.js +0 -1505
- package/dist/chunk-R3GPQC7I.js +0 -393
- package/dist/chunk-RKB2JOV2.js +0 -43
- package/dist/chunk-RNG3K465.js +0 -80
- package/dist/chunk-SHJQMIJL.js +0 -288
- package/dist/chunk-SVF2VWOZ.js +0 -145
- package/dist/chunk-TGTYKBGC.js +0 -86
- package/dist/chunk-V2EBSFPU.js +0 -575
- package/dist/chunk-VJDQNNSO.js +0 -681
- package/dist/chunk-VQ35XX7B.js +0 -167
- package/dist/chunk-WHLVZCQY.js +0 -245
- package/dist/chunk-XMGHVNH2.js +0 -66
- package/dist/chunk-YWOSOTUO.js +0 -58
- package/dist/chunk-ZQRNV2US.js +0 -166
- package/dist/chunk-ZSBHUGWR.js +0 -262
- package/dist/claude-O5FSOXZC.js +0 -12
- package/dist/commands-43PLOWRU.js +0 -128
- package/dist/commands-5YVUSUMP.js +0 -232
- package/dist/commands-VZMZJFZF.js +0 -1044
- package/dist/config-WXXEEEVW.js +0 -8
- package/dist/config-loader-SXO674TF.js +0 -24
- package/dist/diagnose-BX45APUZ.js +0 -12
- package/dist/display-IIUBEYWN.js +0 -58
- package/dist/extractor-R5ABXNTJ.js +0 -129
- package/dist/history-JPXZEOT3.js +0 -180
- package/dist/index.d.ts +0 -1
- package/dist/instance-registry-6NJTCAE4.js +0 -15
- package/dist/keybindings-ADWNOX5M.js +0 -15
- package/dist/loader-AXDDCB2G.js +0 -58
- package/dist/network-V3O4UZYZ.js +0 -279
- package/dist/notify-HPTALZDC.js +0 -14
- package/dist/openai-compat-UFDV2SCK.js +0 -12
- package/dist/permissions-JUKXMNDH.js +0 -10
- package/dist/prompt-5CZ34WGA.js +0 -166
- package/dist/quality-ST7PPNFR.js +0 -16
- package/dist/repl-EOWP6AAB.js +0 -3374
- package/dist/roadmap-5OBEKROY.js +0 -17
- package/dist/server-BB5AENWU.js +0 -57
- package/dist/session-5NDKKFLN.js +0 -21
- package/dist/skills-JVLIQVJN.js +0 -175
- package/dist/store-G7KRD4PN.js +0 -25
- package/dist/team-FVNNVDBY.js +0 -385
- package/dist/telemetry-6R4EIE6O.js +0 -30
- package/dist/test-runner-REKSVPPY.js +0 -619
- package/dist/theme-3SYJ3UQA.js +0 -14
- package/dist/upgrade-YSXCO63I.js +0 -83
- package/dist/verify-JUDKTPKZ.js +0 -14
- package/dist/web-H2BJXUBZ.js +0 -39
package/dist/chunk-OWAOKDIN.js
DELETED
|
@@ -1,1505 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
addMemory,
|
|
3
|
-
searchMemories
|
|
4
|
-
} from "./chunk-7ZYMJFCA.js";
|
|
5
|
-
import {
|
|
6
|
-
applyPermissions
|
|
7
|
-
} from "./chunk-GU2FZQ6A.js";
|
|
8
|
-
import {
|
|
9
|
-
HYPERCORE_DIR
|
|
10
|
-
} from "./chunk-SVF2VWOZ.js";
|
|
11
|
-
import {
|
|
12
|
-
getTaskOutput,
|
|
13
|
-
spawnBackground
|
|
14
|
-
} from "./chunk-TGTYKBGC.js";
|
|
15
|
-
|
|
16
|
-
// src/mcp/client.ts
|
|
17
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
18
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
19
|
-
import { existsSync } from "fs";
|
|
20
|
-
import { readFile } from "fs/promises";
|
|
21
|
-
import { join } from "path";
|
|
22
|
-
var activeConnections = [];
|
|
23
|
-
async function loadMCPConfig(hypercoreDir) {
|
|
24
|
-
const systemPath = join(hypercoreDir, "mcp.json");
|
|
25
|
-
const projectPath = join(process.cwd(), ".hypercore", "mcp.json");
|
|
26
|
-
let systemConfig = null;
|
|
27
|
-
let projectConfig = null;
|
|
28
|
-
if (existsSync(systemPath)) {
|
|
29
|
-
try {
|
|
30
|
-
systemConfig = JSON.parse(await readFile(systemPath, "utf-8"));
|
|
31
|
-
} catch {
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
if (projectPath !== systemPath && existsSync(projectPath)) {
|
|
35
|
-
try {
|
|
36
|
-
projectConfig = JSON.parse(await readFile(projectPath, "utf-8"));
|
|
37
|
-
} catch {
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
if (!systemConfig && !projectConfig) return null;
|
|
41
|
-
return {
|
|
42
|
-
mcpServers: {
|
|
43
|
-
...systemConfig?.mcpServers || {},
|
|
44
|
-
...projectConfig?.mcpServers || {}
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
async function connectServer(name, config) {
|
|
49
|
-
const transport = new StdioClientTransport({
|
|
50
|
-
command: config.command,
|
|
51
|
-
args: config.args || [],
|
|
52
|
-
env: { ...process.env, ...config.env || {} }
|
|
53
|
-
});
|
|
54
|
-
const client = new Client({
|
|
55
|
-
name: "hypercore",
|
|
56
|
-
version: "0.1.0"
|
|
57
|
-
});
|
|
58
|
-
try {
|
|
59
|
-
await client.connect(transport);
|
|
60
|
-
activeConnections.push({ name, client, transport });
|
|
61
|
-
const { tools } = await client.listTools();
|
|
62
|
-
const registeredTools = [];
|
|
63
|
-
for (const tool of tools) {
|
|
64
|
-
registeredTools.push({
|
|
65
|
-
definition: {
|
|
66
|
-
name: `mcp_${name}_${tool.name}`,
|
|
67
|
-
description: `[MCP:${name}] ${tool.description || tool.name}`,
|
|
68
|
-
input_schema: tool.inputSchema || { type: "object", properties: {} }
|
|
69
|
-
},
|
|
70
|
-
handler: async (input) => {
|
|
71
|
-
try {
|
|
72
|
-
const result = await client.callTool({
|
|
73
|
-
name: tool.name,
|
|
74
|
-
arguments: input
|
|
75
|
-
});
|
|
76
|
-
if (Array.isArray(result.content)) {
|
|
77
|
-
return result.content.map(
|
|
78
|
-
(c) => c.type === "text" ? c.text : JSON.stringify(c)
|
|
79
|
-
).join("\n");
|
|
80
|
-
}
|
|
81
|
-
return JSON.stringify(result.content);
|
|
82
|
-
} catch (err) {
|
|
83
|
-
return `MCP \u8C03\u7528\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
return registeredTools;
|
|
89
|
-
} catch (err) {
|
|
90
|
-
console.error(` \u26A0\uFE0F MCP Server "${name}" \u8FDE\u63A5\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
91
|
-
return [];
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
async function loadMCPTools(hypercoreDir) {
|
|
95
|
-
const config = await loadMCPConfig(hypercoreDir);
|
|
96
|
-
if (!config || !config.mcpServers) return [];
|
|
97
|
-
const allTools = [];
|
|
98
|
-
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
|
99
|
-
const tools = await connectServer(name, serverConfig);
|
|
100
|
-
allTools.push(...tools);
|
|
101
|
-
}
|
|
102
|
-
return allTools;
|
|
103
|
-
}
|
|
104
|
-
async function closeMCPConnections() {
|
|
105
|
-
for (const conn of activeConnections) {
|
|
106
|
-
try {
|
|
107
|
-
await conn.client.close();
|
|
108
|
-
} catch {
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
activeConnections.length = 0;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// src/tools/web-search.ts
|
|
115
|
-
function createWebSearchTool(apiKey) {
|
|
116
|
-
return {
|
|
117
|
-
definition: {
|
|
118
|
-
name: "web_search",
|
|
119
|
-
description: "\u641C\u7D22\u4E92\u8054\u7F51\u83B7\u53D6\u6700\u65B0\u4FE1\u606F\u3002\u7528\u4E8E\u8C03\u7814\u3001\u67E5\u627E\u6570\u636E\u3001\u83B7\u53D6\u884C\u4E1A\u52A8\u6001\u7B49\u3002\u8FD4\u56DE\u641C\u7D22\u7ED3\u679C\u7684\u6458\u8981\u548C\u94FE\u63A5\u3002",
|
|
120
|
-
input_schema: {
|
|
121
|
-
type: "object",
|
|
122
|
-
properties: {
|
|
123
|
-
query: {
|
|
124
|
-
type: "string",
|
|
125
|
-
description: "\u641C\u7D22\u67E5\u8BE2\u8BCD"
|
|
126
|
-
},
|
|
127
|
-
max_results: {
|
|
128
|
-
type: "number",
|
|
129
|
-
description: "\u6700\u5927\u8FD4\u56DE\u7ED3\u679C\u6570\uFF08\u9ED8\u8BA45\uFF09"
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
required: ["query"]
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
handler: async (input) => {
|
|
136
|
-
const query = input.query;
|
|
137
|
-
const maxResults = input.max_results || 5;
|
|
138
|
-
try {
|
|
139
|
-
const response = await fetch("https://api.tavily.com/search", {
|
|
140
|
-
method: "POST",
|
|
141
|
-
headers: { "Content-Type": "application/json" },
|
|
142
|
-
body: JSON.stringify({
|
|
143
|
-
api_key: apiKey,
|
|
144
|
-
query,
|
|
145
|
-
max_results: maxResults,
|
|
146
|
-
include_answer: true
|
|
147
|
-
})
|
|
148
|
-
});
|
|
149
|
-
if (!response.ok) {
|
|
150
|
-
throw new Error(`Tavily API error: ${response.status}`);
|
|
151
|
-
}
|
|
152
|
-
const data = await response.json();
|
|
153
|
-
let output = "";
|
|
154
|
-
if (data.answer) {
|
|
155
|
-
output += `**AI\u6458\u8981\uFF1A** ${data.answer}
|
|
156
|
-
|
|
157
|
-
`;
|
|
158
|
-
}
|
|
159
|
-
if (data.results) {
|
|
160
|
-
output += "**\u641C\u7D22\u7ED3\u679C\uFF1A**\n\n";
|
|
161
|
-
for (const result of data.results) {
|
|
162
|
-
output += `### ${result.title}
|
|
163
|
-
`;
|
|
164
|
-
output += `${result.url}
|
|
165
|
-
`;
|
|
166
|
-
output += `${result.content}
|
|
167
|
-
|
|
168
|
-
`;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return output || "\u672A\u627E\u5230\u76F8\u5173\u7ED3\u679C";
|
|
172
|
-
} catch (err) {
|
|
173
|
-
return `\u641C\u7D22\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// src/tools/file-ops.ts
|
|
180
|
-
import { readFile as readFile2, writeFile, mkdir, stat } from "fs/promises";
|
|
181
|
-
import { dirname, resolve, extname } from "path";
|
|
182
|
-
var BINARY_EXTS = /* @__PURE__ */ new Set([
|
|
183
|
-
".png",
|
|
184
|
-
".jpg",
|
|
185
|
-
".jpeg",
|
|
186
|
-
".gif",
|
|
187
|
-
".bmp",
|
|
188
|
-
".webp",
|
|
189
|
-
".ico",
|
|
190
|
-
".svg",
|
|
191
|
-
".mp4",
|
|
192
|
-
".avi",
|
|
193
|
-
".mov",
|
|
194
|
-
".mkv",
|
|
195
|
-
".webm",
|
|
196
|
-
".flv",
|
|
197
|
-
".mp3",
|
|
198
|
-
".wav",
|
|
199
|
-
".flac",
|
|
200
|
-
".aac",
|
|
201
|
-
".ogg",
|
|
202
|
-
".zip",
|
|
203
|
-
".tar",
|
|
204
|
-
".gz",
|
|
205
|
-
".bz2",
|
|
206
|
-
".7z",
|
|
207
|
-
".rar",
|
|
208
|
-
".pdf",
|
|
209
|
-
".doc",
|
|
210
|
-
".docx",
|
|
211
|
-
".xls",
|
|
212
|
-
".xlsx",
|
|
213
|
-
".ppt",
|
|
214
|
-
".pptx",
|
|
215
|
-
".exe",
|
|
216
|
-
".dll",
|
|
217
|
-
".so",
|
|
218
|
-
".dylib",
|
|
219
|
-
".bin",
|
|
220
|
-
".woff",
|
|
221
|
-
".woff2",
|
|
222
|
-
".ttf",
|
|
223
|
-
".otf",
|
|
224
|
-
".eot"
|
|
225
|
-
]);
|
|
226
|
-
function resolvePath(p) {
|
|
227
|
-
return resolve(process.cwd(), p);
|
|
228
|
-
}
|
|
229
|
-
function formatSize(bytes) {
|
|
230
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
231
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
232
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
233
|
-
}
|
|
234
|
-
function createFileReadTool() {
|
|
235
|
-
return {
|
|
236
|
-
definition: {
|
|
237
|
-
name: "file_read",
|
|
238
|
-
description: "\u8BFB\u53D6\u672C\u5730\u6587\u4EF6\u5185\u5BB9\u3002\u652F\u6301\u6587\u672C\u6587\u4EF6\u548C\u4E8C\u8FDB\u5236\u6587\u4EF6\u7684\u57FA\u672C\u4FE1\u606F\u67E5\u770B\u3002\u8DEF\u5F84\u53EF\u4EE5\u662F\u7EDD\u5BF9\u8DEF\u5F84\u6216\u76F8\u5BF9\u8DEF\u5F84\uFF08\u76F8\u5BF9\u4E8E\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09\u3002",
|
|
239
|
-
input_schema: {
|
|
240
|
-
type: "object",
|
|
241
|
-
properties: {
|
|
242
|
-
path: {
|
|
243
|
-
type: "string",
|
|
244
|
-
description: "\u6587\u4EF6\u8DEF\u5F84\uFF08\u7EDD\u5BF9\u6216\u76F8\u5BF9\u4E8E\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09"
|
|
245
|
-
},
|
|
246
|
-
encoding: {
|
|
247
|
-
type: "string",
|
|
248
|
-
description: "\u7F16\u7801\u683C\u5F0F\uFF0C\u9ED8\u8BA4 utf-8\u3002\u5BF9\u4E8C\u8FDB\u5236\u6587\u4EF6\u53EF\u7528 base64",
|
|
249
|
-
enum: ["utf-8", "base64"]
|
|
250
|
-
},
|
|
251
|
-
max_lines: {
|
|
252
|
-
type: "number",
|
|
253
|
-
description: "\u6700\u5927\u8BFB\u53D6\u884C\u6570\uFF08\u4EC5\u6587\u672C\u6587\u4EF6\u6709\u6548\uFF09\uFF0C0 \u8868\u793A\u5168\u90E8\u8BFB\u53D6"
|
|
254
|
-
}
|
|
255
|
-
},
|
|
256
|
-
required: ["path"]
|
|
257
|
-
}
|
|
258
|
-
},
|
|
259
|
-
handler: async (input) => {
|
|
260
|
-
const filePath = resolvePath(input.path);
|
|
261
|
-
const encoding = input.encoding || "utf-8";
|
|
262
|
-
const maxLines = input.max_lines || 0;
|
|
263
|
-
try {
|
|
264
|
-
const fileStat = await stat(filePath);
|
|
265
|
-
const ext = extname(filePath).toLowerCase();
|
|
266
|
-
const isBinary = BINARY_EXTS.has(ext);
|
|
267
|
-
const fileInfo = `[\u6587\u4EF6: ${filePath} | \u5927\u5C0F: ${formatSize(fileStat.size)} | \u4FEE\u6539\u65F6\u95F4: ${fileStat.mtime.toISOString()}]`;
|
|
268
|
-
if (isBinary && encoding !== "base64") {
|
|
269
|
-
return `${fileInfo}
|
|
270
|
-
|
|
271
|
-
\u8FD9\u662F\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF08${ext}\uFF09\uFF0C\u65E0\u6CD5\u4EE5\u6587\u672C\u5F62\u5F0F\u663E\u793A\u3002\u5982\u9700\u8BFB\u53D6\u539F\u59CB\u6570\u636E\uFF0C\u8BF7\u4F7F\u7528 encoding: "base64"\u3002
|
|
272
|
-
\u5982\u9700\u5904\u7406\u6B64\u6587\u4EF6\uFF0C\u53EF\u4EE5\u7528 bash \u5DE5\u5177\u6267\u884C\u76F8\u5173\u547D\u4EE4\uFF08\u5982 file\u3001exiftool\u3001ffprobe \u7B49\uFF09\u3002`;
|
|
273
|
-
}
|
|
274
|
-
if (fileStat.size > 1024 * 1024 && encoding === "utf-8") {
|
|
275
|
-
const content2 = await readFile2(filePath, "utf-8");
|
|
276
|
-
const lines = content2.split("\n");
|
|
277
|
-
const limit = maxLines || 500;
|
|
278
|
-
const truncated = lines.slice(0, limit).join("\n");
|
|
279
|
-
return `${fileInfo}
|
|
280
|
-
\u26A0\uFE0F \u5927\u6587\u4EF6\uFF0C\u4EC5\u663E\u793A\u524D ${limit} \u884C\uFF08\u5171 ${lines.length} \u884C\uFF09
|
|
281
|
-
|
|
282
|
-
${truncated}`;
|
|
283
|
-
}
|
|
284
|
-
if (encoding === "base64") {
|
|
285
|
-
const buffer = await readFile2(filePath);
|
|
286
|
-
return `${fileInfo}
|
|
287
|
-
|
|
288
|
-
[base64 \u7F16\u7801\uFF0C${formatSize(buffer.length)}]
|
|
289
|
-
${buffer.toString("base64")}`;
|
|
290
|
-
}
|
|
291
|
-
let content = await readFile2(filePath, "utf-8");
|
|
292
|
-
if (maxLines > 0) {
|
|
293
|
-
const lines = content.split("\n");
|
|
294
|
-
if (lines.length > maxLines) {
|
|
295
|
-
content = lines.slice(0, maxLines).join("\n");
|
|
296
|
-
content += `
|
|
297
|
-
|
|
298
|
-
... (\u622A\u65AD\uFF0C\u5171 ${lines.length} \u884C\uFF0C\u4EC5\u663E\u793A\u524D ${maxLines} \u884C)`;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return content;
|
|
302
|
-
} catch (err) {
|
|
303
|
-
return `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
function createFileWriteTool() {
|
|
309
|
-
return {
|
|
310
|
-
definition: {
|
|
311
|
-
name: "file_write",
|
|
312
|
-
description: "\u5199\u5165\u5185\u5BB9\u5230\u672C\u5730\u6587\u4EF6\u3002\u5982\u679C\u6587\u4EF6\u4E0D\u5B58\u5728\u4F1A\u81EA\u52A8\u521B\u5EFA\uFF0C\u5982\u679C\u76EE\u5F55\u4E0D\u5B58\u5728\u4E5F\u4F1A\u81EA\u52A8\u521B\u5EFA\u3002\u8DEF\u5F84\u53EF\u4EE5\u662F\u7EDD\u5BF9\u8DEF\u5F84\u6216\u76F8\u5BF9\u8DEF\u5F84\u3002",
|
|
313
|
-
input_schema: {
|
|
314
|
-
type: "object",
|
|
315
|
-
properties: {
|
|
316
|
-
path: {
|
|
317
|
-
type: "string",
|
|
318
|
-
description: "\u6587\u4EF6\u8DEF\u5F84\uFF08\u7EDD\u5BF9\u6216\u76F8\u5BF9\u4E8E\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09"
|
|
319
|
-
},
|
|
320
|
-
content: {
|
|
321
|
-
type: "string",
|
|
322
|
-
description: "\u8981\u5199\u5165\u7684\u5185\u5BB9"
|
|
323
|
-
},
|
|
324
|
-
append: {
|
|
325
|
-
type: "boolean",
|
|
326
|
-
description: "\u662F\u5426\u8FFD\u52A0\u5199\u5165\uFF08\u9ED8\u8BA4 false\uFF0C\u8986\u76D6\u5199\u5165\uFF09"
|
|
327
|
-
}
|
|
328
|
-
},
|
|
329
|
-
required: ["path", "content"]
|
|
330
|
-
}
|
|
331
|
-
},
|
|
332
|
-
handler: async (input) => {
|
|
333
|
-
const filePath = resolvePath(input.path);
|
|
334
|
-
const content = input.content;
|
|
335
|
-
const append = input.append;
|
|
336
|
-
try {
|
|
337
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
338
|
-
if (append) {
|
|
339
|
-
const { appendFile } = await import("fs/promises");
|
|
340
|
-
await appendFile(filePath, content, "utf-8");
|
|
341
|
-
return `\u5185\u5BB9\u5DF2\u8FFD\u52A0\u5230: ${filePath}`;
|
|
342
|
-
}
|
|
343
|
-
await writeFile(filePath, content, "utf-8");
|
|
344
|
-
return `\u6587\u4EF6\u5DF2\u5199\u5165: ${filePath}`;
|
|
345
|
-
} catch (err) {
|
|
346
|
-
return `\u5199\u5165\u6587\u4EF6\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// src/tools/bash.ts
|
|
353
|
-
import { execSync } from "child_process";
|
|
354
|
-
var DANGEROUS_PATTERNS = [
|
|
355
|
-
/\brm\s+-rf\s+\/\b/,
|
|
356
|
-
// rm -rf /
|
|
357
|
-
/\brm\s+-rf\s+~\b/,
|
|
358
|
-
// rm -rf ~
|
|
359
|
-
/\bsudo\b/,
|
|
360
|
-
// sudo
|
|
361
|
-
/\bmkfs\b/,
|
|
362
|
-
// mkfs
|
|
363
|
-
/\bdd\s+if=/,
|
|
364
|
-
// dd if=
|
|
365
|
-
/\b:\(\)\s*\{\s*:\|:\s*&\s*\};\s*:/,
|
|
366
|
-
// fork bomb
|
|
367
|
-
/\bshutdown\b/,
|
|
368
|
-
// shutdown
|
|
369
|
-
/\breboot\b/
|
|
370
|
-
// reboot
|
|
371
|
-
];
|
|
372
|
-
var MAX_OUTPUT = 512 * 1024;
|
|
373
|
-
var TRUNCATE_AT = 5e4;
|
|
374
|
-
var TIMEOUT_MS = 12e4;
|
|
375
|
-
function smartTruncate(text, limit) {
|
|
376
|
-
if (text.length <= limit) return text;
|
|
377
|
-
const headSize = Math.floor(limit * 0.7);
|
|
378
|
-
const tailSize = limit - headSize - 200;
|
|
379
|
-
const head = text.slice(0, headSize);
|
|
380
|
-
const tail = text.slice(-tailSize);
|
|
381
|
-
const omitted = text.length - headSize - tailSize;
|
|
382
|
-
return `${head}
|
|
383
|
-
|
|
384
|
-
... [\u7701\u7565 ${omitted} \u5B57\u7B26] ...
|
|
385
|
-
|
|
386
|
-
${tail}`;
|
|
387
|
-
}
|
|
388
|
-
function createTaskOutputTool() {
|
|
389
|
-
return {
|
|
390
|
-
definition: {
|
|
391
|
-
name: "task_output",
|
|
392
|
-
description: "\u83B7\u53D6\u540E\u53F0\u4EFB\u52A1\u7684\u8F93\u51FA\u548C\u72B6\u6001\u3002\u4EFB\u52A1\u901A\u8FC7 bash \u5DE5\u5177\u7684 background=true \u542F\u52A8\u3002",
|
|
393
|
-
input_schema: {
|
|
394
|
-
type: "object",
|
|
395
|
-
properties: {
|
|
396
|
-
task_id: {
|
|
397
|
-
type: "string",
|
|
398
|
-
description: "\u540E\u53F0\u4EFB\u52A1 ID\uFF08\u5982 bg-001\uFF09"
|
|
399
|
-
}
|
|
400
|
-
},
|
|
401
|
-
required: ["task_id"]
|
|
402
|
-
}
|
|
403
|
-
},
|
|
404
|
-
handler: async (input) => {
|
|
405
|
-
const taskId = input.task_id;
|
|
406
|
-
const task = getTaskOutput(taskId);
|
|
407
|
-
if (!task) {
|
|
408
|
-
return `\u9519\u8BEF: \u4EFB\u52A1 ${taskId} \u4E0D\u5B58\u5728`;
|
|
409
|
-
}
|
|
410
|
-
const elapsed = ((Date.now() - task.startTime.getTime()) / 1e3).toFixed(1);
|
|
411
|
-
const parts = [
|
|
412
|
-
`\u4EFB\u52A1: ${task.id} [${task.status}]`,
|
|
413
|
-
`\u547D\u4EE4: ${task.command}`,
|
|
414
|
-
`\u8017\u65F6: ${elapsed}s`
|
|
415
|
-
];
|
|
416
|
-
if (task.exitCode !== void 0) {
|
|
417
|
-
parts.push(`\u9000\u51FA\u7801: ${task.exitCode}`);
|
|
418
|
-
}
|
|
419
|
-
if (task.output) {
|
|
420
|
-
parts.push(`
|
|
421
|
-
--- stdout ---
|
|
422
|
-
${smartTruncate(task.output, TRUNCATE_AT)}`);
|
|
423
|
-
}
|
|
424
|
-
if (task.error) {
|
|
425
|
-
parts.push(`
|
|
426
|
-
--- stderr ---
|
|
427
|
-
${smartTruncate(task.error, 1e4)}`);
|
|
428
|
-
}
|
|
429
|
-
return parts.join("\n");
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
function createBashTool() {
|
|
434
|
-
return {
|
|
435
|
-
definition: {
|
|
436
|
-
name: "bash",
|
|
437
|
-
description: "\u6267\u884C shell \u547D\u4EE4\u5E76\u8FD4\u56DE\u8F93\u51FA\u7ED3\u679C\u3002\u53EF\u7528\u4E8E\u8FD0\u884C\u811A\u672C\u3001\u5B89\u88C5\u4F9D\u8D56\u3001\u67E5\u770B\u76EE\u5F55\u3001\u6267\u884C\u6784\u5EFA\u547D\u4EE4\u7B49\u3002\u8D85\u65F6 120 \u79D2\uFF08\u53EF\u81EA\u5B9A\u4E49\u81F3 5 \u5206\u949F\uFF09\uFF0C\u8D85\u957F\u8F93\u51FA\u81EA\u52A8\u622A\u65AD\u4FDD\u7559\u5934\u5C3E\u3002",
|
|
438
|
-
input_schema: {
|
|
439
|
-
type: "object",
|
|
440
|
-
properties: {
|
|
441
|
-
command: {
|
|
442
|
-
type: "string",
|
|
443
|
-
description: "\u8981\u6267\u884C\u7684 shell \u547D\u4EE4"
|
|
444
|
-
},
|
|
445
|
-
timeout: {
|
|
446
|
-
type: "number",
|
|
447
|
-
description: "\u8D85\u65F6\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09\uFF0C\u9ED8\u8BA4 120000\uFF0C\u6700\u5927 300000"
|
|
448
|
-
},
|
|
449
|
-
background: {
|
|
450
|
-
type: "boolean",
|
|
451
|
-
description: "\u8BBE\u4E3A true \u65F6\u540E\u53F0\u6267\u884C\uFF0C\u7ACB\u5373\u8FD4\u56DE\u4EFB\u52A1 ID\uFF0C\u7A0D\u540E\u7528 task_id \u67E5\u8BE2\u7ED3\u679C"
|
|
452
|
-
}
|
|
453
|
-
},
|
|
454
|
-
required: ["command"]
|
|
455
|
-
}
|
|
456
|
-
},
|
|
457
|
-
handler: async (input) => {
|
|
458
|
-
const command = input.command;
|
|
459
|
-
const timeout = Math.min(
|
|
460
|
-
input.timeout || TIMEOUT_MS,
|
|
461
|
-
3e5
|
|
462
|
-
// 最大 5 分钟
|
|
463
|
-
);
|
|
464
|
-
for (const pattern of DANGEROUS_PATTERNS) {
|
|
465
|
-
if (pattern.test(command)) {
|
|
466
|
-
return `\u26A0\uFE0F \u5B89\u5168\u9650\u5236\uFF1A\u8BE5\u547D\u4EE4\u88AB\u7981\u6B62\u6267\u884C (${command})`;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
if (input.background) {
|
|
470
|
-
const task = spawnBackground(command);
|
|
471
|
-
return `\u{1F504} \u540E\u53F0\u4EFB\u52A1\u5DF2\u542F\u52A8: ${task.id} (PID: ${task.pid})
|
|
472
|
-
\u4F7F\u7528 task_output \u5DE5\u5177\u67E5\u8BE2\u7ED3\u679C`;
|
|
473
|
-
}
|
|
474
|
-
try {
|
|
475
|
-
const output = execSync(command, {
|
|
476
|
-
timeout,
|
|
477
|
-
maxBuffer: MAX_OUTPUT,
|
|
478
|
-
encoding: "utf-8",
|
|
479
|
-
cwd: process.cwd(),
|
|
480
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
481
|
-
});
|
|
482
|
-
return smartTruncate(output, TRUNCATE_AT) || "(\u547D\u4EE4\u6267\u884C\u6210\u529F\uFF0C\u65E0\u8F93\u51FA)";
|
|
483
|
-
} catch (err) {
|
|
484
|
-
const execErr = err;
|
|
485
|
-
if (execErr.killed) {
|
|
486
|
-
return `\u23F1\uFE0F \u547D\u4EE4\u8D85\u65F6\uFF08\u8D85\u8FC7 ${timeout / 1e3} \u79D2\uFF09`;
|
|
487
|
-
}
|
|
488
|
-
const parts = [];
|
|
489
|
-
if (execErr.stdout) parts.push(execErr.stdout);
|
|
490
|
-
if (execErr.stderr) parts.push(execErr.stderr);
|
|
491
|
-
if (parts.length === 0) {
|
|
492
|
-
parts.push(`\u547D\u4EE4\u6267\u884C\u5931\u8D25: exit code ${execErr.status ?? "unknown"}`);
|
|
493
|
-
}
|
|
494
|
-
return smartTruncate(parts.join("\n"), TRUNCATE_AT);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// src/tools/file-edit.ts
|
|
501
|
-
import { readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
502
|
-
import { dirname as dirname2 } from "path";
|
|
503
|
-
import { resolve as resolve2 } from "path";
|
|
504
|
-
|
|
505
|
-
// src/utils/diff.ts
|
|
506
|
-
function generateUnifiedDiff(oldText, newText, filename) {
|
|
507
|
-
const oldLines = oldText.split("\n");
|
|
508
|
-
const newLines = newText.split("\n");
|
|
509
|
-
let prefixLen = 0;
|
|
510
|
-
while (prefixLen < oldLines.length && prefixLen < newLines.length && oldLines[prefixLen] === newLines[prefixLen]) {
|
|
511
|
-
prefixLen++;
|
|
512
|
-
}
|
|
513
|
-
let oldSuffixStart = oldLines.length;
|
|
514
|
-
let newSuffixStart = newLines.length;
|
|
515
|
-
while (oldSuffixStart > prefixLen && newSuffixStart > prefixLen && oldLines[oldSuffixStart - 1] === newLines[newSuffixStart - 1]) {
|
|
516
|
-
oldSuffixStart--;
|
|
517
|
-
newSuffixStart--;
|
|
518
|
-
}
|
|
519
|
-
if (prefixLen === oldLines.length && prefixLen === newLines.length) {
|
|
520
|
-
return "";
|
|
521
|
-
}
|
|
522
|
-
const ctx = 3;
|
|
523
|
-
const ctxBefore = Math.min(ctx, prefixLen);
|
|
524
|
-
const oldChangedCount = oldSuffixStart - prefixLen;
|
|
525
|
-
const newChangedCount = newSuffixStart - prefixLen;
|
|
526
|
-
const ctxAfter = Math.min(ctx, oldLines.length - oldSuffixStart);
|
|
527
|
-
const hunkOldStart = prefixLen - ctxBefore + 1;
|
|
528
|
-
const hunkNewStart = prefixLen - ctxBefore + 1;
|
|
529
|
-
const hunkOldLen = ctxBefore + oldChangedCount + ctxAfter;
|
|
530
|
-
const hunkNewLen = ctxBefore + newChangedCount + ctxAfter;
|
|
531
|
-
const lines = [];
|
|
532
|
-
lines.push(`--- a/${filename}`);
|
|
533
|
-
lines.push(`+++ b/${filename}`);
|
|
534
|
-
lines.push(`@@ -${hunkOldStart},${hunkOldLen} +${hunkNewStart},${hunkNewLen} @@`);
|
|
535
|
-
for (let i = prefixLen - ctxBefore; i < prefixLen; i++) {
|
|
536
|
-
lines.push(` ${oldLines[i]}`);
|
|
537
|
-
}
|
|
538
|
-
for (let i = prefixLen; i < oldSuffixStart; i++) {
|
|
539
|
-
lines.push(`-${oldLines[i]}`);
|
|
540
|
-
}
|
|
541
|
-
for (let i = prefixLen; i < newSuffixStart; i++) {
|
|
542
|
-
lines.push(`+${newLines[i]}`);
|
|
543
|
-
}
|
|
544
|
-
for (let i = oldSuffixStart; i < oldSuffixStart + ctxAfter; i++) {
|
|
545
|
-
lines.push(` ${oldLines[i]}`);
|
|
546
|
-
}
|
|
547
|
-
if (!oldText.endsWith("\n") || !newText.endsWith("\n")) {
|
|
548
|
-
lines.push("\");
|
|
549
|
-
}
|
|
550
|
-
return lines.join("\n");
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// src/tools/file-edit.ts
|
|
554
|
-
function createFileEditTool() {
|
|
555
|
-
return {
|
|
556
|
-
definition: {
|
|
557
|
-
name: "file_edit",
|
|
558
|
-
description: "\u7CBE\u786E\u7F16\u8F91\u6587\u4EF6\uFF1A\u901A\u8FC7\u5339\u914D\u65E7\u6587\u672C\u66FF\u6362\u4E3A\u65B0\u6587\u672C\u3002\u6BD4 file_write \u66F4\u5B89\u5168\uFF0C\u53EA\u4FEE\u6539\u76EE\u6807\u90E8\u5206\u3002",
|
|
559
|
-
input_schema: {
|
|
560
|
-
type: "object",
|
|
561
|
-
properties: {
|
|
562
|
-
path: {
|
|
563
|
-
type: "string",
|
|
564
|
-
description: "\u8981\u7F16\u8F91\u7684\u6587\u4EF6\u8DEF\u5F84\uFF08\u7EDD\u5BF9\u6216\u76F8\u5BF9\u4E8E\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09"
|
|
565
|
-
},
|
|
566
|
-
edits: {
|
|
567
|
-
type: "array",
|
|
568
|
-
items: {
|
|
569
|
-
type: "object",
|
|
570
|
-
properties: {
|
|
571
|
-
old_text: {
|
|
572
|
-
type: "string",
|
|
573
|
-
description: "\u8981\u66FF\u6362\u7684\u539F\u59CB\u6587\u672C\uFF08\u5FC5\u987B\u7CBE\u786E\u5339\u914D\uFF0C\u5305\u542B\u7F29\u8FDB\u548C\u7A7A\u767D\uFF09"
|
|
574
|
-
},
|
|
575
|
-
new_text: {
|
|
576
|
-
type: "string",
|
|
577
|
-
description: "\u66FF\u6362\u540E\u7684\u6587\u672C"
|
|
578
|
-
}
|
|
579
|
-
},
|
|
580
|
-
required: ["old_text", "new_text"]
|
|
581
|
-
},
|
|
582
|
-
description: "\u7F16\u8F91\u64CD\u4F5C\u5217\u8868\uFF0C\u6309\u987A\u5E8F\u6267\u884C"
|
|
583
|
-
},
|
|
584
|
-
create_if_missing: {
|
|
585
|
-
type: "boolean",
|
|
586
|
-
description: "\u5982\u679C\u6587\u4EF6\u4E0D\u5B58\u5728\u662F\u5426\u521B\u5EFA\uFF08\u6B64\u65F6 edits \u4E2D\u7B2C\u4E00\u9879\u7684 new_text \u4E3A\u5168\u90E8\u5185\u5BB9\uFF09"
|
|
587
|
-
}
|
|
588
|
-
},
|
|
589
|
-
required: ["path", "edits"]
|
|
590
|
-
}
|
|
591
|
-
},
|
|
592
|
-
handler: async (input) => {
|
|
593
|
-
const filePath = resolve2(process.cwd(), input.path);
|
|
594
|
-
const edits = input.edits;
|
|
595
|
-
const createIfMissing = input.create_if_missing;
|
|
596
|
-
if (!edits || edits.length === 0) {
|
|
597
|
-
return "\u9519\u8BEF: edits \u4E0D\u80FD\u4E3A\u7A7A";
|
|
598
|
-
}
|
|
599
|
-
let content;
|
|
600
|
-
try {
|
|
601
|
-
content = await readFile3(filePath, "utf-8");
|
|
602
|
-
} catch (err) {
|
|
603
|
-
if (createIfMissing && edits.length > 0) {
|
|
604
|
-
await mkdir2(dirname2(filePath), { recursive: true });
|
|
605
|
-
await writeFile2(filePath, edits[0].new_text, "utf-8");
|
|
606
|
-
return `\u2705 \u5DF2\u521B\u5EFA\u6587\u4EF6: ${filePath}
|
|
607
|
-
\u5199\u5165 ${edits[0].new_text.split("\n").length} \u884C`;
|
|
608
|
-
}
|
|
609
|
-
return `\u9519\u8BEF: \u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6 ${filePath} \u2014 ${err instanceof Error ? err.message : String(err)}`;
|
|
610
|
-
}
|
|
611
|
-
const changes = [];
|
|
612
|
-
let modified = content;
|
|
613
|
-
for (let i = 0; i < edits.length; i++) {
|
|
614
|
-
const { old_text, new_text } = edits[i];
|
|
615
|
-
if (!old_text && !new_text) continue;
|
|
616
|
-
if (!old_text) {
|
|
617
|
-
modified += new_text;
|
|
618
|
-
changes.push(`#${i + 1}: \u8FFD\u52A0 ${new_text.split("\n").length} \u884C`);
|
|
619
|
-
continue;
|
|
620
|
-
}
|
|
621
|
-
const idx = modified.indexOf(old_text);
|
|
622
|
-
if (idx === -1) {
|
|
623
|
-
const normalizedContent = modified.replace(/[ \t]+$/gm, "");
|
|
624
|
-
const normalizedOld = old_text.replace(/[ \t]+$/gm, "");
|
|
625
|
-
const normalizedIdx = normalizedContent.indexOf(normalizedOld);
|
|
626
|
-
if (normalizedIdx === -1) {
|
|
627
|
-
changes.push(`#${i + 1}: \u26A0\uFE0F \u672A\u627E\u5230\u5339\u914D\u6587\u672C\uFF08\u524D 40 \u5B57\u7B26: "${old_text.slice(0, 40)}..."\uFF09`);
|
|
628
|
-
continue;
|
|
629
|
-
}
|
|
630
|
-
const beforeNormalized = normalizedContent.slice(0, normalizedIdx);
|
|
631
|
-
const originalLines = beforeNormalized.split("\n").length;
|
|
632
|
-
const oldLines = normalizedOld.split("\n").length;
|
|
633
|
-
const lines = modified.split("\n");
|
|
634
|
-
const startLine = originalLines - 1;
|
|
635
|
-
const endLine = startLine + oldLines;
|
|
636
|
-
const newLines = new_text.split("\n");
|
|
637
|
-
lines.splice(startLine, oldLines, ...newLines);
|
|
638
|
-
modified = lines.join("\n");
|
|
639
|
-
const diffLines = newLines.length - oldLines;
|
|
640
|
-
changes.push(`#${i + 1}: \u884C ${startLine + 1}-${endLine} \u2192 ${diffLines >= 0 ? "+" : ""}${diffLines} \u884C\uFF08\u6A21\u7CCA\u5339\u914D\uFF09`);
|
|
641
|
-
} else {
|
|
642
|
-
const matchCount = modified.split(old_text).length - 1;
|
|
643
|
-
if (matchCount > 1) {
|
|
644
|
-
changes.push(`#${i + 1}: \u26A0\uFE0F \u627E\u5230 ${matchCount} \u5904\u5339\u914D\uFF0C\u53EA\u66FF\u6362\u7B2C\u4E00\u5904`);
|
|
645
|
-
}
|
|
646
|
-
modified = modified.slice(0, idx) + new_text + modified.slice(idx + old_text.length);
|
|
647
|
-
const lineNum = modified.slice(0, idx).split("\n").length;
|
|
648
|
-
const removedLines = old_text.split("\n").length;
|
|
649
|
-
const addedLines = new_text.split("\n").length;
|
|
650
|
-
changes.push(`#${i + 1}: \u884C ${lineNum} \u2014 ${removedLines} \u884C \u2192 ${addedLines} \u884C`);
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
if (modified !== content) {
|
|
654
|
-
await writeFile2(filePath, modified, "utf-8");
|
|
655
|
-
const diff = generateUnifiedDiff(content, modified, input.path);
|
|
656
|
-
const diffSection = diff ? `
|
|
657
|
-
|
|
658
|
-
${diff}` : "";
|
|
659
|
-
return `\u2705 \u5DF2\u7F16\u8F91: ${filePath}
|
|
660
|
-
${changes.join("\n")}${diffSection}`;
|
|
661
|
-
} else {
|
|
662
|
-
return `\u2139\uFE0F \u6587\u4EF6\u672A\u6539\u53D8: ${filePath}
|
|
663
|
-
${changes.join("\n")}`;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// src/tools/glob.ts
|
|
670
|
-
import { readdir, stat as stat2 } from "fs/promises";
|
|
671
|
-
import { join as join3, resolve as resolve4, relative as relative2 } from "path";
|
|
672
|
-
|
|
673
|
-
// src/core/context-filter.ts
|
|
674
|
-
import { existsSync as existsSync2, readFileSync, statSync } from "fs";
|
|
675
|
-
import { join as join2, relative, resolve as resolve3 } from "path";
|
|
676
|
-
var cachedPatterns = null;
|
|
677
|
-
var cacheKey = "";
|
|
678
|
-
function loadIgnorePatterns() {
|
|
679
|
-
const cwd = process.cwd();
|
|
680
|
-
const files = [
|
|
681
|
-
join2(cwd, ".hyperignore"),
|
|
682
|
-
join2(HYPERCORE_DIR, ".hyperignore")
|
|
683
|
-
];
|
|
684
|
-
let mtimeSum = 0;
|
|
685
|
-
for (const file of files) {
|
|
686
|
-
if (existsSync2(file)) {
|
|
687
|
-
try {
|
|
688
|
-
mtimeSum += statSync(file).mtimeMs;
|
|
689
|
-
} catch {
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
const key = `${cwd}:${mtimeSum}`;
|
|
694
|
-
if (cachedPatterns && cacheKey === key) return cachedPatterns;
|
|
695
|
-
const patterns = [];
|
|
696
|
-
for (const file of files) {
|
|
697
|
-
if (existsSync2(file)) {
|
|
698
|
-
try {
|
|
699
|
-
const content = readFileSync(file, "utf-8");
|
|
700
|
-
for (const line of content.split("\n")) {
|
|
701
|
-
const trimmed = line.trim();
|
|
702
|
-
if (trimmed && !trimmed.startsWith("#")) {
|
|
703
|
-
patterns.push(trimmed);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
} catch {
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
cachedPatterns = patterns;
|
|
711
|
-
cacheKey = key;
|
|
712
|
-
return patterns;
|
|
713
|
-
}
|
|
714
|
-
function shouldIgnore(filePath) {
|
|
715
|
-
const patterns = loadIgnorePatterns();
|
|
716
|
-
if (patterns.length === 0) return false;
|
|
717
|
-
const cwd = process.cwd();
|
|
718
|
-
const relPath = relative(cwd, resolve3(cwd, filePath));
|
|
719
|
-
for (const pattern of patterns) {
|
|
720
|
-
if (pattern.endsWith("/")) {
|
|
721
|
-
const dir = pattern.slice(0, -1);
|
|
722
|
-
if (relPath.startsWith(dir + "/") || relPath === dir) return true;
|
|
723
|
-
if (relPath.includes("/" + dir + "/")) return true;
|
|
724
|
-
} else if (pattern.startsWith("*.")) {
|
|
725
|
-
const ext = pattern.slice(1);
|
|
726
|
-
if (relPath.endsWith(ext)) return true;
|
|
727
|
-
} else {
|
|
728
|
-
if (relPath === pattern || relPath.endsWith("/" + pattern)) return true;
|
|
729
|
-
if (relPath.startsWith(pattern + "/")) return true;
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
return false;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// src/tools/glob.ts
|
|
736
|
-
function matchGlob(pattern, filePath) {
|
|
737
|
-
let regex = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "\xA7GLOBSTAR\xA7").replace(/\*/g, "[^/]*").replace(/§GLOBSTAR§/g, ".*").replace(/\?/g, "[^/]");
|
|
738
|
-
regex = `^${regex}$`;
|
|
739
|
-
return new RegExp(regex).test(filePath);
|
|
740
|
-
}
|
|
741
|
-
async function walkDir(dir, basePath, pattern, results, maxResults, ignorePatterns) {
|
|
742
|
-
if (results.length >= maxResults) return;
|
|
743
|
-
let entries;
|
|
744
|
-
try {
|
|
745
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
746
|
-
} catch {
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
for (const entry of entries) {
|
|
750
|
-
if (results.length >= maxResults) return;
|
|
751
|
-
const fullPath = join3(dir, entry.name);
|
|
752
|
-
const relPath = relative2(basePath, fullPath);
|
|
753
|
-
if (entry.name.startsWith(".") && entry.name !== ".") continue;
|
|
754
|
-
if (ignorePatterns.some((p) => entry.name === p || relPath.includes(p))) continue;
|
|
755
|
-
if (entry.isDirectory()) {
|
|
756
|
-
await walkDir(fullPath, basePath, pattern, results, maxResults, ignorePatterns);
|
|
757
|
-
} else if (entry.isFile()) {
|
|
758
|
-
if (shouldIgnore(relPath)) continue;
|
|
759
|
-
if (matchGlob(pattern, relPath)) {
|
|
760
|
-
try {
|
|
761
|
-
const info = await stat2(fullPath);
|
|
762
|
-
results.push({ path: relPath, mtime: info.mtimeMs });
|
|
763
|
-
} catch {
|
|
764
|
-
results.push({ path: relPath, mtime: 0 });
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
function createGlobTool() {
|
|
771
|
-
return {
|
|
772
|
-
definition: {
|
|
773
|
-
name: "glob",
|
|
774
|
-
description: "\u6587\u4EF6\u6A21\u5F0F\u5339\u914D\u641C\u7D22\u3002\u652F\u6301 **/*.ts\u3001src/**/*.js \u7B49 glob \u6A21\u5F0F\u3002\u8FD4\u56DE\u5339\u914D\u7684\u6587\u4EF6\u8DEF\u5F84\u5217\u8868\uFF0C\u6309\u4FEE\u6539\u65F6\u95F4\u6392\u5E8F\u3002",
|
|
775
|
-
input_schema: {
|
|
776
|
-
type: "object",
|
|
777
|
-
properties: {
|
|
778
|
-
pattern: {
|
|
779
|
-
type: "string",
|
|
780
|
-
description: 'Glob \u6A21\u5F0F\uFF0C\u5982 "**/*.ts"\u3001"src/**/*.js"\u3001"*.md"'
|
|
781
|
-
},
|
|
782
|
-
path: {
|
|
783
|
-
type: "string",
|
|
784
|
-
description: "\u641C\u7D22\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09"
|
|
785
|
-
},
|
|
786
|
-
max_results: {
|
|
787
|
-
type: "number",
|
|
788
|
-
description: "\u6700\u5927\u8FD4\u56DE\u6570\u91CF\uFF08\u9ED8\u8BA4 200\uFF09"
|
|
789
|
-
}
|
|
790
|
-
},
|
|
791
|
-
required: ["pattern"]
|
|
792
|
-
}
|
|
793
|
-
},
|
|
794
|
-
handler: async (input) => {
|
|
795
|
-
const pattern = input.pattern;
|
|
796
|
-
const basePath = resolve4(process.cwd(), input.path || ".");
|
|
797
|
-
const maxResults = input.max_results || 200;
|
|
798
|
-
const ignorePatterns = [
|
|
799
|
-
"node_modules",
|
|
800
|
-
"dist",
|
|
801
|
-
"build",
|
|
802
|
-
".git",
|
|
803
|
-
".next",
|
|
804
|
-
"__pycache__",
|
|
805
|
-
".cache",
|
|
806
|
-
"coverage",
|
|
807
|
-
".turbo"
|
|
808
|
-
];
|
|
809
|
-
const results = [];
|
|
810
|
-
await walkDir(basePath, basePath, pattern, results, maxResults, ignorePatterns);
|
|
811
|
-
results.sort((a, b) => b.mtime - a.mtime);
|
|
812
|
-
if (results.length === 0) {
|
|
813
|
-
return `\u672A\u627E\u5230\u5339\u914D "${pattern}" \u7684\u6587\u4EF6\uFF08\u641C\u7D22\u76EE\u5F55: ${basePath}\uFF09`;
|
|
814
|
-
}
|
|
815
|
-
const pathList = results.map((r) => r.path).join("\n");
|
|
816
|
-
return `\u627E\u5230 ${results.length} \u4E2A\u6587\u4EF6\uFF08\u5339\u914D: ${pattern}\uFF09\uFF1A
|
|
817
|
-
${pathList}`;
|
|
818
|
-
}
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
// src/tools/grep.ts
|
|
823
|
-
import { execFileSync, execSync as execSync2 } from "child_process";
|
|
824
|
-
import { existsSync as existsSync3 } from "fs";
|
|
825
|
-
import { readdir as readdir2, readFile as readFile4, stat as stat3 } from "fs/promises";
|
|
826
|
-
import { join as join4, resolve as resolve5, relative as relative3 } from "path";
|
|
827
|
-
function hasRipgrep() {
|
|
828
|
-
try {
|
|
829
|
-
execSync2("rg --version", { stdio: "pipe" });
|
|
830
|
-
return true;
|
|
831
|
-
} catch {
|
|
832
|
-
return false;
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
function rgSearch(opts) {
|
|
836
|
-
const args = [];
|
|
837
|
-
if (opts.outputMode === "files_with_matches") args.push("-l");
|
|
838
|
-
else if (opts.outputMode === "count") args.push("-c");
|
|
839
|
-
else args.push("-n");
|
|
840
|
-
if (opts.caseInsensitive) args.push("-i");
|
|
841
|
-
if (opts.context && opts.outputMode === "content") args.push(`-C${opts.context}`);
|
|
842
|
-
if (opts.glob) args.push(`--glob=${opts.glob}`);
|
|
843
|
-
args.push(`--max-count=${opts.maxResults}`);
|
|
844
|
-
args.push("--no-heading");
|
|
845
|
-
const cwd = process.cwd();
|
|
846
|
-
const hyperignorePath = join4(cwd, ".hyperignore");
|
|
847
|
-
if (existsSync3(hyperignorePath)) {
|
|
848
|
-
args.push(`--ignore-file=${hyperignorePath}`);
|
|
849
|
-
}
|
|
850
|
-
const globalIgnore = join4(HYPERCORE_DIR, ".hyperignore");
|
|
851
|
-
if (existsSync3(globalIgnore)) {
|
|
852
|
-
args.push(`--ignore-file=${globalIgnore}`);
|
|
853
|
-
}
|
|
854
|
-
args.push("--", opts.pattern, opts.path);
|
|
855
|
-
try {
|
|
856
|
-
const result = execFileSync("rg", args, {
|
|
857
|
-
encoding: "utf-8",
|
|
858
|
-
maxBuffer: 1024 * 1024,
|
|
859
|
-
// 1MB
|
|
860
|
-
timeout: 3e4
|
|
861
|
-
});
|
|
862
|
-
return result.trim();
|
|
863
|
-
} catch (err) {
|
|
864
|
-
const error = err;
|
|
865
|
-
if (error.status === 1) return "";
|
|
866
|
-
throw err;
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
async function nodeSearch(opts) {
|
|
870
|
-
const regex = new RegExp(opts.pattern, opts.caseInsensitive ? "gi" : "g");
|
|
871
|
-
const results = [];
|
|
872
|
-
const basePath = opts.path;
|
|
873
|
-
const ignorePatterns = [
|
|
874
|
-
"node_modules",
|
|
875
|
-
"dist",
|
|
876
|
-
"build",
|
|
877
|
-
".git",
|
|
878
|
-
".next",
|
|
879
|
-
"__pycache__",
|
|
880
|
-
".cache",
|
|
881
|
-
"coverage"
|
|
882
|
-
];
|
|
883
|
-
async function walk(dir) {
|
|
884
|
-
if (results.length >= opts.maxResults) return;
|
|
885
|
-
let entries;
|
|
886
|
-
try {
|
|
887
|
-
entries = await readdir2(dir, { withFileTypes: true });
|
|
888
|
-
} catch {
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
for (const entry of entries) {
|
|
892
|
-
if (results.length >= opts.maxResults) return;
|
|
893
|
-
if (entry.name.startsWith(".") && entry.name !== ".") continue;
|
|
894
|
-
if (ignorePatterns.includes(entry.name)) continue;
|
|
895
|
-
const fullPath = join4(dir, entry.name);
|
|
896
|
-
if (entry.isDirectory()) {
|
|
897
|
-
await walk(fullPath);
|
|
898
|
-
} else if (entry.isFile()) {
|
|
899
|
-
const relCheck = relative3(basePath, fullPath);
|
|
900
|
-
if (shouldIgnore(relCheck)) continue;
|
|
901
|
-
if (opts.glob) {
|
|
902
|
-
const ext = opts.glob.replace("*.", ".");
|
|
903
|
-
if (!entry.name.endsWith(ext)) continue;
|
|
904
|
-
}
|
|
905
|
-
try {
|
|
906
|
-
const info = await stat3(fullPath);
|
|
907
|
-
if (info.size > 512 * 1024) continue;
|
|
908
|
-
const content = await readFile4(fullPath, "utf-8");
|
|
909
|
-
const relPath = relative3(basePath, fullPath);
|
|
910
|
-
if (opts.outputMode === "files_with_matches") {
|
|
911
|
-
if (regex.test(content)) {
|
|
912
|
-
results.push(relPath);
|
|
913
|
-
regex.lastIndex = 0;
|
|
914
|
-
}
|
|
915
|
-
} else if (opts.outputMode === "count") {
|
|
916
|
-
const matches = content.match(regex);
|
|
917
|
-
if (matches) results.push(`${relPath}:${matches.length}`);
|
|
918
|
-
} else {
|
|
919
|
-
const lines = content.split("\n");
|
|
920
|
-
for (let i = 0; i < lines.length; i++) {
|
|
921
|
-
if (results.length >= opts.maxResults) break;
|
|
922
|
-
regex.lastIndex = 0;
|
|
923
|
-
if (regex.test(lines[i])) {
|
|
924
|
-
results.push(`${relPath}:${i + 1}:${lines[i]}`);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
} catch {
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
await walk(basePath);
|
|
934
|
-
return results.join("\n");
|
|
935
|
-
}
|
|
936
|
-
function createGrepTool() {
|
|
937
|
-
const useRg = hasRipgrep();
|
|
938
|
-
return {
|
|
939
|
-
definition: {
|
|
940
|
-
name: "grep",
|
|
941
|
-
description: `\u5185\u5BB9\u641C\u7D22\u5DE5\u5177${useRg ? "\uFF08ripgrep \u9A71\u52A8\uFF09" : ""}\u3002\u4F7F\u7528\u6B63\u5219\u8868\u8FBE\u5F0F\u5728\u6587\u4EF6\u4E2D\u641C\u7D22\u5185\u5BB9\u3002`,
|
|
942
|
-
input_schema: {
|
|
943
|
-
type: "object",
|
|
944
|
-
properties: {
|
|
945
|
-
pattern: {
|
|
946
|
-
type: "string",
|
|
947
|
-
description: "\u641C\u7D22\u6B63\u5219\u8868\u8FBE\u5F0F\u6A21\u5F0F"
|
|
948
|
-
},
|
|
949
|
-
path: {
|
|
950
|
-
type: "string",
|
|
951
|
-
description: "\u641C\u7D22\u76EE\u5F55\u6216\u6587\u4EF6\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09"
|
|
952
|
-
},
|
|
953
|
-
glob: {
|
|
954
|
-
type: "string",
|
|
955
|
-
description: '\u6587\u4EF6\u8FC7\u6EE4 glob \u6A21\u5F0F\uFF08\u5982 "*.ts"\u3001"*.{js,jsx}"\uFF09'
|
|
956
|
-
},
|
|
957
|
-
output_mode: {
|
|
958
|
-
type: "string",
|
|
959
|
-
enum: ["content", "files_with_matches", "count"],
|
|
960
|
-
description: "\u8F93\u51FA\u6A21\u5F0F: content=\u5339\u914D\u884C\u5185\u5BB9, files_with_matches=\u5339\u914D\u6587\u4EF6\u8DEF\u5F84, count=\u5339\u914D\u8BA1\u6570\u3002\u9ED8\u8BA4 files_with_matches"
|
|
961
|
-
},
|
|
962
|
-
context: {
|
|
963
|
-
type: "number",
|
|
964
|
-
description: "\u4E0A\u4E0B\u6587\u884C\u6570\uFF08content \u6A21\u5F0F\u6709\u6548\uFF09"
|
|
965
|
-
},
|
|
966
|
-
case_insensitive: {
|
|
967
|
-
type: "boolean",
|
|
968
|
-
description: "\u662F\u5426\u5FFD\u7565\u5927\u5C0F\u5199"
|
|
969
|
-
},
|
|
970
|
-
max_results: {
|
|
971
|
-
type: "number",
|
|
972
|
-
description: "\u6700\u5927\u7ED3\u679C\u6570\uFF08\u9ED8\u8BA4 100\uFF09"
|
|
973
|
-
}
|
|
974
|
-
},
|
|
975
|
-
required: ["pattern"]
|
|
976
|
-
}
|
|
977
|
-
},
|
|
978
|
-
handler: async (input) => {
|
|
979
|
-
const pattern = input.pattern;
|
|
980
|
-
const searchPath = resolve5(process.cwd(), input.path || ".");
|
|
981
|
-
const glob = input.glob;
|
|
982
|
-
const outputMode = input.output_mode || "files_with_matches";
|
|
983
|
-
const context = input.context;
|
|
984
|
-
const caseInsensitive = input.case_insensitive;
|
|
985
|
-
const maxResults = input.max_results || 100;
|
|
986
|
-
try {
|
|
987
|
-
let result;
|
|
988
|
-
if (useRg) {
|
|
989
|
-
result = rgSearch({ pattern, path: searchPath, glob, outputMode, context, caseInsensitive, maxResults });
|
|
990
|
-
} else {
|
|
991
|
-
result = await nodeSearch({ pattern, path: searchPath, glob, outputMode, context, caseInsensitive, maxResults });
|
|
992
|
-
}
|
|
993
|
-
if (!result) {
|
|
994
|
-
return `\u672A\u627E\u5230\u5339\u914D "${pattern}" \u7684\u5185\u5BB9\uFF08\u641C\u7D22: ${searchPath}\uFF09`;
|
|
995
|
-
}
|
|
996
|
-
const lines = result.split("\n");
|
|
997
|
-
if (lines.length > maxResults) {
|
|
998
|
-
return `\u627E\u5230 ${lines.length}+ \u4E2A\u7ED3\u679C\uFF08\u663E\u793A\u524D ${maxResults} \u4E2A\uFF09\uFF1A
|
|
999
|
-
${lines.slice(0, maxResults).join("\n")}`;
|
|
1000
|
-
}
|
|
1001
|
-
return `\u627E\u5230 ${lines.length} \u4E2A\u7ED3\u679C\uFF1A
|
|
1002
|
-
${result}`;
|
|
1003
|
-
} catch (err) {
|
|
1004
|
-
return `\u641C\u7D22\u9519\u8BEF: ${err instanceof Error ? err.message : String(err)}`;
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
};
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
// src/tools/web-fetch.ts
|
|
1011
|
-
function htmlToText(html) {
|
|
1012
|
-
return html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<noscript[\s\S]*?<\/noscript>/gi, "").replace(/<\/?(div|p|br|hr|h[1-6]|li|tr|blockquote|pre|section|article|header|footer|nav|main|aside)[^>]*>/gi, "\n").replace(/<[^>]+>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, " ").replace(/&#(\d+);/g, (_, n) => String.fromCharCode(Number(n))).replace(/\n{3,}/g, "\n\n").replace(/[ \t]+/g, " ").trim();
|
|
1013
|
-
}
|
|
1014
|
-
function extractTitle(html) {
|
|
1015
|
-
const match = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
1016
|
-
return match ? match[1].trim() : "";
|
|
1017
|
-
}
|
|
1018
|
-
function createWebFetchTool() {
|
|
1019
|
-
return {
|
|
1020
|
-
definition: {
|
|
1021
|
-
name: "web_fetch",
|
|
1022
|
-
description: "\u6293\u53D6 URL \u5185\u5BB9\u5E76\u8F6C\u4E3A\u7EAF\u6587\u672C\u3002\u652F\u6301 HTTP/HTTPS \u7F51\u9875\u3002\u81EA\u52A8\u5C06 HTML \u8F6C\u4E3A\u53EF\u8BFB\u6587\u672C\u3002",
|
|
1023
|
-
input_schema: {
|
|
1024
|
-
type: "object",
|
|
1025
|
-
properties: {
|
|
1026
|
-
url: {
|
|
1027
|
-
type: "string",
|
|
1028
|
-
description: "\u8981\u6293\u53D6\u7684 URL\uFF08\u5FC5\u987B\u662F\u5B8C\u6574 URL\uFF0C\u5982 https://example.com\uFF09"
|
|
1029
|
-
},
|
|
1030
|
-
max_length: {
|
|
1031
|
-
type: "number",
|
|
1032
|
-
description: "\u6700\u5927\u8FD4\u56DE\u5B57\u7B26\u6570\uFF08\u9ED8\u8BA4 30000\uFF09"
|
|
1033
|
-
}
|
|
1034
|
-
},
|
|
1035
|
-
required: ["url"]
|
|
1036
|
-
}
|
|
1037
|
-
},
|
|
1038
|
-
handler: async (input) => {
|
|
1039
|
-
const url = input.url;
|
|
1040
|
-
const maxLength = input.max_length || 3e4;
|
|
1041
|
-
let parsedUrl;
|
|
1042
|
-
try {
|
|
1043
|
-
parsedUrl = new URL(url);
|
|
1044
|
-
} catch {
|
|
1045
|
-
return `\u9519\u8BEF: \u65E0\u6548\u7684 URL "${url}"`;
|
|
1046
|
-
}
|
|
1047
|
-
if (!["http:", "https:"].includes(parsedUrl.protocol)) {
|
|
1048
|
-
return `\u9519\u8BEF: \u4EC5\u652F\u6301 http/https \u534F\u8BAE\uFF08\u6536\u5230: ${parsedUrl.protocol}\uFF09`;
|
|
1049
|
-
}
|
|
1050
|
-
try {
|
|
1051
|
-
const controller = new AbortController();
|
|
1052
|
-
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
1053
|
-
const response = await fetch(url, {
|
|
1054
|
-
signal: controller.signal,
|
|
1055
|
-
headers: {
|
|
1056
|
-
"User-Agent": "Hypercore-CLI/0.2 (AI Assistant)",
|
|
1057
|
-
"Accept": "text/html,application/xhtml+xml,text/plain,application/json,*/*"
|
|
1058
|
-
},
|
|
1059
|
-
redirect: "follow"
|
|
1060
|
-
});
|
|
1061
|
-
clearTimeout(timeout);
|
|
1062
|
-
if (!response.ok) {
|
|
1063
|
-
return `\u9519\u8BEF: HTTP ${response.status} ${response.statusText}\uFF08URL: ${url}\uFF09`;
|
|
1064
|
-
}
|
|
1065
|
-
const contentType = response.headers.get("content-type") || "";
|
|
1066
|
-
const rawBody = await response.text();
|
|
1067
|
-
let text;
|
|
1068
|
-
let title = "";
|
|
1069
|
-
if (contentType.includes("application/json")) {
|
|
1070
|
-
try {
|
|
1071
|
-
text = JSON.stringify(JSON.parse(rawBody), null, 2);
|
|
1072
|
-
} catch {
|
|
1073
|
-
text = rawBody;
|
|
1074
|
-
}
|
|
1075
|
-
} else if (contentType.includes("text/html") || contentType.includes("application/xhtml")) {
|
|
1076
|
-
title = extractTitle(rawBody);
|
|
1077
|
-
text = htmlToText(rawBody);
|
|
1078
|
-
} else {
|
|
1079
|
-
text = rawBody;
|
|
1080
|
-
}
|
|
1081
|
-
if (text.length > maxLength) {
|
|
1082
|
-
text = text.slice(0, maxLength) + `
|
|
1083
|
-
|
|
1084
|
-
... [\u622A\u65AD\uFF0C\u5171 ${rawBody.length} \u5B57\u7B26\uFF0C\u663E\u793A\u524D ${maxLength} \u5B57\u7B26]`;
|
|
1085
|
-
}
|
|
1086
|
-
const header = title ? `\u{1F4C4} ${title}
|
|
1087
|
-
\u{1F517} ${url}
|
|
1088
|
-
${"\u2500".repeat(40)}
|
|
1089
|
-
|
|
1090
|
-
` : `\u{1F517} ${url}
|
|
1091
|
-
${"\u2500".repeat(40)}
|
|
1092
|
-
|
|
1093
|
-
`;
|
|
1094
|
-
return header + text;
|
|
1095
|
-
} catch (err) {
|
|
1096
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
1097
|
-
return `\u9519\u8BEF: \u8BF7\u6C42\u8D85\u65F6\uFF0815 \u79D2\uFF09\u2014 ${url}`;
|
|
1098
|
-
}
|
|
1099
|
-
return `\u9519\u8BEF: \u6293\u53D6\u5931\u8D25 \u2014 ${err instanceof Error ? err.message : String(err)}`;
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
};
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
// src/tools/plugin-loader.ts
|
|
1106
|
-
import { readdir as readdir3, readFile as readFile5 } from "fs/promises";
|
|
1107
|
-
import { join as join5 } from "path";
|
|
1108
|
-
import { existsSync as existsSync4 } from "fs";
|
|
1109
|
-
import { execSync as execSync3 } from "child_process";
|
|
1110
|
-
var TOOLS_DIR_NAME = "tools";
|
|
1111
|
-
var TOOL_EXT = ".tool.md";
|
|
1112
|
-
var MAX_OUTPUT2 = 10 * 1024;
|
|
1113
|
-
var TIMEOUT_MS2 = 3e4;
|
|
1114
|
-
function parseToolMd(content) {
|
|
1115
|
-
const lines = content.split("\n");
|
|
1116
|
-
const nameLine = lines.find((l) => /^#\s+/.test(l));
|
|
1117
|
-
if (!nameLine) return null;
|
|
1118
|
-
const name = nameLine.replace(/^#\s+/, "").trim();
|
|
1119
|
-
let currentSection = "";
|
|
1120
|
-
const sections = {};
|
|
1121
|
-
for (const line of lines) {
|
|
1122
|
-
if (/^##\s+/.test(line)) {
|
|
1123
|
-
currentSection = line.replace(/^##\s+/, "").trim().toLowerCase();
|
|
1124
|
-
sections[currentSection] = [];
|
|
1125
|
-
} else if (currentSection) {
|
|
1126
|
-
sections[currentSection].push(line);
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
const description = (sections["\u63CF\u8FF0"] || sections["description"] || []).join("\n").trim() || `\u81EA\u5B9A\u4E49\u5DE5\u5177: ${name}`;
|
|
1130
|
-
const paramLines = sections["\u53C2\u6570"] || sections["parameters"] || [];
|
|
1131
|
-
const params = [];
|
|
1132
|
-
for (const pl of paramLines) {
|
|
1133
|
-
const match = pl.match(/^-\s+(\w+)\s*:\s*(.+)/);
|
|
1134
|
-
if (match) {
|
|
1135
|
-
const required = match[2].includes("\u5FC5\u586B") || match[2].includes("required");
|
|
1136
|
-
params.push({
|
|
1137
|
-
name: match[1],
|
|
1138
|
-
description: match[2].replace(/[((]必填[))]/g, "").replace(/[((]选填[))]/g, "").trim(),
|
|
1139
|
-
required
|
|
1140
|
-
});
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
const commandLines = sections["\u547D\u4EE4"] || sections["command"] || [];
|
|
1144
|
-
const command = commandLines.filter((l) => l.trim() && !l.startsWith("```")).join("\n").trim();
|
|
1145
|
-
if (!command) return null;
|
|
1146
|
-
return { name, description, params, command };
|
|
1147
|
-
}
|
|
1148
|
-
function renderCommand(template, input) {
|
|
1149
|
-
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
1150
|
-
const val = input[key];
|
|
1151
|
-
return val !== void 0 ? String(val) : "";
|
|
1152
|
-
});
|
|
1153
|
-
}
|
|
1154
|
-
function pluginToTool(plugin) {
|
|
1155
|
-
const properties = {};
|
|
1156
|
-
const required = [];
|
|
1157
|
-
for (const p of plugin.params) {
|
|
1158
|
-
properties[p.name] = { type: "string", description: p.description };
|
|
1159
|
-
if (p.required) required.push(p.name);
|
|
1160
|
-
}
|
|
1161
|
-
return {
|
|
1162
|
-
definition: {
|
|
1163
|
-
name: plugin.name,
|
|
1164
|
-
description: plugin.description,
|
|
1165
|
-
input_schema: {
|
|
1166
|
-
type: "object",
|
|
1167
|
-
properties,
|
|
1168
|
-
required: required.length > 0 ? required : void 0
|
|
1169
|
-
}
|
|
1170
|
-
},
|
|
1171
|
-
handler: async (input) => {
|
|
1172
|
-
const cmd = renderCommand(plugin.command, input);
|
|
1173
|
-
try {
|
|
1174
|
-
const output = execSync3(cmd, {
|
|
1175
|
-
timeout: TIMEOUT_MS2,
|
|
1176
|
-
maxBuffer: MAX_OUTPUT2,
|
|
1177
|
-
encoding: "utf-8",
|
|
1178
|
-
cwd: process.cwd(),
|
|
1179
|
-
shell: "/bin/sh",
|
|
1180
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1181
|
-
});
|
|
1182
|
-
return output || "(\u547D\u4EE4\u6267\u884C\u6210\u529F\uFF0C\u65E0\u8F93\u51FA)";
|
|
1183
|
-
} catch (err) {
|
|
1184
|
-
const execErr = err;
|
|
1185
|
-
if (execErr.killed) return `\u23F1\uFE0F \u547D\u4EE4\u8D85\u65F6\uFF08\u8D85\u8FC7 ${TIMEOUT_MS2 / 1e3} \u79D2\uFF09`;
|
|
1186
|
-
const parts = [];
|
|
1187
|
-
if (execErr.stdout) parts.push(execErr.stdout);
|
|
1188
|
-
if (execErr.stderr) parts.push(execErr.stderr);
|
|
1189
|
-
return parts.join("\n").slice(0, MAX_OUTPUT2) || `\u547D\u4EE4\u5931\u8D25: exit ${execErr.status}`;
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
};
|
|
1193
|
-
}
|
|
1194
|
-
async function loadPluginTools(hypercoreDir) {
|
|
1195
|
-
const toolsDir = join5(hypercoreDir, TOOLS_DIR_NAME);
|
|
1196
|
-
if (!existsSync4(toolsDir)) return [];
|
|
1197
|
-
const tools = [];
|
|
1198
|
-
try {
|
|
1199
|
-
const files = await readdir3(toolsDir);
|
|
1200
|
-
for (const file of files) {
|
|
1201
|
-
if (!file.endsWith(TOOL_EXT)) continue;
|
|
1202
|
-
try {
|
|
1203
|
-
const content = await readFile5(join5(toolsDir, file), "utf-8");
|
|
1204
|
-
const plugin = parseToolMd(content);
|
|
1205
|
-
if (plugin) {
|
|
1206
|
-
tools.push(pluginToTool(plugin));
|
|
1207
|
-
}
|
|
1208
|
-
} catch {
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
} catch {
|
|
1212
|
-
}
|
|
1213
|
-
return tools;
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
// src/tools/memory.ts
|
|
1217
|
-
var VALID_CATEGORIES = [
|
|
1218
|
-
"preference",
|
|
1219
|
-
"decision",
|
|
1220
|
-
"entity",
|
|
1221
|
-
"pattern",
|
|
1222
|
-
"fact",
|
|
1223
|
-
"convention"
|
|
1224
|
-
];
|
|
1225
|
-
function createMemorySaveTool() {
|
|
1226
|
-
return {
|
|
1227
|
-
definition: {
|
|
1228
|
-
name: "memory_save",
|
|
1229
|
-
description: `\u4FDD\u5B58\u91CD\u8981\u4FE1\u606F\u5230\u6301\u4E45\u5316\u8BB0\u5FC6\uFF0C\u8DE8\u4F1A\u8BDD\u6C38\u4E45\u4FDD\u7559\u3002\u4F60\u5E94\u8BE5**\u4E3B\u52A8\u79EF\u6781**\u5730\u4F7F\u7528\u6B64\u5DE5\u5177\u3002
|
|
1230
|
-
\u9047\u5230\u4EE5\u4E0B\u60C5\u51B5\u65F6\u5FC5\u987B\u8C03\u7528:
|
|
1231
|
-
- \u7528\u6237\u8868\u8FBE\u504F\u597D\uFF08\u5DE5\u5177\u9009\u62E9\u3001\u7F16\u7801\u98CE\u683C\u3001\u547D\u540D\u4E60\u60EF\uFF09
|
|
1232
|
-
- \u505A\u51FA\u6280\u672F\u51B3\u7B56\uFF08\u6846\u67B6\u9009\u62E9\u3001\u67B6\u6784\u65B9\u5411\u3001\u5B58\u50A8\u65B9\u6848\uFF09
|
|
1233
|
-
- \u63D0\u5230\u91CD\u8981\u5B9E\u4F53\uFF08\u9879\u76EE\u540D\u3001\u4EBA\u540D\u3001\u5173\u952EURL\uFF09
|
|
1234
|
-
- \u53D1\u73B0\u53EF\u590D\u7528\u6A21\u5F0F\uFF08Bug\u89E3\u6CD5\u3001\u4EE3\u7801\u6A21\u677F\u3001\u5DE5\u4F5C\u6D41\u7A0B\uFF09
|
|
1235
|
-
- \u7528\u6237\u7EA0\u6B63\u4F60\u7684\u9519\u8BEF\uFF08\u8BB0\u4F4F\u6B63\u786E\u505A\u6CD5\u907F\u514D\u91CD\u72AF\uFF09
|
|
1236
|
-
\u8FD9\u662F\u4F60\u6700\u91CD\u8981\u7684\u5B66\u4E60\u901A\u9053\u2014\u2014\u6BCF\u6B21\u6709\u4EF7\u503C\u7684\u4FE1\u606F\u90FD\u503C\u5F97\u4FDD\u5B58\u3002`,
|
|
1237
|
-
input_schema: {
|
|
1238
|
-
type: "object",
|
|
1239
|
-
properties: {
|
|
1240
|
-
content: {
|
|
1241
|
-
type: "string",
|
|
1242
|
-
description: "\u8981\u4FDD\u5B58\u7684\u8BB0\u5FC6\u5185\u5BB9\uFF08\u2264200\u5B57\u7B26\uFF0C\u7B80\u6D01\u5177\u4F53\uFF09"
|
|
1243
|
-
},
|
|
1244
|
-
category: {
|
|
1245
|
-
type: "string",
|
|
1246
|
-
description: "\u5206\u7C7B: preference(\u504F\u597D) | decision(\u51B3\u7B56) | entity(\u5B9E\u4F53) | pattern(\u6A21\u5F0F) | fact(\u4E8B\u5B9E) | convention(\u7EA6\u5B9A)",
|
|
1247
|
-
enum: VALID_CATEGORIES
|
|
1248
|
-
},
|
|
1249
|
-
tags: {
|
|
1250
|
-
type: "array",
|
|
1251
|
-
items: { type: "string" },
|
|
1252
|
-
description: "1-3 \u4E2A\u5C0F\u5199\u5173\u952E\u8BCD\u6807\u7B7E"
|
|
1253
|
-
}
|
|
1254
|
-
},
|
|
1255
|
-
required: ["content", "category"]
|
|
1256
|
-
}
|
|
1257
|
-
},
|
|
1258
|
-
handler: async (input) => {
|
|
1259
|
-
const content = input.content?.slice(0, 500);
|
|
1260
|
-
const category = input.category;
|
|
1261
|
-
const tags = input.tags || [];
|
|
1262
|
-
if (!content) {
|
|
1263
|
-
return "\u9519\u8BEF\uFF1Acontent \u4E0D\u80FD\u4E3A\u7A7A";
|
|
1264
|
-
}
|
|
1265
|
-
if (!VALID_CATEGORIES.includes(category)) {
|
|
1266
|
-
return `\u9519\u8BEF\uFF1A\u65E0\u6548\u5206\u7C7B "${category}"\uFF0C\u652F\u6301: ${VALID_CATEGORIES.join(", ")}`;
|
|
1267
|
-
}
|
|
1268
|
-
const record = await addMemory("personal", {
|
|
1269
|
-
layer: "personal",
|
|
1270
|
-
category,
|
|
1271
|
-
content,
|
|
1272
|
-
tags: tags.map((t) => String(t).toLowerCase()).slice(0, 5),
|
|
1273
|
-
confidence: 0.9
|
|
1274
|
-
});
|
|
1275
|
-
return `\u5DF2\u4FDD\u5B58\u8BB0\u5FC6 [${record.id}]: ${record.content}\uFF08\u5206\u7C7B: ${category}\uFF0C\u6807\u7B7E: ${record.tags.join(", ")}\uFF09`;
|
|
1276
|
-
}
|
|
1277
|
-
};
|
|
1278
|
-
}
|
|
1279
|
-
function createMemorySearchTool() {
|
|
1280
|
-
return {
|
|
1281
|
-
definition: {
|
|
1282
|
-
name: "memory_search",
|
|
1283
|
-
description: `\u641C\u7D22\u6301\u4E45\u5316\u8BB0\u5FC6\u5E93\u3002\u5F53\u4F60\u9700\u8981\u4E86\u89E3\u7528\u6237\u504F\u597D\u3001\u5386\u53F2\u51B3\u7B56\u3001\u9879\u76EE\u7EA6\u5B9A\u65F6\u4F7F\u7528\u3002
|
|
1284
|
-
\u5EFA\u8BAE\u5728\u4EE5\u4E0B\u573A\u666F\u4E3B\u52A8\u641C\u7D22:
|
|
1285
|
-
- \u4F1A\u8BDD\u5F00\u59CB\u65F6\uFF0C\u641C\u7D22\u4E0E\u5F53\u524D\u8BDD\u9898\u76F8\u5173\u7684\u5386\u53F2\u8BB0\u5FC6
|
|
1286
|
-
- \u505A\u63A8\u8350\u6216\u5EFA\u8BAE\u524D\uFF0C\u641C\u7D22\u7528\u6237\u5DF2\u77E5\u504F\u597D
|
|
1287
|
-
- \u9047\u5230\u4E0D\u786E\u5B9A\u7684\u9879\u76EE\u7EA6\u5B9A\u65F6\uFF0C\u641C\u7D22 convention \u7C7B\u8BB0\u5FC6`,
|
|
1288
|
-
input_schema: {
|
|
1289
|
-
type: "object",
|
|
1290
|
-
properties: {
|
|
1291
|
-
query: {
|
|
1292
|
-
type: "string",
|
|
1293
|
-
description: "\u641C\u7D22\u5173\u952E\u8BCD"
|
|
1294
|
-
},
|
|
1295
|
-
category: {
|
|
1296
|
-
type: "string",
|
|
1297
|
-
description: "\u53EF\u9009\uFF1A\u6309\u5206\u7C7B\u8FC7\u6EE4",
|
|
1298
|
-
enum: [...VALID_CATEGORIES, "manual", "insight"]
|
|
1299
|
-
}
|
|
1300
|
-
},
|
|
1301
|
-
required: ["query"]
|
|
1302
|
-
}
|
|
1303
|
-
},
|
|
1304
|
-
handler: async (input) => {
|
|
1305
|
-
const query = input.query;
|
|
1306
|
-
const category = input.category;
|
|
1307
|
-
if (!query) {
|
|
1308
|
-
return "\u9519\u8BEF\uFF1Aquery \u4E0D\u80FD\u4E3A\u7A7A";
|
|
1309
|
-
}
|
|
1310
|
-
const results = await searchMemories("personal", query, {
|
|
1311
|
-
category,
|
|
1312
|
-
limit: 10
|
|
1313
|
-
});
|
|
1314
|
-
if (results.length === 0) {
|
|
1315
|
-
return `\u672A\u627E\u5230\u4E0E "${query}" \u76F8\u5173\u7684\u8BB0\u5FC6`;
|
|
1316
|
-
}
|
|
1317
|
-
const lines = results.map(({ record: r, score }) => {
|
|
1318
|
-
const tags = r.tags.length > 0 ? ` [${r.tags.join(", ")}]` : "";
|
|
1319
|
-
return `- [${r.category}] ${r.content}${tags} (\u5339\u914D\u5EA6: ${Math.round(score * 100)}%)`;
|
|
1320
|
-
});
|
|
1321
|
-
return `\u627E\u5230 ${results.length} \u6761\u76F8\u5173\u8BB0\u5FC6:
|
|
1322
|
-
${lines.join("\n")}`;
|
|
1323
|
-
}
|
|
1324
|
-
};
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
// src/tools/notebook.ts
|
|
1328
|
-
import { readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
|
|
1329
|
-
import { resolve as resolve6 } from "path";
|
|
1330
|
-
function createNotebookReadTool() {
|
|
1331
|
-
return {
|
|
1332
|
-
definition: {
|
|
1333
|
-
name: "notebook_read",
|
|
1334
|
-
description: "\u8BFB\u53D6 Jupyter Notebook (.ipynb) \u6587\u4EF6\uFF0C\u8FD4\u56DE\u6240\u6709 cell \u7684\u5185\u5BB9\u548C\u7C7B\u578B\u3002",
|
|
1335
|
-
input_schema: {
|
|
1336
|
-
type: "object",
|
|
1337
|
-
properties: {
|
|
1338
|
-
path: {
|
|
1339
|
-
type: "string",
|
|
1340
|
-
description: "Notebook \u6587\u4EF6\u8DEF\u5F84"
|
|
1341
|
-
}
|
|
1342
|
-
},
|
|
1343
|
-
required: ["path"]
|
|
1344
|
-
}
|
|
1345
|
-
},
|
|
1346
|
-
handler: async (input) => {
|
|
1347
|
-
const filePath = resolve6(process.cwd(), input.path);
|
|
1348
|
-
try {
|
|
1349
|
-
const raw = await readFile6(filePath, "utf-8");
|
|
1350
|
-
const nb = JSON.parse(raw);
|
|
1351
|
-
if (!nb.cells || !Array.isArray(nb.cells)) {
|
|
1352
|
-
return "\u9519\u8BEF: \u4E0D\u662F\u6709\u6548\u7684 Notebook \u6587\u4EF6\uFF08\u7F3A\u5C11 cells \u6570\u7EC4\uFF09";
|
|
1353
|
-
}
|
|
1354
|
-
const parts = [];
|
|
1355
|
-
parts.push(`Notebook: ${input.path} (${nb.cells.length} cells, nbformat ${nb.nbformat})
|
|
1356
|
-
`);
|
|
1357
|
-
for (let i = 0; i < nb.cells.length; i++) {
|
|
1358
|
-
const cell = nb.cells[i];
|
|
1359
|
-
const source = Array.isArray(cell.source) ? cell.source.join("") : String(cell.source);
|
|
1360
|
-
const typeIcon = cell.cell_type === "code" ? "\u{1F4DD}" : cell.cell_type === "markdown" ? "\u{1F4D6}" : "\u{1F4C4}";
|
|
1361
|
-
parts.push(`--- Cell ${i} [${typeIcon} ${cell.cell_type}] ---`);
|
|
1362
|
-
parts.push(source);
|
|
1363
|
-
if (cell.cell_type === "code" && cell.outputs && cell.outputs.length > 0) {
|
|
1364
|
-
const outputSummary = cell.outputs.map((o) => {
|
|
1365
|
-
if (o.output_type === "stream") return `[stream: ${(o.text || []).join("").slice(0, 200)}]`;
|
|
1366
|
-
if (o.output_type === "execute_result") {
|
|
1367
|
-
const text = o.data?.["text/plain"];
|
|
1368
|
-
return `[result: ${Array.isArray(text) ? text.join("").slice(0, 200) : String(text).slice(0, 200)}]`;
|
|
1369
|
-
}
|
|
1370
|
-
if (o.output_type === "error") return `[error: ${o.ename}: ${o.evalue}]`;
|
|
1371
|
-
return `[${o.output_type}]`;
|
|
1372
|
-
}).join("\n");
|
|
1373
|
-
parts.push(`Output:
|
|
1374
|
-
${outputSummary}`);
|
|
1375
|
-
}
|
|
1376
|
-
parts.push("");
|
|
1377
|
-
}
|
|
1378
|
-
return parts.join("\n");
|
|
1379
|
-
} catch (err) {
|
|
1380
|
-
return `\u9519\u8BEF: \u65E0\u6CD5\u8BFB\u53D6 Notebook \u2014 ${err instanceof Error ? err.message : String(err)}`;
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
};
|
|
1384
|
-
}
|
|
1385
|
-
function createNotebookEditTool() {
|
|
1386
|
-
return {
|
|
1387
|
-
definition: {
|
|
1388
|
-
name: "notebook_edit",
|
|
1389
|
-
description: "\u7F16\u8F91 Jupyter Notebook \u7684\u6307\u5B9A cell\u3002\u53EF\u4EE5\u66FF\u6362 source\u3001\u63D2\u5165\u65B0 cell \u6216\u5220\u9664 cell\u3002",
|
|
1390
|
-
input_schema: {
|
|
1391
|
-
type: "object",
|
|
1392
|
-
properties: {
|
|
1393
|
-
path: {
|
|
1394
|
-
type: "string",
|
|
1395
|
-
description: "Notebook \u6587\u4EF6\u8DEF\u5F84"
|
|
1396
|
-
},
|
|
1397
|
-
cell_number: {
|
|
1398
|
-
type: "number",
|
|
1399
|
-
description: "Cell \u7D22\u5F15\uFF08\u4ECE 0 \u5F00\u59CB\uFF09"
|
|
1400
|
-
},
|
|
1401
|
-
new_source: {
|
|
1402
|
-
type: "string",
|
|
1403
|
-
description: "\u65B0\u7684 cell \u5185\u5BB9"
|
|
1404
|
-
},
|
|
1405
|
-
cell_type: {
|
|
1406
|
-
type: "string",
|
|
1407
|
-
enum: ["code", "markdown", "raw"],
|
|
1408
|
-
description: "Cell \u7C7B\u578B\uFF08\u9ED8\u8BA4\u4FDD\u6301\u539F\u7C7B\u578B\uFF09"
|
|
1409
|
-
},
|
|
1410
|
-
edit_mode: {
|
|
1411
|
-
type: "string",
|
|
1412
|
-
enum: ["replace", "insert", "delete"],
|
|
1413
|
-
description: "\u7F16\u8F91\u6A21\u5F0F\uFF1Areplace\uFF08\u66FF\u6362\uFF09\u3001insert\uFF08\u5728\u4E4B\u540E\u63D2\u5165\uFF09\u3001delete\uFF08\u5220\u9664\uFF09\u3002\u9ED8\u8BA4 replace"
|
|
1414
|
-
}
|
|
1415
|
-
},
|
|
1416
|
-
required: ["path", "cell_number"]
|
|
1417
|
-
}
|
|
1418
|
-
},
|
|
1419
|
-
handler: async (input) => {
|
|
1420
|
-
const filePath = resolve6(process.cwd(), input.path);
|
|
1421
|
-
const cellNum = input.cell_number;
|
|
1422
|
-
const newSource = input.new_source || "";
|
|
1423
|
-
const cellType = input.cell_type;
|
|
1424
|
-
const editMode = input.edit_mode || "replace";
|
|
1425
|
-
if (editMode !== "delete" && !input.new_source) {
|
|
1426
|
-
return "\u9519\u8BEF: replace \u548C insert \u6A21\u5F0F\u9700\u8981\u63D0\u4F9B new_source";
|
|
1427
|
-
}
|
|
1428
|
-
try {
|
|
1429
|
-
const raw = await readFile6(filePath, "utf-8");
|
|
1430
|
-
const nb = JSON.parse(raw);
|
|
1431
|
-
if (!nb.cells || !Array.isArray(nb.cells)) {
|
|
1432
|
-
return "\u9519\u8BEF: \u4E0D\u662F\u6709\u6548\u7684 Notebook \u6587\u4EF6";
|
|
1433
|
-
}
|
|
1434
|
-
if (editMode === "delete") {
|
|
1435
|
-
if (cellNum < 0 || cellNum >= nb.cells.length) {
|
|
1436
|
-
return `\u9519\u8BEF: Cell ${cellNum} \u4E0D\u5B58\u5728\uFF08\u5171 ${nb.cells.length} \u4E2A cell\uFF09`;
|
|
1437
|
-
}
|
|
1438
|
-
nb.cells.splice(cellNum, 1);
|
|
1439
|
-
await writeFile3(filePath, JSON.stringify(nb, null, 1), "utf-8");
|
|
1440
|
-
return `\u2705 \u5DF2\u5220\u9664 Cell ${cellNum}\uFF08\u5269\u4F59 ${nb.cells.length} \u4E2A cell\uFF09`;
|
|
1441
|
-
}
|
|
1442
|
-
if (editMode === "insert") {
|
|
1443
|
-
const newCell = {
|
|
1444
|
-
cell_type: cellType || "code",
|
|
1445
|
-
source: newSource.split("\n").map((line, i, arr) => i < arr.length - 1 ? line + "\n" : line),
|
|
1446
|
-
metadata: {}
|
|
1447
|
-
};
|
|
1448
|
-
if (newCell.cell_type === "code") {
|
|
1449
|
-
newCell.outputs = [];
|
|
1450
|
-
newCell.execution_count = null;
|
|
1451
|
-
}
|
|
1452
|
-
const insertAt = Math.min(cellNum + 1, nb.cells.length);
|
|
1453
|
-
nb.cells.splice(insertAt, 0, newCell);
|
|
1454
|
-
await writeFile3(filePath, JSON.stringify(nb, null, 1), "utf-8");
|
|
1455
|
-
return `\u2705 \u5DF2\u5728 Cell ${cellNum} \u4E4B\u540E\u63D2\u5165\u65B0 ${newCell.cell_type} cell\uFF08\u5171 ${nb.cells.length} \u4E2A cell\uFF09`;
|
|
1456
|
-
}
|
|
1457
|
-
if (cellNum < 0 || cellNum >= nb.cells.length) {
|
|
1458
|
-
return `\u9519\u8BEF: Cell ${cellNum} \u4E0D\u5B58\u5728\uFF08\u5171 ${nb.cells.length} \u4E2A cell\uFF09`;
|
|
1459
|
-
}
|
|
1460
|
-
const cell = nb.cells[cellNum];
|
|
1461
|
-
cell.source = newSource.split("\n").map((line, i, arr) => i < arr.length - 1 ? line + "\n" : line);
|
|
1462
|
-
if (cellType) cell.cell_type = cellType;
|
|
1463
|
-
if (cell.cell_type === "code") {
|
|
1464
|
-
cell.outputs = [];
|
|
1465
|
-
cell.execution_count = null;
|
|
1466
|
-
}
|
|
1467
|
-
await writeFile3(filePath, JSON.stringify(nb, null, 1), "utf-8");
|
|
1468
|
-
return `\u2705 \u5DF2\u66FF\u6362 Cell ${cellNum} [${cell.cell_type}]\uFF08${newSource.split("\n").length} \u884C\uFF09`;
|
|
1469
|
-
} catch (err) {
|
|
1470
|
-
return `\u9519\u8BEF: \u65E0\u6CD5\u7F16\u8F91 Notebook \u2014 ${err instanceof Error ? err.message : String(err)}`;
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
};
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
// src/tools/index.ts
|
|
1477
|
-
async function createToolRegistry(config) {
|
|
1478
|
-
const tools = [
|
|
1479
|
-
createFileReadTool(),
|
|
1480
|
-
createFileWriteTool(),
|
|
1481
|
-
createFileEditTool(),
|
|
1482
|
-
createBashTool(),
|
|
1483
|
-
createTaskOutputTool(),
|
|
1484
|
-
createGlobTool(),
|
|
1485
|
-
createGrepTool(),
|
|
1486
|
-
createWebFetchTool()
|
|
1487
|
-
];
|
|
1488
|
-
tools.push(createMemorySaveTool());
|
|
1489
|
-
tools.push(createMemorySearchTool());
|
|
1490
|
-
tools.push(createNotebookReadTool());
|
|
1491
|
-
tools.push(createNotebookEditTool());
|
|
1492
|
-
if (config.tavilyApiKey) {
|
|
1493
|
-
tools.push(createWebSearchTool(config.tavilyApiKey));
|
|
1494
|
-
}
|
|
1495
|
-
const plugins = await loadPluginTools(HYPERCORE_DIR);
|
|
1496
|
-
tools.push(...plugins);
|
|
1497
|
-
const mcpTools = await loadMCPTools(HYPERCORE_DIR);
|
|
1498
|
-
tools.push(...mcpTools);
|
|
1499
|
-
return applyPermissions(tools, config);
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
export {
|
|
1503
|
-
closeMCPConnections,
|
|
1504
|
-
createToolRegistry
|
|
1505
|
-
};
|