agents-relay 1.0.111 → 1.0.113

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-help.js CHANGED
@@ -44,7 +44,7 @@ Example:
44
44
 
45
45
  Required: --repo OWNER/REPO, --id JOB_ID, and either --head BRANCH or --pr NUMBER.
46
46
  With --head, reuse/create the PR for that head. With --pr, bind an existing unmanaged PR.
47
- Common: --base BRANCH (default: main), --title TEXT, --objective-file PATH|- (preferred), --objective MARKDOWN (short/simple only), --priority P0|P1|P2|P3, --mode fixed|autonomous, --no-auto-merge, --no-review. --objective and --objective-file are mutually exclusive. For a new --head Job, one is required and must contain enough standalone context to complete the Job without the originating chat/thread. Objective Markdown is also the default PR body; --body is an explicit override.
47
+ Common: --base BRANCH (default: main), --title TEXT, --objective-file PATH|- (preferred), --objective MARKDOWN (short/simple only), --project-path RELATIVE_PATH, --priority P0|P1|P2|P3, --mode fixed|autonomous, --no-auto-merge, --no-review. --objective and --objective-file are mutually exclusive. For a new --head Job, one is required and must contain enough standalone context to complete the Job without the originating chat/thread. Objective Markdown is also the default PR body; --body is an explicit override.
48
48
  Job creation never creates executable tasks. The orchestrator decides the initial task batch separately. Re-running create against an already healthy Job is idempotent and does not mutate its metadata.
49
49
 
50
50
  Example:
@@ -52,7 +52,7 @@ Example:
52
52
  'job update': `Partially update mutable metadata on a healthy existing Job.
53
53
 
54
54
  Required: --repo OWNER/REPO, --pr NUMBER, and --id JOB_ID.
55
- Mutable: --title, --objective-file PATH|- (preferred) or --objective MARKDOWN, --priority, --mode fixed|autonomous, --schedule, --auto-merge|--no-auto-merge, --review|--no-review, and continuation options. Updating the objective also updates the PR body.
55
+ Mutable: --title, --objective-file PATH|- (preferred) or --objective MARKDOWN, --project-path RELATIVE_PATH, --priority, --mode fixed|autonomous, --schedule, --auto-merge|--no-auto-merge, --review|--no-review, and continuation options. Updating the objective also updates the PR body.
56
56
  Unspecified fields are preserved. Marker/binding corruption is never repaired implicitly; update fails with a precise diagnostic instead.
57
57
 
58
58
  Example:
package/dist/cli.js CHANGED
@@ -18,7 +18,7 @@ import { AgentObjectivePlanner, CommandObjectivePlanner } from './planner.js';
18
18
  import { extendRetryBudget, transitionJobEvent, transitionTaskEvent } from './scheduler.js';
19
19
  import { dueBuckets, processDueScheduledJobs, syncScheduleLabels } from './scheduled-jobs.js';
20
20
  import { resolveExecutionContext } from './execution-context.js';
21
- import { defaultWorkspaceRoot, resolveManagedProjectPath } from './workspace.js';
21
+ import { defaultWorkspaceRoot, resolveManagedProjectPath, resolveRepositoryProjectPath } from './workspace.js';
22
22
  import { runBlockerCommand } from './cli-blocker.js';
23
23
  import { helpText, usage } from './cli-help.js';
24
24
  export { helpText } from './cli-help.js';
@@ -44,6 +44,7 @@ export async function ensureManagedGitHubJob(client, repositoryName, trustedAuth
44
44
  id: options.id,
45
45
  title: options.title || pr.title || 'Agents Relay job',
46
46
  objective: options.objective || pr.body || undefined,
47
+ projectPath: options.projectPath,
47
48
  description: pr.body || undefined,
48
49
  priority: options.priority ?? 'P2',
49
50
  executionMode: options.executionMode ?? 'fixed',
@@ -179,8 +180,11 @@ async function runTaskCommand(action, args) {
179
180
  return;
180
181
  }
181
182
  if (action === 'retry' || action === 'cancel') {
182
- if (action === 'retry')
183
+ if (action === 'retry') {
183
184
  task.maxAttempts = extendRetryBudget(task);
185
+ if (task.kind === 'planner' && task.error)
186
+ task.plannerRetryFeedback = task.error;
187
+ }
184
188
  await transitionTaskEvent(task, action === 'retry' ? 'retry' : task.state === 'RUNNING' ? 'task.cancelled' : 'cancel');
185
189
  task.error = action === 'retry' ? null : 'Cancelled by user';
186
190
  task.leaseOwner = null;
@@ -323,6 +327,7 @@ async function runJobCommand(action, args) {
323
327
  id,
324
328
  title: arg(args, '--title') || undefined,
325
329
  objective: objective || undefined,
330
+ projectPath: arg(args, '--project-path') || undefined,
326
331
  priority: priority(args),
327
332
  executionMode: requestedExecutionMode(args),
328
333
  schedule: scheduleFromArgs(args),
@@ -382,6 +387,8 @@ async function runJobCommand(action, args) {
382
387
  throw new Error('Job objective cannot be empty');
383
388
  job.objective = objective;
384
389
  }
390
+ if (hasArg(args, '--project-path'))
391
+ job.projectPath = arg(args, '--project-path') || undefined;
385
392
  if (hasArg(args, '--priority'))
386
393
  job.priority = priority(args, job.priority ?? 'P2');
387
394
  if (hasArg(args, '--mode') || hasArg(args, '--execution-mode'))
@@ -414,7 +421,7 @@ async function runJobCommand(action, args) {
414
421
  }
415
422
  export function runtimePlanner(args) { const plannerCommand = arg([...args], '--planner-command'); const modelRuntime = arg([...args], '--codex', 'codex'); return plannerCommand ? new CommandObjectivePlanner(plannerCommand) : new AgentObjectivePlanner(modelRuntime); }
416
423
  export function runtimeReviewer() { return { requirements: { capabilities: ['model'] } }; }
417
- 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: new LayaCliDecisionEngine(), decisionMode: decisionMode(), reviewer: runtimeReviewer(), eventBus: bus, emit, chatgptTabClosePolicy: policy, projectPathForJob: job => resolveManagedProjectPath(job.repository, arg(args, '--project-path'), arg(args, '--workspace-root') || defaultWorkspaceRoot()) }); }
424
+ 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: new LayaCliDecisionEngine(), decisionMode: decisionMode(), reviewer: runtimeReviewer(), eventBus: bus, emit, chatgptTabClosePolicy: policy, projectPathForJob: async (job) => resolveRepositoryProjectPath(await resolveManagedProjectPath(job.repository, arg(args, '--project-path'), arg(args, '--workspace-root') || defaultWorkspaceRoot()), job.projectPath) }); }
418
425
  export function dashboardTaskAction(store, reconciler, action) {
419
426
  return async (jobId, taskId) => {
420
427
  const job = await store.load(jobId);
package/dist/planner.js CHANGED
@@ -147,7 +147,7 @@ export function plannerInput(context) {
147
147
  boundaries: agent.restrictions,
148
148
  runtime: agent.runtime,
149
149
  }));
150
- return JSON.stringify({ objective: context.objective, previous_planner_task_id: context.previousPlannerTaskId, visible_agents: visibleAgents ?? [], execution_context: context.executionContext, tasks: context.tasks.map(task => ({ id: task.id, kind: task.kind ?? 'work', parent_task_id: task.parentTaskId, state: task.state, input: task.input, result: task.result, planner_result: task.plannerResult, error: task.error })) });
150
+ return JSON.stringify({ objective: context.objective, retry_feedback: context.retryFeedback, previous_planner_task_id: context.previousPlannerTaskId, visible_agents: visibleAgents ?? [], execution_context: context.executionContext, tasks: context.tasks.map(task => ({ id: task.id, kind: task.kind ?? 'work', parent_task_id: task.parentTaskId, state: task.state, input: task.input, result: task.result, planner_result: task.plannerResult, error: task.error })) });
151
151
  }
152
152
  export class CommandObjectivePlanner {
153
153
  command;
@@ -444,7 +444,10 @@ export class Reconciler extends ReconcilerReviewBase {
444
444
  let promise;
445
445
  try {
446
446
  const executionContext = await this.resolvedContext(job, task, 'planner');
447
- promise = planner.plan({ job, tasks: job.tasks, objective: job.objective ?? job.description ?? job.title, previousPlannerTaskId: task.parentTaskId, executionContext }, controller.signal);
447
+ const context = { job, tasks: job.tasks, objective: job.objective ?? job.description ?? job.title, previousPlannerTaskId: task.parentTaskId, executionContext, retryFeedback: task.plannerRetryFeedback };
448
+ task.input = plannerInput(context);
449
+ await this.store.saveTask(task);
450
+ promise = planner.plan(context, controller.signal);
448
451
  }
449
452
  catch (error) {
450
453
  await this.finishPlanner(job.id, task.id, executionId, error instanceof Error ? error : new Error(String(error)));
@@ -465,6 +468,7 @@ export class Reconciler extends ReconcilerReviewBase {
465
468
  if (task.state === 'CANCELLED')
466
469
  return;
467
470
  if (error) {
471
+ task.plannerRetryFeedback = error.message;
468
472
  task.error = error.message;
469
473
  task.leaseOwner = null;
470
474
  task.leaseExpiresAt = null;
@@ -490,6 +494,7 @@ export class Reconciler extends ReconcilerReviewBase {
490
494
  task.plannerResult = result;
491
495
  task.result = { summary: result.assessment, data: { objective_status: result.objective_status, next_tasks: result.next_tasks } };
492
496
  task.error = null;
497
+ task.plannerRetryFeedback = undefined;
493
498
  task.leaseOwner = null;
494
499
  task.leaseExpiresAt = null;
495
500
  task.executionId = null;
@@ -508,6 +513,7 @@ export class Reconciler extends ReconcilerReviewBase {
508
513
  await this.emit(eventFor(job.id, task.id, task.parentTaskId, 'planner.children_failed', 'failed', `Planner ${task.id} child materialization failed: ${message}`, 'user'));
509
514
  }
510
515
  else {
516
+ task.plannerRetryFeedback = message;
511
517
  task.error = message;
512
518
  task.leaseOwner = null;
513
519
  task.leaseExpiresAt = null;
package/dist/relayd.js CHANGED
@@ -13,7 +13,7 @@ import { dashboardManagedJobs, GitHubStore, InMemoryStore, loadManagedGitHubPull
13
13
  import { githubAuthContext } from './github-auth.js';
14
14
  import { dashboardJobAction, dashboardTaskAction, isEntrypoint, runtimePlanner, runtimeReviewer, startService, SERVICE_DEFAULTS } from './cli.js';
15
15
  import { codexAndZaiUsageRegistry } from './usage.js';
16
- import { defaultWorkspaceRoot, discoverWorkspaceRepositories, resolveManagedProjectPath } from './workspace.js';
16
+ import { defaultWorkspaceRoot, discoverWorkspaceRepositories, resolveManagedProjectPath, resolveRepositoryProjectPath } from './workspace.js';
17
17
  import { deriveJobRuntimeStatus } from './runtime-status.js';
18
18
  import { decisionMode, LayaCliDecisionEngine } from './decision.js';
19
19
  import { JsonlDecisionAuditStore } from './decision-audit.js';
@@ -76,7 +76,7 @@ export async function runDaemon(argv) {
76
76
  const usageRegistry = codexAndZaiUsageRegistry();
77
77
  const decisionAuditStore = new JsonlDecisionAuditStore(process.env.AGENTS_RELAY_DECISION_AUDIT_PATH ?? join(homedir(), '.agents-relay', 'decision-audit.jsonl'));
78
78
  let cachedOverviews = [];
79
- 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), usageSnapshotProvider: () => usageRegistry.snapshot(), runtimeSummaryProvider: () => ({ chatgptRunning: summarizeRuntime(cachedOverviews.map(item => item.job)).workers.chatgpt }), decisionEngine: new LayaCliDecisionEngine(), decisionMode: decisionMode(), decisionAuditStore, eventBus: bus, reviewer: runtimeReviewer(), projectPathForJob: job => repositoryPaths.get(job.repository) ?? resolveManagedProjectPath(job.repository, explicitProjectPath, workspaceRoot) });
79
+ 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), usageSnapshotProvider: () => usageRegistry.snapshot(), runtimeSummaryProvider: () => ({ chatgptRunning: summarizeRuntime(cachedOverviews.map(item => item.job)).workers.chatgpt }), decisionEngine: new LayaCliDecisionEngine(), decisionMode: decisionMode(), decisionAuditStore, eventBus: bus, reviewer: runtimeReviewer(), projectPathForJob: async (job) => resolveRepositoryProjectPath(repositoryPaths.get(job.repository) ?? await resolveManagedProjectPath(job.repository, explicitProjectPath, workspaceRoot), job.projectPath) });
80
80
  if (repositories.length === 0)
81
81
  throw new Error(`No Git repositories with GitHub origins found under ${normalized.workspaceRoot ?? defaultWorkspaceRoot()}`);
82
82
  const pool = new RepositoryWorkerPool(client, repositories, trusted, concurrency, makeReconciler);
package/dist/workspace.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { mkdir, readdir, stat } from 'node:fs/promises';
2
- import { dirname, join, relative, resolve } from 'node:path';
2
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
4
  import { execFile } from 'node:child_process';
5
5
  import { promisify } from 'node:util';
@@ -75,11 +75,24 @@ export async function resolveManagedProjectPath(repository, explicitProjectPath
75
75
  return resolve(explicitProjectPath);
76
76
  return repository ? resolveWorkspaceRepositoryPath(repository, root) : resolve(cwd);
77
77
  }
78
+ export function resolveRepositoryProjectPath(repositoryRoot, projectPath) {
79
+ const root = resolve(repositoryRoot);
80
+ if (!projectPath)
81
+ return root;
82
+ if (isAbsolute(projectPath))
83
+ throw new Error('Job projectPath must be repository-relative');
84
+ const target = resolve(root, projectPath);
85
+ const rel = relative(root, target);
86
+ if (rel === '..' || rel.startsWith('..' + sep) || isAbsolute(rel))
87
+ throw new Error('Job projectPath escapes repository root');
88
+ return target;
89
+ }
78
90
  async function git(path, args) {
79
91
  return (await execFileAsync('git', ['-C', path, ...args])).stdout.trim();
80
92
  }
81
93
  export async function prepareManagedExecutionWorktree(projectPath, repository, prNumber, head, executionId) {
82
94
  const primary = await git(projectPath, ['rev-parse', '--show-toplevel']);
95
+ const projectRelative = (await git(projectPath, ['rev-parse', '--show-prefix'])).replace(/\/$/, '');
83
96
  const remote = await git(primary, ['config', '--get', 'remote.origin.url']);
84
97
  if (githubRepositoryFromRemote(remote) !== repository)
85
98
  throw new Error(`Primary checkout ${primary} does not match ${repository}`);
@@ -96,7 +109,7 @@ export async function prepareManagedExecutionWorktree(projectPath, repository, p
96
109
  const existingHead = await git(worktree, ['rev-parse', 'HEAD']);
97
110
  if (resolve(existingRoot) !== resolve(worktree) || existingHead !== head.sha)
98
111
  throw new Error(`Existing execution worktree ${worktree} is not the expected PR head`);
99
- return worktree;
112
+ return projectRelative ? join(worktree, projectRelative) : worktree;
100
113
  }
101
114
  catch (error) {
102
115
  try {
@@ -104,7 +117,7 @@ export async function prepareManagedExecutionWorktree(projectPath, repository, p
104
117
  }
105
118
  catch {
106
119
  await git(primary, ['worktree', 'add', '--detach', worktree, head.sha]);
107
- return worktree;
120
+ return projectRelative ? join(worktree, projectRelative) : worktree;
108
121
  }
109
122
  throw error;
110
123
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "1.0.111",
3
+ "version": "1.0.113",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "type": "module",
6
6
  "bin": {