autowonder 0.3.17 → 0.3.19
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 +37 -16
- package/bin/cli.js +8 -5
- package/bin/token-proxy.js +115 -6
- 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,10 +6,10 @@ AutoWonder 本地 agent runtime。安装后启动 daemon,持续轮询本地 as
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
# Qoder CLI(模型使用 provider model ID;Context Window 使用固定档位)
|
|
9
|
-
npx -y autowonder@0.3.
|
|
9
|
+
npx -y autowonder@0.3.19 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.3.
|
|
12
|
+
npx -y autowonder@0.3.19 connect --ws-url <wss-endpoint> --token <executor-token> --executor-id <executor-id> --provider qodercn
|
|
13
13
|
```
|
|
14
14
|
|
|
15
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 登录状态。
|
|
@@ -99,20 +99,26 @@ daemon 重启或崩溃后重新接管未完成的派单时,runtime 不会直
|
|
|
99
99
|
|
|
100
100
|
## 自动更新
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
daemon 不会自行追新:后台自动更新策略默认关闭,且没有环境变量可以重新开启,因此 daemon 不会主动查询 npm 的 `autowonder/latest`、下载、安装或重启。版本由平台决定,见下文的服务端指定版本升级;没有服务端升级指令时,即使本地配置了更短的检查周期也不会产生任何 npm 请求。
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
升级能力与后台策略是两件事:标准安装的正式版 daemon 仍会初始化升级器并在心跳里上报远程升级能力,因此平台可以下发指定版本的升级指令。收到 `EXECUTOR_UPGRADE`(或 `EXECUTOR_RESTART` 且 `update=true`)后,daemon 才会按指令解析 npm tarball、校验 SHA-512 SRI、包版本和平台二进制;只有在没有运行/恢复任务且结果 outbox 已收到服务端确认时,才会暂停接单、原子切换二进制并重启。启动失败会恢复上一个二进制。
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
# 禁用自动更新
|
|
108
|
-
AUTOWONDER_AUTO_UPDATE=false npx -y autowonder@latest connect ...
|
|
106
|
+
源码构建、开发版本、非标准安装路径、预发布版本和 Windows 不上报升级能力,也不支持远端升级。
|
|
109
107
|
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
```bash
|
|
109
|
+
# 使用兼容的 npm registry(仅影响按平台指令解析版本时使用的源)
|
|
112
110
|
AUTOWONDER_NPM_REGISTRY=https://registry.npmjs.org \
|
|
113
111
|
npx -y autowonder@latest connect ...
|
|
114
112
|
```
|
|
115
113
|
|
|
114
|
+
## 在线应用启动命令
|
|
115
|
+
|
|
116
|
+
支持版本的 Linux/macOS 执行器会声明 `EXECUTOR_LAUNCH_COMMAND_V1`。管理员可在平台“启动命令 → 编辑完整命令”中修改固定发布版本、`--workspace-root`、`--name`、`--debug` 及已有启动参数,保存后在线应用。连接地址、Token、执行器 ID、provider 和 Token 代理参数不可修改,不执行任意 Shell 命令。
|
|
117
|
+
|
|
118
|
+
同版本空闲时用新参数 exec;切换到更高稳定版本要求二进制安装在 `~/.autowonder/bin/autowonder-daemon`,复用 npm tarball SHA-512、包版本、平台二进制和候选自检,再原子切换。失败保留原程序并在支持的故障路径回滚,不支持在线降级。源码构建仅支持保持当前版本的参数应用,Windows 不声明在线应用能力。旧客户端需要先本地安装一次支持版本。
|
|
119
|
+
|
|
120
|
+
工作目录切换前必须没有运行/恢复任务,且原目录的结果 outbox 已全部确认;默认本地队列仍有待执行或已领取任务时也会拒绝切换。目标目录必须可写;历史任务文件保留在原目录,不自动迁移或删除。未指定目录和名称时在线应用沿用当前值,仅更新其他参数不会移动本地队列。
|
|
121
|
+
|
|
116
122
|
候选版本缓存在 `~/.autowonder/updates/`;当前二进制和单个回滚副本位于 `~/.autowonder/bin/`。`GET /health` 会返回 `daemonVersion`、`updateState`、`updateTarget` 和 `updateError`。
|
|
117
123
|
|
|
118
124
|
daemon 建立连接时和每次心跳定时检查时发出的 `HEARTBEAT` 帧都带上 `version` 字段,取值为该二进制构建时注入的发布版本号(`currentReleaseIdentity().Version`,与 `--self-check` 报告的版本同源);版本为空时该字段不出现在帧里。服务端据此记录每台执行机实际运行的客户端版本并在执行器列表展示,为后续客户端批量升级提供数据基础。对执行机用户无新增参数或环境变量。
|
|
@@ -167,7 +173,7 @@ mv "$queue/.assignment.tmp" "$queue/assignment.json"
|
|
|
167
173
|
也可以直接提交到本地 API:
|
|
168
174
|
|
|
169
175
|
```bash
|
|
170
|
-
npx -y autowonder@0.3.
|
|
176
|
+
npx -y autowonder@0.3.19 dispatch ./assignment.json
|
|
171
177
|
```
|
|
172
178
|
|
|
173
179
|
## 上传工单附件
|
|
@@ -175,7 +181,7 @@ npx -y autowonder@0.3.17 dispatch ./assignment.json
|
|
|
175
181
|
把本地需求/设计文档直接上传到工单,避免在会话里传递大文件:
|
|
176
182
|
|
|
177
183
|
```bash
|
|
178
|
-
npx -y autowonder@0.3.
|
|
184
|
+
npx -y autowonder@0.3.19 workitem upload --server-url <autowonder-server-url> --workitem-id <id> --file <filepath-1> --file <filepath-2> --json
|
|
179
185
|
```
|
|
180
186
|
|
|
181
187
|
上传令牌通过 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)。
|
|
@@ -185,7 +191,7 @@ npx -y autowonder@0.3.17 workitem upload --server-url <autowonder-server-url> --
|
|
|
185
191
|
把工单上已有的需求/设计文档下载到本地目录,再用本地文件工具阅读,避免把大文件或二进制附件搬进会话上下文:
|
|
186
192
|
|
|
187
193
|
```bash
|
|
188
|
-
npx -y autowonder@0.3.
|
|
194
|
+
npx -y autowonder@0.3.19 workitem download --server-url <autowonder-server-url> --workitem-id <id> --file <name-or-id> --output-dir <dir> --json
|
|
189
195
|
```
|
|
190
196
|
|
|
191
197
|
下载令牌通过 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)。
|
|
@@ -195,7 +201,7 @@ npx -y autowonder@0.3.17 workitem download --server-url <autowonder-server-url>
|
|
|
195
201
|
把需求/设计文档上传到 7x24 定时任务,用法与工单附件一致(同一套上传令牌与预检规则):
|
|
196
202
|
|
|
197
203
|
```bash
|
|
198
|
-
npx -y autowonder@0.3.
|
|
204
|
+
npx -y autowonder@0.3.19 scheduled-task upload --server-url <autowonder-server-url> --scheduled-task-id <id> --file <filepath-1> --file <filepath-2> --json
|
|
199
205
|
```
|
|
200
206
|
|
|
201
207
|
上传令牌同样通过 `autowonder.workitem_cli_upload_token` 签发(令牌为用户级,对当前可修改的任意定时任务生效),经 `--token` 或 `AUTOWONDER_UPLOAD_TOKEN` 传入;退出码约定与 `workitem upload` 相同。
|
|
@@ -203,16 +209,31 @@ npx -y autowonder@0.3.17 scheduled-task upload --server-url <autowonder-server-u
|
|
|
203
209
|
## 管理 daemon
|
|
204
210
|
|
|
205
211
|
```bash
|
|
206
|
-
npx -y autowonder@0.3.
|
|
207
|
-
npx -y autowonder@0.3.
|
|
212
|
+
npx -y autowonder@0.3.19 status
|
|
213
|
+
npx -y autowonder@0.3.19 stop
|
|
208
214
|
```
|
|
209
215
|
|
|
210
216
|
默认 API 是 `http://127.0.0.1:34989`,日志位于 `~/.autowonder/daemon.log`。npm 包不包含任何 agent、MCP 或服务端凭证。
|
|
211
217
|
|
|
218
|
+
## 日志轮转与保留
|
|
219
|
+
|
|
220
|
+
daemon 会话日志、`cli start` 重定向的 `~/.autowonder/daemon.log`、service 的 stdout/stderr 日志以及 token-proxy 诊断日志都会按大小与日期轮转,且每类日志只有一个轮转写入方。轮转采用改名后重开同一路径的方式,不会原地截断,也不会删除仍被其他进程打开的文件;改名失败时继续写当前文件,单次失败不会关闭日志。
|
|
221
|
+
|
|
222
|
+
daemon 内置独立于工作区 GC 的保留清扫:默认每小时运行一次,删除修改时间超过 TTL(默认 72h)的已关闭日志。运行中写入方的活动文件始终排除,单个文件处理失败只记录并继续,符号链接与目录不会被跟随。任务 capsule 内的日志、token 计量数据、CA 证书与 `config.json` 不属于托管日志,不会被自动删除。
|
|
223
|
+
|
|
224
|
+
可用环境变量调整:
|
|
225
|
+
|
|
226
|
+
- `AUTOWONDER_LOG_MAX_BYTES=104857600`(100 MiB):托管日志的轮转阈值
|
|
227
|
+
- `AUTOWONDER_LOG_RETENTION=72h`:保留 TTL,显式设为 `0` 关闭清扫
|
|
228
|
+
- `AUTOWONDER_LOG_SWEEP_INTERVAL=1h`:清扫周期
|
|
229
|
+
- `AUTOWONDER_RAW_LOG_MAX_BYTES=104857600`(100 MiB):`AUTOWONDER_DEBUG=1` 时 token-tap 原始响应抓包的单文件上限,达到上限后停止写入,但不影响隧道继续转发
|
|
230
|
+
|
|
231
|
+
取值格式非法时回落到默认值。
|
|
232
|
+
|
|
212
233
|
## 调试
|
|
213
234
|
|
|
214
235
|
```bash
|
|
215
|
-
npx -y autowonder@0.3.
|
|
236
|
+
npx -y autowonder@0.3.19 connect ... --debug
|
|
216
237
|
```
|
|
217
238
|
|
|
218
239
|
`--debug` 会让 CLI 以 `AUTOWONDER_DEBUG=1` 启动 daemon,把完整的 daemon 与 agent 活动(runtime 事件流、provider 事件解码、重试退避与会话门等待、空闲时的阻塞点)同时输出到控制台和会话调试日志。调试日志不做脱敏,仅用于本机排障;不传 `--debug` 时该环境变量不会注入。
|
package/bin/cli.js
CHANGED
|
@@ -512,15 +512,14 @@ async function cmdConnect(args) {
|
|
|
512
512
|
const proxyPort = parseInt(process.env.TOKEN_PROXY_PORT || "19876", 10);
|
|
513
513
|
const caCertPath = path.join(AUTOWONDER_HOME, "token-tap", "ca-cert.pem");
|
|
514
514
|
try {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
: ["ignore", "ignore", "ignore"];
|
|
515
|
+
// The proxy owns its own rotating diagnostic log, so the launcher keeps
|
|
516
|
+
// all three streams on /dev/null and never holds a second unbounded
|
|
517
|
+
// handle on proxy-diag.log.
|
|
519
518
|
const proxyEnv = { ...process.env };
|
|
520
519
|
if (flags.debug) proxyEnv.AUTOWONDER_DEBUG = "1";
|
|
521
520
|
tokenProxyChild = spawn(process.execPath, [tokenProxyScript, "start", "--port", String(proxyPort)], {
|
|
522
521
|
detached: true,
|
|
523
|
-
stdio:
|
|
522
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
524
523
|
env: proxyEnv,
|
|
525
524
|
});
|
|
526
525
|
tokenProxyChild.unref();
|
|
@@ -537,6 +536,7 @@ async function cmdConnect(args) {
|
|
|
537
536
|
// so the proxy can tag token records with the exact dispatch ID.
|
|
538
537
|
env.AUTOWONDER_TOKEN_PROXY_PORT = String(proxyPort);
|
|
539
538
|
env.AUTOWONDER_TOKEN_PROXY_CA = caCertPath;
|
|
539
|
+
env.AUTOWONDER_TOKEN_PROXY_DIAG_LOG = path.join(AUTOWONDER_HOME, "token-tap", "proxy-diag.log");
|
|
540
540
|
env.NODE_EXTRA_CA_CERTS = caCertPath;
|
|
541
541
|
log(`Token tracking enabled (proxy pid=${tokenProxyChild.pid}, port=${proxyPort})`);
|
|
542
542
|
} else {
|
|
@@ -604,6 +604,9 @@ async function cmdStart(args) {
|
|
|
604
604
|
AUTOWONDER_LOCAL_API_ADDR: addr,
|
|
605
605
|
AUTOWONDER_MAX_CONCURRENT_DISPATCHES: maxTasks,
|
|
606
606
|
AUTOWONDER_DAEMON_INSTANCE_ID: instanceId,
|
|
607
|
+
// The descriptor keeps the file open before the daemon can log anything;
|
|
608
|
+
// naming the path lets the daemon reopen it and own rotation from here on.
|
|
609
|
+
AUTOWONDER_DAEMON_LOG: LOG_PATH,
|
|
607
610
|
},
|
|
608
611
|
});
|
|
609
612
|
fs.closeSync(logFd);
|
package/bin/token-proxy.js
CHANGED
|
@@ -31,14 +31,23 @@ const os = require('os');
|
|
|
31
31
|
|
|
32
32
|
const DEFAULT_PORT = 19876;
|
|
33
33
|
const MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB, then rotate
|
|
34
|
+
const DEFAULT_LOG_MAX_BYTES = 100 * 1024 * 1024; // 100MiB, then rotate
|
|
35
|
+
const LOG_MAX_BYTES = intEnv('AUTOWONDER_LOG_MAX_BYTES', DEFAULT_LOG_MAX_BYTES);
|
|
36
|
+
const RAW_LOG_MAX_BYTES = intEnv('AUTOWONDER_RAW_LOG_MAX_BYTES', DEFAULT_LOG_MAX_BYTES);
|
|
34
37
|
const DATA_DIR = path.join(os.homedir(), '.autowonder', 'token-tap');
|
|
35
38
|
const CA_KEY_PATH = path.join(DATA_DIR, 'ca-key.pem');
|
|
36
39
|
const CA_CERT_PATH = path.join(DATA_DIR, 'ca-cert.pem');
|
|
37
40
|
const USAGE_LOG_PATH = path.join(DATA_DIR, 'token-usage.jsonl');
|
|
38
41
|
const USAGE_LOG_OLD = path.join(DATA_DIR, 'token-usage.old.jsonl');
|
|
39
42
|
const RAW_LOG_DIR = path.join(DATA_DIR, 'raw');
|
|
43
|
+
const DIAG_LOG_PATH = path.join(DATA_DIR, 'proxy-diag.log');
|
|
40
44
|
const DEBUG = process.env.AUTOWONDER_DEBUG === '1';
|
|
41
45
|
|
|
46
|
+
function intEnv(name, fallback) {
|
|
47
|
+
const parsed = parseInt(process.env[name], 10);
|
|
48
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
49
|
+
}
|
|
50
|
+
|
|
42
51
|
// Qoder/QoderCN LLM API hosts to sniff (others are tunneled without inspection).
|
|
43
52
|
// Only LLM endpoints are listed — non-LLM APIs (openapi.*) are excluded to avoid
|
|
44
53
|
// unnecessary MITM overhead on high-frequency config/auth calls.
|
|
@@ -50,6 +59,93 @@ const SNIFF_HOSTS = new Set([
|
|
|
50
59
|
'api3.qodercn.com', 'api2.qodercn.com', 'api.qodercn.com',
|
|
51
60
|
]);
|
|
52
61
|
|
|
62
|
+
// ─── Managed log rotation (proxy-diag.log) ───────────────────────────────────
|
|
63
|
+
|
|
64
|
+
// Rename-then-reopen: the size trigger closes the live file, archives it under
|
|
65
|
+
// a name carrying the timestamp and pid, and reopens a fresh file at the same
|
|
66
|
+
// path. A live descriptor is never truncated or unlinked, so no diagnostic
|
|
67
|
+
// line is lost mid-write.
|
|
68
|
+
function createRotatingWriter(filePath, { maxBytes = DEFAULT_LOG_MAX_BYTES } = {}) {
|
|
69
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
70
|
+
let fd = fs.openSync(filePath, 'a', 0o600);
|
|
71
|
+
let size = fs.fstatSync(fd).size;
|
|
72
|
+
|
|
73
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
74
|
+
const archivePath = () => {
|
|
75
|
+
const now = new Date();
|
|
76
|
+
const stamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}`
|
|
77
|
+
+ `_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
78
|
+
const ext = path.extname(filePath);
|
|
79
|
+
const base = `${filePath.slice(0, filePath.length - ext.length)}.${stamp}_${process.pid}`;
|
|
80
|
+
for (let n = 0; n < 10000; n++) {
|
|
81
|
+
const candidate = n === 0 ? `${base}${ext}` : `${base}_${n}${ext}`;
|
|
82
|
+
if (!fs.existsSync(candidate)) return candidate;
|
|
83
|
+
}
|
|
84
|
+
return `${base}_${Date.now()}${ext}`;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
filePath,
|
|
89
|
+
get size() { return size; },
|
|
90
|
+
write(chunk) {
|
|
91
|
+
const data = Buffer.from(String(chunk), 'utf8');
|
|
92
|
+
try {
|
|
93
|
+
if (size > 0 && size + data.length > maxBytes) {
|
|
94
|
+
fs.closeSync(fd);
|
|
95
|
+
try {
|
|
96
|
+
fs.renameSync(filePath, archivePath());
|
|
97
|
+
} catch (_) {
|
|
98
|
+
// Archive failure keeps the live file in place; the incoming line
|
|
99
|
+
// still has to land somewhere.
|
|
100
|
+
}
|
|
101
|
+
fd = fs.openSync(filePath, 'a', 0o600);
|
|
102
|
+
size = fs.fstatSync(fd).size;
|
|
103
|
+
}
|
|
104
|
+
fs.writeSync(fd, data);
|
|
105
|
+
size += data.length;
|
|
106
|
+
return true;
|
|
107
|
+
} catch (_) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// rawLogFileName keeps the pid in the name so two proxies writing into one raw
|
|
115
|
+
// directory can never collide on the same file.
|
|
116
|
+
function rawLogFileName(seq, hostname, now = new Date(), pid = process.pid) {
|
|
117
|
+
return `${now.toISOString().slice(0, 10)}_${pid}_${seq}_${hostname}.raw`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// rawSliceFor returns the fragment of chunk still allowed under the per-file
|
|
121
|
+
// cap, or null once the cap is reached. Overflow is dropped here only; the
|
|
122
|
+
// tunnel itself keeps forwarding either way.
|
|
123
|
+
function rawSliceFor(chunk, written, cap = RAW_LOG_MAX_BYTES) {
|
|
124
|
+
if (written >= cap) return null;
|
|
125
|
+
const room = cap - written;
|
|
126
|
+
return chunk.length <= room ? chunk : chunk.subarray(0, room);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// installDiagLog routes the proxy's own console output into a rotating
|
|
130
|
+
// proxy-diag.log when AUTOWONDER_DEBUG is set. A TTY still echoes, so an
|
|
131
|
+
// interactive `token-proxy.js start` stays readable.
|
|
132
|
+
function installDiagLog(logPath = DIAG_LOG_PATH) {
|
|
133
|
+
if (!DEBUG) return null;
|
|
134
|
+
const { format } = require('util');
|
|
135
|
+
const writer = createRotatingWriter(logPath, { maxBytes: LOG_MAX_BYTES });
|
|
136
|
+
const originalStdout = console.log.bind(console);
|
|
137
|
+
const originalStderr = console.error.bind(console);
|
|
138
|
+
console.log = (...args) => {
|
|
139
|
+
writer.write(format(...args) + '\n');
|
|
140
|
+
if (process.stdout.isTTY) originalStdout(...args);
|
|
141
|
+
};
|
|
142
|
+
console.error = (...args) => {
|
|
143
|
+
writer.write(format(...args) + '\n');
|
|
144
|
+
if (process.stderr.isTTY) originalStderr(...args);
|
|
145
|
+
};
|
|
146
|
+
return writer;
|
|
147
|
+
}
|
|
148
|
+
|
|
53
149
|
// ─── CA Certificate (auto-generated, only for this proxy) ────────────────────
|
|
54
150
|
|
|
55
151
|
function ensureCA() {
|
|
@@ -425,13 +521,15 @@ function createProxyServer({ ca = ensureCA(), hostCertificate = getHostCert,
|
|
|
425
521
|
let usageCount = 0;
|
|
426
522
|
let totalBytes = 0;
|
|
427
523
|
let rawFd = null;
|
|
524
|
+
let rawWritten = 0;
|
|
428
525
|
|
|
429
|
-
// Raw response logging: only in debug mode to avoid disk bloat
|
|
526
|
+
// Raw response logging: only in debug mode to avoid disk bloat, and capped
|
|
527
|
+
// per file so one long-lived tunnel cannot fill the disk.
|
|
430
528
|
if (DEBUG) {
|
|
431
529
|
try {
|
|
432
530
|
fs.mkdirSync(RAW_LOG_DIR, { recursive: true });
|
|
433
|
-
const rawFile = path.join(RAW_LOG_DIR,
|
|
434
|
-
rawFd = fs.openSync(rawFile, 'w');
|
|
531
|
+
const rawFile = path.join(RAW_LOG_DIR, rawLogFileName(seq, hostname));
|
|
532
|
+
rawFd = fs.openSync(rawFile, 'w', 0o600);
|
|
435
533
|
} catch (_) {}
|
|
436
534
|
}
|
|
437
535
|
|
|
@@ -465,7 +563,15 @@ function createProxyServer({ ca = ensureCA(), hostCertificate = getHostCert,
|
|
|
465
563
|
|
|
466
564
|
tlsUpstream.on('data', (chunk) => {
|
|
467
565
|
totalBytes += chunk.length;
|
|
468
|
-
if (rawFd !== null) {
|
|
566
|
+
if (rawFd !== null) {
|
|
567
|
+
const slice = rawSliceFor(chunk, rawWritten);
|
|
568
|
+
if (slice !== null) {
|
|
569
|
+
try {
|
|
570
|
+
fs.writeSync(rawFd, slice);
|
|
571
|
+
rawWritten += slice.length;
|
|
572
|
+
} catch (_) {}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
469
575
|
try {
|
|
470
576
|
const text = lineRemainder + chunk.toString('utf8');
|
|
471
577
|
const lastNL = text.lastIndexOf('\n');
|
|
@@ -508,6 +614,7 @@ function createProxyServer({ ca = ensureCA(), hostCertificate = getHostCert,
|
|
|
508
614
|
}
|
|
509
615
|
|
|
510
616
|
function startProxy(port) {
|
|
617
|
+
const diagWriter = installDiagLog();
|
|
511
618
|
const server = createProxyServer();
|
|
512
619
|
server.on('error', (err) => {
|
|
513
620
|
console.error(`[token-proxy] server error: ${err.message}`);
|
|
@@ -519,6 +626,7 @@ function startProxy(port) {
|
|
|
519
626
|
console.log(`[token-proxy] CA cert: ${CA_CERT_PATH}`);
|
|
520
627
|
console.log(`[token-proxy] Usage log: ${USAGE_LOG_PATH}`);
|
|
521
628
|
console.log(`[token-proxy] Sniffing hosts: ${[...SNIFF_HOSTS].join(', ')}`);
|
|
629
|
+
if (diagWriter) console.log(`[token-proxy] Diag log: ${diagWriter.filePath}`);
|
|
522
630
|
});
|
|
523
631
|
|
|
524
632
|
// Graceful shutdown
|
|
@@ -616,9 +724,10 @@ if (require.main === module) {
|
|
|
616
724
|
|
|
617
725
|
Files:
|
|
618
726
|
CA cert: ${CA_CERT_PATH}
|
|
619
|
-
Usage log: ${USAGE_LOG_PATH}
|
|
727
|
+
Usage log: ${USAGE_LOG_PATH}
|
|
728
|
+
Diag log: ${DIAG_LOG_PATH} (written when AUTOWONDER_DEBUG=1)`);
|
|
620
729
|
}
|
|
621
730
|
}
|
|
622
731
|
|
|
623
732
|
module.exports = { extractUsageFromResponse, extractDispatchInfo, buildUsageRecord,
|
|
624
|
-
guardConnection, createProxyServer };
|
|
733
|
+
guardConnection, createProxyServer, createRotatingWriter, rawLogFileName, rawSliceFor, installDiagLog };
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|