diff --git a/packages/core/src/session-manager.ts b/packages/core/src/session-manager.ts index ffe624ae5..26adb760c 100644 --- a/packages/core/src/session-manager.ts +++ b/packages/core/src/session-manager.ts @@ -83,6 +83,8 @@ const VALID_STATUSES: ReadonlySet = new Set([ "stuck", "errored", "killed", + "done", + "terminated", ]); /** Validate and normalize a status string. */ diff --git a/packages/integration-tests/src/tracker-linear.integration.test.ts b/packages/integration-tests/src/tracker-linear.integration.test.ts index 62cdc1314..a0aa22a07 100644 --- a/packages/integration-tests/src/tracker-linear.integration.test.ts +++ b/packages/integration-tests/src/tracker-linear.integration.test.ts @@ -231,7 +231,7 @@ describe.skipIf(!canRun)("tracker-linear (integration)", () => { project, ); - const found = issues.find((i) => i.id === issueIdentifier); + const found = issues.find((i: { id: string }) => i.id === issueIdentifier); expect(found).toBeDefined(); expect(found!.title).toContain("[AO Integration Test]"); }); diff --git a/packages/integration-tests/src/workspace-clone.integration.test.ts b/packages/integration-tests/src/workspace-clone.integration.test.ts index d5249c291..4cd6fccee 100644 --- a/packages/integration-tests/src/workspace-clone.integration.test.ts +++ b/packages/integration-tests/src/workspace-clone.integration.test.ts @@ -95,7 +95,7 @@ describe("workspace-clone (integration)", () => { it("lists the clone", async () => { const list = await workspace.list("inttest"); expect(list.length).toBeGreaterThanOrEqual(1); - const found = list.find((w) => w.sessionId === "session-1"); + const found = list.find((w: { sessionId: string }) => w.sessionId === "session-1"); expect(found).toBeDefined(); expect(found!.branch).toBe("feat/test-branch"); }); @@ -119,7 +119,7 @@ describe("workspace-clone (integration)", () => { it("list returns empty after destroy", async () => { const list = await workspace.list("inttest"); - const found = list.find((w) => w.sessionId === "session-1"); + const found = list.find((w: { sessionId: string }) => w.sessionId === "session-1"); expect(found).toBeUndefined(); }); }); diff --git a/packages/integration-tests/src/workspace-worktree.integration.test.ts b/packages/integration-tests/src/workspace-worktree.integration.test.ts index cc35f264e..4910e6e05 100644 --- a/packages/integration-tests/src/workspace-worktree.integration.test.ts +++ b/packages/integration-tests/src/workspace-worktree.integration.test.ts @@ -89,7 +89,7 @@ describe("workspace-worktree (integration)", () => { it("lists the worktree", async () => { const list = await workspace.list("inttest"); expect(list.length).toBeGreaterThanOrEqual(1); - const found = list.find((w) => w.sessionId === "session-1"); + const found = list.find((w: { sessionId: string }) => w.sessionId === "session-1"); expect(found).toBeDefined(); expect(found!.branch).toBe("feat/test-branch"); }); @@ -124,7 +124,7 @@ describe("workspace-worktree (integration)", () => { it("list returns empty after destroy", async () => { const list = await workspace.list("inttest"); - const found = list.find((w) => w.sessionId === "session-1"); + const found = list.find((w: { sessionId: string }) => w.sessionId === "session-1"); expect(found).toBeUndefined(); }); }); diff --git a/packages/web/src/app/api/sessions/[id]/restore/route.ts b/packages/web/src/app/api/sessions/[id]/restore/route.ts index c31f999ca..caa0b3fc5 100644 --- a/packages/web/src/app/api/sessions/[id]/restore/route.ts +++ b/packages/web/src/app/api/sessions/[id]/restore/route.ts @@ -4,7 +4,7 @@ import { getServices } from "@/lib/services"; import { sessionToDashboard } from "@/lib/serialize"; /** Terminal states that can be restored */ -const RESTORABLE_STATUSES = new Set(["killed", "cleanup"]); +const RESTORABLE_STATUSES = new Set(["killed", "cleanup", "terminated", "done"]); const RESTORABLE_ACTIVITIES = new Set(["exited"]); /** Statuses that must never be restored (e.g. already merged) */ diff --git a/packages/web/src/app/api/sessions/route.ts b/packages/web/src/app/api/sessions/route.ts index b717cdef2..9e18c10f9 100644 --- a/packages/web/src/app/api/sessions/route.ts +++ b/packages/web/src/app/api/sessions/route.ts @@ -1,4 +1,4 @@ -import type { Session, ProjectConfig } from "@agent-orchestrator/core"; +import type { Session, ProjectConfig } from "@composio/ao-core"; import { NextResponse } from "next/server"; import { getServices, getSCM, getTracker } from "@/lib/services"; import { sessionToDashboard, enrichSessionPR, enrichSessionIssue, computeStats } from "@/lib/serialize"; @@ -29,8 +29,8 @@ export async function GET() { const { config, registry, sessionManager } = await getServices(); const coreSessions = await sessionManager.list(); - // Filter out special orchestrator session - it's not a worker session - const workerSessions = coreSessions.filter((s) => s.id !== "orchestrator"); + // Filter out orchestrator sessions — they get their own button, not a card + const workerSessions = coreSessions.filter((s) => !s.id.endsWith("-orchestrator")); const dashboardSessions = workerSessions.map(sessionToDashboard); // Enrich issue labels using tracker plugin (synchronous) diff --git a/packages/web/src/app/page.tsx b/packages/web/src/app/page.tsx index 90eedbf31..488ffabe6 100644 --- a/packages/web/src/app/page.tsx +++ b/packages/web/src/app/page.tsx @@ -8,9 +8,14 @@ export const dynamic = "force-dynamic"; export default async function Home() { let sessions: DashboardSession[] = []; + let orchestratorId: string | null = null; try { const { config, registry, sessionManager } = await getServices(); - const coreSessions = await sessionManager.list(); + const allSessions = await sessionManager.list(); + // Find and filter out orchestrator sessions — they get their own button, not a card + const orchSession = allSessions.find((s) => s.id.endsWith("-orchestrator")); + if (orchSession) orchestratorId = orchSession.id; + const coreSessions = allSessions.filter((s) => !s.id.endsWith("-orchestrator")); sessions = coreSessions.map(sessionToDashboard); // Enrich issue labels using tracker plugin (synchronous) @@ -97,5 +102,5 @@ export default async function Home() { // Config not found or services unavailable — show empty dashboard } - return ; + return ; } diff --git a/packages/web/src/components/Dashboard.tsx b/packages/web/src/components/Dashboard.tsx index 04ce18fed..01e618a64 100644 --- a/packages/web/src/components/Dashboard.tsx +++ b/packages/web/src/components/Dashboard.tsx @@ -14,9 +14,10 @@ import { PRTableRow } from "./PRStatus"; interface DashboardProps { sessions: DashboardSession[]; stats: DashboardStats; + orchestratorId?: string | null; } -export function Dashboard({ sessions, stats }: DashboardProps) { +export function Dashboard({ sessions, stats, orchestratorId }: DashboardProps) { const grouped = useMemo(() => { const zones: Record = { merge: [], @@ -86,12 +87,14 @@ export function Dashboard({ sessions, stats }: DashboardProps) { Agent Orchestrator
- - orchestrator terminal - + {orchestratorId && ( + + orchestrator terminal + + )}
diff --git a/packages/web/src/components/SessionCard.tsx b/packages/web/src/components/SessionCard.tsx index e71fc53c5..113852218 100644 --- a/packages/web/src/components/SessionCard.tsx +++ b/packages/web/src/components/SessionCard.tsx @@ -53,11 +53,14 @@ export function SessionCard({ session, onSend, onKill, onMerge, onRestore }: Ses const alerts = getAlerts(session); const isReadyToMerge = pr?.mergeability.mergeable && pr.state === "open"; - const isRestorable = - (session.status === "killed" || - session.status === "cleanup" || - session.activity === "exited") && - session.status !== "merged"; + const isTerminal = + session.status === "killed" || + session.status === "cleanup" || + session.status === "terminated" || + session.status === "done" || + session.status === "merged" || + session.activity === "exited"; + const isRestorable = isTerminal && session.status !== "merged"; return (
)} - + {!isTerminal && ( + + )}
{/* Meta row: branch + PR pills */} @@ -269,7 +274,7 @@ export function SessionCard({ session, onSend, onKill, onMerge, onRestore }: Ses restore session )} - {!isRestorable && session.status !== "merged" && ( + {!isTerminal && (