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.js
CHANGED
|
@@ -1,139 +1,48 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { configureLocalGitMerge } from './cli-local-merge-config.js';
|
|
5
|
+
import { noGitHubMode } from './no-github.js';
|
|
6
|
+
import { discoverLocalRelayStoreSnapshots } from './relay-store.js';
|
|
2
7
|
import { realpathSync } from 'node:fs';
|
|
3
8
|
import { once } from 'node:events';
|
|
4
9
|
import { fileURLToPath } from 'node:url';
|
|
5
10
|
import { randomUUID } from 'node:crypto';
|
|
6
|
-
import { dashboardManagedJobs, InMemoryStore
|
|
7
|
-
import { githubAuthContext } from './github-auth.js';
|
|
11
|
+
import { dashboardManagedJobs, InMemoryStore } from './store.js';
|
|
8
12
|
import { taskOverallTimeoutMs } from './types.js';
|
|
9
13
|
import { Reconciler } from './reconciler.js';
|
|
10
14
|
import { dashboardJobSetState, dashboardTaskAction, dashboardTaskSetState } from './dashboard-actions.js';
|
|
11
15
|
export { dashboardJobSetState, dashboardTaskAction, dashboardTaskSetState } from './dashboard-actions.js';
|
|
12
16
|
import { CodexAdapter, ChatGptAdapter } from './adapters.js';
|
|
13
17
|
import { CodexThreadContinuation, CommandContinuation, WebhookContinuation } from './continuation.js';
|
|
14
|
-
import { eventFor } from './events.js';
|
|
15
18
|
import { serveDashboard } from './dashboard.js';
|
|
16
19
|
import { codexAndZaiUsageRegistry } from './usage.js';
|
|
17
20
|
import { decisionMode, executionDecisionEngine } from './decision.js';
|
|
18
21
|
import { discoverAgents } from './registry.js';
|
|
19
22
|
import { AgentObjectivePlanner, CommandObjectivePlanner } from './planner.js';
|
|
20
|
-
import { transitionJobEvent } from './scheduler.js';
|
|
21
23
|
import { applyOrchestratorStartAction, applyOrchestratorCompleteAction, applyTaskHoldAction, applyTaskQueueAction, dispatchTaskAction } from './task-actions.js';
|
|
22
24
|
import { applyJobActivateAction, applyJobCancelAction } from './job-actions.js';
|
|
23
25
|
import { resolveExecutionContext } from './execution-context.js';
|
|
24
|
-
import {
|
|
26
|
+
import { startServiceExternalActionFlusher } from './service-external-actions.js';
|
|
27
|
+
import { handleServicePublication } from './service-publication.js';
|
|
28
|
+
import { defaultWorkspaceRoot, prepareLocalJobExecutionWorktree, resolveManagedProjectPath, resolveRepositoryProjectPath } from './workspace.js';
|
|
25
29
|
import { runBlockerCommand } from './cli-blocker.js';
|
|
26
30
|
import { runEscalationCommand } from './cli-escalation.js';
|
|
27
31
|
import { runProjectionCommand } from './cli-projection.js';
|
|
28
32
|
import { helpText, usage } from './cli-help.js';
|
|
29
33
|
import { parseAgentGraph } from './agent-graph.js';
|
|
30
|
-
import {
|
|
31
|
-
import { createJobEntity
|
|
34
|
+
import { runTaskUnblockCommand } from './cli-unblock.js';
|
|
35
|
+
import { createJobEntity } from './workflow/entity-creation.js';
|
|
32
36
|
import { runCliEntrypoint } from './cli-shutdown.js';
|
|
33
37
|
import { shutdownOwnedProcesses } from './owned-process.js';
|
|
34
38
|
export { helpText } from './cli-help.js';
|
|
35
39
|
import { runReviewCommand } from './cli-review.js';
|
|
36
40
|
import { normalizeThreadId, runtimeReviewer } from './reviewer-thread.js';
|
|
37
41
|
export { runtimeReviewer } from './reviewer-thread.js';
|
|
38
|
-
import { SERVICE_DEFAULTS, ServiceEventBus, arg, hasArg, textOption, integerArg, repository, storeFor, saveLocal,
|
|
42
|
+
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';
|
|
39
43
|
export { SERVICE_DEFAULTS, eventBus, executionMode, chatGptTabClosePolicy } from './cli-support.js';
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
throw new Error('--id is required for managed GitHub jobs');
|
|
43
|
-
const pr = await resolvePullRequest(client, repositoryName, { prNumber: options.prNumber, head: options.head, base: options.base, title: options.title, body: options.body });
|
|
44
|
-
const store = new GitHubStore(client, repositoryName, pr.number, trustedAuthors);
|
|
45
|
-
try {
|
|
46
|
-
const existing = await store.load(options.id);
|
|
47
|
-
return { job: existing, pr };
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
if (!(error instanceof Error) || !error.message.startsWith('JOB_MARKER_MISSING:'))
|
|
51
|
-
throw error;
|
|
52
|
-
}
|
|
53
|
-
const now = new Date().toISOString();
|
|
54
|
-
const job = createJobEntity({
|
|
55
|
-
id: options.id,
|
|
56
|
-
title: options.title || pr.title || 'Agents Relay job',
|
|
57
|
-
objective: options.objective || pr.body || undefined,
|
|
58
|
-
projectPath: options.projectPath,
|
|
59
|
-
description: pr.body || undefined,
|
|
60
|
-
priority: options.priority ?? 'P2',
|
|
61
|
-
executionMode: options.executionMode ?? 'fixed',
|
|
62
|
-
tag: options.tag,
|
|
63
|
-
autoMerge: true,
|
|
64
|
-
reviewEnabled: options.reviewEnabled ?? true,
|
|
65
|
-
...(options.reviewerThreadId ? { reviewerThreadId: normalizeThreadId(options.reviewerThreadId, 'reviewer thread ID') } : {}),
|
|
66
|
-
...(options.creatorThreadId ? { creatorThreadId: normalizeThreadId(options.creatorThreadId, 'creator thread ID') } : {}),
|
|
67
|
-
prNumber: pr.number,
|
|
68
|
-
repository: repositoryName,
|
|
69
|
-
continuation: options.continuation ?? null,
|
|
70
|
-
createdAt: now,
|
|
71
|
-
updatedAt: now,
|
|
72
|
-
tasks: [],
|
|
73
|
-
...(options.schedule ? { schedule: options.schedule } : {}),
|
|
74
|
-
}, { activate: Boolean(options.schedule) });
|
|
75
|
-
await store.saveJob(job);
|
|
76
|
-
return { job: await store.load(options.id), pr };
|
|
77
|
-
}
|
|
78
|
-
async function workerTaskFromArgs(job, args, now) {
|
|
79
|
-
const model = arg(args, '--model');
|
|
80
|
-
const adapterValue = arg(args, '--adapter');
|
|
81
|
-
const adapter = (adapterValue || 'codex');
|
|
82
|
-
const outputKind = arg(args, '--output');
|
|
83
|
-
const outputPath = arg(args, '--output-path');
|
|
84
|
-
const input = await textOption(args, '--input', '--input-file');
|
|
85
|
-
const agentGraph = hasArg(args, '--agent-graph') || hasArg(args, '--agent-graph-file') ? await textOption(args, '--agent-graph', '--agent-graph-file') : undefined;
|
|
86
|
-
if (agentGraph)
|
|
87
|
-
parseAgentGraph(agentGraph);
|
|
88
|
-
if (!input.trim())
|
|
89
|
-
throw new Error('task create requires --input-file PATH|- or --input MARKDOWN');
|
|
90
|
-
if (!['codex', 'chatgpt', 'orchestrator'].includes(adapter))
|
|
91
|
-
throw new Error('task create --adapter must be codex, chatgpt, or orchestrator');
|
|
92
|
-
if (outputKind !== 'task-pr' && outputKind !== 'file')
|
|
93
|
-
throw new Error('task create requires --output task-pr|file');
|
|
94
|
-
if (outputKind === 'file' && !outputPath)
|
|
95
|
-
throw new Error('--output file requires --output-path');
|
|
96
|
-
if (adapter !== 'orchestrator' && ['--provider', '--profile', '--reasoning', '--cwd', '--chatgpt-project'].some(flag => hasArg(args, flag)) && !model)
|
|
97
|
-
throw new Error('task create explicit routing requires --model');
|
|
98
|
-
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 };
|
|
99
|
-
const workerRequirements = adapter === 'orchestrator' || routing ? undefined : { capabilities: [], runtime: adapterValue ? adapter : undefined };
|
|
100
|
-
const closePolicy = hasArg(args, '--chatgpt-tab-close-policy') ? chatGptTabClosePolicy(args) : undefined;
|
|
101
|
-
const size = storySize(args);
|
|
102
|
-
const dependencies = arg(args, '--deps').split(',').filter(Boolean);
|
|
103
|
-
const timeoutMs = adapter === 'orchestrator' ? 0 : hasArg(args, '--timeout') ? integerArg(args, '--timeout', taskOverallTimeoutMs(size, routing?.reasoning)) : routing ? taskOverallTimeoutMs(size, routing.reasoning) : 0;
|
|
104
|
-
return createTaskEntity(job, {
|
|
105
|
-
jobId: job.id,
|
|
106
|
-
id: arg(args, '--task-id', 'initial'),
|
|
107
|
-
priority: priority(args, job.priority ?? 'P2'),
|
|
108
|
-
storySize: size,
|
|
109
|
-
projectName: arg(args, '--project') || undefined,
|
|
110
|
-
agentName: arg(args, '--agent') || undefined,
|
|
111
|
-
agentGraph,
|
|
112
|
-
parentTaskId: arg(args, '--parent') || null,
|
|
113
|
-
dependencies,
|
|
114
|
-
capabilities: arg(args, '--capabilities').split(',').filter(Boolean),
|
|
115
|
-
workerRequirements,
|
|
116
|
-
adapter,
|
|
117
|
-
chatgptTabClosePolicy: closePolicy,
|
|
118
|
-
input: input || 'true',
|
|
119
|
-
attachments: arg(args, '--attachments').split(',').filter(Boolean),
|
|
120
|
-
output: outputKind === 'file' ? { kind: 'file', path: outputPath } : { kind: 'task_pr' },
|
|
121
|
-
routing,
|
|
122
|
-
continuation: continuation(args),
|
|
123
|
-
continuationDeliveredAt: null,
|
|
124
|
-
attempt: 0,
|
|
125
|
-
maxAttempts: Number(arg(args, '--max-attempts', '3')),
|
|
126
|
-
leaseOwner: null,
|
|
127
|
-
leaseExpiresAt: null,
|
|
128
|
-
executionId: null,
|
|
129
|
-
threadId: null,
|
|
130
|
-
result: null,
|
|
131
|
-
error: null,
|
|
132
|
-
timeoutMs,
|
|
133
|
-
createdAt: now,
|
|
134
|
-
updatedAt: now
|
|
135
|
-
}, { defer: hasArg(args, '--draft') });
|
|
136
|
-
}
|
|
44
|
+
import { workerTaskFromArgs } from './cli-worker-task.js';
|
|
45
|
+
export { ensureManagedGitHubJob } from './cli-job-create.js';
|
|
137
46
|
async function runManagedTaskCommand(action, args) {
|
|
138
47
|
const jobId = arg(args, '--id');
|
|
139
48
|
if (!jobId)
|
|
@@ -164,16 +73,100 @@ async function runManagedTaskCommand(action, args) {
|
|
|
164
73
|
console.log(JSON.stringify(task, null, 2));
|
|
165
74
|
}
|
|
166
75
|
async function runTaskCommand(action, args) {
|
|
167
|
-
if (!['create', 'update', 'show', 'list', 'start', 'complete', 'retry', 'unblock', 'cancel'].includes(action))
|
|
76
|
+
if (!['create', 'update', 'show', 'list', 'start', 'complete', 'retry', 'recover', 'unblock', 'cancel'].includes(action))
|
|
168
77
|
throw new Error(usage);
|
|
169
|
-
const managed =
|
|
170
|
-
if (managed
|
|
171
|
-
|
|
172
|
-
|
|
78
|
+
const managed = !arg(args, '--file') && Boolean(arg(args, '--repo') && arg(args, '--id'));
|
|
79
|
+
if (managed) {
|
|
80
|
+
const jobId = arg(args, '--id');
|
|
81
|
+
if (action === 'list') {
|
|
82
|
+
const job = await relayControlJob(args, jobId);
|
|
83
|
+
console.log(JSON.stringify(job.tasks, null, 2));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const taskId = arg(args, '--task-id', action === 'create' ? randomUUID() : '');
|
|
87
|
+
if (action === 'show') {
|
|
88
|
+
if (!taskId)
|
|
89
|
+
throw new Error('task show requires --task-id');
|
|
90
|
+
const job = await relayControlJob(args, jobId);
|
|
91
|
+
const task = job.tasks.find(item => item.id === taskId);
|
|
92
|
+
if (!task)
|
|
93
|
+
throw new Error(`Task ${taskId} not found`);
|
|
94
|
+
console.log(JSON.stringify(task, null, 2));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (['create', 'start', 'complete', 'retry', 'cancel'].includes(action)) {
|
|
98
|
+
await runManagedTaskCommand(action, args);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (action === 'recover') {
|
|
102
|
+
if (!taskId)
|
|
103
|
+
throw new Error('task recover requires --task-id');
|
|
104
|
+
const recoveryTaskId = arg(args, '--recovery-task-id');
|
|
105
|
+
if (!recoveryTaskId)
|
|
106
|
+
throw new Error('task recover requires --recovery-task-id');
|
|
107
|
+
const job = await relayControlCommand(args, { command: 'task.recover', jobId, taskId, recoveryTaskId });
|
|
108
|
+
console.log(JSON.stringify(job.tasks.find(item => item.id === taskId), null, 2));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (action === 'unblock') {
|
|
112
|
+
if (!taskId)
|
|
113
|
+
throw new Error('task unblock requires --task-id');
|
|
114
|
+
const reason = arg(args, '--reason');
|
|
115
|
+
if (!reason.trim())
|
|
116
|
+
throw new Error('task unblock requires --reason');
|
|
117
|
+
const job = await relayControlCommand(args, { command: 'task.unblock', jobId, taskId, reason });
|
|
118
|
+
console.log(JSON.stringify(job.tasks.find(item => item.id === taskId), null, 2));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (action === 'update') {
|
|
122
|
+
const job = await relayControlJob(args, jobId);
|
|
123
|
+
const task = job.tasks.find(item => item.id === taskId);
|
|
124
|
+
if (!task)
|
|
125
|
+
throw new Error(`Task ${taskId} not found`);
|
|
126
|
+
const expectedUpdatedAt = task.updatedAt;
|
|
127
|
+
if (['RUNNING', 'UNBLOCKED', 'SUCCEEDED', 'CANCELLED'].includes(task.state))
|
|
128
|
+
throw new Error(`Task ${taskId} is ${task.state}; durable execution fields are immutable in this state`);
|
|
129
|
+
if (hasArg(args, '--adapter')) {
|
|
130
|
+
const adapter = arg(args, '--adapter');
|
|
131
|
+
if (!['codex', 'chatgpt', 'orchestrator'].includes(adapter))
|
|
132
|
+
throw new Error('--adapter must be codex, chatgpt, or orchestrator');
|
|
133
|
+
task.adapter = adapter;
|
|
134
|
+
}
|
|
135
|
+
if (hasArg(args, '--priority'))
|
|
136
|
+
task.priority = priority(args, task.priority ?? job.priority ?? 'P2');
|
|
137
|
+
if (hasArg(args, '--story-size')) {
|
|
138
|
+
task.storySize = storySize(args, task.storySize ?? 'M');
|
|
139
|
+
if (!hasArg(args, '--timeout'))
|
|
140
|
+
task.timeoutMs = taskOverallTimeoutMs(task.storySize, task.routing?.reasoning);
|
|
141
|
+
}
|
|
142
|
+
if (hasArg(args, '--input') || hasArg(args, '--input-file'))
|
|
143
|
+
task.input = await textOption(args, '--input', '--input-file');
|
|
144
|
+
if (hasArg(args, '--project'))
|
|
145
|
+
task.projectName = arg(args, '--project') || undefined;
|
|
146
|
+
if (hasArg(args, '--agent'))
|
|
147
|
+
task.agentName = arg(args, '--agent') || undefined;
|
|
148
|
+
if (hasArg(args, '--parent'))
|
|
149
|
+
task.parentTaskId = arg(args, '--parent') || null;
|
|
150
|
+
if (hasArg(args, '--deps'))
|
|
151
|
+
task.dependencies = arg(args, '--deps').split(',').filter(Boolean);
|
|
152
|
+
if (hasArg(args, '--capabilities'))
|
|
153
|
+
task.capabilities = arg(args, '--capabilities').split(',').filter(Boolean);
|
|
154
|
+
task.timeoutMs = integerArg(args, '--timeout', task.timeoutMs);
|
|
155
|
+
task.maxAttempts = integerArg(args, '--max-attempts', task.maxAttempts);
|
|
156
|
+
if (hasArg(args, '--draft') && hasArg(args, '--queue'))
|
|
157
|
+
throw new Error('--draft and --queue are mutually exclusive');
|
|
158
|
+
if (hasArg(args, '--draft') && task.state === 'QUEUED')
|
|
159
|
+
await applyTaskHoldAction(task);
|
|
160
|
+
if (hasArg(args, '--queue'))
|
|
161
|
+
await applyTaskQueueAction(job, task);
|
|
162
|
+
const updated = await relayTaskCommand(args, { jobId, taskId, action: 'update', task, expectedUpdatedAt });
|
|
163
|
+
console.log(JSON.stringify(updated.tasks.find(item => item.id === taskId), null, 2));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
173
166
|
}
|
|
174
167
|
const loaded = await storeFor(args);
|
|
175
168
|
const { store, localFile } = loaded;
|
|
176
|
-
const job =
|
|
169
|
+
const job = loaded.job;
|
|
177
170
|
if (action === 'list') {
|
|
178
171
|
console.log(JSON.stringify(job.tasks, null, 2));
|
|
179
172
|
return;
|
|
@@ -227,11 +220,14 @@ async function runTaskCommand(action, args) {
|
|
|
227
220
|
console.log(JSON.stringify(task, null, 2));
|
|
228
221
|
return;
|
|
229
222
|
}
|
|
230
|
-
if (action === 'retry' || action === 'cancel') {
|
|
223
|
+
if (action === 'retry' || action === 'recover' || action === 'cancel') {
|
|
231
224
|
let outputJob = job;
|
|
232
225
|
let outputTask = task;
|
|
233
|
-
if (action === 'retry') {
|
|
234
|
-
const
|
|
226
|
+
if (action === 'retry' || action === 'recover') {
|
|
227
|
+
const recoveryTaskId = action === 'recover' ? arg(args, '--recovery-task-id') : '';
|
|
228
|
+
if (action === 'recover' && !recoveryTaskId)
|
|
229
|
+
throw new Error('task recover requires --recovery-task-id');
|
|
230
|
+
const result = await dispatchTaskAction(store, { id: action === 'recover' ? 'task.recover' : 'task.retry', jobId: job.id, taskId: id, ...(recoveryTaskId ? { recoveryTaskId } : {}) });
|
|
235
231
|
if (result.job.state === 'DRAFT') {
|
|
236
232
|
await applyJobActivateAction(result.job);
|
|
237
233
|
await store.saveJob(result.job);
|
|
@@ -365,75 +361,57 @@ async function runJobCommand(action, args) {
|
|
|
365
361
|
if (invalid)
|
|
366
362
|
throw new Error(`job create does not accept task execution option ${invalid}; create the job first, then let the orchestrator create the initial plan/task batch`);
|
|
367
363
|
createObjective = await textOption(args, '--objective', '--objective-file');
|
|
368
|
-
if (!arg(args, '--pr') && !createObjective.trim())
|
|
364
|
+
if ((!arg(args, '--pr') || noGitHubMode(args)) && !createObjective.trim())
|
|
369
365
|
throw new Error('job create with --head requires --objective-file PATH|- or --objective MARKDOWN containing the complete standalone Job context');
|
|
370
366
|
}
|
|
371
|
-
const auth = await githubAuthContext(args);
|
|
372
367
|
if (action === 'list') {
|
|
373
|
-
console.log(JSON.stringify(await
|
|
368
|
+
console.log(JSON.stringify(await relayJobList(args), null, 2));
|
|
374
369
|
return;
|
|
375
370
|
}
|
|
376
371
|
const id = arg(args, '--id');
|
|
377
372
|
if (!id)
|
|
378
|
-
throw new Error('--id is required for managed
|
|
373
|
+
throw new Error('--id is required for managed local Jobs');
|
|
379
374
|
const prValue = arg(args, '--pr');
|
|
380
|
-
const prNumber = prValue ? Number(prValue) :
|
|
375
|
+
const prNumber = prValue ? Number(prValue) : 0;
|
|
381
376
|
if (prValue && (!Number.isInteger(prNumber) || (prNumber ?? 0) <= 0))
|
|
382
377
|
throw new Error('--pr must be a positive integer');
|
|
383
378
|
if (action === 'create') {
|
|
384
|
-
if (!prNumber && !arg(args, '--head'))
|
|
385
|
-
throw new Error('job create requires either --head or --pr');
|
|
386
379
|
const objective = createObjective ?? '';
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
prNumber,
|
|
401
|
-
head: arg(args, '--head') || undefined,
|
|
402
|
-
base: arg(args, '--base', 'main'),
|
|
403
|
-
body: arg(args, '--body') || objective || undefined
|
|
404
|
-
});
|
|
405
|
-
console.log(JSON.stringify({ repository: repo, pr: result.pr.number, job: result.job.id, executionMode: result.job.executionMode, state: result.job.state }, null, 2));
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
if (!prNumber)
|
|
409
|
-
throw new Error(`job ${action} requires --pr`);
|
|
410
|
-
const store = new GitHubStore(auth.client, repo, prNumber, auth.trustedAuthors);
|
|
411
|
-
const job = await store.load(id);
|
|
380
|
+
if (hasArg(args, '--local-merge-source') !== hasArg(args, '--local-merge-target'))
|
|
381
|
+
throw new Error('Supply both --local-merge-source and --local-merge-target');
|
|
382
|
+
const result = await relayControlCommand(args, { command: 'job.create', repository: repo, initialPlan: hasArg(args, '--initial-plan'), options: {
|
|
383
|
+
id, title: arg(args, '--title') || undefined, objective: objective || undefined, projectPath: arg(args, '--project-path') || undefined,
|
|
384
|
+
priority: priority(args), executionMode: requestedExecutionMode(args), schedule: scheduleFromArgs(args), tag: jobTagFromArgs(args), reviewEnabled: !hasArg(args, '--no-review'), continuation: continuation(args),
|
|
385
|
+
reviewerThreadId: hasArg(args, '--reviewer-thread-id') ? arg(args, '--reviewer-thread-id') : undefined, creatorThreadId: hasArg(args, '--creator-thread-id') ? arg(args, '--creator-thread-id') : undefined,
|
|
386
|
+
prNumber, head: arg(args, '--head') || undefined, base: arg(args, '--base', 'main'), body: arg(args, '--body') || objective || undefined,
|
|
387
|
+
localMergeSource: arg(args, '--local-merge-source') || undefined, localMergeTarget: arg(args, '--local-merge-target') || undefined,
|
|
388
|
+
} });
|
|
389
|
+
console.log(JSON.stringify(result, null, 2));
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const job = await relayControlJob(args, id);
|
|
412
393
|
if (action === 'unblock') {
|
|
413
|
-
|
|
394
|
+
const reason = arg(args, '--reason');
|
|
395
|
+
if (!reason.trim())
|
|
396
|
+
throw new Error('job unblock requires --reason');
|
|
397
|
+
const updated = await relayControlCommand(args, { command: 'job.unblock', jobId: id, reason });
|
|
398
|
+
console.log(JSON.stringify(updated, null, 2));
|
|
414
399
|
return;
|
|
415
400
|
}
|
|
416
401
|
if (action === 'show') {
|
|
417
402
|
console.log(JSON.stringify(job, null, 2));
|
|
418
403
|
return;
|
|
419
404
|
}
|
|
420
|
-
|
|
405
|
+
const boundPrNumber = job.externalPrNumber ?? (job.prNumber > 0 ? job.prNumber : 0);
|
|
406
|
+
if (job.repository !== repo || (prValue && boundPrNumber !== prNumber)) {
|
|
407
|
+
throw new Error('Local Job/PR binding mismatch');
|
|
408
|
+
}
|
|
421
409
|
if (action === 'merge') {
|
|
422
|
-
|
|
423
|
-
throw new Error(`Cannot merge cancelled job ${job.id}`);
|
|
424
|
-
const headSha = await store.headSha();
|
|
425
|
-
await store.mergePullRequest(headSha ?? undefined);
|
|
426
|
-
await transitionJobEvent(job, 'pr.merged');
|
|
427
|
-
await store.saveJob(job);
|
|
428
|
-
const resolved = await store.resolveBlockerDependents(id, 'merge');
|
|
429
|
-
await withEventBus(args, bus => publishDependentUnblocked(bus, resolved.unblocked, `Blocker Job ${id} resolved (merge)`));
|
|
430
|
-
console.log(JSON.stringify(await store.load(id), null, 2));
|
|
431
|
-
return;
|
|
410
|
+
throw new Error('GitHub is audit-only. Configure local merge source/target and reconcile the local Job instead.');
|
|
432
411
|
}
|
|
433
412
|
if (action === 'cancel') {
|
|
434
|
-
await
|
|
435
|
-
|
|
436
|
-
console.log(JSON.stringify(await store.load(id), null, 2));
|
|
413
|
+
const updated = await relayJobStateCommand(args, job.id, 'CANCELLED');
|
|
414
|
+
console.log(JSON.stringify(updated, null, 2));
|
|
437
415
|
return;
|
|
438
416
|
}
|
|
439
417
|
if (hasArg(args, '--title'))
|
|
@@ -446,6 +424,8 @@ async function runJobCommand(action, args) {
|
|
|
446
424
|
}
|
|
447
425
|
if (hasArg(args, '--project-path'))
|
|
448
426
|
job.projectPath = arg(args, '--project-path') || undefined;
|
|
427
|
+
if (hasArg(args, '--local-merge-source') || hasArg(args, '--local-merge-target'))
|
|
428
|
+
configureLocalGitMerge(job, arg(args, '--local-merge-source'), arg(args, '--local-merge-target'), noGitHubMode(args));
|
|
449
429
|
if (hasArg(args, '--priority'))
|
|
450
430
|
job.priority = priority(args, job.priority ?? 'P2');
|
|
451
431
|
if (hasArg(args, '--mode') || hasArg(args, '--execution-mode'))
|
|
@@ -476,12 +456,13 @@ async function runJobCommand(action, args) {
|
|
|
476
456
|
throw new Error('--continue-kind and --continue-target must be supplied together');
|
|
477
457
|
job.continuation = next;
|
|
478
458
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
459
|
+
const patch = {};
|
|
460
|
+
for (const key of ['title', 'objective', 'projectPath', 'priority', 'executionMode', 'schedule', 'tag', 'autoMerge', 'reviewEnabled', 'reviewerThreadId', 'creatorThreadId', 'continuation', 'localMerge'])
|
|
461
|
+
patch[key] = job[key];
|
|
462
|
+
console.log(JSON.stringify(await relayControlCommand(args, { command: 'job.update', jobId: id, patch }), null, 2));
|
|
482
463
|
}
|
|
483
464
|
export function runtimePlanner(args) { const plannerCommand = arg([...args], '--planner-command'); const modelRuntime = arg([...args], '--codex', 'codex'); return plannerCommand ? new CommandObjectivePlanner(plannerCommand) : new AgentObjectivePlanner(modelRuntime); }
|
|
484
|
-
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, { 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) =>
|
|
465
|
+
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); } }); }
|
|
485
466
|
export function dashboardJobAction(store, reconciler, action) {
|
|
486
467
|
return async (jobId) => {
|
|
487
468
|
const job = await store.load(jobId);
|
|
@@ -491,7 +472,6 @@ export function dashboardJobAction(store, reconciler, action) {
|
|
|
491
472
|
}
|
|
492
473
|
else if (action === 'cancel') {
|
|
493
474
|
await applyJobCancelAction(store, job);
|
|
494
|
-
await store.closePullRequest?.();
|
|
495
475
|
return await store.load(job.id);
|
|
496
476
|
}
|
|
497
477
|
return await reconciler.wake(job.id);
|
|
@@ -505,17 +485,34 @@ export async function createService(args) {
|
|
|
505
485
|
const usageWindowDays = Number.isInteger(requestedUsageDays) && requestedUsageDays > 0 ? requestedUsageDays : 1;
|
|
506
486
|
const usageRegistry = codexAndZaiUsageRegistry(undefined, usageWindowDays);
|
|
507
487
|
const reconciler = runtime(loaded.store, args, bus, usageRegistry);
|
|
508
|
-
const repo = arg(args, '--repo');
|
|
488
|
+
const repo = noGitHubMode(args) ? repository(args) : arg(args, '--repo');
|
|
509
489
|
let overviewProvider;
|
|
510
490
|
if (repo) {
|
|
511
|
-
|
|
512
|
-
|
|
491
|
+
overviewProvider = async () => {
|
|
492
|
+
const snapshots = await discoverLocalRelayStoreSnapshots(join(homedir(), '.agents-relay', 'stores'));
|
|
493
|
+
return dashboardManagedJobs(snapshots.flatMap(snapshot => snapshot.jobs
|
|
494
|
+
.filter(job => job.repository === repo)
|
|
495
|
+
.map(job => ({
|
|
496
|
+
job, githubState: snapshot.facts?.state ?? 'UNKNOWN', draft: false,
|
|
497
|
+
mergedAt: null, closedAt: null, publication: snapshot.publications[job.id]?.latest ?? null, publicationFailure: snapshot.publications[job.id]?.failure ?? null,
|
|
498
|
+
}))));
|
|
499
|
+
};
|
|
513
500
|
}
|
|
514
501
|
const webhookSecret = arg(args, '--github-webhook-secret') || process.env.AGENTS_RELAY_GITHUB_WEBHOOK_SECRET || '';
|
|
515
|
-
const webhook =
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
502
|
+
const webhook = !noGitHubMode(args) && repo && webhookSecret ? {
|
|
503
|
+
secret: webhookSecret,
|
|
504
|
+
repositories: new Set([repo]),
|
|
505
|
+
pullRequest: loaded.job.externalPrNumber ?? loaded.job.prNumber,
|
|
506
|
+
jobId: loaded.job.id,
|
|
507
|
+
wake: async (event) => {
|
|
508
|
+
if (event.pullRequestFacts && loaded.store.applyPullRequestFacts) {
|
|
509
|
+
await loaded.store.applyPullRequestFacts({
|
|
510
|
+
...event.pullRequestFacts, observedAt: event.pullRequestFacts.observedAt ?? new Date().toISOString(),
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
await handleServicePublication(await loaded.store.load(loaded.job.id), event, bus);
|
|
514
|
+
},
|
|
515
|
+
} : undefined;
|
|
519
516
|
const dashboardServer = serveDashboard(loaded.store, Number(arg(args, '--port', String(SERVICE_DEFAULTS.port))), bus, Number(arg(args, '--refresh-ms', String(SERVICE_DEFAULTS.dashboardRefreshMs))), loaded.job.id, usageRegistry, overviewProvider, webhook, undefined, undefined, undefined, {
|
|
520
517
|
start: dashboardJobAction(loaded.store, reconciler, 'start'),
|
|
521
518
|
reconcile: dashboardJobAction(loaded.store, reconciler, 'reconcile'),
|
|
@@ -529,6 +526,7 @@ export async function createService(args) {
|
|
|
529
526
|
const stopWatcher = await reconciler.watch(loaded.job.id);
|
|
530
527
|
const watchdog = setInterval(() => void reconciler.reconcile(loaded.job.id), Number(arg(args, '--interval', String(SERVICE_DEFAULTS.watchdogMs))));
|
|
531
528
|
watchdog.unref();
|
|
529
|
+
const stopExternalActions = startServiceExternalActionFlusher(loaded.store, reconciler, loaded.job.id, Number(arg(args, '--projection-ms', String(SERVICE_DEFAULTS.projectionFlushMs))));
|
|
532
530
|
let stopping = null;
|
|
533
531
|
const service = {
|
|
534
532
|
store: loaded.store,
|
|
@@ -540,6 +538,7 @@ export async function createService(args) {
|
|
|
540
538
|
if (stopping)
|
|
541
539
|
return stopping;
|
|
542
540
|
clearInterval(watchdog);
|
|
541
|
+
stopExternalActions();
|
|
543
542
|
service.watchdogCleared = true;
|
|
544
543
|
const serverClosed = new Promise((resolve, reject) => { dashboardServer.close(error => error ? reject(error) : resolve()); });
|
|
545
544
|
dashboardServer.closeAllConnections();
|
|
@@ -576,7 +575,7 @@ export function isEntrypoint(moduleUrl) {
|
|
|
576
575
|
}
|
|
577
576
|
}
|
|
578
577
|
async function main() {
|
|
579
|
-
const [command, ...args] = process.argv.slice(2);
|
|
578
|
+
const [command, ...args] = process.argv.slice(2).filter(value => value !== '--no-github');
|
|
580
579
|
if (!command || command === '--help' || command === '-h') {
|
|
581
580
|
console.log(helpText());
|
|
582
581
|
return;
|
|
@@ -620,19 +619,18 @@ async function main() {
|
|
|
620
619
|
return;
|
|
621
620
|
}
|
|
622
621
|
if (command === 'init') {
|
|
622
|
+
const file = arg(args, '--file');
|
|
623
623
|
const now = new Date().toISOString();
|
|
624
624
|
const initialSchedule = scheduleFromArgs(args);
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
file = arg(args, '--file');
|
|
632
|
-
if (!file)
|
|
633
|
-
throw new Error('init requires --repo/--pr or explicit --file demo mode');
|
|
634
|
-
store = new InMemoryStore(job);
|
|
625
|
+
const id = arg(args, '--id', randomUUID());
|
|
626
|
+
if (!file) {
|
|
627
|
+
const repo = repository(args);
|
|
628
|
+
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 } });
|
|
629
|
+
console.log(JSON.stringify(result, null, 2));
|
|
630
|
+
return;
|
|
635
631
|
}
|
|
632
|
+
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) });
|
|
633
|
+
const store = new InMemoryStore(job);
|
|
636
634
|
await store.saveJob(job);
|
|
637
635
|
await saveLocal(file, job);
|
|
638
636
|
console.log(`Initialized ${job.id}`);
|
|
@@ -655,6 +653,46 @@ async function main() {
|
|
|
655
653
|
console.log(JSON.stringify(await response.json(), null, 2));
|
|
656
654
|
return;
|
|
657
655
|
}
|
|
656
|
+
if (!arg(args, '--file') && ['plan', 'reconcile', 'record', 'agent-register', 'agent-discover'].includes(command)) {
|
|
657
|
+
const jobId = arg(args, '--id');
|
|
658
|
+
if (!jobId)
|
|
659
|
+
throw new Error(`${command} requires --repo and --id`);
|
|
660
|
+
if (command === 'plan') {
|
|
661
|
+
const guidance = hasArg(args, '--prompt') || hasArg(args, '--prompt-file') ? await textOption(args, '--prompt', '--prompt-file') : undefined;
|
|
662
|
+
console.log(JSON.stringify(await relayControlCommand(args, { command: 'job.plan', jobId, ...(guidance?.trim() ? { guidance: guidance.trim() } : {}) }), null, 2));
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (command === 'reconcile') {
|
|
666
|
+
console.log(JSON.stringify(await relayControlCommand(args, { command: 'job.reconcile', jobId }), null, 2));
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (command === 'record') {
|
|
670
|
+
const now = new Date().toISOString();
|
|
671
|
+
const id = arg(args, '--task-id', randomUUID());
|
|
672
|
+
const summary = arg(args, '--summary');
|
|
673
|
+
if (!summary)
|
|
674
|
+
throw new Error('record requires --summary');
|
|
675
|
+
const commit = arg(args, '--commit');
|
|
676
|
+
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 };
|
|
677
|
+
const updated = await relayControlCommand(args, { command: 'record', jobId, task });
|
|
678
|
+
console.log(JSON.stringify(updated.tasks.find(item => item.id === id), null, 2));
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
if (command === 'agent-register') {
|
|
682
|
+
const now = new Date().toISOString();
|
|
683
|
+
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)
|
|
684
|
+
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 };
|
|
685
|
+
if (!agent.name || !agent.responsibility || !agent.role || !agent.endpoint.target)
|
|
686
|
+
throw new Error('--name, --responsibility, --role, and --endpoint are required');
|
|
687
|
+
console.log(JSON.stringify(await relayControlCommand(args, { command: 'agent.register', jobId, agent }), null, 2));
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (command === 'agent-discover') {
|
|
691
|
+
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') };
|
|
692
|
+
console.log(JSON.stringify(await relayControlCommand(args, { command: 'agent.discover', jobId, requirements }), null, 2));
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
658
696
|
const loaded = await storeFor(args);
|
|
659
697
|
const { store, job, localFile } = loaded;
|
|
660
698
|
if (command === 'plan') {
|
|
@@ -709,7 +747,7 @@ async function main() {
|
|
|
709
747
|
}
|
|
710
748
|
if (command === 'reconcile') {
|
|
711
749
|
await withEventBus(args, async (bus) => {
|
|
712
|
-
if (!localFile) {
|
|
750
|
+
if (!localFile && !noGitHubMode(args)) {
|
|
713
751
|
await requestManagedReconciliation(bus, job);
|
|
714
752
|
console.log(JSON.stringify(await store.load(job.id), null, 2));
|
|
715
753
|
return;
|