agentxchain 2.155.17 → 2.155.19
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
package/src/commands/resume.js
CHANGED
|
@@ -21,6 +21,7 @@ import { loadProjectContext, loadProjectState } from '../lib/config.js';
|
|
|
21
21
|
import {
|
|
22
22
|
initializeGovernedRun,
|
|
23
23
|
assignGovernedTurn,
|
|
24
|
+
reissueTurn,
|
|
24
25
|
deriveAfterDispatchHookRecoveryAction,
|
|
25
26
|
markRunBlocked,
|
|
26
27
|
getActiveTurns,
|
|
@@ -44,6 +45,7 @@ import { runHooks } from '../lib/hook-runner.js';
|
|
|
44
45
|
import { summarizeRunProvenance } from '../lib/run-provenance.js';
|
|
45
46
|
import { consumeNextApprovedIntent } from '../lib/intake.js';
|
|
46
47
|
import { reconcileStaleTurns } from '../lib/stale-turn-watchdog.js';
|
|
48
|
+
import { resolveGovernedRole } from '../lib/role-resolution.js';
|
|
47
49
|
|
|
48
50
|
function hasStandingPendingExitGate(state, config) {
|
|
49
51
|
const phase = state?.phase;
|
|
@@ -442,12 +444,6 @@ export async function resumeCommand(opts) {
|
|
|
442
444
|
process.exit(1);
|
|
443
445
|
}
|
|
444
446
|
|
|
445
|
-
printResumeRunContext({ root, state, config });
|
|
446
|
-
console.log(chalk.yellow(`Re-dispatching blocked turn: ${retainedTurn.turn_id}`));
|
|
447
|
-
console.log(` Role: ${retainedTurn.assigned_role}`);
|
|
448
|
-
console.log(` Attempt: ${retainedTurn.attempt}`);
|
|
449
|
-
console.log('');
|
|
450
|
-
|
|
451
447
|
const reactivated = reactivateGovernedRun(root, state, { via: turnResumeVia, notificationConfig: config });
|
|
452
448
|
if (!reactivated.ok) {
|
|
453
449
|
console.log(chalk.red(`Failed to reactivate blocked run: ${reactivated.error}`));
|
|
@@ -461,6 +457,38 @@ export async function resumeCommand(opts) {
|
|
|
461
457
|
console.log(chalk.yellow(reactivated.phantom_notice));
|
|
462
458
|
}
|
|
463
459
|
|
|
460
|
+
const materializationResolution = resolveGovernedRole({ state, config });
|
|
461
|
+
if (
|
|
462
|
+
state.charter_materialization_pending
|
|
463
|
+
&& state.phase === 'planning'
|
|
464
|
+
&& materializationResolution.roleId
|
|
465
|
+
&& materializationResolution.roleId !== retainedTurn.assigned_role
|
|
466
|
+
&& !opts.turn
|
|
467
|
+
) {
|
|
468
|
+
const reason = `charter materialization pending superseded stale retained ${retainedTurn.assigned_role} turn`;
|
|
469
|
+
const reissued = reissueTurn(root, config, {
|
|
470
|
+
turnId: retainedTurn.turn_id,
|
|
471
|
+
roleId: materializationResolution.roleId,
|
|
472
|
+
reason,
|
|
473
|
+
});
|
|
474
|
+
if (!reissued.ok) {
|
|
475
|
+
console.log(chalk.red(`Failed to reissue retained turn for materialization: ${reissued.error}`));
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
state = reissued.state;
|
|
479
|
+
retainedTurn = reissued.newTurn;
|
|
480
|
+
console.log(chalk.yellow(`Reissued retained turn for charter materialization: ${retainedTurn.turn_id}`));
|
|
481
|
+
console.log(` Role: ${retainedTurn.assigned_role}`);
|
|
482
|
+
console.log(` Reason: ${reason}`);
|
|
483
|
+
console.log('');
|
|
484
|
+
} else {
|
|
485
|
+
printResumeRunContext({ root, state, config });
|
|
486
|
+
console.log(chalk.yellow(`Re-dispatching blocked turn: ${retainedTurn.turn_id}`));
|
|
487
|
+
console.log(` Role: ${retainedTurn.assigned_role}`);
|
|
488
|
+
console.log(` Attempt: ${retainedTurn.attempt}`);
|
|
489
|
+
console.log('');
|
|
490
|
+
}
|
|
491
|
+
|
|
464
492
|
const bundleResult = writeDispatchBundle(root, state, config, { turnId: retainedTurn.turn_id });
|
|
465
493
|
if (!bundleResult.ok) {
|
|
466
494
|
console.log(chalk.red(`Failed to write dispatch bundle: ${bundleResult.error}`));
|
|
@@ -709,38 +737,24 @@ function printResumeRunContext({ root, state, config }) {
|
|
|
709
737
|
}
|
|
710
738
|
|
|
711
739
|
function resolveTargetRole(opts, state, config) {
|
|
712
|
-
const
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
if (!config.roles?.[opts.role]) {
|
|
718
|
-
console.log(chalk.red(`Unknown role: "${opts.role}"`));
|
|
719
|
-
console.log(chalk.dim(`Available roles: ${Object.keys(config.roles || {}).join(', ')}`));
|
|
720
|
-
return null;
|
|
721
|
-
}
|
|
722
|
-
if (routing?.allowed_next_roles && !routing.allowed_next_roles.includes(opts.role) && opts.role !== 'human') {
|
|
723
|
-
console.log(chalk.yellow(`Warning: role "${opts.role}" is not in allowed_next_roles for phase "${phase}".`));
|
|
724
|
-
console.log(chalk.dim(`Allowed: ${routing.allowed_next_roles.join(', ')}`));
|
|
725
|
-
// Allow it as an override, but warn
|
|
740
|
+
const resolved = resolveGovernedRole({ override: opts.role || null, state, config });
|
|
741
|
+
if (resolved.error) {
|
|
742
|
+
console.log(chalk.red(resolved.error));
|
|
743
|
+
if (resolved.availableRoles.length) {
|
|
744
|
+
console.log(chalk.dim(`Available roles: ${resolved.availableRoles.join(', ')}`));
|
|
726
745
|
}
|
|
727
|
-
return
|
|
746
|
+
return null;
|
|
728
747
|
}
|
|
729
748
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
return routing.entry_role;
|
|
749
|
+
if (!opts.role && state.next_recommended_role && resolved.roleId === state.next_recommended_role) {
|
|
750
|
+
console.log(chalk.dim(`Using recommended role: ${resolved.roleId} (from previous turn)`));
|
|
733
751
|
}
|
|
734
752
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
if (roles.length > 0) {
|
|
738
|
-
console.log(chalk.yellow(`No entry_role for phase "${phase}". Defaulting to "${roles[0]}".`));
|
|
739
|
-
return roles[0];
|
|
753
|
+
for (const warning of resolved.warnings) {
|
|
754
|
+
console.log(chalk.yellow(`Warning: ${warning}`));
|
|
740
755
|
}
|
|
741
756
|
|
|
742
|
-
|
|
743
|
-
return null;
|
|
757
|
+
return resolved.roleId;
|
|
744
758
|
}
|
|
745
759
|
|
|
746
760
|
function runAfterDispatchHooks(root, hooksConfig, state, turn, config) {
|
package/src/commands/step.js
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
assignGovernedTurn,
|
|
31
31
|
acceptGovernedTurn,
|
|
32
32
|
deriveAfterDispatchHookRecoveryAction,
|
|
33
|
+
reissueTurn,
|
|
33
34
|
rejectGovernedTurn,
|
|
34
35
|
markRunBlocked,
|
|
35
36
|
getActiveTurnCount,
|
|
@@ -251,6 +252,30 @@ export async function stepCommand(opts) {
|
|
|
251
252
|
state = reactivated.state;
|
|
252
253
|
skipAssignment = true;
|
|
253
254
|
|
|
255
|
+
const materializationResolution = resolveGovernedRole({ state, config });
|
|
256
|
+
if (
|
|
257
|
+
state.charter_materialization_pending
|
|
258
|
+
&& state.phase === 'planning'
|
|
259
|
+
&& materializationResolution.roleId
|
|
260
|
+
&& materializationResolution.roleId !== targetTurn.assigned_role
|
|
261
|
+
&& !opts.turn
|
|
262
|
+
) {
|
|
263
|
+
const reason = `charter materialization pending superseded stale retained ${targetTurn.assigned_role} turn`;
|
|
264
|
+
const reissued = reissueTurn(root, config, {
|
|
265
|
+
turnId: targetTurn.turn_id,
|
|
266
|
+
roleId: materializationResolution.roleId,
|
|
267
|
+
reason,
|
|
268
|
+
});
|
|
269
|
+
if (!reissued.ok) {
|
|
270
|
+
console.log(chalk.red(`Failed to reissue retained turn for materialization: ${reissued.error}`));
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
state = reissued.state;
|
|
274
|
+
targetTurn = reissued.newTurn;
|
|
275
|
+
console.log(chalk.yellow(`Reissued retained turn for charter materialization: ${targetTurn.turn_id}`));
|
|
276
|
+
console.log(` Role: ${targetTurn.assigned_role}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
254
279
|
// BUG-1 fix: refresh baseline snapshot to capture files dirtied between assignment and dispatch
|
|
255
280
|
refreshTurnBaselineSnapshot(root, targetTurn.turn_id);
|
|
256
281
|
state = JSON.parse(readFileSync(join(root, '.agentxchain/state.json'), 'utf8'));
|
|
@@ -77,6 +77,15 @@ function getDependencyBarrierId(workstreamId) {
|
|
|
77
77
|
|
|
78
78
|
function resolveRecommendedRole(repoState, repoConfig, workstreamPhase) {
|
|
79
79
|
const routing = repoConfig.routing?.[workstreamPhase];
|
|
80
|
+
if (repoState.charter_materialization_pending && workstreamPhase === 'planning') {
|
|
81
|
+
if (repoConfig.roles?.pm) {
|
|
82
|
+
return 'pm';
|
|
83
|
+
}
|
|
84
|
+
if (routing?.entry_role && repoConfig.roles?.[routing.entry_role]) {
|
|
85
|
+
return routing.entry_role;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
80
89
|
const recommendedRole = repoState.next_recommended_role;
|
|
81
90
|
if (
|
|
82
91
|
recommendedRole
|
|
@@ -3712,6 +3712,7 @@ export function refreshTurnBaselineSnapshot(root, turnId) {
|
|
|
3712
3712
|
* @param {object} config - normalized config
|
|
3713
3713
|
* @param {object} opts
|
|
3714
3714
|
* @param {string} [opts.turnId] - specific turn to reissue
|
|
3715
|
+
* @param {string} [opts.roleId] - replacement role; defaults to original turn role
|
|
3715
3716
|
* @param {string} [opts.reason] - reason for reissue
|
|
3716
3717
|
* @returns {{ ok: boolean, state?: object, newTurn?: object, baselineDelta?: object, error?: string }}
|
|
3717
3718
|
*/
|
|
@@ -3769,7 +3770,8 @@ export function reissueTurn(root, config, opts = {}) {
|
|
|
3769
3770
|
const oldTurn = activeTurns[turnId];
|
|
3770
3771
|
if (!oldTurn) return { ok: false, error: `Turn ${turnId} not found in active turns` };
|
|
3771
3772
|
|
|
3772
|
-
const
|
|
3773
|
+
const oldRoleId = oldTurn.assigned_role;
|
|
3774
|
+
const roleId = opts.roleId || oldRoleId;
|
|
3773
3775
|
const role = config.roles?.[roleId];
|
|
3774
3776
|
if (!role) return { ok: false, error: `Role "${roleId}" not found in config` };
|
|
3775
3777
|
|
|
@@ -3795,7 +3797,7 @@ export function reissueTurn(root, config, opts = {}) {
|
|
|
3795
3797
|
appendJsonl(root, HISTORY_PATH, {
|
|
3796
3798
|
turn_id: oldTurn.turn_id,
|
|
3797
3799
|
run_id: state.run_id,
|
|
3798
|
-
role:
|
|
3800
|
+
role: oldRoleId,
|
|
3799
3801
|
phase: state.phase,
|
|
3800
3802
|
status: 'reissued',
|
|
3801
3803
|
summary: `Turn reissued: ${reason}`,
|
|
@@ -3811,7 +3813,8 @@ export function reissueTurn(root, config, opts = {}) {
|
|
|
3811
3813
|
timestamp: now,
|
|
3812
3814
|
decision: 'turn_reissued',
|
|
3813
3815
|
turn_id: oldTurn.turn_id,
|
|
3814
|
-
role:
|
|
3816
|
+
role: oldRoleId,
|
|
3817
|
+
new_role: roleId,
|
|
3815
3818
|
phase: state.phase,
|
|
3816
3819
|
reason,
|
|
3817
3820
|
old_baseline: {
|
|
@@ -3895,6 +3898,8 @@ export function reissueTurn(root, config, opts = {}) {
|
|
|
3895
3898
|
new_head: newBaseline.head_ref,
|
|
3896
3899
|
old_runtime: oldRuntimeId,
|
|
3897
3900
|
new_runtime: currentRuntimeId,
|
|
3901
|
+
old_role: oldRoleId,
|
|
3902
|
+
new_role: roleId,
|
|
3898
3903
|
},
|
|
3899
3904
|
});
|
|
3900
3905
|
|
|
@@ -95,6 +95,24 @@ export function resolveGovernedRole({ override = null, state = null, config }) {
|
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// BUG-73: charter materialization is planning-owned work. Persisted state from
|
|
99
|
+
// older releases may still recommend dev, so resolve the role from the pending
|
|
100
|
+
// materialization rather than trusting a stale implementation recommendation.
|
|
101
|
+
if (state?.charter_materialization_pending && phase === 'planning') {
|
|
102
|
+
const materializationRole = config?.roles?.pm
|
|
103
|
+
? 'pm'
|
|
104
|
+
: (routing?.entry_role && config?.roles?.[routing.entry_role] ? routing.entry_role : null);
|
|
105
|
+
if (materializationRole) {
|
|
106
|
+
return {
|
|
107
|
+
roleId: materializationRole,
|
|
108
|
+
warnings,
|
|
109
|
+
error: null,
|
|
110
|
+
availableRoles: roles,
|
|
111
|
+
phase,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
98
116
|
if (state?.next_recommended_role && config?.roles?.[state.next_recommended_role]) {
|
|
99
117
|
const recommended = state.next_recommended_role;
|
|
100
118
|
const isLegal = !routing?.allowed_next_roles || routing.allowed_next_roles.includes(recommended);
|