agentxchain 2.109.0 → 2.110.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.
@@ -120,8 +120,10 @@ function renderGateActionFailure(gateActions, state) {
120
120
  html += `<div class="annotation-list">`;
121
121
  for (const action of actions) {
122
122
  const label = action.action_label || action.command || `action ${action.action_index || '?'}`;
123
- const outcome = action.status === 'failed' ? '❌ failed' : '✅ succeeded';
124
- const exitStr = action.exit_code != null ? ` (exit ${action.exit_code})` : '';
123
+ const outcome = action.status === 'failed'
124
+ ? (action.timed_out ? `⏱ timed out after ${action.timeout_ms}ms` : '❌ failed')
125
+ : '✅ succeeded';
126
+ const exitStr = action.timed_out ? '' : (action.exit_code != null ? ` (exit ${action.exit_code})` : '');
125
127
  html += `<div class="annotation-card">
126
128
  <span class="mono">${esc(String(action.action_index || '?'))}.</span>
127
129
  <span>${esc(label)}</span>
@@ -240,9 +240,12 @@ function renderGateActionsSection(gateActions) {
240
240
  html += `<ul>`;
241
241
  for (const a of attempt.actions) {
242
242
  const aLabel = a.action_label || a.command || `action ${a.action_index || '?'}`;
243
- const outcome = a.status === 'failed' ? '❌' : '✅';
244
- const exitStr = a.exit_code != null ? ` (exit ${a.exit_code})` : '';
245
- html += `<li>${outcome} ${esc(aLabel)}${esc(exitStr)}</li>`;
243
+ const outcome = a.status === 'failed'
244
+ ? (a.timed_out ? '⏱' : '')
245
+ : '✅';
246
+ const timeoutStr = a.timed_out ? ` timed out after ${a.timeout_ms}ms` : '';
247
+ const exitStr = a.timed_out ? '' : (a.exit_code != null ? ` (exit ${a.exit_code})` : '');
248
+ html += `<li>${outcome} ${esc(aLabel)}${esc(exitStr)}${esc(timeoutStr)}</li>`;
246
249
  }
247
250
  html += `</ul>`;
248
251
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.109.0",
3
+ "version": "2.110.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -44,7 +44,8 @@ export async function approveCompletionCommand(opts) {
44
44
  if (result.gate_actions?.length > 0) {
45
45
  console.log(` ${chalk.dim('Gate actions:')} ${result.gate_actions.length}`);
46
46
  for (const action of result.gate_actions) {
47
- console.log(` ${action.index}. ${action.label || action.run}`);
47
+ const timeoutHint = action.timeout_ms && action.timeout_ms !== 900_000 ? chalk.dim(` [timeout: ${action.timeout_ms}ms]`) : '';
48
+ console.log(` ${action.index}. ${action.label || action.run}${timeoutHint}`);
48
49
  if (action.label) {
49
50
  console.log(` ${chalk.dim(action.run)}`);
50
51
  }
@@ -103,6 +104,10 @@ function printGateHookFailure(result, gateType, gateInfo) {
103
104
 
104
105
  function printGateActionFailure(result, gateInfo) {
105
106
  const failure = result.gateActionRun?.failed_action;
107
+ const exitLabel = failure?.timed_out
108
+ ? `timeout after ${failure.timeout_ms}ms`
109
+ : failure?.exit_code ?? failure?.signal ?? 'unknown';
110
+ const stderrOrError = failure?.stderr_tail || failure?.spawn_error || null;
106
111
 
107
112
  console.log('');
108
113
  console.log(chalk.yellow(' Run Completion Blocked By Gate Action'));
@@ -110,9 +115,9 @@ function printGateActionFailure(result, gateInfo) {
110
115
  console.log('');
111
116
  console.log(` ${chalk.dim('Gate:')} ${gateInfo.gate}`);
112
117
  console.log(` ${chalk.dim('Action:')} ${failure?.action_label || failure?.command || '(unknown)'}`);
113
- console.log(` ${chalk.dim('Exit:')} ${failure?.exit_code ?? failure?.signal ?? 'unknown'}`);
114
- if (failure?.stderr_tail) {
115
- console.log(` ${chalk.dim('stderr:')} ${failure.stderr_tail}`);
118
+ console.log(` ${chalk.dim('Exit:')} ${exitLabel}`);
119
+ if (stderrOrError) {
120
+ console.log(` ${chalk.dim('stderr:')} ${stderrOrError}`);
116
121
  }
117
122
  console.log(` ${chalk.dim('Retry:')} agentxchain approve-completion`);
118
123
  console.log('');
@@ -43,7 +43,8 @@ export async function approveTransitionCommand(opts) {
43
43
  if (result.gate_actions?.length > 0) {
44
44
  console.log(` ${chalk.dim('Gate actions:')} ${result.gate_actions.length}`);
45
45
  for (const action of result.gate_actions) {
46
- console.log(` ${action.index}. ${action.label || action.run}`);
46
+ const timeoutHint = action.timeout_ms && action.timeout_ms !== 900_000 ? chalk.dim(` [timeout: ${action.timeout_ms}ms]`) : '';
47
+ console.log(` ${action.index}. ${action.label || action.run}${timeoutHint}`);
47
48
  if (action.label) {
48
49
  console.log(` ${chalk.dim(action.run)}`);
49
50
  }
@@ -108,6 +109,10 @@ function printGateHookFailure(result, gateType, gateInfo) {
108
109
 
109
110
  function printGateActionFailure(result, gateType, gateInfo) {
110
111
  const failure = result.gateActionRun?.failed_action;
112
+ const exitLabel = failure?.timed_out
113
+ ? `timeout after ${failure.timeout_ms}ms`
114
+ : failure?.exit_code ?? failure?.signal ?? 'unknown';
115
+ const stderrOrError = failure?.stderr_tail || failure?.spawn_error || null;
111
116
 
112
117
  console.log('');
113
118
  console.log(chalk.yellow(` ${gateType === 'phase_transition' ? 'Phase Transition' : 'Run Completion'} Blocked By Gate Action`));
@@ -119,9 +124,9 @@ function printGateActionFailure(result, gateType, gateInfo) {
119
124
  }
120
125
  console.log(` ${chalk.dim('Gate:')} ${gateInfo.gate}`);
121
126
  console.log(` ${chalk.dim('Action:')} ${failure?.action_label || failure?.command || '(unknown)'}`);
122
- console.log(` ${chalk.dim('Exit:')} ${failure?.exit_code ?? failure?.signal ?? 'unknown'}`);
123
- if (failure?.stderr_tail) {
124
- console.log(` ${chalk.dim('stderr:')} ${failure.stderr_tail}`);
127
+ console.log(` ${chalk.dim('Exit:')} ${exitLabel}`);
128
+ if (stderrOrError) {
129
+ console.log(` ${chalk.dim('stderr:')} ${stderrOrError}`);
125
130
  }
126
131
  console.log(` ${chalk.dim('Retry:')} ${gateType === 'phase_transition' ? 'agentxchain approve-transition' : 'agentxchain approve-completion'}`);
127
132
  console.log('');
@@ -358,9 +358,9 @@ function renderGovernedStatus(context, opts) {
358
358
  for (const action of gateActionAttempt.actions) {
359
359
  const label = action.action_label || action.command || `action ${action.action_index || '?'}`;
360
360
  const outcome = action.status === 'failed'
361
- ? chalk.red('failed')
361
+ ? (action.timed_out ? chalk.red(`timed out after ${action.timeout_ms}ms`) : chalk.red('failed'))
362
362
  : chalk.green('succeeded');
363
- const exit = action.exit_code == null ? '' : ` (exit ${action.exit_code})`;
363
+ const exit = action.timed_out ? '' : (action.exit_code == null ? '' : ` (exit ${action.exit_code})`);
364
364
  console.log(` ${action.action_index || '?'}. ${label} — ${outcome}${exit}`);
365
365
  if (action.status === 'failed' && action.stderr_tail) {
366
366
  console.log(` ${chalk.dim(action.stderr_tail)}`);
@@ -5,6 +5,16 @@ import { spawnSync } from 'child_process';
5
5
 
6
6
  const LEDGER_PATH = '.agentxchain/decision-ledger.jsonl';
7
7
  const MAX_OUTPUT_TAIL_CHARS = 1200;
8
+ export const DEFAULT_GATE_ACTION_TIMEOUT_MS = 15 * 60 * 1000;
9
+ const MIN_GATE_ACTION_TIMEOUT_MS = 1000;
10
+ const MAX_GATE_ACTION_TIMEOUT_MS = 60 * 60 * 1000;
11
+
12
+ function normalizeGateActionTimeoutMs(action) {
13
+ if (Number.isInteger(action?.timeout_ms)) {
14
+ return action.timeout_ms;
15
+ }
16
+ return DEFAULT_GATE_ACTION_TIMEOUT_MS;
17
+ }
8
18
 
9
19
  export function validateGateActionsConfig(gates, errors) {
10
20
  if (!gates || typeof gates !== 'object' || Array.isArray(gates)) {
@@ -44,6 +54,15 @@ export function validateGateActionsConfig(gates, errors) {
44
54
  if ('label' in action && (typeof action.label !== 'string' || !action.label.trim())) {
45
55
  errors.push(`${prefix}.label must be a non-empty string when provided`);
46
56
  }
57
+ if ('timeout_ms' in action) {
58
+ if (!Number.isInteger(action.timeout_ms)
59
+ || action.timeout_ms < MIN_GATE_ACTION_TIMEOUT_MS
60
+ || action.timeout_ms > MAX_GATE_ACTION_TIMEOUT_MS) {
61
+ errors.push(
62
+ `${prefix}.timeout_ms must be an integer between ${MIN_GATE_ACTION_TIMEOUT_MS} and ${MAX_GATE_ACTION_TIMEOUT_MS} when provided`,
63
+ );
64
+ }
65
+ }
47
66
  }
48
67
  }
49
68
  }
@@ -60,6 +79,7 @@ export function getGateActions(config, gateId) {
60
79
  index: index + 1,
61
80
  label: typeof action.label === 'string' && action.label.trim() ? action.label.trim() : null,
62
81
  run: action.run.trim(),
82
+ timeout_ms: normalizeGateActionTimeoutMs(action),
63
83
  }));
64
84
  }
65
85
 
@@ -78,6 +98,11 @@ function trimOutputTail(value) {
78
98
  }
79
99
 
80
100
  function buildGateActionEntry(action, meta, runtimeResult, status) {
101
+ const timedOut = runtimeResult?.error?.code === 'ETIMEDOUT';
102
+ const stderrTail = trimOutputTail(runtimeResult?.stderr) || (timedOut ? `Timed out after ${action.timeout_ms}ms` : null);
103
+ const spawnError = timedOut
104
+ ? `Timed out after ${action.timeout_ms}ms`
105
+ : runtimeResult?.error?.message || null;
81
106
  return {
82
107
  type: 'gate_action',
83
108
  timestamp: new Date().toISOString(),
@@ -90,12 +115,14 @@ function buildGateActionEntry(action, meta, runtimeResult, status) {
90
115
  action_index: action.index,
91
116
  action_label: action.label,
92
117
  command: action.run,
118
+ timeout_ms: action.timeout_ms,
93
119
  status,
94
120
  exit_code: Number.isInteger(runtimeResult?.status) ? runtimeResult.status : null,
95
121
  signal: runtimeResult?.signal || null,
96
122
  stdout_tail: trimOutputTail(runtimeResult?.stdout),
97
- stderr_tail: trimOutputTail(runtimeResult?.stderr),
98
- spawn_error: runtimeResult?.error?.message || null,
123
+ stderr_tail: stderrTail,
124
+ spawn_error: spawnError,
125
+ timed_out: timedOut,
99
126
  };
100
127
  }
101
128
 
@@ -125,6 +152,8 @@ export function executeGateActions(root, config, meta, opts = {}) {
125
152
  },
126
153
  encoding: 'utf8',
127
154
  maxBuffer: 10 * 1024 * 1024,
155
+ timeout: action.timeout_ms,
156
+ killSignal: 'SIGTERM',
128
157
  });
129
158
 
130
159
  const status = runtimeResult.error || runtimeResult.status !== 0 ? 'failed' : 'succeeded';
@@ -170,6 +199,8 @@ export function normalizeGateActionEntry(entry) {
170
199
  stdout_tail: entry.stdout_tail || null,
171
200
  stderr_tail: entry.stderr_tail || null,
172
201
  spawn_error: entry.spawn_error || null,
202
+ timeout_ms: Number.isInteger(entry.timeout_ms) ? entry.timeout_ms : null,
203
+ timed_out: entry.timed_out === true,
173
204
  timestamp: entry.timestamp || null,
174
205
  };
175
206
  }
@@ -1281,6 +1281,8 @@ function blockRunForGateActionFailure(root, state, gateFailure, config) {
1281
1281
  : 'agentxchain approve-transition';
1282
1282
  const actionLabel = gateFailure.action_label || gateFailure.command || gateFailure.gate_id || 'gate action';
1283
1283
  const blockedAt = gateFailure.timestamp || new Date().toISOString();
1284
+ const failureVerb = gateFailure.timed_out ? 'timed out' : 'failed';
1285
+ const failureDetail = `Gate action ${failureVerb} for "${gateFailure.gate_id || 'unknown'}": ${actionLabel}`;
1284
1286
  const blockedState = {
1285
1287
  ...state,
1286
1288
  status: 'blocked',
@@ -1289,13 +1291,13 @@ function blockRunForGateActionFailure(root, state, gateFailure, config) {
1289
1291
  category: 'gate_action_failed',
1290
1292
  blocked_at: blockedAt,
1291
1293
  turn_id: gateFailure.requested_by_turn || null,
1292
- detail: `Gate action failed for "${gateFailure.gate_id || 'unknown'}": ${actionLabel}`,
1294
+ detail: failureDetail,
1293
1295
  recovery: {
1294
1296
  typed_reason: 'gate_action_failed',
1295
1297
  owner: 'human',
1296
1298
  recovery_action: recoveryAction,
1297
1299
  turn_retained: false,
1298
- detail: `${gateFailure.gate_id || 'unknown'} action ${gateFailure.action_index || '?'} (${actionLabel})`,
1300
+ detail: `${gateFailure.gate_id || 'unknown'} action ${gateFailure.action_index || '?'} (${actionLabel})${gateFailure.timed_out ? ` timed out after ${gateFailure.timeout_ms}ms` : ''}`,
1299
1301
  },
1300
1302
  gate_action: {
1301
1303
  attempt_id: gateFailure.attempt_id || null,
@@ -1306,6 +1308,8 @@ function blockRunForGateActionFailure(root, state, gateFailure, config) {
1306
1308
  command: gateFailure.command || null,
1307
1309
  exit_code: gateFailure.exit_code ?? null,
1308
1310
  stderr_tail: gateFailure.stderr_tail || null,
1311
+ timeout_ms: gateFailure.timeout_ms ?? null,
1312
+ timed_out: gateFailure.timed_out === true,
1309
1313
  },
1310
1314
  },
1311
1315
  };
package/src/lib/report.js CHANGED
@@ -1444,7 +1444,8 @@ export function formatGovernanceReportText(report) {
1444
1444
  for (const action of run.gate_actions) {
1445
1445
  const label = action.action_label || action.command || `action ${action.action_index || '?'}`;
1446
1446
  const exit = action.exit_code == null ? 'n/a' : String(action.exit_code);
1447
- lines.push(` - ${action.gate_id || 'unknown'} | ${action.gate_type || 'unknown'} | action ${action.action_index || '?'} | ${action.status} | ${label} | exit: ${exit} | at: ${action.timestamp || 'n/a'}`);
1447
+ const timeoutTag = action.timed_out ? ` | timed_out after ${action.timeout_ms}ms` : '';
1448
+ lines.push(` - ${action.gate_id || 'unknown'} | ${action.gate_type || 'unknown'} | action ${action.action_index || '?'} | ${action.status} | ${label} | exit: ${exit}${timeoutTag} | at: ${action.timestamp || 'n/a'}`);
1448
1449
  if (action.stderr_tail) {
1449
1450
  lines.push(` stderr: ${action.stderr_tail}`);
1450
1451
  }
@@ -2014,7 +2015,8 @@ export function formatGovernanceReportMarkdown(report) {
2014
2015
  for (const action of run.gate_actions) {
2015
2016
  const label = action.action_label || action.command || `action ${action.action_index || '?'}`;
2016
2017
  const exit = action.exit_code == null ? 'n/a' : String(action.exit_code);
2017
- lines.push(`- \`${action.gate_id || 'unknown'}\` (${action.gate_type || 'unknown'}) action ${action.action_index || '?'} — **${action.status}** at \`${action.timestamp || 'n/a'}\`: ${label} (exit \`${exit}\`)`);
2018
+ const mdTimeout = action.timed_out ? ` timed out after ${action.timeout_ms}ms` : '';
2019
+ lines.push(`- \`${action.gate_id || 'unknown'}\` (${action.gate_type || 'unknown'}) action ${action.action_index || '?'} — **${action.status}** at \`${action.timestamp || 'n/a'}\`: ${label} (exit \`${exit}\`)${mdTimeout}`);
2018
2020
  if (action.stderr_tail) {
2019
2021
  lines.push(` - stderr: ${action.stderr_tail}`);
2020
2022
  }
@@ -2723,7 +2725,8 @@ function renderRunHtml(report) {
2723
2725
  for (const action of run.gate_actions) {
2724
2726
  const label = action.action_label || action.command || `action ${action.action_index || '?'}`;
2725
2727
  const exit = action.exit_code == null ? 'n/a' : String(action.exit_code);
2726
- gaHtml += `<li><code>${esc(action.gate_id || 'unknown')}</code> (${esc(action.gate_type || 'unknown')}) action ${esc(String(action.action_index || '?'))} — <strong>${esc(action.status)}</strong> at <code>${esc(action.timestamp || 'n/a')}</code>: ${esc(label)} (exit <code>${esc(exit)}</code>)`;
2728
+ const htmlTimeout = action.timed_out ? ` <em>⏱ timed out after ${esc(String(action.timeout_ms))}ms</em>` : '';
2729
+ gaHtml += `<li><code>${esc(action.gate_id || 'unknown')}</code> (${esc(action.gate_type || 'unknown')}) action ${esc(String(action.action_index || '?'))} — <strong>${esc(action.status)}</strong> at <code>${esc(action.timestamp || 'n/a')}</code>: ${esc(label)} (exit <code>${esc(exit)}</code>)${htmlTimeout}`;
2727
2730
  if (action.stderr_tail) {
2728
2731
  gaHtml += `<br><code>${esc(action.stderr_tail)}</code>`;
2729
2732
  }