nextclaw 0.6.8 → 0.6.10

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,97 @@ 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({ channels: reloader.getChannels(), 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 sleep(ms) {
2499
+ await new Promise((resolve10) => setTimeout(resolve10, ms));
2500
+ }
2501
+ async sendRestartSentinelNotice(params) {
2502
+ const outboundBase = {
2503
+ channel: params.channel,
2504
+ chatId: params.chatId,
2505
+ content: params.content,
2506
+ media: [],
2507
+ metadata: params.metadata
2508
+ };
2509
+ const variants = params.replyTo ? [
2510
+ { ...outboundBase, replyTo: params.replyTo },
2511
+ { ...outboundBase }
2512
+ ] : [{ ...outboundBase }];
2513
+ for (let variantIndex = 0; variantIndex < variants.length; variantIndex += 1) {
2514
+ const outbound = variants[variantIndex];
2515
+ const isLastVariant = variantIndex === variants.length - 1;
2516
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
2517
+ try {
2518
+ const delivered = await params.channels.deliver(outbound);
2519
+ if (delivered) {
2520
+ return true;
2521
+ }
2522
+ return false;
2523
+ } catch (error) {
2524
+ if (attempt < 3) {
2525
+ await this.sleep(attempt * 500);
2526
+ continue;
2527
+ }
2528
+ if (isLastVariant) {
2529
+ console.warn(
2530
+ `Warning: restart sentinel notify failed for ${params.channel}:${params.chatId} (attempt ${attempt}): ${String(error)}`
2531
+ );
2532
+ }
2533
+ }
2534
+ }
2535
+ }
2536
+ return false;
2537
+ }
2538
+ async wakeFromRestartSentinel(params) {
2539
+ const sentinel = await consumeRestartSentinel();
2540
+ if (!sentinel) {
2541
+ return;
2542
+ }
2543
+ await new Promise((resolve10) => setTimeout(resolve10, 750));
2544
+ const payload = sentinel.payload;
2545
+ const message = formatRestartSentinelMessage(payload);
2546
+ const sessionKey = this.normalizeOptionalString(payload.sessionKey) ?? "cli:default";
2547
+ const parsedSession = parseSessionKey(sessionKey);
2548
+ const context = payload.deliveryContext;
2549
+ const channel = this.normalizeOptionalString(context?.channel) ?? parsedSession?.channel ?? this.normalizeOptionalString((params.sessionManager.getIfExists(sessionKey)?.metadata ?? {}).last_channel);
2550
+ const chatId = this.normalizeOptionalString(context?.chatId) ?? parsedSession?.chatId ?? this.normalizeOptionalString((params.sessionManager.getIfExists(sessionKey)?.metadata ?? {}).last_to);
2551
+ const replyTo = this.normalizeOptionalString(context?.replyTo);
2552
+ const accountId = this.normalizeOptionalString(context?.accountId);
2553
+ const metadataRaw = context?.metadata;
2554
+ const metadata = metadataRaw && typeof metadataRaw === "object" && !Array.isArray(metadataRaw) ? { ...metadataRaw } : {};
2555
+ if (accountId && !this.normalizeOptionalString(metadata.accountId)) {
2556
+ metadata.accountId = accountId;
2557
+ }
2558
+ if (!channel || !chatId) {
2559
+ enqueuePendingSystemEvent(params.sessionManager, sessionKey, message);
2560
+ return;
2561
+ }
2562
+ const delivered = await this.sendRestartSentinelNotice({
2563
+ channels: params.channels,
2564
+ channel,
2565
+ chatId,
2566
+ content: message,
2567
+ ...replyTo ? { replyTo } : {},
2568
+ metadata
2569
+ });
2570
+ if (!delivered) {
2571
+ enqueuePendingSystemEvent(params.sessionManager, sessionKey, message);
2572
+ }
2573
+ }
2309
2574
  async runForeground(options) {
2310
2575
  const config2 = loadConfig5();
2311
2576
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
@@ -2369,8 +2634,8 @@ var ServiceCommands = class {
2369
2634
  console.log("Warning: UI frontend not found in package assets.");
2370
2635
  }
2371
2636
  const logPath = resolveServiceLogPath();
2372
- const logDir = resolve6(logPath, "..");
2373
- mkdirSync2(logDir, { recursive: true });
2637
+ const logDir = resolve7(logPath, "..");
2638
+ mkdirSync3(logDir, { recursive: true });
2374
2639
  const logFd = openSync(logPath, "a");
2375
2640
  const serveArgs = buildServeArgs({
2376
2641
  uiPort: uiConfig.port
@@ -2464,22 +2729,22 @@ var ServiceCommands = class {
2464
2729
  try {
2465
2730
  const response = await fetch(params.healthUrl, { method: "GET" });
2466
2731
  if (!response.ok) {
2467
- await new Promise((resolve9) => setTimeout(resolve9, 200));
2732
+ await new Promise((resolve10) => setTimeout(resolve10, 200));
2468
2733
  continue;
2469
2734
  }
2470
2735
  const payload = await response.json();
2471
2736
  const healthy = payload?.ok === true && payload?.data?.status === "ok";
2472
2737
  if (!healthy) {
2473
- await new Promise((resolve9) => setTimeout(resolve9, 200));
2738
+ await new Promise((resolve10) => setTimeout(resolve10, 200));
2474
2739
  continue;
2475
2740
  }
2476
- await new Promise((resolve9) => setTimeout(resolve9, 300));
2741
+ await new Promise((resolve10) => setTimeout(resolve10, 300));
2477
2742
  if (isProcessRunning(params.pid)) {
2478
2743
  return true;
2479
2744
  }
2480
2745
  } catch {
2481
2746
  }
2482
- await new Promise((resolve9) => setTimeout(resolve9, 200));
2747
+ await new Promise((resolve10) => setTimeout(resolve10, 200));
2483
2748
  }
2484
2749
  return false;
2485
2750
  }
@@ -2552,11 +2817,11 @@ var ServiceCommands = class {
2552
2817
  };
2553
2818
 
2554
2819
  // 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";
2820
+ import { cpSync, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, readdirSync, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
2556
2821
  import { createRequire } from "module";
2557
- import { dirname, join as join5, resolve as resolve7 } from "path";
2822
+ import { dirname, join as join5, resolve as resolve8 } from "path";
2558
2823
  import { fileURLToPath as fileURLToPath2 } from "url";
2559
- import { APP_NAME as APP_NAME3, getDataDir as getDataDir5 } from "@nextclaw/core";
2824
+ import { APP_NAME as APP_NAME3, getDataDir as getDataDir6 } from "@nextclaw/core";
2560
2825
  import { spawnSync as spawnSync4 } from "child_process";
2561
2826
  var WorkspaceManager = class {
2562
2827
  constructor(logo) {
@@ -2585,28 +2850,28 @@ var WorkspaceManager = class {
2585
2850
  ];
2586
2851
  for (const entry of templateFiles) {
2587
2852
  const filePath = join5(workspace, entry.target);
2588
- if (!force && existsSync6(filePath)) {
2853
+ if (!force && existsSync7(filePath)) {
2589
2854
  continue;
2590
2855
  }
2591
2856
  const templatePath = join5(templateDir, entry.source);
2592
- if (!existsSync6(templatePath)) {
2857
+ if (!existsSync7(templatePath)) {
2593
2858
  console.warn(`Warning: Template file missing: ${templatePath}`);
2594
2859
  continue;
2595
2860
  }
2596
- const raw = readFileSync4(templatePath, "utf-8");
2861
+ const raw = readFileSync5(templatePath, "utf-8");
2597
2862
  const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME3);
2598
- mkdirSync3(dirname(filePath), { recursive: true });
2599
- writeFileSync2(filePath, content);
2863
+ mkdirSync4(dirname(filePath), { recursive: true });
2864
+ writeFileSync3(filePath, content);
2600
2865
  created.push(entry.target);
2601
2866
  }
2602
2867
  const memoryDir = join5(workspace, "memory");
2603
- if (!existsSync6(memoryDir)) {
2604
- mkdirSync3(memoryDir, { recursive: true });
2868
+ if (!existsSync7(memoryDir)) {
2869
+ mkdirSync4(memoryDir, { recursive: true });
2605
2870
  created.push(join5("memory", ""));
2606
2871
  }
2607
2872
  const skillsDir = join5(workspace, "skills");
2608
- if (!existsSync6(skillsDir)) {
2609
- mkdirSync3(skillsDir, { recursive: true });
2873
+ if (!existsSync7(skillsDir)) {
2874
+ mkdirSync4(skillsDir, { recursive: true });
2610
2875
  created.push(join5("skills", ""));
2611
2876
  }
2612
2877
  const seeded = this.seedBuiltinSkills(skillsDir, { force });
@@ -2627,11 +2892,11 @@ var WorkspaceManager = class {
2627
2892
  continue;
2628
2893
  }
2629
2894
  const src = join5(sourceDir, entry.name);
2630
- if (!existsSync6(join5(src, "SKILL.md"))) {
2895
+ if (!existsSync7(join5(src, "SKILL.md"))) {
2631
2896
  continue;
2632
2897
  }
2633
2898
  const dest = join5(targetDir, entry.name);
2634
- if (!force && existsSync6(dest)) {
2899
+ if (!force && existsSync7(dest)) {
2635
2900
  continue;
2636
2901
  }
2637
2902
  cpSync(src, dest, { recursive: true, force: true });
@@ -2643,13 +2908,13 @@ var WorkspaceManager = class {
2643
2908
  try {
2644
2909
  const require2 = createRequire(import.meta.url);
2645
2910
  const entry = require2.resolve("@nextclaw/core");
2646
- const pkgRoot = resolve7(dirname(entry), "..");
2911
+ const pkgRoot = resolve8(dirname(entry), "..");
2647
2912
  const distSkills = join5(pkgRoot, "dist", "skills");
2648
- if (existsSync6(distSkills)) {
2913
+ if (existsSync7(distSkills)) {
2649
2914
  return distSkills;
2650
2915
  }
2651
2916
  const srcSkills = join5(pkgRoot, "src", "agent", "skills");
2652
- if (existsSync6(srcSkills)) {
2917
+ if (existsSync7(srcSkills)) {
2653
2918
  return srcSkills;
2654
2919
  }
2655
2920
  return null;
@@ -2662,33 +2927,33 @@ var WorkspaceManager = class {
2662
2927
  if (override) {
2663
2928
  return override;
2664
2929
  }
2665
- const cliDir = resolve7(fileURLToPath2(new URL(".", import.meta.url)));
2666
- const pkgRoot = resolve7(cliDir, "..", "..");
2930
+ const cliDir = resolve8(fileURLToPath2(new URL(".", import.meta.url)));
2931
+ const pkgRoot = resolve8(cliDir, "..", "..");
2667
2932
  const candidates = [join5(pkgRoot, "templates")];
2668
2933
  for (const candidate of candidates) {
2669
- if (existsSync6(candidate)) {
2934
+ if (existsSync7(candidate)) {
2670
2935
  return candidate;
2671
2936
  }
2672
2937
  }
2673
2938
  return null;
2674
2939
  }
2675
2940
  getBridgeDir() {
2676
- const userBridge = join5(getDataDir5(), "bridge");
2677
- if (existsSync6(join5(userBridge, "dist", "index.js"))) {
2941
+ const userBridge = join5(getDataDir6(), "bridge");
2942
+ if (existsSync7(join5(userBridge, "dist", "index.js"))) {
2678
2943
  return userBridge;
2679
2944
  }
2680
2945
  if (!which("npm")) {
2681
2946
  console.error("npm not found. Please install Node.js >= 18.");
2682
2947
  process.exit(1);
2683
2948
  }
2684
- const cliDir = resolve7(fileURLToPath2(new URL(".", import.meta.url)));
2685
- const pkgRoot = resolve7(cliDir, "..", "..");
2949
+ const cliDir = resolve8(fileURLToPath2(new URL(".", import.meta.url)));
2950
+ const pkgRoot = resolve8(cliDir, "..", "..");
2686
2951
  const pkgBridge = join5(pkgRoot, "bridge");
2687
2952
  const srcBridge = join5(pkgRoot, "..", "..", "bridge");
2688
2953
  let source = null;
2689
- if (existsSync6(join5(pkgBridge, "package.json"))) {
2954
+ if (existsSync7(join5(pkgBridge, "package.json"))) {
2690
2955
  source = pkgBridge;
2691
- } else if (existsSync6(join5(srcBridge, "package.json"))) {
2956
+ } else if (existsSync7(join5(srcBridge, "package.json"))) {
2692
2957
  source = srcBridge;
2693
2958
  }
2694
2959
  if (!source) {
@@ -2696,9 +2961,9 @@ var WorkspaceManager = class {
2696
2961
  process.exit(1);
2697
2962
  }
2698
2963
  console.log(`${this.logo} Setting up bridge...`);
2699
- mkdirSync3(resolve7(userBridge, ".."), { recursive: true });
2700
- if (existsSync6(userBridge)) {
2701
- rmSync2(userBridge, { recursive: true, force: true });
2964
+ mkdirSync4(resolve8(userBridge, ".."), { recursive: true });
2965
+ if (existsSync7(userBridge)) {
2966
+ rmSync3(userBridge, { recursive: true, force: true });
2702
2967
  }
2703
2968
  cpSync(source, userBridge, {
2704
2969
  recursive: true,
@@ -2822,7 +3087,7 @@ var CliRuntime = class {
2822
3087
  const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? Math.max(0, Math.floor(params.delayMs)) : 100;
2823
3088
  const cliPath = process.env.NEXTCLAW_SELF_RELAUNCH_CLI?.trim() || fileURLToPath3(new URL("./index.js", import.meta.url));
2824
3089
  const startArgs = [cliPath, "start", "--ui-port", String(uiPort)];
2825
- const serviceStatePath = resolve8(getDataDir6(), "run", "service.json");
3090
+ const serviceStatePath = resolve9(getDataDir7(), "run", "service.json");
2826
3091
  const helperScript = [
2827
3092
  'const { spawnSync } = require("node:child_process");',
2828
3093
  'const { readFileSync } = require("node:fs");',
@@ -2927,16 +3192,16 @@ var CliRuntime = class {
2927
3192
  const force = Boolean(options.force);
2928
3193
  const configPath = getConfigPath3();
2929
3194
  let createdConfig = false;
2930
- if (!existsSync7(configPath)) {
3195
+ if (!existsSync8(configPath)) {
2931
3196
  const config3 = ConfigSchema2.parse({});
2932
3197
  saveConfig5(config3);
2933
3198
  createdConfig = true;
2934
3199
  }
2935
3200
  const config2 = loadConfig6();
2936
3201
  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 });
3202
+ const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join6(getDataDir7(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
3203
+ const workspaceExisted = existsSync8(workspacePath);
3204
+ mkdirSync5(workspacePath, { recursive: true });
2940
3205
  const templateResult = this.workspaceManager.createWorkspaceTemplates(workspacePath, { force });
2941
3206
  if (createdConfig) {
2942
3207
  console.log(`\u2713 ${prefix}: created config at ${configPath}`);
@@ -3077,14 +3342,14 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
3077
3342
  }
3078
3343
  console.log(`${this.logo} Interactive mode (type exit or Ctrl+C to quit)
3079
3344
  `);
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) : [];
3345
+ const historyFile = join6(getDataDir7(), "history", "cli_history");
3346
+ const historyDir = resolve9(historyFile, "..");
3347
+ mkdirSync5(historyDir, { recursive: true });
3348
+ const history = existsSync8(historyFile) ? readFileSync6(historyFile, "utf-8").split("\n").filter(Boolean) : [];
3084
3349
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
3085
3350
  rl.on("close", () => {
3086
3351
  const merged = history.concat(rl.history ?? []);
3087
- writeFileSync3(historyFile, merged.join("\n"));
3352
+ writeFileSync4(historyFile, merged.join("\n"));
3088
3353
  process.exit(0);
3089
3354
  });
3090
3355
  let running = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextclaw",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
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.7",
41
+ "@nextclaw/core": "^0.6.9",
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