Compare commits
10 Commits
d45accbc90
...
ce99c18cbd
| Author | SHA1 | Date | |
|---|---|---|---|
| ce99c18cbd | |||
| 048a07a049 | |||
| 11a04f4147 | |||
| 5259e2fc91 | |||
| c6d0bc8c8d | |||
| 265839a55b | |||
| 2ff5a8beee | |||
| 8b514e0d4d | |||
| 094a6c3c22 | |||
| 97b5bd953d |
@@ -66,16 +66,16 @@ This file tracks all major tracks for the project. Each track has its own detail
|
||||
|
||||
### Manual UX Controls
|
||||
|
||||
14. [~] **Track: Manual Ticket Queue Management**
|
||||
14. [x] **Track: Manual Ticket Queue Management**
|
||||
*Link: [./tracks/ticket_queue_mgmt_20260306/](./tracks/ticket_queue_mgmt_20260306/)*
|
||||
|
||||
15. [ ] **Track: Kill/Abort Running Workers**
|
||||
15. [x] **Track: Kill/Abort Running Workers**
|
||||
*Link: [./tracks/kill_abort_workers_20260306/](./tracks/kill_abort_workers_20260306/)*
|
||||
|
||||
16. [ ] **Track: Manual Block/Unblock Control**
|
||||
16. [x] **Track: Manual Block/Unblock Control**
|
||||
*Link: [./tracks/manual_block_control_20260306/](./tracks/manual_block_control_20260306/)*
|
||||
|
||||
17. [ ] **Track: Pipeline Pause/Resume**
|
||||
17. [~] **Track: Pipeline Pause/Resume**
|
||||
*Link: [./tracks/pipeline_pause_resume_20260306/](./tracks/pipeline_pause_resume_20260306/)*
|
||||
|
||||
18. [ ] **Track: Per-Ticket Model Override**
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
## Phase 1: Add Manual Block Fields
|
||||
Focus: Add manual_block flag to Ticket
|
||||
|
||||
- [ ] Task 1.1: Initialize MMA Environment
|
||||
- [ ] Task 1.2: Add manual_block field to Ticket
|
||||
- [x] Task 1.1: Initialize MMA Environment
|
||||
- [x] Task 1.2: Add manual_block field to Ticket (094a6c3)
|
||||
- WHERE: `src/models.py` `Ticket` dataclass
|
||||
- WHAT: Add `manual_block: bool = False`
|
||||
- HOW:
|
||||
@@ -14,7 +14,7 @@ Focus: Add manual_block flag to Ticket
|
||||
manual_block: bool = False
|
||||
```
|
||||
|
||||
- [ ] Task 1.3: Add mark_manual_block method
|
||||
- [x] Task 1.3: Add mark_manual_block method (094a6c3)
|
||||
- WHERE: `src/models.py` `Ticket`
|
||||
- WHAT: Method to set manual block with reason
|
||||
- HOW:
|
||||
@@ -28,12 +28,12 @@ Focus: Add manual_block flag to Ticket
|
||||
## Phase 2: Block/Unblock UI
|
||||
Focus: Add block buttons to ticket display
|
||||
|
||||
- [ ] Task 2.1: Add block button
|
||||
- [x] Task 2.1: Add block button (2ff5a8b)
|
||||
- WHERE: `src/gui_2.py` ticket rendering
|
||||
- WHAT: Button to block with reason input
|
||||
- HOW: Modal with text input for reason
|
||||
|
||||
- [ ] Task 2.2: Add unblock button
|
||||
- [x] Task 2.2: Add unblock button (2ff5a8b)
|
||||
- WHERE: `src/gui_2.py` ticket rendering
|
||||
- WHAT: Button to clear manual block
|
||||
- HOW:
|
||||
@@ -48,11 +48,11 @@ Focus: Add block buttons to ticket display
|
||||
## Phase 3: Cascade Integration
|
||||
Focus: Trigger cascade on block/unblock
|
||||
|
||||
- [ ] Task 3.1: Call cascade_blocks after manual block
|
||||
- [x] Task 3.1: Call cascade_blocks after manual block (c6d0bc8)
|
||||
- WHERE: `src/gui_2.py` or `src/multi_agent_conductor.py`
|
||||
- WHAT: Update downstream tickets
|
||||
- HOW: `self.dag.cascade_blocks()`
|
||||
|
||||
## Phase 4: Testing
|
||||
- [ ] Task 4.1: Write unit tests
|
||||
- [ ] Task 4.2: Conductor - Phase Verification
|
||||
- [x] Task 4.1: Write unit tests
|
||||
- [x] Task 4.2: Conductor - Phase Verification
|
||||
|
||||
47
src/gui_2.py
47
src/gui_2.py
@@ -1954,6 +1954,47 @@ class App:
|
||||
if self.controller and self.controller.engine:
|
||||
self.controller.engine.kill_worker(ticket_id)
|
||||
|
||||
def _cb_block_ticket(self, ticket_id: str) -> None:
|
||||
t = next((t for t in self.active_tickets if str(t.get('id', '')) == ticket_id), None)
|
||||
if t:
|
||||
t['status'] = 'blocked'
|
||||
t['manual_block'] = True
|
||||
t['blocked_reason'] = '[MANUAL] User blocked'
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
for t in self.active_tickets:
|
||||
if t.get('status') == 'todo':
|
||||
for dep_id in t.get('depends_on', []):
|
||||
dep = next((x for x in self.active_tickets if str(x.get('id', '')) == dep_id), None)
|
||||
if dep and dep.get('status') == 'blocked':
|
||||
t['status'] = 'blocked'
|
||||
changed = True
|
||||
break
|
||||
self._push_mma_state_update()
|
||||
|
||||
def _cb_unblock_ticket(self, ticket_id: str) -> None:
|
||||
t = next((t for t in self.active_tickets if str(t.get('id', '')) == ticket_id), None)
|
||||
if t and t.get('manual_block', False):
|
||||
t['status'] = 'todo'
|
||||
t['manual_block'] = False
|
||||
t['blocked_reason'] = None
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
for t in self.active_tickets:
|
||||
if t.get('status') == 'blocked' and not t.get('manual_block', False):
|
||||
can_run = True
|
||||
for dep_id in t.get('depends_on', []):
|
||||
dep = next((x for x in self.active_tickets if str(x.get('id', '')) == dep_id), None)
|
||||
if dep and dep.get('status') != 'completed':
|
||||
can_run = False
|
||||
break
|
||||
if can_run:
|
||||
t['status'] = 'todo'
|
||||
changed = True
|
||||
self._push_mma_state_update()
|
||||
|
||||
def _reorder_ticket(self, src_idx: int, dst_idx: int) -> None:
|
||||
if src_idx == dst_idx: return
|
||||
new_tickets = list(self.active_tickets)
|
||||
@@ -2072,6 +2113,12 @@ class App:
|
||||
if status == 'in_progress':
|
||||
if imgui.button(f"Kill##{tid}"):
|
||||
self._cb_kill_ticket(tid)
|
||||
elif status == 'todo':
|
||||
if imgui.button(f"Block##{tid}"):
|
||||
self._cb_block_ticket(tid)
|
||||
elif status == 'blocked' and t.get('manual_block', False):
|
||||
if imgui.button(f"Unblock##{tid}"):
|
||||
self._cb_unblock_ticket(tid)
|
||||
|
||||
imgui.end_table()
|
||||
|
||||
|
||||
@@ -74,11 +74,23 @@ class Ticket:
|
||||
blocked_reason: Optional[str] = None
|
||||
step_mode: bool = False
|
||||
retry_count: int = 0
|
||||
manual_block: bool = False
|
||||
|
||||
def mark_blocked(self, reason: str) -> None:
|
||||
self.status = "blocked"
|
||||
self.blocked_reason = reason
|
||||
|
||||
def mark_manual_block(self, reason: str) -> None:
|
||||
self.status = "blocked"
|
||||
self.blocked_reason = f"[MANUAL] {reason}"
|
||||
self.manual_block = True
|
||||
|
||||
def clear_manual_block(self) -> None:
|
||||
if self.manual_block:
|
||||
self.status = "todo"
|
||||
self.blocked_reason = None
|
||||
self.manual_block = False
|
||||
|
||||
def mark_complete(self) -> None:
|
||||
self.status = "completed"
|
||||
|
||||
@@ -99,6 +111,7 @@ class Ticket:
|
||||
"blocked_reason": self.blocked_reason,
|
||||
"step_mode": self.step_mode,
|
||||
"retry_count": self.retry_count,
|
||||
"manual_block": self.manual_block,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -116,6 +129,7 @@ class Ticket:
|
||||
blocked_reason=data.get("blocked_reason"),
|
||||
step_mode=data.get("step_mode", False),
|
||||
retry_count=data.get("retry_count", 0),
|
||||
manual_block=data.get("manual_block", False),
|
||||
)
|
||||
|
||||
|
||||
|
||||
23
tests/test_manual_block.py
Normal file
23
tests/test_manual_block.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import pytest
|
||||
from src.models import Ticket
|
||||
|
||||
def test_ticket_has_manual_block_field():
|
||||
t = Ticket(id="T-001", description="Test")
|
||||
assert hasattr(t, 'manual_block'), "Ticket must have manual_block field"
|
||||
assert t.manual_block == False, "manual_block should default to False"
|
||||
|
||||
def test_mark_manual_block_method():
|
||||
t = Ticket(id="T-001", description="Test")
|
||||
t.mark_manual_block("Test reason")
|
||||
assert t.status == "blocked", "Status should be blocked"
|
||||
assert t.manual_block == True, "manual_block should be True"
|
||||
assert "[MANUAL]" in t.blocked_reason, "blocked_reason should contain [MANUAL]"
|
||||
|
||||
def test_clear_manual_block_method():
|
||||
t = Ticket(id="T-001", description="Test")
|
||||
t.mark_manual_block("Test reason")
|
||||
assert t.manual_block == True
|
||||
t.clear_manual_block()
|
||||
assert t.status == "todo", "Status should be restored to todo"
|
||||
assert t.manual_block == False, "manual_block should be False"
|
||||
assert t.blocked_reason is None, "blocked_reason should be cleared"
|
||||
Reference in New Issue
Block a user