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/src/ai/provider.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { Stats } from '../stats.ts';
|
|
|
11
11
|
import { createDebug, tag } from '../utils/logger.js';
|
|
12
12
|
import { type RetryOptions, withRetry } from '../utils/retry.js';
|
|
13
13
|
import { RulesLoader } from '../utils/rules-loader.ts';
|
|
14
|
-
import { Conversation } from './conversation.js';
|
|
14
|
+
import { Conversation, toToolExecution } from './conversation.js';
|
|
15
15
|
|
|
16
16
|
const debugLog = createDebug('explorbot:provider');
|
|
17
17
|
const promptLog = createDebug('explorbot:provider:out');
|
|
@@ -22,6 +22,8 @@ export class ContextLengthError extends Error {}
|
|
|
22
22
|
|
|
23
23
|
let telemetryRegistered = false;
|
|
24
24
|
|
|
25
|
+
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'];
|
|
26
|
+
|
|
25
27
|
function extractCachedTokens(usage: any): number {
|
|
26
28
|
if (!usage) return 0;
|
|
27
29
|
const direct = usage.inputTokenDetails?.cacheReadTokens ?? usage.cachedInputTokens;
|
|
@@ -31,20 +33,27 @@ function extractCachedTokens(usage: any): number {
|
|
|
31
33
|
return typeof fromRaw === 'number' ? fromRaw : 0;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
function
|
|
36
|
+
function abortAfterIdle(ms: number, cancel: { cancelled: boolean }, controller: AbortController): Promise<never> {
|
|
35
37
|
return new Promise((_, reject) => {
|
|
36
38
|
const tick = () => {
|
|
37
|
-
if (
|
|
39
|
+
if (cancel.cancelled) return;
|
|
38
40
|
if (executionController.isAwaitingInput()) {
|
|
39
41
|
setTimeout(tick, ms);
|
|
40
42
|
return;
|
|
41
43
|
}
|
|
44
|
+
controller.abort();
|
|
42
45
|
reject(new Error('AI request timeout'));
|
|
43
46
|
};
|
|
44
47
|
setTimeout(tick, ms);
|
|
45
48
|
});
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
function combinedAbortSignal(controller: AbortController): AbortSignal {
|
|
52
|
+
const executionSignal = executionController.getAbortSignal();
|
|
53
|
+
if (!executionSignal) return controller.signal;
|
|
54
|
+
return AbortSignal.any([controller.signal, executionSignal]);
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
export class Provider {
|
|
49
58
|
private config: AIConfig;
|
|
50
59
|
private telemetryEnabled = false;
|
|
@@ -69,7 +78,6 @@ export class Provider {
|
|
|
69
78
|
},
|
|
70
79
|
};
|
|
71
80
|
|
|
72
|
-
static readonly CONTEXT_LENGTH_PATTERNS = ['reduce the length', 'context length', 'maximum context', 'token limit', 'too many tokens', 'max_tokens', 'context_length_exceeded', 'output truncated at maxtokens'];
|
|
73
81
|
lastConversation: Conversation | null = null;
|
|
74
82
|
|
|
75
83
|
constructor(config: AIConfig) {
|
|
@@ -137,12 +145,12 @@ export class Provider {
|
|
|
137
145
|
return parts.length > 0 ? parts.join('\n\n') : undefined;
|
|
138
146
|
}
|
|
139
147
|
|
|
140
|
-
getProviderOptionsForAgent(agentName: string): Record<string, any> | undefined {
|
|
148
|
+
private getProviderOptionsForAgent(agentName: string): Record<string, any> | undefined {
|
|
141
149
|
const agentConfig = this.config.agents?.[agentName as keyof typeof this.config.agents];
|
|
142
150
|
return agentConfig?.providerOptions;
|
|
143
151
|
}
|
|
144
152
|
|
|
145
|
-
getReasoningForAgent(agentName?: string): string | undefined {
|
|
153
|
+
private getReasoningForAgent(agentName?: string): string | undefined {
|
|
146
154
|
if (!agentName) return undefined;
|
|
147
155
|
const agentConfig = this.config.agents?.[agentName as keyof typeof this.config.agents];
|
|
148
156
|
return agentConfig?.reasoning;
|
|
@@ -171,6 +179,50 @@ export class Provider {
|
|
|
171
179
|
if (reasoning) config.reasoning ??= reasoning;
|
|
172
180
|
}
|
|
173
181
|
|
|
182
|
+
private buildGenerateConfig(defaults: Record<string, any>, overrides: Record<string, any>, options: any): Record<string, any> {
|
|
183
|
+
const telemetry = this.getTelemetry(options);
|
|
184
|
+
const config = this.mergeProviderOptions(
|
|
185
|
+
{
|
|
186
|
+
...defaults,
|
|
187
|
+
allowSystemInMessages: true,
|
|
188
|
+
...(this.config.config || {}),
|
|
189
|
+
...options,
|
|
190
|
+
...overrides,
|
|
191
|
+
},
|
|
192
|
+
options.agentName
|
|
193
|
+
);
|
|
194
|
+
this.finalizeConfig(config, options, telemetry);
|
|
195
|
+
return config;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private recordUsage(agentName: string, modelName: string, usage: any): void {
|
|
199
|
+
if (!usage) return;
|
|
200
|
+
Stats.recordTokens(agentName, modelName, {
|
|
201
|
+
input: usage.inputTokens ?? usage.promptTokens ?? 0,
|
|
202
|
+
output: usage.outputTokens ?? usage.completionTokens ?? 0,
|
|
203
|
+
total: usage.totalTokens ?? 0,
|
|
204
|
+
cached: extractCachedTokens(usage),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private async raceWithIdleTimeout<T>(fn: (signal: AbortSignal) => Promise<T>, timeoutMs: number): Promise<T> {
|
|
209
|
+
const cancel = { cancelled: false };
|
|
210
|
+
const controller = new AbortController();
|
|
211
|
+
const combinedSignal = combinedAbortSignal(controller);
|
|
212
|
+
try {
|
|
213
|
+
return await Promise.race([fn(combinedSignal), abortAfterIdle(timeoutMs, cancel, controller)]);
|
|
214
|
+
} finally {
|
|
215
|
+
cancel.cancelled = true;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private async recoverFromContextLength(error: any, messages: ModelMessage[], options: any, retry: (messages: ModelMessage[], options: any) => Promise<any>): Promise<any> {
|
|
220
|
+
const reduced = this.tryReduceMessages(messages, options._contextRetryLevel || 0);
|
|
221
|
+
if (!reduced) throw new ContextLengthError(error.message || error.toString());
|
|
222
|
+
tag('warning').log('Context length exceeded, retrying with reduced messages...');
|
|
223
|
+
return retry(reduced.messages, { ...options, _contextRetryLevel: reduced.nextLevel });
|
|
224
|
+
}
|
|
225
|
+
|
|
174
226
|
private initLangfuse() {
|
|
175
227
|
const langfuseConfig = this.config.langfuse;
|
|
176
228
|
const publicKey = langfuseConfig?.publicKey || process.env.LANGFUSE_PUBLIC_KEY;
|
|
@@ -206,20 +258,25 @@ export class Provider {
|
|
|
206
258
|
|
|
207
259
|
const runTelemetry = Observability.getTelemetry();
|
|
208
260
|
|
|
209
|
-
|
|
261
|
+
let optionTelemetry = options.telemetry;
|
|
262
|
+
if (options.telemetryFunctionId && !optionTelemetry?.functionId) {
|
|
263
|
+
optionTelemetry = { ...optionTelemetry, functionId: options.telemetryFunctionId };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!optionTelemetry) {
|
|
210
267
|
return runTelemetry;
|
|
211
268
|
}
|
|
212
269
|
|
|
213
270
|
if (!runTelemetry) {
|
|
214
|
-
return
|
|
271
|
+
return optionTelemetry;
|
|
215
272
|
}
|
|
216
273
|
|
|
217
274
|
return {
|
|
218
275
|
...runTelemetry,
|
|
219
|
-
...
|
|
276
|
+
...optionTelemetry,
|
|
220
277
|
metadata: {
|
|
221
278
|
...runTelemetry.metadata,
|
|
222
|
-
...
|
|
279
|
+
...optionTelemetry.metadata,
|
|
223
280
|
},
|
|
224
281
|
};
|
|
225
282
|
}
|
|
@@ -253,12 +310,8 @@ export class Provider {
|
|
|
253
310
|
const toolCalls = response.toolCalls || [];
|
|
254
311
|
const toolResults = response.toolResults || [];
|
|
255
312
|
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
input: call.input,
|
|
259
|
-
output: toolResults[index]?.output,
|
|
260
|
-
wasSuccessful: toolResults[index]?.output?.success || false,
|
|
261
|
-
}));
|
|
313
|
+
const resultsById = new Map(toolResults.map((r: any) => [r.toolCallId, r]));
|
|
314
|
+
const toolExecutions = toolCalls.map((call: any) => toToolExecution(call.toolName || '', call.input, resultsById.get(call.toolCallId)?.output));
|
|
262
315
|
|
|
263
316
|
return { conversation, response, toolExecutions };
|
|
264
317
|
}
|
|
@@ -268,19 +321,7 @@ export class Provider {
|
|
|
268
321
|
setActivity(`🤖 Asking ${modelName}`, 'ai');
|
|
269
322
|
promptLog(`Using model: ${modelName}`);
|
|
270
323
|
|
|
271
|
-
const
|
|
272
|
-
const config = this.mergeProviderOptions(
|
|
273
|
-
{
|
|
274
|
-
maxOutputTokens: 16384,
|
|
275
|
-
allowSystemInMessages: true,
|
|
276
|
-
...(this.config.config || {}),
|
|
277
|
-
...options,
|
|
278
|
-
model,
|
|
279
|
-
abortSignal: executionController.getAbortSignal(),
|
|
280
|
-
},
|
|
281
|
-
options.agentName
|
|
282
|
-
);
|
|
283
|
-
this.finalizeConfig(config, options, telemetry);
|
|
324
|
+
const config = this.buildGenerateConfig({ maxOutputTokens: 16384 }, { model, abortSignal: executionController.getAbortSignal() }, options);
|
|
284
325
|
|
|
285
326
|
promptLog(messages[messages.length - 1].content);
|
|
286
327
|
try {
|
|
@@ -302,14 +343,7 @@ export class Provider {
|
|
|
302
343
|
clearActivity();
|
|
303
344
|
responseLog(response.text);
|
|
304
345
|
|
|
305
|
-
|
|
306
|
-
Stats.recordTokens(options.agentName || 'unknown', modelName, {
|
|
307
|
-
input: response.usage.inputTokens ?? response.usage.promptTokens ?? 0,
|
|
308
|
-
output: response.usage.outputTokens ?? response.usage.completionTokens ?? 0,
|
|
309
|
-
total: response.usage.totalTokens ?? 0,
|
|
310
|
-
cached: extractCachedTokens(response.usage),
|
|
311
|
-
});
|
|
312
|
-
}
|
|
346
|
+
this.recordUsage(options.agentName || 'unknown', modelName, response.usage);
|
|
313
347
|
|
|
314
348
|
return response;
|
|
315
349
|
} catch (error: any) {
|
|
@@ -317,12 +351,7 @@ export class Provider {
|
|
|
317
351
|
if (error?.name === 'AbortError') throw error;
|
|
318
352
|
if (error instanceof ContextLengthError) throw error;
|
|
319
353
|
if (Provider.isContextLengthError(error)) {
|
|
320
|
-
|
|
321
|
-
if (reduced) {
|
|
322
|
-
tag('warning').log('Context length exceeded, retrying with reduced messages...');
|
|
323
|
-
return this.chat(reduced.messages, model, { ...options, _contextRetryLevel: reduced.nextLevel });
|
|
324
|
-
}
|
|
325
|
-
throw new ContextLengthError(error.message || error.toString());
|
|
354
|
+
return this.recoverFromContextLength(error, messages, options, (m, o) => this.chat(m, model, o));
|
|
326
355
|
}
|
|
327
356
|
const message = error.message || error.toString();
|
|
328
357
|
if (message !== 'No response text from AI') {
|
|
@@ -342,47 +371,19 @@ export class Provider {
|
|
|
342
371
|
promptLog('Available tools:', toolNames);
|
|
343
372
|
promptLog(messages[messages.length - 1].content);
|
|
344
373
|
|
|
345
|
-
const telemetry = this.getTelemetry(options);
|
|
346
374
|
const maxRoundtrips = options.maxToolRoundtrips ?? 5;
|
|
347
375
|
const extraStop = options.stopWhen;
|
|
348
376
|
const stopConditions: any[] = [isStepCount(maxRoundtrips)];
|
|
349
377
|
if (extraStop) stopConditions.push(extraStop);
|
|
350
|
-
const { stopWhen:
|
|
351
|
-
const config = this.mergeProviderOptions(
|
|
352
|
-
{
|
|
353
|
-
tools,
|
|
354
|
-
maxOutputTokens: 16384,
|
|
355
|
-
toolChoice: 'auto',
|
|
356
|
-
allowSystemInMessages: true,
|
|
357
|
-
...(this.config.config || {}),
|
|
358
|
-
...optionsWithoutStop,
|
|
359
|
-
stopWhen: stopConditions,
|
|
360
|
-
model,
|
|
361
|
-
abortSignal: executionController.getAbortSignal(),
|
|
362
|
-
},
|
|
363
|
-
options.agentName
|
|
364
|
-
);
|
|
365
|
-
this.finalizeConfig(config, options, telemetry);
|
|
378
|
+
const config = this.buildGenerateConfig({ tools, maxOutputTokens: 16384, toolChoice: 'auto' }, { stopWhen: stopConditions, model }, options);
|
|
366
379
|
try {
|
|
367
380
|
const response = await withRetry(async () => {
|
|
368
|
-
const
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
generateText({
|
|
373
|
-
messages,
|
|
374
|
-
...config,
|
|
375
|
-
}),
|
|
376
|
-
rejectAfterIdle(timeout, cancel),
|
|
377
|
-
])) as any;
|
|
378
|
-
const hasToolCall = (result.toolCalls?.length || 0) > 0;
|
|
379
|
-
if (!result.text && !hasToolCall && result.finishReason === 'length') {
|
|
380
|
-
throw new ContextLengthError('AI response empty: output truncated at maxTokens. Increase maxOutputTokens in config or use a model with higher output capacity.');
|
|
381
|
-
}
|
|
382
|
-
return result;
|
|
383
|
-
} finally {
|
|
384
|
-
cancel.cancelled = true;
|
|
381
|
+
const result = (await this.raceWithIdleTimeout((signal) => generateText({ messages, ...config, abortSignal: signal }), config.timeout || 30000)) as any;
|
|
382
|
+
const hasToolCall = (result.toolCalls?.length || 0) > 0;
|
|
383
|
+
if (!result.text && !hasToolCall && result.finishReason === 'length') {
|
|
384
|
+
throw new ContextLengthError('AI response empty: output truncated at maxTokens. Increase maxOutputTokens in config or use a model with higher output capacity.');
|
|
385
385
|
}
|
|
386
|
+
return result;
|
|
386
387
|
}, this.getRetryOptions(options));
|
|
387
388
|
|
|
388
389
|
clearActivity();
|
|
@@ -397,14 +398,7 @@ export class Provider {
|
|
|
397
398
|
|
|
398
399
|
responseLog(response.text);
|
|
399
400
|
|
|
400
|
-
|
|
401
|
-
Stats.recordTokens(options.agentName || 'unknown', modelName, {
|
|
402
|
-
input: response.usage.inputTokens ?? response.usage.promptTokens ?? 0,
|
|
403
|
-
output: response.usage.outputTokens ?? response.usage.completionTokens ?? 0,
|
|
404
|
-
total: response.usage.totalTokens ?? 0,
|
|
405
|
-
cached: extractCachedTokens(response.usage),
|
|
406
|
-
});
|
|
407
|
-
}
|
|
401
|
+
this.recordUsage(options.agentName || 'unknown', modelName, response.usage);
|
|
408
402
|
|
|
409
403
|
return response;
|
|
410
404
|
} catch (error: any) {
|
|
@@ -415,12 +409,7 @@ export class Provider {
|
|
|
415
409
|
if (error?.name === 'AbortError') throw error;
|
|
416
410
|
if (error instanceof ContextLengthError) throw error;
|
|
417
411
|
if (Provider.isContextLengthError(error)) {
|
|
418
|
-
|
|
419
|
-
if (reduced) {
|
|
420
|
-
tag('warning').log('Context length exceeded, retrying with reduced messages...');
|
|
421
|
-
return this.generateWithTools(reduced.messages, model, tools, { ...options, _contextRetryLevel: reduced.nextLevel });
|
|
422
|
-
}
|
|
423
|
-
throw new ContextLengthError(error.message || error.toString());
|
|
412
|
+
return this.recoverFromContextLength(error, messages, options, (m, o) => this.generateWithTools(m, model, tools, o));
|
|
424
413
|
}
|
|
425
414
|
if (error.constructor?.name === 'AI_APICallError') {
|
|
426
415
|
responseLog(error.message);
|
|
@@ -436,49 +425,18 @@ export class Provider {
|
|
|
436
425
|
setActivity(`🤖 Asking ${modelName} for structured output`, 'ai');
|
|
437
426
|
promptLog(`Using model: ${modelName}`);
|
|
438
427
|
|
|
439
|
-
const
|
|
440
|
-
const config = this.mergeProviderOptions(
|
|
441
|
-
{
|
|
442
|
-
schema,
|
|
443
|
-
allowSystemInMessages: true,
|
|
444
|
-
...(this.config.config || {}),
|
|
445
|
-
...options,
|
|
446
|
-
model: modelToUse,
|
|
447
|
-
abortSignal: executionController.getAbortSignal(),
|
|
448
|
-
},
|
|
449
|
-
options.agentName
|
|
450
|
-
);
|
|
451
|
-
this.finalizeConfig(config, options, telemetry);
|
|
428
|
+
const config = this.buildGenerateConfig({ schema }, { model: modelToUse }, options);
|
|
452
429
|
|
|
453
430
|
try {
|
|
454
431
|
promptLog(messages[messages.length - 1].content);
|
|
455
432
|
const response = await withRetry(async () => {
|
|
456
|
-
|
|
457
|
-
const cancel = { cancelled: false };
|
|
458
|
-
try {
|
|
459
|
-
return (await Promise.race([
|
|
460
|
-
generateObject({
|
|
461
|
-
messages,
|
|
462
|
-
...config,
|
|
463
|
-
}),
|
|
464
|
-
rejectAfterIdle(timeout, cancel),
|
|
465
|
-
])) as any;
|
|
466
|
-
} finally {
|
|
467
|
-
cancel.cancelled = true;
|
|
468
|
-
}
|
|
433
|
+
return (await this.raceWithIdleTimeout((signal) => generateObject({ messages, ...config, abortSignal: signal }), config.timeout || 30000)) as any;
|
|
469
434
|
}, this.getRetryOptions(options));
|
|
470
435
|
|
|
471
436
|
clearActivity();
|
|
472
437
|
responseLog(response.object);
|
|
473
438
|
|
|
474
|
-
|
|
475
|
-
Stats.recordTokens(options.agentName || 'unknown', modelName, {
|
|
476
|
-
input: response.usage.inputTokens ?? response.usage.promptTokens ?? 0,
|
|
477
|
-
output: response.usage.outputTokens ?? response.usage.completionTokens ?? 0,
|
|
478
|
-
total: response.usage.totalTokens ?? 0,
|
|
479
|
-
cached: extractCachedTokens(response.usage),
|
|
480
|
-
});
|
|
481
|
-
}
|
|
439
|
+
this.recordUsage(options.agentName || 'unknown', modelName, response.usage);
|
|
482
440
|
|
|
483
441
|
return response;
|
|
484
442
|
} catch (error: any) {
|
|
@@ -486,12 +444,7 @@ export class Provider {
|
|
|
486
444
|
if (error?.name === 'AbortError') throw error;
|
|
487
445
|
if (error instanceof ContextLengthError) throw error;
|
|
488
446
|
if (Provider.isContextLengthError(error)) {
|
|
489
|
-
|
|
490
|
-
if (reduced) {
|
|
491
|
-
tag('warning').log('Context length exceeded, retrying with reduced messages...');
|
|
492
|
-
return this.generateObject(reduced.messages, schema, model, { ...options, _contextRetryLevel: reduced.nextLevel });
|
|
493
|
-
}
|
|
494
|
-
throw new ContextLengthError(error.message || error.toString());
|
|
447
|
+
return this.recoverFromContextLength(error, messages, options, (m, o) => this.generateObject(m, schema, model, o));
|
|
495
448
|
}
|
|
496
449
|
throw new AiError(error.message || error.toString());
|
|
497
450
|
}
|
|
@@ -499,7 +452,7 @@ export class Provider {
|
|
|
499
452
|
|
|
500
453
|
static isContextLengthError(error: any): boolean {
|
|
501
454
|
const msg = (error?.message || error?.toString() || '').toLowerCase();
|
|
502
|
-
return
|
|
455
|
+
return CONTEXT_LENGTH_PATTERNS.some((p) => msg.includes(p));
|
|
503
456
|
}
|
|
504
457
|
|
|
505
458
|
static trimMessagesForRetry(messages: ModelMessage[]): ModelMessage[] | null {
|
|
@@ -670,13 +623,7 @@ export class Provider {
|
|
|
670
623
|
clearActivity();
|
|
671
624
|
responseLog(response.text);
|
|
672
625
|
|
|
673
|
-
|
|
674
|
-
Stats.recordTokens('vision', this.getModelName(this.config.visionModel), {
|
|
675
|
-
input: response.usage.inputTokens ?? response.usage.promptTokens ?? 0,
|
|
676
|
-
output: response.usage.outputTokens ?? response.usage.completionTokens ?? 0,
|
|
677
|
-
total: response.usage.totalTokens ?? 0,
|
|
678
|
-
});
|
|
679
|
-
}
|
|
626
|
+
this.recordUsage('vision', this.getModelName(this.config.visionModel), response.usage);
|
|
680
627
|
|
|
681
628
|
return response;
|
|
682
629
|
} catch (error: any) {
|
package/src/ai/quartermaster.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import type { ActionResult } from '../action-result.ts';
|
|
5
|
-
import {
|
|
5
|
+
import { outputPath } from '../config.ts';
|
|
6
6
|
import type { StateManager, StateTransition, WebPageState } from '../state-manager.ts';
|
|
7
7
|
import type { Task } from '../test-plan.ts';
|
|
8
8
|
import { createDebug, tag } from '../utils/logger.ts';
|
|
@@ -12,36 +12,6 @@ import type { Provider } from './provider.ts';
|
|
|
12
12
|
|
|
13
13
|
const debugLog = createDebug('explorbot:quartermaster');
|
|
14
14
|
|
|
15
|
-
interface AxeViolation {
|
|
16
|
-
id: string;
|
|
17
|
-
impact: 'critical' | 'serious' | 'moderate' | 'minor';
|
|
18
|
-
description: string;
|
|
19
|
-
helpUrl: string;
|
|
20
|
-
nodes: Array<{ html: string; target: string[] }>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface PageAnalysis {
|
|
24
|
-
url: string;
|
|
25
|
-
stateHash: string;
|
|
26
|
-
timestamp: string;
|
|
27
|
-
axeViolations: AxeViolation[];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface SemanticIssue {
|
|
31
|
-
type: 'unclear_intention' | 'confusing_naming' | 'hard_to_interact';
|
|
32
|
-
element: string;
|
|
33
|
-
issue: string;
|
|
34
|
-
suggestion: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface AnalysisReport {
|
|
38
|
-
scenario: string;
|
|
39
|
-
timestamp: string;
|
|
40
|
-
url: string;
|
|
41
|
-
axeViolations: AxeViolation[];
|
|
42
|
-
semanticIssues: SemanticIssue[];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
15
|
export class Quartermaster {
|
|
46
16
|
private provider: Provider;
|
|
47
17
|
private model?: any;
|
|
@@ -55,13 +25,12 @@ export class Quartermaster {
|
|
|
55
25
|
this.provider = provider;
|
|
56
26
|
this.model = options?.model;
|
|
57
27
|
|
|
58
|
-
|
|
59
|
-
this.outputDir = join(configParser.getOutputDir(), 'a11y');
|
|
28
|
+
this.outputDir = outputPath('a11y');
|
|
60
29
|
}
|
|
61
30
|
|
|
62
31
|
start(playwrightHelper: any, stateManager: StateManager): void {
|
|
63
32
|
this.playwrightHelper = playwrightHelper;
|
|
64
|
-
this.
|
|
33
|
+
mkdirSync(this.outputDir, { recursive: true });
|
|
65
34
|
|
|
66
35
|
this.unsubscribe = stateManager.onStateChange((event) => {
|
|
67
36
|
this.onPageChange(event);
|
|
@@ -78,12 +47,6 @@ export class Quartermaster {
|
|
|
78
47
|
debugLog('Quartermaster stopped');
|
|
79
48
|
}
|
|
80
49
|
|
|
81
|
-
private ensureDirectory(): void {
|
|
82
|
-
if (!existsSync(this.outputDir)) {
|
|
83
|
-
mkdirSync(this.outputDir, { recursive: true });
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
50
|
private onPageChange(event: StateTransition): void {
|
|
88
51
|
const state = event.toState;
|
|
89
52
|
if (!state?.hash) return;
|
|
@@ -284,3 +247,33 @@ Focus on what would confuse a real user or caused the agent to make mistakes.`;
|
|
|
284
247
|
return content;
|
|
285
248
|
}
|
|
286
249
|
}
|
|
250
|
+
|
|
251
|
+
interface AxeViolation {
|
|
252
|
+
id: string;
|
|
253
|
+
impact: 'critical' | 'serious' | 'moderate' | 'minor';
|
|
254
|
+
description: string;
|
|
255
|
+
helpUrl: string;
|
|
256
|
+
nodes: Array<{ html: string; target: string[] }>;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
interface PageAnalysis {
|
|
260
|
+
url: string;
|
|
261
|
+
stateHash: string;
|
|
262
|
+
timestamp: string;
|
|
263
|
+
axeViolations: AxeViolation[];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
interface SemanticIssue {
|
|
267
|
+
type: 'unclear_intention' | 'confusing_naming' | 'hard_to_interact';
|
|
268
|
+
element: string;
|
|
269
|
+
issue: string;
|
|
270
|
+
suggestion: string;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
interface AnalysisReport {
|
|
274
|
+
scenario: string;
|
|
275
|
+
timestamp: string;
|
|
276
|
+
url: string;
|
|
277
|
+
axeViolations: AxeViolation[];
|
|
278
|
+
semanticIssues: SemanticIssue[];
|
|
279
|
+
}
|
package/src/ai/rerunner.ts
CHANGED
|
@@ -17,10 +17,10 @@ import type Explorer from '../explorer.ts';
|
|
|
17
17
|
import type { KnowledgeTracker } from '../knowledge-tracker.ts';
|
|
18
18
|
import { Stats } from '../stats.ts';
|
|
19
19
|
import { Task, Test, TestResult } from '../test-plan.ts';
|
|
20
|
+
import { formatHeadings } from '../utils/context-formatter.ts';
|
|
20
21
|
import { createDebug, tag } from '../utils/logger.ts';
|
|
21
22
|
import { loop } from '../utils/loop.ts';
|
|
22
23
|
import { RulesLoader } from '../utils/rules-loader.ts';
|
|
23
|
-
import { loadTestSuites, printTestList } from '../utils/test-files.ts';
|
|
24
24
|
import type { Agent } from './agent.ts';
|
|
25
25
|
import { toolExecutionLabel } from './conversation.ts';
|
|
26
26
|
import type { Navigator } from './navigator.ts';
|
|
@@ -38,8 +38,9 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
38
38
|
private explorer: Explorer;
|
|
39
39
|
private provider: Provider;
|
|
40
40
|
private agentTools: any;
|
|
41
|
-
private healedSteps: Array<{
|
|
41
|
+
private healedSteps: Array<{ original: string; healed: string }> = [];
|
|
42
42
|
private traceDir = '';
|
|
43
|
+
private static pluginsWired = false;
|
|
43
44
|
|
|
44
45
|
constructor(explorer: Explorer, provider: Provider, agentTools?: any) {
|
|
45
46
|
super();
|
|
@@ -76,10 +77,6 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
76
77
|
return this.rerunnerConfig.healMaxIterations ?? 3;
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
listTests(testsDir: string): void {
|
|
80
|
-
printTestList(loadTestSuites(testsDir));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
80
|
async rerun(filePath: string, options?: { testIndices?: number[] }): Promise<RerunResult> {
|
|
84
81
|
const absPath = resolve(filePath);
|
|
85
82
|
if (!existsSync(absPath)) {
|
|
@@ -219,7 +216,6 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
219
216
|
|
|
220
217
|
private setupPlugins(): void {
|
|
221
218
|
const healMod = heal.default || heal;
|
|
222
|
-
healMod.connectToEvents();
|
|
223
219
|
|
|
224
220
|
healMod.addRecipe('explorbot-ai-healer', {
|
|
225
221
|
priority: 10,
|
|
@@ -233,21 +229,29 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
233
229
|
healMod.addRecipe(name, recipe);
|
|
234
230
|
}
|
|
235
231
|
|
|
232
|
+
const outputDir = (global as any).output_dir || 'output';
|
|
233
|
+
this.traceDir = `${outputDir}/rerun-traces`;
|
|
234
|
+
|
|
235
|
+
if (Rerunner.pluginsWired) return;
|
|
236
|
+
Rerunner.pluginsWired = true;
|
|
237
|
+
|
|
238
|
+
healMod.connectToEvents();
|
|
239
|
+
|
|
236
240
|
let currentTest: any = null;
|
|
237
241
|
let healTries = 0;
|
|
238
242
|
let isHealing = false;
|
|
239
243
|
let caughtError: any = null;
|
|
240
|
-
const healLimit = this.healLimit;
|
|
241
244
|
|
|
242
245
|
codeceptjs.event.dispatcher.on('test.before', (test: any) => {
|
|
243
246
|
currentTest = test;
|
|
244
247
|
healTries = 0;
|
|
245
248
|
caughtError = null;
|
|
249
|
+
isHealing = false;
|
|
246
250
|
});
|
|
247
251
|
|
|
248
252
|
codeceptjs.event.dispatcher.on('step.after', (step: any) => {
|
|
249
253
|
if (isHealing) return;
|
|
250
|
-
if (healTries >= healLimit) return;
|
|
254
|
+
if (healTries >= this.healLimit) return;
|
|
251
255
|
if (!healMod.hasCorrespondingRecipes(step)) return;
|
|
252
256
|
|
|
253
257
|
codeceptjs.recorder.catchWithoutStop(async (err: any) => {
|
|
@@ -286,10 +290,6 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
286
290
|
factor: 1.5,
|
|
287
291
|
});
|
|
288
292
|
|
|
289
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
290
|
-
const outputDir = (global as any).output_dir || 'output';
|
|
291
|
-
this.traceDir = `${outputDir}/rerun_${timestamp}`;
|
|
292
|
-
|
|
293
293
|
const aiTrace = aiTracePlugin.default || aiTracePlugin;
|
|
294
294
|
aiTrace(this.rerunnerConfig.aiTrace || { output: this.traceDir });
|
|
295
295
|
|
|
@@ -303,9 +303,9 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
303
303
|
|
|
304
304
|
private teardownHealing(): void {
|
|
305
305
|
const healMod = heal.default || heal;
|
|
306
|
-
healMod.recipes
|
|
306
|
+
Reflect.deleteProperty(healMod.recipes, 'explorbot-ai-healer');
|
|
307
307
|
for (const name of Object.keys(this.rerunnerConfig.recipes || {})) {
|
|
308
|
-
healMod.recipes
|
|
308
|
+
Reflect.deleteProperty(healMod.recipes, name);
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
|
|
@@ -432,7 +432,7 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
432
432
|
throw new Error(`Could not heal: ${failedCode}`);
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
-
this.healedSteps.push({
|
|
435
|
+
this.healedSteps.push({ original: failedCode, healed: healedCommand });
|
|
436
436
|
console.log(chalk.green(` ${figureSet.tick} Healed: ${healedCommand}`));
|
|
437
437
|
};
|
|
438
438
|
}
|
|
@@ -477,11 +477,7 @@ export class Rerunner extends TaskAgent implements Agent {
|
|
|
477
477
|
const state = this.explorer.getStateManager().getCurrentState();
|
|
478
478
|
const actionResult = state ? ActionResult.fromState(state) : null;
|
|
479
479
|
|
|
480
|
-
const headings
|
|
481
|
-
if (state?.h1) headings.push(`H1: ${state.h1}`);
|
|
482
|
-
if (state?.h2) headings.push(`H2: ${state.h2}`);
|
|
483
|
-
if (state?.h3) headings.push(`H3: ${state.h3}`);
|
|
484
|
-
if (state?.h4) headings.push(`H4: ${state.h4}`);
|
|
480
|
+
const headings = formatHeadings(state || {});
|
|
485
481
|
|
|
486
482
|
return dedent`
|
|
487
483
|
A test step failed and needs healing.
|
|
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'no
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { Worker } from 'node:worker_threads';
|
|
4
4
|
import { outputPath } from '../../config.ts';
|
|
5
|
+
import { TTLCache } from '../../utils/cache.ts';
|
|
5
6
|
import { computeHtmlFingerprint } from '../../utils/html-diff.ts';
|
|
6
7
|
import { debugLog } from './mixin.ts';
|
|
7
8
|
|
|
@@ -10,8 +11,7 @@ const FINGERPRINT_MAX_AGE_MS = 60 * 60 * 1000; // 1 hour
|
|
|
10
11
|
const FINGERPRINT_WORKER_TIMEOUT_MS = 10_000;
|
|
11
12
|
const SIMILARITY_THRESHOLD = 90;
|
|
12
13
|
|
|
13
|
-
const memoryCache
|
|
14
|
-
const memoryCacheTimestamps: Record<string, number> = {};
|
|
14
|
+
const memoryCache = new TTLCache<string>(CACHE_TTL_MS);
|
|
15
15
|
|
|
16
16
|
let fingerprintWorker: Worker | null = null;
|
|
17
17
|
|
|
@@ -28,25 +28,20 @@ function getFingerprintWorker(): Worker {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export function clearResearchCache(): void {
|
|
31
|
-
|
|
32
|
-
for (const key of Object.keys(memoryCacheTimestamps)) delete memoryCacheTimestamps[key];
|
|
31
|
+
memoryCache.clear();
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
export function getCachedResearch(hash: string): string {
|
|
36
35
|
if (!hash) return '';
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
if (timestamp && now - timestamp <= CACHE_TTL_MS) {
|
|
40
|
-
return memoryCache[hash] || '';
|
|
41
|
-
}
|
|
36
|
+
const cached = memoryCache.get(hash);
|
|
37
|
+
if (cached !== undefined) return cached;
|
|
42
38
|
const researchFile = outputPath('research', `${hash}.md`);
|
|
43
39
|
if (!existsSync(researchFile)) return '';
|
|
44
40
|
const stats = statSync(researchFile);
|
|
45
|
-
if (now - stats.mtimeMs > CACHE_TTL_MS) return '';
|
|
46
|
-
const
|
|
47
|
-
memoryCache
|
|
48
|
-
|
|
49
|
-
return cached;
|
|
41
|
+
if (Date.now() - stats.mtimeMs > CACHE_TTL_MS) return '';
|
|
42
|
+
const fromDisk = readFileSync(researchFile, 'utf8');
|
|
43
|
+
memoryCache.set(hash, fromDisk);
|
|
44
|
+
return fromDisk;
|
|
50
45
|
}
|
|
51
46
|
|
|
52
47
|
export function getPreviousResearch(hash: string): string {
|
|
@@ -61,8 +56,7 @@ export function saveResearch(hash: string, text: string, combinedHtml?: string):
|
|
|
61
56
|
const researchFile = join(researchDir, `${hash}.md`);
|
|
62
57
|
if (!existsSync(researchDir)) mkdirSync(researchDir, { recursive: true });
|
|
63
58
|
writeFileSync(researchFile, text);
|
|
64
|
-
memoryCache
|
|
65
|
-
memoryCacheTimestamps[hash] = Date.now();
|
|
59
|
+
memoryCache.set(hash, text);
|
|
66
60
|
debugLog(`Research saved to ${researchFile}`);
|
|
67
61
|
|
|
68
62
|
if (combinedHtml) {
|