agentxchain 2.118.0 → 2.119.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.
@@ -676,6 +676,7 @@ program
676
676
  .option('--max-runs <n>', 'Maximum consecutive governed runs in continuous mode (default: 100)', parseInt)
677
677
  .option('--poll-seconds <n>', 'Seconds between idle-detection cycles in continuous mode (default: 30)', parseInt)
678
678
  .option('--max-idle-cycles <n>', 'Stop after N consecutive idle cycles with no derivable work (default: 3)', parseInt)
679
+ .option('--session-budget <usd>', 'Cumulative session-level budget cap in USD for continuous mode', parseFloat)
679
680
  .action(runCommand);
680
681
 
681
682
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.118.0",
3
+ "version": "2.119.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -57,10 +57,17 @@ export async function runCommand(opts) {
57
57
  // Continuous vision-driven mode
58
58
  const contOpts = resolveContinuousOptions(opts, context.config);
59
59
  if (contOpts.enabled) {
60
+ if (contOpts.perSessionMaxUsd != null && (!Number.isFinite(contOpts.perSessionMaxUsd) || contOpts.perSessionMaxUsd <= 0)) {
61
+ console.log(chalk.red('--session-budget must be a finite number greater than 0'));
62
+ process.exit(1);
63
+ }
60
64
  console.log(chalk.cyan.bold('agentxchain run --continuous'));
61
65
  console.log(chalk.dim(` Vision: ${contOpts.visionPath}`));
62
66
  console.log(chalk.dim(` Max runs: ${contOpts.maxRuns}, Poll: ${contOpts.pollSeconds}s, Idle limit: ${contOpts.maxIdleCycles}`));
63
67
  console.log(chalk.dim(` Triage approval: ${contOpts.triageApproval}`));
68
+ if (contOpts.perSessionMaxUsd != null) {
69
+ console.log(chalk.dim(` Session budget: $${contOpts.perSessionMaxUsd.toFixed(2)}`));
70
+ }
64
71
  console.log('');
65
72
  const { exitCode } = await executeContinuousRun(context, contOpts, executeGovernedRun);
66
73
  process.exit(exitCode);
@@ -381,6 +381,9 @@ function createScheduleOwnedSession(schedule, scheduleId) {
381
381
  status: 'running',
382
382
  owner_type: 'schedule',
383
383
  owner_id: scheduleId,
384
+ per_session_max_usd: schedule.continuous.per_session_max_usd || null,
385
+ cumulative_spent_usd: 0,
386
+ budget_exhausted: false,
384
387
  };
385
388
  }
386
389
 
@@ -443,6 +446,7 @@ async function advanceScheduleContinuousSession(context, entry, opts = {}) {
443
446
  maxRuns: contConfig.max_runs,
444
447
  maxIdleCycles: contConfig.max_idle_cycles,
445
448
  triageApproval: contConfig.triage_approval,
449
+ perSessionMaxUsd: contConfig.per_session_max_usd || null,
446
450
  };
447
451
 
448
452
  // Advance one step
@@ -456,7 +460,10 @@ async function advanceScheduleContinuousSession(context, entry, opts = {}) {
456
460
  blocked: 'continuous_blocked',
457
461
  running: 'continuous_running',
458
462
  };
459
- const schedStatus = statusMap[step.status] || 'continuous_running';
463
+ let schedStatus = statusMap[step.status] || 'continuous_running';
464
+ if (step.action === 'session_budget_exhausted') {
465
+ schedStatus = 'continuous_session_budget_exhausted';
466
+ }
460
467
 
461
468
  updateScheduleState(root, config, scheduleId, (record) => ({
462
469
  ...record,
@@ -215,6 +215,17 @@ function renderGovernedStatus(context, opts) {
215
215
  if (continuousSession.idle_cycles > 0) {
216
216
  console.log(chalk.dim(` Idle cycles: ${continuousSession.idle_cycles}/${continuousSession.max_idle_cycles}`));
217
217
  }
218
+ if (continuousSession.per_session_max_usd != null) {
219
+ const spent = (continuousSession.cumulative_spent_usd || 0).toFixed(2);
220
+ const limit = continuousSession.per_session_max_usd.toFixed(2);
221
+ const pct = continuousSession.per_session_max_usd > 0
222
+ ? ((continuousSession.cumulative_spent_usd || 0) / continuousSession.per_session_max_usd * 100).toFixed(1)
223
+ : '0.0';
224
+ const budgetStr = continuousSession.budget_exhausted
225
+ ? chalk.red(`$${spent} / $${limit} (${pct}%) [EXHAUSTED]`)
226
+ : `$${spent} / $${limit} (${pct}%)`;
227
+ console.log(` Budget: ${budgetStr}`);
228
+ }
218
229
  console.log(chalk.dim(' ' + '─'.repeat(44)));
219
230
  console.log('');
220
231
  }
@@ -54,7 +54,7 @@ export function removeContinuousSession(root) {
54
54
  }
55
55
  }
56
56
 
57
- function createSession(visionPath, maxRuns, maxIdleCycles) {
57
+ function createSession(visionPath, maxRuns, maxIdleCycles, perSessionMaxUsd) {
58
58
  return {
59
59
  session_id: `cont-${randomUUID().slice(0, 8)}`,
60
60
  started_at: new Date().toISOString(),
@@ -66,9 +66,25 @@ function createSession(visionPath, maxRuns, maxIdleCycles) {
66
66
  current_run_id: null,
67
67
  current_vision_objective: null,
68
68
  status: 'running',
69
+ per_session_max_usd: perSessionMaxUsd || null,
70
+ cumulative_spent_usd: 0,
71
+ budget_exhausted: false,
69
72
  };
70
73
  }
71
74
 
75
+ function describeContinuousTerminalStep(step, contOpts) {
76
+ if (step.action === 'max_runs_reached') {
77
+ return `Max runs reached (${contOpts.maxRuns}). Stopping.`;
78
+ }
79
+ if (step.action === 'session_budget_exhausted') {
80
+ return 'Session budget exhausted. Stopping.';
81
+ }
82
+ if (step.status === 'idle_exit') {
83
+ return `All vision goals appear addressed (${contOpts.maxIdleCycles} consecutive idle cycles). Stopping.`;
84
+ }
85
+ return null;
86
+ }
87
+
72
88
  // ---------------------------------------------------------------------------
73
89
  // Intake queue check
74
90
  // ---------------------------------------------------------------------------
@@ -274,6 +290,7 @@ export function resolveContinuousOptions(opts, config) {
274
290
  maxIdleCycles: opts.maxIdleCycles ?? configCont.max_idle_cycles ?? 3,
275
291
  triageApproval: configCont.triage_approval ?? 'auto',
276
292
  cooldownSeconds: opts.cooldownSeconds ?? configCont.cooldown_seconds ?? 5,
293
+ perSessionMaxUsd: opts.sessionBudget ?? configCont.per_session_max_usd ?? null,
277
294
  };
278
295
  }
279
296
 
@@ -312,6 +329,16 @@ export async function advanceContinuousRunOnce(context, session, contOpts, execu
312
329
  return { ok: true, status: 'idle_exit', action: 'max_idle_reached', stop_reason: 'idle_exit' };
313
330
  }
314
331
 
332
+ // Session budget check (cumulative spend across all runs)
333
+ const sessionBudget = session.per_session_max_usd ?? contOpts.perSessionMaxUsd ?? null;
334
+ if (sessionBudget != null && (session.cumulative_spent_usd || 0) >= sessionBudget) {
335
+ session.status = 'completed';
336
+ session.budget_exhausted = true;
337
+ writeContinuousSession(root, session);
338
+ log(`Session budget exhausted: $${(session.cumulative_spent_usd || 0).toFixed(2)} spent of $${sessionBudget.toFixed(2)} limit.`);
339
+ return { ok: true, status: 'completed', action: 'session_budget_exhausted', stop_reason: 'session_budget' };
340
+ }
341
+
315
342
  // Validate vision file
316
343
  if (!existsSync(absVisionPath)) {
317
344
  session.status = 'failed';
@@ -390,6 +417,10 @@ export async function advanceContinuousRunOnce(context, session, contOpts, execu
390
417
  session.runs_completed += 1;
391
418
  session.current_run_id = execution.result?.state?.run_id || null;
392
419
 
420
+ // Accumulate cost from this run into the session total
421
+ const runSpentUsd = execution.result?.state?.budget_status?.spent_usd || 0;
422
+ session.cumulative_spent_usd = (session.cumulative_spent_usd || 0) + runSpentUsd;
423
+
393
424
  const stopReason = execution.result?.stop_reason;
394
425
  log(`Run ${session.runs_completed}/${contOpts.maxRuns} completed: ${stopReason || 'unknown'}`);
395
426
 
@@ -449,7 +480,7 @@ export async function executeContinuousRun(context, contOpts, executeGovernedRun
449
480
  return { exitCode: 1, session: null };
450
481
  }
451
482
 
452
- const session = createSession(contOpts.visionPath, contOpts.maxRuns, contOpts.maxIdleCycles);
483
+ const session = createSession(contOpts.visionPath, contOpts.maxRuns, contOpts.maxIdleCycles, contOpts.perSessionMaxUsd);
453
484
  writeContinuousSession(root, session);
454
485
 
455
486
  // SIGINT handler
@@ -466,10 +497,9 @@ export async function executeContinuousRun(context, contOpts, executeGovernedRun
466
497
 
467
498
  // Terminal states
468
499
  if (step.status === 'completed' || step.status === 'idle_exit' || step.status === 'failed' || step.status === 'blocked') {
469
- if (step.status === 'completed') {
470
- log(`Max runs reached (${contOpts.maxRuns}). Stopping.`);
471
- } else if (step.status === 'idle_exit') {
472
- log(`All vision goals appear addressed (${contOpts.maxIdleCycles} consecutive idle cycles). Stopping.`);
500
+ const terminalMessage = describeContinuousTerminalStep(step, contOpts);
501
+ if (terminalMessage) {
502
+ log(terminalMessage);
473
503
  }
474
504
  return { exitCode: step.ok ? 0 : 1, session };
475
505
  }
@@ -711,6 +711,13 @@ export function validateSchedulesConfig(schedules, roles) {
711
711
  if ('triage_approval' in cont && cont.triage_approval !== 'auto' && cont.triage_approval !== 'human') {
712
712
  errors.push(`Schedule "${scheduleId}": continuous.triage_approval must be "auto" or "human"`);
713
713
  }
714
+ if ('per_session_max_usd' in cont && cont.per_session_max_usd != null) {
715
+ if (typeof cont.per_session_max_usd !== 'number' || !Number.isFinite(cont.per_session_max_usd)) {
716
+ errors.push(`Schedule "${scheduleId}": continuous.per_session_max_usd must be a finite number when provided`);
717
+ } else if (cont.per_session_max_usd <= 0) {
718
+ errors.push(`Schedule "${scheduleId}": continuous.per_session_max_usd must be greater than 0 when provided`);
719
+ }
720
+ }
714
721
  }
715
722
  }
716
723
  }
@@ -1153,6 +1160,9 @@ function normalizeContinuousConfig(raw) {
1153
1160
  max_runs: Number.isInteger(raw.max_runs) && raw.max_runs >= 1 ? raw.max_runs : 50,
1154
1161
  max_idle_cycles: Number.isInteger(raw.max_idle_cycles) && raw.max_idle_cycles >= 1 ? raw.max_idle_cycles : 5,
1155
1162
  triage_approval: raw.triage_approval === 'human' ? 'human' : 'auto',
1163
+ per_session_max_usd: Number.isFinite(raw.per_session_max_usd) && raw.per_session_max_usd > 0
1164
+ ? raw.per_session_max_usd
1165
+ : null,
1156
1166
  };
1157
1167
  }
1158
1168