openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
173 lines (172 loc) • 8.12 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { n as resolveGlobalSingleton } from "./global-singleton-PwlQSEal.js";
import { t as createSubsystemLogger } from "./subsystem-BzXSmsuh.js";
import { t as getGlobalHookRunner } from "./hook-runner-global-BnyQREr6.js";
import { createHash } from "node:crypto";
//#region src/agents/harness/hook-context.ts
/** Builds the sparse hook context object passed to agent harness plugin hooks. */
function buildAgentHookContext(params) {
return {
runId: params.runId,
...params.trace ? { trace: params.trace } : {},
...params.jobId ? { jobId: params.jobId } : {},
...params.agentId ? { agentId: params.agentId } : {},
...params.sessionKey ? { sessionKey: params.sessionKey } : {},
...params.sessionId ? { sessionId: params.sessionId } : {},
...params.workspaceDir ? { workspaceDir: params.workspaceDir } : {},
...params.modelProviderId ? { modelProviderId: params.modelProviderId } : {},
...params.modelId ? { modelId: params.modelId } : {},
...params.messageProvider ? { messageProvider: params.messageProvider } : {},
...params.channel ? { channel: params.channel } : {},
...params.chatId ? { chatId: params.chatId } : {},
...params.senderId ? { senderId: params.senderId } : {},
...params.trigger ? { trigger: params.trigger } : {},
...params.channelId ? { channelId: params.channelId } : {},
...params.contextTokenBudget ? { contextTokenBudget: params.contextTokenBudget } : {},
...params.contextWindowSource ? { contextWindowSource: params.contextWindowSource } : {},
...params.contextWindowReferenceTokens ? { contextWindowReferenceTokens: params.contextWindowReferenceTokens } : {}
};
}
//#endregion
//#region src/agents/harness/lifecycle-hook-helpers.ts
/**
* Agent harness lifecycle hook helpers.
*
* This module dispatches LLM/agent lifecycle plugin hooks and normalizes
* before-finalize retry/finalize decisions with bounded retry accounting.
*/
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 hookRunner = params.hookRunner ?? getGlobalHookRunner();
if (!hookRunner?.hasHooks("agent_end") || typeof hookRunner.runAgentEnd !== "function") return;
try {
const options = { unrefTimeout: params.unrefTimeout ?? false };
await hookRunner.runAgentEnd(params.event, buildAgentHookContext(params.ctx), options);
} 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 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
};
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 Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
//#endregion
export { runAgentHarnessLlmInputHook as a, runAgentHarnessBeforeAgentFinalizeHook as i, getAgentHarnessHookRunner as n, runAgentHarnessLlmOutputHook as o, runAgentHarnessAgentEndHook as r, buildAgentHookContext as s, awaitAgentHarnessAgentEndHook as t };