refactor: move tasks into bundled plugin

This commit is contained in:
Peter Steinberger
2026-03-31 14:48:22 +01:00
parent 584db0aff2
commit c75f4695b7
39 changed files with 2492 additions and 736 deletions

View File

@@ -39,6 +39,7 @@ export type BuildPluginApiParams = {
| "registerMemoryFlushPlan"
| "registerMemoryRuntime"
| "registerMemoryEmbeddingProvider"
| "registerOperationsRuntime"
| "on"
>
>;
@@ -69,6 +70,7 @@ const noopRegisterMemoryFlushPlan: OpenClawPluginApi["registerMemoryFlushPlan"]
const noopRegisterMemoryRuntime: OpenClawPluginApi["registerMemoryRuntime"] = () => {};
const noopRegisterMemoryEmbeddingProvider: OpenClawPluginApi["registerMemoryEmbeddingProvider"] =
() => {};
const noopRegisterOperationsRuntime: OpenClawPluginApi["registerOperationsRuntime"] = () => {};
const noopOn: OpenClawPluginApi["on"] = () => {};
export function buildPluginApi(params: BuildPluginApiParams): OpenClawPluginApi {
@@ -112,6 +114,7 @@ export function buildPluginApi(params: BuildPluginApiParams): OpenClawPluginApi
registerMemoryRuntime: handlers.registerMemoryRuntime ?? noopRegisterMemoryRuntime,
registerMemoryEmbeddingProvider:
handlers.registerMemoryEmbeddingProvider ?? noopRegisterMemoryEmbeddingProvider,
registerOperationsRuntime: handlers.registerOperationsRuntime ?? noopRegisterOperationsRuntime,
resolvePath: params.resolvePath,
on: handlers.on ?? noopOn,
};

View File

@@ -48,5 +48,6 @@ describe("captured plugin registration", () => {
expect(captured.tools.map((tool) => tool.name)).toEqual(["captured-tool"]);
expect(captured.providers.map((provider) => provider.id)).toEqual(["captured-provider"]);
expect(captured.api.registerMemoryEmbeddingProvider).toBeTypeOf("function");
expect(captured.api.registerOperationsRuntime).toBeTypeOf("function");
});
});

View File

@@ -30,6 +30,10 @@ import {
registerMemoryRuntime,
resolveMemoryFlushPlan,
} from "./memory-state.js";
import {
getRegisteredOperationsRuntime,
registerOperationsRuntimeForOwner,
} from "./operations-state.js";
import { createEmptyPluginRegistry } from "./registry.js";
import {
getActivePluginRegistry,
@@ -1461,6 +1465,181 @@ module.exports = { id: "skipped-scoped-only", register() { throw new Error("skip
expect(listMemoryEmbeddingProviders()).toEqual([]);
});
it("restores the active operations runtime during snapshot loads", () => {
const activeRuntime = {
async dispatch() {
return { matched: true, created: true, record: null };
},
async getById() {
return null;
},
async findByRunId() {
return null;
},
async list() {
return [];
},
async summarize() {
return {
total: 0,
active: 0,
terminal: 0,
failures: 0,
byNamespace: { active: 0 },
byKind: {},
byStatus: {},
};
},
async audit() {
return [];
},
async maintenance() {
return {
reconciled: 0,
cleanupStamped: 0,
pruned: 0,
};
},
async cancel() {
return { found: false, cancelled: false, reason: "active" };
},
};
registerOperationsRuntimeForOwner(activeRuntime, "active-operations");
const plugin = writePlugin({
id: "snapshot-operations",
filename: "snapshot-operations.cjs",
body: `module.exports = {
id: "snapshot-operations",
register(api) {
api.registerOperationsRuntime({
async dispatch() {
return { matched: true, created: true, record: null };
},
async getById() {
return null;
},
async findByRunId() {
return null;
},
async list() {
return [];
},
async summarize() {
return {
total: 1,
active: 1,
terminal: 0,
failures: 0,
byNamespace: { snapshot: 1 },
byKind: { snapshot: 1 },
byStatus: { queued: 1 },
};
},
async audit() {
return [];
},
async maintenance() {
return {
reconciled: 0,
cleanupStamped: 0,
pruned: 0,
};
},
async cancel() {
return { found: false, cancelled: false, reason: "snapshot" };
},
});
},
};`,
});
const scoped = loadOpenClawPlugins({
cache: false,
activate: false,
workspaceDir: plugin.dir,
config: {
plugins: {
load: { paths: [plugin.file] },
allow: ["snapshot-operations"],
},
},
onlyPluginIds: ["snapshot-operations"],
});
expect(scoped.plugins.find((entry) => entry.id === "snapshot-operations")?.status).toBe(
"loaded",
);
expect(getRegisteredOperationsRuntime()).toBe(activeRuntime);
});
it("clears newly-registered operations runtime when plugin register fails", () => {
const plugin = writePlugin({
id: "failing-operations",
filename: "failing-operations.cjs",
body: `module.exports = {
id: "failing-operations",
register(api) {
api.registerOperationsRuntime({
async dispatch() {
return { matched: true, created: true, record: null };
},
async getById() {
return null;
},
async findByRunId() {
return null;
},
async list() {
return [];
},
async summarize() {
return {
total: 1,
active: 1,
terminal: 0,
failures: 0,
byNamespace: { failing: 1 },
byKind: { failing: 1 },
byStatus: { queued: 1 },
};
},
async audit() {
return [];
},
async maintenance() {
return {
reconciled: 0,
cleanupStamped: 0,
pruned: 0,
};
},
async cancel() {
return { found: false, cancelled: false, reason: "failing" };
},
});
throw new Error("operations register failed");
},
};`,
});
const registry = loadOpenClawPlugins({
cache: false,
workspaceDir: plugin.dir,
config: {
plugins: {
load: { paths: [plugin.file] },
allow: ["failing-operations"],
},
},
onlyPluginIds: ["failing-operations"],
});
expect(registry.plugins.find((entry) => entry.id === "failing-operations")?.status).toBe(
"error",
);
expect(getRegisteredOperationsRuntime()).toBeUndefined();
});
it("throws when activate:false is used without cache:false", () => {
expect(() => loadOpenClawPlugins({ activate: false })).toThrow(
"activate:false requires cache:false",

View File

@@ -35,6 +35,12 @@ import {
getMemoryRuntime,
restoreMemoryPluginState,
} from "./memory-state.js";
import {
clearOperationsRuntimeState,
getRegisteredOperationsRuntime,
getRegisteredOperationsRuntimeOwner,
restoreOperationsRuntimeState,
} from "./operations-state.js";
import { isPathInside, safeStatSync } from "./path-safety.js";
import { createPluginRegistry, type PluginRecord, type PluginRegistry } from "./registry.js";
import { resolvePluginCacheInputs } from "./roots.js";
@@ -116,6 +122,8 @@ type CachedPluginState = {
memoryFlushPlanResolver: ReturnType<typeof getMemoryFlushPlanResolver>;
memoryPromptBuilder: ReturnType<typeof getMemoryPromptSectionBuilder>;
memoryRuntime: ReturnType<typeof getMemoryRuntime>;
operationsRuntime: ReturnType<typeof getRegisteredOperationsRuntime>;
operationsRuntimeOwner: ReturnType<typeof getRegisteredOperationsRuntimeOwner>;
};
const MAX_PLUGIN_REGISTRY_CACHE_ENTRIES = 128;
@@ -136,6 +144,7 @@ const LAZY_RUNTIME_REFLECTION_KEYS = [
"logging",
"state",
"modelAuth",
"operations",
] as const satisfies readonly (keyof PluginRuntime)[];
export function clearPluginLoaderCache(): void {
@@ -143,6 +152,7 @@ export function clearPluginLoaderCache(): void {
openAllowlistWarningCache.clear();
clearMemoryEmbeddingProviders();
clearMemoryPluginState();
clearOperationsRuntimeState();
}
const defaultLogger = () => createSubsystemLogger("plugins");
@@ -843,6 +853,10 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
flushPlanResolver: cached.memoryFlushPlanResolver,
runtime: cached.memoryRuntime,
});
restoreOperationsRuntimeState({
runtime: cached.operationsRuntime,
ownerPluginId: cached.operationsRuntimeOwner,
});
if (shouldActivate) {
activatePluginRegistry(cached.registry, cacheKey, runtimeSubagentMode);
}
@@ -1336,6 +1350,8 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
const previousMemoryFlushPlanResolver = getMemoryFlushPlanResolver();
const previousMemoryPromptBuilder = getMemoryPromptSectionBuilder();
const previousMemoryRuntime = getMemoryRuntime();
const previousOperationsRuntime = getRegisteredOperationsRuntime();
const previousOperationsRuntimeOwner = getRegisteredOperationsRuntimeOwner();
try {
const result = register(api);
@@ -1355,6 +1371,10 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
flushPlanResolver: previousMemoryFlushPlanResolver,
runtime: previousMemoryRuntime,
});
restoreOperationsRuntimeState({
runtime: previousOperationsRuntime,
ownerPluginId: previousOperationsRuntimeOwner,
});
}
registry.plugins.push(record);
seenIds.set(pluginId, candidate.origin);
@@ -1365,6 +1385,10 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
flushPlanResolver: previousMemoryFlushPlanResolver,
runtime: previousMemoryRuntime,
});
restoreOperationsRuntimeState({
runtime: previousOperationsRuntime,
ownerPluginId: previousOperationsRuntimeOwner,
});
recordPluginError({
logger,
registry,
@@ -1404,6 +1428,8 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
memoryFlushPlanResolver: getMemoryFlushPlanResolver(),
memoryPromptBuilder: getMemoryPromptSectionBuilder(),
memoryRuntime: getMemoryRuntime(),
operationsRuntime: getRegisteredOperationsRuntime(),
operationsRuntimeOwner: getRegisteredOperationsRuntimeOwner(),
});
}
if (shouldActivate) {

View File

@@ -0,0 +1,134 @@
import { describe, expect, it } from "vitest";
import {
clearOperationsRuntimeState,
getRegisteredOperationsRuntime,
getRegisteredOperationsRuntimeOwner,
registerOperationsRuntimeForOwner,
restoreOperationsRuntimeState,
summarizeOperationRecords,
type PluginOperationsRuntime,
} from "./operations-state.js";
function createRuntime(label: string): PluginOperationsRuntime {
return {
async dispatch() {
return { matched: true, created: true, record: null };
},
async getById() {
return null;
},
async findByRunId() {
return null;
},
async list() {
return [];
},
async summarize() {
return {
total: 0,
active: 0,
terminal: 0,
failures: 0,
byNamespace: { [label]: 0 },
byKind: {},
byStatus: {},
};
},
async audit() {
return [];
},
async maintenance() {
return {
reconciled: 0,
cleanupStamped: 0,
pruned: 0,
};
},
async cancel() {
return { found: false, cancelled: false, reason: label };
},
};
}
describe("operations-state", () => {
it("registers an operations runtime and tracks the owner", () => {
clearOperationsRuntimeState();
const runtime = createRuntime("one");
expect(registerOperationsRuntimeForOwner(runtime, "plugin-one")).toEqual({ ok: true });
expect(getRegisteredOperationsRuntime()).toBe(runtime);
expect(getRegisteredOperationsRuntimeOwner()).toBe("plugin-one");
});
it("rejects a second owner and allows same-owner refresh", () => {
clearOperationsRuntimeState();
const first = createRuntime("one");
const second = createRuntime("two");
const replacement = createRuntime("three");
expect(registerOperationsRuntimeForOwner(first, "plugin-one")).toEqual({ ok: true });
expect(registerOperationsRuntimeForOwner(second, "plugin-two")).toEqual({
ok: false,
existingOwner: "plugin-one",
});
expect(
registerOperationsRuntimeForOwner(replacement, "plugin-one", {
allowSameOwnerRefresh: true,
}),
).toEqual({ ok: true });
expect(getRegisteredOperationsRuntime()).toBe(replacement);
});
it("restores and clears runtime state", () => {
clearOperationsRuntimeState();
const runtime = createRuntime("restore");
restoreOperationsRuntimeState({
runtime,
ownerPluginId: "plugin-restore",
});
expect(getRegisteredOperationsRuntime()).toBe(runtime);
expect(getRegisteredOperationsRuntimeOwner()).toBe("plugin-restore");
clearOperationsRuntimeState();
expect(getRegisteredOperationsRuntime()).toBeUndefined();
expect(getRegisteredOperationsRuntimeOwner()).toBeUndefined();
});
it("summarizes generic operation records", () => {
const summary = summarizeOperationRecords([
{
operationId: "op-1",
namespace: "tasks",
kind: "cli",
status: "queued",
description: "Queued task",
createdAt: 1,
updatedAt: 1,
},
{
operationId: "op-2",
namespace: "imports",
kind: "csv",
status: "failed",
description: "Failed import",
createdAt: 2,
updatedAt: 2,
},
]);
expect(summary).toEqual({
total: 2,
active: 1,
terminal: 1,
failures: 1,
byNamespace: {
imports: 1,
tasks: 1,
},
byKind: {
cli: 1,
csv: 1,
},
byStatus: {
failed: 1,
queued: 1,
},
});
});
});

View File

@@ -0,0 +1,277 @@
import type { OpenClawConfig } from "../config/config.js";
export type PluginOperationRecord = {
operationId: string;
namespace: string;
kind: string;
status: string;
sourceId?: string;
requesterSessionKey?: string;
childSessionKey?: string;
parentOperationId?: string;
agentId?: string;
runId?: string;
title?: string;
description: string;
createdAt: number;
startedAt?: number;
endedAt?: number;
updatedAt: number;
error?: string;
progressSummary?: string;
terminalSummary?: string;
metadata?: Record<string, unknown>;
};
export type PluginOperationListQuery = {
namespace?: string;
kind?: string;
status?: string;
sessionKey?: string;
runId?: string;
sourceId?: string;
parentOperationId?: string;
limit?: number;
};
export type PluginOperationSummary = {
total: number;
active: number;
terminal: number;
failures: number;
byNamespace: Record<string, number>;
byKind: Record<string, number>;
byStatus: Record<string, number>;
};
export type PluginOperationCreateEvent = {
type: "create";
namespace: string;
kind: string;
status?: string;
sourceId?: string;
requesterSessionKey?: string;
childSessionKey?: string;
parentOperationId?: string;
agentId?: string;
runId?: string;
title?: string;
description: string;
createdAt?: number;
startedAt?: number;
endedAt?: number;
updatedAt?: number;
error?: string;
progressSummary?: string | null;
terminalSummary?: string | null;
metadata?: Record<string, unknown>;
};
export type PluginOperationTransitionEvent = {
type: "transition";
operationId?: string;
runId?: string;
status: string;
at?: number;
startedAt?: number;
endedAt?: number;
error?: string | null;
progressSummary?: string | null;
terminalSummary?: string | null;
metadataPatch?: Record<string, unknown>;
};
export type PluginOperationPatchEvent = {
type: "patch";
operationId?: string;
runId?: string;
at?: number;
title?: string | null;
description?: string | null;
error?: string | null;
progressSummary?: string | null;
terminalSummary?: string | null;
metadataPatch?: Record<string, unknown>;
};
export type PluginOperationDispatchEvent =
| PluginOperationCreateEvent
| PluginOperationTransitionEvent
| PluginOperationPatchEvent;
export type PluginOperationDispatchResult = {
matched: boolean;
created?: boolean;
record: PluginOperationRecord | null;
};
export type PluginOperationsCancelResult = {
found: boolean;
cancelled: boolean;
reason?: string;
record?: PluginOperationRecord | null;
};
export type PluginOperationAuditSeverity = "warn" | "error";
export type PluginOperationAuditFinding = {
severity: PluginOperationAuditSeverity;
code: string;
operation: PluginOperationRecord;
detail: string;
ageMs?: number;
};
export type PluginOperationAuditSummary = {
total: number;
warnings: number;
errors: number;
byCode: Record<string, number>;
};
export type PluginOperationAuditQuery = {
namespace?: string;
severity?: PluginOperationAuditSeverity;
code?: string;
};
export type PluginOperationMaintenanceQuery = {
namespace?: string;
apply?: boolean;
};
export type PluginOperationMaintenanceSummary = {
reconciled: number;
cleanupStamped: number;
pruned: number;
};
export type PluginOperationsRuntime = {
dispatch(event: PluginOperationDispatchEvent): Promise<PluginOperationDispatchResult>;
getById(operationId: string): Promise<PluginOperationRecord | null>;
findByRunId(runId: string): Promise<PluginOperationRecord | null>;
list(query?: PluginOperationListQuery): Promise<PluginOperationRecord[]>;
summarize(query?: PluginOperationListQuery): Promise<PluginOperationSummary>;
audit(query?: PluginOperationAuditQuery): Promise<PluginOperationAuditFinding[]>;
maintenance(query?: PluginOperationMaintenanceQuery): Promise<PluginOperationMaintenanceSummary>;
cancel(params: {
cfg: OpenClawConfig;
operationId: string;
}): Promise<PluginOperationsCancelResult>;
};
type OperationsRuntimeState = {
runtime?: PluginOperationsRuntime;
ownerPluginId?: string;
};
type RegisterOperationsRuntimeResult = { ok: true } | { ok: false; existingOwner?: string };
const operationsRuntimeState: OperationsRuntimeState = {};
function normalizeOwnedPluginId(ownerPluginId: string): string {
return ownerPluginId.trim();
}
export function registerOperationsRuntimeForOwner(
runtime: PluginOperationsRuntime,
ownerPluginId: string,
opts?: { allowSameOwnerRefresh?: boolean },
): RegisterOperationsRuntimeResult {
const nextOwner = normalizeOwnedPluginId(ownerPluginId);
const existingOwner = operationsRuntimeState.ownerPluginId?.trim();
if (
operationsRuntimeState.runtime &&
existingOwner &&
existingOwner !== nextOwner &&
!(opts?.allowSameOwnerRefresh === true && existingOwner === nextOwner)
) {
return {
ok: false,
existingOwner,
};
}
operationsRuntimeState.runtime = runtime;
operationsRuntimeState.ownerPluginId = nextOwner;
return { ok: true };
}
export function getRegisteredOperationsRuntime(): PluginOperationsRuntime | undefined {
return operationsRuntimeState.runtime;
}
export function getRegisteredOperationsRuntimeOwner(): string | undefined {
return operationsRuntimeState.ownerPluginId;
}
export function hasRegisteredOperationsRuntime(): boolean {
return operationsRuntimeState.runtime !== undefined;
}
export function restoreOperationsRuntimeState(state: OperationsRuntimeState): void {
operationsRuntimeState.runtime = state.runtime;
operationsRuntimeState.ownerPluginId = state.ownerPluginId?.trim() || undefined;
}
export function clearOperationsRuntimeState(): void {
operationsRuntimeState.runtime = undefined;
operationsRuntimeState.ownerPluginId = undefined;
}
export function isActiveOperationStatus(status: string): boolean {
return status === "queued" || status === "running";
}
export function isFailureOperationStatus(status: string): boolean {
return status === "failed" || status === "timed_out" || status === "lost";
}
export function summarizeOperationRecords(
records: Iterable<PluginOperationRecord>,
): PluginOperationSummary {
const summary: PluginOperationSummary = {
total: 0,
active: 0,
terminal: 0,
failures: 0,
byNamespace: {},
byKind: {},
byStatus: {},
};
for (const record of records) {
summary.total += 1;
summary.byNamespace[record.namespace] = (summary.byNamespace[record.namespace] ?? 0) + 1;
summary.byKind[record.kind] = (summary.byKind[record.kind] ?? 0) + 1;
summary.byStatus[record.status] = (summary.byStatus[record.status] ?? 0) + 1;
if (isActiveOperationStatus(record.status)) {
summary.active += 1;
} else {
summary.terminal += 1;
}
if (isFailureOperationStatus(record.status)) {
summary.failures += 1;
}
}
return summary;
}
export function summarizeOperationAuditFindings(
findings: Iterable<PluginOperationAuditFinding>,
): PluginOperationAuditSummary {
const summary: PluginOperationAuditSummary = {
total: 0,
warnings: 0,
errors: 0,
byCode: {},
};
for (const finding of findings) {
summary.total += 1;
summary.byCode[finding.code] = (summary.byCode[finding.code] ?? 0) + 1;
if (finding.severity === "error") {
summary.errors += 1;
continue;
}
summary.warnings += 1;
}
return summary;
}

View File

@@ -24,6 +24,7 @@ import {
registerMemoryPromptSection,
registerMemoryRuntime,
} from "./memory-state.js";
import { registerOperationsRuntimeForOwner } from "./operations-state.js";
import { normalizeRegisteredProvider } from "./provider-validation.js";
import { createEmptyPluginRegistry } from "./registry-empty.js";
import { withPluginRuntimePluginIdScope } from "./runtime/gateway-request-scope.js";
@@ -1153,6 +1154,20 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
ownerPluginId: record.id,
});
},
registerOperationsRuntime: (runtime) => {
const result = registerOperationsRuntimeForOwner(runtime, record.id, {
allowSameOwnerRefresh: true,
});
if (!result.ok) {
const ownerDetail = result.existingOwner ? ` (${result.existingOwner})` : "";
pushDiagnostic({
level: "error",
pluginId: record.id,
source: record.source,
message: `operations runtime already registered${ownerDetail}`,
});
}
},
on: (hookName, handler, opts) =>
registerTypedHook(record, hookName, handler, opts, params.hookPolicy),
}

View File

@@ -215,6 +215,20 @@ describe("plugin runtime command execution", () => {
]);
},
},
{
name: "exposes runtime.operations helpers",
assert: (runtime: ReturnType<typeof createPluginRuntime>) => {
expect(runtime.operations).toBeDefined();
expectFunctionKeys(runtime.operations as Record<string, unknown>, [
"dispatch",
"getById",
"findByRunId",
"list",
"summarize",
"cancel",
]);
},
},
] as const)("$name", ({ assert }) => {
expectRuntimeShape(assert);
});

View File

@@ -6,8 +6,10 @@ import {
createLazyRuntimeMethodBinder,
createLazyRuntimeModule,
} from "../../shared/lazy-runtime.js";
import { defaultTaskOperationsRuntime } from "../../tasks/operations-runtime.js";
import { VERSION } from "../../version.js";
import { listWebSearchProviders, runWebSearch } from "../../web-search/runtime.js";
import { getRegisteredOperationsRuntime } from "../operations-state.js";
import { createRuntimeAgent } from "./runtime-agent.js";
import { defineCachedValue } from "./runtime-cache.js";
import { createRuntimeChannel } from "./runtime-channel.js";
@@ -96,6 +98,20 @@ function createRuntimeModelAuth(): PluginRuntime["modelAuth"] {
};
}
function createRuntimeOperations(): PluginRuntime["operations"] {
const resolveRuntime = () => getRegisteredOperationsRuntime() ?? defaultTaskOperationsRuntime;
return {
dispatch: (event) => resolveRuntime().dispatch(event),
getById: (operationId) => resolveRuntime().getById(operationId),
findByRunId: (runId) => resolveRuntime().findByRunId(runId),
list: (query) => resolveRuntime().list(query),
summarize: (query) => resolveRuntime().summarize(query),
audit: (query) => resolveRuntime().audit(query),
maintenance: (query) => resolveRuntime().maintenance(query),
cancel: (params) => resolveRuntime().cancel(params),
};
}
function createUnavailableSubagentRuntime(): PluginRuntime["subagent"] {
const unavailable = () => {
throw new Error("Plugin runtime subagent methods are only available during a gateway request.");
@@ -203,6 +219,7 @@ export function createPluginRuntime(_options: CreatePluginRuntimeOptions = {}):
events: createRuntimeEvents(),
logging: createRuntimeLogging(),
state: { resolveStateDir },
operations: createRuntimeOperations(),
} satisfies Omit<
PluginRuntime,
"tts" | "mediaUnderstanding" | "stt" | "modelAuth" | "imageGeneration"

View File

@@ -1,5 +1,17 @@
import type { HeartbeatRunResult } from "../../infra/heartbeat-wake.js";
import type { LogLevel } from "../../logging/levels.js";
import type {
PluginOperationAuditFinding,
PluginOperationAuditQuery,
PluginOperationDispatchEvent,
PluginOperationDispatchResult,
PluginOperationListQuery,
PluginOperationMaintenanceQuery,
PluginOperationMaintenanceSummary,
PluginOperationRecord,
PluginOperationSummary,
PluginOperationsCancelResult,
} from "../operations-state.js";
export type { HeartbeatRunResult };
@@ -115,4 +127,19 @@ export type PluginRuntimeCore = {
cfg?: import("../../config/config.js").OpenClawConfig;
}) => Promise<import("../../agents/model-auth.js").ResolvedProviderAuth>;
};
operations: {
dispatch: (event: PluginOperationDispatchEvent) => Promise<PluginOperationDispatchResult>;
getById: (operationId: string) => Promise<PluginOperationRecord | null>;
findByRunId: (runId: string) => Promise<PluginOperationRecord | null>;
list: (query?: PluginOperationListQuery) => Promise<PluginOperationRecord[]>;
summarize: (query?: PluginOperationListQuery) => Promise<PluginOperationSummary>;
audit: (query?: PluginOperationAuditQuery) => Promise<PluginOperationAuditFinding[]>;
maintenance: (
query?: PluginOperationMaintenanceQuery,
) => Promise<PluginOperationMaintenanceSummary>;
cancel: (params: {
cfg: import("../../config/config.js").OpenClawConfig;
operationId: string;
}) => Promise<PluginOperationsCancelResult>;
};
};

View File

@@ -54,6 +54,7 @@ import type {
} from "../tts/provider-types.js";
import type { DeliveryContext } from "../utils/delivery-context.js";
import type { WizardPrompter } from "../wizard/prompts.js";
import type { PluginOperationsRuntime } from "./operations-state.js";
import type { SecretInputMode } from "./provider-auth-types.js";
import type { createVpsAwareOAuthHandlers } from "./provider-oauth-flow.js";
import type { PluginRuntime } from "./runtime/types.js";
@@ -1767,6 +1768,8 @@ export type OpenClawPluginApi = {
registerMemoryEmbeddingProvider: (
adapter: import("./memory-embedding-providers.js").MemoryEmbeddingProviderAdapter,
) => void;
/** Register the active operations runtime adapter (exclusive slot — only one active at a time). */
registerOperationsRuntime: (runtime: PluginOperationsRuntime) => void;
resolvePath: (input: string) => string;
/** Register a lifecycle hook handler */
on: <K extends PluginHookName>(