nextclaw 0.5.4 → 0.5.5

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
@@ -954,6 +954,7 @@ var CliRuntime = class {
954
954
  logo;
955
955
  restartCoordinator;
956
956
  serviceRestartTask = null;
957
+ selfRelaunchArmed = false;
957
958
  constructor(options = {}) {
958
959
  this.logo = options.logo ?? LOGO;
959
960
  this.restartCoordinator = new RestartCoordinator({
@@ -1002,7 +1003,99 @@ var CliRuntime = class {
1002
1003
  this.serviceRestartTask = null;
1003
1004
  }
1004
1005
  }
1006
+ armManagedServiceRelaunch(params) {
1007
+ const strategy = params.strategy ?? "background-service-or-manual";
1008
+ if (strategy !== "background-service-or-exit" && strategy !== "exit-process") {
1009
+ return;
1010
+ }
1011
+ if (this.selfRelaunchArmed) {
1012
+ return;
1013
+ }
1014
+ const state = readServiceState();
1015
+ if (!state || state.pid !== process.pid) {
1016
+ return;
1017
+ }
1018
+ const uiPort = typeof state.uiPort === "number" && Number.isFinite(state.uiPort) ? state.uiPort : 18791;
1019
+ const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? Math.max(0, Math.floor(params.delayMs)) : 100;
1020
+ const cliPath = process.env.NEXTCLAW_SELF_RELAUNCH_CLI?.trim() || fileURLToPath2(new URL("./index.js", import.meta.url));
1021
+ const startArgs = [cliPath, "start", "--ui-port", String(uiPort)];
1022
+ const serviceStatePath = resolve4(getDataDir2(), "run", "service.json");
1023
+ const helperScript = [
1024
+ 'const { spawnSync } = require("node:child_process");',
1025
+ 'const { readFileSync } = require("node:fs");',
1026
+ `const parentPid = ${process.pid};`,
1027
+ `const delayMs = ${delayMs};`,
1028
+ "const maxWaitMs = 120000;",
1029
+ "const retryIntervalMs = 1000;",
1030
+ "const startTimeoutMs = 60000;",
1031
+ `const nodePath = ${JSON.stringify(process.execPath)};`,
1032
+ `const startArgs = ${JSON.stringify(startArgs)};`,
1033
+ `const serviceStatePath = ${JSON.stringify(serviceStatePath)};`,
1034
+ "function isRunning(pid) {",
1035
+ " try {",
1036
+ " process.kill(pid, 0);",
1037
+ " return true;",
1038
+ " } catch {",
1039
+ " return false;",
1040
+ " }",
1041
+ "}",
1042
+ "function hasReplacementService() {",
1043
+ " try {",
1044
+ ' const raw = readFileSync(serviceStatePath, "utf-8");',
1045
+ " const state = JSON.parse(raw);",
1046
+ " const pid = Number(state?.pid);",
1047
+ " return Number.isFinite(pid) && pid > 0 && pid !== parentPid && isRunning(pid);",
1048
+ " } catch {",
1049
+ " return false;",
1050
+ " }",
1051
+ "}",
1052
+ "function tryStart() {",
1053
+ " spawnSync(nodePath, startArgs, {",
1054
+ ' stdio: "ignore",',
1055
+ " env: process.env,",
1056
+ " timeout: startTimeoutMs",
1057
+ " });",
1058
+ "}",
1059
+ "setTimeout(() => {",
1060
+ " const startedAt = Date.now();",
1061
+ " const tick = () => {",
1062
+ " if (hasReplacementService()) {",
1063
+ " process.exit(0);",
1064
+ " return;",
1065
+ " }",
1066
+ " if (Date.now() - startedAt >= maxWaitMs) {",
1067
+ " process.exit(0);",
1068
+ " return;",
1069
+ " }",
1070
+ " tryStart();",
1071
+ " if (hasReplacementService()) {",
1072
+ " process.exit(0);",
1073
+ " return;",
1074
+ " }",
1075
+ " setTimeout(tick, retryIntervalMs);",
1076
+ " };",
1077
+ " tick();",
1078
+ "}, delayMs);"
1079
+ ].join("\n");
1080
+ try {
1081
+ const helper = spawn2(process.execPath, ["-e", helperScript], {
1082
+ detached: true,
1083
+ stdio: "ignore",
1084
+ env: process.env
1085
+ });
1086
+ helper.unref();
1087
+ this.selfRelaunchArmed = true;
1088
+ console.warn(`Gateway self-restart armed (${params.reason}).`);
1089
+ } catch (error) {
1090
+ console.error(`Failed to arm gateway self-restart: ${String(error)}`);
1091
+ }
1092
+ }
1005
1093
  async requestRestart(params) {
1094
+ this.armManagedServiceRelaunch({
1095
+ reason: params.reason,
1096
+ strategy: params.strategy,
1097
+ delayMs: params.delayMs
1098
+ });
1006
1099
  const result = await this.restartCoordinator.requestRestart({
1007
1100
  reason: params.reason,
1008
1101
  strategy: params.strategy,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextclaw",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "Lightweight personal AI assistant with CLI, multi-provider routing, and channel integrations.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -38,9 +38,9 @@
38
38
  "dependencies": {
39
39
  "chokidar": "^3.6.0",
40
40
  "commander": "^12.1.0",
41
- "@nextclaw/core": "^0.5.2",
41
+ "@nextclaw/core": "^0.5.3",
42
42
  "@nextclaw/server": "^0.3.7",
43
- "@nextclaw/openclaw-compat": "^0.1.3"
43
+ "@nextclaw/openclaw-compat": "^0.1.4"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^20.17.6",
@@ -285,8 +285,9 @@ Behavior:
285
285
  - If `NEXTCLAW_UPDATE_COMMAND` is set, the CLI executes it (useful for custom update flows).
286
286
  - Otherwise it falls back to `npm i -g nextclaw`.
287
287
  - If the background service is running, restart it after the update to apply changes.
288
+ - 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).
288
289
 
289
- 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 a restart will be scheduled afterward.
290
+ 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.
290
291
 
291
292
  ---
292
293