@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 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
  });
@@ -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 selected = await runSelection({
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 (selected.length === 0) {
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyxa/nyx-agent",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "A lightweight phase orchestrator for repeatedly launching coding agents with fresh context.",
5
5
  "type": "module",
6
6
  "repository": {