open-agents-ai 0.187.500 → 0.187.501
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 +225 -5
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3986,6 +3986,91 @@ ${JSON.stringify(extracted, null, 2)}`);
|
|
|
3986
3986
|
}
|
|
3987
3987
|
});
|
|
3988
3988
|
|
|
3989
|
+
// packages/execution/dist/tools/edit-snippet-finder.js
|
|
3990
|
+
function findClosestSnippet(content, oldString, contextLines = 5) {
|
|
3991
|
+
const lines = content.split("\n");
|
|
3992
|
+
if (lines.length === 0)
|
|
3993
|
+
return null;
|
|
3994
|
+
const oldLines = oldString.split("\n").map((l2) => l2.trim()).filter((l2) => l2.length > 0);
|
|
3995
|
+
if (oldLines.length === 0)
|
|
3996
|
+
return null;
|
|
3997
|
+
const anchor = oldLines.reduce((best, l2) => l2.length > best.length ? l2 : best, oldLines[0]);
|
|
3998
|
+
let bestIdx = 0;
|
|
3999
|
+
let bestScore = -1;
|
|
4000
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
4001
|
+
const score = diceSimilarity(anchor, lines[i2].trim());
|
|
4002
|
+
if (score > bestScore) {
|
|
4003
|
+
bestScore = score;
|
|
4004
|
+
bestIdx = i2;
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
const startIdx = Math.max(0, bestIdx - contextLines);
|
|
4008
|
+
const endIdx = Math.min(lines.length - 1, bestIdx + contextLines);
|
|
4009
|
+
const snippetLines = [];
|
|
4010
|
+
for (let i2 = startIdx; i2 <= endIdx; i2++) {
|
|
4011
|
+
const lineNum = i2 + 1;
|
|
4012
|
+
const marker = i2 === bestIdx ? ">" : " ";
|
|
4013
|
+
snippetLines.push(`${marker} ${lineNum.toString().padStart(4)} | ${lines[i2]}`);
|
|
4014
|
+
}
|
|
4015
|
+
return {
|
|
4016
|
+
startLine: startIdx + 1,
|
|
4017
|
+
endLine: endIdx + 1,
|
|
4018
|
+
content: snippetLines.join("\n"),
|
|
4019
|
+
similarity: Math.max(0, bestScore),
|
|
4020
|
+
totalLines: lines.length
|
|
4021
|
+
};
|
|
4022
|
+
}
|
|
4023
|
+
function snippetAtOffset(content, charOffset, contextLines = 3) {
|
|
4024
|
+
const lines = content.split("\n");
|
|
4025
|
+
const before = content.slice(0, charOffset);
|
|
4026
|
+
const lineNumber = before.split("\n").length;
|
|
4027
|
+
const idx = lineNumber - 1;
|
|
4028
|
+
const startIdx = Math.max(0, idx - contextLines);
|
|
4029
|
+
const endIdx = Math.min(lines.length - 1, idx + contextLines);
|
|
4030
|
+
const out = [];
|
|
4031
|
+
for (let i2 = startIdx; i2 <= endIdx; i2++) {
|
|
4032
|
+
const marker = i2 === idx ? ">" : " ";
|
|
4033
|
+
out.push(`${marker} ${(i2 + 1).toString().padStart(4)} | ${lines[i2]}`);
|
|
4034
|
+
}
|
|
4035
|
+
return { lineNumber, content: out.join("\n") };
|
|
4036
|
+
}
|
|
4037
|
+
function diceSimilarity(a2, b) {
|
|
4038
|
+
if (!a2.length && !b.length)
|
|
4039
|
+
return 1;
|
|
4040
|
+
if (!a2.length || !b.length)
|
|
4041
|
+
return 0;
|
|
4042
|
+
if (a2 === b)
|
|
4043
|
+
return 1;
|
|
4044
|
+
if (a2.length < 2 || b.length < 2) {
|
|
4045
|
+
return a2 === b ? 1 : 0;
|
|
4046
|
+
}
|
|
4047
|
+
const bigramsA = bigrams(a2);
|
|
4048
|
+
const bigramsB = bigrams(b);
|
|
4049
|
+
let intersection = 0;
|
|
4050
|
+
for (const bg of bigramsB.keys()) {
|
|
4051
|
+
const count = Math.min(bigramsA.get(bg) ?? 0, bigramsB.get(bg));
|
|
4052
|
+
if (count > 0) {
|
|
4053
|
+
intersection += count;
|
|
4054
|
+
bigramsA.set(bg, (bigramsA.get(bg) ?? 0) - count);
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
const total = a2.length - 1 + (b.length - 1);
|
|
4058
|
+
return total > 0 ? 2 * intersection / total : 0;
|
|
4059
|
+
}
|
|
4060
|
+
function bigrams(s2) {
|
|
4061
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
4062
|
+
for (let i2 = 0; i2 < s2.length - 1; i2++) {
|
|
4063
|
+
const bg = s2.slice(i2, i2 + 2);
|
|
4064
|
+
map2.set(bg, (map2.get(bg) ?? 0) + 1);
|
|
4065
|
+
}
|
|
4066
|
+
return map2;
|
|
4067
|
+
}
|
|
4068
|
+
var init_edit_snippet_finder = __esm({
|
|
4069
|
+
"packages/execution/dist/tools/edit-snippet-finder.js"() {
|
|
4070
|
+
"use strict";
|
|
4071
|
+
}
|
|
4072
|
+
});
|
|
4073
|
+
|
|
3989
4074
|
// packages/execution/dist/tools/file-edit.js
|
|
3990
4075
|
import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
3991
4076
|
import { resolve as resolve6 } from "node:path";
|
|
@@ -4016,6 +4101,15 @@ function findMatchLines(haystack, needle) {
|
|
|
4016
4101
|
}
|
|
4017
4102
|
return lines;
|
|
4018
4103
|
}
|
|
4104
|
+
function findMatchOffsets(haystack, needle) {
|
|
4105
|
+
const offsets = [];
|
|
4106
|
+
let pos = 0;
|
|
4107
|
+
while ((pos = haystack.indexOf(needle, pos)) !== -1) {
|
|
4108
|
+
offsets.push(pos);
|
|
4109
|
+
pos += needle.length;
|
|
4110
|
+
}
|
|
4111
|
+
return offsets;
|
|
4112
|
+
}
|
|
4019
4113
|
function replaceAllOccurrences(haystack, needle, replacement) {
|
|
4020
4114
|
return haystack.split(needle).join(replacement);
|
|
4021
4115
|
}
|
|
@@ -4024,6 +4118,7 @@ var init_file_edit = __esm({
|
|
|
4024
4118
|
"packages/execution/dist/tools/file-edit.js"() {
|
|
4025
4119
|
"use strict";
|
|
4026
4120
|
init_change_log();
|
|
4121
|
+
init_edit_snippet_finder();
|
|
4027
4122
|
FileEditTool = class {
|
|
4028
4123
|
name = "file_edit";
|
|
4029
4124
|
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.";
|
|
@@ -4085,19 +4180,42 @@ var init_file_edit = __esm({
|
|
|
4085
4180
|
const content = await readFile3(fullPath, "utf-8");
|
|
4086
4181
|
const occurrences = countOccurrences(content, oldString);
|
|
4087
4182
|
if (occurrences === 0) {
|
|
4183
|
+
const snippet = findClosestSnippet(content, oldString, 5);
|
|
4184
|
+
let errorMsg = `old_string not found in ${filePath}.`;
|
|
4185
|
+
if (snippet) {
|
|
4186
|
+
const pct = Math.round(snippet.similarity * 100);
|
|
4187
|
+
errorMsg += `
|
|
4188
|
+
|
|
4189
|
+
Current file content (closest match, ${pct}% similarity, lines ${snippet.startLine}–${snippet.endLine} of ${snippet.totalLines}):
|
|
4190
|
+
${snippet.content}
|
|
4191
|
+
|
|
4192
|
+
Use the EXACT current content above to construct a working old_string. Do not retry with a different guess — the file on disk has changed since you last read it.`;
|
|
4193
|
+
} else {
|
|
4194
|
+
errorMsg += ` The file is empty or binary. Use file_read to inspect.`;
|
|
4195
|
+
}
|
|
4088
4196
|
return {
|
|
4089
4197
|
success: false,
|
|
4090
4198
|
output: "",
|
|
4091
|
-
error:
|
|
4199
|
+
error: errorMsg,
|
|
4092
4200
|
durationMs: performance.now() - start2
|
|
4093
4201
|
};
|
|
4094
4202
|
}
|
|
4095
4203
|
if (!replaceAll && occurrences > 1) {
|
|
4096
4204
|
const matchLines = findMatchLines(content, oldString);
|
|
4205
|
+
const offsets = findMatchOffsets(content, oldString);
|
|
4206
|
+
const snippets = offsets.slice(0, 4).map((off) => {
|
|
4207
|
+
const s2 = snippetAtOffset(content, off, 3);
|
|
4208
|
+
return `
|
|
4209
|
+
--- match at line ${s2.lineNumber} ---
|
|
4210
|
+
${s2.content}`;
|
|
4211
|
+
}).join("\n");
|
|
4097
4212
|
return {
|
|
4098
4213
|
success: false,
|
|
4099
4214
|
output: "",
|
|
4100
|
-
error: `old_string is not unique — found ${occurrences} occurrences at lines ${matchLines.join(", ")}.
|
|
4215
|
+
error: `old_string is not unique — found ${occurrences} occurrences at lines ${matchLines.join(", ")}.
|
|
4216
|
+
${snippets}
|
|
4217
|
+
|
|
4218
|
+
Add UNIQUE surrounding context (a function name, distinctive comment, or unique line above/below) to disambiguate. Or set replace_all=true if you want all ${occurrences} occurrences replaced identically.`,
|
|
4101
4219
|
durationMs: performance.now() - start2
|
|
4102
4220
|
};
|
|
4103
4221
|
}
|
|
@@ -5595,6 +5713,15 @@ var init_aiwg_workflow = __esm({
|
|
|
5595
5713
|
// packages/execution/dist/tools/batch-edit.js
|
|
5596
5714
|
import { readFile as readFile7, writeFile as writeFile4 } from "node:fs/promises";
|
|
5597
5715
|
import { resolve as resolve11 } from "node:path";
|
|
5716
|
+
function findMatchOffsetsBatch(haystack, needle) {
|
|
5717
|
+
const offsets = [];
|
|
5718
|
+
let pos = 0;
|
|
5719
|
+
while ((pos = haystack.indexOf(needle, pos)) !== -1) {
|
|
5720
|
+
offsets.push(pos);
|
|
5721
|
+
pos += needle.length;
|
|
5722
|
+
}
|
|
5723
|
+
return offsets;
|
|
5724
|
+
}
|
|
5598
5725
|
function countOccurrences2(haystack, needle) {
|
|
5599
5726
|
let count = 0;
|
|
5600
5727
|
let pos = 0;
|
|
@@ -5609,6 +5736,7 @@ var init_batch_edit = __esm({
|
|
|
5609
5736
|
"packages/execution/dist/tools/batch-edit.js"() {
|
|
5610
5737
|
"use strict";
|
|
5611
5738
|
init_change_log();
|
|
5739
|
+
init_edit_snippet_finder();
|
|
5612
5740
|
BatchEditTool = class {
|
|
5613
5741
|
name = "batch_edit";
|
|
5614
5742
|
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.";
|
|
@@ -5680,12 +5808,29 @@ var init_batch_edit = __esm({
|
|
|
5680
5808
|
for (const edit of fileEdits) {
|
|
5681
5809
|
const occurrences = countOccurrences2(content, edit.old_string);
|
|
5682
5810
|
if (occurrences === 0) {
|
|
5683
|
-
|
|
5811
|
+
const snippet = findClosestSnippet(content, edit.old_string, 5);
|
|
5812
|
+
if (snippet) {
|
|
5813
|
+
const pct = Math.round(snippet.similarity * 100);
|
|
5814
|
+
results.push(`SKIP: old_string not found in ${edit.relPath}.
|
|
5815
|
+
Closest match in current file (${pct}% similar, lines ${snippet.startLine}–${snippet.endLine} of ${snippet.totalLines}):
|
|
5816
|
+
${snippet.content}
|
|
5817
|
+
Use this exact content for old_string in your next attempt.`);
|
|
5818
|
+
} else {
|
|
5819
|
+
results.push(`SKIP: old_string not found in ${edit.relPath} (file empty or binary)`);
|
|
5820
|
+
}
|
|
5684
5821
|
failCount++;
|
|
5685
5822
|
continue;
|
|
5686
5823
|
}
|
|
5687
5824
|
if (!edit.replace_all && occurrences > 1) {
|
|
5688
|
-
|
|
5825
|
+
const offsets = findMatchOffsetsBatch(content, edit.old_string).slice(0, 4);
|
|
5826
|
+
const snippets = offsets.map((off) => {
|
|
5827
|
+
const s2 = snippetAtOffset(content, off, 3);
|
|
5828
|
+
return `
|
|
5829
|
+
--- match at line ${s2.lineNumber} ---
|
|
5830
|
+
${s2.content}`;
|
|
5831
|
+
}).join("\n");
|
|
5832
|
+
results.push(`AMBIGUOUS: old_string has ${occurrences} matches in ${edit.relPath}.${snippets}
|
|
5833
|
+
Add UNIQUE surrounding context, or use replace_all=true.`);
|
|
5689
5834
|
failCount++;
|
|
5690
5835
|
continue;
|
|
5691
5836
|
}
|
|
@@ -521861,6 +522006,18 @@ var init_agenticRunner = __esm({
|
|
|
521861
522006
|
// for chronic detection.
|
|
521862
522007
|
_lastPfvTurn = -1;
|
|
521863
522008
|
_ssmaFiredCount = 0;
|
|
522009
|
+
// REG-52: PFV cooldown. Without this, chronic-stuck (which only ever
|
|
522010
|
+
// increases) keeps PFV firing every turn after the threshold is
|
|
522011
|
+
// crossed, mostly returning continue (waste). Cooldown for OA_PFV_COOLDOWN
|
|
522012
|
+
// turns (default 8) after every fire — same shape as REG-44/REG-50.
|
|
522013
|
+
_pfvCooldownUntilTurn = -1;
|
|
522014
|
+
// REG-53: edit-fail-thrash detector cooldown. Detects the failure
|
|
522015
|
+
// mode REG-50 misses: agent hammers file_edit/batch_edit on the same
|
|
522016
|
+
// file with old_string variants that never match. Each failure
|
|
522017
|
+
// doesn't increment the file's WRITE count (no bytes hit disk), so
|
|
522018
|
+
// REG-50's cooldown stays armed and never re-fires. The agent stays
|
|
522019
|
+
// stuck.
|
|
522020
|
+
_editFailThrashCooldownUntilTurn = -1;
|
|
521864
522021
|
// REG-46: world-state regeneration. Replaces stream-based context
|
|
521865
522022
|
// re-derivation (agent re-listing dirs, re-reading specs) with periodic
|
|
521866
522023
|
// injected snapshots of workdir + plan reconciliation + recent failures.
|
|
@@ -524762,16 +524919,79 @@ ${_staleSamples.join("\n")}` : ``,
|
|
|
524762
524919
|
}
|
|
524763
524920
|
}
|
|
524764
524921
|
}
|
|
524922
|
+
if (turn > this._editFailThrashCooldownUntilTurn && turn >= 12) {
|
|
524923
|
+
const _efWindow = toolCallLog.slice(-15);
|
|
524924
|
+
if (_efWindow.length >= 12) {
|
|
524925
|
+
const _efEditClass = /* @__PURE__ */ new Set([
|
|
524926
|
+
"file_edit",
|
|
524927
|
+
"batch_edit",
|
|
524928
|
+
"file_patch"
|
|
524929
|
+
]);
|
|
524930
|
+
const _efFailCounts = /* @__PURE__ */ new Map();
|
|
524931
|
+
for (const c9 of _efWindow) {
|
|
524932
|
+
if (!_efEditClass.has(c9.name))
|
|
524933
|
+
continue;
|
|
524934
|
+
if (c9.success !== false)
|
|
524935
|
+
continue;
|
|
524936
|
+
const _ak = c9.argsKey || "";
|
|
524937
|
+
const _m = /(?:^|,)path=([^,]+)/.exec(_ak);
|
|
524938
|
+
const _pk = _m ? _m[1].slice(0, 200) : _ak.slice(0, 200);
|
|
524939
|
+
_efFailCounts.set(_pk, (_efFailCounts.get(_pk) ?? 0) + 1);
|
|
524940
|
+
}
|
|
524941
|
+
const _efThreshold = parseInt(process.env["OA_EDIT_FAIL_THRESHOLD"] || "3", 10) || 3;
|
|
524942
|
+
let _efWorstPath = "";
|
|
524943
|
+
let _efWorstCount = 0;
|
|
524944
|
+
for (const [_p, _n] of _efFailCounts.entries()) {
|
|
524945
|
+
if (_n > _efWorstCount) {
|
|
524946
|
+
_efWorstCount = _n;
|
|
524947
|
+
_efWorstPath = _p;
|
|
524948
|
+
}
|
|
524949
|
+
}
|
|
524950
|
+
if (_efWorstCount >= _efThreshold) {
|
|
524951
|
+
this._editFailThrashCooldownUntilTurn = turn + 8;
|
|
524952
|
+
messages2.push({
|
|
524953
|
+
role: "system",
|
|
524954
|
+
content: [
|
|
524955
|
+
`[EDIT-FAIL-THRASH HALT — REG-53]`,
|
|
524956
|
+
``,
|
|
524957
|
+
`In the last ${_efWindow.length} tool calls you have failed file_edit/batch_edit on the same file ${_efWorstCount} times:`,
|
|
524958
|
+
` ${_efWorstPath}`,
|
|
524959
|
+
``,
|
|
524960
|
+
`Each failure means your old_string did not match the file content. Your remembered version of this file has diverged from what's on disk — likely because an earlier edit succeeded and shifted things, or because you guessed at the file's content.`,
|
|
524961
|
+
``,
|
|
524962
|
+
`STOP guessing variants of old_string. Pick ONE of these for your next response:`,
|
|
524963
|
+
``,
|
|
524964
|
+
` (a) FILE_READ + COPY-EXACT: Call file_read on ${_efWorstPath}, then copy the EXACT bytes (whitespace, indentation, punctuation) for old_string from that read. Do not paraphrase or reformat.`,
|
|
524965
|
+
``,
|
|
524966
|
+
` (b) USE THE INLINE SNIPPET: Recent edit failures already include a "closest match" snippet showing the actual current content near where you tried to edit. Read that snippet in your last error message and use its content verbatim as old_string.`,
|
|
524967
|
+
``,
|
|
524968
|
+
` (c) REPLACE_ALL: If you want a global rename, set replace_all=true and the uniqueness check is bypassed.`,
|
|
524969
|
+
``,
|
|
524970
|
+
` (d) FILE_WRITE: If the changes are extensive, rewrite the whole file with file_write instead of patching it. Faster than 6 failed edits.`,
|
|
524971
|
+
``,
|
|
524972
|
+
`Do NOT in your next response: call file_edit or batch_edit on ${_efWorstPath} again with another guess at old_string.`
|
|
524973
|
+
].join("\n")
|
|
524974
|
+
});
|
|
524975
|
+
this.emit({
|
|
524976
|
+
type: "status",
|
|
524977
|
+
content: `REG-53 EDIT-FAIL-THRASH halt fired at turn ${turn} — file=${_efWorstPath}, failures=${_efWorstCount}/${_efThreshold}`,
|
|
524978
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
524979
|
+
});
|
|
524980
|
+
}
|
|
524981
|
+
}
|
|
524982
|
+
}
|
|
524765
524983
|
try {
|
|
524766
524984
|
const _pfvRaw = (process.env["OA_PFV"] || "off").toLowerCase();
|
|
524767
524985
|
const _pfvOn = _pfvRaw === "on" || _pfvRaw === "1" || _pfvRaw === "true";
|
|
524768
|
-
if (_pfvOn && this._lastPfvTurn !== turn) {
|
|
524986
|
+
if (_pfvOn && this._lastPfvTurn !== turn && turn > this._pfvCooldownUntilTurn) {
|
|
524769
524987
|
const _pfvInterval = parseInt(process.env["OA_PFV_INTERVAL"] || "30", 10) || 30;
|
|
524770
524988
|
const _pfvChronicThreshold = parseInt(process.env["OA_PFV_CHRONIC_THRESHOLD"] || "2", 10) || 2;
|
|
524989
|
+
const _pfvCooldown = parseInt(process.env["OA_PFV_COOLDOWN"] || "8", 10) || 8;
|
|
524771
524990
|
const _pfvPeriodic = turn > 0 && _pfvInterval > 0 && turn % _pfvInterval === 0;
|
|
524772
524991
|
const _pfvChronic = this._ssmaFiredCount >= _pfvChronicThreshold;
|
|
524773
524992
|
if (_pfvPeriodic || _pfvChronic) {
|
|
524774
524993
|
this._lastPfvTurn = turn;
|
|
524994
|
+
this._pfvCooldownUntilTurn = turn + _pfvCooldown;
|
|
524775
524995
|
const _pfvTriggerReason = _pfvChronic ? "chronic-stuck" : "periodic";
|
|
524776
524996
|
const _pfvCallable = async (prompt) => {
|
|
524777
524997
|
try {
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-agents-ai",
|
|
3
|
-
"version": "0.187.
|
|
3
|
+
"version": "0.187.501",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "open-agents-ai",
|
|
9
|
-
"version": "0.187.
|
|
9
|
+
"version": "0.187.501",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "CC-BY-NC-4.0",
|
|
12
12
|
"dependencies": {
|
package/package.json
CHANGED