feat(protocol): add step-level timing (startedAtMs / completedAtMs) (#489)

BREAKING CHANGE: StepRecord now requires startedAtMs and completedAtMs fields.
StepEntry now requires durationMs field. Old CAS data without these fields is invalid.

- Add startedAtMs/completedAtMs to StepRecord and StepNodePayload
- Add durationMs to StepEntry (computed: completedAtMs - startedAtMs)
- Update STEP_NODE_SCHEMA to require timing fields as integers
- Record Date.now() before/after agent execution in createAgent
- Show duration in thread read headers (formatStepHeader)
- Update existing test fixtures with timing fields
This commit is contained in:
2026-05-25 08:01:50 +00:00
parent 3fca67e443
commit 45f479e60f
10 changed files with 465 additions and 5 deletions
+12 -1
View File
@@ -566,14 +566,25 @@ function selectByQuota(
return { selected, skippedCount: candidates.length - selected.length };
}
function formatDuration(ms: number): string {
if (ms < 1000) return `${ms}ms`;
const seconds = ms / 1000;
if (seconds < 60) return `${seconds.toFixed(1)}s`;
const minutes = Math.floor(seconds / 60);
const remainingSec = Math.round(seconds % 60);
return `${minutes}m${remainingSec}s`;
}
function formatStepHeader(stepNum: number, item: OrderedStepItem): string {
const ts = new Date(item.timestamp)
.toISOString()
.replace("T", " ")
.replace(/\.\d+Z$/, "");
const durationMs = item.payload.completedAtMs - item.payload.startedAtMs;
const duration = formatDuration(durationMs);
return [
`## Step ${stepNum}: ${item.payload.role} \`${item.hash}\``,
`**Agent:** ${item.payload.agent} | **Time:** ${ts}`,
`**Agent:** ${item.payload.agent} | **Time:** ${ts} | **Duration:** ${duration}`,
].join("\n");
}