添加或更新子任务 todo(原子操作) status: not-started / in-progress / completed detail: 可选,该子任务的详细产出/说明(Markdown 格式) 约束:同一时刻最多只有 1 个 in-progress 状态的 todo。
(task_id, todo_id, title, status='not-started', detail='')
| 646 | _append_audit(task_id, _infer_agent_id_from_runtime(), 'progress', None, None, clean) |
| 647 | |
| 648 | def cmd_todo(task_id, todo_id, title, status='not-started', detail=''): |
| 649 | """添加或更新子任务 todo(原子操作) |
| 650 | |
| 651 | status: not-started / in-progress / completed |
| 652 | detail: 可选,该子任务的详细产出/说明(Markdown 格式) |
| 653 | |
| 654 | 约束:同一时刻最多只有 1 个 in-progress 状态的 todo。 |
| 655 | """ |
| 656 | # 校验 status 值 |
| 657 | if status not in ('not-started', 'in-progress', 'completed'): |
| 658 | status = 'not-started' |
| 659 | result_info = [0, 0] |
| 660 | rejected = [False] |
| 661 | ready_to_close = [False] |
| 662 | def modifier(tasks): |
| 663 | t = find_task(tasks, task_id) |
| 664 | if not t: |
| 665 | log.error(f'任务 {task_id} 不存在') |
| 666 | return tasks |
| 667 | if 'todos' not in t: |
| 668 | t['todos'] = [] |
| 669 | |
| 670 | # 单一 in-progress 约束 |
| 671 | if status == 'in-progress': |
| 672 | existing_ip = [td for td in t['todos'] |
| 673 | if td.get('status') == 'in-progress' and str(td.get('id')) != str(todo_id)] |
| 674 | if existing_ip: |
| 675 | log.warning( |
| 676 | f'⚠️ todo #{existing_ip[0]["id"]} 正在执行中,' |
| 677 | f'请先完成或取消后再开始 #{todo_id}' |
| 678 | ) |
| 679 | rejected[0] = True |
| 680 | return tasks |
| 681 | |
| 682 | existing = next((td for td in t['todos'] if str(td.get('id')) == str(todo_id)), None) |
| 683 | if existing: |
| 684 | existing['status'] = status |
| 685 | if title: |
| 686 | existing['title'] = title |
| 687 | if detail: |
| 688 | existing['detail'] = detail |
| 689 | else: |
| 690 | item = {'id': todo_id, 'title': title, 'status': status} |
| 691 | if detail: |
| 692 | item['detail'] = detail |
| 693 | t['todos'].append(item) |
| 694 | t['updatedAt'] = now_iso() |
| 695 | result_info[0] = sum(1 for td in t['todos'] if td.get('status') == 'completed') |
| 696 | result_info[1] = len(t['todos']) |
| 697 | # 所有 todo 完成 → 标记 ready_to_close |
| 698 | if result_info[1] > 0 and result_info[0] == result_info[1]: |
| 699 | t['ready_to_close'] = True |
| 700 | ready_to_close[0] = True |
| 701 | return tasks |
| 702 | atomic_json_update(TASKS_FILE, modifier, []) |
| 703 | _trigger_refresh() |
| 704 | if rejected[0]: |
| 705 | log.info(f'❌ {task_id} todo #{todo_id} → in-progress 被拒(已有进行中的 todo)') |
no test coverage detected