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
|
@@ -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.
|
|
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"
|
|
@@ -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": "^
|
|
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
|
-
|
|
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
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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;
|
package/dist/src/action.js
CHANGED
|
@@ -272,12 +272,12 @@ class Action {
|
|
|
272
272
|
}
|
|
273
273
|
if (isPlaywright) {
|
|
274
274
|
const page = this.playwrightHelper.page;
|
|
275
|
-
const asyncFn =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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,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
|
+
}
|