nextclaw 0.13.5 → 0.13.7
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 +642 -579
- package/package.json +9 -8
- package/templates/USAGE.md +15 -5
package/dist/cli/index.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import { APP_NAME as
|
|
5
|
+
import { APP_NAME as APP_NAME6, APP_TAGLINE } from "@nextclaw/core";
|
|
6
|
+
import { registerRemoteCommands } from "@nextclaw/remote";
|
|
6
7
|
|
|
7
8
|
// src/cli/runtime.ts
|
|
8
9
|
import {
|
|
9
|
-
loadConfig as
|
|
10
|
-
saveConfig as
|
|
11
|
-
getConfigPath as
|
|
10
|
+
loadConfig as loadConfig14,
|
|
11
|
+
saveConfig as saveConfig10,
|
|
12
|
+
getConfigPath as getConfigPath7,
|
|
12
13
|
getDataDir as getDataDir9,
|
|
13
14
|
ConfigSchema as ConfigSchema2,
|
|
14
15
|
getWorkspacePath as getWorkspacePath10,
|
|
@@ -17,17 +18,18 @@ import {
|
|
|
17
18
|
AgentLoop,
|
|
18
19
|
ProviderManager as ProviderManager2,
|
|
19
20
|
resolveConfigSecrets as resolveConfigSecrets3,
|
|
20
|
-
APP_NAME as
|
|
21
|
+
APP_NAME as APP_NAME5,
|
|
21
22
|
DEFAULT_WORKSPACE_DIR,
|
|
22
23
|
DEFAULT_WORKSPACE_PATH
|
|
23
24
|
} from "@nextclaw/core";
|
|
25
|
+
import { RemoteRuntimeActions } from "@nextclaw/remote";
|
|
24
26
|
import {
|
|
25
27
|
getPluginChannelBindings as getPluginChannelBindings4,
|
|
26
28
|
resolvePluginChannelMessageToolHints as resolvePluginChannelMessageToolHints2,
|
|
27
29
|
setPluginRuntimeBridge as setPluginRuntimeBridge2
|
|
28
30
|
} from "@nextclaw/openclaw-compat";
|
|
29
|
-
import { existsSync as
|
|
30
|
-
import { join as
|
|
31
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
|
|
32
|
+
import { join as join8, resolve as resolve12 } from "path";
|
|
31
33
|
import { createInterface as createInterface3 } from "readline";
|
|
32
34
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
33
35
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -74,9 +76,9 @@ var RestartCoordinator = class {
|
|
|
74
76
|
message: "Restart already scheduled; skipping duplicate request."
|
|
75
77
|
};
|
|
76
78
|
}
|
|
77
|
-
const
|
|
79
|
+
const delay = typeof request.delayMs === "number" && Number.isFinite(request.delayMs) ? Math.max(0, Math.floor(request.delayMs)) : 100;
|
|
78
80
|
this.exitScheduled = true;
|
|
79
|
-
this.deps.scheduleProcessExit(
|
|
81
|
+
this.deps.scheduleProcessExit(delay, reason);
|
|
80
82
|
return {
|
|
81
83
|
status: "exit-scheduled",
|
|
82
84
|
message: `Restart scheduled (${reason}).`
|
|
@@ -822,6 +824,15 @@ function writeServiceState(state) {
|
|
|
822
824
|
mkdirSync3(resolve4(path2, ".."), { recursive: true });
|
|
823
825
|
writeFileSync3(path2, JSON.stringify(state, null, 2));
|
|
824
826
|
}
|
|
827
|
+
function updateServiceState(updater) {
|
|
828
|
+
const current = readServiceState();
|
|
829
|
+
if (!current) {
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
const next = updater(current);
|
|
833
|
+
writeServiceState(next);
|
|
834
|
+
return next;
|
|
835
|
+
}
|
|
825
836
|
function clearServiceState() {
|
|
826
837
|
const path2 = resolveServiceStatePath();
|
|
827
838
|
if (existsSync4(path2)) {
|
|
@@ -3025,285 +3036,315 @@ var PlatformAuthCommands = class {
|
|
|
3025
3036
|
};
|
|
3026
3037
|
|
|
3027
3038
|
// src/cli/commands/remote.ts
|
|
3039
|
+
import { getConfigPath as getConfigPath4, loadConfig as loadConfig9, saveConfig as saveConfig7 } from "@nextclaw/core";
|
|
3040
|
+
import "@nextclaw/remote";
|
|
3041
|
+
import { hostname } from "os";
|
|
3042
|
+
|
|
3043
|
+
// src/cli/commands/remote-runtime-support.ts
|
|
3028
3044
|
import { getConfigPath as getConfigPath3, getDataDir as getDataDir4, loadConfig as loadConfig8 } from "@nextclaw/core";
|
|
3029
|
-
import {
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3045
|
+
import {
|
|
3046
|
+
RemoteConnector,
|
|
3047
|
+
RemotePlatformClient,
|
|
3048
|
+
RemoteStatusStore,
|
|
3049
|
+
buildConfiguredRemoteState,
|
|
3050
|
+
resolveRemoteStatusSnapshot
|
|
3051
|
+
} from "@nextclaw/remote";
|
|
3052
|
+
function hasRunningNextclawManagedService() {
|
|
3053
|
+
const state = readServiceState();
|
|
3054
|
+
return Boolean(state && isProcessRunning(state.pid));
|
|
3055
|
+
}
|
|
3056
|
+
function createNextclawRemotePlatformClient() {
|
|
3057
|
+
return new RemotePlatformClient({
|
|
3058
|
+
loadConfig: () => loadConfig8(getConfigPath3()),
|
|
3059
|
+
getDataDir: getDataDir4,
|
|
3060
|
+
getPackageVersion,
|
|
3061
|
+
resolvePlatformBase: (rawApiBase) => resolvePlatformApiBase({
|
|
3062
|
+
explicitApiBase: rawApiBase,
|
|
3063
|
+
requireConfigured: true
|
|
3064
|
+
}).platformBase,
|
|
3065
|
+
readManagedServiceState: () => {
|
|
3066
|
+
const state = readServiceState();
|
|
3067
|
+
if (!state) {
|
|
3068
|
+
return null;
|
|
3069
|
+
}
|
|
3070
|
+
return {
|
|
3071
|
+
pid: state.pid,
|
|
3072
|
+
uiPort: state.uiPort
|
|
3073
|
+
};
|
|
3074
|
+
},
|
|
3075
|
+
isProcessRunning
|
|
3076
|
+
});
|
|
3035
3077
|
}
|
|
3036
|
-
function
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3078
|
+
function createNextclawRemoteConnector(params = {}) {
|
|
3079
|
+
return new RemoteConnector({
|
|
3080
|
+
platformClient: createNextclawRemotePlatformClient(),
|
|
3081
|
+
logger: params.logger
|
|
3082
|
+
});
|
|
3041
3083
|
}
|
|
3042
|
-
function
|
|
3043
|
-
return new
|
|
3084
|
+
function createNextclawRemoteStatusStore(mode) {
|
|
3085
|
+
return new RemoteStatusStore(mode, {
|
|
3086
|
+
writeRemoteState: (next) => {
|
|
3087
|
+
updateServiceState((state) => ({
|
|
3088
|
+
...state,
|
|
3089
|
+
remote: next
|
|
3090
|
+
}));
|
|
3091
|
+
}
|
|
3092
|
+
});
|
|
3044
3093
|
}
|
|
3045
|
-
function
|
|
3046
|
-
|
|
3094
|
+
function buildNextclawConfiguredRemoteState(config2) {
|
|
3095
|
+
return buildConfiguredRemoteState(config2);
|
|
3047
3096
|
}
|
|
3048
|
-
function
|
|
3049
|
-
|
|
3050
|
-
return null;
|
|
3051
|
-
}
|
|
3052
|
-
try {
|
|
3053
|
-
return JSON.parse(readFileSync6(path2, "utf-8"));
|
|
3054
|
-
} catch {
|
|
3055
|
-
return null;
|
|
3056
|
-
}
|
|
3097
|
+
function readCurrentNextclawRemoteRuntimeState() {
|
|
3098
|
+
return readServiceState()?.remote ?? null;
|
|
3057
3099
|
}
|
|
3058
|
-
function
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3100
|
+
function resolveNextclawRemoteStatusSnapshot(config2) {
|
|
3101
|
+
return resolveRemoteStatusSnapshot({
|
|
3102
|
+
config: config2,
|
|
3103
|
+
currentRemoteState: readCurrentNextclawRemoteRuntimeState()
|
|
3104
|
+
});
|
|
3062
3105
|
}
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
if (existing?.deviceInstallId?.trim()) {
|
|
3069
|
-
return existing.deviceInstallId.trim();
|
|
3070
|
-
}
|
|
3071
|
-
const deviceInstallId = crypto.randomUUID();
|
|
3072
|
-
ensureDir(this.remoteDir);
|
|
3073
|
-
writeJsonFile(this.devicePath, { deviceInstallId });
|
|
3074
|
-
return deviceInstallId;
|
|
3075
|
-
}
|
|
3076
|
-
resolvePlatformAccess(opts) {
|
|
3077
|
-
const config2 = loadConfig8(getConfigPath3());
|
|
3078
|
-
const providers = config2.providers;
|
|
3079
|
-
const nextclawProvider = providers.nextclaw;
|
|
3080
|
-
const token = typeof nextclawProvider?.apiKey === "string" ? nextclawProvider.apiKey.trim() : "";
|
|
3081
|
-
if (!token) {
|
|
3082
|
-
throw new Error('NextClaw platform token is missing. Run "nextclaw login" first.');
|
|
3083
|
-
}
|
|
3084
|
-
const configuredApiBase = typeof nextclawProvider?.apiBase === "string" ? nextclawProvider.apiBase.trim() : "";
|
|
3085
|
-
const rawApiBase = typeof opts.apiBase === "string" && opts.apiBase.trim().length > 0 ? opts.apiBase.trim() : configuredApiBase;
|
|
3086
|
-
if (!rawApiBase) {
|
|
3087
|
-
throw new Error("Platform API base is missing. Pass --api-base or run nextclaw login.");
|
|
3088
|
-
}
|
|
3089
|
-
const { platformBase } = resolvePlatformApiBase({
|
|
3090
|
-
explicitApiBase: rawApiBase,
|
|
3091
|
-
requireConfigured: true
|
|
3092
|
-
});
|
|
3093
|
-
return { platformBase, token, config: config2 };
|
|
3106
|
+
|
|
3107
|
+
// src/cli/commands/remote.ts
|
|
3108
|
+
function normalizeOptionalString3(value) {
|
|
3109
|
+
if (typeof value !== "string") {
|
|
3110
|
+
return void 0;
|
|
3094
3111
|
}
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
}
|
|
3103
|
-
const configuredPort = typeof config2.ui?.port === "number" && Number.isFinite(config2.ui.port) ? config2.ui.port : 18791;
|
|
3104
|
-
return `http://127.0.0.1:${configuredPort}`;
|
|
3112
|
+
const trimmed = value.trim();
|
|
3113
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
3114
|
+
}
|
|
3115
|
+
function resolveConfiguredLocalOrigin(config2) {
|
|
3116
|
+
const state = readServiceState();
|
|
3117
|
+
if (state && isProcessRunning(state.pid) && Number.isFinite(state.uiPort)) {
|
|
3118
|
+
return `http://127.0.0.1:${state.uiPort}`;
|
|
3105
3119
|
}
|
|
3106
|
-
|
|
3120
|
+
const port = typeof config2.ui.port === "number" && Number.isFinite(config2.ui.port) ? config2.ui.port : 18791;
|
|
3121
|
+
return `http://127.0.0.1:${port}`;
|
|
3122
|
+
}
|
|
3123
|
+
async function probeLocalUi(localOrigin) {
|
|
3124
|
+
try {
|
|
3107
3125
|
const response = await fetch(`${localOrigin}/api/health`);
|
|
3108
3126
|
if (!response.ok) {
|
|
3109
|
-
|
|
3127
|
+
return { ok: false, detail: `health returned ${response.status}` };
|
|
3110
3128
|
}
|
|
3129
|
+
return { ok: true, detail: "health endpoint returned ok" };
|
|
3130
|
+
} catch (error) {
|
|
3131
|
+
return {
|
|
3132
|
+
ok: false,
|
|
3133
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
3134
|
+
};
|
|
3111
3135
|
}
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
}
|
|
3131
|
-
return payload.data.device;
|
|
3136
|
+
}
|
|
3137
|
+
var RemoteCommands = class {
|
|
3138
|
+
enableConfig(opts = {}) {
|
|
3139
|
+
const config2 = loadConfig9(getConfigPath4());
|
|
3140
|
+
const next = {
|
|
3141
|
+
...config2,
|
|
3142
|
+
remote: {
|
|
3143
|
+
...config2.remote,
|
|
3144
|
+
enabled: true,
|
|
3145
|
+
...normalizeOptionalString3(opts.apiBase) ? { platformApiBase: normalizeOptionalString3(opts.apiBase) ?? "" } : {},
|
|
3146
|
+
...normalizeOptionalString3(opts.name) ? { deviceName: normalizeOptionalString3(opts.name) ?? "" } : {}
|
|
3147
|
+
}
|
|
3148
|
+
};
|
|
3149
|
+
saveConfig7(next);
|
|
3150
|
+
return {
|
|
3151
|
+
changed: config2.remote.enabled !== next.remote.enabled || config2.remote.platformApiBase !== next.remote.platformApiBase || config2.remote.deviceName !== next.remote.deviceName,
|
|
3152
|
+
config: next
|
|
3153
|
+
};
|
|
3132
3154
|
}
|
|
3133
|
-
|
|
3134
|
-
const
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3155
|
+
disableConfig() {
|
|
3156
|
+
const config2 = loadConfig9(getConfigPath4());
|
|
3157
|
+
const next = {
|
|
3158
|
+
...config2,
|
|
3159
|
+
remote: {
|
|
3160
|
+
...config2.remote,
|
|
3161
|
+
enabled: false
|
|
3138
3162
|
}
|
|
3163
|
+
};
|
|
3164
|
+
saveConfig7(next);
|
|
3165
|
+
return {
|
|
3166
|
+
changed: config2.remote.enabled !== next.remote.enabled,
|
|
3167
|
+
config: next
|
|
3168
|
+
};
|
|
3169
|
+
}
|
|
3170
|
+
async connect(opts = {}) {
|
|
3171
|
+
const connector = createNextclawRemoteConnector();
|
|
3172
|
+
await connector.run({
|
|
3173
|
+
...opts,
|
|
3174
|
+
mode: "foreground"
|
|
3139
3175
|
});
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
const bridgeCookie = await this.requestBridgeCookie(params.localOrigin);
|
|
3148
|
-
const url = new URL(params.frame.path, params.localOrigin);
|
|
3149
|
-
const headers = new Headers();
|
|
3150
|
-
for (const [key, value] of params.frame.headers) {
|
|
3151
|
-
const lower = key.toLowerCase();
|
|
3152
|
-
if ([
|
|
3153
|
-
"host",
|
|
3154
|
-
"connection",
|
|
3155
|
-
"content-length",
|
|
3156
|
-
"cookie",
|
|
3157
|
-
"x-forwarded-for",
|
|
3158
|
-
"x-forwarded-proto",
|
|
3159
|
-
"cf-connecting-ip"
|
|
3160
|
-
].includes(lower)) {
|
|
3161
|
-
continue;
|
|
3162
|
-
}
|
|
3163
|
-
headers.set(key, value);
|
|
3176
|
+
}
|
|
3177
|
+
async status(opts = {}) {
|
|
3178
|
+
const config2 = loadConfig9(getConfigPath4());
|
|
3179
|
+
const snapshot = resolveNextclawRemoteStatusSnapshot(config2);
|
|
3180
|
+
if (opts.json) {
|
|
3181
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
3182
|
+
return;
|
|
3164
3183
|
}
|
|
3165
|
-
|
|
3166
|
-
|
|
3184
|
+
const runtime2 = snapshot.runtime;
|
|
3185
|
+
console.log("NextClaw Remote Status");
|
|
3186
|
+
console.log(`Enabled: ${snapshot.configuredEnabled ? "yes" : "no"}`);
|
|
3187
|
+
console.log(`Mode: ${runtime2?.mode ?? "service"}`);
|
|
3188
|
+
console.log(`State: ${runtime2?.state ?? "disabled"}`);
|
|
3189
|
+
console.log(`Device: ${runtime2?.deviceName ?? normalizeOptionalString3(config2.remote.deviceName) ?? hostname()}`);
|
|
3190
|
+
console.log(
|
|
3191
|
+
`Platform: ${runtime2?.platformBase ?? normalizeOptionalString3(config2.remote.platformApiBase) ?? normalizeOptionalString3(config2.providers.nextclaw?.apiBase) ?? "not set"}`
|
|
3192
|
+
);
|
|
3193
|
+
console.log(`Local origin: ${runtime2?.localOrigin ?? resolveConfiguredLocalOrigin(config2)}`);
|
|
3194
|
+
if (runtime2?.deviceId) {
|
|
3195
|
+
console.log(`Device ID: ${runtime2.deviceId}`);
|
|
3167
3196
|
}
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
});
|
|
3174
|
-
const responseHeaders = Array.from(response.headers.entries()).filter(([key]) => {
|
|
3175
|
-
const lower = key.toLowerCase();
|
|
3176
|
-
return !["content-length", "connection", "transfer-encoding", "set-cookie"].includes(lower);
|
|
3177
|
-
});
|
|
3178
|
-
const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
3179
|
-
if (response.body && contentType.startsWith("text/event-stream")) {
|
|
3180
|
-
params.socket.send(JSON.stringify({
|
|
3181
|
-
type: "response.start",
|
|
3182
|
-
requestId: params.frame.requestId,
|
|
3183
|
-
status: response.status,
|
|
3184
|
-
headers: responseHeaders
|
|
3185
|
-
}));
|
|
3186
|
-
const reader = response.body.getReader();
|
|
3187
|
-
try {
|
|
3188
|
-
while (true) {
|
|
3189
|
-
const { value, done } = await reader.read();
|
|
3190
|
-
if (done) {
|
|
3191
|
-
break;
|
|
3192
|
-
}
|
|
3193
|
-
if (value && value.length > 0) {
|
|
3194
|
-
params.socket.send(JSON.stringify({
|
|
3195
|
-
type: "response.chunk",
|
|
3196
|
-
requestId: params.frame.requestId,
|
|
3197
|
-
bodyBase64: encodeBase64(value)
|
|
3198
|
-
}));
|
|
3199
|
-
}
|
|
3200
|
-
}
|
|
3201
|
-
} finally {
|
|
3202
|
-
reader.releaseLock();
|
|
3203
|
-
}
|
|
3204
|
-
params.socket.send(JSON.stringify({
|
|
3205
|
-
type: "response.end",
|
|
3206
|
-
requestId: params.frame.requestId
|
|
3207
|
-
}));
|
|
3208
|
-
return;
|
|
3197
|
+
if (runtime2?.lastConnectedAt) {
|
|
3198
|
+
console.log(`Last connected: ${runtime2.lastConnectedAt}`);
|
|
3199
|
+
}
|
|
3200
|
+
if (runtime2?.lastError) {
|
|
3201
|
+
console.log(`Last error: ${runtime2.lastError}`);
|
|
3209
3202
|
}
|
|
3210
|
-
const responseBody = response.body ? new Uint8Array(await response.arrayBuffer()) : new Uint8Array();
|
|
3211
|
-
params.socket.send(JSON.stringify({
|
|
3212
|
-
type: "response",
|
|
3213
|
-
requestId: params.frame.requestId,
|
|
3214
|
-
status: response.status,
|
|
3215
|
-
headers: responseHeaders,
|
|
3216
|
-
bodyBase64: encodeBase64(responseBody)
|
|
3217
|
-
}));
|
|
3218
|
-
}
|
|
3219
|
-
async connectOnce(params) {
|
|
3220
|
-
await new Promise((resolve13, reject) => {
|
|
3221
|
-
const socket = new WebSocket(params.wsUrl);
|
|
3222
|
-
const pingTimer = setInterval(() => {
|
|
3223
|
-
if (socket.readyState === WebSocket.OPEN) {
|
|
3224
|
-
socket.send(JSON.stringify({ type: "ping", at: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
3225
|
-
}
|
|
3226
|
-
}, 15e3);
|
|
3227
|
-
socket.addEventListener("open", () => {
|
|
3228
|
-
console.log(`\u2713 Remote connector connected: ${params.wsUrl}`);
|
|
3229
|
-
});
|
|
3230
|
-
socket.addEventListener("message", (event) => {
|
|
3231
|
-
void (async () => {
|
|
3232
|
-
let frame = null;
|
|
3233
|
-
try {
|
|
3234
|
-
frame = JSON.parse(String(event.data ?? ""));
|
|
3235
|
-
} catch {
|
|
3236
|
-
return;
|
|
3237
|
-
}
|
|
3238
|
-
if (!frame || frame.type !== "request") {
|
|
3239
|
-
return;
|
|
3240
|
-
}
|
|
3241
|
-
try {
|
|
3242
|
-
await this.handleRelayRequest({ frame, localOrigin: params.localOrigin, socket });
|
|
3243
|
-
} catch (error) {
|
|
3244
|
-
socket.send(JSON.stringify({
|
|
3245
|
-
type: "response.error",
|
|
3246
|
-
requestId: frame.requestId,
|
|
3247
|
-
message: error instanceof Error ? error.message : String(error)
|
|
3248
|
-
}));
|
|
3249
|
-
}
|
|
3250
|
-
})();
|
|
3251
|
-
});
|
|
3252
|
-
socket.addEventListener("close", () => {
|
|
3253
|
-
clearInterval(pingTimer);
|
|
3254
|
-
resolve13();
|
|
3255
|
-
});
|
|
3256
|
-
socket.addEventListener("error", () => {
|
|
3257
|
-
clearInterval(pingTimer);
|
|
3258
|
-
reject(new Error("Remote connector websocket failed."));
|
|
3259
|
-
});
|
|
3260
|
-
});
|
|
3261
3203
|
}
|
|
3262
|
-
async
|
|
3263
|
-
const
|
|
3264
|
-
const
|
|
3265
|
-
|
|
3266
|
-
const
|
|
3267
|
-
const
|
|
3268
|
-
const
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
}
|
|
3285
|
-
|
|
3286
|
-
|
|
3204
|
+
async doctor(opts = {}) {
|
|
3205
|
+
const config2 = loadConfig9(getConfigPath4());
|
|
3206
|
+
const snapshot = resolveNextclawRemoteStatusSnapshot(config2);
|
|
3207
|
+
const localOrigin = resolveConfiguredLocalOrigin(config2);
|
|
3208
|
+
const localUi = await probeLocalUi(localOrigin);
|
|
3209
|
+
const token = normalizeOptionalString3(config2.providers.nextclaw?.apiKey);
|
|
3210
|
+
const platformApiBase = normalizeOptionalString3(config2.remote.platformApiBase) ?? normalizeOptionalString3(config2.providers.nextclaw?.apiBase);
|
|
3211
|
+
const checks = [
|
|
3212
|
+
{
|
|
3213
|
+
name: "remote-enabled",
|
|
3214
|
+
ok: snapshot.configuredEnabled,
|
|
3215
|
+
detail: snapshot.configuredEnabled ? "enabled in config" : "disabled in config"
|
|
3216
|
+
},
|
|
3217
|
+
{
|
|
3218
|
+
name: "platform-token",
|
|
3219
|
+
ok: Boolean(token),
|
|
3220
|
+
detail: token ? "token configured" : 'run "nextclaw login" first'
|
|
3221
|
+
},
|
|
3222
|
+
{
|
|
3223
|
+
name: "platform-api-base",
|
|
3224
|
+
ok: Boolean(platformApiBase),
|
|
3225
|
+
detail: platformApiBase ?? "set remote.platformApiBase or login with --api-base"
|
|
3226
|
+
},
|
|
3227
|
+
{
|
|
3228
|
+
name: "local-ui",
|
|
3229
|
+
ok: localUi.ok,
|
|
3230
|
+
detail: `${localOrigin} (${localUi.detail})`
|
|
3231
|
+
},
|
|
3232
|
+
{
|
|
3233
|
+
name: "service-runtime",
|
|
3234
|
+
ok: snapshot.runtime?.state === "connected",
|
|
3235
|
+
detail: snapshot.runtime ? snapshot.runtime.state : "no managed remote runtime detected"
|
|
3287
3236
|
}
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3237
|
+
];
|
|
3238
|
+
if (opts.json) {
|
|
3239
|
+
console.log(JSON.stringify({ generatedAt: (/* @__PURE__ */ new Date()).toISOString(), checks, snapshot }, null, 2));
|
|
3240
|
+
return;
|
|
3241
|
+
}
|
|
3242
|
+
console.log("NextClaw Remote Doctor");
|
|
3243
|
+
for (const check of checks) {
|
|
3244
|
+
console.log(`${check.ok ? "\u2713" : "\u2717"} ${check.name}: ${check.detail}`);
|
|
3245
|
+
}
|
|
3291
3246
|
}
|
|
3292
3247
|
};
|
|
3293
3248
|
|
|
3294
3249
|
// src/cli/commands/diagnostics.ts
|
|
3295
3250
|
import { createServer as createNetServer } from "net";
|
|
3296
|
-
import { existsSync as
|
|
3251
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
3297
3252
|
import { resolve as resolve8 } from "path";
|
|
3298
3253
|
import {
|
|
3299
|
-
APP_NAME,
|
|
3300
|
-
getConfigPath as
|
|
3254
|
+
APP_NAME as APP_NAME2,
|
|
3255
|
+
getConfigPath as getConfigPath5,
|
|
3301
3256
|
getDataDir as getDataDir5,
|
|
3302
3257
|
getWorkspacePath as getWorkspacePath4,
|
|
3303
3258
|
hasSecretRef,
|
|
3304
|
-
loadConfig as
|
|
3259
|
+
loadConfig as loadConfig10
|
|
3305
3260
|
} from "@nextclaw/core";
|
|
3306
3261
|
import { listBuiltinProviders } from "@nextclaw/runtime";
|
|
3262
|
+
|
|
3263
|
+
// src/cli/commands/diagnostics-render.ts
|
|
3264
|
+
import { APP_NAME } from "@nextclaw/core";
|
|
3265
|
+
function printStatusReport(params) {
|
|
3266
|
+
const { logo, report, verbose } = params;
|
|
3267
|
+
console.log(`${logo} ${APP_NAME} Status`);
|
|
3268
|
+
console.log(`Level: ${report.level}`);
|
|
3269
|
+
console.log(`Generated: ${report.generatedAt}`);
|
|
3270
|
+
console.log("");
|
|
3271
|
+
printProcessSection(report);
|
|
3272
|
+
printEndpointSection(report);
|
|
3273
|
+
printProviderSection(report);
|
|
3274
|
+
printTextList("Fix actions", report.fixActions);
|
|
3275
|
+
printTextList("Issues", report.issues);
|
|
3276
|
+
printTextList("Recommendations", report.recommendations);
|
|
3277
|
+
if (verbose && report.logTail.length > 0) {
|
|
3278
|
+
console.log("");
|
|
3279
|
+
console.log("Recent logs:");
|
|
3280
|
+
for (const line of report.logTail) {
|
|
3281
|
+
console.log(line);
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
function printProcessSection(report) {
|
|
3286
|
+
const processLabel = report.process.running ? `running (PID ${report.process.pid})` : report.process.staleState ? "stale-state" : "stopped";
|
|
3287
|
+
console.log(`Process: ${processLabel}`);
|
|
3288
|
+
console.log(`State file: ${report.serviceStatePath} ${report.serviceStateExists ? "\u2713" : "\u2717"}`);
|
|
3289
|
+
if (report.process.startedAt) {
|
|
3290
|
+
console.log(`Started: ${report.process.startedAt}`);
|
|
3291
|
+
}
|
|
3292
|
+
console.log(`Managed health: ${report.health.managed.state} (${report.health.managed.detail})`);
|
|
3293
|
+
if (!report.process.running) {
|
|
3294
|
+
console.log(`Configured health: ${report.health.configured.state} (${report.health.configured.detail})`);
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
function printEndpointSection(report) {
|
|
3298
|
+
console.log(`UI: ${report.endpoints.uiUrl ?? report.endpoints.configuredUiUrl}`);
|
|
3299
|
+
console.log(`API: ${report.endpoints.apiUrl ?? report.endpoints.configuredApiUrl}`);
|
|
3300
|
+
console.log(`Remote: ${report.remote.configuredEnabled ? "enabled" : "disabled"}${report.remote.runtime ? ` (${report.remote.runtime.state})` : ""}`);
|
|
3301
|
+
if (report.remote.runtime?.deviceName) {
|
|
3302
|
+
console.log(`Remote device: ${report.remote.runtime.deviceName}`);
|
|
3303
|
+
}
|
|
3304
|
+
if (report.remote.runtime?.platformBase) {
|
|
3305
|
+
console.log(`Remote platform: ${report.remote.runtime.platformBase}`);
|
|
3306
|
+
}
|
|
3307
|
+
if (report.remote.runtime?.lastError) {
|
|
3308
|
+
console.log(`Remote error: ${report.remote.runtime.lastError}`);
|
|
3309
|
+
}
|
|
3310
|
+
console.log(`Config: ${report.configPath} ${report.configExists ? "\u2713" : "\u2717"}`);
|
|
3311
|
+
console.log(`Workspace: ${report.workspacePath} ${report.workspaceExists ? "\u2713" : "\u2717"}`);
|
|
3312
|
+
console.log(`Model: ${report.model}`);
|
|
3313
|
+
}
|
|
3314
|
+
function printProviderSection(report) {
|
|
3315
|
+
for (const provider of report.providers) {
|
|
3316
|
+
console.log(`${provider.name}: ${provider.configured ? "\u2713" : "not set"}${provider.detail ? ` (${provider.detail})` : ""}`);
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
function printDoctorReport(params) {
|
|
3320
|
+
console.log(`${params.logo} ${APP_NAME} Doctor`);
|
|
3321
|
+
console.log(`Generated: ${params.generatedAt}`);
|
|
3322
|
+
console.log("");
|
|
3323
|
+
for (const check of params.checks) {
|
|
3324
|
+
const icon = check.status === "pass" ? "\u2713" : check.status === "warn" ? "!" : "\u2717";
|
|
3325
|
+
console.log(`${icon} ${check.name}: ${check.detail}`);
|
|
3326
|
+
}
|
|
3327
|
+
printTextList("Recommendations", params.recommendations);
|
|
3328
|
+
if (params.verbose && params.logTail.length > 0) {
|
|
3329
|
+
console.log("");
|
|
3330
|
+
console.log("Recent logs:");
|
|
3331
|
+
for (const line of params.logTail) {
|
|
3332
|
+
console.log(line);
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
function printTextList(title, items) {
|
|
3337
|
+
if (items.length === 0) {
|
|
3338
|
+
return;
|
|
3339
|
+
}
|
|
3340
|
+
console.log("");
|
|
3341
|
+
console.log(`${title}:`);
|
|
3342
|
+
for (const item of items) {
|
|
3343
|
+
console.log(`- ${item}`);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
// src/cli/commands/diagnostics.ts
|
|
3307
3348
|
var DiagnosticsCommands = class {
|
|
3308
3349
|
constructor(deps) {
|
|
3309
3350
|
this.deps = deps;
|
|
@@ -3318,56 +3359,7 @@ var DiagnosticsCommands = class {
|
|
|
3318
3359
|
process.exitCode = 0;
|
|
3319
3360
|
return;
|
|
3320
3361
|
}
|
|
3321
|
-
|
|
3322
|
-
console.log(`Level: ${report.level}`);
|
|
3323
|
-
console.log(`Generated: ${report.generatedAt}`);
|
|
3324
|
-
console.log("");
|
|
3325
|
-
const processLabel = report.process.running ? `running (PID ${report.process.pid})` : report.process.staleState ? "stale-state" : "stopped";
|
|
3326
|
-
console.log(`Process: ${processLabel}`);
|
|
3327
|
-
console.log(`State file: ${report.serviceStatePath} ${report.serviceStateExists ? "\u2713" : "\u2717"}`);
|
|
3328
|
-
if (report.process.startedAt) {
|
|
3329
|
-
console.log(`Started: ${report.process.startedAt}`);
|
|
3330
|
-
}
|
|
3331
|
-
console.log(`Managed health: ${report.health.managed.state} (${report.health.managed.detail})`);
|
|
3332
|
-
if (!report.process.running) {
|
|
3333
|
-
console.log(`Configured health: ${report.health.configured.state} (${report.health.configured.detail})`);
|
|
3334
|
-
}
|
|
3335
|
-
console.log(`UI: ${report.endpoints.uiUrl ?? report.endpoints.configuredUiUrl}`);
|
|
3336
|
-
console.log(`API: ${report.endpoints.apiUrl ?? report.endpoints.configuredApiUrl}`);
|
|
3337
|
-
console.log(`Config: ${report.configPath} ${report.configExists ? "\u2713" : "\u2717"}`);
|
|
3338
|
-
console.log(`Workspace: ${report.workspacePath} ${report.workspaceExists ? "\u2713" : "\u2717"}`);
|
|
3339
|
-
console.log(`Model: ${report.model}`);
|
|
3340
|
-
for (const provider of report.providers) {
|
|
3341
|
-
console.log(`${provider.name}: ${provider.configured ? "\u2713" : "not set"}${provider.detail ? ` (${provider.detail})` : ""}`);
|
|
3342
|
-
}
|
|
3343
|
-
if (report.fixActions.length > 0) {
|
|
3344
|
-
console.log("");
|
|
3345
|
-
console.log("Fix actions:");
|
|
3346
|
-
for (const action of report.fixActions) {
|
|
3347
|
-
console.log(`- ${action}`);
|
|
3348
|
-
}
|
|
3349
|
-
}
|
|
3350
|
-
if (report.issues.length > 0) {
|
|
3351
|
-
console.log("");
|
|
3352
|
-
console.log("Issues:");
|
|
3353
|
-
for (const issue of report.issues) {
|
|
3354
|
-
console.log(`- ${issue}`);
|
|
3355
|
-
}
|
|
3356
|
-
}
|
|
3357
|
-
if (report.recommendations.length > 0) {
|
|
3358
|
-
console.log("");
|
|
3359
|
-
console.log("Recommendations:");
|
|
3360
|
-
for (const recommendation of report.recommendations) {
|
|
3361
|
-
console.log(`- ${recommendation}`);
|
|
3362
|
-
}
|
|
3363
|
-
}
|
|
3364
|
-
if (opts.verbose && report.logTail.length > 0) {
|
|
3365
|
-
console.log("");
|
|
3366
|
-
console.log("Recent logs:");
|
|
3367
|
-
for (const line of report.logTail) {
|
|
3368
|
-
console.log(line);
|
|
3369
|
-
}
|
|
3370
|
-
}
|
|
3362
|
+
printStatusReport({ logo: this.deps.logo, report, verbose: Boolean(opts.verbose) });
|
|
3371
3363
|
process.exitCode = 0;
|
|
3372
3364
|
}
|
|
3373
3365
|
async doctor(opts = {}) {
|
|
@@ -3438,32 +3430,19 @@ var DiagnosticsCommands = class {
|
|
|
3438
3430
|
process.exitCode = exitCode;
|
|
3439
3431
|
return;
|
|
3440
3432
|
}
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
console.log("");
|
|
3450
|
-
console.log("Recommendations:");
|
|
3451
|
-
for (const recommendation of report.recommendations) {
|
|
3452
|
-
console.log(`- ${recommendation}`);
|
|
3453
|
-
}
|
|
3454
|
-
}
|
|
3455
|
-
if (opts.verbose && report.logTail.length > 0) {
|
|
3456
|
-
console.log("");
|
|
3457
|
-
console.log("Recent logs:");
|
|
3458
|
-
for (const line of report.logTail) {
|
|
3459
|
-
console.log(line);
|
|
3460
|
-
}
|
|
3461
|
-
}
|
|
3433
|
+
printDoctorReport({
|
|
3434
|
+
logo: this.deps.logo,
|
|
3435
|
+
generatedAt: report.generatedAt,
|
|
3436
|
+
checks,
|
|
3437
|
+
recommendations: report.recommendations,
|
|
3438
|
+
verbose: Boolean(opts.verbose),
|
|
3439
|
+
logTail: report.logTail
|
|
3440
|
+
});
|
|
3462
3441
|
process.exitCode = exitCode;
|
|
3463
3442
|
}
|
|
3464
3443
|
async collectRuntimeStatus(params) {
|
|
3465
|
-
const configPath =
|
|
3466
|
-
const config2 =
|
|
3444
|
+
const configPath = getConfigPath5();
|
|
3445
|
+
const config2 = loadConfig10();
|
|
3467
3446
|
const workspacePath = getWorkspacePath4(config2.agents.defaults.workspace);
|
|
3468
3447
|
const serviceStatePath = resolve8(getDataDir5(), "run", "service.json");
|
|
3469
3448
|
const fixActions = [];
|
|
@@ -3483,72 +3462,36 @@ var DiagnosticsCommands = class {
|
|
|
3483
3462
|
const managedApiUrl = serviceState?.apiUrl ?? null;
|
|
3484
3463
|
const managedHealth = running && managedApiUrl ? await this.probeApiHealth(`${managedApiUrl}/health`) : { state: "unreachable", detail: "service not running" };
|
|
3485
3464
|
const configuredHealth = await this.probeApiHealth(`${configuredApiUrl}/health`, 900);
|
|
3465
|
+
const remote = resolveNextclawRemoteStatusSnapshot(config2);
|
|
3486
3466
|
const orphanSuspected = !running && configuredHealth.state === "ok";
|
|
3487
|
-
const providers =
|
|
3488
|
-
const provider = config2.providers[spec.name];
|
|
3489
|
-
const apiKeyRefSet = hasSecretRef(config2, `providers.${spec.name}.apiKey`);
|
|
3490
|
-
if (!provider) {
|
|
3491
|
-
return { name: spec.displayName ?? spec.name, configured: false, detail: "missing config" };
|
|
3492
|
-
}
|
|
3493
|
-
if (spec.isLocal) {
|
|
3494
|
-
return {
|
|
3495
|
-
name: spec.displayName ?? spec.name,
|
|
3496
|
-
configured: Boolean(provider.apiBase),
|
|
3497
|
-
detail: provider.apiBase ? provider.apiBase : "apiBase not set"
|
|
3498
|
-
};
|
|
3499
|
-
}
|
|
3500
|
-
return {
|
|
3501
|
-
name: spec.displayName ?? spec.name,
|
|
3502
|
-
configured: Boolean(provider.apiKey) || apiKeyRefSet,
|
|
3503
|
-
detail: provider.apiKey ? "apiKey set" : apiKeyRefSet ? "apiKey ref set" : "apiKey not set"
|
|
3504
|
-
};
|
|
3505
|
-
});
|
|
3467
|
+
const providers = this.listProviderStatuses(config2);
|
|
3506
3468
|
const issues = [];
|
|
3507
3469
|
const recommendations = [];
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
issues
|
|
3518
|
-
recommendations
|
|
3519
|
-
}
|
|
3520
|
-
if (running && managedHealth.state !== "ok") {
|
|
3521
|
-
issues.push(`Managed service health check failed: ${managedHealth.detail}`);
|
|
3522
|
-
recommendations.push(`Check logs at ${serviceState?.logPath ?? resolveServiceLogPath()}.`);
|
|
3523
|
-
}
|
|
3524
|
-
if (running && serviceState?.startupState === "degraded" && managedHealth.state !== "ok") {
|
|
3525
|
-
const startupHint = serviceState.startupLastProbeError ? ` (${serviceState.startupLastProbeError})` : "";
|
|
3526
|
-
issues.push(`Service is in degraded startup state${startupHint}.`);
|
|
3527
|
-
recommendations.push(`Wait and re-check ${APP_NAME} status; if it does not recover, inspect logs and restart.`);
|
|
3528
|
-
}
|
|
3529
|
-
if (!running) {
|
|
3530
|
-
recommendations.push(`Run ${APP_NAME} start to launch the service.`);
|
|
3531
|
-
}
|
|
3532
|
-
if (orphanSuspected) {
|
|
3533
|
-
issues.push("A service appears healthy on configured API endpoint, but state is missing/stale.");
|
|
3534
|
-
recommendations.push("Another process may be occupying the UI port; stop it or use --ui-port with a free port.");
|
|
3535
|
-
}
|
|
3536
|
-
if (!providers.some((provider) => provider.configured)) {
|
|
3537
|
-
recommendations.push("Configure at least one provider API key in UI or config before expecting agent replies.");
|
|
3538
|
-
}
|
|
3470
|
+
this.collectRuntimeIssues({
|
|
3471
|
+
configPath,
|
|
3472
|
+
workspacePath,
|
|
3473
|
+
staleState,
|
|
3474
|
+
running,
|
|
3475
|
+
managedHealth,
|
|
3476
|
+
serviceState,
|
|
3477
|
+
orphanSuspected,
|
|
3478
|
+
providers,
|
|
3479
|
+
issues,
|
|
3480
|
+
recommendations
|
|
3481
|
+
});
|
|
3539
3482
|
const logTail = params.verbose ? this.readLogTail(serviceState?.logPath ?? resolveServiceLogPath(), 25) : [];
|
|
3540
3483
|
const level = running ? managedHealth.state === "ok" ? issues.length > 0 ? "degraded" : "healthy" : "degraded" : "stopped";
|
|
3541
3484
|
const exitCode = 0;
|
|
3542
3485
|
return {
|
|
3543
3486
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3544
3487
|
configPath,
|
|
3545
|
-
configExists:
|
|
3488
|
+
configExists: existsSync6(configPath),
|
|
3546
3489
|
workspacePath,
|
|
3547
|
-
workspaceExists:
|
|
3490
|
+
workspaceExists: existsSync6(workspacePath),
|
|
3548
3491
|
model: config2.agents.defaults.model,
|
|
3549
3492
|
providers,
|
|
3550
3493
|
serviceStatePath,
|
|
3551
|
-
serviceStateExists:
|
|
3494
|
+
serviceStateExists: existsSync6(serviceStatePath),
|
|
3552
3495
|
fixActions,
|
|
3553
3496
|
process: {
|
|
3554
3497
|
managedByState,
|
|
@@ -3571,6 +3514,7 @@ var DiagnosticsCommands = class {
|
|
|
3571
3514
|
issues,
|
|
3572
3515
|
recommendations,
|
|
3573
3516
|
logTail,
|
|
3517
|
+
remote,
|
|
3574
3518
|
level,
|
|
3575
3519
|
exitCode
|
|
3576
3520
|
};
|
|
@@ -3597,12 +3541,66 @@ var DiagnosticsCommands = class {
|
|
|
3597
3541
|
clearTimeout(timer);
|
|
3598
3542
|
}
|
|
3599
3543
|
}
|
|
3544
|
+
listProviderStatuses(config2) {
|
|
3545
|
+
return listBuiltinProviders().map((spec) => {
|
|
3546
|
+
const provider = config2.providers[spec.name];
|
|
3547
|
+
const apiKeyRefSet = hasSecretRef(config2, `providers.${spec.name}.apiKey`);
|
|
3548
|
+
if (!provider) {
|
|
3549
|
+
return { name: spec.displayName ?? spec.name, configured: false, detail: "missing config" };
|
|
3550
|
+
}
|
|
3551
|
+
if (spec.isLocal) {
|
|
3552
|
+
return {
|
|
3553
|
+
name: spec.displayName ?? spec.name,
|
|
3554
|
+
configured: Boolean(provider.apiBase),
|
|
3555
|
+
detail: provider.apiBase ? provider.apiBase : "apiBase not set"
|
|
3556
|
+
};
|
|
3557
|
+
}
|
|
3558
|
+
return {
|
|
3559
|
+
name: spec.displayName ?? spec.name,
|
|
3560
|
+
configured: Boolean(provider.apiKey) || apiKeyRefSet,
|
|
3561
|
+
detail: provider.apiKey ? "apiKey set" : apiKeyRefSet ? "apiKey ref set" : "apiKey not set"
|
|
3562
|
+
};
|
|
3563
|
+
});
|
|
3564
|
+
}
|
|
3565
|
+
collectRuntimeIssues(params) {
|
|
3566
|
+
if (!existsSync6(params.configPath)) {
|
|
3567
|
+
params.issues.push("Config file is missing.");
|
|
3568
|
+
params.recommendations.push(`Run ${APP_NAME2} init to create config files.`);
|
|
3569
|
+
}
|
|
3570
|
+
if (!existsSync6(params.workspacePath)) {
|
|
3571
|
+
params.issues.push("Workspace directory does not exist.");
|
|
3572
|
+
params.recommendations.push(`Run ${APP_NAME2} init to create workspace templates.`);
|
|
3573
|
+
}
|
|
3574
|
+
if (params.staleState) {
|
|
3575
|
+
params.issues.push("Service state is stale (state exists but process is not running).");
|
|
3576
|
+
params.recommendations.push(`Run ${APP_NAME2} status --fix to clean stale state.`);
|
|
3577
|
+
}
|
|
3578
|
+
if (params.running && params.managedHealth.state !== "ok") {
|
|
3579
|
+
params.issues.push(`Managed service health check failed: ${params.managedHealth.detail}`);
|
|
3580
|
+
params.recommendations.push(`Check logs at ${params.serviceState?.logPath ?? resolveServiceLogPath()}.`);
|
|
3581
|
+
}
|
|
3582
|
+
if (params.running && params.serviceState?.startupState === "degraded" && params.managedHealth.state !== "ok") {
|
|
3583
|
+
const startupHint = params.serviceState.startupLastProbeError ? ` (${params.serviceState.startupLastProbeError})` : "";
|
|
3584
|
+
params.issues.push(`Service is in degraded startup state${startupHint}.`);
|
|
3585
|
+
params.recommendations.push(`Wait and re-check ${APP_NAME2} status; if it does not recover, inspect logs and restart.`);
|
|
3586
|
+
}
|
|
3587
|
+
if (!params.running) {
|
|
3588
|
+
params.recommendations.push(`Run ${APP_NAME2} start to launch the service.`);
|
|
3589
|
+
}
|
|
3590
|
+
if (params.orphanSuspected) {
|
|
3591
|
+
params.issues.push("A service appears healthy on configured API endpoint, but state is missing/stale.");
|
|
3592
|
+
params.recommendations.push("Another process may be occupying the UI port; stop it or use --ui-port with a free port.");
|
|
3593
|
+
}
|
|
3594
|
+
if (!params.providers.some((provider) => provider.configured)) {
|
|
3595
|
+
params.recommendations.push("Configure at least one provider API key in UI or config before expecting agent replies.");
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3600
3598
|
readLogTail(path2, maxLines = 25) {
|
|
3601
|
-
if (!
|
|
3599
|
+
if (!existsSync6(path2)) {
|
|
3602
3600
|
return [];
|
|
3603
3601
|
}
|
|
3604
3602
|
try {
|
|
3605
|
-
const lines =
|
|
3603
|
+
const lines = readFileSync6(path2, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
3606
3604
|
if (lines.length <= maxLines) {
|
|
3607
3605
|
return lines;
|
|
3608
3606
|
}
|
|
@@ -3642,8 +3640,8 @@ import {
|
|
|
3642
3640
|
stopPluginChannelGateways as stopPluginChannelGateways2
|
|
3643
3641
|
} from "@nextclaw/openclaw-compat";
|
|
3644
3642
|
import { startUiServer } from "@nextclaw/server";
|
|
3645
|
-
import { appendFileSync, closeSync, cpSync as cpSync2, existsSync as
|
|
3646
|
-
import { dirname as
|
|
3643
|
+
import { appendFileSync, closeSync, cpSync as cpSync2, existsSync as existsSync10, mkdirSync as mkdirSync5, openSync } from "fs";
|
|
3644
|
+
import { dirname as dirname2, join as join6, resolve as resolve10 } from "path";
|
|
3647
3645
|
import { spawn as spawn2 } from "child_process";
|
|
3648
3646
|
import { request as httpRequest } from "http";
|
|
3649
3647
|
import { request as httpsRequest } from "https";
|
|
@@ -3652,7 +3650,7 @@ import chokidar from "chokidar";
|
|
|
3652
3650
|
|
|
3653
3651
|
// src/cli/gateway/controller.ts
|
|
3654
3652
|
import { createHash } from "crypto";
|
|
3655
|
-
import { existsSync as
|
|
3653
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
3656
3654
|
import {
|
|
3657
3655
|
buildConfigSchema,
|
|
3658
3656
|
ConfigSchema,
|
|
@@ -3660,12 +3658,12 @@ import {
|
|
|
3660
3658
|
redactConfigObject
|
|
3661
3659
|
} from "@nextclaw/core";
|
|
3662
3660
|
var hashRaw = (raw) => createHash("sha256").update(raw).digest("hex");
|
|
3663
|
-
var readConfigSnapshot = (
|
|
3664
|
-
const path2 =
|
|
3661
|
+
var readConfigSnapshot = (getConfigPath8) => {
|
|
3662
|
+
const path2 = getConfigPath8();
|
|
3665
3663
|
let raw = "";
|
|
3666
3664
|
let parsed = {};
|
|
3667
|
-
if (
|
|
3668
|
-
raw =
|
|
3665
|
+
if (existsSync7(path2)) {
|
|
3666
|
+
raw = readFileSync7(path2, "utf-8");
|
|
3669
3667
|
try {
|
|
3670
3668
|
parsed = JSON.parse(raw);
|
|
3671
3669
|
} catch {
|
|
@@ -3772,11 +3770,11 @@ var GatewayControllerImpl = class {
|
|
|
3772
3770
|
await this.deps.requestRestart(options);
|
|
3773
3771
|
return;
|
|
3774
3772
|
}
|
|
3775
|
-
const
|
|
3773
|
+
const delay = typeof options?.delayMs === "number" && Number.isFinite(options.delayMs) ? Math.max(0, options.delayMs) : 100;
|
|
3776
3774
|
console.log(`Gateway restart requested via tool${options?.reason ? ` (${options.reason})` : ""}.`);
|
|
3777
3775
|
setTimeout(() => {
|
|
3778
3776
|
process.exit(0);
|
|
3779
|
-
},
|
|
3777
|
+
}, delay);
|
|
3780
3778
|
}
|
|
3781
3779
|
status() {
|
|
3782
3780
|
return {
|
|
@@ -4122,9 +4120,9 @@ var MissingProvider = class extends LLMProvider {
|
|
|
4122
4120
|
};
|
|
4123
4121
|
|
|
4124
4122
|
// src/cli/commands/service-marketplace-installer.ts
|
|
4125
|
-
import { getWorkspacePath as getWorkspacePath5, loadConfig as
|
|
4126
|
-
import { existsSync as
|
|
4127
|
-
import { join as
|
|
4123
|
+
import { getWorkspacePath as getWorkspacePath5, loadConfig as loadConfig12 } from "@nextclaw/core";
|
|
4124
|
+
import { existsSync as existsSync8, rmSync as rmSync4 } from "fs";
|
|
4125
|
+
import { join as join4 } from "path";
|
|
4128
4126
|
|
|
4129
4127
|
// src/cli/commands/service-marketplace-helpers.ts
|
|
4130
4128
|
var containsAbsoluteFsPath = (line) => {
|
|
@@ -4172,7 +4170,7 @@ var buildMarketplaceSkillInstallArgs = (params) => {
|
|
|
4172
4170
|
};
|
|
4173
4171
|
|
|
4174
4172
|
// src/cli/commands/service-mcp-marketplace-ops.ts
|
|
4175
|
-
import { loadConfig as
|
|
4173
|
+
import { loadConfig as loadConfig11, saveConfig as saveConfig8 } from "@nextclaw/core";
|
|
4176
4174
|
import { McpDoctorFacade as McpDoctorFacade2, McpMutationService as McpMutationService2 } from "@nextclaw/mcp";
|
|
4177
4175
|
var ServiceMcpMarketplaceOps = class {
|
|
4178
4176
|
constructor(options) {
|
|
@@ -4240,13 +4238,13 @@ var ServiceMcpMarketplaceOps = class {
|
|
|
4240
4238
|
}
|
|
4241
4239
|
createMutationService() {
|
|
4242
4240
|
return new McpMutationService2({
|
|
4243
|
-
getConfig: () =>
|
|
4244
|
-
saveConfig: (config2) =>
|
|
4241
|
+
getConfig: () => loadConfig11(),
|
|
4242
|
+
saveConfig: (config2) => saveConfig8(config2)
|
|
4245
4243
|
});
|
|
4246
4244
|
}
|
|
4247
4245
|
createDoctorFacade() {
|
|
4248
4246
|
return new McpDoctorFacade2({
|
|
4249
|
-
getConfig: () =>
|
|
4247
|
+
getConfig: () => loadConfig11()
|
|
4250
4248
|
});
|
|
4251
4249
|
}
|
|
4252
4250
|
};
|
|
@@ -4287,7 +4285,7 @@ var ServiceMarketplaceInstaller = class {
|
|
|
4287
4285
|
if (params.kind && params.kind !== "marketplace") {
|
|
4288
4286
|
throw new Error(`Unsupported marketplace skill kind: ${params.kind}`);
|
|
4289
4287
|
}
|
|
4290
|
-
const workspace = getWorkspacePath5(
|
|
4288
|
+
const workspace = getWorkspacePath5(loadConfig12().agents.defaults.workspace);
|
|
4291
4289
|
const args = buildMarketplaceSkillInstallArgs({
|
|
4292
4290
|
slug: params.slug,
|
|
4293
4291
|
workspace,
|
|
@@ -4326,9 +4324,9 @@ var ServiceMarketplaceInstaller = class {
|
|
|
4326
4324
|
return { message: result.message };
|
|
4327
4325
|
}
|
|
4328
4326
|
async uninstallSkill(slug) {
|
|
4329
|
-
const workspace = getWorkspacePath5(
|
|
4330
|
-
const targetDir =
|
|
4331
|
-
if (!
|
|
4327
|
+
const workspace = getWorkspacePath5(loadConfig12().agents.defaults.workspace);
|
|
4328
|
+
const targetDir = join4(workspace, "skills", slug);
|
|
4329
|
+
if (!existsSync8(targetDir)) {
|
|
4332
4330
|
throw new Error(`Skill not installed in workspace: ${slug}`);
|
|
4333
4331
|
}
|
|
4334
4332
|
rmSync4(targetDir, { recursive: true, force: true });
|
|
@@ -4453,6 +4451,35 @@ async function reloadServicePlugins(params) {
|
|
|
4453
4451
|
};
|
|
4454
4452
|
}
|
|
4455
4453
|
|
|
4454
|
+
// src/cli/commands/service-startup-support.ts
|
|
4455
|
+
var pluginGatewayLogger = {
|
|
4456
|
+
info: (message) => console.log(`[plugins] ${message}`),
|
|
4457
|
+
warn: (message) => console.warn(`[plugins] ${message}`),
|
|
4458
|
+
error: (message) => console.error(`[plugins] ${message}`),
|
|
4459
|
+
debug: (message) => console.debug(`[plugins] ${message}`)
|
|
4460
|
+
};
|
|
4461
|
+
function logPluginGatewayDiagnostics(diagnostics) {
|
|
4462
|
+
for (const diag of diagnostics) {
|
|
4463
|
+
const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
|
|
4464
|
+
const text = `${prefix}${diag.message}`;
|
|
4465
|
+
if (diag.level === "error") {
|
|
4466
|
+
console.error(`[plugins] ${text}`);
|
|
4467
|
+
} else {
|
|
4468
|
+
console.warn(`[plugins] ${text}`);
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4472
|
+
async function startGatewaySupportServices(params) {
|
|
4473
|
+
if (params.cronJobs > 0) {
|
|
4474
|
+
console.log(`\u2713 Cron: ${params.cronJobs} scheduled jobs`);
|
|
4475
|
+
}
|
|
4476
|
+
console.log("\u2713 Heartbeat: every 30m");
|
|
4477
|
+
params.remoteModule?.start();
|
|
4478
|
+
params.watchConfigFile();
|
|
4479
|
+
await params.startCron();
|
|
4480
|
+
await params.startHeartbeat();
|
|
4481
|
+
}
|
|
4482
|
+
|
|
4456
4483
|
// src/cli/commands/agent-runtime-pool.ts
|
|
4457
4484
|
import {
|
|
4458
4485
|
NativeAgentEngine,
|
|
@@ -5432,7 +5459,7 @@ function resolveRequestedToolNames(metadata) {
|
|
|
5432
5459
|
)
|
|
5433
5460
|
);
|
|
5434
5461
|
}
|
|
5435
|
-
function
|
|
5462
|
+
function normalizeOptionalString4(value) {
|
|
5436
5463
|
return normalizeString(value) ?? void 0;
|
|
5437
5464
|
}
|
|
5438
5465
|
function readMetadataModel(metadata) {
|
|
@@ -5552,7 +5579,7 @@ var NextclawNcpContextBuilder = class {
|
|
|
5552
5579
|
if (inboundModel) {
|
|
5553
5580
|
session.metadata.preferred_model = inboundModel;
|
|
5554
5581
|
}
|
|
5555
|
-
const effectiveModel =
|
|
5582
|
+
const effectiveModel = normalizeOptionalString4(session.metadata.preferred_model) ?? profile.model;
|
|
5556
5583
|
const clearThinking = requestMetadata.clear_thinking === true || requestMetadata.reset_thinking === true;
|
|
5557
5584
|
if (clearThinking) {
|
|
5558
5585
|
delete session.metadata.preferred_thinking;
|
|
@@ -5569,8 +5596,8 @@ var NextclawNcpContextBuilder = class {
|
|
|
5569
5596
|
model: effectiveModel,
|
|
5570
5597
|
sessionThinkingLevel: parseThinkingLevel(session.metadata.preferred_thinking) ?? null
|
|
5571
5598
|
});
|
|
5572
|
-
const channel =
|
|
5573
|
-
const chatId =
|
|
5599
|
+
const channel = normalizeOptionalString4(requestMetadata.channel) ?? normalizeOptionalString4(session.metadata.last_channel) ?? "ui";
|
|
5600
|
+
const chatId = normalizeOptionalString4(requestMetadata.chatId) ?? normalizeOptionalString4(requestMetadata.chat_id) ?? normalizeOptionalString4(session.metadata.last_to) ?? "web-ui";
|
|
5574
5601
|
session.metadata.last_channel = channel;
|
|
5575
5602
|
session.metadata.last_to = chatId;
|
|
5576
5603
|
const requestedSkillNames = resolveRequestedSkillNames(requestMetadata);
|
|
@@ -6288,15 +6315,68 @@ async function createUiNcpAgent(params) {
|
|
|
6288
6315
|
};
|
|
6289
6316
|
}
|
|
6290
6317
|
|
|
6318
|
+
// src/cli/commands/service-remote-runtime.ts
|
|
6319
|
+
import { RemoteServiceModule } from "@nextclaw/remote";
|
|
6320
|
+
function createManagedRemoteModule(params) {
|
|
6321
|
+
if (!params.config.ui.enabled) {
|
|
6322
|
+
return null;
|
|
6323
|
+
}
|
|
6324
|
+
return new RemoteServiceModule({
|
|
6325
|
+
config: params.config,
|
|
6326
|
+
localOrigin: params.localOrigin,
|
|
6327
|
+
statusStore: createNextclawRemoteStatusStore("service"),
|
|
6328
|
+
createConnector: (logger) => createNextclawRemoteConnector({ logger }),
|
|
6329
|
+
logger: {
|
|
6330
|
+
info: (message) => console.log(`[remote] ${message}`),
|
|
6331
|
+
warn: (message) => console.warn(`[remote] ${message}`),
|
|
6332
|
+
error: (message) => console.error(`[remote] ${message}`)
|
|
6333
|
+
}
|
|
6334
|
+
});
|
|
6335
|
+
}
|
|
6336
|
+
function writeInitialManagedServiceState(params) {
|
|
6337
|
+
writeServiceState({
|
|
6338
|
+
pid: params.snapshot.pid,
|
|
6339
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6340
|
+
uiUrl: params.snapshot.uiUrl,
|
|
6341
|
+
apiUrl: params.snapshot.apiUrl,
|
|
6342
|
+
uiHost: params.snapshot.uiHost,
|
|
6343
|
+
uiPort: params.snapshot.uiPort,
|
|
6344
|
+
logPath: params.snapshot.logPath,
|
|
6345
|
+
startupLastProbeError: null,
|
|
6346
|
+
startupTimeoutMs: params.readinessTimeoutMs,
|
|
6347
|
+
startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6348
|
+
...params.config.remote.enabled ? { remote: buildNextclawConfiguredRemoteState(params.config) } : {}
|
|
6349
|
+
});
|
|
6350
|
+
}
|
|
6351
|
+
function writeReadyManagedServiceState(params) {
|
|
6352
|
+
const currentState = readServiceState();
|
|
6353
|
+
const state = {
|
|
6354
|
+
pid: params.snapshot.pid,
|
|
6355
|
+
startedAt: currentState?.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6356
|
+
uiUrl: params.snapshot.uiUrl,
|
|
6357
|
+
apiUrl: params.snapshot.apiUrl,
|
|
6358
|
+
uiHost: params.snapshot.uiHost,
|
|
6359
|
+
uiPort: params.snapshot.uiPort,
|
|
6360
|
+
logPath: params.snapshot.logPath,
|
|
6361
|
+
startupState: params.readiness.ready ? "ready" : "degraded",
|
|
6362
|
+
startupLastProbeError: params.readiness.lastProbeError,
|
|
6363
|
+
startupTimeoutMs: params.readinessTimeoutMs,
|
|
6364
|
+
startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6365
|
+
...currentState?.remote ? { remote: currentState.remote } : {}
|
|
6366
|
+
};
|
|
6367
|
+
writeServiceState(state);
|
|
6368
|
+
return state;
|
|
6369
|
+
}
|
|
6370
|
+
|
|
6291
6371
|
// src/cli/commands/ui-chat-run-coordinator.ts
|
|
6292
|
-
import { existsSync as
|
|
6293
|
-
import { join as
|
|
6372
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
6373
|
+
import { join as join5 } from "path";
|
|
6294
6374
|
import {
|
|
6295
6375
|
getDataDir as getDataDir6,
|
|
6296
6376
|
parseAgentScopedSessionKey as parseAgentScopedSessionKey2,
|
|
6297
6377
|
safeFilename
|
|
6298
6378
|
} from "@nextclaw/core";
|
|
6299
|
-
var RUNS_DIR =
|
|
6379
|
+
var RUNS_DIR = join5(getDataDir6(), "runs");
|
|
6300
6380
|
var NON_TERMINAL_STATES = /* @__PURE__ */ new Set(["queued", "running"]);
|
|
6301
6381
|
var DEFAULT_SESSION_TYPE = "native";
|
|
6302
6382
|
var SESSION_TYPE_METADATA_KEY = "session_type";
|
|
@@ -6355,7 +6435,7 @@ function hasToolSessionEvent(run) {
|
|
|
6355
6435
|
var UiChatRunCoordinator = class {
|
|
6356
6436
|
constructor(options) {
|
|
6357
6437
|
this.options = options;
|
|
6358
|
-
|
|
6438
|
+
mkdirSync4(RUNS_DIR, { recursive: true });
|
|
6359
6439
|
this.loadPersistedRuns();
|
|
6360
6440
|
}
|
|
6361
6441
|
runs = /* @__PURE__ */ new Map();
|
|
@@ -6822,7 +6902,7 @@ var UiChatRunCoordinator = class {
|
|
|
6822
6902
|
};
|
|
6823
6903
|
}
|
|
6824
6904
|
getRunPath(runId) {
|
|
6825
|
-
return
|
|
6905
|
+
return join5(RUNS_DIR, `${safeFilename(runId)}.json`);
|
|
6826
6906
|
}
|
|
6827
6907
|
persistRun(run) {
|
|
6828
6908
|
const persisted = {
|
|
@@ -6840,20 +6920,20 @@ var UiChatRunCoordinator = class {
|
|
|
6840
6920
|
...typeof run.reply === "string" ? { reply: run.reply } : {},
|
|
6841
6921
|
events: run.events
|
|
6842
6922
|
};
|
|
6843
|
-
|
|
6923
|
+
writeFileSync4(this.getRunPath(run.runId), `${JSON.stringify(persisted, null, 2)}
|
|
6844
6924
|
`);
|
|
6845
6925
|
}
|
|
6846
6926
|
loadPersistedRuns() {
|
|
6847
|
-
if (!
|
|
6927
|
+
if (!existsSync9(RUNS_DIR)) {
|
|
6848
6928
|
return;
|
|
6849
6929
|
}
|
|
6850
6930
|
for (const entry of readdirSync2(RUNS_DIR, { withFileTypes: true })) {
|
|
6851
6931
|
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
6852
6932
|
continue;
|
|
6853
6933
|
}
|
|
6854
|
-
const path2 =
|
|
6934
|
+
const path2 = join5(RUNS_DIR, entry.name);
|
|
6855
6935
|
try {
|
|
6856
|
-
const parsed = JSON.parse(
|
|
6936
|
+
const parsed = JSON.parse(readFileSync8(path2, "utf-8"));
|
|
6857
6937
|
const runId = readOptionalString(parsed.runId);
|
|
6858
6938
|
const sessionKey = readOptionalString(parsed.sessionKey);
|
|
6859
6939
|
if (!runId || !sessionKey) {
|
|
@@ -6908,22 +6988,22 @@ var UiChatRunCoordinator = class {
|
|
|
6908
6988
|
|
|
6909
6989
|
// src/cli/commands/service.ts
|
|
6910
6990
|
var {
|
|
6911
|
-
APP_NAME:
|
|
6991
|
+
APP_NAME: APP_NAME3,
|
|
6912
6992
|
ChannelManager: ChannelManager2,
|
|
6913
6993
|
CronService: CronService2,
|
|
6914
6994
|
getApiBase,
|
|
6915
|
-
getConfigPath:
|
|
6995
|
+
getConfigPath: getConfigPath6,
|
|
6916
6996
|
getDataDir: getDataDir7,
|
|
6917
6997
|
getProvider,
|
|
6918
6998
|
getProviderName,
|
|
6919
6999
|
getWorkspacePath: getWorkspacePath9,
|
|
6920
7000
|
HeartbeatService,
|
|
6921
7001
|
LiteLLMProvider,
|
|
6922
|
-
loadConfig:
|
|
7002
|
+
loadConfig: loadConfig13,
|
|
6923
7003
|
MessageBus,
|
|
6924
7004
|
ProviderManager,
|
|
6925
7005
|
resolveConfigSecrets: resolveConfigSecrets2,
|
|
6926
|
-
saveConfig:
|
|
7006
|
+
saveConfig: saveConfig9,
|
|
6927
7007
|
SessionManager,
|
|
6928
7008
|
parseAgentScopedSessionKey: parseAgentScopedSessionKey3
|
|
6929
7009
|
} = NextclawCore;
|
|
@@ -6943,8 +7023,8 @@ var ServiceCommands = class {
|
|
|
6943
7023
|
async startGateway(options = {}) {
|
|
6944
7024
|
this.applyLiveConfigReload = null;
|
|
6945
7025
|
this.liveUiNcpAgent = null;
|
|
6946
|
-
const runtimeConfigPath =
|
|
6947
|
-
const config2 = resolveConfigSecrets2(
|
|
7026
|
+
const runtimeConfigPath = getConfigPath6();
|
|
7027
|
+
const config2 = resolveConfigSecrets2(loadConfig13(), { configPath: runtimeConfigPath });
|
|
6948
7028
|
const workspace = getWorkspacePath9(config2.agents.defaults.workspace);
|
|
6949
7029
|
let pluginRegistry = loadPluginRegistry(config2, workspace);
|
|
6950
7030
|
let extensionRegistry = toExtensionRegistry(pluginRegistry);
|
|
@@ -6957,27 +7037,11 @@ var ServiceCommands = class {
|
|
|
6957
7037
|
});
|
|
6958
7038
|
const sessionManager = new SessionManager(workspace);
|
|
6959
7039
|
let pluginGatewayHandles = [];
|
|
6960
|
-
const
|
|
6961
|
-
info: (message) => console.log(`[plugins] ${message}`),
|
|
6962
|
-
warn: (message) => console.warn(`[plugins] ${message}`),
|
|
6963
|
-
error: (message) => console.error(`[plugins] ${message}`),
|
|
6964
|
-
debug: (message) => console.debug(`[plugins] ${message}`)
|
|
6965
|
-
};
|
|
6966
|
-
const logPluginGatewayDiagnostics = (diagnostics) => {
|
|
6967
|
-
for (const diag of diagnostics) {
|
|
6968
|
-
const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
|
|
6969
|
-
const text = `${prefix}${diag.message}`;
|
|
6970
|
-
if (diag.level === "error") {
|
|
6971
|
-
console.error(`[plugins] ${text}`);
|
|
6972
|
-
} else {
|
|
6973
|
-
console.warn(`[plugins] ${text}`);
|
|
6974
|
-
}
|
|
6975
|
-
}
|
|
6976
|
-
};
|
|
6977
|
-
const cronStorePath = join7(getDataDir7(), "cron", "jobs.json");
|
|
7040
|
+
const cronStorePath = join6(getDataDir7(), "cron", "jobs.json");
|
|
6978
7041
|
const cron2 = new CronService2(cronStorePath);
|
|
6979
7042
|
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
6980
7043
|
const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
|
|
7044
|
+
const localOrigin = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
6981
7045
|
if (!provider) {
|
|
6982
7046
|
console.warn("Warning: No API key configured. The gateway is running, but agent replies are disabled until provider config is set.");
|
|
6983
7047
|
}
|
|
@@ -6989,7 +7053,7 @@ var ServiceCommands = class {
|
|
|
6989
7053
|
sessionManager,
|
|
6990
7054
|
providerManager,
|
|
6991
7055
|
makeProvider: (nextConfig) => this.makeProvider(nextConfig, { allowMissing: true }) ?? this.makeMissingProvider(nextConfig),
|
|
6992
|
-
loadConfig: () => resolveConfigSecrets2(
|
|
7056
|
+
loadConfig: () => resolveConfigSecrets2(loadConfig13(), { configPath: runtimeConfigPath }),
|
|
6993
7057
|
getExtensionChannels: () => extensionRegistry.channels,
|
|
6994
7058
|
onRestartRequired: (paths) => {
|
|
6995
7059
|
void this.deps.requestRestart({
|
|
@@ -7000,14 +7064,14 @@ var ServiceCommands = class {
|
|
|
7000
7064
|
}
|
|
7001
7065
|
});
|
|
7002
7066
|
this.applyLiveConfigReload = async () => {
|
|
7003
|
-
await reloader.applyReloadPlan(resolveConfigSecrets2(
|
|
7067
|
+
await reloader.applyReloadPlan(resolveConfigSecrets2(loadConfig13(), { configPath: runtimeConfigPath }));
|
|
7004
7068
|
};
|
|
7005
7069
|
const gatewayController = new GatewayControllerImpl({
|
|
7006
7070
|
reloader,
|
|
7007
7071
|
cron: cron2,
|
|
7008
7072
|
sessionManager,
|
|
7009
|
-
getConfigPath:
|
|
7010
|
-
saveConfig:
|
|
7073
|
+
getConfigPath: getConfigPath6,
|
|
7074
|
+
saveConfig: saveConfig9,
|
|
7011
7075
|
requestRestart: async (options2) => {
|
|
7012
7076
|
await this.deps.requestRestart({
|
|
7013
7077
|
reason: options2?.reason ?? "gateway tool restart",
|
|
@@ -7033,7 +7097,7 @@ var ServiceCommands = class {
|
|
|
7033
7097
|
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
7034
7098
|
registry: pluginRegistry,
|
|
7035
7099
|
channel,
|
|
7036
|
-
cfg: resolveConfigSecrets2(
|
|
7100
|
+
cfg: resolveConfigSecrets2(loadConfig13(), { configPath: runtimeConfigPath }),
|
|
7037
7101
|
accountId
|
|
7038
7102
|
})
|
|
7039
7103
|
});
|
|
@@ -7066,14 +7130,14 @@ var ServiceCommands = class {
|
|
|
7066
7130
|
});
|
|
7067
7131
|
let pluginChannelBindings = getPluginChannelBindings3(pluginRegistry);
|
|
7068
7132
|
setPluginRuntimeBridge({
|
|
7069
|
-
loadConfig: () => toPluginConfigView(resolveConfigSecrets2(
|
|
7133
|
+
loadConfig: () => toPluginConfigView(resolveConfigSecrets2(loadConfig13(), { configPath: runtimeConfigPath }), pluginChannelBindings),
|
|
7070
7134
|
writeConfigFile: async (nextConfigView) => {
|
|
7071
7135
|
if (!nextConfigView || typeof nextConfigView !== "object" || Array.isArray(nextConfigView)) {
|
|
7072
7136
|
throw new Error("plugin runtime writeConfigFile expects an object config");
|
|
7073
7137
|
}
|
|
7074
|
-
const current =
|
|
7138
|
+
const current = loadConfig13();
|
|
7075
7139
|
const next = mergePluginConfigView(current, nextConfigView, pluginChannelBindings);
|
|
7076
|
-
|
|
7140
|
+
saveConfig9(next);
|
|
7077
7141
|
},
|
|
7078
7142
|
dispatchReplyWithBufferedBlockDispatcher: async ({ ctx, dispatcherOptions }) => {
|
|
7079
7143
|
const bodyForAgent = typeof ctx.BodyForAgent === "string" ? ctx.BodyForAgent : "";
|
|
@@ -7147,23 +7211,23 @@ var ServiceCommands = class {
|
|
|
7147
7211
|
providerManager,
|
|
7148
7212
|
bus,
|
|
7149
7213
|
gatewayController,
|
|
7150
|
-
() => resolveConfigSecrets2(
|
|
7214
|
+
() => resolveConfigSecrets2(loadConfig13(), { configPath: runtimeConfigPath }),
|
|
7151
7215
|
() => extensionRegistry,
|
|
7152
7216
|
({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
7153
7217
|
registry: pluginRegistry,
|
|
7154
7218
|
channel,
|
|
7155
|
-
cfg: resolveConfigSecrets2(
|
|
7219
|
+
cfg: resolveConfigSecrets2(loadConfig13(), { configPath: runtimeConfigPath }),
|
|
7156
7220
|
accountId
|
|
7157
7221
|
})
|
|
7158
7222
|
);
|
|
7159
|
-
const
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7223
|
+
const remoteModule = createManagedRemoteModule({ config: config2, localOrigin });
|
|
7224
|
+
await startGatewaySupportServices({
|
|
7225
|
+
cronJobs: cron2.status().jobs,
|
|
7226
|
+
remoteModule,
|
|
7227
|
+
watchConfigFile: () => this.watchConfigFile(reloader),
|
|
7228
|
+
startCron: () => cron2.start(),
|
|
7229
|
+
startHeartbeat: () => heartbeat.start()
|
|
7230
|
+
});
|
|
7167
7231
|
try {
|
|
7168
7232
|
const startedPluginGateways = await startPluginChannelGateways2({
|
|
7169
7233
|
registry: pluginRegistry,
|
|
@@ -7177,6 +7241,7 @@ var ServiceCommands = class {
|
|
|
7177
7241
|
} finally {
|
|
7178
7242
|
this.applyLiveConfigReload = null;
|
|
7179
7243
|
this.liveUiNcpAgent = null;
|
|
7244
|
+
await remoteModule?.stop();
|
|
7180
7245
|
await stopPluginChannelGateways2(pluginGatewayHandles);
|
|
7181
7246
|
setPluginRuntimeBridge(null);
|
|
7182
7247
|
}
|
|
@@ -7189,7 +7254,7 @@ var ServiceCommands = class {
|
|
|
7189
7254
|
return trimmed || void 0;
|
|
7190
7255
|
}
|
|
7191
7256
|
watchConfigFile(reloader) {
|
|
7192
|
-
const configPath = resolve10(
|
|
7257
|
+
const configPath = resolve10(getConfigPath6());
|
|
7193
7258
|
const watcher = chokidar.watch(configPath, {
|
|
7194
7259
|
ignoreInitial: true,
|
|
7195
7260
|
awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
|
|
@@ -7310,7 +7375,7 @@ var ServiceCommands = class {
|
|
|
7310
7375
|
});
|
|
7311
7376
|
}
|
|
7312
7377
|
async runForeground(options) {
|
|
7313
|
-
const config2 =
|
|
7378
|
+
const config2 = loadConfig13();
|
|
7314
7379
|
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
7315
7380
|
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
7316
7381
|
if (options.open) {
|
|
@@ -7323,14 +7388,14 @@ var ServiceCommands = class {
|
|
|
7323
7388
|
});
|
|
7324
7389
|
}
|
|
7325
7390
|
async startService(options) {
|
|
7326
|
-
const config2 =
|
|
7391
|
+
const config2 = loadConfig13();
|
|
7327
7392
|
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
7328
7393
|
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
7329
7394
|
const apiUrl = `${uiUrl}/api`;
|
|
7330
7395
|
const staticDir = resolveUiStaticDir();
|
|
7331
7396
|
const existing = readServiceState();
|
|
7332
7397
|
if (existing && isProcessRunning(existing.pid)) {
|
|
7333
|
-
console.log(`\u2713 ${
|
|
7398
|
+
console.log(`\u2713 ${APP_NAME3} is already running (PID ${existing.pid})`);
|
|
7334
7399
|
console.log(`UI: ${existing.uiUrl}`);
|
|
7335
7400
|
console.log(`API: ${existing.apiUrl}`);
|
|
7336
7401
|
const parsedUi = (() => {
|
|
@@ -7378,13 +7443,13 @@ var ServiceCommands = class {
|
|
|
7378
7443
|
healthUrl
|
|
7379
7444
|
});
|
|
7380
7445
|
if (!portPreflight.ok) {
|
|
7381
|
-
console.error(`Error: Cannot start ${
|
|
7446
|
+
console.error(`Error: Cannot start ${APP_NAME3} because UI port ${uiConfig.port} is already occupied.`);
|
|
7382
7447
|
console.error(portPreflight.message);
|
|
7383
7448
|
return;
|
|
7384
7449
|
}
|
|
7385
7450
|
const logPath = resolveServiceLogPath();
|
|
7386
7451
|
const logDir = resolve10(logPath, "..");
|
|
7387
|
-
|
|
7452
|
+
mkdirSync5(logDir, { recursive: true });
|
|
7388
7453
|
const logFd = openSync(logPath, "a");
|
|
7389
7454
|
const readinessTimeoutMs = this.resolveStartupTimeoutMs(options.startupTimeoutMs);
|
|
7390
7455
|
const quickPhaseTimeoutMs = Math.min(8e3, readinessTimeoutMs);
|
|
@@ -7393,10 +7458,8 @@ var ServiceCommands = class {
|
|
|
7393
7458
|
logPath,
|
|
7394
7459
|
`start requested: ui=${uiConfig.host}:${uiConfig.port}, readinessTimeoutMs=${readinessTimeoutMs}`
|
|
7395
7460
|
);
|
|
7396
|
-
console.log(`Starting ${
|
|
7397
|
-
const serveArgs = buildServeArgs({
|
|
7398
|
-
uiPort: uiConfig.port
|
|
7399
|
-
});
|
|
7461
|
+
console.log(`Starting ${APP_NAME3} background service (readiness timeout ${Math.ceil(readinessTimeoutMs / 1e3)}s)...`);
|
|
7462
|
+
const serveArgs = buildServeArgs({ uiPort: uiConfig.port });
|
|
7400
7463
|
this.appendStartupStage(logPath, `spawning background process: ${process.execPath} ${[...process.execArgv, ...serveArgs].join(" ")}`);
|
|
7401
7464
|
const child = spawn2(process.execPath, [...process.execArgv, ...serveArgs], {
|
|
7402
7465
|
env: process.env,
|
|
@@ -7417,6 +7480,11 @@ var ServiceCommands = class {
|
|
|
7417
7480
|
});
|
|
7418
7481
|
return;
|
|
7419
7482
|
}
|
|
7483
|
+
writeInitialManagedServiceState({
|
|
7484
|
+
config: config2,
|
|
7485
|
+
readinessTimeoutMs,
|
|
7486
|
+
snapshot: { pid: child.pid, uiUrl, apiUrl, uiHost: uiConfig.host, uiPort: uiConfig.port, logPath }
|
|
7487
|
+
});
|
|
7420
7488
|
this.appendStartupStage(logPath, `health probe started: ${healthUrl} (phase=quick, timeoutMs=${quickPhaseTimeoutMs})`);
|
|
7421
7489
|
let readiness = await this.waitForBackgroundServiceReady({
|
|
7422
7490
|
pid: child.pid,
|
|
@@ -7458,28 +7526,19 @@ var ServiceCommands = class {
|
|
|
7458
7526
|
);
|
|
7459
7527
|
}
|
|
7460
7528
|
child.unref();
|
|
7461
|
-
const state = {
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
uiUrl,
|
|
7465
|
-
|
|
7466
|
-
uiHost: uiConfig.host,
|
|
7467
|
-
uiPort: uiConfig.port,
|
|
7468
|
-
logPath,
|
|
7469
|
-
startupState: readiness.ready ? "ready" : "degraded",
|
|
7470
|
-
startupLastProbeError: readiness.lastProbeError,
|
|
7471
|
-
startupTimeoutMs: readinessTimeoutMs,
|
|
7472
|
-
startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7473
|
-
};
|
|
7474
|
-
writeServiceState(state);
|
|
7529
|
+
const state = writeReadyManagedServiceState({
|
|
7530
|
+
readinessTimeoutMs,
|
|
7531
|
+
readiness,
|
|
7532
|
+
snapshot: { pid: child.pid, uiUrl, apiUrl, uiHost: uiConfig.host, uiPort: uiConfig.port, logPath }
|
|
7533
|
+
});
|
|
7475
7534
|
if (!readiness.ready) {
|
|
7476
7535
|
const hint = readiness.lastProbeError ? ` Last probe error: ${readiness.lastProbeError}` : "";
|
|
7477
7536
|
console.warn(
|
|
7478
|
-
`Warning: ${
|
|
7537
|
+
`Warning: ${APP_NAME3} is running (PID ${state.pid}) but not healthy yet after ${Math.ceil(readinessTimeoutMs / 1e3)}s. Marked as degraded.${hint}`
|
|
7479
7538
|
);
|
|
7480
|
-
console.warn(`Tip: Run "${
|
|
7539
|
+
console.warn(`Tip: Run "${APP_NAME3} status --json" and check logs: ${logPath}`);
|
|
7481
7540
|
} else {
|
|
7482
|
-
console.log(`\u2713 ${
|
|
7541
|
+
console.log(`\u2713 ${APP_NAME3} started in background (PID ${state.pid})`);
|
|
7483
7542
|
}
|
|
7484
7543
|
console.log(`UI: ${uiUrl}`);
|
|
7485
7544
|
console.log(`API: ${apiUrl}`);
|
|
@@ -7501,7 +7560,7 @@ var ServiceCommands = class {
|
|
|
7501
7560
|
clearServiceState();
|
|
7502
7561
|
return;
|
|
7503
7562
|
}
|
|
7504
|
-
console.log(`Stopping ${
|
|
7563
|
+
console.log(`Stopping ${APP_NAME3} (PID ${state.pid})...`);
|
|
7505
7564
|
try {
|
|
7506
7565
|
process.kill(state.pid, "SIGTERM");
|
|
7507
7566
|
} catch (error) {
|
|
@@ -7519,7 +7578,7 @@ var ServiceCommands = class {
|
|
|
7519
7578
|
await waitForExit(state.pid, 2e3);
|
|
7520
7579
|
}
|
|
7521
7580
|
clearServiceState();
|
|
7522
|
-
console.log(`\u2713 ${
|
|
7581
|
+
console.log(`\u2713 ${APP_NAME3} stopped`);
|
|
7523
7582
|
}
|
|
7524
7583
|
async waitForBackgroundServiceReady(params) {
|
|
7525
7584
|
const startedAt = Date.now();
|
|
@@ -7737,7 +7796,7 @@ var ServiceCommands = class {
|
|
|
7737
7796
|
return null;
|
|
7738
7797
|
}
|
|
7739
7798
|
console.error("Error: No API key configured.");
|
|
7740
|
-
console.error(`Set one in ${
|
|
7799
|
+
console.error(`Set one in ${getConfigPath6()} under providers section`);
|
|
7741
7800
|
process.exit(1);
|
|
7742
7801
|
}
|
|
7743
7802
|
return new LiteLLMProvider({
|
|
@@ -7774,8 +7833,8 @@ var ServiceCommands = class {
|
|
|
7774
7833
|
}
|
|
7775
7834
|
printServiceControlHints() {
|
|
7776
7835
|
console.log("Service controls:");
|
|
7777
|
-
console.log(` - Check status: ${
|
|
7778
|
-
console.log(` - If you need to stop the service, run: ${
|
|
7836
|
+
console.log(` - Check status: ${APP_NAME3} status`);
|
|
7837
|
+
console.log(` - If you need to stop the service, run: ${APP_NAME3} stop`);
|
|
7779
7838
|
}
|
|
7780
7839
|
async startUiIfEnabled(uiConfig, uiStaticDir, cronService, runtimePool, sessionManager, providerManager, bus, gatewayController, getConfig, getExtensionRegistry, resolveMessageToolHints) {
|
|
7781
7840
|
if (!uiConfig.enabled) {
|
|
@@ -7864,7 +7923,7 @@ var ServiceCommands = class {
|
|
|
7864
7923
|
const uiServer = startUiServer({
|
|
7865
7924
|
host: uiConfig.host,
|
|
7866
7925
|
port: uiConfig.port,
|
|
7867
|
-
configPath:
|
|
7926
|
+
configPath: getConfigPath6(),
|
|
7868
7927
|
productVersion: getPackageVersion(),
|
|
7869
7928
|
staticDir: uiStaticDir ?? void 0,
|
|
7870
7929
|
cronService,
|
|
@@ -7952,10 +8011,10 @@ var ServiceCommands = class {
|
|
|
7952
8011
|
}
|
|
7953
8012
|
}
|
|
7954
8013
|
installBuiltinMarketplaceSkill(slug, force) {
|
|
7955
|
-
const workspace = getWorkspacePath9(
|
|
7956
|
-
const destination =
|
|
7957
|
-
const destinationSkillFile =
|
|
7958
|
-
if (
|
|
8014
|
+
const workspace = getWorkspacePath9(loadConfig13().agents.defaults.workspace);
|
|
8015
|
+
const destination = join6(workspace, "skills", slug);
|
|
8016
|
+
const destinationSkillFile = join6(destination, "SKILL.md");
|
|
8017
|
+
if (existsSync10(destinationSkillFile) && !force) {
|
|
7959
8018
|
return {
|
|
7960
8019
|
message: `${slug} is already installed`
|
|
7961
8020
|
};
|
|
@@ -7963,15 +8022,15 @@ var ServiceCommands = class {
|
|
|
7963
8022
|
const loader = createSkillsLoader(workspace);
|
|
7964
8023
|
const builtin = (loader?.listSkills(false) ?? []).find((skill) => skill.name === slug && skill.source === "builtin");
|
|
7965
8024
|
if (!builtin) {
|
|
7966
|
-
if (
|
|
8025
|
+
if (existsSync10(destinationSkillFile)) {
|
|
7967
8026
|
return {
|
|
7968
8027
|
message: `${slug} is already installed`
|
|
7969
8028
|
};
|
|
7970
8029
|
}
|
|
7971
8030
|
return null;
|
|
7972
8031
|
}
|
|
7973
|
-
|
|
7974
|
-
cpSync2(
|
|
8032
|
+
mkdirSync5(join6(workspace, "skills"), { recursive: true });
|
|
8033
|
+
cpSync2(dirname2(builtin.path), destination, { recursive: true, force: true });
|
|
7975
8034
|
return {
|
|
7976
8035
|
message: `Installed skill: ${slug}`
|
|
7977
8036
|
};
|
|
@@ -8030,11 +8089,11 @@ ${stderr}`.trim();
|
|
|
8030
8089
|
};
|
|
8031
8090
|
|
|
8032
8091
|
// src/cli/workspace.ts
|
|
8033
|
-
import { cpSync as cpSync3, existsSync as
|
|
8092
|
+
import { cpSync as cpSync3, existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync9, readdirSync as readdirSync3, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
8034
8093
|
import { createRequire as createRequire2 } from "module";
|
|
8035
|
-
import { dirname as
|
|
8094
|
+
import { dirname as dirname3, join as join7, resolve as resolve11 } from "path";
|
|
8036
8095
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8037
|
-
import { APP_NAME as
|
|
8096
|
+
import { APP_NAME as APP_NAME4, getDataDir as getDataDir8 } from "@nextclaw/core";
|
|
8038
8097
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
8039
8098
|
var WorkspaceManager = class {
|
|
8040
8099
|
constructor(logo) {
|
|
@@ -8062,30 +8121,30 @@ var WorkspaceManager = class {
|
|
|
8062
8121
|
{ source: "memory/MEMORY.md", target: "memory/MEMORY.md" }
|
|
8063
8122
|
];
|
|
8064
8123
|
for (const entry of templateFiles) {
|
|
8065
|
-
const filePath =
|
|
8066
|
-
if (!force &&
|
|
8124
|
+
const filePath = join7(workspace, entry.target);
|
|
8125
|
+
if (!force && existsSync11(filePath)) {
|
|
8067
8126
|
continue;
|
|
8068
8127
|
}
|
|
8069
|
-
const templatePath =
|
|
8070
|
-
if (!
|
|
8128
|
+
const templatePath = join7(templateDir, entry.source);
|
|
8129
|
+
if (!existsSync11(templatePath)) {
|
|
8071
8130
|
console.warn(`Warning: Template file missing: ${templatePath}`);
|
|
8072
8131
|
continue;
|
|
8073
8132
|
}
|
|
8074
|
-
const raw =
|
|
8075
|
-
const content = raw.replace(/\$\{APP_NAME\}/g,
|
|
8076
|
-
|
|
8077
|
-
|
|
8133
|
+
const raw = readFileSync9(templatePath, "utf-8");
|
|
8134
|
+
const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME4);
|
|
8135
|
+
mkdirSync6(dirname3(filePath), { recursive: true });
|
|
8136
|
+
writeFileSync5(filePath, content);
|
|
8078
8137
|
created.push(entry.target);
|
|
8079
8138
|
}
|
|
8080
|
-
const memoryDir =
|
|
8081
|
-
if (!
|
|
8082
|
-
|
|
8083
|
-
created.push(
|
|
8139
|
+
const memoryDir = join7(workspace, "memory");
|
|
8140
|
+
if (!existsSync11(memoryDir)) {
|
|
8141
|
+
mkdirSync6(memoryDir, { recursive: true });
|
|
8142
|
+
created.push(join7("memory", ""));
|
|
8084
8143
|
}
|
|
8085
|
-
const skillsDir =
|
|
8086
|
-
if (!
|
|
8087
|
-
|
|
8088
|
-
created.push(
|
|
8144
|
+
const skillsDir = join7(workspace, "skills");
|
|
8145
|
+
if (!existsSync11(skillsDir)) {
|
|
8146
|
+
mkdirSync6(skillsDir, { recursive: true });
|
|
8147
|
+
created.push(join7("skills", ""));
|
|
8089
8148
|
}
|
|
8090
8149
|
const seeded = this.seedBuiltinSkills(skillsDir, { force });
|
|
8091
8150
|
if (seeded > 0) {
|
|
@@ -8104,12 +8163,12 @@ var WorkspaceManager = class {
|
|
|
8104
8163
|
if (!entry.isDirectory()) {
|
|
8105
8164
|
continue;
|
|
8106
8165
|
}
|
|
8107
|
-
const src =
|
|
8108
|
-
if (!
|
|
8166
|
+
const src = join7(sourceDir, entry.name);
|
|
8167
|
+
if (!existsSync11(join7(src, "SKILL.md"))) {
|
|
8109
8168
|
continue;
|
|
8110
8169
|
}
|
|
8111
|
-
const dest =
|
|
8112
|
-
if (!force &&
|
|
8170
|
+
const dest = join7(targetDir, entry.name);
|
|
8171
|
+
if (!force && existsSync11(dest)) {
|
|
8113
8172
|
continue;
|
|
8114
8173
|
}
|
|
8115
8174
|
try {
|
|
@@ -8126,13 +8185,13 @@ var WorkspaceManager = class {
|
|
|
8126
8185
|
try {
|
|
8127
8186
|
const require3 = createRequire2(import.meta.url);
|
|
8128
8187
|
const entry = require3.resolve("@nextclaw/core");
|
|
8129
|
-
const pkgRoot = resolve11(
|
|
8130
|
-
const distSkills =
|
|
8131
|
-
if (
|
|
8188
|
+
const pkgRoot = resolve11(dirname3(entry), "..");
|
|
8189
|
+
const distSkills = join7(pkgRoot, "dist", "skills");
|
|
8190
|
+
if (existsSync11(distSkills)) {
|
|
8132
8191
|
return distSkills;
|
|
8133
8192
|
}
|
|
8134
|
-
const srcSkills =
|
|
8135
|
-
if (
|
|
8193
|
+
const srcSkills = join7(pkgRoot, "src", "agent", "skills");
|
|
8194
|
+
if (existsSync11(srcSkills)) {
|
|
8136
8195
|
return srcSkills;
|
|
8137
8196
|
}
|
|
8138
8197
|
return null;
|
|
@@ -8147,17 +8206,17 @@ var WorkspaceManager = class {
|
|
|
8147
8206
|
}
|
|
8148
8207
|
const cliDir = resolve11(fileURLToPath4(new URL(".", import.meta.url)));
|
|
8149
8208
|
const pkgRoot = resolve11(cliDir, "..", "..");
|
|
8150
|
-
const candidates = [
|
|
8209
|
+
const candidates = [join7(pkgRoot, "templates")];
|
|
8151
8210
|
for (const candidate of candidates) {
|
|
8152
|
-
if (
|
|
8211
|
+
if (existsSync11(candidate)) {
|
|
8153
8212
|
return candidate;
|
|
8154
8213
|
}
|
|
8155
8214
|
}
|
|
8156
8215
|
return null;
|
|
8157
8216
|
}
|
|
8158
8217
|
getBridgeDir() {
|
|
8159
|
-
const userBridge =
|
|
8160
|
-
if (
|
|
8218
|
+
const userBridge = join7(getDataDir8(), "bridge");
|
|
8219
|
+
if (existsSync11(join7(userBridge, "dist", "index.js"))) {
|
|
8161
8220
|
return userBridge;
|
|
8162
8221
|
}
|
|
8163
8222
|
if (!which("npm")) {
|
|
@@ -8166,21 +8225,21 @@ var WorkspaceManager = class {
|
|
|
8166
8225
|
}
|
|
8167
8226
|
const cliDir = resolve11(fileURLToPath4(new URL(".", import.meta.url)));
|
|
8168
8227
|
const pkgRoot = resolve11(cliDir, "..", "..");
|
|
8169
|
-
const pkgBridge =
|
|
8170
|
-
const srcBridge =
|
|
8228
|
+
const pkgBridge = join7(pkgRoot, "bridge");
|
|
8229
|
+
const srcBridge = join7(pkgRoot, "..", "..", "bridge");
|
|
8171
8230
|
let source = null;
|
|
8172
|
-
if (
|
|
8231
|
+
if (existsSync11(join7(pkgBridge, "package.json"))) {
|
|
8173
8232
|
source = pkgBridge;
|
|
8174
|
-
} else if (
|
|
8233
|
+
} else if (existsSync11(join7(srcBridge, "package.json"))) {
|
|
8175
8234
|
source = srcBridge;
|
|
8176
8235
|
}
|
|
8177
8236
|
if (!source) {
|
|
8178
|
-
console.error(`Bridge source not found. Try reinstalling ${
|
|
8237
|
+
console.error(`Bridge source not found. Try reinstalling ${APP_NAME4}.`);
|
|
8179
8238
|
process.exit(1);
|
|
8180
8239
|
}
|
|
8181
8240
|
console.log(`${this.logo} Setting up bridge...`);
|
|
8182
|
-
|
|
8183
|
-
if (
|
|
8241
|
+
mkdirSync6(resolve11(userBridge, ".."), { recursive: true });
|
|
8242
|
+
if (existsSync11(userBridge)) {
|
|
8184
8243
|
rmSync5(userBridge, { recursive: true, force: true });
|
|
8185
8244
|
}
|
|
8186
8245
|
cpSync3(source, userBridge, {
|
|
@@ -8233,6 +8292,7 @@ var CliRuntime = class {
|
|
|
8233
8292
|
cronCommands;
|
|
8234
8293
|
platformAuthCommands;
|
|
8235
8294
|
remoteCommands;
|
|
8295
|
+
remote;
|
|
8236
8296
|
diagnosticsCommands;
|
|
8237
8297
|
constructor(options = {}) {
|
|
8238
8298
|
this.logo = options.logo ?? LOGO;
|
|
@@ -8256,6 +8316,13 @@ var CliRuntime = class {
|
|
|
8256
8316
|
this.cronCommands = new CronCommands();
|
|
8257
8317
|
this.platformAuthCommands = new PlatformAuthCommands();
|
|
8258
8318
|
this.remoteCommands = new RemoteCommands();
|
|
8319
|
+
this.remote = new RemoteRuntimeActions({
|
|
8320
|
+
appName: APP_NAME5,
|
|
8321
|
+
initAuto: (source) => this.init({ source, auto: true }),
|
|
8322
|
+
remoteCommands: this.remoteCommands,
|
|
8323
|
+
restartBackgroundService: (reason) => this.restartBackgroundService(reason),
|
|
8324
|
+
hasRunningManagedService: hasRunningNextclawManagedService
|
|
8325
|
+
});
|
|
8259
8326
|
this.diagnosticsCommands = new DiagnosticsCommands({ logo: this.logo });
|
|
8260
8327
|
this.restartCoordinator = new RestartCoordinator({
|
|
8261
8328
|
readServiceState,
|
|
@@ -8286,7 +8353,7 @@ var CliRuntime = class {
|
|
|
8286
8353
|
const uiHost = FORCED_PUBLIC_UI_HOST;
|
|
8287
8354
|
const uiPort = typeof state.uiPort === "number" && Number.isFinite(state.uiPort) ? state.uiPort : 18791;
|
|
8288
8355
|
console.log(
|
|
8289
|
-
`Applying changes (${reason}): restarting ${
|
|
8356
|
+
`Applying changes (${reason}): restarting ${APP_NAME5} background service...`
|
|
8290
8357
|
);
|
|
8291
8358
|
await this.serviceCommands.stopService();
|
|
8292
8359
|
await this.serviceCommands.startService({
|
|
@@ -8441,7 +8508,7 @@ var CliRuntime = class {
|
|
|
8441
8508
|
}
|
|
8442
8509
|
async onboard() {
|
|
8443
8510
|
console.warn(
|
|
8444
|
-
`Warning: ${
|
|
8511
|
+
`Warning: ${APP_NAME5} onboard is deprecated. Use "${APP_NAME5} init" instead.`
|
|
8445
8512
|
);
|
|
8446
8513
|
await this.init({ source: "onboard" });
|
|
8447
8514
|
}
|
|
@@ -8449,18 +8516,18 @@ var CliRuntime = class {
|
|
|
8449
8516
|
const source = options.source ?? "init";
|
|
8450
8517
|
const prefix = options.auto ? "Auto init" : "Init";
|
|
8451
8518
|
const force = Boolean(options.force);
|
|
8452
|
-
const configPath =
|
|
8519
|
+
const configPath = getConfigPath7();
|
|
8453
8520
|
let createdConfig = false;
|
|
8454
|
-
if (!
|
|
8521
|
+
if (!existsSync12(configPath)) {
|
|
8455
8522
|
const config3 = ConfigSchema2.parse({});
|
|
8456
|
-
|
|
8523
|
+
saveConfig10(config3);
|
|
8457
8524
|
createdConfig = true;
|
|
8458
8525
|
}
|
|
8459
|
-
const config2 =
|
|
8526
|
+
const config2 = loadConfig14();
|
|
8460
8527
|
const workspaceSetting = config2.agents.defaults.workspace;
|
|
8461
|
-
const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ?
|
|
8462
|
-
const workspaceExisted =
|
|
8463
|
-
|
|
8528
|
+
const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join8(getDataDir9(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
|
|
8529
|
+
const workspaceExisted = existsSync12(workspacePath);
|
|
8530
|
+
mkdirSync7(workspacePath, { recursive: true });
|
|
8464
8531
|
const templateResult = this.workspaceManager.createWorkspaceTemplates(
|
|
8465
8532
|
workspacePath,
|
|
8466
8533
|
{ force }
|
|
@@ -8479,13 +8546,13 @@ var CliRuntime = class {
|
|
|
8479
8546
|
}
|
|
8480
8547
|
if (!options.auto) {
|
|
8481
8548
|
console.log(`
|
|
8482
|
-
${this.logo} ${
|
|
8549
|
+
${this.logo} ${APP_NAME5} is ready! (${source})`);
|
|
8483
8550
|
console.log("\nNext steps:");
|
|
8484
8551
|
console.log(` 1. Add your API key to ${configPath}`);
|
|
8485
|
-
console.log(` 2. Chat: ${
|
|
8552
|
+
console.log(` 2. Chat: ${APP_NAME5} agent -m "Hello!"`);
|
|
8486
8553
|
} else {
|
|
8487
8554
|
console.log(
|
|
8488
|
-
`Tip: Run "${
|
|
8555
|
+
`Tip: Run "${APP_NAME5} init${force ? " --force" : ""}" to re-run initialization if needed.`
|
|
8489
8556
|
);
|
|
8490
8557
|
}
|
|
8491
8558
|
}
|
|
@@ -8493,9 +8560,6 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8493
8560
|
await this.init({ source: "login", auto: true });
|
|
8494
8561
|
await this.platformAuthCommands.login(opts);
|
|
8495
8562
|
}
|
|
8496
|
-
async remoteConnect(opts = {}) {
|
|
8497
|
-
await this.remoteCommands.connect(opts);
|
|
8498
|
-
}
|
|
8499
8563
|
async gateway(opts) {
|
|
8500
8564
|
const uiOverrides = {
|
|
8501
8565
|
host: FORCED_PUBLIC_UI_HOST
|
|
@@ -8546,7 +8610,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8546
8610
|
await this.writeRestartSentinelFromExecContext("cli.restart");
|
|
8547
8611
|
const state = readServiceState();
|
|
8548
8612
|
if (state && isProcessRunning(state.pid)) {
|
|
8549
|
-
console.log(`Restarting ${
|
|
8613
|
+
console.log(`Restarting ${APP_NAME5}...`);
|
|
8550
8614
|
await this.serviceCommands.stopService();
|
|
8551
8615
|
} else if (state) {
|
|
8552
8616
|
clearServiceState();
|
|
@@ -8585,8 +8649,8 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8585
8649
|
await this.serviceCommands.stopService();
|
|
8586
8650
|
}
|
|
8587
8651
|
async agent(opts) {
|
|
8588
|
-
const configPath =
|
|
8589
|
-
const config2 = resolveConfigSecrets3(
|
|
8652
|
+
const configPath = getConfigPath7();
|
|
8653
|
+
const config2 = resolveConfigSecrets3(loadConfig14(), { configPath });
|
|
8590
8654
|
const workspace = getWorkspacePath10(config2.agents.defaults.workspace);
|
|
8591
8655
|
const pluginRegistry = loadPluginRegistry(config2, workspace);
|
|
8592
8656
|
const extensionRegistry = toExtensionRegistry(pluginRegistry);
|
|
@@ -8594,7 +8658,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8594
8658
|
const pluginChannelBindings = getPluginChannelBindings4(pluginRegistry);
|
|
8595
8659
|
setPluginRuntimeBridge2({
|
|
8596
8660
|
loadConfig: () => toPluginConfigView(
|
|
8597
|
-
resolveConfigSecrets3(
|
|
8661
|
+
resolveConfigSecrets3(loadConfig14(), { configPath }),
|
|
8598
8662
|
pluginChannelBindings
|
|
8599
8663
|
),
|
|
8600
8664
|
writeConfigFile: async (nextConfigView) => {
|
|
@@ -8603,13 +8667,13 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8603
8667
|
"plugin runtime writeConfigFile expects an object config"
|
|
8604
8668
|
);
|
|
8605
8669
|
}
|
|
8606
|
-
const current =
|
|
8670
|
+
const current = loadConfig14();
|
|
8607
8671
|
const next = mergePluginConfigView(
|
|
8608
8672
|
current,
|
|
8609
8673
|
nextConfigView,
|
|
8610
8674
|
pluginChannelBindings
|
|
8611
8675
|
);
|
|
8612
|
-
|
|
8676
|
+
saveConfig10(next);
|
|
8613
8677
|
}
|
|
8614
8678
|
});
|
|
8615
8679
|
try {
|
|
@@ -8635,7 +8699,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8635
8699
|
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints2({
|
|
8636
8700
|
registry: pluginRegistry,
|
|
8637
8701
|
channel,
|
|
8638
|
-
cfg: resolveConfigSecrets3(
|
|
8702
|
+
cfg: resolveConfigSecrets3(loadConfig14(), { configPath }),
|
|
8639
8703
|
accountId
|
|
8640
8704
|
})
|
|
8641
8705
|
});
|
|
@@ -8654,10 +8718,10 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8654
8718
|
`${this.logo} Interactive mode (type exit or Ctrl+C to quit)
|
|
8655
8719
|
`
|
|
8656
8720
|
);
|
|
8657
|
-
const historyFile =
|
|
8721
|
+
const historyFile = join8(getDataDir9(), "history", "cli_history");
|
|
8658
8722
|
const historyDir = resolve12(historyFile, "..");
|
|
8659
|
-
|
|
8660
|
-
const history =
|
|
8723
|
+
mkdirSync7(historyDir, { recursive: true });
|
|
8724
|
+
const history = existsSync12(historyFile) ? readFileSync10(historyFile, "utf-8").split("\n").filter(Boolean) : [];
|
|
8661
8725
|
const rl = createInterface3({
|
|
8662
8726
|
input: process.stdin,
|
|
8663
8727
|
output: process.stdout
|
|
@@ -8666,7 +8730,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8666
8730
|
const merged = history.concat(
|
|
8667
8731
|
rl.history ?? []
|
|
8668
8732
|
);
|
|
8669
|
-
|
|
8733
|
+
writeFileSync6(historyFile, merged.join("\n"));
|
|
8670
8734
|
process.exit(0);
|
|
8671
8735
|
});
|
|
8672
8736
|
let running = true;
|
|
@@ -8736,7 +8800,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8736
8800
|
}
|
|
8737
8801
|
const state = readServiceState();
|
|
8738
8802
|
if (state && isProcessRunning(state.pid)) {
|
|
8739
|
-
console.log(`Tip: restart ${
|
|
8803
|
+
console.log(`Tip: restart ${APP_NAME5} to apply the update.`);
|
|
8740
8804
|
}
|
|
8741
8805
|
}
|
|
8742
8806
|
pluginsList(opts = {}) {
|
|
@@ -8830,7 +8894,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8830
8894
|
await this.diagnosticsCommands.doctor(opts);
|
|
8831
8895
|
}
|
|
8832
8896
|
async skillsInstall(options) {
|
|
8833
|
-
const config2 =
|
|
8897
|
+
const config2 = loadConfig14();
|
|
8834
8898
|
const workdir = resolveSkillsInstallWorkdir({
|
|
8835
8899
|
explicitWorkdir: options.workdir,
|
|
8836
8900
|
configuredWorkspace: config2.agents.defaults.workspace
|
|
@@ -8894,20 +8958,19 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8894
8958
|
// src/cli/index.ts
|
|
8895
8959
|
var program = new Command();
|
|
8896
8960
|
var runtime = new CliRuntime({ logo: LOGO });
|
|
8897
|
-
program.name(
|
|
8898
|
-
program.command("onboard").description(`Initialize ${
|
|
8899
|
-
program.command("init").description(`Initialize ${
|
|
8961
|
+
program.name(APP_NAME6).description(`${LOGO} ${APP_NAME6} - ${APP_TAGLINE}`).version(getPackageVersion(), "-v, --version", "show version");
|
|
8962
|
+
program.command("onboard").description(`Initialize ${APP_NAME6} configuration and workspace`).action(async () => runtime.onboard());
|
|
8963
|
+
program.command("init").description(`Initialize ${APP_NAME6} configuration and workspace`).option("-f, --force", "Overwrite existing template files").action(async (opts) => runtime.init({ force: Boolean(opts.force) }));
|
|
8900
8964
|
program.command("login").description("Login to NextClaw platform and save token into providers.nextclaw.apiKey").option("--api-base <url>", "Platform API base (supports /v1 suffix)").option("--email <email>", "Login email").option("--password <password>", "Login password").option("--register", "Register first, then login", false).action(async (opts) => runtime.login(opts));
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
program.command("
|
|
8904
|
-
program.command("
|
|
8905
|
-
program.command("
|
|
8906
|
-
program.command("
|
|
8907
|
-
program.command("
|
|
8908
|
-
program.command("stop").description(`Stop the ${APP_NAME5} background service`).action(async () => runtime.stop());
|
|
8965
|
+
registerRemoteCommands(program, runtime.remote);
|
|
8966
|
+
program.command("gateway").description(`Start the ${APP_NAME6} gateway`).option("-p, --port <port>", "Gateway port", "18790").option("-v, --verbose", "Verbose output", false).option("--ui", "Enable UI server", false).option("--ui-port <port>", "UI port").option("--ui-open", "Open browser when UI starts", false).action(async (opts) => runtime.gateway(opts));
|
|
8967
|
+
program.command("ui").description(`Start the ${APP_NAME6} UI with gateway`).option("--port <port>", "UI port").option("--no-open", "Disable opening browser").action(async (opts) => runtime.ui(opts));
|
|
8968
|
+
program.command("start").description(`Start the ${APP_NAME6} gateway + UI in the background`).option("--ui-port <port>", "UI port").option("--start-timeout <ms>", "Maximum wait time for startup readiness in milliseconds").option("--open", "Open browser after start", false).action(async (opts) => runtime.start(opts));
|
|
8969
|
+
program.command("restart").description(`Restart the ${APP_NAME6} background service`).option("--ui-port <port>", "UI port").option("--start-timeout <ms>", "Maximum wait time for startup readiness in milliseconds").option("--open", "Open browser after restart", false).action(async (opts) => runtime.restart(opts));
|
|
8970
|
+
program.command("serve").description(`Run the ${APP_NAME6} gateway + UI in the foreground`).option("--ui-port <port>", "UI port").option("--open", "Open browser after start", false).action(async (opts) => runtime.serve(opts));
|
|
8971
|
+
program.command("stop").description(`Stop the ${APP_NAME6} background service`).action(async () => runtime.stop());
|
|
8909
8972
|
program.command("agent").description("Interact with the agent directly").option("-m, --message <message>", "Message to send to the agent").option("-s, --session <session>", "Session ID", "cli:default").option("--model <model>", "Session model override for this run").option("--no-markdown", "Disable Markdown rendering").action(async (opts) => runtime.agent(opts));
|
|
8910
|
-
program.command("update").description(`Update ${
|
|
8973
|
+
program.command("update").description(`Update ${APP_NAME6}`).option("--timeout <ms>", "Update command timeout in milliseconds").action(async (opts) => runtime.update(opts));
|
|
8911
8974
|
var skills = program.command("skills").description("Manage skills");
|
|
8912
8975
|
skills.command("install <slug>").description("Install a skill from NextClaw marketplace").option("--api-base <url>", "Marketplace API base URL").option("--workdir <dir>", "Workspace directory to install into").option("--dir <dir>", "Skills directory name (default: skills)").option("-f, --force", "Overwrite existing skill files", false).action(async (slug, opts) => runtime.skillsInstall({ slug, ...opts, apiBaseUrl: opts.apiBase }));
|
|
8913
8976
|
var withRepeatableTag = (value, previous = []) => [...previous, value];
|
|
@@ -8952,6 +9015,6 @@ cron.command("add").requiredOption("-n, --name <name>", "Job name").requiredOpti
|
|
|
8952
9015
|
cron.command("remove <jobId>").action((jobId) => runtime.cronRemove(jobId));
|
|
8953
9016
|
cron.command("enable <jobId>").option("--disable", "Disable instead of enable").action((jobId, opts) => runtime.cronEnable(jobId, opts));
|
|
8954
9017
|
cron.command("run <jobId>").option("-f, --force", "Run even if disabled").action(async (jobId, opts) => runtime.cronRun(jobId, opts));
|
|
8955
|
-
program.command("status").description(`Show ${
|
|
8956
|
-
program.command("doctor").description(`Run ${
|
|
9018
|
+
program.command("status").description(`Show ${APP_NAME6} status`).option("--json", "Output JSON", false).option("--verbose", "Show extra diagnostics", false).option("--fix", "Fix stale service state when safe", false).action(async (opts) => runtime.status(opts));
|
|
9019
|
+
program.command("doctor").description(`Run ${APP_NAME6} diagnostics`).option("--json", "Output JSON", false).option("--verbose", "Show extra diagnostics", false).option("--fix", "Fix stale service state when safe", false).action(async (opts) => runtime.doctor(opts));
|
|
8957
9020
|
program.parseAsync(process.argv);
|