agentxchain 2.155.48 → 2.155.50
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
|
@@ -491,12 +491,15 @@ function renderPrompt(role, roleId, turn, state, config, root) {
|
|
|
491
491
|
lines.push('- `schema_version`: always `"1.0"`');
|
|
492
492
|
lines.push('- `run_id`, `turn_id`, `role`, `runtime_id`: must match the values above exactly');
|
|
493
493
|
lines.push('- `status`: one of `completed`, `blocked`, `needs_human`, `failed`. Do NOT use `complete`, `success`, `done`, or any other synonym — use the exact enum value `completed`.');
|
|
494
|
-
lines.push('- `summary`: concise description of what you did this turn');
|
|
495
494
|
lines.push('- `decisions`: REQUIRED array. Use `[]` when no new decisions were made; do not omit the field.');
|
|
496
495
|
lines.push('- `objections`: REQUIRED array. Use `[]` when no objections are raised; review_only roles must include at least one objection.');
|
|
497
|
-
lines.push('- `
|
|
496
|
+
lines.push('- `summary`: **REQUIRED** non-empty string. Do NOT omit this field.');
|
|
497
|
+
lines.push('- `runtime_id`: **REQUIRED**. Must match the runtime_id provided above exactly.');
|
|
498
|
+
lines.push('- `files_changed`: **REQUIRED** array of **strings** (file paths only). Do NOT use `files_modified` — the field name is `files_changed`. Do NOT use objects like `{path, change_type}` — just the path string (e.g. `["src/cli.js", "tests/smoke.mjs"]`).');
|
|
499
|
+
lines.push('- `proposed_next_role`: **REQUIRED**. Must be in allowed_next_roles for the current phase, or `"human"`.');
|
|
498
500
|
lines.push('- `decisions[].id`: pattern `DEC-NNN` where NNN is digits only (e.g. `DEC-001`, `DEC-002`). Do NOT use `D1`, `D2`, or freeform IDs.');
|
|
499
501
|
lines.push('- `decisions[].statement`: non-empty string describing the decision. Do NOT use `decision` or `description` as the field name — the field is `statement`.');
|
|
502
|
+
lines.push('- `decisions[].rationale`: REQUIRED non-empty string explaining why the decision was made. Do NOT omit this field.');
|
|
500
503
|
lines.push('- `decisions[].category`: one of `implementation`, `architecture`, `scope`, `process`, `quality`, `release`');
|
|
501
504
|
lines.push('- `objections[].id`: pattern `OBJ-NNN` where NNN is digits only (e.g. `OBJ-001`, `OBJ-002`). Do NOT append extra suffixes like `-M31` or use non-numeric characters after `OBJ-`.');
|
|
502
505
|
lines.push('- `objections[].severity`: one of `low`, `medium`, `high`, `blocking`');
|
|
@@ -508,7 +511,6 @@ function renderPrompt(role, roleId, turn, state, config, root) {
|
|
|
508
511
|
lines.push('- If you make zero repo file edits, set `artifact.type` to `"review"` and `files_changed` to `[]`.');
|
|
509
512
|
lines.push('- Only set `artifact.type` to `"workspace"` when you actually modified repo files and listed every changed path in `files_changed`.');
|
|
510
513
|
lines.push('- Every `objections[]` item must include a non-empty `statement`; do not use `summary` or `detail` as a substitute.');
|
|
511
|
-
lines.push('- `proposed_next_role`: must be in allowed_next_roles for current phase, or `human`');
|
|
512
514
|
if (role.write_authority === 'review_only') {
|
|
513
515
|
lines.push('- `objections`: **must be non-empty** (challenge requirement for review_only roles)');
|
|
514
516
|
}
|
|
@@ -85,6 +85,9 @@ export function validateStagedTurnResult(root, state, config, opts = {}) {
|
|
|
85
85
|
if (activeTurn) {
|
|
86
86
|
const roleKey = activeTurn.assigned_role || activeTurn.role;
|
|
87
87
|
normContext.assignedRole = roleKey;
|
|
88
|
+
if (activeTurn.runtime_id) {
|
|
89
|
+
normContext.runtimeId = activeTurn.runtime_id;
|
|
90
|
+
}
|
|
88
91
|
const roleConfig = config?.roles?.[roleKey];
|
|
89
92
|
if (roleConfig) {
|
|
90
93
|
normContext.writeAuthority = roleConfig.write_authority;
|
|
@@ -1002,6 +1005,21 @@ export function normalizeTurnResult(tr, config, context = {}) {
|
|
|
1002
1005
|
}
|
|
1003
1006
|
|
|
1004
1007
|
const normalized = { ...tr };
|
|
1008
|
+
|
|
1009
|
+
// ── BUG-95: rename synonym fields before variable computation ────────
|
|
1010
|
+
// files_modified is an unambiguous synonym for files_changed.
|
|
1011
|
+
if (!('files_changed' in normalized) && Array.isArray(normalized.files_modified)) {
|
|
1012
|
+
corrections.push('files_changed: renamed from synonym files_modified');
|
|
1013
|
+
normalizationEvents.push({
|
|
1014
|
+
field: 'files_changed',
|
|
1015
|
+
original_value: null,
|
|
1016
|
+
normalized_value: '(renamed from files_modified)',
|
|
1017
|
+
rationale: 'files_modified_renamed_to_files_changed',
|
|
1018
|
+
});
|
|
1019
|
+
normalized.files_changed = normalized.files_modified;
|
|
1020
|
+
delete normalized.files_modified;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1005
1023
|
const routing = config?.routing;
|
|
1006
1024
|
const phaseNames = routing ? Object.keys(routing) : [];
|
|
1007
1025
|
const currentPhase = context.phase;
|
|
@@ -1042,6 +1060,68 @@ export function normalizeTurnResult(tr, config, context = {}) {
|
|
|
1042
1060
|
normalized.objections = [];
|
|
1043
1061
|
}
|
|
1044
1062
|
|
|
1063
|
+
// ── BUG-95: default missing runtime_id from dispatch context ─────────
|
|
1064
|
+
if (!normalized.runtime_id && context.runtimeId) {
|
|
1065
|
+
corrections.push(`runtime_id: defaulted from dispatch context "${context.runtimeId}"`);
|
|
1066
|
+
normalizationEvents.push({
|
|
1067
|
+
field: 'runtime_id',
|
|
1068
|
+
original_value: null,
|
|
1069
|
+
normalized_value: context.runtimeId,
|
|
1070
|
+
rationale: 'missing_runtime_id_defaulted_from_context',
|
|
1071
|
+
});
|
|
1072
|
+
normalized.runtime_id = context.runtimeId;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// ── BUG-95: synthesize missing summary from available fields ─────────
|
|
1076
|
+
if (!normalized.summary || (typeof normalized.summary === 'string' && !normalized.summary.trim())) {
|
|
1077
|
+
const alt = (typeof normalized.milestone_title === 'string' && normalized.milestone_title.trim())
|
|
1078
|
+
? normalized.milestone_title.trim()
|
|
1079
|
+
: (typeof normalized.milestone === 'string' && normalized.milestone.trim())
|
|
1080
|
+
? `${normalized.role || 'agent'} turn for ${normalized.milestone.trim()}`
|
|
1081
|
+
: `${normalized.role || 'agent'} turn completed`;
|
|
1082
|
+
const src = (typeof normalized.milestone_title === 'string' && normalized.milestone_title.trim()) ? 'milestone_title'
|
|
1083
|
+
: (typeof normalized.milestone === 'string' && normalized.milestone.trim()) ? 'milestone' : 'fallback';
|
|
1084
|
+
corrections.push(`summary: synthesized from ${src}`);
|
|
1085
|
+
normalizationEvents.push({
|
|
1086
|
+
field: 'summary',
|
|
1087
|
+
original_value: normalized.summary ?? null,
|
|
1088
|
+
normalized_value: alt,
|
|
1089
|
+
rationale: `missing_summary_synthesized_from_${src}`,
|
|
1090
|
+
});
|
|
1091
|
+
normalized.summary = alt;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// ── BUG-95: default missing artifact object ────────────────────────────
|
|
1095
|
+
if (!normalized.artifact || typeof normalized.artifact !== 'object' || Array.isArray(normalized.artifact)) {
|
|
1096
|
+
const hasFiles = Array.isArray(normalized.files_changed) && normalized.files_changed.length > 0;
|
|
1097
|
+
const inferredArtifact = { type: hasFiles ? 'workspace' : 'review' };
|
|
1098
|
+
corrections.push(`artifact: inferred ${JSON.stringify(inferredArtifact)} from files_changed`);
|
|
1099
|
+
normalizationEvents.push({
|
|
1100
|
+
field: 'artifact',
|
|
1101
|
+
original_value: normalized.artifact ?? null,
|
|
1102
|
+
normalized_value: inferredArtifact,
|
|
1103
|
+
rationale: 'missing_artifact_inferred_from_files_changed',
|
|
1104
|
+
});
|
|
1105
|
+
normalized.artifact = inferredArtifact;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// ── BUG-95: default missing proposed_next_role ─────────────────────────
|
|
1109
|
+
if (!normalized.proposed_next_role) {
|
|
1110
|
+
let inferredRole = null;
|
|
1111
|
+
if (allowedNextRoles.length > 0) {
|
|
1112
|
+
inferredRole = allowedNextRoles.find(r => r !== assignedRole) || allowedNextRoles[0];
|
|
1113
|
+
}
|
|
1114
|
+
if (!inferredRole) inferredRole = 'pm';
|
|
1115
|
+
corrections.push(`proposed_next_role: defaulted to "${inferredRole}"`);
|
|
1116
|
+
normalizationEvents.push({
|
|
1117
|
+
field: 'proposed_next_role',
|
|
1118
|
+
original_value: null,
|
|
1119
|
+
normalized_value: inferredRole,
|
|
1120
|
+
rationale: 'missing_proposed_next_role_defaulted',
|
|
1121
|
+
});
|
|
1122
|
+
normalized.proposed_next_role = inferredRole;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1045
1125
|
// ── BUG-90: normalize status synonyms ────────────────────────────────
|
|
1046
1126
|
const STATUS_SYNONYMS = { complete: 'completed', success: 'completed', done: 'completed', error: 'failed', failure: 'failed' };
|
|
1047
1127
|
if (typeof normalized.status === 'string' && !VALID_STATUSES.includes(normalized.status)) {
|
|
@@ -1119,6 +1199,32 @@ export function normalizeTurnResult(tr, config, context = {}) {
|
|
|
1119
1199
|
}
|
|
1120
1200
|
}
|
|
1121
1201
|
|
|
1202
|
+
// Normalize missing rationale from existing decision text. Do not invent
|
|
1203
|
+
// rationale when the decision object has no meaningful source material.
|
|
1204
|
+
const rationale = typeof patched.rationale === 'string' ? patched.rationale.trim() : '';
|
|
1205
|
+
if (!rationale) {
|
|
1206
|
+
const rationaleSources = [
|
|
1207
|
+
['reason', patched.reason],
|
|
1208
|
+
['why', patched.why],
|
|
1209
|
+
['description', patched.description],
|
|
1210
|
+
['decision', patched.decision],
|
|
1211
|
+
['statement', patched.statement],
|
|
1212
|
+
];
|
|
1213
|
+
const source = rationaleSources.find(([, value]) => typeof value === 'string' && value.trim());
|
|
1214
|
+
if (source) {
|
|
1215
|
+
const [srcField, srcValue] = source;
|
|
1216
|
+
const alt = srcValue.trim();
|
|
1217
|
+
corrections.push(`decisions[${index}].rationale: copied from ${srcField}`);
|
|
1218
|
+
normalizationEvents.push({
|
|
1219
|
+
field: `decisions[${index}].rationale`,
|
|
1220
|
+
original_value: patched.rationale ?? null,
|
|
1221
|
+
normalized_value: alt,
|
|
1222
|
+
rationale: `copied_from_${srcField}`,
|
|
1223
|
+
});
|
|
1224
|
+
patched = { ...patched, rationale: alt };
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1122
1228
|
// Default missing category to 'implementation'
|
|
1123
1229
|
if (!patched.category || !VALID_CATEGORIES.includes(patched.category)) {
|
|
1124
1230
|
const defaultCat = 'implementation';
|