explorbot 0.1.27 → 0.1.29
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/README.md +83 -245
- package/bin/explorbot-cli.ts +1 -0
- package/dist/bin/explorbot-cli.js +1 -0
- package/dist/package.json +8 -6
- package/dist/rules/navigator/verification-actions.md +2 -0
- 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/fisherman.js +14 -3
- package/dist/src/ai/historian/screencast.js +11 -2
- package/dist/src/ai/navigator.js +5 -2
- package/dist/src/ai/pilot.js +52 -9
- package/dist/src/ai/planner.js +16 -5
- package/dist/src/ai/provider.js +53 -18
- 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 +24 -19
- package/dist/src/ai/rules.js +44 -0
- package/dist/src/ai/task-agent.js +1 -0
- package/dist/src/ai/tester.js +161 -46
- package/dist/src/ai/tools.js +84 -8
- package/dist/src/commands/explore-command.js +6 -1
- package/dist/src/components/LogPane.js +4 -3
- package/dist/src/explorbot.js +7 -2
- package/dist/src/explorer.js +270 -35
- package/dist/src/stats.js +16 -0
- package/dist/src/utils/aria.js +66 -6
- 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 +8 -6
- package/rules/navigator/verification-actions.md +2 -0
- 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/fisherman.ts +14 -3
- package/src/ai/historian/screencast.ts +11 -2
- package/src/ai/navigator.ts +6 -2
- package/src/ai/pilot.ts +53 -9
- package/src/ai/planner.ts +16 -5
- package/src/ai/provider.ts +51 -19
- 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 +25 -19
- package/src/ai/rules.ts +46 -0
- package/src/ai/task-agent.ts +1 -1
- package/src/ai/tester.ts +175 -48
- package/src/ai/tools.ts +97 -8
- package/src/commands/explore-command.ts +6 -1
- package/src/components/LogPane.tsx +4 -3
- package/src/config.ts +1 -0
- package/src/explorbot.ts +6 -2
- package/src/explorer.ts +295 -38
- package/src/state-manager.ts +2 -0
- package/src/stats.ts +18 -0
- package/src/utils/aria.ts +63 -6
- package/src/utils/browser-errors.ts +25 -0
- package/src/utils/error-page.ts +16 -3
- package/src/utils/logger.ts +3 -3
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Playwright and CodeceptJS surface browser/page disposal as plain Error objects,
|
|
2
|
+
// not typed exceptions. Keep those external message markers in one adapter so
|
|
3
|
+
// recovery decisions are not duplicated across agents/actions.
|
|
4
|
+
const FATAL_BROWSER_ERROR_MARKERS = ['Frame was detached', 'Target closed', 'Target page, context or browser has been closed', 'Execution context was destroyed', 'Protocol error', 'Session closed'];
|
|
5
|
+
|
|
6
|
+
export class BrowserRecoveryError extends Error {
|
|
7
|
+
constructor(
|
|
8
|
+
label: string,
|
|
9
|
+
public originalError: unknown,
|
|
10
|
+
public recovered: boolean
|
|
11
|
+
) {
|
|
12
|
+
super(`${label} failed ${recovered ? 'after browser recovery' : 'because browser could not be recovered'}: ${browserErrorMessage(originalError)}`);
|
|
13
|
+
this.name = 'BrowserRecoveryError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isFatalBrowserError(error: unknown): boolean {
|
|
18
|
+
if (error instanceof BrowserRecoveryError) return true;
|
|
19
|
+
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
20
|
+
return FATAL_BROWSER_ERROR_MARKERS.some((marker) => message.includes(marker.toLowerCase()));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function browserErrorMessage(error: unknown): string {
|
|
24
|
+
return error instanceof Error ? error.message : String(error);
|
|
25
|
+
}
|
package/src/utils/error-page.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ActionResult } from '../action-result.js';
|
|
2
|
+
import type { WebPageState } from '../state-manager.js';
|
|
2
3
|
import { isBodyEmpty } from './html.js';
|
|
3
4
|
|
|
4
5
|
const HTTP_ERRORS = ['400 Bad Request', '401 Unauthorized', '403 Forbidden', '404 Not Found', '405 Method Not Allowed', '408 Request Timeout', '500 Internal Server Error', '502 Bad Gateway', '503 Service Unavailable', '504 Gateway Timeout'];
|
|
@@ -9,6 +10,8 @@ const LOADING_WORD = /\bloading\b/i;
|
|
|
9
10
|
export type PageCondition = 'ok' | 'loading' | 'error';
|
|
10
11
|
|
|
11
12
|
export function detectPageCondition(actionResult: ActionResult): PageCondition {
|
|
13
|
+
if (actionResult.httpStatus && actionResult.httpStatus >= 400) return 'error';
|
|
14
|
+
|
|
12
15
|
const headingFields = [actionResult.title, actionResult.h1, actionResult.h2].filter(Boolean) as string[];
|
|
13
16
|
|
|
14
17
|
for (const field of headingFields) {
|
|
@@ -37,12 +40,22 @@ export function isErrorPage(actionResult: ActionResult): boolean {
|
|
|
37
40
|
return detectPageCondition(actionResult) === 'error';
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
export function getStateErrorPageError(state: WebPageState | null | undefined): ErrorPageError | null {
|
|
44
|
+
if (!state) return null;
|
|
45
|
+
const actionResult = ActionResult.fromState(state);
|
|
46
|
+
if (!isErrorPage(actionResult)) return null;
|
|
47
|
+
return new ErrorPageError(actionResult.url, actionResult.title, actionResult.httpStatus);
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
export class ErrorPageError extends Error {
|
|
41
51
|
constructor(
|
|
42
52
|
public readonly url: string,
|
|
43
|
-
public readonly title?: string
|
|
53
|
+
public readonly title?: string,
|
|
54
|
+
public readonly httpStatus?: number
|
|
44
55
|
) {
|
|
45
|
-
|
|
56
|
+
const status = httpStatus ? `HTTP ${httpStatus}` : '';
|
|
57
|
+
const details = [status, title].filter(Boolean).join(', ');
|
|
58
|
+
super(`Error page detected at ${url}${details ? ` (${details})` : ''}`);
|
|
46
59
|
this.name = 'ErrorPageError';
|
|
47
60
|
}
|
|
48
61
|
}
|
package/src/utils/logger.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { Observability } from '../observability.ts';
|
|
|
11
11
|
import { RecentStepFilter } from './log-filters.ts';
|
|
12
12
|
import { parseMarkdownToTerminal } from './markdown-terminal.ts';
|
|
13
13
|
|
|
14
|
-
export type LogType = 'info' | 'success' | 'error' | 'warning' | 'debug' | 'substep' | 'operation' | 'step' | 'multiline' | 'html' | 'input';
|
|
14
|
+
export type LogType = 'info' | 'success' | 'error' | 'warning' | 'debug' | 'substep' | 'operation' | 'step' | 'multiline' | 'details' | 'html' | 'input';
|
|
15
15
|
|
|
16
16
|
export interface TaggedLogEntry {
|
|
17
17
|
type: LogType;
|
|
@@ -99,7 +99,7 @@ class ConsoleDestination implements LogDestination {
|
|
|
99
99
|
if (entry.type === 'operation' && !this.verboseMode) return;
|
|
100
100
|
if (entry.type === 'step' && !this.verboseMode && this.recentSteps.shouldSuppress(entry.content)) return;
|
|
101
101
|
let content = entry.content;
|
|
102
|
-
if (entry.type === 'multiline') {
|
|
102
|
+
if (entry.type === 'multiline' || entry.type === 'details') {
|
|
103
103
|
const cleaned = stripAnsi(dedent(entry.content));
|
|
104
104
|
const parsed = parseMarkdownToTerminal(cleaned);
|
|
105
105
|
content = parsed;
|
|
@@ -314,7 +314,7 @@ class CaptainDestination implements LogDestination {
|
|
|
314
314
|
|
|
315
315
|
stopCapture(): string[] {
|
|
316
316
|
this.capturing = false;
|
|
317
|
-
const logs = this.entries.filter((e) => e.type !== 'debug' && e.type !== 'html' && e.type !== 'multiline' && e.type !== 'operation').map((e) => `[${e.type}] ${e.content}`);
|
|
317
|
+
const logs = this.entries.filter((e) => e.type !== 'debug' && e.type !== 'html' && e.type !== 'multiline' && e.type !== 'details' && e.type !== 'operation').map((e) => `[${e.type}] ${e.content}`);
|
|
318
318
|
this.entries = [];
|
|
319
319
|
return logs;
|
|
320
320
|
}
|