agents-relay 1.0.20 → 1.0.22

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/dist/cli.js CHANGED
@@ -424,7 +424,8 @@ async function runJobCommand(action, args) {
424
424
  console.log(JSON.stringify({ repository: repo, pr: result.pr.number, job: result.job.id, executionMode: result.job.executionMode, state: result.job.state, task: start ? arg(args, '--task-id', 'initial') : undefined }, null, 2));
425
425
  }
426
426
  export function runtimePlanner(args) { const plannerCommand = arg([...args], '--planner-command'); const modelRuntime = arg([...args], '--codex', 'codex'); return plannerCommand ? new CommandObjectivePlanner(plannerCommand) : new AgentObjectivePlanner(modelRuntime); }
427
- export function runtime(store, args, bus) { 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, eventBus: bus, emit, chatgptTabClosePolicy: policy }); }
427
+ export function runtimeReviewer() { return { adapter: 'chatgpt', routing: { provider: 'chatgpt', model: 'account-defaults', decidedBy: 'fixed-chatgpt-reviewer', decidedAt: new Date().toISOString() }, startAckTimeoutMs: 120000 }; }
428
+ export function runtime(store, args, bus) { 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, reviewer: runtimeReviewer(), eventBus: bus, emit, chatgptTabClosePolicy: policy }); }
428
429
  export async function createService(args) {
429
430
  const loaded = await storeFor(args);
430
431
  const upstream = eventBus(args);
@@ -26,18 +26,32 @@ ${task.input}
26
26
  [Output contract]
27
27
  ${output}
28
28
 
29
- [Execution event contract]
29
+ [Execution event contract — mandatory]
30
30
  Use the canonical Neo events-bus with exactly Job ${job.id} and Task ${task.id}.
31
31
  Follow the events-bus skill/protocol. For sandboxed Codex or hosted ChatGPT,
32
32
  publish through the federated MCP tool events__publish; do not open NATS directly.
33
33
  For a direct non-sandbox local worker, use NEO_EVENTS_EMIT when the caller provides it.
34
34
 
35
+ As your FIRST execution action, before reading files, editing code, running commands, or doing other work,
36
+ you MUST publish exactly one canonical task.started event for this exact Job/Task.
37
+
35
38
  You MAY publish progress events while working.
36
- Before stopping, you MUST publish exactly one terminal event:
39
+ Before returning or allowing the worker process to exit, you MUST publish exactly one canonical terminal event:
37
40
  - task.completed only after the declared output is durable;
38
- - task.failed when execution cannot complete;
39
- - task.blocked when human/external action is required.
40
- The ChatGPT/Codex conversation or process exit is never the task result.`;
41
+ - task.failed for implementation, tool, command, test, patch, commit, push, runtime, or other execution failure that prevents completion;
42
+ - task.blocked only when genuine human/external action is required;
43
+ - task.cancelled only when execution was cancelled.
44
+
45
+ If any execution step fails and you cannot continue, publish task.failed with the concrete reason BEFORE exiting.
46
+ Never exit after task.started without publishing exactly one terminal task event.
47
+ Do not publish duplicate terminal task events.
48
+ The worker owns task.started and task terminal events. Relay-owned task.process.launched, task.process.exited,
49
+ and task.process.async_exited are process lifecycle only and NEVER replace the worker task lifecycle.
50
+ The ChatGPT/Codex conversation or process exit is never the task result.
51
+
52
+ [Final event gate]
53
+ Before your final return, verify that a terminal event for this exact Job/Task was published.
54
+ If no terminal event has been published, publish task.failed before exiting.`;
41
55
  }
42
56
  export class Reconciler {
43
57
  store;
@@ -183,7 +197,7 @@ export class Reconciler {
183
197
  return;
184
198
  const id = `final-review-${headSha.slice(0, 12)}`;
185
199
  const now = new Date().toISOString();
186
- const task = { jobId: job.id, id, kind: 'work', priority: job.priority, projectName: job.title, agentName: 'reviewer', parentTaskId: null, dependencies: job.tasks.filter(item => item.agentName !== 'reviewer').map(item => item.id), capabilities: ['review'], adapter: this.options.reviewer?.adapter ?? 'codex', input: `Review PR #${job.prNumber} at head SHA ${headSha}. Objective: ${job.objective ?? job.description ?? job.title}. First verify the deterministic pre-review gates have passed, then inspect the completed work. Emit review.started, then exactly one of review.approved, review.changes_required, or review.blocked with data.review.headSha set to ${headSha}. Do not modify code and do not merge.`, output: { kind: 'task_pr' }, routing: this.options.reviewer?.routing, continuation: null, continuationDeliveredAt: null, reviewHeadSha: headSha, state: 'QUEUED', attempt: 0, maxAttempts: this.options.reviewer?.maxAttempts ?? 2, leaseOwner: null, leaseExpiresAt: null, executionId: null, threadId: null, result: null, plannerResult: null, error: null, timeoutMs: this.options.reviewer?.timeoutMs ?? 300000, createdAt: now, updatedAt: now };
200
+ const task = { jobId: job.id, id, kind: 'work', priority: job.priority, projectName: job.title, agentName: 'reviewer', parentTaskId: null, dependencies: job.tasks.filter(item => item.agentName !== 'reviewer').map(item => item.id), capabilities: ['review'], adapter: this.options.reviewer?.adapter ?? 'codex', input: `Review PR #${job.prNumber} at head SHA ${headSha}. Objective: ${job.objective ?? job.description ?? job.title}. First publish task.started as the worker start acknowledgement with data.worker_owned=true, then verify the deterministic pre-review gates have passed and inspect the completed work. Emit review.started, then exactly one of review.approved, review.changes_required, or review.blocked with data.review.headSha set to ${headSha}. Do not modify code and do not merge.`, output: { kind: 'task_pr' }, routing: this.options.reviewer?.routing, continuation: null, continuationDeliveredAt: null, reviewHeadSha: headSha, state: 'QUEUED', attempt: 0, maxAttempts: this.options.reviewer?.maxAttempts ?? 2, leaseOwner: null, leaseExpiresAt: null, executionId: null, threadId: null, result: null, plannerResult: null, error: null, timeoutMs: this.options.reviewer?.timeoutMs ?? 300000, createdAt: now, updatedAt: now };
187
201
  await this.store.appendTask(task);
188
202
  job.review = { outcome: 'pending', headSha, taskId: id, evidence: null, updatedAt: now };
189
203
  job.state = 'RUNNING';
@@ -306,7 +320,7 @@ export class Reconciler {
306
320
  const controller = new AbortController();
307
321
  let execution;
308
322
  const workerTask = { ...task, input: workerInput };
309
- const startAckSetup = task.adapter === 'chatgpt' ? await this.startAck(job.id, task.id, Date.now()) : null;
323
+ const startAckSetup = task.adapter === 'chatgpt' ? await this.startAck(job.id, task.id, Date.now(), task.agentName === 'reviewer' ? this.options.reviewer?.startAckTimeoutMs : undefined) : null;
310
324
  const closePolicy = task.chatgptTabClosePolicy ?? this.options.chatgptTabClosePolicy ?? adapter.tabClosePolicy ?? 'after-start';
311
325
  const terminalWait = task.adapter === 'chatgpt' && closePolicy === 'after-terminal'
312
326
  ? this.terminalWait(task.id, durableExecutionId, startedAt - 1)
@@ -338,7 +352,7 @@ export class Reconciler {
338
352
  ? this.failLaunch(job.id, task.id, durableExecutionId, error.message, 'delivery')
339
353
  : this.runtimeSettled(job.id, task.id, durableExecutionId, null, error instanceof Error ? error.message : String(error))), job.id, task.id, 'worker runtime completion');
340
354
  }
341
- async startAck(jobId, taskId, launchBarrier) {
355
+ async startAck(jobId, taskId, launchBarrier, timeoutMs = this.options.startAckTimeoutMs ?? 60000) {
342
356
  if (!this.options.eventBus)
343
357
  throw new Error('Worker start acknowledgement requires an event bus');
344
358
  let resolveAck;
@@ -367,7 +381,7 @@ export class Reconciler {
367
381
  settled = true;
368
382
  void unsubscribe();
369
383
  rejectAck(new StartAckTimeout(`Task ${taskId} worker_start_timeout: no worker task.started start-ack`));
370
- }, this.options.startAckTimeoutMs ?? 60000);
384
+ }, timeoutMs);
371
385
  let stopped = false;
372
386
  const stop = async () => { if (stopped)
373
387
  return; stopped = true; if (timer)
package/dist/relayd.js CHANGED
@@ -11,7 +11,7 @@ import { RepositoryWorkerPool, aggregateManagedGitHubJobs } from './pool.js';
11
11
  import { Reconciler } from './reconciler.js';
12
12
  import { dashboardManagedJobs, GitHubStore, InMemoryStore, loadManagedGitHubPullRequestGraphql } from './store.js';
13
13
  import { githubAuthContext } from './github-auth.js';
14
- import { isEntrypoint, runtimePlanner, startService, SERVICE_DEFAULTS } from './cli.js';
14
+ import { isEntrypoint, runtimePlanner, runtimeReviewer, startService, SERVICE_DEFAULTS } from './cli.js';
15
15
  import { codexAndZaiUsageRegistry } from './usage.js';
16
16
  import { defaultWorkspaceRoot, discoverWorkspaceRepositories } from './workspace.js';
17
17
  export const daemonHelp = `Agents Relay daemon runs the dashboard and worker pool.
@@ -64,7 +64,7 @@ export async function runDaemon(argv) {
64
64
  const trusted = auth.trustedAuthors;
65
65
  const concurrency = Number(value(argv, '--concurrency', '4'));
66
66
  const bus = value(argv, '--events') === 'nats' ? new NatsEventBus(value(argv, '--nats-url', 'nats://127.0.0.1:4222'), value(argv, '--subject-prefix', 'neo.events.job')) : undefined;
67
- const makeReconciler = (store, maxConcurrent) => new Reconciler(store, { owner: `relayd-${process.pid}`, maxConcurrent, leaseMs: Number(value(argv, '--lease-ms', '300000')), adapters: [new CodexAdapter(value(argv, '--codex', 'codex')), new ChatGptAdapter()], continuations: [new CodexThreadContinuation(value(argv, '--codex', 'codex')), new CommandContinuation(), new WebhookContinuation()], planner: runtimePlanner(argv), eventBus: bus });
67
+ const makeReconciler = (store, maxConcurrent) => new Reconciler(store, { owner: `relayd-${process.pid}`, maxConcurrent, leaseMs: Number(value(argv, '--lease-ms', '300000')), adapters: [new CodexAdapter(value(argv, '--codex', 'codex')), new ChatGptAdapter()], continuations: [new CodexThreadContinuation(value(argv, '--codex', 'codex')), new CommandContinuation(), new WebhookContinuation()], planner: runtimePlanner(argv), eventBus: bus, reviewer: runtimeReviewer() });
68
68
  const repositories = normalized.repository
69
69
  ? [normalized.repository]
70
70
  : (await discoverWorkspaceRepositories(normalized.workspaceRoot ?? defaultWorkspaceRoot())).map(item => item.repository);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "type": "module",
6
6
  "bin": {