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 +293 -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,58 @@ var ServiceCommands = class {
|
|
|
2300
2480
|
console.warn(`[plugins] ${text}`);
|
|
2301
2481
|
}
|
|
2302
2482
|
}
|
|
2303
|
-
await
|
|
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 =
|
|
2373
|
-
|
|
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((
|
|
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((
|
|
2699
|
+
await new Promise((resolve10) => setTimeout(resolve10, 200));
|
|
2474
2700
|
continue;
|
|
2475
2701
|
}
|
|
2476
|
-
await new Promise((
|
|
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((
|
|
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
|
|
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
|
|
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
|
|
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 &&
|
|
2814
|
+
if (!force && existsSync7(filePath)) {
|
|
2589
2815
|
continue;
|
|
2590
2816
|
}
|
|
2591
2817
|
const templatePath = join5(templateDir, entry.source);
|
|
2592
|
-
if (!
|
|
2818
|
+
if (!existsSync7(templatePath)) {
|
|
2593
2819
|
console.warn(`Warning: Template file missing: ${templatePath}`);
|
|
2594
2820
|
continue;
|
|
2595
2821
|
}
|
|
2596
|
-
const raw =
|
|
2822
|
+
const raw = readFileSync5(templatePath, "utf-8");
|
|
2597
2823
|
const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME3);
|
|
2598
|
-
|
|
2599
|
-
|
|
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 (!
|
|
2604
|
-
|
|
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 (!
|
|
2609
|
-
|
|
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 (!
|
|
2856
|
+
if (!existsSync7(join5(src, "SKILL.md"))) {
|
|
2631
2857
|
continue;
|
|
2632
2858
|
}
|
|
2633
2859
|
const dest = join5(targetDir, entry.name);
|
|
2634
|
-
if (!force &&
|
|
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 =
|
|
2872
|
+
const pkgRoot = resolve8(dirname(entry), "..");
|
|
2647
2873
|
const distSkills = join5(pkgRoot, "dist", "skills");
|
|
2648
|
-
if (
|
|
2874
|
+
if (existsSync7(distSkills)) {
|
|
2649
2875
|
return distSkills;
|
|
2650
2876
|
}
|
|
2651
2877
|
const srcSkills = join5(pkgRoot, "src", "agent", "skills");
|
|
2652
|
-
if (
|
|
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 =
|
|
2666
|
-
const pkgRoot =
|
|
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 (
|
|
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(
|
|
2677
|
-
if (
|
|
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 =
|
|
2685
|
-
const pkgRoot =
|
|
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 (
|
|
2915
|
+
if (existsSync7(join5(pkgBridge, "package.json"))) {
|
|
2690
2916
|
source = pkgBridge;
|
|
2691
|
-
} else if (
|
|
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
|
-
|
|
2700
|
-
if (
|
|
2701
|
-
|
|
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 =
|
|
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 (!
|
|
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(
|
|
2938
|
-
const workspaceExisted =
|
|
2939
|
-
|
|
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(
|
|
3081
|
-
const historyDir =
|
|
3082
|
-
|
|
3083
|
-
const history =
|
|
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
|
-
|
|
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.
|
|
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.
|
|
41
|
+
"@nextclaw/core": "^0.6.8",
|
|
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
|
|