agents-relay 1.0.115 → 1.0.117

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/planner.js CHANGED
@@ -139,15 +139,41 @@ export function parsePlannerOutput(output) {
139
139
  const candidate = trimmed.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, '').trim();
140
140
  return parsePlannerResult(JSON.parse(candidate));
141
141
  }
142
+ function boundedText(value, max = 4000) {
143
+ if (value === null || value === undefined || value.length <= max)
144
+ return value;
145
+ return value.slice(0, max) + `\n...[truncated ${value.length - max} chars]`;
146
+ }
142
147
  export function plannerInput(context) {
143
148
  const visibleAgents = context.executionContext?.agents.map(agent => ({
144
149
  name: agent.name,
145
- description: agent.description,
150
+ description: boundedText(agent.description, 1000),
146
151
  capabilities: agent.capabilities,
147
152
  boundaries: agent.restrictions,
148
153
  runtime: agent.runtime,
149
154
  }));
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 })) });
155
+ const project = context.executionContext?.project;
156
+ return JSON.stringify({
157
+ objective: context.objective,
158
+ retry_feedback: context.retryFeedback,
159
+ previous_planner_task_id: context.previousPlannerTaskId,
160
+ project: project ? { name: project.name, path: project.path } : undefined,
161
+ visible_agents: visibleAgents ?? [],
162
+ tasks: context.tasks.map(task => ({
163
+ id: task.id,
164
+ kind: task.kind ?? 'work',
165
+ parent_task_id: task.parentTaskId,
166
+ state: task.state,
167
+ input: task.kind === 'planner' ? undefined : boundedText(task.input),
168
+ result_summary: boundedText(task.result?.summary),
169
+ planner_result: task.plannerResult ? {
170
+ objective_status: task.plannerResult.objective_status,
171
+ assessment: boundedText(task.plannerResult.assessment),
172
+ next_task_ids: task.plannerResult.next_tasks.map(item => item.id),
173
+ } : undefined,
174
+ error: boundedText(task.error, 2000),
175
+ })),
176
+ });
151
177
  }
152
178
  export class CommandObjectivePlanner {
153
179
  command;
@@ -1,6 +1,7 @@
1
1
  import { parseAgent, parseJob, parseTask, renderAgentComment, renderJobBody, renderTaskComment, JOB_MARKER } from './markers.js';
2
2
  import { isTaskForJob, sameTaskDefinition } from './store-task-definition.js';
3
3
  import { resolveBlockerDependencies } from './blockers.js';
4
+ const TASK_WRITE_OVERLAY = new Map();
4
5
  export class GitHubStore {
5
6
  client;
6
7
  repository;
@@ -12,6 +13,29 @@ export class GitHubStore {
12
13
  this.prNumber = prNumber;
13
14
  this.trustedAuthors = trustedAuthors;
14
15
  }
16
+ taskKey(task) { return `${this.repository}#${this.prNumber}:${task.jobId ?? ''}/${task.id}`; }
17
+ taskKeyFromBody(body) { const task = parseTask(body); return task ? this.taskKey(task) : null; }
18
+ overlayTaskWrites(comments) {
19
+ if (TASK_WRITE_OVERLAY.size === 0)
20
+ return comments;
21
+ const result = [...comments];
22
+ for (const [key, overlay] of [...TASK_WRITE_OVERLAY]) {
23
+ const index = result.findIndex(comment => this.taskKeyFromBody(comment.body) === key);
24
+ if (index >= 0) {
25
+ const remote = result[index];
26
+ if (remote.updatedAt && overlay.updatedAt && remote.updatedAt >= overlay.updatedAt) {
27
+ TASK_WRITE_OVERLAY.delete(key);
28
+ continue;
29
+ }
30
+ result[index] = { ...remote, ...overlay, id: remote.id || overlay.id, nodeId: remote.nodeId ?? overlay.nodeId, url: remote.url ?? overlay.url, authorLogin: remote.authorLogin ?? overlay.authorLogin };
31
+ }
32
+ else {
33
+ TASK_WRITE_OVERLAY.delete(key);
34
+ }
35
+ }
36
+ return result;
37
+ }
38
+ rememberTaskWrite(task, comment, body) { TASK_WRITE_OVERLAY.set(this.taskKey(task), { ...comment, body }); }
15
39
  isTrusted(comment) { return comment.authorLogin !== null && this.trustedAuthors.has(comment.authorLogin); }
16
40
  repoParts() {
17
41
  const [owner, name] = this.repository.split('/', 2);
@@ -61,7 +85,7 @@ export class GitHubStore {
61
85
  const pageInfo = comments?.pageInfo;
62
86
  cursor = pageInfo?.hasNextPage === true && typeof pageInfo.endCursor === 'string' ? pageInfo.endCursor : null;
63
87
  } while (cursor);
64
- return all;
88
+ return this.overlayTaskWrites(all);
65
89
  }
66
90
  async comments() { return this.list(); }
67
91
  async pullRequestState() {
@@ -151,12 +175,31 @@ export class GitHubStore {
151
175
  async listAgents() { return (await this.list()).filter(comment => this.isTrusted(comment)).map(x => parseAgent(x.body)).filter((x) => x !== null); }
152
176
  async comment(body) {
153
177
  const pr = await this.prNode();
154
- await this.graphql('mutation($subjectId:ID!,$body:String!){addComment(input:{subjectId:$subjectId,body:$body}){commentEdge{node{id}}}}', { subjectId: pr.id, body });
178
+ const data = await this.graphql('mutation($subjectId:ID!,$body:String!){addComment(input:{subjectId:$subjectId,body:$body}){commentEdge{node{id databaseId url body updatedAt author{login}}}}}', { subjectId: pr.id, body });
179
+ const edge = data.addComment?.commentEdge;
180
+ const node = edge?.node;
181
+ return {
182
+ id: Number(node?.databaseId ?? 0),
183
+ nodeId: typeof node?.id === 'string' ? node.id : undefined,
184
+ url: typeof node?.url === 'string' ? node.url : undefined,
185
+ body: typeof node?.body === 'string' ? node.body : body,
186
+ updatedAt: typeof node?.updatedAt === 'string' ? node.updatedAt : new Date().toISOString(),
187
+ authorLogin: typeof node?.author?.login === 'string' ? String((node?.author).login) : [...this.trustedAuthors][0] ?? null,
188
+ };
155
189
  }
156
190
  async updateComment(comment, body) {
157
191
  if (!comment.nodeId)
158
192
  throw new Error('GitHub comment node id is required for GraphQL update');
159
- await this.graphql('mutation($id:ID!,$body:String!){updateIssueComment(input:{id:$id,body:$body}){issueComment{id}}}', { id: comment.nodeId, body });
193
+ const data = await this.graphql('mutation($id:ID!,$body:String!){updateIssueComment(input:{id:$id,body:$body}){issueComment{id databaseId url body updatedAt author{login}}}}', { id: comment.nodeId, body });
194
+ const node = data.updateIssueComment?.issueComment;
195
+ return {
196
+ id: Number(node?.databaseId ?? comment.id),
197
+ nodeId: typeof node?.id === 'string' ? node.id : comment.nodeId,
198
+ url: typeof node?.url === 'string' ? node.url : comment.url,
199
+ body: typeof node?.body === 'string' ? node.body : body,
200
+ updatedAt: typeof node?.updatedAt === 'string' ? node.updatedAt : new Date().toISOString(),
201
+ authorLogin: typeof node?.author?.login === 'string' ? String((node?.author).login) : comment.authorLogin,
202
+ };
160
203
  }
161
204
  async deleteComment(comment) {
162
205
  if (!comment.nodeId)
@@ -190,7 +233,9 @@ export class GitHubStore {
190
233
  return existing !== null && existing.jobId === task.jobId && existing.id === task.id;
191
234
  });
192
235
  if (matches.length === 0) {
193
- await this.comment(renderTaskComment(task));
236
+ const body = renderTaskComment(task);
237
+ const persisted = await this.comment(body);
238
+ this.rememberTaskWrite(task, persisted, body);
194
239
  return;
195
240
  }
196
241
  const parsed = matches.map(comment => ({ comment, task: parseTask(comment.body) }));
@@ -203,7 +248,9 @@ export class GitHubStore {
203
248
  const comments = (await this.list()).filter(comment => this.isTrusted(comment));
204
249
  const existing = comments.filter(comment => { const markerTask = parseTask(comment.body); return markerTask !== null && markerTask.jobId === task.jobId && markerTask.id === task.id; });
205
250
  if (existing.length > 0) {
206
- await this.updateComment(existing[0], renderTaskComment(task));
251
+ const body = renderTaskComment(task);
252
+ const persisted = await this.updateComment(existing[0], body);
253
+ this.rememberTaskWrite(task, persisted, body);
207
254
  for (const duplicate of existing.slice(1))
208
255
  await this.deleteComment(duplicate);
209
256
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "1.0.115",
3
+ "version": "1.0.117",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "type": "module",
6
6
  "bin": {