Merge remote-tracking branch 'origin/master' into feat/task-decomposition-enforce-floor

This commit is contained in:
Renn F
2026-06-14 08:12:13 +02:00
16 changed files with 1290 additions and 40 deletions
+6 -3
View File
@@ -84,13 +84,16 @@ api.interceptors.response.use(
};
useRateLimitStore.getState().hitRateLimit(hitEvent);
// Track retry count; retry the request until exhausted, then toast
// Track retry count; retry the request (after backoff delay) until exhausted, then toast
const retryCount = (error.config?._retryCount ?? 0) + 1;
if (error.config) {
error.config._retryCount = retryCount;
if (retryCount < RATE_LIMIT_MAX_RETRIES) {
// Retry the request — interceptor re-runs on each subsequent 429
return api(error.config);
// Wait retryAfterSeconds before retrying — interceptor re-runs on each subsequent 429
const delayMs = safeRetryAfter * 1000;
return new Promise<void>((resolve) => setTimeout(resolve, delayMs)).then(
() => api(error.config!)
);
}
}
// Retries exhausted — notify the user via Sonner toast
+20
View File
@@ -399,10 +399,22 @@ export const tasksApi = {
if (isMockMode()) {
return mockTasks.filter((t) => t.parent_task_id === taskId);
}
// Hits GET /tasks/{id}/subtasks
const { data } = await api.get<Task[]>("/tasks/" + taskId + "/subtasks");
return data;
},
// Returns the valid next statuses for a task from GET /tasks/{id}/valid-transitions
getValidTransitions: async (taskId: string): Promise<TaskStatus[]> => {
if (isMockMode()) {
return [];
}
const { data } = await api.get<{ valid_statuses: TaskStatus[] }>(
"/tasks/" + taskId + "/valid-transitions"
);
return data.valid_statuses;
},
// =========================================================================
// STATS
// =========================================================================
@@ -593,6 +605,14 @@ export const tasksApi = {
return data;
},
// CEO gate #2: approve the completed work and merge the PR.
// No request body — the backend endpoint accepts no notes parameter.
// May throw HTTP 400 with detail starting 'NO_PR' or 'Merge failed'.
approveAndMerge: async (taskId: string): Promise<Task> => {
const { data } = await api.post<Task>("/tasks/" + taskId + "/approve-and-merge");
return data;
},
// CEO rejects a task (sends back for revision)
ceoReject: async (taskId: string, notes: string): Promise<Task> => {
if (isMockMode()) {