Skip to content

Commit d74591a

Browse files
author
Sentience Dev
committed
Merge pull request #87 from SentienceAPI/more_fix_trace
align trace and indexing file data format
2 parents 4487553 + 01f7eed commit d74591a

File tree

13 files changed

+690
-54
lines changed

13 files changed

+690
-54
lines changed

src/agent.ts

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { click, typeText, press } from './actions';
99
import { Snapshot, Element, ActionResult } from './types';
1010
import { LLMProvider, LLMResponse } from './llm-provider';
1111
import { Tracer } from './tracing/tracer';
12-
import { randomUUID } from 'crypto';
12+
import { randomUUID, createHash } from 'crypto';
1313

1414
/**
1515
* Execution result from agent.act()
@@ -124,6 +124,28 @@ export class SentienceAgent {
124124

125125
}
126126

127+
/**
128+
* Compute SHA256 hash of text
129+
*/
130+
private computeHash(text: string): string {
131+
return createHash('sha256').update(text, 'utf8').digest('hex');
132+
}
133+
134+
/**
135+
* Get bounding box for an element from snapshot
136+
*/
137+
private getElementBbox(elementId: number | undefined, snap: Snapshot): { x: number; y: number; width: number; height: number } | undefined {
138+
if (elementId === undefined) return undefined;
139+
const el = snap.elements.find(e => e.id === elementId);
140+
if (!el) return undefined;
141+
return {
142+
x: el.bbox.x,
143+
y: el.bbox.y,
144+
width: el.bbox.width,
145+
height: el.bbox.height,
146+
};
147+
}
148+
127149
/**
128150
* Execute a high-level goal using observe → think → act loop
129151
* @param goal - Natural language instruction (e.g., "Click the Sign In button")
@@ -288,6 +310,103 @@ export class SentienceAgent {
288310
console.log(`${status} Completed in ${durationMs}ms`);
289311
}
290312

313+
// Emit step_end event if tracer is enabled
314+
if (this.tracer) {
315+
const preUrl = snap.url;
316+
const postUrl = this.browser.getPage()?.url() || null;
317+
318+
// Compute snapshot digest (simplified - use URL + timestamp)
319+
const snapshotDigest = `sha256:${this.computeHash(`${preUrl}${snap.timestamp}`)}`;
320+
321+
// Build LLM data
322+
const llmResponseText = llmResponse.content;
323+
const llmResponseHash = `sha256:${this.computeHash(llmResponseText)}`;
324+
const llmData = {
325+
response_text: llmResponseText,
326+
response_hash: llmResponseHash,
327+
usage: {
328+
prompt_tokens: llmResponse.promptTokens || 0,
329+
completion_tokens: llmResponse.completionTokens || 0,
330+
total_tokens: llmResponse.totalTokens || 0,
331+
},
332+
};
333+
334+
// Build exec data
335+
const execData: any = {
336+
success: result.success,
337+
action: result.action || 'unknown',
338+
outcome: result.outcome || (result.success ? `Action ${result.action || 'unknown'} executed successfully` : `Action ${result.action || 'unknown'} failed`),
339+
duration_ms: durationMs,
340+
};
341+
342+
// Add optional exec fields
343+
if (result.elementId !== undefined) {
344+
execData.element_id = result.elementId;
345+
// Add bounding box if element found
346+
const bbox = this.getElementBbox(result.elementId, snap);
347+
if (bbox) {
348+
execData.bounding_box = bbox;
349+
}
350+
}
351+
if (result.text !== undefined) {
352+
execData.text = result.text;
353+
}
354+
if (result.key !== undefined) {
355+
execData.key = result.key;
356+
}
357+
if (result.error !== undefined) {
358+
execData.error = result.error;
359+
}
360+
361+
// Build verify data (simplified - based on success and url_changed)
362+
const verifyPassed = result.success && (result.urlChanged || result.action !== 'click');
363+
const verifySignals: any = {
364+
url_changed: result.urlChanged || false,
365+
};
366+
if (result.error) {
367+
verifySignals.error = result.error;
368+
}
369+
370+
// Add elements_found array if element was targeted
371+
if (result.elementId !== undefined) {
372+
const bbox = this.getElementBbox(result.elementId, snap);
373+
if (bbox) {
374+
verifySignals.elements_found = [
375+
{
376+
label: `Element ${result.elementId}`,
377+
bounding_box: bbox,
378+
},
379+
];
380+
}
381+
}
382+
383+
const verifyData = {
384+
passed: verifyPassed,
385+
signals: verifySignals,
386+
};
387+
388+
// Build complete step_end event
389+
const stepEndData = {
390+
v: 1,
391+
step_id: stepId,
392+
step_index: this.stepCount,
393+
goal: goal,
394+
attempt: attempt,
395+
pre: {
396+
url: preUrl,
397+
snapshot_digest: snapshotDigest,
398+
},
399+
llm: llmData,
400+
exec: execData,
401+
post: {
402+
url: postUrl,
403+
},
404+
verify: verifyData,
405+
};
406+
407+
this.tracer.emit('step_end', stepEndData, stepId);
408+
}
409+
291410
return result;
292411

293412
} catch (error: any) {

src/tracing/index-schema.ts

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ export class TraceFileInfo {
66
constructor(
77
public path: string,
88
public size_bytes: number,
9-
public sha256: string
9+
public sha256: string,
10+
public line_count: number | null = null // Number of lines in the trace file
1011
) {}
1112

1213
toJSON() {
1314
return {
1415
path: this.path,
1516
size_bytes: this.size_bytes,
1617
sha256: this.sha256,
18+
line_count: this.line_count,
1719
};
1820
}
1921
}
@@ -25,7 +27,11 @@ export class TraceSummary {
2527
public event_count: number,
2628
public step_count: number,
2729
public error_count: number,
28-
public final_url: string | null
30+
public final_url: string | null,
31+
public status: 'success' | 'failure' | 'partial' | 'unknown' | null = null,
32+
public agent_name: string | null = null, // Agent name from run_start event
33+
public duration_ms: number | null = null, // Calculated duration in milliseconds
34+
public counters: { snapshot_count: number; action_count: number; error_count: number } | null = null // Aggregated counters
2935
) {}
3036

3137
toJSON() {
@@ -36,6 +42,10 @@ export class TraceSummary {
3642
step_count: this.step_count,
3743
error_count: this.error_count,
3844
final_url: this.final_url,
45+
status: this.status,
46+
agent_name: this.agent_name,
47+
duration_ms: this.duration_ms,
48+
counters: this.counters,
3949
};
4050
}
4151
}
@@ -92,7 +102,7 @@ export class StepCounters {
92102
}
93103
}
94104

95-
export type StepStatus = 'ok' | 'error' | 'partial';
105+
export type StepStatus = 'success' | 'failure' | 'partial' | 'unknown';
96106

97107
export class StepIndex {
98108
constructor(
@@ -104,6 +114,7 @@ export class StepIndex {
104114
public ts_end: string,
105115
public offset_start: number,
106116
public offset_end: number,
117+
public line_number: number | null = null, // Line number for byte-range fetching
107118
public url_before: string | null,
108119
public url_after: string | null,
109120
public snapshot_before: SnapshotInfo,
@@ -122,6 +133,7 @@ export class StepIndex {
122133
ts_end: this.ts_end,
123134
offset_start: this.offset_start,
124135
offset_end: this.offset_end,
136+
line_number: this.line_number,
125137
url_before: this.url_before,
126138
url_after: this.url_after,
127139
snapshot_before: this.snapshot_before.toJSON(),
@@ -152,4 +164,73 @@ export class TraceIndex {
152164
steps: this.steps.map((s) => s.toJSON()),
153165
};
154166
}
167+
168+
/**
169+
* Convert to SS format.
170+
*
171+
* Maps SDK field names to frontend expectations:
172+
* - created_at -> generated_at
173+
* - first_ts -> start_time
174+
* - last_ts -> end_time
175+
* - step_index -> step (already 1-based, good!)
176+
* - ts_start -> timestamp
177+
* - Filters out "unknown" status
178+
*/
179+
toSentienceStudioJSON(): any {
180+
// Calculate duration if not already set
181+
let durationMs = this.summary.duration_ms;
182+
if (durationMs === null && this.summary.first_ts && this.summary.last_ts) {
183+
const start = new Date(this.summary.first_ts);
184+
const end = new Date(this.summary.last_ts);
185+
durationMs = end.getTime() - start.getTime();
186+
}
187+
188+
// Aggregate counters if not already set
189+
let counters = this.summary.counters;
190+
if (counters === null) {
191+
const snapshotCount = this.steps.reduce((sum, s) => sum + s.counters.snapshots, 0);
192+
const actionCount = this.steps.reduce((sum, s) => sum + s.counters.actions, 0);
193+
counters = {
194+
snapshot_count: snapshotCount,
195+
action_count: actionCount,
196+
error_count: this.summary.error_count,
197+
};
198+
}
199+
200+
return {
201+
version: this.version,
202+
run_id: this.run_id,
203+
generated_at: this.created_at, // Renamed from created_at
204+
trace_file: {
205+
path: this.trace_file.path,
206+
size_bytes: this.trace_file.size_bytes,
207+
line_count: this.trace_file.line_count, // Added
208+
},
209+
summary: {
210+
agent_name: this.summary.agent_name, // Added
211+
total_steps: this.summary.step_count, // Renamed from step_count
212+
status: this.summary.status !== 'unknown' ? this.summary.status : null, // Filter out unknown
213+
start_time: this.summary.first_ts, // Renamed from first_ts
214+
end_time: this.summary.last_ts, // Renamed from last_ts
215+
duration_ms: durationMs, // Added
216+
counters: counters, // Added
217+
},
218+
steps: this.steps.map((s) => ({
219+
step: s.step_index, // Already 1-based ✅
220+
byte_offset: s.offset_start,
221+
line_number: s.line_number, // Added
222+
timestamp: s.ts_start, // Use start time
223+
action: {
224+
type: s.action.type || '',
225+
goal: s.goal, // Move goal into action
226+
digest: s.action.args_digest,
227+
},
228+
snapshot: s.snapshot_after.url ? {
229+
url: s.snapshot_after.url,
230+
digest: s.snapshot_after.digest,
231+
} : undefined,
232+
status: s.status !== 'unknown' ? s.status : undefined, // Filter out unknown
233+
})),
234+
};
235+
}
155236
}

0 commit comments

Comments
 (0)