From cb5c5d0e869ba93cb21ead8ebd41b72f0f18197f Mon Sep 17 00:00:00 2001 From: Denys Kuchma Date: Wed, 13 May 2026 15:31:31 +0300 Subject: [PATCH 1/3] Upd log visual --- src/action.ts | 11 ++++++--- src/ai/driller.ts | 2 +- src/ai/planner.ts | 1 - src/ai/rerunner.ts | 2 +- src/ai/researcher.ts | 7 +++--- src/ai/tester.ts | 2 -- src/components/LogPane.tsx | 50 ++++++++++++++++++++++++++++++++------ 7 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/action.ts b/src/action.ts index d273472..0d1bbe2 100644 --- a/src/action.ts +++ b/src/action.ts @@ -2,7 +2,6 @@ import fs from 'node:fs'; import { join } from 'node:path'; import { faker } from '@faker-js/faker'; import { context, trace } from '@opentelemetry/api'; -import { highlight } from 'cli-highlight'; import { container, recorder } from 'codeceptjs'; import * as codeceptjs from 'codeceptjs'; import { hopeThat, retryTo, tryTo, within } from 'codeceptjs/lib/effects'; @@ -21,7 +20,7 @@ import type { PlaywrightRecorder } from './playwright-recorder.ts'; import type { StateManager } from './state-manager.js'; import { extractCodeBlocks } from './utils/code-extractor.js'; import { htmlCombinedSnapshot, minifyHtml } from './utils/html.js'; -import { createDebug, log, setStepSpanParent, tag } from './utils/logger.js'; +import { createDebug, setStepSpanParent, tag } from './utils/logger.js'; import { safeFilename } from './utils/strings.ts'; import { throttle } from './utils/throttle.ts'; @@ -296,7 +295,13 @@ class Action { async expect(codeOrFunction: string | ((I: CodeceptJS.I) => void)): Promise { const codeString = typeof codeOrFunction === 'string' ? codeOrFunction : codeOrFunction.toString(); this.expectation = codeString.toString(); - log('Expecting', highlight(codeString, { language: 'javascript' })); + const expectationPreview = sanitizeCodeBlock(codeString) + .split('\n') + .map((line) => line.trim()) + .filter(Boolean) + .slice(0, 2) + .join(' '); + tag('step').log(`Expecting: ${expectationPreview || 'assertion'}`); try { debugLog('Executing expectation:', codeString); diff --git a/src/ai/driller.ts b/src/ai/driller.ts index e44d4e8..f7ce7df 100644 --- a/src/ai/driller.ts +++ b/src/ai/driller.ts @@ -168,7 +168,7 @@ export class Driller extends TaskAgent implements Agent { this.allResults = []; return Observability.run(`driller: ${currentState.url}`, { tags: ['driller'], sessionId: sessionName }, async () => { - tag('info').log(`Driller starting on ${currentState.url}`); + tag('step').log(`Drilling page: ${currentState.url}`); await this.hooksRunner.runBeforeHook('driller', currentState.url); const originalState = await this.captureAnnotatedState(); diff --git a/src/ai/planner.ts b/src/ai/planner.ts index 4ab133f..638ef82 100644 --- a/src/ai/planner.ts +++ b/src/ai/planner.ts @@ -160,7 +160,6 @@ export class Planner extends PlannerBase implements Agent { this.freshStart = false; setActivity(`${this.emoji} Planning...`, 'action'); - tag('info').log(`Planning test scenarios for ${state.url}`); if (style) tag('info').log(`Planning style: ${style}`); const tags = ['planner']; diff --git a/src/ai/rerunner.ts b/src/ai/rerunner.ts index 984a0db..1f3f1ec 100644 --- a/src/ai/rerunner.ts +++ b/src/ai/rerunner.ts @@ -87,7 +87,7 @@ export class Rerunner extends TaskAgent implements Agent { return { total: 0, passed: 0, failed: 0, healed: 0 }; } - tag('info').log(`Re-running tests from: ${relative(process.cwd(), absPath)}`); + tag('step').log(`Re-running tests from: ${relative(process.cwd(), absPath)}`); setActivity('๐Ÿ”„ Re-running tests...', 'action'); this.healedSteps = []; diff --git a/src/ai/researcher.ts b/src/ai/researcher.ts index be503f8..c32e252 100644 --- a/src/ai/researcher.ts +++ b/src/ai/researcher.ts @@ -121,7 +121,6 @@ export class Researcher extends ResearcherBase implements Agent { const sessionName = `researcher: ${state.url}`; return Observability.run(sessionName, { tags: ['researcher'], sessionId: stateHash }, async () => { - tag('info').log(`Researching ${state.url} to understand the context...`); setActivity(`${this.emoji} Researching...`, 'action'); await this.ensureNavigated(state.url, screenshot && this.provider.hasVision()); @@ -150,10 +149,10 @@ export class Researcher extends ResearcherBase implements Agent { if (!deep && !force) { const similar = await findSimilarResearch(combinedHtml); if (similar) { - tag('info').log('Similar research found, reusing cached result'); + tag('substep').log('Similar research found, reusing cached result'); if (stateHash) saveResearch(stateHash, similar, combinedHtml); tag('multiline').log(formatResearchSummary(similar)); - tag('success').log(`Research complete! ${similar.length} characters (reused)`); + tag('success').log('Research complete (reused)'); await this.hooksRunner.runAfterHook('researcher', state.url); return similar; } @@ -310,7 +309,7 @@ export class Researcher extends ResearcherBase implements Agent { } tag('multiline').log(formatResearchSummary(result.text, { visionUsed: this.hasScreenshotToAnalyze })); - tag('success').log(`Research complete! ${result.text.length} characters`); + tag('success').log('Research complete'); if (researchFile) tag('substep').log(`Research file saved to: ${researchFile}`); if (this.actionResult?.screenshotFile) { const screenshotPath = outputPath('states', this.actionResult.screenshotFile); diff --git a/src/ai/tester.ts b/src/ai/tester.ts index 3f57ae5..1e8ad0e 100644 --- a/src/ai/tester.ts +++ b/src/ai/tester.ts @@ -118,7 +118,6 @@ export class Tester extends TaskAgent implements Agent { const state = this.explorer.getStateManager().getCurrentState(); if (!state) throw new Error('No state found'); - tag('info').log(`Testing scenario: ${task.scenario}`); setActivity(`๐Ÿงช Testing: ${task.scenario}`, 'action'); this.previousUrl = null; @@ -678,7 +677,6 @@ export class Tester extends TaskAgent implements Agent { if (!task.hasFinished) { task.finish(TestResult.FAILED); } - tag('info').log(`Finished: ${task.scenario}`); if (task.isSuccessful) { tag('success').log(`Successful test: ${task.scenario}`); diff --git a/src/components/LogPane.tsx b/src/components/LogPane.tsx index de6e89f..096e488 100644 --- a/src/components/LogPane.tsx +++ b/src/components/LogPane.tsx @@ -7,21 +7,32 @@ import { parseMarkdownToTerminal } from '../utils/markdown-terminal.js'; import { Box, Text } from 'ink'; import type { LogType, TaggedLogEntry } from '../utils/logger.js'; -import { isDebugMode, registerLogPane, setVerboseMode, unregisterLogPane } from '../utils/logger.js'; +import { isDebugMode, registerLogPane, unregisterLogPane } from '../utils/logger.js'; // marked.use(new markedTerminal()); -type LogEntry = TaggedLogEntry; +type LogEntry = TaggedLogEntry & { collapsedCount?: number }; interface LogPaneProps { verboseMode: boolean; } const LogPane: React.FC = React.memo(({ verboseMode }) => { - const [logs, setLogs] = useState([]); - const pendingLogsRef = React.useRef([]); + const [logs, setLogs] = useState([]); + const pendingLogsRef = React.useRef([]); const flushTimeoutRef = React.useRef | null>(null); + const MAX_MULTILINE_LINES = 16; + const MAX_STEP_LINES = 8; + const MAX_SUBSTEP_LINES = 6; + + const formatCollapsedContent = (lines: string[], collapsedCount: number, label: string) => { + if (collapsedCount <= 0) { + return lines.join('\n'); + } + return [`... ${collapsedCount} earlier ${label}`, ...lines].join('\n'); + }; + const flushLogs = useCallback(() => { if (pendingLogsRef.current.length === 0) return; @@ -29,7 +40,7 @@ const LogPane: React.FC = React.memo(({ verboseMode }) => { pendingLogsRef.current = []; flushTimeoutRef.current = null; - setLogs((prevLogs: TaggedLogEntry[]) => { + setLogs((prevLogs: LogEntry[]) => { const result = [...prevLogs]; for (const logEntry of newLogs) { @@ -43,6 +54,29 @@ const LogPane: React.FC = React.memo(({ verboseMode }) => { continue; } + if ((logEntry.type === 'step' || logEntry.type === 'substep') && lastLog.type === logEntry.type && Math.abs((lastLog.timestamp?.getTime() || 0) - (logEntry.timestamp?.getTime() || 0)) < 1500) { + const currentLines = String(logEntry.content) + .split('\n') + .filter((line) => line.length > 0); + const previousLines = String(lastLog.content) + .split('\n') + .filter((line) => line.length > 0); + const visiblePreviousLines = lastLog.collapsedCount ? previousLines.slice(1) : previousLines; + const maxLines = logEntry.type === 'step' ? MAX_STEP_LINES : MAX_SUBSTEP_LINES; + const mergedLines = [...visiblePreviousLines, ...currentLines]; + const overflow = Math.max(0, mergedLines.length - maxLines); + const collapsedCount = (lastLog.collapsedCount || 0) + overflow; + const visibleLines = mergedLines.slice(-maxLines); + const label = logEntry.type === 'step' ? 'steps' : 'details'; + result[result.length - 1] = { + ...lastLog, + content: formatCollapsedContent(visibleLines, collapsedCount, label), + timestamp: logEntry.timestamp, + collapsedCount, + }; + continue; + } + result.push(logEntry); } @@ -112,12 +146,12 @@ const LogPane: React.FC = React.memo(({ verboseMode }) => { const cleaned = stripAnsi(dedent(log.content)); const parsed = parseMarkdownToTerminal(cleaned); const lines = parsed.split('\n'); - const maxLines = 30; - const truncated = lines.length > maxLines ? `${lines.slice(0, maxLines).join('\n')}\n... (${lines.length - maxLines} more lines)` : cleaned; + const truncated = + lines.length > MAX_MULTILINE_LINES ? `${lines.slice(0, MAX_MULTILINE_LINES).join('\n')}\n... (${lines.length - MAX_MULTILINE_LINES} more lines)` : parsed; return ( - {parsed} + {truncated} ); From 942a370eea3e589c1e9200485473c58c13733dfd Mon Sep 17 00:00:00 2001 From: Denys Kuchma Date: Wed, 13 May 2026 15:35:10 +0300 Subject: [PATCH 2/3] format fix --- src/components/LogPane.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/LogPane.tsx b/src/components/LogPane.tsx index 096e488..7394c00 100644 --- a/src/components/LogPane.tsx +++ b/src/components/LogPane.tsx @@ -146,8 +146,7 @@ const LogPane: React.FC = React.memo(({ verboseMode }) => { const cleaned = stripAnsi(dedent(log.content)); const parsed = parseMarkdownToTerminal(cleaned); const lines = parsed.split('\n'); - const truncated = - lines.length > MAX_MULTILINE_LINES ? `${lines.slice(0, MAX_MULTILINE_LINES).join('\n')}\n... (${lines.length - MAX_MULTILINE_LINES} more lines)` : parsed; + const truncated = lines.length > MAX_MULTILINE_LINES ? `${lines.slice(0, MAX_MULTILINE_LINES).join('\n')}\n... (${lines.length - MAX_MULTILINE_LINES} more lines)` : parsed; return ( From 300a49151e8197a273a71b80d0291ca03f24fd2b Mon Sep 17 00:00:00 2001 From: Denys Kuchma Date: Wed, 13 May 2026 16:34:32 +0300 Subject: [PATCH 3/3] format fix 2 --- src/components/LogPane.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/LogPane.tsx b/src/components/LogPane.tsx index 7394c00..b115453 100644 --- a/src/components/LogPane.tsx +++ b/src/components/LogPane.tsx @@ -26,12 +26,12 @@ const LogPane: React.FC = React.memo(({ verboseMode }) => { const MAX_STEP_LINES = 8; const MAX_SUBSTEP_LINES = 6; - const formatCollapsedContent = (lines: string[], collapsedCount: number, label: string) => { + const formatCollapsedContent = useCallback((lines: string[], collapsedCount: number, label: string) => { if (collapsedCount <= 0) { return lines.join('\n'); } return [`... ${collapsedCount} earlier ${label}`, ...lines].join('\n'); - }; + }, []); const flushLogs = useCallback(() => { if (pendingLogsRef.current.length === 0) return; @@ -82,7 +82,7 @@ const LogPane: React.FC = React.memo(({ verboseMode }) => { return result; }); - }, []); + }, [formatCollapsedContent]); const addLog = useCallback( (logEntry: TaggedLogEntry) => {