mirror of https://github.com/openclaw/openclaw.git
plugin-runtime: expose runHeartbeatOnce in system API (#40299)
* plugin-runtime: expose runHeartbeatOnce in system API
Plugins that enqueue system events and need the agent to deliver
responses to the originating channel currently have no way to
override the default `heartbeat.target: "none"` behaviour.
Expose `runHeartbeatOnce` in the plugin runtime `system` namespace
so plugins can trigger a single heartbeat cycle with an explicit
`heartbeat: { target: "last" }` override — the same pattern the
cron service already uses (see #28508).
Changes:
- Add `RunHeartbeatOnceOptions` type and `runHeartbeatOnce` to
`PluginRuntimeCore.system` (types-core.ts)
- Wire the function through a thin wrapper in runtime-system.ts
- Update the test-utils plugin-runtime mock
Made-with: Cursor
* feat(plugins): expose runHeartbeatOnce in system API (#40299) (thanks @loveyana)
---------
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
This commit is contained in:
parent
4ae4d1fabe
commit
7847e67f8a
|
|
@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai
|
|||
|
||||
- MiniMax: add image generation provider for `image-01` model, supporting generate and image-to-image editing with aspect ratio control. (#54487) Thanks @liyuan97.
|
||||
- MiniMax: trim model catalog to M2.7 only, removing legacy M2, M2.1, M2.5, and VL-01 models. (#54487) Thanks @liyuan97.
|
||||
- Plugins/runtime: expose `runHeartbeatOnce` in the plugin runtime `system` namespace so plugins can trigger a single heartbeat cycle with an explicit delivery target override (e.g. `heartbeat: { target: "last" }`). (#40299) Thanks @loveyana.
|
||||
|
||||
### Fixes
|
||||
|
||||
|
|
|
|||
|
|
@ -496,7 +496,7 @@
|
|||
"exportName": "RuntimeLogger",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 4,
|
||||
"line": 7,
|
||||
"path": "src/plugins/runtime/types-core.ts"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
{"declaration":"export type ProviderRuntimeModel = ProviderRuntimeModel;","entrypoint":"index","exportName":"ProviderRuntimeModel","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":295,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ReplyPayload = ReplyPayload;","entrypoint":"index","exportName":"ReplyPayload","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":76,"sourcePath":"src/auto-reply/types.ts"}
|
||||
{"declaration":"export type RuntimeEnv = RuntimeEnv;","entrypoint":"index","exportName":"RuntimeEnv","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":4,"sourcePath":"src/runtime.ts"}
|
||||
{"declaration":"export type RuntimeLogger = RuntimeLogger;","entrypoint":"index","exportName":"RuntimeLogger","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":4,"sourcePath":"src/plugins/runtime/types-core.ts"}
|
||||
{"declaration":"export type RuntimeLogger = RuntimeLogger;","entrypoint":"index","exportName":"RuntimeLogger","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":7,"sourcePath":"src/plugins/runtime/types-core.ts"}
|
||||
{"declaration":"export type SecretInput = SecretInput;","entrypoint":"index","exportName":"SecretInput","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":16,"sourcePath":"src/config/types.secrets.ts"}
|
||||
{"declaration":"export type SecretRef = SecretRef;","entrypoint":"index","exportName":"SecretRef","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":10,"sourcePath":"src/config/types.secrets.ts"}
|
||||
{"declaration":"export type SpeechProviderPlugin = SpeechProviderPlugin;","entrypoint":"index","exportName":"SpeechProviderPlugin","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":933,"sourcePath":"src/plugins/types.ts"}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,25 @@
|
|||
import { runHeartbeatOnce as runHeartbeatOnceInternal } from "../../infra/heartbeat-runner.js";
|
||||
import { requestHeartbeatNow } from "../../infra/heartbeat-wake.js";
|
||||
import { enqueueSystemEvent } from "../../infra/system-events.js";
|
||||
import { runCommandWithTimeout } from "../../process/exec.js";
|
||||
import { formatNativeDependencyHint } from "./native-deps.js";
|
||||
import type { RunHeartbeatOnceOptions } from "./types-core.js";
|
||||
import type { PluginRuntime } from "./types.js";
|
||||
|
||||
export function createRuntimeSystem(): PluginRuntime["system"] {
|
||||
return {
|
||||
enqueueSystemEvent,
|
||||
requestHeartbeatNow,
|
||||
runHeartbeatOnce: (opts?: RunHeartbeatOnceOptions) => {
|
||||
// Destructure to forward only the plugin-safe subset; prevent cfg/deps injection at runtime.
|
||||
const { reason, agentId, sessionKey, heartbeat } = opts ?? {};
|
||||
return runHeartbeatOnceInternal({
|
||||
reason,
|
||||
agentId,
|
||||
sessionKey,
|
||||
heartbeat: heartbeat ? { target: heartbeat.target } : undefined,
|
||||
});
|
||||
},
|
||||
runCommandWithTimeout,
|
||||
formatNativeDependencyHint,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import type { HeartbeatRunResult } from "../../infra/heartbeat-wake.js";
|
||||
import type { LogLevel } from "../../logging/levels.js";
|
||||
|
||||
export type { HeartbeatRunResult };
|
||||
|
||||
/** Structured logger surface injected into runtime-backed plugin helpers. */
|
||||
export type RuntimeLogger = {
|
||||
debug?: (message: string, meta?: Record<string, unknown>) => void;
|
||||
|
|
@ -8,6 +11,14 @@ export type RuntimeLogger = {
|
|||
error: (message: string, meta?: Record<string, unknown>) => void;
|
||||
};
|
||||
|
||||
export type RunHeartbeatOnceOptions = {
|
||||
reason?: string;
|
||||
agentId?: string;
|
||||
sessionKey?: string;
|
||||
/** Override heartbeat config (e.g. `{ target: "last" }` to deliver to the last active channel). */
|
||||
heartbeat?: { target?: string };
|
||||
};
|
||||
|
||||
/** Core runtime helpers exposed to trusted native plugins. */
|
||||
export type PluginRuntimeCore = {
|
||||
version: string;
|
||||
|
|
@ -37,6 +48,13 @@ export type PluginRuntimeCore = {
|
|||
system: {
|
||||
enqueueSystemEvent: typeof import("../../infra/system-events.js").enqueueSystemEvent;
|
||||
requestHeartbeatNow: typeof import("../../infra/heartbeat-wake.js").requestHeartbeatNow;
|
||||
/**
|
||||
* Run a single heartbeat cycle immediately (bypassing the coalesce timer).
|
||||
* Accepts an optional `heartbeat` config override so callers can force
|
||||
* delivery to the last active channel — the same pattern the cron service
|
||||
* uses to avoid the default `target: "none"` suppression.
|
||||
*/
|
||||
runHeartbeatOnce: (opts?: RunHeartbeatOnceOptions) => Promise<HeartbeatRunResult>;
|
||||
runCommandWithTimeout: typeof import("../../process/exec.js").runCommandWithTimeout;
|
||||
formatNativeDependencyHint: typeof import("./native-deps.js").formatNativeDependencyHint;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -87,6 +87,10 @@ export function createPluginRuntimeMock(overrides: DeepPartial<PluginRuntime> =
|
|||
system: {
|
||||
enqueueSystemEvent: vi.fn() as unknown as PluginRuntime["system"]["enqueueSystemEvent"],
|
||||
requestHeartbeatNow: vi.fn() as unknown as PluginRuntime["system"]["requestHeartbeatNow"],
|
||||
runHeartbeatOnce: vi.fn(async () => ({
|
||||
status: "ran" as const,
|
||||
durationMs: 0,
|
||||
})) as unknown as PluginRuntime["system"]["runHeartbeatOnce"],
|
||||
runCommandWithTimeout: vi.fn() as unknown as PluginRuntime["system"]["runCommandWithTimeout"],
|
||||
formatNativeDependencyHint: vi.fn(
|
||||
() => "",
|
||||
|
|
|
|||
Loading…
Reference in New Issue