Files
openclaw/src/plugins/host-hooks.ts
EVA 1adaa28dc8 [plugin sdk] Add generic plugin host-hook contracts (#72287)
Merged via squash.

Prepared head SHA: 68e5f2ce19
Co-authored-by: 100yenadmin <239388517+100yenadmin@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-27 17:07:02 -07:00

189 lines
5.2 KiB
TypeScript

import type { OperatorScope } from "../gateway/operator-scopes.js";
import type { AgentEventPayload, AgentEventStream } from "../infra/agent-events.js";
import type {
PluginHookAgentContext,
PluginHookBeforeToolCallEvent,
PluginHookBeforeToolCallResult,
PluginHookToolContext,
} from "./hook-types.js";
import type { PluginJsonValue } from "./host-hook-json.js";
import type {
PluginAgentTurnPrepareResult,
PluginNextTurnInjectionPlacement,
PluginNextTurnInjectionRecord,
} from "./host-hook-turn-types.js";
export { isPluginJsonValue } from "./host-hook-json.js";
export type { PluginJsonPrimitive, PluginJsonValue } from "./host-hook-json.js";
export type {
PluginAgentTurnPrepareEvent,
PluginAgentTurnPrepareResult,
PluginHeartbeatPromptContributionEvent,
PluginHeartbeatPromptContributionResult,
PluginNextTurnInjection,
PluginNextTurnInjectionEnqueueResult,
PluginNextTurnInjectionPlacement,
PluginNextTurnInjectionRecord,
} from "./host-hook-turn-types.js";
export type PluginHostCleanupReason = "disable" | "reset" | "delete" | "restart";
export type PluginSessionExtensionProjectionContext = {
sessionKey: string;
sessionId?: string;
state: PluginJsonValue | undefined;
};
export type PluginSessionExtensionRegistration = {
namespace: string;
description: string;
project?: (ctx: PluginSessionExtensionProjectionContext) => PluginJsonValue | undefined;
cleanup?: (ctx: { reason: PluginHostCleanupReason; sessionKey?: string }) => void | Promise<void>;
};
export type PluginSessionExtensionProjection = {
pluginId: string;
namespace: string;
value: PluginJsonValue;
};
export type PluginSessionExtensionPatchParams = {
key: string;
pluginId: string;
namespace: string;
value?: PluginJsonValue;
unset?: boolean;
};
export type PluginToolPolicyDecision =
| PluginHookBeforeToolCallResult
| {
allow?: boolean;
reason?: string;
};
export type PluginTrustedToolPolicyRegistration = {
id: string;
description: string;
evaluate: (
event: PluginHookBeforeToolCallEvent,
ctx: PluginHookToolContext,
) => PluginToolPolicyDecision | void | Promise<PluginToolPolicyDecision | void>;
};
export type PluginToolMetadataRegistration = {
toolName: string;
displayName?: string;
description?: string;
risk?: "low" | "medium" | "high";
tags?: string[];
};
export type PluginCommandContinuation = {
continueAgent?: boolean;
};
export type PluginControlUiDescriptor = {
id: string;
surface: "session" | "tool" | "run" | "settings";
label: string;
description?: string;
placement?: string;
schema?: PluginJsonValue;
requiredScopes?: OperatorScope[];
};
export type PluginRuntimeLifecycleRegistration = {
id: string;
description?: string;
cleanup?: (ctx: {
reason: PluginHostCleanupReason;
sessionKey?: string;
runId?: string;
}) => void | Promise<void>;
};
export type PluginAgentEventSubscriptionRegistration = {
id: string;
description?: string;
streams?: AgentEventStream[];
handle: (
event: AgentEventPayload,
ctx: {
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Run-context JSON reads are caller-typed by namespace.
getRunContext: <T extends PluginJsonValue = PluginJsonValue>(
namespace: string,
) => T | undefined;
setRunContext: (namespace: string, value: PluginJsonValue) => void;
clearRunContext: (namespace?: string) => void;
},
) => void | Promise<void>;
};
export type PluginRunContextPatch = {
runId: string;
namespace: string;
value?: PluginJsonValue;
unset?: boolean;
};
export type PluginRunContextGetParams = {
runId: string;
namespace: string;
};
export type PluginSessionSchedulerJobRegistration = {
id: string;
sessionKey: string;
kind: string;
description?: string;
cleanup?: (ctx: {
reason: PluginHostCleanupReason;
sessionKey: string;
jobId: string;
}) => void | Promise<void>;
};
export type PluginSessionSchedulerJobHandle = {
id: string;
pluginId: string;
sessionKey: string;
kind: string;
};
export function normalizePluginHostHookId(value: string | undefined): string {
return (value ?? "").trim();
}
function normalizeQueuedInjectionText(
entry: PluginNextTurnInjectionRecord,
placement: PluginNextTurnInjectionPlacement,
): string | undefined {
const candidate = entry as {
placement?: unknown;
text?: unknown;
};
if (candidate.placement !== placement || typeof candidate.text !== "string") {
return undefined;
}
const text = candidate.text.trim();
return text || undefined;
}
export function buildPluginAgentTurnPrepareContext(params: {
queuedInjections: PluginNextTurnInjectionRecord[];
}): PluginAgentTurnPrepareResult {
const prepend = params.queuedInjections
.map((entry) => normalizeQueuedInjectionText(entry, "prepend_context"))
.filter(Boolean);
const append = params.queuedInjections
.map((entry) => normalizeQueuedInjectionText(entry, "append_context"))
.filter(Boolean);
return {
...(prepend.length > 0 ? { prependContext: prepend.join("\n\n") } : {}),
...(append.length > 0 ? { appendContext: append.join("\n\n") } : {}),
};
}
export type PluginHostHookRunContext = PluginHookAgentContext;