agentxchain 2.135.1 → 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 +8 -1
- package/package.json +4 -2
- package/scripts/release-preflight.sh +2 -0
- package/src/commands/connector.js +136 -0
- 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/continuous-run.js +60 -5
- package/src/lib/dispatch-bundle.js +14 -0
- package/src/lib/governed-state.js +42 -46
- package/src/lib/intake.js +17 -57
- package/src/lib/intent-startup-migration.js +151 -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')
|
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/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
|
package/src/commands/resume.js
CHANGED
|
@@ -144,6 +144,9 @@ export async function resumeCommand(opts) {
|
|
|
144
144
|
process.exit(1);
|
|
145
145
|
}
|
|
146
146
|
state = reactivated.state;
|
|
147
|
+
if (reactivated.migration_notice) {
|
|
148
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
149
|
+
}
|
|
147
150
|
|
|
148
151
|
// Write dispatch bundle for the existing turn
|
|
149
152
|
const bundleResult = writeDispatchBundle(root, state, config);
|
|
@@ -204,6 +207,9 @@ export async function resumeCommand(opts) {
|
|
|
204
207
|
process.exit(1);
|
|
205
208
|
}
|
|
206
209
|
state = reactivated.state;
|
|
210
|
+
if (reactivated.migration_notice) {
|
|
211
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
212
|
+
}
|
|
207
213
|
|
|
208
214
|
const bundleResult = writeDispatchBundle(root, state, config, { turnId: retainedTurn.turn_id });
|
|
209
215
|
if (!bundleResult.ok) {
|
|
@@ -233,6 +239,9 @@ export async function resumeCommand(opts) {
|
|
|
233
239
|
}
|
|
234
240
|
state = initResult.state;
|
|
235
241
|
console.log(chalk.green(`Initialized governed run: ${state.run_id}`));
|
|
242
|
+
if (initResult.migration_notice) {
|
|
243
|
+
console.log(chalk.yellow(initResult.migration_notice));
|
|
244
|
+
}
|
|
236
245
|
}
|
|
237
246
|
|
|
238
247
|
// §47: paused + run_id exists → resume same run
|
|
@@ -244,6 +253,9 @@ export async function resumeCommand(opts) {
|
|
|
244
253
|
}
|
|
245
254
|
state = reactivated.state;
|
|
246
255
|
console.log(chalk.green(`Resumed blocked run: ${state.run_id}`));
|
|
256
|
+
if (reactivated.migration_notice) {
|
|
257
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
258
|
+
}
|
|
247
259
|
}
|
|
248
260
|
|
|
249
261
|
// §47: paused + run_id exists → resume same run
|
|
@@ -255,6 +267,9 @@ export async function resumeCommand(opts) {
|
|
|
255
267
|
}
|
|
256
268
|
state = reactivated.state;
|
|
257
269
|
console.log(chalk.green(`Resumed governed run: ${state.run_id}`));
|
|
270
|
+
if (reactivated.migration_notice) {
|
|
271
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
272
|
+
}
|
|
258
273
|
}
|
|
259
274
|
|
|
260
275
|
// Print run-context header before dispatch
|
package/src/commands/step.js
CHANGED
|
@@ -260,6 +260,9 @@ export async function stepCommand(opts) {
|
|
|
260
260
|
process.exit(1);
|
|
261
261
|
}
|
|
262
262
|
state = reactivated.state;
|
|
263
|
+
if (reactivated.migration_notice) {
|
|
264
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
265
|
+
}
|
|
263
266
|
skipAssignment = true;
|
|
264
267
|
|
|
265
268
|
// BUG-1 fix: refresh baseline snapshot to capture files dirtied between assignment and dispatch
|
|
@@ -285,6 +288,9 @@ export async function stepCommand(opts) {
|
|
|
285
288
|
}
|
|
286
289
|
state = initResult.state;
|
|
287
290
|
console.log(chalk.green(`Initialized governed run: ${state.run_id}`));
|
|
291
|
+
if (initResult.migration_notice) {
|
|
292
|
+
console.log(chalk.yellow(initResult.migration_notice));
|
|
293
|
+
}
|
|
288
294
|
}
|
|
289
295
|
|
|
290
296
|
// paused → resume
|
|
@@ -296,6 +302,9 @@ export async function stepCommand(opts) {
|
|
|
296
302
|
}
|
|
297
303
|
state = reactivated.state;
|
|
298
304
|
console.log(chalk.green(`Resumed blocked run: ${state.run_id}`));
|
|
305
|
+
if (reactivated.migration_notice) {
|
|
306
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
307
|
+
}
|
|
299
308
|
}
|
|
300
309
|
|
|
301
310
|
if (!skipAssignment && state.status === 'paused' && state.run_id) {
|
|
@@ -306,6 +315,9 @@ export async function stepCommand(opts) {
|
|
|
306
315
|
}
|
|
307
316
|
state = reactivated.state;
|
|
308
317
|
console.log(chalk.green(`Resumed governed run: ${state.run_id}`));
|
|
318
|
+
if (reactivated.migration_notice) {
|
|
319
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
320
|
+
}
|
|
309
321
|
}
|
|
310
322
|
|
|
311
323
|
// Assign the turn
|
|
@@ -106,6 +106,12 @@ export async function validateConfiguredConnector(sourceRoot, options = {}) {
|
|
|
106
106
|
const tempBase = mkdtempSync(join(tmpdir(), 'axc-connector-validate-'));
|
|
107
107
|
const scratchRoot = join(tempBase, 'workspace');
|
|
108
108
|
const warnings = [...roleSelection.warnings];
|
|
109
|
+
|
|
110
|
+
// Surface capability declaration warnings for self-declared connectors
|
|
111
|
+
const { getCapabilityDeclarationWarnings } = await import('./runtime-capabilities.js');
|
|
112
|
+
const capWarnings = getCapabilityDeclarationWarnings(runtime);
|
|
113
|
+
warnings.push(...capWarnings);
|
|
114
|
+
|
|
109
115
|
let keepArtifacts = options.keepArtifacts === true;
|
|
110
116
|
let dispatch = null;
|
|
111
117
|
let validation = null;
|
|
@@ -24,6 +24,11 @@ import {
|
|
|
24
24
|
} from './intake.js';
|
|
25
25
|
import { loadProjectState } from './config.js';
|
|
26
26
|
import { safeWriteJson } from './safe-write.js';
|
|
27
|
+
import { emitRunEvent } from './run-events.js';
|
|
28
|
+
import {
|
|
29
|
+
archiveStaleIntentsForRun,
|
|
30
|
+
formatLegacyIntentMigrationNotice,
|
|
31
|
+
} from './intent-startup-migration.js';
|
|
27
32
|
|
|
28
33
|
const CONTINUOUS_SESSION_PATH = '.agentxchain/continuous-session.json';
|
|
29
34
|
|
|
@@ -56,7 +61,7 @@ export function removeContinuousSession(root) {
|
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
|
|
59
|
-
function createSession(visionPath, maxRuns, maxIdleCycles, perSessionMaxUsd) {
|
|
64
|
+
function createSession(visionPath, maxRuns, maxIdleCycles, perSessionMaxUsd, currentRunId = null) {
|
|
60
65
|
return {
|
|
61
66
|
session_id: `cont-${randomUUID().slice(0, 8)}`,
|
|
62
67
|
started_at: new Date().toISOString(),
|
|
@@ -65,12 +70,13 @@ function createSession(visionPath, maxRuns, maxIdleCycles, perSessionMaxUsd) {
|
|
|
65
70
|
max_runs: maxRuns,
|
|
66
71
|
idle_cycles: 0,
|
|
67
72
|
max_idle_cycles: maxIdleCycles,
|
|
68
|
-
current_run_id:
|
|
73
|
+
current_run_id: currentRunId,
|
|
69
74
|
current_vision_objective: null,
|
|
70
75
|
status: 'running',
|
|
71
76
|
per_session_max_usd: perSessionMaxUsd || null,
|
|
72
77
|
cumulative_spent_usd: 0,
|
|
73
78
|
budget_exhausted: false,
|
|
79
|
+
startup_reconciled_run_id: null,
|
|
74
80
|
};
|
|
75
81
|
}
|
|
76
82
|
|
|
@@ -133,8 +139,46 @@ function buildContinuousProvenance(intentId, options = {}) {
|
|
|
133
139
|
};
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
export function findNextQueuedIntent(root) {
|
|
137
|
-
return findNextDispatchableIntent(root);
|
|
142
|
+
export function findNextQueuedIntent(root, options = {}) {
|
|
143
|
+
return findNextDispatchableIntent(root, { run_id: options.run_id || null });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function reconcileContinuousStartupState(context, session, contOpts, log) {
|
|
147
|
+
const { root, config } = context;
|
|
148
|
+
const governedState = loadProjectState(root, config);
|
|
149
|
+
const scopedRunId = session.current_run_id || contOpts.continueFrom || governedState?.run_id || null;
|
|
150
|
+
|
|
151
|
+
let sessionChanged = false;
|
|
152
|
+
if (scopedRunId && session.current_run_id !== scopedRunId) {
|
|
153
|
+
session.current_run_id = scopedRunId;
|
|
154
|
+
sessionChanged = true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (scopedRunId && session.startup_reconciled_run_id !== scopedRunId) {
|
|
158
|
+
const startupIntents = archiveStaleIntentsForRun(root, scopedRunId, {
|
|
159
|
+
protocolVersion: governedState?.protocol_version || config?.schema_version || '2.x',
|
|
160
|
+
});
|
|
161
|
+
if (startupIntents.archived_migration_intent_ids?.length > 0) {
|
|
162
|
+
emitRunEvent(root, 'intents_migrated', {
|
|
163
|
+
run_id: scopedRunId,
|
|
164
|
+
phase: governedState?.phase || null,
|
|
165
|
+
status: governedState?.status || 'active',
|
|
166
|
+
payload: {
|
|
167
|
+
archived_count: startupIntents.archived_migration_intent_ids.length,
|
|
168
|
+
archived_intent_ids: startupIntents.archived_migration_intent_ids,
|
|
169
|
+
reason: 'pre-BUG-34 intents with approved_run_id: null archived during continuous startup',
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
const migrationNotice = formatLegacyIntentMigrationNotice(startupIntents.archived_migration_intent_ids);
|
|
173
|
+
if (migrationNotice) log(migrationNotice);
|
|
174
|
+
}
|
|
175
|
+
session.startup_reconciled_run_id = scopedRunId;
|
|
176
|
+
sessionChanged = true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (sessionChanged) {
|
|
180
|
+
writeContinuousSession(root, session);
|
|
181
|
+
}
|
|
138
182
|
}
|
|
139
183
|
|
|
140
184
|
// ---------------------------------------------------------------------------
|
|
@@ -232,6 +276,7 @@ export function resolveContinuousOptions(opts, config) {
|
|
|
232
276
|
|
|
233
277
|
return {
|
|
234
278
|
enabled: opts.continuous ?? configCont.enabled ?? false,
|
|
279
|
+
continueFrom: opts.continueFrom ?? null,
|
|
235
280
|
visionPath: opts.vision ?? configCont.vision_path ?? '.planning/VISION.md',
|
|
236
281
|
maxRuns: opts.maxRuns ?? configCont.max_runs ?? 100,
|
|
237
282
|
pollSeconds: opts.pollSeconds ?? configCont.poll_seconds ?? 30,
|
|
@@ -288,6 +333,8 @@ export async function advanceContinuousRunOnce(context, session, contOpts, execu
|
|
|
288
333
|
return { ok: true, status: 'completed', action: 'session_budget_exhausted', stop_reason: 'session_budget' };
|
|
289
334
|
}
|
|
290
335
|
|
|
336
|
+
reconcileContinuousStartupState(context, session, contOpts, log);
|
|
337
|
+
|
|
291
338
|
// Paused-session guard: if session is paused (blocked run awaiting unblock),
|
|
292
339
|
// check governed state before attempting to advance. Without this guard, the
|
|
293
340
|
// loop would try to startIntent() on a blocked project, hit the blocked-state
|
|
@@ -540,7 +587,15 @@ export async function executeContinuousRun(context, contOpts, executeGovernedRun
|
|
|
540
587
|
return { exitCode: 1, session: null };
|
|
541
588
|
}
|
|
542
589
|
|
|
543
|
-
const
|
|
590
|
+
const startupState = loadProjectState(root, context.config);
|
|
591
|
+
const initialRunId = contOpts.continueFrom || startupState?.run_id || null;
|
|
592
|
+
const session = createSession(
|
|
593
|
+
contOpts.visionPath,
|
|
594
|
+
contOpts.maxRuns,
|
|
595
|
+
contOpts.maxIdleCycles,
|
|
596
|
+
contOpts.perSessionMaxUsd,
|
|
597
|
+
initialRunId,
|
|
598
|
+
);
|
|
544
599
|
writeContinuousSession(root, session);
|
|
545
600
|
|
|
546
601
|
// SIGINT handler
|
|
@@ -389,6 +389,20 @@ function renderPrompt(role, roleId, turn, state, config, root) {
|
|
|
389
389
|
}
|
|
390
390
|
lines.push('');
|
|
391
391
|
}
|
|
392
|
+
if (turn.conflict_context.forward_revision_files?.length) {
|
|
393
|
+
lines.push('Forward-revision files already safe to carry forward:');
|
|
394
|
+
for (const file of turn.conflict_context.forward_revision_files) {
|
|
395
|
+
lines.push(`- \`${file}\``);
|
|
396
|
+
}
|
|
397
|
+
if (turn.conflict_context.forward_revision_turns_since?.length) {
|
|
398
|
+
lines.push('');
|
|
399
|
+
lines.push('Forward-revision turns since assignment:');
|
|
400
|
+
for (const acceptedTurn of turn.conflict_context.forward_revision_turns_since) {
|
|
401
|
+
lines.push(`- \`${acceptedTurn.turn_id}\` (${acceptedTurn.role}) touched: ${acceptedTurn.files_changed.join(', ') || '(none)'}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
lines.push('');
|
|
405
|
+
}
|
|
392
406
|
if (turn.conflict_context.non_conflicting_files_preserved?.length) {
|
|
393
407
|
lines.push('Non-conflicting files to preserve from your prior attempt:');
|
|
394
408
|
for (const file of turn.conflict_context.non_conflicting_files_preserved) {
|
|
@@ -46,6 +46,10 @@ import { emitRunEvent } from './run-events.js';
|
|
|
46
46
|
import { writeSessionCheckpoint } from './session-checkpoint.js';
|
|
47
47
|
import { recordRunHistory } from './run-history.js';
|
|
48
48
|
import { buildDefaultRunProvenance } from './run-provenance.js';
|
|
49
|
+
import {
|
|
50
|
+
archiveStaleIntentsForRun,
|
|
51
|
+
formatLegacyIntentMigrationNotice,
|
|
52
|
+
} from './intent-startup-migration.js';
|
|
49
53
|
import {
|
|
50
54
|
ensureHumanEscalation,
|
|
51
55
|
findCurrentHumanEscalation,
|
|
@@ -1243,6 +1247,13 @@ function buildConflictContext(turn) {
|
|
|
1243
1247
|
files_changed: Array.isArray(entry.files_changed) ? entry.files_changed : [],
|
|
1244
1248
|
}))
|
|
1245
1249
|
: [];
|
|
1250
|
+
const forwardRevisionTurnsSince = Array.isArray(conflictError.forward_revision_turns)
|
|
1251
|
+
? conflictError.forward_revision_turns.map((entry) => ({
|
|
1252
|
+
turn_id: entry.turn_id,
|
|
1253
|
+
role: entry.role,
|
|
1254
|
+
files_changed: Array.isArray(entry.files_changed) ? entry.files_changed : [],
|
|
1255
|
+
}))
|
|
1256
|
+
: [];
|
|
1246
1257
|
|
|
1247
1258
|
return {
|
|
1248
1259
|
prior_attempt_turn_id: turn.turn_id,
|
|
@@ -1250,6 +1261,8 @@ function buildConflictContext(turn) {
|
|
|
1250
1261
|
conflict_type: conflictError.type || 'file_conflict',
|
|
1251
1262
|
conflicting_files: Array.isArray(conflictError.conflicting_files) ? conflictError.conflicting_files : [],
|
|
1252
1263
|
accepted_turns_since: acceptedTurnsSince,
|
|
1264
|
+
forward_revision_files: Array.isArray(conflictError.forward_revision_files) ? conflictError.forward_revision_files : [],
|
|
1265
|
+
forward_revision_turns_since: forwardRevisionTurnsSince,
|
|
1253
1266
|
non_conflicting_files_preserved: Array.isArray(conflictError.non_conflicting_files)
|
|
1254
1267
|
? conflictError.non_conflicting_files
|
|
1255
1268
|
: [],
|
|
@@ -2067,6 +2080,25 @@ export function reactivateGovernedRun(root, state, details = {}) {
|
|
|
2067
2080
|
|
|
2068
2081
|
writeState(root, nextState);
|
|
2069
2082
|
|
|
2083
|
+
const startupIntents = nextState.run_id
|
|
2084
|
+
? archiveStaleIntentsForRun(root, nextState.run_id, {
|
|
2085
|
+
protocolVersion: nextState.protocol_version || state.protocol_version || '2.x',
|
|
2086
|
+
})
|
|
2087
|
+
: { archived_migration_intent_ids: [], migration_notice: null };
|
|
2088
|
+
|
|
2089
|
+
if (startupIntents.archived_migration_intent_ids?.length > 0) {
|
|
2090
|
+
emitRunEvent(root, 'intents_migrated', {
|
|
2091
|
+
run_id: nextState.run_id,
|
|
2092
|
+
phase: nextState.phase,
|
|
2093
|
+
status: nextState.status,
|
|
2094
|
+
payload: {
|
|
2095
|
+
archived_count: startupIntents.archived_migration_intent_ids.length,
|
|
2096
|
+
archived_intent_ids: startupIntents.archived_migration_intent_ids,
|
|
2097
|
+
reason: 'pre-BUG-34 intents with approved_run_id: null archived during run reactivation',
|
|
2098
|
+
},
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2070
2102
|
if (humanEscalation) {
|
|
2071
2103
|
resolveHumanEscalation(root, humanEscalation.escalation_id, {
|
|
2072
2104
|
resolved_at: now,
|
|
@@ -2108,7 +2140,11 @@ export function reactivateGovernedRun(root, state, details = {}) {
|
|
|
2108
2140
|
});
|
|
2109
2141
|
}
|
|
2110
2142
|
|
|
2111
|
-
return {
|
|
2143
|
+
return {
|
|
2144
|
+
ok: true,
|
|
2145
|
+
state: attachLegacyCurrentTurnAlias(nextState),
|
|
2146
|
+
migration_notice: formatLegacyIntentMigrationNotice(startupIntents.archived_migration_intent_ids),
|
|
2147
|
+
};
|
|
2112
2148
|
}
|
|
2113
2149
|
|
|
2114
2150
|
// ── Core Operations ──────────────────────────────────────────────────────────
|
|
@@ -2162,48 +2198,10 @@ export function initializeGovernedRun(root, config, options = {}) {
|
|
|
2162
2198
|
|
|
2163
2199
|
writeState(root, updatedState);
|
|
2164
2200
|
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
// run. Silently adopting them caused continuous mode to pick up stale intents.
|
|
2170
|
-
const archivedMigrationIntentIds = [];
|
|
2171
|
-
try {
|
|
2172
|
-
const intentsDir = join(root, '.agentxchain', 'intake', 'intents');
|
|
2173
|
-
if (existsSync(intentsDir)) {
|
|
2174
|
-
const DISPATCHABLE = new Set(['planned', 'approved']);
|
|
2175
|
-
const intNow = new Date().toISOString();
|
|
2176
|
-
for (const f of readdirSync(intentsDir).filter(x => x.endsWith('.json') && !x.startsWith('.tmp-'))) {
|
|
2177
|
-
const ip = join(intentsDir, f);
|
|
2178
|
-
try {
|
|
2179
|
-
const intent = JSON.parse(readFileSync(ip, 'utf8'));
|
|
2180
|
-
if (!intent || !DISPATCHABLE.has(intent.status)) continue;
|
|
2181
|
-
if (intent.cross_run_durable === true) continue;
|
|
2182
|
-
if (intent.approved_run_id === runId) continue;
|
|
2183
|
-
|
|
2184
|
-
const prevStatus = intent.status;
|
|
2185
|
-
if (intent.approved_run_id && intent.approved_run_id !== runId) {
|
|
2186
|
-
// Intent from a different run — archive it (BUG-34)
|
|
2187
|
-
intent.status = 'suppressed';
|
|
2188
|
-
intent.updated_at = intNow;
|
|
2189
|
-
intent.archived_reason = `stale: approved under run ${intent.approved_run_id}, archived on run ${runId} initialization`;
|
|
2190
|
-
if (!intent.history) intent.history = [];
|
|
2191
|
-
intent.history.push({ from: prevStatus, to: 'suppressed', at: intNow, reason: intent.archived_reason });
|
|
2192
|
-
} else if (!intent.approved_run_id) {
|
|
2193
|
-
// BUG-39: pre-BUG-34 legacy intent with no run binding — archive it
|
|
2194
|
-
// with explicit migration reason. Do NOT adopt into current run.
|
|
2195
|
-
intent.status = 'archived_migration';
|
|
2196
|
-
intent.updated_at = intNow;
|
|
2197
|
-
intent.archived_reason = `pre-BUG-34 intent with no run scope; archived during v${updatedState.protocol_version || '2.x'} migration on run ${runId}`;
|
|
2198
|
-
if (!intent.history) intent.history = [];
|
|
2199
|
-
intent.history.push({ from: prevStatus, to: 'archived_migration', at: intNow, reason: intent.archived_reason });
|
|
2200
|
-
if (intent.intent_id) archivedMigrationIntentIds.push(intent.intent_id);
|
|
2201
|
-
}
|
|
2202
|
-
safeWriteJson(ip, intent);
|
|
2203
|
-
} catch { /* non-fatal per-intent */ }
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
} catch { /* non-fatal — intent migration is best-effort */ }
|
|
2201
|
+
const startupIntents = archiveStaleIntentsForRun(root, runId, {
|
|
2202
|
+
protocolVersion: updatedState.protocol_version || '2.x',
|
|
2203
|
+
});
|
|
2204
|
+
const archivedMigrationIntentIds = startupIntents.archived_migration_intent_ids || [];
|
|
2207
2205
|
|
|
2208
2206
|
// BUG-39: emit intents_migrated event when pre-BUG-34 intents were archived
|
|
2209
2207
|
if (archivedMigrationIntentIds.length > 0) {
|
|
@@ -2226,9 +2224,7 @@ export function initializeGovernedRun(root, config, options = {}) {
|
|
|
2226
2224
|
payload: { provenance: provenance || {} },
|
|
2227
2225
|
});
|
|
2228
2226
|
// BUG-39: return migration notice so callers can display it
|
|
2229
|
-
const migrationNotice =
|
|
2230
|
-
? `Archived ${archivedMigrationIntentIds.length} pre-BUG-34 intent(s). Review: agentxchain intake status --archived.`
|
|
2231
|
-
: null;
|
|
2227
|
+
const migrationNotice = startupIntents.migration_notice;
|
|
2232
2228
|
|
|
2233
2229
|
return { ok: true, state: attachLegacyCurrentTurnAlias(updatedState), migration_notice: migrationNotice };
|
|
2234
2230
|
}
|