@agentunion/fastaun 0.5.20 → 0.5.22

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 (128) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/_packed_docs/CHANGELOG.md +104 -0
  3. package/_packed_docs/INDEX.md +59 -34
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +59 -52
  5. package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/344/270/216/346/215/242owner/347/274/226/346/216/222/346/226/271/346/241/210.md +530 -0
  6. package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/345/256/214/346/225/264/350/256/276/350/256/241/344/270/216/345/256/236/346/226/275/350/256/241/345/210/222.md +685 -0
  7. package/_packed_docs/agent.md/working/agent.md-schema/344/270/216/347/255/276/345/217/221/351/252/214/347/255/276/346/265/201/347/250/213/346/276/204/346/270/205.md +349 -0
  8. package/_packed_docs/agent.md/working//346/234/254/345/234/260Docker/346/265/213/350/257/225Bug/346/270/205/345/215/225.md +40 -0
  9. package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +307 -67
  10. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +41 -15
  11. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1433 -1392
  12. package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
  13. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +30 -8
  14. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +2 -0
  15. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +33 -22
  16. package/_packed_docs/sdk/0.5.21-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +368 -0
  17. package/_packed_docs/sdk/0.5.21-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +350 -0
  18. package/_packed_docs/sdk/0.5.22-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +212 -0
  19. package/_packed_docs/sdk/0.5.22-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +199 -0
  20. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
  21. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
  22. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
  23. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +4 -1
  24. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +42 -39
  25. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +163 -14
  26. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +48 -47
  27. package/_packed_docs/sdk/09-group-rpc-manual.md +15 -2
  28. package/_packed_docs/sdk/09-message-rpc-manual.md +250 -160
  29. package/_packed_docs/sdk/09-storage-rpc-manual.md +64 -7
  30. package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +73 -0
  31. package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +90 -0
  32. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +33 -25
  33. package/_packed_docs/sdk/INDEX.md +67 -33
  34. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +74 -57
  35. package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +312 -300
  36. package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +322 -382
  37. package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -708
  38. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +28 -20
  39. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +190 -97
  40. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +158 -127
  41. package/dist/agent-md-schema.js +6 -4
  42. package/dist/agent-md-schema.js.map +1 -1
  43. package/dist/agent-md.d.ts +22 -2
  44. package/dist/agent-md.js +92 -68
  45. package/dist/agent-md.js.map +1 -1
  46. package/dist/aid-store.d.ts +13 -0
  47. package/dist/aid-store.js +50 -15
  48. package/dist/aid-store.js.map +1 -1
  49. package/dist/auth.d.ts +1 -0
  50. package/dist/auth.js +22 -2
  51. package/dist/auth.js.map +1 -1
  52. package/dist/cert-utils.d.ts +1 -0
  53. package/dist/cert-utils.js +13 -0
  54. package/dist/cert-utils.js.map +1 -1
  55. package/dist/client/delivery.d.ts +87 -1
  56. package/dist/client/delivery.js +1387 -139
  57. package/dist/client/delivery.js.map +1 -1
  58. package/dist/client/group-state.js +3 -0
  59. package/dist/client/group-state.js.map +1 -1
  60. package/dist/client/rpc-pipeline.d.ts +4 -1
  61. package/dist/client/rpc-pipeline.js +149 -37
  62. package/dist/client/rpc-pipeline.js.map +1 -1
  63. package/dist/client/v2-e2ee.d.ts +12 -2
  64. package/dist/client/v2-e2ee.js +831 -304
  65. package/dist/client/v2-e2ee.js.map +1 -1
  66. package/dist/client.d.ts +26 -2
  67. package/dist/client.js +412 -68
  68. package/dist/client.js.map +1 -1
  69. package/dist/config.d.ts +4 -0
  70. package/dist/config.js +1 -0
  71. package/dist/config.js.map +1 -1
  72. package/dist/events.d.ts +18 -1
  73. package/dist/events.js +148 -9
  74. package/dist/events.js.map +1 -1
  75. package/dist/facades.d.ts +43 -1
  76. package/dist/facades.js +363 -7
  77. package/dist/facades.js.map +1 -1
  78. package/dist/group-fs.d.ts +1 -0
  79. package/dist/group-fs.js +37 -19
  80. package/dist/group-fs.js.map +1 -1
  81. package/dist/index.d.ts +5 -3
  82. package/dist/index.js +1 -1
  83. package/dist/index.js.map +1 -1
  84. package/dist/keystore/aid-db.d.ts +6 -1
  85. package/dist/keystore/aid-db.js +184 -42
  86. package/dist/keystore/aid-db.js.map +1 -1
  87. package/dist/keystore/index.d.ts +14 -0
  88. package/dist/keystore/local-token-store.d.ts +9 -1
  89. package/dist/keystore/local-token-store.js +79 -14
  90. package/dist/keystore/local-token-store.js.map +1 -1
  91. package/dist/org-protocol.d.ts +46 -0
  92. package/dist/org-protocol.js +802 -12
  93. package/dist/org-protocol.js.map +1 -1
  94. package/dist/ownership-v21.d.ts +36 -5
  95. package/dist/ownership-v21.js +168 -52
  96. package/dist/ownership-v21.js.map +1 -1
  97. package/dist/ownership-workflow.d.ts +24 -2
  98. package/dist/ownership-workflow.js +682 -41
  99. package/dist/ownership-workflow.js.map +1 -1
  100. package/dist/secret-store/file-store.js +84 -0
  101. package/dist/secret-store/file-store.js.map +1 -1
  102. package/dist/seq-tracker.d.ts +1 -0
  103. package/dist/seq-tracker.js +2 -2
  104. package/dist/seq-tracker.js.map +1 -1
  105. package/dist/storage/lowlevel.d.ts +6 -0
  106. package/dist/storage/lowlevel.js +24 -7
  107. package/dist/storage/lowlevel.js.map +1 -1
  108. package/dist/storage/read-cache.d.ts +70 -0
  109. package/dist/storage/read-cache.js +625 -0
  110. package/dist/storage/read-cache.js.map +1 -0
  111. package/dist/storage/vfs.d.ts +2 -0
  112. package/dist/storage/vfs.js +50 -15
  113. package/dist/storage/vfs.js.map +1 -1
  114. package/dist/tools/cross-sdk-agent.d.ts +86 -3
  115. package/dist/tools/cross-sdk-agent.js +758 -46
  116. package/dist/tools/cross-sdk-agent.js.map +1 -1
  117. package/dist/transport.d.ts +3 -0
  118. package/dist/transport.js +104 -46
  119. package/dist/transport.js.map +1 -1
  120. package/dist/types.d.ts +1 -0
  121. package/dist/v2/e2ee/encrypt-group.js +1 -0
  122. package/dist/v2/e2ee/encrypt-group.js.map +1 -1
  123. package/dist/v2/e2ee/encrypt-p2p.js +1 -0
  124. package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
  125. package/dist/v2/e2ee/types.d.ts +1 -0
  126. package/dist/version.d.ts +1 -1
  127. package/dist/version.js +1 -1
  128. package/package.json +1 -1
@@ -5,6 +5,7 @@ import * as crypto from 'node:crypto';
5
5
  import * as fs from 'node:fs';
6
6
  import * as path from 'node:path';
7
7
  import { tmpdir } from 'node:os';
8
+ import { AsyncLocalStorage } from 'node:async_hooks';
8
9
  import { URL } from 'node:url';
9
10
  import { fileURLToPath } from 'node:url';
10
11
  import { AUNClient } from '../client.js';
@@ -14,6 +15,33 @@ import { publicKeyFingerprint } from '../cert-utils.js';
14
15
  import { VERSION } from '../version.js';
15
16
  import { canonicalJson, digest, buildRequestBinding, verifyMembershipBinding, ProtocolError } from '../org-protocol.js';
16
17
  import { ownershipTaskResult } from '../ownership-workflow.js';
18
+ import { TimeoutError } from '../errors.js';
19
+ const credentialProcessStartupId = crypto.randomUUID();
20
+ const cacheReadContext = new AsyncLocalStorage();
21
+ const cacheReadObservers = new WeakMap();
22
+ function acquireCacheReadObserver(target, key, wrap) {
23
+ let observers = cacheReadObservers.get(target);
24
+ if (!observers) {
25
+ observers = new Map();
26
+ cacheReadObservers.set(target, observers);
27
+ }
28
+ let observer = observers.get(key);
29
+ if (!observer) {
30
+ const original = target[key];
31
+ target[key] = wrap(original);
32
+ observer = { users: 0, restore: () => { target[key] = original; } };
33
+ observers.set(key, observer);
34
+ }
35
+ observer.users += 1;
36
+ return () => {
37
+ if (--observer.users === 0) {
38
+ observer.restore();
39
+ observers.delete(key);
40
+ if (!observers.size)
41
+ cacheReadObservers.delete(target);
42
+ }
43
+ };
44
+ }
17
45
  function envBool(name, fallback = false) {
18
46
  const raw = process.env[name];
19
47
  if (raw == null)
@@ -128,10 +156,32 @@ export class CrossSdkTsAgent {
128
156
  rawPushObservations = [];
129
157
  rawPushObservationId = '';
130
158
  rawPushRpcMethods = [];
159
+ eventLedger = [];
160
+ batchEventMode = false;
161
+ batchEvents = [];
162
+ singleEventCounts = { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
163
+ eventBoundClients = new WeakSet();
164
+ batchEventClient;
165
+ batchEventHandlers = [];
166
+ p2pSubscription = true;
167
+ p2pEventClient;
168
+ p2pEventHandler;
169
+ p2pDeliveryPaused = false;
170
+ p2pDeliveryBlocked = 0;
171
+ p2pDeliveryGate = null;
172
+ p2pDeliveryRetiredClients = new WeakSet();
173
+ rpcSpans = [];
174
+ observationGeneration = 0;
175
+ pullBarrier = null;
176
+ rpcFailures = [];
177
+ messageOperations = new Map();
131
178
  rpcObservedTransports = new WeakSet();
179
+ rpcObservedPipelines = new WeakSet();
180
+ rpcObservedPulls = new WeakMap();
132
181
  groupChangedEvents = [];
133
182
  groupInviteEvents = [];
134
183
  readStateEvents = [];
184
+ peersSnapshots = [];
135
185
  ownershipEvents = [];
136
186
  traces = new Map();
137
187
  sendResults = new Map();
@@ -139,6 +189,18 @@ export class CrossSdkTsAgent {
139
189
  constructor() {
140
190
  this.client = this.newClient();
141
191
  }
192
+ credentialStartup;
193
+ credentialProcessId = credentialProcessStartupId;
194
+ credentialProcessCheckpoint() {
195
+ const internal = this.client;
196
+ const metadata = internal._tokenStore._loadMetadataStrict(this.aid) ?? {};
197
+ return structuredClone({ pid: process.pid, startup_uuid: this.credentialProcessId, aid: this.aid,
198
+ cert_fingerprint: this.client.currentAid?.certFingerprint, store: this.aunPath,
199
+ records: metadata.credential_renewals_v1 ?? {}, revocations: metadata.credential_revocations_v1 ?? { version: 1, authorities: {} } });
200
+ }
201
+ captureCredentialStartup() {
202
+ this.credentialStartup ??= { ...this.credentialProcessCheckpoint(), phase: 'before_connect' };
203
+ }
142
204
  async start() {
143
205
  this.bindClientEvents();
144
206
  // Cross-SDK runner 需要先由显式 Tail 建立窗口证明;启动阶段不得遗留后台 Pull。
@@ -154,29 +216,39 @@ export class CrossSdkTsAgent {
154
216
  return client;
155
217
  }
156
218
  bindClientEvents() {
219
+ this.eventBoundClients ??= new WeakSet();
220
+ if (this.eventBoundClients.has(this.client)) {
221
+ this.setP2pSubscription(this.p2pSubscription ?? true);
222
+ return;
223
+ }
224
+ this.eventBoundClients.add(this.client);
157
225
  this.installTransportRpcObserver();
158
226
  this.client.on('_raw.message.received', (data) => this.storeRawPushObservation('_raw.message.received', 'p2p', 'legacy', data));
159
227
  this.client.on('_raw.peer.v2.message_received', (data) => this.storeRawPushObservation('_raw.peer.v2.message_received', 'p2p', 'v2', data));
160
228
  this.client.on('_raw.group.message_created', (data) => this.storeRawPushObservation('_raw.group.message_created', 'group', 'legacy', data));
161
229
  this.client.on('_raw.group.v2.message_created', (data) => this.storeRawPushObservation('_raw.group.v2.message_created', 'group', 'v2', data));
162
230
  this.client.on('_raw.group.changed', (data) => this.storeRawGroupChangedObservation(data));
163
- this.client.on('message.received', (msg) => {
164
- void this.storeInboxItem(this.normalizeMessage(msg, true));
165
- });
166
231
  this.client.on('message.undecryptable', (msg) => {
167
232
  void this.storeInboxItem(this.normalizeMessage(msg, false, 'undecryptable'));
168
233
  });
169
234
  this.client.on('group.message_created', (msg) => {
170
- void this.storeGroupInboxItem(this.normalizeGroupMessage(msg, true));
235
+ this.countSingleEvent('group.message_created');
236
+ void this.storeApplicationEvent('group.message_created', msg);
171
237
  });
172
238
  this.client.on('group.message_undecryptable', (msg) => {
173
239
  void this.storeGroupInboxItem(this.normalizeGroupMessage(msg, false, 'undecryptable'));
174
240
  });
175
- this.client.on('group.changed', (data) => this.storeGroupChangedPublicEvent(data));
241
+ this.client.on('group.changed', (data) => {
242
+ this.countSingleEvent('group.changed');
243
+ void this.storeApplicationEvent('group.changed', data);
244
+ });
176
245
  this.client.on('group.invite_received', (data) => this.storeGroupInviteEvent(data));
177
246
  for (const event of ['message.read_changed', 'group.read_changed', 'read_state_sync']) {
178
247
  this.client.on(event, (payload) => { this.readStateEvents.push({ event, payload: jsonSafe(payload) }); });
179
248
  }
249
+ this.client.on('message.peers_snapshot', (payload) => {
250
+ this.peersSnapshots.push({ event: 'message.peers_snapshot', payload: jsonSafe(payload) });
251
+ });
180
252
  for (const event of ['ownership.request', 'ownership.pending', 'ownership.progress', 'ownership.completed', 'ownership.rejected', 'ownership.stopped', 'ownership.error', 'ownership.action_required']) {
181
253
  this.client.on(event, (payload) => {
182
254
  this.ownershipEvents.push({ event, payload: jsonSafe(payload) });
@@ -189,15 +261,151 @@ export class CrossSdkTsAgent {
189
261
  this.recordTrace(textOf(payload.org_id), { stage: event, payload });
190
262
  });
191
263
  }
264
+ this.setP2pSubscription(this.p2pSubscription ?? true);
265
+ }
266
+ countSingleEvent(event) {
267
+ this.singleEventCounts ??= { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
268
+ this.singleEventCounts[event] = Number(this.singleEventCounts[event] ?? 0) + 1;
269
+ }
270
+ async storeApplicationEvent(event, value) {
271
+ if (event === 'message.received') {
272
+ this.storeEventLedger(event, value);
273
+ await this.storeInboxItem(this.normalizeMessage(value, true));
274
+ }
275
+ else if (event === 'group.message_created') {
276
+ this.storeEventLedger(event, value);
277
+ await this.storeGroupInboxItem(this.normalizeGroupMessage(value, true));
278
+ }
279
+ else if (event === 'group.changed')
280
+ this.storeGroupChangedPublicEvent(value);
281
+ }
282
+ setP2pSubscription(enabled) {
283
+ this.p2pSubscription = enabled;
284
+ if (this.p2pEventHandler && (this.p2pEventClient !== this.client || !enabled)) {
285
+ this.p2pEventClient?.off('message.received', this.p2pEventHandler);
286
+ this.p2pEventHandler = undefined;
287
+ }
288
+ this.p2pEventClient = this.client;
289
+ if (enabled && !this.p2pEventHandler) {
290
+ const client = this.client;
291
+ this.p2pEventHandler = async (msg) => {
292
+ if ((this.p2pDeliveryPaused || this.p2pDeliveryRetiredClients?.has(client)) && !await this.waitForP2pDelivery(client))
293
+ return;
294
+ this.countSingleEvent('message.received');
295
+ await this.storeApplicationEvent('message.received', msg);
296
+ };
297
+ this.client.on('message.received', this.p2pEventHandler);
298
+ }
299
+ this.setBatchEventMode(this.batchEventMode ?? false);
300
+ }
301
+ setP2pDeliveryPaused(paused) {
302
+ this.p2pDeliveryPaused = paused;
303
+ if (!paused) {
304
+ const gate = this.p2pDeliveryGate;
305
+ this.p2pDeliveryGate = null;
306
+ gate?.release();
307
+ }
308
+ }
309
+ async waitForP2pDelivery(client) {
310
+ if (this.p2pDeliveryRetiredClients?.has(client))
311
+ return false;
312
+ this.p2pDeliveryBlocked = (this.p2pDeliveryBlocked ?? 0) + 1;
313
+ try {
314
+ while (this.p2pDeliveryPaused && !this.p2pDeliveryRetiredClients?.has(client)) {
315
+ if (!this.p2pDeliveryGate) {
316
+ let release;
317
+ const promise = new Promise((resolve) => { release = resolve; });
318
+ this.p2pDeliveryGate = { promise, release };
319
+ }
320
+ await this.p2pDeliveryGate.promise;
321
+ }
322
+ return !this.p2pDeliveryRetiredClients?.has(client);
323
+ }
324
+ finally {
325
+ this.p2pDeliveryBlocked -= 1;
326
+ }
327
+ }
328
+ retireP2pDelivery(client) {
329
+ this.p2pDeliveryRetiredClients ??= new WeakSet();
330
+ this.p2pDeliveryRetiredClients.add(client);
331
+ const gate = this.p2pDeliveryGate;
332
+ this.p2pDeliveryGate = null;
333
+ gate?.release();
334
+ }
335
+ applicationDeliveryState() {
336
+ const dispatcher = this.client._dispatcher;
337
+ return { subscribed: this.p2pSubscription ?? true, paused: this.p2pDeliveryPaused ?? false, blocked: this.p2pDeliveryBlocked ?? 0,
338
+ capacity: dispatcher?._messageQueueCapacity?.() ?? 0, used: dispatcher?._queuedMessageCount ?? 0, waiting: dispatcher?._messageAdmissionWaiters ?? 0 };
339
+ }
340
+ setBatchEventMode(enabled) {
341
+ this.batchEventMode = enabled;
342
+ const events = enabled ? ['message.batch', 'group.message_batch', 'group.event_batch'].filter((event) => event !== 'message.batch' || this.p2pSubscription !== false) : [];
343
+ if (this.batchEventClient === this.client && this.batchEventHandlers?.length === events.length && this.batchEventHandlers.every((item, index) => item.event === events[index]))
344
+ return;
345
+ for (const { event, handler } of this.batchEventHandlers ?? [])
346
+ this.batchEventClient?.off(event, handler);
347
+ this.batchEventClient = this.client;
348
+ this.batchEventHandlers = [];
349
+ const client = this.client;
350
+ for (const event of events) {
351
+ const handler = async (value) => {
352
+ if (event === 'message.batch' && (this.p2pDeliveryPaused || this.p2pDeliveryRetiredClients?.has(client)) && !await this.waitForP2pDelivery(client))
353
+ return;
354
+ const data = jsonObjectOf(value) ?? {};
355
+ const items = JSON.parse(JSON.stringify(jsonSafe(Array.isArray(data.items) ? data.items : [])));
356
+ this.batchEvents ??= [];
357
+ this.batchEvents.push({ observation_id: this.rawPushObservationId, time_ms: Date.now(), event,
358
+ namespace: textOf(data.namespace), source: textOf(data.source), items });
359
+ for (const item of items)
360
+ await this.storeApplicationEvent(textOf(item.event), item.payload);
361
+ };
362
+ this.batchEventHandlers.push({ event, handler });
363
+ this.client.on(event, handler);
364
+ }
192
365
  }
193
366
  installTransportRpcObserver() {
367
+ const client = this.client;
368
+ const pipeline = client._rpcPipeline;
369
+ this.rpcObservedPipelines ??= new WeakSet();
370
+ this.rpcObservedPulls ??= new WeakMap();
371
+ if (pipeline?.bindActivePullCancellation && !this.rpcObservedPipelines.has(pipeline)) {
372
+ const originalBind = pipeline.bindActivePullCancellation;
373
+ this.rpcObservedPipelines.add(pipeline);
374
+ pipeline.bindActivePullCancellation = (method, params, request, gateKey = '') => {
375
+ const result = originalBind.call(pipeline, method, params, request, gateKey);
376
+ const observed = this.rpcObservedPulls.get(request);
377
+ if (!this.pullBarrier?.preserve || !observed || !['message.pull', 'message.v2.pull'].includes(method)
378
+ || !textOf(params.sender_aid) || typeof client._delivery?.peerNamespace !== 'function')
379
+ return result;
380
+ const key = gateKey || pipeline.pullGateKeyForCall(method, params);
381
+ const namespace = client._delivery?.peerNamespace(textOf(params.sender_aid));
382
+ const owners = [...pipeline.pullGateStates.values()].flatMap((state) => [...state.activeByNamespace.values()])
383
+ .filter((job) => job.key === key && job.namespace === namespace && job.running && !job.timedOut && !job.invalidated && job.cancel);
384
+ if (owners.length === 1)
385
+ observed.owner = { job: owners[0], cancel: owners[0].cancel, key, namespace: namespace };
386
+ return result;
387
+ };
388
+ }
194
389
  const transport = this.client._transport;
195
390
  transport?.setTraceMode('diag');
196
391
  if (!transport?.call || this.rpcObservedTransports.has(transport))
197
392
  return;
198
393
  const originalCall = transport.call;
394
+ const originalClose = transport.close;
199
395
  this.rpcObservedTransports.add(transport);
200
- transport.call = async (method, params, timeout, trace, background) => {
396
+ if (originalClose)
397
+ transport.close = async () => {
398
+ if (!this.pullBarrier?.preserve)
399
+ this.releasePullBarrier();
400
+ try {
401
+ await originalClose.call(transport);
402
+ }
403
+ finally {
404
+ if (!this.pullBarrier?.preserve)
405
+ this.releasePullBarrier();
406
+ }
407
+ };
408
+ transport.call = (method, params, timeout, trace, background) => {
201
409
  this.storeRawPushRpcMethod({ type: 'rpc', method, request_window: this.rawPushRequestWindow(params) });
202
410
  let requestParams = params;
203
411
  if (method === 'auth.connect' && !this.inlineRealtimePayloadV1 && jsonObjectOf(params)) {
@@ -209,9 +417,220 @@ export class CrossSdkTsAgent {
209
417
  }
210
418
  requestParams = request;
211
419
  }
212
- return await originalCall.call(transport, method, requestParams, timeout, trace, background);
420
+ const generation = this.observationGeneration;
421
+ const span = this.startRpcSpan(method, requestParams);
422
+ const controller = new AbortController();
423
+ let request;
424
+ const observed = {};
425
+ const pending = (async () => {
426
+ try {
427
+ request = originalCall.call(transport, method, requestParams, timeout, trace, background);
428
+ const result = await request;
429
+ if (span) {
430
+ this.finishRpcResponse(span, result);
431
+ if (generation === this.observationGeneration && (this.isObservedPull(method) || this.pullBarrier?.method === method)) {
432
+ await this.waitForPullBarrier(method, controller.signal, timeout ?? transport._timeout ?? 35_000, span, observed.owner);
433
+ }
434
+ if (generation === this.observationGeneration)
435
+ this.injectRPCFailure(method, span);
436
+ span.success = true;
437
+ }
438
+ return result;
439
+ }
440
+ catch (error) {
441
+ if (span) {
442
+ span.success = false;
443
+ span.error = error instanceof Error ? error.name : String(error);
444
+ }
445
+ throw error;
446
+ }
447
+ finally {
448
+ if (span)
449
+ span.end_ms = Date.now();
450
+ }
451
+ })();
452
+ pending.cancel = () => { controller.abort(); request?.cancel?.(); };
453
+ this.rpcObservedPulls.set(pending, observed);
454
+ return pending;
213
455
  };
214
456
  }
457
+ storeEventLedger(event, value) {
458
+ const data = jsonObjectOf(value) ?? {};
459
+ const internal = this.client;
460
+ const payload = JSON.parse(stableStringify(jsonSafe(data.payload ?? null)));
461
+ this.eventLedger.push({
462
+ observation_id: this.rawPushObservationId, event, event_index: this.eventLedger.length + 1, time_ms: Date.now(),
463
+ scope: event === 'message.received' ? 'p2p' : 'group', receiver_aid: this.aid,
464
+ device_id: textOf(internal._deviceId), slot_id: textOf(internal._slotId ?? this.slotId),
465
+ sender_aid: textOf(data.sender_aid ?? data.from_aid ?? data.from), group_id: textOf(data.group_id),
466
+ message_id: textOf(data.message_id ?? data.id), seq: Number(data.seq ?? data.message_seq ?? data.msg_seq ?? 0) || 0,
467
+ storage: textOf(data.storage), volatile_cursor: textOf(data.volatile_cursor),
468
+ payload, payload_sha256: sha256Json(payload), need_render: data.need_render !== false,
469
+ ...(Object.prototype.hasOwnProperty.call(data, 'mention_mode') ? { mention_mode: data.mention_mode } : {}),
470
+ });
471
+ }
472
+ isObservedPull(method) {
473
+ return ['message.pull', 'message.v2.pull', 'group.pull', 'group.v2.pull', 'group.pull_messages', 'group.pull_events', 'group.events'].includes(method);
474
+ }
475
+ startRpcSpan(method, value) {
476
+ if (!this.isObservedPull(method) && !['message.peers', 'message.ack', 'message.v2.ack', 'group.ack', 'group.v2.ack', 'group.ack_messages', 'group.ack_events'].includes(method))
477
+ return null;
478
+ const params = jsonObjectOf(value) ?? {};
479
+ const senderAid = textOf(params.sender_aid), groupId = textOf(params.group_id ?? params.group_aid);
480
+ const namespace = method === 'message.peers' ? '' : method.startsWith('group.')
481
+ ? groupId ? `${method === 'group.pull_events' || method === 'group.events' || method === 'group.ack_events' ? 'group_event' : 'group'}:${groupId}` : ''
482
+ : senderAid ? `p2p.peer.v1/${Buffer.from(senderAid, 'utf8').toString('base64url')}` : `p2p:${this.aid}`;
483
+ const span = {
484
+ observation_id: this.rawPushObservationId, span_id: crypto.randomUUID(), boundary: 'transport_call', method,
485
+ start_ms: Date.now(), response_ms: null, end_ms: null, success: null,
486
+ cursor_scope: textOf(params.cursor_scope) || (method === 'message.peers' || senderAid ? 'peer' : method.startsWith('group.') ? namespace.startsWith('group_event:') ? 'group_event' : 'group' : 'global'), sender_aid: senderAid,
487
+ group_id: groupId, namespace, request_window: this.observedRequestWindow(params),
488
+ response_message_count: 0, response_volatile_count: 0, response_event_count: 0,
489
+ response_item_keys: [], response_event_keys: [],
490
+ has_more: null, persistent_has_more: null, volatile_has_more: null, has_more_persistent: null, has_more_volatile: null, effective_ack_seq: null,
491
+ };
492
+ this.rpcSpans.push(span);
493
+ return span;
494
+ }
495
+ observedRequestWindow(params) {
496
+ const window = {};
497
+ for (const key of ['after_seq', 'before_seq', 'until_seq', 'after_message_seq', 'after_event_seq', 'up_to_seq', 'seq', 'ack_seq', 'msg_seq', 'event_seq', 'limit', 'max_pages']) {
498
+ if (typeof params[key] === 'number' && Number.isSafeInteger(params[key]))
499
+ window[key] = params[key];
500
+ }
501
+ for (const key of ['window_mode', 'cursor_scope', 'sender_aid', 'group_id', 'group_aid', 'device_id', 'slot_id', 'after_volatile_cursor', 'mode', 'after_sender_aid']) {
502
+ if (typeof params[key] === 'string')
503
+ window[key] = params[key];
504
+ }
505
+ for (const key of ['force', 'pending_only'])
506
+ if (typeof params[key] === 'boolean')
507
+ window[key] = params[key];
508
+ for (const key of ['volatile_cursors', 'volatile_ids']) {
509
+ if (Array.isArray(params[key]))
510
+ window[key] = params[key].filter((item) => typeof item === 'string');
511
+ }
512
+ return window;
513
+ }
514
+ finishRpcResponse(span, value) {
515
+ const response = jsonObjectOf(value) ?? {};
516
+ const messages = Array.isArray(response.messages) ? response.messages : [];
517
+ span.response_ms = Date.now();
518
+ span.response_message_count = messages.length;
519
+ span.response_volatile_count = messages.filter((item) => jsonObjectOf(item)?.storage === 'volatile').length;
520
+ span.response_event_count = Array.isArray(response.events) ? response.events.length : 0;
521
+ const sequenceOf = (raw, ...keys) => {
522
+ for (const key of keys) {
523
+ const value = raw[key];
524
+ if (typeof value === 'number' && Number.isFinite(value) && Number.isInteger(value))
525
+ return value;
526
+ }
527
+ return 0;
528
+ };
529
+ const itemKeys = new Map();
530
+ for (const key of ['messages', 'volatile_messages', 'volatile']) {
531
+ for (const item of Array.isArray(response[key]) ? response[key] : []) {
532
+ const raw = jsonObjectOf(item) ?? {}, cursor = textOf(raw.volatile_cursor);
533
+ const identity = { message_id: textOf(raw.message_id ?? raw.id),
534
+ seq: sequenceOf(raw, 'seq', 'message_seq', 'msg_seq'),
535
+ storage: typeof raw.storage === 'string' ? raw.storage : cursor ? 'volatile' : 'persistent', volatile_cursor: cursor };
536
+ itemKeys.set(JSON.stringify(identity), identity);
537
+ }
538
+ }
539
+ span.response_item_keys = [...itemKeys.values()];
540
+ span.response_event_keys = (Array.isArray(response.events) ? response.events : []).map((item) => {
541
+ const raw = jsonObjectOf(item) ?? {};
542
+ return { event_id: textOf(raw.event_id), event_seq: sequenceOf(raw, 'event_seq', 'seq') };
543
+ });
544
+ span.has_more = typeof response.has_more === 'boolean' ? response.has_more : null;
545
+ span.has_more_persistent = typeof response.persistent_has_more === 'boolean' ? response.persistent_has_more : null;
546
+ span.has_more_volatile = typeof response.volatile_has_more === 'boolean' ? response.volatile_has_more : null;
547
+ span.persistent_has_more = span.has_more_persistent;
548
+ span.volatile_has_more = span.has_more_volatile;
549
+ for (const key of ['peer_ack_seq', 'effective_ack_seq', 'server_ack_seq', 'ack_seq', 'up_to_seq', 'acked_seq']) {
550
+ if (typeof response[key] === 'number' && Number.isSafeInteger(response[key]))
551
+ span[key] = response[key];
552
+ }
553
+ const effective = response.peer_ack_seq ?? response.effective_ack_seq ?? response.ack_seq ?? response.up_to_seq ?? response.acked_seq;
554
+ if (typeof effective === 'number' && Number.isSafeInteger(effective))
555
+ span.effective_ack_seq = effective;
556
+ const volatileAck = jsonObjectOf(response.volatile_ack);
557
+ if (volatileAck) {
558
+ const ack = {};
559
+ for (const key of ['accepted', 'expired'])
560
+ if (typeof volatileAck[key] === 'number' && Number.isSafeInteger(volatileAck[key]))
561
+ ack[key] = volatileAck[key];
562
+ if (typeof volatileAck.storage === 'string')
563
+ ack.storage = volatileAck.storage;
564
+ span.volatile_ack = ack;
565
+ }
566
+ }
567
+ releasePullBarrier() {
568
+ const barrier = this.pullBarrier;
569
+ if (!barrier || barrier.released)
570
+ return;
571
+ barrier.released = true;
572
+ barrier.release();
573
+ }
574
+ injectRPCFailure(method, span) {
575
+ const failure = this.rpcFailures?.find((item) => !item.hit && item.method === method && item.sender_aid === span.sender_aid);
576
+ if (!failure)
577
+ return;
578
+ failure.hit = true;
579
+ span.injected_failure = failure.kind ?? 'error';
580
+ if (failure.kind === 'timeout')
581
+ throw new TimeoutError('cross-sdk 响应后一次超时');
582
+ throw new Error('cross-sdk 响应后一次失败');
583
+ }
584
+ resetObservationLedgers(pullBarrierCount = 0, scope = 'all', controls = {}) {
585
+ this.releasePullBarrier();
586
+ this.observationGeneration = (this.observationGeneration ?? 0) + 1;
587
+ this.eventLedger = [];
588
+ this.batchEvents = [];
589
+ this.singleEventCounts = { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 };
590
+ this.rpcSpans = [];
591
+ this.rpcFailures = Array.isArray(controls.rpc_failures) ? controls.rpc_failures.map((item) => ({ ...jsonObjectOf(item) })) : [];
592
+ this.pullBarrier = null;
593
+ if (Number.isSafeInteger(pullBarrierCount) && pullBarrierCount > 0) {
594
+ let release;
595
+ const promise = new Promise((resolve) => { release = resolve; });
596
+ this.pullBarrier = { target: pullBarrierCount, scope: scope === 'p2p' ? 'p2p' : 'all', admitted: 0, waiting: 0, released: false, promise, release,
597
+ sender: textOf(controls.pull_barrier_sender_aid), method: textOf(controls.pull_barrier_method), preserve: controls.preserve_pull_barrier === true };
598
+ }
599
+ }
600
+ async waitForPullBarrier(method, signal, timeout, span, owner) {
601
+ const barrier = this.pullBarrier;
602
+ if (!barrier || barrier.released || barrier.admitted >= barrier.target)
603
+ return;
604
+ if (barrier.scope === 'p2p' && method !== 'message.pull' && method !== 'message.v2.pull' && barrier.method !== method)
605
+ return;
606
+ if ((barrier.sender && barrier.sender !== span.sender_aid) || (barrier.method && barrier.method !== method))
607
+ return;
608
+ barrier.admitted += 1;
609
+ barrier.waiting += 1;
610
+ if (barrier.preserve) {
611
+ barrier.heldMethod = method;
612
+ barrier.owner = owner;
613
+ barrier.span = span;
614
+ }
615
+ let timer;
616
+ let abort;
617
+ try {
618
+ await Promise.race([barrier.promise, new Promise((_resolve, reject) => {
619
+ abort = () => reject(new Error('pull observation barrier cancelled'));
620
+ if (!barrier.preserve)
621
+ signal.addEventListener('abort', abort, { once: true });
622
+ if (signal.aborted && !barrier.preserve)
623
+ abort();
624
+ timer = setTimeout(() => reject(new Error('pull observation barrier timeout')), Math.max(1, timeout));
625
+ })]);
626
+ }
627
+ finally {
628
+ if (timer)
629
+ clearTimeout(timer);
630
+ signal.removeEventListener('abort', abort);
631
+ barrier.waiting -= 1;
632
+ }
633
+ }
215
634
  storeRawPushObservation(rawEvent, scope, defaultKind, value) {
216
635
  const data = jsonObjectOf(value) ?? {};
217
636
  const hasInline = Object.prototype.hasOwnProperty.call(data, 'inline_message');
@@ -299,6 +718,8 @@ export class CrossSdkTsAgent {
299
718
  };
300
719
  }
301
720
  async close() {
721
+ this.resetObservationLedgers();
722
+ this.retireP2pDelivery(this.client);
302
723
  try {
303
724
  await this.client.close();
304
725
  }
@@ -310,7 +731,7 @@ export class CrossSdkTsAgent {
310
731
  const internal = this.client;
311
732
  const store = new AIDStore({
312
733
  aunPath: this.aunPath,
313
- encryptionSeed: '',
734
+ encryptionSeed: process.env.AUN_TEST_ENCRYPTION_SEED || process.env.AUN_ENCRYPTION_SEED || '',
314
735
  slotId: this.slotId,
315
736
  debug: this.debug,
316
737
  });
@@ -343,6 +764,7 @@ export class CrossSdkTsAgent {
343
764
  throw new Error(`load identity failed: ${loaded.error.code}: ${loaded.error.message}`);
344
765
  }
345
766
  this.client.loadIdentity(loaded.data.aid);
767
+ this.captureCredentialStartup();
346
768
  const checked = await store.checkAgentMd(this.aid);
347
769
  const localEtag = checked.ok ? String(checked.data.local_etag ?? '').trim() : '';
348
770
  const remoteEtag = checked.ok ? String(checked.data.remote_etag ?? '').trim() : '';
@@ -489,6 +911,7 @@ export class CrossSdkTsAgent {
489
911
  decrypted,
490
912
  encrypted: Boolean(data.e2ee ?? data.encrypted),
491
913
  seq: Number(data.seq ?? data.message_seq ?? 0) || 0,
914
+ need_render: data.need_render !== false,
492
915
  ack_seq: Number(data.ack_seq ?? 0) || 0,
493
916
  storage: textOf(data.storage),
494
917
  volatile_cursor: textOf(data.volatile_cursor),
@@ -514,6 +937,7 @@ export class CrossSdkTsAgent {
514
937
  decrypted,
515
938
  encrypted: Boolean(data.e2ee ?? data.encrypted),
516
939
  seq: Number(data.seq ?? data.message_seq ?? data.msg_seq ?? 0) || 0,
940
+ need_render: data.need_render !== false,
517
941
  error_code: errorCode,
518
942
  raw_sha256: sha256Json(data),
519
943
  ...metadata,
@@ -543,6 +967,7 @@ export class CrossSdkTsAgent {
543
967
  }
544
968
  if (req.method === 'POST' && url.pathname === '/reset') {
545
969
  const body = await readJson(req);
970
+ this.resetObservationLedgers();
546
971
  const traceId = textOf(body.trace_id ?? '');
547
972
  if (traceId) {
548
973
  this.inbox = this.inbox.filter((item) => item.trace_id !== traceId);
@@ -553,6 +978,7 @@ export class CrossSdkTsAgent {
553
978
  else {
554
979
  this.inbox = [];
555
980
  this.groupInbox = [];
981
+ this.peersSnapshots = [];
556
982
  this.traces.clear();
557
983
  this.sendResults.clear();
558
984
  }
@@ -561,11 +987,38 @@ export class CrossSdkTsAgent {
561
987
  }
562
988
  if (req.method === 'POST' && url.pathname === '/test/raw-push/reset') {
563
989
  const body = await readJson(req);
990
+ if ((body.pull_barrier_count !== undefined && (typeof body.pull_barrier_count !== 'number' || !Number.isSafeInteger(body.pull_barrier_count) || body.pull_barrier_count < 0)) ||
991
+ (body.pull_barrier_scope !== undefined && !['all', 'p2p'].includes(body.pull_barrier_scope)) ||
992
+ (body.observation_id !== undefined && typeof body.observation_id !== 'string') ||
993
+ (body.release_pull_barrier !== undefined && typeof body.release_pull_barrier !== 'boolean') ||
994
+ ['p2p_subscription', 'p2p_delivery_paused', 'preserve_observations'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
995
+ sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid observation parameters' });
996
+ return;
997
+ }
998
+ if (typeof body.p2p_subscription === 'boolean')
999
+ this.setP2pSubscription(body.p2p_subscription);
1000
+ const p2pPullSpanIds = body.preserve_observations === true && body.p2p_subscription === false
1001
+ ? this.rpcSpans.filter((span) => span.method === 'message.pull' || span.method === 'message.v2.pull').map((span) => textOf(span.span_id)) : undefined;
1002
+ if (typeof body.p2p_delivery_paused === 'boolean')
1003
+ this.setP2pDeliveryPaused(body.p2p_delivery_paused);
1004
+ if (body.preserve_observations === true) {
1005
+ if (body.release_pull_barrier === true)
1006
+ this.releasePullBarrier();
1007
+ sendJson(res, 200, { ok: true, observation_id: this.rawPushObservationId, ...(p2pPullSpanIds !== undefined ? { p2p_pull_span_ids: p2pPullSpanIds } : {}) });
1008
+ return;
1009
+ }
1010
+ if (body.release_pull_barrier === true) {
1011
+ this.releasePullBarrier();
1012
+ sendJson(res, 200, { ok: true, observation_id: this.rawPushObservationId });
1013
+ return;
1014
+ }
1015
+ this.resetObservationLedgers(body.pull_barrier_count, textOf(body.pull_barrier_scope), body);
564
1016
  this.rawPushObservations = [];
565
1017
  this.rawPushRpcMethods = [];
566
1018
  this.groupChangedEvents = [];
567
1019
  this.groupInviteEvents = [];
568
1020
  this.readStateEvents = [];
1021
+ this.peersSnapshots = [];
569
1022
  this.rawPushObservationId = textOf(body.observation_id ?? '');
570
1023
  sendJson(res, 200, { ok: true, observation_id: this.rawPushObservationId });
571
1024
  return;
@@ -584,6 +1037,23 @@ export class CrossSdkTsAgent {
584
1037
  let rpcEntries = [...this.rawPushRpcMethods];
585
1038
  let groupChangedEvents = [...this.groupChangedEvents];
586
1039
  let groupInviteEvents = [...this.groupInviteEvents];
1040
+ let eventLedger = [...this.eventLedger ?? []];
1041
+ let rpcSpans = [...this.rpcSpans ?? []];
1042
+ let batchEvents = [...this.batchEvents ?? []];
1043
+ if (observationId)
1044
+ batchEvents = batchEvents.filter((item) => textOf(item.observation_id) === observationId);
1045
+ if (observationId)
1046
+ eventLedger = eventLedger.filter((item) => textOf(item.observation_id) === observationId);
1047
+ if (observationId)
1048
+ rpcSpans = rpcSpans.filter((item) => textOf(item.observation_id) === observationId);
1049
+ if (filters.scope)
1050
+ eventLedger = eventLedger.filter((item) => textOf(item.scope) === filters.scope);
1051
+ if (filters.scope)
1052
+ rpcSpans = rpcSpans.filter((item) => textOf(item.method).startsWith(filters.scope === 'p2p' ? 'message.' : 'group.'));
1053
+ if (filters.group_id)
1054
+ eventLedger = eventLedger.filter((item) => textOf(item.group_id) === filters.group_id);
1055
+ if (filters.group_id)
1056
+ rpcSpans = rpcSpans.filter((item) => textOf(item.group_id) === filters.group_id);
587
1057
  if (observationId)
588
1058
  rpcEntries = rpcEntries.filter((item) => textOf(item.observation_id) === observationId);
589
1059
  if (observationId)
@@ -600,11 +1070,20 @@ export class CrossSdkTsAgent {
600
1070
  for (const method of rpcMethods)
601
1071
  rpcMethodCounts[method] = Number(rpcMethodCounts[method] ?? 0) + 1;
602
1072
  sendJson(res, 200, {
603
- ok: true, items, count: items.length, rpc_methods: rpcMethods, rpc_method_counts: rpcMethodCounts,
1073
+ ok: true, time_ms: Date.now(), items, count: items.length, rpc_methods: rpcMethods, rpc_method_counts: rpcMethodCounts,
1074
+ observation_id: this.rawPushObservationId,
604
1075
  rpc_calls: rpcEntries,
605
1076
  group_changed_events: groupChangedEvents.slice(-limit),
606
1077
  group_invite_events: groupInviteEvents.slice(-limit),
607
1078
  read_state_events: this.readStateEvents.slice(-limit),
1079
+ peers_snapshots: this.peersSnapshots.slice(-limit),
1080
+ event_ledger: eventLedger,
1081
+ rpc_spans: rpcSpans,
1082
+ batch_event_mode: this.batchEventMode ?? false,
1083
+ batch_events: batchEvents,
1084
+ single_event_counts: this.singleEventCounts ?? { 'message.received': 0, 'group.message_created': 0, 'group.changed': 0 },
1085
+ application_delivery: this.applicationDeliveryState(),
1086
+ pull_barrier: { target: this.pullBarrier?.target ?? 0, waiting: this.pullBarrier?.waiting ?? 0, released: this.pullBarrier?.released ?? true, scope: this.pullBarrier?.scope ?? 'all' },
608
1087
  });
609
1088
  return;
610
1089
  }
@@ -896,6 +1375,8 @@ export class CrossSdkTsAgent {
896
1375
  };
897
1376
  if (body.ttl !== undefined)
898
1377
  params.ttl = Number(body.ttl);
1378
+ if (body.need_render !== undefined)
1379
+ params.need_render = body.need_render;
899
1380
  try {
900
1381
  const result = await this.client.call('message.send', params);
901
1382
  const resultObj = result && typeof result === 'object' ? result : {};
@@ -931,6 +1412,10 @@ export class CrossSdkTsAgent {
931
1412
  const params = {};
932
1413
  if (seq > 0)
933
1414
  params.seq = seq;
1415
+ if (textOf(body.sender_aid).trim())
1416
+ params.sender_aid = textOf(body.sender_aid).trim();
1417
+ if (body.cursor_scope !== undefined)
1418
+ params.cursor_scope = body.cursor_scope;
934
1419
  if (Array.isArray(body.volatile_cursors))
935
1420
  params.volatile_cursors = body.volatile_cursors;
936
1421
  else if (textOf(body.volatile_cursor))
@@ -952,9 +1437,26 @@ export class CrossSdkTsAgent {
952
1437
  }
953
1438
  async pull(req, res) {
954
1439
  const body = await readJson(req);
955
- const afterSeq = Number(body.after_seq ?? 0) || 0;
956
- const limit = Number(body.limit ?? 50) || 50;
957
- const params = { after_seq: afterSeq, limit };
1440
+ if ((body.limit !== undefined && (typeof body.limit !== 'number' || !Number.isSafeInteger(body.limit) || body.limit < 1 || body.limit > 200)) ||
1441
+ (body.max_pages !== undefined && (typeof body.max_pages !== 'number' || !Number.isSafeInteger(body.max_pages) || body.max_pages < 1)) ||
1442
+ (body.after_seq !== undefined && (typeof body.after_seq !== 'number' || !Number.isSafeInteger(body.after_seq) || body.after_seq < 0)) ||
1443
+ ['sender_aid', 'cursor_scope', 'after_volatile_cursor'].some((key) => body[key] !== undefined && typeof body[key] !== 'string') ||
1444
+ (body.force !== undefined && typeof body.force !== 'boolean') ||
1445
+ (body.window_mode !== undefined && !['forward', 'tail'].includes(body.window_mode))) {
1446
+ sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid pull parameters' });
1447
+ return;
1448
+ }
1449
+ const senderAid = textOf(body.sender_aid).trim();
1450
+ const limit = body.limit ?? 50;
1451
+ const params = { limit };
1452
+ if (senderAid)
1453
+ params.sender_aid = senderAid;
1454
+ if (body.after_seq !== undefined)
1455
+ params.after_seq = body.after_seq;
1456
+ for (const key of ['cursor_scope', 'force', 'window_mode', 'max_pages']) {
1457
+ if (body[key] !== undefined)
1458
+ params[key] = body[key];
1459
+ }
958
1460
  if (textOf(body.after_volatile_cursor))
959
1461
  params.after_volatile_cursor = textOf(body.after_volatile_cursor);
960
1462
  try {
@@ -976,6 +1478,20 @@ export class CrossSdkTsAgent {
976
1478
  }
977
1479
  async messageCall(req, res) {
978
1480
  const body = await readJson(req);
1481
+ if (['start', 'status', 'detach'].includes(textOf(body.action))) {
1482
+ this.messageOperations ??= new Map();
1483
+ const id = textOf(body.operation_id);
1484
+ if (body.action === 'start') {
1485
+ const operation = { ok: true, operation_id: id, status: 'running', started_ms: Date.now() };
1486
+ this.messageOperations.set(id, operation);
1487
+ Promise.resolve().then(() => { operation.entered_ms = Date.now(); return this.client.call(textOf(body.method), (jsonObjectOf(body.params) ?? {})); }).then((result) => Object.assign(operation, { status: 'completed', end_ms: Date.now(), result: jsonSafe(result) }), (error) => Object.assign(operation, { status: 'failed', end_ms: Date.now(), error_code: error instanceof Error ? error.name : 'Error', error_message: error instanceof Error ? error.message : String(error) }));
1488
+ }
1489
+ const operation = this.messageOperations.get(id);
1490
+ if (body.action === 'detach')
1491
+ Object.assign(operation, { detached: true, cancellation: 'application_wait_only', detached_ms: Date.now() });
1492
+ sendJson(res, 200, operation);
1493
+ return;
1494
+ }
979
1495
  const traceId = textOf(body.trace_id || crypto.randomUUID().replace(/-/g, ''));
980
1496
  const method = textOf(body.method).trim();
981
1497
  const params = jsonObjectOf(body.params) ?? {};
@@ -1007,15 +1523,15 @@ export class CrossSdkTsAgent {
1007
1523
  sendJson(res, 500, error);
1008
1524
  }
1009
1525
  }
1010
- syncNamespace(scope, groupId = '') {
1526
+ syncNamespace(scope, groupId = '', senderAid = '') {
1011
1527
  if (scope === 'p2p')
1012
- return `p2p:${this.aid}`;
1528
+ return senderAid ? `p2p.peer.v1/${Buffer.from(senderAid, 'utf8').toString('base64url')}` : `p2p:${this.aid}`;
1013
1529
  if (scope === 'group' && groupId)
1014
1530
  return `group:${groupId}`;
1015
1531
  throw new Error('scope must be p2p, or group with group_id');
1016
1532
  }
1017
- syncStateValue(scope, groupId = '') {
1018
- const namespace = this.syncNamespace(scope, groupId);
1533
+ syncStateValue(scope, groupId = '', senderAid = '') {
1534
+ const namespace = this.syncNamespace(scope, groupId, senderAid);
1019
1535
  const tracker = this.client._seqTracker;
1020
1536
  if (!tracker)
1021
1537
  throw new Error('SeqTracker is unavailable');
@@ -1024,9 +1540,10 @@ export class CrossSdkTsAgent {
1024
1540
  syncState(url, res) {
1025
1541
  const scope = textOf(url.searchParams.get('scope') || 'p2p').trim().toLowerCase();
1026
1542
  const groupId = textOf(url.searchParams.get('group_id') || '').trim();
1543
+ const senderAid = textOf(url.searchParams.get('sender_aid') || '').trim();
1027
1544
  try {
1028
- const snapshot = this.syncStateValue(scope, groupId);
1029
- sendJson(res, 200, { ok: true, scope, ...snapshot });
1545
+ const snapshot = this.syncStateValue(scope, groupId, senderAid);
1546
+ sendJson(res, 200, { ok: true, scope, ...snapshot, peers_snapshots: this.peersSnapshots });
1030
1547
  }
1031
1548
  catch (err) {
1032
1549
  sendJson(res, 400, {
@@ -1046,27 +1563,24 @@ export class CrossSdkTsAgent {
1046
1563
  const body = await readJson(req);
1047
1564
  const scope = textOf(body.scope || 'p2p').trim().toLowerCase();
1048
1565
  const groupId = textOf(body.group_id || '').trim();
1566
+ const senderAid = textOf(body.sender_aid || '').trim();
1049
1567
  const afterSeq = Number(body.after_seq ?? 0) || 0;
1050
1568
  const limit = Number(body.limit ?? 50) || 50;
1051
1569
  try {
1052
- const params = { after_seq: afterSeq, limit };
1053
- if (tail)
1054
- params.window_mode = 'tail';
1570
+ const params = { limit, window_mode: tail ? 'tail' : 'forward' };
1571
+ if (scope === 'group' || body.after_seq !== undefined)
1572
+ params.after_seq = afterSeq;
1573
+ if (scope === 'p2p' && senderAid)
1574
+ params.sender_aid = senderAid;
1575
+ for (const key of ['cursor_scope', 'force', 'max_pages', 'after_volatile_cursor']) {
1576
+ if (body[key] !== undefined)
1577
+ params[key] = body[key];
1578
+ }
1055
1579
  const method = scope === 'group' ? 'group.pull' : 'message.pull';
1056
1580
  if (scope === 'group')
1057
1581
  params.group_id = groupId;
1058
- this.syncNamespace(scope, groupId);
1059
- let result;
1060
- if (tail) {
1061
- result = await this.client.call(method, params);
1062
- }
1063
- else {
1064
- const transport = this.client._transport;
1065
- if (!transport?.call)
1066
- throw new Error('raw transport is unavailable');
1067
- const wireMethod = scope === 'group' ? 'group.v2.pull' : 'message.v2.pull';
1068
- result = await transport.call(wireMethod, params);
1069
- }
1582
+ this.syncNamespace(scope, groupId, senderAid);
1583
+ const result = await this.client.call(method, params);
1070
1584
  const resultObj = jsonObjectOf(result) ?? {};
1071
1585
  const messages = Array.isArray(resultObj.messages) ? resultObj.messages : [];
1072
1586
  if (tail) {
@@ -1077,7 +1591,7 @@ export class CrossSdkTsAgent {
1077
1591
  await this.storeInboxItem(this.normalizeMessage(msg, true));
1078
1592
  }
1079
1593
  }
1080
- const snapshot = this.syncStateValue(scope, groupId);
1594
+ const snapshot = this.syncStateValue(scope, groupId, senderAid);
1081
1595
  sendJson(res, 200, { ok: true, scope, ...snapshot, result: jsonSafe(result) });
1082
1596
  }
1083
1597
  catch (err) {
@@ -1093,6 +1607,7 @@ export class CrossSdkTsAgent {
1093
1607
  const body = await readJson(req);
1094
1608
  const scope = textOf(body.scope || 'p2p').trim().toLowerCase();
1095
1609
  const groupId = textOf(body.group_id || '').trim();
1610
+ const senderAid = textOf(body.sender_aid || '').trim();
1096
1611
  const beforeSeq = Number(body.before_seq ?? 0) || 0;
1097
1612
  const limit = Number(body.limit ?? 50) || 50;
1098
1613
  try {
@@ -1100,9 +1615,13 @@ export class CrossSdkTsAgent {
1100
1615
  const method = scope === 'group' ? 'group.history' : 'message.history';
1101
1616
  if (scope === 'group')
1102
1617
  params.group_id = groupId;
1103
- this.syncNamespace(scope, groupId);
1618
+ if (scope === 'p2p' && senderAid)
1619
+ params.sender_aid = senderAid;
1620
+ if (body.cursor_scope !== undefined)
1621
+ params.cursor_scope = body.cursor_scope;
1622
+ this.syncNamespace(scope, groupId, senderAid);
1104
1623
  const result = await this.client.call(method, params);
1105
- const snapshot = this.syncStateValue(scope, groupId);
1624
+ const snapshot = this.syncStateValue(scope, groupId, senderAid);
1106
1625
  sendJson(res, 200, { ok: true, scope, ...snapshot, result: jsonSafe(result) });
1107
1626
  }
1108
1627
  catch (err) {
@@ -1117,7 +1636,25 @@ export class CrossSdkTsAgent {
1117
1636
  async sessionDisconnect(res) {
1118
1637
  try {
1119
1638
  this.ready = false;
1120
- await this.client.disconnect();
1639
+ const barrier = this.pullBarrier;
1640
+ let restore;
1641
+ if (barrier?.preserve && barrier.waiting === 1 && !barrier.released && ['message.pull', 'message.v2.pull'].includes(barrier.heldMethod ?? '')) {
1642
+ const binding = barrier.owner, pipeline = this.client._rpcPipeline;
1643
+ const owners = [...(pipeline?.pullGateStates.values() ?? [])].flatMap((state) => [...state.activeByNamespace.values()])
1644
+ .filter((job) => job === binding?.job && job.key === binding.key && job.namespace === binding.namespace && job.running && !job.timedOut && !job.invalidated && job.cancel === binding.cancel);
1645
+ if (owners.length !== 1)
1646
+ throw new Error('保留响应无法定位唯一受跟踪的 pull owner');
1647
+ const owner = owners[0], settled = owner.settled;
1648
+ owner.settled = Promise.resolve();
1649
+ restore = () => { owner.settled = settled; };
1650
+ barrier.span.barrier_owner_isolated = true;
1651
+ }
1652
+ try {
1653
+ await this.client.disconnect();
1654
+ }
1655
+ finally {
1656
+ restore?.();
1657
+ }
1121
1658
  sendJson(res, 200, { ok: true, state: textOf(this.client.state) });
1122
1659
  }
1123
1660
  catch (err) {
@@ -1130,14 +1667,19 @@ export class CrossSdkTsAgent {
1130
1667
  }
1131
1668
  async sessionConnect(req, res) {
1132
1669
  const body = await readJson(req);
1670
+ if (['background_sync', 'inline_realtime_payload_v1', 'batch_events'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
1671
+ sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid session parameters' });
1672
+ return;
1673
+ }
1133
1674
  const backgroundSync = body.background_sync === true;
1134
1675
  if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
1135
1676
  this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
1136
1677
  }
1137
1678
  try {
1679
+ this.setBatchEventMode(typeof body.batch_events === 'boolean' ? body.batch_events : this.batchEventMode ?? false);
1138
1680
  await this.client.connect({ auto_reconnect: false, background_sync: backgroundSync });
1139
1681
  this.ready = true;
1140
- sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1 });
1682
+ sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1, batch_events: this.batchEventMode });
1141
1683
  }
1142
1684
  catch (err) {
1143
1685
  sendJson(res, 500, {
@@ -1149,19 +1691,26 @@ export class CrossSdkTsAgent {
1149
1691
  }
1150
1692
  async sessionRestart(req, res) {
1151
1693
  const body = await readJson(req);
1694
+ if (['background_sync', 'inline_realtime_payload_v1', 'batch_events'].some((key) => body[key] !== undefined && typeof body[key] !== 'boolean')) {
1695
+ sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'invalid session parameters' });
1696
+ return;
1697
+ }
1152
1698
  const backgroundSync = body.background_sync === true;
1153
1699
  if (Object.prototype.hasOwnProperty.call(body, 'inline_realtime_payload_v1')) {
1154
1700
  this.inlineRealtimePayloadV1 = body.inline_realtime_payload_v1 !== false;
1155
1701
  }
1156
1702
  this.ready = false;
1157
1703
  try {
1704
+ if (typeof body.batch_events === 'boolean')
1705
+ this.batchEventMode = body.batch_events;
1706
+ this.retireP2pDelivery(this.client);
1158
1707
  await this.client.close();
1159
1708
  this.client = this.newClient();
1160
1709
  this.bindClientEvents();
1161
1710
  await this.ensureConnected(backgroundSync);
1162
1711
  this.startupError = '';
1163
1712
  this.ready = true;
1164
- sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1 });
1713
+ sendJson(res, 200, { ok: true, background_sync: backgroundSync, inline_realtime_payload_v1: this.inlineRealtimePayloadV1, batch_events: this.batchEventMode });
1165
1714
  }
1166
1715
  catch (err) {
1167
1716
  this.startupError = `${err instanceof Error ? err.name : 'Error'}: ${err instanceof Error ? err.message : String(err)}`;
@@ -1231,15 +1780,36 @@ export class CrossSdkTsAgent {
1231
1780
  const now = Math.floor(Date.now() / 1000);
1232
1781
  if (op === 'request')
1233
1782
  return buildRequestBinding(textOf(params.method), textOf(params.target), params.params, textOf(params.credential_cid), textOf(params.nonce), textOf(params.request_id), now, now + 300);
1234
- if (['register', 'sign', 'certificate', 'create', 'prepare'].includes(op)) {
1783
+ if (op === 'revocation_checkpoint') {
1784
+ const internal = this.client;
1785
+ if (params.sync === true)
1786
+ await internal._getCredentialRenewal()._syncRevocations();
1787
+ return internal._tokenStore._loadMetadataStrict(this.aid)?.credential_revocations_v1 ?? { version: 1, authorities: {} };
1788
+ }
1789
+ if (op === 'credential_process')
1790
+ return params.startup === true ? this.credentialStartup : this.credentialProcessCheckpoint();
1791
+ if (op === 'manage_credential') {
1792
+ const manager = this.client._getCredentialRenewal();
1793
+ await manager.serial(() => manager.rememberNow(params.credential, {}, this.aid, undefined, true));
1794
+ return await this.client.credential.current(textOf(params.credential.cid));
1795
+ }
1796
+ if (op === 'current_credential')
1797
+ return await this.client.credential.current(textOf(params.credential_cid));
1798
+ if (['register', 'sign', 'certificate', 'local_certificate', 'create', 'prepare', 'transfer_accept', 'renew'].includes(op)) {
1235
1799
  const store = this.aidStore();
1236
1800
  try {
1237
- if (op === 'prepare') {
1801
+ if (op === 'transfer_accept')
1802
+ return await this.client.org.lifecycle.transferAccept({ ...jsonObjectOf(params.params), aidStore: store });
1803
+ if (op === 'prepare' || op === 'renew') {
1238
1804
  const request = jsonObjectOf(params.params) ?? {};
1239
1805
  const orgId = textOf(params.org_id ?? request.org_id);
1240
1806
  const loaded = orgId ? store.load(orgId) : null;
1241
1807
  const orgSigner = loaded?.ok && loaded.data.aid.isPrivateKeyValid() ? loaded.data.aid : undefined;
1242
- return this.client.org.prepareRequest(textOf(params.method), request, textOf(params.target), this.client.currentAid, { ...jsonObjectOf(params.options), orgSigner });
1808
+ const credentialOptions = jsonObjectOf(params.credential_options);
1809
+ if (op === 'prepare')
1810
+ return this.client.org.prepareRequest(textOf(params.method), request, textOf(params.target), this.client.currentAid, { ...jsonObjectOf(params.options), orgSigner, ...(credentialOptions ? { credentialOptions } : {}) });
1811
+ const prepared = this.client.org.prepareRequest('org.member.renewCredential', request, textOf(params.target) || `org.${orgId.slice(orgId.indexOf('.') + 1)}`, this.client.currentAid, { orgSigner, credentialOptions: jsonObjectOf(params.credential_options) ?? undefined });
1812
+ return { result: await this.client.org.member.renewCredential(prepared), params: prepared };
1243
1813
  }
1244
1814
  if (op === 'register') {
1245
1815
  const registered = await store.register(textOf(params.org_id), { type: 'org', owner: this.aid, exttype: 'company' });
@@ -1248,12 +1818,14 @@ export class CrossSdkTsAgent {
1248
1818
  }
1249
1819
  const loaded = op === 'certificate'
1250
1820
  ? await store.resolve(textOf(params.as_aid), { skipAgentMd: true })
1251
- : (params.as_aid || params.org_id) ? store.load(textOf(params.as_aid ?? params.org_id)) : null;
1821
+ : (params.as_aid || op === 'local_certificate' || params.org_id) ? store.load(textOf(params.as_aid ?? params.org_id)) : null;
1252
1822
  if (loaded && !loaded.ok)
1253
1823
  throw new Error(String(loaded.error));
1254
1824
  const identity = loaded?.data?.aid ?? this.client.currentAid;
1255
1825
  const result = { cert_fingerprint: identity.certFingerprint, certificate: identity.certPem };
1256
- if (op === 'sign') {
1826
+ if (op === 'local_certificate')
1827
+ result.has_private_key = identity.isPrivateKeyValid();
1828
+ else if (op === 'sign') {
1257
1829
  const signed = await identity.sign(Buffer.from(canonicalJson(params.payload)));
1258
1830
  if (!signed.ok)
1259
1831
  throw new Error(String(signed.error));
@@ -1281,6 +1853,7 @@ export class CrossSdkTsAgent {
1281
1853
  const actual = jsonObjectOf(params.context) ?? params;
1282
1854
  const memberships = jsonObjectOf(params.memberships) ?? {};
1283
1855
  result.verification = { ...this.client.credential.verify(value, { resolveCertificate: resolver, resolveActorCertificate: resolver,
1856
+ resolveStatusProof: (cid) => params.status_proofs?.[cid],
1284
1857
  resolveStatus: (cid) => params.statuses?.[cid], now: verificationNow, audience: textOf(actual.audience), subject: textOf(actual.subject) || undefined,
1285
1858
  ns: textOf(actual.ns), action: textOf(actual.action), resource: textOf(actual.resource), rootIssuer: textOf(actual.root_issuer) || undefined,
1286
1859
  params: jsonObjectOf(actual.params) ?? {}, units: jsonObjectOf(actual.units) ?? {}, clientIp: textOf(actual.client_ip), method: textOf(actual.method), target: textOf(actual.target),
@@ -1322,6 +1895,18 @@ export class CrossSdkTsAgent {
1322
1895
  case 'sdk.org.protocol':
1323
1896
  result = await this.orgProtocol(params);
1324
1897
  break;
1898
+ case 'credential.register':
1899
+ result = await this.client.credential.register(params);
1900
+ break;
1901
+ case 'credential.status':
1902
+ result = await this.client.credential.status(params);
1903
+ break;
1904
+ case 'credential.revoke':
1905
+ result = await this.client.credential.revoke(params);
1906
+ break;
1907
+ case 'credential.revocationList':
1908
+ result = await this.client.credential.revocationList(params);
1909
+ break;
1325
1910
  case 'org.lifecycle.create':
1326
1911
  result = await this.client.org.lifecycle.create(params);
1327
1912
  break;
@@ -1414,6 +1999,10 @@ export class CrossSdkTsAgent {
1414
1999
  error_code: err instanceof Error ? err.name : 'Error',
1415
2000
  error_message: err instanceof Error ? err.message : String(err),
1416
2001
  };
2002
+ if (method.startsWith('org.') || method === 'sdk.org.protocol') {
2003
+ const remote = err;
2004
+ error.rpc_error_code = remote?.stringCode || remote?.data?.error_code || remote?.data?.code || (typeof remote?.code === 'string' ? remote.code : '');
2005
+ }
1417
2006
  this.recordTrace(traceId, { stage: 'group_call_error', method, error });
1418
2007
  sendJson(res, 500, error);
1419
2008
  }
@@ -1578,6 +2167,8 @@ export class CrossSdkTsAgent {
1578
2167
  }
1579
2168
  }
1580
2169
  async callGroupFsAction(action, params, asGroupAid = '') {
2170
+ if (action === 'cache_read')
2171
+ return await this.cacheRead(params, true, asGroupAid);
1581
2172
  const groupFs = this.client.group.fs;
1582
2173
  let signingStore = null;
1583
2174
  const withSigning = (source) => {
@@ -1717,7 +2308,124 @@ export class CrossSdkTsAgent {
1717
2308
  }
1718
2309
  return response;
1719
2310
  }
2311
+ async cacheRead(params, group = false, asGroupAid = '') {
2312
+ const operation = textOf(params.operation || 'read_bytes');
2313
+ if (!['read_bytes', 'stat', 'lstat', 'list', 'ls', 'find', 'du', 'df'].includes(operation)) {
2314
+ throw new Error(`unsupported cache read operation: ${operation}`);
2315
+ }
2316
+ const transport = this.client._transport;
2317
+ const lowlevel = group ? this.client.group.fs.lowlevel : this.client.storage.lowlevel;
2318
+ const counters = { rpc_count: 0, http_count: 0, body_transfers: 0, body_bytes: 0, methods: {}, wire_params: [] };
2319
+ const releaseCall = acquireCacheReadObserver(transport, 'call', (originalCall) => (method, request, ...tail) => {
2320
+ const context = cacheReadContext.getStore();
2321
+ const counted = context && (method.startsWith('storage.') || method.startsWith('group.fs.'));
2322
+ if (counted) {
2323
+ context.rpc_count += 1;
2324
+ context.methods[method] = (context.methods[method] || 0) + 1;
2325
+ context.wire_params.push({ method, params: JSON.parse(JSON.stringify(request ?? {})) });
2326
+ }
2327
+ const pending = originalCall.call(transport, method, request, ...tail);
2328
+ const observed = pending.then((result) => {
2329
+ const payload = jsonObjectOf(result);
2330
+ const content = payload?.content_base64 ?? payload?.content;
2331
+ if (counted && !payload?.not_modified && typeof content === 'string') {
2332
+ context.body_transfers += 1;
2333
+ context.body_bytes += Buffer.from(content, 'base64').byteLength;
2334
+ }
2335
+ return result;
2336
+ });
2337
+ if (pending.cancel)
2338
+ observed.cancel = () => pending.cancel();
2339
+ return observed;
2340
+ });
2341
+ const releaseHttp = acquireCacheReadObserver(lowlevel, 'httpGet', (originalHttp) => async (...args) => {
2342
+ const context = cacheReadContext.getStore();
2343
+ if (context)
2344
+ context.http_count += 1;
2345
+ const data = await originalHttp.call(lowlevel, ...args);
2346
+ if (context) {
2347
+ context.body_transfers += 1;
2348
+ context.body_bytes += data.byteLength;
2349
+ }
2350
+ return data;
2351
+ });
2352
+ const readOnce = async () => {
2353
+ const remotePath = textOf(params.path);
2354
+ const noCache = Boolean(params.no_cache ?? params.noCache);
2355
+ if (group) {
2356
+ const options = withoutKeys(params, ['operation', 'count', 'concurrent', 'no_cache']);
2357
+ options.noCache = noCache;
2358
+ if (operation !== 'read_bytes')
2359
+ return await this.callGroupFsAction(operation === 'list' ? 'ls' : operation, options, asGroupAid);
2360
+ delete options.path;
2361
+ const dst = this.writeTempText('', '.bin');
2362
+ try {
2363
+ const result = await this.callGroupFsAction('cp', { ...options, src: remotePath, dst, force: true }, asGroupAid);
2364
+ const response = jsonObjectOf(result);
2365
+ return { content_base64: response.content_base64, size_bytes: response.size_bytes };
2366
+ }
2367
+ finally {
2368
+ fs.rmSync(dst, { force: true });
2369
+ }
2370
+ }
2371
+ const storage = this.client.storage;
2372
+ const options = {
2373
+ owner: textOf(params.owner_aid ?? params.ownerAID).trim() || undefined,
2374
+ bucket: textOf(params.bucket).trim() || 'default', noCache,
2375
+ };
2376
+ if (operation !== 'df')
2377
+ options.token = textOf(params.token).trim() || undefined;
2378
+ if (operation === 'read_bytes') {
2379
+ const shareId = textOf(params.share_id ?? params.shareId).trim();
2380
+ if (shareId) {
2381
+ const result = await storage.lowlevel.getByShare({ shareId, noCache });
2382
+ const data = result.download_url ? await storage.lowlevel.httpGet(textOf(result.download_url)) : Buffer.from(textOf(result.content_base64 ?? result.content), 'base64');
2383
+ return { content_base64: Buffer.from(data).toString('base64'), size_bytes: data.byteLength };
2384
+ }
2385
+ for (const key of ['offset', 'limit'])
2386
+ if (params[key] != null)
2387
+ options[key] = Number(params[key]);
2388
+ const data = await storage.readBytes(remotePath, options);
2389
+ return { content_base64: Buffer.from(data).toString('base64'), size_bytes: data.byteLength };
2390
+ }
2391
+ const fields = { list: ['page', 'size', 'marker', 'long', 'recursive'], find: ['name', 'mtime', 'page'], du: [] };
2392
+ for (const key of fields[operation] ?? [])
2393
+ if (params[key] != null)
2394
+ options[key] = params[key];
2395
+ if (params.page_size != null || params.pageSize != null)
2396
+ options.pageSize = Number(params.page_size ?? params.pageSize);
2397
+ if (params.max_depth != null || params.maxDepth != null)
2398
+ options.maxDepth = Number(params.max_depth ?? params.maxDepth);
2399
+ if (params.node_type != null || params.nodeType != null)
2400
+ options.nodeType = params.node_type ?? params.nodeType;
2401
+ if (operation === 'find' && (params.size_expr != null || params.sizeExpr != null))
2402
+ options.size = params.size_expr ?? params.sizeExpr;
2403
+ const method = storage[operation === 'ls' ? 'list' : operation];
2404
+ return await method.call(storage, ...(operation === 'df' ? [options] : [remotePath, options]));
2405
+ };
2406
+ try {
2407
+ const count = Math.max(1, Number(params.count || 1));
2408
+ const values = await cacheReadContext.run(counters, async () => {
2409
+ if (params.concurrent)
2410
+ return await Promise.all(Array.from({ length: count }, () => readOnce()));
2411
+ const items = [];
2412
+ for (let index = 0; index < count; index += 1)
2413
+ items.push(await readOnce());
2414
+ return items;
2415
+ });
2416
+ if (values.some((value) => stableStringify(value) !== stableStringify(values[0])))
2417
+ throw new Error('cache_read returned inconsistent values');
2418
+ const result = operation === 'read_bytes' ? jsonObjectOf(values[0]) : { value: values[0] };
2419
+ return { ...result, ...counters };
2420
+ }
2421
+ finally {
2422
+ releaseCall();
2423
+ releaseHttp();
2424
+ }
2425
+ }
1720
2426
  async callStorageAction(action, params) {
2427
+ if (action === 'cache_read')
2428
+ return await this.cacheRead(params);
1721
2429
  const storage = this.client.storage;
1722
2430
  const path = textOf(params.path).trim();
1723
2431
  const owner = textOf(params.owner_aid ?? params.ownerAID).trim() || undefined;
@@ -1896,7 +2604,10 @@ export class CrossSdkTsAgent {
1896
2604
  case 'revoke_share_link':
1897
2605
  return await storage.lowlevel.revokeShareLink({ shareId: textOf(params.share_id ?? params.shareId).trim() });
1898
2606
  case 'get_by_share': {
1899
- const result = await storage.lowlevel.getByShare({ shareId: textOf(params.share_id ?? params.shareId).trim() });
2607
+ const result = await storage.lowlevel.getByShare({
2608
+ shareId: textOf(params.share_id ?? params.shareId).trim(),
2609
+ noCache: Boolean(params.no_cache ?? params.noCache),
2610
+ });
1900
2611
  const resultObj = jsonObjectOf(result);
1901
2612
  const content = textOf(resultObj?.content);
1902
2613
  if (content) {
@@ -2053,6 +2764,7 @@ export class CrossSdkTsAgent {
2053
2764
  payload,
2054
2765
  encrypt: e2ee,
2055
2766
  message_id: messageId,
2767
+ ...(body.need_render !== undefined ? { need_render: body.need_render } : {}),
2056
2768
  });
2057
2769
  const resultObj = result && typeof result === 'object' && !Array.isArray(result) ? result : {};
2058
2770
  const response = {
@@ -2186,7 +2898,7 @@ export class CrossSdkTsAgent {
2186
2898
  aidStore() {
2187
2899
  return new AIDStore({
2188
2900
  aunPath: this.aunPath,
2189
- encryptionSeed: '',
2901
+ encryptionSeed: process.env.AUN_TEST_ENCRYPTION_SEED || process.env.AUN_ENCRYPTION_SEED || '',
2190
2902
  slotId: this.slotId,
2191
2903
  debug: this.debug,
2192
2904
  });