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.
- package/README.md +32 -1
- package/dist/adapters.js +39 -3
- package/dist/blockers.js +17 -4
- package/dist/cli-blocker.js +12 -28
- package/dist/cli-help.js +11 -8
- package/dist/cli-job-create.js +19 -13
- package/dist/cli-local-merge-config.js +4 -2
- package/dist/cli-projection.js +23 -55
- package/dist/cli-review.js +7 -24
- package/dist/cli-support.js +59 -4
- package/dist/cli-worker-task.js +63 -0
- package/dist/cli.js +179 -129
- package/dist/dashboard-html.js +15 -12
- package/dist/dashboard.js +26 -1
- package/dist/e2e-injection.js +76 -0
- 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 +31 -6
- package/dist/job-actions.js +29 -0
- package/dist/local-authority-store.js +10 -1
- package/dist/local-commit-events.js +2 -0
- package/dist/local-git-merge.js +17 -6
- package/dist/local-job-path.js +5 -0
- package/dist/local-job-registry.js +10 -4
- package/dist/local-managed-store.js +2 -4
- package/dist/local-merge-delivery.js +3 -2
- 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/planner-runtime.js +182 -41
- package/dist/planner.js +35 -2
- package/dist/pool.js +22 -6
- package/dist/reconciler-cancellation.js +21 -0
- package/dist/reconciler-review.js +74 -18
- package/dist/reconciler-runtime.js +69 -34
- package/dist/reconciler.js +92 -26
- package/dist/relay-store.js +1 -1
- 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 +220 -210
- package/dist/review-generation-fence.js +12 -0
- package/dist/runtime-status-events.js +21 -0
- package/dist/runtime-status.js +13 -0
- package/dist/service-publication.js +2 -4
- package/dist/task-actions.js +59 -1
- package/dist/troubleshooter.js +21 -2
- package/dist/unblock.js +47 -15
- package/dist/worker-rejection.js +11 -0
- package/dist/workflow-effect-failures.js +30 -0
- package/dist/workspace.js +95 -6
- package/package.json +5 -4
- package/skills/agents-relay/agents/planner.agent.md +3 -0
- package/skills/agents-relay/contracts/orchestrator-workflows.md +4 -7
- package/workflow/review-lifecycle.mmd +3 -0
|
@@ -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
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { openLocalManagedStore } from './local-managed-store.js';
|
|
5
2
|
import { configureLocalGitMerge } from './cli-local-merge-config.js';
|
|
6
|
-
import {
|
|
7
|
-
import { discoverLocalRelayStoreSnapshots } from './relay-store.js';
|
|
3
|
+
import { noGitHubMode } from './no-github.js';
|
|
4
|
+
import { discoverLocalRelayStoreSnapshots, relayStoreDirectory } from './relay-store.js';
|
|
8
5
|
import { realpathSync } from 'node:fs';
|
|
9
6
|
import { once } from 'node:events';
|
|
10
7
|
import { fileURLToPath } from 'node:url';
|
|
@@ -26,83 +23,24 @@ import { applyJobActivateAction, applyJobCancelAction } from './job-actions.js';
|
|
|
26
23
|
import { resolveExecutionContext } from './execution-context.js';
|
|
27
24
|
import { startServiceExternalActionFlusher } from './service-external-actions.js';
|
|
28
25
|
import { handleServicePublication } from './service-publication.js';
|
|
29
|
-
import { defaultWorkspaceRoot, resolveManagedProjectPath, resolveRepositoryProjectPath } from './workspace.js';
|
|
26
|
+
import { defaultWorkspaceRoot, prepareLocalJobExecutionWorktree, resolveManagedProjectPath, resolveRepositoryProjectPath } from './workspace.js';
|
|
30
27
|
import { runBlockerCommand } from './cli-blocker.js';
|
|
31
28
|
import { runEscalationCommand } from './cli-escalation.js';
|
|
32
29
|
import { runProjectionCommand } from './cli-projection.js';
|
|
33
30
|
import { helpText, usage } from './cli-help.js';
|
|
34
31
|
import { parseAgentGraph } from './agent-graph.js';
|
|
35
|
-
import {
|
|
36
|
-
import { createJobEntity
|
|
32
|
+
import { runTaskUnblockCommand } from './cli-unblock.js';
|
|
33
|
+
import { createJobEntity } from './workflow/entity-creation.js';
|
|
37
34
|
import { runCliEntrypoint } from './cli-shutdown.js';
|
|
38
35
|
import { shutdownOwnedProcesses } from './owned-process.js';
|
|
39
36
|
export { helpText } from './cli-help.js';
|
|
40
37
|
import { runReviewCommand } from './cli-review.js';
|
|
41
38
|
import { normalizeThreadId, runtimeReviewer } from './reviewer-thread.js';
|
|
42
39
|
export { runtimeReviewer } from './reviewer-thread.js';
|
|
43
|
-
import { SERVICE_DEFAULTS, ServiceEventBus, arg, hasArg, textOption, integerArg, repository,
|
|
40
|
+
import { SERVICE_DEFAULTS, ServiceEventBus, arg, hasArg, textOption, integerArg, repository, storeFor, saveLocal, eventBus, publishWake, priority, storySize, executionMode, chatGptTabClosePolicy, requestedExecutionMode, continuation, scheduleFromArgs, jobTagFromArgs, requestManagedReconciliation, relayControlJob, relayControlCommand, relayJobList, relayJobStateCommand, relayTaskCommand, withEventBus } from './cli-support.js';
|
|
44
41
|
export { SERVICE_DEFAULTS, eventBus, executionMode, chatGptTabClosePolicy } from './cli-support.js';
|
|
45
|
-
import {
|
|
42
|
+
import { workerTaskFromArgs } from './cli-worker-task.js';
|
|
46
43
|
export { ensureManagedGitHubJob } from './cli-job-create.js';
|
|
47
|
-
async function workerTaskFromArgs(job, args, now) {
|
|
48
|
-
const model = arg(args, '--model');
|
|
49
|
-
const adapterValue = arg(args, '--adapter');
|
|
50
|
-
const adapter = (adapterValue || 'codex');
|
|
51
|
-
const outputKind = arg(args, '--output');
|
|
52
|
-
const outputPath = arg(args, '--output-path');
|
|
53
|
-
const input = await textOption(args, '--input', '--input-file');
|
|
54
|
-
const agentGraph = hasArg(args, '--agent-graph') || hasArg(args, '--agent-graph-file') ? await textOption(args, '--agent-graph', '--agent-graph-file') : undefined;
|
|
55
|
-
if (agentGraph)
|
|
56
|
-
parseAgentGraph(agentGraph);
|
|
57
|
-
if (!input.trim())
|
|
58
|
-
throw new Error('task create requires --input-file PATH|- or --input MARKDOWN');
|
|
59
|
-
if (!['codex', 'chatgpt', 'orchestrator'].includes(adapter))
|
|
60
|
-
throw new Error('task create --adapter must be codex, chatgpt, or orchestrator');
|
|
61
|
-
if (outputKind !== 'task-pr' && outputKind !== 'file')
|
|
62
|
-
throw new Error('task create requires --output task-pr|file');
|
|
63
|
-
if (outputKind === 'file' && !outputPath)
|
|
64
|
-
throw new Error('--output file requires --output-path');
|
|
65
|
-
if (adapter !== 'orchestrator' && ['--provider', '--profile', '--reasoning', '--cwd', '--chatgpt-project'].some(flag => hasArg(args, flag)) && !model)
|
|
66
|
-
throw new Error('task create explicit routing requires --model');
|
|
67
|
-
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 };
|
|
68
|
-
const workerRequirements = adapter === 'orchestrator' || routing ? undefined : { capabilities: [], runtime: adapterValue ? adapter : undefined };
|
|
69
|
-
const closePolicy = hasArg(args, '--chatgpt-tab-close-policy') ? chatGptTabClosePolicy(args) : undefined;
|
|
70
|
-
const size = storySize(args);
|
|
71
|
-
const dependencies = arg(args, '--deps').split(',').filter(Boolean);
|
|
72
|
-
const timeoutMs = adapter === 'orchestrator' ? 0 : hasArg(args, '--timeout') ? integerArg(args, '--timeout', taskOverallTimeoutMs(size, routing?.reasoning)) : routing ? taskOverallTimeoutMs(size, routing.reasoning) : 0;
|
|
73
|
-
return createTaskEntity(job, {
|
|
74
|
-
jobId: job.id,
|
|
75
|
-
id: arg(args, '--task-id', 'initial'),
|
|
76
|
-
priority: priority(args, job.priority ?? 'P2'),
|
|
77
|
-
storySize: size,
|
|
78
|
-
projectName: arg(args, '--project') || undefined,
|
|
79
|
-
agentName: arg(args, '--agent') || undefined,
|
|
80
|
-
agentGraph,
|
|
81
|
-
parentTaskId: arg(args, '--parent') || null,
|
|
82
|
-
dependencies,
|
|
83
|
-
capabilities: arg(args, '--capabilities').split(',').filter(Boolean),
|
|
84
|
-
workerRequirements,
|
|
85
|
-
adapter,
|
|
86
|
-
chatgptTabClosePolicy: closePolicy,
|
|
87
|
-
input: input || 'true',
|
|
88
|
-
attachments: arg(args, '--attachments').split(',').filter(Boolean),
|
|
89
|
-
output: outputKind === 'file' ? { kind: 'file', path: outputPath } : { kind: 'task_pr' },
|
|
90
|
-
routing,
|
|
91
|
-
continuation: continuation(args),
|
|
92
|
-
continuationDeliveredAt: null,
|
|
93
|
-
attempt: 0,
|
|
94
|
-
maxAttempts: Number(arg(args, '--max-attempts', '3')),
|
|
95
|
-
leaseOwner: null,
|
|
96
|
-
leaseExpiresAt: null,
|
|
97
|
-
executionId: null,
|
|
98
|
-
threadId: null,
|
|
99
|
-
result: null,
|
|
100
|
-
error: null,
|
|
101
|
-
timeoutMs,
|
|
102
|
-
createdAt: now,
|
|
103
|
-
updatedAt: now
|
|
104
|
-
}, { defer: hasArg(args, '--draft') });
|
|
105
|
-
}
|
|
106
44
|
async function runManagedTaskCommand(action, args) {
|
|
107
45
|
const jobId = arg(args, '--id');
|
|
108
46
|
if (!jobId)
|
|
@@ -133,16 +71,100 @@ async function runManagedTaskCommand(action, args) {
|
|
|
133
71
|
console.log(JSON.stringify(task, null, 2));
|
|
134
72
|
}
|
|
135
73
|
async function runTaskCommand(action, args) {
|
|
136
|
-
if (!['create', 'update', 'show', 'list', 'start', 'complete', 'retry', 'unblock', 'cancel'].includes(action))
|
|
74
|
+
if (!['create', 'update', 'show', 'list', 'start', 'complete', 'retry', 'recover', 'unblock', 'cancel'].includes(action))
|
|
137
75
|
throw new Error(usage);
|
|
138
|
-
const managed = !
|
|
139
|
-
if (managed
|
|
140
|
-
|
|
141
|
-
|
|
76
|
+
const managed = !arg(args, '--file') && Boolean(arg(args, '--repo') && arg(args, '--id'));
|
|
77
|
+
if (managed) {
|
|
78
|
+
const jobId = arg(args, '--id');
|
|
79
|
+
if (action === 'list') {
|
|
80
|
+
const job = await relayControlJob(args, jobId);
|
|
81
|
+
console.log(JSON.stringify(job.tasks, null, 2));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const taskId = arg(args, '--task-id', action === 'create' ? randomUUID() : '');
|
|
85
|
+
if (action === 'show') {
|
|
86
|
+
if (!taskId)
|
|
87
|
+
throw new Error('task show requires --task-id');
|
|
88
|
+
const job = await relayControlJob(args, jobId);
|
|
89
|
+
const task = job.tasks.find(item => item.id === taskId);
|
|
90
|
+
if (!task)
|
|
91
|
+
throw new Error(`Task ${taskId} not found`);
|
|
92
|
+
console.log(JSON.stringify(task, null, 2));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (['create', 'start', 'complete', 'retry', 'cancel'].includes(action)) {
|
|
96
|
+
await runManagedTaskCommand(action, args);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (action === 'recover') {
|
|
100
|
+
if (!taskId)
|
|
101
|
+
throw new Error('task recover requires --task-id');
|
|
102
|
+
const recoveryTaskId = arg(args, '--recovery-task-id');
|
|
103
|
+
if (!recoveryTaskId)
|
|
104
|
+
throw new Error('task recover requires --recovery-task-id');
|
|
105
|
+
const job = await relayControlCommand(args, { command: 'task.recover', jobId, taskId, recoveryTaskId });
|
|
106
|
+
console.log(JSON.stringify(job.tasks.find(item => item.id === taskId), null, 2));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (action === 'unblock') {
|
|
110
|
+
if (!taskId)
|
|
111
|
+
throw new Error('task unblock requires --task-id');
|
|
112
|
+
const reason = arg(args, '--reason');
|
|
113
|
+
if (!reason.trim())
|
|
114
|
+
throw new Error('task unblock requires --reason');
|
|
115
|
+
const job = await relayControlCommand(args, { command: 'task.unblock', jobId, taskId, reason });
|
|
116
|
+
console.log(JSON.stringify(job.tasks.find(item => item.id === taskId), null, 2));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (action === 'update') {
|
|
120
|
+
const job = await relayControlJob(args, jobId);
|
|
121
|
+
const task = job.tasks.find(item => item.id === taskId);
|
|
122
|
+
if (!task)
|
|
123
|
+
throw new Error(`Task ${taskId} not found`);
|
|
124
|
+
const expectedUpdatedAt = task.updatedAt;
|
|
125
|
+
if (['RUNNING', 'UNBLOCKED', 'SUCCEEDED', 'CANCELLED'].includes(task.state))
|
|
126
|
+
throw new Error(`Task ${taskId} is ${task.state}; durable execution fields are immutable in this state`);
|
|
127
|
+
if (hasArg(args, '--adapter')) {
|
|
128
|
+
const adapter = arg(args, '--adapter');
|
|
129
|
+
if (!['codex', 'chatgpt', 'orchestrator'].includes(adapter))
|
|
130
|
+
throw new Error('--adapter must be codex, chatgpt, or orchestrator');
|
|
131
|
+
task.adapter = adapter;
|
|
132
|
+
}
|
|
133
|
+
if (hasArg(args, '--priority'))
|
|
134
|
+
task.priority = priority(args, task.priority ?? job.priority ?? 'P2');
|
|
135
|
+
if (hasArg(args, '--story-size')) {
|
|
136
|
+
task.storySize = storySize(args, task.storySize ?? 'M');
|
|
137
|
+
if (!hasArg(args, '--timeout'))
|
|
138
|
+
task.timeoutMs = taskOverallTimeoutMs(task.storySize, task.routing?.reasoning);
|
|
139
|
+
}
|
|
140
|
+
if (hasArg(args, '--input') || hasArg(args, '--input-file'))
|
|
141
|
+
task.input = await textOption(args, '--input', '--input-file');
|
|
142
|
+
if (hasArg(args, '--project'))
|
|
143
|
+
task.projectName = arg(args, '--project') || undefined;
|
|
144
|
+
if (hasArg(args, '--agent'))
|
|
145
|
+
task.agentName = arg(args, '--agent') || undefined;
|
|
146
|
+
if (hasArg(args, '--parent'))
|
|
147
|
+
task.parentTaskId = arg(args, '--parent') || null;
|
|
148
|
+
if (hasArg(args, '--deps'))
|
|
149
|
+
task.dependencies = arg(args, '--deps').split(',').filter(Boolean);
|
|
150
|
+
if (hasArg(args, '--capabilities'))
|
|
151
|
+
task.capabilities = arg(args, '--capabilities').split(',').filter(Boolean);
|
|
152
|
+
task.timeoutMs = integerArg(args, '--timeout', task.timeoutMs);
|
|
153
|
+
task.maxAttempts = integerArg(args, '--max-attempts', task.maxAttempts);
|
|
154
|
+
if (hasArg(args, '--draft') && hasArg(args, '--queue'))
|
|
155
|
+
throw new Error('--draft and --queue are mutually exclusive');
|
|
156
|
+
if (hasArg(args, '--draft') && task.state === 'QUEUED')
|
|
157
|
+
await applyTaskHoldAction(task);
|
|
158
|
+
if (hasArg(args, '--queue'))
|
|
159
|
+
await applyTaskQueueAction(job, task);
|
|
160
|
+
const updated = await relayTaskCommand(args, { jobId, taskId, action: 'update', task, expectedUpdatedAt });
|
|
161
|
+
console.log(JSON.stringify(updated.tasks.find(item => item.id === taskId), null, 2));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
142
164
|
}
|
|
143
165
|
const loaded = await storeFor(args);
|
|
144
166
|
const { store, localFile } = loaded;
|
|
145
|
-
const job =
|
|
167
|
+
const job = loaded.job;
|
|
146
168
|
if (action === 'list') {
|
|
147
169
|
console.log(JSON.stringify(job.tasks, null, 2));
|
|
148
170
|
return;
|
|
@@ -196,11 +218,14 @@ async function runTaskCommand(action, args) {
|
|
|
196
218
|
console.log(JSON.stringify(task, null, 2));
|
|
197
219
|
return;
|
|
198
220
|
}
|
|
199
|
-
if (action === 'retry' || action === 'cancel') {
|
|
221
|
+
if (action === 'retry' || action === 'recover' || action === 'cancel') {
|
|
200
222
|
let outputJob = job;
|
|
201
223
|
let outputTask = task;
|
|
202
|
-
if (action === 'retry') {
|
|
203
|
-
const
|
|
224
|
+
if (action === 'retry' || action === 'recover') {
|
|
225
|
+
const recoveryTaskId = action === 'recover' ? arg(args, '--recovery-task-id') : '';
|
|
226
|
+
if (action === 'recover' && !recoveryTaskId)
|
|
227
|
+
throw new Error('task recover requires --recovery-task-id');
|
|
228
|
+
const result = await dispatchTaskAction(store, { id: action === 'recover' ? 'task.recover' : 'task.retry', jobId: job.id, taskId: id, ...(recoveryTaskId ? { recoveryTaskId } : {}) });
|
|
204
229
|
if (result.job.state === 'DRAFT') {
|
|
205
230
|
await applyJobActivateAction(result.job);
|
|
206
231
|
await store.saveJob(result.job);
|
|
@@ -338,13 +363,7 @@ async function runJobCommand(action, args) {
|
|
|
338
363
|
throw new Error('job create with --head requires --objective-file PATH|- or --objective MARKDOWN containing the complete standalone Job context');
|
|
339
364
|
}
|
|
340
365
|
if (action === 'list') {
|
|
341
|
-
|
|
342
|
-
const overviews = snapshots.flatMap(snapshot => snapshot.jobs.filter(job => job.repository === repo)
|
|
343
|
-
.map(job => ({
|
|
344
|
-
job, githubState: snapshot.facts?.state ?? 'UNKNOWN',
|
|
345
|
-
draft: false, mergedAt: null, closedAt: null, publication: snapshot.publications[job.id]?.latest ?? null, publicationFailure: snapshot.publications[job.id]?.failure ?? null,
|
|
346
|
-
})));
|
|
347
|
-
console.log(JSON.stringify(dashboardManagedJobs(overviews), null, 2));
|
|
366
|
+
console.log(JSON.stringify(await relayJobList(args), null, 2));
|
|
348
367
|
return;
|
|
349
368
|
}
|
|
350
369
|
const id = arg(args, '--id');
|
|
@@ -358,49 +377,39 @@ async function runJobCommand(action, args) {
|
|
|
358
377
|
const objective = createObjective ?? '';
|
|
359
378
|
if (hasArg(args, '--local-merge-source') !== hasArg(args, '--local-merge-target'))
|
|
360
379
|
throw new Error('Supply both --local-merge-source and --local-merge-target');
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
priority: priority(args),
|
|
370
|
-
executionMode: requestedExecutionMode(args),
|
|
371
|
-
schedule: scheduleFromArgs(args),
|
|
372
|
-
tag: jobTagFromArgs(args),
|
|
373
|
-
reviewEnabled: !hasArg(args, '--no-review'),
|
|
374
|
-
continuation: continuation(args),
|
|
375
|
-
...(hasArg(args, '--reviewer-thread-id') ? { reviewerThreadId: arg(args, '--reviewer-thread-id') } : {}),
|
|
376
|
-
...(hasArg(args, '--creator-thread-id') ? { creatorThreadId: arg(args, '--creator-thread-id') } : {}),
|
|
377
|
-
prNumber,
|
|
378
|
-
head: arg(args, '--head') || undefined,
|
|
379
|
-
base: arg(args, '--base', 'main'),
|
|
380
|
-
body: arg(args, '--body') || objective || undefined
|
|
381
|
-
});
|
|
382
|
-
console.log(JSON.stringify({ repository: repo, pr: result.pr.number, job: result.job.id, executionMode: result.job.executionMode, state: result.job.state }, null, 2));
|
|
380
|
+
const result = await relayControlCommand(args, { command: 'job.create', repository: repo, initialPlan: hasArg(args, '--initial-plan'), options: {
|
|
381
|
+
id, title: arg(args, '--title') || undefined, objective: objective || undefined, projectPath: arg(args, '--project-path') || undefined,
|
|
382
|
+
priority: priority(args), executionMode: requestedExecutionMode(args), schedule: scheduleFromArgs(args), tag: jobTagFromArgs(args), reviewEnabled: !hasArg(args, '--no-review'), continuation: continuation(args),
|
|
383
|
+
reviewerThreadId: hasArg(args, '--reviewer-thread-id') ? arg(args, '--reviewer-thread-id') : undefined, creatorThreadId: hasArg(args, '--creator-thread-id') ? arg(args, '--creator-thread-id') : undefined,
|
|
384
|
+
prNumber, head: arg(args, '--head') || undefined, base: arg(args, '--base', 'main'), body: arg(args, '--body') || objective || undefined,
|
|
385
|
+
localMergeSource: arg(args, '--local-merge-source') || undefined, localMergeTarget: arg(args, '--local-merge-target') || undefined,
|
|
386
|
+
} });
|
|
387
|
+
console.log(JSON.stringify(result, null, 2));
|
|
383
388
|
return;
|
|
384
389
|
}
|
|
385
|
-
const
|
|
386
|
-
const { store, job } = loaded;
|
|
390
|
+
const job = await relayControlJob(args, id);
|
|
387
391
|
if (action === 'unblock') {
|
|
388
|
-
|
|
392
|
+
const reason = arg(args, '--reason');
|
|
393
|
+
if (!reason.trim())
|
|
394
|
+
throw new Error('job unblock requires --reason');
|
|
395
|
+
const updated = await relayControlCommand(args, { command: 'job.unblock', jobId: id, reason });
|
|
396
|
+
console.log(JSON.stringify(updated, null, 2));
|
|
389
397
|
return;
|
|
390
398
|
}
|
|
391
399
|
if (action === 'show') {
|
|
392
400
|
console.log(JSON.stringify(job, null, 2));
|
|
393
401
|
return;
|
|
394
402
|
}
|
|
395
|
-
|
|
403
|
+
const boundPrNumber = job.externalPrNumber ?? (job.prNumber > 0 ? job.prNumber : 0);
|
|
404
|
+
if (job.repository !== repo || (prValue && boundPrNumber !== prNumber)) {
|
|
396
405
|
throw new Error('Local Job/PR binding mismatch');
|
|
397
406
|
}
|
|
398
407
|
if (action === 'merge') {
|
|
399
408
|
throw new Error('GitHub is audit-only. Configure local merge source/target and reconcile the local Job instead.');
|
|
400
409
|
}
|
|
401
410
|
if (action === 'cancel') {
|
|
402
|
-
await
|
|
403
|
-
console.log(JSON.stringify(
|
|
411
|
+
const updated = await relayJobStateCommand(args, job.id, 'CANCELLED');
|
|
412
|
+
console.log(JSON.stringify(updated, null, 2));
|
|
404
413
|
return;
|
|
405
414
|
}
|
|
406
415
|
if (hasArg(args, '--title'))
|
|
@@ -445,12 +454,13 @@ async function runJobCommand(action, args) {
|
|
|
445
454
|
throw new Error('--continue-kind and --continue-target must be supplied together');
|
|
446
455
|
job.continuation = next;
|
|
447
456
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
457
|
+
const patch = {};
|
|
458
|
+
for (const key of ['title', 'objective', 'projectPath', 'priority', 'executionMode', 'schedule', 'tag', 'autoMerge', 'reviewEnabled', 'reviewerThreadId', 'creatorThreadId', 'continuation', 'localMerge'])
|
|
459
|
+
patch[key] = job[key];
|
|
460
|
+
console.log(JSON.stringify(await relayControlCommand(args, { command: 'job.update', jobId: id, patch }), null, 2));
|
|
451
461
|
}
|
|
452
462
|
export function runtimePlanner(args) { const plannerCommand = arg([...args], '--planner-command'); const modelRuntime = arg([...args], '--codex', 'codex'); return plannerCommand ? new CommandObjectivePlanner(plannerCommand) : new AgentObjectivePlanner(modelRuntime); }
|
|
453
|
-
export function runtime(store, args, bus, usageRegistry = codexAndZaiUsageRegistry()) { const emit = (event) => { process.stderr.write(`${JSON.stringify(event)}\n`); }; const modelRuntime = arg(args, '--codex', 'codex'); const policy = chatGptTabClosePolicy(args); const planner = runtimePlanner(args); return new Reconciler(store, { noGitHub: noGitHubMode(args), owner: arg(args, '--owner', `cli-${process.pid}`), maxConcurrent: Number(arg(args, '--concurrency', '4')), leaseMs: Number(arg(args, '--lease-ms', '300000')), adapters: [new CodexAdapter(modelRuntime), new ChatGptAdapter({ tabClosePolicy: policy })], continuations: [new CodexThreadContinuation(modelRuntime), new CommandContinuation(), new WebhookContinuation()], planner, usageSnapshotProvider: () => usageRegistry.snapshot(), decisionEngine: executionDecisionEngine(), decisionMode: decisionMode(), reviewer: runtimeReviewer(args), eventBus: bus, emit, chatgptTabClosePolicy: policy, projectPathForJob: async (job) =>
|
|
463
|
+
export function runtime(store, args, bus, usageRegistry = codexAndZaiUsageRegistry()) { const emit = (event) => { process.stderr.write(`${JSON.stringify(event)}\n`); }; const modelRuntime = arg(args, '--codex', 'codex'); const policy = chatGptTabClosePolicy(args); const planner = runtimePlanner(args); return new Reconciler(store, { noGitHub: noGitHubMode(args), owner: arg(args, '--owner', `cli-${process.pid}`), maxConcurrent: Number(arg(args, '--concurrency', '4')), leaseMs: Number(arg(args, '--lease-ms', '300000')), adapters: [new CodexAdapter(modelRuntime), new ChatGptAdapter({ tabClosePolicy: policy })], continuations: [new CodexThreadContinuation(modelRuntime), new CommandContinuation(), new WebhookContinuation()], planner, usageSnapshotProvider: () => usageRegistry.snapshot(), decisionEngine: executionDecisionEngine(), decisionMode: decisionMode(), reviewer: runtimeReviewer(args), eventBus: bus, emit, chatgptTabClosePolicy: policy, projectPathForJob: async (job) => { const workspaceRoot = arg(args, '--workspace-root') || defaultWorkspaceRoot(); const canonical = await resolveManagedProjectPath(noGitHubMode(args) ? undefined : job.repository, arg(args, '--project-path'), workspaceRoot); return job.prNumber === 0 && job.localMerge?.sourceRef ? prepareLocalJobExecutionWorktree(canonical, job, workspaceRoot) : resolveRepositoryProjectPath(canonical, job.projectPath); } }); }
|
|
454
464
|
export function dashboardJobAction(store, reconciler, action) {
|
|
455
465
|
return async (jobId) => {
|
|
456
466
|
const job = await store.load(jobId);
|
|
@@ -477,7 +487,7 @@ export async function createService(args) {
|
|
|
477
487
|
let overviewProvider;
|
|
478
488
|
if (repo) {
|
|
479
489
|
overviewProvider = async () => {
|
|
480
|
-
const snapshots = await discoverLocalRelayStoreSnapshots(
|
|
490
|
+
const snapshots = await discoverLocalRelayStoreSnapshots(relayStoreDirectory());
|
|
481
491
|
return dashboardManagedJobs(snapshots.flatMap(snapshot => snapshot.jobs
|
|
482
492
|
.filter(job => job.repository === repo)
|
|
483
493
|
.map(job => ({
|
|
@@ -490,7 +500,8 @@ export async function createService(args) {
|
|
|
490
500
|
const webhook = !noGitHubMode(args) && repo && webhookSecret ? {
|
|
491
501
|
secret: webhookSecret,
|
|
492
502
|
repositories: new Set([repo]),
|
|
493
|
-
pullRequest: loaded.job.prNumber,
|
|
503
|
+
pullRequest: loaded.job.externalPrNumber ?? loaded.job.prNumber,
|
|
504
|
+
jobId: loaded.job.id,
|
|
494
505
|
wake: async (event) => {
|
|
495
506
|
if (event.pullRequestFacts && loaded.store.applyPullRequestFacts) {
|
|
496
507
|
await loaded.store.applyPullRequestFacts({
|
|
@@ -606,19 +617,18 @@ async function main() {
|
|
|
606
617
|
return;
|
|
607
618
|
}
|
|
608
619
|
if (command === 'init') {
|
|
620
|
+
const file = arg(args, '--file');
|
|
609
621
|
const now = new Date().toISOString();
|
|
610
622
|
const initialSchedule = scheduleFromArgs(args);
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
file = arg(args, '--file');
|
|
618
|
-
if (!file)
|
|
619
|
-
throw new Error('init requires --repo/--pr or explicit --file demo mode');
|
|
620
|
-
store = new InMemoryStore(job);
|
|
623
|
+
const id = arg(args, '--id', randomUUID());
|
|
624
|
+
if (!file) {
|
|
625
|
+
const repo = repository(args);
|
|
626
|
+
const result = await relayControlCommand(args, { command: 'job.create', repository: repo, options: { id, title: arg(args, '--title', 'Agents Relay job'), objective: arg(args, '--objective') || undefined, priority: priority(args), executionMode: executionMode(args), schedule: initialSchedule, reviewEnabled: !hasArg(args, '--no-review'), continuation: continuation(args), prNumber: Number(arg(args, '--pr', '0')) || 0 } });
|
|
627
|
+
console.log(JSON.stringify(result, null, 2));
|
|
628
|
+
return;
|
|
621
629
|
}
|
|
630
|
+
const job = createJobEntity({ id, title: arg(args, '--title', 'Agents Relay job'), objective: arg(args, '--objective') || undefined, priority: priority(args), executionMode: executionMode(args), schedule: initialSchedule, autoMerge: !hasArg(args, '--no-auto-merge'), reviewEnabled: !hasArg(args, '--no-review'), prNumber: Number(arg(args, '--pr', '0')), repository: arg(args, '--repo'), continuation: continuation(args), createdAt: now, updatedAt: now, tasks: [] }, { activate: Boolean(initialSchedule) });
|
|
631
|
+
const store = new InMemoryStore(job);
|
|
622
632
|
await store.saveJob(job);
|
|
623
633
|
await saveLocal(file, job);
|
|
624
634
|
console.log(`Initialized ${job.id}`);
|
|
@@ -641,6 +651,46 @@ async function main() {
|
|
|
641
651
|
console.log(JSON.stringify(await response.json(), null, 2));
|
|
642
652
|
return;
|
|
643
653
|
}
|
|
654
|
+
if (!arg(args, '--file') && ['plan', 'reconcile', 'record', 'agent-register', 'agent-discover'].includes(command)) {
|
|
655
|
+
const jobId = arg(args, '--id');
|
|
656
|
+
if (!jobId)
|
|
657
|
+
throw new Error(`${command} requires --repo and --id`);
|
|
658
|
+
if (command === 'plan') {
|
|
659
|
+
const guidance = hasArg(args, '--prompt') || hasArg(args, '--prompt-file') ? await textOption(args, '--prompt', '--prompt-file') : undefined;
|
|
660
|
+
console.log(JSON.stringify(await relayControlCommand(args, { command: 'job.plan', jobId, ...(guidance?.trim() ? { guidance: guidance.trim() } : {}) }), null, 2));
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
if (command === 'reconcile') {
|
|
664
|
+
console.log(JSON.stringify(await relayControlCommand(args, { command: 'job.reconcile', jobId }), null, 2));
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
if (command === 'record') {
|
|
668
|
+
const now = new Date().toISOString();
|
|
669
|
+
const id = arg(args, '--task-id', randomUUID());
|
|
670
|
+
const summary = arg(args, '--summary');
|
|
671
|
+
if (!summary)
|
|
672
|
+
throw new Error('record requires --summary');
|
|
673
|
+
const commit = arg(args, '--commit');
|
|
674
|
+
const task = { jobId, id, projectName: arg(args, '--project') || undefined, agentName: arg(args, '--agent', 'orchestrator'), parentTaskId: arg(args, '--parent') || null, dependencies: arg(args, '--deps').split(',').filter(Boolean), capabilities: [], adapter: 'orchestrator', input: arg(args, '--input', summary), continuation: null, continuationDeliveredAt: null, state: 'SUCCEEDED', attempt: 1, maxAttempts: 1, leaseOwner: null, leaseExpiresAt: null, executionId: commit || null, threadId: null, result: { summary, data: commit ? { commit } : undefined }, error: null, timeoutMs: 0, createdAt: now, updatedAt: now };
|
|
675
|
+
const updated = await relayControlCommand(args, { command: 'record', jobId, task });
|
|
676
|
+
console.log(JSON.stringify(updated.tasks.find(item => item.id === id), null, 2));
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
if (command === 'agent-register') {
|
|
680
|
+
const now = new Date().toISOString();
|
|
681
|
+
const agent = { id: arg(args, '--agent-id', randomUUID()), name: arg(args, '--name'), responsibility: arg(args, '--responsibility'), role: arg(args, '--role'), capabilities: arg(args, '--capabilities').split(',').filter(Boolean), boundaries: arg(args, '--boundaries').split('|').filter(Boolean), endpoint: { kind: arg(args, '--endpoint-kind', 'adapter'), target: arg(args, '--endpoint') }, runtime: { adapter: arg(args, '--runtime', arg(args, '--adapter', 'shell')), provider: arg(args, '--provider') || undefined, model: arg(args, '--model') || undefined }, availability: arg(args, '--availability', 'available'), routing: { labels: arg(args, '--labels').split(',').filter(Boolean).reduce((out, item) => { const [key, value] = item.split('=', 2); if (key && value)
|
|
682
|
+
out[key] = value; return out; }, {}), trustDomains: arg(args, '--trust-domains').split(',').filter(Boolean), regions: arg(args, '--regions').split(',').filter(Boolean) }, evidence: { evaluations: [], outcomes: { succeeded: 0, failed: 0, timedOut: 0 }, lastObservedAt: null }, registeredAt: now, updatedAt: now };
|
|
683
|
+
if (!agent.name || !agent.responsibility || !agent.role || !agent.endpoint.target)
|
|
684
|
+
throw new Error('--name, --responsibility, --role, and --endpoint are required');
|
|
685
|
+
console.log(JSON.stringify(await relayControlCommand(args, { command: 'agent.register', jobId, agent }), null, 2));
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
if (command === 'agent-discover') {
|
|
689
|
+
const requirements = { capabilities: arg(args, '--capabilities').split(',').filter(Boolean), runtime: arg(args, '--runtime') || undefined, availability: arg(args, '--availability').split(',').filter(Boolean), trustDomain: arg(args, '--trust-domain') || undefined, taskKind: arg(args, '--task-kind') || undefined, explore: args.includes('--explore') };
|
|
690
|
+
console.log(JSON.stringify(await relayControlCommand(args, { command: 'agent.discover', jobId, requirements }), null, 2));
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
644
694
|
const loaded = await storeFor(args);
|
|
645
695
|
const { store, job, localFile } = loaded;
|
|
646
696
|
if (command === 'plan') {
|