@code4bug/jarvis-agent 1.0.2 → 1.0.3
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 +1 -1
- package/dist/cli.js +2 -2
- package/dist/commands/index.js +2 -2
- package/dist/commands/init.js +1 -1
- package/dist/components/MessageItem.d.ts +1 -1
- package/dist/components/MessageItem.js +10 -2
- package/dist/components/MultilineInput.d.ts +7 -1
- package/dist/components/MultilineInput.js +148 -4
- package/dist/components/SlashCommandMenu.d.ts +1 -1
- package/dist/components/StatusBar.js +1 -1
- package/dist/components/StreamingText.js +1 -1
- package/dist/components/WelcomeHeader.js +1 -1
- package/dist/config/constants.js +3 -3
- package/dist/config/loader.d.ts +2 -0
- package/dist/core/QueryEngine.d.ts +4 -4
- package/dist/core/QueryEngine.js +19 -17
- package/dist/core/WorkerBridge.d.ts +9 -0
- package/dist/core/WorkerBridge.js +109 -0
- package/dist/core/hint.js +4 -4
- package/dist/core/query.d.ts +8 -1
- package/dist/core/query.js +279 -57
- package/dist/core/queryWorker.d.ts +44 -0
- package/dist/core/queryWorker.js +66 -0
- package/dist/core/safeguard.js +1 -1
- package/dist/hooks/useDoubleCtrlCExit.d.ts +5 -0
- package/dist/hooks/useDoubleCtrlCExit.js +34 -0
- package/dist/hooks/useInputHistory.js +35 -3
- package/dist/hooks/useSlashMenu.d.ts +36 -0
- package/dist/hooks/useSlashMenu.js +216 -0
- package/dist/hooks/useStreamThrottle.d.ts +20 -0
- package/dist/hooks/useStreamThrottle.js +120 -0
- package/dist/hooks/useTerminalWidth.d.ts +2 -0
- package/dist/hooks/useTerminalWidth.js +13 -0
- package/dist/hooks/useTokenDisplay.d.ts +13 -0
- package/dist/hooks/useTokenDisplay.js +45 -0
- package/dist/index.js +1 -1
- package/dist/screens/repl.js +164 -636
- package/dist/screens/slashCommands.d.ts +7 -0
- package/dist/screens/slashCommands.js +134 -0
- package/dist/services/api/llm.d.ts +4 -2
- package/dist/services/api/llm.js +70 -16
- package/dist/services/api/mock.d.ts +1 -1
- package/dist/skills/index.d.ts +2 -2
- package/dist/skills/index.js +3 -3
- package/dist/tools/createSkill.d.ts +1 -1
- package/dist/tools/createSkill.js +3 -3
- package/dist/tools/index.d.ts +9 -8
- package/dist/tools/index.js +10 -9
- package/dist/tools/listDirectory.d.ts +1 -1
- package/dist/tools/readFile.d.ts +1 -1
- package/dist/tools/runCommand.d.ts +1 -1
- package/dist/tools/runCommand.js +38 -7
- package/dist/tools/searchFiles.d.ts +1 -1
- package/dist/tools/semanticSearch.d.ts +9 -0
- package/dist/tools/semanticSearch.js +159 -0
- package/dist/tools/writeFile.d.ts +1 -1
- package/dist/tools/writeFile.js +125 -25
- package/dist/types/index.d.ts +10 -1
- package/package.json +1 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { executeInit } from '../commands/init';
|
|
2
|
+
import { APP_VERSION } from '../config/constants';
|
|
3
|
+
import { allTools } from '../tools/index';
|
|
4
|
+
import { listSkills } from '../skills/index';
|
|
5
|
+
import { getExternalSkillsDir } from '../skills/loader';
|
|
6
|
+
import { listPermanentAuthorizations, DANGER_RULES, } from '../core/safeguard.js';
|
|
7
|
+
/**
|
|
8
|
+
* 斜杠命令执行器
|
|
9
|
+
*
|
|
10
|
+
* 纯函数,返回要追加的系统消息。不涉及 React 状态。
|
|
11
|
+
*/
|
|
12
|
+
export function executeSlashCommand(cmdName) {
|
|
13
|
+
switch (cmdName) {
|
|
14
|
+
case 'init': {
|
|
15
|
+
const result = executeInit();
|
|
16
|
+
return {
|
|
17
|
+
id: `init-${Date.now()}`,
|
|
18
|
+
type: 'system',
|
|
19
|
+
status: 'success',
|
|
20
|
+
content: result.displayText,
|
|
21
|
+
timestamp: Date.now(),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
case 'help': {
|
|
25
|
+
const helpText = [
|
|
26
|
+
'可用命令:',
|
|
27
|
+
' /init 初始化项目信息,生成 JARVIS.md',
|
|
28
|
+
' /new 开启新会话,重新初始化上下文',
|
|
29
|
+
' /resume 恢复历史会话(支持二级菜单选择)',
|
|
30
|
+
' /resume <ID> 直接恢复指定会话',
|
|
31
|
+
' /help 显示此帮助信息',
|
|
32
|
+
' /session_clear 清理所有非当前会话的历史记录',
|
|
33
|
+
' /skills 查看当前所有 tools 和 skills',
|
|
34
|
+
' /permissions 查看所有持久化授权列表',
|
|
35
|
+
' /create_skill <描述> 根据需求创建新 skill',
|
|
36
|
+
' /agent <名称> 切换智能体(需重启生效)',
|
|
37
|
+
' /read <路径> 读取文件内容',
|
|
38
|
+
' /write <路径> 写入文件',
|
|
39
|
+
' /bash <命令> 执行 Bash 命令',
|
|
40
|
+
' /ls <路径> 列出目录',
|
|
41
|
+
' /search <词> 搜索文件内容',
|
|
42
|
+
' /version 显示当前版本号',
|
|
43
|
+
'',
|
|
44
|
+
'快捷键:',
|
|
45
|
+
' Ctrl+L 清屏重置',
|
|
46
|
+
' Ctrl+O 切换详情显示',
|
|
47
|
+
' ESC 中断推理 / 双击清空输入',
|
|
48
|
+
' Ctrl+C ×2 退出',
|
|
49
|
+
].join('\n');
|
|
50
|
+
return {
|
|
51
|
+
id: `help-${Date.now()}`,
|
|
52
|
+
type: 'system',
|
|
53
|
+
status: 'success',
|
|
54
|
+
content: helpText,
|
|
55
|
+
timestamp: Date.now(),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
case 'permissions': {
|
|
59
|
+
const perms = listPermanentAuthorizations();
|
|
60
|
+
const lines = ['持久化授权列表 (~/.jarvis/.permissions.json)', ''];
|
|
61
|
+
if (perms.rules.length > 0) {
|
|
62
|
+
lines.push('按规则授权:');
|
|
63
|
+
for (const r of perms.rules) {
|
|
64
|
+
const rule = DANGER_RULES.find((d) => d.name === r);
|
|
65
|
+
lines.push(` [v] ${r}${rule ? ` — ${rule.reason}` : ''}`);
|
|
66
|
+
}
|
|
67
|
+
lines.push('');
|
|
68
|
+
}
|
|
69
|
+
if (perms.commands.length > 0) {
|
|
70
|
+
lines.push('按命令授权:');
|
|
71
|
+
for (const c of perms.commands) {
|
|
72
|
+
lines.push(` [v] [${c.ruleName}] ${c.command} (${c.grantedAt})`);
|
|
73
|
+
}
|
|
74
|
+
lines.push('');
|
|
75
|
+
}
|
|
76
|
+
if (perms.rules.length === 0 && perms.commands.length === 0) {
|
|
77
|
+
lines.push('(空) 暂无持久化授权记录');
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
id: `perms-${Date.now()}`,
|
|
81
|
+
type: 'system',
|
|
82
|
+
status: 'success',
|
|
83
|
+
content: lines.join('\n'),
|
|
84
|
+
timestamp: Date.now(),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
case 'skills': {
|
|
88
|
+
const skills = listSkills();
|
|
89
|
+
const parts = [];
|
|
90
|
+
parts.push('### Built-in Tools\n');
|
|
91
|
+
allTools.forEach((t, i) => {
|
|
92
|
+
parts.push(`${i + 1}. \`${t.name}\` - ${t.description.slice(0, 60)}`);
|
|
93
|
+
});
|
|
94
|
+
parts.push('');
|
|
95
|
+
parts.push(`### External Skills\n`);
|
|
96
|
+
parts.push(`> ${getExternalSkillsDir()}\n`);
|
|
97
|
+
if (skills.length === 0) {
|
|
98
|
+
parts.push('_(empty)_');
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
skills.forEach((s, i) => {
|
|
102
|
+
const hint = s.meta.argumentHint ? ` \`${s.meta.argumentHint}\`` : '';
|
|
103
|
+
const flags = [];
|
|
104
|
+
if (s.meta.disableModelInvocation)
|
|
105
|
+
flags.push('manual-only');
|
|
106
|
+
if (s.meta.userInvocable === false)
|
|
107
|
+
flags.push('hidden');
|
|
108
|
+
const flagStr = flags.length > 0 ? ` _(${flags.join(', ')})_` : '';
|
|
109
|
+
parts.push(`${i + 1}. \`${s.meta.name}\`${hint} - ${s.meta.description}${flagStr}`);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
parts.push('');
|
|
113
|
+
parts.push(`**Total:** ${allTools.length} tools + ${skills.length} skills = ${allTools.length + skills.length}`);
|
|
114
|
+
return {
|
|
115
|
+
id: `skills-${Date.now()}`,
|
|
116
|
+
type: 'system',
|
|
117
|
+
status: 'success',
|
|
118
|
+
content: parts.join('\n'),
|
|
119
|
+
timestamp: Date.now(),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
case 'version': {
|
|
123
|
+
return {
|
|
124
|
+
id: `version-${Date.now()}`,
|
|
125
|
+
type: 'system',
|
|
126
|
+
status: 'success',
|
|
127
|
+
content: `当前版本: ${APP_VERSION}`,
|
|
128
|
+
timestamp: Date.now(),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
default:
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -6,14 +6,16 @@
|
|
|
6
6
|
* 2. ~/.jarvis/config.json
|
|
7
7
|
* 3. ./.jarvis/config.json
|
|
8
8
|
*/
|
|
9
|
-
import { LLMService, StreamCallbacks, TranscriptMessage, Tool, AbortSignal as AppAbortSignal } from '../../types/index
|
|
10
|
-
import { ModelConfig } from '../../config/loader
|
|
9
|
+
import { LLMService, StreamCallbacks, TranscriptMessage, Tool, AbortSignal as AppAbortSignal } from '../../types/index';
|
|
10
|
+
import { ModelConfig } from '../../config/loader';
|
|
11
11
|
export interface LLMConfig {
|
|
12
12
|
apiKey: string;
|
|
13
13
|
model: string;
|
|
14
14
|
maxTokens: number;
|
|
15
15
|
baseUrl?: string;
|
|
16
16
|
temperature?: number;
|
|
17
|
+
/** 额外请求体参数,直接合并到 API 请求 body */
|
|
18
|
+
extraBody?: Record<string, unknown>;
|
|
17
19
|
}
|
|
18
20
|
/** 从配置文件构建 LLMConfig,找不到则回退环境变量 */
|
|
19
21
|
export declare function getDefaultConfig(): LLMConfig;
|
package/dist/services/api/llm.js
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* 2. ~/.jarvis/config.json
|
|
7
7
|
* 3. ./.jarvis/config.json
|
|
8
8
|
*/
|
|
9
|
-
import { loadConfig, getActiveModel } from '../../config/loader
|
|
10
|
-
import { getAgent } from '../../agents/index
|
|
11
|
-
import { DEFAULT_AGENT } from '../../config/constants
|
|
12
|
-
import { getActiveAgent } from '../../config/agentState
|
|
13
|
-
import { getSystemInfoPrompt } from '../../config/systemInfo
|
|
9
|
+
import { loadConfig, getActiveModel } from '../../config/loader';
|
|
10
|
+
import { getAgent } from '../../agents/index';
|
|
11
|
+
import { DEFAULT_AGENT } from '../../config/constants';
|
|
12
|
+
import { getActiveAgent } from '../../config/agentState';
|
|
13
|
+
import { getSystemInfoPrompt } from '../../config/systemInfo';
|
|
14
14
|
/** 从配置文件构建 LLMConfig,找不到则回退环境变量 */
|
|
15
15
|
export function getDefaultConfig() {
|
|
16
16
|
const jarvisCfg = loadConfig();
|
|
@@ -34,6 +34,7 @@ export function fromModelConfig(mc) {
|
|
|
34
34
|
maxTokens: mc.max_tokens ?? 4096,
|
|
35
35
|
baseUrl: mc.api_url,
|
|
36
36
|
temperature: mc.temperature,
|
|
37
|
+
extraBody: mc.extra_body,
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
40
|
/** 将内部 TranscriptMessage[] 转为 OpenAI messages 格式 */
|
|
@@ -118,7 +119,15 @@ function parseSSELine(line) {
|
|
|
118
119
|
return null;
|
|
119
120
|
try {
|
|
120
121
|
const parsed = JSON.parse(data);
|
|
121
|
-
|
|
122
|
+
const choice = parsed.choices?.[0];
|
|
123
|
+
if (!choice)
|
|
124
|
+
return null;
|
|
125
|
+
const delta = choice.delta;
|
|
126
|
+
if (!delta)
|
|
127
|
+
return null;
|
|
128
|
+
// 兼容 Qwen/vLLM:thinking 内容可能在 delta.reasoning_content 或 delta.content(role=thinking 时)
|
|
129
|
+
// 部分 vLLM 部署会将 thinking 内容放在 content 字段,通过 choice.finish_reason 或特殊标记区分
|
|
130
|
+
return delta;
|
|
122
131
|
}
|
|
123
132
|
catch {
|
|
124
133
|
return null;
|
|
@@ -166,6 +175,10 @@ export class LLMServiceImpl {
|
|
|
166
175
|
body.tools = openaiTools;
|
|
167
176
|
body.tool_choice = 'auto';
|
|
168
177
|
}
|
|
178
|
+
// 合并额外请求体参数(如 enable_thinking、chat_template_kwargs 等)
|
|
179
|
+
if (this.config.extraBody) {
|
|
180
|
+
Object.assign(body, this.config.extraBody);
|
|
181
|
+
}
|
|
169
182
|
const url = this.config.baseUrl || 'https://api.openai.com/v1/chat/completions';
|
|
170
183
|
// 创建 AbortController 用于取消 HTTP 请求
|
|
171
184
|
const controller = new AbortController();
|
|
@@ -213,6 +226,15 @@ export class LLMServiceImpl {
|
|
|
213
226
|
let buffer = '';
|
|
214
227
|
// 用于累积 tool_calls(可能跨多个 chunk)
|
|
215
228
|
const pendingToolCalls = new Map();
|
|
229
|
+
// 轮询 abortSignal,一旦外部中断立即 abort HTTP 请求,打断 reader.read() 阻塞
|
|
230
|
+
const abortPollTimer = abortSignal
|
|
231
|
+
? setInterval(() => {
|
|
232
|
+
if (abortSignal.aborted) {
|
|
233
|
+
controller.abort();
|
|
234
|
+
clearInterval(abortPollTimer);
|
|
235
|
+
}
|
|
236
|
+
}, 50)
|
|
237
|
+
: null;
|
|
216
238
|
try {
|
|
217
239
|
while (true) {
|
|
218
240
|
// 检查是否需要中断
|
|
@@ -240,8 +262,8 @@ export class LLMServiceImpl {
|
|
|
240
262
|
if (delta.reasoning_content && callbacks.onThinking) {
|
|
241
263
|
callbacks.onThinking(delta.reasoning_content);
|
|
242
264
|
}
|
|
243
|
-
//
|
|
244
|
-
if (delta.content) {
|
|
265
|
+
// 文本内容(注意:用 != null 判断,避免空字符串 "" 被跳过)
|
|
266
|
+
if (delta.content != null && delta.content !== '') {
|
|
245
267
|
callbacks.onText(delta.content);
|
|
246
268
|
}
|
|
247
269
|
// 工具调用(增量拼接)
|
|
@@ -285,16 +307,46 @@ export class LLMServiceImpl {
|
|
|
285
307
|
}
|
|
286
308
|
}
|
|
287
309
|
}
|
|
288
|
-
//
|
|
310
|
+
// 流结束后,触发工具调用回调
|
|
289
311
|
if (pendingToolCalls.size > 0) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
312
|
+
// 按 index 排序,收集所有有效的 tool_call
|
|
313
|
+
const sortedCalls = Array.from(pendingToolCalls.entries())
|
|
314
|
+
.sort(([a], [b]) => a - b)
|
|
315
|
+
.map(([, tc]) => tc)
|
|
316
|
+
.filter((tc) => tc.name);
|
|
317
|
+
if (sortedCalls.length > 0) {
|
|
318
|
+
if (sortedCalls.length === 1) {
|
|
319
|
+
// 单工具:走原有路径
|
|
320
|
+
const tc = sortedCalls[0];
|
|
321
|
+
let input = {};
|
|
322
|
+
try {
|
|
323
|
+
input = JSON.parse(tc.args);
|
|
324
|
+
}
|
|
325
|
+
catch { /* 参数解析失败则传空 */ }
|
|
326
|
+
callbacks.onToolUse(tc.id, tc.name, input);
|
|
327
|
+
}
|
|
328
|
+
else if (callbacks.onMultiToolUse) {
|
|
329
|
+
// 多工具:触发并行回调
|
|
330
|
+
const calls = sortedCalls.map((tc) => {
|
|
331
|
+
let input = {};
|
|
332
|
+
try {
|
|
333
|
+
input = JSON.parse(tc.args);
|
|
334
|
+
}
|
|
335
|
+
catch { /* ignore */ }
|
|
336
|
+
return { id: tc.id, name: tc.name, input };
|
|
337
|
+
});
|
|
338
|
+
callbacks.onMultiToolUse(calls);
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
// 降级:逐个触发 onToolUse(只触发第一个,保持原有行为)
|
|
342
|
+
const tc = sortedCalls[0];
|
|
343
|
+
let input = {};
|
|
344
|
+
try {
|
|
345
|
+
input = JSON.parse(tc.args);
|
|
346
|
+
}
|
|
347
|
+
catch { /* ignore */ }
|
|
348
|
+
callbacks.onToolUse(tc.id, tc.name, input);
|
|
295
349
|
}
|
|
296
|
-
catch { /* 参数解析失败则传空 */ }
|
|
297
|
-
callbacks.onToolUse(first.id, first.name, input);
|
|
298
350
|
return; // tool_use 时不触发 onComplete
|
|
299
351
|
}
|
|
300
352
|
}
|
|
@@ -308,6 +360,8 @@ export class LLMServiceImpl {
|
|
|
308
360
|
callbacks.onError(new Error(`流式读取失败: ${err.message}`));
|
|
309
361
|
}
|
|
310
362
|
finally {
|
|
363
|
+
if (abortPollTimer)
|
|
364
|
+
clearInterval(abortPollTimer);
|
|
311
365
|
reader.releaseLock();
|
|
312
366
|
}
|
|
313
367
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LLMService, StreamCallbacks, TranscriptMessage, Tool, AbortSignal as AppAbortSignal } from '../../types/index
|
|
1
|
+
import { LLMService, StreamCallbacks, TranscriptMessage, Tool, AbortSignal as AppAbortSignal } from '../../types/index';
|
|
2
2
|
/**
|
|
3
3
|
* Mock LLM 服务 - 模拟智能体行为,支持工具调用
|
|
4
4
|
*/
|
package/dist/skills/index.d.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* 如果 skill 目录下存在 skill.py,execute 时直接调用 Python 脚本获取真实结果;
|
|
10
10
|
* 否则回退为返回 skill 指令文本(由 LLM 解释执行)。
|
|
11
11
|
*/
|
|
12
|
-
import { Tool } from '../types/index
|
|
13
|
-
import { SkillDefinition } from './loader
|
|
12
|
+
import { Tool } from '../types/index';
|
|
13
|
+
import { SkillDefinition } from './loader';
|
|
14
14
|
/** 加载所有外部 skills(带缓存) */
|
|
15
15
|
export declare function loadExternalSkills(): SkillDefinition[];
|
|
16
16
|
/** 获取合并后的所有工具:内置 tools + 外部 skills */
|
package/dist/skills/index.js
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
import { exec } from 'child_process';
|
|
13
13
|
import fs from 'fs';
|
|
14
14
|
import path from 'path';
|
|
15
|
-
import { scanExternalSkills, getExternalSkillsDir } from './loader
|
|
16
|
-
import { allTools as builtinTools } from '../tools/index
|
|
15
|
+
import { scanExternalSkills, getExternalSkillsDir } from './loader';
|
|
16
|
+
import { allTools as builtinTools } from '../tools/index';
|
|
17
17
|
// ===== 缓存 =====
|
|
18
18
|
let _skillCache = null;
|
|
19
19
|
let _mergedTools = null;
|
|
@@ -110,7 +110,7 @@ async function executeSkillScript(scriptPath, skill, args) {
|
|
|
110
110
|
// 写入临时 Python 文件,避免 shell -c 的转义问题
|
|
111
111
|
const tmpFile = path.join(skill.dirPath, `_tmp_run_${Date.now()}.py`);
|
|
112
112
|
const pyCode = [
|
|
113
|
-
'import sys,
|
|
113
|
+
'import sys,os,json',
|
|
114
114
|
`sys.path.insert(0, ${JSON.stringify(path.dirname(scriptPath))})`,
|
|
115
115
|
`from skill import ${funcName}`,
|
|
116
116
|
`result = ${funcName}(${kwargs.join(', ')})`,
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import os from 'os';
|
|
10
|
-
import { getExternalSkillsDir } from '../skills/loader
|
|
11
|
-
import { reloadSkills } from '../skills/index
|
|
12
|
-
import { LLMServiceImpl, getDefaultConfig } from '../services/api/llm
|
|
10
|
+
import { getExternalSkillsDir } from '../skills/loader';
|
|
11
|
+
import { reloadSkills } from '../skills/index';
|
|
12
|
+
import { LLMServiceImpl, getDefaultConfig } from '../services/api/llm';
|
|
13
13
|
// SKILL_INSTRUCTIONS.md 查找路径:项目根目录 > 用户主目录
|
|
14
14
|
function loadSkillInstructions() {
|
|
15
15
|
const candidates = [
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { Tool } from '../types/index
|
|
2
|
-
import { readFile } from './readFile
|
|
3
|
-
import { writeFile } from './writeFile
|
|
4
|
-
import { runCommand } from './runCommand
|
|
5
|
-
import { listDirectory } from './listDirectory
|
|
6
|
-
import { searchFiles } from './searchFiles
|
|
7
|
-
import {
|
|
8
|
-
|
|
1
|
+
import { Tool } from '../types/index';
|
|
2
|
+
import { readFile } from './readFile';
|
|
3
|
+
import { writeFile } from './writeFile';
|
|
4
|
+
import { runCommand } from './runCommand';
|
|
5
|
+
import { listDirectory } from './listDirectory';
|
|
6
|
+
import { searchFiles } from './searchFiles';
|
|
7
|
+
import { semanticSearch } from './semanticSearch';
|
|
8
|
+
import { createSkill } from './createSkill';
|
|
9
|
+
export { readFile, writeFile, runCommand, listDirectory, searchFiles, semanticSearch, createSkill };
|
|
9
10
|
/** 所有内置工具 */
|
|
10
11
|
export declare const allTools: Tool[];
|
|
11
12
|
/** 按名称查找内置工具 */
|
package/dist/tools/index.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import { readFile } from './readFile
|
|
2
|
-
import { writeFile } from './writeFile
|
|
3
|
-
import { runCommand } from './runCommand
|
|
4
|
-
import { listDirectory } from './listDirectory
|
|
5
|
-
import { searchFiles } from './searchFiles
|
|
6
|
-
import {
|
|
7
|
-
|
|
1
|
+
import { readFile } from './readFile';
|
|
2
|
+
import { writeFile } from './writeFile';
|
|
3
|
+
import { runCommand } from './runCommand';
|
|
4
|
+
import { listDirectory } from './listDirectory';
|
|
5
|
+
import { searchFiles } from './searchFiles';
|
|
6
|
+
import { semanticSearch } from './semanticSearch';
|
|
7
|
+
import { createSkill } from './createSkill';
|
|
8
|
+
export { readFile, writeFile, runCommand, listDirectory, searchFiles, semanticSearch, createSkill };
|
|
8
9
|
/** 所有内置工具 */
|
|
9
|
-
export const allTools = [readFile, writeFile, runCommand, listDirectory, searchFiles, createSkill];
|
|
10
|
+
export const allTools = [readFile, writeFile, runCommand, listDirectory, searchFiles, semanticSearch, createSkill];
|
|
10
11
|
/** 按名称查找内置工具 */
|
|
11
12
|
export function findTool(name) {
|
|
12
13
|
return allTools.find((t) => t.name === name);
|
|
13
14
|
}
|
|
14
15
|
// ===== 合并工具(内置 + 外部 Skills)=====
|
|
15
|
-
import { getMergedTools, findMergedTool } from '../skills/index
|
|
16
|
+
import { getMergedTools, findMergedTool } from '../skills/index';
|
|
16
17
|
/** 获取所有工具(内置 + 外部 skills),供 QueryEngine 使用 */
|
|
17
18
|
export function getAllTools() {
|
|
18
19
|
return getMergedTools();
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Tool } from '../types/index
|
|
1
|
+
import { Tool } from '../types/index';
|
|
2
2
|
export declare const listDirectory: Tool;
|
package/dist/tools/readFile.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Tool } from '../types/index
|
|
1
|
+
import { Tool } from '../types/index';
|
|
2
2
|
export declare const readFile: Tool;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Tool } from '../types/index
|
|
1
|
+
import { Tool } from '../types/index';
|
|
2
2
|
export declare const runCommand: Tool;
|
package/dist/tools/runCommand.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { exec } from 'child_process';
|
|
2
|
-
import { sanitizeOutput } from '../core/safeguard
|
|
2
|
+
import { sanitizeOutput } from '../core/safeguard';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* 异步执行命令,支持通过 abortSignal 中断子进程
|
|
5
5
|
*/
|
|
6
|
-
function execAsync(command, options) {
|
|
6
|
+
function execAsync(command, options, abortSignal) {
|
|
7
7
|
return new Promise((resolve, reject) => {
|
|
8
|
-
exec(command, options, (error, stdout, stderr) => {
|
|
8
|
+
const child = exec(command, options, (error, stdout, stderr) => {
|
|
9
|
+
// 清理轮询
|
|
10
|
+
if (pollTimer !== null)
|
|
11
|
+
clearInterval(pollTimer);
|
|
12
|
+
// 被中断时直接返回已有输出,不视为错误
|
|
13
|
+
if (abortSignal?.aborted) {
|
|
14
|
+
const partial = sanitizeOutput(String(stdout ?? '').trim());
|
|
15
|
+
resolve(partial ? `(命令被中断)\n${partial}` : '(命令被中断)');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
9
18
|
if (error) {
|
|
10
19
|
const parts = [];
|
|
11
20
|
if (stderr)
|
|
@@ -21,6 +30,29 @@ function execAsync(command, options) {
|
|
|
21
30
|
}
|
|
22
31
|
resolve(sanitizeOutput(String(stdout).trim()) || '(命令执行完成,无输出)');
|
|
23
32
|
});
|
|
33
|
+
// 轮询 abortSignal,检测到中断时 kill 子进程
|
|
34
|
+
let pollTimer = null;
|
|
35
|
+
if (abortSignal) {
|
|
36
|
+
pollTimer = setInterval(() => {
|
|
37
|
+
if (abortSignal.aborted && child.pid) {
|
|
38
|
+
if (pollTimer !== null)
|
|
39
|
+
clearInterval(pollTimer);
|
|
40
|
+
pollTimer = null;
|
|
41
|
+
// 先尝试 SIGTERM,给进程优雅退出的机会
|
|
42
|
+
try {
|
|
43
|
+
child.kill('SIGTERM');
|
|
44
|
+
}
|
|
45
|
+
catch { /* ignore */ }
|
|
46
|
+
// 500ms 后强制 SIGKILL
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
try {
|
|
49
|
+
child.kill('SIGKILL');
|
|
50
|
+
}
|
|
51
|
+
catch { /* ignore */ }
|
|
52
|
+
}, 500);
|
|
53
|
+
}
|
|
54
|
+
}, 100);
|
|
55
|
+
}
|
|
24
56
|
});
|
|
25
57
|
}
|
|
26
58
|
export const runCommand = {
|
|
@@ -29,15 +61,14 @@ export const runCommand = {
|
|
|
29
61
|
parameters: {
|
|
30
62
|
command: { type: 'string', description: '要执行的 Bash 命令', required: true },
|
|
31
63
|
},
|
|
32
|
-
execute: async (args) => {
|
|
64
|
+
execute: async (args, abortSignal) => {
|
|
33
65
|
const command = args.command;
|
|
34
|
-
// 安全围栏拦截已在 query 层(executeTool)统一处理,此处仅负责执行 + 脱敏
|
|
35
66
|
return execAsync(command, {
|
|
36
67
|
encoding: 'utf-8',
|
|
37
68
|
timeout: 30000,
|
|
38
69
|
maxBuffer: 1024 * 1024,
|
|
39
70
|
env: sanitizeEnv(process.env),
|
|
40
|
-
});
|
|
71
|
+
}, abortSignal);
|
|
41
72
|
},
|
|
42
73
|
};
|
|
43
74
|
/**
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Tool } from '../types/index
|
|
1
|
+
import { Tool } from '../types/index';
|
|
2
2
|
export declare const searchFiles: Tool;
|