openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
163 lines (162 loc) • 7.46 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-1VuiLaaR.mjs";
import { a as resolveGlobalSingleton } from "./global-singleton-0m-JziJW.mjs";
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { t as createSubsystemLogger } from "./subsystem-I-uqdx4f.mjs";
import { t as getGlobalHookRunner } from "./hook-runner-global-BfCvU9TT.mjs";
import { t as buildAgentHookContext } from "./hook-context-GFn8Q8PR.mjs";
import { createHash } from "node:crypto";
//#region src/agents/harness/lifecycle-hook-messages.ts
const hookMessageLoaders = resolveGlobalSingleton(Symbol.for("openclaw.agentHarnessHookMessageLoaders"), () => /* @__PURE__ */ new WeakMap());
/** Internal evidence belongs to this one event, without expanding the plugin hook contract. */
function bindAgentHarnessHookMessages(event, loadMessages) {
hookMessageLoaders.set(event, loadMessages);
return event;
}
function takeHookMessageLoader(event) {
const load = hookMessageLoaders.get(event);
hookMessageLoaders.delete(event);
return load;
}
//#endregion
//#region src/agents/harness/lifecycle-hook-helpers.ts
const log = createSubsystemLogger("agents/harness");
const FINALIZE_RETRY_BUDGET_KEY = Symbol.for("openclaw.pluginFinalizeRetryBudget");
const FINALIZE_RETRY_BUDGET_MAX_ENTRIES = 2048;
/** Returns the current global hook runner for harness lifecycle hooks. */
function getAgentHarnessHookRunner() {
return getGlobalHookRunner();
}
function getFinalizeRetryBudget() {
return resolveGlobalSingleton(FINALIZE_RETRY_BUDGET_KEY, () => /* @__PURE__ */ new Map());
}
function countFinalizeRetryBudgetEntries(budget) {
let count = 0;
for (const runBudget of budget.values()) count += runBudget.size;
return count;
}
function pruneFinalizeRetryBudget(budget) {
while (countFinalizeRetryBudgetEntries(budget) > FINALIZE_RETRY_BUDGET_MAX_ENTRIES) {
const oldestRunId = budget.keys().next().value;
if (oldestRunId === void 0) return;
const oldestRunBudget = budget.get(oldestRunId);
const oldestRetryKey = oldestRunBudget?.keys().next().value;
if (oldestRunBudget && oldestRetryKey !== void 0) oldestRunBudget.delete(oldestRetryKey);
if (!oldestRunBudget || oldestRunBudget.size === 0) budget.delete(oldestRunId);
}
}
function buildFinalizeRetryInstructionKey(instruction) {
return `instruction:${createHash("sha256").update(instruction).digest("hex")}`;
}
/** Dispatches best-effort LLM input hooks for a harness attempt. */
function runAgentHarnessLlmInputHook(params) {
const hookRunner = params.hookRunner ?? getGlobalHookRunner();
if (!hookRunner?.hasHooks("llm_input") || typeof hookRunner.runLlmInput !== "function") return;
hookRunner.runLlmInput(params.event, buildAgentHookContext(params.ctx)).catch((error) => {
log.warn(`llm_input hook failed: ${String(error)}`);
});
}
/** Dispatches best-effort LLM output hooks for a harness attempt. */
function runAgentHarnessLlmOutputHook(params) {
const hookRunner = params.hookRunner ?? getGlobalHookRunner();
if (!hookRunner?.hasHooks("llm_output") || typeof hookRunner.runLlmOutput !== "function") return;
hookRunner.runLlmOutput(params.event, buildAgentHookContext(params.ctx)).catch((error) => {
log.warn(`llm_output hook failed: ${String(error)}`);
});
}
async function executeAgentHarnessAgentEndHook(params) {
const loadMessages = takeHookMessageLoader(params.event);
const hookRunner = params.hookRunner ?? getGlobalHookRunner();
if (!hookRunner?.hasHooks("agent_end") || typeof hookRunner.runAgentEnd !== "function") return;
try {
const event = loadMessages ? {
...params.event,
messages: await loadMessages()
} : params.event;
await hookRunner.runAgentEnd(event, buildAgentHookContext(params.ctx), { unrefTimeout: params.unrefTimeout ?? false });
} catch (error) {
log.warn(`agent_end hook failed: ${String(error)}`);
}
}
/** Starts agent_end hooks with unref timeout behavior. */
function runAgentHarnessAgentEndHook(params) {
executeAgentHarnessAgentEndHook({
...params,
unrefTimeout: true
});
}
/** Runs agent_end hooks and waits for completion. */
async function awaitAgentHarnessAgentEndHook(params) {
await executeAgentHarnessAgentEndHook({
...params,
unrefTimeout: false
});
}
/** Runs before-finalize hooks and normalizes finalize/revise/continue decisions. */
async function runAgentHarnessBeforeAgentFinalizeHook(params) {
const loadMessages = takeHookMessageLoader(params.event);
const hookRunner = params.hookRunner ?? getGlobalHookRunner();
if (!hookRunner?.hasHooks("before_agent_finalize") || typeof hookRunner.runBeforeAgentFinalize !== "function") return { action: "continue" };
try {
const eventForNormalization = {
...params.event,
runId: params.event.runId ?? params.ctx.runId,
...loadMessages ? { messages: await loadMessages() } : {}
};
return normalizeBeforeAgentFinalizeResult(await hookRunner.runBeforeAgentFinalize(eventForNormalization, buildAgentHookContext(params.ctx)), eventForNormalization);
} catch (error) {
log.warn(`before_agent_finalize hook failed: ${String(error)}`);
return { action: "continue" };
}
}
function normalizeBeforeAgentFinalizeResult(result, event) {
if (result?.action === "finalize") {
const reason = normalizeOptionalString(result.reason);
return reason ? {
action: "finalize",
reason
} : { action: "finalize" };
}
if (result?.action === "revise") {
const retryCandidates = readBeforeAgentFinalizeRetryCandidates(result);
if (retryCandidates.length > 0) {
const reason = normalizeOptionalString(result.reason);
for (const retry of retryCandidates) {
const retryInstruction = normalizeOptionalString(retry.instruction);
if (!retryInstruction) continue;
const maxAttempts = typeof retry.maxAttempts === "number" && Number.isFinite(retry.maxAttempts) ? Math.max(1, Math.floor(retry.maxAttempts)) : 1;
const retryRunId = event?.runId ?? event?.sessionId ?? "unknown-run";
const retryKey = normalizeOptionalString(retry.idempotencyKey) || buildFinalizeRetryInstructionKey(retryInstruction);
const budget = getFinalizeRetryBudget();
const runBudget = budget.get(retryRunId) ?? /* @__PURE__ */ new Map();
const nextCount = (runBudget.get(retryKey) ?? 0) + 1;
runBudget.delete(retryKey);
runBudget.set(retryKey, nextCount);
budget.delete(retryRunId);
budget.set(retryRunId, runBudget);
pruneFinalizeRetryBudget(budget);
if (nextCount > maxAttempts) continue;
return {
action: "revise",
reason: reason && reason.includes(retryInstruction) ? reason : [reason, retryInstruction].filter(Boolean).join("\n\n")
};
}
return { action: "continue" };
}
const reason = normalizeOptionalString(result.reason);
return reason ? {
action: "revise",
reason
} : { action: "continue" };
}
return { action: "continue" };
}
function readBeforeAgentFinalizeRetryCandidates(result) {
const candidateList = result.retryCandidates;
if (Array.isArray(candidateList) && candidateList.length > 0) return candidateList.filter(isBeforeAgentFinalizeRetry);
return isBeforeAgentFinalizeRetry(result.retry) ? [result.retry] : [];
}
function isBeforeAgentFinalizeRetry(value) {
return isRecord(value);
}
//#endregion
export { runAgentHarnessLlmInputHook as a, runAgentHarnessBeforeAgentFinalizeHook as i, getAgentHarnessHookRunner as n, runAgentHarnessLlmOutputHook as o, runAgentHarnessAgentEndHook as r, bindAgentHarnessHookMessages as s, awaitAgentHarnessAgentEndHook as t };