codemini-cli 0.5.6 → 0.5.8
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/codemini-web/dist/assets/{highlighted-body-OFNGDK62-Cc6TA1Qw.js → highlighted-body-OFNGDK62-CCcxtQK_.js} +1 -1
- package/codemini-web/dist/assets/index-CMISAOFr.css +2 -0
- package/codemini-web/dist/assets/index-Cy4HN-FS.js +428 -0
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-BWFzYc7A.js +1 -0
- package/codemini-web/dist/index.html +2 -2
- package/codemini-web/lib/runtime-bridge.js +2 -2
- package/codemini-web/server.js +111 -51
- package/package.json +1 -1
- package/src/core/chat-runtime.js +21 -1
- package/src/core/provider/anthropic.js +137 -24
- package/src/core/session-store.js +132 -21
- package/codemini-web/dist/assets/index-BqNKEgHB.js +0 -428
- package/codemini-web/dist/assets/index-dYs_njBc.css +0 -2
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-JDxagHq_.js +0 -1
|
@@ -7,6 +7,8 @@ import { normalizeTodos } from './todo-state.js';
|
|
|
7
7
|
const ALLOWED_ROLES = new Set(['system', 'user', 'assistant', 'tool']);
|
|
8
8
|
const SESSION_LEGACY_EXT = '.json';
|
|
9
9
|
const SESSION_JSONL_EXT = '.jsonl';
|
|
10
|
+
const SESSION_INDEX_FILE = 'index.json';
|
|
11
|
+
const SESSION_INDEX_VERSION = 1;
|
|
10
12
|
const DEFAULT_SESSION_TITLE = '新会话';
|
|
11
13
|
|
|
12
14
|
function createSessionId() {
|
|
@@ -153,6 +155,10 @@ function sessionPathById(sessionId, ext = SESSION_JSONL_EXT) {
|
|
|
153
155
|
return path.join(getSessionsDir(), `${sessionId}${ext}`);
|
|
154
156
|
}
|
|
155
157
|
|
|
158
|
+
function sessionIndexPath() {
|
|
159
|
+
return path.join(getSessionsDir(), SESSION_INDEX_FILE);
|
|
160
|
+
}
|
|
161
|
+
|
|
156
162
|
function isSafeSessionId(sessionId) {
|
|
157
163
|
return /^[A-Za-z0-9_.-]+$/.test(String(sessionId || ''));
|
|
158
164
|
}
|
|
@@ -172,6 +178,35 @@ async function listSessionFiles() {
|
|
|
172
178
|
.map((e) => path.join(dir, e.name));
|
|
173
179
|
}
|
|
174
180
|
|
|
181
|
+
async function listSessionFileMeta() {
|
|
182
|
+
const files = await listSessionFiles();
|
|
183
|
+
const meta = [];
|
|
184
|
+
for (const file of files) {
|
|
185
|
+
try {
|
|
186
|
+
const stat = await fs.stat(file);
|
|
187
|
+
meta.push({
|
|
188
|
+
name: path.basename(file),
|
|
189
|
+
size: stat.size,
|
|
190
|
+
mtimeMs: Math.trunc(stat.mtimeMs)
|
|
191
|
+
});
|
|
192
|
+
} catch {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
meta.sort((a, b) => a.name.localeCompare(b.name));
|
|
197
|
+
return meta;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function sameSessionFileMeta(a = [], b = []) {
|
|
201
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
202
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
203
|
+
if (a[i]?.name !== b[i]?.name) return false;
|
|
204
|
+
if (Number(a[i]?.size || 0) !== Number(b[i]?.size || 0)) return false;
|
|
205
|
+
if (Number(a[i]?.mtimeMs || 0) !== Number(b[i]?.mtimeMs || 0)) return false;
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
175
210
|
function summarizeParsedSession(parsed, filePath) {
|
|
176
211
|
const id = parsed.id || sessionIdFromFileName(path.basename(filePath));
|
|
177
212
|
const updatedAt = parsed.updatedAt || parsed.createdAt || '';
|
|
@@ -195,6 +230,88 @@ async function tryReadJson(filePath) {
|
|
|
195
230
|
return JSON.parse(raw);
|
|
196
231
|
}
|
|
197
232
|
|
|
233
|
+
async function readSessionIndex() {
|
|
234
|
+
try {
|
|
235
|
+
const index = await tryReadJson(sessionIndexPath());
|
|
236
|
+
if (index?.version !== SESSION_INDEX_VERSION || !Array.isArray(index?.sessions) || !Array.isArray(index?.files)) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
return index;
|
|
240
|
+
} catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function writeSessionIndex(index) {
|
|
246
|
+
const dir = getSessionsDir();
|
|
247
|
+
await fs.mkdir(dir, { recursive: true });
|
|
248
|
+
const filePath = sessionIndexPath();
|
|
249
|
+
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
250
|
+
const payload = {
|
|
251
|
+
version: SESSION_INDEX_VERSION,
|
|
252
|
+
updatedAt: new Date().toISOString(),
|
|
253
|
+
files: Array.isArray(index?.files) ? index.files : [],
|
|
254
|
+
sessions: Array.isArray(index?.sessions) ? index.sessions : []
|
|
255
|
+
};
|
|
256
|
+
await fs.writeFile(tempPath, `${JSON.stringify(payload)}\n`, 'utf8');
|
|
257
|
+
await fs.rename(tempPath, filePath);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function rebuildSessionIndex(fileMeta = null) {
|
|
261
|
+
const files = await listSessionFiles();
|
|
262
|
+
const sessionsById = new Map();
|
|
263
|
+
for (const file of files) {
|
|
264
|
+
try {
|
|
265
|
+
const parsed = file.endsWith(SESSION_JSONL_EXT) ? await loadLatestJsonlObject(file) : await tryReadJson(file);
|
|
266
|
+
const summary = summarizeParsedSession(parsed, file);
|
|
267
|
+
if (!summary.id) continue;
|
|
268
|
+
const existing = sessionsById.get(summary.id);
|
|
269
|
+
if (!existing || String(summary.updatedAt) > String(existing.updatedAt)) {
|
|
270
|
+
sessionsById.set(summary.id, summary);
|
|
271
|
+
}
|
|
272
|
+
} catch {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const sessions = Array.from(sessionsById.values());
|
|
278
|
+
sessions.sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)));
|
|
279
|
+
const filesMeta = fileMeta || await listSessionFileMeta();
|
|
280
|
+
const index = { files: filesMeta, sessions };
|
|
281
|
+
await writeSessionIndex(index);
|
|
282
|
+
return { ...index, version: SESSION_INDEX_VERSION };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function getSessionIndex() {
|
|
286
|
+
const fileMeta = await listSessionFileMeta();
|
|
287
|
+
const index = await readSessionIndex();
|
|
288
|
+
if (index && sameSessionFileMeta(index.files, fileMeta)) return index;
|
|
289
|
+
return rebuildSessionIndex(fileMeta);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function upsertSessionIndexEntry(session, filePath) {
|
|
293
|
+
try {
|
|
294
|
+
const summary = summarizeParsedSession(session, filePath);
|
|
295
|
+
if (!summary.id) return;
|
|
296
|
+
const stat = await fs.stat(filePath);
|
|
297
|
+
const fileEntry = {
|
|
298
|
+
name: path.basename(filePath),
|
|
299
|
+
size: stat.size,
|
|
300
|
+
mtimeMs: Math.trunc(stat.mtimeMs)
|
|
301
|
+
};
|
|
302
|
+
const index = await readSessionIndex();
|
|
303
|
+
const files = Array.isArray(index?.files) ? index.files.filter((entry) => entry?.name !== fileEntry.name) : [];
|
|
304
|
+
files.push(fileEntry);
|
|
305
|
+
files.sort((a, b) => a.name.localeCompare(b.name));
|
|
306
|
+
const sessions = Array.isArray(index?.sessions) ? index.sessions.filter((entry) => entry?.id !== summary.id) : [];
|
|
307
|
+
sessions.push(summary);
|
|
308
|
+
sessions.sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)));
|
|
309
|
+
await writeSessionIndex({ files, sessions });
|
|
310
|
+
} catch {
|
|
311
|
+
// Index updates are an optimization; session data remains authoritative.
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
198
315
|
async function loadLatestJsonlObject(filePath) {
|
|
199
316
|
const raw = await fs.readFile(filePath, 'utf8');
|
|
200
317
|
const lines = String(raw || '')
|
|
@@ -242,6 +359,7 @@ export async function createSession(projectDir = process.cwd()) {
|
|
|
242
359
|
messages: []
|
|
243
360
|
};
|
|
244
361
|
await fs.writeFile(filePath, `${JSON.stringify(payload)}\n`, 'utf8');
|
|
362
|
+
await upsertSessionIndexEntry(payload, filePath);
|
|
245
363
|
return payload;
|
|
246
364
|
}
|
|
247
365
|
|
|
@@ -257,6 +375,7 @@ export async function saveSession(session) {
|
|
|
257
375
|
normalized.updatedAt = new Date().toISOString();
|
|
258
376
|
const filePath = sessionPathById(normalized.id, SESSION_JSONL_EXT);
|
|
259
377
|
await fs.appendFile(filePath, `${JSON.stringify(normalized)}\n`, 'utf8');
|
|
378
|
+
await upsertSessionIndexEntry(normalized, filePath);
|
|
260
379
|
}
|
|
261
380
|
|
|
262
381
|
export async function resolveSession(sessionId) {
|
|
@@ -266,27 +385,11 @@ export async function resolveSession(sessionId) {
|
|
|
266
385
|
return createSession();
|
|
267
386
|
}
|
|
268
387
|
|
|
269
|
-
export async function listSessions(limit = 30) {
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
try {
|
|
275
|
-
const parsed = file.endsWith(SESSION_JSONL_EXT) ? await loadLatestJsonlObject(file) : await tryReadJson(file);
|
|
276
|
-
const summary = summarizeParsedSession(parsed, file);
|
|
277
|
-
if (!summary.id) continue;
|
|
278
|
-
const existing = sessionsById.get(summary.id);
|
|
279
|
-
if (!existing || String(summary.updatedAt) > String(existing.updatedAt)) {
|
|
280
|
-
sessionsById.set(summary.id, summary);
|
|
281
|
-
}
|
|
282
|
-
} catch {
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const sessions = Array.from(sessionsById.values());
|
|
288
|
-
sessions.sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)));
|
|
289
|
-
return sessions.filter((s) => Number(s.messageCount || 0) > 0).slice(0, limit);
|
|
388
|
+
export async function listSessions(limit = 30, { includeEmpty = false } = {}) {
|
|
389
|
+
const index = await getSessionIndex();
|
|
390
|
+
return [...index.sessions]
|
|
391
|
+
.filter((s) => includeEmpty || Number(s.messageCount || 0) > 0)
|
|
392
|
+
.slice(0, limit);
|
|
290
393
|
}
|
|
291
394
|
|
|
292
395
|
export async function deleteSession(sessionId) {
|
|
@@ -322,6 +425,11 @@ export async function deleteSession(sessionId) {
|
|
|
322
425
|
if (error?.code !== 'ENOENT') throw error;
|
|
323
426
|
}
|
|
324
427
|
}
|
|
428
|
+
if (removed > 0) {
|
|
429
|
+
try {
|
|
430
|
+
await rebuildSessionIndex();
|
|
431
|
+
} catch {}
|
|
432
|
+
}
|
|
325
433
|
return { removed };
|
|
326
434
|
}
|
|
327
435
|
|
|
@@ -359,5 +467,8 @@ export async function pruneSessions(policy = {}) {
|
|
|
359
467
|
continue;
|
|
360
468
|
}
|
|
361
469
|
}
|
|
470
|
+
try {
|
|
471
|
+
await rebuildSessionIndex();
|
|
472
|
+
} catch {}
|
|
362
473
|
return { removed, kept: keepIds.size };
|
|
363
474
|
}
|