Ver código fonte

docs: plan professional debug console

Problem: Todo 53 needs a TDD execution sequence that isolates EventPrompt correctness, layout refactoring, UI state, and closeout verification.\n\nRisk: frontend source-contract tests remain intentionally coupled to stable DOM/function boundaries; browser QA covers behavior those tests cannot execute.
zhenyu.hu 2 semanas atrás
pai
commit
c4040be75b

+ 396 - 0
docs/plans/todo-53-professional-debug-console-ui.md

@@ -0,0 +1,396 @@
+# Professional Debug Console UI Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Turn the static Agent Lab page into a professional responsive debug console and make EventPrompt deterministically follow invocation mode, selected tools, and restored state.
+
+**Architecture:** Preserve the FastAPI/static-JavaScript boundary and backend contracts. Consolidate tool/mode-derived state in one refresh path, then reorganize the page into a collapsible control rail, primary chat workspace, and optional Audit/Usage drawer with browser-only presentation persistence.
+
+**Tech Stack:** HTML5, CSS, browser-native JavaScript, FastAPI static assets, pytest source-contract tests, Node syntax checking, local browser QA.
+
+---
+
+## File Map
+
+- Modify `src/agent_lab/presentation/static/app.js`: EventPrompt derivation, restore cleanup, mode-aware prompt collection, collapsible sections, debug drawer, status indicators.
+- Modify `src/agent_lab/presentation/static/index.html`: console layout, status bar, collapsible control sections, right debug drawer, mode-dependent EventAgent groups.
+- Modify `src/agent_lab/presentation/static/styles.css`: design tokens, console shell, messages, controls, drawer, responsive layout.
+- Modify `tests/test_websocket_api.py`: focused frontend source-contract regressions.
+- Modify `README.md`: operator-facing console and EventPrompt behavior.
+- Modify `docs/plans/todos.md`: close Todo 53 only after all verification succeeds.
+
+### Task 1: Make EventPrompt a mode-derived singleton
+
+**Files:**
+
+- Modify: `tests/test_websocket_api.py:1364-1517`
+- Modify: `src/agent_lab/presentation/static/app.js:85-93,259-320,413-450,477-677,867-897`
+- Modify: `src/agent_lab/presentation/static/index.html:212-233`
+
+- [ ] **Step 1: Write failing source-contract tests**
+
+Add these assertions in focused tests:
+
+```python
+assert 'toolInvocationMode.addEventListener("change", refreshToolModeState)' in js
+assert 'checkbox.addEventListener("change", refreshToolModeState)' in js
+assert "function refreshToolModeState()" in js
+assert "function isGeneratedEventPromptContent(" in js
+assert "function removeEventPromptItems()" in js
+assert 'toolInvocationMode.value === "chat_agent_tools"' in update_prompt
+assert 'toolInvocationMode.value !== "chat_agent_tools"' in collect_prompts
+assert 'prompt.prompt_kind !== "event_agent_system"' in normalize_prompts
+assert "!isGeneratedEventPromptContent(prompt.content)" in normalize_prompts
+assert 'id="event-agent-model-settings"' in html
+```
+
+Slice `update_prompt`, `collect_prompts`, and `normalize_prompts` using the existing `js.index(...)` test style so each assertion is scoped to the correct function.
+
+- [ ] **Step 2: Run tests and verify RED**
+
+```bash
+uv run pytest tests/test_websocket_api.py -k "event_prompt or prompt_restore or tool_mode" -q
+```
+
+Expected: new assertions fail because there is no unified refresh, generated-content filter, or model-settings container.
+
+- [ ] **Step 3: Add conservative legacy-generated-prompt detection**
+
+Add:
+
+```javascript
+const EVENT_PROMPT_PREFIX = "You may request EventAgent work using this text protocol.";
+
+function isGeneratedEventPromptContent(content) {
+  const normalized = String(content || "").trim();
+  return normalized.startsWith(EVENT_PROMPT_PREFIX)
+    && normalized.includes("<agent_events>")
+    && normalized.includes("Use exact event names only");
+}
+
+function removeEventPromptItems() {
+  [...systemPrompts.querySelectorAll(".prompt-item")].forEach((item) => {
+    const content = item.querySelector(".system-prompt")?.value || "";
+    if (item.dataset.promptKind === "event_agent_system"
+      || isGeneratedEventPromptContent(content)) {
+      item.remove();
+    }
+  });
+}
+```
+
+Change `normalizePromptItems()` to discard typed EventPrompt entries and legacy generated EventPrompt text. Insert one blank custom prompt only when no custom prompt remains; do not insert EventPrompt there.
+
+- [ ] **Step 4: Centralize mode/tool refresh and outbound filtering**
+
+Implement:
+
+```javascript
+function refreshToolModeState() {
+  updateToolCount();
+  updateToolModeHelp();
+  updateEventAgentConfigAvailability();
+  updateEventPromptItem(availableTools);
+}
+
+function updateEventPromptItem(tools) {
+  removeEventPromptItems();
+  if (toolInvocationMode.value === "chat_agent_tools") {
+    return;
+  }
+  const selected = new Set(selectedTools());
+  const eventPrompt = ensureEventPromptItem();
+  eventPrompt.querySelector(".system-prompt").value = buildAvailableEventsPrompt(
+    tools.filter((tool) => selected.has(tool.name)),
+  );
+}
+```
+
+Bind mode change, checkbox change, All/None, bootstrap, `applySelectedTools()`, and `restoreWorkspaceSnapshot()` to `refreshToolModeState()`. Restore mode/prompts/tools first, then refresh once.
+
+Implement defensive collection:
+
+```javascript
+function collectSystemPrompts() {
+  const includeEventPrompt = toolInvocationMode.value !== "chat_agent_tools";
+  return buildPromptItems()
+    .filter((item) => item.prompt_kind !== "event_agent_system" || includeEventPrompt)
+    .filter((item) => includeEventPrompt || !isGeneratedEventPromptContent(item.content))
+    .map((item) => item.content)
+    .filter(Boolean);
+}
+```
+
+Wrap EventAgent model, temperature, max tokens, system prompt, and extra-body controls in `#event-agent-model-settings`. Disable that fieldset only in `chat_agent_tools`; keep loop, parallelism, and timeout controls outside it and enabled.
+
+- [ ] **Step 5: Verify GREEN**
+
+```bash
+uv run pytest tests/test_websocket_api.py -k "event_prompt or prompt_restore or tool_mode or tools_ui" -q
+node --check src/agent_lab/presentation/static/app.js
+git diff --check
+```
+
+Expected: focused tests pass; Node and diff checks are clean.
+
+- [ ] **Step 6: Commit Task 1**
+
+```bash
+git add tests/test_websocket_api.py src/agent_lab/presentation/static/app.js src/agent_lab/presentation/static/index.html
+git commit -m "fix: synchronize EventPrompt with tool mode" -m "Problem: EventPrompt could remain stale, duplicate after restore, or be sent in chat_agent_tools mode.\n\nRisk: legacy generated prompts are matched conservatively by established protocol markers."
+```
+
+### Task 2: Build the professional console layout and visual system
+
+**Files:**
+
+- Modify: `tests/test_websocket_api.py:1334-1607`
+- Modify: `src/agent_lab/presentation/static/index.html:7-247`
+- Modify: `src/agent_lab/presentation/static/styles.css:1-end`
+
+- [ ] **Step 1: Write failing layout tests**
+
+Require these IDs and structural markers while retaining every existing runtime control ID:
+
+```python
+for control_id in [
+    "current-session-label",
+    "current-mode-label",
+    "toggle-debug-drawer",
+    "debug-drawer",
+    "session-controls",
+    "tool-controls",
+    "agent-config-controls",
+    "snapshot-controls",
+]:
+    assert f'id="{control_id}"' in html
+assert html.count('class="control-section-toggle"') == 4
+assert 'aria-controls="debug-drawer"' in html
+assert "--surface:" in css
+assert ".layout.debug-drawer-closed" in css
+assert "@media (max-width: 900px)" in css
+```
+
+Update the existing fixed-layout test to expect the new left/workspace/drawer shell instead of `.workspace-body` containing a permanent replay column.
+
+- [ ] **Step 2: Run tests and verify RED**
+
+```bash
+uv run pytest tests/test_websocket_api.py -k "professional_console or layout_keeps_controls" -q
+```
+
+Expected: failure because the new shell and CSS tokens do not exist.
+
+- [ ] **Step 3: Restructure the HTML shell**
+
+Use this hierarchy:
+
+```html
+<header class="topbar">
+  <div class="brand-block">
+    <h1>Agent Lab</h1>
+    <span>Chat and event runtime console</span>
+  </div>
+  <div class="top-status" aria-label="Runtime status">
+    <span id="current-session-label" class="status-badge">No session</span>
+    <span id="current-mode-label" class="status-badge">Dual Agent</span>
+    <span id="connection-status" class="status-badge status-neutral">Idle</span>
+    <button id="toggle-debug-drawer" type="button"
+      aria-expanded="true" aria-controls="debug-drawer">Hide Debug</button>
+  </div>
+</header>
+```
+
+Immediately after the header, create `main#console-layout.layout` with exactly three direct children in order: `aside.controls[aria-label="Runtime controls"]`, `section.workspace[aria-label="Conversation workspace"]`, and `aside#debug-drawer.debug-drawer[aria-label="Audit and usage"]`. Create four `.control-section` blocks in the controls child. Each toggle uses `data-collapse-target`, `aria-expanded`, and `aria-controls`; Session and Tools start expanded, Agent Config and Snapshot collapsed. Move Audit Replay and Session Usage into the drawer. Keep metrics above the anchored composer, preserve dialogs and all current IDs, and version both assets as `20260714-professional-console`.
+
+- [ ] **Step 4: Implement the visual system and responsive layout**
+
+Start with these tokens and layout rules:
+
+```css
+:root {
+  --canvas: #f3f5f7;
+  --surface: #ffffff;
+  --surface-subtle: #f8fafc;
+  --border: #d9e0e7;
+  --text: #17212b;
+  --muted: #647180;
+  --accent: #176b87;
+  --accent-strong: #0f536a;
+  --danger: #b42318;
+  --radius: 10px;
+  --shadow: 0 8px 24px rgb(23 33 43 / 8%);
+}
+
+.layout {
+  display: grid;
+  grid-template-columns: minmax(260px, 320px) minmax(0, 1fr) minmax(300px, 380px);
+  height: calc(100vh - 64px);
+  min-height: 0;
+  overflow: hidden;
+}
+
+.layout.debug-drawer-closed {
+  grid-template-columns: minmax(260px, 320px) minmax(0, 1fr) 0;
+}
+```
+
+Add explicit primary/secondary/danger buttons, `:focus-visible`, compact selected tool cards, restrained role-specific messages, metrics/composer boundaries, drawer overflow, and `[hidden]` collapse rules. At `max-width: 900px`, allow body scrolling, stack the three regions, remove locked heights, and prevent horizontal overflow.
+
+- [ ] **Step 5: Verify GREEN**
+
+```bash
+uv run pytest tests/test_websocket_api.py -k "static_ui or professional_console or layout or audit_replay or agent_config" -q
+node --check src/agent_lab/presentation/static/app.js
+git diff --check
+```
+
+Expected: focused static tests and checks pass.
+
+- [ ] **Step 6: Commit Task 2**
+
+```bash
+git add tests/test_websocket_api.py src/agent_lab/presentation/static/index.html src/agent_lab/presentation/static/styles.css
+git commit -m "feat: redesign the debug console layout" -m "Problem: chat lacked visual priority and Audit/Usage permanently consumed workspace width.\n\nRisk: existing DOM IDs are retained to preserve runtime bindings."
+```
+
+### Task 3: Persist presentation state and synchronize top status
+
+**Files:**
+
+- Modify: `tests/test_websocket_api.py:1396-1620`
+- Modify: `src/agent_lab/presentation/static/app.js:1-120 and session lifecycle functions`
+
+- [ ] **Step 1: Write failing persistence/status tests**
+
+```python
+assert 'const CONSOLE_UI_STORAGE_KEY = "agent-lab-console-ui-v1"' in js
+assert "function initializeConsoleUi()" in js
+assert "function setDebugDrawerOpen(" in js
+assert "function setControlSectionExpanded(" in js
+assert "localStorage.getItem(CONSOLE_UI_STORAGE_KEY)" in js
+assert "localStorage.setItem(CONSOLE_UI_STORAGE_KEY" in js
+assert "function updateTopbarStatus()" in js
+assert "current-session-label" in js
+assert "current-mode-label" in js
+assert "CONSOLE_UI_STORAGE_KEY" not in build_request
+```
+
+- [ ] **Step 2: Run tests and verify RED**
+
+```bash
+uv run pytest tests/test_websocket_api.py -k "presentation_state or topbar_status" -q
+```
+
+Expected: failure because the presentation-state functions do not exist.
+
+- [ ] **Step 3: Implement resilient browser-only UI state**
+
+```javascript
+const CONSOLE_UI_STORAGE_KEY = "agent-lab-console-ui-v1";
+
+function readConsoleUiState() {
+  try {
+    return JSON.parse(localStorage.getItem(CONSOLE_UI_STORAGE_KEY) || "{}");
+  } catch (_error) {
+    return {};
+  }
+}
+
+function writeConsoleUiState(state) {
+  try {
+    localStorage.setItem(CONSOLE_UI_STORAGE_KEY, JSON.stringify(state));
+  } catch (_error) {
+    // Presentation preferences must never block the runtime.
+  }
+}
+```
+
+`setControlSectionExpanded(button, expanded, { persist = true } = {})` updates `aria-expanded`, target `hidden`, and `sections[targetId]`. `setDebugDrawerOpen(open, { persist = true } = {})` updates layout class, drawer `hidden`, button text/ARIA, and `debug_drawer_open`. `initializeConsoleUi()` applies stored state over markup defaults and binds controls. None of these values enter snapshots or requests.
+
+- [ ] **Step 4: Synchronize topbar session/mode context**
+
+```javascript
+function updateTopbarStatus() {
+  document.querySelector("#current-session-label").textContent = currentSessionId
+    ? `Session ${currentSessionId.slice(0, 8)}`
+    : "No session";
+  document.querySelector("#current-mode-label").textContent =
+    toolInvocationMode.value === "chat_agent_tools"
+      ? "ChatAgent Tools"
+      : "Dual Agent";
+}
+```
+
+Add `updateTopbarStatus();` to `refreshToolModeState()`, then call it after bootstrap, session creation/load, `session_started`, and any path replacing `currentSessionId`. Preserve active-turn and stale-socket guards.
+
+- [ ] **Step 5: Verify GREEN**
+
+```bash
+uv run pytest tests/test_websocket_api.py -q
+node --check src/agent_lab/presentation/static/app.js
+git diff --check
+```
+
+Expected: all WebSocket/static tests pass with only the existing deprecation warning.
+
+- [ ] **Step 6: Commit Task 3**
+
+```bash
+git add tests/test_websocket_api.py src/agent_lab/presentation/static/app.js
+git commit -m "feat: persist debug console presentation state" -m "Problem: drawer, control-section, session, and mode context were not synchronized.\n\nRisk: localStorage failures are ignored so UI preferences cannot block runtime requests."
+```
+
+### Task 4: Document, visually verify, and close Todo 53
+
+**Files:**
+
+- Modify: `README.md:94-102`
+- Modify: `docs/plans/todos.md:117`
+- Modify: `docs/plans/todo-53-professional-debug-console-ui.md`
+
+- [ ] **Step 1: Update README operator guidance**
+
+Document the left control rail, primary chat workspace, anchored metrics/composer, collapsible Audit/Usage drawer, browser-only presentation state, and these rules:
+
+```markdown
+In `dual_agent`, the read-only EventPrompt is generated from selected tools and
+appears once in Chat Agent Prompt List. In `chat_agent_tools`, EventPrompt is
+hidden and excluded from `system_prompts`; mode changes and session/snapshot
+restores regenerate the correct state. EventAgent model settings are unavailable
+in direct mode, while shared loop, concurrency, and timeout controls remain active.
+```
+
+- [ ] **Step 2: Run the complete automated gate**
+
+```bash
+uv run pytest
+node --check src/agent_lab/presentation/static/app.js
+git diff --check
+uv run python -c "from agent_lab.main import app; print(app.title)"
+```
+
+Expected: full pytest passes with only the known Starlette/httpx warning; Node/diff/import checks succeed.
+
+- [ ] **Step 3: Run desktop browser QA**
+
+Start `uv run uvicorn agent_lab.main:app --host 127.0.0.1 --port 8000`, then verify section defaults/persistence, drawer width recovery, tools/All/None/count, mode badge, exactly one selected-tool EventPrompt in dual mode, no EventPrompt in direct mode, model-only disabled state, restore deduplication, chat streaming, audit dialog, and usage rendering.
+
+- [ ] **Step 4: Run narrow browser QA**
+
+At width `<= 900px`, verify vertical stacking, no horizontal scrollbar, usable messages/metrics/composer/tools/modals, and removal of the drawer region when closed.
+
+- [ ] **Step 5: Observe and update Todo 53**
+
+If a defect exists, keep 53 `in_progress` and add a `53.x` row directly below it with a focused plan. Otherwise mark 53 `done`, record exact test counts and browser evidence, and mark this plan's checkboxes complete.
+
+- [ ] **Step 6: Commit Task 4**
+
+```bash
+git add README.md docs/plans/todos.md docs/plans/todo-53-professional-debug-console-ui.md
+git commit -m "docs: close professional console todo" -m "Problem: the new console and EventPrompt contract need durable guidance and verification evidence."
+```
+
+- [ ] **Step 7: Final clean-state check and local merge**
+
+Run `git status --short` and `git log -5 --oneline`. Merge the verified feature branch locally into `codex/agent-lab-mvp`, then rerun the full automated gate on the merged branch.

+ 1 - 1
docs/plans/todos.md

@@ -114,4 +114,4 @@
 | 52.11 | done | `docs/plans/todo-52.11-bundle-interrupt-and-path-safety.md` | Preserve complete bundles across post-symlink interrupts and reject unsafe report timestamp path components. | Interruptions cannot leave dangling public bundles; explicit timestamps stay inside output_dir; fsync tests verify both files and both directories. |
 | 52.12 | done | `docs/plans/todo-52.12-benchmark-metric-integrity-and-parallel-proof.md` | Exclude consumer backpressure from model elapsed, reject missing token usage, and prove parallel handlers overlap. | Model latency measures provider-await time; absent usage cannot appear as zero cost; the parallel case fails unless both handlers start concurrently. |
 | 52.13 | done | `docs/plans/todo-52.13-provider-cleanup-exception-priority.md` | Preserve active provider/cancellation exceptions when iterator cleanup also fails. | Provider failures and cancellation retain priority; cleanup failures propagate only when no earlier exception is active; timing attempts are still recorded. |
-| 53 | in_progress | `docs/plans/2026-07-14-professional-debug-console-ui-design.md` | Redesign the static UI as a professional debug console and make EventPrompt visibility/content follow invocation mode and selected tools. | Written design is approved before implementation; later verification covers focused static/WebSocket regressions, JS syntax, full pytest, and desktop/narrow browser QA. |
+| 53 | in_progress | `docs/plans/todo-53-professional-debug-console-ui.md` | Redesign the static UI as a professional debug console and make EventPrompt visibility/content follow invocation mode and selected tools. | Written design is approved; implementation verification covers focused static/WebSocket regressions, JS syntax, full pytest, and desktop/narrow browser QA. |