weacpx 0.1.0 → 0.1.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +129 -11
  2. package/package.json +24 -24
package/dist/cli.js CHANGED
@@ -2911,10 +2911,18 @@ class DaemonController {
2911
2911
  paths;
2912
2912
  deps;
2913
2913
  statusStore;
2914
+ startupPollIntervalMs;
2915
+ startupTimeoutMs;
2916
+ onStartupPoll;
2914
2917
  constructor(paths, deps) {
2915
2918
  this.paths = paths;
2916
2919
  this.deps = deps;
2917
2920
  this.statusStore = new DaemonStatusStore(paths.statusFile);
2921
+ this.startupPollIntervalMs = deps.startupPollIntervalMs ?? 50;
2922
+ this.startupTimeoutMs = deps.startupTimeoutMs ?? 5000;
2923
+ this.onStartupPoll = deps.onStartupPoll ?? (async () => {
2924
+ await new Promise((resolve) => setTimeout(resolve, this.startupPollIntervalMs));
2925
+ });
2918
2926
  }
2919
2927
  async getStatus() {
2920
2928
  const pid = await this.loadPid();
@@ -2940,8 +2948,10 @@ class DaemonController {
2940
2948
  if (current.state === "running") {
2941
2949
  return { state: "already-running", pid: current.pid };
2942
2950
  }
2951
+ await this.statusStore.clear();
2943
2952
  const pid = await this.deps.spawnDetached();
2944
2953
  await this.writePid(pid);
2954
+ await this.waitForStartupMetadata(pid);
2945
2955
  return { state: "started", pid };
2946
2956
  }
2947
2957
  async stop() {
@@ -2976,6 +2986,21 @@ class DaemonController {
2976
2986
  await rm2(this.paths.pidFile, { force: true });
2977
2987
  await this.statusStore.clear();
2978
2988
  }
2989
+ async waitForStartupMetadata(pid) {
2990
+ const deadline = Date.now() + this.startupTimeoutMs;
2991
+ while (Date.now() < deadline) {
2992
+ const status = await this.statusStore.load();
2993
+ if (status?.pid === pid) {
2994
+ return;
2995
+ }
2996
+ if (!this.deps.isProcessRunning(pid)) {
2997
+ await this.clearRuntimeFiles();
2998
+ throw new Error(`weacpx daemon exited before reporting ready state (pid ${pid})`);
2999
+ }
3000
+ await this.onStartupPoll();
3001
+ }
3002
+ throw new Error(`weacpx daemon did not report ready state within ${this.startupTimeoutMs}ms (pid ${pid})`);
3003
+ }
2979
3004
  }
2980
3005
 
2981
3006
  // src/daemon/create-daemon-controller.ts
@@ -2987,17 +3012,7 @@ function createDaemonController(paths, options) {
2987
3012
  const stdoutHandle = await open(paths.stdoutLog, "a");
2988
3013
  const stderrHandle = await open(paths.stderrLog, "a");
2989
3014
  try {
2990
- return await (options.spawnProcess ?? defaultSpawnProcess)({
2991
- command: options.processExecPath,
2992
- args: [options.cliEntryPath, "run"],
2993
- options: {
2994
- cwd: options.cwd,
2995
- detached: true,
2996
- env: options.env,
2997
- stdio: ["ignore", stdoutHandle.fd, stderrHandle.fd],
2998
- ...(options.platform ?? process.platform) === "win32" ? { windowsHide: true } : {}
2999
- }
3000
- });
3015
+ return await (options.spawnProcess ?? defaultSpawnProcess)(buildSpawnRequest(paths, options, stdoutHandle.fd, stderrHandle.fd));
3001
3016
  } finally {
3002
3017
  await stdoutHandle.close();
3003
3018
  await stderrHandle.close();
@@ -3014,11 +3029,114 @@ function defaultIsProcessRunning(pid) {
3014
3029
  return false;
3015
3030
  }
3016
3031
  }
3032
+ function buildSpawnRequest(paths, options, stdoutFd, stderrFd) {
3033
+ const platform = options.platform ?? process.platform;
3034
+ if (platform === "win32") {
3035
+ return {
3036
+ mode: "windows-hidden",
3037
+ command: "powershell.exe",
3038
+ args: [
3039
+ "-NoProfile",
3040
+ "-NonInteractive",
3041
+ "-EncodedCommand",
3042
+ buildWindowsLauncherScript()
3043
+ ],
3044
+ options: {
3045
+ cwd: options.cwd,
3046
+ env: {
3047
+ ...options.env,
3048
+ WEACPX_DAEMON_COMMAND: options.processExecPath,
3049
+ WEACPX_DAEMON_ARG0: options.cliEntryPath,
3050
+ WEACPX_DAEMON_ARG1: "run",
3051
+ WEACPX_DAEMON_CWD: options.cwd,
3052
+ WEACPX_DAEMON_STDOUT: paths.stdoutLog,
3053
+ WEACPX_DAEMON_STDERR: paths.stderrLog
3054
+ },
3055
+ stdio: ["ignore", "pipe", "ignore"],
3056
+ windowsHide: true
3057
+ }
3058
+ };
3059
+ }
3060
+ return {
3061
+ mode: "direct",
3062
+ command: options.processExecPath,
3063
+ args: [options.cliEntryPath, "run"],
3064
+ options: {
3065
+ cwd: options.cwd,
3066
+ detached: true,
3067
+ env: options.env,
3068
+ stdio: ["ignore", stdoutFd, stderrFd]
3069
+ }
3070
+ };
3071
+ }
3072
+ function buildWindowsLauncherScript() {
3073
+ const script = [
3074
+ "$process = Start-Process -FilePath $env:WEACPX_DAEMON_COMMAND `",
3075
+ " -ArgumentList @($env:WEACPX_DAEMON_ARG0, $env:WEACPX_DAEMON_ARG1) `",
3076
+ " -WorkingDirectory $env:WEACPX_DAEMON_CWD `",
3077
+ " -RedirectStandardOutput $env:WEACPX_DAEMON_STDOUT `",
3078
+ " -RedirectStandardError $env:WEACPX_DAEMON_STDERR `",
3079
+ " -WindowStyle Hidden `",
3080
+ " -PassThru",
3081
+ "[Console]::Out.WriteLine($process.Id)"
3082
+ ].join(`
3083
+ `);
3084
+ return Buffer.from(script, "utf16le").toString("base64");
3085
+ }
3017
3086
  async function defaultSpawnProcess(request) {
3087
+ if (request.mode === "windows-hidden") {
3088
+ return await spawnWindowsHiddenProcess(request);
3089
+ }
3018
3090
  const child = spawn(request.command, request.args, request.options);
3019
3091
  child.unref();
3020
3092
  return child.pid ?? 0;
3021
3093
  }
3094
+ async function spawnWindowsHiddenProcess(request) {
3095
+ return await new Promise((resolve, reject) => {
3096
+ const child = spawn(request.command, request.args, request.options);
3097
+ let stdout = "";
3098
+ let settled = false;
3099
+ child.stdout?.setEncoding("utf8");
3100
+ child.stdout?.on("data", (chunk) => {
3101
+ stdout += chunk;
3102
+ if (settled) {
3103
+ return;
3104
+ }
3105
+ const pid = Number.parseInt(stdout.trim(), 10);
3106
+ if (!Number.isFinite(pid) || pid <= 0) {
3107
+ return;
3108
+ }
3109
+ settled = true;
3110
+ child.unref();
3111
+ resolve(pid);
3112
+ });
3113
+ child.on("error", (error) => {
3114
+ if (settled) {
3115
+ return;
3116
+ }
3117
+ settled = true;
3118
+ reject(error);
3119
+ });
3120
+ child.on("close", (code) => {
3121
+ if (settled) {
3122
+ return;
3123
+ }
3124
+ if (code !== 0) {
3125
+ settled = true;
3126
+ reject(new Error(`Failed to launch hidden Windows daemon process (exit ${code ?? 1})`));
3127
+ return;
3128
+ }
3129
+ const pid = Number.parseInt(stdout.trim(), 10);
3130
+ if (!Number.isFinite(pid) || pid <= 0) {
3131
+ settled = true;
3132
+ reject(new Error("Failed to read daemon pid from hidden Windows launcher"));
3133
+ return;
3134
+ }
3135
+ settled = true;
3136
+ resolve(pid);
3137
+ });
3138
+ });
3139
+ }
3022
3140
  async function defaultTerminateProcess(pid) {
3023
3141
  await terminateProcessTree(pid);
3024
3142
  }
package/package.json CHANGED
@@ -1,5 +1,28 @@
1
1
  {
2
2
  "name": "weacpx",
3
+ "description": "用微信远程控制 `acpx` 会话的控制台, 底层基于 `weixin-agent-sdk` 与 `acpx`",
4
+ "version": "0.1.1",
5
+ "main": "index.js",
6
+ "directories": {
7
+ "doc": "docs",
8
+ "test": "tests"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/gadzan/weacpx.git"
13
+ },
14
+ "keywords": [
15
+ "acpx",
16
+ "weixin-agent-sdk",
17
+ "openclaw",
18
+ "weixin"
19
+ ],
20
+ "author": "gadzan",
21
+ "license": "MIT",
22
+ "bugs": {
23
+ "url": "https://github.com/gadzan/weacpx/issues"
24
+ },
25
+ "homepage": "https://github.com/gadzan/weacpx#readme",
3
26
  "type": "module",
4
27
  "bin": {
5
28
  "weacpx": "dist/cli.js"
@@ -27,28 +50,5 @@
27
50
  "node-pty": "^1.1.0",
28
51
  "qrcode-terminal": "^0.12.0",
29
52
  "weixin-agent-sdk": "^0.2.0"
30
- },
31
- "description": "用微信远程控制 `acpx` 会话的控制台, 底层基于 `weixin-agent-sdk` 与 `acpx`",
32
- "version": "0.1.0",
33
- "main": "index.js",
34
- "directories": {
35
- "doc": "docs",
36
- "test": "tests"
37
- },
38
- "repository": {
39
- "type": "git",
40
- "url": "git+https://github.com/gadzan/weacpx.git"
41
- },
42
- "keywords": [
43
- "acpx",
44
- "weixin-agent-sdk",
45
- "openclaw",
46
- "weixin"
47
- ],
48
- "author": "gadzan",
49
- "license": "MIT",
50
- "bugs": {
51
- "url": "https://github.com/gadzan/weacpx/issues"
52
- },
53
- "homepage": "https://github.com/gadzan/weacpx#readme"
53
+ }
54
54
  }