From f3daedfbe0f04b00a618db067262a0e21010f32f Mon Sep 17 00:00:00 2001 From: tommy0103 Date: Mon, 3 Aug 2026 00:49:37 +0800 Subject: [PATCH] fix: exclude empty Kimi placeholder sessions --- packages/core/src/providers/kimi.ts | 8 +- tests/app-kimi-index.test.mjs | 118 +++++++++++++++++++++++++++- 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/packages/core/src/providers/kimi.ts b/packages/core/src/providers/kimi.ts index 4e49997..7e3a236 100644 --- a/packages/core/src/providers/kimi.ts +++ b/packages/core/src/providers/kimi.ts @@ -56,7 +56,7 @@ interface ProjectedSession { } const SOURCE = 'kimi'; -export const KIMI_CANONICAL_TRANSCRIPT_MARKER = '__kimi_canonical_transcript_v3__'; +export const KIMI_CANONICAL_TRANSCRIPT_MARKER = '__kimi_canonical_transcript_v4__'; function defaultKimiRoot(): string { return process.env['KIMI_CODE_HOME'] ?? join(homedir(), '.kimi-code'); @@ -679,6 +679,12 @@ export function createKimiProvider({ rootDir = defaultKimiRoot() }: { rootDir?: if (before !== after) throw new Error(`Kimi session changed while indexing: ${meta.sessionDir}`); yield { kind: 'delete-session', sessionId: unit.sessionId }; + // Kimi persists a titled session before the first prompt; keep it retracted until user evidence exists. + const hasLastPrompt = typeof state.lastPrompt === 'string' && state.lastPrompt.length > 0; + const hasProjectedUserPrompt = projected.messages.some((message) => ( + message.agent_id === null && message.role === 'user' && !message.is_meta + )); + if (state.title === 'New Session' && !hasLastPrompt && !hasProjectedUserPrompt) return after; yield { kind: 'session', id: unit.sessionId, diff --git a/tests/app-kimi-index.test.mjs b/tests/app-kimi-index.test.mjs index 4a783d4..990c902 100644 --- a/tests/app-kimi-index.test.mjs +++ b/tests/app-kimi-index.test.mjs @@ -50,6 +50,43 @@ function writeSession(kimiDir, { userSlash = false } = {}) { return { sessionDir, wirePath, records }; } +function writePlaceholderSession(kimiDir, { userPrompt = false } = {}) { + const sessionDir = join(kimiDir, 'sessions', 'workspace-1', 'session-placeholder-1'); + const mainDir = join(sessionDir, 'agents', 'main'); + mkdirSync(mainDir, { recursive: true }); + writeFileSync(join(sessionDir, 'state.json'), JSON.stringify({ + title: 'New Session', + workDir: '/tmp/indexed-kimi', + createdAt: '2026-07-20T10:00:00.000Z', + updatedAt: '2026-07-20T10:00:00.000Z', + agents: { main: { type: 'main' } }, + })); + const records = [ + { type: 'metadata', protocol_version: '1.5', created_at: 1753005600000 }, + { type: 'config.update', profileName: 'agent', systemPrompt: 'Kimi Code CLI' }, + { type: 'tools.set_active_tools', tools: [] }, + { type: 'config.update', modelAlias: 'kimi-code/kimi-for-coding' }, + ]; + if (userPrompt) { + records.push({ + type: 'context.append_message', + time: 1753005601000, + message: { + role: 'user', + content: [{ type: 'text', text: 'real prompt before metadata catches up' }], + toolCalls: [], + origin: { kind: 'user' }, + }, + }); + } + const wirePath = join(mainDir, 'wire.jsonl'); + writeFileSync( + wirePath, + records.map((record) => JSON.stringify(record)).join('\n') + '\n', + ); + return { sessionDir, wirePath }; +} + test('app build indexes Kimi sessions through the provider registry without changing schema', () => { const home = mkdtempSync(join(tmpdir(), 'obelisk-kimi-index-')); const claudeDir = join(home, '.claude'); @@ -90,6 +127,85 @@ test('app build indexes Kimi sessions through the provider registry without chan assert.deepEqual(second.affectedSessionIds, []); }); +test('app build excludes never-started Kimi placeholder sessions', () => { + const home = mkdtempSync(join(tmpdir(), 'obelisk-kimi-placeholder-')); + const kimiDir = join(home, '.kimi-code'); + const dbPath = join(home, '.obelisk', 'obelisk.sqlite'); + writePlaceholderSession(kimiDir); + + buildIndex({ + claudeDir: join(home, '.claude'), + codexDir: join(home, '.codex'), + providerRoots: { kimi: kimiDir }, + dbPath, + DatabaseImpl: TestDatabase, + }); + + const db = new TestDatabase(dbPath); + assert.deepEqual( + db.prepare("SELECT id,title,message_count FROM sessions WHERE source='kimi'").all().map((row) => ({ ...row })), + [], + ); + db.close(); +}); + +test('app build keeps a prompted Kimi session while placeholder metadata catches up', () => { + const home = mkdtempSync(join(tmpdir(), 'obelisk-kimi-prompted-placeholder-')); + const kimiDir = join(home, '.kimi-code'); + const dbPath = join(home, '.obelisk', 'obelisk.sqlite'); + writePlaceholderSession(kimiDir, { userPrompt: true }); + + buildIndex({ + claudeDir: join(home, '.claude'), + codexDir: join(home, '.codex'), + providerRoots: { kimi: kimiDir }, + dbPath, + DatabaseImpl: TestDatabase, + }); + + const db = new TestDatabase(dbPath); + assert.deepEqual( + db.prepare("SELECT id,title,message_count FROM sessions WHERE source='kimi'").all().map((row) => ({ ...row })), + [{ id: 'kimi:session-placeholder-1', title: 'New Session', message_count: 1 }], + ); + db.close(); +}); + +test('Kimi marker upgrade retracts previously indexed placeholder sessions', () => { + const home = mkdtempSync(join(tmpdir(), 'obelisk-kimi-placeholder-replay-')); + const kimiDir = join(home, '.kimi-code'); + const dbPath = join(home, '.obelisk', 'obelisk.sqlite'); + const { wirePath } = writePlaceholderSession(kimiDir); + const options = { + claudeDir: join(home, '.claude'), + codexDir: join(home, '.codex'), + providerRoots: { kimi: kimiDir }, + dbPath, + DatabaseImpl: TestDatabase, + }; + + buildIndex(options); + let db = new TestDatabase(dbPath); + const currentMarker = createKimiProvider({ rootDir: kimiDir }).indexVersionMarker; + db.prepare('DELETE FROM index_state WHERE jsonl_path=?').run(currentMarker); + db.prepare( + 'INSERT INTO index_state (jsonl_path,mtime,lines_processed) VALUES (?,0,0)', + ).run('__kimi_canonical_transcript_v3__'); + db.prepare(` + INSERT INTO sessions (id,title,message_count,jsonl_path,source) + VALUES (?,?,?,?,?) + `).run('kimi:session-placeholder-1', 'New Session', 0, wirePath, 'kimi'); + db.close(); + + buildIndex(options); + db = new TestDatabase(dbPath); + assert.deepEqual( + db.prepare("SELECT id,title,message_count FROM sessions WHERE source='kimi'").all().map((row) => ({ ...row })), + [], + ); + db.close(); +}); + test('Kimi undo and clear replace the indexed session instead of leaving stale rows', () => { const home = mkdtempSync(join(tmpdir(), 'obelisk-kimi-replay-')); const claudeDir = join(home, '.claude'); @@ -151,7 +267,7 @@ test('Kimi canonical transcript marker replays unchanged sessions once', () => { buildIndex(options); let db = new TestDatabase(dbPath); const marker = createKimiProvider({ rootDir: kimiDir }).indexVersionMarker; - assert.equal(marker, '__kimi_canonical_transcript_v3__'); + assert.equal(marker, '__kimi_canonical_transcript_v4__'); db.prepare("UPDATE messages SET text='stale expanded instructions', is_meta=1 WHERE source='kimi'").run(); db.prepare('DELETE FROM index_state WHERE jsonl_path=?').run(marker); db.close();