forge-openclaw-plugin 0.2.10 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -6
- package/dist/openclaw/local-runtime.d.ts +36 -0
- package/dist/openclaw/local-runtime.js +262 -0
- package/dist/openclaw/routes.js +13 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/forge-openclaw/SKILL.md +2 -0
package/README.md
CHANGED
|
@@ -72,21 +72,22 @@ Current OpenClaw builds should use package discovery:
|
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
74
|
openclaw plugins install forge-openclaw-plugin
|
|
75
|
-
openclaw gateway restart
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
If your OpenClaw install does not enable it automatically, run:
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
75
|
openclaw plugins enable forge-openclaw-plugin
|
|
76
|
+
node -e 'const fs=require("fs"); const p=process.env.HOME+"/.openclaw/openclaw.json"; const j=JSON.parse(fs.readFileSync(p,"utf8")); j.plugins ??= {}; j.plugins.allow = Array.from(new Set([...(j.plugins.allow || []), "forge-openclaw-plugin"])); fs.writeFileSync(p, JSON.stringify(j, null, 2)+"\n");'
|
|
82
77
|
openclaw gateway restart
|
|
78
|
+
openclaw forge health
|
|
83
79
|
```
|
|
84
80
|
|
|
81
|
+
`openclaw plugins enable forge-openclaw-plugin` marks the plugin enabled, but it does not guarantee that `plugins.allow` was repaired. The `node -e ...` command above preserves the current allow list and appends `"forge-openclaw-plugin"` if it is missing.
|
|
82
|
+
|
|
85
83
|
For release-parity local development from this repo:
|
|
86
84
|
|
|
87
85
|
```bash
|
|
88
86
|
openclaw plugins install ./projects/forge/openclaw-plugin
|
|
87
|
+
openclaw plugins enable forge-openclaw-plugin
|
|
88
|
+
node -e 'const fs=require("fs"); const p=process.env.HOME+"/.openclaw/openclaw.json"; const j=JSON.parse(fs.readFileSync(p,"utf8")); j.plugins ??= {}; j.plugins.allow = Array.from(new Set([...(j.plugins.allow || []), "forge-openclaw-plugin"])); fs.writeFileSync(p, JSON.stringify(j, null, 2)+"\n");'
|
|
89
89
|
openclaw gateway restart
|
|
90
|
+
openclaw forge health
|
|
90
91
|
```
|
|
91
92
|
|
|
92
93
|
Equivalent config:
|
|
@@ -202,6 +203,17 @@ The skill is entity-format-driven. It teaches the agent how to:
|
|
|
202
203
|
|
|
203
204
|
For local use, set the plugin origin to `http://127.0.0.1` or `http://localhost` and the plugin will bring Forge up on the configured port automatically.
|
|
204
205
|
|
|
206
|
+
If you want to manage that plugin-managed local runtime cleanly, use:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
openclaw forge start
|
|
210
|
+
openclaw forge stop
|
|
211
|
+
openclaw forge restart
|
|
212
|
+
openclaw forge status
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
These commands only manage the runtime when it was auto-started by the OpenClaw plugin. If Forge was started manually some other way, they tell you that instead of killing unrelated processes.
|
|
216
|
+
|
|
205
217
|
## Publishing and listing
|
|
206
218
|
|
|
207
219
|
The reliable publication path for the Forge plugin is:
|
|
@@ -1,3 +1,39 @@
|
|
|
1
1
|
import type { ForgePluginConfig } from "./api-client.js";
|
|
2
|
+
export type ForgeRuntimeStopResult = {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
stopped: boolean;
|
|
5
|
+
managed: boolean;
|
|
6
|
+
message: string;
|
|
7
|
+
pid: number | null;
|
|
8
|
+
};
|
|
9
|
+
export type ForgeRuntimeStartResult = {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
started: boolean;
|
|
12
|
+
managed: boolean;
|
|
13
|
+
message: string;
|
|
14
|
+
pid: number | null;
|
|
15
|
+
baseUrl: string;
|
|
16
|
+
};
|
|
17
|
+
export type ForgeRuntimeStatusResult = {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
running: boolean;
|
|
20
|
+
healthy: boolean;
|
|
21
|
+
managed: boolean;
|
|
22
|
+
message: string;
|
|
23
|
+
pid: number | null;
|
|
24
|
+
baseUrl: string;
|
|
25
|
+
};
|
|
26
|
+
export type ForgeRuntimeRestartResult = {
|
|
27
|
+
ok: boolean;
|
|
28
|
+
restarted: boolean;
|
|
29
|
+
managed: boolean;
|
|
30
|
+
message: string;
|
|
31
|
+
pid: number | null;
|
|
32
|
+
baseUrl: string;
|
|
33
|
+
};
|
|
2
34
|
export declare function ensureForgeRuntimeReady(config: ForgePluginConfig): Promise<void>;
|
|
35
|
+
export declare function startForgeRuntime(config: ForgePluginConfig): Promise<ForgeRuntimeStartResult>;
|
|
3
36
|
export declare function primeForgeRuntime(config: ForgePluginConfig): void;
|
|
37
|
+
export declare function stopForgeRuntime(config: ForgePluginConfig): Promise<ForgeRuntimeStopResult>;
|
|
38
|
+
export declare function getForgeRuntimeStatus(config: ForgePluginConfig): Promise<ForgeRuntimeStatusResult>;
|
|
39
|
+
export declare function restartForgeRuntime(config: ForgePluginConfig): Promise<ForgeRuntimeRestartResult>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
5
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
6
|
import { fileURLToPath } from "node:url";
|
|
5
7
|
const LOCAL_HOSTNAMES = new Set(["127.0.0.1", "localhost", "::1"]);
|
|
6
8
|
const STARTUP_TIMEOUT_MS = 15_000;
|
|
@@ -12,6 +14,63 @@ let startupPromise = null;
|
|
|
12
14
|
function runtimeKey(config) {
|
|
13
15
|
return `${config.origin}:${config.port}`;
|
|
14
16
|
}
|
|
17
|
+
function getRuntimeStatePath(config) {
|
|
18
|
+
const origin = new URL(config.origin).hostname.toLowerCase().replace(/[^a-z0-9._-]+/g, "-");
|
|
19
|
+
return path.join(homedir(), ".openclaw", "run", "forge-openclaw-plugin", `${origin}-${config.port}.json`);
|
|
20
|
+
}
|
|
21
|
+
async function writeRuntimeState(config, pid) {
|
|
22
|
+
const statePath = getRuntimeStatePath(config);
|
|
23
|
+
await mkdir(path.dirname(statePath), { recursive: true });
|
|
24
|
+
const payload = {
|
|
25
|
+
pid,
|
|
26
|
+
origin: config.origin,
|
|
27
|
+
port: config.port,
|
|
28
|
+
baseUrl: config.baseUrl,
|
|
29
|
+
startedAt: new Date().toISOString()
|
|
30
|
+
};
|
|
31
|
+
await writeFile(statePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
32
|
+
}
|
|
33
|
+
async function clearRuntimeState(config) {
|
|
34
|
+
await rm(getRuntimeStatePath(config), { force: true });
|
|
35
|
+
}
|
|
36
|
+
async function readRuntimeState(config) {
|
|
37
|
+
try {
|
|
38
|
+
const payload = await readFile(getRuntimeStatePath(config), "utf8");
|
|
39
|
+
const parsed = JSON.parse(payload);
|
|
40
|
+
if (typeof parsed.pid !== "number" || !Number.isFinite(parsed.pid)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
pid: Math.trunc(parsed.pid),
|
|
45
|
+
origin: typeof parsed.origin === "string" ? parsed.origin : config.origin,
|
|
46
|
+
port: typeof parsed.port === "number" ? parsed.port : config.port,
|
|
47
|
+
baseUrl: typeof parsed.baseUrl === "string" ? parsed.baseUrl : config.baseUrl,
|
|
48
|
+
startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : new Date(0).toISOString()
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function processExists(pid) {
|
|
56
|
+
try {
|
|
57
|
+
process.kill(pid, 0);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return !(error instanceof Error) || !("code" in error) || error.code !== "ESRCH";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function waitForProcessExit(pid, timeoutMs) {
|
|
65
|
+
const deadline = Date.now() + timeoutMs;
|
|
66
|
+
while (Date.now() < deadline) {
|
|
67
|
+
if (!processExists(pid)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
await new Promise((resolve) => setTimeout(resolve, HEALTHCHECK_INTERVAL_MS));
|
|
71
|
+
}
|
|
72
|
+
return !processExists(pid);
|
|
73
|
+
}
|
|
15
74
|
function isLocalOrigin(origin) {
|
|
16
75
|
try {
|
|
17
76
|
return LOCAL_HOSTNAMES.has(new URL(origin).hostname.toLowerCase());
|
|
@@ -87,9 +146,13 @@ function spawnManagedRuntime(config, plan) {
|
|
|
87
146
|
managedRuntimeChild = null;
|
|
88
147
|
managedRuntimeKey = null;
|
|
89
148
|
}
|
|
149
|
+
void clearRuntimeState(config);
|
|
90
150
|
});
|
|
91
151
|
managedRuntimeChild = child;
|
|
92
152
|
managedRuntimeKey = runtimeKey(config);
|
|
153
|
+
void writeRuntimeState(config, child.pid).catch(() => {
|
|
154
|
+
// State tracking is best effort. Runtime health checks remain authoritative.
|
|
155
|
+
});
|
|
93
156
|
}
|
|
94
157
|
async function waitForRuntime(config, timeoutMs) {
|
|
95
158
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -129,8 +192,207 @@ export async function ensureForgeRuntimeReady(config) {
|
|
|
129
192
|
});
|
|
130
193
|
return startupPromise;
|
|
131
194
|
}
|
|
195
|
+
export async function startForgeRuntime(config) {
|
|
196
|
+
if (!isLocalOrigin(config.origin)) {
|
|
197
|
+
return {
|
|
198
|
+
ok: false,
|
|
199
|
+
started: false,
|
|
200
|
+
managed: false,
|
|
201
|
+
message: "Forge start only supports local plugin-managed runtimes. Remote Forge targets must be started where they are hosted.",
|
|
202
|
+
pid: null,
|
|
203
|
+
baseUrl: config.baseUrl
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const existingState = await readRuntimeState(config);
|
|
207
|
+
if (existingState && processExists(existingState.pid) && (await isForgeHealthy(config, HEALTHCHECK_TIMEOUT_MS))) {
|
|
208
|
+
return {
|
|
209
|
+
ok: true,
|
|
210
|
+
started: false,
|
|
211
|
+
managed: true,
|
|
212
|
+
message: `Forge is already running on ${config.baseUrl}.`,
|
|
213
|
+
pid: existingState.pid,
|
|
214
|
+
baseUrl: config.baseUrl
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
await ensureForgeRuntimeReady(config);
|
|
218
|
+
const state = await readRuntimeState(config);
|
|
219
|
+
return {
|
|
220
|
+
ok: true,
|
|
221
|
+
started: true,
|
|
222
|
+
managed: true,
|
|
223
|
+
message: `Started the plugin-managed Forge runtime on ${config.baseUrl}.`,
|
|
224
|
+
pid: state?.pid ?? managedRuntimeChild?.pid ?? null,
|
|
225
|
+
baseUrl: config.baseUrl
|
|
226
|
+
};
|
|
227
|
+
}
|
|
132
228
|
export function primeForgeRuntime(config) {
|
|
133
229
|
void ensureForgeRuntimeReady(config).catch(() => {
|
|
134
230
|
// Keep plugin registration non-blocking. Failures surface on first real call.
|
|
135
231
|
});
|
|
136
232
|
}
|
|
233
|
+
export async function stopForgeRuntime(config) {
|
|
234
|
+
if (!isLocalOrigin(config.origin)) {
|
|
235
|
+
return {
|
|
236
|
+
ok: false,
|
|
237
|
+
stopped: false,
|
|
238
|
+
managed: false,
|
|
239
|
+
message: "Forge stop only supports local plugin-managed runtimes. Remote Forge targets must be stopped where they are hosted.",
|
|
240
|
+
pid: null
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const state = await readRuntimeState(config);
|
|
244
|
+
if (!state) {
|
|
245
|
+
return {
|
|
246
|
+
ok: true,
|
|
247
|
+
stopped: false,
|
|
248
|
+
managed: false,
|
|
249
|
+
message: (await isForgeHealthy(config, HEALTHCHECK_TIMEOUT_MS))
|
|
250
|
+
? "Forge is running, but it does not look like a plugin-managed runtime. Stop it where it was started."
|
|
251
|
+
: "Forge is not running through the plugin-managed local runtime.",
|
|
252
|
+
pid: null
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
if (!processExists(state.pid)) {
|
|
256
|
+
await clearRuntimeState(config);
|
|
257
|
+
return {
|
|
258
|
+
ok: true,
|
|
259
|
+
stopped: false,
|
|
260
|
+
managed: true,
|
|
261
|
+
message: "The saved Forge runtime PID was stale. The plugin-managed runtime is already stopped.",
|
|
262
|
+
pid: state.pid
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
process.kill(state.pid, "SIGTERM");
|
|
266
|
+
if (!(await waitForProcessExit(state.pid, 5_000))) {
|
|
267
|
+
process.kill(state.pid, "SIGKILL");
|
|
268
|
+
if (!(await waitForProcessExit(state.pid, 2_000))) {
|
|
269
|
+
return {
|
|
270
|
+
ok: false,
|
|
271
|
+
stopped: false,
|
|
272
|
+
managed: true,
|
|
273
|
+
message: `Forge runtime pid ${state.pid} did not stop cleanly.`,
|
|
274
|
+
pid: state.pid
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (managedRuntimeChild?.pid === state.pid) {
|
|
279
|
+
managedRuntimeChild = null;
|
|
280
|
+
managedRuntimeKey = null;
|
|
281
|
+
}
|
|
282
|
+
await clearRuntimeState(config);
|
|
283
|
+
return {
|
|
284
|
+
ok: true,
|
|
285
|
+
stopped: true,
|
|
286
|
+
managed: true,
|
|
287
|
+
message: `Stopped the plugin-managed Forge runtime on ${config.baseUrl}.`,
|
|
288
|
+
pid: state.pid
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
export async function getForgeRuntimeStatus(config) {
|
|
292
|
+
const healthy = await isForgeHealthy(config, HEALTHCHECK_TIMEOUT_MS);
|
|
293
|
+
const state = await readRuntimeState(config);
|
|
294
|
+
const pid = state?.pid ?? null;
|
|
295
|
+
const managed = Boolean(state);
|
|
296
|
+
const running = healthy || (pid !== null && processExists(pid));
|
|
297
|
+
if (!isLocalOrigin(config.origin)) {
|
|
298
|
+
return {
|
|
299
|
+
ok: healthy,
|
|
300
|
+
running: healthy,
|
|
301
|
+
healthy,
|
|
302
|
+
managed: false,
|
|
303
|
+
message: healthy
|
|
304
|
+
? `Forge is reachable at ${config.baseUrl}. Runtime lifecycle is managed remotely.`
|
|
305
|
+
: `Forge is not reachable at ${config.baseUrl}. Runtime lifecycle is managed remotely.`,
|
|
306
|
+
pid: null,
|
|
307
|
+
baseUrl: config.baseUrl
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (managed && pid !== null && !processExists(pid)) {
|
|
311
|
+
await clearRuntimeState(config);
|
|
312
|
+
return {
|
|
313
|
+
ok: true,
|
|
314
|
+
running: false,
|
|
315
|
+
healthy: false,
|
|
316
|
+
managed: true,
|
|
317
|
+
message: "The saved Forge runtime PID was stale. The plugin-managed runtime is stopped.",
|
|
318
|
+
pid,
|
|
319
|
+
baseUrl: config.baseUrl
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
if (healthy && managed) {
|
|
323
|
+
return {
|
|
324
|
+
ok: true,
|
|
325
|
+
running: true,
|
|
326
|
+
healthy: true,
|
|
327
|
+
managed: true,
|
|
328
|
+
message: `Forge is running and healthy on ${config.baseUrl}.`,
|
|
329
|
+
pid,
|
|
330
|
+
baseUrl: config.baseUrl
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (healthy) {
|
|
334
|
+
return {
|
|
335
|
+
ok: true,
|
|
336
|
+
running: true,
|
|
337
|
+
healthy: true,
|
|
338
|
+
managed: false,
|
|
339
|
+
message: `Forge is running on ${config.baseUrl}, but it does not look like a plugin-managed runtime.`,
|
|
340
|
+
pid: null,
|
|
341
|
+
baseUrl: config.baseUrl
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
if (managed) {
|
|
345
|
+
return {
|
|
346
|
+
ok: true,
|
|
347
|
+
running: false,
|
|
348
|
+
healthy: false,
|
|
349
|
+
managed: true,
|
|
350
|
+
message: "The plugin-managed Forge runtime is stopped.",
|
|
351
|
+
pid,
|
|
352
|
+
baseUrl: config.baseUrl
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
ok: true,
|
|
357
|
+
running: false,
|
|
358
|
+
healthy: false,
|
|
359
|
+
managed: false,
|
|
360
|
+
message: "Forge is not running through the plugin-managed local runtime.",
|
|
361
|
+
pid: null,
|
|
362
|
+
baseUrl: config.baseUrl
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
export async function restartForgeRuntime(config) {
|
|
366
|
+
if (!isLocalOrigin(config.origin)) {
|
|
367
|
+
return {
|
|
368
|
+
ok: false,
|
|
369
|
+
restarted: false,
|
|
370
|
+
managed: false,
|
|
371
|
+
message: "Forge restart only supports local plugin-managed runtimes. Remote Forge targets must be restarted where they are hosted.",
|
|
372
|
+
pid: null,
|
|
373
|
+
baseUrl: config.baseUrl
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
const stopResult = await stopForgeRuntime(config);
|
|
377
|
+
if (!stopResult.ok) {
|
|
378
|
+
return {
|
|
379
|
+
ok: false,
|
|
380
|
+
restarted: false,
|
|
381
|
+
managed: stopResult.managed,
|
|
382
|
+
message: stopResult.message,
|
|
383
|
+
pid: stopResult.pid,
|
|
384
|
+
baseUrl: config.baseUrl
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const startResult = await startForgeRuntime(config);
|
|
388
|
+
return {
|
|
389
|
+
ok: startResult.ok,
|
|
390
|
+
restarted: startResult.ok,
|
|
391
|
+
managed: true,
|
|
392
|
+
message: startResult.ok
|
|
393
|
+
? `Restarted the plugin-managed Forge runtime on ${config.baseUrl}.`
|
|
394
|
+
: startResult.message,
|
|
395
|
+
pid: startResult.pid,
|
|
396
|
+
baseUrl: config.baseUrl
|
|
397
|
+
};
|
|
398
|
+
}
|
package/dist/openclaw/routes.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { canBootstrapOperatorSession, callConfiguredForgeApi, expectForgeSuccess, readJsonRequestBody, readSingleHeaderValue, requireApiToken, writeForgeProxyResponse, writePluginError, writeRedirectResponse } from "./api-client.js";
|
|
2
|
+
import { getForgeRuntimeStatus, restartForgeRuntime, startForgeRuntime, stopForgeRuntime } from "./local-runtime.js";
|
|
2
3
|
import { collectSupportedPluginApiRouteKeys, makeApiRouteKey } from "./parity.js";
|
|
3
4
|
function passthroughSearch(path, url) {
|
|
4
5
|
return `${path}${url.search}`;
|
|
@@ -374,6 +375,18 @@ export function registerForgePluginCli(api, config) {
|
|
|
374
375
|
command.command("ui").description("Print the Forge UI entrypoint").action(async () => {
|
|
375
376
|
console.log(JSON.stringify({ webAppUrl: await resolveForgeUiUrl(config), pluginUiRoute: "/forge/v1/ui" }, null, 2));
|
|
376
377
|
});
|
|
378
|
+
command.command("start").description("Start the local Forge runtime when it is managed by the OpenClaw plugin").action(async () => {
|
|
379
|
+
console.log(JSON.stringify(await startForgeRuntime(config), null, 2));
|
|
380
|
+
});
|
|
381
|
+
command.command("stop").description("Stop the local Forge runtime when it was auto-started by the OpenClaw plugin").action(async () => {
|
|
382
|
+
console.log(JSON.stringify(await stopForgeRuntime(config), null, 2));
|
|
383
|
+
});
|
|
384
|
+
command.command("restart").description("Restart the local Forge runtime when it is managed by the OpenClaw plugin").action(async () => {
|
|
385
|
+
console.log(JSON.stringify(await restartForgeRuntime(config), null, 2));
|
|
386
|
+
});
|
|
387
|
+
command.command("status").description("Report whether the local Forge runtime is running and whether it is plugin-managed").action(async () => {
|
|
388
|
+
console.log(JSON.stringify(await getForgeRuntimeStatus(config), null, 2));
|
|
389
|
+
});
|
|
377
390
|
command.command("doctor").description("Run plugin connectivity and curated route diagnostics").action(async () => {
|
|
378
391
|
console.log(JSON.stringify(await runDoctor(config), null, 2));
|
|
379
392
|
});
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -16,6 +16,8 @@ Forge data location rule:
|
|
|
16
16
|
- on a linked repo-local install, this usually means `<repo>/openclaw-plugin/data/forge.sqlite`
|
|
17
17
|
- if the user wants the data somewhere else for persistence, backup, or manual control, tell them to set `plugins.entries["forge-openclaw-plugin"].config.dataRoot` and restart the OpenClaw gateway
|
|
18
18
|
- if the user asks where the data is stored or how to move it, explain the current default plainly and show the exact config field
|
|
19
|
+
- if the user wants to manage a plugin-managed local Forge runtime cleanly, tell them to run `openclaw forge start`, `openclaw forge stop`, `openclaw forge restart`, or `openclaw forge status`
|
|
20
|
+
- these commands only manage a runtime that the OpenClaw plugin auto-started itself; if Forge was started manually elsewhere, they will say so instead of killing random local processes
|
|
19
21
|
|
|
20
22
|
Use these exact entity meanings when deciding what the user is describing.
|
|
21
23
|
|