From 787d88cd338e30bcadc43868221c81481f7fd005 Mon Sep 17 00:00:00 2001 From: zhi Date: Fri, 8 May 2026 08:02:30 +0000 Subject: [PATCH] chore: convert plugin to ESM and migrate to current openclaw plugin SDK ESM conversion: - package.json: add "type": "module"; drop stale "main": "index.ts" - tsconfig.json: switch module/moduleResolution to "nodenext" - plugin/index.ts: replace `module.exports = { register }` and `module.exports.X = X` with `export default definePluginEntry({ ... })` plus named ESM re-exports; replace `require('os')`/`require('path')` with proper imports. - plugin/tools/pcexec.ts: replace `require('child_process')` with import from "node:child_process". - plugin/commands/ego-mgr-slash.ts: replace `require('path')` with proper path import. - All relative imports/exports across plugin/ now carry .js extensions as required by Node ESM (nodenext module resolution). Plugin SDK convention update: - Wrap default export with definePluginEntry({ id, name, description, register }) per the current openclaw authoring contract. - Type api parameter as OpenClawPluginApi (was `any`); the non-standard api.registerSlashCommand call is preserved behind a guarded any-cast, so the plugin remains a no-op for slash commands when the host doesn't expose that hook (matches the previous defensive guard). - Add openclaw as a devDependency (file:/usr/lib/node_modules/openclaw) so tsc can resolve openclaw/plugin-sdk/* subpath types at build time. - Modernize openclaw.plugin.json: drop entry/version, add activation.onStartup so gateway_start fires for this plugin at boot, declare contracts.tools listing pcexec/proxy-pcexec/safe_restart. Co-Authored-By: Claude Opus 4.7 (1M context) --- plugin/commands/ego-mgr-slash.ts | 5 +- plugin/commands/slash-commands.ts | 2 +- plugin/core/api.ts | 2 +- plugin/core/index.ts | 8 +-- plugin/core/safe-restart.ts | 2 +- plugin/index.ts | 93 +++++++++++++++++-------------- plugin/openclaw.plugin.json | 12 +++- plugin/package.json | 5 +- plugin/tools/pcexec.ts | 4 +- plugin/tsconfig.json | 7 ++- 10 files changed, 80 insertions(+), 60 deletions(-) diff --git a/plugin/commands/ego-mgr-slash.ts b/plugin/commands/ego-mgr-slash.ts index 16f4024..a8d4fd9 100644 --- a/plugin/commands/ego-mgr-slash.ts +++ b/plugin/commands/ego-mgr-slash.ts @@ -1,4 +1,5 @@ -import { pcexec } from '../tools/pcexec'; +import path from 'node:path'; +import { pcexec } from '../tools/pcexec.js'; export interface EgoMgrSlashCommandOptions { /** OpenClaw base path */ @@ -26,7 +27,7 @@ export class EgoMgrSlashCommand { this.agentId = options.agentId; this.workspaceDir = options.workspaceDir; this.onReply = options.onReply; - this.binDir = require('path').join(this.openclawPath, 'bin'); + this.binDir = path.join(this.openclawPath, 'bin'); } /** diff --git a/plugin/commands/slash-commands.ts b/plugin/commands/slash-commands.ts index 08f8481..6535656 100644 --- a/plugin/commands/slash-commands.ts +++ b/plugin/commands/slash-commands.ts @@ -1,4 +1,4 @@ -import { StatusManager } from '../core/status-manager'; +import { StatusManager } from '../core/status-manager.js'; export interface SlashCommandOptions { statusManager: StatusManager; diff --git a/plugin/core/api.ts b/plugin/core/api.ts index eeb62c2..56405d9 100644 --- a/plugin/core/api.ts +++ b/plugin/core/api.ts @@ -1,5 +1,5 @@ import express from 'express'; -import { StatusManager } from './status-manager'; +import { StatusManager } from './status-manager.js'; export interface ApiOptions { port?: number; diff --git a/plugin/core/index.ts b/plugin/core/index.ts index 1384c25..e2c6c54 100644 --- a/plugin/core/index.ts +++ b/plugin/core/index.ts @@ -1,4 +1,4 @@ -export { StatusManager, type AgentStatus, type GlobalStatus, type AgentState } from './status-manager'; -export { createApiServer, startApiServer } from './api'; -export { safeRestart, createSafeRestartTool, type SafeRestartOptions, type SafeRestartResult } from './safe-restart'; -export { SlashCommandHandler, type SlashCommandOptions } from '../commands/slash-commands'; +export { StatusManager, type AgentStatus, type GlobalStatus, type AgentState } from './status-manager.js'; +export { createApiServer, startApiServer } from './api.js'; +export { safeRestart, createSafeRestartTool, type SafeRestartOptions, type SafeRestartResult } from './safe-restart.js'; +export { SlashCommandHandler, type SlashCommandOptions } from '../commands/slash-commands.js'; diff --git a/plugin/core/safe-restart.ts b/plugin/core/safe-restart.ts index c397801..3c8b777 100644 --- a/plugin/core/safe-restart.ts +++ b/plugin/core/safe-restart.ts @@ -1,7 +1,7 @@ import { spawn } from 'child_process'; import * as fs from 'fs'; import { promisify } from 'util'; -import { StatusManager } from './status-manager'; +import { StatusManager } from './status-manager.js'; const sleep = promisify(setTimeout); diff --git a/plugin/index.ts b/plugin/index.ts index 6bd04b8..a1168ab 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -1,16 +1,21 @@ // PaddedCell Plugin for OpenClaw -// Registers pcexec and safe_restart tools +// Registers pcexec, proxy-pcexec, and safe_restart tools -import { pcexec, pcexecSync } from './tools/pcexec'; +import os from 'node:os'; +import path from 'node:path'; +import { definePluginEntry } from 'openclaw/plugin-sdk/plugin-entry'; +import type { OpenClawPluginApi } from 'openclaw/plugin-sdk/core'; + +import { pcexec, pcexecSync } from './tools/pcexec.js'; import { safeRestart, createSafeRestartTool, StatusManager, createApiServer, startApiServer, -} from './core/index'; -import { SlashCommandHandler } from './commands/slash-commands'; -import { EgoMgrSlashCommand } from './commands/ego-mgr-slash'; +} from './core/index.js'; +import { SlashCommandHandler } from './commands/slash-commands.js'; +import { EgoMgrSlashCommand } from './commands/ego-mgr-slash.js'; /** Sentinel value injected into every pcexec subprocess */ const AGENT_VERIFY = 'IF YOU ARE AN AGENT/MODEL, YOU SHOULD NEVER TOUCH THIS ENV VARIABLE'; @@ -22,11 +27,11 @@ const AGENT_VERIFY = 'IF YOU ARE AN AGENT/MODEL, YOU SHOULD NEVER TOUCH THIS ENV function resolveOpenclawPath(config?: { openclawProfilePath?: string }): string { if (config?.openclawProfilePath) return config.openclawProfilePath; if (process.env.OPENCLAW_PATH) return process.env.OPENCLAW_PATH; - const home = process.env.HOME || require('os').homedir(); - return require('path').join(home, '.openclaw'); + const home = process.env.HOME || os.homedir(); + return path.join(home, '.openclaw'); } -function getPluginConfig(api: any): Record { +function getPluginConfig(api: OpenClawPluginApi): Record { return ((api?.pluginConfig as Record | undefined) || {}); } @@ -36,19 +41,18 @@ function resolveProxyAllowlist(config?: { proxyAllowlist?: unknown; 'proxy-allow return value.filter((item): item is string => typeof item === 'string'); } -// Plugin registration function -function register(api: any) { - const logger = api.logger || { info: console.log, error: console.error }; +function register(api: OpenClawPluginApi): void { + const logger = api.logger || { info: console.log, error: console.error, warn: console.warn }; logger.info('PaddedCell plugin initializing...'); const pluginConfig = getPluginConfig(api); const openclawPath = resolveOpenclawPath(pluginConfig as { openclawProfilePath?: string }); const proxyAllowlist = resolveProxyAllowlist(pluginConfig as { proxyAllowlist?: unknown; 'proxy-allowlist'?: unknown }); - const binDir = require('path').join(openclawPath, 'bin'); + const binDir = path.join(openclawPath, 'bin'); // Register pcexec tool — pass a FACTORY function that receives context - api.registerTool((ctx: any) => { + api.registerTool((ctx) => { const agentId = ctx.agentId; const workspaceDir = ctx.workspaceDir; @@ -94,10 +98,10 @@ function register(api: any) { } return { content: [{ type: 'text', text: output }] }; }, - }; + } as any; }); - api.registerTool((ctx: any) => { + api.registerTool((ctx) => { const agentId = ctx.agentId; const workspaceDir = ctx.workspaceDir; @@ -127,11 +131,7 @@ function register(api: any) { throw new Error('Current agent is not allowed to call proxy-pcexec'); } - logger.info('proxy-pcexec invoked', { - executor: agentId, - proxyFor, - command, - }); + logger.info(`proxy-pcexec invoked executor=${agentId} proxyFor=${proxyFor} command=${command}`); const currentPath = process.env.PATH || ''; const newPath = currentPath.includes(binDir) @@ -157,11 +157,11 @@ function register(api: any) { } return { content: [{ type: 'text', text: output }] }; }, - }; + } as any; }); // Register safe_restart tool - api.registerTool((ctx: any) => { + api.registerTool((ctx) => { const agentId = ctx.agentId; const sessionKey = ctx.sessionKey; @@ -177,18 +177,22 @@ function register(api: any) { }, async execute(_id: string, params: any) { return await safeRestart({ - agentId, - sessionKey, + agentId: agentId ?? '', + sessionKey: sessionKey ?? '', rollback: params.rollback, log: params.log, }); }, - }; + } as any; }); - // Register /ego-mgr slash command - if (api.registerSlashCommand) { - api.registerSlashCommand({ + // Register /ego-mgr slash command if the host exposes the (non-standard) hook. + // This API is not part of the current OpenClawPluginApi surface; the guard + // makes the plugin a no-op for slash commands when the host doesn't support + // them, instead of failing to load. + const apiAny = api as unknown as { registerSlashCommand?: (cmd: unknown) => void }; + if (typeof apiAny.registerSlashCommand === 'function') { + apiAny.registerSlashCommand({ name: 'ego-mgr', description: 'Manage agent identity/profile fields', handler: async (ctx: any, command: string) => { @@ -211,17 +215,24 @@ function register(api: any) { logger.info('PaddedCell plugin initialized'); } -// CommonJS export for OpenClaw -module.exports = { register }; +export default definePluginEntry({ + id: 'padded-cell', + name: 'PaddedCell', + description: 'Secure secret management, agent identity management, safe execution, and coordinated agent restart', + register, +}); -// Also export individual modules for direct use -module.exports.pcexec = pcexec; -module.exports.pcexecSync = pcexecSync; -module.exports.safeRestart = safeRestart; -module.exports.createSafeRestartTool = createSafeRestartTool; -module.exports.StatusManager = StatusManager; -module.exports.createApiServer = createApiServer; -module.exports.startApiServer = startApiServer; -module.exports.SlashCommandHandler = SlashCommandHandler; -module.exports.EgoMgrSlashCommand = EgoMgrSlashCommand; -module.exports.AGENT_VERIFY = AGENT_VERIFY; +// Named ESM re-exports — equivalent to the previous `module.exports.X = X` +// surface, so consumers that reach into the plugin module directly keep working. +export { register }; +export { pcexec, pcexecSync } from './tools/pcexec.js'; +export { + safeRestart, + createSafeRestartTool, + StatusManager, + createApiServer, + startApiServer, +} from './core/index.js'; +export { SlashCommandHandler } from './commands/slash-commands.js'; +export { EgoMgrSlashCommand } from './commands/ego-mgr-slash.js'; +export { AGENT_VERIFY }; diff --git a/plugin/openclaw.plugin.json b/plugin/openclaw.plugin.json index 3dd512d..f9108d4 100644 --- a/plugin/openclaw.plugin.json +++ b/plugin/openclaw.plugin.json @@ -1,9 +1,17 @@ { "id": "padded-cell", "name": "PaddedCell", - "version": "0.2.0", "description": "Secure secret management, agent identity management, safe execution, and coordinated agent restart", - "entry": "./index.js", + "activation": { + "onStartup": true + }, + "contracts": { + "tools": [ + "pcexec", + "proxy-pcexec", + "safe_restart" + ] + }, "configSchema": { "type": "object", "properties": { diff --git a/plugin/package.json b/plugin/package.json index be3bc69..a7bc105 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -2,7 +2,7 @@ "name": "padded-cell-plugin", "version": "0.2.0", "description": "PaddedCell plugin for OpenClaw - secure exec, password management, coordinated restart", - "main": "index.ts", + "type": "module", "scripts": { "build": "tsc", "dev": "tsc --watch" @@ -15,6 +15,7 @@ "devDependencies": { "typescript": "^5.0.0", "@types/express": "^4.17.0", - "@types/ws": "^8.5.0" + "@types/ws": "^8.5.0", + "openclaw": "file:/usr/lib/node_modules/openclaw" } } diff --git a/plugin/tools/pcexec.ts b/plugin/tools/pcexec.ts index a056954..d56df0d 100644 --- a/plugin/tools/pcexec.ts +++ b/plugin/tools/pcexec.ts @@ -1,4 +1,4 @@ -import { spawn, SpawnOptions } from 'child_process'; +import { spawn, execSync, SpawnOptions } from 'node:child_process'; export interface PcExecOptions { /** Current working directory */ @@ -303,8 +303,6 @@ export function pcexecSync( command: string, options: PcExecOptions = {}, ): PcExecResult { - const { execSync } = require('child_process'); - const env: Record = {}; for (const [k, v] of Object.entries(process.env)) { if (v !== undefined) env[k] = v; diff --git a/plugin/tsconfig.json b/plugin/tsconfig.json index 1edf6df..766ac9b 100644 --- a/plugin/tsconfig.json +++ b/plugin/tsconfig.json @@ -1,8 +1,9 @@ { "compilerOptions": { - "target": "ES2020", - "module": "commonjs", - "lib": ["ES2020"], + "target": "ES2022", + "module": "nodenext", + "moduleResolution": "nodenext", + "lib": ["ES2022"], "outDir": "../dist/padded-cell", "rootDir": ".", "strict": true,