codexmate 0.1.24 → 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 +737 -200
- package/lib/cli-sessions.js +1 -1
- package/package.json +3 -3
- package/plugins/prompt-templates/computed.mjs +1 -1
- package/plugins/prompt-templates/index.mjs +3 -3
- package/plugins/prompt-templates/manifest.mjs +5 -3
- package/plugins/prompt-templates/methods.mjs +9 -2
- package/plugins/registry.mjs +5 -2
- package/plugins/text-tools/computed.mjs +23 -0
- package/plugins/text-tools/index.mjs +14 -0
- package/plugins/text-tools/manifest.mjs +20 -0
- package/plugins/text-tools/methods.mjs +24 -0
- package/plugins/text-tools/overview.mjs +12 -0
- package/plugins/text-tools/ownership.mjs +7 -0
- package/tools/dev/launch.js +147 -0
- package/web-ui/app.js +15 -5
- package/web-ui/index.html +1 -2
- package/web-ui/logic.markdown-editor.mjs +5 -57
- 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.navigation.mjs +79 -3
- package/web-ui/modules/app.methods.prompts-editor.mjs +6 -64
- 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.web-ui-preferences.mjs +7 -1
- package/web-ui/modules/i18n/locales/en.mjs +20 -1
- package/web-ui/modules/i18n/locales/ja.mjs +20 -1
- package/web-ui/modules/i18n/locales/vi.mjs +20 -1
- package/web-ui/modules/i18n/locales/zh-tw.mjs +20 -1
- package/web-ui/modules/i18n/locales/zh.mjs +20 -1
- package/web-ui/modules/plugins.computed.mjs +8 -2
- package/web-ui/modules/plugins.methods.mjs +8 -2
- package/web-ui/partials/index/layout-header.html +23 -6
- package/web-ui/partials/index/panel-plugins.html +45 -5
- package/web-ui/partials/index/panel-prompts.html +16 -17
- package/web-ui/partials/index/panel-sessions.html +19 -4
- package/web-ui/res/web-ui-render.precompiled.js +193 -93
- package/web-ui/session-helpers.mjs +1 -2
- package/web-ui/source-bundle.cjs +1 -0
- package/web-ui/styles/base-theme.css +24 -0
- package/web-ui/styles/modals-core.css +48 -0
- package/web-ui/styles/prompts-editor.css +3 -66
- 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
|
|
|
@@ -1098,7 +1117,10 @@ function normalizeWebUiPreferences(value = {}) {
|
|
|
1098
1117
|
skillsTargetApp: navigation.skillsTargetApp === 'claude' || navigation.skillsTargetApp === 'pi'
|
|
1099
1118
|
? navigation.skillsTargetApp
|
|
1100
1119
|
: 'codex',
|
|
1101
|
-
promptTemplatesMode: navigation.promptTemplatesMode === 'manage' ? 'manage' : 'compose'
|
|
1120
|
+
promptTemplatesMode: navigation.promptTemplatesMode === 'manage' ? 'manage' : 'compose',
|
|
1121
|
+
pluginsActiveId: typeof navigation.pluginsActiveId === 'string' && navigation.pluginsActiveId.trim()
|
|
1122
|
+
? navigation.pluginsActiveId.trim()
|
|
1123
|
+
: 'prompt-templates'
|
|
1102
1124
|
}
|
|
1103
1125
|
};
|
|
1104
1126
|
}
|
|
@@ -1192,7 +1214,6 @@ function getApiToolConfigWriteTarget(action) {
|
|
|
1192
1214
|
const opencodeWriteActions = new Set([
|
|
1193
1215
|
'apply-opencode-config',
|
|
1194
1216
|
'update-opencode-selection',
|
|
1195
|
-
'apply-opencode-agents-file'
|
|
1196
1217
|
]);
|
|
1197
1218
|
const kilocodeWriteActions = new Set([
|
|
1198
1219
|
'apply-kilocode-config',
|
|
@@ -2111,6 +2132,16 @@ function getCodeBuddyProjectsDir() {
|
|
|
2111
2132
|
return resolveExistingDir(candidates, CODEBUDDY_PROJECTS_DIR);
|
|
2112
2133
|
}
|
|
2113
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
|
+
|
|
2114
2145
|
function getPiSessionsDir() {
|
|
2115
2146
|
const candidates = [];
|
|
2116
2147
|
const envPiHome = process.env.PI_HOME;
|
|
@@ -2125,6 +2156,250 @@ function getPiSessionsDir() {
|
|
|
2125
2156
|
return resolveExistingDir(candidates, PI_SESSIONS_DIR);
|
|
2126
2157
|
}
|
|
2127
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
|
+
|
|
2128
2403
|
function getCodexmateDerivedSessionsRoot(target) {
|
|
2129
2404
|
if (target === 'claude') {
|
|
2130
2405
|
return CODEXMATE_DERIVED_CLAUDE_DIR;
|
|
@@ -4081,7 +4356,7 @@ function countConversationMessagesInRecords(records, source) {
|
|
|
4081
4356
|
}
|
|
4082
4357
|
continue;
|
|
4083
4358
|
}
|
|
4084
|
-
if (source === 'codebuddy') {
|
|
4359
|
+
if (source === 'codebuddy' || source === 'workbuddy') {
|
|
4085
4360
|
if (record && record.type === 'message') {
|
|
4086
4361
|
const role = normalizeRole(record.role);
|
|
4087
4362
|
if (role === 'assistant' || role === 'user' || role === 'system') {
|
|
@@ -4164,7 +4439,7 @@ async function countConversationMessagesInFile(filePath, source) {
|
|
|
4164
4439
|
role = normalizeRole(record.payload.role);
|
|
4165
4440
|
text = extractMessageText(record.payload.content);
|
|
4166
4441
|
}
|
|
4167
|
-
} else if (source === 'codebuddy') {
|
|
4442
|
+
} else if (source === 'codebuddy' || source === 'workbuddy') {
|
|
4168
4443
|
if (record && record.type === 'message') {
|
|
4169
4444
|
role = normalizeRole(record.role);
|
|
4170
4445
|
if (role === 'assistant' || role === 'user' || role === 'system') {
|
|
@@ -4344,15 +4619,7 @@ async function resolveSessionTrashEntryExactMessageCount(entry) {
|
|
|
4344
4619
|
}
|
|
4345
4620
|
|
|
4346
4621
|
async function hydrateSessionTrashEntries(entries, options = {}) {
|
|
4347
|
-
const source = options.source
|
|
4348
|
-
? 'claude'
|
|
4349
|
-
: (options.source === 'codex'
|
|
4350
|
-
? 'codex'
|
|
4351
|
-
: (options.source === 'gemini'
|
|
4352
|
-
? 'gemini'
|
|
4353
|
-
: (options.source === 'codebuddy'
|
|
4354
|
-
? 'codebuddy'
|
|
4355
|
-
: (options.source === 'pi' ? 'pi' : 'all'))));
|
|
4622
|
+
const source = normalizeSessionSourceName(options.source, '') || 'all';
|
|
4356
4623
|
const hydratedEntries = await mapWithConcurrency(Array.isArray(entries) ? entries : [], 8, async (entry) => {
|
|
4357
4624
|
const normalizedEntry = normalizeSessionTrashEntry(entry);
|
|
4358
4625
|
if (!normalizedEntry) {
|
|
@@ -4361,7 +4628,7 @@ async function hydrateSessionTrashEntries(entries, options = {}) {
|
|
|
4361
4628
|
return await resolveSessionTrashEntryExactMessageCount(normalizedEntry);
|
|
4362
4629
|
});
|
|
4363
4630
|
|
|
4364
|
-
if (source
|
|
4631
|
+
if (normalizeSessionSourceName(source, '')) {
|
|
4365
4632
|
return hydratedEntries.filter((entry) => entry.source === source);
|
|
4366
4633
|
}
|
|
4367
4634
|
return hydratedEntries;
|
|
@@ -4375,15 +4642,7 @@ async function hydrateSessionItemsExactMessageCount(items) {
|
|
|
4375
4642
|
if (item.__messageCountExact === true) {
|
|
4376
4643
|
return item;
|
|
4377
4644
|
}
|
|
4378
|
-
const source = item.source
|
|
4379
|
-
? 'claude'
|
|
4380
|
-
: (item.source === 'codex'
|
|
4381
|
-
? 'codex'
|
|
4382
|
-
: (item.source === 'gemini'
|
|
4383
|
-
? 'gemini'
|
|
4384
|
-
: (item.source === 'codebuddy'
|
|
4385
|
-
? 'codebuddy'
|
|
4386
|
-
: (item.source === 'pi' ? 'pi' : ''))));
|
|
4645
|
+
const source = normalizeSessionSourceName(item.source, '');
|
|
4387
4646
|
const filePath = typeof item.filePath === 'string' ? item.filePath : '';
|
|
4388
4647
|
if (!source || !filePath || !fs.existsSync(filePath)) {
|
|
4389
4648
|
return item;
|
|
@@ -4410,6 +4669,40 @@ async function hydrateSessionItemsExactMessageCount(items) {
|
|
|
4410
4669
|
});
|
|
4411
4670
|
}
|
|
4412
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
|
+
|
|
4413
4706
|
function getSessionExportKeyForApi(item) {
|
|
4414
4707
|
const source = item && item.source ? String(item.source).trim() : '';
|
|
4415
4708
|
const sessionId = item && item.sessionId ? String(item.sessionId) : '';
|
|
@@ -4427,20 +4720,21 @@ async function readSessionMessageCounts(params = {}) {
|
|
|
4427
4720
|
return undefined;
|
|
4428
4721
|
}
|
|
4429
4722
|
const key = getSessionExportKeyForApi(item);
|
|
4430
|
-
const source = item.source
|
|
4431
|
-
? 'claude'
|
|
4432
|
-
: (item.source === 'codex'
|
|
4433
|
-
? 'codex'
|
|
4434
|
-
: (item.source === 'gemini'
|
|
4435
|
-
? 'gemini'
|
|
4436
|
-
: (item.source === 'codebuddy'
|
|
4437
|
-
? 'codebuddy'
|
|
4438
|
-
: (item.source === 'pi' ? 'pi' : ''))));
|
|
4723
|
+
const source = normalizeSessionSourceName(item.source, '');
|
|
4439
4724
|
const filePath = typeof item.filePath === 'string' ? item.filePath : '';
|
|
4440
|
-
if (!source
|
|
4725
|
+
if (!source) {
|
|
4441
4726
|
return { key };
|
|
4442
4727
|
}
|
|
4443
|
-
|
|
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
|
+
}
|
|
4444
4738
|
if (!Number.isFinite(Number(exactMessageCount))) {
|
|
4445
4739
|
return { key };
|
|
4446
4740
|
}
|
|
@@ -4570,6 +4864,10 @@ function createSessionQueryScanState(tokens, options = {}) {
|
|
|
4570
4864
|
const snippetLimit = Number.isFinite(Number(options.snippetLimit))
|
|
4571
4865
|
? Math.max(0, Number(options.snippetLimit))
|
|
4572
4866
|
: 0;
|
|
4867
|
+
const positionsLimitRaw = Number(options.positionsLimit);
|
|
4868
|
+
const positionsLimit = Number.isFinite(positionsLimitRaw) && positionsLimitRaw > 0
|
|
4869
|
+
? Math.floor(positionsLimitRaw)
|
|
4870
|
+
: 0;
|
|
4573
4871
|
|
|
4574
4872
|
return {
|
|
4575
4873
|
tokens,
|
|
@@ -4577,8 +4875,10 @@ function createSessionQueryScanState(tokens, options = {}) {
|
|
|
4577
4875
|
roleFilter,
|
|
4578
4876
|
maxMatches,
|
|
4579
4877
|
snippetLimit,
|
|
4878
|
+
positionsLimit,
|
|
4580
4879
|
count: 0,
|
|
4581
4880
|
snippets: [],
|
|
4881
|
+
positions: [],
|
|
4582
4882
|
leadingSystem: roleFilter !== 'system'
|
|
4583
4883
|
};
|
|
4584
4884
|
}
|
|
@@ -4610,6 +4910,13 @@ function consumeSessionQueryMessage(state, message) {
|
|
|
4610
4910
|
if (state.snippetLimit > 0 && state.snippets.length < state.snippetLimit) {
|
|
4611
4911
|
state.snippets.push(truncateText(text));
|
|
4612
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
|
+
}
|
|
4613
4920
|
return state.count >= state.maxMatches;
|
|
4614
4921
|
}
|
|
4615
4922
|
|
|
@@ -4617,7 +4924,8 @@ function buildSessionQueryScanResult(state) {
|
|
|
4617
4924
|
return {
|
|
4618
4925
|
hit: !!(state && state.count > 0),
|
|
4619
4926
|
count: state && Number.isFinite(state.count) ? state.count : 0,
|
|
4620
|
-
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 : []
|
|
4621
4929
|
};
|
|
4622
4930
|
}
|
|
4623
4931
|
|
|
@@ -4625,12 +4933,14 @@ function scanSessionContentForQueryInRecords(records, source, state) {
|
|
|
4625
4933
|
if (!Array.isArray(records) || !state) {
|
|
4626
4934
|
return buildSessionQueryScanResult(state);
|
|
4627
4935
|
}
|
|
4628
|
-
|
|
4629
|
-
|
|
4936
|
+
for (let recordIndex = 0; recordIndex < records.length; recordIndex++) {
|
|
4937
|
+
const record = records[recordIndex];
|
|
4630
4938
|
const message = extractMessageFromRecord(record, source);
|
|
4631
4939
|
if (!message) {
|
|
4632
4940
|
continue;
|
|
4633
4941
|
}
|
|
4942
|
+
message.recordLineIndex = recordIndex;
|
|
4943
|
+
message.timestamp = toIsoTime(record && record.timestamp, '');
|
|
4634
4944
|
if (consumeSessionQueryMessage(state, message)) {
|
|
4635
4945
|
break;
|
|
4636
4946
|
}
|
|
@@ -4641,12 +4951,12 @@ function scanSessionContentForQueryInRecords(records, source, state) {
|
|
|
4641
4951
|
|
|
4642
4952
|
async function scanSessionContentForQuery(session, tokens, options = {}) {
|
|
4643
4953
|
if (!session || !Array.isArray(tokens) || tokens.length === 0) {
|
|
4644
|
-
return { hit: false, count: 0, snippets: [] };
|
|
4954
|
+
return { hit: false, count: 0, snippets: [], positions: [] };
|
|
4645
4955
|
}
|
|
4646
4956
|
|
|
4647
4957
|
const filePath = resolveSessionFilePath(session.source, session.filePath, session.sessionId);
|
|
4648
4958
|
if (!filePath) {
|
|
4649
|
-
return { hit: false, count: 0, snippets: [] };
|
|
4959
|
+
return { hit: false, count: 0, snippets: [], positions: [] };
|
|
4650
4960
|
}
|
|
4651
4961
|
|
|
4652
4962
|
const rawMaxBytes = Number(options.maxBytes);
|
|
@@ -4654,8 +4964,17 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
|
|
|
4654
4964
|
? Math.max(1024, rawMaxBytes)
|
|
4655
4965
|
: 0;
|
|
4656
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
|
+
}
|
|
4657
4976
|
if (session.source === 'gemini') {
|
|
4658
|
-
if (state.roleFilter !== 'all') {
|
|
4977
|
+
if (state.roleFilter !== 'all' || state.positionsLimit > 0) {
|
|
4659
4978
|
let json;
|
|
4660
4979
|
try {
|
|
4661
4980
|
json = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
@@ -4669,7 +4988,12 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
|
|
|
4669
4988
|
if (!role) continue;
|
|
4670
4989
|
const text = extractMessageText(extractGeminiMessageText(entry.content ?? entry.message ?? entry.text));
|
|
4671
4990
|
if (!text) continue;
|
|
4672
|
-
if (consumeSessionQueryMessage(state, {
|
|
4991
|
+
if (consumeSessionQueryMessage(state, {
|
|
4992
|
+
role,
|
|
4993
|
+
text,
|
|
4994
|
+
timestamp: toIsoTime(entry.timestamp ?? entry.time ?? entry.at, ''),
|
|
4995
|
+
recordLineIndex: -1
|
|
4996
|
+
})) {
|
|
4673
4997
|
break;
|
|
4674
4998
|
}
|
|
4675
4999
|
}
|
|
@@ -4709,7 +5033,10 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
|
|
|
4709
5033
|
rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
4710
5034
|
|
|
4711
5035
|
let bytesRead = 0;
|
|
5036
|
+
let lineIndex = 0;
|
|
4712
5037
|
for await (const line of rl) {
|
|
5038
|
+
const currentLineIndex = lineIndex;
|
|
5039
|
+
lineIndex += 1;
|
|
4713
5040
|
if (maxBytes > 0 && bytesRead >= maxBytes) {
|
|
4714
5041
|
break;
|
|
4715
5042
|
}
|
|
@@ -4731,6 +5058,8 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
|
|
|
4731
5058
|
if (!message) {
|
|
4732
5059
|
continue;
|
|
4733
5060
|
}
|
|
5061
|
+
message.recordLineIndex = currentLineIndex;
|
|
5062
|
+
message.timestamp = toIsoTime(record.timestamp, '');
|
|
4734
5063
|
if (consumeSessionQueryMessage(state, message)) {
|
|
4735
5064
|
break;
|
|
4736
5065
|
}
|
|
@@ -4949,13 +5278,7 @@ function setSessionListCache(cacheKey, value) {
|
|
|
4949
5278
|
}
|
|
4950
5279
|
|
|
4951
5280
|
function buildSessionInventoryCacheKey(source, limit, options = {}) {
|
|
4952
|
-
const normalizedSource = source
|
|
4953
|
-
? 'claude'
|
|
4954
|
-
: (source === 'gemini'
|
|
4955
|
-
? 'gemini'
|
|
4956
|
-
: (source === 'codebuddy'
|
|
4957
|
-
? 'codebuddy'
|
|
4958
|
-
: (source === 'pi' ? 'pi' : 'codex')));
|
|
5281
|
+
const normalizedSource = normalizeSessionSourceName(source, 'codex');
|
|
4959
5282
|
const normalizedLimit = Number.isFinite(Number(limit))
|
|
4960
5283
|
? Math.max(1, Math.floor(Number(limit)))
|
|
4961
5284
|
: 1;
|
|
@@ -5041,7 +5364,7 @@ function getSessionInventoryCache(cacheKey, forceRefresh = false) {
|
|
|
5041
5364
|
}
|
|
5042
5365
|
|
|
5043
5366
|
function registerSessionFileLookupEntries(source, sessions = []) {
|
|
5044
|
-
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'
|
|
5045
5368
|
? source
|
|
5046
5369
|
: 'codex';
|
|
5047
5370
|
const store = g_sessionFileLookupCache[normalizedSource];
|
|
@@ -5082,7 +5405,7 @@ function setSessionInventoryCache(cacheKey, source, value) {
|
|
|
5082
5405
|
}
|
|
5083
5406
|
|
|
5084
5407
|
function listSessionInventoryBySource(source, limit, scanOptions = {}, options = {}) {
|
|
5085
|
-
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'
|
|
5086
5409
|
? source
|
|
5087
5410
|
: 'codex';
|
|
5088
5411
|
const forceRefresh = !!options.forceRefresh;
|
|
@@ -5098,9 +5421,13 @@ function listSessionInventoryBySource(source, limit, scanOptions = {}, options =
|
|
|
5098
5421
|
? listGeminiSessions(limit, scanOptions)
|
|
5099
5422
|
: (normalizedSource === 'codebuddy'
|
|
5100
5423
|
? listCodeBuddySessions(limit, scanOptions)
|
|
5101
|
-
: (normalizedSource === '
|
|
5102
|
-
?
|
|
5103
|
-
:
|
|
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))))));
|
|
5104
5431
|
setSessionInventoryCache(cacheKey, normalizedSource, sessions);
|
|
5105
5432
|
return sessions;
|
|
5106
5433
|
}
|
|
@@ -5113,7 +5440,9 @@ function invalidateSessionListCache() {
|
|
|
5113
5440
|
claude: new Map(),
|
|
5114
5441
|
gemini: new Map(),
|
|
5115
5442
|
codebuddy: new Map(),
|
|
5116
|
-
|
|
5443
|
+
workbuddy: new Map(),
|
|
5444
|
+
pi: new Map(),
|
|
5445
|
+
opencode: new Map()
|
|
5117
5446
|
};
|
|
5118
5447
|
}
|
|
5119
5448
|
|
|
@@ -5347,13 +5676,7 @@ function readSessionProviderFromRecord(record, source = '') {
|
|
|
5347
5676
|
if (provider) {
|
|
5348
5677
|
return provider;
|
|
5349
5678
|
}
|
|
5350
|
-
return source
|
|
5351
|
-
? 'claude'
|
|
5352
|
-
: (source === 'gemini'
|
|
5353
|
-
? 'gemini'
|
|
5354
|
-
: (source === 'codebuddy'
|
|
5355
|
-
? 'codebuddy'
|
|
5356
|
-
: (source === 'pi' ? 'pi' : 'codex')));
|
|
5679
|
+
return getSessionSourceProvider(source);
|
|
5357
5680
|
}
|
|
5358
5681
|
|
|
5359
5682
|
function applySessionUsageSummaryFromRecord(state, record, source) {
|
|
@@ -5898,6 +6221,175 @@ function parseCodeBuddySessionSummary(filePath, options = {}) {
|
|
|
5898
6221
|
};
|
|
5899
6222
|
}
|
|
5900
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
|
+
|
|
5901
6393
|
function extractGeminiMessageText(content) {
|
|
5902
6394
|
if (typeof content === 'string') {
|
|
5903
6395
|
return content;
|
|
@@ -6662,6 +7154,54 @@ function listCodeBuddySessions(limit, options = {}) {
|
|
|
6662
7154
|
return mergeAndLimitSessions(sessions, limit);
|
|
6663
7155
|
}
|
|
6664
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
|
+
|
|
6665
7205
|
function listPiSessions(limit, options = {}) {
|
|
6666
7206
|
const sessionsDir = getPiSessionsDir();
|
|
6667
7207
|
if (!fs.existsSync(sessionsDir)) {
|
|
@@ -6711,9 +7251,7 @@ function listPiSessions(limit, options = {}) {
|
|
|
6711
7251
|
}
|
|
6712
7252
|
|
|
6713
7253
|
async function listAllSessions(params = {}) {
|
|
6714
|
-
const source = params.source
|
|
6715
|
-
? params.source
|
|
6716
|
-
: 'all';
|
|
7254
|
+
const source = normalizeSessionSourceName(params.source, '') || 'all';
|
|
6717
7255
|
const rawLimit = Number(params.limit);
|
|
6718
7256
|
const limit = Number.isFinite(rawLimit)
|
|
6719
7257
|
? Math.max(1, Math.min(rawLimit, MAX_SESSION_LIST_SIZE))
|
|
@@ -6761,9 +7299,15 @@ async function listAllSessions(params = {}) {
|
|
|
6761
7299
|
if (source === 'all' || source === 'codebuddy') {
|
|
6762
7300
|
sessions = sessions.concat(listSessionInventoryBySource('codebuddy', limit, scanOptions, { forceRefresh }));
|
|
6763
7301
|
}
|
|
7302
|
+
if (source === 'all' || source === 'workbuddy') {
|
|
7303
|
+
sessions = sessions.concat(listSessionInventoryBySource('workbuddy', limit, scanOptions, { forceRefresh }));
|
|
7304
|
+
}
|
|
6764
7305
|
if (source === 'all' || source === 'pi') {
|
|
6765
7306
|
sessions = sessions.concat(listSessionInventoryBySource('pi', limit, scanOptions, { forceRefresh }));
|
|
6766
7307
|
}
|
|
7308
|
+
if (source === 'all' || source === 'opencode') {
|
|
7309
|
+
sessions = sessions.concat(listSessionInventoryBySource('opencode', limit, scanOptions, { forceRefresh }));
|
|
7310
|
+
}
|
|
6767
7311
|
|
|
6768
7312
|
if (hasPathFilter) {
|
|
6769
7313
|
sessions = sessions.filter(item => matchesSessionPathFilter(item, normalizedPathFilter));
|
|
@@ -6786,9 +7330,7 @@ async function listAllSessions(params = {}) {
|
|
|
6786
7330
|
}
|
|
6787
7331
|
|
|
6788
7332
|
async function listAllSessionsData(params = {}) {
|
|
6789
|
-
const source = params.source
|
|
6790
|
-
? params.source
|
|
6791
|
-
: 'all';
|
|
7333
|
+
const source = normalizeSessionSourceName(params.source, '') || 'all';
|
|
6792
7334
|
const rawLimit = Number(params.limit);
|
|
6793
7335
|
const limit = Number.isFinite(rawLimit)
|
|
6794
7336
|
? Math.max(1, Math.min(rawLimit, MAX_SESSION_LIST_SIZE))
|
|
@@ -6845,6 +7387,7 @@ async function listSessionUsage(params = {}) {
|
|
|
6845
7387
|
parseCodexSessionSummary,
|
|
6846
7388
|
parseClaudeSessionSummary,
|
|
6847
7389
|
parseCodeBuddySessionSummary,
|
|
7390
|
+
parseWorkBuddySessionSummary,
|
|
6848
7391
|
parseGeminiSessionSummary,
|
|
6849
7392
|
parsePiSessionSummary,
|
|
6850
7393
|
MAX_SESSION_USAGE_LIST_SIZE,
|
|
@@ -6860,10 +7403,10 @@ async function exportSessionUsage(params = {}) {
|
|
|
6860
7403
|
|
|
6861
7404
|
function listSessionPaths(params = {}) {
|
|
6862
7405
|
const source = typeof params.source === 'string' ? params.source.trim().toLowerCase() : '';
|
|
6863
|
-
if (source && source
|
|
7406
|
+
if (source && !isSessionSourceOrAll(source)) {
|
|
6864
7407
|
return [];
|
|
6865
7408
|
}
|
|
6866
|
-
const validSource = source
|
|
7409
|
+
const validSource = normalizeSessionSourceName(source, '') || (source === 'all' ? 'all' : 'all');
|
|
6867
7410
|
const rawLimit = Number(params.limit);
|
|
6868
7411
|
const limit = Number.isFinite(rawLimit)
|
|
6869
7412
|
? Math.max(1, Math.min(rawLimit, MAX_SESSION_PATH_LIST_SIZE))
|
|
@@ -6897,9 +7440,15 @@ function listSessionPaths(params = {}) {
|
|
|
6897
7440
|
if (validSource === 'all' || validSource === 'codebuddy') {
|
|
6898
7441
|
sessions = sessions.concat(listSessionInventoryBySource('codebuddy', gatherLimit, scanOptions, { forceRefresh }));
|
|
6899
7442
|
}
|
|
7443
|
+
if (validSource === 'all' || validSource === 'workbuddy') {
|
|
7444
|
+
sessions = sessions.concat(listSessionInventoryBySource('workbuddy', gatherLimit, scanOptions, { forceRefresh }));
|
|
7445
|
+
}
|
|
6900
7446
|
if (validSource === 'all' || validSource === 'pi') {
|
|
6901
7447
|
sessions = sessions.concat(listSessionInventoryBySource('pi', gatherLimit, scanOptions, { forceRefresh }));
|
|
6902
7448
|
}
|
|
7449
|
+
if (validSource === 'all' || validSource === 'opencode') {
|
|
7450
|
+
sessions = sessions.concat(listSessionInventoryBySource('opencode', gatherLimit, scanOptions, { forceRefresh }));
|
|
7451
|
+
}
|
|
6903
7452
|
|
|
6904
7453
|
const dedupedPaths = [];
|
|
6905
7454
|
const seen = new Set();
|
|
@@ -6925,9 +7474,13 @@ function listSessionPaths(params = {}) {
|
|
|
6925
7474
|
}
|
|
6926
7475
|
|
|
6927
7476
|
function resolveSessionFilePath(source, filePath, sessionId) {
|
|
6928
|
-
const normalizedSource = source
|
|
6929
|
-
|
|
6930
|
-
|
|
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
|
+
}
|
|
6931
7484
|
const homeDir = process && process.env && process.env.HOME ? process.env.HOME : '';
|
|
6932
7485
|
const derivedCodexDir = homeDir ? `${homeDir}/.codexmate/sessions/derived/codex` : '';
|
|
6933
7486
|
const derivedClaudeDir = homeDir ? `${homeDir}/.codexmate/sessions/derived/claude` : '';
|
|
@@ -6937,9 +7490,11 @@ function resolveSessionFilePath(source, filePath, sessionId) {
|
|
|
6937
7490
|
? [getGeminiTmpDir()]
|
|
6938
7491
|
: (normalizedSource === 'codebuddy'
|
|
6939
7492
|
? [getCodeBuddyProjectsDir()]
|
|
6940
|
-
: (normalizedSource === '
|
|
6941
|
-
? [
|
|
6942
|
-
:
|
|
7493
|
+
: (normalizedSource === 'workbuddy'
|
|
7494
|
+
? [getWorkBuddyProjectsDir()]
|
|
7495
|
+
: (normalizedSource === 'pi'
|
|
7496
|
+
? [getPiSessionsDir()]
|
|
7497
|
+
: [getCodexSessionsDir(), derivedCodexDir]))));
|
|
6943
7498
|
const availableRoots = roots.filter((dirPath) => dirPath && fs.existsSync(dirPath));
|
|
6944
7499
|
if (availableRoots.length === 0) {
|
|
6945
7500
|
return '';
|
|
@@ -7485,23 +8040,11 @@ function moveFileSync(sourcePath, targetPath) {
|
|
|
7485
8040
|
|
|
7486
8041
|
function buildSessionSummaryFallback(source, filePath, sessionId = '') {
|
|
7487
8042
|
const resolvedSessionId = sessionId || path.basename(filePath, '.jsonl');
|
|
7488
|
-
const sourceLabel = source
|
|
7489
|
-
? 'Claude Code'
|
|
7490
|
-
: (source === 'gemini'
|
|
7491
|
-
? 'Gemini CLI'
|
|
7492
|
-
: (source === 'codebuddy'
|
|
7493
|
-
? 'CodeBuddy Code'
|
|
7494
|
-
: (source === 'pi' ? 'Pi' : 'Codex')));
|
|
8043
|
+
const sourceLabel = getSessionSourceLabel(source);
|
|
7495
8044
|
return {
|
|
7496
8045
|
source,
|
|
7497
8046
|
sourceLabel,
|
|
7498
|
-
provider: source
|
|
7499
|
-
? 'claude'
|
|
7500
|
-
: (source === 'gemini'
|
|
7501
|
-
? 'gemini'
|
|
7502
|
-
: (source === 'codebuddy'
|
|
7503
|
-
? 'codebuddy'
|
|
7504
|
-
: (source === 'pi' ? 'pi' : 'codex'))),
|
|
8047
|
+
provider: getSessionSourceProvider(source),
|
|
7505
8048
|
sessionId: resolvedSessionId,
|
|
7506
8049
|
title: resolvedSessionId,
|
|
7507
8050
|
cwd: '',
|
|
@@ -7512,7 +8055,7 @@ function buildSessionSummaryFallback(source, filePath, sessionId = '') {
|
|
|
7512
8055
|
contextWindow: 0,
|
|
7513
8056
|
filePath,
|
|
7514
8057
|
keywords: [],
|
|
7515
|
-
capabilities: source === 'claude' || source === 'gemini' || source === 'codebuddy' ? { code: true } : {}
|
|
8058
|
+
capabilities: source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'opencode' ? { code: true } : {}
|
|
7516
8059
|
};
|
|
7517
8060
|
}
|
|
7518
8061
|
|
|
@@ -7556,7 +8099,9 @@ function normalizeSessionTrashEntry(entry) {
|
|
|
7556
8099
|
? 'gemini'
|
|
7557
8100
|
: (entry.source === 'codebuddy'
|
|
7558
8101
|
? 'codebuddy'
|
|
7559
|
-
: (entry.source === '
|
|
8102
|
+
: (entry.source === 'workbuddy'
|
|
8103
|
+
? 'workbuddy'
|
|
8104
|
+
: (entry.source === 'pi' ? 'pi' : '')))));
|
|
7560
8105
|
const trashId = typeof entry.trashId === 'string' ? entry.trashId.trim() : '';
|
|
7561
8106
|
if (!source || !trashId || trashId.includes('/') || trashId.includes('\\') || trashId.includes('\0')) {
|
|
7562
8107
|
return null;
|
|
@@ -7577,7 +8122,9 @@ function normalizeSessionTrashEntry(entry) {
|
|
|
7577
8122
|
? 'Gemini CLI'
|
|
7578
8123
|
: (source === 'codebuddy'
|
|
7579
8124
|
? 'CodeBuddy Code'
|
|
7580
|
-
: (source === '
|
|
8125
|
+
: (source === 'workbuddy'
|
|
8126
|
+
? 'WorkBuddy AI'
|
|
8127
|
+
: (source === 'pi' ? 'Pi' : 'Codex')))),
|
|
7581
8128
|
sessionId: sessionId || trashId,
|
|
7582
8129
|
title: typeof entry.title === 'string' && entry.title.trim() ? entry.title.trim() : (sessionId || trashId),
|
|
7583
8130
|
cwd: typeof entry.cwd === 'string' ? entry.cwd : '',
|
|
@@ -7593,7 +8140,7 @@ function normalizeSessionTrashEntry(entry) {
|
|
|
7593
8140
|
originalFilePath: typeof entry.originalFilePath === 'string' ? entry.originalFilePath : '',
|
|
7594
8141
|
provider: typeof entry.provider === 'string' && entry.provider.trim()
|
|
7595
8142
|
? entry.provider.trim()
|
|
7596
|
-
: (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'))))),
|
|
7597
8144
|
keywords: normalizeKeywords(entry.keywords),
|
|
7598
8145
|
capabilities: normalizeCapabilities(entry.capabilities),
|
|
7599
8146
|
claudeIndexPath: typeof entry.claudeIndexPath === 'string' ? entry.claudeIndexPath : '',
|
|
@@ -7686,7 +8233,9 @@ function buildSessionTrashEntry(summary, options = {}) {
|
|
|
7686
8233
|
? 'gemini'
|
|
7687
8234
|
: (options.source === 'codebuddy'
|
|
7688
8235
|
? 'codebuddy'
|
|
7689
|
-
: (options.source === '
|
|
8236
|
+
: (options.source === 'workbuddy'
|
|
8237
|
+
? 'workbuddy'
|
|
8238
|
+
: (options.source === 'pi' ? 'pi' : 'codex'))));
|
|
7690
8239
|
const sessionId = options.sessionId || summary.sessionId || path.basename(options.originalFilePath || summary.filePath || '', '.jsonl');
|
|
7691
8240
|
const claudeIndexEntry = options.claudeIndexEntry && typeof options.claudeIndexEntry === 'object' && !Array.isArray(options.claudeIndexEntry)
|
|
7692
8241
|
? options.claudeIndexEntry
|
|
@@ -7700,7 +8249,9 @@ function buildSessionTrashEntry(summary, options = {}) {
|
|
|
7700
8249
|
? 'Gemini CLI'
|
|
7701
8250
|
: (source === 'codebuddy'
|
|
7702
8251
|
? 'CodeBuddy Code'
|
|
7703
|
-
: (source === '
|
|
8252
|
+
: (source === 'workbuddy'
|
|
8253
|
+
? 'WorkBuddy AI'
|
|
8254
|
+
: (source === 'pi' ? 'Pi' : 'Codex'))));
|
|
7704
8255
|
const fallbackTitle = truncateText(
|
|
7705
8256
|
(claudeIndexEntry && (claudeIndexEntry.summary || claudeIndexEntry.firstPrompt)) || sessionId,
|
|
7706
8257
|
120
|
|
@@ -7737,7 +8288,7 @@ function buildSessionTrashEntry(summary, options = {}) {
|
|
|
7737
8288
|
originalFilePath: options.originalFilePath || summary.filePath || '',
|
|
7738
8289
|
provider: (claudeIndexEntry && typeof claudeIndexEntry.provider === 'string' && claudeIndexEntry.provider.trim())
|
|
7739
8290
|
? claudeIndexEntry.provider.trim()
|
|
7740
|
-
: (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')))))),
|
|
7741
8292
|
keywords: normalizedClaudeKeywords.length > 0 ? normalizedClaudeKeywords : normalizedSummaryKeywords,
|
|
7742
8293
|
capabilities: Object.keys(normalizedClaudeCapabilities).length > 0
|
|
7743
8294
|
? normalizedClaudeCapabilities
|
|
@@ -7758,7 +8309,9 @@ function resolveSessionRestoreTarget(entry) {
|
|
|
7758
8309
|
? getGeminiTmpDir()
|
|
7759
8310
|
: (normalized.source === 'codebuddy'
|
|
7760
8311
|
? getCodeBuddyProjectsDir()
|
|
7761
|
-
: (normalized.source === '
|
|
8312
|
+
: (normalized.source === 'workbuddy'
|
|
8313
|
+
? getWorkBuddyProjectsDir()
|
|
8314
|
+
: (normalized.source === 'pi' ? getPiSessionsDir() : getCodexSessionsDir()))));
|
|
7762
8315
|
const originalFilePath = typeof normalized.originalFilePath === 'string' ? normalized.originalFilePath.trim() : '';
|
|
7763
8316
|
if (!root || !originalFilePath) {
|
|
7764
8317
|
return '';
|
|
@@ -7892,15 +8445,7 @@ function upsertClaudeSessionIndexEntry(indexPath, sessionFilePath, entry) {
|
|
|
7892
8445
|
}
|
|
7893
8446
|
|
|
7894
8447
|
async function listSessionTrashItems(params = {}) {
|
|
7895
|
-
const source = params.source
|
|
7896
|
-
? 'claude'
|
|
7897
|
-
: (params.source === 'codex'
|
|
7898
|
-
? 'codex'
|
|
7899
|
-
: (params.source === 'gemini'
|
|
7900
|
-
? 'gemini'
|
|
7901
|
-
: (params.source === 'codebuddy'
|
|
7902
|
-
? 'codebuddy'
|
|
7903
|
-
: (params.source === 'pi' ? 'pi' : 'all'))));
|
|
8448
|
+
const source = normalizeSessionSourceName(params.source, '') || 'all';
|
|
7904
8449
|
const countOnly = params.countOnly === true;
|
|
7905
8450
|
const rawLimit = Number(params.limit);
|
|
7906
8451
|
const limit = Number.isFinite(rawLimit)
|
|
@@ -7910,7 +8455,7 @@ async function listSessionTrashItems(params = {}) {
|
|
|
7910
8455
|
purgeExpiredSessionTrashEntries(params.retentionDays);
|
|
7911
8456
|
}
|
|
7912
8457
|
const allEntries = readSessionTrashEntries();
|
|
7913
|
-
let items = source
|
|
8458
|
+
let items = normalizeSessionSourceName(source, '')
|
|
7914
8459
|
? allEntries.filter((entry) => entry.source === source)
|
|
7915
8460
|
: allEntries.slice();
|
|
7916
8461
|
items.sort((a, b) => {
|
|
@@ -8090,18 +8635,13 @@ async function purgeSessionTrashItems(params = {}) {
|
|
|
8090
8635
|
}
|
|
8091
8636
|
|
|
8092
8637
|
async function trashSessionData(params = {}) {
|
|
8093
|
-
const source = params.source
|
|
8094
|
-
? 'claude'
|
|
8095
|
-
: (params.source === 'codex'
|
|
8096
|
-
? 'codex'
|
|
8097
|
-
: (params.source === 'gemini'
|
|
8098
|
-
? 'gemini'
|
|
8099
|
-
: (params.source === 'codebuddy'
|
|
8100
|
-
? 'codebuddy'
|
|
8101
|
-
: (params.source === 'pi' ? 'pi' : ''))));
|
|
8638
|
+
const source = normalizeSessionSourceName(params.source, '');
|
|
8102
8639
|
if (!source) {
|
|
8103
8640
|
return { error: 'Invalid source' };
|
|
8104
8641
|
}
|
|
8642
|
+
if (source === 'opencode') {
|
|
8643
|
+
return { error: 'OpenCode DB-backed sessions cannot be deleted from CodexMate' };
|
|
8644
|
+
}
|
|
8105
8645
|
|
|
8106
8646
|
const filePath = resolveSessionFilePath(source, getSessionFileArg(params), params.sessionId);
|
|
8107
8647
|
if (!filePath) {
|
|
@@ -8114,7 +8654,9 @@ async function trashSessionData(params = {}) {
|
|
|
8114
8654
|
? parseGeminiSessionSummary(filePath)
|
|
8115
8655
|
: (source === 'codebuddy'
|
|
8116
8656
|
? parseCodeBuddySessionSummary(filePath)
|
|
8117
|
-
: (source === '
|
|
8657
|
+
: (source === 'workbuddy'
|
|
8658
|
+
? parseWorkBuddySessionSummary(filePath)
|
|
8659
|
+
: (source === 'pi' ? parsePiSessionSummary(filePath) : parseCodexSessionSummary(filePath))))))
|
|
8118
8660
|
|| buildSessionSummaryFallback(source, filePath, params.sessionId);
|
|
8119
8661
|
const exactMessageCount = await countConversationMessagesInFile(filePath, source);
|
|
8120
8662
|
if (Number.isFinite(Number(exactMessageCount))) {
|
|
@@ -8205,18 +8747,13 @@ async function trashSessionData(params = {}) {
|
|
|
8205
8747
|
}
|
|
8206
8748
|
|
|
8207
8749
|
async function deleteSessionData(params = {}) {
|
|
8208
|
-
const source = params.source
|
|
8209
|
-
? 'claude'
|
|
8210
|
-
: (params.source === 'codex'
|
|
8211
|
-
? 'codex'
|
|
8212
|
-
: (params.source === 'gemini'
|
|
8213
|
-
? 'gemini'
|
|
8214
|
-
: (params.source === 'codebuddy'
|
|
8215
|
-
? 'codebuddy'
|
|
8216
|
-
: (params.source === 'pi' ? 'pi' : ''))));
|
|
8750
|
+
const source = normalizeSessionSourceName(params.source, '');
|
|
8217
8751
|
if (!source) {
|
|
8218
8752
|
return { error: 'Invalid source' };
|
|
8219
8753
|
}
|
|
8754
|
+
if (source === 'opencode') {
|
|
8755
|
+
return { error: 'OpenCode DB-backed sessions cannot be deleted from CodexMate' };
|
|
8756
|
+
}
|
|
8220
8757
|
|
|
8221
8758
|
const filePath = resolveSessionFilePath(source, getSessionFileArg(params), params.sessionId);
|
|
8222
8759
|
if (!filePath) {
|
|
@@ -8764,7 +9301,7 @@ function recordHasPiMessage(record) {
|
|
|
8764
9301
|
|
|
8765
9302
|
function recordHasMessage(record, source) {
|
|
8766
9303
|
if (source === 'codex') return recordHasCodexMessage(record);
|
|
8767
|
-
if (source === 'codebuddy') return recordHasCodeBuddyMessage(record);
|
|
9304
|
+
if (source === 'codebuddy' || source === 'workbuddy') return recordHasCodeBuddyMessage(record);
|
|
8768
9305
|
if (source === 'pi') return recordHasPiMessage(record);
|
|
8769
9306
|
return recordHasClaudeMessage(record);
|
|
8770
9307
|
}
|
|
@@ -8784,7 +9321,7 @@ function extractMessagesFromRecords(records, source, options = {}) {
|
|
|
8784
9321
|
const record = records[lineIndex];
|
|
8785
9322
|
if (source === 'codex') {
|
|
8786
9323
|
extractCodexMessageFromRecord(record, state, lineIndex);
|
|
8787
|
-
} else if (source === 'codebuddy') {
|
|
9324
|
+
} else if (source === 'codebuddy' || source === 'workbuddy') {
|
|
8788
9325
|
extractCodeBuddyMessageFromRecord(record, state, lineIndex);
|
|
8789
9326
|
} else if (source === 'pi') {
|
|
8790
9327
|
extractPiMessageFromRecord(record, state, lineIndex);
|
|
@@ -8849,7 +9386,7 @@ async function extractMessagesFromFile(filePath, source, options = {}) {
|
|
|
8849
9386
|
|
|
8850
9387
|
if (source === 'codex') {
|
|
8851
9388
|
extractCodexMessageFromRecord(record, state, currentLineIndex);
|
|
8852
|
-
} else if (source === 'codebuddy') {
|
|
9389
|
+
} else if (source === 'codebuddy' || source === 'workbuddy') {
|
|
8853
9390
|
extractCodeBuddyMessageFromRecord(record, state, currentLineIndex);
|
|
8854
9391
|
} else if (source === 'pi') {
|
|
8855
9392
|
extractPiMessageFromRecord(record, state, currentLineIndex);
|
|
@@ -8877,15 +9414,7 @@ async function extractMessagesFromFile(filePath, source, options = {}) {
|
|
|
8877
9414
|
}
|
|
8878
9415
|
|
|
8879
9416
|
async function readSessionDetail(params = {}) {
|
|
8880
|
-
const source = params.source
|
|
8881
|
-
? 'claude'
|
|
8882
|
-
: (params.source === 'codex'
|
|
8883
|
-
? 'codex'
|
|
8884
|
-
: (params.source === 'gemini'
|
|
8885
|
-
? 'gemini'
|
|
8886
|
-
: (params.source === 'codebuddy'
|
|
8887
|
-
? 'codebuddy'
|
|
8888
|
-
: (params.source === 'pi' ? 'pi' : ''))));
|
|
9417
|
+
const source = normalizeSessionSourceName(params.source, '');
|
|
8889
9418
|
if (!source) {
|
|
8890
9419
|
return { error: 'Invalid source' };
|
|
8891
9420
|
}
|
|
@@ -8901,9 +9430,30 @@ async function readSessionDetail(params = {}) {
|
|
|
8901
9430
|
? Math.max(1, Math.min(rawLimit, MAX_SESSION_DETAIL_MESSAGES))
|
|
8902
9431
|
: DEFAULT_SESSION_DETAIL_MESSAGES;
|
|
8903
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
|
+
}
|
|
8904
9448
|
|
|
8905
9449
|
let extracted;
|
|
8906
|
-
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') {
|
|
8907
9457
|
let json;
|
|
8908
9458
|
try {
|
|
8909
9459
|
json = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
@@ -8943,13 +9493,7 @@ async function readSessionDetail(params = {}) {
|
|
|
8943
9493
|
extracted = await extractSessionDetailPreviewFromFile(filePath, source, messageLimit, { preview });
|
|
8944
9494
|
}
|
|
8945
9495
|
const sessionId = extracted.sessionId || params.sessionId || path.basename(filePath, source === 'gemini' ? '.json' : '.jsonl');
|
|
8946
|
-
const sourceLabel = source
|
|
8947
|
-
? 'Codex'
|
|
8948
|
-
: (source === 'claude'
|
|
8949
|
-
? 'Claude Code'
|
|
8950
|
-
: (source === 'gemini'
|
|
8951
|
-
? 'Gemini CLI'
|
|
8952
|
-
: (source === 'pi' ? 'Pi' : 'CodeBuddy Code')));
|
|
9496
|
+
const sourceLabel = getSessionSourceLabel(source);
|
|
8953
9497
|
const clippedMessages = Array.isArray(extracted.messages) ? extracted.messages : [];
|
|
8954
9498
|
const hasExactTotalMessages = Number.isFinite(extracted.totalMessages);
|
|
8955
9499
|
const startIndex = hasExactTotalMessages
|
|
@@ -8993,6 +9537,13 @@ async function readSessionDetail(params = {}) {
|
|
|
8993
9537
|
? extracted.clipped
|
|
8994
9538
|
: (hasExactTotalMessages ? extracted.totalMessages > indexedMessages.length : false),
|
|
8995
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
|
+
} : {}),
|
|
8996
9547
|
messages: indexedMessages,
|
|
8997
9548
|
filePath,
|
|
8998
9549
|
...(typeof buildSessionNativeStatus === 'function'
|
|
@@ -9010,15 +9561,7 @@ async function readSessionDetail(params = {}) {
|
|
|
9010
9561
|
}
|
|
9011
9562
|
|
|
9012
9563
|
async function readSessionPlain(params = {}) {
|
|
9013
|
-
const source = params.source
|
|
9014
|
-
? 'claude'
|
|
9015
|
-
: (params.source === 'codex'
|
|
9016
|
-
? 'codex'
|
|
9017
|
-
: (params.source === 'gemini'
|
|
9018
|
-
? 'gemini'
|
|
9019
|
-
: (params.source === 'codebuddy'
|
|
9020
|
-
? 'codebuddy'
|
|
9021
|
-
: (params.source === 'pi' ? 'pi' : ''))));
|
|
9564
|
+
const source = normalizeSessionSourceName(params.source, '');
|
|
9022
9565
|
if (!source) {
|
|
9023
9566
|
return { error: 'Invalid source' };
|
|
9024
9567
|
}
|
|
@@ -9038,7 +9581,13 @@ async function readSessionPlain(params = {}) {
|
|
|
9038
9581
|
);
|
|
9039
9582
|
|
|
9040
9583
|
let extracted;
|
|
9041
|
-
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') {
|
|
9042
9591
|
let json;
|
|
9043
9592
|
try {
|
|
9044
9593
|
json = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
@@ -9088,13 +9637,7 @@ async function readSessionPlain(params = {}) {
|
|
|
9088
9637
|
}
|
|
9089
9638
|
|
|
9090
9639
|
const sessionId = extracted.sessionId || params.sessionId || path.basename(filePath, source === 'gemini' ? '.json' : '.jsonl');
|
|
9091
|
-
const sourceLabel = source
|
|
9092
|
-
? 'Codex'
|
|
9093
|
-
: (source === 'claude'
|
|
9094
|
-
? 'Claude Code'
|
|
9095
|
-
: (source === 'gemini'
|
|
9096
|
-
? 'Gemini CLI'
|
|
9097
|
-
: (source === 'pi' ? 'Pi' : 'CodeBuddy Code')));
|
|
9640
|
+
const sourceLabel = getSessionSourceLabel(source);
|
|
9098
9641
|
const messages = removeLeadingSystemMessage(Array.isArray(extracted.messages) ? extracted.messages : []);
|
|
9099
9642
|
const text = buildSessionPlainText(messages);
|
|
9100
9643
|
|
|
@@ -9110,15 +9653,7 @@ async function readSessionPlain(params = {}) {
|
|
|
9110
9653
|
}
|
|
9111
9654
|
|
|
9112
9655
|
async function exportSessionData(params = {}) {
|
|
9113
|
-
const source = params.source
|
|
9114
|
-
? 'claude'
|
|
9115
|
-
: (params.source === 'codex'
|
|
9116
|
-
? 'codex'
|
|
9117
|
-
: (params.source === 'gemini'
|
|
9118
|
-
? 'gemini'
|
|
9119
|
-
: (params.source === 'codebuddy'
|
|
9120
|
-
? 'codebuddy'
|
|
9121
|
-
: (params.source === 'pi' ? 'pi' : ''))));
|
|
9656
|
+
const source = normalizeSessionSourceName(params.source, '');
|
|
9122
9657
|
if (!source) {
|
|
9123
9658
|
return { error: 'Invalid source' };
|
|
9124
9659
|
}
|
|
@@ -9130,7 +9665,13 @@ async function exportSessionData(params = {}) {
|
|
|
9130
9665
|
}
|
|
9131
9666
|
|
|
9132
9667
|
let extracted;
|
|
9133
|
-
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') {
|
|
9134
9675
|
let json;
|
|
9135
9676
|
try {
|
|
9136
9677
|
json = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
@@ -9188,13 +9729,7 @@ async function exportSessionData(params = {}) {
|
|
|
9188
9729
|
|
|
9189
9730
|
const sessionId = extracted.sessionId || params.sessionId || path.basename(filePath, source === 'gemini' ? '.json' : '.jsonl');
|
|
9190
9731
|
const safeSessionId = String(sessionId).replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
9191
|
-
const sourceLabel = source
|
|
9192
|
-
? 'Codex'
|
|
9193
|
-
: (source === 'claude'
|
|
9194
|
-
? 'Claude Code'
|
|
9195
|
-
: (source === 'gemini'
|
|
9196
|
-
? 'Gemini CLI'
|
|
9197
|
-
: (source === 'pi' ? 'Pi' : 'CodeBuddy Code')));
|
|
9732
|
+
const sourceLabel = getSessionSourceLabel(source);
|
|
9198
9733
|
const truncated = !!extracted.truncated;
|
|
9199
9734
|
const maxMessagesLabel = maxMessages === Infinity ? 'all' : maxMessages;
|
|
9200
9735
|
const markdown = buildSessionMarkdown({
|
|
@@ -11082,7 +11617,8 @@ function runKilocodeCommand(args = [], options = {}) {
|
|
|
11082
11617
|
const child = spawn(bin, finalArgs, {
|
|
11083
11618
|
detached: true,
|
|
11084
11619
|
stdio: 'ignore',
|
|
11085
|
-
windowsHide: true
|
|
11620
|
+
windowsHide: true,
|
|
11621
|
+
shell: process.platform === 'win32'
|
|
11086
11622
|
});
|
|
11087
11623
|
child.unref();
|
|
11088
11624
|
return { success: true, bin, args: finalArgs, pid: child.pid };
|
|
@@ -11090,7 +11626,8 @@ function runKilocodeCommand(args = [], options = {}) {
|
|
|
11090
11626
|
return new Promise((resolve, reject) => {
|
|
11091
11627
|
const child = spawn(bin, finalArgs, {
|
|
11092
11628
|
stdio: 'inherit',
|
|
11093
|
-
windowsHide: false
|
|
11629
|
+
windowsHide: false,
|
|
11630
|
+
shell: process.platform === 'win32'
|
|
11094
11631
|
});
|
|
11095
11632
|
child.on('error', reject);
|
|
11096
11633
|
child.on('exit', (code, signal) => {
|
|
@@ -12379,7 +12916,7 @@ function resolveExportOutputPath(outputPath, defaultFileName) {
|
|
|
12379
12916
|
}
|
|
12380
12917
|
|
|
12381
12918
|
function printExportSessionUsage() {
|
|
12382
|
-
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>]');
|
|
12383
12920
|
console.log('\n示例:');
|
|
12384
12921
|
console.log(' codexmate export-session --source codex --session-id 123456');
|
|
12385
12922
|
console.log(' codexmate export-session --source claude --file "~/.claude/projects/demo/session.jsonl"');
|
|
@@ -12451,8 +12988,8 @@ function parseExportSessionArgs(args = []) {
|
|
|
12451
12988
|
}
|
|
12452
12989
|
|
|
12453
12990
|
const normalizedSource = options.source.trim().toLowerCase();
|
|
12454
|
-
if (normalizedSource && normalizedSource
|
|
12455
|
-
errors.push('参数 --source 仅支持 codex、claude 或
|
|
12991
|
+
if (normalizedSource && !isSessionSourceOrAll(normalizedSource)) {
|
|
12992
|
+
errors.push('参数 --source 仅支持 codex、claude、gemini、codebuddy、workbuddy、pi 或 opencode');
|
|
12456
12993
|
}
|
|
12457
12994
|
options.source = normalizedSource;
|
|
12458
12995
|
|
|
@@ -12525,7 +13062,7 @@ async function cmdExportSession(args = []) {
|
|
|
12525
13062
|
|
|
12526
13063
|
function printAnalyticsUsage() {
|
|
12527
13064
|
console.log('\n用法:');
|
|
12528
|
-
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|->]');
|
|
12529
13066
|
console.log('');
|
|
12530
13067
|
}
|
|
12531
13068
|
|
|
@@ -13880,8 +14417,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13880
14417
|
case 'list-sessions':
|
|
13881
14418
|
{
|
|
13882
14419
|
const source = typeof params.source === 'string' ? params.source.trim().toLowerCase() : '';
|
|
13883
|
-
if (source && source
|
|
13884
|
-
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' };
|
|
13885
14422
|
} else {
|
|
13886
14423
|
result = {
|
|
13887
14424
|
sessions: await listSessionBrowse(params),
|
|
@@ -13894,8 +14431,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13894
14431
|
{
|
|
13895
14432
|
const usageParams = isPlainObject(params) ? params : {};
|
|
13896
14433
|
const source = typeof usageParams.source === 'string' ? usageParams.source.trim().toLowerCase() : '';
|
|
13897
|
-
if (source && source
|
|
13898
|
-
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' };
|
|
13899
14436
|
} else {
|
|
13900
14437
|
result = {
|
|
13901
14438
|
sessions: await listSessionUsage({
|
|
@@ -13911,8 +14448,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13911
14448
|
{
|
|
13912
14449
|
const usageParams = isPlainObject(params) ? params : {};
|
|
13913
14450
|
const source = typeof usageParams.source === 'string' ? usageParams.source.trim().toLowerCase() : '';
|
|
13914
|
-
if (source && source
|
|
13915
|
-
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' };
|
|
13916
14453
|
} else {
|
|
13917
14454
|
result = await exportSessionUsage({
|
|
13918
14455
|
...usageParams,
|
|
@@ -13924,8 +14461,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13924
14461
|
case 'list-session-paths':
|
|
13925
14462
|
{
|
|
13926
14463
|
const source = typeof params.source === 'string' ? params.source.trim().toLowerCase() : '';
|
|
13927
|
-
if (source && source
|
|
13928
|
-
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' };
|
|
13929
14466
|
} else {
|
|
13930
14467
|
result = {
|
|
13931
14468
|
paths: listSessionPaths(params)
|
|
@@ -15436,7 +15973,7 @@ function buildMcpClaudeSettingsPayload() {
|
|
|
15436
15973
|
function normalizeMcpSource(value) {
|
|
15437
15974
|
const source = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
15438
15975
|
if (!source) return '';
|
|
15439
|
-
if (source
|
|
15976
|
+
if (normalizeSessionSourceName(source, '') || source === 'all') {
|
|
15440
15977
|
return source;
|
|
15441
15978
|
}
|
|
15442
15979
|
return null;
|
|
@@ -15749,7 +16286,7 @@ function createWorkflowToolCatalog() {
|
|
|
15749
16286
|
handler: async (args = {}) => {
|
|
15750
16287
|
const source = normalizeMcpSource(args.source);
|
|
15751
16288
|
if (source === null) {
|
|
15752
|
-
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' };
|
|
15753
16290
|
}
|
|
15754
16291
|
return {
|
|
15755
16292
|
source: source || 'all',
|
|
@@ -16115,7 +16652,7 @@ function createMcpTools(options = {}) {
|
|
|
16115
16652
|
const input = args && typeof args === 'object' ? args : {};
|
|
16116
16653
|
const source = normalizeMcpSource(input.source);
|
|
16117
16654
|
if (source === null) {
|
|
16118
|
-
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' };
|
|
16119
16656
|
}
|
|
16120
16657
|
const normalizedInput = {
|
|
16121
16658
|
...input,
|
|
@@ -16575,7 +17112,7 @@ function createMcpResources() {
|
|
|
16575
17112
|
contents: [{
|
|
16576
17113
|
uri,
|
|
16577
17114
|
mimeType: 'application/json',
|
|
16578
|
-
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)
|
|
16579
17116
|
}]
|
|
16580
17117
|
};
|
|
16581
17118
|
}
|
|
@@ -16824,7 +17361,7 @@ function printMainHelp() {
|
|
|
16824
17361
|
console.log(' 注: follow-up 自动排队仅支持 linux/android/netbsd/openbsd/darwin/freebsd 且 stdin 必须是 TTY,其他平台会报错');
|
|
16825
17362
|
console.log(' codexmate qwen [参数...] 等同于 qwen --yolo');
|
|
16826
17363
|
console.log(' codexmate mcp [serve] [--transport stdio] [--allow-write|--read-only]');
|
|
16827
|
-
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>]');
|
|
16828
17365
|
console.log(' codexmate convert-session --from <codex|claude> --to <codex|claude> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
|
|
16829
17366
|
console.log(' codexmate zip <路径> [--max:级别] 压缩(系统 zip 优先,其次 zip-lib)');
|
|
16830
17367
|
console.log(' codexmate unzip <zip文件> [输出目录] 解压(zip-lib)');
|