autowonder 0.2.115 → 0.2.116

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/README.md CHANGED
@@ -6,13 +6,13 @@ AutoWonder 本地 agent runtime。安装后启动 daemon,持续轮询本地 as
6
6
 
7
7
  ```bash
8
8
  # Qoder CLI(模型使用 provider model ID;Context Window 使用固定档位)
9
- npx -y autowonder@latest connect --ws-url <wss-endpoint> --token <executor-token> --executor-id <executor-id> --provider qoder --model qmodel_latest --reasoning-effort medium --context-window 260000
9
+ npx -y autowonder@0.2.116 connect --ws-url <wss-endpoint> --token <executor-token> --executor-id <executor-id> --provider qoder --model qmodel_latest --reasoning-effort medium --context-window 260000
10
10
 
11
11
  # Claude Code
12
- npx -y autowonder@0.2.115 connect --ws-url <wss-endpoint> --token <executor-token> --executor-id <executor-id> --provider claude
12
+ npx -y autowonder@0.2.116 connect --ws-url <wss-endpoint> --token <executor-token> --executor-id <executor-id> --provider claude
13
13
 
14
14
  # Codex CLI
15
- npx -y autowonder@0.2.115 connect --ws-url <wss-endpoint> --token <executor-token> --executor-id <executor-id> --provider codex --model gpt-5.5 --reasoning-effort medium
15
+ npx -y autowonder@0.2.116 connect --ws-url <wss-endpoint> --token <executor-token> --executor-id <executor-id> --provider codex --model gpt-5.5 --reasoning-effort medium
16
16
  ```
17
17
 
18
18
  `connect` 会安装当前 npm 包内置的 daemon,并使用页面生成的 WebSocket endpoint、Token 和执行器 ID 建立连接。选择 Qoder 时需要 Node.js 20+;runtime 会在缺少 `qodercli` 时自动通过 npm 安装,并在尚未登录时打开 Qoder 浏览器登录。Claude Code 和 Codex CLI 仍需提前安装并登录。runtime 会继承当前用户的 HOME、环境变量和 CLI 登录状态。
@@ -57,14 +57,14 @@ mv "$queue/.assignment.tmp" "$queue/assignment.json"
57
57
  也可以直接提交到本地 API:
58
58
 
59
59
  ```bash
60
- npx -y autowonder@0.2.115 dispatch ./assignment.json
60
+ npx -y autowonder@0.2.116 dispatch ./assignment.json
61
61
  ```
62
62
 
63
63
  ## 管理 daemon
64
64
 
65
65
  ```bash
66
- npx -y autowonder@0.2.115 status
67
- npx -y autowonder@0.2.115 stop
66
+ npx -y autowonder@0.2.116 status
67
+ npx -y autowonder@0.2.116 stop
68
68
  ```
69
69
 
70
70
  默认 API 是 `http://127.0.0.1:34989`,日志位于 `~/.autowonder/daemon.log`。npm 包不包含任何 agent、MCP 或服务端凭证。
package/bin/cli.js CHANGED
@@ -12,7 +12,10 @@ const http = require("http");
12
12
  const VERSION = require("../package.json").version;
13
13
  const AUTOWONDER_HOME = path.join(os.homedir(), ".autowonder");
14
14
  const BIN_DIR = path.join(AUTOWONDER_HOME, "bin");
15
- const DAEMON_BIN = path.join(BIN_DIR, "autowonder-daemon");
15
+ function daemonBinPath() {
16
+ return path.join(BIN_DIR, process.platform === "win32" ? "autowonder-daemon.exe" : "autowonder-daemon");
17
+ }
18
+ const DAEMON_BIN = daemonBinPath();
16
19
  const CONFIG_PATH = path.join(AUTOWONDER_HOME, "config.json");
17
20
  const PID_PATH = path.join(AUTOWONDER_HOME, "daemon.pid");
18
21
  const STATE_PATH = path.join(AUTOWONDER_HOME, "daemon-state.json");
@@ -23,7 +26,12 @@ const BINARY_BASE_URL = process.env.AUTOWONDER_BINARY_URL || "";
23
26
  const SOURCE_REPO = process.env.AUTOWONDER_SOURCE_REPO || "";
24
27
  const BUNDLED_BIN_DIR = path.join(__dirname, "..", "vendor");
25
28
 
26
- const DEFAULT_API_ADDR = "127.0.0.1:34989";
29
+ const DEFAULT_API_ADDR = process.env.AUTOWONDER_API_ADDR || "127.0.0.1:34989";
30
+ const IS_WIN32 = process.platform === "win32";
31
+
32
+ function safeChmod(filePath, mode) {
33
+ try { fs.chmodSync(filePath, mode); } catch {}
34
+ }
27
35
 
28
36
  // ─── Helpers ─────────────────────────────────────────────────────────
29
37
 
@@ -104,7 +112,14 @@ function qoderNodeVersion() {
104
112
  return process.versions.node;
105
113
  }
106
114
 
107
- const QODER_AUTH_RECHECK_ATTEMPTS = 3;
115
+ const QODER_AUTH_RECHECK_TIMEOUT_MS = 3 * 60 * 1000;
116
+
117
+ function qoderAuthRecheckTimeoutMs() {
118
+ if (process.env.NODE_ENV === "test" && process.env.AUTOWONDER_TEST_QODER_AUTH_RECHECK_TIMEOUT_MS) {
119
+ return Number.parseInt(process.env.AUTOWONDER_TEST_QODER_AUTH_RECHECK_TIMEOUT_MS, 10) || 1;
120
+ }
121
+ return QODER_AUTH_RECHECK_TIMEOUT_MS;
122
+ }
108
123
 
109
124
  function qoderAuthRecheckDelayMs() {
110
125
  if (process.env.NODE_ENV === "test" && process.env.AUTOWONDER_TEST_QODER_AUTH_RECHECK_DELAY_MS) {
@@ -116,14 +131,19 @@ function qoderAuthRecheckDelayMs() {
116
131
  // Credentials written by `qodercli login` may not be readable immediately,
117
132
  // so the post-login authentication check retries before declaring failure.
118
133
  async function qoderAuthenticatedAfterLogin(executable) {
119
- for (let attempt = 1; attempt <= QODER_AUTH_RECHECK_ATTEMPTS; attempt++) {
134
+ const deadline = Date.now() + qoderAuthRecheckTimeoutMs();
135
+ while (true) {
120
136
  if (qoderAuthenticated(executable)) return true;
121
- if (attempt < QODER_AUTH_RECHECK_ATTEMPTS) {
122
- log("Waiting for Qoder login to finish...");
123
- await new Promise((resolve) => setTimeout(resolve, qoderAuthRecheckDelayMs()));
124
- }
137
+ const remainingMs = deadline - Date.now();
138
+ if (remainingMs <= 0) return false;
139
+ log("Waiting for Qoder login to finish...");
140
+ await new Promise((resolve) => setTimeout(resolve, Math.min(qoderAuthRecheckDelayMs(), remainingMs)));
125
141
  }
126
- return false;
142
+ }
143
+
144
+ function qoderLoginCommand(executable) {
145
+ const quoted = `'${executable.replace(/'/g, "''")}'`;
146
+ return IS_WIN32 ? `& ${quoted} login` : `${quoted} login`;
127
147
  }
128
148
 
129
149
  async function ensureQoderReady() {
@@ -141,7 +161,7 @@ async function ensureQoderReady() {
141
161
  log("Qoder login is required. Opening browser login...");
142
162
  const login = spawnSync(executable, ["login"], { stdio: "inherit" });
143
163
  if (login.status !== 0 || !(await qoderAuthenticatedAfterLogin(executable))) {
144
- error("Qoder login did not complete. Run `qodercli login` and retry.");
164
+ error(`Qoder login did not complete. Run ${qoderLoginCommand(executable)} and retry.`);
145
165
  return false;
146
166
  }
147
167
  }
@@ -193,6 +213,12 @@ function processRunning(pid) {
193
213
  }
194
214
 
195
215
  function processFingerprint(pid) {
216
+ if (IS_WIN32) {
217
+ const result = spawnSync("wmic", [
218
+ "process", "where", `ProcessId=${pid}`, "get", "CreationDate,CommandLine", "/format:list",
219
+ ], { encoding: "utf8", timeout: 3000 });
220
+ return result.status === 0 ? (result.stdout || "").trim() : "";
221
+ }
196
222
  const result = spawnSync("ps", ["-p", String(pid), "-o", "lstart=", "-o", "command="], {
197
223
  encoding: "utf8",
198
224
  timeout: 2000,
@@ -201,6 +227,15 @@ function processFingerprint(pid) {
201
227
  }
202
228
 
203
229
  function legacyDaemonProcess(pid) {
230
+ if (IS_WIN32) {
231
+ const result = spawnSync("wmic", [
232
+ "process", "where", `ProcessId=${pid}`, "get", "ExecutablePath", "/format:list",
233
+ ], { encoding: "utf8", timeout: 3000 });
234
+ if (result.status !== 0) return false;
235
+ const output = (result.stdout || "").trim();
236
+ const normalized = DAEMON_BIN.replace(/\//g, "\\");
237
+ return output.includes(DAEMON_BIN) || output.includes(normalized);
238
+ }
204
239
  const result = spawnSync("ps", ["-p", String(pid), "-o", "command="], {
205
240
  encoding: "utf8",
206
241
  timeout: 2000,
@@ -309,7 +344,7 @@ function installBundledBinary(source, destination) {
309
344
  const temporary = `${destination}.tmp-${process.pid}-${crypto.randomUUID()}`;
310
345
  try {
311
346
  fs.copyFileSync(source, temporary);
312
- fs.chmodSync(temporary, 0o755);
347
+ safeChmod(temporary, 0o755);
313
348
  fs.renameSync(temporary, destination);
314
349
  } finally {
315
350
  try { fs.unlinkSync(temporary); } catch {}
@@ -329,7 +364,8 @@ async function installBinary(options = {}) {
329
364
  ensureDir(BIN_DIR);
330
365
 
331
366
  // Priority 1: bundled binary in npm package (vendor/)
332
- const bundledPath = path.join(BUNDLED_BIN_DIR, `autowonder-daemon-${platform}`);
367
+ const ext = IS_WIN32 ? ".exe" : "";
368
+ const bundledPath = path.join(BUNDLED_BIN_DIR, `autowonder-daemon-${platform}${ext}`);
333
369
  if (fs.existsSync(bundledPath)) {
334
370
  installBundledBinary(bundledPath, DAEMON_BIN);
335
371
  log(`Installed from bundled binary: ${DAEMON_BIN}`);
@@ -338,10 +374,10 @@ async function installBinary(options = {}) {
338
374
 
339
375
  // Priority 2: download from URL (OSS or CDN)
340
376
  if (BINARY_BASE_URL) {
341
- const url = `${BINARY_BASE_URL}/v${VERSION}/autowonder-daemon-${platform}`;
377
+ const url = `${BINARY_BASE_URL}/v${VERSION}/autowonder-daemon-${platform}${ext}`;
342
378
  const downloaded = await tryDownload(url, DAEMON_BIN);
343
379
  if (downloaded) {
344
- fs.chmodSync(DAEMON_BIN, 0o755);
380
+ safeChmod(DAEMON_BIN, 0o755);
345
381
  log(`Downloaded daemon binary: ${DAEMON_BIN}`);
346
382
  return true;
347
383
  }
@@ -389,7 +425,7 @@ function buildFromSource() {
389
425
  return false;
390
426
  }
391
427
 
392
- try { execFileSync("which", ["go"], { stdio: "ignore" }); } catch {
428
+ try { execFileSync(IS_WIN32 ? "where" : "which", ["go"], { stdio: "ignore" }); } catch {
393
429
  error("Go is not installed. Install Go 1.22+ or provide a pre-built binary.");
394
430
  error(" macOS: brew install go");
395
431
  error(" Linux: https://go.dev/dl/");
@@ -403,7 +439,7 @@ function buildFromSource() {
403
439
  log("Building daemon...");
404
440
  ensureDir(BIN_DIR);
405
441
  execFileSync("go", ["build", "-o", DAEMON_BIN, "./cmd/autowonder-daemon"], { cwd: tmpDir, stdio: "inherit" });
406
- fs.chmodSync(DAEMON_BIN, 0o755);
442
+ safeChmod(DAEMON_BIN, 0o755);
407
443
  log(`Built daemon binary: ${DAEMON_BIN}`);
408
444
  return true;
409
445
  } catch (e) {
@@ -601,6 +637,40 @@ async function cmdStop(options = {}) {
601
637
  return true;
602
638
  }
603
639
 
640
+ function httpGetJson(addr, urlPath, timeoutMs = 3000) {
641
+ return new Promise((resolve) => {
642
+ const request = http.get({ hostname: addr.split(":")[0], port: addr.split(":")[1], path: urlPath, timeout: timeoutMs }, (response) => {
643
+ let body = "";
644
+ response.setEncoding("utf8");
645
+ response.on("data", (chunk) => { body += chunk; });
646
+ response.on("end", () => {
647
+ try { resolve({ statusCode: response.statusCode, body: JSON.parse(body) }); } catch { resolve(null); }
648
+ });
649
+ });
650
+ request.on("timeout", () => request.destroy());
651
+ request.on("error", () => resolve(null));
652
+ });
653
+ }
654
+
655
+ function httpPostJson(addr, urlPath, body, timeoutMs = 10000) {
656
+ return new Promise((resolve) => {
657
+ const data = typeof body === "string" ? body : JSON.stringify(body);
658
+ const request = http.request({
659
+ hostname: addr.split(":")[0], port: addr.split(":")[1], path: urlPath, method: "POST", timeout: timeoutMs,
660
+ headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(data) },
661
+ }, (response) => {
662
+ let responseBody = "";
663
+ response.setEncoding("utf8");
664
+ response.on("data", (chunk) => { responseBody += chunk; });
665
+ response.on("end", () => resolve({ statusCode: response.statusCode, body: responseBody }));
666
+ });
667
+ request.on("timeout", () => request.destroy());
668
+ request.on("error", (e) => resolve({ statusCode: 0, body: e.message }));
669
+ request.write(data);
670
+ request.end();
671
+ });
672
+ }
673
+
604
674
  async function cmdStatus() {
605
675
  const running = daemonRunning();
606
676
  const pid = readPid();
@@ -616,23 +686,17 @@ async function cmdStatus() {
616
686
  console.log("");
617
687
 
618
688
  if (running) {
619
- try {
620
- const res = spawnSync("curl", ["-s", `http://${DEFAULT_API_ADDR}/health`], { encoding: "utf8", timeout: 3000 });
621
- if (res.stdout) {
622
- const health = JSON.parse(res.stdout);
623
- console.log(` API: http://${DEFAULT_API_ADDR} (${health.status})`);
624
- }
625
- } catch {}
689
+ const health = await httpGetJson(DEFAULT_API_ADDR, "/health");
690
+ if (health && health.statusCode === 200) {
691
+ console.log(` API: http://${DEFAULT_API_ADDR} (${health.body.status})`);
692
+ }
626
693
 
627
- try {
628
- const res = spawnSync("curl", ["-s", `http://${DEFAULT_API_ADDR}/runtimes`], { encoding: "utf8", timeout: 3000 });
629
- if (res.stdout) {
630
- const runtimes = JSON.parse(res.stdout);
631
- for (const rt of (runtimes.runtimes || runtimes)) {
632
- console.log(` Runtime: ${rt.id} (${rt.provider}) ${rt.available ? "available" : "unavailable"}${rt.version ? " v" + rt.version : ""}`);
633
- }
694
+ const runtimes = await httpGetJson(DEFAULT_API_ADDR, "/runtimes");
695
+ if (runtimes && runtimes.statusCode === 200) {
696
+ for (const rt of (runtimes.body.runtimes || runtimes.body)) {
697
+ console.log(` Runtime: ${rt.id} (${rt.provider}) ${rt.available ? "available" : "unavailable"}${rt.version ? " v" + rt.version : ""}`);
634
698
  }
635
- } catch {}
699
+ }
636
700
  console.log("");
637
701
  }
638
702
  }
@@ -659,24 +723,14 @@ async function cmdDispatch(args) {
659
723
  }
660
724
 
661
725
  log(`Submitting dispatch to http://${DEFAULT_API_ADDR}...`);
662
- const res = spawnSync("curl", [
663
- "-s", "-w", "\n%{http_code}",
664
- "-X", "POST",
665
- "-H", "Content-Type: application/json",
666
- "-d", body,
667
- `http://${DEFAULT_API_ADDR}/dispatches/local`,
668
- ], { encoding: "utf8", timeout: 10000 });
669
-
670
- const lines = (res.stdout || "").trim().split("\n");
671
- const statusCode = lines.pop();
672
- const responseBody = lines.join("\n");
673
-
674
- if (statusCode === "200" || statusCode === "202") {
726
+ const res = await httpPostJson(DEFAULT_API_ADDR, "/dispatches/local", body);
727
+
728
+ if (res.statusCode === 200 || res.statusCode === 202) {
675
729
  log("Dispatch submitted successfully.");
676
- if (responseBody) console.log(responseBody);
730
+ if (res.body) console.log(res.body);
677
731
  } else {
678
- error(`Dispatch failed (HTTP ${statusCode})`);
679
- if (responseBody) console.error(responseBody);
732
+ error(`Dispatch failed (HTTP ${res.statusCode})`);
733
+ if (res.body) console.error(res.body);
680
734
  process.exit(1);
681
735
  }
682
736
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autowonder",
3
- "version": "0.2.115",
3
+ "version": "0.2.116",
4
4
  "description": "AutoWonder local runtime — execute AI agent dispatch packages on your machine",
5
5
  "bin": {
6
6
  "autowonder": "bin/cli.js"
@@ -88,19 +88,23 @@ echo "Go toolchain: $(go env GOVERSION)"
88
88
  # Build for all platforms
89
89
  STAGING_VENDOR_DIR="$(mktemp -d "$PKG_DIR/.vendor-staging.XXXXXX")"
90
90
 
91
- platforms=("darwin-arm64" "darwin-amd64" "linux-amd64" "linux-arm64")
92
- goarch_map=("arm64" "amd64" "amd64" "arm64")
93
- goos_map=("darwin" "darwin" "linux" "linux")
91
+ platforms=("darwin-arm64" "darwin-amd64" "linux-amd64" "linux-arm64" "windows-amd64" "windows-arm64")
92
+ goarch_map=("arm64" "amd64" "amd64" "arm64" "amd64" "arm64")
93
+ goos_map=("darwin" "darwin" "linux" "linux" "windows" "windows")
94
+ filename_map=("autowonder-daemon-darwin-arm64" "autowonder-daemon-darwin-amd64" "autowonder-daemon-linux-amd64" "autowonder-daemon-linux-arm64" "autowonder-daemon-win32-amd64.exe" "autowonder-daemon-win32-arm64.exe")
94
95
 
95
96
  for i in "${!platforms[@]}"; do
96
97
  platform="${platforms[$i]}"
97
98
  goos="${goos_map[$i]}"
98
99
  goarch="${goarch_map[$i]}"
99
- output="$STAGING_VENDOR_DIR/autowonder-daemon-${goos}-${goarch}"
100
+ filename="${filename_map[$i]}"
101
+ output="$STAGING_VENDOR_DIR/$filename"
100
102
 
101
103
  echo "Building $platform..."
102
104
  (cd "$BUILD_SOURCE_DIR" && CGO_ENABLED=0 GOOS="$goos" GOARCH="$goarch" go build -trimpath -buildvcs=true -o "$output" -ldflags="$ldflags" ./cmd/autowonder-daemon)
103
- chmod +x "$output"
105
+ if [[ "$goos" != "windows" ]]; then
106
+ chmod +x "$output"
107
+ fi
104
108
  verify_go_binary_provenance "$output" "$release_commit" "$goos" "$goarch"
105
109
  echo " -> $output ($(du -h "$output" | cut -f1))"
106
110
  done