@agentunion/fastaun-browser 0.3.3 → 0.3.5

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 (67) hide show
  1. package/CHANGELOG.md +113 -85
  2. package/_packed_docs/CHANGELOG.md +113 -85
  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/E2EE_V2/347/256/200/345/214/226/344/270/2721DH/345/212/240Per-AID_Wrap/346/226/271/346/241/210.md +124 -0
  8. 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
  9. 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
  10. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +5 -5
  11. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +1 -1
  12. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +2 -2
  13. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +46 -6
  14. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +89 -12
  15. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +19 -1
  16. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +20 -5
  17. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +8 -8
  18. 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
  19. package/_packed_docs/sdk/INDEX.md +22 -22
  20. package/_packed_docs/sdk/README.md +3 -3
  21. package/dist/auth.d.ts +10 -11
  22. package/dist/auth.d.ts.map +1 -1
  23. package/dist/auth.js +127 -91
  24. package/dist/auth.js.map +1 -1
  25. package/dist/bundle.js +649 -274
  26. package/dist/client.d.ts +19 -10
  27. package/dist/client.d.ts.map +1 -1
  28. package/dist/client.js +238 -111
  29. package/dist/client.js.map +1 -1
  30. package/dist/errors.d.ts +4 -0
  31. package/dist/errors.d.ts.map +1 -1
  32. package/dist/errors.js +7 -0
  33. package/dist/errors.js.map +1 -1
  34. package/dist/index.d.ts +3 -3
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +3 -3
  37. package/dist/index.js.map +1 -1
  38. package/dist/keystore/index.d.ts +5 -0
  39. package/dist/keystore/index.d.ts.map +1 -1
  40. package/dist/keystore/indexeddb.d.ts +12 -0
  41. package/dist/keystore/indexeddb.d.ts.map +1 -1
  42. package/dist/keystore/indexeddb.js +64 -6
  43. package/dist/keystore/indexeddb.js.map +1 -1
  44. package/dist/namespaces/auth.d.ts +9 -3
  45. package/dist/namespaces/auth.d.ts.map +1 -1
  46. package/dist/namespaces/auth.js +64 -20
  47. package/dist/namespaces/auth.js.map +1 -1
  48. package/dist/secret-store/indexeddb-store.js +1 -1
  49. package/dist/secret-store/indexeddb-store.js.map +1 -1
  50. package/dist/transport.d.ts +9 -1
  51. package/dist/transport.d.ts.map +1 -1
  52. package/dist/transport.js +158 -64
  53. package/dist/transport.js.map +1 -1
  54. package/dist/v2/e2ee/decrypt.js +1 -1
  55. package/dist/v2/e2ee/decrypt.js.map +1 -1
  56. package/dist/v2/e2ee/encrypt-p2p.d.ts.map +1 -1
  57. package/dist/v2/e2ee/encrypt-p2p.js +3 -2
  58. package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
  59. package/dist/v2/session/session.d.ts +1 -0
  60. package/dist/v2/session/session.d.ts.map +1 -1
  61. package/dist/v2/session/session.js +7 -1
  62. package/dist/v2/session/session.js.map +1 -1
  63. package/package.json +43 -43
  64. package/dist/e2ee-group.d.ts +0 -276
  65. package/dist/e2ee-group.d.ts.map +0 -1
  66. package/dist/e2ee-group.js +0 -1653
  67. 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.5";
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
  }
@@ -3583,6 +3735,30 @@ var AuthNamespace = class {
3583
3735
  throw err;
3584
3736
  }
3585
3737
  }
3738
+ /** 只读加载本地已注册身份(密钥对 + 证书 + 实例状态)。无副作用,不触发网络请求。 */
3739
+ async loadIdentity(params) {
3740
+ const aid = String((params ?? {})?.aid ?? "").trim() || void 0;
3741
+ const identity = await this._internal._auth.loadIdentityOrNone(aid);
3742
+ if (!identity) {
3743
+ throw new StateError(`identity not found for aid: ${aid ?? "<default>"}`);
3744
+ }
3745
+ return identity;
3746
+ }
3747
+ /** 只读加载本地已注册身份,不存在时返回 null。 */
3748
+ async loadIdentityOrNull(params) {
3749
+ const aid = String((params ?? {})?.aid ?? "").trim() || void 0;
3750
+ return await this._internal._auth.loadIdentityOrNone(aid);
3751
+ }
3752
+ /** 获取对端 AID 的证书 PEM(本地缓存优先,未命中走 PKI HTTP + 链验证)。 */
3753
+ async fetchPeerCert(params) {
3754
+ const aid = String(params?.aid ?? "").trim();
3755
+ if (!aid) throw new Error("auth.fetchPeerCert requires 'aid'");
3756
+ const fp = String(params?.cert_fingerprint ?? "").trim() || void 0;
3757
+ if (typeof this._internal._fetchPeerCert !== "function") {
3758
+ throw new Error("client does not support _fetchPeerCert");
3759
+ }
3760
+ return String(await this._internal._fetchPeerCert(aid, fp)).trim();
3761
+ }
3586
3762
  async signAgentMd(content, opts) {
3587
3763
  const tStart = Date.now();
3588
3764
  this._log.debug(`signAgentMd enter: aid=${opts?.aid ?? "<current>"} content_len=${String(content ?? "").length}`);
@@ -3591,7 +3767,7 @@ var AuthNamespace = class {
3591
3767
  const targetAid = String(opts?.aid ?? client._aid ?? "").trim();
3592
3768
  const identity = await client._auth.loadIdentityOrNone(targetAid || void 0);
3593
3769
  if (!identity) {
3594
- throw new StateError("no local identity found, call auth.createAid() first");
3770
+ throw new StateError("no local identity found, call auth.registerAid() first");
3595
3771
  }
3596
3772
  const privateKeyPem = String(identity.private_key_pem ?? "").trim();
3597
3773
  const certPem = normalizeIdentityCertPem(identity);
@@ -3713,7 +3889,7 @@ var AuthNamespace = class {
3713
3889
  const auth = this._internal._auth;
3714
3890
  let identity = await auth.loadIdentityOrNone(aid);
3715
3891
  if (!identity) {
3716
- throw new StateError("no local identity found, call auth.createAid() first");
3892
+ throw new StateError("no local identity found, call auth.registerAid() first");
3717
3893
  }
3718
3894
  const cachedToken = String(identity.access_token ?? "");
3719
3895
  const expiresAt = auth.getAccessTokenExpiry(identity);
@@ -3745,11 +3921,11 @@ var AuthNamespace = class {
3745
3921
  const auth = this._internal._auth;
3746
3922
  const identity = await auth.loadIdentityOrNone(this._client.aid ?? void 0);
3747
3923
  if (!identity) {
3748
- throw new StateError("no local identity found, call auth.createAid() first");
3924
+ throw new StateError("no local identity found, call auth.registerAid() first");
3749
3925
  }
3750
3926
  const aid = String(identity.aid ?? this._client.aid ?? "").trim();
3751
3927
  if (!aid) {
3752
- throw new StateError("no local identity found, call auth.createAid() first");
3928
+ throw new StateError("no local identity found, call auth.registerAid() first");
3753
3929
  }
3754
3930
  const gatewayUrl = await this._resolveGateway(aid);
3755
3931
  this._client.gatewayUrl = gatewayUrl;
@@ -3829,15 +4005,35 @@ var AuthNamespace = class {
3829
4005
  }
3830
4006
  const cached = this._agentMdCache.get(targetAid);
3831
4007
  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), {
4008
+ const agentMdUrl = await this._resolveAgentMdUrl(targetAid);
4009
+ const response = await fetchWithTimeout(agentMdUrl, {
3835
4010
  method: "GET",
3836
- headers: requestHeaders
4011
+ headers: requestHeaders,
4012
+ redirect: "follow"
3837
4013
  });
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;
4014
+ if (response.status === 304) {
4015
+ if (cached?.text) {
4016
+ this._log.debug(`downloadAgentMd exit (not_modified): elapsed=${Date.now() - tStart}ms aid=${targetAid}`);
4017
+ return cached.text;
4018
+ }
4019
+ this._log.warn(`downloadAgentMd got 304 but no local cache, retrying unconditional GET: aid=${targetAid}`);
4020
+ const retryResp = await fetchWithTimeout(agentMdUrl, {
4021
+ method: "GET",
4022
+ headers: { Accept: "text/markdown" },
4023
+ redirect: "follow"
4024
+ });
4025
+ if (retryResp.status === 404) {
4026
+ throw new NotFoundError(`agent.md not found for aid: ${targetAid}`);
4027
+ }
4028
+ if (!retryResp.ok) {
4029
+ const message = (await retryResp.text()).trim();
4030
+ throw new AUNError(
4031
+ `download agent.md failed (retry): HTTP ${retryResp.status}${message ? ` - ${message}` : ""}`
4032
+ );
4033
+ }
4034
+ const retryText = await retryResp.text();
4035
+ this._log.debug(`downloadAgentMd exit (retry): elapsed=${Date.now() - tStart}ms aid=${targetAid} bytes=${retryText.length}`);
4036
+ return retryText;
3841
4037
  }
3842
4038
  if (response.status === 404) {
3843
4039
  throw new NotFoundError(`agent.md not found for aid: ${targetAid}`);
@@ -4962,6 +5158,12 @@ function sameJson(a, b) {
4962
5158
  }
4963
5159
  var _ENC_ALGO = "AES-GCM";
4964
5160
  var _PBKDF2_ITERATIONS = 1e5;
5161
+ var SeedMigrationError = class extends Error {
5162
+ constructor(message) {
5163
+ super(message);
5164
+ this.name = "SeedMigrationError";
5165
+ }
5166
+ };
4965
5167
  function _uint8ToBase64(bytes) {
4966
5168
  let b = "";
4967
5169
  for (let i = 0; i < bytes.length; i++) b += String.fromCharCode(bytes[i]);
@@ -5000,6 +5202,9 @@ async function _decryptPEM(enc, seed) {
5000
5202
  const pt = await crypto.subtle.decrypt({ name: _ENC_ALGO, iv: toBufferSource2(iv) }, key, toBufferSource2(ct));
5001
5203
  return new TextDecoder().decode(pt);
5002
5204
  }
5205
+ function hasEncryptionSeed(seed) {
5206
+ return seed !== void 0;
5207
+ }
5003
5208
  var DB_NAME = "aun-keystore";
5004
5209
  var DB_VERSION = 6;
5005
5210
  var STORE_KEY_PAIRS = "key_pairs";
@@ -5322,6 +5527,52 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
5322
5527
  setLogger(log) {
5323
5528
  this._log = log;
5324
5529
  }
5530
+ static async changeSeed(oldSeed, newSeed) {
5531
+ if (oldSeed === ".seed") {
5532
+ throw new SeedMigrationError("JS IndexedDB keystore does not support file .seed migration");
5533
+ }
5534
+ const rows = await idbGetAll(STORE_KEY_PAIRS);
5535
+ const migrations = [];
5536
+ for (const row of rows) {
5537
+ if (!isRecord(row.value)) continue;
5538
+ const envelope = row.value._encrypted_pk;
5539
+ if (!isRecord(envelope)) continue;
5540
+ try {
5541
+ const privateKeyPem = await _decryptPEM(envelope, oldSeed);
5542
+ migrations.push({ key: row.key, value: deepClone(row.value), privateKeyPem });
5543
+ } catch {
5544
+ throw new SeedMigrationError(`seed migration refused: private key is not encrypted by old seed: aid=${row.key}`);
5545
+ }
5546
+ }
5547
+ if (migrations.length === 0) {
5548
+ throw new SeedMigrationError("seed migration refused: no encrypted private key verified with old seed");
5549
+ }
5550
+ const result = {
5551
+ migrated: 0,
5552
+ skipped: 0,
5553
+ errors: 0,
5554
+ privateKeysVerified: migrations.length,
5555
+ privateKeysMigrated: 0
5556
+ };
5557
+ for (const item of migrations) {
5558
+ const next = deepClone(item.value);
5559
+ next._encrypted_pk = await _encryptPEM(item.privateKeyPem, newSeed);
5560
+ const verified = await _decryptPEM(next._encrypted_pk, newSeed);
5561
+ if (verified !== item.privateKeyPem) {
5562
+ throw new SeedMigrationError(`new seed verification failed for private key: aid=${item.key}`);
5563
+ }
5564
+ delete next.private_key_pem;
5565
+ await idbPut(STORE_KEY_PAIRS, item.key, next);
5566
+ result.migrated += 1;
5567
+ result.privateKeysMigrated += 1;
5568
+ }
5569
+ return result;
5570
+ }
5571
+ async changeSeed(oldSeed, newSeed) {
5572
+ const result = await _IndexedDBKeyStore.changeSeed(oldSeed, newSeed);
5573
+ this._encryptionSeed = newSeed;
5574
+ return result;
5575
+ }
5325
5576
  async _withAidLock(aid, fn) {
5326
5577
  const key = safeAid(aid);
5327
5578
  const previous = _IndexedDBKeyStore._aidTails.get(key) ?? Promise.resolve();
@@ -5378,7 +5629,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
5378
5629
  if (!isRecord(data)) return null;
5379
5630
  const result = deepClone(data);
5380
5631
  const epk = result._encrypted_pk;
5381
- if (epk && typeof epk === "object" && !Array.isArray(epk) && this._encryptionSeed) {
5632
+ if (epk && typeof epk === "object" && !Array.isArray(epk) && hasEncryptionSeed(this._encryptionSeed)) {
5382
5633
  try {
5383
5634
  const envelope = epk;
5384
5635
  result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed);
@@ -5388,7 +5639,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
5388
5639
  }
5389
5640
  } else if (
5390
5641
  // 透明迁移:旧版明文数据自动加密回写
5391
- !epk && typeof result.private_key_pem === "string" && this._encryptionSeed
5642
+ !epk && typeof result.private_key_pem === "string" && hasEncryptionSeed(this._encryptionSeed)
5392
5643
  ) {
5393
5644
  try {
5394
5645
  await this.saveKeyPair(aid, result);
@@ -5399,7 +5650,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
5399
5650
  }
5400
5651
  async saveKeyPair(aid, keyPair) {
5401
5652
  const record = deepClone(keyPair);
5402
- if (this._encryptionSeed && typeof record.private_key_pem === "string") {
5653
+ if (hasEncryptionSeed(this._encryptionSeed) && typeof record.private_key_pem === "string") {
5403
5654
  record._encrypted_pk = await _encryptPEM(record.private_key_pem, this._encryptionSeed);
5404
5655
  delete record.private_key_pem;
5405
5656
  }
@@ -5775,7 +6026,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
5775
6026
  const data = await idbGet(STORE_KEY_PAIRS, metadataStoreKey(aid));
5776
6027
  if (!isRecord(data)) return null;
5777
6028
  const result = deepClone(data);
5778
- if (isRecord(result._encrypted_pk) && this._encryptionSeed) {
6029
+ if (isRecord(result._encrypted_pk) && hasEncryptionSeed(this._encryptionSeed)) {
5779
6030
  try {
5780
6031
  const envelope = result._encrypted_pk;
5781
6032
  result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed);
@@ -5785,7 +6036,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
5785
6036
  }
5786
6037
  } else if (
5787
6038
  // 透明迁移:旧版明文数据自动加密回写
5788
- !isRecord(result._encrypted_pk) && typeof result.private_key_pem === "string" && this._encryptionSeed
6039
+ !isRecord(result._encrypted_pk) && typeof result.private_key_pem === "string" && hasEncryptionSeed(this._encryptionSeed)
5789
6040
  ) {
5790
6041
  try {
5791
6042
  await this._saveKeyPairUnlocked(aid, result);
@@ -5796,7 +6047,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
5796
6047
  }
5797
6048
  async _saveKeyPairUnlocked(aid, keyPair) {
5798
6049
  const record = deepClone(keyPair);
5799
- if (this._encryptionSeed && typeof record.private_key_pem === "string") {
6050
+ if (hasEncryptionSeed(this._encryptionSeed) && typeof record.private_key_pem === "string") {
5800
6051
  record._encrypted_pk = await _encryptPEM(record.private_key_pem, this._encryptionSeed);
5801
6052
  delete record.private_key_pem;
5802
6053
  }
@@ -9654,6 +9905,7 @@ var V2Session = class {
9654
9905
  __publicField(this, "_peerIKCache", /* @__PURE__ */ new Map());
9655
9906
  __publicField(this, "_verifiedSPKs", /* @__PURE__ */ new Set());
9656
9907
  __publicField(this, "_oldSPKMaxSeq", /* @__PURE__ */ new Map());
9908
+ __publicField(this, "_spkCache", /* @__PURE__ */ new Map());
9657
9909
  __publicField(this, "_nowFn", () => Date.now());
9658
9910
  if (!ikPriv || !ikPubDer) {
9659
9911
  throw new Error("V2Session requires AID priv/pub keys (IK = AID identity)");
@@ -9771,8 +10023,13 @@ var V2Session = class {
9771
10023
  await this.ensureKeys();
9772
10024
  if (!spkId) return { ikPriv: this._ikPriv };
9773
10025
  if (spkId === this._spkId) return { ikPriv: this._ikPriv, spkPriv: this._spkPriv };
10026
+ const cached = this._spkCache.get(spkId);
10027
+ if (cached) return { ikPriv: this._ikPriv, spkPriv: cached };
9774
10028
  const oldSPK = await this._loadSPK(spkId);
9775
- if (oldSPK) return { ikPriv: this._ikPriv, spkPriv: oldSPK };
10029
+ if (oldSPK) {
10030
+ this._spkCache.set(spkId, oldSPK);
10031
+ return { ikPriv: this._ikPriv, spkPriv: oldSPK };
10032
+ }
9776
10033
  const ikAlias = await this._loadIKSPK(spkId);
9777
10034
  if (ikAlias) return { ikPriv: ikAlias.priv, spkPriv: ikAlias.priv };
9778
10035
  if (spkId === await this._ikSPKId()) {
@@ -10079,7 +10336,7 @@ function isPlainObject(value) {
10079
10336
  var encoder3 = new TextEncoder();
10080
10337
  var decoder = new TextDecoder();
10081
10338
  var E2EE_SDK_LANG = "javascript";
10082
- var E2EE_SDK_VERSION = "0.3.2";
10339
+ var E2EE_SDK_VERSION = "0.3.5";
10083
10340
  async function sha2563(data) {
10084
10341
  const buf = await crypto.subtle.digest("SHA-256", data.slice().buffer);
10085
10342
  return new Uint8Array(buf);
@@ -10249,7 +10506,8 @@ function normalizeProtectedHeaders(headers, payload) {
10249
10506
  normalized["payload_type"] = payloadType;
10250
10507
  }
10251
10508
  normalized.sdk_lang = E2EE_SDK_LANG;
10252
- normalized.sdk_vesion = E2EE_SDK_VERSION;
10509
+ delete normalized.sdk_vesion;
10510
+ normalized.sdk_version = E2EE_SDK_VERSION;
10253
10511
  return normalized;
10254
10512
  }
10255
10513
  function normalizeProtectedHeaderKey(key) {
@@ -10641,7 +10899,7 @@ async function verifySenderSignature(env, senderPubDer) {
10641
10899
  }
10642
10900
  function findMyRow(recipients, selfAid, selfDeviceId) {
10643
10901
  for (const row of recipients) {
10644
- if (row[0] === selfAid && row[1] === selfDeviceId) return row;
10902
+ if (row[0] === selfAid && (row[1] === selfDeviceId || row[1] === "")) return row;
10645
10903
  }
10646
10904
  return null;
10647
10905
  }
@@ -11092,6 +11350,54 @@ function isGroupServiceAid(value) {
11092
11350
  const [name, ...issuerParts] = text.split(".");
11093
11351
  return name === "group" && issuerParts.join(".").length > 0;
11094
11352
  }
11353
+ function normalizeV2WrapPolicy(raw) {
11354
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
11355
+ const obj = raw;
11356
+ let protocol = String(obj.protocol ?? "").trim().toUpperCase();
11357
+ let scope = String(obj.scope ?? "").trim().toLowerCase();
11358
+ if (scope !== "aid" && scope !== "device") {
11359
+ if (obj.per_aid_wrap === true) scope = "aid";
11360
+ else if (obj.per_device_wrap === true) scope = "device";
11361
+ else scope = "";
11362
+ }
11363
+ if (protocol !== "1DH" && protocol !== "3DH") protocol = "";
11364
+ if (scope === "aid") protocol = "1DH";
11365
+ if (!protocol && !scope) return void 0;
11366
+ return {
11367
+ protocol: protocol ? protocol : void 0,
11368
+ scope: scope ? scope : void 0
11369
+ };
11370
+ }
11371
+ function v2WrapCapabilities() {
11372
+ return {
11373
+ version: "v2.1",
11374
+ protocols: ["1DH", "3DH"],
11375
+ scopes: ["aid", "device"],
11376
+ per_aid_wrap: true,
11377
+ per_device_wrap: true
11378
+ };
11379
+ }
11380
+ function applyV2WrapPolicyToTargets(targets, policy) {
11381
+ if (!policy) return targets;
11382
+ const normalized = targets.map((target) => {
11383
+ const row = { ...target };
11384
+ if (policy.protocol === "1DH") {
11385
+ row.keySource = "aid_master";
11386
+ row.spkPkDer = void 0;
11387
+ row.spkId = "";
11388
+ }
11389
+ return row;
11390
+ });
11391
+ if (policy.scope !== "aid") return normalized;
11392
+ const collapsed = /* @__PURE__ */ new Map();
11393
+ for (const target of normalized) {
11394
+ const key = `${target.aid}\0${target.role}`;
11395
+ if (!collapsed.has(key)) {
11396
+ collapsed.set(key, { ...target, deviceId: "" });
11397
+ }
11398
+ }
11399
+ return Array.from(collapsed.values());
11400
+ }
11095
11401
  function _v2LeftPad32(b) {
11096
11402
  if (b.length === 32) return b;
11097
11403
  if (b.length > 32) return b.subarray(b.length - 32);
@@ -11315,7 +11621,7 @@ var _AUNClient = class _AUNClient {
11315
11621
  __publicField(this, "_agentMdPath", "");
11316
11622
  __publicField(this, "_agentMdCache", /* @__PURE__ */ new Map());
11317
11623
  __publicField(this, "_agentMdFetchInflight", /* @__PURE__ */ new Set());
11318
- __publicField(this, "_agentMdListLock", Promise.resolve());
11624
+ __publicField(this, "_agentMdLock", Promise.resolve());
11319
11625
  /** 消息序列号跟踪器(群消息 + P2P 空洞检测) */
11320
11626
  __publicField(this, "_seqTracker", new SeqTracker());
11321
11627
  __publicField(this, "_seqTrackerContext", null);
@@ -11329,6 +11635,8 @@ var _AUNClient = class _AUNClient {
11329
11635
  __publicField(this, "_groupSynced", /* @__PURE__ */ new Set());
11330
11636
  /** gap fill 来源标记:true 表示当前正在补洞(pull 触发),false 表示非补洞 */
11331
11637
  __publicField(this, "_gapFillActive", false);
11638
+ // Pull Gate:序列化同一 key 的并发 pull 操作,防止重复拉取
11639
+ __publicField(this, "_pullGates", /* @__PURE__ */ new Map());
11332
11640
  // 重连相关
11333
11641
  __publicField(this, "_reconnectActive", false);
11334
11642
  __publicField(this, "_reconnectAbort", null);
@@ -11372,7 +11680,7 @@ var _AUNClient = class _AUNClient {
11372
11680
  this._clientLog.info(`AUNClient initialized: debug=${_debug} aunPath=${this.configModel.aunPath} aid=${initAid ?? "-"}`);
11373
11681
  this._dispatcher = new EventDispatcher();
11374
11682
  this._discovery = new GatewayDiscovery();
11375
- this._keystore = new IndexedDBKeyStore();
11683
+ this._keystore = new IndexedDBKeyStore({ encryptionSeed: this.configModel.seedPassword ?? void 0 });
11376
11684
  this._slotId = "";
11377
11685
  this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: "fanout" });
11378
11686
  this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
@@ -11474,7 +11782,7 @@ var _AUNClient = class _AUNClient {
11474
11782
  }
11475
11783
  /**
11476
11784
  * 浏览器版本 publishAgentMd。默认从 {agentMdPath}/{self_aid}/agent.md 的等价 IndexedDB 正文读取,
11477
- * 然后签名、上传,并刷新 list.json 元数据。
11785
+ * 然后签名、上传,并刷新 agentmd.json 元数据。
11478
11786
  *
11479
11787
  * 兼容旧浏览器调用:传入 content 时会先写入等价正文,再从该正文发布。
11480
11788
  */
@@ -11516,7 +11824,7 @@ var _AUNClient = class _AUNClient {
11516
11824
  }
11517
11825
  /**
11518
11826
  * 浏览器版本 fetchAgentMd。aid 缺省时取自身;下载后的正文固定写入
11519
- * {agentMdPath}/{aid}/agent.md 的等价 IndexedDB 正文,list.json 只保存元数据。
11827
+ * {agentMdPath}/{aid}/agent.md 的等价 IndexedDB 正文,agentmd.json 只保存元数据。
11520
11828
  */
11521
11829
  async fetchAgentMd(aid) {
11522
11830
  const target = String(aid ?? this._aid ?? "").trim();
@@ -11588,8 +11896,8 @@ var _AUNClient = class _AUNClient {
11588
11896
  }
11589
11897
  return target;
11590
11898
  }
11591
- _agentMdListKey() {
11592
- return "list.json";
11899
+ _agentMdMetaKey(aid) {
11900
+ return `${this._agentMdSafeAid(aid)}/agentmd.json`;
11593
11901
  }
11594
11902
  _agentMdContentKey(aid) {
11595
11903
  return `${this._agentMdSafeAid(aid)}/agent.md`;
@@ -11621,20 +11929,13 @@ var _AUNClient = class _AUNClient {
11621
11929
  fetched_at: Date.now()
11622
11930
  });
11623
11931
  }
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);
11932
+ async _withAgentMdLock(fn) {
11933
+ const previous = this._agentMdLock.catch(() => void 0);
11633
11934
  let release;
11634
11935
  const current = new Promise((resolve) => {
11635
11936
  release = resolve;
11636
11937
  });
11637
- this._agentMdListLock = previous.then(() => current);
11938
+ this._agentMdLock = previous.then(() => current);
11638
11939
  await previous;
11639
11940
  try {
11640
11941
  return await fn();
@@ -11642,70 +11943,35 @@ var _AUNClient = class _AUNClient {
11642
11943
  release();
11643
11944
  }
11644
11945
  }
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
- }
11946
+ _normalizeAgentMdRecord(aid, data) {
11947
+ if (!isJsonObject(data)) return {};
11948
+ const record = {};
11949
+ for (const [key, value] of Object.entries(data)) {
11950
+ if (key !== "content") record[key] = value;
11694
11951
  }
11695
- await this._writeAgentMdListUnlocked(records);
11696
- this._agentMdCache.clear();
11697
- return records;
11952
+ record.aid = this._agentMdSafeAid(String(record.aid ?? aid));
11953
+ for (const key of ["fetched_at", "observed_at", "checked_at", "updated_at"]) {
11954
+ record[key] = Number(record[key] ?? 0) || 0;
11955
+ }
11956
+ return record;
11698
11957
  }
11699
- async _readAgentMdListUnlocked() {
11700
- const raw = await this._readAgentMdStorage(this._agentMdListKey());
11701
- if (raw === null) {
11702
- return await this._rebuildAgentMdListUnlocked();
11958
+ async _writeAgentMdRecordUnlocked(aid, record) {
11959
+ const payload = {};
11960
+ for (const [key, value] of Object.entries(record)) {
11961
+ if (key !== "content" && value !== void 0 && value !== null) payload[key] = value;
11703
11962
  }
11963
+ payload.aid = this._agentMdSafeAid(aid);
11964
+ await this._writeAgentMdStorage(this._agentMdMetaKey(aid), `${JSON.stringify(payload, null, 2)}
11965
+ `);
11966
+ }
11967
+ async _readAgentMdRecordUnlocked(aid) {
11968
+ const raw = await this._readAgentMdStorage(this._agentMdMetaKey(aid));
11969
+ if (raw === null) return {};
11704
11970
  try {
11705
- return this._normalizeAgentMdList(JSON.parse(raw));
11971
+ return this._normalizeAgentMdRecord(aid, JSON.parse(raw));
11706
11972
  } catch (err) {
11707
- this._clientLog.warn(`agent.md list.json damaged, rebuilding: ${err instanceof Error ? err.message : String(err)}`);
11708
- return await this._rebuildAgentMdListUnlocked();
11973
+ this._clientLog.warn(`agent.md metadata damaged, ignoring: aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
11974
+ return {};
11709
11975
  }
11710
11976
  }
11711
11977
  async _readAgentMdContent(aid) {
@@ -11727,20 +11993,28 @@ var _AUNClient = class _AUNClient {
11727
11993
  const target = String(aid ?? "").trim();
11728
11994
  if (!target) return null;
11729
11995
  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}`);
11996
+ const loaded = await this._withAgentMdLock(async () => {
11997
+ const record = await this._readAgentMdRecordUnlocked(target);
11998
+ const next = Object.keys(record).length > 0 ? { ...record, aid: target } : { aid: target };
11999
+ try {
12000
+ const content = await this._readAgentMdContent(target);
12001
+ if (content !== null) {
12002
+ next.content = content;
12003
+ next.local_etag = await this._agentMdContentEtag(content);
12004
+ } else {
12005
+ const metaRaw = await this._readAgentMdStorage(this._agentMdMetaKey(target));
12006
+ if (metaRaw !== null) {
12007
+ this._clientLog.warn(`agent.md content read failed: aid=${target}`);
12008
+ }
12009
+ }
12010
+ } catch (err) {
12011
+ this._clientLog.warn(`agent.md content read failed: aid=${target} err=${err instanceof Error ? err.message : String(err)}`);
11740
12012
  }
11741
- this._agentMdCache.set(target, { ...loaded });
11742
- return { ...loaded };
11743
- }
12013
+ return next;
12014
+ });
12015
+ if (Object.keys(loaded).length <= 1) return null;
12016
+ this._agentMdCache.set(target, { ...loaded });
12017
+ return { ...loaded };
11744
12018
  } catch (err) {
11745
12019
  this._clientLog.debug(`agent.md cache load skipped: aid=${target} err=${err instanceof Error ? err.message : String(err)}`);
11746
12020
  }
@@ -11759,15 +12033,13 @@ var _AUNClient = class _AUNClient {
11759
12033
  if (!inputFields.fetched_at) inputFields.fetched_at = Date.now();
11760
12034
  }
11761
12035
  delete inputFields.content;
11762
- const record = await this._withAgentMdListLock(async () => {
11763
- const records = await this._readAgentMdListUnlocked();
11764
- const next = { ...records[target] ?? {}, aid: target };
12036
+ const record = await this._withAgentMdLock(async () => {
12037
+ const next = { ...await this._readAgentMdRecordUnlocked(target), aid: target };
11765
12038
  for (const [key, value] of Object.entries(inputFields)) {
11766
12039
  if (value !== void 0 && value !== null) next[key] = value;
11767
12040
  }
11768
12041
  next.updated_at = Date.now();
11769
- records[target] = { ...next };
11770
- await this._writeAgentMdListUnlocked(records);
12042
+ await this._writeAgentMdRecordUnlocked(target, next);
11771
12043
  return next;
11772
12044
  });
11773
12045
  const loaded = { ...record };
@@ -12219,6 +12491,19 @@ var _AUNClient = class _AUNClient {
12219
12491
  return this._putMessageThoughtEncryptedV2(p);
12220
12492
  }
12221
12493
  }
12494
+ const pullGateKey = this._pullGateKeyForCall(method, p);
12495
+ if (pullGateKey) {
12496
+ return await this._runPullSerialized(pullGateKey, async () => {
12497
+ return await this._callImplInner(method, p);
12498
+ });
12499
+ }
12500
+ return await this._callImplInner(method, p);
12501
+ }
12502
+ /**
12503
+ * _callImpl 的内层:pull gate 之后的实际 RPC 分发逻辑。
12504
+ * 拆分出来以便 pull gate 包裹整个操作。
12505
+ */
12506
+ async _callImplInner(method, p) {
12222
12507
  if (method === "message.pull" && this._v2Session) {
12223
12508
  this._clientLog.debug("call route: message.pull \u2192 V2 pull");
12224
12509
  const messages = await this.pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50);
@@ -14786,7 +15071,10 @@ var _AUNClient = class _AUNClient {
14786
15071
  const session = this._v2Session;
14787
15072
  if (session && fromAid) {
14788
15073
  try {
14789
- const bs = await this.call("message.v2.bootstrap", { peer_aid: fromAid });
15074
+ const bs = await this.call("message.v2.bootstrap", {
15075
+ peer_aid: fromAid,
15076
+ e2ee_wrap_capabilities: v2WrapCapabilities()
15077
+ });
14790
15078
  const peers = Array.isArray(bs?.peer_devices) ? bs.peer_devices : [];
14791
15079
  for (const dev of peers) this._cacheV2PeerIKFromDevice(dev, fromAid);
14792
15080
  } catch (exc) {
@@ -14794,7 +15082,10 @@ var _AUNClient = class _AUNClient {
14794
15082
  }
14795
15083
  if (groupId) {
14796
15084
  try {
14797
- const gbs = await this.call("group.v2.bootstrap", { group_id: groupId });
15085
+ const gbs = await this.call("group.v2.bootstrap", {
15086
+ group_id: groupId,
15087
+ e2ee_wrap_capabilities: v2WrapCapabilities()
15088
+ });
14798
15089
  const devices = Array.isArray(gbs?.devices) ? gbs.devices : [];
14799
15090
  const audit = Array.isArray(gbs?.audit_recipients) ? gbs.audit_recipients : [];
14800
15091
  for (const dev of devices) this._cacheV2PeerIKFromDevice(dev);
@@ -15040,7 +15331,7 @@ var _AUNClient = class _AUNClient {
15040
15331
  spkId = String(msg.spk_id ?? "");
15041
15332
  if (!spkId) {
15042
15333
  for (const row of envelope.recipients) {
15043
- if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && row[1] === this._deviceId) {
15334
+ if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && (row[1] === this._deviceId || row[1] === "")) {
15044
15335
  spkId = String(row[5] ?? "");
15045
15336
  recipientKeySource = row.length > 3 ? String(row[3] ?? "") : "";
15046
15337
  break;
@@ -15048,7 +15339,7 @@ var _AUNClient = class _AUNClient {
15048
15339
  }
15049
15340
  } else {
15050
15341
  for (const row of envelope.recipients) {
15051
- if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && row[1] === this._deviceId) {
15342
+ if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && (row[1] === this._deviceId || row[1] === "")) {
15052
15343
  recipientKeySource = row.length > 3 ? String(row[3] ?? "") : "";
15053
15344
  break;
15054
15345
  }
@@ -15397,19 +15688,26 @@ var _AUNClient = class _AUNClient {
15397
15688
  const useCache = opts.useCache !== false;
15398
15689
  let peerDevices = [];
15399
15690
  let auditRaw = [];
15691
+ let wrapPolicy;
15400
15692
  const cached = useCache ? this._v2BootstrapCache.get(to) : void 0;
15401
15693
  if (cached && Date.now() - cached.cachedAt < _AUNClient.V2_BOOTSTRAP_TTL_MS) {
15402
15694
  peerDevices = cached.devices;
15403
15695
  auditRaw = cached.auditRecipients;
15696
+ wrapPolicy = cached.wrapPolicy;
15404
15697
  } else {
15405
- const bs = await this.call("message.v2.bootstrap", { peer_aid: to });
15698
+ const bs = await this.call("message.v2.bootstrap", {
15699
+ peer_aid: to,
15700
+ e2ee_wrap_capabilities: v2WrapCapabilities()
15701
+ });
15406
15702
  peerDevices = Array.isArray(bs?.peer_devices) ? bs.peer_devices : [];
15407
15703
  auditRaw = Array.isArray(bs?.audit_recipients) ? bs.audit_recipients : [];
15704
+ wrapPolicy = normalizeV2WrapPolicy(bs?.e2ee_wrap_policy);
15408
15705
  if (peerDevices.length > 0) {
15409
15706
  this._v2BootstrapCache.set(to, {
15410
15707
  devices: peerDevices,
15411
15708
  auditRecipients: auditRaw,
15412
- cachedAt: Date.now()
15709
+ cachedAt: Date.now(),
15710
+ wrapPolicy
15413
15711
  });
15414
15712
  }
15415
15713
  }
@@ -15445,7 +15743,10 @@ var _AUNClient = class _AUNClient {
15445
15743
  if (selfCached && Date.now() - selfCached.cachedAt < _AUNClient.V2_BOOTSTRAP_TTL_MS) {
15446
15744
  selfDevices = selfCached.devices;
15447
15745
  } else {
15448
- const selfBs = await this.call("message.v2.bootstrap", { peer_aid: this._aid });
15746
+ const selfBs = await this.call("message.v2.bootstrap", {
15747
+ peer_aid: this._aid,
15748
+ e2ee_wrap_capabilities: v2WrapCapabilities()
15749
+ });
15449
15750
  selfDevices = Array.isArray(selfBs?.peer_devices) ? selfBs.peer_devices : [];
15450
15751
  if (selfDevices.length > 0) {
15451
15752
  this._v2BootstrapCache.set(this._aid, {
@@ -15472,9 +15773,10 @@ var _AUNClient = class _AUNClient {
15472
15773
  }
15473
15774
  }
15474
15775
  const sender = await session.getSenderIdentity();
15776
+ const sendTargets = applyV2WrapPolicyToTargets(targets, wrapPolicy);
15475
15777
  const envelope = await encryptP2PMessage(
15476
15778
  sender,
15477
- { targets, auditRecipients: [] },
15779
+ { targets: sendTargets, auditRecipients: [] },
15478
15780
  opts.payload,
15479
15781
  { messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context }
15480
15782
  );
@@ -15547,17 +15849,23 @@ var _AUNClient = class _AUNClient {
15547
15849
  let epoch = 0;
15548
15850
  let stateCommitment = { state_version: 0, state_hash: "", state_chain: "" };
15549
15851
  let auditRecipientsRaw = [];
15852
+ let wrapPolicy;
15550
15853
  const cached = useCache ? this._v2BootstrapCache.get(cacheKey) : void 0;
15551
15854
  if (cached && Date.now() - cached.cachedAt < _AUNClient.V2_BOOTSTRAP_TTL_MS) {
15552
15855
  allDevices = cached.devices;
15553
15856
  epoch = cached.epoch ?? 0;
15554
15857
  stateCommitment = cached.stateCommitment ?? { state_version: 0, state_hash: "", state_chain: "" };
15555
15858
  auditRecipientsRaw = cached.auditRecipients;
15859
+ wrapPolicy = cached.wrapPolicy;
15556
15860
  } else {
15557
- const bs = await this.call("group.v2.bootstrap", { group_id: groupId });
15861
+ const bs = await this.call("group.v2.bootstrap", {
15862
+ group_id: groupId,
15863
+ e2ee_wrap_capabilities: v2WrapCapabilities()
15864
+ });
15558
15865
  allDevices = Array.isArray(bs?.devices) ? bs.devices : [];
15559
15866
  epoch = Number(bs?.epoch ?? 0);
15560
15867
  auditRecipientsRaw = Array.isArray(bs?.audit_recipients) ? bs.audit_recipients : [];
15868
+ wrapPolicy = normalizeV2WrapPolicy(bs?.e2ee_wrap_policy);
15561
15869
  await this._v2CheckFork(groupId, String(bs?.state_chain ?? ""));
15562
15870
  await this._v2VerifyStateSignature(groupId, bs);
15563
15871
  await this._publishV2GroupSecurityLevel(groupId, bs);
@@ -15572,7 +15880,8 @@ var _AUNClient = class _AUNClient {
15572
15880
  auditRecipients: auditRecipientsRaw,
15573
15881
  cachedAt: Date.now(),
15574
15882
  epoch,
15575
- stateCommitment
15883
+ stateCommitment,
15884
+ wrapPolicy
15576
15885
  });
15577
15886
  }
15578
15887
  const pendingAdds = Array.isArray(bs?.pending_adds) ? bs.pending_adds : [];
@@ -15612,11 +15921,12 @@ var _AUNClient = class _AUNClient {
15612
15921
  throw new E2EEError(`V2 group: no target devices for ${groupId}`);
15613
15922
  }
15614
15923
  const sender = await session.getSenderIdentity();
15924
+ const sendTargets = applyV2WrapPolicyToTargets(targets, wrapPolicy);
15615
15925
  const envelope = await encryptGroupMessage(
15616
15926
  sender,
15617
15927
  groupId,
15618
15928
  epoch,
15619
- targets,
15929
+ sendTargets,
15620
15930
  opts.payload,
15621
15931
  { messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context },
15622
15932
  stateCommitment
@@ -15697,7 +16007,7 @@ var _AUNClient = class _AUNClient {
15697
16007
  if (Array.isArray(recipients)) {
15698
16008
  for (const row of recipients) {
15699
16009
  if (Array.isArray(row) && row.length >= 6) {
15700
- if (row[0] === this._aid && row[1] === this._deviceId) {
16010
+ if (row[0] === this._aid && (row[1] === this._deviceId || row[1] === "")) {
15701
16011
  spkId = String(row[5] ?? "");
15702
16012
  recipientKeySource = row.length > 3 ? String(row[3] ?? "") : "";
15703
16013
  break;
@@ -15976,7 +16286,10 @@ var _AUNClient = class _AUNClient {
15976
16286
  if (aid === myAid) myRole = role;
15977
16287
  }
15978
16288
  if (myRole !== "owner" && myRole !== "admin") return false;
15979
- const bootstrapResp = await this.call("group.v2.bootstrap", { group_id: groupId });
16289
+ const bootstrapResp = await this.call("group.v2.bootstrap", {
16290
+ group_id: groupId,
16291
+ e2ee_wrap_capabilities: v2WrapCapabilities()
16292
+ });
15980
16293
  const devices = Array.isArray(bootstrapResp?.devices) ? bootstrapResp.devices : [];
15981
16294
  const candidates = [];
15982
16295
  for (const dev of devices) {
@@ -16065,7 +16378,10 @@ var _AUNClient = class _AUNClient {
16065
16378
  }
16066
16379
  }
16067
16380
  }
16068
- const bootstrapResp = await this.call("group.v2.bootstrap", { group_id: groupId });
16381
+ const bootstrapResp = await this.call("group.v2.bootstrap", {
16382
+ group_id: groupId,
16383
+ e2ee_wrap_capabilities: v2WrapCapabilities()
16384
+ });
16069
16385
  const allDevices = Array.isArray(bootstrapResp?.devices) ? bootstrapResp.devices : [];
16070
16386
  const auditRecipients = Array.isArray(bootstrapResp?.audit_recipients) ? bootstrapResp.audit_recipients : [];
16071
16387
  const auditAidsList = [...new Set(
@@ -16374,6 +16690,62 @@ var _AUNClient = class _AUNClient {
16374
16690
  this._clientLog.warn(`background task exception:${String(exc)}`);
16375
16691
  });
16376
16692
  }
16693
+ // ── Pull Gate(序列化同一 key 的并发 pull)──────────────────
16694
+ _pullGateKeyForCall(method, params) {
16695
+ if (method === "message.pull" || method === "message.v2.pull") {
16696
+ return this._aid ? `p2p:${this._aid}` : "";
16697
+ }
16698
+ if (method === "group.pull" || method === "group.v2.pull") {
16699
+ const gid = String(params.group_id ?? "").trim();
16700
+ return gid ? `group:${gid}` : "";
16701
+ }
16702
+ if (method === "group.pull_events") {
16703
+ const gid = String(params.group_id ?? "").trim();
16704
+ return gid ? `group_event:${gid}` : "";
16705
+ }
16706
+ return "";
16707
+ }
16708
+ _tryAcquirePullGate(key) {
16709
+ if (!key) return 0;
16710
+ const now = Date.now();
16711
+ const gate = this._pullGates.get(key) ?? { inflight: false, startedAt: 0, token: 0 };
16712
+ if (gate.inflight && now - gate.startedAt <= _AUNClient._PULL_GATE_STALE_MS) {
16713
+ return null;
16714
+ }
16715
+ if (gate.inflight) {
16716
+ this._clientLog.warn(`pull in-flight stale reset: key=${key} age=${now - gate.startedAt}ms`);
16717
+ }
16718
+ gate.token += 1;
16719
+ gate.inflight = true;
16720
+ gate.startedAt = now;
16721
+ this._pullGates.set(key, gate);
16722
+ return gate.token;
16723
+ }
16724
+ _releasePullGate(key, token) {
16725
+ if (!key || token == null) return;
16726
+ const gate = this._pullGates.get(key);
16727
+ if (!gate || gate.token !== token) return;
16728
+ gate.inflight = false;
16729
+ gate.startedAt = 0;
16730
+ }
16731
+ async _runPullSerialized(key, operation) {
16732
+ let token = this._tryAcquirePullGate(key);
16733
+ if (token === null) {
16734
+ const deadline = Date.now() + _AUNClient._PULL_GATE_STALE_MS + 100;
16735
+ while (token === null && Date.now() <= deadline) {
16736
+ await this._sleep(25);
16737
+ token = this._tryAcquirePullGate(key);
16738
+ }
16739
+ if (token === null) {
16740
+ throw new StateError(`pull already in-flight for ${key}`);
16741
+ }
16742
+ }
16743
+ try {
16744
+ return await operation();
16745
+ } finally {
16746
+ this._releasePullGate(key, token);
16747
+ }
16748
+ }
16377
16749
  /** 可取消的 sleep */
16378
16750
  _sleep(ms) {
16379
16751
  return new Promise((resolve) => {
@@ -16385,6 +16757,7 @@ __publicField(_AUNClient, "V2_BOOTSTRAP_TTL_MS", 60 * 60 * 1e3);
16385
16757
  __publicField(_AUNClient, "V2_RETRYABLE_CODES", /* @__PURE__ */ new Set([-33011, -33012, -33050, -33052, -33054]));
16386
16758
  __publicField(_AUNClient, "_V2_SIG_CACHE_TTL", 60 * 60 * 1e3);
16387
16759
  __publicField(_AUNClient, "_V2_SIG_CACHE_MAX", 16384);
16760
+ __publicField(_AUNClient, "_PULL_GATE_STALE_MS", 3e4);
16388
16761
  // ── 内部:断线重连 ────────────────────────────────
16389
16762
  /** 不重连 close code 集合:认证失败/权限错误/被踢等,重连无意义 */
16390
16763
  __publicField(_AUNClient, "_NO_RECONNECT_CODES", /* @__PURE__ */ new Set([4001, 4003, 4008, 4009, 4010, 4011, 4012, 4013, 4014, 4015]));
@@ -16455,7 +16828,7 @@ var ProtectedHeaders = class _ProtectedHeaders {
16455
16828
  };
16456
16829
 
16457
16830
  // src/index.ts
16458
- var __version__ = "0.3.2";
16831
+ var __version__ = "0.3.5";
16459
16832
  export {
16460
16833
  AUNClient,
16461
16834
  AUNError,
@@ -16480,6 +16853,7 @@ export {
16480
16853
  GroupError,
16481
16854
  GroupNotFoundError,
16482
16855
  GroupStateError,
16856
+ IdentityConflictError,
16483
16857
  IndexedDBKeyStore,
16484
16858
  IndexedDBSecretStore,
16485
16859
  MetaNamespace,
@@ -16490,6 +16864,7 @@ export {
16490
16864
  RPCTransport,
16491
16865
  RateLimitError,
16492
16866
  STATE_PREFIX,
16867
+ SeedMigrationError,
16493
16868
  SerializationError,
16494
16869
  SessionError,
16495
16870
  StateError,