mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-28 01:21:36 +00:00
refactor(channels): dedupe hook and monitor execution paths
This commit is contained in:
@@ -172,6 +172,21 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
|
||||
return next;
|
||||
};
|
||||
|
||||
const handleHookError = (params: {
|
||||
hookName: PluginHookName;
|
||||
pluginId: string;
|
||||
error: unknown;
|
||||
}): never | void => {
|
||||
const msg = `[hooks] ${params.hookName} handler from ${params.pluginId} failed: ${String(
|
||||
params.error,
|
||||
)}`;
|
||||
if (catchErrors) {
|
||||
logger?.error(msg);
|
||||
return;
|
||||
}
|
||||
throw new Error(msg, { cause: params.error });
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a hook that doesn't return a value (fire-and-forget style).
|
||||
* All handlers are executed in parallel for performance.
|
||||
@@ -192,12 +207,7 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
|
||||
try {
|
||||
await (hook.handler as (event: unknown, ctx: unknown) => Promise<void>)(event, ctx);
|
||||
} catch (err) {
|
||||
const msg = `[hooks] ${hookName} handler from ${hook.pluginId} failed: ${String(err)}`;
|
||||
if (catchErrors) {
|
||||
logger?.error(msg);
|
||||
} else {
|
||||
throw new Error(msg, { cause: err });
|
||||
}
|
||||
handleHookError({ hookName, pluginId: hook.pluginId, error: err });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -237,12 +247,7 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
const msg = `[hooks] ${hookName} handler from ${hook.pluginId} failed: ${String(err)}`;
|
||||
if (catchErrors) {
|
||||
logger?.error(msg);
|
||||
} else {
|
||||
throw new Error(msg, { cause: err });
|
||||
}
|
||||
handleHookError({ hookName, pluginId: hook.pluginId, error: err });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
45
src/plugins/installs.test.ts
Normal file
45
src/plugins/installs.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildNpmResolutionInstallFields, recordPluginInstall } from "./installs.js";
|
||||
|
||||
describe("buildNpmResolutionInstallFields", () => {
|
||||
it("maps npm resolution metadata into install record fields", () => {
|
||||
const fields = buildNpmResolutionInstallFields({
|
||||
name: "@openclaw/demo",
|
||||
version: "1.2.3",
|
||||
resolvedSpec: "@openclaw/demo@1.2.3",
|
||||
integrity: "sha512-abc",
|
||||
shasum: "deadbeef",
|
||||
resolvedAt: "2026-02-22T00:00:00.000Z",
|
||||
});
|
||||
expect(fields).toEqual({
|
||||
resolvedName: "@openclaw/demo",
|
||||
resolvedVersion: "1.2.3",
|
||||
resolvedSpec: "@openclaw/demo@1.2.3",
|
||||
integrity: "sha512-abc",
|
||||
shasum: "deadbeef",
|
||||
resolvedAt: "2026-02-22T00:00:00.000Z",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns undefined fields when resolution is missing", () => {
|
||||
expect(buildNpmResolutionInstallFields(undefined)).toEqual({
|
||||
resolvedName: undefined,
|
||||
resolvedVersion: undefined,
|
||||
resolvedSpec: undefined,
|
||||
integrity: undefined,
|
||||
shasum: undefined,
|
||||
resolvedAt: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("recordPluginInstall", () => {
|
||||
it("stores install metadata for the plugin id", () => {
|
||||
const next = recordPluginInstall({}, { pluginId: "demo", source: "npm", spec: "demo@latest" });
|
||||
expect(next.plugins?.installs?.demo).toMatchObject({
|
||||
source: "npm",
|
||||
spec: "demo@latest",
|
||||
});
|
||||
expect(typeof next.plugins?.installs?.demo?.installedAt).toBe("string");
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,25 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
||||
import type { NpmSpecResolution } from "../infra/install-source-utils.js";
|
||||
|
||||
export type PluginInstallUpdate = PluginInstallRecord & { pluginId: string };
|
||||
|
||||
export function buildNpmResolutionInstallFields(
|
||||
resolution?: NpmSpecResolution,
|
||||
): Pick<
|
||||
PluginInstallRecord,
|
||||
"resolvedName" | "resolvedVersion" | "resolvedSpec" | "integrity" | "shasum" | "resolvedAt"
|
||||
> {
|
||||
return {
|
||||
resolvedName: resolution?.name,
|
||||
resolvedVersion: resolution?.version,
|
||||
resolvedSpec: resolution?.resolvedSpec,
|
||||
integrity: resolution?.integrity,
|
||||
shasum: resolution?.shasum,
|
||||
resolvedAt: resolution?.resolvedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export function recordPluginInstall(
|
||||
cfg: OpenClawConfig,
|
||||
update: PluginInstallUpdate,
|
||||
|
||||
@@ -175,6 +175,31 @@ function createPluginRecord(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function recordPluginError(params: {
|
||||
logger: PluginLogger;
|
||||
registry: PluginRegistry;
|
||||
record: PluginRecord;
|
||||
seenIds: Map<string, PluginRecord["origin"]>;
|
||||
pluginId: string;
|
||||
origin: PluginRecord["origin"];
|
||||
error: unknown;
|
||||
logPrefix: string;
|
||||
diagnosticMessagePrefix: string;
|
||||
}) {
|
||||
const errorText = String(params.error);
|
||||
params.logger.error(`${params.logPrefix}${errorText}`);
|
||||
params.record.status = "error";
|
||||
params.record.error = errorText;
|
||||
params.registry.plugins.push(params.record);
|
||||
params.seenIds.set(params.pluginId, params.origin);
|
||||
params.registry.diagnostics.push({
|
||||
level: "error",
|
||||
pluginId: params.record.id,
|
||||
source: params.record.source,
|
||||
message: `${params.diagnosticMessagePrefix}${errorText}`,
|
||||
});
|
||||
}
|
||||
|
||||
function pushDiagnostics(diagnostics: PluginDiagnostic[], append: PluginDiagnostic[]) {
|
||||
diagnostics.push(...append);
|
||||
}
|
||||
@@ -508,16 +533,16 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
try {
|
||||
mod = getJiti()(candidate.source) as OpenClawPluginModule;
|
||||
} catch (err) {
|
||||
logger.error(`[plugins] ${record.id} failed to load from ${record.source}: ${String(err)}`);
|
||||
record.status = "error";
|
||||
record.error = String(err);
|
||||
registry.plugins.push(record);
|
||||
seenIds.set(pluginId, candidate.origin);
|
||||
registry.diagnostics.push({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: `failed to load plugin: ${String(err)}`,
|
||||
recordPluginError({
|
||||
logger,
|
||||
registry,
|
||||
record,
|
||||
seenIds,
|
||||
pluginId,
|
||||
origin: candidate.origin,
|
||||
error: err,
|
||||
logPrefix: `[plugins] ${record.id} failed to load from ${record.source}: `,
|
||||
diagnosticMessagePrefix: "failed to load plugin: ",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -634,18 +659,16 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
registry.plugins.push(record);
|
||||
seenIds.set(pluginId, candidate.origin);
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`[plugins] ${record.id} failed during register from ${record.source}: ${String(err)}`,
|
||||
);
|
||||
record.status = "error";
|
||||
record.error = String(err);
|
||||
registry.plugins.push(record);
|
||||
seenIds.set(pluginId, candidate.origin);
|
||||
registry.diagnostics.push({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: `plugin failed during register: ${String(err)}`,
|
||||
recordPluginError({
|
||||
logger,
|
||||
registry,
|
||||
record,
|
||||
seenIds,
|
||||
pluginId,
|
||||
origin: candidate.origin,
|
||||
error: err,
|
||||
logPrefix: `[plugins] ${record.id} failed during register from ${record.source}: `,
|
||||
diagnosticMessagePrefix: "plugin failed during register: ",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { UpdateChannel } from "../infra/update-channels.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { discoverOpenClawPlugins } from "./discovery.js";
|
||||
import { installPluginFromNpmSpec, resolvePluginInstallDir } from "./install.js";
|
||||
import { recordPluginInstall } from "./installs.js";
|
||||
import { buildNpmResolutionInstallFields, recordPluginInstall } from "./installs.js";
|
||||
import { loadPluginManifest } from "./manifest.js";
|
||||
|
||||
export type PluginUpdateLogger = {
|
||||
@@ -344,12 +344,7 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
spec: record.spec,
|
||||
installPath: result.targetDir,
|
||||
version: nextVersion,
|
||||
resolvedName: result.npmResolution?.name,
|
||||
resolvedVersion: result.npmResolution?.version,
|
||||
resolvedSpec: result.npmResolution?.resolvedSpec,
|
||||
integrity: result.npmResolution?.integrity,
|
||||
shasum: result.npmResolution?.shasum,
|
||||
resolvedAt: result.npmResolution?.resolvedAt,
|
||||
...buildNpmResolutionInstallFields(result.npmResolution),
|
||||
});
|
||||
changed = true;
|
||||
|
||||
@@ -473,12 +468,7 @@ export async function syncPluginsForUpdateChannel(params: {
|
||||
spec,
|
||||
installPath: result.targetDir,
|
||||
version: result.version,
|
||||
resolvedName: result.npmResolution?.name,
|
||||
resolvedVersion: result.npmResolution?.version,
|
||||
resolvedSpec: result.npmResolution?.resolvedSpec,
|
||||
integrity: result.npmResolution?.integrity,
|
||||
shasum: result.npmResolution?.shasum,
|
||||
resolvedAt: result.npmResolution?.resolvedAt,
|
||||
...buildNpmResolutionInstallFields(result.npmResolution),
|
||||
sourcePath: undefined,
|
||||
});
|
||||
summary.switchedToNpm.push(pluginId);
|
||||
|
||||
Reference in New Issue
Block a user