fix comments

This commit is contained in:
harshitsinghbhandari 2026-04-05 18:41:46 +05:30
parent 5e19249ec3
commit e2b43ec0b2
6 changed files with 35 additions and 12 deletions

View File

@ -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;

View File

@ -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"],

View File

@ -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];

View File

@ -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";

View File

@ -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<string | null>(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;
}
};

View File

@ -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,