Lifecycle:
- Move runtime.start() and shutdown handlers out of register() into
api.on("gateway_start", ...) and api.on("gateway_stop", ...). register()
runs in every CLI subprocess that loads plugins (e.g. `openclaw completion`,
`openclaw doctor`); without this gate the runtime would open a network
connection / bind a listener every time those one-shot commands ran.
- Drop process.once("SIGTERM"/"SIGINT") in favour of the gateway_stop hook,
which is the documented way for plugins to react to shutdown.
- Stop relying on the non-standard `api.rootDir` field (not present on the
current OpenClawPluginApi); compute the per-plugin data directory as
~/.openclaw/yonexus-client and ensure it exists before use.
Plugin SDK convention update:
- Wrap default export with definePluginEntry({ id, name, description, register })
per the current openclaw plugin authoring contract.
- Re-type the register function to accept OpenClawPluginApi instead of the
hand-crafted { rootDir, pluginConfig, ... } shape.
- Use focused subpath imports openclaw/plugin-sdk/plugin-entry and
openclaw/plugin-sdk/core.
- Add openclaw as a devDependency (file:/usr/lib/node_modules/openclaw) so
tsc resolves the SDK type subpaths at build time.
- Modernize openclaw.plugin.json: drop version/entry/permissions, add
activation.onStartup so gateway_start fires for this plugin at boot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
152 lines
5.2 KiB
TypeScript
152 lines
5.2 KiB
TypeScript
export { validateYonexusClientConfig, YonexusClientConfigError } from "./core/config.js";
|
|
export type { YonexusClientConfig } from "./core/config.js";
|
|
export {
|
|
CLIENT_STATE_VERSION,
|
|
YonexusClientStateError,
|
|
YonexusClientStateCorruptionError,
|
|
createYonexusClientStateStore,
|
|
loadYonexusClientState,
|
|
saveYonexusClientState,
|
|
createInitialClientState,
|
|
hasClientSecret,
|
|
hasClientKeyPair,
|
|
type YonexusClientState,
|
|
type YonexusClientStateFile,
|
|
type YonexusClientStateStore
|
|
} from "./core/state.js";
|
|
export {
|
|
createClientTransport,
|
|
YonexusClientTransport,
|
|
type ClientTransport,
|
|
type ClientTransportOptions,
|
|
type ClientConnectionState,
|
|
type ClientMessageHandler,
|
|
type ClientStateChangeHandler,
|
|
type ClientErrorHandler
|
|
} from "./core/transport.js";
|
|
export {
|
|
createYonexusClientRuntime,
|
|
YonexusClientRuntime,
|
|
type YonexusClientRuntimeOptions,
|
|
type YonexusClientRuntimeState,
|
|
type YonexusClientPhase
|
|
} from "./core/runtime.js";
|
|
export {
|
|
createClientRuleRegistry,
|
|
YonexusClientRuleRegistry,
|
|
ClientRuleRegistryError,
|
|
type ClientRuleRegistry,
|
|
type ClientRuleProcessor
|
|
} from "./core/rules.js";
|
|
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import fs from "node:fs";
|
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
import { validateYonexusClientConfig } from "./core/config.js";
|
|
import { createYonexusClientStateStore } from "./core/state.js";
|
|
import { createClientTransport } from "./core/transport.js";
|
|
import { createYonexusClientRuntime, type YonexusClientRuntime } from "./core/runtime.js";
|
|
import { createClientRuleRegistry, YonexusClientRuleRegistry } from "./core/rules.js";
|
|
|
|
const _G = globalThis as Record<string, unknown>;
|
|
const _STARTED_KEY = "_yonexusClientStarted";
|
|
const _RUNTIME_KEY = "_yonexusClientRuntime";
|
|
const _REGISTRY_KEY = "_yonexusClientRegistry";
|
|
const _CALLBACKS_KEY = "_yonexusClientOnAuthCallbacks";
|
|
|
|
const PLUGIN_DATA_DIR = path.join(os.homedir(), ".openclaw", "yonexus-client");
|
|
|
|
export interface YonexusClientPluginManifest {
|
|
readonly name: "Yonexus.Client";
|
|
readonly version: string;
|
|
readonly description: string;
|
|
}
|
|
|
|
const manifest: YonexusClientPluginManifest = {
|
|
name: "Yonexus.Client",
|
|
version: "0.1.0",
|
|
description: "Yonexus client plugin for cross-instance OpenClaw communication"
|
|
};
|
|
|
|
export function createYonexusClientPlugin(api: OpenClawPluginApi): void {
|
|
// 1. Ensure shared state survives hot-reload — only initialise when absent
|
|
if (!(_G[_REGISTRY_KEY] instanceof YonexusClientRuleRegistry)) {
|
|
_G[_REGISTRY_KEY] = createClientRuleRegistry();
|
|
}
|
|
if (!Array.isArray(_G[_CALLBACKS_KEY])) {
|
|
_G[_CALLBACKS_KEY] = [];
|
|
}
|
|
|
|
const ruleRegistry = _G[_REGISTRY_KEY] as YonexusClientRuleRegistry;
|
|
const onAuthenticatedCallbacks = _G[_CALLBACKS_KEY] as Array<() => void>;
|
|
|
|
// 2. Refresh the cross-plugin API object every call so that sendRule / submitPairingCode
|
|
// closures always read the live runtime from globalThis.
|
|
_G["__yonexusClient"] = {
|
|
ruleRegistry,
|
|
sendRule: (ruleId: string, content: string): boolean =>
|
|
(_G[_RUNTIME_KEY] as YonexusClientRuntime | undefined)?.sendRuleMessage(ruleId, content) ?? false,
|
|
submitPairingCode: (code: string): boolean =>
|
|
(_G[_RUNTIME_KEY] as YonexusClientRuntime | undefined)?.submitPairingCode(code) ?? false,
|
|
onAuthenticated: onAuthenticatedCallbacks
|
|
};
|
|
|
|
// 3. Runtime startup — only fire when the gateway boots, not eagerly during
|
|
// register() inside one-shot CLI subprocesses (e.g. `openclaw completion`).
|
|
// Without this gate, every CLI invocation that loads plugins would open
|
|
// a WebSocket to the Yonexus server.
|
|
api.on("gateway_start", () => {
|
|
if (_G[_STARTED_KEY]) return;
|
|
_G[_STARTED_KEY] = true;
|
|
|
|
fs.mkdirSync(PLUGIN_DATA_DIR, { recursive: true });
|
|
|
|
const config = validateYonexusClientConfig(api.pluginConfig);
|
|
const stateStore = createYonexusClientStateStore(path.join(PLUGIN_DATA_DIR, "state.json"));
|
|
|
|
const transport = createClientTransport({
|
|
config,
|
|
onMessage: (msg) => {
|
|
(_G[_RUNTIME_KEY] as YonexusClientRuntime | undefined)?.handleMessage(msg).catch((err: unknown) => {
|
|
console.error("[yonexus-client] message handler error:", err);
|
|
});
|
|
},
|
|
onStateChange: (state) => {
|
|
(_G[_RUNTIME_KEY] as YonexusClientRuntime | undefined)?.handleTransportStateChange(state);
|
|
}
|
|
});
|
|
|
|
const runtime = createYonexusClientRuntime({
|
|
config,
|
|
transport,
|
|
stateStore,
|
|
ruleRegistry,
|
|
onAuthenticated: () => {
|
|
for (const cb of onAuthenticatedCallbacks) cb();
|
|
}
|
|
});
|
|
_G[_RUNTIME_KEY] = runtime;
|
|
|
|
runtime.start().catch((err: unknown) => {
|
|
console.error("[yonexus-client] failed to start:", err);
|
|
});
|
|
});
|
|
|
|
api.on("gateway_stop", () => {
|
|
const runtime = _G[_RUNTIME_KEY] as YonexusClientRuntime | undefined;
|
|
runtime?.stop().catch((err: unknown) => {
|
|
console.error("[yonexus-client] shutdown error:", err);
|
|
});
|
|
});
|
|
}
|
|
|
|
export default definePluginEntry({
|
|
id: "yonexus-client",
|
|
name: "Yonexus.Client",
|
|
description: "Yonexus client plugin for cross-instance OpenClaw communication",
|
|
register: createYonexusClientPlugin,
|
|
});
|
|
export { manifest };
|