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 +332 -67
- package/package.json +2 -2
- package/templates/USAGE.md +1 -0
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
|
|
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
|
|
25
|
-
import { join as join6, resolve as
|
|
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((
|
|
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((
|
|
349
|
-
rl.once("line", (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((
|
|
898
|
-
rl.question(`${question} [y/N] `, (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((
|
|
1723
|
+
return await new Promise((resolve10) => {
|
|
1724
1724
|
const server = createNetServer();
|
|
1725
1725
|
server.once("error", (error) => {
|
|
1726
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1772
|
-
import { join as join4, resolve as
|
|
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
|
|
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 (
|
|
1790
|
-
raw =
|
|
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(
|
|
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
|
|
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 =
|
|
2373
|
-
|
|
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((
|
|
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((
|
|
2738
|
+
await new Promise((resolve10) => setTimeout(resolve10, 200));
|
|
2474
2739
|
continue;
|
|
2475
2740
|
}
|
|
2476
|
-
await new Promise((
|
|
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((
|
|
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
|
|
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
|
|
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
|
|
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 &&
|
|
2853
|
+
if (!force && existsSync7(filePath)) {
|
|
2589
2854
|
continue;
|
|
2590
2855
|
}
|
|
2591
2856
|
const templatePath = join5(templateDir, entry.source);
|
|
2592
|
-
if (!
|
|
2857
|
+
if (!existsSync7(templatePath)) {
|
|
2593
2858
|
console.warn(`Warning: Template file missing: ${templatePath}`);
|
|
2594
2859
|
continue;
|
|
2595
2860
|
}
|
|
2596
|
-
const raw =
|
|
2861
|
+
const raw = readFileSync5(templatePath, "utf-8");
|
|
2597
2862
|
const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME3);
|
|
2598
|
-
|
|
2599
|
-
|
|
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 (!
|
|
2604
|
-
|
|
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 (!
|
|
2609
|
-
|
|
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 (!
|
|
2895
|
+
if (!existsSync7(join5(src, "SKILL.md"))) {
|
|
2631
2896
|
continue;
|
|
2632
2897
|
}
|
|
2633
2898
|
const dest = join5(targetDir, entry.name);
|
|
2634
|
-
if (!force &&
|
|
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 =
|
|
2911
|
+
const pkgRoot = resolve8(dirname(entry), "..");
|
|
2647
2912
|
const distSkills = join5(pkgRoot, "dist", "skills");
|
|
2648
|
-
if (
|
|
2913
|
+
if (existsSync7(distSkills)) {
|
|
2649
2914
|
return distSkills;
|
|
2650
2915
|
}
|
|
2651
2916
|
const srcSkills = join5(pkgRoot, "src", "agent", "skills");
|
|
2652
|
-
if (
|
|
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 =
|
|
2666
|
-
const pkgRoot =
|
|
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 (
|
|
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(
|
|
2677
|
-
if (
|
|
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 =
|
|
2685
|
-
const pkgRoot =
|
|
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 (
|
|
2954
|
+
if (existsSync7(join5(pkgBridge, "package.json"))) {
|
|
2690
2955
|
source = pkgBridge;
|
|
2691
|
-
} else if (
|
|
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
|
-
|
|
2700
|
-
if (
|
|
2701
|
-
|
|
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 =
|
|
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 (!
|
|
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(
|
|
2938
|
-
const workspaceExisted =
|
|
2939
|
-
|
|
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(
|
|
3081
|
-
const historyDir =
|
|
3082
|
-
|
|
3083
|
-
const history =
|
|
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
|
-
|
|
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.
|
|
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.
|
|
41
|
+
"@nextclaw/core": "^0.6.9",
|
|
42
42
|
"@nextclaw/server": "^0.4.2",
|
|
43
43
|
"@nextclaw/openclaw-compat": "^0.1.5"
|
|
44
44
|
},
|
package/templates/USAGE.md
CHANGED
|
@@ -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
|
|