explorbot 0.1.30 → 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 +11 -26
- 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/conversation.js +1 -1
- 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 +13 -5
- 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 +11 -26
- package/src/action-result.ts +22 -7
- package/src/action.ts +12 -5
- package/src/ai/conversation.ts +1 -1
- package/src/ai/provider.ts +13 -5
- 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
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/conversation.ts
CHANGED
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;
|
|
@@ -641,8 +648,9 @@ export class Provider {
|
|
|
641
648
|
text: prompt,
|
|
642
649
|
},
|
|
643
650
|
{
|
|
644
|
-
type: '
|
|
645
|
-
|
|
651
|
+
type: 'file',
|
|
652
|
+
mediaType: 'image/png',
|
|
653
|
+
data: imageData,
|
|
646
654
|
},
|
|
647
655
|
],
|
|
648
656
|
},
|
|
@@ -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);
|
package/src/observability.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Span, trace } from '@opentelemetry/api';
|
|
2
|
+
import { redactSecrets } from './utils/secrets.js';
|
|
2
3
|
|
|
3
4
|
type TelemetryMetadata = Record<string, unknown>;
|
|
4
5
|
|
|
@@ -84,7 +85,7 @@ function buildRootSpanAttributes(name: string, metadata: TelemetryMetadata): Rec
|
|
|
84
85
|
attributes['langfuse.trace.tags'] = metadata.tags as string[];
|
|
85
86
|
}
|
|
86
87
|
if (metadata.input !== undefined) {
|
|
87
|
-
attributes['langfuse.trace.input'] = JSON.stringify(metadata.input);
|
|
88
|
+
attributes['langfuse.trace.input'] = redactSecrets(JSON.stringify(metadata.input));
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
return attributes;
|
package/src/state-manager.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { ActionResult } from './action-result.js';
|
|
|
5
5
|
import { ConfigParser, outputPath } from './config.js';
|
|
6
6
|
import { ExperienceTracker } from './experience-tracker.js';
|
|
7
7
|
import { detectFocusArea } from './utils/aria.js';
|
|
8
|
-
import { htmlTextSnapshot } from './utils/html.js';
|
|
9
8
|
import { createDebug, tag } from './utils/logger.js';
|
|
10
9
|
import { extractStatePath } from './utils/url-matcher.js';
|
|
11
10
|
|
|
@@ -126,13 +125,6 @@ export class StateManager {
|
|
|
126
125
|
* Emit state change event to all listeners
|
|
127
126
|
*/
|
|
128
127
|
private emitStateChange(event: StateTransition): void {
|
|
129
|
-
// Log HTML content when state changes
|
|
130
|
-
if (event.toState.html && event.toState.html !== event.fromState?.html) {
|
|
131
|
-
let htmlContent = event?.toState?.html ?? '';
|
|
132
|
-
htmlContent = htmlTextSnapshot(htmlContent);
|
|
133
|
-
// tag('html').log(`Page HTML for ${event.toState.url}:\n${htmlContent}`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
128
|
this.stateChangeListeners.forEach((listener) => {
|
|
137
129
|
try {
|
|
138
130
|
listener(event);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const MIN_SECRET_LENGTH = 4;
|
|
2
|
+
const secretValues = new Set<string>();
|
|
3
|
+
|
|
4
|
+
export function registerSecret(value: string): void {
|
|
5
|
+
if (!value) return;
|
|
6
|
+
if (value.length < MIN_SECRET_LENGTH) return;
|
|
7
|
+
secretValues.add(value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function redactSecrets(text: string): string {
|
|
11
|
+
if (!text) return text;
|
|
12
|
+
let result = text;
|
|
13
|
+
for (const value of secretValues) {
|
|
14
|
+
if (!result.includes(value)) continue;
|
|
15
|
+
result = result.split(value).join('***REDACTED***');
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function clearRegisteredSecrets(): void {
|
|
21
|
+
secretValues.clear();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const SECRET_NAME_TOKENS = ['password', 'passwd', 'secret', 'token', 'apikey', 'api_key', 'credential', 'private_key', 'privatekey', 'access_key'];
|
|
25
|
+
|
|
26
|
+
export function isSecretName(name: string): boolean {
|
|
27
|
+
const lower = name.toLowerCase();
|
|
28
|
+
return SECRET_NAME_TOKENS.some((token) => lower.includes(token));
|
|
29
|
+
}
|
package/src/index.tsx
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import { addKnowledgeCommand } from './commands/add-knowledge.js';
|
|
4
|
-
import { cleanCommand } from './commands/clean.js';
|
|
5
|
-
import { exploreCommand } from './commands/explore.js';
|
|
6
|
-
import { initCommand } from './commands/init.js';
|
|
7
|
-
|
|
8
|
-
const program = new Command();
|
|
9
|
-
|
|
10
|
-
program
|
|
11
|
-
.name('explorbot')
|
|
12
|
-
.description('AI-powered web exploration tool')
|
|
13
|
-
.option('-f, --from <url>', 'Start exploration from a specific URL')
|
|
14
|
-
.option('-v, --verbose', 'Enable verbose logging')
|
|
15
|
-
.option('--debug', 'Enable debug logging (same as --verbose)')
|
|
16
|
-
.option('-c, --config <path>', 'Path to configuration file')
|
|
17
|
-
.option('-p, --path <path>', 'Working directory path')
|
|
18
|
-
.helpOption('-h, --help', 'Show this help message');
|
|
19
|
-
|
|
20
|
-
program
|
|
21
|
-
.command('clean')
|
|
22
|
-
.description('Clean up artifacts or experience folders')
|
|
23
|
-
.option('-t, --type <type>', 'Type of cleanup (artifacts|experience|all)', 'artifacts')
|
|
24
|
-
.option('-p, --path <path>', 'Custom path to clean')
|
|
25
|
-
.action(async (options, command) => {
|
|
26
|
-
const globalOptions = command.parent.opts();
|
|
27
|
-
const allOptions = { ...globalOptions, ...options };
|
|
28
|
-
await cleanCommand(allOptions);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
program
|
|
32
|
-
.command('init')
|
|
33
|
-
.description('Initialize a new project with configuration')
|
|
34
|
-
.option('-c, --config-path <path>', 'Path for the config file', './explorbot.config.js')
|
|
35
|
-
.option('-f, --force', 'Overwrite existing config file', false)
|
|
36
|
-
.option('-p, --path <path>', 'Working directory for initialization')
|
|
37
|
-
.action(async (options, command) => {
|
|
38
|
-
const globalOptions = command.parent.opts();
|
|
39
|
-
const allOptions = { ...globalOptions, ...options };
|
|
40
|
-
await initCommand(allOptions);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
program
|
|
44
|
-
.command('add-knowledge')
|
|
45
|
-
.alias('learn')
|
|
46
|
-
.description('Add knowledge for specific URLs')
|
|
47
|
-
.option('-p, --path <path>', 'Knowledge directory path')
|
|
48
|
-
.action(async (options) => {
|
|
49
|
-
await addKnowledgeCommand(options);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
program.parse();
|
|
53
|
-
|
|
54
|
-
const options = program.opts();
|
|
55
|
-
const command = program.args[0];
|
|
56
|
-
|
|
57
|
-
if (command === 'clean' || command === 'init' || command === 'add-knowledge' || command === 'learn') {
|
|
58
|
-
// These commands are handled by their respective actions
|
|
59
|
-
} else {
|
|
60
|
-
// Default to explore command
|
|
61
|
-
exploreCommand(options);
|
|
62
|
-
}
|