nextclaw 0.6.7 → 0.6.9

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 CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  loadConfig as loadConfig6,
10
10
  saveConfig as saveConfig5,
11
11
  getConfigPath as getConfigPath3,
12
- getDataDir as getDataDir6,
12
+ getDataDir as getDataDir7,
13
13
  ConfigSchema as ConfigSchema2,
14
14
  getWorkspacePath as getWorkspacePath5,
15
15
  expandHome as expandHome2,
@@ -21,8 +21,8 @@ import {
21
21
  DEFAULT_WORKSPACE_PATH
22
22
  } from "@nextclaw/core";
23
23
  import { resolvePluginChannelMessageToolHints as resolvePluginChannelMessageToolHints2 } from "@nextclaw/openclaw-compat";
24
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
25
- import { join as join6, resolve as resolve8 } from "path";
24
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
25
+ import { join as join6, resolve as resolve9 } from "path";
26
26
  import { createInterface as createInterface2 } from "readline";
27
27
  import { fileURLToPath as fileURLToPath3 } from "url";
28
28
  import { spawn as spawn3 } from "child_process";
@@ -255,7 +255,7 @@ async function waitForExit(pid, timeoutMs) {
255
255
  if (!isProcessRunning(pid)) {
256
256
  return true;
257
257
  }
258
- await new Promise((resolve9) => setTimeout(resolve9, 200));
258
+ await new Promise((resolve10) => setTimeout(resolve10, 200));
259
259
  }
260
260
  return !isProcessRunning(pid);
261
261
  }
@@ -345,8 +345,8 @@ function printAgentResponse(response) {
345
345
  async function prompt(rl, question) {
346
346
  rl.setPrompt(question);
347
347
  rl.prompt();
348
- return new Promise((resolve9) => {
349
- rl.once("line", (line) => resolve9(line));
348
+ return new Promise((resolve10) => {
349
+ rl.once("line", (line) => resolve10(line));
350
350
  });
351
351
  }
352
352
 
@@ -894,8 +894,8 @@ var PluginCommands = class {
894
894
  input: process.stdin,
895
895
  output: process.stdout
896
896
  });
897
- const answer = await new Promise((resolve9) => {
898
- rl.question(`${question} [y/N] `, (line) => resolve9(line));
897
+ const answer = await new Promise((resolve10) => {
898
+ rl.question(`${question} [y/N] `, (line) => resolve10(line));
899
899
  });
900
900
  rl.close();
901
901
  const normalized = answer.trim().toLowerCase();
@@ -1720,17 +1720,17 @@ var DiagnosticsCommands = class {
1720
1720
  }
1721
1721
  }
1722
1722
  async checkPortAvailability(params) {
1723
- return await new Promise((resolve9) => {
1723
+ return await new Promise((resolve10) => {
1724
1724
  const server = createNetServer();
1725
1725
  server.once("error", (error) => {
1726
- resolve9({
1726
+ resolve10({
1727
1727
  available: false,
1728
1728
  detail: `bind failed on ${params.host}:${params.port} (${String(error)})`
1729
1729
  });
1730
1730
  });
1731
1731
  server.listen(params.port, params.host, () => {
1732
1732
  server.close(() => {
1733
- resolve9({
1733
+ resolve10({
1734
1734
  available: true,
1735
1735
  detail: `bind ok on ${params.host}:${params.port}`
1736
1736
  });
@@ -1748,7 +1748,7 @@ import {
1748
1748
  CronService as CronService2,
1749
1749
  getApiBase,
1750
1750
  getConfigPath as getConfigPath2,
1751
- getDataDir as getDataDir4,
1751
+ getDataDir as getDataDir5,
1752
1752
  getProvider,
1753
1753
  getProviderName,
1754
1754
  getWorkspacePath as getWorkspacePath4,
@@ -1768,26 +1768,126 @@ import {
1768
1768
  stopPluginChannelGateways
1769
1769
  } from "@nextclaw/openclaw-compat";
1770
1770
  import { startUiServer } from "@nextclaw/server";
1771
- import { closeSync, mkdirSync as mkdirSync2, openSync } from "fs";
1772
- import { join as join4, resolve as resolve6 } from "path";
1771
+ import { closeSync, mkdirSync as mkdirSync3, openSync } from "fs";
1772
+ import { join as join4, resolve as resolve7 } from "path";
1773
1773
  import { spawn as spawn2 } from "child_process";
1774
1774
  import chokidar from "chokidar";
1775
1775
 
1776
1776
  // src/cli/gateway/controller.ts
1777
1777
  import { createHash } from "crypto";
1778
- import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
1778
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
1779
1779
  import {
1780
1780
  buildConfigSchema,
1781
1781
  ConfigSchema,
1782
1782
  redactConfigObject
1783
1783
  } 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
+ function resolveRestartSentinelPath() {
1792
+ return resolve6(getDataDir4(), "run", RESTART_SENTINEL_FILENAME);
1793
+ }
1794
+ async function writeRestartSentinel(payload) {
1795
+ const path = resolveRestartSentinelPath();
1796
+ mkdirSync2(resolve6(path, ".."), { recursive: true });
1797
+ const file = {
1798
+ version: 1,
1799
+ payload
1800
+ };
1801
+ writeFileSync2(path, `${JSON.stringify(file, null, 2)}
1802
+ `, "utf-8");
1803
+ return path;
1804
+ }
1805
+ async function consumeRestartSentinel() {
1806
+ const path = resolveRestartSentinelPath();
1807
+ if (!existsSync5(path)) {
1808
+ return null;
1809
+ }
1810
+ try {
1811
+ const raw = readFileSync3(path, "utf-8");
1812
+ const parsed = JSON.parse(raw);
1813
+ if (!parsed || parsed.version !== 1 || !parsed.payload) {
1814
+ rmSync2(path, { force: true });
1815
+ return null;
1816
+ }
1817
+ rmSync2(path, { force: true });
1818
+ return parsed;
1819
+ } catch {
1820
+ rmSync2(path, { force: true });
1821
+ return null;
1822
+ }
1823
+ }
1824
+ function summarizeRestartSentinel(payload) {
1825
+ const reason = payload.stats?.reason?.trim();
1826
+ if (payload.kind === "update.run") {
1827
+ return payload.status === "ok" ? "\u2705 NextClaw update completed and service restarted." : "\u26A0\uFE0F NextClaw update finished with issues.";
1828
+ }
1829
+ if (payload.kind === "config.apply" || payload.kind === "config.patch") {
1830
+ return payload.status === "ok" ? "\u2705 Config applied and service restarted." : "\u26A0\uFE0F Config change restart finished with issues.";
1831
+ }
1832
+ if (reason) {
1833
+ return `Gateway restart complete (${reason}).`;
1834
+ }
1835
+ return "Gateway restart complete.";
1836
+ }
1837
+ function formatRestartSentinelMessage(payload) {
1838
+ const lines = [summarizeRestartSentinel(payload)];
1839
+ const note = payload.message?.trim();
1840
+ if (note) {
1841
+ lines.push(note);
1842
+ }
1843
+ const reason = payload.stats?.reason?.trim();
1844
+ if (reason && !lines.some((line) => line.includes(reason))) {
1845
+ lines.push(`Reason: ${reason}`);
1846
+ }
1847
+ return lines.join("\n");
1848
+ }
1849
+ function parseSessionKey(sessionKey) {
1850
+ const value = sessionKey?.trim();
1851
+ if (!value) {
1852
+ return null;
1853
+ }
1854
+ const separator = value.indexOf(":");
1855
+ if (separator <= 0 || separator >= value.length - 1) {
1856
+ return null;
1857
+ }
1858
+ return {
1859
+ channel: value.slice(0, separator),
1860
+ chatId: value.slice(separator + 1)
1861
+ };
1862
+ }
1863
+ function enqueuePendingSystemEvent(sessionManager, sessionKey, message) {
1864
+ const text = message.trim();
1865
+ if (!text) {
1866
+ return;
1867
+ }
1868
+ const session = sessionManager.getOrCreate(sessionKey);
1869
+ const queueRaw = session.metadata[PENDING_SYSTEM_EVENTS_KEY];
1870
+ const queue = Array.isArray(queueRaw) ? queueRaw.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean) : [];
1871
+ if (queue.at(-1) === text) {
1872
+ return;
1873
+ }
1874
+ queue.push(text);
1875
+ if (queue.length > 20) {
1876
+ queue.splice(0, queue.length - 20);
1877
+ }
1878
+ session.metadata[PENDING_SYSTEM_EVENTS_KEY] = queue;
1879
+ session.updatedAt = /* @__PURE__ */ new Date();
1880
+ sessionManager.save(session);
1881
+ }
1882
+
1883
+ // src/cli/gateway/controller.ts
1784
1884
  var hashRaw = (raw) => createHash("sha256").update(raw).digest("hex");
1785
1885
  var readConfigSnapshot = (getConfigPath4) => {
1786
1886
  const path = getConfigPath4();
1787
1887
  let raw = "";
1788
1888
  let parsed = {};
1789
- if (existsSync5(path)) {
1790
- raw = readFileSync3(path, "utf-8");
1889
+ if (existsSync6(path)) {
1890
+ raw = readFileSync4(path, "utf-8");
1791
1891
  try {
1792
1892
  parsed = JSON.parse(raw);
1793
1893
  } catch {
@@ -1834,6 +1934,60 @@ var GatewayControllerImpl = class {
1834
1934
  constructor(deps) {
1835
1935
  this.deps = deps;
1836
1936
  }
1937
+ normalizeOptionalString(value) {
1938
+ if (typeof value !== "string") {
1939
+ return void 0;
1940
+ }
1941
+ const trimmed = value.trim();
1942
+ return trimmed || void 0;
1943
+ }
1944
+ resolveDeliveryContext(sessionKey) {
1945
+ const normalizedSessionKey = this.normalizeOptionalString(sessionKey);
1946
+ const keyTarget = parseSessionKey(normalizedSessionKey);
1947
+ const session = normalizedSessionKey ? this.deps.sessionManager?.getIfExists(normalizedSessionKey) : null;
1948
+ const metadata = session?.metadata ?? {};
1949
+ const rawContext = metadata.last_delivery_context;
1950
+ const cachedContext = rawContext && typeof rawContext === "object" && !Array.isArray(rawContext) ? rawContext : null;
1951
+ const cachedMetadataRaw = cachedContext?.metadata;
1952
+ const cachedMetadata = cachedMetadataRaw && typeof cachedMetadataRaw === "object" && !Array.isArray(cachedMetadataRaw) ? { ...cachedMetadataRaw } : {};
1953
+ const channel = this.normalizeOptionalString(cachedContext?.channel) ?? keyTarget?.channel;
1954
+ const chatId = this.normalizeOptionalString(cachedContext?.chatId) ?? this.normalizeOptionalString(metadata.last_to) ?? keyTarget?.chatId;
1955
+ const replyTo = this.normalizeOptionalString(cachedContext?.replyTo) ?? this.normalizeOptionalString(metadata.last_message_id);
1956
+ const accountId = this.normalizeOptionalString(cachedContext?.accountId) ?? this.normalizeOptionalString(metadata.last_account_id);
1957
+ if (!channel || !chatId) {
1958
+ return void 0;
1959
+ }
1960
+ if (accountId && !this.normalizeOptionalString(cachedMetadata.accountId)) {
1961
+ cachedMetadata.accountId = accountId;
1962
+ }
1963
+ return {
1964
+ channel,
1965
+ chatId,
1966
+ ...replyTo ? { replyTo } : {},
1967
+ ...accountId ? { accountId } : {},
1968
+ ...Object.keys(cachedMetadata).length > 0 ? { metadata: cachedMetadata } : {}
1969
+ };
1970
+ }
1971
+ async writeRestartSentinelPayload(params) {
1972
+ const sessionKey = this.normalizeOptionalString(params.sessionKey);
1973
+ const deliveryContext = this.resolveDeliveryContext(sessionKey);
1974
+ try {
1975
+ return await writeRestartSentinel({
1976
+ kind: params.kind,
1977
+ status: params.status,
1978
+ ts: Date.now(),
1979
+ sessionKey,
1980
+ deliveryContext,
1981
+ message: params.note ?? null,
1982
+ stats: {
1983
+ reason: params.reason ?? null,
1984
+ strategy: params.strategy ?? null
1985
+ }
1986
+ });
1987
+ } catch {
1988
+ return null;
1989
+ }
1990
+ }
1837
1991
  async requestRestart(options) {
1838
1992
  if (this.deps.requestRestart) {
1839
1993
  await this.deps.requestRestart(options);
@@ -1899,13 +2053,21 @@ var GatewayControllerImpl = class {
1899
2053
  }
1900
2054
  this.deps.saveConfig(validated);
1901
2055
  const delayMs = params.restartDelayMs ?? 0;
2056
+ const sentinelPath = await this.writeRestartSentinelPayload({
2057
+ kind: "config.apply",
2058
+ status: "ok",
2059
+ sessionKey: params.sessionKey,
2060
+ note: params.note,
2061
+ reason: "config.apply"
2062
+ });
1902
2063
  await this.requestRestart({ delayMs, reason: "config.apply" });
1903
2064
  return {
1904
2065
  ok: true,
1905
2066
  note: params.note ?? null,
1906
2067
  path: this.deps.getConfigPath(),
1907
2068
  config: redactValue(validated),
1908
- restart: { scheduled: true, delayMs }
2069
+ restart: { scheduled: true, delayMs },
2070
+ sentinel: sentinelPath ? { path: sentinelPath } : null
1909
2071
  };
1910
2072
  }
1911
2073
  async patchConfig(params) {
@@ -1934,13 +2096,21 @@ var GatewayControllerImpl = class {
1934
2096
  }
1935
2097
  this.deps.saveConfig(validated);
1936
2098
  const delayMs = params.restartDelayMs ?? 0;
2099
+ const sentinelPath = await this.writeRestartSentinelPayload({
2100
+ kind: "config.patch",
2101
+ status: "ok",
2102
+ sessionKey: params.sessionKey,
2103
+ note: params.note,
2104
+ reason: "config.patch"
2105
+ });
1937
2106
  await this.requestRestart({ delayMs, reason: "config.patch" });
1938
2107
  return {
1939
2108
  ok: true,
1940
2109
  note: params.note ?? null,
1941
2110
  path: this.deps.getConfigPath(),
1942
2111
  config: redactValue(validated),
1943
- restart: { scheduled: true, delayMs }
2112
+ restart: { scheduled: true, delayMs },
2113
+ sentinel: sentinelPath ? { path: sentinelPath } : null
1944
2114
  };
1945
2115
  }
1946
2116
  async updateRun(params) {
@@ -1949,13 +2119,22 @@ var GatewayControllerImpl = class {
1949
2119
  return { ok: false, error: result.error ?? "update failed", steps: result.steps };
1950
2120
  }
1951
2121
  const delayMs = params.restartDelayMs ?? 0;
2122
+ const sentinelPath = await this.writeRestartSentinelPayload({
2123
+ kind: "update.run",
2124
+ status: "ok",
2125
+ sessionKey: params.sessionKey,
2126
+ note: params.note,
2127
+ reason: "update.run",
2128
+ strategy: result.strategy
2129
+ });
1952
2130
  await this.requestRestart({ delayMs, reason: "update.run" });
1953
2131
  return {
1954
2132
  ok: true,
1955
2133
  note: params.note ?? null,
1956
2134
  restart: { scheduled: true, delayMs },
1957
2135
  strategy: result.strategy,
1958
- steps: result.steps
2136
+ steps: result.steps,
2137
+ sentinel: sentinelPath ? { path: sentinelPath } : null
1959
2138
  };
1960
2139
  }
1961
2140
  };
@@ -2126,7 +2305,7 @@ var ServiceCommands = class {
2126
2305
  config: config2
2127
2306
  });
2128
2307
  const sessionManager = new SessionManager(workspace);
2129
- const cronStorePath = join4(getDataDir4(), "cron", "jobs.json");
2308
+ const cronStorePath = join4(getDataDir5(), "cron", "jobs.json");
2130
2309
  const cron2 = new CronService2(cronStorePath);
2131
2310
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
2132
2311
  const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
@@ -2154,6 +2333,7 @@ var ServiceCommands = class {
2154
2333
  const gatewayController = new GatewayControllerImpl({
2155
2334
  reloader,
2156
2335
  cron: cron2,
2336
+ sessionManager,
2157
2337
  getConfigPath: getConfigPath2,
2158
2338
  saveConfig: saveConfig4,
2159
2339
  requestRestart: async (options2) => {
@@ -2300,12 +2480,58 @@ var ServiceCommands = class {
2300
2480
  console.warn(`[plugins] ${text}`);
2301
2481
  }
2302
2482
  }
2303
- await Promise.allSettled([agent.run(), reloader.getChannels().startAll()]);
2483
+ await reloader.getChannels().startAll();
2484
+ await this.wakeFromRestartSentinel({ bus, sessionManager });
2485
+ await agent.run();
2304
2486
  } finally {
2305
2487
  await stopPluginChannelGateways(pluginGatewayHandles);
2306
2488
  setPluginRuntimeBridge(null);
2307
2489
  }
2308
2490
  }
2491
+ normalizeOptionalString(value) {
2492
+ if (typeof value !== "string") {
2493
+ return void 0;
2494
+ }
2495
+ const trimmed = value.trim();
2496
+ return trimmed || void 0;
2497
+ }
2498
+ async wakeFromRestartSentinel(params) {
2499
+ const sentinel = await consumeRestartSentinel();
2500
+ if (!sentinel) {
2501
+ return;
2502
+ }
2503
+ await new Promise((resolve10) => setTimeout(resolve10, 750));
2504
+ const payload = sentinel.payload;
2505
+ const message = formatRestartSentinelMessage(payload);
2506
+ const sessionKey = this.normalizeOptionalString(payload.sessionKey) ?? "cli:default";
2507
+ const parsedSession = parseSessionKey(sessionKey);
2508
+ const context = payload.deliveryContext;
2509
+ const channel = this.normalizeOptionalString(context?.channel) ?? parsedSession?.channel ?? this.normalizeOptionalString((params.sessionManager.getIfExists(sessionKey)?.metadata ?? {}).last_channel);
2510
+ const chatId = this.normalizeOptionalString(context?.chatId) ?? parsedSession?.chatId ?? this.normalizeOptionalString((params.sessionManager.getIfExists(sessionKey)?.metadata ?? {}).last_to);
2511
+ const replyTo = this.normalizeOptionalString(context?.replyTo);
2512
+ const accountId = this.normalizeOptionalString(context?.accountId);
2513
+ const metadataRaw = context?.metadata;
2514
+ const metadata = metadataRaw && typeof metadataRaw === "object" && !Array.isArray(metadataRaw) ? { ...metadataRaw } : {};
2515
+ if (accountId && !this.normalizeOptionalString(metadata.accountId)) {
2516
+ metadata.accountId = accountId;
2517
+ }
2518
+ if (!channel || !chatId) {
2519
+ enqueuePendingSystemEvent(params.sessionManager, sessionKey, message);
2520
+ return;
2521
+ }
2522
+ try {
2523
+ await params.bus.publishOutbound({
2524
+ channel,
2525
+ chatId,
2526
+ content: message,
2527
+ ...replyTo ? { replyTo } : {},
2528
+ media: [],
2529
+ metadata
2530
+ });
2531
+ } catch {
2532
+ enqueuePendingSystemEvent(params.sessionManager, sessionKey, message);
2533
+ }
2534
+ }
2309
2535
  async runForeground(options) {
2310
2536
  const config2 = loadConfig5();
2311
2537
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
@@ -2369,8 +2595,8 @@ var ServiceCommands = class {
2369
2595
  console.log("Warning: UI frontend not found in package assets.");
2370
2596
  }
2371
2597
  const logPath = resolveServiceLogPath();
2372
- const logDir = resolve6(logPath, "..");
2373
- mkdirSync2(logDir, { recursive: true });
2598
+ const logDir = resolve7(logPath, "..");
2599
+ mkdirSync3(logDir, { recursive: true });
2374
2600
  const logFd = openSync(logPath, "a");
2375
2601
  const serveArgs = buildServeArgs({
2376
2602
  uiPort: uiConfig.port
@@ -2464,22 +2690,22 @@ var ServiceCommands = class {
2464
2690
  try {
2465
2691
  const response = await fetch(params.healthUrl, { method: "GET" });
2466
2692
  if (!response.ok) {
2467
- await new Promise((resolve9) => setTimeout(resolve9, 200));
2693
+ await new Promise((resolve10) => setTimeout(resolve10, 200));
2468
2694
  continue;
2469
2695
  }
2470
2696
  const payload = await response.json();
2471
2697
  const healthy = payload?.ok === true && payload?.data?.status === "ok";
2472
2698
  if (!healthy) {
2473
- await new Promise((resolve9) => setTimeout(resolve9, 200));
2699
+ await new Promise((resolve10) => setTimeout(resolve10, 200));
2474
2700
  continue;
2475
2701
  }
2476
- await new Promise((resolve9) => setTimeout(resolve9, 300));
2702
+ await new Promise((resolve10) => setTimeout(resolve10, 300));
2477
2703
  if (isProcessRunning(params.pid)) {
2478
2704
  return true;
2479
2705
  }
2480
2706
  } catch {
2481
2707
  }
2482
- await new Promise((resolve9) => setTimeout(resolve9, 200));
2708
+ await new Promise((resolve10) => setTimeout(resolve10, 200));
2483
2709
  }
2484
2710
  return false;
2485
2711
  }
@@ -2552,11 +2778,11 @@ var ServiceCommands = class {
2552
2778
  };
2553
2779
 
2554
2780
  // src/cli/workspace.ts
2555
- import { cpSync, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, readdirSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
2781
+ import { cpSync, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, readdirSync, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
2556
2782
  import { createRequire } from "module";
2557
- import { dirname, join as join5, resolve as resolve7 } from "path";
2783
+ import { dirname, join as join5, resolve as resolve8 } from "path";
2558
2784
  import { fileURLToPath as fileURLToPath2 } from "url";
2559
- import { APP_NAME as APP_NAME3, getDataDir as getDataDir5 } from "@nextclaw/core";
2785
+ import { APP_NAME as APP_NAME3, getDataDir as getDataDir6 } from "@nextclaw/core";
2560
2786
  import { spawnSync as spawnSync4 } from "child_process";
2561
2787
  var WorkspaceManager = class {
2562
2788
  constructor(logo) {
@@ -2585,28 +2811,28 @@ var WorkspaceManager = class {
2585
2811
  ];
2586
2812
  for (const entry of templateFiles) {
2587
2813
  const filePath = join5(workspace, entry.target);
2588
- if (!force && existsSync6(filePath)) {
2814
+ if (!force && existsSync7(filePath)) {
2589
2815
  continue;
2590
2816
  }
2591
2817
  const templatePath = join5(templateDir, entry.source);
2592
- if (!existsSync6(templatePath)) {
2818
+ if (!existsSync7(templatePath)) {
2593
2819
  console.warn(`Warning: Template file missing: ${templatePath}`);
2594
2820
  continue;
2595
2821
  }
2596
- const raw = readFileSync4(templatePath, "utf-8");
2822
+ const raw = readFileSync5(templatePath, "utf-8");
2597
2823
  const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME3);
2598
- mkdirSync3(dirname(filePath), { recursive: true });
2599
- writeFileSync2(filePath, content);
2824
+ mkdirSync4(dirname(filePath), { recursive: true });
2825
+ writeFileSync3(filePath, content);
2600
2826
  created.push(entry.target);
2601
2827
  }
2602
2828
  const memoryDir = join5(workspace, "memory");
2603
- if (!existsSync6(memoryDir)) {
2604
- mkdirSync3(memoryDir, { recursive: true });
2829
+ if (!existsSync7(memoryDir)) {
2830
+ mkdirSync4(memoryDir, { recursive: true });
2605
2831
  created.push(join5("memory", ""));
2606
2832
  }
2607
2833
  const skillsDir = join5(workspace, "skills");
2608
- if (!existsSync6(skillsDir)) {
2609
- mkdirSync3(skillsDir, { recursive: true });
2834
+ if (!existsSync7(skillsDir)) {
2835
+ mkdirSync4(skillsDir, { recursive: true });
2610
2836
  created.push(join5("skills", ""));
2611
2837
  }
2612
2838
  const seeded = this.seedBuiltinSkills(skillsDir, { force });
@@ -2627,11 +2853,11 @@ var WorkspaceManager = class {
2627
2853
  continue;
2628
2854
  }
2629
2855
  const src = join5(sourceDir, entry.name);
2630
- if (!existsSync6(join5(src, "SKILL.md"))) {
2856
+ if (!existsSync7(join5(src, "SKILL.md"))) {
2631
2857
  continue;
2632
2858
  }
2633
2859
  const dest = join5(targetDir, entry.name);
2634
- if (!force && existsSync6(dest)) {
2860
+ if (!force && existsSync7(dest)) {
2635
2861
  continue;
2636
2862
  }
2637
2863
  cpSync(src, dest, { recursive: true, force: true });
@@ -2643,13 +2869,13 @@ var WorkspaceManager = class {
2643
2869
  try {
2644
2870
  const require2 = createRequire(import.meta.url);
2645
2871
  const entry = require2.resolve("@nextclaw/core");
2646
- const pkgRoot = resolve7(dirname(entry), "..");
2872
+ const pkgRoot = resolve8(dirname(entry), "..");
2647
2873
  const distSkills = join5(pkgRoot, "dist", "skills");
2648
- if (existsSync6(distSkills)) {
2874
+ if (existsSync7(distSkills)) {
2649
2875
  return distSkills;
2650
2876
  }
2651
2877
  const srcSkills = join5(pkgRoot, "src", "agent", "skills");
2652
- if (existsSync6(srcSkills)) {
2878
+ if (existsSync7(srcSkills)) {
2653
2879
  return srcSkills;
2654
2880
  }
2655
2881
  return null;
@@ -2662,33 +2888,33 @@ var WorkspaceManager = class {
2662
2888
  if (override) {
2663
2889
  return override;
2664
2890
  }
2665
- const cliDir = resolve7(fileURLToPath2(new URL(".", import.meta.url)));
2666
- const pkgRoot = resolve7(cliDir, "..", "..");
2891
+ const cliDir = resolve8(fileURLToPath2(new URL(".", import.meta.url)));
2892
+ const pkgRoot = resolve8(cliDir, "..", "..");
2667
2893
  const candidates = [join5(pkgRoot, "templates")];
2668
2894
  for (const candidate of candidates) {
2669
- if (existsSync6(candidate)) {
2895
+ if (existsSync7(candidate)) {
2670
2896
  return candidate;
2671
2897
  }
2672
2898
  }
2673
2899
  return null;
2674
2900
  }
2675
2901
  getBridgeDir() {
2676
- const userBridge = join5(getDataDir5(), "bridge");
2677
- if (existsSync6(join5(userBridge, "dist", "index.js"))) {
2902
+ const userBridge = join5(getDataDir6(), "bridge");
2903
+ if (existsSync7(join5(userBridge, "dist", "index.js"))) {
2678
2904
  return userBridge;
2679
2905
  }
2680
2906
  if (!which("npm")) {
2681
2907
  console.error("npm not found. Please install Node.js >= 18.");
2682
2908
  process.exit(1);
2683
2909
  }
2684
- const cliDir = resolve7(fileURLToPath2(new URL(".", import.meta.url)));
2685
- const pkgRoot = resolve7(cliDir, "..", "..");
2910
+ const cliDir = resolve8(fileURLToPath2(new URL(".", import.meta.url)));
2911
+ const pkgRoot = resolve8(cliDir, "..", "..");
2686
2912
  const pkgBridge = join5(pkgRoot, "bridge");
2687
2913
  const srcBridge = join5(pkgRoot, "..", "..", "bridge");
2688
2914
  let source = null;
2689
- if (existsSync6(join5(pkgBridge, "package.json"))) {
2915
+ if (existsSync7(join5(pkgBridge, "package.json"))) {
2690
2916
  source = pkgBridge;
2691
- } else if (existsSync6(join5(srcBridge, "package.json"))) {
2917
+ } else if (existsSync7(join5(srcBridge, "package.json"))) {
2692
2918
  source = srcBridge;
2693
2919
  }
2694
2920
  if (!source) {
@@ -2696,9 +2922,9 @@ var WorkspaceManager = class {
2696
2922
  process.exit(1);
2697
2923
  }
2698
2924
  console.log(`${this.logo} Setting up bridge...`);
2699
- mkdirSync3(resolve7(userBridge, ".."), { recursive: true });
2700
- if (existsSync6(userBridge)) {
2701
- rmSync2(userBridge, { recursive: true, force: true });
2925
+ mkdirSync4(resolve8(userBridge, ".."), { recursive: true });
2926
+ if (existsSync7(userBridge)) {
2927
+ rmSync3(userBridge, { recursive: true, force: true });
2702
2928
  }
2703
2929
  cpSync(source, userBridge, {
2704
2930
  recursive: true,
@@ -2822,7 +3048,7 @@ var CliRuntime = class {
2822
3048
  const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? Math.max(0, Math.floor(params.delayMs)) : 100;
2823
3049
  const cliPath = process.env.NEXTCLAW_SELF_RELAUNCH_CLI?.trim() || fileURLToPath3(new URL("./index.js", import.meta.url));
2824
3050
  const startArgs = [cliPath, "start", "--ui-port", String(uiPort)];
2825
- const serviceStatePath = resolve8(getDataDir6(), "run", "service.json");
3051
+ const serviceStatePath = resolve9(getDataDir7(), "run", "service.json");
2826
3052
  const helperScript = [
2827
3053
  'const { spawnSync } = require("node:child_process");',
2828
3054
  'const { readFileSync } = require("node:fs");',
@@ -2927,16 +3153,16 @@ var CliRuntime = class {
2927
3153
  const force = Boolean(options.force);
2928
3154
  const configPath = getConfigPath3();
2929
3155
  let createdConfig = false;
2930
- if (!existsSync7(configPath)) {
3156
+ if (!existsSync8(configPath)) {
2931
3157
  const config3 = ConfigSchema2.parse({});
2932
3158
  saveConfig5(config3);
2933
3159
  createdConfig = true;
2934
3160
  }
2935
3161
  const config2 = loadConfig6();
2936
3162
  const workspaceSetting = config2.agents.defaults.workspace;
2937
- const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join6(getDataDir6(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
2938
- const workspaceExisted = existsSync7(workspacePath);
2939
- mkdirSync4(workspacePath, { recursive: true });
3163
+ const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join6(getDataDir7(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
3164
+ const workspaceExisted = existsSync8(workspacePath);
3165
+ mkdirSync5(workspacePath, { recursive: true });
2940
3166
  const templateResult = this.workspaceManager.createWorkspaceTemplates(workspacePath, { force });
2941
3167
  if (createdConfig) {
2942
3168
  console.log(`\u2713 ${prefix}: created config at ${configPath}`);
@@ -3077,14 +3303,14 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
3077
3303
  }
3078
3304
  console.log(`${this.logo} Interactive mode (type exit or Ctrl+C to quit)
3079
3305
  `);
3080
- const historyFile = join6(getDataDir6(), "history", "cli_history");
3081
- const historyDir = resolve8(historyFile, "..");
3082
- mkdirSync4(historyDir, { recursive: true });
3083
- const history = existsSync7(historyFile) ? readFileSync5(historyFile, "utf-8").split("\n").filter(Boolean) : [];
3306
+ const historyFile = join6(getDataDir7(), "history", "cli_history");
3307
+ const historyDir = resolve9(historyFile, "..");
3308
+ mkdirSync5(historyDir, { recursive: true });
3309
+ const history = existsSync8(historyFile) ? readFileSync6(historyFile, "utf-8").split("\n").filter(Boolean) : [];
3084
3310
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
3085
3311
  rl.on("close", () => {
3086
3312
  const merged = history.concat(rl.history ?? []);
3087
- writeFileSync3(historyFile, merged.join("\n"));
3313
+ writeFileSync4(historyFile, merged.join("\n"));
3088
3314
  process.exit(0);
3089
3315
  });
3090
3316
  let running = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextclaw",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
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.6",
41
+ "@nextclaw/core": "^0.6.8",
42
42
  "@nextclaw/server": "^0.4.2",
43
43
  "@nextclaw/openclaw-compat": "^0.1.5"
44
44
  },
@@ -265,6 +265,7 @@ Behavior:
265
265
  - Otherwise it falls back to `npm i -g nextclaw`.
266
266
  - If the background service is running, restart it after the update to apply changes.
267
267
  - When update is triggered from the running gateway (agent `update.run`), NextClaw arms a self-relaunch helper before exiting, so the service comes back automatically (like an OS reboot flow).
268
+ - After restart, NextClaw automatically pings the last active session with restart/update status (including note when provided).
268
269
 
269
270
  If the gateway is running, you can also ask the agent to update; the agent will call the gateway update tool only when you explicitly request it, and restart/relaunch will be scheduled afterward.
270
271