feat: update sidebar agent workflow and host filter UI
This commit is contained in:
@@ -3,7 +3,9 @@ import { describe, expect, it } from "vitest";
|
||||
import type { CheckoutStatusPayload } from "@/hooks/use-checkout-status-query";
|
||||
import {
|
||||
buildNewAgentRoute,
|
||||
parseAgentKey,
|
||||
resolveNewAgentWorkingDir,
|
||||
resolveSelectedAgentForNewAgent,
|
||||
} from "./new-agent-routing";
|
||||
|
||||
describe("buildNewAgentRoute", () => {
|
||||
@@ -35,3 +37,60 @@ describe("resolveNewAgentWorkingDir", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseAgentKey", () => {
|
||||
it("parses server and agent ids from combined key", () => {
|
||||
expect(parseAgentKey("srv-1:agent-9")).toEqual({
|
||||
serverId: "srv-1",
|
||||
agentId: "agent-9",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the last separator to preserve server ids with colons", () => {
|
||||
expect(parseAgentKey("localhost:6767:agent-9")).toEqual({
|
||||
serverId: "localhost:6767",
|
||||
agentId: "agent-9",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns null for malformed keys", () => {
|
||||
expect(parseAgentKey("")).toBeNull();
|
||||
expect(parseAgentKey("only-server")).toBeNull();
|
||||
expect(parseAgentKey(":agent-1")).toBeNull();
|
||||
expect(parseAgentKey("srv-1:")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveSelectedAgentForNewAgent", () => {
|
||||
it("prefers the agent in the current route", () => {
|
||||
expect(
|
||||
resolveSelectedAgentForNewAgent({
|
||||
pathname: "/h/srv-1/agent/agent-2",
|
||||
selectedAgentId: "srv-9:agent-9",
|
||||
})
|
||||
).toEqual({
|
||||
serverId: "srv-1",
|
||||
agentId: "agent-2",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to selected agent key when route has no agent", () => {
|
||||
expect(
|
||||
resolveSelectedAgentForNewAgent({
|
||||
pathname: "/h/srv-1/settings",
|
||||
selectedAgentId: "srv-1:agent-7",
|
||||
})
|
||||
).toEqual({
|
||||
serverId: "srv-1",
|
||||
agentId: "agent-7",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns null when neither route nor selection has an agent", () => {
|
||||
expect(
|
||||
resolveSelectedAgentForNewAgent({
|
||||
pathname: "/h/srv-1/settings",
|
||||
})
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
import type { CheckoutStatusPayload } from "@/hooks/use-checkout-status-query";
|
||||
import { buildHostAgentDraftRoute } from "@/utils/host-routes";
|
||||
import {
|
||||
buildHostAgentDraftRoute,
|
||||
parseHostAgentRouteFromPathname,
|
||||
} from "@/utils/host-routes";
|
||||
|
||||
export function parseAgentKey(
|
||||
key: string | null | undefined
|
||||
): { serverId: string; agentId: string } | null {
|
||||
if (!key) {
|
||||
return null;
|
||||
}
|
||||
const sep = key.lastIndexOf(":");
|
||||
if (sep <= 0 || sep >= key.length - 1) {
|
||||
return null;
|
||||
}
|
||||
const serverId = key.slice(0, sep).trim();
|
||||
const agentId = key.slice(sep + 1).trim();
|
||||
if (!serverId || !agentId) {
|
||||
return null;
|
||||
}
|
||||
return { serverId, agentId };
|
||||
}
|
||||
|
||||
export function resolveSelectedAgentForNewAgent(input: {
|
||||
pathname: string;
|
||||
selectedAgentId?: string;
|
||||
}): { serverId: string; agentId: string } | null {
|
||||
return (
|
||||
parseHostAgentRouteFromPathname(input.pathname) ??
|
||||
parseAgentKey(input.selectedAgentId)
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveNewAgentWorkingDir(
|
||||
cwd: string,
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { AgentLifecycleStatus } from "@server/shared/agent-lifecycle";
|
||||
|
||||
export type SidebarAttentionReason =
|
||||
| "finished"
|
||||
| "error"
|
||||
| "permission"
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
export type SidebarStateBucket =
|
||||
| "needs_input"
|
||||
| "failed"
|
||||
| "running"
|
||||
| "attention"
|
||||
| "done";
|
||||
|
||||
export function deriveSidebarStateBucket(input: {
|
||||
status: AgentLifecycleStatus;
|
||||
requiresAttention?: boolean;
|
||||
attentionReason?: SidebarAttentionReason;
|
||||
}): SidebarStateBucket {
|
||||
if (input.requiresAttention && input.attentionReason === "permission") {
|
||||
return "needs_input";
|
||||
}
|
||||
if (input.status === "error" || input.attentionReason === "error") {
|
||||
return "failed";
|
||||
}
|
||||
if (input.status === "running") {
|
||||
return "running";
|
||||
}
|
||||
if (input.requiresAttention) {
|
||||
// Unread/attention-needed completed agents are active in sidebar logic.
|
||||
return "attention";
|
||||
}
|
||||
return "done";
|
||||
}
|
||||
|
||||
export function isSidebarActiveAgent(input: {
|
||||
status: AgentLifecycleStatus;
|
||||
requiresAttention?: boolean;
|
||||
attentionReason?: SidebarAttentionReason;
|
||||
}): boolean {
|
||||
return deriveSidebarStateBucket(input) !== "done";
|
||||
}
|
||||
Reference in New Issue
Block a user