Ran pnpm lint:fix to resolve 220 auto-fixable lint issues. All changes are import/export reordering — no logic changes.
145 lines
5 KiB
TypeScript
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);
|
|
}
|