Fix live agent refresh without persistence
This commit is contained in:
@@ -14,6 +14,14 @@ import {
|
|||||||
} from "./test-utils/index.js";
|
} from "./test-utils/index.js";
|
||||||
import { getFullAccessConfig, getAskModeConfig } from "./daemon-e2e/agent-configs.js";
|
import { getFullAccessConfig, getAskModeConfig } from "./daemon-e2e/agent-configs.js";
|
||||||
import { chunkPcm16, parsePcm16MonoWav, wordSimilarity } from "./test-utils/dictation-e2e.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;
|
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", () => {
|
describe("daemon client E2E", () => {
|
||||||
let ctx: DaemonTestContext;
|
let ctx: DaemonTestContext;
|
||||||
|
|
||||||
@@ -279,6 +397,38 @@ describe("daemon client E2E", () => {
|
|||||||
}
|
}
|
||||||
}, 120000);
|
}, 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 () => {
|
test("resume_agent auto-unarchives archived agents", async () => {
|
||||||
const cwd = tmpCwd();
|
const cwd = tmpCwd();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -3095,11 +3095,7 @@ export class Session {
|
|||||||
const existing = this.agentManager.getAgent(agentId);
|
const existing = this.agentManager.getAgent(agentId);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
await this.interruptAgentIfRunning(agentId);
|
await this.interruptAgentIfRunning(agentId);
|
||||||
if (existing.persistence) {
|
snapshot = await this.agentManager.reloadAgentSession(agentId);
|
||||||
snapshot = await this.agentManager.reloadAgentSession(agentId);
|
|
||||||
} else {
|
|
||||||
snapshot = existing;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const record = await this.agentStorage.get(agentId);
|
const record = await this.agentStorage.get(agentId);
|
||||||
if (!record) {
|
if (!record) {
|
||||||
|
|||||||
Reference in New Issue
Block a user