agentxchain 2.115.0 → 2.117.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/README.md +1 -1
- package/bin/agentxchain.js +37 -1
- package/package.json +1 -1
- package/src/commands/events.js +8 -1
- package/src/commands/inject.js +81 -0
- package/src/commands/mission.js +327 -0
- package/src/commands/resume.js +6 -4
- package/src/commands/run.js +13 -0
- package/src/commands/schedule.js +165 -19
- package/src/commands/status.js +52 -0
- package/src/commands/unblock.js +67 -0
- package/src/lib/continuous-run.js +448 -0
- package/src/lib/governed-state.js +37 -1
- package/src/lib/human-escalations.js +434 -0
- package/src/lib/intake.js +243 -11
- package/src/lib/mission-plans.js +6 -2
- package/src/lib/notification-runner.js +3 -1
- package/src/lib/run-events.js +2 -0
- package/src/lib/run-loop.js +17 -0
- package/src/lib/run-provenance.js +4 -0
- package/src/lib/run-schedule.js +43 -0
- package/src/lib/vision-reader.js +229 -0
package/src/commands/schedule.js
CHANGED
|
@@ -6,12 +6,14 @@ import {
|
|
|
6
6
|
listSchedules,
|
|
7
7
|
updateScheduleState,
|
|
8
8
|
evaluateScheduleLaunchEligibility,
|
|
9
|
+
findContinuableScheduleRun,
|
|
9
10
|
readDaemonState,
|
|
10
11
|
writeDaemonState,
|
|
11
12
|
updateDaemonHeartbeat,
|
|
12
13
|
createDaemonState,
|
|
13
14
|
evaluateDaemonStatus,
|
|
14
15
|
} from '../lib/run-schedule.js';
|
|
16
|
+
import { consumePreemptionMarker } from '../lib/intake.js';
|
|
15
17
|
import { executeGovernedRun } from './run.js';
|
|
16
18
|
|
|
17
19
|
function loadScheduleContext() {
|
|
@@ -86,7 +88,136 @@ function buildScheduleProvenance(entry) {
|
|
|
86
88
|
};
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
function buildScheduleExecutionResult(entryId, execution, fallbackState, action = 'ran') {
|
|
92
|
+
const state = execution.result?.state || fallbackState || null;
|
|
93
|
+
return {
|
|
94
|
+
id: entryId,
|
|
95
|
+
action,
|
|
96
|
+
run_id: state?.run_id || null,
|
|
97
|
+
stop_reason: execution.result?.stop_reason || null,
|
|
98
|
+
exit_code: execution.exitCode,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function recordScheduleExecution(context, entryId, execution, fallbackState, nowIso, action = 'ran') {
|
|
103
|
+
const state = execution.result?.state || fallbackState || null;
|
|
104
|
+
const runId = state?.run_id || null;
|
|
105
|
+
const startedAt = state?.created_at || nowIso;
|
|
106
|
+
|
|
107
|
+
updateScheduleState(context.root, context.config, entryId, (record) => ({
|
|
108
|
+
...record,
|
|
109
|
+
last_started_at: startedAt,
|
|
110
|
+
last_finished_at: new Date().toISOString(),
|
|
111
|
+
last_run_id: runId,
|
|
112
|
+
last_status: execution.result?.stop_reason || (execution.exitCode === 0 ? 'completed' : 'launch_failed'),
|
|
113
|
+
last_skip_at: null,
|
|
114
|
+
last_skip_reason: null,
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
return buildScheduleExecutionResult(entryId, execution, fallbackState, action);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function consumeScheduledPriorityPreemption(context, scheduleId, schedule, execution, fallbackState, at) {
|
|
121
|
+
const scheduleResult = recordScheduleExecution(
|
|
122
|
+
context,
|
|
123
|
+
scheduleId,
|
|
124
|
+
execution,
|
|
125
|
+
fallbackState,
|
|
126
|
+
at || new Date().toISOString(),
|
|
127
|
+
'preempted',
|
|
128
|
+
);
|
|
129
|
+
const consumed = consumePreemptionMarker(context.root, {
|
|
130
|
+
role: schedule.initial_role || undefined,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (!consumed.ok) {
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
exitCode: 1,
|
|
137
|
+
result: {
|
|
138
|
+
...scheduleResult,
|
|
139
|
+
action: 'preemption_failed',
|
|
140
|
+
error: consumed.error,
|
|
141
|
+
injected_intent_id: execution.result?.preempted_by || null,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
ok: true,
|
|
148
|
+
exitCode: 0,
|
|
149
|
+
result: {
|
|
150
|
+
...scheduleResult,
|
|
151
|
+
action: 'preempted',
|
|
152
|
+
injected_intent_id: consumed.intent_id,
|
|
153
|
+
injected_turn_id: consumed.turn_id,
|
|
154
|
+
injected_role: consumed.role,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function continueActiveScheduledRun(context, opts = {}) {
|
|
160
|
+
const continuation = findContinuableScheduleRun(context.root, context.config, {
|
|
161
|
+
scheduleId: opts.schedule || null,
|
|
162
|
+
});
|
|
163
|
+
if (!continuation.ok) {
|
|
164
|
+
return { matched: false, reason: continuation.reason };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const { schedule_id: scheduleId, schedule, state } = continuation;
|
|
168
|
+
|
|
169
|
+
if (!opts.json) {
|
|
170
|
+
console.log(chalk.cyan(`Continuing active scheduled run: ${scheduleId}`));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const execution = await executeGovernedRun(context, {
|
|
174
|
+
maxTurns: schedule.max_turns,
|
|
175
|
+
autoApprove: schedule.auto_approve !== false,
|
|
176
|
+
report: true,
|
|
177
|
+
log: opts.json ? () => {} : console.log,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (execution.result?.stop_reason === 'priority_preempted') {
|
|
181
|
+
const promoted = consumeScheduledPriorityPreemption(context, scheduleId, schedule, execution, state, opts.at);
|
|
182
|
+
return {
|
|
183
|
+
matched: true,
|
|
184
|
+
ok: promoted.ok,
|
|
185
|
+
exitCode: promoted.exitCode,
|
|
186
|
+
result: promoted.result,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const blocked = execution.result?.stop_reason === 'blocked';
|
|
191
|
+
const action = blocked && opts.tolerateBlockedRun ? 'blocked' : 'continued';
|
|
192
|
+
const result = recordScheduleExecution(context, scheduleId, execution, state, opts.at || new Date().toISOString(), action);
|
|
193
|
+
|
|
194
|
+
if (execution.exitCode !== 0 && !(opts.tolerateBlockedRun && blocked)) {
|
|
195
|
+
return {
|
|
196
|
+
matched: true,
|
|
197
|
+
ok: false,
|
|
198
|
+
exitCode: execution.exitCode,
|
|
199
|
+
result,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
matched: true,
|
|
205
|
+
ok: true,
|
|
206
|
+
exitCode: 0,
|
|
207
|
+
result,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
89
211
|
async function runDueSchedules(context, opts = {}) {
|
|
212
|
+
if (opts.continueActiveScheduleRuns) {
|
|
213
|
+
const continuation = await continueActiveScheduledRun(context, opts);
|
|
214
|
+
if (continuation.matched) {
|
|
215
|
+
return continuation.ok
|
|
216
|
+
? { ok: true, exitCode: continuation.exitCode, results: [continuation.result] }
|
|
217
|
+
: { ok: false, exitCode: continuation.exitCode, results: [continuation.result], error: 'Scheduled run failed' };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
90
221
|
const resolved = resolveScheduleEntries(context, opts.schedule, opts.at);
|
|
91
222
|
if (!resolved.ok) {
|
|
92
223
|
return { ok: false, exitCode: 1, error: resolved.error, results: [] };
|
|
@@ -150,26 +281,29 @@ async function runDueSchedules(context, opts = {}) {
|
|
|
150
281
|
continue;
|
|
151
282
|
}
|
|
152
283
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
284
|
+
if (execution.result?.stop_reason === 'priority_preempted') {
|
|
285
|
+
const promoted = consumeScheduledPriorityPreemption(context, entry.id, entry, execution, execution.result?.state || null, nowIso);
|
|
286
|
+
results.push(promoted.result);
|
|
287
|
+
if (!promoted.ok) {
|
|
288
|
+
return { ok: false, exitCode: promoted.exitCode, results };
|
|
289
|
+
}
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const blocked = execution.result?.stop_reason === 'blocked';
|
|
294
|
+
results.push(recordScheduleExecution(
|
|
295
|
+
context,
|
|
296
|
+
entry.id,
|
|
297
|
+
execution,
|
|
298
|
+
execution.result?.state || null,
|
|
299
|
+
nowIso,
|
|
300
|
+
blocked && opts.tolerateBlockedRun ? 'blocked' : 'ran',
|
|
301
|
+
));
|
|
171
302
|
|
|
172
303
|
if (execution.exitCode !== 0) {
|
|
304
|
+
if (opts.tolerateBlockedRun && blocked) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
173
307
|
return { ok: false, exitCode: execution.exitCode, results };
|
|
174
308
|
}
|
|
175
309
|
}
|
|
@@ -214,6 +348,14 @@ export async function scheduleRunDueCommand(opts) {
|
|
|
214
348
|
for (const entry of result.results) {
|
|
215
349
|
if (entry.action === 'ran') {
|
|
216
350
|
console.log(chalk.green(`Schedule ran: ${entry.id} (${entry.run_id || 'no run id'})`));
|
|
351
|
+
} else if (entry.action === 'continued') {
|
|
352
|
+
console.log(chalk.green(`Schedule continued: ${entry.id} (${entry.run_id || 'no run id'})`));
|
|
353
|
+
} else if (entry.action === 'preempted') {
|
|
354
|
+
console.log(chalk.yellow(`Schedule preempted by injected priority: ${entry.id} (${entry.injected_intent_id || 'unknown intent'})`));
|
|
355
|
+
} else if (entry.action === 'preemption_failed') {
|
|
356
|
+
console.log(chalk.red(`Schedule preemption failed: ${entry.id} (${entry.error || 'unknown error'})`));
|
|
357
|
+
} else if (entry.action === 'blocked') {
|
|
358
|
+
console.log(chalk.yellow(`Schedule waiting on unblock: ${entry.id}`));
|
|
217
359
|
} else if (entry.action === 'skipped') {
|
|
218
360
|
console.log(chalk.yellow(`Schedule skipped: ${entry.id} (${entry.reason})`));
|
|
219
361
|
} else if (entry.action === 'not_due') {
|
|
@@ -338,7 +480,11 @@ export async function scheduleDaemonCommand(opts) {
|
|
|
338
480
|
while (true) {
|
|
339
481
|
cycle += 1;
|
|
340
482
|
daemonState.last_cycle_started_at = new Date().toISOString();
|
|
341
|
-
const result = await runDueSchedules(context,
|
|
483
|
+
const result = await runDueSchedules(context, {
|
|
484
|
+
...opts,
|
|
485
|
+
continueActiveScheduleRuns: true,
|
|
486
|
+
tolerateBlockedRun: true,
|
|
487
|
+
});
|
|
342
488
|
|
|
343
489
|
updateDaemonHeartbeat(context.root, daemonState, result);
|
|
344
490
|
|
package/src/commands/status.js
CHANGED
|
@@ -19,7 +19,10 @@ import { summarizeRunProvenance } from '../lib/run-provenance.js';
|
|
|
19
19
|
import { readRecentRunEventSummary } from '../lib/recent-event-summary.js';
|
|
20
20
|
import { deriveConflictedTurnResolutionActions } from '../lib/conflict-actions.js';
|
|
21
21
|
import { summarizeLatestGateActionAttempt } from '../lib/gate-actions.js';
|
|
22
|
+
import { findCurrentHumanEscalation } from '../lib/human-escalations.js';
|
|
22
23
|
import { getDashboardPid, getDashboardSession } from './dashboard.js';
|
|
24
|
+
import { readPreemptionMarker } from '../lib/intake.js';
|
|
25
|
+
import { readContinuousSession } from '../lib/continuous-run.js';
|
|
23
26
|
|
|
24
27
|
export async function statusCommand(opts) {
|
|
25
28
|
const context = loadStatusContext();
|
|
@@ -127,6 +130,9 @@ function renderGovernedStatus(context, opts) {
|
|
|
127
130
|
const repoDecisionSummary = summarizeRepoDecisions(readRepoDecisions(root), config);
|
|
128
131
|
|
|
129
132
|
const workflowKitArtifacts = deriveWorkflowKitArtifacts(root, config, state);
|
|
133
|
+
const humanEscalation = findCurrentHumanEscalation(root, state);
|
|
134
|
+
const preemptionMarker = readPreemptionMarker(root);
|
|
135
|
+
const continuousSession = readContinuousSession(root);
|
|
130
136
|
const gateActionAttempt = state?.pending_phase_transition
|
|
131
137
|
? summarizeLatestGateActionAttempt(root, 'phase_transition', state.pending_phase_transition.gate)
|
|
132
138
|
: state?.pending_run_completion
|
|
@@ -162,6 +168,9 @@ function renderGovernedStatus(context, opts) {
|
|
|
162
168
|
next_actions: nextActions,
|
|
163
169
|
connector_health: connectorHealth,
|
|
164
170
|
recent_event_summary: recentEventSummary,
|
|
171
|
+
human_escalation: humanEscalation,
|
|
172
|
+
preemption_marker: preemptionMarker,
|
|
173
|
+
continuous_session: continuousSession,
|
|
165
174
|
gate_action_attempt: gateActionAttempt,
|
|
166
175
|
workflow_kit_artifacts: workflowKitArtifacts,
|
|
167
176
|
dashboard_session: dashboardSessionObj,
|
|
@@ -174,6 +183,39 @@ function renderGovernedStatus(context, opts) {
|
|
|
174
183
|
console.log(chalk.dim(' ' + '─'.repeat(44)));
|
|
175
184
|
console.log('');
|
|
176
185
|
|
|
186
|
+
// Priority injection banner — above all other status
|
|
187
|
+
if (preemptionMarker) {
|
|
188
|
+
console.log(chalk.red.bold(' ⚡ Priority injection pending'));
|
|
189
|
+
console.log(chalk.dim(` Intent: ${preemptionMarker.intent_id}`));
|
|
190
|
+
console.log(` Priority: ${chalk.red.bold(preemptionMarker.priority)}`);
|
|
191
|
+
if (preemptionMarker.description) {
|
|
192
|
+
console.log(chalk.dim(` Description: ${preemptionMarker.description}`));
|
|
193
|
+
}
|
|
194
|
+
if (preemptionMarker.injected_at) {
|
|
195
|
+
console.log(chalk.dim(` Injected at: ${preemptionMarker.injected_at}`));
|
|
196
|
+
}
|
|
197
|
+
console.log(chalk.dim(' Effect: Will preempt current workstream after this turn completes'));
|
|
198
|
+
console.log(chalk.dim(' ' + '─'.repeat(44)));
|
|
199
|
+
console.log('');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Continuous session banner
|
|
203
|
+
if (continuousSession) {
|
|
204
|
+
console.log(chalk.cyan.bold(' 🔄 Continuous Vision-Driven Session'));
|
|
205
|
+
console.log(chalk.dim(` Session: ${continuousSession.session_id}`));
|
|
206
|
+
console.log(chalk.dim(` Vision: ${continuousSession.vision_path}`));
|
|
207
|
+
console.log(` Status: ${chalk.cyan(continuousSession.status || 'unknown')}`);
|
|
208
|
+
console.log(` Runs: ${continuousSession.runs_completed || 0}/${continuousSession.max_runs || '?'}`);
|
|
209
|
+
if (continuousSession.current_vision_objective) {
|
|
210
|
+
console.log(` Objective: ${chalk.yellow(continuousSession.current_vision_objective)}`);
|
|
211
|
+
}
|
|
212
|
+
if (continuousSession.idle_cycles > 0) {
|
|
213
|
+
console.log(chalk.dim(` Idle cycles: ${continuousSession.idle_cycles}/${continuousSession.max_idle_cycles}`));
|
|
214
|
+
}
|
|
215
|
+
console.log(chalk.dim(' ' + '─'.repeat(44)));
|
|
216
|
+
console.log('');
|
|
217
|
+
}
|
|
218
|
+
|
|
177
219
|
console.log(` ${chalk.dim('Project:')} ${config.project.name}`);
|
|
178
220
|
if (config.project.goal) {
|
|
179
221
|
console.log(` ${chalk.dim('Goal:')} ${config.project.goal}`);
|
|
@@ -325,6 +367,16 @@ function renderGovernedStatus(context, opts) {
|
|
|
325
367
|
}
|
|
326
368
|
}
|
|
327
369
|
|
|
370
|
+
if (humanEscalation) {
|
|
371
|
+
console.log('');
|
|
372
|
+
console.log(` ${chalk.dim('Human task:')} ${chalk.yellow(humanEscalation.escalation_id)}${humanEscalation.service ? ` (${humanEscalation.service})` : ''}`);
|
|
373
|
+
console.log(` ${chalk.dim('Type:')} ${humanEscalation.type}`);
|
|
374
|
+
console.log(` ${chalk.dim('Unblock:')} ${chalk.cyan(humanEscalation.resolution_command)}`);
|
|
375
|
+
if (humanEscalation.action) {
|
|
376
|
+
console.log(` ${chalk.dim('Task:')} ${humanEscalation.action}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
328
380
|
if (runtimeGuidance.length > 0) {
|
|
329
381
|
console.log('');
|
|
330
382
|
console.log(` ${chalk.dim('Runtime guidance:')}`);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { loadProjectContext, loadProjectState } from '../lib/config.js';
|
|
3
|
+
import { findCurrentHumanEscalation, getOpenHumanEscalation } from '../lib/human-escalations.js';
|
|
4
|
+
import { resumeCommand } from './resume.js';
|
|
5
|
+
|
|
6
|
+
export async function unblockCommand(escalationId) {
|
|
7
|
+
const context = loadProjectContext();
|
|
8
|
+
if (!context) {
|
|
9
|
+
console.log(chalk.red('No agentxchain.json found. Run `agentxchain init` first.'));
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { root, config } = context;
|
|
14
|
+
|
|
15
|
+
if (config.protocol_mode !== 'governed') {
|
|
16
|
+
console.log(chalk.red('The unblock command is only available for governed projects.'));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!escalationId || !String(escalationId).trim()) {
|
|
21
|
+
console.log(chalk.red('An escalation id is required. Example: agentxchain unblock hesc_1234'));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const state = loadProjectState(root, config);
|
|
26
|
+
if (!state) {
|
|
27
|
+
console.log(chalk.red('No governed state.json found. Run `agentxchain init --governed` first.'));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (state.status !== 'blocked') {
|
|
32
|
+
console.log(chalk.red(`Cannot unblock run: status is "${state.status}", expected "blocked".`));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const requested = getOpenHumanEscalation(root, escalationId);
|
|
37
|
+
if (!requested) {
|
|
38
|
+
console.log(chalk.red(`No open human escalation found for ${escalationId}.`));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const current = findCurrentHumanEscalation(root, state);
|
|
43
|
+
if (!current) {
|
|
44
|
+
console.log(chalk.red('The current blocked run does not have a linked human escalation record.'));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (current.escalation_id !== requested.escalation_id) {
|
|
49
|
+
console.log(chalk.red(`Escalation ${escalationId} is not the current blocker for this run.`));
|
|
50
|
+
console.log(chalk.dim(`Current blocker: ${current.escalation_id}`));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(chalk.green(` Unblocking ${requested.escalation_id}`));
|
|
56
|
+
console.log(chalk.dim(` Type: ${requested.type}${requested.service ? ` (${requested.service})` : ''}`));
|
|
57
|
+
if (requested.detail) {
|
|
58
|
+
console.log(chalk.dim(` Detail: ${requested.detail}`));
|
|
59
|
+
}
|
|
60
|
+
console.log(chalk.dim(' Continuing governed execution...'));
|
|
61
|
+
console.log('');
|
|
62
|
+
|
|
63
|
+
await resumeCommand({
|
|
64
|
+
_via: 'operator_unblock',
|
|
65
|
+
turn: requested.turn_id || undefined,
|
|
66
|
+
});
|
|
67
|
+
}
|