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.
@@ -4575,6 +4575,12 @@ import fs9 from "node:fs";
4575
4575
  import fs from "node:fs";
4576
4576
  import os from "node:os";
4577
4577
  import path from "node:path";
4578
+ function toFeishuConfig(channel) {
4579
+ return channel?.provider === "feishu" ? channel.config : void 0;
4580
+ }
4581
+ function toWeixinConfig(channel) {
4582
+ return channel?.provider === "weixin" ? channel.config : void 0;
4583
+ }
4578
4584
  var LEGACY_CTI_HOME = path.join(os.homedir(), ".claude-to-im");
4579
4585
  var DEFAULT_CTI_HOME = path.join(os.homedir(), ".codex-to-im");
4580
4586
  var DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), "cx2im");
@@ -4585,6 +4591,7 @@ function resolveDefaultCtiHome() {
4585
4591
  }
4586
4592
  var CTI_HOME = process.env.CTI_HOME || resolveDefaultCtiHome();
4587
4593
  var CONFIG_PATH = path.join(CTI_HOME, "config.env");
4594
+ var CONFIG_V2_PATH = path.join(CTI_HOME, "config.v2.json");
4588
4595
  function expandHomePath(value) {
4589
4596
  if (!value) return value;
4590
4597
  if (value === "~") return os.homedir();
@@ -4638,48 +4645,187 @@ function parseReasoningEffort(value) {
4638
4645
  }
4639
4646
  return void 0;
4640
4647
  }
4641
- function loadConfig() {
4642
- const env = loadRawConfigEnv();
4648
+ function normalizeFeishuSite(value) {
4649
+ const normalized = (value || "").trim().replace(/\/+$/, "").toLowerCase();
4650
+ if (!normalized) return "feishu";
4651
+ if (normalized === "lark") return "lark";
4652
+ if (normalized === "feishu") return "feishu";
4653
+ if (normalized.includes("open.larksuite.com")) return "lark";
4654
+ return "feishu";
4655
+ }
4656
+ function feishuSiteToApiBaseUrl(site) {
4657
+ return normalizeFeishuSite(site) === "lark" ? "https://open.larksuite.com" : "https://open.feishu.cn";
4658
+ }
4659
+ function nowIso() {
4660
+ return (/* @__PURE__ */ new Date()).toISOString();
4661
+ }
4662
+ function ensureConfigDir() {
4663
+ fs.mkdirSync(CTI_HOME, { recursive: true });
4664
+ }
4665
+ function readConfigV2File() {
4666
+ try {
4667
+ const parsed = JSON.parse(fs.readFileSync(CONFIG_V2_PATH, "utf-8"));
4668
+ if (parsed && parsed.schemaVersion === 2 && parsed.runtime && Array.isArray(parsed.channels)) {
4669
+ return parsed;
4670
+ }
4671
+ return null;
4672
+ } catch {
4673
+ return null;
4674
+ }
4675
+ }
4676
+ function writeConfigV2File(config) {
4677
+ ensureConfigDir();
4678
+ const tmpPath = CONFIG_V2_PATH + ".tmp";
4679
+ fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2), { mode: 384 });
4680
+ fs.renameSync(tmpPath, CONFIG_V2_PATH);
4681
+ }
4682
+ function normalizeChannelId(value) {
4683
+ return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "channel";
4684
+ }
4685
+ function defaultAliasForProvider(provider) {
4686
+ return provider === "feishu" ? "\u98DE\u4E66" : "\u5FAE\u4FE1";
4687
+ }
4688
+ function buildDefaultChannelId(provider) {
4689
+ return `${provider}-default`;
4690
+ }
4691
+ function migrateLegacyEnvToV2(env) {
4643
4692
  const rawRuntime = env.get("CTI_RUNTIME") || "codex";
4644
4693
  const runtime = ["claude", "codex", "auto"].includes(rawRuntime) ? rawRuntime : "codex";
4694
+ const enabledChannels = splitCsv(env.get("CTI_ENABLED_CHANNELS")) ?? ["feishu"];
4695
+ const timestamp = nowIso();
4696
+ const channels = [];
4697
+ const hasFeishuConfig = Boolean(
4698
+ env.get("CTI_FEISHU_APP_ID") || env.get("CTI_FEISHU_APP_SECRET") || env.get("CTI_FEISHU_ALLOWED_USERS") || enabledChannels.includes("feishu")
4699
+ );
4700
+ if (hasFeishuConfig) {
4701
+ channels.push({
4702
+ id: buildDefaultChannelId("feishu"),
4703
+ alias: defaultAliasForProvider("feishu"),
4704
+ provider: "feishu",
4705
+ enabled: enabledChannels.includes("feishu"),
4706
+ createdAt: timestamp,
4707
+ updatedAt: timestamp,
4708
+ config: {
4709
+ appId: env.get("CTI_FEISHU_APP_ID") || void 0,
4710
+ appSecret: env.get("CTI_FEISHU_APP_SECRET") || void 0,
4711
+ site: normalizeFeishuSite(env.get("CTI_FEISHU_SITE") || env.get("CTI_FEISHU_DOMAIN")),
4712
+ allowedUsers: splitCsv(env.get("CTI_FEISHU_ALLOWED_USERS")),
4713
+ streamingEnabled: env.has("CTI_FEISHU_STREAMING_ENABLED") ? env.get("CTI_FEISHU_STREAMING_ENABLED") === "true" : true,
4714
+ feedbackMarkdownEnabled: env.has("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") === "true" : true
4715
+ }
4716
+ });
4717
+ }
4718
+ const hasWeixinConfig = Boolean(
4719
+ env.get("CTI_WEIXIN_BASE_URL") || env.get("CTI_WEIXIN_CDN_BASE_URL") || env.get("CTI_WEIXIN_MEDIA_ENABLED") || enabledChannels.includes("weixin")
4720
+ );
4721
+ if (hasWeixinConfig) {
4722
+ channels.push({
4723
+ id: buildDefaultChannelId("weixin"),
4724
+ alias: defaultAliasForProvider("weixin"),
4725
+ provider: "weixin",
4726
+ enabled: enabledChannels.includes("weixin"),
4727
+ createdAt: timestamp,
4728
+ updatedAt: timestamp,
4729
+ config: {
4730
+ baseUrl: env.get("CTI_WEIXIN_BASE_URL") || void 0,
4731
+ cdnBaseUrl: env.get("CTI_WEIXIN_CDN_BASE_URL") || void 0,
4732
+ mediaEnabled: env.has("CTI_WEIXIN_MEDIA_ENABLED") ? env.get("CTI_WEIXIN_MEDIA_ENABLED") === "true" : void 0,
4733
+ feedbackMarkdownEnabled: env.has("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") === "true" : false
4734
+ }
4735
+ });
4736
+ }
4737
+ return {
4738
+ schemaVersion: 2,
4739
+ runtime: {
4740
+ provider: runtime,
4741
+ defaultWorkspaceRoot: expandHomePath(env.get("CTI_DEFAULT_WORKSPACE_ROOT")) || void 0,
4742
+ defaultModel: env.get("CTI_DEFAULT_MODEL") || void 0,
4743
+ defaultMode: env.get("CTI_DEFAULT_MODE") || "code",
4744
+ historyMessageLimit: parsePositiveInt(env.get("CTI_HISTORY_MESSAGE_LIMIT")) ?? 8,
4745
+ codexSkipGitRepoCheck: env.has("CTI_CODEX_SKIP_GIT_REPO_CHECK") ? env.get("CTI_CODEX_SKIP_GIT_REPO_CHECK") === "true" : true,
4746
+ codexSandboxMode: parseSandboxMode(env.get("CTI_CODEX_SANDBOX_MODE")) ?? "workspace-write",
4747
+ codexReasoningEffort: parseReasoningEffort(env.get("CTI_CODEX_REASONING_EFFORT")) ?? "medium",
4748
+ uiAllowLan: env.get("CTI_UI_ALLOW_LAN") === "true",
4749
+ uiAccessToken: env.get("CTI_UI_ACCESS_TOKEN") || void 0,
4750
+ autoApprove: env.get("CTI_AUTO_APPROVE") === "true"
4751
+ },
4752
+ channels
4753
+ };
4754
+ }
4755
+ function getChannelByProvider(config, provider) {
4756
+ const preferredId = buildDefaultChannelId(provider);
4757
+ return config.channels.find((channel) => channel.id === preferredId) || config.channels.find((channel) => channel.provider === provider);
4758
+ }
4759
+ function expandConfig(v2) {
4760
+ return {
4761
+ schemaVersion: 2,
4762
+ channels: v2.channels,
4763
+ runtime: v2.runtime.provider,
4764
+ enabledChannels: Array.from(new Set(
4765
+ v2.channels.filter((channel) => channel.enabled).map((channel) => channel.provider)
4766
+ )),
4767
+ defaultWorkspaceRoot: v2.runtime.defaultWorkspaceRoot,
4768
+ defaultModel: v2.runtime.defaultModel,
4769
+ defaultMode: v2.runtime.defaultMode || "code",
4770
+ historyMessageLimit: v2.runtime.historyMessageLimit ?? 8,
4771
+ codexSkipGitRepoCheck: v2.runtime.codexSkipGitRepoCheck ?? true,
4772
+ codexSandboxMode: v2.runtime.codexSandboxMode ?? "workspace-write",
4773
+ codexReasoningEffort: v2.runtime.codexReasoningEffort ?? "medium",
4774
+ uiAllowLan: v2.runtime.uiAllowLan === true,
4775
+ uiAccessToken: v2.runtime.uiAccessToken || void 0,
4776
+ autoApprove: v2.runtime.autoApprove === true
4777
+ };
4778
+ }
4779
+ function buildV2FileFromExpandedConfig(config, current) {
4780
+ const hasExplicitChannels = Array.isArray(config.channels);
4781
+ let channels = hasExplicitChannels ? [...config.channels || []] : [...current?.channels || []];
4645
4782
  return {
4646
- runtime,
4647
- enabledChannels: splitCsv(env.get("CTI_ENABLED_CHANNELS")) ?? ["feishu"],
4648
- defaultWorkspaceRoot: expandHomePath(env.get("CTI_DEFAULT_WORKSPACE_ROOT")) || void 0,
4649
- defaultModel: env.get("CTI_DEFAULT_MODEL") || void 0,
4650
- defaultMode: env.get("CTI_DEFAULT_MODE") || "code",
4651
- historyMessageLimit: parsePositiveInt(env.get("CTI_HISTORY_MESSAGE_LIMIT")) ?? 8,
4652
- codexSkipGitRepoCheck: env.has("CTI_CODEX_SKIP_GIT_REPO_CHECK") ? env.get("CTI_CODEX_SKIP_GIT_REPO_CHECK") === "true" : true,
4653
- codexSandboxMode: parseSandboxMode(env.get("CTI_CODEX_SANDBOX_MODE")) ?? "workspace-write",
4654
- codexReasoningEffort: parseReasoningEffort(env.get("CTI_CODEX_REASONING_EFFORT")) ?? "medium",
4655
- uiAllowLan: env.get("CTI_UI_ALLOW_LAN") === "true",
4656
- uiAccessToken: env.get("CTI_UI_ACCESS_TOKEN") || void 0,
4657
- tgBotToken: env.get("CTI_TG_BOT_TOKEN") || void 0,
4658
- tgChatId: env.get("CTI_TG_CHAT_ID") || void 0,
4659
- tgAllowedUsers: splitCsv(env.get("CTI_TG_ALLOWED_USERS")),
4660
- feishuAppId: env.get("CTI_FEISHU_APP_ID") || void 0,
4661
- feishuAppSecret: env.get("CTI_FEISHU_APP_SECRET") || void 0,
4662
- feishuDomain: env.get("CTI_FEISHU_DOMAIN") || void 0,
4663
- feishuAllowedUsers: splitCsv(env.get("CTI_FEISHU_ALLOWED_USERS")),
4664
- feishuStreamingEnabled: env.has("CTI_FEISHU_STREAMING_ENABLED") ? env.get("CTI_FEISHU_STREAMING_ENABLED") === "true" : true,
4665
- feishuCommandMarkdownEnabled: env.has("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") === "true" : true,
4666
- discordBotToken: env.get("CTI_DISCORD_BOT_TOKEN") || void 0,
4667
- discordAllowedUsers: splitCsv(env.get("CTI_DISCORD_ALLOWED_USERS")),
4668
- discordAllowedChannels: splitCsv(
4669
- env.get("CTI_DISCORD_ALLOWED_CHANNELS")
4670
- ),
4671
- discordAllowedGuilds: splitCsv(env.get("CTI_DISCORD_ALLOWED_GUILDS")),
4672
- qqAppId: env.get("CTI_QQ_APP_ID") || void 0,
4673
- qqAppSecret: env.get("CTI_QQ_APP_SECRET") || void 0,
4674
- qqAllowedUsers: splitCsv(env.get("CTI_QQ_ALLOWED_USERS")),
4675
- qqImageEnabled: env.has("CTI_QQ_IMAGE_ENABLED") ? env.get("CTI_QQ_IMAGE_ENABLED") === "true" : void 0,
4676
- qqMaxImageSize: env.get("CTI_QQ_MAX_IMAGE_SIZE") ? Number(env.get("CTI_QQ_MAX_IMAGE_SIZE")) : void 0,
4677
- weixinBaseUrl: env.get("CTI_WEIXIN_BASE_URL") || void 0,
4678
- weixinCdnBaseUrl: env.get("CTI_WEIXIN_CDN_BASE_URL") || void 0,
4679
- weixinMediaEnabled: env.has("CTI_WEIXIN_MEDIA_ENABLED") ? env.get("CTI_WEIXIN_MEDIA_ENABLED") === "true" : void 0,
4680
- weixinCommandMarkdownEnabled: env.has("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") === "true" : false,
4681
- autoApprove: env.get("CTI_AUTO_APPROVE") === "true"
4783
+ schemaVersion: 2,
4784
+ runtime: {
4785
+ provider: config.runtime,
4786
+ defaultWorkspaceRoot: config.defaultWorkspaceRoot,
4787
+ defaultModel: config.defaultModel,
4788
+ defaultMode: config.defaultMode,
4789
+ historyMessageLimit: config.historyMessageLimit,
4790
+ codexSkipGitRepoCheck: config.codexSkipGitRepoCheck,
4791
+ codexSandboxMode: config.codexSandboxMode,
4792
+ codexReasoningEffort: config.codexReasoningEffort,
4793
+ uiAllowLan: config.uiAllowLan,
4794
+ uiAccessToken: config.uiAccessToken,
4795
+ autoApprove: config.autoApprove
4796
+ },
4797
+ channels: channels.map((channel) => ({
4798
+ ...channel,
4799
+ id: normalizeChannelId(channel.id),
4800
+ alias: channel.alias?.trim() || defaultAliasForProvider(channel.provider)
4801
+ }))
4802
+ };
4803
+ }
4804
+ function loadConfig() {
4805
+ const current = readConfigV2File();
4806
+ if (current) return expandConfig(current);
4807
+ const legacyEnv = loadRawConfigEnv();
4808
+ if (legacyEnv.size > 0) {
4809
+ const migrated = migrateLegacyEnvToV2(legacyEnv);
4810
+ writeConfigV2File(migrated);
4811
+ return expandConfig(migrated);
4812
+ }
4813
+ const empty = {
4814
+ schemaVersion: 2,
4815
+ runtime: {
4816
+ provider: "codex",
4817
+ defaultWorkspaceRoot: DEFAULT_WORKSPACE_ROOT,
4818
+ defaultMode: "code",
4819
+ historyMessageLimit: 8,
4820
+ codexSkipGitRepoCheck: true,
4821
+ codexSandboxMode: "workspace-write",
4822
+ codexReasoningEffort: "medium",
4823
+ uiAllowLan: false,
4824
+ autoApprove: false
4825
+ },
4826
+ channels: []
4682
4827
  };
4828
+ return expandConfig(empty);
4683
4829
  }
4684
4830
  function formatEnvLine(key, value) {
4685
4831
  if (value === void 0 || value === "") return "";
@@ -4687,130 +4833,139 @@ function formatEnvLine(key, value) {
4687
4833
  `;
4688
4834
  }
4689
4835
  function saveConfig(config) {
4836
+ const current = readConfigV2File();
4837
+ const next = buildV2FileFromExpandedConfig(config, current);
4838
+ writeConfigV2File(next);
4690
4839
  let out = "";
4691
- out += formatEnvLine("CTI_RUNTIME", config.runtime);
4840
+ out += formatEnvLine("CTI_RUNTIME", next.runtime.provider);
4692
4841
  out += formatEnvLine(
4693
4842
  "CTI_ENABLED_CHANNELS",
4694
- config.enabledChannels.join(",")
4695
- );
4696
- out += formatEnvLine("CTI_DEFAULT_WORKSPACE_ROOT", config.defaultWorkspaceRoot);
4697
- if (config.defaultModel) out += formatEnvLine("CTI_DEFAULT_MODEL", config.defaultModel);
4698
- out += formatEnvLine("CTI_DEFAULT_MODE", config.defaultMode);
4699
- if (config.historyMessageLimit !== void 0)
4700
- out += formatEnvLine("CTI_HISTORY_MESSAGE_LIMIT", String(config.historyMessageLimit));
4701
- if (config.codexSkipGitRepoCheck !== void 0)
4702
- out += formatEnvLine("CTI_CODEX_SKIP_GIT_REPO_CHECK", String(config.codexSkipGitRepoCheck));
4703
- out += formatEnvLine("CTI_CODEX_SANDBOX_MODE", config.codexSandboxMode);
4704
- out += formatEnvLine("CTI_CODEX_REASONING_EFFORT", config.codexReasoningEffort);
4705
- out += formatEnvLine("CTI_UI_ALLOW_LAN", String(config.uiAllowLan === true));
4706
- out += formatEnvLine("CTI_UI_ACCESS_TOKEN", config.uiAccessToken);
4707
- out += formatEnvLine("CTI_TG_BOT_TOKEN", config.tgBotToken);
4708
- out += formatEnvLine("CTI_TG_CHAT_ID", config.tgChatId);
4709
- out += formatEnvLine(
4710
- "CTI_TG_ALLOWED_USERS",
4711
- config.tgAllowedUsers?.join(",")
4712
- );
4713
- out += formatEnvLine("CTI_FEISHU_APP_ID", config.feishuAppId);
4714
- out += formatEnvLine("CTI_FEISHU_APP_SECRET", config.feishuAppSecret);
4715
- out += formatEnvLine("CTI_FEISHU_DOMAIN", config.feishuDomain);
4716
- out += formatEnvLine(
4717
- "CTI_FEISHU_ALLOWED_USERS",
4718
- config.feishuAllowedUsers?.join(",")
4719
- );
4720
- if (config.feishuStreamingEnabled !== void 0)
4721
- out += formatEnvLine(
4722
- "CTI_FEISHU_STREAMING_ENABLED",
4723
- String(config.feishuStreamingEnabled)
4724
- );
4725
- if (config.feishuCommandMarkdownEnabled !== void 0)
4726
- out += formatEnvLine(
4727
- "CTI_FEISHU_COMMAND_MARKDOWN_ENABLED",
4728
- String(config.feishuCommandMarkdownEnabled)
4729
- );
4730
- out += formatEnvLine("CTI_DISCORD_BOT_TOKEN", config.discordBotToken);
4731
- out += formatEnvLine(
4732
- "CTI_DISCORD_ALLOWED_USERS",
4733
- config.discordAllowedUsers?.join(",")
4734
- );
4735
- out += formatEnvLine(
4736
- "CTI_DISCORD_ALLOWED_CHANNELS",
4737
- config.discordAllowedChannels?.join(",")
4738
- );
4739
- out += formatEnvLine(
4740
- "CTI_DISCORD_ALLOWED_GUILDS",
4741
- config.discordAllowedGuilds?.join(",")
4742
- );
4743
- out += formatEnvLine("CTI_QQ_APP_ID", config.qqAppId);
4744
- out += formatEnvLine("CTI_QQ_APP_SECRET", config.qqAppSecret);
4745
- out += formatEnvLine(
4746
- "CTI_QQ_ALLOWED_USERS",
4747
- config.qqAllowedUsers?.join(",")
4843
+ Array.from(new Set(next.channels.filter((channel) => channel.enabled).map((channel) => channel.provider))).join(",")
4748
4844
  );
4749
- if (config.qqImageEnabled !== void 0)
4750
- out += formatEnvLine("CTI_QQ_IMAGE_ENABLED", String(config.qqImageEnabled));
4751
- if (config.qqMaxImageSize !== void 0)
4752
- out += formatEnvLine("CTI_QQ_MAX_IMAGE_SIZE", String(config.qqMaxImageSize));
4753
- out += formatEnvLine("CTI_WEIXIN_BASE_URL", config.weixinBaseUrl);
4754
- out += formatEnvLine("CTI_WEIXIN_CDN_BASE_URL", config.weixinCdnBaseUrl);
4755
- if (config.weixinMediaEnabled !== void 0)
4756
- out += formatEnvLine("CTI_WEIXIN_MEDIA_ENABLED", String(config.weixinMediaEnabled));
4757
- if (config.weixinCommandMarkdownEnabled !== void 0)
4758
- out += formatEnvLine(
4759
- "CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED",
4760
- String(config.weixinCommandMarkdownEnabled)
4761
- );
4762
- out += formatEnvLine("CTI_AUTO_APPROVE", String(config.autoApprove === true));
4763
- fs.mkdirSync(CTI_HOME, { recursive: true });
4845
+ out += formatEnvLine("CTI_DEFAULT_WORKSPACE_ROOT", next.runtime.defaultWorkspaceRoot);
4846
+ out += formatEnvLine("CTI_DEFAULT_MODEL", next.runtime.defaultModel);
4847
+ out += formatEnvLine("CTI_DEFAULT_MODE", next.runtime.defaultMode);
4848
+ if (next.runtime.historyMessageLimit !== void 0) {
4849
+ out += formatEnvLine("CTI_HISTORY_MESSAGE_LIMIT", String(next.runtime.historyMessageLimit));
4850
+ }
4851
+ if (next.runtime.codexSkipGitRepoCheck !== void 0) {
4852
+ out += formatEnvLine("CTI_CODEX_SKIP_GIT_REPO_CHECK", String(next.runtime.codexSkipGitRepoCheck));
4853
+ }
4854
+ out += formatEnvLine("CTI_CODEX_SANDBOX_MODE", next.runtime.codexSandboxMode);
4855
+ out += formatEnvLine("CTI_CODEX_REASONING_EFFORT", next.runtime.codexReasoningEffort);
4856
+ out += formatEnvLine("CTI_UI_ALLOW_LAN", String(next.runtime.uiAllowLan === true));
4857
+ out += formatEnvLine("CTI_UI_ACCESS_TOKEN", next.runtime.uiAccessToken);
4858
+ const feishu = getChannelByProvider(next, "feishu");
4859
+ const feishuConfig = toFeishuConfig(feishu);
4860
+ if (feishuConfig) {
4861
+ out += formatEnvLine("CTI_FEISHU_APP_ID", feishuConfig.appId);
4862
+ out += formatEnvLine("CTI_FEISHU_APP_SECRET", feishuConfig.appSecret);
4863
+ out += formatEnvLine("CTI_FEISHU_SITE", feishuConfig.site);
4864
+ out += formatEnvLine("CTI_FEISHU_ALLOWED_USERS", feishuConfig.allowedUsers?.join(","));
4865
+ if (feishuConfig.streamingEnabled !== void 0) {
4866
+ out += formatEnvLine("CTI_FEISHU_STREAMING_ENABLED", String(feishuConfig.streamingEnabled));
4867
+ }
4868
+ if (feishuConfig.feedbackMarkdownEnabled !== void 0) {
4869
+ out += formatEnvLine("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED", String(feishuConfig.feedbackMarkdownEnabled));
4870
+ }
4871
+ }
4872
+ const weixin = getChannelByProvider(next, "weixin");
4873
+ const weixinConfig = toWeixinConfig(weixin);
4874
+ if (weixinConfig) {
4875
+ out += formatEnvLine("CTI_WEIXIN_BASE_URL", weixinConfig.baseUrl);
4876
+ out += formatEnvLine("CTI_WEIXIN_CDN_BASE_URL", weixinConfig.cdnBaseUrl);
4877
+ if (weixinConfig.mediaEnabled !== void 0) {
4878
+ out += formatEnvLine("CTI_WEIXIN_MEDIA_ENABLED", String(weixinConfig.mediaEnabled));
4879
+ }
4880
+ if (weixinConfig.feedbackMarkdownEnabled !== void 0) {
4881
+ out += formatEnvLine("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED", String(weixinConfig.feedbackMarkdownEnabled));
4882
+ }
4883
+ }
4884
+ out += formatEnvLine("CTI_AUTO_APPROVE", String(next.runtime.autoApprove === true));
4885
+ ensureConfigDir();
4764
4886
  const tmpPath = CONFIG_PATH + ".tmp";
4765
4887
  fs.writeFileSync(tmpPath, out, { mode: 384 });
4766
4888
  fs.renameSync(tmpPath, CONFIG_PATH);
4767
4889
  }
4890
+ function listChannelInstances(config) {
4891
+ return [...config?.channels || loadConfig().channels || []];
4892
+ }
4893
+ function findChannelInstance(channelId, config) {
4894
+ return listChannelInstances(config).find((channel) => channel.id === channelId);
4895
+ }
4768
4896
  function configToSettings(config) {
4769
4897
  const m = /* @__PURE__ */ new Map();
4898
+ const current = {
4899
+ schemaVersion: 2,
4900
+ runtime: {
4901
+ provider: config.runtime,
4902
+ defaultMode: config.defaultMode
4903
+ },
4904
+ channels: config.channels || []
4905
+ };
4906
+ const feishu = getChannelByProvider(current, "feishu");
4907
+ const weixin = getChannelByProvider(current, "weixin");
4908
+ const feishuConfig = toFeishuConfig(feishu);
4909
+ const weixinConfig = toWeixinConfig(weixin);
4770
4910
  m.set("remote_bridge_enabled", "true");
4911
+ if (config.defaultWorkspaceRoot) {
4912
+ m.set("bridge_default_workspace_root", config.defaultWorkspaceRoot);
4913
+ }
4914
+ if (config.defaultModel) {
4915
+ m.set("bridge_default_model", config.defaultModel);
4916
+ m.set("default_model", config.defaultModel);
4917
+ }
4918
+ m.set("bridge_default_mode", config.defaultMode);
4919
+ m.set(
4920
+ "bridge_history_message_limit",
4921
+ String(config.historyMessageLimit && config.historyMessageLimit > 0 ? config.historyMessageLimit : 8)
4922
+ );
4923
+ m.set(
4924
+ "bridge_codex_skip_git_repo_check",
4925
+ config.codexSkipGitRepoCheck === true ? "true" : "false"
4926
+ );
4927
+ m.set(
4928
+ "bridge_codex_sandbox_mode",
4929
+ config.codexSandboxMode || "workspace-write"
4930
+ );
4931
+ m.set(
4932
+ "bridge_codex_reasoning_effort",
4933
+ config.codexReasoningEffort || "medium"
4934
+ );
4935
+ m.set(
4936
+ "bridge_channel_instances_json",
4937
+ JSON.stringify(config.channels || [])
4938
+ );
4771
4939
  m.set(
4772
4940
  "bridge_telegram_enabled",
4773
4941
  config.enabledChannels.includes("telegram") ? "true" : "false"
4774
4942
  );
4775
4943
  if (config.tgBotToken) m.set("telegram_bot_token", config.tgBotToken);
4776
- if (config.tgAllowedUsers)
4777
- m.set("telegram_bridge_allowed_users", config.tgAllowedUsers.join(","));
4944
+ if (config.tgAllowedUsers) m.set("telegram_bridge_allowed_users", config.tgAllowedUsers.join(","));
4778
4945
  if (config.tgChatId) m.set("telegram_chat_id", config.tgChatId);
4779
4946
  m.set(
4780
4947
  "bridge_discord_enabled",
4781
4948
  config.enabledChannels.includes("discord") ? "true" : "false"
4782
4949
  );
4783
- if (config.discordBotToken)
4784
- m.set("bridge_discord_bot_token", config.discordBotToken);
4785
- if (config.discordAllowedUsers)
4786
- m.set("bridge_discord_allowed_users", config.discordAllowedUsers.join(","));
4787
- if (config.discordAllowedChannels)
4788
- m.set(
4789
- "bridge_discord_allowed_channels",
4790
- config.discordAllowedChannels.join(",")
4791
- );
4792
- if (config.discordAllowedGuilds)
4793
- m.set(
4794
- "bridge_discord_allowed_guilds",
4795
- config.discordAllowedGuilds.join(",")
4796
- );
4950
+ if (config.discordBotToken) m.set("bridge_discord_bot_token", config.discordBotToken);
4951
+ if (config.discordAllowedUsers) m.set("bridge_discord_allowed_users", config.discordAllowedUsers.join(","));
4952
+ if (config.discordAllowedChannels) m.set("bridge_discord_allowed_channels", config.discordAllowedChannels.join(","));
4953
+ if (config.discordAllowedGuilds) m.set("bridge_discord_allowed_guilds", config.discordAllowedGuilds.join(","));
4797
4954
  m.set(
4798
4955
  "bridge_feishu_enabled",
4799
- config.enabledChannels.includes("feishu") ? "true" : "false"
4956
+ feishu?.enabled === true ? "true" : "false"
4800
4957
  );
4801
- if (config.feishuAppId) m.set("bridge_feishu_app_id", config.feishuAppId);
4802
- if (config.feishuAppSecret)
4803
- m.set("bridge_feishu_app_secret", config.feishuAppSecret);
4804
- if (config.feishuDomain) m.set("bridge_feishu_domain", config.feishuDomain);
4805
- if (config.feishuAllowedUsers)
4806
- m.set("bridge_feishu_allowed_users", config.feishuAllowedUsers.join(","));
4958
+ if (feishuConfig?.appId) m.set("bridge_feishu_app_id", feishuConfig.appId);
4959
+ if (feishuConfig?.appSecret) m.set("bridge_feishu_app_secret", feishuConfig.appSecret);
4960
+ if (feishuConfig?.site) m.set("bridge_feishu_site", feishuConfig.site);
4961
+ if (feishuConfig?.allowedUsers) m.set("bridge_feishu_allowed_users", feishuConfig.allowedUsers.join(","));
4807
4962
  m.set(
4808
4963
  "bridge_feishu_streaming_enabled",
4809
- config.feishuStreamingEnabled === false ? "false" : "true"
4964
+ feishuConfig?.streamingEnabled === false ? "false" : "true"
4810
4965
  );
4811
4966
  m.set(
4812
4967
  "bridge_feishu_command_markdown_enabled",
4813
- config.feishuCommandMarkdownEnabled === false ? "false" : "true"
4968
+ feishuConfig?.feedbackMarkdownEnabled === false ? "false" : "true"
4814
4969
  );
4815
4970
  m.set(
4816
4971
  "bridge_qq_enabled",
@@ -4818,50 +4973,26 @@ function configToSettings(config) {
4818
4973
  );
4819
4974
  if (config.qqAppId) m.set("bridge_qq_app_id", config.qqAppId);
4820
4975
  if (config.qqAppSecret) m.set("bridge_qq_app_secret", config.qqAppSecret);
4821
- if (config.qqAllowedUsers)
4822
- m.set("bridge_qq_allowed_users", config.qqAllowedUsers.join(","));
4823
- if (config.qqImageEnabled !== void 0)
4976
+ if (config.qqAllowedUsers) m.set("bridge_qq_allowed_users", config.qqAllowedUsers.join(","));
4977
+ if (config.qqImageEnabled !== void 0) {
4824
4978
  m.set("bridge_qq_image_enabled", String(config.qqImageEnabled));
4825
- if (config.qqMaxImageSize !== void 0)
4979
+ }
4980
+ if (config.qqMaxImageSize !== void 0) {
4826
4981
  m.set("bridge_qq_max_image_size", String(config.qqMaxImageSize));
4982
+ }
4827
4983
  m.set(
4828
4984
  "bridge_weixin_enabled",
4829
- config.enabledChannels.includes("weixin") ? "true" : "false"
4830
- );
4831
- if (config.weixinMediaEnabled !== void 0)
4832
- m.set("bridge_weixin_media_enabled", String(config.weixinMediaEnabled));
4833
- m.set(
4834
- "bridge_weixin_command_markdown_enabled",
4835
- config.weixinCommandMarkdownEnabled === true ? "true" : "false"
4985
+ weixin?.enabled === true ? "true" : "false"
4836
4986
  );
4837
- if (config.weixinBaseUrl)
4838
- m.set("bridge_weixin_base_url", config.weixinBaseUrl);
4839
- if (config.weixinCdnBaseUrl)
4840
- m.set("bridge_weixin_cdn_base_url", config.weixinCdnBaseUrl);
4841
- if (config.defaultWorkspaceRoot) {
4842
- m.set("bridge_default_workspace_root", config.defaultWorkspaceRoot);
4987
+ if (weixinConfig?.mediaEnabled !== void 0) {
4988
+ m.set("bridge_weixin_media_enabled", String(weixinConfig.mediaEnabled));
4843
4989
  }
4844
- if (config.defaultModel) {
4845
- m.set("bridge_default_model", config.defaultModel);
4846
- m.set("default_model", config.defaultModel);
4847
- }
4848
- m.set("bridge_default_mode", config.defaultMode);
4849
- m.set(
4850
- "bridge_history_message_limit",
4851
- String(config.historyMessageLimit && config.historyMessageLimit > 0 ? config.historyMessageLimit : 8)
4852
- );
4853
- m.set(
4854
- "bridge_codex_skip_git_repo_check",
4855
- config.codexSkipGitRepoCheck === true ? "true" : "false"
4856
- );
4857
- m.set(
4858
- "bridge_codex_sandbox_mode",
4859
- config.codexSandboxMode || "workspace-write"
4860
- );
4861
4990
  m.set(
4862
- "bridge_codex_reasoning_effort",
4863
- config.codexReasoningEffort || "medium"
4991
+ "bridge_weixin_command_markdown_enabled",
4992
+ weixinConfig?.feedbackMarkdownEnabled === true ? "true" : "false"
4864
4993
  );
4994
+ if (weixinConfig?.baseUrl) m.set("bridge_weixin_base_url", weixinConfig.baseUrl);
4995
+ if (weixinConfig?.cdnBaseUrl) m.set("bridge_weixin_cdn_base_url", weixinConfig.cdnBaseUrl);
4865
4996
  return m;
4866
4997
  }
4867
4998
 
@@ -5598,8 +5729,24 @@ function isArchivedDesktopThread(threadId) {
5598
5729
 
5599
5730
  // src/session-bindings.ts
5600
5731
  import path4 from "node:path";
5601
- function formatChannelLabel(channelType) {
5602
- return channelType === "weixin" ? "\u5FAE\u4FE1" : channelType === "feishu" ? "\u98DE\u4E66" : channelType;
5732
+ function asChannelProvider(value) {
5733
+ return value === "feishu" || value === "weixin" ? value : void 0;
5734
+ }
5735
+ function resolveChannelMeta(channelType, provider) {
5736
+ const instance = findChannelInstance(channelType, loadConfig());
5737
+ if (instance) {
5738
+ return {
5739
+ provider: instance.provider,
5740
+ alias: instance.alias
5741
+ };
5742
+ }
5743
+ return {
5744
+ provider,
5745
+ alias: channelType
5746
+ };
5747
+ }
5748
+ function formatChannelLabel(binding) {
5749
+ return binding.channelAlias?.trim() || resolveChannelMeta(binding.channelType, asChannelProvider(binding.channelProvider)).alias || binding.channelType;
5603
5750
  }
5604
5751
  function formatBindingChatTarget(binding) {
5605
5752
  return binding.chatDisplayName?.trim() || binding.chatId;
@@ -5620,7 +5767,7 @@ function assertBindingTargetAvailable(store, current, opts) {
5620
5767
  );
5621
5768
  if (!conflict) return;
5622
5769
  throw new Error(
5623
- `\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`
5770
+ `\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`
5624
5771
  );
5625
5772
  }
5626
5773
  function getSessionName(session) {
@@ -5641,8 +5788,11 @@ function bindStoreToSession(store, channelType, chatId, sessionId) {
5641
5788
  { channelType, chatId },
5642
5789
  { sessionId: session.id, sdkSessionId: session.sdk_session_id || void 0 }
5643
5790
  );
5791
+ const meta = resolveChannelMeta(channelType);
5644
5792
  return store.upsertChannelBinding({
5645
5793
  channelType,
5794
+ channelProvider: meta.provider,
5795
+ channelAlias: meta.alias,
5646
5796
  chatId,
5647
5797
  codepilotSessionId: session.id,
5648
5798
  sdkSessionId: session.sdk_session_id || "",
@@ -5657,10 +5807,13 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
5657
5807
  { channelType, chatId },
5658
5808
  { sdkSessionId }
5659
5809
  );
5810
+ const meta = resolveChannelMeta(channelType);
5660
5811
  const existing = store.findSessionBySdkSessionId(sdkSessionId);
5661
5812
  if (existing) {
5662
5813
  return store.upsertChannelBinding({
5663
5814
  channelType,
5815
+ channelProvider: meta.provider,
5816
+ channelAlias: meta.alias,
5664
5817
  chatId,
5665
5818
  codepilotSessionId: existing.id,
5666
5819
  sdkSessionId,
@@ -5682,6 +5835,8 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
5682
5835
  store.updateSdkSessionId(session.id, sdkSessionId);
5683
5836
  return store.upsertChannelBinding({
5684
5837
  channelType,
5838
+ channelProvider: meta.provider,
5839
+ channelAlias: meta.alias,
5685
5840
  chatId,
5686
5841
  codepilotSessionId: session.id,
5687
5842
  sdkSessionId,
@@ -5716,6 +5871,8 @@ function listBindingSummaries(store) {
5716
5871
  return {
5717
5872
  id: binding.id,
5718
5873
  channelType: binding.channelType,
5874
+ channelProvider: binding.channelProvider,
5875
+ channelAlias: binding.channelAlias,
5719
5876
  chatId: binding.chatId,
5720
5877
  chatUserId: binding.chatUserId,
5721
5878
  chatDisplayName: binding.chatDisplayName,
@@ -5733,7 +5890,9 @@ function listBindingSummaries(store) {
5733
5890
  mirrorLastEventAt: session?.mirror_last_event_at
5734
5891
  };
5735
5892
  }).sort((a, b) => {
5736
- if (a.channelType !== b.channelType) return a.channelType.localeCompare(b.channelType);
5893
+ const aLabel = a.channelAlias || a.channelType;
5894
+ const bLabel = b.channelAlias || b.channelType;
5895
+ if (aLabel !== bLabel) return aLabel.localeCompare(bLabel);
5737
5896
  return a.chatId.localeCompare(b.chatId);
5738
5897
  });
5739
5898
  }
@@ -5788,6 +5947,8 @@ var bridgePidFile = path5.join(runtimeDir, "bridge.pid");
5788
5947
  var bridgeStatusFile = path5.join(runtimeDir, "status.json");
5789
5948
  var uiStatusFile = path5.join(runtimeDir, "ui-server.json");
5790
5949
  var uiPort = 4781;
5950
+ var bridgeAutostartTaskName = "CodexToIMBridge";
5951
+ var bridgeAutostartLauncherFile = path5.join(runtimeDir, "bridge-autostart.ps1");
5791
5952
  var WINDOWS_HIDE = process.platform === "win32" ? { windowsHide: true } : {};
5792
5953
  function ensureDirs() {
5793
5954
  fs4.mkdirSync(runtimeDir, { recursive: true });
@@ -5821,6 +5982,49 @@ function isProcessAlive(pid) {
5821
5982
  function sleep(ms) {
5822
5983
  return new Promise((resolve) => setTimeout(resolve, ms));
5823
5984
  }
5985
+ function getCurrentWindowsUser() {
5986
+ const user = process.env.USERNAME || os4.userInfo().username;
5987
+ const domain = process.env.USERDOMAIN;
5988
+ return domain ? `${domain}\\${user}` : user;
5989
+ }
5990
+ function escapePowerShellSingleQuoted(value) {
5991
+ return value.replace(/'/g, "''");
5992
+ }
5993
+ function runCommand(command, args, options = {}) {
5994
+ return new Promise((resolve, reject) => {
5995
+ const child = spawn(command, args, {
5996
+ cwd: options.cwd,
5997
+ env: options.env,
5998
+ stdio: ["ignore", "pipe", "pipe"],
5999
+ ...WINDOWS_HIDE
6000
+ });
6001
+ let stdout = "";
6002
+ let stderr = "";
6003
+ child.stdout.on("data", (chunk) => {
6004
+ stdout += chunk.toString();
6005
+ });
6006
+ child.stderr.on("data", (chunk) => {
6007
+ stderr += chunk.toString();
6008
+ });
6009
+ child.on("error", reject);
6010
+ child.on("close", (code) => {
6011
+ resolve({ code: code ?? 0, stdout, stderr });
6012
+ });
6013
+ });
6014
+ }
6015
+ async function runPowerShell(script) {
6016
+ const result = await runCommand(
6017
+ "powershell.exe",
6018
+ ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script]
6019
+ );
6020
+ if (result.code !== 0) {
6021
+ throw new Error((result.stderr || result.stdout || "PowerShell command failed.").trim());
6022
+ }
6023
+ return result.stdout.trim();
6024
+ }
6025
+ function parsePowerShellJson(raw) {
6026
+ return JSON.parse(raw);
6027
+ }
5824
6028
  function getPackageRoot() {
5825
6029
  return packageRoot;
5826
6030
  }
@@ -5932,6 +6136,60 @@ async function restartBridge() {
5932
6136
  await stopBridge();
5933
6137
  return await startBridge();
5934
6138
  }
6139
+ async function getBridgeAutostartStatus() {
6140
+ const base = {
6141
+ supported: process.platform === "win32",
6142
+ installed: false,
6143
+ enabled: false,
6144
+ mode: "startup",
6145
+ taskName: bridgeAutostartTaskName,
6146
+ runAsUser: process.platform === "win32" ? getCurrentWindowsUser() : void 0,
6147
+ launcherPath: bridgeAutostartLauncherFile
6148
+ };
6149
+ if (process.platform !== "win32") {
6150
+ return {
6151
+ ...base,
6152
+ error: "\u5F53\u524D\u53EA\u652F\u6301 Windows \u81EA\u52A8\u542F\u52A8\u3002"
6153
+ };
6154
+ }
6155
+ const script = [
6156
+ `$task = Get-ScheduledTask -TaskName '${escapePowerShellSingleQuoted(bridgeAutostartTaskName)}' -ErrorAction SilentlyContinue`,
6157
+ "if (-not $task) {",
6158
+ " [pscustomobject]@{",
6159
+ " supported = $true",
6160
+ " installed = $false",
6161
+ " enabled = $false",
6162
+ ` mode = 'startup'`,
6163
+ ` taskName = '${escapePowerShellSingleQuoted(bridgeAutostartTaskName)}'`,
6164
+ ` launcherPath = '${escapePowerShellSingleQuoted(bridgeAutostartLauncherFile)}'`,
6165
+ " } | ConvertTo-Json -Compress",
6166
+ " exit 0",
6167
+ "}",
6168
+ `$info = Get-ScheduledTaskInfo -TaskName '${escapePowerShellSingleQuoted(bridgeAutostartTaskName)}' -ErrorAction SilentlyContinue`,
6169
+ "[pscustomobject]@{",
6170
+ " supported = $true",
6171
+ " installed = $true",
6172
+ " enabled = [bool]$task.Settings.Enabled",
6173
+ ` mode = 'startup'`,
6174
+ ` taskName = '${escapePowerShellSingleQuoted(bridgeAutostartTaskName)}'`,
6175
+ ` launcherPath = '${escapePowerShellSingleQuoted(bridgeAutostartLauncherFile)}'`,
6176
+ " runAsUser = $task.Principal.UserId",
6177
+ " state = [string]$task.State",
6178
+ "} | ConvertTo-Json -Compress"
6179
+ ].join("\n");
6180
+ try {
6181
+ const raw = await runPowerShell(script);
6182
+ return {
6183
+ ...base,
6184
+ ...parsePowerShellJson(raw)
6185
+ };
6186
+ } catch (error) {
6187
+ return {
6188
+ ...base,
6189
+ error: error instanceof Error ? error.message : String(error)
6190
+ };
6191
+ }
6192
+ }
5935
6193
  function getBridgeLogs(lines = 200) {
5936
6194
  ensureDirs();
5937
6195
  const filePath = path5.join(logsDir, "bridge.log");
@@ -6007,6 +6265,35 @@ function uuid() {
6007
6265
  function now() {
6008
6266
  return (/* @__PURE__ */ new Date()).toISOString();
6009
6267
  }
6268
+ function defaultAliasForProvider2(provider) {
6269
+ if (provider === "feishu") return "\u98DE\u4E66";
6270
+ if (provider === "weixin") return "\u5FAE\u4FE1";
6271
+ return void 0;
6272
+ }
6273
+ function normalizeLegacyBinding(binding) {
6274
+ const config = loadConfig();
6275
+ const legacyProvider = binding.channelType === "feishu" || binding.channelType === "weixin" ? binding.channelType : void 0;
6276
+ const resolvedInstance = legacyProvider ? (config.channels || []).find((channel) => channel.provider === legacyProvider) : findChannelInstance(binding.channelType, config);
6277
+ if (!resolvedInstance && !legacyProvider) {
6278
+ return {
6279
+ ...binding,
6280
+ active: binding.active !== false
6281
+ };
6282
+ }
6283
+ const channelType = resolvedInstance?.id || binding.channelType;
6284
+ const channelProvider = resolvedInstance?.provider || legacyProvider || binding.channelProvider;
6285
+ const channelAlias = resolvedInstance?.alias || binding.channelAlias || defaultAliasForProvider2(channelProvider);
6286
+ return {
6287
+ ...binding,
6288
+ channelType,
6289
+ channelProvider,
6290
+ channelAlias,
6291
+ active: binding.active !== false
6292
+ };
6293
+ }
6294
+ function didBindingChange(before, after) {
6295
+ return before.channelType !== after.channelType || before.channelProvider !== after.channelProvider || before.channelAlias !== after.channelAlias || before.active !== false !== after.active;
6296
+ }
6010
6297
  var JsonFileStore = class {
6011
6298
  settings;
6012
6299
  dynamicSettings;
@@ -6064,7 +6351,19 @@ var JsonFileStore = class {
6064
6351
  path6.join(DATA_DIR, "bindings.json"),
6065
6352
  {}
6066
6353
  );
6067
- this.bindings = new Map(Object.entries(bindings));
6354
+ const normalized = /* @__PURE__ */ new Map();
6355
+ let changed = false;
6356
+ for (const binding of Object.values(bindings)) {
6357
+ const normalizedBinding = normalizeLegacyBinding(binding);
6358
+ if (didBindingChange(binding, normalizedBinding)) {
6359
+ changed = true;
6360
+ }
6361
+ normalized.set(`${normalizedBinding.channelType}:${normalizedBinding.chatId}`, normalizedBinding);
6362
+ }
6363
+ this.bindings = normalized;
6364
+ if (changed) {
6365
+ this.persistBindings();
6366
+ }
6068
6367
  }
6069
6368
  persistSessions() {
6070
6369
  writeJson(
@@ -6144,6 +6443,8 @@ var JsonFileStore = class {
6144
6443
  ...existing,
6145
6444
  codepilotSessionId: data.codepilotSessionId,
6146
6445
  sdkSessionId: data.sdkSessionId ?? existing.sdkSessionId,
6446
+ channelProvider: data.channelProvider ?? existing.channelProvider,
6447
+ channelAlias: data.channelAlias ?? existing.channelAlias,
6147
6448
  chatUserId: data.chatUserId ?? existing.chatUserId,
6148
6449
  chatDisplayName: data.chatDisplayName ?? existing.chatDisplayName,
6149
6450
  workingDirectory: data.workingDirectory,
@@ -6158,6 +6459,8 @@ var JsonFileStore = class {
6158
6459
  const binding = {
6159
6460
  id: uuid(),
6160
6461
  channelType: data.channelType,
6462
+ channelProvider: data.channelProvider,
6463
+ channelAlias: data.channelAlias,
6161
6464
  chatId: data.chatId,
6162
6465
  chatUserId: data.chatUserId,
6163
6466
  chatDisplayName: data.chatDisplayName,
@@ -6791,10 +7094,9 @@ async function refreshSession(previous, baseUrl) {
6791
7094
  console.log(`[weixin-login] QR code refreshed (${next.refreshCount}/${MAX_REFRESHES})`);
6792
7095
  return next;
6793
7096
  }
6794
- async function runWeixinLogin() {
7097
+ async function runWeixinLogin(config = {}) {
6795
7098
  ensureRuntimeDir();
6796
- const config = loadConfig();
6797
- let session = await createSession(0, config.weixinBaseUrl);
7099
+ let session = await createSession(0, config.baseUrl);
6798
7100
  await writeQrHtml(session);
6799
7101
  const opened = openQrHtml();
6800
7102
  console.log("[weixin-login] WeChat QR login started");
@@ -6805,10 +7107,10 @@ async function runWeixinLogin() {
6805
7107
  let lastStatus = session.status;
6806
7108
  while (true) {
6807
7109
  if (Date.now() - session.startedAt > QR_TTL_MS) {
6808
- session = await refreshSession(session, config.weixinBaseUrl);
7110
+ session = await refreshSession(session, config.baseUrl);
6809
7111
  lastStatus = session.status;
6810
7112
  }
6811
- const response = await pollLoginQrStatus(session.qrcode, config.weixinBaseUrl);
7113
+ const response = await pollLoginQrStatus(session.qrcode, config.baseUrl);
6812
7114
  switch (response.status) {
6813
7115
  case "wait":
6814
7116
  session.status = "waiting";
@@ -6826,8 +7128,8 @@ async function runWeixinLogin() {
6826
7128
  upsertWeixinAccount({
6827
7129
  accountId,
6828
7130
  userId: response.ilink_user_id || "",
6829
- baseUrl: config.weixinBaseUrl || response.baseurl || DEFAULT_BASE_URL,
6830
- cdnBaseUrl: config.weixinCdnBaseUrl || DEFAULT_CDN_BASE_URL,
7131
+ baseUrl: config.baseUrl || response.baseurl || DEFAULT_BASE_URL,
7132
+ cdnBaseUrl: config.cdnBaseUrl || DEFAULT_CDN_BASE_URL,
6831
7133
  token: response.bot_token,
6832
7134
  name: accountId,
6833
7135
  enabled: true
@@ -6840,7 +7142,7 @@ async function runWeixinLogin() {
6840
7142
  return { accountId, htmlPath: HTML_PATH };
6841
7143
  }
6842
7144
  case "expired":
6843
- session = await refreshSession(session, config.weixinBaseUrl);
7145
+ session = await refreshSession(session, config.baseUrl);
6844
7146
  lastStatus = session.status;
6845
7147
  continue;
6846
7148
  default:
@@ -6928,13 +7230,12 @@ function listSelectableCodexModels(cachePath = DEFAULT_CODEX_MODELS_CACHE_PATH)
6928
7230
  // src/ui-server.ts
6929
7231
  var port = 4781;
6930
7232
  var serverStartTime = (/* @__PURE__ */ new Date()).toISOString();
6931
- var supportedChannels = ["feishu", "weixin"];
6932
7233
  var AUTH_COOKIE_NAME = "cti_ui_auth";
6933
7234
  var availableCodexModels = listSelectableCodexModels();
6934
7235
  var availableCodexModelSlugs = new Set(availableCodexModels.map((model) => model.slug));
6935
7236
  var FEISHU_CHAT_LABEL_TTL_MS = 5 * 60 * 1e3;
6936
7237
  var feishuChatLabelCache = /* @__PURE__ */ new Map();
6937
- var feishuTenantTokenCache = null;
7238
+ var feishuTenantTokenCache = /* @__PURE__ */ new Map();
6938
7239
  function parsePreferredPort() {
6939
7240
  const raw = Number(process.env.CTI_UI_PORT || "4781");
6940
7241
  if (!Number.isInteger(raw) || raw <= 0 || raw > 65535) return 4781;
@@ -7019,9 +7320,47 @@ function parsePositiveInt2(value, fallback) {
7019
7320
  if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
7020
7321
  return Math.floor(parsed);
7021
7322
  }
7323
+ function normalizeChannelAlias(value, provider) {
7324
+ const trimmed = value?.trim();
7325
+ if (trimmed) return trimmed;
7326
+ return provider === "feishu" ? "\u98DE\u4E66" : "\u5FAE\u4FE1";
7327
+ }
7328
+ function normalizeChannelId2(value) {
7329
+ return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "channel";
7330
+ }
7331
+ function buildChannelId(provider, alias, takenIds, currentId) {
7332
+ const base = normalizeChannelId2(`${provider}-${alias}`);
7333
+ if (!takenIds.has(base) || base === currentId) return base;
7334
+ let suffix = 2;
7335
+ while (takenIds.has(`${base}-${suffix}`) && `${base}-${suffix}` !== currentId) {
7336
+ suffix += 1;
7337
+ }
7338
+ return `${base}-${suffix}`;
7339
+ }
7340
+ function parseChannelProvider(value) {
7341
+ if (value === "feishu" || value === "weixin") return value;
7342
+ return void 0;
7343
+ }
7022
7344
  function createUiStore() {
7023
7345
  return new JsonFileStore(configToSettings(loadConfig()));
7024
7346
  }
7347
+ function cloneChannel(channel) {
7348
+ return {
7349
+ ...channel,
7350
+ config: { ...channel.config }
7351
+ };
7352
+ }
7353
+ function channelToPayload(channel) {
7354
+ return {
7355
+ id: channel.id,
7356
+ alias: channel.alias,
7357
+ provider: channel.provider,
7358
+ enabled: channel.enabled,
7359
+ createdAt: channel.createdAt,
7360
+ updatedAt: channel.updatedAt,
7361
+ config: { ...channel.config }
7362
+ };
7363
+ }
7025
7364
  function generateAccessToken() {
7026
7365
  return crypto4.randomBytes(18).toString("base64url");
7027
7366
  }
@@ -7093,7 +7432,6 @@ function isRemoteAuthenticated(request, config) {
7093
7432
  function configToPayload(config) {
7094
7433
  return {
7095
7434
  runtime: config.runtime,
7096
- enabledChannels: config.enabledChannels,
7097
7435
  defaultWorkspaceRoot: config.defaultWorkspaceRoot || "",
7098
7436
  defaultModel: config.defaultModel || "",
7099
7437
  codexDefaultModel: readConfiguredCodexModel() || "",
@@ -7106,27 +7444,19 @@ function configToPayload(config) {
7106
7444
  uiAllowLan: config.uiAllowLan === true,
7107
7445
  uiAccessToken: config.uiAccessToken || "",
7108
7446
  autoApprove: config.autoApprove === true,
7109
- feishuAppId: config.feishuAppId || "",
7110
- feishuAppSecret: config.feishuAppSecret || "",
7111
- feishuDomain: config.feishuDomain || "https://open.feishu.cn",
7112
- feishuAllowedUsers: config.feishuAllowedUsers?.join(",") || "",
7113
- feishuStreamingEnabled: config.feishuStreamingEnabled !== false,
7114
- feishuCommandMarkdownEnabled: config.feishuCommandMarkdownEnabled !== false,
7115
- weixinMediaEnabled: config.weixinMediaEnabled === true,
7116
- weixinCommandMarkdownEnabled: config.weixinCommandMarkdownEnabled === true
7447
+ channels: (config.channels || []).map(channelToPayload)
7117
7448
  };
7118
7449
  }
7119
7450
  function mergeConfig(payload) {
7120
7451
  const current = loadConfig();
7121
7452
  const rawDefaultModel = typeof payload.defaultModel === "string" ? payload.defaultModel.trim() : void 0;
7122
- const requestedChannels = Array.isArray(payload.enabledChannels) ? payload.enabledChannels.filter((value) => typeof value === "string") : current.enabledChannels;
7123
7453
  const uiAllowLan = payload.uiAllowLan === true;
7124
7454
  const requestedUiAccessToken = asString(payload.uiAccessToken);
7125
7455
  const uiAccessToken = requestedUiAccessToken || current.uiAccessToken || (uiAllowLan ? generateAccessToken() : void 0);
7126
7456
  return {
7127
7457
  ...current,
7128
7458
  runtime: payload.runtime === "claude" || payload.runtime === "auto" ? payload.runtime : "codex",
7129
- enabledChannels: requestedChannels.filter((channel) => supportedChannels.includes(channel)),
7459
+ enabledChannels: current.enabledChannels,
7130
7460
  defaultWorkspaceRoot: asString(payload.defaultWorkspaceRoot),
7131
7461
  defaultModel: rawDefaultModel === void 0 ? current.defaultModel : rawDefaultModel === "" ? void 0 : availableCodexModelSlugs.has(rawDefaultModel) ? rawDefaultModel : current.defaultModel,
7132
7462
  defaultMode: payload.defaultMode === "plan" || payload.defaultMode === "ask" ? payload.defaultMode : "code",
@@ -7137,14 +7467,57 @@ function mergeConfig(payload) {
7137
7467
  uiAllowLan,
7138
7468
  uiAccessToken,
7139
7469
  autoApprove: payload.autoApprove === true,
7140
- feishuAppId: asString(payload.feishuAppId),
7141
- feishuAppSecret: asString(payload.feishuAppSecret),
7142
- feishuDomain: asString(payload.feishuDomain) || "https://open.feishu.cn",
7143
- feishuAllowedUsers: parseCsv(payload.feishuAllowedUsers),
7144
- feishuStreamingEnabled: payload.feishuStreamingEnabled !== false,
7145
- feishuCommandMarkdownEnabled: payload.feishuCommandMarkdownEnabled !== false,
7146
- weixinMediaEnabled: payload.weixinMediaEnabled === true,
7147
- weixinCommandMarkdownEnabled: payload.weixinCommandMarkdownEnabled === true
7470
+ channels: current.channels
7471
+ };
7472
+ }
7473
+ function mergeChannelInstance(payload, current) {
7474
+ const provider = parseChannelProvider(payload.provider);
7475
+ if (!provider) {
7476
+ throw new Error("\u901A\u9053\u63D0\u4F9B\u65B9\u53EA\u80FD\u662F\u98DE\u4E66\u6216\u5FAE\u4FE1\u3002");
7477
+ }
7478
+ const existingId = asString(payload.id);
7479
+ const existing = existingId ? findChannelInstance(existingId, current) : void 0;
7480
+ const alias = normalizeChannelAlias(asString(payload.alias), provider);
7481
+ const baseChannels = (current.channels || []).map(cloneChannel);
7482
+ const takenIds = new Set(baseChannels.map((channel) => channel.id));
7483
+ const channelId = existing?.id || buildChannelId(provider, alias, takenIds);
7484
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
7485
+ let nextConfig;
7486
+ if (provider === "feishu") {
7487
+ nextConfig = {
7488
+ appId: asString(payload.appId),
7489
+ appSecret: asString(payload.appSecret),
7490
+ site: normalizeFeishuSite(asString(payload.site) || asString(payload.domain)),
7491
+ allowedUsers: parseCsv(payload.allowedUsers),
7492
+ streamingEnabled: payload.streamingEnabled !== false,
7493
+ feedbackMarkdownEnabled: payload.feedbackMarkdownEnabled !== false
7494
+ };
7495
+ } else {
7496
+ nextConfig = {
7497
+ accountId: asString(payload.accountId),
7498
+ baseUrl: asString(payload.baseUrl),
7499
+ cdnBaseUrl: asString(payload.cdnBaseUrl),
7500
+ mediaEnabled: payload.mediaEnabled === true,
7501
+ feedbackMarkdownEnabled: payload.feedbackMarkdownEnabled === true
7502
+ };
7503
+ }
7504
+ const nextChannel = {
7505
+ id: channelId,
7506
+ alias,
7507
+ provider,
7508
+ enabled: payload.enabled !== false,
7509
+ createdAt: existing?.createdAt || now3,
7510
+ updatedAt: now3,
7511
+ config: nextConfig
7512
+ };
7513
+ const nextChannels = existing ? baseChannels.map((channel) => channel.id === existing.id ? nextChannel : channel) : [...baseChannels, nextChannel];
7514
+ return {
7515
+ config: {
7516
+ ...current,
7517
+ channels: nextChannels,
7518
+ enabledChannels: Array.from(new Set(nextChannels.filter((channel) => channel.enabled).map((channel) => channel.provider)))
7519
+ },
7520
+ channel: nextChannel
7148
7521
  };
7149
7522
  }
7150
7523
  function getWeixinAccountsPayload() {
@@ -7159,17 +7532,38 @@ function getWeixinAccountsPayload() {
7159
7532
  updatedAt: account.updatedAt
7160
7533
  }));
7161
7534
  }
7162
- async function validateFeishuCredentials(config) {
7163
- if (!config.feishuAppId || !config.feishuAppSecret) {
7535
+ function getChannelLabel(channel) {
7536
+ const providerLabel = channel.provider === "weixin" ? "\u5FAE\u4FE1" : "\u98DE\u4E66";
7537
+ return channel.alias?.trim() ? `${channel.alias} \xB7 ${providerLabel}` : providerLabel;
7538
+ }
7539
+ function getFeishuSite(channel) {
7540
+ const feishu = channel.config;
7541
+ return normalizeFeishuSite(feishu.site);
7542
+ }
7543
+ function getFeishuDomain(channel) {
7544
+ return feishuSiteToApiBaseUrl(getFeishuSite(channel));
7545
+ }
7546
+ function getFeishuTokenCacheKey(channel) {
7547
+ const feishu = channel.config;
7548
+ return [
7549
+ channel.id,
7550
+ feishu.appId || "",
7551
+ feishu.appSecret || "",
7552
+ getFeishuDomain(channel)
7553
+ ].join(":");
7554
+ }
7555
+ async function validateFeishuCredentials(channel) {
7556
+ const feishu = channel.config;
7557
+ if (!feishu.appId || !feishu.appSecret) {
7164
7558
  return { ok: false, message: "Feishu App ID / App Secret \u4E0D\u80FD\u4E3A\u7A7A\u3002" };
7165
7559
  }
7166
- const domain = config.feishuDomain || "https://open.feishu.cn";
7560
+ const domain = getFeishuDomain(channel);
7167
7561
  const response = await fetch(`${domain}/open-apis/auth/v3/tenant_access_token/internal`, {
7168
7562
  method: "POST",
7169
7563
  headers: { "Content-Type": "application/json" },
7170
7564
  body: JSON.stringify({
7171
- app_id: config.feishuAppId,
7172
- app_secret: config.feishuAppSecret
7565
+ app_id: feishu.appId,
7566
+ app_secret: feishu.appSecret
7173
7567
  })
7174
7568
  });
7175
7569
  const data = await response.json();
@@ -7178,42 +7572,40 @@ async function validateFeishuCredentials(config) {
7178
7572
  }
7179
7573
  return {
7180
7574
  ok: false,
7181
- message: data.msg || `\u98DE\u4E66\u6821\u9A8C\u5931\u8D25\uFF0CHTTP ${response.status}`
7575
+ message: `${getChannelLabel(channel)} \u6821\u9A8C\u5931\u8D25\uFF1A${data.msg || `HTTP ${response.status}`}`
7182
7576
  };
7183
7577
  }
7184
- function getFeishuDomain(config) {
7185
- return (config.feishuDomain || "https://open.feishu.cn").replace(/\/+$/, "");
7186
- }
7187
- async function getFeishuTenantAccessToken(config) {
7188
- if (!config.feishuAppId || !config.feishuAppSecret) return null;
7189
- const domain = getFeishuDomain(config);
7578
+ async function getFeishuTenantAccessToken(channel) {
7579
+ const feishu = channel.config;
7580
+ if (!feishu.appId || !feishu.appSecret) return null;
7581
+ const domain = getFeishuDomain(channel);
7582
+ const cacheKey = getFeishuTokenCacheKey(channel);
7190
7583
  const now3 = Date.now();
7191
- if (feishuTenantTokenCache && feishuTenantTokenCache.appId === config.feishuAppId && feishuTenantTokenCache.appSecret === config.feishuAppSecret && feishuTenantTokenCache.domain === domain && feishuTenantTokenCache.expiresAt > now3 + 6e4) {
7192
- return feishuTenantTokenCache.token;
7584
+ const cached = feishuTenantTokenCache.get(cacheKey);
7585
+ if (cached && cached.expiresAt > now3 + 6e4) {
7586
+ return cached.token;
7193
7587
  }
7194
7588
  const response = await fetch(`${domain}/open-apis/auth/v3/tenant_access_token/internal`, {
7195
7589
  method: "POST",
7196
7590
  headers: { "Content-Type": "application/json" },
7197
7591
  body: JSON.stringify({
7198
- app_id: config.feishuAppId,
7199
- app_secret: config.feishuAppSecret
7592
+ app_id: feishu.appId,
7593
+ app_secret: feishu.appSecret
7200
7594
  })
7201
7595
  });
7202
7596
  const data = await response.json();
7203
7597
  if (!response.ok || data.code !== 0 || !data.tenant_access_token) {
7204
7598
  return null;
7205
7599
  }
7206
- feishuTenantTokenCache = {
7207
- appId: config.feishuAppId,
7208
- appSecret: config.feishuAppSecret,
7209
- domain,
7600
+ feishuTenantTokenCache.set(cacheKey, {
7210
7601
  token: data.tenant_access_token,
7211
7602
  expiresAt: now3 + Math.max(60, Number(data.expire || 7200)) * 1e3
7212
- };
7603
+ });
7213
7604
  return data.tenant_access_token;
7214
7605
  }
7215
7606
  async function resolveFeishuBindingDisplay(config, binding) {
7216
- if (binding.channelType !== "feishu") {
7607
+ const channel = findChannelInstance(binding.channelType, config);
7608
+ if (!channel || channel.provider !== "feishu") {
7217
7609
  return {
7218
7610
  chatDisplayName: binding.chatDisplayName,
7219
7611
  chatUserId: binding.chatUserId
@@ -7226,14 +7618,14 @@ async function resolveFeishuBindingDisplay(config, binding) {
7226
7618
  chatUserId: cached.userId || binding.chatUserId
7227
7619
  };
7228
7620
  }
7229
- const token = await getFeishuTenantAccessToken(config);
7621
+ const token = await getFeishuTenantAccessToken(channel);
7230
7622
  if (!token) {
7231
7623
  return {
7232
7624
  chatDisplayName: binding.chatDisplayName,
7233
7625
  chatUserId: binding.chatUserId
7234
7626
  };
7235
7627
  }
7236
- const domain = getFeishuDomain(config);
7628
+ const domain = getFeishuDomain(channel);
7237
7629
  try {
7238
7630
  const chatResponse = await fetch(
7239
7631
  `${domain}/open-apis/im/v1/chats/${encodeURIComponent(binding.chatId)}`,
@@ -7283,7 +7675,7 @@ async function buildBindingsPayload(store, config) {
7283
7675
  const bindings = listBindingSummaries(store);
7284
7676
  const enriched = await Promise.all(bindings.map(async (binding) => {
7285
7677
  const resolved = await resolveFeishuBindingDisplay(config, binding);
7286
- if (binding.channelType === "feishu" && (resolved.chatDisplayName !== binding.chatDisplayName || resolved.chatUserId !== binding.chatUserId) && (resolved.chatDisplayName || resolved.chatUserId)) {
7678
+ if ((resolved.chatDisplayName !== binding.chatDisplayName || resolved.chatUserId !== binding.chatUserId) && (resolved.chatDisplayName || resolved.chatUserId)) {
7287
7679
  store.updateChannelBinding(binding.id, {
7288
7680
  chatDisplayName: resolved.chatDisplayName,
7289
7681
  chatUserId: resolved.chatUserId
@@ -7297,13 +7689,29 @@ async function buildBindingsPayload(store, config) {
7297
7689
  }));
7298
7690
  return {
7299
7691
  bindings: enriched,
7300
- channels: {
7301
- feishu: enriched.filter((binding) => binding.channelType === "feishu"),
7302
- weixin: enriched.filter((binding) => binding.channelType === "weixin")
7303
- },
7304
7692
  options: listBindingTargetOptions(store, 12)
7305
7693
  };
7306
7694
  }
7695
+ function syncBindingChannelMeta(store, channel) {
7696
+ for (const binding of store.listChannelBindings(channel.id)) {
7697
+ store.updateChannelBinding(binding.id, {
7698
+ channelProvider: channel.provider,
7699
+ channelAlias: channel.alias
7700
+ });
7701
+ }
7702
+ }
7703
+ function deleteChannelInstance(current, channelId) {
7704
+ const channels = current.channels || [];
7705
+ const nextChannels = channels.filter((channel) => channel.id !== channelId);
7706
+ if (nextChannels.length === channels.length) {
7707
+ throw new Error("\u6307\u5B9A\u7684\u901A\u9053\u4E0D\u5B58\u5728\u3002");
7708
+ }
7709
+ return {
7710
+ ...current,
7711
+ channels: nextChannels,
7712
+ enabledChannels: Array.from(new Set(nextChannels.filter((channel) => channel.enabled).map((channel) => channel.provider)))
7713
+ };
7714
+ }
7307
7715
  async function testCodexConnection(config) {
7308
7716
  const provider = new CodexProvider(new PendingPermissions());
7309
7717
  const abortController = new AbortController();
@@ -8202,63 +8610,212 @@ function renderHtml() {
8202
8610
  overflow: hidden;
8203
8611
  }
8204
8612
 
8205
- .channel-tabs {
8206
- display: flex;
8207
- align-items: flex-end;
8208
- gap: 0;
8209
- padding: 0 20px;
8210
- border-bottom: 1px solid var(--border);
8211
- background: #ffffff;
8613
+ .channel-layout {
8614
+ display: grid;
8615
+ grid-template-columns: 280px minmax(0, 1fr);
8616
+ gap: 20px;
8212
8617
  }
8213
8618
 
8214
- .command-sections {
8619
+ .channel-sidebar {
8620
+ border-right: 1px solid var(--border);
8621
+ padding-right: 20px;
8215
8622
  display: grid;
8216
- gap: 14px;
8623
+ gap: 12px;
8624
+ align-content: start;
8217
8625
  }
8218
8626
 
8219
- .command-section {
8220
- border: 1px solid var(--border);
8221
- border-radius: 8px;
8222
- background: var(--surface-soft);
8223
- overflow: hidden;
8627
+ .channel-sidebar-meta {
8628
+ color: var(--muted);
8629
+ font-size: 12px;
8224
8630
  }
8225
8631
 
8226
- .command-section-title {
8227
- margin: 0;
8228
- padding: 10px 14px;
8229
- font-size: 14px;
8230
- font-weight: 700;
8231
- border-bottom: 1px solid var(--border);
8232
- background: #ffffff;
8632
+ .channel-list {
8633
+ display: grid;
8634
+ gap: 8px;
8233
8635
  }
8234
8636
 
8235
- .command-list {
8637
+ .channel-list-item {
8638
+ width: 100%;
8639
+ text-align: left;
8640
+ border-radius: 10px;
8641
+ padding: 12px 14px;
8236
8642
  display: grid;
8643
+ gap: 8px;
8237
8644
  }
8238
8645
 
8239
- .command-list-head,
8240
- .command-item {
8241
- display: grid;
8242
- grid-template-columns: 220px 320px minmax(0, 1fr);
8243
- gap: 16px;
8244
- padding: 10px 14px;
8245
- align-items: start;
8646
+ .channel-list-item.active {
8647
+ border-color: rgba(22, 119, 255, 0.30);
8648
+ background: rgba(22, 119, 255, 0.06);
8649
+ color: var(--text);
8246
8650
  }
8247
8651
 
8248
- .command-list-head {
8249
- padding-top: 12px;
8250
- padding-bottom: 8px;
8251
- color: var(--muted);
8252
- font-size: 12px;
8253
- font-weight: 700;
8254
- text-transform: uppercase;
8255
- letter-spacing: .04em;
8256
- background: #fcfcfd;
8257
- border-top: 1px solid var(--border);
8652
+ .channel-list-item-head {
8653
+ display: flex;
8654
+ justify-content: space-between;
8655
+ align-items: center;
8656
+ gap: 10px;
8258
8657
  }
8259
8658
 
8260
- .command-item:first-child {
8261
- border-top: 0;
8659
+ .channel-list-item-title {
8660
+ font-weight: 700;
8661
+ min-width: 0;
8662
+ word-break: break-word;
8663
+ }
8664
+
8665
+ .channel-list-item-provider,
8666
+ .channel-list-item-meta {
8667
+ color: var(--muted);
8668
+ font-size: 12px;
8669
+ }
8670
+
8671
+ .channel-list-item-stats {
8672
+ display: flex;
8673
+ justify-content: space-between;
8674
+ align-items: center;
8675
+ gap: 12px;
8676
+ font-size: 12px;
8677
+ color: var(--muted);
8678
+ }
8679
+
8680
+ .channel-list-item-status {
8681
+ color: var(--text);
8682
+ font-weight: 600;
8683
+ }
8684
+
8685
+ .channel-editor {
8686
+ min-width: 0;
8687
+ }
8688
+
8689
+ .channel-editor-summary {
8690
+ display: grid;
8691
+ grid-template-columns: repeat(3, minmax(0, 1fr));
8692
+ gap: 12px;
8693
+ margin-bottom: 18px;
8694
+ }
8695
+
8696
+ .channel-editor-stat {
8697
+ border: 1px solid var(--border);
8698
+ border-radius: 8px;
8699
+ background: var(--surface-soft);
8700
+ padding: 12px 14px;
8701
+ display: grid;
8702
+ gap: 4px;
8703
+ }
8704
+
8705
+ .channel-editor-stat strong {
8706
+ font-size: 12px;
8707
+ color: var(--muted);
8708
+ font-weight: 600;
8709
+ }
8710
+
8711
+ .channel-editor-stat span {
8712
+ font-size: 14px;
8713
+ font-weight: 700;
8714
+ color: var(--text);
8715
+ }
8716
+
8717
+ .editor-section {
8718
+ border-top: 1px solid var(--border);
8719
+ padding-top: 16px;
8720
+ display: grid;
8721
+ gap: 14px;
8722
+ }
8723
+
8724
+ .editor-section-title {
8725
+ margin: 0;
8726
+ font-size: 14px;
8727
+ font-weight: 700;
8728
+ }
8729
+
8730
+ .toolbar-split {
8731
+ display: flex;
8732
+ justify-content: space-between;
8733
+ align-items: flex-start;
8734
+ gap: 12px;
8735
+ flex-wrap: wrap;
8736
+ }
8737
+
8738
+ .toolbar-danger {
8739
+ display: flex;
8740
+ gap: 10px;
8741
+ flex-wrap: wrap;
8742
+ }
8743
+
8744
+ button.danger {
8745
+ border-color: rgba(220, 38, 38, 0.24);
8746
+ color: var(--danger);
8747
+ }
8748
+
8749
+ button.danger:hover {
8750
+ border-color: var(--danger);
8751
+ color: var(--danger);
8752
+ }
8753
+
8754
+ .inline-select {
8755
+ display: inline-grid;
8756
+ gap: 6px;
8757
+ color: var(--muted);
8758
+ font-size: 12px;
8759
+ font-weight: 500;
8760
+ }
8761
+
8762
+ .channel-tabs {
8763
+ display: flex;
8764
+ align-items: flex-end;
8765
+ gap: 0;
8766
+ padding: 0 20px;
8767
+ border-bottom: 1px solid var(--border);
8768
+ background: #ffffff;
8769
+ }
8770
+
8771
+ .command-sections {
8772
+ display: grid;
8773
+ gap: 14px;
8774
+ }
8775
+
8776
+ .command-section {
8777
+ border: 1px solid var(--border);
8778
+ border-radius: 8px;
8779
+ background: var(--surface-soft);
8780
+ overflow: hidden;
8781
+ }
8782
+
8783
+ .command-section-title {
8784
+ margin: 0;
8785
+ padding: 10px 14px;
8786
+ font-size: 14px;
8787
+ font-weight: 700;
8788
+ border-bottom: 1px solid var(--border);
8789
+ background: #ffffff;
8790
+ }
8791
+
8792
+ .command-list {
8793
+ display: grid;
8794
+ }
8795
+
8796
+ .command-list-head,
8797
+ .command-item {
8798
+ display: grid;
8799
+ grid-template-columns: 220px 320px minmax(0, 1fr);
8800
+ gap: 16px;
8801
+ padding: 10px 14px;
8802
+ align-items: start;
8803
+ }
8804
+
8805
+ .command-list-head {
8806
+ padding-top: 12px;
8807
+ padding-bottom: 8px;
8808
+ color: var(--muted);
8809
+ font-size: 12px;
8810
+ font-weight: 700;
8811
+ text-transform: uppercase;
8812
+ letter-spacing: .04em;
8813
+ background: #fcfcfd;
8814
+ border-top: 1px solid var(--border);
8815
+ }
8816
+
8817
+ .command-item:first-child {
8818
+ border-top: 0;
8262
8819
  }
8263
8820
 
8264
8821
  .command-item code {
@@ -8464,6 +9021,9 @@ function renderHtml() {
8464
9021
  .sidebar { border-right: 0; border-bottom: 1px solid var(--sidebar-border); }
8465
9022
  .nav { grid-template-columns: repeat(6, minmax(0, 1fr)); }
8466
9023
  .main { padding: 20px 20px 28px; }
9024
+ .channel-layout { grid-template-columns: 1fr; }
9025
+ .channel-sidebar { border-right: 0; padding-right: 0; }
9026
+ .channel-editor-summary { grid-template-columns: 1fr; }
8467
9027
  .field-row,
8468
9028
  .field-row.triple,
8469
9029
  .command-item,
@@ -8516,6 +9076,10 @@ function renderHtml() {
8516
9076
  <strong>Bridge</strong>
8517
9077
  <div class="status-value" id="bridgeStatus">-</div>
8518
9078
  </div>
9079
+ <div class="status-card">
9080
+ <strong>Bridge \u5F00\u673A\u81EA\u542F\u52A8</strong>
9081
+ <div class="status-value" id="autostartStatus">-</div>
9082
+ </div>
8519
9083
  <div class="status-card">
8520
9084
  <strong>Codex Skill</strong>
8521
9085
  <div class="status-value" id="integrationStatus">-</div>
@@ -8559,6 +9123,14 @@ function renderHtml() {
8559
9123
  <div class="notice">\u5DF2\u63A5\u901A\uFF1A\u4FDD\u5B58\u914D\u7F6E\u3001\u540E\u53F0\u542F\u505C\u3001\u98DE\u4E66\u51ED\u636E\u6D4B\u8BD5\u3001\u5FAE\u4FE1\u626B\u7801\u3001Codex \u8FDE\u63A5\u6D4B\u8BD5\u3001\u684C\u9762\u4F1A\u8BDD\u53D1\u73B0\u3001IM \u7ED1\u5B9A\u67E5\u770B\u4E0E\u7F51\u9875\u4FA7\u5207\u6362\u3002</div>
8560
9124
  </div>
8561
9125
 
9126
+ <div class="panel-block">
9127
+ <p class="panel-subtitle">Bridge \u5F00\u673A\u81EA\u542F\u52A8</p>
9128
+ <div class="notice" id="autostartNotice">\u6B63\u5728\u68C0\u67E5\u5F53\u524D Windows \u4EFB\u52A1\u8BA1\u5212\u7A0B\u5E8F\u72B6\u6001\u2026</div>
9129
+ <div class="actions" style="margin-top: 12px;">
9130
+ <button id="refreshAutostartBtn">\u5237\u65B0\u5F00\u673A\u81EA\u542F\u52A8\u72B6\u6001</button>
9131
+ </div>
9132
+ </div>
9133
+
8562
9134
  <div class="panel-block">
8563
9135
  <p class="panel-subtitle">\u53EF\u9009 Codex Skill</p>
8564
9136
  <div class="notice">bridge \u4E0D\u518D\u6CE8\u5165\u53D1\u9001\u9644\u4EF6\u7684\u63D0\u793A\u8BCD\u3002\u9700\u8981\u8BA9 Codex \u77E5\u9053\u201C\u53EF\u4EE5\u628A\u672C\u5730\u56FE\u7247/\u6587\u4EF6\u56DE\u53D1\u5230 IM\u201D\u65F6\uFF0C\u8BF7\u5B89\u88C5\u8FD9\u4E2A\u53EF\u9009 skill\u3002</div>
@@ -8705,10 +9277,8 @@ function renderHtml() {
8705
9277
  </label>
8706
9278
  </div>
8707
9279
  <div class="small">\u672A\u7ED1\u5B9A\u7684 IM \u804A\u5929\u4F1A\u5148\u8FDB\u5165\u4E34\u65F6\u8349\u7A3F\u7EBF\u7A0B\uFF08\u7B49\u540C <code>/t 0</code>\uFF09\uFF1B\u201C\u9ED8\u8BA4\u5DE5\u4F5C\u7A7A\u95F4\u201D\u53EA\u7528\u4E8E <code>/new proj1</code> \u8FD9\u7C7B\u76F8\u5BF9\u9879\u76EE\u540D\u3002\u7559\u7A7A\u65F6\u4F1A\u6309\u5F53\u524D\u7CFB\u7EDF\u81EA\u52A8\u56DE\u9000\u5230 <code>~/cx2im</code>\u3002\u9ED8\u8BA4\u6A21\u578B\u5019\u9009\u9879\u6765\u81EA\u542F\u52A8\u65F6\u8BFB\u53D6\u7684 Codex \u6A21\u578B\u7F13\u5B58\uFF1A\u9690\u85CF\u6A21\u578B\u4E0D\u4F1A\u5C55\u793A\uFF0CCLI only \u6A21\u578B\u4F1A\u6807\u6210\u201C\u4EC5 IM / CLI\u201D\u3002\u7559\u7A7A\u5219\u7EE7\u7EED\u8DDF\u968F Codex \u5F53\u524D\u9ED8\u8BA4\u6A21\u578B\u3002\u6587\u4EF6\u7CFB\u7EDF\u6743\u9650\u662F\u5168\u5C40\u9ED8\u8BA4\u503C\uFF0C\u601D\u8003\u7EA7\u522B\u53EF\u5728 IM \u4F1A\u8BDD\u91CC\u518D\u5355\u72EC\u8986\u76D6\u3002</div>
8708
- <div class="small">\u5F53\u524D\u9700\u8981\u91CD\u542F Bridge \u7684\u914D\u7F6E\uFF1A<code>Runtime</code>\u3001<code>\u81EA\u52A8\u6279\u51C6\u5DE5\u5177\u6743\u9650</code>\u3001<code>\u5141\u8BB8\u5728\u672A\u4FE1\u4EFB Git \u76EE\u5F55\u8FD0\u884C Codex</code>\u3001\u98DE\u4E66 <code>App ID</code>/<code>App Secret</code>/<code>Domain</code>\u3002</div>
9280
+ <div class="small">\u5F53\u524D\u9700\u8981\u91CD\u542F Bridge \u7684\u914D\u7F6E\uFF1A<code>Runtime</code>\u3001<code>\u81EA\u52A8\u6279\u51C6\u5DE5\u5177\u6743\u9650</code>\u3001<code>\u5141\u8BB8\u5728\u672A\u4FE1\u4EFB Git \u76EE\u5F55\u8FD0\u884C Codex</code>\u3002\u901A\u9053\u5B9E\u4F8B\u7684\u63A5\u5165\u914D\u7F6E\u8BF7\u5728\u201C\u901A\u9053\u201D\u9875\u7EF4\u62A4\u3002</div>
8709
9281
  <div class="checkbox-row">
8710
- <label class="checkbox"><input id="channelFeishu" type="checkbox" checked /> \u542F\u7528\u98DE\u4E66</label>
8711
- <label class="checkbox"><input id="channelWeixin" type="checkbox" /> \u542F\u7528\u5FAE\u4FE1</label>
8712
9282
  <label class="checkbox"><input id="autoApprove" type="checkbox" /> \u81EA\u52A8\u6279\u51C6\u5DE5\u5177\u6743\u9650</label>
8713
9283
  </div>
8714
9284
  <div class="checkbox-row">
@@ -8813,112 +9383,35 @@ function renderHtml() {
8813
9383
  </div>
8814
9384
  </div>
8815
9385
 
8816
- <section class="panel channel-shell">
8817
- <div class="channel-tabs" role="tablist" aria-label="\u901A\u9053\u914D\u7F6E">
8818
- <button type="button" class="channel-tab active" data-channel="feishu" role="tab" aria-selected="true">\u98DE\u4E66</button>
8819
- <button type="button" class="channel-tab" data-channel="weixin" role="tab" aria-selected="false">\u5FAE\u4FE1</button>
8820
- </div>
8821
-
8822
- <div class="channel-view active" data-channel="feishu" role="tabpanel">
8823
- <section id="feishu">
8824
- <div class="panel-header">
8825
- <div>
8826
- <h2>\u98DE\u4E66 / Lark</h2>
8827
- <p>\u586B\u5199\u51ED\u636E\u3001\u6D4B\u8BD5\u53EF\u7528\u6027\uFF0C\u5E76\u67E5\u770B\u5F53\u524D\u98DE\u4E66\u804A\u5929\u7ED1\u5B9A\u5230\u54EA\u6761\u4F1A\u8BDD\u3002</p>
8828
- </div>
8829
- <div class="toolbar">
8830
- <button id="saveFeishuChannelBtn">\u4FDD\u5B58\u901A\u9053\u914D\u7F6E</button>
8831
- <button id="testFeishuBtn">\u6D4B\u8BD5\u98DE\u4E66\u51ED\u636E</button>
8832
- <button id="refreshFeishuStateBtn">\u5237\u65B0\u72B6\u6001</button>
8833
- </div>
8834
- </div>
8835
-
8836
- <div class="fields">
8837
- <div class="field-row">
8838
- <label>
8839
- App ID
8840
- <input id="feishuAppId" />
8841
- </label>
8842
- <label>
8843
- App Secret
8844
- <input id="feishuAppSecret" />
8845
- </label>
8846
- </div>
8847
- <div class="field-row">
8848
- <label>
8849
- Domain
8850
- <input id="feishuDomain" value="https://open.feishu.cn" />
8851
- </label>
8852
- <label>
8853
- Allowed Users
8854
- <input id="feishuAllowedUsers" placeholder="\u591A\u4E2A user_id \u7528\u9017\u53F7\u5206\u9694" />
8855
- </label>
8856
- </div>
8857
- <div class="checkbox-row">
8858
- <label class="checkbox"><input id="feishuStreamingEnabled" type="checkbox" checked /> \u542F\u7528\u98DE\u4E66\u6D41\u5F0F\u54CD\u5E94\u5361\u7247</label>
8859
- </div>
8860
- <div class="checkbox-row">
8861
- <label class="checkbox"><input id="feishuCommandMarkdownEnabled" type="checkbox" checked /> \u53CD\u9988\u4F7F\u7528markdown</label>
8862
- </div>
8863
- <div class="small">\u9700\u8981\u98DE\u4E66\u4FA7\u5DF2\u5F00\u901A\u53EF\u66F4\u65B0\u5361\u7247\u7684\u76F8\u5173\u80FD\u529B\uFF1B\u5982\u679C\u6743\u9650\u4E0D\u8DB3\uFF0C\u4F1A\u81EA\u52A8\u56DE\u9000\u4E3A\u6700\u7EC8\u7ED3\u679C\u6D88\u606F\u3002</div>
8864
- <div class="small">\u5F71\u54CD\u901A\u8FC7 bridge \u53D1\u9001\u5230\u98DE\u4E66\u7684\u6587\u672C\u53CD\u9988\uFF0C\u5305\u62EC\u666E\u901A\u56DE\u590D\u3001\u5171\u4EAB\u684C\u9762\u7EBF\u7A0B\u955C\u50CF\u548C <code>/h</code>\u3001<code>/status</code>\u3001<code>/threads</code> \u8FD9\u7C7B\u7CFB\u7EDF\u53CD\u9988\u3002</div>
8865
- <div class="small">\u4FEE\u6539\u98DE\u4E66 <code>App ID</code>\u3001<code>App Secret</code>\u3001<code>Domain</code> \u540E\uFF0C\u9700\u8981\u91CD\u542F Bridge \u8BA9\u5BA2\u6237\u7AEF\u91CD\u65B0\u521D\u59CB\u5316\uFF1B\u767D\u540D\u5355\u3001\u6D41\u5F0F\u5F00\u5173\u3001markdown \u5F00\u5173\u4F1A\u5373\u65F6\u751F\u6548\u3002</div>
8866
- </div>
8867
-
8868
- <div class="panel-block">
8869
- <p class="panel-subtitle">\u901A\u9053\u72B6\u6001</p>
8870
- <div class="small" id="feishuRuntimeMeta">\u6B63\u5728\u52A0\u8F7D\u2026</div>
9386
+ <section class="panel channel-workspace">
9387
+ <div class="panel-header">
9388
+ <div>
9389
+ <h2>\u901A\u9053\u5B9E\u4F8B</h2>
9390
+ <p>\u8FD9\u91CC\u7BA1\u7406\u591A\u4E2A\u98DE\u4E66\u6216\u5FAE\u4FE1\u673A\u5668\u4EBA\u5B9E\u4F8B\u3002\u5B9E\u4F8B\u53EA\u662F\u4E0D\u540C\u804A\u5929\u5165\u53E3\uFF0C\u4E0D\u4F1A\u6539\u53D8 Codex \u7684\u4F1A\u8BDD\u8BED\u4E49\u3002</p>
8871
9391
  </div>
8872
- <div class="panel-block">
8873
- <p class="panel-subtitle">\u5F53\u524D\u98DE\u4E66\u7ED1\u5B9A</p>
8874
- <div class="small" id="feishuBindingMeta">\u6B63\u5728\u52A0\u8F7D\u2026</div>
8875
- <div class="binding-list" id="feishuBindings" style="margin-top: 12px;"></div>
9392
+ <div class="toolbar">
9393
+ <label class="inline-select">
9394
+ \u65B0\u901A\u9053
9395
+ <select id="newChannelProvider">
9396
+ <option value="feishu">\u98DE\u4E66</option>
9397
+ <option value="weixin">\u5FAE\u4FE1</option>
9398
+ </select>
9399
+ </label>
9400
+ <button id="createChannelBtn">\u65B0\u589E\u901A\u9053</button>
9401
+ <button id="refreshChannelsBtn">\u5237\u65B0\u72B6\u6001</button>
8876
9402
  </div>
8877
-
8878
- <div class="message" id="feishuMessage"></div>
8879
- </section>
8880
9403
  </div>
8881
9404
 
8882
- <div class="channel-view" data-channel="weixin" role="tabpanel">
8883
- <section id="wechat">
8884
- <div class="panel-header">
8885
- <div>
8886
- <h2>\u5FAE\u4FE1</h2>
8887
- <p>\u626B\u7801\u767B\u5F55\u5FAE\u4FE1\u5E76\u67E5\u770B\u5F53\u524D\u804A\u5929\u7ED1\u5B9A\u7684\u4F1A\u8BDD\u3002</p>
8888
- </div>
8889
- <div class="toolbar">
8890
- <button id="saveWeixinChannelBtn">\u4FDD\u5B58\u901A\u9053\u914D\u7F6E</button>
8891
- <button id="weixinLoginBtn">\u5F00\u59CB\u5FAE\u4FE1\u626B\u7801</button>
8892
- <button id="refreshWeixinStateBtn">\u5237\u65B0\u72B6\u6001</button>
8893
- </div>
8894
- </div>
8895
-
8896
- <div class="fields">
8897
- <div class="checkbox-row">
8898
- <label class="checkbox"><input id="weixinMediaEnabled" type="checkbox" /> \u542F\u7528\u56FE\u7247 / \u6587\u4EF6 / \u89C6\u9891\u5165\u7AD9\u4E0B\u8F7D</label>
8899
- </div>
8900
- <div class="checkbox-row">
8901
- <label class="checkbox"><input id="weixinCommandMarkdownEnabled" type="checkbox" /> \u53CD\u9988\u4F7F\u7528markdown</label>
8902
- </div>
8903
- </div>
8904
- <div class="small">\u5F71\u54CD\u901A\u8FC7 bridge \u53D1\u9001\u5230\u5FAE\u4FE1\u7684\u6587\u672C\u53CD\u9988\uFF0C\u5305\u62EC\u666E\u901A\u56DE\u590D\u3001\u5171\u4EAB\u684C\u9762\u7EBF\u7A0B\u955C\u50CF\u548C <code>/h</code>\u3001<code>/status</code>\u3001<code>/threads</code> \u8FD9\u7C7B\u7CFB\u7EDF\u53CD\u9988\u3002\u9ED8\u8BA4\u5173\u95ED\u3002</div>
8905
- <div class="panel-block">
8906
- <p class="panel-subtitle">\u901A\u9053\u72B6\u6001</p>
8907
- <div class="small" id="weixinRuntimeMeta">\u6B63\u5728\u52A0\u8F7D\u2026</div>
8908
- </div>
8909
- <div class="panel-block">
8910
- <p class="panel-subtitle">\u5DF2\u767B\u5F55\u5FAE\u4FE1\u8D26\u53F7</p>
8911
- <div class="small" id="weixinAccountMeta">\u6B63\u5728\u52A0\u8F7D\u2026</div>
8912
- <div class="binding-list" id="weixinAccounts" style="margin-top: 12px;"></div>
8913
- </div>
8914
- <div class="panel-block">
8915
- <p class="panel-subtitle">\u5F53\u524D\u5FAE\u4FE1\u7ED1\u5B9A</p>
8916
- <div class="small" id="weixinBindingMeta">\u6B63\u5728\u52A0\u8F7D\u2026</div>
8917
- <div class="binding-list" id="weixinBindings" style="margin-top: 12px;"></div>
8918
- </div>
8919
- <div class="message" id="weixinMessage"></div>
9405
+ <div class="channel-layout">
9406
+ <aside class="channel-sidebar">
9407
+ <div class="channel-sidebar-meta" id="channelListMeta">\u6B63\u5728\u52A0\u8F7D\u2026</div>
9408
+ <div class="channel-list" id="channelList"></div>
9409
+ </aside>
9410
+ <section class="channel-editor" id="channelEditor">
9411
+ <div class="binding-empty">\u6B63\u5728\u52A0\u8F7D\u901A\u9053\u914D\u7F6E\u2026</div>
8920
9412
  </section>
8921
9413
  </div>
9414
+ <div class="message" id="channelMessage"></div>
8922
9415
  </section>
8923
9416
  </section>
8924
9417
 
@@ -8947,17 +9440,16 @@ function renderHtml() {
8947
9440
  availableModels: [],
8948
9441
  uiAccess: null,
8949
9442
  bridgeStatus: null,
9443
+ autostartStatus: null,
8950
9444
  desktopSessions: [],
8951
9445
  bindings: [],
8952
9446
  bindingOptions: [],
8953
- activeBindingByChannel: {
8954
- feishu: '',
8955
- weixin: '',
8956
- },
9447
+ activeBindingByChannelId: {},
8957
9448
  weixinAccounts: [],
8958
9449
  desktopRoot: '',
8959
9450
  activePage: 'overview',
8960
- activeChannel: 'feishu',
9451
+ activeChannelId: '',
9452
+ channelDraft: null,
8961
9453
  };
8962
9454
 
8963
9455
  function escapeHtml(value) {
@@ -9068,16 +9560,6 @@ function renderHtml() {
9068
9560
  return binding.chatDisplayName || binding.chatId || binding.id;
9069
9561
  }
9070
9562
 
9071
- function ensureActiveBinding(channelType, bindings) {
9072
- const current = state.activeBindingByChannel[channelType];
9073
- if (current && bindings.some((binding) => binding.id === current)) {
9074
- return current;
9075
- }
9076
- const next = bindings[0] ? bindings[0].id : '';
9077
- state.activeBindingByChannel[channelType] = next;
9078
- return next;
9079
- }
9080
-
9081
9563
  function renderBindingCard(binding) {
9082
9564
  return ''
9083
9565
  + '<article class="binding-item" data-binding-id="' + escapeHtml(binding.id) + '">'
@@ -9099,13 +9581,6 @@ function renderHtml() {
9099
9581
  + '</article>';
9100
9582
  }
9101
9583
 
9102
- function enabledChannelsFromForm() {
9103
- const channels = [];
9104
- if (document.getElementById('channelFeishu').checked) channels.push('feishu');
9105
- if (document.getElementById('channelWeixin').checked) channels.push('weixin');
9106
- return channels;
9107
- }
9108
-
9109
9584
  function formPayload() {
9110
9585
  return {
9111
9586
  runtime: document.getElementById('runtime').value,
@@ -9118,16 +9593,7 @@ function renderHtml() {
9118
9593
  codexReasoningEffort: document.getElementById('codexReasoningEffort').value,
9119
9594
  uiAllowLan: document.getElementById('uiAllowLan').checked,
9120
9595
  uiAccessToken: document.getElementById('uiAccessToken').value,
9121
- enabledChannels: enabledChannelsFromForm(),
9122
9596
  autoApprove: document.getElementById('autoApprove').checked,
9123
- feishuAppId: document.getElementById('feishuAppId').value,
9124
- feishuAppSecret: document.getElementById('feishuAppSecret').value,
9125
- feishuDomain: document.getElementById('feishuDomain').value,
9126
- feishuAllowedUsers: document.getElementById('feishuAllowedUsers').value,
9127
- feishuStreamingEnabled: document.getElementById('feishuStreamingEnabled').checked,
9128
- feishuCommandMarkdownEnabled: document.getElementById('feishuCommandMarkdownEnabled').checked,
9129
- weixinMediaEnabled: document.getElementById('weixinMediaEnabled').checked,
9130
- weixinCommandMarkdownEnabled: document.getElementById('weixinCommandMarkdownEnabled').checked,
9131
9597
  };
9132
9598
  }
9133
9599
 
@@ -9179,7 +9645,7 @@ function renderHtml() {
9179
9645
 
9180
9646
  if (syncHash !== false) {
9181
9647
  const hash = nextPage === 'channels'
9182
- ? '#channels/' + state.activeChannel
9648
+ ? '#channels/' + (state.activeChannelId || '')
9183
9649
  : '#' + nextPage;
9184
9650
  if (window.location.hash !== hash) {
9185
9651
  history.replaceState(null, '', hash);
@@ -9187,26 +9653,12 @@ function renderHtml() {
9187
9653
  }
9188
9654
  }
9189
9655
 
9190
- function setActiveChannel(channel, syncHash) {
9191
- const nextChannel = channel === 'weixin' ? 'weixin' : 'feishu';
9192
- state.activeChannel = nextChannel;
9193
-
9194
- document.querySelectorAll('.channel-tab').forEach((element) => {
9195
- const node = element;
9196
- const active = node.dataset.channel === nextChannel;
9197
- node.classList.toggle('active', active);
9198
- node.setAttribute('aria-selected', active ? 'true' : 'false');
9199
- });
9200
-
9201
- document.querySelectorAll('.channel-view').forEach((element) => {
9202
- const node = element;
9203
- const active = node.dataset.channel === nextChannel;
9204
- node.classList.toggle('active', active);
9205
- node.hidden = !active;
9206
- });
9656
+ function setActiveChannel(channelId, syncHash) {
9657
+ state.activeChannelId = channelId || '';
9658
+ renderChannelsWorkspace();
9207
9659
 
9208
9660
  if (syncHash !== false && state.activePage === 'channels') {
9209
- const hash = '#channels/' + nextChannel;
9661
+ const hash = '#channels/' + (state.activeChannelId || '');
9210
9662
  if (window.location.hash !== hash) {
9211
9663
  history.replaceState(null, '', hash);
9212
9664
  }
@@ -9217,13 +9669,12 @@ function renderHtml() {
9217
9669
  const raw = String(window.location.hash || '').replace(/^#/, '');
9218
9670
  if (!raw) {
9219
9671
  setActivePage('overview', false);
9220
- setActiveChannel('feishu', false);
9221
9672
  return;
9222
9673
  }
9223
9674
 
9224
9675
  if (raw.startsWith('channels/')) {
9225
9676
  setActivePage('channels', false);
9226
- setActiveChannel(raw.split('/')[1] || 'feishu', false);
9677
+ setActiveChannel(raw.split('/')[1] || '', false);
9227
9678
  return;
9228
9679
  }
9229
9680
 
@@ -9270,21 +9721,44 @@ function renderHtml() {
9270
9721
  });
9271
9722
  }
9272
9723
 
9273
- function isChannelEnabled(channelType) {
9274
- return Boolean(state.config && (state.config.enabledChannels || []).includes(channelType));
9724
+ function providerLabel(provider) {
9725
+ if (provider === 'weixin') return '\u5FAE\u4FE1';
9726
+ if (provider === 'feishu') return '\u98DE\u4E66';
9727
+ return '\u901A\u9053';
9275
9728
  }
9276
9729
 
9277
- function runningChannels() {
9278
- return state.bridgeStatus && Array.isArray(state.bridgeStatus.channels) ? state.bridgeStatus.channels : [];
9730
+ function configuredChannels() {
9731
+ return Array.isArray(state.config && state.config.channels) ? state.config.channels : [];
9279
9732
  }
9280
9733
 
9281
- function isChannelRunning(channelType) {
9282
- return Boolean(state.bridgeStatus && state.bridgeStatus.running && runningChannels().includes(channelType));
9734
+ function visibleChannels() {
9735
+ const channels = configuredChannels().slice();
9736
+ if (state.channelDraft) {
9737
+ channels.push(state.channelDraft);
9738
+ }
9739
+ return channels;
9740
+ }
9741
+
9742
+ function getChannelById(channelId) {
9743
+ if (state.channelDraft && state.channelDraft.id === channelId) return state.channelDraft;
9744
+ return configuredChannels().find((channel) => channel.id === channelId) || null;
9745
+ }
9746
+
9747
+ function adapterStatuses() {
9748
+ return state.bridgeStatus && Array.isArray(state.bridgeStatus.adapters) ? state.bridgeStatus.adapters : [];
9749
+ }
9750
+
9751
+ function getAdapterStatus(channelId) {
9752
+ return adapterStatuses().find((item) => item.channelType === channelId) || null;
9753
+ }
9754
+
9755
+ function isChannelRunning(channelId) {
9756
+ const status = getAdapterStatus(channelId);
9757
+ return Boolean(state.bridgeStatus && state.bridgeStatus.running && status && status.running);
9283
9758
  }
9284
9759
 
9285
9760
  const CONFIG_FIELD_LABELS = {
9286
9761
  runtime: 'Runtime',
9287
- enabledChannels: '\u901A\u9053\u542F\u7528\u72B6\u6001',
9288
9762
  defaultWorkspaceRoot: '\u9ED8\u8BA4\u5DE5\u4F5C\u7A7A\u95F4',
9289
9763
  defaultModel: '\u9ED8\u8BA4\u6A21\u578B',
9290
9764
  defaultMode: '\u9ED8\u8BA4\u6A21\u5F0F',
@@ -9295,28 +9769,15 @@ function renderHtml() {
9295
9769
  uiAllowLan: '\u5141\u8BB8\u5C40\u57DF\u7F51\u8BBF\u95EE Web \u63A7\u5236\u53F0',
9296
9770
  uiAccessToken: '\u5C40\u57DF\u7F51\u8BBF\u95EE token',
9297
9771
  autoApprove: '\u81EA\u52A8\u6279\u51C6\u5DE5\u5177\u6743\u9650',
9298
- feishuAppId: '\u98DE\u4E66 App ID',
9299
- feishuAppSecret: '\u98DE\u4E66 App Secret',
9300
- feishuDomain: '\u98DE\u4E66 Domain',
9301
- feishuAllowedUsers: '\u98DE\u4E66 Allowed Users',
9302
- feishuStreamingEnabled: '\u98DE\u4E66\u6D41\u5F0F\u54CD\u5E94\u5361\u7247',
9303
- feishuCommandMarkdownEnabled: '\u98DE\u4E66\u53CD\u9988markdown',
9304
- weixinMediaEnabled: '\u5FAE\u4FE1\u56FE\u7247/\u6587\u4EF6/\u89C6\u9891\u5165\u7AD9\u4E0B\u8F7D',
9305
- weixinCommandMarkdownEnabled: '\u5FAE\u4FE1\u53CD\u9988markdown',
9306
9772
  };
9307
9773
 
9308
9774
  const BRIDGE_RESTART_FIELDS = new Set([
9309
9775
  'runtime',
9310
9776
  'codexSkipGitRepoCheck',
9311
9777
  'autoApprove',
9312
- 'feishuAppId',
9313
- 'feishuAppSecret',
9314
- 'feishuDomain',
9315
9778
  ]);
9316
9779
 
9317
- const AUTO_SYNC_FIELDS = new Set([
9318
- 'enabledChannels',
9319
- ]);
9780
+ const AUTO_SYNC_FIELDS = new Set([]);
9320
9781
 
9321
9782
  const IMMEDIATE_FIELDS = new Set([
9322
9783
  'defaultWorkspaceRoot',
@@ -9327,29 +9788,12 @@ function renderHtml() {
9327
9788
  'codexReasoningEffort',
9328
9789
  'uiAllowLan',
9329
9790
  'uiAccessToken',
9330
- 'feishuAllowedUsers',
9331
- 'feishuStreamingEnabled',
9332
- 'feishuCommandMarkdownEnabled',
9333
- 'weixinMediaEnabled',
9334
- 'weixinCommandMarkdownEnabled',
9335
9791
  ]);
9336
9792
 
9337
9793
  const SAVE_SCOPE_FIELDS = {
9338
9794
  all: null,
9339
- feishu: new Set([
9340
- 'enabledChannels',
9341
- 'feishuAppId',
9342
- 'feishuAppSecret',
9343
- 'feishuDomain',
9344
- 'feishuAllowedUsers',
9345
- 'feishuStreamingEnabled',
9346
- 'feishuCommandMarkdownEnabled',
9347
- ]),
9348
- weixin: new Set([
9349
- 'enabledChannels',
9350
- 'weixinMediaEnabled',
9351
- 'weixinCommandMarkdownEnabled',
9352
- ]),
9795
+ feishu: new Set([]),
9796
+ weixin: new Set([]),
9353
9797
  };
9354
9798
 
9355
9799
  function normalizeConfigValue(value) {
@@ -9412,63 +9856,40 @@ function renderHtml() {
9412
9856
  return '\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002' + (notes.length > 0 ? ' ' + notes.join('\uFF1B') + '\u3002' : '');
9413
9857
  }
9414
9858
 
9415
- function channelLabel(channelType) {
9416
- return channelType === 'weixin' ? '\u5FAE\u4FE1' : '\u98DE\u4E66';
9859
+ function channelDisplayLabel(channel) {
9860
+ const alias = String(channel.alias || '').trim();
9861
+ const provider = providerLabel(channel.provider);
9862
+ if (!alias) return provider;
9863
+ return alias === provider ? alias : alias + ' \xB7 ' + provider;
9417
9864
  }
9418
9865
 
9419
- function channelRuntimeText(channelType) {
9420
- const label = channelLabel(channelType);
9421
- if (!isChannelEnabled(channelType)) {
9422
- return label + '\u5728\u914D\u7F6E\u4E2D\u672A\u542F\u7528\u3002';
9866
+ function formatChannelRuntimeLabel(channel) {
9867
+ const label = channelDisplayLabel(channel);
9868
+ if (channel.enabled === false) {
9869
+ return label + '\u5DF2\u505C\u7528\u3002';
9423
9870
  }
9424
9871
  if (!state.bridgeStatus || !state.bridgeStatus.running) {
9425
- return label + '\u5DF2\u542F\u7528\uFF0C\u4F46 Bridge \u8FD8\u6CA1\u542F\u52A8\u3002\u542F\u52A8 Bridge \u540E\u624D\u4F1A\u771F\u6B63\u63A5\u901A\u3002';
9872
+ return label + '\u5DF2\u914D\u7F6E\uFF0C\u4F46 Bridge \u8FD8\u6CA1\u542F\u52A8\u3002';
9426
9873
  }
9427
- if (!isChannelRunning(channelType)) {
9428
- return label + '\u5DF2\u5199\u5165\u914D\u7F6E\uFF0CBridge \u4F1A\u5728\u51E0\u79D2\u5185\u81EA\u52A8\u540C\u6B65\u8FD9\u4E2A\u901A\u9053\uFF1B\u5982\u679C\u9875\u9762\u8FD8\u6CA1\u66F4\u65B0\uFF0C\u53EF\u624B\u52A8\u70B9\u201C\u5237\u65B0\u72B6\u6001\u201D\u3002';
9874
+ const status = getAdapterStatus(channel.id);
9875
+ if (!status || !status.running) {
9876
+ return label + '\u5DF2\u4FDD\u5B58\uFF0CBridge \u4F1A\u5728\u51E0\u79D2\u5185\u81EA\u52A8\u540C\u6B65\u3002';
9429
9877
  }
9430
9878
  return label + '\u5DF2\u63A5\u901A\u5230\u5F53\u524D\u8FD0\u884C\u4E2D\u7684 Bridge\u3002';
9431
9879
  }
9432
9880
 
9433
- function emptyBindingText(channelType) {
9434
- const label = channelLabel(channelType);
9435
- if (!isChannelEnabled(channelType)) {
9436
- return label + '\u672A\u542F\u7528\u3002\u5148\u5728\u201C\u914D\u7F6E\u201D\u91CC\u52FE\u9009\u540E\u4FDD\u5B58\u3002';
9437
- }
9438
- if (!state.bridgeStatus || !state.bridgeStatus.running) {
9439
- return label + '\u5DF2\u542F\u7528\uFF0C\u4F46 Bridge \u8FD8\u6CA1\u542F\u52A8\u3002\u542F\u52A8\u540E\u624D\u4F1A\u521B\u5EFA\u7ED1\u5B9A\u3002';
9440
- }
9441
- if (!isChannelRunning(channelType)) {
9442
- return label + '\u5DF2\u542F\u7528\uFF0CBridge \u4F1A\u5728\u51E0\u79D2\u5185\u81EA\u52A8\u540C\u6B65\u8FD9\u4E2A\u901A\u9053\uFF1B\u5982\u679C\u9875\u9762\u8FD8\u6CA1\u66F4\u65B0\uFF0C\u53EF\u624B\u52A8\u70B9\u201C\u5237\u65B0\u72B6\u6001\u201D\u3002';
9443
- }
9444
- return label + '\u5F53\u524D\u8FD8\u6CA1\u6709\u804A\u5929\u63A5\u5165\u3002\u5148\u4ECE' + label + '\u53D1\u4E00\u6761\u6D88\u606F\uFF0Cbridge \u624D\u4F1A\u521B\u5EFA\u7ED1\u5B9A\u3002';
9445
- }
9446
-
9447
- function quickSwitchState(channelType) {
9448
- const label = channelLabel(channelType);
9449
- if (!isChannelEnabled(channelType)) {
9450
- return { disabled: true, title: label + '\u672A\u542F\u7528\u3002' };
9881
+ function emptyBindingText(channel) {
9882
+ const label = channelDisplayLabel(channel);
9883
+ if (channel.enabled === false) {
9884
+ return label + '\u5DF2\u505C\u7528\u3002\u542F\u7528\u540E\u624D\u4F1A\u521B\u5EFA\u804A\u5929\u7ED1\u5B9A\u3002';
9451
9885
  }
9452
9886
  if (!state.bridgeStatus || !state.bridgeStatus.running) {
9453
- return { disabled: true, title: 'Bridge \u8FD8\u6CA1\u542F\u52A8\u3002\u542F\u52A8\u540E\u518D\u5207\u6362' + label + '\u4F1A\u8BDD\u3002' };
9887
+ return label + '\u5DF2\u914D\u7F6E\uFF0C\u4F46 Bridge \u8FD8\u6CA1\u542F\u52A8\u3002\u542F\u52A8\u540E\u624D\u4F1A\u521B\u5EFA\u7ED1\u5B9A\u3002';
9454
9888
  }
9455
- if (!isChannelRunning(channelType)) {
9456
- return { disabled: true, title: label + '\u5DF2\u5199\u5165\u914D\u7F6E\uFF0CBridge \u4F1A\u5728\u51E0\u79D2\u5185\u81EA\u52A8\u540C\u6B65\uFF1B\u540C\u6B65\u5B8C\u6210\u540E\u518D\u5207\u6362\u4F1A\u8BDD\u3002' };
9889
+ if (!isChannelRunning(channel.id)) {
9890
+ return label + '\u5DF2\u914D\u7F6E\uFF0CBridge \u4F1A\u5728\u51E0\u79D2\u5185\u81EA\u52A8\u540C\u6B65\uFF1B\u5982\u679C\u9875\u9762\u8FD8\u6CA1\u66F4\u65B0\uFF0C\u53EF\u624B\u52A8\u70B9\u201C\u5237\u65B0\u72B6\u6001\u201D\u3002';
9457
9891
  }
9458
-
9459
- const bindings = state.bindings.filter((item) => item.channelType === channelType);
9460
- if (bindings.length === 0) {
9461
- return { disabled: true, title: '\u5F53\u524D\u8FD8\u6CA1\u6709' + label + '\u804A\u5929\u7ED1\u5B9A\u3002\u5148\u8BA9' + label + '\u53D1\u6765\u4E00\u6761\u6D88\u606F\u3002' };
9462
- }
9463
- if (bindings.length > 1) {
9464
- return { disabled: true, title: label + '\u6709\u591A\u4E2A\u7ED1\u5B9A\uFF0C\u8BF7\u5230\u901A\u9053\u9875\u5207\u6362\u3002' };
9465
- }
9466
-
9467
- return {
9468
- disabled: false,
9469
- title: '\u5207\u6362' + label + '\u5230\u5F53\u524D\u4F1A\u8BDD',
9470
- bindingId: bindings[0].id,
9471
- };
9892
+ return label + '\u5F53\u524D\u8FD8\u6CA1\u6709\u804A\u5929\u63A5\u5165\u3002\u5148\u4ECE\u8FD9\u4E2A\u673A\u5668\u4EBA\u53D1\u4E00\u6761\u6D88\u606F\u3002';
9472
9893
  }
9473
9894
 
9474
9895
  function currentThreadMarks(threadId) {
@@ -9479,11 +9900,11 @@ function renderHtml() {
9479
9900
  for (const binding of state.bindings || []) {
9480
9901
  const matchesThread = binding.currentThreadId === threadId || binding.currentTargetKey === currentTargetKey;
9481
9902
  if (!matchesThread) continue;
9482
- counts.set(binding.channelType, (counts.get(binding.channelType) || 0) + 1);
9903
+ const label = (binding.channelAlias || providerLabel(binding.channelProvider)) + ' \u5F53\u524D';
9904
+ counts.set(label, (counts.get(label) || 0) + 1);
9483
9905
  }
9484
9906
 
9485
- for (const [channelType, count] of counts.entries()) {
9486
- const label = channelType === 'weixin' ? '\u5FAE\u4FE1\u5F53\u524D' : '\u98DE\u4E66\u5F53\u524D';
9907
+ for (const [label, count] of counts.entries()) {
9487
9908
  marks.push(count > 1 ? label + ' x' + count : label);
9488
9909
  }
9489
9910
 
@@ -9505,7 +9926,10 @@ function renderHtml() {
9505
9926
  }
9506
9927
 
9507
9928
  function formatBindingAccount(binding) {
9508
- return channelLabel(binding.channelType) + ' \xB7 ' + (binding.chatDisplayName || binding.chatId);
9929
+ const alias = String(binding.channelAlias || '').trim();
9930
+ const provider = providerLabel(binding.channelProvider);
9931
+ const channel = alias ? (alias === provider ? alias : alias + ' \xB7 ' + provider) : provider;
9932
+ return channel + ' \xB7 ' + (binding.chatDisplayName || binding.chatId);
9509
9933
  }
9510
9934
 
9511
9935
  function bindingRuntimeText(binding) {
@@ -9533,9 +9957,6 @@ function renderHtml() {
9533
9957
  }
9534
9958
 
9535
9959
  function renderDesktopSessionCard(session) {
9536
- const feishuSwitch = quickSwitchState('feishu');
9537
- const weixinSwitch = quickSwitchState('weixin');
9538
- const targetKey = 'desktop:' + session.threadId;
9539
9960
  const originator = session.originator || 'Codex Desktop';
9540
9961
  const marks = currentThreadMarks(session.threadId);
9541
9962
  const markHtml = marks.map((mark) => '<span class="session-mark">' + escapeHtml(mark) + '</span>').join('');
@@ -9556,8 +9977,7 @@ function renderHtml() {
9556
9977
  + '<div class="session-path">' + escapeHtml(session.cwd || '(no cwd)') + '</div>'
9557
9978
  + '</div>'
9558
9979
  + '<div class="session-actions">'
9559
- + '<button type="button" data-action="bind-channel" data-channel="feishu" data-binding-id="' + escapeHtml(feishuSwitch.bindingId || '') + '" data-target-key="' + escapeHtml(targetKey) + '" title="' + escapeHtml(feishuSwitch.title) + '"' + (feishuSwitch.disabled ? ' disabled' : '') + '>\u98DE\u4E66\u5207\u5230\u6B64\u4F1A\u8BDD</button>'
9560
- + '<button type="button" data-action="bind-channel" data-channel="weixin" data-binding-id="' + escapeHtml(weixinSwitch.bindingId || '') + '" data-target-key="' + escapeHtml(targetKey) + '" title="' + escapeHtml(weixinSwitch.title) + '"' + (weixinSwitch.disabled ? ' disabled' : '') + '>\u5FAE\u4FE1\u5207\u5230\u6B64\u4F1A\u8BDD</button>'
9980
+ + '<button type="button" data-action="copy-thread" data-thread-id="' + escapeHtml(session.threadId) + '">\u590D\u5236 thread</button>'
9561
9981
  + '<button type="button" data-action="copy-bind-command" data-thread-id="' + escapeHtml(session.threadId) + '">\u590D\u5236\u547D\u4EE4</button>'
9562
9982
  + '</div>'
9563
9983
  + '</div>'
@@ -9642,14 +10062,14 @@ function renderHtml() {
9642
10062
  allMeta.textContent = '\u6309\u6700\u8FD1\u6D3B\u52A8\u6392\u5E8F\uFF0C\u5171 ' + sessions.length + ' \u6761\u3002';
9643
10063
 
9644
10064
  if (boundSessions.length === 0) {
9645
- boundList.innerHTML = '<div class="binding-empty">\u5F53\u524D\u6CA1\u6709\u4EFB\u4F55\u684C\u9762\u4F1A\u8BDD\u6B63\u5728\u7ED1\u5B9A\u5230\u98DE\u4E66\u6216\u5FAE\u4FE1\u804A\u5929\u3002</div>';
10065
+ boundList.innerHTML = '<div class="binding-empty">\u5F53\u524D\u6CA1\u6709\u4EFB\u4F55\u684C\u9762\u4F1A\u8BDD\u6B63\u5728\u7ED1\u5B9A\u5230\u804A\u5929\u5165\u53E3\u3002</div>';
9646
10066
  } else {
9647
10067
  boundList.innerHTML = boundSessions.map((session) => renderBoundDesktopSessionCard(session)).join('');
9648
10068
  }
9649
10069
 
9650
10070
  if (state.desktopSessions.length === 0) {
9651
10071
  list.innerHTML = '<div class="notice ghost">\u5F53\u524D\u6CA1\u6709\u53D1\u73B0\u684C\u9762\u7AEF\u4F1A\u8BDD\u3002\u5148\u5728 Codex Desktop App \u4E2D\u6253\u5F00\u6216\u8FD0\u884C\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u518D\u56DE\u5230\u8FD9\u91CC\u5237\u65B0\u3002</div>';
9652
- rerenderBindingPanels();
10072
+ renderChannelsWorkspace();
9653
10073
  return;
9654
10074
  }
9655
10075
 
@@ -9657,7 +10077,7 @@ function renderHtml() {
9657
10077
  + sessions.map((session) => renderDesktopSessionListItem(session)).join('')
9658
10078
  + '</div>';
9659
10079
 
9660
- rerenderBindingPanels();
10080
+ renderChannelsWorkspace();
9661
10081
  }
9662
10082
 
9663
10083
  function rerenderDesktopSessions() {
@@ -9668,42 +10088,91 @@ function renderHtml() {
9668
10088
  });
9669
10089
  }
9670
10090
 
9671
- function rerenderBindingPanels() {
9672
- renderChannelBindings(
9673
- 'feishu',
9674
- 'feishuBindings',
9675
- 'feishuBindingMeta',
9676
- emptyBindingText('feishu')
9677
- );
9678
- renderChannelBindings(
9679
- 'weixin',
9680
- 'weixinBindings',
9681
- 'weixinBindingMeta',
9682
- emptyBindingText('weixin')
9683
- );
10091
+ function bindingsForChannel(channelId) {
10092
+ return (state.bindings || []).filter((item) => item.channelType === channelId);
9684
10093
  }
9685
10094
 
9686
- function renderChannelBindings(channelType, listId, metaId, emptyText) {
9687
- const bindings = state.bindings.filter((item) => item.channelType === channelType);
9688
- const list = document.getElementById(listId);
9689
- const meta = document.getElementById(metaId);
9690
- meta.textContent = bindings.length > 0
9691
- ? '\u5F53\u524D\u5DF2\u53D1\u73B0 ' + bindings.length + ' \u4E2A\u804A\u5929\u7ED1\u5B9A\u3002\u4E00\u4E2A\u4F1A\u8BDD\u53EA\u80FD\u7ED1\u5B9A\u4E00\u4E2A\u804A\u5929\u3002\u8FD9\u91CC\u53EA\u663E\u793A\u548C\u4F1A\u8BDD\u9875\u4E00\u81F4\u7684\u547D\u540D\u684C\u9762\u7EBF\u7A0B\u3002'
9692
- : emptyText;
10095
+ function ensureActiveBinding(channelId, bindings) {
10096
+ const current = state.activeBindingByChannelId[channelId];
10097
+ if (current && bindings.some((binding) => binding.id === current)) {
10098
+ return current;
10099
+ }
10100
+ const next = bindings[0] ? bindings[0].id : '';
10101
+ state.activeBindingByChannelId[channelId] = next;
10102
+ return next;
10103
+ }
9693
10104
 
9694
- if (bindings.length === 0) {
9695
- state.activeBindingByChannel[channelType] = '';
9696
- list.innerHTML = '<div class="binding-empty">' + escapeHtml(emptyText) + '</div>';
10105
+ function ensureActiveChannelId() {
10106
+ if (state.channelDraft && state.activeChannelId === state.channelDraft.id) {
10107
+ return state.activeChannelId;
10108
+ }
10109
+ const channels = visibleChannels();
10110
+ if (state.activeChannelId && channels.some((channel) => channel.id === state.activeChannelId)) {
10111
+ return state.activeChannelId;
10112
+ }
10113
+ state.activeChannelId = channels[0] ? channels[0].id : '';
10114
+ return state.activeChannelId;
10115
+ }
10116
+
10117
+ function getWeixinAccountOptions() {
10118
+ return Array.isArray(state.weixinAccounts) ? state.weixinAccounts : [];
10119
+ }
10120
+
10121
+ function renderChannelList() {
10122
+ const list = document.getElementById('channelList');
10123
+ const meta = document.getElementById('channelListMeta');
10124
+ const channels = visibleChannels();
10125
+
10126
+ meta.textContent = channels.length > 0
10127
+ ? '\u5171 ' + channels.length + ' \u4E2A\u901A\u9053\u5B9E\u4F8B\u3002\u6BCF\u4E2A\u5B9E\u4F8B\u662F\u4E00\u4E2A\u72EC\u7ACB\u804A\u5929\u5165\u53E3\u3002'
10128
+ : '\u5F53\u524D\u8FD8\u6CA1\u6709\u901A\u9053\u5B9E\u4F8B\u3002\u5148\u65B0\u589E\u4E00\u4E2A\u98DE\u4E66\u6216\u5FAE\u4FE1\u673A\u5668\u4EBA\u3002';
10129
+
10130
+ if (channels.length === 0) {
10131
+ list.innerHTML = '<div class="binding-empty">\u5F53\u524D\u8FD8\u6CA1\u6709\u53EF\u7528\u901A\u9053\u5B9E\u4F8B\u3002</div>';
9697
10132
  return;
9698
10133
  }
9699
10134
 
9700
- const activeBindingId = ensureActiveBinding(channelType, bindings);
10135
+ ensureActiveChannelId();
10136
+ list.innerHTML = channels.map((channel) => {
10137
+ const adapter = getAdapterStatus(channel.id);
10138
+ const active = channel.id === state.activeChannelId;
10139
+ const bindingCount = bindingsForChannel(channel.id).length;
10140
+ const statusText = channel.enabled === false
10141
+ ? '\u5DF2\u505C\u7528'
10142
+ : adapter && adapter.running
10143
+ ? '\u8FD0\u884C\u4E2D'
10144
+ : state.bridgeStatus && state.bridgeStatus.running
10145
+ ? '\u7B49\u5F85\u540C\u6B65'
10146
+ : 'Bridge \u672A\u542F\u52A8';
10147
+ return ''
10148
+ + '<button type="button" class="channel-list-item' + (active ? ' active' : '') + '" data-action="select-channel" data-channel-id="' + escapeHtml(channel.id) + '">'
10149
+ + '<div class="channel-list-item-head">'
10150
+ + '<div class="channel-list-item-title">' + escapeHtml(channel.alias) + '</div>'
10151
+ + '<span class="channel-list-item-provider">' + escapeHtml(providerLabel(channel.provider)) + '</span>'
10152
+ + '</div>'
10153
+ + '<div class="channel-list-item-stats">'
10154
+ + '<span class="channel-list-item-status">' + escapeHtml(statusText) + '</span>'
10155
+ + '<span>' + escapeHtml(bindingCount === 0 ? '\u672A\u7ED1\u5B9A\u804A\u5929' : ('\u5DF2\u7ED1\u5B9A ' + bindingCount + ' \u4E2A\u804A\u5929')) + '</span>'
10156
+ + '</div>'
10157
+ + '<div class="channel-list-item-meta">' + escapeHtml(channel.id) + '</div>'
10158
+ + '</button>';
10159
+ }).join('');
10160
+ }
10161
+
10162
+ function renderChannelBindingsV2(channel) {
10163
+ const bindings = bindingsForChannel(channel.id);
10164
+ const emptyText = emptyBindingText(channel);
10165
+ if (bindings.length === 0) {
10166
+ return '<div class="binding-empty">' + escapeHtml(emptyText) + '</div>';
10167
+ }
10168
+
10169
+ const activeBindingId = ensureActiveBinding(channel.id, bindings);
9701
10170
  const activeBinding = bindings.find((binding) => binding.id === activeBindingId) || bindings[0];
9702
10171
  const tabs = bindings.length > 1
9703
10172
  ? '<div class="binding-tabs">' + bindings.map((binding) => (
9704
10173
  '<button type="button" class="binding-tab' + (binding.id === activeBinding.id ? ' active' : '') + '"'
9705
10174
  + ' data-action="select-binding-tab"'
9706
- + ' data-channel="' + escapeHtml(channelType) + '"'
10175
+ + ' data-channel-id="' + escapeHtml(channel.id) + '"'
9707
10176
  + ' data-binding-id="' + escapeHtml(binding.id) + '"'
9708
10177
  + ' title="' + escapeHtml(binding.chatId) + '">'
9709
10178
  + escapeHtml(bindingTabLabel(binding))
@@ -9711,51 +10180,129 @@ function renderHtml() {
9711
10180
  )).join('') + '</div>'
9712
10181
  : '';
9713
10182
 
9714
- list.innerHTML = tabs + renderBindingCard(activeBinding);
10183
+ return ''
10184
+ + '<div class="small">\u5F53\u524D\u5DF2\u53D1\u73B0 ' + bindings.length + ' \u4E2A\u804A\u5929\u7ED1\u5B9A\u3002\u4E00\u4E2A\u4F1A\u8BDD\u540C\u4E00\u65F6\u523B\u53EA\u80FD\u7ED1\u5B9A\u4E00\u4E2A\u804A\u5929\u3002</div>'
10185
+ + tabs
10186
+ + renderBindingCard(activeBinding);
9715
10187
  }
9716
10188
 
9717
- function renderWeixinAccounts() {
9718
- const meta = document.getElementById('weixinAccountMeta');
9719
- const list = document.getElementById('weixinAccounts');
9720
- const accounts = state.weixinAccounts || [];
9721
-
9722
- if (accounts.length === 0) {
9723
- meta.textContent = '\u5F53\u524D\u8FD8\u6CA1\u6709\u5DF2\u4FDD\u5B58\u7684\u5FAE\u4FE1\u8D26\u53F7\u3002\u5148\u70B9\u51FB\u201C\u5F00\u59CB\u5FAE\u4FE1\u626B\u7801\u201D\uFF0C\u7136\u540E\u5728\u624B\u673A\u4E0A\u786E\u8BA4\u3002';
9724
- list.innerHTML = '<div class="binding-empty">\u626B\u7801\u6210\u529F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u663E\u793A\u5F53\u524D\u5DF2\u4FDD\u5B58\u7684\u5FAE\u4FE1\u8D26\u53F7\u3002</div>';
10189
+ function renderChannelEditor() {
10190
+ const editor = document.getElementById('channelEditor');
10191
+ const channel = getChannelById(ensureActiveChannelId());
10192
+ if (!channel) {
10193
+ editor.innerHTML = '<div class="binding-empty">\u8BF7\u9009\u62E9\u4E00\u4E2A\u901A\u9053\u5B9E\u4F8B\uFF0C\u6216\u5148\u521B\u5EFA\u65B0\u901A\u9053\u3002</div>';
9725
10194
  return;
9726
10195
  }
9727
10196
 
9728
- meta.textContent = '\u5F53\u524D\u5DF2\u4FDD\u5B58 ' + accounts.length + ' \u4E2A\u5FAE\u4FE1\u8D26\u53F7\u3002\u5FAE\u4FE1\u6865\u63A5\u662F\u5355\u8D26\u53F7\u6A21\u5F0F\uFF0C\u6700\u65B0\u542F\u7528\u7684\u8D26\u53F7\u4F1A\u751F\u6548\u3002';
9729
- list.innerHTML = accounts.map((account) => ''
9730
- + '<article class="binding-item">'
9731
- + '<div class="binding-head">'
9732
- + '<div class="binding-title">' + escapeHtml(account.name || account.accountId) + '</div>'
9733
- + '<div class="small">' + (account.enabled ? '\u5DF2\u542F\u7528' : '\u5DF2\u505C\u7528') + '</div>'
9734
- + '</div>'
9735
- + '<div class="binding-detail">\u8D26\u53F7 ID\uFF1A<code>' + escapeHtml(account.accountId) + '</code></div>'
9736
- + '<div class="binding-detail">\u7528\u6237 ID\uFF1A<code>' + escapeHtml(account.userId || '-') + '</code></div>'
9737
- + '<div class="binding-detail">Base URL\uFF1A' + escapeHtml(account.baseUrl || '-') + '</div>'
9738
- + '<div class="binding-detail">\u6700\u8FD1\u767B\u5F55\uFF1A' + escapeHtml(formatTime(account.lastLoginAt || account.updatedAt)) + '</div>'
9739
- + '</article>'
10197
+ const adapter = getAdapterStatus(channel.id);
10198
+ const statusText = formatChannelRuntimeLabel(channel);
10199
+ const feishu = channel.provider === 'feishu' ? (channel.config || {}) : {};
10200
+ const weixin = channel.provider === 'weixin' ? (channel.config || {}) : {};
10201
+ const weixinAccounts = getWeixinAccountOptions();
10202
+ const weixinAccountOptions = ['<option value="">\u672A\u7ED1\u5B9A\u8D26\u53F7</option>'].concat(
10203
+ weixinAccounts.map((account) => (
10204
+ '<option value="' + escapeHtml(account.accountId) + '"' + (account.accountId === weixin.accountId ? ' selected' : '') + '>'
10205
+ + escapeHtml(account.name || account.accountId)
10206
+ + '</option>'
10207
+ )),
9740
10208
  ).join('');
10209
+ const bindingsHtml = renderChannelBindingsV2(channel);
10210
+ const bindingCount = bindingsForChannel(channel.id).length;
10211
+ const detailsHtml = channel.provider === 'feishu'
10212
+ ? ''
10213
+ + '<div class="editor-section">'
10214
+ + '<p class="editor-section-title">\u8FDE\u63A5\u914D\u7F6E</p>'
10215
+ + '<div class="field-row">'
10216
+ + '<label>App ID<input id="channelAppId" value="' + escapeHtml(feishu.appId || '') + '" /></label>'
10217
+ + '<label>App Secret<input id="channelAppSecret" value="' + escapeHtml(feishu.appSecret || '') + '" /></label>'
10218
+ + '</div>'
10219
+ + '<div class="field-row">'
10220
+ + '<label>\u7AD9\u70B9<select id="channelSite">'
10221
+ + '<option value="feishu"' + ((feishu.site || 'feishu') === 'feishu' ? ' selected' : '') + '>Feishu</option>'
10222
+ + '<option value="lark"' + (feishu.site === 'lark' ? ' selected' : '') + '>Lark</option>'
10223
+ + '</select></label>'
10224
+ + '<label>Allowed Users<input id="channelAllowedUsers" value="' + escapeHtml(Array.isArray(feishu.allowedUsers) ? feishu.allowedUsers.join(', ') : '') + '" placeholder="\u591A\u4E2A user_id \u7528\u9017\u53F7\u5206\u9694" /></label>'
10225
+ + '</div>'
10226
+ + '</div>'
10227
+ + '<div class="editor-section">'
10228
+ + '<p class="editor-section-title">\u884C\u4E3A\u5F00\u5173</p>'
10229
+ + '<div class="checkbox-row">'
10230
+ + '<label class="checkbox"><input id="channelStreamingEnabled" type="checkbox"' + (feishu.streamingEnabled !== false ? ' checked' : '') + ' /> \u542F\u7528\u98DE\u4E66\u6D41\u5F0F\u54CD\u5E94\u5361\u7247</label>'
10231
+ + '<label class="checkbox"><input id="channelFeedbackMarkdownEnabled" type="checkbox"' + (feishu.feedbackMarkdownEnabled !== false ? ' checked' : '') + ' /> \u53CD\u9988\u4F7F\u7528markdown</label>'
10232
+ + '</div>'
10233
+ + '</div>'
10234
+ : ''
10235
+ + '<div class="editor-section">'
10236
+ + '<p class="editor-section-title">\u8FDE\u63A5\u914D\u7F6E</p>'
10237
+ + '<div class="field-row">'
10238
+ + '<label>\u5FAE\u4FE1\u8D26\u53F7<select id="channelAccountId">' + weixinAccountOptions + '</select></label>'
10239
+ + '<label>Base URL<input id="channelBaseUrl" value="' + escapeHtml(weixin.baseUrl || '') + '" /></label>'
10240
+ + '</div>'
10241
+ + '<div class="field-row">'
10242
+ + '<label>CDN Base URL<input id="channelCdnBaseUrl" value="' + escapeHtml(weixin.cdnBaseUrl || '') + '" /></label>'
10243
+ + '<div></div>'
10244
+ + '</div>'
10245
+ + '</div>'
10246
+ + '<div class="editor-section">'
10247
+ + '<p class="editor-section-title">\u884C\u4E3A\u5F00\u5173</p>'
10248
+ + '<div class="checkbox-row">'
10249
+ + '<label class="checkbox"><input id="channelMediaEnabled" type="checkbox"' + (weixin.mediaEnabled === true ? ' checked' : '') + ' /> \u542F\u7528\u56FE\u7247 / \u6587\u4EF6 / \u89C6\u9891\u5165\u7AD9\u4E0B\u8F7D</label>'
10250
+ + '<label class="checkbox"><input id="channelFeedbackMarkdownEnabled" type="checkbox"' + (weixin.feedbackMarkdownEnabled === true ? ' checked' : '') + ' /> \u53CD\u9988\u4F7F\u7528markdown</label>'
10251
+ + '</div>'
10252
+ + '</div>';
10253
+
10254
+ editor.innerHTML = ''
10255
+ + '<div class="panel-header">'
10256
+ + '<div>'
10257
+ + '<h2>' + escapeHtml(channel.alias) + '</h2>'
10258
+ + '<p>' + escapeHtml(statusText) + (adapter && adapter.lastMessageAt ? ' \xB7 \u6700\u8FD1\u6D88\u606F ' + escapeHtml(formatTime(adapter.lastMessageAt)) : '') + '</p>'
10259
+ + '</div>'
10260
+ + '<div class="toolbar-split">'
10261
+ + '<div class="toolbar">'
10262
+ + '<button type="button" data-action="channel-save" data-channel-id="' + escapeHtml(channel.id) + '" class="primary">\u4FDD\u5B58\u901A\u9053</button>'
10263
+ + '<button type="button" data-action="channel-test" data-channel-id="' + escapeHtml(channel.id) + '">\u6D4B\u8BD5\u5F53\u524D\u901A\u9053</button>'
10264
+ + (channel.provider === 'weixin' ? '<button type="button" data-action="channel-weixin-login" data-channel-id="' + escapeHtml(channel.id) + '">\u5F00\u59CB\u5FAE\u4FE1\u626B\u7801</button>' : '')
10265
+ + '</div>'
10266
+ + '<div class="toolbar-danger">'
10267
+ + '<button type="button" data-action="channel-delete" data-channel-id="' + escapeHtml(channel.id) + '" class="danger">\u5220\u9664\u901A\u9053</button>'
10268
+ + '</div>'
10269
+ + '</div>'
10270
+ + '</div>'
10271
+ + '<div class="channel-editor-summary">'
10272
+ + '<div class="channel-editor-stat"><strong>\u5F53\u524D\u72B6\u6001</strong><span>' + escapeHtml(statusText) + '</span></div>'
10273
+ + '<div class="channel-editor-stat"><strong>\u804A\u5929\u7ED1\u5B9A</strong><span>' + escapeHtml(String(bindingCount)) + '</span></div>'
10274
+ + '<div class="channel-editor-stat"><strong>Provider</strong><span>' + escapeHtml(providerLabel(channel.provider)) + '</span></div>'
10275
+ + '</div>'
10276
+ + '<div class="fields">'
10277
+ + '<div class="editor-section">'
10278
+ + '<p class="editor-section-title">\u57FA\u7840\u4FE1\u606F</p>'
10279
+ + '<div class="field-row triple">'
10280
+ + '<label>\u522B\u540D<input id="channelAliasInput" value="' + escapeHtml(channel.alias) + '" /></label>'
10281
+ + '<label>\u5B9E\u4F8B ID<input value="' + escapeHtml(channel.id) + '" disabled /></label>'
10282
+ + '<label>Provider<input value="' + escapeHtml(providerLabel(channel.provider)) + '" disabled /></label>'
10283
+ + '</div>'
10284
+ + '<div class="checkbox-row">'
10285
+ + '<label class="checkbox"><input id="channelEnabledInput" type="checkbox"' + (channel.enabled !== false ? ' checked' : '') + ' /> \u542F\u7528\u5F53\u524D\u901A\u9053</label>'
10286
+ + '</div>'
10287
+ + '</div>'
10288
+ + detailsHtml
10289
+ + '</div>'
10290
+ + '<div class="panel-block">'
10291
+ + '<p class="panel-subtitle">\u5F53\u524D\u7ED1\u5B9A</p>'
10292
+ + bindingsHtml
10293
+ + '</div>';
10294
+ }
10295
+
10296
+ function renderChannelsWorkspace() {
10297
+ renderChannelList();
10298
+ renderChannelEditor();
9741
10299
  }
9742
10300
 
9743
10301
  function renderBindings(result) {
9744
10302
  state.bindings = result.bindings || [];
9745
10303
  state.bindingOptions = result.options || [];
9746
10304
  document.getElementById('bindingCount').textContent = String(state.bindings.length);
9747
- renderChannelBindings(
9748
- 'feishu',
9749
- 'feishuBindings',
9750
- 'feishuBindingMeta',
9751
- emptyBindingText('feishu')
9752
- );
9753
- renderChannelBindings(
9754
- 'weixin',
9755
- 'weixinBindings',
9756
- 'weixinBindingMeta',
9757
- emptyBindingText('weixin')
9758
- );
10305
+ renderChannelsWorkspace();
9759
10306
  rerenderDesktopSessions();
9760
10307
  }
9761
10308
 
@@ -9815,18 +10362,10 @@ function renderHtml() {
9815
10362
  document.getElementById('codexReasoningEffort').value = config.codexReasoningEffort || 'medium';
9816
10363
  document.getElementById('uiAllowLan').checked = config.uiAllowLan === true;
9817
10364
  document.getElementById('uiAccessToken').value = config.uiAccessToken || '';
9818
- document.getElementById('channelFeishu').checked = (config.enabledChannels || []).includes('feishu');
9819
- document.getElementById('channelWeixin').checked = (config.enabledChannels || []).includes('weixin');
9820
10365
  document.getElementById('autoApprove').checked = config.autoApprove === true;
9821
- document.getElementById('feishuAppId').value = config.feishuAppId || '';
9822
- document.getElementById('feishuAppSecret').value = config.feishuAppSecret || '';
9823
- document.getElementById('feishuDomain').value = config.feishuDomain || 'https://open.feishu.cn';
9824
- document.getElementById('feishuAllowedUsers').value = config.feishuAllowedUsers || '';
9825
- document.getElementById('feishuStreamingEnabled').checked = config.feishuStreamingEnabled !== false;
9826
- document.getElementById('feishuCommandMarkdownEnabled').checked = config.feishuCommandMarkdownEnabled !== false;
9827
- document.getElementById('weixinMediaEnabled').checked = config.weixinMediaEnabled === true;
9828
- document.getElementById('weixinCommandMarkdownEnabled').checked = config.weixinCommandMarkdownEnabled === true;
9829
10366
  renderUiAccess();
10367
+ ensureActiveChannelId();
10368
+ renderChannelsWorkspace();
9830
10369
  rerenderDesktopSessions();
9831
10370
  }
9832
10371
 
@@ -9847,24 +10386,68 @@ function renderHtml() {
9847
10386
  const config = await api('/api/config');
9848
10387
  state.uiAccess = status.uiAccess || null;
9849
10388
  state.bridgeStatus = status.bridge || null;
10389
+ state.autostartStatus = status.autostart || null;
9850
10390
  state.weixinAccounts = status.weixin && Array.isArray(status.weixin.linkedAccounts) ? status.weixin.linkedAccounts : [];
9851
10391
  fillForm(config);
9852
- const runningChannelText = runningChannels().length ? ' \xB7 ' + runningChannels().join(', ') : '';
10392
+ const runningChannelText = adapterStatuses().length
10393
+ ? ' \xB7 ' + adapterStatuses().filter((item) => item.running).map((item) => item.channelAlias || item.channelType).join(', ')
10394
+ : '';
9853
10395
  document.getElementById('bridgeStatus').textContent = status.bridge.running ? 'Running' + runningChannelText : 'Stopped';
10396
+ renderAutostartStatus(status.autostart || null);
9854
10397
  document.getElementById('integrationStatus').textContent = status.codexIntegrationInstalled ? '\u5DF2\u5B89\u88C5' : '\u672A\u5B89\u88C5';
9855
10398
  document.getElementById('runtimeStatus').textContent = config.runtime || 'codex';
9856
10399
  document.getElementById('homeStatus').textContent = status.home;
9857
10400
  document.getElementById('overviewHomeStatus').textContent = status.home;
9858
10401
  document.getElementById('packageRoot').textContent = status.packageRoot;
9859
- document.getElementById('feishuRuntimeMeta').textContent = channelRuntimeText('feishu');
9860
- document.getElementById('weixinRuntimeMeta').textContent = channelRuntimeText('weixin');
9861
- renderWeixinAccounts();
9862
10402
  renderBindings({
9863
10403
  bindings: state.bindings,
9864
10404
  options: state.bindingOptions,
9865
10405
  });
9866
10406
  }
9867
10407
 
10408
+ function renderAutostartStatus(status) {
10409
+ const valueEl = document.getElementById('autostartStatus');
10410
+ const noticeEl = document.getElementById('autostartNotice');
10411
+ const refreshBtn = document.getElementById('refreshAutostartBtn');
10412
+
10413
+ if (!status || status.supported !== true) {
10414
+ valueEl.textContent = '\u4E0D\u652F\u6301';
10415
+ noticeEl.textContent = status && status.error
10416
+ ? status.error
10417
+ : '\u5F53\u524D\u7CFB\u7EDF\u6682\u4E0D\u652F\u6301 Bridge \u5F00\u673A\u81EA\u542F\u52A8\u3002';
10418
+ refreshBtn.disabled = true;
10419
+ return;
10420
+ }
10421
+
10422
+ if (status.error) {
10423
+ valueEl.textContent = '\u914D\u7F6E\u5F02\u5E38';
10424
+ noticeEl.textContent = status.error;
10425
+ refreshBtn.disabled = false;
10426
+ return;
10427
+ }
10428
+
10429
+ if (!status.installed) {
10430
+ valueEl.textContent = '\u672A\u542F\u7528';
10431
+ noticeEl.textContent = [
10432
+ '\u5982\u9700\u542F\u7528\uFF0C\u8BF7\u4EE5\u7BA1\u7406\u5458\u8EAB\u4EFD\u6253\u5F00 PowerShell \u6216\u7EC8\u7AEF\u6267\u884C\uFF1A',
10433
+ 'codex-to-im autostart install',
10434
+ status.runAsUser ? '\u8FD0\u884C\u8D26\u53F7\uFF1A' + status.runAsUser : '',
10435
+ '\u5B89\u88C5\u65F6\u4F1A\u8981\u6C42\u8F93\u5165\u5F53\u524D Windows \u767B\u5F55\u5BC6\u7801\u3002',
10436
+ ].filter(Boolean).join('\\n');
10437
+ refreshBtn.disabled = false;
10438
+ return;
10439
+ }
10440
+
10441
+ valueEl.textContent = status.enabled ? '\u5DF2\u542F\u7528' : '\u5DF2\u7981\u7528';
10442
+ noticeEl.textContent = [
10443
+ '\u65B9\u5F0F\uFF1AWindows \u4EFB\u52A1\u8BA1\u5212\u7A0B\u5E8F\uFF08\u5F00\u673A\u89E6\u53D1\uFF09',
10444
+ status.runAsUser ? '\u8FD0\u884C\u8D26\u53F7\uFF1A' + status.runAsUser : '',
10445
+ status.state ? '\u4EFB\u52A1\u72B6\u6001\uFF1A' + status.state : '',
10446
+ '\u5982\u9700\u5173\u95ED\uFF0C\u8BF7\u4EE5\u7BA1\u7406\u5458\u8EAB\u4EFD\u6267\u884C\uFF1Acodex-to-im autostart uninstall',
10447
+ ].filter(Boolean).join(' \xB7 ');
10448
+ refreshBtn.disabled = false;
10449
+ }
10450
+
9868
10451
  async function loadLogs() {
9869
10452
  const logs = await api('/api/logs?lines=220');
9870
10453
  document.getElementById('logsOutput').textContent = logs.logs || '\u6682\u65E0\u65E5\u5FD7';
@@ -9898,16 +10481,187 @@ function renderHtml() {
9898
10481
  return saved;
9899
10482
  }
9900
10483
 
9901
- document.querySelectorAll('.nav-link').forEach((element) => {
9902
- element.addEventListener('click', () => {
9903
- setActivePage(element.dataset.page || 'overview', true);
10484
+ function createChannelDraft(provider) {
10485
+ state.channelDraft = {
10486
+ id: '__draft__:' + provider,
10487
+ alias: providerLabel(provider),
10488
+ provider,
10489
+ enabled: true,
10490
+ createdAt: new Date().toISOString(),
10491
+ updatedAt: new Date().toISOString(),
10492
+ config: provider === 'feishu'
10493
+ ? {
10494
+ appId: '',
10495
+ appSecret: '',
10496
+ site: 'feishu',
10497
+ allowedUsers: [],
10498
+ streamingEnabled: true,
10499
+ feedbackMarkdownEnabled: true,
10500
+ }
10501
+ : {
10502
+ accountId: '',
10503
+ baseUrl: '',
10504
+ cdnBaseUrl: '',
10505
+ mediaEnabled: false,
10506
+ feedbackMarkdownEnabled: false,
10507
+ },
10508
+ };
10509
+ state.activeChannelId = state.channelDraft.id;
10510
+ renderChannelsWorkspace();
10511
+ }
10512
+
10513
+ function currentChannelEditorPayload(channel) {
10514
+ const payload = {
10515
+ provider: channel.provider,
10516
+ alias: document.getElementById('channelAliasInput').value,
10517
+ enabled: document.getElementById('channelEnabledInput').checked,
10518
+ };
10519
+
10520
+ if (!String(channel.id || '').startsWith('__draft__:')) {
10521
+ payload.id = channel.id;
10522
+ }
10523
+
10524
+ if (channel.provider === 'feishu') {
10525
+ payload.appId = document.getElementById('channelAppId').value;
10526
+ payload.appSecret = document.getElementById('channelAppSecret').value;
10527
+ payload.site = document.getElementById('channelSite').value;
10528
+ payload.allowedUsers = document.getElementById('channelAllowedUsers').value;
10529
+ payload.streamingEnabled = document.getElementById('channelStreamingEnabled').checked;
10530
+ payload.feedbackMarkdownEnabled = document.getElementById('channelFeedbackMarkdownEnabled').checked;
10531
+ return payload;
10532
+ }
10533
+
10534
+ payload.accountId = document.getElementById('channelAccountId').value;
10535
+ payload.baseUrl = document.getElementById('channelBaseUrl').value;
10536
+ payload.cdnBaseUrl = document.getElementById('channelCdnBaseUrl').value;
10537
+ payload.mediaEnabled = document.getElementById('channelMediaEnabled').checked;
10538
+ payload.feedbackMarkdownEnabled = document.getElementById('channelFeedbackMarkdownEnabled').checked;
10539
+ return payload;
10540
+ }
10541
+
10542
+ async function saveChannel(channel) {
10543
+ const result = await api('/api/channels/save', {
10544
+ method: 'POST',
10545
+ body: JSON.stringify(currentChannelEditorPayload(channel)),
9904
10546
  });
9905
- });
10547
+ state.channelDraft = null;
10548
+ fillForm(result.config);
10549
+ renderBindings(result);
10550
+ setActiveChannel(result.channel.id, false);
10551
+ return result;
10552
+ }
10553
+
10554
+ async function deleteCurrentChannel(channel) {
10555
+ if (String(channel.id || '').startsWith('__draft__:')) {
10556
+ state.channelDraft = null;
10557
+ state.activeChannelId = '';
10558
+ renderChannelsWorkspace();
10559
+ showMessage('channelMessage', 'success', '\u672A\u4FDD\u5B58\u7684\u65B0\u901A\u9053\u5DF2\u53D6\u6D88\u3002');
10560
+ return;
10561
+ }
9906
10562
 
9907
- document.querySelectorAll('.channel-tab').forEach((element) => {
10563
+ const result = await api('/api/channels/delete', {
10564
+ method: 'POST',
10565
+ body: JSON.stringify({ channelId: channel.id }),
10566
+ });
10567
+ state.channelDraft = null;
10568
+ fillForm(result.config);
10569
+ renderBindings(result);
10570
+ showMessage('channelMessage', 'success', '\u901A\u9053\u5DF2\u5220\u9664\u3002');
10571
+ }
10572
+
10573
+ async function testCurrentChannel(channel) {
10574
+ if (String(channel.id || '').startsWith('__draft__:')) {
10575
+ const saved = await saveChannel(channel);
10576
+ channel = getChannelById(saved.channel.id);
10577
+ } else {
10578
+ await saveChannel(channel);
10579
+ channel = getChannelById(channel.id);
10580
+ }
10581
+ const result = await api('/api/channels/test', {
10582
+ method: 'POST',
10583
+ body: JSON.stringify({ channelId: channel.id }),
10584
+ });
10585
+ showMessage('channelMessage', result.ok ? 'success' : 'error', result.message);
10586
+ }
10587
+
10588
+ async function loginWeixinForChannel(channel) {
10589
+ if (String(channel.id || '').startsWith('__draft__:')) {
10590
+ const saved = await saveChannel(channel);
10591
+ channel = getChannelById(saved.channel.id);
10592
+ } else {
10593
+ await saveChannel(channel);
10594
+ channel = getChannelById(channel.id);
10595
+ }
10596
+ const result = await api('/api/channels/weixin-login', {
10597
+ method: 'POST',
10598
+ body: JSON.stringify({ channelId: channel.id }),
10599
+ });
10600
+ fillForm(result.config || state.config);
10601
+ await loadStatus();
10602
+ showMessage('channelMessage', result.ok ? 'success' : 'error', result.message);
10603
+ }
10604
+
10605
+ async function handleChannelEditorAction(event) {
10606
+ const source = event.target instanceof Element ? event.target : null;
10607
+ const target = source ? source.closest('button[data-action]') : null;
10608
+ if (!target) return;
10609
+
10610
+ const channelId = target.dataset.channelId || state.activeChannelId;
10611
+ const channel = getChannelById(channelId);
10612
+ if (!channel) return;
10613
+
10614
+ try {
10615
+ if (target.dataset.action === 'channel-save') {
10616
+ await saveChannel(channel);
10617
+ showMessage('channelMessage', 'success', '\u901A\u9053\u5DF2\u4FDD\u5B58\u3002');
10618
+ return;
10619
+ }
10620
+ if (target.dataset.action === 'channel-test') {
10621
+ await testCurrentChannel(channel);
10622
+ return;
10623
+ }
10624
+ if (target.dataset.action === 'channel-delete') {
10625
+ await deleteCurrentChannel(channel);
10626
+ return;
10627
+ }
10628
+ if (target.dataset.action === 'channel-weixin-login') {
10629
+ await loginWeixinForChannel(channel);
10630
+ return;
10631
+ }
10632
+ if (target.dataset.action === 'select-binding-tab') {
10633
+ state.activeBindingByChannelId[channel.id] = target.dataset.bindingId || '';
10634
+ renderChannelsWorkspace();
10635
+ return;
10636
+ }
10637
+ if (target.dataset.action === 'unbind-binding') {
10638
+ const result = await api('/api/bindings/delete', {
10639
+ method: 'POST',
10640
+ body: JSON.stringify({ bindingId: target.dataset.bindingId }),
10641
+ });
10642
+ renderBindings(result);
10643
+ showMessage('channelMessage', 'success', '\u804A\u5929\u7ED1\u5B9A\u5DF2\u89E3\u7ED1\u3002');
10644
+ return;
10645
+ }
10646
+ if (target.dataset.action === 'switch-binding-target') {
10647
+ const result = await api('/api/bindings/update', {
10648
+ method: 'POST',
10649
+ body: JSON.stringify({
10650
+ bindingId: target.dataset.bindingId,
10651
+ targetKey: target.dataset.targetKey,
10652
+ }),
10653
+ });
10654
+ renderBindings(result);
10655
+ showMessage('channelMessage', 'success', '\u804A\u5929\u7ED1\u5B9A\u5DF2\u66F4\u65B0\u3002');
10656
+ }
10657
+ } catch (error) {
10658
+ showMessage('channelMessage', 'error', error.message);
10659
+ }
10660
+ }
10661
+
10662
+ document.querySelectorAll('.nav-link').forEach((element) => {
9908
10663
  element.addEventListener('click', () => {
9909
- setActivePage('channels', false);
9910
- setActiveChannel(element.dataset.channel || 'feishu', true);
10664
+ setActivePage(element.dataset.page || 'overview', true);
9911
10665
  });
9912
10666
  });
9913
10667
 
@@ -9955,76 +10709,6 @@ function renderHtml() {
9955
10709
  }
9956
10710
  });
9957
10711
 
9958
- document.getElementById('saveFeishuChannelBtn').addEventListener('click', async () => {
9959
- try {
9960
- await saveConfig({ messageId: 'feishuMessage', scope: 'feishu' });
9961
- await loadStatus();
9962
- await loadBindings();
9963
- } catch (error) {
9964
- showMessage('feishuMessage', 'error', error.message);
9965
- }
9966
- });
9967
-
9968
- document.getElementById('testFeishuBtn').addEventListener('click', async () => {
9969
- try {
9970
- await saveConfig();
9971
- await loadStatus();
9972
- await loadBindings();
9973
- const result = await api('/api/test/feishu', { method: 'POST' });
9974
- showMessage('feishuMessage', result.ok ? 'success' : 'error', result.message);
9975
- } catch (error) {
9976
- showMessage('feishuMessage', 'error', error.message);
9977
- }
9978
- });
9979
-
9980
- document.getElementById('refreshFeishuStateBtn').addEventListener('click', async () => {
9981
- try {
9982
- await loadStatus();
9983
- await loadBindings();
9984
- await loadDesktopSessions();
9985
- showMessage('feishuMessage', 'success', '\u98DE\u4E66\u901A\u9053\u72B6\u6001\u5DF2\u5237\u65B0\u3002');
9986
- } catch (error) {
9987
- showMessage('feishuMessage', 'error', error.message);
9988
- }
9989
- });
9990
-
9991
- document.getElementById('saveWeixinChannelBtn').addEventListener('click', async () => {
9992
- try {
9993
- await saveConfig({ messageId: 'weixinMessage', scope: 'weixin' });
9994
- await loadStatus();
9995
- await loadBindings();
9996
- } catch (error) {
9997
- showMessage('weixinMessage', 'error', error.message);
9998
- }
9999
- });
10000
-
10001
- document.getElementById('refreshWeixinStateBtn').addEventListener('click', async () => {
10002
- try {
10003
- await loadStatus();
10004
- await loadBindings();
10005
- await loadDesktopSessions();
10006
- showMessage('weixinMessage', 'success', '\u5FAE\u4FE1\u901A\u9053\u72B6\u6001\u5DF2\u5237\u65B0\u3002');
10007
- } catch (error) {
10008
- showMessage('weixinMessage', 'error', error.message);
10009
- }
10010
- });
10011
-
10012
- document.getElementById('weixinLoginBtn').addEventListener('click', async () => {
10013
- try {
10014
- await saveConfig();
10015
- showMessage('weixinMessage', 'success', '\u5FAE\u4FE1\u626B\u7801\u6D41\u7A0B\u5DF2\u542F\u52A8\uFF0C\u6D4F\u89C8\u5668\u4F1A\u6253\u5F00\u4E8C\u7EF4\u7801\u9875\u9762\u3002');
10016
- const result = await api('/api/test/weixin', { method: 'POST' });
10017
- await loadStatus();
10018
- await loadBindings();
10019
- const followup = isChannelRunning('weixin')
10020
- ? '\u5FAE\u4FE1\u8D26\u53F7\u5DF2\u4FDD\u5B58\u3002\u5F53\u524D Bridge \u5DF2\u52A0\u8F7D\u5FAE\u4FE1\u901A\u9053\uFF0C\u51E0\u79D2\u540E\u4F1A\u81EA\u52A8\u63A5\u5165\u65B0\u8D26\u53F7\u3002'
10021
- : '\u5FAE\u4FE1\u8D26\u53F7\u5DF2\u4FDD\u5B58\u3002Bridge \u4F1A\u5728\u51E0\u79D2\u5185\u81EA\u52A8\u540C\u6B65\u5FAE\u4FE1\u901A\u9053\uFF1B\u5982\u679C\u9875\u9762\u8FD8\u6CA1\u66F4\u65B0\uFF0C\u53EF\u624B\u52A8\u70B9\u201C\u5237\u65B0\u72B6\u6001\u201D\u3002';
10022
- showMessage('weixinMessage', result.ok ? 'success' : 'error', followup);
10023
- } catch (error) {
10024
- showMessage('weixinMessage', 'error', error.message);
10025
- }
10026
- });
10027
-
10028
10712
  document.getElementById('startBridgeBtn').addEventListener('click', async () => {
10029
10713
  try {
10030
10714
  await saveConfig();
@@ -10086,6 +10770,15 @@ function renderHtml() {
10086
10770
  }
10087
10771
  });
10088
10772
 
10773
+ document.getElementById('refreshAutostartBtn').addEventListener('click', async () => {
10774
+ try {
10775
+ await loadStatus();
10776
+ showMessage('opsMessage', 'success', '\u5F00\u673A\u81EA\u542F\u52A8\u72B6\u6001\u5DF2\u5237\u65B0\u3002');
10777
+ } catch (error) {
10778
+ showMessage('opsMessage', 'error', error.message);
10779
+ }
10780
+ });
10781
+
10089
10782
  document.getElementById('installIntegrationBtn').addEventListener('click', async () => {
10090
10783
  try {
10091
10784
  const result = await api('/api/install-codex-integration', { method: 'POST' });
@@ -10113,6 +10806,35 @@ function renderHtml() {
10113
10806
  }
10114
10807
  });
10115
10808
 
10809
+ document.getElementById('createChannelBtn').addEventListener('click', () => {
10810
+ const provider = document.getElementById('newChannelProvider').value === 'weixin' ? 'weixin' : 'feishu';
10811
+ createChannelDraft(provider);
10812
+ setActivePage('channels', false);
10813
+ setActiveChannel(state.activeChannelId, true);
10814
+ });
10815
+
10816
+ document.getElementById('refreshChannelsBtn').addEventListener('click', async () => {
10817
+ try {
10818
+ await loadStatus();
10819
+ await loadBindings();
10820
+ showMessage('channelMessage', 'success', '\u901A\u9053\u72B6\u6001\u5DF2\u5237\u65B0\u3002');
10821
+ } catch (error) {
10822
+ showMessage('channelMessage', 'error', error.message);
10823
+ }
10824
+ });
10825
+
10826
+ document.getElementById('channelList').addEventListener('click', (event) => {
10827
+ const source = event.target instanceof Element ? event.target : null;
10828
+ const target = source ? source.closest('button[data-channel-id]') : null;
10829
+ if (!target) return;
10830
+ setActivePage('channels', false);
10831
+ setActiveChannel(target.dataset.channelId || '', true);
10832
+ });
10833
+
10834
+ document.getElementById('channelEditor').addEventListener('click', (event) => {
10835
+ handleChannelEditorAction(event);
10836
+ });
10837
+
10116
10838
  async function handleSessionListAction(event) {
10117
10839
  const source = event.target instanceof Element ? event.target : null;
10118
10840
  const target = source ? source.closest('button[data-action]') : null;
@@ -10139,59 +10861,20 @@ function renderHtml() {
10139
10861
  if (!target) return;
10140
10862
 
10141
10863
  if (target.dataset.action === 'unbind-binding') {
10142
- const channelType = target.dataset.channel || 'feishu';
10143
- await handleBindingAction(event, channelType, 'desktopMessage');
10144
- return;
10145
- }
10146
-
10147
- await handleSessionListAction(event);
10148
- });
10149
-
10150
- async function handleBindingAction(event, channelType, messageId) {
10151
- const source = event.target instanceof Element ? event.target : null;
10152
- const target = source ? source.closest('button[data-action]') : null;
10153
- if (!target) return;
10154
-
10155
- try {
10156
- if (target.dataset.action === 'select-binding-tab') {
10157
- state.activeBindingByChannel[channelType] = target.dataset.bindingId || '';
10158
- rerenderBindingPanels();
10159
- return;
10160
- }
10161
- if (target.dataset.action === 'unbind-binding') {
10864
+ try {
10162
10865
  const result = await api('/api/bindings/delete', {
10163
10866
  method: 'POST',
10164
- body: JSON.stringify({
10165
- bindingId: target.dataset.bindingId,
10166
- }),
10867
+ body: JSON.stringify({ bindingId: target.dataset.bindingId }),
10167
10868
  });
10168
10869
  renderBindings(result);
10169
- showMessage(messageId, 'success', channelType === 'feishu' ? '\u98DE\u4E66\u7ED1\u5B9A\u5DF2\u89E3\u7ED1\u3002' : '\u5FAE\u4FE1\u7ED1\u5B9A\u5DF2\u89E3\u7ED1\u3002');
10170
- return;
10171
- }
10172
- if (target.dataset.action !== 'switch-binding-target') {
10173
- return;
10870
+ showMessage('desktopMessage', 'success', '\u804A\u5929\u7ED1\u5B9A\u5DF2\u89E3\u7ED1\u3002');
10871
+ } catch (error) {
10872
+ showMessage('desktopMessage', 'error', error.message);
10174
10873
  }
10175
- const result = await api('/api/bindings/update', {
10176
- method: 'POST',
10177
- body: JSON.stringify({
10178
- bindingId: target.dataset.bindingId,
10179
- targetKey: target.dataset.targetKey,
10180
- }),
10181
- });
10182
- renderBindings(result);
10183
- showMessage(messageId, 'success', channelType === 'feishu' ? '\u98DE\u4E66\u7ED1\u5B9A\u5DF2\u66F4\u65B0\u3002' : '\u5FAE\u4FE1\u7ED1\u5B9A\u5DF2\u66F4\u65B0\u3002');
10184
- } catch (error) {
10185
- showMessage(messageId, 'error', error.message);
10874
+ return;
10186
10875
  }
10187
- }
10188
10876
 
10189
- document.getElementById('feishuBindings').addEventListener('click', (event) => {
10190
- handleBindingAction(event, 'feishu', 'feishuMessage');
10191
- });
10192
-
10193
- document.getElementById('weixinBindings').addEventListener('click', (event) => {
10194
- handleBindingAction(event, 'weixin', 'weixinMessage');
10877
+ await handleSessionListAction(event);
10195
10878
  });
10196
10879
 
10197
10880
  syncPageFromHash();
@@ -10287,6 +10970,7 @@ var server = http.createServer(async (request, response) => {
10287
10970
  if (request.method === "GET" && url.pathname === "/api/status") {
10288
10971
  json(response, 200, {
10289
10972
  bridge: getBridgeStatus(),
10973
+ autostart: await getBridgeAutostartStatus(),
10290
10974
  ui: getUiServerStatus(),
10291
10975
  uiAccess: buildUiAccessInfo(port, config, request),
10292
10976
  home: CTI_HOME,
@@ -10324,17 +11008,93 @@ var server = http.createServer(async (request, response) => {
10324
11008
  json(response, 200, { ok: true, config: configToPayload(loadConfig()) });
10325
11009
  return;
10326
11010
  }
10327
- if (request.method === "POST" && url.pathname === "/api/test/feishu") {
10328
- const result = await validateFeishuCredentials(loadConfig());
10329
- json(response, 200, result);
11011
+ if (request.method === "POST" && url.pathname === "/api/channels/save") {
11012
+ const payload = await readJsonBody(request);
11013
+ const current = loadConfig();
11014
+ const merged = mergeChannelInstance(payload, current);
11015
+ saveConfig(merged.config);
11016
+ const store = createUiStore();
11017
+ syncBindingChannelMeta(store, merged.channel);
11018
+ json(response, 200, {
11019
+ ok: true,
11020
+ channel: channelToPayload(merged.channel),
11021
+ config: configToPayload(loadConfig()),
11022
+ ...await buildBindingsPayload(store, loadConfig())
11023
+ });
11024
+ return;
11025
+ }
11026
+ if (request.method === "POST" && url.pathname === "/api/channels/delete") {
11027
+ const payload = await readJsonBody(request);
11028
+ const channelId = asString(payload.channelId);
11029
+ if (!channelId) {
11030
+ json(response, 400, { error: "channelId \u4E0D\u80FD\u4E3A\u7A7A\u3002" });
11031
+ return;
11032
+ }
11033
+ const store = createUiStore();
11034
+ const bindings = store.listChannelBindings(channelId);
11035
+ if (bindings.length > 0) {
11036
+ json(response, 400, { error: "\u8BE5\u901A\u9053\u4ECD\u6709\u804A\u5929\u7ED1\u5B9A\uFF0C\u8BF7\u5148\u89E3\u7ED1\u540E\u518D\u5220\u9664\u3002" });
11037
+ return;
11038
+ }
11039
+ const next = deleteChannelInstance(loadConfig(), channelId);
11040
+ saveConfig(next);
11041
+ json(response, 200, {
11042
+ ok: true,
11043
+ config: configToPayload(loadConfig()),
11044
+ ...await buildBindingsPayload(store, loadConfig())
11045
+ });
10330
11046
  return;
10331
11047
  }
10332
- if (request.method === "POST" && url.pathname === "/api/test/weixin") {
10333
- const result = await runWeixinLogin();
11048
+ if (request.method === "POST" && url.pathname === "/api/channels/test") {
11049
+ const payload = await readJsonBody(request);
11050
+ const channelId = asString(payload.channelId);
11051
+ if (!channelId) {
11052
+ json(response, 400, { error: "channelId \u4E0D\u80FD\u4E3A\u7A7A\u3002" });
11053
+ return;
11054
+ }
11055
+ const channel = findChannelInstance(channelId, loadConfig());
11056
+ if (!channel) {
11057
+ json(response, 404, { error: "\u6307\u5B9A\u7684\u901A\u9053\u4E0D\u5B58\u5728\u3002" });
11058
+ return;
11059
+ }
11060
+ if (channel.provider === "feishu") {
11061
+ json(response, 200, await validateFeishuCredentials(channel));
11062
+ return;
11063
+ }
11064
+ json(response, 200, {
11065
+ ok: true,
11066
+ message: "\u5FAE\u4FE1\u901A\u9053\u8BF7\u4F7F\u7528\u201C\u5F00\u59CB\u5FAE\u4FE1\u626B\u7801\u201D\u5E76\u9009\u62E9\u767B\u5F55\u8D26\u53F7\u8FDB\u884C\u9A8C\u8BC1\u3002"
11067
+ });
11068
+ return;
11069
+ }
11070
+ if (request.method === "POST" && url.pathname === "/api/channels/weixin-login") {
11071
+ const payload = await readJsonBody(request);
11072
+ const channelId = asString(payload.channelId);
11073
+ const current = loadConfig();
11074
+ const channel = channelId ? findChannelInstance(channelId, current) : void 0;
11075
+ const loginConfig = channel?.provider === "weixin" ? channel.config : {};
11076
+ const result = await runWeixinLogin(loginConfig);
11077
+ if (channelId) {
11078
+ if (channel && channel.provider === "weixin") {
11079
+ const merged = mergeChannelInstance({
11080
+ id: channel.id,
11081
+ provider: channel.provider,
11082
+ alias: channel.alias,
11083
+ enabled: channel.enabled,
11084
+ accountId: result.accountId,
11085
+ baseUrl: channel.config.baseUrl,
11086
+ cdnBaseUrl: channel.config.cdnBaseUrl,
11087
+ mediaEnabled: channel.config.mediaEnabled === true,
11088
+ feedbackMarkdownEnabled: channel.config.feedbackMarkdownEnabled === true
11089
+ }, current);
11090
+ saveConfig(merged.config);
11091
+ }
11092
+ }
10334
11093
  json(response, 200, {
10335
11094
  ok: true,
10336
11095
  message: `\u5FAE\u4FE1\u626B\u7801\u6210\u529F\uFF0C\u8D26\u53F7 ${result.accountId} \u5DF2\u4FDD\u5B58\u3002`,
10337
- htmlPath: result.htmlPath
11096
+ htmlPath: result.htmlPath,
11097
+ config: configToPayload(loadConfig())
10338
11098
  });
10339
11099
  return;
10340
11100
  }