agents-relay 2.0.169 → 2.0.170
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 +13 -19
- package/dist/adapters.js +2 -8
- package/dist/blockers.js +3 -2
- package/dist/cli-help.js +3 -3
- package/dist/cli-job-create.js +2 -4
- package/dist/cli-support.js +5 -16
- package/dist/cli.js +9 -11
- package/dist/dashboard-html.js +5 -9
- package/dist/dashboard-style.js +0 -1
- package/dist/execution-recovery.js +1 -1
- package/dist/force-restate-validation.js +11 -1
- package/dist/interactive-resume.js +1 -1
- package/dist/job-actions.js +1 -1
- package/dist/job-pause.js +1 -1
- package/dist/legacy-blocked-task-migration.js +1 -1
- package/dist/local-authority-store.js +0 -57
- package/dist/local-git-merge.js +1 -2
- package/dist/local-merge-delivery.js +1 -1
- package/dist/markers.js +5 -6
- package/dist/planned-review-head.js +2 -2
- package/dist/planner-runtime.js +3 -2
- package/dist/planner.js +2 -7
- package/dist/pool.js +7 -36
- package/dist/reconciler-review.js +4 -5
- package/dist/reconciler.js +11 -36
- package/dist/relay-create.js +3 -3
- package/dist/relay-job-snapshot-reads.js +6 -2
- package/dist/relay-store.js +2 -2
- package/dist/relay-workflow-operations.js +2 -2
- package/dist/relayd-control.js +9 -7
- package/dist/relayd-utilities.js +0 -4
- package/dist/relayd.js +7 -24
- package/dist/runtime-status.js +0 -19
- package/dist/single-job-task-control.js +1 -1
- package/dist/store.js +1 -1
- package/dist/task-actions.js +3 -3
- package/dist/task-recovery.js +2 -2
- package/dist/task-transitions.js +61 -0
- package/dist/troubleshooter-fallback.js +1 -1
- package/dist/troubleshooter.js +1 -4
- package/dist/types.js +1 -2
- package/dist/unblock.js +5 -9
- package/dist/worker-rejection.js +1 -1
- package/dist/workflow/authoritative-conditions.js +1 -1
- package/dist/workflow/blocker-resolution-v2.js +2 -4
- package/dist/workflow/contracts.js +0 -1
- package/dist/workflow/definitions-v2.js +3 -6
- package/dist/workflow/durable-transition.js +1 -1
- package/dist/workflow/force-restate-validation-v2.js +2 -1
- package/dist/workflow/orchestrator-rescue.js +1 -1
- package/dist/workflow/planner-orchestration-v2.js +2 -5
- package/dist/workflow/planner-trigger-v2.js +1 -2
- package/dist/workflow/review-v2.js +0 -1
- package/dist/workflow/task-reconcile.js +1 -7
- package/dist/workflow-effect-failures.js +1 -1
- package/dist/workspace.js +1 -1
- package/package.json +3 -4
- package/skills/agents-relay/SKILL.md +6 -6
- package/skills/agents-relay/agents/planner.agent.md +3 -3
- package/skills/agents-relay/contracts/orchestrator-workflows.md +2 -2
- package/skills/agents-relay/contracts/task-job-contract.md +5 -5
- package/workflow/blocker-resolution.mmd +1 -7
- package/workflow/force-restate-validation.mmd +7 -2
- package/workflow/planner-orchestration.mmd +2 -5
- package/workflow/planner-trigger.mmd +2 -9
- package/workflow/review-lifecycle.mmd +2 -8
- package/dist/e2e-injection.js +0 -76
- package/dist/scheduled-jobs.js +0 -67
- package/dist/scheduler.js +0 -214
- package/dist/workflow/schedule-cycle-v2.js +0 -25
- package/dist/workflow/scheduled-run-v2.js +0 -30
- package/workflow/schedule-cycle.mmd +0 -23
- package/workflow/scheduled-run.mmd +0 -59
package/dist/relayd.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { escalationNotifierFromEnvironment } from './escalation-notifier.js';
|
|
3
|
-
import { consumeMethodInjection, withTestInjection } from './e2e-injection.js';
|
|
4
3
|
import { once } from 'node:events';
|
|
5
4
|
import { spawnOwnedProcess } from './owned-process.js';
|
|
6
5
|
import { readFile } from 'node:fs/promises';
|
|
@@ -28,7 +27,7 @@ import { dashboardJobAction, isEntrypoint, runtimePlanner, runtimeReviewer, star
|
|
|
28
27
|
import { eventBusResolutionMessage, publishTaskUnblocked, resolveEventBus } from './cli-support.js';
|
|
29
28
|
import { eventFor } from './events.js';
|
|
30
29
|
import { dispatchTaskAction } from './task-actions.js';
|
|
31
|
-
import { isEffectivelyBlocked,
|
|
30
|
+
import { isEffectivelyBlocked, unblockTask } from './unblock.js';
|
|
32
31
|
import { registerBlockerDependency } from './blockers.js';
|
|
33
32
|
import { ensureManagedGitHubJob } from './cli-job-create.js';
|
|
34
33
|
import { applyJobActivateAction } from './job-actions.js';
|
|
@@ -49,8 +48,8 @@ import { routeExecution } from './execution-routing.js';
|
|
|
49
48
|
import { classifyFailure, errorMessage, observability } from './observability.js';
|
|
50
49
|
import { createWorkspaceControl } from './relayd-control.js';
|
|
51
50
|
import { createDashboardActions } from './relayd-dashboard-actions.js';
|
|
52
|
-
export { daemonHelp, normalizeDaemonArguments, overlayOperationalOverviews, mergeOperationalInventory, recoveryScanRepositories, shouldCoalescePullRequestRefresh,
|
|
53
|
-
import { configureDaemonObservability, firstStageChoices, value, daemonHelp, normalizeDaemonArguments,
|
|
51
|
+
export { daemonHelp, normalizeDaemonArguments, overlayOperationalOverviews, mergeOperationalInventory, recoveryScanRepositories, shouldCoalescePullRequestRefresh, configureExecutionRouterRulesPath } from './relayd-utilities.js';
|
|
52
|
+
import { configureDaemonObservability, firstStageChoices, value, daemonHelp, normalizeDaemonArguments, configureExecutionRouterRulesPath } from './relayd-utilities.js';
|
|
54
53
|
export async function runDaemon(argv) {
|
|
55
54
|
if (argv.includes('--help') || argv.includes('-h')) {
|
|
56
55
|
console.log(daemonHelp);
|
|
@@ -323,7 +322,7 @@ export async function runDaemon(argv) {
|
|
|
323
322
|
if (job.state === 'COMPLETED' && job.localMerge?.status === 'succeeded' && job.localMerge.projectionStrategy === 'target_ref_push') {
|
|
324
323
|
const projection = await projectBindingLocalMerge(job, relay, {
|
|
325
324
|
projectPath: canonical,
|
|
326
|
-
project: async (projectedJob) =>
|
|
325
|
+
project: async (projectedJob) => projectAuthoritativeLocalMerge(canonical, projectedJob),
|
|
327
326
|
verifyObservedRemote: async (sha) => {
|
|
328
327
|
try {
|
|
329
328
|
await gitText(canonical, ['merge-base', '--is-ancestor', sha, 'refs/heads/main']);
|
|
@@ -384,7 +383,7 @@ export async function runDaemon(argv) {
|
|
|
384
383
|
const fallback = initial[0]?.job;
|
|
385
384
|
const dashboardStore = fallback ? (await requireLocalJobBinding(fallback.id)).store : new InMemoryStore();
|
|
386
385
|
const { dashboardOverview, workspaceJobAction, workspaceJobSetState, workspaceTaskCreate, workspaceTaskAction, workspaceTaskSetState } = createDashboardActions({ getOverviews: () => cachedOverviews, requireLocalJobBinding, pool, dashboardJobAction, makeReconciler, concurrency, dashboardJobSetState, refreshAll, applyJobActivateAction, bus, dashboardTaskAction, dashboardTaskSetState });
|
|
387
|
-
const workspaceControl = createWorkspaceControl({ client, trusted, noGitHub, repositories, refreshAll, requireLocalJobBinding, makeReconciler, concurrency, pool, bus, isEffectivelyBlocked,
|
|
386
|
+
const workspaceControl = createWorkspaceControl({ client, trusted, noGitHub, repositories, refreshAll, requireLocalJobBinding, makeReconciler, concurrency, pool, bus, isEffectivelyBlocked, unblockTask, publishTaskUnblocked, dispatchTaskAction, registerBlockerDependency, discoverAgents, ensureManagedGitHubJob });
|
|
388
387
|
const secretFile = value(argv, '--github-webhook-secret-file');
|
|
389
388
|
const secret = noGitHub ? '' : value(argv, '--github-webhook-secret') || process.env.AGENTS_RELAY_GITHUB_WEBHOOK_SECRET || (secretFile ? (await readFile(secretFile, 'utf8')).trim() : '');
|
|
390
389
|
const webhook = secret ? {
|
|
@@ -474,7 +473,7 @@ export async function runDaemon(argv) {
|
|
|
474
473
|
job: {
|
|
475
474
|
id: job.id, title: job.title, objective: job.objective ?? null,
|
|
476
475
|
description: job.description ?? null, priority: job.priority ?? null,
|
|
477
|
-
|
|
476
|
+
repository: job.repository,
|
|
478
477
|
prNumber: job.prNumber, state: job.state,
|
|
479
478
|
},
|
|
480
479
|
task: {
|
|
@@ -525,17 +524,6 @@ export async function runDaemon(argv) {
|
|
|
525
524
|
observability().record({ stage: 'daemon', event: 'daemon.reconcile_failed', level: 'warn', outcome: 'failed', message: `Workspace reconcile unavailable: ${errorMessage(error)}`, data: { classification: classifyFailure(error).classification, reason: errorMessage(error) } });
|
|
526
525
|
}
|
|
527
526
|
};
|
|
528
|
-
const scheduleHeartbeatSafely = async () => {
|
|
529
|
-
const now = new Date();
|
|
530
|
-
for (const item of dueScheduledOverviews(cachedOverviews, now)) {
|
|
531
|
-
try {
|
|
532
|
-
await pool.scheduleTickSignal(bus, item.job, now);
|
|
533
|
-
}
|
|
534
|
-
catch (error) {
|
|
535
|
-
observability().record({ stage: 'daemon', event: 'daemon.schedule_heartbeat_failed', level: 'warn', outcome: 'failed', message: `Scheduled heartbeat unavailable: ${errorMessage(error)}`, correlation: { repository: item.job.repository, prNumber: item.job.prNumber, jobId: item.job.id }, data: { classification: classifyFailure(error).classification, reason: errorMessage(error) } });
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
527
|
const flushProjectionsSafely = async () => {
|
|
540
528
|
let externalActionsCompleted = 0;
|
|
541
529
|
for (const writer of projectionWriters.values()) {
|
|
@@ -552,18 +540,15 @@ export async function runDaemon(argv) {
|
|
|
552
540
|
};
|
|
553
541
|
const watchdogDefault = webhook ? SERVICE_DEFAULTS.webhookWatchdogMs : SERVICE_DEFAULTS.watchdogMs;
|
|
554
542
|
let interval;
|
|
555
|
-
let scheduleInterval;
|
|
556
543
|
let projectionInterval;
|
|
557
544
|
const stop = () => { stopRuntimeStatusEvents(); if (interval)
|
|
558
|
-
clearInterval(interval); if (
|
|
559
|
-
clearInterval(scheduleInterval); if (projectionInterval)
|
|
545
|
+
clearInterval(interval); if (projectionInterval)
|
|
560
546
|
clearInterval(projectionInterval); void Promise.allSettled([stopWatch(), shutdownOwnedProcesses('parent-shutdown')]).finally(() => { for (const store of relayStores.values())
|
|
561
547
|
store.close(); server.close(() => process.exit(0)); }); };
|
|
562
548
|
process.once('SIGINT', stop);
|
|
563
549
|
process.once('SIGTERM', stop);
|
|
564
550
|
await syncGitHubBindingsSafely();
|
|
565
551
|
await pool.reconcileKnown(cachedOverviews);
|
|
566
|
-
await scheduleHeartbeatSafely();
|
|
567
552
|
await flushProjectionsSafely();
|
|
568
553
|
interval = setInterval(() => void reconcileSafely().finally(flushProjectionsSafely), Number(value(argv, '--interval', String(watchdogDefault))));
|
|
569
554
|
interval.unref();
|
|
@@ -571,8 +556,6 @@ export async function runDaemon(argv) {
|
|
|
571
556
|
projectionInterval = setInterval(() => void syncGitHubBindingsSafely().then(flushProjectionsSafely), Number(value(argv, '--projection-ms', String(SERVICE_DEFAULTS.projectionFlushMs))));
|
|
572
557
|
projectionInterval.unref();
|
|
573
558
|
}
|
|
574
|
-
scheduleInterval = setInterval(() => void scheduleHeartbeatSafely(), SCHEDULE_HEARTBEAT_MS);
|
|
575
|
-
scheduleInterval.unref();
|
|
576
559
|
console.log('Dashboard listening; repository worker pool active');
|
|
577
560
|
void refreshAll(true)
|
|
578
561
|
.then(items => pool.reconcileKnown(items))
|
package/dist/runtime-status.js
CHANGED
|
@@ -26,23 +26,6 @@ export function deriveJobRuntimeStatus(job, _prState = 'OPEN') {
|
|
|
26
26
|
// Active task execution still takes precedence below.
|
|
27
27
|
const plannerFailed = currentPlan?.status === 'FAILED' && !job.tasks.some(task => ['QUEUED', 'RUNNING'].includes(task.state));
|
|
28
28
|
const plannerRunning = currentPlan?.status === 'PLANNING';
|
|
29
|
-
if (job.schedule) {
|
|
30
|
-
if (job.tasks.some(task => task.state === 'RUNNING'))
|
|
31
|
-
return 'RUNNING';
|
|
32
|
-
if (job.tasks.some(task => task.state === 'QUEUED'))
|
|
33
|
-
return 'QUEUED';
|
|
34
|
-
if (job.tasks.some(task => task.state === 'BLOCKED'))
|
|
35
|
-
return 'BLOCKED';
|
|
36
|
-
if (job.tasks.some(task => task.state === 'FAILED'))
|
|
37
|
-
return 'FAILED';
|
|
38
|
-
if (job.tasks.some(task => task.state === 'DRAFT'))
|
|
39
|
-
return 'WAITING';
|
|
40
|
-
if (plannerFailed)
|
|
41
|
-
return 'FAILED';
|
|
42
|
-
if (plannerRunning)
|
|
43
|
-
return 'RUNNING';
|
|
44
|
-
return 'WAITING';
|
|
45
|
-
}
|
|
46
29
|
// Draft or queued reviewers must not mask implementation that is still active.
|
|
47
30
|
const implementation = implementationTasks(job);
|
|
48
31
|
if (implementation.some(task => task.state === 'RUNNING'))
|
|
@@ -68,8 +51,6 @@ export function deriveJobRuntimeStatus(job, _prState = 'OPEN') {
|
|
|
68
51
|
return 'FAILED';
|
|
69
52
|
if (plannerRunning)
|
|
70
53
|
return 'RUNNING';
|
|
71
|
-
if (job.executionMode === 'autonomous' && job.autoMerge === false && job.reviewEnabled === false && implementationComplete(job))
|
|
72
|
-
return 'WAITING';
|
|
73
54
|
if (job.reviewEnabled === false && implementationComplete(job))
|
|
74
55
|
return 'MERGE_PENDING';
|
|
75
56
|
return 'IDLE';
|
|
@@ -14,7 +14,7 @@ export async function createSingleJobTaskControl(jobId, store, reconciler, bus)
|
|
|
14
14
|
await reconciler.idle();
|
|
15
15
|
} },
|
|
16
16
|
bus, isEffectivelyBlocked: unblock.isEffectivelyBlocked,
|
|
17
|
-
|
|
17
|
+
unblockTask: unblock.unblockTask,
|
|
18
18
|
publishTaskUnblocked, dispatchTaskAction,
|
|
19
19
|
});
|
|
20
20
|
const allowed = new Set(['task.unblock', 'task.recover', 'task.escalate', 'task.resume-troubleshooter', 'task.classify']);
|
package/dist/store.js
CHANGED
|
@@ -92,7 +92,7 @@ export class InMemoryStore {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
async load(jobId) { const job = await this.findJob(jobId); if (!job)
|
|
95
|
-
throw new Error(`Job ${jobId} not found`); job.plans = this.plansForJob(jobId); job.tasks = this.tasksForJob(jobId).map(task => normalizeLegacyTaskLifecycle(task
|
|
95
|
+
throw new Error(`Job ${jobId} not found`); job.plans = this.plansForJob(jobId); job.tasks = this.tasksForJob(jobId).map(task => normalizeLegacyTaskLifecycle(task)); return job; }
|
|
96
96
|
async mutateJob(jobId, mutation) {
|
|
97
97
|
const current = await this.load(jobId);
|
|
98
98
|
const next = mutation(structuredClone(current));
|
package/dist/task-actions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { extendRetryBudget, transitionTaskEvent, tryTransitionTaskEvent } from './
|
|
1
|
+
import { extendRetryBudget, transitionTaskEvent, tryTransitionTaskEvent } from './task-transitions.js';
|
|
2
2
|
import { selectRelatedTaskMutations } from './workflow/task-action-policy.js';
|
|
3
3
|
import { dispatchTaskResolutionSync, materializeResolution } from './workflow/task-resolution.js';
|
|
4
4
|
import { classifyTaskResolution } from './workflow/task-resolution.js';
|
|
@@ -59,7 +59,7 @@ function applyTaskRetrySnapshot(task, policy = {}) {
|
|
|
59
59
|
}
|
|
60
60
|
/** Fixed-job authoritative unblock continuation, applied inside one Job snapshot mutation. */
|
|
61
61
|
export function applyTaskUnblockRetrySnapshot(task) {
|
|
62
|
-
if (task.state === 'BLOCKED' && !tryTransitionTaskEvent(task, 'task.unblocked', {
|
|
62
|
+
if (task.state === 'BLOCKED' && !tryTransitionTaskEvent(task, 'task.unblocked', {}).accepted)
|
|
63
63
|
throw lifecycleRejection(task, 'retryable after unblock');
|
|
64
64
|
if (!['QUEUED', 'DRAFT'].includes(task.state))
|
|
65
65
|
throw lifecycleRejection(task, 'retryable after unblock');
|
|
@@ -81,7 +81,7 @@ export async function applyTaskRetryAction(task, policy = {}) {
|
|
|
81
81
|
/** Fixed-job authoritative unblock continuation: legality is owned by the lifecycle graph. */
|
|
82
82
|
export async function applyTaskUnblockRetryAction(task) {
|
|
83
83
|
if (task.state === 'BLOCKED')
|
|
84
|
-
await transitionTaskAction(task, 'task.unblocked', 'retryable after unblock', {
|
|
84
|
+
await transitionTaskAction(task, 'task.unblocked', 'retryable after unblock', {});
|
|
85
85
|
if (!['QUEUED', 'DRAFT'].includes(task.state))
|
|
86
86
|
throw lifecycleRejection(task, 'retryable after unblock');
|
|
87
87
|
task.attempt = 0;
|
package/dist/task-recovery.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { eventFor } from './events.js';
|
|
3
|
-
import { tryTransitionTaskEvent } from './
|
|
3
|
+
import { tryTransitionTaskEvent } from './task-transitions.js';
|
|
4
4
|
import { dispatchTaskResolutionSync, materializeResolution } from './workflow/task-resolution.js';
|
|
5
5
|
function activeRecovery(job, original) {
|
|
6
6
|
return job.tasks.find(task => task.agentName === 'troubleshooter'
|
|
@@ -112,7 +112,7 @@ export async function resumeTroubleshooter(store, jobId, recoveryTaskId, respons
|
|
|
112
112
|
throw new Error(`Escalation ${task.escalation.id} already has a response`);
|
|
113
113
|
if (task.state !== 'BLOCKED')
|
|
114
114
|
throw new Error(`Task ${recoveryTaskId} is not a blocked Troubleshooter escalation`);
|
|
115
|
-
if (!tryTransitionTaskEvent(task, 'task.unblocked', {
|
|
115
|
+
if (!tryTransitionTaskEvent(task, 'task.unblocked', {}).accepted)
|
|
116
116
|
throw new Error(`Troubleshooter ${recoveryTaskId} cannot be resumed`);
|
|
117
117
|
task.escalation = { ...task.escalation, status: 'responded', response: response.trim(), respondedAt: now };
|
|
118
118
|
task.result = null;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { implementationTasks } from './runtime-status.js';
|
|
2
|
+
import { dispatchJobLifecycle, dispatchTaskLifecycle, dispatchTaskLifecycleSync } from './workflow/lifecycle.js';
|
|
3
|
+
import { reconcileTaskDependencies } from './workflow/task-reconcile.js';
|
|
4
|
+
export { dependencyReady, detectCycles } from './workflow/task-reconcile.js';
|
|
5
|
+
import { observability } from './observability.js';
|
|
6
|
+
export function completionGateSatisfied(job) {
|
|
7
|
+
const latest = [...(job.plans ?? [])].sort((a, b) => a.round - b.round).at(-1);
|
|
8
|
+
const planSatisfied = latest?.status === 'SUCCEEDED' && latest.objectiveStatus === 'satisfied';
|
|
9
|
+
const legacySatisfied = (job.plans?.length ?? 0) === 0 && job.tasks.some(task => task.kind === 'planner' && task.state === 'SUCCEEDED' && task.plannerResult?.objective_status === 'satisfied');
|
|
10
|
+
const implementation = implementationTasks(job);
|
|
11
|
+
return (planSatisfied || legacySatisfied) && implementation.length > 0 && implementation.every(task => task.state === 'SUCCEEDED');
|
|
12
|
+
}
|
|
13
|
+
export function refreshTaskDependencies(job) {
|
|
14
|
+
if (job.pause)
|
|
15
|
+
return job;
|
|
16
|
+
reconcileTaskDependencies(job);
|
|
17
|
+
return job;
|
|
18
|
+
}
|
|
19
|
+
export function extendRetryBudget(task, windowSize = 3) {
|
|
20
|
+
if (!Number.isInteger(windowSize) || windowSize < 1)
|
|
21
|
+
throw new Error('Retry window size must be a positive integer');
|
|
22
|
+
const usedOrBudgeted = Math.max(task.maxAttempts, task.attempt);
|
|
23
|
+
return Math.max(windowSize, (Math.floor(usedOrBudgeted / windowSize) + 1) * windowSize);
|
|
24
|
+
}
|
|
25
|
+
export function tryTransitionTaskEvent(task, event, context = {}) {
|
|
26
|
+
return dispatchTaskLifecycleSync(task, event, context);
|
|
27
|
+
}
|
|
28
|
+
export async function transitionTaskEvent(task, event, context = {}, executeEffect, persist) {
|
|
29
|
+
const from = task.state;
|
|
30
|
+
const decision = await dispatchTaskLifecycle(task, event, context, executeEffect, persist);
|
|
31
|
+
if (!decision.accepted || !decision.target)
|
|
32
|
+
throw new Error(decision.reason === 'ambiguous'
|
|
33
|
+
? `Ambiguous workflow transition from ${from} on ${event}`
|
|
34
|
+
: `No enabled workflow transition from ${from} on ${event}`);
|
|
35
|
+
observability().record({
|
|
36
|
+
stage: 'transition',
|
|
37
|
+
event,
|
|
38
|
+
outcome: 'succeeded',
|
|
39
|
+
message: `Task ${task.id} ${from} -> ${decision.target}`,
|
|
40
|
+
correlation: { jobId: task.jobId, taskId: task.id, executionId: task.executionId ?? undefined },
|
|
41
|
+
transition: { graph: 'task-lifecycle', from, to: decision.target, actions: decision.actions },
|
|
42
|
+
});
|
|
43
|
+
return decision;
|
|
44
|
+
}
|
|
45
|
+
export async function transitionJobEvent(job, event, context = {}, executeEffect, persist) {
|
|
46
|
+
const from = job.state;
|
|
47
|
+
const decision = await dispatchJobLifecycle(job, event, context, executeEffect, persist);
|
|
48
|
+
if (!decision.accepted || !decision.target)
|
|
49
|
+
throw new Error(decision.reason === 'ambiguous'
|
|
50
|
+
? `Ambiguous workflow transition from ${from} on ${event}`
|
|
51
|
+
: `No enabled workflow transition from ${from} on ${event}`);
|
|
52
|
+
observability().record({
|
|
53
|
+
stage: 'transition',
|
|
54
|
+
event,
|
|
55
|
+
outcome: 'succeeded',
|
|
56
|
+
message: `Job ${job.id} ${from} -> ${decision.target}`,
|
|
57
|
+
correlation: { repository: job.repository, prNumber: job.prNumber, jobId: job.id },
|
|
58
|
+
transition: { graph: 'job-lifecycle', from, to: decision.target, actions: decision.actions },
|
|
59
|
+
});
|
|
60
|
+
return decision;
|
|
61
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { tryTransitionTaskEvent } from './
|
|
1
|
+
import { tryTransitionTaskEvent } from './task-transitions.js';
|
|
2
2
|
import { requestTroubleshooterEscalation } from './task-recovery.js';
|
|
3
3
|
const MARKER = '[Relay execution fallback attempted]';
|
|
4
4
|
const RUNTIME_FAILURE = /worker_process_failed|worker_start_timeout|terminal_event_timeout|model.*not supported|unsupported.model|delivery|process.exited|provider/i;
|
package/dist/troubleshooter.js
CHANGED
|
@@ -28,10 +28,7 @@ Inspect event order, type/status, source, data/phase, duplicate events, missing
|
|
|
28
28
|
|
|
29
29
|
Follow the selected Troubleshooter agent contract. Diagnose root cause from durable task state, append-only comments, task-scoped event history, and relevant runtime logs before retry. The final task.error string alone is never sufficient. Independently verify every human response against evidence from the exact original failing execution context; a response or historical resolution condition alone never proves recovery. Classify evidence as model/provider/runtime, worker/tool/infrastructure, semantic task failure, or timeout/unknown. Select one graph-enabled resolution path: exact_original_retry, same_job_dependency, blocker_job_dependency, escalation_gate, or cancel_supersede. For confirmed adapter/model/provider failure, include --failure-kind, --reason, --evidence and --reroute in task recover; use --exclude-current-route only if the exact failed route should not be selected again. For semantic task failure use --failure-kind task_failure without rerouting. For uncertain failures use --failure-kind unknown and preserve routing unless evidence justifies rerouting. Request the existing agents-relay task recover or agents-relay escalation operation; submission alone is not recovery. Reload authoritative SQLite state and verify the requested resolution and exact-original retry postconditions before reporting success. Bound self-healing retries to the durable Task retry budget. For a shared worker/runtime/tool defect, use its owning repository's P0/self-job workflow and verify the durable fix before requesting recovery. Never use ordinary task retry from a Troubleshooter because manual retry intentionally supersedes active recovery. Do not manually choose a provider/model or create a replacement task. If diagnosis suggests a genuine human/external blocker, invoke the escalation command; do not publish task.blocked or implement a workflow branch procedurally. The canonical escalation graph invokes Neo escalation and owns the resolved/failed/needs_user branch; Relay applies the selected outcome. Retry this exact original task only after the blocker is removed, reconcile the original job, and verify durable progress.`;
|
|
30
30
|
}
|
|
31
|
-
function belongsToCurrentRun(
|
|
32
|
-
const slot = job.schedule?.currentRunSlot;
|
|
33
|
-
return !slot || task.createdAt >= slot;
|
|
34
|
-
}
|
|
31
|
+
function belongsToCurrentRun(_job, _task) { return true; }
|
|
35
32
|
const ACTIVE_RECOVERY_STATES = new Set(['DRAFT', 'QUEUED', 'RUNNING', 'BLOCKED']);
|
|
36
33
|
function recoveryTargetsTask(recovery, original) {
|
|
37
34
|
if (recovery.agentName !== 'troubleshooter')
|
package/dist/types.js
CHANGED
|
@@ -4,8 +4,7 @@ export function storySizeTimeoutMs(size) { return STORY_SIZE_TIMEOUT_MS[size]; }
|
|
|
4
4
|
export function taskOverallTimeoutMs(size, reasoning) { return storySizeTimeoutMs(size) * (reasoning?.toLowerCase() === 'high' ? 2 : 1); }
|
|
5
5
|
export function chatGptStartAckTimeoutMs(overallTimeoutMs) { return Math.min(600000, Math.max(120000, Math.floor(overallTimeoutMs / 3))); }
|
|
6
6
|
/** Normalize snapshots written before UNBLOCKED was removed from the lifecycle graph. */
|
|
7
|
-
export function normalizeLegacyTaskLifecycle(task
|
|
8
|
-
void executionMode;
|
|
7
|
+
export function normalizeLegacyTaskLifecycle(task) {
|
|
9
8
|
const legacy = task;
|
|
10
9
|
if (legacy.state === 'UNBLOCKED') {
|
|
11
10
|
legacy.state = task.agentName === 'troubleshooter' ? 'QUEUED' : 'FAILED';
|
package/dist/unblock.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { applyTaskUnblockRetrySnapshot } from './task-actions.js';
|
|
2
|
-
import { tryTransitionTaskEvent } from './
|
|
2
|
+
import { tryTransitionTaskEvent } from './task-transitions.js';
|
|
3
3
|
import { dispatchAuthoritativeUnblock } from './workflow/authoritative-unblock-v2.js';
|
|
4
4
|
import { hasPendingTroubleshooterEscalation } from './task-recovery.js';
|
|
5
5
|
export function isEffectivelyBlocked(task) { return task.kind !== 'planner' && task.agentName === 'troubleshooter' && task.state === 'BLOCKED'; }
|
|
@@ -31,7 +31,7 @@ function unblockFacts(job, tasks, currentHeadSha, reason) {
|
|
|
31
31
|
return [...new Set(facts)];
|
|
32
32
|
}
|
|
33
33
|
export function unconsumedUnblockTrigger(job) {
|
|
34
|
-
const consumed = new Set((job.plans ?? []).filter(plan => plan.status === 'SUCCEEDED').flatMap(plan => plan.trigger
|
|
34
|
+
const consumed = new Set((job.plans ?? []).filter(plan => plan.status === 'SUCCEEDED').flatMap(plan => plan.trigger?.source === 'unblock' ? plan.trigger.taskIds ?? [] : []));
|
|
35
35
|
const tasks = job.tasks.filter(task => task.agentName !== 'troubleshooter' && task.unblock && !consumed.has(task.id) && ['DRAFT', 'QUEUED'].includes(task.state));
|
|
36
36
|
if (tasks.length === 0) {
|
|
37
37
|
const legacy = job.changeCycle;
|
|
@@ -56,12 +56,8 @@ export function unblockNeedsNewPlan(job) {
|
|
|
56
56
|
if (!pending)
|
|
57
57
|
return false;
|
|
58
58
|
const latest = job.plans?.at(-1);
|
|
59
|
-
return !(latest?.status === 'FAILED' && latest.trigger
|
|
60
|
-
&& latest.trigger
|
|
61
|
-
}
|
|
62
|
-
export async function unblockAutonomousTasks(options) {
|
|
63
|
-
void options;
|
|
64
|
-
throw new Error('Ordinary Tasks cannot be authoritatively unblocked; only a blocked Troubleshooter can resume');
|
|
59
|
+
return !(latest?.status === 'FAILED' && latest.trigger?.source === 'unblock'
|
|
60
|
+
&& latest.trigger?.taskIds?.some(id => pending.taskIds?.includes(id)));
|
|
65
61
|
}
|
|
66
62
|
export async function unblockTask(options) {
|
|
67
63
|
if (!isEffectivelyBlocked(options.task))
|
|
@@ -83,7 +79,7 @@ export async function unblockTask(options) {
|
|
|
83
79
|
const task = current.tasks.find(item => item.id === options.task.id);
|
|
84
80
|
if (!task || !isEffectivelyBlocked(task))
|
|
85
81
|
throw new Error(`Task ${options.task.id} changed before authoritative unblock`);
|
|
86
|
-
if (!tryTransitionTaskEvent(task, 'task.unblocked', {
|
|
82
|
+
if (!tryTransitionTaskEvent(task, 'task.unblocked', {}).accepted)
|
|
87
83
|
throw new Error(`Task ${task.id} cannot be authoritatively unblocked`);
|
|
88
84
|
const baseline = baselineFor(task);
|
|
89
85
|
task.blockedBaseline = baseline;
|
package/dist/worker-rejection.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Classify genuine worker-process rejection text
|
|
1
|
+
/** Classify genuine worker-process rejection text. */
|
|
2
2
|
export function classifyWorkerRejection(message) {
|
|
3
3
|
if (/rate[_ .-]?limit[_ .-]?exceeded|usage limit reached|quota[_ .-]?exceeded|insufficient[_ .-]?quota/i.test(message))
|
|
4
4
|
return 'rate_limit_exceeded';
|
|
@@ -5,13 +5,12 @@ const source = readFileSync(new URL('../../workflow/blocker-resolution.mmd', imp
|
|
|
5
5
|
const boolGuard = (name) => (facts) => facts.durableFacts[name] === true;
|
|
6
6
|
export const blockerResolutionV2 = new GraphRuntime(source, {
|
|
7
7
|
states: new Set([
|
|
8
|
-
'waiting', 'check_target', '
|
|
8
|
+
'waiting', 'check_target', 'check_effective', 'check_already_unblocked',
|
|
9
9
|
'check_draft', 'check_blocked', 'check_failed', 'check_unblocked', 'wait', 'obsolete', 'resumed',
|
|
10
10
|
]),
|
|
11
11
|
guards: {
|
|
12
12
|
resolutionAllowed: boolGuard('resolutionAllowed'),
|
|
13
13
|
targetMatches: boolGuard('targetMatches'),
|
|
14
|
-
autonomousJob: boolGuard('autonomousJob'),
|
|
15
14
|
effectivelyBlocked: boolGuard('effectivelyBlocked'),
|
|
16
15
|
alreadyUnblocked: boolGuard('alreadyUnblocked'),
|
|
17
16
|
isDraft: boolGuard('isDraft'),
|
|
@@ -20,8 +19,7 @@ export const blockerResolutionV2 = new GraphRuntime(source, {
|
|
|
20
19
|
isUnblocked: boolGuard('isUnblocked'),
|
|
21
20
|
},
|
|
22
21
|
actions: Object.fromEntries([
|
|
23
|
-
'blocker.
|
|
24
|
-
'blocker.unblockTask', 'blocker.retryFailed', 'blocker.retryUnblocked',
|
|
22
|
+
'blocker.releaseDraft',
|
|
25
23
|
].map(id => [id, { id }])),
|
|
26
24
|
events: { 'blocker.resolved': { id: 'blocker.resolved' } },
|
|
27
25
|
});
|
|
@@ -5,7 +5,7 @@ import { WORKFLOW_EFFECT_NAMES, WORKFLOW_EVENT_NAMES, WORKFLOW_MUTATION_NAMES, }
|
|
|
5
5
|
const TASK_STATES = ['DRAFT', 'QUEUED', 'RUNNING', 'BLOCKED', 'SUCCEEDED', 'FAILED', 'CANCELLED'];
|
|
6
6
|
const JOB_STATES = ['DRAFT', 'ACTIVE', 'COMPLETED', 'CANCELLED'];
|
|
7
7
|
const conditionRegistry = new ConditionRegistry([
|
|
8
|
-
...new Set(['dependenciesSatisfied', 'dependenciesUnsatisfied', 'runnable', 'dispatchable', 'recoverableExecution', 'troubleshooterRecovery', 'troubleshooterTask', 'awaitingInteractiveDecision', 'retryAvailable', '
|
|
8
|
+
...new Set(['dependenciesSatisfied', 'dependenciesUnsatisfied', 'runnable', 'dispatchable', 'recoverableExecution', 'troubleshooterRecovery', 'troubleshooterTask', 'awaitingInteractiveDecision', 'retryAvailable', 'notMerged', 'autoMerge', 'prOpen', 'reviewDisabled', 'implementationComplete', 'headAvailable', 'mergeCapable', 'localCompletionAllowed', 'localMergeReady'])
|
|
9
9
|
].map(booleanCondition));
|
|
10
10
|
const boolGuard = (name) => conditionRegistry.guard(name);
|
|
11
11
|
export const lifecycleConditions = conditionRegistry;
|
|
@@ -34,7 +34,6 @@ export const taskLifecycleV2 = load('task-lifecycle', TASK_STATES, {
|
|
|
34
34
|
troubleshooterTask: boolGuard('troubleshooterTask'),
|
|
35
35
|
awaitingInteractiveDecision: facts => facts.durableFacts.awaitingInteractiveDecision === true,
|
|
36
36
|
retryAvailable: boolGuard('retryAvailable'),
|
|
37
|
-
autonomousJob: boolGuard('autonomousJob'),
|
|
38
37
|
});
|
|
39
38
|
export const jobLifecycleV2 = load('job-lifecycle', JOB_STATES, {
|
|
40
39
|
notMerged: boolGuard('notMerged'),
|
|
@@ -46,15 +45,13 @@ export const jobLifecycleV2 = load('job-lifecycle', JOB_STATES, {
|
|
|
46
45
|
mergeCapable: boolGuard('mergeCapable'),
|
|
47
46
|
localCompletionAllowed: (facts) => {
|
|
48
47
|
const values = facts.durableFacts;
|
|
49
|
-
return values.jobActive === true && values.autoMergeEnabled === true && values.
|
|
50
|
-
&& values.implementationComplete === true && values.completionGateSatisfied === true
|
|
48
|
+
return values.jobActive === true && values.autoMergeEnabled === true && values.implementationComplete === true && values.completionGateSatisfied === true
|
|
51
49
|
&& values.reviewSatisfied === true
|
|
52
50
|
&& (values.localMergeRequired !== true || values.localMergeSucceeded === true);
|
|
53
51
|
},
|
|
54
52
|
localMergeReady: (facts) => {
|
|
55
53
|
const values = facts.durableFacts;
|
|
56
|
-
return values.jobActive === true && values.autoMergeEnabled === true && values.
|
|
57
|
-
&& values.implementationComplete === true && values.completionGateSatisfied === true
|
|
54
|
+
return values.jobActive === true && values.autoMergeEnabled === true && values.implementationComplete === true && values.completionGateSatisfied === true
|
|
58
55
|
&& values.reviewSatisfied === true && values.localMergePresent === true
|
|
59
56
|
&& values.reviewHeadCurrent === true && values.mergeAttemptReady === true;
|
|
60
57
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { tryTransitionTaskEvent } from '../
|
|
1
|
+
import { tryTransitionTaskEvent } from '../task-transitions.js';
|
|
2
2
|
import { observability } from '../observability.js';
|
|
3
3
|
/**
|
|
4
4
|
* Runtime primitive for applying one canonical Task-graph event to fresh durable state.
|
|
@@ -3,11 +3,12 @@ import { GraphRuntime } from './graph-v2.js';
|
|
|
3
3
|
import { dispatchWorkflowGraphSync } from './dispatcher.js';
|
|
4
4
|
const source = readFileSync(new URL('../../workflow/force-restate-validation.mmd', import.meta.url), 'utf8');
|
|
5
5
|
export const forceRestateValidationV2 = new GraphRuntime(source, {
|
|
6
|
-
states: new Set(['idle', 'check_history_mode', 'check_history', 'check_normal_history', 'check_forward', 'rejected', 'committed']),
|
|
6
|
+
states: new Set(['idle', 'check_history_mode', 'check_history', 'check_normal_history', 'check_satisfied', 'check_forward', 'rejected', 'committed']),
|
|
7
7
|
guards: {
|
|
8
8
|
forceRestateRequested: facts => facts.durableFacts.forceRestateRequested === true,
|
|
9
9
|
historyConsistencyValid: facts => facts.durableFacts.historyConsistencyValid === true,
|
|
10
10
|
normalHistoryMutationAbsent: facts => facts.durableFacts.normalHistoryMutationAbsent === true,
|
|
11
|
+
objectiveSatisfied: facts => facts.durableFacts.objectiveSatisfied === true,
|
|
11
12
|
forwardConvergenceValid: facts => facts.durableFacts.forwardConvergenceValid === true,
|
|
12
13
|
},
|
|
13
14
|
actions: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { dispatchWorkflowGraphSync } from './dispatcher.js';
|
|
3
3
|
import { GraphRuntime } from './graph-v2.js';
|
|
4
|
-
import { tryTransitionTaskEvent } from '../
|
|
4
|
+
import { tryTransitionTaskEvent } from '../task-transitions.js';
|
|
5
5
|
import { taskOverallTimeoutMs } from '../types.js';
|
|
6
6
|
export const STALE_ORCHESTRATOR_TASK_MS = 30 * 60 * 1000;
|
|
7
7
|
const source = readFileSync(new URL('../../workflow/orchestrator-rescue.mmd', import.meta.url), 'utf8');
|
|
@@ -10,7 +10,6 @@ export const plannerOrchestrationV2 = new GraphRuntime(source, {
|
|
|
10
10
|
'check_blocked', 'check_work', 'check_materialized', 'check_objective', 'wait', 'recovered', 'created',
|
|
11
11
|
]),
|
|
12
12
|
guards: {
|
|
13
|
-
autonomousJob: boolGuard('autonomousJob'),
|
|
14
13
|
legacyPlanningPending: boolGuard('legacyPlanningPending'),
|
|
15
14
|
recoverablePendingPlan: boolGuard('recoverablePendingPlan'),
|
|
16
15
|
terminalPlanFailure: boolGuard('terminalPlanFailure'),
|
|
@@ -38,9 +37,8 @@ export function decidePlannerOrchestration(facts) {
|
|
|
38
37
|
return dispatchWorkflowGraphSync(plannerOrchestrationV2, { initialState: 'idle', input: { transport: 'event', id: 'planner.reconcile', payload: {} }, durableFacts: facts, graphName: 'planner-orchestration', isTransientState: state => state.startsWith('check_'), maxSteps: 14 });
|
|
39
38
|
}
|
|
40
39
|
export function plannerOrchestrationFacts(job, recoverablePendingPlan) {
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
const scopedPlans = (job.plans ?? []).filter(plan => !Number.isFinite(slot) || Date.parse(plan.createdAt) >= slot)
|
|
40
|
+
const scopedTasks = job.tasks;
|
|
41
|
+
const scopedPlans = (job.plans ?? [])
|
|
44
42
|
.sort((a, b) => a.round - b.round || a.createdAt.localeCompare(b.createdAt));
|
|
45
43
|
const latestPlan = scopedPlans.at(-1);
|
|
46
44
|
const failedPlan = latestPlan?.status === 'FAILED' ? latestPlan : null;
|
|
@@ -56,7 +54,6 @@ export function plannerOrchestrationFacts(job, recoverablePendingPlan) {
|
|
|
56
54
|
const latestId = latestPlan?.id ?? legacyLatest?.id;
|
|
57
55
|
const latestObjective = latestPlan?.objectiveStatus ?? legacyLatest?.plannerResult?.objective_status;
|
|
58
56
|
return {
|
|
59
|
-
autonomousJob: job.executionMode === 'autonomous' || latestPlan?.forceRestate === true,
|
|
60
57
|
legacyPlanningPending: job.initialPlanning?.status === 'pending',
|
|
61
58
|
recoverablePendingPlan,
|
|
62
59
|
terminalPlanFailure: Boolean(failedPlan && (!failedPlan.manualPlanning || failedPlan.forceRestate) && failedPlan.trigger.source !== 'unblock'),
|
|
@@ -4,11 +4,10 @@ import { dispatchWorkflowGraphSync } from './dispatcher.js';
|
|
|
4
4
|
const source = readFileSync(new URL('../../workflow/planner-trigger.mmd', import.meta.url), 'utf8');
|
|
5
5
|
const boolGuard = (name) => (facts) => facts.durableFacts[name] === true;
|
|
6
6
|
export const plannerTriggerV2 = new GraphRuntime(source, {
|
|
7
|
-
states: new Set(['idle', 'check_unblock', '
|
|
7
|
+
states: new Set(['idle', 'check_unblock', 'check_initial', 'manual', 'unblock', 'initial', 'continuation']),
|
|
8
8
|
guards: {
|
|
9
9
|
manualPlan: boolGuard('manualPlan'),
|
|
10
10
|
hasUnblock: boolGuard('hasUnblock'),
|
|
11
|
-
scheduledRun: boolGuard('scheduledRun'),
|
|
12
11
|
initialPlan: boolGuard('initialPlan'),
|
|
13
12
|
},
|
|
14
13
|
actions: {},
|
|
@@ -6,7 +6,6 @@ const boolGuard = (name) => (facts) => facts.durableFacts[name] === true;
|
|
|
6
6
|
export const reviewLifecycleV2 = new GraphRuntime(source, {
|
|
7
7
|
states: new Set(['NONE', 'PENDING', 'STARTED', 'APPROVED', 'CHANGES_REQUIRED', 'BLOCKED', 'STALE']),
|
|
8
8
|
guards: {
|
|
9
|
-
unscheduled: boolGuard('unscheduled'),
|
|
10
9
|
reviewEnabled: boolGuard('reviewEnabled'),
|
|
11
10
|
prOpen: boolGuard('prOpen'),
|
|
12
11
|
jobActive: boolGuard('jobActive'),
|
|
@@ -20,15 +20,9 @@ export function detectCycles(tasks) {
|
|
|
20
20
|
for (const task of tasks)
|
|
21
21
|
visit(task.id);
|
|
22
22
|
}
|
|
23
|
-
function scheduledCycleTasks(job) {
|
|
24
|
-
const slot = job.schedule?.currentRunSlot ? Date.parse(job.schedule.currentRunSlot) : Number.NaN;
|
|
25
|
-
if (!Number.isFinite(slot))
|
|
26
|
-
return job.tasks;
|
|
27
|
-
return job.tasks.filter(task => Date.parse(task.createdAt) >= slot);
|
|
28
|
-
}
|
|
29
23
|
export function reconcileTaskDependencies(job) {
|
|
30
24
|
detectCycles(job.tasks);
|
|
31
|
-
const scopedTasks =
|
|
25
|
+
const scopedTasks = job.tasks;
|
|
32
26
|
const tasks = new Map(job.tasks.map(task => [task.id, task]));
|
|
33
27
|
for (const task of scopedTasks) {
|
|
34
28
|
if (task.kind === 'planner')
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { transitionJobEvent, transitionTaskEvent } from './
|
|
1
|
+
import { transitionJobEvent, transitionTaskEvent } from './task-transitions.js';
|
|
2
2
|
import { workflowEffectFailureEvent } from './workflow/contracts.js';
|
|
3
3
|
import { eventFor } from './events.js';
|
|
4
4
|
export async function applyJobEffectOutcomes(store, emit, job, outcomes) {
|
package/dist/workspace.js
CHANGED
|
@@ -191,7 +191,7 @@ export function managedLocalJobWorktreePath(repository, jobId, workspaceRoot = d
|
|
|
191
191
|
}
|
|
192
192
|
/** Only code artifacts need an isolated Git source branch and worktree. */
|
|
193
193
|
export function jobRequiresManagedWorktree(job) {
|
|
194
|
-
return !(job.prNumber > 0) &&
|
|
194
|
+
return !(job.prNumber > 0) && (Boolean(job.localMerge?.sourceRef) || job.tasks.some(task => task.output?.kind === 'task_pr'));
|
|
195
195
|
}
|
|
196
196
|
/** Bind an ordinary SQLite Job to one deterministic local work branch.
|
|
197
197
|
* The target is always main, and branch creation never switches the user's checkout.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agents-relay",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.170",
|
|
4
4
|
"description": "Durable async agent jobs coordinated through GitHub pull requests",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,12 +17,11 @@
|
|
|
17
17
|
"test:chatgpt-temp-quality": "AGENTS_RELAY_RUN_CHATGPT_TEMP_QUALITY=1 sh -c \"npm run build && node test/quality/run-temporary-chat.mjs\"",
|
|
18
18
|
"dev": "tsx src/cli.ts",
|
|
19
19
|
"test:publish": "npm run build && node --import ./test/test-store-env.mjs --test --test-concurrency=1 --test-force-exit test/*.test.js",
|
|
20
|
-
"test:fast": "npm run build && node --import ./test/test-store-env.mjs --test --test-concurrency=2 test/workflow-lifecycle.test.js test/workflow-v2-lifecycle-parity.test.js test/workflow-effects.test.js test/relay-store.test.js test/local-authority.test.js test/local-jobid-routing.test.js test/local-sqlite-jobid.test.js test/no-github.test.js test/
|
|
20
|
+
"test:fast": "npm run build && node --import ./test/test-store-env.mjs --test --test-concurrency=2 test/workflow-lifecycle.test.js test/workflow-v2-lifecycle-parity.test.js test/workflow-effects.test.js test/relay-store.test.js test/local-authority.test.js test/local-jobid-routing.test.js test/local-sqlite-jobid.test.js test/no-github.test.js test/reconcile-coordinator.test.js test/service-publication.test.js test/github-webhook.test.js",
|
|
21
21
|
"test:full": "npm run build && node --import ./test/test-store-env.mjs --test --test-concurrency=1 test/*.test.js && npm run test:verification:l3",
|
|
22
22
|
"test:verification:l1": "npm run build && node --test test/verification/harness.test.mjs test/verification/contract.test.mjs test/verification/graph-inventory.test.mjs test/verification/dashboard-drilldown.test.mjs test/verification/evidence-matrix.test.mjs test/verification/task-resolution-classification.test.mjs test/verification/planner-contract.test.mjs test/verification/force-restate-validation.test.mjs test/planner-child-recovery.test.js",
|
|
23
|
-
"test:verification:l3": "npm run build && node --test --test-concurrency=1 test/verification/harness.test.mjs test/verification/l3-sqlite.test.mjs test/verification/l3-ingress.test.mjs test/verification/l3-replay.test.mjs test/verification/objective-authority.test.mjs test/verification/l3-convergence.test.mjs test/verification/l3-review-convergence.test.mjs test/verification/l3-human-escalation.test.mjs test/verification/l3-escalation-notifier.test.mjs test/verification/l3-unknown-effect.test.mjs test/verification/l3-cli-actions.test.mjs test/verification/l3-planner-round.test.mjs test/verification/l3-planner-cli.test.mjs test/verification/l3-task-resolution.test.mjs test/verification/l3-plan-apply-convergence.test.mjs test/verification/full-recovery-gates.test.mjs test/verification/production-wiring.test.mjs test/verification/l3-full-path.test.mjs test/verification/l3-projection-recovery.test.mjs test/verification/l3-daemon-startup-recovery.test.mjs",
|
|
23
|
+
"test:verification:l3": "npm run build && node --test --test-concurrency=1 test/verification/harness.test.mjs test/local-sqlite-jobid.test.js test/verification/l3-sqlite.test.mjs test/verification/l3-ingress.test.mjs test/verification/l3-replay.test.mjs test/verification/objective-authority.test.mjs test/verification/l3-convergence.test.mjs test/verification/l3-review-convergence.test.mjs test/verification/l3-human-escalation.test.mjs test/verification/l3-escalation-notifier.test.mjs test/verification/l3-unknown-effect.test.mjs test/verification/l3-cli-actions.test.mjs test/verification/l3-planner-round.test.mjs test/verification/l3-planner-cli.test.mjs test/verification/l3-task-resolution.test.mjs test/verification/l3-plan-apply-convergence.test.mjs test/verification/full-recovery-gates.test.mjs test/verification/production-wiring.test.mjs test/verification/l3-full-path.test.mjs test/verification/l3-projection-recovery.test.mjs test/verification/l3-daemon-startup-recovery.test.mjs",
|
|
24
24
|
"test:verification": "npm run test:verification:l1 && npm run test:verification:l2 && npm run test:verification:l3 && npm run test:verification:cross-a && node --test test/workflow-production-outbox.test.js && npm run test:verification:matrix",
|
|
25
|
-
"e2e": "node e2e/runner/run.mjs",
|
|
26
25
|
"test:verification:cross-a": "npm run build && RELAY_A_DIST=$PWD/dist node --test test/verification/action-ledger-cross-branch.test.mjs test/verification/outbox-cross-a.test.mjs",
|
|
27
26
|
"test:verification:matrix": "node test/verification/recovery-report.mjs /tmp/agents-relay-job481-evidence",
|
|
28
27
|
"test:verification:l2": "npm run build && node --test test/verification/l2/*.test.mjs",
|