Skip to content

Commit 576fd4f

Browse files
DavertMikclaude
andcommitted
feat(trace): TraceReader API + ariaDiff in run_code
Move artifact-on-disk reading from mcp-server.js into a TraceReader class in lib/utils/trace.js. Python-style indexing via first / last / nth, kept generic across kinds (aria / html / screenshot / console / storage). Sort by filename — aiTrace's zero-padded step prefix means a lexical sort is chronological. run_code uses it to diff ARIA between the last aiTrace capture and the new one produced by the steps inside this call: const reader = new TraceReader(currentAiTraceDir) const before = reader.last('aria') // run code, aiTrace captures per step const after = reader.last('aria') if (before !== after) result.ariaDiff = ariaDiff(before, after) initCodecept now force-enables aiTrace whenever the MCP server initializes the container — it's the canonical per-step capture, no point in MCP doing its own grabAriaSnapshot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 13650e1 commit 576fd4f

4 files changed

Lines changed: 550 additions & 1 deletion

File tree

bin/mcp-server.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
snapshotDirFor,
1313
artifactsToFileUrls,
1414
writeTraceMarkdown,
15+
TraceReader,
16+
ariaDiff,
1517
} from '../lib/utils/trace.js'
1618
import event from '../lib/event.js'
1719
import recorder from '../lib/recorder.js'
@@ -36,6 +38,14 @@ let browserStarted = false
3638
let shellSessionActive = false
3739
let bootstrapDone = false
3840
let currentPluginsSig = ''
41+
let currentAiTraceDir = null // mirrors the dir aiTrace plugin computes per test/session
42+
43+
event.dispatcher.on(event.test.before, test => {
44+
try {
45+
const title = (test && (test.fullTitle ? test.fullTitle() : test.title)) || 'MCP Session'
46+
currentAiTraceDir = traceDirFor(test?.file, title, outputBaseDir())
47+
} catch {}
48+
})
3949

4050
const SESSION_REQUIRED_ERROR = 'No active CodeceptJS session. Call `start_browser` to open a shell session, or `run_test` (use `pause()` in the test, or set `pauseAt`) to inspect during a test run.'
4151

@@ -430,7 +440,10 @@ async function initCodecept(configPath, pluginOverrides) {
430440
const { getConfig } = await import('../lib/command/utils.js')
431441
const config = await getConfig(configPath)
432442

433-
applyPluginOverrides(config, plugins)
443+
// aiTrace is the canonical per-step ARIA/HTML/screenshot capture for MCP.
444+
// Always on so run_code / continue can read the latest snapshot from disk
445+
// instead of double-capturing through grabAriaSnapshot etc.
446+
applyPluginOverrides(config, { aiTrace: {}, ...plugins })
434447

435448
codecept = new Codecept(config, {})
436449
await codecept.init(testRoot)
@@ -691,6 +704,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
691704
mkdirp.sync(traceDir)
692705
const startedAt = Date.now()
693706

707+
// Pin the latest aiTrace ARIA file before running the code, so we
708+
// can diff after. aiTrace owns per-step capture; we just read it.
709+
const reader = new TraceReader(currentAiTraceDir)
710+
const ariaBefore = reader.last('aria')
711+
694712
const MAX_LOG_ENTRIES = 100
695713
const MAX_LOG_MSG_BYTES = 2000
696714
const MAX_RETURN_BYTES = 20000
@@ -753,6 +771,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
753771
}
754772
}
755773

774+
// Diff against the latest aiTrace ARIA file produced by the steps
775+
// that just ran inside this run_code call.
776+
const ariaAfter = reader.last('aria')
777+
if (ariaBefore && ariaAfter && ariaBefore !== ariaAfter) {
778+
const diff = ariaDiff(ariaBefore, ariaAfter)
779+
if (diff) result.ariaDiff = diff
780+
}
781+
756782
const traceFile = writeTraceMarkdown({
757783
dir: traceDir,
758784
title: 'run_code',

0 commit comments

Comments
 (0)