agentxchain 2.135.0 → 2.136.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 +10 -1
- package/package.json +4 -2
- package/scripts/release-preflight.sh +2 -0
- package/src/commands/connector.js +136 -0
- package/src/commands/mission.js +250 -103
- package/src/commands/restart.js +3 -0
- package/src/commands/resume.js +15 -0
- package/src/commands/step.js +12 -0
- package/src/lib/connector-validate.js +6 -0
- package/src/lib/context-section-parser.js +52 -0
- package/src/lib/continuous-run.js +60 -5
- package/src/lib/dispatch-bundle.js +14 -0
- package/src/lib/gate-evaluator.js +18 -1
- package/src/lib/governed-state.js +182 -52
- package/src/lib/intake.js +23 -52
- package/src/lib/intent-startup-migration.js +151 -0
- package/src/lib/normalized-config.js +5 -0
- package/src/lib/runtime-capabilities.js +101 -81
- package/src/lib/schemas/agentxchain-config.schema.json +391 -0
- package/src/lib/schemas/connector-capabilities-output.schema.json +104 -0
package/bin/agentxchain.js
CHANGED
|
@@ -123,7 +123,7 @@ import { historyCommand } from '../src/commands/history.js';
|
|
|
123
123
|
import { decisionsCommand } from '../src/commands/decisions.js';
|
|
124
124
|
import { diffCommand } from '../src/commands/diff.js';
|
|
125
125
|
import { eventsCommand } from '../src/commands/events.js';
|
|
126
|
-
import { connectorCheckCommand, connectorValidateCommand } from '../src/commands/connector.js';
|
|
126
|
+
import { connectorCapabilitiesCommand, connectorCheckCommand, connectorValidateCommand } from '../src/commands/connector.js';
|
|
127
127
|
import { scheduleDaemonCommand, scheduleListCommand, scheduleRunDueCommand, scheduleStatusCommand } from '../src/commands/schedule.js';
|
|
128
128
|
import { chainLatestCommand, chainListCommand, chainShowCommand } from '../src/commands/chain.js';
|
|
129
129
|
import { missionAttachChainCommand, missionBindCoordinatorCommand, missionListCommand, missionPlanApproveCommand, missionPlanAutopilotCommand, missionPlanCommand, missionPlanLaunchCommand, missionPlanListCommand, missionPlanShowCommand, missionShowCommand, missionStartCommand } from '../src/commands/mission.js';
|
|
@@ -299,6 +299,13 @@ connectorCmd
|
|
|
299
299
|
.option('--timeout <ms>', 'Per-probe timeout in milliseconds', '8000')
|
|
300
300
|
.action(connectorCheckCommand);
|
|
301
301
|
|
|
302
|
+
connectorCmd
|
|
303
|
+
.command('capabilities [runtime_id]')
|
|
304
|
+
.description('Show merged capability contract for a runtime or all runtimes (machine-readable handshake)')
|
|
305
|
+
.option('-j, --json', 'Output as JSON')
|
|
306
|
+
.option('--all', 'Show capabilities for all configured runtimes')
|
|
307
|
+
.action(connectorCapabilitiesCommand);
|
|
308
|
+
|
|
302
309
|
connectorCmd
|
|
303
310
|
.command('validate <runtime_id>')
|
|
304
311
|
.description('Dispatch one synthetic governed turn through a runtime and validate the staged turn result')
|
|
@@ -530,6 +537,8 @@ missionPlanCmd
|
|
|
530
537
|
.option('-m, --mission <mission_id>', 'Explicit mission ID')
|
|
531
538
|
.option('--max-waves <n>', 'Maximum number of dependency waves (default: 10)')
|
|
532
539
|
.option('--continue-on-failure', 'Skip failed workstreams and keep launching ready ones')
|
|
540
|
+
.option('--auto-retry', 'Coordinator-only: retry one retryable repo-local failure within the same autopilot session')
|
|
541
|
+
.option('--max-retries <n>', 'Coordinator-only: maximum auto-retries per workstream/repo pair in one autopilot session (default: 1)')
|
|
533
542
|
.option('--cooldown <seconds>', 'Pause between waves in seconds (default: 5)')
|
|
534
543
|
.option('--auto-approve', 'Auto-approve run gates during execution')
|
|
535
544
|
.option('-j, --json', 'Output as JSON')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentxchain",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.136.0",
|
|
4
4
|
"description": "CLI for AgentXchain — governed multi-agent software delivery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
".": "./bin/agentxchain.js",
|
|
11
11
|
"./adapter-interface": "./src/lib/adapter-interface.js",
|
|
12
12
|
"./runner-interface": "./src/lib/runner-interface.js",
|
|
13
|
-
"./run-loop": "./src/lib/run-loop.js"
|
|
13
|
+
"./run-loop": "./src/lib/run-loop.js",
|
|
14
|
+
"./schemas/agentxchain-config": "./src/lib/schemas/agentxchain-config.schema.json",
|
|
15
|
+
"./schemas/connector-capabilities-output": "./src/lib/schemas/connector-capabilities-output.schema.json"
|
|
14
16
|
},
|
|
15
17
|
"files": [
|
|
16
18
|
"bin/",
|
|
@@ -154,6 +154,8 @@ if [[ "$PUBLISH_GATE" -eq 1 ]]; then
|
|
|
154
154
|
test/release-identity-hardening.test.js
|
|
155
155
|
test/normalized-config.test.js
|
|
156
156
|
test/conformance.test.js
|
|
157
|
+
test/beta-scenario-emission-guard.test.js
|
|
158
|
+
test/claim-reality-preflight.test.js
|
|
157
159
|
test/beta-tester-scenarios/*.test.js
|
|
158
160
|
)
|
|
159
161
|
GATE_TEST_ARGS=()
|
|
@@ -3,6 +3,7 @@ import chalk from 'chalk';
|
|
|
3
3
|
import { loadProjectContext } from '../lib/config.js';
|
|
4
4
|
import { DEFAULT_VALIDATE_TIMEOUT_MS, validateConfiguredConnector } from '../lib/connector-validate.js';
|
|
5
5
|
import { DEFAULT_TIMEOUT_MS, probeConfiguredConnectors } from '../lib/connector-probe.js';
|
|
6
|
+
import { getRuntimeCapabilityContract, getRoleRuntimeCapabilityContract, getCapabilityDeclarationWarnings } from '../lib/runtime-capabilities.js';
|
|
6
7
|
|
|
7
8
|
function printJson(result, exitCode) {
|
|
8
9
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -192,6 +193,141 @@ function printValidateText(result, exitCode) {
|
|
|
192
193
|
process.exit(exitCode);
|
|
193
194
|
}
|
|
194
195
|
|
|
196
|
+
function buildCapabilityReport(runtimeId, runtime, roles) {
|
|
197
|
+
const mergedContract = getRuntimeCapabilityContract(runtime);
|
|
198
|
+
const declaredCapabilities = (runtime.capabilities && typeof runtime.capabilities === 'object' && !Array.isArray(runtime.capabilities))
|
|
199
|
+
? { ...runtime.capabilities }
|
|
200
|
+
: {};
|
|
201
|
+
const declarationWarnings = getCapabilityDeclarationWarnings(runtime);
|
|
202
|
+
|
|
203
|
+
const roleBindings = [];
|
|
204
|
+
for (const [roleId, role] of Object.entries(roles)) {
|
|
205
|
+
const boundRuntime = role.runtime_id || role.runtime;
|
|
206
|
+
if (boundRuntime !== runtimeId) continue;
|
|
207
|
+
const roleContract = getRoleRuntimeCapabilityContract(roleId, role, runtime);
|
|
208
|
+
roleBindings.push({
|
|
209
|
+
role_id: roleContract.role_id,
|
|
210
|
+
role_write_authority: roleContract.role_write_authority,
|
|
211
|
+
effective_write_path: roleContract.effective_write_path,
|
|
212
|
+
workflow_artifact_ownership: roleContract.workflow_artifact_ownership,
|
|
213
|
+
notes: roleContract.notes,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
runtime_id: runtimeId,
|
|
219
|
+
runtime_type: runtime.type || 'unknown',
|
|
220
|
+
declared_capabilities: declaredCapabilities,
|
|
221
|
+
merged_contract: mergedContract,
|
|
222
|
+
declaration_warnings: declarationWarnings,
|
|
223
|
+
role_bindings: roleBindings,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function printCapabilitiesText(report) {
|
|
228
|
+
console.log('');
|
|
229
|
+
console.log(chalk.bold(` ${report.runtime_id}`) + chalk.dim(` (${report.runtime_type})`));
|
|
230
|
+
console.log(chalk.dim(' ' + '-'.repeat(44)));
|
|
231
|
+
|
|
232
|
+
const c = report.merged_contract;
|
|
233
|
+
console.log(` ${chalk.dim('Transport:')} ${c.transport}`);
|
|
234
|
+
console.log(` ${chalk.dim('Write files:')} ${c.can_write_files}`);
|
|
235
|
+
console.log(` ${chalk.dim('Proposals:')} ${c.proposal_support}`);
|
|
236
|
+
console.log(` ${chalk.dim('Artifact ownership:')} ${c.workflow_artifact_ownership}`);
|
|
237
|
+
console.log(` ${chalk.dim('Local binary:')} ${c.requires_local_binary ? 'yes' : 'no'}`);
|
|
238
|
+
|
|
239
|
+
if (Object.keys(report.declared_capabilities).length > 0) {
|
|
240
|
+
console.log('');
|
|
241
|
+
console.log(` ${chalk.dim('Declared overrides:')}`);
|
|
242
|
+
for (const [k, v] of Object.entries(report.declared_capabilities)) {
|
|
243
|
+
console.log(` ${k}: ${v}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (report.declaration_warnings.length > 0) {
|
|
248
|
+
console.log('');
|
|
249
|
+
for (const w of report.declaration_warnings) {
|
|
250
|
+
console.log(` ${chalk.yellow('!')} ${w}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (report.role_bindings.length > 0) {
|
|
255
|
+
console.log('');
|
|
256
|
+
console.log(` ${chalk.dim('Role bindings:')}`);
|
|
257
|
+
for (const rb of report.role_bindings) {
|
|
258
|
+
const badge = rb.effective_write_path.startsWith('invalid') ? chalk.red('INVALID') : chalk.green('OK');
|
|
259
|
+
console.log(` ${badge} ${rb.role_id} (${rb.role_write_authority}) -> ${rb.effective_write_path}`);
|
|
260
|
+
for (const note of rb.notes) {
|
|
261
|
+
console.log(` ${chalk.dim(note)}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export async function connectorCapabilitiesCommand(runtimeId, options = {}) {
|
|
268
|
+
const context = loadProjectContext();
|
|
269
|
+
if (!context) {
|
|
270
|
+
const payload = { error: 'No governed agentxchain.json found.' };
|
|
271
|
+
if (options.json) { printJson(payload, 2); return; }
|
|
272
|
+
console.error(chalk.red('No governed agentxchain.json found. Run this inside a governed project.'));
|
|
273
|
+
process.exit(2);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const config = context.config;
|
|
277
|
+
const runtimes = config.runtimes || {};
|
|
278
|
+
const roles = config.roles || {};
|
|
279
|
+
|
|
280
|
+
if (options.all) {
|
|
281
|
+
const reports = [];
|
|
282
|
+
for (const [id, runtime] of Object.entries(runtimes)) {
|
|
283
|
+
reports.push(buildCapabilityReport(id, runtime, roles));
|
|
284
|
+
}
|
|
285
|
+
const payload = { runtimes: reports };
|
|
286
|
+
if (options.json) { printJson(payload, 0); return; }
|
|
287
|
+
|
|
288
|
+
console.log('');
|
|
289
|
+
console.log(chalk.bold(' AgentXchain Connector Capabilities'));
|
|
290
|
+
console.log(chalk.dim(' ' + '='.repeat(44)));
|
|
291
|
+
if (reports.length === 0) {
|
|
292
|
+
console.log(' No runtimes configured.');
|
|
293
|
+
} else {
|
|
294
|
+
for (const r of reports) { printCapabilitiesText(r); }
|
|
295
|
+
}
|
|
296
|
+
console.log('');
|
|
297
|
+
process.exit(0);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!runtimeId) {
|
|
301
|
+
const payload = { error: 'Runtime ID required. Use --all to list all runtimes.', available_runtimes: Object.keys(runtimes) };
|
|
302
|
+
if (options.json) { printJson(payload, 2); return; }
|
|
303
|
+
console.error(chalk.red('Runtime ID required. Use --all to list all runtimes.'));
|
|
304
|
+
if (Object.keys(runtimes).length > 0) {
|
|
305
|
+
console.error(chalk.dim(`Available: ${Object.keys(runtimes).join(', ')}`));
|
|
306
|
+
}
|
|
307
|
+
process.exit(2);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!runtimes[runtimeId]) {
|
|
311
|
+
const payload = { error: `Runtime "${runtimeId}" not found.`, available_runtimes: Object.keys(runtimes) };
|
|
312
|
+
if (options.json) { printJson(payload, 2); return; }
|
|
313
|
+
console.error(chalk.red(`Runtime "${runtimeId}" not found.`));
|
|
314
|
+
if (Object.keys(runtimes).length > 0) {
|
|
315
|
+
console.error(chalk.dim(`Available: ${Object.keys(runtimes).join(', ')}`));
|
|
316
|
+
}
|
|
317
|
+
process.exit(2);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const report = buildCapabilityReport(runtimeId, runtimes[runtimeId], roles);
|
|
321
|
+
if (options.json) { printJson(report, 0); return; }
|
|
322
|
+
|
|
323
|
+
console.log('');
|
|
324
|
+
console.log(chalk.bold(' AgentXchain Connector Capabilities'));
|
|
325
|
+
console.log(chalk.dim(' ' + '='.repeat(44)));
|
|
326
|
+
printCapabilitiesText(report);
|
|
327
|
+
console.log('');
|
|
328
|
+
process.exit(0);
|
|
329
|
+
}
|
|
330
|
+
|
|
195
331
|
export async function connectorValidateCommand(runtimeId, options = {}) {
|
|
196
332
|
const context = loadProjectContext();
|
|
197
333
|
if (!context) {
|
package/src/commands/mission.js
CHANGED
|
@@ -190,6 +190,121 @@ function buildCoordinatorProjectionWarning(message) {
|
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
function formatAutoRetryBudgetError(workstreamId, repoId, maxRetries) {
|
|
194
|
+
return `Auto-retry budget exhausted for ${workstreamId}/${repoId} (max ${maxRetries} per autopilot session).`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function executeCoordinatorRetryRun(root, mission, workstreamId, coordinatorConfig, opts = {}) {
|
|
198
|
+
const retry = retryCoordinatorWorkstream(
|
|
199
|
+
root,
|
|
200
|
+
mission,
|
|
201
|
+
opts.planId,
|
|
202
|
+
workstreamId,
|
|
203
|
+
coordinatorConfig,
|
|
204
|
+
{
|
|
205
|
+
reason: opts.reason || `mission plan retry ${workstreamId}`,
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
if (!retry.ok) {
|
|
209
|
+
return { ok: false, error: retry.error, plan: opts.plan || null };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const executor = opts._executeGovernedRun || executeGovernedRun;
|
|
213
|
+
const childLog = opts.json ? noop : (opts._log || console.log);
|
|
214
|
+
const repoContext = loadProjectContext(retry.retryResult.repo_path);
|
|
215
|
+
if (!repoContext) {
|
|
216
|
+
return {
|
|
217
|
+
ok: false,
|
|
218
|
+
error: `Cannot load project context for retried repo at ${retry.retryResult.repo_path}.`,
|
|
219
|
+
plan: retry.plan,
|
|
220
|
+
retryResult: retry.retryResult,
|
|
221
|
+
launchRecord: retry.launchRecord,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const runOpts = {
|
|
226
|
+
autoApprove: !!opts.autoApprove,
|
|
227
|
+
log: childLog,
|
|
228
|
+
provenance: {
|
|
229
|
+
trigger: opts.trigger || 'manual',
|
|
230
|
+
created_by: 'operator',
|
|
231
|
+
trigger_reason: opts.triggerReasonPrefix
|
|
232
|
+
? `${opts.triggerReasonPrefix} repo:${retry.retryResult.repo_id}`
|
|
233
|
+
: `mission:${mission.mission_id} workstream:${workstreamId} coordinator-retry:${retry.retryResult.repo_id}`,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const retryWarnings = [];
|
|
238
|
+
let execution;
|
|
239
|
+
try {
|
|
240
|
+
execution = await executor(repoContext, runOpts);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
const syncedRetryFailure = synchronizeCoordinatorPlanState(root, mission, retry.plan);
|
|
243
|
+
return {
|
|
244
|
+
ok: false,
|
|
245
|
+
error: `Coordinator retry execution failed: ${error.message}`,
|
|
246
|
+
plan: syncedRetryFailure.ok ? syncedRetryFailure.plan : retry.plan,
|
|
247
|
+
retryResult: retry.retryResult,
|
|
248
|
+
launchRecord: retry.launchRecord,
|
|
249
|
+
warnings: retryWarnings,
|
|
250
|
+
reconciliation_required: false,
|
|
251
|
+
exit_code: 1,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if ((execution?.exitCode ?? 0) === 0) {
|
|
256
|
+
const projection = projectAcceptedCoordinatorTurn(
|
|
257
|
+
mission.coordinator.workspace_path,
|
|
258
|
+
coordinatorConfig,
|
|
259
|
+
retry.retryResult.repo_id,
|
|
260
|
+
retry.retryResult.reissued_turn_id,
|
|
261
|
+
workstreamId,
|
|
262
|
+
loadCoordinatorState(mission.coordinator.workspace_path),
|
|
263
|
+
);
|
|
264
|
+
if (!projection.ok) {
|
|
265
|
+
const warning = buildCoordinatorProjectionWarning(projection.error);
|
|
266
|
+
retryWarnings.push(warning);
|
|
267
|
+
console.error(chalk.yellow(`Coordinator retry projection warning: ${warning.message}`));
|
|
268
|
+
emitRunEvent(mission.coordinator.workspace_path, 'coordinator_retry_projection_warning', {
|
|
269
|
+
run_id: mission.coordinator.super_run_id,
|
|
270
|
+
phase: coordinatorConfig?.workstreams?.[workstreamId]?.phase || null,
|
|
271
|
+
status: 'active',
|
|
272
|
+
payload: {
|
|
273
|
+
workstream_id: workstreamId,
|
|
274
|
+
repo_id: retry.retryResult.repo_id,
|
|
275
|
+
reissued_turn_id: retry.retryResult.reissued_turn_id,
|
|
276
|
+
warning_code: warning.code,
|
|
277
|
+
warning_message: warning.message,
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const syncedRetry = synchronizeCoordinatorPlanState(root, mission, retry.plan);
|
|
284
|
+
const retriedPlan = syncedRetry.ok ? syncedRetry.plan : retry.plan;
|
|
285
|
+
const retriedWorkstream = retriedPlan.workstreams.find((ws) => ws.workstream_id === workstreamId);
|
|
286
|
+
const retriedLaunchRecord = retriedPlan.launch_records.find(
|
|
287
|
+
(record) => record.workstream_id === workstreamId && record.dispatch_mode === 'coordinator',
|
|
288
|
+
) || retry.launchRecord;
|
|
289
|
+
const retryCount = retriedLaunchRecord?.repo_dispatches?.filter(
|
|
290
|
+
(dispatch) => dispatch.repo_id === retry.retryResult.repo_id && dispatch.is_retry,
|
|
291
|
+
).length || 0;
|
|
292
|
+
|
|
293
|
+
const success = (execution?.exitCode ?? 0) === 0;
|
|
294
|
+
return {
|
|
295
|
+
ok: success,
|
|
296
|
+
error: success ? null : `Coordinator retry execution ended with exit code ${execution?.exitCode ?? 1}.`,
|
|
297
|
+
plan: retriedPlan,
|
|
298
|
+
workstream: retriedWorkstream,
|
|
299
|
+
launchRecord: retriedLaunchRecord,
|
|
300
|
+
retryResult: retry.retryResult,
|
|
301
|
+
warnings: retryWarnings,
|
|
302
|
+
reconciliation_required: retryWarnings.length > 0,
|
|
303
|
+
exit_code: execution?.exitCode ?? 0,
|
|
304
|
+
retry_count: retryCount,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
193
308
|
export async function missionListCommand(opts) {
|
|
194
309
|
const root = findProjectRoot(opts.dir || process.cwd());
|
|
195
310
|
if (!root) {
|
|
@@ -608,100 +723,28 @@ export async function missionPlanLaunchCommand(planTarget, opts) {
|
|
|
608
723
|
}
|
|
609
724
|
|
|
610
725
|
if (opts.retry) {
|
|
611
|
-
const
|
|
726
|
+
const retryExecution = await executeCoordinatorRetryRun(
|
|
612
727
|
root,
|
|
613
728
|
mission,
|
|
614
|
-
plan.plan_id,
|
|
615
729
|
opts.workstream,
|
|
616
730
|
coordinatorConfigResult.config,
|
|
617
731
|
{
|
|
732
|
+
...opts,
|
|
733
|
+
planId: plan.plan_id,
|
|
734
|
+
plan,
|
|
618
735
|
reason: `mission plan retry ${opts.workstream}`,
|
|
619
|
-
},
|
|
620
|
-
);
|
|
621
|
-
if (!retry.ok) {
|
|
622
|
-
console.error(chalk.red(retry.error));
|
|
623
|
-
process.exit(1);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
const executor = opts._executeGovernedRun || executeGovernedRun;
|
|
627
|
-
const childLog = opts.json ? noop : (opts._log || console.log);
|
|
628
|
-
const repoContext = loadProjectContext(retry.retryResult.repo_path);
|
|
629
|
-
if (!repoContext) {
|
|
630
|
-
console.error(chalk.red(`Cannot load project context for retried repo at ${retry.retryResult.repo_path}.`));
|
|
631
|
-
process.exit(1);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
const runOpts = {
|
|
635
|
-
autoApprove: !!opts.autoApprove,
|
|
636
|
-
log: childLog,
|
|
637
|
-
provenance: {
|
|
638
736
|
trigger: 'manual',
|
|
639
|
-
|
|
640
|
-
trigger_reason: `mission:${mission.mission_id} workstream:${opts.workstream} coordinator-retry:${retry.retryResult.repo_id}`,
|
|
737
|
+
triggerReasonPrefix: `mission:${mission.mission_id} workstream:${opts.workstream} coordinator-retry`,
|
|
641
738
|
},
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const retryWarnings = [];
|
|
646
|
-
try {
|
|
647
|
-
execution = await executor(repoContext, runOpts);
|
|
648
|
-
} catch (error) {
|
|
649
|
-
const syncedRetryFailure = synchronizeCoordinatorPlanState(root, mission, retry.plan);
|
|
650
|
-
console.error(chalk.red(`Coordinator retry execution failed: ${error.message}`));
|
|
651
|
-
if (opts.json) {
|
|
652
|
-
console.log(JSON.stringify({
|
|
653
|
-
dispatch_mode: 'coordinator',
|
|
654
|
-
retry: true,
|
|
655
|
-
mission_id: mission.mission_id,
|
|
656
|
-
plan_id: retry.plan.plan_id,
|
|
657
|
-
workstream_id: opts.workstream,
|
|
658
|
-
repo_id: retry.retryResult.repo_id,
|
|
659
|
-
retried_repo_turn_id: retry.retryResult.failed_turn_id,
|
|
660
|
-
repo_turn_id: retry.retryResult.reissued_turn_id,
|
|
661
|
-
workstream_status: syncedRetryFailure.ok
|
|
662
|
-
? syncedRetryFailure.plan.workstreams.find((ws) => ws.workstream_id === opts.workstream)?.launch_status || 'needs_attention'
|
|
663
|
-
: 'needs_attention',
|
|
664
|
-
launch_record: retry.launchRecord,
|
|
665
|
-
error: error.message,
|
|
666
|
-
}, null, 2));
|
|
667
|
-
}
|
|
739
|
+
);
|
|
740
|
+
if (!retryExecution.retryResult) {
|
|
741
|
+
console.error(chalk.red(retryExecution.error));
|
|
668
742
|
process.exit(1);
|
|
669
743
|
}
|
|
670
744
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
coordinatorConfigResult.config,
|
|
675
|
-
retry.retryResult.repo_id,
|
|
676
|
-
retry.retryResult.reissued_turn_id,
|
|
677
|
-
opts.workstream,
|
|
678
|
-
loadCoordinatorState(mission.coordinator.workspace_path),
|
|
679
|
-
);
|
|
680
|
-
if (!projection.ok) {
|
|
681
|
-
const warning = buildCoordinatorProjectionWarning(projection.error);
|
|
682
|
-
retryWarnings.push(warning);
|
|
683
|
-
console.error(chalk.yellow(`Coordinator retry projection warning: ${warning.message}`));
|
|
684
|
-
emitRunEvent(mission.coordinator.workspace_path, 'coordinator_retry_projection_warning', {
|
|
685
|
-
run_id: mission.coordinator.super_run_id,
|
|
686
|
-
phase: coordinatorConfigResult.config?.workstreams?.[opts.workstream]?.phase || null,
|
|
687
|
-
status: 'active',
|
|
688
|
-
payload: {
|
|
689
|
-
workstream_id: opts.workstream,
|
|
690
|
-
repo_id: retry.retryResult.repo_id,
|
|
691
|
-
reissued_turn_id: retry.retryResult.reissued_turn_id,
|
|
692
|
-
warning_code: warning.code,
|
|
693
|
-
warning_message: warning.message,
|
|
694
|
-
},
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
const syncedRetry = synchronizeCoordinatorPlanState(root, mission, retry.plan);
|
|
700
|
-
const retriedPlan = syncedRetry.ok ? syncedRetry.plan : retry.plan;
|
|
701
|
-
const retriedWorkstream = retriedPlan.workstreams.find((ws) => ws.workstream_id === opts.workstream);
|
|
702
|
-
const retriedLaunchRecord = retriedPlan.launch_records.find(
|
|
703
|
-
(record) => record.workstream_id === opts.workstream && record.dispatch_mode === 'coordinator',
|
|
704
|
-
) || retry.launchRecord;
|
|
745
|
+
const retriedPlan = retryExecution.plan;
|
|
746
|
+
const retriedWorkstream = retryExecution.workstream;
|
|
747
|
+
const retriedLaunchRecord = retryExecution.launchRecord;
|
|
705
748
|
|
|
706
749
|
if (opts.json) {
|
|
707
750
|
console.log(JSON.stringify({
|
|
@@ -711,41 +754,41 @@ export async function missionPlanLaunchCommand(planTarget, opts) {
|
|
|
711
754
|
plan_id: retriedPlan.plan_id,
|
|
712
755
|
workstream_id: opts.workstream,
|
|
713
756
|
super_run_id: mission.coordinator.super_run_id,
|
|
714
|
-
repo_id:
|
|
715
|
-
retried_repo_turn_id:
|
|
716
|
-
repo_turn_id:
|
|
717
|
-
role:
|
|
718
|
-
bundle_path:
|
|
719
|
-
context_ref:
|
|
757
|
+
repo_id: retryExecution.retryResult.repo_id,
|
|
758
|
+
retried_repo_turn_id: retryExecution.retryResult.failed_turn_id,
|
|
759
|
+
repo_turn_id: retryExecution.retryResult.reissued_turn_id,
|
|
760
|
+
role: retryExecution.retryResult.role,
|
|
761
|
+
bundle_path: retryExecution.retryResult.bundle_path,
|
|
762
|
+
context_ref: retryExecution.retryResult.context_ref,
|
|
720
763
|
workstream_status: retriedWorkstream?.launch_status || 'launched',
|
|
721
764
|
launch_record: retriedLaunchRecord,
|
|
722
|
-
exit_code:
|
|
723
|
-
warnings:
|
|
724
|
-
reconciliation_required:
|
|
765
|
+
exit_code: retryExecution.exit_code ?? 0,
|
|
766
|
+
warnings: retryExecution.warnings || [],
|
|
767
|
+
reconciliation_required: retryExecution.reconciliation_required || false,
|
|
725
768
|
}, null, 2));
|
|
726
|
-
if (
|
|
727
|
-
process.exit(
|
|
769
|
+
if (!retryExecution.ok) {
|
|
770
|
+
process.exit(retryExecution.exit_code || 1);
|
|
728
771
|
}
|
|
729
772
|
return;
|
|
730
773
|
}
|
|
731
774
|
|
|
732
|
-
console.log(chalk.green(`Retried coordinator workstream ${chalk.bold(opts.workstream)} in ${chalk.bold(
|
|
775
|
+
console.log(chalk.green(`Retried coordinator workstream ${chalk.bold(opts.workstream)} in ${chalk.bold(retryExecution.retryResult.repo_id)}`));
|
|
733
776
|
console.log('');
|
|
734
777
|
console.log(chalk.dim(` Mission: ${mission.mission_id}`));
|
|
735
778
|
console.log(chalk.dim(` Plan: ${retriedPlan.plan_id}`));
|
|
736
779
|
console.log(chalk.dim(` Super Run: ${mission.coordinator.super_run_id}`));
|
|
737
|
-
console.log(chalk.dim(` Repo: ${
|
|
738
|
-
console.log(chalk.dim(` Old Turn: ${
|
|
739
|
-
console.log(chalk.dim(` New Turn: ${
|
|
780
|
+
console.log(chalk.dim(` Repo: ${retryExecution.retryResult.repo_id}`));
|
|
781
|
+
console.log(chalk.dim(` Old Turn: ${retryExecution.retryResult.failed_turn_id}`));
|
|
782
|
+
console.log(chalk.dim(` New Turn: ${retryExecution.retryResult.reissued_turn_id}`));
|
|
740
783
|
console.log(chalk.dim(` Workstream: ${retriedWorkstream?.launch_status || 'launched'}`));
|
|
741
|
-
if (
|
|
742
|
-
console.log(chalk.yellow(` Warning: ${
|
|
784
|
+
if ((retryExecution.warnings || []).length > 0) {
|
|
785
|
+
console.log(chalk.yellow(` Warning: ${retryExecution.warnings[0].message}`));
|
|
743
786
|
}
|
|
744
787
|
console.log('');
|
|
745
788
|
renderPlan(retriedPlan);
|
|
746
|
-
if (
|
|
747
|
-
console.error(chalk.red(
|
|
748
|
-
process.exit(
|
|
789
|
+
if (!retryExecution.ok) {
|
|
790
|
+
console.error(chalk.red(retryExecution.error));
|
|
791
|
+
process.exit(retryExecution.exit_code || 1);
|
|
749
792
|
}
|
|
750
793
|
return;
|
|
751
794
|
}
|
|
@@ -1119,6 +1162,11 @@ export async function missionPlanAutopilotCommand(planTarget, opts) {
|
|
|
1119
1162
|
process.exit(1);
|
|
1120
1163
|
}
|
|
1121
1164
|
|
|
1165
|
+
if (opts.autoRetry && !mission.coordinator) {
|
|
1166
|
+
console.error(chalk.red('--auto-retry is only supported for coordinator-bound missions.'));
|
|
1167
|
+
process.exit(1);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1122
1170
|
if (mission.coordinator) {
|
|
1123
1171
|
return coordinatorAutopilot(planTarget, opts, context, mission);
|
|
1124
1172
|
}
|
|
@@ -1766,14 +1814,25 @@ async function coordinatorAutopilot(planTarget, opts, context, mission) {
|
|
|
1766
1814
|
|
|
1767
1815
|
const maxWaves = Math.max(1, parseInt(opts.maxWaves, 10) || 10);
|
|
1768
1816
|
const cooldownSeconds = Math.max(0, parseInt(opts.cooldown, 10) || 5);
|
|
1817
|
+
const autoRetryEnabled = !!opts.autoRetry;
|
|
1818
|
+
const parsedMaxRetries = Number.parseInt(opts.maxRetries, 10);
|
|
1819
|
+
const maxRetries = autoRetryEnabled
|
|
1820
|
+
? (Number.isFinite(parsedMaxRetries) ? parsedMaxRetries : 1)
|
|
1821
|
+
: 0;
|
|
1822
|
+
if (autoRetryEnabled && maxRetries < 1) {
|
|
1823
|
+
console.error(chalk.red('--max-retries must be >= 1 when --auto-retry is set.'));
|
|
1824
|
+
process.exit(1);
|
|
1825
|
+
}
|
|
1769
1826
|
const sleep = opts._sleep || ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
1770
1827
|
|
|
1771
1828
|
const waves = [];
|
|
1772
1829
|
let totalLaunched = 0;
|
|
1773
1830
|
let totalCompleted = 0;
|
|
1774
1831
|
let totalFailed = 0;
|
|
1832
|
+
let totalRetries = 0;
|
|
1775
1833
|
let terminalReason = null;
|
|
1776
1834
|
let interrupted = false;
|
|
1835
|
+
const retryCounts = new Map();
|
|
1777
1836
|
|
|
1778
1837
|
const onSigint = () => { interrupted = true; };
|
|
1779
1838
|
process.on('SIGINT', onSigint);
|
|
@@ -1848,6 +1907,92 @@ async function coordinatorAutopilot(planTarget, opts, context, mission) {
|
|
|
1848
1907
|
totalLaunched++;
|
|
1849
1908
|
|
|
1850
1909
|
if (result.status === 'needs_attention') {
|
|
1910
|
+
if (autoRetryEnabled && result.repo_id) {
|
|
1911
|
+
const retryKey = `${ws.workstream_id}:${result.repo_id}`;
|
|
1912
|
+
const usedRetries = retryCounts.get(retryKey) || 0;
|
|
1913
|
+
|
|
1914
|
+
if (usedRetries < maxRetries) {
|
|
1915
|
+
retryCounts.set(retryKey, usedRetries + 1);
|
|
1916
|
+
|
|
1917
|
+
const retryExecution = await executeCoordinatorRetryRun(
|
|
1918
|
+
root,
|
|
1919
|
+
mission,
|
|
1920
|
+
ws.workstream_id,
|
|
1921
|
+
coord.config,
|
|
1922
|
+
{
|
|
1923
|
+
...opts,
|
|
1924
|
+
planId: plan.plan_id,
|
|
1925
|
+
plan,
|
|
1926
|
+
trigger: 'autopilot',
|
|
1927
|
+
triggerReasonPrefix: `mission:${mission.mission_id} workstream:${ws.workstream_id} autopilot:wave-${waveNum} auto-retry`,
|
|
1928
|
+
reason: `mission autopilot auto-retry ${ws.workstream_id}/${result.repo_id}`,
|
|
1929
|
+
},
|
|
1930
|
+
);
|
|
1931
|
+
|
|
1932
|
+
if (retryExecution.plan) {
|
|
1933
|
+
plan = retryExecution.plan;
|
|
1934
|
+
}
|
|
1935
|
+
if (retryExecution.retryResult) {
|
|
1936
|
+
totalRetries++;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
if (retryExecution.ok) {
|
|
1940
|
+
totalCompleted++;
|
|
1941
|
+
waveResults.push({
|
|
1942
|
+
workstream_id: ws.workstream_id,
|
|
1943
|
+
status: 'dispatched',
|
|
1944
|
+
repo_id: retryExecution.retryResult.repo_id,
|
|
1945
|
+
turn_id: retryExecution.retryResult.reissued_turn_id,
|
|
1946
|
+
retried: true,
|
|
1947
|
+
retry_count: retryExecution.retry_count,
|
|
1948
|
+
retried_repo_turn_id: retryExecution.retryResult.failed_turn_id,
|
|
1949
|
+
warnings: retryExecution.warnings || [],
|
|
1950
|
+
reconciliation_required: retryExecution.reconciliation_required || false,
|
|
1951
|
+
});
|
|
1952
|
+
if (!opts.json) {
|
|
1953
|
+
console.log(chalk.green(`→ ${retryExecution.retryResult.repo_id} retried ✓`));
|
|
1954
|
+
}
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
waveHadFailure = true;
|
|
1959
|
+
totalFailed++;
|
|
1960
|
+
waveResults.push({
|
|
1961
|
+
workstream_id: ws.workstream_id,
|
|
1962
|
+
status: 'needs_attention',
|
|
1963
|
+
repo_id: retryExecution.retryResult?.repo_id || result.repo_id,
|
|
1964
|
+
turn_id: retryExecution.retryResult?.reissued_turn_id || result.turn_id,
|
|
1965
|
+
retried: Boolean(retryExecution.retryResult),
|
|
1966
|
+
retry_count: retryExecution.retry_count || usedRetries + 1,
|
|
1967
|
+
retried_repo_turn_id: retryExecution.retryResult?.failed_turn_id || result.turn_id,
|
|
1968
|
+
error: retryExecution.error,
|
|
1969
|
+
warnings: retryExecution.warnings || [],
|
|
1970
|
+
reconciliation_required: retryExecution.reconciliation_required || false,
|
|
1971
|
+
});
|
|
1972
|
+
if (!opts.json) {
|
|
1973
|
+
console.log(chalk.red(`→ ${result.repo_id} auto-retry failed ✗ (${retryExecution.error})`));
|
|
1974
|
+
}
|
|
1975
|
+
continue;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
waveHadFailure = true;
|
|
1979
|
+
totalFailed++;
|
|
1980
|
+
const budgetError = formatAutoRetryBudgetError(ws.workstream_id, result.repo_id, maxRetries);
|
|
1981
|
+
waveResults.push({
|
|
1982
|
+
workstream_id: ws.workstream_id,
|
|
1983
|
+
status: 'needs_attention',
|
|
1984
|
+
repo_id: result.repo_id,
|
|
1985
|
+
turn_id: result.turn_id,
|
|
1986
|
+
retry_budget_exhausted: true,
|
|
1987
|
+
retry_count: usedRetries,
|
|
1988
|
+
error: budgetError,
|
|
1989
|
+
});
|
|
1990
|
+
if (!opts.json) {
|
|
1991
|
+
console.log(chalk.red(`→ ${result.repo_id} needs_attention ✗ (${budgetError})`));
|
|
1992
|
+
}
|
|
1993
|
+
continue;
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1851
1996
|
waveHadFailure = true;
|
|
1852
1997
|
totalFailed++;
|
|
1853
1998
|
waveResults.push({ workstream_id: ws.workstream_id, status: 'needs_attention', repo_id: result.repo_id, turn_id: result.turn_id });
|
|
@@ -1917,6 +2062,7 @@ async function coordinatorAutopilot(planTarget, opts, context, mission) {
|
|
|
1917
2062
|
total_launched: totalLaunched,
|
|
1918
2063
|
completed: totalCompleted,
|
|
1919
2064
|
failed: totalFailed,
|
|
2065
|
+
total_retries: totalRetries,
|
|
1920
2066
|
terminal_reason: terminalReason,
|
|
1921
2067
|
},
|
|
1922
2068
|
};
|
|
@@ -1930,6 +2076,7 @@ async function coordinatorAutopilot(planTarget, opts, context, mission) {
|
|
|
1930
2076
|
console.log(` Launched: ${totalLaunched}`);
|
|
1931
2077
|
console.log(` Completed: ${totalCompleted}`);
|
|
1932
2078
|
console.log(` Failed: ${totalFailed}`);
|
|
2079
|
+
console.log(` Retries: ${totalRetries}`);
|
|
1933
2080
|
console.log(` Outcome: ${formatTerminalReason(terminalReason)}`);
|
|
1934
2081
|
if (terminalReason === 'plan_completed') {
|
|
1935
2082
|
console.log(chalk.green('\n Plan completed successfully.'));
|
package/src/commands/restart.js
CHANGED
|
@@ -326,6 +326,9 @@ export async function restartCommand(opts) {
|
|
|
326
326
|
console.log(chalk.red(`Failed to reactivate run: ${reactivated.error}`));
|
|
327
327
|
process.exit(1);
|
|
328
328
|
}
|
|
329
|
+
if (reactivated.migration_notice) {
|
|
330
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
331
|
+
}
|
|
329
332
|
}
|
|
330
333
|
|
|
331
334
|
// Determine role from option or routing
|