@ai-workbench/network 0.4.1 → 0.4.4
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/NOTICE.md +5 -0
- package/README.md +38 -4
- package/helpers/darwin-arm64/NOTICE.md +23 -0
- package/helpers/darwin-arm64/README.md +1 -0
- package/helpers/darwin-arm64/THIRD_PARTY_NOTICES.txt +3074 -0
- package/helpers/darwin-arm64/bin/aw-net +0 -0
- package/helpers/darwin-arm64/helper-manifest.json +1 -0
- package/helpers/darwin-arm64/licenses-manifest.json +2540 -0
- package/helpers/darwin-arm64/package.json +16 -0
- package/helpers/darwin-x64/NOTICE.md +23 -0
- package/helpers/darwin-x64/README.md +1 -0
- package/helpers/darwin-x64/THIRD_PARTY_NOTICES.txt +3129 -0
- package/helpers/darwin-x64/bin/aw-net +0 -0
- package/helpers/darwin-x64/helper-manifest.json +1 -0
- package/helpers/darwin-x64/licenses-manifest.json +2556 -0
- package/helpers/darwin-x64/package.json +16 -0
- package/lib/activation.d.ts +3 -1
- package/lib/activation.js +26 -7
- package/lib/api.d.ts +45 -0
- package/lib/control.d.ts +1 -1
- package/lib/control.js +12 -3
- package/lib/download.js +8 -2
- package/lib/employee-endpoint.d.ts +102 -0
- package/lib/employee-endpoint.js +230 -0
- package/lib/index.js +18 -8
- package/lib/network.d.ts +2 -1
- package/lib/network.js +82 -12
- package/lib/observation.d.ts +6 -0
- package/lib/observation.js +14 -0
- package/lib/packaged-helper.js +18 -14
- package/lib/settings.d.ts +10 -0
- package/lib/settings.js +34 -0
- package/package.json +3 -6
package/lib/network.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { control } from './control.ts';
|
|
2
2
|
import type { PluginSettings, NetworkChannels, NetworkState } from './api.ts';
|
|
3
|
+
import type { EmployeeRuntime } from './employee-endpoint.ts';
|
|
3
4
|
export interface Executable {
|
|
4
5
|
path: string;
|
|
5
6
|
sha256: string;
|
|
6
7
|
platform: string;
|
|
7
8
|
arch: string;
|
|
8
9
|
}
|
|
9
|
-
export declare function createNetwork(settings: PluginSettings, executable: Executable, emit?: (state: NetworkState) => void, controlIO?: typeof control): NetworkChannels & {
|
|
10
|
+
export declare function createNetwork(settings: PluginSettings, executable: Executable, emit?: (state: NetworkState) => void, controlIO?: typeof control): NetworkChannels & EmployeeRuntime & {
|
|
10
11
|
start(): Promise<void>;
|
|
11
12
|
};
|
package/lib/network.js
CHANGED
|
@@ -5,6 +5,9 @@ import { privateFile, verifyExecutable } from "./private.js";
|
|
|
5
5
|
import { control, jsonResponse, firstFrame } from "./control.js";
|
|
6
6
|
import { fail, ok } from "./api.js";
|
|
7
7
|
import { downloadThroughProxy } from "./download.js";
|
|
8
|
+
import { effectiveConfig, helperPolicyArgs, validHelperPolicy } from "./settings.js";
|
|
9
|
+
import { ObservationExpired, observationNeed } from "./observation.js";
|
|
10
|
+
import { readEmployeeProcess, employeeReady } from "./employee-endpoint.js";
|
|
8
11
|
const seconds = (v) => typeof v === 'number' && Number.isFinite(v) && v >= 0.001 && v * 1000 <= 2147483647;
|
|
9
12
|
const integer = (v) => Number.isSafeInteger(v) && Number(v) > 0;
|
|
10
13
|
const quote = (s) => "'" + s.replaceAll("'", "'\\''") + "'";
|
|
@@ -39,10 +42,11 @@ export function createNetwork(settings, executable, emit = () => { }, controlIO
|
|
|
39
42
|
let state = { phase: 'starting' }, cfg, child;
|
|
40
43
|
let starting, disposing, disposed = false, online = false;
|
|
41
44
|
let poll;
|
|
42
|
-
let processFailed = false, startupReceipt, terminalNeed, stoppingChild;
|
|
45
|
+
let processFailed = false, startupReceipt, terminalNeed, stoppingChild, shutdownNeed;
|
|
43
46
|
const childGone = new AbortController();
|
|
44
47
|
const lifetime = new AbortController(), listeners = new Set(), updates = new Set();
|
|
45
48
|
const leases = new Set();
|
|
49
|
+
let employeeParent, employeeUmask;
|
|
46
50
|
const snapshot = () => structuredClone(state);
|
|
47
51
|
const publish = (next) => {
|
|
48
52
|
state = next;
|
|
@@ -72,9 +76,10 @@ export function createNetwork(settings, executable, emit = () => { }, controlIO
|
|
|
72
76
|
if (!owned?.pid || owned.exitCode !== null || owned.signalCode !== null)
|
|
73
77
|
return false;
|
|
74
78
|
const exit = new Promise(resolve => owned.once('exit', () => resolve()));
|
|
79
|
+
const started = performance.now();
|
|
75
80
|
owned.kill('SIGTERM');
|
|
76
81
|
let forced = false, timer;
|
|
77
|
-
await Promise.race([exit, new Promise(resolve => { timer = setTimeout(() => { forced = true; owned.kill('SIGKILL'); resolve(); }, settings.shutdownObservationSeconds * 1000); })]);
|
|
82
|
+
await Promise.race([exit, new Promise(resolve => { timer = setTimeout(() => { forced = true; shutdownNeed = observationNeed(new ObservationExpired('HELPER_SHUTDOWN_FORCED', 'shutdownObservationSeconds', settings.shutdownObservationSeconds, started, '自有网络组件正常退出', '检查组件退出日志;本次仅强制结束自有进程,不终止未知进程')); owned.kill('SIGKILL'); resolve(); }, settings.shutdownObservationSeconds * 1000); })]);
|
|
78
83
|
if (timer)
|
|
79
84
|
clearTimeout(timer);
|
|
80
85
|
if (forced)
|
|
@@ -94,9 +99,14 @@ export function createNetwork(settings, executable, emit = () => { }, controlIO
|
|
|
94
99
|
if (!disposed && alive())
|
|
95
100
|
publish({ phase: s.enrollment === 'online' && !s.error ? 'online' : s.error ? 'blocked' : 'starting', status: s, ...s.error ? { error: s.error } : {} });
|
|
96
101
|
}
|
|
97
|
-
catch {
|
|
98
|
-
if (!disposed && alive())
|
|
99
|
-
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (!disposed && alive()) {
|
|
104
|
+
const observed = observationNeed(error);
|
|
105
|
+
if (observed)
|
|
106
|
+
publish({ phase: 'blocked', error: observed });
|
|
107
|
+
else
|
|
108
|
+
block('CONTROL_UNAVAILABLE', '连接中断,正在等待网络组件恢复;根因未查明。', '检查组件私有日志、控制socket和当前网络;状态刷新不是修复,不重启或重交材料');
|
|
109
|
+
}
|
|
100
110
|
}
|
|
101
111
|
if (!disposed && alive())
|
|
102
112
|
poll = setTimeout(() => void observe(), cfg.statusRefreshIntervalSeconds * 1000);
|
|
@@ -119,6 +129,9 @@ export function createNetwork(settings, executable, emit = () => { }, controlIO
|
|
|
119
129
|
finally {
|
|
120
130
|
await file.close();
|
|
121
131
|
}
|
|
132
|
+
if (!validHelperPolicy(settings))
|
|
133
|
+
throw new Error('CONFIG_INVALID');
|
|
134
|
+
cfg = effectiveConfig(cfg, settings);
|
|
122
135
|
if (normalize(settings.helperLogFile) !== settings.helperLogFile || normalize(settings.configFile) !== settings.configFile)
|
|
123
136
|
throw new Error('CONFIG_INVALID');
|
|
124
137
|
const canonical = async (p) => { try {
|
|
@@ -174,8 +187,20 @@ export function createNetwork(settings, executable, emit = () => { }, controlIO
|
|
|
174
187
|
await log.close();
|
|
175
188
|
return;
|
|
176
189
|
}
|
|
190
|
+
const parent = readEmployeeProcess();
|
|
191
|
+
employeeParent = parent.ok ? parent.value : undefined;
|
|
192
|
+
const environment = { ...process.env };
|
|
193
|
+
delete environment.AW_NET_PARENT_PID;
|
|
194
|
+
delete environment.AW_NET_PARENT_SHELL;
|
|
195
|
+
delete environment.AW_NET_PARENT_UMASK;
|
|
196
|
+
if (employeeParent) {
|
|
197
|
+
employeeUmask = process.umask();
|
|
198
|
+
environment.AW_NET_PARENT_PID = String(process.pid);
|
|
199
|
+
environment.AW_NET_PARENT_SHELL = employeeParent.account.shell;
|
|
200
|
+
environment.AW_NET_PARENT_UMASK = String(employeeUmask);
|
|
201
|
+
}
|
|
177
202
|
try {
|
|
178
|
-
child = spawn(executable.path, ['serve', '--config', settings.configFile], { stdio: ['ignore', 'pipe', log.fd], env:
|
|
203
|
+
child = spawn(executable.path, ['serve', '--config', settings.configFile, ...helperPolicyArgs(settings), '--controlResponseMaxBytes', String(settings.controlResponseMaxBytes)], { stdio: ['ignore', 'pipe', log.fd], env: environment });
|
|
179
204
|
}
|
|
180
205
|
catch (error) {
|
|
181
206
|
await log.close();
|
|
@@ -196,7 +221,8 @@ export function createNetwork(settings, executable, emit = () => { }, controlIO
|
|
|
196
221
|
processChild.once('exit', exited);
|
|
197
222
|
const ready = new Promise((resolve, reject) => {
|
|
198
223
|
let data = Buffer.alloc(0), settled = false;
|
|
199
|
-
const
|
|
224
|
+
const started = performance.now();
|
|
225
|
+
const timer = setTimeout(() => finish(new ObservationExpired('HELPER_STARTUP_EXPIRED', 'startupObservationSeconds', settings.startupObservationSeconds, started, '自有网络组件ready首帧', '检查组件私有日志、完整性与启动状态;保留身份,不重交开通材料')), settings.startupObservationSeconds * 1000);
|
|
200
226
|
const aborted = () => finish(new Error('DISPOSED'));
|
|
201
227
|
const earlyExit = () => finish(new Error('HELPER_EXITED'));
|
|
202
228
|
const errored = () => finish(new Error('HELPER_START_FAILED'));
|
|
@@ -258,10 +284,12 @@ export function createNetwork(settings, executable, emit = () => { }, controlIO
|
|
|
258
284
|
if (disposed)
|
|
259
285
|
return;
|
|
260
286
|
const code = error instanceof Error && /^(LOCAL_PERMISSION_DENIED|LOCAL_PATH_INVALID|HELPER_[A-Z_]+|CONTROL_[A-Z_]+|CONFIG_INVALID)$/.test(error.message) ? error.message : 'CONFIG_INVALID';
|
|
261
|
-
terminal(startupNeed ?? needs(code, '网络组件未能启动,根因未查明', '管理员检查安装配置、helper完整性和私有日志'));
|
|
287
|
+
terminal(startupNeed ?? observationNeed(error) ?? needs(code, '网络组件未能启动,根因未查明', '管理员检查安装配置、helper完整性和私有日志'));
|
|
262
288
|
const forced = await stopOwned();
|
|
263
|
-
if (forced && terminalNeed)
|
|
264
|
-
terminalNeed.cause = '
|
|
289
|
+
if (forced && terminalNeed) {
|
|
290
|
+
terminalNeed.cause = [terminalNeed.cause, shutdownNeed?.message, shutdownNeed?.needs].filter(Boolean).join(';');
|
|
291
|
+
publish({ ...state, error: terminalNeed });
|
|
292
|
+
}
|
|
265
293
|
}
|
|
266
294
|
})();
|
|
267
295
|
const api = {
|
|
@@ -325,16 +353,57 @@ export function createNetwork(settings, executable, emit = () => { }, controlIO
|
|
|
325
353
|
return fail('DISPOSED', '登录代理已取消', '重新发起登录');
|
|
326
354
|
return ok(current);
|
|
327
355
|
}
|
|
328
|
-
catch {
|
|
356
|
+
catch (error) {
|
|
329
357
|
owner.abort();
|
|
330
358
|
signal.removeEventListener('abort', cancel);
|
|
331
359
|
lifetime.signal.removeEventListener('abort', cancel);
|
|
332
360
|
childGone.signal.removeEventListener('abort', cancel);
|
|
361
|
+
diagnostic ??= observationNeed(error);
|
|
333
362
|
return diagnostic ? { ok: false, error: diagnostic } : fail('PROXY_UNAVAILABLE', '登录代理未能建立,根因未查明', '查看主机网络与信任材料');
|
|
334
363
|
}
|
|
335
364
|
},
|
|
336
365
|
async download(source, signal) { const lease = await api.openLoginProxy(signal); if (!lease.ok)
|
|
337
366
|
return lease; return downloadThroughProxy(source, lease.value, signal, settings.downloadMaxRedirects, cfg.dialTimeoutSeconds, cfg.httpMaxResponseHeaderBytes); },
|
|
367
|
+
async prepareEmployeeEndpoint(settingsPath, signal) {
|
|
368
|
+
if (disposed)
|
|
369
|
+
return fail('DISPOSED', '网络服务已停止。', '重新加载插件');
|
|
370
|
+
if (signal.aborted)
|
|
371
|
+
return fail('EMPLOYEE_ENDPOINT_PREPARE_CANCELLED', '员工端点准备等待已取消。', '派发前重新核验来源');
|
|
372
|
+
if (!cfg || state.phase !== 'online' || !alive())
|
|
373
|
+
return fail('NETWORK_NOT_READY', '远程主机尚未连接。', '等待原网络连接');
|
|
374
|
+
const actual = readEmployeeProcess();
|
|
375
|
+
if (!actual.ok)
|
|
376
|
+
return actual;
|
|
377
|
+
if (!employeeParent || employeeUmask !== process.umask() || JSON.stringify(employeeParent) !== JSON.stringify(actual.value))
|
|
378
|
+
return fail('EMPLOYEE_ENVIRONMENT_UNAVAILABLE', '客户端环境在组件启动后已变化。', '正常重载插件后重新核验,不替换账户或环境');
|
|
379
|
+
try {
|
|
380
|
+
const response = await controlIO(socket(), 'POST', '/v1/employee-endpoint', AbortSignal.any([signal, childGone.signal, lifetime.signal]), cfg.dialTimeoutSeconds, cfg.httpMaxHeaderBytes, { version: 1, settingsPath });
|
|
381
|
+
const value = await jsonResponse(response, settings.controlResponseMaxBytes);
|
|
382
|
+
if (response.statusCode !== 200) {
|
|
383
|
+
const error = value?.error;
|
|
384
|
+
if (value?.apiVersion !== 1 || !error || !employeeNeedCodes.has(error.code) || !['message', 'needs', 'actor'].every(k => typeof error[k] === 'string') || (error.cause !== null && error.cause !== undefined && typeof error.cause !== 'string'))
|
|
385
|
+
return fail('EMPLOYEE_BRIDGE_PROTOCOL_INVALID', '员工端点控制响应无效。', '核对网络组件版本与合同');
|
|
386
|
+
return { ok: false, error: { code: error.code, message: error.message, needs: error.needs, actor: 'network', cause: error.cause ?? null } };
|
|
387
|
+
}
|
|
388
|
+
if (value?.apiVersion !== 1 || Object.keys(value).sort().join() !== 'apiVersion,bridge,endpoint')
|
|
389
|
+
return fail('EMPLOYEE_BRIDGE_PROTOCOL_INVALID', '员工端点控制帧无效。', '核对网络组件版本');
|
|
390
|
+
return employeeReady({ endpoint: value.endpoint, bridge: value.bridge }, actual.value, settingsPath);
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
if (signal.aborted)
|
|
394
|
+
return fail('EMPLOYEE_ENDPOINT_PREPARE_CANCELLED', '员工端点准备等待已取消。', '重新核验后派发,不重放旧操作');
|
|
395
|
+
const observed = observationNeed(error);
|
|
396
|
+
return observed ? { ok: false, error: observed } : fail('EMPLOYEE_ENDPOINT_UNAVAILABLE', '员工端点控制连接失败,根因未查明。', '检查组件私有日志;普通网络状态不代表端点就绪');
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
async closeEmployeeEndpoint() {
|
|
400
|
+
if (!cfg || !alive())
|
|
401
|
+
return;
|
|
402
|
+
const response = await request('DELETE', '/v1/employee-endpoint', lifetime.signal);
|
|
403
|
+
const value = await jsonResponse(response, settings.controlResponseMaxBytes);
|
|
404
|
+
if (response.statusCode !== 200 || value?.apiVersion !== 1 || value?.closed !== true)
|
|
405
|
+
throw Error('EMPLOYEE_ENDPOINT_UNAVAILABLE');
|
|
406
|
+
},
|
|
338
407
|
dispose() {
|
|
339
408
|
return disposing ??= (async () => {
|
|
340
409
|
disposed = true;
|
|
@@ -346,7 +415,7 @@ export function createNetwork(settings, executable, emit = () => { }, controlIO
|
|
|
346
415
|
await starting;
|
|
347
416
|
await Promise.all([...leases].map(x => x.release()));
|
|
348
417
|
const forced = await stopOwned();
|
|
349
|
-
publish(forced ? { phase: 'blocked', error:
|
|
418
|
+
publish(forced ? { phase: 'blocked', error: shutdownNeed } : { phase: 'stopped' });
|
|
350
419
|
listeners.clear();
|
|
351
420
|
updates.clear();
|
|
352
421
|
})();
|
|
@@ -354,3 +423,4 @@ export function createNetwork(settings, executable, emit = () => { }, controlIO
|
|
|
354
423
|
};
|
|
355
424
|
return api;
|
|
356
425
|
}
|
|
426
|
+
const employeeNeedCodes = new Set(['EMPLOYEE_CONTEXT_UNAVAILABLE', 'EMPLOYEE_CONTEXT_INVALID', 'EMPLOYEE_CONTEXT_CHANGED', 'EMPLOYEE_ACCOUNT_UNAVAILABLE', 'EMPLOYEE_ENVIRONMENT_UNAVAILABLE', 'EMPLOYEE_IDENTITY_STATE_INVALID', 'EMPLOYEE_IDENTITY_ACCOUNT_MISMATCH', 'EMPLOYEE_IDENTITY_BUSY_OR_UNSAFE', 'EMPLOYEE_IDENTITY_CREATE_FAILED', 'EMPLOYEE_IDENTITY_STORAGE_FAILED', 'EMPLOYEE_BRIDGE_UNAVAILABLE', 'EMPLOYEE_BRIDGE_PROTOCOL_INVALID', 'EMPLOYEE_BRIDGE_FAILED', 'EMPLOYEE_ENDPOINT_UNAVAILABLE', 'EMPLOYEE_ENDPOINT_PREPARE_CANCELLED', 'HELPER_PLATFORM_UNSUPPORTED', 'HOST_TRUST_REQUIRED', 'HOST_KEY_MISMATCH', 'ENROLLMENT_MISMATCH', 'DIAL_OBSERVATION_EXPIRED', 'LOCAL_PERMISSION_DENIED', 'NETWORK_INTERRUPTED', 'SSH_FAILED', 'NETWORK_NOT_READY', 'DISPOSED']);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Need } from './api.ts';
|
|
2
|
+
export declare class ObservationExpired extends Error {
|
|
3
|
+
readonly need: Need;
|
|
4
|
+
constructor(code: string, setting: string, seconds: number, started: number, waitingFor: string, nextStep: string);
|
|
5
|
+
}
|
|
6
|
+
export declare function observationNeed(error: unknown): Need | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Only fixed stage descriptions and numeric timings enter public diagnostics.
|
|
2
|
+
export class ObservationExpired extends Error {
|
|
3
|
+
need;
|
|
4
|
+
constructor(code, setting, seconds, started, waitingFor, nextStep) {
|
|
5
|
+
const elapsed = Math.max(0, (performance.now() - started) / 1000);
|
|
6
|
+
const message = `等待${waitingFor},已等${elapsed.toFixed(3)}秒(${setting}=${seconds}秒),观察期限已到,根因未查明。`;
|
|
7
|
+
super(message + ' 下一步:' + nextStep);
|
|
8
|
+
this.name = 'ObservationExpired';
|
|
9
|
+
this.need = { code, message, needs: nextStep, actor: 'network', cause: JSON.stringify({ setting, configuredSeconds: seconds, elapsedSeconds: elapsed, waitingFor }) };
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function observationNeed(error) {
|
|
13
|
+
return error instanceof ObservationExpired ? structuredClone(error.need) : undefined;
|
|
14
|
+
}
|
package/lib/packaged-helper.js
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { readFileSync, lstatSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import { fail, ok } from "./api.js";
|
|
4
4
|
export function resolvePackagedHelper(moduleURL, platform = process.platform, arch = process.arch) {
|
|
5
|
+
if (platform !== 'darwin' || !['arm64', 'x64'].includes(arch))
|
|
6
|
+
return fail('HELPER_PLATFORM_UNSUPPORTED', `我需要支持 ${platform}-${arch} 的联网助手。`, '当前提供 macOS arm64 和 x64,请管理员核对设备平台');
|
|
5
7
|
const name = `@ai-workbench/net-helper-${platform}-${arch}`;
|
|
6
8
|
let expected;
|
|
7
9
|
try {
|
|
8
10
|
const root = JSON.parse(readFileSync(new URL('../package.json', moduleURL), 'utf8'));
|
|
9
|
-
expected = root.
|
|
10
|
-
if (expected
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const manifest = JSON.parse(readFileSync(require.resolve(name + '/helper-manifest.json'), 'utf8'));
|
|
11
|
+
expected = root.version;
|
|
12
|
+
if (root.name !== '@ai-workbench/network' || typeof expected !== 'string' || !expected)
|
|
13
|
+
throw new Error('network主包的名称或版本无效');
|
|
14
|
+
const dir = new URL(`../helpers/${platform}-${arch}/`, moduleURL);
|
|
15
|
+
const metadata = JSON.parse(readFileSync(new URL('package.json', dir), 'utf8'));
|
|
16
|
+
if (metadata.name !== name || metadata.version !== expected || metadata.private !== true || JSON.stringify(metadata.os) !== JSON.stringify([platform]) || JSON.stringify(metadata.cpu) !== JSON.stringify([arch]))
|
|
17
|
+
throw new Error('内嵌助手的名称、版本或平台不匹配');
|
|
18
|
+
const manifest = JSON.parse(readFileSync(new URL('helper-manifest.json', dir), 'utf8'));
|
|
18
19
|
if (manifest.platform !== platform || manifest.arch !== arch || typeof manifest.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(manifest.sha256))
|
|
19
|
-
throw new Error('
|
|
20
|
-
|
|
20
|
+
throw new Error('内嵌助手 manifest 的架构或摘要无效');
|
|
21
|
+
const path = fileURLToPath(new URL('bin/aw-net', dir)), st = lstatSync(path);
|
|
22
|
+
if (!st.isFile() || st.isSymbolicLink())
|
|
23
|
+
throw new Error('内嵌助手不是普通二进制文件');
|
|
24
|
+
return ok({ path, sha256: manifest.sha256, platform, arch });
|
|
21
25
|
}
|
|
22
26
|
catch (cause) {
|
|
23
|
-
return { ok: false, error: { code: 'HELPER_NOT_PACKAGED', message: '
|
|
27
|
+
return { ok: false, error: { code: 'HELPER_NOT_PACKAGED', message: '我需要完整的当前版本远程主机网络插件。', needs: `请管理员重新安装 @ai-workbench/network${expected ? '@' + expected : ''} 的完整包;不要切换架构或改用旧助手`, actor: 'network', cause: cause instanceof Error ? cause.message : String(cause) } };
|
|
24
28
|
}
|
|
25
29
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PluginSettings, HelperConfig } from './api.ts';
|
|
2
|
+
export declare const helperPolicyFields: readonly ["statusRefreshIntervalSeconds", "dialTimeoutSeconds", "headerTimeoutSeconds", "enrollmentObservationSeconds", "httpMaxHeaderBytes", "httpMaxResponseHeaderBytes"];
|
|
3
|
+
export declare const pluginPolicyFields: readonly ["startupObservationSeconds", "shutdownObservationSeconds", "controlResponseMaxBytes", "downloadMaxRedirects"];
|
|
4
|
+
export declare const settingsFields: readonly ["configFile", "helperLogFile", "startupObservationSeconds", "shutdownObservationSeconds", "controlResponseMaxBytes", "downloadMaxRedirects", "statusRefreshIntervalSeconds", "dialTimeoutSeconds", "headerTimeoutSeconds", "enrollmentObservationSeconds", "httpMaxHeaderBytes", "httpMaxResponseHeaderBytes"];
|
|
5
|
+
export declare const timerSeconds: (v: unknown) => v is number;
|
|
6
|
+
export declare const positiveInteger: (v: unknown) => v is number;
|
|
7
|
+
export declare function validHelperPolicy(settings: PluginSettings): boolean;
|
|
8
|
+
export declare function mergedSettings(initial: PluginSettings, provided: PluginSettings): PluginSettings;
|
|
9
|
+
export declare function effectiveConfig(config: HelperConfig, settings: PluginSettings): HelperConfig;
|
|
10
|
+
export declare function helperPolicyArgs(settings: PluginSettings): string[];
|
package/lib/settings.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const helperPolicyFields = ['statusRefreshIntervalSeconds', 'dialTimeoutSeconds', 'headerTimeoutSeconds', 'enrollmentObservationSeconds', 'httpMaxHeaderBytes', 'httpMaxResponseHeaderBytes'];
|
|
2
|
+
export const pluginPolicyFields = ['startupObservationSeconds', 'shutdownObservationSeconds', 'controlResponseMaxBytes', 'downloadMaxRedirects'];
|
|
3
|
+
export const settingsFields = ['configFile', 'helperLogFile', ...pluginPolicyFields, ...helperPolicyFields];
|
|
4
|
+
export const timerSeconds = (v) => typeof v === 'number' && Number.isFinite(v) && v >= 0.001 && v * 1000 <= 2147483647;
|
|
5
|
+
export const positiveInteger = (v) => Number.isSafeInteger(v) && Number(v) > 0;
|
|
6
|
+
export function validHelperPolicy(settings) {
|
|
7
|
+
return helperPolicyFields.every(key => {
|
|
8
|
+
const v = settings[key];
|
|
9
|
+
if (v === undefined)
|
|
10
|
+
return true;
|
|
11
|
+
if (key === 'httpMaxHeaderBytes' || key === 'httpMaxResponseHeaderBytes')
|
|
12
|
+
return positiveInteger(v);
|
|
13
|
+
if (key === 'dialTimeoutSeconds' || key === 'statusRefreshIntervalSeconds')
|
|
14
|
+
return timerSeconds(v);
|
|
15
|
+
return typeof v === 'number' && Number.isFinite(v) && v >= 1e-9 && v < 9223372036.854775;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function mergedSettings(initial, provided) {
|
|
19
|
+
const merged = { ...provided };
|
|
20
|
+
for (const key of [...pluginPolicyFields, ...helperPolicyFields])
|
|
21
|
+
if (initial[key] !== undefined)
|
|
22
|
+
merged[key] = initial[key];
|
|
23
|
+
return merged;
|
|
24
|
+
}
|
|
25
|
+
export function effectiveConfig(config, settings) {
|
|
26
|
+
const result = { ...config };
|
|
27
|
+
for (const key of helperPolicyFields)
|
|
28
|
+
if (settings[key] !== undefined)
|
|
29
|
+
result[key] = settings[key];
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
export function helperPolicyArgs(settings) {
|
|
33
|
+
return helperPolicyFields.flatMap(key => settings[key] === undefined ? [] : ['--' + key, String(settings[key])]);
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-workbench/network",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24.13.1"
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"lib",
|
|
26
26
|
"NOTICE.md",
|
|
27
27
|
"cordis.patch.yml",
|
|
28
|
-
"locale"
|
|
28
|
+
"locale",
|
|
29
|
+
"helpers"
|
|
29
30
|
],
|
|
30
31
|
"dsh": {
|
|
31
32
|
"bundle": {
|
|
@@ -55,10 +56,6 @@
|
|
|
55
56
|
"dsh-plugin",
|
|
56
57
|
"deepseek-harness"
|
|
57
58
|
],
|
|
58
|
-
"optionalDependencies": {
|
|
59
|
-
"@ai-workbench/net-helper-darwin-arm64": "0.4.1",
|
|
60
|
-
"@ai-workbench/net-helper-darwin-x64": "0.4.1"
|
|
61
|
-
},
|
|
62
59
|
"repository": {
|
|
63
60
|
"type": "git",
|
|
64
61
|
"url": "git+https://github.com/yzsnstotz/ai-workbench-network.git"
|