explorbot 0.1.31 → 0.1.32-beta.1
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/boat/api-tester/example/apibot.config.js +30 -0
- package/dist/package.json +10 -25
- package/dist/src/action-result.d.ts +133 -0
- package/dist/src/action-result.js +22 -7
- package/dist/src/action.d.ts +50 -0
- package/dist/src/action.js +10 -5
- package/dist/src/activity.d.ts +26 -0
- package/dist/src/ai/agent.d.ts +3 -0
- package/dist/src/ai/captain/file-tools.d.ts +28 -0
- package/dist/src/ai/captain/idle-mode.d.ts +11 -0
- package/dist/src/ai/captain/mixin.d.ts +10 -0
- package/dist/src/ai/captain/test-mode.d.ts +13 -0
- package/dist/src/ai/captain/web-mode.d.ts +13 -0
- package/dist/src/ai/captain.d.ts +143 -0
- package/dist/src/ai/conversation.d.ts +31 -0
- package/dist/src/ai/driller.d.ts +161 -0
- package/dist/src/ai/experience-compactor.d.ts +53 -0
- package/dist/src/ai/fisherman-tools.d.ts +90 -0
- package/dist/src/ai/fisherman.d.ts +30 -0
- package/dist/src/ai/historian/codeceptjs.d.ts +15 -0
- package/dist/src/ai/historian/experience.d.ts +40 -0
- package/dist/src/ai/historian/mixin.d.ts +2 -0
- package/dist/src/ai/historian/playwright.d.ts +20 -0
- package/dist/src/ai/historian/screencast.d.ts +32 -0
- package/dist/src/ai/historian/utils.d.ts +4 -0
- package/dist/src/ai/historian.d.ts +108 -0
- package/dist/src/ai/navigator.d.ts +56 -0
- package/dist/src/ai/pilot.d.ts +83 -0
- package/dist/src/ai/planner/session-dedup.d.ts +12 -0
- package/dist/src/ai/planner/styles.d.ts +6 -0
- package/dist/src/ai/planner/subpages.d.ts +35 -0
- package/dist/src/ai/planner.d.ts +79 -0
- package/dist/src/ai/provider.d.ts +50 -0
- package/dist/src/ai/provider.js +10 -3
- package/dist/src/ai/quartermaster.d.ts +57 -0
- package/dist/src/ai/rerunner.d.ts +49 -0
- package/dist/src/ai/researcher/cache.d.ts +6 -0
- package/dist/src/ai/researcher/coordinates.d.ts +56 -0
- package/dist/src/ai/researcher/deep-analysis.d.ts +74 -0
- package/dist/src/ai/researcher/fingerprint-worker.d.ts +1 -0
- package/dist/src/ai/researcher/focus.d.ts +8 -0
- package/dist/src/ai/researcher/locators.d.ts +36 -0
- package/dist/src/ai/researcher/mixin.d.ts +2 -0
- package/dist/src/ai/researcher/parser.d.ts +35 -0
- package/dist/src/ai/researcher/research-result.d.ts +16 -0
- package/dist/src/ai/researcher/sections.d.ts +19 -0
- package/dist/src/ai/rules.d.ts +15 -0
- package/dist/src/ai/session-analyst.d.ts +11 -0
- package/dist/src/ai/task-agent.d.ts +28 -0
- package/dist/src/ai/tester.d.ts +103 -0
- package/dist/src/ai/tools.d.ts +41 -0
- package/dist/src/api/api-client.d.ts +17 -0
- package/dist/src/api/request-result.d.ts +44 -0
- package/dist/src/api/request-store.d.ts +29 -0
- package/dist/src/api/spec-reader.d.ts +5 -0
- package/dist/src/api/xhr-capture.d.ts +10 -0
- package/dist/src/browser-server.d.ts +10 -0
- package/dist/src/command-handler.d.ts +68 -0
- package/dist/src/commands/add-rule-command.d.ts +11 -0
- package/dist/src/commands/base-command.d.ts +26 -0
- package/dist/src/commands/clean-command.d.ts +12 -0
- package/dist/src/commands/compact-command.d.ts +20 -0
- package/dist/src/commands/context-aria-command.d.ts +6 -0
- package/dist/src/commands/context-command.d.ts +11 -0
- package/dist/src/commands/context-data-command.d.ts +6 -0
- package/dist/src/commands/context-experience-command.d.ts +6 -0
- package/dist/src/commands/context-html-command.d.ts +6 -0
- package/dist/src/commands/context-knowledge-command.d.ts +6 -0
- package/dist/src/commands/debug-command.d.ts +6 -0
- package/dist/src/commands/drill-command.d.ts +10 -0
- package/dist/src/commands/exit-command.d.ts +7 -0
- package/dist/src/commands/experience-command.d.ts +15 -0
- package/dist/src/commands/explore-command.d.ts +51 -0
- package/dist/src/commands/freesail-command.d.ts +12 -0
- package/dist/src/commands/help-command.d.ts +6 -0
- package/dist/src/commands/index.d.ts +4 -0
- package/dist/src/commands/init-command.d.ts +7 -0
- package/dist/src/commands/knows-command.d.ts +10 -0
- package/dist/src/commands/learn-command.d.ts +7 -0
- package/dist/src/commands/navigate-command.d.ts +7 -0
- package/dist/src/commands/path-command.d.ts +10 -0
- package/dist/src/commands/plan-clear-command.d.ts +7 -0
- package/dist/src/commands/plan-command.d.ts +14 -0
- package/dist/src/commands/plan-edit-command.d.ts +7 -0
- package/dist/src/commands/plan-load-command.d.ts +7 -0
- package/dist/src/commands/plan-reload-command.d.ts +7 -0
- package/dist/src/commands/plan-save-command.d.ts +7 -0
- package/dist/src/commands/plans-command.d.ts +21 -0
- package/dist/src/commands/rerun-command.d.ts +7 -0
- package/dist/src/commands/research-command.d.ts +11 -0
- package/dist/src/commands/runs-command.d.ts +7 -0
- package/dist/src/commands/start-command.d.ts +7 -0
- package/dist/src/commands/status-command.d.ts +6 -0
- package/dist/src/commands/test-command.d.ts +11 -0
- package/dist/src/components/ActivityPane.d.ts +6 -0
- package/dist/src/components/AddKnowledge.d.ts +8 -0
- package/dist/src/components/AddRule.d.ts +9 -0
- package/dist/src/components/App.d.ts +9 -0
- package/dist/src/components/Autocomplete.d.ts +3 -0
- package/dist/src/components/InputPane.d.ts +13 -0
- package/dist/src/components/InputReadline.d.ts +17 -0
- package/dist/src/components/LogPane.d.ts +6 -0
- package/dist/src/components/PlanEditor.d.ts +9 -0
- package/dist/src/components/PlanPane.d.ts +13 -0
- package/dist/src/components/SessionTimer.d.ts +6 -0
- package/dist/src/components/StateTransitionPane.d.ts +8 -0
- package/dist/src/components/StatusPane.d.ts +4 -0
- package/dist/src/components/TaskPane.d.ts +9 -0
- package/dist/src/components/Welcome.d.ts +3 -0
- package/dist/src/components/WelcomeChecklist.d.ts +9 -0
- package/dist/src/components/WelcomeCommands.d.ts +6 -0
- package/dist/src/components/autocomplete-store.d.ts +10 -0
- package/dist/src/components/parse-keypress.d.ts +12 -0
- package/dist/src/config.d.ts +251 -0
- package/dist/src/execution-controller.d.ts +25 -0
- package/dist/src/experience-tracker.d.ts +107 -0
- package/dist/src/experience-tracker.js +2 -1
- package/dist/src/explorbot.d.ts +107 -0
- package/dist/src/explorer.d.ts +140 -0
- package/dist/src/explorer.js +13 -9
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +2 -55
- package/dist/src/knowledge-tracker.d.ts +33 -0
- package/dist/src/knowledge-tracker.js +9 -2
- package/dist/src/observability.d.ts +13 -0
- package/dist/src/observability.js +2 -1
- package/dist/src/playwright-recorder.d.ts +31 -0
- package/dist/src/reporter.d.ts +34 -0
- package/dist/src/state-manager.d.ts +185 -0
- package/dist/src/state-manager.js +0 -7
- package/dist/src/stats.d.ts +24 -0
- package/dist/src/suite.d.ts +24 -0
- package/dist/src/test-plan.d.ts +166 -0
- package/dist/src/utils/aria.d.ts +20 -0
- package/dist/src/utils/browser-errors.d.ts +8 -0
- package/dist/src/utils/cli-name.d.ts +1 -0
- package/dist/src/utils/code-extractor.d.ts +1 -0
- package/dist/src/utils/context-formatter.d.ts +25 -0
- package/dist/src/utils/error-page.d.ts +12 -0
- package/dist/src/utils/expandable.d.ts +4 -0
- package/dist/src/utils/hooks-runner.d.ts +15 -0
- package/dist/src/utils/html-diff.d.ts +19 -0
- package/dist/src/utils/html.d.ts +124 -0
- package/dist/src/utils/log-filters.d.ts +6 -0
- package/dist/src/utils/logger.d.ts +35 -0
- package/dist/src/utils/loop.d.ts +35 -0
- package/dist/src/utils/markdown-parser.d.ts +17 -0
- package/dist/src/utils/markdown-query.d.ts +40 -0
- package/dist/src/utils/markdown-terminal.d.ts +1 -0
- package/dist/src/utils/next-steps.d.ts +11 -0
- package/dist/src/utils/page-readiness.d.ts +6 -0
- package/dist/src/utils/research-parser.d.ts +1 -0
- package/dist/src/utils/retry.d.ts +8 -0
- package/dist/src/utils/rules-loader.d.ts +12 -0
- package/dist/src/utils/secrets.d.ts +4 -0
- package/dist/src/utils/secrets.js +28 -0
- package/dist/src/utils/step-analyzer.d.ts +11 -0
- package/dist/src/utils/strings.d.ts +3 -0
- package/dist/src/utils/test-files.d.ts +3 -0
- package/dist/src/utils/test-plan-markdown.d.ts +9 -0
- package/dist/src/utils/throttle.d.ts +2 -0
- package/dist/src/utils/unique-names.d.ts +2 -0
- package/dist/src/utils/url-matcher.d.ts +6 -0
- package/dist/src/utils/web-element.d.ts +44 -0
- package/dist/src/utils/xpath.d.ts +17 -0
- package/package.json +10 -25
- package/src/action-result.ts +22 -7
- package/src/action.ts +12 -5
- package/src/ai/provider.ts +10 -3
- package/src/experience-tracker.ts +2 -1
- package/src/explorer.ts +14 -9
- package/src/index.ts +9 -0
- package/src/knowledge-tracker.ts +7 -1
- package/src/observability.ts +2 -1
- package/src/state-manager.ts +0 -8
- package/src/utils/secrets.ts +29 -0
- package/src/index.tsx +0 -62
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type Span } from '@opentelemetry/api';
|
|
2
|
+
export type LogType = 'info' | 'success' | 'error' | 'warning' | 'debug' | 'substep' | 'operation' | 'step' | 'multiline' | 'details' | 'html' | 'input';
|
|
3
|
+
export interface TaggedLogEntry {
|
|
4
|
+
type: LogType;
|
|
5
|
+
content: string;
|
|
6
|
+
timestamp?: Date;
|
|
7
|
+
originalArgs?: any[];
|
|
8
|
+
namespace?: string;
|
|
9
|
+
maxLines?: number;
|
|
10
|
+
}
|
|
11
|
+
type LogEntry = TaggedLogEntry;
|
|
12
|
+
export declare const tag: (type: LogType) => {
|
|
13
|
+
log: (...args: any[]) => void;
|
|
14
|
+
};
|
|
15
|
+
export declare const log: (...args: any[]) => void;
|
|
16
|
+
export declare const logSuccess: (...args: any[]) => void;
|
|
17
|
+
export declare const logError: (...args: any[]) => void;
|
|
18
|
+
export declare const logWarning: (...args: any[]) => void;
|
|
19
|
+
export declare const logSubstep: (...args: any[]) => void;
|
|
20
|
+
export declare const createDebug: (namespace: string) => (...args: any[]) => void;
|
|
21
|
+
export declare const getMethodsOfObject: (obj: any) => string[];
|
|
22
|
+
export declare const startLogCapture: () => void;
|
|
23
|
+
export declare const stopLogCapture: () => string[];
|
|
24
|
+
export declare const setVerboseMode: (enabled: boolean) => void;
|
|
25
|
+
export declare const setPreserveConsoleLogs: (enabled: boolean) => void;
|
|
26
|
+
export declare const isVerboseMode: () => boolean;
|
|
27
|
+
export declare const setDebugMode: (enabled: boolean) => void;
|
|
28
|
+
export declare const isDebugMode: () => boolean;
|
|
29
|
+
export declare const registerLogPane: (addLog: (entry: LogEntry) => void) => void;
|
|
30
|
+
export declare const unregisterLogPane: (addLog: (entry: LogEntry) => void) => void;
|
|
31
|
+
export declare const addTruncateTag: (tagName: string) => void;
|
|
32
|
+
export declare const setStepSpanParent: (span: Span | null) => void;
|
|
33
|
+
export declare const setLogCallback: (addLog: (entry: LogEntry) => void) => void;
|
|
34
|
+
export declare const pluralize: (count: number, singular: string, plural?: string) => string;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare class StopError extends Error {
|
|
2
|
+
constructor(message?: string);
|
|
3
|
+
}
|
|
4
|
+
export declare class InterruptError extends Error {
|
|
5
|
+
userInput: string | null;
|
|
6
|
+
constructor(userInput: string | null);
|
|
7
|
+
}
|
|
8
|
+
export interface LoopContext {
|
|
9
|
+
stop: () => void;
|
|
10
|
+
iteration: number;
|
|
11
|
+
pause: (prompt?: string) => Promise<string | null>;
|
|
12
|
+
userInput: string | null;
|
|
13
|
+
setUserInput: (input: string) => void;
|
|
14
|
+
}
|
|
15
|
+
export interface CatchContext {
|
|
16
|
+
error: Error;
|
|
17
|
+
stop: () => void;
|
|
18
|
+
iteration: number;
|
|
19
|
+
}
|
|
20
|
+
export interface LoopOptions {
|
|
21
|
+
maxAttempts?: number;
|
|
22
|
+
interruptible?: boolean;
|
|
23
|
+
interruptPrompt?: string;
|
|
24
|
+
onInterrupt?: (userInput: string | null, context: LoopContext) => Promise<void> | void;
|
|
25
|
+
catch?: (context: CatchContext) => Promise<void> | void;
|
|
26
|
+
observability?: {
|
|
27
|
+
name?: string;
|
|
28
|
+
agent?: string;
|
|
29
|
+
sessionId?: string;
|
|
30
|
+
tags?: string[];
|
|
31
|
+
metadata?: Record<string, unknown>;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export declare function pause(prompt?: string): Promise<string | null>;
|
|
35
|
+
export declare function loop<T>(handler: (context: LoopContext) => Promise<T>, options?: LoopOptions): Promise<any>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface MarkdownSection {
|
|
2
|
+
name: string;
|
|
3
|
+
depth: number;
|
|
4
|
+
rawMarkdown: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function parseSections(markdown: string, opts?: {
|
|
7
|
+
minDepth?: number;
|
|
8
|
+
maxDepth?: number;
|
|
9
|
+
}): MarkdownSection[];
|
|
10
|
+
export declare function extractLabeledCode(markdown: string, labelPattern: RegExp): string | null;
|
|
11
|
+
export declare function tableToJson(markdown: string): Record<string, string>[];
|
|
12
|
+
export declare function jsonToTable(rows: Record<string, string>[], columns?: string[]): string;
|
|
13
|
+
export declare function findTableRaw(markdown: string): string | null;
|
|
14
|
+
export declare function findTableLineRange(markdown: string): {
|
|
15
|
+
start: number;
|
|
16
|
+
end: number;
|
|
17
|
+
} | null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Token } from 'marked';
|
|
2
|
+
export type SelectorType = 'section' | 'section1' | 'section2' | 'section3' | 'section4' | 'section5' | 'section6' | 'table' | 'heading' | 'paragraph' | 'list' | 'item' | 'code' | 'blockquote' | 'hr' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
3
|
+
export interface TextMatcher {
|
|
4
|
+
mode: 'exact' | 'contains' | 'regex';
|
|
5
|
+
value: string;
|
|
6
|
+
negated: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface QuerySegment {
|
|
9
|
+
selector: SelectorType;
|
|
10
|
+
textMatch?: TextMatcher;
|
|
11
|
+
index: number | null;
|
|
12
|
+
slice: {
|
|
13
|
+
from?: number;
|
|
14
|
+
to?: number;
|
|
15
|
+
} | null;
|
|
16
|
+
}
|
|
17
|
+
export interface MatchedRange {
|
|
18
|
+
token: Token;
|
|
19
|
+
start: number;
|
|
20
|
+
length: number;
|
|
21
|
+
innerTokens?: MatchedRange[];
|
|
22
|
+
}
|
|
23
|
+
export declare function parseQuery(input: string): QuerySegment[];
|
|
24
|
+
export declare class MarkdownQuery {
|
|
25
|
+
source: string;
|
|
26
|
+
matches: MatchedRange[];
|
|
27
|
+
constructor(source: string, matches?: MatchedRange[]);
|
|
28
|
+
query(selector: string): MarkdownQuery;
|
|
29
|
+
text(): string;
|
|
30
|
+
get(): string;
|
|
31
|
+
toJson(): Record<string, string>[];
|
|
32
|
+
replace(content: string): string;
|
|
33
|
+
count(): number;
|
|
34
|
+
first(): MarkdownQuery;
|
|
35
|
+
last(): MarkdownQuery;
|
|
36
|
+
before(): MarkdownQuery;
|
|
37
|
+
after(): MarkdownQuery;
|
|
38
|
+
each(): MarkdownQuery[];
|
|
39
|
+
}
|
|
40
|
+
export declare function mdq(source: string): MarkdownQuery;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseMarkdownToTerminal(markdown: string): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface NextStepCommand {
|
|
2
|
+
label: string;
|
|
3
|
+
command: string;
|
|
4
|
+
}
|
|
5
|
+
export interface NextStepSection {
|
|
6
|
+
label: string;
|
|
7
|
+
path?: string;
|
|
8
|
+
commands?: NextStepCommand[];
|
|
9
|
+
}
|
|
10
|
+
export declare function relativeToCwd(absPath: string): string;
|
|
11
|
+
export declare function printNextSteps(sections: NextStepSection[]): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { RESEARCH_COLUMN_ORDER, type ResearchElement, type ResearchSection, extractContainerFromBlockquote, extractValidContainers, formatResearchSummary, mapRowToElement, parseResearchSections, rebuildSectionMarkdown, } from '../ai/researcher/parser.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface RetryOptions {
|
|
2
|
+
maxAttempts?: number;
|
|
3
|
+
baseDelay?: number;
|
|
4
|
+
maxDelay?: number;
|
|
5
|
+
backoffMultiplier?: number;
|
|
6
|
+
retryCondition?: (error: Error) => boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function withRetry<T>(operation: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class RulesLoader {
|
|
2
|
+
static loadRules(agentName: string, rulesConfig: RuleEntry[], currentUrl: string): string;
|
|
3
|
+
static loadStyles(agentName: string, styleNames: string[]): Record<string, string>;
|
|
4
|
+
static listStyleNames(agentName: string): string[];
|
|
5
|
+
static getActiveStyle(styles: Record<string, string>, iteration: number, override?: string): {
|
|
6
|
+
name: string;
|
|
7
|
+
approach: string;
|
|
8
|
+
};
|
|
9
|
+
static extractRules(agentName: string, targetDir: string): string[];
|
|
10
|
+
}
|
|
11
|
+
type RuleEntry = string | Record<string, string>;
|
|
12
|
+
export type { RuleEntry };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const MIN_SECRET_LENGTH = 4;
|
|
2
|
+
const secretValues = new Set();
|
|
3
|
+
export function registerSecret(value) {
|
|
4
|
+
if (!value)
|
|
5
|
+
return;
|
|
6
|
+
if (value.length < MIN_SECRET_LENGTH)
|
|
7
|
+
return;
|
|
8
|
+
secretValues.add(value);
|
|
9
|
+
}
|
|
10
|
+
export function redactSecrets(text) {
|
|
11
|
+
if (!text)
|
|
12
|
+
return text;
|
|
13
|
+
let result = text;
|
|
14
|
+
for (const value of secretValues) {
|
|
15
|
+
if (!result.includes(value))
|
|
16
|
+
continue;
|
|
17
|
+
result = result.split(value).join('***REDACTED***');
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
export function clearRegisteredSecrets() {
|
|
22
|
+
secretValues.clear();
|
|
23
|
+
}
|
|
24
|
+
const SECRET_NAME_TOKENS = ['password', 'passwd', 'secret', 'token', 'apikey', 'api_key', 'credential', 'private_key', 'privatekey', 'access_key'];
|
|
25
|
+
export function isSecretName(name) {
|
|
26
|
+
const lower = name.toLowerCase();
|
|
27
|
+
return SECRET_NAME_TOKENS.some((token) => lower.includes(token));
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SessionStep } from '../experience-tracker.js';
|
|
2
|
+
import type { StepData } from '../test-plan.js';
|
|
3
|
+
export declare const CODECEPT_TOOLS: readonly ["click", "hover", "pressKey", "form"];
|
|
4
|
+
export type CodeceptToolName = (typeof CODECEPT_TOOLS)[number];
|
|
5
|
+
export declare function isCodeceptToolName(toolName: string): toolName is CodeceptToolName;
|
|
6
|
+
export declare function getCodeceptToolName(commandName: string): CodeceptToolName | null;
|
|
7
|
+
export declare function getCodeceptToolNameFromCode(code: string): CodeceptToolName | null;
|
|
8
|
+
export declare function stripComments(code: string): string;
|
|
9
|
+
export declare function isNonReusableCode(code: string): boolean;
|
|
10
|
+
export declare function toReusableSessionStep(step: StepData): SessionStep | null;
|
|
11
|
+
export declare function mergeUniqueStepsByCode(primary: SessionStep[], secondary: SessionStep[]): SessionStep[];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Plan } from '../test-plan.js';
|
|
2
|
+
export declare function planToCompactAiContext(plan: Plan): string;
|
|
3
|
+
export declare function parsePlanFromMarkdown(filePath: string): Plan;
|
|
4
|
+
export declare function parsePlansFromMarkdown(filePath: string): Plan[];
|
|
5
|
+
export declare function savePlanToMarkdown(plan: Plan, filePath: string): void;
|
|
6
|
+
export declare function savePlansToMarkdown(plans: Plan[], filePath: string): void;
|
|
7
|
+
export declare function planToAiContext(plan: Plan, options?: {
|
|
8
|
+
skipSteps?: boolean;
|
|
9
|
+
}): string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function isDynamicSegment(segment: string): boolean;
|
|
2
|
+
export declare function hasDynamicUrlSegment(url: string): boolean;
|
|
3
|
+
export declare function generalizeSegment(segment: string): string;
|
|
4
|
+
export declare function generalizeUrl(url: string): string;
|
|
5
|
+
export declare function matchesUrl(pattern: string, path: string): boolean;
|
|
6
|
+
export declare function extractStatePath(url: string): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type RawElementData } from './html.js';
|
|
2
|
+
import { type XPathMatch } from './xpath.js';
|
|
3
|
+
export { extractElementData } from './html.js';
|
|
4
|
+
export declare class WebElement {
|
|
5
|
+
tag: string;
|
|
6
|
+
role: string;
|
|
7
|
+
xpath: string;
|
|
8
|
+
clickXPath: string;
|
|
9
|
+
attrs: Record<string, string>;
|
|
10
|
+
text: string;
|
|
11
|
+
outerHTML: string;
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
constructor(data: {
|
|
15
|
+
tag: string;
|
|
16
|
+
role?: string;
|
|
17
|
+
xpath: string;
|
|
18
|
+
clickXPath: string;
|
|
19
|
+
attrs: Record<string, string>;
|
|
20
|
+
text: string;
|
|
21
|
+
outerHTML?: string;
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
});
|
|
25
|
+
get description(): string;
|
|
26
|
+
get keyAttrs(): string;
|
|
27
|
+
get coordinates(): string;
|
|
28
|
+
get eidx(): string | null;
|
|
29
|
+
get isNavigationLink(): boolean;
|
|
30
|
+
get filteredClasses(): string[];
|
|
31
|
+
get areaHints(): string[];
|
|
32
|
+
get contextLabel(): string;
|
|
33
|
+
get variantHints(): string[];
|
|
34
|
+
static fromRawData(d: RawElementData, role?: string): WebElement;
|
|
35
|
+
static fromXPathMatch(m: XPathMatch): WebElement;
|
|
36
|
+
static fromPlaywrightLocator(locator: any): Promise<WebElement | null>;
|
|
37
|
+
static fromEidx(page: any, eidx: string): Promise<WebElement | null>;
|
|
38
|
+
static fromEidxList(page: any, eidxList: string[]): Promise<WebElement[]>;
|
|
39
|
+
static findByXPath(html: string, xpath: string): Promise<{
|
|
40
|
+
totalFound: number;
|
|
41
|
+
elements: WebElement[];
|
|
42
|
+
error?: string;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface XPathMatch {
|
|
2
|
+
tag: string;
|
|
3
|
+
outerHTML: string;
|
|
4
|
+
text: string;
|
|
5
|
+
absoluteXPath: string;
|
|
6
|
+
allAttrs: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
export interface XPathResult {
|
|
9
|
+
totalFound: number;
|
|
10
|
+
matches: XPathMatch[];
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const isDynamicId: (id: string) => boolean;
|
|
14
|
+
export declare const isGenericClass: (cls: string) => boolean;
|
|
15
|
+
export declare function buildClickableXPath(el: XPathMatch): string;
|
|
16
|
+
export declare function cssToAncestorXPath(css: string): string | null;
|
|
17
|
+
export declare function evaluateXPath(html: string, xpath: string): Promise<XPathResult>;
|
package/package.json
CHANGED
|
@@ -1,39 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "explorbot",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32-beta.1",
|
|
4
4
|
"description": "CLI app built with React Ink, CodeceptJS, and Playwright",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/src/index.js",
|
|
8
|
+
"types": "dist/src/index.d.ts",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": {
|
|
10
|
-
"
|
|
11
|
+
"types": "./dist/src/index.d.ts",
|
|
12
|
+
"bun": "./src/index.ts",
|
|
11
13
|
"import": "./dist/src/index.js"
|
|
12
14
|
}
|
|
13
15
|
},
|
|
14
16
|
"bin": {
|
|
15
17
|
"explorbot": "./dist/bin/explorbot-cli.js"
|
|
16
18
|
},
|
|
17
|
-
"files": [
|
|
18
|
-
"dist/",
|
|
19
|
-
"src/**/*.ts",
|
|
20
|
-
"src/**/*.tsx",
|
|
21
|
-
"bin/**/*.ts",
|
|
22
|
-
"boat/api-tester/src/**/*.ts",
|
|
23
|
-
"boat/doc-collector/src/**/*.ts",
|
|
24
|
-
"boat/doc-collector/bin/**/*.ts",
|
|
25
|
-
"boat/doc-collector/package.json",
|
|
26
|
-
"rules/",
|
|
27
|
-
"assets/sample-files/"
|
|
28
|
-
],
|
|
19
|
+
"files": ["dist/", "src/**/*.ts", "src/**/*.tsx", "bin/**/*.ts", "boat/api-tester/src/**/*.ts", "boat/doc-collector/src/**/*.ts", "boat/doc-collector/bin/**/*.ts", "boat/doc-collector/package.json", "rules/", "assets/sample-files/"],
|
|
29
20
|
"scripts": {
|
|
30
|
-
"build": "bun run
|
|
21
|
+
"build": "bun run build:bin",
|
|
31
22
|
"build:bin": "bun build bin/explorbot-cli.ts --outdir bin --target node --external commander --format esm",
|
|
32
23
|
"build:npm": "bash scripts/build-npm.sh",
|
|
33
24
|
"prepublishOnly": "npm run build:npm",
|
|
34
|
-
"dev": "bun run
|
|
35
|
-
"start": "node dist/
|
|
36
|
-
"init": "bun run
|
|
25
|
+
"dev": "bun run bin/explorbot-cli.ts start",
|
|
26
|
+
"start": "node dist/bin/explorbot-cli.js",
|
|
27
|
+
"init": "bun run bin/explorbot-cli.ts init",
|
|
37
28
|
"test": "codeceptjs run",
|
|
38
29
|
"test:headless": "codeceptjs run --headless",
|
|
39
30
|
"test:ui": "bun test tests/ui",
|
|
@@ -52,13 +43,7 @@
|
|
|
52
43
|
"check:fix": "biome check --write .",
|
|
53
44
|
"langfuse:export": "bun run .claude/skills/explorbot-debug/langfuse-export.ts"
|
|
54
45
|
},
|
|
55
|
-
"keywords": [
|
|
56
|
-
"cli",
|
|
57
|
-
"react",
|
|
58
|
-
"ink",
|
|
59
|
-
"codeceptjs",
|
|
60
|
-
"playwright"
|
|
61
|
-
],
|
|
46
|
+
"keywords": ["cli", "react", "ink", "codeceptjs", "playwright"],
|
|
62
47
|
"repository": {
|
|
63
48
|
"type": "git",
|
|
64
49
|
"url": "https://github.com/testomatio/explorbot"
|
package/src/action-result.ts
CHANGED
|
@@ -68,6 +68,7 @@ export class ActionResult implements ActionResultData {
|
|
|
68
68
|
private _screenshot: Buffer | undefined = undefined;
|
|
69
69
|
readonly htmlFile: string | undefined = undefined;
|
|
70
70
|
private _html: string | undefined = undefined;
|
|
71
|
+
private snapshotCache = new Map<string, string>();
|
|
71
72
|
readonly logFile: string | undefined = undefined;
|
|
72
73
|
private _browserLogs: any[] | undefined = undefined;
|
|
73
74
|
readonly ariaSnapshotFile: string | undefined = undefined;
|
|
@@ -151,6 +152,7 @@ export class ActionResult implements ActionResultData {
|
|
|
151
152
|
|
|
152
153
|
set html(value: string) {
|
|
153
154
|
this._html = value;
|
|
155
|
+
this.snapshotCache.clear();
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
get screenshot(): Buffer | undefined {
|
|
@@ -263,21 +265,26 @@ export class ActionResult implements ActionResultData {
|
|
|
263
265
|
|
|
264
266
|
async simplifiedHtml(htmlConfig?: HtmlConfig): Promise<string> {
|
|
265
267
|
const normalizedConfig = this.normalizeHtmlConfig(htmlConfig);
|
|
266
|
-
|
|
268
|
+
const cacheKey = `simplified:${JSON.stringify(normalizedConfig?.minimal ?? null)}`;
|
|
269
|
+
return this.cachedSnapshot(cacheKey, () => minifyHtml(htmlMinimalUISnapshot(this.html ?? '', normalizedConfig?.minimal)));
|
|
267
270
|
}
|
|
268
271
|
|
|
269
272
|
async combinedHtml(htmlConfig?: HtmlConfig & { keepPositions?: boolean }): Promise<string> {
|
|
270
273
|
const normalizedConfig = this.normalizeHtmlConfig(htmlConfig);
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
const cacheKey = `combined:${!!htmlConfig?.keepPositions}:${JSON.stringify(normalizedConfig?.combined ?? null)}`;
|
|
275
|
+
return this.cachedSnapshot(cacheKey, async () => {
|
|
276
|
+
const combinedHtml = await minifyHtml(htmlCombinedSnapshot(this.html ?? '', normalizedConfig?.combined, { keepPositions: htmlConfig?.keepPositions }));
|
|
277
|
+
debugLog(`----${this.url}----`);
|
|
278
|
+
debugLog(`Combined HTML: \n${combinedHtml}`);
|
|
279
|
+
debugLog('----');
|
|
280
|
+
return combinedHtml;
|
|
281
|
+
});
|
|
276
282
|
}
|
|
277
283
|
|
|
278
284
|
async textHtml(htmlConfig?: HtmlConfig): Promise<string> {
|
|
279
285
|
const normalizedConfig = this.normalizeHtmlConfig(htmlConfig);
|
|
280
|
-
|
|
286
|
+
const cacheKey = `text:${JSON.stringify(normalizedConfig?.text ?? null)}`;
|
|
287
|
+
return this.cachedSnapshot(cacheKey, () => minifyHtml(htmlTextSnapshot(this.html ?? '', normalizedConfig?.text)));
|
|
281
288
|
}
|
|
282
289
|
|
|
283
290
|
getInteractiveARIA(): string {
|
|
@@ -296,6 +303,14 @@ export class ActionResult implements ActionResultData {
|
|
|
296
303
|
return parser.getConfig().html;
|
|
297
304
|
}
|
|
298
305
|
|
|
306
|
+
private async cachedSnapshot(key: string, compute: () => Promise<string>): Promise<string> {
|
|
307
|
+
const cached = this.snapshotCache.get(key);
|
|
308
|
+
if (cached !== undefined) return cached;
|
|
309
|
+
const value = await compute();
|
|
310
|
+
this.snapshotCache.set(key, value);
|
|
311
|
+
return value;
|
|
312
|
+
}
|
|
313
|
+
|
|
299
314
|
static fromState(state: WebPageState): ActionResult {
|
|
300
315
|
let html: string | undefined = undefined;
|
|
301
316
|
let screenshot: Buffer | undefined = undefined;
|
package/src/action.ts
CHANGED
|
@@ -303,11 +303,11 @@ class Action {
|
|
|
303
303
|
|
|
304
304
|
if (isPlaywright) {
|
|
305
305
|
const page = this.playwrightHelper.page;
|
|
306
|
-
const asyncFn =
|
|
306
|
+
const asyncFn = createSandboxedFunction(['page'], `return (async () => { ${sanitizedCode} })()`);
|
|
307
307
|
await asyncFn(page);
|
|
308
308
|
await sleep(this.config.action?.delay || 500);
|
|
309
309
|
} else {
|
|
310
|
-
const codeFunction =
|
|
310
|
+
const codeFunction = createSandboxedFunction(['I', 'tryTo', 'retryTo', 'within', 'hopeThat', 'step', 'faker'], sanitizedCode);
|
|
311
311
|
codeFunction(this.actor, tryTo, retryTo, within, hopeThat, step, faker);
|
|
312
312
|
await recorder.add(() => sleep(this.config.action?.delay || 500));
|
|
313
313
|
await recorder.promise();
|
|
@@ -366,7 +366,7 @@ class Action {
|
|
|
366
366
|
if (!sanitizedCode) {
|
|
367
367
|
throw new Error('No valid I.* commands found in code block');
|
|
368
368
|
}
|
|
369
|
-
codeFunction =
|
|
369
|
+
codeFunction = createSandboxedFunction(['I', 'tryTo', 'retryTo', 'within', 'hopeThat', 'step'], sanitizedCode);
|
|
370
370
|
}
|
|
371
371
|
codeFunction(this.actor, tryTo, retryTo, within, hopeThat, step);
|
|
372
372
|
await recorder.promise();
|
|
@@ -480,14 +480,21 @@ async function captureTitle(page: any, actor: any): Promise<string> {
|
|
|
480
480
|
return '';
|
|
481
481
|
}
|
|
482
482
|
|
|
483
|
-
|
|
483
|
+
const SHADOWED_GLOBALS = ['process', 'global', 'globalThis', 'fetch', 'Bun', 'require', 'module', 'exports'];
|
|
484
|
+
|
|
485
|
+
export function sanitizeCodeBlock(code: string): string {
|
|
484
486
|
return code
|
|
485
487
|
.split('\n')
|
|
486
488
|
.map((line) => line.trim())
|
|
487
|
-
.filter((line) => line.startsWith('I.') || line.startsWith('page.') || line.startsWith('await '))
|
|
489
|
+
.filter((line) => line.startsWith('I.') || line.startsWith('page.') || line.startsWith('await page.') || line.startsWith('await I.'))
|
|
488
490
|
.join('\n');
|
|
489
491
|
}
|
|
490
492
|
|
|
493
|
+
export function createSandboxedFunction(argNames: string[], body: string): (...args: any[]) => any {
|
|
494
|
+
const fn = new Function(...argNames, ...SHADOWED_GLOBALS, `'use strict';\n${body}`);
|
|
495
|
+
return (...args: any[]) => fn(...args);
|
|
496
|
+
}
|
|
497
|
+
|
|
491
498
|
function hasPlaywrightCommands(code: string): boolean {
|
|
492
499
|
return code.split('\n').some((line) => {
|
|
493
500
|
const trimmed = line.trim();
|
package/src/ai/provider.ts
CHANGED
|
@@ -31,7 +31,7 @@ function extractCachedTokens(usage: any): number {
|
|
|
31
31
|
return typeof fromRaw === 'number' ? fromRaw : 0;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function rejectAfterIdle(ms: number, signal: { cancelled: boolean }): Promise<never> {
|
|
34
|
+
function rejectAfterIdle(ms: number, signal: { cancelled: boolean }, controller: AbortController): Promise<never> {
|
|
35
35
|
return new Promise((_, reject) => {
|
|
36
36
|
const tick = () => {
|
|
37
37
|
if (signal.cancelled) return;
|
|
@@ -39,6 +39,7 @@ function rejectAfterIdle(ms: number, signal: { cancelled: boolean }): Promise<ne
|
|
|
39
39
|
setTimeout(tick, ms);
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
|
+
controller.abort();
|
|
42
43
|
reject(new Error('AI request timeout'));
|
|
43
44
|
};
|
|
44
45
|
setTimeout(tick, ms);
|
|
@@ -367,13 +368,16 @@ export class Provider {
|
|
|
367
368
|
const response = await withRetry(async () => {
|
|
368
369
|
const timeout = config.timeout || 30000;
|
|
369
370
|
const cancel = { cancelled: false };
|
|
371
|
+
const controller = new AbortController();
|
|
372
|
+
const combinedSignal = AbortSignal.any([controller.signal, executionController.getAbortSignal()].filter(Boolean) as AbortSignal[]);
|
|
370
373
|
try {
|
|
371
374
|
const result = (await Promise.race([
|
|
372
375
|
generateText({
|
|
373
376
|
messages,
|
|
374
377
|
...config,
|
|
378
|
+
abortSignal: combinedSignal,
|
|
375
379
|
}),
|
|
376
|
-
rejectAfterIdle(timeout, cancel),
|
|
380
|
+
rejectAfterIdle(timeout, cancel, controller),
|
|
377
381
|
])) as any;
|
|
378
382
|
const hasToolCall = (result.toolCalls?.length || 0) > 0;
|
|
379
383
|
if (!result.text && !hasToolCall && result.finishReason === 'length') {
|
|
@@ -455,13 +459,16 @@ export class Provider {
|
|
|
455
459
|
const response = await withRetry(async () => {
|
|
456
460
|
const timeout = config.timeout || 30000;
|
|
457
461
|
const cancel = { cancelled: false };
|
|
462
|
+
const controller = new AbortController();
|
|
463
|
+
const combinedSignal = AbortSignal.any([controller.signal, executionController.getAbortSignal()].filter(Boolean) as AbortSignal[]);
|
|
458
464
|
try {
|
|
459
465
|
return (await Promise.race([
|
|
460
466
|
generateObject({
|
|
461
467
|
messages,
|
|
462
468
|
...config,
|
|
469
|
+
abortSignal: combinedSignal,
|
|
463
470
|
}),
|
|
464
|
-
rejectAfterIdle(timeout, cancel),
|
|
471
|
+
rejectAfterIdle(timeout, cancel, controller),
|
|
465
472
|
])) as any;
|
|
466
473
|
} finally {
|
|
467
474
|
cancel.cancelled = true;
|
|
@@ -8,6 +8,7 @@ import { KnowledgeTracker } from './knowledge-tracker.js';
|
|
|
8
8
|
import type { WebPageState } from './state-manager.js';
|
|
9
9
|
import { createDebug, tag } from './utils/logger.js';
|
|
10
10
|
import { mdq } from './utils/markdown-query.js';
|
|
11
|
+
import { redactSecrets } from './utils/secrets.js';
|
|
11
12
|
import { isNonReusableCode } from './utils/step-analyzer.ts';
|
|
12
13
|
import { extractStatePath } from './utils/url-matcher.js';
|
|
13
14
|
|
|
@@ -109,7 +110,7 @@ export class ExperienceTracker {
|
|
|
109
110
|
return;
|
|
110
111
|
}
|
|
111
112
|
const filePath = this.getExperienceFilePath(stateHash);
|
|
112
|
-
const fileContent = matter.stringify(content, frontmatter || {});
|
|
113
|
+
const fileContent = matter.stringify(redactSecrets(content), frontmatter || {});
|
|
113
114
|
writeFileSync(filePath, fileContent, 'utf8');
|
|
114
115
|
}
|
|
115
116
|
|
package/src/explorer.ts
CHANGED
|
@@ -727,17 +727,22 @@ class Explorer {
|
|
|
727
727
|
|
|
728
728
|
this.watchActiveTestPage();
|
|
729
729
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
codeceptjs.event.dispatcher.on('step.passed', (step: any) => stepHandler(step, 'passed'));
|
|
733
|
-
codeceptjs.event.dispatcher.on('step.failed', (step: any, error: any) => {
|
|
730
|
+
const onStepPassed = (step: any) => stepHandler(step, 'passed');
|
|
731
|
+
const onStepFailed = (step: any, error: any) => {
|
|
734
732
|
stepHandler(step, 'failed', error?.message || String(error), error?.stack);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
codeceptjs.event.dispatcher.off('step.passed',
|
|
738
|
-
codeceptjs.event.dispatcher.off('step.failed',
|
|
733
|
+
};
|
|
734
|
+
const onTestAfter = () => {
|
|
735
|
+
codeceptjs.event.dispatcher.off('step.passed', onStepPassed);
|
|
736
|
+
codeceptjs.event.dispatcher.off('step.failed', onStepFailed);
|
|
737
|
+
codeceptjs.event.dispatcher.off('test.after', onTestAfter);
|
|
739
738
|
this.unwatchActiveTestPages();
|
|
740
|
-
}
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
codeceptjs.event.dispatcher.emit('test.before', codeceptjsTest);
|
|
742
|
+
codeceptjs.event.dispatcher.emit('test.start', codeceptjsTest);
|
|
743
|
+
codeceptjs.event.dispatcher.on('step.passed', onStepPassed);
|
|
744
|
+
codeceptjs.event.dispatcher.on('step.failed', onStepFailed);
|
|
745
|
+
codeceptjs.event.dispatcher.on('test.after', onTestAfter);
|
|
741
746
|
|
|
742
747
|
return true;
|
|
743
748
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { ExplorBot } from './explorbot.ts';
|
|
2
|
+
export type { ExplorBotOptions, UserResolveFunction } from './explorbot.ts';
|
|
3
|
+
|
|
4
|
+
export { Plan, Test, TestResult, TestStatus } from './test-plan.ts';
|
|
5
|
+
export type { Note, StepData, TestResultType, TestStatusType } from './test-plan.ts';
|
|
6
|
+
|
|
7
|
+
export type { Link, StateTransition, WebPageState } from './state-manager.ts';
|
|
8
|
+
|
|
9
|
+
export type { AIConfig, ExplorbotConfig, PlaywrightConfig } from './config.ts';
|
package/src/knowledge-tracker.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { ActionResult } from './action-result.js';
|
|
|
5
5
|
import { ConfigParser } from './config.js';
|
|
6
6
|
import { getCliName } from './utils/cli-name.ts';
|
|
7
7
|
import { createDebug } from './utils/logger.js';
|
|
8
|
+
import { isSecretName, registerSecret } from './utils/secrets.js';
|
|
8
9
|
|
|
9
10
|
const debugLog = createDebug('explorbot:knowledge-tracker');
|
|
10
11
|
|
|
@@ -138,9 +139,14 @@ export class KnowledgeTracker {
|
|
|
138
139
|
const namespace = expr.slice(0, dotIndex);
|
|
139
140
|
const key = expr.slice(dotIndex + 1);
|
|
140
141
|
|
|
141
|
-
if (namespace === 'env')
|
|
142
|
+
if (namespace === 'env') {
|
|
143
|
+
const value = process.env[key] ?? '';
|
|
144
|
+
if (isSecretName(key)) registerSecret(value);
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
142
147
|
|
|
143
148
|
if (namespace === 'config') {
|
|
149
|
+
if (isSecretName(key)) return '';
|
|
144
150
|
const config = ConfigParser.getInstance().getConfig();
|
|
145
151
|
const value = key.split('.').reduce((obj: any, k) => obj?.[k], config);
|
|
146
152
|
if (value !== undefined && typeof value !== 'object') return String(value);
|