@nyxa/nyx-agent 0.3.1 → 0.3.2

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.
@@ -41,6 +41,11 @@ async function buildRuntimeContract(input) {
41
41
  JSON.stringify(input.context.available_work_items ?? [], null, 2),
42
42
  "```",
43
43
  "",
44
+ "Recommended work item queue:",
45
+ "```json",
46
+ JSON.stringify(input.context.recommended_work_item_queue ?? [], null, 2),
47
+ "```",
48
+ "",
44
49
  "Selected work item queue:",
45
50
  "```json",
46
51
  JSON.stringify(input.context.selected_work_item_queue ?? [], null, 2),
@@ -83,6 +83,7 @@ function buildContext(input) {
83
83
  workflow: input.config.workflow,
84
84
  work_items: input.config.work_items ?? {},
85
85
  available_work_items: input.state.available_work_items ?? [],
86
+ recommended_work_item_queue: input.state.recommended_work_item_queue ?? [],
86
87
  selected_work_item_queue: input.state.selected_work_item_queue ?? [],
87
88
  work_item: input.state.work_item ?? {},
88
89
  seen_work_item_keys: input.state.seen_work_item_keys ?? [],
@@ -27,6 +27,7 @@ export async function runWorkflow(options) {
27
27
  current_iteration: 0,
28
28
  completed_iterations: 0,
29
29
  available_work_items: [],
30
+ recommended_work_item_queue: [],
30
31
  selected_work_item_queue: [],
31
32
  skipped_work_item_keys: [],
32
33
  selection_groups: [],
@@ -64,14 +65,17 @@ export async function runWorkflow(options) {
64
65
  return;
65
66
  }
66
67
  runState.available_work_items = selection.availableWorkItems;
68
+ runState.recommended_work_item_queue = selection.workItems;
67
69
  runState.selection_groups = selection.selectionGroups;
68
70
  await writeJson(path.join(runDir, "state.json"), runState);
69
71
  let confirmedWorkItems;
70
72
  try {
71
73
  confirmedWorkItems = normalizeConfirmedWorkItems({
72
- queue: selection.workItems,
74
+ availableWorkItems: selection.availableWorkItems,
73
75
  confirmed: await (options.confirmWorkItems ?? confirmWorkItemsWithCheckbox)({
74
- workItems: selection.workItems,
76
+ availableWorkItems: selection.availableWorkItems,
77
+ recommendedWorkItems: selection.workItems,
78
+ workItems: selection.availableWorkItems,
75
79
  selectionGroups: selection.selectionGroups,
76
80
  maxIterations: config.workflow.max_iterations
77
81
  })
@@ -113,6 +117,7 @@ export async function runWorkflow(options) {
113
117
  status: "running",
114
118
  work_item: workItem,
115
119
  available_work_items: selectedWorkItems,
120
+ recommended_work_item_queue: selection.workItems,
116
121
  selected_work_item_queue: selectedWorkItems,
117
122
  seen_work_item_keys: [...runState.seen_work_item_keys],
118
123
  completed_work_item_keys: [...ledger.completed_work_item_keys],
@@ -202,6 +207,7 @@ async function runSelectionPhase(input) {
202
207
  iteration: 0,
203
208
  status: "running",
204
209
  available_work_items: availableWorkItems,
210
+ recommended_work_item_queue: [],
205
211
  selected_work_item_queue: [],
206
212
  seen_work_item_keys: [...input.runState.seen_work_item_keys],
207
213
  completed_work_item_keys: [...input.ledger.completed_work_item_keys],
@@ -251,6 +257,7 @@ async function runSelectionPhase(input) {
251
257
  });
252
258
  const selectionGroups = readSelectionGroups(phaseResult.result);
253
259
  selectionState.status = "completed";
260
+ selectionState.recommended_work_item_queue = workItems;
254
261
  selectionState.selected_work_item_queue = workItems;
255
262
  await writeJson(selectionStateFile, selectionState);
256
263
  await writeJson(path.join(input.runDir, "state.json"), input.runState);
@@ -319,24 +326,29 @@ async function runWorkflowPhase(input) {
319
326
  return phaseResult;
320
327
  }
321
328
  export async function confirmWorkItemsWithCheckbox(input) {
322
- if (input.workItems.length === 0) {
329
+ if (input.availableWorkItems.length === 0) {
323
330
  return [];
324
331
  }
325
332
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
326
333
  throw new Error("Work item selection confirmation requires an interactive TTY");
327
334
  }
335
+ const confirmationView = buildConfirmationView(input);
328
336
  const selectedKeys = await checkbox({
329
337
  message: `Confirm work items to run (max ${input.maxIterations})`,
330
- choices: buildConfirmationChoices(input),
338
+ choices: confirmationView.choices,
331
339
  required: false,
332
- pageSize: Math.min(Math.max(input.workItems.length + 2, 5), 20)
340
+ pageSize: Math.min(Math.max(input.availableWorkItems.length + 2, 5), 20),
341
+ validate: (selected) => selected.length <= input.maxIterations ||
342
+ `Select at most ${input.maxIterations} work items.`
333
343
  });
334
344
  const selected = new Set(selectedKeys);
335
- return input.workItems.filter((item) => selected.has(item.key));
345
+ return confirmationView.workItems.filter((item) => selected.has(item.key));
336
346
  }
337
- function buildConfirmationChoices(input) {
347
+ function buildConfirmationView(input) {
338
348
  const choices = [];
339
- const byKey = new Map(input.workItems.map((item) => [item.key, item]));
349
+ const workItems = [];
350
+ const byKey = new Map(input.availableWorkItems.map((item) => [item.key, item]));
351
+ const recommendedKeys = new Set(input.recommendedWorkItems.map((item) => item.key));
340
352
  const displayed = new Set();
341
353
  for (const group of input.selectionGroups) {
342
354
  const groupItems = group.work_item_keys
@@ -352,25 +364,27 @@ function buildConfirmationChoices(input) {
352
364
  }
353
365
  choices.push(new Separator(`-- ${group.title} --`));
354
366
  for (const item of groupItems) {
355
- choices.push(buildConfirmationChoice(item));
367
+ choices.push(buildConfirmationChoice(item, recommendedKeys));
368
+ workItems.push(item);
356
369
  displayed.add(item.key);
357
370
  }
358
371
  }
359
- const ungroupedItems = input.workItems.filter((item) => !displayed.has(item.key));
372
+ const ungroupedItems = input.availableWorkItems.filter((item) => !displayed.has(item.key));
360
373
  if (input.selectionGroups.length > 0 && ungroupedItems.length > 0) {
361
374
  choices.push(new Separator("-- Ungrouped --"));
362
375
  }
363
376
  for (const item of ungroupedItems) {
364
- choices.push(buildConfirmationChoice(item));
377
+ choices.push(buildConfirmationChoice(item, recommendedKeys));
378
+ workItems.push(item);
365
379
  }
366
- return choices;
380
+ return { choices, workItems };
367
381
  }
368
- function buildConfirmationChoice(item) {
382
+ function buildConfirmationChoice(item, recommendedKeys) {
369
383
  return {
370
384
  value: item.key,
371
385
  name: `${item.title} (${item.key})`,
372
386
  description: item.excerpt,
373
- checked: true
387
+ checked: recommendedKeys.has(item.key)
374
388
  };
375
389
  }
376
390
  function resolveNextTarget(phase, outcome) {
@@ -429,19 +443,19 @@ function readSelectedWorkItems(input) {
429
443
  return validation.workItems;
430
444
  }
431
445
  function normalizeConfirmedWorkItems(input) {
432
- const queuedByKey = new Map(input.queue.map((item) => [item.key, item]));
446
+ const availableByKey = new Map(input.availableWorkItems.map((item) => [item.key, item]));
433
447
  const selected = new Set();
434
448
  const normalized = [];
435
449
  for (const item of input.confirmed) {
436
- const queued = queuedByKey.get(item.key);
437
- if (!queued) {
438
- throw new Error(`Confirmed work item key "${item.key}" is not in selected queue`);
450
+ const available = availableByKey.get(item.key);
451
+ if (!available) {
452
+ throw new Error(`Confirmed work item key "${item.key}" is not in available_work_items`);
439
453
  }
440
454
  if (selected.has(item.key)) {
441
455
  throw new Error(`Confirmed work item key "${item.key}" was selected twice`);
442
456
  }
443
457
  selected.add(item.key);
444
- normalized.push(queued);
458
+ normalized.push(available);
445
459
  }
446
460
  return normalized;
447
461
  }
@@ -185,12 +185,13 @@ max_visits_per_iteration = 1
185
185
  - `work_items.excerpt_chars` defaults to `800` and bounds candidate excerpts.
186
186
  - At run start, the engine scans the configured provider, normalizes candidates
187
187
  into `available_work_items`, and sends that inventory to the selection phase.
188
- - The selection phase returns an ordered `work_items` queue. NyxAgent validates
189
- each selected identity against `[work_items]`, rejects duplicates, and
190
- requires every selected key to exist in `available_work_items`.
191
- - Before executing, NyxAgent shows the selected queue in an interactive
192
- checkbox prompt. The user can uncheck work items; only confirmed items are
193
- executed.
188
+ - The selection phase returns an ordered recommended `work_items` queue.
189
+ NyxAgent validates each recommended identity against `[work_items]`, rejects
190
+ duplicates, and requires every selected key to exist in
191
+ `available_work_items`.
192
+ - Before executing, NyxAgent shows the full `available_work_items` inventory in
193
+ an interactive checkbox prompt. The recommended queue is pre-checked, and the
194
+ user can add or remove available items. Only confirmed items are executed.
194
195
  - Local markdown work items do not have a required frontmatter schema. The
195
196
  provider infers titles from the first heading or filename and includes a
196
197
  bounded content excerpt.
@@ -199,7 +200,8 @@ max_visits_per_iteration = 1
199
200
  configured repository.
200
201
  - NyxAgent does not decide whether an item is a plan, PRD, or task. The
201
202
  selection agent makes that semantic choice from the deterministic inventory
202
- and may include optional `selection_groups` for user review.
203
+ and may include optional `selection_groups` for user review. Groups may cover
204
+ the full available inventory, not only the recommended queue.
203
205
 
204
206
  ## Workflow Model
205
207
 
@@ -222,7 +224,7 @@ only knows phases, outcomes, transitions, and visit limits.
222
224
  The default template expresses the standard run:
223
225
 
224
226
  ```text
225
- selection -> user confirms queue
227
+ selection -> user confirms inventory with recommended queue pre-checked
226
228
  for each confirmed work item:
227
229
  execution -> review
228
230
  review.approved -> closure -> next_iteration
@@ -299,6 +301,7 @@ The runtime contract includes:
299
301
  - work item context, when selected
300
302
  - work item config from `[work_items]`
301
303
  - `available_work_items`
304
+ - `recommended_work_item_queue`
302
305
  - `selected_work_item_queue`
303
306
  - `seen_work_item_keys`
304
307
  - `completed_work_item_keys`
@@ -314,6 +317,7 @@ Prompts may use simple interpolation:
314
317
  {{iteration_dir}}
315
318
  {{phase_dir}}
316
319
  {{state_file}}
320
+ {{recommended_work_item_queue}}
317
321
  {{selected_work_item_queue}}
318
322
  {{work_item.key}}
319
323
  {{work_item.title}}
@@ -381,8 +385,9 @@ Each run creates a timestamped directory:
381
385
  - current iteration
382
386
  - completed iterations
383
387
  - available work items seen by the initial selection phase
388
+ - recommended work item queue returned by the selection agent
384
389
  - selected work item queue confirmed for the run
385
- - skipped selected work item keys
390
+ - skipped recommended work item keys
386
391
  - selection groups returned for user review
387
392
  - seen work item keys
388
393
  - completed work item keys
@@ -442,14 +447,15 @@ Default prompts should be concise but operational.
442
447
 
443
448
  Selection:
444
449
 
445
- - choose an ordered queue from `available_work_items`
450
+ - recommend an ordered queue from `available_work_items`
446
451
  - treat candidates agnostically: they may be plans, PRDs, or tasks
447
452
  - prefer the most actionable items based on candidate titles and excerpts
448
453
  - if a plan references concrete candidate tasks, choose the concrete task when
449
454
  that is the best next work
450
455
  - avoid keys already present in `seen_work_item_keys` or
451
456
  `completed_work_item_keys`
452
- - optionally include `selection_groups` to present related work by plan or PRD
457
+ - optionally include `selection_groups` to present related work by the most
458
+ useful grouping the agent can infer
453
459
  - return `selected` or `no_work`
454
460
 
455
461
  Execution:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyxa/nyx-agent",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "A lightweight phase orchestrator for repeatedly launching coding agents with fresh context.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,13 +1,16 @@
1
- Select the ordered work item queue for this run.
1
+ Recommend the ordered work item queue for this run.
2
2
 
3
3
  Use `available_work_items` from the runtime contract as the complete inventory
4
- for selection. Some candidates may be plans, PRDs, or concrete tasks. Build the
5
- most actionable queue for this run, up to `workflow.max_iterations` items.
4
+ for selection. Some candidates may be plans, PRDs, or concrete tasks.
5
+ Recommend the most actionable queue for this run, up to
6
+ `workflow.max_iterations` items. NyxAgent will show the complete inventory to
7
+ the user afterward with your recommendation pre-selected.
6
8
 
7
- If candidates appear to belong to the same plan or PRD, keep related concrete
8
- tasks together and use `selection_groups` to describe the grouping. The grouping
9
- is only for user review; every executable item must still be included in
10
- `work_items`.
9
+ If candidates appear to belong together, use `selection_groups` to describe the
10
+ most useful review grouping. Infer grouping from titles, paths, excerpts, and
11
+ declared relationships when available, but do not assume every project uses
12
+ plan/PRD subdirectories. Groups may include any key from `available_work_items`,
13
+ including items that are not part of your recommended `work_items` queue.
11
14
 
12
15
  Prefer concrete tasks over their parent plan when both are present and the task
13
16
  is ready to execute. Choose the plan itself only when it is the best next work.
@@ -27,7 +30,7 @@ The selected work item identities must match the inventory entries:
27
30
 
28
31
  Return one of these outcomes:
29
32
 
30
- - `selected`: include ordered `work_items`; optionally include
33
+ - `selected`: include recommended ordered `work_items`; optionally include
31
34
  `selection_groups` with `title`, optional `kind`, and `work_item_keys`
32
35
  - `no_work`: include a short `reason`
33
36