open-agents-ai 0.11.8 → 0.12.1
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/index.js +1266 -192
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
6
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
7
|
+
}) : x)(function(x) {
|
|
8
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
9
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
|
+
});
|
|
5
11
|
var __esm = (fn, res) => function __init() {
|
|
6
12
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
13
|
};
|
|
@@ -1164,7 +1170,7 @@ var init_shell = __esm({
|
|
|
1164
1170
|
const timeout = args["timeout"] ?? this.defaultTimeout;
|
|
1165
1171
|
const stdinInput = args["stdin"];
|
|
1166
1172
|
const start = performance.now();
|
|
1167
|
-
return new Promise((
|
|
1173
|
+
return new Promise((resolve15) => {
|
|
1168
1174
|
const child = spawn("bash", ["-c", command], {
|
|
1169
1175
|
cwd: this.workingDir,
|
|
1170
1176
|
env: {
|
|
@@ -1217,7 +1223,7 @@ var init_shell = __esm({
|
|
|
1217
1223
|
const combined = stdout + stderr;
|
|
1218
1224
|
const looksInteractive = /\? .+[›>]|y\/n|yes\/no|\(Y\/n\)|\[y\/N\]/i.test(combined);
|
|
1219
1225
|
const hint = looksInteractive ? " The command appears to be waiting for interactive input. Use non-interactive flags (e.g., --yes, --no-input) or provide input via the stdin parameter." : "";
|
|
1220
|
-
|
|
1226
|
+
resolve15({
|
|
1221
1227
|
success: false,
|
|
1222
1228
|
output: stdout,
|
|
1223
1229
|
error: `Command timed out after ${timeout}ms.${hint}`,
|
|
@@ -1226,7 +1232,7 @@ var init_shell = __esm({
|
|
|
1226
1232
|
return;
|
|
1227
1233
|
}
|
|
1228
1234
|
const success = code === 0;
|
|
1229
|
-
|
|
1235
|
+
resolve15({
|
|
1230
1236
|
success,
|
|
1231
1237
|
output: stdout + (stderr && success ? `
|
|
1232
1238
|
STDERR:
|
|
@@ -1237,7 +1243,7 @@ ${stderr}` : ""),
|
|
|
1237
1243
|
});
|
|
1238
1244
|
child.on("error", (err) => {
|
|
1239
1245
|
clearTimeout(timer);
|
|
1240
|
-
|
|
1246
|
+
resolve15({
|
|
1241
1247
|
success: false,
|
|
1242
1248
|
output: stdout,
|
|
1243
1249
|
error: err.message,
|
|
@@ -1741,24 +1747,50 @@ var init_web_search = __esm({
|
|
|
1741
1747
|
// packages/execution/dist/tools/file-edit.js
|
|
1742
1748
|
import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1743
1749
|
import { resolve as resolve5 } from "node:path";
|
|
1750
|
+
function countOccurrences(haystack, needle) {
|
|
1751
|
+
let count = 0;
|
|
1752
|
+
let pos = 0;
|
|
1753
|
+
while ((pos = haystack.indexOf(needle, pos)) !== -1) {
|
|
1754
|
+
count++;
|
|
1755
|
+
pos += needle.length;
|
|
1756
|
+
}
|
|
1757
|
+
return count;
|
|
1758
|
+
}
|
|
1759
|
+
function findMatchLines(haystack, needle) {
|
|
1760
|
+
const lines = [];
|
|
1761
|
+
let pos = 0;
|
|
1762
|
+
while ((pos = haystack.indexOf(needle, pos)) !== -1) {
|
|
1763
|
+
const lineNumber = haystack.slice(0, pos).split("\n").length;
|
|
1764
|
+
lines.push(lineNumber);
|
|
1765
|
+
pos += needle.length;
|
|
1766
|
+
}
|
|
1767
|
+
return lines;
|
|
1768
|
+
}
|
|
1769
|
+
function replaceAllOccurrences(haystack, needle, replacement) {
|
|
1770
|
+
return haystack.split(needle).join(replacement);
|
|
1771
|
+
}
|
|
1744
1772
|
var FileEditTool;
|
|
1745
1773
|
var init_file_edit = __esm({
|
|
1746
1774
|
"packages/execution/dist/tools/file-edit.js"() {
|
|
1747
1775
|
"use strict";
|
|
1748
1776
|
FileEditTool = class {
|
|
1749
1777
|
name = "file_edit";
|
|
1750
|
-
description = "Make a precise edit to a file by replacing an exact string match.
|
|
1778
|
+
description = "Make a precise edit to a file by replacing an exact string match. The old_string must be unique in the file unless replace_all is true. Use replace_all to rename variables or change repeated patterns throughout the file.";
|
|
1751
1779
|
parameters = {
|
|
1752
1780
|
type: "object",
|
|
1753
1781
|
properties: {
|
|
1754
1782
|
path: { type: "string", description: "Absolute or relative file path" },
|
|
1755
1783
|
old_string: {
|
|
1756
1784
|
type: "string",
|
|
1757
|
-
description: "The exact string to search for and replace"
|
|
1785
|
+
description: "The exact string to search for and replace. Must be unique in the file (or use replace_all)."
|
|
1758
1786
|
},
|
|
1759
1787
|
new_string: {
|
|
1760
1788
|
type: "string",
|
|
1761
1789
|
description: "The replacement string"
|
|
1790
|
+
},
|
|
1791
|
+
replace_all: {
|
|
1792
|
+
type: "boolean",
|
|
1793
|
+
description: "Replace ALL occurrences instead of just the first. Use for variable renames, import path changes, etc. Default: false"
|
|
1762
1794
|
}
|
|
1763
1795
|
},
|
|
1764
1796
|
required: ["path", "old_string", "new_string"]
|
|
@@ -1771,25 +1803,45 @@ var init_file_edit = __esm({
|
|
|
1771
1803
|
const filePath = args["path"];
|
|
1772
1804
|
const oldString = args["old_string"];
|
|
1773
1805
|
const newString = args["new_string"];
|
|
1806
|
+
const replaceAll = args["replace_all"] === true;
|
|
1774
1807
|
const start = performance.now();
|
|
1775
1808
|
try {
|
|
1776
1809
|
const fullPath = resolve5(this.workingDir, filePath);
|
|
1777
1810
|
const content = await readFile2(fullPath, "utf-8");
|
|
1778
|
-
const
|
|
1779
|
-
if (
|
|
1811
|
+
const occurrences = countOccurrences(content, oldString);
|
|
1812
|
+
if (occurrences === 0) {
|
|
1813
|
+
return {
|
|
1814
|
+
success: false,
|
|
1815
|
+
output: "",
|
|
1816
|
+
error: `old_string not found in ${filePath}. Read the file first to verify the exact content.`,
|
|
1817
|
+
durationMs: performance.now() - start
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
if (!replaceAll && occurrences > 1) {
|
|
1821
|
+
const matchLines = findMatchLines(content, oldString);
|
|
1780
1822
|
return {
|
|
1781
1823
|
success: false,
|
|
1782
1824
|
output: "",
|
|
1783
|
-
error: `old_string not found
|
|
1825
|
+
error: `old_string is not unique \u2014 found ${occurrences} occurrences at lines ${matchLines.join(", ")}. Either include more surrounding context to make old_string unique, or set replace_all=true to replace all ${occurrences} occurrences.`,
|
|
1784
1826
|
durationMs: performance.now() - start
|
|
1785
1827
|
};
|
|
1786
1828
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1829
|
+
let updated;
|
|
1830
|
+
let editedLines;
|
|
1831
|
+
if (replaceAll) {
|
|
1832
|
+
editedLines = findMatchLines(content, oldString);
|
|
1833
|
+
updated = replaceAllOccurrences(content, oldString, newString);
|
|
1834
|
+
} else {
|
|
1835
|
+
const index = content.indexOf(oldString);
|
|
1836
|
+
const lineNumber = content.slice(0, index).split("\n").length;
|
|
1837
|
+
editedLines = [lineNumber];
|
|
1838
|
+
updated = content.slice(0, index) + newString + content.slice(index + oldString.length);
|
|
1839
|
+
}
|
|
1789
1840
|
await writeFile2(fullPath, updated, "utf-8");
|
|
1841
|
+
const linesInfo = editedLines.length === 1 ? `line ${editedLines[0]}` : `${editedLines.length} locations (lines ${editedLines.join(", ")})`;
|
|
1790
1842
|
return {
|
|
1791
1843
|
success: true,
|
|
1792
|
-
output: `Edited ${
|
|
1844
|
+
output: `Edited ${filePath} at ${linesInfo}`,
|
|
1793
1845
|
durationMs: performance.now() - start
|
|
1794
1846
|
};
|
|
1795
1847
|
} catch (error) {
|
|
@@ -2431,15 +2483,24 @@ var init_aiwg_workflow = __esm({
|
|
|
2431
2483
|
});
|
|
2432
2484
|
|
|
2433
2485
|
// packages/execution/dist/tools/batch-edit.js
|
|
2434
|
-
import { readFile as readFile5, writeFile as writeFile4
|
|
2435
|
-
import { resolve as resolve9
|
|
2486
|
+
import { readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
|
|
2487
|
+
import { resolve as resolve9 } from "node:path";
|
|
2488
|
+
function countOccurrences2(haystack, needle) {
|
|
2489
|
+
let count = 0;
|
|
2490
|
+
let pos = 0;
|
|
2491
|
+
while ((pos = haystack.indexOf(needle, pos)) !== -1) {
|
|
2492
|
+
count++;
|
|
2493
|
+
pos += needle.length;
|
|
2494
|
+
}
|
|
2495
|
+
return count;
|
|
2496
|
+
}
|
|
2436
2497
|
var BatchEditTool;
|
|
2437
2498
|
var init_batch_edit = __esm({
|
|
2438
2499
|
"packages/execution/dist/tools/batch-edit.js"() {
|
|
2439
2500
|
"use strict";
|
|
2440
2501
|
BatchEditTool = class {
|
|
2441
2502
|
name = "batch_edit";
|
|
2442
|
-
description = "Make multiple precise edits across one or more files in a single call. More efficient than calling file_edit repeatedly. Each edit replaces an exact string match. Edits are applied in order within each file.";
|
|
2503
|
+
description = "Make multiple precise edits across one or more files in a single call. More efficient than calling file_edit repeatedly. Each edit replaces an exact string match with uniqueness validation. Edits are applied in order within each file. Set replace_all on individual edits for bulk renames.";
|
|
2443
2504
|
parameters = {
|
|
2444
2505
|
type: "object",
|
|
2445
2506
|
properties: {
|
|
@@ -2449,9 +2510,22 @@ var init_batch_edit = __esm({
|
|
|
2449
2510
|
items: {
|
|
2450
2511
|
type: "object",
|
|
2451
2512
|
properties: {
|
|
2452
|
-
path: {
|
|
2453
|
-
|
|
2454
|
-
|
|
2513
|
+
path: {
|
|
2514
|
+
type: "string",
|
|
2515
|
+
description: "File path relative to project root"
|
|
2516
|
+
},
|
|
2517
|
+
old_string: {
|
|
2518
|
+
type: "string",
|
|
2519
|
+
description: "Exact string to find and replace (must be unique unless replace_all)"
|
|
2520
|
+
},
|
|
2521
|
+
new_string: {
|
|
2522
|
+
type: "string",
|
|
2523
|
+
description: "Replacement string"
|
|
2524
|
+
},
|
|
2525
|
+
replace_all: {
|
|
2526
|
+
type: "boolean",
|
|
2527
|
+
description: "Replace all occurrences (for renames). Default: false"
|
|
2528
|
+
}
|
|
2455
2529
|
},
|
|
2456
2530
|
required: ["path", "old_string", "new_string"]
|
|
2457
2531
|
}
|
|
@@ -2479,7 +2553,12 @@ var init_batch_edit = __esm({
|
|
|
2479
2553
|
const fullPath = resolve9(this.workingDir, edit.path);
|
|
2480
2554
|
if (!byFile.has(fullPath))
|
|
2481
2555
|
byFile.set(fullPath, []);
|
|
2482
|
-
byFile.get(fullPath).push({
|
|
2556
|
+
byFile.get(fullPath).push({
|
|
2557
|
+
old_string: edit.old_string,
|
|
2558
|
+
new_string: edit.new_string,
|
|
2559
|
+
replace_all: edit.replace_all,
|
|
2560
|
+
relPath: edit.path
|
|
2561
|
+
});
|
|
2483
2562
|
}
|
|
2484
2563
|
const results = [];
|
|
2485
2564
|
let successCount = 0;
|
|
@@ -2488,15 +2567,26 @@ var init_batch_edit = __esm({
|
|
|
2488
2567
|
try {
|
|
2489
2568
|
let content = await readFile5(fullPath, "utf-8");
|
|
2490
2569
|
for (const edit of fileEdits) {
|
|
2491
|
-
const
|
|
2492
|
-
if (
|
|
2493
|
-
results.push(`SKIP: old_string not found in ${
|
|
2570
|
+
const occurrences = countOccurrences2(content, edit.old_string);
|
|
2571
|
+
if (occurrences === 0) {
|
|
2572
|
+
results.push(`SKIP: old_string not found in ${edit.relPath}`);
|
|
2573
|
+
failCount++;
|
|
2574
|
+
continue;
|
|
2575
|
+
}
|
|
2576
|
+
if (!edit.replace_all && occurrences > 1) {
|
|
2577
|
+
results.push(`AMBIGUOUS: old_string has ${occurrences} matches in ${edit.relPath} \u2014 add more context or use replace_all`);
|
|
2494
2578
|
failCount++;
|
|
2495
2579
|
continue;
|
|
2496
2580
|
}
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2581
|
+
if (edit.replace_all) {
|
|
2582
|
+
content = content.split(edit.old_string).join(edit.new_string);
|
|
2583
|
+
results.push(`EDIT: ${edit.relPath} (${occurrences} replacements)`);
|
|
2584
|
+
} else {
|
|
2585
|
+
const index = content.indexOf(edit.old_string);
|
|
2586
|
+
const lineNumber = content.slice(0, index).split("\n").length;
|
|
2587
|
+
content = content.slice(0, index) + edit.new_string + content.slice(index + edit.old_string.length);
|
|
2588
|
+
results.push(`EDIT: ${edit.relPath} line ${lineNumber}`);
|
|
2589
|
+
}
|
|
2500
2590
|
successCount++;
|
|
2501
2591
|
}
|
|
2502
2592
|
await writeFile4(fullPath, content, "utf-8");
|
|
@@ -2518,6 +2608,178 @@ ${results.join("\n")}`,
|
|
|
2518
2608
|
}
|
|
2519
2609
|
});
|
|
2520
2610
|
|
|
2611
|
+
// packages/execution/dist/tools/file-patch.js
|
|
2612
|
+
import { readFile as readFile6, writeFile as writeFile5, copyFile } from "node:fs/promises";
|
|
2613
|
+
import { resolve as resolve10 } from "node:path";
|
|
2614
|
+
var FilePatchTool;
|
|
2615
|
+
var init_file_patch = __esm({
|
|
2616
|
+
"packages/execution/dist/tools/file-patch.js"() {
|
|
2617
|
+
"use strict";
|
|
2618
|
+
FilePatchTool = class {
|
|
2619
|
+
name = "file_patch";
|
|
2620
|
+
description = "Edit specific line ranges in a file. More precise than string matching for large files. Modes: 'replace' replaces lines start_line..end_line with new_content, 'insert_before' inserts before start_line, 'insert_after' inserts after start_line, 'delete' removes lines start_line..end_line. Use dry_run to preview changes.";
|
|
2621
|
+
parameters = {
|
|
2622
|
+
type: "object",
|
|
2623
|
+
properties: {
|
|
2624
|
+
path: {
|
|
2625
|
+
type: "string",
|
|
2626
|
+
description: "File path relative to project root"
|
|
2627
|
+
},
|
|
2628
|
+
start_line: {
|
|
2629
|
+
type: "number",
|
|
2630
|
+
description: "Starting line number (1-based, inclusive)"
|
|
2631
|
+
},
|
|
2632
|
+
end_line: {
|
|
2633
|
+
type: "number",
|
|
2634
|
+
description: "Ending line number (1-based, inclusive). Required for replace and delete modes. For single-line edits, set equal to start_line."
|
|
2635
|
+
},
|
|
2636
|
+
new_content: {
|
|
2637
|
+
type: "string",
|
|
2638
|
+
description: "Replacement content (for replace mode) or content to insert (for insert modes). Not needed for delete mode."
|
|
2639
|
+
},
|
|
2640
|
+
mode: {
|
|
2641
|
+
type: "string",
|
|
2642
|
+
enum: ["replace", "insert_before", "insert_after", "delete"],
|
|
2643
|
+
description: "Edit mode. Default: replace"
|
|
2644
|
+
},
|
|
2645
|
+
dry_run: {
|
|
2646
|
+
type: "boolean",
|
|
2647
|
+
description: "Preview the diff without writing. Returns what would change. Default: false"
|
|
2648
|
+
}
|
|
2649
|
+
},
|
|
2650
|
+
required: ["path", "start_line"]
|
|
2651
|
+
};
|
|
2652
|
+
workingDir;
|
|
2653
|
+
constructor(workingDir) {
|
|
2654
|
+
this.workingDir = workingDir;
|
|
2655
|
+
}
|
|
2656
|
+
async execute(args) {
|
|
2657
|
+
const filePath = args["path"];
|
|
2658
|
+
const startLine = args["start_line"];
|
|
2659
|
+
const endLine = args["end_line"] ?? startLine;
|
|
2660
|
+
const newContent = args["new_content"] ?? "";
|
|
2661
|
+
const mode = args["mode"] ?? "replace";
|
|
2662
|
+
const dryRun = args["dry_run"] === true;
|
|
2663
|
+
const start = performance.now();
|
|
2664
|
+
try {
|
|
2665
|
+
if (!Number.isInteger(startLine) || startLine < 1) {
|
|
2666
|
+
return {
|
|
2667
|
+
success: false,
|
|
2668
|
+
output: "",
|
|
2669
|
+
error: "start_line must be a positive integer (1-based)",
|
|
2670
|
+
durationMs: performance.now() - start
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2673
|
+
if (!Number.isInteger(endLine) || endLine < startLine) {
|
|
2674
|
+
return {
|
|
2675
|
+
success: false,
|
|
2676
|
+
output: "",
|
|
2677
|
+
error: `end_line (${endLine}) must be >= start_line (${startLine})`,
|
|
2678
|
+
durationMs: performance.now() - start
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
const fullPath = resolve10(this.workingDir, filePath);
|
|
2682
|
+
const content = await readFile6(fullPath, "utf-8");
|
|
2683
|
+
const lines = content.split("\n");
|
|
2684
|
+
const totalLines = lines.length;
|
|
2685
|
+
if (startLine > totalLines) {
|
|
2686
|
+
return {
|
|
2687
|
+
success: false,
|
|
2688
|
+
output: "",
|
|
2689
|
+
error: `start_line ${startLine} exceeds file length (${totalLines} lines)`,
|
|
2690
|
+
durationMs: performance.now() - start
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
const effectiveEnd = Math.min(endLine, totalLines);
|
|
2694
|
+
const startIdx = startLine - 1;
|
|
2695
|
+
const endIdx = effectiveEnd;
|
|
2696
|
+
const newLines = newContent.length > 0 ? newContent.split("\n") : [];
|
|
2697
|
+
let resultLines;
|
|
2698
|
+
let description;
|
|
2699
|
+
switch (mode) {
|
|
2700
|
+
case "replace": {
|
|
2701
|
+
const removedLines = lines.slice(startIdx, endIdx);
|
|
2702
|
+
resultLines = [
|
|
2703
|
+
...lines.slice(0, startIdx),
|
|
2704
|
+
...newLines,
|
|
2705
|
+
...lines.slice(endIdx)
|
|
2706
|
+
];
|
|
2707
|
+
description = `Replaced lines ${startLine}-${effectiveEnd} (${removedLines.length} lines \u2192 ${newLines.length} lines)`;
|
|
2708
|
+
break;
|
|
2709
|
+
}
|
|
2710
|
+
case "insert_before": {
|
|
2711
|
+
resultLines = [
|
|
2712
|
+
...lines.slice(0, startIdx),
|
|
2713
|
+
...newLines,
|
|
2714
|
+
...lines.slice(startIdx)
|
|
2715
|
+
];
|
|
2716
|
+
description = `Inserted ${newLines.length} lines before line ${startLine}`;
|
|
2717
|
+
break;
|
|
2718
|
+
}
|
|
2719
|
+
case "insert_after": {
|
|
2720
|
+
resultLines = [
|
|
2721
|
+
...lines.slice(0, startIdx + 1),
|
|
2722
|
+
...newLines,
|
|
2723
|
+
...lines.slice(startIdx + 1)
|
|
2724
|
+
];
|
|
2725
|
+
description = `Inserted ${newLines.length} lines after line ${startLine}`;
|
|
2726
|
+
break;
|
|
2727
|
+
}
|
|
2728
|
+
case "delete": {
|
|
2729
|
+
const removedLines = lines.slice(startIdx, endIdx);
|
|
2730
|
+
resultLines = [...lines.slice(0, startIdx), ...lines.slice(endIdx)];
|
|
2731
|
+
description = `Deleted lines ${startLine}-${effectiveEnd} (${removedLines.length} lines removed)`;
|
|
2732
|
+
break;
|
|
2733
|
+
}
|
|
2734
|
+
default:
|
|
2735
|
+
return {
|
|
2736
|
+
success: false,
|
|
2737
|
+
output: "",
|
|
2738
|
+
error: `Unknown mode: ${mode}. Use replace, insert_before, insert_after, or delete.`,
|
|
2739
|
+
durationMs: performance.now() - start
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
const oldSection = lines.slice(Math.max(0, startIdx - 2), Math.min(totalLines, endIdx + 2)).map((l, i) => `- ${String(Math.max(1, startLine - 2) + i).padStart(4)} | ${l}`).join("\n");
|
|
2743
|
+
const newSectionStart = Math.max(0, startIdx - 2);
|
|
2744
|
+
const newSectionEnd = Math.min(resultLines.length, startIdx + newLines.length + 2);
|
|
2745
|
+
const newSection = resultLines.slice(newSectionStart, newSectionEnd).map((l, i) => `+ ${String(newSectionStart + 1 + i).padStart(4)} | ${l}`).join("\n");
|
|
2746
|
+
const diff = `${description}
|
|
2747
|
+
|
|
2748
|
+
Before:
|
|
2749
|
+
${oldSection}
|
|
2750
|
+
|
|
2751
|
+
After:
|
|
2752
|
+
${newSection}`;
|
|
2753
|
+
if (dryRun) {
|
|
2754
|
+
return {
|
|
2755
|
+
success: true,
|
|
2756
|
+
output: `[DRY RUN] ${diff}
|
|
2757
|
+
|
|
2758
|
+
File NOT modified. Remove dry_run to apply.`,
|
|
2759
|
+
durationMs: performance.now() - start
|
|
2760
|
+
};
|
|
2761
|
+
}
|
|
2762
|
+
await writeFile5(fullPath, resultLines.join("\n"), "utf-8");
|
|
2763
|
+
return {
|
|
2764
|
+
success: true,
|
|
2765
|
+
output: `${filePath}: ${description} (${totalLines} \u2192 ${resultLines.length} total lines)
|
|
2766
|
+
|
|
2767
|
+
${diff}`,
|
|
2768
|
+
durationMs: performance.now() - start
|
|
2769
|
+
};
|
|
2770
|
+
} catch (error) {
|
|
2771
|
+
return {
|
|
2772
|
+
success: false,
|
|
2773
|
+
output: "",
|
|
2774
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2775
|
+
durationMs: performance.now() - start
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
});
|
|
2782
|
+
|
|
2521
2783
|
// packages/execution/dist/tools/codebase-map.js
|
|
2522
2784
|
import { readdirSync as readdirSync3, statSync as statSync3, readFileSync as readFileSync4, existsSync as existsSync4 } from "node:fs";
|
|
2523
2785
|
import { join as join7, relative, extname } from "node:path";
|
|
@@ -3372,7 +3634,7 @@ Exit code: ${task.exitCode ?? "N/A"}`,
|
|
|
3372
3634
|
|
|
3373
3635
|
// packages/execution/dist/tools/image.js
|
|
3374
3636
|
import { existsSync as existsSync7, readFileSync as readFileSync6, statSync as statSync4 } from "node:fs";
|
|
3375
|
-
import { resolve as
|
|
3637
|
+
import { resolve as resolve11, extname as extname2, basename } from "node:path";
|
|
3376
3638
|
import { execSync as execSync7 } from "node:child_process";
|
|
3377
3639
|
import { tmpdir } from "node:os";
|
|
3378
3640
|
import { join as join10 } from "node:path";
|
|
@@ -3481,7 +3743,7 @@ var init_image = __esm({
|
|
|
3481
3743
|
if (!rawPath) {
|
|
3482
3744
|
return { success: false, output: "", error: "path is required", durationMs: 0 };
|
|
3483
3745
|
}
|
|
3484
|
-
const fullPath =
|
|
3746
|
+
const fullPath = resolve11(this.workingDir, rawPath);
|
|
3485
3747
|
if (!existsSync7(fullPath)) {
|
|
3486
3748
|
return { success: false, output: "", error: `File not found: ${rawPath}`, durationMs: Date.now() - start };
|
|
3487
3749
|
}
|
|
@@ -3550,7 +3812,7 @@ ${ocrText}`);
|
|
|
3550
3812
|
}
|
|
3551
3813
|
async execute(args) {
|
|
3552
3814
|
const start = Date.now();
|
|
3553
|
-
const outputPath = args["output_path"] ?
|
|
3815
|
+
const outputPath = args["output_path"] ? resolve11(this.workingDir, String(args["output_path"])) : join10(tmpdir(), `oa-screenshot-${Date.now()}.png`);
|
|
3554
3816
|
const delayMs = typeof args["delay_ms"] === "number" ? args["delay_ms"] : 0;
|
|
3555
3817
|
const region = String(args["region"] ?? "full");
|
|
3556
3818
|
if (delayMs > 0) {
|
|
@@ -3673,7 +3935,7 @@ ${ocrText}`);
|
|
|
3673
3935
|
if (!rawPath) {
|
|
3674
3936
|
return { success: false, output: "", error: "path is required", durationMs: 0 };
|
|
3675
3937
|
}
|
|
3676
|
-
const fullPath =
|
|
3938
|
+
const fullPath = resolve11(this.workingDir, rawPath);
|
|
3677
3939
|
if (!existsSync7(fullPath)) {
|
|
3678
3940
|
return { success: false, output: "", error: `File not found: ${rawPath}`, durationMs: Date.now() - start };
|
|
3679
3941
|
}
|
|
@@ -3723,6 +3985,510 @@ ${text}`,
|
|
|
3723
3985
|
}
|
|
3724
3986
|
});
|
|
3725
3987
|
|
|
3988
|
+
// packages/execution/dist/tools/custom-tool.js
|
|
3989
|
+
var custom_tool_exports = {};
|
|
3990
|
+
__export(custom_tool_exports, {
|
|
3991
|
+
CustomTool: () => CustomTool,
|
|
3992
|
+
buildCustomTools: () => buildCustomTools,
|
|
3993
|
+
deleteCustomToolDefinition: () => deleteCustomToolDefinition,
|
|
3994
|
+
listCustomToolFiles: () => listCustomToolFiles,
|
|
3995
|
+
loadCustomTools: () => loadCustomTools,
|
|
3996
|
+
saveCustomToolDefinition: () => saveCustomToolDefinition
|
|
3997
|
+
});
|
|
3998
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync7, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3999
|
+
import { join as join11 } from "node:path";
|
|
4000
|
+
import { homedir as homedir3 } from "node:os";
|
|
4001
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
4002
|
+
function projectToolsDir(repoRoot) {
|
|
4003
|
+
return join11(repoRoot, ".oa", "tools");
|
|
4004
|
+
}
|
|
4005
|
+
function loadCustomTools(repoRoot) {
|
|
4006
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
4007
|
+
for (const def of loadFromDirectory(GLOBAL_TOOLS_DIR)) {
|
|
4008
|
+
definitions.set(def.name, def);
|
|
4009
|
+
}
|
|
4010
|
+
const projectDir = projectToolsDir(repoRoot);
|
|
4011
|
+
for (const def of loadFromDirectory(projectDir)) {
|
|
4012
|
+
definitions.set(def.name, def);
|
|
4013
|
+
}
|
|
4014
|
+
return Array.from(definitions.values());
|
|
4015
|
+
}
|
|
4016
|
+
function buildCustomTools(repoRoot) {
|
|
4017
|
+
const definitions = loadCustomTools(repoRoot);
|
|
4018
|
+
return definitions.map((def) => new CustomTool(def, repoRoot));
|
|
4019
|
+
}
|
|
4020
|
+
function saveCustomToolDefinition(definition, scope, repoRoot) {
|
|
4021
|
+
const dir = scope === "project" && repoRoot ? projectToolsDir(repoRoot) : GLOBAL_TOOLS_DIR;
|
|
4022
|
+
mkdirSync3(dir, { recursive: true });
|
|
4023
|
+
const filePath = join11(dir, `${definition.name}.json`);
|
|
4024
|
+
writeFileSync3(filePath, JSON.stringify(definition, null, 2), "utf-8");
|
|
4025
|
+
return filePath;
|
|
4026
|
+
}
|
|
4027
|
+
function deleteCustomToolDefinition(name, scope, repoRoot) {
|
|
4028
|
+
const dir = scope === "project" && repoRoot ? projectToolsDir(repoRoot) : GLOBAL_TOOLS_DIR;
|
|
4029
|
+
const filePath = join11(dir, `${name}.json`);
|
|
4030
|
+
if (existsSync8(filePath)) {
|
|
4031
|
+
const { unlinkSync: unlinkSync2 } = __require("node:fs");
|
|
4032
|
+
unlinkSync2(filePath);
|
|
4033
|
+
return true;
|
|
4034
|
+
}
|
|
4035
|
+
return false;
|
|
4036
|
+
}
|
|
4037
|
+
function listCustomToolFiles(repoRoot) {
|
|
4038
|
+
const result = [];
|
|
4039
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4040
|
+
for (const def of loadFromDirectory(projectToolsDir(repoRoot))) {
|
|
4041
|
+
result.push({
|
|
4042
|
+
name: def.name,
|
|
4043
|
+
scope: "project",
|
|
4044
|
+
description: def.description,
|
|
4045
|
+
stepsCount: def.steps.length,
|
|
4046
|
+
version: def.version
|
|
4047
|
+
});
|
|
4048
|
+
seen.add(def.name);
|
|
4049
|
+
}
|
|
4050
|
+
for (const def of loadFromDirectory(GLOBAL_TOOLS_DIR)) {
|
|
4051
|
+
if (!seen.has(def.name)) {
|
|
4052
|
+
result.push({
|
|
4053
|
+
name: def.name,
|
|
4054
|
+
scope: "global",
|
|
4055
|
+
description: def.description,
|
|
4056
|
+
stepsCount: def.steps.length,
|
|
4057
|
+
version: def.version
|
|
4058
|
+
});
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
return result;
|
|
4062
|
+
}
|
|
4063
|
+
function loadFromDirectory(dir) {
|
|
4064
|
+
if (!existsSync8(dir))
|
|
4065
|
+
return [];
|
|
4066
|
+
const definitions = [];
|
|
4067
|
+
try {
|
|
4068
|
+
const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
|
|
4069
|
+
for (const file of files) {
|
|
4070
|
+
try {
|
|
4071
|
+
const content = readFileSync7(join11(dir, file), "utf-8");
|
|
4072
|
+
const def = JSON.parse(content);
|
|
4073
|
+
if (def.name && def.description && Array.isArray(def.steps) && def.steps.length > 0) {
|
|
4074
|
+
if (!def.parameters || typeof def.parameters !== "object") {
|
|
4075
|
+
def.parameters = { type: "object", properties: {}, required: [] };
|
|
4076
|
+
}
|
|
4077
|
+
definitions.push(def);
|
|
4078
|
+
}
|
|
4079
|
+
} catch {
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
} catch {
|
|
4083
|
+
}
|
|
4084
|
+
return definitions;
|
|
4085
|
+
}
|
|
4086
|
+
var GLOBAL_TOOLS_DIR, CustomTool;
|
|
4087
|
+
var init_custom_tool = __esm({
|
|
4088
|
+
"packages/execution/dist/tools/custom-tool.js"() {
|
|
4089
|
+
"use strict";
|
|
4090
|
+
GLOBAL_TOOLS_DIR = join11(homedir3(), ".open-agents", "tools");
|
|
4091
|
+
CustomTool = class {
|
|
4092
|
+
name;
|
|
4093
|
+
description;
|
|
4094
|
+
parameters;
|
|
4095
|
+
steps;
|
|
4096
|
+
workingDir;
|
|
4097
|
+
constructor(definition, workingDir) {
|
|
4098
|
+
this.name = definition.name;
|
|
4099
|
+
this.description = definition.description;
|
|
4100
|
+
this.parameters = definition.parameters;
|
|
4101
|
+
this.steps = definition.steps;
|
|
4102
|
+
this.workingDir = workingDir;
|
|
4103
|
+
}
|
|
4104
|
+
async execute(args) {
|
|
4105
|
+
const start = performance.now();
|
|
4106
|
+
const outputs = [];
|
|
4107
|
+
let allSuccess = true;
|
|
4108
|
+
for (let i = 0; i < this.steps.length; i++) {
|
|
4109
|
+
const step = this.steps[i];
|
|
4110
|
+
const command = this.interpolate(step.command, args);
|
|
4111
|
+
outputs.push(`--- Step ${i + 1}: ${step.description} ---`);
|
|
4112
|
+
outputs.push(`$ ${command}`);
|
|
4113
|
+
try {
|
|
4114
|
+
const result = await this.runCommand(command);
|
|
4115
|
+
outputs.push(result.output);
|
|
4116
|
+
if (!result.success) {
|
|
4117
|
+
allSuccess = false;
|
|
4118
|
+
if (result.error)
|
|
4119
|
+
outputs.push(`Error: ${result.error}`);
|
|
4120
|
+
if (!step.continueOnError) {
|
|
4121
|
+
outputs.push(`Step ${i + 1} failed. Stopping.`);
|
|
4122
|
+
break;
|
|
4123
|
+
}
|
|
4124
|
+
outputs.push(`Step ${i + 1} failed but continueOnError=true, proceeding.`);
|
|
4125
|
+
}
|
|
4126
|
+
} catch (err) {
|
|
4127
|
+
allSuccess = false;
|
|
4128
|
+
outputs.push(`Step ${i + 1} error: ${err instanceof Error ? err.message : String(err)}`);
|
|
4129
|
+
if (!step.continueOnError)
|
|
4130
|
+
break;
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
return {
|
|
4134
|
+
success: allSuccess,
|
|
4135
|
+
output: outputs.join("\n"),
|
|
4136
|
+
error: allSuccess ? void 0 : "One or more steps failed",
|
|
4137
|
+
durationMs: performance.now() - start
|
|
4138
|
+
};
|
|
4139
|
+
}
|
|
4140
|
+
/** Replace {{param}} placeholders with actual argument values */
|
|
4141
|
+
interpolate(template, args) {
|
|
4142
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
4143
|
+
const val = args[key];
|
|
4144
|
+
if (val === void 0 || val === null)
|
|
4145
|
+
return "";
|
|
4146
|
+
return String(val).replace(/[`$\\!"]/g, "\\$&");
|
|
4147
|
+
});
|
|
4148
|
+
}
|
|
4149
|
+
/** Execute a single shell command and return output */
|
|
4150
|
+
runCommand(command) {
|
|
4151
|
+
return new Promise((resolve15) => {
|
|
4152
|
+
const child = spawn3("bash", ["-c", command], {
|
|
4153
|
+
cwd: this.workingDir,
|
|
4154
|
+
env: { ...process.env, CI: "true", NO_COLOR: "1" },
|
|
4155
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4156
|
+
});
|
|
4157
|
+
let stdout = "";
|
|
4158
|
+
let stderr = "";
|
|
4159
|
+
const maxBuf = 512 * 1024;
|
|
4160
|
+
child.stdout.on("data", (data) => {
|
|
4161
|
+
stdout += data.toString();
|
|
4162
|
+
if (stdout.length > maxBuf)
|
|
4163
|
+
stdout = stdout.slice(0, maxBuf);
|
|
4164
|
+
});
|
|
4165
|
+
child.stderr.on("data", (data) => {
|
|
4166
|
+
stderr += data.toString();
|
|
4167
|
+
if (stderr.length > maxBuf)
|
|
4168
|
+
stderr = stderr.slice(0, maxBuf);
|
|
4169
|
+
});
|
|
4170
|
+
child.stdin.end();
|
|
4171
|
+
const timer = setTimeout(() => {
|
|
4172
|
+
try {
|
|
4173
|
+
child.kill("SIGTERM");
|
|
4174
|
+
} catch {
|
|
4175
|
+
}
|
|
4176
|
+
resolve15({ success: false, output: stdout, error: "Command timed out after 60s" });
|
|
4177
|
+
}, 6e4);
|
|
4178
|
+
child.on("close", (code) => {
|
|
4179
|
+
clearTimeout(timer);
|
|
4180
|
+
resolve15({
|
|
4181
|
+
success: code === 0,
|
|
4182
|
+
output: stdout + (stderr && code === 0 ? `
|
|
4183
|
+
STDERR:
|
|
4184
|
+
${stderr}` : ""),
|
|
4185
|
+
error: code !== 0 ? stderr || `Exit code ${code}` : void 0
|
|
4186
|
+
});
|
|
4187
|
+
});
|
|
4188
|
+
child.on("error", (err) => {
|
|
4189
|
+
clearTimeout(timer);
|
|
4190
|
+
resolve15({ success: false, output: stdout, error: err.message });
|
|
4191
|
+
});
|
|
4192
|
+
});
|
|
4193
|
+
}
|
|
4194
|
+
};
|
|
4195
|
+
}
|
|
4196
|
+
});
|
|
4197
|
+
|
|
4198
|
+
// packages/execution/dist/tools/tool-creator.js
|
|
4199
|
+
var CreateToolTool, ManageToolsTool;
|
|
4200
|
+
var init_tool_creator = __esm({
|
|
4201
|
+
"packages/execution/dist/tools/tool-creator.js"() {
|
|
4202
|
+
"use strict";
|
|
4203
|
+
init_custom_tool();
|
|
4204
|
+
CreateToolTool = class {
|
|
4205
|
+
name = "create_tool";
|
|
4206
|
+
description = "Create a reusable custom tool from a multi-step workflow. Use this when you notice you're performing the same sequence of operations repeatedly (3+ times). The tool will be saved and available in future sessions. Parameters in step commands use {{param}} interpolation.";
|
|
4207
|
+
parameters = {
|
|
4208
|
+
type: "object",
|
|
4209
|
+
properties: {
|
|
4210
|
+
tool_name: {
|
|
4211
|
+
type: "string",
|
|
4212
|
+
description: "Unique tool name in snake_case (e.g., deploy_staging, run_full_test_suite)"
|
|
4213
|
+
},
|
|
4214
|
+
tool_description: {
|
|
4215
|
+
type: "string",
|
|
4216
|
+
description: "Clear description of what this tool does (shown to the agent)"
|
|
4217
|
+
},
|
|
4218
|
+
tool_parameters: {
|
|
4219
|
+
type: "object",
|
|
4220
|
+
description: `JSON Schema for the tool's parameters. Example: {"type":"object","properties":{"branch":{"type":"string","description":"Branch name"}},"required":["branch"]}`
|
|
4221
|
+
},
|
|
4222
|
+
steps: {
|
|
4223
|
+
type: "array",
|
|
4224
|
+
description: "Ordered list of steps. Each step has: command (shell command with {{param}} placeholders), description (what this step does), continueOnError (optional boolean).",
|
|
4225
|
+
items: {
|
|
4226
|
+
type: "object",
|
|
4227
|
+
properties: {
|
|
4228
|
+
command: { type: "string" },
|
|
4229
|
+
description: { type: "string" },
|
|
4230
|
+
continueOnError: { type: "boolean" }
|
|
4231
|
+
},
|
|
4232
|
+
required: ["command", "description"]
|
|
4233
|
+
}
|
|
4234
|
+
},
|
|
4235
|
+
scope: {
|
|
4236
|
+
type: "string",
|
|
4237
|
+
enum: ["project", "global"],
|
|
4238
|
+
description: "Where to save: 'project' saves to .oa/tools/ (this workspace only), 'global' saves to ~/.open-agents/tools/ (available in all projects). Use 'project' for project-specific workflows, 'global' for cross-project patterns."
|
|
4239
|
+
},
|
|
4240
|
+
source_pattern: {
|
|
4241
|
+
type: "string",
|
|
4242
|
+
description: "Description of the repeated pattern that motivated creating this tool"
|
|
4243
|
+
}
|
|
4244
|
+
},
|
|
4245
|
+
required: ["tool_name", "tool_description", "steps", "scope"]
|
|
4246
|
+
};
|
|
4247
|
+
repoRoot;
|
|
4248
|
+
constructor(repoRoot) {
|
|
4249
|
+
this.repoRoot = repoRoot;
|
|
4250
|
+
}
|
|
4251
|
+
async execute(args) {
|
|
4252
|
+
const start = performance.now();
|
|
4253
|
+
const toolName = String(args["tool_name"] ?? "").trim();
|
|
4254
|
+
const toolDescription = String(args["tool_description"] ?? "").trim();
|
|
4255
|
+
const rawSteps = args["steps"];
|
|
4256
|
+
const scope = args["scope"] === "global" ? "global" : "project";
|
|
4257
|
+
const toolParameters = args["tool_parameters"] ?? {
|
|
4258
|
+
type: "object",
|
|
4259
|
+
properties: {},
|
|
4260
|
+
required: []
|
|
4261
|
+
};
|
|
4262
|
+
const sourcePattern = args["source_pattern"];
|
|
4263
|
+
if (!toolName) {
|
|
4264
|
+
return {
|
|
4265
|
+
success: false,
|
|
4266
|
+
output: "",
|
|
4267
|
+
error: "tool_name is required",
|
|
4268
|
+
durationMs: performance.now() - start
|
|
4269
|
+
};
|
|
4270
|
+
}
|
|
4271
|
+
if (!/^[a-z][a-z0-9_]*$/.test(toolName)) {
|
|
4272
|
+
return {
|
|
4273
|
+
success: false,
|
|
4274
|
+
output: "",
|
|
4275
|
+
error: "tool_name must be snake_case (lowercase letters, numbers, underscores, starting with a letter)",
|
|
4276
|
+
durationMs: performance.now() - start
|
|
4277
|
+
};
|
|
4278
|
+
}
|
|
4279
|
+
const CORE_TOOLS = /* @__PURE__ */ new Set([
|
|
4280
|
+
"file_read",
|
|
4281
|
+
"file_write",
|
|
4282
|
+
"file_edit",
|
|
4283
|
+
"shell",
|
|
4284
|
+
"grep_search",
|
|
4285
|
+
"find_files",
|
|
4286
|
+
"list_directory",
|
|
4287
|
+
"web_fetch",
|
|
4288
|
+
"web_search",
|
|
4289
|
+
"memory_read",
|
|
4290
|
+
"memory_write",
|
|
4291
|
+
"task_complete",
|
|
4292
|
+
"sub_agent",
|
|
4293
|
+
"background_run",
|
|
4294
|
+
"task_status",
|
|
4295
|
+
"task_output",
|
|
4296
|
+
"task_stop",
|
|
4297
|
+
"batch_edit",
|
|
4298
|
+
"codebase_map",
|
|
4299
|
+
"diagnostic",
|
|
4300
|
+
"git_info",
|
|
4301
|
+
"image_read",
|
|
4302
|
+
"screenshot",
|
|
4303
|
+
"ocr",
|
|
4304
|
+
"create_tool",
|
|
4305
|
+
"manage_tools",
|
|
4306
|
+
"aiwg_setup",
|
|
4307
|
+
"aiwg_health",
|
|
4308
|
+
"aiwg_workflow"
|
|
4309
|
+
]);
|
|
4310
|
+
if (CORE_TOOLS.has(toolName)) {
|
|
4311
|
+
return {
|
|
4312
|
+
success: false,
|
|
4313
|
+
output: "",
|
|
4314
|
+
error: `Cannot override core tool: ${toolName}`,
|
|
4315
|
+
durationMs: performance.now() - start
|
|
4316
|
+
};
|
|
4317
|
+
}
|
|
4318
|
+
if (!toolDescription) {
|
|
4319
|
+
return {
|
|
4320
|
+
success: false,
|
|
4321
|
+
output: "",
|
|
4322
|
+
error: "tool_description is required",
|
|
4323
|
+
durationMs: performance.now() - start
|
|
4324
|
+
};
|
|
4325
|
+
}
|
|
4326
|
+
if (!rawSteps || !Array.isArray(rawSteps) || rawSteps.length === 0) {
|
|
4327
|
+
return {
|
|
4328
|
+
success: false,
|
|
4329
|
+
output: "",
|
|
4330
|
+
error: "steps must be a non-empty array of {command, description} objects",
|
|
4331
|
+
durationMs: performance.now() - start
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4334
|
+
const steps = [];
|
|
4335
|
+
for (let i = 0; i < rawSteps.length; i++) {
|
|
4336
|
+
const raw = rawSteps[i];
|
|
4337
|
+
const command = String(raw["command"] ?? "").trim();
|
|
4338
|
+
const description = String(raw["description"] ?? "").trim();
|
|
4339
|
+
if (!command) {
|
|
4340
|
+
return {
|
|
4341
|
+
success: false,
|
|
4342
|
+
output: "",
|
|
4343
|
+
error: `Step ${i + 1} is missing a command`,
|
|
4344
|
+
durationMs: performance.now() - start
|
|
4345
|
+
};
|
|
4346
|
+
}
|
|
4347
|
+
steps.push({
|
|
4348
|
+
command,
|
|
4349
|
+
description: description || `Step ${i + 1}`,
|
|
4350
|
+
continueOnError: Boolean(raw["continueOnError"])
|
|
4351
|
+
});
|
|
4352
|
+
}
|
|
4353
|
+
const existing = loadCustomTools(this.repoRoot);
|
|
4354
|
+
const existingTool = existing.find((t) => t.name === toolName);
|
|
4355
|
+
const isUpdate = !!existingTool;
|
|
4356
|
+
const definition = {
|
|
4357
|
+
name: toolName,
|
|
4358
|
+
description: toolDescription,
|
|
4359
|
+
version: isUpdate ? existingTool.version + 1 : 1,
|
|
4360
|
+
parameters: toolParameters,
|
|
4361
|
+
steps,
|
|
4362
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4363
|
+
createdBy: "agent",
|
|
4364
|
+
sourcePattern,
|
|
4365
|
+
triggerCount: void 0
|
|
4366
|
+
};
|
|
4367
|
+
try {
|
|
4368
|
+
const filePath = saveCustomToolDefinition(definition, scope, this.repoRoot);
|
|
4369
|
+
const output = [
|
|
4370
|
+
`Custom tool ${isUpdate ? "updated" : "created"}: ${toolName}`,
|
|
4371
|
+
` Scope: ${scope}`,
|
|
4372
|
+
` Saved to: ${filePath}`,
|
|
4373
|
+
` Steps: ${steps.length}`,
|
|
4374
|
+
` Version: ${definition.version}`,
|
|
4375
|
+
"",
|
|
4376
|
+
"The tool will be available in future sessions.",
|
|
4377
|
+
isUpdate ? "Note: This updates the existing tool definition." : "",
|
|
4378
|
+
"",
|
|
4379
|
+
"Definition:",
|
|
4380
|
+
JSON.stringify(definition, null, 2)
|
|
4381
|
+
].join("\n");
|
|
4382
|
+
return {
|
|
4383
|
+
success: true,
|
|
4384
|
+
output,
|
|
4385
|
+
durationMs: performance.now() - start
|
|
4386
|
+
};
|
|
4387
|
+
} catch (err) {
|
|
4388
|
+
return {
|
|
4389
|
+
success: false,
|
|
4390
|
+
output: "",
|
|
4391
|
+
error: `Failed to save tool: ${err instanceof Error ? err.message : String(err)}`,
|
|
4392
|
+
durationMs: performance.now() - start
|
|
4393
|
+
};
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
};
|
|
4397
|
+
ManageToolsTool = class {
|
|
4398
|
+
name = "manage_tools";
|
|
4399
|
+
description = "List, inspect, or delete custom tools. Actions: 'list' (show all custom tools), 'inspect <name>' (show definition), 'delete <name> <scope>' (remove a tool).";
|
|
4400
|
+
parameters = {
|
|
4401
|
+
type: "object",
|
|
4402
|
+
properties: {
|
|
4403
|
+
action: {
|
|
4404
|
+
type: "string",
|
|
4405
|
+
enum: ["list", "inspect", "delete"],
|
|
4406
|
+
description: "Action to perform"
|
|
4407
|
+
},
|
|
4408
|
+
tool_name: {
|
|
4409
|
+
type: "string",
|
|
4410
|
+
description: "Tool name (for inspect/delete)"
|
|
4411
|
+
},
|
|
4412
|
+
scope: {
|
|
4413
|
+
type: "string",
|
|
4414
|
+
enum: ["project", "global"],
|
|
4415
|
+
description: "Scope for delete action"
|
|
4416
|
+
}
|
|
4417
|
+
},
|
|
4418
|
+
required: ["action"]
|
|
4419
|
+
};
|
|
4420
|
+
repoRoot;
|
|
4421
|
+
constructor(repoRoot) {
|
|
4422
|
+
this.repoRoot = repoRoot;
|
|
4423
|
+
}
|
|
4424
|
+
async execute(args) {
|
|
4425
|
+
const start = performance.now();
|
|
4426
|
+
const action = String(args["action"] ?? "list");
|
|
4427
|
+
const toolName = args["tool_name"];
|
|
4428
|
+
switch (action) {
|
|
4429
|
+
case "list": {
|
|
4430
|
+
const { listCustomToolFiles: listCustomToolFiles2 } = await Promise.resolve().then(() => (init_custom_tool(), custom_tool_exports));
|
|
4431
|
+
const tools = listCustomToolFiles2(this.repoRoot);
|
|
4432
|
+
if (tools.length === 0) {
|
|
4433
|
+
return {
|
|
4434
|
+
success: true,
|
|
4435
|
+
output: "No custom tools installed.\n\nUse create_tool to create one from a repeated workflow.",
|
|
4436
|
+
durationMs: performance.now() - start
|
|
4437
|
+
};
|
|
4438
|
+
}
|
|
4439
|
+
const lines = ["Custom Tools:", ""];
|
|
4440
|
+
for (const t of tools) {
|
|
4441
|
+
lines.push(` ${t.name} (${t.scope}, v${t.version}, ${t.stepsCount} steps)`);
|
|
4442
|
+
lines.push(` ${t.description}`);
|
|
4443
|
+
}
|
|
4444
|
+
return {
|
|
4445
|
+
success: true,
|
|
4446
|
+
output: lines.join("\n"),
|
|
4447
|
+
durationMs: performance.now() - start
|
|
4448
|
+
};
|
|
4449
|
+
}
|
|
4450
|
+
case "inspect": {
|
|
4451
|
+
if (!toolName) {
|
|
4452
|
+
return { success: false, output: "", error: "tool_name required for inspect", durationMs: performance.now() - start };
|
|
4453
|
+
}
|
|
4454
|
+
const tools = loadCustomTools(this.repoRoot);
|
|
4455
|
+
const tool = tools.find((t) => t.name === toolName);
|
|
4456
|
+
if (!tool) {
|
|
4457
|
+
return { success: false, output: "", error: `Tool not found: ${toolName}`, durationMs: performance.now() - start };
|
|
4458
|
+
}
|
|
4459
|
+
return {
|
|
4460
|
+
success: true,
|
|
4461
|
+
output: JSON.stringify(tool, null, 2),
|
|
4462
|
+
durationMs: performance.now() - start
|
|
4463
|
+
};
|
|
4464
|
+
}
|
|
4465
|
+
case "delete": {
|
|
4466
|
+
if (!toolName) {
|
|
4467
|
+
return { success: false, output: "", error: "tool_name required for delete", durationMs: performance.now() - start };
|
|
4468
|
+
}
|
|
4469
|
+
const scope = args["scope"] === "global" ? "global" : "project";
|
|
4470
|
+
const { deleteCustomToolDefinition: deleteCustomToolDefinition2 } = await Promise.resolve().then(() => (init_custom_tool(), custom_tool_exports));
|
|
4471
|
+
const deleted = deleteCustomToolDefinition2(toolName, scope, this.repoRoot);
|
|
4472
|
+
return {
|
|
4473
|
+
success: deleted,
|
|
4474
|
+
output: deleted ? `Deleted custom tool: ${toolName} (${scope})` : `Tool not found: ${toolName} (${scope})`,
|
|
4475
|
+
error: deleted ? void 0 : "Tool not found",
|
|
4476
|
+
durationMs: performance.now() - start
|
|
4477
|
+
};
|
|
4478
|
+
}
|
|
4479
|
+
default:
|
|
4480
|
+
return {
|
|
4481
|
+
success: false,
|
|
4482
|
+
output: "",
|
|
4483
|
+
error: `Unknown action: ${action}. Use list, inspect, or delete.`,
|
|
4484
|
+
durationMs: performance.now() - start
|
|
4485
|
+
};
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
};
|
|
4489
|
+
}
|
|
4490
|
+
});
|
|
4491
|
+
|
|
3726
4492
|
// packages/execution/dist/shellRunner.js
|
|
3727
4493
|
var init_shellRunner = __esm({
|
|
3728
4494
|
"packages/execution/dist/shellRunner.js"() {
|
|
@@ -3817,11 +4583,14 @@ var init_dist2 = __esm({
|
|
|
3817
4583
|
init_aiwg_health();
|
|
3818
4584
|
init_aiwg_workflow();
|
|
3819
4585
|
init_batch_edit();
|
|
4586
|
+
init_file_patch();
|
|
3820
4587
|
init_codebase_map();
|
|
3821
4588
|
init_diagnostic();
|
|
3822
4589
|
init_git_info();
|
|
3823
4590
|
init_background_task();
|
|
3824
4591
|
init_image();
|
|
4592
|
+
init_custom_tool();
|
|
4593
|
+
init_tool_creator();
|
|
3825
4594
|
init_shellRunner();
|
|
3826
4595
|
init_gitWorktree();
|
|
3827
4596
|
init_patchApplier();
|
|
@@ -4936,8 +5705,8 @@ var init_code_retriever = __esm({
|
|
|
4936
5705
|
});
|
|
4937
5706
|
}
|
|
4938
5707
|
async getFileContent(filePath, startLine, endLine) {
|
|
4939
|
-
const { readFile:
|
|
4940
|
-
const content = await
|
|
5708
|
+
const { readFile: readFile10 } = await import("node:fs/promises");
|
|
5709
|
+
const content = await readFile10(filePath, "utf-8");
|
|
4941
5710
|
if (startLine === void 0)
|
|
4942
5711
|
return content;
|
|
4943
5712
|
const lines = content.split("\n");
|
|
@@ -4952,8 +5721,8 @@ var init_code_retriever = __esm({
|
|
|
4952
5721
|
// packages/retrieval/dist/lexicalSearch.js
|
|
4953
5722
|
import { execFile as execFile4 } from "node:child_process";
|
|
4954
5723
|
import { promisify as promisify4 } from "node:util";
|
|
4955
|
-
import { readFile as
|
|
4956
|
-
import { join as
|
|
5724
|
+
import { readFile as readFile7, readdir, stat } from "node:fs/promises";
|
|
5725
|
+
import { join as join12, extname as extname3 } from "node:path";
|
|
4957
5726
|
async function searchByPath(pathPattern, options) {
|
|
4958
5727
|
const allFiles = await collectFiles(options.rootDir, options.includeGlobs ?? DEFAULT_INCLUDE_GLOBS, options.excludeGlobs ?? DEFAULT_EXCLUDE_GLOBS);
|
|
4959
5728
|
const pattern = options.caseInsensitive ? pathPattern.toLowerCase() : pathPattern;
|
|
@@ -5058,7 +5827,7 @@ async function searchWithNodeFallback(pattern, kind, options) {
|
|
|
5058
5827
|
if (results.length >= maxMatches)
|
|
5059
5828
|
break;
|
|
5060
5829
|
try {
|
|
5061
|
-
const content = await
|
|
5830
|
+
const content = await readFile7(filePath, "utf-8");
|
|
5062
5831
|
const contentLines = content.split("\n");
|
|
5063
5832
|
for (let i = 0; i < contentLines.length; i++) {
|
|
5064
5833
|
if (results.length >= maxMatches)
|
|
@@ -5095,7 +5864,7 @@ async function walkForFiles(rootDir, dir, excludeGlobs, results) {
|
|
|
5095
5864
|
continue;
|
|
5096
5865
|
if (excludeGlobs.some((g) => entry.name === g || matchesGlob(entry.name, g)))
|
|
5097
5866
|
continue;
|
|
5098
|
-
const absPath =
|
|
5867
|
+
const absPath = join12(dir, entry.name);
|
|
5099
5868
|
if (entry.isDirectory()) {
|
|
5100
5869
|
await walkForFiles(rootDir, absPath, excludeGlobs, results);
|
|
5101
5870
|
} else if (entry.isFile()) {
|
|
@@ -5269,8 +6038,8 @@ var init_graphExpand = __esm({
|
|
|
5269
6038
|
});
|
|
5270
6039
|
|
|
5271
6040
|
// packages/retrieval/dist/snippetPacker.js
|
|
5272
|
-
import { readFile as
|
|
5273
|
-
import { join as
|
|
6041
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
6042
|
+
import { join as join13 } from "node:path";
|
|
5274
6043
|
async function packSnippets(requests, opts = {}) {
|
|
5275
6044
|
const maxTokens = opts.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
5276
6045
|
const contextLines = opts.contextLines ?? DEFAULT_CONTEXT_LINES;
|
|
@@ -5296,10 +6065,10 @@ async function packSnippets(requests, opts = {}) {
|
|
|
5296
6065
|
return { packed, dropped, totalTokens };
|
|
5297
6066
|
}
|
|
5298
6067
|
async function extractSnippet(req, repoRoot, contextLines = DEFAULT_CONTEXT_LINES) {
|
|
5299
|
-
const absPath = req.filePath.startsWith("/") ? req.filePath :
|
|
6068
|
+
const absPath = req.filePath.startsWith("/") ? req.filePath : join13(repoRoot, req.filePath);
|
|
5300
6069
|
let content;
|
|
5301
6070
|
try {
|
|
5302
|
-
content = await
|
|
6071
|
+
content = await readFile8(absPath, "utf-8");
|
|
5303
6072
|
} catch {
|
|
5304
6073
|
return null;
|
|
5305
6074
|
}
|
|
@@ -6458,7 +7227,8 @@ var init_agenticRunner = __esm({
|
|
|
6458
7227
|
|
|
6459
7228
|
- file_read: Read file contents (always read before editing). Supports path, offset, limit.
|
|
6460
7229
|
- file_write: Create or overwrite a file with complete content
|
|
6461
|
-
- file_edit: Make a precise string replacement in a file (preferred over rewriting). Uses old_string/new_string.
|
|
7230
|
+
- file_edit: Make a precise string replacement in a file (preferred over rewriting). Uses old_string/new_string. old_string must be unique unless replace_all=true. Use replace_all for variable renames.
|
|
7231
|
+
- file_patch: Edit specific line ranges in large files. Modes: replace (swap lines), insert_before, insert_after, delete. Use dry_run to preview. Best for large files (500+ lines) where string matching is fragile.
|
|
6462
7232
|
- find_files: Find files by name pattern (glob). Searches recursively, excludes node_modules/.git.
|
|
6463
7233
|
- grep_search: Search file contents with regex. Returns matching lines with paths and line numbers.
|
|
6464
7234
|
- shell: Execute any shell command (tests, builds, git, npm, etc.). Supports stdin parameter for input. Commands run with CI=true for non-interactive mode.
|
|
@@ -6564,11 +7334,39 @@ Commands run non-interactively (CI=true). When running scaffolding tools:
|
|
|
6564
7334
|
- If a command needs specific answers, use the stdin parameter
|
|
6565
7335
|
- If a command times out, it likely hit an interactive prompt \u2014 retry with --yes
|
|
6566
7336
|
|
|
7337
|
+
## Custom Tools
|
|
7338
|
+
|
|
7339
|
+
- create_tool: Create a reusable custom tool from a repeated multi-step workflow. Saves to .oa/tools/ (project) or ~/.open-agents/tools/ (global).
|
|
7340
|
+
- manage_tools: List, inspect, or delete custom tools.
|
|
7341
|
+
|
|
7342
|
+
Custom tools are agent-created shell command sequences that automate repeated workflows.
|
|
7343
|
+
They appear alongside core tools and can be invoked just like any built-in tool.
|
|
7344
|
+
|
|
7345
|
+
### When to Create a Custom Tool
|
|
7346
|
+
|
|
7347
|
+
If you notice you're performing the SAME multi-step sequence for the 3rd time or more:
|
|
7348
|
+
1. Recognize the repeated pattern (e.g., "bump version \u2192 build \u2192 publish \u2192 commit \u2192 push")
|
|
7349
|
+
2. Identify what varies between runs (these become parameters)
|
|
7350
|
+
3. Call create_tool with the steps and parameters
|
|
7351
|
+
4. Choose scope: 'project' for project-specific workflows, 'global' for cross-project patterns
|
|
7352
|
+
|
|
7353
|
+
### Custom Tool Guidelines
|
|
7354
|
+
|
|
7355
|
+
- Name tools descriptively in snake_case (e.g., run_full_validation, deploy_to_staging)
|
|
7356
|
+
- Use {{param}} syntax in step commands for interpolation
|
|
7357
|
+
- Set continueOnError=true on steps that may fail but shouldn't stop the pipeline
|
|
7358
|
+
- Test the tool mentally before creating \u2014 ensure the steps would work in order
|
|
7359
|
+
- Prefer 'project' scope unless the pattern genuinely applies to all projects
|
|
7360
|
+
|
|
6567
7361
|
## Context Efficiency
|
|
6568
7362
|
|
|
6569
7363
|
- Use grep_search to find specific code instead of reading many files
|
|
6570
7364
|
- Use file_edit for targeted changes instead of full file rewrites
|
|
6571
|
-
-
|
|
7365
|
+
- Use file_edit with replace_all=true for variable/function renames across a file
|
|
7366
|
+
- If file_edit fails with "not unique", include more surrounding context in old_string
|
|
7367
|
+
- For large files (500+ lines): use file_read with offset/limit, then file_patch with line numbers
|
|
7368
|
+
- file_patch with dry_run=true lets you preview changes before applying them
|
|
7369
|
+
- batch_edit to apply multiple edits across files in one call (reduces turns)
|
|
6572
7370
|
- Focus on error messages in shell output \u2014 skip verbose build logs
|
|
6573
7371
|
- Don't read files you don't need to modify`;
|
|
6574
7372
|
AgenticRunner = class {
|
|
@@ -7708,6 +8506,7 @@ function renderSlashHelp() {
|
|
|
7708
8506
|
["/voice <model>", "Set voice: glados, overwatch"],
|
|
7709
8507
|
["/stream", "Toggle real-time token streaming (pastel syntax highlighting)"],
|
|
7710
8508
|
["/bruteforce", "Toggle brute-force mode (auto re-engage on turn limit)"],
|
|
8509
|
+
["/tools", "List agent-created custom tools"],
|
|
7711
8510
|
["/verbose", "Toggle verbose mode"],
|
|
7712
8511
|
["/clear", "Clear the screen"],
|
|
7713
8512
|
["/help", "Show this help"],
|
|
@@ -7953,6 +8752,7 @@ var init_render = __esm({
|
|
|
7953
8752
|
"file_read",
|
|
7954
8753
|
"file_write",
|
|
7955
8754
|
"file_edit",
|
|
8755
|
+
"file_patch",
|
|
7956
8756
|
"shell",
|
|
7957
8757
|
"grep_search",
|
|
7958
8758
|
"find_files",
|
|
@@ -7976,6 +8776,8 @@ var init_render = __esm({
|
|
|
7976
8776
|
"aiwg_setup",
|
|
7977
8777
|
"aiwg_health",
|
|
7978
8778
|
"aiwg_workflow",
|
|
8779
|
+
"create_tool",
|
|
8780
|
+
"manage_tools",
|
|
7979
8781
|
"task_complete"
|
|
7980
8782
|
];
|
|
7981
8783
|
COMMAND_NAMES = [
|
|
@@ -7988,6 +8790,7 @@ var init_render = __esm({
|
|
|
7988
8790
|
"/stream",
|
|
7989
8791
|
"/verbose",
|
|
7990
8792
|
"/bruteforce",
|
|
8793
|
+
"/tools",
|
|
7991
8794
|
"/clear",
|
|
7992
8795
|
"/help",
|
|
7993
8796
|
"/quit"
|
|
@@ -8080,6 +8883,27 @@ async function handleSlashCommand(input, ctx) {
|
|
|
8080
8883
|
renderInfo(`Token streaming: ${isOn ? "on" : "off"}${hasLocal ? " (project-local)" : ""}` + (isOn ? " \u2014 thinking tokens in grey italics, responses with pastel syntax highlighting" : ""));
|
|
8081
8884
|
return "handled";
|
|
8082
8885
|
}
|
|
8886
|
+
case "tools": {
|
|
8887
|
+
const tools = listCustomToolFiles(ctx.repoRoot);
|
|
8888
|
+
if (tools.length === 0) {
|
|
8889
|
+
renderInfo("No custom tools installed.");
|
|
8890
|
+
renderInfo("The agent will automatically create tools when it detects repeated workflows (3+ times).");
|
|
8891
|
+
renderInfo('Or ask the agent: "create a tool for [workflow]"');
|
|
8892
|
+
} else {
|
|
8893
|
+
process.stdout.write(`
|
|
8894
|
+
${c2.bold("Custom Tools:")}
|
|
8895
|
+
|
|
8896
|
+
`);
|
|
8897
|
+
for (const t of tools) {
|
|
8898
|
+
process.stdout.write(` ${c2.cyan(t.name.padEnd(28))} ${c2.dim(`(${t.scope}, v${t.version}, ${t.stepsCount} steps)`)}
|
|
8899
|
+
`);
|
|
8900
|
+
process.stdout.write(` ${"".padEnd(28)} ${t.description}
|
|
8901
|
+
`);
|
|
8902
|
+
}
|
|
8903
|
+
process.stdout.write("\n");
|
|
8904
|
+
}
|
|
8905
|
+
return "handled";
|
|
8906
|
+
}
|
|
8083
8907
|
case "bruteforce":
|
|
8084
8908
|
case "brute": {
|
|
8085
8909
|
const isOn = ctx.bruteForceToggle();
|
|
@@ -8208,17 +9032,17 @@ async function handleUpdate() {
|
|
|
8208
9032
|
try {
|
|
8209
9033
|
const { createRequire: createRequire4 } = await import("node:module");
|
|
8210
9034
|
const { fileURLToPath: fileURLToPath3 } = await import("node:url");
|
|
8211
|
-
const { dirname:
|
|
8212
|
-
const { existsSync:
|
|
9035
|
+
const { dirname: dirname4, join: join23 } = await import("node:path");
|
|
9036
|
+
const { existsSync: existsSync15 } = await import("node:fs");
|
|
8213
9037
|
const req = createRequire4(import.meta.url);
|
|
8214
|
-
const thisDir =
|
|
9038
|
+
const thisDir = dirname4(fileURLToPath3(import.meta.url));
|
|
8215
9039
|
const candidates = [
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
9040
|
+
join23(thisDir, "..", "package.json"),
|
|
9041
|
+
join23(thisDir, "..", "..", "package.json"),
|
|
9042
|
+
join23(thisDir, "..", "..", "..", "package.json")
|
|
8219
9043
|
];
|
|
8220
9044
|
for (const pkgPath of candidates) {
|
|
8221
|
-
if (
|
|
9045
|
+
if (existsSync15(pkgPath)) {
|
|
8222
9046
|
const pkg = req(pkgPath);
|
|
8223
9047
|
if (pkg.name === "open-agents-ai" || pkg.name === "@open-agents/cli") {
|
|
8224
9048
|
currentVersion = pkg.version ?? "0.0.0";
|
|
@@ -8286,6 +9110,7 @@ var init_commands = __esm({
|
|
|
8286
9110
|
"use strict";
|
|
8287
9111
|
init_model_picker();
|
|
8288
9112
|
init_render();
|
|
9113
|
+
init_dist2();
|
|
8289
9114
|
init_config();
|
|
8290
9115
|
init_updater();
|
|
8291
9116
|
}
|
|
@@ -8294,9 +9119,9 @@ var init_commands = __esm({
|
|
|
8294
9119
|
// packages/cli/dist/tui/setup.js
|
|
8295
9120
|
import * as readline from "node:readline";
|
|
8296
9121
|
import { execSync as execSync8 } from "node:child_process";
|
|
8297
|
-
import { existsSync as
|
|
8298
|
-
import { join as
|
|
8299
|
-
import { homedir as
|
|
9122
|
+
import { existsSync as existsSync9, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "node:fs";
|
|
9123
|
+
import { join as join14 } from "node:path";
|
|
9124
|
+
import { homedir as homedir4 } from "node:os";
|
|
8300
9125
|
function detectSystemSpecs() {
|
|
8301
9126
|
let totalRamGB = 0;
|
|
8302
9127
|
let availableRamGB = 0;
|
|
@@ -8379,8 +9204,8 @@ function modelSupportsToolCalling(modelName) {
|
|
|
8379
9204
|
return false;
|
|
8380
9205
|
}
|
|
8381
9206
|
function ask(rl, question) {
|
|
8382
|
-
return new Promise((
|
|
8383
|
-
rl.question(question, (answer) =>
|
|
9207
|
+
return new Promise((resolve15) => {
|
|
9208
|
+
rl.question(question, (answer) => resolve15(answer.trim()));
|
|
8384
9209
|
});
|
|
8385
9210
|
}
|
|
8386
9211
|
function pullModelWithAutoUpdate(tag) {
|
|
@@ -8591,10 +9416,10 @@ async function doSetup(config, rl) {
|
|
|
8591
9416
|
`PARAMETER num_predict 16384`,
|
|
8592
9417
|
`PARAMETER stop "<|endoftext|>"`
|
|
8593
9418
|
].join("\n");
|
|
8594
|
-
const modelDir2 =
|
|
8595
|
-
|
|
8596
|
-
const modelfilePath =
|
|
8597
|
-
|
|
9419
|
+
const modelDir2 = join14(homedir4(), ".open-agents", "models");
|
|
9420
|
+
mkdirSync4(modelDir2, { recursive: true });
|
|
9421
|
+
const modelfilePath = join14(modelDir2, `Modelfile.${customName}`);
|
|
9422
|
+
writeFileSync4(modelfilePath, modelfileContent + "\n", "utf8");
|
|
8598
9423
|
process.stdout.write(` ${c2.dim("Creating model...")} `);
|
|
8599
9424
|
execSync8(`ollama create ${customName} -f ${modelfilePath}`, {
|
|
8600
9425
|
stdio: "pipe",
|
|
@@ -8639,7 +9464,7 @@ async function isModelAvailable(config) {
|
|
|
8639
9464
|
}
|
|
8640
9465
|
function isFirstRun() {
|
|
8641
9466
|
try {
|
|
8642
|
-
return !
|
|
9467
|
+
return !existsSync9(join14(homedir4(), ".open-agents", "config.json"));
|
|
8643
9468
|
} catch {
|
|
8644
9469
|
return true;
|
|
8645
9470
|
}
|
|
@@ -8679,52 +9504,52 @@ var init_setup = __esm({
|
|
|
8679
9504
|
});
|
|
8680
9505
|
|
|
8681
9506
|
// packages/cli/dist/tui/oa-directory.js
|
|
8682
|
-
import { existsSync as
|
|
8683
|
-
import { join as
|
|
8684
|
-
import { homedir as
|
|
9507
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5, readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
|
|
9508
|
+
import { join as join15, relative as relative2, basename as basename2, extname as extname4 } from "node:path";
|
|
9509
|
+
import { homedir as homedir5 } from "node:os";
|
|
8685
9510
|
function initOaDirectory(repoRoot) {
|
|
8686
|
-
const oaPath =
|
|
9511
|
+
const oaPath = join15(repoRoot, OA_DIR);
|
|
8687
9512
|
for (const sub of SUBDIRS) {
|
|
8688
|
-
|
|
9513
|
+
mkdirSync5(join15(oaPath, sub), { recursive: true });
|
|
8689
9514
|
}
|
|
8690
9515
|
return oaPath;
|
|
8691
9516
|
}
|
|
8692
9517
|
function hasOaDirectory(repoRoot) {
|
|
8693
|
-
return
|
|
9518
|
+
return existsSync10(join15(repoRoot, OA_DIR, "index"));
|
|
8694
9519
|
}
|
|
8695
9520
|
function loadProjectSettings(repoRoot) {
|
|
8696
|
-
const settingsPath =
|
|
9521
|
+
const settingsPath = join15(repoRoot, OA_DIR, "settings.json");
|
|
8697
9522
|
try {
|
|
8698
|
-
if (
|
|
8699
|
-
return JSON.parse(
|
|
9523
|
+
if (existsSync10(settingsPath)) {
|
|
9524
|
+
return JSON.parse(readFileSync8(settingsPath, "utf-8"));
|
|
8700
9525
|
}
|
|
8701
9526
|
} catch {
|
|
8702
9527
|
}
|
|
8703
9528
|
return {};
|
|
8704
9529
|
}
|
|
8705
9530
|
function saveProjectSettings(repoRoot, settings) {
|
|
8706
|
-
const oaPath =
|
|
8707
|
-
|
|
9531
|
+
const oaPath = join15(repoRoot, OA_DIR);
|
|
9532
|
+
mkdirSync5(oaPath, { recursive: true });
|
|
8708
9533
|
const existing = loadProjectSettings(repoRoot);
|
|
8709
9534
|
const merged = { ...existing, ...settings };
|
|
8710
|
-
|
|
9535
|
+
writeFileSync5(join15(oaPath, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
8711
9536
|
}
|
|
8712
9537
|
function loadGlobalSettings() {
|
|
8713
|
-
const settingsPath =
|
|
9538
|
+
const settingsPath = join15(homedir5(), ".open-agents", "settings.json");
|
|
8714
9539
|
try {
|
|
8715
|
-
if (
|
|
8716
|
-
return JSON.parse(
|
|
9540
|
+
if (existsSync10(settingsPath)) {
|
|
9541
|
+
return JSON.parse(readFileSync8(settingsPath, "utf-8"));
|
|
8717
9542
|
}
|
|
8718
9543
|
} catch {
|
|
8719
9544
|
}
|
|
8720
9545
|
return {};
|
|
8721
9546
|
}
|
|
8722
9547
|
function saveGlobalSettings(settings) {
|
|
8723
|
-
const dir =
|
|
8724
|
-
|
|
9548
|
+
const dir = join15(homedir5(), ".open-agents");
|
|
9549
|
+
mkdirSync5(dir, { recursive: true });
|
|
8725
9550
|
const existing = loadGlobalSettings();
|
|
8726
9551
|
const merged = { ...existing, ...settings };
|
|
8727
|
-
|
|
9552
|
+
writeFileSync5(join15(dir, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
8728
9553
|
}
|
|
8729
9554
|
function resolveSettings(repoRoot) {
|
|
8730
9555
|
const global = loadGlobalSettings();
|
|
@@ -8739,12 +9564,12 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
|
|
|
8739
9564
|
while (dir && !visited.has(dir)) {
|
|
8740
9565
|
visited.add(dir);
|
|
8741
9566
|
for (const name of CONTEXT_FILES) {
|
|
8742
|
-
const filePath =
|
|
9567
|
+
const filePath = join15(dir, name);
|
|
8743
9568
|
const normalizedName = name.toLowerCase();
|
|
8744
|
-
if (
|
|
9569
|
+
if (existsSync10(filePath) && !seen.has(filePath)) {
|
|
8745
9570
|
seen.add(filePath);
|
|
8746
9571
|
try {
|
|
8747
|
-
let content =
|
|
9572
|
+
let content = readFileSync8(filePath, "utf-8");
|
|
8748
9573
|
if (content.length > maxContentLen) {
|
|
8749
9574
|
content = content.slice(0, maxContentLen) + "\n\n...(truncated)";
|
|
8750
9575
|
}
|
|
@@ -8758,11 +9583,11 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
|
|
|
8758
9583
|
}
|
|
8759
9584
|
}
|
|
8760
9585
|
}
|
|
8761
|
-
const projectMap =
|
|
8762
|
-
if (
|
|
9586
|
+
const projectMap = join15(dir, OA_DIR, "context", "project-map.md");
|
|
9587
|
+
if (existsSync10(projectMap) && !seen.has(projectMap)) {
|
|
8763
9588
|
seen.add(projectMap);
|
|
8764
9589
|
try {
|
|
8765
|
-
let content =
|
|
9590
|
+
let content = readFileSync8(projectMap, "utf-8");
|
|
8766
9591
|
if (content.length > maxContentLen) {
|
|
8767
9592
|
content = content.slice(0, maxContentLen) + "\n\n...(truncated)";
|
|
8768
9593
|
}
|
|
@@ -8774,7 +9599,7 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
|
|
|
8774
9599
|
} catch {
|
|
8775
9600
|
}
|
|
8776
9601
|
}
|
|
8777
|
-
const parent =
|
|
9602
|
+
const parent = join15(dir, "..");
|
|
8778
9603
|
if (parent === dir)
|
|
8779
9604
|
break;
|
|
8780
9605
|
dir = parent;
|
|
@@ -8792,9 +9617,9 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
|
|
|
8792
9617
|
return found;
|
|
8793
9618
|
}
|
|
8794
9619
|
function readIndexMeta(repoRoot) {
|
|
8795
|
-
const metaPath =
|
|
9620
|
+
const metaPath = join15(repoRoot, OA_DIR, "index", "meta.json");
|
|
8796
9621
|
try {
|
|
8797
|
-
return JSON.parse(
|
|
9622
|
+
return JSON.parse(readFileSync8(metaPath, "utf-8"));
|
|
8798
9623
|
} catch {
|
|
8799
9624
|
return null;
|
|
8800
9625
|
}
|
|
@@ -8845,28 +9670,28 @@ ${tree}\`\`\`
|
|
|
8845
9670
|
sections.push("");
|
|
8846
9671
|
}
|
|
8847
9672
|
const content = sections.join("\n");
|
|
8848
|
-
const contextDir =
|
|
8849
|
-
|
|
8850
|
-
|
|
9673
|
+
const contextDir = join15(repoRoot, OA_DIR, "context");
|
|
9674
|
+
mkdirSync5(contextDir, { recursive: true });
|
|
9675
|
+
writeFileSync5(join15(contextDir, "project-map.md"), content, "utf-8");
|
|
8851
9676
|
return content;
|
|
8852
9677
|
}
|
|
8853
9678
|
function saveSession(repoRoot, session) {
|
|
8854
|
-
const historyDir =
|
|
8855
|
-
|
|
8856
|
-
|
|
9679
|
+
const historyDir = join15(repoRoot, OA_DIR, "history");
|
|
9680
|
+
mkdirSync5(historyDir, { recursive: true });
|
|
9681
|
+
writeFileSync5(join15(historyDir, `${session.id}.json`), JSON.stringify(session, null, 2), "utf-8");
|
|
8857
9682
|
}
|
|
8858
9683
|
function loadRecentSessions(repoRoot, limit = 5) {
|
|
8859
|
-
const historyDir =
|
|
8860
|
-
if (!
|
|
9684
|
+
const historyDir = join15(repoRoot, OA_DIR, "history");
|
|
9685
|
+
if (!existsSync10(historyDir))
|
|
8861
9686
|
return [];
|
|
8862
9687
|
try {
|
|
8863
|
-
const files =
|
|
8864
|
-
const stat3 = statSync5(
|
|
9688
|
+
const files = readdirSync5(historyDir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
9689
|
+
const stat3 = statSync5(join15(historyDir, f));
|
|
8865
9690
|
return { file: f, mtime: stat3.mtimeMs };
|
|
8866
9691
|
}).sort((a, b) => b.mtime - a.mtime).slice(0, limit);
|
|
8867
9692
|
return files.map((f) => {
|
|
8868
9693
|
try {
|
|
8869
|
-
return JSON.parse(
|
|
9694
|
+
return JSON.parse(readFileSync8(join15(historyDir, f.file), "utf-8"));
|
|
8870
9695
|
} catch {
|
|
8871
9696
|
return null;
|
|
8872
9697
|
}
|
|
@@ -8893,12 +9718,12 @@ function detectManifests(repoRoot) {
|
|
|
8893
9718
|
{ file: "docker-compose.yaml", type: "Docker Compose" }
|
|
8894
9719
|
];
|
|
8895
9720
|
for (const check of checks) {
|
|
8896
|
-
const filePath =
|
|
8897
|
-
if (
|
|
9721
|
+
const filePath = join15(repoRoot, check.file);
|
|
9722
|
+
if (existsSync10(filePath)) {
|
|
8898
9723
|
let name;
|
|
8899
9724
|
if (check.nameField) {
|
|
8900
9725
|
try {
|
|
8901
|
-
const data = JSON.parse(
|
|
9726
|
+
const data = JSON.parse(readFileSync8(filePath, "utf-8"));
|
|
8902
9727
|
name = data[check.nameField];
|
|
8903
9728
|
} catch {
|
|
8904
9729
|
}
|
|
@@ -8927,7 +9752,7 @@ function findKeyFiles(repoRoot) {
|
|
|
8927
9752
|
{ pattern: "CLAUDE.md", description: "Claude Code context" }
|
|
8928
9753
|
];
|
|
8929
9754
|
for (const check of checks) {
|
|
8930
|
-
if (
|
|
9755
|
+
if (existsSync10(join15(repoRoot, check.pattern))) {
|
|
8931
9756
|
keyFiles.push({ path: check.pattern, description: check.description });
|
|
8932
9757
|
}
|
|
8933
9758
|
}
|
|
@@ -8938,7 +9763,7 @@ function buildDirTree(root, maxDepth, prefix = "", depth = 0) {
|
|
|
8938
9763
|
return "";
|
|
8939
9764
|
let result = "";
|
|
8940
9765
|
try {
|
|
8941
|
-
const entries =
|
|
9766
|
+
const entries = readdirSync5(root, { withFileTypes: true }).filter((e) => !e.name.startsWith(".") || e.name === ".github").filter((e) => !SKIP_DIRS.has(e.name)).sort((a, b) => {
|
|
8942
9767
|
if (a.isDirectory() && !b.isDirectory())
|
|
8943
9768
|
return -1;
|
|
8944
9769
|
if (!a.isDirectory() && b.isDirectory())
|
|
@@ -8953,12 +9778,12 @@ function buildDirTree(root, maxDepth, prefix = "", depth = 0) {
|
|
|
8953
9778
|
if (entry.isDirectory()) {
|
|
8954
9779
|
let fileCount = 0;
|
|
8955
9780
|
try {
|
|
8956
|
-
fileCount =
|
|
9781
|
+
fileCount = readdirSync5(join15(root, entry.name)).filter((f) => !f.startsWith(".")).length;
|
|
8957
9782
|
} catch {
|
|
8958
9783
|
}
|
|
8959
9784
|
result += `${prefix}${connector}${entry.name}/ (${fileCount})
|
|
8960
9785
|
`;
|
|
8961
|
-
result += buildDirTree(
|
|
9786
|
+
result += buildDirTree(join15(root, entry.name), maxDepth, childPrefix, depth + 1);
|
|
8962
9787
|
} else if (depth < maxDepth) {
|
|
8963
9788
|
result += `${prefix}${connector}${entry.name}
|
|
8964
9789
|
`;
|
|
@@ -8973,7 +9798,7 @@ var init_oa_directory = __esm({
|
|
|
8973
9798
|
"packages/cli/dist/tui/oa-directory.js"() {
|
|
8974
9799
|
"use strict";
|
|
8975
9800
|
OA_DIR = ".oa";
|
|
8976
|
-
SUBDIRS = ["memory", "index", "context", "history", "notes", "embedded", "provenance"];
|
|
9801
|
+
SUBDIRS = ["memory", "index", "context", "history", "notes", "embedded", "provenance", "tools"];
|
|
8977
9802
|
CONTEXT_FILES = [
|
|
8978
9803
|
"AGENTS.md",
|
|
8979
9804
|
"OA.md",
|
|
@@ -9007,10 +9832,10 @@ var init_oa_directory = __esm({
|
|
|
9007
9832
|
});
|
|
9008
9833
|
|
|
9009
9834
|
// packages/cli/dist/tui/project-context.js
|
|
9010
|
-
import { existsSync as
|
|
9011
|
-
import { join as
|
|
9835
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9, readdirSync as readdirSync6 } from "node:fs";
|
|
9836
|
+
import { join as join16, basename as basename3 } from "node:path";
|
|
9012
9837
|
import { execSync as execSync9 } from "node:child_process";
|
|
9013
|
-
import { homedir as
|
|
9838
|
+
import { homedir as homedir6, platform, release } from "node:os";
|
|
9014
9839
|
function loadProjectFiles(repoRoot) {
|
|
9015
9840
|
const discovered = discoverContextFiles(repoRoot);
|
|
9016
9841
|
if (discovered.length === 0)
|
|
@@ -9028,10 +9853,10 @@ function loadProjectMap(repoRoot) {
|
|
|
9028
9853
|
if (!hasOaDirectory(repoRoot)) {
|
|
9029
9854
|
initOaDirectory(repoRoot);
|
|
9030
9855
|
}
|
|
9031
|
-
const mapPath =
|
|
9032
|
-
if (
|
|
9856
|
+
const mapPath = join16(repoRoot, OA_DIR, "context", "project-map.md");
|
|
9857
|
+
if (existsSync11(mapPath)) {
|
|
9033
9858
|
try {
|
|
9034
|
-
const content =
|
|
9859
|
+
const content = readFileSync9(mapPath, "utf-8");
|
|
9035
9860
|
return content;
|
|
9036
9861
|
} catch {
|
|
9037
9862
|
}
|
|
@@ -9072,31 +9897,31 @@ ${log}`);
|
|
|
9072
9897
|
}
|
|
9073
9898
|
function loadMemoryContext(repoRoot) {
|
|
9074
9899
|
const sections = [];
|
|
9075
|
-
const oaMemDir =
|
|
9900
|
+
const oaMemDir = join16(repoRoot, OA_DIR, "memory");
|
|
9076
9901
|
const oaEntries = loadMemoryDir(oaMemDir, "project");
|
|
9077
9902
|
if (oaEntries)
|
|
9078
9903
|
sections.push(oaEntries);
|
|
9079
|
-
const legacyMemDir =
|
|
9080
|
-
if (legacyMemDir !== oaMemDir &&
|
|
9904
|
+
const legacyMemDir = join16(repoRoot, ".open-agents", "memory");
|
|
9905
|
+
if (legacyMemDir !== oaMemDir && existsSync11(legacyMemDir)) {
|
|
9081
9906
|
const legacyEntries = loadMemoryDir(legacyMemDir, "project/legacy");
|
|
9082
9907
|
if (legacyEntries)
|
|
9083
9908
|
sections.push(legacyEntries);
|
|
9084
9909
|
}
|
|
9085
|
-
const globalMemDir =
|
|
9910
|
+
const globalMemDir = join16(homedir6(), ".open-agents", "memory");
|
|
9086
9911
|
const globalEntries = loadMemoryDir(globalMemDir, "global");
|
|
9087
9912
|
if (globalEntries)
|
|
9088
9913
|
sections.push(globalEntries);
|
|
9089
9914
|
return sections.join("\n\n");
|
|
9090
9915
|
}
|
|
9091
9916
|
function loadMemoryDir(memDir, scope) {
|
|
9092
|
-
if (!
|
|
9917
|
+
if (!existsSync11(memDir))
|
|
9093
9918
|
return "";
|
|
9094
9919
|
const lines = [];
|
|
9095
9920
|
try {
|
|
9096
|
-
const files =
|
|
9921
|
+
const files = readdirSync6(memDir).filter((f) => f.endsWith(".json"));
|
|
9097
9922
|
for (const file of files.slice(0, 10)) {
|
|
9098
9923
|
try {
|
|
9099
|
-
const raw =
|
|
9924
|
+
const raw = readFileSync9(join16(memDir, file), "utf-8");
|
|
9100
9925
|
const entries = JSON.parse(raw);
|
|
9101
9926
|
const topic = basename3(file, ".json");
|
|
9102
9927
|
const keys = Object.keys(entries);
|
|
@@ -9186,6 +10011,31 @@ function loadFailurePatterns(store) {
|
|
|
9186
10011
|
return "";
|
|
9187
10012
|
}
|
|
9188
10013
|
}
|
|
10014
|
+
function loadPatternSuggestions(repoRoot, store) {
|
|
10015
|
+
try {
|
|
10016
|
+
const projectPatterns = store.detectPatterns({ repoRoot, minOccurrences: 3, minLen: 3 });
|
|
10017
|
+
const globalPatterns = store.detectPatterns({ minOccurrences: 3, minLen: 3 });
|
|
10018
|
+
const all = [...projectPatterns];
|
|
10019
|
+
for (const gp of globalPatterns) {
|
|
10020
|
+
const fp = gp.pattern.map((e) => e.tool).join("->");
|
|
10021
|
+
const exists = all.some((p) => p.pattern.map((e) => e.tool).join("->") === fp);
|
|
10022
|
+
if (!exists)
|
|
10023
|
+
all.push(gp);
|
|
10024
|
+
}
|
|
10025
|
+
if (all.length === 0)
|
|
10026
|
+
return "";
|
|
10027
|
+
const lines = [
|
|
10028
|
+
"Repeated workflow patterns detected (consider creating custom tools with create_tool):"
|
|
10029
|
+
];
|
|
10030
|
+
for (const p of all.slice(0, 3)) {
|
|
10031
|
+
const steps = p.pattern.map((e) => e.tool).join(" \u2192 ");
|
|
10032
|
+
lines.push(`- [${p.occurrences}x, ${p.scope}] ${steps}`);
|
|
10033
|
+
}
|
|
10034
|
+
return lines.join("\n");
|
|
10035
|
+
} catch {
|
|
10036
|
+
return "";
|
|
10037
|
+
}
|
|
10038
|
+
}
|
|
9189
10039
|
function buildProjectContext(repoRoot, stores) {
|
|
9190
10040
|
return {
|
|
9191
10041
|
projectInstructions: loadProjectFiles(repoRoot),
|
|
@@ -9195,7 +10045,8 @@ function buildProjectContext(repoRoot, stores) {
|
|
|
9195
10045
|
sessionHistory: loadSessionHistory(repoRoot),
|
|
9196
10046
|
environment: getEnvironment(repoRoot),
|
|
9197
10047
|
taskMemories: stores?.taskMemoryStore ? loadTaskMemories(repoRoot, stores.taskMemoryStore) : "",
|
|
9198
|
-
failurePatterns: stores?.failureStore ? loadFailurePatterns(stores.failureStore) : ""
|
|
10048
|
+
failurePatterns: stores?.failureStore ? loadFailurePatterns(stores.failureStore) : "",
|
|
10049
|
+
patternSuggestions: stores?.toolPatternStore ? loadPatternSuggestions(repoRoot, stores.toolPatternStore) : ""
|
|
9199
10050
|
};
|
|
9200
10051
|
}
|
|
9201
10052
|
function formatContextForPrompt(ctx) {
|
|
@@ -9243,6 +10094,13 @@ Use this history to avoid re-doing completed work and to learn from past approac
|
|
|
9243
10094
|
${ctx.failurePatterns}
|
|
9244
10095
|
|
|
9245
10096
|
Avoid approaches that led to these failures. If you encounter these errors, try a different strategy.`);
|
|
10097
|
+
}
|
|
10098
|
+
if (ctx.patternSuggestions) {
|
|
10099
|
+
sections.push(`## Tool Creation Suggestions
|
|
10100
|
+
|
|
10101
|
+
${ctx.patternSuggestions}
|
|
10102
|
+
|
|
10103
|
+
These patterns have been repeated 3+ times. Consider using create_tool to automate them.`);
|
|
9246
10104
|
}
|
|
9247
10105
|
return sections.join("\n\n");
|
|
9248
10106
|
}
|
|
@@ -9587,6 +10445,189 @@ var init_validationStore = __esm({
|
|
|
9587
10445
|
}
|
|
9588
10446
|
});
|
|
9589
10447
|
|
|
10448
|
+
// packages/memory/dist/toolPatternStore.js
|
|
10449
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
10450
|
+
var ToolPatternStore;
|
|
10451
|
+
var init_toolPatternStore = __esm({
|
|
10452
|
+
"packages/memory/dist/toolPatternStore.js"() {
|
|
10453
|
+
"use strict";
|
|
10454
|
+
ToolPatternStore = class {
|
|
10455
|
+
db;
|
|
10456
|
+
constructor(db) {
|
|
10457
|
+
this.db = db;
|
|
10458
|
+
this.ensureTable();
|
|
10459
|
+
}
|
|
10460
|
+
ensureTable() {
|
|
10461
|
+
this.db.exec(`
|
|
10462
|
+
CREATE TABLE IF NOT EXISTS tool_sequences (
|
|
10463
|
+
id TEXT PRIMARY KEY,
|
|
10464
|
+
task_id TEXT NOT NULL,
|
|
10465
|
+
session_id TEXT NOT NULL,
|
|
10466
|
+
repo_root TEXT NOT NULL,
|
|
10467
|
+
sequence TEXT NOT NULL DEFAULT '[]',
|
|
10468
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
10469
|
+
);
|
|
10470
|
+
|
|
10471
|
+
CREATE INDEX IF NOT EXISTS idx_tool_sequences_repo
|
|
10472
|
+
ON tool_sequences (repo_root);
|
|
10473
|
+
|
|
10474
|
+
CREATE INDEX IF NOT EXISTS idx_tool_sequences_created
|
|
10475
|
+
ON tool_sequences (created_at);
|
|
10476
|
+
|
|
10477
|
+
CREATE TABLE IF NOT EXISTS custom_tools (
|
|
10478
|
+
name TEXT PRIMARY KEY,
|
|
10479
|
+
definition TEXT NOT NULL,
|
|
10480
|
+
scope TEXT NOT NULL DEFAULT 'project',
|
|
10481
|
+
repo_root TEXT,
|
|
10482
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
|
|
10483
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
10484
|
+
);
|
|
10485
|
+
|
|
10486
|
+
CREATE INDEX IF NOT EXISTS idx_custom_tools_scope
|
|
10487
|
+
ON custom_tools (scope);
|
|
10488
|
+
`);
|
|
10489
|
+
}
|
|
10490
|
+
/** Record a tool call sequence from a completed task */
|
|
10491
|
+
insert(input) {
|
|
10492
|
+
const id = randomUUID3();
|
|
10493
|
+
this.db.prepare(`
|
|
10494
|
+
INSERT INTO tool_sequences (id, task_id, session_id, repo_root, sequence)
|
|
10495
|
+
VALUES (?, ?, ?, ?, ?)
|
|
10496
|
+
`).run(id, input.taskId, input.sessionId, input.repoRoot, JSON.stringify(input.sequence));
|
|
10497
|
+
return id;
|
|
10498
|
+
}
|
|
10499
|
+
/** Get all sequences for a given repo */
|
|
10500
|
+
listByRepo(repoRoot) {
|
|
10501
|
+
const rows = this.db.prepare(`
|
|
10502
|
+
SELECT * FROM tool_sequences WHERE repo_root = ? ORDER BY created_at DESC
|
|
10503
|
+
`).all(repoRoot);
|
|
10504
|
+
return rows.map(this.hydrate);
|
|
10505
|
+
}
|
|
10506
|
+
/** Get recent sequences globally */
|
|
10507
|
+
recent(limit = 20) {
|
|
10508
|
+
const rows = this.db.prepare(`
|
|
10509
|
+
SELECT * FROM tool_sequences ORDER BY created_at DESC LIMIT ?
|
|
10510
|
+
`).all(limit);
|
|
10511
|
+
return rows.map(this.hydrate);
|
|
10512
|
+
}
|
|
10513
|
+
/**
|
|
10514
|
+
* Detect repeated patterns across recorded sequences.
|
|
10515
|
+
* Looks for subsequences of length >= minLen that appear in >= minOccurrences tasks.
|
|
10516
|
+
*
|
|
10517
|
+
* Algorithm:
|
|
10518
|
+
* 1. Extract all contiguous subsequences of length minLen..maxLen
|
|
10519
|
+
* 2. Fingerprint each subsequence (tool names + arg keys)
|
|
10520
|
+
* 3. Count occurrences across tasks
|
|
10521
|
+
* 4. Return patterns that meet the threshold
|
|
10522
|
+
*/
|
|
10523
|
+
detectPatterns(opts = {}) {
|
|
10524
|
+
const minOccurrences = opts.minOccurrences ?? 3;
|
|
10525
|
+
const minLen = opts.minLen ?? 3;
|
|
10526
|
+
const maxLen = opts.maxLen ?? 10;
|
|
10527
|
+
const sequences = opts.repoRoot ? this.listByRepo(opts.repoRoot) : this.recent(50);
|
|
10528
|
+
if (sequences.length < minOccurrences)
|
|
10529
|
+
return [];
|
|
10530
|
+
const fingerprints = /* @__PURE__ */ new Map();
|
|
10531
|
+
for (const seq of sequences) {
|
|
10532
|
+
const entries = seq.sequence;
|
|
10533
|
+
for (let len = minLen; len <= Math.min(maxLen, entries.length); len++) {
|
|
10534
|
+
for (let start = 0; start <= entries.length - len; start++) {
|
|
10535
|
+
const sub = entries.slice(start, start + len);
|
|
10536
|
+
const fp = this.fingerprint(sub);
|
|
10537
|
+
if (!fingerprints.has(fp)) {
|
|
10538
|
+
fingerprints.set(fp, { pattern: sub, taskIds: /* @__PURE__ */ new Set(), repos: /* @__PURE__ */ new Set() });
|
|
10539
|
+
}
|
|
10540
|
+
const entry = fingerprints.get(fp);
|
|
10541
|
+
entry.taskIds.add(seq.taskId);
|
|
10542
|
+
entry.repos.add(seq.repoRoot);
|
|
10543
|
+
}
|
|
10544
|
+
}
|
|
10545
|
+
}
|
|
10546
|
+
const suggestions = [];
|
|
10547
|
+
for (const [, data] of fingerprints) {
|
|
10548
|
+
if (data.taskIds.size >= minOccurrences) {
|
|
10549
|
+
suggestions.push({
|
|
10550
|
+
pattern: data.pattern,
|
|
10551
|
+
occurrences: data.taskIds.size,
|
|
10552
|
+
scope: data.repos.size === 1 ? "project" : "global",
|
|
10553
|
+
taskIds: Array.from(data.taskIds)
|
|
10554
|
+
});
|
|
10555
|
+
}
|
|
10556
|
+
}
|
|
10557
|
+
suggestions.sort((a, b) => b.occurrences - a.occurrences || b.pattern.length - a.pattern.length);
|
|
10558
|
+
return this.deduplicatePatterns(suggestions);
|
|
10559
|
+
}
|
|
10560
|
+
/** Create a fingerprint for a tool call subsequence */
|
|
10561
|
+
fingerprint(entries) {
|
|
10562
|
+
return entries.map((e) => `${e.tool}(${e.argKeys.sort().join(",")})`).join(" -> ");
|
|
10563
|
+
}
|
|
10564
|
+
/** Remove patterns that are strict subsets of longer detected patterns */
|
|
10565
|
+
deduplicatePatterns(suggestions) {
|
|
10566
|
+
const result = [];
|
|
10567
|
+
const kept = /* @__PURE__ */ new Set();
|
|
10568
|
+
for (let i = 0; i < suggestions.length; i++) {
|
|
10569
|
+
const fpI = this.fingerprint(suggestions[i].pattern);
|
|
10570
|
+
let isSubset = false;
|
|
10571
|
+
for (let j = 0; j < suggestions.length; j++) {
|
|
10572
|
+
if (i === j)
|
|
10573
|
+
continue;
|
|
10574
|
+
const fpJ = this.fingerprint(suggestions[j].pattern);
|
|
10575
|
+
if (fpJ.includes(fpI) && fpJ.length > fpI.length) {
|
|
10576
|
+
if (suggestions[j].occurrences >= suggestions[i].occurrences) {
|
|
10577
|
+
isSubset = true;
|
|
10578
|
+
break;
|
|
10579
|
+
}
|
|
10580
|
+
}
|
|
10581
|
+
}
|
|
10582
|
+
if (!isSubset) {
|
|
10583
|
+
result.push(suggestions[i]);
|
|
10584
|
+
kept.add(i);
|
|
10585
|
+
}
|
|
10586
|
+
}
|
|
10587
|
+
return result.slice(0, 5);
|
|
10588
|
+
}
|
|
10589
|
+
// -- Custom tool tracking in DB -----------------------------------------
|
|
10590
|
+
/** Record a custom tool in the database */
|
|
10591
|
+
saveCustomTool(name, definition, scope, repoRoot) {
|
|
10592
|
+
this.db.prepare(`
|
|
10593
|
+
INSERT OR REPLACE INTO custom_tools (name, definition, scope, repo_root, updated_at)
|
|
10594
|
+
VALUES (?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
10595
|
+
`).run(name, definition, scope, repoRoot ?? null);
|
|
10596
|
+
}
|
|
10597
|
+
/** List all custom tools */
|
|
10598
|
+
listCustomTools(scope, repoRoot) {
|
|
10599
|
+
let query = "SELECT name, scope, created_at FROM custom_tools";
|
|
10600
|
+
const params = [];
|
|
10601
|
+
if (scope && repoRoot) {
|
|
10602
|
+
query += " WHERE (scope = ? AND repo_root = ?) OR scope = 'global'";
|
|
10603
|
+
params.push(scope, repoRoot);
|
|
10604
|
+
} else if (scope) {
|
|
10605
|
+
query += " WHERE scope = ?";
|
|
10606
|
+
params.push(scope);
|
|
10607
|
+
}
|
|
10608
|
+
query += " ORDER BY created_at DESC";
|
|
10609
|
+
const rows = this.db.prepare(query).all(...params);
|
|
10610
|
+
return rows.map((r) => ({
|
|
10611
|
+
name: r["name"],
|
|
10612
|
+
scope: r["scope"],
|
|
10613
|
+
createdAt: r["created_at"]
|
|
10614
|
+
}));
|
|
10615
|
+
}
|
|
10616
|
+
// -- Helpers -------------------------------------------------------------
|
|
10617
|
+
hydrate(row) {
|
|
10618
|
+
return {
|
|
10619
|
+
id: row["id"],
|
|
10620
|
+
taskId: row["task_id"],
|
|
10621
|
+
sessionId: row["session_id"],
|
|
10622
|
+
repoRoot: row["repo_root"],
|
|
10623
|
+
sequence: JSON.parse(row["sequence"] || "[]"),
|
|
10624
|
+
createdAt: row["created_at"]
|
|
10625
|
+
};
|
|
10626
|
+
}
|
|
10627
|
+
};
|
|
10628
|
+
}
|
|
10629
|
+
});
|
|
10630
|
+
|
|
9590
10631
|
// packages/memory/dist/index.js
|
|
9591
10632
|
var init_dist6 = __esm({
|
|
9592
10633
|
"packages/memory/dist/index.js"() {
|
|
@@ -9598,6 +10639,7 @@ var init_dist6 = __esm({
|
|
|
9598
10639
|
init_patchHistoryStore();
|
|
9599
10640
|
init_failureStore();
|
|
9600
10641
|
init_validationStore();
|
|
10642
|
+
init_toolPatternStore();
|
|
9601
10643
|
}
|
|
9602
10644
|
});
|
|
9603
10645
|
|
|
@@ -9885,19 +10927,19 @@ var init_carousel = __esm({
|
|
|
9885
10927
|
});
|
|
9886
10928
|
|
|
9887
10929
|
// packages/cli/dist/tui/voice.js
|
|
9888
|
-
import { existsSync as
|
|
9889
|
-
import { join as
|
|
9890
|
-
import { homedir as
|
|
10930
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, readFileSync as readFileSync10, unlinkSync } from "node:fs";
|
|
10931
|
+
import { join as join17 } from "node:path";
|
|
10932
|
+
import { homedir as homedir7, tmpdir as tmpdir2, platform as platform2 } from "node:os";
|
|
9891
10933
|
import { execSync as execSync10, spawn as nodeSpawn } from "node:child_process";
|
|
9892
10934
|
import { createRequire } from "node:module";
|
|
9893
10935
|
function modelDir(id) {
|
|
9894
|
-
return
|
|
10936
|
+
return join17(MODELS_DIR, id);
|
|
9895
10937
|
}
|
|
9896
10938
|
function modelOnnxPath(id) {
|
|
9897
|
-
return
|
|
10939
|
+
return join17(modelDir(id), "model.onnx");
|
|
9898
10940
|
}
|
|
9899
10941
|
function modelConfigPath(id) {
|
|
9900
|
-
return
|
|
10942
|
+
return join17(modelDir(id), "config.json");
|
|
9901
10943
|
}
|
|
9902
10944
|
function describeToolCall(toolName, args) {
|
|
9903
10945
|
const path = args["path"];
|
|
@@ -9909,6 +10951,8 @@ function describeToolCall(toolName, args) {
|
|
|
9909
10951
|
return `Writing ${file}`;
|
|
9910
10952
|
case "file_edit":
|
|
9911
10953
|
return `Editing ${file}`;
|
|
10954
|
+
case "file_patch":
|
|
10955
|
+
return `Patching ${file}`;
|
|
9912
10956
|
case "shell": {
|
|
9913
10957
|
const cmd = String(args["command"] ?? "");
|
|
9914
10958
|
if (/npm\s+test|vitest|jest|mocha/.test(cmd))
|
|
@@ -9971,6 +11015,10 @@ function describeToolCall(toolName, args) {
|
|
|
9971
11015
|
return `Taking screenshot`;
|
|
9972
11016
|
case "ocr":
|
|
9973
11017
|
return `Extracting text from image`;
|
|
11018
|
+
case "create_tool":
|
|
11019
|
+
return `Creating custom tool`;
|
|
11020
|
+
case "manage_tools":
|
|
11021
|
+
return `Managing custom tools`;
|
|
9974
11022
|
default:
|
|
9975
11023
|
return `Using ${toolName}`;
|
|
9976
11024
|
}
|
|
@@ -10006,8 +11054,8 @@ var init_voice = __esm({
|
|
|
10006
11054
|
configUrl: "https://raw.githubusercontent.com/robit-man/combine_overwatch_onnx/main/overwatch.onnx.json"
|
|
10007
11055
|
}
|
|
10008
11056
|
};
|
|
10009
|
-
VOICE_DIR =
|
|
10010
|
-
MODELS_DIR =
|
|
11057
|
+
VOICE_DIR = join17(homedir7(), ".open-agents", "voice");
|
|
11058
|
+
MODELS_DIR = join17(VOICE_DIR, "models");
|
|
10011
11059
|
VoiceEngine = class {
|
|
10012
11060
|
enabled = false;
|
|
10013
11061
|
modelId = "glados";
|
|
@@ -10166,7 +11214,7 @@ var init_voice = __esm({
|
|
|
10166
11214
|
const audioData = result["output"].data;
|
|
10167
11215
|
if (audioData.length === 0)
|
|
10168
11216
|
return;
|
|
10169
|
-
const wavPath =
|
|
11217
|
+
const wavPath = join17(tmpdir2(), `oa-voice-${Date.now()}.wav`);
|
|
10170
11218
|
this.writeWav(audioData, this.config.audio.sample_rate, wavPath);
|
|
10171
11219
|
await this.playWav(wavPath);
|
|
10172
11220
|
try {
|
|
@@ -10246,7 +11294,7 @@ var init_voice = __esm({
|
|
|
10246
11294
|
buffer.write("data", 36);
|
|
10247
11295
|
buffer.writeUInt32LE(dataSize, 40);
|
|
10248
11296
|
Buffer.from(int16.buffer, int16.byteOffset, int16.byteLength).copy(buffer, 44);
|
|
10249
|
-
|
|
11297
|
+
writeFileSync6(path, buffer);
|
|
10250
11298
|
}
|
|
10251
11299
|
// -------------------------------------------------------------------------
|
|
10252
11300
|
// Audio playback (system default speakers)
|
|
@@ -10255,7 +11303,7 @@ var init_voice = __esm({
|
|
|
10255
11303
|
const cmd = this.getPlayCommand(path);
|
|
10256
11304
|
if (!cmd)
|
|
10257
11305
|
return;
|
|
10258
|
-
return new Promise((
|
|
11306
|
+
return new Promise((resolve15) => {
|
|
10259
11307
|
const child = nodeSpawn(cmd[0], cmd.slice(1), {
|
|
10260
11308
|
stdio: "ignore",
|
|
10261
11309
|
detached: false
|
|
@@ -10264,12 +11312,12 @@ var init_voice = __esm({
|
|
|
10264
11312
|
child.on("close", () => {
|
|
10265
11313
|
if (this.currentPlayback === child)
|
|
10266
11314
|
this.currentPlayback = null;
|
|
10267
|
-
|
|
11315
|
+
resolve15();
|
|
10268
11316
|
});
|
|
10269
11317
|
child.on("error", () => {
|
|
10270
11318
|
if (this.currentPlayback === child)
|
|
10271
11319
|
this.currentPlayback = null;
|
|
10272
|
-
|
|
11320
|
+
resolve15();
|
|
10273
11321
|
});
|
|
10274
11322
|
setTimeout(() => {
|
|
10275
11323
|
if (this.currentPlayback === child) {
|
|
@@ -10279,7 +11327,7 @@ var init_voice = __esm({
|
|
|
10279
11327
|
}
|
|
10280
11328
|
this.currentPlayback = null;
|
|
10281
11329
|
}
|
|
10282
|
-
|
|
11330
|
+
resolve15();
|
|
10283
11331
|
}, 15e3);
|
|
10284
11332
|
});
|
|
10285
11333
|
}
|
|
@@ -10318,30 +11366,30 @@ var init_voice = __esm({
|
|
|
10318
11366
|
async ensureRuntime() {
|
|
10319
11367
|
if (this.ort)
|
|
10320
11368
|
return;
|
|
10321
|
-
|
|
10322
|
-
const pkgPath =
|
|
11369
|
+
mkdirSync6(VOICE_DIR, { recursive: true });
|
|
11370
|
+
const pkgPath = join17(VOICE_DIR, "package.json");
|
|
10323
11371
|
const expectedDeps = {
|
|
10324
11372
|
"onnxruntime-node": "^1.21.0",
|
|
10325
11373
|
"phonemizer": "^1.2.1"
|
|
10326
11374
|
};
|
|
10327
|
-
if (
|
|
11375
|
+
if (existsSync12(pkgPath)) {
|
|
10328
11376
|
try {
|
|
10329
|
-
const existing = JSON.parse(
|
|
11377
|
+
const existing = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
10330
11378
|
if (!existing.dependencies?.["phonemizer"]) {
|
|
10331
11379
|
existing.dependencies = { ...existing.dependencies, ...expectedDeps };
|
|
10332
|
-
|
|
11380
|
+
writeFileSync6(pkgPath, JSON.stringify(existing, null, 2));
|
|
10333
11381
|
}
|
|
10334
11382
|
} catch {
|
|
10335
11383
|
}
|
|
10336
11384
|
}
|
|
10337
|
-
if (!
|
|
10338
|
-
|
|
11385
|
+
if (!existsSync12(pkgPath)) {
|
|
11386
|
+
writeFileSync6(pkgPath, JSON.stringify({
|
|
10339
11387
|
name: "open-agents-voice",
|
|
10340
11388
|
private: true,
|
|
10341
11389
|
dependencies: expectedDeps
|
|
10342
11390
|
}, null, 2));
|
|
10343
11391
|
}
|
|
10344
|
-
const voiceRequire = createRequire(
|
|
11392
|
+
const voiceRequire = createRequire(join17(VOICE_DIR, "index.js"));
|
|
10345
11393
|
try {
|
|
10346
11394
|
this.ort = voiceRequire("onnxruntime-node");
|
|
10347
11395
|
} catch {
|
|
@@ -10387,18 +11435,18 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
|
10387
11435
|
const dir = modelDir(id);
|
|
10388
11436
|
const onnxPath = modelOnnxPath(id);
|
|
10389
11437
|
const configPath = modelConfigPath(id);
|
|
10390
|
-
if (
|
|
11438
|
+
if (existsSync12(onnxPath) && existsSync12(configPath))
|
|
10391
11439
|
return;
|
|
10392
|
-
|
|
10393
|
-
if (!
|
|
11440
|
+
mkdirSync6(dir, { recursive: true });
|
|
11441
|
+
if (!existsSync12(configPath)) {
|
|
10394
11442
|
renderInfo(`Downloading ${model.label} voice config...`);
|
|
10395
11443
|
const configResp = await fetch(model.configUrl);
|
|
10396
11444
|
if (!configResp.ok)
|
|
10397
11445
|
throw new Error(`Failed to download config: HTTP ${configResp.status}`);
|
|
10398
11446
|
const configText = await configResp.text();
|
|
10399
|
-
|
|
11447
|
+
writeFileSync6(configPath, configText);
|
|
10400
11448
|
}
|
|
10401
|
-
if (!
|
|
11449
|
+
if (!existsSync12(onnxPath)) {
|
|
10402
11450
|
renderInfo(`Downloading ${model.label} voice model (this may take a minute)...`);
|
|
10403
11451
|
const onnxResp = await fetch(model.onnxUrl);
|
|
10404
11452
|
if (!onnxResp.ok)
|
|
@@ -10422,7 +11470,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
|
10422
11470
|
}
|
|
10423
11471
|
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
10424
11472
|
const fullBuffer = Buffer.concat(chunks);
|
|
10425
|
-
|
|
11473
|
+
writeFileSync6(onnxPath, fullBuffer);
|
|
10426
11474
|
renderInfo(`${model.label} model downloaded (${formatBytes2(fullBuffer.length)}).`);
|
|
10427
11475
|
}
|
|
10428
11476
|
}
|
|
@@ -10434,10 +11482,10 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
|
10434
11482
|
throw new Error("ONNX runtime not loaded");
|
|
10435
11483
|
const onnxPath = modelOnnxPath(this.modelId);
|
|
10436
11484
|
const configPath = modelConfigPath(this.modelId);
|
|
10437
|
-
if (!
|
|
11485
|
+
if (!existsSync12(onnxPath) || !existsSync12(configPath)) {
|
|
10438
11486
|
throw new Error(`Model files not found for ${this.modelId}`);
|
|
10439
11487
|
}
|
|
10440
|
-
this.config = JSON.parse(
|
|
11488
|
+
this.config = JSON.parse(readFileSync10(configPath, "utf8"));
|
|
10441
11489
|
renderInfo("Loading voice model...");
|
|
10442
11490
|
this.session = await this.ort.InferenceSession.create(onnxPath, {
|
|
10443
11491
|
executionProviders: ["cpu"],
|
|
@@ -10712,23 +11760,23 @@ var init_stream_renderer = __esm({
|
|
|
10712
11760
|
// packages/cli/dist/tui/interactive.js
|
|
10713
11761
|
import * as readline2 from "node:readline";
|
|
10714
11762
|
import { cwd } from "node:process";
|
|
10715
|
-
import { resolve as
|
|
11763
|
+
import { resolve as resolve12, join as join18, dirname as dirname2 } from "node:path";
|
|
10716
11764
|
import { createRequire as createRequire2 } from "node:module";
|
|
10717
11765
|
import { fileURLToPath } from "node:url";
|
|
10718
|
-
import { readFileSync as
|
|
10719
|
-
import { existsSync as
|
|
11766
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
11767
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
10720
11768
|
import { extname as extname5 } from "node:path";
|
|
10721
11769
|
function getVersion() {
|
|
10722
11770
|
try {
|
|
10723
11771
|
const require2 = createRequire2(import.meta.url);
|
|
10724
|
-
const thisDir =
|
|
11772
|
+
const thisDir = dirname2(fileURLToPath(import.meta.url));
|
|
10725
11773
|
const candidates = [
|
|
10726
|
-
|
|
10727
|
-
|
|
10728
|
-
|
|
11774
|
+
join18(thisDir, "..", "package.json"),
|
|
11775
|
+
join18(thisDir, "..", "..", "package.json"),
|
|
11776
|
+
join18(thisDir, "..", "..", "..", "package.json")
|
|
10729
11777
|
];
|
|
10730
11778
|
for (const pkgPath of candidates) {
|
|
10731
|
-
if (
|
|
11779
|
+
if (existsSync13(pkgPath)) {
|
|
10732
11780
|
const pkg = require2(pkgPath);
|
|
10733
11781
|
if (pkg.name === "open-agents-ai" || pkg.name === "@open-agents/cli") {
|
|
10734
11782
|
return pkg.version ?? "0.0.0";
|
|
@@ -10785,6 +11833,7 @@ function buildTools(repoRoot, config) {
|
|
|
10785
11833
|
new AiwgWorkflowTool(repoRoot),
|
|
10786
11834
|
// Advanced tools (mirroring Claude Code capabilities)
|
|
10787
11835
|
new BatchEditTool(repoRoot),
|
|
11836
|
+
new FilePatchTool(repoRoot),
|
|
10788
11837
|
new CodebaseMapTool(repoRoot),
|
|
10789
11838
|
new DiagnosticTool(repoRoot),
|
|
10790
11839
|
new GitInfoTool(repoRoot),
|
|
@@ -10796,7 +11845,12 @@ function buildTools(repoRoot, config) {
|
|
|
10796
11845
|
// Image tools (multimodal context)
|
|
10797
11846
|
new ImageReadTool(repoRoot),
|
|
10798
11847
|
new ScreenshotTool(repoRoot),
|
|
10799
|
-
new OCRTool(repoRoot)
|
|
11848
|
+
new OCRTool(repoRoot),
|
|
11849
|
+
// Custom tool system (agent-created tools)
|
|
11850
|
+
new CreateToolTool(repoRoot),
|
|
11851
|
+
new ManageToolsTool(repoRoot),
|
|
11852
|
+
// Load agent-created custom tools from .oa/tools/ and ~/.open-agents/tools/
|
|
11853
|
+
...buildCustomTools(repoRoot)
|
|
10800
11854
|
];
|
|
10801
11855
|
return [
|
|
10802
11856
|
...executionTools.map(adaptTool),
|
|
@@ -10893,9 +11947,14 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
|
|
|
10893
11947
|
});
|
|
10894
11948
|
runner.registerTools(buildTools(repoRoot, config));
|
|
10895
11949
|
const filesTouched = /* @__PURE__ */ new Set();
|
|
11950
|
+
const toolSequence = [];
|
|
10896
11951
|
runner.onEvent((event) => {
|
|
10897
11952
|
switch (event.type) {
|
|
10898
11953
|
case "tool_call":
|
|
11954
|
+
toolSequence.push({
|
|
11955
|
+
tool: event.toolName ?? "unknown",
|
|
11956
|
+
argKeys: Object.keys(event.toolArgs ?? {})
|
|
11957
|
+
});
|
|
10899
11958
|
if (event.toolArgs?.path && typeof event.toolArgs.path === "string") {
|
|
10900
11959
|
const name = event.toolName ?? "";
|
|
10901
11960
|
if (name === "file_write" || name === "file_edit" || name === "batch_edit") {
|
|
@@ -10993,11 +12052,22 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
|
|
|
10993
12052
|
} catch {
|
|
10994
12053
|
}
|
|
10995
12054
|
}
|
|
12055
|
+
if (taskStores?.toolPatternStore && toolSequence.length > 0) {
|
|
12056
|
+
try {
|
|
12057
|
+
taskStores.toolPatternStore.insert({
|
|
12058
|
+
taskId: sessionId,
|
|
12059
|
+
sessionId,
|
|
12060
|
+
repoRoot,
|
|
12061
|
+
sequence: toolSequence
|
|
12062
|
+
});
|
|
12063
|
+
} catch {
|
|
12064
|
+
}
|
|
12065
|
+
}
|
|
10996
12066
|
});
|
|
10997
12067
|
return { runner, promise };
|
|
10998
12068
|
}
|
|
10999
12069
|
async function startInteractive(config, repoPath) {
|
|
11000
|
-
const repoRoot =
|
|
12070
|
+
const repoRoot = resolve12(repoPath ?? cwd());
|
|
11001
12071
|
const isResumed = !!process.env.__OA_RESUMED;
|
|
11002
12072
|
if (isResumed) {
|
|
11003
12073
|
delete process.env.__OA_RESUMED;
|
|
@@ -11007,12 +12077,14 @@ async function startInteractive(config, repoPath) {
|
|
|
11007
12077
|
let memoryDb = null;
|
|
11008
12078
|
let taskMemoryStore = null;
|
|
11009
12079
|
let failureStore = null;
|
|
12080
|
+
let toolPatternStore = null;
|
|
11010
12081
|
let contextStores;
|
|
11011
12082
|
try {
|
|
11012
12083
|
memoryDb = initDb(config.dbPath);
|
|
11013
12084
|
taskMemoryStore = new TaskMemoryStore(memoryDb);
|
|
11014
12085
|
failureStore = new FailureStore(memoryDb);
|
|
11015
|
-
|
|
12086
|
+
toolPatternStore = new ToolPatternStore(memoryDb);
|
|
12087
|
+
contextStores = { taskMemoryStore, failureStore, toolPatternStore };
|
|
11016
12088
|
} catch {
|
|
11017
12089
|
}
|
|
11018
12090
|
if (savedSettings.model)
|
|
@@ -11124,6 +12196,7 @@ async function startInteractive(config, repoPath) {
|
|
|
11124
12196
|
get config() {
|
|
11125
12197
|
return currentConfig;
|
|
11126
12198
|
},
|
|
12199
|
+
repoRoot,
|
|
11127
12200
|
setModel(model) {
|
|
11128
12201
|
currentConfig = { ...currentConfig, model };
|
|
11129
12202
|
},
|
|
@@ -11208,12 +12281,12 @@ ${c2.dim("Goodbye!")}
|
|
|
11208
12281
|
}
|
|
11209
12282
|
}
|
|
11210
12283
|
const cleanPath = input.replace(/^['"]|['"]$/g, "").trim();
|
|
11211
|
-
const isImage = isImagePath(cleanPath) &&
|
|
12284
|
+
const isImage = isImagePath(cleanPath) && existsSync13(resolve12(repoRoot, cleanPath));
|
|
11212
12285
|
if (activeTask) {
|
|
11213
12286
|
if (isImage) {
|
|
11214
12287
|
try {
|
|
11215
|
-
const imgPath =
|
|
11216
|
-
const imgBuffer =
|
|
12288
|
+
const imgPath = resolve12(repoRoot, cleanPath);
|
|
12289
|
+
const imgBuffer = readFileSync11(imgPath);
|
|
11217
12290
|
const base64 = imgBuffer.toString("base64");
|
|
11218
12291
|
const ext = extname5(cleanPath).toLowerCase();
|
|
11219
12292
|
const mime = ext === ".png" ? "image/png" : ext === ".gif" ? "image/gif" : ext === ".webp" ? "image/webp" : "image/jpeg";
|
|
@@ -11250,7 +12323,8 @@ ${c2.dim("Goodbye!")}
|
|
|
11250
12323
|
}, {
|
|
11251
12324
|
contextStores,
|
|
11252
12325
|
taskMemoryStore: taskMemoryStore ?? void 0,
|
|
11253
|
-
failureStore: failureStore ?? void 0
|
|
12326
|
+
failureStore: failureStore ?? void 0,
|
|
12327
|
+
toolPatternStore: toolPatternStore ?? void 0
|
|
11254
12328
|
}, bruteForceEnabled);
|
|
11255
12329
|
activeTask = task;
|
|
11256
12330
|
showPrompt();
|
|
@@ -11322,7 +12396,7 @@ ${c2.dim("(Use /quit to exit)")}
|
|
|
11322
12396
|
});
|
|
11323
12397
|
}
|
|
11324
12398
|
async function runWithTUI(task, config, repoPath) {
|
|
11325
|
-
const repoRoot =
|
|
12399
|
+
const repoRoot = resolve12(repoPath ?? cwd());
|
|
11326
12400
|
const needsSetup = isFirstRun() || !await isModelAvailable(config);
|
|
11327
12401
|
if (needsSetup && config.backendType === "ollama") {
|
|
11328
12402
|
const setupModel = await runSetupWizard(config);
|
|
@@ -11402,9 +12476,9 @@ var init_run = __esm({
|
|
|
11402
12476
|
// packages/indexer/dist/codebase-indexer.js
|
|
11403
12477
|
import { glob } from "glob";
|
|
11404
12478
|
import ignore from "ignore";
|
|
11405
|
-
import { readFile as
|
|
12479
|
+
import { readFile as readFile9, stat as stat2 } from "node:fs/promises";
|
|
11406
12480
|
import { createHash } from "node:crypto";
|
|
11407
|
-
import { join as
|
|
12481
|
+
import { join as join19, relative as relative3, extname as extname6, basename as basename4 } from "node:path";
|
|
11408
12482
|
var DEFAULT_EXCLUDE, LANGUAGE_MAP, CodebaseIndexer;
|
|
11409
12483
|
var init_codebase_indexer = __esm({
|
|
11410
12484
|
"packages/indexer/dist/codebase-indexer.js"() {
|
|
@@ -11448,7 +12522,7 @@ var init_codebase_indexer = __esm({
|
|
|
11448
12522
|
const ig = ignore.default();
|
|
11449
12523
|
if (this.config.respectGitignore) {
|
|
11450
12524
|
try {
|
|
11451
|
-
const gitignoreContent = await
|
|
12525
|
+
const gitignoreContent = await readFile9(join19(this.config.rootDir, ".gitignore"), "utf-8");
|
|
11452
12526
|
ig.add(gitignoreContent);
|
|
11453
12527
|
} catch {
|
|
11454
12528
|
}
|
|
@@ -11463,12 +12537,12 @@ var init_codebase_indexer = __esm({
|
|
|
11463
12537
|
for (const relativePath of files) {
|
|
11464
12538
|
if (ig.ignores(relativePath))
|
|
11465
12539
|
continue;
|
|
11466
|
-
const fullPath =
|
|
12540
|
+
const fullPath = join19(this.config.rootDir, relativePath);
|
|
11467
12541
|
try {
|
|
11468
12542
|
const fileStat = await stat2(fullPath);
|
|
11469
12543
|
if (fileStat.size > this.config.maxFileSize)
|
|
11470
12544
|
continue;
|
|
11471
|
-
const content = await
|
|
12545
|
+
const content = await readFile9(fullPath);
|
|
11472
12546
|
const hash = createHash("sha256").update(content).digest("hex");
|
|
11473
12547
|
const ext = extname6(relativePath);
|
|
11474
12548
|
indexed.push({
|
|
@@ -11509,7 +12583,7 @@ var init_codebase_indexer = __esm({
|
|
|
11509
12583
|
if (!child) {
|
|
11510
12584
|
child = {
|
|
11511
12585
|
name: part,
|
|
11512
|
-
path:
|
|
12586
|
+
path: join19(current.path, part),
|
|
11513
12587
|
type: "directory",
|
|
11514
12588
|
children: []
|
|
11515
12589
|
};
|
|
@@ -11583,14 +12657,14 @@ var index_repo_exports = {};
|
|
|
11583
12657
|
__export(index_repo_exports, {
|
|
11584
12658
|
indexRepoCommand: () => indexRepoCommand
|
|
11585
12659
|
});
|
|
11586
|
-
import { resolve as
|
|
11587
|
-
import { existsSync as
|
|
12660
|
+
import { resolve as resolve13 } from "node:path";
|
|
12661
|
+
import { existsSync as existsSync14, statSync as statSync6 } from "node:fs";
|
|
11588
12662
|
import { cwd as cwd2 } from "node:process";
|
|
11589
12663
|
async function indexRepoCommand(opts, _config) {
|
|
11590
|
-
const repoRoot =
|
|
12664
|
+
const repoRoot = resolve13(opts.repoPath ?? cwd2());
|
|
11591
12665
|
printHeader("Index Repository");
|
|
11592
12666
|
printInfo(`Indexing: ${repoRoot}`);
|
|
11593
|
-
if (!
|
|
12667
|
+
if (!existsSync14(repoRoot)) {
|
|
11594
12668
|
printError(`Path does not exist: ${repoRoot}`);
|
|
11595
12669
|
process.exit(1);
|
|
11596
12670
|
}
|
|
@@ -11836,8 +12910,8 @@ var config_exports = {};
|
|
|
11836
12910
|
__export(config_exports, {
|
|
11837
12911
|
configCommand: () => configCommand
|
|
11838
12912
|
});
|
|
11839
|
-
import { join as
|
|
11840
|
-
import { homedir as
|
|
12913
|
+
import { join as join20, resolve as resolve14 } from "node:path";
|
|
12914
|
+
import { homedir as homedir8 } from "node:os";
|
|
11841
12915
|
import { cwd as cwd3 } from "node:process";
|
|
11842
12916
|
function coerceForSettings(key, value) {
|
|
11843
12917
|
if (INT_KEYS.has(key))
|
|
@@ -11857,7 +12931,7 @@ async function configCommand(opts, config) {
|
|
|
11857
12931
|
return handleShow(opts, config);
|
|
11858
12932
|
}
|
|
11859
12933
|
function handleShow(opts, config) {
|
|
11860
|
-
const repoRoot =
|
|
12934
|
+
const repoRoot = resolve14(opts.repoPath ?? cwd3());
|
|
11861
12935
|
printHeader("Configuration");
|
|
11862
12936
|
printSection("Active Settings (merged)");
|
|
11863
12937
|
printKeyValue("backendUrl", config.backendUrl, 2);
|
|
@@ -11889,7 +12963,7 @@ function handleShow(opts, config) {
|
|
|
11889
12963
|
}
|
|
11890
12964
|
}
|
|
11891
12965
|
printSection("Config File");
|
|
11892
|
-
printInfo(`~/.open-agents/config.json (${
|
|
12966
|
+
printInfo(`~/.open-agents/config.json (${join20(homedir8(), ".open-agents", "config.json")})`);
|
|
11893
12967
|
printSection("Priority Chain");
|
|
11894
12968
|
printInfo(" 1. CLI flags (--model, --backend-url, etc.)");
|
|
11895
12969
|
printInfo(" 2. Project .oa/settings.json (--local)");
|
|
@@ -11922,13 +12996,13 @@ function handleSet(opts, _config) {
|
|
|
11922
12996
|
process.exit(1);
|
|
11923
12997
|
}
|
|
11924
12998
|
if (opts.local) {
|
|
11925
|
-
const repoRoot =
|
|
12999
|
+
const repoRoot = resolve14(opts.repoPath ?? cwd3());
|
|
11926
13000
|
try {
|
|
11927
13001
|
initOaDirectory(repoRoot);
|
|
11928
13002
|
const coerced = coerceForSettings(key, value);
|
|
11929
13003
|
saveProjectSettings(repoRoot, { [key]: coerced });
|
|
11930
13004
|
printSuccess(`Project override set: ${key} = ${value}`);
|
|
11931
|
-
printInfo(`Saved to ${
|
|
13005
|
+
printInfo(`Saved to ${join20(repoRoot, ".oa", "settings.json")}`);
|
|
11932
13006
|
printInfo("This override applies only when running in this workspace.");
|
|
11933
13007
|
} catch (err) {
|
|
11934
13008
|
printError(`Failed to save: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -11988,7 +13062,7 @@ var serve_exports = {};
|
|
|
11988
13062
|
__export(serve_exports, {
|
|
11989
13063
|
serveCommand: () => serveCommand
|
|
11990
13064
|
});
|
|
11991
|
-
import { spawn as
|
|
13065
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
11992
13066
|
async function serveCommand(opts, config) {
|
|
11993
13067
|
const backendType = config.backendType;
|
|
11994
13068
|
if (backendType === "ollama") {
|
|
@@ -12080,8 +13154,8 @@ async function serveVllm(opts, config) {
|
|
|
12080
13154
|
await runVllmServer(args, opts.verbose ?? false);
|
|
12081
13155
|
}
|
|
12082
13156
|
async function runVllmServer(args, verbose) {
|
|
12083
|
-
return new Promise((
|
|
12084
|
-
const child =
|
|
13157
|
+
return new Promise((resolve15, reject) => {
|
|
13158
|
+
const child = spawn4("python", args, {
|
|
12085
13159
|
stdio: verbose ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
12086
13160
|
env: { ...process.env }
|
|
12087
13161
|
});
|
|
@@ -12115,10 +13189,10 @@ async function runVllmServer(args, verbose) {
|
|
|
12115
13189
|
child.once("exit", (code, signal) => {
|
|
12116
13190
|
if (signal) {
|
|
12117
13191
|
printInfo(`vLLM server stopped by signal ${signal}`);
|
|
12118
|
-
|
|
13192
|
+
resolve15();
|
|
12119
13193
|
} else if (code === 0) {
|
|
12120
13194
|
printSuccess("vLLM server exited cleanly");
|
|
12121
|
-
|
|
13195
|
+
resolve15();
|
|
12122
13196
|
} else {
|
|
12123
13197
|
printError(`vLLM server exited with code ${code}`);
|
|
12124
13198
|
reject(new Error(`vLLM exited with code ${code}`));
|
|
@@ -12146,8 +13220,8 @@ __export(eval_exports, {
|
|
|
12146
13220
|
evalCommand: () => evalCommand
|
|
12147
13221
|
});
|
|
12148
13222
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
12149
|
-
import { mkdirSync as
|
|
12150
|
-
import { join as
|
|
13223
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
13224
|
+
import { join as join21 } from "node:path";
|
|
12151
13225
|
async function evalCommand(opts, config) {
|
|
12152
13226
|
const suiteName = opts.suite ?? "basic";
|
|
12153
13227
|
const suite = SUITES[suiteName];
|
|
@@ -12268,9 +13342,9 @@ async function evalCommand(opts, config) {
|
|
|
12268
13342
|
process.exit(failed > 0 ? 1 : 0);
|
|
12269
13343
|
}
|
|
12270
13344
|
function createTempEvalRepo() {
|
|
12271
|
-
const dir =
|
|
12272
|
-
|
|
12273
|
-
|
|
13345
|
+
const dir = join21(tmpdir3(), `open-agents-eval-${Date.now()}`);
|
|
13346
|
+
mkdirSync7(dir, { recursive: true });
|
|
13347
|
+
writeFileSync7(join21(dir, "package.json"), JSON.stringify({ name: "eval-repo", version: "0.0.0" }, null, 2) + "\n", "utf8");
|
|
12274
13348
|
return dir;
|
|
12275
13349
|
}
|
|
12276
13350
|
var BASIC_SUITE, FULL_SUITE, SUITES;
|
|
@@ -12330,7 +13404,7 @@ init_updater();
|
|
|
12330
13404
|
import { parseArgs as nodeParseArgs2 } from "node:util";
|
|
12331
13405
|
import { createRequire as createRequire3 } from "node:module";
|
|
12332
13406
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
12333
|
-
import { dirname as
|
|
13407
|
+
import { dirname as dirname3, join as join22 } from "node:path";
|
|
12334
13408
|
|
|
12335
13409
|
// packages/cli/dist/cli.js
|
|
12336
13410
|
import { createInterface } from "node:readline";
|
|
@@ -12437,7 +13511,7 @@ init_output();
|
|
|
12437
13511
|
function getVersion2() {
|
|
12438
13512
|
try {
|
|
12439
13513
|
const require2 = createRequire3(import.meta.url);
|
|
12440
|
-
const pkgPath =
|
|
13514
|
+
const pkgPath = join22(dirname3(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
12441
13515
|
const pkg = require2(pkgPath);
|
|
12442
13516
|
return pkg.version;
|
|
12443
13517
|
} catch {
|