autowonder 0.2.115 → 0.2.117

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.117 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.117 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.117 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.117 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.117 status
67
+ npx -y autowonder@0.2.117 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
 
@@ -74,8 +82,14 @@ function findExecutable(name) {
74
82
  return "";
75
83
  }
76
84
 
85
+ function spawnQoderSync(executable, args, options = {}) {
86
+ // npm exposes package binaries as .cmd shims on Windows. CreateProcess
87
+ // cannot execute those shims directly, so let ComSpec resolve them.
88
+ return spawnSync(executable, args, { ...options, shell: IS_WIN32 });
89
+ }
90
+
77
91
  function qoderAuthenticated(executable) {
78
- const result = spawnSync(executable, ["--list-models"], {
92
+ const result = spawnQoderSync(executable, ["--list-models"], {
79
93
  encoding: "utf8",
80
94
  stdio: ["ignore", "pipe", "pipe"],
81
95
  timeout: 20000,
@@ -104,7 +118,14 @@ function qoderNodeVersion() {
104
118
  return process.versions.node;
105
119
  }
106
120
 
107
- const QODER_AUTH_RECHECK_ATTEMPTS = 3;
121
+ const QODER_AUTH_RECHECK_TIMEOUT_MS = 3 * 60 * 1000;
122
+
123
+ function qoderAuthRecheckTimeoutMs() {
124
+ if (process.env.NODE_ENV === "test" && process.env.AUTOWONDER_TEST_QODER_AUTH_RECHECK_TIMEOUT_MS) {
125
+ return Number.parseInt(process.env.AUTOWONDER_TEST_QODER_AUTH_RECHECK_TIMEOUT_MS, 10) || 1;
126
+ }
127
+ return QODER_AUTH_RECHECK_TIMEOUT_MS;
128
+ }
108
129
 
109
130
  function qoderAuthRecheckDelayMs() {
110
131
  if (process.env.NODE_ENV === "test" && process.env.AUTOWONDER_TEST_QODER_AUTH_RECHECK_DELAY_MS) {
@@ -116,14 +137,19 @@ function qoderAuthRecheckDelayMs() {
116
137
  // Credentials written by `qodercli login` may not be readable immediately,
117
138
  // so the post-login authentication check retries before declaring failure.
118
139
  async function qoderAuthenticatedAfterLogin(executable) {
119
- for (let attempt = 1; attempt <= QODER_AUTH_RECHECK_ATTEMPTS; attempt++) {
140
+ const deadline = Date.now() + qoderAuthRecheckTimeoutMs();
141
+ while (true) {
120
142
  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
- }
143
+ const remainingMs = deadline - Date.now();
144
+ if (remainingMs <= 0) return false;
145
+ log("Waiting for Qoder login to finish...");
146
+ await new Promise((resolve) => setTimeout(resolve, Math.min(qoderAuthRecheckDelayMs(), remainingMs)));
125
147
  }
126
- return false;
148
+ }
149
+
150
+ function qoderLoginCommand(executable) {
151
+ const quoted = `'${executable.replace(/'/g, "''")}'`;
152
+ return IS_WIN32 ? `& ${quoted} login` : `${quoted} login`;
127
153
  }
128
154
 
129
155
  async function ensureQoderReady() {
@@ -139,9 +165,15 @@ async function ensureQoderReady() {
139
165
  if (!executable) return false;
140
166
  if (!qoderAuthenticated(executable)) {
141
167
  log("Qoder login is required. Opening browser login...");
142
- const login = spawnSync(executable, ["login"], { stdio: "inherit" });
143
- if (login.status !== 0 || !(await qoderAuthenticatedAfterLogin(executable))) {
144
- error("Qoder login did not complete. Run `qodercli login` and retry.");
168
+ const login = spawnQoderSync(executable, ["login"], { stdio: "inherit" });
169
+ if (login.status !== 0) {
170
+ const detail = login.error?.message || `exit status ${login.status}`;
171
+ error(`Qoder login command failed: ${detail}`);
172
+ error(`Run ${qoderLoginCommand(executable)} and retry.`);
173
+ return false;
174
+ }
175
+ if (!(await qoderAuthenticatedAfterLogin(executable))) {
176
+ error(`Qoder login did not complete. Run ${qoderLoginCommand(executable)} and retry.`);
145
177
  return false;
146
178
  }
147
179
  }
@@ -193,6 +225,12 @@ function processRunning(pid) {
193
225
  }
194
226
 
195
227
  function processFingerprint(pid) {
228
+ if (IS_WIN32) {
229
+ const result = spawnSync("wmic", [
230
+ "process", "where", `ProcessId=${pid}`, "get", "CreationDate,CommandLine", "/format:list",
231
+ ], { encoding: "utf8", timeout: 3000 });
232
+ return result.status === 0 ? (result.stdout || "").trim() : "";
233
+ }
196
234
  const result = spawnSync("ps", ["-p", String(pid), "-o", "lstart=", "-o", "command="], {
197
235
  encoding: "utf8",
198
236
  timeout: 2000,
@@ -201,6 +239,15 @@ function processFingerprint(pid) {
201
239
  }
202
240
 
203
241
  function legacyDaemonProcess(pid) {
242
+ if (IS_WIN32) {
243
+ const result = spawnSync("wmic", [
244
+ "process", "where", `ProcessId=${pid}`, "get", "ExecutablePath", "/format:list",
245
+ ], { encoding: "utf8", timeout: 3000 });
246
+ if (result.status !== 0) return false;
247
+ const output = (result.stdout || "").trim();
248
+ const normalized = DAEMON_BIN.replace(/\//g, "\\");
249
+ return output.includes(DAEMON_BIN) || output.includes(normalized);
250
+ }
204
251
  const result = spawnSync("ps", ["-p", String(pid), "-o", "command="], {
205
252
  encoding: "utf8",
206
253
  timeout: 2000,
@@ -309,7 +356,7 @@ function installBundledBinary(source, destination) {
309
356
  const temporary = `${destination}.tmp-${process.pid}-${crypto.randomUUID()}`;
310
357
  try {
311
358
  fs.copyFileSync(source, temporary);
312
- fs.chmodSync(temporary, 0o755);
359
+ safeChmod(temporary, 0o755);
313
360
  fs.renameSync(temporary, destination);
314
361
  } finally {
315
362
  try { fs.unlinkSync(temporary); } catch {}
@@ -329,7 +376,8 @@ async function installBinary(options = {}) {
329
376
  ensureDir(BIN_DIR);
330
377
 
331
378
  // Priority 1: bundled binary in npm package (vendor/)
332
- const bundledPath = path.join(BUNDLED_BIN_DIR, `autowonder-daemon-${platform}`);
379
+ const ext = IS_WIN32 ? ".exe" : "";
380
+ const bundledPath = path.join(BUNDLED_BIN_DIR, `autowonder-daemon-${platform}${ext}`);
333
381
  if (fs.existsSync(bundledPath)) {
334
382
  installBundledBinary(bundledPath, DAEMON_BIN);
335
383
  log(`Installed from bundled binary: ${DAEMON_BIN}`);
@@ -338,10 +386,10 @@ async function installBinary(options = {}) {
338
386
 
339
387
  // Priority 2: download from URL (OSS or CDN)
340
388
  if (BINARY_BASE_URL) {
341
- const url = `${BINARY_BASE_URL}/v${VERSION}/autowonder-daemon-${platform}`;
389
+ const url = `${BINARY_BASE_URL}/v${VERSION}/autowonder-daemon-${platform}${ext}`;
342
390
  const downloaded = await tryDownload(url, DAEMON_BIN);
343
391
  if (downloaded) {
344
- fs.chmodSync(DAEMON_BIN, 0o755);
392
+ safeChmod(DAEMON_BIN, 0o755);
345
393
  log(`Downloaded daemon binary: ${DAEMON_BIN}`);
346
394
  return true;
347
395
  }
@@ -389,7 +437,7 @@ function buildFromSource() {
389
437
  return false;
390
438
  }
391
439
 
392
- try { execFileSync("which", ["go"], { stdio: "ignore" }); } catch {
440
+ try { execFileSync(IS_WIN32 ? "where" : "which", ["go"], { stdio: "ignore" }); } catch {
393
441
  error("Go is not installed. Install Go 1.22+ or provide a pre-built binary.");
394
442
  error(" macOS: brew install go");
395
443
  error(" Linux: https://go.dev/dl/");
@@ -403,7 +451,7 @@ function buildFromSource() {
403
451
  log("Building daemon...");
404
452
  ensureDir(BIN_DIR);
405
453
  execFileSync("go", ["build", "-o", DAEMON_BIN, "./cmd/autowonder-daemon"], { cwd: tmpDir, stdio: "inherit" });
406
- fs.chmodSync(DAEMON_BIN, 0o755);
454
+ safeChmod(DAEMON_BIN, 0o755);
407
455
  log(`Built daemon binary: ${DAEMON_BIN}`);
408
456
  return true;
409
457
  } catch (e) {
@@ -601,6 +649,40 @@ async function cmdStop(options = {}) {
601
649
  return true;
602
650
  }
603
651
 
652
+ function httpGetJson(addr, urlPath, timeoutMs = 3000) {
653
+ return new Promise((resolve) => {
654
+ const request = http.get({ hostname: addr.split(":")[0], port: addr.split(":")[1], path: urlPath, timeout: timeoutMs }, (response) => {
655
+ let body = "";
656
+ response.setEncoding("utf8");
657
+ response.on("data", (chunk) => { body += chunk; });
658
+ response.on("end", () => {
659
+ try { resolve({ statusCode: response.statusCode, body: JSON.parse(body) }); } catch { resolve(null); }
660
+ });
661
+ });
662
+ request.on("timeout", () => request.destroy());
663
+ request.on("error", () => resolve(null));
664
+ });
665
+ }
666
+
667
+ function httpPostJson(addr, urlPath, body, timeoutMs = 10000) {
668
+ return new Promise((resolve) => {
669
+ const data = typeof body === "string" ? body : JSON.stringify(body);
670
+ const request = http.request({
671
+ hostname: addr.split(":")[0], port: addr.split(":")[1], path: urlPath, method: "POST", timeout: timeoutMs,
672
+ headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(data) },
673
+ }, (response) => {
674
+ let responseBody = "";
675
+ response.setEncoding("utf8");
676
+ response.on("data", (chunk) => { responseBody += chunk; });
677
+ response.on("end", () => resolve({ statusCode: response.statusCode, body: responseBody }));
678
+ });
679
+ request.on("timeout", () => request.destroy());
680
+ request.on("error", (e) => resolve({ statusCode: 0, body: e.message }));
681
+ request.write(data);
682
+ request.end();
683
+ });
684
+ }
685
+
604
686
  async function cmdStatus() {
605
687
  const running = daemonRunning();
606
688
  const pid = readPid();
@@ -616,23 +698,17 @@ async function cmdStatus() {
616
698
  console.log("");
617
699
 
618
700
  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 {}
701
+ const health = await httpGetJson(DEFAULT_API_ADDR, "/health");
702
+ if (health && health.statusCode === 200) {
703
+ console.log(` API: http://${DEFAULT_API_ADDR} (${health.body.status})`);
704
+ }
626
705
 
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
- }
706
+ const runtimes = await httpGetJson(DEFAULT_API_ADDR, "/runtimes");
707
+ if (runtimes && runtimes.statusCode === 200) {
708
+ for (const rt of (runtimes.body.runtimes || runtimes.body)) {
709
+ console.log(` Runtime: ${rt.id} (${rt.provider}) ${rt.available ? "available" : "unavailable"}${rt.version ? " v" + rt.version : ""}`);
634
710
  }
635
- } catch {}
711
+ }
636
712
  console.log("");
637
713
  }
638
714
  }
@@ -659,24 +735,14 @@ async function cmdDispatch(args) {
659
735
  }
660
736
 
661
737
  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") {
738
+ const res = await httpPostJson(DEFAULT_API_ADDR, "/dispatches/local", body);
739
+
740
+ if (res.statusCode === 200 || res.statusCode === 202) {
675
741
  log("Dispatch submitted successfully.");
676
- if (responseBody) console.log(responseBody);
742
+ if (res.body) console.log(res.body);
677
743
  } else {
678
- error(`Dispatch failed (HTTP ${statusCode})`);
679
- if (responseBody) console.error(responseBody);
744
+ error(`Dispatch failed (HTTP ${res.statusCode})`);
745
+ if (res.body) console.error(res.body);
680
746
  process.exit(1);
681
747
  }
682
748
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autowonder",
3
- "version": "0.2.115",
3
+ "version": "0.2.117",
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