diff --git a/.changeset/session-workdir-resume-fix.md b/.changeset/session-workdir-resume-fix.md new file mode 100644 index 0000000000..f5199fcbcc --- /dev/null +++ b/.changeset/session-workdir-resume-fix.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Restore older working-directory sessions in `/sessions` and `kimi --continue`. \ No newline at end of file diff --git a/apps/kimi-code/src/cli/v2/run-v2-print.ts b/apps/kimi-code/src/cli/v2/run-v2-print.ts index 299e926caa..fc1d778653 100644 --- a/apps/kimi-code/src/cli/v2/run-v2-print.ts +++ b/apps/kimi-code/src/cli/v2/run-v2-print.ts @@ -374,7 +374,9 @@ async function resolveNativeSession( if (opts.continue) { const page = await index.listRecent({}); - const previous = page.items.find((summary) => summary.cwd === workDir); + const previous = page.items.find( + (summary) => summary.cwd !== undefined && resolve(summary.cwd) === resolve(workDir), + ); if (previous !== undefined) { const session = await resumeById(previous.id); const agentContext = await ensureMainAgent(session); diff --git a/packages/agent-core-v2/src/app/sessionIndex/sessionIndexService.ts b/packages/agent-core-v2/src/app/sessionIndex/sessionIndexService.ts index df259588f8..4d0af389aa 100644 --- a/packages/agent-core-v2/src/app/sessionIndex/sessionIndexService.ts +++ b/packages/agent-core-v2/src/app/sessionIndex/sessionIndexService.ts @@ -267,7 +267,20 @@ export class FileSessionIndex extends Disposable implements ISessionIndex { async listRecent(query: SessionListQuery): Promise> { return this.withReadModel( - (generation) => this.listRecentFromReadModel(generation, query), + async (generation) => { + const page = await this.listRecentFromReadModel(generation, query); + // A workspace-scoped read that returns nothing can still hide sessions + // that exist on disk but were not projected into the read model (e.g. + // legacy/v1-era sessions recorded with only `workDir`, or sessions in + // alias buckets). Coalesce onto the authoritative source so `/sessions` + // (working-directory scope) and `--continue` never hide them. Unscooped + // reads stay on the projected recency column. + if (page.items.length === 0 && query.workspaceIds !== undefined) { + const legacy = await this.listLegacy(query); + if (legacy.items.length > 0) return legacy; + } + return page; + }, () => this.listLegacy(query), ); } diff --git a/packages/agent-core-v2/test/app/sessionIndex/sessionIndex.test.ts b/packages/agent-core-v2/test/app/sessionIndex/sessionIndex.test.ts index 61b93cc999..c024266835 100644 --- a/packages/agent-core-v2/test/app/sessionIndex/sessionIndex.test.ts +++ b/packages/agent-core-v2/test/app/sessionIndex/sessionIndex.test.ts @@ -525,6 +525,22 @@ describe('FileSessionIndex (read model)', () => { expect(fileStorage.listCalls).toBe(0); }); + it('coalesces a workspace-scoped miss onto the authoritative directory', async () => { + const store = build(); + await store.prepare(); + store.stopReconcileLoop(); + // A legacy/v1-era session recorded with only `workDir` (no `cwd`) that + // exists on disk but was never projected into the read model. + await seedSession('legacy', { workDir: WORK_DIR, createdAt: 1, updatedAt: 2 }); + + // Read model answers empty for the workspace; the built-in fallback must + // surface the on-disk session so `/sessions` (cwd scope) and --continue + // do not hide it. + const page = await store.listRecent({ workspaceIds: [workspaceId] }); + expect(page.items.map((s) => s.id)).toEqual(['legacy']); + expect(page.items[0]).toMatchObject({ cwd: WORK_DIR }); + }); + it('paginates exactly through same-millisecond ties', async () => { const specs: [string, number][] = [ ['a', 100],