nextclaw 0.6.11 → 0.6.13

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.
Files changed (2) hide show
  1. package/dist/cli/index.js +217 -224
  2. package/package.json +2 -2
package/dist/cli/index.js CHANGED
@@ -84,24 +84,120 @@ 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 RESTART_REASON_MAX_CHARS = 240;
93
+ var RESTART_NOTE_MAX_CHARS = 600;
94
+ var RESTART_OUTBOUND_MAX_CHARS = 1200;
95
+ function trimTo(value, maxChars) {
96
+ const text = value.trim();
97
+ if (!text) {
98
+ return "";
99
+ }
100
+ if (text.length <= maxChars) {
101
+ return text;
102
+ }
103
+ return `${text.slice(0, Math.max(0, maxChars - 1)).trimEnd()}\u2026`;
104
+ }
105
+ function normalizeLine(value, maxChars) {
106
+ const trimmed = trimTo(value, maxChars);
107
+ return trimmed ? trimmed : null;
108
+ }
109
+ function resolveRestartSentinelPath() {
110
+ return resolve(getDataDir(), "run", RESTART_SENTINEL_FILENAME);
111
+ }
112
+ async function writeRestartSentinel(payload) {
113
+ const path = resolveRestartSentinelPath();
114
+ mkdirSync(resolve(path, ".."), { recursive: true });
115
+ const file = {
116
+ version: 1,
117
+ payload
118
+ };
119
+ writeFileSync(path, `${JSON.stringify(file, null, 2)}
120
+ `, "utf-8");
121
+ return path;
122
+ }
123
+ async function consumeRestartSentinel() {
124
+ const path = resolveRestartSentinelPath();
125
+ if (!existsSync(path)) {
126
+ return null;
127
+ }
128
+ try {
129
+ const raw = readFileSync(path, "utf-8");
130
+ const parsed = JSON.parse(raw);
131
+ if (!parsed || parsed.version !== 1 || !parsed.payload) {
132
+ rmSync(path, { force: true });
133
+ return null;
134
+ }
135
+ rmSync(path, { force: true });
136
+ return parsed;
137
+ } catch {
138
+ rmSync(path, { force: true });
139
+ return null;
140
+ }
141
+ }
142
+ function summarizeRestartSentinel(payload) {
143
+ const reason = normalizeLine(payload.stats?.reason ?? "", RESTART_REASON_MAX_CHARS);
144
+ if (payload.kind === "update.run") {
145
+ return payload.status === "ok" ? "\u2705 NextClaw update completed and service restarted." : "\u26A0\uFE0F NextClaw update finished with issues.";
146
+ }
147
+ if (payload.kind === "config.apply" || payload.kind === "config.patch") {
148
+ return payload.status === "ok" ? "\u2705 Config applied and service restarted." : "\u26A0\uFE0F Config change restart finished with issues.";
149
+ }
150
+ if (reason) {
151
+ return `Gateway restart complete (${reason}).`;
152
+ }
153
+ return "Gateway restart complete.";
154
+ }
155
+ function formatRestartSentinelMessage(payload) {
156
+ const lines = [summarizeRestartSentinel(payload)];
157
+ const note = normalizeLine(payload.message ?? "", RESTART_NOTE_MAX_CHARS);
158
+ if (note) {
159
+ lines.push(note);
160
+ }
161
+ const reason = normalizeLine(payload.stats?.reason ?? "", RESTART_REASON_MAX_CHARS);
162
+ if (reason && !lines.some((line) => line.includes(reason))) {
163
+ lines.push(`Reason: ${reason}`);
164
+ }
165
+ const message = lines.join("\n").trim();
166
+ return trimTo(message, RESTART_OUTBOUND_MAX_CHARS);
167
+ }
168
+ function parseSessionKey(sessionKey) {
169
+ const value = sessionKey?.trim();
170
+ if (!value) {
171
+ return null;
172
+ }
173
+ const separator = value.indexOf(":");
174
+ if (separator <= 0 || separator >= value.length - 1) {
175
+ return null;
176
+ }
177
+ return {
178
+ channel: value.slice(0, separator),
179
+ chatId: value.slice(separator + 1)
180
+ };
181
+ }
182
+
87
183
  // src/cli/skills/clawhub.ts
88
184
  import { spawnSync } from "child_process";
89
- import { existsSync } from "fs";
90
- import { isAbsolute, join, resolve } from "path";
185
+ import { existsSync as existsSync2 } from "fs";
186
+ import { isAbsolute, join, resolve as resolve2 } from "path";
91
187
  async function installClawHubSkill(options) {
92
188
  const slug = options.slug.trim();
93
189
  if (!slug) {
94
190
  throw new Error("Skill slug is required.");
95
191
  }
96
- const workdir = resolve(options.workdir);
97
- if (!existsSync(workdir)) {
192
+ const workdir = resolve2(options.workdir);
193
+ if (!existsSync2(workdir)) {
98
194
  throw new Error(`Workdir does not exist: ${workdir}`);
99
195
  }
100
196
  const dirName = options.dir?.trim() || "skills";
101
- const destinationDir = isAbsolute(dirName) ? resolve(dirName, slug) : resolve(workdir, dirName, slug);
197
+ const destinationDir = isAbsolute(dirName) ? resolve2(dirName, slug) : resolve2(workdir, dirName, slug);
102
198
  const skillFile = join(destinationDir, "SKILL.md");
103
- if (!options.force && existsSync(destinationDir)) {
104
- if (existsSync(skillFile)) {
199
+ if (!options.force && existsSync2(destinationDir)) {
200
+ if (existsSync2(skillFile)) {
105
201
  return {
106
202
  slug,
107
203
  version: options.version,
@@ -156,15 +252,15 @@ function buildClawHubArgs(slug, options) {
156
252
 
157
253
  // src/cli/update/runner.ts
158
254
  import { spawnSync as spawnSync2 } from "child_process";
159
- import { resolve as resolve3 } from "path";
255
+ import { resolve as resolve4 } from "path";
160
256
 
161
257
  // src/cli/utils.ts
162
- import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync, rmSync } from "fs";
163
- import { join as join2, resolve as resolve2 } from "path";
258
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "fs";
259
+ import { join as join2, resolve as resolve3 } from "path";
164
260
  import { spawn } from "child_process";
165
261
  import { isIP } from "net";
166
262
  import { fileURLToPath } from "url";
167
- import { getDataDir, getPackageVersion as getCorePackageVersion } from "@nextclaw/core";
263
+ import { getDataDir as getDataDir2, getPackageVersion as getCorePackageVersion } from "@nextclaw/core";
168
264
  function resolveUiConfig(config2, overrides) {
169
265
  const base = config2.ui ?? { enabled: false, host: "127.0.0.1", port: 18791, open: false };
170
266
  return { ...base, ...overrides ?? {} };
@@ -214,11 +310,11 @@ function buildServeArgs(options) {
214
310
  }
215
311
  function readServiceState() {
216
312
  const path = resolveServiceStatePath();
217
- if (!existsSync2(path)) {
313
+ if (!existsSync3(path)) {
218
314
  return null;
219
315
  }
220
316
  try {
221
- const raw = readFileSync(path, "utf-8");
317
+ const raw = readFileSync2(path, "utf-8");
222
318
  return JSON.parse(raw);
223
319
  } catch {
224
320
  return null;
@@ -226,20 +322,20 @@ function readServiceState() {
226
322
  }
227
323
  function writeServiceState(state) {
228
324
  const path = resolveServiceStatePath();
229
- mkdirSync(resolve2(path, ".."), { recursive: true });
230
- writeFileSync(path, JSON.stringify(state, null, 2));
325
+ mkdirSync2(resolve3(path, ".."), { recursive: true });
326
+ writeFileSync2(path, JSON.stringify(state, null, 2));
231
327
  }
232
328
  function clearServiceState() {
233
329
  const path = resolveServiceStatePath();
234
- if (existsSync2(path)) {
235
- rmSync(path, { force: true });
330
+ if (existsSync3(path)) {
331
+ rmSync2(path, { force: true });
236
332
  }
237
333
  }
238
334
  function resolveServiceStatePath() {
239
- return resolve2(getDataDir(), "run", "service.json");
335
+ return resolve3(getDataDir2(), "run", "service.json");
240
336
  }
241
337
  function resolveServiceLogPath() {
242
- return resolve2(getDataDir(), "logs", "service.log");
338
+ return resolve3(getDataDir2(), "logs", "service.log");
243
339
  }
244
340
  function isProcessRunning(pid) {
245
341
  try {
@@ -265,8 +361,8 @@ function resolveUiStaticDir() {
265
361
  if (envDir) {
266
362
  candidates.push(envDir);
267
363
  }
268
- const cliDir = resolve2(fileURLToPath(new URL(".", import.meta.url)));
269
- const pkgRoot = resolve2(cliDir, "..", "..");
364
+ const cliDir = resolve3(fileURLToPath(new URL(".", import.meta.url)));
365
+ const pkgRoot = resolve3(cliDir, "..", "..");
270
366
  candidates.push(join2(pkgRoot, "ui-dist"));
271
367
  candidates.push(join2(pkgRoot, "ui"));
272
368
  candidates.push(join2(pkgRoot, "..", "ui-dist"));
@@ -278,7 +374,7 @@ function resolveUiStaticDir() {
278
374
  candidates.push(join2(pkgRoot, "..", "..", "packages", "nextclaw-ui", "dist"));
279
375
  candidates.push(join2(pkgRoot, "..", "..", "nextclaw-ui", "dist"));
280
376
  for (const dir of candidates) {
281
- if (existsSync2(join2(dir, "index.html"))) {
377
+ if (existsSync3(join2(dir, "index.html"))) {
282
378
  return dir;
283
379
  }
284
380
  }
@@ -305,19 +401,19 @@ function which(binary) {
305
401
  const paths = (process.env.PATH ?? "").split(":");
306
402
  for (const dir of paths) {
307
403
  const full = join2(dir, binary);
308
- if (existsSync2(full)) {
404
+ if (existsSync3(full)) {
309
405
  return true;
310
406
  }
311
407
  }
312
408
  return false;
313
409
  }
314
410
  function resolveVersionFromPackageTree(startDir, expectedName) {
315
- let current = resolve2(startDir);
411
+ let current = resolve3(startDir);
316
412
  while (current.length > 0) {
317
413
  const pkgPath = join2(current, "package.json");
318
- if (existsSync2(pkgPath)) {
414
+ if (existsSync3(pkgPath)) {
319
415
  try {
320
- const raw = readFileSync(pkgPath, "utf-8");
416
+ const raw = readFileSync2(pkgPath, "utf-8");
321
417
  const parsed = JSON.parse(raw);
322
418
  if (typeof parsed.version === "string") {
323
419
  if (!expectedName || parsed.name === expectedName) {
@@ -327,7 +423,7 @@ function resolveVersionFromPackageTree(startDir, expectedName) {
327
423
  } catch {
328
424
  }
329
425
  }
330
- const parent = resolve2(current, "..");
426
+ const parent = resolve3(current, "..");
331
427
  if (parent === current) {
332
428
  break;
333
429
  }
@@ -336,7 +432,7 @@ function resolveVersionFromPackageTree(startDir, expectedName) {
336
432
  return null;
337
433
  }
338
434
  function getPackageVersion() {
339
- const cliDir = resolve2(fileURLToPath(new URL(".", import.meta.url)));
435
+ const cliDir = resolve3(fileURLToPath(new URL(".", import.meta.url)));
340
436
  return resolveVersionFromPackageTree(cliDir, "nextclaw") ?? resolveVersionFromPackageTree(cliDir) ?? getCorePackageVersion();
341
437
  }
342
438
  function printAgentResponse(response) {
@@ -375,7 +471,7 @@ function runSelfUpdate(options = {}) {
375
471
  return { ok: result.status === 0, code: result.status };
376
472
  };
377
473
  if (updateCommand) {
378
- const cwd = options.cwd ? resolve3(options.cwd) : process.cwd();
474
+ const cwd = options.cwd ? resolve4(options.cwd) : process.cwd();
379
475
  const ok = runStep("sh", ["-c", updateCommand], cwd);
380
476
  if (!ok.ok) {
381
477
  return { ok: false, error: "update command failed", strategy: "command", steps };
@@ -413,8 +509,8 @@ import {
413
509
  expandHome
414
510
  } from "@nextclaw/core";
415
511
  import { createInterface } from "readline";
416
- import { existsSync as existsSync3 } from "fs";
417
- import { resolve as resolve4 } from "path";
512
+ import { existsSync as existsSync4 } from "fs";
513
+ import { resolve as resolve5 } from "path";
418
514
  function loadPluginRegistry(config2, workspaceDir) {
419
515
  return loadOpenClawPlugins({
420
516
  config: config2,
@@ -691,7 +787,7 @@ var PluginCommands = class {
691
787
  process.exit(1);
692
788
  }
693
789
  const install = config2.plugins.installs?.[pluginId];
694
- const isLinked = install?.source === "path" && (!install.installPath || !install.sourcePath || resolve4(install.installPath) === resolve4(install.sourcePath));
790
+ const isLinked = install?.source === "path" && (!install.installPath || !install.sourcePath || resolve5(install.installPath) === resolve5(install.sourcePath));
695
791
  const preview = [];
696
792
  if (hasEntry) {
697
793
  preview.push("config entry");
@@ -770,9 +866,9 @@ var PluginCommands = class {
770
866
  process.exit(1);
771
867
  }
772
868
  const normalized = fileSpec && fileSpec.ok ? fileSpec.path : pathOrSpec;
773
- const resolved = resolve4(expandHome(normalized));
869
+ const resolved = resolve5(expandHome(normalized));
774
870
  const config2 = loadConfig();
775
- if (existsSync3(resolved)) {
871
+ if (existsSync4(resolved)) {
776
872
  if (opts.link) {
777
873
  const probe = await installPluginFromPath({ path: resolved, dryRun: true });
778
874
  if (!probe.ok) {
@@ -1331,11 +1427,11 @@ var ChannelCommands = class {
1331
1427
  };
1332
1428
 
1333
1429
  // src/cli/commands/cron.ts
1334
- import { CronService, getDataDir as getDataDir2 } from "@nextclaw/core";
1430
+ import { CronService, getDataDir as getDataDir3 } from "@nextclaw/core";
1335
1431
  import { join as join3 } from "path";
1336
1432
  var CronCommands = class {
1337
1433
  cronList(opts) {
1338
- const storePath = join3(getDataDir2(), "cron", "jobs.json");
1434
+ const storePath = join3(getDataDir3(), "cron", "jobs.json");
1339
1435
  const service = new CronService(storePath);
1340
1436
  const jobs = service.listJobs(Boolean(opts.all));
1341
1437
  if (!jobs.length) {
@@ -1355,7 +1451,7 @@ var CronCommands = class {
1355
1451
  }
1356
1452
  }
1357
1453
  cronAdd(opts) {
1358
- const storePath = join3(getDataDir2(), "cron", "jobs.json");
1454
+ const storePath = join3(getDataDir3(), "cron", "jobs.json");
1359
1455
  const service = new CronService(storePath);
1360
1456
  let schedule = null;
1361
1457
  if (opts.every) {
@@ -1380,7 +1476,7 @@ var CronCommands = class {
1380
1476
  console.log(`\u2713 Added job '${job.name}' (${job.id})`);
1381
1477
  }
1382
1478
  cronRemove(jobId) {
1383
- const storePath = join3(getDataDir2(), "cron", "jobs.json");
1479
+ const storePath = join3(getDataDir3(), "cron", "jobs.json");
1384
1480
  const service = new CronService(storePath);
1385
1481
  if (service.removeJob(jobId)) {
1386
1482
  console.log(`\u2713 Removed job ${jobId}`);
@@ -1389,7 +1485,7 @@ var CronCommands = class {
1389
1485
  }
1390
1486
  }
1391
1487
  cronEnable(jobId, opts) {
1392
- const storePath = join3(getDataDir2(), "cron", "jobs.json");
1488
+ const storePath = join3(getDataDir3(), "cron", "jobs.json");
1393
1489
  const service = new CronService(storePath);
1394
1490
  const job = service.enableJob(jobId, !opts.disable);
1395
1491
  if (job) {
@@ -1399,7 +1495,7 @@ var CronCommands = class {
1399
1495
  }
1400
1496
  }
1401
1497
  async cronRun(jobId, opts) {
1402
- const storePath = join3(getDataDir2(), "cron", "jobs.json");
1498
+ const storePath = join3(getDataDir3(), "cron", "jobs.json");
1403
1499
  const service = new CronService(storePath);
1404
1500
  const ok = await service.runJob(jobId, Boolean(opts.force));
1405
1501
  console.log(ok ? "\u2713 Job executed" : `Failed to run job ${jobId}`);
@@ -1408,12 +1504,12 @@ var CronCommands = class {
1408
1504
 
1409
1505
  // src/cli/commands/diagnostics.ts
1410
1506
  import { createServer as createNetServer } from "net";
1411
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
1412
- import { resolve as resolve5 } from "path";
1507
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
1508
+ import { resolve as resolve6 } from "path";
1413
1509
  import {
1414
1510
  APP_NAME,
1415
1511
  getConfigPath,
1416
- getDataDir as getDataDir3,
1512
+ getDataDir as getDataDir4,
1417
1513
  getWorkspacePath as getWorkspacePath3,
1418
1514
  loadConfig as loadConfig4,
1419
1515
  PROVIDERS as PROVIDERS3
@@ -1579,7 +1675,7 @@ var DiagnosticsCommands = class {
1579
1675
  const configPath = getConfigPath();
1580
1676
  const config2 = loadConfig4();
1581
1677
  const workspacePath = getWorkspacePath3(config2.agents.defaults.workspace);
1582
- const serviceStatePath = resolve5(getDataDir3(), "run", "service.json");
1678
+ const serviceStatePath = resolve6(getDataDir4(), "run", "service.json");
1583
1679
  const fixActions = [];
1584
1680
  let serviceState = readServiceState();
1585
1681
  if (params.fix && serviceState && !isProcessRunning(serviceState.pid)) {
@@ -1618,11 +1714,11 @@ var DiagnosticsCommands = class {
1618
1714
  });
1619
1715
  const issues = [];
1620
1716
  const recommendations = [];
1621
- if (!existsSync4(configPath)) {
1717
+ if (!existsSync5(configPath)) {
1622
1718
  issues.push("Config file is missing.");
1623
1719
  recommendations.push(`Run ${APP_NAME} init to create config files.`);
1624
1720
  }
1625
- if (!existsSync4(workspacePath)) {
1721
+ if (!existsSync5(workspacePath)) {
1626
1722
  issues.push("Workspace directory does not exist.");
1627
1723
  recommendations.push(`Run ${APP_NAME} init to create workspace templates.`);
1628
1724
  }
@@ -1650,13 +1746,13 @@ var DiagnosticsCommands = class {
1650
1746
  return {
1651
1747
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1652
1748
  configPath,
1653
- configExists: existsSync4(configPath),
1749
+ configExists: existsSync5(configPath),
1654
1750
  workspacePath,
1655
- workspaceExists: existsSync4(workspacePath),
1751
+ workspaceExists: existsSync5(workspacePath),
1656
1752
  model: config2.agents.defaults.model,
1657
1753
  providers,
1658
1754
  serviceStatePath,
1659
- serviceStateExists: existsSync4(serviceStatePath),
1755
+ serviceStateExists: existsSync5(serviceStatePath),
1660
1756
  fixActions,
1661
1757
  process: {
1662
1758
  managedByState,
@@ -1706,11 +1802,11 @@ var DiagnosticsCommands = class {
1706
1802
  }
1707
1803
  }
1708
1804
  readLogTail(path, maxLines = 25) {
1709
- if (!existsSync4(path)) {
1805
+ if (!existsSync5(path)) {
1710
1806
  return [];
1711
1807
  }
1712
1808
  try {
1713
- const lines = readFileSync2(path, "utf-8").split(/\r?\n/).filter(Boolean);
1809
+ const lines = readFileSync3(path, "utf-8").split(/\r?\n/).filter(Boolean);
1714
1810
  if (lines.length <= maxLines) {
1715
1811
  return lines;
1716
1812
  }
@@ -1781,124 +1877,6 @@ import {
1781
1877
  ConfigSchema,
1782
1878
  redactConfigObject
1783
1879
  } 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
1880
  var hashRaw = (raw) => createHash("sha256").update(raw).digest("hex");
1903
1881
  var readConfigSnapshot = (getConfigPath4) => {
1904
1882
  const path = getConfigPath4();
@@ -2028,6 +2006,12 @@ var GatewayControllerImpl = class {
2028
2006
  return this.deps.reloader.reloadConfig(reason);
2029
2007
  }
2030
2008
  async restart(options) {
2009
+ await this.writeRestartSentinelPayload({
2010
+ kind: "restart",
2011
+ status: "ok",
2012
+ sessionKey: options?.sessionKey,
2013
+ reason: options?.reason ?? "gateway.restart"
2014
+ });
2031
2015
  await this.requestRestart(options);
2032
2016
  return "Restart scheduled";
2033
2017
  }
@@ -2499,7 +2483,7 @@ var ServiceCommands = class {
2499
2483
  }
2500
2484
  }
2501
2485
  await reloader.getChannels().startAll();
2502
- await this.wakeFromRestartSentinel({ channels: reloader.getChannels(), sessionManager });
2486
+ await this.wakeFromRestartSentinel({ bus, sessionManager });
2503
2487
  await agent.run();
2504
2488
  } finally {
2505
2489
  await stopPluginChannelGateways(pluginGatewayHandles);
@@ -2513,9 +2497,6 @@ var ServiceCommands = class {
2513
2497
  const trimmed = value.trim();
2514
2498
  return trimmed || void 0;
2515
2499
  }
2516
- async sleep(ms) {
2517
- await new Promise((resolve10) => setTimeout(resolve10, ms));
2518
- }
2519
2500
  resolveMostRecentRoutableSessionKey(sessionManager) {
2520
2501
  const sessions = sessionManager.listSessions();
2521
2502
  let best = null;
@@ -2542,42 +2523,27 @@ var ServiceCommands = class {
2542
2523
  }
2543
2524
  return best?.key;
2544
2525
  }
2545
- async sendRestartSentinelNotice(params) {
2546
- const outboundBase = {
2547
- channel: params.channel,
2548
- chatId: params.chatId,
2549
- content: params.content,
2550
- media: [],
2551
- metadata: params.metadata
2552
- };
2553
- const variants = params.replyTo ? [
2554
- { ...outboundBase, replyTo: params.replyTo },
2555
- { ...outboundBase }
2556
- ] : [{ ...outboundBase }];
2557
- for (let variantIndex = 0; variantIndex < variants.length; variantIndex += 1) {
2558
- const outbound = variants[variantIndex];
2559
- const isLastVariant = variantIndex === variants.length - 1;
2560
- for (let attempt = 1; attempt <= 3; attempt += 1) {
2561
- try {
2562
- const delivered = await params.channels.deliver(outbound);
2563
- if (delivered) {
2564
- return true;
2565
- }
2566
- return false;
2567
- } catch (error) {
2568
- if (attempt < 3) {
2569
- await this.sleep(attempt * 500);
2570
- continue;
2571
- }
2572
- if (isLastVariant) {
2573
- console.warn(
2574
- `Warning: restart sentinel notify failed for ${params.channel}:${params.chatId} (attempt ${attempt}): ${String(error)}`
2575
- );
2576
- }
2577
- }
2578
- }
2526
+ buildRestartWakePrompt(params) {
2527
+ const lines = [
2528
+ "System event: the gateway has restarted successfully.",
2529
+ "Please send one short confirmation to the user that you are back online.",
2530
+ "Do not call any tools.",
2531
+ "Use the same language as the user's recent conversation.",
2532
+ `Reference summary: ${params.summary}`
2533
+ ];
2534
+ const reason = this.normalizeOptionalString(params.reason);
2535
+ if (reason) {
2536
+ lines.push(`Restart reason: ${reason}`);
2579
2537
  }
2580
- return false;
2538
+ const note = this.normalizeOptionalString(params.note);
2539
+ if (note) {
2540
+ lines.push(`Extra note: ${note}`);
2541
+ }
2542
+ const replyTo = this.normalizeOptionalString(params.replyTo);
2543
+ if (replyTo) {
2544
+ lines.push(`Reply target message id: ${replyTo}. If suitable, include [[reply_to:${replyTo}]].`);
2545
+ }
2546
+ return lines.join("\n");
2581
2547
  }
2582
2548
  async wakeFromRestartSentinel(params) {
2583
2549
  const sentinel = await consumeRestartSentinel();
@@ -2586,7 +2552,7 @@ var ServiceCommands = class {
2586
2552
  }
2587
2553
  await new Promise((resolve10) => setTimeout(resolve10, 750));
2588
2554
  const payload = sentinel.payload;
2589
- const message = formatRestartSentinelMessage(payload);
2555
+ const summary = formatRestartSentinelMessage(payload);
2590
2556
  const sentinelSessionKey = this.normalizeOptionalString(payload.sessionKey);
2591
2557
  const fallbackSessionKey = sentinelSessionKey ? void 0 : this.resolveMostRecentRoutableSessionKey(params.sessionManager);
2592
2558
  if (!sentinelSessionKey && fallbackSessionKey) {
@@ -2599,26 +2565,31 @@ var ServiceCommands = class {
2599
2565
  const chatId = this.normalizeOptionalString(context?.chatId) ?? parsedSession?.chatId ?? this.normalizeOptionalString((params.sessionManager.getIfExists(sessionKey)?.metadata ?? {}).last_to);
2600
2566
  const replyTo = this.normalizeOptionalString(context?.replyTo);
2601
2567
  const accountId = this.normalizeOptionalString(context?.accountId);
2602
- const metadataRaw = context?.metadata;
2603
- const metadata = metadataRaw && typeof metadataRaw === "object" && !Array.isArray(metadataRaw) ? { ...metadataRaw } : {};
2604
- if (accountId && !this.normalizeOptionalString(metadata.accountId)) {
2605
- metadata.accountId = accountId;
2606
- }
2607
2568
  if (!channel || !chatId) {
2608
- enqueuePendingSystemEvent(params.sessionManager, sessionKey, message);
2569
+ console.warn(`Warning: restart sentinel cannot resolve route for session ${sessionKey}.`);
2609
2570
  return;
2610
2571
  }
2611
- const delivered = await this.sendRestartSentinelNotice({
2612
- channels: params.channels,
2613
- channel,
2614
- chatId,
2615
- content: message,
2616
- ...replyTo ? { replyTo } : {},
2572
+ const prompt2 = this.buildRestartWakePrompt({
2573
+ summary,
2574
+ reason: this.normalizeOptionalString(payload.stats?.reason),
2575
+ note: this.normalizeOptionalString(payload.message),
2576
+ ...replyTo ? { replyTo } : {}
2577
+ });
2578
+ const metadata = {
2579
+ source: "restart-sentinel",
2580
+ restart_summary: summary,
2581
+ ...replyTo ? { reply_to: replyTo } : {},
2582
+ ...accountId ? { account_id: accountId, accountId } : {}
2583
+ };
2584
+ await params.bus.publishInbound({
2585
+ channel: "system",
2586
+ senderId: "restart-sentinel",
2587
+ chatId: `${channel}:${chatId}`,
2588
+ content: prompt2,
2589
+ timestamp: /* @__PURE__ */ new Date(),
2590
+ attachments: [],
2617
2591
  metadata
2618
2592
  });
2619
- if (!delivered) {
2620
- enqueuePendingSystemEvent(params.sessionManager, sessionKey, message);
2621
- }
2622
2593
  }
2623
2594
  async runForeground(options) {
2624
2595
  const config2 = loadConfig5();
@@ -3231,6 +3202,27 @@ var CliRuntime = class {
3231
3202
  }
3232
3203
  console.warn(result.message);
3233
3204
  }
3205
+ async writeRestartSentinelFromExecContext(reason) {
3206
+ const sessionKeyRaw = process.env.NEXTCLAW_RUNTIME_SESSION_KEY;
3207
+ const sessionKey = typeof sessionKeyRaw === "string" ? sessionKeyRaw.trim() : "";
3208
+ if (!sessionKey) {
3209
+ return;
3210
+ }
3211
+ try {
3212
+ await writeRestartSentinel({
3213
+ kind: "restart",
3214
+ status: "ok",
3215
+ ts: Date.now(),
3216
+ sessionKey,
3217
+ stats: {
3218
+ reason: reason || "cli.restart",
3219
+ strategy: "exec-tool"
3220
+ }
3221
+ });
3222
+ } catch (error) {
3223
+ console.warn(`Warning: failed to write restart sentinel from exec context: ${String(error)}`);
3224
+ }
3225
+ }
3234
3226
  async onboard() {
3235
3227
  console.warn(`Warning: ${APP_NAME4} onboard is deprecated. Use "${APP_NAME4} init" instead.`);
3236
3228
  await this.init({ source: "onboard" });
@@ -3316,6 +3308,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
3316
3308
  });
3317
3309
  }
3318
3310
  async restart(opts) {
3311
+ await this.writeRestartSentinelFromExecContext("cli.restart");
3319
3312
  const state = readServiceState();
3320
3313
  if (state && isProcessRunning(state.pid)) {
3321
3314
  console.log(`Restarting ${APP_NAME4}...`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextclaw",
3
- "version": "0.6.11",
3
+ "version": "0.6.13",
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.9",
41
+ "@nextclaw/core": "^0.6.11",
42
42
  "@nextclaw/server": "^0.4.2",
43
43
  "@nextclaw/openclaw-compat": "^0.1.5"
44
44
  },