explorbot 0.1.27 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +1 -1
- package/dist/src/action-result.js +2 -0
- package/dist/src/action.js +88 -7
- package/dist/src/ai/captain/file-tools.js +100 -0
- package/dist/src/ai/captain/idle-mode.js +70 -6
- package/dist/src/ai/captain/web-mode.js +36 -6
- package/dist/src/ai/captain.js +87 -19
- package/dist/src/ai/historian/screencast.js +11 -2
- package/dist/src/ai/navigator.js +5 -2
- package/dist/src/ai/pilot.js +33 -5
- package/dist/src/ai/researcher/coordinates.js +2 -3
- package/dist/src/ai/researcher/deep-analysis.js +3 -4
- package/dist/src/ai/researcher/locators.js +1 -2
- package/dist/src/ai/researcher.js +17 -18
- package/dist/src/ai/task-agent.js +1 -0
- package/dist/src/ai/tester.js +91 -39
- package/dist/src/ai/tools.js +17 -7
- package/dist/src/commands/explore-command.js +6 -1
- package/dist/src/components/LogPane.js +4 -3
- package/dist/src/explorer.js +270 -35
- package/dist/src/utils/browser-errors.js +23 -0
- package/dist/src/utils/error-page.js +17 -2
- package/dist/src/utils/logger.js +2 -2
- package/package.json +1 -1
- package/src/action-result.ts +2 -0
- package/src/action.ts +83 -7
- package/src/ai/captain/file-tools.ts +126 -0
- package/src/ai/captain/idle-mode.ts +72 -6
- package/src/ai/captain/mixin.ts +1 -1
- package/src/ai/captain/web-mode.ts +40 -5
- package/src/ai/captain.ts +94 -20
- package/src/ai/historian/screencast.ts +11 -2
- package/src/ai/navigator.ts +6 -2
- package/src/ai/pilot.ts +34 -5
- package/src/ai/researcher/coordinates.ts +2 -3
- package/src/ai/researcher/deep-analysis.ts +3 -4
- package/src/ai/researcher/locators.ts +1 -2
- package/src/ai/researcher.ts +17 -18
- package/src/ai/task-agent.ts +1 -1
- package/src/ai/tester.ts +101 -41
- package/src/ai/tools.ts +17 -7
- package/src/commands/explore-command.ts +6 -1
- package/src/components/LogPane.tsx +4 -3
- package/src/explorer.ts +295 -38
- package/src/state-manager.ts +2 -0
- package/src/utils/browser-errors.ts +25 -0
- package/src/utils/error-page.ts +16 -3
- package/src/utils/logger.ts +3 -3
package/dist/src/ai/tester.js
CHANGED
|
@@ -4,13 +4,13 @@ import { tool } from 'ai';
|
|
|
4
4
|
import dedent from 'dedent';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { ActionResult } from "../action-result.js";
|
|
7
|
-
import { setActivity } from "../activity.js";
|
|
7
|
+
import { clearActivity, setActivity } from "../activity.js";
|
|
8
8
|
import { ConfigParser } from "../config.js";
|
|
9
9
|
import { Observability } from "../observability.js";
|
|
10
10
|
import { Stats } from "../stats.js";
|
|
11
11
|
import { TestResult } from "../test-plan.js";
|
|
12
12
|
import { detectFocusArea, extractFocusedElement } from "../utils/aria.js";
|
|
13
|
-
import { ErrorPageError } from "../utils/error-page.js";
|
|
13
|
+
import { ErrorPageError, isErrorPage } from "../utils/error-page.js";
|
|
14
14
|
import { HooksRunner } from "../utils/hooks-runner.js";
|
|
15
15
|
import { createDebug, tag } from "../utils/logger.js";
|
|
16
16
|
import { loop } from "../utils/loop.js";
|
|
@@ -107,18 +107,13 @@ export class Tester extends TaskAgent {
|
|
|
107
107
|
const offFailedRequest = requestStore?.onFailedRequest((r) => {
|
|
108
108
|
task.addNote(`Network error: ${r.method} ${r.path} → ${r.status}`, TestResult.FAILED);
|
|
109
109
|
});
|
|
110
|
-
const page = this.explorer.playwrightHelper?.page;
|
|
111
|
-
const onPageError = (err) => {
|
|
112
|
-
task.addNote(`Console error: ${err.message}`, TestResult.FAILED);
|
|
113
|
-
};
|
|
114
|
-
const onConsoleMessage = (msg) => {
|
|
115
|
-
if (msg.type() !== 'error')
|
|
116
|
-
return;
|
|
117
|
-
task.addNote(`Console error: ${msg.text()}`, TestResult.FAILED);
|
|
118
|
-
};
|
|
119
|
-
page?.on('pageerror', onPageError);
|
|
120
|
-
page?.on('console', onConsoleMessage);
|
|
121
110
|
const initialState = ActionResult.fromState(state);
|
|
111
|
+
if (isErrorPage(initialState)) {
|
|
112
|
+
task.start();
|
|
113
|
+
await this.explorer.startTest(task);
|
|
114
|
+
offFailedRequest?.();
|
|
115
|
+
return await this.abortStartedTestOnErrorPage(task, initialState);
|
|
116
|
+
}
|
|
122
117
|
const conversation = this.provider.startConversation(this.getSystemMessage(), 'tester');
|
|
123
118
|
conversation.markLastMessageCacheable();
|
|
124
119
|
this.currentConversation = conversation;
|
|
@@ -140,17 +135,15 @@ export class Tester extends TaskAgent {
|
|
|
140
135
|
startUrl: task.startUrl,
|
|
141
136
|
expected: task.expected,
|
|
142
137
|
},
|
|
143
|
-
}, async () => this.runTestSession(task, initialState, conversation, { offFailedRequest
|
|
138
|
+
}, async () => this.runTestSession(task, initialState, conversation, { offFailedRequest }));
|
|
144
139
|
}
|
|
145
140
|
async runTestSession(task, initialState, conversation, handlers) {
|
|
146
|
-
const { offFailedRequest
|
|
141
|
+
const { offFailedRequest } = handlers;
|
|
147
142
|
if (this.pilot) {
|
|
148
143
|
try {
|
|
149
144
|
const plan = await this.pilot.planTest(task, initialState);
|
|
150
145
|
if (task.hasFinished) {
|
|
151
146
|
offFailedRequest?.();
|
|
152
|
-
page?.off('pageerror', onPageError);
|
|
153
|
-
page?.off('console', onConsoleMessage);
|
|
154
147
|
return { success: task.isSuccessful };
|
|
155
148
|
}
|
|
156
149
|
if (plan) {
|
|
@@ -163,19 +156,36 @@ export class Tester extends TaskAgent {
|
|
|
163
156
|
task.addNote(`Planning failed: ${message}`, TestResult.FAILED);
|
|
164
157
|
task.finish(TestResult.FAILED);
|
|
165
158
|
offFailedRequest?.();
|
|
166
|
-
page?.off('pageerror', onPageError);
|
|
167
|
-
page?.off('console', onConsoleMessage);
|
|
168
159
|
return { success: false };
|
|
169
160
|
}
|
|
170
161
|
}
|
|
171
162
|
debugLog('Starting test execution with tools');
|
|
172
|
-
|
|
173
|
-
|
|
163
|
+
if (!(await this.explorer.startTest(task))) {
|
|
164
|
+
offFailedRequest?.();
|
|
165
|
+
await this.cleanupStartedTest(task);
|
|
166
|
+
return { success: task.isSuccessful };
|
|
167
|
+
}
|
|
174
168
|
debugLog(`Navigating to ${task.startUrl}`);
|
|
175
|
-
|
|
169
|
+
try {
|
|
170
|
+
await this.explorer.visit(task.startUrl);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
const result = await this.handleLoopError(task, error);
|
|
174
|
+
if (result === 'stop') {
|
|
175
|
+
offFailedRequest?.();
|
|
176
|
+
await this.cleanupStartedTest(task);
|
|
177
|
+
return { success: task.isSuccessful };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
176
180
|
const startState = this.explorer.getStateManager().getCurrentState();
|
|
177
|
-
if (startState)
|
|
181
|
+
if (startState) {
|
|
178
182
|
task.addUrlNote(startState);
|
|
183
|
+
const startActionResult = ActionResult.fromState(startState);
|
|
184
|
+
if (isErrorPage(startActionResult)) {
|
|
185
|
+
offFailedRequest?.();
|
|
186
|
+
return await this.abortStartedTestOnErrorPage(task, startActionResult);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
179
189
|
const currentUrl = startState?.url || task.startUrl || '';
|
|
180
190
|
await this.hooksRunner.runBeforeHook('tester', currentUrl);
|
|
181
191
|
const offStateChange = this.explorer.getStateManager().onStateChange((event) => {
|
|
@@ -195,6 +205,12 @@ export class Tester extends TaskAgent {
|
|
|
195
205
|
shouldContinue = false;
|
|
196
206
|
await loop(async ({ stop, pause, iteration, userInput }) => {
|
|
197
207
|
debugLog('iteration', iteration);
|
|
208
|
+
if (!(await this.explorer.ensurePageAvailable())) {
|
|
209
|
+
task.addNote('Browser page is unavailable');
|
|
210
|
+
task.finish(TestResult.FAILED);
|
|
211
|
+
stop();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
198
214
|
const currentState = this.getCurrentState();
|
|
199
215
|
const tools = {
|
|
200
216
|
...codeceptjsTools,
|
|
@@ -327,20 +343,15 @@ export class Tester extends TaskAgent {
|
|
|
327
343
|
}
|
|
328
344
|
: undefined,
|
|
329
345
|
catch: async ({ error, stop }) => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (!task.hasFinished) {
|
|
333
|
-
task.addNote(`Execution error: ${message}`);
|
|
334
|
-
}
|
|
335
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
346
|
+
const result = await this.handleLoopError(task, error);
|
|
347
|
+
if (result === 'stop')
|
|
336
348
|
stop();
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
conversation.addUserText(`Previous AI call failed: ${message}. Take a different approach on the next step.`);
|
|
340
349
|
},
|
|
341
350
|
});
|
|
342
351
|
if (task.hasFinished)
|
|
343
352
|
break;
|
|
353
|
+
if (!(await this.explorer.ensurePageAvailable()))
|
|
354
|
+
break;
|
|
344
355
|
const finalState = this.getCurrentState();
|
|
345
356
|
const wantsContinue = await this.pilot.finalReview(task, finalState, conversation, this.navigator);
|
|
346
357
|
if (!wantsContinue || task.hasFinished)
|
|
@@ -366,14 +377,8 @@ export class Tester extends TaskAgent {
|
|
|
366
377
|
await this.getQuartermaster().analyzeSession(task, initialState, conversation);
|
|
367
378
|
offStateChange();
|
|
368
379
|
offFailedRequest?.();
|
|
369
|
-
page?.off('pageerror', onPageError);
|
|
370
|
-
page?.off('console', onConsoleMessage);
|
|
371
380
|
await this.finishTest(task);
|
|
372
|
-
await this.explorer.stopTest(task,
|
|
373
|
-
startUrl: task.startUrl,
|
|
374
|
-
style: task.style,
|
|
375
|
-
sessionName: task.sessionName,
|
|
376
|
-
});
|
|
381
|
+
await this.explorer.stopTest(task, this.buildStopTestMeta(task));
|
|
377
382
|
return {
|
|
378
383
|
success: task.isSuccessful,
|
|
379
384
|
...task,
|
|
@@ -607,6 +612,26 @@ export class Tester extends TaskAgent {
|
|
|
607
612
|
tag('warning').log(`Test with no result: ${task.scenario}`);
|
|
608
613
|
}
|
|
609
614
|
}
|
|
615
|
+
async abortStartedTestOnErrorPage(task, actionResult) {
|
|
616
|
+
const error = new ErrorPageError(actionResult.url || task.startUrl || '', actionResult.title, actionResult.httpStatus);
|
|
617
|
+
tag('warning').log(error.message);
|
|
618
|
+
task.addNote(error.message, TestResult.FAILED, actionResult.screenshotFile, actionResult.fullUrl || actionResult.url);
|
|
619
|
+
task.finish(TestResult.FAILED);
|
|
620
|
+
this.finishTest(task);
|
|
621
|
+
await this.explorer.stopTest(task, this.buildStopTestMeta(task));
|
|
622
|
+
clearActivity(true);
|
|
623
|
+
return { success: false };
|
|
624
|
+
}
|
|
625
|
+
buildStopTestMeta(task) {
|
|
626
|
+
const meta = {
|
|
627
|
+
startUrl: task.startUrl,
|
|
628
|
+
};
|
|
629
|
+
if (task.style)
|
|
630
|
+
meta.style = task.style;
|
|
631
|
+
if (task.sessionName)
|
|
632
|
+
meta.sessionName = task.sessionName;
|
|
633
|
+
return meta;
|
|
634
|
+
}
|
|
610
635
|
getSystemMessage() {
|
|
611
636
|
return dedent `
|
|
612
637
|
<role>
|
|
@@ -991,4 +1016,31 @@ export class Tester extends TaskAgent {
|
|
|
991
1016
|
}),
|
|
992
1017
|
};
|
|
993
1018
|
}
|
|
1019
|
+
async handleLoopError(task, error) {
|
|
1020
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1021
|
+
if (!task.hasFinished)
|
|
1022
|
+
task.addNote(`Execution error: ${message}`);
|
|
1023
|
+
const result = await this.explorer.handleExecutionError(error);
|
|
1024
|
+
tag('info').log(`Browser supervisor: ${result.action} - ${result.message}`);
|
|
1025
|
+
task.addNote(result.message);
|
|
1026
|
+
if (result.action === 'stop') {
|
|
1027
|
+
task.finish(TestResult.FAILED);
|
|
1028
|
+
return 'stop';
|
|
1029
|
+
}
|
|
1030
|
+
if (result.recovered) {
|
|
1031
|
+
this.resetFailureCount();
|
|
1032
|
+
this.previousUrl = null;
|
|
1033
|
+
this.previousStateHash = null;
|
|
1034
|
+
}
|
|
1035
|
+
this.currentConversation?.addUserText(result.message);
|
|
1036
|
+
return 'continue';
|
|
1037
|
+
}
|
|
1038
|
+
async cleanupStartedTest(task) {
|
|
1039
|
+
await this.finishTest(task);
|
|
1040
|
+
await this.explorer.stopTest(task, {
|
|
1041
|
+
startUrl: task.startUrl,
|
|
1042
|
+
style: task.style,
|
|
1043
|
+
sessionName: task.sessionName,
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
994
1046
|
}
|
package/dist/src/ai/tools.js
CHANGED
|
@@ -4,6 +4,7 @@ import { z } from 'zod';
|
|
|
4
4
|
import { ActionResult } from "../action-result.js";
|
|
5
5
|
import { TestResult } from '../test-plan.js';
|
|
6
6
|
import { extractFocusedElement } from "../utils/aria.js";
|
|
7
|
+
import { isFatalBrowserError } from "../utils/browser-errors.js";
|
|
7
8
|
import { createDebug, tag } from '../utils/logger.js';
|
|
8
9
|
import { pause } from '../utils/loop.js';
|
|
9
10
|
import { WebElement } from "../utils/web-element.js";
|
|
@@ -243,6 +244,7 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
243
244
|
});
|
|
244
245
|
}
|
|
245
246
|
catch (error) {
|
|
247
|
+
throwIfFatalBrowserError(error);
|
|
246
248
|
activeNote.commit(TestResult.FAILED);
|
|
247
249
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
248
250
|
return failedToolResult('pressKey', `PressKey tool failed: ${errorMessage}`);
|
|
@@ -343,6 +345,7 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
343
345
|
}, action);
|
|
344
346
|
}
|
|
345
347
|
catch (error) {
|
|
348
|
+
throwIfFatalBrowserError(error);
|
|
346
349
|
activeNote.commit(TestResult.FAILED);
|
|
347
350
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
348
351
|
return failedToolResult('form', `Form tool failed: ${errorMessage}`);
|
|
@@ -376,8 +379,7 @@ export function createSpecialContextTools(explorer, context) {
|
|
|
376
379
|
});
|
|
377
380
|
}
|
|
378
381
|
await explorer.switchToMainFrame();
|
|
379
|
-
const
|
|
380
|
-
const nextState = await action.capturePageState();
|
|
382
|
+
const nextState = await explorer.capturePageState();
|
|
381
383
|
const toolResult = await nextState.toToolResult(previousState, 'I.switchTo()');
|
|
382
384
|
return successToolResult('exitIframe', {
|
|
383
385
|
...toolResult,
|
|
@@ -386,6 +388,7 @@ export function createSpecialContextTools(explorer, context) {
|
|
|
386
388
|
});
|
|
387
389
|
}
|
|
388
390
|
catch (error) {
|
|
391
|
+
throwIfFatalBrowserError(error);
|
|
389
392
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
390
393
|
return failedToolResult('exitIframe', `Failed to exit iframe: ${errorMessage}`);
|
|
391
394
|
}
|
|
@@ -415,8 +418,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
415
418
|
return failedToolResult('see', 'Vision tools are disabled for this session. Use context() to get fresh ARIA snapshot and analyze page state from ARIA data.');
|
|
416
419
|
}
|
|
417
420
|
try {
|
|
418
|
-
const
|
|
419
|
-
const actionResult = await action.caputrePageWithScreenshot();
|
|
421
|
+
const actionResult = await explorer.capturePageWithScreenshot();
|
|
420
422
|
if (!actionResult.screenshot) {
|
|
421
423
|
return failedToolResult('see', 'Failed to capture screenshot for analysis');
|
|
422
424
|
}
|
|
@@ -431,6 +433,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
431
433
|
});
|
|
432
434
|
}
|
|
433
435
|
catch (error) {
|
|
436
|
+
throwIfFatalBrowserError(error);
|
|
434
437
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
435
438
|
visionDisabled = true;
|
|
436
439
|
tag('warning').log('⚠️ Vision model is not available. Visual checks are disabled for this session.');
|
|
@@ -506,8 +509,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
506
509
|
suggestion: verifications[assertion] ? 'This verification already passed. Call finish() to complete the test.' : 'This verification already failed. Perform actions to change the page state, then try again.',
|
|
507
510
|
});
|
|
508
511
|
}
|
|
509
|
-
const
|
|
510
|
-
const actionResult = await action.capturePageState();
|
|
512
|
+
const actionResult = await explorer.capturePageState();
|
|
511
513
|
const result = await navigator.verifyState(assertion, actionResult);
|
|
512
514
|
if (result.verified) {
|
|
513
515
|
return successToolResult('verify', {
|
|
@@ -520,6 +522,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
520
522
|
});
|
|
521
523
|
}
|
|
522
524
|
catch (error) {
|
|
525
|
+
throwIfFatalBrowserError(error);
|
|
523
526
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
524
527
|
return failedToolResult('verify', `Verify tool failed: ${errorMessage}`, {
|
|
525
528
|
error: errorMessage,
|
|
@@ -572,6 +575,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
572
575
|
});
|
|
573
576
|
}
|
|
574
577
|
catch (error) {
|
|
578
|
+
throwIfFatalBrowserError(error);
|
|
575
579
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
576
580
|
return failedToolResult('research', `Research tool failed: ${errorMessage}`, {
|
|
577
581
|
error: errorMessage,
|
|
@@ -611,6 +615,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
611
615
|
});
|
|
612
616
|
}
|
|
613
617
|
catch (error) {
|
|
618
|
+
throwIfFatalBrowserError(error);
|
|
614
619
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
615
620
|
return failedToolResult('interact', `Interact tool failed: ${errorMessage}`, {
|
|
616
621
|
error: errorMessage,
|
|
@@ -645,7 +650,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
645
650
|
}
|
|
646
651
|
const previousState = ActionResult.fromState(currentState);
|
|
647
652
|
const action = explorer.createAction();
|
|
648
|
-
const actionResult = await
|
|
653
|
+
const actionResult = await explorer.capturePageWithScreenshot();
|
|
649
654
|
if (!actionResult.screenshot) {
|
|
650
655
|
return failedToolResult('visualClick', 'Failed to capture screenshot for visual analysis');
|
|
651
656
|
}
|
|
@@ -678,6 +683,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
678
683
|
});
|
|
679
684
|
}
|
|
680
685
|
catch (error) {
|
|
686
|
+
throwIfFatalBrowserError(error);
|
|
681
687
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
682
688
|
visionDisabled = true;
|
|
683
689
|
tag('warning').log('⚠️ Vision model is not available. Visual clicks are disabled for this session.');
|
|
@@ -874,6 +880,10 @@ function cap(text, max) {
|
|
|
874
880
|
return text;
|
|
875
881
|
return `${text.slice(0, max)}\n[...truncated; ${text.length - max} chars omitted...]`;
|
|
876
882
|
}
|
|
883
|
+
function throwIfFatalBrowserError(error) {
|
|
884
|
+
if (isFatalBrowserError(error))
|
|
885
|
+
throw error;
|
|
886
|
+
}
|
|
877
887
|
function transformContainsCommand(command) {
|
|
878
888
|
if (!command.includes(':contains('))
|
|
879
889
|
return command;
|
|
@@ -5,7 +5,7 @@ import { normalizeUrl } from '../state-manager.js';
|
|
|
5
5
|
import { Stats } from '../stats.js';
|
|
6
6
|
import { TestResult } from '../test-plan.js';
|
|
7
7
|
import { getCliName } from "../utils/cli-name.js";
|
|
8
|
-
import { ErrorPageError } from "../utils/error-page.js";
|
|
8
|
+
import { ErrorPageError, getStateErrorPageError } from "../utils/error-page.js";
|
|
9
9
|
import { tag } from '../utils/logger.js';
|
|
10
10
|
import { printNextSteps, relativeToCwd } from "../utils/next-steps.js";
|
|
11
11
|
import { safeFilename } from "../utils/strings.js";
|
|
@@ -53,6 +53,11 @@ export class ExploreCommand extends BaseCommand {
|
|
|
53
53
|
Stats.mode ??= 'explore';
|
|
54
54
|
Stats.focus ??= feature;
|
|
55
55
|
const mainUrl = this.getCurrentPageUrl();
|
|
56
|
+
const error = getStateErrorPageError(this.explorBot.getExplorer().getStateManager().getCurrentState());
|
|
57
|
+
if (error) {
|
|
58
|
+
tag('warning').log(error.message);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
56
61
|
if (cfg.enabled) {
|
|
57
62
|
await this.runReuseMode(mainUrl, feature, cfg);
|
|
58
63
|
}
|
|
@@ -94,6 +94,7 @@ const LogPane = React.memo(({ verboseMode }) => {
|
|
|
94
94
|
case 'step':
|
|
95
95
|
return { color: 'cyan', dimColor: true };
|
|
96
96
|
case 'multiline':
|
|
97
|
+
case 'details':
|
|
97
98
|
return { color: 'gray', dimColor: true };
|
|
98
99
|
case 'html':
|
|
99
100
|
return { color: 'gray' };
|
|
@@ -113,14 +114,14 @@ const LogPane = React.memo(({ verboseMode }) => {
|
|
|
113
114
|
return null;
|
|
114
115
|
}
|
|
115
116
|
const styles = getLogStyles(log.type);
|
|
116
|
-
if (log.type === 'multiline') {
|
|
117
|
+
if (log.type === 'multiline' || log.type === 'details') {
|
|
117
118
|
const cleaned = stripAnsi(dedent(log.content));
|
|
118
119
|
const parsed = parseMarkdownToTerminal(cleaned);
|
|
119
120
|
const lines = parsed.split('\n');
|
|
120
121
|
const maxLines = log.maxLines || 16;
|
|
121
|
-
const
|
|
122
|
+
const content = log.type === 'details' ? parsed : lines.length > maxLines ? `${lines.slice(0, maxLines).join('\n')}\n... (${lines.length - maxLines} more lines)` : parsed;
|
|
122
123
|
return (React.createElement(Box, { key: index, borderStyle: "classic", borderLeft: false, borderRight: false, marginY: 1, padding: 1, borderColor: "dim", overflow: "hidden" },
|
|
123
|
-
React.createElement(Text, { color: "gray", dimColor: true },
|
|
124
|
+
React.createElement(Text, { color: "gray", dimColor: true }, content)));
|
|
124
125
|
}
|
|
125
126
|
if (log.type === 'html') {
|
|
126
127
|
// Convert HTML to markdown, then render as multiline
|