/** * update_phase tool * * Agent tool to update a phase's status. * Auto-sets started_at when transitioning to "in_progress". * Auto-sets finish_at when transitioning to "done" or "removed". * * Used by: mission-orchestrator skill */ import { readState, readRun, writeRun, findPhase, updatePhaseInRun, getTimestamp, PhaseStatus } from "../state.js"; export interface UpdatePhaseParams { phase_id: string; status: PhaseStatus; } export interface UpdatePhaseResult { success: boolean; message: string; phaseId: string | null; previousStatus: PhaseStatus | null; newStatus: PhaseStatus | null; startedAt: string | null; finishAt: string | null; errors: string[]; } /** * Update a phase's status in the active mission run * * @param params.phase_id - ID of the phase to update * @param params.status - New status ("pending" | "in_progress" | "done" | "removed") * @returns Result with updated phase info */ export function updatePhase(params: UpdatePhaseParams): UpdatePhaseResult { const result: UpdatePhaseResult = { success: false, message: "", phaseId: null, previousStatus: null, newStatus: null, startedAt: null, finishAt: null, errors: [] }; try { // Validate parameters if (!params.phase_id || params.phase_id.trim() === "") { result.errors.push("Phase ID is required"); result.message = "Failed to update phase: phase_id is required"; return result; } if (!params.status) { result.errors.push("Status is required"); result.message = "Failed to update phase: status is required"; return result; } const validStatuses: PhaseStatus[] = ["pending", "in_progress", "done", "removed"]; if (!validStatuses.includes(params.status)) { result.errors.push(`Invalid status: ${params.status}. Must be one of: ${validStatuses.join(", ")}`); result.message = "Failed to update phase: invalid status"; return result; } // Get active run const state = readState(); if (!state.active_run_id) { result.errors.push("No active run"); result.message = "Failed to update phase: no active mission run"; return result; } // Read run.json const run = readRun(state.active_run_id); if (!run) { result.errors.push(`Run not found: ${state.active_run_id}`); result.message = "Failed to update phase: run not found"; return result; } // Find phase const phase = findPhase(run, params.phase_id.trim()); if (!phase) { result.errors.push(`Phase not found: ${params.phase_id}`); result.message = "Failed to update phase: phase not found"; return result; } // Capture previous status result.previousStatus = phase.status; result.phaseId = phase.id; // Calculate timestamp updates const updates: Partial = { status: params.status }; const now = getTimestamp(); // Set started_at when transitioning to in_progress if (params.status === "in_progress" && phase.status !== "in_progress") { updates.started_at = now; result.startedAt = now; } // Set finish_at when transitioning to terminal states const terminalStatuses: PhaseStatus[] = ["done", "removed"]; if (terminalStatuses.includes(params.status) && !terminalStatuses.includes(phase.status)) { updates.finish_at = now; result.finishAt = now; } // Update run const updatedRun = updatePhaseInRun(run, phase.id, updates); writeRun(updatedRun); result.success = true; result.newStatus = params.status; result.message = `Phase ${phase.id} updated: ${phase.status} → ${params.status}`; } catch (error) { result.success = false; result.message = `Error updating phase: ${error instanceof Error ? error.message : String(error)}`; result.errors.push(String(error)); } return result; }