agentxchain 2.155.11 → 2.155.13
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 +1 -1
- package/src/lib/dispatch-bundle.js +48 -2
- package/src/lib/governed-state.js +46 -4
- package/src/lib/run-events.js +1 -0
package/package.json
CHANGED
|
@@ -51,6 +51,14 @@ const RESERVED_PATHS = [
|
|
|
51
51
|
'.agentxchain/lock.json',
|
|
52
52
|
];
|
|
53
53
|
|
|
54
|
+
function phaseTransitionAutoApprovalApplies(config) {
|
|
55
|
+
return config?.approval_policy?.phase_transitions?.default === 'auto_approve';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function runCompletionAutoApprovalApplies(config) {
|
|
59
|
+
return config?.approval_policy?.run_completion?.action === 'auto_approve';
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
/**
|
|
55
63
|
* Write a dispatch bundle for the currently assigned turn.
|
|
56
64
|
*
|
|
@@ -294,6 +302,36 @@ function renderPrompt(role, roleId, turn, state, config, root) {
|
|
|
294
302
|
lines.push('');
|
|
295
303
|
}
|
|
296
304
|
|
|
305
|
+
// BUG-70: Charter materialization directive for PM after idle-expansion new_intake_intent
|
|
306
|
+
if (state.charter_materialization_pending && phase === 'planning') {
|
|
307
|
+
const cmp = state.charter_materialization_pending;
|
|
308
|
+
lines.push('### Charter Materialization Required');
|
|
309
|
+
lines.push('');
|
|
310
|
+
lines.push('A previous idle-expansion turn proposed a new intake intent that has NOT been materialized into planning artifacts.');
|
|
311
|
+
lines.push('The phase transition to implementation was suppressed because the planning artifacts do not yet charter the new work.');
|
|
312
|
+
lines.push('');
|
|
313
|
+
lines.push('**Your job this turn is to materialize this charter into concrete planning artifacts:**');
|
|
314
|
+
lines.push('');
|
|
315
|
+
if (cmp.charter) {
|
|
316
|
+
lines.push(`- **Charter:** ${cmp.charter}`);
|
|
317
|
+
}
|
|
318
|
+
if (Array.isArray(cmp.acceptance_contract) && cmp.acceptance_contract.length > 0) {
|
|
319
|
+
lines.push('- **Acceptance Contract:**');
|
|
320
|
+
for (const req of cmp.acceptance_contract) {
|
|
321
|
+
lines.push(` - ${req}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
lines.push('');
|
|
325
|
+
lines.push('You MUST create or update these planning artifacts to reflect the new work:');
|
|
326
|
+
lines.push('- `.planning/SYSTEM_SPEC.md` — technical specification for the new increment');
|
|
327
|
+
lines.push('- `.planning/ROADMAP.md` — updated roadmap including the new milestone');
|
|
328
|
+
lines.push('- `.planning/PM_SIGNOFF.md` — planning sign-off with acceptance criteria');
|
|
329
|
+
lines.push('');
|
|
330
|
+
lines.push('Only after these artifacts exist and reference the new charter may you request `phase_transition_request: "implementation"`.');
|
|
331
|
+
lines.push('Do NOT repeat the idle-expansion proposal. Write the actual planning documents.');
|
|
332
|
+
lines.push('');
|
|
333
|
+
}
|
|
334
|
+
|
|
297
335
|
const workflowResponsibilities = getWorkflowPromptResponsibilities(config, phase, roleId, root);
|
|
298
336
|
if (workflowResponsibilities.length > 0) {
|
|
299
337
|
const isReviewOnlyOwner = role.write_authority === 'review_only';
|
|
@@ -334,7 +372,10 @@ function renderPrompt(role, roleId, turn, state, config, root) {
|
|
|
334
372
|
if (gateConfig.requires_verification_pass) {
|
|
335
373
|
lines.push('- Requires verification pass');
|
|
336
374
|
}
|
|
337
|
-
if (gateConfig.requires_human_approval) {
|
|
375
|
+
if (gateConfig.requires_human_approval && phaseTransitionAutoApprovalApplies(config)) {
|
|
376
|
+
lines.push('- Requires approval, but `approval_policy.phase_transitions.default` is `auto_approve` for this run.');
|
|
377
|
+
lines.push('- Do NOT set `status: "needs_human"` solely to request phase-gate approval. If the required artifacts are complete, set the appropriate `phase_transition_request`; the orchestrator will evaluate and auto-approve the gate.');
|
|
378
|
+
} else if (gateConfig.requires_human_approval) {
|
|
338
379
|
lines.push('- Requires human approval');
|
|
339
380
|
}
|
|
340
381
|
lines.push('');
|
|
@@ -512,7 +553,12 @@ function renderPrompt(role, roleId, turn, state, config, root) {
|
|
|
512
553
|
const isTerminal = currentPhase && phaseNames.indexOf(currentPhase) === phaseNames.length - 1;
|
|
513
554
|
if (isTerminal) {
|
|
514
555
|
lines.push(`- **You are in the \`${currentPhase}\` phase (final phase).**`);
|
|
515
|
-
|
|
556
|
+
if (runCompletionAutoApprovalApplies(config)) {
|
|
557
|
+
lines.push('- **If your review verdict is ship-ready (no blocking issues):** set `run_completion_request: true` and `status: "completed"`. This triggers orchestrator run-completion evaluation and auto-approval under `approval_policy.run_completion.action: "auto_approve"`.');
|
|
558
|
+
lines.push('- Do NOT use `status: "needs_human"` solely to request final approval when the approval policy is auto-approve. Use `needs_human` only for genuine blockers.');
|
|
559
|
+
} else {
|
|
560
|
+
lines.push('- **If your review verdict is ship-ready (no blocking issues):** set `run_completion_request: true` and `status: "completed"`. This triggers the human approval gate — it does NOT bypass human review.');
|
|
561
|
+
}
|
|
516
562
|
lines.push('- **If you found genuine blocking issues that prevent shipping:** set `status: "needs_human"` and explain the blockers in `needs_human_reason`.');
|
|
517
563
|
lines.push('- Do NOT use `status: "needs_human"` to mean "human should approve the release." That is what `run_completion_request: true` is for.');
|
|
518
564
|
lines.push('- Do NOT set `phase_transition_request` to the exit gate name.');
|
|
@@ -5222,6 +5222,42 @@ function _acceptGovernedTurnLocked(root, config, opts) {
|
|
|
5222
5222
|
let gateResult = null;
|
|
5223
5223
|
let completionResult = null;
|
|
5224
5224
|
let timeoutResult = null;
|
|
5225
|
+
|
|
5226
|
+
// BUG-70: idle-expansion new_intake_intent is a proposal, not a chartered plan.
|
|
5227
|
+
// Suppress phase_transition_request — the new intake must be materialized into
|
|
5228
|
+
// planning artifacts by a subsequent turn before implementation can proceed.
|
|
5229
|
+
const isIdleExpansionNewIntake = idleExpansionResultSummary?.kind === 'new_intake_intent';
|
|
5230
|
+
if (isIdleExpansionNewIntake && turnResult.phase_transition_request) {
|
|
5231
|
+
const rawIdleResult = turnResult.idle_expansion_result;
|
|
5232
|
+
updatedState.charter_materialization_pending = {
|
|
5233
|
+
charter: rawIdleResult?.new_intake_intent?.charter
|
|
5234
|
+
|| idleExpansionResultSummary.new_intent_title
|
|
5235
|
+
|| null,
|
|
5236
|
+
acceptance_contract: rawIdleResult?.new_intake_intent?.acceptance_contract
|
|
5237
|
+
|| [],
|
|
5238
|
+
suppressed_transition: turnResult.phase_transition_request,
|
|
5239
|
+
source_turn_id: turnResult.turn_id,
|
|
5240
|
+
recorded_at: now,
|
|
5241
|
+
};
|
|
5242
|
+
emitRunEvent(root, 'charter_materialization_required', {
|
|
5243
|
+
run_id: updatedState.run_id,
|
|
5244
|
+
phase: state.phase,
|
|
5245
|
+
status: updatedState.status,
|
|
5246
|
+
turn: { turn_id: currentTurn.turn_id, role_id: currentTurn.assigned_role },
|
|
5247
|
+
payload: {
|
|
5248
|
+
suppressed_transition: turnResult.phase_transition_request,
|
|
5249
|
+
reason: 'New intake from idle expansion must be materialized into planning artifacts before implementation dispatch.',
|
|
5250
|
+
new_intake_charter: updatedState.charter_materialization_pending.charter,
|
|
5251
|
+
},
|
|
5252
|
+
});
|
|
5253
|
+
}
|
|
5254
|
+
|
|
5255
|
+
// BUG-70: clear charter_materialization_pending when a non-idle-expansion turn
|
|
5256
|
+
// in the planning phase is accepted (the PM has had a chance to materialize).
|
|
5257
|
+
if (updatedState.charter_materialization_pending && !isIdleExpansionNewIntake && state.phase === 'planning') {
|
|
5258
|
+
updatedState.charter_materialization_pending = null;
|
|
5259
|
+
}
|
|
5260
|
+
|
|
5225
5261
|
const hasRemainingTurns = Object.keys(remainingTurns).length > 0;
|
|
5226
5262
|
if (turnResult.status !== 'needs_human') {
|
|
5227
5263
|
if (hasRemainingTurns) {
|
|
@@ -5231,7 +5267,7 @@ function _acceptGovernedTurnLocked(root, config, opts) {
|
|
|
5231
5267
|
requested_at: now,
|
|
5232
5268
|
};
|
|
5233
5269
|
}
|
|
5234
|
-
if (turnResult.phase_transition_request && !updatedState.queued_phase_transition) {
|
|
5270
|
+
if (turnResult.phase_transition_request && !updatedState.queued_phase_transition && !isIdleExpansionNewIntake) {
|
|
5235
5271
|
updatedState.queued_phase_transition = {
|
|
5236
5272
|
from: state.phase,
|
|
5237
5273
|
to: turnResult.phase_transition_request,
|
|
@@ -5342,8 +5378,14 @@ function _acceptGovernedTurnLocked(root, config, opts) {
|
|
|
5342
5378
|
}
|
|
5343
5379
|
|
|
5344
5380
|
if (updatedState.status !== 'blocked' && updatedState.status !== 'paused' && updatedState.status !== 'completed') {
|
|
5345
|
-
|
|
5346
|
-
|
|
5381
|
+
// BUG-70: suppress phase transition for idle-expansion new_intake_intent turns.
|
|
5382
|
+
// Strip phase_transition_request from the effective turn so evaluatePhaseExit
|
|
5383
|
+
// sees no transition request and returns { action: 'no_request' }.
|
|
5384
|
+
const effectiveTurnResult = isIdleExpansionNewIntake
|
|
5385
|
+
? { ...turnResult, phase_transition_request: null }
|
|
5386
|
+
: turnResult;
|
|
5387
|
+
const phaseSource = effectiveTurnResult.phase_transition_request
|
|
5388
|
+
? effectiveTurnResult
|
|
5347
5389
|
: findHistoryTurnRequest(nextHistoryEntries, state.queued_phase_transition?.requested_by_turn, 'phase_transition');
|
|
5348
5390
|
|
|
5349
5391
|
// Always evaluate phase exit when the run drains — even without a request,
|
|
@@ -5351,7 +5393,7 @@ function _acceptGovernedTurnLocked(root, config, opts) {
|
|
|
5351
5393
|
gateResult = evaluatePhaseExit({
|
|
5352
5394
|
state: postAcceptanceState,
|
|
5353
5395
|
config,
|
|
5354
|
-
acceptedTurn: phaseSource ||
|
|
5396
|
+
acceptedTurn: phaseSource || effectiveTurnResult,
|
|
5355
5397
|
root,
|
|
5356
5398
|
});
|
|
5357
5399
|
|