agentxchain 2.155.59 → 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
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);
|
|
@@ -1421,6 +1421,47 @@ export function normalizeTurnResult(tr, config, context = {}) {
|
|
|
1421
1421
|
}
|
|
1422
1422
|
}
|
|
1423
1423
|
|
|
1424
|
+
// ── BUG-106: auto-declare expected_exit_code when verification.status is "pass" ──
|
|
1425
|
+
// When verification.status is "pass" but some machine_evidence commands have
|
|
1426
|
+
// non-zero exit codes without expected_exit_code, the agent clearly intended
|
|
1427
|
+
// those failures (e.g. testing error handling). Auto-set expected_exit_code to
|
|
1428
|
+
// match exit_code so the Stage D validator passes. This is safe because the
|
|
1429
|
+
// agent explicitly declared the overall verification as passing.
|
|
1430
|
+
if (
|
|
1431
|
+
normalized.verification
|
|
1432
|
+
&& typeof normalized.verification === 'object'
|
|
1433
|
+
&& !Array.isArray(normalized.verification)
|
|
1434
|
+
&& normalized.verification.status === 'pass'
|
|
1435
|
+
&& Array.isArray(normalized.verification.machine_evidence)
|
|
1436
|
+
) {
|
|
1437
|
+
const patchedEvidence = [];
|
|
1438
|
+
let anyPatched = false;
|
|
1439
|
+
normalized.verification.machine_evidence.forEach((entry, index) => {
|
|
1440
|
+
if (
|
|
1441
|
+
entry
|
|
1442
|
+
&& typeof entry === 'object'
|
|
1443
|
+
&& typeof entry.exit_code === 'number'
|
|
1444
|
+
&& entry.exit_code !== 0
|
|
1445
|
+
&& !Number.isInteger(entry.expected_exit_code)
|
|
1446
|
+
) {
|
|
1447
|
+
patchedEvidence.push({ ...entry, expected_exit_code: entry.exit_code });
|
|
1448
|
+
anyPatched = true;
|
|
1449
|
+
corrections.push(`verification.machine_evidence[${index}].expected_exit_code: set to ${entry.exit_code} (verification.status is "pass")`);
|
|
1450
|
+
normalizationEvents.push({
|
|
1451
|
+
field: `verification.machine_evidence[${index}].expected_exit_code`,
|
|
1452
|
+
original_value: null,
|
|
1453
|
+
normalized_value: entry.exit_code,
|
|
1454
|
+
rationale: 'verification_pass_expected_exit_code_inferred',
|
|
1455
|
+
});
|
|
1456
|
+
} else {
|
|
1457
|
+
patchedEvidence.push(entry);
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
if (anyPatched) {
|
|
1461
|
+
normalized.verification = { ...normalized.verification, machine_evidence: patchedEvidence };
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1424
1465
|
// ── BUG-90: normalize missing artifact.type ─────────────────────────
|
|
1425
1466
|
if (
|
|
1426
1467
|
normalized.artifact
|