agents-relay 2.0.68 → 2.0.70

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.
Files changed (56) hide show
  1. package/README.md +32 -1
  2. package/dist/adapters.js +39 -3
  3. package/dist/blockers.js +17 -4
  4. package/dist/cli-blocker.js +12 -28
  5. package/dist/cli-help.js +11 -8
  6. package/dist/cli-job-create.js +19 -13
  7. package/dist/cli-local-merge-config.js +4 -2
  8. package/dist/cli-projection.js +23 -55
  9. package/dist/cli-review.js +7 -24
  10. package/dist/cli-support.js +59 -4
  11. package/dist/cli-worker-task.js +63 -0
  12. package/dist/cli.js +179 -129
  13. package/dist/dashboard-html.js +15 -12
  14. package/dist/dashboard.js +26 -1
  15. package/dist/e2e-injection.js +76 -0
  16. package/dist/github-binding-order.js +15 -0
  17. package/dist/github-client.js +7 -2
  18. package/dist/github-pr-archive.js +22 -0
  19. package/dist/github-webhook.js +31 -6
  20. package/dist/job-actions.js +29 -0
  21. package/dist/local-authority-store.js +10 -1
  22. package/dist/local-commit-events.js +2 -0
  23. package/dist/local-git-merge.js +17 -6
  24. package/dist/local-job-path.js +5 -0
  25. package/dist/local-job-registry.js +10 -4
  26. package/dist/local-managed-store.js +2 -4
  27. package/dist/local-merge-delivery.js +3 -2
  28. package/dist/local-merge-projection.js +60 -0
  29. package/dist/managed-worker-input.js +13 -6
  30. package/dist/manual-plan.js +29 -7
  31. package/dist/planner-runtime.js +182 -41
  32. package/dist/planner.js +35 -2
  33. package/dist/pool.js +22 -6
  34. package/dist/reconciler-cancellation.js +21 -0
  35. package/dist/reconciler-review.js +74 -18
  36. package/dist/reconciler-runtime.js +69 -34
  37. package/dist/reconciler.js +92 -26
  38. package/dist/relay-store.js +1 -1
  39. package/dist/relayd-control.js +184 -0
  40. package/dist/relayd-dashboard-actions.js +108 -0
  41. package/dist/relayd-utilities.js +107 -0
  42. package/dist/relayd.js +220 -210
  43. package/dist/review-generation-fence.js +12 -0
  44. package/dist/runtime-status-events.js +21 -0
  45. package/dist/runtime-status.js +13 -0
  46. package/dist/service-publication.js +2 -4
  47. package/dist/task-actions.js +59 -1
  48. package/dist/troubleshooter.js +21 -2
  49. package/dist/unblock.js +47 -15
  50. package/dist/worker-rejection.js +11 -0
  51. package/dist/workflow-effect-failures.js +30 -0
  52. package/dist/workspace.js +95 -6
  53. package/package.json +5 -4
  54. package/skills/agents-relay/agents/planner.agent.md +3 -0
  55. package/skills/agents-relay/contracts/orchestrator-workflows.md +4 -7
  56. package/workflow/review-lifecycle.mmd +3 -0
@@ -0,0 +1,184 @@
1
+ export function createWorkspaceControl(deps) {
2
+ const { client, trusted, noGitHub, repositories, refreshAll, requireLocalJobBinding, makeReconciler, concurrency, pool, bus, isEffectivelyBlocked, unblockAutonomousTasks, unblockTask, publishTaskUnblocked, dispatchTaskAction, registerBlockerDependency, discoverAgents, ensureManagedGitHubJob } = deps;
3
+ const workspaceControl = async (body) => {
4
+ const command = typeof body.command === 'string' ? body.command : '';
5
+ const jobId = typeof body.jobId === 'string' ? body.jobId : '';
6
+ if (!command)
7
+ throw new Error('control command is required');
8
+ if (command === 'job.create') {
9
+ const repository = typeof body.repository === 'string' ? body.repository : '';
10
+ const options = (body.options && typeof body.options === 'object' ? body.options : {});
11
+ if (!repository || typeof options.id !== 'string' || !options.id)
12
+ throw new Error('job.create requires repository and id');
13
+ const result = await ensureManagedGitHubJob(client, repository, trusted, {
14
+ id: options.id,
15
+ title: typeof options.title === 'string' ? options.title : undefined,
16
+ objective: typeof options.objective === 'string' ? options.objective : undefined,
17
+ projectPath: typeof options.projectPath === 'string' ? options.projectPath : undefined,
18
+ priority: options.priority,
19
+ executionMode: options.executionMode,
20
+ schedule: options.schedule,
21
+ tag: options.tag,
22
+ reviewEnabled: typeof options.reviewEnabled === 'boolean' ? options.reviewEnabled : undefined,
23
+ continuation: options.continuation,
24
+ prNumber: typeof options.prNumber === 'number' ? options.prNumber : undefined,
25
+ head: typeof options.head === 'string' ? options.head : undefined,
26
+ base: typeof options.base === 'string' ? options.base : undefined,
27
+ body: typeof options.body === 'string' ? options.body : undefined,
28
+ reviewerThreadId: typeof options.reviewerThreadId === 'string' ? options.reviewerThreadId : undefined,
29
+ creatorThreadId: typeof options.creatorThreadId === 'string' ? options.creatorThreadId : undefined,
30
+ noGitHub,
31
+ localMergeSource: typeof options.localMergeSource === 'string' ? options.localMergeSource : undefined,
32
+ localMergeTarget: typeof options.localMergeTarget === 'string' ? options.localMergeTarget : undefined,
33
+ });
34
+ if (!repositories.includes(repository))
35
+ repositories.push(repository);
36
+ await refreshAll();
37
+ if (body.initialPlan === true) {
38
+ const binding = await requireLocalJobBinding(result.job.id);
39
+ const reconciler = makeReconciler(binding.store, concurrency);
40
+ try {
41
+ const planned = await reconciler.planJob(result.job.id);
42
+ await pool.reconcileLocalJob(result.job.id, 'control.job.create.initial-plan');
43
+ return { repository, pr: result.pr.number, job: result.job.id, executionMode: result.job.executionMode, state: result.job.state, initialPlan: planned.round.id };
44
+ }
45
+ finally {
46
+ await reconciler.close();
47
+ }
48
+ }
49
+ return { repository, pr: result.pr.number, job: result.job.id, executionMode: result.job.executionMode, state: result.job.state };
50
+ }
51
+ if (!jobId)
52
+ throw new Error(command + ' requires jobId');
53
+ const binding = await requireLocalJobBinding(jobId);
54
+ const store = binding.store;
55
+ const job = await store.load(jobId);
56
+ if (command === 'job.update') {
57
+ const patch = (body.patch && typeof body.patch === 'object' ? body.patch : {});
58
+ const mutable = ['title', 'objective', 'projectPath', 'priority', 'executionMode', 'schedule', 'tag', 'autoMerge', 'reviewEnabled', 'reviewerThreadId', 'creatorThreadId', 'continuation', 'localMerge'];
59
+ for (const key of mutable)
60
+ if (Object.prototype.hasOwnProperty.call(patch, key))
61
+ job[key] = patch[key];
62
+ job.updatedAt = new Date().toISOString();
63
+ await store.saveJob(job);
64
+ await refreshAll();
65
+ return await store.load(jobId);
66
+ }
67
+ if (command === 'job.plan') {
68
+ const reconciler = makeReconciler(store, concurrency);
69
+ try {
70
+ const planned = await reconciler.planJob(jobId, typeof body.guidance === 'string' && body.guidance.trim() ? body.guidance : undefined);
71
+ await pool.reconcileLocalJob(jobId, 'control.job.plan');
72
+ return planned.round;
73
+ }
74
+ finally {
75
+ await reconciler.close();
76
+ }
77
+ }
78
+ if (command === 'job.unblock') {
79
+ const reason = typeof body.reason === 'string' ? body.reason : '';
80
+ if (!reason.trim())
81
+ throw new Error('job unblock requires reason');
82
+ const blocked = job.tasks.filter(isEffectivelyBlocked);
83
+ if (!blocked.length)
84
+ throw new Error(`Job ${job.id} has no effective BLOCKED work task`);
85
+ if (job.executionMode === 'autonomous')
86
+ await unblockAutonomousTasks({ store, job, tasks: blocked, trigger: { source: 'cli', reason } });
87
+ else
88
+ for (const task of blocked)
89
+ await unblockTask({ store, job, task, trigger: { source: 'cli', reason } });
90
+ const refreshed = await store.load(jobId);
91
+ for (const task of blocked)
92
+ await publishTaskUnblocked(bus, refreshed, task.id, reason);
93
+ await pool.reconcileLocalJob(jobId, 'control.job.unblock');
94
+ return await store.load(jobId);
95
+ }
96
+ if (command === 'task.recover') {
97
+ const taskId = typeof body.taskId === 'string' ? body.taskId : '';
98
+ const recoveryTaskId = typeof body.recoveryTaskId === 'string' ? body.recoveryTaskId : '';
99
+ if (!taskId || !recoveryTaskId)
100
+ throw new Error('task recover requires taskId and recoveryTaskId');
101
+ await dispatchTaskAction(store, { id: 'task.recover', jobId, taskId, recoveryTaskId });
102
+ await pool.reconcileLocalJob(jobId, 'control.task.recover');
103
+ return await store.load(jobId);
104
+ }
105
+ if (command === 'task.unblock') {
106
+ const taskId = typeof body.taskId === 'string' ? body.taskId : '';
107
+ const reason = typeof body.reason === 'string' ? body.reason : '';
108
+ const task = job.tasks.find((item) => item.id === taskId);
109
+ if (!task)
110
+ throw new Error(`Task ${taskId} not found`);
111
+ await unblockTask({ store, job, task, trigger: { source: 'cli', reason } });
112
+ const refreshed = await store.load(jobId);
113
+ await publishTaskUnblocked(bus, refreshed, taskId, reason);
114
+ await pool.reconcileLocalJob(jobId, 'control.task.unblock');
115
+ return await store.load(jobId);
116
+ }
117
+ if (command === 'review.request') {
118
+ if (job.state === 'CANCELLED' || job.state === 'COMPLETED')
119
+ throw new Error('Cannot request review for terminal Job');
120
+ if (!job.reviewEnabled)
121
+ throw new Error('Review disabled for this Job');
122
+ if (typeof body.threadId === 'string') {
123
+ job.reviewerThreadId = body.threadId;
124
+ job.updatedAt = new Date().toISOString();
125
+ await store.saveJob(job);
126
+ }
127
+ await pool.reconcileLocalJob(jobId, 'control.review.request');
128
+ return await store.load(jobId);
129
+ }
130
+ if (command === 'blocker.add') {
131
+ const dependentJobId = typeof body.dependentJobId === 'string' ? body.dependentJobId : '';
132
+ const dependentTaskId = typeof body.dependentTaskId === 'string' ? body.dependentTaskId : '';
133
+ const dependent = await requireLocalJobBinding(dependentJobId);
134
+ const relation = await registerBlockerDependency({ blockerStore: store, blockerJobId: jobId, dependentStore: dependent.store, dependentJobId, dependentTaskId, resolutionMode: body.resolutionMode === 'verified' ? 'verified' : 'merge' });
135
+ await refreshAll();
136
+ return relation;
137
+ }
138
+ if (command === 'blocker.resolve') {
139
+ if (!store.resolveBlockerDependents)
140
+ throw new Error('Blocker resolution unavailable');
141
+ const summary = await store.resolveBlockerDependents(jobId, body.resolution === 'merge' ? 'merge' : 'verified');
142
+ for (const target of summary.unblocked)
143
+ await pool.reconcileLocalJob(target.jobId, 'control.blocker.resolve');
144
+ await refreshAll();
145
+ return summary;
146
+ }
147
+ if (command === 'projection.conflicts')
148
+ return await binding.relay.listProjectionConflicts();
149
+ if (command === 'job.reconcile') {
150
+ await pool.reconcileLocalJob(jobId, 'control.job.reconcile');
151
+ return await store.load(jobId);
152
+ }
153
+ if (command === 'agent.discover') {
154
+ return discoverAgents(await store.listAgents(), (body.requirements && typeof body.requirements === 'object' ? body.requirements : {}));
155
+ }
156
+ if (command === 'projection.resolve') {
157
+ const id = Number(body.projectionId);
158
+ const action = body.action === 'discard_local' ? 'discard_local' : 'requeue';
159
+ if (!Number.isInteger(id) || id <= 0)
160
+ throw new Error('projection resolve requires projectionId');
161
+ const resolved = await binding.relay.resolveProjectionConflict(id, { action, ...(typeof body.note === 'string' && body.note ? { note: body.note } : {}) });
162
+ if (!resolved)
163
+ throw new Error(`Projection ${id} is not in a resolvable conflict state`);
164
+ return { resolved: id, action };
165
+ }
166
+ if (command === 'record') {
167
+ const task = body.task;
168
+ if (!task || task.jobId !== jobId)
169
+ throw new Error('record requires a task for this Job');
170
+ await store.appendTask(task);
171
+ await pool.reconcileLocalJob(jobId, 'control.record');
172
+ return await store.load(jobId);
173
+ }
174
+ if (command === 'agent.register') {
175
+ const agent = body.agent;
176
+ if (!agent)
177
+ throw new Error('agent.register requires agent');
178
+ await store.saveAgent(agent);
179
+ return agent;
180
+ }
181
+ throw new Error('Unsupported control command ' + command);
182
+ };
183
+ return workspaceControl;
184
+ }
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ export function createDashboardActions(deps) {
3
+ const { cachedOverviews, requireLocalJobBinding, pool, dashboardJobAction, makeReconciler, concurrency, dashboardJobSetState, refreshAll, applyJobActivateAction, applyOrchestratorStartAction, applyOrchestratorCompleteAction, publishOrchestratorTaskStarted, publishOrchestratorTaskCompletion, bus, dashboardTaskAction, dashboardTaskSetState } = deps;
4
+ const dashboardOverview = (jobId, target) => cachedOverviews.find((item) => item.job.id === jobId
5
+ && (!target?.repository || item.job.repository === target.repository)
6
+ && (!target?.prNumber || (item.job.externalPrNumber ?? item.job.prNumber) === target.prNumber));
7
+ const workspaceJobAction = (action) => async (jobId) => {
8
+ const overview = cachedOverviews.find((item) => item.job.id === jobId);
9
+ if (!overview)
10
+ throw new Error(`Managed job ${jobId} not found`);
11
+ const store = (await requireLocalJobBinding(jobId)).store;
12
+ if (action === 'reconcile') {
13
+ await pool.reconcileLocalJob(jobId, 'control.job.reconcile');
14
+ return await store.load(jobId);
15
+ }
16
+ return await dashboardJobAction(store, makeReconciler(store, concurrency), action)(jobId);
17
+ };
18
+ const workspaceJobSetState = async (jobId, state, target) => {
19
+ const overview = dashboardOverview(jobId, target);
20
+ if (!overview)
21
+ throw new Error(`Managed job ${jobId} not found`);
22
+ const store = (await requireLocalJobBinding(jobId)).store;
23
+ const result = await dashboardJobSetState(store)(jobId, state);
24
+ await refreshAll();
25
+ return result;
26
+ };
27
+ const workspaceTaskCreate = async (jobId, task) => {
28
+ const overview = cachedOverviews.find((item) => item.job.id === jobId);
29
+ if (!overview)
30
+ throw new Error(`Managed job ${jobId} not found`);
31
+ if (task.jobId !== jobId)
32
+ throw new Error(`Task ${task.id} belongs to ${task.jobId}, not ${jobId}`);
33
+ const store = (await requireLocalJobBinding(jobId)).store;
34
+ const job = await store.load(jobId);
35
+ if (['COMPLETED', 'CANCELLED'].includes(job.state))
36
+ throw new Error(`Cannot create task on terminal job ${job.id} (${job.state})`);
37
+ if (store.mutateJob) {
38
+ await store.mutateJob(jobId, (current) => {
39
+ if (['COMPLETED', 'CANCELLED'].includes(current.state))
40
+ throw new Error(`Cannot create task on terminal job ${current.id}`);
41
+ if (current.tasks.some((item) => item.id === task.id))
42
+ throw new Error(`Task ${task.id} already exists`);
43
+ current.tasks.push(structuredClone(task));
44
+ if (current.state === 'DRAFT')
45
+ applyJobActivateAction(current);
46
+ current.updatedAt = new Date().toISOString();
47
+ return current;
48
+ });
49
+ }
50
+ else {
51
+ await store.appendTask(task);
52
+ const refreshed = await store.load(jobId);
53
+ if (refreshed.state === 'DRAFT') {
54
+ await applyJobActivateAction(refreshed);
55
+ await store.saveJob(refreshed);
56
+ }
57
+ }
58
+ await pool.reconcileLocalJob(jobId, 'control.task.create');
59
+ return await store.load(jobId);
60
+ };
61
+ const workspaceTaskStart = async (jobId, taskId) => {
62
+ const overview = cachedOverviews.find((item) => item.job.id === jobId);
63
+ if (!overview)
64
+ throw new Error(`Managed job ${jobId} not found`);
65
+ const store = (await requireLocalJobBinding(jobId)).store;
66
+ const job = await store.load(jobId);
67
+ const task = job.tasks.find((item) => item.id === taskId);
68
+ if (!task)
69
+ throw new Error(`Task ${taskId} not found`);
70
+ await applyOrchestratorStartAction(task);
71
+ await store.saveTask(task);
72
+ await publishOrchestratorTaskStarted(bus, job, task);
73
+ await pool.reconcileLocalJob(jobId, 'control.task.start');
74
+ return await store.load(jobId);
75
+ };
76
+ const workspaceTaskComplete = async (jobId, taskId, summary) => {
77
+ const overview = cachedOverviews.find((item) => item.job.id === jobId);
78
+ if (!overview)
79
+ throw new Error(`Managed job ${jobId} not found`);
80
+ const store = (await requireLocalJobBinding(jobId)).store;
81
+ const job = await store.load(jobId);
82
+ const task = job.tasks.find((item) => item.id === taskId);
83
+ if (!task)
84
+ throw new Error(`Task ${taskId} not found`);
85
+ await applyOrchestratorCompleteAction(task, summary);
86
+ await store.saveTask(task);
87
+ await publishOrchestratorTaskCompletion(bus, job, task, summary);
88
+ await pool.reconcileLocalJob(jobId, 'control.task.complete');
89
+ return await store.load(jobId);
90
+ };
91
+ const workspaceTaskAction = (action) => async (jobId, taskId) => {
92
+ const overview = cachedOverviews.find((item) => item.job.id === jobId);
93
+ if (!overview)
94
+ throw new Error(`Managed job ${jobId} not found`);
95
+ const store = (await requireLocalJobBinding(jobId)).store;
96
+ return await dashboardTaskAction(store, makeReconciler(store, concurrency), action)(jobId, taskId);
97
+ };
98
+ const workspaceTaskSetState = async (jobId, taskId, state, target) => {
99
+ const overview = dashboardOverview(jobId, target);
100
+ if (!overview)
101
+ throw new Error(`Managed job ${jobId} not found`);
102
+ const store = (await requireLocalJobBinding(jobId)).store;
103
+ const result = await dashboardTaskSetState(store)(jobId, taskId, state);
104
+ await refreshAll();
105
+ return result;
106
+ };
107
+ return { dashboardOverview, workspaceJobAction, workspaceJobSetState, workspaceTaskCreate, workspaceTaskStart, workspaceTaskComplete, workspaceTaskAction, workspaceTaskSetState };
108
+ }
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ import { defaultWorkspaceRoot } from './workspace.js';
3
+ import { executionRouterRulesPath } from './routing-policy.js';
4
+ import { RelayObservability, setObservability } from './observability.js';
5
+ /** Initialize the process-wide structured observability sink once, from environment. */
6
+ export function configureDaemonObservability(env = process.env) {
7
+ const configured = RelayObservability.fromEnvironment(env);
8
+ setObservability(configured);
9
+ return configured;
10
+ }
11
+ export async function overlayOperationalOverviews(overviews, storeFor) {
12
+ return await Promise.all(overviews.map(async (item) => {
13
+ const store = storeFor(item.job.repository, item.job.prNumber);
14
+ await store.hydrate?.(item.job);
15
+ const job = await store.load(item.job.id);
16
+ return { ...item, job };
17
+ }));
18
+ }
19
+ function managedOverviewKey(item) {
20
+ return item.job.repository + '#' + item.job.prNumber + ':' + item.job.id;
21
+ }
22
+ export function mergeOperationalInventory(current, discovered) {
23
+ const merged = new Map(current.map(item => [managedOverviewKey(item), item]));
24
+ for (const item of discovered)
25
+ merged.set(managedOverviewKey(item), item);
26
+ return [...merged.values()];
27
+ }
28
+ const PR_REFRESH_COALESCE_MS = 1000;
29
+ export const SCHEDULE_HEARTBEAT_MS = 600_000;
30
+ export function dueScheduledOverviews(overviews, now = new Date()) {
31
+ return overviews.filter(item => item.job.state === 'ACTIVE' && item.job.executionMode === 'autonomous' && item.job.schedule && Date.parse(item.job.schedule.nextRunAt) <= now.getTime());
32
+ }
33
+ export function shouldCoalescePullRequestRefresh(completedAt, now = Date.now(), force = false) {
34
+ return !force && completedAt !== null && now >= completedAt && now - completedAt < PR_REFRESH_COALESCE_MS;
35
+ }
36
+ export const daemonHelp = `Agents Relay daemon runs the dashboard and worker pool.
37
+
38
+ Usage:
39
+ npx agents-relayd [options]
40
+
41
+ Options:
42
+ --no-github Local-only mode: no GitHub auth, transport, PR, merge, or fetch
43
+ --repo OWNER/REPO Watch one repository
44
+ --workspace PATH Discover repositories below PATH (default: ~/Workspace)
45
+ --port PORT Dashboard port (default: 8787)
46
+ --concurrency N Maximum concurrent workers (default: 4)
47
+ --interval MS Recovery interval
48
+ --projection-ms MS GitHub projection flush interval (default: 1000)
49
+ --execution-router-rules PATH
50
+ Router Rules file (default: ~/.agents-relay/execution-router-rules.md)
51
+ --events nats Require NATS event wakeups (otherwise auto-detect reachable NATS)
52
+
53
+ Legacy single-job mode also accepts --repo OWNER/REPO --pr NUMBER --id JOB_ID.
54
+
55
+ Examples:
56
+ npx agents-relayd --repo OWNER/REPO
57
+ npx agents-relayd --workspace ~/Workspace`;
58
+ export function firstStageChoices(decisionInput) {
59
+ const stages = decisionInput?.stages;
60
+ if (!Array.isArray(stages) || stages.length === 0)
61
+ return [];
62
+ const contract = stages[0].contract;
63
+ return Array.isArray(contract?.choices) ? contract.choices.filter((item) => typeof item === 'string') : [];
64
+ }
65
+ export function value(args, name, fallback = '') { const index = args.indexOf(name); return index >= 0 ? args[index + 1] ?? fallback : fallback; }
66
+ export function normalizeDaemonArguments(argv) {
67
+ const repository = value(argv, '--repo') || null;
68
+ const workspaceRoot = value(argv, '--workspace', defaultWorkspaceRoot()) || null;
69
+ const prValue = value(argv, '--pr');
70
+ const id = value(argv, '--id');
71
+ if ((prValue && !id) || (!prValue && id))
72
+ throw new Error('--pr and --id must be supplied together for legacy single-job mode');
73
+ if ((prValue || id) && !repository)
74
+ throw new Error('--repo is required for legacy single-job mode');
75
+ if (repository && argv.includes('--workspace'))
76
+ throw new Error('--repo and --workspace are mutually exclusive');
77
+ const pullRequest = prValue ? Number(prValue) : null;
78
+ if (pullRequest !== null && (!Number.isInteger(pullRequest) || pullRequest <= 0))
79
+ throw new Error('--pr must be positive');
80
+ return { repository, workspaceRoot: repository ? null : workspaceRoot, pullRequest, jobId: id || null, serviceArguments: [...argv] };
81
+ }
82
+ export function recoveryScanRepositories(repositories, cached, dormantProbeIndex = 0) {
83
+ const byRepository = new Map();
84
+ for (const item of cached) {
85
+ const values = byRepository.get(item.job.repository) ?? [];
86
+ values.push(item);
87
+ byRepository.set(item.job.repository, values);
88
+ }
89
+ const activeOrUnknown = [];
90
+ const dormant = [];
91
+ for (const repository of repositories) {
92
+ const values = byRepository.get(repository);
93
+ if (!values || values.length === 0 || values.some(item => !['COMPLETED', 'CANCELLED'].includes(item.job.state)))
94
+ activeOrUnknown.push(repository);
95
+ else
96
+ dormant.push(repository);
97
+ }
98
+ if (dormant.length === 0)
99
+ return activeOrUnknown;
100
+ return [...activeOrUnknown, dormant[Math.abs(dormantProbeIndex) % dormant.length]];
101
+ }
102
+ export function configureExecutionRouterRulesPath(argv) {
103
+ const explicitRulesPath = value(argv, '--execution-router-rules') || undefined;
104
+ if (explicitRulesPath)
105
+ process.env.AGENTS_RELAY_EXECUTION_ROUTER_RULES = executionRouterRulesPath(explicitRulesPath);
106
+ return executionRouterRulesPath();
107
+ }