nextclaw 0.6.11 → 0.6.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/dist/cli/index.js +195 -169
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -84,24 +84,140 @@ var RestartCoordinator = class {
|
|
|
84
84
|
}
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
+
// src/cli/restart-sentinel.ts
|
|
88
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
89
|
+
import { resolve } from "path";
|
|
90
|
+
import { getDataDir } from "@nextclaw/core";
|
|
91
|
+
var RESTART_SENTINEL_FILENAME = "restart-sentinel.json";
|
|
92
|
+
var PENDING_SYSTEM_EVENTS_KEY = "pending_system_events";
|
|
93
|
+
var RESTART_REASON_MAX_CHARS = 240;
|
|
94
|
+
var RESTART_NOTE_MAX_CHARS = 600;
|
|
95
|
+
var RESTART_OUTBOUND_MAX_CHARS = 1200;
|
|
96
|
+
function trimTo(value, maxChars) {
|
|
97
|
+
const text = value.trim();
|
|
98
|
+
if (!text) {
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
if (text.length <= maxChars) {
|
|
102
|
+
return text;
|
|
103
|
+
}
|
|
104
|
+
return `${text.slice(0, Math.max(0, maxChars - 1)).trimEnd()}\u2026`;
|
|
105
|
+
}
|
|
106
|
+
function normalizeLine(value, maxChars) {
|
|
107
|
+
const trimmed = trimTo(value, maxChars);
|
|
108
|
+
return trimmed ? trimmed : null;
|
|
109
|
+
}
|
|
110
|
+
function resolveRestartSentinelPath() {
|
|
111
|
+
return resolve(getDataDir(), "run", RESTART_SENTINEL_FILENAME);
|
|
112
|
+
}
|
|
113
|
+
async function writeRestartSentinel(payload) {
|
|
114
|
+
const path = resolveRestartSentinelPath();
|
|
115
|
+
mkdirSync(resolve(path, ".."), { recursive: true });
|
|
116
|
+
const file = {
|
|
117
|
+
version: 1,
|
|
118
|
+
payload
|
|
119
|
+
};
|
|
120
|
+
writeFileSync(path, `${JSON.stringify(file, null, 2)}
|
|
121
|
+
`, "utf-8");
|
|
122
|
+
return path;
|
|
123
|
+
}
|
|
124
|
+
async function consumeRestartSentinel() {
|
|
125
|
+
const path = resolveRestartSentinelPath();
|
|
126
|
+
if (!existsSync(path)) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const raw = readFileSync(path, "utf-8");
|
|
131
|
+
const parsed = JSON.parse(raw);
|
|
132
|
+
if (!parsed || parsed.version !== 1 || !parsed.payload) {
|
|
133
|
+
rmSync(path, { force: true });
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
rmSync(path, { force: true });
|
|
137
|
+
return parsed;
|
|
138
|
+
} catch {
|
|
139
|
+
rmSync(path, { force: true });
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function summarizeRestartSentinel(payload) {
|
|
144
|
+
const reason = normalizeLine(payload.stats?.reason ?? "", RESTART_REASON_MAX_CHARS);
|
|
145
|
+
if (payload.kind === "update.run") {
|
|
146
|
+
return payload.status === "ok" ? "\u2705 NextClaw update completed and service restarted." : "\u26A0\uFE0F NextClaw update finished with issues.";
|
|
147
|
+
}
|
|
148
|
+
if (payload.kind === "config.apply" || payload.kind === "config.patch") {
|
|
149
|
+
return payload.status === "ok" ? "\u2705 Config applied and service restarted." : "\u26A0\uFE0F Config change restart finished with issues.";
|
|
150
|
+
}
|
|
151
|
+
if (reason) {
|
|
152
|
+
return `Gateway restart complete (${reason}).`;
|
|
153
|
+
}
|
|
154
|
+
return "Gateway restart complete.";
|
|
155
|
+
}
|
|
156
|
+
function formatRestartSentinelMessage(payload) {
|
|
157
|
+
const lines = [summarizeRestartSentinel(payload)];
|
|
158
|
+
const note = normalizeLine(payload.message ?? "", RESTART_NOTE_MAX_CHARS);
|
|
159
|
+
if (note) {
|
|
160
|
+
lines.push(note);
|
|
161
|
+
}
|
|
162
|
+
const reason = normalizeLine(payload.stats?.reason ?? "", RESTART_REASON_MAX_CHARS);
|
|
163
|
+
if (reason && !lines.some((line) => line.includes(reason))) {
|
|
164
|
+
lines.push(`Reason: ${reason}`);
|
|
165
|
+
}
|
|
166
|
+
const message = lines.join("\n").trim();
|
|
167
|
+
return trimTo(message, RESTART_OUTBOUND_MAX_CHARS);
|
|
168
|
+
}
|
|
169
|
+
function parseSessionKey(sessionKey) {
|
|
170
|
+
const value = sessionKey?.trim();
|
|
171
|
+
if (!value) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const separator = value.indexOf(":");
|
|
175
|
+
if (separator <= 0 || separator >= value.length - 1) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
channel: value.slice(0, separator),
|
|
180
|
+
chatId: value.slice(separator + 1)
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function enqueuePendingSystemEvent(sessionManager, sessionKey, message) {
|
|
184
|
+
const text = message.trim();
|
|
185
|
+
if (!text) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const session = sessionManager.getOrCreate(sessionKey);
|
|
189
|
+
const queueRaw = session.metadata[PENDING_SYSTEM_EVENTS_KEY];
|
|
190
|
+
const queue = Array.isArray(queueRaw) ? queueRaw.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean) : [];
|
|
191
|
+
if (queue.at(-1) === text) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
queue.push(text);
|
|
195
|
+
if (queue.length > 20) {
|
|
196
|
+
queue.splice(0, queue.length - 20);
|
|
197
|
+
}
|
|
198
|
+
session.metadata[PENDING_SYSTEM_EVENTS_KEY] = queue;
|
|
199
|
+
session.updatedAt = /* @__PURE__ */ new Date();
|
|
200
|
+
sessionManager.save(session);
|
|
201
|
+
}
|
|
202
|
+
|
|
87
203
|
// src/cli/skills/clawhub.ts
|
|
88
204
|
import { spawnSync } from "child_process";
|
|
89
|
-
import { existsSync } from "fs";
|
|
90
|
-
import { isAbsolute, join, resolve } from "path";
|
|
205
|
+
import { existsSync as existsSync2 } from "fs";
|
|
206
|
+
import { isAbsolute, join, resolve as resolve2 } from "path";
|
|
91
207
|
async function installClawHubSkill(options) {
|
|
92
208
|
const slug = options.slug.trim();
|
|
93
209
|
if (!slug) {
|
|
94
210
|
throw new Error("Skill slug is required.");
|
|
95
211
|
}
|
|
96
|
-
const workdir =
|
|
97
|
-
if (!
|
|
212
|
+
const workdir = resolve2(options.workdir);
|
|
213
|
+
if (!existsSync2(workdir)) {
|
|
98
214
|
throw new Error(`Workdir does not exist: ${workdir}`);
|
|
99
215
|
}
|
|
100
216
|
const dirName = options.dir?.trim() || "skills";
|
|
101
|
-
const destinationDir = isAbsolute(dirName) ?
|
|
217
|
+
const destinationDir = isAbsolute(dirName) ? resolve2(dirName, slug) : resolve2(workdir, dirName, slug);
|
|
102
218
|
const skillFile = join(destinationDir, "SKILL.md");
|
|
103
|
-
if (!options.force &&
|
|
104
|
-
if (
|
|
219
|
+
if (!options.force && existsSync2(destinationDir)) {
|
|
220
|
+
if (existsSync2(skillFile)) {
|
|
105
221
|
return {
|
|
106
222
|
slug,
|
|
107
223
|
version: options.version,
|
|
@@ -156,15 +272,15 @@ function buildClawHubArgs(slug, options) {
|
|
|
156
272
|
|
|
157
273
|
// src/cli/update/runner.ts
|
|
158
274
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
159
|
-
import { resolve as
|
|
275
|
+
import { resolve as resolve4 } from "path";
|
|
160
276
|
|
|
161
277
|
// src/cli/utils.ts
|
|
162
|
-
import { existsSync as
|
|
163
|
-
import { join as join2, resolve as
|
|
278
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "fs";
|
|
279
|
+
import { join as join2, resolve as resolve3 } from "path";
|
|
164
280
|
import { spawn } from "child_process";
|
|
165
281
|
import { isIP } from "net";
|
|
166
282
|
import { fileURLToPath } from "url";
|
|
167
|
-
import { getDataDir, getPackageVersion as getCorePackageVersion } from "@nextclaw/core";
|
|
283
|
+
import { getDataDir as getDataDir2, getPackageVersion as getCorePackageVersion } from "@nextclaw/core";
|
|
168
284
|
function resolveUiConfig(config2, overrides) {
|
|
169
285
|
const base = config2.ui ?? { enabled: false, host: "127.0.0.1", port: 18791, open: false };
|
|
170
286
|
return { ...base, ...overrides ?? {} };
|
|
@@ -214,11 +330,11 @@ function buildServeArgs(options) {
|
|
|
214
330
|
}
|
|
215
331
|
function readServiceState() {
|
|
216
332
|
const path = resolveServiceStatePath();
|
|
217
|
-
if (!
|
|
333
|
+
if (!existsSync3(path)) {
|
|
218
334
|
return null;
|
|
219
335
|
}
|
|
220
336
|
try {
|
|
221
|
-
const raw =
|
|
337
|
+
const raw = readFileSync2(path, "utf-8");
|
|
222
338
|
return JSON.parse(raw);
|
|
223
339
|
} catch {
|
|
224
340
|
return null;
|
|
@@ -226,20 +342,20 @@ function readServiceState() {
|
|
|
226
342
|
}
|
|
227
343
|
function writeServiceState(state) {
|
|
228
344
|
const path = resolveServiceStatePath();
|
|
229
|
-
|
|
230
|
-
|
|
345
|
+
mkdirSync2(resolve3(path, ".."), { recursive: true });
|
|
346
|
+
writeFileSync2(path, JSON.stringify(state, null, 2));
|
|
231
347
|
}
|
|
232
348
|
function clearServiceState() {
|
|
233
349
|
const path = resolveServiceStatePath();
|
|
234
|
-
if (
|
|
235
|
-
|
|
350
|
+
if (existsSync3(path)) {
|
|
351
|
+
rmSync2(path, { force: true });
|
|
236
352
|
}
|
|
237
353
|
}
|
|
238
354
|
function resolveServiceStatePath() {
|
|
239
|
-
return
|
|
355
|
+
return resolve3(getDataDir2(), "run", "service.json");
|
|
240
356
|
}
|
|
241
357
|
function resolveServiceLogPath() {
|
|
242
|
-
return
|
|
358
|
+
return resolve3(getDataDir2(), "logs", "service.log");
|
|
243
359
|
}
|
|
244
360
|
function isProcessRunning(pid) {
|
|
245
361
|
try {
|
|
@@ -265,8 +381,8 @@ function resolveUiStaticDir() {
|
|
|
265
381
|
if (envDir) {
|
|
266
382
|
candidates.push(envDir);
|
|
267
383
|
}
|
|
268
|
-
const cliDir =
|
|
269
|
-
const pkgRoot =
|
|
384
|
+
const cliDir = resolve3(fileURLToPath(new URL(".", import.meta.url)));
|
|
385
|
+
const pkgRoot = resolve3(cliDir, "..", "..");
|
|
270
386
|
candidates.push(join2(pkgRoot, "ui-dist"));
|
|
271
387
|
candidates.push(join2(pkgRoot, "ui"));
|
|
272
388
|
candidates.push(join2(pkgRoot, "..", "ui-dist"));
|
|
@@ -278,7 +394,7 @@ function resolveUiStaticDir() {
|
|
|
278
394
|
candidates.push(join2(pkgRoot, "..", "..", "packages", "nextclaw-ui", "dist"));
|
|
279
395
|
candidates.push(join2(pkgRoot, "..", "..", "nextclaw-ui", "dist"));
|
|
280
396
|
for (const dir of candidates) {
|
|
281
|
-
if (
|
|
397
|
+
if (existsSync3(join2(dir, "index.html"))) {
|
|
282
398
|
return dir;
|
|
283
399
|
}
|
|
284
400
|
}
|
|
@@ -305,19 +421,19 @@ function which(binary) {
|
|
|
305
421
|
const paths = (process.env.PATH ?? "").split(":");
|
|
306
422
|
for (const dir of paths) {
|
|
307
423
|
const full = join2(dir, binary);
|
|
308
|
-
if (
|
|
424
|
+
if (existsSync3(full)) {
|
|
309
425
|
return true;
|
|
310
426
|
}
|
|
311
427
|
}
|
|
312
428
|
return false;
|
|
313
429
|
}
|
|
314
430
|
function resolveVersionFromPackageTree(startDir, expectedName) {
|
|
315
|
-
let current =
|
|
431
|
+
let current = resolve3(startDir);
|
|
316
432
|
while (current.length > 0) {
|
|
317
433
|
const pkgPath = join2(current, "package.json");
|
|
318
|
-
if (
|
|
434
|
+
if (existsSync3(pkgPath)) {
|
|
319
435
|
try {
|
|
320
|
-
const raw =
|
|
436
|
+
const raw = readFileSync2(pkgPath, "utf-8");
|
|
321
437
|
const parsed = JSON.parse(raw);
|
|
322
438
|
if (typeof parsed.version === "string") {
|
|
323
439
|
if (!expectedName || parsed.name === expectedName) {
|
|
@@ -327,7 +443,7 @@ function resolveVersionFromPackageTree(startDir, expectedName) {
|
|
|
327
443
|
} catch {
|
|
328
444
|
}
|
|
329
445
|
}
|
|
330
|
-
const parent =
|
|
446
|
+
const parent = resolve3(current, "..");
|
|
331
447
|
if (parent === current) {
|
|
332
448
|
break;
|
|
333
449
|
}
|
|
@@ -336,7 +452,7 @@ function resolveVersionFromPackageTree(startDir, expectedName) {
|
|
|
336
452
|
return null;
|
|
337
453
|
}
|
|
338
454
|
function getPackageVersion() {
|
|
339
|
-
const cliDir =
|
|
455
|
+
const cliDir = resolve3(fileURLToPath(new URL(".", import.meta.url)));
|
|
340
456
|
return resolveVersionFromPackageTree(cliDir, "nextclaw") ?? resolveVersionFromPackageTree(cliDir) ?? getCorePackageVersion();
|
|
341
457
|
}
|
|
342
458
|
function printAgentResponse(response) {
|
|
@@ -375,7 +491,7 @@ function runSelfUpdate(options = {}) {
|
|
|
375
491
|
return { ok: result.status === 0, code: result.status };
|
|
376
492
|
};
|
|
377
493
|
if (updateCommand) {
|
|
378
|
-
const cwd = options.cwd ?
|
|
494
|
+
const cwd = options.cwd ? resolve4(options.cwd) : process.cwd();
|
|
379
495
|
const ok = runStep("sh", ["-c", updateCommand], cwd);
|
|
380
496
|
if (!ok.ok) {
|
|
381
497
|
return { ok: false, error: "update command failed", strategy: "command", steps };
|
|
@@ -413,8 +529,8 @@ import {
|
|
|
413
529
|
expandHome
|
|
414
530
|
} from "@nextclaw/core";
|
|
415
531
|
import { createInterface } from "readline";
|
|
416
|
-
import { existsSync as
|
|
417
|
-
import { resolve as
|
|
532
|
+
import { existsSync as existsSync4 } from "fs";
|
|
533
|
+
import { resolve as resolve5 } from "path";
|
|
418
534
|
function loadPluginRegistry(config2, workspaceDir) {
|
|
419
535
|
return loadOpenClawPlugins({
|
|
420
536
|
config: config2,
|
|
@@ -691,7 +807,7 @@ var PluginCommands = class {
|
|
|
691
807
|
process.exit(1);
|
|
692
808
|
}
|
|
693
809
|
const install = config2.plugins.installs?.[pluginId];
|
|
694
|
-
const isLinked = install?.source === "path" && (!install.installPath || !install.sourcePath ||
|
|
810
|
+
const isLinked = install?.source === "path" && (!install.installPath || !install.sourcePath || resolve5(install.installPath) === resolve5(install.sourcePath));
|
|
695
811
|
const preview = [];
|
|
696
812
|
if (hasEntry) {
|
|
697
813
|
preview.push("config entry");
|
|
@@ -770,9 +886,9 @@ var PluginCommands = class {
|
|
|
770
886
|
process.exit(1);
|
|
771
887
|
}
|
|
772
888
|
const normalized = fileSpec && fileSpec.ok ? fileSpec.path : pathOrSpec;
|
|
773
|
-
const resolved =
|
|
889
|
+
const resolved = resolve5(expandHome(normalized));
|
|
774
890
|
const config2 = loadConfig();
|
|
775
|
-
if (
|
|
891
|
+
if (existsSync4(resolved)) {
|
|
776
892
|
if (opts.link) {
|
|
777
893
|
const probe = await installPluginFromPath({ path: resolved, dryRun: true });
|
|
778
894
|
if (!probe.ok) {
|
|
@@ -1331,11 +1447,11 @@ var ChannelCommands = class {
|
|
|
1331
1447
|
};
|
|
1332
1448
|
|
|
1333
1449
|
// src/cli/commands/cron.ts
|
|
1334
|
-
import { CronService, getDataDir as
|
|
1450
|
+
import { CronService, getDataDir as getDataDir3 } from "@nextclaw/core";
|
|
1335
1451
|
import { join as join3 } from "path";
|
|
1336
1452
|
var CronCommands = class {
|
|
1337
1453
|
cronList(opts) {
|
|
1338
|
-
const storePath = join3(
|
|
1454
|
+
const storePath = join3(getDataDir3(), "cron", "jobs.json");
|
|
1339
1455
|
const service = new CronService(storePath);
|
|
1340
1456
|
const jobs = service.listJobs(Boolean(opts.all));
|
|
1341
1457
|
if (!jobs.length) {
|
|
@@ -1355,7 +1471,7 @@ var CronCommands = class {
|
|
|
1355
1471
|
}
|
|
1356
1472
|
}
|
|
1357
1473
|
cronAdd(opts) {
|
|
1358
|
-
const storePath = join3(
|
|
1474
|
+
const storePath = join3(getDataDir3(), "cron", "jobs.json");
|
|
1359
1475
|
const service = new CronService(storePath);
|
|
1360
1476
|
let schedule = null;
|
|
1361
1477
|
if (opts.every) {
|
|
@@ -1380,7 +1496,7 @@ var CronCommands = class {
|
|
|
1380
1496
|
console.log(`\u2713 Added job '${job.name}' (${job.id})`);
|
|
1381
1497
|
}
|
|
1382
1498
|
cronRemove(jobId) {
|
|
1383
|
-
const storePath = join3(
|
|
1499
|
+
const storePath = join3(getDataDir3(), "cron", "jobs.json");
|
|
1384
1500
|
const service = new CronService(storePath);
|
|
1385
1501
|
if (service.removeJob(jobId)) {
|
|
1386
1502
|
console.log(`\u2713 Removed job ${jobId}`);
|
|
@@ -1389,7 +1505,7 @@ var CronCommands = class {
|
|
|
1389
1505
|
}
|
|
1390
1506
|
}
|
|
1391
1507
|
cronEnable(jobId, opts) {
|
|
1392
|
-
const storePath = join3(
|
|
1508
|
+
const storePath = join3(getDataDir3(), "cron", "jobs.json");
|
|
1393
1509
|
const service = new CronService(storePath);
|
|
1394
1510
|
const job = service.enableJob(jobId, !opts.disable);
|
|
1395
1511
|
if (job) {
|
|
@@ -1399,7 +1515,7 @@ var CronCommands = class {
|
|
|
1399
1515
|
}
|
|
1400
1516
|
}
|
|
1401
1517
|
async cronRun(jobId, opts) {
|
|
1402
|
-
const storePath = join3(
|
|
1518
|
+
const storePath = join3(getDataDir3(), "cron", "jobs.json");
|
|
1403
1519
|
const service = new CronService(storePath);
|
|
1404
1520
|
const ok = await service.runJob(jobId, Boolean(opts.force));
|
|
1405
1521
|
console.log(ok ? "\u2713 Job executed" : `Failed to run job ${jobId}`);
|
|
@@ -1408,12 +1524,12 @@ var CronCommands = class {
|
|
|
1408
1524
|
|
|
1409
1525
|
// src/cli/commands/diagnostics.ts
|
|
1410
1526
|
import { createServer as createNetServer } from "net";
|
|
1411
|
-
import { existsSync as
|
|
1412
|
-
import { resolve as
|
|
1527
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
1528
|
+
import { resolve as resolve6 } from "path";
|
|
1413
1529
|
import {
|
|
1414
1530
|
APP_NAME,
|
|
1415
1531
|
getConfigPath,
|
|
1416
|
-
getDataDir as
|
|
1532
|
+
getDataDir as getDataDir4,
|
|
1417
1533
|
getWorkspacePath as getWorkspacePath3,
|
|
1418
1534
|
loadConfig as loadConfig4,
|
|
1419
1535
|
PROVIDERS as PROVIDERS3
|
|
@@ -1579,7 +1695,7 @@ var DiagnosticsCommands = class {
|
|
|
1579
1695
|
const configPath = getConfigPath();
|
|
1580
1696
|
const config2 = loadConfig4();
|
|
1581
1697
|
const workspacePath = getWorkspacePath3(config2.agents.defaults.workspace);
|
|
1582
|
-
const serviceStatePath =
|
|
1698
|
+
const serviceStatePath = resolve6(getDataDir4(), "run", "service.json");
|
|
1583
1699
|
const fixActions = [];
|
|
1584
1700
|
let serviceState = readServiceState();
|
|
1585
1701
|
if (params.fix && serviceState && !isProcessRunning(serviceState.pid)) {
|
|
@@ -1618,11 +1734,11 @@ var DiagnosticsCommands = class {
|
|
|
1618
1734
|
});
|
|
1619
1735
|
const issues = [];
|
|
1620
1736
|
const recommendations = [];
|
|
1621
|
-
if (!
|
|
1737
|
+
if (!existsSync5(configPath)) {
|
|
1622
1738
|
issues.push("Config file is missing.");
|
|
1623
1739
|
recommendations.push(`Run ${APP_NAME} init to create config files.`);
|
|
1624
1740
|
}
|
|
1625
|
-
if (!
|
|
1741
|
+
if (!existsSync5(workspacePath)) {
|
|
1626
1742
|
issues.push("Workspace directory does not exist.");
|
|
1627
1743
|
recommendations.push(`Run ${APP_NAME} init to create workspace templates.`);
|
|
1628
1744
|
}
|
|
@@ -1650,13 +1766,13 @@ var DiagnosticsCommands = class {
|
|
|
1650
1766
|
return {
|
|
1651
1767
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1652
1768
|
configPath,
|
|
1653
|
-
configExists:
|
|
1769
|
+
configExists: existsSync5(configPath),
|
|
1654
1770
|
workspacePath,
|
|
1655
|
-
workspaceExists:
|
|
1771
|
+
workspaceExists: existsSync5(workspacePath),
|
|
1656
1772
|
model: config2.agents.defaults.model,
|
|
1657
1773
|
providers,
|
|
1658
1774
|
serviceStatePath,
|
|
1659
|
-
serviceStateExists:
|
|
1775
|
+
serviceStateExists: existsSync5(serviceStatePath),
|
|
1660
1776
|
fixActions,
|
|
1661
1777
|
process: {
|
|
1662
1778
|
managedByState,
|
|
@@ -1706,11 +1822,11 @@ var DiagnosticsCommands = class {
|
|
|
1706
1822
|
}
|
|
1707
1823
|
}
|
|
1708
1824
|
readLogTail(path, maxLines = 25) {
|
|
1709
|
-
if (!
|
|
1825
|
+
if (!existsSync5(path)) {
|
|
1710
1826
|
return [];
|
|
1711
1827
|
}
|
|
1712
1828
|
try {
|
|
1713
|
-
const lines =
|
|
1829
|
+
const lines = readFileSync3(path, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
1714
1830
|
if (lines.length <= maxLines) {
|
|
1715
1831
|
return lines;
|
|
1716
1832
|
}
|
|
@@ -1781,124 +1897,6 @@ import {
|
|
|
1781
1897
|
ConfigSchema,
|
|
1782
1898
|
redactConfigObject
|
|
1783
1899
|
} from "@nextclaw/core";
|
|
1784
|
-
|
|
1785
|
-
// src/cli/restart-sentinel.ts
|
|
1786
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1787
|
-
import { resolve as resolve6 } from "path";
|
|
1788
|
-
import { getDataDir as getDataDir4 } from "@nextclaw/core";
|
|
1789
|
-
var RESTART_SENTINEL_FILENAME = "restart-sentinel.json";
|
|
1790
|
-
var PENDING_SYSTEM_EVENTS_KEY = "pending_system_events";
|
|
1791
|
-
var RESTART_REASON_MAX_CHARS = 240;
|
|
1792
|
-
var RESTART_NOTE_MAX_CHARS = 600;
|
|
1793
|
-
var RESTART_OUTBOUND_MAX_CHARS = 1200;
|
|
1794
|
-
function trimTo(value, maxChars) {
|
|
1795
|
-
const text = value.trim();
|
|
1796
|
-
if (!text) {
|
|
1797
|
-
return "";
|
|
1798
|
-
}
|
|
1799
|
-
if (text.length <= maxChars) {
|
|
1800
|
-
return text;
|
|
1801
|
-
}
|
|
1802
|
-
return `${text.slice(0, Math.max(0, maxChars - 1)).trimEnd()}\u2026`;
|
|
1803
|
-
}
|
|
1804
|
-
function normalizeLine(value, maxChars) {
|
|
1805
|
-
const trimmed = trimTo(value, maxChars);
|
|
1806
|
-
return trimmed ? trimmed : null;
|
|
1807
|
-
}
|
|
1808
|
-
function resolveRestartSentinelPath() {
|
|
1809
|
-
return resolve6(getDataDir4(), "run", RESTART_SENTINEL_FILENAME);
|
|
1810
|
-
}
|
|
1811
|
-
async function writeRestartSentinel(payload) {
|
|
1812
|
-
const path = resolveRestartSentinelPath();
|
|
1813
|
-
mkdirSync2(resolve6(path, ".."), { recursive: true });
|
|
1814
|
-
const file = {
|
|
1815
|
-
version: 1,
|
|
1816
|
-
payload
|
|
1817
|
-
};
|
|
1818
|
-
writeFileSync2(path, `${JSON.stringify(file, null, 2)}
|
|
1819
|
-
`, "utf-8");
|
|
1820
|
-
return path;
|
|
1821
|
-
}
|
|
1822
|
-
async function consumeRestartSentinel() {
|
|
1823
|
-
const path = resolveRestartSentinelPath();
|
|
1824
|
-
if (!existsSync5(path)) {
|
|
1825
|
-
return null;
|
|
1826
|
-
}
|
|
1827
|
-
try {
|
|
1828
|
-
const raw = readFileSync3(path, "utf-8");
|
|
1829
|
-
const parsed = JSON.parse(raw);
|
|
1830
|
-
if (!parsed || parsed.version !== 1 || !parsed.payload) {
|
|
1831
|
-
rmSync2(path, { force: true });
|
|
1832
|
-
return null;
|
|
1833
|
-
}
|
|
1834
|
-
rmSync2(path, { force: true });
|
|
1835
|
-
return parsed;
|
|
1836
|
-
} catch {
|
|
1837
|
-
rmSync2(path, { force: true });
|
|
1838
|
-
return null;
|
|
1839
|
-
}
|
|
1840
|
-
}
|
|
1841
|
-
function summarizeRestartSentinel(payload) {
|
|
1842
|
-
const reason = normalizeLine(payload.stats?.reason ?? "", RESTART_REASON_MAX_CHARS);
|
|
1843
|
-
if (payload.kind === "update.run") {
|
|
1844
|
-
return payload.status === "ok" ? "\u2705 NextClaw update completed and service restarted." : "\u26A0\uFE0F NextClaw update finished with issues.";
|
|
1845
|
-
}
|
|
1846
|
-
if (payload.kind === "config.apply" || payload.kind === "config.patch") {
|
|
1847
|
-
return payload.status === "ok" ? "\u2705 Config applied and service restarted." : "\u26A0\uFE0F Config change restart finished with issues.";
|
|
1848
|
-
}
|
|
1849
|
-
if (reason) {
|
|
1850
|
-
return `Gateway restart complete (${reason}).`;
|
|
1851
|
-
}
|
|
1852
|
-
return "Gateway restart complete.";
|
|
1853
|
-
}
|
|
1854
|
-
function formatRestartSentinelMessage(payload) {
|
|
1855
|
-
const lines = [summarizeRestartSentinel(payload)];
|
|
1856
|
-
const note = normalizeLine(payload.message ?? "", RESTART_NOTE_MAX_CHARS);
|
|
1857
|
-
if (note) {
|
|
1858
|
-
lines.push(note);
|
|
1859
|
-
}
|
|
1860
|
-
const reason = normalizeLine(payload.stats?.reason ?? "", RESTART_REASON_MAX_CHARS);
|
|
1861
|
-
if (reason && !lines.some((line) => line.includes(reason))) {
|
|
1862
|
-
lines.push(`Reason: ${reason}`);
|
|
1863
|
-
}
|
|
1864
|
-
const message = lines.join("\n").trim();
|
|
1865
|
-
return trimTo(message, RESTART_OUTBOUND_MAX_CHARS);
|
|
1866
|
-
}
|
|
1867
|
-
function parseSessionKey(sessionKey) {
|
|
1868
|
-
const value = sessionKey?.trim();
|
|
1869
|
-
if (!value) {
|
|
1870
|
-
return null;
|
|
1871
|
-
}
|
|
1872
|
-
const separator = value.indexOf(":");
|
|
1873
|
-
if (separator <= 0 || separator >= value.length - 1) {
|
|
1874
|
-
return null;
|
|
1875
|
-
}
|
|
1876
|
-
return {
|
|
1877
|
-
channel: value.slice(0, separator),
|
|
1878
|
-
chatId: value.slice(separator + 1)
|
|
1879
|
-
};
|
|
1880
|
-
}
|
|
1881
|
-
function enqueuePendingSystemEvent(sessionManager, sessionKey, message) {
|
|
1882
|
-
const text = message.trim();
|
|
1883
|
-
if (!text) {
|
|
1884
|
-
return;
|
|
1885
|
-
}
|
|
1886
|
-
const session = sessionManager.getOrCreate(sessionKey);
|
|
1887
|
-
const queueRaw = session.metadata[PENDING_SYSTEM_EVENTS_KEY];
|
|
1888
|
-
const queue = Array.isArray(queueRaw) ? queueRaw.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean) : [];
|
|
1889
|
-
if (queue.at(-1) === text) {
|
|
1890
|
-
return;
|
|
1891
|
-
}
|
|
1892
|
-
queue.push(text);
|
|
1893
|
-
if (queue.length > 20) {
|
|
1894
|
-
queue.splice(0, queue.length - 20);
|
|
1895
|
-
}
|
|
1896
|
-
session.metadata[PENDING_SYSTEM_EVENTS_KEY] = queue;
|
|
1897
|
-
session.updatedAt = /* @__PURE__ */ new Date();
|
|
1898
|
-
sessionManager.save(session);
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
// src/cli/gateway/controller.ts
|
|
1902
1900
|
var hashRaw = (raw) => createHash("sha256").update(raw).digest("hex");
|
|
1903
1901
|
var readConfigSnapshot = (getConfigPath4) => {
|
|
1904
1902
|
const path = getConfigPath4();
|
|
@@ -2028,6 +2026,12 @@ var GatewayControllerImpl = class {
|
|
|
2028
2026
|
return this.deps.reloader.reloadConfig(reason);
|
|
2029
2027
|
}
|
|
2030
2028
|
async restart(options) {
|
|
2029
|
+
await this.writeRestartSentinelPayload({
|
|
2030
|
+
kind: "restart",
|
|
2031
|
+
status: "ok",
|
|
2032
|
+
sessionKey: options?.sessionKey,
|
|
2033
|
+
reason: options?.reason ?? "gateway.restart"
|
|
2034
|
+
});
|
|
2031
2035
|
await this.requestRestart(options);
|
|
2032
2036
|
return "Restart scheduled";
|
|
2033
2037
|
}
|
|
@@ -3231,6 +3235,27 @@ var CliRuntime = class {
|
|
|
3231
3235
|
}
|
|
3232
3236
|
console.warn(result.message);
|
|
3233
3237
|
}
|
|
3238
|
+
async writeRestartSentinelFromExecContext(reason) {
|
|
3239
|
+
const sessionKeyRaw = process.env.NEXTCLAW_RUNTIME_SESSION_KEY;
|
|
3240
|
+
const sessionKey = typeof sessionKeyRaw === "string" ? sessionKeyRaw.trim() : "";
|
|
3241
|
+
if (!sessionKey) {
|
|
3242
|
+
return;
|
|
3243
|
+
}
|
|
3244
|
+
try {
|
|
3245
|
+
await writeRestartSentinel({
|
|
3246
|
+
kind: "restart",
|
|
3247
|
+
status: "ok",
|
|
3248
|
+
ts: Date.now(),
|
|
3249
|
+
sessionKey,
|
|
3250
|
+
stats: {
|
|
3251
|
+
reason: reason || "cli.restart",
|
|
3252
|
+
strategy: "exec-tool"
|
|
3253
|
+
}
|
|
3254
|
+
});
|
|
3255
|
+
} catch (error) {
|
|
3256
|
+
console.warn(`Warning: failed to write restart sentinel from exec context: ${String(error)}`);
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3234
3259
|
async onboard() {
|
|
3235
3260
|
console.warn(`Warning: ${APP_NAME4} onboard is deprecated. Use "${APP_NAME4} init" instead.`);
|
|
3236
3261
|
await this.init({ source: "onboard" });
|
|
@@ -3316,6 +3341,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
3316
3341
|
});
|
|
3317
3342
|
}
|
|
3318
3343
|
async restart(opts) {
|
|
3344
|
+
await this.writeRestartSentinelFromExecContext("cli.restart");
|
|
3319
3345
|
const state = readServiceState();
|
|
3320
3346
|
if (state && isProcessRunning(state.pid)) {
|
|
3321
3347
|
console.log(`Restarting ${APP_NAME4}...`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nextclaw",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.12",
|
|
4
4
|
"description": "Lightweight personal AI assistant with CLI, multi-provider routing, and channel integrations.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"chokidar": "^3.6.0",
|
|
40
40
|
"commander": "^12.1.0",
|
|
41
|
-
"@nextclaw/core": "^0.6.
|
|
41
|
+
"@nextclaw/core": "^0.6.10",
|
|
42
42
|
"@nextclaw/server": "^0.4.2",
|
|
43
43
|
"@nextclaw/openclaw-compat": "^0.1.5"
|
|
44
44
|
},
|