agentxchain 2.155.60 → 2.155.62

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.155.60",
3
+ "version": "2.155.62",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,6 +46,7 @@ import {
46
46
  import { getDispatchLogPath, getTurnStagingResultPath } from './turn-paths.js';
47
47
  import { reconcileOperatorHead } from './operator-commit-reconcile.js';
48
48
  import { getContinuityStatus } from './continuity-status.js';
49
+ import { resolveGovernedRole } from './role-resolution.js';
49
50
  import {
50
51
  archiveStaleIntentsForRun,
51
52
  formatLegacyIntentMigrationNotice,
@@ -132,6 +133,36 @@ function createSession(visionPath, maxRuns, maxIdleCycles, perSessionMaxUsd, cur
132
133
  };
133
134
  }
134
135
 
136
+ function canResumeExistingContinuousSession(session, contOpts) {
137
+ if (!session || typeof session !== 'object') return false;
138
+ if (!['paused', 'running'].includes(session.status)) return false;
139
+ if (session.owner_type === 'schedule') return false;
140
+ if (session.vision_path && session.vision_path !== contOpts.visionPath) return false;
141
+ if (contOpts.continueFrom && session.current_run_id && session.current_run_id !== contOpts.continueFrom) return false;
142
+ return true;
143
+ }
144
+
145
+ function resumeExistingContinuousSession(session, contOpts, initialRunId, snapshotOpts = {}) {
146
+ return {
147
+ ...session,
148
+ vision_path: session.vision_path || contOpts.visionPath,
149
+ max_runs: contOpts.maxRuns,
150
+ max_idle_cycles: contOpts.maxIdleCycles,
151
+ current_run_id: session.current_run_id || initialRunId || null,
152
+ status: session.status === 'paused' ? 'paused' : 'running',
153
+ per_session_max_usd: session.per_session_max_usd ?? contOpts.perSessionMaxUsd ?? null,
154
+ cumulative_spent_usd: session.cumulative_spent_usd || 0,
155
+ budget_exhausted: Boolean(session.budget_exhausted),
156
+ startup_reconciled_run_id: session.startup_reconciled_run_id || null,
157
+ vision_headings_snapshot: session.vision_headings_snapshot || snapshotOpts.visionHeadingsSnapshot || null,
158
+ vision_sha_at_snapshot: session.vision_sha_at_snapshot || snapshotOpts.visionShaAtSnapshot || null,
159
+ expansion_iteration: session.expansion_iteration ?? snapshotOpts.expansionIteration ?? 0,
160
+ _vision_stale_warned_shas: Array.isArray(session._vision_stale_warned_shas)
161
+ ? session._vision_stale_warned_shas
162
+ : [],
163
+ };
164
+ }
165
+
135
166
  function describeContinuousTerminalStep(step, contOpts) {
136
167
  if (step.action === 'max_runs_reached') {
137
168
  return `Max runs reached (${contOpts.maxRuns}). Stopping.`;
@@ -277,6 +308,59 @@ function getBlockedCategory(state) {
277
308
  return state?.blocked_reason?.category || null;
278
309
  }
279
310
 
311
+ const RECOVERABLE_ACTIVE_TURN_STATUSES = new Set(['assigned', 'dispatched', 'starting', 'running']);
312
+
313
+ function hasOnlyRecoverableActiveTurns(activeTurns = {}) {
314
+ const turns = Object.values(activeTurns || {});
315
+ if (turns.length === 0) return false;
316
+ return turns.every((turn) => RECOVERABLE_ACTIVE_TURN_STATUSES.has(turn?.status || 'assigned'));
317
+ }
318
+
319
+ export function isPausedContinuousSessionRecoverableActiveRun(session, state, config) {
320
+ if (!session || session.status !== 'paused') return false;
321
+ if (!state || state.status !== 'active') return false;
322
+ if (session.current_run_id && state.run_id && session.current_run_id !== state.run_id) return false;
323
+ if (state.blocked_on || state.blocked_reason || state.escalation) return false;
324
+ if (state.pending_phase_transition || state.pending_run_completion) return false;
325
+ if (state.queued_phase_transition || state.queued_run_completion) return false;
326
+ if (Object.keys(state.active_turns || {}).length > 0) {
327
+ return hasOnlyRecoverableActiveTurns(state.active_turns);
328
+ }
329
+
330
+ const resolved = resolveGovernedRole({ state, config });
331
+ return Boolean(resolved?.roleId && !resolved.error);
332
+ }
333
+
334
+ function isPausedActiveRunWaitingOnGovernance(session, state) {
335
+ if (!session || session.status !== 'paused') return false;
336
+ if (!state || state.status !== 'active') return false;
337
+ if (session.current_run_id && state.run_id && session.current_run_id !== state.run_id) return false;
338
+ return true;
339
+ }
340
+
341
+ function recoverPausedActiveContinuousSession(context, session, log = console.log, reason = 'paused_active_run') {
342
+ const latestSession = readContinuousSession(context.root) || session;
343
+ const state = loadProjectState(context.root, context.config);
344
+ if (!isPausedContinuousSessionRecoverableActiveRun(latestSession, state, context.config)) {
345
+ return false;
346
+ }
347
+
348
+ Object.assign(session, latestSession, { status: 'running' });
349
+ writeContinuousSession(context.root, session);
350
+ emitRunEvent(context.root, 'continuous_paused_active_run_recovered', {
351
+ run_id: state.run_id || session.current_run_id || null,
352
+ phase: state.phase || null,
353
+ status: state.status || 'active',
354
+ payload: {
355
+ session_id: session.session_id,
356
+ reason,
357
+ next_recommended_role: state.next_recommended_role || null,
358
+ },
359
+ });
360
+ log(`Continuous session was paused while run ${state.run_id || session.current_run_id || '(unknown)'} remained active; resuming next role dispatch.`);
361
+ return true;
362
+ }
363
+
280
364
  function writeGovernedState(root, state) {
281
365
  safeWriteJson(join(root, '.agentxchain', 'state.json'), state);
282
366
  }
@@ -1650,6 +1734,31 @@ export async function advanceContinuousRunOnce(context, session, contOpts, execu
1650
1734
  blocked_category: getBlockedCategory(governedState),
1651
1735
  };
1652
1736
  }
1737
+ if (isPausedContinuousSessionRecoverableActiveRun(session, governedState, context.config)) {
1738
+ session.status = 'running';
1739
+ writeContinuousSession(root, session);
1740
+ emitRunEvent(root, 'continuous_paused_active_run_recovered', {
1741
+ run_id: governedState.run_id || session.current_run_id || null,
1742
+ phase: governedState.phase || null,
1743
+ status: governedState.status || 'active',
1744
+ payload: {
1745
+ session_id: session.session_id,
1746
+ reason: 'advance_once_paused_active_run',
1747
+ next_recommended_role: governedState.next_recommended_role || null,
1748
+ },
1749
+ });
1750
+ log(`Paused continuous session has active unblocked run ${governedState.run_id || session.current_run_id || '(unknown)'}; resuming next role dispatch.`);
1751
+ } else if (isPausedActiveRunWaitingOnGovernance(session, governedState)) {
1752
+ writeContinuousSession(root, session);
1753
+ return {
1754
+ ok: true,
1755
+ status: 'blocked',
1756
+ action: 'paused_active_run_waiting',
1757
+ run_id: session.current_run_id || governedState.run_id || null,
1758
+ recovery_action: 'Review pending approvals, active turns, or role-resolution errors before resuming this paused continuous session.',
1759
+ blocked_category: 'paused_active_run_waiting',
1760
+ };
1761
+ }
1653
1762
  // Unblocked — resume by continuing the existing governed run directly.
1654
1763
  // Skip the intake pipeline: the run is already in progress, and startIntent
1655
1764
  // would reject because the governed state is active.
@@ -2057,15 +2166,31 @@ export async function executeContinuousRun(context, contOpts, executeGovernedRun
2057
2166
  // VISION.md unreadable — will fail at first advanceContinuousRunOnce anyway
2058
2167
  }
2059
2168
 
2060
- const session = createSession(
2061
- contOpts.visionPath,
2062
- contOpts.maxRuns,
2063
- contOpts.maxIdleCycles,
2064
- contOpts.perSessionMaxUsd,
2065
- initialRunId,
2066
- { visionHeadingsSnapshot, visionShaAtSnapshot },
2067
- );
2169
+ const existingSession = readContinuousSession(root);
2170
+ const snapshotOpts = { visionHeadingsSnapshot, visionShaAtSnapshot };
2171
+ const session = canResumeExistingContinuousSession(existingSession, contOpts)
2172
+ ? resumeExistingContinuousSession(existingSession, contOpts, initialRunId, snapshotOpts)
2173
+ : createSession(
2174
+ contOpts.visionPath,
2175
+ contOpts.maxRuns,
2176
+ contOpts.maxIdleCycles,
2177
+ contOpts.perSessionMaxUsd,
2178
+ initialRunId,
2179
+ snapshotOpts,
2180
+ );
2068
2181
  writeContinuousSession(root, session);
2182
+ if (existingSession && session.session_id === existingSession.session_id) {
2183
+ emitRunEvent(root, 'continuous_session_resumed', {
2184
+ run_id: session.current_run_id || null,
2185
+ phase: startupState?.phase || null,
2186
+ status: session.status,
2187
+ payload: {
2188
+ session_id: session.session_id,
2189
+ prior_status: existingSession.status || null,
2190
+ },
2191
+ });
2192
+ log(`Resuming existing continuous session ${session.session_id}.`);
2193
+ }
2069
2194
 
2070
2195
  // SIGINT handler
2071
2196
  let stopping = false;
@@ -2088,6 +2213,10 @@ export async function executeContinuousRun(context, contOpts, executeGovernedRun
2088
2213
  return { exitCode: step.ok ? 0 : 1, session };
2089
2214
  }
2090
2215
 
2216
+ if (recoverPausedActiveContinuousSession(context, session, log, `post_step_${step.action || step.status || 'unknown'}`)) {
2217
+ continue;
2218
+ }
2219
+
2091
2220
  // Non-terminal: sleep before next step
2092
2221
  if (!stopping) {
2093
2222
  const sleepMs = step.action === 'no_work_found' || step.action === 'waited_for_human'