feat(planning): full chat history + auto-advance phases

Three fixes that together restore the planning UX:

- Dashboard reopen showed only a 500-char truncated excerpt of the last
  assistant message. Backend now reads the Claude session JSONL directly
  and returns full per-turn history; frontend iterates and renders all
  bubbles, falling back to last_text_excerpt when the JSONL is missing.
- Phases never advanced because the agent ran /plan-* skills inline as
  tool calls and the marker protocol was loose. Tightened the planning
  prompt (mandatory PHASE_STATUS marker on the last line of every turn,
  ban on inline phase invocation), and the frontend now auto-calls
  /plan/advance when phase_ready=true.
- The phase strip never showed visual state because data-phase values
  ("office-hours") didn't match orchestrator phase names ("/office-hours").
  Added normalizePhase + cleanup of PHASE_STATUS markers from rendered
  bubbles.

Also bumps eco.py session-content truncation from 2k to 20k so /eco
session views aren't cut mid-response either.

Bumps last_text_excerpt fallback in planning_session.py from 500 to
50_000 so even when the JSONL is unavailable, the bubble isn't sliced
mid-word.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-05 07:47:10 +00:00
parent d0faeed181
commit 8432fe3150
5 changed files with 197 additions and 22 deletions

View File

@@ -94,7 +94,7 @@ PHASE_NEEDS_INPUT_MARKER = "PHASE_STATUS: needs_input"
# "planning_session_id": "<echo internal uuid>",
# "started_at": "...",
# "updated_at": "...",
# "last_text_excerpt": "...", # 500 char excerpt for debugging
# "last_text_excerpt": "...", # 50K char fallback excerpt; full transcript lives in Claude's session JSONL
# "last_subtype": "success" | "error_max_turns" | ...,
# }
# }
@@ -418,7 +418,7 @@ class PlanningSession:
"channel_id": self.channel_id,
"started_at": existing.get("started_at", now),
"updated_at": now,
"last_text_excerpt": (self._last_response or "")[:500],
"last_text_excerpt": (self._last_response or "")[:50000],
"last_subtype": self._last_subtype,
"total_cost_usd": (
float(existing.get("total_cost_usd") or 0.0) + float(cost_usd or 0.0)