agents-relay 2.0.90 → 2.0.92
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 +6 -0
- package/dist/cli.js +18 -1
- package/dist/local-authority-store.js +1 -1
- package/dist/reconciler.js +2 -1
- package/dist/relay-store.js +2 -1
- package/dist/relayd-control.js +10 -0
- package/dist/task-comments.js +31 -0
- package/dist/task-context.js +1 -1
- package/package.json +1 -1
package/dist/cli-help.js
CHANGED
|
@@ -129,6 +129,12 @@ 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.`,
|
|
132
138
|
'task context': `Read the authoritative execution-scoped Task contract.
|
|
133
139
|
|
|
134
140
|
Required: --repo OWNER/REPO --id JOB_ID --task-id TASK_ID --execution-id EXECUTION_ID. Fails closed on stale execution.`,
|
package/dist/cli.js
CHANGED
|
@@ -68,7 +68,7 @@ async function runManagedTaskCommand(action, args) {
|
|
|
68
68
|
console.log(JSON.stringify(task, null, 2));
|
|
69
69
|
}
|
|
70
70
|
async function runTaskCommand(action, args) {
|
|
71
|
-
if (!['create', 'update', 'show', 'context', 'list', 'retry', 'recover', 'unblock', 'cancel', 'supersede'].includes(action))
|
|
71
|
+
if (!['create', 'update', 'show', 'context', 'comment', 'comments', 'list', 'retry', 'recover', 'unblock', 'cancel', 'supersede'].includes(action))
|
|
72
72
|
throw new Error(usage);
|
|
73
73
|
const managed = !arg(args, '--file') && Boolean(arg(args, '--repo') && arg(args, '--id'));
|
|
74
74
|
if (managed) {
|
|
@@ -79,6 +79,23 @@ async function runTaskCommand(action, args) {
|
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
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
|
+
}
|
|
82
99
|
if (action === 'context') {
|
|
83
100
|
if (!taskId)
|
|
84
101
|
throw new Error('task context 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
|
}
|
package/dist/reconciler.js
CHANGED
|
@@ -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';
|
|
@@ -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;
|
package/dist/relay-store.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { preserveTaskComments } from './task-comments.js';
|
|
1
2
|
import { DatabaseSync } from 'node:sqlite';
|
|
2
3
|
import { dirname } from 'node:path';
|
|
3
4
|
import { mkdirSync } from 'node:fs';
|
|
@@ -23,7 +24,7 @@ function mergeRecords(local, remote) {
|
|
|
23
24
|
}
|
|
24
25
|
export function mergeInboundJobSnapshot(local, remote) {
|
|
25
26
|
const base = remote.updatedAt > local.updatedAt ? structuredClone(remote) : structuredClone(local);
|
|
26
|
-
base.tasks = mergeRecords(local.tasks, remote.tasks);
|
|
27
|
+
base.tasks = preserveTaskComments(local.tasks, mergeRecords(local.tasks, remote.tasks));
|
|
27
28
|
const localPlans = local.plans ?? [];
|
|
28
29
|
const remotePlans = remote.plans ?? [];
|
|
29
30
|
if (localPlans.length > 0 || remotePlans.length > 0) {
|
package/dist/relayd-control.js
CHANGED
|
@@ -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,31 @@
|
|
|
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
|
+
}
|
|
17
|
+
/** Preserve append-only comments across stale external or lifecycle snapshots. */
|
|
18
|
+
export function preserveTaskComments(previous, incoming) {
|
|
19
|
+
return incoming.map(task => {
|
|
20
|
+
const old = previous.find(item => item.id === task.id);
|
|
21
|
+
if (!old?.comments?.length)
|
|
22
|
+
return task;
|
|
23
|
+
const comments = [...old.comments], known = new Set(comments.map(comment => comment.id));
|
|
24
|
+
for (const comment of task.comments ?? [])
|
|
25
|
+
if (!known.has(comment.id)) {
|
|
26
|
+
comments.push(comment);
|
|
27
|
+
known.add(comment.id);
|
|
28
|
+
}
|
|
29
|
+
return { ...task, comments };
|
|
30
|
+
});
|
|
31
|
+
}
|
package/dist/task-context.js
CHANGED
|
@@ -8,7 +8,7 @@ export function taskExecutionContext(job, taskId, executionId) {
|
|
|
8
8
|
if (task.executionId !== executionId || !['QUEUED', 'RUNNING'].includes(task.state))
|
|
9
9
|
throw new Error(`Stale Task execution ${taskId}/${executionId}; current state ${task.state}`);
|
|
10
10
|
return { job: { id: job.id, repository: job.repository, title: job.title, objective: job.objective,
|
|
11
|
-
projectPath: job.projectPath, state: job.state }, task, dependencies: task.dependencies.map(id => job.tasks.find(item => item.id === id) ?? { id, missing: true }),
|
|
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
12
|
plan: job.plans?.find(plan => plan.id === task.planningDecisionId) ?? null };
|
|
13
13
|
}
|
|
14
14
|
export function taskReferencePrompt(repository, jobId, taskId, executionId) {
|