mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 15:47:28 +00:00
fix: load source bundled plugins from pnpm workspaces
This commit is contained in:
@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
|
- Plugins/source checkout: load bundled plugins from the `extensions/*` pnpm workspace tree in source checkouts, so plugin-local dependencies and edits are used directly while packaged installs keep using the built runtime tree. Thanks @vincentkoc.
|
||||||
- Plugins/ClawHub: prefer versioned ClawPack artifacts when ClawHub publishes digest metadata, verifying the ClawPack response header and downloaded bytes before installing. Thanks @vincentkoc.
|
- Plugins/ClawHub: prefer versioned ClawPack artifacts when ClawHub publishes digest metadata, verifying the ClawPack response header and downloaded bytes before installing. Thanks @vincentkoc.
|
||||||
- Plugins/ClawHub: persist ClawPack digest metadata on ClawHub plugin install and update records so registry refreshes and download verification can reuse stored artifact facts. Thanks @vincentkoc.
|
- Plugins/ClawHub: persist ClawPack digest metadata on ClawHub plugin install and update records so registry refreshes and download verification can reuse stored artifact facts. Thanks @vincentkoc.
|
||||||
- Providers/OpenAI: add `extraBody`/`extra_body` passthrough for OpenAI-compatible TTS endpoints, so custom speech servers can receive fields such as `lang` in `/audio/speech` requests. Fixes #39900. Thanks @R3NK0R.
|
- Providers/OpenAI: add `extraBody`/`extra_body` passthrough for OpenAI-compatible TTS endpoints, so custom speech servers can receive fields such as `lang` in `/audio/speech` requests. Fixes #39900. Thanks @R3NK0R.
|
||||||
|
|||||||
@@ -210,7 +210,10 @@ Runbook: [iOS connect](https://docs.openclaw.ai/platforms/ios).
|
|||||||
|
|
||||||
## From source (development)
|
## From source (development)
|
||||||
|
|
||||||
Prefer `pnpm` for builds from source. Bun is optional for running TypeScript directly.
|
Use `pnpm` for source checkouts. The repository is a pnpm workspace, and bundled
|
||||||
|
plugins load from `extensions/*` during development so their package-local
|
||||||
|
dependencies and your edits are used directly. Plain `npm install` at the repo
|
||||||
|
root is not a supported source setup.
|
||||||
|
|
||||||
For the dev loop:
|
For the dev loop:
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ falls back to npm automatically for packages that still use npm distribution.
|
|||||||
|
|
||||||
- Node >= 22 and a package manager (npm or pnpm)
|
- Node >= 22 and a package manager (npm or pnpm)
|
||||||
- Familiarity with TypeScript (ESM)
|
- Familiarity with TypeScript (ESM)
|
||||||
- For in-repo plugins: repository cloned and `pnpm install` done
|
- For in-repo plugins: repository cloned and `pnpm install` done. Source
|
||||||
|
checkout plugin development is pnpm-only because OpenClaw loads bundled
|
||||||
|
plugins from the `extensions/*` workspace packages.
|
||||||
|
|
||||||
## What kind of plugin?
|
## What kind of plugin?
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,12 @@ Bundled plugin manifests must not request dependency staging. Large or optional
|
|||||||
plugin functionality should be packaged as a normal plugin and installed through
|
plugin functionality should be packaged as a normal plugin and installed through
|
||||||
the same npm/git/ClawHub path as third-party plugins.
|
the same npm/git/ClawHub path as third-party plugins.
|
||||||
|
|
||||||
|
In source checkouts, OpenClaw treats the repository as a pnpm monorepo. After
|
||||||
|
`pnpm install`, bundled plugins load from `extensions/<id>` so package-local
|
||||||
|
workspace dependencies are available and edits are picked up directly. Source
|
||||||
|
checkout development is pnpm-only; plain `npm install` at the repository root is
|
||||||
|
not a supported way to prepare bundled plugin dependencies.
|
||||||
|
|
||||||
## Legacy cleanup
|
## Legacy cleanup
|
||||||
|
|
||||||
Older OpenClaw versions generated bundled-plugin dependency roots at startup or
|
Older OpenClaw versions generated bundled-plugin dependency roots at startup or
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ Pick a setup workflow based on how often you want updates and whether you want t
|
|||||||
## Prereqs (from source)
|
## Prereqs (from source)
|
||||||
|
|
||||||
- Node 24 recommended (Node 22 LTS, currently `22.14+`, still supported)
|
- Node 24 recommended (Node 22 LTS, currently `22.14+`, still supported)
|
||||||
- `pnpm` preferred (or Bun if you intentionally use the [Bun workflow](/install/bun))
|
- `pnpm` required for source checkouts. OpenClaw loads bundled plugins from the
|
||||||
|
`extensions/*` pnpm workspace packages in dev mode, so root `npm install` does
|
||||||
|
not prepare the full source tree.
|
||||||
- Docker (optional; only for containerized setup/e2e — see [Docker](/install/docker))
|
- Docker (optional; only for containerized setup/e2e — see [Docker](/install/docker))
|
||||||
|
|
||||||
## Tailoring strategy (so updates do not hurt)
|
## Tailoring strategy (so updates do not hurt)
|
||||||
@@ -44,7 +46,7 @@ From inside this repo, use the local CLI entry:
|
|||||||
openclaw setup
|
openclaw setup
|
||||||
```
|
```
|
||||||
|
|
||||||
If you don’t have a global install yet, run it via `pnpm openclaw setup` (or `bun run openclaw setup` if you are using the Bun workflow).
|
If you don’t have a global install yet, run it via `pnpm openclaw setup`.
|
||||||
|
|
||||||
## Run the Gateway from this repo
|
## Run the Gateway from this repo
|
||||||
|
|
||||||
@@ -105,15 +107,6 @@ reloads on relevant source, config, and bundled-plugin metadata changes.
|
|||||||
`pnpm openclaw setup` is the one-time local config/workspace initialization step for a fresh checkout.
|
`pnpm openclaw setup` is the one-time local config/workspace initialization step for a fresh checkout.
|
||||||
`pnpm gateway:watch` does not rebuild `dist/control-ui`, so rerun `pnpm ui:build` after `ui/` changes or use `pnpm ui:dev` while developing the Control UI.
|
`pnpm gateway:watch` does not rebuild `dist/control-ui`, so rerun `pnpm ui:build` after `ui/` changes or use `pnpm ui:dev` while developing the Control UI.
|
||||||
|
|
||||||
If you are intentionally using the Bun workflow, the equivalent commands are:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bun install
|
|
||||||
# First run only (or after resetting local OpenClaw config/workspace)
|
|
||||||
bun run openclaw setup
|
|
||||||
bun run gateway:watch
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2) Point the macOS app at your running Gateway
|
### 2) Point the macOS app at your running Gateway
|
||||||
|
|
||||||
In **OpenClaw.app**:
|
In **OpenClaw.app**:
|
||||||
@@ -158,7 +151,7 @@ Use this when debugging auth or deciding what to back up:
|
|||||||
## Updating (without wrecking your setup)
|
## Updating (without wrecking your setup)
|
||||||
|
|
||||||
- Keep `~/.openclaw/workspace` and `~/.openclaw/` as “your stuff”; don’t put personal prompts/config into the `openclaw` repo.
|
- Keep `~/.openclaw/workspace` and `~/.openclaw/` as “your stuff”; don’t put personal prompts/config into the `openclaw` repo.
|
||||||
- Updating source: `git pull` + your chosen package-manager install step (`pnpm install` by default; `bun install` for Bun workflow) + keep using the matching `gateway:watch` command.
|
- Updating source: `git pull` + `pnpm install` + keep using `pnpm gateway:watch`.
|
||||||
|
|
||||||
## Linux (systemd user service)
|
## Linux (systemd user service)
|
||||||
|
|
||||||
|
|||||||
@@ -106,6 +106,12 @@ through `openclaw plugins install`.
|
|||||||
See [Plugin dependency resolution](/plugins/dependency-resolution) for the
|
See [Plugin dependency resolution](/plugins/dependency-resolution) for the
|
||||||
install-time lifecycle.
|
install-time lifecycle.
|
||||||
|
|
||||||
|
Source checkouts are pnpm workspaces. If you clone OpenClaw to hack on bundled
|
||||||
|
plugins, run `pnpm install`; OpenClaw then loads bundled plugins from
|
||||||
|
`extensions/<id>` so edits and package-local dependencies are used directly.
|
||||||
|
Plain npm root installs are for packaged OpenClaw, not source checkout
|
||||||
|
development.
|
||||||
|
|
||||||
## Plugin types
|
## Plugin types
|
||||||
|
|
||||||
OpenClaw recognizes two plugin formats:
|
OpenClaw recognizes two plugin formats:
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ function createOpenClawRoot(params: {
|
|||||||
hasDistRuntimeExtensions?: boolean;
|
hasDistRuntimeExtensions?: boolean;
|
||||||
hasDistExtensions?: boolean;
|
hasDistExtensions?: boolean;
|
||||||
hasGitCheckout?: boolean;
|
hasGitCheckout?: boolean;
|
||||||
|
hasPnpmWorkspace?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const repoRoot = makeRepoRoot(params.prefix);
|
const repoRoot = makeRepoRoot(params.prefix);
|
||||||
if (params.hasExtensions) {
|
if (params.hasExtensions) {
|
||||||
@@ -39,6 +40,13 @@ function createOpenClawRoot(params: {
|
|||||||
if (params.hasGitCheckout) {
|
if (params.hasGitCheckout) {
|
||||||
fs.writeFileSync(path.join(repoRoot, ".git"), "gitdir: /tmp/fake.git\n", "utf8");
|
fs.writeFileSync(path.join(repoRoot, ".git"), "gitdir: /tmp/fake.git\n", "utf8");
|
||||||
}
|
}
|
||||||
|
if (params.hasPnpmWorkspace) {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(repoRoot, "pnpm-workspace.yaml"),
|
||||||
|
"packages:\n - .\n - extensions/*\n",
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
}
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.join(repoRoot, "package.json"),
|
path.join(repoRoot, "package.json"),
|
||||||
`${JSON.stringify({ name: "openclaw" }, null, 2)}\n`,
|
`${JSON.stringify({ name: "openclaw" }, null, 2)}\n`,
|
||||||
@@ -188,7 +196,7 @@ describe("resolveBundledPluginsDir", () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"prefers built dist/extensions in a git checkout outside vitest",
|
"prefers source extensions in a pnpm git checkout outside vitest",
|
||||||
{
|
{
|
||||||
prefix: "openclaw-bundled-dir-git-built-",
|
prefix: "openclaw-bundled-dir-git-built-",
|
||||||
hasExtensions: true,
|
hasExtensions: true,
|
||||||
@@ -196,9 +204,10 @@ describe("resolveBundledPluginsDir", () => {
|
|||||||
hasDistRuntimeExtensions: true,
|
hasDistRuntimeExtensions: true,
|
||||||
hasDistExtensions: true,
|
hasDistExtensions: true,
|
||||||
hasGitCheckout: true,
|
hasGitCheckout: true,
|
||||||
|
hasPnpmWorkspace: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedRelativeDir: path.join("dist-runtime", "extensions"),
|
expectedRelativeDir: "extensions",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -215,7 +224,7 @@ describe("resolveBundledPluginsDir", () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"still prefers built bundled plugins during tsx-driven source execution",
|
"still prefers source extensions during tsx-driven pnpm source execution",
|
||||||
{
|
{
|
||||||
prefix: "openclaw-bundled-dir-tsx-built-",
|
prefix: "openclaw-bundled-dir-tsx-built-",
|
||||||
hasExtensions: true,
|
hasExtensions: true,
|
||||||
@@ -223,19 +232,21 @@ describe("resolveBundledPluginsDir", () => {
|
|||||||
hasDistRuntimeExtensions: true,
|
hasDistRuntimeExtensions: true,
|
||||||
hasDistExtensions: true,
|
hasDistExtensions: true,
|
||||||
hasGitCheckout: true,
|
hasGitCheckout: true,
|
||||||
|
hasPnpmWorkspace: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedRelativeDir: path.join("dist-runtime", "extensions"),
|
expectedRelativeDir: "extensions",
|
||||||
execArgv: ["--import", "tsx"],
|
execArgv: ["--import", "tsx"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"falls back to source extensions in a git checkout when built trees are missing",
|
"uses source extensions in a pnpm git checkout when built trees are missing",
|
||||||
{
|
{
|
||||||
prefix: "openclaw-bundled-dir-git-",
|
prefix: "openclaw-bundled-dir-git-",
|
||||||
hasExtensions: true,
|
hasExtensions: true,
|
||||||
hasSrc: true,
|
hasSrc: true,
|
||||||
hasGitCheckout: true,
|
hasGitCheckout: true,
|
||||||
|
hasPnpmWorkspace: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedRelativeDir: "extensions",
|
expectedRelativeDir: "extensions",
|
||||||
@@ -267,6 +278,7 @@ describe("resolveBundledPluginsDir", () => {
|
|||||||
hasDistRuntimeExtensions: true,
|
hasDistRuntimeExtensions: true,
|
||||||
hasDistExtensions: true,
|
hasDistExtensions: true,
|
||||||
hasGitCheckout: true,
|
hasGitCheckout: true,
|
||||||
|
hasPnpmWorkspace: true,
|
||||||
});
|
});
|
||||||
fs.mkdirSync(path.join(repoRoot, "dist", "extensions", "discord"), { recursive: true });
|
fs.mkdirSync(path.join(repoRoot, "dist", "extensions", "discord"), { recursive: true });
|
||||||
fs.mkdirSync(path.join(repoRoot, "dist-runtime", "extensions", "discord"), {
|
fs.mkdirSync(path.join(repoRoot, "dist-runtime", "extensions", "discord"), {
|
||||||
@@ -280,6 +292,25 @@ describe("resolveBundledPluginsDir", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps built bundled plugins for git-looking trees without pnpm workspace metadata", () => {
|
||||||
|
const repoRoot = createOpenClawRoot({
|
||||||
|
prefix: "openclaw-bundled-dir-git-no-pnpm-",
|
||||||
|
hasExtensions: true,
|
||||||
|
hasSrc: true,
|
||||||
|
hasDistRuntimeExtensions: true,
|
||||||
|
hasDistExtensions: true,
|
||||||
|
hasGitCheckout: true,
|
||||||
|
});
|
||||||
|
seedBundledPluginTree(repoRoot, "extensions");
|
||||||
|
seedBundledPluginTree(repoRoot, path.join("dist", "extensions"));
|
||||||
|
seedBundledPluginTree(repoRoot, path.join("dist-runtime", "extensions"));
|
||||||
|
|
||||||
|
expectResolvedBundledDirFromRoot({
|
||||||
|
repoRoot,
|
||||||
|
expectedRelativeDir: path.join("dist-runtime", "extensions"),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("returns a stable empty bundled plugin directory when bundled plugins are disabled", () => {
|
it("returns a stable empty bundled plugin directory when bundled plugins are disabled", () => {
|
||||||
const repoRoot = createOpenClawRoot({
|
const repoRoot = createOpenClawRoot({
|
||||||
prefix: "openclaw-bundled-dir-disabled-",
|
prefix: "openclaw-bundled-dir-disabled-",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ function resolveDisabledBundledPluginsDir(): string {
|
|||||||
function isSourceCheckoutRoot(packageRoot: string): boolean {
|
function isSourceCheckoutRoot(packageRoot: string): boolean {
|
||||||
return (
|
return (
|
||||||
fs.existsSync(path.join(packageRoot, ".git")) &&
|
fs.existsSync(path.join(packageRoot, ".git")) &&
|
||||||
|
fs.existsSync(path.join(packageRoot, "pnpm-workspace.yaml")) &&
|
||||||
fs.existsSync(path.join(packageRoot, "src")) &&
|
fs.existsSync(path.join(packageRoot, "src")) &&
|
||||||
fs.existsSync(path.join(packageRoot, "extensions"))
|
fs.existsSync(path.join(packageRoot, "extensions"))
|
||||||
);
|
);
|
||||||
@@ -126,12 +127,13 @@ function resolveBundledDirFromPackageRoot(packageRoot: string): string | undefin
|
|||||||
const builtExtensionsDir = path.join(packageRoot, "dist", "extensions");
|
const builtExtensionsDir = path.join(packageRoot, "dist", "extensions");
|
||||||
const sourceCheckout = isSourceCheckoutRoot(packageRoot);
|
const sourceCheckout = isSourceCheckoutRoot(packageRoot);
|
||||||
const hasUsableSourceTree = sourceCheckout && hasUsableBundledPluginTree(sourceExtensionsDir);
|
const hasUsableSourceTree = sourceCheckout && hasUsableBundledPluginTree(sourceExtensionsDir);
|
||||||
// Local source checkouts stage a runtime-complete bundled plugin tree under
|
// In pnpm source checkouts, extensions/* is a workspace package tree with its
|
||||||
// dist-runtime/. Prefer that over source extensions only when the paired
|
// own package.json dependencies. Prefer it so git checkouts remain editable
|
||||||
// dist/ tree exists; otherwise wrappers can drift ahead of the last build.
|
// and dependency-complete without moving optional plugin deps back into root.
|
||||||
// Even when OpenClaw itself runs from TypeScript, bundled plugins should use
|
if (hasUsableSourceTree) {
|
||||||
// compiled JavaScript whenever it is available. Source plugin entries force
|
return sourceExtensionsDir;
|
||||||
// jiti onto hot runtime paths such as per-run tool construction.
|
}
|
||||||
|
|
||||||
const runtimeExtensionsDir = path.join(packageRoot, "dist-runtime", "extensions");
|
const runtimeExtensionsDir = path.join(packageRoot, "dist-runtime", "extensions");
|
||||||
const hasUsableRuntimeTree = sourceCheckout
|
const hasUsableRuntimeTree = sourceCheckout
|
||||||
? hasUsableBundledPluginTree(runtimeExtensionsDir)
|
? hasUsableBundledPluginTree(runtimeExtensionsDir)
|
||||||
|
|||||||
Reference in New Issue
Block a user