Files
openclaw/src/plugin-sdk/agent-harness-runtime.ts
2026-05-04 01:35:27 +01:00

229 lines
9.2 KiB
TypeScript

// Lightweight runtime surface for plugin-owned agent harnesses.
// Keep heavyweight tool construction out of this module so harness imports can
// register quickly inside gateway startup and Docker e2e runs.
import type { EmbeddedRunAttemptResult } from "../agents/pi-embedded-runner/run/types.js";
import { formatToolDetail, resolveToolDisplay } from "../agents/tool-display.js";
import { redactToolDetail } from "../logging/redact.js";
import { truncateUtf16Safe } from "../utils.js";
export const TOOL_PROGRESS_OUTPUT_MAX_CHARS = 8_000;
export type { AgentMessage } from "@mariozechner/pi-agent-core";
export type {
AgentHarness,
AgentHarnessAttemptParams,
AgentHarnessAttemptResult,
AgentHarnessCompactParams,
AgentHarnessCompactResult,
AgentHarnessDeliveryDefaults,
AgentHarnessResultClassification,
AgentHarnessResetParams,
AgentHarnessSupport,
AgentHarnessSupportContext,
} from "../agents/harness/types.js";
export type {
EmbeddedRunAttemptParams,
EmbeddedRunAttemptResult,
} from "../agents/pi-embedded-runner/run/types.js";
export type { ContextEngine as HarnessContextEngine } from "../context-engine/types.js";
export type { CompactEmbeddedPiSessionParams } from "../agents/pi-embedded-runner/compact.js";
export type { EmbeddedPiCompactResult } from "../agents/pi-embedded-runner/types.js";
export type { AnyAgentTool } from "../agents/tools/common.js";
export type { MessagingToolSend } from "../agents/pi-embedded-messaging.types.js";
export type { HeartbeatToolResponse } from "../auto-reply/heartbeat-tool-response.js";
export type { AgentApprovalEventData, AgentEventPayload } from "../infra/agent-events.js";
export type { ExecApprovalDecision } from "../infra/exec-approvals.js";
export type { NormalizedUsage } from "../agents/usage.js";
export type {
AgentToolResultMiddleware,
AgentToolResultMiddlewareContext,
AgentToolResultMiddlewareEvent,
AgentToolResultMiddlewareHarness,
AgentToolResultMiddlewareOptions,
AgentToolResultMiddlewareResult,
AgentToolResultMiddlewareRuntime,
OpenClawAgentToolResult,
} from "../plugins/agent-tool-result-middleware-types.js";
export type {
CodexAppServerExtensionContext,
CodexAppServerExtensionFactory,
CodexAppServerExtensionRuntime,
CodexAppServerToolResultEvent,
CodexAppServerToolResultHandlerResult,
} from "../plugins/codex-app-server-extension-types.js";
export type {
NativeHookRelayEvent,
NativeHookRelayProvider,
NativeHookRelayRegistrationHandle,
} from "../agents/harness/native-hook-relay.js";
export { VERSION as OPENCLAW_VERSION } from "../version.js";
export { formatErrorMessage } from "../infra/errors.js";
export { formatApprovalDisplayPath } from "../infra/approval-display-paths.js";
export { emitAgentEvent, onAgentEvent, resetAgentEventsForTest } from "../infra/agent-events.js";
export { runAgentCleanupStep } from "../agents/run-cleanup-timeout.js";
export { log as embeddedAgentLog } from "../agents/pi-embedded-runner/logger.js";
export { buildAgentRuntimePlan } from "../agents/runtime-plan/build.js";
export { classifyEmbeddedPiRunResultForModelFallback } from "../agents/pi-embedded-runner/result-fallback-classifier.js";
export { resolveEmbeddedAgentRuntime } from "../agents/pi-embedded-runner/runtime.js";
export { resolveUserPath } from "../utils.js";
export { callGatewayTool } from "../agents/tools/gateway.js";
export type { NodeListNode } from "../agents/tools/nodes-utils.js";
export {
listNodes,
resolveNodeIdFromList,
selectDefaultNodeFromList,
} from "../agents/tools/nodes-utils.js";
export { formatToolAggregate } from "../auto-reply/tool-meta.js";
export {
HEARTBEAT_RESPONSE_TOOL_NAME,
normalizeHeartbeatToolResponse,
} from "../auto-reply/heartbeat-tool-response.js";
export { isMessagingTool, isMessagingToolSendAction } from "../agents/pi-embedded-messaging.js";
export {
extractToolResultMediaArtifact,
filterToolResultMediaUrls,
} from "../agents/pi-embedded-subscribe.tools.js";
export { normalizeUsage } from "../agents/usage.js";
export { resolveOpenClawAgentDir } from "../agents/agent-paths.js";
export { resolveSessionAgentIds } from "../agents/agent-scope.js";
export { resolveModelAuthMode } from "../agents/model-auth.js";
export { supportsModelTools } from "../agents/model-tool-support.js";
export { resolveAttemptSpawnWorkspaceDir } from "../agents/pi-embedded-runner/run/attempt.thread-helpers.js";
export { buildEmbeddedAttemptToolRunContext } from "../agents/pi-embedded-runner/run/attempt.tool-run-context.js";
export {
abortEmbeddedPiRun as abortAgentHarnessRun,
clearActiveEmbeddedRun,
queueEmbeddedPiMessage as queueAgentHarnessMessage,
setActiveEmbeddedRun,
} from "../agents/pi-embedded-runner/runs.js";
export { disposeRegisteredAgentHarnesses } from "../agents/harness/registry.js";
export {
logAgentRuntimeToolDiagnostics,
normalizeAgentRuntimeTools,
} from "../agents/runtime-plan/tools.js";
export { normalizeProviderToolSchemas } from "../agents/pi-embedded-runner/tool-schema-runtime.js";
export { resolveSandboxContext } from "../agents/sandbox.js";
export { resolveBootstrapContextForRun } from "../agents/bootstrap-files.js";
export type { EmbeddedContextFile } from "../agents/pi-embedded-helpers/types.js";
export { isSubagentSessionKey } from "../routing/session-key.js";
export {
acquireSessionWriteLock,
resolveSessionWriteLockAcquireTimeoutMs,
type SessionWriteLockAcquireTimeoutConfig,
} from "../agents/session-write-lock.js";
export { appendSessionTranscriptMessage } from "../config/sessions/transcript-append.js";
export { emitSessionTranscriptUpdate } from "../sessions/transcript-events.js";
export {
isToolWrappedWithBeforeToolCallHook,
wrapToolWithBeforeToolCallHook,
} from "../agents/pi-tools.before-tool-call.js";
export {
resolveAgentHarnessBeforePromptBuildResult,
runAgentHarnessAfterCompactionHook,
runAgentHarnessBeforeCompactionHook,
} from "../agents/harness/prompt-compaction-hook-helpers.js";
export { createCodexAppServerToolResultExtensionRunner } from "../agents/harness/codex-app-server-extensions.js";
export { createAgentToolResultMiddlewareRunner } from "../agents/harness/tool-result-middleware.js";
export {
assembleHarnessContextEngine,
bootstrapHarnessContextEngine,
buildHarnessContextEngineRuntimeContext,
buildHarnessContextEngineRuntimeContextFromUsage,
finalizeHarnessContextEngineTurn,
isActiveHarnessContextEngine,
runHarnessContextEngineMaintenance,
} from "../agents/harness/context-engine-lifecycle.js";
export {
runAgentHarnessAfterToolCallHook,
runAgentHarnessBeforeMessageWriteHook,
} from "../agents/harness/hook-helpers.js";
export {
runAgentHarnessBeforeAgentFinalizeHook,
runAgentHarnessAgentEndHook,
runAgentHarnessLlmInputHook,
runAgentHarnessLlmOutputHook,
} from "../agents/harness/lifecycle-hook-helpers.js";
export {
buildNativeHookRelayCommand,
__testing as nativeHookRelayTesting,
registerNativeHookRelay,
} from "../agents/harness/native-hook-relay.js";
/**
* Derive the same compact user-facing tool detail that Pi uses for progress logs.
*/
export type ToolProgressDetailMode = "explain" | "raw";
export function inferToolMetaFromArgs(
toolName: string,
args: unknown,
options?: { detailMode?: ToolProgressDetailMode },
): string | undefined {
const display = resolveToolDisplay({ name: toolName, args, detailMode: options?.detailMode });
return formatToolDetail(display);
}
/**
* Prepare verbose tool output for user-facing progress messages.
*/
export function formatToolProgressOutput(
output: string,
options?: { maxChars?: number },
): string | undefined {
const trimmed = output.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim();
if (!trimmed) {
return undefined;
}
const redacted = redactToolDetail(trimmed);
const maxChars = options?.maxChars ?? TOOL_PROGRESS_OUTPUT_MAX_CHARS;
if (redacted.length <= maxChars) {
return redacted;
}
return `${truncateUtf16Safe(redacted, maxChars)}\n...(truncated)...`;
}
export type AgentHarnessTerminalOutcomeInput = {
assistantTexts: readonly string[];
reasoningText?: string | null;
planText?: string | null;
promptError?: unknown;
turnCompleted: boolean;
};
export type AgentHarnessTerminalOutcomeClassification = NonNullable<
EmbeddedRunAttemptResult["agentHarnessResultClassification"]
>;
/**
* Classify terminal harness turns that completed without assistant output that
* should advance fallback. Deliberate silent replies such as NO_REPLY count as
* intentional output, while whitespace-only text remains fallback-eligible.
* This is intentionally SDK-level so plugin harness adapters such as Codex
* preserve the same OpenClaw-owned fallback signals as the built-in PI path
* without re-implementing terminal-result policy.
*/
export function classifyAgentHarnessTerminalOutcome(
params: AgentHarnessTerminalOutcomeInput,
): AgentHarnessTerminalOutcomeClassification | undefined {
if (
!params.turnCompleted ||
(params.promptError !== undefined && params.promptError !== null) ||
hasVisibleAssistantText(params.assistantTexts)
) {
return undefined;
}
if (params.planText?.trim()) {
return "planning-only";
}
if (params.reasoningText?.trim()) {
return "reasoning-only";
}
return "empty";
}
function hasVisibleAssistantText(assistantTexts: readonly string[]): boolean {
return assistantTexts.some((text) => text.trim().length > 0);
}