diff --git a/package.json b/package.json index 5f6726ff..c506ebe1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sentienceapi", - "version": "0.91.0", + "version": "0.91.1", "description": "TypeScript SDK for Sentience AI Agent Browser Automation", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/agent.ts b/src/agent.ts index 5a8f8eed..8c7b7bfa 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -214,15 +214,26 @@ export class SentienceAgent { // Emit snapshot event if (this.tracer) { + // Include ALL elements with full data for DOM tree display + // Use snap.elements (all elements) not filteredSnap.elements const snapshotData: any = { - url: filteredSnap.url, - element_count: filteredSnap.elements.length, - timestamp: filteredSnap.timestamp, - elements: filteredSnap.elements.slice(0, 50).map(el => ({ + url: snap.url, + element_count: snap.elements.length, + timestamp: snap.timestamp, + elements: snap.elements.map(el => ({ id: el.id, - bbox: el.bbox, role: el.role, - text: el.text?.substring(0, 50), + text: el.text, + importance: el.importance, + bbox: el.bbox, + visual_cues: el.visual_cues, + in_viewport: el.in_viewport, + is_occluded: el.is_occluded, + z_index: el.z_index, + rerank_index: el.rerank_index, + heuristic_index: el.heuristic_index, + ml_probability: el.ml_probability, + ml_score: el.ml_score, })) }; diff --git a/src/tracing/cloud-sink.ts b/src/tracing/cloud-sink.ts index 7dbbfdde..780fe72e 100644 --- a/src/tracing/cloud-sink.ts +++ b/src/tracing/cloud-sink.ts @@ -572,7 +572,10 @@ export class CloudTraceSink extends TraceSink { private generateIndex(): void { try { const { writeTraceIndex } = require('./indexer'); - writeTraceIndex(this.tempFilePath); + // Use frontend format to ensure 'step' field is present (1-based) + // Frontend derives sequence from step.step - 1, so step must be valid + const indexPath = this.tempFilePath.replace('.jsonl', '.index.json'); + writeTraceIndex(this.tempFilePath, indexPath, true); } catch (error: any) { // Non-fatal: log but don't crash this.logger?.warn(`Failed to generate trace index: ${error.message}`); @@ -609,9 +612,30 @@ export class CloudTraceSink extends TraceSink { return; } - // Read and compress index file - const indexData = await fsPromises.readFile(indexPath); - const compressedIndex = zlib.gzipSync(indexData); + // Read index file and update trace_file.path to cloud storage path + const indexContent = await fsPromises.readFile(indexPath, 'utf-8'); + const indexJson = JSON.parse(indexContent); + + // Extract cloud storage path from trace upload URL + // uploadUrl format: https://...digitaloceanspaces.com/traces/{run_id}.jsonl.gz + // Extract path: traces/{run_id}.jsonl.gz + try { + const parsedUrl = new URL(this.uploadUrl); + // Extract path after domain (e.g., /traces/run-123.jsonl.gz -> traces/run-123.jsonl.gz) + const cloudTracePath = parsedUrl.pathname.startsWith('/') + ? parsedUrl.pathname.substring(1) + : parsedUrl.pathname; + // Update trace_file.path in index + if (indexJson.trace_file && typeof indexJson.trace_file === 'object') { + indexJson.trace_file.path = cloudTracePath; + } + } catch (error: any) { + this.logger?.warn(`Failed to extract cloud path from upload URL: ${error.message}`); + } + + // Serialize updated index to JSON + const updatedIndexData = Buffer.from(JSON.stringify(indexJson, null, 2), 'utf-8'); + const compressedIndex = zlib.gzipSync(updatedIndexData); const indexSize = compressedIndex.length; this.indexFileSizeBytes = indexSize; // Track index file size diff --git a/src/tracing/indexer.ts b/src/tracing/indexer.ts index 77c9fef5..a9e0ab04 100644 --- a/src/tracing/indexer.ts +++ b/src/tracing/indexer.ts @@ -57,14 +57,25 @@ function computeSnapshotDigest(snapshotData: any): string { const elements = snapshotData.elements || []; // Canonicalize elements - const canonicalElements = elements.map((elem: any) => ({ - id: elem.id, - role: elem.role || '', - text_norm: normalizeText(elem.text), - bbox: roundBBox(elem.bbox || { x: 0, y: 0, width: 0, height: 0 }), - is_primary: elem.is_primary || false, - is_clickable: elem.is_clickable || false, - })); + const canonicalElements = elements.map((elem: any) => { + // Extract is_primary and is_clickable from visual_cues if present + const visualCues = elem.visual_cues || {}; + const isPrimary = (typeof visualCues === 'object' && visualCues !== null) + ? (visualCues.is_primary || false) + : (elem.is_primary || false); + const isClickable = (typeof visualCues === 'object' && visualCues !== null) + ? (visualCues.is_clickable || false) + : (elem.is_clickable || false); + + return { + id: elem.id, + role: elem.role || '', + text_norm: normalizeText(elem.text), + bbox: roundBBox(elem.bbox || { x: 0, y: 0, width: 0, height: 0 }), + is_primary: isPrimary, + is_clickable: isClickable, + }; + }); // Sort by element id for determinism canonicalElements.sort((a: { id?: number }, b: { id?: number }) => (a.id || 0) - (b.id || 0)); diff --git a/src/tracing/jsonl-sink.ts b/src/tracing/jsonl-sink.ts index 942b76f9..9a4d0aa0 100644 --- a/src/tracing/jsonl-sink.ts +++ b/src/tracing/jsonl-sink.ts @@ -162,7 +162,10 @@ export class JsonlTraceSink extends TraceSink { private generateIndex(): void { try { const { writeTraceIndex } = require('./indexer'); - writeTraceIndex(this.path); + // Use frontend format to ensure 'step' field is present (1-based) + // Frontend derives sequence from step.step - 1, so step must be valid + const indexPath = this.path.replace(/\.jsonl$/, '.index.json'); + writeTraceIndex(this.path, indexPath, true); } catch (error: any) { // Non-fatal: log but don't crash console.log(`⚠️ Failed to generate trace index: ${error.message}`);