Files
Agent-System/deploy/pi-agent/settle-policy.mjs
T
weichenandCursor 051e69ccd1 fix(pi-agent): clear recovered SDK transients before settle
auto_retry_end success=true now drops providerTransient and lastErr so
a later agent_settled can succeed when the in-process retry landed a
real target. A new error after recovery still fails.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 19:25:41 +00:00

123 lines
3.6 KiB
JavaScript

/**
* Success / retry classification for a PI RPC session.
* Write() arguments in traces are never treated as deliverables.
*/
export function newSettleState() {
return {
retryFailed: false,
providerTransient: false,
lastErr: "",
succeeded: false,
};
}
function messageField(ev, key) {
const msg = ev && ev.message;
if (!msg || typeof msg !== "object") {
return "";
}
const v = msg[key];
return typeof v === "string" ? v : "";
}
export function isTransientText(s) {
const t = String(s || "").toLowerCase();
return t.includes("connection error") ||
t.includes("terminated") ||
t.includes("auto_retry") ||
t.includes("econnreset") ||
t.includes("socket hang up");
}
export function isPermanentReason(s) {
const t = String(s || "").toLowerCase();
return t.includes("prompt_empty") ||
t.includes("unauthorized") ||
t.includes("治理");
}
export function applyAgentEvent(state, ev) {
if (!state || !ev || typeof ev !== "object") {
return state;
}
const t = ev.type || "";
const stop = messageField(ev, "stopReason");
const msgErr = messageField(ev, "errorMessage") ||
(typeof ev.errorMessage === "string" ? ev.errorMessage : "") ||
(typeof ev.finalError === "string" ? ev.finalError : "") ||
(ev.error && typeof ev.error.message === "string" ? ev.error.message : "");
if (msgErr && isTransientText(msgErr)) {
state.providerTransient = true;
state.lastErr = msgErr;
}
if (stop === "error") {
state.providerTransient = true;
if (msgErr) {
state.lastErr = msgErr;
}
}
if (t === "auto_retry_start") {
state.providerTransient = true;
if (ev.errorMessage) {
state.lastErr = String(ev.errorMessage);
}
}
if (t === "auto_retry_end") {
if (ev.success === false) {
state.retryFailed = true;
state.providerTransient = true;
state.lastErr = ev.finalError || ev.errorMessage || state.lastErr || "auto_retry_failed";
} else if (ev.success === true) {
// SDK in-process retry recovered. Drop this-round transient so a later
// agent_settled can succeed; later events may set a new error again.
state.retryFailed = false;
state.providerTransient = false;
state.lastErr = "";
} else {
state.retryFailed = false;
}
}
if (t === "agent_error") {
const err = (ev.error && ev.error.message) || ev.message || "agent_error";
const text = typeof err === "string" ? err : "agent_error";
state.lastErr = text;
if (isTransientText(text)) {
state.providerTransient = true;
}
}
if (t === "agent_settled") {
// PI emits agent_settled even after exhausted SDK retries.
state.succeeded = !state.retryFailed && !state.providerTransient;
}
return state;
}
export function classifyRunOutcome({
succeeded = false,
retryFailed = false,
providerTransient = false,
lastErr = "",
missing = [],
} = {}) {
const miss = Array.isArray(missing) ? missing.filter(Boolean) : [];
if (isPermanentReason(lastErr)) {
return { ok: false, retryable: false, fallback_reason: lastErr || "permanent_failure" };
}
if (!succeeded) {
const reason = lastErr || "agent_not_settled";
const retryable = (retryFailed || providerTransient || isTransientText(reason)) &&
!isPermanentReason(reason);
return { ok: false, retryable, fallback_reason: reason };
}
if (miss.length > 0) {
return {
ok: false,
retryable: Boolean(retryFailed || providerTransient),
fallback_reason: `missing target deliverable ${miss.join(",")}`,
};
}
return { ok: true, retryable: false, fallback_reason: "" };
}