agents-relay 1.0.3 → 1.0.4
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 +8 -0
- package/dist/cli.js +89 -2
- package/package.json +1 -1
- package/skills/agents-relay/SKILL.md +2 -0
package/README.md
CHANGED
|
@@ -17,6 +17,14 @@ npx agents-relay reconcile --repo OWNER/REPO --pr 12 --id job-1
|
|
|
17
17
|
npx agents-relay status --repo OWNER/REPO --pr 12 --id job-1
|
|
18
18
|
~~~
|
|
19
19
|
|
|
20
|
+
Mutable queued/retryable task fields can be changed in place without replacing the durable task identity. `task update` rejects `RUNNING`, `SUCCEEDED`, and `CANCELLED` tasks so active execution and terminal history cannot be rewritten. For example:
|
|
21
|
+
|
|
22
|
+
~~~sh
|
|
23
|
+
npx agents-relay task update --repo OWNER/REPO --pr 12 --id job-1 --task-id child \
|
|
24
|
+
--adapter chatgpt --provider chatgpt --model gpt-5.6-sol --reasoning high \
|
|
25
|
+
--capabilities model,chatgpt,browser-harness
|
|
26
|
+
~~~
|
|
27
|
+
|
|
20
28
|
Jobs default to `fixed`, preserving the original submit-and-run behavior. Use
|
|
21
29
|
`--mode autonomous` with `init` or `job create` to make reconciliation own
|
|
22
30
|
planner progress. The durable status JSON and dashboard show the mode. An
|
package/dist/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ import { codexAndZaiUsageRegistry } from './usage.js';
|
|
|
15
15
|
import { discoverAgents } from './registry.js';
|
|
16
16
|
import { AgentObjectivePlanner, CommandObjectivePlanner } from './planner.js';
|
|
17
17
|
import { extendRetryBudget } from './scheduler.js';
|
|
18
|
-
const usage = 'agents-relay <job|init|submit|record|agent-register|agent-discover|status|reconcile|retry|cancel|serve> [options]';
|
|
18
|
+
const usage = 'agents-relay <job|task|init|submit|record|agent-register|agent-discover|status|reconcile|retry|cancel|serve> [options]';
|
|
19
19
|
const help = {
|
|
20
20
|
root: `Agents Relay coordinates durable asynchronous agent jobs through GitHub pull requests.
|
|
21
21
|
|
|
@@ -24,6 +24,7 @@ Usage:
|
|
|
24
24
|
|
|
25
25
|
Commands:
|
|
26
26
|
job create|adopt|repair Create or repair a durable GitHub-backed job
|
|
27
|
+
task update Update mutable durable task fields in place
|
|
27
28
|
init Create a local demo job or initialize a GitHub job
|
|
28
29
|
submit Queue a task for a managed job
|
|
29
30
|
record Backfill a completed task into durable history
|
|
@@ -76,6 +77,21 @@ Required: --repo OWNER/REPO, --pr NUMBER, and --id JOB_ID.
|
|
|
76
77
|
|
|
77
78
|
Example:
|
|
78
79
|
npx agents-relay job repair --repo OWNER/REPO --pr 12 --id job-12`,
|
|
80
|
+
task: `Manage a durable task.
|
|
81
|
+
|
|
82
|
+
Usage:
|
|
83
|
+
npx agents-relay task update [options]
|
|
84
|
+
|
|
85
|
+
Actions:
|
|
86
|
+
update Update mutable task fields without changing task identity/history`,
|
|
87
|
+
'task update': `Update mutable durable task fields in place.
|
|
88
|
+
|
|
89
|
+
Required: --task-id ID and the normal job storage options.
|
|
90
|
+
Mutable while not RUNNING/SUCCEEDED/CANCELLED: --adapter, --priority, --input, --project, --agent, --parent, --deps, --capabilities, --timeout, --max-attempts, --provider, --model, --profile, --reasoning, --cwd, --chatgpt-project.
|
|
91
|
+
Use --clear-routing to remove routing metadata. A model update requires --model; provider defaults to the existing provider or openai.
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
npx agents-relay task update --repo OWNER/REPO --pr 12 --id job-1 --task-id build --adapter chatgpt --provider chatgpt --model gpt-5.6-sol --reasoning high`,
|
|
79
95
|
submit: `Queue a task for a managed job.
|
|
80
96
|
|
|
81
97
|
Required: --repo OWNER/REPO, --pr NUMBER, --id JOB_ID, and --input TEXT.
|
|
@@ -145,6 +161,8 @@ Example:
|
|
|
145
161
|
export function helpText(command, action) {
|
|
146
162
|
if (command === 'job')
|
|
147
163
|
return action ? help[`job ${action}`] ?? help.job : help.job;
|
|
164
|
+
if (command === 'task')
|
|
165
|
+
return action ? help[`task ${action}`] ?? help.task : help.task;
|
|
148
166
|
return command ? help[command] ?? help.root : help.root;
|
|
149
167
|
}
|
|
150
168
|
export const SERVICE_DEFAULTS = { port: 8787, watchdogMs: 300000, webhookWatchdogMs: 1800000, dashboardRefreshMs: 300000 };
|
|
@@ -172,6 +190,10 @@ class ServiceEventBus {
|
|
|
172
190
|
}
|
|
173
191
|
}
|
|
174
192
|
function arg(args, name, fallback = '') { const index = args.indexOf(name); return index >= 0 ? args[index + 1] ?? fallback : fallback; }
|
|
193
|
+
function hasArg(args, name) { return args.includes(name); }
|
|
194
|
+
function integerArg(args, name, fallback) { if (!hasArg(args, name))
|
|
195
|
+
return fallback; const value = Number(arg(args, name)); if (!Number.isInteger(value) || value < 0)
|
|
196
|
+
throw new Error(`${name} must be a non-negative integer`); return value; }
|
|
175
197
|
function repository(args) { const value = arg(args, '--repo'); if (!value)
|
|
176
198
|
throw new Error('--repo is required for GitHub-backed operation'); return value; }
|
|
177
199
|
function pullRequest(args) { const value = Number(arg(args, '--pr')); if (!Number.isInteger(value) || value <= 0)
|
|
@@ -233,6 +255,66 @@ export async function ensureManagedGitHubJob(client, repositoryName, trustedAuth
|
|
|
233
255
|
await store.saveJob(job);
|
|
234
256
|
return { job: await store.load(options.id), pr };
|
|
235
257
|
}
|
|
258
|
+
async function runTaskCommand(action, args) {
|
|
259
|
+
if (action !== 'update')
|
|
260
|
+
throw new Error(usage);
|
|
261
|
+
const { store, job, localFile } = await storeFor(args);
|
|
262
|
+
const id = arg(args, '--task-id');
|
|
263
|
+
if (!id)
|
|
264
|
+
throw new Error('task update requires --task-id');
|
|
265
|
+
const task = job.tasks.find(item => item.id === id);
|
|
266
|
+
if (!task)
|
|
267
|
+
throw new Error(`Task ${id} not found`);
|
|
268
|
+
if (['RUNNING', 'SUCCEEDED', 'CANCELLED'].includes(task.state))
|
|
269
|
+
throw new Error(`Task ${id} is ${task.state}; durable execution fields are immutable in this state`);
|
|
270
|
+
if (hasArg(args, '--adapter')) {
|
|
271
|
+
const adapter = arg(args, '--adapter');
|
|
272
|
+
if (!['shell', 'codex', 'chatgpt', 'orchestrator'].includes(adapter))
|
|
273
|
+
throw new Error('--adapter must be shell, codex, chatgpt, or orchestrator');
|
|
274
|
+
task.adapter = adapter;
|
|
275
|
+
}
|
|
276
|
+
if (hasArg(args, '--priority'))
|
|
277
|
+
task.priority = priority(args, task.priority ?? job.priority ?? 'P2');
|
|
278
|
+
if (hasArg(args, '--input'))
|
|
279
|
+
task.input = arg(args, '--input');
|
|
280
|
+
if (hasArg(args, '--project'))
|
|
281
|
+
task.projectName = arg(args, '--project') || undefined;
|
|
282
|
+
if (hasArg(args, '--agent'))
|
|
283
|
+
task.agentName = arg(args, '--agent') || undefined;
|
|
284
|
+
if (hasArg(args, '--parent'))
|
|
285
|
+
task.parentTaskId = arg(args, '--parent') || null;
|
|
286
|
+
if (hasArg(args, '--deps'))
|
|
287
|
+
task.dependencies = arg(args, '--deps').split(',').filter(Boolean);
|
|
288
|
+
if (hasArg(args, '--capabilities'))
|
|
289
|
+
task.capabilities = arg(args, '--capabilities').split(',').filter(Boolean);
|
|
290
|
+
task.timeoutMs = integerArg(args, '--timeout', task.timeoutMs);
|
|
291
|
+
task.maxAttempts = integerArg(args, '--max-attempts', task.maxAttempts);
|
|
292
|
+
if (hasArg(args, '--max-attempts') && task.maxAttempts < task.attempt)
|
|
293
|
+
throw new Error('--max-attempts cannot be less than the current attempt count');
|
|
294
|
+
const routingFlags = ['--provider', '--model', '--profile', '--reasoning', '--cwd', '--chatgpt-project'];
|
|
295
|
+
if (args.includes('--clear-routing'))
|
|
296
|
+
task.routing = undefined;
|
|
297
|
+
else if (routingFlags.some(flag => hasArg(args, flag))) {
|
|
298
|
+
const current = task.routing;
|
|
299
|
+
const model = hasArg(args, '--model') ? arg(args, '--model') : current?.model;
|
|
300
|
+
if (!model)
|
|
301
|
+
throw new Error('task routing update requires --model when no existing routing is present');
|
|
302
|
+
task.routing = {
|
|
303
|
+
provider: hasArg(args, '--provider') ? arg(args, '--provider') : current?.provider ?? 'openai',
|
|
304
|
+
model,
|
|
305
|
+
profile: hasArg(args, '--profile') ? arg(args, '--profile') || undefined : current?.profile,
|
|
306
|
+
reasoning: hasArg(args, '--reasoning') ? arg(args, '--reasoning') || undefined : current?.reasoning,
|
|
307
|
+
cwd: hasArg(args, '--cwd') ? arg(args, '--cwd') || undefined : current?.cwd,
|
|
308
|
+
projectId: hasArg(args, '--chatgpt-project') ? arg(args, '--chatgpt-project') || undefined : current?.projectId,
|
|
309
|
+
decidedBy: 'cli:update',
|
|
310
|
+
decidedAt: new Date().toISOString()
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
task.updatedAt = new Date().toISOString();
|
|
314
|
+
await store.saveTask(task);
|
|
315
|
+
await saveLocal(localFile, job);
|
|
316
|
+
console.log(JSON.stringify(task, null, 2));
|
|
317
|
+
}
|
|
236
318
|
async function runJobCommand(action, args) {
|
|
237
319
|
if (!['create', 'adopt', 'repair'].includes(action))
|
|
238
320
|
throw new Error(usage);
|
|
@@ -342,7 +424,7 @@ async function main() {
|
|
|
342
424
|
return;
|
|
343
425
|
}
|
|
344
426
|
if (args.includes('--help') || args.includes('-h')) {
|
|
345
|
-
console.log(helpText(command, command === 'job' ? args[0] : undefined));
|
|
427
|
+
console.log(helpText(command, command === 'job' || command === 'task' ? args[0] : undefined));
|
|
346
428
|
return;
|
|
347
429
|
}
|
|
348
430
|
if (command === 'job') {
|
|
@@ -350,6 +432,11 @@ async function main() {
|
|
|
350
432
|
await runJobCommand(action ?? '', jobArgs);
|
|
351
433
|
return;
|
|
352
434
|
}
|
|
435
|
+
if (command === 'task') {
|
|
436
|
+
const [action, ...taskArgs] = args;
|
|
437
|
+
await runTaskCommand(action ?? '', taskArgs);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
353
440
|
if (command === 'init') {
|
|
354
441
|
const now = new Date().toISOString();
|
|
355
442
|
const job = { id: arg(args, '--id', randomUUID()), title: arg(args, '--title', 'Agents Relay job'), objective: arg(args, '--objective') || undefined, priority: priority(args), executionMode: executionMode(args), prNumber: Number(arg(args, '--pr', '0')), repository: arg(args, '--repo'), state: 'OPEN', continuation: continuation(args), createdAt: now, updatedAt: now, tasks: [] };
|
package/package.json
CHANGED
|
@@ -62,6 +62,8 @@ Use --file PATH only for explicit local demo/test mode. Optional --events nats e
|
|
|
62
62
|
|
|
63
63
|
Use task-level continuation when the sender needs to resume, otherwise the job continuation. Continuation delivery is deduplicated by the durable delivery timestamp. Treat BLOCKED as an approval/manual-release state until an explicit retry/release changes it. Cancellation, timeout, and lease expiry are durable state transitions; do not claim success from a worker process exit alone.
|
|
64
64
|
|
|
65
|
+
Use `npx agents-relay task update` to change safe durable fields of a `QUEUED`, `READY`, `WAITING`, `BLOCKED`, or `FAILED` task without changing its task ID or prior attempt/error history. This is the preferred way to reroute future execution to a different adapter/provider/model. The CLI rejects `RUNNING`, `SUCCEEDED`, and `CANCELLED` task updates so active execution semantics and terminal audit history are not rewritten.
|
|
66
|
+
|
|
65
67
|
For a ChatGPT worker, use the chatgpt adapter with capabilities model,chatgpt,browser-harness. The runtime launches the installed `chatgpt-browser-worker` agent, stores its observed `thread_id` as the durable task threadId, and reuses that ID on retry. Pass --chatgpt-project when the worker must run inside one exact ChatGPT Project. Set `AGENTS_RELAY_CHATGPT_BROWSER_WORKER_AGENT` to an explicit agent definition path when needed; otherwise standard skill roots are searched. Never place credentials or private prompts in job/task markers.
|
|
66
68
|
|
|
67
69
|
Managed Codex and ChatGPT worker prompts are enriched at launch with the PR URL and durable job/task/parent/project identity; do not duplicate that context manually in task input. Keep one ChatGPT conversation per logical task. Retries reuse the same task conversation, but sibling tasks use separate conversations even when their adapter is the same.
|