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.
- package/bin/agentxchain.js +1 -0
- package/package.json +1 -1
- package/src/commands/run.js +7 -0
- package/src/commands/schedule.js +8 -1
- package/src/commands/status.js +11 -0
- package/src/lib/continuous-run.js +36 -6
- package/src/lib/normalized-config.js +10 -0
package/bin/agentxchain.js
CHANGED
|
@@ -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
package/src/commands/run.js
CHANGED
|
@@ -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);
|
package/src/commands/schedule.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
package/src/commands/status.js
CHANGED
|
@@ -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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
|