Merge branch 'archive-ui-label-hard-cut-impl'
# Conflicts: # packages/app/src/screens/agent/agent-ready-screen.tsx
This commit is contained in:
@@ -21,7 +21,7 @@ export default function HostIndexRoute() {
|
||||
|
||||
const visibleAgents = sessionAgents
|
||||
? Array.from(sessionAgents.values()).filter(
|
||||
(agent) => !agent.archivedAt && agent.labels.ui === "true"
|
||||
(agent) => !agent.archivedAt
|
||||
)
|
||||
: [];
|
||||
visibleAgents.sort(
|
||||
|
||||
@@ -1550,7 +1550,6 @@ function SessionProviderInternal({
|
||||
}
|
||||
return client.createAgent({
|
||||
config,
|
||||
labels: { ui: "true" },
|
||||
...(trimmedPrompt ? { initialPrompt: trimmedPrompt } : {}),
|
||||
...(imagesData && imagesData.length > 0 ? { images: imagesData } : {}),
|
||||
...(git ? { git } : {}),
|
||||
|
||||
@@ -74,7 +74,7 @@ export function useAllAgentsList(options?: {
|
||||
serverId,
|
||||
serverLabel,
|
||||
});
|
||||
if (aggregated.labels.ui !== "true") {
|
||||
if (aggregated.archivedAt) {
|
||||
continue;
|
||||
}
|
||||
list.push(aggregated);
|
||||
|
||||
@@ -743,7 +743,7 @@ describe("HostRuntimeStore", () => {
|
||||
|
||||
expect(fakeClient.fetchAgentsCalls).toHaveLength(1);
|
||||
expect(fakeClient.fetchAgentsCalls[0]).toEqual({
|
||||
filter: { labels: { ui: "true" } },
|
||||
filter: { includeArchived: true },
|
||||
subscribe: { subscriptionId: "app:srv_test" },
|
||||
page: { limit: 200 },
|
||||
});
|
||||
|
||||
@@ -1113,7 +1113,7 @@ export class HostRuntimeStore {
|
||||
controller.markAgentDirectorySyncLoading();
|
||||
try {
|
||||
const payload = await client.fetchAgents({
|
||||
filter: input.filter ?? { labels: { ui: "true" }, includeArchived: true },
|
||||
filter: input.filter ?? { includeArchived: true },
|
||||
...(input.subscribe ? { subscribe: input.subscribe } : {}),
|
||||
...(input.page ? { page: input.page } : {}),
|
||||
});
|
||||
|
||||
@@ -1017,7 +1017,6 @@ function DraftAgentScreenContent({
|
||||
const imagesData = await encodeImages(images)
|
||||
const result = await createAgentClient.createAgent({
|
||||
config,
|
||||
labels: { ui: 'true' },
|
||||
initialPrompt: trimmedPrompt,
|
||||
clientMessageId: attempt.clientMessageId,
|
||||
...(imagesData && imagesData.length > 0 ? { images: imagesData } : {}),
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { Agent } from "@/stores/session-store";
|
||||
import {
|
||||
canOpenAgentTabFromRoute,
|
||||
deriveWorkspaceAgentVisibility,
|
||||
} from "@/screens/workspace/workspace-agent-visibility";
|
||||
|
||||
function makeAgent(input: {
|
||||
id: string;
|
||||
cwd: string;
|
||||
archivedAt?: Date | null;
|
||||
createdAt?: Date;
|
||||
lastActivityAt?: Date;
|
||||
}): Agent {
|
||||
const createdAt = input.createdAt ?? new Date("2026-03-04T00:00:00.000Z");
|
||||
const lastActivityAt = input.lastActivityAt ?? createdAt;
|
||||
return {
|
||||
serverId: "srv",
|
||||
id: input.id,
|
||||
provider: "codex",
|
||||
status: "idle",
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
lastUserMessageAt: null,
|
||||
lastActivityAt,
|
||||
capabilities: {
|
||||
supportsStreaming: true,
|
||||
supportsSessionPersistence: true,
|
||||
supportsDynamicModes: true,
|
||||
supportsMcpServers: true,
|
||||
supportsReasoningStream: true,
|
||||
supportsToolInvocations: true,
|
||||
},
|
||||
currentModeId: null,
|
||||
availableModes: [],
|
||||
pendingPermissions: [],
|
||||
persistence: null,
|
||||
runtimeInfo: {
|
||||
provider: "codex",
|
||||
sessionId: null,
|
||||
},
|
||||
title: null,
|
||||
cwd: input.cwd,
|
||||
model: null,
|
||||
thinkingOptionId: null,
|
||||
labels: {},
|
||||
requiresAttention: false,
|
||||
attentionReason: null,
|
||||
attentionTimestamp: null,
|
||||
archivedAt: input.archivedAt ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
describe("workspace agent visibility", () => {
|
||||
it("keeps archived agents hidden from visible list but present in workspace lookup", () => {
|
||||
const workspaceId = "/repo/worktree";
|
||||
const visible = makeAgent({
|
||||
id: "visible-agent",
|
||||
cwd: workspaceId,
|
||||
createdAt: new Date("2026-03-04T00:00:00.000Z"),
|
||||
});
|
||||
const archived = makeAgent({
|
||||
id: "archived-agent",
|
||||
cwd: workspaceId,
|
||||
archivedAt: new Date("2026-03-04T00:01:00.000Z"),
|
||||
createdAt: new Date("2026-03-04T00:01:00.000Z"),
|
||||
});
|
||||
const otherWorkspace = makeAgent({
|
||||
id: "other-workspace-agent",
|
||||
cwd: "/repo/other",
|
||||
});
|
||||
|
||||
const sessionAgents = new Map<string, Agent>([
|
||||
[visible.id, visible],
|
||||
[archived.id, archived],
|
||||
[otherWorkspace.id, otherWorkspace],
|
||||
]);
|
||||
|
||||
const result = deriveWorkspaceAgentVisibility({
|
||||
sessionAgents,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(result.visibleAgents.map((agent) => agent.id)).toEqual(["visible-agent"]);
|
||||
expect(result.lookupById.has("visible-agent")).toBe(true);
|
||||
expect(result.lookupById.has("archived-agent")).toBe(true);
|
||||
expect(result.lookupById.has("other-workspace-agent")).toBe(false);
|
||||
});
|
||||
|
||||
it("allows explicit route open for archived agent once agents are hydrated", () => {
|
||||
const archivedAgent = makeAgent({
|
||||
id: "archived-agent",
|
||||
cwd: "/repo/worktree",
|
||||
archivedAt: new Date("2026-03-04T00:01:00.000Z"),
|
||||
});
|
||||
const lookup = new Map<string, Agent>([[archivedAgent.id, archivedAgent]]);
|
||||
|
||||
expect(
|
||||
canOpenAgentTabFromRoute({
|
||||
agentId: "archived-agent",
|
||||
agentsHydrated: true,
|
||||
workspaceAgentLookup: lookup,
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("sorts same-createdAt agents by lastActivityAt descending", () => {
|
||||
const workspaceId = "/repo/worktree";
|
||||
const createdAt = new Date("2026-03-04T00:00:00.000Z");
|
||||
const newerActivity = makeAgent({
|
||||
id: "newer-activity",
|
||||
cwd: workspaceId,
|
||||
createdAt,
|
||||
lastActivityAt: new Date("2026-03-04T00:05:00.000Z"),
|
||||
});
|
||||
const olderActivity = makeAgent({
|
||||
id: "older-activity",
|
||||
cwd: workspaceId,
|
||||
createdAt,
|
||||
lastActivityAt: new Date("2026-03-04T00:01:00.000Z"),
|
||||
});
|
||||
const sessionAgents = new Map<string, Agent>([
|
||||
[olderActivity.id, olderActivity],
|
||||
[newerActivity.id, newerActivity],
|
||||
]);
|
||||
|
||||
const result = deriveWorkspaceAgentVisibility({
|
||||
sessionAgents,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(result.visibleAgents.map((agent) => agent.id)).toEqual([
|
||||
"newer-activity",
|
||||
"older-activity",
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { Agent } from "@/stores/session-store";
|
||||
|
||||
function sortAgentsByCreatedAtDescending(agents: Agent[]): Agent[] {
|
||||
const sorted = [...agents];
|
||||
sorted.sort((left, right) => {
|
||||
const createdAtDelta = right.createdAt.getTime() - left.createdAt.getTime();
|
||||
if (createdAtDelta !== 0) {
|
||||
return createdAtDelta;
|
||||
}
|
||||
return right.lastActivityAt.getTime() - left.lastActivityAt.getTime();
|
||||
});
|
||||
return sorted;
|
||||
}
|
||||
|
||||
function trimNonEmpty(value: string | null | undefined): string | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : null;
|
||||
}
|
||||
|
||||
export function deriveWorkspaceAgentVisibility(input: {
|
||||
sessionAgents: Map<string, Agent> | undefined;
|
||||
workspaceId: string;
|
||||
}): {
|
||||
visibleAgents: Agent[];
|
||||
lookupById: Map<string, Agent>;
|
||||
} {
|
||||
const { sessionAgents, workspaceId } = input;
|
||||
if (!sessionAgents || !workspaceId) {
|
||||
return {
|
||||
visibleAgents: [],
|
||||
lookupById: new Map<string, Agent>(),
|
||||
};
|
||||
}
|
||||
|
||||
const lookupById = new Map<string, Agent>();
|
||||
const visible: Agent[] = [];
|
||||
for (const agent of sessionAgents.values()) {
|
||||
if ((trimNonEmpty(agent.cwd) ?? "") !== workspaceId) {
|
||||
continue;
|
||||
}
|
||||
lookupById.set(agent.id, agent);
|
||||
if (!agent.archivedAt) {
|
||||
visible.push(agent);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
visibleAgents: sortAgentsByCreatedAtDescending(visible),
|
||||
lookupById,
|
||||
};
|
||||
}
|
||||
|
||||
export function canOpenAgentTabFromRoute(input: {
|
||||
agentId: string;
|
||||
agentsHydrated: boolean;
|
||||
workspaceAgentLookup: Map<string, Agent>;
|
||||
}): boolean {
|
||||
if (!input.agentId.trim()) {
|
||||
return false;
|
||||
}
|
||||
if (!input.agentsHydrated) {
|
||||
return true;
|
||||
}
|
||||
return input.workspaceAgentLookup.has(input.agentId);
|
||||
}
|
||||
@@ -257,7 +257,6 @@ export function WorkspaceDraftAgentTab({
|
||||
const imagesData = await encodeImages(images);
|
||||
const result = await client.createAgent({
|
||||
config,
|
||||
labels: { ui: "true" },
|
||||
initialPrompt: trimmedPrompt,
|
||||
clientMessageId: attempt.clientMessageId,
|
||||
...(imagesData && imagesData.length > 0 ? { images: imagesData } : {}),
|
||||
|
||||
@@ -78,6 +78,10 @@ import {
|
||||
resolveWorkspaceHeader,
|
||||
shouldRenderMissingWorkspaceDescriptor,
|
||||
} from "@/screens/workspace/workspace-header-source";
|
||||
import {
|
||||
canOpenAgentTabFromRoute,
|
||||
deriveWorkspaceAgentVisibility,
|
||||
} from "@/screens/workspace/workspace-agent-visibility";
|
||||
|
||||
const TERMINALS_QUERY_STALE_TIME = 5_000;
|
||||
const NEW_TAB_AGENT_OPTION_ID = "__new_tab_agent__";
|
||||
@@ -196,17 +200,6 @@ function resolveTabAvailability(input: {
|
||||
return input.terminalIds.has(input.tab.terminalId) ? "available" : "invalid";
|
||||
}
|
||||
|
||||
function sortAgentsByCreatedAtDescending(agents: Agent[]): Agent[] {
|
||||
return [...agents].sort((left, right) => {
|
||||
const createdAtDelta =
|
||||
right.createdAt.getTime() - left.createdAt.getTime();
|
||||
if (createdAtDelta !== 0) {
|
||||
return createdAtDelta;
|
||||
}
|
||||
return right.lastActivityAt.getTime() - left.lastActivityAt.getTime();
|
||||
});
|
||||
}
|
||||
|
||||
export function WorkspaceScreen({
|
||||
serverId,
|
||||
workspaceId,
|
||||
@@ -243,24 +236,15 @@ function WorkspaceScreenContent({
|
||||
const sessionAgents = useSessionStore(
|
||||
(state) => state.sessions[normalizedServerId]?.agents
|
||||
);
|
||||
const workspaceAgents = useMemo(() => {
|
||||
if (!sessionAgents || !normalizedWorkspaceId) {
|
||||
return [] as Agent[];
|
||||
}
|
||||
|
||||
const collected: Agent[] = [];
|
||||
for (const agent of sessionAgents.values()) {
|
||||
if (agent.archivedAt) {
|
||||
continue;
|
||||
}
|
||||
if ((trimNonEmpty(agent.cwd) ?? "") !== normalizedWorkspaceId) {
|
||||
continue;
|
||||
}
|
||||
collected.push(agent);
|
||||
}
|
||||
|
||||
return sortAgentsByCreatedAtDescending(collected);
|
||||
}, [normalizedWorkspaceId, sessionAgents]);
|
||||
const workspaceAgentVisibility = useMemo(
|
||||
() =>
|
||||
deriveWorkspaceAgentVisibility({
|
||||
sessionAgents,
|
||||
workspaceId: normalizedWorkspaceId,
|
||||
}),
|
||||
[normalizedWorkspaceId, sessionAgents]
|
||||
);
|
||||
const workspaceAgents = workspaceAgentVisibility.visibleAgents;
|
||||
|
||||
const terminalsQueryKey = useMemo(
|
||||
() => ["terminals", normalizedServerId, normalizedWorkspaceId] as const,
|
||||
@@ -487,13 +471,7 @@ function WorkspaceScreenContent({
|
||||
return () => handler.remove();
|
||||
}, [closeToAgent, isExplorerOpen]);
|
||||
|
||||
const agentsById = useMemo(() => {
|
||||
const map = new Map<string, Agent>();
|
||||
for (const agent of workspaceAgents) {
|
||||
map.set(agent.id, agent);
|
||||
}
|
||||
return map;
|
||||
}, [workspaceAgents]);
|
||||
const agentsById = workspaceAgentVisibility.lookupById;
|
||||
|
||||
const terminalIds = useMemo(() => {
|
||||
const set = new Set<string>();
|
||||
@@ -580,7 +558,13 @@ function WorkspaceScreenContent({
|
||||
if (normalized.startsWith("agent_")) {
|
||||
const agentId = normalized.slice("agent_".length).trim();
|
||||
if (agentId) {
|
||||
if (areWorkspaceAgentsHydrated && !agentsById.has(agentId)) {
|
||||
if (
|
||||
!canOpenAgentTabFromRoute({
|
||||
agentId,
|
||||
agentsHydrated: areWorkspaceAgentsHydrated,
|
||||
workspaceAgentLookup: agentsById,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const tabId = openOrFocusTab({
|
||||
|
||||
Reference in New Issue
Block a user