agent-gear 2.1.0 → 2.2.0

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.
@@ -0,0 +1,292 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { homedir } from 'node:os';
3
+ /**
4
+ * 单条脚本的尾部输出上限(256 KiB),与 cloud-controller 的 `WORKLOAD_LOG_MAX_BYTES` 对齐。
5
+ */
6
+ export const MAX_OUTPUT_BYTES = 256 * 1024;
7
+ /** 未确认执行结果在内存中的保留上限(重连重放用)。 */
8
+ export const MAX_PENDING_RESULTS = 50;
9
+ /** 空 script(含纯空白)的失败文案。 */
10
+ export const EMPTY_SCRIPT_MESSAGE = '工作负载脚本为空,跳过执行';
11
+ /**
12
+ * 单条脚本输出的尾部环形截断缓冲:始终保留最近 `limit` 字节。
13
+ *
14
+ * 大输出脚本(如构建日志、循环 echo)若不截断会打爆内存与 Mongo,
15
+ * 契约要求保留尾部 ≤ 256 KiB(尾部通常包含真正的错误信息)。
16
+ */
17
+ class TailBuffer {
18
+ limit;
19
+ chunks = [];
20
+ size = 0;
21
+ constructor(limit) {
22
+ this.limit = limit;
23
+ }
24
+ push(chunk) {
25
+ if (chunk.length === 0)
26
+ return;
27
+ this.chunks.push(chunk);
28
+ this.size += chunk.length;
29
+ while (this.size > this.limit && this.chunks.length > 0) {
30
+ const first = this.chunks[0];
31
+ const excess = this.size - this.limit;
32
+ if (first.length <= excess) {
33
+ this.chunks.shift();
34
+ this.size -= first.length;
35
+ }
36
+ else {
37
+ this.chunks[0] = first.subarray(excess);
38
+ this.size -= excess;
39
+ }
40
+ }
41
+ }
42
+ toString() {
43
+ const buffer = Buffer.concat(this.chunks);
44
+ // 截断保留尾部时,起始处可能落在多字节 UTF-8 字符的续字节上(0b10xxxxxx),
45
+ // 直接解码会在日志首字符产生乱码(U+FFFD);跳过续字节到字符边界再解码
46
+ // (与服务端 `truncateLogTail` 同一口径)。
47
+ let start = 0;
48
+ while (start < buffer.length && (buffer[start] & 0xc0) === 0x80)
49
+ start += 1;
50
+ return buffer.subarray(start).toString('utf8');
51
+ }
52
+ }
53
+ function nowIso() {
54
+ return new Date().toISOString();
55
+ }
56
+ /**
57
+ * 工作负载脚本执行器(agent-gear 侧)。
58
+ *
59
+ * - 按 `items` 顺序**串行**执行 `bash -c <script>`(`cwd=os.homedir()`,继承进程环境,stdout/stderr 合并捕获);
60
+ * - 单条超时或收到新批次时终止整个进程组(`detached: true` + `kill(-pid)`);
61
+ * - 非零退出回报 `failed` + `exitCode`,不影响后续条目;
62
+ * - `post` 批次共享 `budgetMs`:剩余预算耗尽时后续条目直接回报 `timeout`(不启动进程);
63
+ * - 未被服务端确认的结果保存在内存(上限 50 条)供重连重放。
64
+ *
65
+ * 进程内全局单实例,保证同一时间只跑一个批次(后到的批次打断前一个,用于 post 打断 init)。
66
+ */
67
+ export class WorkloadExecutor {
68
+ active = null;
69
+ activePromise = null;
70
+ pendingResults = [];
71
+ /** 当前是否有批次正在执行。 */
72
+ isRunning() {
73
+ return this.active !== null;
74
+ }
75
+ /** 未确认的执行结果快照(供重连重放读取)。 */
76
+ getPendingResults() {
77
+ return [...this.pendingResults];
78
+ }
79
+ /** 清空未确认的执行结果。 */
80
+ clearPendingResults() {
81
+ this.pendingResults = [];
82
+ }
83
+ /**
84
+ * 重连后重放未确认结果:逐条 emit 后清空缓冲。
85
+ * 服务端对已进入终态的记录按幂等忽略(契约 §2.9/§2.10)。
86
+ */
87
+ replayPendingResults(emit) {
88
+ const pending = this.getPendingResults();
89
+ this.clearPendingResults();
90
+ for (const result of pending) {
91
+ emit(result);
92
+ }
93
+ }
94
+ /** 等待当前批次结束;无批次时立即返回,超时也返回(不阻塞退出流程)。 */
95
+ async waitForIdle(timeoutMs) {
96
+ const promise = this.activePromise;
97
+ if (!this.active || !promise)
98
+ return;
99
+ let timer;
100
+ const timeout = new Promise((resolve) => {
101
+ timer = setTimeout(resolve, timeoutMs);
102
+ });
103
+ try {
104
+ await Promise.race([promise.then(() => undefined, () => undefined), timeout]);
105
+ }
106
+ finally {
107
+ if (timer)
108
+ clearTimeout(timer);
109
+ }
110
+ }
111
+ /**
112
+ * 执行一个批次。若已有批次在跑,先终止其进程组并停止推进,再执行新批次。
113
+ */
114
+ runBatch(payload, onResult) {
115
+ this.interruptActive();
116
+ const run = { cancelled: false, child: null };
117
+ this.active = run;
118
+ const promise = this.executeBatch(payload, onResult, run).finally(() => {
119
+ // 仅当自己仍是当前批次时才清空(被打断时 active 已指向新批次)
120
+ if (this.active === run) {
121
+ this.active = null;
122
+ this.activePromise = null;
123
+ }
124
+ });
125
+ this.activePromise = promise;
126
+ return promise;
127
+ }
128
+ async executeBatch(payload, onResult, run) {
129
+ const items = Array.isArray(payload?.items) ? payload.items : [];
130
+ if (items.length === 0)
131
+ return;
132
+ const budgetMs = typeof payload.budgetMs === 'number' && payload.budgetMs > 0 ? payload.budgetMs : null;
133
+ const batchStart = Date.now();
134
+ for (const item of items) {
135
+ if (run.cancelled)
136
+ return;
137
+ let effectiveTimeoutMs = item.timeoutMs;
138
+ if (budgetMs !== null) {
139
+ const remaining = budgetMs - (Date.now() - batchStart);
140
+ if (remaining <= 0) {
141
+ // 预算耗尽:后续条目不再启动进程,直接回报 timeout
142
+ this.report(onResult, this.emptyResult(item.logId, 'timeout', ''));
143
+ continue;
144
+ }
145
+ effectiveTimeoutMs = Math.min(item.timeoutMs, remaining);
146
+ }
147
+ if (!item.script || item.script.trim().length === 0) {
148
+ this.report(onResult, this.emptyResult(item.logId, 'failed', EMPTY_SCRIPT_MESSAGE));
149
+ continue;
150
+ }
151
+ const outcome = await this.executeItem(item, effectiveTimeoutMs, run);
152
+ if (outcome.cancelled)
153
+ return;
154
+ if (outcome.result)
155
+ this.report(onResult, outcome.result);
156
+ }
157
+ }
158
+ report(onResult, result) {
159
+ this.pendingResults.push(result);
160
+ while (this.pendingResults.length > MAX_PENDING_RESULTS) {
161
+ this.pendingResults.shift();
162
+ }
163
+ onResult(result);
164
+ }
165
+ emptyResult(logId, status, log) {
166
+ const now = nowIso();
167
+ return { logId, status, exitCode: null, log, startedAt: now, finishedAt: now };
168
+ }
169
+ executeItem(item, timeoutMs, run) {
170
+ return new Promise((resolve) => {
171
+ if (run.cancelled) {
172
+ resolve({ cancelled: true });
173
+ return;
174
+ }
175
+ const startedAt = nowIso();
176
+ let child;
177
+ try {
178
+ child = spawn('bash', ['-c', item.script], {
179
+ cwd: homedir(),
180
+ detached: true,
181
+ env: process.env,
182
+ });
183
+ }
184
+ catch (error) {
185
+ resolve({
186
+ cancelled: false,
187
+ result: {
188
+ logId: item.logId,
189
+ status: 'failed',
190
+ exitCode: null,
191
+ log: `启动脚本失败: ${error.message}`,
192
+ startedAt,
193
+ finishedAt: nowIso(),
194
+ },
195
+ });
196
+ return;
197
+ }
198
+ run.child = child;
199
+ const tail = new TailBuffer(MAX_OUTPUT_BYTES);
200
+ let settled = false;
201
+ let timedOut = false;
202
+ const finish = (outcome) => {
203
+ if (settled)
204
+ return;
205
+ settled = true;
206
+ clearTimeout(timer);
207
+ if (run.child === child)
208
+ run.child = null;
209
+ resolve(outcome);
210
+ };
211
+ const timer = setTimeout(() => {
212
+ timedOut = true;
213
+ this.killGroup(child);
214
+ }, Math.max(1, timeoutMs));
215
+ child.stdout?.on('data', (chunk) => tail.push(chunk));
216
+ child.stderr?.on('data', (chunk) => tail.push(chunk));
217
+ child.on('error', (error) => {
218
+ tail.push(Buffer.from(`\n[执行脚本失败] ${error.message}`));
219
+ if (run.cancelled) {
220
+ finish({ cancelled: true });
221
+ return;
222
+ }
223
+ finish({
224
+ cancelled: false,
225
+ result: {
226
+ logId: item.logId,
227
+ status: 'failed',
228
+ exitCode: null,
229
+ log: tail.toString(),
230
+ startedAt,
231
+ finishedAt: nowIso(),
232
+ },
233
+ });
234
+ });
235
+ child.on('close', (code, signal) => {
236
+ if (run.cancelled) {
237
+ finish({ cancelled: true });
238
+ return;
239
+ }
240
+ if (timedOut) {
241
+ finish({
242
+ cancelled: false,
243
+ result: {
244
+ logId: item.logId,
245
+ status: 'timeout',
246
+ exitCode: null,
247
+ log: tail.toString(),
248
+ startedAt,
249
+ finishedAt: nowIso(),
250
+ },
251
+ });
252
+ return;
253
+ }
254
+ const exitCode = code ?? (signal ? 1 : 0);
255
+ finish({
256
+ cancelled: false,
257
+ result: {
258
+ logId: item.logId,
259
+ status: exitCode === 0 ? 'success' : 'failed',
260
+ exitCode,
261
+ log: tail.toString(),
262
+ startedAt,
263
+ finishedAt: nowIso(),
264
+ },
265
+ });
266
+ });
267
+ });
268
+ }
269
+ interruptActive() {
270
+ const run = this.active;
271
+ if (!run)
272
+ return;
273
+ run.cancelled = true;
274
+ this.killGroup(run.child);
275
+ }
276
+ killGroup(child) {
277
+ if (!child || child.pid == null)
278
+ return;
279
+ try {
280
+ process.kill(-child.pid, 'SIGKILL');
281
+ }
282
+ catch {
283
+ try {
284
+ child.kill('SIGKILL');
285
+ }
286
+ catch {
287
+ /* process already exited */
288
+ }
289
+ }
290
+ }
291
+ }
292
+ //# sourceMappingURL=workload-executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workload-executor.js","sourceRoot":"","sources":["../src/workload-executor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=workload-executor.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workload-executor.test.d.ts","sourceRoot":"","sources":["../src/workload-executor.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,214 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { mkdtemp, mkdir, rm, access } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { setTimeout as delay } from 'node:timers/promises';
5
+ import { WorkloadExecutor, MAX_OUTPUT_BYTES, MAX_PENDING_RESULTS, EMPTY_SCRIPT_MESSAGE, } from './workload-executor.js';
6
+ const TMP_BASE = '/tmp/opencode';
7
+ function collect() {
8
+ const results = [];
9
+ return { results, sink: (result) => results.push(result) };
10
+ }
11
+ async function makeTmpDir() {
12
+ await mkdir(TMP_BASE, { recursive: true });
13
+ return mkdtemp(join(TMP_BASE, 'we-'));
14
+ }
15
+ async function fileExists(path) {
16
+ try {
17
+ await access(path);
18
+ return true;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ describe('WorkloadExecutor', () => {
25
+ const dirs = [];
26
+ afterEach(async () => {
27
+ await Promise.all(dirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
28
+ });
29
+ it('T-WE1 runs items serially, merges stdout+stderr and reports success in item order', async () => {
30
+ const executor = new WorkloadExecutor();
31
+ const { results, sink } = collect();
32
+ await executor.runBatch({
33
+ phase: 'init',
34
+ items: [
35
+ { logId: 'a', workloadName: 'A', script: 'echo out-a; echo err-a >&2', timeoutMs: 5000 },
36
+ { logId: 'b', workloadName: 'B', script: 'echo out-b', timeoutMs: 5000 },
37
+ ],
38
+ }, sink);
39
+ expect(results.map((r) => r.logId)).toEqual(['a', 'b']);
40
+ expect(results[0]).toMatchObject({ status: 'success', exitCode: 0 });
41
+ expect(results[0].log).toContain('out-a');
42
+ expect(results[0].log).toContain('err-a');
43
+ expect(results[1]).toMatchObject({ status: 'success', exitCode: 0 });
44
+ expect(results[1].log).toContain('out-b');
45
+ expect(results[0].startedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
46
+ expect(results[0].finishedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
47
+ });
48
+ it('T-WE2 reports failed with exitCode on non-zero exit and continues the batch', async () => {
49
+ const executor = new WorkloadExecutor();
50
+ const { results, sink } = collect();
51
+ await executor.runBatch({
52
+ phase: 'init',
53
+ items: [
54
+ { logId: 'fail', workloadName: 'F', script: 'echo boom >&2; exit 3', timeoutMs: 5000 },
55
+ { logId: 'ok', workloadName: 'O', script: 'echo ok', timeoutMs: 5000 },
56
+ ],
57
+ }, sink);
58
+ expect(results[0]).toMatchObject({ logId: 'fail', status: 'failed', exitCode: 3 });
59
+ expect(results[0].log).toContain('boom');
60
+ expect(results[1]).toMatchObject({ logId: 'ok', status: 'success' });
61
+ });
62
+ it('T-WE3 kills the whole process group on per-item timeout and continues', async () => {
63
+ const dir = await makeTmpDir();
64
+ dirs.push(dir);
65
+ const marker = join(dir, 'leaked.txt');
66
+ const executor = new WorkloadExecutor();
67
+ const { results, sink } = collect();
68
+ const started = Date.now();
69
+ await executor.runBatch({
70
+ phase: 'init',
71
+ items: [
72
+ { logId: 'slow', workloadName: 'S', script: `( sleep 1.5; echo leaked > ${marker} ) & sleep 30`, timeoutMs: 300 },
73
+ { logId: 'next', workloadName: 'N', script: 'echo next', timeoutMs: 5000 },
74
+ ],
75
+ }, sink);
76
+ const elapsed = Date.now() - started;
77
+ expect(elapsed).toBeLessThan(5000);
78
+ expect(results[0]).toMatchObject({ logId: 'slow', status: 'timeout', exitCode: null });
79
+ expect(results[1]).toMatchObject({ logId: 'next', status: 'success' });
80
+ // the backgrounded child would write the marker at 1.5s if the group was not killed
81
+ await delay(2000);
82
+ expect(await fileExists(marker)).toBe(false);
83
+ }, 15000);
84
+ it('T-WE4 truncates oversized output keeping the tail <= 256 KiB', async () => {
85
+ const executor = new WorkloadExecutor();
86
+ const { results, sink } = collect();
87
+ await executor.runBatch({
88
+ phase: 'init',
89
+ items: [{ logId: 'big', workloadName: 'Big', script: "yes A | head -c 300000; printf 'ENDMARK'", timeoutMs: 10000 }],
90
+ }, sink);
91
+ expect(results).toHaveLength(1);
92
+ expect(results[0].status).toBe('success');
93
+ expect(Buffer.byteLength(results[0].log, 'utf8')).toBeLessThanOrEqual(MAX_OUTPUT_BYTES);
94
+ expect(results[0].log.endsWith('ENDMARK')).toBe(true);
95
+ }, 15000);
96
+ it('T-WE4b: skips a leading split UTF-8 continuation byte when truncating the tail', async () => {
97
+ const executor = new WorkloadExecutor();
98
+ const { results, sink } = collect();
99
+ // 总长 262145 字节(3 字节「中」+ 262142 个 A),尾部保留 262144 字节 => 起点落在
100
+ // 「中」的第 2 个字节(续字节)上;截断后必须跳过续字节到字符边界,首字符不得是乱码。
101
+ await executor.runBatch({
102
+ phase: 'init',
103
+ items: [
104
+ {
105
+ logId: 'utf8',
106
+ workloadName: 'Utf8',
107
+ script: "printf '中'; head -c 262142 /dev/zero | tr '\\0' 'A'",
108
+ timeoutMs: 10000,
109
+ },
110
+ ],
111
+ }, sink);
112
+ expect(results).toHaveLength(1);
113
+ const log = results[0].log;
114
+ expect(log.includes('\uFFFD')).toBe(false);
115
+ expect(log).toBe('A'.repeat(262142));
116
+ expect(Buffer.byteLength(log, 'utf8')).toBeLessThanOrEqual(MAX_OUTPUT_BYTES);
117
+ }, 15000);
118
+ it('T-WE5 shares the post budget and reports timeout without starting later items', async () => {
119
+ const executor = new WorkloadExecutor();
120
+ const { results, sink } = collect();
121
+ const started = Date.now();
122
+ await executor.runBatch({
123
+ phase: 'post',
124
+ budgetMs: 300,
125
+ items: [
126
+ { logId: 'p1', workloadName: 'P1', script: 'sleep 2', timeoutMs: 10000 },
127
+ { logId: 'p2', workloadName: 'P2', script: 'echo should-not-run', timeoutMs: 10000 },
128
+ ],
129
+ }, sink);
130
+ const elapsed = Date.now() - started;
131
+ expect(elapsed).toBeLessThan(5000);
132
+ expect(results.map((r) => r.logId)).toEqual(['p1', 'p2']);
133
+ expect(results[0]).toMatchObject({ status: 'timeout', exitCode: null });
134
+ expect(results[1]).toMatchObject({ status: 'timeout', exitCode: null });
135
+ expect(results[1].log).not.toContain('should-not-run');
136
+ }, 15000);
137
+ it('T-WE6 buffers unconfirmed results and replays then clears on reconnect', async () => {
138
+ const executor = new WorkloadExecutor();
139
+ await executor.runBatch({
140
+ phase: 'init',
141
+ items: [
142
+ { logId: 'r1', workloadName: 'R1', script: 'echo one', timeoutMs: 5000 },
143
+ { logId: 'r2', workloadName: 'R2', script: 'echo two', timeoutMs: 5000 },
144
+ ],
145
+ }, () => { });
146
+ expect(executor.getPendingResults().map((r) => r.logId)).toEqual(['r1', 'r2']);
147
+ const replayed = [];
148
+ executor.replayPendingResults((result) => replayed.push(result));
149
+ expect(replayed.map((r) => r.logId)).toEqual(['r1', 'r2']);
150
+ expect(executor.getPendingResults()).toHaveLength(0);
151
+ });
152
+ it('T-WE6b caps the replay buffer at 50 results', async () => {
153
+ const executor = new WorkloadExecutor();
154
+ const items = Array.from({ length: MAX_PENDING_RESULTS + 5 }, (_, i) => ({
155
+ logId: `x${i}`,
156
+ workloadName: `X${i}`,
157
+ script: ':',
158
+ timeoutMs: 5000,
159
+ }));
160
+ await executor.runBatch({ phase: 'init', items }, () => { });
161
+ const pending = executor.getPendingResults();
162
+ expect(pending).toHaveLength(MAX_PENDING_RESULTS);
163
+ expect(pending[pending.length - 1].logId).toBe(`x${MAX_PENDING_RESULTS + 4}`);
164
+ }, 30000);
165
+ it('T-WE7 terminates the running init batch when a new post batch arrives', async () => {
166
+ const dir = await makeTmpDir();
167
+ dirs.push(dir);
168
+ const startedFile = join(dir, 'init-started');
169
+ const executor = new WorkloadExecutor();
170
+ const initResults = [];
171
+ const initBatch = executor.runBatch({
172
+ phase: 'init',
173
+ items: [{ logId: 'init-1', workloadName: 'Init', script: `touch ${startedFile}; sleep 30`, timeoutMs: 60000 }],
174
+ }, (result) => initResults.push(result));
175
+ // wait until the init child process has actually started
176
+ const deadline = Date.now() + 5000;
177
+ let startedFlag = false;
178
+ while (Date.now() < deadline) {
179
+ if (await fileExists(startedFile)) {
180
+ startedFlag = true;
181
+ break;
182
+ }
183
+ await delay(25);
184
+ }
185
+ expect(startedFlag).toBe(true);
186
+ const postResults = [];
187
+ await executor.runBatch({ phase: 'post', items: [{ logId: 'post-1', workloadName: 'Post', script: 'echo post-ok', timeoutMs: 5000 }] }, (result) => postResults.push(result));
188
+ await initBatch;
189
+ expect(postResults.map((r) => r.logId)).toEqual(['post-1']);
190
+ expect(postResults[0].status).toBe('success');
191
+ // the interrupted init item is not reported; the server marks it TIMEOUT
192
+ expect(initResults).toHaveLength(0);
193
+ }, 20000);
194
+ it('T-WE8 reports failed for an empty script and does nothing for empty items', async () => {
195
+ const executor = new WorkloadExecutor();
196
+ const { results, sink } = collect();
197
+ await executor.runBatch({ phase: 'init', items: [{ logId: 'empty', workloadName: 'Empty', script: ' ', timeoutMs: 5000 }] }, sink);
198
+ expect(results).toHaveLength(1);
199
+ expect(results[0]).toMatchObject({ logId: 'empty', status: 'failed', exitCode: null, log: EMPTY_SCRIPT_MESSAGE });
200
+ const empty = collect();
201
+ await executor.runBatch({ phase: 'init', items: [] }, empty.sink);
202
+ expect(empty.results).toHaveLength(0);
203
+ });
204
+ it('T-WE9 exposes running state and waitForIdle resolves after the batch completes', async () => {
205
+ const executor = new WorkloadExecutor();
206
+ expect(executor.isRunning()).toBe(false);
207
+ const batch = executor.runBatch({ phase: 'post', items: [{ logId: 'w', workloadName: 'W', script: 'sleep 0.4', timeoutMs: 5000 }] }, () => { });
208
+ expect(executor.isRunning()).toBe(true);
209
+ await executor.waitForIdle(2000);
210
+ expect(executor.isRunning()).toBe(false);
211
+ await batch;
212
+ }, 10000);
213
+ });
214
+ //# sourceMappingURL=workload-executor.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workload-executor.test.js","sourceRoot":"","sources":["../src/workload-executor.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-gear",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "ZeroAsh Agent Gear - Agent controller daemon",
5
5
  "repository": {
6
6
  "type": "git",
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
+ import { createRequire } from 'node:module';
3
4
  import { hostname, platform } from 'os';
4
5
  import { resolveKey, resolveEndpoint, saveKeyToConfig, saveEndpointToConfig } from './config.js';
5
6
  import { connectToServer } from './socketio-client.js';
@@ -8,12 +9,17 @@ import { startMcpRegistration } from './commands/mcp-proxy.js';
8
9
  import { startMcpProxy } from './commands/mcp.js';
9
10
  import { processRegistry, isProcessAlive } from './acp/process-registry.js';
10
11
 
12
+ // 版本号唯一事实源为 package.json(`--version` 与 npm 发布版本保持一致,避免手工同步漂移)。
13
+ const { version: CLI_VERSION } = createRequire(import.meta.url)('../package.json') as {
14
+ version: string;
15
+ };
16
+
11
17
  const program = new Command();
12
18
 
13
19
  program
14
20
  .name('agent-gear')
15
21
  .description('Agent Controller - manages connection to controller-api')
16
- .version('1.0.0')
22
+ .version(CLI_VERSION)
17
23
  .option('-k, --key <key>', 'Controller key')
18
24
  .option('-s, --server <endpoint>', 'Server endpoint URL')
19
25
  .option('-n, --name <name>', 'Controller name')