agents-relay 2.0.67 → 2.0.69
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 +56 -20
- package/dist/adapters.js +39 -3
- package/dist/blockers.js +17 -4
- package/dist/cli-blocker.js +12 -30
- package/dist/cli-help.js +22 -17
- package/dist/cli-job-create.js +77 -0
- package/dist/cli-local-merge-config.js +11 -0
- package/dist/cli-projection.js +23 -55
- package/dist/cli-review.js +7 -24
- package/dist/cli-support.js +81 -8
- package/dist/cli-worker-task.js +63 -0
- package/dist/cli.js +223 -185
- package/dist/dashboard-html.js +15 -12
- package/dist/dashboard.js +27 -2
- package/dist/e2e-injection.js +76 -0
- package/dist/github-auth.js +4 -3
- package/dist/github-binding-order.js +15 -0
- package/dist/github-client.js +7 -2
- package/dist/github-pr-archive.js +22 -0
- package/dist/github-webhook.js +43 -10
- package/dist/job-actions.js +29 -0
- package/dist/local-authority-store.js +288 -0
- package/dist/local-commit-events.js +2 -0
- package/dist/local-git-merge.js +124 -0
- package/dist/local-job-path.js +7 -0
- package/dist/local-job-registry.js +29 -0
- package/dist/local-managed-store.js +21 -0
- package/dist/local-merge-delivery.js +53 -0
- package/dist/local-merge-lock.js +103 -0
- package/dist/local-merge-projection.js +60 -0
- package/dist/managed-worker-input.js +13 -6
- package/dist/manual-plan.js +29 -7
- package/dist/no-github.js +11 -0
- package/dist/planned-review-head.js +7 -8
- package/dist/planner-runtime.js +182 -41
- package/dist/planner.js +35 -2
- package/dist/pool.js +89 -136
- package/dist/projected-store.js +20 -3
- package/dist/projection-outbox.js +3 -1
- package/dist/publication-store.js +37 -0
- package/dist/reconciler-cancellation.js +21 -0
- package/dist/reconciler-review.js +99 -29
- package/dist/reconciler-runtime.js +72 -35
- package/dist/reconciler.js +124 -85
- package/dist/relay-store.js +108 -6
- package/dist/relayd-control.js +184 -0
- package/dist/relayd-dashboard-actions.js +108 -0
- package/dist/relayd-utilities.js +107 -0
- package/dist/relayd.js +304 -299
- package/dist/review-generation-fence.js +12 -0
- package/dist/runtime-status-events.js +21 -0
- package/dist/runtime-status.js +21 -7
- package/dist/scheduled-jobs.js +43 -36
- package/dist/service-external-actions.js +27 -0
- package/dist/service-publication.js +29 -0
- package/dist/store-github.js +17 -6
- package/dist/task-actions.js +59 -1
- package/dist/task-update.js +1 -1
- package/dist/troubleshooter.js +21 -2
- package/dist/unblock.js +47 -15
- package/dist/worker-rejection.js +11 -0
- package/dist/workflow/contracts.js +10 -1
- package/dist/workflow/definitions-v2.js +2 -0
- package/dist/workflow/job-reconcile-v2.js +9 -60
- package/dist/workflow/runtime.js +18 -3
- package/dist/workflow-effect-failures.js +30 -0
- package/dist/workspace.js +95 -6
- package/package.json +6 -3
- package/skills/agents-relay/agents/planner.agent.md +3 -0
- package/skills/agents-relay/contracts/orchestrator-workflows.md +4 -7
- package/workflow/job-lifecycle.mmd +19 -60
- package/workflow/review-lifecycle.mmd +5 -8
package/dist/cli-review.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { normalizeThreadId
|
|
1
|
+
import { arg, hasArg, relayControlCommand } from './cli-support.js';
|
|
2
|
+
import { normalizeThreadId } from './reviewer-thread.js';
|
|
3
3
|
export function reviewRequestThreadOverride(args) {
|
|
4
4
|
const values = [...args];
|
|
5
5
|
return hasArg(values, '--thread-id')
|
|
@@ -9,27 +9,10 @@ export function reviewRequestThreadOverride(args) {
|
|
|
9
9
|
export async function runReviewCommand(action, reviewArgs) {
|
|
10
10
|
if (action !== 'request')
|
|
11
11
|
throw new Error('review supports: request');
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const jobId = arg(reviewArgs, '--id');
|
|
13
|
+
if (!arg(reviewArgs, '--repo') || !jobId)
|
|
14
|
+
throw new Error('review request requires --repo and --id');
|
|
14
15
|
const threadId = reviewRequestThreadOverride(reviewArgs);
|
|
15
|
-
const {
|
|
16
|
-
|
|
17
|
-
throw new Error('review request requires a managed GitHub Job');
|
|
18
|
-
if (job.state === 'CANCELLED' || job.state === 'COMPLETED')
|
|
19
|
-
throw new Error('Cannot request review for terminal Job');
|
|
20
|
-
if (!job.reviewEnabled)
|
|
21
|
-
throw new Error('Review disabled for this Job');
|
|
22
|
-
if (job.tasks.some(task => task.agentName === 'reviewer' && ['RUNNING', 'QUEUED', 'DRAFT'].includes(task.state)))
|
|
23
|
-
throw new Error('Review already queued or running; refusing duplicate request');
|
|
24
|
-
if (job.review?.outcome === 'approved')
|
|
25
|
-
throw new Error('Current head already approved; review request not needed');
|
|
26
|
-
if (threadId !== undefined) {
|
|
27
|
-
job.reviewerThreadId = threadId;
|
|
28
|
-
job.updatedAt = new Date().toISOString();
|
|
29
|
-
await store.saveJob(job);
|
|
30
|
-
}
|
|
31
|
-
const destination = resolveReviewerThreadDestination(job);
|
|
32
|
-
await withEventBus(reviewArgs, async (bus) => requestManagedReconciliation(bus, job));
|
|
33
|
-
console.log(JSON.stringify({ jobId: job.id, pr: job.prNumber, reviewerThreadId: destination?.threadId ?? null, status: 'requested', note: 'Relay graph will create the review Task once eligible; this is not a delivery receipt.' }, null, 2));
|
|
34
|
-
return;
|
|
16
|
+
const job = await relayControlCommand(reviewArgs, { command: 'review.request', jobId, ...(threadId !== undefined ? { threadId } : {}) });
|
|
17
|
+
console.log(JSON.stringify({ jobId: job.id, pr: job.externalPrNumber ?? job.prNumber ?? 0, reviewerThreadId: job.reviewerThreadId ?? null, status: 'requested', note: 'Relay graph will create the review Task once eligible; this is not a delivery receipt.' }, null, 2));
|
|
35
18
|
}
|
package/dist/cli-support.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { InMemoryStore, GitHubStore } from './store.js';
|
|
3
3
|
import { githubAuthContext } from './github-auth.js';
|
|
4
|
+
import { noGitHubMode } from './no-github.js';
|
|
5
|
+
import { basename } from 'node:path';
|
|
6
|
+
import { createJobIdRegistryResolver } from './local-job-registry.js';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
8
|
+
import { join } from 'node:path';
|
|
4
9
|
import { NatsEventBus, eventFor } from './events.js';
|
|
5
10
|
import { parseJobSchedule } from './scheduler.js';
|
|
6
11
|
import { parseJobTagJson } from './task-tag.js';
|
|
@@ -51,20 +56,33 @@ export async function textOption(args, inlineFlag, fileFlag) {
|
|
|
51
56
|
export function integerArg(args, name, fallback) { if (!hasArg(args, name))
|
|
52
57
|
return fallback; const value = Number(arg(args, name)); if (!Number.isInteger(value) || value < 0)
|
|
53
58
|
throw new Error(`${name} must be a non-negative integer`); return value; }
|
|
54
|
-
export function repository(args) { const value = arg(args, '--repo'); if (!value)
|
|
59
|
+
export function repository(args) { const value = arg(args, '--repo'); if (!value && noGitHubMode(args))
|
|
60
|
+
return 'local/' + basename(process.cwd()); if (!value)
|
|
55
61
|
throw new Error('--repo is required for GitHub-backed operation'); return value; }
|
|
56
62
|
export function pullRequest(args) { const value = Number(arg(args, '--pr')); if (!Number.isInteger(value) || value <= 0)
|
|
57
63
|
throw new Error('--pr is required for GitHub-backed operation'); return value; }
|
|
58
64
|
async function localStore(file) { const parsed = JSON.parse(await readFile(file, 'utf8')); const store = new InMemoryStore(parsed); return { store, job: await store.load(parsed.id) }; }
|
|
59
65
|
export async function githubStore(args) { const auth = await githubAuthContext(args); return new GitHubStore(auth.client, repository(args), pullRequest(args), auth.trustedAuthors); }
|
|
60
|
-
export async function storeFor(args) {
|
|
61
|
-
const
|
|
66
|
+
export async function storeFor(args) {
|
|
67
|
+
const file = arg(args, '--file');
|
|
68
|
+
if (file) {
|
|
69
|
+
const loaded = await localStore(file);
|
|
70
|
+
return { ...loaded, localFile: file };
|
|
71
|
+
}
|
|
72
|
+
const repo = noGitHubMode(args) ? repository(args) : arg(args, '--repo');
|
|
62
73
|
const jobId = arg(args, '--id');
|
|
63
|
-
if (!jobId)
|
|
64
|
-
throw new Error('--
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
if (!repo || !jobId)
|
|
75
|
+
throw new Error('Local Job requires --repo and --id (or explicit --file)');
|
|
76
|
+
const directory = arg(args, '--store-dir') || join(homedir(), '.agents-relay', 'stores');
|
|
77
|
+
const binding = await createJobIdRegistryResolver(directory, new Map())(jobId);
|
|
78
|
+
if (!binding || binding.job.repository !== repo)
|
|
79
|
+
throw new Error('Immutable local Job not found for repository: ' + jobId);
|
|
80
|
+
const suppliedPr = arg(args, '--pr');
|
|
81
|
+
const boundPr = binding.job.externalPrNumber ?? (binding.job.prNumber > 0 ? binding.job.prNumber : 0);
|
|
82
|
+
if (suppliedPr && boundPr !== Number(suppliedPr))
|
|
83
|
+
throw new Error('Local Job external PR binding mismatch');
|
|
84
|
+
return { store: binding.store, job: await binding.store.load(jobId), localFile: null };
|
|
85
|
+
}
|
|
68
86
|
export async function saveLocal(file, job) { if (file)
|
|
69
87
|
await writeFile(file, JSON.stringify(job, null, 2)); }
|
|
70
88
|
export async function relayControlJob(args, jobId) {
|
|
@@ -90,6 +108,61 @@ export async function relayControlJob(args, jobId) {
|
|
|
90
108
|
throw new Error(`Agents Relay job request failed: ${await response.text() || `HTTP ${response.status}`}`);
|
|
91
109
|
return await response.json();
|
|
92
110
|
}
|
|
111
|
+
export async function relayControlCommand(args, body) {
|
|
112
|
+
const host = arg(args, '--host', '127.0.0.1');
|
|
113
|
+
const port = Number(arg(args, '--port', String(SERVICE_DEFAULTS.port)));
|
|
114
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535)
|
|
115
|
+
throw new Error('--port must be an integer between 1 and 65535');
|
|
116
|
+
let response;
|
|
117
|
+
try {
|
|
118
|
+
response = await fetch(`http://${host}:${port}/api/control`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
throw new Error(`Agents Relay service unavailable at ${host}:${port}: ${error instanceof Error ? error.message : String(error)}`);
|
|
122
|
+
}
|
|
123
|
+
if (!response.ok)
|
|
124
|
+
throw new Error(`Agents Relay control command failed: ${await response.text() || `HTTP ${response.status}`}`);
|
|
125
|
+
return await response.json();
|
|
126
|
+
}
|
|
127
|
+
export async function relayJobList(args) {
|
|
128
|
+
const host = arg(args, '--host', '127.0.0.1');
|
|
129
|
+
const port = Number(arg(args, '--port', String(SERVICE_DEFAULTS.port)));
|
|
130
|
+
const repo = arg(args, '--repo');
|
|
131
|
+
let response;
|
|
132
|
+
try {
|
|
133
|
+
response = await fetch(`http://${host}:${port}/api/jobs`);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
throw new Error(`Agents Relay service unavailable at ${host}:${port}: ${error instanceof Error ? error.message : String(error)}`);
|
|
137
|
+
}
|
|
138
|
+
if (!response.ok)
|
|
139
|
+
throw new Error(`Agents Relay jobs request failed: ${await response.text() || `HTTP ${response.status}`}`);
|
|
140
|
+
const jobs = await response.json();
|
|
141
|
+
return repo ? jobs.filter(job => job.repository === repo) : jobs;
|
|
142
|
+
}
|
|
143
|
+
export async function relayJobStateCommand(args, jobId, state) {
|
|
144
|
+
const host = arg(args, '--host', '127.0.0.1');
|
|
145
|
+
const port = Number(arg(args, '--port', String(SERVICE_DEFAULTS.port)));
|
|
146
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535)
|
|
147
|
+
throw new Error('--port must be an integer between 1 and 65535');
|
|
148
|
+
const params = new URLSearchParams({ id: jobId, state });
|
|
149
|
+
const repo = arg(args, '--repo');
|
|
150
|
+
const pr = Number(arg(args, '--pr'));
|
|
151
|
+
if (repo)
|
|
152
|
+
params.set('repo', repo);
|
|
153
|
+
if (Number.isInteger(pr) && pr > 0)
|
|
154
|
+
params.set('pr', String(pr));
|
|
155
|
+
let response;
|
|
156
|
+
try {
|
|
157
|
+
response = await fetch(`http://${host}:${port}/api/job-state?${params.toString()}`, { method: 'POST' });
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
throw new Error(`Agents Relay service unavailable at ${host}:${port}: ${error instanceof Error ? error.message : String(error)}`);
|
|
161
|
+
}
|
|
162
|
+
if (!response.ok)
|
|
163
|
+
throw new Error(`Agents Relay job state command failed: ${await response.text() || `HTTP ${response.status}`}`);
|
|
164
|
+
return await response.json();
|
|
165
|
+
}
|
|
93
166
|
export async function relayTaskCommand(args, body) {
|
|
94
167
|
const host = arg(args, '--host', '127.0.0.1');
|
|
95
168
|
const port = Number(arg(args, '--port', String(SERVICE_DEFAULTS.port)));
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { taskOverallTimeoutMs } from './types.js';
|
|
2
|
+
import { arg, hasArg, textOption, integerArg, chatGptTabClosePolicy, storySize, priority, continuation } from './cli-support.js';
|
|
3
|
+
import { parseAgentGraph } from './agent-graph.js';
|
|
4
|
+
import { createTaskEntity } from './workflow/entity-creation.js';
|
|
5
|
+
export async function workerTaskFromArgs(job, args, now) {
|
|
6
|
+
const model = arg(args, '--model');
|
|
7
|
+
const adapterValue = arg(args, '--adapter');
|
|
8
|
+
const adapter = (adapterValue || 'codex');
|
|
9
|
+
const outputKind = arg(args, '--output');
|
|
10
|
+
const outputPath = arg(args, '--output-path');
|
|
11
|
+
const input = await textOption(args, '--input', '--input-file');
|
|
12
|
+
const agentGraph = hasArg(args, '--agent-graph') || hasArg(args, '--agent-graph-file') ? await textOption(args, '--agent-graph', '--agent-graph-file') : undefined;
|
|
13
|
+
if (agentGraph)
|
|
14
|
+
parseAgentGraph(agentGraph);
|
|
15
|
+
if (!input.trim())
|
|
16
|
+
throw new Error('task create requires --input-file PATH|- or --input MARKDOWN');
|
|
17
|
+
if (!['codex', 'chatgpt', 'orchestrator'].includes(adapter))
|
|
18
|
+
throw new Error('task create --adapter must be codex, chatgpt, or orchestrator');
|
|
19
|
+
if (outputKind !== 'task-pr' && outputKind !== 'file')
|
|
20
|
+
throw new Error('task create requires --output task-pr|file');
|
|
21
|
+
if (outputKind === 'file' && !outputPath)
|
|
22
|
+
throw new Error('--output file requires --output-path');
|
|
23
|
+
if (adapter !== 'orchestrator' && ['--provider', '--profile', '--reasoning', '--cwd', '--chatgpt-project'].some(flag => hasArg(args, flag)) && !model)
|
|
24
|
+
throw new Error('task create explicit routing requires --model');
|
|
25
|
+
const routing = adapter === 'orchestrator' || !model ? undefined : { provider: arg(args, '--provider', 'openai'), model, profile: arg(args, '--profile') || undefined, reasoning: arg(args, '--reasoning') || undefined, cwd: arg(args, '--cwd') || undefined, projectId: arg(args, '--chatgpt-project') || undefined, decidedBy: 'cli', decidedAt: now };
|
|
26
|
+
const workerRequirements = adapter === 'orchestrator' || routing ? undefined : { capabilities: [], runtime: adapterValue ? adapter : undefined };
|
|
27
|
+
const closePolicy = hasArg(args, '--chatgpt-tab-close-policy') ? chatGptTabClosePolicy(args) : undefined;
|
|
28
|
+
const size = storySize(args);
|
|
29
|
+
const dependencies = arg(args, '--deps').split(',').filter(Boolean);
|
|
30
|
+
const timeoutMs = adapter === 'orchestrator' ? 0 : hasArg(args, '--timeout') ? integerArg(args, '--timeout', taskOverallTimeoutMs(size, routing?.reasoning)) : routing ? taskOverallTimeoutMs(size, routing.reasoning) : 0;
|
|
31
|
+
return createTaskEntity(job, {
|
|
32
|
+
jobId: job.id,
|
|
33
|
+
id: arg(args, '--task-id', 'initial'),
|
|
34
|
+
priority: priority(args, job.priority ?? 'P2'),
|
|
35
|
+
storySize: size,
|
|
36
|
+
projectName: arg(args, '--project') || undefined,
|
|
37
|
+
agentName: arg(args, '--agent') || undefined,
|
|
38
|
+
agentGraph,
|
|
39
|
+
parentTaskId: arg(args, '--parent') || null,
|
|
40
|
+
dependencies,
|
|
41
|
+
capabilities: arg(args, '--capabilities').split(',').filter(Boolean),
|
|
42
|
+
workerRequirements,
|
|
43
|
+
adapter,
|
|
44
|
+
chatgptTabClosePolicy: closePolicy,
|
|
45
|
+
input: input || 'true',
|
|
46
|
+
attachments: arg(args, '--attachments').split(',').filter(Boolean),
|
|
47
|
+
output: outputKind === 'file' ? { kind: 'file', path: outputPath } : { kind: 'task_pr' },
|
|
48
|
+
routing,
|
|
49
|
+
continuation: continuation(args),
|
|
50
|
+
continuationDeliveredAt: null,
|
|
51
|
+
attempt: 0,
|
|
52
|
+
maxAttempts: Number(arg(args, '--max-attempts', '3')),
|
|
53
|
+
leaseOwner: null,
|
|
54
|
+
leaseExpiresAt: null,
|
|
55
|
+
executionId: null,
|
|
56
|
+
threadId: null,
|
|
57
|
+
result: null,
|
|
58
|
+
error: null,
|
|
59
|
+
timeoutMs,
|
|
60
|
+
createdAt: now,
|
|
61
|
+
updatedAt: now
|
|
62
|
+
}, { defer: hasArg(args, '--draft') });
|
|
63
|
+
}
|