agent-ecosystem/src/main/ipc/crossTeam.ts
iliya 51f8f3545c style: auto-fix import/export sorting and formatting
Ran pnpm lint:fix to resolve 220 auto-fixable lint issues.
All changes are import/export reordering — no logic changes.
2026-03-16 20:48:42 +02:00

145 lines
5 KiB
TypeScript

import {
CROSS_TEAM_GET_OUTBOX,
CROSS_TEAM_LIST_TARGETS,
CROSS_TEAM_SEND,
// eslint-disable-next-line boundaries/element-types -- IPC channel constants are shared between main and preload by design
} from '@preload/constants/ipcChannels';
import { createLogger } from '@shared/utils/logger';
import { isAgentActionMode } from '../services/team/actionModeInstructions';
import { validateTaskId, validateTeamName } from './guards';
import type { CrossTeamService } from '../services/team/CrossTeamService';
import type { IpcResult, TaskRef } from '@shared/types';
import type { IpcMain, IpcMainInvokeEvent } from 'electron';
const logger = createLogger('IPC:crossTeam');
let crossTeamService: CrossTeamService | null = null;
export function initializeCrossTeamHandlers(service: CrossTeamService): void {
crossTeamService = service;
}
function validateTaskRefs(
value: unknown
): { valid: true; value: TaskRef[] | undefined } | { valid: false; error: string } {
if (value === undefined) {
return { valid: true, value: undefined };
}
if (!Array.isArray(value)) {
return { valid: false, error: 'taskRefs must be an array' };
}
const taskRefs: TaskRef[] = [];
for (const entry of value) {
if (!entry || typeof entry !== 'object') {
return { valid: false, error: 'taskRefs entries must be objects' };
}
const row = entry as Partial<TaskRef>;
const taskId = typeof row.taskId === 'string' ? row.taskId.trim() : '';
const displayId = typeof row.displayId === 'string' ? row.displayId.trim() : '';
const teamName = typeof row.teamName === 'string' ? row.teamName.trim() : '';
if (!taskId || !displayId || !teamName) {
return { valid: false, error: 'Each taskRef must include taskId, displayId, and teamName' };
}
const vTaskId = validateTaskId(taskId);
if (!vTaskId.valid) {
return { valid: false, error: vTaskId.error ?? 'Invalid taskRef taskId' };
}
const vTeamName = validateTeamName(teamName);
if (!vTeamName.valid) {
return { valid: false, error: vTeamName.error ?? 'Invalid taskRef teamName' };
}
taskRefs.push({ taskId: vTaskId.value!, displayId, teamName: vTeamName.value! });
}
return { valid: true, value: taskRefs };
}
function getService(): CrossTeamService {
if (!crossTeamService) {
throw new Error('CrossTeamService not initialized');
}
return crossTeamService;
}
async function wrapCrossTeamHandler<T>(
operation: string,
handler: () => Promise<T>
): Promise<IpcResult<T>> {
try {
const data = await handler();
return { success: true, data };
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error(`[crossTeam:${operation}] ${message}`);
return { success: false, error: message };
}
}
async function handleSend(
_event: IpcMainInvokeEvent,
request: unknown
): Promise<IpcResult<unknown>> {
return wrapCrossTeamHandler('send', () => {
if (!request || typeof request !== 'object') {
throw new Error('Invalid request');
}
const req = request as Record<string, unknown>;
if (req.actionMode !== undefined && !isAgentActionMode(req.actionMode)) {
throw new Error('actionMode must be one of: do, ask, delegate');
}
const taskRefs = validateTaskRefs(req.taskRefs);
if (!taskRefs.valid) {
throw new Error(taskRefs.error);
}
return getService().send({
fromTeam: String(req.fromTeam ?? ''),
fromMember: String(req.fromMember ?? ''),
toTeam: String(req.toTeam ?? ''),
conversationId: typeof req.conversationId === 'string' ? req.conversationId : undefined,
replyToConversationId:
typeof req.replyToConversationId === 'string' ? req.replyToConversationId : undefined,
text: String(req.text ?? ''),
taskRefs: taskRefs.value,
actionMode: isAgentActionMode(req.actionMode) ? req.actionMode : undefined,
summary: typeof req.summary === 'string' ? req.summary : undefined,
chainDepth: typeof req.chainDepth === 'number' ? req.chainDepth : undefined,
});
});
}
async function handleListTargets(
_event: IpcMainInvokeEvent,
excludeTeam?: string
): Promise<IpcResult<unknown>> {
return wrapCrossTeamHandler('listTargets', () =>
getService().listAvailableTargets(typeof excludeTeam === 'string' ? excludeTeam : undefined)
);
}
async function handleGetOutbox(
_event: IpcMainInvokeEvent,
teamName: string
): Promise<IpcResult<unknown>> {
return wrapCrossTeamHandler('getOutbox', () => {
if (typeof teamName !== 'string' || !teamName.trim()) {
throw new Error('teamName is required');
}
return getService().getOutbox(teamName);
});
}
export function registerCrossTeamHandlers(ipcMain: IpcMain): void {
ipcMain.handle(CROSS_TEAM_SEND, handleSend);
ipcMain.handle(CROSS_TEAM_LIST_TARGETS, handleListTargets);
ipcMain.handle(CROSS_TEAM_GET_OUTBOX, handleGetOutbox);
}
export function removeCrossTeamHandlers(ipcMain: IpcMain): void {
ipcMain.removeHandler(CROSS_TEAM_SEND);
ipcMain.removeHandler(CROSS_TEAM_LIST_TARGETS);
ipcMain.removeHandler(CROSS_TEAM_GET_OUTBOX);
}