@ahyi/restart-continuity 0.3.1 → 0.5.0
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 +17 -70
- package/index.ts +52 -30
- package/openclaw.plugin.json +1 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,91 +1,38 @@
|
|
|
1
1
|
# Restart Continuity Plugin
|
|
2
2
|
|
|
3
|
-
OpenClaw plugin for **gateway restart continuity
|
|
4
|
-
|
|
5
|
-
- runs resumable startup checks when the plugin service starts
|
|
6
|
-
- can health-check and replay selected cron jobs
|
|
7
|
-
- writes a structured continuity state file
|
|
8
|
-
- writes a human-readable daily memory log
|
|
9
|
-
- can hand off a one-shot startup receipt during agent bootstrap
|
|
3
|
+
OpenClaw plugin for **gateway restart continuity** with **zero-config behavior on this mh instance**.
|
|
10
4
|
|
|
11
5
|
## Install
|
|
12
6
|
|
|
13
|
-
### Local path
|
|
14
|
-
|
|
15
7
|
```bash
|
|
16
|
-
openclaw --profile <profile> plugins install /
|
|
8
|
+
openclaw --profile <profile> plugins install @ahyi/restart-continuity@0.5.0
|
|
17
9
|
openclaw --profile <profile> plugins enable restart-continuity
|
|
18
10
|
```
|
|
19
11
|
|
|
20
|
-
|
|
12
|
+
Then restart the gateway.
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
openclaw --profile <profile> plugins install @ahyi/restart-continuity@0.3.1
|
|
24
|
-
openclaw --profile <profile> plugins enable restart-continuity
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## What it does
|
|
14
|
+
## Zero-config behavior
|
|
28
15
|
|
|
29
|
-
|
|
16
|
+
After install + enable, the plugin will work **without any extra config** on this mh instance.
|
|
30
17
|
|
|
31
|
-
|
|
18
|
+
It automatically:
|
|
32
19
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
20
|
+
- detects the mh workspace directory
|
|
21
|
+
- assumes profile `mh` unless overridden
|
|
22
|
+
- uses built-in default resumers for:
|
|
23
|
+
- `mh-nightly-learnings-check`
|
|
24
|
+
- `mh-weekly-self-improve`
|
|
25
|
+
- writes `memory/restart-continuity-state.json`
|
|
26
|
+
- writes `memory/restart-continuity-installed.json`
|
|
27
|
+
- appends a summary to the daily memory log
|
|
28
|
+
- stages a one-shot bootstrap receipt
|
|
38
29
|
|
|
39
|
-
##
|
|
30
|
+
## Optional explicit config
|
|
40
31
|
|
|
41
|
-
|
|
42
|
-
{
|
|
43
|
-
"plugins": {
|
|
44
|
-
"entries": {
|
|
45
|
-
"restart-continuity": {
|
|
46
|
-
"enabled": true,
|
|
47
|
-
"config": {
|
|
48
|
-
"profile": "mh",
|
|
49
|
-
"workspaceDir": "/root/.openclaw/workspace-mh",
|
|
50
|
-
"logToDailyMemory": true,
|
|
51
|
-
"notifyOnBootstrap": true,
|
|
52
|
-
"resumers": [
|
|
53
|
-
{
|
|
54
|
-
"id": "nightly-learnings",
|
|
55
|
-
"kind": "cron-healthcheck",
|
|
56
|
-
"name": "mh-nightly-learnings-check",
|
|
57
|
-
"jobId": "38a8b8cb-c25a-4bc2-a8a1-d252f9d933ec"
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"id": "weekly-self-improve",
|
|
61
|
-
"kind": "cron-healthcheck",
|
|
62
|
-
"name": "mh-weekly-self-improve",
|
|
63
|
-
"jobId": "6e912522-0591-4307-a155-a6ece496be9c"
|
|
64
|
-
}
|
|
65
|
-
]
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
```
|
|
32
|
+
You can still override behavior with `plugins.entries.restart-continuity.config`, but it is not required for this instance.
|
|
72
33
|
|
|
73
34
|
## Uninstall
|
|
74
35
|
|
|
75
36
|
```bash
|
|
76
37
|
openclaw --profile <profile> plugins uninstall restart-continuity
|
|
77
38
|
```
|
|
78
|
-
|
|
79
|
-
OpenClaw removes the plugin config entry and install record. If you want a fully clean teardown, also delete plugin runtime artifacts such as:
|
|
80
|
-
|
|
81
|
-
- `memory/restart-continuity-state.json`
|
|
82
|
-
- `memory/restart-continuity-installed.json`
|
|
83
|
-
- `memory/restart-continuity-receipt.json`
|
|
84
|
-
|
|
85
|
-
## Publish
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
npm publish --access public
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
If scoped publishing is used, make sure the npm account/org owns the scope.
|
package/index.ts
CHANGED
|
@@ -5,6 +5,22 @@ import { promisify } from "node:util";
|
|
|
5
5
|
|
|
6
6
|
const execFileAsync = promisify(execFile);
|
|
7
7
|
const OPENCLAW_BIN = process.env.OPENCLAW_BIN || "openclaw";
|
|
8
|
+
let bootStarted = false;
|
|
9
|
+
|
|
10
|
+
const BUILTIN_DEFAULT_RESUMERS = [
|
|
11
|
+
{
|
|
12
|
+
id: "nightly-learnings",
|
|
13
|
+
kind: "cron-healthcheck",
|
|
14
|
+
name: "mh-nightly-learnings-check",
|
|
15
|
+
jobId: "38a8b8cb-c25a-4bc2-a8a1-d252f9d933ec",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "weekly-self-improve",
|
|
19
|
+
kind: "cron-healthcheck",
|
|
20
|
+
name: "mh-weekly-self-improve",
|
|
21
|
+
jobId: "6e912522-0591-4307-a155-a6ece496be9c",
|
|
22
|
+
},
|
|
23
|
+
];
|
|
8
24
|
|
|
9
25
|
function nowIso() {
|
|
10
26
|
return new Date().toISOString();
|
|
@@ -14,14 +30,17 @@ function normalizeString(value, fallback = "") {
|
|
|
14
30
|
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
15
31
|
}
|
|
16
32
|
|
|
17
|
-
function
|
|
33
|
+
function detectWorkspaceDir(api) {
|
|
18
34
|
const pluginConfig = api.pluginConfig || {};
|
|
19
35
|
const explicit = normalizeString(pluginConfig.workspaceDir, "");
|
|
20
36
|
if (explicit) return explicit;
|
|
21
|
-
|
|
37
|
+
const cfgWorkspace = normalizeString(api.config?.workspace?.dir, "");
|
|
38
|
+
if (cfgWorkspace) return cfgWorkspace;
|
|
39
|
+
const mhWorkspace = "/root/.openclaw/workspace-mh";
|
|
40
|
+
return mhWorkspace;
|
|
22
41
|
}
|
|
23
42
|
|
|
24
|
-
function
|
|
43
|
+
function detectProfile(api) {
|
|
25
44
|
const pluginConfig = api.pluginConfig || {};
|
|
26
45
|
return normalizeString(pluginConfig.profile, "mh");
|
|
27
46
|
}
|
|
@@ -120,6 +139,13 @@ function getDatePartsForShanghai(date = new Date()) {
|
|
|
120
139
|
};
|
|
121
140
|
}
|
|
122
141
|
|
|
142
|
+
function getEffectiveResumers(pluginConfig) {
|
|
143
|
+
if (Array.isArray(pluginConfig.resumers) && pluginConfig.resumers.length > 0) {
|
|
144
|
+
return pluginConfig.resumers;
|
|
145
|
+
}
|
|
146
|
+
return BUILTIN_DEFAULT_RESUMERS;
|
|
147
|
+
}
|
|
148
|
+
|
|
123
149
|
async function runCronHealthcheck(profile, resumer) {
|
|
124
150
|
const successStatuses = Array.isArray(resumer.successStatuses) && resumer.successStatuses.length
|
|
125
151
|
? resumer.successStatuses.map(String)
|
|
@@ -143,21 +169,17 @@ async function runCronHealthcheck(profile, resumer) {
|
|
|
143
169
|
return { status: "resumed", detail: `${resumer.name || resumer.id} => ${normalized}`, latestStatus };
|
|
144
170
|
}
|
|
145
171
|
|
|
146
|
-
async function ensureConfigInitialized(api) {
|
|
147
|
-
const workspaceDir = getWorkspaceDir(api);
|
|
148
|
-
const pluginConfig = api.pluginConfig || {};
|
|
172
|
+
async function ensureConfigInitialized(api, workspaceDir, pluginConfig) {
|
|
149
173
|
const stateFile = resolvePath(workspaceDir, pluginConfig.stateFile, "memory/restart-continuity-state.json");
|
|
150
|
-
const defaultsFile = resolvePath(workspaceDir, pluginConfig.defaultsFile, "plugins/restart-continuity.defaults.json");
|
|
151
174
|
const markerFile = resolvePath(workspaceDir, pluginConfig.markerFile, "memory/restart-continuity-installed.json");
|
|
152
175
|
|
|
153
176
|
if (await fileExists(markerFile)) return;
|
|
154
177
|
|
|
155
|
-
const defaults = await readJsonSafe(defaultsFile, null);
|
|
156
178
|
await writeJson(markerFile, {
|
|
157
179
|
installedAt: nowIso(),
|
|
158
180
|
source: "restart-continuity",
|
|
159
181
|
workspaceDir,
|
|
160
|
-
|
|
182
|
+
builtInDefaultsActive: !(Array.isArray(pluginConfig.resumers) && pluginConfig.resumers.length > 0),
|
|
161
183
|
});
|
|
162
184
|
|
|
163
185
|
if (!(await fileExists(stateFile))) {
|
|
@@ -166,23 +188,21 @@ async function ensureConfigInitialized(api) {
|
|
|
166
188
|
initializedAt: nowIso(),
|
|
167
189
|
status: "initialized",
|
|
168
190
|
source: "restart-continuity",
|
|
169
|
-
|
|
191
|
+
builtInDefaultsActive: !(Array.isArray(pluginConfig.resumers) && pluginConfig.resumers.length > 0),
|
|
170
192
|
});
|
|
171
193
|
}
|
|
172
194
|
|
|
173
|
-
api.logger.info?.(
|
|
195
|
+
api.logger.info?.("[restart-continuity] initialized");
|
|
174
196
|
}
|
|
175
197
|
|
|
176
|
-
async function runStartupCheck(api) {
|
|
177
|
-
const
|
|
178
|
-
const profile = getProfile(api);
|
|
179
|
-
const workspaceDir = getWorkspaceDir(api);
|
|
198
|
+
async function runStartupCheck(api, workspaceDir, pluginConfig) {
|
|
199
|
+
const profile = detectProfile(api);
|
|
180
200
|
const stateFile = resolvePath(workspaceDir, pluginConfig.stateFile, "memory/restart-continuity-state.json");
|
|
181
201
|
const receiptFile = resolvePath(workspaceDir, pluginConfig.receiptFile, "memory/restart-continuity-receipt.json");
|
|
182
202
|
const logToDailyMemory = pluginConfig.logToDailyMemory !== false;
|
|
183
203
|
const notifyOnBootstrap = pluginConfig.notifyOnBootstrap !== false;
|
|
184
204
|
const { date, local } = getDatePartsForShanghai(new Date());
|
|
185
|
-
const resumers =
|
|
205
|
+
const resumers = getEffectiveResumers(pluginConfig);
|
|
186
206
|
|
|
187
207
|
const summary = {
|
|
188
208
|
version: 1,
|
|
@@ -241,6 +261,20 @@ async function runStartupCheck(api) {
|
|
|
241
261
|
api.logger.info?.(`[restart-continuity] ${summary.receipt}`);
|
|
242
262
|
}
|
|
243
263
|
|
|
264
|
+
async function boot(api) {
|
|
265
|
+
if (bootStarted) return;
|
|
266
|
+
bootStarted = true;
|
|
267
|
+
const pluginConfig = api.pluginConfig || {};
|
|
268
|
+
const workspaceDir = detectWorkspaceDir(api);
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
await ensureConfigInitialized(api, workspaceDir, pluginConfig);
|
|
272
|
+
await runStartupCheck(api, workspaceDir, pluginConfig);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
api.logger.warn?.(`[restart-continuity] boot failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
244
278
|
const plugin = {
|
|
245
279
|
id: "restart-continuity",
|
|
246
280
|
name: "Restart Continuity",
|
|
@@ -254,7 +288,6 @@ const plugin = {
|
|
|
254
288
|
workspaceDir: { label: "Workspace Directory" },
|
|
255
289
|
stateFile: { label: "State File" },
|
|
256
290
|
receiptFile: { label: "Receipt File" },
|
|
257
|
-
defaultsFile: { label: "Defaults File" },
|
|
258
291
|
markerFile: { label: "Install Marker File" },
|
|
259
292
|
logToDailyMemory: { label: "Append summary to daily memory" },
|
|
260
293
|
notifyOnBootstrap: { label: "Inject startup receipt into next bootstrap reply" },
|
|
@@ -262,18 +295,7 @@ const plugin = {
|
|
|
262
295
|
},
|
|
263
296
|
},
|
|
264
297
|
register(api) {
|
|
265
|
-
api
|
|
266
|
-
id: "restart-continuity-service",
|
|
267
|
-
start: async () => {
|
|
268
|
-
try {
|
|
269
|
-
await ensureConfigInitialized(api);
|
|
270
|
-
await runStartupCheck(api);
|
|
271
|
-
} catch (err) {
|
|
272
|
-
api.logger.warn?.(`[restart-continuity] service start failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
273
|
-
}
|
|
274
|
-
},
|
|
275
|
-
stop: async () => {},
|
|
276
|
-
});
|
|
298
|
+
void boot(api);
|
|
277
299
|
|
|
278
300
|
api.registerHook(
|
|
279
301
|
"agent:bootstrap",
|
|
@@ -284,7 +306,7 @@ const plugin = {
|
|
|
284
306
|
const bootstrapFiles = Array.isArray(ctx.bootstrapFiles) ? ctx.bootstrapFiles : null;
|
|
285
307
|
if (!bootstrapFiles) return;
|
|
286
308
|
|
|
287
|
-
const workspaceDir =
|
|
309
|
+
const workspaceDir = detectWorkspaceDir(api);
|
|
288
310
|
const receiptFile = resolvePath(workspaceDir, pluginConfig.receiptFile, "memory/restart-continuity-receipt.json");
|
|
289
311
|
const pending = await readJsonSafe(receiptFile, null);
|
|
290
312
|
if (!pending || !pending.receipt) return;
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "restart-continuity",
|
|
3
3
|
"name": "Restart Continuity",
|
|
4
4
|
"description": "Gateway restart continuity plugin with resumable startup checks and receipt handoff.",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.5.0",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"workspaceDir": { "type": "string", "default": "" },
|
|
12
12
|
"stateFile": { "type": "string", "default": "memory/restart-continuity-state.json" },
|
|
13
13
|
"receiptFile": { "type": "string", "default": "memory/restart-continuity-receipt.json" },
|
|
14
|
-
"defaultsFile": { "type": "string", "default": "plugins/restart-continuity.defaults.json" },
|
|
15
14
|
"markerFile": { "type": "string", "default": "memory/restart-continuity-installed.json" },
|
|
16
15
|
"logToDailyMemory": { "type": "boolean", "default": true },
|
|
17
16
|
"notifyOnBootstrap": { "type": "boolean", "default": true },
|
|
@@ -45,7 +44,6 @@
|
|
|
45
44
|
"workspaceDir": { "label": "Workspace Directory" },
|
|
46
45
|
"stateFile": { "label": "State File" },
|
|
47
46
|
"receiptFile": { "label": "Receipt File" },
|
|
48
|
-
"defaultsFile": { "label": "Defaults File" },
|
|
49
47
|
"markerFile": { "label": "Install Marker File" },
|
|
50
48
|
"logToDailyMemory": { "label": "Append summary to daily memory" },
|
|
51
49
|
"notifyOnBootstrap": { "label": "Inject startup receipt into next bootstrap reply" },
|