mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 03:45:46 -06:00
7fd723b515
* fix(agent): apply steering before unstarted tools Restore steering checkpoints before sequential tool launches and before parallel batch launch. Preserve paired synthetic tool results, async callback compatibility, and Code Mode outcome handling. * fix(agent): delay tool loop admission commits Commit loop-detection history only for calls crossing the final launch checkpoint. Release steering-skipped markers, add repeated-steer coverage, and align remaining steering contract text. * fix(agent): keep tool admission lifecycle internal Attach delayed admission callbacks through the private internal-hooks seam so steering history remains correct without widening the public Agent Core or Plugin SDK contract. * fix(agent): preserve steering API contracts Keep public steering callbacks Promise-based and protocol error kinds unchanged. Use private synchronous draining and structured skip details to retain launch-boundary behavior without API or generated protocol drift. * test(gateway): use canonical steering fixture config Use keyed agent entries in the real gateway steering harness so current main does not migrate the fixture during startup. * fix(agent): remove unused lifecycle re-export * fix(agent): gate tool launch after wrapper preflight Split OpenClaw tool execution into private prepare and launch phases so steering is checked after policy, approval, validation, and reconciliation but before the original side effect. Preserve final arguments, voice grants, loop admission, context wrappers, and direct tool execution. * fix(agent): preserve steering callback receiver Invoke public steering callbacks with their AgentLoopConfig receiver and cover method-style implementations that read config-owned queue state.
134 lines
4.3 KiB
TypeScript
134 lines
4.3 KiB
TypeScript
import type {
|
|
AgentLoopConfig,
|
|
AgentMessage,
|
|
AgentToolResult,
|
|
AgentToolUpdateCallback,
|
|
InternalBeforeToolBatchContext,
|
|
InternalBeforeToolBatchResult,
|
|
} from "./types.js";
|
|
|
|
export type InternalBeforeToolBatchHook = (
|
|
context: InternalBeforeToolBatchContext,
|
|
signal?: AbortSignal,
|
|
) => Promise<InternalBeforeToolBatchResult | undefined>;
|
|
|
|
const beforeToolBatchByAgent = new WeakMap<object, InternalBeforeToolBatchHook>();
|
|
|
|
type InternalReadyToolCall = { toolCallId: string; args: unknown };
|
|
|
|
export type InternalToolBatchLifecycle = {
|
|
/** Commit admitted calls whose tool implementations are about to start. May throw before launch. */
|
|
commitReadyCalls: (calls: readonly InternalReadyToolCall[]) => void;
|
|
/** Release admission state for admitted prepared calls suppressed by steering. */
|
|
releaseSkippedCalls: (toolCallIds: readonly string[]) => void;
|
|
};
|
|
|
|
const toolBatchLifecycleByResult = new WeakMap<
|
|
InternalBeforeToolBatchResult,
|
|
InternalToolBatchLifecycle
|
|
>();
|
|
|
|
type InternalSteeringGetter = NonNullable<AgentLoopConfig["getSteeringMessages"]>;
|
|
type InternalSyncSteeringGetter = () => AgentMessage[];
|
|
const syncSteeringGetterByCallback = new WeakMap<
|
|
InternalSteeringGetter,
|
|
InternalSyncSteeringGetter
|
|
>();
|
|
|
|
export type InternalToolExecutionPreparation =
|
|
| {
|
|
kind: "immediate";
|
|
outcome:
|
|
| { kind: "result"; result: AgentToolResult<unknown>; isError: boolean }
|
|
| { kind: "error"; error: unknown };
|
|
dispose: () => void;
|
|
}
|
|
| {
|
|
kind: "ready";
|
|
args: unknown;
|
|
execute: (onImplementationStart?: () => void) => Promise<AgentToolResult<unknown>>;
|
|
dispose: () => void;
|
|
};
|
|
|
|
export type InternalToolExecutionPreparer = (params: {
|
|
toolCallId: string;
|
|
args: unknown;
|
|
signal?: AbortSignal;
|
|
onUpdate?: AgentToolUpdateCallback;
|
|
executionArgs?: unknown[];
|
|
}) => Promise<InternalToolExecutionPreparation>;
|
|
|
|
const toolExecutionPreparerByTool = new WeakMap<object, InternalToolExecutionPreparer>();
|
|
|
|
/** Install OpenClaw-owned loop control without adding a plugin-facing Agent option. */
|
|
export function setInternalBeforeToolBatch(
|
|
agent: object,
|
|
hook: InternalBeforeToolBatchHook | undefined,
|
|
): void {
|
|
if (hook) {
|
|
beforeToolBatchByAgent.set(agent, hook);
|
|
} else {
|
|
beforeToolBatchByAgent.delete(agent);
|
|
}
|
|
}
|
|
|
|
export function getInternalBeforeToolBatch(agent: object): InternalBeforeToolBatchHook | undefined {
|
|
return beforeToolBatchByAgent.get(agent);
|
|
}
|
|
|
|
/** Attach scheduler lifecycle ownership without widening the public admission result. */
|
|
export function attachInternalToolBatchLifecycle(
|
|
result: InternalBeforeToolBatchResult,
|
|
lifecycle: InternalToolBatchLifecycle,
|
|
): InternalBeforeToolBatchResult {
|
|
toolBatchLifecycleByResult.set(result, lifecycle);
|
|
return result;
|
|
}
|
|
|
|
export function takeInternalToolBatchLifecycle(
|
|
result: InternalBeforeToolBatchResult,
|
|
): InternalToolBatchLifecycle | undefined {
|
|
const lifecycle = toolBatchLifecycleByResult.get(result);
|
|
toolBatchLifecycleByResult.delete(result);
|
|
return lifecycle;
|
|
}
|
|
|
|
/** Attach Agent-owned synchronous draining to the exact public async callback identity. */
|
|
export function attachInternalSyncSteeringGetter(
|
|
callback: InternalSteeringGetter,
|
|
syncGetter: InternalSyncSteeringGetter,
|
|
): InternalSteeringGetter {
|
|
syncSteeringGetterByCallback.set(callback, syncGetter);
|
|
return callback;
|
|
}
|
|
|
|
export function getInternalSyncSteeringGetter(
|
|
callback: InternalSteeringGetter,
|
|
): InternalSyncSteeringGetter | undefined {
|
|
return syncSteeringGetterByCallback.get(callback);
|
|
}
|
|
|
|
/** Attach OpenClaw-owned two-phase execution without changing the public AgentTool shape. */
|
|
export function attachInternalToolExecutionPreparer<T extends object>(
|
|
tool: T,
|
|
preparer: InternalToolExecutionPreparer,
|
|
): T {
|
|
toolExecutionPreparerByTool.set(tool, preparer);
|
|
return tool;
|
|
}
|
|
|
|
export function getInternalToolExecutionPreparer(
|
|
tool: object,
|
|
): InternalToolExecutionPreparer | undefined {
|
|
return toolExecutionPreparerByTool.get(tool);
|
|
}
|
|
|
|
/** Preserve private execution ownership when an adapter replaces a tool object. */
|
|
export function copyInternalToolExecutionPreparer<T extends object>(source: object, target: T): T {
|
|
const preparer = toolExecutionPreparerByTool.get(source);
|
|
if (preparer) {
|
|
toolExecutionPreparerByTool.set(target, preparer);
|
|
}
|
|
return target;
|
|
}
|