mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-29 22:48:43 +00:00
* refactor: share talk event metric extraction * refactor: reuse shared coercion helpers * refactor: reuse shared primitive guards * refactor: reuse shared record guard * refactor: reuse shared primitive helpers * refactor: reuse shared string guards * refactor: reuse shared non-empty string guard * refactor: share plugin primitive coercion helpers * refactor: reuse plugin coercion helpers * refactor: reuse plugin coercion helpers in more plugins * refactor: reuse channel coercion helpers * refactor: reuse monitor coercion helpers * refactor: reuse provider coercion helpers * refactor: reuse core coercion helpers * refactor: reuse runtime coercion helpers * refactor: reuse helper coercion in codex paths * refactor: reuse helper coercion in runtime paths * refactor: reuse codex app-server coercion helpers * refactor: reuse codex record helpers * refactor: reuse migration and qa record helpers * refactor: reuse feishu and core helper guards * refactor: reuse browser and policy coercion helpers * refactor: reuse memory wiki record helper * refactor: share boolean coercion helpers * refactor: reuse finite number coercion * refactor: reuse trimmed string list helpers * refactor: reuse string list normalization * refactor: reuse remaining string list helpers * refactor: reuse string entry normalizer * refactor: share sorted string helpers * refactor: share string list normalization * test: preserve command registry browser imports * refactor: reuse trimmed list helpers * refactor: reuse string dedupe helpers * refactor: reuse local dedupe helpers * refactor: reuse more string dedupe helpers * refactor: reuse command string dedupe helpers * refactor: dedupe memory path lists with helper * refactor: expose string dedupe helpers to plugins * refactor: reuse core string dedupe helpers * refactor: reuse shared unique value helpers * refactor: reuse unique helpers in agent utilities * refactor: reuse unique helpers in config plumbing * refactor: reuse unique helpers in extensions * refactor: reuse unique helpers in core utilities * refactor: reuse unique helpers in qa plugins * refactor: reuse unique helpers in memory plugins * refactor: reuse unique helpers in channel plugins * refactor: reuse unique helpers in core tails * refactor: reuse unique helper in comfy workflow * refactor: reuse unique helpers in test utilities * refactor: expose unique value helper to plugins * refactor: reuse unique helpers for numeric lists * refactor: replace index dedupe filters * refactor: reuse string entry normalization * refactor: reuse string normalization in plugin helpers * refactor: reuse string normalization in extension helpers * refactor: reuse string normalization in channel parsers * refactor: reuse string normalization in memory search * refactor: reuse string normalization in provider parsers * refactor: reuse string normalization in qa helpers * refactor: reuse string normalization in infra parsers * refactor: reuse string normalization in messaging parsers * refactor: reuse string normalization in core parsers * refactor: reuse string normalization in extension parsers * refactor: reuse string normalization in remaining parsers * refactor: reuse string normalization in final parser spots * refactor: reuse string normalization in qa media helpers * refactor: reuse normalization in provider and media lists * refactor: reuse normalization for remaining set filters * refactor: reuse normalization in policy allowlists * refactor: reuse normalization in session and owner lists * refactor: centralize primitive string lists * refactor: reuse lowercase entry helpers * refactor: reuse sorted string helpers * refactor: reuse unique trimmed helpers * refactor: reuse string normalization helpers * refactor: reuse catalog string helpers * refactor: reuse remaining string helpers * refactor: simplify remaining list normalization * refactor: reuse codex auth order normalization * chore: refresh plugin sdk api baseline * fix: make shared string sorting deterministic * chore: refresh plugin sdk api baseline * fix: align host env security ordering
262 lines
8.9 KiB
TypeScript
262 lines
8.9 KiB
TypeScript
import { buildAnnounceIdempotencyKey } from "../agents/announce-idempotency.js";
|
|
import {
|
|
AGENT_INTERNAL_EVENT_TYPE_TASK_COMPLETION,
|
|
type AgentInternalEventStatus,
|
|
} from "../agents/internal-event-contract.js";
|
|
import {
|
|
formatAgentInternalEventsForPrompt,
|
|
type AgentInternalEvent,
|
|
} from "../agents/internal-events.js";
|
|
import {
|
|
deliverSubagentAnnouncement,
|
|
isInternalAnnounceRequesterSession,
|
|
loadRequesterSessionEntry,
|
|
resolveSubagentCompletionOrigin,
|
|
} from "../agents/subagent-announce-delivery.js";
|
|
import { resolveAnnounceOrigin } from "../agents/subagent-announce-origin.js";
|
|
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
|
import {
|
|
assertAgentHarnessTaskRuntimeScope,
|
|
type AgentHarnessTaskRuntimeScope,
|
|
} from "../tasks/agent-harness-task-runtime-scope.js";
|
|
import {
|
|
createRunningTaskRun,
|
|
finalizeTaskRunByRunId,
|
|
recordTaskRunProgressByRunId,
|
|
setDetachedTaskDeliveryStatusByRunId,
|
|
} from "../tasks/detached-task-runtime.js";
|
|
import { listTaskRecords, type TaskRecord } from "../tasks/runtime-internal.js";
|
|
import { INTERNAL_MESSAGE_CHANNEL } from "../utils/message-channel.js";
|
|
|
|
export type { TaskRecord as AgentHarnessTaskRecord };
|
|
export type { AgentHarnessTaskRuntimeScope };
|
|
|
|
type AgentHarnessTaskRuntimeId = Parameters<typeof createRunningTaskRun>[0]["runtime"];
|
|
type CreateRunningTaskRunParams = Parameters<typeof createRunningTaskRun>[0];
|
|
type RecordTaskRunProgressParams = Parameters<typeof recordTaskRunProgressByRunId>[0];
|
|
type FinalizeTaskRunParams = Parameters<typeof finalizeTaskRunByRunId>[0];
|
|
type SetDeliveryStatusParams = Parameters<typeof setDetachedTaskDeliveryStatusByRunId>[0];
|
|
|
|
export type AgentHarnessTaskRuntimeScopeParams = {
|
|
runtime: AgentHarnessTaskRuntimeId;
|
|
scope: AgentHarnessTaskRuntimeScope;
|
|
taskKind?: string;
|
|
runIdPrefix?: string;
|
|
};
|
|
|
|
export type AgentHarnessScopedCreateRunningTaskRunParams = Omit<
|
|
CreateRunningTaskRunParams,
|
|
"runtime" | "taskKind" | "requesterSessionKey" | "ownerKey" | "scopeKind"
|
|
> & {
|
|
runId: string;
|
|
};
|
|
|
|
export type AgentHarnessScopedRecordTaskRunProgressParams = Omit<
|
|
RecordTaskRunProgressParams,
|
|
"runtime" | "sessionKey"
|
|
>;
|
|
|
|
export type AgentHarnessScopedFinalizeTaskRunParams = Omit<
|
|
FinalizeTaskRunParams,
|
|
"runtime" | "sessionKey"
|
|
>;
|
|
|
|
export type AgentHarnessScopedSetDeliveryStatusParams = Omit<
|
|
SetDeliveryStatusParams,
|
|
"runtime" | "sessionKey"
|
|
>;
|
|
|
|
export type AgentHarnessTaskRuntime = {
|
|
createRunningTaskRun(params: AgentHarnessScopedCreateRunningTaskRunParams): TaskRecord;
|
|
recordTaskRunProgressByRunId(params: AgentHarnessScopedRecordTaskRunProgressParams): TaskRecord[];
|
|
finalizeTaskRunByRunId(params: AgentHarnessScopedFinalizeTaskRunParams): TaskRecord[];
|
|
setDetachedTaskDeliveryStatusByRunId(
|
|
params: AgentHarnessScopedSetDeliveryStatusParams,
|
|
): TaskRecord[];
|
|
listTaskRecords(): TaskRecord[];
|
|
};
|
|
|
|
export type AgentHarnessCompletionStatus = "succeeded" | "failed" | "cancelled";
|
|
|
|
export type AgentHarnessCompletionDelivery = Awaited<
|
|
ReturnType<typeof deliverSubagentAnnouncement>
|
|
>;
|
|
|
|
const AGENT_HARNESS_COMPLETION_SOURCE_TOOL = "agent_harness_task";
|
|
|
|
export function createAgentHarnessTaskRuntime(
|
|
params: AgentHarnessTaskRuntimeScopeParams,
|
|
): AgentHarnessTaskRuntime {
|
|
const runtime = params.runtime;
|
|
const scope = assertAgentHarnessTaskRuntimeScope(params.scope);
|
|
const requesterSessionKey = scope.requesterSessionKey;
|
|
const taskKind = normalizeOptionalString(params.taskKind);
|
|
const runIdPrefix = normalizeOptionalString(params.runIdPrefix);
|
|
const assertRunId = (runId: string) => assertScopedRunId(runId, runIdPrefix);
|
|
return {
|
|
createRunningTaskRun(taskParams) {
|
|
assertRunId(taskParams.runId);
|
|
return createRunningTaskRun({
|
|
...taskParams,
|
|
runtime,
|
|
...(taskKind ? { taskKind } : {}),
|
|
requesterSessionKey,
|
|
ownerKey: requesterSessionKey,
|
|
scopeKind: "session",
|
|
});
|
|
},
|
|
recordTaskRunProgressByRunId(taskParams) {
|
|
assertRunId(taskParams.runId);
|
|
return recordTaskRunProgressByRunId({
|
|
...taskParams,
|
|
runtime,
|
|
sessionKey: requesterSessionKey,
|
|
});
|
|
},
|
|
finalizeTaskRunByRunId(taskParams) {
|
|
assertRunId(taskParams.runId);
|
|
return finalizeTaskRunByRunId({
|
|
...taskParams,
|
|
runtime,
|
|
sessionKey: requesterSessionKey,
|
|
});
|
|
},
|
|
setDetachedTaskDeliveryStatusByRunId(taskParams) {
|
|
assertRunId(taskParams.runId);
|
|
return setDetachedTaskDeliveryStatusByRunId({
|
|
...taskParams,
|
|
runtime,
|
|
sessionKey: requesterSessionKey,
|
|
});
|
|
},
|
|
listTaskRecords() {
|
|
return listTaskRecords().filter(
|
|
(task) =>
|
|
task.runtime === runtime &&
|
|
(!taskKind || task.taskKind === taskKind) &&
|
|
task.scopeKind === "session" &&
|
|
task.ownerKey === requesterSessionKey &&
|
|
(!runIdPrefix || task.runId?.startsWith(runIdPrefix)),
|
|
);
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function deliverAgentHarnessTaskCompletion(params: {
|
|
scope: AgentHarnessTaskRuntimeScope;
|
|
childSessionKey: string;
|
|
childSessionId: string;
|
|
announceId: string;
|
|
status: AgentHarnessCompletionStatus;
|
|
statusLabel?: string;
|
|
result: string;
|
|
taskLabel?: string;
|
|
announceType?: string;
|
|
replyInstruction?: string;
|
|
signal?: AbortSignal;
|
|
}): Promise<AgentHarnessCompletionDelivery> {
|
|
const scope = assertAgentHarnessTaskRuntimeScope(params.scope);
|
|
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 = mapHarnessCompletionStatus(params.status);
|
|
const requesterIsSubagent = isInternalAnnounceRequesterSession(requesterSessionKey);
|
|
let directOrigin = scope.requesterOrigin;
|
|
if (!requesterIsSubagent) {
|
|
const { entry } = loadRequesterSessionEntry(requesterSessionKey);
|
|
directOrigin = resolveAnnounceOrigin(entry, scope.requesterOrigin);
|
|
}
|
|
const completionDirectOrigin =
|
|
requesterIsSubagent || !directOrigin
|
|
? directOrigin
|
|
: await resolveSubagentCompletionOrigin({
|
|
childSessionKey,
|
|
requesterSessionKey,
|
|
requesterOrigin: directOrigin,
|
|
childRunId: childSessionKey,
|
|
spawnMode: "run",
|
|
expectsCompletionMessage: true,
|
|
});
|
|
const internalEvents: AgentInternalEvent[] = [
|
|
{
|
|
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);
|
|
return await deliverSubagentAnnouncement({
|
|
requesterSessionKey,
|
|
announceId: params.announceId,
|
|
triggerMessage: prompt,
|
|
steerMessage: prompt,
|
|
internalEvents,
|
|
summaryLine: taskLabel,
|
|
requesterSessionOrigin: scope.requesterOrigin,
|
|
requesterOrigin: completionDirectOrigin ?? directOrigin,
|
|
completionDirectOrigin: completionDirectOrigin ?? directOrigin,
|
|
directOrigin,
|
|
sourceSessionKey: childSessionKey,
|
|
sourceChannel: INTERNAL_MESSAGE_CHANNEL,
|
|
sourceTool: AGENT_HARNESS_COMPLETION_SOURCE_TOOL,
|
|
targetRequesterSessionKey: requesterSessionKey,
|
|
requesterIsSubagent,
|
|
expectsCompletionMessage: true,
|
|
bestEffortDeliver: true,
|
|
directIdempotencyKey: buildAnnounceIdempotencyKey(params.announceId),
|
|
signal: params.signal,
|
|
});
|
|
}
|
|
|
|
function mapHarnessCompletionStatus(
|
|
status: AgentHarnessCompletionStatus,
|
|
): AgentInternalEventStatus {
|
|
if (status === "succeeded") {
|
|
return "ok";
|
|
}
|
|
return "error";
|
|
}
|
|
|
|
export function isDurableAgentHarnessCompletionDelivery(
|
|
delivery: AgentHarnessCompletionDelivery,
|
|
): boolean {
|
|
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 : undefined;
|
|
if (!phases) {
|
|
return true;
|
|
}
|
|
return phases.some(
|
|
(phase) => phase.phase === "direct-primary" && phase.delivered && phase.path === "direct",
|
|
);
|
|
}
|
|
|
|
function assertScopedRunId(runId: string, runIdPrefix: string | undefined): void {
|
|
const normalized = runId.trim();
|
|
if (!normalized) {
|
|
throw new Error("Agent harness task runtime requires runId");
|
|
}
|
|
if (runIdPrefix && !normalized.startsWith(runIdPrefix)) {
|
|
throw new Error("Agent harness task runId is outside the configured scope");
|
|
}
|
|
}
|