mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-21 06:35:50 +00:00
fix(agent-core-v2): let session archive proceed after a failed resume (#3139)
A failed resume is cached by SessionManager and rethrown from whenResumeSettled, so archiving a session whose workspace is gone failed with the stale resume error even though cold archive only rewrites session metadata. Swallow the settle failure: still wait for an in-flight resume before the live/cold classification, but fall through to the cold metadata path after a failed one.
This commit is contained in:
parent
e9a99e5ec6
commit
381142aff1
4 changed files with 35 additions and 8 deletions
5
.changeset/archive-missing-workspace.md
Normal file
5
.changeset/archive-missing-workspace.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
Fix sessions failing to archive when their workspace folder no longer exists.
|
||||
|
|
@ -72,7 +72,7 @@ export async function setSessionArchived(
|
|||
): Promise<ColdSessionArchiveOutcome> {
|
||||
const manager = accessor.get(ISessionManager);
|
||||
return manager.withLifecycleSerialization(sessionId, async (unguarded) => {
|
||||
await manager.whenResumeSettled(sessionId);
|
||||
await manager.whenResumeSettled(sessionId).catch(() => undefined);
|
||||
const live = getLiveSessionById(accessor, sessionId);
|
||||
if (live !== undefined) {
|
||||
if (archived) await unguarded.archive();
|
||||
|
|
|
|||
|
|
@ -171,20 +171,16 @@ describe('setSessionArchivedBatch', () => {
|
|||
expect(persisted['isCustomTitle']).toBe(true);
|
||||
});
|
||||
|
||||
it('fails the item when a concurrent resume failed instead of cold-classifying', async () => {
|
||||
it('cold-classifies the item when a concurrent resume failed', async () => {
|
||||
const outcomes = await setSessionArchivedBatch(
|
||||
coldPathAccessor({
|
||||
storeGet: async () => {
|
||||
throw new Error('unreachable — the settle throws first');
|
||||
},
|
||||
storeGet: async () => ({ id: 's1', createdAt: 1, updatedAt: 2, archived: false }),
|
||||
resumeError: new Error('resume boom'),
|
||||
}),
|
||||
['s1'],
|
||||
true,
|
||||
);
|
||||
expect(outcomes).toEqual([
|
||||
{ id: 's1', ok: false, reason: 'error', message: 'resume boom' },
|
||||
]);
|
||||
expect(outcomes).toEqual([{ id: 's1', ok: true }]);
|
||||
});
|
||||
|
||||
it('reads and migrates the legacy session-meta location before answering not_found', async () => {
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@ import {
|
|||
IEventService,
|
||||
ISessionCronService,
|
||||
ISessionManager,
|
||||
IWorkspaceService,
|
||||
MAIN_AGENT_ID,
|
||||
closeSessionById,
|
||||
getLiveSessionById,
|
||||
resumeSessionById,
|
||||
sessionDirOf,
|
||||
type ServiceIdentifier,
|
||||
type ScopeSeed,
|
||||
|
|
@ -782,6 +784,30 @@ describe('server-v2 /api/v1/sessions', () => {
|
|||
expect(got.body.data.archived).toBe(true);
|
||||
});
|
||||
|
||||
it('archives a cold session after a failed resume when the workspace root is gone', async () => {
|
||||
const cwd = join(home as string, 'gone-ws');
|
||||
await mkdir(cwd);
|
||||
const created = await postJson<SessionWire>('/api/v1/sessions', { metadata: { cwd } });
|
||||
const id = created.body.data.id;
|
||||
await closeSessionById((server as RunningServer).core.accessor, id);
|
||||
await (server as RunningServer).core.accessor
|
||||
.get(IWorkspaceService)
|
||||
.delete(encodeWorkDirKey(cwd));
|
||||
await rm(cwd, { recursive: true, force: true });
|
||||
|
||||
await expect(
|
||||
resumeSessionById((server as RunningServer).core.accessor, id),
|
||||
).rejects.toThrow(/does not exist/);
|
||||
|
||||
const archived = await postJson<{ archived: boolean }>(`/api/v1/sessions/${id}:archive`);
|
||||
expect(archived.body.code).toBe(0);
|
||||
expect(archived.body.data).toEqual({ archived: true });
|
||||
|
||||
const got = await getJson<SessionWire>(`/api/v1/sessions/${id}`);
|
||||
expect(got.body.code).toBe(0);
|
||||
expect(got.body.data.archived).toBe(true);
|
||||
});
|
||||
|
||||
it('restores an archived session via :restore and returns it to the default list', async () => {
|
||||
const cwd = home as string;
|
||||
const created = await postJson<SessionWire>('/api/v1/sessions', { metadata: { cwd } });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue