nextclaw 0.4.16 → 0.4.17
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 +203 -206
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -82,7 +82,7 @@ import {
|
|
|
82
82
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
83
83
|
import { join, resolve } from "path";
|
|
84
84
|
import { spawn } from "child_process";
|
|
85
|
-
import {
|
|
85
|
+
import { isIP } from "net";
|
|
86
86
|
import { fileURLToPath } from "url";
|
|
87
87
|
import { getDataDir, getPackageVersion as getCorePackageVersion } from "@nextclaw/core";
|
|
88
88
|
function resolveUiConfig(config2, overrides) {
|
|
@@ -128,48 +128,9 @@ async function resolvePublicIp(timeoutMs = 1500) {
|
|
|
128
128
|
}
|
|
129
129
|
return null;
|
|
130
130
|
}
|
|
131
|
-
function isDevRuntime() {
|
|
132
|
-
return import.meta.url.includes("/src/cli/") || process.env.NEXTCLAW_DEV === "1";
|
|
133
|
-
}
|
|
134
|
-
function normalizeHostForPortCheck(host) {
|
|
135
|
-
return host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
136
|
-
}
|
|
137
|
-
async function findAvailablePort(port, host, attempts = 20) {
|
|
138
|
-
const basePort = Number.isFinite(port) ? port : 0;
|
|
139
|
-
let candidate = basePort;
|
|
140
|
-
for (let i = 0; i < attempts; i += 1) {
|
|
141
|
-
const ok = await isPortAvailable(candidate, host);
|
|
142
|
-
if (ok) {
|
|
143
|
-
return candidate;
|
|
144
|
-
}
|
|
145
|
-
candidate += 1;
|
|
146
|
-
}
|
|
147
|
-
return basePort;
|
|
148
|
-
}
|
|
149
|
-
async function isPortAvailable(port, host) {
|
|
150
|
-
const checkHost = normalizeHostForPortCheck(host);
|
|
151
|
-
return await canBindPort(port, checkHost);
|
|
152
|
-
}
|
|
153
|
-
async function canBindPort(port, host) {
|
|
154
|
-
return await new Promise((resolve5) => {
|
|
155
|
-
const server = createServer();
|
|
156
|
-
server.unref();
|
|
157
|
-
server.once("error", () => resolve5(false));
|
|
158
|
-
server.listen({ port, host }, () => {
|
|
159
|
-
server.close(() => resolve5(true));
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
131
|
function buildServeArgs(options) {
|
|
164
132
|
const cliPath = fileURLToPath(new URL("./index.js", import.meta.url));
|
|
165
|
-
|
|
166
|
-
if (options.frontend) {
|
|
167
|
-
args.push("--frontend");
|
|
168
|
-
}
|
|
169
|
-
if (Number.isFinite(options.frontendPort)) {
|
|
170
|
-
args.push("--frontend-port", String(options.frontendPort));
|
|
171
|
-
}
|
|
172
|
-
return args;
|
|
133
|
+
return [cliPath, "serve", "--ui-host", options.uiHost, "--ui-port", String(options.uiPort)];
|
|
173
134
|
}
|
|
174
135
|
function readServiceState() {
|
|
175
136
|
const path = resolveServiceStatePath();
|
|
@@ -298,64 +259,6 @@ function getPackageVersion() {
|
|
|
298
259
|
const cliDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
|
|
299
260
|
return resolveVersionFromPackageTree(cliDir, "nextclaw") ?? resolveVersionFromPackageTree(cliDir) ?? getCorePackageVersion();
|
|
300
261
|
}
|
|
301
|
-
function startUiFrontend(options) {
|
|
302
|
-
const uiDir = options.dir ?? resolveUiFrontendDir();
|
|
303
|
-
if (!uiDir) {
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
const runner = resolveUiFrontendRunner();
|
|
307
|
-
if (!runner) {
|
|
308
|
-
console.log("Warning: pnpm/npm not found. Skipping UI frontend.");
|
|
309
|
-
return null;
|
|
310
|
-
}
|
|
311
|
-
const args = [...runner.args];
|
|
312
|
-
if (options.port) {
|
|
313
|
-
if (runner.useArgSeparator) {
|
|
314
|
-
args.push("--");
|
|
315
|
-
}
|
|
316
|
-
args.push("--port", String(options.port));
|
|
317
|
-
}
|
|
318
|
-
const env = { ...process.env, VITE_API_BASE: options.apiBase };
|
|
319
|
-
const child = spawn(runner.cmd, args, { cwd: uiDir, stdio: "inherit", env });
|
|
320
|
-
child.on("exit", (code) => {
|
|
321
|
-
if (code && code !== 0) {
|
|
322
|
-
console.log(`UI frontend exited with code ${code}`);
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
const url = `http://127.0.0.1:${options.port}`;
|
|
326
|
-
console.log(`\u2713 UI frontend: ${url}`);
|
|
327
|
-
return { url, dir: uiDir };
|
|
328
|
-
}
|
|
329
|
-
function resolveUiFrontendRunner() {
|
|
330
|
-
if (which("pnpm")) {
|
|
331
|
-
return { cmd: "pnpm", args: ["dev"], useArgSeparator: false };
|
|
332
|
-
}
|
|
333
|
-
if (which("npm")) {
|
|
334
|
-
return { cmd: "npm", args: ["run", "dev"], useArgSeparator: true };
|
|
335
|
-
}
|
|
336
|
-
return null;
|
|
337
|
-
}
|
|
338
|
-
function resolveUiFrontendDir() {
|
|
339
|
-
const candidates = [];
|
|
340
|
-
const envDir = process.env.NEXTCLAW_UI_DIR;
|
|
341
|
-
if (envDir) {
|
|
342
|
-
candidates.push(envDir);
|
|
343
|
-
}
|
|
344
|
-
const cwd = process.cwd();
|
|
345
|
-
candidates.push(join(cwd, "packages", "nextclaw-ui"));
|
|
346
|
-
candidates.push(join(cwd, "nextclaw-ui"));
|
|
347
|
-
const cliDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
|
|
348
|
-
const pkgRoot = resolve(cliDir, "..", "..");
|
|
349
|
-
candidates.push(join(pkgRoot, "..", "nextclaw-ui"));
|
|
350
|
-
candidates.push(join(pkgRoot, "..", "..", "packages", "nextclaw-ui"));
|
|
351
|
-
candidates.push(join(pkgRoot, "..", "..", "nextclaw-ui"));
|
|
352
|
-
for (const dir of candidates) {
|
|
353
|
-
if (existsSync(join(dir, "package.json"))) {
|
|
354
|
-
return dir;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
return null;
|
|
358
|
-
}
|
|
359
262
|
function printAgentResponse(response) {
|
|
360
263
|
console.log("\n" + response + "\n");
|
|
361
264
|
}
|
|
@@ -461,17 +364,21 @@ var mergeDeep = (base, patch) => {
|
|
|
461
364
|
}
|
|
462
365
|
return next;
|
|
463
366
|
};
|
|
464
|
-
var scheduleRestart = (delayMs, reason) => {
|
|
465
|
-
const delay = typeof delayMs === "number" && Number.isFinite(delayMs) ? Math.max(0, delayMs) : 100;
|
|
466
|
-
console.log(`Gateway restart requested via tool${reason ? ` (${reason})` : ""}.`);
|
|
467
|
-
setTimeout(() => {
|
|
468
|
-
process.exit(0);
|
|
469
|
-
}, delay);
|
|
470
|
-
};
|
|
471
367
|
var GatewayControllerImpl = class {
|
|
472
368
|
constructor(deps) {
|
|
473
369
|
this.deps = deps;
|
|
474
370
|
}
|
|
371
|
+
async requestRestart(options) {
|
|
372
|
+
if (this.deps.requestRestart) {
|
|
373
|
+
await this.deps.requestRestart(options);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const delay = typeof options?.delayMs === "number" && Number.isFinite(options.delayMs) ? Math.max(0, options.delayMs) : 100;
|
|
377
|
+
console.log(`Gateway restart requested via tool${options?.reason ? ` (${options.reason})` : ""}.`);
|
|
378
|
+
setTimeout(() => {
|
|
379
|
+
process.exit(0);
|
|
380
|
+
}, delay);
|
|
381
|
+
}
|
|
475
382
|
status() {
|
|
476
383
|
return {
|
|
477
384
|
channels: this.deps.reloader.getChannels().enabledChannels,
|
|
@@ -483,7 +390,7 @@ var GatewayControllerImpl = class {
|
|
|
483
390
|
return this.deps.reloader.reloadConfig(reason);
|
|
484
391
|
}
|
|
485
392
|
async restart(options) {
|
|
486
|
-
|
|
393
|
+
await this.requestRestart(options);
|
|
487
394
|
return "Restart scheduled";
|
|
488
395
|
}
|
|
489
396
|
async getConfig() {
|
|
@@ -528,7 +435,7 @@ var GatewayControllerImpl = class {
|
|
|
528
435
|
}
|
|
529
436
|
this.deps.saveConfig(validated);
|
|
530
437
|
const delayMs = params.restartDelayMs ?? 0;
|
|
531
|
-
|
|
438
|
+
await this.requestRestart({ delayMs, reason: "config.apply" });
|
|
532
439
|
return {
|
|
533
440
|
ok: true,
|
|
534
441
|
note: params.note ?? null,
|
|
@@ -564,7 +471,7 @@ var GatewayControllerImpl = class {
|
|
|
564
471
|
}
|
|
565
472
|
this.deps.saveConfig(validated);
|
|
566
473
|
const delayMs = params.restartDelayMs ?? 0;
|
|
567
|
-
|
|
474
|
+
await this.requestRestart({ delayMs, reason: "config.patch" });
|
|
568
475
|
return {
|
|
569
476
|
ok: true,
|
|
570
477
|
note: params.note ?? null,
|
|
@@ -579,7 +486,7 @@ var GatewayControllerImpl = class {
|
|
|
579
486
|
return { ok: false, error: result.error ?? "update failed", steps: result.steps };
|
|
580
487
|
}
|
|
581
488
|
const delayMs = params.restartDelayMs ?? 0;
|
|
582
|
-
|
|
489
|
+
await this.requestRestart({ delayMs, reason: "update.run" });
|
|
583
490
|
return {
|
|
584
491
|
ok: true,
|
|
585
492
|
note: params.note ?? null,
|
|
@@ -590,6 +497,63 @@ var GatewayControllerImpl = class {
|
|
|
590
497
|
}
|
|
591
498
|
};
|
|
592
499
|
|
|
500
|
+
// src/cli/restart-coordinator.ts
|
|
501
|
+
var RestartCoordinator = class {
|
|
502
|
+
constructor(deps) {
|
|
503
|
+
this.deps = deps;
|
|
504
|
+
}
|
|
505
|
+
restartingService = false;
|
|
506
|
+
exitScheduled = false;
|
|
507
|
+
async requestRestart(request) {
|
|
508
|
+
const reason = request.reason.trim() || "config changed";
|
|
509
|
+
const strategy = request.strategy ?? "background-service-or-manual";
|
|
510
|
+
if (strategy !== "exit-process") {
|
|
511
|
+
const state = this.deps.readServiceState();
|
|
512
|
+
const serviceRunning = Boolean(state && this.deps.isProcessRunning(state.pid));
|
|
513
|
+
const managedByCurrentProcess = Boolean(state && state.pid === this.deps.currentPid());
|
|
514
|
+
if (serviceRunning && !managedByCurrentProcess) {
|
|
515
|
+
if (this.restartingService) {
|
|
516
|
+
return {
|
|
517
|
+
status: "restart-in-progress",
|
|
518
|
+
message: "Restart already in progress; skipping duplicate request."
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
this.restartingService = true;
|
|
522
|
+
try {
|
|
523
|
+
const restarted = await this.deps.restartBackgroundService(reason);
|
|
524
|
+
if (restarted) {
|
|
525
|
+
return {
|
|
526
|
+
status: "service-restarted",
|
|
527
|
+
message: `Restarted background service to apply changes (${reason}).`
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
} finally {
|
|
531
|
+
this.restartingService = false;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (strategy === "background-service-or-exit" || strategy === "exit-process") {
|
|
536
|
+
if (this.exitScheduled) {
|
|
537
|
+
return {
|
|
538
|
+
status: "exit-scheduled",
|
|
539
|
+
message: "Restart already scheduled; skipping duplicate request."
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
const delay = typeof request.delayMs === "number" && Number.isFinite(request.delayMs) ? Math.max(0, Math.floor(request.delayMs)) : 100;
|
|
543
|
+
this.exitScheduled = true;
|
|
544
|
+
this.deps.scheduleProcessExit(delay, reason);
|
|
545
|
+
return {
|
|
546
|
+
status: "exit-scheduled",
|
|
547
|
+
message: `Restart scheduled (${reason}).`
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
return {
|
|
551
|
+
status: "manual-required",
|
|
552
|
+
message: request.manualMessage ?? "Restart the gateway to apply changes."
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
|
|
593
557
|
// src/cli/skills/clawhub.ts
|
|
594
558
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
595
559
|
import { existsSync as existsSync3 } from "fs";
|
|
@@ -986,12 +950,75 @@ var ConfigReloader = class {
|
|
|
986
950
|
};
|
|
987
951
|
var CliRuntime = class {
|
|
988
952
|
logo;
|
|
953
|
+
restartCoordinator;
|
|
954
|
+
serviceRestartTask = null;
|
|
989
955
|
constructor(options = {}) {
|
|
990
956
|
this.logo = options.logo ?? LOGO;
|
|
957
|
+
this.restartCoordinator = new RestartCoordinator({
|
|
958
|
+
readServiceState,
|
|
959
|
+
isProcessRunning,
|
|
960
|
+
currentPid: () => process.pid,
|
|
961
|
+
restartBackgroundService: async (reason) => this.restartBackgroundService(reason),
|
|
962
|
+
scheduleProcessExit: (delayMs, reason) => this.scheduleProcessExit(delayMs, reason)
|
|
963
|
+
});
|
|
991
964
|
}
|
|
992
965
|
get version() {
|
|
993
966
|
return getPackageVersion();
|
|
994
967
|
}
|
|
968
|
+
scheduleProcessExit(delayMs, reason) {
|
|
969
|
+
console.warn(`Gateway restart requested (${reason}).`);
|
|
970
|
+
setTimeout(() => {
|
|
971
|
+
process.exit(0);
|
|
972
|
+
}, delayMs);
|
|
973
|
+
}
|
|
974
|
+
async restartBackgroundService(reason) {
|
|
975
|
+
if (this.serviceRestartTask) {
|
|
976
|
+
return this.serviceRestartTask;
|
|
977
|
+
}
|
|
978
|
+
this.serviceRestartTask = (async () => {
|
|
979
|
+
const state = readServiceState();
|
|
980
|
+
if (!state || !isProcessRunning(state.pid) || state.pid === process.pid) {
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
983
|
+
const uiHost = state.uiHost ?? "127.0.0.1";
|
|
984
|
+
const uiPort = typeof state.uiPort === "number" && Number.isFinite(state.uiPort) ? state.uiPort : 18791;
|
|
985
|
+
console.log(`Applying changes (${reason}): restarting ${APP_NAME} background service...`);
|
|
986
|
+
await this.stopService();
|
|
987
|
+
await this.startService({
|
|
988
|
+
uiOverrides: {
|
|
989
|
+
enabled: true,
|
|
990
|
+
host: uiHost,
|
|
991
|
+
port: uiPort
|
|
992
|
+
},
|
|
993
|
+
open: false
|
|
994
|
+
});
|
|
995
|
+
return true;
|
|
996
|
+
})();
|
|
997
|
+
try {
|
|
998
|
+
return await this.serviceRestartTask;
|
|
999
|
+
} finally {
|
|
1000
|
+
this.serviceRestartTask = null;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
async requestRestart(params) {
|
|
1004
|
+
const result = await this.restartCoordinator.requestRestart({
|
|
1005
|
+
reason: params.reason,
|
|
1006
|
+
strategy: params.strategy,
|
|
1007
|
+
delayMs: params.delayMs,
|
|
1008
|
+
manualMessage: params.manualMessage
|
|
1009
|
+
});
|
|
1010
|
+
if (result.status === "manual-required" || result.status === "restart-in-progress") {
|
|
1011
|
+
console.log(result.message);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
if (result.status === "service-restarted") {
|
|
1015
|
+
if (!params.silentOnServiceRestart) {
|
|
1016
|
+
console.log(result.message);
|
|
1017
|
+
}
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
console.warn(result.message);
|
|
1021
|
+
}
|
|
995
1022
|
async onboard() {
|
|
996
1023
|
console.warn(`Warning: ${APP_NAME} onboard is deprecated. Use "${APP_NAME} init" instead.`);
|
|
997
1024
|
await this.init({ source: "onboard" });
|
|
@@ -1088,35 +1115,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1088
1115
|
if (opts.public && !opts.uiHost) {
|
|
1089
1116
|
uiOverrides.host = "0.0.0.0";
|
|
1090
1117
|
}
|
|
1091
|
-
const devMode = isDevRuntime();
|
|
1092
|
-
if (devMode) {
|
|
1093
|
-
const requestedUiPort = Number.isFinite(Number(opts.uiPort)) ? Number(opts.uiPort) : 18792;
|
|
1094
|
-
const requestedFrontendPort = Number.isFinite(Number(opts.frontendPort)) ? Number(opts.frontendPort) : 5174;
|
|
1095
|
-
const uiHost = uiOverrides.host ?? "127.0.0.1";
|
|
1096
|
-
const devUiPort = await findAvailablePort(requestedUiPort, uiHost);
|
|
1097
|
-
const shouldStartFrontend = opts.frontend === void 0 ? true : Boolean(opts.frontend);
|
|
1098
|
-
const devFrontendPort = shouldStartFrontend ? await findAvailablePort(requestedFrontendPort, "127.0.0.1") : requestedFrontendPort;
|
|
1099
|
-
uiOverrides.port = devUiPort;
|
|
1100
|
-
if (requestedUiPort !== devUiPort) {
|
|
1101
|
-
console.log(`Dev mode: UI port ${requestedUiPort} is in use, switched to ${devUiPort}.`);
|
|
1102
|
-
}
|
|
1103
|
-
if (shouldStartFrontend && requestedFrontendPort !== devFrontendPort) {
|
|
1104
|
-
console.log(`Dev mode: Frontend port ${requestedFrontendPort} is in use, switched to ${devFrontendPort}.`);
|
|
1105
|
-
}
|
|
1106
|
-
console.log(`Dev mode: UI ${devUiPort}, Frontend ${devFrontendPort}`);
|
|
1107
|
-
console.log("Dev mode runs in the foreground (Ctrl+C to stop).");
|
|
1108
|
-
await this.runForeground({
|
|
1109
|
-
uiOverrides,
|
|
1110
|
-
frontend: shouldStartFrontend,
|
|
1111
|
-
frontendPort: devFrontendPort,
|
|
1112
|
-
open: Boolean(opts.open)
|
|
1113
|
-
});
|
|
1114
|
-
return;
|
|
1115
|
-
}
|
|
1116
1118
|
await this.startService({
|
|
1117
1119
|
uiOverrides,
|
|
1118
|
-
frontend: Boolean(opts.frontend),
|
|
1119
|
-
frontendPort: Number(opts.frontendPort),
|
|
1120
1120
|
open: Boolean(opts.open)
|
|
1121
1121
|
});
|
|
1122
1122
|
}
|
|
@@ -1147,29 +1147,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1147
1147
|
if (opts.public && !opts.uiHost) {
|
|
1148
1148
|
uiOverrides.host = "0.0.0.0";
|
|
1149
1149
|
}
|
|
1150
|
-
const devMode = isDevRuntime();
|
|
1151
|
-
if (devMode && uiOverrides.port === void 0) {
|
|
1152
|
-
uiOverrides.port = 18792;
|
|
1153
|
-
}
|
|
1154
|
-
const shouldStartFrontend = Boolean(opts.frontend);
|
|
1155
|
-
const defaultFrontendPort = devMode ? 5174 : 5173;
|
|
1156
|
-
const requestedFrontendPort = Number.isFinite(Number(opts.frontendPort)) ? Number(opts.frontendPort) : defaultFrontendPort;
|
|
1157
|
-
if (devMode && uiOverrides.port !== void 0) {
|
|
1158
|
-
const uiHost = uiOverrides.host ?? "127.0.0.1";
|
|
1159
|
-
const uiPort = await findAvailablePort(uiOverrides.port, uiHost);
|
|
1160
|
-
if (uiPort !== uiOverrides.port) {
|
|
1161
|
-
console.log(`Dev mode: UI port ${uiOverrides.port} is in use, switched to ${uiPort}.`);
|
|
1162
|
-
uiOverrides.port = uiPort;
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
const frontendPort = devMode && shouldStartFrontend ? await findAvailablePort(requestedFrontendPort, "127.0.0.1") : requestedFrontendPort;
|
|
1166
|
-
if (devMode && shouldStartFrontend && frontendPort !== requestedFrontendPort) {
|
|
1167
|
-
console.log(`Dev mode: Frontend port ${requestedFrontendPort} is in use, switched to ${frontendPort}.`);
|
|
1168
|
-
}
|
|
1169
1150
|
await this.runForeground({
|
|
1170
1151
|
uiOverrides,
|
|
1171
|
-
frontend: shouldStartFrontend,
|
|
1172
|
-
frontendPort,
|
|
1173
1152
|
open: Boolean(opts.open)
|
|
1174
1153
|
});
|
|
1175
1154
|
}
|
|
@@ -1428,7 +1407,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1428
1407
|
}
|
|
1429
1408
|
console.log(JSON.stringify(result.value ?? null, null, 2));
|
|
1430
1409
|
}
|
|
1431
|
-
configSet(pathExpr, value, opts = {}) {
|
|
1410
|
+
async configSet(pathExpr, value, opts = {}) {
|
|
1432
1411
|
let parsedPath;
|
|
1433
1412
|
try {
|
|
1434
1413
|
parsedPath = parseRequiredConfigPath(pathExpr);
|
|
@@ -1454,9 +1433,12 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1454
1433
|
return;
|
|
1455
1434
|
}
|
|
1456
1435
|
saveConfig(config2);
|
|
1457
|
-
|
|
1436
|
+
await this.requestRestart({
|
|
1437
|
+
reason: `config.set ${pathExpr}`,
|
|
1438
|
+
manualMessage: `Updated ${pathExpr}. Restart the gateway to apply.`
|
|
1439
|
+
});
|
|
1458
1440
|
}
|
|
1459
|
-
configUnset(pathExpr) {
|
|
1441
|
+
async configUnset(pathExpr) {
|
|
1460
1442
|
let parsedPath;
|
|
1461
1443
|
try {
|
|
1462
1444
|
parsedPath = parseRequiredConfigPath(pathExpr);
|
|
@@ -1473,19 +1455,28 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1473
1455
|
return;
|
|
1474
1456
|
}
|
|
1475
1457
|
saveConfig(config2);
|
|
1476
|
-
|
|
1458
|
+
await this.requestRestart({
|
|
1459
|
+
reason: `config.unset ${pathExpr}`,
|
|
1460
|
+
manualMessage: `Removed ${pathExpr}. Restart the gateway to apply.`
|
|
1461
|
+
});
|
|
1477
1462
|
}
|
|
1478
|
-
pluginsEnable(id) {
|
|
1463
|
+
async pluginsEnable(id) {
|
|
1479
1464
|
const config2 = loadConfig();
|
|
1480
1465
|
const next = enablePluginInConfig(config2, id);
|
|
1481
1466
|
saveConfig(next);
|
|
1482
|
-
|
|
1467
|
+
await this.requestRestart({
|
|
1468
|
+
reason: `plugin enabled: ${id}`,
|
|
1469
|
+
manualMessage: `Enabled plugin "${id}". Restart the gateway to apply.`
|
|
1470
|
+
});
|
|
1483
1471
|
}
|
|
1484
|
-
pluginsDisable(id) {
|
|
1472
|
+
async pluginsDisable(id) {
|
|
1485
1473
|
const config2 = loadConfig();
|
|
1486
1474
|
const next = disablePluginInConfig(config2, id);
|
|
1487
1475
|
saveConfig(next);
|
|
1488
|
-
|
|
1476
|
+
await this.requestRestart({
|
|
1477
|
+
reason: `plugin disabled: ${id}`,
|
|
1478
|
+
manualMessage: `Disabled plugin "${id}". Restart the gateway to apply.`
|
|
1479
|
+
});
|
|
1489
1480
|
}
|
|
1490
1481
|
async pluginsUninstall(id, opts = {}) {
|
|
1491
1482
|
const config2 = loadConfig();
|
|
@@ -1582,7 +1573,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1582
1573
|
removed.push("directory");
|
|
1583
1574
|
}
|
|
1584
1575
|
console.log(`Uninstalled plugin "${pluginId}". Removed: ${removed.length > 0 ? removed.join(", ") : "nothing"}.`);
|
|
1585
|
-
|
|
1576
|
+
await this.requestRestart({
|
|
1577
|
+
reason: `plugin uninstalled: ${pluginId}`,
|
|
1578
|
+
manualMessage: "Restart the gateway to apply changes."
|
|
1579
|
+
});
|
|
1586
1580
|
}
|
|
1587
1581
|
async pluginsInstall(pathOrSpec, opts = {}) {
|
|
1588
1582
|
const fileSpec = this.resolveFileNpmSpecToLocalPath(pathOrSpec);
|
|
@@ -1611,7 +1605,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1611
1605
|
});
|
|
1612
1606
|
saveConfig(next3);
|
|
1613
1607
|
console.log(`Linked plugin path: ${resolved}`);
|
|
1614
|
-
|
|
1608
|
+
await this.requestRestart({
|
|
1609
|
+
reason: `plugin linked: ${probe.pluginId}`,
|
|
1610
|
+
manualMessage: "Restart the gateway to load plugins."
|
|
1611
|
+
});
|
|
1615
1612
|
return;
|
|
1616
1613
|
}
|
|
1617
1614
|
const result2 = await installPluginFromPath({
|
|
@@ -1635,7 +1632,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1635
1632
|
});
|
|
1636
1633
|
saveConfig(next2);
|
|
1637
1634
|
console.log(`Installed plugin: ${result2.pluginId}`);
|
|
1638
|
-
|
|
1635
|
+
await this.requestRestart({
|
|
1636
|
+
reason: `plugin installed: ${result2.pluginId}`,
|
|
1637
|
+
manualMessage: "Restart the gateway to load plugins."
|
|
1638
|
+
});
|
|
1639
1639
|
return;
|
|
1640
1640
|
}
|
|
1641
1641
|
if (opts.link) {
|
|
@@ -1667,7 +1667,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1667
1667
|
});
|
|
1668
1668
|
saveConfig(next);
|
|
1669
1669
|
console.log(`Installed plugin: ${result.pluginId}`);
|
|
1670
|
-
|
|
1670
|
+
await this.requestRestart({
|
|
1671
|
+
reason: `plugin installed: ${result.pluginId}`,
|
|
1672
|
+
manualMessage: "Restart the gateway to load plugins."
|
|
1673
|
+
});
|
|
1671
1674
|
}
|
|
1672
1675
|
pluginsDoctor() {
|
|
1673
1676
|
const config2 = loadConfig();
|
|
@@ -1757,7 +1760,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1757
1760
|
console.error(`Bridge failed: ${result.status ?? 1}`);
|
|
1758
1761
|
}
|
|
1759
1762
|
}
|
|
1760
|
-
channelsAdd(opts) {
|
|
1763
|
+
async channelsAdd(opts) {
|
|
1761
1764
|
const channelId = opts.channel?.trim();
|
|
1762
1765
|
if (!channelId) {
|
|
1763
1766
|
console.error("--channel is required");
|
|
@@ -1808,7 +1811,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1808
1811
|
next = enablePluginInConfig(next, binding.pluginId);
|
|
1809
1812
|
saveConfig(next);
|
|
1810
1813
|
console.log(`Configured channel "${binding.channelId}" via plugin "${binding.pluginId}".`);
|
|
1811
|
-
|
|
1814
|
+
await this.requestRestart({
|
|
1815
|
+
reason: `channel configured via plugin: ${binding.pluginId}`,
|
|
1816
|
+
manualMessage: "Restart the gateway to apply changes."
|
|
1817
|
+
});
|
|
1812
1818
|
}
|
|
1813
1819
|
toPluginConfigView(config2, bindings) {
|
|
1814
1820
|
const view = JSON.parse(JSON.stringify(config2));
|
|
@@ -2032,7 +2038,11 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2032
2038
|
loadConfig,
|
|
2033
2039
|
getExtensionChannels: () => extensionRegistry.channels,
|
|
2034
2040
|
onRestartRequired: (paths) => {
|
|
2035
|
-
|
|
2041
|
+
void this.requestRestart({
|
|
2042
|
+
reason: `config reload requires restart: ${paths.join(", ")}`,
|
|
2043
|
+
manualMessage: `Config changes require restart: ${paths.join(", ")}`,
|
|
2044
|
+
strategy: "background-service-or-manual"
|
|
2045
|
+
});
|
|
2036
2046
|
}
|
|
2037
2047
|
});
|
|
2038
2048
|
const gatewayController = new GatewayControllerImpl({
|
|
@@ -2040,7 +2050,16 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2040
2050
|
cron: cron2,
|
|
2041
2051
|
getConfigPath,
|
|
2042
2052
|
saveConfig,
|
|
2043
|
-
getPluginUiMetadata: () => pluginUiMetadata
|
|
2053
|
+
getPluginUiMetadata: () => pluginUiMetadata,
|
|
2054
|
+
requestRestart: async (options2) => {
|
|
2055
|
+
await this.requestRestart({
|
|
2056
|
+
reason: options2?.reason ?? "gateway tool restart",
|
|
2057
|
+
manualMessage: "Restart the gateway to apply changes.",
|
|
2058
|
+
strategy: "background-service-or-exit",
|
|
2059
|
+
delayMs: options2?.delayMs,
|
|
2060
|
+
silentOnServiceRestart: true
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
2044
2063
|
});
|
|
2045
2064
|
const agent = new AgentLoop({
|
|
2046
2065
|
bus,
|
|
@@ -2213,34 +2232,14 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2213
2232
|
async runForeground(options) {
|
|
2214
2233
|
const config2 = loadConfig();
|
|
2215
2234
|
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
2216
|
-
const
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
const staticDir = resolveUiStaticDir();
|
|
2220
|
-
let frontendUrl = null;
|
|
2221
|
-
if (shouldStartFrontend && frontendDir) {
|
|
2222
|
-
const frontend = startUiFrontend({
|
|
2223
|
-
apiBase: resolveUiApiBase(uiConfig.host, uiConfig.port),
|
|
2224
|
-
port: frontendPort,
|
|
2225
|
-
dir: frontendDir
|
|
2226
|
-
});
|
|
2227
|
-
frontendUrl = frontend?.url ?? null;
|
|
2228
|
-
} else if (shouldStartFrontend && !frontendDir) {
|
|
2229
|
-
console.log("Warning: UI frontend not found. Start it separately.");
|
|
2230
|
-
}
|
|
2231
|
-
if (!frontendUrl && staticDir) {
|
|
2232
|
-
frontendUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
2233
|
-
}
|
|
2234
|
-
if (options.open && frontendUrl) {
|
|
2235
|
-
openBrowser(frontendUrl);
|
|
2236
|
-
} else if (options.open && !frontendUrl) {
|
|
2237
|
-
console.log("Warning: UI frontend not started. Browser not opened.");
|
|
2235
|
+
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
2236
|
+
if (options.open) {
|
|
2237
|
+
openBrowser(uiUrl);
|
|
2238
2238
|
}
|
|
2239
|
-
const uiStaticDir = shouldStartFrontend && frontendDir ? null : staticDir;
|
|
2240
2239
|
await this.startGateway({
|
|
2241
2240
|
uiOverrides: options.uiOverrides,
|
|
2242
2241
|
allowMissingProvider: true,
|
|
2243
|
-
uiStaticDir
|
|
2242
|
+
uiStaticDir: resolveUiStaticDir()
|
|
2244
2243
|
});
|
|
2245
2244
|
}
|
|
2246
2245
|
async startService(options) {
|
|
@@ -2283,8 +2282,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2283
2282
|
if (existing) {
|
|
2284
2283
|
clearServiceState();
|
|
2285
2284
|
}
|
|
2286
|
-
if (!staticDir
|
|
2287
|
-
console.log("Warning: UI frontend not found
|
|
2285
|
+
if (!staticDir) {
|
|
2286
|
+
console.log("Warning: UI frontend not found in package assets.");
|
|
2288
2287
|
}
|
|
2289
2288
|
const logPath = resolveServiceLogPath();
|
|
2290
2289
|
const logDir = resolve4(logPath, "..");
|
|
@@ -2292,9 +2291,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2292
2291
|
const logFd = openSync(logPath, "a");
|
|
2293
2292
|
const serveArgs = buildServeArgs({
|
|
2294
2293
|
uiHost: uiConfig.host,
|
|
2295
|
-
uiPort: uiConfig.port
|
|
2296
|
-
frontend: options.frontend,
|
|
2297
|
-
frontendPort: options.frontendPort
|
|
2294
|
+
uiPort: uiConfig.port
|
|
2298
2295
|
});
|
|
2299
2296
|
const child = spawn2(process.execPath, [...process.execArgv, ...serveArgs], {
|
|
2300
2297
|
env: process.env,
|
|
@@ -2597,9 +2594,9 @@ program.command("onboard").description(`Initialize ${APP_NAME2} configuration an
|
|
|
2597
2594
|
program.command("init").description(`Initialize ${APP_NAME2} configuration and workspace`).option("-f, --force", "Overwrite existing template files").action(async (opts) => runtime.init({ force: Boolean(opts.force) }));
|
|
2598
2595
|
program.command("gateway").description(`Start the ${APP_NAME2} gateway`).option("-p, --port <port>", "Gateway port", "18790").option("-v, --verbose", "Verbose output", false).option("--ui", "Enable UI server", false).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--ui-open", "Open browser when UI starts", false).option("--public", "Expose UI on 0.0.0.0 and print public URL", false).action(async (opts) => runtime.gateway(opts));
|
|
2599
2596
|
program.command("ui").description(`Start the ${APP_NAME2} UI with gateway`).option("--host <host>", "UI host").option("--port <port>", "UI port").option("--no-open", "Disable opening browser").option("--public", "Expose UI on 0.0.0.0 and print public URL", false).action(async (opts) => runtime.ui(opts));
|
|
2600
|
-
program.command("start").description(`Start the ${APP_NAME2} gateway + UI in the background`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--
|
|
2601
|
-
program.command("restart").description(`Restart the ${APP_NAME2} background service`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--
|
|
2602
|
-
program.command("serve").description(`Run the ${APP_NAME2} gateway + UI in the foreground`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--
|
|
2597
|
+
program.command("start").description(`Start the ${APP_NAME2} gateway + UI in the background`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--open", "Open browser after start", false).option("--public", "Expose UI on 0.0.0.0 and print public URL", false).action(async (opts) => runtime.start(opts));
|
|
2598
|
+
program.command("restart").description(`Restart the ${APP_NAME2} background service`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--open", "Open browser after restart", false).option("--public", "Expose UI on 0.0.0.0 and print public URL", false).action(async (opts) => runtime.restart(opts));
|
|
2599
|
+
program.command("serve").description(`Run the ${APP_NAME2} gateway + UI in the foreground`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--open", "Open browser after start", false).option("--public", "Expose UI on 0.0.0.0 and print public URL", false).action(async (opts) => runtime.serve(opts));
|
|
2603
2600
|
program.command("stop").description(`Stop the ${APP_NAME2} background service`).action(async () => runtime.stop());
|
|
2604
2601
|
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("--no-markdown", "Disable Markdown rendering").action(async (opts) => runtime.agent(opts));
|
|
2605
2602
|
program.command("update").description(`Update ${APP_NAME2}`).option("--timeout <ms>", "Update command timeout in milliseconds").action(async (opts) => runtime.update(opts));
|