agentxchain 2.154.10 → 2.155.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.
@@ -759,6 +759,7 @@ program
759
759
  .option('--poll-seconds <n>', 'Seconds between idle-detection cycles in continuous mode (default: 30)', parseInt)
760
760
  .option('--triage-approval <mode>', 'Triage policy for vision-derived intents: auto or human (default: config or auto)')
761
761
  .option('--max-idle-cycles <n>', 'Stop after N consecutive idle cycles with no derivable work (default: 3)', parseInt)
762
+ .option('--on-idle <mode>', 'Continuous idle policy: exit, perpetual, or human_review (default: config or exit)')
762
763
  .option('--session-budget <usd>', 'Cumulative session-level budget cap in USD for continuous mode', parseFloat)
763
764
  .option('--auto-retry-on-ghost', 'Enable bounded automatic retry for continuous-mode startup ghost turns')
764
765
  .option('--no-auto-retry-on-ghost', 'Disable bounded automatic retry for continuous-mode startup ghost turns')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.154.10",
3
+ "version": "2.155.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,6 +12,29 @@ import { buildGovernedPlanningArtifacts, interpolateTemplateContent } from '../l
12
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
13
  const TEMPLATES_DIR = join(__dirname, '../templates');
14
14
 
15
+ const PM_IDLE_EXPANSION_PROMPT = `# Idle-Expansion Charter
16
+
17
+ You are running in IDLE-EXPANSION mode. The continuous loop found no directly derivable work after the configured idle threshold. Your task is to decide the next governed product increment from the configured sources, or to declare the product vision exhausted.
18
+
19
+ Read these sources in order:
20
+
21
+ 1. \`.planning/VISION.md\` (read-only; never modify)
22
+ 2. \`.planning/ROADMAP.md\`
23
+ 3. \`.planning/SYSTEM_SPEC.md\`
24
+ 4. \`.agentxchain/intake/\`
25
+ 5. \`.planning/acceptance-matrix.md\` when present
26
+
27
+ You must emit exactly one structured \`idle_expansion_result\` in the turn result:
28
+
29
+ 1. \`kind: "new_intake_intent"\`
30
+ Produce one concrete intake intent with \`title\`, \`priority\`, \`template\`, \`charter\`, \`acceptance_contract\`, and \`vision_traceability\`. The intent must cite at least one existing \`VISION.md\` heading or goal it advances. Do not invent work outside the human-owned vision.
31
+
32
+ 2. \`kind: "vision_exhausted"\`
33
+ Classify every top-level \`VISION.md\` heading as \`complete\`, \`deferred\`, or \`out_of_scope\`, with a reason for each. Use this only when no next governed product increment can be justified from the configured sources.
34
+
35
+ Do not modify \`.planning/VISION.md\`. If you cannot cite \`VISION.md\` for a proposed intent, return \`vision_exhausted\` or a deferred classification instead of expanding scope.
36
+ `;
37
+
15
38
  const DEFAULT_AGENTS = {
16
39
  pm: {
17
40
  name: 'Product Manager',
@@ -906,6 +929,7 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
906
929
  const prompt = appendPromptOverride(basePrompt, template.prompt_overrides?.[roleId]);
907
930
  writeFileSync(join(dir, '.agentxchain', 'prompts', `${roleId}.md`), prompt);
908
931
  }
932
+ writeFileSync(join(dir, '.agentxchain', 'prompts', 'pm-idle-expansion.md'), PM_IDLE_EXPANSION_PROMPT);
909
933
 
910
934
  // Planning artifacts
911
935
  for (const artifact of buildGovernedPlanningArtifacts({
@@ -258,6 +258,7 @@ export async function executeGovernedRun(context, opts = {}) {
258
258
  // ── Track first-call for --role override ────────────────────────────────
259
259
  let firstSelectRole = true;
260
260
  let qaMissingCredentialsFallback = null;
261
+ const acceptedTurnResults = [];
261
262
 
262
263
  // ── Callbacks ───────────────────────────────────────────────────────────
263
264
  const callbacks = {
@@ -573,7 +574,15 @@ export async function executeGovernedRun(context, opts = {}) {
573
574
  return approved;
574
575
  },
575
576
 
576
- async afterAccept({ turn }) {
577
+ async afterAccept({ turn, acceptResult }) {
578
+ if (acceptResult) {
579
+ acceptedTurnResults.push({
580
+ turn_id: turn.turn_id,
581
+ accepted: acceptResult.accepted || null,
582
+ turn_result: acceptResult.validation?.turnResult || null,
583
+ state: acceptResult.state || null,
584
+ });
585
+ }
577
586
  if (!autoCheckpoint) {
578
587
  return { ok: true };
579
588
  }
@@ -688,7 +697,10 @@ export async function executeGovernedRun(context, opts = {}) {
688
697
  const successReasons = new Set(['completed', 'gate_held', 'caller_stopped', 'max_turns_reached']);
689
698
  return {
690
699
  exitCode: result.ok || successReasons.has(result.stop_reason) ? 0 : 1,
691
- result,
700
+ result: {
701
+ ...result,
702
+ accepted_turn_results: acceptedTurnResults,
703
+ },
692
704
  skipped: false,
693
705
  skipReason: null,
694
706
  provenance: provenance || null,
@@ -21,8 +21,8 @@ import {
21
21
  advanceContinuousRunOnce,
22
22
  resolveContinuousOptions,
23
23
  } from '../lib/continuous-run.js';
24
- import { resolveVisionPath } from '../lib/vision-reader.js';
25
- import { existsSync } from 'node:fs';
24
+ import { captureVisionHeadingsSnapshot, computeVisionContentSha, resolveVisionPath } from '../lib/vision-reader.js';
25
+ import { existsSync, readFileSync } from 'node:fs';
26
26
  import { randomUUID } from 'node:crypto';
27
27
 
28
28
  function loadScheduleContext() {
@@ -349,7 +349,7 @@ async function runDueSchedules(context, opts = {}) {
349
349
  // ---------------------------------------------------------------------------
350
350
 
351
351
  function isSessionTerminal(session) {
352
- return ['completed', 'idle_exit', 'failed', 'stopped'].includes(session?.status);
352
+ return ['completed', 'idle_exit', 'failed', 'stopped', 'vision_exhausted', 'vision_expansion_exhausted'].includes(session?.status);
353
353
  }
354
354
 
355
355
  function getContinuousEnabledScheduleIds(config) {
@@ -393,7 +393,7 @@ export function selectContinuousScheduleEntry(root, config, opts = {}) {
393
393
  return { id: dueEntry.id, schedule: config.schedules[dueEntry.id], due: dueEntry.due };
394
394
  }
395
395
 
396
- function createScheduleOwnedSession(schedule, scheduleId) {
396
+ function createScheduleOwnedSession(schedule, scheduleId, snapshotOpts = {}) {
397
397
  return {
398
398
  session_id: `cont-${randomUUID().slice(0, 8)}`,
399
399
  started_at: new Date().toISOString(),
@@ -410,6 +410,10 @@ function createScheduleOwnedSession(schedule, scheduleId) {
410
410
  per_session_max_usd: schedule.continuous.per_session_max_usd || null,
411
411
  cumulative_spent_usd: 0,
412
412
  budget_exhausted: false,
413
+ vision_headings_snapshot: snapshotOpts.visionHeadingsSnapshot || null,
414
+ vision_sha_at_snapshot: snapshotOpts.visionShaAtSnapshot || null,
415
+ expansion_iteration: 0,
416
+ _vision_stale_warned_shas: [],
413
417
  };
414
418
  }
415
419
 
@@ -453,7 +457,11 @@ async function advanceScheduleContinuousSession(context, entry, opts = {}) {
453
457
  return { ok: false, action: 'failed', reason: `VISION.md not found at ${absVision}` };
454
458
  }
455
459
 
456
- session = createScheduleOwnedSession(schedule, scheduleId);
460
+ const visionContent = readFileSync(absVision, 'utf8');
461
+ session = createScheduleOwnedSession(schedule, scheduleId, {
462
+ visionHeadingsSnapshot: captureVisionHeadingsSnapshot(visionContent),
463
+ visionShaAtSnapshot: computeVisionContentSha(visionContent),
464
+ });
457
465
  writeContinuousSession(root, session);
458
466
  log(chalk.cyan(`Started schedule-owned continuous session: ${session.session_id} (schedule: ${scheduleId})`));
459
467
 
@@ -467,13 +475,17 @@ async function advanceScheduleContinuousSession(context, entry, opts = {}) {
467
475
  }
468
476
 
469
477
  // Build contOpts from schedule continuous config
470
- const contOpts = {
471
- visionPath: contConfig.vision_path,
472
- maxRuns: contConfig.max_runs,
473
- maxIdleCycles: contConfig.max_idle_cycles,
474
- triageApproval: contConfig.triage_approval,
475
- perSessionMaxUsd: contConfig.per_session_max_usd || null,
476
- };
478
+ const contOpts = resolveContinuousOptions({ continuous: true }, {
479
+ ...config,
480
+ run_loop: {
481
+ ...(config.run_loop || {}),
482
+ continuous: {
483
+ ...(config.run_loop?.continuous || {}),
484
+ ...contConfig,
485
+ enabled: true,
486
+ },
487
+ },
488
+ });
477
489
 
478
490
  // Advance one step
479
491
  const step = await advanceContinuousRunOnce(context, session, contOpts, executeGovernedRun, log);
@@ -484,9 +496,14 @@ async function advanceScheduleContinuousSession(context, entry, opts = {}) {
484
496
  idle_exit: 'continuous_idle_exit',
485
497
  failed: 'continuous_failed',
486
498
  blocked: 'continuous_blocked',
499
+ vision_exhausted: 'continuous_vision_exhausted',
500
+ vision_expansion_exhausted: 'continuous_vision_expansion_exhausted',
487
501
  running: 'continuous_running',
488
502
  };
489
503
  let schedStatus = statusMap[step.status] || 'continuous_running';
504
+ if (step.action === 'idle_expansion_dispatched') {
505
+ schedStatus = 'continuous_running';
506
+ }
490
507
  if (step.action === 'session_budget_exhausted') {
491
508
  schedStatus = 'continuous_session_budget_exhausted';
492
509
  }