liangzimixin 0.3.2 → 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.
package/dist/index.cjs CHANGED
@@ -17483,10 +17483,7 @@ var DEFAULT_INTERNAL_CONFIG = {
17483
17483
  }
17484
17484
  },
17485
17485
  auth: {
17486
- serverUrl: process.env.LZMX_AUTH_URL || "https://seal.example.com",
17487
- clientName: "liangzimixin",
17488
- scopes: ["openid", "im_sdk", "data_operate", "offline_access"],
17489
- autoRegister: true,
17486
+ serverUrl: process.env.LZMX_AUTH_URL || "https://imtwo.zdxlz.com/open-apis/v1",
17490
17487
  refreshAheadMs: 3e5
17491
17488
  },
17492
17489
  crypto: {
@@ -17974,7 +17971,7 @@ async function resolveAndUploadMedia(params) {
17974
17971
  };
17975
17972
  }
17976
17973
  const msgType = fileType;
17977
- const contentPayload = fileType === "file" ? { fileKey: uploadResult.fileKey, filename: fileName, size: uploadResult.fileSize } : { fileKey: uploadResult.fileKey };
17974
+ const contentPayload = fileType === "file" ? { fileId: uploadResult.fileKey, fileName, size: uploadResult.fileSize } : { fileId: uploadResult.fileKey };
17978
17975
  await messagePipe.sendMessage({
17979
17976
  chatId,
17980
17977
  senderId: chatId,
@@ -17984,7 +17981,7 @@ async function resolveAndUploadMedia(params) {
17984
17981
  });
17985
17982
  log3.info("media:sent", {
17986
17983
  chatId,
17987
- fileKey: uploadResult.fileKey,
17984
+ fileId: uploadResult.fileKey,
17988
17985
  msgType
17989
17986
  });
17990
17987
  return {
@@ -18111,20 +18108,20 @@ ${body}` : body || raw.content;
18111
18108
  }
18112
18109
  case "image": {
18113
18110
  const c = parsed;
18114
- fileId = c?.fileKey;
18111
+ fileId = c?.fileId;
18115
18112
  text = c?.altText ?? (fileId ? `![image](${fileId})` : "[image]");
18116
18113
  break;
18117
18114
  }
18118
18115
  case "file": {
18119
18116
  const c = parsed;
18120
- fileId = c?.fileKey;
18121
- fileName = c?.filename;
18117
+ fileId = c?.fileId;
18118
+ fileName = c?.fileName;
18122
18119
  text = fileName ? `[File: ${fileName}]` : "[file]";
18123
18120
  break;
18124
18121
  }
18125
18122
  case "voice": {
18126
18123
  const c = parsed;
18127
- fileId = c?.fileKey;
18124
+ fileId = c?.fileId;
18128
18125
  const durationMs = c?.duration;
18129
18126
  const durationStr = durationMs != null ? ` ${(durationMs / 1e3).toFixed(1)}s` : "";
18130
18127
  text = `[Voice${durationStr}]`;
@@ -18132,7 +18129,7 @@ ${body}` : body || raw.content;
18132
18129
  }
18133
18130
  case "video": {
18134
18131
  const c = parsed;
18135
- fileId = c?.fileKey;
18132
+ fileId = c?.fileId;
18136
18133
  const durationMs = c?.duration;
18137
18134
  const durationStr = durationMs != null ? ` ${(durationMs / 1e3).toFixed(1)}s` : "";
18138
18135
  text = `[Video${durationStr}]`;
@@ -19008,13 +19005,13 @@ var CryptoEngine = class _CryptoEngine {
19008
19005
 
19009
19006
  // src/auth/oauth-client.ts
19010
19007
  var log16 = createLogger("auth/oauth-client");
19011
- var SealApiError = class extends Error {
19008
+ var ApiError = class extends Error {
19012
19009
  constructor(status, body, endpoint) {
19013
- super(`Seal API error: ${status} on ${endpoint}`);
19010
+ super(`API error: ${status} on ${endpoint}`);
19014
19011
  this.status = status;
19015
19012
  this.body = body;
19016
19013
  this.endpoint = endpoint;
19017
- this.name = "SealApiError";
19014
+ this.name = "ApiError";
19018
19015
  }
19019
19016
  };
19020
19017
  var TokenAcquireError = class extends Error {
@@ -19031,28 +19028,21 @@ function sleep(ms) {
19031
19028
  return new Promise((resolve2) => setTimeout(resolve2, ms));
19032
19029
  }
19033
19030
  var DEFAULT_TIMEOUT_MS = 3e4;
19034
- var SealClient = class {
19031
+ var GRANT_TYPE = "client_credentials";
19032
+ var SCOPE = "client_credentials refresh_token";
19033
+ var OAuthClient = class {
19035
19034
  baseUrl;
19036
- clientId;
19037
- clientSecret;
19038
- clientName;
19039
- scopes;
19035
+ appId;
19036
+ appSecret;
19040
19037
  constructor(config2) {
19041
- this.baseUrl = config2.serverUrl.replace(/\/+$/, "");
19042
- this.clientId = config2.clientId;
19043
- this.clientSecret = config2.clientSecret;
19044
- this.clientName = config2.clientName;
19045
- this.scopes = config2.scopes;
19046
- log16.info("SealClient initialized", { serverUrl: this.baseUrl, clientName: config2.clientName });
19038
+ this.baseUrl = config2.baseUrl.replace(/\/+$/, "");
19039
+ this.appId = config2.appId;
19040
+ this.appSecret = config2.appSecret;
19041
+ log16.info("OAuthClient initialized", { baseUrl: this.baseUrl, appId: sanitize(this.appId) });
19047
19042
  }
19048
19043
  // ── 通用 HTTP 请求 ──
19049
19044
  /**
19050
19045
  * 统一 HTTP 请求封装 — 含超时、日志脱敏和错误处理
19051
- * @param method - HTTP 方法
19052
- * @param path - 请求路径 (如 '/seal/client/register')
19053
- * @param options - 请求选项
19054
- * @returns 解析后的 JSON 响应
19055
- * @throws SealApiError — HTTP 非 2xx 响应
19056
19046
  */
19057
19047
  async request(method, path2, options = {}) {
19058
19048
  const url2 = `${this.baseUrl}${path2}`;
@@ -19084,109 +19074,38 @@ var SealClient = class {
19084
19074
  parsedBody = responseBody;
19085
19075
  }
19086
19076
  log16.error("HTTP error", { method, url: url2, status: response.status });
19087
- throw new SealApiError(response.status, parsedBody, path2);
19077
+ throw new ApiError(response.status, parsedBody, path2);
19088
19078
  }
19089
19079
  log16.debug("HTTP response", { method, url: url2, status: response.status });
19090
19080
  try {
19091
19081
  return JSON.parse(responseBody);
19092
19082
  } catch {
19093
- throw new SealApiError(response.status, responseBody, path2);
19083
+ throw new ApiError(response.status, responseBody, path2);
19094
19084
  }
19095
19085
  }
19096
19086
  // ── 公共方法 ──
19097
- /** 判断是否已注册 (有 clientId + clientSecret) */
19098
- isRegistered() {
19099
- return !!(this.clientId && this.clientSecret);
19100
- }
19101
- /** 更新 clientId / clientSecret (注册成功后或从持久化加载后调用) */
19102
- setCredentials(clientId, clientSecret) {
19103
- this.clientId = clientId;
19104
- this.clientSecret = clientSecret;
19105
- log16.info("Credentials updated", { clientId: sanitize(clientId) });
19106
- }
19107
- /**
19108
- * 动态注册客户端 → POST /seal/client/register
19109
- *
19110
- * 首次运行时自动调用,获取 clientId 和 clientSecret。
19111
- * 注册成功后自动更新内部凭据。
19112
- */
19113
- async register(params) {
19114
- const body = {
19115
- client_name: params.clientName,
19116
- scopes: params.scopes,
19117
- authentication_methods: params.authMethods ?? ["client_secret_post"],
19118
- grant_types: params.grantTypes ?? ["authorization_code", "client_credentials", "refresh_token"],
19119
- redirect_uris: params.redirectUris ?? ["https://placeholder.local"],
19120
- logout_redirect_uris: [],
19121
- client_settings: {
19122
- "settings.client.require-authorization-consent": true
19123
- }
19124
- };
19125
- if (params.tokenSettings) {
19126
- const ts = {};
19127
- if (params.tokenSettings.accessTokenTtl) {
19128
- ts["settings.token.access-token-time-to-live"] = params.tokenSettings.accessTokenTtl;
19129
- }
19130
- if (params.tokenSettings.refreshTokenTtl) {
19131
- ts["settings.token.refresh-token-time-to-live"] = params.tokenSettings.refreshTokenTtl;
19132
- }
19133
- if (params.tokenSettings.accessTokenFormat) {
19134
- ts["settings.token.access-token-format"] = params.tokenSettings.accessTokenFormat;
19135
- }
19136
- if (params.tokenSettings.reuseRefreshTokens !== void 0) {
19137
- ts["settings.token.reuse-refresh-tokens"] = params.tokenSettings.reuseRefreshTokens;
19138
- }
19139
- ts["settings.token.authorization-code-time-to-live"] = "300";
19140
- body.token_settings = ts;
19141
- }
19142
- log16.info("Registering OAuth client", { clientName: params.clientName });
19143
- const response = await this.request(
19144
- "POST",
19145
- "/seal/client/register",
19146
- { body, contentType: "json" }
19147
- );
19148
- const registration = {
19149
- clientId: response.client_id,
19150
- clientSecret: response.client_secret,
19151
- clientSecretExpiresAt: response.client_secret_expires_at ?? "",
19152
- clientIdIssuedAt: response.client_id_issued_at ?? "",
19153
- scopes: response.scopes ?? params.scopes
19154
- };
19155
- this.setCredentials(registration.clientId, registration.clientSecret);
19156
- log16.info("OAuth client registered", {
19157
- clientId: sanitize(registration.clientId),
19158
- clientSecret: sanitize(registration.clientSecret),
19159
- scopes: registration.scopes,
19160
- expiresAt: registration.clientSecretExpiresAt
19161
- });
19162
- return registration;
19163
- }
19164
19087
  /**
19165
- * 获取访问令牌 → POST /seal/oauth2/token (client_credentials)
19088
+ * 获取访问令牌 → POST /auth/token (client_credentials)
19166
19089
  *
19167
- * 使用客户端凭证模式获取 Access Token。
19168
- * 前置条件: clientId 和 clientSecret 必须存在 (通过 register 或 setCredentials 设置)
19090
+ * 请求参数 (x-www-form-urlencoded):
19091
+ * - grant_type: client_credentials (写死)
19092
+ * - client_id: appId
19093
+ * - client_secret: appSecret
19094
+ * - scope: client_credentials refresh_token (写死)
19169
19095
  *
19170
- * @param scope - 请求的权限范围 (空格分隔),不传则使用注册时的全部 scope
19171
19096
  * @returns TokenData 包含 access_token、过期时间等
19172
- * @throws ErrorclientId/clientSecret 未设置
19173
- * @throws SealApiError — HTTP 错误
19097
+ * @throws ApiErrorHTTP 错误
19174
19098
  */
19175
- async getToken(scope) {
19176
- if (!this.clientId || !this.clientSecret) {
19177
- throw new Error("Cannot get token: clientId and clientSecret are required. Call register() or setCredentials() first.");
19178
- }
19099
+ async getToken() {
19179
19100
  const params = new URLSearchParams();
19180
- params.set("grant_type", "client_credentials");
19181
- params.set("client_id", this.clientId);
19182
- params.set("client_secret", this.clientSecret);
19183
- if (scope) {
19184
- params.set("scope", scope);
19185
- }
19186
- log16.info("Requesting access token", { clientId: sanitize(this.clientId) });
19101
+ params.set("grant_type", GRANT_TYPE);
19102
+ params.set("client_id", this.appId);
19103
+ params.set("client_secret", this.appSecret);
19104
+ params.set("scope", SCOPE);
19105
+ log16.info("Requesting access token", { appId: sanitize(this.appId) });
19187
19106
  const response = await this.request(
19188
19107
  "POST",
19189
- "/seal/oauth2/token",
19108
+ "/auth/token",
19190
19109
  { body: params, contentType: "form" }
19191
19110
  );
19192
19111
  const now = Date.now();
@@ -19197,7 +19116,6 @@ var SealClient = class {
19197
19116
  expiresIn,
19198
19117
  scope: response.scope ?? "",
19199
19118
  refreshToken: void 0,
19200
- // Seal 不返回 refresh_token
19201
19119
  grantedAt: now,
19202
19120
  expiresAt: now + expiresIn * 1e3
19203
19121
  };
@@ -19208,12 +19126,9 @@ var SealClient = class {
19208
19126
  });
19209
19127
  return tokenData;
19210
19128
  }
19211
- /**
19212
- * 令牌校验 → GET /seal/oauth2/introspect
19213
- * 暂不实现 — 保留接口签名,后续需要时再补充
19214
- */
19215
- async introspect(_token) {
19216
- throw new Error("introspect() not implemented yet \u2014 Seal introspect API \u6682\u4E0D\u4F7F\u7528");
19129
+ /** 获取 baseUrl (供其他模块复用) */
19130
+ getBaseUrl() {
19131
+ return this.baseUrl;
19217
19132
  }
19218
19133
  };
19219
19134
 
@@ -19347,12 +19262,10 @@ var DEFAULT_REFRESH_AHEAD_MS = 5 * 60 * 1e3;
19347
19262
  var MAX_RETRIES = 3;
19348
19263
  var RETRY_BASE_MS = 1e3;
19349
19264
  var TokenManager = class {
19350
- sealClient;
19265
+ oauthClient;
19351
19266
  tokenStore;
19352
19267
  /** Token 过期前提前刷新的时间 (ms) */
19353
19268
  refreshAheadMs;
19354
- /** 自动注册的参数 */
19355
- autoRegisterParams;
19356
19269
  // ── 内存缓存 ──
19357
19270
  /** 内存中缓存的 access_token */
19358
19271
  cachedToken = null;
@@ -19366,10 +19279,9 @@ var TokenManager = class {
19366
19279
  /** 定时刷新器句柄 */
19367
19280
  refreshTimer = null;
19368
19281
  constructor(deps) {
19369
- this.sealClient = deps.sealClient;
19282
+ this.oauthClient = deps.oauthClient;
19370
19283
  this.tokenStore = deps.tokenStore;
19371
19284
  this.refreshAheadMs = deps.refreshAheadMs ?? DEFAULT_REFRESH_AHEAD_MS;
19372
- this.autoRegisterParams = deps.autoRegisterParams;
19373
19285
  log18.info("TokenManager initialized", { refreshAheadMs: this.refreshAheadMs });
19374
19286
  }
19375
19287
  // ── 核心方法 ──
@@ -19398,13 +19310,6 @@ var TokenManager = class {
19398
19310
  this.refreshLock = null;
19399
19311
  }
19400
19312
  }
19401
- /**
19402
- * 令牌自省 — 暂不实现,后续补充。
19403
- * 返回的 identity_id 用于 gate.ts 的 anti-loop 检查 (bot 自己的 ID)。
19404
- */
19405
- async introspect() {
19406
- throw new Error("introspect() not implemented yet \u2014 Seal introspect API \u6682\u4E0D\u4F7F\u7528");
19407
- }
19408
19313
  /** 检查当前令牌是否包含指定的 scope */
19409
19314
  hasScope(scope) {
19410
19315
  if (!this.cachedScope) return false;
@@ -19435,14 +19340,14 @@ var TokenManager = class {
19435
19340
  *
19436
19341
  * 重试策略:
19437
19342
  * - 网络错误 / 5xx → 重试 (最多 3 次)
19438
- * - 401/403 → 不重试 (client_secret 可能无效)
19343
+ * - 401/403 → 不重试 (凭证可能无效)
19439
19344
  */
19440
19345
  async _acquireTokenWithRetry() {
19441
19346
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
19442
19347
  try {
19443
19348
  return await this._acquireToken();
19444
19349
  } catch (err) {
19445
- if (err instanceof SealApiError && (err.status === 401 || err.status === 403)) {
19350
+ if (err instanceof ApiError && (err.status === 401 || err.status === 403)) {
19446
19351
  log18.error("Token acquire failed with auth error, not retrying", { status: err.status });
19447
19352
  throw err;
19448
19353
  }
@@ -19470,10 +19375,9 @@ var TokenManager = class {
19470
19375
  *
19471
19376
  * 流程:
19472
19377
  * 1. 尝试从 TokenStore 加载缓存
19473
- * 2. 检查是否需要注册客户端
19474
- * 3. 调用 SealClient.getToken() 获取新 Token
19475
- * 4. 保存到内存缓存 + TokenStore
19476
- * 5. 设置定时刷新器
19378
+ * 2. 调用 OAuthClient.getToken() 获取新 Token
19379
+ * 3. 保存到内存缓存 + TokenStore
19380
+ * 4. 设置定时刷新器
19477
19381
  */
19478
19382
  async _acquireToken() {
19479
19383
  try {
@@ -19490,43 +19394,8 @@ var TokenManager = class {
19490
19394
  } catch {
19491
19395
  log18.warn("Failed to load token from store, acquiring new one");
19492
19396
  }
19493
- if (!this.sealClient.isRegistered()) {
19494
- try {
19495
- const savedCreds = await this.tokenStore.load("credentials");
19496
- if (savedCreds?.clientId && savedCreds?.clientSecret) {
19497
- this.sealClient.setCredentials(
19498
- savedCreds.clientId,
19499
- savedCreds.clientSecret
19500
- );
19501
- log18.info("Credentials loaded from store");
19502
- }
19503
- } catch {
19504
- log18.debug("No saved credentials found");
19505
- }
19506
- }
19507
- if (!this.sealClient.isRegistered()) {
19508
- if (!this.autoRegisterParams) {
19509
- throw new Error("SealClient not registered and autoRegisterParams not provided. Call setCredentials() or enable autoRegister.");
19510
- }
19511
- log18.info("Auto-registering OAuth client");
19512
- const registration = await this.sealClient.register({
19513
- clientName: this.autoRegisterParams.clientName,
19514
- scopes: this.autoRegisterParams.scopes,
19515
- tokenSettings: this.autoRegisterParams.tokenSettings ? {
19516
- accessTokenTtl: this.autoRegisterParams.tokenSettings.accessTokenTtl,
19517
- refreshTokenTtl: this.autoRegisterParams.tokenSettings.refreshTokenTtl,
19518
- accessTokenFormat: this.autoRegisterParams.tokenSettings.accessTokenFormat
19519
- } : void 0
19520
- });
19521
- await this.tokenStore.save("credentials", {
19522
- clientId: registration.clientId,
19523
- clientSecret: registration.clientSecret,
19524
- clientSecretExpiresAt: registration.clientSecretExpiresAt
19525
- });
19526
- log18.info("OAuth client registered and credentials saved");
19527
- }
19528
- log18.info("Acquiring new access token");
19529
- const tokenData = await this.sealClient.getToken();
19397
+ log18.info("Acquiring new access token via POST /auth/token");
19398
+ const tokenData = await this.oauthClient.getToken();
19530
19399
  this.updateCache(tokenData);
19531
19400
  await this.tokenStore.save("token", tokenData);
19532
19401
  this.scheduleRefresh(tokenData);
@@ -19883,11 +19752,18 @@ var MessagePipe = class {
19883
19752
  log21.warn("inbound:data-parse-error", { error: err.message });
19884
19753
  return;
19885
19754
  }
19886
- if (callbackData.eventType !== "MessageReceived") {
19755
+ if (callbackData.eventType !== "callback:direct") {
19887
19756
  log21.debug("inbound:event-ignored", { eventType: callbackData.eventType });
19888
19757
  return;
19889
19758
  }
19890
- const msg = callbackData.content;
19759
+ const msg = {
19760
+ messageId: callbackData.msgUid,
19761
+ chatId: callbackData.groupId || callbackData.userId,
19762
+ senderId: callbackData.userId,
19763
+ msgType: callbackData.type,
19764
+ content: JSON.stringify(callbackData.content),
19765
+ timestamp: Date.now()
19766
+ };
19891
19767
  log21.debug("inbound:frame", { messageId: msg.messageId, eventType: callbackData.eventType });
19892
19768
  if (this.dedup.isDuplicate(msg.messageId)) {
19893
19769
  log21.debug("inbound:dedup-hit", { messageId: msg.messageId });
@@ -19948,10 +19824,14 @@ var ConnectionManager = class {
19948
19824
  this.running = true;
19949
19825
  this.reconnectAttempts = 0;
19950
19826
  const token = await tokenFn();
19827
+ const wsUrl = url2.replace(/\/+$/, "") + "/events/stream";
19951
19828
  await this.client.connect({
19952
- url: url2,
19829
+ url: wsUrl,
19953
19830
  token,
19954
- headers: this.appId ? { "X-App-ID": this.appId } : void 0
19831
+ headers: {
19832
+ "Authorization": token,
19833
+ ...this.appId ? { "X-App-ID": this.appId } : {}
19834
+ }
19955
19835
  });
19956
19836
  this.client.on("close", () => {
19957
19837
  if (this.running) {
@@ -20004,10 +19884,14 @@ var ConnectionManager = class {
20004
19884
  this.reconnectAttempts++;
20005
19885
  try {
20006
19886
  const token = await this.tokenFn();
19887
+ const wsUrl = this.url.replace(/\/+$/, "") + "/events/stream";
20007
19888
  await this.client.connect({
20008
- url: this.url,
19889
+ url: wsUrl,
20009
19890
  token,
20010
- headers: this.appId ? { "X-App-ID": this.appId } : void 0
19891
+ headers: {
19892
+ "Authorization": token,
19893
+ ...this.appId ? { "X-App-ID": this.appId } : {}
19894
+ }
20011
19895
  });
20012
19896
  this.reconnectAttempts = 0;
20013
19897
  this.startHeartbeat();
@@ -20432,28 +20316,17 @@ async function startPlugin(accountConfig, internalOverrides) {
20432
20316
  cryptoEngine = CryptoEngine.createPassthrough();
20433
20317
  log25.info("Crypto passthrough mode \u2713 (disabled by config)");
20434
20318
  }
20435
- const sealClient = new SealClient({
20436
- serverUrl: config2.auth.serverUrl,
20437
- clientName: config2.auth.clientName,
20438
- clientId: config2.auth.clientId,
20439
- clientSecret: config2.auth.clientSecret,
20440
- scopes: config2.auth.scopes
20319
+ const oauthClient = new OAuthClient({
20320
+ baseUrl: config2.auth.serverUrl,
20321
+ appId: accountConfig.appId,
20322
+ appSecret: accountConfig.appSecret
20441
20323
  });
20442
20324
  const tokenStorePath = (0, import_node_path2.join)((0, import_node_os.homedir)(), TOKEN_STORE_DIR, "tokens");
20443
20325
  const tokenStore = new TokenStore(tokenStorePath, cryptoEngine);
20444
20326
  const tokenManager = new TokenManager({
20445
- sealClient,
20327
+ oauthClient,
20446
20328
  tokenStore,
20447
- refreshAheadMs: config2.auth.refreshAheadMs,
20448
- autoRegisterParams: config2.auth.autoRegister ? {
20449
- clientName: config2.auth.clientName,
20450
- scopes: config2.auth.scopes,
20451
- tokenSettings: config2.auth.tokenSettings ? {
20452
- accessTokenTtl: config2.auth.tokenSettings.accessTokenTtl,
20453
- refreshTokenTtl: config2.auth.tokenSettings.refreshTokenTtl,
20454
- accessTokenFormat: config2.auth.tokenSettings.accessTokenFormat
20455
- } : void 0
20456
- } : void 0
20329
+ refreshAheadMs: config2.auth.refreshAheadMs
20457
20330
  });
20458
20331
  log25.info("Auth initialized \u2713");
20459
20332
  let pushQueue = null;
package/dist/index.d.cts CHANGED
@@ -64,26 +64,10 @@ interface InternalConfig {
64
64
  };
65
65
  };
66
66
  auth: {
67
- /** 🔴 OAuth 认证服务器地址 */
67
+ /** 🔴 API 基础地址 (如 https://imtwo.zdxlz.com/open-apis/v1) */
68
68
  serverUrl: string;
69
- /** 🔴 OAuth 客户端显示名称 */
70
- clientName: string;
71
- /** 🟡 OAuth Client ID (不填则自动注册) */
72
- clientId?: string;
73
- /** 🟡 OAuth Client Secret */
74
- clientSecret?: string;
75
- /** 🟡 OAuth 授权范围 */
76
- scopes: string[];
77
- /** 🟡 是否自动注册 OAuth 客户端 */
78
- autoRegister: boolean;
79
69
  /** 🟡 Token 提前刷新时间 (ms) */
80
70
  refreshAheadMs: number;
81
- /** 🟡 Token 高级设置 */
82
- tokenSettings?: {
83
- accessTokenTtl: string;
84
- refreshTokenTtl: string;
85
- accessTokenFormat: string;
86
- };
87
71
  };
88
72
  crypto: {
89
73
  /** 🟡 是否启用量子加密 */
@@ -162,10 +146,10 @@ interface InboundMessage {
162
146
  * 根据 msgType 解析为对应的 Content 类型:
163
147
  * - text: `{"content": "你好"}`
164
148
  * - markdown: `{"title": "标题", "content": "正文", "recommendations": [...]}`
165
- * - image: `{"fileKey": "f_abc", "width": 800, "height": 600}`
166
- * - file: `{"fileKey": "f_xyz", "filename": "报告.pdf", "size": 1048576}`
167
- * - voice: `{"fileKey": "f_aud", "duration": 5000}`
168
- * - video: `{"fileKey": "f_vid", "duration": 120, "width": 1920, "height": 1080}`
149
+ * - image: `{"fileId": "xxx", "width": 1920, "height": 1200}`
150
+ * - file: `{"fileId": "xxx", "fileName": "test.xlsx", "size": 9763}`
151
+ * - voice: `{"fileId": "xxx", "duration": 2914.23}`
152
+ * - video: `{"fileId": "xxx", "duration": 5291667.32, "width": 1200}`
169
153
  * - system: `{"text": "用户已加入会话"}`
170
154
  */
171
155
  content: string;
@@ -204,31 +188,7 @@ declare enum EncryptionStrategy {
204
188
  METADATA = "metadata",
205
189
  NONE = "none"
206
190
  }
207
- /** Seal OAuth 服务器连接配置 */
208
- interface SealConfig {
209
- /** OAuth 服务器地址 */
210
- serverUrl: string;
211
- /** 客户端 ID (动态注册后获取) */
212
- clientId?: string;
213
- /** 客户端 Secret (动态注册后获取) */
214
- clientSecret?: string;
215
- /** 客户端显示名称 (注册时使用) */
216
- clientName: string;
217
- /** 请求的 OAuth scope 列表 */
218
- scopes: string[];
219
- }
220
- /** 客户端动态注册结果 — POST /seal/client/register 响应 */
221
- interface SealClientRegistration {
222
- clientId: string;
223
- clientSecret: string;
224
- /** Secret 过期时间 (ISO 8601) */
225
- clientSecretExpiresAt: string;
226
- /** 客户端 ID 签发时间 (ISO 8601) */
227
- clientIdIssuedAt: string;
228
- /** 实际授权的 scope 列表 */
229
- scopes: string[];
230
- }
231
- /** OAuth 令牌数据 — 包含 access_token 和可选的 refresh_token */
191
+ /** OAuth 令牌数据 — POST /auth/token 响应 */
232
192
  interface TokenData {
233
193
  accessToken: string;
234
194
  tokenType: 'Bearer';
@@ -242,23 +202,12 @@ interface TokenData {
242
202
  /** 签发时间戳 (Unix ms) */
243
203
  grantedAt: number;
244
204
  }
245
- /** Token 自省结果 — GET /seal/oauth2/introspect 响应 */
246
- interface IntrospectResult {
247
- /** 令牌类型标识 */
248
- schema: string;
249
- /** 客户端 ID */
250
- clientId: string;
251
- /** 用户身份 ID (用于 gate.ts anti-loop 检查: 识别 bot 自己的 ID) */
252
- identityId: string;
253
- /** 实际授权的 scope */
254
- scope: string;
255
- }
256
205
 
257
206
  /**
258
- * openclaw-liangzimixin — Seal OAuth HTTP 客户端
207
+ * openclaw-liangzimixin — OAuth HTTP 客户端
259
208
  *
260
- * 封装 Seal 授权服务器通信: register / getToken
261
- * 基于《seal认证技术文档》OAuth2 Client Credentials 模式
209
+ * 封装 IM 开放平台授权通信: POST /auth/token (client_credentials)
210
+ * 基于《第三方应用机器人对接 API 文档》
262
211
  *
263
212
  * 安全要求:
264
213
  * - client_secret / access_token 不得出现在日志中 (仅前 8 位 + ***)
@@ -266,73 +215,48 @@ interface IntrospectResult {
266
215
  */
267
216
 
268
217
  /**
269
- * Seal OAuth HTTP 客户端 封装与 Seal 授权服务器的所有 HTTP 通信。
270
- * 提供核心操作: 动态注册 / 获取令牌
218
+ * OAuth 客户端配置构造 OAuthClient 时传入
219
+ */
220
+ interface OAuthClientConfig {
221
+ /** API 基础地址 (如 https://imtwo.zdxlz.com/open-apis/v1) */
222
+ baseUrl: string;
223
+ /** 应用 ID — 用作 OAuth client_id */
224
+ appId: string;
225
+ /** 应用密钥 — 用作 OAuth client_secret */
226
+ appSecret: string;
227
+ }
228
+ /**
229
+ * OAuth HTTP 客户端 — 封装与 IM 开放平台的认证通信。
271
230
  *
272
- * Introspect 暂不实现,保留接口签名。
231
+ * 对接《第三方应用机器人对接 API 文档》中的授权接口:
232
+ * POST {{baseUrl}}/auth/token
233
+ * Content-Type: application/x-www-form-urlencoded
234
+ * grant_type=client_credentials & client_id={appId} & client_secret={appSecret} & scope=...
273
235
  */
274
- declare class SealClient {
236
+ declare class OAuthClient {
275
237
  private readonly baseUrl;
276
- private clientId?;
277
- private clientSecret?;
278
- private readonly clientName;
279
- private readonly scopes;
280
- constructor(config: SealConfig);
238
+ private readonly appId;
239
+ private readonly appSecret;
240
+ constructor(config: OAuthClientConfig);
281
241
  /**
282
242
  * 统一 HTTP 请求封装 — 含超时、日志脱敏和错误处理
283
- * @param method - HTTP 方法
284
- * @param path - 请求路径 (如 '/seal/client/register')
285
- * @param options - 请求选项
286
- * @returns 解析后的 JSON 响应
287
- * @throws SealApiError — HTTP 非 2xx 响应
288
243
  */
289
244
  private request;
290
- /** 判断是否已注册 (有 clientId + clientSecret) */
291
- isRegistered(): boolean;
292
- /** 更新 clientId / clientSecret (注册成功后或从持久化加载后调用) */
293
- setCredentials(clientId: string, clientSecret: string): void;
294
245
  /**
295
- * 动态注册客户端 → POST /seal/client/register
246
+ * 获取访问令牌 → POST /auth/token (client_credentials)
296
247
  *
297
- * 首次运行时自动调用,获取 clientId 和 clientSecret。
298
- * 注册成功后自动更新内部凭据。
299
- */
300
- register(params: {
301
- /** 客户端显示名称 */
302
- clientName: string;
303
- /** 请求的 OAuth scope */
304
- scopes: string[];
305
- /** 授权类型 (默认包含 authorization_code, client_credentials, refresh_token) */
306
- grantTypes?: string[];
307
- /** 认证方法 (默认 ['client_secret_post']) */
308
- authMethods?: string[];
309
- /** 重定向 URI (默认 ['https://placeholder.local']) */
310
- redirectUris?: string[];
311
- /** Token 高级设置 */
312
- tokenSettings?: {
313
- accessTokenTtl?: string;
314
- refreshTokenTtl?: string;
315
- accessTokenFormat?: string;
316
- reuseRefreshTokens?: boolean;
317
- };
318
- }): Promise<SealClientRegistration>;
319
- /**
320
- * 获取访问令牌 → POST /seal/oauth2/token (client_credentials)
248
+ * 请求参数 (x-www-form-urlencoded):
249
+ * - grant_type: client_credentials (写死)
250
+ * - client_id: appId
251
+ * - client_secret: appSecret
252
+ * - scope: client_credentials refresh_token (写死)
321
253
  *
322
- * 使用客户端凭证模式获取 Access Token。
323
- * 前置条件: clientId 和 clientSecret 必须存在 (通过 register 或 setCredentials 设置)
324
- *
325
- * @param scope - 请求的权限范围 (空格分隔),不传则使用注册时的全部 scope
326
254
  * @returns TokenData 包含 access_token、过期时间等
327
- * @throws ErrorclientId/clientSecret 未设置
328
- * @throws SealApiError — HTTP 错误
255
+ * @throws ApiErrorHTTP 错误
329
256
  */
330
- getToken(scope?: string): Promise<TokenData>;
331
- /**
332
- * 令牌校验 → GET /seal/oauth2/introspect
333
- * 暂不实现 — 保留接口签名,后续需要时再补充
334
- */
335
- introspect(_token: string): Promise<IntrospectResult>;
257
+ getToken(): Promise<TokenData>;
258
+ /** 获取 baseUrl (供其他模块复用) */
259
+ getBaseUrl(): string;
336
260
  }
337
261
 
338
262
  /**
@@ -540,11 +464,11 @@ declare class TokenStore {
540
464
  /**
541
465
  * openclaw-liangzimixin — Token 生命周期管理
542
466
  *
543
- * 自动获取 / 缓存 / 过期重取 / 并发锁 / scope 检查 / 定时刷新
467
+ * 自动获取 / 缓存 / 过期重取 / 并发锁 / 定时刷新
544
468
  *
545
469
  * Token 生命周期策略:
546
- * - 首次调用: 检查本地缓存 → 未命中则注册(autoRegister) 获取 Token → 缓存
547
- * - 过期处理: 直接重新调用 POST /seal/oauth2/token 获取新 Token (无 refresh_token)
470
+ * - 首次调用: 检查本地缓存 → 未命中则调用 POST /auth/token → 缓存
471
+ * - 过期处理: 直接重新调用 getToken() 获取新 Token
548
472
  * - 提前刷新: Token 过期前 refreshAheadMs 毫秒自动重新获取
549
473
  * - 并发锁: 多个并发请求共享同一个 Token 获取 Promise
550
474
  *
@@ -565,12 +489,10 @@ declare class TokenStore {
565
489
  * shutdown() — 清理定时器和并发锁
566
490
  */
567
491
  declare class TokenManager {
568
- private readonly sealClient;
492
+ private readonly oauthClient;
569
493
  private readonly tokenStore;
570
494
  /** Token 过期前提前刷新的时间 (ms) */
571
495
  private readonly refreshAheadMs;
572
- /** 自动注册的参数 */
573
- private readonly autoRegisterParams?;
574
496
  /** 内存中缓存的 access_token */
575
497
  private cachedToken;
576
498
  /** 内存中缓存的 scope (用于 hasScope 检查) */
@@ -582,19 +504,9 @@ declare class TokenManager {
582
504
  /** 定时刷新器句柄 */
583
505
  private refreshTimer;
584
506
  constructor(deps: {
585
- sealClient: SealClient;
507
+ oauthClient: OAuthClient;
586
508
  tokenStore: TokenStore;
587
509
  refreshAheadMs?: number;
588
- /** 自动注册参数 — autoRegister=true 时使用 */
589
- autoRegisterParams?: {
590
- clientName: string;
591
- scopes: string[];
592
- tokenSettings?: {
593
- accessTokenTtl?: string;
594
- refreshTokenTtl?: string;
595
- accessTokenFormat?: string;
596
- };
597
- };
598
510
  });
599
511
  /**
600
512
  * 获取有效的 access_token — 自动获取/重取,含并发锁。
@@ -607,11 +519,6 @@ declare class TokenManager {
607
519
  * @returns 有效的 access_token 字符串
608
520
  */
609
521
  getValidToken(): Promise<string>;
610
- /**
611
- * 令牌自省 — 暂不实现,后续补充。
612
- * 返回的 identity_id 用于 gate.ts 的 anti-loop 检查 (bot 自己的 ID)。
613
- */
614
- introspect(): Promise<IntrospectResult>;
615
522
  /** 检查当前令牌是否包含指定的 scope */
616
523
  hasScope(scope: string): boolean;
617
524
  /** 检查是否已授权 (是否持有有效令牌) */
@@ -625,7 +532,7 @@ declare class TokenManager {
625
532
  *
626
533
  * 重试策略:
627
534
  * - 网络错误 / 5xx → 重试 (最多 3 次)
628
- * - 401/403 → 不重试 (client_secret 可能无效)
535
+ * - 401/403 → 不重试 (凭证可能无效)
629
536
  */
630
537
  private _acquireTokenWithRetry;
631
538
  /**
@@ -633,10 +540,9 @@ declare class TokenManager {
633
540
  *
634
541
  * 流程:
635
542
  * 1. 尝试从 TokenStore 加载缓存
636
- * 2. 检查是否需要注册客户端
637
- * 3. 调用 SealClient.getToken() 获取新 Token
638
- * 4. 保存到内存缓存 + TokenStore
639
- * 5. 设置定时刷新器
543
+ * 2. 调用 OAuthClient.getToken() 获取新 Token
544
+ * 3. 保存到内存缓存 + TokenStore
545
+ * 4. 设置定时刷新器
640
546
  */
641
547
  private _acquireToken;
642
548
  /** 更新内存缓存 */
@@ -4120,7 +4120,7 @@ async function resolveAndUploadMedia(params) {
4120
4120
  };
4121
4121
  }
4122
4122
  const msgType = fileType;
4123
- const contentPayload = fileType === "file" ? { fileKey: uploadResult.fileKey, filename: fileName, size: uploadResult.fileSize } : { fileKey: uploadResult.fileKey };
4123
+ const contentPayload = fileType === "file" ? { fileId: uploadResult.fileKey, fileName, size: uploadResult.fileSize } : { fileId: uploadResult.fileKey };
4124
4124
  await messagePipe.sendMessage({
4125
4125
  chatId,
4126
4126
  senderId: chatId,
@@ -4130,7 +4130,7 @@ async function resolveAndUploadMedia(params) {
4130
4130
  });
4131
4131
  log3.info("media:sent", {
4132
4132
  chatId,
4133
- fileKey: uploadResult.fileKey,
4133
+ fileId: uploadResult.fileKey,
4134
4134
  msgType
4135
4135
  });
4136
4136
  return {
@@ -18039,10 +18039,7 @@ var DEFAULT_INTERNAL_CONFIG = {
18039
18039
  }
18040
18040
  },
18041
18041
  auth: {
18042
- serverUrl: process.env.LZMX_AUTH_URL || "https://seal.example.com",
18043
- clientName: "liangzimixin",
18044
- scopes: ["openid", "im_sdk", "data_operate", "offline_access"],
18045
- autoRegister: true,
18042
+ serverUrl: process.env.LZMX_AUTH_URL || "https://imtwo.zdxlz.com/open-apis/v1",
18046
18043
  refreshAheadMs: 3e5
18047
18044
  },
18048
18045
  crypto: {
@@ -18266,13 +18263,13 @@ var CryptoEngine = class _CryptoEngine {
18266
18263
 
18267
18264
  // src/auth/oauth-client.ts
18268
18265
  var log7 = createLogger("auth/oauth-client");
18269
- var SealApiError = class extends Error {
18266
+ var ApiError = class extends Error {
18270
18267
  constructor(status, body, endpoint) {
18271
- super(`Seal API error: ${status} on ${endpoint}`);
18268
+ super(`API error: ${status} on ${endpoint}`);
18272
18269
  this.status = status;
18273
18270
  this.body = body;
18274
18271
  this.endpoint = endpoint;
18275
- this.name = "SealApiError";
18272
+ this.name = "ApiError";
18276
18273
  }
18277
18274
  };
18278
18275
  var TokenAcquireError = class extends Error {
@@ -18289,28 +18286,21 @@ function sleep(ms) {
18289
18286
  return new Promise((resolve2) => setTimeout(resolve2, ms));
18290
18287
  }
18291
18288
  var DEFAULT_TIMEOUT_MS = 3e4;
18292
- var SealClient = class {
18289
+ var GRANT_TYPE = "client_credentials";
18290
+ var SCOPE = "client_credentials refresh_token";
18291
+ var OAuthClient = class {
18293
18292
  baseUrl;
18294
- clientId;
18295
- clientSecret;
18296
- clientName;
18297
- scopes;
18293
+ appId;
18294
+ appSecret;
18298
18295
  constructor(config2) {
18299
- this.baseUrl = config2.serverUrl.replace(/\/+$/, "");
18300
- this.clientId = config2.clientId;
18301
- this.clientSecret = config2.clientSecret;
18302
- this.clientName = config2.clientName;
18303
- this.scopes = config2.scopes;
18304
- log7.info("SealClient initialized", { serverUrl: this.baseUrl, clientName: config2.clientName });
18296
+ this.baseUrl = config2.baseUrl.replace(/\/+$/, "");
18297
+ this.appId = config2.appId;
18298
+ this.appSecret = config2.appSecret;
18299
+ log7.info("OAuthClient initialized", { baseUrl: this.baseUrl, appId: sanitize(this.appId) });
18305
18300
  }
18306
18301
  // ── 通用 HTTP 请求 ──
18307
18302
  /**
18308
18303
  * 统一 HTTP 请求封装 — 含超时、日志脱敏和错误处理
18309
- * @param method - HTTP 方法
18310
- * @param path - 请求路径 (如 '/seal/client/register')
18311
- * @param options - 请求选项
18312
- * @returns 解析后的 JSON 响应
18313
- * @throws SealApiError — HTTP 非 2xx 响应
18314
18304
  */
18315
18305
  async request(method, path2, options = {}) {
18316
18306
  const url2 = `${this.baseUrl}${path2}`;
@@ -18342,109 +18332,38 @@ var SealClient = class {
18342
18332
  parsedBody = responseBody;
18343
18333
  }
18344
18334
  log7.error("HTTP error", { method, url: url2, status: response.status });
18345
- throw new SealApiError(response.status, parsedBody, path2);
18335
+ throw new ApiError(response.status, parsedBody, path2);
18346
18336
  }
18347
18337
  log7.debug("HTTP response", { method, url: url2, status: response.status });
18348
18338
  try {
18349
18339
  return JSON.parse(responseBody);
18350
18340
  } catch {
18351
- throw new SealApiError(response.status, responseBody, path2);
18341
+ throw new ApiError(response.status, responseBody, path2);
18352
18342
  }
18353
18343
  }
18354
18344
  // ── 公共方法 ──
18355
- /** 判断是否已注册 (有 clientId + clientSecret) */
18356
- isRegistered() {
18357
- return !!(this.clientId && this.clientSecret);
18358
- }
18359
- /** 更新 clientId / clientSecret (注册成功后或从持久化加载后调用) */
18360
- setCredentials(clientId, clientSecret) {
18361
- this.clientId = clientId;
18362
- this.clientSecret = clientSecret;
18363
- log7.info("Credentials updated", { clientId: sanitize(clientId) });
18364
- }
18365
- /**
18366
- * 动态注册客户端 → POST /seal/client/register
18367
- *
18368
- * 首次运行时自动调用,获取 clientId 和 clientSecret。
18369
- * 注册成功后自动更新内部凭据。
18370
- */
18371
- async register(params) {
18372
- const body = {
18373
- client_name: params.clientName,
18374
- scopes: params.scopes,
18375
- authentication_methods: params.authMethods ?? ["client_secret_post"],
18376
- grant_types: params.grantTypes ?? ["authorization_code", "client_credentials", "refresh_token"],
18377
- redirect_uris: params.redirectUris ?? ["https://placeholder.local"],
18378
- logout_redirect_uris: [],
18379
- client_settings: {
18380
- "settings.client.require-authorization-consent": true
18381
- }
18382
- };
18383
- if (params.tokenSettings) {
18384
- const ts = {};
18385
- if (params.tokenSettings.accessTokenTtl) {
18386
- ts["settings.token.access-token-time-to-live"] = params.tokenSettings.accessTokenTtl;
18387
- }
18388
- if (params.tokenSettings.refreshTokenTtl) {
18389
- ts["settings.token.refresh-token-time-to-live"] = params.tokenSettings.refreshTokenTtl;
18390
- }
18391
- if (params.tokenSettings.accessTokenFormat) {
18392
- ts["settings.token.access-token-format"] = params.tokenSettings.accessTokenFormat;
18393
- }
18394
- if (params.tokenSettings.reuseRefreshTokens !== void 0) {
18395
- ts["settings.token.reuse-refresh-tokens"] = params.tokenSettings.reuseRefreshTokens;
18396
- }
18397
- ts["settings.token.authorization-code-time-to-live"] = "300";
18398
- body.token_settings = ts;
18399
- }
18400
- log7.info("Registering OAuth client", { clientName: params.clientName });
18401
- const response = await this.request(
18402
- "POST",
18403
- "/seal/client/register",
18404
- { body, contentType: "json" }
18405
- );
18406
- const registration = {
18407
- clientId: response.client_id,
18408
- clientSecret: response.client_secret,
18409
- clientSecretExpiresAt: response.client_secret_expires_at ?? "",
18410
- clientIdIssuedAt: response.client_id_issued_at ?? "",
18411
- scopes: response.scopes ?? params.scopes
18412
- };
18413
- this.setCredentials(registration.clientId, registration.clientSecret);
18414
- log7.info("OAuth client registered", {
18415
- clientId: sanitize(registration.clientId),
18416
- clientSecret: sanitize(registration.clientSecret),
18417
- scopes: registration.scopes,
18418
- expiresAt: registration.clientSecretExpiresAt
18419
- });
18420
- return registration;
18421
- }
18422
18345
  /**
18423
- * 获取访问令牌 → POST /seal/oauth2/token (client_credentials)
18346
+ * 获取访问令牌 → POST /auth/token (client_credentials)
18424
18347
  *
18425
- * 使用客户端凭证模式获取 Access Token。
18426
- * 前置条件: clientId 和 clientSecret 必须存在 (通过 register 或 setCredentials 设置)
18348
+ * 请求参数 (x-www-form-urlencoded):
18349
+ * - grant_type: client_credentials (写死)
18350
+ * - client_id: appId
18351
+ * - client_secret: appSecret
18352
+ * - scope: client_credentials refresh_token (写死)
18427
18353
  *
18428
- * @param scope - 请求的权限范围 (空格分隔),不传则使用注册时的全部 scope
18429
18354
  * @returns TokenData 包含 access_token、过期时间等
18430
- * @throws ErrorclientId/clientSecret 未设置
18431
- * @throws SealApiError — HTTP 错误
18355
+ * @throws ApiErrorHTTP 错误
18432
18356
  */
18433
- async getToken(scope) {
18434
- if (!this.clientId || !this.clientSecret) {
18435
- throw new Error("Cannot get token: clientId and clientSecret are required. Call register() or setCredentials() first.");
18436
- }
18357
+ async getToken() {
18437
18358
  const params = new URLSearchParams();
18438
- params.set("grant_type", "client_credentials");
18439
- params.set("client_id", this.clientId);
18440
- params.set("client_secret", this.clientSecret);
18441
- if (scope) {
18442
- params.set("scope", scope);
18443
- }
18444
- log7.info("Requesting access token", { clientId: sanitize(this.clientId) });
18359
+ params.set("grant_type", GRANT_TYPE);
18360
+ params.set("client_id", this.appId);
18361
+ params.set("client_secret", this.appSecret);
18362
+ params.set("scope", SCOPE);
18363
+ log7.info("Requesting access token", { appId: sanitize(this.appId) });
18445
18364
  const response = await this.request(
18446
18365
  "POST",
18447
- "/seal/oauth2/token",
18366
+ "/auth/token",
18448
18367
  { body: params, contentType: "form" }
18449
18368
  );
18450
18369
  const now = Date.now();
@@ -18455,7 +18374,6 @@ var SealClient = class {
18455
18374
  expiresIn,
18456
18375
  scope: response.scope ?? "",
18457
18376
  refreshToken: void 0,
18458
- // Seal 不返回 refresh_token
18459
18377
  grantedAt: now,
18460
18378
  expiresAt: now + expiresIn * 1e3
18461
18379
  };
@@ -18466,12 +18384,9 @@ var SealClient = class {
18466
18384
  });
18467
18385
  return tokenData;
18468
18386
  }
18469
- /**
18470
- * 令牌校验 → GET /seal/oauth2/introspect
18471
- * 暂不实现 — 保留接口签名,后续需要时再补充
18472
- */
18473
- async introspect(_token) {
18474
- throw new Error("introspect() not implemented yet \u2014 Seal introspect API \u6682\u4E0D\u4F7F\u7528");
18387
+ /** 获取 baseUrl (供其他模块复用) */
18388
+ getBaseUrl() {
18389
+ return this.baseUrl;
18475
18390
  }
18476
18391
  };
18477
18392
 
@@ -18605,12 +18520,10 @@ var DEFAULT_REFRESH_AHEAD_MS = 5 * 60 * 1e3;
18605
18520
  var MAX_RETRIES = 3;
18606
18521
  var RETRY_BASE_MS = 1e3;
18607
18522
  var TokenManager = class {
18608
- sealClient;
18523
+ oauthClient;
18609
18524
  tokenStore;
18610
18525
  /** Token 过期前提前刷新的时间 (ms) */
18611
18526
  refreshAheadMs;
18612
- /** 自动注册的参数 */
18613
- autoRegisterParams;
18614
18527
  // ── 内存缓存 ──
18615
18528
  /** 内存中缓存的 access_token */
18616
18529
  cachedToken = null;
@@ -18624,10 +18537,9 @@ var TokenManager = class {
18624
18537
  /** 定时刷新器句柄 */
18625
18538
  refreshTimer = null;
18626
18539
  constructor(deps) {
18627
- this.sealClient = deps.sealClient;
18540
+ this.oauthClient = deps.oauthClient;
18628
18541
  this.tokenStore = deps.tokenStore;
18629
18542
  this.refreshAheadMs = deps.refreshAheadMs ?? DEFAULT_REFRESH_AHEAD_MS;
18630
- this.autoRegisterParams = deps.autoRegisterParams;
18631
18543
  log9.info("TokenManager initialized", { refreshAheadMs: this.refreshAheadMs });
18632
18544
  }
18633
18545
  // ── 核心方法 ──
@@ -18656,13 +18568,6 @@ var TokenManager = class {
18656
18568
  this.refreshLock = null;
18657
18569
  }
18658
18570
  }
18659
- /**
18660
- * 令牌自省 — 暂不实现,后续补充。
18661
- * 返回的 identity_id 用于 gate.ts 的 anti-loop 检查 (bot 自己的 ID)。
18662
- */
18663
- async introspect() {
18664
- throw new Error("introspect() not implemented yet \u2014 Seal introspect API \u6682\u4E0D\u4F7F\u7528");
18665
- }
18666
18571
  /** 检查当前令牌是否包含指定的 scope */
18667
18572
  hasScope(scope) {
18668
18573
  if (!this.cachedScope) return false;
@@ -18693,14 +18598,14 @@ var TokenManager = class {
18693
18598
  *
18694
18599
  * 重试策略:
18695
18600
  * - 网络错误 / 5xx → 重试 (最多 3 次)
18696
- * - 401/403 → 不重试 (client_secret 可能无效)
18601
+ * - 401/403 → 不重试 (凭证可能无效)
18697
18602
  */
18698
18603
  async _acquireTokenWithRetry() {
18699
18604
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
18700
18605
  try {
18701
18606
  return await this._acquireToken();
18702
18607
  } catch (err) {
18703
- if (err instanceof SealApiError && (err.status === 401 || err.status === 403)) {
18608
+ if (err instanceof ApiError && (err.status === 401 || err.status === 403)) {
18704
18609
  log9.error("Token acquire failed with auth error, not retrying", { status: err.status });
18705
18610
  throw err;
18706
18611
  }
@@ -18728,10 +18633,9 @@ var TokenManager = class {
18728
18633
  *
18729
18634
  * 流程:
18730
18635
  * 1. 尝试从 TokenStore 加载缓存
18731
- * 2. 检查是否需要注册客户端
18732
- * 3. 调用 SealClient.getToken() 获取新 Token
18733
- * 4. 保存到内存缓存 + TokenStore
18734
- * 5. 设置定时刷新器
18636
+ * 2. 调用 OAuthClient.getToken() 获取新 Token
18637
+ * 3. 保存到内存缓存 + TokenStore
18638
+ * 4. 设置定时刷新器
18735
18639
  */
18736
18640
  async _acquireToken() {
18737
18641
  try {
@@ -18748,43 +18652,8 @@ var TokenManager = class {
18748
18652
  } catch {
18749
18653
  log9.warn("Failed to load token from store, acquiring new one");
18750
18654
  }
18751
- if (!this.sealClient.isRegistered()) {
18752
- try {
18753
- const savedCreds = await this.tokenStore.load("credentials");
18754
- if (savedCreds?.clientId && savedCreds?.clientSecret) {
18755
- this.sealClient.setCredentials(
18756
- savedCreds.clientId,
18757
- savedCreds.clientSecret
18758
- );
18759
- log9.info("Credentials loaded from store");
18760
- }
18761
- } catch {
18762
- log9.debug("No saved credentials found");
18763
- }
18764
- }
18765
- if (!this.sealClient.isRegistered()) {
18766
- if (!this.autoRegisterParams) {
18767
- throw new Error("SealClient not registered and autoRegisterParams not provided. Call setCredentials() or enable autoRegister.");
18768
- }
18769
- log9.info("Auto-registering OAuth client");
18770
- const registration = await this.sealClient.register({
18771
- clientName: this.autoRegisterParams.clientName,
18772
- scopes: this.autoRegisterParams.scopes,
18773
- tokenSettings: this.autoRegisterParams.tokenSettings ? {
18774
- accessTokenTtl: this.autoRegisterParams.tokenSettings.accessTokenTtl,
18775
- refreshTokenTtl: this.autoRegisterParams.tokenSettings.refreshTokenTtl,
18776
- accessTokenFormat: this.autoRegisterParams.tokenSettings.accessTokenFormat
18777
- } : void 0
18778
- });
18779
- await this.tokenStore.save("credentials", {
18780
- clientId: registration.clientId,
18781
- clientSecret: registration.clientSecret,
18782
- clientSecretExpiresAt: registration.clientSecretExpiresAt
18783
- });
18784
- log9.info("OAuth client registered and credentials saved");
18785
- }
18786
- log9.info("Acquiring new access token");
18787
- const tokenData = await this.sealClient.getToken();
18655
+ log9.info("Acquiring new access token via POST /auth/token");
18656
+ const tokenData = await this.oauthClient.getToken();
18788
18657
  this.updateCache(tokenData);
18789
18658
  await this.tokenStore.save("token", tokenData);
18790
18659
  this.scheduleRefresh(tokenData);
@@ -19141,11 +19010,18 @@ var MessagePipe = class {
19141
19010
  log12.warn("inbound:data-parse-error", { error: err.message });
19142
19011
  return;
19143
19012
  }
19144
- if (callbackData.eventType !== "MessageReceived") {
19013
+ if (callbackData.eventType !== "callback:direct") {
19145
19014
  log12.debug("inbound:event-ignored", { eventType: callbackData.eventType });
19146
19015
  return;
19147
19016
  }
19148
- const msg = callbackData.content;
19017
+ const msg = {
19018
+ messageId: callbackData.msgUid,
19019
+ chatId: callbackData.groupId || callbackData.userId,
19020
+ senderId: callbackData.userId,
19021
+ msgType: callbackData.type,
19022
+ content: JSON.stringify(callbackData.content),
19023
+ timestamp: Date.now()
19024
+ };
19149
19025
  log12.debug("inbound:frame", { messageId: msg.messageId, eventType: callbackData.eventType });
19150
19026
  if (this.dedup.isDuplicate(msg.messageId)) {
19151
19027
  log12.debug("inbound:dedup-hit", { messageId: msg.messageId });
@@ -19206,10 +19082,14 @@ var ConnectionManager = class {
19206
19082
  this.running = true;
19207
19083
  this.reconnectAttempts = 0;
19208
19084
  const token = await tokenFn();
19085
+ const wsUrl = url2.replace(/\/+$/, "") + "/events/stream";
19209
19086
  await this.client.connect({
19210
- url: url2,
19087
+ url: wsUrl,
19211
19088
  token,
19212
- headers: this.appId ? { "X-App-ID": this.appId } : void 0
19089
+ headers: {
19090
+ "Authorization": token,
19091
+ ...this.appId ? { "X-App-ID": this.appId } : {}
19092
+ }
19213
19093
  });
19214
19094
  this.client.on("close", () => {
19215
19095
  if (this.running) {
@@ -19262,10 +19142,14 @@ var ConnectionManager = class {
19262
19142
  this.reconnectAttempts++;
19263
19143
  try {
19264
19144
  const token = await this.tokenFn();
19145
+ const wsUrl = this.url.replace(/\/+$/, "") + "/events/stream";
19265
19146
  await this.client.connect({
19266
- url: this.url,
19147
+ url: wsUrl,
19267
19148
  token,
19268
- headers: this.appId ? { "X-App-ID": this.appId } : void 0
19149
+ headers: {
19150
+ "Authorization": token,
19151
+ ...this.appId ? { "X-App-ID": this.appId } : {}
19152
+ }
19269
19153
  });
19270
19154
  this.reconnectAttempts = 0;
19271
19155
  this.startHeartbeat();
@@ -19738,28 +19622,17 @@ async function startPlugin(accountConfig, internalOverrides) {
19738
19622
  cryptoEngine = CryptoEngine.createPassthrough();
19739
19623
  log16.info("Crypto passthrough mode \u2713 (disabled by config)");
19740
19624
  }
19741
- const sealClient = new SealClient({
19742
- serverUrl: config2.auth.serverUrl,
19743
- clientName: config2.auth.clientName,
19744
- clientId: config2.auth.clientId,
19745
- clientSecret: config2.auth.clientSecret,
19746
- scopes: config2.auth.scopes
19625
+ const oauthClient = new OAuthClient({
19626
+ baseUrl: config2.auth.serverUrl,
19627
+ appId: accountConfig.appId,
19628
+ appSecret: accountConfig.appSecret
19747
19629
  });
19748
19630
  const tokenStorePath = (0, import_node_path2.join)((0, import_node_os.homedir)(), TOKEN_STORE_DIR, "tokens");
19749
19631
  const tokenStore = new TokenStore(tokenStorePath, cryptoEngine);
19750
19632
  const tokenManager = new TokenManager({
19751
- sealClient,
19633
+ oauthClient,
19752
19634
  tokenStore,
19753
- refreshAheadMs: config2.auth.refreshAheadMs,
19754
- autoRegisterParams: config2.auth.autoRegister ? {
19755
- clientName: config2.auth.clientName,
19756
- scopes: config2.auth.scopes,
19757
- tokenSettings: config2.auth.tokenSettings ? {
19758
- accessTokenTtl: config2.auth.tokenSettings.accessTokenTtl,
19759
- refreshTokenTtl: config2.auth.tokenSettings.refreshTokenTtl,
19760
- accessTokenFormat: config2.auth.tokenSettings.accessTokenFormat
19761
- } : void 0
19762
- } : void 0
19635
+ refreshAheadMs: config2.auth.refreshAheadMs
19763
19636
  });
19764
19637
  log16.info("Auth initialized \u2713");
19765
19638
  let pushQueue = null;
@@ -19844,20 +19717,20 @@ ${body}` : body || raw.content;
19844
19717
  }
19845
19718
  case "image": {
19846
19719
  const c = parsed;
19847
- fileId = c?.fileKey;
19720
+ fileId = c?.fileId;
19848
19721
  text = c?.altText ?? (fileId ? `![image](${fileId})` : "[image]");
19849
19722
  break;
19850
19723
  }
19851
19724
  case "file": {
19852
19725
  const c = parsed;
19853
- fileId = c?.fileKey;
19854
- fileName = c?.filename;
19726
+ fileId = c?.fileId;
19727
+ fileName = c?.fileName;
19855
19728
  text = fileName ? `[File: ${fileName}]` : "[file]";
19856
19729
  break;
19857
19730
  }
19858
19731
  case "voice": {
19859
19732
  const c = parsed;
19860
- fileId = c?.fileKey;
19733
+ fileId = c?.fileId;
19861
19734
  const durationMs = c?.duration;
19862
19735
  const durationStr = durationMs != null ? ` ${(durationMs / 1e3).toFixed(1)}s` : "";
19863
19736
  text = `[Voice${durationStr}]`;
@@ -19865,7 +19738,7 @@ ${body}` : body || raw.content;
19865
19738
  }
19866
19739
  case "video": {
19867
19740
  const c = parsed;
19868
- fileId = c?.fileKey;
19741
+ fileId = c?.fileId;
19869
19742
  const durationMs = c?.duration;
19870
19743
  const durationStr = durationMs != null ? ` ${(durationMs / 1e3).toFixed(1)}s` : "";
19871
19744
  text = `[Video${durationStr}]`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "liangzimixin",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Quantum-encrypted IM channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",