From 205ab8a223ddf6591a3e8bfc9108506502977c11 Mon Sep 17 00:00:00 2001 From: soryu Date: Fri, 16 Jan 2026 12:23:49 +0000 Subject: Fixup: use default api.makima.jp URL and fix default branch detection Also add checkpointing/history --- makima/frontend/src/lib/api.ts | 399 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 399 insertions(+) (limited to 'makima/frontend/src/lib/api.ts') diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts index 1e62732..9c56f6b 100644 --- a/makima/frontend/src/lib/api.ts +++ b/makima/frontend/src/lib/api.ts @@ -2093,3 +2093,402 @@ export async function createAdhocTask( } return res.json(); } + +// ============================================================================= +// History Types +// ============================================================================= + +/** History event from the timeline */ +export interface HistoryEvent { + id: string; + ownerId: string; + contractId: string | null; + taskId: string | null; + eventType: string; + eventSubtype: string | null; + phase: string | null; + eventData: Record; + createdAt: string; +} + +/** Response for contract history endpoint */ +export interface ContractHistoryResponse { + contractId: string; + entries: HistoryEvent[]; + totalCount: number; + cursor: string | null; +} + +/** Tool call info in conversation messages */ +export interface ToolCallInfo { + id: string; + name: string; + input: Record; +} + +/** Conversation message with optional tool calls */ +export interface ConversationMessage { + id: string; + role: string; + content: string; + timestamp: string; + toolCalls?: ToolCallInfo[]; + toolName?: string; + toolInput?: Record; + toolResult?: string; + isError?: boolean; + tokenCount?: number; + costUsd?: number; +} + +/** Reference to a spawned task in supervisor conversation */ +export interface TaskReference { + taskId: string; + taskName: string; + status: string; + createdAt: string; + completedAt: string | null; +} + +/** Response for supervisor conversation endpoint */ +export interface SupervisorConversationResponse { + contractId: string; + supervisorTaskId: string; + phase: string; + lastActivity: string; + pendingTaskIds: string[]; + messages: ConversationMessage[]; + spawnedTasks: TaskReference[]; +} + +/** Response for task conversation endpoint */ +export interface TaskConversationResponse { + taskId: string; + taskName: string; + status: string; + messages: ConversationMessage[]; + totalTokens: number | null; + totalCost: number | null; +} + +/** Query filters for timeline endpoint */ +export interface TimelineQueryFilters { + contractId?: string; + taskId?: string; + includeSubtasks?: boolean; + from?: string; + to?: string; + limit?: number; +} + +/** Query filters for contract history endpoint */ +export interface HistoryQueryFilters { + phase?: string; + eventTypes?: string; + from?: string; + to?: string; + limit?: number; +} + +/** Task checkpoint */ +export interface TaskCheckpoint { + id: string; + taskId: string; + checkpointNumber: number; + commitSha: string; + branchName: string; + message: string; + filesChanged: Array<{ path: string; action: string }>; + linesAdded: number; + linesRemoved: number; + createdAt: string; +} + +// ============================================================================= +// History API Functions +// ============================================================================= + +/** + * Get contract history timeline. + */ +export async function getContractHistory( + contractId: string, + filters?: HistoryQueryFilters +): Promise { + const params = new URLSearchParams(); + if (filters?.phase) params.append("phase", filters.phase); + if (filters?.eventTypes) params.append("event_types", filters.eventTypes); + if (filters?.from) params.append("from", filters.from); + if (filters?.to) params.append("to", filters.to); + if (filters?.limit) params.append("limit", filters.limit.toString()); + + const query = params.toString(); + const url = `${API_BASE}/api/v1/contracts/${contractId}/history${query ? `?${query}` : ""}`; + + const res = await authFetch(url); + if (!res.ok) { + throw new Error(`Failed to get contract history: ${res.statusText}`); + } + return res.json(); +} + +/** + * Get supervisor conversation history. + */ +export async function getSupervisorConversation( + contractId: string +): Promise { + const res = await authFetch( + `${API_BASE}/api/v1/contracts/${contractId}/supervisor/conversation` + ); + if (!res.ok) { + throw new Error(`Failed to get supervisor conversation: ${res.statusText}`); + } + return res.json(); +} + +/** + * Get task conversation history. + */ +export async function getTaskConversation( + taskId: string, + options?: { includeToolCalls?: boolean; includeToolResults?: boolean; limit?: number } +): Promise { + const params = new URLSearchParams(); + if (options?.includeToolCalls !== undefined) + params.append("include_tool_calls", options.includeToolCalls.toString()); + if (options?.includeToolResults !== undefined) + params.append("include_tool_results", options.includeToolResults.toString()); + if (options?.limit) params.append("limit", options.limit.toString()); + + const query = params.toString(); + const url = `${API_BASE}/api/v1/mesh/tasks/${taskId}/conversation${query ? `?${query}` : ""}`; + + const res = await authFetch(url); + if (!res.ok) { + throw new Error(`Failed to get task conversation: ${res.statusText}`); + } + return res.json(); +} + +/** + * Get unified timeline for current user. + */ +export async function getTimeline( + filters?: TimelineQueryFilters +): Promise { + const params = new URLSearchParams(); + if (filters?.contractId) params.append("contract_id", filters.contractId); + if (filters?.taskId) params.append("task_id", filters.taskId); + if (filters?.includeSubtasks !== undefined) + params.append("include_subtasks", filters.includeSubtasks.toString()); + if (filters?.from) params.append("from", filters.from); + if (filters?.to) params.append("to", filters.to); + if (filters?.limit) params.append("limit", filters.limit.toString()); + + const query = params.toString(); + const url = `${API_BASE}/api/v1/timeline${query ? `?${query}` : ""}`; + + const res = await authFetch(url); + if (!res.ok) { + throw new Error(`Failed to get timeline: ${res.statusText}`); + } + return res.json(); +} + +/** + * Get task checkpoints. + */ +export async function getTaskCheckpoints(taskId: string): Promise { + const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/checkpoints`); + if (!res.ok) { + throw new Error(`Failed to get task checkpoints: ${res.statusText}`); + } + return res.json(); +} + +// ============================================================================= +// Resume/Rewind/Fork Types +// ============================================================================= + +/** Request to rewind a task to a checkpoint */ +export interface RewindTaskRequest { + checkpointId?: string; + checkpointSha?: string; + preserveMode: "discard" | "create_branch" | "stash"; + branchName?: string; +} + +/** Response from task rewind */ +export interface RewindTaskResponse { + taskId: string; + rewindedTo: { + checkpointNumber: number; + sha: string; + message: string; + }; + preservedAs?: { + stateType: string; + reference: string; + }; +} + +/** Request to fork a task from a checkpoint */ +export interface ForkTaskRequest { + forkFromType: "checkpoint" | "timestamp" | "message_id"; + forkFromValue: string; + newTaskName: string; + newTaskPlan: string; + includeConversation?: boolean; + createBranch?: boolean; + branchName?: string; +} + +/** Response from task fork */ +export interface ForkTaskResponse { + newTaskId: string; + sourceTaskId: string; + forkPoint: { + forkType: string; + checkpoint?: TaskCheckpoint; + timestamp: string; + }; + branchName?: string; + conversationIncluded: boolean; + messageCount?: number; +} + +/** Request to resume supervisor */ +export interface ResumeSupervisorRequest { + resumeMode: "continue" | "restart_phase" | "from_checkpoint"; + checkpointId?: string; + additionalContext?: string; +} + +/** Response from supervisor resume */ +export interface ResumeSupervisorResponse { + supervisorTaskId: string; + daemonId: string | null; + contractId: string; + phase: string; + status: string; + conversationMessageCount: number; +} + +/** Request to rewind supervisor conversation */ +export interface RewindConversationRequest { + toMessageId?: string; + byMessageCount?: number; + rewindCode?: boolean; +} + +/** Response from conversation rewind */ +export interface RewindConversationResponse { + contractId: string; + messagesRemoved: number; + newMessageCount: number; +} + +// ============================================================================= +// Resume/Rewind/Fork API Functions +// ============================================================================= + +/** + * Rewind a task to a checkpoint. + */ +export async function rewindTask( + taskId: string, + request: RewindTaskRequest +): Promise { + const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/rewind`, { + method: "POST", + body: JSON.stringify(request), + }); + if (!res.ok) { + const errorText = await res.text(); + throw new Error(`Failed to rewind task: ${errorText || res.statusText}`); + } + return res.json(); +} + +/** + * Fork a task from a checkpoint. + */ +export async function forkTask( + taskId: string, + request: ForkTaskRequest +): Promise { + const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/fork`, { + method: "POST", + body: JSON.stringify(request), + }); + if (!res.ok) { + const errorText = await res.text(); + throw new Error(`Failed to fork task: ${errorText || res.statusText}`); + } + return res.json(); +} + +/** + * Resume a supervisor. + */ +export async function resumeSupervisor( + contractId: string, + request: ResumeSupervisorRequest +): Promise { + const res = await authFetch( + `${API_BASE}/api/v1/contracts/${contractId}/supervisor/resume`, + { + method: "POST", + body: JSON.stringify(request), + } + ); + if (!res.ok) { + const errorText = await res.text(); + throw new Error(`Failed to resume supervisor: ${errorText || res.statusText}`); + } + return res.json(); +} + +/** + * Rewind supervisor conversation. + */ +export async function rewindSupervisorConversation( + contractId: string, + request: RewindConversationRequest +): Promise { + const res = await authFetch( + `${API_BASE}/api/v1/contracts/${contractId}/supervisor/conversation/rewind`, + { + method: "POST", + body: JSON.stringify(request), + } + ); + if (!res.ok) { + const errorText = await res.text(); + throw new Error(`Failed to rewind conversation: ${errorText || res.statusText}`); + } + return res.json(); +} + +/** + * Resume task from a checkpoint. + */ +export async function resumeFromCheckpoint( + taskId: string, + checkpointId: string, + request: { taskName?: string; plan: string; includeConversation?: boolean } +): Promise<{ taskId: string }> { + const res = await authFetch( + `${API_BASE}/api/v1/mesh/tasks/${taskId}/checkpoints/${checkpointId}/resume`, + { + method: "POST", + body: JSON.stringify(request), + } + ); + if (!res.ok) { + const errorText = await res.text(); + throw new Error(`Failed to resume from checkpoint: ${errorText || res.statusText}`); + } + return res.json(); +} -- cgit v1.2.3