openhorse 0.2.22 → 0.2.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/target-command.d.ts +21 -0
- package/dist/commands/target-command.d.ts.map +1 -0
- package/dist/commands/target-command.js +81 -0
- package/dist/commands/target-command.js.map +1 -0
- package/dist/harness/capsule.d.ts +1 -0
- package/dist/harness/capsule.d.ts.map +1 -1
- package/dist/harness/capsule.js +38 -9
- package/dist/harness/capsule.js.map +1 -1
- package/dist/harness/contract.d.ts +2 -0
- package/dist/harness/contract.d.ts.map +1 -1
- package/dist/harness/contract.js +78 -14
- package/dist/harness/contract.js.map +1 -1
- package/dist/harness/index.d.ts +2 -2
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js +4 -1
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/state.d.ts.map +1 -1
- package/dist/harness/state.js +37 -7
- package/dist/harness/state.js.map +1 -1
- package/dist/runtime/agent-runtime-controller.d.ts +5 -0
- package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
- package/dist/runtime/agent-runtime-controller.js +54 -0
- package/dist/runtime/agent-runtime-controller.js.map +1 -1
- package/dist/runtime/agent-runtime-protocol.d.ts +4 -0
- package/dist/runtime/agent-runtime-protocol.d.ts.map +1 -1
- package/dist/runtime/agent-runtime-protocol.js.map +1 -1
- package/dist/runtime/aggregate-tool-presenter.d.ts +13 -0
- package/dist/runtime/aggregate-tool-presenter.d.ts.map +1 -0
- package/dist/runtime/aggregate-tool-presenter.js +44 -0
- package/dist/runtime/aggregate-tool-presenter.js.map +1 -0
- package/dist/runtime/chat-controller.d.ts +9 -2
- package/dist/runtime/chat-controller.d.ts.map +1 -1
- package/dist/runtime/chat-controller.js +129 -17
- package/dist/runtime/chat-controller.js.map +1 -1
- package/dist/runtime/goal-coordinator.d.ts +66 -0
- package/dist/runtime/goal-coordinator.d.ts.map +1 -0
- package/dist/runtime/goal-coordinator.js +200 -0
- package/dist/runtime/goal-coordinator.js.map +1 -0
- package/dist/runtime/goals/accounting.d.ts +17 -0
- package/dist/runtime/goals/accounting.d.ts.map +1 -0
- package/dist/runtime/goals/accounting.js +45 -0
- package/dist/runtime/goals/accounting.js.map +1 -0
- package/dist/runtime/goals/completion-audit.d.ts +24 -0
- package/dist/runtime/goals/completion-audit.d.ts.map +1 -0
- package/dist/runtime/goals/completion-audit.js +48 -0
- package/dist/runtime/goals/completion-audit.js.map +1 -0
- package/dist/runtime/goals/coordinator.d.ts +42 -0
- package/dist/runtime/goals/coordinator.d.ts.map +1 -0
- package/dist/runtime/goals/coordinator.js +277 -0
- package/dist/runtime/goals/coordinator.js.map +1 -0
- package/dist/runtime/goals/prompt.d.ts +16 -0
- package/dist/runtime/goals/prompt.d.ts.map +1 -0
- package/dist/runtime/goals/prompt.js +50 -0
- package/dist/runtime/goals/prompt.js.map +1 -0
- package/dist/runtime/goals/tools.d.ts +43 -0
- package/dist/runtime/goals/tools.d.ts.map +1 -0
- package/dist/runtime/goals/tools.js +88 -0
- package/dist/runtime/goals/tools.js.map +1 -0
- package/dist/runtime/goals/types.d.ts +167 -0
- package/dist/runtime/goals/types.d.ts.map +1 -0
- package/dist/runtime/goals/types.js +72 -0
- package/dist/runtime/goals/types.js.map +1 -0
- package/dist/runtime/tool-detail-repository.d.ts +45 -0
- package/dist/runtime/tool-detail-repository.d.ts.map +1 -0
- package/dist/runtime/tool-detail-repository.js +131 -0
- package/dist/runtime/tool-detail-repository.js.map +1 -0
- package/dist/runtime/tool-output-presentation.d.ts +89 -0
- package/dist/runtime/tool-output-presentation.d.ts.map +1 -0
- package/dist/runtime/tool-output-presentation.js +329 -0
- package/dist/runtime/tool-output-presentation.js.map +1 -0
- package/dist/runtime/ui-events.d.ts +4 -0
- package/dist/runtime/ui-events.d.ts.map +1 -1
- package/dist/runtime/ui-events.js.map +1 -1
- package/dist/services/goal-sidecar.d.ts +96 -0
- package/dist/services/goal-sidecar.d.ts.map +1 -0
- package/dist/services/goal-sidecar.js +145 -0
- package/dist/services/goal-sidecar.js.map +1 -0
- package/dist/services/goal-storage.d.ts +22 -0
- package/dist/services/goal-storage.d.ts.map +1 -0
- package/dist/services/goal-storage.js +153 -0
- package/dist/services/goal-storage.js.map +1 -0
- package/dist/services/model-context.d.ts.map +1 -1
- package/dist/services/model-context.js +7 -0
- package/dist/services/model-context.js.map +1 -1
- package/dist/services/session-storage.d.ts +7 -0
- package/dist/services/session-storage.d.ts.map +1 -1
- package/dist/services/session-storage.js +64 -27
- package/dist/services/session-storage.js.map +1 -1
- package/dist/services/storage-maintenance.d.ts.map +1 -1
- package/dist/services/storage-maintenance.js +3 -2
- package/dist/services/storage-maintenance.js.map +1 -1
- package/dist/terminal-ui/editor.d.ts.map +1 -1
- package/dist/terminal-ui/editor.js +14 -3
- package/dist/terminal-ui/editor.js.map +1 -1
- package/dist/terminal-ui/launch.d.ts +19 -0
- package/dist/terminal-ui/launch.d.ts.map +1 -1
- package/dist/terminal-ui/launch.js +187 -24
- package/dist/terminal-ui/launch.js.map +1 -1
- package/dist/terminal-ui/output-queue.d.ts +43 -0
- package/dist/terminal-ui/output-queue.d.ts.map +1 -0
- package/dist/terminal-ui/output-queue.js +139 -0
- package/dist/terminal-ui/output-queue.js.map +1 -0
- package/dist/terminal-ui/raw-editor.d.ts +33 -0
- package/dist/terminal-ui/raw-editor.d.ts.map +1 -1
- package/dist/terminal-ui/raw-editor.js +142 -23
- package/dist/terminal-ui/raw-editor.js.map +1 -1
- package/dist/tools/goal.d.ts +17 -0
- package/dist/tools/goal.d.ts.map +1 -0
- package/dist/tools/goal.js +191 -0
- package/dist/tools/goal.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +133 -58
- package/dist/tools/index.js.map +1 -1
- package/dist/tui-core/input-parser.d.ts +9 -2
- package/dist/tui-core/input-parser.d.ts.map +1 -1
- package/dist/tui-core/input-parser.js +37 -2
- package/dist/tui-core/input-parser.js.map +1 -1
- package/dist/tui-ui/inline-surface.d.ts +7 -1
- package/dist/tui-ui/inline-surface.d.ts.map +1 -1
- package/dist/tui-ui/inline-surface.js +23 -2
- package/dist/tui-ui/inline-surface.js.map +1 -1
- package/dist/tui-ui/input-ownership.d.ts +46 -0
- package/dist/tui-ui/input-ownership.d.ts.map +1 -0
- package/dist/tui-ui/input-ownership.js +74 -0
- package/dist/tui-ui/input-ownership.js.map +1 -0
- package/dist/tui-ui/launch.d.ts +1 -0
- package/dist/tui-ui/launch.d.ts.map +1 -1
- package/dist/tui-ui/launch.js +104 -2
- package/dist/tui-ui/launch.js.map +1 -1
- package/dist/tui-ui/layout.d.ts +1 -0
- package/dist/tui-ui/layout.d.ts.map +1 -1
- package/dist/tui-ui/layout.js +5 -1
- package/dist/tui-ui/layout.js.map +1 -1
- package/dist/tui-ui/runner.d.ts +32 -0
- package/dist/tui-ui/runner.d.ts.map +1 -1
- package/dist/tui-ui/runner.js +553 -24
- package/dist/tui-ui/runner.js.map +1 -1
- package/dist/tui-ui/state.d.ts +65 -0
- package/dist/tui-ui/state.d.ts.map +1 -1
- package/dist/tui-ui/state.js +187 -2
- package/dist/tui-ui/state.js.map +1 -1
- package/dist/tui-ui/theme-profile.d.ts +25 -0
- package/dist/tui-ui/theme-profile.d.ts.map +1 -0
- package/dist/tui-ui/theme-profile.js +130 -0
- package/dist/tui-ui/theme-profile.js.map +1 -0
- package/dist/tui-ui/transcript-inspector-layout.d.ts +9 -0
- package/dist/tui-ui/transcript-inspector-layout.d.ts.map +1 -0
- package/dist/tui-ui/transcript-inspector-layout.js +97 -0
- package/dist/tui-ui/transcript-inspector-layout.js.map +1 -0
- package/dist/tui-ui/transcript-inspector-surface.d.ts +18 -0
- package/dist/tui-ui/transcript-inspector-surface.d.ts.map +1 -0
- package/dist/tui-ui/transcript-inspector-surface.js +88 -0
- package/dist/tui-ui/transcript-inspector-surface.js.map +1 -0
- package/dist/tui-ui/transcript-inspector.d.ts +35 -0
- package/dist/tui-ui/transcript-inspector.d.ts.map +1 -0
- package/dist/tui-ui/transcript-inspector.js +128 -0
- package/dist/tui-ui/transcript-inspector.js.map +1 -0
- package/dist/tui-ui/transcript-layout.d.ts +1 -0
- package/dist/tui-ui/transcript-layout.d.ts.map +1 -1
- package/dist/tui-ui/transcript-layout.js +44 -3
- package/dist/tui-ui/transcript-layout.js.map +1 -1
- package/package.json +1 -1
package/dist/tools/index.js
CHANGED
|
@@ -75,7 +75,7 @@ exports.TOOLS = [
|
|
|
75
75
|
// File tools
|
|
76
76
|
(0, tool_1.buildTool)({
|
|
77
77
|
name: 'read_file',
|
|
78
|
-
description: '
|
|
78
|
+
description: '读取文件内容,支持从指定行开始分页。返回文件内容字符串。',
|
|
79
79
|
parameters: {
|
|
80
80
|
type: 'object',
|
|
81
81
|
properties: {
|
|
@@ -87,6 +87,10 @@ exports.TOOLS = [
|
|
|
87
87
|
type: 'number',
|
|
88
88
|
description: '最大读取行数(可选,默认 500 行)',
|
|
89
89
|
},
|
|
90
|
+
offset: {
|
|
91
|
+
type: 'number',
|
|
92
|
+
description: '开始读取的行号(可选,1-based,默认第 1 行)',
|
|
93
|
+
},
|
|
90
94
|
},
|
|
91
95
|
required: ['path'],
|
|
92
96
|
},
|
|
@@ -96,11 +100,19 @@ exports.TOOLS = [
|
|
|
96
100
|
if (!path || typeof path !== 'string') {
|
|
97
101
|
return { success: false, output: '', error: 'read_file requires a path parameter' };
|
|
98
102
|
}
|
|
99
|
-
|
|
103
|
+
const maxLines = readPositiveInteger(args.maxLines, 'maxLines', 500);
|
|
104
|
+
if (typeof maxLines === 'string') {
|
|
105
|
+
return { success: false, output: '', error: maxLines };
|
|
106
|
+
}
|
|
107
|
+
const offset = readPositiveInteger(args.offset, 'offset', 1);
|
|
108
|
+
if (typeof offset === 'string') {
|
|
109
|
+
return { success: false, output: '', error: offset };
|
|
110
|
+
}
|
|
111
|
+
return readFileSync_(path, maxLines, offset, context.cwd);
|
|
100
112
|
},
|
|
101
113
|
isReadOnly: () => true,
|
|
102
114
|
isConcurrencySafe: () => true,
|
|
103
|
-
userFacingName:
|
|
115
|
+
userFacingName: args => `Read ${args.path}`,
|
|
104
116
|
getSummary: (args, result) => {
|
|
105
117
|
const path = args.path;
|
|
106
118
|
if (!result.success)
|
|
@@ -144,7 +156,7 @@ exports.TOOLS = [
|
|
|
144
156
|
// Destructive operation - ask for confirmation in default mode
|
|
145
157
|
return { behavior: 'ask', reason: 'Write operation may modify existing files' };
|
|
146
158
|
},
|
|
147
|
-
userFacingName:
|
|
159
|
+
userFacingName: args => `Write ${args.path}`,
|
|
148
160
|
getSummary: (args, result) => {
|
|
149
161
|
const path = args.path;
|
|
150
162
|
if (!result.success)
|
|
@@ -180,7 +192,7 @@ exports.TOOLS = [
|
|
|
180
192
|
},
|
|
181
193
|
isReadOnly: () => true,
|
|
182
194
|
isConcurrencySafe: () => true,
|
|
183
|
-
userFacingName:
|
|
195
|
+
userFacingName: args => `List ${args.path}`,
|
|
184
196
|
getSummary: (args, result) => {
|
|
185
197
|
const path = args.path;
|
|
186
198
|
if (!result.success)
|
|
@@ -223,7 +235,7 @@ exports.TOOLS = [
|
|
|
223
235
|
// Issue #32 #3.2: 传递 abortSignal
|
|
224
236
|
return execCommand_(command, args.cwd, args.timeout, args.maxOutput, context.abortSignal, context.cwd);
|
|
225
237
|
},
|
|
226
|
-
isDestructive:
|
|
238
|
+
isDestructive: args => {
|
|
227
239
|
const cmd = args.command || '';
|
|
228
240
|
return /(rm\s+-rf|mkfs|dd\s)/.test(cmd);
|
|
229
241
|
},
|
|
@@ -232,7 +244,10 @@ exports.TOOLS = [
|
|
|
232
244
|
// Use the bash_security module for comprehensive checks
|
|
233
245
|
const security = (0, bash_security_1.assessCommandSecurity)(cmd);
|
|
234
246
|
if (security.level === 'blocked') {
|
|
235
|
-
return {
|
|
247
|
+
return {
|
|
248
|
+
behavior: 'deny',
|
|
249
|
+
reason: security.reason || `Command blocked by safety policy: ${cmd.slice(0, 50)}`,
|
|
250
|
+
};
|
|
236
251
|
}
|
|
237
252
|
if (security.level === 'safe' && security.isReadOnly) {
|
|
238
253
|
return { behavior: 'allow' };
|
|
@@ -243,15 +258,15 @@ exports.TOOLS = [
|
|
|
243
258
|
// Default: ask for confirmation
|
|
244
259
|
return { behavior: 'ask', reason: 'Command requires confirmation' };
|
|
245
260
|
},
|
|
246
|
-
isReadOnly:
|
|
261
|
+
isReadOnly: args => {
|
|
247
262
|
const cmd = args.command || '';
|
|
248
263
|
return (0, bash_security_1.isReadOnlyCommand)(cmd);
|
|
249
264
|
},
|
|
250
|
-
isConcurrencySafe:
|
|
265
|
+
isConcurrencySafe: args => {
|
|
251
266
|
const cmd = args.command || '';
|
|
252
267
|
return (0, bash_security_1.isReadOnlyCommand)(cmd);
|
|
253
268
|
},
|
|
254
|
-
userFacingName:
|
|
269
|
+
userFacingName: args => `Exec ${compactOneLine(args.command || '', 80)}`,
|
|
255
270
|
getSummary: (args, result) => {
|
|
256
271
|
const command = args.command || '';
|
|
257
272
|
const commandSummary = command ? `\n $ ${compactOneLine(command, 160)}` : '';
|
|
@@ -327,7 +342,7 @@ exports.TOOLS = [
|
|
|
327
342
|
checkPermissions: (_args, _context) => {
|
|
328
343
|
return { behavior: 'ask', reason: 'Edit operation modifies file contents' };
|
|
329
344
|
},
|
|
330
|
-
userFacingName:
|
|
345
|
+
userFacingName: args => `Edit ${args.path}`,
|
|
331
346
|
getSummary: (args, result) => {
|
|
332
347
|
const path = args.path;
|
|
333
348
|
if (!result.success)
|
|
@@ -363,7 +378,7 @@ exports.TOOLS = [
|
|
|
363
378
|
},
|
|
364
379
|
isReadOnly: () => true,
|
|
365
380
|
isConcurrencySafe: () => true,
|
|
366
|
-
userFacingName:
|
|
381
|
+
userFacingName: args => `Glob ${args.pattern}`,
|
|
367
382
|
getSummary: (args, result) => {
|
|
368
383
|
const pattern = args.pattern;
|
|
369
384
|
if (!result.success)
|
|
@@ -407,7 +422,7 @@ exports.TOOLS = [
|
|
|
407
422
|
},
|
|
408
423
|
isReadOnly: () => true,
|
|
409
424
|
isConcurrencySafe: () => true,
|
|
410
|
-
userFacingName:
|
|
425
|
+
userFacingName: args => `Grep ${args.pattern}`,
|
|
411
426
|
getSummary: (args, result) => {
|
|
412
427
|
const pattern = args.pattern;
|
|
413
428
|
if (!result.success)
|
|
@@ -445,7 +460,7 @@ exports.TOOLS = [
|
|
|
445
460
|
execute: async (args, context) => executeBatchRead(args, context),
|
|
446
461
|
isReadOnly: () => true,
|
|
447
462
|
isConcurrencySafe: () => true,
|
|
448
|
-
userFacingName:
|
|
463
|
+
userFacingName: args => {
|
|
449
464
|
const count = Array.isArray(args.steps) ? args.steps.length : 0;
|
|
450
465
|
return `Batch read ${count} steps`;
|
|
451
466
|
},
|
|
@@ -492,7 +507,11 @@ exports.TOOLS = [
|
|
|
492
507
|
return { success: false, output: '', error: 'memory_save requires a name parameter' };
|
|
493
508
|
}
|
|
494
509
|
if (!type || !['user', 'feedback', 'project', 'reference'].includes(type)) {
|
|
495
|
-
return {
|
|
510
|
+
return {
|
|
511
|
+
success: false,
|
|
512
|
+
output: '',
|
|
513
|
+
error: 'memory_save requires a valid type: user, feedback, project, or reference',
|
|
514
|
+
};
|
|
496
515
|
}
|
|
497
516
|
if (!content || typeof content !== 'string') {
|
|
498
517
|
return { success: false, output: '', error: 'memory_save requires a content parameter' };
|
|
@@ -521,7 +540,7 @@ exports.TOOLS = [
|
|
|
521
540
|
}
|
|
522
541
|
},
|
|
523
542
|
isReadOnly: () => false,
|
|
524
|
-
userFacingName:
|
|
543
|
+
userFacingName: args => `Memory save ${args.name}`,
|
|
525
544
|
getSummary: (args, result) => {
|
|
526
545
|
const name = args.name;
|
|
527
546
|
const type = args.type;
|
|
@@ -609,7 +628,7 @@ exports.TOOLS = [
|
|
|
609
628
|
}
|
|
610
629
|
},
|
|
611
630
|
isReadOnly: () => true,
|
|
612
|
-
userFacingName:
|
|
631
|
+
userFacingName: args => `Memory recall ${args.query || 'all'}`,
|
|
613
632
|
getSummary: (args, result) => {
|
|
614
633
|
const query = args.query || 'all';
|
|
615
634
|
if (!result.success)
|
|
@@ -661,7 +680,7 @@ exports.TOOLS = [
|
|
|
661
680
|
}
|
|
662
681
|
},
|
|
663
682
|
isReadOnly: () => false,
|
|
664
|
-
userFacingName:
|
|
683
|
+
userFacingName: args => `Memory forget ${args.name}`,
|
|
665
684
|
getSummary: (args, result) => {
|
|
666
685
|
const name = args.name;
|
|
667
686
|
if (!result.success)
|
|
@@ -756,7 +775,7 @@ exports.TOOLS = [
|
|
|
756
775
|
}
|
|
757
776
|
},
|
|
758
777
|
isReadOnly: () => true,
|
|
759
|
-
userFacingName:
|
|
778
|
+
userFacingName: args => `History search ${args.query}`,
|
|
760
779
|
getSummary: (args, result) => {
|
|
761
780
|
const query = args.query;
|
|
762
781
|
if (!result.success)
|
|
@@ -836,7 +855,15 @@ function truncateToBytes(text, maxBytes) {
|
|
|
836
855
|
cut--;
|
|
837
856
|
return { text: buf.subarray(0, cut).toString('utf-8'), bytes: cut };
|
|
838
857
|
}
|
|
839
|
-
|
|
858
|
+
function readPositiveInteger(value, name, defaultValue) {
|
|
859
|
+
if (value === undefined)
|
|
860
|
+
return defaultValue;
|
|
861
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 1) {
|
|
862
|
+
return `read_file ${name} must be a positive integer`;
|
|
863
|
+
}
|
|
864
|
+
return value;
|
|
865
|
+
}
|
|
866
|
+
async function readFileSync_(path, maxLines, offset, cwd) {
|
|
840
867
|
try {
|
|
841
868
|
const normalizedPath = normalizeToolPath(path);
|
|
842
869
|
const resolved = safePath(path, cwd);
|
|
@@ -844,35 +871,48 @@ async function readFileSync_(path, maxLines, cwd) {
|
|
|
844
871
|
return { success: false, output: '', error: `File not found: ${normalizedPath}` };
|
|
845
872
|
}
|
|
846
873
|
if ((0, fs_1.statSync)(resolved).isDirectory()) {
|
|
847
|
-
return {
|
|
874
|
+
return {
|
|
875
|
+
success: false,
|
|
876
|
+
output: '',
|
|
877
|
+
error: `Path is a directory, not a file: ${normalizedPath}`,
|
|
878
|
+
};
|
|
848
879
|
}
|
|
849
880
|
const content = (0, fs_1.readFileSync)(resolved, 'utf-8');
|
|
850
881
|
const lines = content.split('\n');
|
|
851
|
-
const limit = maxLines ?? 500;
|
|
852
882
|
const maxBytes = 51200; // 50KB byte limit
|
|
853
|
-
if (lines.length
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
883
|
+
if (offset > lines.length) {
|
|
884
|
+
return {
|
|
885
|
+
success: false,
|
|
886
|
+
output: '',
|
|
887
|
+
error: `read_file offset ${offset} is beyond the file (${lines.length} lines)`,
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
const startIndex = offset - 1;
|
|
891
|
+
const selectedLines = lines.slice(startIndex, startIndex + maxLines);
|
|
892
|
+
const selected = selectedLines.join('\n');
|
|
893
|
+
const remainingLines = Math.max(0, lines.length - startIndex - selectedLines.length);
|
|
894
|
+
if (remainingLines > 0) {
|
|
895
|
+
const byteLen = Buffer.byteLength(selected, 'utf8');
|
|
896
|
+
const notice = `\n\n[... truncated, ${remainingLines} more lines; showing lines ${offset}-${offset + selectedLines.length - 1} of ${lines.length}]`;
|
|
857
897
|
if (byteLen > maxBytes) {
|
|
858
|
-
const cut = truncateToBytes(
|
|
898
|
+
const cut = truncateToBytes(selected, maxBytes);
|
|
859
899
|
return {
|
|
860
900
|
success: true,
|
|
861
901
|
output: cut.text + `\n\n[... truncated at ${cut.bytes}B]`,
|
|
862
902
|
};
|
|
863
903
|
}
|
|
864
|
-
return { success: true, output:
|
|
904
|
+
return { success: true, output: selected + notice };
|
|
865
905
|
}
|
|
866
|
-
// Also apply byte limit to
|
|
867
|
-
const byteLen = Buffer.byteLength(
|
|
906
|
+
// Also apply byte limit to the selected page.
|
|
907
|
+
const byteLen = Buffer.byteLength(selected, 'utf8');
|
|
868
908
|
if (byteLen > maxBytes) {
|
|
869
|
-
const cut = truncateToBytes(
|
|
909
|
+
const cut = truncateToBytes(selected, maxBytes);
|
|
870
910
|
return {
|
|
871
911
|
success: true,
|
|
872
912
|
output: cut.text + `\n\n[... truncated at ${cut.bytes}B of ${byteLen}B]`,
|
|
873
913
|
};
|
|
874
914
|
}
|
|
875
|
-
return { success: true, output:
|
|
915
|
+
return { success: true, output: selected };
|
|
876
916
|
}
|
|
877
917
|
catch (err) {
|
|
878
918
|
return { success: false, output: '', error: String(err.message) };
|
|
@@ -883,7 +923,10 @@ async function writeFileSync_(path, content, cwd) {
|
|
|
883
923
|
const normalizedPath = normalizeToolPath(path);
|
|
884
924
|
const resolved = safePath(path, cwd);
|
|
885
925
|
(0, fs_1.writeFileSync)(resolved, content, 'utf-8');
|
|
886
|
-
return {
|
|
926
|
+
return {
|
|
927
|
+
success: true,
|
|
928
|
+
output: `Wrote ${content.split('\n').length} lines to ${normalizedPath}`,
|
|
929
|
+
};
|
|
887
930
|
}
|
|
888
931
|
catch (err) {
|
|
889
932
|
return { success: false, output: '', error: String(err.message) };
|
|
@@ -927,14 +970,15 @@ async function listFiles_(path, maxDepth, cwd) {
|
|
|
927
970
|
// Limit output to 500 entries
|
|
928
971
|
const maxEntries = 500;
|
|
929
972
|
const output = results.length > maxEntries
|
|
930
|
-
? results.slice(0, maxEntries).join('\n') +
|
|
973
|
+
? results.slice(0, maxEntries).join('\n') +
|
|
974
|
+
`\n\n[... truncated, ${results.length - maxEntries} more entries]`
|
|
931
975
|
: results.join('\n');
|
|
932
976
|
return { success: true, output };
|
|
933
977
|
}
|
|
934
978
|
// Issue #32 #3.2: execCommand_ 支持 abortSignal
|
|
935
979
|
async function execCommand_(command, cwd, timeout, maxOutput, abortSignal, baseCwd) {
|
|
936
|
-
return new Promise(
|
|
937
|
-
const workdir = cwd ? safePath(cwd, baseCwd) : baseCwd ?? process.cwd();
|
|
980
|
+
return new Promise(resolve => {
|
|
981
|
+
const workdir = cwd ? safePath(cwd, baseCwd) : (baseCwd ?? process.cwd());
|
|
938
982
|
const timeoutMs = timeout ?? 30000;
|
|
939
983
|
const maxBytes = maxOutput ?? 51200; // Default 50KB, Issue #28 fix
|
|
940
984
|
// Use spawn for streaming output with truncation support
|
|
@@ -1044,7 +1088,7 @@ async function execCommand_(command, cwd, timeout, maxOutput, abortSignal, baseC
|
|
|
1044
1088
|
}
|
|
1045
1089
|
}
|
|
1046
1090
|
});
|
|
1047
|
-
child.on('close',
|
|
1091
|
+
child.on('close', code => {
|
|
1048
1092
|
if (interrupted === 'aborted') {
|
|
1049
1093
|
finish({
|
|
1050
1094
|
success: false,
|
|
@@ -1079,11 +1123,13 @@ async function execCommand_(command, cwd, timeout, maxOutput, abortSignal, baseC
|
|
|
1079
1123
|
finish({
|
|
1080
1124
|
success: true,
|
|
1081
1125
|
output: finalOutput || '(no output)',
|
|
1082
|
-
error: stderrTruncated
|
|
1126
|
+
error: stderrTruncated
|
|
1127
|
+
? errOutput + '\n\n[... stderr truncated]'
|
|
1128
|
+
: errOutput || undefined,
|
|
1083
1129
|
});
|
|
1084
1130
|
}
|
|
1085
1131
|
});
|
|
1086
|
-
child.on('error',
|
|
1132
|
+
child.on('error', err => {
|
|
1087
1133
|
finish({
|
|
1088
1134
|
success: false,
|
|
1089
1135
|
output: '',
|
|
@@ -1155,7 +1201,10 @@ function formatEditPreview(params) {
|
|
|
1155
1201
|
*/
|
|
1156
1202
|
function fuzzyMatch(content, oldString) {
|
|
1157
1203
|
// Strategy 1: Line-by-line matching (allow different indentation)
|
|
1158
|
-
const oldLines = oldString
|
|
1204
|
+
const oldLines = oldString
|
|
1205
|
+
.split('\n')
|
|
1206
|
+
.map(l => l.trim())
|
|
1207
|
+
.filter(Boolean);
|
|
1159
1208
|
const contentLines = content.split('\n');
|
|
1160
1209
|
const lineMatches = [];
|
|
1161
1210
|
for (let i = 0; i <= contentLines.length - oldLines.length; i++) {
|
|
@@ -1205,7 +1254,11 @@ async function editFile_(path, old_string, new_string, replace_all, fuzzy_match,
|
|
|
1205
1254
|
return { success: false, output: '', error: `File not found: ${normalizedPath}` };
|
|
1206
1255
|
}
|
|
1207
1256
|
if ((0, fs_1.statSync)(resolved).isDirectory()) {
|
|
1208
|
-
return {
|
|
1257
|
+
return {
|
|
1258
|
+
success: false,
|
|
1259
|
+
output: '',
|
|
1260
|
+
error: `Path is a directory, not a file: ${normalizedPath}`,
|
|
1261
|
+
};
|
|
1209
1262
|
}
|
|
1210
1263
|
const content = (0, fs_1.readFileSync)(resolved, 'utf-8');
|
|
1211
1264
|
// Check if old_string exists exactly
|
|
@@ -1243,7 +1296,11 @@ async function editFile_(path, old_string, new_string, replace_all, fuzzy_match,
|
|
|
1243
1296
|
// Try fuzzy match strategies
|
|
1244
1297
|
const fuzzyResult = fuzzyMatch(content, old_string);
|
|
1245
1298
|
if (fuzzyResult === null) {
|
|
1246
|
-
return {
|
|
1299
|
+
return {
|
|
1300
|
+
success: false,
|
|
1301
|
+
output: '',
|
|
1302
|
+
error: `old_string not found in file: ${old_string.slice(0, 100)}...`,
|
|
1303
|
+
};
|
|
1247
1304
|
}
|
|
1248
1305
|
if (preview) {
|
|
1249
1306
|
return {
|
|
@@ -1270,7 +1327,10 @@ async function editFile_(path, old_string, new_string, replace_all, fuzzy_match,
|
|
|
1270
1327
|
return {
|
|
1271
1328
|
success: false,
|
|
1272
1329
|
output: '',
|
|
1273
|
-
error: `Fuzzy match found ${fuzzyResult.matches.length} candidates. Provide a more specific string. First 3 candidates:\n${fuzzyResult.matches
|
|
1330
|
+
error: `Fuzzy match found ${fuzzyResult.matches.length} candidates. Provide a more specific string. First 3 candidates:\n${fuzzyResult.matches
|
|
1331
|
+
.slice(0, 3)
|
|
1332
|
+
.map((m, i) => ` ${i + 1}: "${m.slice(0, 80)}..."`)
|
|
1333
|
+
.join('\n')}`,
|
|
1274
1334
|
};
|
|
1275
1335
|
}
|
|
1276
1336
|
// Use the single fuzzy match only.
|
|
@@ -1324,13 +1384,17 @@ function escapeRegExp(str) {
|
|
|
1324
1384
|
*/
|
|
1325
1385
|
async function glob_(pattern, basePath, cwd) {
|
|
1326
1386
|
try {
|
|
1327
|
-
const normalizedBasePath = basePath ? normalizeToolPath(basePath) : cwd ?? process.cwd();
|
|
1328
|
-
const base = basePath ? safePath(basePath, cwd) : cwd ?? process.cwd();
|
|
1387
|
+
const normalizedBasePath = basePath ? normalizeToolPath(basePath) : (cwd ?? process.cwd());
|
|
1388
|
+
const base = basePath ? safePath(basePath, cwd) : (cwd ?? process.cwd());
|
|
1329
1389
|
if (!(0, fs_1.existsSync)(base)) {
|
|
1330
1390
|
return { success: false, output: '', error: `Path not found: ${normalizedBasePath}` };
|
|
1331
1391
|
}
|
|
1332
1392
|
if (!(0, fs_1.statSync)(base).isDirectory()) {
|
|
1333
|
-
return {
|
|
1393
|
+
return {
|
|
1394
|
+
success: false,
|
|
1395
|
+
output: '',
|
|
1396
|
+
error: `Path is not a directory: ${normalizedBasePath}`,
|
|
1397
|
+
};
|
|
1334
1398
|
}
|
|
1335
1399
|
const results = [];
|
|
1336
1400
|
// Convert glob pattern to regex.
|
|
@@ -1395,7 +1459,8 @@ async function glob_(pattern, basePath, cwd) {
|
|
|
1395
1459
|
const maxMatches = 200;
|
|
1396
1460
|
const sorted = results.sort();
|
|
1397
1461
|
const output = sorted.length > maxMatches
|
|
1398
|
-
? sorted.slice(0, maxMatches).join('\n') +
|
|
1462
|
+
? sorted.slice(0, maxMatches).join('\n') +
|
|
1463
|
+
`\n\n[... truncated, ${sorted.length - maxMatches} more matches]`
|
|
1399
1464
|
: sorted.join('\n');
|
|
1400
1465
|
return { success: true, output };
|
|
1401
1466
|
}
|
|
@@ -1408,8 +1473,8 @@ async function glob_(pattern, basePath, cwd) {
|
|
|
1408
1473
|
*/
|
|
1409
1474
|
async function grep_(pattern, basePath, globPattern, contextLines, cwd) {
|
|
1410
1475
|
try {
|
|
1411
|
-
const normalizedBasePath = basePath ? normalizeToolPath(basePath) : cwd ?? process.cwd();
|
|
1412
|
-
const base = basePath ? safePath(basePath, cwd) : cwd ?? process.cwd();
|
|
1476
|
+
const normalizedBasePath = basePath ? normalizeToolPath(basePath) : (cwd ?? process.cwd());
|
|
1477
|
+
const base = basePath ? safePath(basePath, cwd) : (cwd ?? process.cwd());
|
|
1413
1478
|
if (!(0, fs_1.existsSync)(base)) {
|
|
1414
1479
|
return { success: false, output: '', error: `Path not found: ${normalizedBasePath}` };
|
|
1415
1480
|
}
|
|
@@ -1465,10 +1530,10 @@ async function grep_(pattern, basePath, globPattern, contextLines, cwd) {
|
|
|
1465
1530
|
});
|
|
1466
1531
|
const lines = [];
|
|
1467
1532
|
const relPath = (0, path_1.relative)(base, file);
|
|
1468
|
-
rl.on('line',
|
|
1533
|
+
rl.on('line', line => {
|
|
1469
1534
|
lines.push(line);
|
|
1470
1535
|
});
|
|
1471
|
-
await new Promise(
|
|
1536
|
+
await new Promise(resolve => {
|
|
1472
1537
|
rl.on('close', resolve);
|
|
1473
1538
|
});
|
|
1474
1539
|
// Search for matches
|
|
@@ -1541,7 +1606,9 @@ function parseBatchReadSteps(rawSteps) {
|
|
|
1541
1606
|
stepArgs = JSON.parse(stepArgs);
|
|
1542
1607
|
}
|
|
1543
1608
|
catch {
|
|
1544
|
-
return {
|
|
1609
|
+
return {
|
|
1610
|
+
error: `batch_read step ${i + 1} args must be an object or valid JSON object string`,
|
|
1611
|
+
};
|
|
1545
1612
|
}
|
|
1546
1613
|
}
|
|
1547
1614
|
if (!isRecord(stepArgs)) {
|
|
@@ -1554,7 +1621,9 @@ function parseBatchReadSteps(rawSteps) {
|
|
|
1554
1621
|
function buildBatchReadPayload(success, summary, steps, error) {
|
|
1555
1622
|
const payload = {
|
|
1556
1623
|
success,
|
|
1557
|
-
output: steps
|
|
1624
|
+
output: steps
|
|
1625
|
+
.map(step => `${step.index}. ${step.tool}: ${step.summary || (step.success ? 'ok' : step.error || 'error')}`)
|
|
1626
|
+
.join('\n'),
|
|
1558
1627
|
summary,
|
|
1559
1628
|
steps,
|
|
1560
1629
|
};
|
|
@@ -1579,25 +1648,29 @@ async function executeBatchRead(args, context) {
|
|
|
1579
1648
|
const tool = runtimeTools.find(t => t.name === step.tool);
|
|
1580
1649
|
if (!BATCH_READ_ALLOWED_TOOLS.has(step.tool)) {
|
|
1581
1650
|
const error = `Tool ${step.tool} is not allowed in batch_read`;
|
|
1582
|
-
return buildBatchReadPayload(false, error, [
|
|
1651
|
+
return buildBatchReadPayload(false, error, [
|
|
1652
|
+
{
|
|
1583
1653
|
index: i + 1,
|
|
1584
1654
|
tool: step.tool,
|
|
1585
1655
|
args: step.args,
|
|
1586
1656
|
success: false,
|
|
1587
1657
|
error,
|
|
1588
1658
|
output: '',
|
|
1589
|
-
}
|
|
1659
|
+
},
|
|
1660
|
+
], error);
|
|
1590
1661
|
}
|
|
1591
1662
|
if (!tool || tool.isReadOnly?.(step.args) !== true) {
|
|
1592
1663
|
const error = `Tool ${step.tool} is unavailable or not read-only`;
|
|
1593
|
-
return buildBatchReadPayload(false, error, [
|
|
1664
|
+
return buildBatchReadPayload(false, error, [
|
|
1665
|
+
{
|
|
1594
1666
|
index: i + 1,
|
|
1595
1667
|
tool: step.tool,
|
|
1596
1668
|
args: step.args,
|
|
1597
1669
|
success: false,
|
|
1598
1670
|
error,
|
|
1599
1671
|
output: '',
|
|
1600
|
-
}
|
|
1672
|
+
},
|
|
1673
|
+
], error);
|
|
1601
1674
|
}
|
|
1602
1675
|
}
|
|
1603
1676
|
const stepResults = [];
|
|
@@ -1699,7 +1772,9 @@ function summarizeToolResult(tool, args, result) {
|
|
|
1699
1772
|
* 获取可用工具名称列表
|
|
1700
1773
|
*/
|
|
1701
1774
|
function getToolNames() {
|
|
1702
|
-
return getRuntimeTools()
|
|
1775
|
+
return getRuntimeTools()
|
|
1776
|
+
.map(t => t.name)
|
|
1777
|
+
.join(', ');
|
|
1703
1778
|
}
|
|
1704
1779
|
// Re-export bash_security module
|
|
1705
1780
|
var bash_security_2 = require("./bash_security");
|