codexmate 0.1.25 → 0.1.28
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/cli/config-bootstrap.js +2 -9
- package/cli/session-usage.js +10 -4
- package/cli.js +772 -225
- package/lib/cli-sessions.js +1 -1
- package/package.json +3 -3
- package/tools/dev/launch.js +147 -0
- package/web-ui/app.js +17 -3
- package/web-ui/index.html +12 -1
- package/web-ui/logic.prompts-token-count.mjs +49 -0
- package/web-ui/logic.sessions.mjs +30 -15
- package/web-ui/modules/app.computed.dashboard.mjs +1 -0
- package/web-ui/modules/app.computed.prompts.mjs +18 -0
- package/web-ui/modules/app.computed.session.mjs +3 -1
- package/web-ui/modules/app.methods.index.mjs +2 -0
- package/web-ui/modules/app.methods.session-actions.mjs +10 -4
- package/web-ui/modules/app.methods.session-browser.mjs +34 -45
- package/web-ui/modules/app.methods.session-timeline.mjs +1 -0
- package/web-ui/modules/app.methods.theme.mjs +38 -0
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +9 -0
- package/web-ui/modules/i18n/locales/en.mjs +15 -2
- package/web-ui/modules/i18n/locales/ja.mjs +15 -2
- package/web-ui/modules/i18n/locales/vi.mjs +15 -2
- package/web-ui/modules/i18n/locales/zh-tw.mjs +15 -2
- package/web-ui/modules/i18n/locales/zh.mjs +15 -2
- package/web-ui/partials/index/layout-header.html +4 -2
- package/web-ui/partials/index/panel-prompts.html +14 -0
- package/web-ui/partials/index/panel-sessions.html +21 -6
- package/web-ui/partials/index/panel-settings.html +24 -0
- package/web-ui/res/web-ui-render.precompiled.js +115 -23
- package/web-ui/session-helpers.mjs +1 -1
- package/web-ui/styles/base-theme.css +25 -0
- package/web-ui/styles/controls-forms.css +1 -1
- package/web-ui/styles/modals-core.css +48 -0
- package/web-ui/styles/sessions-list.css +1 -1
- package/web-ui/styles/sessions-preview.css +56 -7
- package/web-ui/styles/sessions-toolbar-trash.css +2 -8
- package/web-ui/styles/sessions-usage.css +1 -1
- package/web-ui/styles/settings-panel.css +2 -2
- package/web-ui/styles/theme-dark-tokens.css +83 -0
- package/web-ui/styles/theme-dark.css +1355 -0
- package/web-ui/styles.css +1 -0
package/cli.js
CHANGED
|
@@ -64,6 +64,18 @@ const {
|
|
|
64
64
|
parseMaxMessagesValue,
|
|
65
65
|
resolveMaxMessagesValue
|
|
66
66
|
} = require('./lib/cli-session-utils');
|
|
67
|
+
let DatabaseSync = null;
|
|
68
|
+
function getNodeSqliteDatabaseSync() {
|
|
69
|
+
if (DatabaseSync !== null) {
|
|
70
|
+
return DatabaseSync;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
DatabaseSync = require('node:sqlite').DatabaseSync || false;
|
|
74
|
+
} catch (_) {
|
|
75
|
+
DatabaseSync = false;
|
|
76
|
+
}
|
|
77
|
+
return DatabaseSync;
|
|
78
|
+
}
|
|
67
79
|
const { createMcpStdioServer } = require('./lib/mcp-stdio');
|
|
68
80
|
const {
|
|
69
81
|
validateWorkflowDefinition,
|
|
@@ -212,6 +224,8 @@ const CLAUDE_MD_FILE_NAME = 'CLAUDE.md';
|
|
|
212
224
|
const CLAUDE_PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');
|
|
213
225
|
const CODEBUDDY_DIR = path.join(os.homedir(), '.codebuddy');
|
|
214
226
|
const CODEBUDDY_PROJECTS_DIR = path.join(CODEBUDDY_DIR, 'projects');
|
|
227
|
+
const WORKBUDDY_DIR = path.join(os.homedir(), '.workbuddy-ai');
|
|
228
|
+
const WORKBUDDY_PROJECTS_DIR = path.join(WORKBUDDY_DIR, 'projects');
|
|
215
229
|
const CODEXMATE_DIR = path.join(os.homedir(), '.codexmate');
|
|
216
230
|
const PROVIDER_CACHE_FILE_GROUPS = Object.freeze({
|
|
217
231
|
claude: [
|
|
@@ -247,6 +261,9 @@ const GEMINI_DIR = path.join(os.homedir(), '.gemini');
|
|
|
247
261
|
const GEMINI_TMP_DIR = path.join(GEMINI_DIR, 'tmp');
|
|
248
262
|
const PI_DIR = path.join(os.homedir(), '.pi');
|
|
249
263
|
const PI_SESSIONS_DIR = path.join(PI_DIR, 'agent', 'sessions');
|
|
264
|
+
const OPENCODE_DATA_DIR = path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share'), 'opencode');
|
|
265
|
+
const OPENCODE_DB_VIRTUAL_ROOT = path.join(OPENCODE_DATA_DIR, 'sessions');
|
|
266
|
+
const OPENCODE_DB_READ_LIMIT = 2000;
|
|
250
267
|
const RECENT_CONFIGS_FILE = path.join(CONFIG_DIR, 'recent-configs.json');
|
|
251
268
|
const WORKFLOW_DEFINITIONS_FILE = path.join(CONFIG_DIR, 'codexmate-workflows.json');
|
|
252
269
|
const WORKFLOW_RUNS_FILE = path.join(CONFIG_DIR, 'codexmate-workflow-runs.jsonl');
|
|
@@ -271,6 +288,7 @@ const SESSION_LIST_CACHE_TTL_MS = 4000;
|
|
|
271
288
|
const SESSION_SUMMARY_READ_BYTES = 256 * 1024;
|
|
272
289
|
const SESSION_CONTENT_READ_BYTES = SESSION_SUMMARY_READ_BYTES;
|
|
273
290
|
const SESSION_PREVIEW_MESSAGE_TEXT_MAX_LENGTH = 4000;
|
|
291
|
+
const SESSION_QUERY_MATCH_POSITIONS_LIMIT = 500;
|
|
274
292
|
const EXACT_MESSAGE_COUNT_CACHE_MAX_ENTRIES = 800;
|
|
275
293
|
const DEFAULT_CONTENT_SCAN_LIMIT = 50;
|
|
276
294
|
const SESSION_SCAN_FACTOR = 4;
|
|
@@ -739,6 +757,7 @@ base_url = "http://127.0.0.1:3737/bridge/local/v1"
|
|
|
739
757
|
wire_api = "responses"
|
|
740
758
|
requires_openai_auth = true
|
|
741
759
|
preferred_auth_method = "codexmate"
|
|
760
|
+
experimental_bearer_token = "codexmate"
|
|
742
761
|
codexmate_bridge = "local"
|
|
743
762
|
request_max_retries = 4
|
|
744
763
|
stream_max_retries = 10
|
|
@@ -752,6 +771,7 @@ let g_sessionFileLookupCache = {
|
|
|
752
771
|
claude: new Map(),
|
|
753
772
|
gemini: new Map(),
|
|
754
773
|
codebuddy: new Map(),
|
|
774
|
+
workbuddy: new Map(),
|
|
755
775
|
pi: new Map()
|
|
756
776
|
};
|
|
757
777
|
let g_exactMessageCountCache = new Map();
|
|
@@ -874,17 +894,27 @@ function writeCurrentModels(data) {
|
|
|
874
894
|
fs.writeFileSync(CURRENT_MODELS_FILE, JSON.stringify(data, null, 2), 'utf-8');
|
|
875
895
|
}
|
|
876
896
|
|
|
877
|
-
function
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
897
|
+
function ensureActiveProviderBearerToken(content, providerName, providerSegments = null) {
|
|
898
|
+
// 单文件认证:auth.json 不再写入,激活 provider 块缺 experimental_bearer_token 时
|
|
899
|
+
// 从 preferred_auth_method(内部键仓)惰性补写,保证 codex 仅凭 config.toml 取到密钥。
|
|
900
|
+
const ranges = findProviderSectionRanges(content, providerName, providerSegments);
|
|
901
|
+
if (!ranges.length) return content;
|
|
902
|
+
let updated = content;
|
|
903
|
+
const sorted = [...ranges].sort((a, b) => b.start - a.start);
|
|
904
|
+
for (const range of sorted) {
|
|
905
|
+
const block = updated.slice(range.start, range.end);
|
|
906
|
+
if (/(^|\n)\s*experimental_bearer_token\s*=/.test(block)) continue;
|
|
907
|
+
const prefMatch = block.match(/^(\s*)preferred_auth_method\s*=\s*"((?:\\.|[^"\\\n])*)"\s*(?:#.*)?$/m);
|
|
908
|
+
if (!prefMatch || !prefMatch[2].trim()) continue;
|
|
909
|
+
const insertLine = `${prefMatch[1]}experimental_bearer_token = "${prefMatch[2]}"`;
|
|
910
|
+
const lineEndIndex = block.indexOf('\n', prefMatch.index + prefMatch[0].length);
|
|
911
|
+
const insertAt = lineEndIndex === -1
|
|
912
|
+
? range.start + block.length
|
|
913
|
+
: range.start + lineEndIndex + 1;
|
|
914
|
+
const prefix = lineEndIndex === -1 ? '\n' : '';
|
|
915
|
+
updated = updated.slice(0, insertAt) + prefix + insertLine + '\n' + updated.slice(insertAt);
|
|
885
916
|
}
|
|
886
|
-
|
|
887
|
-
fs.writeFileSync(AUTH_FILE, JSON.stringify(authData, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
917
|
+
return updated;
|
|
888
918
|
}
|
|
889
919
|
|
|
890
920
|
function isPlainObject(value) {
|
|
@@ -1010,9 +1040,13 @@ function normalizeSidebarCollapsedPreference(value) {
|
|
|
1010
1040
|
return normalizeBooleanPreference(value, false);
|
|
1011
1041
|
}
|
|
1012
1042
|
|
|
1043
|
+
function normalizeThemePreference(value) {
|
|
1044
|
+
return value === 'dark' ? 'dark' : 'light';
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1013
1047
|
function normalizeSessionFilterSourcePreference(value) {
|
|
1014
1048
|
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1015
|
-
if (normalized === 'all' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'pi') return normalized;
|
|
1049
|
+
if (normalized === 'all' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'workbuddy' || normalized === 'pi') return normalized;
|
|
1016
1050
|
return 'codex';
|
|
1017
1051
|
}
|
|
1018
1052
|
|
|
@@ -1081,6 +1115,7 @@ function normalizeWebUiPreferences(value = {}) {
|
|
|
1081
1115
|
sysPromptMode: normalizeSysPromptModePreference(source.sysPromptMode),
|
|
1082
1116
|
projectClaudeMdPath: typeof source.projectClaudeMdPath === 'string' ? source.projectClaudeMdPath : '',
|
|
1083
1117
|
sidebarCollapsed: normalizeSidebarCollapsedPreference(source.sidebarCollapsed),
|
|
1118
|
+
theme: normalizeThemePreference(source.theme),
|
|
1084
1119
|
starPrompted: normalizeBooleanPreference(source.starPrompted, false),
|
|
1085
1120
|
sessionLoadNativeDialog: normalizeBooleanPreference(source.sessionLoadNativeDialog, false),
|
|
1086
1121
|
language: typeof source.language === 'string' ? source.language : '',
|
|
@@ -1195,7 +1230,6 @@ function getApiToolConfigWriteTarget(action) {
|
|
|
1195
1230
|
const opencodeWriteActions = new Set([
|
|
1196
1231
|
'apply-opencode-config',
|
|
1197
1232
|
'update-opencode-selection',
|
|
1198
|
-
'apply-opencode-agents-file'
|
|
1199
1233
|
]);
|
|
1200
1234
|
const kilocodeWriteActions = new Set([
|
|
1201
1235
|
'apply-kilocode-config',
|
|
@@ -1531,6 +1565,7 @@ const PROVIDER_CONFIG_KEYS = new Set([
|
|
|
1531
1565
|
'wire_api',
|
|
1532
1566
|
'requires_openai_auth',
|
|
1533
1567
|
'preferred_auth_method',
|
|
1568
|
+
'experimental_bearer_token',
|
|
1534
1569
|
'request_max_retries',
|
|
1535
1570
|
'stream_max_retries',
|
|
1536
1571
|
'stream_idle_timeout_ms'
|
|
@@ -2114,6 +2149,16 @@ function getCodeBuddyProjectsDir() {
|
|
|
2114
2149
|
return resolveExistingDir(candidates, CODEBUDDY_PROJECTS_DIR);
|
|
2115
2150
|
}
|
|
2116
2151
|
|
|
2152
|
+
function getWorkBuddyProjectsDir() {
|
|
2153
|
+
const candidates = [];
|
|
2154
|
+
const envHome = process.env.WORKBUDDY_AI_HOME_DIR || process.env.WORKBUDDY_HOME;
|
|
2155
|
+
if (envHome) {
|
|
2156
|
+
candidates.push(path.join(envHome, 'projects'));
|
|
2157
|
+
}
|
|
2158
|
+
candidates.push(WORKBUDDY_PROJECTS_DIR);
|
|
2159
|
+
return resolveExistingDir(candidates, WORKBUDDY_PROJECTS_DIR);
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2117
2162
|
function getPiSessionsDir() {
|
|
2118
2163
|
const candidates = [];
|
|
2119
2164
|
const envPiHome = process.env.PI_HOME;
|
|
@@ -2128,6 +2173,250 @@ function getPiSessionsDir() {
|
|
|
2128
2173
|
return resolveExistingDir(candidates, PI_SESSIONS_DIR);
|
|
2129
2174
|
}
|
|
2130
2175
|
|
|
2176
|
+
function getOpencodeDataDir() {
|
|
2177
|
+
const candidates = [];
|
|
2178
|
+
const envOpencodeData = process.env.OPENCODE_DATA_HOME || process.env.OPENCODE_DATA_DIR;
|
|
2179
|
+
if (envOpencodeData) {
|
|
2180
|
+
candidates.push(envOpencodeData);
|
|
2181
|
+
}
|
|
2182
|
+
const xdgData = process.env.XDG_DATA_HOME;
|
|
2183
|
+
if (xdgData) {
|
|
2184
|
+
candidates.push(path.join(xdgData, 'opencode'));
|
|
2185
|
+
}
|
|
2186
|
+
candidates.push(OPENCODE_DATA_DIR);
|
|
2187
|
+
return resolveExistingDir(candidates, OPENCODE_DATA_DIR);
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
function getOpencodeDbFile() {
|
|
2191
|
+
const dataDir = getOpencodeDataDir();
|
|
2192
|
+
return path.join(dataDir, 'opencode.db');
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
function buildOpencodeVirtualSessionPath(sessionId) {
|
|
2196
|
+
const id = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
2197
|
+
if (!id) return '';
|
|
2198
|
+
return path.join(OPENCODE_DB_VIRTUAL_ROOT, `${id}.jsonl`);
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
function parseOpencodeVirtualSessionId(filePath) {
|
|
2202
|
+
const raw = typeof filePath === 'string' ? filePath.trim() : '';
|
|
2203
|
+
if (!raw) return '';
|
|
2204
|
+
const base = path.basename(raw, path.extname(raw));
|
|
2205
|
+
return base && base.startsWith('ses_') ? base : '';
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
function isOpencodeDbAvailable() {
|
|
2209
|
+
const Database = getNodeSqliteDatabaseSync();
|
|
2210
|
+
const dbFile = getOpencodeDbFile();
|
|
2211
|
+
return !!(Database && dbFile && fs.existsSync(dbFile));
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
function withOpencodeDb(callback, fallback = null) {
|
|
2215
|
+
const Database = getNodeSqliteDatabaseSync();
|
|
2216
|
+
const dbFile = getOpencodeDbFile();
|
|
2217
|
+
if (!Database || !dbFile || !fs.existsSync(dbFile)) {
|
|
2218
|
+
return fallback;
|
|
2219
|
+
}
|
|
2220
|
+
let db;
|
|
2221
|
+
try {
|
|
2222
|
+
db = new Database(dbFile, { readOnly: true });
|
|
2223
|
+
return callback(db, dbFile);
|
|
2224
|
+
} catch (_) {
|
|
2225
|
+
return fallback;
|
|
2226
|
+
} finally {
|
|
2227
|
+
if (db) {
|
|
2228
|
+
try { db.close(); } catch (_) { }
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
function parseJsonObjectSafe(value) {
|
|
2234
|
+
if (!value || typeof value !== 'string') return null;
|
|
2235
|
+
try {
|
|
2236
|
+
const parsed = JSON.parse(value);
|
|
2237
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
2238
|
+
} catch (_) {
|
|
2239
|
+
return null;
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
function toOpencodeIsoTime(value, fallback = '') {
|
|
2244
|
+
const numeric = Number(value);
|
|
2245
|
+
if (Number.isFinite(numeric) && numeric > 0) {
|
|
2246
|
+
return toIsoTime(numeric, fallback);
|
|
2247
|
+
}
|
|
2248
|
+
return toIsoTime(value, fallback);
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
function readOpencodeModelName(value) {
|
|
2252
|
+
const parsed = typeof value === 'string' ? parseJsonObjectSafe(value) : (value && typeof value === 'object' ? value : null);
|
|
2253
|
+
if (!parsed) return '';
|
|
2254
|
+
return typeof parsed.id === 'string' && parsed.id.trim()
|
|
2255
|
+
? parsed.id.trim()
|
|
2256
|
+
: (typeof parsed.modelID === 'string' ? parsed.modelID.trim() : '');
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
function readOpencodeProviderName(value) {
|
|
2260
|
+
const parsed = typeof value === 'string' ? parseJsonObjectSafe(value) : (value && typeof value === 'object' ? value : null);
|
|
2261
|
+
if (!parsed) return 'opencode';
|
|
2262
|
+
return typeof parsed.providerID === 'string' && parsed.providerID.trim() ? parsed.providerID.trim() : 'opencode';
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
function extractOpencodeTextFromPartData(data) {
|
|
2266
|
+
const part = typeof data === 'string' ? parseJsonObjectSafe(data) : (data && typeof data === 'object' ? data : null);
|
|
2267
|
+
if (!part) return '';
|
|
2268
|
+
const type = typeof part.type === 'string' ? part.type.trim().toLowerCase() : '';
|
|
2269
|
+
if (type !== 'text' && type !== 'reasoning') return '';
|
|
2270
|
+
return typeof part.text === 'string' ? part.text.trim() : '';
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
function normalizeOpencodeMessageRow(row) {
|
|
2274
|
+
if (!row || typeof row !== 'object') return null;
|
|
2275
|
+
const messageData = parseJsonObjectSafe(row.message_data || row.data || '');
|
|
2276
|
+
const role = normalizeRole((messageData && messageData.role) || row.role || '');
|
|
2277
|
+
if (role !== 'user' && role !== 'assistant' && role !== 'system') return null;
|
|
2278
|
+
const rawParts = typeof row.parts === 'string' && row.parts ? row.parts.split('\n') : [];
|
|
2279
|
+
const textParts = rawParts.map(extractOpencodeTextFromPartData).filter(Boolean);
|
|
2280
|
+
const text = textParts.join('\n').trim();
|
|
2281
|
+
if (!text) return null;
|
|
2282
|
+
const model = messageData ? (messageData.modelID || readOpencodeModelName(messageData.model)) : '';
|
|
2283
|
+
const provider = messageData ? (messageData.providerID || readOpencodeProviderName(messageData.model)) : 'opencode';
|
|
2284
|
+
return {
|
|
2285
|
+
role,
|
|
2286
|
+
text,
|
|
2287
|
+
timestamp: toOpencodeIsoTime(row.time_created || (messageData && messageData.time && messageData.time.created), ''),
|
|
2288
|
+
model: typeof model === 'string' ? model : '',
|
|
2289
|
+
provider: typeof provider === 'string' && provider ? provider : 'opencode'
|
|
2290
|
+
};
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
function queryOpencodeSessionMessages(sessionId, options = {}) {
|
|
2294
|
+
const id = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
2295
|
+
if (!id) return null;
|
|
2296
|
+
const maxMessages = resolveMaxMessagesValue(options.maxMessages, DEFAULT_SESSION_DETAIL_MESSAGES);
|
|
2297
|
+
const limit = maxMessages === Infinity ? OPENCODE_DB_READ_LIMIT : Math.min(OPENCODE_DB_READ_LIMIT, Math.max(1, Math.floor(maxMessages)));
|
|
2298
|
+
return withOpencodeDb((db) => {
|
|
2299
|
+
const totalRow = db.prepare('select count(*) as count from message where session_id = ?').get(id) || {};
|
|
2300
|
+
const totalMessages = Math.max(0, Math.floor(Number(totalRow.count) || 0));
|
|
2301
|
+
const rows = db.prepare(`
|
|
2302
|
+
select m.id as message_id, m.session_id, m.time_created, m.time_updated, m.data as message_data,
|
|
2303
|
+
group_concat(p.data, char(10)) as parts
|
|
2304
|
+
from (
|
|
2305
|
+
select * from message where session_id = ? order by time_created desc limit ?
|
|
2306
|
+
) m
|
|
2307
|
+
left join part p on p.message_id = m.id
|
|
2308
|
+
group by m.id
|
|
2309
|
+
order by m.time_created asc
|
|
2310
|
+
`).all(id, limit);
|
|
2311
|
+
const messages = rows.map(normalizeOpencodeMessageRow).filter(Boolean);
|
|
2312
|
+
const sessionRow = db.prepare('select id, directory, path, time_updated from session where id = ?').get(id) || null;
|
|
2313
|
+
return {
|
|
2314
|
+
sessionId: id,
|
|
2315
|
+
cwd: sessionRow && (sessionRow.path || sessionRow.directory) ? (sessionRow.path || sessionRow.directory) : '',
|
|
2316
|
+
updatedAt: sessionRow ? toOpencodeIsoTime(sessionRow.time_updated, '') : '',
|
|
2317
|
+
messages,
|
|
2318
|
+
totalMessages,
|
|
2319
|
+
clipped: maxMessages !== Infinity && totalMessages > messages.length,
|
|
2320
|
+
truncated: maxMessages !== Infinity && totalMessages > messages.length
|
|
2321
|
+
};
|
|
2322
|
+
}, null);
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
function countOpencodeMessages(sessionId) {
|
|
2326
|
+
const id = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
2327
|
+
if (!id) return null;
|
|
2328
|
+
return withOpencodeDb((db) => {
|
|
2329
|
+
const row = db.prepare('select count(*) as count from message where session_id = ?').get(id) || {};
|
|
2330
|
+
return Math.max(0, Math.floor(Number(row.count) || 0));
|
|
2331
|
+
}, null);
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
function parseOpencodeSessionSummaryRow(row, db) {
|
|
2335
|
+
if (!row || typeof row !== 'object') return null;
|
|
2336
|
+
const sessionId = typeof row.id === 'string' ? row.id.trim() : '';
|
|
2337
|
+
if (!sessionId) return null;
|
|
2338
|
+
const model = readOpencodeModelName(row.model);
|
|
2339
|
+
const provider = readOpencodeProviderName(row.model);
|
|
2340
|
+
let title = typeof row.title === 'string' && row.title.trim() ? row.title.trim() : (row.slug || sessionId);
|
|
2341
|
+
const firstRow = db.prepare(`
|
|
2342
|
+
select m.data as message_data, group_concat(p.data, char(10)) as parts
|
|
2343
|
+
from message m left join part p on p.message_id = m.id
|
|
2344
|
+
where m.session_id = ?
|
|
2345
|
+
group by m.id
|
|
2346
|
+
order by m.time_created asc
|
|
2347
|
+
limit 12
|
|
2348
|
+
`).all(sessionId).map(normalizeOpencodeMessageRow).filter(Boolean).find(item => item.role === 'user' && item.text);
|
|
2349
|
+
if (firstRow && firstRow.text) {
|
|
2350
|
+
title = truncateText(firstRow.text, 120);
|
|
2351
|
+
}
|
|
2352
|
+
const messageCount = Math.max(0, Math.floor(Number(row.message_count) || 0));
|
|
2353
|
+
const inputTokens = readNonNegativeInteger(row.tokens_input) || 0;
|
|
2354
|
+
const outputTokens = readNonNegativeInteger(row.tokens_output) || 0;
|
|
2355
|
+
const reasoningOutputTokens = readNonNegativeInteger(row.tokens_reasoning) || 0;
|
|
2356
|
+
const cachedInputTokens = readNonNegativeInteger(row.tokens_cache_read) || 0;
|
|
2357
|
+
const cacheCreationInputTokens = readNonNegativeInteger(row.tokens_cache_write) || 0;
|
|
2358
|
+
const totalTokens = inputTokens + outputTokens + reasoningOutputTokens + cachedInputTokens + cacheCreationInputTokens;
|
|
2359
|
+
return {
|
|
2360
|
+
source: 'opencode',
|
|
2361
|
+
sourceLabel: 'OpenCode',
|
|
2362
|
+
provider,
|
|
2363
|
+
model,
|
|
2364
|
+
models: model ? [model] : [],
|
|
2365
|
+
sessionId,
|
|
2366
|
+
title,
|
|
2367
|
+
cwd: row.path || row.directory || '',
|
|
2368
|
+
createdAt: toOpencodeIsoTime(row.time_created, ''),
|
|
2369
|
+
updatedAt: toOpencodeIsoTime(row.time_updated, ''),
|
|
2370
|
+
messageCount,
|
|
2371
|
+
totalTokens,
|
|
2372
|
+
contextWindow: 0,
|
|
2373
|
+
inputTokens,
|
|
2374
|
+
cachedInputTokens,
|
|
2375
|
+
cacheCreationInputTokens,
|
|
2376
|
+
outputTokens,
|
|
2377
|
+
reasoningOutputTokens,
|
|
2378
|
+
__messageCountExact: true,
|
|
2379
|
+
filePath: buildOpencodeVirtualSessionPath(sessionId),
|
|
2380
|
+
keywords: [],
|
|
2381
|
+
capabilities: { code: true }
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
function parseOpencodeSessionSummary(sessionId) {
|
|
2386
|
+
const id = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
2387
|
+
if (!id) return null;
|
|
2388
|
+
return withOpencodeDb((db) => {
|
|
2389
|
+
const row = db.prepare(`
|
|
2390
|
+
select s.*, (select count(*) from message m where m.session_id = s.id) as message_count
|
|
2391
|
+
from session s where s.id = ?
|
|
2392
|
+
`).get(id);
|
|
2393
|
+
return parseOpencodeSessionSummaryRow(row, db);
|
|
2394
|
+
}, null);
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
function listOpencodeSessions(limit, options = {}) {
|
|
2398
|
+
if (!isOpencodeDbAvailable()) return [];
|
|
2399
|
+
const scanFactor = Number.isFinite(Number(options.scanFactor)) ? Math.max(1, Number(options.scanFactor)) : SESSION_SCAN_FACTOR;
|
|
2400
|
+
const targetCount = Number.isFinite(Number(options.targetCount)) ? Math.max(1, Math.floor(Number(options.targetCount))) : Math.max(1, Math.floor(limit * scanFactor));
|
|
2401
|
+
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));
|
|
2402
|
+
return withOpencodeDb((db) => {
|
|
2403
|
+
const rows = db.prepare(`
|
|
2404
|
+
select s.*, (select count(*) from message m where m.session_id = s.id) as message_count
|
|
2405
|
+
from session s
|
|
2406
|
+
where s.time_archived is null
|
|
2407
|
+
order by s.time_updated desc
|
|
2408
|
+
limit ?
|
|
2409
|
+
`).all(Math.min(OPENCODE_DB_READ_LIMIT, Math.max(scanCount, targetCount)));
|
|
2410
|
+
const sessions = [];
|
|
2411
|
+
for (const row of rows) {
|
|
2412
|
+
const summary = parseOpencodeSessionSummaryRow(row, db);
|
|
2413
|
+
if (summary) sessions.push(summary);
|
|
2414
|
+
if (sessions.length >= targetCount) break;
|
|
2415
|
+
}
|
|
2416
|
+
return mergeAndLimitSessions(sessions, limit);
|
|
2417
|
+
}, []);
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2131
2420
|
function getCodexmateDerivedSessionsRoot(target) {
|
|
2132
2421
|
if (target === 'claude') {
|
|
2133
2422
|
return CODEXMATE_DERIVED_CLAUDE_DIR;
|
|
@@ -2481,7 +2770,6 @@ const {
|
|
|
2481
2770
|
writeModels,
|
|
2482
2771
|
readCurrentModels,
|
|
2483
2772
|
writeCurrentModels,
|
|
2484
|
-
updateAuthJson,
|
|
2485
2773
|
CONFIG_DIR,
|
|
2486
2774
|
CONFIG_FILE,
|
|
2487
2775
|
AUTH_FILE,
|
|
@@ -2799,8 +3087,8 @@ function applyConfigTemplate(params = {}) {
|
|
|
2799
3087
|
return { error: `模板中找不到当前 provider: ${activeProvider}` };
|
|
2800
3088
|
}
|
|
2801
3089
|
|
|
2802
|
-
|
|
2803
|
-
|
|
3090
|
+
const ensuredTemplate = ensureActiveProviderBearerToken(template.trim(), activeProvider);
|
|
3091
|
+
writeConfig(ensuredTemplate + '\n');
|
|
2804
3092
|
|
|
2805
3093
|
const models = readModels();
|
|
2806
3094
|
if (!models.includes(parsed.model)) {
|
|
@@ -3038,6 +3326,7 @@ function addProviderToConfig(params = {}) {
|
|
|
3038
3326
|
`wire_api = "responses"`,
|
|
3039
3327
|
`requires_openai_auth = true`,
|
|
3040
3328
|
`preferred_auth_method = "${safeKey}"`,
|
|
3329
|
+
`experimental_bearer_token = "${safeKey}"`,
|
|
3041
3330
|
`models = [{ id = "${escapeTomlBasicString(model)}", name = "${escapeTomlBasicString(model)}" }]`,
|
|
3042
3331
|
...extraLines,
|
|
3043
3332
|
`request_max_retries = 4`,
|
|
@@ -4084,7 +4373,7 @@ function countConversationMessagesInRecords(records, source) {
|
|
|
4084
4373
|
}
|
|
4085
4374
|
continue;
|
|
4086
4375
|
}
|
|
4087
|
-
if (source === 'codebuddy') {
|
|
4376
|
+
if (source === 'codebuddy' || source === 'workbuddy') {
|
|
4088
4377
|
if (record && record.type === 'message') {
|
|
4089
4378
|
const role = normalizeRole(record.role);
|
|
4090
4379
|
if (role === 'assistant' || role === 'user' || role === 'system') {
|
|
@@ -4167,7 +4456,7 @@ async function countConversationMessagesInFile(filePath, source) {
|
|
|
4167
4456
|
role = normalizeRole(record.payload.role);
|
|
4168
4457
|
text = extractMessageText(record.payload.content);
|
|
4169
4458
|
}
|
|
4170
|
-
} else if (source === 'codebuddy') {
|
|
4459
|
+
} else if (source === 'codebuddy' || source === 'workbuddy') {
|
|
4171
4460
|
if (record && record.type === 'message') {
|
|
4172
4461
|
role = normalizeRole(record.role);
|
|
4173
4462
|
if (role === 'assistant' || role === 'user' || role === 'system') {
|
|
@@ -4347,15 +4636,7 @@ async function resolveSessionTrashEntryExactMessageCount(entry) {
|
|
|
4347
4636
|
}
|
|
4348
4637
|
|
|
4349
4638
|
async function hydrateSessionTrashEntries(entries, options = {}) {
|
|
4350
|
-
const source = options.source
|
|
4351
|
-
? 'claude'
|
|
4352
|
-
: (options.source === 'codex'
|
|
4353
|
-
? 'codex'
|
|
4354
|
-
: (options.source === 'gemini'
|
|
4355
|
-
? 'gemini'
|
|
4356
|
-
: (options.source === 'codebuddy'
|
|
4357
|
-
? 'codebuddy'
|
|
4358
|
-
: (options.source === 'pi' ? 'pi' : 'all'))));
|
|
4639
|
+
const source = normalizeSessionSourceName(options.source, '') || 'all';
|
|
4359
4640
|
const hydratedEntries = await mapWithConcurrency(Array.isArray(entries) ? entries : [], 8, async (entry) => {
|
|
4360
4641
|
const normalizedEntry = normalizeSessionTrashEntry(entry);
|
|
4361
4642
|
if (!normalizedEntry) {
|
|
@@ -4364,7 +4645,7 @@ async function hydrateSessionTrashEntries(entries, options = {}) {
|
|
|
4364
4645
|
return await resolveSessionTrashEntryExactMessageCount(normalizedEntry);
|
|
4365
4646
|
});
|
|
4366
4647
|
|
|
4367
|
-
if (source
|
|
4648
|
+
if (normalizeSessionSourceName(source, '')) {
|
|
4368
4649
|
return hydratedEntries.filter((entry) => entry.source === source);
|
|
4369
4650
|
}
|
|
4370
4651
|
return hydratedEntries;
|
|
@@ -4378,15 +4659,7 @@ async function hydrateSessionItemsExactMessageCount(items) {
|
|
|
4378
4659
|
if (item.__messageCountExact === true) {
|
|
4379
4660
|
return item;
|
|
4380
4661
|
}
|
|
4381
|
-
const source = item.source
|
|
4382
|
-
? 'claude'
|
|
4383
|
-
: (item.source === 'codex'
|
|
4384
|
-
? 'codex'
|
|
4385
|
-
: (item.source === 'gemini'
|
|
4386
|
-
? 'gemini'
|
|
4387
|
-
: (item.source === 'codebuddy'
|
|
4388
|
-
? 'codebuddy'
|
|
4389
|
-
: (item.source === 'pi' ? 'pi' : ''))));
|
|
4662
|
+
const source = normalizeSessionSourceName(item.source, '');
|
|
4390
4663
|
const filePath = typeof item.filePath === 'string' ? item.filePath : '';
|
|
4391
4664
|
if (!source || !filePath || !fs.existsSync(filePath)) {
|
|
4392
4665
|
return item;
|
|
@@ -4413,6 +4686,40 @@ async function hydrateSessionItemsExactMessageCount(items) {
|
|
|
4413
4686
|
});
|
|
4414
4687
|
}
|
|
4415
4688
|
|
|
4689
|
+
|
|
4690
|
+
function normalizeSessionSourceName(value, fallback = '') {
|
|
4691
|
+
const source = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
4692
|
+
if (source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'pi' || source === 'opencode') {
|
|
4693
|
+
return source;
|
|
4694
|
+
}
|
|
4695
|
+
return fallback;
|
|
4696
|
+
}
|
|
4697
|
+
|
|
4698
|
+
function isSessionSourceOrAll(value) {
|
|
4699
|
+
const source = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
4700
|
+
return source === 'all' || !!normalizeSessionSourceName(source, '');
|
|
4701
|
+
}
|
|
4702
|
+
|
|
4703
|
+
function getSessionSourceLabel(source) {
|
|
4704
|
+
if (source === 'claude') return 'Claude Code';
|
|
4705
|
+
if (source === 'gemini') return 'Gemini CLI';
|
|
4706
|
+
if (source === 'codebuddy') return 'CodeBuddy Code';
|
|
4707
|
+
if (source === 'workbuddy') return 'WorkBuddy AI';
|
|
4708
|
+
if (source === 'pi') return 'Pi';
|
|
4709
|
+
if (source === 'opencode') return 'OpenCode';
|
|
4710
|
+
return 'Codex';
|
|
4711
|
+
}
|
|
4712
|
+
|
|
4713
|
+
function getSessionSourceProvider(source) {
|
|
4714
|
+
if (source === 'claude') return 'claude';
|
|
4715
|
+
if (source === 'gemini') return 'gemini';
|
|
4716
|
+
if (source === 'codebuddy') return 'codebuddy';
|
|
4717
|
+
if (source === 'workbuddy') return 'workbuddy';
|
|
4718
|
+
if (source === 'pi') return 'pi';
|
|
4719
|
+
if (source === 'opencode') return 'opencode';
|
|
4720
|
+
return 'codex';
|
|
4721
|
+
}
|
|
4722
|
+
|
|
4416
4723
|
function getSessionExportKeyForApi(item) {
|
|
4417
4724
|
const source = item && item.source ? String(item.source).trim() : '';
|
|
4418
4725
|
const sessionId = item && item.sessionId ? String(item.sessionId) : '';
|
|
@@ -4430,20 +4737,21 @@ async function readSessionMessageCounts(params = {}) {
|
|
|
4430
4737
|
return undefined;
|
|
4431
4738
|
}
|
|
4432
4739
|
const key = getSessionExportKeyForApi(item);
|
|
4433
|
-
const source = item.source
|
|
4434
|
-
? 'claude'
|
|
4435
|
-
: (item.source === 'codex'
|
|
4436
|
-
? 'codex'
|
|
4437
|
-
: (item.source === 'gemini'
|
|
4438
|
-
? 'gemini'
|
|
4439
|
-
: (item.source === 'codebuddy'
|
|
4440
|
-
? 'codebuddy'
|
|
4441
|
-
: (item.source === 'pi' ? 'pi' : ''))));
|
|
4740
|
+
const source = normalizeSessionSourceName(item.source, '');
|
|
4442
4741
|
const filePath = typeof item.filePath === 'string' ? item.filePath : '';
|
|
4443
|
-
if (!source
|
|
4742
|
+
if (!source) {
|
|
4444
4743
|
return { key };
|
|
4445
4744
|
}
|
|
4446
|
-
|
|
4745
|
+
let exactMessageCount;
|
|
4746
|
+
if (source === 'opencode') {
|
|
4747
|
+
const opencodeSessionId = item.sessionId || parseOpencodeVirtualSessionId(filePath);
|
|
4748
|
+
exactMessageCount = countOpencodeMessages(opencodeSessionId);
|
|
4749
|
+
} else {
|
|
4750
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
4751
|
+
return { key };
|
|
4752
|
+
}
|
|
4753
|
+
exactMessageCount = await countConversationMessagesInFile(filePath, source);
|
|
4754
|
+
}
|
|
4447
4755
|
if (!Number.isFinite(Number(exactMessageCount))) {
|
|
4448
4756
|
return { key };
|
|
4449
4757
|
}
|
|
@@ -4573,6 +4881,10 @@ function createSessionQueryScanState(tokens, options = {}) {
|
|
|
4573
4881
|
const snippetLimit = Number.isFinite(Number(options.snippetLimit))
|
|
4574
4882
|
? Math.max(0, Number(options.snippetLimit))
|
|
4575
4883
|
: 0;
|
|
4884
|
+
const positionsLimitRaw = Number(options.positionsLimit);
|
|
4885
|
+
const positionsLimit = Number.isFinite(positionsLimitRaw) && positionsLimitRaw > 0
|
|
4886
|
+
? Math.floor(positionsLimitRaw)
|
|
4887
|
+
: 0;
|
|
4576
4888
|
|
|
4577
4889
|
return {
|
|
4578
4890
|
tokens,
|
|
@@ -4580,8 +4892,10 @@ function createSessionQueryScanState(tokens, options = {}) {
|
|
|
4580
4892
|
roleFilter,
|
|
4581
4893
|
maxMatches,
|
|
4582
4894
|
snippetLimit,
|
|
4895
|
+
positionsLimit,
|
|
4583
4896
|
count: 0,
|
|
4584
4897
|
snippets: [],
|
|
4898
|
+
positions: [],
|
|
4585
4899
|
leadingSystem: roleFilter !== 'system'
|
|
4586
4900
|
};
|
|
4587
4901
|
}
|
|
@@ -4613,6 +4927,13 @@ function consumeSessionQueryMessage(state, message) {
|
|
|
4613
4927
|
if (state.snippetLimit > 0 && state.snippets.length < state.snippetLimit) {
|
|
4614
4928
|
state.snippets.push(truncateText(text));
|
|
4615
4929
|
}
|
|
4930
|
+
if (state.positionsLimit > 0 && state.positions.length < state.positionsLimit) {
|
|
4931
|
+
state.positions.push({
|
|
4932
|
+
lineIndex: Number.isInteger(message.recordLineIndex) ? message.recordLineIndex : -1,
|
|
4933
|
+
role,
|
|
4934
|
+
timestamp: typeof message.timestamp === 'string' ? message.timestamp : ''
|
|
4935
|
+
});
|
|
4936
|
+
}
|
|
4616
4937
|
return state.count >= state.maxMatches;
|
|
4617
4938
|
}
|
|
4618
4939
|
|
|
@@ -4620,7 +4941,8 @@ function buildSessionQueryScanResult(state) {
|
|
|
4620
4941
|
return {
|
|
4621
4942
|
hit: !!(state && state.count > 0),
|
|
4622
4943
|
count: state && Number.isFinite(state.count) ? state.count : 0,
|
|
4623
|
-
snippets: state && Array.isArray(state.snippets) ? state.snippets : []
|
|
4944
|
+
snippets: state && Array.isArray(state.snippets) ? state.snippets : [],
|
|
4945
|
+
positions: state && Array.isArray(state.positions) ? state.positions : []
|
|
4624
4946
|
};
|
|
4625
4947
|
}
|
|
4626
4948
|
|
|
@@ -4628,12 +4950,14 @@ function scanSessionContentForQueryInRecords(records, source, state) {
|
|
|
4628
4950
|
if (!Array.isArray(records) || !state) {
|
|
4629
4951
|
return buildSessionQueryScanResult(state);
|
|
4630
4952
|
}
|
|
4631
|
-
|
|
4632
|
-
|
|
4953
|
+
for (let recordIndex = 0; recordIndex < records.length; recordIndex++) {
|
|
4954
|
+
const record = records[recordIndex];
|
|
4633
4955
|
const message = extractMessageFromRecord(record, source);
|
|
4634
4956
|
if (!message) {
|
|
4635
4957
|
continue;
|
|
4636
4958
|
}
|
|
4959
|
+
message.recordLineIndex = recordIndex;
|
|
4960
|
+
message.timestamp = toIsoTime(record && record.timestamp, '');
|
|
4637
4961
|
if (consumeSessionQueryMessage(state, message)) {
|
|
4638
4962
|
break;
|
|
4639
4963
|
}
|
|
@@ -4644,12 +4968,12 @@ function scanSessionContentForQueryInRecords(records, source, state) {
|
|
|
4644
4968
|
|
|
4645
4969
|
async function scanSessionContentForQuery(session, tokens, options = {}) {
|
|
4646
4970
|
if (!session || !Array.isArray(tokens) || tokens.length === 0) {
|
|
4647
|
-
return { hit: false, count: 0, snippets: [] };
|
|
4971
|
+
return { hit: false, count: 0, snippets: [], positions: [] };
|
|
4648
4972
|
}
|
|
4649
4973
|
|
|
4650
4974
|
const filePath = resolveSessionFilePath(session.source, session.filePath, session.sessionId);
|
|
4651
4975
|
if (!filePath) {
|
|
4652
|
-
return { hit: false, count: 0, snippets: [] };
|
|
4976
|
+
return { hit: false, count: 0, snippets: [], positions: [] };
|
|
4653
4977
|
}
|
|
4654
4978
|
|
|
4655
4979
|
const rawMaxBytes = Number(options.maxBytes);
|
|
@@ -4657,8 +4981,17 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
|
|
|
4657
4981
|
? Math.max(1024, rawMaxBytes)
|
|
4658
4982
|
: 0;
|
|
4659
4983
|
const state = createSessionQueryScanState(tokens, options);
|
|
4984
|
+
if (session.source === 'opencode') {
|
|
4985
|
+
const extracted = queryOpencodeSessionMessages(session.sessionId || parseOpencodeVirtualSessionId(filePath), { maxMessages: Infinity });
|
|
4986
|
+
const messages = extracted && Array.isArray(extracted.messages) ? extracted.messages : [];
|
|
4987
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
4988
|
+
const message = { ...messages[i], recordLineIndex: i };
|
|
4989
|
+
if (consumeSessionQueryMessage(state, message)) break;
|
|
4990
|
+
}
|
|
4991
|
+
return buildSessionQueryScanResult(state);
|
|
4992
|
+
}
|
|
4660
4993
|
if (session.source === 'gemini') {
|
|
4661
|
-
if (state.roleFilter !== 'all') {
|
|
4994
|
+
if (state.roleFilter !== 'all' || state.positionsLimit > 0) {
|
|
4662
4995
|
let json;
|
|
4663
4996
|
try {
|
|
4664
4997
|
json = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
@@ -4672,7 +5005,12 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
|
|
|
4672
5005
|
if (!role) continue;
|
|
4673
5006
|
const text = extractMessageText(extractGeminiMessageText(entry.content ?? entry.message ?? entry.text));
|
|
4674
5007
|
if (!text) continue;
|
|
4675
|
-
if (consumeSessionQueryMessage(state, {
|
|
5008
|
+
if (consumeSessionQueryMessage(state, {
|
|
5009
|
+
role,
|
|
5010
|
+
text,
|
|
5011
|
+
timestamp: toIsoTime(entry.timestamp ?? entry.time ?? entry.at, ''),
|
|
5012
|
+
recordLineIndex: -1
|
|
5013
|
+
})) {
|
|
4676
5014
|
break;
|
|
4677
5015
|
}
|
|
4678
5016
|
}
|
|
@@ -4712,7 +5050,10 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
|
|
|
4712
5050
|
rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
4713
5051
|
|
|
4714
5052
|
let bytesRead = 0;
|
|
5053
|
+
let lineIndex = 0;
|
|
4715
5054
|
for await (const line of rl) {
|
|
5055
|
+
const currentLineIndex = lineIndex;
|
|
5056
|
+
lineIndex += 1;
|
|
4716
5057
|
if (maxBytes > 0 && bytesRead >= maxBytes) {
|
|
4717
5058
|
break;
|
|
4718
5059
|
}
|
|
@@ -4734,6 +5075,8 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
|
|
|
4734
5075
|
if (!message) {
|
|
4735
5076
|
continue;
|
|
4736
5077
|
}
|
|
5078
|
+
message.recordLineIndex = currentLineIndex;
|
|
5079
|
+
message.timestamp = toIsoTime(record.timestamp, '');
|
|
4737
5080
|
if (consumeSessionQueryMessage(state, message)) {
|
|
4738
5081
|
break;
|
|
4739
5082
|
}
|
|
@@ -4952,13 +5295,7 @@ function setSessionListCache(cacheKey, value) {
|
|
|
4952
5295
|
}
|
|
4953
5296
|
|
|
4954
5297
|
function buildSessionInventoryCacheKey(source, limit, options = {}) {
|
|
4955
|
-
const normalizedSource = source
|
|
4956
|
-
? 'claude'
|
|
4957
|
-
: (source === 'gemini'
|
|
4958
|
-
? 'gemini'
|
|
4959
|
-
: (source === 'codebuddy'
|
|
4960
|
-
? 'codebuddy'
|
|
4961
|
-
: (source === 'pi' ? 'pi' : 'codex')));
|
|
5298
|
+
const normalizedSource = normalizeSessionSourceName(source, 'codex');
|
|
4962
5299
|
const normalizedLimit = Number.isFinite(Number(limit))
|
|
4963
5300
|
? Math.max(1, Math.floor(Number(limit)))
|
|
4964
5301
|
: 1;
|
|
@@ -5044,7 +5381,7 @@ function getSessionInventoryCache(cacheKey, forceRefresh = false) {
|
|
|
5044
5381
|
}
|
|
5045
5382
|
|
|
5046
5383
|
function registerSessionFileLookupEntries(source, sessions = []) {
|
|
5047
|
-
const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi'
|
|
5384
|
+
const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'pi' || source === 'opencode'
|
|
5048
5385
|
? source
|
|
5049
5386
|
: 'codex';
|
|
5050
5387
|
const store = g_sessionFileLookupCache[normalizedSource];
|
|
@@ -5085,7 +5422,7 @@ function setSessionInventoryCache(cacheKey, source, value) {
|
|
|
5085
5422
|
}
|
|
5086
5423
|
|
|
5087
5424
|
function listSessionInventoryBySource(source, limit, scanOptions = {}, options = {}) {
|
|
5088
|
-
const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi'
|
|
5425
|
+
const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'pi' || source === 'opencode'
|
|
5089
5426
|
? source
|
|
5090
5427
|
: 'codex';
|
|
5091
5428
|
const forceRefresh = !!options.forceRefresh;
|
|
@@ -5101,9 +5438,13 @@ function listSessionInventoryBySource(source, limit, scanOptions = {}, options =
|
|
|
5101
5438
|
? listGeminiSessions(limit, scanOptions)
|
|
5102
5439
|
: (normalizedSource === 'codebuddy'
|
|
5103
5440
|
? listCodeBuddySessions(limit, scanOptions)
|
|
5104
|
-
: (normalizedSource === '
|
|
5105
|
-
?
|
|
5106
|
-
:
|
|
5441
|
+
: (normalizedSource === 'workbuddy'
|
|
5442
|
+
? listWorkBuddySessions(limit, scanOptions)
|
|
5443
|
+
: (normalizedSource === 'pi'
|
|
5444
|
+
? listPiSessions(limit, scanOptions)
|
|
5445
|
+
: (normalizedSource === 'opencode'
|
|
5446
|
+
? listOpencodeSessions(limit, scanOptions)
|
|
5447
|
+
: listCodexSessions(limit, scanOptions))))));
|
|
5107
5448
|
setSessionInventoryCache(cacheKey, normalizedSource, sessions);
|
|
5108
5449
|
return sessions;
|
|
5109
5450
|
}
|
|
@@ -5116,7 +5457,9 @@ function invalidateSessionListCache() {
|
|
|
5116
5457
|
claude: new Map(),
|
|
5117
5458
|
gemini: new Map(),
|
|
5118
5459
|
codebuddy: new Map(),
|
|
5119
|
-
|
|
5460
|
+
workbuddy: new Map(),
|
|
5461
|
+
pi: new Map(),
|
|
5462
|
+
opencode: new Map()
|
|
5120
5463
|
};
|
|
5121
5464
|
}
|
|
5122
5465
|
|
|
@@ -5350,13 +5693,7 @@ function readSessionProviderFromRecord(record, source = '') {
|
|
|
5350
5693
|
if (provider) {
|
|
5351
5694
|
return provider;
|
|
5352
5695
|
}
|
|
5353
|
-
return source
|
|
5354
|
-
? 'claude'
|
|
5355
|
-
: (source === 'gemini'
|
|
5356
|
-
? 'gemini'
|
|
5357
|
-
: (source === 'codebuddy'
|
|
5358
|
-
? 'codebuddy'
|
|
5359
|
-
: (source === 'pi' ? 'pi' : 'codex')));
|
|
5696
|
+
return getSessionSourceProvider(source);
|
|
5360
5697
|
}
|
|
5361
5698
|
|
|
5362
5699
|
function applySessionUsageSummaryFromRecord(state, record, source) {
|
|
@@ -5901,6 +6238,175 @@ function parseCodeBuddySessionSummary(filePath, options = {}) {
|
|
|
5901
6238
|
};
|
|
5902
6239
|
}
|
|
5903
6240
|
|
|
6241
|
+
// WorkBuddy AI 会话记录与 CodeBuddy 同族(type:"message" + 顶层 role/content,
|
|
6242
|
+
// 且存在 __codebuddyLocal 字段),标题来自 ai-title 记录,模型名位于 providerData.model,
|
|
6243
|
+
// token 用量位于嵌套 message.usage。
|
|
6244
|
+
function parseWorkBuddySessionSummary(filePath, options = {}) {
|
|
6245
|
+
const summaryReadBytes = Number.isFinite(Number(options.summaryReadBytes))
|
|
6246
|
+
? Math.max(1024, Math.floor(Number(options.summaryReadBytes)))
|
|
6247
|
+
: SESSION_SUMMARY_READ_BYTES;
|
|
6248
|
+
const titleReadBytes = Number.isFinite(Number(options.titleReadBytes))
|
|
6249
|
+
? Math.max(1024, Math.floor(Number(options.titleReadBytes)))
|
|
6250
|
+
: SESSION_TITLE_READ_BYTES;
|
|
6251
|
+
const records = parseJsonlHeadRecords(filePath, summaryReadBytes);
|
|
6252
|
+
if (records.length === 0) {
|
|
6253
|
+
return null;
|
|
6254
|
+
}
|
|
6255
|
+
|
|
6256
|
+
let stat;
|
|
6257
|
+
try {
|
|
6258
|
+
stat = fs.statSync(filePath);
|
|
6259
|
+
} catch (_) {
|
|
6260
|
+
return null;
|
|
6261
|
+
}
|
|
6262
|
+
|
|
6263
|
+
let sessionId = path.basename(filePath, '.jsonl');
|
|
6264
|
+
let cwd = '';
|
|
6265
|
+
let firstPrompt = '';
|
|
6266
|
+
let aiTitle = '';
|
|
6267
|
+
let messageCount = 0;
|
|
6268
|
+
let totalTokens = 0;
|
|
6269
|
+
let contextWindow = 0;
|
|
6270
|
+
let inputTokens = 0;
|
|
6271
|
+
let cachedInputTokens = 0;
|
|
6272
|
+
let cacheCreationInputTokens = 0;
|
|
6273
|
+
let outputTokens = 0;
|
|
6274
|
+
let reasoningOutputTokens = 0;
|
|
6275
|
+
let provider = 'workbuddy';
|
|
6276
|
+
let model = '';
|
|
6277
|
+
const models = [];
|
|
6278
|
+
const usageState = { totalTokens, contextWindow, inputTokens, cachedInputTokens, cacheCreationInputTokens, outputTokens, reasoningOutputTokens };
|
|
6279
|
+
const previewMessages = [];
|
|
6280
|
+
let createdAt = '';
|
|
6281
|
+
let updatedAt = stat.mtime.toISOString();
|
|
6282
|
+
|
|
6283
|
+
for (const record of records) {
|
|
6284
|
+
if (!createdAt && record && record.timestamp) {
|
|
6285
|
+
createdAt = toIsoTime(record.timestamp, createdAt);
|
|
6286
|
+
}
|
|
6287
|
+
if (record && record.timestamp) {
|
|
6288
|
+
updatedAt = updateLatestIso(updatedAt, record.timestamp);
|
|
6289
|
+
}
|
|
6290
|
+
|
|
6291
|
+
applySessionUsageSummaryFromRecord(usageState, record, 'workbuddy');
|
|
6292
|
+
totalTokens = usageState.totalTokens || 0;
|
|
6293
|
+
contextWindow = usageState.contextWindow || 0;
|
|
6294
|
+
inputTokens = usageState.inputTokens || 0;
|
|
6295
|
+
cachedInputTokens = usageState.cachedInputTokens || 0;
|
|
6296
|
+
cacheCreationInputTokens = usageState.cacheCreationInputTokens || 0;
|
|
6297
|
+
outputTokens = usageState.outputTokens || 0;
|
|
6298
|
+
reasoningOutputTokens = usageState.reasoningOutputTokens || 0;
|
|
6299
|
+
|
|
6300
|
+
if (record && typeof record.sessionId === 'string' && record.sessionId.trim()) {
|
|
6301
|
+
sessionId = record.sessionId.trim();
|
|
6302
|
+
}
|
|
6303
|
+
if (!cwd && record && typeof record.cwd === 'string' && record.cwd.trim()) {
|
|
6304
|
+
cwd = record.cwd.trim();
|
|
6305
|
+
}
|
|
6306
|
+
|
|
6307
|
+
provider = readExplicitSessionProviderFromRecord(record) || provider;
|
|
6308
|
+
const recordModels = readSessionModelsFromRecord(record);
|
|
6309
|
+
for (const recordModel of recordModels) {
|
|
6310
|
+
if (!models.includes(recordModel)) {
|
|
6311
|
+
models.push(recordModel);
|
|
6312
|
+
}
|
|
6313
|
+
}
|
|
6314
|
+
model = recordModels[0] || model;
|
|
6315
|
+
|
|
6316
|
+
if (record && record.type === 'ai-title' && typeof record.aiTitle === 'string' && record.aiTitle.trim()) {
|
|
6317
|
+
aiTitle = record.aiTitle.trim();
|
|
6318
|
+
}
|
|
6319
|
+
|
|
6320
|
+
if (record && record.type === 'message') {
|
|
6321
|
+
const role = normalizeRole(record.role);
|
|
6322
|
+
if (role === 'assistant' || role === 'user' || role === 'system') {
|
|
6323
|
+
const content = record.message?.content ?? record.content ?? '';
|
|
6324
|
+
previewMessages.push({
|
|
6325
|
+
role,
|
|
6326
|
+
text: extractMessageText(content)
|
|
6327
|
+
});
|
|
6328
|
+
}
|
|
6329
|
+
}
|
|
6330
|
+
}
|
|
6331
|
+
|
|
6332
|
+
const tailRecords = parseJsonlTailRecords(filePath, summaryReadBytes);
|
|
6333
|
+
for (const record of tailRecords) {
|
|
6334
|
+
applySessionUsageSummaryFromRecord(usageState, record, 'workbuddy');
|
|
6335
|
+
totalTokens = usageState.totalTokens || 0;
|
|
6336
|
+
contextWindow = usageState.contextWindow || 0;
|
|
6337
|
+
inputTokens = usageState.inputTokens || 0;
|
|
6338
|
+
cachedInputTokens = usageState.cachedInputTokens || 0;
|
|
6339
|
+
cacheCreationInputTokens = usageState.cacheCreationInputTokens || 0;
|
|
6340
|
+
outputTokens = usageState.outputTokens || 0;
|
|
6341
|
+
reasoningOutputTokens = usageState.reasoningOutputTokens || 0;
|
|
6342
|
+
provider = readExplicitSessionProviderFromRecord(record) || provider;
|
|
6343
|
+
const recordModels = readSessionModelsFromRecord(record);
|
|
6344
|
+
for (const recordModel of recordModels) {
|
|
6345
|
+
if (!models.includes(recordModel)) {
|
|
6346
|
+
models.push(recordModel);
|
|
6347
|
+
}
|
|
6348
|
+
}
|
|
6349
|
+
model = recordModels[0] || model;
|
|
6350
|
+
}
|
|
6351
|
+
|
|
6352
|
+
const filteredPreviewMessages = removeLeadingSystemMessage(previewMessages);
|
|
6353
|
+
messageCount = filteredPreviewMessages.length;
|
|
6354
|
+
const firstUser = filteredPreviewMessages.find(item => item.role === 'user' && item.text);
|
|
6355
|
+
if (firstUser) {
|
|
6356
|
+
firstPrompt = truncateText(firstUser.text);
|
|
6357
|
+
}
|
|
6358
|
+
|
|
6359
|
+
if (!firstPrompt) {
|
|
6360
|
+
const titleRecords = parseJsonlHeadRecords(filePath, titleReadBytes);
|
|
6361
|
+
const titleMessages = [];
|
|
6362
|
+
for (const record of titleRecords) {
|
|
6363
|
+
if (record && record.type === 'message') {
|
|
6364
|
+
const role = normalizeRole(record.role);
|
|
6365
|
+
if (role === 'assistant' || role === 'user' || role === 'system') {
|
|
6366
|
+
const content = record.message?.content ?? record.content ?? '';
|
|
6367
|
+
titleMessages.push({
|
|
6368
|
+
role,
|
|
6369
|
+
text: extractMessageText(content)
|
|
6370
|
+
});
|
|
6371
|
+
}
|
|
6372
|
+
}
|
|
6373
|
+
}
|
|
6374
|
+
|
|
6375
|
+
const filteredTitleMessages = removeLeadingSystemMessage(titleMessages);
|
|
6376
|
+
const titleUser = filteredTitleMessages.find(item => item.role === 'user' && item.text);
|
|
6377
|
+
if (titleUser) {
|
|
6378
|
+
firstPrompt = truncateText(titleUser.text);
|
|
6379
|
+
}
|
|
6380
|
+
}
|
|
6381
|
+
|
|
6382
|
+
messageCount = Math.max(0, messageCount);
|
|
6383
|
+
|
|
6384
|
+
return {
|
|
6385
|
+
source: 'workbuddy',
|
|
6386
|
+
sourceLabel: 'WorkBuddy AI',
|
|
6387
|
+
provider,
|
|
6388
|
+
model,
|
|
6389
|
+
models,
|
|
6390
|
+
sessionId,
|
|
6391
|
+
title: aiTitle || firstPrompt || sessionId,
|
|
6392
|
+
cwd,
|
|
6393
|
+
createdAt,
|
|
6394
|
+
updatedAt,
|
|
6395
|
+
messageCount,
|
|
6396
|
+
totalTokens,
|
|
6397
|
+
contextWindow,
|
|
6398
|
+
inputTokens,
|
|
6399
|
+
cachedInputTokens,
|
|
6400
|
+
cacheCreationInputTokens,
|
|
6401
|
+
outputTokens,
|
|
6402
|
+
reasoningOutputTokens,
|
|
6403
|
+
__messageCountExact: isSessionSummaryMessageCountExact(stat, summaryReadBytes),
|
|
6404
|
+
filePath,
|
|
6405
|
+
keywords: [],
|
|
6406
|
+
capabilities: { code: true }
|
|
6407
|
+
};
|
|
6408
|
+
}
|
|
6409
|
+
|
|
5904
6410
|
function extractGeminiMessageText(content) {
|
|
5905
6411
|
if (typeof content === 'string') {
|
|
5906
6412
|
return content;
|
|
@@ -6665,6 +7171,54 @@ function listCodeBuddySessions(limit, options = {}) {
|
|
|
6665
7171
|
return mergeAndLimitSessions(sessions, limit);
|
|
6666
7172
|
}
|
|
6667
7173
|
|
|
7174
|
+
function listWorkBuddySessions(limit, options = {}) {
|
|
7175
|
+
const projectsDir = getWorkBuddyProjectsDir();
|
|
7176
|
+
if (!fs.existsSync(projectsDir)) {
|
|
7177
|
+
return [];
|
|
7178
|
+
}
|
|
7179
|
+
|
|
7180
|
+
const scanFactor = Number.isFinite(Number(options.scanFactor))
|
|
7181
|
+
? Math.max(1, Number(options.scanFactor))
|
|
7182
|
+
: SESSION_SCAN_FACTOR;
|
|
7183
|
+
const minFiles = Number.isFinite(Number(options.minFiles))
|
|
7184
|
+
? Math.max(1, Number(options.minFiles))
|
|
7185
|
+
: Math.min(SESSION_SCAN_MIN_FILES, MAX_SESSION_LIST_SIZE * SESSION_SCAN_FACTOR);
|
|
7186
|
+
const targetCount = Number.isFinite(Number(options.targetCount))
|
|
7187
|
+
? Math.max(1, Math.floor(Number(options.targetCount)))
|
|
7188
|
+
: Math.max(1, Math.floor(limit * scanFactor));
|
|
7189
|
+
const scanCount = Number.isFinite(Number(options.scanCount))
|
|
7190
|
+
? Math.max(targetCount, Math.floor(Number(options.scanCount)))
|
|
7191
|
+
: Math.max(targetCount, minFiles);
|
|
7192
|
+
const maxFilesScanned = Number.isFinite(Number(options.maxFilesScanned))
|
|
7193
|
+
? Math.max(scanCount, Math.floor(Number(options.maxFilesScanned)))
|
|
7194
|
+
: Math.max(scanCount * 2, minFiles);
|
|
7195
|
+
const summaryReadBytes = Number.isFinite(Number(options.summaryReadBytes))
|
|
7196
|
+
? Math.max(1024, Math.floor(Number(options.summaryReadBytes)))
|
|
7197
|
+
: SESSION_SUMMARY_READ_BYTES;
|
|
7198
|
+
const titleReadBytes = Number.isFinite(Number(options.titleReadBytes))
|
|
7199
|
+
? Math.max(1024, Math.floor(Number(options.titleReadBytes)))
|
|
7200
|
+
: SESSION_TITLE_READ_BYTES;
|
|
7201
|
+
|
|
7202
|
+
const files = collectRecentJsonlFiles(projectsDir, {
|
|
7203
|
+
returnCount: scanCount,
|
|
7204
|
+
maxFilesScanned
|
|
7205
|
+
});
|
|
7206
|
+
const sessions = [];
|
|
7207
|
+
for (const filePath of files) {
|
|
7208
|
+
const summary = parseWorkBuddySessionSummary(filePath, {
|
|
7209
|
+
summaryReadBytes,
|
|
7210
|
+
titleReadBytes
|
|
7211
|
+
});
|
|
7212
|
+
if (summary) {
|
|
7213
|
+
sessions.push(summary);
|
|
7214
|
+
}
|
|
7215
|
+
if (sessions.length >= targetCount) {
|
|
7216
|
+
break;
|
|
7217
|
+
}
|
|
7218
|
+
}
|
|
7219
|
+
return mergeAndLimitSessions(sessions, limit);
|
|
7220
|
+
}
|
|
7221
|
+
|
|
6668
7222
|
function listPiSessions(limit, options = {}) {
|
|
6669
7223
|
const sessionsDir = getPiSessionsDir();
|
|
6670
7224
|
if (!fs.existsSync(sessionsDir)) {
|
|
@@ -6714,9 +7268,7 @@ function listPiSessions(limit, options = {}) {
|
|
|
6714
7268
|
}
|
|
6715
7269
|
|
|
6716
7270
|
async function listAllSessions(params = {}) {
|
|
6717
|
-
const source = params.source
|
|
6718
|
-
? params.source
|
|
6719
|
-
: 'all';
|
|
7271
|
+
const source = normalizeSessionSourceName(params.source, '') || 'all';
|
|
6720
7272
|
const rawLimit = Number(params.limit);
|
|
6721
7273
|
const limit = Number.isFinite(rawLimit)
|
|
6722
7274
|
? Math.max(1, Math.min(rawLimit, MAX_SESSION_LIST_SIZE))
|
|
@@ -6764,9 +7316,15 @@ async function listAllSessions(params = {}) {
|
|
|
6764
7316
|
if (source === 'all' || source === 'codebuddy') {
|
|
6765
7317
|
sessions = sessions.concat(listSessionInventoryBySource('codebuddy', limit, scanOptions, { forceRefresh }));
|
|
6766
7318
|
}
|
|
7319
|
+
if (source === 'all' || source === 'workbuddy') {
|
|
7320
|
+
sessions = sessions.concat(listSessionInventoryBySource('workbuddy', limit, scanOptions, { forceRefresh }));
|
|
7321
|
+
}
|
|
6767
7322
|
if (source === 'all' || source === 'pi') {
|
|
6768
7323
|
sessions = sessions.concat(listSessionInventoryBySource('pi', limit, scanOptions, { forceRefresh }));
|
|
6769
7324
|
}
|
|
7325
|
+
if (source === 'all' || source === 'opencode') {
|
|
7326
|
+
sessions = sessions.concat(listSessionInventoryBySource('opencode', limit, scanOptions, { forceRefresh }));
|
|
7327
|
+
}
|
|
6770
7328
|
|
|
6771
7329
|
if (hasPathFilter) {
|
|
6772
7330
|
sessions = sessions.filter(item => matchesSessionPathFilter(item, normalizedPathFilter));
|
|
@@ -6789,9 +7347,7 @@ async function listAllSessions(params = {}) {
|
|
|
6789
7347
|
}
|
|
6790
7348
|
|
|
6791
7349
|
async function listAllSessionsData(params = {}) {
|
|
6792
|
-
const source = params.source
|
|
6793
|
-
? params.source
|
|
6794
|
-
: 'all';
|
|
7350
|
+
const source = normalizeSessionSourceName(params.source, '') || 'all';
|
|
6795
7351
|
const rawLimit = Number(params.limit);
|
|
6796
7352
|
const limit = Number.isFinite(rawLimit)
|
|
6797
7353
|
? Math.max(1, Math.min(rawLimit, MAX_SESSION_LIST_SIZE))
|
|
@@ -6848,6 +7404,7 @@ async function listSessionUsage(params = {}) {
|
|
|
6848
7404
|
parseCodexSessionSummary,
|
|
6849
7405
|
parseClaudeSessionSummary,
|
|
6850
7406
|
parseCodeBuddySessionSummary,
|
|
7407
|
+
parseWorkBuddySessionSummary,
|
|
6851
7408
|
parseGeminiSessionSummary,
|
|
6852
7409
|
parsePiSessionSummary,
|
|
6853
7410
|
MAX_SESSION_USAGE_LIST_SIZE,
|
|
@@ -6863,10 +7420,10 @@ async function exportSessionUsage(params = {}) {
|
|
|
6863
7420
|
|
|
6864
7421
|
function listSessionPaths(params = {}) {
|
|
6865
7422
|
const source = typeof params.source === 'string' ? params.source.trim().toLowerCase() : '';
|
|
6866
|
-
if (source && source
|
|
7423
|
+
if (source && !isSessionSourceOrAll(source)) {
|
|
6867
7424
|
return [];
|
|
6868
7425
|
}
|
|
6869
|
-
const validSource = source
|
|
7426
|
+
const validSource = normalizeSessionSourceName(source, '') || (source === 'all' ? 'all' : 'all');
|
|
6870
7427
|
const rawLimit = Number(params.limit);
|
|
6871
7428
|
const limit = Number.isFinite(rawLimit)
|
|
6872
7429
|
? Math.max(1, Math.min(rawLimit, MAX_SESSION_PATH_LIST_SIZE))
|
|
@@ -6900,9 +7457,15 @@ function listSessionPaths(params = {}) {
|
|
|
6900
7457
|
if (validSource === 'all' || validSource === 'codebuddy') {
|
|
6901
7458
|
sessions = sessions.concat(listSessionInventoryBySource('codebuddy', gatherLimit, scanOptions, { forceRefresh }));
|
|
6902
7459
|
}
|
|
7460
|
+
if (validSource === 'all' || validSource === 'workbuddy') {
|
|
7461
|
+
sessions = sessions.concat(listSessionInventoryBySource('workbuddy', gatherLimit, scanOptions, { forceRefresh }));
|
|
7462
|
+
}
|
|
6903
7463
|
if (validSource === 'all' || validSource === 'pi') {
|
|
6904
7464
|
sessions = sessions.concat(listSessionInventoryBySource('pi', gatherLimit, scanOptions, { forceRefresh }));
|
|
6905
7465
|
}
|
|
7466
|
+
if (validSource === 'all' || validSource === 'opencode') {
|
|
7467
|
+
sessions = sessions.concat(listSessionInventoryBySource('opencode', gatherLimit, scanOptions, { forceRefresh }));
|
|
7468
|
+
}
|
|
6906
7469
|
|
|
6907
7470
|
const dedupedPaths = [];
|
|
6908
7471
|
const seen = new Set();
|
|
@@ -6928,9 +7491,13 @@ function listSessionPaths(params = {}) {
|
|
|
6928
7491
|
}
|
|
6929
7492
|
|
|
6930
7493
|
function resolveSessionFilePath(source, filePath, sessionId) {
|
|
6931
|
-
const normalizedSource = source
|
|
6932
|
-
|
|
6933
|
-
|
|
7494
|
+
const normalizedSource = normalizeSessionSourceName(source, 'codex');
|
|
7495
|
+
if (normalizedSource === 'opencode') {
|
|
7496
|
+
const id = typeof sessionId === 'string' && sessionId.trim()
|
|
7497
|
+
? sessionId.trim()
|
|
7498
|
+
: parseOpencodeVirtualSessionId(filePath);
|
|
7499
|
+
return id && parseOpencodeSessionSummary(id) ? buildOpencodeVirtualSessionPath(id) : '';
|
|
7500
|
+
}
|
|
6934
7501
|
const homeDir = process && process.env && process.env.HOME ? process.env.HOME : '';
|
|
6935
7502
|
const derivedCodexDir = homeDir ? `${homeDir}/.codexmate/sessions/derived/codex` : '';
|
|
6936
7503
|
const derivedClaudeDir = homeDir ? `${homeDir}/.codexmate/sessions/derived/claude` : '';
|
|
@@ -6940,9 +7507,11 @@ function resolveSessionFilePath(source, filePath, sessionId) {
|
|
|
6940
7507
|
? [getGeminiTmpDir()]
|
|
6941
7508
|
: (normalizedSource === 'codebuddy'
|
|
6942
7509
|
? [getCodeBuddyProjectsDir()]
|
|
6943
|
-
: (normalizedSource === '
|
|
6944
|
-
? [
|
|
6945
|
-
:
|
|
7510
|
+
: (normalizedSource === 'workbuddy'
|
|
7511
|
+
? [getWorkBuddyProjectsDir()]
|
|
7512
|
+
: (normalizedSource === 'pi'
|
|
7513
|
+
? [getPiSessionsDir()]
|
|
7514
|
+
: [getCodexSessionsDir(), derivedCodexDir]))));
|
|
6946
7515
|
const availableRoots = roots.filter((dirPath) => dirPath && fs.existsSync(dirPath));
|
|
6947
7516
|
if (availableRoots.length === 0) {
|
|
6948
7517
|
return '';
|
|
@@ -7488,23 +8057,11 @@ function moveFileSync(sourcePath, targetPath) {
|
|
|
7488
8057
|
|
|
7489
8058
|
function buildSessionSummaryFallback(source, filePath, sessionId = '') {
|
|
7490
8059
|
const resolvedSessionId = sessionId || path.basename(filePath, '.jsonl');
|
|
7491
|
-
const sourceLabel = source
|
|
7492
|
-
? 'Claude Code'
|
|
7493
|
-
: (source === 'gemini'
|
|
7494
|
-
? 'Gemini CLI'
|
|
7495
|
-
: (source === 'codebuddy'
|
|
7496
|
-
? 'CodeBuddy Code'
|
|
7497
|
-
: (source === 'pi' ? 'Pi' : 'Codex')));
|
|
8060
|
+
const sourceLabel = getSessionSourceLabel(source);
|
|
7498
8061
|
return {
|
|
7499
8062
|
source,
|
|
7500
8063
|
sourceLabel,
|
|
7501
|
-
provider: source
|
|
7502
|
-
? 'claude'
|
|
7503
|
-
: (source === 'gemini'
|
|
7504
|
-
? 'gemini'
|
|
7505
|
-
: (source === 'codebuddy'
|
|
7506
|
-
? 'codebuddy'
|
|
7507
|
-
: (source === 'pi' ? 'pi' : 'codex'))),
|
|
8064
|
+
provider: getSessionSourceProvider(source),
|
|
7508
8065
|
sessionId: resolvedSessionId,
|
|
7509
8066
|
title: resolvedSessionId,
|
|
7510
8067
|
cwd: '',
|
|
@@ -7515,7 +8072,7 @@ function buildSessionSummaryFallback(source, filePath, sessionId = '') {
|
|
|
7515
8072
|
contextWindow: 0,
|
|
7516
8073
|
filePath,
|
|
7517
8074
|
keywords: [],
|
|
7518
|
-
capabilities: source === 'claude' || source === 'gemini' || source === 'codebuddy' ? { code: true } : {}
|
|
8075
|
+
capabilities: source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'opencode' ? { code: true } : {}
|
|
7519
8076
|
};
|
|
7520
8077
|
}
|
|
7521
8078
|
|
|
@@ -7559,7 +8116,9 @@ function normalizeSessionTrashEntry(entry) {
|
|
|
7559
8116
|
? 'gemini'
|
|
7560
8117
|
: (entry.source === 'codebuddy'
|
|
7561
8118
|
? 'codebuddy'
|
|
7562
|
-
: (entry.source === '
|
|
8119
|
+
: (entry.source === 'workbuddy'
|
|
8120
|
+
? 'workbuddy'
|
|
8121
|
+
: (entry.source === 'pi' ? 'pi' : '')))));
|
|
7563
8122
|
const trashId = typeof entry.trashId === 'string' ? entry.trashId.trim() : '';
|
|
7564
8123
|
if (!source || !trashId || trashId.includes('/') || trashId.includes('\\') || trashId.includes('\0')) {
|
|
7565
8124
|
return null;
|
|
@@ -7580,7 +8139,9 @@ function normalizeSessionTrashEntry(entry) {
|
|
|
7580
8139
|
? 'Gemini CLI'
|
|
7581
8140
|
: (source === 'codebuddy'
|
|
7582
8141
|
? 'CodeBuddy Code'
|
|
7583
|
-
: (source === '
|
|
8142
|
+
: (source === 'workbuddy'
|
|
8143
|
+
? 'WorkBuddy AI'
|
|
8144
|
+
: (source === 'pi' ? 'Pi' : 'Codex')))),
|
|
7584
8145
|
sessionId: sessionId || trashId,
|
|
7585
8146
|
title: typeof entry.title === 'string' && entry.title.trim() ? entry.title.trim() : (sessionId || trashId),
|
|
7586
8147
|
cwd: typeof entry.cwd === 'string' ? entry.cwd : '',
|
|
@@ -7596,7 +8157,7 @@ function normalizeSessionTrashEntry(entry) {
|
|
|
7596
8157
|
originalFilePath: typeof entry.originalFilePath === 'string' ? entry.originalFilePath : '',
|
|
7597
8158
|
provider: typeof entry.provider === 'string' && entry.provider.trim()
|
|
7598
8159
|
? entry.provider.trim()
|
|
7599
|
-
: (source === 'claude' ? 'claude' : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : (source === 'pi' ? 'pi' : 'codex')))),
|
|
8160
|
+
: (source === 'claude' ? 'claude' : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : (source === 'workbuddy' ? 'workbuddy' : (source === 'pi' ? 'pi' : 'codex'))))),
|
|
7600
8161
|
keywords: normalizeKeywords(entry.keywords),
|
|
7601
8162
|
capabilities: normalizeCapabilities(entry.capabilities),
|
|
7602
8163
|
claudeIndexPath: typeof entry.claudeIndexPath === 'string' ? entry.claudeIndexPath : '',
|
|
@@ -7689,7 +8250,9 @@ function buildSessionTrashEntry(summary, options = {}) {
|
|
|
7689
8250
|
? 'gemini'
|
|
7690
8251
|
: (options.source === 'codebuddy'
|
|
7691
8252
|
? 'codebuddy'
|
|
7692
|
-
: (options.source === '
|
|
8253
|
+
: (options.source === 'workbuddy'
|
|
8254
|
+
? 'workbuddy'
|
|
8255
|
+
: (options.source === 'pi' ? 'pi' : 'codex'))));
|
|
7693
8256
|
const sessionId = options.sessionId || summary.sessionId || path.basename(options.originalFilePath || summary.filePath || '', '.jsonl');
|
|
7694
8257
|
const claudeIndexEntry = options.claudeIndexEntry && typeof options.claudeIndexEntry === 'object' && !Array.isArray(options.claudeIndexEntry)
|
|
7695
8258
|
? options.claudeIndexEntry
|
|
@@ -7703,7 +8266,9 @@ function buildSessionTrashEntry(summary, options = {}) {
|
|
|
7703
8266
|
? 'Gemini CLI'
|
|
7704
8267
|
: (source === 'codebuddy'
|
|
7705
8268
|
? 'CodeBuddy Code'
|
|
7706
|
-
: (source === '
|
|
8269
|
+
: (source === 'workbuddy'
|
|
8270
|
+
? 'WorkBuddy AI'
|
|
8271
|
+
: (source === 'pi' ? 'Pi' : 'Codex'))));
|
|
7707
8272
|
const fallbackTitle = truncateText(
|
|
7708
8273
|
(claudeIndexEntry && (claudeIndexEntry.summary || claudeIndexEntry.firstPrompt)) || sessionId,
|
|
7709
8274
|
120
|
|
@@ -7740,7 +8305,7 @@ function buildSessionTrashEntry(summary, options = {}) {
|
|
|
7740
8305
|
originalFilePath: options.originalFilePath || summary.filePath || '',
|
|
7741
8306
|
provider: (claudeIndexEntry && typeof claudeIndexEntry.provider === 'string' && claudeIndexEntry.provider.trim())
|
|
7742
8307
|
? claudeIndexEntry.provider.trim()
|
|
7743
|
-
: (summary.provider || (source === 'claude' ? 'claude' : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : (source === 'pi' ? 'pi' : 'codex'))))),
|
|
8308
|
+
: (summary.provider || (source === 'claude' ? 'claude' : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : (source === 'workbuddy' ? 'workbuddy' : (source === 'pi' ? 'pi' : 'codex')))))),
|
|
7744
8309
|
keywords: normalizedClaudeKeywords.length > 0 ? normalizedClaudeKeywords : normalizedSummaryKeywords,
|
|
7745
8310
|
capabilities: Object.keys(normalizedClaudeCapabilities).length > 0
|
|
7746
8311
|
? normalizedClaudeCapabilities
|
|
@@ -7761,7 +8326,9 @@ function resolveSessionRestoreTarget(entry) {
|
|
|
7761
8326
|
? getGeminiTmpDir()
|
|
7762
8327
|
: (normalized.source === 'codebuddy'
|
|
7763
8328
|
? getCodeBuddyProjectsDir()
|
|
7764
|
-
: (normalized.source === '
|
|
8329
|
+
: (normalized.source === 'workbuddy'
|
|
8330
|
+
? getWorkBuddyProjectsDir()
|
|
8331
|
+
: (normalized.source === 'pi' ? getPiSessionsDir() : getCodexSessionsDir()))));
|
|
7765
8332
|
const originalFilePath = typeof normalized.originalFilePath === 'string' ? normalized.originalFilePath.trim() : '';
|
|
7766
8333
|
if (!root || !originalFilePath) {
|
|
7767
8334
|
return '';
|
|
@@ -7895,15 +8462,7 @@ function upsertClaudeSessionIndexEntry(indexPath, sessionFilePath, entry) {
|
|
|
7895
8462
|
}
|
|
7896
8463
|
|
|
7897
8464
|
async function listSessionTrashItems(params = {}) {
|
|
7898
|
-
const source = params.source
|
|
7899
|
-
? 'claude'
|
|
7900
|
-
: (params.source === 'codex'
|
|
7901
|
-
? 'codex'
|
|
7902
|
-
: (params.source === 'gemini'
|
|
7903
|
-
? 'gemini'
|
|
7904
|
-
: (params.source === 'codebuddy'
|
|
7905
|
-
? 'codebuddy'
|
|
7906
|
-
: (params.source === 'pi' ? 'pi' : 'all'))));
|
|
8465
|
+
const source = normalizeSessionSourceName(params.source, '') || 'all';
|
|
7907
8466
|
const countOnly = params.countOnly === true;
|
|
7908
8467
|
const rawLimit = Number(params.limit);
|
|
7909
8468
|
const limit = Number.isFinite(rawLimit)
|
|
@@ -7913,7 +8472,7 @@ async function listSessionTrashItems(params = {}) {
|
|
|
7913
8472
|
purgeExpiredSessionTrashEntries(params.retentionDays);
|
|
7914
8473
|
}
|
|
7915
8474
|
const allEntries = readSessionTrashEntries();
|
|
7916
|
-
let items = source
|
|
8475
|
+
let items = normalizeSessionSourceName(source, '')
|
|
7917
8476
|
? allEntries.filter((entry) => entry.source === source)
|
|
7918
8477
|
: allEntries.slice();
|
|
7919
8478
|
items.sort((a, b) => {
|
|
@@ -8093,18 +8652,13 @@ async function purgeSessionTrashItems(params = {}) {
|
|
|
8093
8652
|
}
|
|
8094
8653
|
|
|
8095
8654
|
async function trashSessionData(params = {}) {
|
|
8096
|
-
const source = params.source
|
|
8097
|
-
? 'claude'
|
|
8098
|
-
: (params.source === 'codex'
|
|
8099
|
-
? 'codex'
|
|
8100
|
-
: (params.source === 'gemini'
|
|
8101
|
-
? 'gemini'
|
|
8102
|
-
: (params.source === 'codebuddy'
|
|
8103
|
-
? 'codebuddy'
|
|
8104
|
-
: (params.source === 'pi' ? 'pi' : ''))));
|
|
8655
|
+
const source = normalizeSessionSourceName(params.source, '');
|
|
8105
8656
|
if (!source) {
|
|
8106
8657
|
return { error: 'Invalid source' };
|
|
8107
8658
|
}
|
|
8659
|
+
if (source === 'opencode') {
|
|
8660
|
+
return { error: 'OpenCode DB-backed sessions cannot be deleted from CodexMate' };
|
|
8661
|
+
}
|
|
8108
8662
|
|
|
8109
8663
|
const filePath = resolveSessionFilePath(source, getSessionFileArg(params), params.sessionId);
|
|
8110
8664
|
if (!filePath) {
|
|
@@ -8117,7 +8671,9 @@ async function trashSessionData(params = {}) {
|
|
|
8117
8671
|
? parseGeminiSessionSummary(filePath)
|
|
8118
8672
|
: (source === 'codebuddy'
|
|
8119
8673
|
? parseCodeBuddySessionSummary(filePath)
|
|
8120
|
-
: (source === '
|
|
8674
|
+
: (source === 'workbuddy'
|
|
8675
|
+
? parseWorkBuddySessionSummary(filePath)
|
|
8676
|
+
: (source === 'pi' ? parsePiSessionSummary(filePath) : parseCodexSessionSummary(filePath))))))
|
|
8121
8677
|
|| buildSessionSummaryFallback(source, filePath, params.sessionId);
|
|
8122
8678
|
const exactMessageCount = await countConversationMessagesInFile(filePath, source);
|
|
8123
8679
|
if (Number.isFinite(Number(exactMessageCount))) {
|
|
@@ -8208,18 +8764,13 @@ async function trashSessionData(params = {}) {
|
|
|
8208
8764
|
}
|
|
8209
8765
|
|
|
8210
8766
|
async function deleteSessionData(params = {}) {
|
|
8211
|
-
const source = params.source
|
|
8212
|
-
? 'claude'
|
|
8213
|
-
: (params.source === 'codex'
|
|
8214
|
-
? 'codex'
|
|
8215
|
-
: (params.source === 'gemini'
|
|
8216
|
-
? 'gemini'
|
|
8217
|
-
: (params.source === 'codebuddy'
|
|
8218
|
-
? 'codebuddy'
|
|
8219
|
-
: (params.source === 'pi' ? 'pi' : ''))));
|
|
8767
|
+
const source = normalizeSessionSourceName(params.source, '');
|
|
8220
8768
|
if (!source) {
|
|
8221
8769
|
return { error: 'Invalid source' };
|
|
8222
8770
|
}
|
|
8771
|
+
if (source === 'opencode') {
|
|
8772
|
+
return { error: 'OpenCode DB-backed sessions cannot be deleted from CodexMate' };
|
|
8773
|
+
}
|
|
8223
8774
|
|
|
8224
8775
|
const filePath = resolveSessionFilePath(source, getSessionFileArg(params), params.sessionId);
|
|
8225
8776
|
if (!filePath) {
|
|
@@ -8767,7 +9318,7 @@ function recordHasPiMessage(record) {
|
|
|
8767
9318
|
|
|
8768
9319
|
function recordHasMessage(record, source) {
|
|
8769
9320
|
if (source === 'codex') return recordHasCodexMessage(record);
|
|
8770
|
-
if (source === 'codebuddy') return recordHasCodeBuddyMessage(record);
|
|
9321
|
+
if (source === 'codebuddy' || source === 'workbuddy') return recordHasCodeBuddyMessage(record);
|
|
8771
9322
|
if (source === 'pi') return recordHasPiMessage(record);
|
|
8772
9323
|
return recordHasClaudeMessage(record);
|
|
8773
9324
|
}
|
|
@@ -8787,7 +9338,7 @@ function extractMessagesFromRecords(records, source, options = {}) {
|
|
|
8787
9338
|
const record = records[lineIndex];
|
|
8788
9339
|
if (source === 'codex') {
|
|
8789
9340
|
extractCodexMessageFromRecord(record, state, lineIndex);
|
|
8790
|
-
} else if (source === 'codebuddy') {
|
|
9341
|
+
} else if (source === 'codebuddy' || source === 'workbuddy') {
|
|
8791
9342
|
extractCodeBuddyMessageFromRecord(record, state, lineIndex);
|
|
8792
9343
|
} else if (source === 'pi') {
|
|
8793
9344
|
extractPiMessageFromRecord(record, state, lineIndex);
|
|
@@ -8852,7 +9403,7 @@ async function extractMessagesFromFile(filePath, source, options = {}) {
|
|
|
8852
9403
|
|
|
8853
9404
|
if (source === 'codex') {
|
|
8854
9405
|
extractCodexMessageFromRecord(record, state, currentLineIndex);
|
|
8855
|
-
} else if (source === 'codebuddy') {
|
|
9406
|
+
} else if (source === 'codebuddy' || source === 'workbuddy') {
|
|
8856
9407
|
extractCodeBuddyMessageFromRecord(record, state, currentLineIndex);
|
|
8857
9408
|
} else if (source === 'pi') {
|
|
8858
9409
|
extractPiMessageFromRecord(record, state, currentLineIndex);
|
|
@@ -8880,15 +9431,7 @@ async function extractMessagesFromFile(filePath, source, options = {}) {
|
|
|
8880
9431
|
}
|
|
8881
9432
|
|
|
8882
9433
|
async function readSessionDetail(params = {}) {
|
|
8883
|
-
const source = params.source
|
|
8884
|
-
? 'claude'
|
|
8885
|
-
: (params.source === 'codex'
|
|
8886
|
-
? 'codex'
|
|
8887
|
-
: (params.source === 'gemini'
|
|
8888
|
-
? 'gemini'
|
|
8889
|
-
: (params.source === 'codebuddy'
|
|
8890
|
-
? 'codebuddy'
|
|
8891
|
-
: (params.source === 'pi' ? 'pi' : ''))));
|
|
9434
|
+
const source = normalizeSessionSourceName(params.source, '');
|
|
8892
9435
|
if (!source) {
|
|
8893
9436
|
return { error: 'Invalid source' };
|
|
8894
9437
|
}
|
|
@@ -8904,9 +9447,30 @@ async function readSessionDetail(params = {}) {
|
|
|
8904
9447
|
? Math.max(1, Math.min(rawLimit, MAX_SESSION_DETAIL_MESSAGES))
|
|
8905
9448
|
: DEFAULT_SESSION_DETAIL_MESSAGES;
|
|
8906
9449
|
const preview = params.preview === true || params.preview === 'true';
|
|
9450
|
+
const rawSessionQuery = typeof params.query === 'string' ? params.query.trim() : '';
|
|
9451
|
+
const sessionQueryTokens = rawSessionQuery
|
|
9452
|
+
? expandSessionQueryTokens(normalizeQueryTokens(rawSessionQuery))
|
|
9453
|
+
: [];
|
|
9454
|
+
let sessionQueryMatch = null;
|
|
9455
|
+
if (sessionQueryTokens.length > 0) {
|
|
9456
|
+
sessionQueryMatch = await scanSessionContentForQuery(
|
|
9457
|
+
{ source, filePath, sessionId: params.sessionId },
|
|
9458
|
+
sessionQueryTokens,
|
|
9459
|
+
{
|
|
9460
|
+
maxMatches: Number.MAX_SAFE_INTEGER,
|
|
9461
|
+
positionsLimit: SESSION_QUERY_MATCH_POSITIONS_LIMIT
|
|
9462
|
+
}
|
|
9463
|
+
);
|
|
9464
|
+
}
|
|
8907
9465
|
|
|
8908
9466
|
let extracted;
|
|
8909
|
-
if (source === '
|
|
9467
|
+
if (source === 'opencode') {
|
|
9468
|
+
const opencodeSessionId = params.sessionId || parseOpencodeVirtualSessionId(filePath);
|
|
9469
|
+
extracted = queryOpencodeSessionMessages(opencodeSessionId, { maxMessages: messageLimit });
|
|
9470
|
+
if (!extracted) {
|
|
9471
|
+
return { error: 'Failed to parse session file' };
|
|
9472
|
+
}
|
|
9473
|
+
} else if (source === 'gemini') {
|
|
8910
9474
|
let json;
|
|
8911
9475
|
try {
|
|
8912
9476
|
json = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
@@ -8946,13 +9510,7 @@ async function readSessionDetail(params = {}) {
|
|
|
8946
9510
|
extracted = await extractSessionDetailPreviewFromFile(filePath, source, messageLimit, { preview });
|
|
8947
9511
|
}
|
|
8948
9512
|
const sessionId = extracted.sessionId || params.sessionId || path.basename(filePath, source === 'gemini' ? '.json' : '.jsonl');
|
|
8949
|
-
const sourceLabel = source
|
|
8950
|
-
? 'Codex'
|
|
8951
|
-
: (source === 'claude'
|
|
8952
|
-
? 'Claude Code'
|
|
8953
|
-
: (source === 'gemini'
|
|
8954
|
-
? 'Gemini CLI'
|
|
8955
|
-
: (source === 'pi' ? 'Pi' : 'CodeBuddy Code')));
|
|
9513
|
+
const sourceLabel = getSessionSourceLabel(source);
|
|
8956
9514
|
const clippedMessages = Array.isArray(extracted.messages) ? extracted.messages : [];
|
|
8957
9515
|
const hasExactTotalMessages = Number.isFinite(extracted.totalMessages);
|
|
8958
9516
|
const startIndex = hasExactTotalMessages
|
|
@@ -8996,6 +9554,13 @@ async function readSessionDetail(params = {}) {
|
|
|
8996
9554
|
? extracted.clipped
|
|
8997
9555
|
: (hasExactTotalMessages ? extracted.totalMessages > indexedMessages.length : false),
|
|
8998
9556
|
messageLimit,
|
|
9557
|
+
...(sessionQueryMatch ? {
|
|
9558
|
+
match: {
|
|
9559
|
+
hit: !!sessionQueryMatch.hit,
|
|
9560
|
+
count: Number.isFinite(sessionQueryMatch.count) ? sessionQueryMatch.count : 0,
|
|
9561
|
+
positions: Array.isArray(sessionQueryMatch.positions) ? sessionQueryMatch.positions : []
|
|
9562
|
+
}
|
|
9563
|
+
} : {}),
|
|
8999
9564
|
messages: indexedMessages,
|
|
9000
9565
|
filePath,
|
|
9001
9566
|
...(typeof buildSessionNativeStatus === 'function'
|
|
@@ -9013,15 +9578,7 @@ async function readSessionDetail(params = {}) {
|
|
|
9013
9578
|
}
|
|
9014
9579
|
|
|
9015
9580
|
async function readSessionPlain(params = {}) {
|
|
9016
|
-
const source = params.source
|
|
9017
|
-
? 'claude'
|
|
9018
|
-
: (params.source === 'codex'
|
|
9019
|
-
? 'codex'
|
|
9020
|
-
: (params.source === 'gemini'
|
|
9021
|
-
? 'gemini'
|
|
9022
|
-
: (params.source === 'codebuddy'
|
|
9023
|
-
? 'codebuddy'
|
|
9024
|
-
: (params.source === 'pi' ? 'pi' : ''))));
|
|
9581
|
+
const source = normalizeSessionSourceName(params.source, '');
|
|
9025
9582
|
if (!source) {
|
|
9026
9583
|
return { error: 'Invalid source' };
|
|
9027
9584
|
}
|
|
@@ -9041,7 +9598,13 @@ async function readSessionPlain(params = {}) {
|
|
|
9041
9598
|
);
|
|
9042
9599
|
|
|
9043
9600
|
let extracted;
|
|
9044
|
-
if (source === '
|
|
9601
|
+
if (source === 'opencode') {
|
|
9602
|
+
const opencodeSessionId = params.sessionId || parseOpencodeVirtualSessionId(filePath);
|
|
9603
|
+
extracted = queryOpencodeSessionMessages(opencodeSessionId, { maxMessages });
|
|
9604
|
+
if (!extracted) {
|
|
9605
|
+
return { error: 'Failed to parse session file' };
|
|
9606
|
+
}
|
|
9607
|
+
} else if (source === 'gemini') {
|
|
9045
9608
|
let json;
|
|
9046
9609
|
try {
|
|
9047
9610
|
json = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
@@ -9091,13 +9654,7 @@ async function readSessionPlain(params = {}) {
|
|
|
9091
9654
|
}
|
|
9092
9655
|
|
|
9093
9656
|
const sessionId = extracted.sessionId || params.sessionId || path.basename(filePath, source === 'gemini' ? '.json' : '.jsonl');
|
|
9094
|
-
const sourceLabel = source
|
|
9095
|
-
? 'Codex'
|
|
9096
|
-
: (source === 'claude'
|
|
9097
|
-
? 'Claude Code'
|
|
9098
|
-
: (source === 'gemini'
|
|
9099
|
-
? 'Gemini CLI'
|
|
9100
|
-
: (source === 'pi' ? 'Pi' : 'CodeBuddy Code')));
|
|
9657
|
+
const sourceLabel = getSessionSourceLabel(source);
|
|
9101
9658
|
const messages = removeLeadingSystemMessage(Array.isArray(extracted.messages) ? extracted.messages : []);
|
|
9102
9659
|
const text = buildSessionPlainText(messages);
|
|
9103
9660
|
|
|
@@ -9113,15 +9670,7 @@ async function readSessionPlain(params = {}) {
|
|
|
9113
9670
|
}
|
|
9114
9671
|
|
|
9115
9672
|
async function exportSessionData(params = {}) {
|
|
9116
|
-
const source = params.source
|
|
9117
|
-
? 'claude'
|
|
9118
|
-
: (params.source === 'codex'
|
|
9119
|
-
? 'codex'
|
|
9120
|
-
: (params.source === 'gemini'
|
|
9121
|
-
? 'gemini'
|
|
9122
|
-
: (params.source === 'codebuddy'
|
|
9123
|
-
? 'codebuddy'
|
|
9124
|
-
: (params.source === 'pi' ? 'pi' : ''))));
|
|
9673
|
+
const source = normalizeSessionSourceName(params.source, '');
|
|
9125
9674
|
if (!source) {
|
|
9126
9675
|
return { error: 'Invalid source' };
|
|
9127
9676
|
}
|
|
@@ -9133,7 +9682,13 @@ async function exportSessionData(params = {}) {
|
|
|
9133
9682
|
}
|
|
9134
9683
|
|
|
9135
9684
|
let extracted;
|
|
9136
|
-
if (source === '
|
|
9685
|
+
if (source === 'opencode') {
|
|
9686
|
+
const opencodeSessionId = params.sessionId || parseOpencodeVirtualSessionId(filePath);
|
|
9687
|
+
extracted = queryOpencodeSessionMessages(opencodeSessionId, { maxMessages });
|
|
9688
|
+
if (!extracted) {
|
|
9689
|
+
return { error: 'Failed to parse session file' };
|
|
9690
|
+
}
|
|
9691
|
+
} else if (source === 'gemini') {
|
|
9137
9692
|
let json;
|
|
9138
9693
|
try {
|
|
9139
9694
|
json = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
@@ -9191,13 +9746,7 @@ async function exportSessionData(params = {}) {
|
|
|
9191
9746
|
|
|
9192
9747
|
const sessionId = extracted.sessionId || params.sessionId || path.basename(filePath, source === 'gemini' ? '.json' : '.jsonl');
|
|
9193
9748
|
const safeSessionId = String(sessionId).replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
9194
|
-
const sourceLabel = source
|
|
9195
|
-
? 'Codex'
|
|
9196
|
-
: (source === 'claude'
|
|
9197
|
-
? 'Claude Code'
|
|
9198
|
-
: (source === 'gemini'
|
|
9199
|
-
? 'Gemini CLI'
|
|
9200
|
-
: (source === 'pi' ? 'Pi' : 'CodeBuddy Code')));
|
|
9749
|
+
const sourceLabel = getSessionSourceLabel(source);
|
|
9201
9750
|
const truncated = !!extracted.truncated;
|
|
9202
9751
|
const maxMessagesLabel = maxMessages === Infinity ? 'all' : maxMessages;
|
|
9203
9752
|
const markdown = buildSessionMarkdown({
|
|
@@ -10389,17 +10938,16 @@ function cmdSwitch(providerName, silent = false) {
|
|
|
10389
10938
|
throw new Error('提供商不存在');
|
|
10390
10939
|
}
|
|
10391
10940
|
|
|
10392
|
-
//
|
|
10941
|
+
// 切换提供商(单文件认证:同步确保目标块含 experimental_bearer_token)
|
|
10393
10942
|
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
10394
|
-
const
|
|
10943
|
+
const switchedContent = content.replace(
|
|
10395
10944
|
/^(model_provider\s*=\s*)(["']).*?(["'])/m,
|
|
10396
10945
|
`$1$2${providerName}$3`
|
|
10397
10946
|
);
|
|
10398
|
-
|
|
10399
|
-
|
|
10400
|
-
|
|
10401
|
-
|
|
10402
|
-
updateAuthJson(apiKey);
|
|
10947
|
+
const providerSegments = providers[providerName] && Array.isArray(providers[providerName].__codexmate_legacy_segments)
|
|
10948
|
+
? providers[providerName].__codexmate_legacy_segments
|
|
10949
|
+
: null;
|
|
10950
|
+
writeConfig(ensureActiveProviderBearerToken(switchedContent, providerName, providerSegments));
|
|
10403
10951
|
|
|
10404
10952
|
// 切换到该提供商的模型
|
|
10405
10953
|
const currentModels = readCurrentModels();
|
|
@@ -10540,6 +11088,7 @@ base_url = "${safeBaseUrl}"
|
|
|
10540
11088
|
wire_api = "responses"
|
|
10541
11089
|
requires_openai_auth = true
|
|
10542
11090
|
preferred_auth_method = "${safeApiKey}"
|
|
11091
|
+
experimental_bearer_token = "${safeApiKey}"
|
|
10543
11092
|
request_max_retries = 4
|
|
10544
11093
|
stream_max_retries = 10
|
|
10545
11094
|
stream_idle_timeout_ms = 300000
|
|
@@ -10841,6 +11390,7 @@ function cmdUpdate(name, baseUrl, apiKey, silent = false, options = {}) {
|
|
|
10841
11390
|
updatedBlock = replaceTomlStringField(updatedBlock, 'base_url', computeLocalOpenaiBridgeBaseUrl());
|
|
10842
11391
|
updatedBlock = replaceTomlBooleanField(updatedBlock, 'requires_openai_auth', true);
|
|
10843
11392
|
updatedBlock = replaceTomlStringField(updatedBlock, 'preferred_auth_method', 'codexmate');
|
|
11393
|
+
updatedBlock = replaceTomlStringField(updatedBlock, 'experimental_bearer_token', 'codexmate');
|
|
10844
11394
|
updatedBlock = replaceTomlStringField(updatedBlock, 'codexmate_bridge', 'openai');
|
|
10845
11395
|
if (hasOpenaiBridgeMaxRetries) {
|
|
10846
11396
|
updatedBlock = replaceTomlNumberField(updatedBlock, 'codexmate_bridge_max_retries', openaiBridgeMaxRetries);
|
|
@@ -10851,6 +11401,7 @@ function cmdUpdate(name, baseUrl, apiKey, silent = false, options = {}) {
|
|
|
10851
11401
|
}
|
|
10852
11402
|
if (apiKey !== undefined) {
|
|
10853
11403
|
updatedBlock = replaceTomlStringField(updatedBlock, 'preferred_auth_method', apiKey);
|
|
11404
|
+
updatedBlock = replaceTomlStringField(updatedBlock, 'experimental_bearer_token', apiKey);
|
|
10854
11405
|
}
|
|
10855
11406
|
}
|
|
10856
11407
|
|
|
@@ -10865,12 +11416,6 @@ function cmdUpdate(name, baseUrl, apiKey, silent = false, options = {}) {
|
|
|
10865
11416
|
}
|
|
10866
11417
|
writeConfig(finalContent);
|
|
10867
11418
|
|
|
10868
|
-
// 如果更新了 API Key 且该提供商是当前激活的,同步更新 auth.json
|
|
10869
|
-
const currentProvider = config.model_provider;
|
|
10870
|
-
if (apiKey !== undefined && name === currentProvider) {
|
|
10871
|
-
updateAuthJson(apiKey);
|
|
10872
|
-
}
|
|
10873
|
-
|
|
10874
11419
|
if (!silent) {
|
|
10875
11420
|
console.log('✓ 已更新提供商:', name);
|
|
10876
11421
|
console.log();
|
|
@@ -11085,7 +11630,8 @@ function runKilocodeCommand(args = [], options = {}) {
|
|
|
11085
11630
|
const child = spawn(bin, finalArgs, {
|
|
11086
11631
|
detached: true,
|
|
11087
11632
|
stdio: 'ignore',
|
|
11088
|
-
windowsHide: true
|
|
11633
|
+
windowsHide: true,
|
|
11634
|
+
shell: process.platform === 'win32'
|
|
11089
11635
|
});
|
|
11090
11636
|
child.unref();
|
|
11091
11637
|
return { success: true, bin, args: finalArgs, pid: child.pid };
|
|
@@ -11093,7 +11639,8 @@ function runKilocodeCommand(args = [], options = {}) {
|
|
|
11093
11639
|
return new Promise((resolve, reject) => {
|
|
11094
11640
|
const child = spawn(bin, finalArgs, {
|
|
11095
11641
|
stdio: 'inherit',
|
|
11096
|
-
windowsHide: false
|
|
11642
|
+
windowsHide: false,
|
|
11643
|
+
shell: process.platform === 'win32'
|
|
11097
11644
|
});
|
|
11098
11645
|
child.on('error', reject);
|
|
11099
11646
|
child.on('exit', (code, signal) => {
|
|
@@ -12382,7 +12929,7 @@ function resolveExportOutputPath(outputPath, defaultFileName) {
|
|
|
12382
12929
|
}
|
|
12383
12930
|
|
|
12384
12931
|
function printExportSessionUsage() {
|
|
12385
|
-
console.log('\n用法: codexmate export-session --source <codex|claude|gemini|codebuddy|pi> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
|
|
12932
|
+
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>]');
|
|
12386
12933
|
console.log('\n示例:');
|
|
12387
12934
|
console.log(' codexmate export-session --source codex --session-id 123456');
|
|
12388
12935
|
console.log(' codexmate export-session --source claude --file "~/.claude/projects/demo/session.jsonl"');
|
|
@@ -12454,8 +13001,8 @@ function parseExportSessionArgs(args = []) {
|
|
|
12454
13001
|
}
|
|
12455
13002
|
|
|
12456
13003
|
const normalizedSource = options.source.trim().toLowerCase();
|
|
12457
|
-
if (normalizedSource && normalizedSource
|
|
12458
|
-
errors.push('参数 --source 仅支持 codex、claude 或
|
|
13004
|
+
if (normalizedSource && !isSessionSourceOrAll(normalizedSource)) {
|
|
13005
|
+
errors.push('参数 --source 仅支持 codex、claude、gemini、codebuddy、workbuddy、pi 或 opencode');
|
|
12459
13006
|
}
|
|
12460
13007
|
options.source = normalizedSource;
|
|
12461
13008
|
|
|
@@ -12528,7 +13075,7 @@ async function cmdExportSession(args = []) {
|
|
|
12528
13075
|
|
|
12529
13076
|
function printAnalyticsUsage() {
|
|
12530
13077
|
console.log('\n用法:');
|
|
12531
|
-
console.log(' codexmate analytics export [--format csv|json] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--model <MODEL>] [--source <codex|claude|gemini|codebuddy|pi|all>] [--output <PATH|->] [-o <PATH|->]');
|
|
13078
|
+
console.log(' codexmate analytics export [--format csv|json] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--model <MODEL>] [--source <codex|claude|gemini|codebuddy|workbuddy|pi|opencode|all>] [--output <PATH|->] [-o <PATH|->]');
|
|
12532
13079
|
console.log('');
|
|
12533
13080
|
}
|
|
12534
13081
|
|
|
@@ -13883,8 +14430,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13883
14430
|
case 'list-sessions':
|
|
13884
14431
|
{
|
|
13885
14432
|
const source = typeof params.source === 'string' ? params.source.trim().toLowerCase() : '';
|
|
13886
|
-
if (source && source
|
|
13887
|
-
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' };
|
|
14433
|
+
if (source && !isSessionSourceOrAll(source)) {
|
|
14434
|
+
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' };
|
|
13888
14435
|
} else {
|
|
13889
14436
|
result = {
|
|
13890
14437
|
sessions: await listSessionBrowse(params),
|
|
@@ -13897,8 +14444,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13897
14444
|
{
|
|
13898
14445
|
const usageParams = isPlainObject(params) ? params : {};
|
|
13899
14446
|
const source = typeof usageParams.source === 'string' ? usageParams.source.trim().toLowerCase() : '';
|
|
13900
|
-
if (source && source
|
|
13901
|
-
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' };
|
|
14447
|
+
if (source && !isSessionSourceOrAll(source)) {
|
|
14448
|
+
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' };
|
|
13902
14449
|
} else {
|
|
13903
14450
|
result = {
|
|
13904
14451
|
sessions: await listSessionUsage({
|
|
@@ -13914,8 +14461,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13914
14461
|
{
|
|
13915
14462
|
const usageParams = isPlainObject(params) ? params : {};
|
|
13916
14463
|
const source = typeof usageParams.source === 'string' ? usageParams.source.trim().toLowerCase() : '';
|
|
13917
|
-
if (source && source
|
|
13918
|
-
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' };
|
|
14464
|
+
if (source && !isSessionSourceOrAll(source)) {
|
|
14465
|
+
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' };
|
|
13919
14466
|
} else {
|
|
13920
14467
|
result = await exportSessionUsage({
|
|
13921
14468
|
...usageParams,
|
|
@@ -13927,8 +14474,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13927
14474
|
case 'list-session-paths':
|
|
13928
14475
|
{
|
|
13929
14476
|
const source = typeof params.source === 'string' ? params.source.trim().toLowerCase() : '';
|
|
13930
|
-
if (source && source
|
|
13931
|
-
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' };
|
|
14477
|
+
if (source && !isSessionSourceOrAll(source)) {
|
|
14478
|
+
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' };
|
|
13932
14479
|
} else {
|
|
13933
14480
|
result = {
|
|
13934
14481
|
paths: listSessionPaths(params)
|
|
@@ -15439,7 +15986,7 @@ function buildMcpClaudeSettingsPayload() {
|
|
|
15439
15986
|
function normalizeMcpSource(value) {
|
|
15440
15987
|
const source = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
15441
15988
|
if (!source) return '';
|
|
15442
|
-
if (source
|
|
15989
|
+
if (normalizeSessionSourceName(source, '') || source === 'all') {
|
|
15443
15990
|
return source;
|
|
15444
15991
|
}
|
|
15445
15992
|
return null;
|
|
@@ -15752,7 +16299,7 @@ function createWorkflowToolCatalog() {
|
|
|
15752
16299
|
handler: async (args = {}) => {
|
|
15753
16300
|
const source = normalizeMcpSource(args.source);
|
|
15754
16301
|
if (source === null) {
|
|
15755
|
-
return { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' };
|
|
16302
|
+
return { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' };
|
|
15756
16303
|
}
|
|
15757
16304
|
return {
|
|
15758
16305
|
source: source || 'all',
|
|
@@ -16118,7 +16665,7 @@ function createMcpTools(options = {}) {
|
|
|
16118
16665
|
const input = args && typeof args === 'object' ? args : {};
|
|
16119
16666
|
const source = normalizeMcpSource(input.source);
|
|
16120
16667
|
if (source === null) {
|
|
16121
|
-
return { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' };
|
|
16668
|
+
return { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' };
|
|
16122
16669
|
}
|
|
16123
16670
|
const normalizedInput = {
|
|
16124
16671
|
...input,
|
|
@@ -16578,7 +17125,7 @@ function createMcpResources() {
|
|
|
16578
17125
|
contents: [{
|
|
16579
17126
|
uri,
|
|
16580
17127
|
mimeType: 'application/json',
|
|
16581
|
-
text: JSON.stringify({ error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' }, null, 2)
|
|
17128
|
+
text: JSON.stringify({ error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' }, null, 2)
|
|
16582
17129
|
}]
|
|
16583
17130
|
};
|
|
16584
17131
|
}
|
|
@@ -16827,7 +17374,7 @@ function printMainHelp() {
|
|
|
16827
17374
|
console.log(' 注: follow-up 自动排队仅支持 linux/android/netbsd/openbsd/darwin/freebsd 且 stdin 必须是 TTY,其他平台会报错');
|
|
16828
17375
|
console.log(' codexmate qwen [参数...] 等同于 qwen --yolo');
|
|
16829
17376
|
console.log(' codexmate mcp [serve] [--transport stdio] [--allow-write|--read-only]');
|
|
16830
|
-
console.log(' codexmate export-session --source <codex|claude|gemini|codebuddy|pi> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
|
|
17377
|
+
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>]');
|
|
16831
17378
|
console.log(' codexmate convert-session --from <codex|claude> --to <codex|claude> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
|
|
16832
17379
|
console.log(' codexmate zip <路径> [--max:级别] 压缩(系统 zip 优先,其次 zip-lib)');
|
|
16833
17380
|
console.log(' codexmate unzip <zip文件> [输出目录] 解压(zip-lib)');
|