Heuristic:Openclaw Openclaw Plugin Dependency Gotchas
| Knowledge Sources | |
|---|---|
| Domains | Plugins, Dependencies |
| Last Updated | 2026-02-06 12:00 GMT |
Overview
Plugin development rules: avoid `workspace:*` in runtime dependencies, use dual import path resolution (src/ for dev, dist/ for production), and keep plugin-only deps in the extension package.json.
Description
OpenClaw plugins (extensions) are standalone npm packages that run inside the gateway process. Three critical gotchas affect plugin development: (1) Never use `workspace:*` in `dependencies` — it breaks `npm install` in production; put `openclaw` in `devDependencies` or `peerDependencies` instead, as the runtime resolves `openclaw/plugin-sdk` via jiti alias. (2) Plugins must handle dual import paths: `../../../src/` during development and `../../../dist/` for built installs, using a try/catch fallback pattern. (3) Plugin-only dependencies must stay in the extension's `package.json`; do not add them to the root `package.json` unless core uses them.
Usage
Apply this heuristic when creating or modifying extension plugins. Check these rules before publishing any plugin to npm.
The Insight (Rule of Thumb)
- Action 1: Never use `workspace:*` in plugin `dependencies`. Use `devDependencies` or `peerDependencies` for the `openclaw` package.
- Action 2: Resolve internal imports dynamically: try `src/` path first, fall back to `dist/` path.
- Action 3: Keep plugin-specific deps in the extension `package.json`, not the root.
- Trade-off: Dual import resolution adds complexity but ensures plugins work in both dev and production environments.
Reasoning
The `workspace:*` protocol is a pnpm feature that resolves to local workspace packages during development. When a plugin is published to npm and installed independently, `workspace:*` cannot resolve and `npm install` fails. The dual import path pattern addresses the structural difference between development (source tree under `src/`) and production (built output under `dist/`). Keeping plugin deps isolated prevents bloating the core package.
Code Evidence from `extensions/llm-task/src/llm-task-tool.ts:6-32`:
// NOTE: This extension is intended to be bundled with OpenClaw.
// When running from source (tests/dev), OpenClaw internals live under src/.
// When running from a built install, internals live under dist/ (no src/ tree).
// So we resolve internal imports dynamically with src-first, dist-fallback.
async function loadRunEmbeddedPiAgent(): Promise<RunEmbeddedPiAgentFn> {
// Source checkout (tests/dev)
try {
const mod = await import("../../../src/agents/pi-embedded-runner.js");
if (typeof (mod as any).runEmbeddedPiAgent === "function") {
return (mod as any).runEmbeddedPiAgent;
}
} catch {
// ignore
}
// Bundled install (built)
const mod = await import("../../../agents/pi-embedded-runner.js");
return (mod as any).runEmbeddedPiAgent;
}