codex-to-im 1.0.28 → 1.0.30
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 +261 -21
- package/dist/daemon.mjs +247 -57
- package/dist/ui-server.mjs +763 -105
- package/package.json +1 -1
- package/references/setup-guides.md +1 -1
- package/scripts/doctor.sh +9 -9
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));
|
|
@@ -86,6 +263,30 @@ function isProcessAlive(pid) {
|
|
|
86
263
|
return false;
|
|
87
264
|
}
|
|
88
265
|
}
|
|
266
|
+
function collectTrackedBridgePids(bridgePid, statusPid) {
|
|
267
|
+
const unique = /* @__PURE__ */ new Set();
|
|
268
|
+
for (const pid of [bridgePid, statusPid]) {
|
|
269
|
+
if (Number.isFinite(pid) && pid > 0) {
|
|
270
|
+
unique.add(pid);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return [...unique];
|
|
274
|
+
}
|
|
275
|
+
function resolveTrackedBridgePid(bridgePid, statusPid, isAlive = isProcessAlive) {
|
|
276
|
+
if (isAlive(bridgePid)) return bridgePid;
|
|
277
|
+
if (isAlive(statusPid)) return statusPid;
|
|
278
|
+
return bridgePid ?? statusPid;
|
|
279
|
+
}
|
|
280
|
+
function getTrackedBridgePids(status) {
|
|
281
|
+
const resolvedStatus = status ?? readJsonFile(bridgeStatusFile, { running: false });
|
|
282
|
+
return collectTrackedBridgePids(readPid(bridgePidFile), resolvedStatus.pid);
|
|
283
|
+
}
|
|
284
|
+
function clearBridgePidFile() {
|
|
285
|
+
try {
|
|
286
|
+
fs2.unlinkSync(bridgePidFile);
|
|
287
|
+
} catch {
|
|
288
|
+
}
|
|
289
|
+
}
|
|
89
290
|
function sleep(ms) {
|
|
90
291
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
91
292
|
}
|
|
@@ -246,7 +447,7 @@ function getCurrentUiServerUrl() {
|
|
|
246
447
|
}
|
|
247
448
|
function getBridgeStatus() {
|
|
248
449
|
const status = readJsonFile(bridgeStatusFile, { running: false });
|
|
249
|
-
const pid = readPid(bridgePidFile)
|
|
450
|
+
const pid = resolveTrackedBridgePid(readPid(bridgePidFile), status.pid);
|
|
250
451
|
if (!isProcessAlive(pid)) {
|
|
251
452
|
return {
|
|
252
453
|
...status,
|
|
@@ -284,6 +485,27 @@ function buildDaemonEnv() {
|
|
|
284
485
|
delete env.CLAUDECODE;
|
|
285
486
|
return env;
|
|
286
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
|
+
}
|
|
287
509
|
async function waitForBridgeRunning(timeoutMs = 2e4) {
|
|
288
510
|
const startedAt = Date.now();
|
|
289
511
|
while (Date.now() - startedAt < timeoutMs) {
|
|
@@ -311,7 +533,16 @@ async function waitForUiServer(timeoutMs = 15e3) {
|
|
|
311
533
|
async function startBridge() {
|
|
312
534
|
ensureDirs();
|
|
313
535
|
const current = getBridgeStatus();
|
|
314
|
-
|
|
536
|
+
const extraAlivePids = getTrackedBridgePids(current).filter((pid) => pid !== current.pid && isProcessAlive(pid));
|
|
537
|
+
if (current.running && extraAlivePids.length === 0) return current;
|
|
538
|
+
if (current.running && extraAlivePids.length > 0) {
|
|
539
|
+
await stopBridge();
|
|
540
|
+
}
|
|
541
|
+
const config = loadConfig();
|
|
542
|
+
const preflightFailure = describeBridgeStartupPreflightFailure(config.channels);
|
|
543
|
+
if (preflightFailure) {
|
|
544
|
+
throw new Error(preflightFailure);
|
|
545
|
+
}
|
|
315
546
|
const daemonEntry = path2.join(packageRoot, "dist", "daemon.mjs");
|
|
316
547
|
if (!fs2.existsSync(daemonEntry)) {
|
|
317
548
|
throw new Error(`Daemon bundle not found at ${daemonEntry}. Run npm run build first.`);
|
|
@@ -328,36 +559,45 @@ async function startBridge() {
|
|
|
328
559
|
child.unref();
|
|
329
560
|
const status = await waitForBridgeRunning();
|
|
330
561
|
if (!status.running) {
|
|
331
|
-
throw new Error(
|
|
562
|
+
throw new Error(
|
|
563
|
+
describeBridgeActivationFailure(status, config.channels) || "Bridge failed to report running=true."
|
|
564
|
+
);
|
|
332
565
|
}
|
|
333
566
|
return status;
|
|
334
567
|
}
|
|
335
568
|
async function stopBridge() {
|
|
336
|
-
const status =
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
569
|
+
const status = readJsonFile(bridgeStatusFile, { running: false });
|
|
570
|
+
const pids = getTrackedBridgePids(status).filter((pid) => isProcessAlive(pid));
|
|
571
|
+
if (pids.length === 0) {
|
|
572
|
+
clearBridgePidFile();
|
|
573
|
+
return { ...getBridgeStatus(), running: false };
|
|
574
|
+
}
|
|
575
|
+
for (const pid of pids) {
|
|
576
|
+
if (process.platform === "win32") {
|
|
577
|
+
await new Promise((resolve) => {
|
|
578
|
+
const killer = spawn("cmd", ["/c", "taskkill", "/PID", String(pid), "/T", "/F"], {
|
|
579
|
+
stdio: "ignore",
|
|
580
|
+
...WINDOWS_HIDE
|
|
581
|
+
});
|
|
582
|
+
killer.on("exit", () => resolve());
|
|
583
|
+
killer.on("error", () => resolve());
|
|
345
584
|
});
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
process.kill(status.pid, "SIGTERM");
|
|
352
|
-
} catch {
|
|
585
|
+
} else {
|
|
586
|
+
try {
|
|
587
|
+
process.kill(pid, "SIGTERM");
|
|
588
|
+
} catch {
|
|
589
|
+
}
|
|
353
590
|
}
|
|
354
591
|
}
|
|
355
592
|
const startedAt = Date.now();
|
|
356
593
|
while (Date.now() - startedAt < 1e4) {
|
|
357
|
-
|
|
358
|
-
|
|
594
|
+
if (pids.every((pid) => !isProcessAlive(pid))) {
|
|
595
|
+
clearBridgePidFile();
|
|
596
|
+
return getBridgeStatus();
|
|
597
|
+
}
|
|
359
598
|
await sleep(300);
|
|
360
599
|
}
|
|
600
|
+
clearBridgePidFile();
|
|
361
601
|
return getBridgeStatus();
|
|
362
602
|
}
|
|
363
603
|
async function getBridgeAutostartStatus() {
|