@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),
|
package/dist/runtime/runPhase.js
CHANGED
|
@@ -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
|
-
|
|
74
|
+
availableWorkItems: selection.availableWorkItems,
|
|
73
75
|
confirmed: await (options.confirmWorkItems ?? confirmWorkItemsWithCheckbox)({
|
|
74
|
-
|
|
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.
|
|
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:
|
|
338
|
+
choices: confirmationView.choices,
|
|
331
339
|
required: false,
|
|
332
|
-
pageSize: Math.min(Math.max(input.
|
|
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
|
|
345
|
+
return confirmationView.workItems.filter((item) => selected.has(item.key));
|
|
336
346
|
}
|
|
337
|
-
function
|
|
347
|
+
function buildConfirmationView(input) {
|
|
338
348
|
const choices = [];
|
|
339
|
-
const
|
|
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.
|
|
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:
|
|
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
|
|
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
|
|
437
|
-
if (!
|
|
438
|
-
throw new Error(`Confirmed work item key "${item.key}" is not in
|
|
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(
|
|
458
|
+
normalized.push(available);
|
|
445
459
|
}
|
|
446
460
|
return normalized;
|
|
447
461
|
}
|
package/docs/nyxagent-v0-spec.md
CHANGED
|
@@ -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.
|
|
189
|
-
each
|
|
190
|
-
requires every selected key to exist in
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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,13 +1,16 @@
|
|
|
1
|
-
|
|
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.
|
|
5
|
-
most actionable queue for this run, up to
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
`
|
|
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
|
|