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 +6 -6
- package/bin/cli.js +116 -50
- package/package.json +1 -1
- package/scripts/build-and-publish.sh +9 -5
- package/vendor/autowonder-daemon-darwin-amd64 +0 -0
- package/vendor/autowonder-daemon-darwin-arm64 +0 -0
- package/vendor/autowonder-daemon-linux-amd64 +0 -0
- package/vendor/autowonder-daemon-linux-arm64 +0 -0
- package/vendor/autowonder-daemon-win32-amd64.exe +0 -0
- package/vendor/autowonder-daemon-win32-arm64.exe +0 -0
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@
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
67
|
-
npx -y autowonder@0.2.
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
140
|
+
const deadline = Date.now() + qoderAuthRecheckTimeoutMs();
|
|
141
|
+
while (true) {
|
|
120
142
|
if (qoderAuthenticated(executable)) return true;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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 =
|
|
143
|
-
if (login.status !== 0
|
|
144
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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
|
-
}
|
|
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 =
|
|
663
|
-
|
|
664
|
-
|
|
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 (
|
|
742
|
+
if (res.body) console.log(res.body);
|
|
677
743
|
} else {
|
|
678
|
-
error(`Dispatch failed (HTTP ${statusCode})`);
|
|
679
|
-
if (
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|