diff --git a/services/minecraft/src/cognitive/conscious/brain.ts b/services/minecraft/src/cognitive/conscious/brain.ts index 272f7217a..8a0dce1a4 100644 --- a/services/minecraft/src/cognitive/conscious/brain.ts +++ b/services/minecraft/src/cognitive/conscious/brain.ts @@ -813,11 +813,32 @@ export class Brain { return JSON.parse(JSON.stringify(messages)) as Message[] } + /** + * Re-emit the current conversation state with full context metadata. + * Used by the debug dashboard's `request_conversation` handler on reconnect. + */ + public broadcastConversationState(): void { + this.emitConversationUpdate(this.isProcessing) + } + private emitConversationUpdate(isProcessing: boolean, sessionBoundary?: boolean): void { this.debugService.emitConversationUpdate({ messages: this.cloneMessages(this.conversationHistory), isProcessing, ...(sessionBoundary && { sessionBoundary }), + activeContext: { + label: this.activeContextState.label, + startTurnId: this.activeContextState.startTurnId, + messageCount: this.conversationHistory.length - this.activeContextStartIndex, + }, + archivedContexts: this.archivedContexts.map(ctx => ({ + label: ctx.label, + summary: ctx.summary, + turns: ctx.endTurnId - ctx.startTurnId + 1, + archivedAt: ctx.archivedAt, + })), + activeContextStartIndex: this.activeContextStartIndex, + contextHistoryMessage: this.getContextHistoryMessage(), }) } diff --git a/services/minecraft/src/cognitive/index.ts b/services/minecraft/src/cognitive/index.ts index 43046fec4..e25698bcf 100644 --- a/services/minecraft/src/cognitive/index.ts +++ b/services/minecraft/src/cognitive/index.ts @@ -33,11 +33,7 @@ export function CognitiveEngine(options: CognitiveEngineOptions): MineflayerPlug }) debugService.onCommand('request_conversation', () => { - const snapshot = brain.getDebugSnapshot() - debugService.emitConversationUpdate({ - messages: snapshot.conversationHistory, - isProcessing: snapshot.isProcessing, - }) + brain.broadcastConversationState() }) debugService.onCommand('execute_repl', async (command) => { diff --git a/services/minecraft/src/debug/types.ts b/services/minecraft/src/debug/types.ts index b645d5809..6cc596447 100644 --- a/services/minecraft/src/debug/types.ts +++ b/services/minecraft/src/debug/types.ts @@ -96,6 +96,16 @@ export interface TraceBatchEvent { timestamp: number } +/** + * Archived context summary for the debug dashboard + */ +export interface ContextBoundaryInfo { + label: string + summary: string + turns: number + archivedAt: number +} + /** * Live conversation state update from the brain */ @@ -103,6 +113,18 @@ export interface ConversationUpdateEvent { messages: Array<{ role: string, content: string, reasoning?: string }> isProcessing: boolean sessionBoundary?: boolean + /** Active context boundary state */ + activeContext?: { + label: string | null + startTurnId: number + messageCount: number + } + /** Archived context summaries */ + archivedContexts?: ContextBoundaryInfo[] + /** Index in messages where the active context starts */ + activeContextStartIndex?: number + /** The [CONTEXT_HISTORY] prefix message content, if any */ + contextHistoryMessage?: string | null timestamp: number } diff --git a/services/minecraft/src/debug/web/app.js b/services/minecraft/src/debug/web/app.js index 68f54c00d..b61959728 100644 --- a/services/minecraft/src/debug/web/app.js +++ b/services/minecraft/src/debug/web/app.js @@ -566,13 +566,39 @@ class LogsPanel { // Conversation Panel (Live Chat View) // ============================================================================= +// --- User message parser: extracts structured sections from brain's buildUserMessage output --- +function parseUserMessage(content) { + if (typeof content !== 'string') + return { sections: [], raw: '' } + const sections = [] + // Known section tags in order they may appear + const tagPattern = /^\[(EVENT|FEEDBACK|PERCEPTION|STATE|ERROR_BURST_GUARD|ERROR_BURST|MANDATORY|SCRIPT|ACTION_QUEUE|NO_ACTION_BUDGET|CONTEXT)\]\s*/ + // Split on double-newline (the separator used by buildUserMessage) + const blocks = content.split(/\n\n/) + for (const block of blocks) { + const trimmed = block.trim() + if (!trimmed) + continue + const m = trimmed.match(tagPattern) + if (m) { + sections.push({ tag: m[1], text: trimmed.slice(m[0].length) }) + } + else { + // Could be a continuation of PERCEPTION or unknown block + sections.push({ tag: 'OTHER', text: trimmed }) + } + } + return { sections, raw: content } +} + class ConversationPanel { constructor(client) { this.client = client - // Each session: { messages: Message[], greyed: boolean } - this.sessions = [{ messages: [], greyed: false }] + this._mkSession = () => ({ messages: [], greyed: false, activeContext: null, archivedContexts: [], activeContextStartIndex: 0, contextHistoryMessage: null }) + this.sessions = [this._mkSession()] this.isProcessing = false this.autoScroll = true + this.turnCounter = 0 this.elements = { container: document.getElementById('conversation-container'), count: document.getElementById('conversation-count'), @@ -588,44 +614,63 @@ class ConversationPanel { this.reset() this.client.send({ type: 'request_conversation' }) }) + + // Unified collapsible toggle handler (event delegation — attached once) + if (this.elements.container) { + this.elements.container.addEventListener('click', (e) => { + const toggle = e.target.closest('[data-toggle]') + if (!toggle) + return + const targetId = toggle.getAttribute('data-toggle') + const body = document.getElementById(targetId) + if (!body) + return + const isOpen = body.classList.toggle('cv-open') + const arrow = toggle.querySelector('.cv-arrow') + if (arrow) + arrow.textContent = isOpen ? '\u25BC' : '\u25B6' + }) + } + this.render() } handleUpdate(data) { if (data.sessionBoundary) { - // Grey out current session and start a new one - const currentSession = this.sessions[this.sessions.length - 1] - if (currentSession) { - currentSession.greyed = true - } - this.sessions.push({ messages: [], greyed: false }) + const cur = this.sessions[this.sessions.length - 1] + if (cur) + cur.greyed = true + this.sessions.push(this._mkSession()) } else { - // Update current session messages - const currentSession = this.sessions[this.sessions.length - 1] - if (currentSession) { - currentSession.messages = data.messages || [] + const cur = this.sessions[this.sessions.length - 1] + if (cur) { + cur.messages = data.messages || [] + cur.activeContext = data.activeContext || null + cur.archivedContexts = data.archivedContexts || [] + cur.activeContextStartIndex = data.activeContextStartIndex ?? 0 + cur.contextHistoryMessage = data.contextHistoryMessage || null } } - this.isProcessing = !!data.isProcessing this.updateStats() this.render() } reset() { - this.sessions = [{ messages: [], greyed: false }] + this.sessions = [this._mkSession()] this.isProcessing = false + this.turnCounter = 0 this.updateStats() this.render() } updateStats() { - const totalMessages = this.sessions.reduce((sum, s) => sum + s.messages.length, 0) + const total = this.sessions.reduce((s, sess) => s + sess.messages.length, 0) if (this.elements.count) - this.elements.count.textContent = totalMessages + this.elements.count.textContent = total if (this.elements.statLlm) - this.elements.statLlm.textContent = totalMessages + this.elements.statLlm.textContent = total if (this.elements.processingBadge) this.elements.processingBadge.classList.toggle('hidden', !this.isProcessing) } @@ -633,100 +678,305 @@ class ConversationPanel { render() { if (!this.elements.container) return + this.turnCounter = 0 - const html = this.sessions.map((session, sessionIdx) => { - const sessionClass = session.greyed ? 'chat-session chat-session-greyed' : 'chat-session' - const messagesHtml = this.renderSessionMessages(session.messages) - const dividerHtml = session.greyed - ? '
Session cleared
' - : '' - - return `
${messagesHtml}
${dividerHtml}` + const html = this.sessions.map((session) => { + const cls = session.greyed ? 'chat-session chat-session-greyed' : 'chat-session' + const body = this.renderSession(session) + const divider = session.greyed ? '
Session cleared
' : '' + return `
${body}
${divider}` }).join('') - const typingHtml = this.isProcessing - ? `
- - - -
` + const typing = this.isProcessing + ? '
' : '' - this.elements.container.innerHTML = html + typingHtml - - // Attach toggle handlers for system messages - this.elements.container.querySelectorAll('.chat-system-toggle').forEach((btn) => { - btn.addEventListener('click', () => { - const body = btn.closest('.chat-message-system')?.querySelector('.chat-system-body') - if (body) { - body.classList.toggle('open') - btn.textContent = body.classList.contains('open') ? '▼ System Prompt' : '▶ System Prompt' - } - }) - }) + this.elements.container.innerHTML = html + typing if (this.autoScroll) { - const scrollEl = this.elements.scroll - if (scrollEl) { - requestAnimationFrame(() => { - scrollEl.scrollTop = scrollEl.scrollHeight - }) - } + const el = this.elements.scroll + if (el) + requestAnimationFrame(() => { el.scrollTop = el.scrollHeight }) } } - renderSessionMessages(messages) { - if (!messages || messages.length === 0) { + // --- Session rendering --- + + renderSession(session) { + const { messages, activeContext, archivedContexts, contextHistoryMessage } = session + if (!messages || messages.length === 0) return '
No messages yet
' + + const parts = [] + + // Context status bar + if (archivedContexts?.length > 0 || activeContext) { + parts.push(this.renderContextStatusBar(activeContext, archivedContexts, contextHistoryMessage)) } - return messages.map((msg) => { - const role = msg.role || 'unknown' - - if (role === 'system') { - return this.renderSystemMessage(msg) - } - - if (role === 'user') { - return this.renderUserMessage(msg) - } - - if (role === 'assistant') { - return this.renderAssistantMessage(msg) - } - - return `
-
${escapeHtml(role)}
-
${escapeHtml(msg.content || '')}
-
` - }).join('') + // Group messages into turns (user+assistant pairs) + const turns = this.groupIntoTurns(messages) + for (const turn of turns) { + parts.push(this.renderTurn(turn)) + } + return parts.join('') } - renderSystemMessage(msg) { - const preview = formatSystemMessageContent(msg.content || '') - return `
- -
-
${escapeHtml(preview)}
+ groupIntoTurns(messages) { + const turns = [] + let current = null + for (const msg of messages) { + if (msg.role === 'system') { + turns.push({ type: 'system', system: msg }) + } + else if (msg.role === 'user') { + current = { type: 'turn', user: msg, assistant: null } + turns.push(current) + } + else if (msg.role === 'assistant') { + if (current && !current.assistant) { + current.assistant = msg + } + else { + turns.push({ type: 'turn', user: null, assistant: msg }) + } + } + } + return turns + } + + renderTurn(turn) { + if (turn.type === 'system') + return this.renderSystemMessage(turn.system) + + this.turnCounter++ + const n = this.turnCounter + const userParsed = turn.user ? parseUserMessage(turn.user.content || '') : null + const eventSection = userParsed?.sections.find(s => s.tag === 'EVENT' || s.tag === 'FEEDBACK') + const contextSection = userParsed?.sections.find(s => s.tag === 'CONTEXT') + + // Build a short summary for the turn header + let summary = `Turn ${n}` + if (eventSection) { + summary = this.summarizeEvent(eventSection) + } + + // Detect context label from the [CONTEXT] section + let ctxBadge = '' + if (contextSection) { + const ctxMatch = contextSection.text.match(/active="([^"]+)"/) + if (ctxMatch) { + ctxBadge = `${escapeHtml(ctxMatch[1])}` + } + else if (contextSection.text.includes('no active context')) { + ctxBadge = 'no ctx' + } + } + + const turnId = `cv-turn-${n}` + const userHtml = turn.user ? this.renderParsedUserMessage(userParsed, n) : '' + const assistantHtml = turn.assistant ? this.renderParsedAssistantMessage(turn.assistant, n) : '' + + return `
+ +
+ ${userHtml} + ${assistantHtml}
` } - renderUserMessage(msg) { - return `
-
user
-
${escapeHtml(msg.content || '')}
+ // --- Event summary helpers --- + + summarizeEvent(section) { + const text = section.text + // Chat messages: "Chat from X: "message"" + const chatMatch = text.match(/^Chat from (\w+):\s*"(.+)"$/s) + if (chatMatch) + return `Chat from ${chatMatch[1]}: "${chatMatch[2].slice(0, 60)}${chatMatch[2].length > 60 ? '...' : ''}"` + + // Perception Signal + if (text.startsWith('Perception Signal:')) + return text.slice(0, 70) + (text.length > 70 ? '...' : '') + + // system_alert with JSON — extract reason + returnValue preview + if (text.startsWith('system_alert:')) { + try { + const json = JSON.parse(text.slice('system_alert:'.length).trim()) + const reason = json.reason || 'unknown' + const rv = json.returnValue + const rvPreview = typeof rv === 'string' ? rv.slice(0, 50) : '' + return `system: ${reason}${rvPreview ? ` → ${rvPreview}${rv.length > 50 ? '...' : ''}` : ''}` + } + catch { /* fall through */ } + } + + // FEEDBACK: "toolName: Success/Failed. details" + if (section.tag === 'FEEDBACK') { + const fbMatch = text.match(/^(\w+):\s*(Success|Failed)\.?\s*(.*)$/s) + if (fbMatch) { + const detail = fbMatch[3].slice(0, 50) + return `${fbMatch[1]}: ${fbMatch[2]}${detail ? ` — ${detail}${fbMatch[3].length > 50 ? '...' : ''}` : ''}` + } + } + + // Fallback: truncate + const flat = text.replace(/\n/g, ' ').slice(0, 70) + return flat + (text.length > 70 ? '...' : '') + } + + // --- Parsed user message rendering --- + + renderParsedUserMessage(parsed, turnNum) { + if (!parsed) + return '' + const cards = [] + for (const section of parsed.sections) { + cards.push(this.renderUserSection(section, turnNum)) + } + return `
${cards.join('')}
` + } + + renderUserSection(section, turnNum) { + const id = `cv-s-${turnNum}-${section.tag}-${Math.random().toString(36).slice(2, 6)}` + const tagColors = { + EVENT: 'cv-tag-event', + FEEDBACK: 'cv-tag-feedback', + PERCEPTION: 'cv-tag-perception', + SCRIPT: 'cv-tag-script', + ACTION_QUEUE: 'cv-tag-queue', + NO_ACTION_BUDGET: 'cv-tag-budget', + CONTEXT: 'cv-tag-context', + ERROR_BURST: 'cv-tag-error', + ERROR_BURST_GUARD: 'cv-tag-error', + MANDATORY: 'cv-tag-error', + STATE: 'cv-tag-state', + OTHER: 'cv-tag-other', + } + const colorCls = tagColors[section.tag] || 'cv-tag-other' + + // Some sections are compact enough to show inline + if (['ACTION_QUEUE', 'NO_ACTION_BUDGET', 'CONTEXT', 'ERROR_BURST', 'STATE'].includes(section.tag)) { + return `
+ + ${escapeHtml(section.text)} +
` + } + + // EVENT / FEEDBACK — show prominently + if (section.tag === 'EVENT' || section.tag === 'FEEDBACK') { + return `
+ + ${escapeHtml(section.text)} +
` + } + + // SCRIPT, PERCEPTION, OTHER — collapsible + const preview = section.text.slice(0, 60).replace(/\n/g, ' ') + return `
+ +
+
${escapeHtml(section.text)}
+
` } - renderAssistantMessage(msg) { - const reasoningHtml = msg.reasoning - ? `
${escapeHtml(msg.reasoning)}
` - : '' - return `
-
assistant
- ${reasoningHtml} -
${escapeHtml(msg.content || '')}
+ // --- Parsed assistant message rendering --- + + renderParsedAssistantMessage(msg, turnNum) { + const reasoning = msg.reasoning || '' + const code = msg.content || '' + const parts = [] + + if (reasoning) { + const rid = `cv-reason-${turnNum}` + const preview = reasoning.slice(0, 80).replace(/\n/g, ' ') + parts.push(`
+ +
+
${escapeHtml(reasoning)}
+
+
`) + } + + if (code) { + parts.push(`
+
Code
+
${escapeHtml(code)}
+
`) + } + + return `
${parts.join('')}
` + } + + // --- Context status bar --- + + renderContextStatusBar(activeContext, archivedContexts, contextHistoryMessage) { + const parts = [] + // Active context indicator + if (activeContext?.label) { + parts.push(` ${escapeHtml(activeContext.label)} (${activeContext.messageCount} msgs)`) + } + else { + parts.push(' No active context') + } + + // Archived count + if (archivedContexts?.length > 0) { + const archId = `cv-archived-${Math.random().toString(36).slice(2, 6)}` + const items = archivedContexts.map((ctx, i) => { + const time = new Date(ctx.archivedAt).toLocaleTimeString() + return `
+ #${i + 1} + ${escapeHtml(ctx.label || 'unnamed')} + ${ctx.turns}t · ${time} +
${escapeHtml(ctx.summary)}
+
` + }).join('') + parts.push(``) + // Append the collapsible body after the status bar + parts.push(`
${items}
`) + } + + // Context history prefix + if (contextHistoryMessage) { + const chId = `cv-ctxhist-${Math.random().toString(36).slice(2, 6)}` + parts.push(``) + parts.push(`
${escapeHtml(contextHistoryMessage)}
`) + } + + return `
${parts.join('')}
` + } + + // --- System message --- + + renderSystemMessage(msg) { + const id = `cv-sys-${Math.random().toString(36).slice(2, 6)}` + const preview = formatSystemMessageContent(msg.content || '') + return `
+ +
+
${escapeHtml(preview)}
+
` } } diff --git a/services/minecraft/src/debug/web/conversation.css b/services/minecraft/src/debug/web/conversation.css new file mode 100644 index 000000000..f9bb5dc59 --- /dev/null +++ b/services/minecraft/src/debug/web/conversation.css @@ -0,0 +1,435 @@ +/* ========================================================================== + Conversation Panel v2 — Structured Turn Cards + ========================================================================== */ + +/* Unified collapsible mechanism */ +.cv-arrow { + font-size: 10px; + flex-shrink: 0; + width: 10px; + display: inline-block; + text-align: center; +} + +[data-toggle] { cursor: pointer; } + +/* All collapsible bodies hidden by default */ +.cv-turn-body, +.cv-section-body, +.cv-reasoning-body, +.cv-system-body, +.cv-ctx-arch-body, +.cv-ctx-hist-body { + display: none; +} + +.cv-open { + display: block !important; +} + +/* --- Context status bar --- */ +.cv-ctx-bar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + padding: 6px 10px; + margin-bottom: 8px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + font-size: 11px; + font-family: var(--font-mono); +} + +.cv-ctx-dot { + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent-success); + margin-right: 4px; + animation: cvPulse 2s infinite ease-in-out; +} + +.cv-ctx-dot-idle { + background: var(--text-muted); + animation: none; +} + +.cv-ctx-status-active { color: var(--accent-success); } +.cv-ctx-status-idle { color: var(--text-muted); } + +.cv-ctx-arch-btn, +.cv-ctx-hist-btn { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + font-size: 10px; + font-family: var(--font-mono); + background: rgba(163, 113, 247, 0.1); + border: 1px solid rgba(163, 113, 247, 0.3); + border-radius: 4px; + color: #a371f7; +} + +.cv-ctx-arch-btn:hover, +.cv-ctx-hist-btn:hover { + background: rgba(163, 113, 247, 0.18); +} + +.cv-ctx-arch-body, +.cv-ctx-hist-body { + width: 100%; + margin-top: 6px; +} + +.cv-arch-item { + padding: 5px 8px; + margin-bottom: 3px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-left: 3px solid rgba(163, 113, 247, 0.5); + border-radius: 4px; + font-size: 11px; + font-family: var(--font-mono); +} + +.cv-arch-idx { color: var(--text-muted); margin-right: 4px; } +.cv-arch-meta { color: var(--text-muted); margin-left: 6px; font-size: 10px; } +.cv-arch-summary { + margin-top: 3px; + color: var(--text-secondary); + white-space: pre-wrap; + word-break: break-word; +} + +.cv-ctx-hist-content { + margin: 0; + padding: 6px 8px; + background: var(--bg-primary); + border: 1px solid rgba(163, 113, 247, 0.25); + border-radius: 4px; + font-family: var(--font-mono); + font-size: 11px; + color: var(--text-secondary); + white-space: pre-wrap; + word-break: break-word; +} + +@keyframes cvPulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +/* --- Turn card --- */ +.cv-turn { + margin-bottom: 4px; + border: 1px solid var(--border-color); + border-radius: 6px; + overflow: hidden; + background: var(--bg-secondary); +} + +.cv-turn-header { + display: flex; + align-items: center; + gap: 6px; + width: 100%; + padding: 6px 10px; + font-size: 11px; + font-family: var(--font-mono); + background: var(--bg-tertiary); + border: none; + border-bottom: 1px solid transparent; + color: var(--text-primary); + text-align: left; +} + +.cv-turn-header:hover { background: var(--border-color); } + +.cv-turn-num { + color: var(--accent-info); + font-weight: 600; + flex-shrink: 0; +} + +.cv-turn-summary { + color: var(--text-secondary); + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.cv-ctx-badge { + padding: 1px 6px; + border-radius: 3px; + font-size: 10px; + flex-shrink: 0; +} + +.cv-ctx-active { + background: rgba(63, 185, 80, 0.15); + color: var(--accent-success); + border: 1px solid rgba(63, 185, 80, 0.3); +} + +.cv-ctx-none { + background: rgba(110, 118, 129, 0.1); + color: var(--text-muted); + border: 1px solid var(--border-color); +} + +.cv-turn-body { + padding: 6px 8px; + border-top: 1px solid var(--border-color); +} + +/* --- User message sections --- */ +.cv-user { + display: flex; + flex-direction: column; + gap: 3px; + margin-bottom: 6px; +} + +.cv-section { + border-radius: 4px; + font-family: var(--font-mono); + font-size: 11px; +} + +.cv-section-tag { + display: inline-block; + padding: 0 5px; + border-radius: 3px; + font-size: 10px; + font-weight: 600; + margin-right: 4px; + text-transform: uppercase; + flex-shrink: 0; +} + +/* Tag colors */ +.cv-tag-event .cv-section-tag { background: rgba(63, 185, 80, 0.15); color: var(--accent-success); } +.cv-tag-feedback .cv-section-tag { background: rgba(88, 166, 255, 0.15); color: var(--accent-info); } +.cv-tag-perception .cv-section-tag { background: rgba(210, 153, 34, 0.15); color: var(--accent-warning); } +.cv-tag-script .cv-section-tag { background: rgba(163, 113, 247, 0.15); color: #a371f7; } +.cv-tag-queue .cv-section-tag { background: rgba(110, 118, 129, 0.12); color: var(--text-secondary); } +.cv-tag-budget .cv-section-tag { background: rgba(110, 118, 129, 0.12); color: var(--text-secondary); } +.cv-tag-context .cv-section-tag { background: rgba(63, 185, 80, 0.12); color: var(--accent-success); } +.cv-tag-error .cv-section-tag { background: rgba(248, 81, 73, 0.15); color: var(--accent-error); } +.cv-tag-state .cv-section-tag { background: rgba(210, 153, 34, 0.12); color: var(--accent-warning); } +.cv-tag-other .cv-section-tag { background: rgba(110, 118, 129, 0.1); color: var(--text-muted); } + +/* Inline sections (compact one-liners) */ +.cv-section-inline { + display: flex; + align-items: baseline; + gap: 4px; + padding: 2px 6px; + background: var(--bg-primary); + border: 1px solid var(--border-color); +} + +.cv-section-inline-text { + color: var(--text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Event/Feedback sections (prominent) */ +.cv-section-event { + display: flex; + align-items: baseline; + gap: 4px; + padding: 4px 8px; + background: var(--bg-primary); + border: 1px solid var(--border-color); +} + +.cv-section-text { + color: var(--text-primary); + white-space: pre-wrap; + word-break: break-word; +} + +/* Collapsible sections (SCRIPT, PERCEPTION, OTHER) */ +.cv-section-toggle { + display: flex; + align-items: center; + gap: 4px; + width: 100%; + padding: 3px 6px; + font-size: 11px; + font-family: var(--font-mono); + background: var(--bg-primary); + border: 1px solid var(--border-color); + color: var(--text-primary); + text-align: left; + border-radius: 4px; +} + +.cv-section-toggle:hover { background: var(--bg-tertiary); } + +.cv-section-preview { + color: var(--text-muted); + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.cv-section-body { + border: 1px solid var(--border-color); + border-top: none; + border-radius: 0 0 4px 4px; +} + +.cv-section-content { + margin: 0; + padding: 6px 8px; + font-family: var(--font-mono); + font-size: 11px; + color: var(--text-secondary); + white-space: pre-wrap; + word-break: break-word; + line-height: 1.4; + max-height: 200px; + overflow-y: auto; + background: var(--bg-primary); +} + +/* --- Assistant message --- */ +.cv-assistant { + display: flex; + flex-direction: column; + gap: 4px; +} + +/* Reasoning (collapsible) */ +.cv-reasoning { border-radius: 4px; } + +.cv-reasoning-toggle { + display: flex; + align-items: center; + gap: 4px; + width: 100%; + padding: 3px 6px; + font-size: 11px; + font-family: var(--font-mono); + background: rgba(210, 153, 34, 0.06); + border: 1px solid rgba(210, 153, 34, 0.25); + border-radius: 4px; + color: var(--accent-warning); + text-align: left; +} + +.cv-reasoning-toggle:hover { background: rgba(210, 153, 34, 0.12); } + +.cv-reasoning-label { + font-weight: 600; + flex-shrink: 0; +} + +.cv-reasoning-preview { + color: var(--text-muted); + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.cv-reasoning-body { + border: 1px solid rgba(210, 153, 34, 0.2); + border-top: none; + border-radius: 0 0 4px 4px; +} + +.cv-reasoning-content { + margin: 0; + padding: 6px 8px; + font-family: var(--font-mono); + font-size: 11px; + color: var(--text-muted); + white-space: pre-wrap; + word-break: break-word; + line-height: 1.4; + max-height: 250px; + overflow-y: auto; + background: rgba(210, 153, 34, 0.03); +} + +/* Code block */ +.cv-code { + border: 1px solid rgba(88, 166, 255, 0.3); + border-radius: 4px; + overflow: hidden; +} + +.cv-code-label { + padding: 2px 8px; + font-size: 10px; + font-family: var(--font-mono); + font-weight: 600; + color: var(--accent-info); + background: rgba(88, 166, 255, 0.08); + border-bottom: 1px solid rgba(88, 166, 255, 0.15); +} + +.cv-code-content { + margin: 0; + padding: 6px 8px; + font-family: var(--font-mono); + font-size: 12px; + color: var(--text-primary); + white-space: pre-wrap; + word-break: break-word; + line-height: 1.45; + background: var(--bg-primary); +} + +/* --- System message --- */ +.cv-system { + margin-bottom: 4px; +} + +.cv-system-toggle { + display: flex; + align-items: center; + gap: 6px; + width: 100%; + padding: 4px 8px; + font-size: 11px; + font-family: var(--font-mono); + background: var(--bg-tertiary); + border: 1px solid rgba(163, 113, 247, 0.3); + border-radius: 4px; + color: #a371f7; + text-align: left; +} + +.cv-system-toggle:hover { background: rgba(163, 113, 247, 0.1); } + +.cv-system-body { + border: 1px solid rgba(163, 113, 247, 0.2); + border-top: none; + border-radius: 0 0 4px 4px; +} + +.cv-system-content { + margin: 0; + padding: 6px 8px; + font-family: var(--font-mono); + font-size: 11px; + color: var(--text-secondary); + white-space: pre-wrap; + word-break: break-word; + line-height: 1.4; + max-height: 300px; + overflow-y: auto; + background: var(--bg-primary); +} diff --git a/services/minecraft/src/debug/web/index.html b/services/minecraft/src/debug/web/index.html index c13db1c7a..97d8e2e6f 100644 --- a/services/minecraft/src/debug/web/index.html +++ b/services/minecraft/src/debug/web/index.html @@ -7,6 +7,7 @@ Debug Dashboard + diff --git a/services/minecraft/src/debug/web/styles.css b/services/minecraft/src/debug/web/styles.css index a07b070ec..c1bb6d8a8 100644 --- a/services/minecraft/src/debug/web/styles.css +++ b/services/minecraft/src/debug/web/styles.css @@ -1020,3 +1020,5 @@ button:hover, max-height: 100px; overflow-y: auto; } + +/* Context boundary styles moved to conversation.css */