codexmate 0.1.25 → 0.1.27
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/session-usage.js +10 -4
- package/cli.js +733 -199
- package/lib/cli-sessions.js +1 -1
- package/package.json +3 -3
- package/tools/dev/launch.js +147 -0
- package/web-ui/app.js +13 -3
- package/web-ui/index.html +1 -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.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/i18n/locales/en.mjs +9 -1
- package/web-ui/modules/i18n/locales/ja.mjs +9 -1
- package/web-ui/modules/i18n/locales/vi.mjs +9 -1
- package/web-ui/modules/i18n/locales/zh-tw.mjs +9 -1
- package/web-ui/modules/i18n/locales/zh.mjs +9 -1
- 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 +19 -4
- package/web-ui/res/web-ui-render.precompiled.js +89 -21
- package/web-ui/session-helpers.mjs +1 -1
- package/web-ui/styles/base-theme.css +24 -0
- 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 +1 -7
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;
|
|
@@ -752,6 +770,7 @@ let g_sessionFileLookupCache = {
|
|
|
752
770
|
claude: new Map(),
|
|
753
771
|
gemini: new Map(),
|
|
754
772
|
codebuddy: new Map(),
|
|
773
|
+
workbuddy: new Map(),
|
|
755
774
|
pi: new Map()
|
|
756
775
|
};
|
|
757
776
|
let g_exactMessageCountCache = new Map();
|
|
@@ -1012,7 +1031,7 @@ function normalizeSidebarCollapsedPreference(value) {
|
|
|
1012
1031
|
|
|
1013
1032
|
function normalizeSessionFilterSourcePreference(value) {
|
|
1014
1033
|
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1015
|
-
if (normalized === 'all' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'pi') return normalized;
|
|
1034
|
+
if (normalized === 'all' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'workbuddy' || normalized === 'pi') return normalized;
|
|
1016
1035
|
return 'codex';
|
|
1017
1036
|
}
|
|
1018
1037
|
|
|
@@ -1195,7 +1214,6 @@ function getApiToolConfigWriteTarget(action) {
|
|
|
1195
1214
|
const opencodeWriteActions = new Set([
|
|
1196
1215
|
'apply-opencode-config',
|
|
1197
1216
|
'update-opencode-selection',
|
|
1198
|
-
'apply-opencode-agents-file'
|
|
1199
1217
|
]);
|
|
1200
1218
|
const kilocodeWriteActions = new Set([
|
|
1201
1219
|
'apply-kilocode-config',
|
|
@@ -2114,6 +2132,16 @@ function getCodeBuddyProjectsDir() {
|
|
|
2114
2132
|
return resolveExistingDir(candidates, CODEBUDDY_PROJECTS_DIR);
|
|
2115
2133
|
}
|
|
2116
2134
|
|
|
2135
|
+
function getWorkBuddyProjectsDir() {
|
|
2136
|
+
const candidates = [];
|
|
2137
|
+
const envHome = process.env.WORKBUDDY_AI_HOME_DIR || process.env.WORKBUDDY_HOME;
|
|
2138
|
+
if (envHome) {
|
|
2139
|
+
candidates.push(path.join(envHome, 'projects'));
|
|
2140
|
+
}
|
|
2141
|
+
candidates.push(WORKBUDDY_PROJECTS_DIR);
|
|
2142
|
+
return resolveExistingDir(candidates, WORKBUDDY_PROJECTS_DIR);
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2117
2145
|
function getPiSessionsDir() {
|
|
2118
2146
|
const candidates = [];
|
|
2119
2147
|
const envPiHome = process.env.PI_HOME;
|
|
@@ -2128,6 +2156,250 @@ function getPiSessionsDir() {
|
|
|
2128
2156
|
return resolveExistingDir(candidates, PI_SESSIONS_DIR);
|
|
2129
2157
|
}
|
|
2130
2158
|
|
|
2159
|
+
function getOpencodeDataDir() {
|
|
2160
|
+
const candidates = [];
|
|
2161
|
+
const envOpencodeData = process.env.OPENCODE_DATA_HOME || process.env.OPENCODE_DATA_DIR;
|
|
2162
|
+
if (envOpencodeData) {
|
|
2163
|
+
candidates.push(envOpencodeData);
|
|
2164
|
+
}
|
|
2165
|
+
const xdgData = process.env.XDG_DATA_HOME;
|
|
2166
|
+
if (xdgData) {
|
|
2167
|
+
candidates.push(path.join(xdgData, 'opencode'));
|
|
2168
|
+
}
|
|
2169
|
+
candidates.push(OPENCODE_DATA_DIR);
|
|
2170
|
+
return resolveExistingDir(candidates, OPENCODE_DATA_DIR);
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
function getOpencodeDbFile() {
|
|
2174
|
+
const dataDir = getOpencodeDataDir();
|
|
2175
|
+
return path.join(dataDir, 'opencode.db');
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
function buildOpencodeVirtualSessionPath(sessionId) {
|
|
2179
|
+
const id = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
2180
|
+
if (!id) return '';
|
|
2181
|
+
return path.join(OPENCODE_DB_VIRTUAL_ROOT, `${id}.jsonl`);
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
function parseOpencodeVirtualSessionId(filePath) {
|
|
2185
|
+
const raw = typeof filePath === 'string' ? filePath.trim() : '';
|
|
2186
|
+
if (!raw) return '';
|
|
2187
|
+
const base = path.basename(raw, path.extname(raw));
|
|
2188
|
+
return base && base.startsWith('ses_') ? base : '';
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
function isOpencodeDbAvailable() {
|
|
2192
|
+
const Database = getNodeSqliteDatabaseSync();
|
|
2193
|
+
const dbFile = getOpencodeDbFile();
|
|
2194
|
+
return !!(Database && dbFile && fs.existsSync(dbFile));
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
function withOpencodeDb(callback, fallback = null) {
|
|
2198
|
+
const Database = getNodeSqliteDatabaseSync();
|
|
2199
|
+
const dbFile = getOpencodeDbFile();
|
|
2200
|
+
if (!Database || !dbFile || !fs.existsSync(dbFile)) {
|
|
2201
|
+
return fallback;
|
|
2202
|
+
}
|
|
2203
|
+
let db;
|
|
2204
|
+
try {
|
|
2205
|
+
db = new Database(dbFile, { readOnly: true });
|
|
2206
|
+
return callback(db, dbFile);
|
|
2207
|
+
} catch (_) {
|
|
2208
|
+
return fallback;
|
|
2209
|
+
} finally {
|
|
2210
|
+
if (db) {
|
|
2211
|
+
try { db.close(); } catch (_) { }
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
function parseJsonObjectSafe(value) {
|
|
2217
|
+
if (!value || typeof value !== 'string') return null;
|
|
2218
|
+
try {
|
|
2219
|
+
const parsed = JSON.parse(value);
|
|
2220
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
2221
|
+
} catch (_) {
|
|
2222
|
+
return null;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
function toOpencodeIsoTime(value, fallback = '') {
|
|
2227
|
+
const numeric = Number(value);
|
|
2228
|
+
if (Number.isFinite(numeric) && numeric > 0) {
|
|
2229
|
+
return toIsoTime(numeric, fallback);
|
|
2230
|
+
}
|
|
2231
|
+
return toIsoTime(value, fallback);
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
function readOpencodeModelName(value) {
|
|
2235
|
+
const parsed = typeof value === 'string' ? parseJsonObjectSafe(value) : (value && typeof value === 'object' ? value : null);
|
|
2236
|
+
if (!parsed) return '';
|
|
2237
|
+
return typeof parsed.id === 'string' && parsed.id.trim()
|
|
2238
|
+
? parsed.id.trim()
|
|
2239
|
+
: (typeof parsed.modelID === 'string' ? parsed.modelID.trim() : '');
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
function readOpencodeProviderName(value) {
|
|
2243
|
+
const parsed = typeof value === 'string' ? parseJsonObjectSafe(value) : (value && typeof value === 'object' ? value : null);
|
|
2244
|
+
if (!parsed) return 'opencode';
|
|
2245
|
+
return typeof parsed.providerID === 'string' && parsed.providerID.trim() ? parsed.providerID.trim() : 'opencode';
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
function extractOpencodeTextFromPartData(data) {
|
|
2249
|
+
const part = typeof data === 'string' ? parseJsonObjectSafe(data) : (data && typeof data === 'object' ? data : null);
|
|
2250
|
+
if (!part) return '';
|
|
2251
|
+
const type = typeof part.type === 'string' ? part.type.trim().toLowerCase() : '';
|
|
2252
|
+
if (type !== 'text' && type !== 'reasoning') return '';
|
|
2253
|
+
return typeof part.text === 'string' ? part.text.trim() : '';
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
function normalizeOpencodeMessageRow(row) {
|
|
2257
|
+
if (!row || typeof row !== 'object') return null;
|
|
2258
|
+
const messageData = parseJsonObjectSafe(row.message_data || row.data || '');
|
|
2259
|
+
const role = normalizeRole((messageData && messageData.role) || row.role || '');
|
|
2260
|
+
if (role !== 'user' && role !== 'assistant' && role !== 'system') return null;
|
|
2261
|
+
const rawParts = typeof row.parts === 'string' && row.parts ? row.parts.split('\n') : [];
|
|
2262
|
+
const textParts = rawParts.map(extractOpencodeTextFromPartData).filter(Boolean);
|
|
2263
|
+
const text = textParts.join('\n').trim();
|
|
2264
|
+
if (!text) return null;
|
|
2265
|
+
const model = messageData ? (messageData.modelID || readOpencodeModelName(messageData.model)) : '';
|
|
2266
|
+
const provider = messageData ? (messageData.providerID || readOpencodeProviderName(messageData.model)) : 'opencode';
|
|
2267
|
+
return {
|
|
2268
|
+
role,
|
|
2269
|
+
text,
|
|
2270
|
+
timestamp: toOpencodeIsoTime(row.time_created || (messageData && messageData.time && messageData.time.created), ''),
|
|
2271
|
+
model: typeof model === 'string' ? model : '',
|
|
2272
|
+
provider: typeof provider === 'string' && provider ? provider : 'opencode'
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
function queryOpencodeSessionMessages(sessionId, options = {}) {
|
|
2277
|
+
const id = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
2278
|
+
if (!id) return null;
|
|
2279
|
+
const maxMessages = resolveMaxMessagesValue(options.maxMessages, DEFAULT_SESSION_DETAIL_MESSAGES);
|
|
2280
|
+
const limit = maxMessages === Infinity ? OPENCODE_DB_READ_LIMIT : Math.min(OPENCODE_DB_READ_LIMIT, Math.max(1, Math.floor(maxMessages)));
|
|
2281
|
+
return withOpencodeDb((db) => {
|
|
2282
|
+
const totalRow = db.prepare('select count(*) as count from message where session_id = ?').get(id) || {};
|
|
2283
|
+
const totalMessages = Math.max(0, Math.floor(Number(totalRow.count) || 0));
|
|
2284
|
+
const rows = db.prepare(`
|
|
2285
|
+
select m.id as message_id, m.session_id, m.time_created, m.time_updated, m.data as message_data,
|
|
2286
|
+
group_concat(p.data, char(10)) as parts
|
|
2287
|
+
from (
|
|
2288
|
+
select * from message where session_id = ? order by time_created desc limit ?
|
|
2289
|
+
) m
|
|
2290
|
+
left join part p on p.message_id = m.id
|
|
2291
|
+
group by m.id
|
|
2292
|
+
order by m.time_created asc
|
|
2293
|
+
`).all(id, limit);
|
|
2294
|
+
const messages = rows.map(normalizeOpencodeMessageRow).filter(Boolean);
|
|
2295
|
+
const sessionRow = db.prepare('select id, directory, path, time_updated from session where id = ?').get(id) || null;
|
|
2296
|
+
return {
|
|
2297
|
+
sessionId: id,
|
|
2298
|
+
cwd: sessionRow && (sessionRow.path || sessionRow.directory) ? (sessionRow.path || sessionRow.directory) : '',
|
|
2299
|
+
updatedAt: sessionRow ? toOpencodeIsoTime(sessionRow.time_updated, '') : '',
|
|
2300
|
+
messages,
|
|
2301
|
+
totalMessages,
|
|
2302
|
+
clipped: maxMessages !== Infinity && totalMessages > messages.length,
|
|
2303
|
+
truncated: maxMessages !== Infinity && totalMessages > messages.length
|
|
2304
|
+
};
|
|
2305
|
+
}, null);
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
function countOpencodeMessages(sessionId) {
|
|
2309
|
+
const id = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
2310
|
+
if (!id) return null;
|
|
2311
|
+
return withOpencodeDb((db) => {
|
|
2312
|
+
const row = db.prepare('select count(*) as count from message where session_id = ?').get(id) || {};
|
|
2313
|
+
return Math.max(0, Math.floor(Number(row.count) || 0));
|
|
2314
|
+
}, null);
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
function parseOpencodeSessionSummaryRow(row, db) {
|
|
2318
|
+
if (!row || typeof row !== 'object') return null;
|
|
2319
|
+
const sessionId = typeof row.id === 'string' ? row.id.trim() : '';
|
|
2320
|
+
if (!sessionId) return null;
|
|
2321
|
+
const model = readOpencodeModelName(row.model);
|
|
2322
|
+
const provider = readOpencodeProviderName(row.model);
|
|
2323
|
+
let title = typeof row.title === 'string' && row.title.trim() ? row.title.trim() : (row.slug || sessionId);
|
|
2324
|
+
const firstRow = db.prepare(`
|
|
2325
|
+
select m.data as message_data, group_concat(p.data, char(10)) as parts
|
|
2326
|
+
from message m left join part p on p.message_id = m.id
|
|
2327
|
+
where m.session_id = ?
|
|
2328
|
+
group by m.id
|
|
2329
|
+
order by m.time_created asc
|
|
2330
|
+
limit 12
|
|
2331
|
+
`).all(sessionId).map(normalizeOpencodeMessageRow).filter(Boolean).find(item => item.role === 'user' && item.text);
|
|
2332
|
+
if (firstRow && firstRow.text) {
|
|
2333
|
+
title = truncateText(firstRow.text, 120);
|
|
2334
|
+
}
|
|
2335
|
+
const messageCount = Math.max(0, Math.floor(Number(row.message_count) || 0));
|
|
2336
|
+
const inputTokens = readNonNegativeInteger(row.tokens_input) || 0;
|
|
2337
|
+
const outputTokens = readNonNegativeInteger(row.tokens_output) || 0;
|
|
2338
|
+
const reasoningOutputTokens = readNonNegativeInteger(row.tokens_reasoning) || 0;
|
|
2339
|
+
const cachedInputTokens = readNonNegativeInteger(row.tokens_cache_read) || 0;
|
|
2340
|
+
const cacheCreationInputTokens = readNonNegativeInteger(row.tokens_cache_write) || 0;
|
|
2341
|
+
const totalTokens = inputTokens + outputTokens + reasoningOutputTokens + cachedInputTokens + cacheCreationInputTokens;
|
|
2342
|
+
return {
|
|
2343
|
+
source: 'opencode',
|
|
2344
|
+
sourceLabel: 'OpenCode',
|
|
2345
|
+
provider,
|
|
2346
|
+
model,
|
|
2347
|
+
models: model ? [model] : [],
|
|
2348
|
+
sessionId,
|
|
2349
|
+
title,
|
|
2350
|
+
cwd: row.path || row.directory || '',
|
|
2351
|
+
createdAt: toOpencodeIsoTime(row.time_created, ''),
|
|
2352
|
+
updatedAt: toOpencodeIsoTime(row.time_updated, ''),
|
|
2353
|
+
messageCount,
|
|
2354
|
+
totalTokens,
|
|
2355
|
+
contextWindow: 0,
|
|
2356
|
+
inputTokens,
|
|
2357
|
+
cachedInputTokens,
|
|
2358
|
+
cacheCreationInputTokens,
|
|
2359
|
+
outputTokens,
|
|
2360
|
+
reasoningOutputTokens,
|
|
2361
|
+
__messageCountExact: true,
|
|
2362
|
+
filePath: buildOpencodeVirtualSessionPath(sessionId),
|
|
2363
|
+
keywords: [],
|
|
2364
|
+
capabilities: { code: true }
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
function parseOpencodeSessionSummary(sessionId) {
|
|
2369
|
+
const id = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
2370
|
+
if (!id) return null;
|
|
2371
|
+
return withOpencodeDb((db) => {
|
|
2372
|
+
const row = db.prepare(`
|
|
2373
|
+
select s.*, (select count(*) from message m where m.session_id = s.id) as message_count
|
|
2374
|
+
from session s where s.id = ?
|
|
2375
|
+
`).get(id);
|
|
2376
|
+
return parseOpencodeSessionSummaryRow(row, db);
|
|
2377
|
+
}, null);
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
function listOpencodeSessions(limit, options = {}) {
|
|
2381
|
+
if (!isOpencodeDbAvailable()) return [];
|
|
2382
|
+
const scanFactor = Number.isFinite(Number(options.scanFactor)) ? Math.max(1, Number(options.scanFactor)) : SESSION_SCAN_FACTOR;
|
|
2383
|
+
const targetCount = Number.isFinite(Number(options.targetCount)) ? Math.max(1, Math.floor(Number(options.targetCount))) : Math.max(1, Math.floor(limit * scanFactor));
|
|
2384
|
+
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));
|
|
2385
|
+
return withOpencodeDb((db) => {
|
|
2386
|
+
const rows = db.prepare(`
|
|
2387
|
+
select s.*, (select count(*) from message m where m.session_id = s.id) as message_count
|
|
2388
|
+
from session s
|
|
2389
|
+
where s.time_archived is null
|
|
2390
|
+
order by s.time_updated desc
|
|
2391
|
+
limit ?
|
|
2392
|
+
`).all(Math.min(OPENCODE_DB_READ_LIMIT, Math.max(scanCount, targetCount)));
|
|
2393
|
+
const sessions = [];
|
|
2394
|
+
for (const row of rows) {
|
|
2395
|
+
const summary = parseOpencodeSessionSummaryRow(row, db);
|
|
2396
|
+
if (summary) sessions.push(summary);
|
|
2397
|
+
if (sessions.length >= targetCount) break;
|
|
2398
|
+
}
|
|
2399
|
+
return mergeAndLimitSessions(sessions, limit);
|
|
2400
|
+
}, []);
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2131
2403
|
function getCodexmateDerivedSessionsRoot(target) {
|
|
2132
2404
|
if (target === 'claude') {
|
|
2133
2405
|
return CODEXMATE_DERIVED_CLAUDE_DIR;
|
|
@@ -4084,7 +4356,7 @@ function countConversationMessagesInRecords(records, source) {
|
|
|
4084
4356
|
}
|
|
4085
4357
|
continue;
|
|
4086
4358
|
}
|
|
4087
|
-
if (source === 'codebuddy') {
|
|
4359
|
+
if (source === 'codebuddy' || source === 'workbuddy') {
|
|
4088
4360
|
if (record && record.type === 'message') {
|
|
4089
4361
|
const role = normalizeRole(record.role);
|
|
4090
4362
|
if (role === 'assistant' || role === 'user' || role === 'system') {
|
|
@@ -4167,7 +4439,7 @@ async function countConversationMessagesInFile(filePath, source) {
|
|
|
4167
4439
|
role = normalizeRole(record.payload.role);
|
|
4168
4440
|
text = extractMessageText(record.payload.content);
|
|
4169
4441
|
}
|
|
4170
|
-
} else if (source === 'codebuddy') {
|
|
4442
|
+
} else if (source === 'codebuddy' || source === 'workbuddy') {
|
|
4171
4443
|
if (record && record.type === 'message') {
|
|
4172
4444
|
role = normalizeRole(record.role);
|
|
4173
4445
|
if (role === 'assistant' || role === 'user' || role === 'system') {
|
|
@@ -4347,15 +4619,7 @@ async function resolveSessionTrashEntryExactMessageCount(entry) {
|
|
|
4347
4619
|
}
|
|
4348
4620
|
|
|
4349
4621
|
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'))));
|
|
4622
|
+
const source = normalizeSessionSourceName(options.source, '') || 'all';
|
|
4359
4623
|
const hydratedEntries = await mapWithConcurrency(Array.isArray(entries) ? entries : [], 8, async (entry) => {
|
|
4360
4624
|
const normalizedEntry = normalizeSessionTrashEntry(entry);
|
|
4361
4625
|
if (!normalizedEntry) {
|
|
@@ -4364,7 +4628,7 @@ async function hydrateSessionTrashEntries(entries, options = {}) {
|
|
|
4364
4628
|
return await resolveSessionTrashEntryExactMessageCount(normalizedEntry);
|
|
4365
4629
|
});
|
|
4366
4630
|
|
|
4367
|
-
if (source
|
|
4631
|
+
if (normalizeSessionSourceName(source, '')) {
|
|
4368
4632
|
return hydratedEntries.filter((entry) => entry.source === source);
|
|
4369
4633
|
}
|
|
4370
4634
|
return hydratedEntries;
|
|
@@ -4378,15 +4642,7 @@ async function hydrateSessionItemsExactMessageCount(items) {
|
|
|
4378
4642
|
if (item.__messageCountExact === true) {
|
|
4379
4643
|
return item;
|
|
4380
4644
|
}
|
|
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' : ''))));
|
|
4645
|
+
const source = normalizeSessionSourceName(item.source, '');
|
|
4390
4646
|
const filePath = typeof item.filePath === 'string' ? item.filePath : '';
|
|
4391
4647
|
if (!source || !filePath || !fs.existsSync(filePath)) {
|
|
4392
4648
|
return item;
|
|
@@ -4413,6 +4669,40 @@ async function hydrateSessionItemsExactMessageCount(items) {
|
|
|
4413
4669
|
});
|
|
4414
4670
|
}
|
|
4415
4671
|
|
|
4672
|
+
|
|
4673
|
+
function normalizeSessionSourceName(value, fallback = '') {
|
|
4674
|
+
const source = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
4675
|
+
if (source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'pi' || source === 'opencode') {
|
|
4676
|
+
return source;
|
|
4677
|
+
}
|
|
4678
|
+
return fallback;
|
|
4679
|
+
}
|
|
4680
|
+
|
|
4681
|
+
function isSessionSourceOrAll(value) {
|
|
4682
|
+
const source = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
4683
|
+
return source === 'all' || !!normalizeSessionSourceName(source, '');
|
|
4684
|
+
}
|
|
4685
|
+
|
|
4686
|
+
function getSessionSourceLabel(source) {
|
|
4687
|
+
if (source === 'claude') return 'Claude Code';
|
|
4688
|
+
if (source === 'gemini') return 'Gemini CLI';
|
|
4689
|
+
if (source === 'codebuddy') return 'CodeBuddy Code';
|
|
4690
|
+
if (source === 'workbuddy') return 'WorkBuddy AI';
|
|
4691
|
+
if (source === 'pi') return 'Pi';
|
|
4692
|
+
if (source === 'opencode') return 'OpenCode';
|
|
4693
|
+
return 'Codex';
|
|
4694
|
+
}
|
|
4695
|
+
|
|
4696
|
+
function getSessionSourceProvider(source) {
|
|
4697
|
+
if (source === 'claude') return 'claude';
|
|
4698
|
+
if (source === 'gemini') return 'gemini';
|
|
4699
|
+
if (source === 'codebuddy') return 'codebuddy';
|
|
4700
|
+
if (source === 'workbuddy') return 'workbuddy';
|
|
4701
|
+
if (source === 'pi') return 'pi';
|
|
4702
|
+
if (source === 'opencode') return 'opencode';
|
|
4703
|
+
return 'codex';
|
|
4704
|
+
}
|
|
4705
|
+
|
|
4416
4706
|
function getSessionExportKeyForApi(item) {
|
|
4417
4707
|
const source = item && item.source ? String(item.source).trim() : '';
|
|
4418
4708
|
const sessionId = item && item.sessionId ? String(item.sessionId) : '';
|
|
@@ -4430,20 +4720,21 @@ async function readSessionMessageCounts(params = {}) {
|
|
|
4430
4720
|
return undefined;
|
|
4431
4721
|
}
|
|
4432
4722
|
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' : ''))));
|
|
4723
|
+
const source = normalizeSessionSourceName(item.source, '');
|
|
4442
4724
|
const filePath = typeof item.filePath === 'string' ? item.filePath : '';
|
|
4443
|
-
if (!source
|
|
4725
|
+
if (!source) {
|
|
4444
4726
|
return { key };
|
|
4445
4727
|
}
|
|
4446
|
-
|
|
4728
|
+
let exactMessageCount;
|
|
4729
|
+
if (source === 'opencode') {
|
|
4730
|
+
const opencodeSessionId = item.sessionId || parseOpencodeVirtualSessionId(filePath);
|
|
4731
|
+
exactMessageCount = countOpencodeMessages(opencodeSessionId);
|
|
4732
|
+
} else {
|
|
4733
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
4734
|
+
return { key };
|
|
4735
|
+
}
|
|
4736
|
+
exactMessageCount = await countConversationMessagesInFile(filePath, source);
|
|
4737
|
+
}
|
|
4447
4738
|
if (!Number.isFinite(Number(exactMessageCount))) {
|
|
4448
4739
|
return { key };
|
|
4449
4740
|
}
|
|
@@ -4573,6 +4864,10 @@ function createSessionQueryScanState(tokens, options = {}) {
|
|
|
4573
4864
|
const snippetLimit = Number.isFinite(Number(options.snippetLimit))
|
|
4574
4865
|
? Math.max(0, Number(options.snippetLimit))
|
|
4575
4866
|
: 0;
|
|
4867
|
+
const positionsLimitRaw = Number(options.positionsLimit);
|
|
4868
|
+
const positionsLimit = Number.isFinite(positionsLimitRaw) && positionsLimitRaw > 0
|
|
4869
|
+
? Math.floor(positionsLimitRaw)
|
|
4870
|
+
: 0;
|
|
4576
4871
|
|
|
4577
4872
|
return {
|
|
4578
4873
|
tokens,
|
|
@@ -4580,8 +4875,10 @@ function createSessionQueryScanState(tokens, options = {}) {
|
|
|
4580
4875
|
roleFilter,
|
|
4581
4876
|
maxMatches,
|
|
4582
4877
|
snippetLimit,
|
|
4878
|
+
positionsLimit,
|
|
4583
4879
|
count: 0,
|
|
4584
4880
|
snippets: [],
|
|
4881
|
+
positions: [],
|
|
4585
4882
|
leadingSystem: roleFilter !== 'system'
|
|
4586
4883
|
};
|
|
4587
4884
|
}
|
|
@@ -4613,6 +4910,13 @@ function consumeSessionQueryMessage(state, message) {
|
|
|
4613
4910
|
if (state.snippetLimit > 0 && state.snippets.length < state.snippetLimit) {
|
|
4614
4911
|
state.snippets.push(truncateText(text));
|
|
4615
4912
|
}
|
|
4913
|
+
if (state.positionsLimit > 0 && state.positions.length < state.positionsLimit) {
|
|
4914
|
+
state.positions.push({
|
|
4915
|
+
lineIndex: Number.isInteger(message.recordLineIndex) ? message.recordLineIndex : -1,
|
|
4916
|
+
role,
|
|
4917
|
+
timestamp: typeof message.timestamp === 'string' ? message.timestamp : ''
|
|
4918
|
+
});
|
|
4919
|
+
}
|
|
4616
4920
|
return state.count >= state.maxMatches;
|
|
4617
4921
|
}
|
|
4618
4922
|
|
|
@@ -4620,7 +4924,8 @@ function buildSessionQueryScanResult(state) {
|
|
|
4620
4924
|
return {
|
|
4621
4925
|
hit: !!(state && state.count > 0),
|
|
4622
4926
|
count: state && Number.isFinite(state.count) ? state.count : 0,
|
|
4623
|
-
snippets: state && Array.isArray(state.snippets) ? state.snippets : []
|
|
4927
|
+
snippets: state && Array.isArray(state.snippets) ? state.snippets : [],
|
|
4928
|
+
positions: state && Array.isArray(state.positions) ? state.positions : []
|
|
4624
4929
|
};
|
|
4625
4930
|
}
|
|
4626
4931
|
|
|
@@ -4628,12 +4933,14 @@ function scanSessionContentForQueryInRecords(records, source, state) {
|
|
|
4628
4933
|
if (!Array.isArray(records) || !state) {
|
|
4629
4934
|
return buildSessionQueryScanResult(state);
|
|
4630
4935
|
}
|
|
4631
|
-
|
|
4632
|
-
|
|
4936
|
+
for (let recordIndex = 0; recordIndex < records.length; recordIndex++) {
|
|
4937
|
+
const record = records[recordIndex];
|
|
4633
4938
|
const message = extractMessageFromRecord(record, source);
|
|
4634
4939
|
if (!message) {
|
|
4635
4940
|
continue;
|
|
4636
4941
|
}
|
|
4942
|
+
message.recordLineIndex = recordIndex;
|
|
4943
|
+
message.timestamp = toIsoTime(record && record.timestamp, '');
|
|
4637
4944
|
if (consumeSessionQueryMessage(state, message)) {
|
|
4638
4945
|
break;
|
|
4639
4946
|
}
|
|
@@ -4644,12 +4951,12 @@ function scanSessionContentForQueryInRecords(records, source, state) {
|
|
|
4644
4951
|
|
|
4645
4952
|
async function scanSessionContentForQuery(session, tokens, options = {}) {
|
|
4646
4953
|
if (!session || !Array.isArray(tokens) || tokens.length === 0) {
|
|
4647
|
-
return { hit: false, count: 0, snippets: [] };
|
|
4954
|
+
return { hit: false, count: 0, snippets: [], positions: [] };
|
|
4648
4955
|
}
|
|
4649
4956
|
|
|
4650
4957
|
const filePath = resolveSessionFilePath(session.source, session.filePath, session.sessionId);
|
|
4651
4958
|
if (!filePath) {
|
|
4652
|
-
return { hit: false, count: 0, snippets: [] };
|
|
4959
|
+
return { hit: false, count: 0, snippets: [], positions: [] };
|
|
4653
4960
|
}
|
|
4654
4961
|
|
|
4655
4962
|
const rawMaxBytes = Number(options.maxBytes);
|
|
@@ -4657,8 +4964,17 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
|
|
|
4657
4964
|
? Math.max(1024, rawMaxBytes)
|
|
4658
4965
|
: 0;
|
|
4659
4966
|
const state = createSessionQueryScanState(tokens, options);
|
|
4967
|
+
if (session.source === 'opencode') {
|
|
4968
|
+
const extracted = queryOpencodeSessionMessages(session.sessionId || parseOpencodeVirtualSessionId(filePath), { maxMessages: Infinity });
|
|
4969
|
+
const messages = extracted && Array.isArray(extracted.messages) ? extracted.messages : [];
|
|
4970
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
4971
|
+
const message = { ...messages[i], recordLineIndex: i };
|
|
4972
|
+
if (consumeSessionQueryMessage(state, message)) break;
|
|
4973
|
+
}
|
|
4974
|
+
return buildSessionQueryScanResult(state);
|
|
4975
|
+
}
|
|
4660
4976
|
if (session.source === 'gemini') {
|
|
4661
|
-
if (state.roleFilter !== 'all') {
|
|
4977
|
+
if (state.roleFilter !== 'all' || state.positionsLimit > 0) {
|
|
4662
4978
|
let json;
|
|
4663
4979
|
try {
|
|
4664
4980
|
json = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
@@ -4672,7 +4988,12 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
|
|
|
4672
4988
|
if (!role) continue;
|
|
4673
4989
|
const text = extractMessageText(extractGeminiMessageText(entry.content ?? entry.message ?? entry.text));
|
|
4674
4990
|
if (!text) continue;
|
|
4675
|
-
if (consumeSessionQueryMessage(state, {
|
|
4991
|
+
if (consumeSessionQueryMessage(state, {
|
|
4992
|
+
role,
|
|
4993
|
+
text,
|
|
4994
|
+
timestamp: toIsoTime(entry.timestamp ?? entry.time ?? entry.at, ''),
|
|
4995
|
+
recordLineIndex: -1
|
|
4996
|
+
})) {
|
|
4676
4997
|
break;
|
|
4677
4998
|
}
|
|
4678
4999
|
}
|
|
@@ -4712,7 +5033,10 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
|
|
|
4712
5033
|
rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
4713
5034
|
|
|
4714
5035
|
let bytesRead = 0;
|
|
5036
|
+
let lineIndex = 0;
|
|
4715
5037
|
for await (const line of rl) {
|
|
5038
|
+
const currentLineIndex = lineIndex;
|
|
5039
|
+
lineIndex += 1;
|
|
4716
5040
|
if (maxBytes > 0 && bytesRead >= maxBytes) {
|
|
4717
5041
|
break;
|
|
4718
5042
|
}
|
|
@@ -4734,6 +5058,8 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
|
|
|
4734
5058
|
if (!message) {
|
|
4735
5059
|
continue;
|
|
4736
5060
|
}
|
|
5061
|
+
message.recordLineIndex = currentLineIndex;
|
|
5062
|
+
message.timestamp = toIsoTime(record.timestamp, '');
|
|
4737
5063
|
if (consumeSessionQueryMessage(state, message)) {
|
|
4738
5064
|
break;
|
|
4739
5065
|
}
|
|
@@ -4952,13 +5278,7 @@ function setSessionListCache(cacheKey, value) {
|
|
|
4952
5278
|
}
|
|
4953
5279
|
|
|
4954
5280
|
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')));
|
|
5281
|
+
const normalizedSource = normalizeSessionSourceName(source, 'codex');
|
|
4962
5282
|
const normalizedLimit = Number.isFinite(Number(limit))
|
|
4963
5283
|
? Math.max(1, Math.floor(Number(limit)))
|
|
4964
5284
|
: 1;
|
|
@@ -5044,7 +5364,7 @@ function getSessionInventoryCache(cacheKey, forceRefresh = false) {
|
|
|
5044
5364
|
}
|
|
5045
5365
|
|
|
5046
5366
|
function registerSessionFileLookupEntries(source, sessions = []) {
|
|
5047
|
-
const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi'
|
|
5367
|
+
const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'pi' || source === 'opencode'
|
|
5048
5368
|
? source
|
|
5049
5369
|
: 'codex';
|
|
5050
5370
|
const store = g_sessionFileLookupCache[normalizedSource];
|
|
@@ -5085,7 +5405,7 @@ function setSessionInventoryCache(cacheKey, source, value) {
|
|
|
5085
5405
|
}
|
|
5086
5406
|
|
|
5087
5407
|
function listSessionInventoryBySource(source, limit, scanOptions = {}, options = {}) {
|
|
5088
|
-
const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi'
|
|
5408
|
+
const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'pi' || source === 'opencode'
|
|
5089
5409
|
? source
|
|
5090
5410
|
: 'codex';
|
|
5091
5411
|
const forceRefresh = !!options.forceRefresh;
|
|
@@ -5101,9 +5421,13 @@ function listSessionInventoryBySource(source, limit, scanOptions = {}, options =
|
|
|
5101
5421
|
? listGeminiSessions(limit, scanOptions)
|
|
5102
5422
|
: (normalizedSource === 'codebuddy'
|
|
5103
5423
|
? listCodeBuddySessions(limit, scanOptions)
|
|
5104
|
-
: (normalizedSource === '
|
|
5105
|
-
?
|
|
5106
|
-
:
|
|
5424
|
+
: (normalizedSource === 'workbuddy'
|
|
5425
|
+
? listWorkBuddySessions(limit, scanOptions)
|
|
5426
|
+
: (normalizedSource === 'pi'
|
|
5427
|
+
? listPiSessions(limit, scanOptions)
|
|
5428
|
+
: (normalizedSource === 'opencode'
|
|
5429
|
+
? listOpencodeSessions(limit, scanOptions)
|
|
5430
|
+
: listCodexSessions(limit, scanOptions))))));
|
|
5107
5431
|
setSessionInventoryCache(cacheKey, normalizedSource, sessions);
|
|
5108
5432
|
return sessions;
|
|
5109
5433
|
}
|
|
@@ -5116,7 +5440,9 @@ function invalidateSessionListCache() {
|
|
|
5116
5440
|
claude: new Map(),
|
|
5117
5441
|
gemini: new Map(),
|
|
5118
5442
|
codebuddy: new Map(),
|
|
5119
|
-
|
|
5443
|
+
workbuddy: new Map(),
|
|
5444
|
+
pi: new Map(),
|
|
5445
|
+
opencode: new Map()
|
|
5120
5446
|
};
|
|
5121
5447
|
}
|
|
5122
5448
|
|
|
@@ -5350,13 +5676,7 @@ function readSessionProviderFromRecord(record, source = '') {
|
|
|
5350
5676
|
if (provider) {
|
|
5351
5677
|
return provider;
|
|
5352
5678
|
}
|
|
5353
|
-
return source
|
|
5354
|
-
? 'claude'
|
|
5355
|
-
: (source === 'gemini'
|
|
5356
|
-
? 'gemini'
|
|
5357
|
-
: (source === 'codebuddy'
|
|
5358
|
-
? 'codebuddy'
|
|
5359
|
-
: (source === 'pi' ? 'pi' : 'codex')));
|
|
5679
|
+
return getSessionSourceProvider(source);
|
|
5360
5680
|
}
|
|
5361
5681
|
|
|
5362
5682
|
function applySessionUsageSummaryFromRecord(state, record, source) {
|
|
@@ -5901,6 +6221,175 @@ function parseCodeBuddySessionSummary(filePath, options = {}) {
|
|
|
5901
6221
|
};
|
|
5902
6222
|
}
|
|
5903
6223
|
|
|
6224
|
+
// WorkBuddy AI 会话记录与 CodeBuddy 同族(type:"message" + 顶层 role/content,
|
|
6225
|
+
// 且存在 __codebuddyLocal 字段),标题来自 ai-title 记录,模型名位于 providerData.model,
|
|
6226
|
+
// token 用量位于嵌套 message.usage。
|
|
6227
|
+
function parseWorkBuddySessionSummary(filePath, options = {}) {
|
|
6228
|
+
const summaryReadBytes = Number.isFinite(Number(options.summaryReadBytes))
|
|
6229
|
+
? Math.max(1024, Math.floor(Number(options.summaryReadBytes)))
|
|
6230
|
+
: SESSION_SUMMARY_READ_BYTES;
|
|
6231
|
+
const titleReadBytes = Number.isFinite(Number(options.titleReadBytes))
|
|
6232
|
+
? Math.max(1024, Math.floor(Number(options.titleReadBytes)))
|
|
6233
|
+
: SESSION_TITLE_READ_BYTES;
|
|
6234
|
+
const records = parseJsonlHeadRecords(filePath, summaryReadBytes);
|
|
6235
|
+
if (records.length === 0) {
|
|
6236
|
+
return null;
|
|
6237
|
+
}
|
|
6238
|
+
|
|
6239
|
+
let stat;
|
|
6240
|
+
try {
|
|
6241
|
+
stat = fs.statSync(filePath);
|
|
6242
|
+
} catch (_) {
|
|
6243
|
+
return null;
|
|
6244
|
+
}
|
|
6245
|
+
|
|
6246
|
+
let sessionId = path.basename(filePath, '.jsonl');
|
|
6247
|
+
let cwd = '';
|
|
6248
|
+
let firstPrompt = '';
|
|
6249
|
+
let aiTitle = '';
|
|
6250
|
+
let messageCount = 0;
|
|
6251
|
+
let totalTokens = 0;
|
|
6252
|
+
let contextWindow = 0;
|
|
6253
|
+
let inputTokens = 0;
|
|
6254
|
+
let cachedInputTokens = 0;
|
|
6255
|
+
let cacheCreationInputTokens = 0;
|
|
6256
|
+
let outputTokens = 0;
|
|
6257
|
+
let reasoningOutputTokens = 0;
|
|
6258
|
+
let provider = 'workbuddy';
|
|
6259
|
+
let model = '';
|
|
6260
|
+
const models = [];
|
|
6261
|
+
const usageState = { totalTokens, contextWindow, inputTokens, cachedInputTokens, cacheCreationInputTokens, outputTokens, reasoningOutputTokens };
|
|
6262
|
+
const previewMessages = [];
|
|
6263
|
+
let createdAt = '';
|
|
6264
|
+
let updatedAt = stat.mtime.toISOString();
|
|
6265
|
+
|
|
6266
|
+
for (const record of records) {
|
|
6267
|
+
if (!createdAt && record && record.timestamp) {
|
|
6268
|
+
createdAt = toIsoTime(record.timestamp, createdAt);
|
|
6269
|
+
}
|
|
6270
|
+
if (record && record.timestamp) {
|
|
6271
|
+
updatedAt = updateLatestIso(updatedAt, record.timestamp);
|
|
6272
|
+
}
|
|
6273
|
+
|
|
6274
|
+
applySessionUsageSummaryFromRecord(usageState, record, 'workbuddy');
|
|
6275
|
+
totalTokens = usageState.totalTokens || 0;
|
|
6276
|
+
contextWindow = usageState.contextWindow || 0;
|
|
6277
|
+
inputTokens = usageState.inputTokens || 0;
|
|
6278
|
+
cachedInputTokens = usageState.cachedInputTokens || 0;
|
|
6279
|
+
cacheCreationInputTokens = usageState.cacheCreationInputTokens || 0;
|
|
6280
|
+
outputTokens = usageState.outputTokens || 0;
|
|
6281
|
+
reasoningOutputTokens = usageState.reasoningOutputTokens || 0;
|
|
6282
|
+
|
|
6283
|
+
if (record && typeof record.sessionId === 'string' && record.sessionId.trim()) {
|
|
6284
|
+
sessionId = record.sessionId.trim();
|
|
6285
|
+
}
|
|
6286
|
+
if (!cwd && record && typeof record.cwd === 'string' && record.cwd.trim()) {
|
|
6287
|
+
cwd = record.cwd.trim();
|
|
6288
|
+
}
|
|
6289
|
+
|
|
6290
|
+
provider = readExplicitSessionProviderFromRecord(record) || provider;
|
|
6291
|
+
const recordModels = readSessionModelsFromRecord(record);
|
|
6292
|
+
for (const recordModel of recordModels) {
|
|
6293
|
+
if (!models.includes(recordModel)) {
|
|
6294
|
+
models.push(recordModel);
|
|
6295
|
+
}
|
|
6296
|
+
}
|
|
6297
|
+
model = recordModels[0] || model;
|
|
6298
|
+
|
|
6299
|
+
if (record && record.type === 'ai-title' && typeof record.aiTitle === 'string' && record.aiTitle.trim()) {
|
|
6300
|
+
aiTitle = record.aiTitle.trim();
|
|
6301
|
+
}
|
|
6302
|
+
|
|
6303
|
+
if (record && record.type === 'message') {
|
|
6304
|
+
const role = normalizeRole(record.role);
|
|
6305
|
+
if (role === 'assistant' || role === 'user' || role === 'system') {
|
|
6306
|
+
const content = record.message?.content ?? record.content ?? '';
|
|
6307
|
+
previewMessages.push({
|
|
6308
|
+
role,
|
|
6309
|
+
text: extractMessageText(content)
|
|
6310
|
+
});
|
|
6311
|
+
}
|
|
6312
|
+
}
|
|
6313
|
+
}
|
|
6314
|
+
|
|
6315
|
+
const tailRecords = parseJsonlTailRecords(filePath, summaryReadBytes);
|
|
6316
|
+
for (const record of tailRecords) {
|
|
6317
|
+
applySessionUsageSummaryFromRecord(usageState, record, 'workbuddy');
|
|
6318
|
+
totalTokens = usageState.totalTokens || 0;
|
|
6319
|
+
contextWindow = usageState.contextWindow || 0;
|
|
6320
|
+
inputTokens = usageState.inputTokens || 0;
|
|
6321
|
+
cachedInputTokens = usageState.cachedInputTokens || 0;
|
|
6322
|
+
cacheCreationInputTokens = usageState.cacheCreationInputTokens || 0;
|
|
6323
|
+
outputTokens = usageState.outputTokens || 0;
|
|
6324
|
+
reasoningOutputTokens = usageState.reasoningOutputTokens || 0;
|
|
6325
|
+
provider = readExplicitSessionProviderFromRecord(record) || provider;
|
|
6326
|
+
const recordModels = readSessionModelsFromRecord(record);
|
|
6327
|
+
for (const recordModel of recordModels) {
|
|
6328
|
+
if (!models.includes(recordModel)) {
|
|
6329
|
+
models.push(recordModel);
|
|
6330
|
+
}
|
|
6331
|
+
}
|
|
6332
|
+
model = recordModels[0] || model;
|
|
6333
|
+
}
|
|
6334
|
+
|
|
6335
|
+
const filteredPreviewMessages = removeLeadingSystemMessage(previewMessages);
|
|
6336
|
+
messageCount = filteredPreviewMessages.length;
|
|
6337
|
+
const firstUser = filteredPreviewMessages.find(item => item.role === 'user' && item.text);
|
|
6338
|
+
if (firstUser) {
|
|
6339
|
+
firstPrompt = truncateText(firstUser.text);
|
|
6340
|
+
}
|
|
6341
|
+
|
|
6342
|
+
if (!firstPrompt) {
|
|
6343
|
+
const titleRecords = parseJsonlHeadRecords(filePath, titleReadBytes);
|
|
6344
|
+
const titleMessages = [];
|
|
6345
|
+
for (const record of titleRecords) {
|
|
6346
|
+
if (record && record.type === 'message') {
|
|
6347
|
+
const role = normalizeRole(record.role);
|
|
6348
|
+
if (role === 'assistant' || role === 'user' || role === 'system') {
|
|
6349
|
+
const content = record.message?.content ?? record.content ?? '';
|
|
6350
|
+
titleMessages.push({
|
|
6351
|
+
role,
|
|
6352
|
+
text: extractMessageText(content)
|
|
6353
|
+
});
|
|
6354
|
+
}
|
|
6355
|
+
}
|
|
6356
|
+
}
|
|
6357
|
+
|
|
6358
|
+
const filteredTitleMessages = removeLeadingSystemMessage(titleMessages);
|
|
6359
|
+
const titleUser = filteredTitleMessages.find(item => item.role === 'user' && item.text);
|
|
6360
|
+
if (titleUser) {
|
|
6361
|
+
firstPrompt = truncateText(titleUser.text);
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
|
|
6365
|
+
messageCount = Math.max(0, messageCount);
|
|
6366
|
+
|
|
6367
|
+
return {
|
|
6368
|
+
source: 'workbuddy',
|
|
6369
|
+
sourceLabel: 'WorkBuddy AI',
|
|
6370
|
+
provider,
|
|
6371
|
+
model,
|
|
6372
|
+
models,
|
|
6373
|
+
sessionId,
|
|
6374
|
+
title: aiTitle || firstPrompt || sessionId,
|
|
6375
|
+
cwd,
|
|
6376
|
+
createdAt,
|
|
6377
|
+
updatedAt,
|
|
6378
|
+
messageCount,
|
|
6379
|
+
totalTokens,
|
|
6380
|
+
contextWindow,
|
|
6381
|
+
inputTokens,
|
|
6382
|
+
cachedInputTokens,
|
|
6383
|
+
cacheCreationInputTokens,
|
|
6384
|
+
outputTokens,
|
|
6385
|
+
reasoningOutputTokens,
|
|
6386
|
+
__messageCountExact: isSessionSummaryMessageCountExact(stat, summaryReadBytes),
|
|
6387
|
+
filePath,
|
|
6388
|
+
keywords: [],
|
|
6389
|
+
capabilities: { code: true }
|
|
6390
|
+
};
|
|
6391
|
+
}
|
|
6392
|
+
|
|
5904
6393
|
function extractGeminiMessageText(content) {
|
|
5905
6394
|
if (typeof content === 'string') {
|
|
5906
6395
|
return content;
|
|
@@ -6665,6 +7154,54 @@ function listCodeBuddySessions(limit, options = {}) {
|
|
|
6665
7154
|
return mergeAndLimitSessions(sessions, limit);
|
|
6666
7155
|
}
|
|
6667
7156
|
|
|
7157
|
+
function listWorkBuddySessions(limit, options = {}) {
|
|
7158
|
+
const projectsDir = getWorkBuddyProjectsDir();
|
|
7159
|
+
if (!fs.existsSync(projectsDir)) {
|
|
7160
|
+
return [];
|
|
7161
|
+
}
|
|
7162
|
+
|
|
7163
|
+
const scanFactor = Number.isFinite(Number(options.scanFactor))
|
|
7164
|
+
? Math.max(1, Number(options.scanFactor))
|
|
7165
|
+
: SESSION_SCAN_FACTOR;
|
|
7166
|
+
const minFiles = Number.isFinite(Number(options.minFiles))
|
|
7167
|
+
? Math.max(1, Number(options.minFiles))
|
|
7168
|
+
: Math.min(SESSION_SCAN_MIN_FILES, MAX_SESSION_LIST_SIZE * SESSION_SCAN_FACTOR);
|
|
7169
|
+
const targetCount = Number.isFinite(Number(options.targetCount))
|
|
7170
|
+
? Math.max(1, Math.floor(Number(options.targetCount)))
|
|
7171
|
+
: Math.max(1, Math.floor(limit * scanFactor));
|
|
7172
|
+
const scanCount = Number.isFinite(Number(options.scanCount))
|
|
7173
|
+
? Math.max(targetCount, Math.floor(Number(options.scanCount)))
|
|
7174
|
+
: Math.max(targetCount, minFiles);
|
|
7175
|
+
const maxFilesScanned = Number.isFinite(Number(options.maxFilesScanned))
|
|
7176
|
+
? Math.max(scanCount, Math.floor(Number(options.maxFilesScanned)))
|
|
7177
|
+
: Math.max(scanCount * 2, minFiles);
|
|
7178
|
+
const summaryReadBytes = Number.isFinite(Number(options.summaryReadBytes))
|
|
7179
|
+
? Math.max(1024, Math.floor(Number(options.summaryReadBytes)))
|
|
7180
|
+
: SESSION_SUMMARY_READ_BYTES;
|
|
7181
|
+
const titleReadBytes = Number.isFinite(Number(options.titleReadBytes))
|
|
7182
|
+
? Math.max(1024, Math.floor(Number(options.titleReadBytes)))
|
|
7183
|
+
: SESSION_TITLE_READ_BYTES;
|
|
7184
|
+
|
|
7185
|
+
const files = collectRecentJsonlFiles(projectsDir, {
|
|
7186
|
+
returnCount: scanCount,
|
|
7187
|
+
maxFilesScanned
|
|
7188
|
+
});
|
|
7189
|
+
const sessions = [];
|
|
7190
|
+
for (const filePath of files) {
|
|
7191
|
+
const summary = parseWorkBuddySessionSummary(filePath, {
|
|
7192
|
+
summaryReadBytes,
|
|
7193
|
+
titleReadBytes
|
|
7194
|
+
});
|
|
7195
|
+
if (summary) {
|
|
7196
|
+
sessions.push(summary);
|
|
7197
|
+
}
|
|
7198
|
+
if (sessions.length >= targetCount) {
|
|
7199
|
+
break;
|
|
7200
|
+
}
|
|
7201
|
+
}
|
|
7202
|
+
return mergeAndLimitSessions(sessions, limit);
|
|
7203
|
+
}
|
|
7204
|
+
|
|
6668
7205
|
function listPiSessions(limit, options = {}) {
|
|
6669
7206
|
const sessionsDir = getPiSessionsDir();
|
|
6670
7207
|
if (!fs.existsSync(sessionsDir)) {
|
|
@@ -6714,9 +7251,7 @@ function listPiSessions(limit, options = {}) {
|
|
|
6714
7251
|
}
|
|
6715
7252
|
|
|
6716
7253
|
async function listAllSessions(params = {}) {
|
|
6717
|
-
const source = params.source
|
|
6718
|
-
? params.source
|
|
6719
|
-
: 'all';
|
|
7254
|
+
const source = normalizeSessionSourceName(params.source, '') || 'all';
|
|
6720
7255
|
const rawLimit = Number(params.limit);
|
|
6721
7256
|
const limit = Number.isFinite(rawLimit)
|
|
6722
7257
|
? Math.max(1, Math.min(rawLimit, MAX_SESSION_LIST_SIZE))
|
|
@@ -6764,9 +7299,15 @@ async function listAllSessions(params = {}) {
|
|
|
6764
7299
|
if (source === 'all' || source === 'codebuddy') {
|
|
6765
7300
|
sessions = sessions.concat(listSessionInventoryBySource('codebuddy', limit, scanOptions, { forceRefresh }));
|
|
6766
7301
|
}
|
|
7302
|
+
if (source === 'all' || source === 'workbuddy') {
|
|
7303
|
+
sessions = sessions.concat(listSessionInventoryBySource('workbuddy', limit, scanOptions, { forceRefresh }));
|
|
7304
|
+
}
|
|
6767
7305
|
if (source === 'all' || source === 'pi') {
|
|
6768
7306
|
sessions = sessions.concat(listSessionInventoryBySource('pi', limit, scanOptions, { forceRefresh }));
|
|
6769
7307
|
}
|
|
7308
|
+
if (source === 'all' || source === 'opencode') {
|
|
7309
|
+
sessions = sessions.concat(listSessionInventoryBySource('opencode', limit, scanOptions, { forceRefresh }));
|
|
7310
|
+
}
|
|
6770
7311
|
|
|
6771
7312
|
if (hasPathFilter) {
|
|
6772
7313
|
sessions = sessions.filter(item => matchesSessionPathFilter(item, normalizedPathFilter));
|
|
@@ -6789,9 +7330,7 @@ async function listAllSessions(params = {}) {
|
|
|
6789
7330
|
}
|
|
6790
7331
|
|
|
6791
7332
|
async function listAllSessionsData(params = {}) {
|
|
6792
|
-
const source = params.source
|
|
6793
|
-
? params.source
|
|
6794
|
-
: 'all';
|
|
7333
|
+
const source = normalizeSessionSourceName(params.source, '') || 'all';
|
|
6795
7334
|
const rawLimit = Number(params.limit);
|
|
6796
7335
|
const limit = Number.isFinite(rawLimit)
|
|
6797
7336
|
? Math.max(1, Math.min(rawLimit, MAX_SESSION_LIST_SIZE))
|
|
@@ -6848,6 +7387,7 @@ async function listSessionUsage(params = {}) {
|
|
|
6848
7387
|
parseCodexSessionSummary,
|
|
6849
7388
|
parseClaudeSessionSummary,
|
|
6850
7389
|
parseCodeBuddySessionSummary,
|
|
7390
|
+
parseWorkBuddySessionSummary,
|
|
6851
7391
|
parseGeminiSessionSummary,
|
|
6852
7392
|
parsePiSessionSummary,
|
|
6853
7393
|
MAX_SESSION_USAGE_LIST_SIZE,
|
|
@@ -6863,10 +7403,10 @@ async function exportSessionUsage(params = {}) {
|
|
|
6863
7403
|
|
|
6864
7404
|
function listSessionPaths(params = {}) {
|
|
6865
7405
|
const source = typeof params.source === 'string' ? params.source.trim().toLowerCase() : '';
|
|
6866
|
-
if (source && source
|
|
7406
|
+
if (source && !isSessionSourceOrAll(source)) {
|
|
6867
7407
|
return [];
|
|
6868
7408
|
}
|
|
6869
|
-
const validSource = source
|
|
7409
|
+
const validSource = normalizeSessionSourceName(source, '') || (source === 'all' ? 'all' : 'all');
|
|
6870
7410
|
const rawLimit = Number(params.limit);
|
|
6871
7411
|
const limit = Number.isFinite(rawLimit)
|
|
6872
7412
|
? Math.max(1, Math.min(rawLimit, MAX_SESSION_PATH_LIST_SIZE))
|
|
@@ -6900,9 +7440,15 @@ function listSessionPaths(params = {}) {
|
|
|
6900
7440
|
if (validSource === 'all' || validSource === 'codebuddy') {
|
|
6901
7441
|
sessions = sessions.concat(listSessionInventoryBySource('codebuddy', gatherLimit, scanOptions, { forceRefresh }));
|
|
6902
7442
|
}
|
|
7443
|
+
if (validSource === 'all' || validSource === 'workbuddy') {
|
|
7444
|
+
sessions = sessions.concat(listSessionInventoryBySource('workbuddy', gatherLimit, scanOptions, { forceRefresh }));
|
|
7445
|
+
}
|
|
6903
7446
|
if (validSource === 'all' || validSource === 'pi') {
|
|
6904
7447
|
sessions = sessions.concat(listSessionInventoryBySource('pi', gatherLimit, scanOptions, { forceRefresh }));
|
|
6905
7448
|
}
|
|
7449
|
+
if (validSource === 'all' || validSource === 'opencode') {
|
|
7450
|
+
sessions = sessions.concat(listSessionInventoryBySource('opencode', gatherLimit, scanOptions, { forceRefresh }));
|
|
7451
|
+
}
|
|
6906
7452
|
|
|
6907
7453
|
const dedupedPaths = [];
|
|
6908
7454
|
const seen = new Set();
|
|
@@ -6928,9 +7474,13 @@ function listSessionPaths(params = {}) {
|
|
|
6928
7474
|
}
|
|
6929
7475
|
|
|
6930
7476
|
function resolveSessionFilePath(source, filePath, sessionId) {
|
|
6931
|
-
const normalizedSource = source
|
|
6932
|
-
|
|
6933
|
-
|
|
7477
|
+
const normalizedSource = normalizeSessionSourceName(source, 'codex');
|
|
7478
|
+
if (normalizedSource === 'opencode') {
|
|
7479
|
+
const id = typeof sessionId === 'string' && sessionId.trim()
|
|
7480
|
+
? sessionId.trim()
|
|
7481
|
+
: parseOpencodeVirtualSessionId(filePath);
|
|
7482
|
+
return id && parseOpencodeSessionSummary(id) ? buildOpencodeVirtualSessionPath(id) : '';
|
|
7483
|
+
}
|
|
6934
7484
|
const homeDir = process && process.env && process.env.HOME ? process.env.HOME : '';
|
|
6935
7485
|
const derivedCodexDir = homeDir ? `${homeDir}/.codexmate/sessions/derived/codex` : '';
|
|
6936
7486
|
const derivedClaudeDir = homeDir ? `${homeDir}/.codexmate/sessions/derived/claude` : '';
|
|
@@ -6940,9 +7490,11 @@ function resolveSessionFilePath(source, filePath, sessionId) {
|
|
|
6940
7490
|
? [getGeminiTmpDir()]
|
|
6941
7491
|
: (normalizedSource === 'codebuddy'
|
|
6942
7492
|
? [getCodeBuddyProjectsDir()]
|
|
6943
|
-
: (normalizedSource === '
|
|
6944
|
-
? [
|
|
6945
|
-
:
|
|
7493
|
+
: (normalizedSource === 'workbuddy'
|
|
7494
|
+
? [getWorkBuddyProjectsDir()]
|
|
7495
|
+
: (normalizedSource === 'pi'
|
|
7496
|
+
? [getPiSessionsDir()]
|
|
7497
|
+
: [getCodexSessionsDir(), derivedCodexDir]))));
|
|
6946
7498
|
const availableRoots = roots.filter((dirPath) => dirPath && fs.existsSync(dirPath));
|
|
6947
7499
|
if (availableRoots.length === 0) {
|
|
6948
7500
|
return '';
|
|
@@ -7488,23 +8040,11 @@ function moveFileSync(sourcePath, targetPath) {
|
|
|
7488
8040
|
|
|
7489
8041
|
function buildSessionSummaryFallback(source, filePath, sessionId = '') {
|
|
7490
8042
|
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')));
|
|
8043
|
+
const sourceLabel = getSessionSourceLabel(source);
|
|
7498
8044
|
return {
|
|
7499
8045
|
source,
|
|
7500
8046
|
sourceLabel,
|
|
7501
|
-
provider: source
|
|
7502
|
-
? 'claude'
|
|
7503
|
-
: (source === 'gemini'
|
|
7504
|
-
? 'gemini'
|
|
7505
|
-
: (source === 'codebuddy'
|
|
7506
|
-
? 'codebuddy'
|
|
7507
|
-
: (source === 'pi' ? 'pi' : 'codex'))),
|
|
8047
|
+
provider: getSessionSourceProvider(source),
|
|
7508
8048
|
sessionId: resolvedSessionId,
|
|
7509
8049
|
title: resolvedSessionId,
|
|
7510
8050
|
cwd: '',
|
|
@@ -7515,7 +8055,7 @@ function buildSessionSummaryFallback(source, filePath, sessionId = '') {
|
|
|
7515
8055
|
contextWindow: 0,
|
|
7516
8056
|
filePath,
|
|
7517
8057
|
keywords: [],
|
|
7518
|
-
capabilities: source === 'claude' || source === 'gemini' || source === 'codebuddy' ? { code: true } : {}
|
|
8058
|
+
capabilities: source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'opencode' ? { code: true } : {}
|
|
7519
8059
|
};
|
|
7520
8060
|
}
|
|
7521
8061
|
|
|
@@ -7559,7 +8099,9 @@ function normalizeSessionTrashEntry(entry) {
|
|
|
7559
8099
|
? 'gemini'
|
|
7560
8100
|
: (entry.source === 'codebuddy'
|
|
7561
8101
|
? 'codebuddy'
|
|
7562
|
-
: (entry.source === '
|
|
8102
|
+
: (entry.source === 'workbuddy'
|
|
8103
|
+
? 'workbuddy'
|
|
8104
|
+
: (entry.source === 'pi' ? 'pi' : '')))));
|
|
7563
8105
|
const trashId = typeof entry.trashId === 'string' ? entry.trashId.trim() : '';
|
|
7564
8106
|
if (!source || !trashId || trashId.includes('/') || trashId.includes('\\') || trashId.includes('\0')) {
|
|
7565
8107
|
return null;
|
|
@@ -7580,7 +8122,9 @@ function normalizeSessionTrashEntry(entry) {
|
|
|
7580
8122
|
? 'Gemini CLI'
|
|
7581
8123
|
: (source === 'codebuddy'
|
|
7582
8124
|
? 'CodeBuddy Code'
|
|
7583
|
-
: (source === '
|
|
8125
|
+
: (source === 'workbuddy'
|
|
8126
|
+
? 'WorkBuddy AI'
|
|
8127
|
+
: (source === 'pi' ? 'Pi' : 'Codex')))),
|
|
7584
8128
|
sessionId: sessionId || trashId,
|
|
7585
8129
|
title: typeof entry.title === 'string' && entry.title.trim() ? entry.title.trim() : (sessionId || trashId),
|
|
7586
8130
|
cwd: typeof entry.cwd === 'string' ? entry.cwd : '',
|
|
@@ -7596,7 +8140,7 @@ function normalizeSessionTrashEntry(entry) {
|
|
|
7596
8140
|
originalFilePath: typeof entry.originalFilePath === 'string' ? entry.originalFilePath : '',
|
|
7597
8141
|
provider: typeof entry.provider === 'string' && entry.provider.trim()
|
|
7598
8142
|
? entry.provider.trim()
|
|
7599
|
-
: (source === 'claude' ? 'claude' : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : (source === 'pi' ? 'pi' : 'codex')))),
|
|
8143
|
+
: (source === 'claude' ? 'claude' : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : (source === 'workbuddy' ? 'workbuddy' : (source === 'pi' ? 'pi' : 'codex'))))),
|
|
7600
8144
|
keywords: normalizeKeywords(entry.keywords),
|
|
7601
8145
|
capabilities: normalizeCapabilities(entry.capabilities),
|
|
7602
8146
|
claudeIndexPath: typeof entry.claudeIndexPath === 'string' ? entry.claudeIndexPath : '',
|
|
@@ -7689,7 +8233,9 @@ function buildSessionTrashEntry(summary, options = {}) {
|
|
|
7689
8233
|
? 'gemini'
|
|
7690
8234
|
: (options.source === 'codebuddy'
|
|
7691
8235
|
? 'codebuddy'
|
|
7692
|
-
: (options.source === '
|
|
8236
|
+
: (options.source === 'workbuddy'
|
|
8237
|
+
? 'workbuddy'
|
|
8238
|
+
: (options.source === 'pi' ? 'pi' : 'codex'))));
|
|
7693
8239
|
const sessionId = options.sessionId || summary.sessionId || path.basename(options.originalFilePath || summary.filePath || '', '.jsonl');
|
|
7694
8240
|
const claudeIndexEntry = options.claudeIndexEntry && typeof options.claudeIndexEntry === 'object' && !Array.isArray(options.claudeIndexEntry)
|
|
7695
8241
|
? options.claudeIndexEntry
|
|
@@ -7703,7 +8249,9 @@ function buildSessionTrashEntry(summary, options = {}) {
|
|
|
7703
8249
|
? 'Gemini CLI'
|
|
7704
8250
|
: (source === 'codebuddy'
|
|
7705
8251
|
? 'CodeBuddy Code'
|
|
7706
|
-
: (source === '
|
|
8252
|
+
: (source === 'workbuddy'
|
|
8253
|
+
? 'WorkBuddy AI'
|
|
8254
|
+
: (source === 'pi' ? 'Pi' : 'Codex'))));
|
|
7707
8255
|
const fallbackTitle = truncateText(
|
|
7708
8256
|
(claudeIndexEntry && (claudeIndexEntry.summary || claudeIndexEntry.firstPrompt)) || sessionId,
|
|
7709
8257
|
120
|
|
@@ -7740,7 +8288,7 @@ function buildSessionTrashEntry(summary, options = {}) {
|
|
|
7740
8288
|
originalFilePath: options.originalFilePath || summary.filePath || '',
|
|
7741
8289
|
provider: (claudeIndexEntry && typeof claudeIndexEntry.provider === 'string' && claudeIndexEntry.provider.trim())
|
|
7742
8290
|
? claudeIndexEntry.provider.trim()
|
|
7743
|
-
: (summary.provider || (source === 'claude' ? 'claude' : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : (source === 'pi' ? 'pi' : 'codex'))))),
|
|
8291
|
+
: (summary.provider || (source === 'claude' ? 'claude' : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : (source === 'workbuddy' ? 'workbuddy' : (source === 'pi' ? 'pi' : 'codex')))))),
|
|
7744
8292
|
keywords: normalizedClaudeKeywords.length > 0 ? normalizedClaudeKeywords : normalizedSummaryKeywords,
|
|
7745
8293
|
capabilities: Object.keys(normalizedClaudeCapabilities).length > 0
|
|
7746
8294
|
? normalizedClaudeCapabilities
|
|
@@ -7761,7 +8309,9 @@ function resolveSessionRestoreTarget(entry) {
|
|
|
7761
8309
|
? getGeminiTmpDir()
|
|
7762
8310
|
: (normalized.source === 'codebuddy'
|
|
7763
8311
|
? getCodeBuddyProjectsDir()
|
|
7764
|
-
: (normalized.source === '
|
|
8312
|
+
: (normalized.source === 'workbuddy'
|
|
8313
|
+
? getWorkBuddyProjectsDir()
|
|
8314
|
+
: (normalized.source === 'pi' ? getPiSessionsDir() : getCodexSessionsDir()))));
|
|
7765
8315
|
const originalFilePath = typeof normalized.originalFilePath === 'string' ? normalized.originalFilePath.trim() : '';
|
|
7766
8316
|
if (!root || !originalFilePath) {
|
|
7767
8317
|
return '';
|
|
@@ -7895,15 +8445,7 @@ function upsertClaudeSessionIndexEntry(indexPath, sessionFilePath, entry) {
|
|
|
7895
8445
|
}
|
|
7896
8446
|
|
|
7897
8447
|
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'))));
|
|
8448
|
+
const source = normalizeSessionSourceName(params.source, '') || 'all';
|
|
7907
8449
|
const countOnly = params.countOnly === true;
|
|
7908
8450
|
const rawLimit = Number(params.limit);
|
|
7909
8451
|
const limit = Number.isFinite(rawLimit)
|
|
@@ -7913,7 +8455,7 @@ async function listSessionTrashItems(params = {}) {
|
|
|
7913
8455
|
purgeExpiredSessionTrashEntries(params.retentionDays);
|
|
7914
8456
|
}
|
|
7915
8457
|
const allEntries = readSessionTrashEntries();
|
|
7916
|
-
let items = source
|
|
8458
|
+
let items = normalizeSessionSourceName(source, '')
|
|
7917
8459
|
? allEntries.filter((entry) => entry.source === source)
|
|
7918
8460
|
: allEntries.slice();
|
|
7919
8461
|
items.sort((a, b) => {
|
|
@@ -8093,18 +8635,13 @@ async function purgeSessionTrashItems(params = {}) {
|
|
|
8093
8635
|
}
|
|
8094
8636
|
|
|
8095
8637
|
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' : ''))));
|
|
8638
|
+
const source = normalizeSessionSourceName(params.source, '');
|
|
8105
8639
|
if (!source) {
|
|
8106
8640
|
return { error: 'Invalid source' };
|
|
8107
8641
|
}
|
|
8642
|
+
if (source === 'opencode') {
|
|
8643
|
+
return { error: 'OpenCode DB-backed sessions cannot be deleted from CodexMate' };
|
|
8644
|
+
}
|
|
8108
8645
|
|
|
8109
8646
|
const filePath = resolveSessionFilePath(source, getSessionFileArg(params), params.sessionId);
|
|
8110
8647
|
if (!filePath) {
|
|
@@ -8117,7 +8654,9 @@ async function trashSessionData(params = {}) {
|
|
|
8117
8654
|
? parseGeminiSessionSummary(filePath)
|
|
8118
8655
|
: (source === 'codebuddy'
|
|
8119
8656
|
? parseCodeBuddySessionSummary(filePath)
|
|
8120
|
-
: (source === '
|
|
8657
|
+
: (source === 'workbuddy'
|
|
8658
|
+
? parseWorkBuddySessionSummary(filePath)
|
|
8659
|
+
: (source === 'pi' ? parsePiSessionSummary(filePath) : parseCodexSessionSummary(filePath))))))
|
|
8121
8660
|
|| buildSessionSummaryFallback(source, filePath, params.sessionId);
|
|
8122
8661
|
const exactMessageCount = await countConversationMessagesInFile(filePath, source);
|
|
8123
8662
|
if (Number.isFinite(Number(exactMessageCount))) {
|
|
@@ -8208,18 +8747,13 @@ async function trashSessionData(params = {}) {
|
|
|
8208
8747
|
}
|
|
8209
8748
|
|
|
8210
8749
|
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' : ''))));
|
|
8750
|
+
const source = normalizeSessionSourceName(params.source, '');
|
|
8220
8751
|
if (!source) {
|
|
8221
8752
|
return { error: 'Invalid source' };
|
|
8222
8753
|
}
|
|
8754
|
+
if (source === 'opencode') {
|
|
8755
|
+
return { error: 'OpenCode DB-backed sessions cannot be deleted from CodexMate' };
|
|
8756
|
+
}
|
|
8223
8757
|
|
|
8224
8758
|
const filePath = resolveSessionFilePath(source, getSessionFileArg(params), params.sessionId);
|
|
8225
8759
|
if (!filePath) {
|
|
@@ -8767,7 +9301,7 @@ function recordHasPiMessage(record) {
|
|
|
8767
9301
|
|
|
8768
9302
|
function recordHasMessage(record, source) {
|
|
8769
9303
|
if (source === 'codex') return recordHasCodexMessage(record);
|
|
8770
|
-
if (source === 'codebuddy') return recordHasCodeBuddyMessage(record);
|
|
9304
|
+
if (source === 'codebuddy' || source === 'workbuddy') return recordHasCodeBuddyMessage(record);
|
|
8771
9305
|
if (source === 'pi') return recordHasPiMessage(record);
|
|
8772
9306
|
return recordHasClaudeMessage(record);
|
|
8773
9307
|
}
|
|
@@ -8787,7 +9321,7 @@ function extractMessagesFromRecords(records, source, options = {}) {
|
|
|
8787
9321
|
const record = records[lineIndex];
|
|
8788
9322
|
if (source === 'codex') {
|
|
8789
9323
|
extractCodexMessageFromRecord(record, state, lineIndex);
|
|
8790
|
-
} else if (source === 'codebuddy') {
|
|
9324
|
+
} else if (source === 'codebuddy' || source === 'workbuddy') {
|
|
8791
9325
|
extractCodeBuddyMessageFromRecord(record, state, lineIndex);
|
|
8792
9326
|
} else if (source === 'pi') {
|
|
8793
9327
|
extractPiMessageFromRecord(record, state, lineIndex);
|
|
@@ -8852,7 +9386,7 @@ async function extractMessagesFromFile(filePath, source, options = {}) {
|
|
|
8852
9386
|
|
|
8853
9387
|
if (source === 'codex') {
|
|
8854
9388
|
extractCodexMessageFromRecord(record, state, currentLineIndex);
|
|
8855
|
-
} else if (source === 'codebuddy') {
|
|
9389
|
+
} else if (source === 'codebuddy' || source === 'workbuddy') {
|
|
8856
9390
|
extractCodeBuddyMessageFromRecord(record, state, currentLineIndex);
|
|
8857
9391
|
} else if (source === 'pi') {
|
|
8858
9392
|
extractPiMessageFromRecord(record, state, currentLineIndex);
|
|
@@ -8880,15 +9414,7 @@ async function extractMessagesFromFile(filePath, source, options = {}) {
|
|
|
8880
9414
|
}
|
|
8881
9415
|
|
|
8882
9416
|
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' : ''))));
|
|
9417
|
+
const source = normalizeSessionSourceName(params.source, '');
|
|
8892
9418
|
if (!source) {
|
|
8893
9419
|
return { error: 'Invalid source' };
|
|
8894
9420
|
}
|
|
@@ -8904,9 +9430,30 @@ async function readSessionDetail(params = {}) {
|
|
|
8904
9430
|
? Math.max(1, Math.min(rawLimit, MAX_SESSION_DETAIL_MESSAGES))
|
|
8905
9431
|
: DEFAULT_SESSION_DETAIL_MESSAGES;
|
|
8906
9432
|
const preview = params.preview === true || params.preview === 'true';
|
|
9433
|
+
const rawSessionQuery = typeof params.query === 'string' ? params.query.trim() : '';
|
|
9434
|
+
const sessionQueryTokens = rawSessionQuery
|
|
9435
|
+
? expandSessionQueryTokens(normalizeQueryTokens(rawSessionQuery))
|
|
9436
|
+
: [];
|
|
9437
|
+
let sessionQueryMatch = null;
|
|
9438
|
+
if (sessionQueryTokens.length > 0) {
|
|
9439
|
+
sessionQueryMatch = await scanSessionContentForQuery(
|
|
9440
|
+
{ source, filePath, sessionId: params.sessionId },
|
|
9441
|
+
sessionQueryTokens,
|
|
9442
|
+
{
|
|
9443
|
+
maxMatches: Number.MAX_SAFE_INTEGER,
|
|
9444
|
+
positionsLimit: SESSION_QUERY_MATCH_POSITIONS_LIMIT
|
|
9445
|
+
}
|
|
9446
|
+
);
|
|
9447
|
+
}
|
|
8907
9448
|
|
|
8908
9449
|
let extracted;
|
|
8909
|
-
if (source === '
|
|
9450
|
+
if (source === 'opencode') {
|
|
9451
|
+
const opencodeSessionId = params.sessionId || parseOpencodeVirtualSessionId(filePath);
|
|
9452
|
+
extracted = queryOpencodeSessionMessages(opencodeSessionId, { maxMessages: messageLimit });
|
|
9453
|
+
if (!extracted) {
|
|
9454
|
+
return { error: 'Failed to parse session file' };
|
|
9455
|
+
}
|
|
9456
|
+
} else if (source === 'gemini') {
|
|
8910
9457
|
let json;
|
|
8911
9458
|
try {
|
|
8912
9459
|
json = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
@@ -8946,13 +9493,7 @@ async function readSessionDetail(params = {}) {
|
|
|
8946
9493
|
extracted = await extractSessionDetailPreviewFromFile(filePath, source, messageLimit, { preview });
|
|
8947
9494
|
}
|
|
8948
9495
|
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')));
|
|
9496
|
+
const sourceLabel = getSessionSourceLabel(source);
|
|
8956
9497
|
const clippedMessages = Array.isArray(extracted.messages) ? extracted.messages : [];
|
|
8957
9498
|
const hasExactTotalMessages = Number.isFinite(extracted.totalMessages);
|
|
8958
9499
|
const startIndex = hasExactTotalMessages
|
|
@@ -8996,6 +9537,13 @@ async function readSessionDetail(params = {}) {
|
|
|
8996
9537
|
? extracted.clipped
|
|
8997
9538
|
: (hasExactTotalMessages ? extracted.totalMessages > indexedMessages.length : false),
|
|
8998
9539
|
messageLimit,
|
|
9540
|
+
...(sessionQueryMatch ? {
|
|
9541
|
+
match: {
|
|
9542
|
+
hit: !!sessionQueryMatch.hit,
|
|
9543
|
+
count: Number.isFinite(sessionQueryMatch.count) ? sessionQueryMatch.count : 0,
|
|
9544
|
+
positions: Array.isArray(sessionQueryMatch.positions) ? sessionQueryMatch.positions : []
|
|
9545
|
+
}
|
|
9546
|
+
} : {}),
|
|
8999
9547
|
messages: indexedMessages,
|
|
9000
9548
|
filePath,
|
|
9001
9549
|
...(typeof buildSessionNativeStatus === 'function'
|
|
@@ -9013,15 +9561,7 @@ async function readSessionDetail(params = {}) {
|
|
|
9013
9561
|
}
|
|
9014
9562
|
|
|
9015
9563
|
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' : ''))));
|
|
9564
|
+
const source = normalizeSessionSourceName(params.source, '');
|
|
9025
9565
|
if (!source) {
|
|
9026
9566
|
return { error: 'Invalid source' };
|
|
9027
9567
|
}
|
|
@@ -9041,7 +9581,13 @@ async function readSessionPlain(params = {}) {
|
|
|
9041
9581
|
);
|
|
9042
9582
|
|
|
9043
9583
|
let extracted;
|
|
9044
|
-
if (source === '
|
|
9584
|
+
if (source === 'opencode') {
|
|
9585
|
+
const opencodeSessionId = params.sessionId || parseOpencodeVirtualSessionId(filePath);
|
|
9586
|
+
extracted = queryOpencodeSessionMessages(opencodeSessionId, { maxMessages });
|
|
9587
|
+
if (!extracted) {
|
|
9588
|
+
return { error: 'Failed to parse session file' };
|
|
9589
|
+
}
|
|
9590
|
+
} else if (source === 'gemini') {
|
|
9045
9591
|
let json;
|
|
9046
9592
|
try {
|
|
9047
9593
|
json = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
@@ -9091,13 +9637,7 @@ async function readSessionPlain(params = {}) {
|
|
|
9091
9637
|
}
|
|
9092
9638
|
|
|
9093
9639
|
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')));
|
|
9640
|
+
const sourceLabel = getSessionSourceLabel(source);
|
|
9101
9641
|
const messages = removeLeadingSystemMessage(Array.isArray(extracted.messages) ? extracted.messages : []);
|
|
9102
9642
|
const text = buildSessionPlainText(messages);
|
|
9103
9643
|
|
|
@@ -9113,15 +9653,7 @@ async function readSessionPlain(params = {}) {
|
|
|
9113
9653
|
}
|
|
9114
9654
|
|
|
9115
9655
|
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' : ''))));
|
|
9656
|
+
const source = normalizeSessionSourceName(params.source, '');
|
|
9125
9657
|
if (!source) {
|
|
9126
9658
|
return { error: 'Invalid source' };
|
|
9127
9659
|
}
|
|
@@ -9133,7 +9665,13 @@ async function exportSessionData(params = {}) {
|
|
|
9133
9665
|
}
|
|
9134
9666
|
|
|
9135
9667
|
let extracted;
|
|
9136
|
-
if (source === '
|
|
9668
|
+
if (source === 'opencode') {
|
|
9669
|
+
const opencodeSessionId = params.sessionId || parseOpencodeVirtualSessionId(filePath);
|
|
9670
|
+
extracted = queryOpencodeSessionMessages(opencodeSessionId, { maxMessages });
|
|
9671
|
+
if (!extracted) {
|
|
9672
|
+
return { error: 'Failed to parse session file' };
|
|
9673
|
+
}
|
|
9674
|
+
} else if (source === 'gemini') {
|
|
9137
9675
|
let json;
|
|
9138
9676
|
try {
|
|
9139
9677
|
json = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
@@ -9191,13 +9729,7 @@ async function exportSessionData(params = {}) {
|
|
|
9191
9729
|
|
|
9192
9730
|
const sessionId = extracted.sessionId || params.sessionId || path.basename(filePath, source === 'gemini' ? '.json' : '.jsonl');
|
|
9193
9731
|
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')));
|
|
9732
|
+
const sourceLabel = getSessionSourceLabel(source);
|
|
9201
9733
|
const truncated = !!extracted.truncated;
|
|
9202
9734
|
const maxMessagesLabel = maxMessages === Infinity ? 'all' : maxMessages;
|
|
9203
9735
|
const markdown = buildSessionMarkdown({
|
|
@@ -11085,7 +11617,8 @@ function runKilocodeCommand(args = [], options = {}) {
|
|
|
11085
11617
|
const child = spawn(bin, finalArgs, {
|
|
11086
11618
|
detached: true,
|
|
11087
11619
|
stdio: 'ignore',
|
|
11088
|
-
windowsHide: true
|
|
11620
|
+
windowsHide: true,
|
|
11621
|
+
shell: process.platform === 'win32'
|
|
11089
11622
|
});
|
|
11090
11623
|
child.unref();
|
|
11091
11624
|
return { success: true, bin, args: finalArgs, pid: child.pid };
|
|
@@ -11093,7 +11626,8 @@ function runKilocodeCommand(args = [], options = {}) {
|
|
|
11093
11626
|
return new Promise((resolve, reject) => {
|
|
11094
11627
|
const child = spawn(bin, finalArgs, {
|
|
11095
11628
|
stdio: 'inherit',
|
|
11096
|
-
windowsHide: false
|
|
11629
|
+
windowsHide: false,
|
|
11630
|
+
shell: process.platform === 'win32'
|
|
11097
11631
|
});
|
|
11098
11632
|
child.on('error', reject);
|
|
11099
11633
|
child.on('exit', (code, signal) => {
|
|
@@ -12382,7 +12916,7 @@ function resolveExportOutputPath(outputPath, defaultFileName) {
|
|
|
12382
12916
|
}
|
|
12383
12917
|
|
|
12384
12918
|
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>]');
|
|
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>]');
|
|
12386
12920
|
console.log('\n示例:');
|
|
12387
12921
|
console.log(' codexmate export-session --source codex --session-id 123456');
|
|
12388
12922
|
console.log(' codexmate export-session --source claude --file "~/.claude/projects/demo/session.jsonl"');
|
|
@@ -12454,8 +12988,8 @@ function parseExportSessionArgs(args = []) {
|
|
|
12454
12988
|
}
|
|
12455
12989
|
|
|
12456
12990
|
const normalizedSource = options.source.trim().toLowerCase();
|
|
12457
|
-
if (normalizedSource && normalizedSource
|
|
12458
|
-
errors.push('参数 --source 仅支持 codex、claude 或
|
|
12991
|
+
if (normalizedSource && !isSessionSourceOrAll(normalizedSource)) {
|
|
12992
|
+
errors.push('参数 --source 仅支持 codex、claude、gemini、codebuddy、workbuddy、pi 或 opencode');
|
|
12459
12993
|
}
|
|
12460
12994
|
options.source = normalizedSource;
|
|
12461
12995
|
|
|
@@ -12528,7 +13062,7 @@ async function cmdExportSession(args = []) {
|
|
|
12528
13062
|
|
|
12529
13063
|
function printAnalyticsUsage() {
|
|
12530
13064
|
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|->]');
|
|
13065
|
+
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
13066
|
console.log('');
|
|
12533
13067
|
}
|
|
12534
13068
|
|
|
@@ -13883,8 +14417,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13883
14417
|
case 'list-sessions':
|
|
13884
14418
|
{
|
|
13885
14419
|
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' };
|
|
14420
|
+
if (source && !isSessionSourceOrAll(source)) {
|
|
14421
|
+
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' };
|
|
13888
14422
|
} else {
|
|
13889
14423
|
result = {
|
|
13890
14424
|
sessions: await listSessionBrowse(params),
|
|
@@ -13897,8 +14431,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13897
14431
|
{
|
|
13898
14432
|
const usageParams = isPlainObject(params) ? params : {};
|
|
13899
14433
|
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' };
|
|
14434
|
+
if (source && !isSessionSourceOrAll(source)) {
|
|
14435
|
+
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' };
|
|
13902
14436
|
} else {
|
|
13903
14437
|
result = {
|
|
13904
14438
|
sessions: await listSessionUsage({
|
|
@@ -13914,8 +14448,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13914
14448
|
{
|
|
13915
14449
|
const usageParams = isPlainObject(params) ? params : {};
|
|
13916
14450
|
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' };
|
|
14451
|
+
if (source && !isSessionSourceOrAll(source)) {
|
|
14452
|
+
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' };
|
|
13919
14453
|
} else {
|
|
13920
14454
|
result = await exportSessionUsage({
|
|
13921
14455
|
...usageParams,
|
|
@@ -13927,8 +14461,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13927
14461
|
case 'list-session-paths':
|
|
13928
14462
|
{
|
|
13929
14463
|
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' };
|
|
14464
|
+
if (source && !isSessionSourceOrAll(source)) {
|
|
14465
|
+
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' };
|
|
13932
14466
|
} else {
|
|
13933
14467
|
result = {
|
|
13934
14468
|
paths: listSessionPaths(params)
|
|
@@ -15439,7 +15973,7 @@ function buildMcpClaudeSettingsPayload() {
|
|
|
15439
15973
|
function normalizeMcpSource(value) {
|
|
15440
15974
|
const source = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
15441
15975
|
if (!source) return '';
|
|
15442
|
-
if (source
|
|
15976
|
+
if (normalizeSessionSourceName(source, '') || source === 'all') {
|
|
15443
15977
|
return source;
|
|
15444
15978
|
}
|
|
15445
15979
|
return null;
|
|
@@ -15752,7 +16286,7 @@ function createWorkflowToolCatalog() {
|
|
|
15752
16286
|
handler: async (args = {}) => {
|
|
15753
16287
|
const source = normalizeMcpSource(args.source);
|
|
15754
16288
|
if (source === null) {
|
|
15755
|
-
return { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' };
|
|
16289
|
+
return { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' };
|
|
15756
16290
|
}
|
|
15757
16291
|
return {
|
|
15758
16292
|
source: source || 'all',
|
|
@@ -16118,7 +16652,7 @@ function createMcpTools(options = {}) {
|
|
|
16118
16652
|
const input = args && typeof args === 'object' ? args : {};
|
|
16119
16653
|
const source = normalizeMcpSource(input.source);
|
|
16120
16654
|
if (source === null) {
|
|
16121
|
-
return { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' };
|
|
16655
|
+
return { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' };
|
|
16122
16656
|
}
|
|
16123
16657
|
const normalizedInput = {
|
|
16124
16658
|
...input,
|
|
@@ -16578,7 +17112,7 @@ function createMcpResources() {
|
|
|
16578
17112
|
contents: [{
|
|
16579
17113
|
uri,
|
|
16580
17114
|
mimeType: 'application/json',
|
|
16581
|
-
text: JSON.stringify({ error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' }, null, 2)
|
|
17115
|
+
text: JSON.stringify({ error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' }, null, 2)
|
|
16582
17116
|
}]
|
|
16583
17117
|
};
|
|
16584
17118
|
}
|
|
@@ -16827,7 +17361,7 @@ function printMainHelp() {
|
|
|
16827
17361
|
console.log(' 注: follow-up 自动排队仅支持 linux/android/netbsd/openbsd/darwin/freebsd 且 stdin 必须是 TTY,其他平台会报错');
|
|
16828
17362
|
console.log(' codexmate qwen [参数...] 等同于 qwen --yolo');
|
|
16829
17363
|
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>]');
|
|
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>]');
|
|
16831
17365
|
console.log(' codexmate convert-session --from <codex|claude> --to <codex|claude> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
|
|
16832
17366
|
console.log(' codexmate zip <路径> [--max:级别] 压缩(系统 zip 优先,其次 zip-lib)');
|
|
16833
17367
|
console.log(' codexmate unzip <zip文件> [输出目录] 解压(zip-lib)');
|