@code4bug/jarvis-agent 1.0.2 → 1.0.4
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/components/MessageItem.js +9 -1
- package/dist/components/MultilineInput.d.ts +7 -1
- package/dist/components/MultilineInput.js +148 -4
- package/dist/config/loader.d.ts +2 -0
- package/dist/core/QueryEngine.d.ts +3 -3
- package/dist/core/QueryEngine.js +13 -11
- package/dist/core/WorkerBridge.d.ts +9 -0
- package/dist/core/WorkerBridge.js +109 -0
- package/dist/core/query.d.ts +8 -1
- package/dist/core/query.js +276 -54
- 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/screens/repl.js +153 -625
- package/dist/screens/slashCommands.d.ts +7 -0
- package/dist/screens/slashCommands.js +134 -0
- package/dist/services/api/llm.d.ts +2 -0
- package/dist/services/api/llm.js +65 -11
- package/dist/skills/index.js +1 -1
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.js +3 -2
- package/dist/tools/runCommand.js +37 -6
- package/dist/tools/semanticSearch.d.ts +9 -0
- package/dist/tools/semanticSearch.js +159 -0
- package/dist/tools/writeFile.js +124 -24
- package/dist/types/index.d.ts +10 -1
- package/package.json +1 -1
package/dist/tools/writeFile.js
CHANGED
|
@@ -1,42 +1,142 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { detectSensitiveContent } from '../core/safeguard.js';
|
|
4
|
+
/**
|
|
5
|
+
* 将 unified diff 补丁应用到原始文本上。
|
|
6
|
+
* 支持标准 unified diff 格式(@@ -a,b +c,d @@ 开头的 hunk)。
|
|
7
|
+
*/
|
|
8
|
+
function applyUnifiedDiff(original, diff) {
|
|
9
|
+
const originalLines = original.split('\n');
|
|
10
|
+
const diffLines = diff.split('\n');
|
|
11
|
+
const hunks = [];
|
|
12
|
+
let currentHunk = null;
|
|
13
|
+
for (const line of diffLines) {
|
|
14
|
+
// 跳过 --- / +++ 头部行
|
|
15
|
+
if (line.startsWith('--- ') || line.startsWith('+++ '))
|
|
16
|
+
continue;
|
|
17
|
+
const hunkHeader = line.match(/^@@\s+-(\d+)(?:,(\d+))?\s+\+\d+(?:,\d+)?\s+@@/);
|
|
18
|
+
if (hunkHeader) {
|
|
19
|
+
currentHunk = {
|
|
20
|
+
oldStart: parseInt(hunkHeader[1], 10),
|
|
21
|
+
oldCount: hunkHeader[2] !== undefined ? parseInt(hunkHeader[2], 10) : 1,
|
|
22
|
+
lines: [],
|
|
23
|
+
};
|
|
24
|
+
hunks.push(currentHunk);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (currentHunk && (line.startsWith('+') || line.startsWith('-') || line.startsWith(' ') || line === '')) {
|
|
28
|
+
// 空行在 diff 中视为上下文行(无前缀空格的情况)
|
|
29
|
+
if (line === '' && currentHunk.lines.length > 0) {
|
|
30
|
+
currentHunk.lines.push(' ');
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
currentHunk.lines.push(line);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (hunks.length === 0) {
|
|
38
|
+
throw new Error('未找到有效的 diff hunk(需要 @@ -a,b +c,d @@ 格式)');
|
|
39
|
+
}
|
|
40
|
+
// 从后往前应用 hunk,避免行号偏移
|
|
41
|
+
hunks.sort((a, b) => b.oldStart - a.oldStart);
|
|
42
|
+
const result = [...originalLines];
|
|
43
|
+
for (const hunk of hunks) {
|
|
44
|
+
const startIdx = hunk.oldStart - 1; // 转为 0-based
|
|
45
|
+
const removedLines = [];
|
|
46
|
+
const addedLines = [];
|
|
47
|
+
// 先收集本 hunk 中所有操作,按顺序重建目标区域
|
|
48
|
+
const newSection = [];
|
|
49
|
+
let oldLineIdx = startIdx;
|
|
50
|
+
for (const hLine of hunk.lines) {
|
|
51
|
+
if (hLine.startsWith('-')) {
|
|
52
|
+
// 删除行:跳过原文中对应行
|
|
53
|
+
oldLineIdx++;
|
|
54
|
+
}
|
|
55
|
+
else if (hLine.startsWith('+')) {
|
|
56
|
+
// 新增行
|
|
57
|
+
newSection.push(hLine.substring(1));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// 上下文行(以空格开头或空行)
|
|
61
|
+
newSection.push(hLine.substring(1));
|
|
62
|
+
oldLineIdx++;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// 计算实际消耗的原文行数
|
|
66
|
+
let oldLinesConsumed = 0;
|
|
67
|
+
for (const hLine of hunk.lines) {
|
|
68
|
+
if (hLine.startsWith('-') || hLine.startsWith(' ')) {
|
|
69
|
+
oldLinesConsumed++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
result.splice(startIdx, oldLinesConsumed, ...newSection);
|
|
73
|
+
}
|
|
74
|
+
return result.join('\n');
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 写入文件内容(支持安全检测)。
|
|
78
|
+
* 返回写入结果消息。
|
|
79
|
+
*/
|
|
80
|
+
function doWrite(filePath, content) {
|
|
81
|
+
const findings = detectSensitiveContent(content);
|
|
82
|
+
const warning = findings.length > 0
|
|
83
|
+
? `\n检测到文件中包含疑似敏感信息: ${findings.join(', ')}\n建议使用环境变量替代硬编码。文件仍将写入,但请注意安全风险。`
|
|
84
|
+
: '';
|
|
85
|
+
try {
|
|
86
|
+
const dir = path.dirname(filePath);
|
|
87
|
+
if (!fs.existsSync(dir))
|
|
88
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
89
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
90
|
+
return `文件已写入: ${filePath}${warning}`;
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
throw new Error(`写入文件失败: ${e.message}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
4
96
|
export const writeFile = {
|
|
5
97
|
name: 'write_file',
|
|
6
|
-
description: '
|
|
98
|
+
description: '写入内容到指定文件。支持两种模式:overwrite(默认)完整替换文件内容;diff 模式接收 unified diff 格式补丁,对文件进行增量更新。',
|
|
7
99
|
parameters: {
|
|
8
100
|
path: { type: 'string', description: '文件路径', required: true },
|
|
9
|
-
content: { type: 'string', description: '
|
|
101
|
+
content: { type: 'string', description: '文件内容(overwrite 模式必填)', required: false },
|
|
102
|
+
mode: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
description: '写入模式:overwrite(完整替换,默认)| diff(增量更新)',
|
|
105
|
+
required: false,
|
|
106
|
+
},
|
|
107
|
+
diff: {
|
|
108
|
+
type: 'string',
|
|
109
|
+
description: 'unified diff 格式的补丁内容(diff 模式必填),需包含 @@ hunk header',
|
|
110
|
+
required: false,
|
|
111
|
+
},
|
|
10
112
|
},
|
|
11
113
|
execute: async (args) => {
|
|
12
114
|
const filePath = args.path;
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
//
|
|
115
|
+
const mode = args.mode || 'overwrite';
|
|
116
|
+
if (mode === 'diff') {
|
|
117
|
+
const diffContent = args.diff;
|
|
118
|
+
if (!diffContent) {
|
|
119
|
+
throw new Error('diff 模式下必须提供 diff 参数');
|
|
120
|
+
}
|
|
121
|
+
// 读取原文件
|
|
122
|
+
if (!fs.existsSync(filePath)) {
|
|
123
|
+
throw new Error(`diff 模式要求目标文件已存在: ${filePath}`);
|
|
124
|
+
}
|
|
125
|
+
const original = fs.readFileSync(filePath, 'utf-8');
|
|
126
|
+
let patched;
|
|
20
127
|
try {
|
|
21
|
-
|
|
22
|
-
if (!fs.existsSync(dir))
|
|
23
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
24
|
-
fs.writeFileSync(filePath, content, 'utf-8');
|
|
25
|
-
return `文件已写入: ${filePath}\n${warning}`;
|
|
128
|
+
patched = applyUnifiedDiff(original, diffContent);
|
|
26
129
|
}
|
|
27
130
|
catch (e) {
|
|
28
|
-
throw new Error(
|
|
131
|
+
throw new Error(`应用 diff 失败: ${e.message}`);
|
|
29
132
|
}
|
|
133
|
+
return doWrite(filePath, patched);
|
|
30
134
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
fs.writeFileSync(filePath, content, 'utf-8');
|
|
36
|
-
return `文件已写入: ${filePath}`;
|
|
37
|
-
}
|
|
38
|
-
catch (e) {
|
|
39
|
-
throw new Error(`写入文件失败: ${e.message}`);
|
|
135
|
+
// overwrite 模式
|
|
136
|
+
const content = args.content;
|
|
137
|
+
if (content === undefined || content === null) {
|
|
138
|
+
throw new Error('overwrite 模式下必须提供 content 参数');
|
|
40
139
|
}
|
|
140
|
+
return doWrite(filePath, content);
|
|
41
141
|
},
|
|
42
142
|
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface Message {
|
|
|
22
22
|
think?: string;
|
|
23
23
|
/** 中断提示文案,仅 aborted 状态时使用 */
|
|
24
24
|
abortHint?: string;
|
|
25
|
+
/** 并行执行组 ID,同组工具同时运行 */
|
|
26
|
+
parallelGroupId?: string;
|
|
25
27
|
}
|
|
26
28
|
export type ContentBlock = {
|
|
27
29
|
type: 'text';
|
|
@@ -47,7 +49,7 @@ export interface Tool {
|
|
|
47
49
|
name: string;
|
|
48
50
|
description: string;
|
|
49
51
|
parameters: Record<string, ToolParameter>;
|
|
50
|
-
execute: (args: Record<string, unknown
|
|
52
|
+
execute: (args: Record<string, unknown>, abortSignal?: AbortSignal) => Promise<string>;
|
|
51
53
|
}
|
|
52
54
|
export interface Session {
|
|
53
55
|
id: string;
|
|
@@ -64,11 +66,18 @@ export interface LLMServiceConfig {
|
|
|
64
66
|
model?: string;
|
|
65
67
|
maxTokens?: number;
|
|
66
68
|
}
|
|
69
|
+
export interface ToolCallInfo {
|
|
70
|
+
id: string;
|
|
71
|
+
name: string;
|
|
72
|
+
input: Record<string, unknown>;
|
|
73
|
+
}
|
|
67
74
|
export interface StreamCallbacks {
|
|
68
75
|
onText: (text: string) => void;
|
|
69
76
|
/** 大模型思考过程(reasoning_content),仅本地展示 */
|
|
70
77
|
onThinking?: (text: string) => void;
|
|
71
78
|
onToolUse: (id: string, name: string, input: Record<string, unknown>) => void;
|
|
79
|
+
/** LLM 返回多个并行工具调用时触发(替代多次 onToolUse) */
|
|
80
|
+
onMultiToolUse?: (calls: ToolCallInfo[]) => void;
|
|
72
81
|
onComplete: () => void;
|
|
73
82
|
onError: (error: Error) => void;
|
|
74
83
|
}
|