@agentunion/fastaun-browser 0.3.3 → 0.3.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 (66) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/_packed_docs/CHANGELOG.md +26 -0
  3. package/_packed_docs/INDEX.md +81 -0
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +55 -0
  5. 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 +328 -0
  6. package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +686 -0
  7. 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 -0
  8. 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 -0
  9. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +5 -5
  10. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +1 -1
  11. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +2 -2
  12. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +454 -429
  13. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1410 -1398
  14. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +19 -1
  15. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +20 -5
  16. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +8 -8
  17. 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 -0
  18. package/_packed_docs/sdk/INDEX.md +22 -22
  19. package/_packed_docs/sdk/README.md +3 -3
  20. package/dist/auth.d.ts +10 -11
  21. package/dist/auth.d.ts.map +1 -1
  22. package/dist/auth.js +127 -91
  23. package/dist/auth.js.map +1 -1
  24. package/dist/bundle.js +625 -274
  25. package/dist/client.d.ts +19 -10
  26. package/dist/client.d.ts.map +1 -1
  27. package/dist/client.js +238 -111
  28. package/dist/client.js.map +1 -1
  29. package/dist/errors.d.ts +4 -0
  30. package/dist/errors.d.ts.map +1 -1
  31. package/dist/errors.js +7 -0
  32. package/dist/errors.js.map +1 -1
  33. package/dist/index.d.ts +3 -3
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +3 -3
  36. package/dist/index.js.map +1 -1
  37. package/dist/keystore/index.d.ts +5 -0
  38. package/dist/keystore/index.d.ts.map +1 -1
  39. package/dist/keystore/indexeddb.d.ts +12 -0
  40. package/dist/keystore/indexeddb.d.ts.map +1 -1
  41. package/dist/keystore/indexeddb.js +64 -6
  42. package/dist/keystore/indexeddb.js.map +1 -1
  43. package/dist/namespaces/auth.d.ts +3 -3
  44. package/dist/namespaces/auth.d.ts.map +1 -1
  45. package/dist/namespaces/auth.js +39 -20
  46. package/dist/namespaces/auth.js.map +1 -1
  47. package/dist/secret-store/indexeddb-store.js +1 -1
  48. package/dist/secret-store/indexeddb-store.js.map +1 -1
  49. package/dist/transport.d.ts +9 -1
  50. package/dist/transport.d.ts.map +1 -1
  51. package/dist/transport.js +158 -64
  52. package/dist/transport.js.map +1 -1
  53. package/dist/v2/e2ee/decrypt.js +1 -1
  54. package/dist/v2/e2ee/decrypt.js.map +1 -1
  55. package/dist/v2/e2ee/encrypt-p2p.d.ts.map +1 -1
  56. package/dist/v2/e2ee/encrypt-p2p.js +3 -2
  57. package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
  58. package/dist/v2/session/session.d.ts +1 -0
  59. package/dist/v2/session/session.d.ts.map +1 -1
  60. package/dist/v2/session/session.js +7 -1
  61. package/dist/v2/session/session.js.map +1 -1
  62. package/package.json +43 -43
  63. package/dist/e2ee-group.d.ts +0 -276
  64. package/dist/e2ee-group.d.ts.map +0 -1
  65. package/dist/e2ee-group.js +0 -1653
  66. package/dist/e2ee-group.js.map +0 -1
package/dist/bundle.js CHANGED
@@ -346,7 +346,7 @@ var init_indexeddb_store = __esm({
346
346
  return this._masterKeyPromise;
347
347
  }
348
348
  async _initMasterKey() {
349
- if (this._encryptionSeed) {
349
+ if (this._encryptionSeed !== void 0) {
350
350
  return this._deriveKeyFromSeed(this._encryptionSeed);
351
351
  }
352
352
  const storedSeed = await secretGet(STORE_MASTER, "seed");
@@ -495,6 +495,12 @@ var AuthError = class extends AUNError {
495
495
  this.name = "AuthError";
496
496
  }
497
497
  };
498
+ var IdentityConflictError = class extends AuthError {
499
+ constructor(message, opts) {
500
+ super(message, opts);
501
+ this.name = "IdentityConflictError";
502
+ }
503
+ };
498
504
  var PermissionError = class extends AUNError {
499
505
  constructor(message, opts) {
500
506
  super(message, opts);
@@ -970,6 +976,8 @@ var GatewayDiscovery = class {
970
976
 
971
977
  // src/transport.ts
972
978
  var MAX_WS_PAYLOAD_SIZE = 1e6;
979
+ var MAX_RPC_INFLIGHT = 16;
980
+ var MAX_BACKGROUND_RPC_INFLIGHT = 8;
973
981
  var _noopLog3 = { error: () => {
974
982
  }, warn: () => {
975
983
  }, info: () => {
@@ -1226,6 +1234,10 @@ var RPCTransport = class {
1226
1234
  __publicField(this, "_closed", true);
1227
1235
  __publicField(this, "_challenge", null);
1228
1236
  __publicField(this, "_pending", /* @__PURE__ */ new Map());
1237
+ __publicField(this, "_pendingBackground", /* @__PURE__ */ new Set());
1238
+ __publicField(this, "_rpcQueue", []);
1239
+ __publicField(this, "_backgroundRpcQueue", []);
1240
+ __publicField(this, "_drainingRpcQueue", false);
1229
1241
  // Gateway 在 RPC envelope 注入 _meta 字段(与 result 同级),由 client 层 observer 接收。
1230
1242
  // 注入失败 / 字段缺失时 observer 不会被调用,不影响业务路径。
1231
1243
  __publicField(this, "_metaObserver", null);
@@ -1329,7 +1341,8 @@ var RPCTransport = class {
1329
1341
  async close() {
1330
1342
  const tStart = Date.now();
1331
1343
  const pendingCount = this._pending.size;
1332
- this._log.debug(`close enter: pending_rpc=${pendingCount}`);
1344
+ const queuedCount = this._rpcQueue.length + this._backgroundRpcQueue.length;
1345
+ this._log.debug(`close enter: pending_rpc=${pendingCount}, queued_rpc=${queuedCount}, background_pending=${this._pendingBackground.size}`);
1333
1346
  this._closed = true;
1334
1347
  if (this._ws) {
1335
1348
  try {
@@ -1343,16 +1356,28 @@ var RPCTransport = class {
1343
1356
  this._ws = null;
1344
1357
  }
1345
1358
  for (const [, pending] of this._pending) {
1359
+ clearTimeout(pending.timer);
1346
1360
  pending.reject(new ConnectionError("transport closed"));
1347
1361
  }
1348
1362
  this._pending.clear();
1349
- this._log.debug(`close exit: elapsed=${Date.now() - tStart}ms`);
1363
+ this._pendingBackground.clear();
1364
+ for (const queued of this._rpcQueue) {
1365
+ clearTimeout(queued.pending.timer);
1366
+ queued.pending.reject(new ConnectionError("transport closed"));
1367
+ }
1368
+ this._rpcQueue = [];
1369
+ for (const queued of this._backgroundRpcQueue) {
1370
+ clearTimeout(queued.pending.timer);
1371
+ queued.pending.reject(new ConnectionError("transport closed"));
1372
+ }
1373
+ this._backgroundRpcQueue = [];
1374
+ this._log.debug(`close exit: elapsed=${Date.now() - tStart}ms, cancelled ${pendingCount} pending, ${queuedCount} queued`);
1350
1375
  }
1351
1376
  /**
1352
1377
  * 发起 JSON-RPC 2.0 调用。
1353
1378
  * 返回 result 字段的值;若有 error 字段则抛出映射后的错误。
1354
1379
  */
1355
- async call(method, params, timeout, trace) {
1380
+ async call(method, params, timeout, trace, background = false) {
1356
1381
  if (this._closed || !this._ws) {
1357
1382
  throw new ConnectionError("transport not connected");
1358
1383
  }
@@ -1361,20 +1386,19 @@ var RPCTransport = class {
1361
1386
  const tStart = Date.now();
1362
1387
  const effectiveTraceMode = trace === "off" || trace === "log" || trace === "diag" ? trace : this._traceMode;
1363
1388
  let traceId = "";
1364
- let sendParams = params ?? {};
1389
+ const sendParams = { ...params ?? {} };
1390
+ const localParams = sendParams;
1391
+ const backgroundRpc = background || localParams._rpc_background === true;
1392
+ delete localParams._rpc_background;
1365
1393
  if (effectiveTraceMode !== "off") {
1366
1394
  traceId = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID().replace(/-/g, "") : Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join("");
1367
- sendParams = { ...params ?? {} };
1368
1395
  const tracePayload = { trace_id: traceId, mode: effectiveTraceMode };
1369
1396
  if (effectiveTraceMode === "diag") {
1370
1397
  tracePayload.spans = [{ node: "sdk", ts: tStart, action: "send" }];
1371
1398
  }
1372
- sendParams._trace = tracePayload;
1399
+ localParams._trace = tracePayload;
1373
1400
  this._log.info(`[trace=${traceId}] rpc_send method=${method} rpc_id=${rpcId}`);
1374
1401
  }
1375
- const promise = new Promise((resolve, reject) => {
1376
- this._pending.set(rpcId, { resolve, reject });
1377
- });
1378
1402
  const payload = JSON.stringify({
1379
1403
  jsonrpc: "2.0",
1380
1404
  id: rpcId,
@@ -1383,65 +1407,136 @@ var RPCTransport = class {
1383
1407
  });
1384
1408
  const payloadSize = new TextEncoder().encode(payload).length;
1385
1409
  if (payloadSize > MAX_WS_PAYLOAD_SIZE) {
1386
- this._pending.delete(rpcId);
1387
1410
  throw new ValidationError("payload is too large");
1388
1411
  }
1389
- try {
1390
- this._ws.send(payload);
1391
- this._log.debug(`RPC request sent: method=${method}, id=${rpcId} ${summarizeDict(sendParams, DIAG_PARAM_FIELDS)}`);
1392
- } catch (exc) {
1393
- this._pending.delete(rpcId);
1394
- this._log.error(`RPC send failed: method=${method}, id=${rpcId}, error=${String(exc)}`, exc instanceof Error ? exc : void 0);
1395
- throw new ConnectionError(`failed to send rpc ${method}: ${exc}`);
1396
- }
1397
- let timeoutHandle = null;
1398
- const timeoutPromise = new Promise((_, reject) => {
1399
- timeoutHandle = globalThis.setTimeout(() => {
1400
- this._pending.delete(rpcId);
1412
+ return new Promise((resolve, reject) => {
1413
+ const timer = globalThis.setTimeout(() => {
1414
+ this._removeRpc(rpcId, pending);
1401
1415
  this._log.warn(`RPC timeout: method=${method}, id=${rpcId}, elapsed=${Date.now() - tStart}ms, timeout=${effectiveTimeout}ms`);
1402
1416
  reject(new TimeoutError(`rpc timeout: ${method}`, { retryable: true }));
1417
+ this._drainRpcQueue();
1403
1418
  }, effectiveTimeout);
1419
+ const pending = {
1420
+ resolve: (response) => {
1421
+ clearTimeout(timer);
1422
+ const elapsed = Date.now() - tStart;
1423
+ if (response.error !== void 0) {
1424
+ this._log.debug(`RPC error response: method=${method}, id=${rpcId}, elapsed=${elapsed}ms, error=${JSON.stringify(response.error)}`);
1425
+ if (traceId) {
1426
+ this._log.info(`[trace=${traceId}] rpc_recv method=${method} rpc_id=${rpcId} duration_ms=${elapsed} status=error`);
1427
+ }
1428
+ const respTrace = response._trace;
1429
+ if (respTrace && typeof respTrace === "object" && !Array.isArray(respTrace)) {
1430
+ this._handleResponseTrace(method, "error", elapsed, respTrace);
1431
+ }
1432
+ reject(mapRemoteError(response.error));
1433
+ } else if (response.result !== void 0) {
1434
+ this._log.debug(`RPC response ok: method=${method}, id=${rpcId}, elapsed=${elapsed}ms ${summarizeDict(response.result, DIAG_RESULT_FIELDS)}`);
1435
+ if (traceId) {
1436
+ this._log.info(`[trace=${traceId}] rpc_recv method=${method} rpc_id=${rpcId} duration_ms=${elapsed} status=ok`);
1437
+ }
1438
+ if (this._metaObserver !== null) {
1439
+ const meta = response._meta;
1440
+ if (isJsonObject(meta)) {
1441
+ try {
1442
+ this._metaObserver(meta);
1443
+ } catch (exc) {
1444
+ this._log.debug(`meta_observer raised: ${String(exc)}`);
1445
+ }
1446
+ }
1447
+ }
1448
+ const respTrace = response._trace;
1449
+ if (respTrace && typeof respTrace === "object" && !Array.isArray(respTrace)) {
1450
+ this._handleResponseTrace(method, "ok", elapsed, respTrace);
1451
+ }
1452
+ resolve(response.result);
1453
+ } else {
1454
+ this._log.warn(`RPC response missing result or error: method=${method}, id=${rpcId}, elapsed=${elapsed}ms`);
1455
+ reject(new SerializationError(`rpc response missing result and error: ${method}`));
1456
+ }
1457
+ },
1458
+ reject: (err) => {
1459
+ clearTimeout(timer);
1460
+ reject(err);
1461
+ },
1462
+ timer
1463
+ };
1464
+ if (backgroundRpc) {
1465
+ this._backgroundRpcQueue.push({
1466
+ rpcId,
1467
+ method,
1468
+ payload,
1469
+ pending,
1470
+ tStart,
1471
+ timeoutMs: effectiveTimeout,
1472
+ background: true
1473
+ });
1474
+ } else {
1475
+ this._rpcQueue.push({
1476
+ rpcId,
1477
+ method,
1478
+ payload,
1479
+ pending,
1480
+ tStart,
1481
+ timeoutMs: effectiveTimeout,
1482
+ background: false
1483
+ });
1484
+ }
1485
+ this._drainRpcQueue();
1404
1486
  });
1487
+ }
1488
+ /** 从 pending / queue 中移除指定 RPC */
1489
+ _removeRpc(rpcId, pending) {
1490
+ const current = this._pending.get(rpcId);
1491
+ if (current && (!pending || current === pending)) {
1492
+ this._pending.delete(rpcId);
1493
+ this._pendingBackground.delete(rpcId);
1494
+ }
1495
+ if (this._rpcQueue.length > 0) {
1496
+ this._rpcQueue = this._rpcQueue.filter((entry) => entry.rpcId !== rpcId || pending !== void 0 && entry.pending !== pending);
1497
+ }
1498
+ if (this._backgroundRpcQueue.length > 0) {
1499
+ this._backgroundRpcQueue = this._backgroundRpcQueue.filter((entry) => entry.rpcId !== rpcId || pending !== void 0 && entry.pending !== pending);
1500
+ }
1501
+ }
1502
+ /** 从队列中取出 RPC 并发送,直到 inflight 达到上限 */
1503
+ _drainRpcQueue() {
1504
+ if (this._drainingRpcQueue) return;
1505
+ this._drainingRpcQueue = true;
1405
1506
  try {
1406
- const response = await Promise.race([promise, timeoutPromise]);
1407
- const elapsed = Date.now() - tStart;
1408
- if (response.error !== void 0) {
1409
- this._log.debug(`RPC error response: method=${method}, id=${rpcId}, elapsed=${elapsed}ms, error=${JSON.stringify(response.error)}`);
1410
- if (traceId) {
1411
- this._log.info(`[trace=${traceId}] rpc_recv method=${method} rpc_id=${rpcId} duration_ms=${elapsed} status=error`);
1507
+ while (!this._closed && this._ws && this._pending.size < MAX_RPC_INFLIGHT) {
1508
+ let entry;
1509
+ if (this._rpcQueue.length > 0) {
1510
+ entry = this._rpcQueue.shift();
1511
+ } else if (this._backgroundRpcQueue.length > 0 && this._pendingBackground.size < MAX_BACKGROUND_RPC_INFLIGHT) {
1512
+ entry = this._backgroundRpcQueue.shift();
1513
+ } else {
1514
+ break;
1412
1515
  }
1413
- const respTrace2 = response._trace;
1414
- if (respTrace2 && typeof respTrace2 === "object" && !Array.isArray(respTrace2)) {
1415
- this._handleResponseTrace(method, "error", elapsed, respTrace2);
1516
+ const elapsed = Date.now() - entry.tStart;
1517
+ if (elapsed >= entry.timeoutMs) {
1518
+ clearTimeout(entry.pending.timer);
1519
+ this._log.warn(`RPC queue timeout: method=${entry.method}, id=${entry.rpcId}, elapsed=${elapsed}ms, timeout=${entry.timeoutMs}ms`);
1520
+ entry.pending.reject(new TimeoutError(`rpc timeout before send: ${entry.method}`, { retryable: true }));
1521
+ continue;
1416
1522
  }
1417
- throw mapRemoteError(response.error);
1418
- }
1419
- if (response.result === void 0) {
1420
- throw new SerializationError(`rpc response missing result and error: ${method}`);
1421
- }
1422
- this._log.debug(`RPC response ok: method=${method}, id=${rpcId}, elapsed=${elapsed}ms ${summarizeDict(response.result, DIAG_RESULT_FIELDS)}`);
1423
- if (traceId) {
1424
- this._log.info(`[trace=${traceId}] rpc_recv method=${method} rpc_id=${rpcId} duration_ms=${elapsed} status=ok`);
1425
- }
1426
- if (this._metaObserver !== null) {
1427
- const meta = response._meta;
1428
- if (isJsonObject(meta)) {
1429
- try {
1430
- this._metaObserver(meta);
1431
- } catch (exc) {
1432
- this._log.debug(`meta_observer raised: ${String(exc)}`);
1433
- }
1523
+ this._pending.set(entry.rpcId, entry.pending);
1524
+ if (entry.background) {
1525
+ this._pendingBackground.add(entry.rpcId);
1526
+ }
1527
+ try {
1528
+ this._ws.send(entry.payload);
1529
+ this._log.debug(`RPC request sent: method=${entry.method}, id=${entry.rpcId}, background=${entry.background}`);
1530
+ } catch (err) {
1531
+ this._removeRpc(entry.rpcId, entry.pending);
1532
+ this._log.error(`RPC send failed: method=${entry.method}, id=${entry.rpcId}, error=${String(err)}`, err instanceof Error ? err : void 0);
1533
+ entry.pending.reject(
1534
+ new ConnectionError(`failed to send rpc ${entry.method}: ${err instanceof Error ? err.message : String(err)}`)
1535
+ );
1434
1536
  }
1435
1537
  }
1436
- const respTrace = response._trace;
1437
- if (respTrace && typeof respTrace === "object" && !Array.isArray(respTrace)) {
1438
- this._handleResponseTrace(method, "ok", elapsed, respTrace);
1439
- }
1440
- return response.result;
1441
1538
  } finally {
1442
- if (timeoutHandle !== null) {
1443
- globalThis.clearTimeout(timeoutHandle);
1444
- }
1539
+ this._drainingRpcQueue = false;
1445
1540
  }
1446
1541
  }
1447
1542
  /** 处理 RPC 响应中的 _trace 字段:追加 sdk.recv span,格式化输出,通知 observer */
@@ -1476,10 +1571,23 @@ var RPCTransport = class {
1476
1571
  _handleClose(event) {
1477
1572
  const wasClosed = this._closed;
1478
1573
  this._closed = true;
1574
+ const err = new ConnectionError(`websocket closed: code=${event.code}`);
1479
1575
  for (const [, pending] of this._pending) {
1480
- pending.reject(new ConnectionError(`websocket closed: code=${event.code}`));
1576
+ clearTimeout(pending.timer);
1577
+ pending.reject(err);
1481
1578
  }
1482
1579
  this._pending.clear();
1580
+ this._pendingBackground.clear();
1581
+ for (const queued of this._rpcQueue) {
1582
+ clearTimeout(queued.pending.timer);
1583
+ queued.pending.reject(err);
1584
+ }
1585
+ this._rpcQueue = [];
1586
+ for (const queued of this._backgroundRpcQueue) {
1587
+ clearTimeout(queued.pending.timer);
1588
+ queued.pending.reject(err);
1589
+ }
1590
+ this._backgroundRpcQueue = [];
1483
1591
  if (!wasClosed) {
1484
1592
  const error = new ConnectionError(`websocket closed: code=${event.code} reason=${event.reason}`);
1485
1593
  this._dispatcher.publish("connection.error", { error });
@@ -1494,7 +1602,9 @@ var RPCTransport = class {
1494
1602
  const pending = this._pending.get(rpcId);
1495
1603
  if (pending) {
1496
1604
  this._pending.delete(rpcId);
1605
+ this._pendingBackground.delete(rpcId);
1497
1606
  pending.resolve(message);
1607
+ this._drainRpcQueue();
1498
1608
  } else {
1499
1609
  this._log.warn("[aun_core.transport] recv unknown rpc response (maybe arrived after timeout): id=" + rpcId);
1500
1610
  }
@@ -1591,6 +1701,8 @@ var _noopLog4 = { error: () => {
1591
1701
  }, info: () => {
1592
1702
  }, debug: () => {
1593
1703
  } };
1704
+ var AUN_SDK_LANG = "javascript";
1705
+ var AUN_SDK_VERSION = "0.3.4";
1594
1706
  function splitPemBundle(bundle) {
1595
1707
  const marker = "-----END CERTIFICATE-----";
1596
1708
  const certs = [];
@@ -1932,47 +2044,85 @@ var _AuthFlow = class _AuthFlow {
1932
2044
  this._slotId = String(opts.slotId ?? "").trim();
1933
2045
  }
1934
2046
  /**
1935
- * 注册新 AID
2047
+ * 严格注册新 AID(对齐 TS registerAid / Go RegisterAID)。
1936
2048
  *
1937
- * 流程:
1938
- * 1. 确保本地密钥对存在
1939
- * 2. 短连接 RPC 调用 auth.create_aid
1940
- * 3. 保存返回的证书
2049
+ * 注册与认证彻底分离:此方法绝不被 SDK 内部自动调用,
2050
+ * 必须由应用层显式调用。
1941
2051
  */
1942
- async createAid(gatewayUrl, aid) {
2052
+ async registerAid(gatewayUrl, aid) {
1943
2053
  const tStart = Date.now();
1944
- this._log.debug(`createAid enter: aid=${aid} gateway=${gatewayUrl}`);
2054
+ this._log.debug(`registerAid enter: aid=${aid} gateway=${gatewayUrl}`);
1945
2055
  _AuthFlow._validateAidName(aid);
1946
2056
  try {
1947
- const identity = await this._ensureLocalIdentity(aid);
1948
- if (identity.cert) {
1949
- this._log.debug(`createAid exit: elapsed=${Date.now() - tStart}ms aid=${aid} reason=already_has_cert`);
1950
- return { aid: identity.aid, cert: identity.cert };
1951
- }
1952
- try {
1953
- const created = await this._createAid(gatewayUrl, identity);
1954
- Object.assign(identity, created);
1955
- } catch (e) {
1956
- if (e instanceof Error && e.message.includes("already exists")) {
1957
- try {
1958
- const recovered = await this._recoverCertViaDownload(gatewayUrl, identity);
1959
- Object.assign(identity, recovered);
1960
- } catch {
1961
- this._log.debug(`createAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=already_registered_recover_failed`);
1962
- throw new StateError(
1963
- `AID ${aid} already registered on server but local certificate is missing. Certificate download recovery failed. Options: (1) use a different AID name, or (2) restart server to clear registration.`
2057
+ const existing = await this._keystore.loadIdentity(aid);
2058
+ if (existing && existing.private_key_pem && existing.public_key_der_b64) {
2059
+ this._log.debug(`registerAid: local keypair exists, checking server: aid=${aid}`);
2060
+ const localPubB642 = String(existing.public_key_der_b64);
2061
+ const serverCertPem2 = await this._downloadRegisteredCert(gatewayUrl, aid);
2062
+ if (serverCertPem2) {
2063
+ const serverCert = parseCertDer(serverCertPem2);
2064
+ const serverPubB64 = uint8ToBase64(serverCert.spkiBytes);
2065
+ if (serverPubB64 !== localPubB642) {
2066
+ throw new IdentityConflictError(
2067
+ `AID '${aid}' is registered by another party on server (public key mismatch). Choose a different name.`
1964
2068
  );
1965
2069
  }
2070
+ this._log.info(`registerAid: idempotent return for already-registered AID: aid=${aid}`);
2071
+ if (!existing.cert) {
2072
+ existing.cert = serverCertPem2;
2073
+ await this._persistIdentity(existing);
2074
+ }
2075
+ this._aid = aid;
2076
+ return { aid, cert: serverCertPem2 };
1966
2077
  } else {
1967
- throw e;
2078
+ this._log.debug(`registerAid: server has no record, registering with existing keypair: aid=${aid}`);
2079
+ const created2 = await this._createAid(gatewayUrl, existing);
2080
+ const certPem2 = String(created2.cert ?? "");
2081
+ if (!certPem2) {
2082
+ throw new AuthError(`registerAid: server response missing cert for ${aid}`);
2083
+ }
2084
+ existing.cert = certPem2;
2085
+ const returnedCert2 = parseCertDer(certPem2);
2086
+ const certPubB642 = uint8ToBase64(returnedCert2.spkiBytes);
2087
+ if (certPubB642 !== localPubB642) {
2088
+ throw new AuthError(
2089
+ `registerAid: server returned certificate with mismatched public key for ${aid}`
2090
+ );
2091
+ }
2092
+ await this._persistIdentity(existing);
2093
+ this._aid = aid;
2094
+ this._log.debug(`registerAid exit (recovered): elapsed=${Date.now() - tStart}ms aid=${aid}`);
2095
+ return { aid, cert: certPem2 };
1968
2096
  }
1969
2097
  }
2098
+ const serverCertPem = await this._downloadRegisteredCert(gatewayUrl, aid);
2099
+ if (serverCertPem) {
2100
+ throw new IdentityConflictError(
2101
+ `AID '${aid}' is already registered on server. Choose a different name, or if you own the keypair use a recovery flow.`
2102
+ );
2103
+ }
2104
+ const identity = await this._crypto.generateIdentity();
2105
+ identity.aid = aid;
2106
+ const created = await this._createAid(gatewayUrl, identity);
2107
+ const certPem = String(created.cert ?? "");
2108
+ if (!certPem) {
2109
+ throw new AuthError(`registerAid: server response missing cert for ${aid}`);
2110
+ }
2111
+ identity.cert = certPem;
2112
+ const returnedCert = parseCertDer(certPem);
2113
+ const certPubB64 = uint8ToBase64(returnedCert.spkiBytes);
2114
+ const localPubB64 = String(identity.public_key_der_b64);
2115
+ if (certPubB64 !== localPubB64) {
2116
+ throw new AuthError(
2117
+ `registerAid: server returned certificate with mismatched public key for ${aid}`
2118
+ );
2119
+ }
1970
2120
  await this._persistIdentity(identity);
1971
- this._aid = identity.aid;
1972
- this._log.debug(`createAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
2121
+ this._aid = aid;
2122
+ this._log.debug(`registerAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
1973
2123
  return { aid: identity.aid, cert: identity.cert };
1974
2124
  } catch (err) {
1975
- this._log.debug(`createAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
2125
+ this._log.debug(`registerAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
1976
2126
  throw err;
1977
2127
  }
1978
2128
  }
@@ -2009,23 +2159,16 @@ var _AuthFlow = class _AuthFlow {
2009
2159
  await this._persistIdentity(identity);
2010
2160
  } catch (e) {
2011
2161
  throw new StateError(
2012
- `local certificate missing and recovery failed: ${e}. Run auth.createAid() to register a new identity.`
2162
+ `local certificate missing and recovery failed: ${e}. Run auth.registerAid() to register a new identity.`
2013
2163
  );
2014
2164
  }
2015
2165
  }
2166
+ this._assertCertMatchesLocalKeypair(identity);
2016
2167
  let login;
2017
2168
  try {
2018
2169
  login = await this._login(gatewayUrl, identity);
2019
2170
  } catch (e) {
2020
- if (e instanceof AuthError && (String(e.message).includes("not registered") || String(e.message).includes("public key mismatch"))) {
2021
- this._log.warn(`[auth] cert not registered on server, auto re-register: aid=${identity.aid}`);
2022
- const created = await this._createAid(gatewayUrl, identity);
2023
- identity.cert = created.cert;
2024
- await this._persistIdentity(identity);
2025
- login = await this._login(gatewayUrl, identity);
2026
- } else {
2027
- throw e;
2028
- }
2171
+ throw e;
2029
2172
  }
2030
2173
  this._rememberTokens(identity, login);
2031
2174
  await this._validateNewCert(identity, gatewayUrl);
@@ -2045,18 +2188,19 @@ var _AuthFlow = class _AuthFlow {
2045
2188
  }
2046
2189
  }
2047
2190
  /**
2048
- * 确保已认证(如无身份则先注册再登录)。
2191
+ * 确保已认证。注册和登录彻底分离:无身份或无 cert 直接抛错。
2049
2192
  */
2050
2193
  async ensureAuthenticated(gatewayUrl) {
2051
2194
  const tStart = Date.now();
2052
2195
  this._log.debug(`ensureAuthenticated enter: gateway=${gatewayUrl}`);
2053
2196
  try {
2054
- const identity = await this._ensureIdentity();
2197
+ const identity = await this._loadIdentityOrRaise();
2055
2198
  if (!identity.cert) {
2056
- const created = await this._createAid(gatewayUrl, identity);
2057
- Object.assign(identity, created);
2058
- await this._persistIdentity(identity);
2199
+ throw new StateError(
2200
+ `local identity for aid ${identity.aid} has no certificate; call auth.authenticate() to attempt cert recovery, or auth.registerAid() if this is a fresh registration.`
2201
+ );
2059
2202
  }
2203
+ this._assertCertMatchesLocalKeypair(identity);
2060
2204
  const login = await this._login(gatewayUrl, identity);
2061
2205
  this._rememberTokens(identity, login);
2062
2206
  await this._validateNewCert(identity, gatewayUrl);
@@ -2388,6 +2532,35 @@ var _AuthFlow = class _AuthFlow {
2388
2532
  });
2389
2533
  return { cert: response.cert };
2390
2534
  }
2535
+ /** 下载服务端当前登记的证书;未注册返回 null */
2536
+ async _downloadRegisteredCert(gatewayUrl, aid) {
2537
+ const certUrl = gatewayHttpUrl(gatewayUrl, `/pki/cert/${aid}`);
2538
+ try {
2539
+ const certPem = await this._fetchText(certUrl);
2540
+ if (!certPem || !certPem.includes("BEGIN CERTIFICATE")) return null;
2541
+ return certPem;
2542
+ } catch {
2543
+ return null;
2544
+ }
2545
+ }
2546
+ /** 防线 B:cert 公钥必须与本地 keypair 公钥一致,否则拒绝登录 */
2547
+ _assertCertMatchesLocalKeypair(identity) {
2548
+ const aid = identity.aid ?? "?";
2549
+ const certPem = identity.cert;
2550
+ const localPubB64 = identity.public_key_der_b64;
2551
+ if (!certPem || !localPubB64) {
2552
+ throw new AuthError(
2553
+ `identity for aid ${aid} missing cert or public key; refusing to start two-phase login`
2554
+ );
2555
+ }
2556
+ const cert = parseCertDer(certPem);
2557
+ const certSpkiB64 = uint8ToBase64(cert.spkiBytes);
2558
+ if (certSpkiB64 !== localPubB64) {
2559
+ throw new AuthError(
2560
+ `local certificate public key does not match local keypair for aid ${aid}; refusing to start two-phase login. Run auth.registerAid() to repair identity.`
2561
+ );
2562
+ }
2563
+ }
2391
2564
  /** 下载已注册证书恢复本地状态 */
2392
2565
  async _recoverCertViaDownload(gatewayUrl, identity) {
2393
2566
  const certUrl = gatewayHttpUrl(gatewayUrl, `/pki/cert/${identity.aid}`);
@@ -2455,7 +2628,11 @@ var _AuthFlow = class _AuthFlow {
2455
2628
  auth: { method: "kite_token", token },
2456
2629
  protocol: { min: "1.0", max: "1.0" },
2457
2630
  device: { id: String(opts?.deviceId ?? this._deviceId ?? ""), type: "sdk" },
2458
- client: { slot_id: String(opts?.slotId ?? this._slotId ?? "") },
2631
+ client: {
2632
+ slot_id: String(opts?.slotId ?? this._slotId ?? ""),
2633
+ sdk_lang: AUN_SDK_LANG,
2634
+ sdk_version: AUN_SDK_VERSION
2635
+ },
2459
2636
  delivery_mode: opts?.deliveryMode ?? { mode: "fanout" },
2460
2637
  capabilities
2461
2638
  };
@@ -2843,26 +3020,9 @@ var _AuthFlow = class _AuthFlow {
2843
3020
  }
2844
3021
  }
2845
3022
  /** 确保本地有密钥对(没有则生成) */
2846
- async _ensureLocalIdentity(aid) {
2847
- const existing = await this._keystore.loadIdentity(aid);
2848
- if (existing && existing.private_key_pem && existing.public_key_der_b64) {
2849
- this._aid = aid;
2850
- return existing;
2851
- }
2852
- const identity = await this._crypto.generateIdentity();
2853
- identity.aid = aid;
2854
- if (existing) {
2855
- for (const [k, v] of Object.entries(existing)) {
2856
- if (k !== "aid" && !(k in identity)) {
2857
- identity[k] = v;
2858
- }
2859
- }
2860
- }
2861
- await this._persistIdentity(identity);
2862
- this._aid = aid;
2863
- return identity;
2864
- }
2865
- /** 加载身份,不存在时抛出异常 */
3023
+ // _ensureLocalIdentity 已移除:注册和登录彻底分离,
3024
+ // 登录路径绝不再隐式生成密钥;新身份必须由应用层显式调 registerAid)
3025
+ /** 加载身份,不存在或半成品时抛出异常 */
2866
3026
  async _loadIdentityOrRaise(aid) {
2867
3027
  const requestedAid = aid ?? this._aid;
2868
3028
  if (requestedAid) {
@@ -2870,26 +3030,18 @@ var _AuthFlow = class _AuthFlow {
2870
3030
  if (!existing) {
2871
3031
  throw new StateError(`identity not found for aid: ${requestedAid}`);
2872
3032
  }
3033
+ if (!existing.private_key_pem || !existing.public_key_der_b64) {
3034
+ throw new StateError(
3035
+ `local identity for aid ${requestedAid} is incomplete (missing keypair); call auth.registerAid() first`
3036
+ );
3037
+ }
2873
3038
  this._aid = requestedAid;
2874
3039
  if (!existing.aid) existing.aid = requestedAid;
2875
3040
  return existing;
2876
3041
  }
2877
- throw new StateError("no local identity found, call auth.createAid() first");
2878
- }
2879
- /** 确保有身份(无则尝试生成) */
2880
- async _ensureIdentity() {
2881
- try {
2882
- return await this._loadIdentityOrRaise();
2883
- } catch {
2884
- if (!this._aid) {
2885
- throw new StateError("no local identity found, call auth.createAid() first");
2886
- }
2887
- const identity = await this._crypto.generateIdentity();
2888
- identity.aid = this._aid;
2889
- await this._persistIdentity(identity);
2890
- return identity;
2891
- }
3042
+ throw new StateError("no local identity found, call auth.registerAid() first");
2892
3043
  }
3044
+ // (_ensureIdentity 已移除:注册和登录彻底分离)
2893
3045
  async _loadInstanceState(aid) {
2894
3046
  if (typeof this._keystore.loadInstanceState !== "function") {
2895
3047
  return null;
@@ -3533,29 +3685,29 @@ var AuthNamespace = class {
3533
3685
  }
3534
3686
  /** 内部访问 client 私有属性 */
3535
3687
  /**
3536
- * 注册新 AID
3537
- * 通过 well-known 发现 gateway → 调用 AuthFlow.createAid 注册。
3688
+ * 严格注册新 AID(对齐 TS registerAid)。
3689
+ * 通过 well-known 发现 gateway → 调用 AuthFlow.registerAid 注册。
3538
3690
  */
3539
- async createAid(params) {
3691
+ async registerAid(params) {
3540
3692
  const tStart = Date.now();
3541
3693
  const aid = String(params?.aid ?? "");
3542
- this._log.debug(`createAid enter: aid=${aid}`);
3694
+ this._log.debug(`registerAid enter: aid=${aid}`);
3543
3695
  try {
3544
- if (!aid) throw new ValidationError("auth.createAid requires 'aid'");
3696
+ if (!aid) throw new ValidationError("auth.registerAid requires 'aid'");
3545
3697
  const gatewayUrl = await this._resolveGateway(aid);
3546
3698
  this._client.gatewayUrl = gatewayUrl;
3547
3699
  const auth = this._internal._auth;
3548
- const result = await auth.createAid(gatewayUrl, aid);
3700
+ const result = await auth.registerAid(gatewayUrl, aid);
3549
3701
  this._internal._aid = aid;
3550
3702
  this._internal._identity = await auth.loadIdentityOrNone(aid);
3551
- this._log.debug(`createAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
3703
+ this._log.debug(`registerAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
3552
3704
  return {
3553
3705
  aid,
3554
3706
  cert_pem: result.cert,
3555
3707
  gateway: gatewayUrl
3556
3708
  };
3557
3709
  } catch (err) {
3558
- this._log.debug(`createAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
3710
+ this._log.debug(`registerAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
3559
3711
  throw err;
3560
3712
  }
3561
3713
  }
@@ -3591,7 +3743,7 @@ var AuthNamespace = class {
3591
3743
  const targetAid = String(opts?.aid ?? client._aid ?? "").trim();
3592
3744
  const identity = await client._auth.loadIdentityOrNone(targetAid || void 0);
3593
3745
  if (!identity) {
3594
- throw new StateError("no local identity found, call auth.createAid() first");
3746
+ throw new StateError("no local identity found, call auth.registerAid() first");
3595
3747
  }
3596
3748
  const privateKeyPem = String(identity.private_key_pem ?? "").trim();
3597
3749
  const certPem = normalizeIdentityCertPem(identity);
@@ -3713,7 +3865,7 @@ var AuthNamespace = class {
3713
3865
  const auth = this._internal._auth;
3714
3866
  let identity = await auth.loadIdentityOrNone(aid);
3715
3867
  if (!identity) {
3716
- throw new StateError("no local identity found, call auth.createAid() first");
3868
+ throw new StateError("no local identity found, call auth.registerAid() first");
3717
3869
  }
3718
3870
  const cachedToken = String(identity.access_token ?? "");
3719
3871
  const expiresAt = auth.getAccessTokenExpiry(identity);
@@ -3745,11 +3897,11 @@ var AuthNamespace = class {
3745
3897
  const auth = this._internal._auth;
3746
3898
  const identity = await auth.loadIdentityOrNone(this._client.aid ?? void 0);
3747
3899
  if (!identity) {
3748
- throw new StateError("no local identity found, call auth.createAid() first");
3900
+ throw new StateError("no local identity found, call auth.registerAid() first");
3749
3901
  }
3750
3902
  const aid = String(identity.aid ?? this._client.aid ?? "").trim();
3751
3903
  if (!aid) {
3752
- throw new StateError("no local identity found, call auth.createAid() first");
3904
+ throw new StateError("no local identity found, call auth.registerAid() first");
3753
3905
  }
3754
3906
  const gatewayUrl = await this._resolveGateway(aid);
3755
3907
  this._client.gatewayUrl = gatewayUrl;
@@ -3829,15 +3981,35 @@ var AuthNamespace = class {
3829
3981
  }
3830
3982
  const cached = this._agentMdCache.get(targetAid);
3831
3983
  const requestHeaders = { Accept: "text/markdown" };
3832
- if (cached?.etag) requestHeaders["If-None-Match"] = cached.etag;
3833
- if (cached?.lastModified) requestHeaders["If-Modified-Since"] = cached.lastModified;
3834
- const response = await fetchWithTimeout(await this._resolveAgentMdUrl(targetAid), {
3984
+ const agentMdUrl = await this._resolveAgentMdUrl(targetAid);
3985
+ const response = await fetchWithTimeout(agentMdUrl, {
3835
3986
  method: "GET",
3836
- headers: requestHeaders
3987
+ headers: requestHeaders,
3988
+ redirect: "follow"
3837
3989
  });
3838
- if (response.status === 304 && cached) {
3839
- this._log.debug(`downloadAgentMd exit (not_modified): elapsed=${Date.now() - tStart}ms aid=${targetAid}`);
3840
- return cached.text;
3990
+ if (response.status === 304) {
3991
+ if (cached?.text) {
3992
+ this._log.debug(`downloadAgentMd exit (not_modified): elapsed=${Date.now() - tStart}ms aid=${targetAid}`);
3993
+ return cached.text;
3994
+ }
3995
+ this._log.warn(`downloadAgentMd got 304 but no local cache, retrying unconditional GET: aid=${targetAid}`);
3996
+ const retryResp = await fetchWithTimeout(agentMdUrl, {
3997
+ method: "GET",
3998
+ headers: { Accept: "text/markdown" },
3999
+ redirect: "follow"
4000
+ });
4001
+ if (retryResp.status === 404) {
4002
+ throw new NotFoundError(`agent.md not found for aid: ${targetAid}`);
4003
+ }
4004
+ if (!retryResp.ok) {
4005
+ const message = (await retryResp.text()).trim();
4006
+ throw new AUNError(
4007
+ `download agent.md failed (retry): HTTP ${retryResp.status}${message ? ` - ${message}` : ""}`
4008
+ );
4009
+ }
4010
+ const retryText = await retryResp.text();
4011
+ this._log.debug(`downloadAgentMd exit (retry): elapsed=${Date.now() - tStart}ms aid=${targetAid} bytes=${retryText.length}`);
4012
+ return retryText;
3841
4013
  }
3842
4014
  if (response.status === 404) {
3843
4015
  throw new NotFoundError(`agent.md not found for aid: ${targetAid}`);
@@ -4962,6 +5134,12 @@ function sameJson(a, b) {
4962
5134
  }
4963
5135
  var _ENC_ALGO = "AES-GCM";
4964
5136
  var _PBKDF2_ITERATIONS = 1e5;
5137
+ var SeedMigrationError = class extends Error {
5138
+ constructor(message) {
5139
+ super(message);
5140
+ this.name = "SeedMigrationError";
5141
+ }
5142
+ };
4965
5143
  function _uint8ToBase64(bytes) {
4966
5144
  let b = "";
4967
5145
  for (let i = 0; i < bytes.length; i++) b += String.fromCharCode(bytes[i]);
@@ -5000,6 +5178,9 @@ async function _decryptPEM(enc, seed) {
5000
5178
  const pt = await crypto.subtle.decrypt({ name: _ENC_ALGO, iv: toBufferSource2(iv) }, key, toBufferSource2(ct));
5001
5179
  return new TextDecoder().decode(pt);
5002
5180
  }
5181
+ function hasEncryptionSeed(seed) {
5182
+ return seed !== void 0;
5183
+ }
5003
5184
  var DB_NAME = "aun-keystore";
5004
5185
  var DB_VERSION = 6;
5005
5186
  var STORE_KEY_PAIRS = "key_pairs";
@@ -5322,6 +5503,52 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
5322
5503
  setLogger(log) {
5323
5504
  this._log = log;
5324
5505
  }
5506
+ static async changeSeed(oldSeed, newSeed) {
5507
+ if (oldSeed === ".seed") {
5508
+ throw new SeedMigrationError("JS IndexedDB keystore does not support file .seed migration");
5509
+ }
5510
+ const rows = await idbGetAll(STORE_KEY_PAIRS);
5511
+ const migrations = [];
5512
+ for (const row of rows) {
5513
+ if (!isRecord(row.value)) continue;
5514
+ const envelope = row.value._encrypted_pk;
5515
+ if (!isRecord(envelope)) continue;
5516
+ try {
5517
+ const privateKeyPem = await _decryptPEM(envelope, oldSeed);
5518
+ migrations.push({ key: row.key, value: deepClone(row.value), privateKeyPem });
5519
+ } catch {
5520
+ throw new SeedMigrationError(`seed migration refused: private key is not encrypted by old seed: aid=${row.key}`);
5521
+ }
5522
+ }
5523
+ if (migrations.length === 0) {
5524
+ throw new SeedMigrationError("seed migration refused: no encrypted private key verified with old seed");
5525
+ }
5526
+ const result = {
5527
+ migrated: 0,
5528
+ skipped: 0,
5529
+ errors: 0,
5530
+ privateKeysVerified: migrations.length,
5531
+ privateKeysMigrated: 0
5532
+ };
5533
+ for (const item of migrations) {
5534
+ const next = deepClone(item.value);
5535
+ next._encrypted_pk = await _encryptPEM(item.privateKeyPem, newSeed);
5536
+ const verified = await _decryptPEM(next._encrypted_pk, newSeed);
5537
+ if (verified !== item.privateKeyPem) {
5538
+ throw new SeedMigrationError(`new seed verification failed for private key: aid=${item.key}`);
5539
+ }
5540
+ delete next.private_key_pem;
5541
+ await idbPut(STORE_KEY_PAIRS, item.key, next);
5542
+ result.migrated += 1;
5543
+ result.privateKeysMigrated += 1;
5544
+ }
5545
+ return result;
5546
+ }
5547
+ async changeSeed(oldSeed, newSeed) {
5548
+ const result = await _IndexedDBKeyStore.changeSeed(oldSeed, newSeed);
5549
+ this._encryptionSeed = newSeed;
5550
+ return result;
5551
+ }
5325
5552
  async _withAidLock(aid, fn) {
5326
5553
  const key = safeAid(aid);
5327
5554
  const previous = _IndexedDBKeyStore._aidTails.get(key) ?? Promise.resolve();
@@ -5378,7 +5605,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
5378
5605
  if (!isRecord(data)) return null;
5379
5606
  const result = deepClone(data);
5380
5607
  const epk = result._encrypted_pk;
5381
- if (epk && typeof epk === "object" && !Array.isArray(epk) && this._encryptionSeed) {
5608
+ if (epk && typeof epk === "object" && !Array.isArray(epk) && hasEncryptionSeed(this._encryptionSeed)) {
5382
5609
  try {
5383
5610
  const envelope = epk;
5384
5611
  result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed);
@@ -5388,7 +5615,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
5388
5615
  }
5389
5616
  } else if (
5390
5617
  // 透明迁移:旧版明文数据自动加密回写
5391
- !epk && typeof result.private_key_pem === "string" && this._encryptionSeed
5618
+ !epk && typeof result.private_key_pem === "string" && hasEncryptionSeed(this._encryptionSeed)
5392
5619
  ) {
5393
5620
  try {
5394
5621
  await this.saveKeyPair(aid, result);
@@ -5399,7 +5626,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
5399
5626
  }
5400
5627
  async saveKeyPair(aid, keyPair) {
5401
5628
  const record = deepClone(keyPair);
5402
- if (this._encryptionSeed && typeof record.private_key_pem === "string") {
5629
+ if (hasEncryptionSeed(this._encryptionSeed) && typeof record.private_key_pem === "string") {
5403
5630
  record._encrypted_pk = await _encryptPEM(record.private_key_pem, this._encryptionSeed);
5404
5631
  delete record.private_key_pem;
5405
5632
  }
@@ -5775,7 +6002,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
5775
6002
  const data = await idbGet(STORE_KEY_PAIRS, metadataStoreKey(aid));
5776
6003
  if (!isRecord(data)) return null;
5777
6004
  const result = deepClone(data);
5778
- if (isRecord(result._encrypted_pk) && this._encryptionSeed) {
6005
+ if (isRecord(result._encrypted_pk) && hasEncryptionSeed(this._encryptionSeed)) {
5779
6006
  try {
5780
6007
  const envelope = result._encrypted_pk;
5781
6008
  result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed);
@@ -5785,7 +6012,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
5785
6012
  }
5786
6013
  } else if (
5787
6014
  // 透明迁移:旧版明文数据自动加密回写
5788
- !isRecord(result._encrypted_pk) && typeof result.private_key_pem === "string" && this._encryptionSeed
6015
+ !isRecord(result._encrypted_pk) && typeof result.private_key_pem === "string" && hasEncryptionSeed(this._encryptionSeed)
5789
6016
  ) {
5790
6017
  try {
5791
6018
  await this._saveKeyPairUnlocked(aid, result);
@@ -5796,7 +6023,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
5796
6023
  }
5797
6024
  async _saveKeyPairUnlocked(aid, keyPair) {
5798
6025
  const record = deepClone(keyPair);
5799
- if (this._encryptionSeed && typeof record.private_key_pem === "string") {
6026
+ if (hasEncryptionSeed(this._encryptionSeed) && typeof record.private_key_pem === "string") {
5800
6027
  record._encrypted_pk = await _encryptPEM(record.private_key_pem, this._encryptionSeed);
5801
6028
  delete record.private_key_pem;
5802
6029
  }
@@ -9654,6 +9881,7 @@ var V2Session = class {
9654
9881
  __publicField(this, "_peerIKCache", /* @__PURE__ */ new Map());
9655
9882
  __publicField(this, "_verifiedSPKs", /* @__PURE__ */ new Set());
9656
9883
  __publicField(this, "_oldSPKMaxSeq", /* @__PURE__ */ new Map());
9884
+ __publicField(this, "_spkCache", /* @__PURE__ */ new Map());
9657
9885
  __publicField(this, "_nowFn", () => Date.now());
9658
9886
  if (!ikPriv || !ikPubDer) {
9659
9887
  throw new Error("V2Session requires AID priv/pub keys (IK = AID identity)");
@@ -9771,8 +9999,13 @@ var V2Session = class {
9771
9999
  await this.ensureKeys();
9772
10000
  if (!spkId) return { ikPriv: this._ikPriv };
9773
10001
  if (spkId === this._spkId) return { ikPriv: this._ikPriv, spkPriv: this._spkPriv };
10002
+ const cached = this._spkCache.get(spkId);
10003
+ if (cached) return { ikPriv: this._ikPriv, spkPriv: cached };
9774
10004
  const oldSPK = await this._loadSPK(spkId);
9775
- if (oldSPK) return { ikPriv: this._ikPriv, spkPriv: oldSPK };
10005
+ if (oldSPK) {
10006
+ this._spkCache.set(spkId, oldSPK);
10007
+ return { ikPriv: this._ikPriv, spkPriv: oldSPK };
10008
+ }
9776
10009
  const ikAlias = await this._loadIKSPK(spkId);
9777
10010
  if (ikAlias) return { ikPriv: ikAlias.priv, spkPriv: ikAlias.priv };
9778
10011
  if (spkId === await this._ikSPKId()) {
@@ -10079,7 +10312,7 @@ function isPlainObject(value) {
10079
10312
  var encoder3 = new TextEncoder();
10080
10313
  var decoder = new TextDecoder();
10081
10314
  var E2EE_SDK_LANG = "javascript";
10082
- var E2EE_SDK_VERSION = "0.3.2";
10315
+ var E2EE_SDK_VERSION = "0.3.4";
10083
10316
  async function sha2563(data) {
10084
10317
  const buf = await crypto.subtle.digest("SHA-256", data.slice().buffer);
10085
10318
  return new Uint8Array(buf);
@@ -10249,7 +10482,8 @@ function normalizeProtectedHeaders(headers, payload) {
10249
10482
  normalized["payload_type"] = payloadType;
10250
10483
  }
10251
10484
  normalized.sdk_lang = E2EE_SDK_LANG;
10252
- normalized.sdk_vesion = E2EE_SDK_VERSION;
10485
+ delete normalized.sdk_vesion;
10486
+ normalized.sdk_version = E2EE_SDK_VERSION;
10253
10487
  return normalized;
10254
10488
  }
10255
10489
  function normalizeProtectedHeaderKey(key) {
@@ -10641,7 +10875,7 @@ async function verifySenderSignature(env, senderPubDer) {
10641
10875
  }
10642
10876
  function findMyRow(recipients, selfAid, selfDeviceId) {
10643
10877
  for (const row of recipients) {
10644
- if (row[0] === selfAid && row[1] === selfDeviceId) return row;
10878
+ if (row[0] === selfAid && (row[1] === selfDeviceId || row[1] === "")) return row;
10645
10879
  }
10646
10880
  return null;
10647
10881
  }
@@ -11092,6 +11326,54 @@ function isGroupServiceAid(value) {
11092
11326
  const [name, ...issuerParts] = text.split(".");
11093
11327
  return name === "group" && issuerParts.join(".").length > 0;
11094
11328
  }
11329
+ function normalizeV2WrapPolicy(raw) {
11330
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
11331
+ const obj = raw;
11332
+ let protocol = String(obj.protocol ?? "").trim().toUpperCase();
11333
+ let scope = String(obj.scope ?? "").trim().toLowerCase();
11334
+ if (scope !== "aid" && scope !== "device") {
11335
+ if (obj.per_aid_wrap === true) scope = "aid";
11336
+ else if (obj.per_device_wrap === true) scope = "device";
11337
+ else scope = "";
11338
+ }
11339
+ if (protocol !== "1DH" && protocol !== "3DH") protocol = "";
11340
+ if (scope === "aid") protocol = "1DH";
11341
+ if (!protocol && !scope) return void 0;
11342
+ return {
11343
+ protocol: protocol ? protocol : void 0,
11344
+ scope: scope ? scope : void 0
11345
+ };
11346
+ }
11347
+ function v2WrapCapabilities() {
11348
+ return {
11349
+ version: "v2.1",
11350
+ protocols: ["1DH", "3DH"],
11351
+ scopes: ["aid", "device"],
11352
+ per_aid_wrap: true,
11353
+ per_device_wrap: true
11354
+ };
11355
+ }
11356
+ function applyV2WrapPolicyToTargets(targets, policy) {
11357
+ if (!policy) return targets;
11358
+ const normalized = targets.map((target) => {
11359
+ const row = { ...target };
11360
+ if (policy.protocol === "1DH") {
11361
+ row.keySource = "aid_master";
11362
+ row.spkPkDer = void 0;
11363
+ row.spkId = "";
11364
+ }
11365
+ return row;
11366
+ });
11367
+ if (policy.scope !== "aid") return normalized;
11368
+ const collapsed = /* @__PURE__ */ new Map();
11369
+ for (const target of normalized) {
11370
+ const key = `${target.aid}\0${target.role}`;
11371
+ if (!collapsed.has(key)) {
11372
+ collapsed.set(key, { ...target, deviceId: "" });
11373
+ }
11374
+ }
11375
+ return Array.from(collapsed.values());
11376
+ }
11095
11377
  function _v2LeftPad32(b) {
11096
11378
  if (b.length === 32) return b;
11097
11379
  if (b.length > 32) return b.subarray(b.length - 32);
@@ -11315,7 +11597,7 @@ var _AUNClient = class _AUNClient {
11315
11597
  __publicField(this, "_agentMdPath", "");
11316
11598
  __publicField(this, "_agentMdCache", /* @__PURE__ */ new Map());
11317
11599
  __publicField(this, "_agentMdFetchInflight", /* @__PURE__ */ new Set());
11318
- __publicField(this, "_agentMdListLock", Promise.resolve());
11600
+ __publicField(this, "_agentMdLock", Promise.resolve());
11319
11601
  /** 消息序列号跟踪器(群消息 + P2P 空洞检测) */
11320
11602
  __publicField(this, "_seqTracker", new SeqTracker());
11321
11603
  __publicField(this, "_seqTrackerContext", null);
@@ -11329,6 +11611,8 @@ var _AUNClient = class _AUNClient {
11329
11611
  __publicField(this, "_groupSynced", /* @__PURE__ */ new Set());
11330
11612
  /** gap fill 来源标记:true 表示当前正在补洞(pull 触发),false 表示非补洞 */
11331
11613
  __publicField(this, "_gapFillActive", false);
11614
+ // Pull Gate:序列化同一 key 的并发 pull 操作,防止重复拉取
11615
+ __publicField(this, "_pullGates", /* @__PURE__ */ new Map());
11332
11616
  // 重连相关
11333
11617
  __publicField(this, "_reconnectActive", false);
11334
11618
  __publicField(this, "_reconnectAbort", null);
@@ -11372,7 +11656,7 @@ var _AUNClient = class _AUNClient {
11372
11656
  this._clientLog.info(`AUNClient initialized: debug=${_debug} aunPath=${this.configModel.aunPath} aid=${initAid ?? "-"}`);
11373
11657
  this._dispatcher = new EventDispatcher();
11374
11658
  this._discovery = new GatewayDiscovery();
11375
- this._keystore = new IndexedDBKeyStore();
11659
+ this._keystore = new IndexedDBKeyStore({ encryptionSeed: this.configModel.seedPassword ?? void 0 });
11376
11660
  this._slotId = "";
11377
11661
  this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: "fanout" });
11378
11662
  this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
@@ -11474,7 +11758,7 @@ var _AUNClient = class _AUNClient {
11474
11758
  }
11475
11759
  /**
11476
11760
  * 浏览器版本 publishAgentMd。默认从 {agentMdPath}/{self_aid}/agent.md 的等价 IndexedDB 正文读取,
11477
- * 然后签名、上传,并刷新 list.json 元数据。
11761
+ * 然后签名、上传,并刷新 agentmd.json 元数据。
11478
11762
  *
11479
11763
  * 兼容旧浏览器调用:传入 content 时会先写入等价正文,再从该正文发布。
11480
11764
  */
@@ -11516,7 +11800,7 @@ var _AUNClient = class _AUNClient {
11516
11800
  }
11517
11801
  /**
11518
11802
  * 浏览器版本 fetchAgentMd。aid 缺省时取自身;下载后的正文固定写入
11519
- * {agentMdPath}/{aid}/agent.md 的等价 IndexedDB 正文,list.json 只保存元数据。
11803
+ * {agentMdPath}/{aid}/agent.md 的等价 IndexedDB 正文,agentmd.json 只保存元数据。
11520
11804
  */
11521
11805
  async fetchAgentMd(aid) {
11522
11806
  const target = String(aid ?? this._aid ?? "").trim();
@@ -11588,8 +11872,8 @@ var _AUNClient = class _AUNClient {
11588
11872
  }
11589
11873
  return target;
11590
11874
  }
11591
- _agentMdListKey() {
11592
- return "list.json";
11875
+ _agentMdMetaKey(aid) {
11876
+ return `${this._agentMdSafeAid(aid)}/agentmd.json`;
11593
11877
  }
11594
11878
  _agentMdContentKey(aid) {
11595
11879
  return `${this._agentMdSafeAid(aid)}/agent.md`;
@@ -11621,20 +11905,13 @@ var _AUNClient = class _AUNClient {
11621
11905
  fetched_at: Date.now()
11622
11906
  });
11623
11907
  }
11624
- async _listAgentMdContentAids() {
11625
- const list = this._keystore.listAgentMdContentAids;
11626
- if (typeof list !== "function") {
11627
- throw new Error("IndexedDB agent.md storage unavailable");
11628
- }
11629
- return await list.call(this._keystore, this._agentMdRoot());
11630
- }
11631
- async _withAgentMdListLock(fn) {
11632
- const previous = this._agentMdListLock.catch(() => void 0);
11908
+ async _withAgentMdLock(fn) {
11909
+ const previous = this._agentMdLock.catch(() => void 0);
11633
11910
  let release;
11634
11911
  const current = new Promise((resolve) => {
11635
11912
  release = resolve;
11636
11913
  });
11637
- this._agentMdListLock = previous.then(() => current);
11914
+ this._agentMdLock = previous.then(() => current);
11638
11915
  await previous;
11639
11916
  try {
11640
11917
  return await fn();
@@ -11642,70 +11919,35 @@ var _AUNClient = class _AUNClient {
11642
11919
  release();
11643
11920
  }
11644
11921
  }
11645
- _normalizeAgentMdList(data) {
11646
- const records = {};
11647
- let iterable = [];
11648
- if (isJsonObject(data)) {
11649
- const payload = data;
11650
- if (Array.isArray(payload.records)) iterable = payload.records;
11651
- else if (isJsonObject(payload.records)) iterable = Object.values(payload.records);
11652
- } else if (Array.isArray(data)) {
11653
- iterable = data;
11654
- }
11655
- for (const item of iterable) {
11656
- if (!isJsonObject(item)) continue;
11657
- const raw = item;
11658
- const aid = String(raw.aid ?? "").trim();
11659
- if (!aid) continue;
11660
- const record = {};
11661
- for (const [key, value] of Object.entries(raw)) {
11662
- if (key !== "content") record[key] = value;
11663
- }
11664
- record.aid = aid;
11665
- for (const key of ["fetched_at", "observed_at", "checked_at", "updated_at"]) {
11666
- record[key] = Number(record[key] ?? 0) || 0;
11667
- }
11668
- records[aid] = record;
11669
- }
11670
- return records;
11671
- }
11672
- async _writeAgentMdListUnlocked(records) {
11673
- const sorted = {};
11674
- for (const aid of Object.keys(records).sort()) sorted[aid] = records[aid];
11675
- await this._writeAgentMdStorage(this._agentMdListKey(), `${JSON.stringify({ version: 1, updated_at: Date.now(), records: sorted }, null, 2)}
11676
- `);
11677
- }
11678
- async _rebuildAgentMdListUnlocked() {
11679
- const records = {};
11680
- const now = Date.now();
11681
- for (const aid of await this._listAgentMdContentAids()) {
11682
- try {
11683
- const content = await this._readAgentMdStorage(this._agentMdContentKey(aid));
11684
- if (content === null) continue;
11685
- records[aid] = {
11686
- aid,
11687
- local_etag: await this._agentMdContentEtag(content),
11688
- fetched_at: now,
11689
- updated_at: now
11690
- };
11691
- } catch (err) {
11692
- this._clientLog.warn(`agent.md rebuild skipped unreadable file aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
11693
- }
11922
+ _normalizeAgentMdRecord(aid, data) {
11923
+ if (!isJsonObject(data)) return {};
11924
+ const record = {};
11925
+ for (const [key, value] of Object.entries(data)) {
11926
+ if (key !== "content") record[key] = value;
11694
11927
  }
11695
- await this._writeAgentMdListUnlocked(records);
11696
- this._agentMdCache.clear();
11697
- return records;
11928
+ record.aid = this._agentMdSafeAid(String(record.aid ?? aid));
11929
+ for (const key of ["fetched_at", "observed_at", "checked_at", "updated_at"]) {
11930
+ record[key] = Number(record[key] ?? 0) || 0;
11931
+ }
11932
+ return record;
11698
11933
  }
11699
- async _readAgentMdListUnlocked() {
11700
- const raw = await this._readAgentMdStorage(this._agentMdListKey());
11701
- if (raw === null) {
11702
- return await this._rebuildAgentMdListUnlocked();
11934
+ async _writeAgentMdRecordUnlocked(aid, record) {
11935
+ const payload = {};
11936
+ for (const [key, value] of Object.entries(record)) {
11937
+ if (key !== "content" && value !== void 0 && value !== null) payload[key] = value;
11703
11938
  }
11939
+ payload.aid = this._agentMdSafeAid(aid);
11940
+ await this._writeAgentMdStorage(this._agentMdMetaKey(aid), `${JSON.stringify(payload, null, 2)}
11941
+ `);
11942
+ }
11943
+ async _readAgentMdRecordUnlocked(aid) {
11944
+ const raw = await this._readAgentMdStorage(this._agentMdMetaKey(aid));
11945
+ if (raw === null) return {};
11704
11946
  try {
11705
- return this._normalizeAgentMdList(JSON.parse(raw));
11947
+ return this._normalizeAgentMdRecord(aid, JSON.parse(raw));
11706
11948
  } catch (err) {
11707
- this._clientLog.warn(`agent.md list.json damaged, rebuilding: ${err instanceof Error ? err.message : String(err)}`);
11708
- return await this._rebuildAgentMdListUnlocked();
11949
+ this._clientLog.warn(`agent.md metadata damaged, ignoring: aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
11950
+ return {};
11709
11951
  }
11710
11952
  }
11711
11953
  async _readAgentMdContent(aid) {
@@ -11727,20 +11969,28 @@ var _AUNClient = class _AUNClient {
11727
11969
  const target = String(aid ?? "").trim();
11728
11970
  if (!target) return null;
11729
11971
  try {
11730
- const records = await this._withAgentMdListLock(async () => await this._readAgentMdListUnlocked());
11731
- const record = records[target];
11732
- if (record && typeof record === "object") {
11733
- const loaded = { ...record, aid: target };
11734
- const content = await this._readAgentMdContent(target);
11735
- if (content !== null) {
11736
- loaded.content = content;
11737
- loaded.local_etag = await this._agentMdContentEtag(content);
11738
- } else {
11739
- this._clientLog.warn(`agent.md content read failed: aid=${target}`);
11972
+ const loaded = await this._withAgentMdLock(async () => {
11973
+ const record = await this._readAgentMdRecordUnlocked(target);
11974
+ const next = Object.keys(record).length > 0 ? { ...record, aid: target } : { aid: target };
11975
+ try {
11976
+ const content = await this._readAgentMdContent(target);
11977
+ if (content !== null) {
11978
+ next.content = content;
11979
+ next.local_etag = await this._agentMdContentEtag(content);
11980
+ } else {
11981
+ const metaRaw = await this._readAgentMdStorage(this._agentMdMetaKey(target));
11982
+ if (metaRaw !== null) {
11983
+ this._clientLog.warn(`agent.md content read failed: aid=${target}`);
11984
+ }
11985
+ }
11986
+ } catch (err) {
11987
+ this._clientLog.warn(`agent.md content read failed: aid=${target} err=${err instanceof Error ? err.message : String(err)}`);
11740
11988
  }
11741
- this._agentMdCache.set(target, { ...loaded });
11742
- return { ...loaded };
11743
- }
11989
+ return next;
11990
+ });
11991
+ if (Object.keys(loaded).length <= 1) return null;
11992
+ this._agentMdCache.set(target, { ...loaded });
11993
+ return { ...loaded };
11744
11994
  } catch (err) {
11745
11995
  this._clientLog.debug(`agent.md cache load skipped: aid=${target} err=${err instanceof Error ? err.message : String(err)}`);
11746
11996
  }
@@ -11759,15 +12009,13 @@ var _AUNClient = class _AUNClient {
11759
12009
  if (!inputFields.fetched_at) inputFields.fetched_at = Date.now();
11760
12010
  }
11761
12011
  delete inputFields.content;
11762
- const record = await this._withAgentMdListLock(async () => {
11763
- const records = await this._readAgentMdListUnlocked();
11764
- const next = { ...records[target] ?? {}, aid: target };
12012
+ const record = await this._withAgentMdLock(async () => {
12013
+ const next = { ...await this._readAgentMdRecordUnlocked(target), aid: target };
11765
12014
  for (const [key, value] of Object.entries(inputFields)) {
11766
12015
  if (value !== void 0 && value !== null) next[key] = value;
11767
12016
  }
11768
12017
  next.updated_at = Date.now();
11769
- records[target] = { ...next };
11770
- await this._writeAgentMdListUnlocked(records);
12018
+ await this._writeAgentMdRecordUnlocked(target, next);
11771
12019
  return next;
11772
12020
  });
11773
12021
  const loaded = { ...record };
@@ -12219,6 +12467,19 @@ var _AUNClient = class _AUNClient {
12219
12467
  return this._putMessageThoughtEncryptedV2(p);
12220
12468
  }
12221
12469
  }
12470
+ const pullGateKey = this._pullGateKeyForCall(method, p);
12471
+ if (pullGateKey) {
12472
+ return await this._runPullSerialized(pullGateKey, async () => {
12473
+ return await this._callImplInner(method, p);
12474
+ });
12475
+ }
12476
+ return await this._callImplInner(method, p);
12477
+ }
12478
+ /**
12479
+ * _callImpl 的内层:pull gate 之后的实际 RPC 分发逻辑。
12480
+ * 拆分出来以便 pull gate 包裹整个操作。
12481
+ */
12482
+ async _callImplInner(method, p) {
12222
12483
  if (method === "message.pull" && this._v2Session) {
12223
12484
  this._clientLog.debug("call route: message.pull \u2192 V2 pull");
12224
12485
  const messages = await this.pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50);
@@ -14786,7 +15047,10 @@ var _AUNClient = class _AUNClient {
14786
15047
  const session = this._v2Session;
14787
15048
  if (session && fromAid) {
14788
15049
  try {
14789
- const bs = await this.call("message.v2.bootstrap", { peer_aid: fromAid });
15050
+ const bs = await this.call("message.v2.bootstrap", {
15051
+ peer_aid: fromAid,
15052
+ e2ee_wrap_capabilities: v2WrapCapabilities()
15053
+ });
14790
15054
  const peers = Array.isArray(bs?.peer_devices) ? bs.peer_devices : [];
14791
15055
  for (const dev of peers) this._cacheV2PeerIKFromDevice(dev, fromAid);
14792
15056
  } catch (exc) {
@@ -14794,7 +15058,10 @@ var _AUNClient = class _AUNClient {
14794
15058
  }
14795
15059
  if (groupId) {
14796
15060
  try {
14797
- const gbs = await this.call("group.v2.bootstrap", { group_id: groupId });
15061
+ const gbs = await this.call("group.v2.bootstrap", {
15062
+ group_id: groupId,
15063
+ e2ee_wrap_capabilities: v2WrapCapabilities()
15064
+ });
14798
15065
  const devices = Array.isArray(gbs?.devices) ? gbs.devices : [];
14799
15066
  const audit = Array.isArray(gbs?.audit_recipients) ? gbs.audit_recipients : [];
14800
15067
  for (const dev of devices) this._cacheV2PeerIKFromDevice(dev);
@@ -15040,7 +15307,7 @@ var _AUNClient = class _AUNClient {
15040
15307
  spkId = String(msg.spk_id ?? "");
15041
15308
  if (!spkId) {
15042
15309
  for (const row of envelope.recipients) {
15043
- if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && row[1] === this._deviceId) {
15310
+ if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && (row[1] === this._deviceId || row[1] === "")) {
15044
15311
  spkId = String(row[5] ?? "");
15045
15312
  recipientKeySource = row.length > 3 ? String(row[3] ?? "") : "";
15046
15313
  break;
@@ -15048,7 +15315,7 @@ var _AUNClient = class _AUNClient {
15048
15315
  }
15049
15316
  } else {
15050
15317
  for (const row of envelope.recipients) {
15051
- if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && row[1] === this._deviceId) {
15318
+ if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && (row[1] === this._deviceId || row[1] === "")) {
15052
15319
  recipientKeySource = row.length > 3 ? String(row[3] ?? "") : "";
15053
15320
  break;
15054
15321
  }
@@ -15397,19 +15664,26 @@ var _AUNClient = class _AUNClient {
15397
15664
  const useCache = opts.useCache !== false;
15398
15665
  let peerDevices = [];
15399
15666
  let auditRaw = [];
15667
+ let wrapPolicy;
15400
15668
  const cached = useCache ? this._v2BootstrapCache.get(to) : void 0;
15401
15669
  if (cached && Date.now() - cached.cachedAt < _AUNClient.V2_BOOTSTRAP_TTL_MS) {
15402
15670
  peerDevices = cached.devices;
15403
15671
  auditRaw = cached.auditRecipients;
15672
+ wrapPolicy = cached.wrapPolicy;
15404
15673
  } else {
15405
- const bs = await this.call("message.v2.bootstrap", { peer_aid: to });
15674
+ const bs = await this.call("message.v2.bootstrap", {
15675
+ peer_aid: to,
15676
+ e2ee_wrap_capabilities: v2WrapCapabilities()
15677
+ });
15406
15678
  peerDevices = Array.isArray(bs?.peer_devices) ? bs.peer_devices : [];
15407
15679
  auditRaw = Array.isArray(bs?.audit_recipients) ? bs.audit_recipients : [];
15680
+ wrapPolicy = normalizeV2WrapPolicy(bs?.e2ee_wrap_policy);
15408
15681
  if (peerDevices.length > 0) {
15409
15682
  this._v2BootstrapCache.set(to, {
15410
15683
  devices: peerDevices,
15411
15684
  auditRecipients: auditRaw,
15412
- cachedAt: Date.now()
15685
+ cachedAt: Date.now(),
15686
+ wrapPolicy
15413
15687
  });
15414
15688
  }
15415
15689
  }
@@ -15445,7 +15719,10 @@ var _AUNClient = class _AUNClient {
15445
15719
  if (selfCached && Date.now() - selfCached.cachedAt < _AUNClient.V2_BOOTSTRAP_TTL_MS) {
15446
15720
  selfDevices = selfCached.devices;
15447
15721
  } else {
15448
- const selfBs = await this.call("message.v2.bootstrap", { peer_aid: this._aid });
15722
+ const selfBs = await this.call("message.v2.bootstrap", {
15723
+ peer_aid: this._aid,
15724
+ e2ee_wrap_capabilities: v2WrapCapabilities()
15725
+ });
15449
15726
  selfDevices = Array.isArray(selfBs?.peer_devices) ? selfBs.peer_devices : [];
15450
15727
  if (selfDevices.length > 0) {
15451
15728
  this._v2BootstrapCache.set(this._aid, {
@@ -15472,9 +15749,10 @@ var _AUNClient = class _AUNClient {
15472
15749
  }
15473
15750
  }
15474
15751
  const sender = await session.getSenderIdentity();
15752
+ const sendTargets = applyV2WrapPolicyToTargets(targets, wrapPolicy);
15475
15753
  const envelope = await encryptP2PMessage(
15476
15754
  sender,
15477
- { targets, auditRecipients: [] },
15755
+ { targets: sendTargets, auditRecipients: [] },
15478
15756
  opts.payload,
15479
15757
  { messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context }
15480
15758
  );
@@ -15547,17 +15825,23 @@ var _AUNClient = class _AUNClient {
15547
15825
  let epoch = 0;
15548
15826
  let stateCommitment = { state_version: 0, state_hash: "", state_chain: "" };
15549
15827
  let auditRecipientsRaw = [];
15828
+ let wrapPolicy;
15550
15829
  const cached = useCache ? this._v2BootstrapCache.get(cacheKey) : void 0;
15551
15830
  if (cached && Date.now() - cached.cachedAt < _AUNClient.V2_BOOTSTRAP_TTL_MS) {
15552
15831
  allDevices = cached.devices;
15553
15832
  epoch = cached.epoch ?? 0;
15554
15833
  stateCommitment = cached.stateCommitment ?? { state_version: 0, state_hash: "", state_chain: "" };
15555
15834
  auditRecipientsRaw = cached.auditRecipients;
15835
+ wrapPolicy = cached.wrapPolicy;
15556
15836
  } else {
15557
- const bs = await this.call("group.v2.bootstrap", { group_id: groupId });
15837
+ const bs = await this.call("group.v2.bootstrap", {
15838
+ group_id: groupId,
15839
+ e2ee_wrap_capabilities: v2WrapCapabilities()
15840
+ });
15558
15841
  allDevices = Array.isArray(bs?.devices) ? bs.devices : [];
15559
15842
  epoch = Number(bs?.epoch ?? 0);
15560
15843
  auditRecipientsRaw = Array.isArray(bs?.audit_recipients) ? bs.audit_recipients : [];
15844
+ wrapPolicy = normalizeV2WrapPolicy(bs?.e2ee_wrap_policy);
15561
15845
  await this._v2CheckFork(groupId, String(bs?.state_chain ?? ""));
15562
15846
  await this._v2VerifyStateSignature(groupId, bs);
15563
15847
  await this._publishV2GroupSecurityLevel(groupId, bs);
@@ -15572,7 +15856,8 @@ var _AUNClient = class _AUNClient {
15572
15856
  auditRecipients: auditRecipientsRaw,
15573
15857
  cachedAt: Date.now(),
15574
15858
  epoch,
15575
- stateCommitment
15859
+ stateCommitment,
15860
+ wrapPolicy
15576
15861
  });
15577
15862
  }
15578
15863
  const pendingAdds = Array.isArray(bs?.pending_adds) ? bs.pending_adds : [];
@@ -15612,11 +15897,12 @@ var _AUNClient = class _AUNClient {
15612
15897
  throw new E2EEError(`V2 group: no target devices for ${groupId}`);
15613
15898
  }
15614
15899
  const sender = await session.getSenderIdentity();
15900
+ const sendTargets = applyV2WrapPolicyToTargets(targets, wrapPolicy);
15615
15901
  const envelope = await encryptGroupMessage(
15616
15902
  sender,
15617
15903
  groupId,
15618
15904
  epoch,
15619
- targets,
15905
+ sendTargets,
15620
15906
  opts.payload,
15621
15907
  { messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context },
15622
15908
  stateCommitment
@@ -15697,7 +15983,7 @@ var _AUNClient = class _AUNClient {
15697
15983
  if (Array.isArray(recipients)) {
15698
15984
  for (const row of recipients) {
15699
15985
  if (Array.isArray(row) && row.length >= 6) {
15700
- if (row[0] === this._aid && row[1] === this._deviceId) {
15986
+ if (row[0] === this._aid && (row[1] === this._deviceId || row[1] === "")) {
15701
15987
  spkId = String(row[5] ?? "");
15702
15988
  recipientKeySource = row.length > 3 ? String(row[3] ?? "") : "";
15703
15989
  break;
@@ -15976,7 +16262,10 @@ var _AUNClient = class _AUNClient {
15976
16262
  if (aid === myAid) myRole = role;
15977
16263
  }
15978
16264
  if (myRole !== "owner" && myRole !== "admin") return false;
15979
- const bootstrapResp = await this.call("group.v2.bootstrap", { group_id: groupId });
16265
+ const bootstrapResp = await this.call("group.v2.bootstrap", {
16266
+ group_id: groupId,
16267
+ e2ee_wrap_capabilities: v2WrapCapabilities()
16268
+ });
15980
16269
  const devices = Array.isArray(bootstrapResp?.devices) ? bootstrapResp.devices : [];
15981
16270
  const candidates = [];
15982
16271
  for (const dev of devices) {
@@ -16065,7 +16354,10 @@ var _AUNClient = class _AUNClient {
16065
16354
  }
16066
16355
  }
16067
16356
  }
16068
- const bootstrapResp = await this.call("group.v2.bootstrap", { group_id: groupId });
16357
+ const bootstrapResp = await this.call("group.v2.bootstrap", {
16358
+ group_id: groupId,
16359
+ e2ee_wrap_capabilities: v2WrapCapabilities()
16360
+ });
16069
16361
  const allDevices = Array.isArray(bootstrapResp?.devices) ? bootstrapResp.devices : [];
16070
16362
  const auditRecipients = Array.isArray(bootstrapResp?.audit_recipients) ? bootstrapResp.audit_recipients : [];
16071
16363
  const auditAidsList = [...new Set(
@@ -16374,6 +16666,62 @@ var _AUNClient = class _AUNClient {
16374
16666
  this._clientLog.warn(`background task exception:${String(exc)}`);
16375
16667
  });
16376
16668
  }
16669
+ // ── Pull Gate(序列化同一 key 的并发 pull)──────────────────
16670
+ _pullGateKeyForCall(method, params) {
16671
+ if (method === "message.pull" || method === "message.v2.pull") {
16672
+ return this._aid ? `p2p:${this._aid}` : "";
16673
+ }
16674
+ if (method === "group.pull" || method === "group.v2.pull") {
16675
+ const gid = String(params.group_id ?? "").trim();
16676
+ return gid ? `group:${gid}` : "";
16677
+ }
16678
+ if (method === "group.pull_events") {
16679
+ const gid = String(params.group_id ?? "").trim();
16680
+ return gid ? `group_event:${gid}` : "";
16681
+ }
16682
+ return "";
16683
+ }
16684
+ _tryAcquirePullGate(key) {
16685
+ if (!key) return 0;
16686
+ const now = Date.now();
16687
+ const gate = this._pullGates.get(key) ?? { inflight: false, startedAt: 0, token: 0 };
16688
+ if (gate.inflight && now - gate.startedAt <= _AUNClient._PULL_GATE_STALE_MS) {
16689
+ return null;
16690
+ }
16691
+ if (gate.inflight) {
16692
+ this._clientLog.warn(`pull in-flight stale reset: key=${key} age=${now - gate.startedAt}ms`);
16693
+ }
16694
+ gate.token += 1;
16695
+ gate.inflight = true;
16696
+ gate.startedAt = now;
16697
+ this._pullGates.set(key, gate);
16698
+ return gate.token;
16699
+ }
16700
+ _releasePullGate(key, token) {
16701
+ if (!key || token == null) return;
16702
+ const gate = this._pullGates.get(key);
16703
+ if (!gate || gate.token !== token) return;
16704
+ gate.inflight = false;
16705
+ gate.startedAt = 0;
16706
+ }
16707
+ async _runPullSerialized(key, operation) {
16708
+ let token = this._tryAcquirePullGate(key);
16709
+ if (token === null) {
16710
+ const deadline = Date.now() + _AUNClient._PULL_GATE_STALE_MS + 100;
16711
+ while (token === null && Date.now() <= deadline) {
16712
+ await this._sleep(25);
16713
+ token = this._tryAcquirePullGate(key);
16714
+ }
16715
+ if (token === null) {
16716
+ throw new StateError(`pull already in-flight for ${key}`);
16717
+ }
16718
+ }
16719
+ try {
16720
+ return await operation();
16721
+ } finally {
16722
+ this._releasePullGate(key, token);
16723
+ }
16724
+ }
16377
16725
  /** 可取消的 sleep */
16378
16726
  _sleep(ms) {
16379
16727
  return new Promise((resolve) => {
@@ -16385,6 +16733,7 @@ __publicField(_AUNClient, "V2_BOOTSTRAP_TTL_MS", 60 * 60 * 1e3);
16385
16733
  __publicField(_AUNClient, "V2_RETRYABLE_CODES", /* @__PURE__ */ new Set([-33011, -33012, -33050, -33052, -33054]));
16386
16734
  __publicField(_AUNClient, "_V2_SIG_CACHE_TTL", 60 * 60 * 1e3);
16387
16735
  __publicField(_AUNClient, "_V2_SIG_CACHE_MAX", 16384);
16736
+ __publicField(_AUNClient, "_PULL_GATE_STALE_MS", 3e4);
16388
16737
  // ── 内部:断线重连 ────────────────────────────────
16389
16738
  /** 不重连 close code 集合:认证失败/权限错误/被踢等,重连无意义 */
16390
16739
  __publicField(_AUNClient, "_NO_RECONNECT_CODES", /* @__PURE__ */ new Set([4001, 4003, 4008, 4009, 4010, 4011, 4012, 4013, 4014, 4015]));
@@ -16455,7 +16804,7 @@ var ProtectedHeaders = class _ProtectedHeaders {
16455
16804
  };
16456
16805
 
16457
16806
  // src/index.ts
16458
- var __version__ = "0.3.2";
16807
+ var __version__ = "0.3.4";
16459
16808
  export {
16460
16809
  AUNClient,
16461
16810
  AUNError,
@@ -16480,6 +16829,7 @@ export {
16480
16829
  GroupError,
16481
16830
  GroupNotFoundError,
16482
16831
  GroupStateError,
16832
+ IdentityConflictError,
16483
16833
  IndexedDBKeyStore,
16484
16834
  IndexedDBSecretStore,
16485
16835
  MetaNamespace,
@@ -16490,6 +16840,7 @@ export {
16490
16840
  RPCTransport,
16491
16841
  RateLimitError,
16492
16842
  STATE_PREFIX,
16843
+ SeedMigrationError,
16493
16844
  SerializationError,
16494
16845
  SessionError,
16495
16846
  StateError,