agents-relay 1.0.114 → 1.0.116

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
@@ -11,9 +11,9 @@ export function planningSpec(task) {
11
11
  throw new Error(`Initial task ${task.id} requires a supported adapter`);
12
12
  return { id: task.id, input: task.input, priority: task.priority, storySize: task.storySize, projectName: task.projectName, agentName: task.agentName, agentGraph: task.agentGraph, dependencies: task.dependencies, capabilities: task.capabilities, workerRequirements: task.workerRequirements, adapter: task.adapter, output: task.output ?? { kind: 'task_pr' }, routing: task.routing, continuation: task.continuation, maxAttempts: task.maxAttempts, timeoutMs: task.timeoutMs };
13
13
  }
14
- function record(value) {
14
+ function record(value, field = 'result') {
15
15
  if (!value || typeof value !== 'object' || Array.isArray(value))
16
- throw new Error('Planner result must be an object');
16
+ throw new Error(`Planner ${field} must be an object`);
17
17
  return value;
18
18
  }
19
19
  function optionalString(value, field) {
@@ -33,7 +33,7 @@ function stringArray(value, field, fallback = []) {
33
33
  function workerRequirements(value, field) {
34
34
  if (value === undefined || value === null)
35
35
  return undefined;
36
- const root = record(value);
36
+ const root = record(value, field);
37
37
  const capabilities = stringArray(root.capabilities, `${field}.capabilities`);
38
38
  const optional = (name) => optionalString(root[name], `${field}.${name}`);
39
39
  const labelsValue = root.labels;
@@ -77,14 +77,14 @@ export function normalizePlannerPriority(value, field) {
77
77
  function routing(value, field) {
78
78
  if (value === undefined || value === null)
79
79
  return undefined;
80
- const route = record(value);
80
+ const route = record(value, field);
81
81
  for (const key of ['provider', 'model', 'decidedBy', 'decidedAt'])
82
82
  if (typeof route[key] !== 'string' || String(route[key]).length === 0)
83
83
  throw new Error(`Planner ${field}.${key} is required`);
84
84
  return { provider: route.provider, model: route.model, profile: optionalString(route.profile, `${field}.profile`), reasoning: optionalString(route.reasoning, `${field}.reasoning`), cwd: optionalString(route.cwd, `${field}.cwd`), projectId: optionalString(route.projectId, `${field}.projectId`), decidedBy: route.decidedBy, decidedAt: route.decidedAt };
85
85
  }
86
86
  export function parsePlannerResult(value) {
87
- const root = record(value);
87
+ const root = record(value, 'result');
88
88
  const status = root.objective_status;
89
89
  if (status !== 'in_progress' && status !== 'satisfied')
90
90
  throw new Error('Planner objective_status must be in_progress or satisfied');
@@ -94,7 +94,7 @@ export function parsePlannerResult(value) {
94
94
  throw new Error('Planner next_tasks must be an array');
95
95
  const ids = new Set();
96
96
  const next_tasks = root.next_tasks.map((item, index) => {
97
- const task = record(item);
97
+ const task = record(item, `next_tasks[${index}]`);
98
98
  const id = task.id;
99
99
  if (typeof id !== 'string' || id.length === 0)
100
100
  throw new Error(`Planner next_tasks[${index}].id is required`);
@@ -106,7 +106,7 @@ export function parsePlannerResult(value) {
106
106
  const adapter = task.adapter;
107
107
  if (adapter !== undefined && !['codex', 'chatgpt'].includes(String(adapter)))
108
108
  throw new Error(`Planner next_tasks[${index}].adapter must be codex or chatgpt when provided`);
109
- const output = task.output === undefined ? undefined : record(task.output);
109
+ const output = task.output === undefined ? undefined : record(task.output, `next_tasks[${index}].output`);
110
110
  if (!output || (output.kind !== 'task_pr' && output.kind !== 'file'))
111
111
  throw new Error(`Planner next_tasks[${index}].output is required`);
112
112
  if (output.kind === 'file' && (typeof output.path !== 'string' || output.path.length === 0))
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "1.0.114",
3
+ "version": "1.0.116",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "type": "module",
6
6
  "bin": {
@@ -87,3 +87,36 @@ For `objective_status: "in_progress"`, `assessment` must be Markdown containing:
87
87
  For `objective_status: "satisfied"`, `assessment` may be a concise evidence-based completion assessment without a graph.
88
88
 
89
89
  Each item in next_tasks contains id, input, output, and storySize. It may also contain priority, projectName, agentName, dependencies, capabilities, workerRequirements, maxAttempts, and timeoutMs. Adapter/routing fields are selected by Relay after planning; do not invent them.
90
+
91
+ ### Exact `next_tasks` serialization
92
+
93
+ Use these exact serialized forms:
94
+
95
+ - `output` is always an object, never a string:
96
+ - PR-backed work: `{"kind":"task_pr"}`
97
+ - File artifact: `{"kind":"file","path":"runs/2026-09-26/example/output.md"}`
98
+ - `agentGraph`, when present, is one Mermaid graph **string**, for example `"graph LR\n build-log-editor --> build-log-producer"`. Do not emit an object with `nodes` or `edges`.
99
+ - `priority`, when present, is exactly `"P0"`, `"P1"`, `"P2"`, or `"P3"`; otherwise omit it.
100
+ - `storySize` is exactly `"S"`, `"M"`, or `"L"`.
101
+
102
+ A valid non-satisfied response:
103
+
104
+ ```json
105
+ {
106
+ "objective_status": "in_progress",
107
+ "assessment": "```mermaid\nflowchart LR\n A[Evidence] --> B[Content package]\n```\nOne daily package is the durable Task boundary.",
108
+ "next_tasks": [
109
+ {
110
+ "id": "episode-2026-09-26",
111
+ "input": "Produce and verify the dated content package.",
112
+ "output": {"kind": "file", "path": "runs/2026-09-26/package.md"},
113
+ "storySize": "L",
114
+ "priority": "P1",
115
+ "agentName": "build-log-editor",
116
+ "agentGraph": "graph LR\n build-log-editor --> build-log-producer"
117
+ }
118
+ ]
119
+ }
120
+ ```
121
+
122
+ Return this JSON object directly. Do not wrap the whole result in another JSON string.