openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
373 lines (372 loc) • 20 kB
JavaScript
import { g as isFutureDateTimestampMs } from "../number-coercion-CkhMiRw8.mjs";
import { a as resolveGlobalSingleton } from "../global-singleton-0m-JziJW.mjs";
import { t as createSubsystemLogger } from "../subsystem-4NzR43uz.mjs";
import { b as retainGatewayRootWorkAdmissionContinuationScope } from "../gateway-work-admission-DAV5kkgV.mjs";
import { c as withPluginRuntimeGatewayContextResolver, g as getGatewayContextResolver, m as getCanonicalGatewayContextResolver, o as getPluginRuntimeGatewayRequestScope } from "../gateway-request-scope-DV3FHtde.mjs";
import { f as getAgentRunLifecycleGeneration, p as hasAgentRunContextExecutionOwner, u as getAgentRunContext } from "../agent-run-registry-Cm8FlLIi.mjs";
import { a as emitAgentEvent, d as getAgentEventLifecycleGeneration } from "../agent-events-jWdQ_ESE.mjs";
import { i as rethrowIncognitoSessionError } from "../incognito-session-error-BUzgcwoZ.mjs";
import { t as captureIncognitoSessionBinding } from "../session-incognito-binding-Dp6RTei2.mjs";
import { A as getRestartRecoveryTerminalDeliveryEvidence, N as hasRestartRecoveryTerminalRun } from "../session-accessor.sqlite-entry-worker-publication-BNeFwCVE.mjs";
import { t as loadExactSessionEntry } from "../session-accessor.sqlite-exact-read-HBi2gDDN.mjs";
import { n as resolveSessionTranscriptReadFence } from "../session-transcript-read-fence-DqVaDIch.mjs";
import { n as decodeSessionTranscriptWorkerReadError } from "../session-history-worker-errors-DRVUiJbS.mjs";
import { D as readSessionSubmittedInput } from "../session-accessor-C3ljJ974.mjs";
import { t as readAdmittedHarnessCompletionInput } from "../session-harness-completion-source.kernel-Uf_MmyFd.mjs";
import { a as assertAgentHarnessCompletionScope, i as hasHarnessCompletionFinalReceipt, r as getOwedHarnessCompletionTask, s as withAgentHarnessCompletionAdmission } from "../agent-harness-completion-recovery-C0Xm0Pt1.mjs";
import { t as AGENT_INTERNAL_EVENT_TYPE_TASK_COMPLETION } from "../internal-event-contract-pF6FHp8g.mjs";
import { o as captureOperatorToolGatewayContinuationContext } from "../server-plugin-in-process-dispatch-CHKjkAiH.mjs";
import { m as buildAnnounceIdempotencyKey } from "../subagent-requester-settle-identity-DXprCej6.mjs";
import { r as loadRequesterSessionEntry } from "../subagent-announce-delivery.runtime-CyCjaWRS.mjs";
import { i as formatAgentInternalEventsForPrompt } from "../internal-events-BxRyNeVO.mjs";
import { d as resolveAnnounceOrigin, m as resolveSubagentCompletionOrigin } from "../subagent-announce-completion-delivery-_wXWxJda.mjs";
import { n as isInternalAnnounceRequesterSession, t as deliverSubagentAnnouncement } from "../subagent-announce-delivery-BfpLWFn0.mjs";
import { isDeepStrictEqual } from "node:util";
//#region src/agents/agent-harness-completion-custody.ts
const owners = resolveGlobalSingleton(Symbol.for("openclaw.agentHarnessCompletionCustody.registry"), () => /* @__PURE__ */ new WeakMap());
function getCompletionOwner(custody, scope) {
const owner = owners.get(custody);
const expected = owner && getGatewayContextResolver(owner.scope);
const actual = getGatewayContextResolver(scope);
if (!owner || owner.scope.requesterSessionKey !== scope.requesterSessionKey || owner.scope.requesterAgentId !== scope.requesterAgentId || (expected && getCanonicalGatewayContextResolver(expected)) !== (actual && getCanonicalGatewayContextResolver(actual))) throw new Error("Harness completion custody does not own this requester");
return owner;
}
/** Retains admitted completion work for this exact physical requester lifecycle. */
function captureAgentHarnessCompletionCustody(scope) {
assertAgentHarnessCompletionScope(scope);
const entry = loadRequesterSessionEntry(scope.requesterSessionKey, scope.requesterAgentId).entry;
const expected = {
sessionId: entry?.sessionId,
lifecycleRevision: entry?.lifecycleRevision
};
return captureAgentHarnessCompletionCustodyOwner(scope, () => {
const current = loadRequesterSessionEntry(scope.requesterSessionKey, scope.requesterAgentId).entry;
if (current?.sessionId !== expected.sessionId || current?.lifecycleRevision !== expected.lifecycleRevision) throw new Error("Harness completion requester lifecycle was replaced");
});
}
/** Capture during admission; assignment/recovery owners retain their own holds before yielding. */
async function captureAgentHarnessCompletionCustodyOwner(scopeInput, assertRequesterCurrent) {
const scope = assertAgentHarnessCompletionScope(scopeInput);
const resolver = getGatewayContextResolver(scope);
const capture = () => captureOperatorToolGatewayContinuationContext({
sessionKey: scope.requesterSessionKey,
agentId: scope.requesterAgentId
});
const preparation = resolver ? withPluginRuntimeGatewayContextResolver(resolver, capture) : capture();
if (!preparation) return;
const root = retainGatewayRootWorkAdmissionContinuationScope();
const captured = await preparation.catch((error) => {
root?.release();
throw error;
});
const releaseRoot = () => root?.release();
captured.signal.addEventListener("abort", releaseRoot, { once: true });
let references = 0;
let executions = 0;
const retain = (settled = false) => {
captured.signal.throwIfAborted();
references += 1;
if (!settled) executions += 1;
const lifetime = new AbortController();
let executionSettled = settled;
const settleExecution = () => {
if (!executionSettled) {
executionSettled = true;
if (--executions === 0) {
captured.signal.removeEventListener("abort", releaseRoot);
releaseRoot();
}
}
};
const assertCurrent = () => {
lifetime.signal.throwIfAborted();
captured.signal.throwIfAborted();
assertRequesterCurrent();
};
const custody = {
signal: AbortSignal.any([lifetime.signal, captured.signal]),
isCurrent: () => isAgentHarnessCompletionCustodyCurrent(custody, scope),
retain() {
assertCurrent();
return retain(executionSettled);
},
settleExecution,
release() {
if (lifetime.signal.aborted) return;
lifetime.abort(/* @__PURE__ */ new Error("Harness completion custody was released"));
settleExecution();
if (--references === 0) captured.release();
}
};
owners.set(custody, {
scope,
run(run) {
assertCurrent();
return !executionSettled && root ? root.runSync(() => captured.run(run)) : captured.run(run);
},
emit(run) {
assertCurrent();
if (executionSettled) throw new Error("Harness execution custody was settled");
captured.run(() => root ? root.runSync(run) : run());
}
});
return custody;
};
try {
captured.assertCurrent();
assertRequesterCurrent();
return retain();
} catch (error) {
root?.release();
captured.release();
throw error;
}
}
/** Binds activity to one live native assignment, not a persisted Tasks projection. */
function createAgentHarnessCompletionEventSink(params) {
const scope = assertAgentHarnessCompletionScope(params.scope);
const owner = getCompletionOwner(params.completionCustody, scope);
const runId = params.runId.trim();
if (!runId) throw new Error("Harness native event assignment requires a run ID");
const isSourceCurrent = params.isSourceCurrent;
const generation = getAgentEventLifecycleGeneration();
return (event) => owner.emit(() => {
if (!isSourceCurrent()) throw new Error("Harness native event assignment was replaced");
emitAgentEvent({
stream: event.stream,
data: event.data,
runId,
agentId: scope.requesterAgentId,
lifecycleGeneration: generation
});
});
}
/** Only the completion SDK can enter retained authority; plugins cannot execute a callback in it. */
function runWithAgentHarnessCompletionCustody(custody, scope, run) {
return getCompletionOwner(custody, scope).run(run);
}
/** Revalidate the retained source at asynchronous delivery effect boundaries. */
function isAgentHarnessCompletionCustodyCurrent(custody, scope) {
try {
return getCompletionOwner(custody, scope).run(() => true);
} catch {
return false;
}
}
//#endregion
//#region src/agents/agent-harness-completion-delivery.ts
const log = createSubsystemLogger("agents/harness-completion-recovery");
function sameRequester(claim, entry) {
return claim.sessionId === entry.sessionId && claim.lifecycleRevision === entry.lifecycleRevision;
}
function readCurrent(target) {
const loaded = loadExactSessionEntry({
...target,
readConsistency: "latest"
});
return loaded?.sessionKey === target.sessionKey ? loaded.entry : void 0;
}
async function readActorCurrent(target, binding) {
const { actor, admissionSignal } = binding;
return (await actor.sessions.read({ assertCurrent: () => actor.assertReadable() }, { sessionKey: target.sessionKey }, admissionSignal)).entry;
}
/** Only current process owners can hold admission before its input is committed. */
function hasLiveCompletionOwner(claim, runId) {
const scope = getPluginRuntimeGatewayRequestScope();
const admission = (scope?.resolveGatewayContext ? scope.resolveGatewayContext() : scope?.context)?.chatAbortControllers.get(runId);
if (admission && admission.sessionKey === claim.requesterSessionKey && admission.sessionId === claim.sessionId && admission.agentId === claim.requesterAgentId && admission.lifecycleGeneration === getAgentRunLifecycleGeneration() && admission.projectSessionActive === true && !admission.registrationCleanupRequested && !admission.controller.signal.aborted && isFutureDateTimestampMs(admission.expiresAtMs)) return true;
const context = getAgentRunContext(runId);
return hasAgentRunContextExecutionOwner(runId) && context?.sessionKey === claim.requesterSessionKey && context.sessionId === claim.sessionId && context.agentId === claim.requesterAgentId;
}
function classifyCompletionClaim(params, entry, claim) {
if (claim.sourceRunId !== params.sourceRunId || params.taskRunId !== void 0 && claim.taskRunId !== params.taskRunId || claim.requesterAgentId !== params.agentId || claim.requesterSessionKey !== params.sessionKey || !sameRequester(claim, entry)) return "blocked";
const receipt = getRestartRecoveryTerminalDeliveryEvidence(entry, params.sourceRunId);
if (isDeepStrictEqual(receipt?.harnessCompletion, claim) && receipt !== void 0 && hasHarnessCompletionFinalReceipt(receipt)) return "delivered";
if (!getOwedHarnessCompletionTask(claim, entry) || entry.mainRestartRecovery?.tombstone || entry.restartRecoveryDeliverySourceRunId !== claim.sourceRunId || entry.restartRecoveryHarnessCompletion?.taskId !== claim.taskId) return "blocked";
}
/** Reconcile before any steer/direct path, including when a restored native parent has no live owner. */
async function reconcileHarnessCompletionDelivery(params) {
const binding = captureIncognitoSessionBinding(params);
const claim = binding?.actor.sessions.captureCurrent(params.sessionKey);
const reconcile = async () => {
claim?.assertCurrent();
const result = await reconcileCurrentHarnessCompletionDelivery(params, binding);
claim?.assertCurrent();
return result;
};
return binding ? binding.actor.sessions.withSharedState(reconcile) : reconcile();
}
async function reconcileCurrentHarnessCompletionDelivery(params, binding) {
const entry = binding ? await readActorCurrent(params, binding) : readCurrent(params);
if (!entry) return "unowned";
const receipt = getRestartRecoveryTerminalDeliveryEvidence(entry, params.sourceRunId);
const claim = entry.restartRecoveryHarnessCompletion?.sourceRunId === params.sourceRunId ? entry.restartRecoveryHarnessCompletion : receipt?.harnessCompletion;
if (!claim) {
if (entry.restartRecoveryDeliverySourceRunId === params.sourceRunId || hasRestartRecoveryTerminalRun(entry, params.sourceRunId)) return "blocked";
const submitted = await readSessionSubmittedInput({
...params,
sessionId: entry.sessionId
}, `${params.sourceRunId}:user`);
const current = binding ? await readActorCurrent(params, binding) : readCurrent(params);
return !current || current.sessionId !== entry.sessionId || current.lifecycleRevision !== entry.lifecycleRevision || current.restartRecoveryDeliverySourceRunId === params.sourceRunId || current.restartRecoveryHarnessCompletion?.sourceRunId === params.sourceRunId || hasRestartRecoveryTerminalRun(current, params.sourceRunId) || getRestartRecoveryTerminalDeliveryEvidence(current, params.sourceRunId)?.harnessCompletion || submitted ? "blocked" : "unowned";
}
const classified = classifyCompletionClaim(params, entry, claim);
if (classified) return classified;
const operationalRunId = entry.restartRecoveryDeliveryRunId;
try {
if (operationalRunId && hasLiveCompletionOwner(claim, operationalRunId)) return "pending";
if (binding) {
const snapshot = await binding.actor.sessions.history({ assertCurrent: () => binding.actor.assertReadable() }, {
type: "session.history.harness-completion-source",
input: {
sessionKey: params.sessionKey,
sessionId: entry.sessionId,
lifecycleRevision: entry.lifecycleRevision,
claim,
admission: resolveSessionTranscriptReadFence({
agentId: params.agentId,
sessionId: entry.sessionId
})
}
}, binding.admissionSignal);
if (!snapshot.entry) return "blocked";
const current = classifyCompletionClaim(params, snapshot.entry, claim);
if (current) return current;
const currentRunId = snapshot.entry.restartRecoveryDeliveryRunId;
if (currentRunId && hasLiveCompletionOwner(claim, currentRunId)) return "pending";
if (snapshot.readError) throw decodeSessionTranscriptWorkerReadError(snapshot.readError);
return snapshot.validInput ? "pending" : "blocked";
}
return readAdmittedHarnessCompletionInput({
claim,
entry,
storePath: params.storePath,
operationalRunId
}) ? "pending" : "blocked";
} catch (error) {
rethrowIncognitoSessionError(error);
log.warn(`Could not inspect harness completion custody for ${params.sessionKey}: ${String(error)}`);
return "blocked";
}
}
//#endregion
//#region src/plugin-sdk/agent-harness-completion.ts
/** Delivers a completed harness task result back to the requester or parent session. */
async function deliverAgentHarnessCompletion(params) {
const scope = assertAgentHarnessCompletionScope(params.scope);
const completionCustody = params.completionCustody;
const signal = completionCustody ? AbortSignal.any([completionCustody.signal, ...params.signal ? [params.signal] : []]) : params.signal;
const requesterSessionKey = scope.requesterSessionKey;
const childSessionKey = params.childSessionKey.trim();
const childSessionId = params.childSessionId.trim();
const taskLabel = params.taskLabel?.trim() || "Agent harness task";
const announceType = params.announceType?.trim() || "Agent harness task";
const statusLabel = params.statusLabel?.trim() || params.status;
const eventStatus = params.status === "succeeded" ? "ok" : "error";
const expectedRequester = params.expectedRequester;
const requester = loadRequesterSessionEntry(requesterSessionKey, scope.requesterAgentId);
const requesterSessionId = requester.entry?.sessionId;
const requesterLifecycleRevision = requester.entry?.lifecycleRevision;
const isRequesterCurrent = () => {
if (completionCustody && !isAgentHarnessCompletionCustodyCurrent(completionCustody, scope)) return false;
const current = loadRequesterSessionEntry(requesterSessionKey, scope.requesterAgentId).entry;
return Boolean(requesterSessionId) && current?.sessionId === requesterSessionId && current?.lifecycleRevision === requesterLifecycleRevision && (!expectedRequester || current?.sessionId === expectedRequester.sessionId && current?.lifecycleRevision === expectedRequester.lifecycleRevision);
};
const isSourceSessionEffectsAllowed = () => !signal?.aborted && isRequesterCurrent() && params.isSourceSessionAdmissionAllowed();
const requesterIsSubagent = isInternalAnnounceRequesterSession(requesterSessionKey);
const directOrigin = requesterIsSubagent ? scope.requesterOrigin : resolveAnnounceOrigin(requester.entry, scope.requesterOrigin);
const completionDirectOrigin = requesterIsSubagent || !directOrigin ? directOrigin : await resolveSubagentCompletionOrigin({
childSessionKey,
requesterSessionKey,
requesterOrigin: directOrigin,
childRunId: childSessionKey,
spawnMode: "run",
expectsCompletionMessage: true
});
const internalEvents = [{
type: AGENT_INTERNAL_EVENT_TYPE_TASK_COMPLETION,
source: "subagent",
childSessionKey,
childSessionId,
announceType,
taskLabel,
status: eventStatus,
statusLabel,
result: params.result,
replyInstruction: params.replyInstruction?.trim() || "Use the completed harness task result to continue or wrap up the parent task. If this is a channel session, send the visible response with the message tool instead of only writing a transcript final answer."
}];
const prompt = formatAgentInternalEventsForPrompt(internalEvents);
const deliver = async () => {
if (!requesterSessionId || !isRequesterCurrent()) return {
delivered: false,
path: "none",
recoveryBlocked: true,
error: "completion requester locator is missing or replaced"
};
if (requester.agentId && requester.storePath) {
const custody = await reconcileHarnessCompletionDelivery({
agentId: requester.agentId,
storePath: requester.storePath,
sessionKey: requester.canonicalKey,
sourceRunId: buildAnnounceIdempotencyKey(params.announceId),
taskRunId: childSessionKey
});
if (!isRequesterCurrent()) return {
delivered: false,
path: "none",
recoveryBlocked: true,
error: "completion requester locator is missing or replaced"
};
if (custody === "delivered") return {
delivered: true,
path: "direct"
};
if (custody !== "unowned") return {
delivered: false,
path: "none",
...custody === "pending" ? { recoveryPending: true } : { recoveryBlocked: true },
error: custody === "pending" ? "completion is owned by requester recovery" : "completion recovery receipt or owner is unresolved"
};
}
return await withAgentHarnessCompletionAdmission({
scope,
sourceSessionKey: childSessionKey,
sourceRunId: buildAnnounceIdempotencyKey(params.announceId),
requesterSessionId,
requesterLifecycleRevision,
isSourceCurrent: isSourceSessionEffectsAllowed
}, () => deliverSubagentAnnouncement({
requesterSessionKey,
requesterAgentId: scope.requesterAgentId,
isSourceSessionEffectsAllowed,
triggerMessage: prompt,
internalEvents,
requesterSessionOrigin: scope.requesterOrigin,
completionDirectOrigin: completionDirectOrigin ?? directOrigin,
directOrigin,
sourceSessionKey: childSessionKey,
sourceTool: "agent_harness_completion",
isSourceSessionAdmissionAllowed: isSourceSessionEffectsAllowed,
targetRequesterSessionKey: requesterSessionKey,
requesterIsSubagent,
expectsCompletionMessage: true,
bestEffortDeliver: true,
directIdempotencyKey: buildAnnounceIdempotencyKey(params.announceId),
signal
}));
};
const resolveGatewayContext = getGatewayContextResolver(scope);
const deliverInGateway = () => resolveGatewayContext ? withPluginRuntimeGatewayContextResolver(resolveGatewayContext, deliver) : deliver();
return completionCustody ? await runWithAgentHarnessCompletionCustody(completionCustody, scope, deliverInGateway) : await deliverInGateway();
}
/** Returns true when completion delivery reached a persistent direct or steered path. */
function isDurableAgentHarnessCompletionDelivery(delivery) {
if (!delivery.delivered) return false;
if (delivery.path === "steered") return true;
if (delivery.path !== "direct") return false;
const phases = Array.isArray(delivery.phases) ? delivery.phases : void 0;
if (!phases) return true;
return phases.some((phase) => phase.phase === "direct-primary" && phase.delivered && phase.path === "direct");
}
//#endregion
export { captureAgentHarnessCompletionCustody, createAgentHarnessCompletionEventSink, deliverAgentHarnessCompletion, isDurableAgentHarnessCompletionDelivery };