chore(scripts): removed todo workflow from scripts/rate-edit-tool
- Removed todo workflow event handling from `scripts/rate-edit-tool.py` by deleting `Todo*` event plumbing and ops. - Pruned Go/Markdown fixture coverage from prompts and fixture maps, keeping TypeScript/Rust/Python only. - Dropped todo-based completion gating in `is_effectively_complete` and removed todo columns from progress output.
This commit is contained in:
+5
-468
@@ -11,7 +11,7 @@ import tempfile
|
||||
import textwrap
|
||||
import threading
|
||||
import time
|
||||
from dataclasses import asdict, dataclass, field, is_dataclass
|
||||
from dataclasses import asdict, dataclass, is_dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, TextIO
|
||||
|
||||
@@ -36,10 +36,6 @@ from omp_rpc import ( # noqa: E402
|
||||
RpcError,
|
||||
RpcNotification,
|
||||
RpcProcessExitError,
|
||||
TodoAutoClearEvent,
|
||||
TodoItem,
|
||||
TodoPhase,
|
||||
TodoReminderEvent,
|
||||
ToolExecutionEndEvent,
|
||||
ToolExecutionStartEvent,
|
||||
ToolExecutionUpdateEvent,
|
||||
@@ -69,7 +65,7 @@ PROMPT = textwrap.dedent(
|
||||
|
||||
2. Exercise every supported edit operation against each fixture. Perform the full range of supported mutations — replacing content, inserting above/below a target, deleting, substring rewrites, append/prepend — using only what the schema actually exposes.
|
||||
|
||||
3. Push into awkward cases. Probe boundary conditions: first/last line of file, indentation-sensitive blocks (Python), nested members (decorators, methods, enum variants, Go interfaces/generics), markdown tables, fenced code blocks. Note whether error messages were clear and actionable when something went wrong.
|
||||
3. Push into awkward cases. Probe boundary conditions: first/last line of file, indentation-sensitive blocks (Python), nested members (decorators, methods, enum variants, traits and generics). Note whether error messages were clear and actionable when something went wrong.
|
||||
|
||||
4. Verify after edits. Re-read each file after meaningful edits and confirm only the intended lines changed.
|
||||
|
||||
@@ -142,13 +138,6 @@ ORACLE_REVIEW_PROMPT = textwrap.dedent(
|
||||
"""
|
||||
).strip()
|
||||
|
||||
TODOS = [
|
||||
"Map the edit tool surface area across every fixture: main.ts, main.rs, main.go, main.py, main.md.",
|
||||
"Exercise every supported edit operation on each fixture with concrete before/after verification.",
|
||||
"Probe awkward boundary cases: decorators, docstrings, enum variants, Go interfaces/generics, indentation-sensitive blocks, markdown tables, fenced code blocks.",
|
||||
"Summarize what was awkward, impossible, ambiguous, or under-documented with concrete examples spanning all fixtures.",
|
||||
]
|
||||
|
||||
TS_FIXTURE = (
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
@@ -486,134 +475,6 @@ RUST_FIXTURE = (
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
GO_FIXTURE = (
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// LogLevel names the severity of a structured log entry.
|
||||
type LogLevel int
|
||||
|
||||
const (
|
||||
DebugLevel LogLevel = iota
|
||||
InfoLevel
|
||||
WarnLevel
|
||||
ErrorLevel
|
||||
)
|
||||
|
||||
// String returns the canonical upper-case name for a LogLevel.
|
||||
func (l LogLevel) String() string {
|
||||
switch l {
|
||||
case DebugLevel:
|
||||
return "DEBUG"
|
||||
case InfoLevel:
|
||||
return "INFO"
|
||||
case WarnLevel:
|
||||
return "WARN"
|
||||
case ErrorLevel:
|
||||
return "ERROR"
|
||||
default:
|
||||
return fmt.Sprintf("LogLevel(%d)", int(l))
|
||||
}
|
||||
}
|
||||
|
||||
// Entry is a single structured log line.
|
||||
type Entry struct {
|
||||
Level LogLevel `json:"level"`
|
||||
Message string `json:"message"`
|
||||
Fields map[string]string `json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
// Sink receives structured log entries.
|
||||
type Sink interface {
|
||||
Write(ctx context.Context, entry Entry) error
|
||||
}
|
||||
|
||||
// MemorySink stores entries in memory, useful for tests.
|
||||
type MemorySink struct {
|
||||
mu sync.Mutex
|
||||
entries []Entry
|
||||
}
|
||||
|
||||
// Write appends the entry to the in-memory buffer.
|
||||
func (s *MemorySink) Write(_ context.Context, entry Entry) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.entries = append(s.entries, entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Snapshot returns a copy of the buffered entries.
|
||||
func (s *MemorySink) Snapshot() []Entry {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
out := make([]Entry, len(s.entries))
|
||||
copy(out, s.entries)
|
||||
return out
|
||||
}
|
||||
|
||||
// Filter returns items for which keep returns true.
|
||||
func Filter[T any](items []T, keep func(T) bool) []T {
|
||||
out := make([]T, 0, len(items))
|
||||
for _, item := range items {
|
||||
if keep(item) {
|
||||
out = append(out, item)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Router fans entries out to every configured sink.
|
||||
type Router struct {
|
||||
sinks []Sink
|
||||
}
|
||||
|
||||
// NewRouter constructs a router with the given sinks.
|
||||
func NewRouter(sinks ...Sink) *Router {
|
||||
return &Router{sinks: sinks}
|
||||
}
|
||||
|
||||
// Dispatch writes the entry to every sink, joining per-sink errors.
|
||||
func (r *Router) Dispatch(ctx context.Context, entry Entry) error {
|
||||
var errs []error
|
||||
for _, sink := range r.sinks {
|
||||
if err := sink.Write(ctx, entry); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
messages := make([]string, 0, len(errs))
|
||||
for _, err := range errs {
|
||||
messages = append(messages, err.Error())
|
||||
}
|
||||
return errors.New(strings.Join(messages, "; "))
|
||||
}
|
||||
|
||||
func main() {
|
||||
sink := &MemorySink{}
|
||||
router := NewRouter(sink)
|
||||
_ = router.Dispatch(context.Background(), Entry{
|
||||
Level: InfoLevel,
|
||||
Message: "router ready",
|
||||
})
|
||||
fmt.Println(sink.Snapshot())
|
||||
}
|
||||
"""
|
||||
).strip()
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
|
||||
PYTHON_FIXTURE = (
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
@@ -695,90 +556,29 @@ PYTHON_FIXTURE = (
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
MARKDOWN_FIXTURE = (
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
---
|
||||
title: Tooling Evaluation Notes
|
||||
owner: Fixtures Team
|
||||
---
|
||||
|
||||
# Fixture Overview
|
||||
|
||||
This markdown fixture is intentionally prose-heavy.
|
||||
It should help reveal how read and edit behave when the file is not routed through a language AST.
|
||||
|
||||
## Acceptance checklist
|
||||
|
||||
- [ ] Verify heading edits keep spacing intact.
|
||||
- [ ] Verify list edits preserve indentation.
|
||||
- [ ] Verify table edits do not destroy alignment beyond what the tool promises.
|
||||
- [ ] Verify fenced code blocks remain fenced after edits.
|
||||
|
||||
## Comparison table
|
||||
|
||||
| Surface | Expected stress |
|
||||
| --- | --- |
|
||||
| `main.ts` | Type/interface and class member edits |
|
||||
| `main.rs` | Enum and impl member edits |
|
||||
| `main.py` | Indentation-sensitive blocks |
|
||||
| `main.md` | Prose and block-level text edits |
|
||||
|
||||
## Embedded examples
|
||||
|
||||
```python
|
||||
def greet(name: str) -> str:
|
||||
return f"hello {name}"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"mode": "demo",
|
||||
"strict": true
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
1. Paragraph edits should preserve blank lines.
|
||||
2. List insertions should not collapse into one paragraph.
|
||||
3. Deleting this section should not damage the fenced blocks above.
|
||||
"""
|
||||
).strip()
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
REFERENCE_FILES = {
|
||||
"PROMPT.md": PROMPT + "\n",
|
||||
"main.ts": TS_FIXTURE,
|
||||
"main.rs": RUST_FIXTURE,
|
||||
"main.go": GO_FIXTURE,
|
||||
"main.py": PYTHON_FIXTURE,
|
||||
"main.md": MARKDOWN_FIXTURE,
|
||||
}
|
||||
|
||||
FIXTURES: tuple[tuple[str, str], ...] = (
|
||||
("typescript", "main.ts"),
|
||||
("rust", "main.rs"),
|
||||
("go", "main.go"),
|
||||
("python", "main.py"),
|
||||
("markdown", "main.md"),
|
||||
)
|
||||
|
||||
FIXTURE_DESCRIPTIONS: dict[str, str] = {
|
||||
"typescript": "TypeScript/AST",
|
||||
"rust": "Rust/AST",
|
||||
"go": "Go/AST",
|
||||
"python": "indentation-sensitive",
|
||||
"markdown": "prose/non-AST",
|
||||
}
|
||||
|
||||
WORKSPACE_FILES = {
|
||||
"main.ts": TS_FIXTURE,
|
||||
"main.rs": RUST_FIXTURE,
|
||||
"main.go": GO_FIXTURE,
|
||||
"main.py": PYTHON_FIXTURE,
|
||||
"main.md": MARKDOWN_FIXTURE,
|
||||
}
|
||||
|
||||
|
||||
@@ -813,9 +613,6 @@ class ModelResult:
|
||||
token_input: int | None
|
||||
token_output: int | None
|
||||
token_total: int | None
|
||||
todo_completed: int
|
||||
todo_total: int
|
||||
todo_current: str | None
|
||||
error: str | None
|
||||
session_state: dict[str, Any] | None
|
||||
|
||||
@@ -832,23 +629,17 @@ class ModelProgress:
|
||||
token_input: int | None = None
|
||||
token_output: int | None = None
|
||||
token_total: int | None = None
|
||||
todo_completed: int = 0
|
||||
todo_total: int = 0
|
||||
todo_current: str | None = None
|
||||
last_activity: str = "waiting"
|
||||
last_thinking: str | None = None
|
||||
last_text: str | None = None
|
||||
duration_seconds: float | None = None
|
||||
error: str | None = None
|
||||
todo_order: list[str] = field(default_factory=list)
|
||||
todo_items: dict[str, tuple[str, str]] = field(default_factory=dict)
|
||||
|
||||
|
||||
TOOL_WHITELIST = ("read", "edit", "todo_write", "report_tool_issue")
|
||||
TOOL_WHITELIST = ("read", "edit")
|
||||
MODEL_LABEL_WIDTH = 30
|
||||
STATUS_WIDTH = 7
|
||||
TOKENS_WIDTH = 9
|
||||
TODOS_WIDTH = 18
|
||||
ACTIVITY_WIDTH_FLOOR = 24
|
||||
THINKING_SNIPPET_LIMIT = 80
|
||||
TEXT_SNIPPET_LIMIT = 64
|
||||
@@ -907,175 +698,6 @@ def extract_usage_tokens(
|
||||
)
|
||||
|
||||
|
||||
def build_todo_state_from_phases(
|
||||
phases: tuple[TodoPhase, ...],
|
||||
) -> tuple[list[str], dict[str, tuple[str, str]]]:
|
||||
order: list[str] = []
|
||||
items: dict[str, tuple[str, str]] = {}
|
||||
for phase in phases:
|
||||
for task in phase.tasks:
|
||||
order.append(task.id)
|
||||
items[task.id] = (task.content, task.status)
|
||||
return order, items
|
||||
|
||||
|
||||
def seed_todo_state(todos: list[str]) -> tuple[list[str], dict[str, tuple[str, str]]]:
|
||||
order: list[str] = []
|
||||
items: dict[str, tuple[str, str]] = {}
|
||||
for index, content in enumerate(todos, start=1):
|
||||
task_id = f"task-{index}"
|
||||
order.append(task_id)
|
||||
items[task_id] = (content, "pending")
|
||||
return order, items
|
||||
|
||||
|
||||
def summarize_todo_state(
|
||||
order: list[str], items: dict[str, tuple[str, str]]
|
||||
) -> tuple[int, int, str | None]:
|
||||
if not order:
|
||||
return 0, 0, None
|
||||
completed = 0
|
||||
current: str | None = None
|
||||
for task_id in order:
|
||||
content, status = items.get(task_id, ("", "pending"))
|
||||
if status == "completed":
|
||||
completed += 1
|
||||
elif current is None and status == "in_progress":
|
||||
current = content
|
||||
if current is None:
|
||||
for task_id in order:
|
||||
content, status = items.get(task_id, ("", "pending"))
|
||||
if status == "pending":
|
||||
current = content
|
||||
break
|
||||
return completed, len(order), current
|
||||
|
||||
|
||||
def apply_todo_ops(progress: ModelProgress, args: Any) -> None:
|
||||
if not isinstance(args, dict):
|
||||
return
|
||||
raw_ops = args.get("ops")
|
||||
if not isinstance(raw_ops, list):
|
||||
return
|
||||
|
||||
for raw_op in raw_ops:
|
||||
if not isinstance(raw_op, dict):
|
||||
continue
|
||||
op = raw_op.get("op")
|
||||
if op == "replace":
|
||||
raw_phases = raw_op.get("phases")
|
||||
if isinstance(raw_phases, list):
|
||||
phases: list[TodoPhase] = []
|
||||
for phase_index, raw_phase in enumerate(raw_phases, start=1):
|
||||
if not isinstance(raw_phase, dict):
|
||||
continue
|
||||
raw_tasks = raw_phase.get("tasks")
|
||||
if not isinstance(raw_tasks, list):
|
||||
continue
|
||||
tasks: list[TodoItem] = []
|
||||
for task_index, raw_task in enumerate(raw_tasks, start=1):
|
||||
if not isinstance(raw_task, dict):
|
||||
continue
|
||||
content = raw_task.get("content")
|
||||
status = raw_task.get("status")
|
||||
if not isinstance(content, str) or not isinstance(status, str):
|
||||
continue
|
||||
task_id = raw_task.get("id")
|
||||
if not isinstance(task_id, str) or not task_id:
|
||||
task_id = f"task-{task_index}"
|
||||
tasks.append(
|
||||
TodoItem(
|
||||
id=task_id,
|
||||
content=content,
|
||||
status=status,
|
||||
notes=None,
|
||||
details=None,
|
||||
)
|
||||
)
|
||||
phase_id = raw_phase.get("id")
|
||||
name = raw_phase.get("name")
|
||||
if not isinstance(name, str) or not name:
|
||||
name = f"Phase {phase_index}"
|
||||
if not isinstance(phase_id, str) or not phase_id:
|
||||
phase_id = f"phase-{phase_index}"
|
||||
phases.append(TodoPhase(id=phase_id, name=name, tasks=tuple(tasks)))
|
||||
progress.todo_order, progress.todo_items = build_todo_state_from_phases(
|
||||
tuple(phases)
|
||||
)
|
||||
elif op == "update":
|
||||
task_id = raw_op.get("id")
|
||||
if not isinstance(task_id, str) or task_id not in progress.todo_items:
|
||||
continue
|
||||
content, status = progress.todo_items[task_id]
|
||||
next_content = (
|
||||
raw_op.get("content")
|
||||
if isinstance(raw_op.get("content"), str)
|
||||
else content
|
||||
)
|
||||
next_status = (
|
||||
raw_op.get("status")
|
||||
if isinstance(raw_op.get("status"), str)
|
||||
else status
|
||||
)
|
||||
progress.todo_items[task_id] = (next_content, next_status)
|
||||
elif op == "add_task":
|
||||
phase = raw_op.get("phase")
|
||||
task_payload = raw_op.get("task")
|
||||
if not isinstance(task_payload, dict):
|
||||
continue
|
||||
task_id = task_payload.get("id")
|
||||
if not isinstance(task_id, str) or not task_id:
|
||||
task_id = (
|
||||
raw_op.get("id")
|
||||
if isinstance(raw_op.get("id"), str)
|
||||
else f"task-{len(progress.todo_order) + 1}"
|
||||
)
|
||||
content = task_payload.get("content")
|
||||
status = task_payload.get("status")
|
||||
if not isinstance(content, str) or not isinstance(status, str):
|
||||
continue
|
||||
if task_id not in progress.todo_items:
|
||||
insert_after = (
|
||||
raw_op.get("after")
|
||||
if isinstance(raw_op.get("after"), str)
|
||||
else None
|
||||
)
|
||||
if insert_after in progress.todo_order:
|
||||
index = progress.todo_order.index(insert_after) + 1
|
||||
progress.todo_order.insert(index, task_id)
|
||||
else:
|
||||
progress.todo_order.append(task_id)
|
||||
progress.todo_items[task_id] = (content, status)
|
||||
elif op == "remove_task":
|
||||
task_id = raw_op.get("id")
|
||||
if not isinstance(task_id, str):
|
||||
continue
|
||||
progress.todo_items.pop(task_id, None)
|
||||
progress.todo_order = [
|
||||
candidate for candidate in progress.todo_order if candidate != task_id
|
||||
]
|
||||
elif op == "add_phase":
|
||||
raw_tasks = raw_op.get("tasks")
|
||||
if not isinstance(raw_tasks, list):
|
||||
continue
|
||||
for raw_task in raw_tasks:
|
||||
if not isinstance(raw_task, dict):
|
||||
continue
|
||||
content = raw_task.get("content")
|
||||
status = raw_task.get("status")
|
||||
if not isinstance(content, str) or not isinstance(status, str):
|
||||
continue
|
||||
task_id = raw_task.get("id")
|
||||
if not isinstance(task_id, str) or not task_id:
|
||||
task_id = f"task-{len(progress.todo_order) + 1}"
|
||||
progress.todo_order.append(task_id)
|
||||
progress.todo_items[task_id] = (content, status)
|
||||
|
||||
progress.todo_completed, progress.todo_total, progress.todo_current = (
|
||||
summarize_todo_state(progress.todo_order, progress.todo_items)
|
||||
)
|
||||
|
||||
|
||||
class ProgressPrinter:
|
||||
def __init__(
|
||||
self,
|
||||
@@ -1122,16 +744,6 @@ class ProgressPrinter:
|
||||
def mark_ready(self, model: str) -> None:
|
||||
self._mutate_model(model, status="ready", last_activity="rpc ready")
|
||||
|
||||
def seed_todos(self, model: str, todos: list[str]) -> None:
|
||||
with self._lock:
|
||||
progress = self._states[model]
|
||||
progress.todo_order, progress.todo_items = seed_todo_state(todos)
|
||||
progress.todo_completed, progress.todo_total, progress.todo_current = (
|
||||
summarize_todo_state(progress.todo_order, progress.todo_items)
|
||||
)
|
||||
progress.last_activity = f"seeded {progress.todo_total} todos"
|
||||
self._refresh_locked()
|
||||
|
||||
def mark_prompt_submitted(self, model: str) -> None:
|
||||
self._mutate_model(model, status="run", last_activity="prompt submitted")
|
||||
|
||||
@@ -1142,14 +754,12 @@ class ProgressPrinter:
|
||||
self._mutate_model(model, turns=turns)
|
||||
|
||||
def note_tool_start(
|
||||
self, model: str, tool_name: str, intent: str | None, tool_calls: int, args: Any
|
||||
self, model: str, tool_name: str, intent: str | None, tool_calls: int
|
||||
) -> None:
|
||||
with self._lock:
|
||||
progress = self._states[model]
|
||||
progress.status = "run"
|
||||
progress.tool_calls = tool_calls
|
||||
if tool_name == "todo_write":
|
||||
apply_todo_ops(progress, args)
|
||||
detail = truncate_text(intent, 36)
|
||||
progress.last_activity = f"{tool_name} · {detail}" if detail else tool_name
|
||||
self._refresh_locked()
|
||||
@@ -1158,25 +768,6 @@ class ProgressPrinter:
|
||||
if is_error:
|
||||
self._mutate_model(model, last_activity=f"{tool_name} failed")
|
||||
|
||||
def note_todo_reminder(self, model: str, todos: tuple[TodoItem, ...]) -> None:
|
||||
with self._lock:
|
||||
progress = self._states[model]
|
||||
progress.todo_order = [task.id for task in todos]
|
||||
progress.todo_items = {
|
||||
task.id: (task.content, task.status) for task in todos
|
||||
}
|
||||
progress.todo_completed, progress.todo_total, progress.todo_current = (
|
||||
summarize_todo_state(progress.todo_order, progress.todo_items)
|
||||
)
|
||||
self._refresh_locked()
|
||||
|
||||
def note_todo_auto_clear(self, model: str) -> None:
|
||||
with self._lock:
|
||||
progress = self._states[model]
|
||||
progress.todo_completed = progress.todo_total
|
||||
progress.todo_current = None
|
||||
self._refresh_locked()
|
||||
|
||||
def note_thinking(self, model: str, delta: str, total_chars: int) -> None:
|
||||
with self._lock:
|
||||
progress = self._states[model]
|
||||
@@ -1286,7 +877,6 @@ class ProgressPrinter:
|
||||
table.add_column("Turns", justify="right", no_wrap=True, width=5)
|
||||
table.add_column("Tools", justify="right", no_wrap=True, width=5)
|
||||
table.add_column("Tokens", justify="right", no_wrap=True, width=8)
|
||||
table.add_column("Todos", ratio=1, min_width=24)
|
||||
|
||||
for model in self._model_order:
|
||||
state = self._states[model]
|
||||
@@ -1296,7 +886,6 @@ class ProgressPrinter:
|
||||
str(state.turns),
|
||||
str(state.tool_calls),
|
||||
format_count(state.token_total),
|
||||
self._todo_text(state),
|
||||
)
|
||||
|
||||
if self._final_message:
|
||||
@@ -1321,15 +910,6 @@ class ProgressPrinter:
|
||||
}
|
||||
return Text(status, style=styles.get(status, "white"))
|
||||
|
||||
@staticmethod
|
||||
def _todo_text(state: ModelProgress) -> str:
|
||||
if state.todo_total == 0:
|
||||
return "-"
|
||||
summary = f"{state.todo_completed}/{state.todo_total}"
|
||||
if state.todo_current:
|
||||
summary = f"{summary} · {truncate_text(state.todo_current, 24)}"
|
||||
return summary
|
||||
|
||||
@staticmethod
|
||||
def _model_text(state: ModelProgress) -> Text:
|
||||
text = Text(truncate_text(state.label, 34), style="bold")
|
||||
@@ -1407,9 +987,6 @@ class ModelRunRecorder:
|
||||
self.token_input: int | None = None
|
||||
self.token_output: int | None = None
|
||||
self.token_total: int | None = None
|
||||
self.todo_completed = 0
|
||||
self.todo_total = 0
|
||||
self.todo_current: str | None = None
|
||||
self.review_sections: list[str] = []
|
||||
self.agent_ended = False
|
||||
self.auto_retry_active = False
|
||||
@@ -1445,7 +1022,7 @@ class ModelRunRecorder:
|
||||
self._touch()
|
||||
self.tool_calls += 1
|
||||
self.printer.note_tool_start(
|
||||
self.run_id, event.tool_name, event.intent, self.tool_calls, event.args
|
||||
self.run_id, event.tool_name, event.intent, self.tool_calls
|
||||
)
|
||||
|
||||
def record_tool_execution_update(self, _event: ToolExecutionUpdateEvent) -> None:
|
||||
@@ -1542,35 +1119,6 @@ class ModelRunRecorder:
|
||||
self.text_chars += len(delta)
|
||||
self.printer.note_text(self.run_id, delta, self.text_chars)
|
||||
|
||||
def record_todo_reminder(self, event: TodoReminderEvent) -> None:
|
||||
self._touch()
|
||||
self.todo_completed = sum(
|
||||
1 for task in event.todos if task.status == "completed"
|
||||
)
|
||||
self.todo_total = len(event.todos)
|
||||
in_progress = next(
|
||||
(task.content for task in event.todos if task.status == "in_progress"), None
|
||||
)
|
||||
pending = next(
|
||||
(task.content for task in event.todos if task.status == "pending"), None
|
||||
)
|
||||
self.todo_current = in_progress or pending
|
||||
self.printer.note_todo_reminder(self.run_id, event.todos)
|
||||
|
||||
def record_todo_auto_clear(self, _event: TodoAutoClearEvent) -> None:
|
||||
self._touch()
|
||||
self.todo_completed = self.todo_total
|
||||
self.todo_current = None
|
||||
self.printer.note_todo_auto_clear(self.run_id)
|
||||
|
||||
def sync_final_todos(self, phases: tuple[TodoPhase, ...]) -> None:
|
||||
order, items = build_todo_state_from_phases(phases)
|
||||
self.todo_completed, self.todo_total, self.todo_current = summarize_todo_state(
|
||||
order, items
|
||||
)
|
||||
flattened = tuple(task for phase in phases for task in phase.tasks)
|
||||
self.printer.note_todo_reminder(self.run_id, flattened)
|
||||
|
||||
def build_review_markdown(self) -> str:
|
||||
if not self.review_sections:
|
||||
return ""
|
||||
@@ -1579,8 +1127,6 @@ class ModelRunRecorder:
|
||||
def is_effectively_complete(self, *, quiet_seconds: float) -> bool:
|
||||
return (
|
||||
len(self.review_sections) > 0
|
||||
and self.todo_total > 0
|
||||
and self.todo_completed >= self.todo_total
|
||||
and not self.auto_retry_active
|
||||
and (time.monotonic() - self.last_event_at) >= quiet_seconds
|
||||
)
|
||||
@@ -1651,14 +1197,10 @@ def run_model_sync(
|
||||
client.on_agent_end(recorder.record_agent_end)
|
||||
client.on_message_update(recorder.record_message_update)
|
||||
client.on_message_end(recorder.record_message_end)
|
||||
client.on_todo_reminder(recorder.record_todo_reminder)
|
||||
client.on_todo_auto_clear(recorder.record_todo_auto_clear)
|
||||
client.on_ui_request(recorder.record_ui)
|
||||
client.install_headless_ui()
|
||||
|
||||
printer.mark_ready(run_id)
|
||||
client.set_todos(TODOS)
|
||||
printer.seed_todos(run_id, TODOS)
|
||||
|
||||
deadline = time.monotonic() + timeout
|
||||
|
||||
@@ -1716,8 +1258,6 @@ def run_model_sync(
|
||||
raise RpcError("Agent completed without final review text after retry")
|
||||
|
||||
stats = client.get_session_stats()
|
||||
todo_phases = client.get_todos()
|
||||
recorder.sync_final_todos(todo_phases)
|
||||
if (
|
||||
recorder.token_total is None or recorder.token_total <= 0
|
||||
) and stats.tokens.total > 0:
|
||||
@@ -1766,9 +1306,6 @@ def run_model_sync(
|
||||
token_input=recorder.token_input,
|
||||
token_output=recorder.token_output,
|
||||
token_total=recorder.token_total,
|
||||
todo_completed=recorder.todo_completed,
|
||||
todo_total=recorder.todo_total,
|
||||
todo_current=recorder.todo_current,
|
||||
error=error_message,
|
||||
session_state=session_state,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user