709 lines
23 KiB
TypeScript
709 lines
23 KiB
TypeScript
import type {
|
|
StatusResponse,
|
|
ToolSpec,
|
|
CronJob,
|
|
CronRun,
|
|
Integration,
|
|
DiagResult,
|
|
MemoryEntry,
|
|
CostSummary,
|
|
CliTool,
|
|
HealthSnapshot,
|
|
Session,
|
|
ChannelDetail,
|
|
BrowserSession,
|
|
UserNotification,
|
|
DebugStoreResponse,
|
|
DebugPlan,
|
|
DebugTask,
|
|
DebugRun,
|
|
DashboardResponse,
|
|
FlowResponse,
|
|
SkillSpec,
|
|
CreatePlanRequest,
|
|
RouteDecisionTimelineResponse,
|
|
IngressRouteRequest,
|
|
IngressRouteResponse,
|
|
GovernanceCommandRequest,
|
|
GovernanceCommandResponse,
|
|
DigitalEmployeeWorkdayProjection,
|
|
DigitalEmployeeWorkdayActionRequest,
|
|
PlanDispatchResponse,
|
|
} from '../types/api';
|
|
import { clearToken, getToken, setToken } from './auth';
|
|
import { apiOrigin, apiBase } from './basePath';
|
|
import { handleQimingclawDigitalApi } from './qimingclawAdapter';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Base fetch wrapper
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const API_FETCH_TIMEOUT_MS = 9000;
|
|
const DIGITAL_EMPLOYEE_WORKDAY_TIMEOUT_MS = 20000;
|
|
|
|
type ApiFetchOptions = RequestInit & {
|
|
timeoutMs?: number;
|
|
};
|
|
|
|
export class UnauthorizedError extends Error {
|
|
constructor() {
|
|
super('Unauthorized');
|
|
this.name = 'UnauthorizedError';
|
|
}
|
|
}
|
|
|
|
export async function apiFetch<T = unknown>(
|
|
path: string,
|
|
options: ApiFetchOptions = {},
|
|
): Promise<T> {
|
|
const adapterData = await handleQimingclawDigitalApi<T>(path, options);
|
|
if (adapterData !== undefined) {
|
|
return adapterData;
|
|
}
|
|
|
|
const { timeoutMs = API_FETCH_TIMEOUT_MS, ...fetchOptions } = options;
|
|
const token = getToken();
|
|
const headers = new Headers(fetchOptions.headers);
|
|
const controller = new AbortController();
|
|
let timedOut = false;
|
|
const timeout = window.setTimeout(() => {
|
|
timedOut = true;
|
|
controller.abort();
|
|
}, timeoutMs);
|
|
const externalSignal = fetchOptions.signal;
|
|
const abortFromExternal = () => controller.abort();
|
|
if (externalSignal) {
|
|
if (externalSignal.aborted) {
|
|
controller.abort();
|
|
} else {
|
|
externalSignal.addEventListener('abort', abortFromExternal, { once: true });
|
|
}
|
|
}
|
|
|
|
if (token) {
|
|
headers.set('Authorization', `Bearer ${token}`);
|
|
}
|
|
|
|
if (
|
|
fetchOptions.body &&
|
|
typeof fetchOptions.body === 'string' &&
|
|
!headers.has('Content-Type')
|
|
) {
|
|
headers.set('Content-Type', 'application/json');
|
|
}
|
|
|
|
let response: Response;
|
|
try {
|
|
response = await fetch(`${apiOrigin()}${apiBase}${path}`, {
|
|
...fetchOptions,
|
|
headers,
|
|
signal: controller.signal,
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof DOMException && error.name === 'AbortError' && timedOut) {
|
|
throw new Error(`API request timed out after ${timeoutMs / 1000}s: ${path}`);
|
|
}
|
|
throw error;
|
|
} finally {
|
|
window.clearTimeout(timeout);
|
|
externalSignal?.removeEventListener('abort', abortFromExternal);
|
|
}
|
|
|
|
if (response.status === 401) {
|
|
clearToken();
|
|
window.dispatchEvent(new Event('qimingclaw-digital-unauthorized'));
|
|
throw new UnauthorizedError();
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const text = await response.text().catch(() => '');
|
|
let message = text || response.statusText;
|
|
if (text) {
|
|
try {
|
|
const parsed = JSON.parse(text) as { message?: unknown; error?: unknown };
|
|
if (typeof parsed.message === 'string' && parsed.message.trim()) {
|
|
message = parsed.message.trim();
|
|
} else if (typeof parsed.error === 'string' && parsed.error.trim()) {
|
|
message = parsed.error.trim();
|
|
}
|
|
} catch {
|
|
message = text;
|
|
}
|
|
}
|
|
throw new Error(`API ${response.status}: ${message}`);
|
|
}
|
|
|
|
// Some endpoints may return 204 No Content
|
|
if (response.status === 204) {
|
|
return undefined as unknown as T;
|
|
}
|
|
|
|
return response.json() as Promise<T>;
|
|
}
|
|
|
|
function unwrapField<T>(value: T | Record<string, T>, key: string): T {
|
|
if (value !== null && typeof value === 'object' && !Array.isArray(value) && key in value) {
|
|
const unwrapped = (value as Record<string, T | undefined>)[key];
|
|
if (unwrapped !== undefined) {
|
|
return unwrapped;
|
|
}
|
|
}
|
|
return value as T;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Pairing
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function pair(code: string): Promise<{ token: string }> {
|
|
const response = await fetch(`${apiOrigin()}${apiBase}/pair`, {
|
|
method: 'POST',
|
|
headers: { 'X-Pairing-Code': code },
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const text = await response.text().catch(() => '');
|
|
throw new Error(`Pairing failed (${response.status}): ${text || response.statusText}`);
|
|
}
|
|
|
|
const data = (await response.json()) as { token: string };
|
|
setToken(data.token);
|
|
return data;
|
|
}
|
|
|
|
export async function getAdminPairCode(): Promise<{ pairing_code: string | null; pairing_required: boolean }> {
|
|
const response = await fetch(`${apiOrigin()}${apiBase}/admin/paircode`);
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to fetch pairing code (${response.status})`);
|
|
}
|
|
return response.json() as Promise<{ pairing_code: string | null; pairing_required: boolean }>;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Public health (no auth required)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function getPublicHealth(): Promise<{ require_pairing: boolean; paired: boolean }> {
|
|
const response = await fetch(`${apiOrigin()}${apiBase}/health`);
|
|
if (!response.ok) {
|
|
throw new Error(`Health check failed (${response.status})`);
|
|
}
|
|
return response.json() as Promise<{ require_pairing: boolean; paired: boolean }>;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Status / Health
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function getStatus(): Promise<StatusResponse> {
|
|
return apiFetch<StatusResponse>('/api/status');
|
|
}
|
|
|
|
export function getHealth(): Promise<HealthSnapshot> {
|
|
return apiFetch<HealthSnapshot | { health: HealthSnapshot }>('/api/health').then((data) =>
|
|
unwrapField(data, 'health'),
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Config
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function getConfig(): Promise<string> {
|
|
return apiFetch<string | { format?: string; content: string }>('/api/config').then((data) =>
|
|
typeof data === 'string' ? data : data.content,
|
|
);
|
|
}
|
|
|
|
export function putConfig(content: string, contentType = 'application/json'): Promise<void> {
|
|
return apiFetch<void>('/api/config', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': contentType },
|
|
body: content,
|
|
});
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tools
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function getTools(): Promise<ToolSpec[]> {
|
|
return apiFetch<ToolSpec[] | { tools: ToolSpec[] }>('/api/tools').then((data) =>
|
|
unwrapField(data, 'tools'),
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Cron
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function getCronJobs(): Promise<CronJob[]> {
|
|
return apiFetch<CronJob[] | { jobs: CronJob[] }>('/api/cron').then((data) =>
|
|
unwrapField(data, 'jobs'),
|
|
);
|
|
}
|
|
|
|
export function getSchedulerJobs(): Promise<CronJob[]> {
|
|
return apiFetch<CronJob[] | { jobs: CronJob[] }>('/api/scheduler/jobs').then((data) =>
|
|
unwrapField(data, 'jobs'),
|
|
);
|
|
}
|
|
|
|
export function addCronJob(body: {
|
|
name?: string;
|
|
command?: string;
|
|
prompt?: string;
|
|
job_type?: string;
|
|
schedule: string;
|
|
enabled?: boolean;
|
|
session_target?: string;
|
|
model?: string;
|
|
allowed_tools?: string[];
|
|
delete_after_run?: boolean;
|
|
}): Promise<CronJob> {
|
|
return apiFetch<CronJob | { status: string; job: CronJob }>('/api/cron', {
|
|
method: 'POST',
|
|
body: JSON.stringify(body),
|
|
}).then((data) => (typeof (data as { job?: CronJob }).job === 'object' ? (data as { job: CronJob }).job : (data as CronJob)));
|
|
}
|
|
|
|
export function deleteCronJob(id: string): Promise<void> {
|
|
return apiFetch<void>(`/api/cron/${encodeURIComponent(id)}`, {
|
|
method: 'DELETE',
|
|
});
|
|
}
|
|
export function patchCronJob(
|
|
id: string,
|
|
patch: { name?: string; schedule?: string; command?: string },
|
|
): Promise<CronJob> {
|
|
return apiFetch<CronJob | { status: string; job: CronJob }>(
|
|
`/api/cron/${encodeURIComponent(id)}`,
|
|
{
|
|
method: 'PATCH',
|
|
body: JSON.stringify(patch),
|
|
},
|
|
).then((data) => (typeof (data as { job?: CronJob }).job === 'object' ? (data as { job: CronJob }).job : (data as CronJob)));
|
|
}
|
|
|
|
|
|
export function getCronRuns(
|
|
jobId: string,
|
|
limit: number = 20,
|
|
): Promise<CronRun[]> {
|
|
const params = new URLSearchParams({ limit: String(limit) });
|
|
return apiFetch<CronRun[] | { runs: CronRun[] }>(
|
|
`/api/cron/${encodeURIComponent(jobId)}/runs?${params}`,
|
|
).then((data) => unwrapField(data, 'runs'));
|
|
}
|
|
|
|
export interface CronSettings {
|
|
enabled: boolean;
|
|
catch_up_on_startup: boolean;
|
|
max_run_history: number;
|
|
}
|
|
|
|
export function getCronSettings(): Promise<CronSettings> {
|
|
return apiFetch<CronSettings>('/api/cron/settings');
|
|
}
|
|
|
|
export function patchCronSettings(
|
|
patch: Partial<CronSettings>,
|
|
): Promise<CronSettings> {
|
|
return apiFetch<CronSettings & { status: string }>('/api/cron/settings', {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(patch),
|
|
});
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Integrations
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function getIntegrations(): Promise<Integration[]> {
|
|
return apiFetch<Integration[] | { integrations: Integration[] }>('/api/integrations').then(
|
|
(data) => unwrapField(data, 'integrations'),
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Doctor / Diagnostics
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function runDoctor(): Promise<DiagResult[]> {
|
|
return apiFetch<DiagResult[] | { results: DiagResult[]; summary?: unknown }>('/api/doctor', {
|
|
method: 'POST',
|
|
body: JSON.stringify({}),
|
|
}).then((data) => (Array.isArray(data) ? data : data.results));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Memory
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function getMemory(
|
|
query?: string,
|
|
category?: string,
|
|
): Promise<MemoryEntry[]> {
|
|
const params = new URLSearchParams();
|
|
if (query) params.set('query', query);
|
|
if (category) params.set('category', category);
|
|
const qs = params.toString();
|
|
return apiFetch<MemoryEntry[] | { entries: MemoryEntry[] }>(`/api/memory${qs ? `?${qs}` : ''}`).then(
|
|
(data) => unwrapField(data, 'entries'),
|
|
);
|
|
}
|
|
|
|
export function storeMemory(
|
|
key: string,
|
|
content: string,
|
|
category?: string,
|
|
): Promise<void> {
|
|
return apiFetch<unknown>('/api/memory', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ key, content, category }),
|
|
}).then(() => undefined);
|
|
}
|
|
|
|
export function deleteMemory(key: string): Promise<void> {
|
|
return apiFetch<void>(`/api/memory/${encodeURIComponent(key)}`, {
|
|
method: 'DELETE',
|
|
});
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Cost
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function getCost(): Promise<CostSummary> {
|
|
return apiFetch<CostSummary | { cost: CostSummary }>('/api/cost').then((data) =>
|
|
unwrapField(data, 'cost'),
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Sessions
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function getSessions(): Promise<Session[]> {
|
|
return apiFetch<{ sessions: Session[] }>('/api/sessions').then((data) =>
|
|
data.sessions ?? [],
|
|
);
|
|
}
|
|
|
|
export function getSession(id: string): Promise<Session> {
|
|
return apiFetch<Session>(`/api/sessions/${encodeURIComponent(id)}`);
|
|
}
|
|
|
|
export function renameSession(id: string, name: string): Promise<{ session_id: string; name: string }> {
|
|
return apiFetch<{ session_id: string; name: string }>(`/api/sessions/${encodeURIComponent(id)}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify({ name }),
|
|
});
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Channels (detailed)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function getChannels(): Promise<ChannelDetail[]> {
|
|
return apiFetch<ChannelDetail[] | { channels: ChannelDetail[] }>('/api/channels').then((data) =>
|
|
unwrapField(data, 'channels'),
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CLI Tools
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function getCliTools(): Promise<CliTool[]> {
|
|
return apiFetch<CliTool[] | { cli_tools: CliTool[] }>('/api/cli-tools').then((data) =>
|
|
unwrapField(data, 'cli_tools'),
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Browser / SuperRPA
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface LaunchBrowserResponse {
|
|
session_id: string;
|
|
status: string;
|
|
}
|
|
|
|
export interface BrowserStatusResponse {
|
|
running: boolean;
|
|
sessions: BrowserSession[];
|
|
}
|
|
|
|
export function launchBrowser(url?: string, superrpaPath?: string): Promise<LaunchBrowserResponse> {
|
|
return apiFetch<LaunchBrowserResponse>('/api/browser/launch', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ url: url || '', superrpa_path: superrpaPath || '' }),
|
|
});
|
|
}
|
|
|
|
export function getBrowserStatus(): Promise<BrowserStatusResponse> {
|
|
return apiFetch<BrowserStatusResponse>('/api/browser/status');
|
|
}
|
|
|
|
export function closeBrowserSession(sessionId: string): Promise<void> {
|
|
return apiFetch<void>(`/api/browser/session/${encodeURIComponent(sessionId)}/close`, {
|
|
method: 'POST',
|
|
});
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Plans
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function getPlans() {
|
|
return apiFetch<{ plans: import('../types/api').PlanSummary[] }>('/api/debug/plans');
|
|
}
|
|
|
|
export interface PlanMessage {
|
|
role: string;
|
|
content: string;
|
|
created_at?: string | null;
|
|
}
|
|
|
|
export function getSessionMessages(sessionId: string): Promise<{ messages: PlanMessage[] }> {
|
|
return apiFetch<{ session_id: string; messages: PlanMessage[] }>(
|
|
`/api/sessions/${encodeURIComponent(sessionId)}/messages`,
|
|
).then((d) => ({ messages: d.messages || [] }));
|
|
}
|
|
|
|
export function getPlanMessages(planId: string): Promise<{ messages: PlanMessage[] }> {
|
|
return apiFetch<{ plan_id: string; messages: PlanMessage[] }>(
|
|
`/api/plan/${encodeURIComponent(planId)}/messages`,
|
|
).then((d) => ({ messages: d.messages || [] }));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Execution governance
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function getRouteDecisionTimeline(): Promise<RouteDecisionTimelineResponse> {
|
|
return apiFetch<RouteDecisionTimelineResponse>('/api/route-decisions');
|
|
}
|
|
|
|
export function routeIngress(
|
|
body: IngressRouteRequest,
|
|
): Promise<IngressRouteResponse> {
|
|
return apiFetch<IngressRouteResponse>('/api/ingress/route', {
|
|
method: 'POST',
|
|
body: JSON.stringify(body),
|
|
});
|
|
}
|
|
|
|
export function governanceCommand(
|
|
body: GovernanceCommandRequest,
|
|
): Promise<GovernanceCommandResponse> {
|
|
return apiFetch<GovernanceCommandResponse>('/api/governance/command', {
|
|
method: 'POST',
|
|
body: JSON.stringify(body),
|
|
});
|
|
}
|
|
|
|
export function runSchedulerNow(): Promise<GovernanceCommandResponse> {
|
|
const stamp = Date.now();
|
|
return governanceCommand({
|
|
command_kind: 'run_schedule_now',
|
|
context_refs: { schedule_id: 'scheduler-manual-check' },
|
|
payload: { source: 'scheduler-jobs-page' },
|
|
correlation_id: `ui-run-schedule-now-${stamp}`,
|
|
idempotency_key: `ui-run-schedule-now-${stamp}`,
|
|
});
|
|
}
|
|
|
|
export function getPlanRevisions(planId: string): Promise<any> {
|
|
return apiFetch(`/api/plan/${encodeURIComponent(planId)}/revisions`);
|
|
}
|
|
|
|
export function getEventLog(params: {
|
|
correlation_id?: string;
|
|
plan_id?: string;
|
|
task_id?: string;
|
|
run_id?: string;
|
|
limit?: number;
|
|
} = {}): Promise<any> {
|
|
const query = new URLSearchParams();
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value !== undefined && value !== null && `${value}`.trim() !== '') {
|
|
query.set(key, `${value}`);
|
|
}
|
|
});
|
|
const suffix = query.toString();
|
|
return apiFetch(`/api/event-log${suffix ? `?${suffix}` : ''}`);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Notifications
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export { type UserNotification };
|
|
|
|
export function getNotifications(
|
|
status: string = 'unread',
|
|
): Promise<UserNotification[]> {
|
|
return apiFetch<{ notifications: UserNotification[] }>(
|
|
`/api/notifications?status=${encodeURIComponent(status)}`,
|
|
).then((d) => d.notifications ?? []);
|
|
}
|
|
|
|
export function getUnreadNotificationCount(): Promise<number> {
|
|
return apiFetch<{ count: number }>('/api/notifications/unread-count').then(
|
|
(d) => d.count ?? 0,
|
|
);
|
|
}
|
|
|
|
export function markNotificationRead(id: string): Promise<boolean> {
|
|
return apiFetch<{ ok: boolean }>(`/api/notifications/${encodeURIComponent(id)}/read`, {
|
|
method: 'POST',
|
|
}).then((d) => d.ok ?? false);
|
|
}
|
|
|
|
export function dismissNotification(id: string): Promise<boolean> {
|
|
return apiFetch<{ ok: boolean }>(
|
|
`/api/notifications/${encodeURIComponent(id)}/dismiss`,
|
|
{ method: 'POST' },
|
|
).then((d) => d.ok ?? false);
|
|
}
|
|
|
|
export function replyToNotification(
|
|
id: string,
|
|
content: string,
|
|
): Promise<{ ok: boolean; task_id?: string; status?: string }> {
|
|
return apiFetch<{ ok: boolean; task_id?: string; status?: string }>(
|
|
`/api/notifications/${encodeURIComponent(id)}/reply`,
|
|
{
|
|
method: 'POST',
|
|
body: JSON.stringify({ content }),
|
|
},
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Console: Debug Store, Plans, Tasks
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function getDebugStore(): Promise<DebugStoreResponse> {
|
|
return apiFetch<DebugStoreResponse>('/api/debug/store?limit=200');
|
|
}
|
|
|
|
export function getDashboardStats(): Promise<DashboardResponse> {
|
|
return apiFetch<DashboardResponse | { tasks: DashboardResponse['tasks'] }>('/api/dashboard')
|
|
.then((data) => ('tasks' in data && !('runtime' in data))
|
|
? { runtime: { active_tasks: 0, failed_tasks: 0, pending_confirmations: 0 }, tasks: data.tasks, approvals: { total: 0, pending: 0 } }
|
|
: data as DashboardResponse);
|
|
}
|
|
|
|
export function getDigitalEmployeeWorkday(date?: string): Promise<DigitalEmployeeWorkdayProjection> {
|
|
const query = date ? `?date=${encodeURIComponent(date)}` : '';
|
|
return apiFetch<DigitalEmployeeWorkdayProjection>(`/api/digital-employee/workday${query}`, {
|
|
timeoutMs: DIGITAL_EMPLOYEE_WORKDAY_TIMEOUT_MS,
|
|
});
|
|
}
|
|
|
|
export function postDigitalEmployeeWorkdayAction(
|
|
body: DigitalEmployeeWorkdayActionRequest,
|
|
): Promise<DigitalEmployeeWorkdayProjection> {
|
|
return apiFetch<DigitalEmployeeWorkdayProjection>('/api/digital-employee/workday/actions', {
|
|
method: 'POST',
|
|
body: JSON.stringify(body),
|
|
});
|
|
}
|
|
|
|
export function dispatchPlanNow(planId: string): Promise<PlanDispatchResponse> {
|
|
return apiFetch<PlanDispatchResponse>(`/api/debug/plan/${encodeURIComponent(planId)}/dispatch`, {
|
|
method: 'POST',
|
|
timeoutMs: DIGITAL_EMPLOYEE_WORKDAY_TIMEOUT_MS,
|
|
});
|
|
}
|
|
|
|
export function getDebugPlans(): Promise<DebugPlan[]> {
|
|
return apiFetch<{ plans: DebugPlan[] }>('/api/debug/plans?limit=80')
|
|
.then((data) => data.plans ?? []);
|
|
}
|
|
|
|
export function searchDebugPlans(query: string, limit = 50): Promise<DebugPlan[]> {
|
|
const params = new URLSearchParams({ limit: String(limit), q: query });
|
|
return apiFetch<{ plans: DebugPlan[] }>(`/api/debug/plans?${params}`)
|
|
.then((data) => data.plans ?? []);
|
|
}
|
|
|
|
export function getDebugTasks(): Promise<{ tasks: DebugTask[]; runs: DebugRun[]; total?: number; truncated?: boolean }> {
|
|
return apiFetch<{ tasks: DebugTask[]; runs: DebugRun[]; total?: number; truncated?: boolean }>('/api/debug/tasks?limit=80')
|
|
.then((data) => ({ tasks: data.tasks ?? [], runs: data.runs ?? [], total: data.total, truncated: data.truncated }));
|
|
}
|
|
|
|
export function getDebugTasksForPlan(planId: string): Promise<{ tasks: DebugTask[]; runs: DebugRun[]; total?: number; truncated?: boolean }> {
|
|
const params = new URLSearchParams({ limit: '50', plan_id: planId });
|
|
return apiFetch<{ tasks: DebugTask[]; runs: DebugRun[]; total?: number; truncated?: boolean }>(`/api/debug/tasks?${params}`)
|
|
.then((data) => ({ tasks: data.tasks ?? [], runs: data.runs ?? [], total: data.total, truncated: data.truncated }));
|
|
}
|
|
|
|
export function getDebugPlanFlow(planId: string): Promise<FlowResponse> {
|
|
return apiFetch<FlowResponse>(`/api/debug/plan/${encodeURIComponent(planId)}/flow`);
|
|
}
|
|
|
|
export function getDebugTaskFlow(taskId: string): Promise<FlowResponse> {
|
|
return apiFetch<FlowResponse>(`/api/debug/task/${encodeURIComponent(taskId)}/flow`);
|
|
}
|
|
|
|
export function getSkills(): Promise<SkillSpec[]> {
|
|
return apiFetch<{ skills: SkillSpec[] }>('/api/skill')
|
|
.then((data) => data.skills ?? []);
|
|
}
|
|
|
|
export function setSkillEnabled(skillId: string, enabled: boolean): Promise<{ ok: boolean; skill_id: string; enabled: boolean; error?: string }> {
|
|
return apiFetch<{ ok: boolean; skill_id: string; enabled: boolean; error?: string }>(
|
|
`/api/skill/${encodeURIComponent(skillId)}/enabled`,
|
|
{
|
|
method: 'POST',
|
|
body: JSON.stringify({ enabled }),
|
|
},
|
|
);
|
|
}
|
|
|
|
export function createPlan(body: CreatePlanRequest): Promise<{ ok?: boolean; plan_id: string; task_count?: number }> {
|
|
return apiFetch('/api/debug/plan/create', {
|
|
method: 'POST',
|
|
body: JSON.stringify(body),
|
|
});
|
|
}
|
|
|
|
export function planAction(planId: string, action: string): Promise<any> {
|
|
const endpoint = action === 'submit'
|
|
? `/api/plan/${encodeURIComponent(planId)}/submit`
|
|
: `/api/debug/plan/${encodeURIComponent(planId)}/${action}`;
|
|
return apiFetch(endpoint, { method: 'POST' });
|
|
}
|
|
|
|
export function taskAction(taskId: string, action: string): Promise<any> {
|
|
return apiFetch(`/api/task/${encodeURIComponent(taskId)}/${action}`, { method: 'POST' });
|
|
}
|
|
|
|
/**
|
|
* Legacy compatibility only.
|
|
* Current React pages must trigger scheduler checks through runSchedulerNow()
|
|
* so backend governance rejection and command errors are surfaced correctly.
|
|
*/
|
|
export function triggerCronCheck(): Promise<{ ok?: boolean; activated: number }> {
|
|
return apiFetch<{ ok?: boolean; activated: number }>('/api/cron/check', { method: 'POST' });
|
|
}
|
|
|
|
export function getAuditTrace(kind: string, id: string): Promise<any> {
|
|
const params = new URLSearchParams({ kind, id });
|
|
return apiFetch(`/api/audit?${params}`);
|
|
}
|
|
|
|
export function getArtifacts(): Promise<any[]> {
|
|
return apiFetch<{ artifacts: any[] }>('/api/artifact')
|
|
.then((data) => data.artifacts ?? []);
|
|
}
|
|
|
|
export function getDebugConversations(): Promise<any[]> {
|
|
return apiFetch<{ conversations: any[] }>('/api/debug/conversations')
|
|
.then((data) => data.conversations ?? []);
|
|
}
|