mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-12 19:35:04 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
679edaf10e | ||
|
|
00693ec201 | ||
|
|
61b243d3cc | ||
|
|
0a24f731a6 |
@@ -170,7 +170,10 @@ describe("agent lifecycle commands", () => {
|
||||
manager.liveAgents.set("agent-1", managedAgent("agent-1", "running"));
|
||||
manager.inFlightAgentIds.add("agent-1");
|
||||
|
||||
const result = await cancelAgentRunCommand({ agentManager: manager, logger }, "agent-1");
|
||||
const result = await cancelAgentRunCommand(
|
||||
{ agentManager: manager, agentStorage: storage, logger },
|
||||
"agent-1",
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
agent: manager.liveAgents.get("agent-1"),
|
||||
@@ -187,13 +190,46 @@ describe("agent lifecycle commands", () => {
|
||||
manager.settledDuringCancellationAgentIds.add("agent-1");
|
||||
|
||||
await expect(
|
||||
cancelAgentRunCommand({ agentManager: manager, logger }, "agent-1"),
|
||||
cancelAgentRunCommand({ agentManager: manager, agentStorage: storage, logger }, "agent-1"),
|
||||
).resolves.toEqual({
|
||||
agent: manager.liveAgents.get("agent-1"),
|
||||
cancelled: false,
|
||||
});
|
||||
});
|
||||
|
||||
test.each(["closed", "idle", "error", "running", "initializing"] as const)(
|
||||
"accepts Stop on a stored %s agent without modifying its record",
|
||||
async (lastStatus) => {
|
||||
const storage = new FakeLifecycleAgentStorage();
|
||||
const manager = new FakeLifecycleAgentManager(storage);
|
||||
const record = { ...storedAgent("agent-1"), lastStatus };
|
||||
storage.records.set("agent-1", record);
|
||||
|
||||
await expect(
|
||||
cancelAgentRunCommand({ agentManager: manager, agentStorage: storage, logger }, "agent-1"),
|
||||
).resolves.toEqual({
|
||||
agent: { id: "agent-1", cwd: record.cwd, lifecycle: lastStatus },
|
||||
cancelled: false,
|
||||
});
|
||||
expect(manager.cancelledAgentIds).toEqual([]);
|
||||
expect(storage.upserts).toEqual([]);
|
||||
expect(storage.records.get("agent-1")).toEqual(record);
|
||||
},
|
||||
);
|
||||
|
||||
test("still reports not found when no stored record exists", async () => {
|
||||
const storage = new FakeLifecycleAgentStorage();
|
||||
const manager = new FakeLifecycleAgentManager(storage);
|
||||
|
||||
await expect(
|
||||
cancelAgentRunCommand(
|
||||
{ agentManager: manager, agentStorage: storage, logger },
|
||||
"missing-agent",
|
||||
),
|
||||
).rejects.toThrow("Agent missing-agent not found");
|
||||
expect(storage.upserts).toEqual([]);
|
||||
});
|
||||
|
||||
test("archives a live agent after canceling and clearing attention", async () => {
|
||||
const storage = new FakeLifecycleAgentStorage();
|
||||
const manager = new FakeLifecycleAgentManager(storage);
|
||||
|
||||
@@ -56,14 +56,13 @@ interface RequestedAgentRunCancellation extends CancelAgentRunResult {
|
||||
}
|
||||
|
||||
async function requestAgentRunCancellation(
|
||||
dependencies: Pick<AgentLifecycleCommandDependencies, "agentManager" | "logger">,
|
||||
dependencies: Pick<AgentLifecycleCommandDependencies, "agentManager" | "agentStorage" | "logger">,
|
||||
agentId: string,
|
||||
): Promise<RequestedAgentRunCancellation> {
|
||||
const { agentManager, logger } = dependencies;
|
||||
const agent = agentManager.getAgent(agentId);
|
||||
if (!agent) {
|
||||
logger.trace({ agentId }, "cancelAgentRunCommand: agent not found");
|
||||
throw new Error(`Agent ${agentId} not found`);
|
||||
return storedAgentWithoutRun(dependencies, agentId);
|
||||
}
|
||||
|
||||
const hasInFlightRun = agentManager.hasInFlightRun(agentId);
|
||||
@@ -93,8 +92,30 @@ async function requestAgentRunCancellation(
|
||||
};
|
||||
}
|
||||
|
||||
async function storedAgentWithoutRun(
|
||||
dependencies: Pick<AgentLifecycleCommandDependencies, "agentStorage" | "logger">,
|
||||
agentId: string,
|
||||
): Promise<RequestedAgentRunCancellation> {
|
||||
const { agentStorage, logger } = dependencies;
|
||||
const record = await agentStorage.get(agentId);
|
||||
if (!record) {
|
||||
logger.trace({ agentId }, "cancelAgentRunCommand: agent not found");
|
||||
throw new Error(`Agent ${agentId} not found`);
|
||||
}
|
||||
|
||||
logger.trace(
|
||||
{ agentId, lastStatus: record.lastStatus },
|
||||
"cancelAgentRunCommand: stored agent has no live runtime",
|
||||
);
|
||||
return {
|
||||
agent: { id: record.id, cwd: record.cwd, lifecycle: record.lastStatus },
|
||||
cancelled: false,
|
||||
cancellation: { status: "not_running" },
|
||||
};
|
||||
}
|
||||
|
||||
export async function cancelAgentRunCommand(
|
||||
dependencies: Pick<AgentLifecycleCommandDependencies, "agentManager" | "logger">,
|
||||
dependencies: Pick<AgentLifecycleCommandDependencies, "agentManager" | "agentStorage" | "logger">,
|
||||
agentId: string,
|
||||
): Promise<CancelAgentRunResult> {
|
||||
const result = await requestAgentRunCancellation(dependencies, agentId);
|
||||
|
||||
@@ -2226,7 +2226,7 @@ function createToolCatalog(
|
||||
},
|
||||
async ({ agentId }) => {
|
||||
const { cancelled } = await cancelAgentRunCommand(
|
||||
{ agentManager, logger: childLogger },
|
||||
{ agentManager, agentStorage, logger: childLogger },
|
||||
agentId,
|
||||
);
|
||||
return {
|
||||
|
||||
@@ -1155,6 +1155,14 @@ test("disabled recovery closes stale active records without resuming sessions",
|
||||
expect(
|
||||
Object.fromEntries(snapshot.entries.map((entry) => [entry.agent.id, entry.agent.status])),
|
||||
).toEqual(Object.fromEntries(expected.map((record) => [record.id, record.lastStatus])));
|
||||
const closedId = records.find((record) => record.lastStatus === "closed")!.id;
|
||||
const beforeStop = await handle.daemon.agentStorage.get(closedId);
|
||||
await expect(client.cancelAgent(closedId)).resolves.toBeUndefined();
|
||||
await expect(client.cancelAgent(closedId)).resolves.toBeUndefined();
|
||||
expect(await handle.daemon.agentStorage.get(closedId)).toEqual(beforeStop);
|
||||
expect(handle.daemon.agentManager.listAgents()).toEqual([]);
|
||||
expect(sessionsCreated).toBe(0);
|
||||
await expect(client.cancelAgent("missing-agent")).rejects.toThrow("not found");
|
||||
const opened = await ensureAgentLoaded(records[0]!.id, {
|
||||
agentManager: handle.daemon.agentManager,
|
||||
agentStorage: handle.daemon.agentStorage,
|
||||
|
||||
@@ -1387,7 +1387,8 @@ export async function createPaseoDaemon(
|
||||
agentManager,
|
||||
agentStorage,
|
||||
createAgent,
|
||||
interruptAgent: (agentId) => cancelAgentRunCommand({ agentManager, logger }, agentId),
|
||||
interruptAgent: (agentId) =>
|
||||
cancelAgentRunCommand({ agentManager, agentStorage, logger }, agentId),
|
||||
archiveWorkspace: archiveWorkspaceByIdExternal,
|
||||
cleanupFailedCreate: (input) =>
|
||||
hubAgentLifecycle.cleanupCreatedWorktreeAfterFailedAgentCreate(input),
|
||||
|
||||
@@ -64,7 +64,7 @@ function isAlive(pid: number): boolean {
|
||||
|
||||
async function waitFor<T>(
|
||||
probe: () => Promise<T | null | undefined | false> | T | null | undefined | false,
|
||||
message: string,
|
||||
message: string | (() => string),
|
||||
timeoutMs = READY_TIMEOUT_MS,
|
||||
): Promise<T> {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
@@ -78,7 +78,8 @@ async function waitFor<T>(
|
||||
}
|
||||
await delay(100);
|
||||
}
|
||||
throw new Error(`${message}${lastError ? ` (last error: ${String(lastError)})` : ""}`);
|
||||
const detail = typeof message === "function" ? message() : message;
|
||||
throw new Error(`${detail}${lastError ? ` (last error: ${String(lastError)})` : ""}`);
|
||||
}
|
||||
|
||||
async function getAvailablePort(): Promise<number> {
|
||||
@@ -123,10 +124,14 @@ async function startSupervisor(
|
||||
supervisor.stderr?.on("data", (chunk) => (output = (output + String(chunk)).slice(-20_000)));
|
||||
const harness: Harness = { home, workDir, port, supervisor, output: () => output };
|
||||
harnesses.push(harness);
|
||||
await waitFor(async () => {
|
||||
const instance = await readDaemonInstance(home);
|
||||
return instance?.pid === supervisor.pid && instance.listen ? instance : null;
|
||||
}, `supervisor did not become ready:\n${output}`);
|
||||
await waitFor(
|
||||
async () => {
|
||||
const instance = await readDaemonInstance(home);
|
||||
return instance?.pid === supervisor.pid && instance.listen ? instance : null;
|
||||
},
|
||||
() =>
|
||||
`supervisor did not become ready (exit=${supervisor.exitCode}, signal=${supervisor.signalCode}):\n${output}`,
|
||||
);
|
||||
return harness;
|
||||
}
|
||||
|
||||
|
||||
@@ -5230,7 +5230,11 @@ export class Session {
|
||||
|
||||
try {
|
||||
await cancelAgentRunCommand(
|
||||
{ agentManager: this.agentManager, logger: this.sessionLogger },
|
||||
{
|
||||
agentManager: this.agentManager,
|
||||
agentStorage: this.agentStorage,
|
||||
logger: this.sessionLogger,
|
||||
},
|
||||
agentId,
|
||||
);
|
||||
if (requestId) {
|
||||
|
||||
Reference in New Issue
Block a user