codexmate 0.1.27 → 0.1.29

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.
Files changed (48) hide show
  1. package/README.md +53 -44
  2. package/README.vi.md +45 -36
  3. package/README.zh.md +51 -42
  4. package/cli/config-bootstrap.js +2 -9
  5. package/cli/session-usage.js +2 -2
  6. package/cli.js +407 -40
  7. package/package.json +1 -1
  8. package/web-ui/app.js +7 -0
  9. package/web-ui/index.html +12 -0
  10. package/web-ui/logic.sessions.mjs +6 -4
  11. package/web-ui/modules/app.computed.session.mjs +52 -33
  12. package/web-ui/modules/app.methods.agents.mjs +36 -0
  13. package/web-ui/modules/app.methods.codex-config.mjs +3 -1
  14. package/web-ui/modules/app.methods.index.mjs +2 -0
  15. package/web-ui/modules/app.methods.providers.mjs +12 -4
  16. package/web-ui/modules/app.methods.session-actions.mjs +10 -8
  17. package/web-ui/modules/app.methods.session-browser.mjs +3 -1
  18. package/web-ui/modules/app.methods.session-trash.mjs +16 -16
  19. package/web-ui/modules/app.methods.theme.mjs +39 -0
  20. package/web-ui/modules/app.methods.web-ui-preferences.mjs +9 -0
  21. package/web-ui/modules/i18n/locales/en.mjs +80 -2
  22. package/web-ui/modules/i18n/locales/ja.mjs +80 -2
  23. package/web-ui/modules/i18n/locales/vi.mjs +80 -2
  24. package/web-ui/modules/i18n/locales/zh-tw.mjs +80 -2
  25. package/web-ui/modules/i18n/locales/zh.mjs +80 -2
  26. package/web-ui/modules/i18n.mjs +7 -0
  27. package/web-ui/partials/index/modal-config-template-agents.html +1 -1
  28. package/web-ui/partials/index/modal-health-check.html +1 -1
  29. package/web-ui/partials/index/modal-openclaw-config.html +50 -50
  30. package/web-ui/partials/index/panel-dashboard.html +1 -1
  31. package/web-ui/partials/index/panel-prompts.html +2 -2
  32. package/web-ui/partials/index/panel-sessions.html +47 -30
  33. package/web-ui/partials/index/panel-settings.html +30 -0
  34. package/web-ui/res/web-ui-render.precompiled.js +244 -157
  35. package/web-ui/session-helpers.mjs +3 -0
  36. package/web-ui/styles/base-theme.css +3 -2
  37. package/web-ui/styles/bridge-pool.css +1 -1
  38. package/web-ui/styles/controls-forms.css +1 -1
  39. package/web-ui/styles/layout-shell.css +0 -1
  40. package/web-ui/styles/sessions-preview.css +77 -0
  41. package/web-ui/styles/sessions-toolbar-trash.css +1 -1
  42. package/web-ui/styles/sessions-usage.css +1 -1
  43. package/web-ui/styles/settings-panel.css +2 -2
  44. package/web-ui/styles/theme-dark-tokens.css +83 -0
  45. package/web-ui/styles/theme-dark.css +1355 -0
  46. package/web-ui/styles/theme-ocean-tokens.css +88 -0
  47. package/web-ui/styles/theme-ocean.css +920 -0
  48. package/web-ui/styles.css +2 -0
package/cli.js CHANGED
@@ -7,6 +7,7 @@ const toml = require('@iarna/toml');
7
7
  const JSON5 = require('json5');
8
8
  const zipLib = require('zip-lib');
9
9
  const yauzl = require('yauzl');
10
+ const zlib = require('node:zlib');
10
11
  const { exec, execSync, execFileSync, spawn, spawnSync } = require('child_process');
11
12
  const http = require('http');
12
13
  const https = require('https');
@@ -264,6 +265,20 @@ const PI_SESSIONS_DIR = path.join(PI_DIR, 'agent', 'sessions');
264
265
  const OPENCODE_DATA_DIR = path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share'), 'opencode');
265
266
  const OPENCODE_DB_VIRTUAL_ROOT = path.join(OPENCODE_DATA_DIR, 'sessions');
266
267
  const OPENCODE_DB_READ_LIMIT = 2000;
268
+ function resolveZedDataDir() {
269
+ if (process.env.ZED_DATA_DIR) return process.env.ZED_DATA_DIR;
270
+ if (process.platform === 'win32') {
271
+ return process.env.LOCALAPPDATA
272
+ ? path.join(process.env.LOCALAPPDATA, 'Zed')
273
+ : path.join(os.homedir(), 'AppData', 'Local', 'Zed');
274
+ }
275
+ if (process.platform === 'darwin') return path.join(os.homedir(), '.zed');
276
+ return path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share'), 'zed');
277
+ }
278
+ const ZED_DATA_DIR = resolveZedDataDir();
279
+ const ZED_THREADS_DB_FILE = path.join(ZED_DATA_DIR, 'threads', 'threads.db');
280
+ const ZED_DB_VIRTUAL_ROOT = path.join(ZED_DATA_DIR, 'threads', 'sessions');
281
+ const ZED_SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
267
282
  const RECENT_CONFIGS_FILE = path.join(CONFIG_DIR, 'recent-configs.json');
268
283
  const WORKFLOW_DEFINITIONS_FILE = path.join(CONFIG_DIR, 'codexmate-workflows.json');
269
284
  const WORKFLOW_RUNS_FILE = path.join(CONFIG_DIR, 'codexmate-workflow-runs.jsonl');
@@ -757,6 +772,7 @@ base_url = "http://127.0.0.1:3737/bridge/local/v1"
757
772
  wire_api = "responses"
758
773
  requires_openai_auth = true
759
774
  preferred_auth_method = "codexmate"
775
+ experimental_bearer_token = "codexmate"
760
776
  codexmate_bridge = "local"
761
777
  request_max_retries = 4
762
778
  stream_max_retries = 10
@@ -771,7 +787,8 @@ let g_sessionFileLookupCache = {
771
787
  gemini: new Map(),
772
788
  codebuddy: new Map(),
773
789
  workbuddy: new Map(),
774
- pi: new Map()
790
+ pi: new Map(),
791
+ zed: new Map()
775
792
  };
776
793
  let g_exactMessageCountCache = new Map();
777
794
  let g_modelsCache = new Map();
@@ -893,17 +910,27 @@ function writeCurrentModels(data) {
893
910
  fs.writeFileSync(CURRENT_MODELS_FILE, JSON.stringify(data, null, 2), 'utf-8');
894
911
  }
895
912
 
896
- function updateAuthJson(apiKey) {
897
- assertToolConfigWriteAllowed('codex');
898
- let authData = {};
899
- if (fs.existsSync(AUTH_FILE)) {
900
- try {
901
- const content = fs.readFileSync(AUTH_FILE, 'utf-8');
902
- if (content.trim()) authData = JSON.parse(content);
903
- } catch (e) { }
913
+ function ensureActiveProviderBearerToken(content, providerName, providerSegments = null) {
914
+ // 单文件认证:auth.json 不再写入,激活 provider 块缺 experimental_bearer_token 时
915
+ // 从 preferred_auth_method(内部键仓)惰性补写,保证 codex 仅凭 config.toml 取到密钥。
916
+ const ranges = findProviderSectionRanges(content, providerName, providerSegments);
917
+ if (!ranges.length) return content;
918
+ let updated = content;
919
+ const sorted = [...ranges].sort((a, b) => b.start - a.start);
920
+ for (const range of sorted) {
921
+ const block = updated.slice(range.start, range.end);
922
+ if (/(^|\n)\s*experimental_bearer_token\s*=/.test(block)) continue;
923
+ const prefMatch = block.match(/^(\s*)preferred_auth_method\s*=\s*"((?:\\.|[^"\\\n])*)"\s*(?:#.*)?$/m);
924
+ if (!prefMatch || !prefMatch[2].trim()) continue;
925
+ const insertLine = `${prefMatch[1]}experimental_bearer_token = "${prefMatch[2]}"`;
926
+ const lineEndIndex = block.indexOf('\n', prefMatch.index + prefMatch[0].length);
927
+ const insertAt = lineEndIndex === -1
928
+ ? range.start + block.length
929
+ : range.start + lineEndIndex + 1;
930
+ const prefix = lineEndIndex === -1 ? '\n' : '';
931
+ updated = updated.slice(0, insertAt) + prefix + insertLine + '\n' + updated.slice(insertAt);
904
932
  }
905
- authData['OPENAI_API_KEY'] = apiKey;
906
- fs.writeFileSync(AUTH_FILE, JSON.stringify(authData, null, 2), { encoding: 'utf-8', mode: 0o600 });
933
+ return updated;
907
934
  }
908
935
 
909
936
  function isPlainObject(value) {
@@ -1029,6 +1056,11 @@ function normalizeSidebarCollapsedPreference(value) {
1029
1056
  return normalizeBooleanPreference(value, false);
1030
1057
  }
1031
1058
 
1059
+ function normalizeThemePreference(value) {
1060
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
1061
+ return normalized === 'dark' || normalized === 'ocean' ? normalized : 'light';
1062
+ }
1063
+
1032
1064
  function normalizeSessionFilterSourcePreference(value) {
1033
1065
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
1034
1066
  if (normalized === 'all' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'workbuddy' || normalized === 'pi') return normalized;
@@ -1100,6 +1132,7 @@ function normalizeWebUiPreferences(value = {}) {
1100
1132
  sysPromptMode: normalizeSysPromptModePreference(source.sysPromptMode),
1101
1133
  projectClaudeMdPath: typeof source.projectClaudeMdPath === 'string' ? source.projectClaudeMdPath : '',
1102
1134
  sidebarCollapsed: normalizeSidebarCollapsedPreference(source.sidebarCollapsed),
1135
+ theme: normalizeThemePreference(source.theme),
1103
1136
  starPrompted: normalizeBooleanPreference(source.starPrompted, false),
1104
1137
  sessionLoadNativeDialog: normalizeBooleanPreference(source.sessionLoadNativeDialog, false),
1105
1138
  language: typeof source.language === 'string' ? source.language : '',
@@ -1549,6 +1582,7 @@ const PROVIDER_CONFIG_KEYS = new Set([
1549
1582
  'wire_api',
1550
1583
  'requires_openai_auth',
1551
1584
  'preferred_auth_method',
1585
+ 'experimental_bearer_token',
1552
1586
  'request_max_retries',
1553
1587
  'stream_max_retries',
1554
1588
  'stream_idle_timeout_ms'
@@ -2400,6 +2434,260 @@ function listOpencodeSessions(limit, options = {}) {
2400
2434
  }, []);
2401
2435
  }
2402
2436
 
2437
+
2438
+ function isZedThreadsDbAvailable() {
2439
+ const Database = getNodeSqliteDatabaseSync();
2440
+ return !!(Database && ZED_THREADS_DB_FILE && fs.existsSync(ZED_THREADS_DB_FILE));
2441
+ }
2442
+
2443
+ function withZedThreadsDb(callback, fallback = null) {
2444
+ const Database = getNodeSqliteDatabaseSync();
2445
+ if (!Database || !ZED_THREADS_DB_FILE || !fs.existsSync(ZED_THREADS_DB_FILE)) {
2446
+ return fallback;
2447
+ }
2448
+ let db;
2449
+ try {
2450
+ db = new Database(ZED_THREADS_DB_FILE, { readOnly: true });
2451
+ return callback(db);
2452
+ } catch (_) {
2453
+ return fallback;
2454
+ } finally {
2455
+ if (db) {
2456
+ try { db.close(); } catch (_) { }
2457
+ }
2458
+ }
2459
+ }
2460
+
2461
+ function buildZedVirtualSessionPath(sessionId) {
2462
+ const id = typeof sessionId === 'string' ? sessionId.trim() : '';
2463
+ if (!id) return '';
2464
+ return path.join(ZED_DB_VIRTUAL_ROOT, `${id}.zed.jsonl`);
2465
+ }
2466
+
2467
+ function parseZedVirtualSessionId(filePath) {
2468
+ const raw = typeof filePath === 'string' ? filePath.trim() : '';
2469
+ if (!raw) return '';
2470
+ const base = path.basename(raw, path.extname(raw));
2471
+ return ZED_SESSION_ID_RE.test(base) ? base.toLowerCase() : '';
2472
+ }
2473
+
2474
+ /* Zed 会话文档存于 threads.db 的 threads.data 列(zstd 压缩 JSON)。
2475
+ * 版本 0.3.0 结构:{ title, messages: [{User:{content:[{Text}]}} | {Agent:{content:[{Text}|{Thinking:{text}}], tool_results, reasoning_details}}],
2476
+ * model: {provider, model}, cumulative_token_usage, initial_project_snapshot } */
2477
+ function parseZedThreadDocument(row) {
2478
+ if (!row || typeof row !== 'object') return null;
2479
+ const dataType = typeof row.data_type === 'string' ? row.data_type.trim().toLowerCase() : '';
2480
+ let text = '';
2481
+ try {
2482
+ if (dataType === 'zstd') {
2483
+ text = zlib.zstdDecompressSync(Buffer.from(row.data)).toString('utf8');
2484
+ } else if (!dataType || dataType === 'json' || dataType === 'utf8' || dataType === 'text') {
2485
+ text = Buffer.from(row.data).toString('utf8');
2486
+ } else {
2487
+ return null;
2488
+ }
2489
+ const parsed = JSON.parse(text);
2490
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
2491
+ } catch (_) {
2492
+ return null;
2493
+ }
2494
+ }
2495
+
2496
+ function extractZedTextFromContentBlocks(blocks) {
2497
+ if (!Array.isArray(blocks)) return '';
2498
+ const parts = [];
2499
+ for (const block of blocks) {
2500
+ if (!block || typeof block !== 'object') continue;
2501
+ if (typeof block.Text === 'string' && block.Text.trim()) {
2502
+ parts.push(block.Text.trim());
2503
+ } else if (block.Thinking && typeof block.Thinking === 'object'
2504
+ && typeof block.Thinking.text === 'string' && block.Thinking.text.trim()) {
2505
+ parts.push(block.Thinking.text.trim());
2506
+ }
2507
+ }
2508
+ return parts.join('\n').trim();
2509
+ }
2510
+
2511
+ function readZedThreadModelInfo(doc) {
2512
+ const model = doc && doc.model && typeof doc.model === 'object' ? doc.model : null;
2513
+ return {
2514
+ model: model && typeof model.model === 'string' ? model.model.trim() : '',
2515
+ provider: model && typeof model.provider === 'string' && model.provider.trim() ? model.provider.trim() : 'zed'
2516
+ };
2517
+ }
2518
+
2519
+ function normalizeZedThreadMessage(entry, modelInfo) {
2520
+ if (!entry || typeof entry !== 'object') return null;
2521
+ if (entry.User && typeof entry.User === 'object') {
2522
+ const text = extractZedTextFromContentBlocks(entry.User.content);
2523
+ if (!text) return null;
2524
+ return { role: 'user', text, timestamp: '', model: modelInfo.model, provider: modelInfo.provider };
2525
+ }
2526
+ if (entry.Agent && typeof entry.Agent === 'object') {
2527
+ const text = extractZedTextFromContentBlocks(entry.Agent.content);
2528
+ if (!text) return null;
2529
+ return { role: 'assistant', text, timestamp: '', model: modelInfo.model, provider: modelInfo.provider };
2530
+ }
2531
+ return null;
2532
+ }
2533
+
2534
+ function countZedThreadMessages(doc, modelInfo) {
2535
+ const rawMessages = doc && Array.isArray(doc.messages) ? doc.messages : [];
2536
+ return rawMessages.map(entry => normalizeZedThreadMessage(entry, modelInfo)).filter(Boolean).length;
2537
+ }
2538
+
2539
+ function readZedThreadUsage(doc) {
2540
+ const usage = doc && doc.cumulative_token_usage && typeof doc.cumulative_token_usage === 'object'
2541
+ ? doc.cumulative_token_usage
2542
+ : {};
2543
+ const inputTokens = readNonNegativeInteger(usage.input_tokens) || 0;
2544
+ const cachedInputTokens = readNonNegativeInteger(usage.cache_read_input_tokens) || 0;
2545
+ const cacheCreationInputTokens = readNonNegativeInteger(usage.cache_creation_input_tokens) || 0;
2546
+ const outputTokens = readNonNegativeInteger(usage.output_tokens) || 0;
2547
+ const reasoningOutputTokens = readNonNegativeInteger(usage.reasoning_output_tokens) || 0;
2548
+ return {
2549
+ inputTokens,
2550
+ cachedInputTokens,
2551
+ cacheCreationInputTokens,
2552
+ outputTokens,
2553
+ reasoningOutputTokens,
2554
+ totalTokens: inputTokens + outputTokens + reasoningOutputTokens + cachedInputTokens + cacheCreationInputTokens
2555
+ };
2556
+ }
2557
+
2558
+ function readZedThreadCwd(row, doc) {
2559
+ const raw = typeof row.folder_paths === 'string' ? row.folder_paths.trim() : '';
2560
+ if (raw && !raw.startsWith('[')) {
2561
+ return raw;
2562
+ }
2563
+ if (raw.startsWith('[')) {
2564
+ try {
2565
+ const parsed = JSON.parse(raw);
2566
+ const first = Array.isArray(parsed) ? parsed.find(item => typeof item === 'string' && item.trim()) : null;
2567
+ if (first) return first;
2568
+ } catch (_) { }
2569
+ }
2570
+ const snapshot = doc && doc.initial_project_snapshot && Array.isArray(doc.initial_project_snapshot.worktree_snapshots)
2571
+ ? doc.initial_project_snapshot.worktree_snapshots.find(item => item && typeof item.worktree_path === 'string' && item.worktree_path.trim())
2572
+ : null;
2573
+ return snapshot ? snapshot.worktree_path : '';
2574
+ }
2575
+
2576
+ function queryZedSessionMessages(sessionId, options = {}) {
2577
+ const id = typeof sessionId === 'string' ? sessionId.trim().toLowerCase() : '';
2578
+ if (!id) return null;
2579
+ const maxMessages = resolveMaxMessagesValue(options.maxMessages, DEFAULT_SESSION_DETAIL_MESSAGES);
2580
+ return withZedThreadsDb((db) => {
2581
+ const row = db.prepare('select data, data_type, folder_paths, updated_at from threads where id = ?').get(id) || null;
2582
+ const doc = parseZedThreadDocument(row);
2583
+ const modelInfo = readZedThreadModelInfo(doc);
2584
+ const all = (doc && Array.isArray(doc.messages) ? doc.messages : [])
2585
+ .map(entry => normalizeZedThreadMessage(entry, modelInfo))
2586
+ .filter(Boolean);
2587
+ const totalMessages = all.length;
2588
+ const limit = maxMessages === Infinity
2589
+ ? totalMessages
2590
+ : Math.min(totalMessages, Math.max(1, Math.floor(maxMessages)));
2591
+ const messages = limit < totalMessages ? all.slice(totalMessages - limit) : all;
2592
+ return {
2593
+ sessionId: id,
2594
+ cwd: doc ? readZedThreadCwd(row, doc) : '',
2595
+ updatedAt: row ? toIsoTime(row.updated_at, '') : '',
2596
+ messages,
2597
+ totalMessages,
2598
+ clipped: maxMessages !== Infinity && totalMessages > messages.length,
2599
+ truncated: maxMessages !== Infinity && totalMessages > messages.length
2600
+ };
2601
+ }, null);
2602
+ }
2603
+
2604
+ function countZedMessages(sessionId) {
2605
+ const id = typeof sessionId === 'string' ? sessionId.trim().toLowerCase() : '';
2606
+ if (!id) return null;
2607
+ return withZedThreadsDb((db) => {
2608
+ const row = db.prepare('select data, data_type from threads where id = ?').get(id) || null;
2609
+ const doc = parseZedThreadDocument(row);
2610
+ if (!doc) return null;
2611
+ return countZedThreadMessages(doc, readZedThreadModelInfo(doc));
2612
+ }, null);
2613
+ }
2614
+
2615
+ function parseZedSessionSummaryRow(row) {
2616
+ if (!row || typeof row !== 'object') return null;
2617
+ const sessionId = typeof row.id === 'string' ? row.id.trim().toLowerCase() : '';
2618
+ if (!sessionId) return null;
2619
+ const doc = parseZedThreadDocument(row);
2620
+ if (!doc) return null;
2621
+ const modelInfo = readZedThreadModelInfo(doc);
2622
+ const usage = readZedThreadUsage(doc);
2623
+ let title = typeof row.summary === 'string' ? row.summary.trim() : '';
2624
+ if (!title && typeof doc.title === 'string') title = doc.title.trim();
2625
+ if (!title) {
2626
+ const firstUser = (Array.isArray(doc.messages) ? doc.messages : [])
2627
+ .map(entry => normalizeZedThreadMessage(entry, modelInfo))
2628
+ .find(item => item && item.role === 'user' && item.text);
2629
+ if (firstUser) title = truncateText(firstUser.text, 120);
2630
+ }
2631
+ if (!title) title = sessionId;
2632
+ return {
2633
+ source: 'zed',
2634
+ sourceLabel: 'Zed',
2635
+ provider: modelInfo.provider,
2636
+ model: modelInfo.model,
2637
+ models: modelInfo.model ? [modelInfo.model] : [],
2638
+ sessionId,
2639
+ title,
2640
+ cwd: readZedThreadCwd(row, doc),
2641
+ createdAt: toIsoTime(row.created_at, ''),
2642
+ updatedAt: toIsoTime(row.updated_at, ''),
2643
+ messageCount: countZedThreadMessages(doc, modelInfo),
2644
+ totalTokens: usage.totalTokens,
2645
+ contextWindow: 0,
2646
+ inputTokens: usage.inputTokens,
2647
+ cachedInputTokens: usage.cachedInputTokens,
2648
+ cacheCreationInputTokens: usage.cacheCreationInputTokens,
2649
+ outputTokens: usage.outputTokens,
2650
+ reasoningOutputTokens: usage.reasoningOutputTokens,
2651
+ __messageCountExact: true,
2652
+ filePath: buildZedVirtualSessionPath(sessionId),
2653
+ keywords: [],
2654
+ capabilities: { code: true }
2655
+ };
2656
+ }
2657
+
2658
+ function parseZedSessionSummary(sessionId) {
2659
+ const id = typeof sessionId === 'string' ? sessionId.trim().toLowerCase() : '';
2660
+ if (!id) return null;
2661
+ return withZedThreadsDb((db) => {
2662
+ const row = db.prepare(`
2663
+ select id, summary, folder_paths, created_at, updated_at, data, data_type
2664
+ from threads where id = ?
2665
+ `).get(id) || null;
2666
+ return parseZedSessionSummaryRow(row);
2667
+ }, null);
2668
+ }
2669
+
2670
+ function listZedSessions(limit, options = {}) {
2671
+ if (!isZedThreadsDbAvailable()) return [];
2672
+ const scanFactor = Number.isFinite(Number(options.scanFactor)) ? Math.max(1, Number(options.scanFactor)) : SESSION_SCAN_FACTOR;
2673
+ const targetCount = Number.isFinite(Number(options.targetCount)) ? Math.max(1, Math.floor(Number(options.targetCount))) : Math.max(1, Math.floor(limit * scanFactor));
2674
+ const scanCount = Number.isFinite(Number(options.scanCount)) ? Math.max(targetCount, Math.floor(Number(options.scanCount))) : Math.max(targetCount, Math.min(SESSION_SCAN_MIN_FILES, MAX_SESSION_LIST_SIZE * SESSION_SCAN_FACTOR));
2675
+ return withZedThreadsDb((db) => {
2676
+ const rows = db.prepare(`
2677
+ select id, summary, folder_paths, created_at, updated_at, data, data_type
2678
+ from threads
2679
+ order by updated_at desc
2680
+ limit ?
2681
+ `).all(Math.min(OPENCODE_DB_READ_LIMIT, Math.max(scanCount, targetCount)));
2682
+ const sessions = [];
2683
+ for (const row of rows) {
2684
+ const summary = parseZedSessionSummaryRow(row);
2685
+ if (summary) sessions.push(summary);
2686
+ if (sessions.length >= targetCount) break;
2687
+ }
2688
+ return mergeAndLimitSessions(sessions, limit);
2689
+ }, []);
2690
+ }
2403
2691
  function getCodexmateDerivedSessionsRoot(target) {
2404
2692
  if (target === 'claude') {
2405
2693
  return CODEXMATE_DERIVED_CLAUDE_DIR;
@@ -2753,7 +3041,6 @@ const {
2753
3041
  writeModels,
2754
3042
  readCurrentModels,
2755
3043
  writeCurrentModels,
2756
- updateAuthJson,
2757
3044
  CONFIG_DIR,
2758
3045
  CONFIG_FILE,
2759
3046
  AUTH_FILE,
@@ -3071,8 +3358,8 @@ function applyConfigTemplate(params = {}) {
3071
3358
  return { error: `模板中找不到当前 provider: ${activeProvider}` };
3072
3359
  }
3073
3360
 
3074
- writeConfig(template.trim() + '\n');
3075
- updateAuthJson(activeProviderBlock.preferred_auth_method || '');
3361
+ const ensuredTemplate = ensureActiveProviderBearerToken(template.trim(), activeProvider);
3362
+ writeConfig(ensuredTemplate + '\n');
3076
3363
 
3077
3364
  const models = readModels();
3078
3365
  if (!models.includes(parsed.model)) {
@@ -3310,6 +3597,7 @@ function addProviderToConfig(params = {}) {
3310
3597
  `wire_api = "responses"`,
3311
3598
  `requires_openai_auth = true`,
3312
3599
  `preferred_auth_method = "${safeKey}"`,
3600
+ `experimental_bearer_token = "${safeKey}"`,
3313
3601
  `models = [{ id = "${escapeTomlBasicString(model)}", name = "${escapeTomlBasicString(model)}" }]`,
3314
3602
  ...extraLines,
3315
3603
  `request_max_retries = 4`,
@@ -4672,7 +4960,7 @@ async function hydrateSessionItemsExactMessageCount(items) {
4672
4960
 
4673
4961
  function normalizeSessionSourceName(value, fallback = '') {
4674
4962
  const source = typeof value === 'string' ? value.trim().toLowerCase() : '';
4675
- if (source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'pi' || source === 'opencode') {
4963
+ if (source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'pi' || source === 'opencode' || source === 'zed') {
4676
4964
  return source;
4677
4965
  }
4678
4966
  return fallback;
@@ -4690,6 +4978,7 @@ function getSessionSourceLabel(source) {
4690
4978
  if (source === 'workbuddy') return 'WorkBuddy AI';
4691
4979
  if (source === 'pi') return 'Pi';
4692
4980
  if (source === 'opencode') return 'OpenCode';
4981
+ if (source === 'zed') return 'Zed';
4693
4982
  return 'Codex';
4694
4983
  }
4695
4984
 
@@ -4700,6 +4989,7 @@ function getSessionSourceProvider(source) {
4700
4989
  if (source === 'workbuddy') return 'workbuddy';
4701
4990
  if (source === 'pi') return 'pi';
4702
4991
  if (source === 'opencode') return 'opencode';
4992
+ if (source === 'zed') return 'zed';
4703
4993
  return 'codex';
4704
4994
  }
4705
4995
 
@@ -4729,6 +5019,9 @@ async function readSessionMessageCounts(params = {}) {
4729
5019
  if (source === 'opencode') {
4730
5020
  const opencodeSessionId = item.sessionId || parseOpencodeVirtualSessionId(filePath);
4731
5021
  exactMessageCount = countOpencodeMessages(opencodeSessionId);
5022
+ } else if (source === 'zed') {
5023
+ const zedSessionId = item.sessionId || parseZedVirtualSessionId(filePath);
5024
+ exactMessageCount = countZedMessages(zedSessionId);
4732
5025
  } else {
4733
5026
  if (!filePath || !fs.existsSync(filePath)) {
4734
5027
  return { key };
@@ -4879,6 +5172,7 @@ function createSessionQueryScanState(tokens, options = {}) {
4879
5172
  count: 0,
4880
5173
  snippets: [],
4881
5174
  positions: [],
5175
+ complete: true,
4882
5176
  leadingSystem: roleFilter !== 'system'
4883
5177
  };
4884
5178
  }
@@ -4917,11 +5211,13 @@ function consumeSessionQueryMessage(state, message) {
4917
5211
  timestamp: typeof message.timestamp === 'string' ? message.timestamp : ''
4918
5212
  });
4919
5213
  }
5214
+ if (state.count >= state.maxMatches) state.complete = false;
4920
5215
  return state.count >= state.maxMatches;
4921
5216
  }
4922
5217
 
4923
5218
  function buildSessionQueryScanResult(state) {
4924
5219
  return {
5220
+ complete: !!(state && state.complete),
4925
5221
  hit: !!(state && state.count > 0),
4926
5222
  count: state && Number.isFinite(state.count) ? state.count : 0,
4927
5223
  snippets: state && Array.isArray(state.snippets) ? state.snippets : [],
@@ -4966,6 +5262,7 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
4966
5262
  const state = createSessionQueryScanState(tokens, options);
4967
5263
  if (session.source === 'opencode') {
4968
5264
  const extracted = queryOpencodeSessionMessages(session.sessionId || parseOpencodeVirtualSessionId(filePath), { maxMessages: Infinity });
5265
+ state.complete = !!extracted && !extracted.truncated;
4969
5266
  const messages = extracted && Array.isArray(extracted.messages) ? extracted.messages : [];
4970
5267
  for (let i = 0; i < messages.length; i += 1) {
4971
5268
  const message = { ...messages[i], recordLineIndex: i };
@@ -4973,6 +5270,16 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
4973
5270
  }
4974
5271
  return buildSessionQueryScanResult(state);
4975
5272
  }
5273
+ if (session.source === 'zed') {
5274
+ const zedExtracted = queryZedSessionMessages(session.sessionId || parseZedVirtualSessionId(filePath), { maxMessages: Infinity });
5275
+ state.complete = !!zedExtracted && !zedExtracted.truncated;
5276
+ const zedMessages = zedExtracted && Array.isArray(zedExtracted.messages) ? zedExtracted.messages : [];
5277
+ for (let i = 0; i < zedMessages.length; i += 1) {
5278
+ const message = { ...zedMessages[i], recordLineIndex: i };
5279
+ if (consumeSessionQueryMessage(state, message)) break;
5280
+ }
5281
+ return buildSessionQueryScanResult(state);
5282
+ }
4976
5283
  if (session.source === 'gemini') {
4977
5284
  if (state.roleFilter !== 'all' || state.positionsLimit > 0) {
4978
5285
  let json;
@@ -4981,6 +5288,7 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
4981
5288
  } catch (_) {
4982
5289
  json = null;
4983
5290
  }
5291
+ state.complete = !!json;
4984
5292
  const rawMessages = json && Array.isArray(json.messages) ? json.messages : [];
4985
5293
  for (const entry of rawMessages) {
4986
5294
  if (!entry || typeof entry !== 'object') continue;
@@ -5008,12 +5316,14 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
5008
5316
  const buf = Buffer.alloc(targetBytes);
5009
5317
  const bytes = fs.readSync(fd, buf, 0, targetBytes, 0);
5010
5318
  fs.closeSync(fd);
5319
+ state.complete = bytes >= stat.size;
5011
5320
  text = bytes > 0 ? buf.slice(0, bytes).toString('utf-8') : '';
5012
5321
  } catch (_) {
5013
5322
  try {
5014
5323
  text = fs.readFileSync(filePath, 'utf-8');
5015
5324
  } catch (_) {
5016
5325
  text = '';
5326
+ state.complete = false;
5017
5327
  }
5018
5328
  }
5019
5329
 
@@ -5038,6 +5348,7 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
5038
5348
  const currentLineIndex = lineIndex;
5039
5349
  lineIndex += 1;
5040
5350
  if (maxBytes > 0 && bytesRead >= maxBytes) {
5351
+ state.complete = false;
5041
5352
  break;
5042
5353
  }
5043
5354
 
@@ -5067,6 +5378,7 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
5067
5378
 
5068
5379
  return buildSessionQueryScanResult(state);
5069
5380
  } catch (e) {
5381
+ state.complete = false;
5070
5382
  return scanSessionContentForQueryInRecords(readJsonlRecords(filePath), session.source, state);
5071
5383
  } finally {
5072
5384
  if (rl) {
@@ -5081,7 +5393,9 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
5081
5393
  async function applySessionQueryFilter(sessions, options = {}) {
5082
5394
  const tokens = Array.isArray(options.tokens) ? options.tokens : [];
5083
5395
  if (tokens.length === 0) {
5084
- return sessions;
5396
+ const result = sessions.slice();
5397
+ Object.defineProperty(result, 'searchInfo', { value: null });
5398
+ return result;
5085
5399
  }
5086
5400
 
5087
5401
  const mode = normalizeQueryMode(options.queryMode);
@@ -5095,10 +5409,12 @@ async function applySessionQueryFilter(sessions, options = {}) {
5095
5409
  : 0;
5096
5410
 
5097
5411
  let scanned = 0;
5412
+ let complete = true;
5098
5413
  const results = [];
5099
5414
 
5100
5415
  for (const session of sessions) {
5101
5416
  if (scope === 'content' && scanned >= contentScanLimit) {
5417
+ complete = false;
5102
5418
  break;
5103
5419
  }
5104
5420
 
@@ -5118,6 +5434,10 @@ async function applySessionQueryFilter(sessions, options = {}) {
5118
5434
  snippetLimit: 2
5119
5435
  });
5120
5436
  contentHit = contentInfo.hit;
5437
+ // 命中已完成候选的存在性判定,无需穷尽匹配或消息。
5438
+ if (!contentHit && !contentInfo.complete) complete = false;
5439
+ } else if (shouldScanContent) {
5440
+ complete = false;
5121
5441
  }
5122
5442
 
5123
5443
  const hit = scope === 'summary'
@@ -5139,6 +5459,9 @@ async function applySessionQueryFilter(sessions, options = {}) {
5139
5459
  results.push(session);
5140
5460
  }
5141
5461
 
5462
+ Object.defineProperty(results, 'searchInfo', {
5463
+ value: { scanned, candidates: sessions.length, complete }
5464
+ });
5142
5465
  return results;
5143
5466
  }
5144
5467
  function collectRecentJsonlFiles(rootDir, options = {}) {
@@ -5364,7 +5687,7 @@ function getSessionInventoryCache(cacheKey, forceRefresh = false) {
5364
5687
  }
5365
5688
 
5366
5689
  function registerSessionFileLookupEntries(source, sessions = []) {
5367
- const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'pi' || source === 'opencode'
5690
+ const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'pi' || source === 'opencode' || source === 'zed'
5368
5691
  ? source
5369
5692
  : 'codex';
5370
5693
  const store = g_sessionFileLookupCache[normalizedSource];
@@ -5405,7 +5728,7 @@ function setSessionInventoryCache(cacheKey, source, value) {
5405
5728
  }
5406
5729
 
5407
5730
  function listSessionInventoryBySource(source, limit, scanOptions = {}, options = {}) {
5408
- const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'pi' || source === 'opencode'
5731
+ const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'pi' || source === 'opencode' || source === 'zed'
5409
5732
  ? source
5410
5733
  : 'codex';
5411
5734
  const forceRefresh = !!options.forceRefresh;
@@ -5427,7 +5750,9 @@ function listSessionInventoryBySource(source, limit, scanOptions = {}, options =
5427
5750
  ? listPiSessions(limit, scanOptions)
5428
5751
  : (normalizedSource === 'opencode'
5429
5752
  ? listOpencodeSessions(limit, scanOptions)
5430
- : listCodexSessions(limit, scanOptions))))));
5753
+ : (normalizedSource === 'zed'
5754
+ ? listZedSessions(limit, scanOptions)
5755
+ : listCodexSessions(limit, scanOptions)))))));
5431
5756
  setSessionInventoryCache(cacheKey, normalizedSource, sessions);
5432
5757
  return sessions;
5433
5758
  }
@@ -5442,7 +5767,8 @@ function invalidateSessionListCache() {
5442
5767
  codebuddy: new Map(),
5443
5768
  workbuddy: new Map(),
5444
5769
  pi: new Map(),
5445
- opencode: new Map()
5770
+ opencode: new Map(),
5771
+ zed: new Map()
5446
5772
  };
5447
5773
  }
5448
5774
 
@@ -7308,6 +7634,9 @@ async function listAllSessions(params = {}) {
7308
7634
  if (source === 'all' || source === 'opencode') {
7309
7635
  sessions = sessions.concat(listSessionInventoryBySource('opencode', limit, scanOptions, { forceRefresh }));
7310
7636
  }
7637
+ if (source === 'all' || source === 'zed') {
7638
+ sessions = sessions.concat(listSessionInventoryBySource('zed', limit, scanOptions, { forceRefresh }));
7639
+ }
7311
7640
 
7312
7641
  if (hasPathFilter) {
7313
7642
  sessions = sessions.filter(item => matchesSessionPathFilter(item, normalizedPathFilter));
@@ -7324,7 +7653,9 @@ async function listAllSessions(params = {}) {
7324
7653
  contentScanBytes: params.contentScanBytes
7325
7654
  });
7326
7655
  }
7656
+ const searchInfo = hasQuery ? result.searchInfo : null;
7327
7657
  result = mergeAndLimitSessions(result, limit);
7658
+ Object.defineProperty(result, 'searchInfo', { value: searchInfo });
7328
7659
  setSessionListCache(cacheKey, result);
7329
7660
  return result;
7330
7661
  }
@@ -7357,6 +7688,7 @@ async function listAllSessionsData(params = {}) {
7357
7688
  delete normalized.__messageCountExact;
7358
7689
  return normalized;
7359
7690
  });
7691
+ Object.defineProperty(result, 'searchInfo', { value: sessions.searchInfo || null });
7360
7692
  if (!hasQuery) {
7361
7693
  setSessionListCache(cacheKey, result);
7362
7694
  }
@@ -7368,7 +7700,7 @@ async function listSessionBrowse(params = {}) {
7368
7700
  ...params,
7369
7701
  browseLightweight: true
7370
7702
  });
7371
- return Array.isArray(sessions)
7703
+ const result = Array.isArray(sessions)
7372
7704
  ? sessions.map((item) => {
7373
7705
  if (!item || typeof item !== 'object' || Array.isArray(item)) {
7374
7706
  return item;
@@ -7378,6 +7710,8 @@ async function listSessionBrowse(params = {}) {
7378
7710
  return normalized;
7379
7711
  })
7380
7712
  : [];
7713
+ Object.defineProperty(result, 'searchInfo', { value: sessions.searchInfo || null });
7714
+ return result;
7381
7715
  }
7382
7716
 
7383
7717
  async function listSessionUsage(params = {}) {
@@ -7449,6 +7783,9 @@ function listSessionPaths(params = {}) {
7449
7783
  if (validSource === 'all' || validSource === 'opencode') {
7450
7784
  sessions = sessions.concat(listSessionInventoryBySource('opencode', gatherLimit, scanOptions, { forceRefresh }));
7451
7785
  }
7786
+ if (validSource === 'all' || validSource === 'zed') {
7787
+ sessions = sessions.concat(listSessionInventoryBySource('zed', gatherLimit, scanOptions, { forceRefresh }));
7788
+ }
7452
7789
 
7453
7790
  const dedupedPaths = [];
7454
7791
  const seen = new Set();
@@ -7475,6 +7812,12 @@ function listSessionPaths(params = {}) {
7475
7812
 
7476
7813
  function resolveSessionFilePath(source, filePath, sessionId) {
7477
7814
  const normalizedSource = normalizeSessionSourceName(source, 'codex');
7815
+ if (normalizedSource === 'zed') {
7816
+ const zedId = typeof sessionId === 'string' && sessionId.trim()
7817
+ ? sessionId.trim()
7818
+ : parseZedVirtualSessionId(filePath);
7819
+ return zedId && parseZedSessionSummary(zedId) ? buildZedVirtualSessionPath(zedId) : '';
7820
+ }
7478
7821
  if (normalizedSource === 'opencode') {
7479
7822
  const id = typeof sessionId === 'string' && sessionId.trim()
7480
7823
  ? sessionId.trim()
@@ -8055,7 +8398,7 @@ function buildSessionSummaryFallback(source, filePath, sessionId = '') {
8055
8398
  contextWindow: 0,
8056
8399
  filePath,
8057
8400
  keywords: [],
8058
- capabilities: source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'opencode' ? { code: true } : {}
8401
+ capabilities: source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'opencode' || source === 'zed' ? { code: true } : {}
8059
8402
  };
8060
8403
  }
8061
8404
 
@@ -8642,6 +8985,9 @@ async function trashSessionData(params = {}) {
8642
8985
  if (source === 'opencode') {
8643
8986
  return { error: 'OpenCode DB-backed sessions cannot be deleted from CodexMate' };
8644
8987
  }
8988
+ if (source === 'zed') {
8989
+ return { error: 'Zed DB-backed sessions cannot be deleted from CodexMate' };
8990
+ }
8645
8991
 
8646
8992
  const filePath = resolveSessionFilePath(source, getSessionFileArg(params), params.sessionId);
8647
8993
  if (!filePath) {
@@ -8754,6 +9100,9 @@ async function deleteSessionData(params = {}) {
8754
9100
  if (source === 'opencode') {
8755
9101
  return { error: 'OpenCode DB-backed sessions cannot be deleted from CodexMate' };
8756
9102
  }
9103
+ if (source === 'zed') {
9104
+ return { error: 'Zed DB-backed sessions cannot be deleted from CodexMate' };
9105
+ }
8757
9106
 
8758
9107
  const filePath = resolveSessionFilePath(source, getSessionFileArg(params), params.sessionId);
8759
9108
  if (!filePath) {
@@ -9453,6 +9802,13 @@ async function readSessionDetail(params = {}) {
9453
9802
  if (!extracted) {
9454
9803
  return { error: 'Failed to parse session file' };
9455
9804
  }
9805
+ }
9806
+ if (source === 'zed') {
9807
+ const zedSessionId = params.sessionId || parseZedVirtualSessionId(filePath);
9808
+ extracted = queryZedSessionMessages(zedSessionId, { maxMessages: messageLimit });
9809
+ if (!extracted) {
9810
+ return { error: 'Failed to parse session file' };
9811
+ }
9456
9812
  } else if (source === 'gemini') {
9457
9813
  let json;
9458
9814
  try {
@@ -9587,6 +9943,13 @@ async function readSessionPlain(params = {}) {
9587
9943
  if (!extracted) {
9588
9944
  return { error: 'Failed to parse session file' };
9589
9945
  }
9946
+ }
9947
+ if (source === 'zed') {
9948
+ const zedSessionId = params.sessionId || parseZedVirtualSessionId(filePath);
9949
+ extracted = queryZedSessionMessages(zedSessionId, { maxMessages });
9950
+ if (!extracted) {
9951
+ return { error: 'Failed to parse session file' };
9952
+ }
9590
9953
  } else if (source === 'gemini') {
9591
9954
  let json;
9592
9955
  try {
@@ -9671,6 +10034,12 @@ async function exportSessionData(params = {}) {
9671
10034
  if (!extracted) {
9672
10035
  return { error: 'Failed to parse session file' };
9673
10036
  }
10037
+ } else if (source === 'zed') {
10038
+ const zedSessionId = params.sessionId || parseZedVirtualSessionId(filePath);
10039
+ extracted = queryZedSessionMessages(zedSessionId, { maxMessages });
10040
+ if (!extracted) {
10041
+ return { error: 'Failed to parse session file' };
10042
+ }
9674
10043
  } else if (source === 'gemini') {
9675
10044
  let json;
9676
10045
  try {
@@ -10921,17 +11290,16 @@ function cmdSwitch(providerName, silent = false) {
10921
11290
  throw new Error('提供商不存在');
10922
11291
  }
10923
11292
 
10924
- // 切换提供商
11293
+ // 切换提供商(单文件认证:同步确保目标块含 experimental_bearer_token)
10925
11294
  const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
10926
- const newContent = content.replace(
11295
+ const switchedContent = content.replace(
10927
11296
  /^(model_provider\s*=\s*)(["']).*?(["'])/m,
10928
11297
  `$1$2${providerName}$3`
10929
11298
  );
10930
- writeConfig(newContent);
10931
-
10932
- // 更新认证信息
10933
- const apiKey = providers[providerName].preferred_auth_method || '';
10934
- updateAuthJson(apiKey);
11299
+ const providerSegments = providers[providerName] && Array.isArray(providers[providerName].__codexmate_legacy_segments)
11300
+ ? providers[providerName].__codexmate_legacy_segments
11301
+ : null;
11302
+ writeConfig(ensureActiveProviderBearerToken(switchedContent, providerName, providerSegments));
10935
11303
 
10936
11304
  // 切换到该提供商的模型
10937
11305
  const currentModels = readCurrentModels();
@@ -11072,6 +11440,7 @@ base_url = "${safeBaseUrl}"
11072
11440
  wire_api = "responses"
11073
11441
  requires_openai_auth = true
11074
11442
  preferred_auth_method = "${safeApiKey}"
11443
+ experimental_bearer_token = "${safeApiKey}"
11075
11444
  request_max_retries = 4
11076
11445
  stream_max_retries = 10
11077
11446
  stream_idle_timeout_ms = 300000
@@ -11373,6 +11742,7 @@ function cmdUpdate(name, baseUrl, apiKey, silent = false, options = {}) {
11373
11742
  updatedBlock = replaceTomlStringField(updatedBlock, 'base_url', computeLocalOpenaiBridgeBaseUrl());
11374
11743
  updatedBlock = replaceTomlBooleanField(updatedBlock, 'requires_openai_auth', true);
11375
11744
  updatedBlock = replaceTomlStringField(updatedBlock, 'preferred_auth_method', 'codexmate');
11745
+ updatedBlock = replaceTomlStringField(updatedBlock, 'experimental_bearer_token', 'codexmate');
11376
11746
  updatedBlock = replaceTomlStringField(updatedBlock, 'codexmate_bridge', 'openai');
11377
11747
  if (hasOpenaiBridgeMaxRetries) {
11378
11748
  updatedBlock = replaceTomlNumberField(updatedBlock, 'codexmate_bridge_max_retries', openaiBridgeMaxRetries);
@@ -11383,6 +11753,7 @@ function cmdUpdate(name, baseUrl, apiKey, silent = false, options = {}) {
11383
11753
  }
11384
11754
  if (apiKey !== undefined) {
11385
11755
  updatedBlock = replaceTomlStringField(updatedBlock, 'preferred_auth_method', apiKey);
11756
+ updatedBlock = replaceTomlStringField(updatedBlock, 'experimental_bearer_token', apiKey);
11386
11757
  }
11387
11758
  }
11388
11759
 
@@ -11397,12 +11768,6 @@ function cmdUpdate(name, baseUrl, apiKey, silent = false, options = {}) {
11397
11768
  }
11398
11769
  writeConfig(finalContent);
11399
11770
 
11400
- // 如果更新了 API Key 且该提供商是当前激活的,同步更新 auth.json
11401
- const currentProvider = config.model_provider;
11402
- if (apiKey !== undefined && name === currentProvider) {
11403
- updateAuthJson(apiKey);
11404
- }
11405
-
11406
11771
  if (!silent) {
11407
11772
  console.log('✓ 已更新提供商:', name);
11408
11773
  console.log();
@@ -12916,7 +13281,7 @@ function resolveExportOutputPath(outputPath, defaultFileName) {
12916
13281
  }
12917
13282
 
12918
13283
  function printExportSessionUsage() {
12919
- console.log('\n用法: codexmate export-session --source <codex|claude|gemini|codebuddy|workbuddy|pi|opencode> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
13284
+ console.log('\n用法: codexmate export-session --source <codex|claude|gemini|codebuddy|workbuddy|pi|opencode|zed> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
12920
13285
  console.log('\n示例:');
12921
13286
  console.log(' codexmate export-session --source codex --session-id 123456');
12922
13287
  console.log(' codexmate export-session --source claude --file "~/.claude/projects/demo/session.jsonl"');
@@ -12989,7 +13354,7 @@ function parseExportSessionArgs(args = []) {
12989
13354
 
12990
13355
  const normalizedSource = options.source.trim().toLowerCase();
12991
13356
  if (normalizedSource && !isSessionSourceOrAll(normalizedSource)) {
12992
- errors.push('参数 --source 仅支持 codex、claude、gemini、codebuddy、workbuddy、pi 或 opencode');
13357
+ errors.push('参数 --source 仅支持 codex、claude、gemini、codebuddy、workbuddy、pi、opencode 或 zed');
12993
13358
  }
12994
13359
  options.source = normalizedSource;
12995
13360
 
@@ -14420,9 +14785,11 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
14420
14785
  if (source && !isSessionSourceOrAll(source)) {
14421
14786
  result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' };
14422
14787
  } else {
14788
+ const sessions = await listSessionBrowse(params);
14423
14789
  result = {
14424
- sessions: await listSessionBrowse(params),
14425
- source: source || 'all'
14790
+ sessions,
14791
+ source: source || 'all',
14792
+ searchInfo: sessions.searchInfo
14426
14793
  };
14427
14794
  }
14428
14795
  }
@@ -17361,7 +17728,7 @@ function printMainHelp() {
17361
17728
  console.log(' 注: follow-up 自动排队仅支持 linux/android/netbsd/openbsd/darwin/freebsd 且 stdin 必须是 TTY,其他平台会报错');
17362
17729
  console.log(' codexmate qwen [参数...] 等同于 qwen --yolo');
17363
17730
  console.log(' codexmate mcp [serve] [--transport stdio] [--allow-write|--read-only]');
17364
- console.log(' codexmate export-session --source <codex|claude|gemini|codebuddy|workbuddy|pi|opencode> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
17731
+ console.log(' codexmate export-session --source <codex|claude|gemini|codebuddy|workbuddy|pi|opencode|zed> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
17365
17732
  console.log(' codexmate convert-session --from <codex|claude> --to <codex|claude> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
17366
17733
  console.log(' codexmate zip <路径> [--max:级别] 压缩(系统 zip 优先,其次 zip-lib)');
17367
17734
  console.log(' codexmate unzip <zip文件> [输出目录] 解压(zip-lib)');