fix(runner): fix fingerprint collision on projectId and relax loop-break limits
This commit is contained in:
46
vibn-agent-runner/dist/agent-session-runner.js
vendored
46
vibn-agent-runner/dist/agent-session-runner.js
vendored
@@ -300,21 +300,30 @@ File: "${path.relative(opts.repoRoot ?? ctx.workspaceRoot, task.filePath)}" (lin
|
||||
const toolFingerprints = [];
|
||||
let ralphIteration = 0;
|
||||
function fingerprintToolCall(tc) {
|
||||
if (tc.name === "shell_exec") {
|
||||
const cmd = String(tc.args?.command ?? "").trim();
|
||||
const verb = cmd
|
||||
.split("&&")
|
||||
.map((s) => s.trim())
|
||||
.find((s) => !s.startsWith("cd "))
|
||||
?.split(/\s+/)[0] ?? "shell";
|
||||
return `shell_exec:${verb}`;
|
||||
const name = tc.name;
|
||||
const args = tc.args ?? {};
|
||||
if (name === "shell_exec") {
|
||||
const cmd = String(args.command ?? "").trim();
|
||||
const firstWord = cmd.split(/\s+/)[0] ?? "shell";
|
||||
return `shell_exec:${firstWord}`;
|
||||
}
|
||||
if (tc.name === "fs_write" ||
|
||||
tc.name === "fs_edit" ||
|
||||
tc.name === "fs_read") {
|
||||
return `${tc.name}:${tc.args?.path}`;
|
||||
// Determine target based on most common descriptive parameter keys
|
||||
const target = args.path ??
|
||||
args.pattern ??
|
||||
args.command ??
|
||||
args.commandId ??
|
||||
args.appUuid ??
|
||||
args.uuid ??
|
||||
"";
|
||||
if (target) {
|
||||
return `${name}:${target}`;
|
||||
}
|
||||
return `${tc.name}:${Object.values(tc.args ?? {})[0]}`;
|
||||
// Filter out common metadata like projectId, and use first real argument
|
||||
const keys = Object.keys(args).filter((k) => k !== "projectId");
|
||||
if (keys.length > 0) {
|
||||
return `${name}:${args[keys[0]]}`;
|
||||
}
|
||||
return `${name}:default`;
|
||||
}
|
||||
while (subTurn < SUB_MAX_TURNS) {
|
||||
if (opts.isStopped()) {
|
||||
@@ -421,20 +430,23 @@ ${verification.error}
|
||||
for (const tc of resp.toolCalls) {
|
||||
toolFingerprints.push(fingerprintToolCall(tc));
|
||||
}
|
||||
const window = toolFingerprints.slice(-6);
|
||||
const window = toolFingerprints.slice(-12);
|
||||
const counts = new Map();
|
||||
for (const fp of window)
|
||||
counts.set(fp, (counts.get(fp) ?? 0) + 1);
|
||||
let maxRepeats = 0;
|
||||
let repeatedCmd = "";
|
||||
for (const [fp, n] of counts.entries()) {
|
||||
if (n > maxRepeats)
|
||||
if (n > maxRepeats) {
|
||||
maxRepeats = n;
|
||||
repeatedCmd = fp;
|
||||
}
|
||||
}
|
||||
if (maxRepeats >= 4) {
|
||||
if (maxRepeats >= 6) {
|
||||
await emit({
|
||||
ts: now(),
|
||||
type: "error",
|
||||
text: `Loop detected in subtask execution, breaking loop.`,
|
||||
text: `Loop detected in subtask execution (repeated "${repeatedCmd}" ${maxRepeats}x in last 12 calls), breaking loop.`,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -403,24 +403,35 @@ File: "${path.relative(opts.repoRoot ?? ctx.workspaceRoot, task.filePath)}" (lin
|
||||
let ralphIteration = 0;
|
||||
|
||||
function fingerprintToolCall(tc: any) {
|
||||
if (tc.name === "shell_exec") {
|
||||
const cmd = String(tc.args?.command ?? "").trim();
|
||||
const verb =
|
||||
cmd
|
||||
.split("&&")
|
||||
.map((s) => s.trim())
|
||||
.find((s) => !s.startsWith("cd "))
|
||||
?.split(/\s+/)[0] ?? "shell";
|
||||
return `shell_exec:${verb}`;
|
||||
const name = tc.name;
|
||||
const args = tc.args ?? {};
|
||||
|
||||
if (name === "shell_exec") {
|
||||
const cmd = String(args.command ?? "").trim();
|
||||
const firstWord = cmd.split(/\s+/)[0] ?? "shell";
|
||||
return `shell_exec:${firstWord}`;
|
||||
}
|
||||
if (
|
||||
tc.name === "fs_write" ||
|
||||
tc.name === "fs_edit" ||
|
||||
tc.name === "fs_read"
|
||||
) {
|
||||
return `${tc.name}:${tc.args?.path}`;
|
||||
|
||||
// Determine target based on most common descriptive parameter keys
|
||||
const target =
|
||||
args.path ??
|
||||
args.pattern ??
|
||||
args.command ??
|
||||
args.commandId ??
|
||||
args.appUuid ??
|
||||
args.uuid ??
|
||||
"";
|
||||
if (target) {
|
||||
return `${name}:${target}`;
|
||||
}
|
||||
return `${tc.name}:${Object.values(tc.args ?? {})[0]}`;
|
||||
|
||||
// Filter out common metadata like projectId, and use first real argument
|
||||
const keys = Object.keys(args).filter((k) => k !== "projectId");
|
||||
if (keys.length > 0) {
|
||||
return `${name}:${args[keys[0]]}`;
|
||||
}
|
||||
|
||||
return `${name}:default`;
|
||||
}
|
||||
|
||||
while (subTurn < SUB_MAX_TURNS) {
|
||||
@@ -536,20 +547,24 @@ ${verification.error}
|
||||
for (const tc of resp.toolCalls) {
|
||||
toolFingerprints.push(fingerprintToolCall(tc));
|
||||
}
|
||||
const window = toolFingerprints.slice(-6);
|
||||
const window = toolFingerprints.slice(-12);
|
||||
const counts = new Map<string, number>();
|
||||
for (const fp of window) counts.set(fp, (counts.get(fp) ?? 0) + 1);
|
||||
|
||||
let maxRepeats = 0;
|
||||
let repeatedCmd = "";
|
||||
for (const [fp, n] of counts.entries()) {
|
||||
if (n > maxRepeats) maxRepeats = n;
|
||||
if (n > maxRepeats) {
|
||||
maxRepeats = n;
|
||||
repeatedCmd = fp;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxRepeats >= 4) {
|
||||
if (maxRepeats >= 6) {
|
||||
await emit({
|
||||
ts: now(),
|
||||
type: "error",
|
||||
text: `Loop detected in subtask execution, breaking loop.`,
|
||||
text: `Loop detected in subtask execution (repeated "${repeatedCmd}" ${maxRepeats}x in last 12 calls), breaking loop.`,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user