explorbot 0.1.32-beta.1 → 0.2.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/README.md +35 -19
- package/bin/explorbot-cli.ts +33 -10
- package/boat/api-tester/src/config.ts +45 -3
- package/boat/doc-collector/src/ai/documentarian.ts +26 -28
- package/boat/doc-collector/src/ai/tools.ts +74 -16
- package/boat/doc-collector/src/cli.ts +1 -6
- package/boat/doc-collector/src/docbot.ts +30 -9
- package/boat/doc-collector/src/docs-renderer.ts +19 -24
- package/boat/doc-collector/src/screenshots.ts +34 -2
- package/boat/doc-collector/src/state-diagram.ts +281 -0
- package/dist/bin/explorbot-cli.js +29 -9
- package/dist/boat/api-tester/src/config.js +43 -4
- package/dist/boat/doc-collector/src/ai/documentarian.js +19 -7
- package/dist/boat/doc-collector/src/ai/tools.js +53 -16
- package/dist/boat/doc-collector/src/cli.js +1 -5
- package/dist/boat/doc-collector/src/docbot.js +27 -8
- package/dist/boat/doc-collector/src/docs-renderer.js +18 -3
- package/dist/boat/doc-collector/src/screenshots.js +30 -1
- package/dist/boat/doc-collector/src/state-diagram.js +200 -0
- package/dist/models.json +30 -0
- package/dist/package.json +23 -3
- package/dist/src/action-result.d.ts +8 -10
- package/dist/src/action-result.js +72 -164
- package/dist/src/action.d.ts +13 -12
- package/dist/src/action.js +37 -113
- package/dist/src/ai/agent.d.ts +17 -0
- package/dist/src/ai/captain/idle-mode.js +1 -1
- package/dist/src/ai/captain/test-mode.js +1 -1
- package/dist/src/ai/captain/web-mode.js +18 -29
- package/dist/src/ai/captain.d.ts +1 -3
- package/dist/src/ai/captain.js +17 -45
- package/dist/src/ai/conversation.d.ts +2 -5
- package/dist/src/ai/conversation.js +9 -28
- package/dist/src/ai/driller.d.ts +2 -17
- package/dist/src/ai/driller.js +26 -50
- package/dist/src/ai/experience-compactor.d.ts +2 -2
- package/dist/src/ai/experience-compactor.js +16 -33
- package/dist/src/ai/fisherman-tools.js +1 -1
- package/dist/src/ai/fisherman.d.ts +0 -1
- package/dist/src/ai/fisherman.js +0 -3
- package/dist/src/ai/historian/codeceptjs.d.ts +2 -0
- package/dist/src/ai/historian/codeceptjs.js +4 -3
- package/dist/src/ai/historian/playwright.d.ts +4 -1
- package/dist/src/ai/historian/playwright.js +4 -3
- package/dist/src/ai/historian/screencast.d.ts +2 -1
- package/dist/src/ai/historian/screencast.js +2 -2
- package/dist/src/ai/historian/utils.d.ts +0 -1
- package/dist/src/ai/historian/utils.js +0 -1
- package/dist/src/ai/historian.d.ts +10 -8
- package/dist/src/ai/historian.js +1 -5
- package/dist/src/ai/navigator.d.ts +8 -6
- package/dist/src/ai/navigator.js +34 -65
- package/dist/src/ai/pilot.d.ts +13 -8
- package/dist/src/ai/pilot.js +53 -59
- package/dist/src/ai/planner.d.ts +5 -4
- package/dist/src/ai/planner.js +66 -52
- package/dist/src/ai/provider.d.ts +4 -1
- package/dist/src/ai/provider.js +77 -137
- package/dist/src/ai/quartermaster.d.ts +22 -22
- package/dist/src/ai/quartermaster.js +8 -14
- package/dist/src/ai/rerunner.d.ts +3 -13
- package/dist/src/ai/rerunner.js +23 -44
- package/dist/src/ai/researcher/cache.js +11 -18
- package/dist/src/ai/researcher/coordinates.js +4 -3
- package/dist/src/ai/researcher/deep-analysis.d.ts +2 -0
- package/dist/src/ai/researcher/deep-analysis.js +8 -11
- package/dist/src/ai/researcher/locators.d.ts +9 -2
- package/dist/src/ai/researcher/locators.js +65 -8
- package/dist/src/ai/researcher/parser.js +1 -1
- package/dist/src/ai/researcher/research-result.d.ts +0 -1
- package/dist/src/ai/researcher/research-result.js +0 -19
- package/dist/src/ai/researcher/sections.d.ts +2 -0
- package/dist/src/ai/researcher/sections.js +7 -3
- package/dist/src/ai/researcher.js +40 -106
- package/dist/src/ai/rules.d.ts +1 -0
- package/dist/src/ai/rules.js +22 -15
- package/dist/src/ai/session-analyst.js +8 -5
- package/dist/src/ai/task-agent.d.ts +19 -6
- package/dist/src/ai/task-agent.js +37 -33
- package/dist/src/ai/tester.d.ts +6 -14
- package/dist/src/ai/tester.js +46 -111
- package/dist/src/ai/tools.d.ts +26 -14
- package/dist/src/ai/tools.js +138 -132
- package/dist/src/command-handler.js +1 -1
- package/dist/src/commands/compact-command.d.ts +1 -1
- package/dist/src/commands/compact-command.js +6 -6
- package/dist/src/commands/context-aria-command.js +1 -1
- package/dist/src/commands/context-command.js +8 -6
- package/dist/src/commands/context-data-command.js +2 -2
- package/dist/src/commands/context-experience-command.js +2 -2
- package/dist/src/commands/context-html-command.js +2 -2
- package/dist/src/commands/context-knowledge-command.js +2 -2
- package/dist/src/commands/drill-command.js +1 -1
- package/dist/src/commands/experience-command.js +1 -1
- package/dist/src/commands/explore-command.js +4 -3
- package/dist/src/commands/freesail-command.js +2 -2
- package/dist/src/commands/knows-command.js +1 -1
- package/dist/src/commands/learn-command.js +5 -3
- package/dist/src/commands/path-command.js +1 -1
- package/dist/src/commands/research-command.js +1 -1
- package/dist/src/commands/test-command.js +1 -1
- package/dist/src/components/AddKnowledge.d.ts +2 -0
- package/dist/src/components/AddKnowledge.js +3 -7
- package/dist/src/components/App.js +3 -3
- package/dist/src/config.d.ts +19 -0
- package/dist/src/config.js +156 -10
- package/dist/src/experience-tracker.d.ts +11 -6
- package/dist/src/experience-tracker.js +82 -155
- package/dist/src/explorbot.d.ts +21 -13
- package/dist/src/explorbot.js +84 -80
- package/dist/src/explorer.d.ts +66 -102
- package/dist/src/explorer.js +332 -623
- package/dist/src/knowledge-tracker.d.ts +2 -2
- package/dist/src/knowledge-tracker.js +38 -45
- package/dist/src/state-manager.d.ts +10 -30
- package/dist/src/state-manager.js +11 -129
- package/dist/src/utils/aria.d.ts +6 -1
- package/dist/src/utils/aria.js +4 -1
- package/dist/src/utils/cache.d.ts +15 -0
- package/dist/src/utils/cache.js +34 -0
- package/dist/src/utils/context-formatter.d.ts +6 -0
- package/dist/src/utils/context-formatter.js +15 -27
- package/dist/src/utils/hooks-runner.js +2 -4
- package/dist/src/utils/html.d.ts +5 -0
- package/dist/src/utils/html.js +71 -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-query.d.ts +6 -0
- package/dist/src/utils/markdown-query.js +14 -1
- package/dist/src/utils/page-readiness.d.ts +1 -0
- package/dist/src/utils/page-readiness.js +1 -1
- package/dist/src/utils/secrets.js +2 -2
- package/dist/src/utils/strings.d.ts +2 -0
- package/dist/src/utils/strings.js +10 -0
- package/dist/src/utils/test-files.js +1 -1
- package/dist/src/utils/web-annotate.d.ts +5 -0
- package/dist/src/utils/web-annotate.js +58 -0
- package/dist/src/utils/web-eidx.d.ts +2 -0
- package/dist/src/utils/web-eidx.js +20 -0
- package/dist/src/utils/web-element.d.ts +3 -1
- package/dist/src/utils/web-element.js +32 -0
- package/dist/src/utils/web-sandbox.d.ts +4 -0
- package/dist/src/utils/web-sandbox.js +37 -0
- package/models.json +30 -0
- package/package.json +23 -3
- package/src/action-result.ts +70 -173
- package/src/action.ts +38 -132
- package/src/ai/agent.ts +20 -0
- package/src/ai/captain/idle-mode.ts +1 -1
- package/src/ai/captain/test-mode.ts +1 -1
- package/src/ai/captain/web-mode.ts +18 -30
- package/src/ai/captain.ts +17 -42
- package/src/ai/conversation.ts +9 -32
- package/src/ai/driller.ts +53 -83
- 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 +5 -3
- package/src/ai/historian/playwright.ts +7 -4
- package/src/ai/historian/screencast.ts +4 -3
- package/src/ai/historian/utils.ts +0 -1
- package/src/ai/historian.ts +6 -8
- package/src/ai/navigator.ts +39 -70
- package/src/ai/pilot.ts +59 -61
- package/src/ai/planner.ts +76 -62
- package/src/ai/provider.ts +85 -146
- package/src/ai/quartermaster.ts +39 -45
- package/src/ai/rerunner.ts +27 -50
- package/src/ai/researcher/cache.ts +10 -16
- package/src/ai/researcher/coordinates.ts +4 -3
- package/src/ai/researcher/deep-analysis.ts +10 -11
- package/src/ai/researcher/locators.ts +66 -9
- package/src/ai/researcher/parser.ts +1 -1
- package/src/ai/researcher/research-result.ts +0 -18
- package/src/ai/researcher/sections.ts +9 -3
- package/src/ai/researcher.ts +42 -119
- package/src/ai/rules.ts +24 -13
- package/src/ai/session-analyst.ts +8 -5
- package/src/ai/task-agent.ts +45 -38
- package/src/ai/tester.ts +51 -129
- package/src/ai/tools.ts +145 -153
- package/src/command-handler.ts +1 -1
- package/src/commands/compact-command.ts +7 -8
- package/src/commands/context-aria-command.ts +1 -1
- package/src/commands/context-command.ts +8 -6
- package/src/commands/context-data-command.ts +2 -2
- package/src/commands/context-experience-command.ts +2 -2
- package/src/commands/context-html-command.ts +2 -2
- package/src/commands/context-knowledge-command.ts +2 -2
- package/src/commands/drill-command.ts +1 -1
- package/src/commands/experience-command.ts +1 -1
- package/src/commands/explore-command.ts +4 -3
- package/src/commands/freesail-command.ts +2 -2
- package/src/commands/knows-command.ts +1 -1
- package/src/commands/learn-command.ts +5 -3
- package/src/commands/path-command.ts +1 -1
- package/src/commands/research-command.ts +1 -1
- package/src/commands/test-command.ts +1 -1
- package/src/components/AddKnowledge.tsx +5 -7
- package/src/components/App.tsx +3 -3
- package/src/config.ts +185 -11
- package/src/experience-tracker.ts +89 -152
- package/src/explorbot.ts +88 -83
- package/src/explorer.ts +402 -693
- package/src/knowledge-tracker.ts +42 -49
- package/src/state-manager.ts +20 -155
- 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/hooks-runner.ts +2 -4
- 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 +2 -3
- package/src/utils/strings.ts +12 -0
- package/src/utils/test-files.ts +1 -1
- package/src/utils/web-annotate.ts +64 -0
- package/src/utils/web-eidx.ts +21 -0
- package/src/utils/web-element.ts +34 -0
- package/src/utils/web-sandbox.ts +43 -0
- package/dist/boat/api-tester/example/apibot.config.js +0 -30
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,10 +29,10 @@ 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);
|
|
@@ -43,6 +44,12 @@ function rejectAfterIdle(ms, signal, controller) {
|
|
|
43
44
|
setTimeout(tick, ms);
|
|
44
45
|
});
|
|
45
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
|
+
}
|
|
46
53
|
export class Provider {
|
|
47
54
|
config;
|
|
48
55
|
telemetryEnabled = false;
|
|
@@ -64,7 +71,6 @@ export class Provider {
|
|
|
64
71
|
!error.message.includes('output truncated at maxTokens'));
|
|
65
72
|
},
|
|
66
73
|
};
|
|
67
|
-
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'];
|
|
68
74
|
lastConversation = null;
|
|
69
75
|
constructor(config) {
|
|
70
76
|
if (!config?.model) {
|
|
@@ -81,7 +87,6 @@ export class Provider {
|
|
|
81
87
|
await generateText({
|
|
82
88
|
model: this.config.model,
|
|
83
89
|
prompt: 'hi',
|
|
84
|
-
maxOutputTokens: 1,
|
|
85
90
|
});
|
|
86
91
|
}
|
|
87
92
|
catch (error) {
|
|
@@ -161,6 +166,46 @@ export class Provider {
|
|
|
161
166
|
if (reasoning)
|
|
162
167
|
config.reasoning ??= reasoning;
|
|
163
168
|
}
|
|
169
|
+
buildGenerateConfig(defaults, overrides, options) {
|
|
170
|
+
const telemetry = this.getTelemetry(options);
|
|
171
|
+
const config = this.mergeProviderOptions({
|
|
172
|
+
...defaults,
|
|
173
|
+
allowSystemInMessages: true,
|
|
174
|
+
...(this.config.config || {}),
|
|
175
|
+
...options,
|
|
176
|
+
...overrides,
|
|
177
|
+
}, options.agentName);
|
|
178
|
+
this.finalizeConfig(config, options, telemetry);
|
|
179
|
+
return config;
|
|
180
|
+
}
|
|
181
|
+
recordUsage(agentName, modelName, usage) {
|
|
182
|
+
if (!usage)
|
|
183
|
+
return;
|
|
184
|
+
Stats.recordTokens(agentName, modelName, {
|
|
185
|
+
input: usage.inputTokens ?? usage.promptTokens ?? 0,
|
|
186
|
+
output: usage.outputTokens ?? usage.completionTokens ?? 0,
|
|
187
|
+
total: usage.totalTokens ?? 0,
|
|
188
|
+
cached: extractCachedTokens(usage),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async raceWithIdleTimeout(fn, timeoutMs) {
|
|
192
|
+
const cancel = { cancelled: false };
|
|
193
|
+
const controller = new AbortController();
|
|
194
|
+
const combinedSignal = combinedAbortSignal(controller);
|
|
195
|
+
try {
|
|
196
|
+
return await Promise.race([fn(combinedSignal), abortAfterIdle(timeoutMs, cancel, controller)]);
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
cancel.cancelled = true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async recoverFromContextLength(error, messages, options, retry) {
|
|
203
|
+
const reduced = this.tryReduceMessages(messages, options._contextRetryLevel || 0);
|
|
204
|
+
if (!reduced)
|
|
205
|
+
throw new ContextLengthError(error.message || error.toString());
|
|
206
|
+
tag('warning').log('Context length exceeded, retrying with reduced messages...');
|
|
207
|
+
return retry(reduced.messages, { ...options, _contextRetryLevel: reduced.nextLevel });
|
|
208
|
+
}
|
|
164
209
|
initLangfuse() {
|
|
165
210
|
const langfuseConfig = this.config.langfuse;
|
|
166
211
|
const publicKey = langfuseConfig?.publicKey || process.env.LANGFUSE_PUBLIC_KEY;
|
|
@@ -191,18 +236,22 @@ export class Provider {
|
|
|
191
236
|
return undefined;
|
|
192
237
|
}
|
|
193
238
|
const runTelemetry = Observability.getTelemetry();
|
|
194
|
-
|
|
239
|
+
let optionTelemetry = options.telemetry;
|
|
240
|
+
if (options.telemetryFunctionId && !optionTelemetry?.functionId) {
|
|
241
|
+
optionTelemetry = { ...optionTelemetry, functionId: options.telemetryFunctionId };
|
|
242
|
+
}
|
|
243
|
+
if (!optionTelemetry) {
|
|
195
244
|
return runTelemetry;
|
|
196
245
|
}
|
|
197
246
|
if (!runTelemetry) {
|
|
198
|
-
return
|
|
247
|
+
return optionTelemetry;
|
|
199
248
|
}
|
|
200
249
|
return {
|
|
201
250
|
...runTelemetry,
|
|
202
|
-
...
|
|
251
|
+
...optionTelemetry,
|
|
203
252
|
metadata: {
|
|
204
253
|
...runTelemetry.metadata,
|
|
205
|
-
...
|
|
254
|
+
...optionTelemetry.metadata,
|
|
206
255
|
},
|
|
207
256
|
};
|
|
208
257
|
}
|
|
@@ -228,28 +277,15 @@ export class Provider {
|
|
|
228
277
|
this.lastConversation = conversation;
|
|
229
278
|
const toolCalls = response.toolCalls || [];
|
|
230
279
|
const toolResults = response.toolResults || [];
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
input: call.input,
|
|
234
|
-
output: toolResults[index]?.output,
|
|
235
|
-
wasSuccessful: toolResults[index]?.output?.success || false,
|
|
236
|
-
}));
|
|
280
|
+
const resultsById = new Map(toolResults.map((r) => [r.toolCallId, r]));
|
|
281
|
+
const toolExecutions = toolCalls.map((call) => toToolExecution(call.toolName || '', call.input, resultsById.get(call.toolCallId)?.output));
|
|
237
282
|
return { conversation, response, toolExecutions };
|
|
238
283
|
}
|
|
239
284
|
async chat(messages, model, options = {}) {
|
|
240
285
|
const modelName = this.getModelName(model);
|
|
241
286
|
setActivity(`🤖 Asking ${modelName}`, 'ai');
|
|
242
287
|
promptLog(`Using model: ${modelName}`);
|
|
243
|
-
const
|
|
244
|
-
const config = this.mergeProviderOptions({
|
|
245
|
-
maxOutputTokens: 16384,
|
|
246
|
-
allowSystemInMessages: true,
|
|
247
|
-
...(this.config.config || {}),
|
|
248
|
-
...options,
|
|
249
|
-
model,
|
|
250
|
-
abortSignal: executionController.getAbortSignal(),
|
|
251
|
-
}, options.agentName);
|
|
252
|
-
this.finalizeConfig(config, options, telemetry);
|
|
288
|
+
const config = this.buildGenerateConfig({ maxOutputTokens: 16384 }, { model, abortSignal: executionController.getAbortSignal() }, options);
|
|
253
289
|
promptLog(messages[messages.length - 1].content);
|
|
254
290
|
try {
|
|
255
291
|
const response = await withRetry(async () => {
|
|
@@ -268,14 +304,7 @@ export class Provider {
|
|
|
268
304
|
}, this.getRetryOptions(options));
|
|
269
305
|
clearActivity();
|
|
270
306
|
responseLog(response.text);
|
|
271
|
-
|
|
272
|
-
Stats.recordTokens(options.agentName || 'unknown', modelName, {
|
|
273
|
-
input: response.usage.inputTokens ?? response.usage.promptTokens ?? 0,
|
|
274
|
-
output: response.usage.outputTokens ?? response.usage.completionTokens ?? 0,
|
|
275
|
-
total: response.usage.totalTokens ?? 0,
|
|
276
|
-
cached: extractCachedTokens(response.usage),
|
|
277
|
-
});
|
|
278
|
-
}
|
|
307
|
+
this.recordUsage(options.agentName || 'unknown', modelName, response.usage);
|
|
279
308
|
return response;
|
|
280
309
|
}
|
|
281
310
|
catch (error) {
|
|
@@ -285,12 +314,7 @@ export class Provider {
|
|
|
285
314
|
if (error instanceof ContextLengthError)
|
|
286
315
|
throw error;
|
|
287
316
|
if (Provider.isContextLengthError(error)) {
|
|
288
|
-
|
|
289
|
-
if (reduced) {
|
|
290
|
-
tag('warning').log('Context length exceeded, retrying with reduced messages...');
|
|
291
|
-
return this.chat(reduced.messages, model, { ...options, _contextRetryLevel: reduced.nextLevel });
|
|
292
|
-
}
|
|
293
|
-
throw new ContextLengthError(error.message || error.toString());
|
|
317
|
+
return this.recoverFromContextLength(error, messages, options, (m, o) => this.chat(m, model, o));
|
|
294
318
|
}
|
|
295
319
|
const message = error.message || error.toString();
|
|
296
320
|
if (message !== 'No response text from AI') {
|
|
@@ -307,49 +331,20 @@ export class Provider {
|
|
|
307
331
|
tag('debug').log(`Tools enabled: [${toolNames.join(', ')}]`);
|
|
308
332
|
promptLog('Available tools:', toolNames);
|
|
309
333
|
promptLog(messages[messages.length - 1].content);
|
|
310
|
-
const telemetry = this.getTelemetry(options);
|
|
311
334
|
const maxRoundtrips = options.maxToolRoundtrips ?? 5;
|
|
312
335
|
const extraStop = options.stopWhen;
|
|
313
336
|
const stopConditions = [isStepCount(maxRoundtrips)];
|
|
314
337
|
if (extraStop)
|
|
315
338
|
stopConditions.push(extraStop);
|
|
316
|
-
const { stopWhen:
|
|
317
|
-
const config = this.mergeProviderOptions({
|
|
318
|
-
tools,
|
|
319
|
-
maxOutputTokens: 16384,
|
|
320
|
-
toolChoice: 'auto',
|
|
321
|
-
allowSystemInMessages: true,
|
|
322
|
-
...(this.config.config || {}),
|
|
323
|
-
...optionsWithoutStop,
|
|
324
|
-
stopWhen: stopConditions,
|
|
325
|
-
model,
|
|
326
|
-
abortSignal: executionController.getAbortSignal(),
|
|
327
|
-
}, options.agentName);
|
|
328
|
-
this.finalizeConfig(config, options, telemetry);
|
|
339
|
+
const config = this.buildGenerateConfig({ tools, maxOutputTokens: 16384, toolChoice: 'auto' }, { stopWhen: stopConditions, model }, options);
|
|
329
340
|
try {
|
|
330
341
|
const response = await withRetry(async () => {
|
|
331
|
-
const
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
try {
|
|
336
|
-
const result = (await Promise.race([
|
|
337
|
-
generateText({
|
|
338
|
-
messages,
|
|
339
|
-
...config,
|
|
340
|
-
abortSignal: combinedSignal,
|
|
341
|
-
}),
|
|
342
|
-
rejectAfterIdle(timeout, cancel, controller),
|
|
343
|
-
]));
|
|
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.');
|
|
347
|
-
}
|
|
348
|
-
return result;
|
|
349
|
-
}
|
|
350
|
-
finally {
|
|
351
|
-
cancel.cancelled = true;
|
|
342
|
+
const result = (await this.raceWithIdleTimeout((signal) => generateText({ messages, ...config, abortSignal: signal }), config.timeout || 30000));
|
|
343
|
+
const hasToolCall = (result.toolCalls?.length || 0) > 0;
|
|
344
|
+
if (!result.text && !hasToolCall && result.finishReason === 'length') {
|
|
345
|
+
throw new ContextLengthError('AI response empty: output truncated at maxTokens. Increase maxOutputTokens in config or use a model with higher output capacity.');
|
|
352
346
|
}
|
|
347
|
+
return result;
|
|
353
348
|
}, this.getRetryOptions(options));
|
|
354
349
|
clearActivity();
|
|
355
350
|
// Log tool usage summary
|
|
@@ -360,14 +355,7 @@ export class Provider {
|
|
|
360
355
|
});
|
|
361
356
|
}
|
|
362
357
|
responseLog(response.text);
|
|
363
|
-
|
|
364
|
-
Stats.recordTokens(options.agentName || 'unknown', modelName, {
|
|
365
|
-
input: response.usage.inputTokens ?? response.usage.promptTokens ?? 0,
|
|
366
|
-
output: response.usage.outputTokens ?? response.usage.completionTokens ?? 0,
|
|
367
|
-
total: response.usage.totalTokens ?? 0,
|
|
368
|
-
cached: extractCachedTokens(response.usage),
|
|
369
|
-
});
|
|
370
|
-
}
|
|
358
|
+
this.recordUsage(options.agentName || 'unknown', modelName, response.usage);
|
|
371
359
|
return response;
|
|
372
360
|
}
|
|
373
361
|
catch (error) {
|
|
@@ -380,12 +368,7 @@ export class Provider {
|
|
|
380
368
|
if (error instanceof ContextLengthError)
|
|
381
369
|
throw error;
|
|
382
370
|
if (Provider.isContextLengthError(error)) {
|
|
383
|
-
|
|
384
|
-
if (reduced) {
|
|
385
|
-
tag('warning').log('Context length exceeded, retrying with reduced messages...');
|
|
386
|
-
return this.generateWithTools(reduced.messages, model, tools, { ...options, _contextRetryLevel: reduced.nextLevel });
|
|
387
|
-
}
|
|
388
|
-
throw new ContextLengthError(error.message || error.toString());
|
|
371
|
+
return this.recoverFromContextLength(error, messages, options, (m, o) => this.generateWithTools(m, model, tools, o));
|
|
389
372
|
}
|
|
390
373
|
if (error.constructor?.name === 'AI_APICallError') {
|
|
391
374
|
responseLog(error.message);
|
|
@@ -399,47 +382,15 @@ export class Provider {
|
|
|
399
382
|
const modelName = this.getModelName(modelToUse);
|
|
400
383
|
setActivity(`🤖 Asking ${modelName} for structured output`, 'ai');
|
|
401
384
|
promptLog(`Using model: ${modelName}`);
|
|
402
|
-
const
|
|
403
|
-
const config = this.mergeProviderOptions({
|
|
404
|
-
schema,
|
|
405
|
-
allowSystemInMessages: true,
|
|
406
|
-
...(this.config.config || {}),
|
|
407
|
-
...options,
|
|
408
|
-
model: modelToUse,
|
|
409
|
-
abortSignal: executionController.getAbortSignal(),
|
|
410
|
-
}, options.agentName);
|
|
411
|
-
this.finalizeConfig(config, options, telemetry);
|
|
385
|
+
const config = this.buildGenerateConfig({ schema }, { model: modelToUse }, options);
|
|
412
386
|
try {
|
|
413
387
|
promptLog(messages[messages.length - 1].content);
|
|
414
388
|
const response = await withRetry(async () => {
|
|
415
|
-
|
|
416
|
-
const cancel = { cancelled: false };
|
|
417
|
-
const controller = new AbortController();
|
|
418
|
-
const combinedSignal = AbortSignal.any([controller.signal, executionController.getAbortSignal()].filter(Boolean));
|
|
419
|
-
try {
|
|
420
|
-
return (await Promise.race([
|
|
421
|
-
generateObject({
|
|
422
|
-
messages,
|
|
423
|
-
...config,
|
|
424
|
-
abortSignal: combinedSignal,
|
|
425
|
-
}),
|
|
426
|
-
rejectAfterIdle(timeout, cancel, controller),
|
|
427
|
-
]));
|
|
428
|
-
}
|
|
429
|
-
finally {
|
|
430
|
-
cancel.cancelled = true;
|
|
431
|
-
}
|
|
389
|
+
return (await this.raceWithIdleTimeout((signal) => generateObject({ messages, ...config, abortSignal: signal }), config.timeout || 30000));
|
|
432
390
|
}, this.getRetryOptions(options));
|
|
433
391
|
clearActivity();
|
|
434
392
|
responseLog(response.object);
|
|
435
|
-
|
|
436
|
-
Stats.recordTokens(options.agentName || 'unknown', modelName, {
|
|
437
|
-
input: response.usage.inputTokens ?? response.usage.promptTokens ?? 0,
|
|
438
|
-
output: response.usage.outputTokens ?? response.usage.completionTokens ?? 0,
|
|
439
|
-
total: response.usage.totalTokens ?? 0,
|
|
440
|
-
cached: extractCachedTokens(response.usage),
|
|
441
|
-
});
|
|
442
|
-
}
|
|
393
|
+
this.recordUsage(options.agentName || 'unknown', modelName, response.usage);
|
|
443
394
|
return response;
|
|
444
395
|
}
|
|
445
396
|
catch (error) {
|
|
@@ -449,19 +400,14 @@ export class Provider {
|
|
|
449
400
|
if (error instanceof ContextLengthError)
|
|
450
401
|
throw error;
|
|
451
402
|
if (Provider.isContextLengthError(error)) {
|
|
452
|
-
|
|
453
|
-
if (reduced) {
|
|
454
|
-
tag('warning').log('Context length exceeded, retrying with reduced messages...');
|
|
455
|
-
return this.generateObject(reduced.messages, schema, model, { ...options, _contextRetryLevel: reduced.nextLevel });
|
|
456
|
-
}
|
|
457
|
-
throw new ContextLengthError(error.message || error.toString());
|
|
403
|
+
return this.recoverFromContextLength(error, messages, options, (m, o) => this.generateObject(m, schema, model, o));
|
|
458
404
|
}
|
|
459
405
|
throw new AiError(error.message || error.toString());
|
|
460
406
|
}
|
|
461
407
|
}
|
|
462
408
|
static isContextLengthError(error) {
|
|
463
409
|
const msg = (error?.message || error?.toString() || '').toLowerCase();
|
|
464
|
-
return
|
|
410
|
+
return CONTEXT_LENGTH_PATTERNS.some((p) => msg.includes(p));
|
|
465
411
|
}
|
|
466
412
|
static trimMessagesForRetry(messages) {
|
|
467
413
|
const tagRegex = /<(\w[\w-]*)>([\s\S]*?)<\/\1>/g;
|
|
@@ -611,13 +557,7 @@ export class Provider {
|
|
|
611
557
|
}, this.getRetryOptions());
|
|
612
558
|
clearActivity();
|
|
613
559
|
responseLog(response.text);
|
|
614
|
-
|
|
615
|
-
Stats.recordTokens('vision', this.getModelName(this.config.visionModel), {
|
|
616
|
-
input: response.usage.inputTokens ?? response.usage.promptTokens ?? 0,
|
|
617
|
-
output: response.usage.outputTokens ?? response.usage.completionTokens ?? 0,
|
|
618
|
-
total: response.usage.totalTokens ?? 0,
|
|
619
|
-
});
|
|
620
|
-
}
|
|
560
|
+
this.recordUsage('vision', this.getModelName(this.config.visionModel), response.usage);
|
|
621
561
|
return response;
|
|
622
562
|
}
|
|
623
563
|
catch (error) {
|
|
@@ -1,8 +1,30 @@
|
|
|
1
1
|
import type { ActionResult } from '../action-result.js';
|
|
2
|
+
import type Explorer from '../explorer.js';
|
|
2
3
|
import type { StateManager, StateTransition, WebPageState } from '../state-manager.js';
|
|
3
4
|
import type { Task } from '../test-plan.js';
|
|
4
5
|
import type { Conversation, ToolExecution } from './conversation.js';
|
|
5
6
|
import type { Provider } from './provider.js';
|
|
7
|
+
export declare class Quartermaster {
|
|
8
|
+
provider: Provider;
|
|
9
|
+
model?: any;
|
|
10
|
+
outputDir: string;
|
|
11
|
+
pageAnalyses: Map<string, PageAnalysis>;
|
|
12
|
+
unsubscribe: (() => void) | null;
|
|
13
|
+
explorer: Explorer | null;
|
|
14
|
+
pendingAnalyses: Promise<void>[];
|
|
15
|
+
constructor(provider: Provider, options?: {
|
|
16
|
+
model?: any;
|
|
17
|
+
});
|
|
18
|
+
start(explorer: Explorer, stateManager: StateManager): void;
|
|
19
|
+
stop(): void;
|
|
20
|
+
onPageChange(event: StateTransition): void;
|
|
21
|
+
runAxeAnalysis(state: WebPageState): Promise<void>;
|
|
22
|
+
analyzeSession(task: Task, initialState: ActionResult, conversation: Conversation): Promise<AnalysisReport | null>;
|
|
23
|
+
generateSemanticAnalysis(axeViolations: AxeViolation[], executions: ToolExecution[], initialState: ActionResult): Promise<SemanticIssue[]>;
|
|
24
|
+
addNotesToTask(task: Task, report: AnalysisReport): void;
|
|
25
|
+
saveReport(stateHash: string, report: AnalysisReport): void;
|
|
26
|
+
formatReportMarkdown(report: AnalysisReport): string;
|
|
27
|
+
}
|
|
6
28
|
interface AxeViolation {
|
|
7
29
|
id: string;
|
|
8
30
|
impact: 'critical' | 'serious' | 'moderate' | 'minor';
|
|
@@ -32,26 +54,4 @@ interface AnalysisReport {
|
|
|
32
54
|
axeViolations: AxeViolation[];
|
|
33
55
|
semanticIssues: SemanticIssue[];
|
|
34
56
|
}
|
|
35
|
-
export declare class Quartermaster {
|
|
36
|
-
provider: Provider;
|
|
37
|
-
model?: any;
|
|
38
|
-
outputDir: string;
|
|
39
|
-
pageAnalyses: Map<string, PageAnalysis>;
|
|
40
|
-
unsubscribe: (() => void) | null;
|
|
41
|
-
playwrightHelper: any;
|
|
42
|
-
pendingAnalyses: Promise<void>[];
|
|
43
|
-
constructor(provider: Provider, options?: {
|
|
44
|
-
model?: any;
|
|
45
|
-
});
|
|
46
|
-
start(playwrightHelper: any, stateManager: StateManager): void;
|
|
47
|
-
stop(): void;
|
|
48
|
-
ensureDirectory(): void;
|
|
49
|
-
onPageChange(event: StateTransition): void;
|
|
50
|
-
runAxeAnalysis(state: WebPageState): Promise<void>;
|
|
51
|
-
analyzeSession(task: Task, initialState: ActionResult, conversation: Conversation): Promise<AnalysisReport | null>;
|
|
52
|
-
generateSemanticAnalysis(axeViolations: AxeViolation[], executions: ToolExecution[], initialState: ActionResult): Promise<SemanticIssue[]>;
|
|
53
|
-
addNotesToTask(task: Task, report: AnalysisReport): void;
|
|
54
|
-
saveReport(stateHash: string, report: AnalysisReport): void;
|
|
55
|
-
formatReportMarkdown(report: AnalysisReport): string;
|
|
56
|
-
}
|
|
57
57
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import {
|
|
4
|
+
import { outputPath } from "../config.js";
|
|
5
5
|
import { createDebug, tag } from "../utils/logger.js";
|
|
6
6
|
import { isCodeceptToolName } from "../utils/step-analyzer.js";
|
|
7
7
|
const debugLog = createDebug('explorbot:quartermaster');
|
|
@@ -11,17 +11,16 @@ export class Quartermaster {
|
|
|
11
11
|
outputDir;
|
|
12
12
|
pageAnalyses = new Map();
|
|
13
13
|
unsubscribe = null;
|
|
14
|
-
|
|
14
|
+
explorer = null;
|
|
15
15
|
pendingAnalyses = [];
|
|
16
16
|
constructor(provider, options) {
|
|
17
17
|
this.provider = provider;
|
|
18
18
|
this.model = options?.model;
|
|
19
|
-
|
|
20
|
-
this.outputDir = join(configParser.getOutputDir(), 'a11y');
|
|
19
|
+
this.outputDir = outputPath('a11y');
|
|
21
20
|
}
|
|
22
|
-
start(
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
21
|
+
start(explorer, stateManager) {
|
|
22
|
+
this.explorer = explorer;
|
|
23
|
+
mkdirSync(this.outputDir, { recursive: true });
|
|
25
24
|
this.unsubscribe = stateManager.onStateChange((event) => {
|
|
26
25
|
this.onPageChange(event);
|
|
27
26
|
});
|
|
@@ -34,11 +33,6 @@ export class Quartermaster {
|
|
|
34
33
|
}
|
|
35
34
|
debugLog('Quartermaster stopped');
|
|
36
35
|
}
|
|
37
|
-
ensureDirectory() {
|
|
38
|
-
if (!existsSync(this.outputDir)) {
|
|
39
|
-
mkdirSync(this.outputDir, { recursive: true });
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
36
|
onPageChange(event) {
|
|
43
37
|
const state = event.toState;
|
|
44
38
|
if (!state?.hash)
|
|
@@ -51,7 +45,7 @@ export class Quartermaster {
|
|
|
51
45
|
this.pendingAnalyses.push(analysisPromise);
|
|
52
46
|
}
|
|
53
47
|
async runAxeAnalysis(state) {
|
|
54
|
-
const page = this.
|
|
48
|
+
const page = this.explorer?.page;
|
|
55
49
|
if (!page || !state.hash) {
|
|
56
50
|
debugLog('No page available for axe analysis');
|
|
57
51
|
return;
|
|
@@ -1,32 +1,22 @@
|
|
|
1
|
-
import type { ExperienceTracker } from '../experience-tracker.js';
|
|
2
|
-
import type Explorer from '../explorer.js';
|
|
3
|
-
import type { KnowledgeTracker } from '../knowledge-tracker.js';
|
|
4
1
|
import { Test } from '../test-plan.js';
|
|
5
|
-
import type { Agent } from './agent.js';
|
|
2
|
+
import type { Agent, AgentDeps } from './agent.js';
|
|
6
3
|
import type { Navigator } from './navigator.js';
|
|
7
|
-
import { Provider } from './provider.js';
|
|
8
4
|
import { TaskAgent } from './task-agent.js';
|
|
9
5
|
export declare class Rerunner extends TaskAgent implements Agent {
|
|
10
6
|
readonly ACTION_TOOLS: string[];
|
|
11
7
|
emoji: string;
|
|
12
|
-
explorer: Explorer;
|
|
13
|
-
provider: Provider;
|
|
14
8
|
agentTools: any;
|
|
15
9
|
healedSteps: Array<{
|
|
16
|
-
test: string;
|
|
17
10
|
original: string;
|
|
18
11
|
healed: string;
|
|
19
12
|
}>;
|
|
20
13
|
traceDir: string;
|
|
21
|
-
|
|
14
|
+
static pluginsWired: boolean;
|
|
15
|
+
constructor(deps: AgentDeps, agentTools?: any);
|
|
22
16
|
getNavigator(): Navigator;
|
|
23
|
-
getExperienceTracker(): ExperienceTracker;
|
|
24
|
-
getKnowledgeTracker(): KnowledgeTracker;
|
|
25
|
-
getProvider(): Provider;
|
|
26
17
|
get rerunnerConfig(): Record<string, any>;
|
|
27
18
|
get healLimit(): number;
|
|
28
19
|
get healMaxIterations(): number;
|
|
29
|
-
listTests(testsDir: string): void;
|
|
30
20
|
rerun(filePath: string, options?: {
|
|
31
21
|
testIndices?: number[];
|
|
32
22
|
}): Promise<RerunResult>;
|