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/src/ai/tester.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { tool } from 'ai';
|
|
|
4
4
|
import dedent from 'dedent';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { ActionResult } from '../action-result.ts';
|
|
7
|
-
import { setActivity } from '../activity.ts';
|
|
7
|
+
import { clearActivity, setActivity } from '../activity.ts';
|
|
8
8
|
import { ConfigParser } from '../config.ts';
|
|
9
9
|
import type { ExperienceTracker } from '../experience-tracker.ts';
|
|
10
10
|
import type Explorer from '../explorer.ts';
|
|
@@ -13,7 +13,7 @@ import type { StateTransition, WebPageState } from '../state-manager.ts';
|
|
|
13
13
|
import { Stats } from '../stats.ts';
|
|
14
14
|
import { type Note, type Test, TestResult, type TestResultType } from '../test-plan.ts';
|
|
15
15
|
import { detectFocusArea, extractFocusedElement } from '../utils/aria.ts';
|
|
16
|
-
import { ErrorPageError } from '../utils/error-page.ts';
|
|
16
|
+
import { ErrorPageError, isErrorPage } from '../utils/error-page.ts';
|
|
17
17
|
import { HooksRunner } from '../utils/hooks-runner.ts';
|
|
18
18
|
import { codeToMarkdown } from '../utils/html.ts';
|
|
19
19
|
import { createDebug, tag } from '../utils/logger.ts';
|
|
@@ -136,18 +136,13 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
136
136
|
task.addNote(`Network error: ${r.method} ${r.path} → ${r.status}`, TestResult.FAILED);
|
|
137
137
|
});
|
|
138
138
|
|
|
139
|
-
const page = this.explorer.playwrightHelper?.page;
|
|
140
|
-
const onPageError = (err: Error) => {
|
|
141
|
-
task.addNote(`Console error: ${err.message}`, TestResult.FAILED);
|
|
142
|
-
};
|
|
143
|
-
const onConsoleMessage = (msg: any) => {
|
|
144
|
-
if (msg.type() !== 'error') return;
|
|
145
|
-
task.addNote(`Console error: ${msg.text()}`, TestResult.FAILED);
|
|
146
|
-
};
|
|
147
|
-
page?.on('pageerror', onPageError);
|
|
148
|
-
page?.on('console', onConsoleMessage);
|
|
149
|
-
|
|
150
139
|
const initialState = ActionResult.fromState(state);
|
|
140
|
+
if (isErrorPage(initialState)) {
|
|
141
|
+
task.start();
|
|
142
|
+
await this.explorer.startTest(task);
|
|
143
|
+
offFailedRequest?.();
|
|
144
|
+
return await this.abortStartedTestOnErrorPage(task, initialState);
|
|
145
|
+
}
|
|
151
146
|
|
|
152
147
|
const conversation = this.provider.startConversation(this.getSystemMessage(), 'tester');
|
|
153
148
|
conversation.markLastMessageCacheable();
|
|
@@ -176,20 +171,18 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
176
171
|
expected: task.expected,
|
|
177
172
|
},
|
|
178
173
|
},
|
|
179
|
-
async () => this.runTestSession(task, initialState, conversation, { offFailedRequest
|
|
174
|
+
async () => this.runTestSession(task, initialState, conversation, { offFailedRequest })
|
|
180
175
|
);
|
|
181
176
|
}
|
|
182
177
|
|
|
183
|
-
private async runTestSession(task: Test, initialState: ActionResult, conversation: Conversation, handlers:
|
|
184
|
-
const { offFailedRequest
|
|
178
|
+
private async runTestSession(task: Test, initialState: ActionResult, conversation: Conversation, handlers: TestSessionHandlers): Promise<{ success: boolean }> {
|
|
179
|
+
const { offFailedRequest } = handlers;
|
|
185
180
|
|
|
186
181
|
if (this.pilot) {
|
|
187
182
|
try {
|
|
188
183
|
const plan = await this.pilot.planTest(task, initialState);
|
|
189
184
|
if (task.hasFinished) {
|
|
190
185
|
offFailedRequest?.();
|
|
191
|
-
page?.off('pageerror', onPageError);
|
|
192
|
-
page?.off('console', onConsoleMessage);
|
|
193
186
|
return { success: task.isSuccessful };
|
|
194
187
|
}
|
|
195
188
|
if (plan) {
|
|
@@ -201,22 +194,39 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
201
194
|
task.addNote(`Planning failed: ${message}`, TestResult.FAILED);
|
|
202
195
|
task.finish(TestResult.FAILED);
|
|
203
196
|
offFailedRequest?.();
|
|
204
|
-
page?.off('pageerror', onPageError);
|
|
205
|
-
page?.off('console', onConsoleMessage);
|
|
206
197
|
return { success: false };
|
|
207
198
|
}
|
|
208
199
|
}
|
|
209
200
|
|
|
210
201
|
debugLog('Starting test execution with tools');
|
|
211
202
|
|
|
212
|
-
|
|
213
|
-
|
|
203
|
+
if (!(await this.explorer.startTest(task))) {
|
|
204
|
+
offFailedRequest?.();
|
|
205
|
+
await this.cleanupStartedTest(task);
|
|
206
|
+
return { success: task.isSuccessful };
|
|
207
|
+
}
|
|
214
208
|
|
|
215
209
|
debugLog(`Navigating to ${task.startUrl}`);
|
|
216
|
-
|
|
210
|
+
try {
|
|
211
|
+
await this.explorer.visit(task.startUrl!);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
const result = await this.handleLoopError(task, error);
|
|
214
|
+
if (result === 'stop') {
|
|
215
|
+
offFailedRequest?.();
|
|
216
|
+
await this.cleanupStartedTest(task);
|
|
217
|
+
return { success: task.isSuccessful };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
217
220
|
|
|
218
221
|
const startState = this.explorer.getStateManager().getCurrentState();
|
|
219
|
-
if (startState)
|
|
222
|
+
if (startState) {
|
|
223
|
+
task.addUrlNote(startState);
|
|
224
|
+
const startActionResult = ActionResult.fromState(startState);
|
|
225
|
+
if (isErrorPage(startActionResult)) {
|
|
226
|
+
offFailedRequest?.();
|
|
227
|
+
return await this.abortStartedTestOnErrorPage(task, startActionResult);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
220
230
|
const currentUrl = startState?.url || task.startUrl || '';
|
|
221
231
|
await this.hooksRunner.runBeforeHook('tester', currentUrl);
|
|
222
232
|
|
|
@@ -238,6 +248,12 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
238
248
|
await loop(
|
|
239
249
|
async ({ stop, pause, iteration, userInput }) => {
|
|
240
250
|
debugLog('iteration', iteration);
|
|
251
|
+
if (!(await this.explorer.ensurePageAvailable())) {
|
|
252
|
+
task.addNote('Browser page is unavailable');
|
|
253
|
+
task.finish(TestResult.FAILED);
|
|
254
|
+
stop();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
241
257
|
const currentState = this.getCurrentState();
|
|
242
258
|
|
|
243
259
|
const tools = {
|
|
@@ -385,22 +401,16 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
385
401
|
}
|
|
386
402
|
: undefined,
|
|
387
403
|
catch: async ({ error, stop }) => {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (!task.hasFinished) {
|
|
391
|
-
task.addNote(`Execution error: ${message}`);
|
|
392
|
-
}
|
|
393
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
394
|
-
stop();
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
conversation.addUserText(`Previous AI call failed: ${message}. Take a different approach on the next step.`);
|
|
404
|
+
const result = await this.handleLoopError(task, error);
|
|
405
|
+
if (result === 'stop') stop();
|
|
398
406
|
},
|
|
399
407
|
}
|
|
400
408
|
);
|
|
401
409
|
|
|
402
410
|
if (task.hasFinished) break;
|
|
403
411
|
|
|
412
|
+
if (!(await this.explorer.ensurePageAvailable())) break;
|
|
413
|
+
|
|
404
414
|
const finalState = this.getCurrentState();
|
|
405
415
|
const wantsContinue = await this.pilot!.finalReview(task, finalState, conversation, this.navigator);
|
|
406
416
|
|
|
@@ -429,14 +439,8 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
429
439
|
|
|
430
440
|
offStateChange();
|
|
431
441
|
offFailedRequest?.();
|
|
432
|
-
page?.off('pageerror', onPageError);
|
|
433
|
-
page?.off('console', onConsoleMessage);
|
|
434
442
|
await this.finishTest(task);
|
|
435
|
-
await this.explorer.stopTest(task,
|
|
436
|
-
startUrl: task.startUrl,
|
|
437
|
-
style: task.style,
|
|
438
|
-
sessionName: task.sessionName,
|
|
439
|
-
});
|
|
443
|
+
await this.explorer.stopTest(task, this.buildStopTestMeta(task));
|
|
440
444
|
|
|
441
445
|
return {
|
|
442
446
|
success: task.isSuccessful,
|
|
@@ -689,6 +693,26 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
689
693
|
}
|
|
690
694
|
}
|
|
691
695
|
|
|
696
|
+
private async abortStartedTestOnErrorPage(task: Test, actionResult: ActionResult): Promise<{ success: boolean }> {
|
|
697
|
+
const error = new ErrorPageError(actionResult.url || task.startUrl || '', actionResult.title, actionResult.httpStatus);
|
|
698
|
+
tag('warning').log(error.message);
|
|
699
|
+
task.addNote(error.message, TestResult.FAILED, actionResult.screenshotFile, actionResult.fullUrl || actionResult.url);
|
|
700
|
+
task.finish(TestResult.FAILED);
|
|
701
|
+
this.finishTest(task);
|
|
702
|
+
await this.explorer.stopTest(task, this.buildStopTestMeta(task));
|
|
703
|
+
clearActivity(true);
|
|
704
|
+
return { success: false };
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
private buildStopTestMeta(task: Test): Record<string, string> {
|
|
708
|
+
const meta: Record<string, string> = {
|
|
709
|
+
startUrl: task.startUrl,
|
|
710
|
+
};
|
|
711
|
+
if (task.style) meta.style = task.style;
|
|
712
|
+
if (task.sessionName) meta.sessionName = task.sessionName;
|
|
713
|
+
return meta;
|
|
714
|
+
}
|
|
715
|
+
|
|
692
716
|
getSystemMessage(): string {
|
|
693
717
|
return dedent`
|
|
694
718
|
<role>
|
|
@@ -1093,4 +1117,40 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
1093
1117
|
}),
|
|
1094
1118
|
};
|
|
1095
1119
|
}
|
|
1120
|
+
|
|
1121
|
+
private async handleLoopError(task: Test, error: unknown): Promise<'continue' | 'stop'> {
|
|
1122
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1123
|
+
if (!task.hasFinished) task.addNote(`Execution error: ${message}`);
|
|
1124
|
+
|
|
1125
|
+
const result = await this.explorer.handleExecutionError(error);
|
|
1126
|
+
tag('info').log(`Browser supervisor: ${result.action} - ${result.message}`);
|
|
1127
|
+
task.addNote(result.message);
|
|
1128
|
+
|
|
1129
|
+
if (result.action === 'stop') {
|
|
1130
|
+
task.finish(TestResult.FAILED);
|
|
1131
|
+
return 'stop';
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
if (result.recovered) {
|
|
1135
|
+
this.resetFailureCount();
|
|
1136
|
+
this.previousUrl = null;
|
|
1137
|
+
this.previousStateHash = null;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
this.currentConversation?.addUserText(result.message);
|
|
1141
|
+
return 'continue';
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
private async cleanupStartedTest(task: Test): Promise<void> {
|
|
1145
|
+
await this.finishTest(task);
|
|
1146
|
+
await this.explorer.stopTest(task, {
|
|
1147
|
+
startUrl: task.startUrl,
|
|
1148
|
+
style: task.style,
|
|
1149
|
+
sessionName: task.sessionName,
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
interface TestSessionHandlers {
|
|
1155
|
+
offFailedRequest?: () => void;
|
|
1096
1156
|
}
|
package/src/ai/tools.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { ExperienceTracker } from '../experience-tracker.ts';
|
|
|
6
6
|
import type Explorer from '../explorer.ts';
|
|
7
7
|
import { type Task, TestResult } from '../test-plan.js';
|
|
8
8
|
import { extractFocusedElement } from '../utils/aria.ts';
|
|
9
|
+
import { isFatalBrowserError } from '../utils/browser-errors.ts';
|
|
9
10
|
import { createDebug, tag } from '../utils/logger.js';
|
|
10
11
|
import { pause } from '../utils/loop.js';
|
|
11
12
|
import { WebElement } from '../utils/web-element.ts';
|
|
@@ -287,6 +288,7 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
287
288
|
suggestion: 'Verify the key name is correct. For typing text, use form() tool instead.',
|
|
288
289
|
});
|
|
289
290
|
} catch (error) {
|
|
291
|
+
throwIfFatalBrowserError(error);
|
|
290
292
|
activeNote.commit(TestResult.FAILED);
|
|
291
293
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
292
294
|
return failedToolResult('pressKey', `PressKey tool failed: ${errorMessage}`);
|
|
@@ -405,6 +407,7 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
405
407
|
action
|
|
406
408
|
);
|
|
407
409
|
} catch (error) {
|
|
410
|
+
throwIfFatalBrowserError(error);
|
|
408
411
|
activeNote.commit(TestResult.FAILED);
|
|
409
412
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
410
413
|
return failedToolResult('form', `Form tool failed: ${errorMessage}`);
|
|
@@ -444,8 +447,7 @@ export function createSpecialContextTools(explorer: Explorer, context: 'iframe')
|
|
|
444
447
|
|
|
445
448
|
await explorer.switchToMainFrame();
|
|
446
449
|
|
|
447
|
-
const
|
|
448
|
-
const nextState = await action.capturePageState();
|
|
450
|
+
const nextState = await explorer.capturePageState();
|
|
449
451
|
const toolResult = await nextState.toToolResult(previousState, 'I.switchTo()');
|
|
450
452
|
|
|
451
453
|
return successToolResult('exitIframe', {
|
|
@@ -454,6 +456,7 @@ export function createSpecialContextTools(explorer: Explorer, context: 'iframe')
|
|
|
454
456
|
code: 'I.switchTo()',
|
|
455
457
|
});
|
|
456
458
|
} catch (error) {
|
|
459
|
+
throwIfFatalBrowserError(error);
|
|
457
460
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
458
461
|
return failedToolResult('exitIframe', `Failed to exit iframe: ${errorMessage}`);
|
|
459
462
|
}
|
|
@@ -500,8 +503,7 @@ export function createAgentTools({
|
|
|
500
503
|
}
|
|
501
504
|
|
|
502
505
|
try {
|
|
503
|
-
const
|
|
504
|
-
const actionResult = await action.caputrePageWithScreenshot();
|
|
506
|
+
const actionResult = await explorer.capturePageWithScreenshot();
|
|
505
507
|
|
|
506
508
|
if (!actionResult.screenshot) {
|
|
507
509
|
return failedToolResult('see', 'Failed to capture screenshot for analysis');
|
|
@@ -519,6 +521,7 @@ export function createAgentTools({
|
|
|
519
521
|
suggestion: 'Visual confirmation is valid evidence for test results. Use record() to note the visual findings.',
|
|
520
522
|
});
|
|
521
523
|
} catch (error) {
|
|
524
|
+
throwIfFatalBrowserError(error);
|
|
522
525
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
523
526
|
visionDisabled = true;
|
|
524
527
|
tag('warning').log('⚠️ Vision model is not available. Visual checks are disabled for this session.');
|
|
@@ -600,8 +603,7 @@ export function createAgentTools({
|
|
|
600
603
|
});
|
|
601
604
|
}
|
|
602
605
|
|
|
603
|
-
const
|
|
604
|
-
const actionResult = await action.capturePageState();
|
|
606
|
+
const actionResult = await explorer.capturePageState();
|
|
605
607
|
const result = await navigator.verifyState(assertion, actionResult);
|
|
606
608
|
|
|
607
609
|
if (result.verified) {
|
|
@@ -619,6 +621,7 @@ export function createAgentTools({
|
|
|
619
621
|
suggestion: 'The assertion could not be verified. Check if the condition is actually present on the page or try a different assertion.',
|
|
620
622
|
});
|
|
621
623
|
} catch (error) {
|
|
624
|
+
throwIfFatalBrowserError(error);
|
|
622
625
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
623
626
|
return failedToolResult('verify', `Verify tool failed: ${errorMessage}`, {
|
|
624
627
|
error: errorMessage,
|
|
@@ -674,6 +677,7 @@ export function createAgentTools({
|
|
|
674
677
|
`,
|
|
675
678
|
});
|
|
676
679
|
} catch (error) {
|
|
680
|
+
throwIfFatalBrowserError(error);
|
|
677
681
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
678
682
|
return failedToolResult('research', `Research tool failed: ${errorMessage}`, {
|
|
679
683
|
error: errorMessage,
|
|
@@ -718,6 +722,7 @@ export function createAgentTools({
|
|
|
718
722
|
suggestion: 'The action could not be completed. Try a different instruction or use more specific element descriptions.',
|
|
719
723
|
});
|
|
720
724
|
} catch (error) {
|
|
725
|
+
throwIfFatalBrowserError(error);
|
|
721
726
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
722
727
|
return failedToolResult('interact', `Interact tool failed: ${errorMessage}`, {
|
|
723
728
|
error: errorMessage,
|
|
@@ -756,7 +761,7 @@ export function createAgentTools({
|
|
|
756
761
|
|
|
757
762
|
const previousState = ActionResult.fromState(currentState);
|
|
758
763
|
const action = explorer.createAction();
|
|
759
|
-
const actionResult = await
|
|
764
|
+
const actionResult = await explorer.capturePageWithScreenshot();
|
|
760
765
|
|
|
761
766
|
if (!actionResult.screenshot) {
|
|
762
767
|
return failedToolResult('visualClick', 'Failed to capture screenshot for visual analysis');
|
|
@@ -797,6 +802,7 @@ export function createAgentTools({
|
|
|
797
802
|
analysis: locationResult,
|
|
798
803
|
});
|
|
799
804
|
} catch (error) {
|
|
805
|
+
throwIfFatalBrowserError(error);
|
|
800
806
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
801
807
|
visionDisabled = true;
|
|
802
808
|
tag('warning').log('⚠️ Vision model is not available. Visual clicks are disabled for this session.');
|
|
@@ -1018,6 +1024,10 @@ function cap(text: string | undefined | null, max: number): string {
|
|
|
1018
1024
|
return `${text.slice(0, max)}\n[...truncated; ${text.length - max} chars omitted...]`;
|
|
1019
1025
|
}
|
|
1020
1026
|
|
|
1027
|
+
function throwIfFatalBrowserError(error: unknown): void {
|
|
1028
|
+
if (isFatalBrowserError(error)) throw error;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1021
1031
|
function transformContainsCommand(command: string): string {
|
|
1022
1032
|
if (!command.includes(':contains(')) return command;
|
|
1023
1033
|
|
|
@@ -5,7 +5,7 @@ import { normalizeUrl } from '../state-manager.js';
|
|
|
5
5
|
import { Stats } from '../stats.js';
|
|
6
6
|
import { type Plan, type Test, TestResult } from '../test-plan.js';
|
|
7
7
|
import { getCliName } from '../utils/cli-name.ts';
|
|
8
|
-
import { ErrorPageError } from '../utils/error-page.ts';
|
|
8
|
+
import { ErrorPageError, getStateErrorPageError } from '../utils/error-page.ts';
|
|
9
9
|
import { tag } from '../utils/logger.js';
|
|
10
10
|
import { type NextStepSection, printNextSteps, relativeToCwd } from '../utils/next-steps.ts';
|
|
11
11
|
import { safeFilename } from '../utils/strings.ts';
|
|
@@ -56,6 +56,11 @@ export class ExploreCommand extends BaseCommand {
|
|
|
56
56
|
Stats.mode ??= 'explore';
|
|
57
57
|
Stats.focus ??= feature;
|
|
58
58
|
const mainUrl = this.getCurrentPageUrl();
|
|
59
|
+
const error = getStateErrorPageError(this.explorBot.getExplorer().getStateManager().getCurrentState());
|
|
60
|
+
if (error) {
|
|
61
|
+
tag('warning').log(error.message);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
59
64
|
|
|
60
65
|
if (cfg.enabled) {
|
|
61
66
|
await this.runReuseMode(mainUrl, feature, cfg);
|
|
@@ -121,6 +121,7 @@ const LogPane: React.FC<LogPaneProps> = React.memo(({ verboseMode }) => {
|
|
|
121
121
|
case 'step':
|
|
122
122
|
return { color: 'cyan' as const, dimColor: true };
|
|
123
123
|
case 'multiline':
|
|
124
|
+
case 'details':
|
|
124
125
|
return { color: 'gray' as const, dimColor: true };
|
|
125
126
|
case 'html':
|
|
126
127
|
return { color: 'gray' as const };
|
|
@@ -143,16 +144,16 @@ const LogPane: React.FC<LogPaneProps> = React.memo(({ verboseMode }) => {
|
|
|
143
144
|
}
|
|
144
145
|
const styles = getLogStyles(log.type);
|
|
145
146
|
|
|
146
|
-
if (log.type === 'multiline') {
|
|
147
|
+
if (log.type === 'multiline' || log.type === 'details') {
|
|
147
148
|
const cleaned = stripAnsi(dedent(log.content));
|
|
148
149
|
const parsed = parseMarkdownToTerminal(cleaned);
|
|
149
150
|
const lines = parsed.split('\n');
|
|
150
151
|
const maxLines = log.maxLines || 16;
|
|
151
|
-
const
|
|
152
|
+
const content = log.type === 'details' ? parsed : lines.length > maxLines ? `${lines.slice(0, maxLines).join('\n')}\n... (${lines.length - maxLines} more lines)` : parsed;
|
|
152
153
|
return (
|
|
153
154
|
<Box key={index} borderStyle="classic" borderLeft={false} borderRight={false} marginY={1} padding={1} borderColor="dim" overflow="hidden">
|
|
154
155
|
<Text color="gray" dimColor>
|
|
155
|
-
{
|
|
156
|
+
{content}
|
|
156
157
|
</Text>
|
|
157
158
|
</Box>
|
|
158
159
|
);
|