@zqagent/shared 0.0.0-stage → 0.3.8
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/LICENSE +190 -0
- package/README.md +21 -2
- package/dist/approval-view.cjs +99 -0
- package/dist/approval-view.cjs.map +1 -0
- package/dist/approval-view.d.cts +39 -0
- package/dist/approval-view.d.ts +39 -0
- package/dist/approval-view.js +69 -0
- package/dist/approval-view.js.map +1 -0
- package/dist/fleet.cjs +263 -0
- package/dist/fleet.cjs.map +1 -0
- package/dist/fleet.d.cts +284 -0
- package/dist/fleet.d.ts +284 -0
- package/dist/fleet.js +214 -0
- package/dist/fleet.js.map +1 -0
- package/dist/hub-api.cjs +901 -0
- package/dist/hub-api.cjs.map +1 -0
- package/dist/hub-api.d.cts +1677 -0
- package/dist/hub-api.d.ts +1677 -0
- package/dist/hub-api.js +776 -0
- package/dist/hub-api.js.map +1 -0
- package/dist/index-DokiNlGJ.d.cts +182 -0
- package/dist/index-DokiNlGJ.d.ts +182 -0
- package/dist/index.cjs +861 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +779 -0
- package/dist/index.js.map +1 -0
- package/dist/llm-presets.cjs +92 -0
- package/dist/llm-presets.cjs.map +1 -0
- package/dist/llm-presets.d.cts +28 -0
- package/dist/llm-presets.d.ts +28 -0
- package/dist/llm-presets.js +66 -0
- package/dist/llm-presets.js.map +1 -0
- package/dist/media.cjs +111 -0
- package/dist/media.cjs.map +1 -0
- package/dist/media.d.cts +59 -0
- package/dist/media.d.ts +59 -0
- package/dist/media.js +79 -0
- package/dist/media.js.map +1 -0
- package/dist/messages-CiXjdR1Z.d.cts +257 -0
- package/dist/messages-CiXjdR1Z.d.ts +257 -0
- package/dist/model-capabilities.cjs +105 -0
- package/dist/model-capabilities.cjs.map +1 -0
- package/dist/model-capabilities.d.cts +46 -0
- package/dist/model-capabilities.d.ts +46 -0
- package/dist/model-capabilities.js +74 -0
- package/dist/model-capabilities.js.map +1 -0
- package/dist/notify/index.cjs +375 -0
- package/dist/notify/index.cjs.map +1 -0
- package/dist/notify/index.d.cts +2 -0
- package/dist/notify/index.d.ts +2 -0
- package/dist/notify/index.js +330 -0
- package/dist/notify/index.js.map +1 -0
- package/dist/secrets.cjs +121 -0
- package/dist/secrets.cjs.map +1 -0
- package/dist/secrets.d.cts +25 -0
- package/dist/secrets.d.ts +25 -0
- package/dist/secrets.js +82 -0
- package/dist/secrets.js.map +1 -0
- package/dist/wire/index.cjs +1265 -0
- package/dist/wire/index.cjs.map +1 -0
- package/dist/wire/index.d.cts +199 -0
- package/dist/wire/index.d.ts +199 -0
- package/dist/wire/index.js +1181 -0
- package/dist/wire/index.js.map +1 -0
- package/dist/wire/node.cjs +224 -0
- package/dist/wire/node.cjs.map +1 -0
- package/dist/wire/node.d.cts +14 -0
- package/dist/wire/node.d.ts +14 -0
- package/dist/wire/node.js +195 -0
- package/dist/wire/node.js.map +1 -0
- package/package.json +99 -3
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// src/wire/node.ts
|
|
2
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
3
|
+
|
|
4
|
+
// src/wire/utf8.ts
|
|
5
|
+
var Utf8Error = class extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "Utf8Error";
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
function utf8Encode(s) {
|
|
12
|
+
const buf = new Uint8Array(s.length * 4);
|
|
13
|
+
let n = 0;
|
|
14
|
+
for (let i = 0; i < s.length; i++) {
|
|
15
|
+
let cp = s.charCodeAt(i);
|
|
16
|
+
if (cp >= 55296 && cp <= 56319) {
|
|
17
|
+
const lo = i + 1 < s.length ? s.charCodeAt(i + 1) : 0;
|
|
18
|
+
if (lo < 56320 || lo > 57343) throw new Utf8Error("\u5B64\u7ACB\u7684 UTF-16 \u9AD8\u4EE3\u7406\u9879");
|
|
19
|
+
cp = 65536 + (cp - 55296 << 10) + (lo - 56320);
|
|
20
|
+
i++;
|
|
21
|
+
} else if (cp >= 56320 && cp <= 57343) {
|
|
22
|
+
throw new Utf8Error("\u5B64\u7ACB\u7684 UTF-16 \u4F4E\u4EE3\u7406\u9879");
|
|
23
|
+
}
|
|
24
|
+
if (cp < 128) {
|
|
25
|
+
buf[n++] = cp;
|
|
26
|
+
} else if (cp < 2048) {
|
|
27
|
+
buf[n++] = 192 | cp >> 6;
|
|
28
|
+
buf[n++] = 128 | cp & 63;
|
|
29
|
+
} else if (cp < 65536) {
|
|
30
|
+
buf[n++] = 224 | cp >> 12;
|
|
31
|
+
buf[n++] = 128 | cp >> 6 & 63;
|
|
32
|
+
buf[n++] = 128 | cp & 63;
|
|
33
|
+
} else {
|
|
34
|
+
buf[n++] = 240 | cp >> 18;
|
|
35
|
+
buf[n++] = 128 | cp >> 12 & 63;
|
|
36
|
+
buf[n++] = 128 | cp >> 6 & 63;
|
|
37
|
+
buf[n++] = 128 | cp & 63;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return buf.slice(0, n);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/wire/varint.ts
|
|
44
|
+
var WIRE_VARINT = 0;
|
|
45
|
+
var WIRE_LEN = 2;
|
|
46
|
+
var WireError = class extends Error {
|
|
47
|
+
constructor(message) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = "WireError";
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
|
|
53
|
+
var EMPTY = new Uint8Array(0);
|
|
54
|
+
var Writer = class {
|
|
55
|
+
buf;
|
|
56
|
+
len = 0;
|
|
57
|
+
constructor(initialCapacity = 64) {
|
|
58
|
+
this.buf = new Uint8Array(Math.max(8, initialCapacity));
|
|
59
|
+
}
|
|
60
|
+
ensure(n) {
|
|
61
|
+
if (this.len + n <= this.buf.length) return;
|
|
62
|
+
let cap = this.buf.length * 2;
|
|
63
|
+
while (cap < this.len + n) cap *= 2;
|
|
64
|
+
const next = new Uint8Array(cap);
|
|
65
|
+
next.set(this.buf.subarray(0, this.len));
|
|
66
|
+
this.buf = next;
|
|
67
|
+
}
|
|
68
|
+
/** 裸字节(帧头用,不带任何前缀) */
|
|
69
|
+
u8(b) {
|
|
70
|
+
this.ensure(1);
|
|
71
|
+
this.buf[this.len++] = b & 255;
|
|
72
|
+
}
|
|
73
|
+
raw(b) {
|
|
74
|
+
this.ensure(b.length);
|
|
75
|
+
this.buf.set(b, this.len);
|
|
76
|
+
this.len += b.length;
|
|
77
|
+
}
|
|
78
|
+
/** 无符号 varint(接受 number 或 bigint) */
|
|
79
|
+
varint(v) {
|
|
80
|
+
let x = typeof v === "bigint" ? v : BigInt(v);
|
|
81
|
+
if (x < 0n) throw new WireError("varint \u4E0D\u63A5\u53D7\u8D1F\u503C");
|
|
82
|
+
this.ensure(10);
|
|
83
|
+
while (x >= 0x80n) {
|
|
84
|
+
this.buf[this.len++] = Number(x & 0x7fn | 0x80n);
|
|
85
|
+
x >>= 7n;
|
|
86
|
+
}
|
|
87
|
+
this.buf[this.len++] = Number(x);
|
|
88
|
+
}
|
|
89
|
+
tag(field, wire) {
|
|
90
|
+
this.varint(field << 3 | wire);
|
|
91
|
+
}
|
|
92
|
+
/** length-delimited:长度 varint + 内容 */
|
|
93
|
+
lenPrefixed(b) {
|
|
94
|
+
this.varint(b.length);
|
|
95
|
+
this.raw(b);
|
|
96
|
+
}
|
|
97
|
+
// ---- proto3 字段:默认值不写线 ----
|
|
98
|
+
uint64Field(field, v) {
|
|
99
|
+
const x = typeof v === "bigint" ? v : BigInt(v);
|
|
100
|
+
if (x === 0n) return;
|
|
101
|
+
this.tag(field, WIRE_VARINT);
|
|
102
|
+
this.varint(x);
|
|
103
|
+
}
|
|
104
|
+
boolField(field, v) {
|
|
105
|
+
if (!v) return;
|
|
106
|
+
this.tag(field, WIRE_VARINT);
|
|
107
|
+
this.varint(1);
|
|
108
|
+
}
|
|
109
|
+
stringField(field, v) {
|
|
110
|
+
if (v === "") return;
|
|
111
|
+
this.tag(field, WIRE_LEN);
|
|
112
|
+
this.lenPrefixed(utf8Encode(v));
|
|
113
|
+
}
|
|
114
|
+
bytesField(field, v) {
|
|
115
|
+
if (v.length === 0) return;
|
|
116
|
+
this.tag(field, WIRE_LEN);
|
|
117
|
+
this.lenPrefixed(v);
|
|
118
|
+
}
|
|
119
|
+
finish() {
|
|
120
|
+
return this.buf.slice(0, this.len);
|
|
121
|
+
}
|
|
122
|
+
get length() {
|
|
123
|
+
return this.len;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// src/wire/messages.ts
|
|
128
|
+
function encodeEnvelope(env) {
|
|
129
|
+
const w = new Writer(128);
|
|
130
|
+
w.uint64Field(1, env.t);
|
|
131
|
+
w.stringField(2, env.nonce);
|
|
132
|
+
w.stringField(3, env.reqId);
|
|
133
|
+
w.stringField(4, env.type);
|
|
134
|
+
w.stringField(5, env.clientId);
|
|
135
|
+
w.stringField(6, env.nodeId);
|
|
136
|
+
w.stringField(7, env.projectId ?? "");
|
|
137
|
+
w.stringField(8, env.sessionId ?? "");
|
|
138
|
+
w.bytesField(9, env.payload ?? EMPTY);
|
|
139
|
+
w.uint64Field(10, env.timeoutMs ?? 0);
|
|
140
|
+
w.bytesField(11, env.sig ?? EMPTY);
|
|
141
|
+
return w.finish();
|
|
142
|
+
}
|
|
143
|
+
function signableBytes(env) {
|
|
144
|
+
return encodeEnvelope({ ...env, sig: void 0 });
|
|
145
|
+
}
|
|
146
|
+
function encodeAuth(auth) {
|
|
147
|
+
const w = new Writer(64);
|
|
148
|
+
w.stringField(1, auth.id);
|
|
149
|
+
w.uint64Field(2, auth.ts);
|
|
150
|
+
w.stringField(3, auth.nonce);
|
|
151
|
+
w.uint64Field(4, auth.proto);
|
|
152
|
+
w.stringField(5, auth.kind);
|
|
153
|
+
w.stringField(6, auth.client ?? "");
|
|
154
|
+
w.bytesField(7, auth.token ?? EMPTY);
|
|
155
|
+
for (const id of auth.scope ?? []) w.stringField(8, id);
|
|
156
|
+
w.uint64Field(9, auth.expires ?? 0);
|
|
157
|
+
w.uint64Field(10, auth.caps ?? 0);
|
|
158
|
+
return w.finish();
|
|
159
|
+
}
|
|
160
|
+
function signableAuthBytes(auth) {
|
|
161
|
+
return encodeAuth({ ...auth, token: void 0 });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/wire/node.ts
|
|
165
|
+
function hmacSha256(secret, data) {
|
|
166
|
+
const key = typeof secret === "string" ? Buffer.from(secret, "utf8") : secret;
|
|
167
|
+
const mac = createHmac("sha256", key).update(data).digest();
|
|
168
|
+
return new Uint8Array(mac);
|
|
169
|
+
}
|
|
170
|
+
function signEnvelope(secret, env) {
|
|
171
|
+
return { ...env, sig: hmacSha256(secret, signableBytes(env)) };
|
|
172
|
+
}
|
|
173
|
+
function verifyEnvelope(secret, env) {
|
|
174
|
+
const sig = env.sig;
|
|
175
|
+
if (!sig || sig.length !== 32) return false;
|
|
176
|
+
const expected = hmacSha256(secret, signableBytes(env));
|
|
177
|
+
return timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
|
|
178
|
+
}
|
|
179
|
+
function signAuth(secret, auth) {
|
|
180
|
+
return { ...auth, token: hmacSha256(secret, signableAuthBytes(auth)) };
|
|
181
|
+
}
|
|
182
|
+
function verifyAuth(secret, auth) {
|
|
183
|
+
const token = auth.token;
|
|
184
|
+
if (!token || token.length !== 32) return false;
|
|
185
|
+
const expected = hmacSha256(secret, signableAuthBytes(auth));
|
|
186
|
+
return timingSafeEqual(Buffer.from(expected), Buffer.from(token));
|
|
187
|
+
}
|
|
188
|
+
export {
|
|
189
|
+
hmacSha256,
|
|
190
|
+
signAuth,
|
|
191
|
+
signEnvelope,
|
|
192
|
+
verifyAuth,
|
|
193
|
+
verifyEnvelope
|
|
194
|
+
};
|
|
195
|
+
//# sourceMappingURL=node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/wire/node.ts","../../src/wire/utf8.ts","../../src/wire/varint.ts","../../src/wire/messages.ts"],"sourcesContent":["/**\n * 线协议签名(Node 侧,node:crypto)\n *\n * 规范见 docs/wire-protocol.md §2:sig = HMAC-SHA256(fleetSecret, 不含 sig 的编码字节)。\n * 浏览器/小程序侧不可引入 node:crypto,用 Web Crypto 对 `signableBytes()` 做同样的运算。\n */\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\nimport type { Auth, Envelope } from \"./messages.js\";\nimport { signableAuthBytes, signableBytes } from \"./messages.js\";\n\n/** HMAC-SHA256 原始 32 字节 */\nexport function hmacSha256(secret: string | Uint8Array, data: Uint8Array): Uint8Array {\n const key = typeof secret === \"string\" ? Buffer.from(secret, \"utf8\") : secret;\n const mac = createHmac(\"sha256\", key).update(data).digest();\n return new Uint8Array(mac);\n}\n\n/** 计算并写入签名(返回新对象) */\nexport function signEnvelope(secret: string | Uint8Array, env: Envelope): Envelope {\n return { ...env, sig: hmacSha256(secret, signableBytes(env)) };\n}\n\n/** 常量时间校验签名 */\nexport function verifyEnvelope(secret: string | Uint8Array, env: Envelope): boolean {\n const sig = env.sig;\n if (!sig || sig.length !== 32) return false;\n const expected = hmacSha256(secret, signableBytes(env));\n return timingSafeEqual(Buffer.from(expected), Buffer.from(sig));\n}\n\n// ===================== 握手认证(M2) =====================\n\n/** 计算并写入握手认证 token(节点面:以 fleetSecret 为密钥) */\nexport function signAuth(secret: string | Uint8Array, auth: Auth): Auth {\n return { ...auth, token: hmacSha256(secret, signableAuthBytes(auth)) };\n}\n\n/** 常量时间校验握手 token */\nexport function verifyAuth(secret: string | Uint8Array, auth: Auth): boolean {\n const token = auth.token;\n if (!token || token.length !== 32) return false;\n const expected = hmacSha256(secret, signableAuthBytes(auth));\n return timingSafeEqual(Buffer.from(expected), Buffer.from(token));\n}\n","/**\n * UTF-8 编解码(手工实现,零依赖)\n *\n * 不依赖 TextEncoder/TextDecoder:小程序的 JS 运行时(部分基础库)不保证提供这两个全局,\n * 而本模块必须能被 Node / 浏览器 / 小程序三处共用。严格校验(拒绝孤立代理项、过长编码、\n * 超出码点范围)以对齐 protobuf 对 `string` 字段的 UTF-8 要求。\n */\n\nexport class Utf8Error extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"Utf8Error\";\n }\n}\n\n/** 字符串 → UTF-8 字节 */\nexport function utf8Encode(s: string): Uint8Array {\n const buf = new Uint8Array(s.length * 4); // 每字符最多 4 字节(上界)\n let n = 0;\n for (let i = 0; i < s.length; i++) {\n let cp = s.charCodeAt(i);\n if (cp >= 0xd800 && cp <= 0xdbff) {\n const lo = i + 1 < s.length ? s.charCodeAt(i + 1) : 0;\n if (lo < 0xdc00 || lo > 0xdfff) throw new Utf8Error(\"孤立的 UTF-16 高代理项\");\n cp = 0x10000 + ((cp - 0xd800) << 10) + (lo - 0xdc00);\n i++;\n } else if (cp >= 0xdc00 && cp <= 0xdfff) {\n throw new Utf8Error(\"孤立的 UTF-16 低代理项\");\n }\n if (cp < 0x80) {\n buf[n++] = cp;\n } else if (cp < 0x800) {\n buf[n++] = 0xc0 | (cp >> 6);\n buf[n++] = 0x80 | (cp & 0x3f);\n } else if (cp < 0x10000) {\n buf[n++] = 0xe0 | (cp >> 12);\n buf[n++] = 0x80 | ((cp >> 6) & 0x3f);\n buf[n++] = 0x80 | (cp & 0x3f);\n } else {\n buf[n++] = 0xf0 | (cp >> 18);\n buf[n++] = 0x80 | ((cp >> 12) & 0x3f);\n buf[n++] = 0x80 | ((cp >> 6) & 0x3f);\n buf[n++] = 0x80 | (cp & 0x3f);\n }\n }\n return buf.slice(0, n);\n}\n\n/** UTF-8 字节 → 字符串(严格校验) */\nexport function utf8Decode(b: Uint8Array): string {\n let out = \"\";\n let i = 0;\n while (i < b.length) {\n const c = b[i]!;\n if (c < 0x80) {\n out += String.fromCharCode(c);\n i++;\n continue;\n }\n let cp: number;\n let need: number;\n let min: number;\n if ((c & 0xe0) === 0xc0) {\n cp = c & 0x1f;\n need = 1;\n min = 0x80;\n } else if ((c & 0xf0) === 0xe0) {\n cp = c & 0x0f;\n need = 2;\n min = 0x800;\n } else if ((c & 0xf8) === 0xf0) {\n cp = c & 0x07;\n need = 3;\n min = 0x10000;\n } else {\n throw new Utf8Error(`非法 UTF-8 起始字节 0x${c.toString(16)}`);\n }\n if (i + need >= b.length) throw new Utf8Error(\"UTF-8 序列被截断\");\n for (let k = 1; k <= need; k++) {\n const cc = b[i + k]!;\n if ((cc & 0xc0) !== 0x80) throw new Utf8Error(`非法 UTF-8 续接字节 0x${cc.toString(16)}`);\n cp = (cp << 6) | (cc & 0x3f);\n }\n if (cp < min) throw new Utf8Error(\"UTF-8 过长编码(overlong)\");\n if (cp > 0x10ffff) throw new Utf8Error(\"码点超出 U+10FFFF\");\n if (cp >= 0xd800 && cp <= 0xdfff) throw new Utf8Error(\"UTF-8 编码了 UTF-16 代理项\");\n i += need + 1;\n if (cp < 0x10000) {\n out += String.fromCharCode(cp);\n } else {\n const v = cp - 0x10000;\n out += String.fromCharCode(0xd800 + (v >> 10), 0xdc00 + (v & 0x3ff));\n }\n }\n return out;\n}\n","/**\n * 最小 protobuf 原语(varint + length-delimited 读写,零依赖)\n *\n * Node 与浏览器/小程序共用:只用 `Uint8Array`(`Buffer` 是其子类)与 `DataView`,\n * 不触碰任何平台 API。编码规则严格对齐 proto3 线格式(默认值省略、字段号升序),\n * 以便与 Rust 侧 `prost` 产出**逐字节一致**的结果(见 docs/wire-protocol.md §5)。\n */\nimport { utf8Encode, utf8Decode } from \"./utf8.js\";\n\nexport const WIRE_VARINT = 0;\nexport const WIRE_FIXED64 = 1;\nexport const WIRE_LEN = 2;\nexport const WIRE_FIXED32 = 5;\n\nexport class WireError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WireError\";\n }\n}\n\nconst MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);\nconst EMPTY = new Uint8Array(0);\n\n/** 增量字节写入器(自动扩容) */\nexport class Writer {\n private buf: Uint8Array;\n private len = 0;\n\n constructor(initialCapacity = 64) {\n this.buf = new Uint8Array(Math.max(8, initialCapacity));\n }\n\n private ensure(n: number): void {\n if (this.len + n <= this.buf.length) return;\n let cap = this.buf.length * 2;\n while (cap < this.len + n) cap *= 2;\n const next = new Uint8Array(cap);\n next.set(this.buf.subarray(0, this.len));\n this.buf = next;\n }\n\n /** 裸字节(帧头用,不带任何前缀) */\n u8(b: number): void {\n this.ensure(1);\n this.buf[this.len++] = b & 0xff;\n }\n\n raw(b: Uint8Array): void {\n this.ensure(b.length);\n this.buf.set(b, this.len);\n this.len += b.length;\n }\n\n /** 无符号 varint(接受 number 或 bigint) */\n varint(v: number | bigint): void {\n let x = typeof v === \"bigint\" ? v : BigInt(v);\n if (x < 0n) throw new WireError(\"varint 不接受负值\");\n this.ensure(10);\n while (x >= 0x80n) {\n this.buf[this.len++] = Number((x & 0x7fn) | 0x80n);\n x >>= 7n;\n }\n this.buf[this.len++] = Number(x);\n }\n\n tag(field: number, wire: number): void {\n this.varint((field << 3) | wire);\n }\n\n /** length-delimited:长度 varint + 内容 */\n lenPrefixed(b: Uint8Array): void {\n this.varint(b.length);\n this.raw(b);\n }\n\n // ---- proto3 字段:默认值不写线 ----\n\n uint64Field(field: number, v: number | bigint): void {\n const x = typeof v === \"bigint\" ? v : BigInt(v);\n if (x === 0n) return;\n this.tag(field, WIRE_VARINT);\n this.varint(x);\n }\n\n boolField(field: number, v: boolean): void {\n if (!v) return;\n this.tag(field, WIRE_VARINT);\n this.varint(1);\n }\n\n stringField(field: number, v: string): void {\n if (v === \"\") return;\n this.tag(field, WIRE_LEN);\n this.lenPrefixed(utf8Encode(v));\n }\n\n bytesField(field: number, v: Uint8Array): void {\n if (v.length === 0) return;\n this.tag(field, WIRE_LEN);\n this.lenPrefixed(v);\n }\n\n finish(): Uint8Array {\n return this.buf.slice(0, this.len);\n }\n\n get length(): number {\n return this.len;\n }\n}\n\n/** 顺序字节读取器(越界一律判错) */\nexport class Reader {\n private pos = 0;\n\n constructor(private readonly buf: Uint8Array) {}\n\n get remaining(): number {\n return this.buf.length - this.pos;\n }\n\n get eof(): boolean {\n return this.pos >= this.buf.length;\n }\n\n varint(): bigint {\n let shift = 0n;\n let result = 0n;\n for (let i = 0; i < 10; i++) {\n if (this.pos >= this.buf.length) throw new WireError(\"varint 越界\");\n const b = this.buf[this.pos++]!;\n result |= BigInt(b & 0x7f) << shift;\n if ((b & 0x80) === 0) {\n if (i === 9 && b > 1) throw new WireError(\"varint 溢出(超过 64 位)\");\n return result;\n }\n shift += 7n;\n }\n throw new WireError(\"varint 超过 10 字节\");\n }\n\n /** u64 → number(超安全整数范围即判错,避免静默精度丢失) */\n u64(): number {\n const v = this.varint();\n if (v > MAX_SAFE) throw new WireError(`u64 超出 JS 安全整数范围: ${v}`);\n return Number(v);\n }\n\n tag(): { field: number; wire: number } {\n const t = this.varint();\n const field = Number(t >> 3n);\n const wire = Number(t & 7n);\n if (field <= 0) throw new WireError(\"字段号必须 ≥ 1\");\n return { field, wire };\n }\n\n /** 取 n 个字节(越界判错) */\n take(n: number): Uint8Array {\n if (!Number.isInteger(n) || n < 0) throw new WireError(`非法长度: ${n}`);\n if (this.pos + n > this.buf.length) {\n throw new WireError(`读取越界:需要 ${n} 字节,剩余 ${this.buf.length - this.pos}`);\n }\n const out = this.buf.subarray(this.pos, this.pos + n);\n this.pos += n;\n return out;\n }\n\n /** length-delimited:读长度再取其内容 */\n lenPrefixed(): Uint8Array {\n return this.take(this.u64());\n }\n\n string(): string {\n return utf8Decode(this.lenPrefixed());\n }\n\n /** 跳过未知字段(前向兼容的关键:未来新增字段号不应导致解析失败) */\n skip(wire: number): void {\n switch (wire) {\n case WIRE_VARINT:\n this.varint();\n return;\n case WIRE_LEN:\n this.lenPrefixed();\n return;\n case WIRE_FIXED64:\n this.take(8);\n return;\n case WIRE_FIXED32:\n this.take(4);\n return;\n default:\n throw new WireError(`不支持的 wire type: ${wire}`);\n }\n }\n}\n\nexport { EMPTY as EMPTY_BYTES };\n","/**\n * 消息编解码(Envelope / Ack / Status / Event)\n *\n * 字段定义见 proto/fleet.proto。手写编解码,规则与 prost 对齐(proto3 默认值省略、\n * 字段号升序、string 严格 UTF-8),未知字段按 wire type 跳过以支持前向兼容。\n *\n * 签名:`signableBytes()` 给出「不含 sig」的编码字节;`sig` 是 10 号字段(最后),\n * 因此 `encode(env, sig 置空) === signableBytes(env)` 恒成立,校验方可重编码比对。\n */\nimport { Reader, Writer, WIRE_LEN, WIRE_VARINT, WireError, EMPTY_BYTES } from \"./varint.js\";\n\n/** 与现有 FleetCmdEnvelope(packages/shared/src/fleet.ts:159-178)语义一致 */\nexport interface Envelope {\n /** 毫秒时间戳 */\n t: number;\n nonce: string;\n reqId: string;\n type: string;\n clientId: string;\n nodeId: string;\n projectId?: string;\n sessionId?: string;\n /** 载荷字节(按 type 选择强类型消息或 JSON 字节) */\n payload?: Uint8Array;\n /**\n * 执行预算(毫秒,可选):由发起方指定,网关据此决定等待回执的上限。\n * 0/缺省表示按 `type` 的默认分级(见 docs/wire-protocol.md §5.2)。\n */\n timeoutMs?: number;\n /** HMAC-SHA256 原始 32 字节(**必须是最后一个字段**:其前缀即签名字节) */\n sig?: Uint8Array;\n}\n\nexport interface Ack {\n reqId: string;\n type: string;\n ok: boolean;\n error?: string;\n data?: Uint8Array;\n}\n\n/**\n * `STATUS` 帧的消息身份标记。\n *\n * 该帧类型**同时承载**上行 `Status` 与下行 `NodeState`:两者字段 1..3 同构\n * (string / bool / uint64),错用解码器不会报错、只会静默读到错值\n * (`ts` 被当成 `last_seen`)。编码方写入标记,解码方据此拒绝错配。\n */\nexport const MSG_KIND_NODE_STATE = 1;\nexport const MSG_KIND_STATUS = 2;\n\nexport interface Status {\n nodeId: string;\n online: boolean;\n /** 毫秒时间戳(现有 JSON 协议为 ISO 字符串,映射在 M2) */\n ts: number;\n label?: string;\n platform?: string;\n mode?: string;\n version?: string;\n ip?: string;\n}\n\nexport interface EventMsg {\n seq: number;\n ts: number;\n type: string;\n projectId?: string;\n sessionId?: string;\n data?: Uint8Array;\n /**\n * 事件来源节点。由**网关**按连接绑定的 nodeId 填充(节点上行时留空)。\n *\n * 终端面一条连接可订阅多个节点,缺了它就无法分辨事件来自哪台节点。\n */\n nodeId?: string;\n}\n\n/**\n * 命令终端数据(`FrameType.TERMINAL`)。\n *\n * 与 `EventMsg` 完全平行的另一条通道,**不是** Event 的一种:它不参与\n * seq 游标、不入事件环形缓冲、不跨副本广播(理由见 proto 注释)。\n */\nexport interface TerminalData {\n /** 目标节点;节点上行时留空,由网关按连接绑定填充 */\n nodeId?: string;\n /** 终端实例 ID(节点侧生成,`t_` 前缀;与会话 ID 不同空间) */\n terminalId: string;\n /**\n * 帧种类:\n * opened | data | exited | error —— 节点 → 网关 → 终端\n * input | resize —— 终端 → 网关 → 节点\n *\n * 开/关终端不走这条通道(见 `terminal_create` / `terminal_close` 指令)。\n */\n kind: string;\n /** data/input 为原始字节;resize 为 JSON 字节({\"cols\":N,\"rows\":N}) */\n data?: Uint8Array;\n /** 终端内局部序号(仅该终端内有效,不参与 resume);文件流中为 stream_id */\n seq?: number;\n /**\n * 通道:0/缺省 = 终端;1 = 文件流(音视频预览)。\n *\n * 文件流复用 TERMINAL 帧以避开协议版本升级(帧类型低 4 位已用尽),\n * 但网关按该字段分流,不会 fanout 给终端连接。\n */\n channel?: number;\n /**\n * P2P 打洞信令(`channel=2`)的会话标识。\n *\n * 网关按它把信令**回发给发起连接**,不 fanout——否则双方公网 IP 会泄漏给\n * 同一节点的其他协作者。\n */\n session?: string;\n}\n\n// ===================== Envelope =====================\n\nexport function encodeEnvelope(env: Envelope): Uint8Array {\n const w = new Writer(128);\n w.uint64Field(1, env.t);\n w.stringField(2, env.nonce);\n w.stringField(3, env.reqId);\n w.stringField(4, env.type);\n w.stringField(5, env.clientId);\n w.stringField(6, env.nodeId);\n w.stringField(7, env.projectId ?? \"\");\n w.stringField(8, env.sessionId ?? \"\");\n w.bytesField(9, env.payload ?? EMPTY_BYTES);\n w.uint64Field(10, env.timeoutMs ?? 0);\n w.bytesField(11, env.sig ?? EMPTY_BYTES);\n return w.finish();\n}\n\n/** 参与签名的字节(字段 1..10,不含 sig) */\nexport function signableBytes(env: Envelope): Uint8Array {\n return encodeEnvelope({ ...env, sig: undefined });\n}\n\nexport function decodeEnvelope(bytes: Uint8Array): Envelope {\n const r = new Reader(bytes);\n const env: Envelope = { t: 0, nonce: \"\", reqId: \"\", type: \"\", clientId: \"\", nodeId: \"\" };\n while (!r.eof) {\n const { field, wire } = r.tag();\n switch (field) {\n case 1:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else env.t = r.u64();\n break;\n case 2:\n if (wire !== WIRE_LEN) r.skip(wire);\n else env.nonce = r.string();\n break;\n case 3:\n if (wire !== WIRE_LEN) r.skip(wire);\n else env.reqId = r.string();\n break;\n case 4:\n if (wire !== WIRE_LEN) r.skip(wire);\n else env.type = r.string();\n break;\n case 5:\n if (wire !== WIRE_LEN) r.skip(wire);\n else env.clientId = r.string();\n break;\n case 6:\n if (wire !== WIRE_LEN) r.skip(wire);\n else env.nodeId = r.string();\n break;\n case 7:\n if (wire !== WIRE_LEN) r.skip(wire);\n else env.projectId = r.string();\n break;\n case 8:\n if (wire !== WIRE_LEN) r.skip(wire);\n else env.sessionId = r.string();\n break;\n case 9:\n if (wire !== WIRE_LEN) r.skip(wire);\n else env.payload = r.lenPrefixed();\n break;\n case 10:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else env.timeoutMs = r.u64();\n break;\n case 11:\n if (wire !== WIRE_LEN) r.skip(wire);\n else env.sig = r.lenPrefixed();\n break;\n default:\n r.skip(wire);\n }\n }\n return env;\n}\n\n// ===================== Ack =====================\n\nexport function encodeAck(ack: Ack): Uint8Array {\n const w = new Writer(64);\n w.stringField(1, ack.reqId);\n w.stringField(2, ack.type);\n w.boolField(3, ack.ok);\n w.stringField(4, ack.error ?? \"\");\n w.bytesField(5, ack.data ?? EMPTY_BYTES);\n return w.finish();\n}\n\nexport function decodeAck(bytes: Uint8Array): Ack {\n const r = new Reader(bytes);\n const ack: Ack = { reqId: \"\", type: \"\", ok: false };\n while (!r.eof) {\n const { field, wire } = r.tag();\n switch (field) {\n case 1:\n if (wire !== WIRE_LEN) r.skip(wire);\n else ack.reqId = r.string();\n break;\n case 2:\n if (wire !== WIRE_LEN) r.skip(wire);\n else ack.type = r.string();\n break;\n case 3:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else ack.ok = r.varint() !== 0n;\n break;\n case 4:\n if (wire !== WIRE_LEN) r.skip(wire);\n else ack.error = r.string();\n break;\n case 5:\n if (wire !== WIRE_LEN) r.skip(wire);\n else ack.data = r.lenPrefixed();\n break;\n default:\n r.skip(wire);\n }\n }\n return ack;\n}\n\n// ===================== Status =====================\n\nexport function encodeStatus(st: Status): Uint8Array {\n const w = new Writer(96);\n w.stringField(1, st.nodeId);\n w.boolField(2, st.online);\n w.uint64Field(3, st.ts);\n w.stringField(4, st.label ?? \"\");\n w.stringField(5, st.platform ?? \"\");\n w.stringField(6, st.mode ?? \"\");\n w.stringField(7, st.version ?? \"\");\n w.stringField(8, st.ip ?? \"\");\n w.uint64Field(9, MSG_KIND_STATUS);\n return w.finish();\n}\n\nexport function decodeStatus(bytes: Uint8Array): Status {\n const r = new Reader(bytes);\n const st: Status = { nodeId: \"\", online: false, ts: 0 };\n while (!r.eof) {\n const { field, wire } = r.tag();\n switch (field) {\n case 1:\n if (wire !== WIRE_LEN) r.skip(wire);\n else st.nodeId = r.string();\n break;\n case 2:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else st.online = r.varint() !== 0n;\n break;\n case 3:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else st.ts = r.u64();\n break;\n case 4:\n if (wire !== WIRE_LEN) r.skip(wire);\n else st.label = r.string();\n break;\n case 5:\n if (wire !== WIRE_LEN) r.skip(wire);\n else st.platform = r.string();\n break;\n case 6:\n if (wire !== WIRE_LEN) r.skip(wire);\n else st.mode = r.string();\n break;\n case 7:\n if (wire !== WIRE_LEN) r.skip(wire);\n else st.version = r.string();\n break;\n case 8:\n if (wire !== WIRE_LEN) r.skip(wire);\n else st.ip = r.string();\n break;\n case 9: {\n if (wire !== WIRE_VARINT) r.skip(wire);\n else checkMsgKind(\"Status\", Number(r.varint()), MSG_KIND_STATUS);\n break;\n }\n default:\n r.skip(wire);\n }\n }\n return st;\n}\n\n// ===================== Event =====================\n\nexport function encodeEvent(ev: EventMsg): Uint8Array {\n const w = new Writer(128);\n w.uint64Field(1, ev.seq);\n w.uint64Field(2, ev.ts);\n w.stringField(3, ev.type);\n w.stringField(4, ev.projectId ?? \"\");\n w.stringField(5, ev.sessionId ?? \"\");\n w.bytesField(6, ev.data ?? EMPTY_BYTES);\n w.stringField(7, ev.nodeId ?? \"\");\n return w.finish();\n}\n\nexport function decodeEvent(bytes: Uint8Array): EventMsg {\n const r = new Reader(bytes);\n const ev: EventMsg = { seq: 0, ts: 0, type: \"\" };\n while (!r.eof) {\n const { field, wire } = r.tag();\n switch (field) {\n case 1:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else ev.seq = r.u64();\n break;\n case 2:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else ev.ts = r.u64();\n break;\n case 3:\n if (wire !== WIRE_LEN) r.skip(wire);\n else ev.type = r.string();\n break;\n case 4:\n if (wire !== WIRE_LEN) r.skip(wire);\n else ev.projectId = r.string();\n break;\n case 5:\n if (wire !== WIRE_LEN) r.skip(wire);\n else ev.sessionId = r.string();\n break;\n case 6:\n if (wire !== WIRE_LEN) r.skip(wire);\n else ev.data = r.lenPrefixed();\n break;\n case 7:\n if (wire !== WIRE_LEN) r.skip(wire);\n else ev.nodeId = r.string();\n break;\n default:\n r.skip(wire);\n }\n }\n return ev;\n}\n\nexport { WireError };\n\n// ===================== 节点能力/项目快照 =====================\n\nexport interface Info {\n nodeId: string;\n platform?: string;\n version?: string;\n ip?: string;\n /** 项目数 */\n projects: number;\n /** 会话数 */\n sessions: number;\n /** 运行中的会话数 */\n running: number;\n /** 等待审批/提问的会话数 */\n waiting: number;\n /** 毫秒时间戳 */\n ts: number;\n /**\n * 节点上报的**原始 info JSON**(含 label / 项目明细 / stats / usage)。\n *\n * 5..9 的计数供网关快速聚合;本字段原样承载完整快照,供控制面读取\n * (替代从前经 MQTT retained info 的那条路径),避免为视图反复改 schema。\n */\n detail?: Uint8Array;\n}\n\nexport function encodeInfo(info: Info): Uint8Array {\n const w = new Writer(96);\n w.stringField(1, info.nodeId);\n w.stringField(2, info.platform ?? \"\");\n w.stringField(3, info.version ?? \"\");\n w.stringField(4, info.ip ?? \"\");\n w.uint64Field(5, info.projects);\n w.uint64Field(6, info.sessions);\n w.uint64Field(7, info.running);\n w.uint64Field(8, info.waiting);\n w.uint64Field(9, info.ts);\n w.bytesField(10, info.detail ?? EMPTY_BYTES);\n return w.finish();\n}\n\nexport function decodeInfo(bytes: Uint8Array): Info {\n const r = new Reader(bytes);\n const info: Info = { nodeId: \"\", projects: 0, sessions: 0, running: 0, waiting: 0, ts: 0 };\n while (!r.eof) {\n const { field, wire } = r.tag();\n switch (field) {\n case 1:\n if (wire !== WIRE_LEN) r.skip(wire);\n else info.nodeId = r.string();\n break;\n case 2:\n if (wire !== WIRE_LEN) r.skip(wire);\n else info.platform = r.string();\n break;\n case 3:\n if (wire !== WIRE_LEN) r.skip(wire);\n else info.version = r.string();\n break;\n case 4:\n if (wire !== WIRE_LEN) r.skip(wire);\n else info.ip = r.string();\n break;\n case 5:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else info.projects = r.u64();\n break;\n case 6:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else info.sessions = r.u64();\n break;\n case 7:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else info.running = r.u64();\n break;\n case 8:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else info.waiting = r.u64();\n break;\n case 9:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else info.ts = r.u64();\n break;\n case 10:\n if (wire !== WIRE_LEN) r.skip(wire);\n else info.detail = r.lenPrefixed();\n break;\n default:\n r.skip(wire);\n }\n }\n return info;\n}\n\n// ===================== 通知(需可靠送达的上行) =====================\n\n/**\n * 节点通知:审批请求 / 提问 / 回合完成 / 错误。\n *\n * 与 `EventMsg` 的分工:Event 是高频实时进度,Notify 是低频但重要(审批/提问挂起必须\n * 让终端看到)。两者共用网关分配的 `seq` 游标与缓冲,因此 `resume` 时都能回放。\n */\nexport interface NotifyMsg {\n /** 由网关分配的游标(节点上报时留 0) */\n seq: number;\n ts: number;\n kind: string;\n projectId?: string;\n sessionId?: string;\n data?: Uint8Array;\n /** 通知来源节点;由网关填充(多路复用终端据此归属到具体节点) */\n nodeId?: string;\n}\n\nexport function encodeNotify(n: NotifyMsg): Uint8Array {\n const w = new Writer(160);\n w.uint64Field(1, n.seq);\n w.uint64Field(2, n.ts);\n w.stringField(3, n.kind);\n w.stringField(4, n.projectId ?? \"\");\n w.stringField(5, n.sessionId ?? \"\");\n w.bytesField(6, n.data ?? EMPTY_BYTES);\n w.stringField(7, n.nodeId ?? \"\");\n return w.finish();\n}\n\nexport function decodeNotify(bytes: Uint8Array): NotifyMsg {\n const r = new Reader(bytes);\n const n: NotifyMsg = { seq: 0, ts: 0, kind: \"\" };\n while (!r.eof) {\n const { field, wire } = r.tag();\n switch (field) {\n case 1:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else n.seq = r.u64();\n break;\n case 2:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else n.ts = r.u64();\n break;\n case 3:\n if (wire !== WIRE_LEN) r.skip(wire);\n else n.kind = r.string();\n break;\n case 4:\n if (wire !== WIRE_LEN) r.skip(wire);\n else n.projectId = r.string();\n break;\n case 5:\n if (wire !== WIRE_LEN) r.skip(wire);\n else n.sessionId = r.string();\n break;\n case 6:\n if (wire !== WIRE_LEN) r.skip(wire);\n else n.data = r.lenPrefixed();\n break;\n case 7:\n if (wire !== WIRE_LEN) r.skip(wire);\n else n.nodeId = r.string();\n break;\n default:\n r.skip(wire);\n }\n }\n return n;\n}\n\n// ===================== 握手 / 认证 =====================\n\nexport interface Auth {\n /** 节点面:nodeId;终端面:userId */\n id: string;\n /** 毫秒时间戳(±60s 窗口) */\n ts: number;\n nonce: string;\n /** 协议能力版本 */\n proto: number;\n /** \"node\" | \"terminal\" */\n kind: string;\n /** 终端载体:\"web\" | \"miniapp\" | \"app\"(节点面留空) */\n client?: string;\n /**\n * 终端面 = HMAC-SHA256(terminalSecret, signableAuthBytes);节点面 = HMAC-SHA256(fleetSecret, …)\n *\n * 注意 `scope`/`expires` 在 `token` **之后**:proto3 按字段号升序编码,因此它们仍在\n * `signableAuthBytes` 内 —— 即**同样受签名完整性保护**(改 scope 会让 token 失效)。\n */\n token?: Uint8Array;\n /** 终端面被授权的 nodeId 列表(非空时网关严格校验;见 `verifyTerminalAuth`) */\n scope?: string[];\n /** 终端面过期时刻(毫秒);>0 时网关跳过 ±60s 窗口 */\n expires?: number;\n /**\n * 客户端能力位(见 `frame.ts` 的 `CAP_DEFLATE`)。\n *\n * 只用于声明支持;节点只有在 `AuthOk.caps` 也回同样位时才启用压缩上行。\n * 旧网关忽略未知字段,行为不变。\n */\n caps?: number;\n}\n\nexport interface AuthOk {\n ok: boolean;\n error?: string;\n serverTs: number;\n /** 网关能力位(语义同 `Auth.caps`);旧网关不回此字段(视为 0) */\n caps?: number;\n}\n\nexport function encodeAuth(auth: Auth): Uint8Array {\n const w = new Writer(64);\n w.stringField(1, auth.id);\n w.uint64Field(2, auth.ts);\n w.stringField(3, auth.nonce);\n // 注意:proto3 默认值省略 → proto=0 与 proto=1 不同,proto=0 不出现在线格式中\n w.uint64Field(4, auth.proto);\n w.stringField(5, auth.kind);\n w.stringField(6, auth.client ?? \"\");\n w.bytesField(7, auth.token ?? EMPTY_BYTES);\n // repeated string 在 protobuf 中是重复 tag(非 packed),逐个写即可\n for (const id of auth.scope ?? []) w.stringField(8, id);\n w.uint64Field(9, auth.expires ?? 0);\n w.uint64Field(10, auth.caps ?? 0);\n return w.finish();\n}\n\n/**\n * 参与签名的字节:清空 `token` 后编码。\n *\n * `scope`/`expires` 字段号大于 `token`,但 prost/本实现都按字段号升序编码,\n * 因此它们**仍在签名字节内**(受完整性保护),不是「token 之后的免费字段」。\n */\nexport function signableAuthBytes(auth: Auth): Uint8Array {\n return encodeAuth({ ...auth, token: undefined });\n}\n\nexport function decodeAuth(bytes: Uint8Array): Auth {\n const r = new Reader(bytes);\n const auth: Auth = { id: \"\", ts: 0, nonce: \"\", proto: 0, kind: \"\" };\n while (!r.eof) {\n const { field, wire } = r.tag();\n switch (field) {\n case 1:\n if (wire !== WIRE_LEN) r.skip(wire);\n else auth.id = r.string();\n break;\n case 2:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else auth.ts = r.u64();\n break;\n case 3:\n if (wire !== WIRE_LEN) r.skip(wire);\n else auth.nonce = r.string();\n break;\n case 4:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else auth.proto = r.u64();\n break;\n case 5:\n if (wire !== WIRE_LEN) r.skip(wire);\n else auth.kind = r.string();\n break;\n case 6:\n if (wire !== WIRE_LEN) r.skip(wire);\n else auth.client = r.string();\n break;\n case 7:\n if (wire !== WIRE_LEN) r.skip(wire);\n else auth.token = r.lenPrefixed();\n break;\n case 8:\n if (wire !== WIRE_LEN) r.skip(wire);\n else (auth.scope ??= []).push(r.string());\n break;\n case 9:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else auth.expires = r.u64();\n break;\n case 10:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else auth.caps = r.u64();\n break;\n default:\n r.skip(wire);\n }\n }\n return auth;\n}\n\nexport function encodeAuthOk(ok: AuthOk): Uint8Array {\n const w = new Writer(48);\n w.boolField(1, ok.ok);\n w.stringField(2, ok.error ?? \"\");\n w.uint64Field(3, ok.serverTs);\n w.uint64Field(4, ok.caps ?? 0);\n return w.finish();\n}\n\nexport function decodeAuthOk(bytes: Uint8Array): AuthOk {\n const r = new Reader(bytes);\n const out: AuthOk = { ok: false, serverTs: 0 };\n while (!r.eof) {\n const { field, wire } = r.tag();\n switch (field) {\n case 1:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else out.ok = r.varint() !== 0n;\n break;\n case 2:\n if (wire !== WIRE_LEN) r.skip(wire);\n else out.error = r.string();\n break;\n case 3:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else out.serverTs = r.u64();\n break;\n case 4:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else out.caps = r.u64();\n break;\n default:\n r.skip(wire);\n }\n }\n return out;\n}\n\n// ===================== 终端数据 =====================\n\nexport function encodeTerminalData(td: TerminalData): Uint8Array {\n const w = new Writer(64 + (td.data?.length ?? 0));\n w.stringField(1, td.nodeId ?? \"\");\n w.stringField(2, td.terminalId);\n w.stringField(3, td.kind);\n w.bytesField(4, td.data ?? EMPTY_BYTES);\n w.uint64Field(5, td.seq ?? 0);\n // 0 = 终端:不写字段,保持与旧端字节兼容(proto3 默认值不上线)\n if (td.channel) w.uint64Field(6, td.channel);\n if (td.session) w.stringField(7, td.session);\n return w.finish();\n}\n\nexport function decodeTerminalData(bytes: Uint8Array): TerminalData {\n const r = new Reader(bytes);\n const td: TerminalData = { terminalId: \"\", kind: \"\" };\n while (!r.eof) {\n const { field, wire } = r.tag();\n switch (field) {\n case 1:\n if (wire !== WIRE_LEN) r.skip(wire);\n else td.nodeId = r.string();\n break;\n case 2:\n if (wire !== WIRE_LEN) r.skip(wire);\n else td.terminalId = r.string();\n break;\n case 3:\n if (wire !== WIRE_LEN) r.skip(wire);\n else td.kind = r.string();\n break;\n case 4:\n if (wire !== WIRE_LEN) r.skip(wire);\n else td.data = r.lenPrefixed();\n break;\n case 5:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else td.seq = r.u64();\n break;\n case 6:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else td.channel = r.u64();\n break;\n case 7:\n if (wire !== WIRE_LEN) r.skip(wire);\n else td.session = r.string();\n break;\n default:\n r.skip(wire);\n }\n }\n return td;\n}\n\n// ===================== 快照 / 订阅 =====================\n\nexport interface NodeState {\n nodeId: string;\n online: boolean;\n lastSeen: number;\n platform?: string;\n version?: string;\n ip?: string;\n /**\n * 节点上报的原始 info JSON(`Info.detail` 的转发),供终端快照读取项目/统计。\n *\n * 快照借此**替代 MQTT retained info**:订阅后一次拿到,不必逐节点发指令。\n * 节点从未上报过 INFO 时为空。\n */\n detail?: Uint8Array;\n}\n\nexport interface Snapshot {\n nodes: NodeState[];\n /** 当前事件游标(客户端据此发起 resume) */\n seq: number;\n}\n\nexport function encodeNodeState(n: NodeState): Uint8Array {\n const w = new Writer(96);\n w.stringField(1, n.nodeId);\n w.boolField(2, n.online);\n w.uint64Field(3, n.lastSeen);\n w.stringField(4, n.platform ?? \"\");\n w.stringField(5, n.version ?? \"\");\n w.stringField(6, n.ip ?? \"\");\n w.bytesField(7, n.detail ?? EMPTY_BYTES);\n // 字段号必须与 `Status.msg_kind` 一致(9),否则错配无法被识别\n w.uint64Field(9, MSG_KIND_NODE_STATE);\n return w.finish();\n}\n\nexport function decodeNodeState(bytes: Uint8Array): NodeState {\n const r = new Reader(bytes);\n const n: NodeState = { nodeId: \"\", online: false, lastSeen: 0 };\n while (!r.eof) {\n const { field, wire } = r.tag();\n switch (field) {\n case 1:\n if (wire !== WIRE_LEN) r.skip(wire);\n else n.nodeId = r.string();\n break;\n case 2:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else n.online = r.varint() !== 0n;\n break;\n case 3:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else n.lastSeen = r.u64();\n break;\n case 4:\n if (wire !== WIRE_LEN) r.skip(wire);\n else n.platform = r.string();\n break;\n case 5:\n if (wire !== WIRE_LEN) r.skip(wire);\n else n.version = r.string();\n break;\n case 6:\n if (wire !== WIRE_LEN) r.skip(wire);\n else n.ip = r.string();\n break;\n case 7:\n if (wire !== WIRE_LEN) r.skip(wire);\n else n.detail = r.lenPrefixed();\n break;\n case 9: {\n if (wire !== WIRE_VARINT) r.skip(wire);\n else checkMsgKind(\"NodeState\", Number(r.varint()), MSG_KIND_NODE_STATE);\n break;\n }\n default:\n r.skip(wire);\n }\n }\n return n;\n}\n\n/**\n * 校验 `STATUS` 帧的身份标记。\n *\n * `0`(旧实现未填)放行——否则新网关与新客户端会让旧版本直接不可用。\n * 非 0 且不匹配说明**用错了解码器**(把 NodeState 当 Status 解),\n * 这种错用此前会静默读到错值,这里改成显式报错。\n */\nfunction checkMsgKind(label: string, actual: number, expected: number): void {\n if (actual !== 0 && actual !== expected) {\n throw new WireError(\n `${label} 帧的身份标记不符(msg_kind=${actual},期望 ${expected}):很可能用错了解码器`,\n );\n }\n}\n\nexport function encodeSnapshot(snap: Snapshot): Uint8Array {\n const w = new Writer(128);\n for (const node of snap.nodes) {\n w.tag(1, WIRE_LEN);\n w.lenPrefixed(encodeNodeState(node));\n }\n w.uint64Field(2, snap.seq);\n return w.finish();\n}\n\nexport function decodeSnapshot(bytes: Uint8Array): Snapshot {\n const r = new Reader(bytes);\n const snap: Snapshot = { nodes: [], seq: 0 };\n while (!r.eof) {\n const { field, wire } = r.tag();\n switch (field) {\n case 1:\n if (wire !== WIRE_LEN) r.skip(wire);\n else snap.nodes.push(decodeNodeState(r.lenPrefixed()));\n break;\n case 2:\n if (wire !== WIRE_VARINT) r.skip(wire);\n else snap.seq = r.u64();\n break;\n default:\n r.skip(wire);\n }\n }\n return snap;\n}\n\nfunction encodeNodeIds(ids: string[]): Uint8Array {\n const w = new Writer(64);\n for (const id of ids) w.stringField(1, id);\n return w.finish();\n}\n\nfunction decodeNodeIds(bytes: Uint8Array): string[] {\n const r = new Reader(bytes);\n const ids: string[] = [];\n while (!r.eof) {\n const { field, wire } = r.tag();\n if (field === 1 && wire === WIRE_LEN) ids.push(r.string());\n else r.skip(wire);\n }\n return ids;\n}\n\n/**\n * 订阅帧:节点列表 + 是否要内容增量。\n *\n * `noDetail` 用反向布尔的 `.proto` 字段(`Subscribe.no_detail`):默认 false\n * 表示「要细节」,与本字段出现前的行为一致。只有双方(控制台/网关)都支持时\n * 才会真正省下带宽;旧网关忽略未知字段,退化为全量推送。\n */\nexport function encodeSubscribe(ids: string[], noDetail = false): Uint8Array {\n if (!noDetail) return encodeNodeIds(ids);\n const w = new Writer(64);\n for (const id of ids) w.stringField(1, id);\n w.boolField(2, true);\n return w.finish();\n}\n\n/** 只取节点列表(保持既有调用方与测试的返回类型不变) */\nexport const decodeSubscribe = decodeNodeIds;\n\n/** 连 `no_detail` 一起解出(网关侧 / 测试需要判定订阅意图时用) */\nexport function decodeSubscribeDetail(bytes: Uint8Array): {\n nodeIds: string[];\n noDetail: boolean;\n} {\n const r = new Reader(bytes);\n const nodeIds: string[] = [];\n let noDetail = false;\n while (!r.eof) {\n const { field, wire } = r.tag();\n if (field === 1 && wire === WIRE_LEN) nodeIds.push(r.string());\n else if (field === 2 && wire === WIRE_VARINT) noDetail = r.varint() !== 0n;\n else r.skip(wire);\n }\n return { nodeIds, noDetail };\n}\nexport const encodeUnsubscribe = encodeNodeIds;\nexport const decodeUnsubscribe = decodeNodeIds;\n\nexport function encodeResume(since: number, nodeIds: string[] = []): Uint8Array {\n const w = new Writer(16 + nodeIds.length * 16);\n w.uint64Field(1, since);\n for (const id of nodeIds) w.stringField(2, id);\n return w.finish();\n}\n\nexport function decodeResume(bytes: Uint8Array): { since: number; nodeIds: string[] } {\n const r = new Reader(bytes);\n let since = 0;\n const nodeIds: string[] = [];\n while (!r.eof) {\n const { field, wire } = r.tag();\n if (field === 1 && wire === WIRE_VARINT) since = r.u64();\n else if (field === 2 && wire === WIRE_LEN) nodeIds.push(r.string());\n else r.skip(wire);\n }\n return { since, nodeIds };\n}"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,102 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zqagent/shared",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.3.8",
|
|
4
|
+
"description": "core 与 hub 共用的基础模块:节点管理协议、凭据加密、审批视图、通知渠道",
|
|
5
|
+
"author": "成都志前科技有限公司",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/zqagent/zqagent.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://agent.cd-zq.com/",
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18.17"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"ai-agent",
|
|
17
|
+
"wire-protocol",
|
|
18
|
+
"credentials",
|
|
19
|
+
"approval-view"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"require": "./dist/index.cjs"
|
|
27
|
+
},
|
|
28
|
+
"./fleet": {
|
|
29
|
+
"types": "./dist/fleet.d.ts",
|
|
30
|
+
"import": "./dist/fleet.js",
|
|
31
|
+
"require": "./dist/fleet.cjs"
|
|
32
|
+
},
|
|
33
|
+
"./secrets": {
|
|
34
|
+
"types": "./dist/secrets.d.ts",
|
|
35
|
+
"import": "./dist/secrets.js",
|
|
36
|
+
"require": "./dist/secrets.cjs"
|
|
37
|
+
},
|
|
38
|
+
"./approval-view": {
|
|
39
|
+
"types": "./dist/approval-view.d.ts",
|
|
40
|
+
"import": "./dist/approval-view.js",
|
|
41
|
+
"require": "./dist/approval-view.cjs"
|
|
42
|
+
},
|
|
43
|
+
"./llm-presets": {
|
|
44
|
+
"types": "./dist/llm-presets.d.ts",
|
|
45
|
+
"import": "./dist/llm-presets.js",
|
|
46
|
+
"require": "./dist/llm-presets.cjs"
|
|
47
|
+
},
|
|
48
|
+
"./model-capabilities": {
|
|
49
|
+
"types": "./dist/model-capabilities.d.ts",
|
|
50
|
+
"import": "./dist/model-capabilities.js",
|
|
51
|
+
"require": "./dist/model-capabilities.cjs"
|
|
52
|
+
},
|
|
53
|
+
"./media": {
|
|
54
|
+
"types": "./dist/media.d.ts",
|
|
55
|
+
"import": "./dist/media.js",
|
|
56
|
+
"require": "./dist/media.cjs"
|
|
57
|
+
},
|
|
58
|
+
"./hub-api": {
|
|
59
|
+
"types": "./dist/hub-api.d.ts",
|
|
60
|
+
"import": "./dist/hub-api.js",
|
|
61
|
+
"require": "./dist/hub-api.cjs"
|
|
62
|
+
},
|
|
63
|
+
"./notify": {
|
|
64
|
+
"types": "./dist/notify/index.d.ts",
|
|
65
|
+
"import": "./dist/notify/index.js",
|
|
66
|
+
"require": "./dist/notify/index.cjs"
|
|
67
|
+
},
|
|
68
|
+
"./wire": {
|
|
69
|
+
"types": "./dist/wire/index.d.ts",
|
|
70
|
+
"import": "./dist/wire/index.js",
|
|
71
|
+
"require": "./dist/wire/index.cjs"
|
|
72
|
+
},
|
|
73
|
+
"./wire/node": {
|
|
74
|
+
"types": "./dist/wire/node.d.ts",
|
|
75
|
+
"import": "./dist/wire/node.js",
|
|
76
|
+
"require": "./dist/wire/node.cjs"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"files": [
|
|
80
|
+
"dist/",
|
|
81
|
+
"README.md",
|
|
82
|
+
"LICENSE"
|
|
83
|
+
],
|
|
84
|
+
"dependencies": {
|
|
85
|
+
"nodemailer": "^10.0.16"
|
|
86
|
+
},
|
|
87
|
+
"devDependencies": {
|
|
88
|
+
"@types/node": "^22.20.1",
|
|
89
|
+
"@types/nodemailer": "^8.0.2",
|
|
90
|
+
"tsup": "^8.5.1",
|
|
91
|
+
"typescript": "^5.6.0",
|
|
92
|
+
"vitest": "^4.1.10",
|
|
93
|
+
"@vitest/coverage-v8": "^4.1.10"
|
|
94
|
+
},
|
|
95
|
+
"scripts": {
|
|
96
|
+
"build": "tsup",
|
|
97
|
+
"dev": "tsup --watch",
|
|
98
|
+
"typecheck": "tsc --noEmit",
|
|
99
|
+
"test": "vitest run",
|
|
100
|
+
"test:coverage": "vitest run --coverage"
|
|
101
|
+
}
|
|
6
102
|
}
|