From e2b43ec0b27a584fecab2b8db1973becbea06ebc Mon Sep 17 00:00:00 2001 From: harshitsinghbhandari <24b4506@iitb.ac.in> Date: Sun, 5 Apr 2026 18:41:46 +0530 Subject: [PATCH] fix comments --- packages/cli/src/commands/start.ts | 12 +++++++++--- packages/core/src/config.ts | 5 ++++- packages/web/src/app/api/orchestrators/route.ts | 13 ++++++++----- packages/web/src/app/orchestrators/page.tsx | 6 +++++- .../web/src/components/OrchestratorSelector.tsx | 8 +++++++- packages/web/src/lib/orchestrator-utils.ts | 3 ++- 6 files changed, 35 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 10039862c..e104172b4 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -29,6 +29,7 @@ import { configToYaml, normalizeOrchestratorSessionStrategy, isOrchestratorSession, + isTerminalSession, ConfigNotFoundError, type OrchestratorConfig, type ProjectConfig, @@ -1010,8 +1011,13 @@ async function runStartup( { cause: err }, ); } - const existingOrchestrators = allSessions.filter((s) => - isOrchestratorSession(s, project.sessionPrefix ?? projectId), + const allSessionPrefixes = Object.entries(config.projects).map( + ([, p]) => p.sessionPrefix ?? generateSessionPrefix(p.name ?? ""), + ); + const existingOrchestrators = allSessions.filter( + (s) => + isOrchestratorSession(s, project.sessionPrefix ?? projectId, allSessionPrefixes) && + !isTerminalSession(s), ); if (existingOrchestrators.length > 0) { @@ -1019,7 +1025,7 @@ async function runStartup( if (opts?.dashboard === false) { // No dashboard — auto-select the most recently active orchestrator const sortedOrchestrators = [...existingOrchestrators].sort( - (a, b) => b.lastActivityAt.getTime() - a.lastActivityAt.getTime(), + (a, b) => (b.lastActivityAt?.getTime() ?? 0) - (a.lastActivityAt?.getTime() ?? 0), ); const selected = sortedOrchestrators[0]; selectedOrchestratorId = selected.id; diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 0da3db105..bc21c290c 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -250,7 +250,10 @@ const OrchestratorConfigSchema = z.object({ readyThresholdMs: z.number().nonnegative().default(300_000), defaults: DefaultPluginsSchema.default({}), plugins: z.array(InstalledPluginConfigSchema).default([]), - projects: z.record(ProjectConfigSchema), + projects: z.record( + z.string().regex(/^[a-zA-Z0-9_-]+$/, "Project ID must match [a-zA-Z0-9_-]+ (no dots, slashes, or special characters)"), + ProjectConfigSchema, + ), notifiers: z.record(NotifierConfigSchema).default({}), notificationRouting: z.record(z.array(z.string())).default({ urgent: ["desktop", "composio"], diff --git a/packages/web/src/app/api/orchestrators/route.ts b/packages/web/src/app/api/orchestrators/route.ts index ffbf209e6..d3c0b2359 100644 --- a/packages/web/src/app/api/orchestrators/route.ts +++ b/packages/web/src/app/api/orchestrators/route.ts @@ -1,5 +1,5 @@ import { type NextRequest, NextResponse } from "next/server"; -import { generateOrchestratorPrompt } from "@composio/ao-core"; +import { generateOrchestratorPrompt, generateSessionPrefix } from "@composio/ao-core"; import { getServices } from "@/lib/services"; import { validateIdentifier, validateConfiguredProject } from "@/lib/validation"; import { mapSessionsToOrchestrators } from "@/lib/orchestrator-utils"; @@ -30,7 +30,10 @@ export async function GET(request: NextRequest) { const sessionPrefix = project.sessionPrefix ?? projectId; const allSessions = await sessionManager.list(projectId); - const orchestrators = mapSessionsToOrchestrators(allSessions, sessionPrefix, project.name); + const allSessionPrefixes = Object.entries(config.projects).map( + ([, p]) => p.sessionPrefix ?? generateSessionPrefix(p.name ?? ""), + ); + const orchestrators = mapSessionsToOrchestrators(allSessions, sessionPrefix, project.name, allSessionPrefixes); return NextResponse.json({ orchestrators, projectName: project.name }); } catch (err) { @@ -55,9 +58,9 @@ export async function POST(request: NextRequest) { try { const { config, sessionManager } = await getServices(); const projectId = body.projectId as string; - const projectErr = validateConfiguredProject(config.projects, projectId); - if (projectErr) { - return NextResponse.json({ error: projectErr }, { status: 404 }); + const configProjectErr = validateConfiguredProject(config.projects, projectId); + if (configProjectErr) { + return NextResponse.json({ error: configProjectErr }, { status: 404 }); } const project = config.projects[projectId]; diff --git a/packages/web/src/app/orchestrators/page.tsx b/packages/web/src/app/orchestrators/page.tsx index e4b5906c8..ba8b47d3f 100644 --- a/packages/web/src/app/orchestrators/page.tsx +++ b/packages/web/src/app/orchestrators/page.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { OrchestratorSelector, type Orchestrator } from "@/components/OrchestratorSelector"; import { getServices } from "@/lib/services"; import { getAllProjects } from "@/lib/project-name"; +import { generateSessionPrefix } from "@composio/ao-core"; import { mapSessionsToOrchestrators } from "@/lib/orchestrator-utils"; export const dynamic = "force-dynamic"; @@ -57,7 +58,10 @@ export default async function OrchestratorsRoute(props: { projectName = project.name; const sessionPrefix = project.sessionPrefix ?? projectId; const allSessions = await sessionManager.list(projectId); - orchestrators = mapSessionsToOrchestrators(allSessions, sessionPrefix, project.name); + const allSessionPrefixes = Object.entries(config.projects).map( + ([, p]) => p.sessionPrefix ?? generateSessionPrefix(p.name ?? ""), + ); + orchestrators = mapSessionsToOrchestrators(allSessions, sessionPrefix, project.name, allSessionPrefixes); } } catch (err) { error = err instanceof Error ? err.message : "Failed to load orchestrators"; diff --git a/packages/web/src/components/OrchestratorSelector.tsx b/packages/web/src/components/OrchestratorSelector.tsx index 1af10103b..c80fa015b 100644 --- a/packages/web/src/components/OrchestratorSelector.tsx +++ b/packages/web/src/components/OrchestratorSelector.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, useRef } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { cn } from "@/lib/cn"; @@ -95,8 +95,13 @@ export function OrchestratorSelector({ const router = useRouter(); const [isSpawning, setIsSpawning] = useState(false); const [spawnError, setSpawnError] = useState(null); + const spawnLockRef = useRef(false); const handleSpawnNew = async () => { + // Synchronous re-entrancy guard: React state updates are async, + // so two clicks before rerender would fire two POSTs without this. + if (spawnLockRef.current) return; + spawnLockRef.current = true; setIsSpawning(true); setSpawnError(null); @@ -118,6 +123,7 @@ export function OrchestratorSelector({ setSpawnError(err instanceof Error ? err.message : "Failed to spawn orchestrator"); } finally { setIsSpawning(false); + spawnLockRef.current = false; } }; diff --git a/packages/web/src/lib/orchestrator-utils.ts b/packages/web/src/lib/orchestrator-utils.ts index 7157c6d8b..ff2b71839 100644 --- a/packages/web/src/lib/orchestrator-utils.ts +++ b/packages/web/src/lib/orchestrator-utils.ts @@ -10,9 +10,10 @@ export function mapSessionsToOrchestrators( sessions: Session[], sessionPrefix: string, projectName: string, + allSessionPrefixes?: string[], ): Orchestrator[] { return sessions - .filter((s) => isOrchestratorSession(s, sessionPrefix)) + .filter((s) => isOrchestratorSession(s, sessionPrefix, allSessionPrefixes)) .map((s) => ({ id: s.id, projectId: s.projectId,