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.
Files changed (222) hide show
  1. package/README.md +35 -19
  2. package/bin/explorbot-cli.ts +33 -10
  3. package/boat/api-tester/src/config.ts +45 -3
  4. package/boat/doc-collector/src/ai/documentarian.ts +26 -28
  5. package/boat/doc-collector/src/ai/tools.ts +74 -16
  6. package/boat/doc-collector/src/cli.ts +1 -6
  7. package/boat/doc-collector/src/docbot.ts +30 -9
  8. package/boat/doc-collector/src/docs-renderer.ts +19 -24
  9. package/boat/doc-collector/src/screenshots.ts +34 -2
  10. package/boat/doc-collector/src/state-diagram.ts +281 -0
  11. package/dist/bin/explorbot-cli.js +29 -9
  12. package/dist/boat/api-tester/src/config.js +43 -4
  13. package/dist/boat/doc-collector/src/ai/documentarian.js +19 -7
  14. package/dist/boat/doc-collector/src/ai/tools.js +53 -16
  15. package/dist/boat/doc-collector/src/cli.js +1 -5
  16. package/dist/boat/doc-collector/src/docbot.js +27 -8
  17. package/dist/boat/doc-collector/src/docs-renderer.js +18 -3
  18. package/dist/boat/doc-collector/src/screenshots.js +30 -1
  19. package/dist/boat/doc-collector/src/state-diagram.js +200 -0
  20. package/dist/models.json +30 -0
  21. package/dist/package.json +23 -3
  22. package/dist/src/action-result.d.ts +8 -10
  23. package/dist/src/action-result.js +72 -164
  24. package/dist/src/action.d.ts +13 -12
  25. package/dist/src/action.js +37 -113
  26. package/dist/src/ai/agent.d.ts +17 -0
  27. package/dist/src/ai/captain/idle-mode.js +1 -1
  28. package/dist/src/ai/captain/test-mode.js +1 -1
  29. package/dist/src/ai/captain/web-mode.js +18 -29
  30. package/dist/src/ai/captain.d.ts +1 -3
  31. package/dist/src/ai/captain.js +17 -45
  32. package/dist/src/ai/conversation.d.ts +2 -5
  33. package/dist/src/ai/conversation.js +9 -28
  34. package/dist/src/ai/driller.d.ts +2 -17
  35. package/dist/src/ai/driller.js +26 -50
  36. package/dist/src/ai/experience-compactor.d.ts +2 -2
  37. package/dist/src/ai/experience-compactor.js +16 -33
  38. package/dist/src/ai/fisherman-tools.js +1 -1
  39. package/dist/src/ai/fisherman.d.ts +0 -1
  40. package/dist/src/ai/fisherman.js +0 -3
  41. package/dist/src/ai/historian/codeceptjs.d.ts +2 -0
  42. package/dist/src/ai/historian/codeceptjs.js +4 -3
  43. package/dist/src/ai/historian/playwright.d.ts +4 -1
  44. package/dist/src/ai/historian/playwright.js +4 -3
  45. package/dist/src/ai/historian/screencast.d.ts +2 -1
  46. package/dist/src/ai/historian/screencast.js +2 -2
  47. package/dist/src/ai/historian/utils.d.ts +0 -1
  48. package/dist/src/ai/historian/utils.js +0 -1
  49. package/dist/src/ai/historian.d.ts +10 -8
  50. package/dist/src/ai/historian.js +1 -5
  51. package/dist/src/ai/navigator.d.ts +8 -6
  52. package/dist/src/ai/navigator.js +34 -65
  53. package/dist/src/ai/pilot.d.ts +13 -8
  54. package/dist/src/ai/pilot.js +53 -59
  55. package/dist/src/ai/planner.d.ts +5 -4
  56. package/dist/src/ai/planner.js +66 -52
  57. package/dist/src/ai/provider.d.ts +4 -1
  58. package/dist/src/ai/provider.js +77 -137
  59. package/dist/src/ai/quartermaster.d.ts +22 -22
  60. package/dist/src/ai/quartermaster.js +8 -14
  61. package/dist/src/ai/rerunner.d.ts +3 -13
  62. package/dist/src/ai/rerunner.js +23 -44
  63. package/dist/src/ai/researcher/cache.js +11 -18
  64. package/dist/src/ai/researcher/coordinates.js +4 -3
  65. package/dist/src/ai/researcher/deep-analysis.d.ts +2 -0
  66. package/dist/src/ai/researcher/deep-analysis.js +8 -11
  67. package/dist/src/ai/researcher/locators.d.ts +9 -2
  68. package/dist/src/ai/researcher/locators.js +65 -8
  69. package/dist/src/ai/researcher/parser.js +1 -1
  70. package/dist/src/ai/researcher/research-result.d.ts +0 -1
  71. package/dist/src/ai/researcher/research-result.js +0 -19
  72. package/dist/src/ai/researcher/sections.d.ts +2 -0
  73. package/dist/src/ai/researcher/sections.js +7 -3
  74. package/dist/src/ai/researcher.js +40 -106
  75. package/dist/src/ai/rules.d.ts +1 -0
  76. package/dist/src/ai/rules.js +22 -15
  77. package/dist/src/ai/session-analyst.js +8 -5
  78. package/dist/src/ai/task-agent.d.ts +19 -6
  79. package/dist/src/ai/task-agent.js +37 -33
  80. package/dist/src/ai/tester.d.ts +6 -14
  81. package/dist/src/ai/tester.js +46 -111
  82. package/dist/src/ai/tools.d.ts +26 -14
  83. package/dist/src/ai/tools.js +138 -132
  84. package/dist/src/command-handler.js +1 -1
  85. package/dist/src/commands/compact-command.d.ts +1 -1
  86. package/dist/src/commands/compact-command.js +6 -6
  87. package/dist/src/commands/context-aria-command.js +1 -1
  88. package/dist/src/commands/context-command.js +8 -6
  89. package/dist/src/commands/context-data-command.js +2 -2
  90. package/dist/src/commands/context-experience-command.js +2 -2
  91. package/dist/src/commands/context-html-command.js +2 -2
  92. package/dist/src/commands/context-knowledge-command.js +2 -2
  93. package/dist/src/commands/drill-command.js +1 -1
  94. package/dist/src/commands/experience-command.js +1 -1
  95. package/dist/src/commands/explore-command.js +4 -3
  96. package/dist/src/commands/freesail-command.js +2 -2
  97. package/dist/src/commands/knows-command.js +1 -1
  98. package/dist/src/commands/learn-command.js +5 -3
  99. package/dist/src/commands/path-command.js +1 -1
  100. package/dist/src/commands/research-command.js +1 -1
  101. package/dist/src/commands/test-command.js +1 -1
  102. package/dist/src/components/AddKnowledge.d.ts +2 -0
  103. package/dist/src/components/AddKnowledge.js +3 -7
  104. package/dist/src/components/App.js +3 -3
  105. package/dist/src/config.d.ts +19 -0
  106. package/dist/src/config.js +156 -10
  107. package/dist/src/experience-tracker.d.ts +11 -6
  108. package/dist/src/experience-tracker.js +82 -155
  109. package/dist/src/explorbot.d.ts +21 -13
  110. package/dist/src/explorbot.js +84 -80
  111. package/dist/src/explorer.d.ts +66 -102
  112. package/dist/src/explorer.js +332 -623
  113. package/dist/src/knowledge-tracker.d.ts +2 -2
  114. package/dist/src/knowledge-tracker.js +38 -45
  115. package/dist/src/state-manager.d.ts +10 -30
  116. package/dist/src/state-manager.js +11 -129
  117. package/dist/src/utils/aria.d.ts +6 -1
  118. package/dist/src/utils/aria.js +4 -1
  119. package/dist/src/utils/cache.d.ts +15 -0
  120. package/dist/src/utils/cache.js +34 -0
  121. package/dist/src/utils/context-formatter.d.ts +6 -0
  122. package/dist/src/utils/context-formatter.js +15 -27
  123. package/dist/src/utils/hooks-runner.js +2 -4
  124. package/dist/src/utils/html.d.ts +5 -0
  125. package/dist/src/utils/html.js +71 -0
  126. package/dist/src/utils/markdown-files.d.ts +10 -0
  127. package/dist/src/utils/markdown-files.js +21 -0
  128. package/dist/src/utils/markdown-query.d.ts +6 -0
  129. package/dist/src/utils/markdown-query.js +14 -1
  130. package/dist/src/utils/page-readiness.d.ts +1 -0
  131. package/dist/src/utils/page-readiness.js +1 -1
  132. package/dist/src/utils/secrets.js +2 -2
  133. package/dist/src/utils/strings.d.ts +2 -0
  134. package/dist/src/utils/strings.js +10 -0
  135. package/dist/src/utils/test-files.js +1 -1
  136. package/dist/src/utils/web-annotate.d.ts +5 -0
  137. package/dist/src/utils/web-annotate.js +58 -0
  138. package/dist/src/utils/web-eidx.d.ts +2 -0
  139. package/dist/src/utils/web-eidx.js +20 -0
  140. package/dist/src/utils/web-element.d.ts +3 -1
  141. package/dist/src/utils/web-element.js +32 -0
  142. package/dist/src/utils/web-sandbox.d.ts +4 -0
  143. package/dist/src/utils/web-sandbox.js +37 -0
  144. package/models.json +30 -0
  145. package/package.json +23 -3
  146. package/src/action-result.ts +70 -173
  147. package/src/action.ts +38 -132
  148. package/src/ai/agent.ts +20 -0
  149. package/src/ai/captain/idle-mode.ts +1 -1
  150. package/src/ai/captain/test-mode.ts +1 -1
  151. package/src/ai/captain/web-mode.ts +18 -30
  152. package/src/ai/captain.ts +17 -42
  153. package/src/ai/conversation.ts +9 -32
  154. package/src/ai/driller.ts +53 -83
  155. package/src/ai/experience-compactor.ts +16 -35
  156. package/src/ai/fisherman-tools.ts +1 -1
  157. package/src/ai/fisherman.ts +0 -4
  158. package/src/ai/historian/codeceptjs.ts +5 -3
  159. package/src/ai/historian/playwright.ts +7 -4
  160. package/src/ai/historian/screencast.ts +4 -3
  161. package/src/ai/historian/utils.ts +0 -1
  162. package/src/ai/historian.ts +6 -8
  163. package/src/ai/navigator.ts +39 -70
  164. package/src/ai/pilot.ts +59 -61
  165. package/src/ai/planner.ts +76 -62
  166. package/src/ai/provider.ts +85 -146
  167. package/src/ai/quartermaster.ts +39 -45
  168. package/src/ai/rerunner.ts +27 -50
  169. package/src/ai/researcher/cache.ts +10 -16
  170. package/src/ai/researcher/coordinates.ts +4 -3
  171. package/src/ai/researcher/deep-analysis.ts +10 -11
  172. package/src/ai/researcher/locators.ts +66 -9
  173. package/src/ai/researcher/parser.ts +1 -1
  174. package/src/ai/researcher/research-result.ts +0 -18
  175. package/src/ai/researcher/sections.ts +9 -3
  176. package/src/ai/researcher.ts +42 -119
  177. package/src/ai/rules.ts +24 -13
  178. package/src/ai/session-analyst.ts +8 -5
  179. package/src/ai/task-agent.ts +45 -38
  180. package/src/ai/tester.ts +51 -129
  181. package/src/ai/tools.ts +145 -153
  182. package/src/command-handler.ts +1 -1
  183. package/src/commands/compact-command.ts +7 -8
  184. package/src/commands/context-aria-command.ts +1 -1
  185. package/src/commands/context-command.ts +8 -6
  186. package/src/commands/context-data-command.ts +2 -2
  187. package/src/commands/context-experience-command.ts +2 -2
  188. package/src/commands/context-html-command.ts +2 -2
  189. package/src/commands/context-knowledge-command.ts +2 -2
  190. package/src/commands/drill-command.ts +1 -1
  191. package/src/commands/experience-command.ts +1 -1
  192. package/src/commands/explore-command.ts +4 -3
  193. package/src/commands/freesail-command.ts +2 -2
  194. package/src/commands/knows-command.ts +1 -1
  195. package/src/commands/learn-command.ts +5 -3
  196. package/src/commands/path-command.ts +1 -1
  197. package/src/commands/research-command.ts +1 -1
  198. package/src/commands/test-command.ts +1 -1
  199. package/src/components/AddKnowledge.tsx +5 -7
  200. package/src/components/App.tsx +3 -3
  201. package/src/config.ts +185 -11
  202. package/src/experience-tracker.ts +89 -152
  203. package/src/explorbot.ts +88 -83
  204. package/src/explorer.ts +402 -693
  205. package/src/knowledge-tracker.ts +42 -49
  206. package/src/state-manager.ts +20 -155
  207. package/src/utils/aria.ts +11 -2
  208. package/src/utils/cache.ts +40 -0
  209. package/src/utils/context-formatter.ts +12 -15
  210. package/src/utils/hooks-runner.ts +2 -4
  211. package/src/utils/html.ts +79 -0
  212. package/src/utils/markdown-files.ts +30 -0
  213. package/src/utils/markdown-query.ts +15 -1
  214. package/src/utils/page-readiness.ts +1 -1
  215. package/src/utils/secrets.ts +2 -3
  216. package/src/utils/strings.ts +12 -0
  217. package/src/utils/test-files.ts +1 -1
  218. package/src/utils/web-annotate.ts +64 -0
  219. package/src/utils/web-eidx.ts +21 -0
  220. package/src/utils/web-element.ts +34 -0
  221. package/src/utils/web-sandbox.ts +43 -0
  222. package/dist/boat/api-tester/example/apibot.config.js +0 -30
@@ -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 rejectAfterIdle(ms, signal, controller) {
32
+ function abortAfterIdle(ms, cancel, controller) {
32
33
  return new Promise((_, reject) => {
33
34
  const tick = () => {
34
- if (signal.cancelled)
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
- if (!options.telemetry) {
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 options.telemetry;
247
+ return optionTelemetry;
199
248
  }
200
249
  return {
201
250
  ...runTelemetry,
202
- ...options.telemetry,
251
+ ...optionTelemetry,
203
252
  metadata: {
204
253
  ...runTelemetry.metadata,
205
- ...options.telemetry.metadata,
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 toolExecutions = toolCalls.map((call, index) => ({
232
- toolName: call.toolName || '',
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 telemetry = this.getTelemetry(options);
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
- if (response.usage) {
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
- const reduced = this.tryReduceMessages(messages, options._contextRetryLevel || 0);
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: _ignoredStopWhen, ...optionsWithoutStop } = options;
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 timeout = config.timeout || 30000;
332
- const cancel = { cancelled: false };
333
- const controller = new AbortController();
334
- const combinedSignal = AbortSignal.any([controller.signal, executionController.getAbortSignal()].filter(Boolean));
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
- if (response.usage) {
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
- const reduced = this.tryReduceMessages(messages, options._contextRetryLevel || 0);
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 telemetry = this.getTelemetry(options);
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
- const timeout = config.timeout || 30000;
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
- if (response.usage) {
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
- const reduced = this.tryReduceMessages(messages, options._contextRetryLevel || 0);
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 Provider.CONTEXT_LENGTH_PATTERNS.some((p) => msg.includes(p));
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
- if (response.usage) {
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 { existsSync, mkdirSync, writeFileSync } from 'node:fs';
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { z } from 'zod';
4
- import { ConfigParser } from "../config.js";
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
- playwrightHelper = null;
14
+ explorer = null;
15
15
  pendingAnalyses = [];
16
16
  constructor(provider, options) {
17
17
  this.provider = provider;
18
18
  this.model = options?.model;
19
- const configParser = ConfigParser.getInstance();
20
- this.outputDir = join(configParser.getOutputDir(), 'a11y');
19
+ this.outputDir = outputPath('a11y');
21
20
  }
22
- start(playwrightHelper, stateManager) {
23
- this.playwrightHelper = playwrightHelper;
24
- this.ensureDirectory();
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.playwrightHelper?.page;
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
- constructor(explorer: Explorer, provider: Provider, agentTools?: any);
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>;