autowonder 0.2.161 → 0.2.163
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 +23 -10
- package/bin/cli.js +77 -3
- package/bin/executor-environment.js +86 -0
- package/bin/qoder-bootstrap.js +59 -2
- package/package.json +1 -1
- 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,26 @@ AutoWonder 本地 agent runtime。安装后启动 daemon,持续轮询本地 as
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
# Qoder CLI(模型使用 provider model ID;Context Window 使用固定档位)
|
|
9
|
-
npx -y autowonder@0.2.
|
|
9
|
+
npx -y autowonder@0.2.163 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
|
# Qoder CLI CN
|
|
12
|
-
npx -y autowonder@0.2.
|
|
12
|
+
npx -y autowonder@0.2.163 connect --ws-url <wss-endpoint> --token <executor-token> --executor-id <executor-id> --provider qodercn
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
`connect` 会安装当前 npm 包内置的 daemon,并使用页面生成的 WebSocket endpoint、Token 和执行器 ID 建立连接。客户端只支持通过 Qoder 系列 CLI 启动(`qoder` 对应 `qodercli`,`qodercn` 对应 `qodercn`),其他 `--provider` 取值会在启动 daemon 前直接报错拦截并提示 `Unsupported provider`。选择 Qoder 时需要 Node.js 20+;runtime
|
|
15
|
+
`connect` 会安装当前 npm 包内置的 daemon,并使用页面生成的 WebSocket endpoint、Token 和执行器 ID 建立连接。客户端只支持通过 Qoder 系列 CLI 启动(`qoder` 对应 `qodercli`,`qodercn` 对应 `qodercn`),其他 `--provider` 取值会在启动 daemon 前直接报错拦截并提示 `Unsupported provider`。选择 Qoder CLI 或 Qoder CLI CN 时需要 Node.js 20+;runtime 会在缺少对应 CLI 时自动通过 npm 安装,并在尚未登录时打开浏览器登录。runtime 会继承当前用户的 HOME、环境变量和 CLI 登录状态。
|
|
16
|
+
|
|
17
|
+
### Qoder 系列 Access Token 认证
|
|
18
|
+
|
|
19
|
+
以下认证规则同时适用于 `connect --provider qoder` 和 `connect --provider qodercn`。runtime 启动时按以下顺序选择认证方式:
|
|
20
|
+
|
|
21
|
+
1. 对应 provider 的命令行参数:`qoder` 使用 `--QODER_PERSONAL_ACCESS_TOKEN`,`qodercn` 使用 `--QODERCN_PERSONAL_ACCESS_TOKEN`;两者都支持 `--KEY=<token>` 和 `--KEY <token>` 形式。
|
|
22
|
+
2. 当前进程中对应的原生环境变量:`QODER_PERSONAL_ACCESS_TOKEN` 或 `QODERCN_PERSONAL_ACCESS_TOKEN`。
|
|
23
|
+
3. 执行器绑定的数字员工当前在线版本中对应的同名 Platform 环境变量。
|
|
24
|
+
4. 均未配置时,沿用现有的浏览器 OAuth 登录。
|
|
25
|
+
|
|
26
|
+
runtime 启动时始终只拉取一次 Platform 环境变量快照,即使已有本地参数或环境变量也不会跳过;拉取或鉴权失败会直接导致启动失败,不会静默降级。命令行参数或当前进程环境变量提供的 token 会按启动需要保留给 daemon;Platform 预取的 token 仅临时用于启动认证,不会保留在 daemon 环境中。实际任务或 conversation 派发时,仍沿用现有机制注入并覆盖最新的 Platform 环境变量快照。
|
|
27
|
+
|
|
28
|
+
出于安全考虑,命令行 token 可能出现在 shell 历史和操作系统进程列表中,建议优先使用环境变量或 Platform 绑定。
|
|
16
29
|
|
|
17
30
|
`connect` 和 `start` 都要求 Qoder CLI / Qoder CLI CN 版本至少为 `1.1.50`。检测到旧版或无法解析的版本时,runtime 会对当前优先选中的用户 CLI 执行内置 `update`,原位升级到最新版并再次强校验;升级失败或升级后仍不满足最低版本时拒绝启动 daemon。所有 Qoder 系列子进程(普通任务、conversation、warm 会话、恢复回退、命令与模型探测)都会强制注入固定的 `QODER_INTEGRATION_ID`,调用方环境不能覆盖该流量归因标识。
|
|
18
31
|
|
|
@@ -119,7 +132,7 @@ mv "$queue/.assignment.tmp" "$queue/assignment.json"
|
|
|
119
132
|
也可以直接提交到本地 API:
|
|
120
133
|
|
|
121
134
|
```bash
|
|
122
|
-
npx -y autowonder@0.2.
|
|
135
|
+
npx -y autowonder@0.2.163 dispatch ./assignment.json
|
|
123
136
|
```
|
|
124
137
|
|
|
125
138
|
## 上传工单附件
|
|
@@ -127,7 +140,7 @@ npx -y autowonder@0.2.161 dispatch ./assignment.json
|
|
|
127
140
|
把本地需求/设计文档直接上传到工单,避免在会话里传递大文件:
|
|
128
141
|
|
|
129
142
|
```bash
|
|
130
|
-
npx -y autowonder@0.2.
|
|
143
|
+
npx -y autowonder@0.2.163 workitem upload --server-url <autowonder-server-url> --workitem-id <id> --file <filepath-1> --file <filepath-2> --json
|
|
131
144
|
```
|
|
132
145
|
|
|
133
146
|
上传令牌通过 MCP 工具 `autowonder.workitem_cli_upload_token` 签发,经 `--token` 或 `AUTOWONDER_UPLOAD_TOKEN` 环境变量传入。`--file` 可重复指定多个文件;`--json` 输出机器可读结果。失败时按错误类型返回不同退出码(401→3、403→4、404→5、409→6、413→7、其他 4xx→8、网络/重定向→9)。
|
|
@@ -137,7 +150,7 @@ npx -y autowonder@0.2.161 workitem upload --server-url <autowonder-server-url> -
|
|
|
137
150
|
把工单上已有的需求/设计文档下载到本地目录,再用本地文件工具阅读,避免把大文件或二进制附件搬进会话上下文:
|
|
138
151
|
|
|
139
152
|
```bash
|
|
140
|
-
npx -y autowonder@0.2.
|
|
153
|
+
npx -y autowonder@0.2.163 workitem download --server-url <autowonder-server-url> --workitem-id <id> --file <name-or-id> --output-dir <dir> --json
|
|
141
154
|
```
|
|
142
155
|
|
|
143
156
|
下载令牌通过 MCP 工具 `autowonder.workitem_cli_download_token` 签发(与上传令牌相互独立),经 `--token` 或 `AUTOWONDER_DOWNLOAD_TOKEN` 环境变量传入。`--file` 可重复指定文档名或 artifact id,省略则下载该工单的全部附件;`--output-dir` 默认为当前目录;`--json` 输出机器可读结果,含每个文件的 `id`、`name`、`path`、`size`。退出码约定与 `workitem upload` 相同(401→3、403→4、404→5、409→6、413→7、其他 4xx→8、网络/重定向→9)。
|
|
@@ -147,7 +160,7 @@ npx -y autowonder@0.2.161 workitem download --server-url <autowonder-server-url>
|
|
|
147
160
|
把需求/设计文档上传到 7x24 定时任务,用法与工单附件一致(同一套上传令牌与预检规则):
|
|
148
161
|
|
|
149
162
|
```bash
|
|
150
|
-
npx -y autowonder@0.2.
|
|
163
|
+
npx -y autowonder@0.2.163 scheduled-task upload --server-url <autowonder-server-url> --scheduled-task-id <id> --file <filepath-1> --file <filepath-2> --json
|
|
151
164
|
```
|
|
152
165
|
|
|
153
166
|
上传令牌同样通过 `autowonder.workitem_cli_upload_token` 签发(令牌为用户级,对当前可修改的任意定时任务生效),经 `--token` 或 `AUTOWONDER_UPLOAD_TOKEN` 传入;退出码约定与 `workitem upload` 相同。
|
|
@@ -155,8 +168,8 @@ npx -y autowonder@0.2.161 scheduled-task upload --server-url <autowonder-server-
|
|
|
155
168
|
## 管理 daemon
|
|
156
169
|
|
|
157
170
|
```bash
|
|
158
|
-
npx -y autowonder@0.2.
|
|
159
|
-
npx -y autowonder@0.2.
|
|
171
|
+
npx -y autowonder@0.2.163 status
|
|
172
|
+
npx -y autowonder@0.2.163 stop
|
|
160
173
|
```
|
|
161
174
|
|
|
162
175
|
默认 API 是 `http://127.0.0.1:34989`,日志位于 `~/.autowonder/daemon.log`。npm 包不包含任何 agent、MCP 或服务端凭证。
|
|
@@ -164,7 +177,7 @@ npx -y autowonder@0.2.161 stop
|
|
|
164
177
|
## 调试
|
|
165
178
|
|
|
166
179
|
```bash
|
|
167
|
-
npx -y autowonder@0.2.
|
|
180
|
+
npx -y autowonder@0.2.163 connect ... --debug
|
|
168
181
|
```
|
|
169
182
|
|
|
170
183
|
`--debug` 会让 CLI 以 `AUTOWONDER_DEBUG=1` 启动 daemon,把完整的 daemon 与 agent 活动(runtime 事件流、provider 事件解码、重试退避与会话门等待、空闲时的阻塞点)同时输出到控制台和会话调试日志。调试日志不做脱敏,仅用于本机排障;不传 `--debug` 时该环境变量不会注入。
|
package/bin/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
findQoderExecutable,
|
|
16
16
|
isQoderFamilyProvider,
|
|
17
17
|
} = require("./qoder-bootstrap");
|
|
18
|
+
const { fetchExecutorEnvironment } = require("./executor-environment");
|
|
18
19
|
|
|
19
20
|
const VERSION = require("../package.json").version;
|
|
20
21
|
const AUTOWONDER_HOME = path.join(os.homedir(), ".autowonder");
|
|
@@ -329,13 +330,32 @@ function buildFromSource() {
|
|
|
329
330
|
// ─── Commands ────────────────────────────────────────────────────────
|
|
330
331
|
|
|
331
332
|
async function cmdConnect(args) {
|
|
332
|
-
const
|
|
333
|
+
const normalizedArgs = args.flatMap((arg) => {
|
|
334
|
+
for (const key of ["QODER_PERSONAL_ACCESS_TOKEN", "QODERCN_PERSONAL_ACCESS_TOKEN"]) {
|
|
335
|
+
const prefix = `--${key}=`;
|
|
336
|
+
if (arg.startsWith(prefix)) return [`--${key}`, arg.slice(prefix.length)];
|
|
337
|
+
}
|
|
338
|
+
return [arg];
|
|
339
|
+
});
|
|
340
|
+
const flags = parseFlags(normalizedArgs, ["ws-url", "token", "executor-id", "provider", "name", "max-tasks", "model", "reasoning-effort", "context-window", "memory-mode", "settings", "workspace-root", "token-aware-enable", "QODER_PERSONAL_ACCESS_TOKEN", "QODERCN_PERSONAL_ACCESS_TOKEN"]);
|
|
341
|
+
|
|
342
|
+
const requestedAccessTokenEnv = flags.provider === "qodercn"
|
|
343
|
+
? "QODERCN_PERSONAL_ACCESS_TOKEN"
|
|
344
|
+
: "QODER_PERSONAL_ACCESS_TOKEN";
|
|
345
|
+
const hasRequestedAccessToken = Object.prototype.hasOwnProperty.call(flags, requestedAccessTokenEnv);
|
|
346
|
+
|
|
347
|
+
if (
|
|
348
|
+
hasRequestedAccessToken &&
|
|
349
|
+
(flags[requestedAccessTokenEnv] === "true" || !flags[requestedAccessTokenEnv].trim())
|
|
350
|
+
) {
|
|
351
|
+
error(`--${requestedAccessTokenEnv} requires a non-empty value`);
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
333
354
|
|
|
334
355
|
if (!flags["ws-url"] || !flags.token || !flags["executor-id"]) {
|
|
335
356
|
error("Usage: autowonder connect --ws-url <url> --token <token> --executor-id <id> [--provider qoder|qodercn] [--workspace-root <path>]");
|
|
336
357
|
process.exit(1);
|
|
337
358
|
}
|
|
338
|
-
|
|
339
359
|
const requestedProvider = flags.provider;
|
|
340
360
|
if (requestedProvider) assertSupportedProvider(requestedProvider);
|
|
341
361
|
if (isQoderFamilyProvider(requestedProvider) && !validateQoderOptions(flags)) process.exit(1);
|
|
@@ -347,7 +367,55 @@ async function cmdConnect(args) {
|
|
|
347
367
|
process.exit(1);
|
|
348
368
|
}
|
|
349
369
|
assertSupportedProvider(provider);
|
|
350
|
-
|
|
370
|
+
let selectedAccessTokenEnv = "";
|
|
371
|
+
let hasExplicitAccessToken = false;
|
|
372
|
+
let inheritedAccessToken;
|
|
373
|
+
if (isQoderFamilyProvider(provider)) {
|
|
374
|
+
selectedAccessTokenEnv = provider === "qodercn"
|
|
375
|
+
? "QODERCN_PERSONAL_ACCESS_TOKEN"
|
|
376
|
+
: "QODER_PERSONAL_ACCESS_TOKEN";
|
|
377
|
+
hasExplicitAccessToken = Object.prototype.hasOwnProperty.call(flags, selectedAccessTokenEnv);
|
|
378
|
+
const inheritedAccessTokenPresent = Object.prototype.hasOwnProperty.call(process.env, selectedAccessTokenEnv);
|
|
379
|
+
inheritedAccessToken = process.env[selectedAccessTokenEnv];
|
|
380
|
+
const nodeVersion = process.env.NODE_ENV === "test" && process.env.AUTOWONDER_TEST_NODE_VERSION
|
|
381
|
+
? process.env.AUTOWONDER_TEST_NODE_VERSION
|
|
382
|
+
: process.versions.node;
|
|
383
|
+
const nodeMajor = Number.parseInt(nodeVersion.split(".")[0], 10);
|
|
384
|
+
if (!Number.isInteger(nodeMajor) || nodeMajor < 20) {
|
|
385
|
+
const displayName = provider === "qodercn" ? "Qoder CLI CN" : "Qoder CLI";
|
|
386
|
+
error(`${displayName} requires Node.js 20 or newer; current version is ${nodeVersion}. Upgrade Node.js and retry.`);
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const fetchOptions = {
|
|
391
|
+
wsUrl: flags["ws-url"],
|
|
392
|
+
executorId: flags["executor-id"],
|
|
393
|
+
executorToken: flags.token,
|
|
394
|
+
};
|
|
395
|
+
if (process.env.NODE_ENV === "test" && process.env.AUTOWONDER_TEST_EXECUTOR_ENVIRONMENT_TIMEOUT_MS) {
|
|
396
|
+
fetchOptions.timeoutMs = Number.parseInt(process.env.AUTOWONDER_TEST_EXECUTOR_ENVIRONMENT_TIMEOUT_MS, 10) || 1;
|
|
397
|
+
}
|
|
398
|
+
const platformEnvironment = await fetchExecutorEnvironment(fetchOptions);
|
|
399
|
+
const candidates = [
|
|
400
|
+
hasExplicitAccessToken ? flags[selectedAccessTokenEnv] : "",
|
|
401
|
+
inheritedAccessToken,
|
|
402
|
+
platformEnvironment[selectedAccessTokenEnv],
|
|
403
|
+
];
|
|
404
|
+
const selectedQoderToken = candidates.find((value) => typeof value === "string" && value.trim());
|
|
405
|
+
const ignoredBlankInheritedToken = inheritedAccessTokenPresent && !selectedQoderToken;
|
|
406
|
+
if (ignoredBlankInheritedToken) delete process.env[selectedAccessTokenEnv];
|
|
407
|
+
let qoderReady = false;
|
|
408
|
+
try {
|
|
409
|
+
qoderReady = await ensureQoderReady(provider, {
|
|
410
|
+
debug: Boolean(flags.debug),
|
|
411
|
+
authenticationMode: selectedQoderToken ? "access_token" : "oauth",
|
|
412
|
+
accessToken: selectedQoderToken || null,
|
|
413
|
+
});
|
|
414
|
+
} finally {
|
|
415
|
+
if (ignoredBlankInheritedToken) process.env[selectedAccessTokenEnv] = inheritedAccessToken;
|
|
416
|
+
}
|
|
417
|
+
if (!qoderReady) process.exit(1);
|
|
418
|
+
}
|
|
351
419
|
|
|
352
420
|
// A version-pinned npx command must always run the binary bundled with that package.
|
|
353
421
|
// Windows locks a running .exe. Its versioned path is immutable, so another
|
|
@@ -429,6 +497,12 @@ async function cmdConnect(args) {
|
|
|
429
497
|
if (flags["context-window"]) env.AUTOWONDER_CONTEXT_WINDOW = flags["context-window"];
|
|
430
498
|
if (config.claudeSettingsPath) env.AUTOWONDER_CLAUDE_SETTINGS = config.claudeSettingsPath;
|
|
431
499
|
if (flags.debug) env.AUTOWONDER_DEBUG = "1";
|
|
500
|
+
if (isQoderFamilyProvider(provider) && hasExplicitAccessToken) {
|
|
501
|
+
env[selectedAccessTokenEnv] = flags[selectedAccessTokenEnv];
|
|
502
|
+
} else if (isQoderFamilyProvider(provider)
|
|
503
|
+
&& !(typeof inheritedAccessToken === "string" && inheritedAccessToken.trim())) {
|
|
504
|
+
delete env[selectedAccessTokenEnv];
|
|
505
|
+
}
|
|
432
506
|
|
|
433
507
|
// --token-aware-enable: start token-proxy as a sidecar (bypass, non-blocking)
|
|
434
508
|
let tokenProxyChild = null;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
class ExecutorEnvironmentError extends Error {}
|
|
4
|
+
|
|
5
|
+
function executorEnvironmentURL(wsUrl, executorId) {
|
|
6
|
+
let url;
|
|
7
|
+
try {
|
|
8
|
+
url = new URL(wsUrl);
|
|
9
|
+
} catch {
|
|
10
|
+
throw new Error("Invalid executor WebSocket URL");
|
|
11
|
+
}
|
|
12
|
+
if (url.protocol === "ws:") {
|
|
13
|
+
url.protocol = "http:";
|
|
14
|
+
} else if (url.protocol === "wss:") {
|
|
15
|
+
url.protocol = "https:";
|
|
16
|
+
} else {
|
|
17
|
+
throw new Error(`Unsupported executor WebSocket protocol: ${url.protocol}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
url.pathname = `/api/daemon/executors/${encodeURIComponent(executorId)}/environment-variables`;
|
|
21
|
+
url.search = "";
|
|
22
|
+
url.hash = "";
|
|
23
|
+
return url.toString();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function fetchExecutorEnvironment({ wsUrl, executorId, executorToken, timeoutMs = 5000 }) {
|
|
27
|
+
const url = executorEnvironmentURL(wsUrl, executorId);
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(url, {
|
|
33
|
+
method: "GET",
|
|
34
|
+
headers: {
|
|
35
|
+
Authorization: `Bearer ${executorToken}`,
|
|
36
|
+
Accept: "application/json",
|
|
37
|
+
},
|
|
38
|
+
cache: "no-store",
|
|
39
|
+
redirect: "error",
|
|
40
|
+
signal: controller.signal,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
if (response.body) {
|
|
45
|
+
try {
|
|
46
|
+
await response.body.cancel();
|
|
47
|
+
} catch {}
|
|
48
|
+
}
|
|
49
|
+
throw new ExecutorEnvironmentError(`Executor environment request failed with status ${response.status}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let body;
|
|
53
|
+
try {
|
|
54
|
+
body = await response.json();
|
|
55
|
+
} catch {
|
|
56
|
+
if (controller.signal.aborted) {
|
|
57
|
+
throw new ExecutorEnvironmentError(`Executor environment request timed out after ${timeoutMs}ms`);
|
|
58
|
+
}
|
|
59
|
+
throw new ExecutorEnvironmentError("Invalid executor environment response");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
body === null ||
|
|
64
|
+
typeof body !== "object" ||
|
|
65
|
+
Array.isArray(body) ||
|
|
66
|
+
body.environmentVariables === null ||
|
|
67
|
+
typeof body.environmentVariables !== "object" ||
|
|
68
|
+
Array.isArray(body.environmentVariables) ||
|
|
69
|
+
Object.values(body.environmentVariables).some((value) => typeof value !== "string")
|
|
70
|
+
) {
|
|
71
|
+
throw new ExecutorEnvironmentError("Invalid executor environment response");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { ...body.environmentVariables };
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (controller.signal.aborted) {
|
|
77
|
+
throw new Error(`Executor environment request timed out after ${timeoutMs}ms`);
|
|
78
|
+
}
|
|
79
|
+
if (error instanceof ExecutorEnvironmentError) throw error;
|
|
80
|
+
throw new Error("Failed to fetch executor environment");
|
|
81
|
+
} finally {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { executorEnvironmentURL, fetchExecutorEnvironment };
|
package/bin/qoder-bootstrap.js
CHANGED
|
@@ -46,6 +46,7 @@ const QODER_FAMILY_PROFILES = {
|
|
|
46
46
|
managedDirName: "qoder-cli",
|
|
47
47
|
managedConfigRootName: "qoder-home",
|
|
48
48
|
configDirEnv: "QODER_CONFIG_DIR",
|
|
49
|
+
accessTokenEnv: "QODER_PERSONAL_ACCESS_TOKEN",
|
|
49
50
|
defaultConfigRootName: ".qoder",
|
|
50
51
|
managedBinName: "qodercli",
|
|
51
52
|
windowsShimName: "qodercli.cmd",
|
|
@@ -63,6 +64,7 @@ const QODER_FAMILY_PROFILES = {
|
|
|
63
64
|
managedDirName: "qoder-cn-cli",
|
|
64
65
|
managedConfigRootName: "qoder-cn-home",
|
|
65
66
|
configDirEnv: "QODERCN_CONFIG_DIR",
|
|
67
|
+
accessTokenEnv: "QODERCN_PERSONAL_ACCESS_TOKEN",
|
|
66
68
|
defaultConfigRootName: ".qoder-cn",
|
|
67
69
|
managedBinName: "qodercn",
|
|
68
70
|
windowsShimName: "qodercn.cmd",
|
|
@@ -671,12 +673,24 @@ function cliUnusableReason(probe) {
|
|
|
671
673
|
// against the config root under evaluation, otherwise the verdict would
|
|
672
674
|
// describe whatever root the ambient environment points at.
|
|
673
675
|
function probeQoderAuth(profile, executable, configRoot, options = {}) {
|
|
676
|
+
const probeEnv = { ...process.env, [profile.configDirEnv]: configRoot };
|
|
677
|
+
const hasPrivateAccessToken = typeof options.accessToken === "string" && Boolean(options.accessToken.trim());
|
|
678
|
+
if (Object.prototype.hasOwnProperty.call(options, "accessToken")) {
|
|
679
|
+
if (hasPrivateAccessToken) {
|
|
680
|
+
probeEnv[profile.accessTokenEnv] = options.accessToken;
|
|
681
|
+
} else {
|
|
682
|
+
delete probeEnv[profile.accessTokenEnv];
|
|
683
|
+
}
|
|
684
|
+
}
|
|
674
685
|
const result = spawnQoderSync(executable, ["--list-models"], {
|
|
675
686
|
encoding: "utf8",
|
|
676
687
|
stdio: ["ignore", "pipe", "pipe"],
|
|
677
688
|
timeout: 20000,
|
|
689
|
+
// Keep the trusted timeout runner in the probe path so timed-out Qoder
|
|
690
|
+
// descendants are reclaimed. This invocation is the only one whose child
|
|
691
|
+
// environment receives a private flag/platform token.
|
|
678
692
|
treeTimeout: true,
|
|
679
|
-
env:
|
|
693
|
+
env: probeEnv,
|
|
680
694
|
});
|
|
681
695
|
const probe = {
|
|
682
696
|
ok: result.status === 0 && Boolean((result.stdout || "").trim()),
|
|
@@ -739,6 +753,29 @@ function inspectQoderHealth(profile, executable, configRoot, options = {}) {
|
|
|
739
753
|
return verdict("ready", "");
|
|
740
754
|
}
|
|
741
755
|
|
|
756
|
+
// Access-token authentication is independent of the OAuth credential store.
|
|
757
|
+
// Keep probe output private because a failed CLI may echo the supplied token in
|
|
758
|
+
// its diagnostics, and require two successes to establish stable readiness.
|
|
759
|
+
function inspectQoderAccessToken(profile, executable, configRoot, options = {}) {
|
|
760
|
+
const probeOptions = { ...options, debug: false };
|
|
761
|
+
const probeOne = probeQoderAuth(profile, executable, configRoot, probeOptions);
|
|
762
|
+
if (isCliUnusable(probeOne)) {
|
|
763
|
+
return { state: "cli_unusable", reason: cliUnusableReason(probeOne) };
|
|
764
|
+
}
|
|
765
|
+
if (!probeOne.ok) {
|
|
766
|
+
return { state: "token_unusable", reason: "the access token authentication probe failed" };
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const probeTwo = probeQoderAuth(profile, executable, configRoot, probeOptions);
|
|
770
|
+
if (isCliUnusable(probeTwo)) {
|
|
771
|
+
return { state: "cli_unusable", reason: cliUnusableReason(probeTwo) };
|
|
772
|
+
}
|
|
773
|
+
if (!probeTwo.ok) {
|
|
774
|
+
return { state: "token_unusable", reason: "the second access token authentication probe failed" };
|
|
775
|
+
}
|
|
776
|
+
return { state: "ready", reason: "" };
|
|
777
|
+
}
|
|
778
|
+
|
|
742
779
|
function describeMachineId(read) {
|
|
743
780
|
if (read.unreadable) return `unreadable (${read.unreadable})`;
|
|
744
781
|
return read.present ? read.value : "absent";
|
|
@@ -900,6 +937,9 @@ function finishQoderReady(profile, provider, executable, version, configRoot, ro
|
|
|
900
937
|
async function ensureQoderReady(provider, options = {}) {
|
|
901
938
|
const profile = QODER_FAMILY_PROFILES[provider];
|
|
902
939
|
const inspectOptions = { debug: Boolean(options.debug) };
|
|
940
|
+
if (Object.prototype.hasOwnProperty.call(options, "accessToken")) {
|
|
941
|
+
inspectOptions.accessToken = options.accessToken;
|
|
942
|
+
}
|
|
903
943
|
|
|
904
944
|
const nodeVersion = qoderNodeVersion();
|
|
905
945
|
const nodeMajor = Number.parseInt(nodeVersion.split(".")[0], 10);
|
|
@@ -926,11 +966,27 @@ async function ensureQoderReady(provider, options = {}) {
|
|
|
926
966
|
let version = versionGate.version;
|
|
927
967
|
|
|
928
968
|
const paths = qoderPaths(profile);
|
|
929
|
-
const
|
|
969
|
+
const explicitConfigRoot = process.env[profile.configDirEnv];
|
|
970
|
+
const selection = options.authenticationMode === "access_token"
|
|
971
|
+
? (explicitConfigRoot
|
|
972
|
+
? { root: resolveUserPath(explicitConfigRoot), source: "explicit" }
|
|
973
|
+
: fs.existsSync(paths.managedConfigRoot)
|
|
974
|
+
? { root: paths.managedConfigRoot, source: "managed" }
|
|
975
|
+
: { root: paths.defaultConfigRoot, source: "default" })
|
|
976
|
+
: selectQoderConfigRoot(profile, executable, inspectOptions);
|
|
930
977
|
let configRoot = selection.root;
|
|
931
978
|
let rootSource = selection.source;
|
|
932
979
|
applyQoderConfigRoot(profile, rootSource, configRoot);
|
|
933
980
|
|
|
981
|
+
if (options.authenticationMode === "access_token") {
|
|
982
|
+
const tokenVerdict = inspectQoderAccessToken(profile, executable, configRoot, inspectOptions);
|
|
983
|
+
if (tokenVerdict.state === "ready") {
|
|
984
|
+
return finishQoderReady(profile, provider, executable, version, configRoot, rootSource);
|
|
985
|
+
}
|
|
986
|
+
error(`${profile.displayName} access token authentication failed (${tokenVerdict.reason}).`);
|
|
987
|
+
return false;
|
|
988
|
+
}
|
|
989
|
+
|
|
934
990
|
const previous = readQoderState(profile)[provider] || null;
|
|
935
991
|
const identityChanged = Boolean(previous
|
|
936
992
|
&& (previous.resolvedPath !== executable || previous.version !== version));
|
|
@@ -1036,6 +1092,7 @@ module.exports = {
|
|
|
1036
1092
|
ensureQoderReady,
|
|
1037
1093
|
findInInstallDirs,
|
|
1038
1094
|
findQoderExecutable,
|
|
1095
|
+
inspectQoderAccessToken,
|
|
1039
1096
|
inspectQoderHealth,
|
|
1040
1097
|
installQoderCLIUnlocked,
|
|
1041
1098
|
isQoderFamilyProvider,
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|