agentxchain 2.155.29 → 2.155.30

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.
@@ -708,6 +708,7 @@ program
708
708
  .option('--checkpoint', 'Checkpoint the accepted turn to git immediately after acceptance')
709
709
  .option('--resolution <mode>', 'Conflict resolution mode for conflicted turns (standard, human_merge)', 'standard')
710
710
  .option('--normalize-artifact-type <type>', 'Normalize an empty workspace artifact to a safe artifact type before acceptance (currently: review)')
711
+ .option('--normalize-staged-result', 'Run known-safe staged-result normalization before acceptance')
711
712
  .action(acceptTurnCommand);
712
713
 
713
714
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.155.29",
3
+ "version": "2.155.30",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,6 +23,7 @@ export async function acceptTurnCommand(opts = {}) {
23
23
  turnId: opts.turn,
24
24
  resolutionMode: opts.resolution || 'standard',
25
25
  normalizeArtifactType: opts.normalizeArtifactType || null,
26
+ normalizeStagedResult: Boolean(opts.normalizeStagedResult),
26
27
  });
27
28
  if (!result.ok) {
28
29
  if (result.error_code === 'policy_escalation' || result.error_code === 'policy_violation') {
@@ -137,6 +138,10 @@ export async function acceptTurnCommand(opts = {}) {
137
138
  console.log(` ${chalk.dim('Reason:')} ${errorClass}`);
138
139
  console.log(` ${chalk.dim('Owner:')} human`);
139
140
  console.log(` ${chalk.dim('Action:')} Fix staged result and rerun agentxchain accept-turn, or reject with agentxchain reject-turn --reason "..."`);
141
+ if (result.validation?.errors?.some((err) => /objections\[\d+\]\.statement/.test(err))) {
142
+ const retainedTurnId = opts.turn || result.state?.current_turn?.turn_id || '(turn_id)';
143
+ console.log(` ${chalk.dim('Recovery:')} agentxchain accept-turn --turn ${retainedTurnId} --normalize-staged-result`);
144
+ }
140
145
  console.log(` ${chalk.dim('Turn:')} retained`);
141
146
  if (result.validation?.errors?.length) {
142
147
  for (const err of result.validation.errors) {
@@ -546,6 +546,7 @@ You are the **${role.title}** on this project.
546
546
  3. Write your structured turn result to the turn-scoped staging path printed by the orchestrator (\`.agentxchain/staging/<turn_id>/turn-result.json\`).
547
547
  4. If you make zero repo file edits, set \`artifact.type: "review"\` and \`files_changed: []\`.
548
548
  5. Only set \`artifact.type: "workspace"\` when you actually modified repo files and listed every changed path in \`files_changed\`.
549
+ 6. Every objection must include a non-empty \`statement\`; do not use \`summary\` or \`detail\` as a substitute.
549
550
 
550
551
  ## File Access
551
552
 
@@ -502,6 +502,7 @@ function renderPrompt(role, roleId, turn, state, config, root) {
502
502
  lines.push('- `artifact.type`: one of `workspace`, `patch`, `commit`, `review`');
503
503
  lines.push('- If you make zero repo file edits, set `artifact.type` to `"review"` and `files_changed` to `[]`.');
504
504
  lines.push('- Only set `artifact.type` to `"workspace"` when you actually modified repo files and listed every changed path in `files_changed`.');
505
+ lines.push('- Every `objections[]` item must include a non-empty `statement`; do not use `summary` or `detail` as a substitute.');
505
506
  lines.push('- `proposed_next_role`: must be in allowed_next_roles for current phase, or `human`');
506
507
  if (role.write_authority === 'review_only') {
507
508
  lines.push('- `objections`: **must be non-empty** (challenge requirement for review_only roles)');
@@ -4315,7 +4315,10 @@ function _acceptGovernedTurnLocked(root, config, opts) {
4315
4315
  }
4316
4316
 
4317
4317
  if (!validation.ok) {
4318
- const failError = `Validation failed at stage ${validation.stage}: ${validation.errors.join('; ')}`;
4318
+ const recoveryHint = validation.errors.some((error) => /objections\[\d+\]\.statement/.test(error))
4319
+ ? ` Recovery: agentxchain accept-turn --turn ${currentTurn.turn_id} --normalize-staged-result`
4320
+ : '';
4321
+ const failError = `Validation failed at stage ${validation.stage}: ${validation.errors.join('; ')}${recoveryHint}`;
4319
4322
  transitionToFailedAcceptance(root, state, currentTurn, failError, {
4320
4323
  error_code: 'validation_failed',
4321
4324
  stage: validation.stage,
@@ -4345,6 +4348,22 @@ function _acceptGovernedTurnLocked(root, config, opts) {
4345
4348
  },
4346
4349
  });
4347
4350
  }
4351
+ for (const event of Array.isArray(validation.normalization_events) ? validation.normalization_events : []) {
4352
+ emitRunEvent(root, 'staged_result_auto_normalized', {
4353
+ run_id: state.run_id,
4354
+ phase: state.phase,
4355
+ status: state.status,
4356
+ turn: { turn_id: currentTurn.turn_id, role_id: currentTurn.assigned_role },
4357
+ intent_id: currentTurn.intake_context?.intent_id || null,
4358
+ payload: {
4359
+ field: event.field,
4360
+ original_value: event.original_value,
4361
+ normalized_value: event.normalized_value,
4362
+ rationale: event.rationale,
4363
+ staging_path: resolvedStagingPath,
4364
+ },
4365
+ });
4366
+ }
4348
4367
 
4349
4368
  const rawTurnResult = validation.turnResult;
4350
4369
  const verificationProducedFiles = normalizeVerificationProducedFiles(rawTurnResult.verification);
@@ -50,6 +50,7 @@ export const VALID_RUN_EVENTS = [
50
50
  'operator_commit_reconcile_refused',
51
51
  'charter_materialization_required',
52
52
  'artifact_type_auto_normalized',
53
+ 'staged_result_auto_normalized',
53
54
  ];
54
55
 
55
56
  /**
@@ -94,7 +94,7 @@ export function validateStagedTurnResult(root, state, config, opts = {}) {
94
94
  if (opts.normalizeArtifactType === 'review') {
95
95
  normContext.forceReviewArtifact = true;
96
96
  }
97
- const { normalized, corrections } = normalizeTurnResult(turnResult, config, normContext);
97
+ const { normalized, corrections, normalizationEvents } = normalizeTurnResult(turnResult, config, normContext);
98
98
  turnResult = normalized;
99
99
  const sidecarResult = maybeAttachIdleExpansionSidecar(
100
100
  root,
@@ -159,6 +159,7 @@ export function validateStagedTurnResult(root, state, config, opts = {}) {
159
159
  warnings: allWarnings,
160
160
  turnResult,
161
161
  normalizations: corrections,
162
+ normalization_events: normalizationEvents,
162
163
  };
163
164
  }
164
165
 
@@ -995,8 +996,9 @@ function validateProtocol(tr, state, config) {
995
996
  */
996
997
  export function normalizeTurnResult(tr, config, context = {}) {
997
998
  const corrections = [];
999
+ const normalizationEvents = [];
998
1000
  if (tr === null || typeof tr !== 'object' || Array.isArray(tr)) {
999
- return { normalized: tr, corrections };
1001
+ return { normalized: tr, corrections, normalizationEvents };
1000
1002
  }
1001
1003
 
1002
1004
  const normalized = { ...tr };
@@ -1023,7 +1025,7 @@ export function normalizeTurnResult(tr, config, context = {}) {
1023
1025
  && !Array.isArray(normalized.artifact)
1024
1026
  && normalized.artifact.type === 'workspace'
1025
1027
  && filesChangedIsEmpty
1026
- && (context.forceReviewArtifact || isReviewOnly || hasExplicitNoEditLifecycleSignal)
1028
+ && (context.forceReviewArtifact || hasExplicitNoEditLifecycleSignal)
1027
1029
  ) {
1028
1030
  normalized.artifact = {
1029
1031
  ...normalized.artifact,
@@ -1033,6 +1035,42 @@ export function normalizeTurnResult(tr, config, context = {}) {
1033
1035
  : normalized.artifact.ref ?? null,
1034
1036
  };
1035
1037
  corrections.push('artifact.type: auto-normalized empty workspace artifact to review because files_changed is empty and no repo mutation was declared');
1038
+ normalizationEvents.push({
1039
+ field: 'artifact.type',
1040
+ original_value: 'workspace',
1041
+ normalized_value: 'review',
1042
+ rationale: 'empty_files_changed_no_repo_mutation_declared',
1043
+ });
1044
+ }
1045
+
1046
+ if (Array.isArray(normalized.objections)) {
1047
+ normalized.objections = normalized.objections.map((objection, index) => {
1048
+ if (objection === null || typeof objection !== 'object' || Array.isArray(objection)) {
1049
+ return objection;
1050
+ }
1051
+ const statement = typeof objection.statement === 'string' ? objection.statement.trim() : '';
1052
+ if (statement) {
1053
+ return objection;
1054
+ }
1055
+ const summary = typeof objection.summary === 'string' ? objection.summary.trim() : '';
1056
+ const detail = typeof objection.detail === 'string' ? objection.detail.trim() : '';
1057
+ const sourceField = summary ? 'summary' : detail ? 'detail' : null;
1058
+ const sourceValue = summary || detail;
1059
+ if (!sourceField) {
1060
+ return objection;
1061
+ }
1062
+ corrections.push(`objections[${index}].statement: copied from ${sourceField}`);
1063
+ normalizationEvents.push({
1064
+ field: `objections[${index}].statement`,
1065
+ original_value: objection.statement ?? null,
1066
+ normalized_value: sourceValue,
1067
+ rationale: `copied_from_${sourceField}`,
1068
+ });
1069
+ return {
1070
+ ...objection,
1071
+ statement: sourceValue,
1072
+ };
1073
+ });
1036
1074
  }
1037
1075
 
1038
1076
  const pickAllowedRoleFallback = () => {
@@ -1238,7 +1276,7 @@ export function normalizeTurnResult(tr, config, context = {}) {
1238
1276
  }
1239
1277
  }
1240
1278
 
1241
- return { normalized, corrections };
1279
+ return { normalized, corrections, normalizationEvents };
1242
1280
  }
1243
1281
 
1244
1282
  function normalizeIdleExpansionMutualExclusionSentinel(result) {