Fix live agent refresh without persistence

This commit is contained in:
Mohamed Boudra
2026-04-09 20:05:07 +07:00
parent 17bd036359
commit 29af07e383
2 changed files with 151 additions and 5 deletions

View File

@@ -14,6 +14,14 @@ import {
} from "./test-utils/index.js";
import { getFullAccessConfig, getAskModeConfig } from "./daemon-e2e/agent-configs.js";
import { chunkPcm16, parsePcm16MonoWav, wordSimilarity } from "./test-utils/dictation-e2e.js";
import type {
AgentClient,
AgentPersistenceHandle,
AgentRunResult,
AgentSession,
AgentSessionConfig,
AgentStreamEvent,
} from "./agent/agent-sdk-types.js";
const openaiApiKey = process.env.OPENAI_API_KEY ?? null;
@@ -94,6 +102,116 @@ function waitForSignal<T>(
});
}
class NonPersistentReloadSession implements AgentSession {
readonly provider = "claude" as const;
readonly id = null;
readonly capabilities = {
supportsStreaming: false,
supportsSessionPersistence: true,
supportsDynamicModes: false,
supportsMcpServers: false,
supportsReasoningStream: false,
supportsToolInvocations: false,
} as const;
constructor(private readonly onClose: () => void) {}
async run(): Promise<AgentRunResult> {
return {
sessionId: "non-persistent",
finalText: "",
timeline: [],
};
}
async startTurn(): Promise<{ turnId: string }> {
return { turnId: "non-persistent-turn" };
}
subscribe(_callback: (event: AgentStreamEvent) => void): () => void {
return () => undefined;
}
async *streamHistory(): AsyncGenerator<AgentStreamEvent> {
return;
}
async getRuntimeInfo() {
return {
provider: "claude" as const,
sessionId: null,
model: null,
modeId: null,
};
}
async getAvailableModes(): Promise<[]> {
return [];
}
async getCurrentMode(): Promise<string | null> {
return null;
}
async setMode(_modeId: string): Promise<void> {}
getPendingPermissions() {
return [];
}
async respondToPermission(): Promise<void> {}
describePersistence(): AgentPersistenceHandle | null {
return null;
}
async interrupt(): Promise<void> {}
async close(): Promise<void> {
this.onClose();
}
}
class NonPersistentReloadClient implements AgentClient {
readonly provider = "claude" as const;
readonly capabilities = {
supportsStreaming: false,
supportsSessionPersistence: true,
supportsDynamicModes: false,
supportsMcpServers: false,
supportsReasoningStream: false,
supportsToolInvocations: false,
} as const;
createSessionCalls = 0;
resumeSessionCalls = 0;
closeCalls = 0;
async isAvailable(): Promise<boolean> {
return true;
}
async createSession(_config: AgentSessionConfig): Promise<AgentSession> {
this.createSessionCalls += 1;
return new NonPersistentReloadSession(() => {
this.closeCalls += 1;
});
}
async resumeSession(
_handle: AgentPersistenceHandle,
_overrides?: Partial<AgentSessionConfig>,
): Promise<AgentSession> {
this.resumeSessionCalls += 1;
return new NonPersistentReloadSession(() => {
this.closeCalls += 1;
});
}
async listModels() {
return [];
}
}
describe("daemon client E2E", () => {
let ctx: DaemonTestContext;
@@ -279,6 +397,38 @@ describe("daemon client E2E", () => {
}
}, 120000);
test("refresh_agent rebuilds a live agent even when it has no persistence handle", async () => {
const cwd = tmpCwd();
const client = new NonPersistentReloadClient();
const localCtx = await createDaemonTestContext({
agentClients: {
claude: client,
},
});
try {
const created = await localCtx.client.createAgent({
config: {
provider: "claude",
cwd,
},
});
expect(client.createSessionCalls).toBe(1);
expect(client.resumeSessionCalls).toBe(0);
expect(client.closeCalls).toBe(0);
await localCtx.client.refreshAgent(created.id);
expect(client.createSessionCalls).toBe(2);
expect(client.resumeSessionCalls).toBe(0);
expect(client.closeCalls).toBe(1);
} finally {
await localCtx.cleanup();
rmSync(cwd, { recursive: true, force: true });
}
});
test("resume_agent auto-unarchives archived agents", async () => {
const cwd = tmpCwd();
try {

View File

@@ -3095,11 +3095,7 @@ export class Session {
const existing = this.agentManager.getAgent(agentId);
if (existing) {
await this.interruptAgentIfRunning(agentId);
if (existing.persistence) {
snapshot = await this.agentManager.reloadAgentSession(agentId);
} else {
snapshot = existing;
}
snapshot = await this.agentManager.reloadAgentSession(agentId);
} else {
const record = await this.agentStorage.get(agentId);
if (!record) {