agent-orchestrator/packages/cli/src/lib/session-utils.ts

84 lines
2.8 KiB
TypeScript

import type { OrchestratorConfig } from "@aoagents/ao-core";
export function escapeRegex(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
/**
* Strip optional 12-char hex hash prefix from a tmux session name.
* "1686e4aaaeaa-ao-145" → "ao-145"
* "ao-145" → "ao-145" (no-op if no hash prefix)
*/
export function stripHashPrefix(name: string): string {
const match = name.match(/^[a-f0-9]{12}-(.+)$/);
return match ? match[1] : name;
}
/** Check whether a session name matches a project prefix (strict: prefix-\d+ only). */
export function matchesPrefix(sessionName: string, prefix: string): boolean {
const normalizedName = stripHashPrefix(sessionName);
return new RegExp(`^${escapeRegex(prefix)}-\\d+$`).test(normalizedName);
}
/** Find which project a session belongs to by matching its name against session prefixes. */
export function findProjectForSession(
config: OrchestratorConfig,
sessionName: string,
): string | null {
for (const [id, project] of Object.entries(config.projects) as Array<
[string, OrchestratorConfig["projects"][string]]
>) {
const prefix = project.sessionPrefix || id;
if (matchesPrefix(sessionName, prefix) || isOrchestratorSessionName(config, sessionName, id)) {
return id;
}
}
return null;
}
export function isOrchestratorSessionName(
config: OrchestratorConfig,
sessionName: string,
projectId?: string,
): boolean {
const normalizedName = stripHashPrefix(sessionName);
// If sessionName is a numbered worker for any configured project, it is not an orchestrator.
// This guard runs first to prevent cross-project false positives: e.g. prefix "app" would
// match "app-orchestrator-1" as an orchestrator pattern, but if another project has prefix
// "app-orchestrator" then "app-orchestrator-1" is a worker, not an orchestrator.
for (const [id, project] of Object.entries(config.projects) as Array<
[string, OrchestratorConfig["projects"][string]]
>) {
const prefix = project.sessionPrefix || id;
if (matchesPrefix(normalizedName, prefix)) return false;
}
if (projectId) {
const project = config.projects[projectId];
if (project) {
const prefix = project.sessionPrefix || projectId;
if (
normalizedName === `${prefix}-orchestrator` ||
new RegExp(`^${escapeRegex(prefix)}-orchestrator-\\d+$`).test(normalizedName)
) {
return true;
}
}
}
for (const [id, project] of Object.entries(config.projects) as Array<
[string, OrchestratorConfig["projects"][string]]
>) {
const prefix = project.sessionPrefix || id;
if (
normalizedName === `${prefix}-orchestrator` ||
new RegExp(`^${escapeRegex(prefix)}-orchestrator-\\d+$`).test(normalizedName)
) {
return true;
}
}
return normalizedName.endsWith("-orchestrator");
}