Files
kandev/apps/web/lib/api/domains/plan-api.ts
T
2026-08-16 12:08:59 +01:00

158 lines
4.4 KiB
TypeScript

import { getWebSocketClient } from "@/lib/ws/connection";
import type { TaskPlan, TaskPlanRevision } from "@/lib/types/http";
// i18n-exempt: diagnostic thrown to the caller, never rendered.
const WS_CLIENT_UNAVAILABLE = "WebSocket client not available";
/**
* Get the task plan for a specific task.
* Returns null if no plan exists.
*/
export async function getTaskPlan(taskId: string): Promise<TaskPlan | null> {
const client = getWebSocketClient();
if (!client) {
throw new Error(WS_CLIENT_UNAVAILABLE);
}
const response = await client.request("task.plan.get", { task_id: taskId });
if (!response || Object.keys(response).length === 0) {
return null;
}
return response as TaskPlan;
}
/**
* Create a new task plan.
*/
export async function createTaskPlan(
taskId: string,
content: string,
title?: string,
): Promise<TaskPlan> {
const client = getWebSocketClient();
if (!client) {
throw new Error(WS_CLIENT_UNAVAILABLE);
}
const response = await client.request("task.plan.create", {
task_id: taskId,
// i18n-exempt: canonical English persisted as the plan title. See the comment below.
// Canonical English on purpose: the backend persists this into
// `task_plans.title` / `task_plan_revisions.title`, so a localized default
// would freeze whichever locale created the plan into the stored record.
title: title || "Plan",
content,
created_by: "user",
});
return response as TaskPlan;
}
/**
* Update an existing task plan.
*/
export async function updateTaskPlan(
taskId: string,
content: string,
title?: string,
): Promise<TaskPlan> {
const client = getWebSocketClient();
if (!client) {
throw new Error(WS_CLIENT_UNAVAILABLE);
}
const payload: Record<string, string> = {
task_id: taskId,
content,
created_by: "user",
};
if (title !== undefined) {
payload.title = title;
}
const response = await client.request("task.plan.update", payload);
return response as TaskPlan;
}
export async function markPlanImplementationStarted(
taskId: string,
sessionId: string,
): Promise<TaskPlan> {
const client = getWebSocketClient();
if (!client) {
throw new Error(WS_CLIENT_UNAVAILABLE);
}
const response = await client.request("task.plan.implementation_started", {
task_id: taskId,
session_id: sessionId,
actor: "user",
});
return response as TaskPlan;
}
/**
* Delete a task plan.
*/
export async function deleteTaskPlan(taskId: string): Promise<void> {
const client = getWebSocketClient();
if (!client) {
throw new Error(WS_CLIENT_UNAVAILABLE);
}
await client.request("task.plan.delete", { task_id: taskId });
}
/**
* List all revisions for a plan (metadata only, newest first).
*/
export async function listPlanRevisions(taskId: string): Promise<TaskPlanRevision[]> {
const client = getWebSocketClient();
if (!client) {
throw new Error(WS_CLIENT_UNAVAILABLE);
}
const response = await client.request("task.plan.revisions.list", { task_id: taskId });
const list = (response as { revisions?: TaskPlanRevision[] })?.revisions;
return list ?? [];
}
/**
* Fetch a single revision including its content (for diff/preview).
* `taskId` is optional but recommended — the backend uses it to enforce
* ownership (a connected client can only read revisions belonging to a
* task it already holds a reference to).
*/
export async function getPlanRevision(
revisionId: string,
taskId?: string,
): Promise<TaskPlanRevision> {
const client = getWebSocketClient();
if (!client) {
throw new Error(WS_CLIENT_UNAVAILABLE);
}
const payload: Record<string, string> = { revision_id: revisionId };
if (taskId) payload.task_id = taskId;
const response = await client.request("task.plan.revision.get", payload);
return response as TaskPlanRevision;
}
/**
* Revert the plan to a previous revision. Server appends a new revision with the target's content.
*/
export async function revertPlanRevision(
taskId: string,
revisionId: string,
authorName?: string,
): Promise<TaskPlanRevision> {
const client = getWebSocketClient();
if (!client) {
throw new Error(WS_CLIENT_UNAVAILABLE);
}
const payload: Record<string, string> = {
task_id: taskId,
revision_id: revisionId,
};
if (authorName) payload.author_name = authorName;
const response = await client.request("task.plan.revert", payload);
return response as TaskPlanRevision;
}