agentxchain 2.155.60 → 2.155.61
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/continuous-run.js +137 -8
package/package.json
CHANGED
|
@@ -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
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
contOpts
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
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;
|
|
@@ -2079,6 +2204,10 @@ export async function executeContinuousRun(context, contOpts, executeGovernedRun
|
|
|
2079
2204
|
while (!stopping) {
|
|
2080
2205
|
const step = await advanceContinuousRunOnce(context, session, contOpts, executeGovernedRun, log);
|
|
2081
2206
|
|
|
2207
|
+
if (recoverPausedActiveContinuousSession(context, session, log, `post_step_${step.action || step.status || 'unknown'}`)) {
|
|
2208
|
+
continue;
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2082
2211
|
// Terminal states
|
|
2083
2212
|
if (step.status === 'completed' || step.status === 'idle_exit' || step.status === 'failed' || step.status === 'blocked' || step.status === 'stopped' || step.status === 'vision_exhausted' || step.status === 'vision_expansion_exhausted' || step.status === 'session_budget') {
|
|
2084
2213
|
const terminalMessage = describeContinuousTerminalStep(step, contOpts);
|