@agentunion/fastaun-browser 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +190 -178
  2. package/_packed_docs/AUN_SDK_0.4.0_/350/256/276/350/256/241/345/257/271/346/257/224/345/210/206/346/236/220.md +194 -194
  3. package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/345/256/236/346/226/275/350/256/241/345/210/222.md +596 -596
  4. package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/350/256/276/350/256/241/346/226/271/346/241/210_v3.md +1698 -1697
  5. package/_packed_docs/CHANGELOG.md +190 -178
  6. package/_packed_docs/INDEX.md +17 -17
  7. package/_packed_docs/KITE_DOCS_GUIDE.md +11 -11
  8. package/_packed_docs/agent.md/SCHEMA.md +49 -49
  9. package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +22 -22
  10. package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +327 -327
  11. package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +686 -686
  12. package/_packed_docs/design/2026-05-22-aun-rpc-trace-enhancement.md +542 -542
  13. package/_packed_docs/design/E2EE_V2/347/256/200/345/214/226/344/270/2721DH/345/212/240Per-AID_Wrap/346/226/271/346/241/210.md +124 -124
  14. package/_packed_docs/design//350/267/250/350/257/255/350/250/200/345/256/271/345/231/250E2E/346/265/213/350/257/225/346/226/271/346/241/210.md +665 -665
  15. package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +2 -2
  16. package/_packed_docs/protocol/14-/344/272/244/344/272/222/346/234/272/345/210/266-/345/223/215/345/272/224/346/250/241/345/274/217/344/270/216/350/207/252/344/270/273/346/250/241/345/274/217.md +170 -170
  17. package/_packed_docs/protocol/15-/347/246/273/347/272/277/346/216/250/351/200/201/351/200/232/347/237/245/345/215/217/350/256/256.md +419 -419
  18. package/_packed_docs/protocol/README.md +1 -1
  19. package/_packed_docs/protocol/aun-docs-guide.md +1 -1
  20. package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +15 -15
  21. package/_packed_docs/protocol//351/231/204/345/275/225B-/346/211/251/345/261/225/346/200/247/346/214/207/345/215/227.md +4 -4
  22. package/_packed_docs/protocol//351/231/204/345/275/225J-/345/256/242/346/210/267/347/253/257/346/216/245/345/205/245/347/244/272/344/276/213.md +98 -98
  23. package/_packed_docs/protocol//351/231/204/345/275/225M-JWT/350/256/244/350/257/201/345/256/236/347/216/260/346/214/207/345/215/227.md +46 -46
  24. package/_packed_docs/protocol//351/231/204/345/275/225N-/345/210/206/345/270/203/345/274/217Trace/345/215/217/350/256/256.md +257 -257
  25. package/_packed_docs/python-sdk-v2-only-changelog.md +189 -189
  26. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
  27. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
  28. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1 -0
  29. package/_packed_docs/sdk/09-payload-reference.md +13 -13
  30. package/_packed_docs/sdk/E2EE_V2/346/266/210/346/201/257/351/200/232/344/277/241/346/227/266/345/272/217/345/233/276.md +171 -171
  31. package/dist/aid.d.ts +2 -1
  32. package/dist/aid.d.ts.map +1 -1
  33. package/dist/aid.js +7 -6
  34. package/dist/aid.js.map +1 -1
  35. package/dist/auth.d.ts.map +1 -1
  36. package/dist/auth.js +4 -0
  37. package/dist/auth.js.map +1 -1
  38. package/dist/bundle.js +237 -149
  39. package/dist/client.d.ts +7 -1
  40. package/dist/client.d.ts.map +1 -1
  41. package/dist/client.js +238 -153
  42. package/dist/client.js.map +1 -1
  43. package/dist/version.d.ts +1 -1
  44. package/dist/version.js +1 -1
  45. package/package.json +1 -1
package/dist/bundle.js CHANGED
@@ -454,7 +454,7 @@ var init_indexeddb_store = __esm({
454
454
  });
455
455
 
456
456
  // src/version.ts
457
- var VERSION = "0.4.3";
457
+ var VERSION = "0.4.4";
458
458
 
459
459
  // src/types.ts
460
460
  var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
@@ -3142,6 +3142,9 @@ var _AuthFlow = class _AuthFlow {
3142
3142
  delete persistedRecord[key];
3143
3143
  }
3144
3144
  }
3145
+ for (const key of ["private_key_pem", "public_key_der_b64", "curve"]) {
3146
+ delete persistedRecord[key];
3147
+ }
3145
3148
  await this._keystore.saveIdentity(aid, persisted);
3146
3149
  if (Object.keys(instanceState).length === 0 || typeof this._keystore.updateInstanceState !== "function") {
3147
3150
  return;
@@ -9703,7 +9706,8 @@ var AID = class _AID {
9703
9706
  __publicField(this, "verifySsl");
9704
9707
  __publicField(this, "rootCaPath");
9705
9708
  __publicField(this, "debug");
9706
- __publicField(this, "_privateKeyPem");
9709
+ /** AIDStore 加载时注入的明文私钥 PEM,供 AUNClient 直接使用(无需 seed)。*/
9710
+ __publicField(this, "privateKeyPem");
9707
9711
  __publicField(this, "_certValid");
9708
9712
  __publicField(this, "_privateKeyValid");
9709
9713
  __publicField(this, "_certFingerprint", "");
@@ -9721,7 +9725,7 @@ var AID = class _AID {
9721
9725
  this.certIssuer = meta.issuer;
9722
9726
  this.certNotBefore = meta.notBefore;
9723
9727
  this.certNotAfter = meta.notAfter;
9724
- this._privateKeyPem = params.privateKeyPem;
9728
+ this.privateKeyPem = params.privateKeyPem ?? "";
9725
9729
  this._certValid = params.certValid;
9726
9730
  this._privateKeyValid = params.privateKeyValid;
9727
9731
  }
@@ -9740,10 +9744,10 @@ var AID = class _AID {
9740
9744
  return this._privateKeyValid;
9741
9745
  }
9742
9746
  async sign(payload) {
9743
- if (!this._privateKeyValid || !this._privateKeyPem) return resultErr(PRIVATE_KEY_NOT_VALID, "private key is not valid");
9747
+ if (!this._privateKeyValid || !this.privateKeyPem) return resultErr(PRIVATE_KEY_NOT_VALID, "private key is not valid");
9744
9748
  try {
9745
9749
  const data = typeof payload === "string" ? new TextEncoder().encode(payload) : payload;
9746
- return resultOk({ signature: await signBytes(this._privateKeyPem, data) });
9750
+ return resultOk({ signature: await signBytes(this.privateKeyPem, data) });
9747
9751
  } catch (exc) {
9748
9752
  return resultErr(SIGNATURE_OPERATION_ERROR, String(exc), exc);
9749
9753
  }
@@ -9758,10 +9762,10 @@ var AID = class _AID {
9758
9762
  }
9759
9763
  }
9760
9764
  async signAgentMd(content) {
9761
- if (!this._privateKeyValid || !this._privateKeyPem) return resultErr(PRIVATE_KEY_NOT_VALID, "private key is not valid");
9765
+ if (!this._privateKeyValid || !this.privateKeyPem) return resultErr(PRIVATE_KEY_NOT_VALID, "private key is not valid");
9762
9766
  try {
9763
9767
  const payload = normalizeAgentMdPayload(content);
9764
- const signature = await signBytes(this._privateKeyPem, new TextEncoder().encode(payload));
9768
+ const signature = await signBytes(this.privateKeyPem, new TextEncoder().encode(payload));
9765
9769
  return resultOk({ signed: payload + buildAgentMdSignatureBlock(this.certFingerprint, Date.now() / 1e3, signature) });
9766
9770
  } catch (exc) {
9767
9771
  return resultErr(SIGNATURE_OPERATION_ERROR, String(exc), exc);
@@ -9809,6 +9813,12 @@ function getV2DeviceId(dev) {
9809
9813
  }
9810
9814
  return { present: false, value: "" };
9811
9815
  }
9816
+ function isAIDObject(value) {
9817
+ const candidate = value;
9818
+ return Boolean(
9819
+ candidate && typeof candidate === "object" && typeof candidate.aid === "string" && typeof candidate.aunPath === "string" && typeof candidate.isPrivateKeyValid === "function"
9820
+ );
9821
+ }
9812
9822
  function sortObjectKeys(obj) {
9813
9823
  if (obj === null || obj === void 0 || typeof obj !== "object") return obj;
9814
9824
  if (Array.isArray(obj)) return obj.map(sortObjectKeys);
@@ -9941,6 +9951,20 @@ var DEFAULT_SESSION_OPTIONS = {
9941
9951
  http: 30
9942
9952
  }
9943
9953
  };
9954
+ var PUBLIC_CONNECTION_OPTION_KEYS = /* @__PURE__ */ new Set([
9955
+ "auto_reconnect",
9956
+ "connect_timeout",
9957
+ "retry_initial_delay",
9958
+ "retry_max_delay",
9959
+ "retry_max_attempts",
9960
+ "heartbeat_interval",
9961
+ "call_timeout",
9962
+ "connection_kind",
9963
+ "short_ttl_ms",
9964
+ "delivery_mode",
9965
+ "extra_info",
9966
+ "background_sync"
9967
+ ]);
9944
9968
  var PROTECTED_HEADERS_METHODS = /* @__PURE__ */ new Set([
9945
9969
  "message.send",
9946
9970
  "group.send",
@@ -10358,6 +10382,7 @@ var _AUNClient = class _AUNClient {
10358
10382
  // V2 E2EE 状态
10359
10383
  __publicField(this, "_v2Session");
10360
10384
  __publicField(this, "_v2KeyStore");
10385
+ __publicField(this, "_v2SessionInitInFlight", null);
10361
10386
  __publicField(this, "_v2BootstrapCache", /* @__PURE__ */ new Map());
10362
10387
  __publicField(this, "_v2SenderIKPending", /* @__PURE__ */ new Map());
10363
10388
  __publicField(this, "_v2SenderIKFetching", /* @__PURE__ */ new Set());
@@ -10433,10 +10458,10 @@ var _AUNClient = class _AUNClient {
10433
10458
  */
10434
10459
  __publicField(this, "_v2PullInflight", false);
10435
10460
  __publicField(this, "_v2PullPending", false);
10436
- const inputAid = aid !== null && aid !== void 0 && typeof aid.aunPath === "string" && typeof aid.isPrivateKeyValid === "function" ? aid : null;
10437
- if (typeof aid === "string") {
10438
- throw new ValidationError("AUNClient aid must be an AID object, not a string");
10461
+ if (aid !== null && aid !== void 0 && !isAIDObject(aid)) {
10462
+ throw new ValidationError("AUNClient only accepts an AID object or no argument");
10439
10463
  }
10464
+ const inputAid = aid ?? null;
10440
10465
  const rawConfig = {};
10441
10466
  if (inputAid) rawConfig.aun_path = inputAid.aunPath;
10442
10467
  const _debug = inputAid ? inputAid.debug : false;
@@ -10460,7 +10485,7 @@ var _AUNClient = class _AUNClient {
10460
10485
  this._clientLog.info(`AUNClient initialized: debug=${_debug} aunPath=${this.configModel.aunPath} aid=${initAid ?? "-"}`);
10461
10486
  this._dispatcher = new EventDispatcher();
10462
10487
  this._discovery = new GatewayDiscovery();
10463
- this._keystore = new IndexedDBKeyStore({ encryptionSeed: this.configModel.seedPassword ?? void 0 });
10488
+ this._keystore = new IndexedDBKeyStore({});
10464
10489
  this._slotId = inputAid?.slotId || "default";
10465
10490
  this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: "fanout" });
10466
10491
  this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
@@ -10489,7 +10514,7 @@ var _AUNClient = class _AUNClient {
10489
10514
  this._currentAid = inputAid;
10490
10515
  this._identity = {
10491
10516
  aid: inputAid.aid,
10492
- private_key_pem: inputAid._privateKeyPem ?? "",
10517
+ private_key_pem: inputAid.privateKeyPem,
10493
10518
  public_key_der_b64: inputAid.publicKey,
10494
10519
  cert: inputAid.certPem
10495
10520
  };
@@ -11215,22 +11240,78 @@ var _AUNClient = class _AUNClient {
11215
11240
  get lastErrorCode() {
11216
11241
  return this._lastErrorCode;
11217
11242
  }
11243
+ _applyAidRuntimeContext(aid) {
11244
+ const nextConfig = createConfig({
11245
+ aunPath: aid.aunPath,
11246
+ rootCaPem: aid.rootCaPath,
11247
+ verifySsl: aid.verifySsl
11248
+ });
11249
+ Object.assign(this.configModel, nextConfig);
11250
+ this.config.aun_path = nextConfig.aunPath;
11251
+ this.config.root_ca_path = nextConfig.rootCaPem;
11252
+ this.config.seed_password = nextConfig.seedPassword;
11253
+ this._agentMdPath = this._agentMdDefaultRoot();
11254
+ this._agentMdCache.clear();
11255
+ this._agentMdFetchInflight.clear();
11256
+ this._peerCache.clear();
11257
+ this._certCache.clear();
11258
+ this._gatewayUrl = null;
11259
+ this._deviceId = aid.deviceId || getDeviceId();
11260
+ this._slotId = aid.slotId || "default";
11261
+ this._logger = new AUNLogger({ debug: aid.debug, aunPath: nextConfig.aunPath });
11262
+ this._logger.bindDeviceId(this._deviceId);
11263
+ this._clientLog = this._logger.for("aun_core.client");
11264
+ this._logAuth = this._logger.for("aun_core.auth");
11265
+ this._logTransport = this._logger.for("aun_core.transport");
11266
+ this._logKeystore = this._logger.for("aun_core.keystore");
11267
+ this._logDiscovery = this._logger.for("aun_core.discovery");
11268
+ this._logEvents = this._logger.for("aun_core.events");
11269
+ this._discovery = new GatewayDiscovery();
11270
+ this._keystore = new IndexedDBKeyStore({});
11271
+ this._auth = new AuthFlow({
11272
+ keystore: this._keystore,
11273
+ crypto: new CryptoProvider(),
11274
+ aid: aid.aid,
11275
+ deviceId: this._deviceId,
11276
+ slotId: this._slotId,
11277
+ rootCaPem: nextConfig.rootCaPem,
11278
+ verifySsl: nextConfig.verifySsl
11279
+ });
11280
+ this._transport = new RPCTransport({
11281
+ eventDispatcher: this._dispatcher,
11282
+ timeout: DEFAULT_SESSION_OPTIONS.timeouts.call,
11283
+ onDisconnect: (error, closeCode) => this._handleTransportDisconnect(error, closeCode)
11284
+ });
11285
+ this._transport.setMetaObserver((meta) => {
11286
+ void this._observeRpcMeta(meta).catch((exc) => {
11287
+ this._clientLog.debug(`agent.md meta observer skipped: ${String(exc)}`);
11288
+ });
11289
+ });
11290
+ this._auth.setLogger(this._logAuth);
11291
+ this._transport.setLogger(this._logTransport);
11292
+ this._dispatcher.setLogger(this._logEvents);
11293
+ if (typeof this._discovery.setLogger === "function") {
11294
+ this._discovery.setLogger(this._logDiscovery);
11295
+ }
11296
+ if (typeof this._keystore.setLogger === "function") {
11297
+ this._keystore.setLogger(this._logKeystore);
11298
+ }
11299
+ }
11218
11300
  loadIdentity(aid) {
11219
11301
  if (!aid?.isPrivateKeyValid()) throw new StateError("loadIdentity requires an AID with a valid private key");
11220
11302
  const publicState = this.state;
11221
11303
  if (publicState !== "no_identity" /* NO_IDENTITY */ && publicState !== "closed" /* CLOSED */) {
11222
11304
  throw new StateError(`loadIdentity not allowed in state ${publicState}`);
11223
11305
  }
11306
+ this._applyAidRuntimeContext(aid);
11224
11307
  this._currentAid = aid;
11225
11308
  this._aid = aid.aid;
11226
11309
  this._identity = {
11227
11310
  aid: aid.aid,
11228
- private_key_pem: aid._privateKeyPem ?? "",
11311
+ private_key_pem: aid.privateKeyPem,
11229
11312
  public_key_der_b64: aid.publicKey,
11230
11313
  cert: aid.certPem
11231
11314
  };
11232
- this._auth._aid = aid.aid;
11233
- this._slotId = aid.slotId || "default";
11234
11315
  this._state = "disconnected";
11235
11316
  this._closing = false;
11236
11317
  }
@@ -11274,9 +11355,6 @@ var _AUNClient = class _AUNClient {
11274
11355
  get gatewayUrl() {
11275
11356
  return this._gatewayUrl;
11276
11357
  }
11277
- set gatewayUrl(url) {
11278
- this._gatewayUrl = url;
11279
- }
11280
11358
  get discovery() {
11281
11359
  return this._discovery;
11282
11360
  }
@@ -11317,10 +11395,11 @@ var _AUNClient = class _AUNClient {
11317
11395
  /** 连接到 Gateway;身份来自构造函数或 loadIdentity(aid),认证由 SDK 内部自动完成。 */
11318
11396
  async connect(opts) {
11319
11397
  const tStart = Date.now();
11320
- if (opts !== void 0 && typeof opts === "object") {
11398
+ if (opts !== void 0 && opts !== null && typeof opts === "object") {
11321
11399
  const raw = opts;
11322
- if ("access_token" in raw || "aid" in raw || "token" in raw) {
11323
- throw new ValidationError("connect options must not include access_token/aid; these are managed internally");
11400
+ const invalid = Object.keys(raw).filter((key) => !PUBLIC_CONNECTION_OPTION_KEYS.has(key)).sort();
11401
+ if (invalid.length > 0) {
11402
+ throw new ValidationError(`connect options contain unsupported field(s): ${invalid.join(", ")}`);
11324
11403
  }
11325
11404
  }
11326
11405
  const target = this._currentAid?.aid ?? this._aid ?? "";
@@ -11482,6 +11561,12 @@ var _AUNClient = class _AUNClient {
11482
11561
  }
11483
11562
  this._validateOutboundCall(method, p);
11484
11563
  this._injectMessageCursorContext(method, p);
11564
+ if (method.startsWith("group.") && !("_group_cursor_params" in p) && !Boolean(p._pull_gate_locked)) {
11565
+ const explicitCursorParams = this._groupCursorParams(p);
11566
+ if (Object.keys(explicitCursorParams).length > 0) {
11567
+ p._group_cursor_params = explicitCursorParams;
11568
+ }
11569
+ }
11485
11570
  if (method.startsWith("group.") && p.group_id !== void 0 && p.group_id !== null) {
11486
11571
  const rawGroupId = String(p.group_id);
11487
11572
  const normalizedGroupId = normalizeGroupId(rawGroupId);
@@ -11500,9 +11585,10 @@ var _AUNClient = class _AUNClient {
11500
11585
  const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
11501
11586
  delete p.encrypt;
11502
11587
  if (encrypt) {
11503
- if (!this._v2Session) {
11504
- throw new StateError("V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)");
11505
- }
11588
+ await this._ensureV2SessionReady(
11589
+ "message.send",
11590
+ "V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)"
11591
+ );
11506
11592
  this._clientLog.debug("call route: message.send \u2192 V2 encrypted send");
11507
11593
  return await this._sendV2(String(p.to ?? ""), p.payload ?? {}, {
11508
11594
  messageId: String(p.message_id ?? "") || void 0,
@@ -11517,9 +11603,10 @@ var _AUNClient = class _AUNClient {
11517
11603
  const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
11518
11604
  delete p.encrypt;
11519
11605
  if (encrypt) {
11520
- if (!this._v2Session) {
11521
- throw new StateError("V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)");
11522
- }
11606
+ await this._ensureV2SessionReady(
11607
+ "group.send",
11608
+ "V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)"
11609
+ );
11523
11610
  this._clientLog.debug("call route: group.send \u2192 V2 encrypted send");
11524
11611
  return await this._sendGroupV2(String(p.group_id ?? ""), p.payload ?? {}, {
11525
11612
  messageId: String(p.message_id ?? "") || void 0,
@@ -11534,9 +11621,10 @@ var _AUNClient = class _AUNClient {
11534
11621
  const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
11535
11622
  delete p.encrypt;
11536
11623
  if (encrypt) {
11537
- if (!this._v2Session) {
11538
- throw new StateError("V2 session not initialized; encrypted group.thought.put requires V2 (V1 E2EE removed)");
11539
- }
11624
+ await this._ensureV2SessionReady(
11625
+ "group.thought.put",
11626
+ "V2 session not initialized; encrypted group.thought.put requires V2 (V1 E2EE removed)"
11627
+ );
11540
11628
  this._clientLog.debug("call route: group.thought.put \u2192 V2 encrypted put");
11541
11629
  return this._putGroupThoughtEncryptedV2(p);
11542
11630
  }
@@ -11545,9 +11633,10 @@ var _AUNClient = class _AUNClient {
11545
11633
  const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
11546
11634
  delete p.encrypt;
11547
11635
  if (encrypt) {
11548
- if (!this._v2Session) {
11549
- throw new StateError("V2 session not initialized; encrypted message.thought.put requires V2 (V1 E2EE removed)");
11550
- }
11636
+ await this._ensureV2SessionReady(
11637
+ "message.thought.put",
11638
+ "V2 session not initialized; encrypted message.thought.put requires V2 (V1 E2EE removed)"
11639
+ );
11551
11640
  this._clientLog.debug("call route: message.thought.put \u2192 V2 encrypted put");
11552
11641
  return this._putMessageThoughtEncryptedV2(p);
11553
11642
  }
@@ -11565,26 +11654,43 @@ var _AUNClient = class _AUNClient {
11565
11654
  * 拆分出来以便 pull gate 包裹整个操作。
11566
11655
  */
11567
11656
  async _callImplInner(method, p) {
11568
- if (method === "message.pull" && this._v2Session) {
11657
+ if (method === "message.pull") {
11658
+ await this._ensureV2SessionReady("message.pull");
11569
11659
  this._clientLog.debug("call route: message.pull \u2192 V2 pull");
11570
11660
  const messages = await this._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { force: p.force === true });
11571
11661
  return { messages };
11572
11662
  }
11573
- if (method === "message.ack" && this._v2Session) {
11663
+ if (method === "message.ack") {
11664
+ await this._ensureV2SessionReady("message.ack");
11574
11665
  this._clientLog.debug("call route: message.ack \u2192 V2 ack");
11575
11666
  return await this._ackV2(Number(p.seq ?? p.up_to_seq ?? 0) || void 0);
11576
11667
  }
11577
- if (method === "group.pull" && this._v2Session && p.group_id) {
11668
+ if (method === "group.pull" && p.group_id) {
11669
+ await this._ensureV2SessionReady("group.pull");
11578
11670
  this._clientLog.debug("call route: group.pull \u2192 V2 pull");
11671
+ const hasExplicitAfterSeq = "after_seq" in p || "after_message_seq" in p;
11672
+ const cursorParams = this._explicitGroupCursorParams(p);
11673
+ const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
11674
+ const pullOpts = {};
11675
+ if (hasExplicitAfterSeq) pullOpts.explicitAfterSeq = true;
11676
+ if (Object.keys(cursorParams).length > 0) pullOpts.cursorParams = cursorParams;
11677
+ if (!ownsCursor) pullOpts.ownsCursor = false;
11579
11678
  const messages = await this._pullGroupV2(
11580
11679
  String(p.group_id),
11581
11680
  Number(p.after_seq ?? p.after_message_seq ?? 0) || 0,
11582
- Number(p.limit ?? 50) || 50
11681
+ Number(p.limit ?? 50) || 50,
11682
+ Object.keys(pullOpts).length > 0 ? pullOpts : void 0
11583
11683
  );
11584
11684
  return { messages };
11585
11685
  }
11586
- if (method === "group.ack_messages" && this._v2Session && p.group_id) {
11686
+ if (method === "group.ack_messages" && p.group_id) {
11687
+ await this._ensureV2SessionReady("group.ack_messages");
11587
11688
  this._clientLog.debug("call route: group.ack_messages \u2192 V2 ack");
11689
+ const cursorParams = this._explicitGroupCursorParams(p);
11690
+ const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
11691
+ if (!ownsCursor) {
11692
+ return await this._rawGroupAckMessages(p);
11693
+ }
11588
11694
  return await this._ackGroupV2(
11589
11695
  String(p.group_id),
11590
11696
  Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || void 0
@@ -11685,6 +11791,7 @@ var _AUNClient = class _AUNClient {
11685
11791
  delete p._pull_gate_locked;
11686
11792
  delete p._skip_auto_ack;
11687
11793
  delete p.skip_auto_ack;
11794
+ delete p._group_cursor_params;
11688
11795
  if (method.startsWith("group.") && p.group_id !== void 0 && p.group_id !== null) {
11689
11796
  p.group_id = normalizeGroupId(String(p.group_id)) || String(p.group_id);
11690
11797
  }
@@ -12014,54 +12121,27 @@ var _AUNClient = class _AUNClient {
12014
12121
  /** 后台补齐群消息空洞 */
12015
12122
  async _fillGroupGap(groupId) {
12016
12123
  if (this._state !== "connected" || this._closing) return;
12124
+ groupId = normalizeGroupId(groupId) || String(groupId ?? "").trim();
12125
+ if (!groupId) return;
12017
12126
  const ns = `group:${groupId}`;
12018
12127
  const afterSeq = this._seqTracker.getContiguousSeq(ns);
12019
12128
  const dedupKey = `group_pull:${ns}`;
12020
12129
  if (this._gapFillDone.has(dedupKey)) return;
12021
12130
  this._gapFillDone.add(dedupKey);
12022
12131
  this._gapFillActive = true;
12132
+ let filled = 0;
12023
12133
  try {
12024
- const result = await this.call("group.pull", {
12025
- group_id: groupId,
12026
- after_message_seq: afterSeq,
12027
- device_id: this._deviceId,
12028
- limit: 50
12029
- });
12030
- if (isJsonObject(result)) {
12031
- const messages = result.messages;
12032
- if (Array.isArray(messages)) {
12033
- const pushed = this._pushedSeqs.get(ns);
12034
- for (const msg of messages) {
12035
- if (isJsonObject(msg)) {
12036
- const s = msg.seq;
12037
- if (pushed && s !== void 0 && s !== null && pushed.has(s)) continue;
12038
- if (s !== void 0 && s !== null) {
12039
- await this._publishPulledMessage("group.message_created", ns, s, msg);
12040
- } else {
12041
- await this._publishAppEvent("group.message_created", msg);
12042
- }
12043
- }
12044
- }
12045
- this._prunePushedSeqs(ns);
12046
- const contig = this._seqTracker.getContiguousSeq(ns);
12047
- if (contig > 0) {
12048
- const gid = groupId;
12049
- this._transport.call("group.ack_messages", {
12050
- group_id: gid,
12051
- msg_seq: contig,
12052
- device_id: this._deviceId,
12053
- slot_id: this._slotId
12054
- }).catch((e) => {
12055
- this._clientLog.warn(`group gap-fill auto-ack failed: group=${gid}`, e);
12056
- });
12057
- }
12058
- }
12059
- }
12134
+ const messages = await this._pullGroupV2(groupId, afterSeq, 50);
12135
+ filled = messages.length;
12136
+ this._prunePushedSeqs(ns);
12060
12137
  } catch (exc) {
12061
12138
  this._clientLog.warn(`group message gap-fill failed:${String(exc)}`);
12062
12139
  } finally {
12063
12140
  this._gapFillDone.delete(dedupKey);
12064
12141
  this._gapFillActive = false;
12142
+ if (filled > 0 && this._seqTracker.getContiguousSeq(ns) > afterSeq) {
12143
+ this._safeAsync(this._fillGroupGap(groupId));
12144
+ }
12065
12145
  }
12066
12146
  }
12067
12147
  /** 后台补齐群事件空洞 */
@@ -12159,44 +12239,19 @@ var _AUNClient = class _AUNClient {
12159
12239
  if (this._gapFillDone.has(dedupKey)) return;
12160
12240
  this._gapFillDone.add(dedupKey);
12161
12241
  this._gapFillActive = true;
12242
+ let filled = 0;
12162
12243
  try {
12163
- const result = await this.call("message.pull", {
12164
- after_seq: afterSeq,
12165
- limit: 50
12166
- });
12167
- if (isJsonObject(result)) {
12168
- const messages = result.messages;
12169
- if (Array.isArray(messages)) {
12170
- const pushed = this._pushedSeqs.get(ns);
12171
- for (const msg of messages) {
12172
- if (isJsonObject(msg)) {
12173
- const s = msg.seq;
12174
- if (pushed && s !== void 0 && s !== null && pushed.has(s)) continue;
12175
- if (s !== void 0 && s !== null) {
12176
- await this._publishPulledMessage("message.received", ns, s, msg);
12177
- } else {
12178
- await this._publishAppEvent("message.received", msg);
12179
- }
12180
- }
12181
- }
12182
- this._prunePushedSeqs(ns);
12183
- const contig = this._seqTracker.getContiguousSeq(ns);
12184
- if (contig > 0) {
12185
- this._transport.call("message.ack", {
12186
- seq: contig,
12187
- device_id: this._deviceId,
12188
- slot_id: this._slotId
12189
- }).catch((e) => {
12190
- this._clientLog.warn(`P2P gap-fill auto-ack failed:${String(e)}`);
12191
- });
12192
- }
12193
- }
12194
- }
12244
+ const messages = await this._pullV2(afterSeq, 50);
12245
+ filled = messages.length;
12246
+ this._prunePushedSeqs(ns);
12195
12247
  } catch (exc) {
12196
12248
  this._clientLog.warn(`P2P message gap-fill failed:${String(exc)}`);
12197
12249
  } finally {
12198
12250
  this._gapFillDone.delete(dedupKey);
12199
12251
  this._gapFillActive = false;
12252
+ if (filled > 0 && this._seqTracker.getContiguousSeq(ns) > afterSeq) {
12253
+ this._safeAsync(this._fillP2pGap());
12254
+ }
12200
12255
  }
12201
12256
  }
12202
12257
  /** 只按硬上限裁剪 published guard,不能按 contiguousSeq 清理。 */
@@ -13039,10 +13094,10 @@ var _AUNClient = class _AUNClient {
13039
13094
  * 使用 SubtleCrypto 异步签名。
13040
13095
  */
13041
13096
  async _signClientOperation(method, params) {
13042
- const identity = this._identity;
13043
- if (!identity || !identity.private_key_pem) return;
13097
+ const currentAid = this._currentAid;
13098
+ if (!currentAid?.privateKeyPem) return;
13044
13099
  try {
13045
- const aid = identity.aid ?? "";
13100
+ const aid = currentAid.aid;
13046
13101
  const ts = String(Math.floor(Date.now() / 1e3));
13047
13102
  const paramsForHash = {};
13048
13103
  for (const [k, v] of Object.entries(params)) {
@@ -13057,7 +13112,7 @@ var _AUNClient = class _AUNClient {
13057
13112
  );
13058
13113
  const paramsHash = Array.from(new Uint8Array(paramsHashBuf)).map((b) => b.toString(16).padStart(2, "0")).join("");
13059
13114
  const signData = new TextEncoder().encode(`${method}|${aid}|${ts}|${paramsHash}`);
13060
- const pkcs8 = pemToArrayBuffer(identity.private_key_pem);
13115
+ const pkcs8 = pemToArrayBuffer(currentAid.privateKeyPem);
13061
13116
  const cryptoKey = await crypto.subtle.importKey(
13062
13117
  "pkcs8",
13063
13118
  pkcs8,
@@ -13072,7 +13127,7 @@ var _AUNClient = class _AUNClient {
13072
13127
  );
13073
13128
  const sigDer = p1363ToDer(new Uint8Array(sigP1363));
13074
13129
  let certFingerprint = "";
13075
- const certPem = identity.cert ?? "";
13130
+ const certPem = currentAid.certPem;
13076
13131
  if (certPem) {
13077
13132
  const certDer = pemToArrayBuffer(certPem);
13078
13133
  const fpBuf = await crypto.subtle.digest("SHA-256", certDer);
@@ -13172,12 +13227,20 @@ var _AUNClient = class _AUNClient {
13172
13227
  await this._restoreSeqTrackerState();
13173
13228
  }
13174
13229
  this._startBackgroundTasks();
13175
- try {
13176
- await this._initV2Session();
13177
- } catch (exc) {
13178
- this._clientLog.warn(`V2 session init failed (non-fatal): ${String(exc)}`);
13230
+ const connectionKind = String(params.connection_kind ?? "long");
13231
+ const isShortConnection = connectionKind === "short";
13232
+ if (!isShortConnection) {
13233
+ try {
13234
+ await this._initV2Session();
13235
+ } catch (exc) {
13236
+ this._clientLog.warn(`V2 session init failed (non-fatal): ${String(exc)}`);
13237
+ }
13238
+ } else {
13239
+ this._clientLog.debug("V2 session init deferred for short connection");
13179
13240
  }
13180
- if (this._sessionOptions?.background_sync !== false) {
13241
+ const hasExplicitBackgroundSync = Object.prototype.hasOwnProperty.call(params, "background_sync");
13242
+ const backgroundSyncEnabled = this._sessionOptions?.background_sync !== false && (!isShortConnection || hasExplicitBackgroundSync);
13243
+ if (backgroundSyncEnabled) {
13181
13244
  this._safeAsync(this._fillP2pGap());
13182
13245
  }
13183
13246
  this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms aid=${this._aid ?? "-"}`);
@@ -14056,6 +14119,19 @@ var _AUNClient = class _AUNClient {
14056
14119
  );
14057
14120
  return repaired;
14058
14121
  }
14122
+ async _ensureV2SessionReady(method, errorMessage) {
14123
+ if (!this._v2Session) {
14124
+ if (!this._v2SessionInitInFlight) {
14125
+ this._v2SessionInitInFlight = this._initV2Session().finally(() => {
14126
+ this._v2SessionInitInFlight = null;
14127
+ });
14128
+ }
14129
+ await this._v2SessionInitInFlight;
14130
+ }
14131
+ if (!this._v2Session) {
14132
+ throw new StateError(errorMessage ?? `V2 session not initialized; encrypted ${method} requires E2EE V2`);
14133
+ }
14134
+ }
14059
14135
  // ── V2 E2EE API(async,与 Python `client.py` `_init_v2_session` / `send_v2` / `pull_v2` / `ack_v2` 对齐) ──
14060
14136
  /**
14061
14137
  * 初始化 V2 session:从 AID PEM 私钥提取 raw scalar + DER 公钥,
@@ -14065,31 +14141,13 @@ var _AUNClient = class _AUNClient {
14065
14141
  */
14066
14142
  async _initV2Session() {
14067
14143
  if (!this._aid) return;
14068
- let identity = this._identity;
14069
- if (!identity?.private_key_pem) {
14070
- try {
14071
- const reloaded = await this._keystore.loadIdentity(this._aid);
14072
- if (reloaded?.private_key_pem) {
14073
- this._identity = reloaded;
14074
- identity = reloaded;
14075
- this._clientLog.warn("V2 session init: identity cache was stale, reloaded from keystore");
14076
- try {
14077
- const persistIdentity = this._auth._persistIdentity;
14078
- if (typeof persistIdentity === "function") {
14079
- await persistIdentity.call(this._auth, reloaded);
14080
- }
14081
- } catch {
14082
- }
14083
- }
14084
- } catch {
14085
- }
14086
- }
14087
- if (!identity?.private_key_pem) {
14144
+ const currentAid = this._currentAid;
14145
+ if (!currentAid?.privateKeyPem) {
14088
14146
  this._clientLog.warn("V2 session init skipped: no AID private key");
14089
14147
  return;
14090
14148
  }
14091
14149
  if (this._v2Session) return;
14092
- const pem = String(identity.private_key_pem).trim();
14150
+ const pem = currentAid.privateKeyPem.trim();
14093
14151
  const pemBody = pem.replace(/-----BEGIN [^-]+-----/g, "").replace(/-----END [^-]+-----/g, "").replace(/\s+/g, "");
14094
14152
  const pkcs8Der = _v2B64ToBytes(pemBody);
14095
14153
  const privKey = await crypto.subtle.importKey(
@@ -14461,6 +14519,7 @@ var _AUNClient = class _AUNClient {
14461
14519
  decrypted.push(plaintext);
14462
14520
  }
14463
14521
  }
14522
+ const hasServerAckSeq = Object.prototype.hasOwnProperty.call(result, "server_ack_seq");
14464
14523
  const serverAckSeq = Number(result.server_ack_seq ?? 0);
14465
14524
  if (ns && Number.isFinite(serverAckSeq) && serverAckSeq > 0) {
14466
14525
  const contig = this._seqTracker.getContiguousSeq(ns);
@@ -14476,7 +14535,8 @@ var _AUNClient = class _AUNClient {
14476
14535
  await this._drainOrderedMessages(ns);
14477
14536
  this._saveSeqTrackerState();
14478
14537
  }
14479
- if (messages.length > 0 && contigAdvanced && ackSeq > 0) {
14538
+ const ackNeeded = messages.length > 0 && ackSeq > 0 && (contigAdvanced || hasServerAckSeq && ackSeq > serverAckSeq);
14539
+ if (ackNeeded) {
14480
14540
  this._safeAsync(this._ackV2(ackSeq).then(() => void 0));
14481
14541
  }
14482
14542
  }
@@ -14505,7 +14565,7 @@ var _AUNClient = class _AUNClient {
14505
14565
  seq = maxSeen;
14506
14566
  }
14507
14567
  }
14508
- const raw = await this.call("message.v2.ack", { up_to_seq: seq });
14568
+ const raw = await this._callRawV2Rpc("message.v2.ack", { up_to_seq: seq });
14509
14569
  const result = isJsonObject(raw) ? { ...raw } : { result: raw };
14510
14570
  let actualAckSeq = seq;
14511
14571
  if ("effective_ack_seq" in result) actualAckSeq = Number(result.effective_ack_seq ?? 0);
@@ -14775,7 +14835,7 @@ var _AUNClient = class _AUNClient {
14775
14835
  * @param afterSeq 从此 seq 之后开始拉取(0/省略 = 从当前 contiguous 开始)
14776
14836
  * @param limit 最多拉取条数
14777
14837
  */
14778
- async _pullGroupV2(groupId, afterSeq = 0, limit = 50) {
14838
+ async _pullGroupV2(groupId, afterSeq = 0, limit = 50, opts) {
14779
14839
  if (!this._v2Session) {
14780
14840
  throw new StateError("V2 session not initialized (not connected?)");
14781
14841
  }
@@ -14783,15 +14843,18 @@ var _AUNClient = class _AUNClient {
14783
14843
  if (!gid) throw new ValidationError("group.pull requires group_id");
14784
14844
  const ns = `group:${gid}`;
14785
14845
  const decrypted = [];
14786
- let nextAfterSeq = afterSeq || this._seqTracker.getContiguousSeq(ns);
14846
+ const cursorParams = opts?.cursorParams ?? {};
14847
+ const ownsCursor = opts?.ownsCursor !== false;
14848
+ let nextAfterSeq = opts?.explicitAfterSeq ? afterSeq : afterSeq || this._seqTracker.getContiguousSeq(ns);
14787
14849
  let pageCount = 0;
14788
14850
  const maxPages = 100;
14789
14851
  while (pageCount < maxPages) {
14790
14852
  pageCount += 1;
14791
- const result = await this.call("group.v2.pull", {
14853
+ const result = await this._callRawV2Rpc("group.v2.pull", {
14792
14854
  group_id: gid,
14793
14855
  after_seq: nextAfterSeq,
14794
- limit
14856
+ limit,
14857
+ ...cursorParams
14795
14858
  });
14796
14859
  const messages = Array.isArray(result?.messages) ? result.messages : [];
14797
14860
  const seqs = messages.map((msg) => Number(msg.seq ?? 0)).filter((seq) => Number.isFinite(seq) && seq > 0);
@@ -14853,6 +14916,7 @@ var _AUNClient = class _AUNClient {
14853
14916
  decrypted.push(plaintext);
14854
14917
  }
14855
14918
  const cursor = isJsonObject(result.cursor) ? result.cursor : null;
14919
+ const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, "current_seq");
14856
14920
  const serverAckSeq = Number(cursor?.current_seq ?? 0);
14857
14921
  if (Number.isFinite(serverAckSeq) && serverAckSeq > 0) {
14858
14922
  const contig = this._seqTracker.getContiguousSeq(ns);
@@ -14867,10 +14931,12 @@ var _AUNClient = class _AUNClient {
14867
14931
  await this._drainOrderedMessages(ns);
14868
14932
  this._saveSeqTrackerState();
14869
14933
  }
14870
- if (messages.length > 0 && contigAdvanced && ackSeq > 0) {
14934
+ const ackNeeded = messages.length > 0 && ackSeq > 0 && ownsCursor && (contigAdvanced || hasServerCursor && ackSeq > serverAckSeq);
14935
+ if (ackNeeded) {
14871
14936
  this._safeAsync(this._ackGroupV2(gid, ackSeq).then(() => void 0));
14872
14937
  }
14873
14938
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
14939
+ if (!ownsCursor) break;
14874
14940
  if (messages.length === 0 || nextAfter <= nextAfterSeq || result.has_more === false) break;
14875
14941
  nextAfterSeq = nextAfter;
14876
14942
  }
@@ -14879,6 +14945,28 @@ var _AUNClient = class _AUNClient {
14879
14945
  }
14880
14946
  return decrypted;
14881
14947
  }
14948
+ _groupCursorParams(params) {
14949
+ const cursorParams = {};
14950
+ for (const key of ["device_id", "slot_id", "device_name", "device_type"]) {
14951
+ const value = params[key];
14952
+ if (value !== void 0 && value !== null) cursorParams[key] = value;
14953
+ }
14954
+ return cursorParams;
14955
+ }
14956
+ _explicitGroupCursorParams(params) {
14957
+ const value = params._group_cursor_params;
14958
+ if (!isJsonObject(value)) return {};
14959
+ return { ...value };
14960
+ }
14961
+ _groupCursorTargetsCurrentInstance(params) {
14962
+ const deviceId = String(params.device_id ?? "").trim();
14963
+ const slotId = String(params.slot_id ?? "").trim();
14964
+ return (!deviceId || deviceId === (this._deviceId ?? "")) && (!slotId || slotId === (this._slotId ?? ""));
14965
+ }
14966
+ async _rawGroupAckMessages(params) {
14967
+ const p = { ...params };
14968
+ return await this._callRawV2Rpc("group.ack_messages", p);
14969
+ }
14882
14970
  /**
14883
14971
  * 确认 V2 群消息已消费。
14884
14972
  *
@@ -14896,7 +14984,7 @@ var _AUNClient = class _AUNClient {
14896
14984
  this._clientLog.warn(`ackGroupV2 clamp: group=${gid} up_to_seq=${seq} > max_seen=${maxSeen}, clamp`);
14897
14985
  seq = maxSeen;
14898
14986
  }
14899
- return this.call("group.v2.ack", { group_id: gid, up_to_seq: seq });
14987
+ return this._callRawV2Rpc("group.v2.ack", { group_id: gid, up_to_seq: seq });
14900
14988
  }
14901
14989
  // ── V2 thought(per-device wrap,服务端透传,不持久化)──────────
14902
14990
  /**
@@ -15655,8 +15743,8 @@ var _AUNClient = class _AUNClient {
15655
15743
  return;
15656
15744
  }
15657
15745
  let signature = "";
15658
- const identity = this._identity;
15659
- if (identity?.private_key_pem) {
15746
+ const currentAid = this._currentAid;
15747
+ if (currentAid?.privateKeyPem) {
15660
15748
  try {
15661
15749
  const signPayloadObj = {
15662
15750
  group_id: groupId,
@@ -15666,7 +15754,7 @@ var _AUNClient = class _AUNClient {
15666
15754
  };
15667
15755
  const signPayload = stableStringify(signPayloadObj);
15668
15756
  const signPayloadBytes = new TextEncoder().encode(signPayload);
15669
- const privKey = await importPrivateKeyEcdsa(identity.private_key_pem);
15757
+ const privKey = await importPrivateKeyEcdsa(currentAid.privateKeyPem);
15670
15758
  const sigBytes = await ecdsaSignDer(privKey, signPayloadBytes);
15671
15759
  signature = uint8ToBase64(sigBytes);
15672
15760
  } catch (sigExc) {
@@ -15840,7 +15928,7 @@ var _AUNClient = class _AUNClient {
15840
15928
  if (newContig > 0 && newContig !== contigBefore) {
15841
15929
  const maxSeen = this._seqTracker.getMaxSeenSeq(ns);
15842
15930
  const ackSeq = maxSeen > 0 ? Math.min(newContig, maxSeen) : newContig;
15843
- this.call("message.v2.ack", { up_to_seq: ackSeq }).catch((e) => this._clientLog.debug(`V2 P2P push-ack failed: ${e}`));
15931
+ this._callRawV2Rpc("message.v2.ack", { up_to_seq: ackSeq }).catch((e) => this._clientLog.debug(`V2 P2P push-ack failed: ${e}`));
15844
15932
  }
15845
15933
  this._clientLog.debug(
15846
15934
  `_onV2PushNotification: push \u5E26 payload \u89E3\u5BC6\u6210\u529F, contiguous_seq=${contigBefore}->${newContig} push_seq=${pushSeq}`