explorbot 0.1.13 → 0.1.15

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/src/stats.ts CHANGED
@@ -4,6 +4,7 @@ interface TokenUsage {
4
4
  input: number;
5
5
  output: number;
6
6
  total: number;
7
+ cached?: number;
7
8
  }
8
9
 
9
10
  export type ExplorbotMode = 'explore' | 'test' | 'freesail' | 'tui';
@@ -20,11 +21,12 @@ export class Stats {
20
21
 
21
22
  static recordTokens(_agent: string, model: string, usage: TokenUsage): void {
22
23
  if (!Stats.models[model]) {
23
- Stats.models[model] = { input: 0, output: 0, total: 0 };
24
+ Stats.models[model] = { input: 0, output: 0, total: 0, cached: 0 };
24
25
  }
25
26
  Stats.models[model].input += usage.input;
26
27
  Stats.models[model].output += usage.output;
27
28
  Stats.models[model].total += usage.total;
29
+ Stats.models[model].cached = (Stats.models[model].cached ?? 0) + (usage.cached ?? 0);
28
30
  }
29
31
 
30
32
  static getElapsedTime(): string {
package/src/test-plan.ts CHANGED
@@ -26,6 +26,7 @@ export interface Note {
26
26
  startTime: number;
27
27
  endTime: number;
28
28
  screenshot?: string;
29
+ log?: string;
29
30
  }
30
31
 
31
32
  export class ActiveNote {
@@ -34,6 +35,7 @@ export class ActiveNote {
34
35
  message: string;
35
36
  status?: TestResultType;
36
37
  screenshot?: string;
38
+ log?: string;
37
39
 
38
40
  constructor(task: Task, message: string, status?: TestResultType) {
39
41
  this.task = task;
@@ -73,6 +75,7 @@ export class Task {
73
75
  steps: Record<string, StepData>;
74
76
  states: WebPageState[];
75
77
  startUrl: string;
78
+ verification?: Verification;
76
79
  protected timestampCounter = 0;
77
80
  private activeNote?: ActiveNote;
78
81
 
@@ -102,6 +105,7 @@ export class Task {
102
105
  startTime: activeNote.getStartTime(),
103
106
  endTime,
104
107
  screenshot: activeNote.screenshot,
108
+ log: activeNote.log,
105
109
  };
106
110
  this.activeNote = undefined;
107
111
  }
@@ -118,13 +122,28 @@ export class Task {
118
122
  .join('\n');
119
123
  }
120
124
 
121
- addNote(message: string, status: TestResultType = null, screenshot?: string): void {
122
- const isDuplicate = Object.values(this.notes).some((note) => note.message === message && note.status === status);
125
+ addNote(message: string, status: TestResultType = null, screenshot?: string, log?: string): void {
126
+ const isDuplicate = Object.values(this.notes).some((note) => note.message === message && note.status === status && note.log === log);
123
127
  if (isDuplicate) return;
124
128
 
125
129
  const now = performance.now();
126
130
  const timestamp = `${now}_${this.timestampCounter++}`;
127
- this.notes[timestamp] = { message, status, startTime: now, endTime: now, screenshot };
131
+ this.notes[timestamp] = { message, status, startTime: now, endTime: now, screenshot, log };
132
+ }
133
+
134
+ addUrlNote(state: UrlNoteState, prevState?: { title?: string; h1?: string; h2?: string }): void {
135
+ const fullUrl = state.fullUrl || state.url;
136
+ if (!fullUrl) return;
137
+
138
+ let label: string | undefined;
139
+ if (state.title && state.title !== prevState?.title) label = state.title;
140
+ else if (state.h1 && state.h1 !== prevState?.h1) label = state.h1;
141
+ else if (state.h2 && state.h2 !== prevState?.h2) label = state.h2;
142
+ else label = state.title || state.h1 || state.h2;
143
+
144
+ if (!label) return;
145
+
146
+ this.addNote(`Navigated to ${label}`, TestResult.PASSED, state.screenshotFile, fullUrl);
128
147
  }
129
148
 
130
149
  addState(state: WebPageState): void {
@@ -136,6 +155,28 @@ export class Task {
136
155
  this.steps[timestamp] = { text, duration, status, error, log, artifacts, noteStartTime: this.activeNote?.getStartTime() };
137
156
  }
138
157
 
158
+ setActiveNoteScreenshot(screenshotFile?: string): void {
159
+ if (!this.activeNote || !screenshotFile) return;
160
+ this.activeNote.screenshot = screenshotFile;
161
+ }
162
+
163
+ setVerification(message: string, status: TestResultType, state?: UrlNoteState): void {
164
+ this.verification ||= { message: '', status: null, details: [] };
165
+ this.verification.message = message;
166
+ this.verification.status = status;
167
+ if (!state) return;
168
+ if (state.screenshotFile) this.verification.screenshot = state.screenshotFile;
169
+ const fullUrl = state.fullUrl || state.url;
170
+ if (fullUrl) this.verification.url = fullUrl;
171
+ this.verification.pageLabel = state.title || state.h1 || state.h2 || undefined;
172
+ }
173
+
174
+ addVerificationDetail(detail: string): void {
175
+ if (!detail) return;
176
+ this.verification ||= { message: '', status: null, details: [] };
177
+ this.verification.details.push(detail);
178
+ }
179
+
139
180
  getLog(): Array<{ type: 'step' | 'note' | 'artifact'; content: string; timestamp: number }> {
140
181
  const merged: Record<string, { type: 'step' | 'note' | 'artifact'; content: string }> = {};
141
182
 
@@ -442,3 +483,21 @@ export class Plan {
442
483
  return planToAiContext(this, options);
443
484
  }
444
485
  }
486
+
487
+ interface Verification {
488
+ message: string;
489
+ status: TestResultType;
490
+ screenshot?: string;
491
+ url?: string;
492
+ pageLabel?: string;
493
+ details: string[];
494
+ }
495
+
496
+ interface UrlNoteState {
497
+ url?: string;
498
+ fullUrl?: string;
499
+ title?: string;
500
+ h1?: string;
501
+ h2?: string;
502
+ screenshotFile?: string;
503
+ }