File size: 3,502 Bytes
4440aec | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | // Recognizes stop requests before loading active-run cancellation machinery.
import {
normalizeOptionalLowercaseString,
normalizeOptionalString,
} from "@openclaw/normalization-core/string-coerce";
import { resolveSessionAgentId } from "../../agents/agent-scope.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import type { FinalizedRuntimeMsgContext } from "../templating.js";
import { isAbortRequestText } from "./abort-primitives.js";
import { stripMentions, stripStructuralPrefixes } from "./mentions.js";
export type FastAbortRequestParams = {
ctx: FinalizedRuntimeMsgContext;
cfg: OpenClawConfig;
isCommandTargetCurrent?: () => boolean;
};
export type FastAbortResult = {
handled: boolean;
aborted: boolean;
rejectionReason?: "finalizing";
stoppedSubagents?: number;
failedSubagents?: number;
};
export function formatAbortReplyText(
stoppedSubagents?: number,
rejectionReason?: "finalizing",
failedSubagents?: number,
): string {
const failureSuffix =
typeof failedSubagents === "number" && failedSubagents > 0
? ` Cancellation was incomplete for ${failedSubagents} sub-agent${failedSubagents === 1 ? "" : "s"}. Retry /stop.`
: "";
if (rejectionReason === "finalizing") {
const base = "Agent reply is already finalizing and can no longer be aborted.";
if (typeof stoppedSubagents !== "number" || stoppedSubagents <= 0) {
return `${base}${failureSuffix}`;
}
const label = stoppedSubagents === 1 ? "sub-agent" : "sub-agents";
return `${base} Stopped ${stoppedSubagents} ${label}.${failureSuffix}`;
}
if (typeof stoppedSubagents !== "number" || stoppedSubagents <= 0) {
return `⚙️ Agent was aborted.${failureSuffix}`;
}
const label = stoppedSubagents === 1 ? "sub-agent" : "sub-agents";
return `⚙️ Agent was aborted. Stopped ${stoppedSubagents} ${label}.${failureSuffix}`;
}
/** Normalize ingress once; current authorization belongs to the loaded operation. */
function resolveFastAbortRequest(params: FastAbortRequestParams) {
const { ctx, cfg } = params;
const commandSessionKey =
normalizeOptionalString(ctx.SessionKey) ?? normalizeOptionalString(ctx.ParentSessionKey);
const targetKey = normalizeOptionalString(ctx.CommandTargetSessionKey) ?? commandSessionKey;
const resolveTargetAgentId = () =>
resolveSessionAgentId({
sessionKey: targetKey ?? ctx.SessionKey ?? "",
config: cfg,
fallbackAgentId: ctx.AgentId,
});
const raw = stripStructuralPrefixes(ctx.commandText);
const isGroup = normalizeOptionalLowercaseString(ctx.ChatType) === "group";
const stripped = isGroup ? stripMentions(raw, ctx, cfg, resolveTargetAgentId()) : raw;
const abortRequested = isAbortRequestText(stripped);
if (!abortRequested) {
return undefined;
}
return { commandSessionKey, targetKey, resolveTargetAgentId };
}
export type PreparedFastAbortRequest = NonNullable<ReturnType<typeof resolveFastAbortRequest>>;
export async function tryFastAbortFromMessage(
params: FastAbortRequestParams,
): Promise<FastAbortResult> {
const request = resolveFastAbortRequest(params);
if (!request) {
return { handled: false, aborted: false };
}
// Normalized ingress is not authority. The operation resolves authorization
// and retains current-target validation after loading cancellation machinery.
const { executeFastAbortRequest } = await import("./abort-operation.js");
return executeFastAbortRequest(params, request);
}
|