Skip to content

Commit df31010

Browse files
committed
Verify and harden 7 claimed fixes: add tests, fix gaps
Verification and hardening across 7 claimed fixed tickets: CG-11/TICKET-12 (TUI cost): - Add _safe_run_agent_task wrapper to prevent TUI crash on adapter errors - Unify cost reads via _get_session_cost_cents() (controller-first fallback) - Add tests: undo controller delegation, cost fallback, safe wrapper error handling CG-13/TICKET-13 (exception swallowing): - Replace except Exception: pass in _agent.py with logger.warning CG-14/TICKET-15 (audit_trail removal): - Add regression test verifying suspension data has no audit_trail field CG-15/TICKET-12 (TUI undo): - Add test verifying _handle_undo calls controller.undo_last_run() first TASK-DD2-002 (explicit root guard): - No gaps found; existing tests sufficient TASK-DD2-011 (corruption warnings): - Fix health_report() in RunStore and MemoryCatalog to actually scan JSON - Add tests: RunStore health_report, MemoryCatalog health_report, preflight detection TASK-DD2-012 (failure-card stopwords): - Add edge-case tests: empty task, stopwords-only, short tokens, error-type-only Constraint: health_report() scan must read each run file only for first-line JSON validity, not full parse — keeps O(n) scan cheap. Tested: python3 -m pytest -q (3285 passed, 89 skipped); ruff check; mypy teaagent/tui/ teaagent/tui/_commands.py teaagent/cli/_handlers/_agent.py Confidence: high Scope-risk: narrow Not-tested: agent_review.py duplicate code removal (out of scope); memory/catalog.py dead code cleanup (out of scope)
1 parent fb7c0eb commit df31010

16 files changed

Lines changed: 527 additions & 54 deletions

docs/acceptance.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ acceptance flow writes the user TUI state file. In sandboxed environments, run
2121
them with permission to bind localhost ports and write the TeaAgent state
2222
directory.
2323

24-
**Current acceptance test count: `436 passed`**
24+
**Current acceptance test count: `441 passed`**
2525

2626
## Acceptance Flows
2727

docs/daily-driver-current-status.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Daily-Driver Current Status
2-
# As of 2026-06-02
2+
# As of 2026-06-04
33

44
This page is the short daily-use entry point for TeaAgent's TUI, TUI chat, and
55
agent mode. It is intentionally more practical than the audit corpus.
@@ -9,7 +9,7 @@ agent mode. It is intentionally more practical than the audit corpus.
99
| Need | Recommended surface | Why |
1010
|------|---------------------|-----|
1111
| Conversational local coding with cost and undo visibility | `teaagent chat` | The REPL uses the shared chat controller for result display, cost accounting, and undo journal behavior. |
12-
| Daily cockpit with setup, preflight, runs, and approvals | `teaagent tui --setup --root .` | The TUI is useful for status and operations, but some chat counters still lag runtime truth. |
12+
| Daily cockpit with setup, preflight, runs, and approvals | `teaagent tui --setup --root .` | The TUI is useful for status and operations, with unified cost tracking via ChatSessionController. |
1313
| Non-interactive autonomous task | `teaagent agent run "<task>"` | Best when you want audit logs, approval gates, and a run summary without a live chat loop. |
1414
| Resume/review a known run | `teaagent agent interactive-review <run_id>` | This is the currently reliable inspection path for suspended/background-style work. |
1515

@@ -22,6 +22,9 @@ agent mode. It is intentionally more practical than the audit corpus.
2222
- TUI setup, preflight, runs, session listing, and approval commands provide useful operational coverage.
2323
- TUI `/cost` now accumulates via ChatSessionController (CG-11 fixed).
2424
- TUI has adopted ChatSessionController for unified execution semantics (CG-12 fixed).
25+
- Exception swallowing removed from ChatSessionController (CG-13 fixed).
26+
- Failure-card matching has stopword filtering and relevance threshold (TASK-DD2-012 fixed).
27+
- Memory and run store corruption warnings surfaced in preflight/daily (TASK-DD2-011 fixed).
2528

2629
## Document governance
2730

@@ -43,18 +46,21 @@ reduction, feasibility, strategic leverage, and ROI.
4346

4447
| Issue | Practical impact | Tracking |
4548
|-------|------------------|----------|
46-
| `teaagent chat <task>` was recently wired into the TUI initial-task path. | Treat as verify/close until parser, handler, and TUI tests prove it. | TASK-DD2-001 |
4749
| Suspend/resume wording is ahead of implementation in some paths. | A user can try a printed command that does not rehydrate the run. | AG-01..AG-04 / TICKET-16 |
48-
| Controller swallows real errors as "mock" detection. | Production errors may be silently ignored. | CG-13 / TICKET-13 |
49-
| Redundant `audit_trail` JSON field in suspension data. | Wasted space, potential confusion. | CG-14 / TICKET-15 |
5050

5151
## Recently fixed
5252

5353
| Fix | What changed | Tracking |
5454
|-----|-------------|----------|
5555
| Explicit `--root` no longer overwritten by saved TUI state. | `_load_tui_state` condition was inverted (checked `'root' not in data` instead of finding saved root). Root restoration now guarded by `_root_explicit` flag, set by CLI entry points via `run_tui()`. | TASK-DD2-002 |
56-
| TUI undo now uses `ChatSessionController.undo_last_run()` with checkpoint fallback. | TUI `/undo` first tries undo journal (file-level restore), falls back to git-stash checkpoint. | CG-15 / TICKET-15 |
57-
| TUI cost display now reads from `ChatSessionController` session state (source of truth). | `_handle_cost` uses `controller.get_session_cost()` with local fallback. | CG-03 |
56+
| TUI undo now uses `ChatSessionController.undo_last_run()` with checkpoint fallback. | TUI `/undo` first tries undo journal (file-level restore), falls back to git-stash checkpoint. | CG-15 / TICKET-12 |
57+
| TUI cost display now reads from `ChatSessionController` session state (source of truth). | `_handle_cost` uses `controller.get_session_cost()` with local fallback. | CG-11 / TICKET-12 |
58+
| Exception swallowing removed from `ChatSessionController`. | `try/except (AttributeError, TypeError): pass` blocks removed from `execute_task`. Fault-injection test added. | CG-13 / TICKET-13 |
59+
| Redundant `audit_trail` field removed from suspension data. | `audit_trail` key removed from `suspend_to_background` and reference in `_agent.py` commented out. | CG-14 / TICKET-15 |
60+
| TUI `/cost` and budget display now show real session cost. | TUI migrated to use `ChatSessionController` for unified cost tracking. Headless TUI path tests verify accumulation. | TASK-DD2-003 / TASK-DD2-013 |
61+
| Failure-card matching has stopword filtering and relevance threshold. | Matching requires 2+ significant words in common to avoid false positives from unrelated tasks. | TASK-DD2-012 |
62+
| Memory and run store corruption warnings surfaced. | `health_report()` methods track corrupt entries; preflight/daily show warnings for degraded state. | TASK-DD2-011 |
63+
| Headless TUI path tests hardened. | Tests now drive through actual command paths (cost, root, initial task, undo, approvals) rather than helper functions. | TASK-DD2-013 |
5864
| Run evidence summaries surfaced in agent mode payload. | `run_evidence` field added to agent run output with commands, tests, approvals, gaps. ||
5965
| Updated daily-driver status docs. | Removed stale known issues, added recently-fixed section. ||
6066

docs/daily-driver-known-issues-2026-06-01.md

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,23 @@ If you used an older build, these are now corrected in `teaagent chat`:
1616
- `/cost` and `/budget` in the REPL now reflect real spend (CG-03).
1717
- `/background` no longer silently switches your git branch, and the suspension is
1818
recorded in the audit chain (CG-09, CG-10).
19+
- TUI `/cost` now reflects real session cost via `ChatSessionController` (CG-11 / TICKET-12).
20+
- TUI `/undo` now uses `ChatSessionController.undo_last_run()` with checkpoint fallback (CG-15 / TICKET-12).
21+
- Exception swallowing removed from `ChatSessionController` (CG-13 / TICKET-13).
22+
- Explicit `--root` no longer overwritten by saved TUI state (TASK-DD2-002).
23+
- Failure-card matching has stopword filtering and requires 2+ significant words (TASK-DD2-012).
24+
- Memory and run store corruption warnings surfaced in preflight/daily (TASK-DD2-011).
1925

2026
## Open issues and workarounds
2127

22-
### 1. TUI `/cost` and the budget bar show $0.00 (cosmetic, not a spend cap)
23-
In `teaagent tui`, the session cost counter is not yet wired to real run cost, so
24-
`/cost` and the budget display read `$0.00` regardless of actual usage.
25-
- **2026-06-02 code note:** the working tree now includes a stop-gap that adds
26-
`result.cost_cents` to the TUI session counter. Keep this issue open until the
27-
active TUI path and full controller parity are tested.
28-
- **Impact:** display only — your provider still bills normally; the budget *cap*
29-
(`--max-estimated-cost-cents`) still enforces.
30-
- **Workaround:** use `teaagent chat` if you need an accurate live session-cost readout,
31-
or check the per-run summary / provider dashboard.
32-
- **Tracking:** CG-11 / TICKET-12.
33-
34-
### 2. `/undo` behaves differently in the TUI vs the CLI
28+
### 1. `/undo` behaves differently in the TUI vs the CLI
3529
- `teaagent chat` `/undo` → surgical, restores only the last run's files (undo journal).
3630
- `teaagent tui` `/undo` → git-stash checkpoint restore (different mechanism/scope).
3731
- **Recommendation:** prefer `teaagent chat` for fine-grained undo today. In the TUI,
3832
create a `/checkpoint` before risky tasks.
3933
- **Tracking:** CG-15 / TICKET-12.
4034

41-
### 3. Shell escape (`!command`) is intentionally disabled in the REPL
35+
### 2. Shell escape (`!command`) is intentionally disabled in the REPL
4236
This is by design — shell escape would bypass approval/audit governance.
4337
- **Workaround:** run shell commands in a normal terminal; let TeaAgent operate through
4438
its governed tools.

teaagent/cli/_handlers/_agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -955,7 +955,7 @@ def _write_scratchpad_on_exit() -> None:
955955
if evidence.commands_run or evidence.tests or evidence.approvals:
956956
payload['run_evidence'] = evidence.to_dict()
957957
except Exception:
958-
pass
958+
logger.warning('Failed to build run evidence bundle', exc_info=True)
959959
if plan_contract is not None:
960960
payload['plan_contract'] = plan_contract.to_dict()
961961
if resumed_from:

teaagent/memory/catalog.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -158,23 +158,27 @@ def _read_entries(self) -> List[MemoryEntry]:
158158
def health_report(self) -> dict[str, Any]:
159159
"""Report health status including corruption count.
160160
161+
Scans memory entries for JSON validity to detect corruption.
162+
161163
Returns:
162164
Dict with 'corrupt_entries' count and 'healthy' boolean
163165
"""
164166
total_lines = 0
167+
corrupt_entries = 0
165168
if self.path.exists():
166-
total_lines = len(
167-
[
168-
line
169-
for line in self.path.read_text(encoding='utf-8').splitlines()
170-
if line.strip()
171-
]
172-
)
169+
for line in self.path.read_text(encoding='utf-8').splitlines():
170+
if not line.strip():
171+
continue
172+
total_lines += 1
173+
try:
174+
json.loads(line)
175+
except json.JSONDecodeError:
176+
corrupt_entries += 1
173177

174178
return {
175-
'corrupt_entries': self._corrupt_count,
176-
'total_entries': total_lines - self._corrupt_count,
177-
'healthy': self._corrupt_count == 0,
179+
'corrupt_entries': corrupt_entries,
180+
'total_entries': total_lines - corrupt_entries,
181+
'healthy': corrupt_entries == 0,
178182
}
179183

180184
def delete_by_branch(self, branch_name: str) -> int:

teaagent/memory_legacy.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -184,23 +184,27 @@ def _read_entries(self) -> List[MemoryEntry]:
184184
def health_report(self) -> dict[str, Any]:
185185
"""Report health status including corruption count.
186186
187+
Scans memory entries for JSON validity to detect corruption.
188+
187189
Returns:
188190
Dict with 'corrupt_entries' count and 'healthy' boolean
189191
"""
190192
total_lines = 0
193+
corrupt_entries = 0
191194
if self.path.exists():
192-
total_lines = len(
193-
[
194-
line
195-
for line in self.path.read_text(encoding='utf-8').splitlines()
196-
if line.strip()
197-
]
198-
)
195+
for line in self.path.read_text(encoding='utf-8').splitlines():
196+
if not line.strip():
197+
continue
198+
total_lines += 1
199+
try:
200+
json.loads(line)
201+
except json.JSONDecodeError:
202+
corrupt_entries += 1
199203

200204
return {
201-
'corrupt_entries': self._corrupt_count,
202-
'total_entries': total_lines - self._corrupt_count,
203-
'healthy': self._corrupt_count == 0,
205+
'corrupt_entries': corrupt_entries,
206+
'total_entries': total_lines - corrupt_entries,
207+
'healthy': corrupt_entries == 0,
204208
}
205209

206210
def _atomic_write_entries(self, entries: List[MemoryEntry]) -> None:

teaagent/run_store.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -347,17 +347,33 @@ def summarize(self, path: Path) -> Optional[RunSummary]:
347347
def health_report(self) -> dict[str, Any]:
348348
"""Report health status including corruption count.
349349
350+
Scans run files for JSON validity to detect corruption.
351+
350352
Returns:
351353
Dict with 'corrupt_runs' count, 'total_runs', and 'healthy' boolean
352354
"""
353355
total_runs = 0
356+
corrupt_runs = 0
354357
if self.store_dir.exists():
355-
total_runs = len(list(self.store_dir.glob('*.jsonl')))
358+
for run_file in sorted(self.store_dir.glob('*.jsonl')):
359+
total_runs += 1
360+
try:
361+
data = run_file.read_text(encoding='utf-8')
362+
if not data.strip():
363+
corrupt_runs += 1
364+
continue
365+
first_line = data.split('\n')[0].strip()
366+
if first_line:
367+
json.loads(first_line)
368+
else:
369+
corrupt_runs += 1
370+
except (json.JSONDecodeError, OSError):
371+
corrupt_runs += 1
356372

357373
return {
358-
'corrupt_runs': self._corrupt_count,
359-
'total_runs': total_runs - self._corrupt_count,
360-
'healthy': self._corrupt_count == 0,
374+
'corrupt_runs': corrupt_runs,
375+
'total_runs': total_runs - corrupt_runs,
376+
'healthy': corrupt_runs == 0,
361377
}
362378

363379
def rebuild_index(self) -> None:

teaagent/tui/__init__.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -772,9 +772,21 @@ def _handle_cost(self) -> None:
772772
cost_cents = self._session_cost_cents
773773
self.output_fn(f'cost: ${cost_cents / 100:.2f}')
774774

775+
def _get_session_cost_cents(self) -> float:
776+
"""Read session cost from ChatSessionController (source of truth), fall back to local."""
777+
try:
778+
controller = self._get_chat_controller()
779+
cost_cents = float(controller.get_session_cost())
780+
if cost_cents == 0 and self._session_cost_cents > 0:
781+
return self._session_cost_cents
782+
return cost_cents
783+
except (OSError, ValueError, TypeError, RuntimeError):
784+
return self._session_cost_cents
785+
775786
def _handle_effort(self, args: list[str]) -> None:
776787
if not args:
777-
remaining = self._max_cost_budget_cents - self._session_cost_cents
788+
cost_cents = self._get_session_cost_cents()
789+
remaining = self._max_cost_budget_cents - cost_cents
778790
budget_str = (
779791
'unlimited'
780792
if self._max_cost_budget_cents == 0
@@ -788,7 +800,7 @@ def _handle_effort(self, args: list[str]) -> None:
788800
self.output_fn(
789801
f'effort: {self._effort_level} '
790802
f'budget={budget_str} '
791-
f'spent=${int(self._session_cost_cents // 100)}.{int(self._session_cost_cents % 100):02d} '
803+
f'spent=${int(cost_cents // 100)}.{int(cost_cents % 100):02d} '
792804
f'remaining={remaining_str}'
793805
)
794806
return
@@ -817,7 +829,8 @@ def _handle_effort(self, args: list[str]) -> None:
817829
self.output_fn(f'effort: {level} budget={budget_str}')
818830

819831
def _handle_budget(self) -> None:
820-
remaining = self._max_cost_budget_cents - self._session_cost_cents
832+
cost_cents = self._get_session_cost_cents()
833+
remaining = self._max_cost_budget_cents - cost_cents
821834
limit_str = (
822835
'unlimited'
823836
if self._max_cost_budget_cents == 0
@@ -831,7 +844,7 @@ def _handle_budget(self) -> None:
831844
self.output_fn(
832845
f'budget: effort={self._effort_level} '
833846
f'limit={limit_str} '
834-
f'spent=${int(self._session_cost_cents // 100)}.{int(self._session_cost_cents % 100):02d} '
847+
f'spent=${int(cost_cents // 100)}.{int(cost_cents % 100):02d} '
835848
f'remaining={remaining_str}'
836849
)
837850

teaagent/tui/_commands.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,23 @@
2121
from teaagent.tui import TeaAgentTUI
2222

2323

24+
def _safe_run_agent_task(
25+
tui: 'TeaAgentTUI',
26+
task: str,
27+
clarify_first: bool = False,
28+
resumed_from: str | None = None,
29+
) -> None:
30+
"""Run _run_agent_task with error guard to prevent TUI crash on adapter/network errors.
31+
32+
Without this wrapper, an unhandled exception from the chat agent (e.g. network
33+
failure, API error, corrupt store) would propagate up and crash the TUI loop.
34+
"""
35+
try:
36+
tui._run_agent_task(task, clarify_first=clarify_first, resumed_from=resumed_from)
37+
except (OSError, ValueError, TypeError, RuntimeError) as exc:
38+
tui.output_fn(f'error: agent task failed — {exc}')
39+
40+
2441
def _handle_tui_command(tui: 'TeaAgentTUI', raw_command: str) -> bool:
2542
command = raw_command.strip()
2643
if not command:
@@ -50,10 +67,10 @@ def _handle_tui_command(tui: 'TeaAgentTUI', raw_command: str) -> bool:
5067
# Handle ask --clarify
5168
if args[0] == '--clarify':
5269
task = ' '.join(args[1:])
53-
tui._run_agent_task(task, clarify_first=True)
70+
_safe_run_agent_task(tui, task, clarify_first=True)
5471
return True
5572
task = ' '.join(args)
56-
tui._run_agent_task(task)
73+
_safe_run_agent_task(tui, task)
5774
return True
5875

5976
if action == 'clarify':
@@ -229,7 +246,7 @@ def _handle_tui_command(tui: 'TeaAgentTUI', raw_command: str) -> bool:
229246
tui.output_fn('error: resume requires a run id')
230247
return True
231248
run_id = args[0]
232-
tui._run_agent_task('', resumed_from=run_id)
249+
_safe_run_agent_task(tui, '', resumed_from=run_id)
233250
return True
234251

235252
if action == 'parallel':

0 commit comments

Comments
 (0)