codex-to-im 1.0.20 → 1.0.21

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/daemon.mjs CHANGED
@@ -4231,6 +4231,8 @@ function getBridgeContext() {
4231
4231
 
4232
4232
  // src/lib/bridge/channel-adapter.ts
4233
4233
  var BaseChannelAdapter = class {
4234
+ /** Human-readable instance alias, when applicable. */
4235
+ alias;
4234
4236
  /**
4235
4237
  * Answer a callback query (e.g. Telegram inline button press).
4236
4238
  * Not all platforms support this — default implementation is a no-op.
@@ -4239,12 +4241,12 @@ var BaseChannelAdapter = class {
4239
4241
  }
4240
4242
  };
4241
4243
  var adapterFactories = /* @__PURE__ */ new Map();
4242
- function registerAdapterFactory(channelType, factory) {
4243
- adapterFactories.set(channelType, factory);
4244
+ function registerAdapterFactory(provider, factory) {
4245
+ adapterFactories.set(provider, factory);
4244
4246
  }
4245
- function createAdapter(channelType) {
4246
- const factory = adapterFactories.get(channelType);
4247
- return factory ? factory() : null;
4247
+ function createAdapter(instance) {
4248
+ const factory = adapterFactories.get(instance.provider);
4249
+ return factory ? factory(instance) : null;
4248
4250
  }
4249
4251
  function getRegisteredTypes() {
4250
4252
  return Array.from(adapterFactories.keys());
@@ -4464,6 +4466,7 @@ function tokenShortHash(botToken) {
4464
4466
  var MEDIA_GROUP_DEBOUNCE_MS = 500;
4465
4467
  var TelegramAdapter = class extends BaseChannelAdapter {
4466
4468
  channelType = "telegram";
4469
+ provider = "telegram";
4467
4470
  running = false;
4468
4471
  abortController = null;
4469
4472
  queue = [];
@@ -5079,9 +5082,346 @@ registerAdapterFactory("telegram", () => new TelegramAdapter());
5079
5082
 
5080
5083
  // src/lib/bridge/adapters/feishu-adapter.ts
5081
5084
  import crypto2 from "crypto";
5085
+ import fs2 from "node:fs";
5086
+ import path2 from "node:path";
5087
+ import * as lark from "@larksuiteoapi/node-sdk";
5088
+
5089
+ // src/config.ts
5082
5090
  import fs from "node:fs";
5091
+ import os from "node:os";
5083
5092
  import path from "node:path";
5084
- import * as lark from "@larksuiteoapi/node-sdk";
5093
+ function toFeishuConfig(channel) {
5094
+ return channel?.provider === "feishu" ? channel.config : void 0;
5095
+ }
5096
+ function toWeixinConfig(channel) {
5097
+ return channel?.provider === "weixin" ? channel.config : void 0;
5098
+ }
5099
+ var LEGACY_CTI_HOME = path.join(os.homedir(), ".claude-to-im");
5100
+ var DEFAULT_CTI_HOME = path.join(os.homedir(), ".codex-to-im");
5101
+ var DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), "cx2im");
5102
+ function resolveDefaultCtiHome() {
5103
+ if (fs.existsSync(DEFAULT_CTI_HOME)) return DEFAULT_CTI_HOME;
5104
+ if (fs.existsSync(LEGACY_CTI_HOME)) return LEGACY_CTI_HOME;
5105
+ return DEFAULT_CTI_HOME;
5106
+ }
5107
+ var CTI_HOME = process.env.CTI_HOME || resolveDefaultCtiHome();
5108
+ var CONFIG_PATH = path.join(CTI_HOME, "config.env");
5109
+ var CONFIG_V2_PATH = path.join(CTI_HOME, "config.v2.json");
5110
+ function expandHomePath(value) {
5111
+ if (!value) return value;
5112
+ if (value === "~") return os.homedir();
5113
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
5114
+ return path.join(os.homedir(), value.slice(2));
5115
+ }
5116
+ return value;
5117
+ }
5118
+ function parseEnvFile(content) {
5119
+ const entries = /* @__PURE__ */ new Map();
5120
+ for (const line of content.split("\n")) {
5121
+ const trimmed = line.trim();
5122
+ if (!trimmed || trimmed.startsWith("#")) continue;
5123
+ const eqIdx = trimmed.indexOf("=");
5124
+ if (eqIdx === -1) continue;
5125
+ const key = trimmed.slice(0, eqIdx).trim();
5126
+ let value = trimmed.slice(eqIdx + 1).trim();
5127
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
5128
+ value = value.slice(1, -1);
5129
+ }
5130
+ entries.set(key, value);
5131
+ }
5132
+ return entries;
5133
+ }
5134
+ function loadRawConfigEnv() {
5135
+ try {
5136
+ return parseEnvFile(fs.readFileSync(CONFIG_PATH, "utf-8"));
5137
+ } catch {
5138
+ return /* @__PURE__ */ new Map();
5139
+ }
5140
+ }
5141
+ function splitCsv(value) {
5142
+ if (!value) return void 0;
5143
+ return value.split(",").map((s) => s.trim()).filter(Boolean);
5144
+ }
5145
+ function parsePositiveInt(value) {
5146
+ if (!value) return void 0;
5147
+ const parsed = Number(value);
5148
+ if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
5149
+ return Math.floor(parsed);
5150
+ }
5151
+ function parseSandboxMode(value) {
5152
+ if (value === "read-only" || value === "workspace-write" || value === "danger-full-access") {
5153
+ return value;
5154
+ }
5155
+ return void 0;
5156
+ }
5157
+ function parseReasoningEffort(value) {
5158
+ if (value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh") {
5159
+ return value;
5160
+ }
5161
+ return void 0;
5162
+ }
5163
+ function normalizeFeishuSite(value) {
5164
+ const normalized = (value || "").trim().replace(/\/+$/, "").toLowerCase();
5165
+ if (!normalized) return "feishu";
5166
+ if (normalized === "lark") return "lark";
5167
+ if (normalized === "feishu") return "feishu";
5168
+ if (normalized.includes("open.larksuite.com")) return "lark";
5169
+ return "feishu";
5170
+ }
5171
+ function feishuSiteToApiBaseUrl(site) {
5172
+ return normalizeFeishuSite(site) === "lark" ? "https://open.larksuite.com" : "https://open.feishu.cn";
5173
+ }
5174
+ function nowIso() {
5175
+ return (/* @__PURE__ */ new Date()).toISOString();
5176
+ }
5177
+ function ensureConfigDir() {
5178
+ fs.mkdirSync(CTI_HOME, { recursive: true });
5179
+ }
5180
+ function readConfigV2File() {
5181
+ try {
5182
+ const parsed = JSON.parse(fs.readFileSync(CONFIG_V2_PATH, "utf-8"));
5183
+ if (parsed && parsed.schemaVersion === 2 && parsed.runtime && Array.isArray(parsed.channels)) {
5184
+ return parsed;
5185
+ }
5186
+ return null;
5187
+ } catch {
5188
+ return null;
5189
+ }
5190
+ }
5191
+ function writeConfigV2File(config2) {
5192
+ ensureConfigDir();
5193
+ const tmpPath = CONFIG_V2_PATH + ".tmp";
5194
+ fs.writeFileSync(tmpPath, JSON.stringify(config2, null, 2), { mode: 384 });
5195
+ fs.renameSync(tmpPath, CONFIG_V2_PATH);
5196
+ }
5197
+ function defaultAliasForProvider(provider) {
5198
+ return provider === "feishu" ? "\u98DE\u4E66" : "\u5FAE\u4FE1";
5199
+ }
5200
+ function buildDefaultChannelId(provider) {
5201
+ return `${provider}-default`;
5202
+ }
5203
+ function migrateLegacyEnvToV2(env) {
5204
+ const rawRuntime = env.get("CTI_RUNTIME") || "codex";
5205
+ const runtime = ["claude", "codex", "auto"].includes(rawRuntime) ? rawRuntime : "codex";
5206
+ const enabledChannels = splitCsv(env.get("CTI_ENABLED_CHANNELS")) ?? ["feishu"];
5207
+ const timestamp = nowIso();
5208
+ const channels = [];
5209
+ const hasFeishuConfig = Boolean(
5210
+ env.get("CTI_FEISHU_APP_ID") || env.get("CTI_FEISHU_APP_SECRET") || env.get("CTI_FEISHU_ALLOWED_USERS") || enabledChannels.includes("feishu")
5211
+ );
5212
+ if (hasFeishuConfig) {
5213
+ channels.push({
5214
+ id: buildDefaultChannelId("feishu"),
5215
+ alias: defaultAliasForProvider("feishu"),
5216
+ provider: "feishu",
5217
+ enabled: enabledChannels.includes("feishu"),
5218
+ createdAt: timestamp,
5219
+ updatedAt: timestamp,
5220
+ config: {
5221
+ appId: env.get("CTI_FEISHU_APP_ID") || void 0,
5222
+ appSecret: env.get("CTI_FEISHU_APP_SECRET") || void 0,
5223
+ site: normalizeFeishuSite(env.get("CTI_FEISHU_SITE") || env.get("CTI_FEISHU_DOMAIN")),
5224
+ allowedUsers: splitCsv(env.get("CTI_FEISHU_ALLOWED_USERS")),
5225
+ streamingEnabled: env.has("CTI_FEISHU_STREAMING_ENABLED") ? env.get("CTI_FEISHU_STREAMING_ENABLED") === "true" : true,
5226
+ feedbackMarkdownEnabled: env.has("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") === "true" : true
5227
+ }
5228
+ });
5229
+ }
5230
+ const hasWeixinConfig = Boolean(
5231
+ env.get("CTI_WEIXIN_BASE_URL") || env.get("CTI_WEIXIN_CDN_BASE_URL") || env.get("CTI_WEIXIN_MEDIA_ENABLED") || enabledChannels.includes("weixin")
5232
+ );
5233
+ if (hasWeixinConfig) {
5234
+ channels.push({
5235
+ id: buildDefaultChannelId("weixin"),
5236
+ alias: defaultAliasForProvider("weixin"),
5237
+ provider: "weixin",
5238
+ enabled: enabledChannels.includes("weixin"),
5239
+ createdAt: timestamp,
5240
+ updatedAt: timestamp,
5241
+ config: {
5242
+ baseUrl: env.get("CTI_WEIXIN_BASE_URL") || void 0,
5243
+ cdnBaseUrl: env.get("CTI_WEIXIN_CDN_BASE_URL") || void 0,
5244
+ mediaEnabled: env.has("CTI_WEIXIN_MEDIA_ENABLED") ? env.get("CTI_WEIXIN_MEDIA_ENABLED") === "true" : void 0,
5245
+ feedbackMarkdownEnabled: env.has("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") === "true" : false
5246
+ }
5247
+ });
5248
+ }
5249
+ return {
5250
+ schemaVersion: 2,
5251
+ runtime: {
5252
+ provider: runtime,
5253
+ defaultWorkspaceRoot: expandHomePath(env.get("CTI_DEFAULT_WORKSPACE_ROOT")) || void 0,
5254
+ defaultModel: env.get("CTI_DEFAULT_MODEL") || void 0,
5255
+ defaultMode: env.get("CTI_DEFAULT_MODE") || "code",
5256
+ historyMessageLimit: parsePositiveInt(env.get("CTI_HISTORY_MESSAGE_LIMIT")) ?? 8,
5257
+ codexSkipGitRepoCheck: env.has("CTI_CODEX_SKIP_GIT_REPO_CHECK") ? env.get("CTI_CODEX_SKIP_GIT_REPO_CHECK") === "true" : true,
5258
+ codexSandboxMode: parseSandboxMode(env.get("CTI_CODEX_SANDBOX_MODE")) ?? "workspace-write",
5259
+ codexReasoningEffort: parseReasoningEffort(env.get("CTI_CODEX_REASONING_EFFORT")) ?? "medium",
5260
+ uiAllowLan: env.get("CTI_UI_ALLOW_LAN") === "true",
5261
+ uiAccessToken: env.get("CTI_UI_ACCESS_TOKEN") || void 0,
5262
+ autoApprove: env.get("CTI_AUTO_APPROVE") === "true"
5263
+ },
5264
+ channels
5265
+ };
5266
+ }
5267
+ function getChannelByProvider(config2, provider) {
5268
+ const preferredId = buildDefaultChannelId(provider);
5269
+ return config2.channels.find((channel) => channel.id === preferredId) || config2.channels.find((channel) => channel.provider === provider);
5270
+ }
5271
+ function expandConfig(v2) {
5272
+ return {
5273
+ schemaVersion: 2,
5274
+ channels: v2.channels,
5275
+ runtime: v2.runtime.provider,
5276
+ enabledChannels: Array.from(new Set(
5277
+ v2.channels.filter((channel) => channel.enabled).map((channel) => channel.provider)
5278
+ )),
5279
+ defaultWorkspaceRoot: v2.runtime.defaultWorkspaceRoot,
5280
+ defaultModel: v2.runtime.defaultModel,
5281
+ defaultMode: v2.runtime.defaultMode || "code",
5282
+ historyMessageLimit: v2.runtime.historyMessageLimit ?? 8,
5283
+ codexSkipGitRepoCheck: v2.runtime.codexSkipGitRepoCheck ?? true,
5284
+ codexSandboxMode: v2.runtime.codexSandboxMode ?? "workspace-write",
5285
+ codexReasoningEffort: v2.runtime.codexReasoningEffort ?? "medium",
5286
+ uiAllowLan: v2.runtime.uiAllowLan === true,
5287
+ uiAccessToken: v2.runtime.uiAccessToken || void 0,
5288
+ autoApprove: v2.runtime.autoApprove === true
5289
+ };
5290
+ }
5291
+ function loadConfig() {
5292
+ const current = readConfigV2File();
5293
+ if (current) return expandConfig(current);
5294
+ const legacyEnv = loadRawConfigEnv();
5295
+ if (legacyEnv.size > 0) {
5296
+ const migrated = migrateLegacyEnvToV2(legacyEnv);
5297
+ writeConfigV2File(migrated);
5298
+ return expandConfig(migrated);
5299
+ }
5300
+ const empty = {
5301
+ schemaVersion: 2,
5302
+ runtime: {
5303
+ provider: "codex",
5304
+ defaultWorkspaceRoot: DEFAULT_WORKSPACE_ROOT,
5305
+ defaultMode: "code",
5306
+ historyMessageLimit: 8,
5307
+ codexSkipGitRepoCheck: true,
5308
+ codexSandboxMode: "workspace-write",
5309
+ codexReasoningEffort: "medium",
5310
+ uiAllowLan: false,
5311
+ autoApprove: false
5312
+ },
5313
+ channels: []
5314
+ };
5315
+ return expandConfig(empty);
5316
+ }
5317
+ function listChannelInstances(config2) {
5318
+ return [...config2?.channels || loadConfig().channels || []];
5319
+ }
5320
+ function findChannelInstance(channelId, config2) {
5321
+ return listChannelInstances(config2).find((channel) => channel.id === channelId);
5322
+ }
5323
+ function configToSettings(config2) {
5324
+ const m = /* @__PURE__ */ new Map();
5325
+ const current = {
5326
+ schemaVersion: 2,
5327
+ runtime: {
5328
+ provider: config2.runtime,
5329
+ defaultMode: config2.defaultMode
5330
+ },
5331
+ channels: config2.channels || []
5332
+ };
5333
+ const feishu = getChannelByProvider(current, "feishu");
5334
+ const weixin = getChannelByProvider(current, "weixin");
5335
+ const feishuConfig = toFeishuConfig(feishu);
5336
+ const weixinConfig = toWeixinConfig(weixin);
5337
+ m.set("remote_bridge_enabled", "true");
5338
+ if (config2.defaultWorkspaceRoot) {
5339
+ m.set("bridge_default_workspace_root", config2.defaultWorkspaceRoot);
5340
+ }
5341
+ if (config2.defaultModel) {
5342
+ m.set("bridge_default_model", config2.defaultModel);
5343
+ m.set("default_model", config2.defaultModel);
5344
+ }
5345
+ m.set("bridge_default_mode", config2.defaultMode);
5346
+ m.set(
5347
+ "bridge_history_message_limit",
5348
+ String(config2.historyMessageLimit && config2.historyMessageLimit > 0 ? config2.historyMessageLimit : 8)
5349
+ );
5350
+ m.set(
5351
+ "bridge_codex_skip_git_repo_check",
5352
+ config2.codexSkipGitRepoCheck === true ? "true" : "false"
5353
+ );
5354
+ m.set(
5355
+ "bridge_codex_sandbox_mode",
5356
+ config2.codexSandboxMode || "workspace-write"
5357
+ );
5358
+ m.set(
5359
+ "bridge_codex_reasoning_effort",
5360
+ config2.codexReasoningEffort || "medium"
5361
+ );
5362
+ m.set(
5363
+ "bridge_channel_instances_json",
5364
+ JSON.stringify(config2.channels || [])
5365
+ );
5366
+ m.set(
5367
+ "bridge_telegram_enabled",
5368
+ config2.enabledChannels.includes("telegram") ? "true" : "false"
5369
+ );
5370
+ if (config2.tgBotToken) m.set("telegram_bot_token", config2.tgBotToken);
5371
+ if (config2.tgAllowedUsers) m.set("telegram_bridge_allowed_users", config2.tgAllowedUsers.join(","));
5372
+ if (config2.tgChatId) m.set("telegram_chat_id", config2.tgChatId);
5373
+ m.set(
5374
+ "bridge_discord_enabled",
5375
+ config2.enabledChannels.includes("discord") ? "true" : "false"
5376
+ );
5377
+ if (config2.discordBotToken) m.set("bridge_discord_bot_token", config2.discordBotToken);
5378
+ if (config2.discordAllowedUsers) m.set("bridge_discord_allowed_users", config2.discordAllowedUsers.join(","));
5379
+ if (config2.discordAllowedChannels) m.set("bridge_discord_allowed_channels", config2.discordAllowedChannels.join(","));
5380
+ if (config2.discordAllowedGuilds) m.set("bridge_discord_allowed_guilds", config2.discordAllowedGuilds.join(","));
5381
+ m.set(
5382
+ "bridge_feishu_enabled",
5383
+ feishu?.enabled === true ? "true" : "false"
5384
+ );
5385
+ if (feishuConfig?.appId) m.set("bridge_feishu_app_id", feishuConfig.appId);
5386
+ if (feishuConfig?.appSecret) m.set("bridge_feishu_app_secret", feishuConfig.appSecret);
5387
+ if (feishuConfig?.site) m.set("bridge_feishu_site", feishuConfig.site);
5388
+ if (feishuConfig?.allowedUsers) m.set("bridge_feishu_allowed_users", feishuConfig.allowedUsers.join(","));
5389
+ m.set(
5390
+ "bridge_feishu_streaming_enabled",
5391
+ feishuConfig?.streamingEnabled === false ? "false" : "true"
5392
+ );
5393
+ m.set(
5394
+ "bridge_feishu_command_markdown_enabled",
5395
+ feishuConfig?.feedbackMarkdownEnabled === false ? "false" : "true"
5396
+ );
5397
+ m.set(
5398
+ "bridge_qq_enabled",
5399
+ config2.enabledChannels.includes("qq") ? "true" : "false"
5400
+ );
5401
+ if (config2.qqAppId) m.set("bridge_qq_app_id", config2.qqAppId);
5402
+ if (config2.qqAppSecret) m.set("bridge_qq_app_secret", config2.qqAppSecret);
5403
+ if (config2.qqAllowedUsers) m.set("bridge_qq_allowed_users", config2.qqAllowedUsers.join(","));
5404
+ if (config2.qqImageEnabled !== void 0) {
5405
+ m.set("bridge_qq_image_enabled", String(config2.qqImageEnabled));
5406
+ }
5407
+ if (config2.qqMaxImageSize !== void 0) {
5408
+ m.set("bridge_qq_max_image_size", String(config2.qqMaxImageSize));
5409
+ }
5410
+ m.set(
5411
+ "bridge_weixin_enabled",
5412
+ weixin?.enabled === true ? "true" : "false"
5413
+ );
5414
+ if (weixinConfig?.mediaEnabled !== void 0) {
5415
+ m.set("bridge_weixin_media_enabled", String(weixinConfig.mediaEnabled));
5416
+ }
5417
+ m.set(
5418
+ "bridge_weixin_command_markdown_enabled",
5419
+ weixinConfig?.feedbackMarkdownEnabled === true ? "true" : "false"
5420
+ );
5421
+ if (weixinConfig?.baseUrl) m.set("bridge_weixin_base_url", weixinConfig.baseUrl);
5422
+ if (weixinConfig?.cdnBaseUrl) m.set("bridge_weixin_cdn_base_url", weixinConfig.cdnBaseUrl);
5423
+ return m;
5424
+ }
5085
5425
 
5086
5426
  // src/lib/bridge/markdown/feishu.ts
5087
5427
  function hasComplexMarkdown(text2) {
@@ -5259,7 +5599,10 @@ var MIME_BY_TYPE = {
5259
5599
  media: "application/octet-stream"
5260
5600
  };
5261
5601
  var FeishuAdapter = class extends BaseChannelAdapter {
5262
- channelType = "feishu";
5602
+ channelType;
5603
+ provider = "feishu";
5604
+ alias;
5605
+ channelConfig;
5263
5606
  running = false;
5264
5607
  queue = [];
5265
5608
  waiters = [];
@@ -5279,8 +5622,23 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5279
5622
  cardCreatePromises = /* @__PURE__ */ new Map();
5280
5623
  /** Cached tenant token for upload APIs. */
5281
5624
  tenantTokenCache = null;
5625
+ constructor(instance) {
5626
+ super();
5627
+ this.channelType = instance?.id || "feishu";
5628
+ this.alias = instance?.alias;
5629
+ this.channelConfig = instance?.config || {};
5630
+ }
5631
+ get appId() {
5632
+ return this.channelConfig.appId?.trim() || "";
5633
+ }
5634
+ get appSecret() {
5635
+ return this.channelConfig.appSecret?.trim() || "";
5636
+ }
5637
+ get site() {
5638
+ return normalizeFeishuSite(this.channelConfig.site);
5639
+ }
5282
5640
  isStreamingEnabled() {
5283
- return getBridgeContext().store.getSetting("bridge_feishu_streaming_enabled") !== "false";
5641
+ return this.channelConfig.streamingEnabled !== false;
5284
5642
  }
5285
5643
  resolveStreamKey(chatId, streamKey) {
5286
5644
  return streamKey?.trim() || chatId;
@@ -5293,10 +5651,10 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5293
5651
  console.warn("[feishu-adapter] Cannot start:", configError);
5294
5652
  return;
5295
5653
  }
5296
- const appId = getBridgeContext().store.getSetting("bridge_feishu_app_id") || "";
5297
- const appSecret = getBridgeContext().store.getSetting("bridge_feishu_app_secret") || "";
5298
- const domainSetting = getBridgeContext().store.getSetting("bridge_feishu_domain") || "feishu";
5299
- const domain = domainSetting === "lark" ? lark.Domain.Lark : lark.Domain.Feishu;
5654
+ const appId = this.appId;
5655
+ const appSecret = this.appSecret;
5656
+ const site = this.site;
5657
+ const domain = site === "lark" ? lark.Domain.Lark : lark.Domain.Feishu;
5300
5658
  this.restClient = new lark.Client({
5301
5659
  appId,
5302
5660
  appSecret,
@@ -5446,7 +5804,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5446
5804
  const callbackMsg = {
5447
5805
  messageId: messageId || `card_action_${Date.now()}`,
5448
5806
  address: {
5449
- channelType: "feishu",
5807
+ channelType: this.channelType,
5808
+ channelProvider: this.provider,
5809
+ channelAlias: this.alias,
5450
5810
  chatId,
5451
5811
  userId
5452
5812
  },
@@ -5755,18 +6115,11 @@ ${trimmedResponse}`;
5755
6115
  return this.sendAsPost(message.address.chatId, text2);
5756
6116
  }
5757
6117
  getOpenApiBaseUrl() {
5758
- const configured = (getBridgeContext().store.getSetting("bridge_feishu_domain") || "").trim().replace(/\/+$/, "");
5759
- if (configured.startsWith("http://") || configured.startsWith("https://")) {
5760
- return configured;
5761
- }
5762
- if (configured.toLowerCase() === "lark") {
5763
- return "https://open.larksuite.com";
5764
- }
5765
- return "https://open.feishu.cn";
6118
+ return feishuSiteToApiBaseUrl(this.site);
5766
6119
  }
5767
6120
  async getTenantAccessToken() {
5768
- const appId = getBridgeContext().store.getSetting("bridge_feishu_app_id") || "";
5769
- const appSecret = getBridgeContext().store.getSetting("bridge_feishu_app_secret") || "";
6121
+ const appId = this.appId;
6122
+ const appSecret = this.appSecret;
5770
6123
  const domain = this.getOpenApiBaseUrl();
5771
6124
  if (!appId || !appSecret) {
5772
6125
  throw new Error("Feishu App ID / App Secret not configured");
@@ -5806,7 +6159,7 @@ ${trimmedResponse}`;
5806
6159
  return { ok: true, messageId: lastMessageId };
5807
6160
  }
5808
6161
  async sendAttachment(chatId, attachment, replyToMessageId) {
5809
- if (!fs.existsSync(attachment.path)) {
6162
+ if (!fs2.existsSync(attachment.path)) {
5810
6163
  return { ok: false, error: `Attachment not found: ${attachment.path}` };
5811
6164
  }
5812
6165
  try {
@@ -5832,10 +6185,10 @@ ${trimmedResponse}`;
5832
6185
  }
5833
6186
  async uploadImage(attachment) {
5834
6187
  const token = await this.getTenantAccessToken();
5835
- const fileName = attachment.name || path.basename(attachment.path) || "image.png";
6188
+ const fileName = attachment.name || path2.basename(attachment.path) || "image.png";
5836
6189
  const form = new FormData();
5837
6190
  form.set("image_type", "message");
5838
- form.set("image", new Blob([fs.readFileSync(attachment.path)]), fileName);
6191
+ form.set("image", new Blob([fs2.readFileSync(attachment.path)]), fileName);
5839
6192
  const response = await fetch(`${this.getOpenApiBaseUrl()}/open-apis/im/v1/images`, {
5840
6193
  method: "POST",
5841
6194
  headers: { Authorization: `Bearer ${token}` },
@@ -5849,11 +6202,11 @@ ${trimmedResponse}`;
5849
6202
  }
5850
6203
  async uploadFile(attachment) {
5851
6204
  const token = await this.getTenantAccessToken();
5852
- const fileName = attachment.name || path.basename(attachment.path) || "attachment.bin";
6205
+ const fileName = attachment.name || path2.basename(attachment.path) || "attachment.bin";
5853
6206
  const form = new FormData();
5854
6207
  form.set("file_type", "stream");
5855
6208
  form.set("file_name", fileName);
5856
- form.set("file", new Blob([fs.readFileSync(attachment.path)]), fileName);
6209
+ form.set("file", new Blob([fs2.readFileSync(attachment.path)]), fileName);
5857
6210
  const response = await fetch(`${this.getOpenApiBaseUrl()}/open-apis/im/v1/files`, {
5858
6211
  method: "POST",
5859
6212
  headers: { Authorization: `Bearer ${token}` },
@@ -6061,16 +6414,14 @@ ${trimmedResponse}`;
6061
6414
  }
6062
6415
  // ── Config & Auth ───────────────────────────────────────────
6063
6416
  validateConfig() {
6064
- const enabled = getBridgeContext().store.getSetting("bridge_feishu_enabled");
6065
- if (enabled !== "true") return "bridge_feishu_enabled is not true";
6066
- const appId = getBridgeContext().store.getSetting("bridge_feishu_app_id");
6067
- if (!appId) return "bridge_feishu_app_id not configured";
6068
- const appSecret = getBridgeContext().store.getSetting("bridge_feishu_app_secret");
6069
- if (!appSecret) return "bridge_feishu_app_secret not configured";
6417
+ const appId = this.appId;
6418
+ if (!appId) return "Feishu App ID \u672A\u914D\u7F6E";
6419
+ const appSecret = this.appSecret;
6420
+ if (!appSecret) return "Feishu App Secret \u672A\u914D\u7F6E";
6070
6421
  return null;
6071
6422
  }
6072
6423
  isAuthorized(userId, chatId) {
6073
- const allowedUsers = getBridgeContext().store.getSetting("bridge_feishu_allowed_users") || "";
6424
+ const allowedUsers = (this.channelConfig.allowedUsers || []).join(",");
6074
6425
  if (!allowedUsers) {
6075
6426
  return true;
6076
6427
  }
@@ -6120,7 +6471,9 @@ ${trimmedResponse}`;
6120
6471
  console.log("[feishu-adapter] Group message ignored (bot not @mentioned), chatId:", chatId, "msgId:", msg.message_id);
6121
6472
  try {
6122
6473
  getBridgeContext().store.insertAuditLog({
6123
- channelType: "feishu",
6474
+ channelType: this.channelType,
6475
+ channelProvider: this.provider,
6476
+ channelAlias: this.alias,
6124
6477
  chatId,
6125
6478
  direction: "inbound",
6126
6479
  messageId: msg.message_id,
@@ -6149,7 +6502,9 @@ ${trimmedResponse}`;
6149
6502
  text2 = "[image download failed]";
6150
6503
  try {
6151
6504
  getBridgeContext().store.insertAuditLog({
6152
- channelType: "feishu",
6505
+ channelType: this.channelType,
6506
+ channelProvider: this.provider,
6507
+ channelAlias: this.alias,
6153
6508
  chatId,
6154
6509
  direction: "inbound",
6155
6510
  messageId: msg.message_id,
@@ -6170,7 +6525,9 @@ ${trimmedResponse}`;
6170
6525
  text2 = `[${messageType} download failed]`;
6171
6526
  try {
6172
6527
  getBridgeContext().store.insertAuditLog({
6173
- channelType: "feishu",
6528
+ channelType: this.channelType,
6529
+ channelProvider: this.provider,
6530
+ channelAlias: this.alias,
6174
6531
  chatId,
6175
6532
  direction: "inbound",
6176
6533
  messageId: msg.message_id,
@@ -6197,7 +6554,9 @@ ${trimmedResponse}`;
6197
6554
  if (!text2.trim() && attachments.length === 0) return;
6198
6555
  const timestamp = parseInt(msg.create_time, 10) || Date.now();
6199
6556
  const address = {
6200
- channelType: "feishu",
6557
+ channelType: this.channelType,
6558
+ channelProvider: this.provider,
6559
+ channelAlias: this.alias,
6201
6560
  chatId,
6202
6561
  userId
6203
6562
  };
@@ -6229,7 +6588,9 @@ ${trimmedResponse}`;
6229
6588
  try {
6230
6589
  const summary = attachments.length > 0 ? `[${attachments.length} attachment(s)] ${text2.slice(0, 150)}` : text2.slice(0, 200);
6231
6590
  getBridgeContext().store.insertAuditLog({
6232
- channelType: "feishu",
6591
+ channelType: this.channelType,
6592
+ channelProvider: this.provider,
6593
+ channelAlias: this.alias,
6233
6594
  chatId,
6234
6595
  direction: "inbound",
6235
6596
  messageId: msg.message_id,
@@ -6444,7 +6805,7 @@ ${trimmedResponse}`;
6444
6805
  }
6445
6806
  }
6446
6807
  };
6447
- registerAdapterFactory("feishu", () => new FeishuAdapter());
6808
+ registerAdapterFactory("feishu", (instance) => new FeishuAdapter(instance));
6448
6809
 
6449
6810
  // src/lib/bridge/adapters/discord-adapter.ts
6450
6811
  import crypto3 from "crypto";
@@ -6462,6 +6823,7 @@ async function loadDiscordJs() {
6462
6823
  }
6463
6824
  var DiscordAdapter = class extends BaseChannelAdapter {
6464
6825
  channelType = "discord";
6826
+ provider = "discord";
6465
6827
  running = false;
6466
6828
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
6467
6829
  client = null;
@@ -7048,6 +7410,7 @@ async function sendPrivateMessage(accessToken, params) {
7048
7410
  // src/lib/bridge/adapters/qq-adapter.ts
7049
7411
  var QQAdapter = class extends BaseChannelAdapter {
7050
7412
  channelType = "qq";
7413
+ provider = "qq";
7051
7414
  _running = false;
7052
7415
  queue = [];
7053
7416
  waiters = [];
@@ -7438,218 +7801,6 @@ registerAdapterFactory("qq", () => new QQAdapter());
7438
7801
  // src/weixin-store.ts
7439
7802
  import fs3 from "node:fs";
7440
7803
  import path3 from "node:path";
7441
-
7442
- // src/config.ts
7443
- import fs2 from "node:fs";
7444
- import os from "node:os";
7445
- import path2 from "node:path";
7446
- var LEGACY_CTI_HOME = path2.join(os.homedir(), ".claude-to-im");
7447
- var DEFAULT_CTI_HOME = path2.join(os.homedir(), ".codex-to-im");
7448
- var DEFAULT_WORKSPACE_ROOT = path2.join(os.homedir(), "cx2im");
7449
- function resolveDefaultCtiHome() {
7450
- if (fs2.existsSync(DEFAULT_CTI_HOME)) return DEFAULT_CTI_HOME;
7451
- if (fs2.existsSync(LEGACY_CTI_HOME)) return LEGACY_CTI_HOME;
7452
- return DEFAULT_CTI_HOME;
7453
- }
7454
- var CTI_HOME = process.env.CTI_HOME || resolveDefaultCtiHome();
7455
- var CONFIG_PATH = path2.join(CTI_HOME, "config.env");
7456
- function expandHomePath(value) {
7457
- if (!value) return value;
7458
- if (value === "~") return os.homedir();
7459
- if (value.startsWith("~/") || value.startsWith("~\\")) {
7460
- return path2.join(os.homedir(), value.slice(2));
7461
- }
7462
- return value;
7463
- }
7464
- function parseEnvFile(content) {
7465
- const entries = /* @__PURE__ */ new Map();
7466
- for (const line of content.split("\n")) {
7467
- const trimmed = line.trim();
7468
- if (!trimmed || trimmed.startsWith("#")) continue;
7469
- const eqIdx = trimmed.indexOf("=");
7470
- if (eqIdx === -1) continue;
7471
- const key = trimmed.slice(0, eqIdx).trim();
7472
- let value = trimmed.slice(eqIdx + 1).trim();
7473
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
7474
- value = value.slice(1, -1);
7475
- }
7476
- entries.set(key, value);
7477
- }
7478
- return entries;
7479
- }
7480
- function loadRawConfigEnv() {
7481
- try {
7482
- return parseEnvFile(fs2.readFileSync(CONFIG_PATH, "utf-8"));
7483
- } catch {
7484
- return /* @__PURE__ */ new Map();
7485
- }
7486
- }
7487
- function splitCsv(value) {
7488
- if (!value) return void 0;
7489
- return value.split(",").map((s) => s.trim()).filter(Boolean);
7490
- }
7491
- function parsePositiveInt(value) {
7492
- if (!value) return void 0;
7493
- const parsed = Number(value);
7494
- if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
7495
- return Math.floor(parsed);
7496
- }
7497
- function parseSandboxMode(value) {
7498
- if (value === "read-only" || value === "workspace-write" || value === "danger-full-access") {
7499
- return value;
7500
- }
7501
- return void 0;
7502
- }
7503
- function parseReasoningEffort(value) {
7504
- if (value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh") {
7505
- return value;
7506
- }
7507
- return void 0;
7508
- }
7509
- function loadConfig() {
7510
- const env = loadRawConfigEnv();
7511
- const rawRuntime = env.get("CTI_RUNTIME") || "codex";
7512
- const runtime = ["claude", "codex", "auto"].includes(rawRuntime) ? rawRuntime : "codex";
7513
- return {
7514
- runtime,
7515
- enabledChannels: splitCsv(env.get("CTI_ENABLED_CHANNELS")) ?? ["feishu"],
7516
- defaultWorkspaceRoot: expandHomePath(env.get("CTI_DEFAULT_WORKSPACE_ROOT")) || void 0,
7517
- defaultModel: env.get("CTI_DEFAULT_MODEL") || void 0,
7518
- defaultMode: env.get("CTI_DEFAULT_MODE") || "code",
7519
- historyMessageLimit: parsePositiveInt(env.get("CTI_HISTORY_MESSAGE_LIMIT")) ?? 8,
7520
- codexSkipGitRepoCheck: env.has("CTI_CODEX_SKIP_GIT_REPO_CHECK") ? env.get("CTI_CODEX_SKIP_GIT_REPO_CHECK") === "true" : true,
7521
- codexSandboxMode: parseSandboxMode(env.get("CTI_CODEX_SANDBOX_MODE")) ?? "workspace-write",
7522
- codexReasoningEffort: parseReasoningEffort(env.get("CTI_CODEX_REASONING_EFFORT")) ?? "medium",
7523
- uiAllowLan: env.get("CTI_UI_ALLOW_LAN") === "true",
7524
- uiAccessToken: env.get("CTI_UI_ACCESS_TOKEN") || void 0,
7525
- tgBotToken: env.get("CTI_TG_BOT_TOKEN") || void 0,
7526
- tgChatId: env.get("CTI_TG_CHAT_ID") || void 0,
7527
- tgAllowedUsers: splitCsv(env.get("CTI_TG_ALLOWED_USERS")),
7528
- feishuAppId: env.get("CTI_FEISHU_APP_ID") || void 0,
7529
- feishuAppSecret: env.get("CTI_FEISHU_APP_SECRET") || void 0,
7530
- feishuDomain: env.get("CTI_FEISHU_DOMAIN") || void 0,
7531
- feishuAllowedUsers: splitCsv(env.get("CTI_FEISHU_ALLOWED_USERS")),
7532
- feishuStreamingEnabled: env.has("CTI_FEISHU_STREAMING_ENABLED") ? env.get("CTI_FEISHU_STREAMING_ENABLED") === "true" : true,
7533
- feishuCommandMarkdownEnabled: env.has("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") === "true" : true,
7534
- discordBotToken: env.get("CTI_DISCORD_BOT_TOKEN") || void 0,
7535
- discordAllowedUsers: splitCsv(env.get("CTI_DISCORD_ALLOWED_USERS")),
7536
- discordAllowedChannels: splitCsv(
7537
- env.get("CTI_DISCORD_ALLOWED_CHANNELS")
7538
- ),
7539
- discordAllowedGuilds: splitCsv(env.get("CTI_DISCORD_ALLOWED_GUILDS")),
7540
- qqAppId: env.get("CTI_QQ_APP_ID") || void 0,
7541
- qqAppSecret: env.get("CTI_QQ_APP_SECRET") || void 0,
7542
- qqAllowedUsers: splitCsv(env.get("CTI_QQ_ALLOWED_USERS")),
7543
- qqImageEnabled: env.has("CTI_QQ_IMAGE_ENABLED") ? env.get("CTI_QQ_IMAGE_ENABLED") === "true" : void 0,
7544
- qqMaxImageSize: env.get("CTI_QQ_MAX_IMAGE_SIZE") ? Number(env.get("CTI_QQ_MAX_IMAGE_SIZE")) : void 0,
7545
- weixinBaseUrl: env.get("CTI_WEIXIN_BASE_URL") || void 0,
7546
- weixinCdnBaseUrl: env.get("CTI_WEIXIN_CDN_BASE_URL") || void 0,
7547
- weixinMediaEnabled: env.has("CTI_WEIXIN_MEDIA_ENABLED") ? env.get("CTI_WEIXIN_MEDIA_ENABLED") === "true" : void 0,
7548
- weixinCommandMarkdownEnabled: env.has("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") === "true" : false,
7549
- autoApprove: env.get("CTI_AUTO_APPROVE") === "true"
7550
- };
7551
- }
7552
- function configToSettings(config2) {
7553
- const m = /* @__PURE__ */ new Map();
7554
- m.set("remote_bridge_enabled", "true");
7555
- m.set(
7556
- "bridge_telegram_enabled",
7557
- config2.enabledChannels.includes("telegram") ? "true" : "false"
7558
- );
7559
- if (config2.tgBotToken) m.set("telegram_bot_token", config2.tgBotToken);
7560
- if (config2.tgAllowedUsers)
7561
- m.set("telegram_bridge_allowed_users", config2.tgAllowedUsers.join(","));
7562
- if (config2.tgChatId) m.set("telegram_chat_id", config2.tgChatId);
7563
- m.set(
7564
- "bridge_discord_enabled",
7565
- config2.enabledChannels.includes("discord") ? "true" : "false"
7566
- );
7567
- if (config2.discordBotToken)
7568
- m.set("bridge_discord_bot_token", config2.discordBotToken);
7569
- if (config2.discordAllowedUsers)
7570
- m.set("bridge_discord_allowed_users", config2.discordAllowedUsers.join(","));
7571
- if (config2.discordAllowedChannels)
7572
- m.set(
7573
- "bridge_discord_allowed_channels",
7574
- config2.discordAllowedChannels.join(",")
7575
- );
7576
- if (config2.discordAllowedGuilds)
7577
- m.set(
7578
- "bridge_discord_allowed_guilds",
7579
- config2.discordAllowedGuilds.join(",")
7580
- );
7581
- m.set(
7582
- "bridge_feishu_enabled",
7583
- config2.enabledChannels.includes("feishu") ? "true" : "false"
7584
- );
7585
- if (config2.feishuAppId) m.set("bridge_feishu_app_id", config2.feishuAppId);
7586
- if (config2.feishuAppSecret)
7587
- m.set("bridge_feishu_app_secret", config2.feishuAppSecret);
7588
- if (config2.feishuDomain) m.set("bridge_feishu_domain", config2.feishuDomain);
7589
- if (config2.feishuAllowedUsers)
7590
- m.set("bridge_feishu_allowed_users", config2.feishuAllowedUsers.join(","));
7591
- m.set(
7592
- "bridge_feishu_streaming_enabled",
7593
- config2.feishuStreamingEnabled === false ? "false" : "true"
7594
- );
7595
- m.set(
7596
- "bridge_feishu_command_markdown_enabled",
7597
- config2.feishuCommandMarkdownEnabled === false ? "false" : "true"
7598
- );
7599
- m.set(
7600
- "bridge_qq_enabled",
7601
- config2.enabledChannels.includes("qq") ? "true" : "false"
7602
- );
7603
- if (config2.qqAppId) m.set("bridge_qq_app_id", config2.qqAppId);
7604
- if (config2.qqAppSecret) m.set("bridge_qq_app_secret", config2.qqAppSecret);
7605
- if (config2.qqAllowedUsers)
7606
- m.set("bridge_qq_allowed_users", config2.qqAllowedUsers.join(","));
7607
- if (config2.qqImageEnabled !== void 0)
7608
- m.set("bridge_qq_image_enabled", String(config2.qqImageEnabled));
7609
- if (config2.qqMaxImageSize !== void 0)
7610
- m.set("bridge_qq_max_image_size", String(config2.qqMaxImageSize));
7611
- m.set(
7612
- "bridge_weixin_enabled",
7613
- config2.enabledChannels.includes("weixin") ? "true" : "false"
7614
- );
7615
- if (config2.weixinMediaEnabled !== void 0)
7616
- m.set("bridge_weixin_media_enabled", String(config2.weixinMediaEnabled));
7617
- m.set(
7618
- "bridge_weixin_command_markdown_enabled",
7619
- config2.weixinCommandMarkdownEnabled === true ? "true" : "false"
7620
- );
7621
- if (config2.weixinBaseUrl)
7622
- m.set("bridge_weixin_base_url", config2.weixinBaseUrl);
7623
- if (config2.weixinCdnBaseUrl)
7624
- m.set("bridge_weixin_cdn_base_url", config2.weixinCdnBaseUrl);
7625
- if (config2.defaultWorkspaceRoot) {
7626
- m.set("bridge_default_workspace_root", config2.defaultWorkspaceRoot);
7627
- }
7628
- if (config2.defaultModel) {
7629
- m.set("bridge_default_model", config2.defaultModel);
7630
- m.set("default_model", config2.defaultModel);
7631
- }
7632
- m.set("bridge_default_mode", config2.defaultMode);
7633
- m.set(
7634
- "bridge_history_message_limit",
7635
- String(config2.historyMessageLimit && config2.historyMessageLimit > 0 ? config2.historyMessageLimit : 8)
7636
- );
7637
- m.set(
7638
- "bridge_codex_skip_git_repo_check",
7639
- config2.codexSkipGitRepoCheck === true ? "true" : "false"
7640
- );
7641
- m.set(
7642
- "bridge_codex_sandbox_mode",
7643
- config2.codexSandboxMode || "workspace-write"
7644
- );
7645
- m.set(
7646
- "bridge_codex_reasoning_effort",
7647
- config2.codexReasoningEffort || "medium"
7648
- );
7649
- return m;
7650
- }
7651
-
7652
- // src/weixin-store.ts
7653
7804
  var DATA_DIR = path3.join(CTI_HOME, "data");
7654
7805
  var ACCOUNTS_PATH = path3.join(DATA_DIR, "weixin-accounts.json");
7655
7806
  var CONTEXT_TOKENS_PATH = path3.join(DATA_DIR, "weixin-context-tokens.json");
@@ -13984,7 +14135,10 @@ var DEDUP_MAX3 = 500;
13984
14135
  var BACKOFF_BASE_MS = 2e3;
13985
14136
  var BACKOFF_MAX_MS = 3e4;
13986
14137
  var WeixinAdapter = class extends BaseChannelAdapter {
13987
- channelType = "weixin";
14138
+ channelType;
14139
+ provider = "weixin";
14140
+ alias;
14141
+ channelConfig;
13988
14142
  _running = false;
13989
14143
  idleLogged = false;
13990
14144
  reconcileTimer = null;
@@ -13997,6 +14151,19 @@ var WeixinAdapter = class extends BaseChannelAdapter {
13997
14151
  typingTickets = /* @__PURE__ */ new Map();
13998
14152
  pendingCursors = /* @__PURE__ */ new Map();
13999
14153
  nextBatchId = 1;
14154
+ constructor(instance) {
14155
+ super();
14156
+ this.channelType = instance?.id || "weixin";
14157
+ this.alias = instance?.alias;
14158
+ this.channelConfig = instance?.config || {};
14159
+ }
14160
+ get mediaEnabled() {
14161
+ return this.channelConfig.mediaEnabled === true;
14162
+ }
14163
+ get configuredAccountId() {
14164
+ const value = this.channelConfig.accountId?.trim();
14165
+ return value || void 0;
14166
+ }
14000
14167
  async start() {
14001
14168
  if (this._running) return;
14002
14169
  this._running = true;
@@ -14072,7 +14239,9 @@ var WeixinAdapter = class extends BaseChannelAdapter {
14072
14239
  }
14073
14240
  }
14074
14241
  validateConfig() {
14075
- const linkedAccounts = listWeixinAccounts().filter((account) => account.enabled && account.token);
14242
+ const linkedAccounts = this.filterConfiguredAccounts(
14243
+ listWeixinAccounts().filter((account) => account.enabled && account.token)
14244
+ );
14076
14245
  if (linkedAccounts.length === 0) {
14077
14246
  return "No linked WeChat account. Run the WeChat QR login helper first.";
14078
14247
  }
@@ -14104,7 +14273,9 @@ var WeixinAdapter = class extends BaseChannelAdapter {
14104
14273
  this.queue.push(message);
14105
14274
  }
14106
14275
  async reconcileAccounts() {
14107
- const linkedAccounts = listWeixinAccounts().filter((account) => account.enabled && account.token);
14276
+ const linkedAccounts = this.filterConfiguredAccounts(
14277
+ listWeixinAccounts().filter((account) => account.enabled && account.token)
14278
+ );
14108
14279
  if (linkedAccounts.length === 0 && this.pollAborts.size === 0) {
14109
14280
  if (!this.idleLogged) {
14110
14281
  console.log("[weixin-adapter] No linked WeChat account is enabled, adapter started but idle");
@@ -14242,7 +14413,7 @@ var WeixinAdapter = class extends BaseChannelAdapter {
14242
14413
  const attachments = [];
14243
14414
  let failedCount = 0;
14244
14415
  let missingVoiceTranscriptCount = 0;
14245
- const mediaEnabled = getBridgeContext().store.getSetting("bridge_weixin_media_enabled") === "true";
14416
+ const mediaEnabled = this.mediaEnabled;
14246
14417
  const account = mediaEnabled ? getWeixinAccount(accountId) : void 0;
14247
14418
  const creds = account ? this.accountToCreds(account) : void 0;
14248
14419
  for (const item of message.item_list || []) {
@@ -14294,7 +14465,9 @@ ${failureNote}` : attachments.length > 0 ? failureNote : text2;
14294
14465
  const inbound = {
14295
14466
  messageId: message.message_id || `weixin_${accountId}_${message.seq || Date.now()}`,
14296
14467
  address: {
14297
- channelType: "weixin",
14468
+ channelType: this.channelType,
14469
+ channelProvider: this.provider,
14470
+ channelAlias: this.alias,
14298
14471
  chatId,
14299
14472
  userId: message.from_user_id,
14300
14473
  displayName: message.from_user_id.slice(0, 12)
@@ -14325,7 +14498,9 @@ ${failureNote}` : attachments.length > 0 ? failureNote : text2;
14325
14498
  this.enqueue(inbound);
14326
14499
  const summary = attachments.length > 0 ? `[${attachments.length} attachment(s)] ${inbound.text.slice(0, 150)}` : missingVoiceTranscriptCount > 0 && !inbound.text ? "[voice transcription unavailable]" : failedCount > 0 && !inbound.text ? `[${failedCount} attachment(s) failed]` : inbound.text.slice(0, 200);
14327
14500
  getBridgeContext().store.insertAuditLog({
14328
- channelType: "weixin",
14501
+ channelType: this.channelType,
14502
+ channelProvider: this.provider,
14503
+ channelAlias: this.alias,
14329
14504
  chatId,
14330
14505
  direction: "inbound",
14331
14506
  messageId: inbound.messageId,
@@ -14388,6 +14563,10 @@ ${failureNote}` : attachments.length > 0 ? failureNote : text2;
14388
14563
  getBridgeContext().store.setChannelOffset(batch.offsetKey, batch.cursor);
14389
14564
  this.pendingCursors.delete(updateId);
14390
14565
  }
14566
+ filterConfiguredAccounts(accounts) {
14567
+ if (!this.configuredAccountId) return accounts;
14568
+ return accounts.filter((account) => account.accountId === this.configuredAccountId);
14569
+ }
14391
14570
  };
14392
14571
  function stripFormatting(text2, parseMode) {
14393
14572
  if (parseMode === "HTML") {
@@ -14398,7 +14577,7 @@ function stripFormatting(text2, parseMode) {
14398
14577
  }
14399
14578
  return text2;
14400
14579
  }
14401
- registerAdapterFactory("weixin", () => new WeixinAdapter());
14580
+ registerAdapterFactory("weixin", (instance) => new WeixinAdapter(instance));
14402
14581
 
14403
14582
  // src/session-bindings.ts
14404
14583
  import path5 from "node:path";
@@ -15082,8 +15261,24 @@ function readDesktopSessionEventStream(threadId) {
15082
15261
  }
15083
15262
 
15084
15263
  // src/session-bindings.ts
15085
- function formatChannelLabel(channelType) {
15086
- return channelType === "weixin" ? "\u5FAE\u4FE1" : channelType === "feishu" ? "\u98DE\u4E66" : channelType;
15264
+ function asChannelProvider(value) {
15265
+ return value === "feishu" || value === "weixin" ? value : void 0;
15266
+ }
15267
+ function resolveChannelMeta(channelType, provider) {
15268
+ const instance = findChannelInstance(channelType, loadConfig());
15269
+ if (instance) {
15270
+ return {
15271
+ provider: instance.provider,
15272
+ alias: instance.alias
15273
+ };
15274
+ }
15275
+ return {
15276
+ provider,
15277
+ alias: channelType
15278
+ };
15279
+ }
15280
+ function formatChannelLabel(binding) {
15281
+ return binding.channelAlias?.trim() || resolveChannelMeta(binding.channelType, asChannelProvider(binding.channelProvider)).alias || binding.channelType;
15087
15282
  }
15088
15283
  function formatBindingChatTarget(binding) {
15089
15284
  return binding.chatDisplayName?.trim() || binding.chatId;
@@ -15104,7 +15299,7 @@ function assertBindingTargetAvailable(store, current, opts) {
15104
15299
  );
15105
15300
  if (!conflict) return;
15106
15301
  throw new Error(
15107
- `\u8BE5\u4F1A\u8BDD\u5DF2\u7ED1\u5B9A\u5230 ${formatChannelLabel(conflict.channelType)} \u804A\u5929 ${formatBindingChatTarget(conflict)}\u3002\u4E00\u4E2A\u4F1A\u8BDD\u53EA\u80FD\u7ED1\u5B9A\u4E00\u4E2A\u804A\u5929\u3002`
15302
+ `\u8BE5\u4F1A\u8BDD\u5DF2\u7ED1\u5B9A\u5230 ${formatChannelLabel(conflict)} \u804A\u5929 ${formatBindingChatTarget(conflict)}\u3002\u4E00\u4E2A\u4F1A\u8BDD\u53EA\u80FD\u7ED1\u5B9A\u4E00\u4E2A\u804A\u5929\u3002`
15108
15303
  );
15109
15304
  }
15110
15305
  function getSessionMode(store, session) {
@@ -15118,8 +15313,11 @@ function bindStoreToSession(store, channelType, chatId, sessionId) {
15118
15313
  { channelType, chatId },
15119
15314
  { sessionId: session.id, sdkSessionId: session.sdk_session_id || void 0 }
15120
15315
  );
15316
+ const meta = resolveChannelMeta(channelType);
15121
15317
  return store.upsertChannelBinding({
15122
15318
  channelType,
15319
+ channelProvider: meta.provider,
15320
+ channelAlias: meta.alias,
15123
15321
  chatId,
15124
15322
  codepilotSessionId: session.id,
15125
15323
  sdkSessionId: session.sdk_session_id || "",
@@ -15134,10 +15332,13 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
15134
15332
  { channelType, chatId },
15135
15333
  { sdkSessionId }
15136
15334
  );
15335
+ const meta = resolveChannelMeta(channelType);
15137
15336
  const existing = store.findSessionBySdkSessionId(sdkSessionId);
15138
15337
  if (existing) {
15139
15338
  return store.upsertChannelBinding({
15140
15339
  channelType,
15340
+ channelProvider: meta.provider,
15341
+ channelAlias: meta.alias,
15141
15342
  chatId,
15142
15343
  codepilotSessionId: existing.id,
15143
15344
  sdkSessionId,
@@ -15159,6 +15360,8 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
15159
15360
  store.updateSdkSessionId(session.id, sdkSessionId);
15160
15361
  return store.upsertChannelBinding({
15161
15362
  channelType,
15363
+ channelProvider: meta.provider,
15364
+ channelAlias: meta.alias,
15162
15365
  chatId,
15163
15366
  codepilotSessionId: session.id,
15164
15367
  sdkSessionId,
@@ -15397,8 +15600,8 @@ function stripOutboundArtifactBlocksForStreaming(text2) {
15397
15600
  }
15398
15601
  return stripped.replace(/\n{3,}/g, "\n\n").trimEnd();
15399
15602
  }
15400
- function supportsOutboundArtifacts(channelType) {
15401
- return channelType === "feishu";
15603
+ function supportsOutboundArtifacts(provider) {
15604
+ return provider === "feishu";
15402
15605
  }
15403
15606
 
15404
15607
  // src/lib/bridge/conversation-engine.ts
@@ -15940,9 +16143,9 @@ async function deliver(adapter, message, opts) {
15940
16143
  } catch {
15941
16144
  }
15942
16145
  }
15943
- const limit = PLATFORM_LIMITS[adapter.channelType] || 4096;
16146
+ const limit = PLATFORM_LIMITS[adapter.provider] || PLATFORM_LIMITS[adapter.channelType] || 4096;
15944
16147
  let chunks = chunkText2(message.text, limit);
15945
- if (adapter.channelType === "qq" && chunks.length > 3) {
16148
+ if (adapter.provider === "qq" && chunks.length > 3) {
15946
16149
  const first2 = chunks.slice(0, 2);
15947
16150
  let merged = chunks.slice(2).join("\n");
15948
16151
  if (merged.length > limit) {
@@ -16123,8 +16326,8 @@ async function forwardPermissionRequest(adapter, address, permissionRequestId, t
16123
16326
  const inputStr = JSON.stringify(toolInput, null, 2);
16124
16327
  const truncatedInput = inputStr.length > 300 ? inputStr.slice(0, 300) + "..." : inputStr;
16125
16328
  let result;
16126
- if (adapter.channelType === "qq" || adapter.channelType === "weixin") {
16127
- const channelLabel = adapter.channelType === "weixin" ? "WeChat" : "QQ";
16329
+ if (adapter.provider === "qq" || adapter.provider === "weixin") {
16330
+ const channelLabel = adapter.provider === "weixin" ? "WeChat" : "QQ";
16128
16331
  const plainText = [
16129
16332
  `Permission Required`,
16130
16333
  ``,
@@ -16827,6 +17030,27 @@ function getStreamConfig(channelType = "telegram") {
16827
17030
  const maxChars = parseInt(store.getSetting(`${prefix}max_chars`) || "", 10) || defaults.maxChars;
16828
17031
  return { intervalMs, minDeltaChars, maxChars };
16829
17032
  }
17033
+ function listConfiguredChannelInstances() {
17034
+ const { store } = getBridgeContext();
17035
+ const raw = store.getSetting("bridge_channel_instances_json");
17036
+ if (!raw) return [];
17037
+ try {
17038
+ const parsed = JSON.parse(raw);
17039
+ return Array.isArray(parsed) ? parsed : [];
17040
+ } catch {
17041
+ return [];
17042
+ }
17043
+ }
17044
+ function getConfiguredChannelInstance(channelType) {
17045
+ return listConfiguredChannelInstances().find((channel) => channel.id === channelType) || null;
17046
+ }
17047
+ function inferChannelProvider(channelType) {
17048
+ const instance = getConfiguredChannelInstance(channelType);
17049
+ return instance?.provider;
17050
+ }
17051
+ function getChannelProviderKey(channelType) {
17052
+ return inferChannelProvider(channelType) || channelType;
17053
+ }
16830
17054
  function parseListIndex(raw) {
16831
17055
  const trimmed = raw.trim();
16832
17056
  if (!/^\d+$/.test(trimmed)) return null;
@@ -16909,7 +17133,7 @@ function getSessionDisplayName(session, fallbackDirectory) {
16909
17133
  if (session?.id) return session.id.slice(0, 8);
16910
17134
  return "\u672A\u547D\u540D\u4F1A\u8BDD";
16911
17135
  }
16912
- function nowIso() {
17136
+ function nowIso2() {
16913
17137
  return (/* @__PURE__ */ new Date()).toISOString();
16914
17138
  }
16915
17139
  function buildInteractiveStreamKey(sessionId, messageId) {
@@ -17026,12 +17250,12 @@ function buildDesktopThreadsCommandResponse(desktopSessions, markdown, showAll,
17026
17250
  );
17027
17251
  }
17028
17252
  function isFeedbackMarkdownEnabled(channelType) {
17029
- const { store } = getBridgeContext();
17030
- if (channelType === "feishu") {
17031
- return store.getSetting("bridge_feishu_command_markdown_enabled") !== "false";
17253
+ const instance = getConfiguredChannelInstance(channelType);
17254
+ if (instance?.provider === "feishu") {
17255
+ return instance.config.feedbackMarkdownEnabled !== false;
17032
17256
  }
17033
- if (channelType === "weixin") {
17034
- return store.getSetting("bridge_weixin_command_markdown_enabled") === "true";
17257
+ if (instance?.provider === "weixin") {
17258
+ return instance.config.feedbackMarkdownEnabled === true;
17035
17259
  }
17036
17260
  return false;
17037
17261
  }
@@ -17052,7 +17276,8 @@ function toUserVisibleBindingError(error, fallback) {
17052
17276
  return fallback;
17053
17277
  }
17054
17278
  function formatBindingChatLabel(binding) {
17055
- const channelLabel = binding.channelType === "weixin" ? "\u5FAE\u4FE1" : binding.channelType === "feishu" ? "\u98DE\u4E66" : binding.channelType;
17279
+ const instance = getConfiguredChannelInstance(binding.channelType);
17280
+ const channelLabel = binding.channelAlias || instance?.alias || (binding.channelProvider === "feishu" ? "\u98DE\u4E66" : binding.channelProvider === "weixin" ? "\u5FAE\u4FE1" : binding.channelType);
17056
17281
  const chatLabel = binding.chatDisplayName?.trim() || binding.chatId;
17057
17282
  return `${channelLabel} \u804A\u5929 ${chatLabel}`;
17058
17283
  }
@@ -17391,7 +17616,7 @@ async function deliverTextResponse(adapter, address, responseText, sessionId, re
17391
17616
  }
17392
17617
  return { ok: true };
17393
17618
  }
17394
- if (parseMode === "Markdown" && adapter.channelType === "feishu") {
17619
+ if (parseMode === "Markdown" && adapter.provider === "feishu") {
17395
17620
  return deliver(adapter, {
17396
17621
  address,
17397
17622
  text: responseText,
@@ -17418,7 +17643,7 @@ async function deliverResponse(adapter, address, responseText, sessionId, replyT
17418
17643
  if (!captionResult.ok) return captionResult;
17419
17644
  lastResult = captionResult;
17420
17645
  }
17421
- if (!supportsOutboundArtifacts(adapter.channelType)) {
17646
+ if (!supportsOutboundArtifacts(adapter.provider)) {
17422
17647
  lastResult = await deliverTextResponse(
17423
17648
  adapter,
17424
17649
  address,
@@ -17556,7 +17781,7 @@ function syncSessionRuntimeState(sessionId) {
17556
17781
  store.updateSession(sessionId, {
17557
17782
  queued_count: queuedCount,
17558
17783
  runtime_status: runtimeStatus,
17559
- last_runtime_update_at: nowIso()
17784
+ last_runtime_update_at: nowIso2()
17560
17785
  });
17561
17786
  }
17562
17787
  function incrementQueuedCount(sessionId) {
@@ -17918,7 +18143,7 @@ function getMirrorStreamingAdapter(subscription) {
17918
18143
  const state = getState();
17919
18144
  const adapter = state.adapters.get(subscription.channelType);
17920
18145
  if (!adapter || !adapter.isRunning()) return null;
17921
- if (subscription.channelType !== "feishu") return null;
18146
+ if (getChannelProviderKey(subscription.channelType) !== "feishu") return null;
17922
18147
  if (typeof adapter.onStreamText !== "function" || typeof adapter.onStreamEnd !== "function") {
17923
18148
  return null;
17924
18149
  }
@@ -17998,7 +18223,7 @@ async function deliverMirrorTurn(subscription, turn) {
17998
18223
  renderedStreamText || buildMirrorTitle(title, markdown),
17999
18224
  responseParseMode
18000
18225
  );
18001
- if (subscription.channelType === "feishu" && typeof adapter.onStreamEnd === "function") {
18226
+ if (getChannelProviderKey(subscription.channelType) === "feishu" && typeof adapter.onStreamEnd === "function") {
18002
18227
  try {
18003
18228
  const finalized = await adapter.onStreamEnd(
18004
18229
  subscription.chatId,
@@ -18007,7 +18232,7 @@ async function deliverMirrorTurn(subscription, turn) {
18007
18232
  turn.streamKey
18008
18233
  );
18009
18234
  if (finalized) {
18010
- subscription.lastDeliveredAt = turn.timestamp || nowIso();
18235
+ subscription.lastDeliveredAt = turn.timestamp || nowIso2();
18011
18236
  return;
18012
18237
  }
18013
18238
  } catch (error) {
@@ -18029,7 +18254,7 @@ async function deliverMirrorTurn(subscription, turn) {
18029
18254
  if (!response.ok) {
18030
18255
  throw new Error(response.error || "mirror delivery failed");
18031
18256
  }
18032
- subscription.lastDeliveredAt = turn.timestamp || nowIso();
18257
+ subscription.lastDeliveredAt = turn.timestamp || nowIso2();
18033
18258
  }
18034
18259
  async function deliverMirrorTurns(subscription, turns) {
18035
18260
  for (const turn of turns.slice(-MIRROR_EVENT_BATCH_LIMIT)) {
@@ -18037,7 +18262,7 @@ async function deliverMirrorTurns(subscription, turns) {
18037
18262
  }
18038
18263
  }
18039
18264
  function createMirrorTurnState(sessionId, timestamp, turnId) {
18040
- const safeTimestamp = timestamp || nowIso();
18265
+ const safeTimestamp = timestamp || nowIso2();
18041
18266
  return {
18042
18267
  turnId: turnId || null,
18043
18268
  streamKey: buildMirrorStreamKey(sessionId, turnId || null, safeTimestamp),
@@ -18101,7 +18326,7 @@ function finalizeMirrorTurn(subscription, signature, timestamp, status, preferre
18101
18326
  userText,
18102
18327
  text: text2,
18103
18328
  signature,
18104
- timestamp: timestamp || pendingTurn.lastActivityAt || nowIso(),
18329
+ timestamp: timestamp || pendingTurn.lastActivityAt || nowIso2(),
18105
18330
  status,
18106
18331
  ...signature.startsWith("timeout:") ? { timedOut: true } : {}
18107
18332
  };
@@ -18336,7 +18561,7 @@ async function reconcileMirrorSubscription(subscription) {
18336
18561
  resetMirrorReadState(subscription);
18337
18562
  }
18338
18563
  watchMirrorFile(subscription, subscription.filePath);
18339
- subscription.lastReconciledAt = nowIso();
18564
+ subscription.lastReconciledAt = nowIso2();
18340
18565
  if (!subscription.filePath) {
18341
18566
  syncMirrorSessionState(subscription.sessionId);
18342
18567
  return;
@@ -18484,6 +18709,51 @@ function notifyAdapterSetChanged() {
18484
18709
  const { lifecycle } = getBridgeContext();
18485
18710
  lifecycle.onBridgeAdaptersChanged?.(getActiveChannelTypes());
18486
18711
  }
18712
+ function stableFingerprintValue(value) {
18713
+ if (Array.isArray(value)) {
18714
+ return value.map(stableFingerprintValue);
18715
+ }
18716
+ if (value && typeof value === "object") {
18717
+ return Object.fromEntries(
18718
+ Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entryValue]) => [key, stableFingerprintValue(entryValue)])
18719
+ );
18720
+ }
18721
+ return value;
18722
+ }
18723
+ function buildAdapterConfigFingerprint(instance) {
18724
+ const normalizedConfig = stableFingerprintValue(instance.config);
18725
+ return JSON.stringify({
18726
+ provider: instance.provider,
18727
+ alias: instance.alias,
18728
+ enabled: instance.enabled,
18729
+ config: normalizedConfig
18730
+ });
18731
+ }
18732
+ function listEnabledAdapterInstances() {
18733
+ const { store } = getBridgeContext();
18734
+ const configured = listConfiguredChannelInstances().filter((channel) => channel.enabled).map((channel) => ({
18735
+ id: channel.id,
18736
+ provider: channel.provider,
18737
+ alias: channel.alias,
18738
+ enabled: channel.enabled,
18739
+ config: channel.config
18740
+ }));
18741
+ const configuredProviders = new Set(configured.map((channel) => channel.provider));
18742
+ for (const provider of getRegisteredTypes()) {
18743
+ if (provider === "feishu" || provider === "weixin") continue;
18744
+ if (configuredProviders.has(provider)) continue;
18745
+ const enabled = store.getSetting(`bridge_${provider}_enabled`) === "true";
18746
+ if (!enabled) continue;
18747
+ configured.push({
18748
+ id: provider,
18749
+ provider,
18750
+ alias: provider,
18751
+ enabled: true,
18752
+ config: {}
18753
+ });
18754
+ }
18755
+ return configured;
18756
+ }
18487
18757
  async function stopAdapterInstance(channelType) {
18488
18758
  const state = getState();
18489
18759
  const adapter = state.adapters.get(channelType);
@@ -18501,44 +18771,50 @@ async function stopAdapterInstance(channelType) {
18501
18771
  }
18502
18772
  async function syncConfiguredAdapters(options) {
18503
18773
  const state = getState();
18504
- const { store } = getBridgeContext();
18505
18774
  let changed = false;
18506
- for (const channelType of getRegisteredTypes()) {
18507
- const enabled = store.getSetting(`bridge_${channelType}_enabled`) === "true";
18508
- const existing = state.adapters.get(channelType);
18509
- if (!enabled) {
18510
- if (existing) {
18511
- await stopAdapterInstance(channelType);
18512
- changed = true;
18513
- }
18514
- continue;
18515
- }
18775
+ const desiredInstances = listEnabledAdapterInstances();
18776
+ const desiredKeys = new Set(desiredInstances.map((channel) => channel.id));
18777
+ const desiredFingerprints = new Map(
18778
+ desiredInstances.map((instance) => [instance.id, buildAdapterConfigFingerprint(instance)])
18779
+ );
18780
+ for (const existingKey of Array.from(state.adapters.keys())) {
18781
+ if (desiredKeys.has(existingKey)) continue;
18782
+ await stopAdapterInstance(existingKey);
18783
+ changed = true;
18784
+ }
18785
+ for (const instance of desiredInstances) {
18786
+ const existing = state.adapters.get(instance.id);
18787
+ const desiredFingerprint = desiredFingerprints.get(instance.id) || "";
18788
+ const existingMeta = state.adapterMeta.get(instance.id);
18789
+ if (existing && existingMeta?.configFingerprint === desiredFingerprint) continue;
18516
18790
  if (existing) {
18517
- continue;
18791
+ await stopAdapterInstance(instance.id);
18792
+ changed = true;
18518
18793
  }
18519
- const adapter = createAdapter(channelType);
18794
+ const adapter = createAdapter(instance);
18520
18795
  if (!adapter) continue;
18521
18796
  const configError = adapter.validateConfig();
18522
18797
  if (configError) {
18523
- console.warn(`[bridge-manager] ${channelType} adapter not valid:`, configError);
18798
+ console.warn(`[bridge-manager] ${instance.id} adapter not valid:`, configError);
18524
18799
  continue;
18525
18800
  }
18526
18801
  try {
18527
- state.adapters.set(channelType, adapter);
18528
- state.adapterMeta.set(channelType, {
18802
+ state.adapters.set(instance.id, adapter);
18803
+ state.adapterMeta.set(instance.id, {
18529
18804
  lastMessageAt: null,
18530
- lastError: null
18805
+ lastError: null,
18806
+ configFingerprint: desiredFingerprint
18531
18807
  });
18532
18808
  await adapter.start();
18533
- console.log(`[bridge-manager] Started adapter: ${channelType}`);
18809
+ console.log(`[bridge-manager] Started adapter: ${instance.id}`);
18534
18810
  if (options.startLoops && state.running && adapter.isRunning()) {
18535
18811
  runAdapterLoop(adapter);
18536
18812
  }
18537
18813
  changed = true;
18538
18814
  } catch (err) {
18539
- state.adapters.delete(channelType);
18540
- state.adapterMeta.delete(channelType);
18541
- console.error(`[bridge-manager] Failed to start adapter ${channelType}:`, err);
18815
+ state.adapters.delete(instance.id);
18816
+ state.adapterMeta.delete(instance.id);
18817
+ console.error(`[bridge-manager] Failed to start adapter ${instance.id}:`, err);
18542
18818
  }
18543
18819
  }
18544
18820
  if (changed) {
@@ -18637,6 +18913,8 @@ function getStatus() {
18637
18913
  const meta = state.adapterMeta.get(type);
18638
18914
  return {
18639
18915
  channelType: adapter.channelType,
18916
+ channelProvider: adapter.provider,
18917
+ channelAlias: adapter.alias,
18640
18918
  running: adapter.isRunning(),
18641
18919
  connectedAt: state.startedAt,
18642
18920
  lastMessageAt: meta?.lastMessageAt ?? null,
@@ -18654,7 +18932,7 @@ function runAdapterLoop(adapter) {
18654
18932
  try {
18655
18933
  const msg = await adapter.consumeOne();
18656
18934
  if (!msg) continue;
18657
- if (msg.callbackData || msg.text.trim().startsWith("/") || isNumericPermissionShortcut(adapter.channelType, msg.text.trim(), msg.address.chatId)) {
18935
+ if (msg.callbackData || msg.text.trim().startsWith("/") || isNumericPermissionShortcut(adapter.provider, msg.text.trim(), msg.address.chatId)) {
18658
18936
  await handleMessage(adapter, msg);
18659
18937
  } else {
18660
18938
  const binding = resolve(msg.address);
@@ -18669,7 +18947,7 @@ function runAdapterLoop(adapter) {
18669
18947
  if (abort.signal.aborted) break;
18670
18948
  const errMsg = err instanceof Error ? err.message : String(err);
18671
18949
  console.error(`[bridge-manager] Error in ${adapter.channelType} loop:`, err);
18672
- const meta = state.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null };
18950
+ const meta = state.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null, configFingerprint: "" };
18673
18951
  meta.lastError = errMsg;
18674
18952
  state.adapterMeta.set(adapter.channelType, meta);
18675
18953
  await new Promise((r) => setTimeout(r, 1e3));
@@ -18679,7 +18957,7 @@ function runAdapterLoop(adapter) {
18679
18957
  if (!abort.signal.aborted) {
18680
18958
  const errMsg = err instanceof Error ? err.message : String(err);
18681
18959
  console.error(`[bridge-manager] ${adapter.channelType} loop crashed:`, err);
18682
- const meta = state.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null };
18960
+ const meta = state.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null, configFingerprint: "" };
18683
18961
  meta.lastError = errMsg;
18684
18962
  state.adapterMeta.set(adapter.channelType, meta);
18685
18963
  }
@@ -18688,7 +18966,7 @@ function runAdapterLoop(adapter) {
18688
18966
  async function handleMessage(adapter, msg) {
18689
18967
  const { store } = getBridgeContext();
18690
18968
  const adapterState = getState();
18691
- const meta = adapterState.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null };
18969
+ const meta = adapterState.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null, configFingerprint: "" };
18692
18970
  meta.lastMessageAt = (/* @__PURE__ */ new Date()).toISOString();
18693
18971
  adapterState.adapterMeta.set(adapter.channelType, meta);
18694
18972
  const ack = () => {
@@ -18732,7 +19010,7 @@ async function handleMessage(adapter, msg) {
18732
19010
  ack();
18733
19011
  return;
18734
19012
  }
18735
- if (adapter.channelType === "feishu" || adapter.channelType === "qq" || adapter.channelType === "weixin") {
19013
+ if (adapter.provider === "feishu" || adapter.provider === "qq" || adapter.provider === "weixin") {
18736
19014
  const normalized = rawText.normalize("NFKC").replace(/[\u200B-\u200D\uFEFF]/g, "").trim();
18737
19015
  if (/^[123]$/.test(normalized)) {
18738
19016
  const currentBinding = store.getChannelBinding(msg.address.channelType, msg.address.chatId);
@@ -18852,7 +19130,7 @@ async function handleMessage(adapter, msg) {
18852
19130
  pendingText: ""
18853
19131
  };
18854
19132
  }
18855
- const streamCfg = previewState ? getStreamConfig(adapter.channelType) : null;
19133
+ const streamCfg = previewState ? getStreamConfig(adapter.provider) : null;
18856
19134
  const previewOnPartialText = previewState && streamCfg ? (fullText) => {
18857
19135
  const ps = previewState;
18858
19136
  const cfg = streamCfg;
@@ -19643,6 +19921,35 @@ function uuid() {
19643
19921
  function now() {
19644
19922
  return (/* @__PURE__ */ new Date()).toISOString();
19645
19923
  }
19924
+ function defaultAliasForProvider2(provider) {
19925
+ if (provider === "feishu") return "\u98DE\u4E66";
19926
+ if (provider === "weixin") return "\u5FAE\u4FE1";
19927
+ return void 0;
19928
+ }
19929
+ function normalizeLegacyBinding(binding) {
19930
+ const config2 = loadConfig();
19931
+ const legacyProvider = binding.channelType === "feishu" || binding.channelType === "weixin" ? binding.channelType : void 0;
19932
+ const resolvedInstance = legacyProvider ? (config2.channels || []).find((channel) => channel.provider === legacyProvider) : findChannelInstance(binding.channelType, config2);
19933
+ if (!resolvedInstance && !legacyProvider) {
19934
+ return {
19935
+ ...binding,
19936
+ active: binding.active !== false
19937
+ };
19938
+ }
19939
+ const channelType = resolvedInstance?.id || binding.channelType;
19940
+ const channelProvider = resolvedInstance?.provider || legacyProvider || binding.channelProvider;
19941
+ const channelAlias = resolvedInstance?.alias || binding.channelAlias || defaultAliasForProvider2(channelProvider);
19942
+ return {
19943
+ ...binding,
19944
+ channelType,
19945
+ channelProvider,
19946
+ channelAlias,
19947
+ active: binding.active !== false
19948
+ };
19949
+ }
19950
+ function didBindingChange(before, after) {
19951
+ return before.channelType !== after.channelType || before.channelProvider !== after.channelProvider || before.channelAlias !== after.channelAlias || before.active !== false !== after.active;
19952
+ }
19646
19953
  var JsonFileStore = class {
19647
19954
  settings;
19648
19955
  dynamicSettings;
@@ -19700,7 +20007,19 @@ var JsonFileStore = class {
19700
20007
  path12.join(DATA_DIR2, "bindings.json"),
19701
20008
  {}
19702
20009
  );
19703
- this.bindings = new Map(Object.entries(bindings));
20010
+ const normalized = /* @__PURE__ */ new Map();
20011
+ let changed = false;
20012
+ for (const binding of Object.values(bindings)) {
20013
+ const normalizedBinding = normalizeLegacyBinding(binding);
20014
+ if (didBindingChange(binding, normalizedBinding)) {
20015
+ changed = true;
20016
+ }
20017
+ normalized.set(`${normalizedBinding.channelType}:${normalizedBinding.chatId}`, normalizedBinding);
20018
+ }
20019
+ this.bindings = normalized;
20020
+ if (changed) {
20021
+ this.persistBindings();
20022
+ }
19704
20023
  }
19705
20024
  persistSessions() {
19706
20025
  writeJson(
@@ -19780,6 +20099,8 @@ var JsonFileStore = class {
19780
20099
  ...existing,
19781
20100
  codepilotSessionId: data.codepilotSessionId,
19782
20101
  sdkSessionId: data.sdkSessionId ?? existing.sdkSessionId,
20102
+ channelProvider: data.channelProvider ?? existing.channelProvider,
20103
+ channelAlias: data.channelAlias ?? existing.channelAlias,
19783
20104
  chatUserId: data.chatUserId ?? existing.chatUserId,
19784
20105
  chatDisplayName: data.chatDisplayName ?? existing.chatDisplayName,
19785
20106
  workingDirectory: data.workingDirectory,
@@ -19794,6 +20115,8 @@ var JsonFileStore = class {
19794
20115
  const binding = {
19795
20116
  id: uuid(),
19796
20117
  channelType: data.channelType,
20118
+ channelProvider: data.channelProvider,
20119
+ channelAlias: data.channelAlias,
19797
20120
  chatId: data.chatId,
19798
20121
  chatUserId: data.chatUserId,
19799
20122
  chatDisplayName: data.chatDisplayName,
@@ -20731,6 +21054,9 @@ function writeStatus(info) {
20731
21054
  function getRunningChannels() {
20732
21055
  return getStatus().adapters.map((adapter) => adapter.channelType).sort();
20733
21056
  }
21057
+ function getAdapterStatuses() {
21058
+ return getStatus().adapters;
21059
+ }
20734
21060
  async function main() {
20735
21061
  const config2 = loadConfig();
20736
21062
  setupLogger();
@@ -20758,7 +21084,8 @@ async function main() {
20758
21084
  pid: process.pid,
20759
21085
  runId,
20760
21086
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
20761
- channels
21087
+ channels,
21088
+ adapters: getAdapterStatuses()
20762
21089
  });
20763
21090
  console.log(`[codex-to-im] Bridge started (PID: ${process.pid}, channels: ${channels.join(", ")})`);
20764
21091
  },
@@ -20767,12 +21094,13 @@ async function main() {
20767
21094
  running: true,
20768
21095
  pid: process.pid,
20769
21096
  runId,
20770
- channels
21097
+ channels,
21098
+ adapters: getAdapterStatuses()
20771
21099
  });
20772
21100
  console.log(`[codex-to-im] Active channels updated: ${channels.join(", ") || "none"}`);
20773
21101
  },
20774
21102
  onBridgeStop: () => {
20775
- writeStatus({ running: false, channels: [] });
21103
+ writeStatus({ running: false, channels: [], adapters: [] });
20776
21104
  console.log("[codex-to-im] Bridge stopped");
20777
21105
  }
20778
21106
  }