codex-to-im 1.0.62 → 1.0.64
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 +12 -0
- package/README_EN.md +6 -0
- package/dist/cli.mjs +1 -1
- package/dist/daemon.mjs +1512 -72
- package/dist/ui-server.mjs +98 -17
- package/docs/install-windows.md +2 -0
- package/package.json +1 -1
- package/scripts/build.js +13 -1
package/dist/ui-server.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createRequire } from 'module'; const require =
|
|
1
|
+
import { createRequire as __ctiCreateRequire } from 'module'; const require = __ctiCreateRequire(import.meta.url);
|
|
2
2
|
var __create = Object.create;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -5210,11 +5210,25 @@ import fs3 from "node:fs";
|
|
|
5210
5210
|
import os2 from "node:os";
|
|
5211
5211
|
import path2 from "node:path";
|
|
5212
5212
|
import crypto3 from "node:crypto";
|
|
5213
|
-
import {
|
|
5213
|
+
import { createRequire } from "node:module";
|
|
5214
5214
|
var ACTIVE_WINDOW_MS = 15 * 60 * 1e3;
|
|
5215
5215
|
var MAX_SESSION_META_BYTES = 4 * 1024 * 1024;
|
|
5216
5216
|
var MAX_SESSION_TITLE_SCAN_BYTES = 512 * 1024;
|
|
5217
|
+
var INITIAL_HISTORY_TAIL_BYTES = 256 * 1024;
|
|
5218
|
+
var MAX_HISTORY_TAIL_BYTES = 64 * 1024 * 1024;
|
|
5217
5219
|
var TITLE_MAX_CHARS = 72;
|
|
5220
|
+
var localRequire = createRequire(import.meta.url);
|
|
5221
|
+
var databaseSyncConstructor;
|
|
5222
|
+
function getDatabaseSyncConstructor() {
|
|
5223
|
+
if (databaseSyncConstructor !== void 0) return databaseSyncConstructor;
|
|
5224
|
+
try {
|
|
5225
|
+
const sqlite = localRequire("node:sqlite");
|
|
5226
|
+
databaseSyncConstructor = typeof sqlite.DatabaseSync === "function" ? sqlite.DatabaseSync : null;
|
|
5227
|
+
} catch {
|
|
5228
|
+
databaseSyncConstructor = null;
|
|
5229
|
+
}
|
|
5230
|
+
return databaseSyncConstructor;
|
|
5231
|
+
}
|
|
5218
5232
|
function getCodexHome() {
|
|
5219
5233
|
return process.env.CODEX_HOME || path2.join(os2.homedir(), ".codex");
|
|
5220
5234
|
}
|
|
@@ -5357,10 +5371,37 @@ function walkSessionFiles(dirPath, target) {
|
|
|
5357
5371
|
}
|
|
5358
5372
|
}
|
|
5359
5373
|
}
|
|
5374
|
+
function normalizeSourceKind(value) {
|
|
5375
|
+
if (typeof value === "string") {
|
|
5376
|
+
const normalized = value.trim();
|
|
5377
|
+
if (!normalized) return "";
|
|
5378
|
+
if (normalized.startsWith("{")) {
|
|
5379
|
+
try {
|
|
5380
|
+
return normalizeSourceKind(JSON.parse(normalized));
|
|
5381
|
+
} catch {
|
|
5382
|
+
return normalized.toLowerCase();
|
|
5383
|
+
}
|
|
5384
|
+
}
|
|
5385
|
+
return normalized.toLowerCase();
|
|
5386
|
+
}
|
|
5387
|
+
if (!value || typeof value !== "object") return "";
|
|
5388
|
+
const record = value;
|
|
5389
|
+
if ("subagent" in record || "sub_agent" in record) return "subagent";
|
|
5390
|
+
for (const key of ["type", "kind", "source", "name"]) {
|
|
5391
|
+
const nested = normalizeSourceKind(record[key]);
|
|
5392
|
+
if (nested) return nested;
|
|
5393
|
+
}
|
|
5394
|
+
return "";
|
|
5395
|
+
}
|
|
5396
|
+
function isSubagentThread(source, threadSource, parentThreadId) {
|
|
5397
|
+
if (typeof parentThreadId === "string" && parentThreadId.trim()) return true;
|
|
5398
|
+
return normalizeSourceKind(threadSource) === "subagent" || normalizeSourceKind(source) === "subagent";
|
|
5399
|
+
}
|
|
5360
5400
|
function isDesktopLike(meta) {
|
|
5361
5401
|
const originator = typeof meta?.originator === "string" ? meta.originator.toLowerCase() : "";
|
|
5362
|
-
const source =
|
|
5402
|
+
const source = normalizeSourceKind(meta?.source);
|
|
5363
5403
|
if (source === "exec") return false;
|
|
5404
|
+
if (isSubagentThread(meta?.source, meta?.thread_source, meta?.parent_thread_id)) return false;
|
|
5364
5405
|
return originator.includes("desktop") || source === "vscode" || source === "desktop";
|
|
5365
5406
|
}
|
|
5366
5407
|
function loadThreadIndexEntries(archivedThreadIds) {
|
|
@@ -5409,25 +5450,46 @@ function parseUpdatedAtValue(value) {
|
|
|
5409
5450
|
function loadVisibleDesktopThreads(limit) {
|
|
5410
5451
|
const dbPath = getDesktopStateDbPath();
|
|
5411
5452
|
if (!dbPath || !fs3.existsSync(dbPath)) return null;
|
|
5453
|
+
const DatabaseSync = getDatabaseSyncConstructor();
|
|
5454
|
+
if (!DatabaseSync) return null;
|
|
5412
5455
|
let db = null;
|
|
5413
5456
|
try {
|
|
5414
5457
|
db = new DatabaseSync(dbPath, { readOnly: true });
|
|
5458
|
+
const columns = new Set(
|
|
5459
|
+
db.prepare("PRAGMA table_info(threads)").all().map((row) => row && typeof row === "object" ? row.name : null).filter((name) => typeof name === "string")
|
|
5460
|
+
);
|
|
5461
|
+
if (!columns.has("id") || !columns.has("updated_at")) return null;
|
|
5415
5462
|
const hasLimit = typeof limit === "number" && Number.isFinite(limit) && limit > 0;
|
|
5463
|
+
const selectedColumns = [
|
|
5464
|
+
"id",
|
|
5465
|
+
"updated_at",
|
|
5466
|
+
...["updated_at_ms", "rollout_path", "title", "cwd", "source", "thread_source"].filter((column) => columns.has(column))
|
|
5467
|
+
];
|
|
5468
|
+
const whereClauses = columns.has("archived") ? ["archived = 0"] : ["1 = 1"];
|
|
5469
|
+
if (columns.has("source")) whereClauses.push("COALESCE(source, '') != 'exec'");
|
|
5470
|
+
if (columns.has("thread_source")) whereClauses.push("COALESCE(thread_source, '') != 'subagent'");
|
|
5416
5471
|
const sql = `
|
|
5417
|
-
SELECT
|
|
5472
|
+
SELECT ${selectedColumns.join(", ")}
|
|
5418
5473
|
FROM threads
|
|
5419
|
-
WHERE
|
|
5420
|
-
|
|
5421
|
-
ORDER BY updated_at DESC
|
|
5474
|
+
WHERE ${whereClauses.join("\n AND ")}
|
|
5475
|
+
ORDER BY ${columns.has("updated_at_ms") ? "updated_at_ms" : "updated_at"} DESC
|
|
5422
5476
|
${hasLimit ? "LIMIT ?" : ""}
|
|
5423
5477
|
`;
|
|
5424
5478
|
const rows = hasLimit ? db.prepare(sql).all(Math.max(1, Math.floor(limit))) : db.prepare(sql).all();
|
|
5425
5479
|
const ids = rows.map((row) => {
|
|
5426
|
-
|
|
5480
|
+
if (!row || typeof row !== "object") return null;
|
|
5481
|
+
const record = row;
|
|
5482
|
+
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
5427
5483
|
if (!id) return null;
|
|
5484
|
+
if (isSubagentThread(record.source, record.thread_source)) return null;
|
|
5428
5485
|
return {
|
|
5429
5486
|
id,
|
|
5430
|
-
updatedAtMs: parseUpdatedAtValue(
|
|
5487
|
+
updatedAtMs: parseUpdatedAtValue(record.updated_at_ms ?? record.updated_at),
|
|
5488
|
+
...typeof record.rollout_path === "string" && record.rollout_path.trim() ? { rolloutPath: record.rollout_path.trim() } : {},
|
|
5489
|
+
...typeof record.title === "string" && trimTitle(record.title) ? { title: trimTitle(record.title) } : {},
|
|
5490
|
+
...typeof record.cwd === "string" ? { cwd: record.cwd } : {},
|
|
5491
|
+
...typeof record.source === "string" ? { source: record.source } : {},
|
|
5492
|
+
...typeof record.thread_source === "string" ? { threadSource: record.thread_source } : {}
|
|
5431
5493
|
};
|
|
5432
5494
|
}).filter((row) => Boolean(row));
|
|
5433
5495
|
return ids.length > 0 ? ids : null;
|
|
@@ -5458,7 +5520,7 @@ function buildFallbackTitle(threadId, filePath, cwd) {
|
|
|
5458
5520
|
if (dirName) return dirName;
|
|
5459
5521
|
return `Session ${threadId.slice(0, 8)}`;
|
|
5460
5522
|
}
|
|
5461
|
-
function parseDesktopSession(filePath, threadIndexEntries, archivedThreadIds) {
|
|
5523
|
+
function parseDesktopSession(filePath, threadIndexEntries, archivedThreadIds, visibleThread) {
|
|
5462
5524
|
const firstLine = readFirstLine(filePath);
|
|
5463
5525
|
if (!firstLine) return null;
|
|
5464
5526
|
let parsed;
|
|
@@ -5470,7 +5532,8 @@ function parseDesktopSession(filePath, threadIndexEntries, archivedThreadIds) {
|
|
|
5470
5532
|
if (parsed.type !== "session_meta" || !parsed.payload?.id || !isDesktopLike(parsed.payload)) {
|
|
5471
5533
|
return null;
|
|
5472
5534
|
}
|
|
5473
|
-
|
|
5535
|
+
const threadId = visibleThread?.id || parsed.payload.id;
|
|
5536
|
+
if (archivedThreadIds.has(threadId)) {
|
|
5474
5537
|
return null;
|
|
5475
5538
|
}
|
|
5476
5539
|
let stat;
|
|
@@ -5479,20 +5542,19 @@ function parseDesktopSession(filePath, threadIndexEntries, archivedThreadIds) {
|
|
|
5479
5542
|
} catch {
|
|
5480
5543
|
return null;
|
|
5481
5544
|
}
|
|
5482
|
-
const cwd = parsed.payload.cwd || "";
|
|
5545
|
+
const cwd = visibleThread?.cwd || parsed.payload.cwd || "";
|
|
5483
5546
|
if (isInternalSkillWorkspace(cwd)) {
|
|
5484
5547
|
return null;
|
|
5485
5548
|
}
|
|
5486
|
-
const lastEventAt = stat.mtime.toISOString();
|
|
5549
|
+
const lastEventAt = visibleThread?.updatedAtMs ? new Date(visibleThread.updatedAtMs).toISOString() : stat.mtime.toISOString();
|
|
5487
5550
|
const firstSeenAt = parsed.payload.timestamp || parsed.timestamp || stat.birthtime.toISOString();
|
|
5488
|
-
const
|
|
5489
|
-
const title = threadIndexEntries.get(threadId)?.title || buildFallbackTitle(threadId, filePath, cwd);
|
|
5551
|
+
const title = visibleThread?.title || threadIndexEntries.get(threadId)?.title || buildFallbackTitle(threadId, filePath, cwd);
|
|
5490
5552
|
return {
|
|
5491
5553
|
threadId,
|
|
5492
5554
|
filePath,
|
|
5493
5555
|
cwd,
|
|
5494
5556
|
originator: typeof parsed.payload.originator === "string" ? parsed.payload.originator : "Codex Desktop",
|
|
5495
|
-
source: typeof parsed.payload.source === "string" ? parsed.payload.source : void 0,
|
|
5557
|
+
source: visibleThread?.source || (typeof parsed.payload.source === "string" ? parsed.payload.source : void 0),
|
|
5496
5558
|
cliVersion: typeof parsed.payload.cli_version === "string" ? parsed.payload.cli_version : void 0,
|
|
5497
5559
|
firstSeenAt,
|
|
5498
5560
|
lastEventAt,
|
|
@@ -5558,12 +5620,31 @@ function listDesktopSessions(limit) {
|
|
|
5558
5620
|
const visibleThreadIds = visibleThreads?.map((thread) => thread.id) || null;
|
|
5559
5621
|
const visibleThreadSet = visibleThreadIds ? new Set(visibleThreadIds) : null;
|
|
5560
5622
|
const visibleThreadUpdatedAt = new Map(visibleThreads?.map((thread) => [thread.id, thread.updatedAtMs]) || []);
|
|
5623
|
+
const visibleThreadRows = new Map(visibleThreads?.map((thread) => [thread.id, thread]) || []);
|
|
5561
5624
|
const oldestVisibleUpdatedAtMs = visibleThreads && visibleThreads.length > 0 ? Math.min(...visibleThreads.map((thread) => thread.updatedAtMs || Number.MAX_SAFE_INTEGER)) : 0;
|
|
5562
5625
|
const files = [];
|
|
5563
5626
|
walkSessionFiles(root, files);
|
|
5564
5627
|
const allSessions = /* @__PURE__ */ new Map();
|
|
5628
|
+
for (const thread of visibleThreads || []) {
|
|
5629
|
+
if (!thread.rolloutPath || !fs3.existsSync(thread.rolloutPath)) continue;
|
|
5630
|
+
const session = parseDesktopSession(
|
|
5631
|
+
thread.rolloutPath,
|
|
5632
|
+
threadIndexEntries,
|
|
5633
|
+
archivedThreadIds,
|
|
5634
|
+
thread
|
|
5635
|
+
);
|
|
5636
|
+
if (!session || !isWithinSavedWorkspaceRoots(session.cwd, savedWorkspaceRoots)) continue;
|
|
5637
|
+
allSessions.set(session.threadId, session);
|
|
5638
|
+
}
|
|
5565
5639
|
for (const filePath of files) {
|
|
5566
|
-
const
|
|
5640
|
+
const threadId = extractThreadIdFromRolloutName(path2.basename(filePath));
|
|
5641
|
+
if (threadId && allSessions.has(threadId)) continue;
|
|
5642
|
+
const session = parseDesktopSession(
|
|
5643
|
+
filePath,
|
|
5644
|
+
threadIndexEntries,
|
|
5645
|
+
archivedThreadIds,
|
|
5646
|
+
threadId ? visibleThreadRows.get(threadId) : void 0
|
|
5647
|
+
);
|
|
5567
5648
|
if (!session) continue;
|
|
5568
5649
|
if (!isWithinSavedWorkspaceRoots(session.cwd, savedWorkspaceRoots)) continue;
|
|
5569
5650
|
allSessions.set(session.threadId, session);
|
package/docs/install-windows.md
CHANGED
|
@@ -64,6 +64,8 @@ npm -v
|
|
|
64
64
|
- 已有可用的 Codex CLI 登录态
|
|
65
65
|
- 已配置可用的 API Key(`CTI_CODEX_API_KEY`、`CODEX_API_KEY` 或 `OPENAI_API_KEY`)
|
|
66
66
|
|
|
67
|
+
bridge 默认以 `CTI_CODEX_TRANSPORT=auto` 运行:纯 IM 会话使用包内 Codex app-server,Desktop thread 复用保持 SDK + JSONL 路径。即使显式设置 `CTI_CODEX_TRANSPORT=app-server`,Desktop thread 也不会被独立 app-server 接管。需要紧急回退时,可在服务环境中设置 `CTI_CODEX_TRANSPORT=sdk` 后按安全流程重启服务;不要直接连接或改写 Codex Desktop 的 Remote enrollment 数据。
|
|
68
|
+
|
|
67
69
|
说明:
|
|
68
70
|
|
|
69
71
|
- `codex-to-im` 现在已经随包带上运行 bridge 所需的 `@openai/codex-sdk` / Codex CLI 平台依赖
|
package/package.json
CHANGED
package/scripts/build.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as esbuild from 'esbuild';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
2
3
|
|
|
3
4
|
const common = {
|
|
4
5
|
bundle: true,
|
|
@@ -17,7 +18,9 @@ const common = {
|
|
|
17
18
|
'stream', 'events', 'url', 'util', 'child_process', 'worker_threads',
|
|
18
19
|
'node:*',
|
|
19
20
|
],
|
|
20
|
-
banner: {
|
|
21
|
+
banner: {
|
|
22
|
+
js: "import { createRequire as __ctiCreateRequire } from 'module'; const require = __ctiCreateRequire(import.meta.url);",
|
|
23
|
+
},
|
|
21
24
|
};
|
|
22
25
|
|
|
23
26
|
async function build(entryPoint, outfile) {
|
|
@@ -26,6 +29,15 @@ async function build(entryPoint, outfile) {
|
|
|
26
29
|
entryPoints: [entryPoint],
|
|
27
30
|
outfile,
|
|
28
31
|
});
|
|
32
|
+
|
|
33
|
+
const syntaxCheck = spawnSync(process.execPath, ['--check', outfile], {
|
|
34
|
+
encoding: 'utf8',
|
|
35
|
+
});
|
|
36
|
+
if (syntaxCheck.status !== 0) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Generated bundle failed syntax validation: ${outfile}\n${syntaxCheck.stderr || syntaxCheck.stdout}`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
29
41
|
}
|
|
30
42
|
|
|
31
43
|
await build('src/main.ts', 'dist/daemon.mjs');
|