openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
342 lines (341 loc) • 15.9 kB
JavaScript
import { c as normalizeOptionalLowercaseString } from "./string-coerce-1VuiLaaR.mjs";
import { D as resolveExpiresAtMsFromDurationMs, E as resolveDateTimestampMs, g as isFutureDateTimestampMs, o as asDateTimestampMs } from "./number-coercion-CuVhdCBs.mjs";
import { T as tryResolveLegacyCompatibilityAgentId } from "./agent-scope-config-CDYpzA0r.mjs";
import "./legacy.default-agent-owner-CiZjlN5C.mjs";
import { C as releaseAgentRunDelegatedAuthority } from "./agent-run-registry-BaxMDLZF.mjs";
import { i as emitAgentEvent, l as getAgentEventLifecycleGeneration } from "./agent-events-GvNTkbe3.mjs";
import { n as resolveRequestedSessionAgentId, o as resolveSessionSubscriptionKey, s as resolveSessionSubscriptionKeys } from "./session-request-agent-Bxi11ET4.mjs";
import { o as createAgentRunRestartAbortError } from "./run-termination-DGv2EKii.mjs";
import { r as resolveChatRunOwnerAgentId } from "./chat-run-owner-Ci8Mz1ow.mjs";
import { t as AGENT_RUN_TERMINAL_RETRY_GRACE_MS } from "./agent-run-terminal-outcome-BTCs8JFs.mjs";
import { i as readToolValidationErrorSummary } from "./tool-error-summary-64MNk8zq.mjs";
import { o as removeChatAbortControllerEntry } from "./chat-abort-lifecycle-internal-t_mR3jTJ.mjs";
import { n as appendChatCanvasBlocksToMessage } from "./chat-display-projection.canvas-DwEc-tyR.mjs";
import { i as projectLiveAssistantBufferedText } from "./live-chat-projector-DYrwpcEg.mjs";
import { n as createChatAbortMarker } from "./server-chat-state-BP63MB41.mjs";
//#region src/gateway/chat-abort.ts
const DEFAULT_CHAT_RUN_ABORT_GRACE_MS = 6e4;
function projectInFlightRunSnapshot(params) {
const run = params.chatRunState.runs.get(params.runId);
const projected = projectLiveAssistantBufferedText(params.chatRunState.resolveBuffer(params.runId).text, { suppressLeadFragments: true });
const plan = run?.planSnapshot;
const events = run?.progressSnapshot?.events;
return {
runId: params.runId,
text: projected.suppress ? "" : projected.text,
...params.startedAtMs === void 0 ? {} : { startedAt: params.startedAtMs },
...params.sessionAbortable ? { sessionAbortable: true } : {},
...plan ? { plan } : {},
...events?.length ? { events } : {}
};
}
function createChatAbortSignalReason(stopReason) {
if (stopReason === "restart") return createAgentRunRestartAbortError();
if (stopReason !== "timeout") return;
const reason = /* @__PURE__ */ new Error("chat run timed out");
reason.name = "TimeoutError";
return reason;
}
function resolveChatRunExpiresAtMs(params) {
const { now, timeoutMs, graceMs = DEFAULT_CHAT_RUN_ABORT_GRACE_MS, minMs = 12e4, maxMs = 864e5 } = params;
const safeNow = asDateTimestampMs(now);
if (safeNow === void 0) return 0;
const targetDurationMs = Math.max(0, timeoutMs) + graceMs;
const target = resolveExpiresAtMsFromDurationMs(targetDurationMs, { nowMs: safeNow });
const min = resolveExpiresAtMsFromDurationMs(minMs, { nowMs: safeNow });
const max = resolveExpiresAtMsFromDurationMs(maxMs, { nowMs: safeNow });
if (target === void 0 || min === void 0 || max === void 0) return 0;
return Math.min(max, Math.max(min, target));
}
function resolveAgentRunExpiresAtMs(params) {
const graceMs = Math.max(0, params.graceMs ?? DEFAULT_CHAT_RUN_ABORT_GRACE_MS);
return resolveChatRunExpiresAtMs({
now: params.now,
timeoutMs: params.timeoutMs,
graceMs,
minMs: graceMs,
maxMs: Math.max(0, params.timeoutMs) + graceMs
});
}
function registerChatAbortController(params) {
const controller = new AbortController();
const bindAgentRunDelegatedAuthority = (authority) => {
const entry = params.chatAbortControllers.get(params.runId);
if (entry?.controller !== controller || entry.registrationCleanupRequested || !entry.operationalRunInstance || authority.operationalRunInstance !== entry.operationalRunInstance) throw new Error("agent run authority does not belong to this controller registration");
if (entry.agentRunDelegatedAuthority && entry.agentRunDelegatedAuthority !== authority) throw new Error("agent run controller already owns a different authority");
entry.agentRunDelegatedAuthority = authority;
};
let executionStarted = false;
const markExecutionStarted = () => {
if (executionStarted) return false;
const entry = params.chatAbortControllers.get(params.runId);
if (entry?.controller !== controller || entry.registrationCleanupRequested || controller.signal.aborted) return false;
executionStarted = true;
entry.executionStarted = true;
if (entry.kind !== "agent") return true;
const now = Date.now();
if (!isFutureDateTimestampMs(entry.expiresAtMs, { nowMs: now })) return true;
entry.expiresAtMs = resolveAgentRunExpiresAtMs({
now,
timeoutMs: params.timeoutMs
});
return true;
};
const cleanup = () => {
const entry = params.chatAbortControllers.get(params.runId);
if (entry?.controller === controller) {
if (entry.agentRunDelegatedAuthority) releaseAgentRunDelegatedAuthority(entry.agentRunDelegatedAuthority);
entry.registrationCleanupRequested = true;
entry.projectSessionActive = false;
entry.pendingTimeoutCompletion = void 0;
if (entry.projectSessionTerminalPending === true) return;
const persistence = entry.projectSessionTerminalPersistence;
if (persistence) {
persistence.then(() => {
if (params.chatAbortControllers.get(params.runId)?.controller === controller && entry.projectSessionTerminalPersistence === persistence) {
entry.projectSessionTerminalPersistence = void 0;
removeChatAbortControllerEntry(params.chatAbortControllers, params.runId, entry);
}
}).catch(() => {
if (params.chatAbortControllers.get(params.runId)?.controller === controller && entry.projectSessionTerminalPersistence === persistence) removeChatAbortControllerEntry(params.chatAbortControllers, params.runId, entry);
});
return;
}
removeChatAbortControllerEntry(params.chatAbortControllers, params.runId, entry);
}
};
if (!params.sessionKey || params.chatAbortControllers.has(params.runId)) return {
controller,
registered: false,
deferTimeoutCompletion: () => false,
markExecutionStarted,
bindAgentRunDelegatedAuthority,
cleanup
};
const rawNow = params.now ?? Date.now();
const now = resolveDateTimestampMs(rawNow, 0);
const explicitExpiresAtMs = params.expiresAtMs === void 0 ? void 0 : asDateTimestampMs(params.expiresAtMs) ?? 0;
const entry = {
controller,
sessionId: params.sessionId,
sessionKey: params.sessionKey,
lifecycleGeneration: params.lifecycleGeneration ?? getAgentEventLifecycleGeneration(),
operationalRunInstance: params.operationalRunInstance,
agentId: normalizeOptionalLowercaseString(params.agentId),
startedAtMs: now,
executionStarted: false,
expiresAtMs: explicitExpiresAtMs ?? resolveChatRunExpiresAtMs({
now: rawNow,
timeoutMs: params.timeoutMs
}),
ownerConnId: params.ownerConnId,
ownerDeviceId: params.ownerDeviceId,
providerId: normalizeOptionalLowercaseString(params.providerId),
authProviderId: normalizeOptionalLowercaseString(params.authProviderId),
controlUiVisible: params.controlUiVisible,
isAbortable: params.isAbortable,
resolveTerminalProducer: params.resolveTerminalProducer ? () => params.resolveTerminalProducer?.(entry) : void 0,
onRemoved: params.onRemoved,
projectSessionActive: params.projectSessionActive ?? true,
kind: params.kind,
turnKind: params.turnKind
};
params.chatAbortControllers.set(params.runId, entry);
return {
controller,
registered: true,
entry,
deferTimeoutCompletion: (settle) => {
if (params.chatAbortControllers.get(params.runId) !== entry) return false;
entry.pendingTimeoutCompletion = {
expiresAtMs: Date.now() + AGENT_RUN_TERMINAL_RETRY_GRACE_MS,
settle
};
return true;
},
markExecutionStarted,
bindAgentRunDelegatedAuthority,
cleanup
};
}
/** Restore the newest visible run when chat.history switches back to its session.
* Match requested and canonical keys, with agent scoping for the shared global row.
*/
function resolveInFlightRunSnapshot(params) {
const matchesKey = (entry, key) => {
if (entry.sessionKey !== key) return false;
if (key !== "global") return true;
const requestedAgentId = normalizeOptionalLowercaseString(params.agentId) ?? normalizeOptionalLowercaseString(params.defaultAgentId);
if (!requestedAgentId) return false;
return (normalizeOptionalLowercaseString(entry.agentId) ?? normalizeOptionalLowercaseString(params.defaultAgentId)) === requestedAgentId;
};
if (!(params.chatAbortControllers instanceof Map)) return;
let best;
for (const [runId, entry] of params.chatAbortControllers) {
if (entry.projectSessionActive === false || entry.controlUiVisible === false || entry.controller.signal.aborted || entry.kind === "agent") continue;
if (!matchesKey(entry, params.requestedSessionKey) && !matchesKey(entry, params.canonicalSessionKey)) continue;
const newer = best === void 0 || entry.startedAtMs > best.startedAtMs;
const tie = best !== void 0 && entry.startedAtMs === best.startedAtMs && runId > best.runId;
if (newer || tie) best = {
runId,
startedAtMs: entry.startedAtMs
};
}
if (best === void 0) return;
return projectInFlightRunSnapshot({
chatRunState: params.chatRunState,
runId: best.runId,
startedAtMs: best.startedAtMs
});
}
function resolveChatAbortDeliverySessionKeys(ops, sessionKey, agentId) {
const scopedAgentId = normalizeOptionalLowercaseString(agentId);
if (!scopedAgentId) return [sessionKey];
const canonicalKey = resolveSessionSubscriptionKey(sessionKey, scopedAgentId);
if (canonicalKey === sessionKey) return [canonicalKey];
return resolveSessionSubscriptionKeys(sessionKey, scopedAgentId, resolveDefaultGlobalAgentId(ops));
}
function broadcastChatAborted(ops, params) {
const { runId, sessionKey, stopReason } = params;
const errorMessage = readToolValidationErrorSummary(params.errorMessage);
const explicitAgentId = normalizeOptionalLowercaseString(params.agentId);
const defaultGlobalAgentId = sessionKey === "global" && !explicitAgentId ? normalizeOptionalLowercaseString(resolveDefaultGlobalAgentId(ops)) : void 0;
const payloadAgentId = sessionKey === "global" ? explicitAgentId ?? defaultGlobalAgentId : explicitAgentId;
const payload = {
runId,
sessionKey,
...payloadAgentId ? { agentId: payloadAgentId } : {},
seq: (ops.agentRunSeq.get(runId) ?? 0) + 1,
state: "aborted",
stopReason,
...errorMessage ? { errorMessage } : {},
message: params.message ? {
...params.message,
timestamp: Date.now()
} : void 0
};
const deliverySessionKeys = resolveChatAbortDeliverySessionKeys(ops, sessionKey, payloadAgentId);
ops.broadcast("chat", payload, {
sessionKeys: deliverySessionKeys,
...params.liveTextGroup ? { liveText: { group: params.liveTextGroup } } : {}
});
for (const deliverySessionKey of deliverySessionKeys) ops.nodeSendToSession(deliverySessionKey, "chat", payload);
}
function resolveDefaultGlobalAgentId(ops) {
const cfg = ops.getRuntimeConfig?.();
if (!cfg) return;
const resolved = resolveRequestedSessionAgentId(cfg, "global");
return resolved.ok ? resolved.agentId : void 0;
}
function isChatAbortControllerEntryAbortable(entry) {
if (entry.controller.signal.aborted) return false;
try {
return entry.isAbortable?.(entry) !== false;
} catch {
return false;
}
}
function abortChatRunById(ops, params) {
const { runId, sessionKey, stopReason } = params;
const active = ops.chatAbortControllers.get(runId);
if (!active) return { aborted: false };
if (active.sessionKey !== sessionKey) return { aborted: false };
if (active.registrationCleanupRequested || !isChatAbortControllerEntryAbortable(active)) return { aborted: false };
const bufferedText = ops.chatRunState.resolveBuffer(runId, { final: true }).text;
const run = ops.chatRunState.runs.get(runId);
const liveTextGroup = run?.liveTextGroup?.signal;
const partialText = bufferedText && bufferedText.trim() ? bufferedText : void 0;
const canvasBlocks = run?.bufferIsCurrent?.() !== false && (partialText || !(run?.rawBuffer ?? run?.buffer ?? "").trim()) ? run?.canvasBlocks ?? [] : [];
const message = appendChatCanvasBlocksToMessage(partialText || canvasBlocks.length ? {
role: "assistant",
content: partialText ? [{
type: "text",
text: partialText
}] : []
} : void 0, canvasBlocks);
ops.chatRunState.getOrCreate(runId).abortMarker = createChatAbortMarker();
if (stopReason) active.abortStopReason = stopReason;
active.abortDiagnosticReason = params.diagnosticReason;
try {
params.onAbortCommitted?.();
} catch {}
active.projectSessionActive = false;
active.projectSessionTerminalPending = true;
active.projectSessionTerminalObservedAt = void 0;
active.registrationCleanupRequested = true;
if (active.agentRunDelegatedAuthority) releaseAgentRunDelegatedAuthority(active.agentRunDelegatedAuthority);
try {
ops.onRunAborted?.(runId);
} catch {}
active.controller.abort(createChatAbortSignalReason(stopReason));
ops.chatRunState.clearRun(runId);
const removed = ops.removeChatRun(runId, runId, sessionKey);
if (active.controlUiVisible !== false) broadcastChatAborted(ops, {
runId,
sessionKey,
agentId: active.agentId,
stopReason,
message,
errorMessage: active.toolErrorSummary,
liveTextGroup
});
emitAgentEvent({
runId,
...active.lifecycleGeneration ? { lifecycleGeneration: active.lifecycleGeneration } : {},
sessionKey,
sessionId: active.sessionId,
agentId: active.agentId,
stream: "lifecycle",
data: {
phase: "end",
status: "cancelled",
aborted: true,
stopReason,
...active.toolErrorSummary ? { toolErrorSummary: active.toolErrorSummary } : {},
startedAt: active.executionStarted === false ? void 0 : active.startedAtMs,
...active.executionStarted === false ? {
executionStarted: false,
providerStarted: false,
...stopReason === "timeout" ? { timeoutPhase: "queue" } : {}
} : {},
endedAt: Date.now()
}
});
if (ops.chatAbortControllers.get(runId) === active && active.projectSessionTerminalObservedAt === void 0 && !active.projectSessionTerminalPersistence) {
active.projectSessionTerminalPending = false;
removeChatAbortControllerEntry(ops.chatAbortControllers, runId, active);
}
ops.agentRunSeq.delete(runId);
if (removed?.clientRunId) ops.agentRunSeq.delete(removed.clientRunId);
return { aborted: true };
}
function updateChatRunProvider(chatAbortControllers, params) {
const entry = chatAbortControllers.get(params.runId);
if (!entry) return false;
entry.providerId = normalizeOptionalLowercaseString(params.providerId);
entry.authProviderId = normalizeOptionalLowercaseString(params.authProviderId);
return true;
}
function abortChatRunsForProvider(ops, params) {
const providerId = normalizeOptionalLowercaseString(params.providerId);
const agentId = normalizeOptionalLowercaseString(params.agentId);
if (!providerId) return { runIds: [] };
const compatibilityOwnerAgentId = agentId && tryResolveLegacyCompatibilityAgentId(params.cfg);
const matches = [...ops.chatAbortControllers.entries()].filter(([, entry]) => {
if (normalizeOptionalLowercaseString(entry.authProviderId) !== providerId && normalizeOptionalLowercaseString(entry.providerId) !== providerId) return false;
return !agentId || resolveChatRunOwnerAgentId({
agentId: entry.agentId,
sessionKey: entry.sessionKey,
defaultAgentId: compatibilityOwnerAgentId
}) === agentId;
});
const runIds = [];
for (const [runId, entry] of matches) if (abortChatRunById(ops, {
runId,
sessionKey: entry.sessionKey,
stopReason: params.stopReason
}).aborted) runIds.push(runId);
return { runIds };
}
//#endregion
export { registerChatAbortController as a, resolveInFlightRunSnapshot as c, projectInFlightRunSnapshot as i, updateChatRunProvider as l, abortChatRunsForProvider as n, resolveAgentRunExpiresAtMs as o, isChatAbortControllerEntryAbortable as r, resolveChatRunExpiresAtMs as s, abortChatRunById as t };