weacpx 0.4.1 → 0.4.2
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.js +129 -21
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2610,18 +2610,22 @@ class DaemonController {
|
|
|
2610
2610
|
statusStore;
|
|
2611
2611
|
startupPollIntervalMs;
|
|
2612
2612
|
startupTimeoutMs;
|
|
2613
|
+
onboardingStartupTimeoutMs;
|
|
2613
2614
|
onStartupPoll;
|
|
2614
2615
|
shutdownPollIntervalMs;
|
|
2615
2616
|
shutdownTimeoutMs;
|
|
2616
2617
|
onShutdownPoll;
|
|
2618
|
+
now;
|
|
2617
2619
|
constructor(paths, deps) {
|
|
2618
2620
|
this.paths = paths;
|
|
2619
2621
|
this.deps = deps;
|
|
2620
2622
|
this.statusStore = new DaemonStatusStore(paths.statusFile);
|
|
2621
2623
|
this.startupPollIntervalMs = deps.startupPollIntervalMs ?? 50;
|
|
2622
2624
|
this.startupTimeoutMs = deps.startupTimeoutMs ?? 5000;
|
|
2625
|
+
this.onboardingStartupTimeoutMs = deps.onboardingStartupTimeoutMs ?? 300000;
|
|
2623
2626
|
this.shutdownPollIntervalMs = deps.shutdownPollIntervalMs ?? 50;
|
|
2624
2627
|
this.shutdownTimeoutMs = deps.shutdownTimeoutMs ?? 5000;
|
|
2628
|
+
this.now = deps.now ?? (() => Date.now());
|
|
2625
2629
|
this.onStartupPoll = deps.onStartupPoll ?? (async () => {
|
|
2626
2630
|
await new Promise((resolve) => setTimeout(resolve, this.startupPollIntervalMs));
|
|
2627
2631
|
});
|
|
@@ -2659,7 +2663,7 @@ class DaemonController {
|
|
|
2659
2663
|
await this.statusStore.clear();
|
|
2660
2664
|
const pid = await this.deps.spawnDetached(options);
|
|
2661
2665
|
await this.writePid(pid);
|
|
2662
|
-
await this.waitForStartupMetadata(pid);
|
|
2666
|
+
await this.waitForStartupMetadata(pid, options.firstRunOnboarding ? this.onboardingStartupTimeoutMs : this.startupTimeoutMs, options.startupWait);
|
|
2663
2667
|
return { state: "started", pid };
|
|
2664
2668
|
}
|
|
2665
2669
|
async stop() {
|
|
@@ -2695,9 +2699,10 @@ class DaemonController {
|
|
|
2695
2699
|
await rm2(this.paths.pidFile, { force: true });
|
|
2696
2700
|
await this.statusStore.clear();
|
|
2697
2701
|
}
|
|
2698
|
-
async waitForStartupMetadata(pid) {
|
|
2699
|
-
const
|
|
2700
|
-
|
|
2702
|
+
async waitForStartupMetadata(pid, timeoutMs, startupWait) {
|
|
2703
|
+
const startedAt = this.now();
|
|
2704
|
+
const deadline = startedAt + timeoutMs;
|
|
2705
|
+
while (this.now() < deadline) {
|
|
2701
2706
|
const status = await this.statusStore.load();
|
|
2702
2707
|
if (status?.pid === pid) {
|
|
2703
2708
|
return;
|
|
@@ -2706,9 +2711,17 @@ class DaemonController {
|
|
|
2706
2711
|
await this.clearRuntimeFiles();
|
|
2707
2712
|
throw new Error(`weacpx daemon exited before reporting ready state (pid ${pid})`);
|
|
2708
2713
|
}
|
|
2714
|
+
if (startupWait?.shouldStopWaiting?.()) {
|
|
2715
|
+
return;
|
|
2716
|
+
}
|
|
2717
|
+
await startupWait?.onPoll?.({
|
|
2718
|
+
elapsedMs: this.now() - startedAt,
|
|
2719
|
+
timeoutMs,
|
|
2720
|
+
pid
|
|
2721
|
+
});
|
|
2709
2722
|
await this.onStartupPoll();
|
|
2710
2723
|
}
|
|
2711
|
-
throw new Error(`weacpx daemon did not report ready state within ${
|
|
2724
|
+
throw new Error(`weacpx daemon did not report ready state within ${timeoutMs}ms (pid ${pid})`);
|
|
2712
2725
|
}
|
|
2713
2726
|
async waitForShutdown(pid) {
|
|
2714
2727
|
const deadline = Date.now() + this.shutdownTimeoutMs;
|
|
@@ -26172,7 +26185,7 @@ init_create_daemon_controller();
|
|
|
26172
26185
|
init_daemon_files();
|
|
26173
26186
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
26174
26187
|
import { homedir as homedir13 } from "node:os";
|
|
26175
|
-
import { sep } from "node:path";
|
|
26188
|
+
import { dirname as dirname13, join as join16, sep } from "node:path";
|
|
26176
26189
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
26177
26190
|
|
|
26178
26191
|
// src/daemon/daemon-runtime.ts
|
|
@@ -41139,6 +41152,79 @@ async function runRestart2(deps) {
|
|
|
41139
41152
|
}
|
|
41140
41153
|
}
|
|
41141
41154
|
|
|
41155
|
+
// src/cli/startup-wait-ui.ts
|
|
41156
|
+
var SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
41157
|
+
var DEFAULT_SPINNER_FRAME = "⠋";
|
|
41158
|
+
var ENVIRONMENT_HINT_DELAY_MS = 20000;
|
|
41159
|
+
function renderStartupWaitLine(input) {
|
|
41160
|
+
const elapsedSeconds = Math.floor(input.elapsedMs / 1000);
|
|
41161
|
+
const timeoutSeconds = Math.ceil(input.timeoutMs / 1000);
|
|
41162
|
+
const detail = input.elapsedMs >= ENVIRONMENT_HINT_DELAY_MS ? ",首次启动可能需要准备依赖和运行环境" : "";
|
|
41163
|
+
return `${input.frame} 正在创建初始会话${detail} ${elapsedSeconds}s / ${timeoutSeconds}s,按 Ctrl+B 跳过等待`;
|
|
41164
|
+
}
|
|
41165
|
+
function createStartupWaitUi(input) {
|
|
41166
|
+
const stdin2 = input.stdin ?? process.stdin;
|
|
41167
|
+
const stdout2 = input.stdout ?? process.stdout;
|
|
41168
|
+
if (!input.isInteractive() || !stdin2.isTTY || !stdout2.isTTY) {
|
|
41169
|
+
return { stop: () => {} };
|
|
41170
|
+
}
|
|
41171
|
+
let skipped = false;
|
|
41172
|
+
let interrupted = false;
|
|
41173
|
+
let frameIndex = 0;
|
|
41174
|
+
let rawModeEnabled = false;
|
|
41175
|
+
let stopped = false;
|
|
41176
|
+
const cleanup = () => {
|
|
41177
|
+
if (stopped) {
|
|
41178
|
+
return;
|
|
41179
|
+
}
|
|
41180
|
+
stopped = true;
|
|
41181
|
+
stdin2.off("data", onData);
|
|
41182
|
+
if (rawModeEnabled && typeof stdin2.setRawMode === "function") {
|
|
41183
|
+
stdin2.setRawMode(false);
|
|
41184
|
+
}
|
|
41185
|
+
stdin2.pause();
|
|
41186
|
+
stdout2.write("\r\x1B[2K");
|
|
41187
|
+
};
|
|
41188
|
+
const onData = (chunk) => {
|
|
41189
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
41190
|
+
if (buffer.includes(2)) {
|
|
41191
|
+
skipped = true;
|
|
41192
|
+
}
|
|
41193
|
+
if (buffer.includes(3)) {
|
|
41194
|
+
interrupted = true;
|
|
41195
|
+
cleanup();
|
|
41196
|
+
(input.onInterrupt ?? (() => process.kill(process.pid, "SIGINT")))();
|
|
41197
|
+
}
|
|
41198
|
+
};
|
|
41199
|
+
if (typeof stdin2.setRawMode === "function") {
|
|
41200
|
+
stdin2.setRawMode(true);
|
|
41201
|
+
rawModeEnabled = true;
|
|
41202
|
+
}
|
|
41203
|
+
stdin2.resume();
|
|
41204
|
+
stdin2.on("data", onData);
|
|
41205
|
+
const render = (poll) => {
|
|
41206
|
+
const frame = SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length] ?? DEFAULT_SPINNER_FRAME;
|
|
41207
|
+
frameIndex += 1;
|
|
41208
|
+
stdout2.write(`\r\x1B[2K${renderStartupWaitLine({
|
|
41209
|
+
elapsedMs: poll.elapsedMs,
|
|
41210
|
+
timeoutMs: poll.timeoutMs,
|
|
41211
|
+
frame
|
|
41212
|
+
})}`);
|
|
41213
|
+
};
|
|
41214
|
+
return {
|
|
41215
|
+
wait: {
|
|
41216
|
+
onPoll: render,
|
|
41217
|
+
shouldStopWaiting: () => {
|
|
41218
|
+
if (interrupted) {
|
|
41219
|
+
throw new Error("startup wait interrupted");
|
|
41220
|
+
}
|
|
41221
|
+
return skipped;
|
|
41222
|
+
}
|
|
41223
|
+
},
|
|
41224
|
+
stop: cleanup
|
|
41225
|
+
};
|
|
41226
|
+
}
|
|
41227
|
+
|
|
41142
41228
|
// src/cli.ts
|
|
41143
41229
|
init_bootstrap();
|
|
41144
41230
|
async function prepareMcpCoordinatorStartup(input) {
|
|
@@ -41363,6 +41449,7 @@ async function runCli(args, deps = {}) {
|
|
|
41363
41449
|
case "start": {
|
|
41364
41450
|
const controller = deps.controller ?? createDefaultController();
|
|
41365
41451
|
try {
|
|
41452
|
+
const isInteractive = deps.isInteractive ?? defaultIsInteractive;
|
|
41366
41453
|
const status = await controller.getStatus();
|
|
41367
41454
|
if (status.state === "running") {
|
|
41368
41455
|
print("weacpx 已在后台运行");
|
|
@@ -41375,10 +41462,19 @@ async function runCli(args, deps = {}) {
|
|
|
41375
41462
|
const onboarding2 = await runOnboardingBeforeStart({
|
|
41376
41463
|
print,
|
|
41377
41464
|
cwd: deps.cwd ?? (() => process.cwd()),
|
|
41378
|
-
isInteractive
|
|
41465
|
+
isInteractive,
|
|
41379
41466
|
promptText: deps.promptText
|
|
41380
41467
|
});
|
|
41381
|
-
const
|
|
41468
|
+
const startupWaitUi = onboarding2 ? createStartupWaitUi({ isInteractive }) : null;
|
|
41469
|
+
let result;
|
|
41470
|
+
try {
|
|
41471
|
+
result = await controller.start({
|
|
41472
|
+
firstRunOnboarding: onboarding2 ?? undefined,
|
|
41473
|
+
...startupWaitUi?.wait ? { startupWait: startupWaitUi.wait } : {}
|
|
41474
|
+
});
|
|
41475
|
+
} finally {
|
|
41476
|
+
startupWaitUi?.stop();
|
|
41477
|
+
}
|
|
41382
41478
|
if (result.state === "already-running") {
|
|
41383
41479
|
print("weacpx 已在后台运行");
|
|
41384
41480
|
print(`PID: ${result.pid}`);
|
|
@@ -41389,9 +41485,7 @@ async function runCli(args, deps = {}) {
|
|
|
41389
41485
|
return 0;
|
|
41390
41486
|
} catch (error2) {
|
|
41391
41487
|
print(`weacpx 启动失败:${describeFriendlyError(error2)}`);
|
|
41392
|
-
|
|
41393
|
-
if (stderrPath)
|
|
41394
|
-
print(`请查看 Stderr: ${stderrPath}`);
|
|
41488
|
+
printDaemonLogHints(print);
|
|
41395
41489
|
return 1;
|
|
41396
41490
|
}
|
|
41397
41491
|
}
|
|
@@ -41434,9 +41528,7 @@ async function runCli(args, deps = {}) {
|
|
|
41434
41528
|
return await restartDaemonCli(controller, print);
|
|
41435
41529
|
} catch (error2) {
|
|
41436
41530
|
print(`weacpx 重启失败:${describeFriendlyError(error2)}`);
|
|
41437
|
-
|
|
41438
|
-
if (stderrPath)
|
|
41439
|
-
print(`请查看 Stderr: ${stderrPath}`);
|
|
41531
|
+
printDaemonLogHints(print);
|
|
41440
41532
|
return 1;
|
|
41441
41533
|
}
|
|
41442
41534
|
}
|
|
@@ -41454,7 +41546,7 @@ async function defaultUpdate(args, input) {
|
|
|
41454
41546
|
saveConfig: async (config2) => await store.save(config2),
|
|
41455
41547
|
readCurrentVersion: readVersion,
|
|
41456
41548
|
print: input.print,
|
|
41457
|
-
isInteractive: input.isInteractive ??
|
|
41549
|
+
isInteractive: input.isInteractive ?? defaultIsInteractive,
|
|
41458
41550
|
promptText: input.promptText ?? defaultPromptText,
|
|
41459
41551
|
...input.overrides
|
|
41460
41552
|
};
|
|
@@ -41474,7 +41566,7 @@ async function runOnboardingBeforeStart(input) {
|
|
|
41474
41566
|
deps: {
|
|
41475
41567
|
print: input.print,
|
|
41476
41568
|
cwd: input.cwd,
|
|
41477
|
-
isInteractive: input.isInteractive ??
|
|
41569
|
+
isInteractive: input.isInteractive ?? defaultIsInteractive,
|
|
41478
41570
|
promptText: input.promptText ?? defaultPromptText
|
|
41479
41571
|
}
|
|
41480
41572
|
});
|
|
@@ -41741,7 +41833,7 @@ async function createChannelCliDeps(input) {
|
|
|
41741
41833
|
saveConfig: async (config2) => await store.save(config2),
|
|
41742
41834
|
print: input.print,
|
|
41743
41835
|
stderr: input.stderr ?? ((text) => process.stderr.write(text)),
|
|
41744
|
-
isInteractive: input.isInteractive ??
|
|
41836
|
+
isInteractive: input.isInteractive ?? defaultIsInteractive,
|
|
41745
41837
|
promptText: input.promptText ?? defaultPromptText,
|
|
41746
41838
|
promptSecret: input.promptSecret ?? defaultPromptSecret,
|
|
41747
41839
|
getDaemonStatus: async () => {
|
|
@@ -41763,7 +41855,7 @@ async function createPluginCliDeps(input) {
|
|
|
41763
41855
|
loadConfig: async () => await store.load(),
|
|
41764
41856
|
saveConfig: async (config2) => await store.save(config2),
|
|
41765
41857
|
print: input.print,
|
|
41766
|
-
isInteractive: input.isInteractive ??
|
|
41858
|
+
isInteractive: input.isInteractive ?? defaultIsInteractive,
|
|
41767
41859
|
promptText: input.promptText ?? defaultPromptText,
|
|
41768
41860
|
getDaemonStatus: async () => {
|
|
41769
41861
|
const status = await controller.getStatus();
|
|
@@ -41849,7 +41941,8 @@ function createDefaultController() {
|
|
|
41849
41941
|
getStatus: () => controller.getStatus(),
|
|
41850
41942
|
stop: () => controller.stop(),
|
|
41851
41943
|
start: (options) => controller.start({
|
|
41852
|
-
...options?.firstRunOnboarding ? { firstRunOnboarding: encodeFirstRunOnboarding(options.firstRunOnboarding) } : {}
|
|
41944
|
+
...options?.firstRunOnboarding ? { firstRunOnboarding: encodeFirstRunOnboarding(options.firstRunOnboarding) } : {},
|
|
41945
|
+
...options?.startupWait ? { startupWait: options.startupWait } : {}
|
|
41853
41946
|
})
|
|
41854
41947
|
};
|
|
41855
41948
|
}
|
|
@@ -41883,12 +41976,27 @@ function requireHome2() {
|
|
|
41883
41976
|
}
|
|
41884
41977
|
return home;
|
|
41885
41978
|
}
|
|
41979
|
+
function defaultIsInteractive() {
|
|
41980
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
41981
|
+
}
|
|
41886
41982
|
function describeFriendlyError(error2) {
|
|
41887
41983
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
41888
41984
|
}
|
|
41889
|
-
function
|
|
41985
|
+
function printDaemonLogHints(print) {
|
|
41986
|
+
const paths = safeDaemonLogPaths();
|
|
41987
|
+
if (!paths)
|
|
41988
|
+
return;
|
|
41989
|
+
print(`请查看 App Log: ${paths.appLog}`);
|
|
41990
|
+
print(`请查看 Stderr: ${paths.stderrLog}`);
|
|
41991
|
+
}
|
|
41992
|
+
function safeDaemonLogPaths() {
|
|
41890
41993
|
try {
|
|
41891
|
-
|
|
41994
|
+
const configPath = process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
|
|
41995
|
+
const paths = resolveDaemonPaths({ home: requireHome2() });
|
|
41996
|
+
return {
|
|
41997
|
+
appLog: join16(dirname13(configPath), "runtime", "app.log"),
|
|
41998
|
+
stderrLog: paths.stderrLog
|
|
41999
|
+
};
|
|
41892
42000
|
} catch {
|
|
41893
42001
|
return null;
|
|
41894
42002
|
}
|