explorbot 0.1.31 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/bin/explorbot-cli.ts +4 -3
- package/boat/doc-collector/src/ai/documentarian.ts +22 -27
- package/boat/doc-collector/src/ai/tools.ts +67 -10
- package/boat/doc-collector/src/docbot.ts +28 -7
- package/boat/doc-collector/src/docs-renderer.ts +19 -24
- package/boat/doc-collector/src/screenshots.ts +33 -1
- package/boat/doc-collector/src/state-diagram.ts +281 -0
- package/dist/bin/explorbot-cli.js +3 -3
- package/dist/boat/doc-collector/src/ai/documentarian.js +16 -6
- package/dist/boat/doc-collector/src/ai/tools.js +47 -10
- package/dist/boat/doc-collector/src/docbot.js +25 -6
- package/dist/boat/doc-collector/src/docs-renderer.js +18 -3
- package/dist/boat/doc-collector/src/screenshots.js +29 -0
- package/dist/boat/doc-collector/src/state-diagram.js +200 -0
- package/dist/package.json +8 -6
- package/dist/src/action-result.d.ts +131 -0
- package/dist/src/action-result.js +82 -159
- package/dist/src/action.d.ts +43 -0
- package/dist/src/action.js +18 -106
- 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/web-mode.js +0 -7
- package/dist/src/ai/captain.d.ts +141 -0
- package/dist/src/ai/captain.js +4 -19
- package/dist/src/ai/conversation.d.ts +28 -0
- package/dist/src/ai/conversation.js +9 -28
- package/dist/src/ai/driller.d.ts +157 -0
- package/dist/src/ai/driller.js +3 -12
- package/dist/src/ai/experience-compactor.d.ts +53 -0
- package/dist/src/ai/experience-compactor.js +16 -33
- package/dist/src/ai/fisherman-tools.d.ts +90 -0
- package/dist/src/ai/fisherman-tools.js +1 -1
- package/dist/src/ai/fisherman.d.ts +29 -0
- package/dist/src/ai/fisherman.js +0 -3
- package/dist/src/ai/historian/codeceptjs.d.ts +17 -0
- package/dist/src/ai/historian/codeceptjs.js +3 -2
- 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 +22 -0
- package/dist/src/ai/historian/playwright.js +3 -2
- package/dist/src/ai/historian/screencast.d.ts +32 -0
- package/dist/src/ai/historian/utils.d.ts +3 -0
- package/dist/src/ai/historian/utils.js +0 -1
- package/dist/src/ai/historian.d.ts +109 -0
- package/dist/src/ai/historian.js +1 -5
- package/dist/src/ai/navigator.d.ts +54 -0
- package/dist/src/ai/navigator.js +11 -46
- package/dist/src/ai/pilot.d.ts +81 -0
- package/dist/src/ai/pilot.js +6 -17
- 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/planner.js +49 -37
- package/dist/src/ai/provider.d.ts +53 -0
- package/dist/src/ai/provider.js +78 -130
- package/dist/src/ai/quartermaster.d.ts +56 -0
- package/dist/src/ai/quartermaster.js +4 -10
- package/dist/src/ai/rerunner.d.ts +48 -0
- package/dist/src/ai/rerunner.js +14 -22
- package/dist/src/ai/researcher/cache.d.ts +6 -0
- package/dist/src/ai/researcher/cache.js +11 -18
- 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/deep-analysis.js +3 -6
- 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/parser.js +1 -1
- package/dist/src/ai/researcher/research-result.d.ts +15 -0
- package/dist/src/ai/researcher/research-result.js +0 -19
- package/dist/src/ai/researcher/sections.d.ts +19 -0
- package/dist/src/ai/researcher/sections.js +5 -1
- package/dist/src/ai/researcher.js +12 -53
- package/dist/src/ai/rules.d.ts +16 -0
- package/dist/src/ai/rules.js +22 -15
- package/dist/src/ai/session-analyst.d.ts +11 -0
- package/dist/src/ai/session-analyst.js +8 -5
- package/dist/src/ai/task-agent.d.ts +28 -0
- package/dist/src/ai/task-agent.js +2 -27
- package/dist/src/ai/tester.d.ts +101 -0
- package/dist/src/ai/tester.js +10 -61
- package/dist/src/ai/tools.d.ts +52 -0
- package/dist/src/ai/tools.js +119 -101
- 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/compact-command.js +6 -6
- 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-command.js +1 -1
- 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/context-knowledge-command.js +1 -1
- 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/experience-command.js +1 -1
- 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/knows-command.js +1 -1
- package/dist/src/commands/learn-command.d.ts +7 -0
- package/dist/src/commands/learn-command.js +3 -1
- 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 +10 -0
- package/dist/src/components/AddKnowledge.js +3 -7
- package/dist/src/components/AddRule.d.ts +9 -0
- package/dist/src/components/App.d.ts +9 -0
- package/dist/src/components/App.js +2 -2
- 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 +253 -0
- package/dist/src/config.js +12 -0
- package/dist/src/execution-controller.d.ts +25 -0
- package/dist/src/experience-tracker.d.ts +112 -0
- package/dist/src/experience-tracker.js +83 -155
- package/dist/src/explorbot.d.ts +106 -0
- package/dist/src/explorbot.js +17 -51
- package/dist/src/explorer.d.ts +129 -0
- package/dist/src/explorer.js +31 -77
- 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 +47 -47
- 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 +160 -0
- package/dist/src/state-manager.js +10 -136
- 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 +25 -0
- package/dist/src/utils/aria.js +4 -1
- package/dist/src/utils/browser-errors.d.ts +8 -0
- package/dist/src/utils/cache.d.ts +15 -0
- package/dist/src/utils/cache.js +34 -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 +31 -0
- package/dist/src/utils/context-formatter.js +15 -27
- 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 +129 -0
- package/dist/src/utils/html.js +71 -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-files.d.ts +10 -0
- package/dist/src/utils/markdown-files.js +21 -0
- package/dist/src/utils/markdown-parser.d.ts +17 -0
- package/dist/src/utils/markdown-query.d.ts +46 -0
- package/dist/src/utils/markdown-query.js +14 -1
- 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 +7 -0
- package/dist/src/utils/page-readiness.js +1 -1
- 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 +5 -0
- package/dist/src/utils/strings.js +10 -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 +45 -0
- package/dist/src/utils/web-element.js +3 -0
- package/dist/src/utils/web-sandbox.d.ts +4 -0
- package/dist/src/utils/web-sandbox.js +37 -0
- package/dist/src/utils/xpath.d.ts +17 -0
- package/package.json +8 -6
- package/src/action-result.ts +80 -168
- package/src/action.ts +16 -123
- package/src/ai/captain/web-mode.ts +0 -7
- package/src/ai/captain.ts +5 -17
- package/src/ai/conversation.ts +9 -32
- package/src/ai/driller.ts +3 -16
- package/src/ai/experience-compactor.ts +16 -35
- package/src/ai/fisherman-tools.ts +1 -1
- package/src/ai/fisherman.ts +0 -4
- package/src/ai/historian/codeceptjs.ts +4 -2
- package/src/ai/historian/playwright.ts +4 -2
- package/src/ai/historian/utils.ts +0 -1
- package/src/ai/historian.ts +4 -7
- package/src/ai/navigator.ts +13 -49
- package/src/ai/pilot.ts +6 -16
- package/src/ai/planner.ts +56 -44
- package/src/ai/provider.ts +86 -139
- package/src/ai/quartermaster.ts +34 -41
- package/src/ai/rerunner.ts +17 -21
- package/src/ai/researcher/cache.ts +10 -16
- package/src/ai/researcher/deep-analysis.ts +3 -6
- package/src/ai/researcher/parser.ts +1 -1
- package/src/ai/researcher/research-result.ts +0 -18
- package/src/ai/researcher/sections.ts +5 -1
- package/src/ai/researcher.ts +11 -59
- package/src/ai/rules.ts +24 -13
- package/src/ai/session-analyst.ts +8 -5
- package/src/ai/task-agent.ts +3 -30
- package/src/ai/tester.ts +12 -73
- package/src/ai/tools.ts +119 -106
- package/src/commands/compact-command.ts +7 -8
- package/src/commands/context-command.ts +1 -1
- package/src/commands/context-knowledge-command.ts +1 -1
- package/src/commands/experience-command.ts +1 -1
- package/src/commands/knows-command.ts +1 -1
- package/src/commands/learn-command.ts +3 -1
- package/src/components/AddKnowledge.tsx +5 -7
- package/src/components/App.tsx +2 -2
- package/src/config.ts +12 -0
- package/src/experience-tracker.ts +90 -152
- package/src/explorbot.ts +17 -51
- package/src/explorer.ts +34 -81
- package/src/index.ts +9 -0
- package/src/knowledge-tracker.ts +49 -50
- package/src/observability.ts +2 -1
- package/src/state-manager.ts +15 -164
- package/src/utils/aria.ts +11 -2
- package/src/utils/cache.ts +40 -0
- package/src/utils/context-formatter.ts +12 -15
- package/src/utils/html.ts +79 -0
- package/src/utils/markdown-files.ts +30 -0
- package/src/utils/markdown-query.ts +15 -1
- package/src/utils/page-readiness.ts +1 -1
- package/src/utils/secrets.ts +28 -0
- package/src/utils/strings.ts +12 -0
- package/src/utils/web-element.ts +4 -0
- package/src/utils/web-sandbox.ts +43 -0
- package/src/index.tsx +0 -62
package/dist/src/ai/planner.js
CHANGED
|
@@ -3,7 +3,6 @@ import { z } from 'zod';
|
|
|
3
3
|
import { ActionResult } from "../action-result.js";
|
|
4
4
|
import { setActivity } from "../activity.js";
|
|
5
5
|
import { ConfigParser } from "../config.js";
|
|
6
|
-
import { ExperienceTracker } from "../experience-tracker.js";
|
|
7
6
|
import { Observability } from "../observability.js";
|
|
8
7
|
import { Stats } from "../stats.js";
|
|
9
8
|
import { Suite } from "../suite.js";
|
|
@@ -16,7 +15,7 @@ import { Conversation } from "./conversation.js";
|
|
|
16
15
|
import { WithSessionDedup } from "./planner/session-dedup.js";
|
|
17
16
|
import { getActiveStyle } from "./planner/styles.js";
|
|
18
17
|
import { WithSubPages, getPlannedByStateHash, getRegisteredPlan, registerPlan } from "./planner/subpages.js";
|
|
19
|
-
import { POSSIBLE_SECTIONS
|
|
18
|
+
import { POSSIBLE_SECTIONS } from "./researcher.js";
|
|
20
19
|
import { findSimilarStateHash } from "./researcher/cache.js";
|
|
21
20
|
import { hasFocusedSection } from "./researcher/focus.js";
|
|
22
21
|
import { capabilityGroundingRule, dataProtectionRules, fileUploadRule } from "./rules.js";
|
|
@@ -50,13 +49,13 @@ export class Planner extends PlannerBase {
|
|
|
50
49
|
lastSuite = null;
|
|
51
50
|
researcher;
|
|
52
51
|
fisherman = null;
|
|
53
|
-
constructor(explorer, provider) {
|
|
52
|
+
constructor(explorer, provider, researcher) {
|
|
54
53
|
super();
|
|
55
54
|
this.explorer = explorer;
|
|
56
55
|
this.provider = provider;
|
|
57
|
-
this.researcher =
|
|
56
|
+
this.researcher = researcher;
|
|
58
57
|
this.stateManager = explorer.getStateManager();
|
|
59
|
-
this.experienceTracker =
|
|
58
|
+
this.experienceTracker = explorer.getStateManager().getExperienceTracker();
|
|
60
59
|
}
|
|
61
60
|
setFisherman(fisherman) {
|
|
62
61
|
this.fisherman = fisherman;
|
|
@@ -81,7 +80,7 @@ export class Planner extends PlannerBase {
|
|
|
81
80
|
You are ISTQB certified senior manual QA planning exploratory testing session of a web application.
|
|
82
81
|
Each test scenario must complete a meaningful user workflow — not just open a UI element and verify it exists.
|
|
83
82
|
Bad: "Open the Help panel" — just opens something.
|
|
84
|
-
Good: "Create a new
|
|
83
|
+
Good: "Create a new post and verify it appears in the list" — completes a business action.
|
|
85
84
|
</role>
|
|
86
85
|
<task>
|
|
87
86
|
List possible testing scenarios for the web page.
|
|
@@ -149,6 +148,7 @@ export class Planner extends PlannerBase {
|
|
|
149
148
|
const tags = ['planner'];
|
|
150
149
|
if (style)
|
|
151
150
|
tags.push(style);
|
|
151
|
+
const existingTestScenarios = this.getExistingTestFileScenarios(state.url);
|
|
152
152
|
const result = await Observability.run(`planner: ${state.url}`, { tags, sessionId: state.url }, async () => {
|
|
153
153
|
const actionResult = ActionResult.fromState(state);
|
|
154
154
|
const conversation = await this.buildConversation(actionResult, style, parentPlan, feature);
|
|
@@ -182,7 +182,6 @@ export class Planner extends PlannerBase {
|
|
|
182
182
|
if (parentPlan)
|
|
183
183
|
this.currentPlan.parentPlan = parentPlan;
|
|
184
184
|
const allPreviousScenarios = this.getPreviousSessionScenarios();
|
|
185
|
-
const existingTestScenarios = this.getExistingTestFileScenarios(state.url);
|
|
186
185
|
for (const s of existingTestScenarios)
|
|
187
186
|
allPreviousScenarios.add(s);
|
|
188
187
|
for (const t of tests) {
|
|
@@ -232,6 +231,7 @@ export class Planner extends PlannerBase {
|
|
|
232
231
|
return added;
|
|
233
232
|
}
|
|
234
233
|
getExistingTestFileScenarios(currentUrl) {
|
|
234
|
+
this.lastSuite = null;
|
|
235
235
|
if (!currentUrl)
|
|
236
236
|
return new Set();
|
|
237
237
|
try {
|
|
@@ -244,27 +244,37 @@ export class Planner extends PlannerBase {
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
cleanExperienceFlows(text) {
|
|
247
|
-
const seenTitles = new Set();
|
|
248
247
|
let result = text;
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
.query(
|
|
253
|
-
.
|
|
254
|
-
.
|
|
255
|
-
.
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
248
|
+
const seenTitles = new Set();
|
|
249
|
+
for (const selector of ['section2', 'section3']) {
|
|
250
|
+
result = mdq(result)
|
|
251
|
+
.query(selector)
|
|
252
|
+
.replaceEach((section) => {
|
|
253
|
+
const heading = section.query('heading').text().trim();
|
|
254
|
+
const withoutHeadings = mdq(section.text()).query('heading').replace('');
|
|
255
|
+
const body = mdq(withoutHeadings).query('hr').replace('').trim();
|
|
256
|
+
if (body && !seenTitles.has(heading)) {
|
|
257
|
+
seenTitles.add(heading);
|
|
258
|
+
return section.text();
|
|
259
|
+
}
|
|
260
|
+
return '';
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
const trimmedTitles = new Set();
|
|
264
|
+
for (const selector of ['section2', 'section3']) {
|
|
265
|
+
result = mdq(result)
|
|
266
|
+
.query(selector)
|
|
267
|
+
.replaceEach((section) => {
|
|
268
|
+
const heading = section.query('heading').text().trim();
|
|
269
|
+
if (trimmedTitles.has(heading))
|
|
270
|
+
return section.text();
|
|
271
|
+
const count = section.query('blockquote').count();
|
|
272
|
+
if (count <= 10)
|
|
273
|
+
return section.text();
|
|
274
|
+
const kept = mdq(section.text()).query('blockquote[10:]').replace('');
|
|
275
|
+
trimmedTitles.add(heading);
|
|
276
|
+
return `${kept.trimEnd()}\n> ... and ${count - 10} more discoveries\n`;
|
|
277
|
+
});
|
|
268
278
|
}
|
|
269
279
|
return result.trim() || null;
|
|
270
280
|
}
|
|
@@ -298,6 +308,7 @@ export class Planner extends PlannerBase {
|
|
|
298
308
|
If there are subpages (pages with same URL path) plan testing of those subpages as well
|
|
299
309
|
If you plan to test CRUD operations, plan them in correct order: create, read, update.
|
|
300
310
|
Do not invent specific route names, success messages, validation texts, badge counts, or welcome messages unless they are visible in research, visited pages, or prior observed flows.
|
|
311
|
+
When validation placement or wording was not observed, require feedback associated with the invalid input without inventing a specific location or message.
|
|
301
312
|
If exact wording is unknown, describe the expected result generically, for example "an authentication error is shown" or "the user stays on the login page" instead of guessing the literal text.
|
|
302
313
|
If exact redirect destination is unknown, describe the destination by visible page identity, for example "the dashboard page opens" or "the current workspace home page opens" instead of inventing a URL slug.
|
|
303
314
|
Only propose scenarios whose prerequisites are evident from page research, visited pages, or API data preparation context.
|
|
@@ -337,17 +348,18 @@ export class Planner extends PlannerBase {
|
|
|
337
348
|
deep: true,
|
|
338
349
|
});
|
|
339
350
|
let plannerResearch = mdq(research).query('code').replace('');
|
|
340
|
-
|
|
341
|
-
|
|
351
|
+
plannerResearch = mdq(plannerResearch)
|
|
352
|
+
.query('table')
|
|
353
|
+
.replaceEach((table) => {
|
|
342
354
|
const rows = table.toJson();
|
|
343
355
|
if (rows.length === 0 || !rows[0].Element)
|
|
344
|
-
|
|
356
|
+
return table.text();
|
|
345
357
|
const elementWithType = rows.map((r) => ({
|
|
346
358
|
Element: r.Element,
|
|
347
359
|
Type: r.Type || '',
|
|
348
360
|
}));
|
|
349
|
-
|
|
350
|
-
}
|
|
361
|
+
return jsonToTable(elementWithType, ['Element', 'Type']);
|
|
362
|
+
});
|
|
351
363
|
const hasFocusedOverlay = hasFocusedSection(plannerResearch);
|
|
352
364
|
const focusNote = hasFocusedOverlay ? "IMPORTANT: One section is marked as **Focused** — this is the user's current focus area. Concentrate testing on the Focused section FIRST — test all interactions inside it before planning tests for the rest of the page." : '';
|
|
353
365
|
const featureFilter = feature ? `FOCUS FILTER: Only propose scenarios using elements relevant to "${feature}". Ignore all other elements.` : '';
|
|
@@ -525,17 +537,17 @@ export class Planner extends PlannerBase {
|
|
|
525
537
|
|
|
526
538
|
STEPS - actionable commands:
|
|
527
539
|
- Each step should be a specific action to perform
|
|
528
|
-
- Good: "Click Create
|
|
529
|
-
- Good: "Click Star icon on '
|
|
540
|
+
- Good: "Click Create Post button", "Enter 'My New Post' as post title", "Click Save button"
|
|
541
|
+
- Good: "Click Star icon on 'Quarterly Report' item"
|
|
530
542
|
- Bad: "Open the dropdown menu" (opening is not a goal, it's a means)
|
|
531
543
|
- Bad: "Verify modal appears" (verification belongs in expected outcomes, not steps)
|
|
532
544
|
- Steps should form a complete workflow, not stop at opening a UI element
|
|
533
545
|
- NEVER split a workflow across two tests. One test = one complete action + its verification
|
|
534
546
|
|
|
535
547
|
EXPECTED OUTCOMES - verifiable results:
|
|
536
|
-
- Good: "New
|
|
537
|
-
- Good: "
|
|
538
|
-
- Good: "Success message '
|
|
548
|
+
- Good: "New post 'My New Post' appears in the posts list"
|
|
549
|
+
- Good: "Item appears under Starred filter tab"
|
|
550
|
+
- Good: "Success message 'Post created' is displayed"
|
|
539
551
|
- Good when wording is unknown: "An authentication error is displayed"
|
|
540
552
|
- Good when route is unknown: "The workspace home page is displayed"
|
|
541
553
|
- Bad: "Modal is displayed" (just verifying existence, no business value)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
2
|
+
import type { ModelMessage } from 'ai';
|
|
3
|
+
import type { AIConfig } from '../config.js';
|
|
4
|
+
import { type RetryOptions } from '../utils/retry.js';
|
|
5
|
+
import { Conversation } from './conversation.js';
|
|
6
|
+
declare class AiError extends Error {
|
|
7
|
+
}
|
|
8
|
+
export declare class ContextLengthError extends Error {
|
|
9
|
+
}
|
|
10
|
+
export declare class Provider {
|
|
11
|
+
config: AIConfig;
|
|
12
|
+
telemetryEnabled: boolean;
|
|
13
|
+
otelSdk: NodeSDK | null;
|
|
14
|
+
defaultRetryOptions: RetryOptions;
|
|
15
|
+
lastConversation: Conversation | null;
|
|
16
|
+
constructor(config: AIConfig);
|
|
17
|
+
getModelName(model: any): string;
|
|
18
|
+
validateConnection(): Promise<void>;
|
|
19
|
+
getModelForAgent(agentName?: string): any;
|
|
20
|
+
getAgenticModel(agentName?: string): any;
|
|
21
|
+
getConfiguredModels(): Record<string, string>;
|
|
22
|
+
getSystemPromptForAgent(agentName: string, currentUrl?: string): string | undefined;
|
|
23
|
+
getProviderOptionsForAgent(agentName: string): Record<string, any> | undefined;
|
|
24
|
+
getReasoningForAgent(agentName?: string): string | undefined;
|
|
25
|
+
getRetryOptions(options?: any): RetryOptions;
|
|
26
|
+
mergeProviderOptions(config: Record<string, any>, agentName?: string): Record<string, any>;
|
|
27
|
+
finalizeConfig(config: Record<string, any>, options: any, telemetry: any): void;
|
|
28
|
+
buildGenerateConfig(defaults: Record<string, any>, overrides: Record<string, any>, options: any): Record<string, any>;
|
|
29
|
+
recordUsage(agentName: string, modelName: string, usage: any): void;
|
|
30
|
+
raceWithIdleTimeout<T>(fn: (signal: AbortSignal) => Promise<T>, timeoutMs: number): Promise<T>;
|
|
31
|
+
recoverFromContextLength(error: any, messages: ModelMessage[], options: any, retry: (messages: ModelMessage[], options: any) => Promise<any>): Promise<any>;
|
|
32
|
+
initLangfuse(): void;
|
|
33
|
+
getTelemetry(options: any): any;
|
|
34
|
+
startConversation(systemMessage: string, agentName?: string, model?: any): Conversation;
|
|
35
|
+
invokeConversation(conversation: Conversation, tools?: any, options?: any): Promise<{
|
|
36
|
+
conversation: Conversation;
|
|
37
|
+
response: any;
|
|
38
|
+
toolExecutions?: any[];
|
|
39
|
+
} | null>;
|
|
40
|
+
chat(messages: ModelMessage[], model: any, options?: any): Promise<any>;
|
|
41
|
+
generateWithTools(messages: ModelMessage[], model: any, tools: any, options?: any): Promise<any>;
|
|
42
|
+
generateObject(messages: ModelMessage[], schema: any, model?: any, options?: any): Promise<any>;
|
|
43
|
+
static isContextLengthError(error: any): boolean;
|
|
44
|
+
static trimMessagesForRetry(messages: ModelMessage[]): ModelMessage[] | null;
|
|
45
|
+
static compactMessagesForRetry(messages: ModelMessage[]): ModelMessage[] | null;
|
|
46
|
+
tryReduceMessages(messages: ModelMessage[], retryLevel: number): {
|
|
47
|
+
messages: ModelMessage[];
|
|
48
|
+
nextLevel: number;
|
|
49
|
+
} | null;
|
|
50
|
+
processImage(prompt: string, image: string): Promise<any>;
|
|
51
|
+
hasVision(): boolean;
|
|
52
|
+
}
|
|
53
|
+
export { AiError, Provider as AIProvider };
|
package/dist/src/ai/provider.js
CHANGED
|
@@ -9,7 +9,7 @@ import { Stats } from "../stats.js";
|
|
|
9
9
|
import { createDebug, tag } from '../utils/logger.js';
|
|
10
10
|
import { withRetry } from '../utils/retry.js';
|
|
11
11
|
import { RulesLoader } from "../utils/rules-loader.js";
|
|
12
|
-
import { Conversation } from './conversation.js';
|
|
12
|
+
import { Conversation, toToolExecution } from './conversation.js';
|
|
13
13
|
const debugLog = createDebug('explorbot:provider');
|
|
14
14
|
const promptLog = createDebug('explorbot:provider:out');
|
|
15
15
|
const responseLog = createDebug('explorbot:provider:in');
|
|
@@ -18,6 +18,7 @@ class AiError extends Error {
|
|
|
18
18
|
export class ContextLengthError extends Error {
|
|
19
19
|
}
|
|
20
20
|
let telemetryRegistered = false;
|
|
21
|
+
const CONTEXT_LENGTH_PATTERNS = ['reduce the length', 'context length', 'maximum context', 'token limit', 'too many tokens', 'max_tokens', 'context_length_exceeded', 'output truncated at maxtokens'];
|
|
21
22
|
function extractCachedTokens(usage) {
|
|
22
23
|
if (!usage)
|
|
23
24
|
return 0;
|
|
@@ -28,20 +29,27 @@ function extractCachedTokens(usage) {
|
|
|
28
29
|
const fromRaw = raw?.prompt_tokens_details?.cached_tokens ?? raw?.promptTokensDetails?.cachedTokens;
|
|
29
30
|
return typeof fromRaw === 'number' ? fromRaw : 0;
|
|
30
31
|
}
|
|
31
|
-
function
|
|
32
|
+
function abortAfterIdle(ms, cancel, controller) {
|
|
32
33
|
return new Promise((_, reject) => {
|
|
33
34
|
const tick = () => {
|
|
34
|
-
if (
|
|
35
|
+
if (cancel.cancelled)
|
|
35
36
|
return;
|
|
36
37
|
if (executionController.isAwaitingInput()) {
|
|
37
38
|
setTimeout(tick, ms);
|
|
38
39
|
return;
|
|
39
40
|
}
|
|
41
|
+
controller.abort();
|
|
40
42
|
reject(new Error('AI request timeout'));
|
|
41
43
|
};
|
|
42
44
|
setTimeout(tick, ms);
|
|
43
45
|
});
|
|
44
46
|
}
|
|
47
|
+
function combinedAbortSignal(controller) {
|
|
48
|
+
const executionSignal = executionController.getAbortSignal();
|
|
49
|
+
if (!executionSignal)
|
|
50
|
+
return controller.signal;
|
|
51
|
+
return AbortSignal.any([controller.signal, executionSignal]);
|
|
52
|
+
}
|
|
45
53
|
export class Provider {
|
|
46
54
|
config;
|
|
47
55
|
telemetryEnabled = false;
|
|
@@ -63,7 +71,6 @@ export class Provider {
|
|
|
63
71
|
!error.message.includes('output truncated at maxTokens'));
|
|
64
72
|
},
|
|
65
73
|
};
|
|
66
|
-
static CONTEXT_LENGTH_PATTERNS = ['reduce the length', 'context length', 'maximum context', 'token limit', 'too many tokens', 'max_tokens', 'context_length_exceeded', 'output truncated at maxtokens'];
|
|
67
74
|
lastConversation = null;
|
|
68
75
|
constructor(config) {
|
|
69
76
|
if (!config?.model) {
|
|
@@ -160,6 +167,46 @@ export class Provider {
|
|
|
160
167
|
if (reasoning)
|
|
161
168
|
config.reasoning ??= reasoning;
|
|
162
169
|
}
|
|
170
|
+
buildGenerateConfig(defaults, overrides, options) {
|
|
171
|
+
const telemetry = this.getTelemetry(options);
|
|
172
|
+
const config = this.mergeProviderOptions({
|
|
173
|
+
...defaults,
|
|
174
|
+
allowSystemInMessages: true,
|
|
175
|
+
...(this.config.config || {}),
|
|
176
|
+
...options,
|
|
177
|
+
...overrides,
|
|
178
|
+
}, options.agentName);
|
|
179
|
+
this.finalizeConfig(config, options, telemetry);
|
|
180
|
+
return config;
|
|
181
|
+
}
|
|
182
|
+
recordUsage(agentName, modelName, usage) {
|
|
183
|
+
if (!usage)
|
|
184
|
+
return;
|
|
185
|
+
Stats.recordTokens(agentName, modelName, {
|
|
186
|
+
input: usage.inputTokens ?? usage.promptTokens ?? 0,
|
|
187
|
+
output: usage.outputTokens ?? usage.completionTokens ?? 0,
|
|
188
|
+
total: usage.totalTokens ?? 0,
|
|
189
|
+
cached: extractCachedTokens(usage),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
async raceWithIdleTimeout(fn, timeoutMs) {
|
|
193
|
+
const cancel = { cancelled: false };
|
|
194
|
+
const controller = new AbortController();
|
|
195
|
+
const combinedSignal = combinedAbortSignal(controller);
|
|
196
|
+
try {
|
|
197
|
+
return await Promise.race([fn(combinedSignal), abortAfterIdle(timeoutMs, cancel, controller)]);
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
cancel.cancelled = true;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async recoverFromContextLength(error, messages, options, retry) {
|
|
204
|
+
const reduced = this.tryReduceMessages(messages, options._contextRetryLevel || 0);
|
|
205
|
+
if (!reduced)
|
|
206
|
+
throw new ContextLengthError(error.message || error.toString());
|
|
207
|
+
tag('warning').log('Context length exceeded, retrying with reduced messages...');
|
|
208
|
+
return retry(reduced.messages, { ...options, _contextRetryLevel: reduced.nextLevel });
|
|
209
|
+
}
|
|
163
210
|
initLangfuse() {
|
|
164
211
|
const langfuseConfig = this.config.langfuse;
|
|
165
212
|
const publicKey = langfuseConfig?.publicKey || process.env.LANGFUSE_PUBLIC_KEY;
|
|
@@ -190,18 +237,22 @@ export class Provider {
|
|
|
190
237
|
return undefined;
|
|
191
238
|
}
|
|
192
239
|
const runTelemetry = Observability.getTelemetry();
|
|
193
|
-
|
|
240
|
+
let optionTelemetry = options.telemetry;
|
|
241
|
+
if (options.telemetryFunctionId && !optionTelemetry?.functionId) {
|
|
242
|
+
optionTelemetry = { ...optionTelemetry, functionId: options.telemetryFunctionId };
|
|
243
|
+
}
|
|
244
|
+
if (!optionTelemetry) {
|
|
194
245
|
return runTelemetry;
|
|
195
246
|
}
|
|
196
247
|
if (!runTelemetry) {
|
|
197
|
-
return
|
|
248
|
+
return optionTelemetry;
|
|
198
249
|
}
|
|
199
250
|
return {
|
|
200
251
|
...runTelemetry,
|
|
201
|
-
...
|
|
252
|
+
...optionTelemetry,
|
|
202
253
|
metadata: {
|
|
203
254
|
...runTelemetry.metadata,
|
|
204
|
-
...
|
|
255
|
+
...optionTelemetry.metadata,
|
|
205
256
|
},
|
|
206
257
|
};
|
|
207
258
|
}
|
|
@@ -227,28 +278,15 @@ export class Provider {
|
|
|
227
278
|
this.lastConversation = conversation;
|
|
228
279
|
const toolCalls = response.toolCalls || [];
|
|
229
280
|
const toolResults = response.toolResults || [];
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
input: call.input,
|
|
233
|
-
output: toolResults[index]?.output,
|
|
234
|
-
wasSuccessful: toolResults[index]?.output?.success || false,
|
|
235
|
-
}));
|
|
281
|
+
const resultsById = new Map(toolResults.map((r) => [r.toolCallId, r]));
|
|
282
|
+
const toolExecutions = toolCalls.map((call) => toToolExecution(call.toolName || '', call.input, resultsById.get(call.toolCallId)?.output));
|
|
236
283
|
return { conversation, response, toolExecutions };
|
|
237
284
|
}
|
|
238
285
|
async chat(messages, model, options = {}) {
|
|
239
286
|
const modelName = this.getModelName(model);
|
|
240
287
|
setActivity(`🤖 Asking ${modelName}`, 'ai');
|
|
241
288
|
promptLog(`Using model: ${modelName}`);
|
|
242
|
-
const
|
|
243
|
-
const config = this.mergeProviderOptions({
|
|
244
|
-
maxOutputTokens: 16384,
|
|
245
|
-
allowSystemInMessages: true,
|
|
246
|
-
...(this.config.config || {}),
|
|
247
|
-
...options,
|
|
248
|
-
model,
|
|
249
|
-
abortSignal: executionController.getAbortSignal(),
|
|
250
|
-
}, options.agentName);
|
|
251
|
-
this.finalizeConfig(config, options, telemetry);
|
|
289
|
+
const config = this.buildGenerateConfig({ maxOutputTokens: 16384 }, { model, abortSignal: executionController.getAbortSignal() }, options);
|
|
252
290
|
promptLog(messages[messages.length - 1].content);
|
|
253
291
|
try {
|
|
254
292
|
const response = await withRetry(async () => {
|
|
@@ -267,14 +305,7 @@ export class Provider {
|
|
|
267
305
|
}, this.getRetryOptions(options));
|
|
268
306
|
clearActivity();
|
|
269
307
|
responseLog(response.text);
|
|
270
|
-
|
|
271
|
-
Stats.recordTokens(options.agentName || 'unknown', modelName, {
|
|
272
|
-
input: response.usage.inputTokens ?? response.usage.promptTokens ?? 0,
|
|
273
|
-
output: response.usage.outputTokens ?? response.usage.completionTokens ?? 0,
|
|
274
|
-
total: response.usage.totalTokens ?? 0,
|
|
275
|
-
cached: extractCachedTokens(response.usage),
|
|
276
|
-
});
|
|
277
|
-
}
|
|
308
|
+
this.recordUsage(options.agentName || 'unknown', modelName, response.usage);
|
|
278
309
|
return response;
|
|
279
310
|
}
|
|
280
311
|
catch (error) {
|
|
@@ -284,12 +315,7 @@ export class Provider {
|
|
|
284
315
|
if (error instanceof ContextLengthError)
|
|
285
316
|
throw error;
|
|
286
317
|
if (Provider.isContextLengthError(error)) {
|
|
287
|
-
|
|
288
|
-
if (reduced) {
|
|
289
|
-
tag('warning').log('Context length exceeded, retrying with reduced messages...');
|
|
290
|
-
return this.chat(reduced.messages, model, { ...options, _contextRetryLevel: reduced.nextLevel });
|
|
291
|
-
}
|
|
292
|
-
throw new ContextLengthError(error.message || error.toString());
|
|
318
|
+
return this.recoverFromContextLength(error, messages, options, (m, o) => this.chat(m, model, o));
|
|
293
319
|
}
|
|
294
320
|
const message = error.message || error.toString();
|
|
295
321
|
if (message !== 'No response text from AI') {
|
|
@@ -306,46 +332,20 @@ export class Provider {
|
|
|
306
332
|
tag('debug').log(`Tools enabled: [${toolNames.join(', ')}]`);
|
|
307
333
|
promptLog('Available tools:', toolNames);
|
|
308
334
|
promptLog(messages[messages.length - 1].content);
|
|
309
|
-
const telemetry = this.getTelemetry(options);
|
|
310
335
|
const maxRoundtrips = options.maxToolRoundtrips ?? 5;
|
|
311
336
|
const extraStop = options.stopWhen;
|
|
312
337
|
const stopConditions = [isStepCount(maxRoundtrips)];
|
|
313
338
|
if (extraStop)
|
|
314
339
|
stopConditions.push(extraStop);
|
|
315
|
-
const { stopWhen:
|
|
316
|
-
const config = this.mergeProviderOptions({
|
|
317
|
-
tools,
|
|
318
|
-
maxOutputTokens: 16384,
|
|
319
|
-
toolChoice: 'auto',
|
|
320
|
-
allowSystemInMessages: true,
|
|
321
|
-
...(this.config.config || {}),
|
|
322
|
-
...optionsWithoutStop,
|
|
323
|
-
stopWhen: stopConditions,
|
|
324
|
-
model,
|
|
325
|
-
abortSignal: executionController.getAbortSignal(),
|
|
326
|
-
}, options.agentName);
|
|
327
|
-
this.finalizeConfig(config, options, telemetry);
|
|
340
|
+
const config = this.buildGenerateConfig({ tools, maxOutputTokens: 16384, toolChoice: 'auto' }, { stopWhen: stopConditions, model }, options);
|
|
328
341
|
try {
|
|
329
342
|
const response = await withRetry(async () => {
|
|
330
|
-
const
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
generateText({
|
|
335
|
-
messages,
|
|
336
|
-
...config,
|
|
337
|
-
}),
|
|
338
|
-
rejectAfterIdle(timeout, cancel),
|
|
339
|
-
]));
|
|
340
|
-
const hasToolCall = (result.toolCalls?.length || 0) > 0;
|
|
341
|
-
if (!result.text && !hasToolCall && result.finishReason === 'length') {
|
|
342
|
-
throw new ContextLengthError('AI response empty: output truncated at maxTokens. Increase maxOutputTokens in config or use a model with higher output capacity.');
|
|
343
|
-
}
|
|
344
|
-
return result;
|
|
345
|
-
}
|
|
346
|
-
finally {
|
|
347
|
-
cancel.cancelled = true;
|
|
343
|
+
const result = (await this.raceWithIdleTimeout((signal) => generateText({ messages, ...config, abortSignal: signal }), config.timeout || 30000));
|
|
344
|
+
const hasToolCall = (result.toolCalls?.length || 0) > 0;
|
|
345
|
+
if (!result.text && !hasToolCall && result.finishReason === 'length') {
|
|
346
|
+
throw new ContextLengthError('AI response empty: output truncated at maxTokens. Increase maxOutputTokens in config or use a model with higher output capacity.');
|
|
348
347
|
}
|
|
348
|
+
return result;
|
|
349
349
|
}, this.getRetryOptions(options));
|
|
350
350
|
clearActivity();
|
|
351
351
|
// Log tool usage summary
|
|
@@ -356,14 +356,7 @@ export class Provider {
|
|
|
356
356
|
});
|
|
357
357
|
}
|
|
358
358
|
responseLog(response.text);
|
|
359
|
-
|
|
360
|
-
Stats.recordTokens(options.agentName || 'unknown', modelName, {
|
|
361
|
-
input: response.usage.inputTokens ?? response.usage.promptTokens ?? 0,
|
|
362
|
-
output: response.usage.outputTokens ?? response.usage.completionTokens ?? 0,
|
|
363
|
-
total: response.usage.totalTokens ?? 0,
|
|
364
|
-
cached: extractCachedTokens(response.usage),
|
|
365
|
-
});
|
|
366
|
-
}
|
|
359
|
+
this.recordUsage(options.agentName || 'unknown', modelName, response.usage);
|
|
367
360
|
return response;
|
|
368
361
|
}
|
|
369
362
|
catch (error) {
|
|
@@ -376,12 +369,7 @@ export class Provider {
|
|
|
376
369
|
if (error instanceof ContextLengthError)
|
|
377
370
|
throw error;
|
|
378
371
|
if (Provider.isContextLengthError(error)) {
|
|
379
|
-
|
|
380
|
-
if (reduced) {
|
|
381
|
-
tag('warning').log('Context length exceeded, retrying with reduced messages...');
|
|
382
|
-
return this.generateWithTools(reduced.messages, model, tools, { ...options, _contextRetryLevel: reduced.nextLevel });
|
|
383
|
-
}
|
|
384
|
-
throw new ContextLengthError(error.message || error.toString());
|
|
372
|
+
return this.recoverFromContextLength(error, messages, options, (m, o) => this.generateWithTools(m, model, tools, o));
|
|
385
373
|
}
|
|
386
374
|
if (error.constructor?.name === 'AI_APICallError') {
|
|
387
375
|
responseLog(error.message);
|
|
@@ -395,44 +383,15 @@ export class Provider {
|
|
|
395
383
|
const modelName = this.getModelName(modelToUse);
|
|
396
384
|
setActivity(`🤖 Asking ${modelName} for structured output`, 'ai');
|
|
397
385
|
promptLog(`Using model: ${modelName}`);
|
|
398
|
-
const
|
|
399
|
-
const config = this.mergeProviderOptions({
|
|
400
|
-
schema,
|
|
401
|
-
allowSystemInMessages: true,
|
|
402
|
-
...(this.config.config || {}),
|
|
403
|
-
...options,
|
|
404
|
-
model: modelToUse,
|
|
405
|
-
abortSignal: executionController.getAbortSignal(),
|
|
406
|
-
}, options.agentName);
|
|
407
|
-
this.finalizeConfig(config, options, telemetry);
|
|
386
|
+
const config = this.buildGenerateConfig({ schema }, { model: modelToUse }, options);
|
|
408
387
|
try {
|
|
409
388
|
promptLog(messages[messages.length - 1].content);
|
|
410
389
|
const response = await withRetry(async () => {
|
|
411
|
-
|
|
412
|
-
const cancel = { cancelled: false };
|
|
413
|
-
try {
|
|
414
|
-
return (await Promise.race([
|
|
415
|
-
generateObject({
|
|
416
|
-
messages,
|
|
417
|
-
...config,
|
|
418
|
-
}),
|
|
419
|
-
rejectAfterIdle(timeout, cancel),
|
|
420
|
-
]));
|
|
421
|
-
}
|
|
422
|
-
finally {
|
|
423
|
-
cancel.cancelled = true;
|
|
424
|
-
}
|
|
390
|
+
return (await this.raceWithIdleTimeout((signal) => generateObject({ messages, ...config, abortSignal: signal }), config.timeout || 30000));
|
|
425
391
|
}, this.getRetryOptions(options));
|
|
426
392
|
clearActivity();
|
|
427
393
|
responseLog(response.object);
|
|
428
|
-
|
|
429
|
-
Stats.recordTokens(options.agentName || 'unknown', modelName, {
|
|
430
|
-
input: response.usage.inputTokens ?? response.usage.promptTokens ?? 0,
|
|
431
|
-
output: response.usage.outputTokens ?? response.usage.completionTokens ?? 0,
|
|
432
|
-
total: response.usage.totalTokens ?? 0,
|
|
433
|
-
cached: extractCachedTokens(response.usage),
|
|
434
|
-
});
|
|
435
|
-
}
|
|
394
|
+
this.recordUsage(options.agentName || 'unknown', modelName, response.usage);
|
|
436
395
|
return response;
|
|
437
396
|
}
|
|
438
397
|
catch (error) {
|
|
@@ -442,19 +401,14 @@ export class Provider {
|
|
|
442
401
|
if (error instanceof ContextLengthError)
|
|
443
402
|
throw error;
|
|
444
403
|
if (Provider.isContextLengthError(error)) {
|
|
445
|
-
|
|
446
|
-
if (reduced) {
|
|
447
|
-
tag('warning').log('Context length exceeded, retrying with reduced messages...');
|
|
448
|
-
return this.generateObject(reduced.messages, schema, model, { ...options, _contextRetryLevel: reduced.nextLevel });
|
|
449
|
-
}
|
|
450
|
-
throw new ContextLengthError(error.message || error.toString());
|
|
404
|
+
return this.recoverFromContextLength(error, messages, options, (m, o) => this.generateObject(m, schema, model, o));
|
|
451
405
|
}
|
|
452
406
|
throw new AiError(error.message || error.toString());
|
|
453
407
|
}
|
|
454
408
|
}
|
|
455
409
|
static isContextLengthError(error) {
|
|
456
410
|
const msg = (error?.message || error?.toString() || '').toLowerCase();
|
|
457
|
-
return
|
|
411
|
+
return CONTEXT_LENGTH_PATTERNS.some((p) => msg.includes(p));
|
|
458
412
|
}
|
|
459
413
|
static trimMessagesForRetry(messages) {
|
|
460
414
|
const tagRegex = /<(\w[\w-]*)>([\s\S]*?)<\/\1>/g;
|
|
@@ -604,13 +558,7 @@ export class Provider {
|
|
|
604
558
|
}, this.getRetryOptions());
|
|
605
559
|
clearActivity();
|
|
606
560
|
responseLog(response.text);
|
|
607
|
-
|
|
608
|
-
Stats.recordTokens('vision', this.getModelName(this.config.visionModel), {
|
|
609
|
-
input: response.usage.inputTokens ?? response.usage.promptTokens ?? 0,
|
|
610
|
-
output: response.usage.outputTokens ?? response.usage.completionTokens ?? 0,
|
|
611
|
-
total: response.usage.totalTokens ?? 0,
|
|
612
|
-
});
|
|
613
|
-
}
|
|
561
|
+
this.recordUsage('vision', this.getModelName(this.config.visionModel), response.usage);
|
|
614
562
|
return response;
|
|
615
563
|
}
|
|
616
564
|
catch (error) {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ActionResult } from '../action-result.js';
|
|
2
|
+
import type { StateManager, StateTransition, WebPageState } from '../state-manager.js';
|
|
3
|
+
import type { Task } from '../test-plan.js';
|
|
4
|
+
import type { Conversation, ToolExecution } from './conversation.js';
|
|
5
|
+
import type { Provider } from './provider.js';
|
|
6
|
+
export declare class Quartermaster {
|
|
7
|
+
provider: Provider;
|
|
8
|
+
model?: any;
|
|
9
|
+
outputDir: string;
|
|
10
|
+
pageAnalyses: Map<string, PageAnalysis>;
|
|
11
|
+
unsubscribe: (() => void) | null;
|
|
12
|
+
playwrightHelper: any;
|
|
13
|
+
pendingAnalyses: Promise<void>[];
|
|
14
|
+
constructor(provider: Provider, options?: {
|
|
15
|
+
model?: any;
|
|
16
|
+
});
|
|
17
|
+
start(playwrightHelper: any, stateManager: StateManager): void;
|
|
18
|
+
stop(): void;
|
|
19
|
+
onPageChange(event: StateTransition): void;
|
|
20
|
+
runAxeAnalysis(state: WebPageState): Promise<void>;
|
|
21
|
+
analyzeSession(task: Task, initialState: ActionResult, conversation: Conversation): Promise<AnalysisReport | null>;
|
|
22
|
+
generateSemanticAnalysis(axeViolations: AxeViolation[], executions: ToolExecution[], initialState: ActionResult): Promise<SemanticIssue[]>;
|
|
23
|
+
addNotesToTask(task: Task, report: AnalysisReport): void;
|
|
24
|
+
saveReport(stateHash: string, report: AnalysisReport): void;
|
|
25
|
+
formatReportMarkdown(report: AnalysisReport): string;
|
|
26
|
+
}
|
|
27
|
+
interface AxeViolation {
|
|
28
|
+
id: string;
|
|
29
|
+
impact: 'critical' | 'serious' | 'moderate' | 'minor';
|
|
30
|
+
description: string;
|
|
31
|
+
helpUrl: string;
|
|
32
|
+
nodes: Array<{
|
|
33
|
+
html: string;
|
|
34
|
+
target: string[];
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
interface PageAnalysis {
|
|
38
|
+
url: string;
|
|
39
|
+
stateHash: string;
|
|
40
|
+
timestamp: string;
|
|
41
|
+
axeViolations: AxeViolation[];
|
|
42
|
+
}
|
|
43
|
+
interface SemanticIssue {
|
|
44
|
+
type: 'unclear_intention' | 'confusing_naming' | 'hard_to_interact';
|
|
45
|
+
element: string;
|
|
46
|
+
issue: string;
|
|
47
|
+
suggestion: string;
|
|
48
|
+
}
|
|
49
|
+
interface AnalysisReport {
|
|
50
|
+
scenario: string;
|
|
51
|
+
timestamp: string;
|
|
52
|
+
url: string;
|
|
53
|
+
axeViolations: AxeViolation[];
|
|
54
|
+
semanticIssues: SemanticIssue[];
|
|
55
|
+
}
|
|
56
|
+
export {};
|