agents-relay 2.0.89 → 2.0.91

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/adapters.js CHANGED
@@ -4,6 +4,7 @@ import { pathToFileURL } from 'node:url';
4
4
  import { randomUUID } from 'node:crypto';
5
5
  import { AsyncLocalStorage } from 'node:async_hooks';
6
6
  import { withTestInjection } from './e2e-injection.js';
7
+ import { taskReferencePrompt } from './task-context.js';
7
8
  import { spawnOwnedProcess, terminateOwnedProcess, ownedProcessTerminationReason } from './owned-process.js';
8
9
  export function buildCodexArgs(route, input) {
9
10
  const args = ['exec', '--json'];
@@ -372,7 +373,8 @@ export class ChatGptAdapter {
372
373
  throw new Error('browser-worker execution aborted');
373
374
  const policy = chatGptTabClosePolicy(task.chatgptTabClosePolicy, this.tabClosePolicy);
374
375
  const submit = () => (this.runner ?? browserWorkerRunner())(definition, {
375
- prompt: task.input,
376
+ prompt: task.chatgptThreadDelivery?.threadSource === 'implementation' && task.chatgptThreadDelivery.repository && task.chatgptThreadDelivery.executionId
377
+ ? taskReferencePrompt(task.chatgptThreadDelivery.repository, task.chatgptThreadDelivery.jobId, task.id, task.chatgptThreadDelivery.executionId) : task.input,
376
378
  ...(task.attachments?.length ? { files: task.attachments } : {}),
377
379
  ...(task.chatgptThreadDelivery ? { thread: task.chatgptThreadDelivery } : {}),
378
380
  tabClosePolicy: policy,
package/dist/cli-help.js CHANGED
@@ -129,6 +129,15 @@ Use --clear-routing to remove routing metadata.
129
129
 
130
130
  Example:
131
131
  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`,
132
+ 'task comment': `Append an immutable Task comment. Does not retry or change Task state.
133
+
134
+ Required: --repo OWNER/REPO --id JOB_ID --task-id TASK_ID and --message TEXT or --message-file FILE.`,
135
+ 'task comments': `List append-only Task comments.
136
+
137
+ Required: --repo OWNER/REPO --id JOB_ID --task-id TASK_ID.`,
138
+ 'task context': `Read the authoritative execution-scoped Task contract.
139
+
140
+ Required: --repo OWNER/REPO --id JOB_ID --task-id TASK_ID --execution-id EXECUTION_ID. Fails closed on stale execution.`,
132
141
  'task show': `Show one durable Task.
133
142
 
134
143
  Required: --task-id ID and the normal Job storage options.`,
package/dist/cli.js CHANGED
@@ -40,6 +40,7 @@ export { runtimeReviewer } from './reviewer-thread.js';
40
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';
41
41
  export { SERVICE_DEFAULTS, eventBus, executionMode, chatGptTabClosePolicy } from './cli-support.js';
42
42
  import { workerTaskFromArgs } from './cli-worker-task.js';
43
+ import { taskExecutionContext } from './task-context.js';
43
44
  export { ensureManagedGitHubJob } from './cli-job-create.js';
44
45
  async function runManagedTaskCommand(action, args) {
45
46
  const jobId = arg(args, '--id');
@@ -67,7 +68,7 @@ async function runManagedTaskCommand(action, args) {
67
68
  console.log(JSON.stringify(task, null, 2));
68
69
  }
69
70
  async function runTaskCommand(action, args) {
70
- if (!['create', 'update', 'show', 'list', 'retry', 'recover', 'unblock', 'cancel', 'supersede'].includes(action))
71
+ if (!['create', 'update', 'show', 'context', 'comment', 'comments', 'list', 'retry', 'recover', 'unblock', 'cancel', 'supersede'].includes(action))
71
72
  throw new Error(usage);
72
73
  const managed = !arg(args, '--file') && Boolean(arg(args, '--repo') && arg(args, '--id'));
73
74
  if (managed) {
@@ -78,6 +79,30 @@ async function runTaskCommand(action, args) {
78
79
  return;
79
80
  }
80
81
  const taskId = arg(args, '--task-id');
82
+ if (action === 'comment') {
83
+ if (!taskId)
84
+ throw new Error('task comment requires --task-id');
85
+ const message = await textOption(args, '--message', '--message-file');
86
+ console.log(JSON.stringify(await relayControlCommand(args, { command: 'task.comment', jobId, taskId, message }), null, 2));
87
+ return;
88
+ }
89
+ if (action === 'comments') {
90
+ if (!taskId)
91
+ throw new Error('task comments requires --task-id');
92
+ const job = await relayControlJob(args, jobId);
93
+ const task = job.tasks.find(item => item.id === taskId);
94
+ if (!task)
95
+ throw new Error(`Task ${taskId} not found`);
96
+ console.log(JSON.stringify(task.comments ?? [], null, 2));
97
+ return;
98
+ }
99
+ if (action === 'context') {
100
+ if (!taskId)
101
+ throw new Error('task context requires --task-id');
102
+ const job = await relayControlJob(args, jobId);
103
+ console.log(JSON.stringify(taskExecutionContext(job, taskId, arg(args, '--execution-id')), null, 2));
104
+ return;
105
+ }
81
106
  if (action === 'show') {
82
107
  if (!taskId)
83
108
  throw new Error('task show requires --task-id');
@@ -260,7 +260,7 @@ export class LocalAuthorityDurableStore {
260
260
  if (current.attempt === task.attempt && current.updatedAt === task.updatedAt
261
261
  && TASK_STATE_ORDER[current.state] >= TASK_STATE_ORDER[task.state])
262
262
  return job;
263
- job.tasks[index] = structuredClone(task);
263
+ job.tasks[index] = { ...structuredClone(task), comments: current.comments ?? task.comments };
264
264
  return job;
265
265
  });
266
266
  }
@@ -1,3 +1,4 @@
1
+ import { taskCommentContext } from './task-comments.js';
1
2
  import { cancelActiveTasks } from './reconciler-cancellation.js';
2
3
  import { authorizedInjection } from './e2e-injection.js';
3
4
  import { implementationComplete } from './runtime-status.js';
@@ -375,7 +376,7 @@ export class Reconciler extends ReconcilerReviewBase {
375
376
  freshTask.chatgptThreadDelivery = {
376
377
  kind: 'new-turn', threadId: implementationThread, threadSource: 'implementation',
377
378
  requestId: `${freshJob.id}:${freshTask.id}:${freshTask.executionId}`,
378
- jobId: freshJob.id, prNumber: freshJob.prNumber, executionId: freshTask.executionId,
379
+ jobId: freshJob.id, repository: freshJob.repository, taskId: freshTask.id, prNumber: freshJob.prNumber, executionId: freshTask.executionId,
379
380
  projectId: freshTask.routing?.projectId ?? null, deliveredAt: null,
380
381
  };
381
382
  }
@@ -404,7 +405,7 @@ export class Reconciler extends ReconcilerReviewBase {
404
405
  durable.chatgptThreadDelivery = {
405
406
  kind: 'new-turn', threadId: implementationThread, threadSource: 'implementation',
406
407
  requestId: `${current.id}:${durable.id}:${durable.executionId}`,
407
- jobId: current.id, prNumber: current.prNumber, executionId: durable.executionId,
408
+ jobId: current.id, repository: current.repository, taskId: durable.id, prNumber: current.prNumber, executionId: durable.executionId,
408
409
  projectId: durable.routing?.projectId ?? null, deliveredAt: null,
409
410
  };
410
411
  }
@@ -450,7 +451,7 @@ export class Reconciler extends ReconcilerReviewBase {
450
451
  const workerInput = canonicalProjectRun
451
452
  ? `${baseWorkerInput}\n\n[Canonical Neo project run]\nExecution cwd: ${executionContext.project.path}\nKeep generated artifacts under runs/.`
452
453
  : baseWorkerInput;
453
- const workerTask = { ...task, input: workerInput, routing: task.routing ? { ...task.routing, cwd: executionContext.project.path } : task.routing };
454
+ const workerTask = { ...task, input: workerInput + taskCommentContext(task), routing: task.routing ? { ...task.routing, cwd: executionContext.project.path } : task.routing };
454
455
  const startAckSetup = task.adapter === 'chatgpt' ? await this.startAck(job.id, task.id, sourceId, Date.now(), this.options.startAckTimeoutMs ?? chatGptStartAckTimeoutMs(task.timeoutMs)) : null;
455
456
  const closePolicy = task.chatgptTabClosePolicy ?? this.options.chatgptTabClosePolicy ?? adapter.tabClosePolicy ?? 'after-start';
456
457
  const terminalWait = task.adapter === 'chatgpt' && closePolicy === 'after-terminal' ? this.terminalWait(task.id, durableExecutionId, startedAt - 1) : null;
@@ -1,3 +1,4 @@
1
+ import { appendTaskComment } from './task-comments.js';
1
2
  import { normalizeThreadId } from './reviewer-thread.js';
2
3
  import { supersedeCancelledTask } from './task-actions.js';
3
4
  export function createWorkspaceControl(deps) {
@@ -107,6 +108,15 @@ export function createWorkspaceControl(deps) {
107
108
  await pool.reconcileLocalJob(jobId, 'control.job.unblock');
108
109
  return await store.load(jobId);
109
110
  }
111
+ if (command === 'task.comment') {
112
+ const taskId = typeof body.taskId === 'string' ? body.taskId : '';
113
+ const message = typeof body.message === 'string' ? body.message : '';
114
+ if (!taskId || !message.trim())
115
+ throw new Error('task comment requires taskId and message');
116
+ let appended;
117
+ await store.mutateJob(jobId, (current) => { appended = appendTaskComment(current, taskId, message); return current; });
118
+ return appended;
119
+ }
110
120
  if (command === 'task.supersede') {
111
121
  const taskId = typeof body.taskId === 'string' ? body.taskId : '';
112
122
  const reason = typeof body.reason === 'string' ? body.reason : '';
@@ -0,0 +1,16 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export function appendTaskComment(job, taskId, message, author = 'cli') {
3
+ const task = job.tasks.find(item => item.id === taskId);
4
+ if (!task)
5
+ throw new Error(`Task ${taskId} not found`);
6
+ if (!message.trim())
7
+ throw new Error('task comment requires nonempty --message');
8
+ const comment = { id: randomUUID(), text: message.trim(), author, createdAt: new Date().toISOString() };
9
+ task.comments = [...(task.comments ?? []), comment];
10
+ return comment;
11
+ }
12
+ export function taskCommentContext(task) {
13
+ if (!task.comments?.length)
14
+ return '';
15
+ return '\n\n[Task comments — append-only supplementary context]\n' + task.comments.map(c => `${c.createdAt} (${c.author}, ${c.id}): ${c.text}`).join('\n');
16
+ }
@@ -0,0 +1,16 @@
1
+ /** Resolve the execution-scoped contract from the authoritative Job snapshot. */
2
+ export function taskExecutionContext(job, taskId, executionId) {
3
+ if (!executionId)
4
+ throw new Error('task context requires --execution-id');
5
+ const task = job.tasks.find(item => item.id === taskId);
6
+ if (!task)
7
+ throw new Error(`Task ${taskId} not found`);
8
+ if (task.executionId !== executionId || !['QUEUED', 'RUNNING'].includes(task.state))
9
+ throw new Error(`Stale Task execution ${taskId}/${executionId}; current state ${task.state}`);
10
+ return { job: { id: job.id, repository: job.repository, title: job.title, objective: job.objective,
11
+ projectPath: job.projectPath, state: job.state }, task, comments: task.comments ?? [], dependencies: task.dependencies.map(id => job.tasks.find(item => item.id === id) ?? { id, missing: true }),
12
+ plan: job.plans?.find(plan => plan.id === task.planningDecisionId) ?? null };
13
+ }
14
+ export function taskReferencePrompt(repository, jobId, taskId, executionId) {
15
+ return `[Agents Relay managed task]\nRepository: ${repository}\nJob ID: ${jobId}\nTask ID: ${taskId}\nExecution ID: ${executionId}\n\nRetrieve the authoritative task contract using the connected NeoY shell/Agents Relay CLI:\n agents-relay task context --repo ${repository} --id ${jobId} --task-id ${taskId} --execution-id ${executionId}\nRead the returned Task input, dependencies, Plan, project context, and output requirements before execution. Check that the execution ID matches. If the CLI or context is unavailable, report blocked; never guess or execute from this envelope. Continue in this thread; Relay owns completion and review. Do not create another Job.`;
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "2.0.89",
3
+ "version": "2.0.91",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "type": "module",
6
6
  "bin": {