fix(dashboard): resolve planning 404 for sessions started outside dashboard

_resolve_planning_key searches all active sessions by slug regardless of
adapter, so respond/finalize/cancel/advance work even when planning was
initiated from Discord or Telegram.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-04-28 10:57:34 +00:00
parent 1462f98ae9
commit 8594f98bff

View File

@@ -690,6 +690,35 @@ class ProjectsHandlers:
"""Dashboard planning sessions live in adapter='dashboard', channel=slug."""
return ("dashboard", slug)
@staticmethod
def _resolve_planning_key(slug: str) -> tuple[str, str]:
"""Find the active session's (adapter, channel) for slug, regardless of
where it was started. Falls back to ('dashboard', slug) if nothing
matches — preserving prior behavior for the no-session case.
Picks the most-recently-updated entry when multiple exist.
"""
try:
from src.planning_session import _load_planning_state # type: ignore
except Exception:
return ("dashboard", slug)
try:
all_state = _load_planning_state()
except Exception:
return ("dashboard", slug)
matches = [
entry for entry in all_state.values()
if (entry.get("slug") or "").lower() == slug.lower()
]
if not matches:
return ("dashboard", slug)
matches.sort(key=lambda e: e.get("updated_at") or "", reverse=True)
best = matches[0]
adapter = best.get("adapter") or "dashboard"
channel = best.get("channel_id") or slug
return (adapter, channel)
# ── POST /api/projects/<slug>/plan/start ──────────────────────
def handle_plan_start(self, slug: str):
if validate_slug(slug):
@@ -793,7 +822,7 @@ class ProjectsHandlers:
self.send_json({"error": "message required"}, 400)
return
adapter, channel = self._planning_key(slug)
adapter, channel = self._resolve_planning_key(slug)
try:
from src.planning_orchestrator import PlanningOrchestrator # type: ignore
session, text, phase_ready = PlanningOrchestrator.respond(
@@ -915,7 +944,7 @@ class ProjectsHandlers:
if validate_slug(slug):
self.send_json({"error": "invalid_slug"}, 400)
return
adapter, channel = self._planning_key(slug)
adapter, channel = self._resolve_planning_key(slug)
try:
from src.planning_session import get_planning_state, clear_planning_state # type: ignore
from src.planning_orchestrator import PlanningOrchestrator # type: ignore
@@ -964,7 +993,7 @@ class ProjectsHandlers:
if validate_slug(slug):
self.send_json({"error": "invalid_slug"}, 400)
return
adapter, channel = self._planning_key(slug)
adapter, channel = self._resolve_planning_key(slug)
try:
from src.planning_orchestrator import PlanningOrchestrator # type: ignore
except Exception as exc:
@@ -992,7 +1021,7 @@ class ProjectsHandlers:
if validate_slug(slug):
self.send_json({"error": "invalid_slug"}, 400)
return
adapter, channel = self._planning_key(slug)
adapter, channel = self._resolve_planning_key(slug)
try:
from src.planning_orchestrator import PlanningOrchestrator # type: ignore
session, text, completed = PlanningOrchestrator.advance(adapter, channel)