## Summary Wave 11 of package code organization (continues #2328 / wave 10), after syncing main into the wave10 stack. ### Peels | New module | Parent | |---|---| | `app/api/event-source.ts` | resilient `EventSource` reconnect + pagehide cleanup | | `app/api/chat.ts` | chat sessions / rooms / streaming | | `app/api/projects.ts` | multi-project management client | | `app/api/agents.ts` | agent CRUD / soul / memory | | `app/api/workflows.ts` | workflow definition client | | `app/api/scheduling.ts` | automations + routines | | `app/api/ai-text.ts` | text refine / import translate / subtasks | | `app/api/ai-sessions.ts` | AI / planning session client + `startKeepAlive` | | `app/api/research.ts` | research + evals client | | `task-store/branch-and-pr-entities.ts` | rename of `remaining-ops-6` | Public paths stay stable via `legacy.ts` re-exports (`app/api.ts` → `legacy`). ### LOC - `legacy.ts` ~8921 → ~5670 ### Shims - `legacy.ts` → peels above (delete-when: dashboard imports domain modules) - `remaining-ops-6` → `branch-and-pr-entities` (rename complete) ## Test plan - [x] eslint on peeled API modules + legacy - [x] dashboard app typecheck (`app/api/*` clean) - [x] core typecheck - [x] `plugin-setup-api` tests - [ ] CI merge gate <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Expanded dashboard APIs for agents, AI sessions, chat rooms/sessions, research runs, projects, workflows, routines/automations, and scheduling. * Added AI text refinement/translation, planning title regeneration, and streamed subtask breakdown with task creation. * Introduced resilient server-sent-events streaming for chat and other live updates. * Added workflow export/import and workflow design support, plus scripts and workflow configuration controls. * Added research export and ability to attach research outputs to tasks. * **Enhancements** * Missions support clearing an auto-merge override back to project defaults. * Subtasks can now include an optional priority when creating tasks. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
227 lines
7.8 KiB
TypeScript
227 lines
7.8 KiB
TypeScript
/**
|
|
* FNXC:CodeOrganization 2026-07-19-12:00:
|
|
* Research + evals client API peeled from legacy.ts.
|
|
*/
|
|
import type {
|
|
EvalRun,
|
|
EvalTaskResult,
|
|
ResearchRunStatus,
|
|
Task,
|
|
} from "@fusion/core";
|
|
import type {
|
|
ResearchAvailability,
|
|
ResearchRunDetail,
|
|
ResearchRunsResponse,
|
|
ResearchRunResponse,
|
|
ResearchProviderOption,
|
|
} from "../research-types";
|
|
import { api, ApiRequestError } from "./client.js";
|
|
import { withProjectId } from "./health.js";
|
|
|
|
// ── Research API ────────────────────────────────────────────────────────────
|
|
|
|
export interface EvalsListOptions {
|
|
q?: string;
|
|
runId?: string;
|
|
scoreMin?: number;
|
|
scoreMax?: number;
|
|
limit?: number;
|
|
offset?: number;
|
|
}
|
|
|
|
export function listEvals(options: EvalsListOptions = {}, projectId?: string): Promise<{ results: EvalTaskResult[]; count: number }> {
|
|
const params = new URLSearchParams();
|
|
if (options.q) params.set("q", options.q);
|
|
if (options.runId) params.set("runId", options.runId);
|
|
if (options.scoreMin !== undefined) params.set("scoreMin", String(options.scoreMin));
|
|
if (options.scoreMax !== undefined) params.set("scoreMax", String(options.scoreMax));
|
|
if (options.limit !== undefined) params.set("limit", String(options.limit));
|
|
if (options.offset !== undefined) params.set("offset", String(options.offset));
|
|
const suffix = params.size > 0 ? `?${params.toString()}` : "";
|
|
return api<{ results: EvalTaskResult[]; count: number }>(withProjectId(`/evals${suffix}`, projectId));
|
|
}
|
|
|
|
export function getEval(id: string, projectId?: string): Promise<{ result: EvalTaskResult }> {
|
|
return api<{ result: EvalTaskResult }>(withProjectId(`/evals/${encodeURIComponent(id)}`, projectId));
|
|
}
|
|
|
|
export function listEvalRuns(projectId?: string): Promise<{ runs: EvalRun[] }> {
|
|
return api<{ runs: EvalRun[] }>(withProjectId("/evals/runs", projectId));
|
|
}
|
|
|
|
export interface CreateResearchRunInput {
|
|
query: string;
|
|
providers: ResearchProviderOption[];
|
|
githubRepo?: string;
|
|
githubIssueNumber?: number;
|
|
includeLocalDocs?: boolean;
|
|
enableSynthesis?: boolean;
|
|
maxResults?: number;
|
|
depth?: "shallow" | "normal" | "deep";
|
|
}
|
|
|
|
export function listResearchRuns(
|
|
options: { q?: string; status?: ResearchRunStatus; limit?: number } = {},
|
|
projectId?: string,
|
|
): Promise<ResearchRunsResponse> {
|
|
const params = new URLSearchParams();
|
|
if (options.q) params.set("q", options.q);
|
|
if (options.status) params.set("status", options.status);
|
|
if (options.limit !== undefined) params.set("limit", String(options.limit));
|
|
const suffix = params.size > 0 ? `?${params.toString()}` : "";
|
|
return api<ResearchRunsResponse>(withProjectId(`/research/runs${suffix}`, projectId));
|
|
}
|
|
|
|
export function createResearchRun(input: CreateResearchRunInput, projectId?: string): Promise<ResearchRunResponse> {
|
|
return api<ResearchRunResponse>(withProjectId("/research/runs", projectId), {
|
|
method: "POST",
|
|
body: JSON.stringify(input),
|
|
});
|
|
}
|
|
|
|
export function getResearchRun(id: string, projectId?: string): Promise<ResearchRunResponse> {
|
|
return api<ResearchRunResponse>(withProjectId(`/research/runs/${encodeURIComponent(id)}`, projectId));
|
|
}
|
|
|
|
export type ResearchActionErrorCode =
|
|
| "FEATURE_DISABLED"
|
|
| "MISSING_CREDENTIALS"
|
|
| "PROVIDER_UNAVAILABLE"
|
|
| "RATE_LIMITED"
|
|
| "PROVIDER_TIMEOUT"
|
|
| "RUN_CANCELLED"
|
|
| "RETRY_EXHAUSTED"
|
|
| "INVALID_TRANSITION"
|
|
| "NON_RETRYABLE_PROVIDER_ERROR"
|
|
| "INTERNAL_ERROR";
|
|
|
|
export interface ResearchActionError extends ApiRequestError {
|
|
researchCode: ResearchActionErrorCode;
|
|
setupHint?: string;
|
|
retryable?: boolean;
|
|
}
|
|
|
|
function asResearchActionError(error: unknown): never {
|
|
if (error instanceof ApiRequestError) {
|
|
const codeCandidate = error.details?.code;
|
|
const code = typeof codeCandidate === "string" ? codeCandidate : "INTERNAL_ERROR";
|
|
const setupHint = typeof error.details?.setupHint === "string" ? error.details.setupHint : undefined;
|
|
const retryable = typeof error.details?.retryable === "boolean" ? error.details.retryable : undefined;
|
|
const enriched = error as ResearchActionError;
|
|
enriched.researchCode = code as ResearchActionErrorCode;
|
|
enriched.setupHint = setupHint;
|
|
enriched.retryable = retryable;
|
|
throw enriched;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
export async function cancelResearchRun(id: string, projectId?: string): Promise<{ run: ResearchRunDetail }> {
|
|
try {
|
|
return await api<{ run: ResearchRunDetail }>(withProjectId(`/research/runs/${encodeURIComponent(id)}/cancel`, projectId), {
|
|
method: "POST",
|
|
});
|
|
} catch (error) {
|
|
asResearchActionError(error);
|
|
}
|
|
}
|
|
|
|
export async function retryResearchRun(id: string, projectId?: string): Promise<{ run: ResearchRunDetail }> {
|
|
try {
|
|
return await api<{ run: ResearchRunDetail }>(withProjectId(`/research/runs/${encodeURIComponent(id)}/retry`, projectId), {
|
|
method: "POST",
|
|
});
|
|
} catch (error) {
|
|
asResearchActionError(error);
|
|
}
|
|
}
|
|
|
|
export function exportResearchRun(
|
|
id: string,
|
|
format: "markdown" | "json" | "html",
|
|
projectId?: string,
|
|
): Promise<{ format: string; content: string; filename: string }> {
|
|
return api<{ format: string; content: string; filename: string }>(
|
|
withProjectId(`/research/runs/${encodeURIComponent(id)}/export?format=${encodeURIComponent(format)}`, projectId),
|
|
);
|
|
}
|
|
|
|
export function createTaskFromResearchRun(
|
|
id: string,
|
|
input: { findingId?: string; title?: string; description?: string; priority?: "low" | "normal" | "high" | "urgent"; attachExport?: boolean },
|
|
projectId?: string,
|
|
): Promise<{ task: Task; documentKey: string; attachmentFilename?: string }> {
|
|
const findingId = input.findingId ?? "finding-1";
|
|
return api<{ task: Task; documentKey: string; attachmentFilename?: string }>(
|
|
withProjectId(`/research/runs/${encodeURIComponent(id)}/findings/${encodeURIComponent(findingId)}/task`, projectId),
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
title: input.title,
|
|
description: input.description,
|
|
priority: input.priority,
|
|
attachExport: input.attachExport,
|
|
}),
|
|
},
|
|
);
|
|
}
|
|
|
|
export function attachResearchRunToTask(
|
|
id: string,
|
|
input: { findingId?: string; taskId: string; attachExport?: boolean },
|
|
projectId?: string,
|
|
): Promise<{ taskId: string; documentKey: string; revision: number; attachmentFilename?: string }> {
|
|
const findingId = input.findingId ?? "finding-1";
|
|
return api<{ taskId: string; documentKey: string; revision: number; attachmentFilename?: string }>(
|
|
withProjectId(
|
|
`/research/runs/${encodeURIComponent(id)}/findings/${encodeURIComponent(findingId)}/tasks/${encodeURIComponent(input.taskId)}/enrich`,
|
|
projectId,
|
|
),
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
attachExport: input.attachExport,
|
|
}),
|
|
},
|
|
);
|
|
}
|
|
|
|
export function getResearchAvailability(projectId?: string): Promise<ResearchAvailability> {
|
|
return listResearchRuns({}, projectId).then((response) => response.availability);
|
|
}
|
|
|
|
export interface ResearchStatsResponse {
|
|
total: number;
|
|
byStatus: Record<ResearchRunStatus, number>;
|
|
}
|
|
|
|
export function getResearchStats(projectId?: string): Promise<ResearchStatsResponse> {
|
|
return api<ResearchStatsResponse>(withProjectId("/research/stats", projectId));
|
|
}
|
|
|
|
/*
|
|
* FNXC:CodeOrganization 2026-07-18-14:00:
|
|
* Preserve legacy `system-panel` imports while implementations live in system-panel.ts.
|
|
*/
|
|
export {
|
|
fetchCurrentSystemRebuild,
|
|
fetchSystemInfo,
|
|
fetchSystemLogs,
|
|
promoteResearchFinding,
|
|
reloadAllSystemPlugins,
|
|
requestSystemRestart,
|
|
restartAllSystemAgents,
|
|
restartSystemEngines,
|
|
startFnBinaryLinkLocal,
|
|
startFnBinaryUseGlobal,
|
|
startSystemRebuild,
|
|
} from "./system-panel.js";
|
|
export type {
|
|
ResearchFindingPromotionInput,
|
|
SystemInfoResponse,
|
|
SystemLogEntryDto,
|
|
SystemRebuildJobLine,
|
|
SystemRebuildJobSnapshot,
|
|
} from "./system-panel.js";
|
|
|