codex-to-im 1.0.19 → 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
  },
@@ -5649,7 +6009,16 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5649
6009
  status: statusLabels[status] || status,
5650
6010
  elapsed: formatElapsed(elapsedMs)
5651
6011
  };
5652
- const finalCardJson = buildFinalCardJson(responseText, state.toolCalls, footer);
6012
+ const existingText = state.pendingText || "";
6013
+ const trimmedExisting = existingText.trim();
6014
+ const trimmedResponse = responseText.trim();
6015
+ let finalText = trimmedResponse || trimmedExisting;
6016
+ if (status === "interrupted" && trimmedExisting && trimmedResponse && trimmedResponse !== trimmedExisting && !trimmedExisting.includes(trimmedResponse)) {
6017
+ finalText = `${trimmedExisting}
6018
+
6019
+ ${trimmedResponse}`;
6020
+ }
6021
+ const finalCardJson = buildFinalCardJson(finalText, state.toolCalls, footer);
5653
6022
  state.sequence++;
5654
6023
  await cardkit.card.update({
5655
6024
  path: { card_id: state.cardId },
@@ -5746,18 +6115,11 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5746
6115
  return this.sendAsPost(message.address.chatId, text2);
5747
6116
  }
5748
6117
  getOpenApiBaseUrl() {
5749
- const configured = (getBridgeContext().store.getSetting("bridge_feishu_domain") || "").trim().replace(/\/+$/, "");
5750
- if (configured.startsWith("http://") || configured.startsWith("https://")) {
5751
- return configured;
5752
- }
5753
- if (configured.toLowerCase() === "lark") {
5754
- return "https://open.larksuite.com";
5755
- }
5756
- return "https://open.feishu.cn";
6118
+ return feishuSiteToApiBaseUrl(this.site);
5757
6119
  }
5758
6120
  async getTenantAccessToken() {
5759
- const appId = getBridgeContext().store.getSetting("bridge_feishu_app_id") || "";
5760
- const appSecret = getBridgeContext().store.getSetting("bridge_feishu_app_secret") || "";
6121
+ const appId = this.appId;
6122
+ const appSecret = this.appSecret;
5761
6123
  const domain = this.getOpenApiBaseUrl();
5762
6124
  if (!appId || !appSecret) {
5763
6125
  throw new Error("Feishu App ID / App Secret not configured");
@@ -5797,7 +6159,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5797
6159
  return { ok: true, messageId: lastMessageId };
5798
6160
  }
5799
6161
  async sendAttachment(chatId, attachment, replyToMessageId) {
5800
- if (!fs.existsSync(attachment.path)) {
6162
+ if (!fs2.existsSync(attachment.path)) {
5801
6163
  return { ok: false, error: `Attachment not found: ${attachment.path}` };
5802
6164
  }
5803
6165
  try {
@@ -5823,10 +6185,10 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5823
6185
  }
5824
6186
  async uploadImage(attachment) {
5825
6187
  const token = await this.getTenantAccessToken();
5826
- const fileName = attachment.name || path.basename(attachment.path) || "image.png";
6188
+ const fileName = attachment.name || path2.basename(attachment.path) || "image.png";
5827
6189
  const form = new FormData();
5828
6190
  form.set("image_type", "message");
5829
- form.set("image", new Blob([fs.readFileSync(attachment.path)]), fileName);
6191
+ form.set("image", new Blob([fs2.readFileSync(attachment.path)]), fileName);
5830
6192
  const response = await fetch(`${this.getOpenApiBaseUrl()}/open-apis/im/v1/images`, {
5831
6193
  method: "POST",
5832
6194
  headers: { Authorization: `Bearer ${token}` },
@@ -5840,11 +6202,11 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5840
6202
  }
5841
6203
  async uploadFile(attachment) {
5842
6204
  const token = await this.getTenantAccessToken();
5843
- const fileName = attachment.name || path.basename(attachment.path) || "attachment.bin";
6205
+ const fileName = attachment.name || path2.basename(attachment.path) || "attachment.bin";
5844
6206
  const form = new FormData();
5845
6207
  form.set("file_type", "stream");
5846
6208
  form.set("file_name", fileName);
5847
- form.set("file", new Blob([fs.readFileSync(attachment.path)]), fileName);
6209
+ form.set("file", new Blob([fs2.readFileSync(attachment.path)]), fileName);
5848
6210
  const response = await fetch(`${this.getOpenApiBaseUrl()}/open-apis/im/v1/files`, {
5849
6211
  method: "POST",
5850
6212
  headers: { Authorization: `Bearer ${token}` },
@@ -6052,16 +6414,14 @@ var FeishuAdapter = class extends BaseChannelAdapter {
6052
6414
  }
6053
6415
  // ── Config & Auth ───────────────────────────────────────────
6054
6416
  validateConfig() {
6055
- const enabled = getBridgeContext().store.getSetting("bridge_feishu_enabled");
6056
- if (enabled !== "true") return "bridge_feishu_enabled is not true";
6057
- const appId = getBridgeContext().store.getSetting("bridge_feishu_app_id");
6058
- if (!appId) return "bridge_feishu_app_id not configured";
6059
- const appSecret = getBridgeContext().store.getSetting("bridge_feishu_app_secret");
6060
- 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";
6061
6421
  return null;
6062
6422
  }
6063
6423
  isAuthorized(userId, chatId) {
6064
- const allowedUsers = getBridgeContext().store.getSetting("bridge_feishu_allowed_users") || "";
6424
+ const allowedUsers = (this.channelConfig.allowedUsers || []).join(",");
6065
6425
  if (!allowedUsers) {
6066
6426
  return true;
6067
6427
  }
@@ -6111,7 +6471,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
6111
6471
  console.log("[feishu-adapter] Group message ignored (bot not @mentioned), chatId:", chatId, "msgId:", msg.message_id);
6112
6472
  try {
6113
6473
  getBridgeContext().store.insertAuditLog({
6114
- channelType: "feishu",
6474
+ channelType: this.channelType,
6475
+ channelProvider: this.provider,
6476
+ channelAlias: this.alias,
6115
6477
  chatId,
6116
6478
  direction: "inbound",
6117
6479
  messageId: msg.message_id,
@@ -6140,7 +6502,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
6140
6502
  text2 = "[image download failed]";
6141
6503
  try {
6142
6504
  getBridgeContext().store.insertAuditLog({
6143
- channelType: "feishu",
6505
+ channelType: this.channelType,
6506
+ channelProvider: this.provider,
6507
+ channelAlias: this.alias,
6144
6508
  chatId,
6145
6509
  direction: "inbound",
6146
6510
  messageId: msg.message_id,
@@ -6161,7 +6525,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
6161
6525
  text2 = `[${messageType} download failed]`;
6162
6526
  try {
6163
6527
  getBridgeContext().store.insertAuditLog({
6164
- channelType: "feishu",
6528
+ channelType: this.channelType,
6529
+ channelProvider: this.provider,
6530
+ channelAlias: this.alias,
6165
6531
  chatId,
6166
6532
  direction: "inbound",
6167
6533
  messageId: msg.message_id,
@@ -6188,7 +6554,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
6188
6554
  if (!text2.trim() && attachments.length === 0) return;
6189
6555
  const timestamp = parseInt(msg.create_time, 10) || Date.now();
6190
6556
  const address = {
6191
- channelType: "feishu",
6557
+ channelType: this.channelType,
6558
+ channelProvider: this.provider,
6559
+ channelAlias: this.alias,
6192
6560
  chatId,
6193
6561
  userId
6194
6562
  };
@@ -6220,7 +6588,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
6220
6588
  try {
6221
6589
  const summary = attachments.length > 0 ? `[${attachments.length} attachment(s)] ${text2.slice(0, 150)}` : text2.slice(0, 200);
6222
6590
  getBridgeContext().store.insertAuditLog({
6223
- channelType: "feishu",
6591
+ channelType: this.channelType,
6592
+ channelProvider: this.provider,
6593
+ channelAlias: this.alias,
6224
6594
  chatId,
6225
6595
  direction: "inbound",
6226
6596
  messageId: msg.message_id,
@@ -6435,7 +6805,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
6435
6805
  }
6436
6806
  }
6437
6807
  };
6438
- registerAdapterFactory("feishu", () => new FeishuAdapter());
6808
+ registerAdapterFactory("feishu", (instance) => new FeishuAdapter(instance));
6439
6809
 
6440
6810
  // src/lib/bridge/adapters/discord-adapter.ts
6441
6811
  import crypto3 from "crypto";
@@ -6453,6 +6823,7 @@ async function loadDiscordJs() {
6453
6823
  }
6454
6824
  var DiscordAdapter = class extends BaseChannelAdapter {
6455
6825
  channelType = "discord";
6826
+ provider = "discord";
6456
6827
  running = false;
6457
6828
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
6458
6829
  client = null;
@@ -7039,6 +7410,7 @@ async function sendPrivateMessage(accessToken, params) {
7039
7410
  // src/lib/bridge/adapters/qq-adapter.ts
7040
7411
  var QQAdapter = class extends BaseChannelAdapter {
7041
7412
  channelType = "qq";
7413
+ provider = "qq";
7042
7414
  _running = false;
7043
7415
  queue = [];
7044
7416
  waiters = [];
@@ -7429,218 +7801,6 @@ registerAdapterFactory("qq", () => new QQAdapter());
7429
7801
  // src/weixin-store.ts
7430
7802
  import fs3 from "node:fs";
7431
7803
  import path3 from "node:path";
7432
-
7433
- // src/config.ts
7434
- import fs2 from "node:fs";
7435
- import os from "node:os";
7436
- import path2 from "node:path";
7437
- var LEGACY_CTI_HOME = path2.join(os.homedir(), ".claude-to-im");
7438
- var DEFAULT_CTI_HOME = path2.join(os.homedir(), ".codex-to-im");
7439
- var DEFAULT_WORKSPACE_ROOT = path2.join(os.homedir(), "cx2im");
7440
- function resolveDefaultCtiHome() {
7441
- if (fs2.existsSync(DEFAULT_CTI_HOME)) return DEFAULT_CTI_HOME;
7442
- if (fs2.existsSync(LEGACY_CTI_HOME)) return LEGACY_CTI_HOME;
7443
- return DEFAULT_CTI_HOME;
7444
- }
7445
- var CTI_HOME = process.env.CTI_HOME || resolveDefaultCtiHome();
7446
- var CONFIG_PATH = path2.join(CTI_HOME, "config.env");
7447
- function expandHomePath(value) {
7448
- if (!value) return value;
7449
- if (value === "~") return os.homedir();
7450
- if (value.startsWith("~/") || value.startsWith("~\\")) {
7451
- return path2.join(os.homedir(), value.slice(2));
7452
- }
7453
- return value;
7454
- }
7455
- function parseEnvFile(content) {
7456
- const entries = /* @__PURE__ */ new Map();
7457
- for (const line of content.split("\n")) {
7458
- const trimmed = line.trim();
7459
- if (!trimmed || trimmed.startsWith("#")) continue;
7460
- const eqIdx = trimmed.indexOf("=");
7461
- if (eqIdx === -1) continue;
7462
- const key = trimmed.slice(0, eqIdx).trim();
7463
- let value = trimmed.slice(eqIdx + 1).trim();
7464
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
7465
- value = value.slice(1, -1);
7466
- }
7467
- entries.set(key, value);
7468
- }
7469
- return entries;
7470
- }
7471
- function loadRawConfigEnv() {
7472
- try {
7473
- return parseEnvFile(fs2.readFileSync(CONFIG_PATH, "utf-8"));
7474
- } catch {
7475
- return /* @__PURE__ */ new Map();
7476
- }
7477
- }
7478
- function splitCsv(value) {
7479
- if (!value) return void 0;
7480
- return value.split(",").map((s) => s.trim()).filter(Boolean);
7481
- }
7482
- function parsePositiveInt(value) {
7483
- if (!value) return void 0;
7484
- const parsed = Number(value);
7485
- if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
7486
- return Math.floor(parsed);
7487
- }
7488
- function parseSandboxMode(value) {
7489
- if (value === "read-only" || value === "workspace-write" || value === "danger-full-access") {
7490
- return value;
7491
- }
7492
- return void 0;
7493
- }
7494
- function parseReasoningEffort(value) {
7495
- if (value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh") {
7496
- return value;
7497
- }
7498
- return void 0;
7499
- }
7500
- function loadConfig() {
7501
- const env = loadRawConfigEnv();
7502
- const rawRuntime = env.get("CTI_RUNTIME") || "codex";
7503
- const runtime = ["claude", "codex", "auto"].includes(rawRuntime) ? rawRuntime : "codex";
7504
- return {
7505
- runtime,
7506
- enabledChannels: splitCsv(env.get("CTI_ENABLED_CHANNELS")) ?? ["feishu"],
7507
- defaultWorkspaceRoot: expandHomePath(env.get("CTI_DEFAULT_WORKSPACE_ROOT")) || void 0,
7508
- defaultModel: env.get("CTI_DEFAULT_MODEL") || void 0,
7509
- defaultMode: env.get("CTI_DEFAULT_MODE") || "code",
7510
- historyMessageLimit: parsePositiveInt(env.get("CTI_HISTORY_MESSAGE_LIMIT")) ?? 8,
7511
- codexSkipGitRepoCheck: env.has("CTI_CODEX_SKIP_GIT_REPO_CHECK") ? env.get("CTI_CODEX_SKIP_GIT_REPO_CHECK") === "true" : true,
7512
- codexSandboxMode: parseSandboxMode(env.get("CTI_CODEX_SANDBOX_MODE")) ?? "workspace-write",
7513
- codexReasoningEffort: parseReasoningEffort(env.get("CTI_CODEX_REASONING_EFFORT")) ?? "medium",
7514
- uiAllowLan: env.get("CTI_UI_ALLOW_LAN") === "true",
7515
- uiAccessToken: env.get("CTI_UI_ACCESS_TOKEN") || void 0,
7516
- tgBotToken: env.get("CTI_TG_BOT_TOKEN") || void 0,
7517
- tgChatId: env.get("CTI_TG_CHAT_ID") || void 0,
7518
- tgAllowedUsers: splitCsv(env.get("CTI_TG_ALLOWED_USERS")),
7519
- feishuAppId: env.get("CTI_FEISHU_APP_ID") || void 0,
7520
- feishuAppSecret: env.get("CTI_FEISHU_APP_SECRET") || void 0,
7521
- feishuDomain: env.get("CTI_FEISHU_DOMAIN") || void 0,
7522
- feishuAllowedUsers: splitCsv(env.get("CTI_FEISHU_ALLOWED_USERS")),
7523
- feishuStreamingEnabled: env.has("CTI_FEISHU_STREAMING_ENABLED") ? env.get("CTI_FEISHU_STREAMING_ENABLED") === "true" : true,
7524
- feishuCommandMarkdownEnabled: env.has("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") === "true" : true,
7525
- discordBotToken: env.get("CTI_DISCORD_BOT_TOKEN") || void 0,
7526
- discordAllowedUsers: splitCsv(env.get("CTI_DISCORD_ALLOWED_USERS")),
7527
- discordAllowedChannels: splitCsv(
7528
- env.get("CTI_DISCORD_ALLOWED_CHANNELS")
7529
- ),
7530
- discordAllowedGuilds: splitCsv(env.get("CTI_DISCORD_ALLOWED_GUILDS")),
7531
- qqAppId: env.get("CTI_QQ_APP_ID") || void 0,
7532
- qqAppSecret: env.get("CTI_QQ_APP_SECRET") || void 0,
7533
- qqAllowedUsers: splitCsv(env.get("CTI_QQ_ALLOWED_USERS")),
7534
- qqImageEnabled: env.has("CTI_QQ_IMAGE_ENABLED") ? env.get("CTI_QQ_IMAGE_ENABLED") === "true" : void 0,
7535
- qqMaxImageSize: env.get("CTI_QQ_MAX_IMAGE_SIZE") ? Number(env.get("CTI_QQ_MAX_IMAGE_SIZE")) : void 0,
7536
- weixinBaseUrl: env.get("CTI_WEIXIN_BASE_URL") || void 0,
7537
- weixinCdnBaseUrl: env.get("CTI_WEIXIN_CDN_BASE_URL") || void 0,
7538
- weixinMediaEnabled: env.has("CTI_WEIXIN_MEDIA_ENABLED") ? env.get("CTI_WEIXIN_MEDIA_ENABLED") === "true" : void 0,
7539
- weixinCommandMarkdownEnabled: env.has("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") === "true" : false,
7540
- autoApprove: env.get("CTI_AUTO_APPROVE") === "true"
7541
- };
7542
- }
7543
- function configToSettings(config2) {
7544
- const m = /* @__PURE__ */ new Map();
7545
- m.set("remote_bridge_enabled", "true");
7546
- m.set(
7547
- "bridge_telegram_enabled",
7548
- config2.enabledChannels.includes("telegram") ? "true" : "false"
7549
- );
7550
- if (config2.tgBotToken) m.set("telegram_bot_token", config2.tgBotToken);
7551
- if (config2.tgAllowedUsers)
7552
- m.set("telegram_bridge_allowed_users", config2.tgAllowedUsers.join(","));
7553
- if (config2.tgChatId) m.set("telegram_chat_id", config2.tgChatId);
7554
- m.set(
7555
- "bridge_discord_enabled",
7556
- config2.enabledChannels.includes("discord") ? "true" : "false"
7557
- );
7558
- if (config2.discordBotToken)
7559
- m.set("bridge_discord_bot_token", config2.discordBotToken);
7560
- if (config2.discordAllowedUsers)
7561
- m.set("bridge_discord_allowed_users", config2.discordAllowedUsers.join(","));
7562
- if (config2.discordAllowedChannels)
7563
- m.set(
7564
- "bridge_discord_allowed_channels",
7565
- config2.discordAllowedChannels.join(",")
7566
- );
7567
- if (config2.discordAllowedGuilds)
7568
- m.set(
7569
- "bridge_discord_allowed_guilds",
7570
- config2.discordAllowedGuilds.join(",")
7571
- );
7572
- m.set(
7573
- "bridge_feishu_enabled",
7574
- config2.enabledChannels.includes("feishu") ? "true" : "false"
7575
- );
7576
- if (config2.feishuAppId) m.set("bridge_feishu_app_id", config2.feishuAppId);
7577
- if (config2.feishuAppSecret)
7578
- m.set("bridge_feishu_app_secret", config2.feishuAppSecret);
7579
- if (config2.feishuDomain) m.set("bridge_feishu_domain", config2.feishuDomain);
7580
- if (config2.feishuAllowedUsers)
7581
- m.set("bridge_feishu_allowed_users", config2.feishuAllowedUsers.join(","));
7582
- m.set(
7583
- "bridge_feishu_streaming_enabled",
7584
- config2.feishuStreamingEnabled === false ? "false" : "true"
7585
- );
7586
- m.set(
7587
- "bridge_feishu_command_markdown_enabled",
7588
- config2.feishuCommandMarkdownEnabled === false ? "false" : "true"
7589
- );
7590
- m.set(
7591
- "bridge_qq_enabled",
7592
- config2.enabledChannels.includes("qq") ? "true" : "false"
7593
- );
7594
- if (config2.qqAppId) m.set("bridge_qq_app_id", config2.qqAppId);
7595
- if (config2.qqAppSecret) m.set("bridge_qq_app_secret", config2.qqAppSecret);
7596
- if (config2.qqAllowedUsers)
7597
- m.set("bridge_qq_allowed_users", config2.qqAllowedUsers.join(","));
7598
- if (config2.qqImageEnabled !== void 0)
7599
- m.set("bridge_qq_image_enabled", String(config2.qqImageEnabled));
7600
- if (config2.qqMaxImageSize !== void 0)
7601
- m.set("bridge_qq_max_image_size", String(config2.qqMaxImageSize));
7602
- m.set(
7603
- "bridge_weixin_enabled",
7604
- config2.enabledChannels.includes("weixin") ? "true" : "false"
7605
- );
7606
- if (config2.weixinMediaEnabled !== void 0)
7607
- m.set("bridge_weixin_media_enabled", String(config2.weixinMediaEnabled));
7608
- m.set(
7609
- "bridge_weixin_command_markdown_enabled",
7610
- config2.weixinCommandMarkdownEnabled === true ? "true" : "false"
7611
- );
7612
- if (config2.weixinBaseUrl)
7613
- m.set("bridge_weixin_base_url", config2.weixinBaseUrl);
7614
- if (config2.weixinCdnBaseUrl)
7615
- m.set("bridge_weixin_cdn_base_url", config2.weixinCdnBaseUrl);
7616
- if (config2.defaultWorkspaceRoot) {
7617
- m.set("bridge_default_workspace_root", config2.defaultWorkspaceRoot);
7618
- }
7619
- if (config2.defaultModel) {
7620
- m.set("bridge_default_model", config2.defaultModel);
7621
- m.set("default_model", config2.defaultModel);
7622
- }
7623
- m.set("bridge_default_mode", config2.defaultMode);
7624
- m.set(
7625
- "bridge_history_message_limit",
7626
- String(config2.historyMessageLimit && config2.historyMessageLimit > 0 ? config2.historyMessageLimit : 8)
7627
- );
7628
- m.set(
7629
- "bridge_codex_skip_git_repo_check",
7630
- config2.codexSkipGitRepoCheck === true ? "true" : "false"
7631
- );
7632
- m.set(
7633
- "bridge_codex_sandbox_mode",
7634
- config2.codexSandboxMode || "workspace-write"
7635
- );
7636
- m.set(
7637
- "bridge_codex_reasoning_effort",
7638
- config2.codexReasoningEffort || "medium"
7639
- );
7640
- return m;
7641
- }
7642
-
7643
- // src/weixin-store.ts
7644
7804
  var DATA_DIR = path3.join(CTI_HOME, "data");
7645
7805
  var ACCOUNTS_PATH = path3.join(DATA_DIR, "weixin-accounts.json");
7646
7806
  var CONTEXT_TOKENS_PATH = path3.join(DATA_DIR, "weixin-context-tokens.json");
@@ -13975,7 +14135,10 @@ var DEDUP_MAX3 = 500;
13975
14135
  var BACKOFF_BASE_MS = 2e3;
13976
14136
  var BACKOFF_MAX_MS = 3e4;
13977
14137
  var WeixinAdapter = class extends BaseChannelAdapter {
13978
- channelType = "weixin";
14138
+ channelType;
14139
+ provider = "weixin";
14140
+ alias;
14141
+ channelConfig;
13979
14142
  _running = false;
13980
14143
  idleLogged = false;
13981
14144
  reconcileTimer = null;
@@ -13988,6 +14151,19 @@ var WeixinAdapter = class extends BaseChannelAdapter {
13988
14151
  typingTickets = /* @__PURE__ */ new Map();
13989
14152
  pendingCursors = /* @__PURE__ */ new Map();
13990
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
+ }
13991
14167
  async start() {
13992
14168
  if (this._running) return;
13993
14169
  this._running = true;
@@ -14063,7 +14239,9 @@ var WeixinAdapter = class extends BaseChannelAdapter {
14063
14239
  }
14064
14240
  }
14065
14241
  validateConfig() {
14066
- const linkedAccounts = listWeixinAccounts().filter((account) => account.enabled && account.token);
14242
+ const linkedAccounts = this.filterConfiguredAccounts(
14243
+ listWeixinAccounts().filter((account) => account.enabled && account.token)
14244
+ );
14067
14245
  if (linkedAccounts.length === 0) {
14068
14246
  return "No linked WeChat account. Run the WeChat QR login helper first.";
14069
14247
  }
@@ -14095,7 +14273,9 @@ var WeixinAdapter = class extends BaseChannelAdapter {
14095
14273
  this.queue.push(message);
14096
14274
  }
14097
14275
  async reconcileAccounts() {
14098
- const linkedAccounts = listWeixinAccounts().filter((account) => account.enabled && account.token);
14276
+ const linkedAccounts = this.filterConfiguredAccounts(
14277
+ listWeixinAccounts().filter((account) => account.enabled && account.token)
14278
+ );
14099
14279
  if (linkedAccounts.length === 0 && this.pollAborts.size === 0) {
14100
14280
  if (!this.idleLogged) {
14101
14281
  console.log("[weixin-adapter] No linked WeChat account is enabled, adapter started but idle");
@@ -14233,7 +14413,7 @@ var WeixinAdapter = class extends BaseChannelAdapter {
14233
14413
  const attachments = [];
14234
14414
  let failedCount = 0;
14235
14415
  let missingVoiceTranscriptCount = 0;
14236
- const mediaEnabled = getBridgeContext().store.getSetting("bridge_weixin_media_enabled") === "true";
14416
+ const mediaEnabled = this.mediaEnabled;
14237
14417
  const account = mediaEnabled ? getWeixinAccount(accountId) : void 0;
14238
14418
  const creds = account ? this.accountToCreds(account) : void 0;
14239
14419
  for (const item of message.item_list || []) {
@@ -14285,7 +14465,9 @@ ${failureNote}` : attachments.length > 0 ? failureNote : text2;
14285
14465
  const inbound = {
14286
14466
  messageId: message.message_id || `weixin_${accountId}_${message.seq || Date.now()}`,
14287
14467
  address: {
14288
- channelType: "weixin",
14468
+ channelType: this.channelType,
14469
+ channelProvider: this.provider,
14470
+ channelAlias: this.alias,
14289
14471
  chatId,
14290
14472
  userId: message.from_user_id,
14291
14473
  displayName: message.from_user_id.slice(0, 12)
@@ -14316,7 +14498,9 @@ ${failureNote}` : attachments.length > 0 ? failureNote : text2;
14316
14498
  this.enqueue(inbound);
14317
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);
14318
14500
  getBridgeContext().store.insertAuditLog({
14319
- channelType: "weixin",
14501
+ channelType: this.channelType,
14502
+ channelProvider: this.provider,
14503
+ channelAlias: this.alias,
14320
14504
  chatId,
14321
14505
  direction: "inbound",
14322
14506
  messageId: inbound.messageId,
@@ -14379,6 +14563,10 @@ ${failureNote}` : attachments.length > 0 ? failureNote : text2;
14379
14563
  getBridgeContext().store.setChannelOffset(batch.offsetKey, batch.cursor);
14380
14564
  this.pendingCursors.delete(updateId);
14381
14565
  }
14566
+ filterConfiguredAccounts(accounts) {
14567
+ if (!this.configuredAccountId) return accounts;
14568
+ return accounts.filter((account) => account.accountId === this.configuredAccountId);
14569
+ }
14382
14570
  };
14383
14571
  function stripFormatting(text2, parseMode) {
14384
14572
  if (parseMode === "HTML") {
@@ -14389,7 +14577,7 @@ function stripFormatting(text2, parseMode) {
14389
14577
  }
14390
14578
  return text2;
14391
14579
  }
14392
- registerAdapterFactory("weixin", () => new WeixinAdapter());
14580
+ registerAdapterFactory("weixin", (instance) => new WeixinAdapter(instance));
14393
14581
 
14394
14582
  // src/session-bindings.ts
14395
14583
  import path5 from "node:path";
@@ -15073,8 +15261,24 @@ function readDesktopSessionEventStream(threadId) {
15073
15261
  }
15074
15262
 
15075
15263
  // src/session-bindings.ts
15076
- function formatChannelLabel(channelType) {
15077
- 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;
15078
15282
  }
15079
15283
  function formatBindingChatTarget(binding) {
15080
15284
  return binding.chatDisplayName?.trim() || binding.chatId;
@@ -15095,7 +15299,7 @@ function assertBindingTargetAvailable(store, current, opts) {
15095
15299
  );
15096
15300
  if (!conflict) return;
15097
15301
  throw new Error(
15098
- `\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`
15099
15303
  );
15100
15304
  }
15101
15305
  function getSessionMode(store, session) {
@@ -15109,8 +15313,11 @@ function bindStoreToSession(store, channelType, chatId, sessionId) {
15109
15313
  { channelType, chatId },
15110
15314
  { sessionId: session.id, sdkSessionId: session.sdk_session_id || void 0 }
15111
15315
  );
15316
+ const meta = resolveChannelMeta(channelType);
15112
15317
  return store.upsertChannelBinding({
15113
15318
  channelType,
15319
+ channelProvider: meta.provider,
15320
+ channelAlias: meta.alias,
15114
15321
  chatId,
15115
15322
  codepilotSessionId: session.id,
15116
15323
  sdkSessionId: session.sdk_session_id || "",
@@ -15125,10 +15332,13 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
15125
15332
  { channelType, chatId },
15126
15333
  { sdkSessionId }
15127
15334
  );
15335
+ const meta = resolveChannelMeta(channelType);
15128
15336
  const existing = store.findSessionBySdkSessionId(sdkSessionId);
15129
15337
  if (existing) {
15130
15338
  return store.upsertChannelBinding({
15131
15339
  channelType,
15340
+ channelProvider: meta.provider,
15341
+ channelAlias: meta.alias,
15132
15342
  chatId,
15133
15343
  codepilotSessionId: existing.id,
15134
15344
  sdkSessionId,
@@ -15150,6 +15360,8 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
15150
15360
  store.updateSdkSessionId(session.id, sdkSessionId);
15151
15361
  return store.upsertChannelBinding({
15152
15362
  channelType,
15363
+ channelProvider: meta.provider,
15364
+ channelAlias: meta.alias,
15153
15365
  chatId,
15154
15366
  codepilotSessionId: session.id,
15155
15367
  sdkSessionId,
@@ -15388,8 +15600,8 @@ function stripOutboundArtifactBlocksForStreaming(text2) {
15388
15600
  }
15389
15601
  return stripped.replace(/\n{3,}/g, "\n\n").trimEnd();
15390
15602
  }
15391
- function supportsOutboundArtifacts(channelType) {
15392
- return channelType === "feishu";
15603
+ function supportsOutboundArtifacts(provider) {
15604
+ return provider === "feishu";
15393
15605
  }
15394
15606
 
15395
15607
  // src/lib/bridge/conversation-engine.ts
@@ -15931,9 +16143,9 @@ async function deliver(adapter, message, opts) {
15931
16143
  } catch {
15932
16144
  }
15933
16145
  }
15934
- const limit = PLATFORM_LIMITS[adapter.channelType] || 4096;
16146
+ const limit = PLATFORM_LIMITS[adapter.provider] || PLATFORM_LIMITS[adapter.channelType] || 4096;
15935
16147
  let chunks = chunkText2(message.text, limit);
15936
- if (adapter.channelType === "qq" && chunks.length > 3) {
16148
+ if (adapter.provider === "qq" && chunks.length > 3) {
15937
16149
  const first2 = chunks.slice(0, 2);
15938
16150
  let merged = chunks.slice(2).join("\n");
15939
16151
  if (merged.length > limit) {
@@ -16114,8 +16326,8 @@ async function forwardPermissionRequest(adapter, address, permissionRequestId, t
16114
16326
  const inputStr = JSON.stringify(toolInput, null, 2);
16115
16327
  const truncatedInput = inputStr.length > 300 ? inputStr.slice(0, 300) + "..." : inputStr;
16116
16328
  let result;
16117
- if (adapter.channelType === "qq" || adapter.channelType === "weixin") {
16118
- const channelLabel = adapter.channelType === "weixin" ? "WeChat" : "QQ";
16329
+ if (adapter.provider === "qq" || adapter.provider === "weixin") {
16330
+ const channelLabel = adapter.provider === "weixin" ? "WeChat" : "QQ";
16119
16331
  const plainText = [
16120
16332
  `Permission Required`,
16121
16333
  ``,
@@ -16798,7 +17010,8 @@ var MIRROR_WATCH_DEBOUNCE_MS = 350;
16798
17010
  var MIRROR_EVENT_BATCH_LIMIT = 8;
16799
17011
  var MIRROR_SUPPRESSION_WINDOW_MS = 4e3;
16800
17012
  var MIRROR_PROMPT_MATCH_GRACE_MS = 12e4;
16801
- var MIRROR_IDLE_TIMEOUT_MS = 12e5;
17013
+ var INTERACTIVE_IDLE_REMINDER_MS = 6e5;
17014
+ var MIRROR_IDLE_TIMEOUT_MS = 6e5;
16802
17015
  var AVAILABLE_CODEX_MODELS = listSelectableCodexModels();
16803
17016
  var AVAILABLE_CODEX_MODEL_MAP = new Map(AVAILABLE_CODEX_MODELS.map((model) => [model.slug, model]));
16804
17017
  function generateDraftId() {
@@ -16817,6 +17030,27 @@ function getStreamConfig(channelType = "telegram") {
16817
17030
  const maxChars = parseInt(store.getSetting(`${prefix}max_chars`) || "", 10) || defaults.maxChars;
16818
17031
  return { intervalMs, minDeltaChars, maxChars };
16819
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
+ }
16820
17054
  function parseListIndex(raw) {
16821
17055
  const trimmed = raw.trim();
16822
17056
  if (!/^\d+$/.test(trimmed)) return null;
@@ -16899,7 +17133,7 @@ function getSessionDisplayName(session, fallbackDirectory) {
16899
17133
  if (session?.id) return session.id.slice(0, 8);
16900
17134
  return "\u672A\u547D\u540D\u4F1A\u8BDD";
16901
17135
  }
16902
- function nowIso() {
17136
+ function nowIso2() {
16903
17137
  return (/* @__PURE__ */ new Date()).toISOString();
16904
17138
  }
16905
17139
  function buildInteractiveStreamKey(sessionId, messageId) {
@@ -17016,12 +17250,12 @@ function buildDesktopThreadsCommandResponse(desktopSessions, markdown, showAll,
17016
17250
  );
17017
17251
  }
17018
17252
  function isFeedbackMarkdownEnabled(channelType) {
17019
- const { store } = getBridgeContext();
17020
- if (channelType === "feishu") {
17021
- 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;
17022
17256
  }
17023
- if (channelType === "weixin") {
17024
- return store.getSetting("bridge_weixin_command_markdown_enabled") === "true";
17257
+ if (instance?.provider === "weixin") {
17258
+ return instance.config.feedbackMarkdownEnabled === true;
17025
17259
  }
17026
17260
  return false;
17027
17261
  }
@@ -17042,7 +17276,8 @@ function toUserVisibleBindingError(error, fallback) {
17042
17276
  return fallback;
17043
17277
  }
17044
17278
  function formatBindingChatLabel(binding) {
17045
- 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);
17046
17281
  const chatLabel = binding.chatDisplayName?.trim() || binding.chatId;
17047
17282
  return `${channelLabel} \u804A\u5929 ${chatLabel}`;
17048
17283
  }
@@ -17381,7 +17616,7 @@ async function deliverTextResponse(adapter, address, responseText, sessionId, re
17381
17616
  }
17382
17617
  return { ok: true };
17383
17618
  }
17384
- if (parseMode === "Markdown" && adapter.channelType === "feishu") {
17619
+ if (parseMode === "Markdown" && adapter.provider === "feishu") {
17385
17620
  return deliver(adapter, {
17386
17621
  address,
17387
17622
  text: responseText,
@@ -17408,7 +17643,7 @@ async function deliverResponse(adapter, address, responseText, sessionId, replyT
17408
17643
  if (!captionResult.ok) return captionResult;
17409
17644
  lastResult = captionResult;
17410
17645
  }
17411
- if (!supportsOutboundArtifacts(adapter.channelType)) {
17646
+ if (!supportsOutboundArtifacts(adapter.provider)) {
17412
17647
  lastResult = await deliverTextResponse(
17413
17648
  adapter,
17414
17649
  address,
@@ -17486,6 +17721,53 @@ function getQueuedCount(sessionId) {
17486
17721
  const state = getState();
17487
17722
  return state.queuedCounts.get(sessionId) || 0;
17488
17723
  }
17724
+ function buildInteractiveIdleReminderNotice() {
17725
+ return [
17726
+ "\u63D0\u9192\uFF1A\u8FD9\u8F6E\u4EFB\u52A1\u4ECD\u5728\u8FD0\u884C\uFF0C\u4F46\u5DF2\u7ECF\u8D85\u8FC7 10 \u5206\u949F\u6CA1\u6709\u65B0\u7684\u6267\u884C\u8F93\u51FA\u3002",
17727
+ "\u7CFB\u7EDF\u4E0D\u4F1A\u81EA\u52A8\u7EC8\u6B62\u5B83\uFF1B\u5982\u679C\u4F60\u4ECD\u5728\u5BF9\u5E94\u7EBF\u7A0B\uFF0C\u53EF\u53D1\u9001 `/stop` \u4E3B\u52A8\u505C\u6B62\uFF1B\u5982\u679C\u5DF2\u7ECF\u5207\u5230\u522B\u7684\u7EBF\u7A0B\uFF0C\u9700\u8981\u5148\u5207\u56DE\u5BF9\u5E94\u7EBF\u7A0B\u3002"
17728
+ ].join("\n");
17729
+ }
17730
+ function isCurrentInteractiveTask(sessionId, taskId) {
17731
+ return getState().activeTasks.get(sessionId)?.id === taskId;
17732
+ }
17733
+ function touchInteractiveTask(sessionId, taskId) {
17734
+ const task = getState().activeTasks.get(sessionId);
17735
+ if (task?.id !== taskId) return;
17736
+ task.lastActivityAt = Date.now();
17737
+ task.idleReminderSent = false;
17738
+ }
17739
+ function releaseInteractiveTask(sessionId, taskId) {
17740
+ const state = getState();
17741
+ const current = state.activeTasks.get(sessionId);
17742
+ if (current?.id !== taskId) return;
17743
+ state.activeTasks.delete(sessionId);
17744
+ syncSessionRuntimeState(sessionId);
17745
+ }
17746
+ async function remindIdleInteractiveTask(task) {
17747
+ if (!isCurrentInteractiveTask(task.sessionId, task.id) || task.idleReminderSent) return;
17748
+ task.idleReminderSent = true;
17749
+ try {
17750
+ await deliver(task.adapter, {
17751
+ address: task.address,
17752
+ text: renderFeedbackTextForChannel(
17753
+ task.adapter.channelType,
17754
+ buildInteractiveIdleReminderNotice()
17755
+ ),
17756
+ parseMode: getFeedbackParseMode(task.adapter.channelType),
17757
+ replyToMessageId: task.requestMessageId
17758
+ });
17759
+ } catch {
17760
+ }
17761
+ }
17762
+ async function reconcileIdleInteractiveTasks() {
17763
+ const now2 = Date.now();
17764
+ const tasks = Array.from(getState().activeTasks.values());
17765
+ for (const task of tasks) {
17766
+ if (task.idleReminderSent) continue;
17767
+ if (now2 - task.lastActivityAt < INTERACTIVE_IDLE_REMINDER_MS) continue;
17768
+ await remindIdleInteractiveTask(task);
17769
+ }
17770
+ }
17489
17771
  function syncSessionRuntimeState(sessionId) {
17490
17772
  const { store } = getBridgeContext();
17491
17773
  const session = store.getSession(sessionId);
@@ -17499,7 +17781,7 @@ function syncSessionRuntimeState(sessionId) {
17499
17781
  store.updateSession(sessionId, {
17500
17782
  queued_count: queuedCount,
17501
17783
  runtime_status: runtimeStatus,
17502
- last_runtime_update_at: nowIso()
17784
+ last_runtime_update_at: nowIso2()
17503
17785
  });
17504
17786
  }
17505
17787
  function incrementQueuedCount(sessionId) {
@@ -17861,7 +18143,7 @@ function getMirrorStreamingAdapter(subscription) {
17861
18143
  const state = getState();
17862
18144
  const adapter = state.adapters.get(subscription.channelType);
17863
18145
  if (!adapter || !adapter.isRunning()) return null;
17864
- if (subscription.channelType !== "feishu") return null;
18146
+ if (getChannelProviderKey(subscription.channelType) !== "feishu") return null;
17865
18147
  if (typeof adapter.onStreamText !== "function" || typeof adapter.onStreamEnd !== "function") {
17866
18148
  return null;
17867
18149
  }
@@ -17941,7 +18223,7 @@ async function deliverMirrorTurn(subscription, turn) {
17941
18223
  renderedStreamText || buildMirrorTitle(title, markdown),
17942
18224
  responseParseMode
17943
18225
  );
17944
- if (subscription.channelType === "feishu" && typeof adapter.onStreamEnd === "function") {
18226
+ if (getChannelProviderKey(subscription.channelType) === "feishu" && typeof adapter.onStreamEnd === "function") {
17945
18227
  try {
17946
18228
  const finalized = await adapter.onStreamEnd(
17947
18229
  subscription.chatId,
@@ -17950,7 +18232,7 @@ async function deliverMirrorTurn(subscription, turn) {
17950
18232
  turn.streamKey
17951
18233
  );
17952
18234
  if (finalized) {
17953
- subscription.lastDeliveredAt = turn.timestamp || nowIso();
18235
+ subscription.lastDeliveredAt = turn.timestamp || nowIso2();
17954
18236
  return;
17955
18237
  }
17956
18238
  } catch (error) {
@@ -17972,7 +18254,7 @@ async function deliverMirrorTurn(subscription, turn) {
17972
18254
  if (!response.ok) {
17973
18255
  throw new Error(response.error || "mirror delivery failed");
17974
18256
  }
17975
- subscription.lastDeliveredAt = turn.timestamp || nowIso();
18257
+ subscription.lastDeliveredAt = turn.timestamp || nowIso2();
17976
18258
  }
17977
18259
  async function deliverMirrorTurns(subscription, turns) {
17978
18260
  for (const turn of turns.slice(-MIRROR_EVENT_BATCH_LIMIT)) {
@@ -17980,7 +18262,7 @@ async function deliverMirrorTurns(subscription, turns) {
17980
18262
  }
17981
18263
  }
17982
18264
  function createMirrorTurnState(sessionId, timestamp, turnId) {
17983
- const safeTimestamp = timestamp || nowIso();
18265
+ const safeTimestamp = timestamp || nowIso2();
17984
18266
  return {
17985
18267
  turnId: turnId || null,
17986
18268
  streamKey: buildMirrorStreamKey(sessionId, turnId || null, safeTimestamp),
@@ -18044,7 +18326,7 @@ function finalizeMirrorTurn(subscription, signature, timestamp, status, preferre
18044
18326
  userText,
18045
18327
  text: text2,
18046
18328
  signature,
18047
- timestamp: timestamp || pendingTurn.lastActivityAt || nowIso(),
18329
+ timestamp: timestamp || pendingTurn.lastActivityAt || nowIso2(),
18048
18330
  status,
18049
18331
  ...signature.startsWith("timeout:") ? { timedOut: true } : {}
18050
18332
  };
@@ -18279,7 +18561,7 @@ async function reconcileMirrorSubscription(subscription) {
18279
18561
  resetMirrorReadState(subscription);
18280
18562
  }
18281
18563
  watchMirrorFile(subscription, subscription.filePath);
18282
- subscription.lastReconciledAt = nowIso();
18564
+ subscription.lastReconciledAt = nowIso2();
18283
18565
  if (!subscription.filePath) {
18284
18566
  syncMirrorSessionState(subscription.sessionId);
18285
18567
  return;
@@ -18427,6 +18709,51 @@ function notifyAdapterSetChanged() {
18427
18709
  const { lifecycle } = getBridgeContext();
18428
18710
  lifecycle.onBridgeAdaptersChanged?.(getActiveChannelTypes());
18429
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
+ }
18430
18757
  async function stopAdapterInstance(channelType) {
18431
18758
  const state = getState();
18432
18759
  const adapter = state.adapters.get(channelType);
@@ -18444,44 +18771,50 @@ async function stopAdapterInstance(channelType) {
18444
18771
  }
18445
18772
  async function syncConfiguredAdapters(options) {
18446
18773
  const state = getState();
18447
- const { store } = getBridgeContext();
18448
18774
  let changed = false;
18449
- for (const channelType of getRegisteredTypes()) {
18450
- const enabled = store.getSetting(`bridge_${channelType}_enabled`) === "true";
18451
- const existing = state.adapters.get(channelType);
18452
- if (!enabled) {
18453
- if (existing) {
18454
- await stopAdapterInstance(channelType);
18455
- changed = true;
18456
- }
18457
- continue;
18458
- }
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;
18459
18790
  if (existing) {
18460
- continue;
18791
+ await stopAdapterInstance(instance.id);
18792
+ changed = true;
18461
18793
  }
18462
- const adapter = createAdapter(channelType);
18794
+ const adapter = createAdapter(instance);
18463
18795
  if (!adapter) continue;
18464
18796
  const configError = adapter.validateConfig();
18465
18797
  if (configError) {
18466
- console.warn(`[bridge-manager] ${channelType} adapter not valid:`, configError);
18798
+ console.warn(`[bridge-manager] ${instance.id} adapter not valid:`, configError);
18467
18799
  continue;
18468
18800
  }
18469
18801
  try {
18470
- state.adapters.set(channelType, adapter);
18471
- state.adapterMeta.set(channelType, {
18802
+ state.adapters.set(instance.id, adapter);
18803
+ state.adapterMeta.set(instance.id, {
18472
18804
  lastMessageAt: null,
18473
- lastError: null
18805
+ lastError: null,
18806
+ configFingerprint: desiredFingerprint
18474
18807
  });
18475
18808
  await adapter.start();
18476
- console.log(`[bridge-manager] Started adapter: ${channelType}`);
18809
+ console.log(`[bridge-manager] Started adapter: ${instance.id}`);
18477
18810
  if (options.startLoops && state.running && adapter.isRunning()) {
18478
18811
  runAdapterLoop(adapter);
18479
18812
  }
18480
18813
  changed = true;
18481
18814
  } catch (err) {
18482
- state.adapters.delete(channelType);
18483
- state.adapterMeta.delete(channelType);
18484
- 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);
18485
18818
  }
18486
18819
  }
18487
18820
  if (changed) {
@@ -18517,6 +18850,9 @@ async function start() {
18517
18850
  void syncConfiguredAdapters({ startLoops: true }).catch((err) => {
18518
18851
  console.error("[bridge-manager] Adapter reconcile failed:", err);
18519
18852
  });
18853
+ void reconcileIdleInteractiveTasks().catch((err) => {
18854
+ console.error("[bridge-manager] Interactive idle reminder reconcile failed:", err);
18855
+ });
18520
18856
  }, 5e3);
18521
18857
  state.mirrorPollTimer = setInterval(() => {
18522
18858
  void reconcileMirrorSubscriptions().catch((err) => {
@@ -18550,8 +18886,8 @@ async function stop() {
18550
18886
  }
18551
18887
  state.loopAborts.clear();
18552
18888
  const activeSessionIds = Array.from(state.activeTasks.keys());
18553
- for (const abort of state.activeTasks.values()) {
18554
- abort.abort();
18889
+ for (const task of state.activeTasks.values()) {
18890
+ task.abortController.abort();
18555
18891
  }
18556
18892
  state.activeTasks.clear();
18557
18893
  state.mirrorSuppressUntil.clear();
@@ -18577,6 +18913,8 @@ function getStatus() {
18577
18913
  const meta = state.adapterMeta.get(type);
18578
18914
  return {
18579
18915
  channelType: adapter.channelType,
18916
+ channelProvider: adapter.provider,
18917
+ channelAlias: adapter.alias,
18580
18918
  running: adapter.isRunning(),
18581
18919
  connectedAt: state.startedAt,
18582
18920
  lastMessageAt: meta?.lastMessageAt ?? null,
@@ -18594,7 +18932,7 @@ function runAdapterLoop(adapter) {
18594
18932
  try {
18595
18933
  const msg = await adapter.consumeOne();
18596
18934
  if (!msg) continue;
18597
- 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)) {
18598
18936
  await handleMessage(adapter, msg);
18599
18937
  } else {
18600
18938
  const binding = resolve(msg.address);
@@ -18609,7 +18947,7 @@ function runAdapterLoop(adapter) {
18609
18947
  if (abort.signal.aborted) break;
18610
18948
  const errMsg = err instanceof Error ? err.message : String(err);
18611
18949
  console.error(`[bridge-manager] Error in ${adapter.channelType} loop:`, err);
18612
- const meta = state.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null };
18950
+ const meta = state.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null, configFingerprint: "" };
18613
18951
  meta.lastError = errMsg;
18614
18952
  state.adapterMeta.set(adapter.channelType, meta);
18615
18953
  await new Promise((r) => setTimeout(r, 1e3));
@@ -18619,7 +18957,7 @@ function runAdapterLoop(adapter) {
18619
18957
  if (!abort.signal.aborted) {
18620
18958
  const errMsg = err instanceof Error ? err.message : String(err);
18621
18959
  console.error(`[bridge-manager] ${adapter.channelType} loop crashed:`, err);
18622
- const meta = state.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null };
18960
+ const meta = state.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null, configFingerprint: "" };
18623
18961
  meta.lastError = errMsg;
18624
18962
  state.adapterMeta.set(adapter.channelType, meta);
18625
18963
  }
@@ -18628,7 +18966,7 @@ function runAdapterLoop(adapter) {
18628
18966
  async function handleMessage(adapter, msg) {
18629
18967
  const { store } = getBridgeContext();
18630
18968
  const adapterState = getState();
18631
- const meta = adapterState.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null };
18969
+ const meta = adapterState.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null, configFingerprint: "" };
18632
18970
  meta.lastMessageAt = (/* @__PURE__ */ new Date()).toISOString();
18633
18971
  adapterState.adapterMeta.set(adapter.channelType, meta);
18634
18972
  const ack = () => {
@@ -18672,7 +19010,7 @@ async function handleMessage(adapter, msg) {
18672
19010
  ack();
18673
19011
  return;
18674
19012
  }
18675
- if (adapter.channelType === "feishu" || adapter.channelType === "qq" || adapter.channelType === "weixin") {
19013
+ if (adapter.provider === "feishu" || adapter.provider === "qq" || adapter.provider === "weixin") {
18676
19014
  const normalized = rawText.normalize("NFKC").replace(/[\u200B-\u200D\uFEFF]/g, "").trim();
18677
19015
  if (/^[123]$/.test(normalized)) {
18678
19016
  const currentBinding = store.getChannelBinding(msg.address.channelType, msg.address.chatId);
@@ -18759,10 +19097,25 @@ async function handleMessage(adapter, msg) {
18759
19097
  const streamKey = buildInteractiveStreamKey(binding.codepilotSessionId, msg.messageId);
18760
19098
  adapter.onMessageStart?.(msg.address.chatId, streamKey);
18761
19099
  const taskAbort = new AbortController();
19100
+ const taskId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
18762
19101
  const state = getState();
18763
19102
  resetMirrorSessionForInteractiveRun(binding.codepilotSessionId);
18764
- state.activeTasks.set(binding.codepilotSessionId, taskAbort);
18765
- let mirrorSuppressionId = null;
19103
+ const taskState = {
19104
+ id: taskId,
19105
+ abortController: taskAbort,
19106
+ adapter,
19107
+ address: msg.address,
19108
+ requestMessageId: msg.messageId,
19109
+ streamKey,
19110
+ sessionId: binding.codepilotSessionId,
19111
+ hasStreamingCards: false,
19112
+ lastActivityAt: Date.now(),
19113
+ idleReminderSent: false,
19114
+ streamFinalized: false,
19115
+ uiEnded: false,
19116
+ mirrorSuppressionId: null
19117
+ };
19118
+ state.activeTasks.set(binding.codepilotSessionId, taskState);
18766
19119
  syncSessionRuntimeState(binding.codepilotSessionId);
18767
19120
  let previewState = null;
18768
19121
  const caps = adapter.getPreviewCapabilities?.(msg.address.chatId) ?? null;
@@ -18777,7 +19130,7 @@ async function handleMessage(adapter, msg) {
18777
19130
  pendingText: ""
18778
19131
  };
18779
19132
  }
18780
- const streamCfg = previewState ? getStreamConfig(adapter.channelType) : null;
19133
+ const streamCfg = previewState ? getStreamConfig(adapter.provider) : null;
18781
19134
  const previewOnPartialText = previewState && streamCfg ? (fullText) => {
18782
19135
  const ps = previewState;
18783
19136
  const cfg = streamCfg;
@@ -18811,8 +19164,10 @@ async function handleMessage(adapter, msg) {
18811
19164
  flushPreview(adapter, ps, cfg);
18812
19165
  } : void 0;
18813
19166
  const hasStreamingCards = typeof adapter.onStreamText === "function";
19167
+ taskState.hasStreamingCards = hasStreamingCards;
18814
19168
  const toolCallTracker = /* @__PURE__ */ new Map();
18815
19169
  const onStreamCardText = hasStreamingCards ? (fullText) => {
19170
+ if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
18816
19171
  const rendered = renderFeedbackTextForChannel(
18817
19172
  adapter.channelType,
18818
19173
  stripOutboundArtifactBlocksForStreaming(fullText)
@@ -18823,6 +19178,8 @@ async function handleMessage(adapter, msg) {
18823
19178
  }
18824
19179
  } : void 0;
18825
19180
  const onToolEvent = hasStreamingCards ? (toolId, toolName, status) => {
19181
+ if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
19182
+ touchInteractiveTask(binding.codepilotSessionId, taskId);
18826
19183
  if (toolName) {
18827
19184
  toolCallTracker.set(toolId, { id: toolId, name: toolName, status });
18828
19185
  } else {
@@ -18835,6 +19192,8 @@ async function handleMessage(adapter, msg) {
18835
19192
  }
18836
19193
  } : void 0;
18837
19194
  const onPartialText = previewOnPartialText || onStreamCardText ? (fullText) => {
19195
+ if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
19196
+ touchInteractiveTask(binding.codepilotSessionId, taskId);
18838
19197
  if (previewOnPartialText) previewOnPartialText(fullText);
18839
19198
  if (onStreamCardText) onStreamCardText(fullText);
18840
19199
  } : void 0;
@@ -18852,10 +19211,13 @@ async function handleMessage(adapter, msg) {
18852
19211
  msg.messageId
18853
19212
  );
18854
19213
  }, taskAbort.signal, hasAttachments ? msg.attachments : void 0, onPartialText, onToolEvent, (preparedPrompt) => {
18855
- if (!mirrorSuppressionId) {
18856
- mirrorSuppressionId = beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
19214
+ if (!taskState.mirrorSuppressionId) {
19215
+ taskState.mirrorSuppressionId = beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
18857
19216
  }
18858
19217
  });
19218
+ if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) {
19219
+ return;
19220
+ }
18859
19221
  let cardFinalized = false;
18860
19222
  if (hasStreamingCards && adapter.onStreamEnd) {
18861
19223
  try {
@@ -18866,6 +19228,7 @@ async function handleMessage(adapter, msg) {
18866
19228
  renderFeedbackTextForChannel(adapter.channelType, result.responseText),
18867
19229
  streamKey
18868
19230
  );
19231
+ taskState.streamFinalized = cardFinalized;
18869
19232
  } catch (err) {
18870
19233
  console.warn("[bridge-manager] Card finalize failed:", err instanceof Error ? err.message : err);
18871
19234
  }
@@ -18890,14 +19253,9 @@ async function handleMessage(adapter, msg) {
18890
19253
  []
18891
19254
  );
18892
19255
  }
18893
- if (binding.id) {
18894
- try {
18895
- const update = computeSdkSessionUpdate(result.sdkSessionId, result.hasError);
18896
- if (update !== null) {
18897
- store.updateChannelBinding(binding.id, { sdkSessionId: update });
18898
- }
18899
- } catch {
18900
- }
19256
+ try {
19257
+ persistSdkSessionUpdate(binding.codepilotSessionId, result.sdkSessionId, result.hasError);
19258
+ } catch {
18901
19259
  }
18902
19260
  } finally {
18903
19261
  if (previewState) {
@@ -18907,18 +19265,22 @@ async function handleMessage(adapter, msg) {
18907
19265
  }
18908
19266
  adapter.endPreview?.(msg.address.chatId, previewState.draftId);
18909
19267
  }
18910
- if (hasStreamingCards && adapter.onStreamEnd && taskAbort.signal.aborted) {
19268
+ if (hasStreamingCards && adapter.onStreamEnd && taskAbort.signal.aborted && !taskState.streamFinalized) {
18911
19269
  try {
18912
19270
  await adapter.onStreamEnd(msg.address.chatId, "interrupted", "", streamKey);
19271
+ taskState.streamFinalized = true;
18913
19272
  } catch {
18914
19273
  }
18915
19274
  }
18916
- if (mirrorSuppressionId) {
18917
- settleMirrorSuppression(binding.codepilotSessionId, mirrorSuppressionId);
19275
+ if (taskState.mirrorSuppressionId) {
19276
+ settleMirrorSuppression(binding.codepilotSessionId, taskState.mirrorSuppressionId);
19277
+ taskState.mirrorSuppressionId = null;
19278
+ }
19279
+ releaseInteractiveTask(binding.codepilotSessionId, taskId);
19280
+ if (!taskState.uiEnded) {
19281
+ adapter.onMessageEnd?.(msg.address.chatId, streamKey);
19282
+ taskState.uiEnded = true;
18918
19283
  }
18919
- state.activeTasks.delete(binding.codepilotSessionId);
18920
- syncSessionRuntimeState(binding.codepilotSessionId);
18921
- adapter.onMessageEnd?.(msg.address.chatId, streamKey);
18922
19284
  ack();
18923
19285
  }
18924
19286
  }
@@ -18971,13 +19333,6 @@ async function handleCommand(adapter, msg, text2) {
18971
19333
  response = resolved.message;
18972
19334
  break;
18973
19335
  }
18974
- if (currentBinding) {
18975
- const st = getState();
18976
- const oldTask = st.activeTasks.get(currentBinding.codepilotSessionId);
18977
- if (oldTask) {
18978
- oldTask.abort();
18979
- }
18980
- }
18981
19336
  const workDir = resolved.workDir;
18982
19337
  ensureWorkingDirectoryExists(workDir);
18983
19338
  const binding = createBinding(msg.address, workDir);
@@ -18991,6 +19346,7 @@ async function handleCommand(adapter, msg, text2) {
18991
19346
  ],
18992
19347
  [
18993
19348
  args.trim() ? "\u63A5\u4E0B\u6765\u76F4\u63A5\u53D1\u9001\u6587\u672C\u5373\u53EF\u7EE7\u7EED\u3002" : "\u5DF2\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u4E0B\u65B0\u5EFA\u4E00\u4E2A\u7EBF\u7A0B\u3002\u63A5\u4E0B\u6765\u76F4\u63A5\u53D1\u9001\u6587\u672C\u5373\u53EF\u7EE7\u7EED\u3002",
19349
+ "\u5982\u679C\u5F53\u524D\u804A\u5929\u91CC\u5DF2\u6709\u65E7\u4EFB\u52A1\u5728\u8FD0\u884C\uFF0C\u5B83\u4E0D\u4F1A\u88AB\u7EC8\u6B62\uFF0C\u4ECD\u4F1A\u5728\u540E\u53F0\u7EE7\u7EED\u6267\u884C\u5E76\u53EF\u80FD\u7A0D\u540E\u56DE\u6D88\u606F\u3002",
18994
19350
  "\u8FD9\u662F IM \u4FA7\u7EBF\u7A0B\uFF0C\u5F53\u524D\u53EA\u4FDD\u8BC1\u5728 IM \u4E2D\u53EF\u7EE7\u7EED\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u51FA\u73B0\u5728 Codex Desktop \u4F1A\u8BDD\u5217\u8868\u4E2D\u3002"
18995
19351
  ],
18996
19352
  responseParseMode === "Markdown"
@@ -19402,6 +19758,7 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
19402
19758
  return {
19403
19759
  heading: `${getSessionDisplayName(session, session.working_directory)}${session.id === currentBinding?.codepilotSessionId ? " [\u5F53\u524D]" : ""}`,
19404
19760
  details: [
19761
+ `\u72B6\u6001\uFF1A${formatRuntimeStatus(session)}`,
19405
19762
  `\u76EE\u5F55\uFF1A${formatCommandPath(session.working_directory)}`
19406
19763
  ]
19407
19764
  };
@@ -19420,7 +19777,7 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
19420
19777
  const st = getState();
19421
19778
  const taskAbort = st.activeTasks.get(binding.codepilotSessionId);
19422
19779
  if (taskAbort) {
19423
- taskAbort.abort();
19780
+ taskAbort.abortController.abort();
19424
19781
  response = "\u6B63\u5728\u505C\u6B62\u5F53\u524D\u4EFB\u52A1...";
19425
19782
  } else {
19426
19783
  response = "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1\u3002";
@@ -19525,6 +19882,13 @@ function computeSdkSessionUpdate(sdkSessionId, hasError) {
19525
19882
  }
19526
19883
  return null;
19527
19884
  }
19885
+ function persistSdkSessionUpdate(sessionId, sdkSessionId, hasError) {
19886
+ const update = computeSdkSessionUpdate(sdkSessionId, hasError);
19887
+ if (update === null) {
19888
+ return;
19889
+ }
19890
+ getBridgeContext().store.updateSdkSessionId(sessionId, update);
19891
+ }
19528
19892
 
19529
19893
  // src/store.ts
19530
19894
  import fs9 from "node:fs";
@@ -19557,6 +19921,35 @@ function uuid() {
19557
19921
  function now() {
19558
19922
  return (/* @__PURE__ */ new Date()).toISOString();
19559
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
+ }
19560
19953
  var JsonFileStore = class {
19561
19954
  settings;
19562
19955
  dynamicSettings;
@@ -19614,7 +20007,19 @@ var JsonFileStore = class {
19614
20007
  path12.join(DATA_DIR2, "bindings.json"),
19615
20008
  {}
19616
20009
  );
19617
- 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
+ }
19618
20023
  }
19619
20024
  persistSessions() {
19620
20025
  writeJson(
@@ -19694,6 +20099,8 @@ var JsonFileStore = class {
19694
20099
  ...existing,
19695
20100
  codepilotSessionId: data.codepilotSessionId,
19696
20101
  sdkSessionId: data.sdkSessionId ?? existing.sdkSessionId,
20102
+ channelProvider: data.channelProvider ?? existing.channelProvider,
20103
+ channelAlias: data.channelAlias ?? existing.channelAlias,
19697
20104
  chatUserId: data.chatUserId ?? existing.chatUserId,
19698
20105
  chatDisplayName: data.chatDisplayName ?? existing.chatDisplayName,
19699
20106
  workingDirectory: data.workingDirectory,
@@ -19708,6 +20115,8 @@ var JsonFileStore = class {
19708
20115
  const binding = {
19709
20116
  id: uuid(),
19710
20117
  channelType: data.channelType,
20118
+ channelProvider: data.channelProvider,
20119
+ channelAlias: data.channelAlias,
19711
20120
  chatId: data.chatId,
19712
20121
  chatUserId: data.chatUserId,
19713
20122
  chatDisplayName: data.chatDisplayName,
@@ -20645,6 +21054,9 @@ function writeStatus(info) {
20645
21054
  function getRunningChannels() {
20646
21055
  return getStatus().adapters.map((adapter) => adapter.channelType).sort();
20647
21056
  }
21057
+ function getAdapterStatuses() {
21058
+ return getStatus().adapters;
21059
+ }
20648
21060
  async function main() {
20649
21061
  const config2 = loadConfig();
20650
21062
  setupLogger();
@@ -20672,7 +21084,8 @@ async function main() {
20672
21084
  pid: process.pid,
20673
21085
  runId,
20674
21086
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
20675
- channels
21087
+ channels,
21088
+ adapters: getAdapterStatuses()
20676
21089
  });
20677
21090
  console.log(`[codex-to-im] Bridge started (PID: ${process.pid}, channels: ${channels.join(", ")})`);
20678
21091
  },
@@ -20681,12 +21094,13 @@ async function main() {
20681
21094
  running: true,
20682
21095
  pid: process.pid,
20683
21096
  runId,
20684
- channels
21097
+ channels,
21098
+ adapters: getAdapterStatuses()
20685
21099
  });
20686
21100
  console.log(`[codex-to-im] Active channels updated: ${channels.join(", ") || "none"}`);
20687
21101
  },
20688
21102
  onBridgeStop: () => {
20689
- writeStatus({ running: false, channels: [] });
21103
+ writeStatus({ running: false, channels: [], adapters: [] });
20690
21104
  console.log("[codex-to-im] Bridge stopped");
20691
21105
  }
20692
21106
  }