codex-to-im 0.1.2 → 0.1.5
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/README.md +11 -0
- package/README_CN.md +11 -0
- package/config.env.example +9 -3
- package/dist/daemon.mjs +171 -39
- package/dist/ui-server.mjs +533 -19
- package/docs/install-windows.md +16 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,15 @@ http://127.0.0.1:4781
|
|
|
86
86
|
|
|
87
87
|
If that port is already occupied, the app automatically finds an available local port and prints the actual address to the terminal when starting.
|
|
88
88
|
|
|
89
|
+
By default, the web workbench only accepts local access.
|
|
90
|
+
|
|
91
|
+
If you want to open it from your phone or another device on the same LAN, enable `允许局域网访问 Web 控制台` in the `配置` page. When enabled:
|
|
92
|
+
|
|
93
|
+
- the workbench shows detected LAN URLs
|
|
94
|
+
- the workbench displays an access token
|
|
95
|
+
- LAN devices see a login page before they can view or modify settings
|
|
96
|
+
- you can also copy a ready-to-use login link that includes `?token=...`
|
|
97
|
+
|
|
89
98
|
If you forget the current address, run:
|
|
90
99
|
|
|
91
100
|
```bash
|
|
@@ -114,6 +123,8 @@ codex-to-im stop
|
|
|
114
123
|
6. Bind a Feishu or Weixin chat to the target thread
|
|
115
124
|
7. Continue the same Codex thread from IM
|
|
116
125
|
|
|
126
|
+
If LAN access is enabled, the easiest path is to copy the LAN login link from the local workbench and open it on your phone or another device on the same network.
|
|
127
|
+
|
|
117
128
|
Useful command:
|
|
118
129
|
|
|
119
130
|
- `/history` shows the latest N messages of the current session
|
package/README_CN.md
CHANGED
|
@@ -86,6 +86,15 @@ http://127.0.0.1:4781
|
|
|
86
86
|
|
|
87
87
|
如果默认端口已被占用,应用会自动选择一个可用端口,并在启动时把实际地址打印到命令行。
|
|
88
88
|
|
|
89
|
+
默认情况下,Web 工作台只允许本机访问。
|
|
90
|
+
|
|
91
|
+
如果你需要在手机或同一局域网里的其他设备上打开配置页,可以在“配置”页里勾选“允许局域网访问 Web 控制台”。开启后:
|
|
92
|
+
|
|
93
|
+
- 工作台会显示当前可用的局域网地址
|
|
94
|
+
- 工作台会生成并展示一个访问 token
|
|
95
|
+
- 局域网设备访问时会先进入登录页,输入 token 后才能查看和修改配置
|
|
96
|
+
- 也可以直接复制页面里的局域网登录链接,链接里会附带 `?token=...`
|
|
97
|
+
|
|
89
98
|
如果你忘了当前地址,可以执行:
|
|
90
99
|
|
|
91
100
|
```bash
|
|
@@ -114,6 +123,8 @@ codex-to-im stop
|
|
|
114
123
|
6. 把飞书或微信聊天绑定到目标 thread
|
|
115
124
|
7. 在 IM 中继续同一条 Codex 会话
|
|
116
125
|
|
|
126
|
+
如果开启了局域网访问,推荐在本机工作台里复制局域网登录链接,再发给你的手机或局域网里的其他设备。
|
|
127
|
+
|
|
117
128
|
常用命令补充:
|
|
118
129
|
|
|
119
130
|
- `/history` 查看当前会话最近 N 条消息
|
package/config.env.example
CHANGED
|
@@ -43,9 +43,15 @@ CTI_DEFAULT_MODE=code
|
|
|
43
43
|
# Allow Codex to run when CTI_DEFAULT_WORKDIR is not inside a trusted Git repo.
|
|
44
44
|
# This project defaults it to true so first-time setup works more smoothly.
|
|
45
45
|
CTI_CODEX_SKIP_GIT_REPO_CHECK=true
|
|
46
|
-
|
|
47
|
-
# ──
|
|
48
|
-
|
|
46
|
+
|
|
47
|
+
# ── Web 控制台访问 ──
|
|
48
|
+
# 默认仅允许本机访问本地工作台。开启后,局域网设备访问时需要先输入 token。
|
|
49
|
+
# 可以在 Web 工作台里直接勾选并自动生成 token。
|
|
50
|
+
# CTI_UI_ALLOW_LAN=false
|
|
51
|
+
# CTI_UI_ACCESS_TOKEN=your-random-access-token
|
|
52
|
+
|
|
53
|
+
# ── Telegram ──
|
|
54
|
+
CTI_TG_BOT_TOKEN=your-telegram-bot-token
|
|
49
55
|
# Chat ID for authorization (at least one of CHAT_ID or ALLOWED_USERS is required)
|
|
50
56
|
# Get it: send a message to the bot, then visit https://api.telegram.org/botYOUR_TOKEN/getUpdates
|
|
51
57
|
CTI_TG_CHAT_ID=your-chat-id
|
package/dist/daemon.mjs
CHANGED
|
@@ -195849,6 +195849,8 @@ function loadConfig() {
|
|
|
195849
195849
|
defaultMode: env.get("CTI_DEFAULT_MODE") || "code",
|
|
195850
195850
|
historyMessageLimit: parsePositiveInt(env.get("CTI_HISTORY_MESSAGE_LIMIT")) ?? 8,
|
|
195851
195851
|
codexSkipGitRepoCheck: env.has("CTI_CODEX_SKIP_GIT_REPO_CHECK") ? env.get("CTI_CODEX_SKIP_GIT_REPO_CHECK") === "true" : true,
|
|
195852
|
+
uiAllowLan: env.get("CTI_UI_ALLOW_LAN") === "true",
|
|
195853
|
+
uiAccessToken: env.get("CTI_UI_ACCESS_TOKEN") || void 0,
|
|
195852
195854
|
tgBotToken: env.get("CTI_TG_BOT_TOKEN") || void 0,
|
|
195853
195855
|
tgChatId: env.get("CTI_TG_CHAT_ID") || void 0,
|
|
195854
195856
|
tgAllowedUsers: splitCsv(env.get("CTI_TG_ALLOWED_USERS")),
|
|
@@ -196775,6 +196777,7 @@ import os2 from "node:os";
|
|
|
196775
196777
|
import path3 from "node:path";
|
|
196776
196778
|
var ACTIVE_WINDOW_MS = 15 * 60 * 1e3;
|
|
196777
196779
|
var MAX_SESSION_META_BYTES = 4 * 1024 * 1024;
|
|
196780
|
+
var MAX_SESSION_TITLE_SCAN_BYTES = 512 * 1024;
|
|
196778
196781
|
var TITLE_MAX_CHARS = 72;
|
|
196779
196782
|
function getCodexHome() {
|
|
196780
196783
|
return process.env.CODEX_HOME || path3.join(os2.homedir(), ".codex");
|
|
@@ -196832,6 +196835,24 @@ function readFirstLine(filePath, maxBytes = MAX_SESSION_META_BYTES) {
|
|
|
196832
196835
|
fs3.closeSync(fd);
|
|
196833
196836
|
}
|
|
196834
196837
|
}
|
|
196838
|
+
function readFilePrefix(filePath, maxBytes = MAX_SESSION_TITLE_SCAN_BYTES) {
|
|
196839
|
+
const fd = fs3.openSync(filePath, "r");
|
|
196840
|
+
try {
|
|
196841
|
+
const buffer = Buffer.alloc(Math.min(maxBytes, 64 * 1024));
|
|
196842
|
+
const chunks = [];
|
|
196843
|
+
let offset = 0;
|
|
196844
|
+
while (offset < maxBytes) {
|
|
196845
|
+
const bytesToRead = Math.min(buffer.length, maxBytes - offset);
|
|
196846
|
+
const bytesRead = fs3.readSync(fd, buffer, 0, bytesToRead, offset);
|
|
196847
|
+
if (bytesRead <= 0) break;
|
|
196848
|
+
chunks.push(Buffer.from(buffer.subarray(0, bytesRead)));
|
|
196849
|
+
offset += bytesRead;
|
|
196850
|
+
}
|
|
196851
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
196852
|
+
} finally {
|
|
196853
|
+
fs3.closeSync(fd);
|
|
196854
|
+
}
|
|
196855
|
+
}
|
|
196835
196856
|
function walkSessionFiles(dirPath, target) {
|
|
196836
196857
|
let entries;
|
|
196837
196858
|
try {
|
|
@@ -196853,6 +196874,7 @@ function walkSessionFiles(dirPath, target) {
|
|
|
196853
196874
|
function isDesktopLike(meta) {
|
|
196854
196875
|
const originator = meta?.originator?.toLowerCase() || "";
|
|
196855
196876
|
const source = meta?.source?.toLowerCase() || "";
|
|
196877
|
+
if (source === "exec") return false;
|
|
196856
196878
|
return originator.includes("desktop") || source === "vscode" || source === "desktop";
|
|
196857
196879
|
}
|
|
196858
196880
|
function loadThreadNameIndex(archivedThreadIds) {
|
|
@@ -196884,6 +196906,27 @@ function loadThreadNameIndex(archivedThreadIds) {
|
|
|
196884
196906
|
}
|
|
196885
196907
|
return new Map(Array.from(titles.entries()).map(([threadId, entry]) => [threadId, entry.title]));
|
|
196886
196908
|
}
|
|
196909
|
+
function buildFallbackTitle(threadId, filePath, cwd) {
|
|
196910
|
+
try {
|
|
196911
|
+
const content = readFilePrefix(filePath);
|
|
196912
|
+
for (const line of content.split(/\r?\n/)) {
|
|
196913
|
+
if (!line.trim()) continue;
|
|
196914
|
+
let parsed;
|
|
196915
|
+
try {
|
|
196916
|
+
parsed = JSON.parse(line);
|
|
196917
|
+
} catch {
|
|
196918
|
+
continue;
|
|
196919
|
+
}
|
|
196920
|
+
if (!isSessionEventLine(parsed) || parsed.payload?.type !== "user_message") continue;
|
|
196921
|
+
const firstUserMessage = trimTitle(normalizeFreeText(parsed.payload.message || ""));
|
|
196922
|
+
if (firstUserMessage) return firstUserMessage;
|
|
196923
|
+
}
|
|
196924
|
+
} catch {
|
|
196925
|
+
}
|
|
196926
|
+
const dirName = trimTitle(path3.basename(cwd || ""));
|
|
196927
|
+
if (dirName) return dirName;
|
|
196928
|
+
return `Session ${threadId.slice(0, 8)}`;
|
|
196929
|
+
}
|
|
196887
196930
|
function parseDesktopSession(filePath, threadNames, archivedThreadIds) {
|
|
196888
196931
|
const firstLine = readFirstLine(filePath);
|
|
196889
196932
|
if (!firstLine) return null;
|
|
@@ -196909,8 +196952,7 @@ function parseDesktopSession(filePath, threadNames, archivedThreadIds) {
|
|
|
196909
196952
|
const lastEventAt = stat.mtime.toISOString();
|
|
196910
196953
|
const firstSeenAt = parsed.payload.timestamp || parsed.timestamp || stat.birthtime.toISOString();
|
|
196911
196954
|
const threadId = parsed.payload.id;
|
|
196912
|
-
const title = threadNames.get(threadId);
|
|
196913
|
-
if (!title) return null;
|
|
196955
|
+
const title = threadNames.get(threadId) || buildFallbackTitle(threadId, filePath, cwd);
|
|
196914
196956
|
return {
|
|
196915
196957
|
threadId,
|
|
196916
196958
|
filePath,
|
|
@@ -196944,7 +196986,6 @@ function listDesktopSessions(limit = 12) {
|
|
|
196944
196986
|
if (!fs3.existsSync(root)) return [];
|
|
196945
196987
|
const archivedThreadIds = loadArchivedThreadIds();
|
|
196946
196988
|
const threadNames = loadThreadNameIndex(archivedThreadIds);
|
|
196947
|
-
if (threadNames.size === 0) return [];
|
|
196948
196989
|
const files = [];
|
|
196949
196990
|
walkSessionFiles(root, files);
|
|
196950
196991
|
const sessions = [];
|
|
@@ -204421,6 +204462,7 @@ function getState() {
|
|
|
204421
204462
|
running: false,
|
|
204422
204463
|
startedAt: null,
|
|
204423
204464
|
loopAborts: /* @__PURE__ */ new Map(),
|
|
204465
|
+
reconcileTimer: null,
|
|
204424
204466
|
activeTasks: /* @__PURE__ */ new Map(),
|
|
204425
204467
|
sessionLocks: /* @__PURE__ */ new Map(),
|
|
204426
204468
|
autoStartChecked: false
|
|
@@ -204444,37 +204486,85 @@ function processWithSessionLock(sessionId, fn) {
|
|
|
204444
204486
|
});
|
|
204445
204487
|
return current;
|
|
204446
204488
|
}
|
|
204447
|
-
|
|
204489
|
+
function getActiveChannelTypes(state = getState()) {
|
|
204490
|
+
return Array.from(state.adapters.keys()).sort();
|
|
204491
|
+
}
|
|
204492
|
+
function notifyAdapterSetChanged() {
|
|
204493
|
+
const { lifecycle } = getBridgeContext();
|
|
204494
|
+
lifecycle.onBridgeAdaptersChanged?.(getActiveChannelTypes());
|
|
204495
|
+
}
|
|
204496
|
+
async function stopAdapterInstance(channelType) {
|
|
204448
204497
|
const state = getState();
|
|
204449
|
-
|
|
204450
|
-
|
|
204451
|
-
|
|
204452
|
-
|
|
204453
|
-
|
|
204454
|
-
|
|
204498
|
+
const adapter = state.adapters.get(channelType);
|
|
204499
|
+
if (!adapter) return;
|
|
204500
|
+
state.loopAborts.get(channelType)?.abort();
|
|
204501
|
+
state.loopAborts.delete(channelType);
|
|
204502
|
+
try {
|
|
204503
|
+
await adapter.stop();
|
|
204504
|
+
console.log(`[bridge-manager] Stopped adapter: ${channelType}`);
|
|
204505
|
+
} catch (err) {
|
|
204506
|
+
console.error(`[bridge-manager] Error stopping adapter ${channelType}:`, err);
|
|
204455
204507
|
}
|
|
204508
|
+
state.adapters.delete(channelType);
|
|
204509
|
+
state.adapterMeta.delete(channelType);
|
|
204510
|
+
}
|
|
204511
|
+
async function syncConfiguredAdapters(options) {
|
|
204512
|
+
const state = getState();
|
|
204513
|
+
const { store } = getBridgeContext();
|
|
204514
|
+
let changed = false;
|
|
204456
204515
|
for (const channelType of getRegisteredTypes()) {
|
|
204457
|
-
const
|
|
204458
|
-
|
|
204516
|
+
const enabled = store.getSetting(`bridge_${channelType}_enabled`) === "true";
|
|
204517
|
+
const existing = state.adapters.get(channelType);
|
|
204518
|
+
if (!enabled) {
|
|
204519
|
+
if (existing) {
|
|
204520
|
+
await stopAdapterInstance(channelType);
|
|
204521
|
+
changed = true;
|
|
204522
|
+
}
|
|
204523
|
+
continue;
|
|
204524
|
+
}
|
|
204525
|
+
if (existing) {
|
|
204526
|
+
continue;
|
|
204527
|
+
}
|
|
204459
204528
|
const adapter = createAdapter(channelType);
|
|
204460
204529
|
if (!adapter) continue;
|
|
204461
204530
|
const configError = adapter.validateConfig();
|
|
204462
|
-
if (
|
|
204463
|
-
registerAdapter(adapter);
|
|
204464
|
-
} else {
|
|
204531
|
+
if (configError) {
|
|
204465
204532
|
console.warn(`[bridge-manager] ${channelType} adapter not valid:`, configError);
|
|
204533
|
+
continue;
|
|
204466
204534
|
}
|
|
204467
|
-
}
|
|
204468
|
-
let startedCount = 0;
|
|
204469
|
-
for (const [type, adapter] of state.adapters) {
|
|
204470
204535
|
try {
|
|
204536
|
+
state.adapters.set(channelType, adapter);
|
|
204537
|
+
state.adapterMeta.set(channelType, {
|
|
204538
|
+
lastMessageAt: null,
|
|
204539
|
+
lastError: null
|
|
204540
|
+
});
|
|
204471
204541
|
await adapter.start();
|
|
204472
|
-
console.log(`[bridge-manager] Started adapter: ${
|
|
204473
|
-
|
|
204542
|
+
console.log(`[bridge-manager] Started adapter: ${channelType}`);
|
|
204543
|
+
if (options.startLoops && state.running && adapter.isRunning()) {
|
|
204544
|
+
runAdapterLoop(adapter);
|
|
204545
|
+
}
|
|
204546
|
+
changed = true;
|
|
204474
204547
|
} catch (err) {
|
|
204475
|
-
|
|
204548
|
+
state.adapters.delete(channelType);
|
|
204549
|
+
state.adapterMeta.delete(channelType);
|
|
204550
|
+
console.error(`[bridge-manager] Failed to start adapter ${channelType}:`, err);
|
|
204476
204551
|
}
|
|
204477
204552
|
}
|
|
204553
|
+
if (changed) {
|
|
204554
|
+
notifyAdapterSetChanged();
|
|
204555
|
+
}
|
|
204556
|
+
}
|
|
204557
|
+
async function start() {
|
|
204558
|
+
const state = getState();
|
|
204559
|
+
if (state.running) return;
|
|
204560
|
+
const { store, lifecycle } = getBridgeContext();
|
|
204561
|
+
const bridgeEnabled = store.getSetting("remote_bridge_enabled") === "true";
|
|
204562
|
+
if (!bridgeEnabled) {
|
|
204563
|
+
console.log("[bridge-manager] Bridge not enabled (remote_bridge_enabled != true)");
|
|
204564
|
+
return;
|
|
204565
|
+
}
|
|
204566
|
+
await syncConfiguredAdapters({ startLoops: false });
|
|
204567
|
+
const startedCount = state.adapters.size;
|
|
204478
204568
|
if (startedCount === 0) {
|
|
204479
204569
|
console.warn("[bridge-manager] No adapters started successfully, bridge not activated");
|
|
204480
204570
|
state.adapters.clear();
|
|
@@ -204489,6 +204579,11 @@ async function start() {
|
|
|
204489
204579
|
runAdapterLoop(adapter);
|
|
204490
204580
|
}
|
|
204491
204581
|
}
|
|
204582
|
+
state.reconcileTimer = setInterval(() => {
|
|
204583
|
+
void syncConfiguredAdapters({ startLoops: true }).catch((err) => {
|
|
204584
|
+
console.error("[bridge-manager] Adapter reconcile failed:", err);
|
|
204585
|
+
});
|
|
204586
|
+
}, 5e3);
|
|
204492
204587
|
console.log(`[bridge-manager] Bridge started with ${startedCount} adapter(s)`);
|
|
204493
204588
|
}
|
|
204494
204589
|
async function stop() {
|
|
@@ -204496,27 +204591,37 @@ async function stop() {
|
|
|
204496
204591
|
if (!state.running) return;
|
|
204497
204592
|
const { lifecycle } = getBridgeContext();
|
|
204498
204593
|
state.running = false;
|
|
204594
|
+
if (state.reconcileTimer) {
|
|
204595
|
+
clearInterval(state.reconcileTimer);
|
|
204596
|
+
state.reconcileTimer = null;
|
|
204597
|
+
}
|
|
204499
204598
|
for (const [, abort] of state.loopAborts) {
|
|
204500
204599
|
abort.abort();
|
|
204501
204600
|
}
|
|
204502
204601
|
state.loopAborts.clear();
|
|
204503
|
-
for (const
|
|
204504
|
-
|
|
204505
|
-
await adapter.stop();
|
|
204506
|
-
console.log(`[bridge-manager] Stopped adapter: ${type}`);
|
|
204507
|
-
} catch (err) {
|
|
204508
|
-
console.error(`[bridge-manager] Error stopping adapter ${type}:`, err);
|
|
204509
|
-
}
|
|
204602
|
+
for (const type of Array.from(state.adapters.keys())) {
|
|
204603
|
+
await stopAdapterInstance(type);
|
|
204510
204604
|
}
|
|
204511
|
-
state.adapters.clear();
|
|
204512
|
-
state.adapterMeta.clear();
|
|
204513
204605
|
state.startedAt = null;
|
|
204514
204606
|
lifecycle.onBridgeStop?.();
|
|
204515
204607
|
console.log("[bridge-manager] Bridge stopped");
|
|
204516
204608
|
}
|
|
204517
|
-
function
|
|
204609
|
+
function getStatus() {
|
|
204518
204610
|
const state = getState();
|
|
204519
|
-
|
|
204611
|
+
return {
|
|
204612
|
+
running: state.running,
|
|
204613
|
+
startedAt: state.startedAt,
|
|
204614
|
+
adapters: Array.from(state.adapters.entries()).map(([type, adapter]) => {
|
|
204615
|
+
const meta = state.adapterMeta.get(type);
|
|
204616
|
+
return {
|
|
204617
|
+
channelType: adapter.channelType,
|
|
204618
|
+
running: adapter.isRunning(),
|
|
204619
|
+
connectedAt: state.startedAt,
|
|
204620
|
+
lastMessageAt: meta?.lastMessageAt ?? null,
|
|
204621
|
+
error: meta?.lastError ?? null
|
|
204622
|
+
};
|
|
204623
|
+
})
|
|
204624
|
+
};
|
|
204520
204625
|
}
|
|
204521
204626
|
function runAdapterLoop(adapter) {
|
|
204522
204627
|
const state = getState();
|
|
@@ -205256,6 +205361,7 @@ function now() {
|
|
|
205256
205361
|
}
|
|
205257
205362
|
var JsonFileStore = class {
|
|
205258
205363
|
settings;
|
|
205364
|
+
dynamicSettings;
|
|
205259
205365
|
sessions = /* @__PURE__ */ new Map();
|
|
205260
205366
|
bindings = /* @__PURE__ */ new Map();
|
|
205261
205367
|
messages = /* @__PURE__ */ new Map();
|
|
@@ -205264,8 +205370,9 @@ var JsonFileStore = class {
|
|
|
205264
205370
|
dedupKeys = /* @__PURE__ */ new Map();
|
|
205265
205371
|
locks = /* @__PURE__ */ new Map();
|
|
205266
205372
|
auditLog = [];
|
|
205267
|
-
constructor(settingsMap) {
|
|
205373
|
+
constructor(settingsMap, options) {
|
|
205268
205374
|
this.settings = settingsMap;
|
|
205375
|
+
this.dynamicSettings = options?.dynamicSettings === true;
|
|
205269
205376
|
ensureDir2(DATA_DIR2);
|
|
205270
205377
|
ensureDir2(MESSAGES_DIR);
|
|
205271
205378
|
this.loadAll();
|
|
@@ -205360,7 +205467,19 @@ var JsonFileStore = class {
|
|
|
205360
205467
|
return msgs;
|
|
205361
205468
|
}
|
|
205362
205469
|
// ── Settings ──
|
|
205470
|
+
refreshSettings() {
|
|
205471
|
+
if (!this.dynamicSettings) return;
|
|
205472
|
+
try {
|
|
205473
|
+
const next = configToSettings(loadConfig());
|
|
205474
|
+
this.settings = new Map([
|
|
205475
|
+
...this.settings,
|
|
205476
|
+
...next
|
|
205477
|
+
]);
|
|
205478
|
+
} catch {
|
|
205479
|
+
}
|
|
205480
|
+
}
|
|
205363
205481
|
getSetting(key) {
|
|
205482
|
+
this.refreshSettings();
|
|
205364
205483
|
return this.settings.get(key) ?? null;
|
|
205365
205484
|
}
|
|
205366
205485
|
// ── Channel Bindings ──
|
|
@@ -205393,7 +205512,7 @@ var JsonFileStore = class {
|
|
|
205393
205512
|
sdkSessionId: data.sdkSessionId ?? "",
|
|
205394
205513
|
workingDirectory: data.workingDirectory,
|
|
205395
205514
|
model: data.model,
|
|
205396
|
-
mode: this.
|
|
205515
|
+
mode: this.getSetting("bridge_default_mode") || "code",
|
|
205397
205516
|
active: true,
|
|
205398
205517
|
createdAt: now(),
|
|
205399
205518
|
updatedAt: now()
|
|
@@ -205441,7 +205560,7 @@ var JsonFileStore = class {
|
|
|
205441
205560
|
const session = {
|
|
205442
205561
|
id: uuid(),
|
|
205443
205562
|
name,
|
|
205444
|
-
working_directory: cwd || this.
|
|
205563
|
+
working_directory: cwd || this.getSetting("bridge_default_work_dir") || process.cwd(),
|
|
205445
205564
|
model,
|
|
205446
205565
|
system_prompt: systemPrompt
|
|
205447
205566
|
};
|
|
@@ -206248,13 +206367,16 @@ function writeStatus(info) {
|
|
|
206248
206367
|
fs9.writeFileSync(tmp, JSON.stringify(merged, null, 2), "utf-8");
|
|
206249
206368
|
fs9.renameSync(tmp, STATUS_FILE);
|
|
206250
206369
|
}
|
|
206370
|
+
function getRunningChannels() {
|
|
206371
|
+
return getStatus().adapters.map((adapter) => adapter.channelType).sort();
|
|
206372
|
+
}
|
|
206251
206373
|
async function main() {
|
|
206252
206374
|
const config2 = loadConfig();
|
|
206253
206375
|
setupLogger();
|
|
206254
206376
|
const runId = crypto9.randomUUID();
|
|
206255
206377
|
console.log(`[codex-to-im] Starting bridge (run_id: ${runId})`);
|
|
206256
206378
|
const settings = configToSettings(config2);
|
|
206257
|
-
const store = new JsonFileStore(settings);
|
|
206379
|
+
const store = new JsonFileStore(settings, { dynamicSettings: true });
|
|
206258
206380
|
const pendingPerms = new PendingPermissions();
|
|
206259
206381
|
const llm = await resolveProvider(config2, pendingPerms);
|
|
206260
206382
|
console.log(`[codex-to-im] Runtime: ${config2.runtime}`);
|
|
@@ -206269,17 +206391,27 @@ async function main() {
|
|
|
206269
206391
|
onBridgeStart: () => {
|
|
206270
206392
|
fs9.mkdirSync(RUNTIME_DIR, { recursive: true });
|
|
206271
206393
|
fs9.writeFileSync(PID_FILE, String(process.pid), "utf-8");
|
|
206394
|
+
const channels = getRunningChannels();
|
|
206272
206395
|
writeStatus({
|
|
206273
206396
|
running: true,
|
|
206274
206397
|
pid: process.pid,
|
|
206275
206398
|
runId,
|
|
206276
206399
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
206277
|
-
channels
|
|
206400
|
+
channels
|
|
206401
|
+
});
|
|
206402
|
+
console.log(`[codex-to-im] Bridge started (PID: ${process.pid}, channels: ${channels.join(", ")})`);
|
|
206403
|
+
},
|
|
206404
|
+
onBridgeAdaptersChanged: (channels) => {
|
|
206405
|
+
writeStatus({
|
|
206406
|
+
running: true,
|
|
206407
|
+
pid: process.pid,
|
|
206408
|
+
runId,
|
|
206409
|
+
channels
|
|
206278
206410
|
});
|
|
206279
|
-
console.log(`[codex-to-im]
|
|
206411
|
+
console.log(`[codex-to-im] Active channels updated: ${channels.join(", ") || "none"}`);
|
|
206280
206412
|
},
|
|
206281
206413
|
onBridgeStop: () => {
|
|
206282
|
-
writeStatus({ running: false });
|
|
206414
|
+
writeStatus({ running: false, channels: [] });
|
|
206283
206415
|
console.log("[codex-to-im] Bridge stopped");
|
|
206284
206416
|
}
|
|
206285
206417
|
}
|