agentxchain 2.65.0 → 2.67.0
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/package.json +1 -1
- package/src/commands/accept-turn.js +4 -1
- package/src/commands/status.js +20 -1
- package/src/commands/step.js +4 -1
- package/src/commands/turn.js +29 -0
- package/src/lib/context-compressor.js +3 -0
- package/src/lib/context-section-parser.js +6 -0
- package/src/lib/dispatch-bundle.js +70 -0
- package/src/lib/governed-state.js +8 -0
- package/src/lib/report.js +24 -3
- package/src/lib/verification-replay.js +3 -0
package/package.json
CHANGED
|
@@ -167,7 +167,10 @@ export async function acceptTurnCommand(opts = {}) {
|
|
|
167
167
|
console.log(` ${chalk.dim('Cost:')} $${formatUsd(accepted.cost.usd)}`);
|
|
168
168
|
}
|
|
169
169
|
if (accepted?.verification_replay) {
|
|
170
|
-
|
|
170
|
+
const verifiedAt = accepted.verification_replay.verified_at
|
|
171
|
+
? ` at ${accepted.verification_replay.verified_at}`
|
|
172
|
+
: '';
|
|
173
|
+
console.log(` ${chalk.dim('Replay:')} ${accepted.verification_replay.overall} (${accepted.verification_replay.matched_commands}/${accepted.verification_replay.replayed_commands})${verifiedAt}`);
|
|
171
174
|
}
|
|
172
175
|
if (result.budget_warning) {
|
|
173
176
|
console.log(` ${chalk.yellow('Budget warning:')} ${result.budget_warning}`);
|
package/src/commands/status.js
CHANGED
|
@@ -141,7 +141,16 @@ function renderGovernedStatus(context, opts) {
|
|
|
141
141
|
const statusLabel = turn.status === 'conflicted'
|
|
142
142
|
? chalk.red('conflicted')
|
|
143
143
|
: turn.status;
|
|
144
|
-
|
|
144
|
+
let elapsedTag = '';
|
|
145
|
+
if (turn.started_at) {
|
|
146
|
+
const elMs = Date.now() - new Date(turn.started_at).getTime();
|
|
147
|
+
if (elMs >= 0) {
|
|
148
|
+
const s = Math.floor(elMs / 1000);
|
|
149
|
+
const m = Math.floor(s / 60);
|
|
150
|
+
elapsedTag = m > 0 ? ` — ${m}m ${s % 60}s` : ` — ${s}s`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
console.log(` ${marker} ${turn.turn_id} — ${chalk.bold(turn.assigned_role)} (${statusLabel}) [attempt ${turn.attempt}]${elapsedTag}`);
|
|
145
154
|
if (turn.status === 'conflicted' && turn.conflict_state) {
|
|
146
155
|
const cs = turn.conflict_state;
|
|
147
156
|
const files = cs.conflict_error?.conflicting_files || [];
|
|
@@ -161,6 +170,16 @@ function renderGovernedStatus(context, opts) {
|
|
|
161
170
|
console.log(` ${chalk.dim('Role:')} ${chalk.bold(singleActiveTurn.assigned_role)} (${singleActiveTurn.status})`);
|
|
162
171
|
console.log(` ${chalk.dim('Runtime:')} ${singleActiveTurn.runtime_id}`);
|
|
163
172
|
console.log(` ${chalk.dim('Attempt:')} ${singleActiveTurn.attempt}`);
|
|
173
|
+
if (singleActiveTurn.started_at) {
|
|
174
|
+
const elapsedMs = Date.now() - new Date(singleActiveTurn.started_at).getTime();
|
|
175
|
+
if (elapsedMs >= 0) {
|
|
176
|
+
const secs = Math.floor(elapsedMs / 1000);
|
|
177
|
+
const mins = Math.floor(secs / 60);
|
|
178
|
+
const remainSecs = secs % 60;
|
|
179
|
+
const elapsed = mins > 0 ? `${mins}m ${remainSecs}s` : `${remainSecs}s`;
|
|
180
|
+
console.log(` ${chalk.dim('Elapsed:')} ${elapsed}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
164
183
|
if (singleActiveTurn.status === 'conflicted' && singleActiveTurn.conflict_state) {
|
|
165
184
|
const cs = singleActiveTurn.conflict_state;
|
|
166
185
|
const files = cs.conflict_error?.conflicting_files || [];
|
package/src/commands/step.js
CHANGED
|
@@ -1024,7 +1024,10 @@ function printAcceptSummary(result) {
|
|
|
1024
1024
|
console.log(` ${chalk.dim('Cost:')} $${(accepted.cost.usd || 0).toFixed(2)}`);
|
|
1025
1025
|
}
|
|
1026
1026
|
if (accepted?.verification_replay) {
|
|
1027
|
-
|
|
1027
|
+
const verifiedAt = accepted.verification_replay.verified_at
|
|
1028
|
+
? ` at ${accepted.verification_replay.verified_at}`
|
|
1029
|
+
: '';
|
|
1030
|
+
console.log(` ${chalk.dim('Replay:')} ${accepted.verification_replay.overall} (${accepted.verification_replay.matched_commands}/${accepted.verification_replay.replayed_commands})${verifiedAt}`);
|
|
1028
1031
|
}
|
|
1029
1032
|
console.log('');
|
|
1030
1033
|
|
package/src/commands/turn.js
CHANGED
|
@@ -119,6 +119,7 @@ function buildArtifactIndex(root, turnId) {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
function buildTurnPayload(turnId, turn, state, artifacts, assignment) {
|
|
122
|
+
const elapsedMs = getElapsedMs(turn.started_at);
|
|
122
123
|
return {
|
|
123
124
|
turn_id: turnId,
|
|
124
125
|
run_id: state.run_id || assignment?.run_id || null,
|
|
@@ -127,6 +128,8 @@ function buildTurnPayload(turnId, turn, state, artifacts, assignment) {
|
|
|
127
128
|
runtime: turn.runtime_id,
|
|
128
129
|
status: turn.status,
|
|
129
130
|
attempt: turn.attempt,
|
|
131
|
+
started_at: turn.started_at || null,
|
|
132
|
+
elapsed_ms: elapsedMs,
|
|
130
133
|
dispatch_dir: getDispatchTurnDir(turnId),
|
|
131
134
|
staging_result_path: assignment?.staging_result_path || null,
|
|
132
135
|
active_turn_count: getActiveTurnCount(state),
|
|
@@ -140,6 +143,7 @@ function buildTurnPayload(turnId, turn, state, artifacts, assignment) {
|
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
function printTurnSummary(turnId, turn, state, artifacts, assignment) {
|
|
146
|
+
const elapsedMs = getElapsedMs(turn.started_at);
|
|
143
147
|
console.log('');
|
|
144
148
|
console.log(chalk.bold(` Turn: ${chalk.cyan(turnId)}`));
|
|
145
149
|
console.log(chalk.dim(' ' + '─'.repeat(44)));
|
|
@@ -149,6 +153,12 @@ function printTurnSummary(turnId, turn, state, artifacts, assignment) {
|
|
|
149
153
|
console.log(` ${chalk.dim('Runtime:')} ${turn.runtime_id}`);
|
|
150
154
|
console.log(` ${chalk.dim('Status:')} ${turn.status}`);
|
|
151
155
|
console.log(` ${chalk.dim('Attempt:')} ${turn.attempt}`);
|
|
156
|
+
if (turn.started_at) {
|
|
157
|
+
console.log(` ${chalk.dim('Started:')} ${turn.started_at}`);
|
|
158
|
+
}
|
|
159
|
+
if (elapsedMs != null) {
|
|
160
|
+
console.log(` ${chalk.dim('Elapsed:')} ${formatElapsed(elapsedMs)}`);
|
|
161
|
+
}
|
|
152
162
|
console.log(` ${chalk.dim('Dispatch:')} ${getDispatchTurnDir(turnId)}`);
|
|
153
163
|
if (assignment?.staging_result_path) {
|
|
154
164
|
console.log(` ${chalk.dim('Staging:')} ${assignment.staging_result_path}`);
|
|
@@ -208,3 +218,22 @@ function readJsonArtifact(absPath) {
|
|
|
208
218
|
return null;
|
|
209
219
|
}
|
|
210
220
|
}
|
|
221
|
+
|
|
222
|
+
function getElapsedMs(startedAt) {
|
|
223
|
+
if (typeof startedAt !== 'string') {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
const started = Date.parse(startedAt);
|
|
227
|
+
if (!Number.isFinite(started)) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
const elapsed = Date.now() - started;
|
|
231
|
+
return elapsed >= 0 ? elapsed : null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function formatElapsed(ms) {
|
|
235
|
+
const totalSeconds = Math.floor(ms / 1000);
|
|
236
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
237
|
+
const seconds = totalSeconds % 60;
|
|
238
|
+
return minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
|
239
|
+
}
|
|
@@ -15,6 +15,9 @@ import { renderContextSections } from './context-section-parser.js';
|
|
|
15
15
|
const COMPRESSION_STEPS = [
|
|
16
16
|
{ id: 'budget', action: 'drop' },
|
|
17
17
|
{ id: 'phase_gate_status', action: 'drop' },
|
|
18
|
+
{ id: 'decision_history', action: 'drop' },
|
|
19
|
+
{ id: 'workflow_artifacts', action: 'drop' },
|
|
20
|
+
{ id: 'last_turn_verification', action: 'drop' },
|
|
18
21
|
{ id: 'gate_required_files', action: 'drop' },
|
|
19
22
|
{ id: 'last_turn_objections', action: 'drop' },
|
|
20
23
|
{ id: 'last_turn_decisions', action: 'drop' },
|
|
@@ -3,11 +3,14 @@ const CONTEXT_TITLE = '# Execution Context';
|
|
|
3
3
|
const SECTION_DEFINITIONS = [
|
|
4
4
|
{ id: 'current_state', header: 'Current State', required: true },
|
|
5
5
|
{ id: 'budget', header: null, required: false },
|
|
6
|
+
{ id: 'project_goal', header: 'Project Goal', required: true },
|
|
7
|
+
{ id: 'inherited_run_context', header: 'Inherited Run Context', required: true },
|
|
6
8
|
{ id: 'last_turn_header', header: 'Last Accepted Turn', required: true },
|
|
7
9
|
{ id: 'last_turn_summary', header: null, required: false },
|
|
8
10
|
{ id: 'last_turn_decisions', header: null, required: false },
|
|
9
11
|
{ id: 'last_turn_objections', header: null, required: false },
|
|
10
12
|
{ id: 'last_turn_verification', header: null, required: false },
|
|
13
|
+
{ id: 'decision_history', header: 'Decision History', required: false },
|
|
11
14
|
{ id: 'blockers', header: 'Blockers', required: true },
|
|
12
15
|
{ id: 'escalation', header: 'Escalation', required: true },
|
|
13
16
|
{ id: 'workflow_artifacts', header: 'Workflow Artifacts', required: false },
|
|
@@ -79,6 +82,8 @@ export function renderContextSections(sections) {
|
|
|
79
82
|
sectionMap.get('budget')?.content,
|
|
80
83
|
]);
|
|
81
84
|
|
|
85
|
+
appendTopLevelSection(lines, 'Project Goal', [sectionMap.get('project_goal')?.content]);
|
|
86
|
+
appendTopLevelSection(lines, 'Inherited Run Context', [sectionMap.get('inherited_run_context')?.content]);
|
|
82
87
|
appendTopLevelSection(lines, 'Last Accepted Turn', [
|
|
83
88
|
sectionMap.get('last_turn_header')?.content,
|
|
84
89
|
sectionMap.get('last_turn_summary')?.content,
|
|
@@ -87,6 +92,7 @@ export function renderContextSections(sections) {
|
|
|
87
92
|
sectionMap.get('last_turn_verification')?.content,
|
|
88
93
|
]);
|
|
89
94
|
|
|
95
|
+
appendTopLevelSection(lines, 'Decision History', [sectionMap.get('decision_history')?.content]);
|
|
90
96
|
appendTopLevelSection(lines, 'Blockers', [sectionMap.get('blockers')?.content]);
|
|
91
97
|
appendTopLevelSection(lines, 'Escalation', [sectionMap.get('escalation')?.content]);
|
|
92
98
|
appendTopLevelSection(lines, 'Workflow Artifacts', [sectionMap.get('workflow_artifacts')?.content]);
|
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
} from './turn-paths.js';
|
|
31
31
|
|
|
32
32
|
const HISTORY_PATH = '.agentxchain/history.jsonl';
|
|
33
|
+
const LEDGER_PATH = '.agentxchain/decision-ledger.jsonl';
|
|
34
|
+
const DECISION_HISTORY_MAX_ENTRIES = 50;
|
|
33
35
|
const FILE_PREVIEW_MAX_FILES = 5;
|
|
34
36
|
const FILE_PREVIEW_MAX_LINES = 120;
|
|
35
37
|
const PROPOSAL_SUMMARY_MAX_LINES = 80;
|
|
@@ -691,6 +693,12 @@ function renderContext(state, config, root, turn, role) {
|
|
|
691
693
|
}
|
|
692
694
|
}
|
|
693
695
|
|
|
696
|
+
// Cumulative decision history from the decision ledger
|
|
697
|
+
const decisionHistoryLines = renderDecisionHistory(root, warnings);
|
|
698
|
+
if (decisionHistoryLines.length > 0) {
|
|
699
|
+
lines.push(...decisionHistoryLines);
|
|
700
|
+
}
|
|
701
|
+
|
|
694
702
|
// Blockers / escalation
|
|
695
703
|
if (state.blocked_on) {
|
|
696
704
|
lines.push('## Blockers');
|
|
@@ -1102,6 +1110,68 @@ function writeDispatchIndex(root, state, warningsByTurn = {}) {
|
|
|
1102
1110
|
);
|
|
1103
1111
|
}
|
|
1104
1112
|
|
|
1113
|
+
/**
|
|
1114
|
+
* Read agent-authored decisions from the decision ledger and render as a
|
|
1115
|
+
* markdown section for CONTEXT.md.
|
|
1116
|
+
*
|
|
1117
|
+
* Returns an array of markdown lines (including the ## header) or an empty
|
|
1118
|
+
* array when there are no agent-authored decisions.
|
|
1119
|
+
*/
|
|
1120
|
+
function renderDecisionHistory(root, warnings = []) {
|
|
1121
|
+
const ledgerPath = join(root, LEDGER_PATH);
|
|
1122
|
+
if (!existsSync(ledgerPath)) return [];
|
|
1123
|
+
|
|
1124
|
+
let content;
|
|
1125
|
+
try {
|
|
1126
|
+
content = readFileSync(ledgerPath, 'utf8').trim();
|
|
1127
|
+
} catch (err) {
|
|
1128
|
+
warnings.push(`Failed to read ${LEDGER_PATH}: ${err.message}`);
|
|
1129
|
+
return [];
|
|
1130
|
+
}
|
|
1131
|
+
if (!content) return [];
|
|
1132
|
+
|
|
1133
|
+
// Parse all lines, skip malformed ones
|
|
1134
|
+
const rawLines = content.split('\n');
|
|
1135
|
+
const agentDecisions = [];
|
|
1136
|
+
for (const line of rawLines) {
|
|
1137
|
+
if (!line.trim()) continue;
|
|
1138
|
+
try {
|
|
1139
|
+
const entry = JSON.parse(line);
|
|
1140
|
+
// Agent-authored decisions have an `id` field; system entries do not
|
|
1141
|
+
if (entry.id) {
|
|
1142
|
+
agentDecisions.push(entry);
|
|
1143
|
+
}
|
|
1144
|
+
} catch {
|
|
1145
|
+
warnings.push(`Skipped malformed decision-ledger line`);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
if (agentDecisions.length === 0) return [];
|
|
1150
|
+
|
|
1151
|
+
const totalCount = agentDecisions.length;
|
|
1152
|
+
const displayed = totalCount > DECISION_HISTORY_MAX_ENTRIES
|
|
1153
|
+
? agentDecisions.slice(totalCount - DECISION_HISTORY_MAX_ENTRIES)
|
|
1154
|
+
: agentDecisions;
|
|
1155
|
+
|
|
1156
|
+
const lines = [];
|
|
1157
|
+
lines.push('## Decision History');
|
|
1158
|
+
lines.push('');
|
|
1159
|
+
lines.push('| ID | Phase | Role | Statement |');
|
|
1160
|
+
lines.push('|----|-------|------|-----------|');
|
|
1161
|
+
for (const d of displayed) {
|
|
1162
|
+
// Escape pipes in statement to avoid breaking the table
|
|
1163
|
+
const stmt = (d.statement || '').replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
1164
|
+
lines.push(`| ${d.id} | ${d.phase || ''} | ${d.role || ''} | ${stmt} |`);
|
|
1165
|
+
}
|
|
1166
|
+
if (totalCount > DECISION_HISTORY_MAX_ENTRIES) {
|
|
1167
|
+
lines.push('');
|
|
1168
|
+
lines.push(`_Showing ${DECISION_HISTORY_MAX_ENTRIES} of ${totalCount} decisions. Full ledger at ${LEDGER_PATH}._`);
|
|
1169
|
+
}
|
|
1170
|
+
lines.push('');
|
|
1171
|
+
|
|
1172
|
+
return lines;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1105
1175
|
function readLastHistoryEntry(root, warnings = []) {
|
|
1106
1176
|
const historyPath = join(root, HISTORY_PATH);
|
|
1107
1177
|
if (!existsSync(historyPath)) return null;
|
|
@@ -2590,7 +2590,9 @@ function _acceptGovernedTurnLocked(root, config, opts) {
|
|
|
2590
2590
|
accepted_sequence: acceptedSequence,
|
|
2591
2591
|
concurrent_with: Array.isArray(currentTurn.concurrent_with) ? currentTurn.concurrent_with : [],
|
|
2592
2592
|
cost: turnResult.cost || {},
|
|
2593
|
+
...(currentTurn.started_at ? { started_at: currentTurn.started_at } : {}),
|
|
2593
2594
|
accepted_at: now,
|
|
2595
|
+
...(currentTurn.started_at ? { duration_ms: Math.max(0, new Date(now).getTime() - new Date(currentTurn.started_at).getTime()) } : {}),
|
|
2594
2596
|
};
|
|
2595
2597
|
const nextHistoryEntries = [...historyEntries, historyEntry];
|
|
2596
2598
|
// Build ledger entries for the journal
|
|
@@ -3081,11 +3083,17 @@ function _acceptGovernedTurnLocked(root, config, opts) {
|
|
|
3081
3083
|
}
|
|
3082
3084
|
|
|
3083
3085
|
// Emit turn_accepted event to local log.
|
|
3086
|
+
const turnAcceptedPayload = {};
|
|
3087
|
+
if (currentTurn.started_at) {
|
|
3088
|
+
turnAcceptedPayload.started_at = currentTurn.started_at;
|
|
3089
|
+
turnAcceptedPayload.duration_ms = Math.max(0, new Date(now).getTime() - new Date(currentTurn.started_at).getTime());
|
|
3090
|
+
}
|
|
3084
3091
|
emitRunEvent(root, 'turn_accepted', {
|
|
3085
3092
|
run_id: updatedState.run_id,
|
|
3086
3093
|
phase: updatedState.phase,
|
|
3087
3094
|
status: updatedState.status,
|
|
3088
3095
|
turn: { turn_id: currentTurn.turn_id, role_id: currentTurn.assigned_role },
|
|
3096
|
+
payload: turnAcceptedPayload,
|
|
3089
3097
|
});
|
|
3090
3098
|
|
|
3091
3099
|
if (updatedState.status === 'blocked') {
|
package/src/lib/report.js
CHANGED
|
@@ -66,6 +66,25 @@ function formatUsd(value) {
|
|
|
66
66
|
return typeof value === 'number' ? `$${value.toFixed(2)}` : 'n/a';
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function formatDurationCompact(ms) {
|
|
70
|
+
if (typeof ms !== 'number' || !Number.isFinite(ms) || ms < 0) return null;
|
|
71
|
+
if (ms < 1000) return `${ms}ms`;
|
|
72
|
+
const secs = Math.floor(ms / 1000);
|
|
73
|
+
if (secs < 60) return `${secs}s`;
|
|
74
|
+
const mins = Math.floor(secs / 60);
|
|
75
|
+
const remainSecs = secs % 60;
|
|
76
|
+
if (mins < 60) return `${mins}m ${remainSecs}s`;
|
|
77
|
+
const hrs = Math.floor(mins / 60);
|
|
78
|
+
const remainMins = mins % 60;
|
|
79
|
+
return `${hrs}h ${remainMins}m`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function formatTurnTimelineTime(turn) {
|
|
83
|
+
const acceptedAt = turn.accepted_at || 'n/a';
|
|
84
|
+
const duration = formatDurationCompact(turn.duration_ms);
|
|
85
|
+
return duration ? `${acceptedAt} (${duration})` : acceptedAt;
|
|
86
|
+
}
|
|
87
|
+
|
|
69
88
|
function formatStatusCounts(statusCounts) {
|
|
70
89
|
const entries = Object.entries(statusCounts || {}).sort(([left], [right]) => left.localeCompare(right, 'en'));
|
|
71
90
|
if (entries.length === 0) return 'none';
|
|
@@ -99,6 +118,8 @@ function extractHistoryTimeline(artifact) {
|
|
|
99
118
|
decisions: Array.isArray(e.decisions) ? e.decisions.map((d) => d?.id || d).filter(Boolean) : [],
|
|
100
119
|
objections: Array.isArray(e.objections) ? e.objections.map((o) => o?.id || o).filter(Boolean) : [],
|
|
101
120
|
cost_usd: typeof e.cost?.total_usd === 'number' ? e.cost.total_usd : null,
|
|
121
|
+
started_at: e.started_at || null,
|
|
122
|
+
duration_ms: typeof e.duration_ms === 'number' ? e.duration_ms : null,
|
|
102
123
|
accepted_at: e.accepted_at || null,
|
|
103
124
|
}));
|
|
104
125
|
}
|
|
@@ -943,7 +964,7 @@ export function formatGovernanceReportText(report) {
|
|
|
943
964
|
const cost = t.cost_usd != null ? formatUsd(t.cost_usd) : 'n/a';
|
|
944
965
|
const phase = t.phase_transition ? `${t.phase || '?'} -> ${t.phase_transition}` : (t.phase || '?');
|
|
945
966
|
const siblingNote = Array.isArray(t.sibling_attributed_files) ? ` (${t.sibling_attributed_files.length} sibling-attributed)` : '';
|
|
946
|
-
lines.push(` ${i + 1}. [${t.role}] ${t.summary || '(no summary)'} | phase: ${phase} | files: ${t.files_changed_count}${siblingNote} | cost: ${cost} | ${t
|
|
967
|
+
lines.push(` ${i + 1}. [${t.role}] ${t.summary || '(no summary)'} | phase: ${phase} | files: ${t.files_changed_count}${siblingNote} | cost: ${cost} | ${formatTurnTimelineTime(t)}`);
|
|
947
968
|
}
|
|
948
969
|
}
|
|
949
970
|
|
|
@@ -1353,7 +1374,7 @@ export function formatGovernanceReportMarkdown(report) {
|
|
|
1353
1374
|
const phase = t.phase_transition ? `${t.phase || '?'} → ${t.phase_transition}` : (t.phase || '?');
|
|
1354
1375
|
const summary = (t.summary || '(no summary)').replace(/\|/g, '\\|');
|
|
1355
1376
|
const siblingNote = Array.isArray(t.sibling_attributed_files) ? ` (${t.sibling_attributed_files.length} sibling)` : '';
|
|
1356
|
-
lines.push(`| ${i + 1} | ${t.role} | ${phase} | ${summary} | ${t.files_changed_count}${siblingNote} | ${cost} | ${t.
|
|
1377
|
+
lines.push(`| ${i + 1} | ${t.role} | ${phase} | ${summary} | ${t.files_changed_count}${siblingNote} | ${cost} | ${formatTurnTimelineTime(t).replace(/\|/g, '\\|')} |`);
|
|
1357
1378
|
}
|
|
1358
1379
|
}
|
|
1359
1380
|
|
|
@@ -1611,7 +1632,7 @@ export function formatGovernanceReportMarkdown(report) {
|
|
|
1611
1632
|
const phase = t.phase_transition ? `${t.phase || '?'} → ${t.phase_transition}` : (t.phase || '?');
|
|
1612
1633
|
const summary = (t.summary || '(no summary)').replace(/\|/g, '\\|');
|
|
1613
1634
|
const siblingNote = Array.isArray(t.sibling_attributed_files) ? ` (${t.sibling_attributed_files.length} sibling)` : '';
|
|
1614
|
-
repoLines.push(`| ${i + 1} | ${t.role} | ${phase} | ${summary} | ${t.files_changed_count}${siblingNote} | ${cost} | ${t.
|
|
1635
|
+
repoLines.push(`| ${i + 1} | ${t.role} | ${phase} | ${summary} | ${t.files_changed_count}${siblingNote} | ${cost} | ${formatTurnTimelineTime(t).replace(/\|/g, '\\|')} |`);
|
|
1615
1636
|
}
|
|
1616
1637
|
}
|
|
1617
1638
|
if (repo.decisions && repo.decisions.length > 0) {
|
|
@@ -3,11 +3,13 @@ import { spawnSync } from 'node:child_process';
|
|
|
3
3
|
export const DEFAULT_VERIFICATION_REPLAY_TIMEOUT_MS = 30_000;
|
|
4
4
|
|
|
5
5
|
export function replayVerificationMachineEvidence({ root, verification, timeoutMs = DEFAULT_VERIFICATION_REPLAY_TIMEOUT_MS }) {
|
|
6
|
+
const verifiedAt = new Date().toISOString();
|
|
6
7
|
const machineEvidence = Array.isArray(verification?.machine_evidence)
|
|
7
8
|
? verification.machine_evidence
|
|
8
9
|
: [];
|
|
9
10
|
|
|
10
11
|
const payload = {
|
|
12
|
+
verified_at: verifiedAt,
|
|
11
13
|
timeout_ms: timeoutMs,
|
|
12
14
|
overall: 'not_reproducible',
|
|
13
15
|
replayed_commands: 0,
|
|
@@ -59,6 +61,7 @@ export function summarizeVerificationReplay(payload) {
|
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
return {
|
|
64
|
+
verified_at: payload.verified_at || null,
|
|
62
65
|
overall: payload.overall,
|
|
63
66
|
replayed_commands: payload.replayed_commands || 0,
|
|
64
67
|
matched_commands: payload.matched_commands || 0,
|