@nyxa/nyx-agent 0.6.0 → 0.6.1
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/README.md +5 -2
- package/dist/cli.js +1 -0
- package/dist/commands/run.js +2 -1
- package/dist/runtime/runPipeline.js +15 -3
- package/dist/runtime/selectionConfirmation.js +85 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,8 @@ GitHub issue at a time, each phase with fresh context.
|
|
|
7
7
|
|
|
8
8
|
For every run NyxAgent:
|
|
9
9
|
|
|
10
|
-
1. **Selects** open GitHub issues to work on (read-only)
|
|
10
|
+
1. **Selects** open GitHub issues to work on (read-only), then asks the user to
|
|
11
|
+
confirm the proposed checklist.
|
|
11
12
|
2. For each selected issue, in an isolated git **worktree**:
|
|
12
13
|
- **implements** it (the agent — the only customizable prompt),
|
|
13
14
|
- optionally **reviews** and **revises** it (bounded loop),
|
|
@@ -27,7 +28,8 @@ is editable.
|
|
|
27
28
|
|
|
28
29
|
```bash
|
|
29
30
|
nyxagent init # create .nyxagent/config.json (interactive)
|
|
30
|
-
nyxagent run # run the pipeline
|
|
31
|
+
nyxagent run # run the pipeline, confirming selected work items
|
|
32
|
+
nyxagent run --yes # accept the agent selection without prompting
|
|
31
33
|
nyxagent run --harness claude # override the configured harness for one run
|
|
32
34
|
nyxagent update # self-update to the latest published version
|
|
33
35
|
```
|
|
@@ -63,3 +65,4 @@ unresolved feedback, so the work is never stranded on an orphaned branch.
|
|
|
63
65
|
- A git repository with a GitHub remote.
|
|
64
66
|
- The `gh` CLI authenticated for the configured repository.
|
|
65
67
|
- The selected harness CLI (`codex` or `claude`) on `PATH`.
|
|
68
|
+
- An interactive terminal for `nyxagent run`, unless `--yes` is used.
|
package/dist/cli.js
CHANGED
|
@@ -33,6 +33,7 @@ program
|
|
|
33
33
|
.description("Run the NyxAgent pipeline")
|
|
34
34
|
.option("--config <path>", "config path (default: .nyxagent/config.json)")
|
|
35
35
|
.option("--harness <name>", "override the configured harness: codex or claude")
|
|
36
|
+
.option("-y, --yes", "accept the agent-selected work items without prompting")
|
|
36
37
|
.action(async (options) => {
|
|
37
38
|
await runCommand(options);
|
|
38
39
|
});
|
package/dist/commands/run.js
CHANGED
|
@@ -8,7 +8,8 @@ export async function runCommand(options, projectRoot = process.cwd()) {
|
|
|
8
8
|
configPath: options.config
|
|
9
9
|
? path.resolve(projectRoot, options.config)
|
|
10
10
|
: undefined,
|
|
11
|
-
harness: normalizeHarness(options.harness)
|
|
11
|
+
harness: normalizeHarness(options.harness),
|
|
12
|
+
autoAcceptSelection: options.yes ?? false
|
|
12
13
|
});
|
|
13
14
|
}
|
|
14
15
|
function normalizeHarness(value) {
|
|
@@ -9,6 +9,7 @@ import { buildContextBlock, buildPhasePrompt, EXECUTION_PROMPT, GLOBAL_REVIEW_PR
|
|
|
9
9
|
import { runAgentPhase } from "./runPhase.js";
|
|
10
10
|
import { GLOBAL_REVIEW_SCHEMA, REVIEW_SCHEMA, SELECTION_SCHEMA } from "./schemas.js";
|
|
11
11
|
import { commitAll, commitsAhead, createPullRequest, pushBranch, rangeDiff, stageAllAndDiff } from "./scm.js";
|
|
12
|
+
import { confirmWorkItemSelection } from "./selectionConfirmation.js";
|
|
12
13
|
import { createRunId } from "./time.js";
|
|
13
14
|
import { filterAvailable, listGitHubIssues, resolveSelectedQueue } from "./workItems.js";
|
|
14
15
|
const MAX_CANDIDATES = 50;
|
|
@@ -18,7 +19,8 @@ export function defaultPipelineDependencies() {
|
|
|
18
19
|
listIssues: listGitHubIssues,
|
|
19
20
|
runPhase: runAgentPhase,
|
|
20
21
|
pushBranch,
|
|
21
|
-
createPullRequest
|
|
22
|
+
createPullRequest,
|
|
23
|
+
confirmSelection: confirmWorkItemSelection
|
|
22
24
|
};
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
@@ -55,7 +57,7 @@ export async function runPipeline(input = {}, deps = defaultPipelineDependencies
|
|
|
55
57
|
console.log("No open work items available. Nothing to do.");
|
|
56
58
|
return;
|
|
57
59
|
}
|
|
58
|
-
const
|
|
60
|
+
const proposed = await runSelection({
|
|
59
61
|
projectRoot,
|
|
60
62
|
runDir,
|
|
61
63
|
harness,
|
|
@@ -63,10 +65,20 @@ export async function runPipeline(input = {}, deps = defaultPipelineDependencies
|
|
|
63
65
|
candidates,
|
|
64
66
|
runPhase: deps.runPhase
|
|
65
67
|
});
|
|
66
|
-
if (
|
|
68
|
+
if (proposed.length === 0) {
|
|
67
69
|
console.log("Selection chose no work items. Nothing to do.");
|
|
68
70
|
return;
|
|
69
71
|
}
|
|
72
|
+
const selected = await deps.confirmSelection({
|
|
73
|
+
candidates,
|
|
74
|
+
proposed,
|
|
75
|
+
maxItems: config.max_iterations,
|
|
76
|
+
autoAccept: input.autoAcceptSelection ?? false
|
|
77
|
+
});
|
|
78
|
+
if (selected.length === 0) {
|
|
79
|
+
console.log("No work items selected. Nothing to do.");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
70
82
|
const planned = selected.slice(0, config.max_iterations);
|
|
71
83
|
console.log(`Selected ${planned.length} work item(s):`);
|
|
72
84
|
for (const item of planned) {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { checkbox, Separator } from "@inquirer/prompts";
|
|
2
|
+
export async function confirmWorkItemSelection(input) {
|
|
3
|
+
if (input.autoAccept) {
|
|
4
|
+
return input.proposed;
|
|
5
|
+
}
|
|
6
|
+
const isInteractive = input.isInteractive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
7
|
+
if (!isInteractive) {
|
|
8
|
+
throw new Error('Interactive work item selection requires a TTY. Re-run with "nyxagent run --yes" to accept the agent selection.');
|
|
9
|
+
}
|
|
10
|
+
const selectedKeys = await checkbox({
|
|
11
|
+
message: `Select work items to run (max ${input.maxItems})`,
|
|
12
|
+
choices: buildSelectionChoiceItems(input).map(toInquirerChoice),
|
|
13
|
+
pageSize: Math.min(Math.max(input.candidates.length, 7), 20),
|
|
14
|
+
required: false,
|
|
15
|
+
validate: (selected) => selected.length <= input.maxItems ||
|
|
16
|
+
`Select at most ${input.maxItems} work item(s).`,
|
|
17
|
+
shortcuts: {
|
|
18
|
+
all: null,
|
|
19
|
+
invert: null
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const selected = new Set(selectedKeys);
|
|
23
|
+
return input.candidates.filter((candidate) => selected.has(candidate.key));
|
|
24
|
+
}
|
|
25
|
+
export function buildSelectionChoiceItems(input) {
|
|
26
|
+
const proposedKeys = new Set(input.proposed.map((item) => item.key));
|
|
27
|
+
const items = [];
|
|
28
|
+
let currentGroup;
|
|
29
|
+
for (const candidate of input.candidates) {
|
|
30
|
+
const group = detectPlanGroup(candidate);
|
|
31
|
+
if (group && group !== currentGroup) {
|
|
32
|
+
items.push({ type: "separator", label: group });
|
|
33
|
+
}
|
|
34
|
+
currentGroup = group;
|
|
35
|
+
const proposed = proposedKeys.has(candidate.key);
|
|
36
|
+
items.push({
|
|
37
|
+
type: "choice",
|
|
38
|
+
value: candidate.key,
|
|
39
|
+
name: `#${candidate.number} ${candidate.title}${proposed ? " (agent)" : ""}`,
|
|
40
|
+
checked: proposed
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return items;
|
|
44
|
+
}
|
|
45
|
+
function toInquirerChoice(item) {
|
|
46
|
+
if (item.type === "separator") {
|
|
47
|
+
return new Separator(item.label);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
value: item.value,
|
|
51
|
+
name: item.name,
|
|
52
|
+
checked: item.checked
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function detectPlanGroup(candidate) {
|
|
56
|
+
for (const label of candidate.labels ?? []) {
|
|
57
|
+
const group = parseGroupLabel(label);
|
|
58
|
+
if (group) {
|
|
59
|
+
return group;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return parseBracketedTitleGroup(candidate.title);
|
|
63
|
+
}
|
|
64
|
+
function parseGroupLabel(label) {
|
|
65
|
+
const match = /^(plan|prd)\s*[:/=-]\s*(.+)$/i.exec(label.trim());
|
|
66
|
+
if (!match) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
return formatGroupLabel(match[1], match[2]);
|
|
70
|
+
}
|
|
71
|
+
function parseBracketedTitleGroup(title) {
|
|
72
|
+
const match = /^\[(plan|prd)\s*[:/=-]\s*([^\]]+)\]/i.exec(title.trim());
|
|
73
|
+
if (!match) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
return formatGroupLabel(match[1], match[2]);
|
|
77
|
+
}
|
|
78
|
+
function formatGroupLabel(kind, rawName) {
|
|
79
|
+
const name = rawName.trim();
|
|
80
|
+
if (!name) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
const prefix = kind.toLowerCase() === "prd" ? "PRD" : "Plan";
|
|
84
|
+
return `${prefix}: ${name}`;
|
|
85
|
+
}
|