summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--makima/frontend/src/components/directives/DirectiveDetail.tsx19
-rw-r--r--makima/frontend/src/hooks/useDirectives.ts17
-rw-r--r--makima/frontend/src/lib/api.ts8
-rw-r--r--makima/frontend/src/routes/directives.tsx3
-rw-r--r--makima/src/server/handlers/directives.rs1
-rw-r--r--makima/src/server/mod.rs1
-rw-r--r--makima/src/server/openapi.rs3
7 files changed, 44 insertions, 8 deletions
diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx
index ab6ddbb..332a417 100644
--- a/makima/frontend/src/components/directives/DirectiveDetail.tsx
+++ b/makima/frontend/src/components/directives/DirectiveDetail.tsx
@@ -34,6 +34,7 @@ interface DirectiveDetailProps {
onUpdateGoal: (goal: string) => void;
onDelete: () => void;
onRefresh: () => void;
+ onCleanupTasks: () => void;
}
export function DirectiveDetail({
@@ -47,6 +48,7 @@ export function DirectiveDetail({
onUpdateGoal,
onDelete,
onRefresh,
+ onCleanupTasks,
}: DirectiveDetailProps) {
const [editingGoal, setEditingGoal] = useState(false);
const [goalText, setGoalText] = useState(directive.goal);
@@ -59,6 +61,8 @@ export function DirectiveDetail({
const completedSteps = directive.steps.filter((s) => s.status === "completed").length;
const totalSteps = directive.steps.length;
const progress = totalSteps > 0 ? Math.round((completedSteps / totalSteps) * 100) : 0;
+ const terminalStatuses = new Set(["completed", "failed", "skipped"]);
+ const hasTerminalTasks = directive.steps.some((s) => s.taskId && terminalStatuses.has(s.status));
// Memory panel state
const [memoryOpen, setMemoryOpen] = useState(false);
@@ -221,11 +225,11 @@ export function DirectiveDetail({
)}
{/* Completion task indicator */}
- {directive.completionTaskId && !directive.prUrl && (
+ {directive.completionTaskId && (
<div className="flex items-center gap-2 mb-2 px-2 py-1.5 bg-[#1a1a10] border border-yellow-900 rounded">
<span className="inline-block w-2 h-2 rounded-full bg-yellow-400 animate-pulse" />
<span className="text-[10px] font-mono text-yellow-400">
- Creating PR...
+ {directive.prUrl ? "Updating PR..." : "Creating PR..."}
</span>
<a
href={`/mesh/${directive.completionTaskId}`}
@@ -279,10 +283,19 @@ export function DirectiveDetail({
</button>
</div>
)}
+ {hasTerminalTasks && (
+ <button
+ type="button"
+ onClick={onCleanupTasks}
+ className="text-[10px] font-mono text-[#7788aa] hover:text-white border border-[#2a3a5a] rounded px-2 py-1 ml-auto"
+ >
+ Clean up tasks
+ </button>
+ )}
<button
type="button"
onClick={onDelete}
- className="text-[10px] font-mono text-red-400 hover:text-red-300 border border-red-800 rounded px-2 py-1 ml-auto"
+ className={`text-[10px] font-mono text-red-400 hover:text-red-300 border border-red-800 rounded px-2 py-1 ${hasTerminalTasks ? "" : "ml-auto"}`}
>
Delete
</button>
diff --git a/makima/frontend/src/hooks/useDirectives.ts b/makima/frontend/src/hooks/useDirectives.ts
index f5f2b36..e67733c 100644
--- a/makima/frontend/src/hooks/useDirectives.ts
+++ b/makima/frontend/src/hooks/useDirectives.ts
@@ -19,6 +19,7 @@ import {
failDirectiveStep,
skipDirectiveStep,
updateDirectiveGoal,
+ cleanupDirectiveTasks,
} from "../lib/api";
export function useDirectives() {
@@ -80,16 +81,18 @@ export function useDirective(id: string | undefined) {
refresh();
}, [refresh]);
- // Auto-poll while directive is active or has an orchestrator task
+ // Auto-poll while directive is active, has an orchestrator task, or has a completion task
useEffect(() => {
if (!directive) return;
const needsPolling =
- directive.status === "active" || directive.orchestratorTaskId != null;
+ directive.status === "active" ||
+ directive.orchestratorTaskId != null ||
+ directive.completionTaskId != null;
if (!needsPolling) return;
const interval = setInterval(refresh, 5000);
return () => clearInterval(interval);
- }, [directive?.status, directive?.orchestratorTaskId, refresh]);
+ }, [directive?.status, directive?.orchestratorTaskId, directive?.completionTaskId, refresh]);
const update = useCallback(async (req: UpdateDirectiveRequest) => {
if (!id) return;
@@ -151,11 +154,17 @@ export function useDirective(id: string | undefined) {
await refresh();
}, [id, refresh]);
+ const cleanupTasks = useCallback(async () => {
+ if (!id) return;
+ await cleanupDirectiveTasks(id);
+ await refresh();
+ }, [id, refresh]);
+
return {
directive, loading, error, refresh,
update, addStep, removeStep,
start, pause, advance,
completeStep, failStep, skipStep,
- updateGoal,
+ updateGoal, cleanupTasks,
};
}
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts
index 552829a..9d9cb1c 100644
--- a/makima/frontend/src/lib/api.ts
+++ b/makima/frontend/src/lib/api.ts
@@ -3238,6 +3238,14 @@ export async function updateDirectiveGoal(id: string, goal: string): Promise<Dir
return res.json();
}
+export async function cleanupDirectiveTasks(id: string): Promise<{ deleted: number }> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${id}/cleanup-tasks`, {
+ method: "POST",
+ });
+ if (!res.ok) throw new Error(`Failed to cleanup tasks: ${res.statusText}`);
+ return res.json();
+}
+
// =============================================================================
// Directive Memory Types & API
// =============================================================================
diff --git a/makima/frontend/src/routes/directives.tsx b/makima/frontend/src/routes/directives.tsx
index bf6955b..ca4437c 100644
--- a/makima/frontend/src/routes/directives.tsx
+++ b/makima/frontend/src/routes/directives.tsx
@@ -12,7 +12,7 @@ export default function DirectivesPage() {
const navigate = useNavigate();
const { id: selectedId } = useParams<{ id: string }>();
const { directives, loading: listLoading, create, remove } = useDirectives();
- const { directive, refresh: refreshDetail, start, pause, advance, completeStep, failStep, skipStep, updateGoal } = useDirective(selectedId);
+ const { directive, refresh: refreshDetail, start, pause, advance, completeStep, failStep, skipStep, updateGoal, cleanupTasks } = useDirective(selectedId);
const [showCreate, setShowCreate] = useState(false);
const [newTitle, setNewTitle] = useState("");
@@ -209,6 +209,7 @@ export default function DirectivesPage() {
onUpdateGoal={updateGoal}
onDelete={handleDelete}
onRefresh={refreshDetail}
+ onCleanupTasks={cleanupTasks}
/>
) : (
<div className="flex-1 flex items-center justify-center h-full">
diff --git a/makima/src/server/handlers/directives.rs b/makima/src/server/handlers/directives.rs
index 585899e..9314031 100644
--- a/makima/src/server/handlers/directives.rs
+++ b/makima/src/server/handlers/directives.rs
@@ -1231,6 +1231,7 @@ pub async fn clear_memories(
// Task Cleanup
// =============================================================================
+
/// Clean up terminal tasks associated with a directive.
#[utoipa::path(
post,
diff --git a/makima/src/server/mod.rs b/makima/src/server/mod.rs
index b380508..7110ef8 100644
--- a/makima/src/server/mod.rs
+++ b/makima/src/server/mod.rs
@@ -237,6 +237,7 @@ pub fn make_router(state: SharedState) -> Router {
.route("/directives/{id}/steps/{step_id}/fail", post(directives::fail_step))
.route("/directives/{id}/steps/{step_id}/skip", post(directives::skip_step))
.route("/directives/{id}/goal", put(directives::update_goal))
+ .route("/directives/{id}/cleanup-tasks", post(directives::cleanup_tasks))
// Directive memory endpoints
.route("/directives/{id}/memories", get(directives::list_memories).post(directives::set_memory).delete(directives::clear_memories))
.route("/directives/{id}/memories/batch", post(directives::batch_set_memories))
diff --git a/makima/src/server/openapi.rs b/makima/src/server/openapi.rs
index f049759..f28b105 100644
--- a/makima/src/server/openapi.rs
+++ b/makima/src/server/openapi.rs
@@ -8,6 +8,7 @@ use crate::db::models::{
ChangePhaseRequest,
Contract, ContractChatHistoryResponse, ContractChatMessageRecord, ContractEvent,
ContractListResponse, ContractRepository, ContractSummary, ContractWithRelations,
+ CleanupTasksResponse,
CreateContractRequest, CreateDirectiveRequest, CreateDirectiveStepRequest, CreateFileRequest,
CreateManagedRepositoryRequest, CreateTaskRequest, Daemon, DaemonDirectoriesResponse,
DaemonDirectory, DaemonListResponse, Directive, DirectiveListResponse, DirectiveMemory,
@@ -123,6 +124,7 @@ use crate::server::messages::{ApiError, AudioEncoding, StartMessage, StopMessage
directives::fail_step,
directives::skip_step,
directives::update_goal,
+ directives::cleanup_tasks,
// Directive memory endpoints
directives::list_memories,
directives::get_memory,
@@ -226,6 +228,7 @@ use crate::server::messages::{ApiError, AudioEncoding, StartMessage, StopMessage
UpdateGoalRequest,
CreateDirectiveStepRequest,
UpdateDirectiveStepRequest,
+ CleanupTasksResponse,
DirectiveMemory,
DirectiveMemoryListResponse,
SetDirectiveMemoryRequest,