codex-to-im 1.0.29 → 1.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -20,6 +20,14 @@ var DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), "cx2im");
20
20
  var CTI_HOME = process.env.CTI_HOME || DEFAULT_CTI_HOME;
21
21
  var CONFIG_PATH = path.join(CTI_HOME, "config.env");
22
22
  var CONFIG_V2_PATH = path.join(CTI_HOME, "config.v2.json");
23
+ function expandHomePath(value) {
24
+ if (!value) return value;
25
+ if (value === "~") return os.homedir();
26
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
27
+ return path.join(os.homedir(), value.slice(2));
28
+ }
29
+ return value;
30
+ }
23
31
  function parseEnvFile(content) {
24
32
  const entries = /* @__PURE__ */ new Map();
25
33
  for (const line of content.split("\n")) {
@@ -43,6 +51,175 @@ function loadRawConfigEnv() {
43
51
  return /* @__PURE__ */ new Map();
44
52
  }
45
53
  }
54
+ function splitCsv(value) {
55
+ if (!value) return void 0;
56
+ return value.split(",").map((s) => s.trim()).filter(Boolean);
57
+ }
58
+ function parsePositiveInt(value) {
59
+ if (!value) return void 0;
60
+ const parsed = Number(value);
61
+ if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
62
+ return Math.floor(parsed);
63
+ }
64
+ function parseSandboxMode(value) {
65
+ if (value === "read-only" || value === "workspace-write" || value === "danger-full-access") {
66
+ return value;
67
+ }
68
+ return void 0;
69
+ }
70
+ function parseReasoningEffort(value) {
71
+ if (value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh") {
72
+ return value;
73
+ }
74
+ return void 0;
75
+ }
76
+ function normalizeFeishuSite(value) {
77
+ const normalized = (value || "").trim().replace(/\/+$/, "").toLowerCase();
78
+ if (!normalized) return "feishu";
79
+ if (normalized === "lark") return "lark";
80
+ if (normalized === "feishu") return "feishu";
81
+ if (normalized.includes("open.larksuite.com")) return "lark";
82
+ return "feishu";
83
+ }
84
+ function nowIso() {
85
+ return (/* @__PURE__ */ new Date()).toISOString();
86
+ }
87
+ function ensureConfigDir() {
88
+ fs.mkdirSync(CTI_HOME, { recursive: true });
89
+ }
90
+ function readConfigV2File() {
91
+ try {
92
+ const parsed = JSON.parse(fs.readFileSync(CONFIG_V2_PATH, "utf-8"));
93
+ if (parsed && parsed.schemaVersion === 2 && parsed.runtime && Array.isArray(parsed.channels)) {
94
+ return parsed;
95
+ }
96
+ return null;
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+ function writeConfigV2File(config) {
102
+ ensureConfigDir();
103
+ const tmpPath = CONFIG_V2_PATH + ".tmp";
104
+ fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2), { mode: 384 });
105
+ fs.renameSync(tmpPath, CONFIG_V2_PATH);
106
+ }
107
+ function defaultAliasForProvider(provider) {
108
+ return provider === "feishu" ? "\u98DE\u4E66" : "\u5FAE\u4FE1";
109
+ }
110
+ function buildDefaultChannelId(provider) {
111
+ return `${provider}-default`;
112
+ }
113
+ function migrateLegacyEnvToV2(env) {
114
+ const rawRuntime = env.get("CTI_RUNTIME") || "codex";
115
+ const runtime = ["claude", "codex", "auto"].includes(rawRuntime) ? rawRuntime : "codex";
116
+ const enabledChannels = splitCsv(env.get("CTI_ENABLED_CHANNELS")) ?? ["feishu"];
117
+ const timestamp = nowIso();
118
+ const channels = [];
119
+ const hasFeishuConfig = Boolean(
120
+ env.get("CTI_FEISHU_APP_ID") || env.get("CTI_FEISHU_APP_SECRET") || env.get("CTI_FEISHU_ALLOWED_USERS") || enabledChannels.includes("feishu")
121
+ );
122
+ if (hasFeishuConfig) {
123
+ channels.push({
124
+ id: buildDefaultChannelId("feishu"),
125
+ alias: defaultAliasForProvider("feishu"),
126
+ provider: "feishu",
127
+ enabled: enabledChannels.includes("feishu"),
128
+ createdAt: timestamp,
129
+ updatedAt: timestamp,
130
+ config: {
131
+ appId: env.get("CTI_FEISHU_APP_ID") || void 0,
132
+ appSecret: env.get("CTI_FEISHU_APP_SECRET") || void 0,
133
+ site: normalizeFeishuSite(env.get("CTI_FEISHU_SITE") || env.get("CTI_FEISHU_DOMAIN")),
134
+ allowedUsers: splitCsv(env.get("CTI_FEISHU_ALLOWED_USERS")),
135
+ streamingEnabled: env.has("CTI_FEISHU_STREAMING_ENABLED") ? env.get("CTI_FEISHU_STREAMING_ENABLED") === "true" : true,
136
+ feedbackMarkdownEnabled: env.has("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") === "true" : true
137
+ }
138
+ });
139
+ }
140
+ const hasWeixinConfig = Boolean(
141
+ env.get("CTI_WEIXIN_BASE_URL") || env.get("CTI_WEIXIN_CDN_BASE_URL") || env.get("CTI_WEIXIN_MEDIA_ENABLED") || enabledChannels.includes("weixin")
142
+ );
143
+ if (hasWeixinConfig) {
144
+ channels.push({
145
+ id: buildDefaultChannelId("weixin"),
146
+ alias: defaultAliasForProvider("weixin"),
147
+ provider: "weixin",
148
+ enabled: enabledChannels.includes("weixin"),
149
+ createdAt: timestamp,
150
+ updatedAt: timestamp,
151
+ config: {
152
+ baseUrl: env.get("CTI_WEIXIN_BASE_URL") || void 0,
153
+ cdnBaseUrl: env.get("CTI_WEIXIN_CDN_BASE_URL") || void 0,
154
+ mediaEnabled: env.has("CTI_WEIXIN_MEDIA_ENABLED") ? env.get("CTI_WEIXIN_MEDIA_ENABLED") === "true" : void 0,
155
+ feedbackMarkdownEnabled: env.has("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") === "true" : false
156
+ }
157
+ });
158
+ }
159
+ return {
160
+ schemaVersion: 2,
161
+ runtime: {
162
+ provider: runtime,
163
+ defaultWorkspaceRoot: expandHomePath(env.get("CTI_DEFAULT_WORKSPACE_ROOT")) || void 0,
164
+ defaultModel: env.get("CTI_DEFAULT_MODEL") || void 0,
165
+ defaultMode: env.get("CTI_DEFAULT_MODE") || "code",
166
+ historyMessageLimit: parsePositiveInt(env.get("CTI_HISTORY_MESSAGE_LIMIT")) ?? 8,
167
+ codexSkipGitRepoCheck: env.has("CTI_CODEX_SKIP_GIT_REPO_CHECK") ? env.get("CTI_CODEX_SKIP_GIT_REPO_CHECK") === "true" : true,
168
+ codexSandboxMode: parseSandboxMode(env.get("CTI_CODEX_SANDBOX_MODE")) ?? "workspace-write",
169
+ codexReasoningEffort: parseReasoningEffort(env.get("CTI_CODEX_REASONING_EFFORT")) ?? "medium",
170
+ uiAllowLan: env.get("CTI_UI_ALLOW_LAN") === "true",
171
+ uiAccessToken: env.get("CTI_UI_ACCESS_TOKEN") || void 0,
172
+ autoApprove: env.get("CTI_AUTO_APPROVE") === "true"
173
+ },
174
+ channels
175
+ };
176
+ }
177
+ function expandConfig(v2) {
178
+ return {
179
+ schemaVersion: 2,
180
+ channels: v2.channels,
181
+ runtime: v2.runtime.provider,
182
+ enabledChannels: Array.from(new Set(
183
+ v2.channels.filter((channel) => channel.enabled).map((channel) => channel.provider)
184
+ )),
185
+ defaultWorkspaceRoot: v2.runtime.defaultWorkspaceRoot,
186
+ defaultModel: v2.runtime.defaultModel,
187
+ defaultMode: v2.runtime.defaultMode || "code",
188
+ historyMessageLimit: v2.runtime.historyMessageLimit ?? 8,
189
+ codexSkipGitRepoCheck: v2.runtime.codexSkipGitRepoCheck ?? true,
190
+ codexSandboxMode: v2.runtime.codexSandboxMode ?? "workspace-write",
191
+ codexReasoningEffort: v2.runtime.codexReasoningEffort ?? "medium",
192
+ uiAllowLan: v2.runtime.uiAllowLan === true,
193
+ uiAccessToken: v2.runtime.uiAccessToken || void 0,
194
+ autoApprove: v2.runtime.autoApprove === true
195
+ };
196
+ }
197
+ function loadConfig() {
198
+ const current = readConfigV2File();
199
+ if (current) return expandConfig(current);
200
+ const legacyEnv = loadRawConfigEnv();
201
+ if (legacyEnv.size > 0) {
202
+ const migrated = migrateLegacyEnvToV2(legacyEnv);
203
+ writeConfigV2File(migrated);
204
+ return expandConfig(migrated);
205
+ }
206
+ const empty = {
207
+ schemaVersion: 2,
208
+ runtime: {
209
+ provider: "codex",
210
+ defaultWorkspaceRoot: DEFAULT_WORKSPACE_ROOT,
211
+ defaultMode: "code",
212
+ historyMessageLimit: 8,
213
+ codexSkipGitRepoCheck: true,
214
+ codexSandboxMode: "workspace-write",
215
+ codexReasoningEffort: "medium",
216
+ uiAllowLan: false,
217
+ autoApprove: false
218
+ },
219
+ channels: []
220
+ };
221
+ return expandConfig(empty);
222
+ }
46
223
 
47
224
  // src/service-manager.ts
48
225
  var moduleDir = path2.dirname(fileURLToPath(import.meta.url));
@@ -308,6 +485,27 @@ function buildDaemonEnv() {
308
485
  delete env.CLAUDECODE;
309
486
  return env;
310
487
  }
488
+ function describeBridgeStartupPreflightFailure(channels) {
489
+ const configured = Array.isArray(channels) ? channels : [];
490
+ if (configured.length === 0) {
491
+ return "\u672A\u914D\u7F6E\u4EFB\u4F55\u901A\u9053\u5B9E\u4F8B\u3002\u8BF7\u5148\u5728 Web \u63A7\u5236\u53F0\u521B\u5EFA\u5E76\u4FDD\u5B58\u81F3\u5C11\u4E00\u4E2A\u98DE\u4E66\u6216\u5FAE\u4FE1\u901A\u9053\uFF0C\u7136\u540E\u518D\u542F\u52A8\u6865\u63A5\u670D\u52A1\u3002";
492
+ }
493
+ const enabled = configured.filter((channel) => channel.enabled !== false);
494
+ if (enabled.length === 0) {
495
+ return "\u5F53\u524D\u6240\u6709\u901A\u9053\u5B9E\u4F8B\u90FD\u5DF2\u7981\u7528\u3002\u8BF7\u5148\u542F\u7528\u81F3\u5C11\u4E00\u4E2A\u901A\u9053\u5B9E\u4F8B\uFF0C\u7136\u540E\u518D\u542F\u52A8\u6865\u63A5\u670D\u52A1\u3002";
496
+ }
497
+ return null;
498
+ }
499
+ function describeBridgeActivationFailure(status, channels) {
500
+ const statusReason = status.lastExitReason?.trim();
501
+ if (statusReason) return statusReason;
502
+ const preflightFailure = describeBridgeStartupPreflightFailure(channels);
503
+ if (preflightFailure) return preflightFailure;
504
+ const enabled = (channels || []).filter((channel) => channel.enabled !== false);
505
+ if (enabled.length === 0) return null;
506
+ const labels = enabled.map((channel) => channel.alias?.trim() || channel.id).join("\u3001");
507
+ return `\u6CA1\u6709\u4EFB\u4F55\u901A\u9053\u9002\u914D\u5668\u542F\u52A8\u6210\u529F\u3002\u8BF7\u68C0\u67E5\u901A\u9053\u914D\u7F6E\u3001\u51ED\u636E\u548C\u65E5\u5FD7\u3002\u5F53\u524D\u5DF2\u542F\u7528\u901A\u9053\uFF1A${labels}`;
508
+ }
311
509
  async function waitForBridgeRunning(timeoutMs = 2e4) {
312
510
  const startedAt = Date.now();
313
511
  while (Date.now() - startedAt < timeoutMs) {
@@ -340,6 +538,11 @@ async function startBridge() {
340
538
  if (current.running && extraAlivePids.length > 0) {
341
539
  await stopBridge();
342
540
  }
541
+ const config = loadConfig();
542
+ const preflightFailure = describeBridgeStartupPreflightFailure(config.channels);
543
+ if (preflightFailure) {
544
+ throw new Error(preflightFailure);
545
+ }
343
546
  const daemonEntry = path2.join(packageRoot, "dist", "daemon.mjs");
344
547
  if (!fs2.existsSync(daemonEntry)) {
345
548
  throw new Error(`Daemon bundle not found at ${daemonEntry}. Run npm run build first.`);
@@ -356,7 +559,9 @@ async function startBridge() {
356
559
  child.unref();
357
560
  const status = await waitForBridgeRunning();
358
561
  if (!status.running) {
359
- throw new Error(status.lastExitReason || "Bridge failed to report running=true.");
562
+ throw new Error(
563
+ describeBridgeActivationFailure(status, config.channels) || "Bridge failed to report running=true."
564
+ );
360
565
  }
361
566
  return status;
362
567
  }
package/dist/daemon.mjs CHANGED
@@ -7818,11 +7818,8 @@ function readJson(filePath, fallback) {
7818
7818
  function getAccountRecency(account) {
7819
7819
  return account.lastLoginAt ?? account.updatedAt ?? account.createdAt;
7820
7820
  }
7821
- function normalizeAccounts(accounts) {
7822
- if (accounts.length <= 1) {
7823
- return { accounts, removedAccountIds: [] };
7824
- }
7825
- const sorted = [...accounts].sort((a, b) => {
7821
+ function sortAccountsByRecency(accounts) {
7822
+ return [...accounts].sort((a, b) => {
7826
7823
  const recencyDiff = getAccountRecency(b).localeCompare(getAccountRecency(a));
7827
7824
  if (recencyDiff !== 0) return recencyDiff;
7828
7825
  const updatedDiff = b.updatedAt.localeCompare(a.updatedAt);
@@ -7831,16 +7828,14 @@ function normalizeAccounts(accounts) {
7831
7828
  if (createdDiff !== 0) return createdDiff;
7832
7829
  return 0;
7833
7830
  });
7834
- const kept = sorted[0];
7835
- const removedAccountIds = [
7836
- ...new Set(
7837
- sorted.slice(1).map((account) => account.accountId).filter((accountId) => accountId !== kept.accountId)
7838
- )
7839
- ];
7840
- return {
7841
- accounts: [kept],
7842
- removedAccountIds
7843
- };
7831
+ }
7832
+ function normalizeAccounts(accounts) {
7833
+ const deduped = /* @__PURE__ */ new Map();
7834
+ for (const account of sortAccountsByRecency(accounts)) {
7835
+ if (!account?.accountId || deduped.has(account.accountId)) continue;
7836
+ deduped.set(account.accountId, account);
7837
+ }
7838
+ return Array.from(deduped.values());
7844
7839
  }
7845
7840
  function readStoredAccounts() {
7846
7841
  ensureDir(DATA_DIR);
@@ -7848,7 +7843,7 @@ function readStoredAccounts() {
7848
7843
  return Array.isArray(raw) ? raw : [];
7849
7844
  }
7850
7845
  function readAccounts() {
7851
- return normalizeAccounts(readStoredAccounts()).accounts;
7846
+ return normalizeAccounts(readStoredAccounts());
7852
7847
  }
7853
7848
  function readContextTokens() {
7854
7849
  ensureDir(DATA_DIR);
@@ -7862,7 +7857,7 @@ function contextKey(accountId, peerUserId) {
7862
7857
  return `${accountId}::${peerUserId}`;
7863
7858
  }
7864
7859
  function listWeixinAccounts() {
7865
- return readAccounts().sort((a, b) => b.createdAt.localeCompare(a.createdAt));
7860
+ return sortAccountsByRecency(readAccounts());
7866
7861
  }
7867
7862
  function getWeixinAccount(accountId) {
7868
7863
  return readAccounts().find((account) => account.accountId === accountId);
@@ -14234,6 +14229,22 @@ var WeixinAdapter = class extends BaseChannelAdapter {
14234
14229
  }
14235
14230
  }
14236
14231
  validateConfig() {
14232
+ const configuredAccountId = this.configuredAccountId;
14233
+ const accounts = listWeixinAccounts();
14234
+ const enabledAccounts = accounts.filter((account) => account.enabled && account.token);
14235
+ if (configuredAccountId) {
14236
+ const configured = getWeixinAccount(configuredAccountId);
14237
+ if (!configured) {
14238
+ return `Linked WeChat account ${configuredAccountId} not found`;
14239
+ }
14240
+ if (!configured.enabled || !configured.token) {
14241
+ return `Linked WeChat account ${configuredAccountId} is disabled or missing token`;
14242
+ }
14243
+ return null;
14244
+ }
14245
+ if (enabledAccounts.length > 1) {
14246
+ return "Multiple linked WeChat accounts detected. Please select a WeChat account for this channel.";
14247
+ }
14237
14248
  return null;
14238
14249
  }
14239
14250
  isAuthorized(_userId, _chatId) {
@@ -15370,7 +15381,7 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
15370
15381
  import fs5 from "node:fs";
15371
15382
  import path6 from "node:path";
15372
15383
  var DRAFT_TTL_MS = 24 * 60 * 60 * 1e3;
15373
- var MAX_HIDDEN_DRAFT_SESSIONS = 20;
15384
+ var MAX_HIDDEN_DRAFT_SESSIONS = 64;
15374
15385
  var INTERNAL_SESSION_ROOT = path6.join(CTI_HOME, "runtime", "internal-sessions");
15375
15386
  var DRAFT_SESSION_PREFIX = "Draft";
15376
15387
  var HISTORY_SESSION_PREFIX = "History Summary";
@@ -18164,6 +18175,29 @@ function buildMirrorTitle(threadTitle, markdown = false) {
18164
18175
  function buildMirrorSpeakerLabel(label, markdown = false) {
18165
18176
  return markdown ? `**${label}:**` : `${label}:`;
18166
18177
  }
18178
+ var MIRROR_USER_WRAPPER_LABELS = /* @__PURE__ */ new Map([
18179
+ ["# Review findings:", "Review findings"],
18180
+ ["# Context from my IDE setup:", "IDE setup"],
18181
+ ["# Files mentioned by the user:", "Files"]
18182
+ ]);
18183
+ var MIRROR_USER_REQUEST_MARKER = "## My request for Codex:";
18184
+ function formatMirrorUserText(text2) {
18185
+ const normalized = (text2 || "").replace(/\r\n?/g, "\n").trim();
18186
+ if (!normalized) return null;
18187
+ const lines = normalized.split("\n");
18188
+ const firstNonEmptyIndex = lines.findIndex((line) => line.trim().length > 0);
18189
+ if (firstNonEmptyIndex < 0) return null;
18190
+ const wrapperLabel = MIRROR_USER_WRAPPER_LABELS.get(lines[firstNonEmptyIndex].trim());
18191
+ if (!wrapperLabel) return normalized;
18192
+ const requestMarkerIndex = lines.findIndex(
18193
+ (line, index) => index > firstNonEmptyIndex && line.trim() === MIRROR_USER_REQUEST_MARKER
18194
+ );
18195
+ if (requestMarkerIndex < 0) return normalized;
18196
+ const requestBody = lines.slice(requestMarkerIndex + 1).join("\n").trim();
18197
+ if (!requestBody) return normalized;
18198
+ return `\uFF08\u57FA\u4E8E ${wrapperLabel}\uFF09
18199
+ ${requestBody}`;
18200
+ }
18167
18201
  function formatMirrorSpeakerBlock(label, text2, markdown = false, forceLabel = false) {
18168
18202
  const normalized = (text2 || "").trim();
18169
18203
  if (!normalized) {
@@ -18342,7 +18376,7 @@ function createMirrorTurnState(sessionId, timestamp, turnId) {
18342
18376
  };
18343
18377
  }
18344
18378
  function appendMirrorUserText(turnState, chunk) {
18345
- const normalized = chunk.trim();
18379
+ const normalized = formatMirrorUserText(chunk);
18346
18380
  if (!normalized) return;
18347
18381
  if (!turnState.userText) {
18348
18382
  turnState.userText = normalized;
@@ -18416,7 +18450,6 @@ function consumeMirrorRecords(subscription, records) {
18416
18450
  subscription.pendingTurn.lastActivityAt = record.timestamp;
18417
18451
  }
18418
18452
  }
18419
- startMirrorStreaming(subscription, subscription.pendingTurn);
18420
18453
  continue;
18421
18454
  }
18422
18455
  if (record.type === "task_complete") {