shennian 0.2.88 → 0.2.90

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.
Files changed (143) hide show
  1. package/dist/assets/wechat-channel/macos/manifest.json +22 -0
  2. package/dist/assets/wechat-channel/macos/shennian-wechat-channel-helper +0 -0
  3. package/dist/bin/shennian.js +1 -1
  4. package/dist/publish-build-manifest.json +548 -0
  5. package/dist/scripts/wechat-rpa-confirmation.mjs +5 -97
  6. package/dist/src/agent-env.js +4 -105
  7. package/dist/src/agents/adapter.d.ts +6 -0
  8. package/dist/src/agents/adapter.js +1 -19
  9. package/dist/src/agents/claude.js +8 -305
  10. package/dist/src/agents/codex-control.d.ts +35 -0
  11. package/dist/src/agents/codex-control.js +2 -0
  12. package/dist/src/agents/codex-utils.js +7 -200
  13. package/dist/src/agents/codex.d.ts +8 -0
  14. package/dist/src/agents/codex.js +15 -863
  15. package/dist/src/agents/command-spec.js +2 -413
  16. package/dist/src/agents/config-status.js +1 -226
  17. package/dist/src/agents/cursor.js +1 -249
  18. package/dist/src/agents/custom.js +4 -271
  19. package/dist/src/agents/detect.js +1 -56
  20. package/dist/src/agents/external-channel-instructions.js +10 -94
  21. package/dist/src/agents/gemini.js +1 -173
  22. package/dist/src/agents/manager.js +13 -157
  23. package/dist/src/agents/model-registry/cache.js +1 -37
  24. package/dist/src/agents/model-registry/discovery.js +2 -187
  25. package/dist/src/agents/model-registry/parsers.js +4 -447
  26. package/dist/src/agents/model-registry/runner.js +1 -30
  27. package/dist/src/agents/model-registry/service.js +1 -78
  28. package/dist/src/agents/model-registry/types.js +1 -8
  29. package/dist/src/agents/model-registry.js +1 -18
  30. package/dist/src/agents/openclaw.js +2 -275
  31. package/dist/src/agents/opencode.js +1 -231
  32. package/dist/src/agents/pi-context.js +12 -217
  33. package/dist/src/agents/pi.js +14 -723
  34. package/dist/src/agents/platform-instructions.js +9 -54
  35. package/dist/src/channels/base.d.ts +4 -1
  36. package/dist/src/channels/base.js +1 -3
  37. package/dist/src/channels/registry.js +1 -30
  38. package/dist/src/channels/reply-split.js +10 -89
  39. package/dist/src/channels/runtime.d.ts +1 -0
  40. package/dist/src/channels/runtime.js +5 -533
  41. package/dist/src/channels/secret-registry.d.ts +1 -0
  42. package/dist/src/channels/secret-registry.js +1 -46
  43. package/dist/src/channels/websocket.js +8 -378
  44. package/dist/src/channels/wechat-channel/anchor.d.ts +10 -0
  45. package/dist/src/channels/wechat-channel/anchor.js +1 -0
  46. package/dist/src/channels/wechat-channel/client.d.ts +74 -0
  47. package/dist/src/channels/wechat-channel/client.js +1 -0
  48. package/dist/src/channels/wechat-channel/cooldown.d.ts +15 -0
  49. package/dist/src/channels/wechat-channel/cooldown.js +1 -0
  50. package/dist/src/channels/wechat-channel/fingerprint.d.ts +28 -0
  51. package/dist/src/channels/wechat-channel/fingerprint.js +1 -0
  52. package/dist/src/channels/wechat-channel/helper-assets.d.ts +37 -0
  53. package/dist/src/channels/wechat-channel/helper-assets.js +1 -0
  54. package/dist/src/channels/wechat-channel/helper-client.d.ts +25 -0
  55. package/dist/src/channels/wechat-channel/helper-client.js +3 -0
  56. package/dist/src/channels/wechat-channel/helper-protocol.d.ts +84 -0
  57. package/dist/src/channels/wechat-channel/helper-protocol.js +1 -0
  58. package/dist/src/channels/wechat-channel/index.d.ts +17 -0
  59. package/dist/src/channels/wechat-channel/index.js +1 -0
  60. package/dist/src/channels/wechat-channel/ledger.d.ts +33 -0
  61. package/dist/src/channels/wechat-channel/ledger.js +1 -0
  62. package/dist/src/channels/wechat-channel/media-resolver.d.ts +32 -0
  63. package/dist/src/channels/wechat-channel/media-resolver.js +1 -0
  64. package/dist/src/channels/wechat-channel/message-key.d.ts +19 -0
  65. package/dist/src/channels/wechat-channel/message-key.js +1 -0
  66. package/dist/src/channels/wechat-channel/observer.d.ts +64 -0
  67. package/dist/src/channels/wechat-channel/observer.js +1 -0
  68. package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +69 -0
  69. package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -0
  70. package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
  71. package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
  72. package/dist/src/channels/wechat-channel/preflight.d.ts +37 -0
  73. package/dist/src/channels/wechat-channel/preflight.js +1 -0
  74. package/dist/src/channels/wechat-channel/runner.d.ts +34 -0
  75. package/dist/src/channels/wechat-channel/runner.js +1 -0
  76. package/dist/src/channels/wechat-channel/runtime.d.ts +45 -0
  77. package/dist/src/channels/wechat-channel/runtime.js +1 -0
  78. package/dist/src/channels/wechat-channel/scheduler.d.ts +35 -0
  79. package/dist/src/channels/wechat-channel/scheduler.js +1 -0
  80. package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
  81. package/dist/src/channels/wechat-rpa/macos.js +6 -48
  82. package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
  83. package/dist/src/channels/wechat-rpa.d.ts +21 -0
  84. package/dist/src/channels/wechat-rpa.js +6 -1022
  85. package/dist/src/channels/wecom.js +4 -357
  86. package/dist/src/commands/agent.js +6 -131
  87. package/dist/src/commands/daemon-windows.js +8 -48
  88. package/dist/src/commands/daemon.js +19 -1013
  89. package/dist/src/commands/external-attachments.js +1 -51
  90. package/dist/src/commands/external.js +1 -137
  91. package/dist/src/commands/manager.js +2 -389
  92. package/dist/src/commands/pair-qr.js +1 -6
  93. package/dist/src/commands/pair.js +9 -287
  94. package/dist/src/commands/tools.js +1 -34
  95. package/dist/src/commands/upgrade.js +1 -198
  96. package/dist/src/config/index.js +1 -35
  97. package/dist/src/daemon-log.js +6 -58
  98. package/dist/src/env-path.js +1 -64
  99. package/dist/src/fs/boundary.js +1 -126
  100. package/dist/src/fs/handler.js +1 -130
  101. package/dist/src/fs/security.js +1 -32
  102. package/dist/src/fs/text-decoder.d.ts +10 -0
  103. package/dist/src/fs/text-decoder.js +1 -0
  104. package/dist/src/index.js +2 -404
  105. package/dist/src/log-reporter.js +1 -16
  106. package/dist/src/manager/prompt.js +29 -34
  107. package/dist/src/manager/registry.js +2 -269
  108. package/dist/src/manager/runtime.js +19 -1003
  109. package/dist/src/native-fusion/config.js +1 -5
  110. package/dist/src/native-fusion/opencode-parser.js +3 -123
  111. package/dist/src/native-fusion/parser-common.js +8 -264
  112. package/dist/src/native-fusion/parsers.js +8 -729
  113. package/dist/src/native-fusion/service.d.ts +10 -0
  114. package/dist/src/native-fusion/service.js +2 -198
  115. package/dist/src/native-fusion/state.js +1 -22
  116. package/dist/src/native-fusion/types.js +1 -1
  117. package/dist/src/region.js +1 -88
  118. package/dist/src/relay/client.js +1 -343
  119. package/dist/src/session/archive-zip.js +1 -220
  120. package/dist/src/session/handlers/agent-config.js +1 -150
  121. package/dist/src/session/handlers/agents.js +1 -55
  122. package/dist/src/session/handlers/chat.js +2 -733
  123. package/dist/src/session/handlers/control.js +1 -55
  124. package/dist/src/session/handlers/fs.js +1 -747
  125. package/dist/src/session/handlers/session-refresh.js +1 -35
  126. package/dist/src/session/handlers/skills.js +1 -121
  127. package/dist/src/session/handlers/title.js +1 -60
  128. package/dist/src/session/handlers/tool-detail.d.ts +3 -0
  129. package/dist/src/session/handlers/tool-detail.js +1 -0
  130. package/dist/src/session/manager.d.ts +3 -0
  131. package/dist/src/session/manager.js +1 -261
  132. package/dist/src/session/projection.js +1 -54
  133. package/dist/src/session/queue.js +4 -317
  134. package/dist/src/session/remote-attachments.js +1 -72
  135. package/dist/src/session/store.js +3 -109
  136. package/dist/src/session/types.d.ts +4 -0
  137. package/dist/src/session/types.js +1 -4
  138. package/dist/src/skills/registry.js +15 -148
  139. package/dist/src/skills/setup.js +1 -101
  140. package/dist/src/tools/markdown-to-pdf.js +10 -346
  141. package/dist/src/upgrade/engine.js +3 -347
  142. package/package.json +3 -2
  143. package/dist/scripts/wechat-rpa-download-candidates.mjs +0 -105
@@ -1,343 +1 @@
1
- // @arch docs/architecture/cli/daemon.md#关键数据流
2
- // @test src/__tests__/e2e-relay.ts
3
- import WebSocket from 'ws';
4
- import { generateTraceId } from '../log-reporter.js';
5
- const HEARTBEAT_TIMEOUT = 90_000;
6
- const MAX_RECONNECT_DELAY = 30_000;
7
- const BASE_RECONNECT_DELAY = 1_000;
8
- const JITTER_FACTOR = 0.2;
9
- const PING_INTERVAL = 20_000;
10
- const PONG_TIMEOUT = 10_000;
11
- const CONNECT_TIMEOUT = 15_000;
12
- const MAX_SEND_BUFFER = 1000;
13
- export class CliRelayClient {
14
- options;
15
- ws = null;
16
- state = 'disconnected';
17
- reconnectAttempts = 0;
18
- heartbeatTimer = null;
19
- reconnectTimer = null;
20
- connectTimer = null;
21
- pingTimer = null;
22
- pongTimer = null;
23
- disposed = false;
24
- closeTrigger;
25
- lastSocketError;
26
- /** Buffered agent events awaiting server ack, keyed by event id */
27
- sendBuffer = new Map();
28
- pendingAcks = new Map();
29
- pendingRequests = new Map();
30
- constructor(options) {
31
- this.options = options;
32
- }
33
- connect() {
34
- if (this.disposed)
35
- return;
36
- // Always clean up stale connection before creating a new one
37
- if (this.ws) {
38
- this.cleanup(false);
39
- }
40
- this.state = 'connecting';
41
- const sep = this.options.serverUrl.includes('?') ? '&' : '?';
42
- const vParam = this.options.cliVersion ? `&v=${encodeURIComponent(this.options.cliVersion)}` : '';
43
- const agentsParam = this.options.agentList?.length
44
- ? `&agents=${encodeURIComponent(this.options.agentList.join(','))}`
45
- : '';
46
- const url = `${this.options.serverUrl}${sep}token=${encodeURIComponent(this.options.machineToken)}${vParam}${agentsParam}`;
47
- const ws = new WebSocket(url);
48
- this.ws = ws;
49
- // Guard against connections that never open (DNS hang, TCP timeout, etc.)
50
- this.connectTimer = setTimeout(() => {
51
- if (this.ws === ws && this.state === 'connecting') {
52
- this.closeTrigger = 'connect-timeout';
53
- ws.terminate();
54
- }
55
- }, CONNECT_TIMEOUT);
56
- ws.on('open', () => {
57
- if (this.ws !== ws)
58
- return;
59
- this.clearConnectTimer();
60
- this.state = 'connected';
61
- this.reconnectAttempts = 0;
62
- this.startHeartbeat();
63
- this.startPing(ws);
64
- this.flushSendBuffer();
65
- this.options.onConnected?.();
66
- });
67
- ws.on('pong', () => {
68
- if (this.ws !== ws)
69
- return;
70
- this.clearPongTimer();
71
- this.resetHeartbeat();
72
- });
73
- ws.on('message', (data) => {
74
- if (this.ws !== ws)
75
- return;
76
- let frame;
77
- try {
78
- frame = JSON.parse(data.toString());
79
- }
80
- catch {
81
- return;
82
- }
83
- this.handleFrame(frame);
84
- });
85
- ws.on('close', (code, reason) => {
86
- if (this.ws !== ws)
87
- return;
88
- const phase = this.state;
89
- const info = {
90
- code,
91
- reason: reason.toString(),
92
- error: this.lastSocketError,
93
- phase,
94
- trigger: this.closeTrigger ?? 'socket-close',
95
- reconnectAttempt: this.reconnectAttempts + 1,
96
- };
97
- this.cleanup(false);
98
- this.options.onDisconnected?.(info);
99
- this.scheduleReconnect();
100
- });
101
- ws.on('error', (error) => {
102
- this.lastSocketError = error.message;
103
- this.closeTrigger = 'socket-error';
104
- ws.close();
105
- });
106
- }
107
- disconnect() {
108
- this.disposed = true;
109
- this.cleanup(true);
110
- }
111
- sendRes(res) {
112
- if (this.state !== 'connected' || !this.ws)
113
- return;
114
- if (!res.traceId)
115
- res.traceId = generateTraceId();
116
- this.ws.send(JSON.stringify(res));
117
- }
118
- sendEvent(event) {
119
- if (this.state !== 'connected' || !this.ws)
120
- return;
121
- if (!event.traceId)
122
- event.traceId = generateTraceId();
123
- this.ws.send(JSON.stringify(event));
124
- }
125
- sendReq(req, timeoutMs = 60_000) {
126
- if (this.state !== 'connected' || !this.ws) {
127
- return Promise.reject(new Error('Relay is not connected'));
128
- }
129
- if (!req.traceId)
130
- req.traceId = generateTraceId();
131
- return new Promise((resolve, reject) => {
132
- const existing = this.pendingRequests.get(req.id);
133
- if (existing) {
134
- clearTimeout(existing.timer);
135
- existing.reject(new Error('Superseded by a newer relay request'));
136
- }
137
- const timer = setTimeout(() => {
138
- this.pendingRequests.delete(req.id);
139
- reject(new Error('Relay request timed out'));
140
- }, timeoutMs);
141
- this.pendingRequests.set(req.id, { resolve, reject, timer });
142
- this.ws?.send(JSON.stringify(req));
143
- });
144
- }
145
- sendBufferedEvent(event, timeoutMs = 120_000) {
146
- if (!event.traceId)
147
- event.traceId = generateTraceId();
148
- if (!event.id) {
149
- this.sendEvent(event);
150
- return Promise.resolve();
151
- }
152
- const eventId = event.id;
153
- return new Promise((resolve, reject) => {
154
- const existing = this.pendingAcks.get(eventId);
155
- if (existing) {
156
- clearTimeout(existing.timer);
157
- existing.reject(new Error('Superseded by a newer buffered event'));
158
- }
159
- const timer = setTimeout(() => {
160
- this.pendingAcks.delete(eventId);
161
- this.sendBuffer.delete(eventId);
162
- reject(new Error('Relay event acknowledgement timed out'));
163
- }, timeoutMs);
164
- this.pendingAcks.set(eventId, { resolve, reject, timer });
165
- this.bufferEvent(event);
166
- });
167
- }
168
- /**
169
- * Send an agent event with at-least-once delivery guarantee.
170
- * The event is buffered until the server acknowledges it.
171
- * On reconnect, all unacked events are resent in order.
172
- */
173
- sendAgentEvent(event) {
174
- void this.sendBufferedEvent(event).catch(() => { });
175
- }
176
- getState() {
177
- return this.state;
178
- }
179
- handleFrame(frame) {
180
- if (frame.type === 'event' && frame.event === 'tick') {
181
- this.resetHeartbeat();
182
- return;
183
- }
184
- if (frame.type === 'res' && frame.id) {
185
- // Server ack for a buffered agent event — remove from buffer
186
- this.sendBuffer.delete(frame.id);
187
- const pending = this.pendingAcks.get(frame.id);
188
- if (pending) {
189
- this.pendingAcks.delete(frame.id);
190
- clearTimeout(pending.timer);
191
- if (frame.ok)
192
- pending.resolve();
193
- else
194
- pending.reject(new Error(frame.error ?? 'Relay event failed'));
195
- }
196
- const pendingRequest = this.pendingRequests.get(frame.id);
197
- if (pendingRequest) {
198
- this.pendingRequests.delete(frame.id);
199
- clearTimeout(pendingRequest.timer);
200
- pendingRequest.resolve(frame);
201
- }
202
- return;
203
- }
204
- if (frame.type === 'req') {
205
- this.options.onReq?.(frame);
206
- }
207
- }
208
- /** Resend all buffered agent events after reconnect, in insertion order */
209
- flushSendBuffer() {
210
- if (this.sendBuffer.size === 0 || !this.ws)
211
- return;
212
- for (const event of this.sendBuffer.values()) {
213
- if (this.state !== 'connected' || !this.ws)
214
- break;
215
- this.ws.send(JSON.stringify(event));
216
- }
217
- }
218
- bufferEvent(event) {
219
- if (!event.id) {
220
- this.sendEvent(event);
221
- return;
222
- }
223
- this.sendBuffer.set(event.id, event);
224
- if (this.sendBuffer.size > MAX_SEND_BUFFER) {
225
- const oldest = this.sendBuffer.keys().next().value;
226
- if (oldest) {
227
- this.sendBuffer.delete(oldest);
228
- const pending = this.pendingAcks.get(oldest);
229
- if (pending) {
230
- this.pendingAcks.delete(oldest);
231
- clearTimeout(pending.timer);
232
- pending.reject(new Error('Relay send buffer overflow'));
233
- }
234
- }
235
- }
236
- if (this.state === 'connected' && this.ws) {
237
- this.ws.send(JSON.stringify(event));
238
- }
239
- }
240
- clearConnectTimer() {
241
- if (this.connectTimer !== null) {
242
- clearTimeout(this.connectTimer);
243
- this.connectTimer = null;
244
- }
245
- }
246
- startPing(ws) {
247
- this.clearPing();
248
- this.pingTimer = setInterval(() => {
249
- if (this.ws !== ws || this.state !== 'connected')
250
- return;
251
- // Set pong timeout before sending ping
252
- this.pongTimer = setTimeout(() => {
253
- // No pong received — connection is dead, force close
254
- this.closeTrigger = 'pong-timeout';
255
- ws.close();
256
- }, PONG_TIMEOUT);
257
- try {
258
- ws.ping();
259
- }
260
- catch {
261
- this.clearPongTimer();
262
- this.closeTrigger = 'socket-error';
263
- ws.close();
264
- }
265
- }, PING_INTERVAL);
266
- }
267
- clearPing() {
268
- if (this.pingTimer !== null) {
269
- clearInterval(this.pingTimer);
270
- this.pingTimer = null;
271
- }
272
- this.clearPongTimer();
273
- }
274
- clearPongTimer() {
275
- if (this.pongTimer !== null) {
276
- clearTimeout(this.pongTimer);
277
- this.pongTimer = null;
278
- }
279
- }
280
- startHeartbeat() {
281
- this.clearHeartbeat();
282
- this.heartbeatTimer = setTimeout(() => {
283
- this.closeTrigger = 'heartbeat-timeout';
284
- this.ws?.close();
285
- }, HEARTBEAT_TIMEOUT);
286
- }
287
- resetHeartbeat() {
288
- this.startHeartbeat();
289
- }
290
- clearHeartbeat() {
291
- if (this.heartbeatTimer !== null) {
292
- clearTimeout(this.heartbeatTimer);
293
- this.heartbeatTimer = null;
294
- }
295
- }
296
- scheduleReconnect() {
297
- if (this.disposed)
298
- return;
299
- const base = Math.min(BASE_RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts), MAX_RECONNECT_DELAY);
300
- const jitter = base * JITTER_FACTOR * (Math.random() * 2 - 1);
301
- const delay = Math.round(base + jitter);
302
- this.reconnectAttempts++;
303
- this.reconnectTimer = setTimeout(() => {
304
- this.reconnectTimer = null;
305
- this.connect();
306
- }, delay);
307
- }
308
- cleanup(rejectAll) {
309
- this.state = 'disconnected';
310
- this.closeTrigger = undefined;
311
- this.lastSocketError = undefined;
312
- this.clearConnectTimer();
313
- this.clearHeartbeat();
314
- this.clearPing();
315
- if (this.reconnectTimer !== null) {
316
- clearTimeout(this.reconnectTimer);
317
- this.reconnectTimer = null;
318
- }
319
- if (this.ws) {
320
- const ws = this.ws;
321
- this.ws = null;
322
- ws.removeAllListeners();
323
- try {
324
- ws.close();
325
- }
326
- catch {
327
- // already closed
328
- }
329
- }
330
- for (const [id, pending] of this.pendingRequests) {
331
- clearTimeout(pending.timer);
332
- pending.reject(new Error('Relay client disconnected'));
333
- this.pendingRequests.delete(id);
334
- }
335
- if (rejectAll) {
336
- for (const [id, pending] of this.pendingAcks) {
337
- clearTimeout(pending.timer);
338
- pending.reject(new Error('Relay client disconnected'));
339
- this.pendingAcks.delete(id);
340
- }
341
- }
342
- }
343
- }
1
+ import d from"ws";import{generateTraceId as c}from"../log-reporter.js";const a=9e4,u=3e4,g=1e3,f=.2,m=2e4,p=1e4,T=15e3,w=1e3;class k{options;ws=null;state="disconnected";reconnectAttempts=0;heartbeatTimer=null;reconnectTimer=null;connectTimer=null;pingTimer=null;pongTimer=null;disposed=!1;closeTrigger;lastSocketError;sendBuffer=new Map;pendingAcks=new Map;pendingRequests=new Map;constructor(e){this.options=e}connect(){if(this.disposed)return;this.ws&&this.cleanup(!1),this.state="connecting";const e=this.options.serverUrl.includes("?")?"&":"?",t=this.options.cliVersion?`&v=${encodeURIComponent(this.options.cliVersion)}`:"",s=this.options.agentList?.length?`&agents=${encodeURIComponent(this.options.agentList.join(","))}`:"",r=`${this.options.serverUrl}${e}token=${encodeURIComponent(this.options.machineToken)}${t}${s}`,i=new d(r);this.ws=i,this.connectTimer=setTimeout(()=>{this.ws===i&&this.state==="connecting"&&(this.closeTrigger="connect-timeout",i.terminate())},T),i.on("open",()=>{this.ws===i&&(this.clearConnectTimer(),this.state="connected",this.reconnectAttempts=0,this.startHeartbeat(),this.startPing(i),this.flushSendBuffer(),this.options.onConnected?.())}),i.on("pong",()=>{this.ws===i&&(this.clearPongTimer(),this.resetHeartbeat())}),i.on("message",n=>{if(this.ws!==i)return;let o;try{o=JSON.parse(n.toString())}catch{return}this.handleFrame(o)}),i.on("close",(n,o)=>{if(this.ws!==i)return;const h=this.state,l={code:n,reason:o.toString(),error:this.lastSocketError,phase:h,trigger:this.closeTrigger??"socket-close",reconnectAttempt:this.reconnectAttempts+1};this.cleanup(!1),this.options.onDisconnected?.(l),this.scheduleReconnect()}),i.on("error",n=>{this.lastSocketError=n.message,this.closeTrigger="socket-error",i.close()})}disconnect(){this.disposed=!0,this.cleanup(!0)}sendRes(e){this.state!=="connected"||!this.ws||(e.traceId||(e.traceId=c()),this.ws.send(JSON.stringify(e)))}sendEvent(e){this.state!=="connected"||!this.ws||(e.traceId||(e.traceId=c()),this.ws.send(JSON.stringify(e)))}sendReq(e,t=6e4){return this.state!=="connected"||!this.ws?Promise.reject(new Error("Relay is not connected")):(e.traceId||(e.traceId=c()),new Promise((s,r)=>{const i=this.pendingRequests.get(e.id);i&&(clearTimeout(i.timer),i.reject(new Error("Superseded by a newer relay request")));const n=setTimeout(()=>{this.pendingRequests.delete(e.id),r(new Error("Relay request timed out"))},t);this.pendingRequests.set(e.id,{resolve:s,reject:r,timer:n}),this.ws?.send(JSON.stringify(e))}))}sendBufferedEvent(e,t=12e4){if(e.traceId||(e.traceId=c()),!e.id)return this.sendEvent(e),Promise.resolve();const s=e.id;return new Promise((r,i)=>{const n=this.pendingAcks.get(s);n&&(clearTimeout(n.timer),n.reject(new Error("Superseded by a newer buffered event")));const o=setTimeout(()=>{this.pendingAcks.delete(s),this.sendBuffer.delete(s),i(new Error("Relay event acknowledgement timed out"))},t);this.pendingAcks.set(s,{resolve:r,reject:i,timer:o}),this.bufferEvent(e)})}sendAgentEvent(e){this.sendBufferedEvent(e).catch(()=>{})}getState(){return this.state}handleFrame(e){if(e.type==="event"&&e.event==="tick"){this.resetHeartbeat();return}if(e.type==="res"&&e.id){this.sendBuffer.delete(e.id);const t=this.pendingAcks.get(e.id);t&&(this.pendingAcks.delete(e.id),clearTimeout(t.timer),e.ok?t.resolve():t.reject(new Error(e.error??"Relay event failed")));const s=this.pendingRequests.get(e.id);s&&(this.pendingRequests.delete(e.id),clearTimeout(s.timer),s.resolve(e));return}e.type==="req"&&this.options.onReq?.(e)}flushSendBuffer(){if(!(this.sendBuffer.size===0||!this.ws))for(const e of this.sendBuffer.values()){if(this.state!=="connected"||!this.ws)break;this.ws.send(JSON.stringify(e))}}bufferEvent(e){if(!e.id){this.sendEvent(e);return}if(this.sendBuffer.set(e.id,e),this.sendBuffer.size>w){const t=this.sendBuffer.keys().next().value;if(t){this.sendBuffer.delete(t);const s=this.pendingAcks.get(t);s&&(this.pendingAcks.delete(t),clearTimeout(s.timer),s.reject(new Error("Relay send buffer overflow")))}}this.state==="connected"&&this.ws&&this.ws.send(JSON.stringify(e))}clearConnectTimer(){this.connectTimer!==null&&(clearTimeout(this.connectTimer),this.connectTimer=null)}startPing(e){this.clearPing(),this.pingTimer=setInterval(()=>{if(!(this.ws!==e||this.state!=="connected")){this.pongTimer=setTimeout(()=>{this.closeTrigger="pong-timeout",e.close()},p);try{e.ping()}catch{this.clearPongTimer(),this.closeTrigger="socket-error",e.close()}}},m)}clearPing(){this.pingTimer!==null&&(clearInterval(this.pingTimer),this.pingTimer=null),this.clearPongTimer()}clearPongTimer(){this.pongTimer!==null&&(clearTimeout(this.pongTimer),this.pongTimer=null)}startHeartbeat(){this.clearHeartbeat(),this.heartbeatTimer=setTimeout(()=>{this.closeTrigger="heartbeat-timeout",this.ws?.close()},a)}resetHeartbeat(){this.startHeartbeat()}clearHeartbeat(){this.heartbeatTimer!==null&&(clearTimeout(this.heartbeatTimer),this.heartbeatTimer=null)}scheduleReconnect(){if(this.disposed)return;const e=Math.min(g*Math.pow(2,this.reconnectAttempts),u),t=e*f*(Math.random()*2-1),s=Math.round(e+t);this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},s)}cleanup(e){if(this.state="disconnected",this.closeTrigger=void 0,this.lastSocketError=void 0,this.clearConnectTimer(),this.clearHeartbeat(),this.clearPing(),this.reconnectTimer!==null&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws){const t=this.ws;this.ws=null,t.removeAllListeners();try{t.close()}catch{}}for(const[t,s]of this.pendingRequests)clearTimeout(s.timer),s.reject(new Error("Relay client disconnected")),this.pendingRequests.delete(t);if(e)for(const[t,s]of this.pendingAcks)clearTimeout(s.timer),s.reject(new Error("Relay client disconnected")),this.pendingAcks.delete(t)}}export{k as CliRelayClient};
@@ -1,220 +1 @@
1
- // @arch docs/features/file-management-enhancements.md#文件夹打包下载
2
- // @test src/__tests__/archive-zip.test.ts
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
- const DEFAULT_MAX_FILES = 5000;
6
- const DEFAULT_MAX_TOTAL_SIZE = 1024 * 1024 * 1024;
7
- const ZIP_VERSION_NEEDED = 20;
8
- const ZIP_UTF8_FLAG = 0x0800;
9
- const ZIP_STORE_METHOD = 0;
10
- const CHUNK_SIZE = 1024 * 1024;
11
- const crcTable = new Uint32Array(256);
12
- for (let index = 0; index < 256; index += 1) {
13
- let value = index;
14
- for (let bit = 0; bit < 8; bit += 1) {
15
- value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
16
- }
17
- crcTable[index] = value >>> 0;
18
- }
19
- function updateCrc32(crc, buffer) {
20
- let next = crc;
21
- for (let index = 0; index < buffer.length; index += 1) {
22
- next = crcTable[(next ^ buffer[index]) & 0xff] ^ (next >>> 8);
23
- }
24
- return next >>> 0;
25
- }
26
- function dosDateTime(mtimeMs) {
27
- const date = new Date(mtimeMs || Date.now());
28
- const year = Math.max(1980, Math.min(2107, date.getFullYear()));
29
- return {
30
- date: ((year - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate(),
31
- time: (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2),
32
- };
33
- }
34
- function writeUInt32(buffer, value, offset) {
35
- buffer.writeUInt32LE(value >>> 0, offset);
36
- }
37
- function writeLocalHeader(fd, entry) {
38
- const name = Buffer.from(entry.name, 'utf8');
39
- const { date, time } = dosDateTime(entry.mtimeMs);
40
- const header = Buffer.alloc(30);
41
- writeUInt32(header, 0x04034b50, 0);
42
- header.writeUInt16LE(ZIP_VERSION_NEEDED, 4);
43
- header.writeUInt16LE(ZIP_UTF8_FLAG, 6);
44
- header.writeUInt16LE(ZIP_STORE_METHOD, 8);
45
- header.writeUInt16LE(time, 10);
46
- header.writeUInt16LE(date, 12);
47
- writeUInt32(header, entry.crc32, 14);
48
- writeUInt32(header, entry.size, 18);
49
- writeUInt32(header, entry.size, 22);
50
- header.writeUInt16LE(name.length, 26);
51
- header.writeUInt16LE(0, 28);
52
- fs.writeSync(fd, header);
53
- fs.writeSync(fd, name);
54
- }
55
- function writeCentralDirectoryHeader(fd, entry) {
56
- const name = Buffer.from(entry.name, 'utf8');
57
- const { date, time } = dosDateTime(entry.mtimeMs);
58
- const header = Buffer.alloc(46);
59
- writeUInt32(header, 0x02014b50, 0);
60
- header.writeUInt16LE(0x031e, 4);
61
- header.writeUInt16LE(ZIP_VERSION_NEEDED, 6);
62
- header.writeUInt16LE(ZIP_UTF8_FLAG, 8);
63
- header.writeUInt16LE(ZIP_STORE_METHOD, 10);
64
- header.writeUInt16LE(time, 12);
65
- header.writeUInt16LE(date, 14);
66
- writeUInt32(header, entry.crc32, 16);
67
- writeUInt32(header, entry.size, 20);
68
- writeUInt32(header, entry.size, 24);
69
- header.writeUInt16LE(name.length, 28);
70
- header.writeUInt16LE(0, 30);
71
- header.writeUInt16LE(0, 32);
72
- header.writeUInt16LE(0, 34);
73
- header.writeUInt16LE(0, 36);
74
- writeUInt32(header, entry.isDir ? 0x10 : 0, 38);
75
- writeUInt32(header, entry.offset, 42);
76
- fs.writeSync(fd, header);
77
- fs.writeSync(fd, name);
78
- }
79
- function writeEndOfCentralDirectory(fd, entryCount, centralSize, centralOffset) {
80
- const header = Buffer.alloc(22);
81
- writeUInt32(header, 0x06054b50, 0);
82
- header.writeUInt16LE(0, 4);
83
- header.writeUInt16LE(0, 6);
84
- header.writeUInt16LE(entryCount, 8);
85
- header.writeUInt16LE(entryCount, 10);
86
- writeUInt32(header, centralSize, 12);
87
- writeUInt32(header, centralOffset, 16);
88
- header.writeUInt16LE(0, 20);
89
- fs.writeSync(fd, header);
90
- }
91
- function normalizeZipName(relativePath, isDir) {
92
- const normalized = relativePath.split(path.sep).join('/');
93
- return isDir && !normalized.endsWith('/') ? `${normalized}/` : normalized;
94
- }
95
- function collectEntries(sourceDir, options) {
96
- const sourceStat = fs.statSync(sourceDir);
97
- const rootName = normalizeZipName(path.basename(sourceDir) || 'folder', true);
98
- const entries = [{
99
- name: rootName,
100
- sourcePath: null,
101
- crc32: 0,
102
- size: 0,
103
- isDir: true,
104
- mtimeMs: sourceStat.mtimeMs,
105
- }];
106
- let fileCount = 0;
107
- let totalSize = 0;
108
- const visit = (dirPath) => {
109
- const raw = fs.readdirSync(dirPath, { withFileTypes: true });
110
- for (const dirent of raw) {
111
- const fullPath = path.join(dirPath, dirent.name);
112
- const relative = path.relative(sourceDir, fullPath);
113
- if (!relative || relative.startsWith('..') || path.isAbsolute(relative))
114
- continue;
115
- const stat = fs.lstatSync(fullPath);
116
- if (stat.isSymbolicLink())
117
- continue;
118
- if (stat.isDirectory()) {
119
- entries.push({
120
- name: path.posix.join(rootName, normalizeZipName(relative, true)),
121
- sourcePath: null,
122
- crc32: 0,
123
- size: 0,
124
- isDir: true,
125
- mtimeMs: stat.mtimeMs,
126
- });
127
- visit(fullPath);
128
- continue;
129
- }
130
- if (!stat.isFile())
131
- continue;
132
- fileCount += 1;
133
- if (fileCount > options.maxFiles)
134
- throw new Error(`Too many files: ${fileCount}`);
135
- totalSize += stat.size;
136
- if (totalSize > options.maxTotalSize) {
137
- throw new Error(`Folder too large: ${totalSize} bytes`);
138
- }
139
- entries.push({
140
- name: path.posix.join(rootName, normalizeZipName(relative, false)),
141
- sourcePath: fullPath,
142
- crc32: computeFileCrc32(fullPath),
143
- size: stat.size,
144
- isDir: false,
145
- mtimeMs: stat.mtimeMs,
146
- });
147
- }
148
- };
149
- visit(sourceDir);
150
- return entries;
151
- }
152
- function computeFileCrc32(filePath) {
153
- const fd = fs.openSync(filePath, 'r');
154
- const buffer = Buffer.alloc(CHUNK_SIZE);
155
- let crc = 0xffffffff;
156
- try {
157
- while (true) {
158
- const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, null);
159
- if (bytesRead <= 0)
160
- break;
161
- crc = updateCrc32(crc, buffer.subarray(0, bytesRead));
162
- }
163
- }
164
- finally {
165
- fs.closeSync(fd);
166
- }
167
- return (crc ^ 0xffffffff) >>> 0;
168
- }
169
- function writeFileData(fd, filePath) {
170
- const sourceFd = fs.openSync(filePath, 'r');
171
- const buffer = Buffer.alloc(CHUNK_SIZE);
172
- try {
173
- while (true) {
174
- const bytesRead = fs.readSync(sourceFd, buffer, 0, buffer.length, null);
175
- if (bytesRead <= 0)
176
- break;
177
- fs.writeSync(fd, buffer.subarray(0, bytesRead));
178
- }
179
- }
180
- finally {
181
- fs.closeSync(sourceFd);
182
- }
183
- }
184
- export function createZipArchive(sourceDir, outputPath, options = {}) {
185
- const sourceStat = fs.statSync(sourceDir);
186
- if (!sourceStat.isDirectory())
187
- throw new Error('Not a directory');
188
- const limits = {
189
- maxFiles: options.maxFiles ?? DEFAULT_MAX_FILES,
190
- maxTotalSize: options.maxTotalSize ?? DEFAULT_MAX_TOTAL_SIZE,
191
- };
192
- const scanned = collectEntries(sourceDir, limits);
193
- const zipEntries = [];
194
- let fileCount = 0;
195
- let totalSize = 0;
196
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
197
- const fd = fs.openSync(outputPath, 'w');
198
- try {
199
- for (const entry of scanned) {
200
- const offset = fs.fstatSync(fd).size;
201
- const nextEntry = { ...entry, offset };
202
- writeLocalHeader(fd, nextEntry);
203
- if (nextEntry.sourcePath) {
204
- writeFileData(fd, nextEntry.sourcePath);
205
- fileCount += 1;
206
- totalSize += nextEntry.size;
207
- }
208
- zipEntries.push(nextEntry);
209
- }
210
- const centralOffset = fs.fstatSync(fd).size;
211
- for (const entry of zipEntries)
212
- writeCentralDirectoryHeader(fd, entry);
213
- const centralSize = fs.fstatSync(fd).size - centralOffset;
214
- writeEndOfCentralDirectory(fd, zipEntries.length, centralSize, centralOffset);
215
- }
216
- finally {
217
- fs.closeSync(fd);
218
- }
219
- return { outputPath, fileCount, totalSize };
220
- }
1
+ import c from"node:fs";import u from"node:path";const p=5e3,M=1024*1024*1024,L=20,U=2048,h=0,x=1024*1024,z=new Uint32Array(256);for(let i=0;i<256;i+=1){let t=i;for(let n=0;n<8;n+=1)t=t&1?3988292384^t>>>1:t>>>1;z[i]=t>>>0}function F(i,t){let n=i;for(let o=0;o<t.length;o+=1)n=z[(n^t[o])&255]^n>>>8;return n>>>0}function b(i){const t=new Date(i||Date.now());return{date:Math.max(1980,Math.min(2107,t.getFullYear()))-1980<<9|t.getMonth()+1<<5|t.getDate(),time:t.getHours()<<11|t.getMinutes()<<5|Math.floor(t.getSeconds()/2)}}function s(i,t,n){i.writeUInt32LE(t>>>0,n)}function D(i,t){const n=Buffer.from(t.name,"utf8"),{date:o,time:r}=b(t.mtimeMs),e=Buffer.alloc(30);s(e,67324752,0),e.writeUInt16LE(L,4),e.writeUInt16LE(U,6),e.writeUInt16LE(h,8),e.writeUInt16LE(r,10),e.writeUInt16LE(o,12),s(e,t.crc32,14),s(e,t.size,18),s(e,t.size,22),e.writeUInt16LE(n.length,26),e.writeUInt16LE(0,28),c.writeSync(i,e),c.writeSync(i,n)}function T(i,t){const n=Buffer.from(t.name,"utf8"),{date:o,time:r}=b(t.mtimeMs),e=Buffer.alloc(46);s(e,33639248,0),e.writeUInt16LE(798,4),e.writeUInt16LE(L,6),e.writeUInt16LE(U,8),e.writeUInt16LE(h,10),e.writeUInt16LE(r,12),e.writeUInt16LE(o,14),s(e,t.crc32,16),s(e,t.size,20),s(e,t.size,24),e.writeUInt16LE(n.length,28),e.writeUInt16LE(0,30),e.writeUInt16LE(0,32),e.writeUInt16LE(0,34),e.writeUInt16LE(0,36),s(e,t.isDir?16:0,38),s(e,t.offset,42),c.writeSync(i,e),c.writeSync(i,n)}function g(i,t,n,o){const r=Buffer.alloc(22);s(r,101010256,0),r.writeUInt16LE(0,4),r.writeUInt16LE(0,6),r.writeUInt16LE(t,8),r.writeUInt16LE(t,10),s(r,n,12),s(r,o,16),r.writeUInt16LE(0,20),c.writeSync(i,r)}function d(i,t){const n=i.split(u.sep).join("/");return t&&!n.endsWith("/")?`${n}/`:n}function _(i,t){const n=c.statSync(i),o=d(u.basename(i)||"folder",!0),r=[{name:o,sourcePath:null,crc32:0,size:0,isDir:!0,mtimeMs:n.mtimeMs}];let e=0,m=0;const E=S=>{const a=c.readdirSync(S,{withFileTypes:!0});for(const I of a){const w=u.join(S,I.name),f=u.relative(i,w);if(!f||f.startsWith("..")||u.isAbsolute(f))continue;const l=c.lstatSync(w);if(!l.isSymbolicLink()){if(l.isDirectory()){r.push({name:u.posix.join(o,d(f,!0)),sourcePath:null,crc32:0,size:0,isDir:!0,mtimeMs:l.mtimeMs}),E(w);continue}if(l.isFile()){if(e+=1,e>t.maxFiles)throw new Error(`Too many files: ${e}`);if(m+=l.size,m>t.maxTotalSize)throw new Error(`Folder too large: ${m} bytes`);r.push({name:u.posix.join(o,d(f,!1)),sourcePath:w,crc32:A(w),size:l.size,isDir:!1,mtimeMs:l.mtimeMs})}}}};return E(i),r}function A(i){const t=c.openSync(i,"r"),n=Buffer.alloc(x);let o=4294967295;try{for(;;){const r=c.readSync(t,n,0,n.length,null);if(r<=0)break;o=F(o,n.subarray(0,r))}}finally{c.closeSync(t)}return(o^4294967295)>>>0}function B(i,t){const n=c.openSync(t,"r"),o=Buffer.alloc(x);try{for(;;){const r=c.readSync(n,o,0,o.length,null);if(r<=0)break;c.writeSync(i,o.subarray(0,r))}}finally{c.closeSync(n)}}function C(i,t,n={}){if(!c.statSync(i).isDirectory())throw new Error("Not a directory");const r={maxFiles:n.maxFiles??p,maxTotalSize:n.maxTotalSize??M},e=_(i,r),m=[];let E=0,S=0;c.mkdirSync(u.dirname(t),{recursive:!0});const a=c.openSync(t,"w");try{for(const f of e){const l=c.fstatSync(a).size,y={...f,offset:l};D(a,y),y.sourcePath&&(B(a,y.sourcePath),E+=1,S+=y.size),m.push(y)}const I=c.fstatSync(a).size;for(const f of m)T(a,f);const w=c.fstatSync(a).size-I;g(a,m.length,w,I)}finally{c.closeSync(a)}return{outputPath:t,fileCount:E,totalSize:S}}export{C as createZipArchive};