openhorse 0.2.22 → 0.2.23

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.
Files changed (111) hide show
  1. package/dist/harness/capsule.d.ts +1 -0
  2. package/dist/harness/capsule.d.ts.map +1 -1
  3. package/dist/harness/capsule.js +38 -9
  4. package/dist/harness/capsule.js.map +1 -1
  5. package/dist/harness/contract.d.ts +2 -0
  6. package/dist/harness/contract.d.ts.map +1 -1
  7. package/dist/harness/contract.js +78 -14
  8. package/dist/harness/contract.js.map +1 -1
  9. package/dist/harness/index.d.ts +2 -2
  10. package/dist/harness/index.d.ts.map +1 -1
  11. package/dist/harness/index.js +4 -1
  12. package/dist/harness/index.js.map +1 -1
  13. package/dist/harness/state.d.ts.map +1 -1
  14. package/dist/harness/state.js +37 -7
  15. package/dist/harness/state.js.map +1 -1
  16. package/dist/runtime/aggregate-tool-presenter.d.ts +13 -0
  17. package/dist/runtime/aggregate-tool-presenter.d.ts.map +1 -0
  18. package/dist/runtime/aggregate-tool-presenter.js +44 -0
  19. package/dist/runtime/aggregate-tool-presenter.js.map +1 -0
  20. package/dist/runtime/chat-controller.d.ts +9 -2
  21. package/dist/runtime/chat-controller.d.ts.map +1 -1
  22. package/dist/runtime/chat-controller.js +129 -17
  23. package/dist/runtime/chat-controller.js.map +1 -1
  24. package/dist/runtime/tool-detail-repository.d.ts +45 -0
  25. package/dist/runtime/tool-detail-repository.d.ts.map +1 -0
  26. package/dist/runtime/tool-detail-repository.js +131 -0
  27. package/dist/runtime/tool-detail-repository.js.map +1 -0
  28. package/dist/runtime/tool-output-presentation.d.ts +89 -0
  29. package/dist/runtime/tool-output-presentation.d.ts.map +1 -0
  30. package/dist/runtime/tool-output-presentation.js +329 -0
  31. package/dist/runtime/tool-output-presentation.js.map +1 -0
  32. package/dist/runtime/ui-events.d.ts +4 -0
  33. package/dist/runtime/ui-events.d.ts.map +1 -1
  34. package/dist/runtime/ui-events.js.map +1 -1
  35. package/dist/services/model-context.d.ts.map +1 -1
  36. package/dist/services/model-context.js +7 -0
  37. package/dist/services/model-context.js.map +1 -1
  38. package/dist/services/session-storage.d.ts +3 -0
  39. package/dist/services/session-storage.d.ts.map +1 -1
  40. package/dist/services/session-storage.js +55 -28
  41. package/dist/services/session-storage.js.map +1 -1
  42. package/dist/services/storage-maintenance.d.ts.map +1 -1
  43. package/dist/services/storage-maintenance.js +3 -2
  44. package/dist/services/storage-maintenance.js.map +1 -1
  45. package/dist/terminal-ui/editor.d.ts.map +1 -1
  46. package/dist/terminal-ui/editor.js +14 -3
  47. package/dist/terminal-ui/editor.js.map +1 -1
  48. package/dist/terminal-ui/launch.d.ts +19 -0
  49. package/dist/terminal-ui/launch.d.ts.map +1 -1
  50. package/dist/terminal-ui/launch.js +138 -24
  51. package/dist/terminal-ui/launch.js.map +1 -1
  52. package/dist/terminal-ui/output-queue.d.ts +43 -0
  53. package/dist/terminal-ui/output-queue.d.ts.map +1 -0
  54. package/dist/terminal-ui/output-queue.js +139 -0
  55. package/dist/terminal-ui/output-queue.js.map +1 -0
  56. package/dist/terminal-ui/raw-editor.d.ts +33 -0
  57. package/dist/terminal-ui/raw-editor.d.ts.map +1 -1
  58. package/dist/terminal-ui/raw-editor.js +142 -23
  59. package/dist/terminal-ui/raw-editor.js.map +1 -1
  60. package/dist/tools/index.d.ts.map +1 -1
  61. package/dist/tools/index.js +133 -58
  62. package/dist/tools/index.js.map +1 -1
  63. package/dist/tui-core/input-parser.d.ts +9 -2
  64. package/dist/tui-core/input-parser.d.ts.map +1 -1
  65. package/dist/tui-core/input-parser.js +37 -2
  66. package/dist/tui-core/input-parser.js.map +1 -1
  67. package/dist/tui-ui/inline-surface.d.ts +7 -1
  68. package/dist/tui-ui/inline-surface.d.ts.map +1 -1
  69. package/dist/tui-ui/inline-surface.js +23 -2
  70. package/dist/tui-ui/inline-surface.js.map +1 -1
  71. package/dist/tui-ui/input-ownership.d.ts +46 -0
  72. package/dist/tui-ui/input-ownership.d.ts.map +1 -0
  73. package/dist/tui-ui/input-ownership.js +74 -0
  74. package/dist/tui-ui/input-ownership.js.map +1 -0
  75. package/dist/tui-ui/launch.d.ts +1 -0
  76. package/dist/tui-ui/launch.d.ts.map +1 -1
  77. package/dist/tui-ui/launch.js +104 -2
  78. package/dist/tui-ui/launch.js.map +1 -1
  79. package/dist/tui-ui/layout.d.ts +1 -0
  80. package/dist/tui-ui/layout.d.ts.map +1 -1
  81. package/dist/tui-ui/layout.js +5 -1
  82. package/dist/tui-ui/layout.js.map +1 -1
  83. package/dist/tui-ui/runner.d.ts +32 -0
  84. package/dist/tui-ui/runner.d.ts.map +1 -1
  85. package/dist/tui-ui/runner.js +553 -24
  86. package/dist/tui-ui/runner.js.map +1 -1
  87. package/dist/tui-ui/state.d.ts +65 -0
  88. package/dist/tui-ui/state.d.ts.map +1 -1
  89. package/dist/tui-ui/state.js +187 -2
  90. package/dist/tui-ui/state.js.map +1 -1
  91. package/dist/tui-ui/theme-profile.d.ts +25 -0
  92. package/dist/tui-ui/theme-profile.d.ts.map +1 -0
  93. package/dist/tui-ui/theme-profile.js +130 -0
  94. package/dist/tui-ui/theme-profile.js.map +1 -0
  95. package/dist/tui-ui/transcript-inspector-layout.d.ts +9 -0
  96. package/dist/tui-ui/transcript-inspector-layout.d.ts.map +1 -0
  97. package/dist/tui-ui/transcript-inspector-layout.js +97 -0
  98. package/dist/tui-ui/transcript-inspector-layout.js.map +1 -0
  99. package/dist/tui-ui/transcript-inspector-surface.d.ts +18 -0
  100. package/dist/tui-ui/transcript-inspector-surface.d.ts.map +1 -0
  101. package/dist/tui-ui/transcript-inspector-surface.js +88 -0
  102. package/dist/tui-ui/transcript-inspector-surface.js.map +1 -0
  103. package/dist/tui-ui/transcript-inspector.d.ts +35 -0
  104. package/dist/tui-ui/transcript-inspector.d.ts.map +1 -0
  105. package/dist/tui-ui/transcript-inspector.js +128 -0
  106. package/dist/tui-ui/transcript-inspector.js.map +1 -0
  107. package/dist/tui-ui/transcript-layout.d.ts +1 -0
  108. package/dist/tui-ui/transcript-layout.d.ts.map +1 -1
  109. package/dist/tui-ui/transcript-layout.js +44 -3
  110. package/dist/tui-ui/transcript-layout.js.map +1 -1
  111. package/package.json +1 -1
@@ -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
- return readFileSync_(path, args.maxLines, context.cwd);
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: (args) => `Read ${args.path}`,
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: (args) => `Write ${args.path}`,
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: (args) => `List ${args.path}`,
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: (args) => {
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 { behavior: 'deny', reason: security.reason || `Command blocked by safety policy: ${cmd.slice(0, 50)}` };
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: (args) => {
261
+ isReadOnly: args => {
247
262
  const cmd = args.command || '';
248
263
  return (0, bash_security_1.isReadOnlyCommand)(cmd);
249
264
  },
250
- isConcurrencySafe: (args) => {
265
+ isConcurrencySafe: args => {
251
266
  const cmd = args.command || '';
252
267
  return (0, bash_security_1.isReadOnlyCommand)(cmd);
253
268
  },
254
- userFacingName: (args) => `Exec ${compactOneLine(args.command || '', 80)}`,
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: (args) => `Edit ${args.path}`,
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: (args) => `Glob ${args.pattern}`,
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: (args) => `Grep ${args.pattern}`,
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: (args) => {
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 { success: false, output: '', error: 'memory_save requires a valid type: user, feedback, project, or reference' };
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: (args) => `Memory save ${args.name}`,
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: (args) => `Memory recall ${args.query || 'all'}`,
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: (args) => `Memory forget ${args.name}`,
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: (args) => `History search ${args.query}`,
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
- async function readFileSync_(path, maxLines, cwd) {
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 { success: false, output: '', error: `Path is a directory, not a file: ${normalizedPath}` };
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 > limit) {
854
- const truncated = lines.slice(0, limit).join('\n');
855
- const byteLen = Buffer.byteLength(truncated, 'utf8');
856
- const notice = `\n\n[... truncated, ${lines.length - limit} more lines]`;
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(truncated, maxBytes);
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: truncated + notice };
904
+ return { success: true, output: selected + notice };
865
905
  }
866
- // Also apply byte limit to full content
867
- const byteLen = Buffer.byteLength(content, 'utf8');
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(content, maxBytes);
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: content };
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 { success: true, output: `Wrote ${content.split('\n').length} lines to ${normalizedPath}` };
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') + `\n\n[... truncated, ${results.length - maxEntries} more entries]`
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((resolve) => {
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', (code) => {
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 ? errOutput + '\n\n[... stderr truncated]' : errOutput || undefined,
1126
+ error: stderrTruncated
1127
+ ? errOutput + '\n\n[... stderr truncated]'
1128
+ : errOutput || undefined,
1083
1129
  });
1084
1130
  }
1085
1131
  });
1086
- child.on('error', (err) => {
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.split('\n').map(l => l.trim()).filter(Boolean);
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 { success: false, output: '', error: `Path is a directory, not a file: ${normalizedPath}` };
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 { success: false, output: '', error: `old_string not found in file: ${old_string.slice(0, 100)}...` };
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.slice(0, 3).map((m, i) => ` ${i + 1}: "${m.slice(0, 80)}..."`).join('\n')}`,
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 { success: false, output: '', error: `Path is not a directory: ${normalizedBasePath}` };
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') + `\n\n[... truncated, ${sorted.length - maxMatches} more matches]`
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', (line) => {
1533
+ rl.on('line', line => {
1469
1534
  lines.push(line);
1470
1535
  });
1471
- await new Promise((resolve) => {
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 { error: `batch_read step ${i + 1} args must be an object or valid JSON object string` };
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.map(step => `${step.index}. ${step.tool}: ${step.summary || (step.success ? 'ok' : step.error || 'error')}`).join('\n'),
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
- }], error);
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
- }], error);
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().map(t => t.name).join(', ');
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");