codex-to-im 0.1.2 → 0.1.6

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.
@@ -4566,7 +4566,9 @@ var require_lib = __commonJS({
4566
4566
 
4567
4567
  // src/ui-server.ts
4568
4568
  import http from "node:http";
4569
+ import crypto4 from "node:crypto";
4569
4570
  import net from "node:net";
4571
+ import os5 from "node:os";
4570
4572
 
4571
4573
  // src/config.ts
4572
4574
  import fs from "node:fs";
@@ -4574,6 +4576,7 @@ import os from "node:os";
4574
4576
  import path from "node:path";
4575
4577
  var LEGACY_CTI_HOME = path.join(os.homedir(), ".claude-to-im");
4576
4578
  var DEFAULT_CTI_HOME = path.join(os.homedir(), ".codex-to-im");
4579
+ var DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), "cx2im");
4577
4580
  function resolveDefaultCtiHome() {
4578
4581
  if (fs.existsSync(DEFAULT_CTI_HOME)) return DEFAULT_CTI_HOME;
4579
4582
  if (fs.existsSync(LEGACY_CTI_HOME)) return LEGACY_CTI_HOME;
@@ -4581,6 +4584,14 @@ function resolveDefaultCtiHome() {
4581
4584
  }
4582
4585
  var CTI_HOME = process.env.CTI_HOME || resolveDefaultCtiHome();
4583
4586
  var CONFIG_PATH = path.join(CTI_HOME, "config.env");
4587
+ function expandHomePath(value) {
4588
+ if (!value) return value;
4589
+ if (value === "~") return os.homedir();
4590
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
4591
+ return path.join(os.homedir(), value.slice(2));
4592
+ }
4593
+ return value;
4594
+ }
4584
4595
  function parseEnvFile(content) {
4585
4596
  const entries = /* @__PURE__ */ new Map();
4586
4597
  for (const line of content.split("\n")) {
@@ -4614,6 +4625,18 @@ function parsePositiveInt(value) {
4614
4625
  if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
4615
4626
  return Math.floor(parsed);
4616
4627
  }
4628
+ function parseSandboxMode(value) {
4629
+ if (value === "read-only" || value === "workspace-write" || value === "danger-full-access") {
4630
+ return value;
4631
+ }
4632
+ return void 0;
4633
+ }
4634
+ function parseReasoningEffort(value) {
4635
+ if (value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh") {
4636
+ return value;
4637
+ }
4638
+ return void 0;
4639
+ }
4617
4640
  function loadConfig() {
4618
4641
  const env = loadRawConfigEnv();
4619
4642
  const rawRuntime = env.get("CTI_RUNTIME") || "codex";
@@ -4621,11 +4644,16 @@ function loadConfig() {
4621
4644
  return {
4622
4645
  runtime,
4623
4646
  enabledChannels: splitCsv(env.get("CTI_ENABLED_CHANNELS")) ?? ["feishu"],
4624
- defaultWorkDir: env.get("CTI_DEFAULT_WORKDIR") || process.cwd(),
4647
+ defaultWorkDir: expandHomePath(env.get("CTI_DEFAULT_WORKDIR")) || process.cwd(),
4648
+ defaultWorkspaceRoot: expandHomePath(env.get("CTI_DEFAULT_WORKSPACE_ROOT")) || DEFAULT_WORKSPACE_ROOT,
4625
4649
  defaultModel: env.get("CTI_DEFAULT_MODEL") || void 0,
4626
4650
  defaultMode: env.get("CTI_DEFAULT_MODE") || "code",
4627
4651
  historyMessageLimit: parsePositiveInt(env.get("CTI_HISTORY_MESSAGE_LIMIT")) ?? 8,
4628
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,
4629
4657
  tgBotToken: env.get("CTI_TG_BOT_TOKEN") || void 0,
4630
4658
  tgChatId: env.get("CTI_TG_CHAT_ID") || void 0,
4631
4659
  tgAllowedUsers: splitCsv(env.get("CTI_TG_ALLOWED_USERS")),
@@ -4634,6 +4662,7 @@ function loadConfig() {
4634
4662
  feishuDomain: env.get("CTI_FEISHU_DOMAIN") || void 0,
4635
4663
  feishuAllowedUsers: splitCsv(env.get("CTI_FEISHU_ALLOWED_USERS")),
4636
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,
4637
4666
  discordBotToken: env.get("CTI_DISCORD_BOT_TOKEN") || void 0,
4638
4667
  discordAllowedUsers: splitCsv(env.get("CTI_DISCORD_ALLOWED_USERS")),
4639
4668
  discordAllowedChannels: splitCsv(
@@ -4648,6 +4677,7 @@ function loadConfig() {
4648
4677
  weixinBaseUrl: env.get("CTI_WEIXIN_BASE_URL") || void 0,
4649
4678
  weixinCdnBaseUrl: env.get("CTI_WEIXIN_CDN_BASE_URL") || void 0,
4650
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,
4651
4681
  autoApprove: env.get("CTI_AUTO_APPROVE") === "true"
4652
4682
  };
4653
4683
  }
@@ -4664,12 +4694,17 @@ function saveConfig(config) {
4664
4694
  config.enabledChannels.join(",")
4665
4695
  );
4666
4696
  out += formatEnvLine("CTI_DEFAULT_WORKDIR", config.defaultWorkDir);
4697
+ out += formatEnvLine("CTI_DEFAULT_WORKSPACE_ROOT", config.defaultWorkspaceRoot);
4667
4698
  if (config.defaultModel) out += formatEnvLine("CTI_DEFAULT_MODEL", config.defaultModel);
4668
4699
  out += formatEnvLine("CTI_DEFAULT_MODE", config.defaultMode);
4669
4700
  if (config.historyMessageLimit !== void 0)
4670
4701
  out += formatEnvLine("CTI_HISTORY_MESSAGE_LIMIT", String(config.historyMessageLimit));
4671
4702
  if (config.codexSkipGitRepoCheck !== void 0)
4672
4703
  out += formatEnvLine("CTI_CODEX_SKIP_GIT_REPO_CHECK", String(config.codexSkipGitRepoCheck));
4704
+ out += formatEnvLine("CTI_CODEX_SANDBOX_MODE", config.codexSandboxMode);
4705
+ out += formatEnvLine("CTI_CODEX_REASONING_EFFORT", config.codexReasoningEffort);
4706
+ out += formatEnvLine("CTI_UI_ALLOW_LAN", String(config.uiAllowLan === true));
4707
+ out += formatEnvLine("CTI_UI_ACCESS_TOKEN", config.uiAccessToken);
4673
4708
  out += formatEnvLine("CTI_TG_BOT_TOKEN", config.tgBotToken);
4674
4709
  out += formatEnvLine("CTI_TG_CHAT_ID", config.tgChatId);
4675
4710
  out += formatEnvLine(
@@ -4688,6 +4723,11 @@ function saveConfig(config) {
4688
4723
  "CTI_FEISHU_STREAMING_ENABLED",
4689
4724
  String(config.feishuStreamingEnabled)
4690
4725
  );
4726
+ if (config.feishuCommandMarkdownEnabled !== void 0)
4727
+ out += formatEnvLine(
4728
+ "CTI_FEISHU_COMMAND_MARKDOWN_ENABLED",
4729
+ String(config.feishuCommandMarkdownEnabled)
4730
+ );
4691
4731
  out += formatEnvLine("CTI_DISCORD_BOT_TOKEN", config.discordBotToken);
4692
4732
  out += formatEnvLine(
4693
4733
  "CTI_DISCORD_ALLOWED_USERS",
@@ -4715,6 +4755,12 @@ function saveConfig(config) {
4715
4755
  out += formatEnvLine("CTI_WEIXIN_CDN_BASE_URL", config.weixinCdnBaseUrl);
4716
4756
  if (config.weixinMediaEnabled !== void 0)
4717
4757
  out += formatEnvLine("CTI_WEIXIN_MEDIA_ENABLED", String(config.weixinMediaEnabled));
4758
+ if (config.weixinCommandMarkdownEnabled !== void 0)
4759
+ out += formatEnvLine(
4760
+ "CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED",
4761
+ String(config.weixinCommandMarkdownEnabled)
4762
+ );
4763
+ out += formatEnvLine("CTI_AUTO_APPROVE", String(config.autoApprove === true));
4718
4764
  fs.mkdirSync(CTI_HOME, { recursive: true });
4719
4765
  const tmpPath = CONFIG_PATH + ".tmp";
4720
4766
  fs.writeFileSync(tmpPath, out, { mode: 384 });
@@ -4763,6 +4809,10 @@ function configToSettings(config) {
4763
4809
  "bridge_feishu_streaming_enabled",
4764
4810
  config.feishuStreamingEnabled === false ? "false" : "true"
4765
4811
  );
4812
+ m.set(
4813
+ "bridge_feishu_command_markdown_enabled",
4814
+ config.feishuCommandMarkdownEnabled === false ? "false" : "true"
4815
+ );
4766
4816
  m.set(
4767
4817
  "bridge_qq_enabled",
4768
4818
  config.enabledChannels.includes("qq") ? "true" : "false"
@@ -4781,11 +4831,18 @@ function configToSettings(config) {
4781
4831
  );
4782
4832
  if (config.weixinMediaEnabled !== void 0)
4783
4833
  m.set("bridge_weixin_media_enabled", String(config.weixinMediaEnabled));
4834
+ m.set(
4835
+ "bridge_weixin_command_markdown_enabled",
4836
+ config.weixinCommandMarkdownEnabled === true ? "true" : "false"
4837
+ );
4784
4838
  if (config.weixinBaseUrl)
4785
4839
  m.set("bridge_weixin_base_url", config.weixinBaseUrl);
4786
4840
  if (config.weixinCdnBaseUrl)
4787
4841
  m.set("bridge_weixin_cdn_base_url", config.weixinCdnBaseUrl);
4788
4842
  m.set("bridge_default_work_dir", config.defaultWorkDir);
4843
+ if (config.defaultWorkspaceRoot) {
4844
+ m.set("bridge_default_workspace_root", config.defaultWorkspaceRoot);
4845
+ }
4789
4846
  if (config.defaultModel) {
4790
4847
  m.set("bridge_default_model", config.defaultModel);
4791
4848
  m.set("default_model", config.defaultModel);
@@ -4799,6 +4856,14 @@ function configToSettings(config) {
4799
4856
  "bridge_codex_skip_git_repo_check",
4800
4857
  config.codexSkipGitRepoCheck === true ? "true" : "false"
4801
4858
  );
4859
+ m.set(
4860
+ "bridge_codex_sandbox_mode",
4861
+ config.codexSandboxMode || "workspace-write"
4862
+ );
4863
+ m.set(
4864
+ "bridge_codex_reasoning_effort",
4865
+ config.codexReasoningEffort || "medium"
4866
+ );
4802
4867
  return m;
4803
4868
  }
4804
4869
 
@@ -4862,6 +4927,8 @@ var MIME_EXT = {
4862
4927
  };
4863
4928
  function toApprovalPolicy(permissionMode) {
4864
4929
  switch (permissionMode) {
4930
+ case "never":
4931
+ return "never";
4865
4932
  case "acceptEdits":
4866
4933
  return "on-failure";
4867
4934
  case "plan":
@@ -4878,6 +4945,18 @@ function shouldPassModelToCodex() {
4878
4945
  function shouldSkipGitRepoCheck() {
4879
4946
  return process.env.CTI_CODEX_SKIP_GIT_REPO_CHECK === "true";
4880
4947
  }
4948
+ function normalizeSandboxMode(mode) {
4949
+ if (mode === "read-only" || mode === "workspace-write" || mode === "danger-full-access") {
4950
+ return mode;
4951
+ }
4952
+ return "workspace-write";
4953
+ }
4954
+ function normalizeReasoningEffort(value) {
4955
+ if (value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh") {
4956
+ return value;
4957
+ }
4958
+ return void 0;
4959
+ }
4881
4960
  function shouldRetryFreshThread(message) {
4882
4961
  const lower = message.toLowerCase();
4883
4962
  return lower.includes("resuming session with different model") || lower.includes("no such session") || lower.includes("resume") && lower.includes("session");
@@ -4925,10 +5004,14 @@ var CodexProvider = class {
4925
5004
  let savedThreadId = inMemoryThreadId || params.sdkSessionId || void 0;
4926
5005
  const approvalPolicy = toApprovalPolicy(params.permissionMode);
4927
5006
  const passModel = shouldPassModelToCodex();
5007
+ const sandboxMode = normalizeSandboxMode(params.sandboxMode);
5008
+ const modelReasoningEffort = normalizeReasoningEffort(params.modelReasoningEffort);
4928
5009
  const threadOptions = {
4929
5010
  ...passModel && params.model ? { model: params.model } : {},
4930
5011
  ...params.workingDirectory ? { workingDirectory: params.workingDirectory } : {},
4931
5012
  ...shouldSkipGitRepoCheck() ? { skipGitRepoCheck: true } : {},
5013
+ sandboxMode,
5014
+ ...modelReasoningEffort ? { modelReasoningEffort } : {},
4932
5015
  approvalPolicy
4933
5016
  };
4934
5017
  const imageFiles = params.files?.filter(
@@ -5130,8 +5213,10 @@ var CodexProvider = class {
5130
5213
  import fs3 from "node:fs";
5131
5214
  import os3 from "node:os";
5132
5215
  import path3 from "node:path";
5216
+ import crypto from "node:crypto";
5133
5217
  var ACTIVE_WINDOW_MS = 15 * 60 * 1e3;
5134
5218
  var MAX_SESSION_META_BYTES = 4 * 1024 * 1024;
5219
+ var MAX_SESSION_TITLE_SCAN_BYTES = 512 * 1024;
5135
5220
  var TITLE_MAX_CHARS = 72;
5136
5221
  function getCodexHome() {
5137
5222
  return process.env.CODEX_HOME || path3.join(os3.homedir(), ".codex");
@@ -5189,6 +5274,24 @@ function readFirstLine(filePath, maxBytes = MAX_SESSION_META_BYTES) {
5189
5274
  fs3.closeSync(fd);
5190
5275
  }
5191
5276
  }
5277
+ function readFilePrefix(filePath, maxBytes = MAX_SESSION_TITLE_SCAN_BYTES) {
5278
+ const fd = fs3.openSync(filePath, "r");
5279
+ try {
5280
+ const buffer = Buffer.alloc(Math.min(maxBytes, 64 * 1024));
5281
+ const chunks = [];
5282
+ let offset = 0;
5283
+ while (offset < maxBytes) {
5284
+ const bytesToRead = Math.min(buffer.length, maxBytes - offset);
5285
+ const bytesRead = fs3.readSync(fd, buffer, 0, bytesToRead, offset);
5286
+ if (bytesRead <= 0) break;
5287
+ chunks.push(Buffer.from(buffer.subarray(0, bytesRead)));
5288
+ offset += bytesRead;
5289
+ }
5290
+ return Buffer.concat(chunks).toString("utf-8");
5291
+ } finally {
5292
+ fs3.closeSync(fd);
5293
+ }
5294
+ }
5192
5295
  function walkSessionFiles(dirPath, target) {
5193
5296
  let entries;
5194
5297
  try {
@@ -5210,6 +5313,7 @@ function walkSessionFiles(dirPath, target) {
5210
5313
  function isDesktopLike(meta) {
5211
5314
  const originator = meta?.originator?.toLowerCase() || "";
5212
5315
  const source = meta?.source?.toLowerCase() || "";
5316
+ if (source === "exec") return false;
5213
5317
  return originator.includes("desktop") || source === "vscode" || source === "desktop";
5214
5318
  }
5215
5319
  function loadThreadNameIndex(archivedThreadIds) {
@@ -5241,6 +5345,27 @@ function loadThreadNameIndex(archivedThreadIds) {
5241
5345
  }
5242
5346
  return new Map(Array.from(titles.entries()).map(([threadId, entry]) => [threadId, entry.title]));
5243
5347
  }
5348
+ function buildFallbackTitle(threadId, filePath, cwd) {
5349
+ try {
5350
+ const content = readFilePrefix(filePath);
5351
+ for (const line of content.split(/\r?\n/)) {
5352
+ if (!line.trim()) continue;
5353
+ let parsed;
5354
+ try {
5355
+ parsed = JSON.parse(line);
5356
+ } catch {
5357
+ continue;
5358
+ }
5359
+ if (!isSessionEventLine(parsed) || parsed.payload?.type !== "user_message") continue;
5360
+ const firstUserMessage = trimTitle(normalizeFreeText(parsed.payload.message || ""));
5361
+ if (firstUserMessage) return firstUserMessage;
5362
+ }
5363
+ } catch {
5364
+ }
5365
+ const dirName = trimTitle(path3.basename(cwd || ""));
5366
+ if (dirName) return dirName;
5367
+ return `Session ${threadId.slice(0, 8)}`;
5368
+ }
5244
5369
  function parseDesktopSession(filePath, threadNames, archivedThreadIds) {
5245
5370
  const firstLine = readFirstLine(filePath);
5246
5371
  if (!firstLine) return null;
@@ -5266,8 +5391,7 @@ function parseDesktopSession(filePath, threadNames, archivedThreadIds) {
5266
5391
  const lastEventAt = stat.mtime.toISOString();
5267
5392
  const firstSeenAt = parsed.payload.timestamp || parsed.timestamp || stat.birthtime.toISOString();
5268
5393
  const threadId = parsed.payload.id;
5269
- const title = threadNames.get(threadId);
5270
- if (!title) return null;
5394
+ const title = threadNames.get(threadId) || buildFallbackTitle(threadId, filePath, cwd);
5271
5395
  return {
5272
5396
  threadId,
5273
5397
  filePath,
@@ -5287,12 +5411,17 @@ function trimTitle(text2) {
5287
5411
  if (normalized.length <= TITLE_MAX_CHARS) return normalized;
5288
5412
  return `${normalized.slice(0, TITLE_MAX_CHARS - 3).trimEnd()}...`;
5289
5413
  }
5414
+ function normalizeFreeText(text2) {
5415
+ return text2.replace(/\s+/g, " ").trim();
5416
+ }
5417
+ function isSessionEventLine(line) {
5418
+ return line.type === "event_msg";
5419
+ }
5290
5420
  function listDesktopSessions(limit = 12) {
5291
5421
  const root = getCodexSessionsRoot();
5292
5422
  if (!fs3.existsSync(root)) return [];
5293
5423
  const archivedThreadIds = loadArchivedThreadIds();
5294
5424
  const threadNames = loadThreadNameIndex(archivedThreadIds);
5295
- if (threadNames.size === 0) return [];
5296
5425
  const files = [];
5297
5426
  walkSessionFiles(root, files);
5298
5427
  const sessions = [];
@@ -5313,10 +5442,15 @@ function isArchivedDesktopThread(threadId) {
5313
5442
  // src/session-bindings.ts
5314
5443
  import path4 from "node:path";
5315
5444
  function getSessionName(session) {
5445
+ if (session.session_type === "draft") return "\u4E34\u65F6\u8349\u7A3F\u7EBF\u7A0B";
5446
+ if (session.session_type === "history_summary") return "\u5386\u53F2\u6458\u8981\u7EBF\u7A0B";
5316
5447
  if (session.name?.trim()) return session.name.trim();
5317
5448
  if (session.working_directory) return path4.basename(session.working_directory);
5318
5449
  return session.id.slice(0, 8);
5319
5450
  }
5451
+ function getSessionMode(store, session) {
5452
+ return session.preferred_mode || store.getSetting("bridge_default_mode") || "code";
5453
+ }
5320
5454
  function bindStoreToSession(store, channelType, chatId, sessionId) {
5321
5455
  const session = store.getSession(sessionId);
5322
5456
  if (!session) return null;
@@ -5326,7 +5460,8 @@ function bindStoreToSession(store, channelType, chatId, sessionId) {
5326
5460
  codepilotSessionId: session.id,
5327
5461
  sdkSessionId: session.sdk_session_id || "",
5328
5462
  workingDirectory: session.working_directory,
5329
- model: session.model
5463
+ model: session.model,
5464
+ mode: getSessionMode(store, session)
5330
5465
  });
5331
5466
  }
5332
5467
  function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
@@ -5338,7 +5473,8 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
5338
5473
  codepilotSessionId: existing.id,
5339
5474
  sdkSessionId,
5340
5475
  workingDirectory: opts?.workingDirectory || existing.working_directory,
5341
- model: opts?.model || existing.model
5476
+ model: opts?.model || existing.model,
5477
+ mode: getSessionMode(store, existing)
5342
5478
  });
5343
5479
  }
5344
5480
  const workingDirectory = opts?.workingDirectory || store.getSetting("bridge_default_work_dir") || process.env.HOME || "";
@@ -5358,7 +5494,8 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
5358
5494
  codepilotSessionId: session.id,
5359
5495
  sdkSessionId,
5360
5496
  workingDirectory: workingDirectory || session.working_directory,
5361
- model: model || session.model
5497
+ model: model || session.model,
5498
+ mode: getSessionMode(store, session)
5362
5499
  });
5363
5500
  }
5364
5501
  function listBindingTargetOptions(_store, desktopLimit = 12) {
@@ -5395,7 +5532,11 @@ function listBindingSummaries(store) {
5395
5532
  currentTargetLabel,
5396
5533
  currentSessionId: binding.codepilotSessionId,
5397
5534
  currentSessionName: session ? getSessionName(session) : binding.codepilotSessionId.slice(0, 8),
5398
- currentThreadId
5535
+ currentThreadId,
5536
+ runtimeStatus: session?.runtime_status,
5537
+ queuedCount: session?.queued_count,
5538
+ mirrorStatus: session?.mirror_status,
5539
+ mirrorLastEventAt: session?.mirror_last_event_at
5399
5540
  };
5400
5541
  }).sort((a, b) => {
5401
5542
  if (a.channelType !== b.channelType) return a.channelType.localeCompare(b.channelType);
@@ -5449,6 +5590,7 @@ var bridgePidFile = path5.join(runtimeDir, "bridge.pid");
5449
5590
  var bridgeStatusFile = path5.join(runtimeDir, "status.json");
5450
5591
  var uiStatusFile = path5.join(runtimeDir, "ui-server.json");
5451
5592
  var uiPort = 4781;
5593
+ var WINDOWS_HIDE = process.platform === "win32" ? { windowsHide: true } : {};
5452
5594
  function ensureDirs() {
5453
5595
  fs4.mkdirSync(runtimeDir, { recursive: true });
5454
5596
  fs4.mkdirSync(logsDir, { recursive: true });
@@ -5550,7 +5692,8 @@ async function startBridge() {
5550
5692
  cwd: packageRoot,
5551
5693
  detached: true,
5552
5694
  env: buildDaemonEnv(),
5553
- stdio: ["ignore", stdoutFd, stderrFd]
5695
+ stdio: ["ignore", stdoutFd, stderrFd],
5696
+ ...WINDOWS_HIDE
5554
5697
  });
5555
5698
  child.unref();
5556
5699
  const status = await waitForBridgeRunning();
@@ -5567,7 +5710,8 @@ async function stopBridge() {
5567
5710
  if (process.platform === "win32") {
5568
5711
  await new Promise((resolve) => {
5569
5712
  const killer = spawn("cmd", ["/c", "taskkill", "/PID", String(status.pid), "/T", "/F"], {
5570
- stdio: "ignore"
5713
+ stdio: "ignore",
5714
+ ...WINDOWS_HIDE
5571
5715
  });
5572
5716
  killer.on("exit", () => resolve());
5573
5717
  killer.on("error", () => resolve());
@@ -5635,7 +5779,7 @@ function isCodexIntegrationInstalled() {
5635
5779
  // src/store.ts
5636
5780
  import fs5 from "node:fs";
5637
5781
  import path6 from "node:path";
5638
- import crypto from "node:crypto";
5782
+ import crypto2 from "node:crypto";
5639
5783
  var DATA_DIR = path6.join(CTI_HOME, "data");
5640
5784
  var MESSAGES_DIR = path6.join(DATA_DIR, "messages");
5641
5785
  function ensureDir(dir) {
@@ -5658,13 +5802,14 @@ function writeJson(filePath, data) {
5658
5802
  atomicWrite(filePath, JSON.stringify(data, null, 2));
5659
5803
  }
5660
5804
  function uuid() {
5661
- return crypto.randomUUID();
5805
+ return crypto2.randomUUID();
5662
5806
  }
5663
5807
  function now() {
5664
5808
  return (/* @__PURE__ */ new Date()).toISOString();
5665
5809
  }
5666
5810
  var JsonFileStore = class {
5667
5811
  settings;
5812
+ dynamicSettings;
5668
5813
  sessions = /* @__PURE__ */ new Map();
5669
5814
  bindings = /* @__PURE__ */ new Map();
5670
5815
  messages = /* @__PURE__ */ new Map();
@@ -5673,8 +5818,9 @@ var JsonFileStore = class {
5673
5818
  dedupKeys = /* @__PURE__ */ new Map();
5674
5819
  locks = /* @__PURE__ */ new Map();
5675
5820
  auditLog = [];
5676
- constructor(settingsMap) {
5821
+ constructor(settingsMap, options) {
5677
5822
  this.settings = settingsMap;
5823
+ this.dynamicSettings = options?.dynamicSettings === true;
5678
5824
  ensureDir(DATA_DIR);
5679
5825
  ensureDir(MESSAGES_DIR);
5680
5826
  this.loadAll();
@@ -5769,7 +5915,19 @@ var JsonFileStore = class {
5769
5915
  return msgs;
5770
5916
  }
5771
5917
  // ── Settings ──
5918
+ refreshSettings() {
5919
+ if (!this.dynamicSettings) return;
5920
+ try {
5921
+ const next = configToSettings(loadConfig());
5922
+ this.settings = new Map([
5923
+ ...this.settings,
5924
+ ...next
5925
+ ]);
5926
+ } catch {
5927
+ }
5928
+ }
5772
5929
  getSetting(key) {
5930
+ this.refreshSettings();
5773
5931
  return this.settings.get(key) ?? null;
5774
5932
  }
5775
5933
  // ── Channel Bindings ──
@@ -5788,6 +5946,7 @@ var JsonFileStore = class {
5788
5946
  sdkSessionId: data.sdkSessionId ?? existing.sdkSessionId,
5789
5947
  workingDirectory: data.workingDirectory,
5790
5948
  model: data.model,
5949
+ mode: data.mode ?? existing.mode,
5791
5950
  updatedAt: now()
5792
5951
  };
5793
5952
  this.bindings.set(key, updated);
@@ -5802,7 +5961,7 @@ var JsonFileStore = class {
5802
5961
  sdkSessionId: data.sdkSessionId ?? "",
5803
5962
  workingDirectory: data.workingDirectory,
5804
5963
  model: data.model,
5805
- mode: this.settings.get("bridge_default_mode") || "code",
5964
+ mode: data.mode || this.getSetting("bridge_default_mode") || "code",
5806
5965
  active: true,
5807
5966
  createdAt: now(),
5808
5967
  updatedAt: now()
@@ -5845,14 +6004,23 @@ var JsonFileStore = class {
5845
6004
  }
5846
6005
  return null;
5847
6006
  }
5848
- createSession(name, model, systemPrompt, cwd, _mode) {
6007
+ createSession(name, model, systemPrompt, cwd, mode, options) {
5849
6008
  this.reloadSessions();
6009
+ const timestamp = now();
5850
6010
  const session = {
5851
6011
  id: uuid(),
5852
6012
  name,
5853
- working_directory: cwd || this.settings.get("bridge_default_work_dir") || process.cwd(),
6013
+ working_directory: cwd || this.getSetting("bridge_default_work_dir") || process.cwd(),
5854
6014
  model,
5855
- system_prompt: systemPrompt
6015
+ preferred_mode: mode,
6016
+ system_prompt: systemPrompt,
6017
+ reasoning_effort: options?.reasoningEffort,
6018
+ session_type: options?.sessionType || "normal",
6019
+ hidden: options?.hidden === true,
6020
+ parent_session_id: options?.parentSessionId,
6021
+ expires_at: options?.expiresAt,
6022
+ created_at: timestamp,
6023
+ updated_at: timestamp
5856
6024
  };
5857
6025
  this.sessions.set(session.id, session);
5858
6026
  this.persistSessions();
@@ -5863,9 +6031,40 @@ var JsonFileStore = class {
5863
6031
  const s = this.sessions.get(sessionId);
5864
6032
  if (s) {
5865
6033
  s.provider_id = providerId;
6034
+ s.updated_at = now();
5866
6035
  this.persistSessions();
5867
6036
  }
5868
6037
  }
6038
+ updateSession(sessionId, updates) {
6039
+ this.reloadSessions();
6040
+ const session = this.sessions.get(sessionId);
6041
+ if (!session) return;
6042
+ const next = {
6043
+ ...session,
6044
+ ...updates,
6045
+ id: session.id,
6046
+ updated_at: now()
6047
+ };
6048
+ this.sessions.set(sessionId, next);
6049
+ this.persistSessions();
6050
+ }
6051
+ deleteSession(sessionId) {
6052
+ this.reloadSessions();
6053
+ this.reloadBindings();
6054
+ this.sessions.delete(sessionId);
6055
+ for (const [key, binding] of this.bindings) {
6056
+ if (binding.codepilotSessionId === sessionId) {
6057
+ this.bindings.delete(key);
6058
+ }
6059
+ }
6060
+ this.messages.delete(sessionId);
6061
+ try {
6062
+ fs5.rmSync(path6.join(MESSAGES_DIR, `${sessionId}.json`), { force: true });
6063
+ } catch {
6064
+ }
6065
+ this.persistSessions();
6066
+ this.persistBindings();
6067
+ }
5869
6068
  // ── Messages ──
5870
6069
  addMessage(sessionId, role, content, _usage) {
5871
6070
  const msgs = this.loadMessages(sessionId);
@@ -5905,6 +6104,26 @@ var JsonFileStore = class {
5905
6104
  }
5906
6105
  }
5907
6106
  setSessionRuntimeStatus(_sessionId, _status) {
6107
+ this.reloadSessions();
6108
+ const session = this.sessions.get(_sessionId);
6109
+ if (!session) return;
6110
+ const queuedCount = session.queued_count && session.queued_count > 0 ? session.queued_count : 0;
6111
+ let runtimeStatus;
6112
+ if (_status === "running") {
6113
+ runtimeStatus = queuedCount > 0 ? "queued" : "running";
6114
+ } else if (_status === "idle") {
6115
+ runtimeStatus = queuedCount > 0 ? "queued" : "idle";
6116
+ } else {
6117
+ runtimeStatus = session.runtime_status;
6118
+ }
6119
+ const next = {
6120
+ ...session,
6121
+ runtime_status: runtimeStatus,
6122
+ last_runtime_update_at: now(),
6123
+ updated_at: now()
6124
+ };
6125
+ this.sessions.set(_sessionId, next);
6126
+ this.persistSessions();
5908
6127
  }
5909
6128
  // ── SDK Session ──
5910
6129
  updateSdkSessionId(sessionId, sdkSessionId) {
@@ -5913,6 +6132,7 @@ var JsonFileStore = class {
5913
6132
  const s = this.sessions.get(sessionId);
5914
6133
  if (s) {
5915
6134
  s.sdk_session_id = sdkSessionId;
6135
+ s.updated_at = now();
5916
6136
  this.persistSessions();
5917
6137
  }
5918
6138
  for (const [key, b] of this.bindings) {
@@ -5927,6 +6147,7 @@ var JsonFileStore = class {
5927
6147
  const s = this.sessions.get(sessionId);
5928
6148
  if (s) {
5929
6149
  s.model = model;
6150
+ s.updated_at = now();
5930
6151
  this.persistSessions();
5931
6152
  }
5932
6153
  }
@@ -5983,6 +6204,7 @@ var JsonFileStore = class {
5983
6204
  permissionRequestId: link.permissionRequestId,
5984
6205
  chatId: link.chatId,
5985
6206
  messageId: link.messageId,
6207
+ sessionId: link.sessionId,
5986
6208
  resolved: false,
5987
6209
  suggestions: link.suggestions
5988
6210
  };
@@ -6025,7 +6247,7 @@ import path8 from "node:path";
6025
6247
  import { spawn as spawn2 } from "node:child_process";
6026
6248
 
6027
6249
  // src/adapters/weixin/weixin-api.ts
6028
- import crypto2 from "node:crypto";
6250
+ import crypto3 from "node:crypto";
6029
6251
 
6030
6252
  // src/adapters/weixin/weixin-types.ts
6031
6253
  var DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com";
@@ -6316,7 +6538,7 @@ function openQrHtml() {
6316
6538
  return true;
6317
6539
  }
6318
6540
  if (process.platform === "win32") {
6319
- const child2 = spawn2("cmd", ["/c", "start", "", HTML_PATH], { detached: true, stdio: "ignore" });
6541
+ const child2 = spawn2("cmd", ["/c", "start", "", HTML_PATH], { detached: true, stdio: "ignore", windowsHide: true });
6320
6542
  child2.unref();
6321
6543
  return true;
6322
6544
  }
@@ -6438,6 +6660,7 @@ if (isMainModule) {
6438
6660
  var port = 4781;
6439
6661
  var serverStartTime = (/* @__PURE__ */ new Date()).toISOString();
6440
6662
  var supportedChannels = ["feishu", "weixin"];
6663
+ var AUTH_COOKIE_NAME = "cti_ui_auth";
6441
6664
  function parsePreferredPort() {
6442
6665
  const raw = Number(process.env.CTI_UI_PORT || "4781");
6443
6666
  if (!Number.isInteger(raw) || raw <= 0 || raw > 65535) return 4781;
@@ -6448,7 +6671,7 @@ async function canListen(portToCheck) {
6448
6671
  const probe = net.createServer();
6449
6672
  probe.unref();
6450
6673
  probe.once("error", () => resolve(false));
6451
- probe.listen(portToCheck, "127.0.0.1", () => {
6674
+ probe.listen(portToCheck, "0.0.0.0", () => {
6452
6675
  probe.close(() => resolve(true));
6453
6676
  });
6454
6677
  });
@@ -6462,7 +6685,7 @@ async function resolveUiPort(preferredPort) {
6462
6685
  const probe = net.createServer();
6463
6686
  probe.unref();
6464
6687
  probe.once("error", reject);
6465
- probe.listen(0, "127.0.0.1", () => {
6688
+ probe.listen(0, "0.0.0.0", () => {
6466
6689
  const address = probe.address();
6467
6690
  const dynamicPort = typeof address === "object" && address ? address.port : preferredPort;
6468
6691
  probe.close((error) => {
@@ -6525,43 +6748,128 @@ function parsePositiveInt2(value, fallback) {
6525
6748
  function createUiStore() {
6526
6749
  return new JsonFileStore(configToSettings(loadConfig()));
6527
6750
  }
6751
+ function generateAccessToken() {
6752
+ return crypto4.randomBytes(18).toString("base64url");
6753
+ }
6754
+ function timingSafeMatch(left, right) {
6755
+ if (!left || !right) return false;
6756
+ const leftBuffer = Buffer.from(left);
6757
+ const rightBuffer = Buffer.from(right);
6758
+ if (leftBuffer.length !== rightBuffer.length) return false;
6759
+ return crypto4.timingSafeEqual(leftBuffer, rightBuffer);
6760
+ }
6761
+ function parseCookies(request) {
6762
+ const header = request.headers.cookie;
6763
+ if (!header) return /* @__PURE__ */ new Map();
6764
+ return new Map(
6765
+ header.split(";").map((part) => part.trim()).filter(Boolean).map((part) => {
6766
+ const separator = part.indexOf("=");
6767
+ if (separator === -1) return [part, ""];
6768
+ return [part.slice(0, separator), decodeURIComponent(part.slice(separator + 1))];
6769
+ })
6770
+ );
6771
+ }
6772
+ function makeAuthCookie(token) {
6773
+ return `${AUTH_COOKIE_NAME}=${encodeURIComponent(token)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000`;
6774
+ }
6775
+ function clearAuthCookie() {
6776
+ return `${AUTH_COOKIE_NAME}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`;
6777
+ }
6778
+ function redirect(response, location, cookie) {
6779
+ const headers = { Location: location };
6780
+ if (cookie) headers["Set-Cookie"] = cookie;
6781
+ response.writeHead(302, headers);
6782
+ response.end();
6783
+ }
6784
+ function getRemoteAddress(request) {
6785
+ return request.socket.remoteAddress || "";
6786
+ }
6787
+ function isLoopbackAddress(address) {
6788
+ return address === "127.0.0.1" || address === "::1" || address === "::ffff:127.0.0.1";
6789
+ }
6790
+ function isLocalRequest(request) {
6791
+ return isLoopbackAddress(getRemoteAddress(request));
6792
+ }
6793
+ function getLanUrls(currentPort) {
6794
+ const interfaces = os5.networkInterfaces();
6795
+ const urls = /* @__PURE__ */ new Set();
6796
+ for (const records of Object.values(interfaces)) {
6797
+ for (const record of records || []) {
6798
+ if (!record || record.internal || record.family !== "IPv4") continue;
6799
+ urls.add(`http://${record.address}:${currentPort}`);
6800
+ }
6801
+ }
6802
+ return Array.from(urls).sort();
6803
+ }
6804
+ function buildUiAccessInfo(currentPort, config, request) {
6805
+ return {
6806
+ allowLan: config.uiAllowLan === true,
6807
+ localUrl: getUiServerUrl(currentPort),
6808
+ lanUrls: getLanUrls(currentPort),
6809
+ accessToken: config.uiAccessToken || "",
6810
+ requestIsLocal: request ? isLocalRequest(request) : true,
6811
+ authenticated: request ? isRemoteAuthenticated(request, config) : true
6812
+ };
6813
+ }
6814
+ function isRemoteAuthenticated(request, config) {
6815
+ if (isLocalRequest(request)) return true;
6816
+ if (config.uiAllowLan !== true) return false;
6817
+ return timingSafeMatch(parseCookies(request).get(AUTH_COOKIE_NAME), config.uiAccessToken);
6818
+ }
6528
6819
  function configToPayload(config) {
6529
6820
  return {
6530
6821
  runtime: config.runtime,
6531
6822
  enabledChannels: config.enabledChannels,
6532
6823
  defaultWorkDir: config.defaultWorkDir,
6824
+ defaultWorkspaceRoot: config.defaultWorkspaceRoot || "",
6533
6825
  defaultModel: config.defaultModel || "",
6534
6826
  defaultMode: config.defaultMode,
6535
6827
  historyMessageLimit: config.historyMessageLimit ?? 8,
6536
6828
  codexSkipGitRepoCheck: config.codexSkipGitRepoCheck === true,
6829
+ codexSandboxMode: config.codexSandboxMode || "workspace-write",
6830
+ codexReasoningEffort: config.codexReasoningEffort || "medium",
6831
+ uiAllowLan: config.uiAllowLan === true,
6832
+ uiAccessToken: config.uiAccessToken || "",
6537
6833
  autoApprove: config.autoApprove === true,
6538
6834
  feishuAppId: config.feishuAppId || "",
6539
6835
  feishuAppSecret: config.feishuAppSecret || "",
6540
6836
  feishuDomain: config.feishuDomain || "https://open.feishu.cn",
6541
6837
  feishuAllowedUsers: config.feishuAllowedUsers?.join(",") || "",
6542
6838
  feishuStreamingEnabled: config.feishuStreamingEnabled !== false,
6543
- weixinMediaEnabled: config.weixinMediaEnabled === true
6839
+ feishuCommandMarkdownEnabled: config.feishuCommandMarkdownEnabled !== false,
6840
+ weixinMediaEnabled: config.weixinMediaEnabled === true,
6841
+ weixinCommandMarkdownEnabled: config.weixinCommandMarkdownEnabled === true
6544
6842
  };
6545
6843
  }
6546
6844
  function mergeConfig(payload) {
6547
6845
  const current = loadConfig();
6548
6846
  const requestedChannels = Array.isArray(payload.enabledChannels) ? payload.enabledChannels.filter((value) => typeof value === "string") : current.enabledChannels;
6847
+ const uiAllowLan = payload.uiAllowLan === true;
6848
+ const requestedUiAccessToken = asString(payload.uiAccessToken);
6849
+ const uiAccessToken = requestedUiAccessToken || current.uiAccessToken || (uiAllowLan ? generateAccessToken() : void 0);
6549
6850
  return {
6550
6851
  ...current,
6551
6852
  runtime: payload.runtime === "claude" || payload.runtime === "auto" ? payload.runtime : "codex",
6552
6853
  enabledChannels: requestedChannels.filter((channel) => supportedChannels.includes(channel)),
6553
6854
  defaultWorkDir: asString(payload.defaultWorkDir) || current.defaultWorkDir || process.cwd(),
6855
+ defaultWorkspaceRoot: asString(payload.defaultWorkspaceRoot),
6554
6856
  defaultModel: asString(payload.defaultModel),
6555
6857
  defaultMode: payload.defaultMode === "plan" || payload.defaultMode === "ask" ? payload.defaultMode : "code",
6556
6858
  historyMessageLimit: asPositiveInt(payload.historyMessageLimit) || current.historyMessageLimit || 8,
6557
6859
  codexSkipGitRepoCheck: payload.codexSkipGitRepoCheck === true,
6860
+ codexSandboxMode: payload.codexSandboxMode === "read-only" || payload.codexSandboxMode === "danger-full-access" ? payload.codexSandboxMode : "workspace-write",
6861
+ codexReasoningEffort: payload.codexReasoningEffort === "minimal" || payload.codexReasoningEffort === "low" || payload.codexReasoningEffort === "high" || payload.codexReasoningEffort === "xhigh" ? payload.codexReasoningEffort : "medium",
6862
+ uiAllowLan,
6863
+ uiAccessToken,
6558
6864
  autoApprove: payload.autoApprove === true,
6559
6865
  feishuAppId: asString(payload.feishuAppId),
6560
6866
  feishuAppSecret: asString(payload.feishuAppSecret),
6561
6867
  feishuDomain: asString(payload.feishuDomain) || "https://open.feishu.cn",
6562
6868
  feishuAllowedUsers: parseCsv(payload.feishuAllowedUsers),
6563
6869
  feishuStreamingEnabled: payload.feishuStreamingEnabled !== false,
6564
- weixinMediaEnabled: payload.weixinMediaEnabled === true
6870
+ feishuCommandMarkdownEnabled: payload.feishuCommandMarkdownEnabled !== false,
6871
+ weixinMediaEnabled: payload.weixinMediaEnabled === true,
6872
+ weixinCommandMarkdownEnabled: payload.weixinCommandMarkdownEnabled === true
6565
6873
  };
6566
6874
  }
6567
6875
  function getWeixinAccountsPayload() {
@@ -6643,6 +6951,182 @@ async function testCodexConnection(config) {
6643
6951
  clearTimeout(timeout);
6644
6952
  }
6645
6953
  }
6954
+ function renderLoginHtml() {
6955
+ return `<!doctype html>
6956
+ <html lang="zh-CN">
6957
+ <head>
6958
+ <meta charset="utf-8" />
6959
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6960
+ <title>Codex to IM \u767B\u5F55</title>
6961
+ <style>
6962
+ :root {
6963
+ --bg: #f5f7fa;
6964
+ --surface: #ffffff;
6965
+ --border: #e5e7eb;
6966
+ --border-strong: #d0d7e2;
6967
+ --text: #111827;
6968
+ --muted: #667085;
6969
+ --primary: #1677ff;
6970
+ --primary-strong: #0958d9;
6971
+ --danger: #dc2626;
6972
+ }
6973
+ * { box-sizing: border-box; }
6974
+ body {
6975
+ margin: 0;
6976
+ min-height: 100vh;
6977
+ display: grid;
6978
+ place-items: center;
6979
+ padding: 24px;
6980
+ background: var(--bg);
6981
+ color: var(--text);
6982
+ font: 14px/1.5 "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif;
6983
+ }
6984
+ .auth-card {
6985
+ width: min(420px, 100%);
6986
+ background: var(--surface);
6987
+ border: 1px solid var(--border);
6988
+ border-radius: 10px;
6989
+ padding: 24px;
6990
+ }
6991
+ h1 {
6992
+ margin: 0 0 8px;
6993
+ font-size: 24px;
6994
+ line-height: 1.2;
6995
+ }
6996
+ p {
6997
+ margin: 0 0 18px;
6998
+ color: var(--muted);
6999
+ }
7000
+ label {
7001
+ display: grid;
7002
+ gap: 6px;
7003
+ color: var(--muted);
7004
+ font-weight: 500;
7005
+ }
7006
+ input {
7007
+ width: 100%;
7008
+ border: 1px solid var(--border-strong);
7009
+ border-radius: 8px;
7010
+ padding: 10px 12px;
7011
+ font: inherit;
7012
+ }
7013
+ input:focus {
7014
+ outline: 2px solid rgba(22, 119, 255, 0.14);
7015
+ border-color: var(--primary);
7016
+ }
7017
+ button {
7018
+ margin-top: 16px;
7019
+ width: 100%;
7020
+ border: 1px solid var(--primary);
7021
+ background: var(--primary);
7022
+ color: #ffffff;
7023
+ border-radius: 8px;
7024
+ padding: 10px 14px;
7025
+ font: inherit;
7026
+ cursor: pointer;
7027
+ }
7028
+ button:hover {
7029
+ background: var(--primary-strong);
7030
+ border-color: var(--primary-strong);
7031
+ }
7032
+ .message {
7033
+ display: none;
7034
+ margin-top: 14px;
7035
+ padding: 10px 12px;
7036
+ border-radius: 8px;
7037
+ background: rgba(220, 38, 38, 0.08);
7038
+ color: var(--danger);
7039
+ }
7040
+ .message.show { display: block; }
7041
+ </style>
7042
+ </head>
7043
+ <body>
7044
+ <section class="auth-card">
7045
+ <h1>\u8BBF\u95EE Codex to IM</h1>
7046
+ <p>\u5F53\u524D\u5DE5\u4F5C\u53F0\u5DF2\u5F00\u542F\u5C40\u57DF\u7F51\u8BBF\u95EE\u3002\u8BF7\u8F93\u5165\u8BBF\u95EE token\uFF0C\u9A8C\u8BC1\u901A\u8FC7\u540E\u624D\u80FD\u67E5\u770B\u548C\u4FEE\u6539\u914D\u7F6E\u3002</p>
7047
+ <form id="loginForm">
7048
+ <label>
7049
+ \u8BBF\u95EE token
7050
+ <input id="token" name="token" autocomplete="off" spellcheck="false" />
7051
+ </label>
7052
+ <button type="submit">\u767B\u5F55</button>
7053
+ </form>
7054
+ <div class="message" id="message"></div>
7055
+ </section>
7056
+ <script>
7057
+ const form = document.getElementById('loginForm');
7058
+ const message = document.getElementById('message');
7059
+ const tokenInput = document.getElementById('token');
7060
+ form.addEventListener('submit', async (event) => {
7061
+ event.preventDefault();
7062
+ message.className = 'message';
7063
+ message.textContent = '';
7064
+
7065
+ try {
7066
+ const response = await fetch('/api/auth/login', {
7067
+ method: 'POST',
7068
+ headers: { 'Content-Type': 'application/json' },
7069
+ body: JSON.stringify({ token: tokenInput.value }),
7070
+ });
7071
+ const text = await response.text();
7072
+ const data = text ? JSON.parse(text) : {};
7073
+ if (!response.ok) {
7074
+ throw new Error(data.error || '\u767B\u5F55\u5931\u8D25');
7075
+ }
7076
+ window.location.href = '/';
7077
+ } catch (error) {
7078
+ message.className = 'message show';
7079
+ message.textContent = error instanceof Error ? error.message : String(error);
7080
+ }
7081
+ });
7082
+ </script>
7083
+ </body>
7084
+ </html>`;
7085
+ }
7086
+ function renderAccessDeniedHtml() {
7087
+ return `<!doctype html>
7088
+ <html lang="zh-CN">
7089
+ <head>
7090
+ <meta charset="utf-8" />
7091
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7092
+ <title>Codex to IM</title>
7093
+ <style>
7094
+ body {
7095
+ margin: 0;
7096
+ min-height: 100vh;
7097
+ display: grid;
7098
+ place-items: center;
7099
+ padding: 24px;
7100
+ background: #f5f7fa;
7101
+ color: #111827;
7102
+ font: 14px/1.5 "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif;
7103
+ }
7104
+ .card {
7105
+ width: min(420px, 100%);
7106
+ background: #ffffff;
7107
+ border: 1px solid #e5e7eb;
7108
+ border-radius: 10px;
7109
+ padding: 24px;
7110
+ }
7111
+ h1 {
7112
+ margin: 0 0 8px;
7113
+ font-size: 24px;
7114
+ line-height: 1.2;
7115
+ }
7116
+ p {
7117
+ margin: 0;
7118
+ color: #667085;
7119
+ }
7120
+ </style>
7121
+ </head>
7122
+ <body>
7123
+ <section class="card">
7124
+ <h1>\u5F53\u524D\u672A\u5F00\u653E\u5C40\u57DF\u7F51\u8BBF\u95EE</h1>
7125
+ <p>\u8FD9\u4E2A Web \u5DE5\u4F5C\u53F0\u76EE\u524D\u53EA\u5141\u8BB8\u672C\u673A\u8BBF\u95EE\u3002\u8BF7\u5148\u5728\u672C\u673A\u914D\u7F6E\u9875\u4E2D\u52FE\u9009\u201C\u5141\u8BB8\u5C40\u57DF\u7F51\u8BBF\u95EE Web \u63A7\u5236\u53F0\u201D\u3002</p>
7126
+ </section>
7127
+ </body>
7128
+ </html>`;
7129
+ }
6646
7130
  function renderHtml() {
6647
7131
  return `<!doctype html>
6648
7132
  <html lang="zh-CN">
@@ -6956,43 +7440,80 @@ function renderHtml() {
6956
7440
 
6957
7441
  .message {
6958
7442
  display: none;
6959
- margin-top: 14px;
6960
- padding: 10px 12px;
6961
- border-radius: 8px;
6962
7443
  }
6963
7444
 
6964
- .message.show { display: block; }
6965
- .message.success { background: rgba(21, 128, 61, 0.10); color: var(--success); }
6966
- .message.error { background: rgba(220, 38, 38, 0.10); color: var(--danger); }
6967
-
6968
7445
  .global-message-host {
6969
7446
  position: fixed;
6970
- top: 20px;
7447
+ top: 18px;
6971
7448
  left: 50%;
6972
7449
  transform: translateX(-50%);
6973
7450
  display: grid;
6974
- gap: 10px;
6975
- z-index: 2000;
7451
+ gap: 12px;
7452
+ z-index: 2400;
6976
7453
  pointer-events: none;
6977
7454
  }
6978
7455
 
6979
7456
  .global-message {
6980
- min-width: 240px;
6981
- max-width: min(560px, calc(100vw - 32px));
6982
- padding: 10px 14px;
6983
- border-radius: 8px;
6984
- border: 1px solid var(--border);
6985
- background: rgba(255, 255, 255, 0.96);
7457
+ display: inline-flex;
7458
+ align-items: center;
7459
+ gap: 10px;
7460
+ min-width: 260px;
7461
+ max-width: min(640px, calc(100vw - 32px));
7462
+ padding: 11px 14px;
7463
+ border-radius: 10px;
7464
+ border: 1px solid rgba(208, 215, 226, 0.88);
7465
+ background: rgba(255, 255, 255, 0.98);
6986
7466
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
6987
7467
  color: var(--text);
7468
+ line-height: 1.45;
7469
+ animation: message-enter 160ms ease;
6988
7470
  }
6989
7471
 
6990
7472
  .global-message.success {
6991
- border-color: rgba(21, 128, 61, 0.16);
7473
+ border-color: rgba(22, 163, 74, 0.22);
6992
7474
  }
6993
7475
 
6994
7476
  .global-message.error {
6995
- border-color: rgba(220, 38, 38, 0.16);
7477
+ border-color: rgba(220, 38, 38, 0.24);
7478
+ }
7479
+
7480
+ .global-message-icon {
7481
+ flex: 0 0 auto;
7482
+ width: 18px;
7483
+ height: 18px;
7484
+ border-radius: 999px;
7485
+ display: inline-flex;
7486
+ align-items: center;
7487
+ justify-content: center;
7488
+ font-size: 12px;
7489
+ font-weight: 700;
7490
+ background: rgba(15, 23, 42, 0.06);
7491
+ }
7492
+
7493
+ .global-message.success .global-message-icon {
7494
+ color: var(--success);
7495
+ background: rgba(22, 163, 74, 0.12);
7496
+ }
7497
+
7498
+ .global-message.error .global-message-icon {
7499
+ color: var(--danger);
7500
+ background: rgba(220, 38, 38, 0.10);
7501
+ }
7502
+
7503
+ .global-message-content {
7504
+ min-width: 0;
7505
+ word-break: break-word;
7506
+ }
7507
+
7508
+ @keyframes message-enter {
7509
+ from {
7510
+ opacity: 0;
7511
+ transform: translateY(-6px);
7512
+ }
7513
+ to {
7514
+ opacity: 1;
7515
+ transform: translateY(0);
7516
+ }
6996
7517
  }
6997
7518
 
6998
7519
  .info-list {
@@ -7210,11 +7731,24 @@ function renderHtml() {
7210
7731
  display: grid;
7211
7732
  }
7212
7733
 
7734
+ .command-list-head,
7213
7735
  .command-item {
7214
7736
  display: grid;
7215
- grid-template-columns: 280px minmax(0, 1fr);
7737
+ grid-template-columns: 220px 320px minmax(0, 1fr);
7216
7738
  gap: 16px;
7217
7739
  padding: 10px 14px;
7740
+ align-items: start;
7741
+ }
7742
+
7743
+ .command-list-head {
7744
+ padding-top: 12px;
7745
+ padding-bottom: 8px;
7746
+ color: var(--muted);
7747
+ font-size: 12px;
7748
+ font-weight: 700;
7749
+ text-transform: uppercase;
7750
+ letter-spacing: .04em;
7751
+ background: #fcfcfd;
7218
7752
  border-top: 1px solid var(--border);
7219
7753
  }
7220
7754
 
@@ -7226,6 +7760,15 @@ function renderHtml() {
7226
7760
  word-break: break-all;
7227
7761
  }
7228
7762
 
7763
+ .command-col-command,
7764
+ .command-col-original {
7765
+ min-width: 0;
7766
+ }
7767
+
7768
+ .command-col-desc {
7769
+ color: #475467;
7770
+ }
7771
+
7229
7772
  .channel-tab {
7230
7773
  border: 0;
7231
7774
  border-bottom: 2px solid transparent;
@@ -7398,6 +7941,7 @@ function renderHtml() {
7398
7941
  .field-row,
7399
7942
  .field-row.triple,
7400
7943
  .command-item,
7944
+ .command-list-head,
7401
7945
  .binding-controls { grid-template-columns: 1fr; }
7402
7946
  .status-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
7403
7947
  }
@@ -7539,7 +8083,7 @@ function renderHtml() {
7539
8083
  </div>
7540
8084
 
7541
8085
  <section class="panel" id="desktop">
7542
- <div class="notice">\u8FD9\u91CC\u53EA\u5C55\u793A\u5728 Codex \u684C\u9762\u7D22\u5F15\u91CC\u6709\u540D\u5B57\u7684\u7EBF\u7A0B\uFF0C\u548C Windows App \u5DE6\u4FA7\u5217\u8868\u4FDD\u6301\u4E00\u81F4\u3002</div>
8086
+ <div class="notice">\u8FD9\u91CC\u53EA\u5C55\u793A\u5728 Codex \u684C\u9762\u7D22\u5F15\u91CC\u6709\u540D\u5B57\u7684\u7EBF\u7A0B\uFF0C\u548C Codex Desktop App \u5DE6\u4FA7\u5217\u8868\u4FDD\u6301\u4E00\u81F4\u3002</div>
7543
8087
  <div class="notice" style="margin-top: 12px;">\u6700\u77ED\u8DEF\u5F84\uFF1A\u627E\u5230\u76EE\u6807 thread\uFF0C\u7136\u540E\u628A <code>/thread 019d1da4</code> \u8FD9\u6837\u7684\u547D\u4EE4\u53D1\u7ED9\u98DE\u4E66\u673A\u5668\u4EBA\uFF0C\u6216\u76F4\u63A5\u5230\u201C\u901A\u9053\u201D\u9875\u5207\u6362\u7ED1\u5B9A\u3002</div>
7544
8088
  <div class="small" id="desktopSessionMeta" style="margin: 14px 0 16px;">\u6B63\u5728\u52A0\u8F7D\u2026</div>
7545
8089
  <div class="session-list" id="desktopSessionsList"></div>
@@ -7559,7 +8103,7 @@ function renderHtml() {
7559
8103
  <div class="panel-header">
7560
8104
  <div>
7561
8105
  <h2>\u57FA\u7840\u914D\u7F6E</h2>
7562
- <p>\u4FDD\u5B58\u540E\u4F1A\u5199\u5165\u672C\u5730\u914D\u7F6E\u76EE\u5F55\uFF1B\u6D89\u53CA\u8FD0\u884C\u65F6\u7684\u9009\u9879\u901A\u5E38\u9700\u8981\u91CD\u542F Bridge \u624D\u751F\u6548\u3002</p>
8106
+ <p>\u4FDD\u5B58\u540E\u4F1A\u5199\u5165\u672C\u5730\u914D\u7F6E\u76EE\u5F55\u3002\u9ED8\u8BA4\u76EE\u5F55\u3001\u9ED8\u8BA4\u5DE5\u4F5C\u7A7A\u95F4\u3001Sandbox\u3001\u601D\u8003\u7EA7\u522B\u7B49\u4F1A\u5728\u4E0B\u4E00\u6B21\u8BF7\u6C42\u751F\u6548\uFF1B\u901A\u9053\u542F\u505C\u4F1A\u81EA\u52A8\u540C\u6B65\uFF1B\u53EA\u6709\u5C11\u6570\u8FD0\u884C\u65F6\u914D\u7F6E\u9700\u8981\u91CD\u542F Bridge\u3002</p>
7563
8107
  </div>
7564
8108
  <div class="toolbar">
7565
8109
  <button class="primary" id="saveConfigBtn">\u4FDD\u5B58\u914D\u7F6E</button>
@@ -7593,10 +8137,36 @@ function renderHtml() {
7593
8137
  \u9ED8\u8BA4\u5DE5\u4F5C\u76EE\u5F55
7594
8138
  <input id="defaultWorkDir" placeholder="D:\\workspace\\project" />
7595
8139
  </label>
8140
+ <label>
8141
+ \u9ED8\u8BA4\u5DE5\u4F5C\u7A7A\u95F4
8142
+ <input id="defaultWorkspaceRoot" placeholder="\u7559\u7A7A\u65F6\u4F7F\u7528 ~/cx2im" />
8143
+ </label>
7596
8144
  <label>
7597
8145
  \u9ED8\u8BA4\u6A21\u578B
7598
8146
  <input id="defaultModel" placeholder="\u7559\u7A7A\u5219\u4F7F\u7528 runtime \u9ED8\u8BA4\u6A21\u578B" />
7599
8147
  </label>
8148
+ <div class="field-row">
8149
+ <label>
8150
+ Codex \u6587\u4EF6\u7CFB\u7EDF\u6743\u9650
8151
+ <select id="codexSandboxMode">
8152
+ <option value="workspace-write">workspace-write</option>
8153
+ <option value="read-only">read-only</option>
8154
+ <option value="danger-full-access">danger-full-access</option>
8155
+ </select>
8156
+ </label>
8157
+ <label>
8158
+ Codex \u601D\u8003\u7EA7\u522B
8159
+ <select id="codexReasoningEffort">
8160
+ <option value="medium">medium</option>
8161
+ <option value="minimal">minimal</option>
8162
+ <option value="low">low</option>
8163
+ <option value="high">high</option>
8164
+ <option value="xhigh">xhigh</option>
8165
+ </select>
8166
+ </label>
8167
+ </div>
8168
+ <div class="small">\u201C\u9ED8\u8BA4\u5DE5\u4F5C\u76EE\u5F55\u201D\u7528\u4E8E\u65B0\u4F1A\u8BDD\u9ED8\u8BA4 cwd\uFF1B\u201C\u9ED8\u8BA4\u5DE5\u4F5C\u7A7A\u95F4\u201D\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\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>
8169
+ <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>
7600
8170
  <div class="checkbox-row">
7601
8171
  <label class="checkbox"><input id="channelFeishu" type="checkbox" checked /> \u542F\u7528\u98DE\u4E66</label>
7602
8172
  <label class="checkbox"><input id="channelWeixin" type="checkbox" /> \u542F\u7528\u5FAE\u4FE1</label>
@@ -7606,6 +8176,28 @@ function renderHtml() {
7606
8176
  <label class="checkbox"><input id="codexSkipGitRepoCheck" type="checkbox" checked /> \u5141\u8BB8\u5728\u672A\u4FE1\u4EFB Git \u76EE\u5F55\u8FD0\u884C Codex</label>
7607
8177
  </div>
7608
8178
  <div class="small">\u5982\u679C\u65B0\u5EFA\u4F1A\u8BDD\u62A5 \u201CNot inside a trusted directory\u201D\uFF0C\u53EF\u4EE5\u6253\u5F00\u8FD9\u4E2A\u9009\u9879\u3002\u4FEE\u6539\u540E\u9700\u8981\u91CD\u542F Bridge \u624D\u4F1A\u751F\u6548\u3002</div>
8179
+ <div class="checkbox-row" style="margin-top: 12px;">
8180
+ <label class="checkbox"><input id="uiAllowLan" type="checkbox" /> \u5141\u8BB8\u5C40\u57DF\u7F51\u8BBF\u95EE Web \u63A7\u5236\u53F0</label>
8181
+ </div>
8182
+ <div class="notice" id="uiAccessSummary">\u9ED8\u8BA4\u4EC5\u5141\u8BB8\u672C\u673A\u8BBF\u95EE\u5F53\u524D\u5DE5\u4F5C\u53F0\u3002</div>
8183
+ <div id="uiLanDetails" hidden>
8184
+ <div class="field-row" style="margin-top: 16px;">
8185
+ <label>
8186
+ \u8BBF\u95EE token
8187
+ <input id="uiAccessToken" readonly />
8188
+ </label>
8189
+ <div style="display: grid; gap: 10px; align-content: end;">
8190
+ <div class="toolbar">
8191
+ <button type="button" id="copyUiTokenBtn">\u590D\u5236 token</button>
8192
+ <button type="button" id="regenerateUiTokenBtn">\u91CD\u65B0\u751F\u6210 token</button>
8193
+ </div>
8194
+ <div class="toolbar">
8195
+ <button type="button" id="copyUiLanLinkBtn">\u590D\u5236\u5C40\u57DF\u7F51\u767B\u5F55\u94FE\u63A5</button>
8196
+ </div>
8197
+ </div>
8198
+ </div>
8199
+ <div class="info-list" id="uiAccessUrls" style="margin-top: 16px;"></div>
8200
+ </div>
7609
8201
  </div>
7610
8202
 
7611
8203
  <div class="message" id="configMessage"></div>
@@ -7628,45 +8220,42 @@ function renderHtml() {
7628
8220
  </div>
7629
8221
  </div>
7630
8222
 
7631
- <div class="notice" style="margin-bottom: 16px;">\u6700\u77ED\u4F7F\u7528\u8DEF\u5F84\uFF1A\u5148\u53D1 <code>/threads</code> \u67E5\u770B\u6700\u8FD1\u684C\u9762\u4F1A\u8BDD\uFF0C\u518D\u53D1 <code>/thread 1</code> \u63A5\u7BA1\uFF1B\u4E4B\u540E\u76F4\u63A5\u53D1\u9001\u6587\u672C\u5373\u53EF\u7EE7\u7EED\u5F53\u524D\u4F1A\u8BDD\u3002</div>
8223
+ <div class="notice" style="margin-bottom: 16px;">\u6700\u77ED\u4F7F\u7528\u8DEF\u5F84\uFF1A\u5148\u53D1 <code>/t</code> \u67E5\u770B\u6700\u8FD1\u4F1A\u8BDD\uFF0C\u518D\u53D1 <code>/t 1</code> \u63A5\u7BA1\uFF1B\u4E4B\u540E\u76F4\u63A5\u53D1\u9001\u6587\u672C\u5373\u53EF\u7EE7\u7EED\u5F53\u524D\u4F1A\u8BDD\u3002\u8FD9\u91CC\u4FDD\u7559\u539F\u59CB\u547D\u4EE4\uFF0C\u4EC5\u7528\u4E8E\u540E\u53F0\u67E5\u9605\u548C\u517C\u5BB9\u65E7\u7528\u6CD5\u3002</div>
7632
8224
 
7633
8225
  <div class="command-sections">
7634
8226
  <section class="command-section">
7635
8227
  <h3 class="command-section-title">\u6700\u5E38\u7528</h3>
7636
8228
  <div class="command-list">
7637
- <div class="command-item"><code>/help</code><div>\u67E5\u770B\u5B8C\u6574\u547D\u4EE4\u8BF4\u660E\u3002</div></div>
7638
- <div class="command-item"><code>/threads</code><div>\u5217\u51FA\u6700\u8FD1\u684C\u9762\u4F1A\u8BDD\u3002</div></div>
7639
- <div class="command-item"><code>/thread &lt;thread-id | \u5E8F\u53F7&gt;</code><div>\u7ED1\u5B9A\u684C\u9762 thread\uFF0C\u63A5\u7BA1\u684C\u9762\u4F1A\u8BDD\u3002</div></div>
7640
- <div class="command-item"><code>\u76F4\u63A5\u53D1\u9001\u6587\u672C</code><div>\u7EE7\u7EED\u5F53\u524D\u5DF2\u7ED1\u5B9A\u4F1A\u8BDD\u3002</div></div>
7641
- <div class="command-item"><code>/status</code><div>\u67E5\u770B\u5F53\u524D\u804A\u5929\u7ED1\u5B9A\u5230\u4E86\u54EA\u6761\u4F1A\u8BDD\u3001thread\u3001\u76EE\u5F55\u548C\u6A21\u5F0F\u3002</div></div>
7642
- <div class="command-item"><code>/history</code><div>\u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u6700\u8FD1 N \u6761\u6D88\u606F\u3002</div></div>
7643
- </div>
7644
- </section>
7645
-
7646
- <section class="command-section">
7647
- <h3 class="command-section-title">\u5207\u6362\u4E0E\u7ED1\u5B9A</h3>
7648
- <div class="command-list">
7649
- <div class="command-item"><code>/sessions</code><div>\u5217\u51FA\u5185\u90E8\u4F1A\u8BDD\u3002</div></div>
7650
- <div class="command-item"><code>/use &lt;session-id | \u5E8F\u53F7&gt;</code><div>\u5207\u6362\u5230\u5DF2\u6709\u5185\u90E8\u4F1A\u8BDD\u3002</div></div>
7651
- <div class="command-item"><code>/bind &lt;session-id | thread-id | \u5E8F\u53F7&gt;</code><div>\u667A\u80FD\u7ED1\u5B9A\uFF0C\u517C\u5BB9\u65E7\u7528\u6CD5\uFF1B\u53EF\u7ED1\u5B9A\u5185\u90E8\u4F1A\u8BDD\u6216\u684C\u9762 thread\u3002</div></div>
8229
+ <div class="command-list-head"><div>\u547D\u4EE4</div><div>\u539F\u59CB\u547D\u4EE4</div><div>\u8BF4\u660E</div></div>
8230
+ <div class="command-item"><div class="command-col-command"><code>/</code></div><div class="command-col-original"><code>/status</code></div><div class="command-col-desc">\u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u3002</div></div>
8231
+ <div class="command-item"><div class="command-col-command"><code>/h</code></div><div class="command-col-original"><code>/help</code></div><div class="command-col-desc">\u67E5\u770B\u5E2E\u52A9\u3002</div></div>
8232
+ <div class="command-item"><div class="command-col-command"><code>/t</code></div><div class="command-col-original"><code>/threads</code></div><div class="command-col-desc">\u5217\u51FA\u6700\u8FD1\u684C\u9762\u4F1A\u8BDD\u3002</div></div>
8233
+ <div class="command-item"><div class="command-col-command"><code>/t &lt;\u5E8F\u53F7&gt;</code></div><div class="command-col-original"><code>/thread &lt;\u5E8F\u53F7&gt;</code></div><div class="command-col-desc">\u6309\u5E8F\u53F7\u63A5\u7BA1\u684C\u9762\u4F1A\u8BDD\u3002</div></div>
8234
+ <div class="command-item"><div class="command-col-command"><code>/n [\u7EDD\u5BF9\u8DEF\u5F84 | \u9879\u76EE\u540D]</code></div><div class="command-col-original"><code>/new [\u7EDD\u5BF9\u8DEF\u5F84 | \u9879\u76EE\u540D]</code></div><div class="command-col-desc">\u65B0\u5EFA\u4F1A\u8BDD\uFF1B\u76F8\u5BF9\u9879\u76EE\u540D\u4F1A\u5728\u201C\u9ED8\u8BA4\u5DE5\u4F5C\u7A7A\u95F4\u201D\u4E0B\u521B\u5EFA\u76EE\u5F55\u3002</div></div>
8235
+ <div class="command-item"><div class="command-col-command"><code>\u76F4\u63A5\u53D1\u9001\u6587\u672C</code></div><div class="command-col-original">\u2014</div><div class="command-col-desc">\u7EE7\u7EED\u5F53\u524D\u5DF2\u7ED1\u5B9A\u4F1A\u8BDD\u3002</div></div>
8236
+ <div class="command-item"><div class="command-col-command"><code>/his</code></div><div class="command-col-original"><code>/history</code></div><div class="command-col-desc">\u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u6574\u7406\u540E\u7684\u6458\u8981\u3002</div></div>
8237
+ <div class="command-item"><div class="command-col-command"><code>/his raw</code></div><div class="command-col-original"><code>/history raw</code></div><div class="command-col-desc">\u67E5\u770B\u6700\u8FD1 N \u6761\u539F\u59CB\u6D88\u606F\u3002</div></div>
7652
8238
  </div>
7653
8239
  </section>
7654
8240
 
7655
8241
  <section class="command-section">
7656
- <h3 class="command-section-title">\u4F1A\u8BDD\u8BBE\u7F6E</h3>
8242
+ <h3 class="command-section-title">\u8BBE\u7F6E\u4E0E\u5207\u6362</h3>
7657
8243
  <div class="command-list">
7658
- <div class="command-item"><code>/new [\u7EDD\u5BF9\u8DEF\u5F84]</code><div>\u65B0\u5EFA\u4F1A\u8BDD\uFF1B\u53EF\u9009\u5730\u6307\u5B9A\u5DE5\u4F5C\u76EE\u5F55\u3002</div></div>
7659
- <div class="command-item"><code>/cwd /path/to/project</code><div>\u4FEE\u6539\u5F53\u524D\u4F1A\u8BDD\u5DE5\u4F5C\u76EE\u5F55\u3002</div></div>
7660
- <div class="command-item"><code>/mode plan|code|ask</code><div>\u4FEE\u6539\u5F53\u524D\u4F1A\u8BDD\u6A21\u5F0F\u3002</div></div>
7661
- <div class="command-item"><code>/stop</code><div>\u505C\u6B62\u5F53\u524D\u4EFB\u52A1\u3002</div></div>
8244
+ <div class="command-list-head"><div>\u547D\u4EE4</div><div>\u539F\u59CB\u547D\u4EE4</div><div>\u8BF4\u660E</div></div>
8245
+ <div class="command-item"><div class="command-col-command"><code>/m</code></div><div class="command-col-original"><code>/mode</code></div><div class="command-col-desc">\u67E5\u770B\u5F53\u524D\u6A21\u5F0F\uFF1B\u53EF\u9009 <code>code</code>\u3001<code>plan</code>\u3001<code>ask</code>\u3002</div></div>
8246
+ <div class="command-item"><div class="command-col-command"><code>/r</code></div><div class="command-col-original"><code>/reasoning</code></div><div class="command-col-desc">\u67E5\u770B\u5F53\u524D\u601D\u8003\u7EA7\u522B\uFF1B\u53EF\u9009 <code>1=minimal</code>\u3001<code>2=low</code>\u3001<code>3=medium</code>\u3001<code>4=high</code>\u3001<code>5=xhigh</code>\u3002</div></div>
8247
+ <div class="command-item"><div class="command-col-command"><code>/t 0</code></div><div class="command-col-original"><code>/thread 0</code></div><div class="command-col-desc">\u5207\u6362\u5230\u5F53\u524D\u804A\u5929\u7684\u4E34\u65F6\u8349\u7A3F\u7EBF\u7A0B\u3002</div></div>
8248
+ <div class="command-item"><div class="command-col-command"><code>/t 0 reset</code></div><div class="command-col-original"><code>/thread 0 reset</code></div><div class="command-col-desc">\u4E22\u5F03\u5F53\u524D\u8349\u7A3F\u4E0A\u4E0B\u6587\u5E76\u91CD\u5EFA\u4E00\u6761\u65B0\u7684\u8349\u7A3F\u7EBF\u7A0B\u3002</div></div>
8249
+ <div class="command-item"><div class="command-col-command">\u2014</div><div class="command-col-original"><code>/stop</code></div><div class="command-col-desc">\u505C\u6B62\u5F53\u524D\u4EFB\u52A1\u3002</div></div>
7662
8250
  </div>
7663
8251
  </section>
7664
8252
 
7665
8253
  <section class="command-section">
7666
8254
  <h3 class="command-section-title">\u6743\u9650</h3>
7667
8255
  <div class="command-list">
7668
- <div class="command-item"><code>/perm allow|allow_session|deny &lt;id&gt;</code><div>\u6587\u672C\u65B9\u5F0F\u5904\u7406\u4E00\u4E2A\u5F85\u6279\u51C6\u6743\u9650\u3002</div></div>
7669
- <div class="command-item"><code>1 / 2 / 3</code><div>\u5FEB\u901F\u5904\u7406\u5355\u4E2A\u5F85\u6279\u51C6\u6743\u9650\u3002</div></div>
8256
+ <div class="command-list-head"><div>\u547D\u4EE4</div><div>\u539F\u59CB\u547D\u4EE4</div><div>\u8BF4\u660E</div></div>
8257
+ <div class="command-item"><div class="command-col-command">\u2014</div><div class="command-col-original"><code>/perm allow|allow_session|deny &lt;id&gt;</code></div><div class="command-col-desc">\u6587\u672C\u65B9\u5F0F\u5904\u7406\u4E00\u4E2A\u5F85\u6279\u51C6\u6743\u9650\u3002</div></div>
8258
+ <div class="command-item"><div class="command-col-command"><code>1 / 2 / 3</code></div><div class="command-col-original">\u2014</div><div class="command-col-desc">\u5FEB\u901F\u5904\u7406\u5355\u4E2A\u5F85\u6279\u51C6\u6743\u9650\u3002</div></div>
7670
8259
  </div>
7671
8260
  </section>
7672
8261
  </div>
@@ -7697,6 +8286,7 @@ function renderHtml() {
7697
8286
  <div class="toolbar">
7698
8287
  <button id="saveFeishuChannelBtn">\u4FDD\u5B58\u901A\u9053\u914D\u7F6E</button>
7699
8288
  <button id="testFeishuBtn">\u6D4B\u8BD5\u98DE\u4E66\u51ED\u636E</button>
8289
+ <button id="refreshFeishuStateBtn">\u5237\u65B0\u72B6\u6001</button>
7700
8290
  </div>
7701
8291
  </div>
7702
8292
 
@@ -7724,7 +8314,12 @@ function renderHtml() {
7724
8314
  <div class="checkbox-row">
7725
8315
  <label class="checkbox"><input id="feishuStreamingEnabled" type="checkbox" checked /> \u542F\u7528\u98DE\u4E66\u6D41\u5F0F\u54CD\u5E94\u5361\u7247</label>
7726
8316
  </div>
8317
+ <div class="checkbox-row">
8318
+ <label class="checkbox"><input id="feishuCommandMarkdownEnabled" type="checkbox" checked /> \u547D\u4EE4\u53CD\u9988\u4F7F\u7528 Markdown</label>
8319
+ </div>
7727
8320
  <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>
8321
+ <div class="small">\u53EA\u5F71\u54CD <code>/h</code>\u3001<code>/status</code>\u3001<code>/threads</code> \u8FD9\u7C7B\u7CFB\u7EDF\u53CD\u9988\uFF0C\u4E0D\u5F71\u54CD Codex \u539F\u59CB\u56DE\u590D\u3002</div>
8322
+ <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>
7728
8323
  </div>
7729
8324
 
7730
8325
  <div class="panel-block">
@@ -7751,6 +8346,7 @@ function renderHtml() {
7751
8346
  <div class="toolbar">
7752
8347
  <button id="saveWeixinChannelBtn">\u4FDD\u5B58\u901A\u9053\u914D\u7F6E</button>
7753
8348
  <button id="weixinLoginBtn">\u5F00\u59CB\u5FAE\u4FE1\u626B\u7801</button>
8349
+ <button id="refreshWeixinStateBtn">\u5237\u65B0\u72B6\u6001</button>
7754
8350
  </div>
7755
8351
  </div>
7756
8352
 
@@ -7758,7 +8354,11 @@ function renderHtml() {
7758
8354
  <div class="checkbox-row">
7759
8355
  <label class="checkbox"><input id="weixinMediaEnabled" type="checkbox" /> \u542F\u7528\u56FE\u7247 / \u6587\u4EF6 / \u89C6\u9891\u5165\u7AD9\u4E0B\u8F7D</label>
7760
8356
  </div>
8357
+ <div class="checkbox-row">
8358
+ <label class="checkbox"><input id="weixinCommandMarkdownEnabled" type="checkbox" /> \u547D\u4EE4\u53CD\u9988\u4F7F\u7528 Markdown</label>
8359
+ </div>
7761
8360
  </div>
8361
+ <div class="small">\u53EA\u5F71\u54CD <code>/h</code>\u3001<code>/status</code>\u3001<code>/threads</code> \u8FD9\u7C7B\u7CFB\u7EDF\u53CD\u9988\uFF0C\u4E0D\u5F71\u54CD Codex \u539F\u59CB\u56DE\u590D\u3002\u9ED8\u8BA4\u5173\u95ED\u3002</div>
7762
8362
  <div class="panel-block">
7763
8363
  <p class="panel-subtitle">\u901A\u9053\u72B6\u6001</p>
7764
8364
  <div class="small" id="weixinRuntimeMeta">\u6B63\u5728\u52A0\u8F7D\u2026</div>
@@ -7801,6 +8401,7 @@ function renderHtml() {
7801
8401
  <script>
7802
8402
  const state = {
7803
8403
  config: null,
8404
+ uiAccess: null,
7804
8405
  bridgeStatus: null,
7805
8406
  desktopSessions: [],
7806
8407
  bindings: [],
@@ -7890,8 +8491,13 @@ function renderHtml() {
7890
8491
  defaultMode: document.getElementById('defaultMode').value,
7891
8492
  historyMessageLimit: document.getElementById('historyMessageLimit').value,
7892
8493
  defaultWorkDir: document.getElementById('defaultWorkDir').value,
8494
+ defaultWorkspaceRoot: document.getElementById('defaultWorkspaceRoot').value,
7893
8495
  defaultModel: document.getElementById('defaultModel').value,
7894
8496
  codexSkipGitRepoCheck: document.getElementById('codexSkipGitRepoCheck').checked,
8497
+ codexSandboxMode: document.getElementById('codexSandboxMode').value,
8498
+ codexReasoningEffort: document.getElementById('codexReasoningEffort').value,
8499
+ uiAllowLan: document.getElementById('uiAllowLan').checked,
8500
+ uiAccessToken: document.getElementById('uiAccessToken').value,
7895
8501
  enabledChannels: enabledChannelsFromForm(),
7896
8502
  autoApprove: document.getElementById('autoApprove').checked,
7897
8503
  feishuAppId: document.getElementById('feishuAppId').value,
@@ -7899,15 +8505,19 @@ function renderHtml() {
7899
8505
  feishuDomain: document.getElementById('feishuDomain').value,
7900
8506
  feishuAllowedUsers: document.getElementById('feishuAllowedUsers').value,
7901
8507
  feishuStreamingEnabled: document.getElementById('feishuStreamingEnabled').checked,
8508
+ feishuCommandMarkdownEnabled: document.getElementById('feishuCommandMarkdownEnabled').checked,
7902
8509
  weixinMediaEnabled: document.getElementById('weixinMediaEnabled').checked,
8510
+ weixinCommandMarkdownEnabled: document.getElementById('weixinCommandMarkdownEnabled').checked,
7903
8511
  };
7904
8512
  }
7905
8513
 
7906
8514
  function showMessage(id, type, message) {
7907
8515
  const node = document.getElementById(id);
7908
- if (!node) return;
7909
- node.className = 'message show ' + type;
7910
- node.textContent = message;
8516
+ if (node) {
8517
+ node.className = 'message';
8518
+ node.textContent = '';
8519
+ }
8520
+ showGlobalMessage(type, message);
7911
8521
  }
7912
8522
 
7913
8523
  function showGlobalMessage(type, message) {
@@ -7916,7 +8526,16 @@ function renderHtml() {
7916
8526
 
7917
8527
  const item = document.createElement('div');
7918
8528
  item.className = 'global-message ' + (type || 'success');
7919
- item.textContent = message;
8529
+ const icon = document.createElement('span');
8530
+ icon.className = 'global-message-icon';
8531
+ icon.textContent = type === 'error' ? '!' : '\u2713';
8532
+
8533
+ const content = document.createElement('div');
8534
+ content.className = 'global-message-content';
8535
+ content.textContent = message;
8536
+
8537
+ item.appendChild(icon);
8538
+ item.appendChild(content);
7920
8539
  host.appendChild(item);
7921
8540
 
7922
8541
  window.setTimeout(() => {
@@ -8043,11 +8662,136 @@ function renderHtml() {
8043
8662
  return Boolean(state.bridgeStatus && state.bridgeStatus.running && runningChannels().includes(channelType));
8044
8663
  }
8045
8664
 
8046
- function bridgeNeedsRestart() {
8047
- if (!state.config || !state.bridgeStatus || !state.bridgeStatus.running) return false;
8048
- const configured = (state.config.enabledChannels || []).slice().sort().join(',');
8049
- const running = runningChannels().slice().sort().join(',');
8050
- return configured !== running;
8665
+ const CONFIG_FIELD_LABELS = {
8666
+ runtime: 'Runtime',
8667
+ enabledChannels: '\u901A\u9053\u542F\u7528\u72B6\u6001',
8668
+ defaultWorkDir: '\u9ED8\u8BA4\u5DE5\u4F5C\u76EE\u5F55',
8669
+ defaultWorkspaceRoot: '\u9ED8\u8BA4\u5DE5\u4F5C\u7A7A\u95F4',
8670
+ defaultModel: '\u9ED8\u8BA4\u6A21\u578B',
8671
+ defaultMode: '\u9ED8\u8BA4\u6A21\u5F0F',
8672
+ historyMessageLimit: '/history \u8FD4\u56DE\u6761\u6570',
8673
+ codexSkipGitRepoCheck: '\u5141\u8BB8\u5728\u672A\u4FE1\u4EFB Git \u76EE\u5F55\u8FD0\u884C Codex',
8674
+ codexSandboxMode: 'Codex \u6587\u4EF6\u7CFB\u7EDF\u6743\u9650',
8675
+ codexReasoningEffort: 'Codex \u601D\u8003\u7EA7\u522B',
8676
+ uiAllowLan: '\u5141\u8BB8\u5C40\u57DF\u7F51\u8BBF\u95EE Web \u63A7\u5236\u53F0',
8677
+ uiAccessToken: '\u5C40\u57DF\u7F51\u8BBF\u95EE token',
8678
+ autoApprove: '\u81EA\u52A8\u6279\u51C6\u5DE5\u5177\u6743\u9650',
8679
+ feishuAppId: '\u98DE\u4E66 App ID',
8680
+ feishuAppSecret: '\u98DE\u4E66 App Secret',
8681
+ feishuDomain: '\u98DE\u4E66 Domain',
8682
+ feishuAllowedUsers: '\u98DE\u4E66 Allowed Users',
8683
+ feishuStreamingEnabled: '\u98DE\u4E66\u6D41\u5F0F\u54CD\u5E94\u5361\u7247',
8684
+ feishuCommandMarkdownEnabled: '\u98DE\u4E66\u547D\u4EE4 Markdown',
8685
+ weixinMediaEnabled: '\u5FAE\u4FE1\u56FE\u7247/\u6587\u4EF6/\u89C6\u9891\u5165\u7AD9\u4E0B\u8F7D',
8686
+ weixinCommandMarkdownEnabled: '\u5FAE\u4FE1\u547D\u4EE4 Markdown',
8687
+ };
8688
+
8689
+ const BRIDGE_RESTART_FIELDS = new Set([
8690
+ 'runtime',
8691
+ 'codexSkipGitRepoCheck',
8692
+ 'autoApprove',
8693
+ 'feishuAppId',
8694
+ 'feishuAppSecret',
8695
+ 'feishuDomain',
8696
+ ]);
8697
+
8698
+ const AUTO_SYNC_FIELDS = new Set([
8699
+ 'enabledChannels',
8700
+ ]);
8701
+
8702
+ const IMMEDIATE_FIELDS = new Set([
8703
+ 'defaultWorkDir',
8704
+ 'defaultWorkspaceRoot',
8705
+ 'defaultModel',
8706
+ 'defaultMode',
8707
+ 'historyMessageLimit',
8708
+ 'codexSandboxMode',
8709
+ 'codexReasoningEffort',
8710
+ 'uiAllowLan',
8711
+ 'uiAccessToken',
8712
+ 'feishuAllowedUsers',
8713
+ 'feishuStreamingEnabled',
8714
+ 'feishuCommandMarkdownEnabled',
8715
+ 'weixinMediaEnabled',
8716
+ 'weixinCommandMarkdownEnabled',
8717
+ ]);
8718
+
8719
+ const SAVE_SCOPE_FIELDS = {
8720
+ all: null,
8721
+ feishu: new Set([
8722
+ 'enabledChannels',
8723
+ 'feishuAppId',
8724
+ 'feishuAppSecret',
8725
+ 'feishuDomain',
8726
+ 'feishuAllowedUsers',
8727
+ 'feishuStreamingEnabled',
8728
+ 'feishuCommandMarkdownEnabled',
8729
+ ]),
8730
+ weixin: new Set([
8731
+ 'enabledChannels',
8732
+ 'weixinMediaEnabled',
8733
+ 'weixinCommandMarkdownEnabled',
8734
+ ]),
8735
+ };
8736
+
8737
+ function normalizeConfigValue(value) {
8738
+ if (Array.isArray(value)) {
8739
+ return value.slice().map((item) => String(item)).sort().join('|');
8740
+ }
8741
+ if (value === undefined || value === null) return '';
8742
+ if (typeof value === 'boolean') return value ? 'true' : 'false';
8743
+ return String(value);
8744
+ }
8745
+
8746
+ function listChangedConfigFields(before, after, scope) {
8747
+ const allowedFields = SAVE_SCOPE_FIELDS[scope] || null;
8748
+ const keys = new Set([
8749
+ ...Object.keys(before || {}),
8750
+ ...Object.keys(after || {}),
8751
+ ]);
8752
+
8753
+ return Array.from(keys).filter((key) => {
8754
+ if (allowedFields && !allowedFields.has(key)) return false;
8755
+ return normalizeConfigValue(before ? before[key] : undefined) !== normalizeConfigValue(after ? after[key] : undefined);
8756
+ });
8757
+ }
8758
+
8759
+ function formatFieldLabels(fields) {
8760
+ return fields
8761
+ .map((field) => CONFIG_FIELD_LABELS[field] || field)
8762
+ .join('\u3001');
8763
+ }
8764
+
8765
+ function buildConfigSaveMessage(before, after, scope) {
8766
+ const changed = listChangedConfigFields(before, after, scope);
8767
+ if (changed.length === 0) {
8768
+ return '\u914D\u7F6E\u672A\u53D8\u66F4\u3002';
8769
+ }
8770
+
8771
+ const restartFields = changed.filter((field) => BRIDGE_RESTART_FIELDS.has(field));
8772
+ const autoSyncFields = changed.filter((field) => AUTO_SYNC_FIELDS.has(field));
8773
+ const immediateFields = changed.filter((field) => IMMEDIATE_FIELDS.has(field));
8774
+ const notes = [];
8775
+
8776
+ if (immediateFields.length > 0) {
8777
+ notes.push('\u5DF2\u5373\u65F6\u751F\u6548\uFF1A' + formatFieldLabels(immediateFields));
8778
+ }
8779
+ if (autoSyncFields.length > 0) {
8780
+ notes.push(
8781
+ (state.bridgeStatus && state.bridgeStatus.running)
8782
+ ? '\u4F1A\u5728\u51E0\u79D2\u5185\u81EA\u52A8\u540C\u6B65\uFF1A' + formatFieldLabels(autoSyncFields)
8783
+ : '\u4F1A\u5728\u4E0B\u6B21\u542F\u52A8 Bridge \u65F6\u751F\u6548\uFF1A' + formatFieldLabels(autoSyncFields),
8784
+ );
8785
+ }
8786
+ if (restartFields.length > 0) {
8787
+ notes.push(
8788
+ (state.bridgeStatus && state.bridgeStatus.running)
8789
+ ? '\u9700\u8981\u91CD\u542F Bridge \u540E\u751F\u6548\uFF1A' + formatFieldLabels(restartFields)
8790
+ : '\u4F1A\u5728\u4E0B\u6B21\u542F\u52A8 Bridge \u65F6\u751F\u6548\uFF1A' + formatFieldLabels(restartFields),
8791
+ );
8792
+ }
8793
+
8794
+ return '\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002' + (notes.length > 0 ? ' ' + notes.join('\uFF1B') + '\u3002' : '');
8051
8795
  }
8052
8796
 
8053
8797
  function channelLabel(channelType) {
@@ -8063,7 +8807,7 @@ function renderHtml() {
8063
8807
  return label + '\u5DF2\u542F\u7528\uFF0C\u4F46 Bridge \u8FD8\u6CA1\u542F\u52A8\u3002\u542F\u52A8 Bridge \u540E\u624D\u4F1A\u771F\u6B63\u63A5\u901A\u3002';
8064
8808
  }
8065
8809
  if (!isChannelRunning(channelType)) {
8066
- return label + '\u5DF2\u5199\u5165\u914D\u7F6E\uFF0C\u4F46\u5F53\u524D\u8FD0\u884C\u4E2D\u7684 Bridge \u8FD8\u6CA1\u52A0\u8F7D\u8FD9\u4E2A\u901A\u9053\u3002\u70B9\u51FB\u201C\u91CD\u542F Bridge\u201D\u540E\u751F\u6548\u3002';
8810
+ 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';
8067
8811
  }
8068
8812
  return label + '\u5DF2\u63A5\u901A\u5230\u5F53\u524D\u8FD0\u884C\u4E2D\u7684 Bridge\u3002';
8069
8813
  }
@@ -8077,7 +8821,7 @@ function renderHtml() {
8077
8821
  return label + '\u5DF2\u542F\u7528\uFF0C\u4F46 Bridge \u8FD8\u6CA1\u542F\u52A8\u3002\u542F\u52A8\u540E\u624D\u4F1A\u521B\u5EFA\u7ED1\u5B9A\u3002';
8078
8822
  }
8079
8823
  if (!isChannelRunning(channelType)) {
8080
- return label + '\u5DF2\u542F\u7528\uFF0C\u4F46\u5F53\u524D\u8FD0\u884C\u4E2D\u7684 Bridge \u8FD8\u6CA1\u52A0\u8F7D\u8FD9\u4E2A\u901A\u9053\u3002\u70B9\u51FB\u201C\u91CD\u542F Bridge\u201D\u540E\u751F\u6548\u3002';
8824
+ 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';
8081
8825
  }
8082
8826
  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';
8083
8827
  }
@@ -8091,7 +8835,7 @@ function renderHtml() {
8091
8835
  return { disabled: true, title: 'Bridge \u8FD8\u6CA1\u542F\u52A8\u3002\u542F\u52A8\u540E\u518D\u5207\u6362' + label + '\u4F1A\u8BDD\u3002' };
8092
8836
  }
8093
8837
  if (!isChannelRunning(channelType)) {
8094
- return { disabled: true, title: label + '\u5DF2\u5199\u5165\u914D\u7F6E\uFF0C\u4F46\u5F53\u524D\u8FD0\u884C\u4E2D\u7684 Bridge \u8FD8\u6CA1\u52A0\u8F7D\u3002\u8BF7\u5148\u91CD\u542F Bridge\u3002' };
8838
+ 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' };
8095
8839
  }
8096
8840
 
8097
8841
  const bindings = state.bindings.filter((item) => item.channelType === channelType);
@@ -8128,6 +8872,30 @@ function renderHtml() {
8128
8872
  return marks;
8129
8873
  }
8130
8874
 
8875
+ function bindingRuntimeText(binding) {
8876
+ const status = binding.runtimeStatus || 'idle';
8877
+ const queuedCount = Number(binding.queuedCount || 0);
8878
+ if (status === 'queued') {
8879
+ return queuedCount > 0 ? '\u6392\u961F\u4E2D\uFF08' + queuedCount + '\uFF09' : '\u6392\u961F\u4E2D';
8880
+ }
8881
+ if (status === 'running') {
8882
+ return '\u8FD0\u884C\u4E2D';
8883
+ }
8884
+ return '\u7A7A\u95F2';
8885
+ }
8886
+
8887
+ function bindingMirrorText(binding) {
8888
+ if (binding.mirrorStatus === 'watching') {
8889
+ return binding.mirrorLastEventAt
8890
+ ? '\u76D1\u542C\u4E2D \xB7 \u6700\u8FD1\u540C\u6B65 ' + formatTime(binding.mirrorLastEventAt)
8891
+ : '\u76D1\u542C\u4E2D';
8892
+ }
8893
+ if (binding.mirrorStatus === 'stale') {
8894
+ return '\u5F85\u6062\u590D\uFF08\u6682\u65F6\u6CA1\u5B9A\u4F4D\u5230\u684C\u9762 thread \u6587\u4EF6\uFF09';
8895
+ }
8896
+ return '\u672A\u76D1\u542C';
8897
+ }
8898
+
8131
8899
  function renderDesktopSessionCard(session) {
8132
8900
  const feishuSwitch = quickSwitchState('feishu');
8133
8901
  const weixinSwitch = quickSwitchState('weixin');
@@ -8171,7 +8939,7 @@ function renderHtml() {
8171
8939
 
8172
8940
  const list = document.getElementById('desktopSessionsList');
8173
8941
  if (state.desktopSessions.length === 0) {
8174
- list.innerHTML = '<div class="notice ghost">\u5F53\u524D\u6CA1\u6709\u53D1\u73B0\u684C\u9762\u7AEF\u4F1A\u8BDD\u3002\u5148\u5728 Codex Windows App \u4E2D\u6253\u5F00\u6216\u8FD0\u884C\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u518D\u56DE\u5230\u8FD9\u91CC\u5237\u65B0\u3002</div>';
8942
+ 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>';
8175
8943
  rerenderBindingPanels();
8176
8944
  return;
8177
8945
  }
@@ -8240,6 +9008,8 @@ function renderHtml() {
8240
9008
  + '<div class="binding-detail">\u5F53\u524D\u4F1A\u8BDD\uFF1A<code>' + escapeHtml(binding.currentSessionId.slice(0, 8)) + '...</code> \xB7 ' + escapeHtml(binding.currentSessionName) + '</div>'
8241
9009
  + '<div class="binding-detail">\u5F53\u524D\u76EE\u6807\uFF1A' + escapeHtml(binding.currentTargetLabel || '\u672A\u7ED1\u5B9A') + '</div>'
8242
9010
  + '<div class="binding-detail">\u5F53\u524D thread\uFF1A<code>' + escapeHtml(binding.currentThreadId || 'not-shared') + '</code></div>'
9011
+ + '<div class="binding-detail">\u8FD0\u884C\u72B6\u6001\uFF1A' + escapeHtml(bindingRuntimeText(binding)) + '</div>'
9012
+ + '<div class="binding-detail">\u5171\u4EAB\u955C\u50CF\uFF1A' + escapeHtml(bindingMirrorText(binding)) + '</div>'
8243
9013
  + '<div class="binding-detail">\u76EE\u5F55\uFF1A' + escapeHtml(binding.workingDirectory || '~') + '</div>'
8244
9014
  + renderBindingTable(binding)
8245
9015
  + '</article>';
@@ -8291,14 +9061,63 @@ function renderHtml() {
8291
9061
  rerenderDesktopSessions();
8292
9062
  }
8293
9063
 
9064
+ function renderUiAccess() {
9065
+ const config = state.config || {};
9066
+ const uiAccess = state.uiAccess || {};
9067
+ const allowLan = config.uiAllowLan === true;
9068
+ const token = config.uiAccessToken || '';
9069
+ const lanUrls = Array.isArray(uiAccess.lanUrls) ? uiAccess.lanUrls : [];
9070
+ const localUrl = uiAccess.localUrl || window.location.origin;
9071
+ const summary = document.getElementById('uiAccessSummary');
9072
+ const details = document.getElementById('uiLanDetails');
9073
+ const tokenInput = document.getElementById('uiAccessToken');
9074
+ const urlList = document.getElementById('uiAccessUrls');
9075
+ const copyTokenBtn = document.getElementById('copyUiTokenBtn');
9076
+ const copyLanLinkBtn = document.getElementById('copyUiLanLinkBtn');
9077
+
9078
+ document.getElementById('uiAllowLan').checked = allowLan;
9079
+ tokenInput.value = token;
9080
+ details.hidden = !allowLan;
9081
+ copyTokenBtn.disabled = !allowLan || !token;
9082
+ copyLanLinkBtn.disabled = !allowLan || !token || lanUrls.length === 0;
9083
+
9084
+ if (!allowLan) {
9085
+ summary.textContent = '\u9ED8\u8BA4\u4EC5\u5141\u8BB8\u672C\u673A\u8BBF\u95EE\u5F53\u524D\u5DE5\u4F5C\u53F0\u3002\u5F00\u542F\u540E\uFF0C\u5C40\u57DF\u7F51\u8BBE\u5907\u9700\u8981\u5148\u8F93\u5165\u8BBF\u95EE token\u3002';
9086
+ urlList.innerHTML = '';
9087
+ return;
9088
+ }
9089
+
9090
+ summary.textContent = '\u5C40\u57DF\u7F51\u8BBF\u95EE\u5DF2\u5F00\u542F\u3002\u975E\u672C\u673A\u8BBE\u5907\u8BBF\u95EE\u65F6\u9700\u8981\u5148\u8F93\u5165 token\uFF1B\u672C\u673A\u8BBF\u95EE\u4ECD\u7136\u4E0D\u53D7\u5F71\u54CD\u3002';
9091
+ const items = [
9092
+ '<div class="info-item"><strong>\u672C\u673A\u5730\u5740</strong><div class="mono">' + escapeHtml(localUrl) + '</div></div>',
9093
+ ];
9094
+
9095
+ if (lanUrls.length === 0) {
9096
+ items.push('<div class="info-item"><strong>\u5C40\u57DF\u7F51\u5730\u5740</strong><div>\u672A\u68C0\u6D4B\u5230\u53EF\u7528\u7684 IPv4 \u5730\u5740\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5\u3002</div></div>');
9097
+ } else {
9098
+ for (const lanUrl of lanUrls) {
9099
+ items.push(
9100
+ '<div class="info-item"><strong>\u5C40\u57DF\u7F51\u5730\u5740</strong><div class="mono">' + escapeHtml(lanUrl) + '</div><div class="small">\u767B\u5F55\u94FE\u63A5\uFF1A' + escapeHtml(lanUrl + '/?token=' + token) + '</div></div>'
9101
+ );
9102
+ }
9103
+ }
9104
+
9105
+ urlList.innerHTML = items.join('');
9106
+ }
9107
+
8294
9108
  function fillForm(config) {
8295
9109
  state.config = config;
8296
9110
  document.getElementById('runtime').value = config.runtime || 'codex';
8297
9111
  document.getElementById('defaultMode').value = config.defaultMode || 'code';
8298
9112
  document.getElementById('historyMessageLimit').value = String(config.historyMessageLimit || 8);
8299
9113
  document.getElementById('defaultWorkDir').value = config.defaultWorkDir || '';
9114
+ document.getElementById('defaultWorkspaceRoot').value = config.defaultWorkspaceRoot || '';
8300
9115
  document.getElementById('defaultModel').value = config.defaultModel || '';
8301
9116
  document.getElementById('codexSkipGitRepoCheck').checked = config.codexSkipGitRepoCheck === true;
9117
+ document.getElementById('codexSandboxMode').value = config.codexSandboxMode || 'workspace-write';
9118
+ document.getElementById('codexReasoningEffort').value = config.codexReasoningEffort || 'medium';
9119
+ document.getElementById('uiAllowLan').checked = config.uiAllowLan === true;
9120
+ document.getElementById('uiAccessToken').value = config.uiAccessToken || '';
8302
9121
  document.getElementById('channelFeishu').checked = (config.enabledChannels || []).includes('feishu');
8303
9122
  document.getElementById('channelWeixin').checked = (config.enabledChannels || []).includes('weixin');
8304
9123
  document.getElementById('autoApprove').checked = config.autoApprove === true;
@@ -8307,7 +9126,10 @@ function renderHtml() {
8307
9126
  document.getElementById('feishuDomain').value = config.feishuDomain || 'https://open.feishu.cn';
8308
9127
  document.getElementById('feishuAllowedUsers').value = config.feishuAllowedUsers || '';
8309
9128
  document.getElementById('feishuStreamingEnabled').checked = config.feishuStreamingEnabled !== false;
9129
+ document.getElementById('feishuCommandMarkdownEnabled').checked = config.feishuCommandMarkdownEnabled !== false;
8310
9130
  document.getElementById('weixinMediaEnabled').checked = config.weixinMediaEnabled === true;
9131
+ document.getElementById('weixinCommandMarkdownEnabled').checked = config.weixinCommandMarkdownEnabled === true;
9132
+ renderUiAccess();
8311
9133
  rerenderDesktopSessions();
8312
9134
  }
8313
9135
 
@@ -8326,6 +9148,7 @@ function renderHtml() {
8326
9148
  async function loadStatus() {
8327
9149
  const status = await api('/api/status');
8328
9150
  const config = await api('/api/config');
9151
+ state.uiAccess = status.uiAccess || null;
8329
9152
  state.bridgeStatus = status.bridge || null;
8330
9153
  state.weixinAccounts = status.weixin && Array.isArray(status.weixin.linkedAccounts) ? status.weixin.linkedAccounts : [];
8331
9154
  fillForm(config);
@@ -8360,19 +9183,21 @@ function renderHtml() {
8360
9183
  renderBindings(result);
8361
9184
  }
8362
9185
 
8363
- async function saveConfig() {
9186
+ async function saveConfig(options) {
9187
+ const opts = options || {};
9188
+ const beforeConfig = state.config || {};
8364
9189
  const saved = await api('/api/config', {
8365
9190
  method: 'POST',
8366
9191
  body: JSON.stringify(formPayload()),
8367
9192
  });
8368
9193
  fillForm(saved.config);
8369
- showMessage(
8370
- 'configMessage',
8371
- 'success',
8372
- bridgeNeedsRestart()
8373
- ? '\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002\u5F53\u524D Bridge \u8FD8\u5728\u4F7F\u7528\u65E7\u901A\u9053\u914D\u7F6E\uFF0C\u70B9\u51FB\u201C\u91CD\u542F Bridge\u201D\u540E\u751F\u6548\u3002'
8374
- : '\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002'
8375
- );
9194
+ if (opts.messageId) {
9195
+ showMessage(
9196
+ opts.messageId,
9197
+ 'success',
9198
+ buildConfigSaveMessage(beforeConfig, saved.config, opts.scope || 'all'),
9199
+ );
9200
+ }
8376
9201
  return saved;
8377
9202
  }
8378
9203
 
@@ -8391,9 +9216,41 @@ function renderHtml() {
8391
9216
 
8392
9217
  window.addEventListener('hashchange', syncPageFromHash);
8393
9218
 
9219
+ document.getElementById('copyUiTokenBtn').addEventListener('click', async () => {
9220
+ try {
9221
+ await copyText(document.getElementById('uiAccessToken').value, '\u8BBF\u95EE token \u5DF2\u590D\u5236\u3002');
9222
+ } catch (error) {
9223
+ showMessage('configMessage', 'error', error.message);
9224
+ }
9225
+ });
9226
+
9227
+ document.getElementById('copyUiLanLinkBtn').addEventListener('click', async () => {
9228
+ try {
9229
+ const urls = state.uiAccess && Array.isArray(state.uiAccess.lanUrls) ? state.uiAccess.lanUrls : [];
9230
+ const token = document.getElementById('uiAccessToken').value;
9231
+ if (!urls.length || !token) {
9232
+ throw new Error('\u5F53\u524D\u8FD8\u6CA1\u6709\u53EF\u590D\u5236\u7684\u5C40\u57DF\u7F51\u767B\u5F55\u94FE\u63A5\u3002');
9233
+ }
9234
+ await copyText(urls[0] + '/?token=' + token, '\u5C40\u57DF\u7F51\u767B\u5F55\u94FE\u63A5\u5DF2\u590D\u5236\u3002');
9235
+ } catch (error) {
9236
+ showMessage('configMessage', 'error', error.message);
9237
+ }
9238
+ });
9239
+
9240
+ document.getElementById('regenerateUiTokenBtn').addEventListener('click', () => {
9241
+ if (!window.crypto || typeof window.crypto.randomUUID !== 'function') {
9242
+ showMessage('configMessage', 'error', '\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u751F\u6210 token\u3002');
9243
+ return;
9244
+ }
9245
+ document.getElementById('uiAccessToken').value =
9246
+ window.crypto.randomUUID().replaceAll('-', '') + window.crypto.randomUUID().replaceAll('-', '');
9247
+ renderUiAccess();
9248
+ showMessage('configMessage', 'success', '\u5DF2\u751F\u6210\u65B0\u7684\u8BBF\u95EE token\uFF0C\u70B9\u51FB\u201C\u4FDD\u5B58\u914D\u7F6E\u201D\u540E\u751F\u6548\u3002');
9249
+ });
9250
+
8394
9251
  document.getElementById('saveConfigBtn').addEventListener('click', async () => {
8395
9252
  try {
8396
- await saveConfig();
9253
+ await saveConfig({ messageId: 'configMessage', scope: 'all' });
8397
9254
  await loadStatus();
8398
9255
  await loadBindings();
8399
9256
  } catch (error) {
@@ -8403,16 +9260,9 @@ function renderHtml() {
8403
9260
 
8404
9261
  document.getElementById('saveFeishuChannelBtn').addEventListener('click', async () => {
8405
9262
  try {
8406
- await saveConfig();
9263
+ await saveConfig({ messageId: 'feishuMessage', scope: 'feishu' });
8407
9264
  await loadStatus();
8408
9265
  await loadBindings();
8409
- showMessage(
8410
- 'feishuMessage',
8411
- 'success',
8412
- bridgeNeedsRestart()
8413
- ? '\u98DE\u4E66\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002\u5F53\u524D Bridge \u8FD8\u5728\u4F7F\u7528\u65E7\u901A\u9053\u914D\u7F6E\uFF0C\u70B9\u51FB\u201C\u91CD\u542F Bridge\u201D\u540E\u751F\u6548\u3002'
8414
- : '\u98DE\u4E66\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002'
8415
- );
8416
9266
  } catch (error) {
8417
9267
  showMessage('feishuMessage', 'error', error.message);
8418
9268
  }
@@ -8430,18 +9280,33 @@ function renderHtml() {
8430
9280
  }
8431
9281
  });
8432
9282
 
9283
+ document.getElementById('refreshFeishuStateBtn').addEventListener('click', async () => {
9284
+ try {
9285
+ await loadStatus();
9286
+ await loadBindings();
9287
+ await loadDesktopSessions();
9288
+ showMessage('feishuMessage', 'success', '\u98DE\u4E66\u901A\u9053\u72B6\u6001\u5DF2\u5237\u65B0\u3002');
9289
+ } catch (error) {
9290
+ showMessage('feishuMessage', 'error', error.message);
9291
+ }
9292
+ });
9293
+
8433
9294
  document.getElementById('saveWeixinChannelBtn').addEventListener('click', async () => {
8434
9295
  try {
8435
- await saveConfig();
9296
+ await saveConfig({ messageId: 'weixinMessage', scope: 'weixin' });
8436
9297
  await loadStatus();
8437
9298
  await loadBindings();
8438
- showMessage(
8439
- 'weixinMessage',
8440
- 'success',
8441
- bridgeNeedsRestart()
8442
- ? '\u5FAE\u4FE1\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002\u5F53\u524D Bridge \u8FD8\u5728\u4F7F\u7528\u65E7\u901A\u9053\u914D\u7F6E\uFF0C\u70B9\u51FB\u201C\u91CD\u542F Bridge\u201D\u540E\u751F\u6548\u3002'
8443
- : '\u5FAE\u4FE1\u914D\u7F6E\u5DF2\u4FDD\u5B58\u3002'
8444
- );
9299
+ } catch (error) {
9300
+ showMessage('weixinMessage', 'error', error.message);
9301
+ }
9302
+ });
9303
+
9304
+ document.getElementById('refreshWeixinStateBtn').addEventListener('click', async () => {
9305
+ try {
9306
+ await loadStatus();
9307
+ await loadBindings();
9308
+ await loadDesktopSessions();
9309
+ showMessage('weixinMessage', 'success', '\u5FAE\u4FE1\u901A\u9053\u72B6\u6001\u5DF2\u5237\u65B0\u3002');
8445
9310
  } catch (error) {
8446
9311
  showMessage('weixinMessage', 'error', error.message);
8447
9312
  }
@@ -8456,7 +9321,7 @@ function renderHtml() {
8456
9321
  await loadBindings();
8457
9322
  const followup = isChannelRunning('weixin')
8458
9323
  ? '\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'
8459
- : '\u5FAE\u4FE1\u8D26\u53F7\u5DF2\u4FDD\u5B58\u3002\u5F53\u524D\u8FD0\u884C\u4E2D\u7684 Bridge \u8FD8\u6CA1\u52A0\u8F7D\u5FAE\u4FE1\u901A\u9053\uFF0C\u70B9\u51FB\u201C\u91CD\u542F Bridge\u201D\u540E\u751F\u6548\u3002';
9324
+ : '\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';
8460
9325
  showMessage('weixinMessage', result.ok ? 'success' : 'error', followup);
8461
9326
  } catch (error) {
8462
9327
  showMessage('weixinMessage', 'error', error.message);
@@ -8628,6 +9493,10 @@ function renderHtml() {
8628
9493
  Promise.all([loadStatus(), loadBindings(), loadDesktopSessions(), loadLogs()]).catch((error) => {
8629
9494
  showMessage('opsMessage', 'error', error.message);
8630
9495
  });
9496
+
9497
+ setInterval(() => {
9498
+ loadBindings().catch(() => {});
9499
+ }, 4000);
8631
9500
  </script>
8632
9501
  </body>
8633
9502
  </html>`;
@@ -8635,7 +9504,63 @@ function renderHtml() {
8635
9504
  var server = http.createServer(async (request, response) => {
8636
9505
  try {
8637
9506
  const url = new URL(request.url || "/", getUiServerUrl(port));
9507
+ const config = loadConfig();
9508
+ const localRequest = isLocalRequest(request);
9509
+ const queryToken = asString(url.searchParams.get("token"));
9510
+ if (request.method === "GET" && !localRequest && config.uiAllowLan === true && timingSafeMatch(queryToken, config.uiAccessToken)) {
9511
+ const redirectUrl = new URL(url.pathname || "/", getUiServerUrl(port));
9512
+ redirect(response, `${redirectUrl.pathname}${redirectUrl.search}`, makeAuthCookie(config.uiAccessToken || ""));
9513
+ return;
9514
+ }
9515
+ if (request.method === "GET" && url.pathname === "/login") {
9516
+ if (localRequest || isRemoteAuthenticated(request, config)) {
9517
+ redirect(response, "/");
9518
+ return;
9519
+ }
9520
+ if (config.uiAllowLan !== true) {
9521
+ html(response, renderAccessDeniedHtml());
9522
+ return;
9523
+ }
9524
+ html(response, renderLoginHtml());
9525
+ return;
9526
+ }
9527
+ if (request.method === "POST" && url.pathname === "/api/auth/login") {
9528
+ if (config.uiAllowLan !== true) {
9529
+ json(response, 403, { error: "\u5F53\u524D\u672A\u5F00\u542F\u5C40\u57DF\u7F51\u8BBF\u95EE\u3002" });
9530
+ return;
9531
+ }
9532
+ const payload = await readJsonBody(request);
9533
+ const token = asString(payload.token);
9534
+ if (!timingSafeMatch(token, config.uiAccessToken)) {
9535
+ json(response, 401, { error: "\u8BBF\u95EE token \u4E0D\u6B63\u786E\u3002" });
9536
+ return;
9537
+ }
9538
+ response.writeHead(200, {
9539
+ "Content-Type": "application/json; charset=utf-8",
9540
+ "Set-Cookie": makeAuthCookie(config.uiAccessToken || "")
9541
+ });
9542
+ response.end(JSON.stringify({ ok: true }));
9543
+ return;
9544
+ }
9545
+ if (request.method === "POST" && url.pathname === "/api/auth/logout") {
9546
+ response.writeHead(200, {
9547
+ "Content-Type": "application/json; charset=utf-8",
9548
+ "Set-Cookie": clearAuthCookie()
9549
+ });
9550
+ response.end(JSON.stringify({ ok: true }));
9551
+ return;
9552
+ }
8638
9553
  if (request.method === "GET" && url.pathname === "/") {
9554
+ if (!localRequest) {
9555
+ if (config.uiAllowLan !== true) {
9556
+ html(response, renderAccessDeniedHtml());
9557
+ return;
9558
+ }
9559
+ if (!isRemoteAuthenticated(request, config)) {
9560
+ html(response, renderLoginHtml());
9561
+ return;
9562
+ }
9563
+ }
8639
9564
  html(response, renderHtml());
8640
9565
  return;
8641
9566
  }
@@ -8643,10 +9568,21 @@ var server = http.createServer(async (request, response) => {
8643
9568
  json(response, 200, { ok: true });
8644
9569
  return;
8645
9570
  }
9571
+ if (!localRequest) {
9572
+ if (config.uiAllowLan !== true) {
9573
+ json(response, 403, { error: "\u5F53\u524D\u672A\u5F00\u542F\u5C40\u57DF\u7F51\u8BBF\u95EE\u3002" });
9574
+ return;
9575
+ }
9576
+ if (!isRemoteAuthenticated(request, config)) {
9577
+ json(response, 401, { error: "\u9700\u8981\u5148\u767B\u5F55\u5E76\u63D0\u4F9B\u8BBF\u95EE token\u3002" });
9578
+ return;
9579
+ }
9580
+ }
8646
9581
  if (request.method === "GET" && url.pathname === "/api/status") {
8647
9582
  json(response, 200, {
8648
9583
  bridge: getBridgeStatus(),
8649
9584
  ui: getUiServerStatus(),
9585
+ uiAccess: buildUiAccessInfo(port, config, request),
8650
9586
  home: CTI_HOME,
8651
9587
  packageRoot: getPackageRoot(),
8652
9588
  codexIntegrationInstalled: isCodexIntegrationInstalled(),
@@ -8658,7 +9594,7 @@ var server = http.createServer(async (request, response) => {
8658
9594
  return;
8659
9595
  }
8660
9596
  if (request.method === "GET" && url.pathname === "/api/config") {
8661
- json(response, 200, configToPayload(loadConfig()));
9597
+ json(response, 200, configToPayload(config));
8662
9598
  return;
8663
9599
  }
8664
9600
  if (request.method === "GET" && url.pathname === "/api/desktop-sessions") {
@@ -8683,8 +9619,8 @@ var server = http.createServer(async (request, response) => {
8683
9619
  }
8684
9620
  if (request.method === "POST" && url.pathname === "/api/config") {
8685
9621
  const payload = await readJsonBody(request);
8686
- const config = mergeConfig(payload);
8687
- saveConfig(config);
9622
+ const config2 = mergeConfig(payload);
9623
+ saveConfig(config2);
8688
9624
  json(response, 200, { ok: true, config: configToPayload(loadConfig()) });
8689
9625
  return;
8690
9626
  }
@@ -8765,7 +9701,7 @@ async function startServer() {
8765
9701
  port = await resolveUiPort(parsePreferredPort());
8766
9702
  await new Promise((resolve, reject) => {
8767
9703
  server.once("error", reject);
8768
- server.listen(port, "127.0.0.1", () => {
9704
+ server.listen(port, "0.0.0.0", () => {
8769
9705
  server.removeListener("error", reject);
8770
9706
  resolve();
8771
9707
  });