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.
@@ -1,4 +1,4 @@
1
- import { createRequire } from 'module'; const require = createRequire(import.meta.url);
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 { DatabaseSync } from "node:sqlite";
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 = typeof meta?.source === "string" ? meta.source.toLowerCase() : "";
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 id, updated_at
5472
+ SELECT ${selectedColumns.join(", ")}
5418
5473
  FROM threads
5419
- WHERE archived = 0
5420
- AND source != 'exec'
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
- const id = typeof row.id === "string" ? row.id.trim() : "";
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(row.updated_at)
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
- if (archivedThreadIds.has(parsed.payload.id)) {
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 threadId = parsed.payload.id;
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 session = parseDesktopSession(filePath, threadIndexEntries, archivedThreadIds);
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);
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-to-im",
3
- "version": "1.0.62",
3
+ "version": "1.0.64",
4
4
  "description": "Installable Codex-to-IM bridge with local setup UI and background service",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/zhangle1987/codex-to-im#readme",
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: { js: "import { createRequire } from 'module'; const require = createRequire(import.meta.url);" },
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');