mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
fix(cli): report an unknown agent ID as AGENT_NOT_FOUND (#6053)
* fix(server): report an unknown agent as not found to CLI commands fetch_agent_request answered an unknown agent ID with an error string, so DaemonClient.fetchAgent threw instead of returning null. Every CLI command that looks up one agent checks for null to report AGENT_NOT_FOUND, so delete, stop, inspect, logs and the rest reported their generic failure code and skipped the "paseo ls" hint. The daemon now answers an unknown agent with a null agent and no error. An empty or ambiguous identifier is still an error. * test(cli): expect the not-found message agent logs now prints * test(cli): move the unknown agent ID cases into the delete and stop suites --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -146,7 +146,7 @@ test("managed two-home restart retains its supervisor and never routes ordinary
|
||||
expect(await f.ok(["project", "ls", "--home", a])).toEqual([]);
|
||||
expect((await f.ok(["project", "ls", "--home", b], poisoned)).length).toBe(1);
|
||||
const stream = await f.run(["logs", "missing-agent", "--follow", "--home", b], poisoned);
|
||||
expect(stream.stderr).toContain("Agent not found: missing-agent");
|
||||
expect(stream.stderr).toContain("No agent found matching: missing-agent");
|
||||
const restart = await f.ok(["restart", "--home", b, "--timeout", "30"], poisoned);
|
||||
expect(restart.supervisorPid).toBe(launchB.pid);
|
||||
expect(restart.workerPid).not.toBe(beforeB.workerPid);
|
||||
@@ -503,7 +503,7 @@ test("raw log following subscribes to a stored agent that exists only in B", asy
|
||||
const launchA = await f.ok(["start", "--home", a]);
|
||||
await f.ok(["start", "--home", b]);
|
||||
const absent = await f.run(["logs", agentId, "--home", a]);
|
||||
expect(absent.stderr).toContain("Agent not found");
|
||||
expect(absent.stderr).toContain(`No agent found matching: ${agentId}`);
|
||||
follower = spawn(
|
||||
process.execPath,
|
||||
[cli, "logs", agentId, "--follow", "--tail", "0", "--home", b],
|
||||
@@ -522,7 +522,7 @@ test("raw log following subscribes to a stored agent that exists only in B", asy
|
||||
errors += data;
|
||||
});
|
||||
await expect.poll(() => output, { timeout: 15_000 }).toContain("Following logs (no history;");
|
||||
expect(errors).not.toContain("Agent not found");
|
||||
expect(errors).not.toContain("No agent found matching");
|
||||
} finally {
|
||||
if (follower && follower.exitCode === null) {
|
||||
const exited = new Promise((resolve) => follower!.once("exit", resolve));
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import assert from "node:assert";
|
||||
import { getAvailablePort } from "./helpers/network.ts";
|
||||
import { runPaseoCli, startTestDaemon } from "./helpers/test-daemon.ts";
|
||||
import { $ } from "zx";
|
||||
import { mkdtemp, rm } from "fs/promises";
|
||||
import { tmpdir } from "os";
|
||||
@@ -129,6 +130,28 @@ try {
|
||||
assert(!output.includes("error: option"), "should not have option parsing error");
|
||||
console.log("✓ -q (quiet) flag is accepted with stop\n");
|
||||
}
|
||||
|
||||
{
|
||||
console.log("Test 9: stop reports AGENT_NOT_FOUND for an unknown ID");
|
||||
const daemon = await startTestDaemon();
|
||||
try {
|
||||
const result = await runPaseoCli(daemon, [
|
||||
"agent",
|
||||
"stop",
|
||||
"does-not-exist",
|
||||
"--host",
|
||||
`127.0.0.1:${daemon.port}`,
|
||||
"--json",
|
||||
]);
|
||||
assert.notStrictEqual(result.exitCode, 0, "stop should fail for an unknown ID");
|
||||
const { error } = JSON.parse(result.stderr);
|
||||
assert.strictEqual(error.code, "AGENT_NOT_FOUND", result.stderr);
|
||||
assert.match(error.details, /paseo ls/);
|
||||
} finally {
|
||||
await daemon.stop();
|
||||
}
|
||||
console.log("✓ stop reports AGENT_NOT_FOUND for an unknown ID\n");
|
||||
}
|
||||
} finally {
|
||||
// Clean up temp directory
|
||||
await rm(paseoHome, { recursive: true, force: true });
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import assert from "node:assert";
|
||||
import { runLocalPaseo } from "./helpers/local-cli.ts";
|
||||
import { getAvailablePort } from "./helpers/network.ts";
|
||||
import { runPaseoCli, startTestDaemon } from "./helpers/test-daemon.ts";
|
||||
import { mkdtemp, rm } from "fs/promises";
|
||||
import { tmpdir } from "os";
|
||||
import { join } from "path";
|
||||
@@ -112,6 +113,28 @@ try {
|
||||
assert(!output.includes("error: option"), "should not have option parsing error");
|
||||
console.log("✓ -q (quiet) flag is accepted with delete\n");
|
||||
}
|
||||
|
||||
{
|
||||
console.log("Test 9: delete reports AGENT_NOT_FOUND for an unknown ID");
|
||||
const daemon = await startTestDaemon();
|
||||
try {
|
||||
const result = await runPaseoCli(daemon, [
|
||||
"agent",
|
||||
"delete",
|
||||
"does-not-exist",
|
||||
"--host",
|
||||
`127.0.0.1:${daemon.port}`,
|
||||
"--json",
|
||||
]);
|
||||
assert.notStrictEqual(result.exitCode, 0, "delete should fail for an unknown ID");
|
||||
const { error } = JSON.parse(result.stderr);
|
||||
assert.strictEqual(error.code, "AGENT_NOT_FOUND", result.stderr);
|
||||
assert.match(error.details, /paseo ls/);
|
||||
} finally {
|
||||
await daemon.stop();
|
||||
}
|
||||
console.log("✓ delete reports AGENT_NOT_FOUND for an unknown ID\n");
|
||||
}
|
||||
} finally {
|
||||
await rm(paseoHome, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
@@ -5216,10 +5216,10 @@ export class Session {
|
||||
|
||||
private async resolveAgentIdentifier(
|
||||
identifier: string,
|
||||
): Promise<{ ok: true; agentId: string } | { ok: false; error: string }> {
|
||||
): Promise<{ ok: true; agentId: string } | { ok: false; notFound: boolean; error: string }> {
|
||||
const trimmed = identifier.trim();
|
||||
if (!trimmed) {
|
||||
return { ok: false, error: "Agent identifier cannot be empty" };
|
||||
return { ok: false, notFound: false, error: "Agent identifier cannot be empty" };
|
||||
}
|
||||
|
||||
const stored = await this.agentStorage.list();
|
||||
@@ -5243,6 +5243,7 @@ export class Session {
|
||||
if (prefixMatches.length > 1) {
|
||||
return {
|
||||
ok: false,
|
||||
notFound: false,
|
||||
error: `Agent identifier "${trimmed}" is ambiguous (${prefixMatches
|
||||
.slice(0, 5)
|
||||
.map((id) => id.slice(0, 8))
|
||||
@@ -5257,6 +5258,7 @@ export class Session {
|
||||
if (titleMatches.length > 1) {
|
||||
return {
|
||||
ok: false,
|
||||
notFound: false,
|
||||
error: `Agent title "${trimmed}" is ambiguous (${titleMatches
|
||||
.slice(0, 5)
|
||||
.map((r) => r.id.slice(0, 8))
|
||||
@@ -5264,7 +5266,7 @@ export class Session {
|
||||
};
|
||||
}
|
||||
|
||||
return { ok: false, error: `Agent not found: ${trimmed}` };
|
||||
return { ok: false, notFound: true, error: `Agent not found: ${trimmed}` };
|
||||
}
|
||||
|
||||
private async getAgentPayloadById(agentId: string): Promise<AgentSnapshotPayload | null> {
|
||||
@@ -7574,11 +7576,17 @@ export class Session {
|
||||
}
|
||||
|
||||
private async handleFetchAgent(agentIdOrIdentifier: string, requestId: string): Promise<void> {
|
||||
// An unknown agent is a null agent, not an error. Errors are for empty or ambiguous identifiers.
|
||||
const resolved = await this.resolveAgentIdentifier(agentIdOrIdentifier);
|
||||
if (!resolved.ok) {
|
||||
this.emit({
|
||||
type: "fetch_agent_response",
|
||||
payload: { requestId, agent: null, project: null, error: resolved.error },
|
||||
payload: {
|
||||
requestId,
|
||||
agent: null,
|
||||
project: null,
|
||||
error: resolved.notFound ? null : resolved.error,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -7587,12 +7595,7 @@ export class Session {
|
||||
if (!agent) {
|
||||
this.emit({
|
||||
type: "fetch_agent_response",
|
||||
payload: {
|
||||
requestId,
|
||||
agent: null,
|
||||
project: null,
|
||||
error: `Agent not found: ${resolved.agentId}`,
|
||||
},
|
||||
payload: { requestId, agent: null, project: null, error: null },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3466,7 +3466,7 @@ test("fetch_agent_request still resolves archived historical agents", async () =
|
||||
session.resolveAgentIdentifier = async (identifier: string) =>
|
||||
identifier === "Archived History Agent"
|
||||
? { ok: true, agentId: agent.id }
|
||||
: { ok: false, error: `Agent not found: ${identifier}` };
|
||||
: { ok: false, notFound: true, error: `Agent not found: ${identifier}` };
|
||||
session.getAgentPayloadById = async (agentId: string) => (agentId === agent.id ? agent : null);
|
||||
session.buildProjectPlacementForWorkspaceId = async () => ({
|
||||
projectKey: "proj-history-detail",
|
||||
|
||||
Reference in New Issue
Block a user