codex-to-im 1.0.59 → 1.0.61

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.
@@ -1572,10 +1572,10 @@ var require_segments = __commonJS({
1572
1572
  const segs = getSegmentsFromString(data, Utils.isKanjiModeEnabled());
1573
1573
  const nodes = buildNodes(segs);
1574
1574
  const graph = buildGraph(nodes, version);
1575
- const path10 = dijkstra.find_path(graph.map, "start", "end");
1575
+ const path11 = dijkstra.find_path(graph.map, "start", "end");
1576
1576
  const optimizedSegs = [];
1577
- for (let i = 1; i < path10.length - 1; i++) {
1578
- optimizedSegs.push(graph.table[path10[i]].node);
1577
+ for (let i = 1; i < path11.length - 1; i++) {
1578
+ optimizedSegs.push(graph.table[path11[i]].node);
1579
1579
  }
1580
1580
  return exports.fromArray(mergeSegments(optimizedSegs));
1581
1581
  };
@@ -4011,7 +4011,7 @@ var require_utils2 = __commonJS({
4011
4011
  // node_modules/qrcode/lib/renderer/png.js
4012
4012
  var require_png2 = __commonJS({
4013
4013
  "node_modules/qrcode/lib/renderer/png.js"(exports) {
4014
- var fs9 = __require("fs");
4014
+ var fs11 = __require("fs");
4015
4015
  var PNG = require_png().PNG;
4016
4016
  var Utils = require_utils2();
4017
4017
  exports.render = function render(qrData, options) {
@@ -4052,7 +4052,7 @@ var require_png2 = __commonJS({
4052
4052
  });
4053
4053
  png.pack();
4054
4054
  };
4055
- exports.renderToFile = function renderToFile(path10, qrData, options, cb) {
4055
+ exports.renderToFile = function renderToFile(path11, qrData, options, cb) {
4056
4056
  if (typeof cb === "undefined") {
4057
4057
  cb = options;
4058
4058
  options = void 0;
@@ -4063,7 +4063,7 @@ var require_png2 = __commonJS({
4063
4063
  called = true;
4064
4064
  cb.apply(null, args);
4065
4065
  };
4066
- const stream = fs9.createWriteStream(path10);
4066
+ const stream = fs11.createWriteStream(path11);
4067
4067
  stream.on("error", done);
4068
4068
  stream.on("close", done);
4069
4069
  exports.renderToFileStream(stream, qrData, options);
@@ -4125,14 +4125,14 @@ var require_utf8 = __commonJS({
4125
4125
  }
4126
4126
  return output;
4127
4127
  };
4128
- exports.renderToFile = function renderToFile(path10, qrData, options, cb) {
4128
+ exports.renderToFile = function renderToFile(path11, qrData, options, cb) {
4129
4129
  if (typeof cb === "undefined") {
4130
4130
  cb = options;
4131
4131
  options = void 0;
4132
4132
  }
4133
- const fs9 = __require("fs");
4133
+ const fs11 = __require("fs");
4134
4134
  const utf8 = exports.render(qrData, options);
4135
- fs9.writeFile(path10, utf8, cb);
4135
+ fs11.writeFile(path11, utf8, cb);
4136
4136
  };
4137
4137
  }
4138
4138
  });
@@ -4253,7 +4253,7 @@ var require_svg_tag = __commonJS({
4253
4253
  return str;
4254
4254
  }
4255
4255
  function qrToPath(data, size, margin) {
4256
- let path10 = "";
4256
+ let path11 = "";
4257
4257
  let moveBy = 0;
4258
4258
  let newRow = false;
4259
4259
  let lineLength = 0;
@@ -4264,19 +4264,19 @@ var require_svg_tag = __commonJS({
4264
4264
  if (data[i]) {
4265
4265
  lineLength++;
4266
4266
  if (!(i > 0 && col > 0 && data[i - 1])) {
4267
- path10 += newRow ? svgCmd("M", col + margin, 0.5 + row + margin) : svgCmd("m", moveBy, 0);
4267
+ path11 += newRow ? svgCmd("M", col + margin, 0.5 + row + margin) : svgCmd("m", moveBy, 0);
4268
4268
  moveBy = 0;
4269
4269
  newRow = false;
4270
4270
  }
4271
4271
  if (!(col + 1 < size && data[i + 1])) {
4272
- path10 += svgCmd("h", lineLength);
4272
+ path11 += svgCmd("h", lineLength);
4273
4273
  lineLength = 0;
4274
4274
  }
4275
4275
  } else {
4276
4276
  moveBy++;
4277
4277
  }
4278
4278
  }
4279
- return path10;
4279
+ return path11;
4280
4280
  }
4281
4281
  exports.render = function render(qrData, options, cb) {
4282
4282
  const opts = Utils.getOptions(options);
@@ -4284,10 +4284,10 @@ var require_svg_tag = __commonJS({
4284
4284
  const data = qrData.modules.data;
4285
4285
  const qrcodesize = size + opts.margin * 2;
4286
4286
  const bg = !opts.color.light.a ? "" : "<path " + getColorAttrib(opts.color.light, "fill") + ' d="M0 0h' + qrcodesize + "v" + qrcodesize + 'H0z"/>';
4287
- const path10 = "<path " + getColorAttrib(opts.color.dark, "stroke") + ' d="' + qrToPath(data, size, opts.margin) + '"/>';
4287
+ const path11 = "<path " + getColorAttrib(opts.color.dark, "stroke") + ' d="' + qrToPath(data, size, opts.margin) + '"/>';
4288
4288
  const viewBox = 'viewBox="0 0 ' + qrcodesize + " " + qrcodesize + '"';
4289
4289
  const width = !opts.width ? "" : 'width="' + opts.width + '" height="' + opts.width + '" ';
4290
- const svgTag = '<svg xmlns="http://www.w3.org/2000/svg" ' + width + viewBox + ' shape-rendering="crispEdges">' + bg + path10 + "</svg>\n";
4290
+ const svgTag = '<svg xmlns="http://www.w3.org/2000/svg" ' + width + viewBox + ' shape-rendering="crispEdges">' + bg + path11 + "</svg>\n";
4291
4291
  if (typeof cb === "function") {
4292
4292
  cb(null, svgTag);
4293
4293
  }
@@ -4301,15 +4301,15 @@ var require_svg = __commonJS({
4301
4301
  "node_modules/qrcode/lib/renderer/svg.js"(exports) {
4302
4302
  var svgTagRenderer = require_svg_tag();
4303
4303
  exports.render = svgTagRenderer.render;
4304
- exports.renderToFile = function renderToFile(path10, qrData, options, cb) {
4304
+ exports.renderToFile = function renderToFile(path11, qrData, options, cb) {
4305
4305
  if (typeof cb === "undefined") {
4306
4306
  cb = options;
4307
4307
  options = void 0;
4308
4308
  }
4309
- const fs9 = __require("fs");
4309
+ const fs11 = __require("fs");
4310
4310
  const svgTag = exports.render(qrData, options);
4311
4311
  const xmlStr = '<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' + svgTag;
4312
- fs9.writeFile(path10, xmlStr, cb);
4312
+ fs11.writeFile(path11, xmlStr, cb);
4313
4313
  };
4314
4314
  }
4315
4315
  });
@@ -4467,8 +4467,8 @@ var require_server = __commonJS({
4467
4467
  cb
4468
4468
  };
4469
4469
  }
4470
- function getTypeFromFilename(path10) {
4471
- return path10.slice((path10.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
4470
+ function getTypeFromFilename(path11) {
4471
+ return path11.slice((path11.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
4472
4472
  }
4473
4473
  function getRendererFromType(type) {
4474
4474
  switch (type) {
@@ -4532,17 +4532,17 @@ var require_server = __commonJS({
4532
4532
  const renderer = getRendererFromType(params.opts.type);
4533
4533
  return render(renderer.renderToBuffer, text2, params);
4534
4534
  };
4535
- exports.toFile = function toFile(path10, text2, opts, cb) {
4536
- if (typeof path10 !== "string" || !(typeof text2 === "string" || typeof text2 === "object")) {
4535
+ exports.toFile = function toFile(path11, text2, opts, cb) {
4536
+ if (typeof path11 !== "string" || !(typeof text2 === "string" || typeof text2 === "object")) {
4537
4537
  throw new Error("Invalid argument");
4538
4538
  }
4539
4539
  if (arguments.length < 3 && !canPromise()) {
4540
4540
  throw new Error("Too few arguments provided");
4541
4541
  }
4542
4542
  const params = checkParams(text2, opts, cb);
4543
- const type = params.opts.type || getTypeFromFilename(path10);
4543
+ const type = params.opts.type || getTypeFromFilename(path11);
4544
4544
  const renderer = getRendererFromType(type);
4545
- const renderToFile = renderer.renderToFile.bind(null, path10);
4545
+ const renderToFile = renderer.renderToFile.bind(null, path11);
4546
4546
  return render(renderToFile, text2, params);
4547
4547
  };
4548
4548
  exports.toFileStream = function toFileStream(stream, text2, opts) {
@@ -4566,12 +4566,13 @@ var require_lib = __commonJS({
4566
4566
 
4567
4567
  // src/ui-server.ts
4568
4568
  import http from "node:http";
4569
- import crypto5 from "node:crypto";
4569
+ import crypto9 from "node:crypto";
4570
4570
  import net from "node:net";
4571
4571
  import os5 from "node:os";
4572
4572
 
4573
4573
  // src/config.ts
4574
- import fs from "node:fs";
4574
+ import fs2 from "node:fs";
4575
+ import crypto2 from "node:crypto";
4575
4576
  import os from "node:os";
4576
4577
  import path from "node:path";
4577
4578
 
@@ -4598,10 +4599,109 @@ function normalizeChannelId(value) {
4598
4599
  return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "channel";
4599
4600
  }
4600
4601
 
4602
+ // src/file-lock.ts
4603
+ import crypto from "node:crypto";
4604
+ import fs from "node:fs";
4605
+ var DEFAULT_LOCK_TIMEOUT_MS = 5e3;
4606
+ var DEFAULT_STALE_LOCK_MS = 3e4;
4607
+ var LOCK_RETRY_INTERVAL_MS = 25;
4608
+ var WAIT_ARRAY = new Int32Array(new SharedArrayBuffer(4));
4609
+ var ACTIVE_LOCKS = /* @__PURE__ */ new Map();
4610
+ function sleepSync(ms) {
4611
+ Atomics.wait(WAIT_ARRAY, 0, 0, Math.max(1, ms));
4612
+ }
4613
+ function isProcessAlive(pid) {
4614
+ if (!Number.isInteger(pid) || pid <= 0) return false;
4615
+ try {
4616
+ process.kill(pid, 0);
4617
+ return true;
4618
+ } catch (error) {
4619
+ return error.code === "EPERM";
4620
+ }
4621
+ }
4622
+ function removeStaleLock(lockPath, staleAfterMs) {
4623
+ try {
4624
+ const stat = fs.statSync(lockPath);
4625
+ if (Date.now() - stat.mtimeMs < staleAfterMs) return false;
4626
+ let ownerPid = 0;
4627
+ try {
4628
+ const parsed = JSON.parse(fs.readFileSync(lockPath, "utf8"));
4629
+ ownerPid = typeof parsed.pid === "number" ? parsed.pid : 0;
4630
+ } catch {
4631
+ }
4632
+ if (ownerPid && isProcessAlive(ownerPid)) return false;
4633
+ fs.rmSync(lockPath, { force: true });
4634
+ return true;
4635
+ } catch (error) {
4636
+ return error.code === "ENOENT";
4637
+ }
4638
+ }
4639
+ function withFileLock(targetPath, operation, options) {
4640
+ const lockPath = `${targetPath}.lock`;
4641
+ const activeDepth = ACTIVE_LOCKS.get(lockPath) || 0;
4642
+ if (activeDepth > 0) {
4643
+ ACTIVE_LOCKS.set(lockPath, activeDepth + 1);
4644
+ try {
4645
+ return operation();
4646
+ } finally {
4647
+ const nextDepth = (ACTIVE_LOCKS.get(lockPath) || 1) - 1;
4648
+ if (nextDepth > 0) ACTIVE_LOCKS.set(lockPath, nextDepth);
4649
+ else ACTIVE_LOCKS.delete(lockPath);
4650
+ }
4651
+ }
4652
+ const timeoutMs = Math.max(0, options?.timeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS);
4653
+ const staleAfterMs = Math.max(timeoutMs, options?.staleAfterMs ?? DEFAULT_STALE_LOCK_MS);
4654
+ const deadline = Date.now() + timeoutMs;
4655
+ const record = {
4656
+ token: crypto.randomUUID(),
4657
+ pid: process.pid,
4658
+ createdAt: Date.now()
4659
+ };
4660
+ let handle = null;
4661
+ while (handle === null) {
4662
+ try {
4663
+ handle = fs.openSync(lockPath, "wx", 384);
4664
+ fs.writeFileSync(handle, JSON.stringify(record), "utf8");
4665
+ } catch (error) {
4666
+ const code = error.code;
4667
+ if (code !== "EEXIST") throw error;
4668
+ if (removeStaleLock(lockPath, staleAfterMs)) continue;
4669
+ if (Date.now() >= deadline) {
4670
+ throw new Error(`Timed out waiting for file lock: ${lockPath}`);
4671
+ }
4672
+ sleepSync(Math.min(LOCK_RETRY_INTERVAL_MS, Math.max(1, deadline - Date.now())));
4673
+ }
4674
+ }
4675
+ try {
4676
+ ACTIVE_LOCKS.set(lockPath, 1);
4677
+ return operation();
4678
+ } finally {
4679
+ ACTIVE_LOCKS.delete(lockPath);
4680
+ try {
4681
+ fs.closeSync(handle);
4682
+ } catch {
4683
+ }
4684
+ try {
4685
+ const current = JSON.parse(fs.readFileSync(lockPath, "utf8"));
4686
+ if (current.token === record.token) fs.rmSync(lockPath, { force: true });
4687
+ } catch {
4688
+ }
4689
+ }
4690
+ }
4691
+ function withFileLocks(targetPaths, operation) {
4692
+ const paths = Array.from(new Set(targetPaths)).sort();
4693
+ const acquire = (index) => {
4694
+ if (index >= paths.length) return operation();
4695
+ return withFileLock(paths[index], () => acquire(index + 1));
4696
+ };
4697
+ return acquire(0);
4698
+ }
4699
+
4601
4700
  // src/config.ts
4602
4701
  function isSupportedChannelProvider(value) {
4603
4702
  return value === "feishu" || value === "weixin";
4604
4703
  }
4704
+ var RAW_CHANNELS = Symbol("rawConfigV2Channels");
4605
4705
  function toFeishuConfig(channel) {
4606
4706
  return channel?.provider === "feishu" ? channel.config : void 0;
4607
4707
  }
@@ -4641,7 +4741,7 @@ function parseEnvFile(content) {
4641
4741
  }
4642
4742
  function loadRawConfigEnv() {
4643
4743
  try {
4644
- return parseEnvFile(fs.readFileSync(CONFIG_PATH, "utf-8"));
4744
+ return parseEnvFile(fs2.readFileSync(CONFIG_PATH, "utf-8"));
4645
4745
  } catch {
4646
4746
  return /* @__PURE__ */ new Map();
4647
4747
  }
@@ -4671,26 +4771,66 @@ function nowIso() {
4671
4771
  return (/* @__PURE__ */ new Date()).toISOString();
4672
4772
  }
4673
4773
  function ensureConfigDir() {
4674
- fs.mkdirSync(CTI_HOME, { recursive: true });
4774
+ fs2.mkdirSync(CTI_HOME, { recursive: true });
4675
4775
  }
4676
4776
  function readConfigV2File() {
4777
+ if (!fs2.existsSync(CONFIG_V2_PATH)) return null;
4778
+ let content;
4779
+ try {
4780
+ content = fs2.readFileSync(CONFIG_V2_PATH, "utf-8");
4781
+ } catch (error) {
4782
+ throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6 ${CONFIG_V2_PATH}: ${error instanceof Error ? error.message : String(error)}`);
4783
+ }
4784
+ let raw;
4785
+ try {
4786
+ raw = JSON.parse(content);
4787
+ } catch (error) {
4788
+ throw new Error(`\u914D\u7F6E\u6587\u4EF6 ${CONFIG_V2_PATH} \u4E0D\u662F\u6709\u6548 JSON\uFF0C\u5DF2\u62D2\u7EDD\u8986\u76D6: ${error instanceof Error ? error.message : String(error)}`);
4789
+ }
4790
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
4791
+ throw new Error(`\u914D\u7F6E\u6587\u4EF6 ${CONFIG_V2_PATH} \u7684\u6839\u8282\u70B9\u5FC5\u987B\u662F\u5BF9\u8C61\uFF0C\u5DF2\u62D2\u7EDD\u8986\u76D6\u3002`);
4792
+ }
4793
+ if (raw.schemaVersion !== 2) {
4794
+ throw new Error(`\u4E0D\u652F\u6301\u914D\u7F6E schemaVersion=${String(raw.schemaVersion)}\uFF0C\u5DF2\u62D2\u7EDD\u7528\u5F53\u524D\u7248\u672C\u8986\u76D6\u3002`);
4795
+ }
4796
+ if (!raw.runtime || typeof raw.runtime !== "object" || Array.isArray(raw.runtime)) {
4797
+ throw new Error(`\u914D\u7F6E\u6587\u4EF6 ${CONFIG_V2_PATH} \u7F3A\u5C11\u6709\u6548\u7684 runtime \u5BF9\u8C61\uFF0C\u5DF2\u62D2\u7EDD\u8986\u76D6\u3002`);
4798
+ }
4799
+ if (!Array.isArray(raw.channels)) {
4800
+ throw new Error(`\u914D\u7F6E\u6587\u4EF6 ${CONFIG_V2_PATH} \u7F3A\u5C11 channels \u6570\u7EC4\uFF0C\u5DF2\u62D2\u7EDD\u8986\u76D6\u3002`);
4801
+ }
4802
+ const rawChannels = raw.channels;
4803
+ const parsed = {
4804
+ ...raw,
4805
+ schemaVersion: 2,
4806
+ runtime: {
4807
+ ...raw.runtime,
4808
+ provider: normalizeRuntimeProvider(raw.runtime.provider)
4809
+ },
4810
+ channels: normalizeChannelInstances(rawChannels)
4811
+ };
4812
+ Object.defineProperty(parsed, RAW_CHANNELS, { value: rawChannels, enumerable: false });
4813
+ return parsed;
4814
+ }
4815
+ function atomicWriteFile(filePath, content) {
4816
+ ensureConfigDir();
4817
+ const tmpPath = `${filePath}.${process.pid}.${crypto2.randomUUID()}.tmp`;
4677
4818
  try {
4678
- const parsed = JSON.parse(fs.readFileSync(CONFIG_V2_PATH, "utf-8"));
4679
- if (parsed && parsed.schemaVersion === 2 && parsed.runtime && Array.isArray(parsed.channels)) {
4680
- parsed.runtime.provider = normalizeRuntimeProvider(parsed.runtime.provider);
4681
- parsed.channels = normalizeChannelInstances(parsed.channels);
4682
- return parsed;
4819
+ fs2.writeFileSync(tmpPath, content, { mode: 384 });
4820
+ fs2.renameSync(tmpPath, filePath);
4821
+ } finally {
4822
+ try {
4823
+ fs2.rmSync(tmpPath, { force: true });
4824
+ } catch {
4683
4825
  }
4684
- return null;
4685
- } catch {
4686
- return null;
4687
4826
  }
4688
4827
  }
4689
4828
  function writeConfigV2File(config) {
4690
- ensureConfigDir();
4691
- const tmpPath = CONFIG_V2_PATH + ".tmp";
4692
- fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2), { mode: 384 });
4693
- fs.renameSync(tmpPath, CONFIG_V2_PATH);
4829
+ const serialized = {
4830
+ ...config,
4831
+ channels: config[RAW_CHANNELS] || config.channels
4832
+ };
4833
+ atomicWriteFile(CONFIG_V2_PATH, JSON.stringify(serialized, null, 2));
4694
4834
  }
4695
4835
  function defaultAliasForProvider(provider) {
4696
4836
  return provider === "feishu" ? "\u98DE\u4E66" : "\u5FAE\u4FE1";
@@ -4711,6 +4851,7 @@ function normalizeChannelInstances(value) {
4711
4851
  const config = record.config && typeof record.config === "object" ? record.config : {};
4712
4852
  const timestamp = nowIso();
4713
4853
  return [{
4854
+ ...record,
4714
4855
  id: normalizeChannelId(
4715
4856
  typeof record.id === "string" && record.id.trim() ? record.id : buildDefaultChannelId(provider)
4716
4857
  ),
@@ -4811,13 +4952,45 @@ function expandConfig(v2) {
4811
4952
  uiAccessToken: v2.runtime.uiAccessToken || void 0
4812
4953
  };
4813
4954
  }
4955
+ function mergeRawChannelRecords(current, channels) {
4956
+ const remaining = new Map(channels.map((channel) => [channel.id, channel]));
4957
+ const rawChannels = current?.[RAW_CHANNELS] || current?.channels || [];
4958
+ const merged = [];
4959
+ for (const rawEntry of rawChannels) {
4960
+ if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) continue;
4961
+ const rawRecord = rawEntry;
4962
+ if (!isSupportedChannelProvider(rawRecord.provider)) {
4963
+ merged.push(rawEntry);
4964
+ continue;
4965
+ }
4966
+ const rawId = normalizeChannelId(
4967
+ typeof rawRecord.id === "string" && rawRecord.id.trim() ? rawRecord.id : buildDefaultChannelId(rawRecord.provider)
4968
+ );
4969
+ const replacement = remaining.get(rawId);
4970
+ if (!replacement) continue;
4971
+ remaining.delete(rawId);
4972
+ const rawConfig = rawRecord.config && typeof rawRecord.config === "object" && !Array.isArray(rawRecord.config) ? rawRecord.config : {};
4973
+ merged.push({
4974
+ ...rawRecord,
4975
+ ...replacement,
4976
+ config: {
4977
+ ...rawConfig,
4978
+ ...replacement.config
4979
+ }
4980
+ });
4981
+ }
4982
+ merged.push(...remaining.values());
4983
+ return merged;
4984
+ }
4814
4985
  function buildV2FileFromExpandedConfig(config, current) {
4815
4986
  const hasExplicitChannels = Array.isArray(config.channels);
4816
4987
  let channels = hasExplicitChannels ? [...config.channels || []] : [...current?.channels || []];
4817
4988
  channels = normalizeChannelInstances(channels);
4818
- return {
4989
+ const next = {
4990
+ ...current || {},
4819
4991
  schemaVersion: 2,
4820
4992
  runtime: {
4993
+ ...current?.runtime || {},
4821
4994
  provider: config.runtime,
4822
4995
  defaultWorkspaceRoot: config.defaultWorkspaceRoot,
4823
4996
  defaultModel: config.defaultModel,
@@ -4837,33 +5010,42 @@ function buildV2FileFromExpandedConfig(config, current) {
4837
5010
  alias: channel.alias?.trim() || defaultAliasForProvider(channel.provider)
4838
5011
  }))
4839
5012
  };
5013
+ Object.defineProperty(next, RAW_CHANNELS, {
5014
+ value: mergeRawChannelRecords(current, next.channels),
5015
+ enumerable: false
5016
+ });
5017
+ return next;
4840
5018
  }
4841
5019
  function loadConfig() {
4842
5020
  const current = readConfigV2File();
4843
5021
  if (current) return expandConfig(current);
4844
- const legacyEnv = loadRawConfigEnv();
4845
- if (legacyEnv.size > 0) {
4846
- const migrated = migrateLegacyEnvToV2(legacyEnv);
4847
- writeConfigV2File(migrated);
4848
- return expandConfig(migrated);
4849
- }
4850
- const empty = {
4851
- schemaVersion: 2,
4852
- runtime: {
4853
- provider: "codex",
4854
- defaultWorkspaceRoot: DEFAULT_WORKSPACE_ROOT,
4855
- defaultMode: "code",
4856
- historyMessageLimit: 8,
4857
- streamStatusIdleStartSeconds: DEFAULT_STREAM_STATUS_IDLE_START_SECONDS,
4858
- streamStatusCheckIntervalSeconds: DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS,
4859
- codexSkipGitRepoCheck: true,
4860
- codexSandboxMode: "workspace-write",
4861
- codexReasoningEffort: "medium",
4862
- uiAllowLan: false
4863
- },
4864
- channels: []
4865
- };
4866
- return expandConfig(empty);
5022
+ return withFileLock(CONFIG_V2_PATH, () => {
5023
+ const concurrentlyCreated = readConfigV2File();
5024
+ if (concurrentlyCreated) return expandConfig(concurrentlyCreated);
5025
+ const legacyEnv = loadRawConfigEnv();
5026
+ if (legacyEnv.size > 0) {
5027
+ const migrated = migrateLegacyEnvToV2(legacyEnv);
5028
+ writeConfigV2File(migrated);
5029
+ return expandConfig(migrated);
5030
+ }
5031
+ const empty = {
5032
+ schemaVersion: 2,
5033
+ runtime: {
5034
+ provider: "codex",
5035
+ defaultWorkspaceRoot: DEFAULT_WORKSPACE_ROOT,
5036
+ defaultMode: "code",
5037
+ historyMessageLimit: 8,
5038
+ streamStatusIdleStartSeconds: DEFAULT_STREAM_STATUS_IDLE_START_SECONDS,
5039
+ streamStatusCheckIntervalSeconds: DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS,
5040
+ codexSkipGitRepoCheck: true,
5041
+ codexSandboxMode: "workspace-write",
5042
+ codexReasoningEffort: "medium",
5043
+ uiAllowLan: false
5044
+ },
5045
+ channels: []
5046
+ };
5047
+ return expandConfig(empty);
5048
+ });
4867
5049
  }
4868
5050
  function formatEnvLine(key, value) {
4869
5051
  if (value === void 0 || value === "") return "";
@@ -4871,64 +5053,63 @@ function formatEnvLine(key, value) {
4871
5053
  `;
4872
5054
  }
4873
5055
  function saveConfig(config) {
4874
- const current = readConfigV2File();
4875
- const next = buildV2FileFromExpandedConfig(config, current);
4876
- writeConfigV2File(next);
4877
- let out = "";
4878
- out += formatEnvLine("CTI_RUNTIME", next.runtime.provider);
4879
- out += formatEnvLine(
4880
- "CTI_ENABLED_CHANNELS",
4881
- Array.from(new Set(next.channels.filter((channel) => channel.enabled).map((channel) => channel.provider))).join(",")
4882
- );
4883
- out += formatEnvLine("CTI_DEFAULT_WORKSPACE_ROOT", next.runtime.defaultWorkspaceRoot);
4884
- out += formatEnvLine("CTI_DEFAULT_MODEL", next.runtime.defaultModel);
4885
- out += formatEnvLine("CTI_DEFAULT_MODE", next.runtime.defaultMode);
4886
- if (next.runtime.historyMessageLimit !== void 0) {
4887
- out += formatEnvLine("CTI_HISTORY_MESSAGE_LIMIT", String(next.runtime.historyMessageLimit));
4888
- }
4889
- if (next.runtime.streamStatusIdleStartSeconds !== void 0) {
4890
- out += formatEnvLine("CTI_STREAM_STATUS_IDLE_START_SECONDS", String(next.runtime.streamStatusIdleStartSeconds));
4891
- }
4892
- if (next.runtime.streamStatusCheckIntervalSeconds !== void 0) {
4893
- out += formatEnvLine("CTI_STREAM_STATUS_CHECK_INTERVAL_SECONDS", String(next.runtime.streamStatusCheckIntervalSeconds));
4894
- }
4895
- if (next.runtime.codexSkipGitRepoCheck !== void 0) {
4896
- out += formatEnvLine("CTI_CODEX_SKIP_GIT_REPO_CHECK", String(next.runtime.codexSkipGitRepoCheck));
4897
- }
4898
- out += formatEnvLine("CTI_CODEX_SANDBOX_MODE", next.runtime.codexSandboxMode);
4899
- out += formatEnvLine("CTI_CODEX_REASONING_EFFORT", next.runtime.codexReasoningEffort);
4900
- out += formatEnvLine("CTI_UI_ALLOW_LAN", String(next.runtime.uiAllowLan === true));
4901
- out += formatEnvLine("CTI_UI_ACCESS_TOKEN", next.runtime.uiAccessToken);
4902
- const feishu = getChannelByProvider(next, "feishu");
4903
- const feishuConfig = toFeishuConfig(feishu);
4904
- if (feishuConfig) {
4905
- out += formatEnvLine("CTI_FEISHU_APP_ID", feishuConfig.appId);
4906
- out += formatEnvLine("CTI_FEISHU_APP_SECRET", feishuConfig.appSecret);
4907
- out += formatEnvLine("CTI_FEISHU_SITE", feishuConfig.site);
4908
- out += formatEnvLine("CTI_FEISHU_ALLOWED_USERS", feishuConfig.allowedUsers?.join(","));
4909
- if (feishuConfig.streamingEnabled !== void 0) {
4910
- out += formatEnvLine("CTI_FEISHU_STREAMING_ENABLED", String(feishuConfig.streamingEnabled));
4911
- }
4912
- if (feishuConfig.feedbackMarkdownEnabled !== void 0) {
4913
- out += formatEnvLine("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED", String(feishuConfig.feedbackMarkdownEnabled));
4914
- }
4915
- }
4916
- const weixin = getChannelByProvider(next, "weixin");
4917
- const weixinConfig = toWeixinConfig(weixin);
4918
- if (weixinConfig) {
4919
- out += formatEnvLine("CTI_WEIXIN_BASE_URL", weixinConfig.baseUrl);
4920
- out += formatEnvLine("CTI_WEIXIN_CDN_BASE_URL", weixinConfig.cdnBaseUrl);
4921
- if (weixinConfig.mediaEnabled !== void 0) {
4922
- out += formatEnvLine("CTI_WEIXIN_MEDIA_ENABLED", String(weixinConfig.mediaEnabled));
4923
- }
4924
- if (weixinConfig.feedbackMarkdownEnabled !== void 0) {
4925
- out += formatEnvLine("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED", String(weixinConfig.feedbackMarkdownEnabled));
4926
- }
4927
- }
4928
- ensureConfigDir();
4929
- const tmpPath = CONFIG_PATH + ".tmp";
4930
- fs.writeFileSync(tmpPath, out, { mode: 384 });
4931
- fs.renameSync(tmpPath, CONFIG_PATH);
5056
+ withFileLock(CONFIG_V2_PATH, () => {
5057
+ const current = readConfigV2File();
5058
+ const next = buildV2FileFromExpandedConfig(config, current);
5059
+ writeConfigV2File(next);
5060
+ let out = "";
5061
+ out += formatEnvLine("CTI_RUNTIME", next.runtime.provider);
5062
+ out += formatEnvLine(
5063
+ "CTI_ENABLED_CHANNELS",
5064
+ Array.from(new Set(next.channels.filter((channel) => channel.enabled).map((channel) => channel.provider))).join(",")
5065
+ );
5066
+ out += formatEnvLine("CTI_DEFAULT_WORKSPACE_ROOT", next.runtime.defaultWorkspaceRoot);
5067
+ out += formatEnvLine("CTI_DEFAULT_MODEL", next.runtime.defaultModel);
5068
+ out += formatEnvLine("CTI_DEFAULT_MODE", next.runtime.defaultMode);
5069
+ if (next.runtime.historyMessageLimit !== void 0) {
5070
+ out += formatEnvLine("CTI_HISTORY_MESSAGE_LIMIT", String(next.runtime.historyMessageLimit));
5071
+ }
5072
+ if (next.runtime.streamStatusIdleStartSeconds !== void 0) {
5073
+ out += formatEnvLine("CTI_STREAM_STATUS_IDLE_START_SECONDS", String(next.runtime.streamStatusIdleStartSeconds));
5074
+ }
5075
+ if (next.runtime.streamStatusCheckIntervalSeconds !== void 0) {
5076
+ out += formatEnvLine("CTI_STREAM_STATUS_CHECK_INTERVAL_SECONDS", String(next.runtime.streamStatusCheckIntervalSeconds));
5077
+ }
5078
+ if (next.runtime.codexSkipGitRepoCheck !== void 0) {
5079
+ out += formatEnvLine("CTI_CODEX_SKIP_GIT_REPO_CHECK", String(next.runtime.codexSkipGitRepoCheck));
5080
+ }
5081
+ out += formatEnvLine("CTI_CODEX_SANDBOX_MODE", next.runtime.codexSandboxMode);
5082
+ out += formatEnvLine("CTI_CODEX_REASONING_EFFORT", next.runtime.codexReasoningEffort);
5083
+ out += formatEnvLine("CTI_UI_ALLOW_LAN", String(next.runtime.uiAllowLan === true));
5084
+ out += formatEnvLine("CTI_UI_ACCESS_TOKEN", next.runtime.uiAccessToken);
5085
+ const feishu = getChannelByProvider(next, "feishu");
5086
+ const feishuConfig = toFeishuConfig(feishu);
5087
+ if (feishuConfig) {
5088
+ out += formatEnvLine("CTI_FEISHU_APP_ID", feishuConfig.appId);
5089
+ out += formatEnvLine("CTI_FEISHU_APP_SECRET", feishuConfig.appSecret);
5090
+ out += formatEnvLine("CTI_FEISHU_SITE", feishuConfig.site);
5091
+ out += formatEnvLine("CTI_FEISHU_ALLOWED_USERS", feishuConfig.allowedUsers?.join(","));
5092
+ if (feishuConfig.streamingEnabled !== void 0) {
5093
+ out += formatEnvLine("CTI_FEISHU_STREAMING_ENABLED", String(feishuConfig.streamingEnabled));
5094
+ }
5095
+ if (feishuConfig.feedbackMarkdownEnabled !== void 0) {
5096
+ out += formatEnvLine("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED", String(feishuConfig.feedbackMarkdownEnabled));
5097
+ }
5098
+ }
5099
+ const weixin = getChannelByProvider(next, "weixin");
5100
+ const weixinConfig = toWeixinConfig(weixin);
5101
+ if (weixinConfig) {
5102
+ out += formatEnvLine("CTI_WEIXIN_BASE_URL", weixinConfig.baseUrl);
5103
+ out += formatEnvLine("CTI_WEIXIN_CDN_BASE_URL", weixinConfig.cdnBaseUrl);
5104
+ if (weixinConfig.mediaEnabled !== void 0) {
5105
+ out += formatEnvLine("CTI_WEIXIN_MEDIA_ENABLED", String(weixinConfig.mediaEnabled));
5106
+ }
5107
+ if (weixinConfig.feedbackMarkdownEnabled !== void 0) {
5108
+ out += formatEnvLine("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED", String(weixinConfig.feedbackMarkdownEnabled));
5109
+ }
5110
+ }
5111
+ atomicWriteFile(CONFIG_PATH, out);
5112
+ });
4932
5113
  }
4933
5114
  function listChannelInstances(config) {
4934
5115
  return [...config?.channels || loadConfig().channels || []];
@@ -5025,10 +5206,10 @@ function configToSettings(config) {
5025
5206
  }
5026
5207
 
5027
5208
  // src/desktop-sessions.ts
5028
- import fs2 from "node:fs";
5209
+ import fs3 from "node:fs";
5029
5210
  import os2 from "node:os";
5030
5211
  import path2 from "node:path";
5031
- import crypto from "node:crypto";
5212
+ import crypto3 from "node:crypto";
5032
5213
  import { DatabaseSync } from "node:sqlite";
5033
5214
  var ACTIVE_WINDOW_MS = 15 * 60 * 1e3;
5034
5215
  var MAX_SESSION_META_BYTES = 4 * 1024 * 1024;
@@ -5053,13 +5234,13 @@ function getDesktopStateDbPath() {
5053
5234
  const codexHome = getCodexHome();
5054
5235
  let entries;
5055
5236
  try {
5056
- entries = fs2.readdirSync(codexHome, { withFileTypes: true });
5237
+ entries = fs3.readdirSync(codexHome, { withFileTypes: true });
5057
5238
  } catch {
5058
5239
  return null;
5059
5240
  }
5060
5241
  const candidates = entries.filter((entry) => entry.isFile() && /^state_\d+\.sqlite$/i.test(entry.name)).map((entry) => path2.join(codexHome, entry.name)).sort((left, right) => {
5061
5242
  try {
5062
- return fs2.statSync(right).mtimeMs - fs2.statSync(left).mtimeMs;
5243
+ return fs3.statSync(right).mtimeMs - fs3.statSync(left).mtimeMs;
5063
5244
  } catch {
5064
5245
  return 0;
5065
5246
  }
@@ -5084,10 +5265,10 @@ function isInternalSkillWorkspace(cwd) {
5084
5265
  }
5085
5266
  function loadSavedWorkspaceRoots() {
5086
5267
  const statePath = getCodexGlobalStatePath();
5087
- if (!fs2.existsSync(statePath)) return null;
5268
+ if (!fs3.existsSync(statePath)) return null;
5088
5269
  let parsed;
5089
5270
  try {
5090
- parsed = JSON.parse(fs2.readFileSync(statePath, "utf-8"));
5271
+ parsed = JSON.parse(fs3.readFileSync(statePath, "utf-8"));
5091
5272
  } catch {
5092
5273
  return null;
5093
5274
  }
@@ -5102,10 +5283,10 @@ function isWithinSavedWorkspaceRoots(cwd, roots) {
5102
5283
  }
5103
5284
  function loadArchivedThreadIds() {
5104
5285
  const archivedRoot = getArchivedSessionsRoot();
5105
- if (!fs2.existsSync(archivedRoot)) return /* @__PURE__ */ new Set();
5286
+ if (!fs3.existsSync(archivedRoot)) return /* @__PURE__ */ new Set();
5106
5287
  let entries;
5107
5288
  try {
5108
- entries = fs2.readdirSync(archivedRoot, { withFileTypes: true });
5289
+ entries = fs3.readdirSync(archivedRoot, { withFileTypes: true });
5109
5290
  } catch {
5110
5291
  return /* @__PURE__ */ new Set();
5111
5292
  }
@@ -5118,13 +5299,13 @@ function loadArchivedThreadIds() {
5118
5299
  return ids;
5119
5300
  }
5120
5301
  function readFirstLine(filePath, maxBytes = MAX_SESSION_META_BYTES) {
5121
- const fd = fs2.openSync(filePath, "r");
5302
+ const fd = fs3.openSync(filePath, "r");
5122
5303
  try {
5123
5304
  const chunks = [];
5124
5305
  let bytesReadTotal = 0;
5125
5306
  const buffer = Buffer.alloc(4096);
5126
5307
  while (bytesReadTotal < maxBytes) {
5127
- const bytesRead = fs2.readSync(fd, buffer, 0, buffer.length, bytesReadTotal);
5308
+ const bytesRead = fs3.readSync(fd, buffer, 0, buffer.length, bytesReadTotal);
5128
5309
  if (bytesRead <= 0) break;
5129
5310
  const slice = Buffer.from(buffer.subarray(0, bytesRead));
5130
5311
  chunks.push(slice);
@@ -5137,31 +5318,31 @@ function readFirstLine(filePath, maxBytes = MAX_SESSION_META_BYTES) {
5137
5318
  }
5138
5319
  return Buffer.concat(chunks).toString("utf-8").split(/\r?\n/, 1)[0] || "";
5139
5320
  } finally {
5140
- fs2.closeSync(fd);
5321
+ fs3.closeSync(fd);
5141
5322
  }
5142
5323
  }
5143
5324
  function readFilePrefix(filePath, maxBytes = MAX_SESSION_TITLE_SCAN_BYTES) {
5144
- const fd = fs2.openSync(filePath, "r");
5325
+ const fd = fs3.openSync(filePath, "r");
5145
5326
  try {
5146
5327
  const buffer = Buffer.alloc(Math.min(maxBytes, 64 * 1024));
5147
5328
  const chunks = [];
5148
5329
  let offset = 0;
5149
5330
  while (offset < maxBytes) {
5150
5331
  const bytesToRead = Math.min(buffer.length, maxBytes - offset);
5151
- const bytesRead = fs2.readSync(fd, buffer, 0, bytesToRead, offset);
5332
+ const bytesRead = fs3.readSync(fd, buffer, 0, bytesToRead, offset);
5152
5333
  if (bytesRead <= 0) break;
5153
5334
  chunks.push(Buffer.from(buffer.subarray(0, bytesRead)));
5154
5335
  offset += bytesRead;
5155
5336
  }
5156
5337
  return Buffer.concat(chunks).toString("utf-8");
5157
5338
  } finally {
5158
- fs2.closeSync(fd);
5339
+ fs3.closeSync(fd);
5159
5340
  }
5160
5341
  }
5161
5342
  function walkSessionFiles(dirPath, target) {
5162
5343
  let entries;
5163
5344
  try {
5164
- entries = fs2.readdirSync(dirPath, { withFileTypes: true });
5345
+ entries = fs3.readdirSync(dirPath, { withFileTypes: true });
5165
5346
  } catch {
5166
5347
  return;
5167
5348
  }
@@ -5184,10 +5365,10 @@ function isDesktopLike(meta) {
5184
5365
  }
5185
5366
  function loadThreadIndexEntries(archivedThreadIds) {
5186
5367
  const indexPath = getSessionIndexPath();
5187
- if (!fs2.existsSync(indexPath)) return /* @__PURE__ */ new Map();
5368
+ if (!fs3.existsSync(indexPath)) return /* @__PURE__ */ new Map();
5188
5369
  let content = "";
5189
5370
  try {
5190
- content = fs2.readFileSync(indexPath, "utf-8");
5371
+ content = fs3.readFileSync(indexPath, "utf-8");
5191
5372
  } catch {
5192
5373
  return /* @__PURE__ */ new Map();
5193
5374
  }
@@ -5227,7 +5408,7 @@ function parseUpdatedAtValue(value) {
5227
5408
  }
5228
5409
  function loadVisibleDesktopThreads(limit) {
5229
5410
  const dbPath = getDesktopStateDbPath();
5230
- if (!dbPath || !fs2.existsSync(dbPath)) return null;
5411
+ if (!dbPath || !fs3.existsSync(dbPath)) return null;
5231
5412
  let db = null;
5232
5413
  try {
5233
5414
  db = new DatabaseSync(dbPath, { readOnly: true });
@@ -5294,7 +5475,7 @@ function parseDesktopSession(filePath, threadIndexEntries, archivedThreadIds) {
5294
5475
  }
5295
5476
  let stat;
5296
5477
  try {
5297
- stat = fs2.statSync(filePath);
5478
+ stat = fs3.statSync(filePath);
5298
5479
  } catch {
5299
5480
  return null;
5300
5481
  }
@@ -5369,7 +5550,7 @@ function isSessionEventLine(line) {
5369
5550
  }
5370
5551
  function listDesktopSessions(limit) {
5371
5552
  const root = getCodexSessionsRoot();
5372
- if (!fs2.existsSync(root)) return [];
5553
+ if (!fs3.existsSync(root)) return [];
5373
5554
  const archivedThreadIds = loadArchivedThreadIds();
5374
5555
  const threadIndexEntries = loadThreadIndexEntries(archivedThreadIds);
5375
5556
  const savedWorkspaceRoots = loadSavedWorkspaceRoots();
@@ -5407,9 +5588,17 @@ function listDesktopSessions(limit) {
5407
5588
  }
5408
5589
  return sessions.sort((a, b) => b.lastEventAt.localeCompare(a.lastEventAt)).slice(0, typeof limit === "number" && Number.isFinite(limit) && limit > 0 ? Math.max(1, Math.floor(limit)) : void 0);
5409
5590
  }
5591
+ var DESKTOP_SESSION_LOOKUP_CACHE_TTL_MS = 1e3;
5592
+ var desktopSessionLookupCache = null;
5410
5593
  function getDesktopSessionByThreadId(threadId) {
5411
- const sessions = listDesktopSessions(200);
5412
- return sessions.find((session) => session.threadId === threadId) || null;
5594
+ const timestamp = Date.now();
5595
+ if (!desktopSessionLookupCache || desktopSessionLookupCache.expiresAt <= timestamp) {
5596
+ desktopSessionLookupCache = {
5597
+ expiresAt: timestamp + DESKTOP_SESSION_LOOKUP_CACHE_TTL_MS,
5598
+ sessions: new Map(listDesktopSessions().map((session) => [session.threadId, session]))
5599
+ };
5600
+ }
5601
+ return desktopSessionLookupCache.sessions.get(threadId) || null;
5413
5602
  }
5414
5603
  function isArchivedDesktopThread(threadId) {
5415
5604
  return loadArchivedThreadIds().has(threadId);
@@ -5727,25 +5916,25 @@ function removeBinding(store, bindingId) {
5727
5916
  }
5728
5917
 
5729
5918
  // src/service-manager.ts
5730
- import fs4 from "node:fs";
5919
+ import fs5 from "node:fs";
5731
5920
  import os3 from "node:os";
5732
5921
  import path5 from "node:path";
5733
5922
  import { spawn } from "node:child_process";
5734
5923
  import { fileURLToPath } from "node:url";
5735
5924
 
5736
5925
  // src/bridge-instance-lock.ts
5737
- import fs3 from "node:fs";
5926
+ import fs4 from "node:fs";
5738
5927
  import path4 from "node:path";
5739
5928
  var runtimeDir = path4.join(CTI_HOME, "runtime");
5740
5929
  var bridgeInstanceLockFile = path4.join(runtimeDir, "bridge.instance.lock");
5741
5930
  function readJsonFile(filePath, fallback) {
5742
5931
  try {
5743
- return JSON.parse(fs3.readFileSync(filePath, "utf-8"));
5932
+ return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
5744
5933
  } catch {
5745
5934
  return fallback;
5746
5935
  }
5747
5936
  }
5748
- function isProcessAlive(pid) {
5937
+ function isProcessAlive2(pid) {
5749
5938
  if (!pid) return false;
5750
5939
  try {
5751
5940
  process.kill(pid, 0);
@@ -5761,11 +5950,11 @@ function readBridgeInstanceLock(filePath = bridgeInstanceLockFile) {
5761
5950
  if (!Number.isFinite(pid) || pid <= 0 || !createdAt) return null;
5762
5951
  return { pid, createdAt };
5763
5952
  }
5764
- function clearStaleBridgeInstanceLock(filePath = bridgeInstanceLockFile, isAlive = isProcessAlive) {
5953
+ function clearStaleBridgeInstanceLock(filePath = bridgeInstanceLockFile, isAlive = isProcessAlive2) {
5765
5954
  const existing = readBridgeInstanceLock(filePath);
5766
5955
  if (existing && isAlive(existing.pid)) return;
5767
5956
  try {
5768
- fs3.unlinkSync(filePath);
5957
+ fs4.unlinkSync(filePath);
5769
5958
  } catch {
5770
5959
  }
5771
5960
  }
@@ -5786,26 +5975,26 @@ var npmUninstallLogFile = path5.join(runtimeDir2, "npm-uninstall.log");
5786
5975
  var WINDOWS_HIDE = process.platform === "win32" ? { windowsHide: true } : {};
5787
5976
  var BRIDGE_START_LOCK_STALE_MS = 3e4;
5788
5977
  function ensureDirs() {
5789
- fs4.mkdirSync(runtimeDir2, { recursive: true });
5790
- fs4.mkdirSync(logsDir, { recursive: true });
5978
+ fs5.mkdirSync(runtimeDir2, { recursive: true });
5979
+ fs5.mkdirSync(logsDir, { recursive: true });
5791
5980
  }
5792
5981
  function readJsonFile2(filePath, fallback) {
5793
5982
  try {
5794
- return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
5983
+ return JSON.parse(fs5.readFileSync(filePath, "utf-8"));
5795
5984
  } catch {
5796
5985
  return fallback;
5797
5986
  }
5798
5987
  }
5799
5988
  function readPid(filePath) {
5800
5989
  try {
5801
- const raw = fs4.readFileSync(filePath, "utf-8").trim();
5990
+ const raw = fs5.readFileSync(filePath, "utf-8").trim();
5802
5991
  const pid = Number(raw);
5803
5992
  return Number.isFinite(pid) ? pid : void 0;
5804
5993
  } catch {
5805
5994
  return void 0;
5806
5995
  }
5807
5996
  }
5808
- function isProcessAlive2(pid) {
5997
+ function isProcessAlive3(pid) {
5809
5998
  if (!pid) return false;
5810
5999
  try {
5811
6000
  process.kill(pid, 0);
@@ -5823,7 +6012,7 @@ function collectTrackedBridgePids(bridgePid, statusPid, instanceLockPid) {
5823
6012
  }
5824
6013
  return [...unique];
5825
6014
  }
5826
- function resolveTrackedBridgePid(bridgePid, statusPid, instanceLockPid, isAlive = isProcessAlive2) {
6015
+ function resolveTrackedBridgePid(bridgePid, statusPid, instanceLockPid, isAlive = isProcessAlive3) {
5827
6016
  if (isAlive(bridgePid)) return bridgePid;
5828
6017
  if (isAlive(statusPid)) return statusPid;
5829
6018
  if (isAlive(instanceLockPid)) return instanceLockPid;
@@ -5839,7 +6028,7 @@ function getTrackedBridgePids(status) {
5839
6028
  }
5840
6029
  function clearBridgePidFile() {
5841
6030
  try {
5842
- fs4.unlinkSync(bridgePidFile);
6031
+ fs5.unlinkSync(bridgePidFile);
5843
6032
  } catch {
5844
6033
  }
5845
6034
  }
@@ -5854,7 +6043,7 @@ function isBridgeStartLockStale(lock, options = {}) {
5854
6043
  if (!lock) return true;
5855
6044
  const nowMs = options.nowMs ?? Date.now();
5856
6045
  const staleMs = options.staleMs ?? BRIDGE_START_LOCK_STALE_MS;
5857
- const isAlive = options.isAlive ?? isProcessAlive2;
6046
+ const isAlive = options.isAlive ?? isProcessAlive3;
5858
6047
  const createdAtMs = Date.parse(lock.createdAt);
5859
6048
  if (!Number.isFinite(createdAtMs)) return true;
5860
6049
  if (!isAlive(lock.pid)) return true;
@@ -5870,7 +6059,7 @@ function tryAcquireBridgeStartLock(options = {}) {
5870
6059
  };
5871
6060
  for (let attempt = 0; attempt < 2; attempt += 1) {
5872
6061
  try {
5873
- fs4.writeFileSync(filePath, JSON.stringify(payload, null, 2), { encoding: "utf-8", flag: "wx" });
6062
+ fs5.writeFileSync(filePath, JSON.stringify(payload, null, 2), { encoding: "utf-8", flag: "wx" });
5874
6063
  return { acquired: true };
5875
6064
  } catch (error) {
5876
6065
  const code = error.code;
@@ -5884,7 +6073,7 @@ function tryAcquireBridgeStartLock(options = {}) {
5884
6073
  return { acquired: false, holderPid: existing2?.pid };
5885
6074
  }
5886
6075
  try {
5887
- fs4.unlinkSync(filePath);
6076
+ fs5.unlinkSync(filePath);
5888
6077
  } catch {
5889
6078
  }
5890
6079
  }
@@ -5896,14 +6085,14 @@ function releaseBridgeStartLock(filePath = bridgeStartLockFile, ownerPid = proce
5896
6085
  const existing = readBridgeStartLock(filePath);
5897
6086
  if (!existing) {
5898
6087
  try {
5899
- fs4.unlinkSync(filePath);
6088
+ fs5.unlinkSync(filePath);
5900
6089
  } catch {
5901
6090
  }
5902
6091
  return;
5903
6092
  }
5904
6093
  if (existing.pid !== ownerPid) return;
5905
6094
  try {
5906
- fs4.unlinkSync(filePath);
6095
+ fs5.unlinkSync(filePath);
5907
6096
  } catch {
5908
6097
  }
5909
6098
  }
@@ -5966,7 +6155,7 @@ function getBridgeStatus() {
5966
6155
  status.pid,
5967
6156
  readBridgeInstanceLock()?.pid
5968
6157
  );
5969
- if (!isProcessAlive2(pid)) {
6158
+ if (!isProcessAlive3(pid)) {
5970
6159
  return {
5971
6160
  ...status,
5972
6161
  pid,
@@ -5981,7 +6170,7 @@ function getBridgeStatus() {
5981
6170
  }
5982
6171
  function getUiServerStatus() {
5983
6172
  const status = readJsonFile2(uiStatusFile, { running: false, port: uiPort });
5984
- if (!isProcessAlive2(status.pid)) {
6173
+ if (!isProcessAlive3(status.pid)) {
5985
6174
  return {
5986
6175
  ...status,
5987
6176
  running: false,
@@ -6051,7 +6240,7 @@ async function waitForBridgeStartupTurn(timeoutMs = 2e4) {
6051
6240
  async function startBridge() {
6052
6241
  ensureDirs();
6053
6242
  const current = getBridgeStatus();
6054
- const extraAlivePids = getTrackedBridgePids(current).filter((pid) => pid !== current.pid && isProcessAlive2(pid));
6243
+ const extraAlivePids = getTrackedBridgePids(current).filter((pid) => pid !== current.pid && isProcessAlive3(pid));
6055
6244
  if (current.running && extraAlivePids.length === 0) return current;
6056
6245
  if (current.running && extraAlivePids.length > 0) {
6057
6246
  await stopBridge();
@@ -6076,17 +6265,17 @@ async function startBridge() {
6076
6265
  startLockHeld = true;
6077
6266
  try {
6078
6267
  const currentAfterLock = getBridgeStatus();
6079
- const extraAlivePidsAfterLock = getTrackedBridgePids(currentAfterLock).filter((pid) => pid !== currentAfterLock.pid && isProcessAlive2(pid));
6268
+ const extraAlivePidsAfterLock = getTrackedBridgePids(currentAfterLock).filter((pid) => pid !== currentAfterLock.pid && isProcessAlive3(pid));
6080
6269
  if (currentAfterLock.running && extraAlivePidsAfterLock.length === 0) return currentAfterLock;
6081
6270
  if (currentAfterLock.running && extraAlivePidsAfterLock.length > 0) {
6082
6271
  await stopBridge();
6083
6272
  }
6084
6273
  const daemonEntry = path5.join(packageRoot, "dist", "daemon.mjs");
6085
- if (!fs4.existsSync(daemonEntry)) {
6274
+ if (!fs5.existsSync(daemonEntry)) {
6086
6275
  throw new Error(`Daemon bundle not found at ${daemonEntry}. Run npm run build first.`);
6087
6276
  }
6088
- const stdoutFd = fs4.openSync(path5.join(logsDir, "bridge-launcher.out.log"), "a");
6089
- const stderrFd = fs4.openSync(path5.join(logsDir, "bridge-launcher.err.log"), "a");
6277
+ const stdoutFd = fs5.openSync(path5.join(logsDir, "bridge-launcher.out.log"), "a");
6278
+ const stderrFd = fs5.openSync(path5.join(logsDir, "bridge-launcher.err.log"), "a");
6090
6279
  const child = spawn(process.execPath, [daemonEntry], {
6091
6280
  cwd: packageRoot,
6092
6281
  detached: true,
@@ -6110,7 +6299,7 @@ async function startBridge() {
6110
6299
  }
6111
6300
  async function stopBridge() {
6112
6301
  const status = readJsonFile2(bridgeStatusFile, { running: false });
6113
- const pids = getTrackedBridgePids(status).filter((pid) => isProcessAlive2(pid));
6302
+ const pids = getTrackedBridgePids(status).filter((pid) => isProcessAlive3(pid));
6114
6303
  if (pids.length === 0) {
6115
6304
  clearBridgePidFile();
6116
6305
  clearStaleBridgeInstanceLock();
@@ -6135,7 +6324,7 @@ async function stopBridge() {
6135
6324
  }
6136
6325
  const startedAt = Date.now();
6137
6326
  while (Date.now() - startedAt < 1e4) {
6138
- if (pids.every((pid) => !isProcessAlive2(pid))) {
6327
+ if (pids.every((pid) => !isProcessAlive3(pid))) {
6139
6328
  clearBridgePidFile();
6140
6329
  clearStaleBridgeInstanceLock();
6141
6330
  return getBridgeStatus();
@@ -6207,30 +6396,30 @@ async function getBridgeAutostartStatus() {
6207
6396
  function getBridgeLogs(lines = 200) {
6208
6397
  ensureDirs();
6209
6398
  const filePath = path5.join(logsDir, "bridge.log");
6210
- if (!fs4.existsSync(filePath)) return "";
6211
- const all = fs4.readFileSync(filePath, "utf-8").split(/\r?\n/);
6399
+ if (!fs5.existsSync(filePath)) return "";
6400
+ const all = fs5.readFileSync(filePath, "utf-8").split(/\r?\n/);
6212
6401
  return all.slice(Math.max(0, all.length - lines)).join("\n");
6213
6402
  }
6214
6403
  function writeUiServerStatus(status) {
6215
6404
  ensureDirs();
6216
- fs4.writeFileSync(uiStatusFile, JSON.stringify(status, null, 2), "utf-8");
6405
+ fs5.writeFileSync(uiStatusFile, JSON.stringify(status, null, 2), "utf-8");
6217
6406
  }
6218
6407
  async function installCodexIntegration() {
6219
6408
  const sourceSkill = path5.join(packageRoot, "SKILL.md");
6220
- if (!fs4.existsSync(sourceSkill)) {
6409
+ if (!fs5.existsSync(sourceSkill)) {
6221
6410
  throw new Error(`SKILL.md not found at ${sourceSkill}`);
6222
6411
  }
6223
6412
  const skillsDir = path5.join(os3.homedir(), ".codex", "skills");
6224
6413
  const targetDir = path5.join(skillsDir, "codex-to-im");
6225
- fs4.mkdirSync(skillsDir, { recursive: true });
6226
- if (fs4.existsSync(targetDir)) {
6414
+ fs5.mkdirSync(skillsDir, { recursive: true });
6415
+ if (fs5.existsSync(targetDir)) {
6227
6416
  return { targetDir, method: "existing" };
6228
6417
  }
6229
6418
  try {
6230
- fs4.symlinkSync(packageRoot, targetDir, process.platform === "win32" ? "junction" : "dir");
6419
+ fs5.symlinkSync(packageRoot, targetDir, process.platform === "win32" ? "junction" : "dir");
6231
6420
  return { targetDir, method: "junction" };
6232
6421
  } catch {
6233
- fs4.cpSync(packageRoot, targetDir, {
6422
+ fs5.cpSync(packageRoot, targetDir, {
6234
6423
  recursive: true,
6235
6424
  filter: (source) => {
6236
6425
  const relative = path5.relative(packageRoot, source);
@@ -6245,36 +6434,91 @@ async function installCodexIntegration() {
6245
6434
  }
6246
6435
  function isCodexIntegrationInstalled() {
6247
6436
  const targetDir = path5.join(os3.homedir(), ".codex", "skills", "codex-to-im");
6248
- return fs4.existsSync(path5.join(targetDir, "SKILL.md"));
6437
+ return fs5.existsSync(path5.join(targetDir, "SKILL.md"));
6249
6438
  }
6250
6439
 
6251
6440
  // src/store.ts
6252
- import fs5 from "node:fs";
6441
+ import fs7 from "node:fs";
6442
+ import path7 from "node:path";
6443
+ import crypto5 from "node:crypto";
6444
+
6445
+ // src/atomic-file.ts
6446
+ import crypto4 from "node:crypto";
6447
+ import fs6 from "node:fs";
6253
6448
  import path6 from "node:path";
6254
- import crypto2 from "node:crypto";
6255
- var DATA_DIR = path6.join(CTI_HOME, "data");
6256
- var MESSAGES_DIR = path6.join(DATA_DIR, "messages");
6449
+ var RETRYABLE_RENAME_CODES = /* @__PURE__ */ new Set(["EACCES", "EBUSY", "EPERM"]);
6450
+ var WAIT_ARRAY2 = new Int32Array(new SharedArrayBuffer(4));
6451
+ function defaultSleepSync(delayMs) {
6452
+ Atomics.wait(WAIT_ARRAY2, 0, 0, Math.max(1, delayMs));
6453
+ }
6454
+ function isRetryableRenameError(error) {
6455
+ return RETRYABLE_RENAME_CODES.has(error?.code || "");
6456
+ }
6457
+ function renameFileWithRetrySync(source, destination, options = {}) {
6458
+ const maxAttempts = Math.max(1, options.maxAttempts ?? 8);
6459
+ const baseDelayMs = Math.max(1, options.baseDelayMs ?? 15);
6460
+ const renameSync = options.renameSync ?? fs6.renameSync;
6461
+ const sleepSync2 = options.sleepSync ?? defaultSleepSync;
6462
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
6463
+ try {
6464
+ renameSync(source, destination);
6465
+ return;
6466
+ } catch (error) {
6467
+ if (!isRetryableRenameError(error) || attempt === maxAttempts - 1) {
6468
+ throw error;
6469
+ }
6470
+ sleepSync2(Math.min(250, baseDelayMs * 2 ** attempt));
6471
+ }
6472
+ }
6473
+ }
6474
+ function atomicWriteFileSync(filePath, data) {
6475
+ fs6.mkdirSync(path6.dirname(filePath), { recursive: true });
6476
+ const tmpPath = `${filePath}.${process.pid}.${crypto4.randomUUID()}.tmp`;
6477
+ try {
6478
+ fs6.writeFileSync(tmpPath, data, "utf-8");
6479
+ renameFileWithRetrySync(tmpPath, filePath);
6480
+ } finally {
6481
+ try {
6482
+ fs6.rmSync(tmpPath, { force: true });
6483
+ } catch {
6484
+ }
6485
+ }
6486
+ }
6487
+
6488
+ // src/store.ts
6489
+ var DATA_DIR = path7.join(CTI_HOME, "data");
6490
+ var MESSAGES_DIR = path7.join(DATA_DIR, "messages");
6491
+ var SESSIONS_PATH = path7.join(DATA_DIR, "sessions.json");
6492
+ var BINDINGS_PATH = path7.join(DATA_DIR, "bindings.json");
6493
+ var PERMISSIONS_PATH = path7.join(DATA_DIR, "permissions.json");
6494
+ var OFFSETS_PATH = path7.join(DATA_DIR, "offsets.json");
6495
+ var DEDUP_PATH = path7.join(DATA_DIR, "dedup.json");
6496
+ var AUDIT_PATH = path7.join(DATA_DIR, "audit.json");
6497
+ var DEFAULT_DEDUP_TTL_MS = 5 * 60 * 1e3;
6498
+ var INBOUND_DEDUP_TTL_MS = 24 * 60 * 60 * 1e3;
6257
6499
  function ensureDir(dir) {
6258
- fs5.mkdirSync(dir, { recursive: true });
6500
+ fs7.mkdirSync(dir, { recursive: true });
6259
6501
  }
6260
6502
  function atomicWrite(filePath, data) {
6261
- const tmp = filePath + ".tmp";
6262
- fs5.writeFileSync(tmp, data, "utf-8");
6263
- fs5.renameSync(tmp, filePath);
6503
+ atomicWriteFileSync(filePath, data);
6504
+ }
6505
+ function dedupTtlMs(key) {
6506
+ return key.startsWith("inbound:") ? INBOUND_DEDUP_TTL_MS : DEFAULT_DEDUP_TTL_MS;
6264
6507
  }
6265
6508
  function readJson(filePath, fallback) {
6266
6509
  try {
6267
- const raw = fs5.readFileSync(filePath, "utf-8");
6510
+ const raw = fs7.readFileSync(filePath, "utf-8");
6268
6511
  return JSON.parse(raw);
6269
- } catch {
6270
- return fallback;
6512
+ } catch (error) {
6513
+ if (error.code === "ENOENT") return fallback;
6514
+ throw new Error(`\u65E0\u6CD5\u8BFB\u53D6 JSON \u6570\u636E\u6587\u4EF6 ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
6271
6515
  }
6272
6516
  }
6273
6517
  function writeJson(filePath, data) {
6274
6518
  atomicWrite(filePath, JSON.stringify(data, null, 2));
6275
6519
  }
6276
6520
  function uuid() {
6277
- return crypto2.randomUUID();
6521
+ return crypto5.randomUUID();
6278
6522
  }
6279
6523
  function now() {
6280
6524
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -6333,38 +6577,38 @@ var JsonFileStore = class {
6333
6577
  this.reloadSessions();
6334
6578
  this.reloadBindings();
6335
6579
  const perms = readJson(
6336
- path6.join(DATA_DIR, "permissions.json"),
6580
+ PERMISSIONS_PATH,
6337
6581
  {}
6338
6582
  );
6339
6583
  for (const [id, p] of Object.entries(perms)) {
6340
6584
  this.permissionLinks.set(id, p);
6341
6585
  }
6342
6586
  const offsets = readJson(
6343
- path6.join(DATA_DIR, "offsets.json"),
6587
+ OFFSETS_PATH,
6344
6588
  {}
6345
6589
  );
6346
6590
  for (const [k, v] of Object.entries(offsets)) {
6347
6591
  this.offsets.set(k, v);
6348
6592
  }
6349
6593
  const dedup = readJson(
6350
- path6.join(DATA_DIR, "dedup.json"),
6594
+ DEDUP_PATH,
6351
6595
  {}
6352
6596
  );
6353
6597
  for (const [k, v] of Object.entries(dedup)) {
6354
6598
  this.dedupKeys.set(k, v);
6355
6599
  }
6356
- this.auditLog = readJson(path6.join(DATA_DIR, "audit.json"), []);
6600
+ this.auditLog = readJson(AUDIT_PATH, []);
6357
6601
  }
6358
6602
  reloadSessions() {
6359
6603
  const sessions = readJson(
6360
- path6.join(DATA_DIR, "sessions.json"),
6604
+ SESSIONS_PATH,
6361
6605
  {}
6362
6606
  );
6363
6607
  this.sessions = new Map(Object.entries(sessions));
6364
6608
  }
6365
6609
  reloadBindings() {
6366
6610
  const bindings = readJson(
6367
- path6.join(DATA_DIR, "bindings.json"),
6611
+ BINDINGS_PATH,
6368
6612
  {}
6369
6613
  );
6370
6614
  const normalized = /* @__PURE__ */ new Map();
@@ -6378,57 +6622,104 @@ var JsonFileStore = class {
6378
6622
  }
6379
6623
  this.bindings = normalized;
6380
6624
  if (changed) {
6381
- this.persistBindings();
6625
+ withFileLock(BINDINGS_PATH, () => {
6626
+ const latest = readJson(BINDINGS_PATH, {});
6627
+ this.bindings = new Map(Object.values(latest).map((binding) => {
6628
+ const upgraded = upgradeLegacyBinding(binding);
6629
+ return [`${upgraded.channelType}:${upgraded.chatId}`, upgraded];
6630
+ }));
6631
+ this.persistBindings();
6632
+ });
6382
6633
  }
6383
6634
  }
6635
+ reloadPermissions() {
6636
+ this.permissionLinks = new Map(Object.entries(
6637
+ readJson(PERMISSIONS_PATH, {})
6638
+ ));
6639
+ }
6640
+ reloadOffsets() {
6641
+ this.offsets = new Map(Object.entries(readJson(OFFSETS_PATH, {})));
6642
+ }
6643
+ reloadDedup() {
6644
+ this.dedupKeys = new Map(Object.entries(readJson(DEDUP_PATH, {})));
6645
+ }
6646
+ reloadAudit() {
6647
+ this.auditLog = readJson(AUDIT_PATH, []);
6648
+ }
6384
6649
  persistSessions() {
6385
6650
  writeJson(
6386
- path6.join(DATA_DIR, "sessions.json"),
6651
+ SESSIONS_PATH,
6387
6652
  Object.fromEntries(this.sessions)
6388
6653
  );
6389
6654
  }
6390
6655
  persistBindings() {
6391
6656
  writeJson(
6392
- path6.join(DATA_DIR, "bindings.json"),
6657
+ BINDINGS_PATH,
6393
6658
  Object.fromEntries(this.bindings)
6394
6659
  );
6395
6660
  }
6396
6661
  persistPermissions() {
6397
6662
  writeJson(
6398
- path6.join(DATA_DIR, "permissions.json"),
6663
+ PERMISSIONS_PATH,
6399
6664
  Object.fromEntries(this.permissionLinks)
6400
6665
  );
6401
6666
  }
6402
6667
  persistOffsets() {
6403
6668
  writeJson(
6404
- path6.join(DATA_DIR, "offsets.json"),
6669
+ OFFSETS_PATH,
6405
6670
  Object.fromEntries(this.offsets)
6406
6671
  );
6407
6672
  }
6408
6673
  persistDedup() {
6409
6674
  writeJson(
6410
- path6.join(DATA_DIR, "dedup.json"),
6675
+ DEDUP_PATH,
6411
6676
  Object.fromEntries(this.dedupKeys)
6412
6677
  );
6413
6678
  }
6414
6679
  persistAudit() {
6415
- writeJson(path6.join(DATA_DIR, "audit.json"), this.auditLog);
6680
+ writeJson(AUDIT_PATH, this.auditLog);
6416
6681
  }
6417
6682
  persistMessages(sessionId) {
6418
6683
  const msgs = this.messages.get(sessionId) || [];
6419
- writeJson(path6.join(MESSAGES_DIR, `${sessionId}.json`), msgs);
6684
+ writeJson(path7.join(MESSAGES_DIR, `${sessionId}.json`), msgs);
6420
6685
  }
6421
6686
  loadMessages(sessionId) {
6422
6687
  if (this.messages.has(sessionId)) {
6423
6688
  return this.messages.get(sessionId);
6424
6689
  }
6425
6690
  const msgs = readJson(
6426
- path6.join(MESSAGES_DIR, `${sessionId}.json`),
6691
+ path7.join(MESSAGES_DIR, `${sessionId}.json`),
6427
6692
  []
6428
6693
  );
6429
6694
  this.messages.set(sessionId, msgs);
6430
6695
  return msgs;
6431
6696
  }
6697
+ mutateBindings(mutation) {
6698
+ return withFileLock(BINDINGS_PATH, () => {
6699
+ this.reloadBindings();
6700
+ const result = mutation();
6701
+ this.persistBindings();
6702
+ return result;
6703
+ });
6704
+ }
6705
+ mutateSessions(mutation) {
6706
+ return withFileLock(SESSIONS_PATH, () => {
6707
+ this.reloadSessions();
6708
+ const result = mutation();
6709
+ this.persistSessions();
6710
+ return result;
6711
+ });
6712
+ }
6713
+ mutateMessages(sessionId, mutation) {
6714
+ const messagePath = path7.join(MESSAGES_DIR, `${sessionId}.json`);
6715
+ return withFileLock(messagePath, () => {
6716
+ const messages = readJson(messagePath, []);
6717
+ this.messages.set(sessionId, messages);
6718
+ const result = mutation(messages);
6719
+ this.persistMessages(sessionId);
6720
+ return result;
6721
+ });
6722
+ }
6432
6723
  // ── Settings ──
6433
6724
  refreshSettings() {
6434
6725
  if (!this.dynamicSettings) return;
@@ -6451,66 +6742,65 @@ var JsonFileStore = class {
6451
6742
  return this.bindings.get(`${channelType}:${chatId}`) ?? null;
6452
6743
  }
6453
6744
  upsertChannelBinding(data) {
6454
- this.reloadBindings();
6455
- const key = `${data.channelType}:${data.chatId}`;
6456
- const existing = this.bindings.get(key);
6457
- if (existing) {
6458
- const updated = {
6459
- ...existing,
6745
+ return this.mutateBindings(() => {
6746
+ const key = `${data.channelType}:${data.chatId}`;
6747
+ const existing = this.bindings.get(key);
6748
+ if (existing) {
6749
+ const updated = {
6750
+ ...existing,
6751
+ codepilotSessionId: data.codepilotSessionId,
6752
+ sdkSessionId: data.sdkSessionId ?? existing.sdkSessionId,
6753
+ channelProvider: data.channelProvider ?? existing.channelProvider,
6754
+ channelAlias: data.channelAlias ?? existing.channelAlias,
6755
+ chatUserId: data.chatUserId ?? existing.chatUserId,
6756
+ chatDisplayName: data.chatDisplayName ?? existing.chatDisplayName,
6757
+ workingDirectory: data.workingDirectory,
6758
+ model: data.model,
6759
+ mode: data.mode ?? existing.mode,
6760
+ updatedAt: now()
6761
+ };
6762
+ this.bindings.set(key, updated);
6763
+ return updated;
6764
+ }
6765
+ const binding = {
6766
+ id: uuid(),
6767
+ channelType: data.channelType,
6768
+ channelProvider: data.channelProvider,
6769
+ channelAlias: data.channelAlias,
6770
+ chatId: data.chatId,
6771
+ chatUserId: data.chatUserId,
6772
+ chatDisplayName: data.chatDisplayName,
6460
6773
  codepilotSessionId: data.codepilotSessionId,
6461
- sdkSessionId: data.sdkSessionId ?? existing.sdkSessionId,
6462
- channelProvider: data.channelProvider ?? existing.channelProvider,
6463
- channelAlias: data.channelAlias ?? existing.channelAlias,
6464
- chatUserId: data.chatUserId ?? existing.chatUserId,
6465
- chatDisplayName: data.chatDisplayName ?? existing.chatDisplayName,
6774
+ sdkSessionId: data.sdkSessionId ?? "",
6466
6775
  workingDirectory: data.workingDirectory,
6467
6776
  model: data.model,
6468
- mode: data.mode ?? existing.mode,
6777
+ mode: data.mode || this.getSetting("bridge_default_mode") || "code",
6778
+ active: true,
6779
+ createdAt: now(),
6469
6780
  updatedAt: now()
6470
6781
  };
6471
- this.bindings.set(key, updated);
6472
- this.persistBindings();
6473
- return updated;
6474
- }
6475
- const binding = {
6476
- id: uuid(),
6477
- channelType: data.channelType,
6478
- channelProvider: data.channelProvider,
6479
- channelAlias: data.channelAlias,
6480
- chatId: data.chatId,
6481
- chatUserId: data.chatUserId,
6482
- chatDisplayName: data.chatDisplayName,
6483
- codepilotSessionId: data.codepilotSessionId,
6484
- sdkSessionId: data.sdkSessionId ?? "",
6485
- workingDirectory: data.workingDirectory,
6486
- model: data.model,
6487
- mode: data.mode || this.getSetting("bridge_default_mode") || "code",
6488
- active: true,
6489
- createdAt: now(),
6490
- updatedAt: now()
6491
- };
6492
- this.bindings.set(key, binding);
6493
- this.persistBindings();
6494
- return binding;
6782
+ this.bindings.set(key, binding);
6783
+ return binding;
6784
+ });
6495
6785
  }
6496
6786
  deleteChannelBinding(id) {
6497
- this.reloadBindings();
6498
- for (const [key, binding] of this.bindings) {
6499
- if (binding.id !== id) continue;
6500
- this.bindings.delete(key);
6501
- this.persistBindings();
6502
- return;
6503
- }
6787
+ this.mutateBindings(() => {
6788
+ for (const [key, binding] of this.bindings) {
6789
+ if (binding.id !== id) continue;
6790
+ this.bindings.delete(key);
6791
+ return;
6792
+ }
6793
+ });
6504
6794
  }
6505
6795
  updateChannelBinding(id, updates) {
6506
- this.reloadBindings();
6507
- for (const [key, b] of this.bindings) {
6508
- if (b.id === id) {
6509
- this.bindings.set(key, { ...b, ...updates, updatedAt: now() });
6510
- this.persistBindings();
6511
- break;
6796
+ this.mutateBindings(() => {
6797
+ for (const [key, b] of this.bindings) {
6798
+ if (b.id === id) {
6799
+ this.bindings.set(key, { ...b, ...updates, updatedAt: now() });
6800
+ break;
6801
+ }
6512
6802
  }
6513
- }
6803
+ });
6514
6804
  }
6515
6805
  listChannelBindings(channelType) {
6516
6806
  this.reloadBindings();
@@ -6537,74 +6827,78 @@ var JsonFileStore = class {
6537
6827
  return null;
6538
6828
  }
6539
6829
  createSession(name, model, systemPrompt, cwd, mode, options) {
6540
- this.reloadSessions();
6541
- const timestamp = now();
6542
- const session = {
6543
- id: uuid(),
6544
- name,
6545
- working_directory: cwd || process.cwd(),
6546
- model,
6547
- preferred_mode: mode,
6548
- system_prompt: systemPrompt,
6549
- reasoning_effort: options?.reasoningEffort,
6550
- session_type: options?.sessionType || "normal",
6551
- hidden: options?.hidden === true,
6552
- parent_session_id: options?.parentSessionId,
6553
- expires_at: options?.expiresAt,
6554
- created_at: timestamp,
6555
- updated_at: timestamp
6556
- };
6557
- this.sessions.set(session.id, session);
6558
- this.persistSessions();
6559
- return session;
6830
+ return this.mutateSessions(() => {
6831
+ const timestamp = now();
6832
+ const session = {
6833
+ id: uuid(),
6834
+ name,
6835
+ working_directory: cwd || process.cwd(),
6836
+ model,
6837
+ preferred_mode: mode,
6838
+ system_prompt: systemPrompt,
6839
+ reasoning_effort: options?.reasoningEffort,
6840
+ session_type: options?.sessionType || "normal",
6841
+ hidden: options?.hidden === true,
6842
+ parent_session_id: options?.parentSessionId,
6843
+ expires_at: options?.expiresAt,
6844
+ created_at: timestamp,
6845
+ updated_at: timestamp
6846
+ };
6847
+ this.sessions.set(session.id, session);
6848
+ return session;
6849
+ });
6560
6850
  }
6561
6851
  updateSessionProviderId(sessionId, providerId) {
6562
- this.reloadSessions();
6563
- const s = this.sessions.get(sessionId);
6564
- if (s) {
6852
+ this.mutateSessions(() => {
6853
+ const s = this.sessions.get(sessionId);
6854
+ if (!s) return;
6565
6855
  s.provider_id = providerId;
6566
6856
  s.updated_at = now();
6567
- this.persistSessions();
6568
- }
6857
+ });
6569
6858
  }
6570
6859
  updateSession(sessionId, updates, options) {
6571
- this.reloadSessions();
6572
- const session = this.sessions.get(sessionId);
6573
- if (!session) return;
6574
- const next = {
6575
- ...session,
6576
- ...updates,
6577
- id: session.id,
6578
- updated_at: options?.touch === false ? session.updated_at : now()
6579
- };
6580
- this.sessions.set(sessionId, next);
6581
- this.persistSessions();
6860
+ this.mutateSessions(() => {
6861
+ const session = this.sessions.get(sessionId);
6862
+ if (!session) return;
6863
+ const next = {
6864
+ ...session,
6865
+ ...updates,
6866
+ id: session.id,
6867
+ updated_at: options?.touch === false ? session.updated_at : now()
6868
+ };
6869
+ this.sessions.set(sessionId, next);
6870
+ });
6582
6871
  }
6583
6872
  deleteSession(sessionId) {
6584
- this.reloadSessions();
6585
- this.reloadBindings();
6586
- this.sessions.delete(sessionId);
6587
- for (const [key, binding] of this.bindings) {
6588
- if (binding.codepilotSessionId === sessionId) {
6589
- this.bindings.delete(key);
6873
+ const messagePath = path7.join(MESSAGES_DIR, `${sessionId}.json`);
6874
+ withFileLocks([SESSIONS_PATH, BINDINGS_PATH, messagePath], () => {
6875
+ this.reloadSessions();
6876
+ this.reloadBindings();
6877
+ this.sessions.delete(sessionId);
6878
+ for (const [key, binding] of this.bindings) {
6879
+ if (binding.codepilotSessionId === sessionId) {
6880
+ this.bindings.delete(key);
6881
+ }
6590
6882
  }
6591
- }
6592
- this.messages.delete(sessionId);
6593
- try {
6594
- fs5.rmSync(path6.join(MESSAGES_DIR, `${sessionId}.json`), { force: true });
6595
- } catch {
6596
- }
6597
- this.persistSessions();
6598
- this.persistBindings();
6883
+ this.messages.delete(sessionId);
6884
+ try {
6885
+ fs7.rmSync(messagePath, { force: true });
6886
+ } catch {
6887
+ }
6888
+ this.persistSessions();
6889
+ this.persistBindings();
6890
+ });
6599
6891
  }
6600
6892
  // ── Messages ──
6601
6893
  addMessage(sessionId, role, content, _usage) {
6602
- const msgs = this.loadMessages(sessionId);
6603
- msgs.push({ role, content });
6604
- this.persistMessages(sessionId);
6894
+ this.mutateMessages(sessionId, (messages) => {
6895
+ messages.push({ role, content });
6896
+ });
6605
6897
  }
6606
6898
  getMessages(sessionId, opts) {
6607
- const msgs = this.loadMessages(sessionId);
6899
+ const messagePath = path7.join(MESSAGES_DIR, `${sessionId}.json`);
6900
+ const msgs = readJson(messagePath, []);
6901
+ this.messages.set(sessionId, msgs);
6608
6902
  if (opts?.limit && opts.limit > 0) {
6609
6903
  return { messages: msgs.slice(-opts.limit) };
6610
6904
  }
@@ -6636,61 +6930,62 @@ var JsonFileStore = class {
6636
6930
  }
6637
6931
  }
6638
6932
  setSessionRuntimeStatus(_sessionId, _status) {
6639
- this.reloadSessions();
6640
- const session = this.sessions.get(_sessionId);
6641
- if (!session) return;
6642
- const queuedCount = session.queued_count && session.queued_count > 0 ? session.queued_count : 0;
6643
- let runtimeStatus;
6644
- if (_status === "running") {
6645
- runtimeStatus = queuedCount > 0 ? "queued" : "running";
6646
- } else if (_status === "idle") {
6647
- runtimeStatus = queuedCount > 0 ? "queued" : "idle";
6648
- } else {
6649
- runtimeStatus = session.runtime_status;
6650
- }
6651
- const next = {
6652
- ...session,
6653
- runtime_status: runtimeStatus,
6654
- last_runtime_update_at: now(),
6655
- updated_at: now()
6656
- };
6657
- this.sessions.set(_sessionId, next);
6658
- this.persistSessions();
6933
+ this.mutateSessions(() => {
6934
+ const session = this.sessions.get(_sessionId);
6935
+ if (!session) return;
6936
+ const queuedCount = session.queued_count && session.queued_count > 0 ? session.queued_count : 0;
6937
+ let runtimeStatus;
6938
+ if (_status === "running") {
6939
+ runtimeStatus = queuedCount > 0 ? "queued" : "running";
6940
+ } else if (_status === "idle") {
6941
+ runtimeStatus = queuedCount > 0 ? "queued" : "idle";
6942
+ } else {
6943
+ runtimeStatus = session.runtime_status;
6944
+ }
6945
+ const next = {
6946
+ ...session,
6947
+ runtime_status: runtimeStatus,
6948
+ last_runtime_update_at: now(),
6949
+ updated_at: now()
6950
+ };
6951
+ this.sessions.set(_sessionId, next);
6952
+ });
6659
6953
  }
6660
6954
  // ── SDK Session ──
6661
6955
  updateSdkSessionId(sessionId, sdkSessionId) {
6662
- this.reloadSessions();
6663
- this.reloadBindings();
6664
- const s = this.sessions.get(sessionId);
6665
- if (s) {
6666
- s.sdk_session_id = sdkSessionId;
6667
- if (sdkSessionId) {
6668
- s.codex_thread_id = sdkSessionId;
6669
- s.thread_origin = s.thread_origin || "bridge";
6670
- } else {
6671
- delete s.codex_thread_id;
6672
- if (s.thread_origin !== "desktop") {
6673
- delete s.thread_origin;
6956
+ withFileLocks([SESSIONS_PATH, BINDINGS_PATH], () => {
6957
+ this.reloadSessions();
6958
+ this.reloadBindings();
6959
+ const s = this.sessions.get(sessionId);
6960
+ if (s) {
6961
+ s.sdk_session_id = sdkSessionId;
6962
+ if (sdkSessionId) {
6963
+ s.codex_thread_id = sdkSessionId;
6964
+ s.thread_origin = s.thread_origin || "bridge";
6965
+ } else {
6966
+ delete s.codex_thread_id;
6967
+ if (s.thread_origin !== "desktop") {
6968
+ delete s.thread_origin;
6969
+ }
6674
6970
  }
6971
+ s.updated_at = now();
6675
6972
  }
6676
- s.updated_at = now();
6677
- this.persistSessions();
6678
- }
6679
- for (const [key, b] of this.bindings) {
6680
- if (b.codepilotSessionId === sessionId) {
6681
- this.bindings.set(key, { ...b, sdkSessionId, updatedAt: now() });
6973
+ for (const [key, b] of this.bindings) {
6974
+ if (b.codepilotSessionId === sessionId) {
6975
+ this.bindings.set(key, { ...b, sdkSessionId, updatedAt: now() });
6976
+ }
6682
6977
  }
6683
- }
6684
- this.persistBindings();
6978
+ this.persistSessions();
6979
+ this.persistBindings();
6980
+ });
6685
6981
  }
6686
6982
  updateSessionModel(sessionId, model) {
6687
- this.reloadSessions();
6688
- const s = this.sessions.get(sessionId);
6689
- if (s) {
6983
+ this.mutateSessions(() => {
6984
+ const s = this.sessions.get(sessionId);
6985
+ if (!s) return;
6690
6986
  s.model = model;
6691
6987
  s.updated_at = now();
6692
- this.persistSessions();
6693
- }
6988
+ });
6694
6989
  }
6695
6990
  syncSdkTasks(_sessionId, _todos) {
6696
6991
  }
@@ -6703,66 +6998,84 @@ var JsonFileStore = class {
6703
6998
  }
6704
6999
  // ── Audit & Dedup ──
6705
7000
  insertAuditLog(entry) {
6706
- this.auditLog.push({
6707
- ...entry,
6708
- id: uuid(),
6709
- createdAt: now()
7001
+ withFileLock(AUDIT_PATH, () => {
7002
+ this.reloadAudit();
7003
+ this.auditLog.push({
7004
+ ...entry,
7005
+ id: uuid(),
7006
+ createdAt: now()
7007
+ });
7008
+ if (this.auditLog.length > 1e3) {
7009
+ this.auditLog = this.auditLog.slice(-1e3);
7010
+ }
7011
+ this.persistAudit();
6710
7012
  });
6711
- if (this.auditLog.length > 1e3) {
6712
- this.auditLog = this.auditLog.slice(-1e3);
6713
- }
6714
- this.persistAudit();
6715
7013
  }
6716
7014
  checkDedup(key) {
7015
+ this.reloadDedup();
6717
7016
  const ts = this.dedupKeys.get(key);
6718
7017
  if (ts === void 0) return false;
6719
- if (Date.now() - ts > 5 * 60 * 1e3) {
7018
+ if (Date.now() - ts > dedupTtlMs(key)) {
6720
7019
  this.dedupKeys.delete(key);
6721
7020
  return false;
6722
7021
  }
6723
7022
  return true;
6724
7023
  }
6725
7024
  insertDedup(key) {
6726
- this.dedupKeys.set(key, Date.now());
6727
- this.persistDedup();
7025
+ withFileLock(DEDUP_PATH, () => {
7026
+ this.reloadDedup();
7027
+ this.dedupKeys.set(key, Date.now());
7028
+ this.persistDedup();
7029
+ });
6728
7030
  }
6729
7031
  cleanupExpiredDedup() {
6730
- const cutoff = Date.now() - 5 * 60 * 1e3;
6731
- let changed = false;
6732
- for (const [key, ts] of this.dedupKeys) {
6733
- if (ts < cutoff) {
6734
- this.dedupKeys.delete(key);
6735
- changed = true;
7032
+ withFileLock(DEDUP_PATH, () => {
7033
+ this.reloadDedup();
7034
+ const timestamp = Date.now();
7035
+ let changed = false;
7036
+ for (const [key, ts] of this.dedupKeys) {
7037
+ if (timestamp - ts > dedupTtlMs(key)) {
7038
+ this.dedupKeys.delete(key);
7039
+ changed = true;
7040
+ }
6736
7041
  }
6737
- }
6738
- if (changed) this.persistDedup();
7042
+ if (changed) this.persistDedup();
7043
+ });
6739
7044
  }
6740
7045
  insertOutboundRef(_ref) {
6741
7046
  }
6742
7047
  // ── Permission Links ──
6743
7048
  insertPermissionLink(link) {
6744
- const record = {
6745
- permissionRequestId: link.permissionRequestId,
6746
- chatId: link.chatId,
6747
- messageId: link.messageId,
6748
- sessionId: link.sessionId,
6749
- resolved: false,
6750
- suggestions: link.suggestions
6751
- };
6752
- this.permissionLinks.set(link.permissionRequestId, record);
6753
- this.persistPermissions();
7049
+ withFileLock(PERMISSIONS_PATH, () => {
7050
+ this.reloadPermissions();
7051
+ const record = {
7052
+ permissionRequestId: link.permissionRequestId,
7053
+ chatId: link.chatId,
7054
+ messageId: link.messageId,
7055
+ sessionId: link.sessionId,
7056
+ resolved: false,
7057
+ suggestions: link.suggestions
7058
+ };
7059
+ this.permissionLinks.set(link.permissionRequestId, record);
7060
+ this.persistPermissions();
7061
+ });
6754
7062
  }
6755
7063
  getPermissionLink(permissionRequestId) {
7064
+ this.reloadPermissions();
6756
7065
  return this.permissionLinks.get(permissionRequestId) ?? null;
6757
7066
  }
6758
7067
  markPermissionLinkResolved(permissionRequestId) {
6759
- const link = this.permissionLinks.get(permissionRequestId);
6760
- if (!link || link.resolved) return false;
6761
- link.resolved = true;
6762
- this.persistPermissions();
6763
- return true;
7068
+ return withFileLock(PERMISSIONS_PATH, () => {
7069
+ this.reloadPermissions();
7070
+ const link = this.permissionLinks.get(permissionRequestId);
7071
+ if (!link || link.resolved) return false;
7072
+ link.resolved = true;
7073
+ this.persistPermissions();
7074
+ return true;
7075
+ });
6764
7076
  }
6765
7077
  listPendingPermissionLinksByChat(chatId) {
7078
+ this.reloadPermissions();
6766
7079
  const result = [];
6767
7080
  for (const link of this.permissionLinks.values()) {
6768
7081
  if (link.chatId === chatId && !link.resolved) {
@@ -6773,23 +7086,27 @@ var JsonFileStore = class {
6773
7086
  }
6774
7087
  // ── Channel Offsets ──
6775
7088
  getChannelOffset(key) {
7089
+ this.reloadOffsets();
6776
7090
  return this.offsets.get(key) ?? "0";
6777
7091
  }
6778
7092
  setChannelOffset(key, offset) {
6779
- this.offsets.set(key, offset);
6780
- this.persistOffsets();
7093
+ withFileLock(OFFSETS_PATH, () => {
7094
+ this.reloadOffsets();
7095
+ this.offsets.set(key, offset);
7096
+ this.persistOffsets();
7097
+ });
6781
7098
  }
6782
7099
  };
6783
7100
 
6784
7101
  // src/weixin-login.ts
6785
7102
  var import_qrcode = __toESM(require_lib(), 1);
6786
- import fs7 from "node:fs";
6787
- import path8 from "node:path";
6788
- import crypto4 from "node:crypto";
7103
+ import fs9 from "node:fs";
7104
+ import path9 from "node:path";
7105
+ import crypto8 from "node:crypto";
6789
7106
  import { spawn as spawn2 } from "node:child_process";
6790
7107
 
6791
7108
  // src/adapters/weixin/weixin-api.ts
6792
- import crypto3 from "node:crypto";
7109
+ import crypto6 from "node:crypto";
6793
7110
 
6794
7111
  // src/adapters/weixin/weixin-types.ts
6795
7112
  var DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com";
@@ -6838,27 +7155,36 @@ async function pollLoginQrStatus(qrcode, baseUrl) {
6838
7155
  }
6839
7156
 
6840
7157
  // src/weixin-store.ts
6841
- import fs6 from "node:fs";
6842
- import path7 from "node:path";
6843
- var DATA_DIR2 = path7.join(CTI_HOME, "data");
6844
- var ACCOUNTS_PATH = path7.join(DATA_DIR2, "weixin-accounts.json");
6845
- var CONTEXT_TOKENS_PATH = path7.join(DATA_DIR2, "weixin-context-tokens.json");
7158
+ import fs8 from "node:fs";
7159
+ import crypto7 from "node:crypto";
7160
+ import path8 from "node:path";
7161
+ var DATA_DIR2 = path8.join(CTI_HOME, "data");
7162
+ var ACCOUNTS_PATH = path8.join(DATA_DIR2, "weixin-accounts.json");
7163
+ var CONTEXT_TOKENS_PATH = path8.join(DATA_DIR2, "weixin-context-tokens.json");
6846
7164
  var DEFAULT_BASE_URL2 = "https://ilinkai.weixin.qq.com";
6847
7165
  var DEFAULT_CDN_BASE_URL2 = "https://novac2c.cdn.weixin.qq.com/c2c";
6848
7166
  function ensureDir2(dir) {
6849
- fs6.mkdirSync(dir, { recursive: true });
7167
+ fs8.mkdirSync(dir, { recursive: true });
6850
7168
  }
6851
7169
  function atomicWrite2(filePath, data) {
6852
- const tmpPath = `${filePath}.tmp`;
6853
- fs6.writeFileSync(tmpPath, data, "utf-8");
6854
- fs6.renameSync(tmpPath, filePath);
7170
+ const tmpPath = `${filePath}.${process.pid}.${crypto7.randomUUID()}.tmp`;
7171
+ try {
7172
+ fs8.writeFileSync(tmpPath, data, "utf-8");
7173
+ fs8.renameSync(tmpPath, filePath);
7174
+ } finally {
7175
+ try {
7176
+ fs8.rmSync(tmpPath, { force: true });
7177
+ } catch {
7178
+ }
7179
+ }
6855
7180
  }
6856
7181
  function readJson2(filePath, fallback) {
6857
7182
  try {
6858
- const raw = fs6.readFileSync(filePath, "utf-8");
7183
+ const raw = fs8.readFileSync(filePath, "utf-8");
6859
7184
  return JSON.parse(raw);
6860
- } catch {
6861
- return fallback;
7185
+ } catch (error) {
7186
+ if (error.code === "ENOENT") return fallback;
7187
+ throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u5FAE\u4FE1\u6570\u636E\u6587\u4EF6 ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
6862
7188
  }
6863
7189
  }
6864
7190
  function now2() {
@@ -6907,37 +7233,38 @@ function listWeixinAccounts() {
6907
7233
  return sortAccountsByRecency(readAccounts());
6908
7234
  }
6909
7235
  function upsertWeixinAccount(params) {
6910
- const accounts = readAccounts();
6911
- const existing = accounts.find((account2) => account2.accountId === params.accountId);
6912
- const timestamp = now2();
6913
- const account = existing ? {
6914
- ...existing,
6915
- userId: params.userId ?? existing.userId,
6916
- baseUrl: params.baseUrl ?? existing.baseUrl,
6917
- cdnBaseUrl: params.cdnBaseUrl ?? existing.cdnBaseUrl,
6918
- token: params.token ?? existing.token,
6919
- name: params.name ?? existing.name,
6920
- enabled: params.enabled ?? existing.enabled,
6921
- lastLoginAt: timestamp,
6922
- updatedAt: timestamp
6923
- } : {
6924
- accountId: params.accountId,
6925
- userId: params.userId ?? "",
6926
- baseUrl: params.baseUrl ?? DEFAULT_BASE_URL2,
6927
- cdnBaseUrl: params.cdnBaseUrl ?? DEFAULT_CDN_BASE_URL2,
6928
- token: params.token ?? "",
6929
- name: params.name ?? params.accountId,
6930
- enabled: params.enabled ?? true,
6931
- lastLoginAt: timestamp,
6932
- createdAt: timestamp,
6933
- updatedAt: timestamp
6934
- };
6935
- const nextAccounts = [
6936
- account,
6937
- ...accounts.filter((item) => item.accountId !== account.accountId)
6938
- ];
6939
- writeAccounts(nextAccounts);
6940
- return account;
7236
+ return withFileLock(ACCOUNTS_PATH, () => {
7237
+ const accounts = readAccounts();
7238
+ const existing = accounts.find((account2) => account2.accountId === params.accountId);
7239
+ const timestamp = now2();
7240
+ const account = existing ? {
7241
+ ...existing,
7242
+ userId: params.userId ?? existing.userId,
7243
+ baseUrl: params.baseUrl ?? existing.baseUrl,
7244
+ cdnBaseUrl: params.cdnBaseUrl ?? existing.cdnBaseUrl,
7245
+ token: params.token ?? existing.token,
7246
+ name: params.name ?? existing.name,
7247
+ enabled: params.enabled ?? existing.enabled,
7248
+ lastLoginAt: timestamp,
7249
+ updatedAt: timestamp
7250
+ } : {
7251
+ accountId: params.accountId,
7252
+ userId: params.userId ?? "",
7253
+ baseUrl: params.baseUrl ?? DEFAULT_BASE_URL2,
7254
+ cdnBaseUrl: params.cdnBaseUrl ?? DEFAULT_CDN_BASE_URL2,
7255
+ token: params.token ?? "",
7256
+ name: params.name ?? params.accountId,
7257
+ enabled: params.enabled ?? true,
7258
+ lastLoginAt: timestamp,
7259
+ createdAt: timestamp,
7260
+ updatedAt: timestamp
7261
+ };
7262
+ writeAccounts([
7263
+ account,
7264
+ ...accounts.filter((item) => item.accountId !== account.accountId)
7265
+ ]);
7266
+ return account;
7267
+ });
6941
7268
  }
6942
7269
 
6943
7270
  // src/weixin-login.ts
@@ -6945,11 +7272,11 @@ var MAX_REFRESHES = 3;
6945
7272
  var QR_TTL_MS = 5 * 6e4;
6946
7273
  var POLL_INTERVAL_MS = 3e3;
6947
7274
  var WEB_SESSION_TTL_MS = 15 * 6e4;
6948
- var RUNTIME_DIR = path8.join(CTI_HOME, "runtime");
6949
- var HTML_PATH = path8.join(RUNTIME_DIR, "weixin-login.html");
7275
+ var RUNTIME_DIR = path9.join(CTI_HOME, "runtime");
7276
+ var HTML_PATH = path9.join(RUNTIME_DIR, "weixin-login.html");
6950
7277
  var webLoginSessions = /* @__PURE__ */ new Map();
6951
7278
  function ensureRuntimeDir() {
6952
- fs7.mkdirSync(RUNTIME_DIR, { recursive: true });
7279
+ fs9.mkdirSync(RUNTIME_DIR, { recursive: true });
6953
7280
  }
6954
7281
  function escapeHtml(text2) {
6955
7282
  return text2.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
@@ -7262,7 +7589,7 @@ function buildWeixinLoginPopupHtml(sessionId) {
7262
7589
  async function writeQrHtml(session) {
7263
7590
  ensureRuntimeDir();
7264
7591
  const qrSvg = await buildQrSvg(session.qrImageUrl);
7265
- fs7.writeFileSync(HTML_PATH, buildQrHtml(qrSvg), "utf-8");
7592
+ fs9.writeFileSync(HTML_PATH, buildQrHtml(qrSvg), "utf-8");
7266
7593
  }
7267
7594
  function openQrHtml() {
7268
7595
  try {
@@ -7451,7 +7778,7 @@ async function startWeixinLoginWebSession(options = {}) {
7451
7778
  ensureRuntimeDir();
7452
7779
  const config = options.config || {};
7453
7780
  const seed = await createSession(0, config.baseUrl);
7454
- const sessionId = crypto4.randomUUID();
7781
+ const sessionId = crypto8.randomUUID();
7455
7782
  const qrSvg = await buildQrSvg(seed.qrImageUrl);
7456
7783
  const session = {
7457
7784
  id: sessionId,
@@ -7523,7 +7850,7 @@ async function runWeixinLogin(config = {}) {
7523
7850
  }
7524
7851
  var isMainModule = (() => {
7525
7852
  const entry = process.argv[1];
7526
- return !!entry && path8.resolve(entry) === path8.resolve(new URL(import.meta.url).pathname);
7853
+ return !!entry && path9.resolve(entry) === path9.resolve(new URL(import.meta.url).pathname);
7527
7854
  })();
7528
7855
  if (isMainModule) {
7529
7856
  runWeixinLogin().catch((err) => {
@@ -7533,11 +7860,11 @@ if (isMainModule) {
7533
7860
  }
7534
7861
 
7535
7862
  // src/codex-models.ts
7536
- import fs8 from "node:fs";
7863
+ import fs10 from "node:fs";
7537
7864
  import os4 from "node:os";
7538
- import path9 from "node:path";
7539
- var DEFAULT_CODEX_CONFIG_PATH = path9.join(os4.homedir(), ".codex", "config.toml");
7540
- var DEFAULT_CODEX_MODELS_CACHE_PATH = path9.join(os4.homedir(), ".codex", "models_cache.json");
7865
+ import path10 from "node:path";
7866
+ var DEFAULT_CODEX_CONFIG_PATH = path10.join(os4.homedir(), ".codex", "config.toml");
7867
+ var DEFAULT_CODEX_MODELS_CACHE_PATH = path10.join(os4.homedir(), ".codex", "models_cache.json");
7541
7868
  var REASONING_LEVEL_DESCRIPTIONS = {
7542
7869
  low: "Fast responses with lighter reasoning",
7543
7870
  medium: "Balances speed and reasoning depth for everyday tasks",
@@ -7586,7 +7913,7 @@ var KNOWN_CODEX_MODEL_FALLBACKS = [
7586
7913
  ];
7587
7914
  function readConfiguredCodexModel(configPath = DEFAULT_CODEX_CONFIG_PATH) {
7588
7915
  try {
7589
- const raw = fs8.readFileSync(configPath, "utf-8");
7916
+ const raw = fs10.readFileSync(configPath, "utf-8");
7590
7917
  let inSection = false;
7591
7918
  for (const line of raw.split(/\r?\n/)) {
7592
7919
  const trimmed = line.trim();
@@ -7630,7 +7957,7 @@ function parseSupportedReasoningLevels(value) {
7630
7957
  }
7631
7958
  function listCachedCodexModels(cachePath = DEFAULT_CODEX_MODELS_CACHE_PATH) {
7632
7959
  try {
7633
- const raw = fs8.readFileSync(cachePath, "utf-8");
7960
+ const raw = fs10.readFileSync(cachePath, "utf-8");
7634
7961
  const parsed = JSON.parse(raw);
7635
7962
  if (!Array.isArray(parsed.models)) return [];
7636
7963
  const seen = /* @__PURE__ */ new Set();
@@ -7696,6 +8023,74 @@ function listAvailableCodexModels(cachePath = DEFAULT_CODEX_MODELS_CACHE_PATH, a
7696
8023
  return merged;
7697
8024
  }
7698
8025
 
8026
+ // src/ui-http-security.ts
8027
+ var DEFAULT_JSON_BODY_LIMIT_BYTES = 1024 * 1024;
8028
+ var HttpRequestError = class extends Error {
8029
+ constructor(statusCode, message) {
8030
+ super(message);
8031
+ this.statusCode = statusCode;
8032
+ this.name = "HttpRequestError";
8033
+ }
8034
+ };
8035
+ function isMutationMethod(method) {
8036
+ return method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE";
8037
+ }
8038
+ function getExpectedOrigin(headers) {
8039
+ const host = headers.host?.trim();
8040
+ if (!host) throw new HttpRequestError(400, "\u8BF7\u6C42\u7F3A\u5C11 Host header\u3002");
8041
+ return `http://${host}`;
8042
+ }
8043
+ function assertTrustedMutationRequest(request) {
8044
+ if (!isMutationMethod(request.method)) return;
8045
+ const fetchSite = request.headers["sec-fetch-site"];
8046
+ if (fetchSite === "cross-site") {
8047
+ throw new HttpRequestError(403, "\u5DF2\u62D2\u7EDD\u8DE8\u7AD9\u7BA1\u7406\u8BF7\u6C42\u3002");
8048
+ }
8049
+ const origin = request.headers.origin;
8050
+ if (!origin) return;
8051
+ if (origin === "null") {
8052
+ throw new HttpRequestError(403, "\u5DF2\u62D2\u7EDD\u6765\u6E90\u4E0D\u660E\u7684\u7BA1\u7406\u8BF7\u6C42\u3002");
8053
+ }
8054
+ let normalizedOrigin;
8055
+ try {
8056
+ normalizedOrigin = new URL(origin).origin;
8057
+ } catch {
8058
+ throw new HttpRequestError(403, "\u8BF7\u6C42 Origin \u65E0\u6548\u3002");
8059
+ }
8060
+ if (normalizedOrigin !== getExpectedOrigin(request.headers)) {
8061
+ throw new HttpRequestError(403, "\u5DF2\u62D2\u7EDD\u975E\u540C\u6E90\u7BA1\u7406\u8BF7\u6C42\u3002");
8062
+ }
8063
+ }
8064
+ async function readJsonBody(request, maxBytes = DEFAULT_JSON_BODY_LIMIT_BYTES) {
8065
+ const boundedMaxBytes = Math.max(1, maxBytes);
8066
+ const declaredLength = Number(request.headers["content-length"]);
8067
+ if (Number.isFinite(declaredLength) && declaredLength > boundedMaxBytes) {
8068
+ throw new HttpRequestError(413, `\u8BF7\u6C42\u4F53\u8D85\u8FC7 ${boundedMaxBytes} \u5B57\u8282\u9650\u5236\u3002`);
8069
+ }
8070
+ const chunks = [];
8071
+ let receivedBytes = 0;
8072
+ for await (const chunk of request) {
8073
+ const buffer = Buffer.from(chunk);
8074
+ receivedBytes += buffer.length;
8075
+ if (receivedBytes > boundedMaxBytes) {
8076
+ request.resume();
8077
+ throw new HttpRequestError(413, `\u8BF7\u6C42\u4F53\u8D85\u8FC7 ${boundedMaxBytes} \u5B57\u8282\u9650\u5236\u3002`);
8078
+ }
8079
+ chunks.push(buffer);
8080
+ }
8081
+ const raw = Buffer.concat(chunks).toString("utf-8").trim();
8082
+ if (!raw) return {};
8083
+ const contentType = request.headers["content-type"] || "";
8084
+ if (!/^application\/json(?:\s*;|$)/i.test(contentType)) {
8085
+ throw new HttpRequestError(415, "\u8BF7\u6C42\u4F53\u5FC5\u987B\u4F7F\u7528 application/json\u3002");
8086
+ }
8087
+ try {
8088
+ return JSON.parse(raw);
8089
+ } catch {
8090
+ throw new HttpRequestError(400, "\u8BF7\u6C42\u4F53\u4E0D\u662F\u6709\u6548 JSON\u3002");
8091
+ }
8092
+ }
8093
+
7699
8094
  // src/ui-server.ts
7700
8095
  var port = 4781;
7701
8096
  var serverStartTime = (/* @__PURE__ */ new Date()).toISOString();
@@ -7781,14 +8176,6 @@ function text(response, statusCode, body) {
7781
8176
  response.writeHead(statusCode, { "Content-Type": "text/plain; charset=utf-8" });
7782
8177
  response.end(body);
7783
8178
  }
7784
- async function readJsonBody(request) {
7785
- const chunks = [];
7786
- for await (const chunk of request) {
7787
- chunks.push(Buffer.from(chunk));
7788
- }
7789
- const raw = Buffer.concat(chunks).toString("utf-8").trim();
7790
- return raw ? JSON.parse(raw) : {};
7791
- }
7792
8179
  function asString(value) {
7793
8180
  if (typeof value !== "string") return void 0;
7794
8181
  const trimmed = value.trim();
@@ -7869,14 +8256,14 @@ function channelToPayload(channel) {
7869
8256
  };
7870
8257
  }
7871
8258
  function generateAccessToken() {
7872
- return crypto5.randomBytes(18).toString("base64url");
8259
+ return crypto9.randomBytes(18).toString("base64url");
7873
8260
  }
7874
8261
  function timingSafeMatch(left, right) {
7875
8262
  if (!left || !right) return false;
7876
8263
  const leftBuffer = Buffer.from(left);
7877
8264
  const rightBuffer = Buffer.from(right);
7878
8265
  if (leftBuffer.length !== rightBuffer.length) return false;
7879
- return crypto5.timingSafeEqual(leftBuffer, rightBuffer);
8266
+ return crypto9.timingSafeEqual(leftBuffer, rightBuffer);
7880
8267
  }
7881
8268
  function parseCookies(request) {
7882
8269
  const header = request.headers.cookie;
@@ -11627,6 +12014,11 @@ function renderHtml() {
11627
12014
  }
11628
12015
  var server = http.createServer(async (request, response) => {
11629
12016
  try {
12017
+ response.setHeader("X-Content-Type-Options", "nosniff");
12018
+ response.setHeader("X-Frame-Options", "DENY");
12019
+ response.setHeader("Referrer-Policy", "no-referrer");
12020
+ response.setHeader("Cache-Control", "no-store");
12021
+ assertTrustedMutationRequest(request);
11630
12022
  const url = new URL(request.url || "/", getUiServerUrl(port));
11631
12023
  const config = loadConfig();
11632
12024
  const localRequest = isLocalRequest(request);
@@ -11964,16 +12356,18 @@ var server = http.createServer(async (request, response) => {
11964
12356
  }
11965
12357
  text(response, 404, "Not found");
11966
12358
  } catch (error) {
11967
- json(response, 500, {
12359
+ const statusCode = error instanceof HttpRequestError ? error.statusCode : 500;
12360
+ json(response, statusCode, {
11968
12361
  error: error instanceof Error ? error.message : String(error)
11969
12362
  });
11970
12363
  }
11971
12364
  });
11972
12365
  async function startServer() {
11973
12366
  port = await resolveUiPort(parsePreferredPort());
12367
+ const listenHost = loadConfig().uiAllowLan === true ? "0.0.0.0" : "127.0.0.1";
11974
12368
  await new Promise((resolve, reject) => {
11975
12369
  server.once("error", reject);
11976
- server.listen(port, "0.0.0.0", () => {
12370
+ server.listen(port, listenHost, () => {
11977
12371
  server.removeListener("error", reject);
11978
12372
  resolve();
11979
12373
  });