@ahyi/restart-continuity 0.3.1
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 +91 -0
- package/index.ts +313 -0
- package/openclaw.plugin.json +54 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Restart Continuity Plugin
|
|
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
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
### Local path
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
openclaw --profile <profile> plugins install /path/to/restart-continuity-plugin
|
|
17
|
+
openclaw --profile <profile> plugins enable restart-continuity
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### npm registry
|
|
21
|
+
|
|
22
|
+
```bash
|
|
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
|
|
28
|
+
|
|
29
|
+
This plugin is designed to replace scattered hook-based restart continuity setups with one installable extension.
|
|
30
|
+
|
|
31
|
+
On plugin service start it can:
|
|
32
|
+
|
|
33
|
+
1. initialize its runtime marker/state files
|
|
34
|
+
2. run configured startup resumers
|
|
35
|
+
3. write `memory/restart-continuity-state.json`
|
|
36
|
+
4. append a summary to `memory/YYYY-MM-DD.md`
|
|
37
|
+
5. stage a one-time startup receipt for bootstrap delivery
|
|
38
|
+
|
|
39
|
+
## Example config
|
|
40
|
+
|
|
41
|
+
```json
|
|
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
|
+
```
|
|
72
|
+
|
|
73
|
+
## Uninstall
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
openclaw --profile <profile> plugins uninstall restart-continuity
|
|
77
|
+
```
|
|
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
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const OPENCLAW_BIN = process.env.OPENCLAW_BIN || "openclaw";
|
|
8
|
+
|
|
9
|
+
function nowIso() {
|
|
10
|
+
return new Date().toISOString();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeString(value, fallback = "") {
|
|
14
|
+
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getWorkspaceDir(api) {
|
|
18
|
+
const pluginConfig = api.pluginConfig || {};
|
|
19
|
+
const explicit = normalizeString(pluginConfig.workspaceDir, "");
|
|
20
|
+
if (explicit) return explicit;
|
|
21
|
+
return normalizeString(api.config?.workspace?.dir, process.cwd());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getProfile(api) {
|
|
25
|
+
const pluginConfig = api.pluginConfig || {};
|
|
26
|
+
return normalizeString(pluginConfig.profile, "mh");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolvePath(workspaceDir, filePath, fallbackRelative) {
|
|
30
|
+
const raw = normalizeString(filePath, fallbackRelative);
|
|
31
|
+
if (path.isAbsolute(raw)) return raw;
|
|
32
|
+
return path.join(workspaceDir, raw);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function readJsonSafe(filePath, fallback = null) {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
38
|
+
} catch {
|
|
39
|
+
return fallback;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function ensureDir(filePath) {
|
|
44
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function writeJson(filePath, data) {
|
|
48
|
+
await ensureDir(filePath);
|
|
49
|
+
await fs.writeFile(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function appendText(filePath, text) {
|
|
53
|
+
await ensureDir(filePath);
|
|
54
|
+
await fs.appendFile(filePath, text, "utf8");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function fileExists(filePath) {
|
|
58
|
+
try {
|
|
59
|
+
await fs.access(filePath);
|
|
60
|
+
return true;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function runOpenclaw(profile, args, timeoutMs = 180000) {
|
|
67
|
+
const finalArgs = profile ? ["--profile", profile, ...args] : args;
|
|
68
|
+
const { stdout } = await execFileAsync(OPENCLAW_BIN, finalArgs, {
|
|
69
|
+
timeout: timeoutMs,
|
|
70
|
+
maxBuffer: 1024 * 1024,
|
|
71
|
+
});
|
|
72
|
+
return String(stdout || "").trim();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseCronRunsJson(raw) {
|
|
76
|
+
try {
|
|
77
|
+
const obj = JSON.parse(raw);
|
|
78
|
+
if (Array.isArray(obj?.entries) && obj.entries.length > 0) return obj.entries[0];
|
|
79
|
+
if (Array.isArray(obj) && obj.length > 0) return obj[0];
|
|
80
|
+
return null;
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function buildReceipt(summary) {
|
|
87
|
+
return `启动自检完成:检查 ${summary.checked} 个任务,健康 ${summary.healthy.length},续跑 ${summary.resumed.length},跳过 ${summary.skipped.length},错误 ${summary.errors.length}。`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function buildDailyLog(summary) {
|
|
91
|
+
const lines = [
|
|
92
|
+
`\n## Restart continuity check (${summary.timestampLocal || summary.timestamp})`,
|
|
93
|
+
"",
|
|
94
|
+
`- Checked jobs: ${summary.checked}`,
|
|
95
|
+
`- Already healthy: ${summary.healthy.length ? summary.healthy.join(", ") : "none"}`,
|
|
96
|
+
`- Resumed jobs: ${summary.resumed.length ? summary.resumed.join(", ") : "none"}`,
|
|
97
|
+
`- Skipped jobs: ${summary.skipped.length ? summary.skipped.join(", ") : "none"}`,
|
|
98
|
+
`- Errors: ${summary.errors.length ? summary.errors.join(" | ") : "none"}`,
|
|
99
|
+
`- Outcome: ${summary.errors.length ? (summary.resumed.length ? "partial" : "error") : (summary.resumed.length ? "resumed" : "no-op")}`,
|
|
100
|
+
"",
|
|
101
|
+
];
|
|
102
|
+
return lines.join("\n");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getDatePartsForShanghai(date = new Date()) {
|
|
106
|
+
const rendered = new Intl.DateTimeFormat("en-CA", {
|
|
107
|
+
timeZone: "Asia/Shanghai",
|
|
108
|
+
year: "numeric",
|
|
109
|
+
month: "2-digit",
|
|
110
|
+
day: "2-digit",
|
|
111
|
+
hour: "2-digit",
|
|
112
|
+
minute: "2-digit",
|
|
113
|
+
second: "2-digit",
|
|
114
|
+
hour12: false,
|
|
115
|
+
}).formatToParts(date);
|
|
116
|
+
const map = Object.fromEntries(rendered.filter((p) => p.type !== "literal").map((p) => [p.type, p.value]));
|
|
117
|
+
return {
|
|
118
|
+
date: `${map.year}-${map.month}-${map.day}`,
|
|
119
|
+
local: `${map.year}-${map.month}-${map.day} ${map.hour}:${map.minute}:${map.second} Asia/Shanghai`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function runCronHealthcheck(profile, resumer) {
|
|
124
|
+
const successStatuses = Array.isArray(resumer.successStatuses) && resumer.successStatuses.length
|
|
125
|
+
? resumer.successStatuses.map(String)
|
|
126
|
+
: ["ok"];
|
|
127
|
+
const runTimeoutMs = Number.isInteger(resumer.runTimeoutMs) ? resumer.runTimeoutMs : 120000;
|
|
128
|
+
|
|
129
|
+
const runsRaw = await runOpenclaw(profile, ["cron", "runs", "--id", String(resumer.jobId), "--limit", "1"], 120000);
|
|
130
|
+
const latest = parseCronRunsJson(runsRaw);
|
|
131
|
+
const latestStatus = latest && typeof latest.status === "string" ? latest.status : null;
|
|
132
|
+
|
|
133
|
+
if (latestStatus && successStatuses.includes(latestStatus)) {
|
|
134
|
+
return { status: "healthy", detail: `${resumer.name || resumer.id}`, latestStatus };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (resumer.autoResume === false) {
|
|
138
|
+
return { status: "skipped", detail: `${resumer.name || resumer.id} (latest=${latestStatus || "none"})`, latestStatus };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const runRaw = await runOpenclaw(profile, ["cron", "run", String(resumer.jobId), "--expect-final", "--timeout", String(runTimeoutMs)], Math.max(runTimeoutMs + 20000, 30000));
|
|
142
|
+
const normalized = /\bok\b/i.test(runRaw) ? "ok" : "ran";
|
|
143
|
+
return { status: "resumed", detail: `${resumer.name || resumer.id} => ${normalized}`, latestStatus };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function ensureConfigInitialized(api) {
|
|
147
|
+
const workspaceDir = getWorkspaceDir(api);
|
|
148
|
+
const pluginConfig = api.pluginConfig || {};
|
|
149
|
+
const stateFile = resolvePath(workspaceDir, pluginConfig.stateFile, "memory/restart-continuity-state.json");
|
|
150
|
+
const defaultsFile = resolvePath(workspaceDir, pluginConfig.defaultsFile, "plugins/restart-continuity.defaults.json");
|
|
151
|
+
const markerFile = resolvePath(workspaceDir, pluginConfig.markerFile, "memory/restart-continuity-installed.json");
|
|
152
|
+
|
|
153
|
+
if (await fileExists(markerFile)) return;
|
|
154
|
+
|
|
155
|
+
const defaults = await readJsonSafe(defaultsFile, null);
|
|
156
|
+
await writeJson(markerFile, {
|
|
157
|
+
installedAt: nowIso(),
|
|
158
|
+
source: "restart-continuity",
|
|
159
|
+
workspaceDir,
|
|
160
|
+
defaultsApplied: Boolean(defaults),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (!(await fileExists(stateFile))) {
|
|
164
|
+
await writeJson(stateFile, {
|
|
165
|
+
version: 1,
|
|
166
|
+
initializedAt: nowIso(),
|
|
167
|
+
status: "initialized",
|
|
168
|
+
source: "restart-continuity",
|
|
169
|
+
defaultsApplied: Boolean(defaults),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
api.logger.info?.(`[restart-continuity] initialized (${defaults ? "defaults detected" : "no defaults file"})`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function runStartupCheck(api) {
|
|
177
|
+
const pluginConfig = api.pluginConfig || {};
|
|
178
|
+
const profile = getProfile(api);
|
|
179
|
+
const workspaceDir = getWorkspaceDir(api);
|
|
180
|
+
const stateFile = resolvePath(workspaceDir, pluginConfig.stateFile, "memory/restart-continuity-state.json");
|
|
181
|
+
const receiptFile = resolvePath(workspaceDir, pluginConfig.receiptFile, "memory/restart-continuity-receipt.json");
|
|
182
|
+
const logToDailyMemory = pluginConfig.logToDailyMemory !== false;
|
|
183
|
+
const notifyOnBootstrap = pluginConfig.notifyOnBootstrap !== false;
|
|
184
|
+
const { date, local } = getDatePartsForShanghai(new Date());
|
|
185
|
+
const resumers = Array.isArray(pluginConfig.resumers) ? pluginConfig.resumers : [];
|
|
186
|
+
|
|
187
|
+
const summary = {
|
|
188
|
+
version: 1,
|
|
189
|
+
timestamp: nowIso(),
|
|
190
|
+
timestampLocal: local,
|
|
191
|
+
checked: 0,
|
|
192
|
+
healthy: [],
|
|
193
|
+
resumed: [],
|
|
194
|
+
skipped: [],
|
|
195
|
+
errors: [],
|
|
196
|
+
results: [],
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
for (const resumer of resumers) {
|
|
200
|
+
if (!resumer || typeof resumer !== "object") continue;
|
|
201
|
+
if (resumer.enabled === false) {
|
|
202
|
+
summary.skipped.push(`${resumer.name || resumer.id || "unknown"} (disabled)`);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
summary.checked += 1;
|
|
206
|
+
try {
|
|
207
|
+
if (resumer.kind === "cron-healthcheck") {
|
|
208
|
+
const result = await runCronHealthcheck(profile, resumer);
|
|
209
|
+
summary.results.push({ id: resumer.id, kind: resumer.kind, ...result });
|
|
210
|
+
if (result.status === "healthy") summary.healthy.push(result.detail);
|
|
211
|
+
else if (result.status === "resumed") summary.resumed.push(result.detail);
|
|
212
|
+
else if (result.status === "skipped") summary.skipped.push(result.detail);
|
|
213
|
+
else summary.errors.push(`${resumer.name || resumer.id}: unknown status`);
|
|
214
|
+
} else {
|
|
215
|
+
summary.errors.push(`${resumer.name || resumer.id}: unsupported kind ${String(resumer.kind)}`);
|
|
216
|
+
}
|
|
217
|
+
} catch (error) {
|
|
218
|
+
const message = error && error.message ? error.message : String(error);
|
|
219
|
+
summary.errors.push(`${resumer.name || resumer.id}: ${message}`);
|
|
220
|
+
summary.results.push({ id: resumer.id, kind: resumer.kind, status: "error", error: message });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
summary.receipt = buildReceipt(summary);
|
|
225
|
+
await writeJson(stateFile, summary);
|
|
226
|
+
|
|
227
|
+
if (notifyOnBootstrap) {
|
|
228
|
+
await writeJson(receiptFile, {
|
|
229
|
+
createdAt: summary.timestamp,
|
|
230
|
+
source: "restart-continuity",
|
|
231
|
+
receipt: summary.receipt,
|
|
232
|
+
summary,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (logToDailyMemory) {
|
|
237
|
+
const dailyPath = path.join(workspaceDir, "memory", `${date}.md`);
|
|
238
|
+
await appendText(dailyPath, buildDailyLog(summary));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
api.logger.info?.(`[restart-continuity] ${summary.receipt}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const plugin = {
|
|
245
|
+
id: "restart-continuity",
|
|
246
|
+
name: "Restart Continuity",
|
|
247
|
+
description: "Gateway restart continuity plugin with resumable startup checks and receipt handoff.",
|
|
248
|
+
configSchema: {
|
|
249
|
+
parse(value) {
|
|
250
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
251
|
+
},
|
|
252
|
+
uiHints: {
|
|
253
|
+
profile: { label: "OpenClaw Profile" },
|
|
254
|
+
workspaceDir: { label: "Workspace Directory" },
|
|
255
|
+
stateFile: { label: "State File" },
|
|
256
|
+
receiptFile: { label: "Receipt File" },
|
|
257
|
+
defaultsFile: { label: "Defaults File" },
|
|
258
|
+
markerFile: { label: "Install Marker File" },
|
|
259
|
+
logToDailyMemory: { label: "Append summary to daily memory" },
|
|
260
|
+
notifyOnBootstrap: { label: "Inject startup receipt into next bootstrap reply" },
|
|
261
|
+
resumers: { label: "Resumable task definitions" },
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
register(api) {
|
|
265
|
+
api.registerService({
|
|
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
|
+
});
|
|
277
|
+
|
|
278
|
+
api.registerHook(
|
|
279
|
+
"agent:bootstrap",
|
|
280
|
+
async (event) => {
|
|
281
|
+
const pluginConfig = api.pluginConfig || {};
|
|
282
|
+
if (pluginConfig.notifyOnBootstrap === false) return;
|
|
283
|
+
const ctx = event && event.context ? event.context : {};
|
|
284
|
+
const bootstrapFiles = Array.isArray(ctx.bootstrapFiles) ? ctx.bootstrapFiles : null;
|
|
285
|
+
if (!bootstrapFiles) return;
|
|
286
|
+
|
|
287
|
+
const workspaceDir = getWorkspaceDir(api);
|
|
288
|
+
const receiptFile = resolvePath(workspaceDir, pluginConfig.receiptFile, "memory/restart-continuity-receipt.json");
|
|
289
|
+
const pending = await readJsonSafe(receiptFile, null);
|
|
290
|
+
if (!pending || !pending.receipt) return;
|
|
291
|
+
|
|
292
|
+
const content = [
|
|
293
|
+
"## Restart Continuity Receipt",
|
|
294
|
+
"",
|
|
295
|
+
pending.receipt,
|
|
296
|
+
"",
|
|
297
|
+
"Instruction: In your next reply to the user, include this startup receipt concisely (1-2 lines) before normal content. Do this once only.",
|
|
298
|
+
].join("\n");
|
|
299
|
+
|
|
300
|
+
bootstrapFiles.push({ path: "RESTART_CONTINUITY_RECEIPT.md", content, virtual: true });
|
|
301
|
+
try {
|
|
302
|
+
await fs.unlink(receiptFile);
|
|
303
|
+
} catch {}
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: "restart-continuity.agent-bootstrap",
|
|
307
|
+
description: "Injects the pending restart continuity receipt into the next bootstrap reply.",
|
|
308
|
+
},
|
|
309
|
+
);
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
export default plugin;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "restart-continuity",
|
|
3
|
+
"name": "Restart Continuity",
|
|
4
|
+
"description": "Gateway restart continuity plugin with resumable startup checks and receipt handoff.",
|
|
5
|
+
"version": "0.3.0",
|
|
6
|
+
"configSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"profile": { "type": "string", "default": "mh" },
|
|
11
|
+
"workspaceDir": { "type": "string", "default": "" },
|
|
12
|
+
"stateFile": { "type": "string", "default": "memory/restart-continuity-state.json" },
|
|
13
|
+
"receiptFile": { "type": "string", "default": "memory/restart-continuity-receipt.json" },
|
|
14
|
+
"defaultsFile": { "type": "string", "default": "plugins/restart-continuity.defaults.json" },
|
|
15
|
+
"markerFile": { "type": "string", "default": "memory/restart-continuity-installed.json" },
|
|
16
|
+
"logToDailyMemory": { "type": "boolean", "default": true },
|
|
17
|
+
"notifyOnBootstrap": { "type": "boolean", "default": true },
|
|
18
|
+
"resumers": {
|
|
19
|
+
"type": "array",
|
|
20
|
+
"items": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"properties": {
|
|
24
|
+
"id": { "type": "string" },
|
|
25
|
+
"kind": { "type": "string", "enum": ["cron-healthcheck"] },
|
|
26
|
+
"name": { "type": "string" },
|
|
27
|
+
"enabled": { "type": "boolean", "default": true },
|
|
28
|
+
"autoResume": { "type": "boolean", "default": true },
|
|
29
|
+
"jobId": { "type": "string" },
|
|
30
|
+
"successStatuses": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"items": { "type": "string" },
|
|
33
|
+
"default": ["ok"]
|
|
34
|
+
},
|
|
35
|
+
"runTimeoutMs": { "type": "integer", "minimum": 1000, "default": 120000 }
|
|
36
|
+
},
|
|
37
|
+
"required": ["id", "kind", "jobId"]
|
|
38
|
+
},
|
|
39
|
+
"default": []
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"uiHints": {
|
|
44
|
+
"profile": { "label": "OpenClaw Profile" },
|
|
45
|
+
"workspaceDir": { "label": "Workspace Directory" },
|
|
46
|
+
"stateFile": { "label": "State File" },
|
|
47
|
+
"receiptFile": { "label": "Receipt File" },
|
|
48
|
+
"defaultsFile": { "label": "Defaults File" },
|
|
49
|
+
"markerFile": { "label": "Install Marker File" },
|
|
50
|
+
"logToDailyMemory": { "label": "Append summary to daily memory" },
|
|
51
|
+
"notifyOnBootstrap": { "label": "Inject startup receipt into next bootstrap reply" },
|
|
52
|
+
"resumers": { "label": "Resumable task definitions" }
|
|
53
|
+
}
|
|
54
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ahyi/restart-continuity",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "OpenClaw restart continuity plugin with resumable startup checks and receipt handoff",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"openclaw",
|
|
12
|
+
"plugin",
|
|
13
|
+
"restart",
|
|
14
|
+
"continuity",
|
|
15
|
+
"gateway"
|
|
16
|
+
],
|
|
17
|
+
"files": [
|
|
18
|
+
"index.ts",
|
|
19
|
+
"openclaw.plugin.json",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"openclaw": {
|
|
23
|
+
"extensions": [
|
|
24
|
+
"./index.ts"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20"
|
|
29
|
+
}
|
|
30
|
+
}
|