agentxchain 2.66.1 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.66.1",
3
+ "version": "2.67.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- console.log(` ${marker} ${turn.turn_id} ${chalk.bold(turn.assigned_role)} (${statusLabel}) [attempt ${turn.attempt}]`);
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 || [];
@@ -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
+ }
@@ -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.accepted_at || 'n/a'}`);
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.accepted_at || 'n/a'} |`);
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.accepted_at || 'n/a'} |`);
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) {