fix(agent): debug panel copyDebugInfo — full state snapshot (was 9 fields, now all model atoms)

Previously copyDebugInfo() only exported 9 hand-picked fields
(conversation_id, thread_id, connection/streaming state, user, env,
message count, truncated tool calls, error). The debug panel was
missing LLM health (status, retry, banner), confirmation/HITL state
(pending_tool, args, risk), queue position, message previews, full
tool call objects, and UI flags (sidebar, debug panel, cancelled).

Now:
- Full model  snapshot with all fields
- active_tool_calls_full — complete ToolCall objects
- last_message_preview — first 200 chars of last message
- LLM status, banner dismiss, retry countdown
- HITL confirmation state (pending_tool_name, args, risk, level)
- Conversations count, queue position, user_cancelled flag
- Visual debug panel grid: 5-column layout with new rows for
  LLM health, confirmation, queue, sidebar, convs_count
This commit is contained in:
2026-07-03 15:35:22 +03:00
parent 53eb2b1cca
commit 89b340c64f

View File

@@ -287,18 +287,49 @@
}
async function copyDebugInfo() {
const lastMsg = model.messages.at(-1);
const payload = {
// ── Identifiers ──
conversation_id: model.currentConversationId,
pending_thread_id: model.pendingThreadId,
connection_state: model.connectionState,
streaming_state: model.streamingState,
user_id: model.userId,
environment_id: model.envId,
messages: model.messages.length,
active_tool_calls: model.activeToolCalls.map((tc) => ({
tool: tc.tool,
status: tc.status,
})),
// ── Connection & Streaming ──
connection_state: model.connectionState,
streaming_state: model.streamingState,
agent_phase: model.agentPhase,
// ── Messages ──
messages_count: model.messages.length,
last_message_id: lastMsg?.id ?? null,
last_message_role: lastMsg?.role ?? null,
last_message_preview: lastMsg?.text?.slice(0, 200) ?? null,
// ── Streaming buffer ──
partial_text_length: model.partialText.length,
partial_tokens_count: model.partialTokens.length,
// ── Tool calls (full objects) ──
active_tool_calls: model.activeToolCalls.length,
active_tool_calls_full: model.activeToolCalls,
// ── HITL / Confirmation ──
confirmation_message: model.confirmationMessage,
pending_tool_name: model.pendingToolName,
pending_tool_args: model.pendingToolArgs,
pending_confirmation_risk: model.pendingConfirmationRisk,
pending_risk_level: model.pendingRiskLevel,
// ── LLM health ──
llm_status: model.llmStatus,
llm_banner_dismissed: model.llmBannerDismissed,
llm_retry_countdown: model.llmRetryCountdown,
llm_banner_message: model.llmBannerMessage,
// ── Conversation list ──
conversations_count: model.conversations.length,
conversations_has_next: model.conversationsHasNext,
// ── UI state ──
is_debug_panel_open: model.isDebugPanelOpen,
is_conversation_sidebar_open: model.isConversationSidebarOpen,
is_loading_history: model.isLoadingHistory,
queue_position: model.queuePosition,
user_cancelled: model._userCancelled,
// ── Error ──
error: model.error,
};
try {
@@ -415,23 +446,29 @@
</div>
{#if model.isDebugPanelOpen}
<div class="mt-3 grid gap-2 rounded-lg border border-border bg-surface-page px-3 py-2 text-[11px] text-text-muted sm:grid-cols-2 lg:grid-cols-4">
<div class="mt-3 grid gap-2 rounded-lg border border-border bg-surface-page px-3 py-2 text-[11px] text-text-muted sm:grid-cols-2 lg:grid-cols-5">
<!-- ── Row 1: IDs ── -->
<div class="min-w-0">
<span class="font-medium text-text-subtle">conv_id</span>
<div class="truncate font-mono text-text" title={model.conversationIdLabel}>{model.conversationIdLabel}</div>
<div class="truncate font-mono text-text" title={model.conversationIdLabel || "null"}>{model.conversationIdLabel || "null"}</div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">thread_id</span>
<div class="truncate font-mono text-text" title={model.pendingThreadIdLabel}>{model.pendingThreadIdLabel}</div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">state</span>
<div class="truncate font-mono text-text">{model.connectionState} / {model.streamingState}</div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">env / user</span>
<div class="truncate font-mono text-text" title={`${model.envIdLabel} / ${model.userIdLabel}`}>{model.envIdLabel} / {model.userIdLabel}</div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">phase</span>
<div class="truncate font-mono text-text">{model.agentPhase}</div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">state</span>
<div class="truncate font-mono text-text">{model.connectionState} / {model.streamingState}</div>
</div>
<!-- ── Row 2: Messages & tools ── -->
<div class="min-w-0">
<span class="font-medium text-text-subtle">messages</span>
<div class="truncate font-mono text-text">{model.messageCountLabel}</div>
@@ -441,12 +478,69 @@
<div class="truncate font-mono text-text" title={model.lastMessageIdLabel}>{model.lastMessageIdLabel}</div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">tools</span>
<span class="font-medium text-text-subtle">tools (active)</span>
<div class="truncate font-mono text-text">{model.activeToolCalls.length}</div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">queue</span>
<div class="truncate font-mono text-text">{model.queuePosition}</div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">partial_tokens</span>
<div class="truncate font-mono text-text">{model.partialTokens.length}</div>
</div>
<!-- ── Row 3: LLM health ── -->
<div class="min-w-0">
<span class="font-medium text-text-subtle">llm_status</span>
<div class="truncate font-mono text-text">{model.llmStatus}</div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">llm_retry</span>
<div class="truncate font-mono text-text">{model.llmRetryCountdown}s</div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">banner_dismissed</span>
<div class="truncate font-mono text-text">{String(model.llmBannerDismissed)}</div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">sidebar</span>
<div class="truncate font-mono text-text">{String(model.isConversationSidebarOpen)}</div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">convs_count</span>
<div class="truncate font-mono text-text">{model.conversations.length}</div>
</div>
<!-- ── Row 4: Confirmation (if active) ── -->
{#if model.pendingToolName}
<div class="min-w-0">
<span class="font-medium text-text-subtle">pending_tool</span>
<div class="truncate font-mono text-text">{model.pendingToolName}</div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">risk</span>
<div class="truncate font-mono text-text">{model.pendingConfirmationRisk || model.pendingRiskLevel || "—"}</div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">confirm_msg</span>
<div class="truncate font-mono text-text" title={model.confirmationMessage || ""}>{model.confirmationMessage?.slice(0, 60) || "—"}</div>
</div>
{:else}
<div class="min-w-0">
<span class="font-medium text-text-subtle">pending_tool</span>
<div class="truncate font-mono text-text"></div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">pending_thread</span>
<div class="truncate font-mono text-text">{model.pendingThreadId ? model.pendingThreadId.slice(0, 12) : "—"}</div>
</div>
<div class="min-w-0">
<span class="font-medium text-text-subtle">cancelled</span>
<div class="truncate font-mono text-text">{String(model._userCancelled)}</div>
</div>
{/if}
<div class="min-w-0">
<span class="font-medium text-text-subtle">error</span>
<div class="truncate font-mono text-text" title={model.error || "none"}>{model.error || "none"}</div>
<div class="truncate font-mono text-text" title={model.error || ""}>{model.error?.slice(0, 80) || "none"}</div>
</div>
</div>
{/if}