11import { spawnSync } from "node:child_process" ;
22import fs from "node:fs" ;
3+ import os from "node:os" ;
34import path from "node:path" ;
45
56const readJson = ( file ) => JSON . parse ( fs . readFileSync ( file , "utf8" ) ) ;
7+ const normalizePathForProbe = ( value ) => String ( value ?? "" ) . replace ( / \\ / g, "/" ) ;
8+ const bundledRuntimeFragments = ( pluginDir ) => [
9+ `/dist/extensions/${ pluginDir } ` ,
10+ `/dist-runtime/extensions/${ pluginDir } ` ,
11+ ] ;
12+
13+ function resolveStateDir ( ) {
14+ if ( process . env . OPENCLAW_STATE_DIR ) {
15+ return process . env . OPENCLAW_STATE_DIR ;
16+ }
17+ return path . join ( process . env . HOME || os . homedir ( ) , ".openclaw" ) ;
18+ }
19+
20+ function pathReferencesBundledRuntime ( value , pluginDir ) {
21+ const normalized = normalizePathForProbe ( value ) ;
22+ return bundledRuntimeFragments ( pluginDir ) . some ( ( fragment ) => normalized . includes ( fragment ) ) ;
23+ }
624
725function resolveOpenClawEntry ( ) {
826 if ( process . env . OPENCLAW_ENTRY ) {
@@ -109,8 +127,9 @@ async function selectedManifestEntries() {
109127}
110128
111129function assertInstalled ( pluginId , pluginDir , requiresConfig ) {
112- const configPath = path . join ( process . env . HOME , ".openclaw" , "openclaw.json" ) ;
113- const indexPath = path . join ( process . env . HOME , ".openclaw" , "plugins" , "installs.json" ) ;
130+ const stateDir = resolveStateDir ( ) ;
131+ const configPath = path . join ( stateDir , "openclaw.json" ) ;
132+ const indexPath = path . join ( stateDir , "plugins" , "installs.json" ) ;
114133 const config = readJson ( configPath ) ;
115134 const index = readJson ( indexPath ) ;
116135 const records = index . installRecords ?? index . records ?? { } ;
@@ -125,23 +144,15 @@ function assertInstalled(pluginId, pluginDir, requiresConfig) {
125144 }
126145 if (
127146 typeof record . sourcePath !== "string" ||
128- ! [ `/dist/extensions/${ pluginDir } ` , `/dist-runtime/extensions/${ pluginDir } ` ] . some ( ( fragment ) =>
129- record . sourcePath . includes ( fragment ) ,
130- )
147+ ! pathReferencesBundledRuntime ( record . sourcePath , pluginDir )
131148 ) {
132149 throw new Error ( `unexpected bundled source path for ${ pluginId } : ${ record . sourcePath } ` ) ;
133150 }
134- if ( record . installPath !== record . sourcePath ) {
151+ if ( normalizePathForProbe ( record . installPath ) !== normalizePathForProbe ( record . sourcePath ) ) {
135152 throw new Error ( `bundled install path should equal source path for ${ pluginId } ` ) ;
136153 }
137154 const paths = config . plugins ?. load ?. paths || [ ] ;
138- if (
139- paths . some ( ( entry ) =>
140- [ `/dist/extensions/${ pluginDir } ` , `/dist-runtime/extensions/${ pluginDir } ` ] . some (
141- ( fragment ) => String ( entry ) . includes ( fragment ) ,
142- ) ,
143- )
144- ) {
155+ if ( paths . some ( ( entry ) => pathReferencesBundledRuntime ( entry , pluginDir ) ) ) {
145156 throw new Error ( `config load paths should not include bundled install path for ${ pluginId } ` ) ;
146157 }
147158 if ( requiresConfig && config . plugins ?. entries ?. [ pluginId ] ?. enabled === true ) {
@@ -162,22 +173,17 @@ function assertInstalled(pluginId, pluginDir, requiresConfig) {
162173}
163174
164175function assertUninstalled ( pluginId , pluginDir ) {
165- const configPath = path . join ( process . env . HOME , ".openclaw" , "openclaw.json" ) ;
166- const indexPath = path . join ( process . env . HOME , ".openclaw" , "plugins" , "installs.json" ) ;
176+ const stateDir = resolveStateDir ( ) ;
177+ const configPath = path . join ( stateDir , "openclaw.json" ) ;
178+ const indexPath = path . join ( stateDir , "plugins" , "installs.json" ) ;
167179 const config = fs . existsSync ( configPath ) ? readJson ( configPath ) : { } ;
168180 const index = fs . existsSync ( indexPath ) ? readJson ( indexPath ) : { } ;
169181 const records = index . installRecords ?? index . records ?? { } ;
170182 if ( records [ pluginId ] ) {
171183 throw new Error ( `install record still present after uninstall for ${ pluginId } ` ) ;
172184 }
173185 const paths = config . plugins ?. load ?. paths || [ ] ;
174- if (
175- paths . some ( ( entry ) =>
176- [ `/dist/extensions/${ pluginDir } ` , `/dist-runtime/extensions/${ pluginDir } ` ] . some (
177- ( fragment ) => String ( entry ) . includes ( fragment ) ,
178- ) ,
179- )
180- ) {
186+ if ( paths . some ( ( entry ) => pathReferencesBundledRuntime ( entry , pluginDir ) ) ) {
181187 throw new Error ( `load path still present after uninstall for ${ pluginId } ` ) ;
182188 }
183189 if ( config . plugins ?. entries ?. [ pluginId ] ) {
@@ -189,7 +195,7 @@ function assertUninstalled(pluginId, pluginDir) {
189195 if ( ( config . plugins ?. deny || [ ] ) . includes ( pluginId ) ) {
190196 throw new Error ( `denylist still contains ${ pluginId } after uninstall` ) ;
191197 }
192- const managedPath = path . join ( process . env . HOME , ".openclaw" , "extensions" , pluginId ) ;
198+ const managedPath = path . join ( stateDir , "extensions" , pluginId ) ;
193199 if ( fs . existsSync ( managedPath ) ) {
194200 throw new Error (
195201 `managed install directory unexpectedly exists for bundled plugin ${ pluginId } : ${ managedPath } ` ,
0 commit comments