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.
Files changed (179) hide show
  1. package/dist/boat/api-tester/example/apibot.config.js +30 -0
  2. package/dist/package.json +11 -26
  3. package/dist/src/action-result.d.ts +133 -0
  4. package/dist/src/action-result.js +22 -7
  5. package/dist/src/action.d.ts +50 -0
  6. package/dist/src/action.js +10 -5
  7. package/dist/src/activity.d.ts +26 -0
  8. package/dist/src/ai/agent.d.ts +3 -0
  9. package/dist/src/ai/captain/file-tools.d.ts +28 -0
  10. package/dist/src/ai/captain/idle-mode.d.ts +11 -0
  11. package/dist/src/ai/captain/mixin.d.ts +10 -0
  12. package/dist/src/ai/captain/test-mode.d.ts +13 -0
  13. package/dist/src/ai/captain/web-mode.d.ts +13 -0
  14. package/dist/src/ai/captain.d.ts +143 -0
  15. package/dist/src/ai/conversation.d.ts +31 -0
  16. package/dist/src/ai/conversation.js +1 -1
  17. package/dist/src/ai/driller.d.ts +161 -0
  18. package/dist/src/ai/experience-compactor.d.ts +53 -0
  19. package/dist/src/ai/fisherman-tools.d.ts +90 -0
  20. package/dist/src/ai/fisherman.d.ts +30 -0
  21. package/dist/src/ai/historian/codeceptjs.d.ts +15 -0
  22. package/dist/src/ai/historian/experience.d.ts +40 -0
  23. package/dist/src/ai/historian/mixin.d.ts +2 -0
  24. package/dist/src/ai/historian/playwright.d.ts +20 -0
  25. package/dist/src/ai/historian/screencast.d.ts +32 -0
  26. package/dist/src/ai/historian/utils.d.ts +4 -0
  27. package/dist/src/ai/historian.d.ts +108 -0
  28. package/dist/src/ai/navigator.d.ts +56 -0
  29. package/dist/src/ai/pilot.d.ts +83 -0
  30. package/dist/src/ai/planner/session-dedup.d.ts +12 -0
  31. package/dist/src/ai/planner/styles.d.ts +6 -0
  32. package/dist/src/ai/planner/subpages.d.ts +35 -0
  33. package/dist/src/ai/planner.d.ts +79 -0
  34. package/dist/src/ai/provider.d.ts +50 -0
  35. package/dist/src/ai/provider.js +13 -5
  36. package/dist/src/ai/quartermaster.d.ts +57 -0
  37. package/dist/src/ai/rerunner.d.ts +49 -0
  38. package/dist/src/ai/researcher/cache.d.ts +6 -0
  39. package/dist/src/ai/researcher/coordinates.d.ts +56 -0
  40. package/dist/src/ai/researcher/deep-analysis.d.ts +74 -0
  41. package/dist/src/ai/researcher/fingerprint-worker.d.ts +1 -0
  42. package/dist/src/ai/researcher/focus.d.ts +8 -0
  43. package/dist/src/ai/researcher/locators.d.ts +36 -0
  44. package/dist/src/ai/researcher/mixin.d.ts +2 -0
  45. package/dist/src/ai/researcher/parser.d.ts +35 -0
  46. package/dist/src/ai/researcher/research-result.d.ts +16 -0
  47. package/dist/src/ai/researcher/sections.d.ts +19 -0
  48. package/dist/src/ai/rules.d.ts +15 -0
  49. package/dist/src/ai/session-analyst.d.ts +11 -0
  50. package/dist/src/ai/task-agent.d.ts +28 -0
  51. package/dist/src/ai/tester.d.ts +103 -0
  52. package/dist/src/ai/tools.d.ts +41 -0
  53. package/dist/src/api/api-client.d.ts +17 -0
  54. package/dist/src/api/request-result.d.ts +44 -0
  55. package/dist/src/api/request-store.d.ts +29 -0
  56. package/dist/src/api/spec-reader.d.ts +5 -0
  57. package/dist/src/api/xhr-capture.d.ts +10 -0
  58. package/dist/src/browser-server.d.ts +10 -0
  59. package/dist/src/command-handler.d.ts +68 -0
  60. package/dist/src/commands/add-rule-command.d.ts +11 -0
  61. package/dist/src/commands/base-command.d.ts +26 -0
  62. package/dist/src/commands/clean-command.d.ts +12 -0
  63. package/dist/src/commands/compact-command.d.ts +20 -0
  64. package/dist/src/commands/context-aria-command.d.ts +6 -0
  65. package/dist/src/commands/context-command.d.ts +11 -0
  66. package/dist/src/commands/context-data-command.d.ts +6 -0
  67. package/dist/src/commands/context-experience-command.d.ts +6 -0
  68. package/dist/src/commands/context-html-command.d.ts +6 -0
  69. package/dist/src/commands/context-knowledge-command.d.ts +6 -0
  70. package/dist/src/commands/debug-command.d.ts +6 -0
  71. package/dist/src/commands/drill-command.d.ts +10 -0
  72. package/dist/src/commands/exit-command.d.ts +7 -0
  73. package/dist/src/commands/experience-command.d.ts +15 -0
  74. package/dist/src/commands/explore-command.d.ts +51 -0
  75. package/dist/src/commands/freesail-command.d.ts +12 -0
  76. package/dist/src/commands/help-command.d.ts +6 -0
  77. package/dist/src/commands/index.d.ts +4 -0
  78. package/dist/src/commands/init-command.d.ts +7 -0
  79. package/dist/src/commands/knows-command.d.ts +10 -0
  80. package/dist/src/commands/learn-command.d.ts +7 -0
  81. package/dist/src/commands/navigate-command.d.ts +7 -0
  82. package/dist/src/commands/path-command.d.ts +10 -0
  83. package/dist/src/commands/plan-clear-command.d.ts +7 -0
  84. package/dist/src/commands/plan-command.d.ts +14 -0
  85. package/dist/src/commands/plan-edit-command.d.ts +7 -0
  86. package/dist/src/commands/plan-load-command.d.ts +7 -0
  87. package/dist/src/commands/plan-reload-command.d.ts +7 -0
  88. package/dist/src/commands/plan-save-command.d.ts +7 -0
  89. package/dist/src/commands/plans-command.d.ts +21 -0
  90. package/dist/src/commands/rerun-command.d.ts +7 -0
  91. package/dist/src/commands/research-command.d.ts +11 -0
  92. package/dist/src/commands/runs-command.d.ts +7 -0
  93. package/dist/src/commands/start-command.d.ts +7 -0
  94. package/dist/src/commands/status-command.d.ts +6 -0
  95. package/dist/src/commands/test-command.d.ts +11 -0
  96. package/dist/src/components/ActivityPane.d.ts +6 -0
  97. package/dist/src/components/AddKnowledge.d.ts +8 -0
  98. package/dist/src/components/AddRule.d.ts +9 -0
  99. package/dist/src/components/App.d.ts +9 -0
  100. package/dist/src/components/Autocomplete.d.ts +3 -0
  101. package/dist/src/components/InputPane.d.ts +13 -0
  102. package/dist/src/components/InputReadline.d.ts +17 -0
  103. package/dist/src/components/LogPane.d.ts +6 -0
  104. package/dist/src/components/PlanEditor.d.ts +9 -0
  105. package/dist/src/components/PlanPane.d.ts +13 -0
  106. package/dist/src/components/SessionTimer.d.ts +6 -0
  107. package/dist/src/components/StateTransitionPane.d.ts +8 -0
  108. package/dist/src/components/StatusPane.d.ts +4 -0
  109. package/dist/src/components/TaskPane.d.ts +9 -0
  110. package/dist/src/components/Welcome.d.ts +3 -0
  111. package/dist/src/components/WelcomeChecklist.d.ts +9 -0
  112. package/dist/src/components/WelcomeCommands.d.ts +6 -0
  113. package/dist/src/components/autocomplete-store.d.ts +10 -0
  114. package/dist/src/components/parse-keypress.d.ts +12 -0
  115. package/dist/src/config.d.ts +251 -0
  116. package/dist/src/execution-controller.d.ts +25 -0
  117. package/dist/src/experience-tracker.d.ts +107 -0
  118. package/dist/src/experience-tracker.js +2 -1
  119. package/dist/src/explorbot.d.ts +107 -0
  120. package/dist/src/explorer.d.ts +140 -0
  121. package/dist/src/explorer.js +13 -9
  122. package/dist/src/index.d.ts +6 -0
  123. package/dist/src/index.js +2 -55
  124. package/dist/src/knowledge-tracker.d.ts +33 -0
  125. package/dist/src/knowledge-tracker.js +9 -2
  126. package/dist/src/observability.d.ts +13 -0
  127. package/dist/src/observability.js +2 -1
  128. package/dist/src/playwright-recorder.d.ts +31 -0
  129. package/dist/src/reporter.d.ts +34 -0
  130. package/dist/src/state-manager.d.ts +185 -0
  131. package/dist/src/state-manager.js +0 -7
  132. package/dist/src/stats.d.ts +24 -0
  133. package/dist/src/suite.d.ts +24 -0
  134. package/dist/src/test-plan.d.ts +166 -0
  135. package/dist/src/utils/aria.d.ts +20 -0
  136. package/dist/src/utils/browser-errors.d.ts +8 -0
  137. package/dist/src/utils/cli-name.d.ts +1 -0
  138. package/dist/src/utils/code-extractor.d.ts +1 -0
  139. package/dist/src/utils/context-formatter.d.ts +25 -0
  140. package/dist/src/utils/error-page.d.ts +12 -0
  141. package/dist/src/utils/expandable.d.ts +4 -0
  142. package/dist/src/utils/hooks-runner.d.ts +15 -0
  143. package/dist/src/utils/html-diff.d.ts +19 -0
  144. package/dist/src/utils/html.d.ts +124 -0
  145. package/dist/src/utils/log-filters.d.ts +6 -0
  146. package/dist/src/utils/logger.d.ts +35 -0
  147. package/dist/src/utils/loop.d.ts +35 -0
  148. package/dist/src/utils/markdown-parser.d.ts +17 -0
  149. package/dist/src/utils/markdown-query.d.ts +40 -0
  150. package/dist/src/utils/markdown-terminal.d.ts +1 -0
  151. package/dist/src/utils/next-steps.d.ts +11 -0
  152. package/dist/src/utils/page-readiness.d.ts +6 -0
  153. package/dist/src/utils/research-parser.d.ts +1 -0
  154. package/dist/src/utils/retry.d.ts +8 -0
  155. package/dist/src/utils/rules-loader.d.ts +12 -0
  156. package/dist/src/utils/secrets.d.ts +4 -0
  157. package/dist/src/utils/secrets.js +28 -0
  158. package/dist/src/utils/step-analyzer.d.ts +11 -0
  159. package/dist/src/utils/strings.d.ts +3 -0
  160. package/dist/src/utils/test-files.d.ts +3 -0
  161. package/dist/src/utils/test-plan-markdown.d.ts +9 -0
  162. package/dist/src/utils/throttle.d.ts +2 -0
  163. package/dist/src/utils/unique-names.d.ts +2 -0
  164. package/dist/src/utils/url-matcher.d.ts +6 -0
  165. package/dist/src/utils/web-element.d.ts +44 -0
  166. package/dist/src/utils/xpath.d.ts +17 -0
  167. package/package.json +11 -26
  168. package/src/action-result.ts +22 -7
  169. package/src/action.ts +12 -5
  170. package/src/ai/conversation.ts +1 -1
  171. package/src/ai/provider.ts +13 -5
  172. package/src/experience-tracker.ts +2 -1
  173. package/src/explorer.ts +14 -9
  174. package/src/index.ts +9 -0
  175. package/src/knowledge-tracker.ts +7 -1
  176. package/src/observability.ts +2 -1
  177. package/src/state-manager.ts +0 -8
  178. package/src/utils/secrets.ts +29 -0
  179. package/src/index.tsx +0 -62
@@ -0,0 +1,30 @@
1
+ import { createOpenRouter } from '@openrouter/ai-sdk-provider';
2
+ const openrouter = createOpenRouter({
3
+ apiKey: process.env.OPENROUTER_API_KEY,
4
+ });
5
+ export default {
6
+ ai: {
7
+ model: openrouter('mistralai/devstral-small:nitro') // openrouter('x-ai/grok-4-fast') // openrouter('moonshotai/kimi-k2.5:nitro'), // openrouter('x-ai/grok-4-fast') // minimax/minimax-m2.5:nitro
8
+ },
9
+ api: {
10
+ baseEndpoint: 'http://localhost:3000/api/v2/amr_testing',
11
+ headers: {
12
+ 'Authorization': `Bearer ${process.env.TESTOMATIO_API_KEY}`,
13
+ 'Content-Type': 'application/json',
14
+ },
15
+ spec: [
16
+ 'http://127.0.0.1:3000/api/v2/openapi'
17
+ ],
18
+ // bootstrap: async ({ headers, baseEndpoint }) => {
19
+ // // Run before tests — e.g. obtain auth token
20
+ // // Return headers to merge: { Authorization: 'Bearer ...' }
21
+ // },
22
+ // teardown: async ({ headers, baseEndpoint }) => {
23
+ // // Run after tests — e.g. cleanup test data
24
+ // },
25
+ },
26
+ dirs: {
27
+ output: 'output',
28
+ knowledge: 'knowledge',
29
+ },
30
+ };
package/dist/package.json CHANGED
@@ -1,39 +1,30 @@
1
1
  {
2
2
  "name": "explorbot",
3
- "version": "0.1.30",
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
- "bun": "./src/index.tsx",
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 src/index.tsx build && bun run build:bin",
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 src/index.tsx",
35
- "start": "node dist/index.js",
36
- "init": "bun run src/index.tsx init",
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"
@@ -74,7 +59,7 @@
74
59
  "@faker-js/faker": "^10.4.0",
75
60
  "@inkjs/ui": "^2.0.0",
76
61
  "@langfuse/otel": "^4.5.1",
77
- "@openrouter/ai-sdk-provider": "^2.3.3",
62
+ "@openrouter/ai-sdk-provider": "^3.0.0",
78
63
  "@opentelemetry/api": "^1.9.0",
79
64
  "@opentelemetry/auto-instrumentations-node": "^0.67.3",
80
65
  "@opentelemetry/instrumentation": "^0.208.0",
@@ -0,0 +1,133 @@
1
+ import { type HtmlConfig } from './config.js';
2
+ import type { Link, WebPageState } from './state-manager.js';
3
+ import { type HtmlDiffPart, type HtmlDiffResult } from './utils/html-diff.js';
4
+ interface ActionResultData extends WebPageState {
5
+ html?: string;
6
+ fullUrl?: string | undefined;
7
+ screenshot?: Buffer;
8
+ screenshotFile?: string;
9
+ logFile?: string;
10
+ htmlFile?: string;
11
+ title?: string;
12
+ timestamp?: Date;
13
+ error?: string | null;
14
+ h1?: string | undefined;
15
+ h2?: string | undefined;
16
+ h3?: string | undefined;
17
+ h4?: string | undefined;
18
+ browserLogs?: any[];
19
+ iframeSnapshots?: Array<{
20
+ src: string;
21
+ html: string;
22
+ id?: string;
23
+ }>;
24
+ ariaSnapshot?: string | null;
25
+ ariaSnapshotFile?: string;
26
+ iframeURL?: string;
27
+ links?: Link[];
28
+ }
29
+ export interface PageDiff {
30
+ urlChanged: boolean;
31
+ previousUrl?: string;
32
+ currentUrl: string;
33
+ ariaChanges?: string | null;
34
+ htmlParts?: HtmlDiffPart[];
35
+ iframes?: string;
36
+ }
37
+ export interface ToolResultMetadata {
38
+ url: string;
39
+ locator: string;
40
+ targetedHtml: string;
41
+ pageDiff: PageDiff | null;
42
+ iframeURL?: string;
43
+ }
44
+ export declare class ActionResult implements ActionResultData {
45
+ id?: number;
46
+ title: string;
47
+ httpStatus: number | undefined;
48
+ error: string | null;
49
+ timestamp: Date;
50
+ h1: string | undefined;
51
+ h2: string | undefined;
52
+ h3: string | undefined;
53
+ h4: string | undefined;
54
+ url: string;
55
+ fullUrl: string | undefined;
56
+ browserLogs: any[];
57
+ iframeSnapshots: Array<{
58
+ src: string;
59
+ html: string;
60
+ id?: string;
61
+ }>;
62
+ iframeURL: string | undefined;
63
+ readonly screenshotFile: string | undefined;
64
+ _screenshot: Buffer | undefined;
65
+ readonly htmlFile: string | undefined;
66
+ _html: string | undefined;
67
+ snapshotCache: Map<string, string>;
68
+ readonly logFile: string | undefined;
69
+ _browserLogs: any[] | undefined;
70
+ readonly ariaSnapshotFile: string | undefined;
71
+ _ariaSnapshot: string | null | undefined;
72
+ _lastExtractedHtml: string | undefined;
73
+ notes: any;
74
+ links: Link[];
75
+ verifications?: Record<string, boolean>;
76
+ constructor(data: ActionResultData);
77
+ get hash(): string;
78
+ get html(): string;
79
+ set html(value: string);
80
+ get screenshot(): Buffer | undefined;
81
+ get browserLogsContent(): any[];
82
+ get ariaSnapshot(): string | null;
83
+ set ariaSnapshot(value: string | null);
84
+ extractHeadings(html: string): void;
85
+ addVerification(assertion: string, passed: boolean): void;
86
+ getVerification(message: string | RegExp): boolean | null;
87
+ isSameUrl(state: WebPageState): boolean;
88
+ isMatchedBy(state: WebPageState): boolean;
89
+ isRelevantExperienceRecord(record: WebPageState, options?: {
90
+ includeDescendantExperience?: boolean;
91
+ }): boolean;
92
+ simplifiedHtml(htmlConfig?: HtmlConfig): Promise<string>;
93
+ combinedHtml(htmlConfig?: HtmlConfig & {
94
+ keepPositions?: boolean;
95
+ }): Promise<string>;
96
+ textHtml(htmlConfig?: HtmlConfig): Promise<string>;
97
+ getInteractiveARIA(): string;
98
+ getCompactARIA(): string;
99
+ normalizeHtmlConfig(htmlConfig?: HtmlConfig): HtmlConfig | undefined;
100
+ cachedSnapshot(key: string, compute: () => Promise<string>): Promise<string>;
101
+ static fromState(state: WebPageState): ActionResult;
102
+ static loadHtmlFromFile(htmlFile: string): string | null;
103
+ static loadScreenshotFromFile(screenshotFile: string): Buffer | undefined;
104
+ static loadBrowserLogsFromFile(logFile: string): any[];
105
+ static loadAriaSnapshotFromFile(ariaSnapshotFile: string): string | null;
106
+ toAiContext(): string;
107
+ get relativeUrl(): string | null;
108
+ get isInsideIframe(): boolean;
109
+ getStateHash(): string;
110
+ saveBrowserLogs(): void;
111
+ saveHtmlOutput(): void;
112
+ diff(previousState: ActionResult | null): Promise<Diff>;
113
+ toToolResult(previousState: ActionResult | null, locator: string): Promise<ToolResultMetadata>;
114
+ }
115
+ export declare class Diff {
116
+ current: ActionResult;
117
+ previous: ActionResult | null;
118
+ _htmlDiffResult: HtmlDiffResult | null;
119
+ _ariaDiffResult: string | null;
120
+ _isSameUrl: boolean;
121
+ _urlChanged: boolean;
122
+ constructor(current: ActionResult, previous: ActionResult | null);
123
+ hasChanges(): boolean;
124
+ isSameUrl(): boolean;
125
+ urlHasChanged(): boolean;
126
+ get htmlParts(): HtmlDiffPart[];
127
+ get ariaChanged(): string | null;
128
+ get ariaRemoved(): string | null;
129
+ get htmlDiff(): HtmlDiffResult | null;
130
+ get ariaDiff(): string | null;
131
+ calculate(): Promise<void>;
132
+ }
133
+ export {};
@@ -26,6 +26,7 @@ export class ActionResult {
26
26
  _screenshot = undefined;
27
27
  htmlFile = undefined;
28
28
  _html = undefined;
29
+ snapshotCache = new Map();
29
30
  logFile = undefined;
30
31
  _browserLogs = undefined;
31
32
  ariaSnapshotFile = undefined;
@@ -100,6 +101,7 @@ export class ActionResult {
100
101
  }
101
102
  set html(value) {
102
103
  this._html = value;
104
+ this.snapshotCache.clear();
103
105
  }
104
106
  get screenshot() {
105
107
  if (this._screenshot) {
@@ -206,19 +208,24 @@ export class ActionResult {
206
208
  }
207
209
  async simplifiedHtml(htmlConfig) {
208
210
  const normalizedConfig = this.normalizeHtmlConfig(htmlConfig);
209
- return await minifyHtml(htmlMinimalUISnapshot(this.html ?? '', normalizedConfig?.minimal));
211
+ const cacheKey = `simplified:${JSON.stringify(normalizedConfig?.minimal ?? null)}`;
212
+ return this.cachedSnapshot(cacheKey, () => minifyHtml(htmlMinimalUISnapshot(this.html ?? '', normalizedConfig?.minimal)));
210
213
  }
211
214
  async combinedHtml(htmlConfig) {
212
215
  const normalizedConfig = this.normalizeHtmlConfig(htmlConfig);
213
- const combinedHtml = await minifyHtml(htmlCombinedSnapshot(this.html ?? '', normalizedConfig?.combined, { keepPositions: htmlConfig?.keepPositions }));
214
- debugLog(`----${this.url}----`);
215
- debugLog(`Combined HTML: \n${combinedHtml}`);
216
- debugLog('----');
217
- return combinedHtml;
216
+ const cacheKey = `combined:${!!htmlConfig?.keepPositions}:${JSON.stringify(normalizedConfig?.combined ?? null)}`;
217
+ return this.cachedSnapshot(cacheKey, async () => {
218
+ const combinedHtml = await minifyHtml(htmlCombinedSnapshot(this.html ?? '', normalizedConfig?.combined, { keepPositions: htmlConfig?.keepPositions }));
219
+ debugLog(`----${this.url}----`);
220
+ debugLog(`Combined HTML: \n${combinedHtml}`);
221
+ debugLog('----');
222
+ return combinedHtml;
223
+ });
218
224
  }
219
225
  async textHtml(htmlConfig) {
220
226
  const normalizedConfig = this.normalizeHtmlConfig(htmlConfig);
221
- return await minifyHtml(htmlTextSnapshot(this.html ?? '', normalizedConfig?.text));
227
+ const cacheKey = `text:${JSON.stringify(normalizedConfig?.text ?? null)}`;
228
+ return this.cachedSnapshot(cacheKey, () => minifyHtml(htmlTextSnapshot(this.html ?? '', normalizedConfig?.text)));
222
229
  }
223
230
  getInteractiveARIA() {
224
231
  return compactAriaSnapshot(this.ariaSnapshot, false);
@@ -233,6 +240,14 @@ export class ActionResult {
233
240
  const parser = ConfigParser.getInstance();
234
241
  return parser.getConfig().html;
235
242
  }
243
+ async cachedSnapshot(key, compute) {
244
+ const cached = this.snapshotCache.get(key);
245
+ if (cached !== undefined)
246
+ return cached;
247
+ const value = await compute();
248
+ this.snapshotCache.set(key, value);
249
+ return value;
250
+ }
236
251
  static fromState(state) {
237
252
  let html = undefined;
238
253
  let screenshot = undefined;
@@ -0,0 +1,50 @@
1
+ import { ActionResult } from './action-result.js';
2
+ import type { ExplorbotConfig } from './config.js';
3
+ import type { PlaywrightRecorder } from './playwright-recorder.js';
4
+ import type { StateManager } from './state-manager.js';
5
+ declare class Action {
6
+ actor: CodeceptJS.I;
7
+ stateManager: StateManager;
8
+ actionResult: ActionResult | null;
9
+ config: ExplorbotConfig;
10
+ action: string | null;
11
+ expectation: string | null;
12
+ lastError: Error | null;
13
+ playwrightHelper: any;
14
+ playwrightGroupId: string | null;
15
+ assertionSteps: Array<{
16
+ name: string;
17
+ args: any[];
18
+ }>;
19
+ recorder?: PlaywrightRecorder;
20
+ mainDocumentStatus: number | undefined;
21
+ constructor(actor: CodeceptJS.I, stateManager: StateManager, recorder?: PlaywrightRecorder);
22
+ caputrePageWithScreenshot(): Promise<ActionResult>;
23
+ saveScreenshot(): Promise<string | undefined>;
24
+ capturePageState({ includeScreenshot }?: {
25
+ includeScreenshot?: boolean;
26
+ }): Promise<ActionResult>;
27
+ captureMainDocumentStatus(): Promise<number | undefined>;
28
+ captureMainDocumentResponse(): () => void;
29
+ /**
30
+ * Capture HTML snapshots of all iframes on the page
31
+ */
32
+ captureIframeSnapshots(mainHtml: string): Promise<Array<{
33
+ src: string;
34
+ html: string;
35
+ id?: string;
36
+ }>>;
37
+ captureBrowserLogs(): Promise<any[]>;
38
+ execute(code: string): Promise<Action>;
39
+ expect(codeOrFunction: string | ((I: CodeceptJS.I) => void)): Promise<Action>;
40
+ waitForInteraction(): Promise<Action>;
41
+ attempt(codeBlock: string, originalMessage?: string, experience?: boolean): Promise<boolean>;
42
+ getActor(): CodeceptJS.I;
43
+ setActor(actor: CodeceptJS.I): void;
44
+ getCurrentState(): ActionResult | null;
45
+ getActionResult(): ActionResult | null;
46
+ waitForPageReadiness(page: any): Promise<void>;
47
+ }
48
+ export default Action;
49
+ export declare function sanitizeCodeBlock(code: string): string;
50
+ export declare function createSandboxedFunction(argNames: string[], body: string): (...args: any[]) => any;
@@ -272,12 +272,12 @@ class Action {
272
272
  }
273
273
  if (isPlaywright) {
274
274
  const page = this.playwrightHelper.page;
275
- const asyncFn = new Function('page', `return (async () => { ${sanitizedCode} })()`);
275
+ const asyncFn = createSandboxedFunction(['page'], `return (async () => { ${sanitizedCode} })()`);
276
276
  await asyncFn(page);
277
277
  await sleep(this.config.action?.delay || 500);
278
278
  }
279
279
  else {
280
- const codeFunction = new Function('I', 'tryTo', 'retryTo', 'within', 'hopeThat', 'step', 'faker', sanitizedCode);
280
+ const codeFunction = createSandboxedFunction(['I', 'tryTo', 'retryTo', 'within', 'hopeThat', 'step', 'faker'], sanitizedCode);
281
281
  codeFunction(this.actor, tryTo, retryTo, within, hopeThat, step, faker);
282
282
  await recorder.add(() => sleep(this.config.action?.delay || 500));
283
283
  await recorder.promise();
@@ -334,7 +334,7 @@ class Action {
334
334
  if (!sanitizedCode) {
335
335
  throw new Error('No valid I.* commands found in code block');
336
336
  }
337
- codeFunction = new Function('I', 'tryTo', 'retryTo', 'within', 'hopeThat', 'step', sanitizedCode);
337
+ codeFunction = createSandboxedFunction(['I', 'tryTo', 'retryTo', 'within', 'hopeThat', 'step'], sanitizedCode);
338
338
  }
339
339
  codeFunction(this.actor, tryTo, retryTo, within, hopeThat, step);
340
340
  await recorder.promise();
@@ -438,13 +438,18 @@ async function captureTitle(page, actor) {
438
438
  return actor.grabTitle();
439
439
  return '';
440
440
  }
441
- function sanitizeCodeBlock(code) {
441
+ const SHADOWED_GLOBALS = ['process', 'global', 'globalThis', 'fetch', 'Bun', 'require', 'module', 'exports'];
442
+ export function sanitizeCodeBlock(code) {
442
443
  return code
443
444
  .split('\n')
444
445
  .map((line) => line.trim())
445
- .filter((line) => line.startsWith('I.') || line.startsWith('page.') || line.startsWith('await '))
446
+ .filter((line) => line.startsWith('I.') || line.startsWith('page.') || line.startsWith('await page.') || line.startsWith('await I.'))
446
447
  .join('\n');
447
448
  }
449
+ export function createSandboxedFunction(argNames, body) {
450
+ const fn = new Function(...argNames, ...SHADOWED_GLOBALS, `'use strict';\n${body}`);
451
+ return (...args) => fn(...args);
452
+ }
448
453
  function hasPlaywrightCommands(code) {
449
454
  return code.split('\n').some((line) => {
450
455
  const trimmed = line.trim();
@@ -0,0 +1,26 @@
1
+ export interface ActivityEntry {
2
+ message: string;
3
+ timestamp: Date;
4
+ type?: 'ai' | 'action' | 'navigation' | 'general';
5
+ }
6
+ type ActivityListener = (activity: ActivityEntry | null) => void;
7
+ declare class Activity {
8
+ static instance: Activity;
9
+ currentActivity: ActivityEntry | null;
10
+ listeners: ActivityListener[];
11
+ clearTimeoutId: NodeJS.Timeout | null;
12
+ constructor();
13
+ static getInstance(): Activity;
14
+ setActivity(message: string, type?: ActivityEntry['type']): void;
15
+ clearActivity(force?: boolean): void;
16
+ getCurrentActivity(): ActivityEntry | null;
17
+ addListener(listener: ActivityListener): void;
18
+ removeListener(listener: ActivityListener): void;
19
+ notifyListeners(): void;
20
+ }
21
+ export declare const setActivity: (message: string, type?: ActivityEntry["type"]) => void;
22
+ export declare const clearActivity: (force?: boolean) => void;
23
+ export declare const getCurrentActivity: () => ActivityEntry;
24
+ export declare const addActivityListener: (listener: ActivityListener) => void;
25
+ export declare const removeActivityListener: (listener: ActivityListener) => void;
26
+ export default Activity;
@@ -0,0 +1,3 @@
1
+ export interface Agent {
2
+ emoji?: string;
3
+ }
@@ -0,0 +1,28 @@
1
+ export declare const CAPTAIN_ARTIFACT_DIRS: readonly ["reports", "plans", "tests", "states"];
2
+ export declare const CAPTAIN_ALLOWED_READ_DIRS: readonly ["output", "knowledge", "experience"];
3
+ export declare const CAPTAIN_ARTIFACT_SCAN_LIMIT = 200;
4
+ export declare const CAPTAIN_ARTIFACT_LIST_LIMIT = 20;
5
+ export declare const CAPTAIN_READ_FILE_DEFAULT_LIMIT = 12000;
6
+ export declare const CAPTAIN_READ_FILE_MAX_LIMIT = 50000;
7
+ export declare const CAPTAIN_READ_FILE_MIN_LIMIT = 1000;
8
+ export declare function listRecentArtifacts(outputDir: string): Array<{
9
+ path: string;
10
+ size: number;
11
+ modifiedAt: string;
12
+ }>;
13
+ export declare function readCaptainFile(projectRoot: string | null, input: ReadCaptainFileInput, allowedDirs?: readonly string[]): ReadCaptainFileResult;
14
+ export interface ReadCaptainFileInput {
15
+ path: string;
16
+ startLine?: number;
17
+ endLine?: number;
18
+ maxChars?: number;
19
+ }
20
+ export type ReadCaptainFileResult = {
21
+ success: true;
22
+ path: string;
23
+ truncated: boolean;
24
+ content: string;
25
+ } | {
26
+ success: false;
27
+ message: string;
28
+ };
@@ -0,0 +1,11 @@
1
+ import { type Constructor, type ModeContext } from './mixin.js';
2
+ export declare function WithIdleMode<T extends Constructor>(Base: T): {
3
+ new (...args: any[]): {
4
+ idleModeTools(ctx: ModeContext): Promise<Record<string, any>>;
5
+ idleModePrompt(): string;
6
+ };
7
+ } & T;
8
+ export interface IdleModeMethods {
9
+ idleModeTools(ctx: ModeContext): Promise<Record<string, any>>;
10
+ idleModePrompt(): string;
11
+ }
@@ -0,0 +1,10 @@
1
+ import type { ExplorBot } from '../../explorbot.js';
2
+ import type { Task } from '../../test-plan.js';
3
+ export type Constructor<T = object> = new (...args: any[]) => T;
4
+ export declare const debugLog: (...args: any[]) => void;
5
+ export type CaptainMode = 'idle' | 'web' | 'test' | 'heal';
6
+ export interface ModeContext {
7
+ explorBot: ExplorBot;
8
+ task: Task;
9
+ }
10
+ export declare function resolveProjectRoot(): string | null;
@@ -0,0 +1,13 @@
1
+ import { type Constructor, type ModeContext } from './mixin.js';
2
+ export declare function WithTestMode<T extends Constructor>(Base: T): {
3
+ new (...args: any[]): {
4
+ testModeTools(ctx: ModeContext): Record<string, any>;
5
+ testModePrompt(): string;
6
+ testModeRules(): string;
7
+ };
8
+ } & T;
9
+ export interface TestModeMethods {
10
+ testModeTools(ctx: ModeContext): Record<string, any>;
11
+ testModePrompt(): string;
12
+ testModeRules(): string;
13
+ }
@@ -0,0 +1,13 @@
1
+ import { type Constructor, type ModeContext } from './mixin.js';
2
+ export declare function WithWebMode<T extends Constructor>(Base: T): {
3
+ new (...args: any[]): {
4
+ webModeTools(ctx: ModeContext): Record<string, any>;
5
+ webModePrompt(): string;
6
+ webModeRules(): string;
7
+ };
8
+ } & T;
9
+ export interface WebModeMethods {
10
+ webModeTools(ctx: ModeContext): Record<string, any>;
11
+ webModePrompt(): string;
12
+ webModeRules(): string;
13
+ }
@@ -0,0 +1,143 @@
1
+ import { ExperienceTracker } from '../experience-tracker.js';
2
+ import type { ExplorBot } from '../explorbot.js';
3
+ import type { WebPageState } from '../state-manager.js';
4
+ import { Task, Test } from '../test-plan.js';
5
+ import { HooksRunner } from '../utils/hooks-runner.js';
6
+ import type { Agent } from './agent.js';
7
+ import { type CaptainMode, type ModeContext } from './captain/mixin.js';
8
+ import { type Conversation } from './conversation.js';
9
+ import type { Navigator } from './navigator.js';
10
+ import type { Provider } from './provider.js';
11
+ import { TaskAgent } from './task-agent.js';
12
+ declare const CaptainBase: {
13
+ new (...args: any[]): {
14
+ testModeTools(ctx: ModeContext): Record<string, any>;
15
+ testModePrompt(): string;
16
+ testModeRules(): string;
17
+ };
18
+ } & {
19
+ new (...args: any[]): {
20
+ webModeTools(ctx: ModeContext): Record<string, any>;
21
+ webModePrompt(): string;
22
+ webModeRules(): string;
23
+ };
24
+ } & {
25
+ new (...args: any[]): {
26
+ idleModeTools(ctx: ModeContext): Promise<Record<string, any>>;
27
+ idleModePrompt(): string;
28
+ };
29
+ } & (new (...args: any[]) => TaskAgent);
30
+ export declare class Captain extends CaptainBase implements Agent {
31
+ emoji: string;
32
+ explorBot: ExplorBot;
33
+ conversation: Conversation | null;
34
+ experienceTracker: ExperienceTracker;
35
+ hooksRunner: HooksRunner | null;
36
+ commandExecutor: ((cmd: string) => Promise<void>) | null;
37
+ commandDescriptions: {
38
+ name: string;
39
+ description: string;
40
+ options: string;
41
+ }[];
42
+ constructor(explorBot: ExplorBot);
43
+ setCommandExecutor(fn: (cmd: string) => Promise<void>, descriptions: {
44
+ name: string;
45
+ description: string;
46
+ options: string;
47
+ }[]): void;
48
+ getHooksRunner(): HooksRunner;
49
+ getNavigator(): Navigator;
50
+ getExperienceTracker(): ExperienceTracker;
51
+ getKnowledgeTracker(): import("../knowledge-tracker.js").KnowledgeTracker;
52
+ getProvider(): Provider;
53
+ trackToolExecutions(toolExecutions: any[]): void;
54
+ getMode(): CaptainMode;
55
+ systemPrompt(): string;
56
+ resetConversation(): Conversation;
57
+ ensureConversation(): Conversation;
58
+ getConversation(): Conversation | null;
59
+ cleanConversation(): void;
60
+ getPageContext(): Promise<string>;
61
+ planSummary(): string;
62
+ reinjectContextIfNeeded(conversation: Conversation, currentState: WebPageState): Promise<void>;
63
+ coreTools(task: Task, onDone: (summary: string) => void): {
64
+ done: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
65
+ summary: string;
66
+ details?: string;
67
+ }, {
68
+ success: boolean;
69
+ message: string;
70
+ summary?: undefined;
71
+ } | {
72
+ success: boolean;
73
+ summary: any;
74
+ message?: undefined;
75
+ }, import("@ai-sdk/provider-utils").Context>>;
76
+ runCommand: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
77
+ command: any;
78
+ }, {
79
+ success: boolean;
80
+ message: string;
81
+ command?: undefined;
82
+ output?: undefined;
83
+ } | {
84
+ success: boolean;
85
+ command: any;
86
+ message: string;
87
+ output?: undefined;
88
+ } | {
89
+ success: boolean;
90
+ command: any;
91
+ output: string;
92
+ message?: undefined;
93
+ }, import("@ai-sdk/provider-utils").Context>>;
94
+ };
95
+ tools(task: Task, onDone: (summary: string) => void): Promise<{
96
+ done: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
97
+ summary: string;
98
+ details?: string;
99
+ }, {
100
+ success: boolean;
101
+ message: string;
102
+ summary?: undefined;
103
+ } | {
104
+ success: boolean;
105
+ summary: any;
106
+ message?: undefined;
107
+ }, import("@ai-sdk/provider-utils").Context>>;
108
+ runCommand: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
109
+ command: any;
110
+ }, {
111
+ success: boolean;
112
+ message: string;
113
+ command?: undefined;
114
+ output?: undefined;
115
+ } | {
116
+ success: boolean;
117
+ command: any;
118
+ message: string;
119
+ output?: undefined;
120
+ } | {
121
+ success: boolean;
122
+ command: any;
123
+ output: string;
124
+ message?: undefined;
125
+ }, import("@ai-sdk/provider-utils").Context>>;
126
+ }>;
127
+ processSupervisorInterrupt(userMessage: string, activeTest: Test): Promise<SupervisorAction>;
128
+ processExecutionError(error: Error, activeTest: Test): Promise<ExecutionRecoveryAction>;
129
+ canCompleteWithoutDetails(): boolean;
130
+ handle(input: string, options?: {
131
+ reset?: boolean;
132
+ }): Promise<string | null>;
133
+ }
134
+ export default Captain;
135
+ interface SupervisorAction {
136
+ action: 'inject' | 'stop' | 'pass' | 'skip';
137
+ message: string;
138
+ }
139
+ interface ExecutionRecoveryAction {
140
+ action: 'continue' | 'stop';
141
+ message: string;
142
+ recovered?: boolean;
143
+ }