From 5ffbc0f58e48c74dc0fdf8cc391665d42da694c8 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Fri, 16 Oct 2026 17:32:26 +0800 Subject: [PATCH 1/5] fix(todos): automate owner lifecycle and administrative edits Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../personal-workspace/context-drawer.tsx | 36 ++--- .../src/features/personal-workspace/i18n.tsx | 4 + loopx/chat_todo_actions.py | 11 +- loopx/cli_commands/task_lease.py | 16 +++ loopx/cli_commands/todo_registration.py | 2 +- .../coordination/task_lease_proof.ts | 52 +++++++- .../coordination/todo_blocked_lifecycle.ts | 21 +-- .../coordination/todo_deferred_lifecycle.ts | 10 +- .../coordination/todo_update_admission.ts | 55 +++++++- .../coordination/todo_update_intent.ts | 10 +- loopx/control_plane/todos/markdown.py | 10 +- loopx/control_plane/todos/provider_update.py | 10 ++ .../test_native_todo_planning_update.py | 2 +- .../test_task_lease_argument_recovery.py | 3 + .../test_todo_administrative_journey.py | 108 +++++++++++++++ .../test_todo_update_lease_recovery.py | 10 +- .../authority_store_conformance.ts | 7 +- .../blocked_hard_lease_lifecycle.test.ts | 7 +- .../native_planning_update_conformance.ts | 2 +- .../todo_administrative_edits.test.ts | 124 ++++++++++++++++++ tests/control_plane_ts/todo_update.test.ts | 21 +-- 21 files changed, 445 insertions(+), 76 deletions(-) create mode 100644 tests/control_plane/test_todo_administrative_journey.py create mode 100644 tests/control_plane_ts/todo_administrative_edits.test.ts diff --git a/apps/presentation/dashboard/src/features/personal-workspace/context-drawer.tsx b/apps/presentation/dashboard/src/features/personal-workspace/context-drawer.tsx index 506cc30ba2..4ced7c7da0 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/context-drawer.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/context-drawer.tsx @@ -68,7 +68,7 @@ const focusableSelector = [ "[tabindex]:not([tabindex='-1'])", ].join(","); -type TodoOperation = "block" | "complete" | "defer" | "successor_create"; +type TodoOperation = "block" | "resume" | "complete" | "defer" | "successor_create"; type GoalSubagentPreview = { codexHostCapacity?: WorkspaceGoalSubagentConfiguration["codexHostCapacity"]; @@ -422,7 +422,8 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention normalizedParameters: { agent_id: todo.claimedBy ?? todoAgentId, goal_id: todo.goalId, - operation, + operation: operation === "resume" ? "edit" : operation, + ...(operation === "resume" ? {status: "open"} : {}), ...(operation === "block" ? { note: t("drawer.taskSuccessorNote") } : {}), ...(operation === "defer" && resumeWhen ? { resume_when: resumeWhen } : {}), todo_id: todo.todoId, @@ -432,6 +433,8 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention }); } + const todoAwaitingResume = selection.kind === "todo" && ["blocked", "deferred"].includes(selection.item.status ?? ""); + const todoCompleted = selection.kind === "todo" && (selection.item.status === "done" || selection.item.status == null && selection.item.done); const currentSubagentConfiguration: WorkspaceGoalSubagentConfiguration = selection.kind === "goal" ? verifiedSubagentConfiguration ?? selection.item.subagentExecution @@ -642,14 +645,14 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention <>
- - {selection.item.done ? t("drawer.taskStatusCompleted") : selection.item.retryReadback ? t("drawer.resumePending") : selection.item.status === "deferred" ? t("drawer.taskStatusDeferred") : selection.item.status === "blocked" ? t("drawer.taskStatusBlocked") : t("drawer.taskStatusOpen")} + + {selection.item.status === "done" ? t("drawer.taskStatusCompleted") : selection.item.retryReadback ? t("drawer.resumePending") : selection.item.status === "deferred" ? t("drawer.taskStatusDeferred") : selection.item.status === "blocked" ? t("drawer.taskStatusBlocked") : t("drawer.taskStatusOpen")} {selection.item.priority ? {selection.item.priority} : null} {selection.item.taskClass === "advancement_task" ? t("drawer.taskAdvancement") : selection.item.taskClass ?? t("drawer.taskOrdinary")}
- {selection.item.status === "blocked" && !selection.item.done ?
+ {selection.item.status === "blocked" ?

{t("drawer.blockedReason")}

{selection.item.blockedReason ?? t("drawer.blockedReasonMissing")}

: null} @@ -659,7 +662,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
Goal
{selection.item.goalTitle}
{t("common.owner")}
{selection.item.claimedBy ? selection.item.ownerLabel ?? selection.item.claimedBy : t("drawer.notAssigned")}
-
{t("common.status")}
{selection.item.done ? t("drawer.taskStatusCompleted") : selection.item.retryReadback ? t("drawer.resumePending") : selection.item.status === "deferred" ? t("drawer.taskStatusDeferred") : selection.item.status === "blocked" ? t("drawer.taskStatusBlocked") : t("drawer.taskStatusOpen")}
+
{t("common.status")}
{selection.item.status === "done" ? t("drawer.taskStatusCompleted") : selection.item.retryReadback ? t("drawer.resumePending") : selection.item.status === "deferred" ? t("drawer.taskStatusDeferred") : selection.item.status === "blocked" ? t("drawer.taskStatusBlocked") : t("drawer.taskStatusOpen")}
{t("drawer.priority")}
{selection.item.priority ?? t("drawer.notSet")}
{selection.item.completedAt ?
{t("drawer.completedAt")}
: null} {selection.item.dependencies ?
{t("drawer.dependencies")}
{selection.item.dependencies.join(" · ") || t("common.none")}
: null} @@ -676,10 +679,10 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
{t("drawer.validationDigest")}
{selection.item.validationDigest}
{selection.item.validationRevisionActor ?
{t("drawer.validationRevisionActor")}
{selection.item.validationRevisionActor}
: null} : null} -
{t("drawer.nextTransition")}
{selection.item.nextTransition ?? (selection.item.done ? t("drawer.taskNextCompleted") : selection.item.resumeInvalidReason === "dependency_cycle" ? t("drawer.taskNextCycle") : selection.item.retryReadback ? t("drawer.taskNextDeferred") : selection.item.resumeReady ? t("drawer.taskNextResumeReady") : selection.item.status === "deferred" ? t("drawer.taskNextDeferred") : selection.item.status === "blocked" ? t("drawer.taskNextBlocked") : t("drawer.taskNextOpen"))}
+
{t("drawer.nextTransition")}
{selection.item.nextTransition ?? (selection.item.status === "done" ? t("drawer.taskNextCompleted") : selection.item.resumeInvalidReason === "dependency_cycle" ? t("drawer.taskNextCycle") : selection.item.retryReadback ? t("drawer.taskNextDeferred") : selection.item.resumeReady ? t("drawer.taskNextResumeReady") : selection.item.status === "deferred" ? t("drawer.taskNextDeferred") : selection.item.status === "blocked" ? t("drawer.taskNextBlocked") : t("drawer.taskNextOpen"))}
- {!readOnly && !selection.item.done ?
+ {!readOnly && selection.item.status !== "done" ?
{t("drawer.taskManage")}
@@ -706,12 +709,13 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention actionKind: "todo.update", context: {goal_id: selection.item.goalId, kind: "todo", todo_id: selection.item.todoId}, idempotencyKey: `workspace-todo-${selection.item.todoId}-priority-${todoPriority}-${Date.now().toString(36)}`, - normalizedParameters: {goal_id: selection.item.goalId, todo_id: selection.item.todoId, operation: "edit", + normalizedParameters: {goal_id: selection.item.goalId, todo_id: selection.item.todoId, + agent_id: selection.item.claimedBy ?? todoAgentId, operation: "edit", ...(todoPriority === "clear" ? {clear_priority: true} : {priority: todoPriority})}, summary: `${t("drawer.taskPriority")}: ${todoPriority === "clear" ? t("drawer.taskPriorityClear") : todoPriority}`, })} type="button">{t("timeline.review")} - {t("drawer.taskDeferUntil")} + {!todoAwaitingResume ? <>{t("drawer.taskDeferUntil")} + : null}
- {todoTransitions.map((transition) => ( + {todoTransitions.filter(transition => !todoAwaitingResume || transition.operation !== "block").map((transition) => ( ))}
- -
+ + {todoAwaitingResume ? {t("drawer.taskResumeHint")} :
{t("drawer.taskCompletionProof")}