nightralph 0.0.40 → 0.0.42
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 +10 -2
- package/dist/display.js +18 -4
- package/dist/display.js.map +2 -2
- package/dist/index.js +79 -43
- package/dist/index.js.map +2 -2
- package/dist/meta.json +10 -10
- package/dist/orchestrator.js +50 -8
- package/dist/orchestrator.js.map +2 -2
- package/dist/src/display.d.ts +7 -0
- package/dist/src/display.d.ts.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/orchestrator.d.ts +5 -2
- package/dist/src/orchestrator.d.ts.map +1 -1
- package/dist/src/worktree.d.ts +1 -1
- package/dist/src/worktree.d.ts.map +1 -1
- package/dist/worktree.js +3 -26
- package/dist/worktree.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,10 +36,11 @@ so they can't drift as long as you install the skills with this package.
|
|
|
36
36
|
* [Mac OS](#mac-os)
|
|
37
37
|
+ [Claude Opus 4.6](#claude-opus-46)
|
|
38
38
|
+ [Claude Sonnet 5](#claude-sonnet-5)
|
|
39
|
+
+ [Claude Fable 5.1](#claude-fable-51)
|
|
39
40
|
+ [`pi` + OpenRouter model](#pi--openrouter-model)
|
|
40
41
|
+ [`codex` + Luna](#codex--luna)
|
|
41
42
|
+ [`codex` + Terra](#codex--terra)
|
|
42
|
-
+ [Mercury](#mercury)
|
|
43
|
+
+ [Mercury + `pi`](#mercury--pi)
|
|
43
44
|
|
|
44
45
|
<!-- tocstop -->
|
|
45
46
|
|
|
@@ -165,6 +166,7 @@ Flags:
|
|
|
165
166
|
| `--effort <level>` | no | -- | Starting effort/reasoning level. Each provider has its own ladder: `claude` supports `low`, `medium`, `high`, `xhigh`, `max` (forwarded as `--effort`); `codex` supports `low`, `medium`, `high` (forwarded as `-c model_reasoning_effort=<level>`); `pi` supports `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max` (forwarded as `--thinking`). Omitted = provider default. Each retry steps up from here (see `--retries`) |
|
|
166
167
|
| `--retries <n>` | no | `3` | Extra attempts per ticket after a failed run. Each retry starts from a fresh worktree and raises the effort level one rung (see Notes). `0` disables retries |
|
|
167
168
|
| `--retry-delay <n>` | no | `30` | Seconds to wait before the first retry; doubles each further retry (30s, 60s, 120s, ...). `0` disables the wait |
|
|
169
|
+
| `--concurrency <n>` | no | `5` | Max agents running at once within a wave. A wave with more ready tickets than this runs them through a pool: as one agent exits, the next ticket starts. The wave still finishes fully before its branches are merged |
|
|
168
170
|
|
|
169
171
|
|
|
170
172
|
**Notes**:
|
|
@@ -310,6 +312,12 @@ caffeinate -i npx nightralph claude -m claude-opus-4-6 --effort low
|
|
|
310
312
|
caffeinate -i npx nightralph claude -m claude-sonnet-5 --effort high
|
|
311
313
|
```
|
|
312
314
|
|
|
315
|
+
#### Claude Fable 5.1
|
|
316
|
+
|
|
317
|
+
```sh
|
|
318
|
+
caffeinate -i npx nightralph claude -m claude-fable-5-1 --effort low
|
|
319
|
+
```
|
|
320
|
+
|
|
313
321
|
#### `pi` + OpenRouter model
|
|
314
322
|
|
|
315
323
|
```sh
|
|
@@ -328,7 +336,7 @@ caffeinate -i npx nightralph codex -m gpt-5.6-luna --effort high
|
|
|
328
336
|
caffeinate -i npx nightralph codex -m gpt-5.6-terra --effort medium
|
|
329
337
|
```
|
|
330
338
|
|
|
331
|
-
#### Mercury
|
|
339
|
+
#### Mercury + `pi`
|
|
332
340
|
|
|
333
341
|
```sh
|
|
334
342
|
caffeinate -i npx nightralph pi -m openrouter/inception/mercury-2.5 --effort high
|
package/dist/display.js
CHANGED
|
@@ -53,6 +53,16 @@ function fitLine(line, width) {
|
|
|
53
53
|
return visible.slice(0, width);
|
|
54
54
|
}
|
|
55
55
|
__name(fitLine, "fitLine");
|
|
56
|
+
function wrapLine(line, width) {
|
|
57
|
+
const visible = line.replace(ANSI_RE, "");
|
|
58
|
+
if (visible.length <= width) return [line];
|
|
59
|
+
const rows = [];
|
|
60
|
+
for (let i = 0; i < visible.length; i += width) {
|
|
61
|
+
rows.push(visible.slice(i, i + width));
|
|
62
|
+
}
|
|
63
|
+
return rows;
|
|
64
|
+
}
|
|
65
|
+
__name(wrapLine, "wrapLine");
|
|
56
66
|
function renderDot(status) {
|
|
57
67
|
switch (status) {
|
|
58
68
|
case "pending":
|
|
@@ -247,9 +257,12 @@ class StatusDisplay {
|
|
|
247
257
|
0,
|
|
248
258
|
termHeight - 3 - renderedTicketCount
|
|
249
259
|
);
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
260
|
+
const wrapped = this.messages.flatMap(
|
|
261
|
+
(msg) => wrapLine(msg, width)
|
|
262
|
+
);
|
|
263
|
+
const visibleMessages = wrapped.slice(-logRows);
|
|
264
|
+
for (const row of visibleMessages) {
|
|
265
|
+
output += `${row}
|
|
253
266
|
`;
|
|
254
267
|
}
|
|
255
268
|
const emptyRows = logRows - visibleMessages.length;
|
|
@@ -294,6 +307,7 @@ export {
|
|
|
294
307
|
color,
|
|
295
308
|
createDisplay,
|
|
296
309
|
fitLine,
|
|
297
|
-
renderDot
|
|
310
|
+
renderDot,
|
|
311
|
+
wrapLine
|
|
298
312
|
};
|
|
299
313
|
//# sourceMappingURL=display.js.map
|
package/dist/display.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/display.ts"],
|
|
4
|
-
"sourcesContent": ["export type TicketStatus =\n | 'pending'\n | 'in-progress'\n | 'done'\n | 'failed'\n\nexport type DisplayTicket = {\n readonly num:number\n readonly filename:string\n status:TicketStatus\n}\n\nexport type WaveState = {\n readonly waveNum:number\n readonly totalWaves:number\n readonly tickets:Array<DisplayTicket>\n}\n\nexport type DisplayConfig = {\n readonly stream:NodeJS.WriteStream\n}\n\n/**\n * Progress across the whole run, not just the current wave.\n * `completed` counts tickets done before the wave started;\n * tickets that finish during the wave are added on top.\n */\nexport type RunProgress = {\n readonly completed:number\n readonly total:number\n}\n\nexport type Display = {\n startWave(\n waveNum:number,\n totalWaves:number,\n tickets:ReadonlyArray<{\n num:number\n filename:string\n }>,\n progress?:RunProgress,\n remaining?:ReadonlyArray<{\n num:number\n filename:string\n }>,\n ):void\n setTicketStatus(num:number, status:TicketStatus):void\n log(message:string):void\n cleanup():void\n}\n\nconst ESC = '\\x1b['\n\nexport const ansi = {\n cursorUp (n:number):string {\n return `${ESC}${n}A`\n },\n cursorTo (col:number):string {\n return `${ESC}${col}G`\n },\n eraseLine ():string {\n return `${ESC}2K`\n },\n eraseLines (n:number):string {\n let out = ''\n for (let i = 0; i < n; i++) {\n out += ansi.eraseLine()\n if (i < n - 1) out += ansi.cursorUp(1)\n }\n return out\n },\n cursorHide ():string {\n return `${ESC}?25l`\n },\n cursorShow ():string {\n return `${ESC}?25h`\n },\n} as const\n\nexport const color = {\n green (text:string):string {\n return `${ESC}32m${text}${ESC}0m`\n },\n yellow (text:string):string {\n return `${ESC}33m${text}${ESC}0m`\n },\n red (text:string):string {\n return `${ESC}31m${text}${ESC}0m`\n },\n dim (text:string):string {\n return `${ESC}2m${text}${ESC}0m`\n },\n} as const\n\nconst FILLED_DOT = '\u25CF'\nconst EMPTY_DOT = '\u25CB'\nconst SEPARATOR_WIDTH = 50\nconst DEFAULT_COLUMNS = 80\nconst MIN_LOG_ROWS = 1\nconst MAX_MESSAGE_BUFFER = 200\n\n// eslint-disable-next-line no-control-regex\nconst ANSI_RE = /\\x1b\\[[0-9;?]*[A-Za-z]/g\n\n/**\n * Clamp a line to `width` visible columns so it can never wrap.\n * A wrapped line occupies more terminal rows than `render`\n * counts, which leaves stale rows behind on the next erase.\n */\nexport function fitLine (line:string, width:number):string {\n const visible = line.replace(ANSI_RE, '')\n if (visible.length <= width) return line\n return visible.slice(0, width)\n}\n\nexport function renderDot (status:TicketStatus):string {\n switch (status) {\n case 'pending':\n return color.dim(EMPTY_DOT)\n case 'in-progress':\n return color.yellow(FILLED_DOT)\n case 'done':\n return color.green(FILLED_DOT)\n case 'failed':\n return color.red(FILLED_DOT)\n }\n}\n\nexport class StatusDisplay implements Display {\n private wave:WaveState|null = null\n private progress:RunProgress|null = null\n private remaining:Array<DisplayTicket> = []\n private messages:Array<string> = []\n private renderedLines:number = 0\n private config:DisplayConfig\n private viewportStart:number = 0\n private resizeListener:(() => void)|null = null\n\n constructor (config?:Readonly<Partial<DisplayConfig>>) {\n this.config = {\n stream:config?.stream ?? process.stdout,\n }\n }\n\n private getAvailableRows ():number {\n const termHeight = this.config.stream.rows ?? 24\n const overhead = 2 + MIN_LOG_ROWS\n const available = termHeight - overhead\n return Math.max(1, available)\n }\n\n startWave (\n waveNum:number,\n totalWaves:number,\n tickets:Array<Readonly<{ num:number; filename:string }>>,\n progress?:RunProgress,\n remaining?:ReadonlyArray<{\n num:number\n filename:string\n }>,\n ):void {\n this.wave = {\n waveNum,\n totalWaves,\n tickets:tickets.map(t => ({\n ...t,\n status:'pending' as const,\n })),\n }\n this.remaining = (remaining ?? []).map(t => ({\n ...t,\n status:'pending' as const,\n }))\n this.progress = progress ?? null\n this.messages = []\n this.viewportStart = 0\n this.config.stream.write(ansi.cursorHide())\n\n if (this.config.stream.isTTY) {\n if (this.resizeListener) {\n this.config.stream.removeListener(\n 'resize', this.resizeListener,\n )\n }\n this.resizeListener = () => this.render()\n this.config.stream.on(\n 'resize', this.resizeListener,\n )\n }\n\n this.render()\n }\n\n setTicketStatus (num:number, status:TicketStatus):void {\n if (!this.wave) return\n\n const ticket = this.wave.tickets.find(t => t.num === num)\n if (ticket) {\n ticket.status = status\n\n if (status === 'in-progress') {\n const ticketIndex = this.wave.tickets.indexOf(ticket)\n const availableRows = this.getAvailableRows()\n const totalTickets = this.wave.tickets.length\n\n if (ticketIndex < this.viewportStart) {\n this.viewportStart = ticketIndex\n } else if (ticketIndex >= this.viewportStart + availableRows) {\n // offset by 3 to keep context visible below\n // the active ticket\n this.viewportStart = ticketIndex - availableRows + 3\n }\n this.viewportStart = Math.max(\n 0,\n Math.min(this.viewportStart, totalTickets - 1),\n )\n }\n\n this.render()\n }\n }\n\n log (message:string):void {\n for (const line of message.split('\\n')) {\n this.messages.push(line)\n }\n while (this.messages.length > MAX_MESSAGE_BUFFER) {\n this.messages.shift()\n }\n this.render()\n }\n\n cleanup ():void {\n // Remove resize listener if it exists\n if (this.resizeListener && this.config.stream.isTTY) {\n this.config.stream.removeListener('resize', this.resizeListener)\n }\n this.config.stream.write(ansi.cursorShow())\n }\n\n private render ():void {\n if (!this.wave) return\n\n let output = ''\n\n if (this.renderedLines > 0) {\n output += ansi.eraseLines(this.renderedLines + 1)\n }\n\n const doneInWave = this.wave.tickets.filter(\n t => t.status === 'done',\n ).length\n const completed = doneInWave + (this.progress?.completed ?? 0)\n const total = this.progress?.total ?? this.wave.tickets.length\n const pct = total === 0 ? 0 : Math.round((completed / total) * 100)\n const width = Math.max(\n 1,\n (this.config.stream.columns ?? DEFAULT_COLUMNS) - 1,\n )\n\n output += `Wave ${this.wave.waveNum}/` +\n `${this.wave.totalWaves} - ` +\n `${pct}% complete\\n`\n\n // Pagination logic\n const availableRows = this.getAvailableRows()\n const tickets:ReadonlyArray<DisplayTicket> = [\n ...this.wave.tickets,\n ...this.remaining,\n ]\n let renderedTicketCount = 0\n\n if (tickets.length <= availableRows) {\n // All tickets fit, no pagination\n for (const ticket of tickets) {\n const dot = renderDot(ticket.status)\n const name = fitLine(ticket.filename, width - 2)\n output += `${dot} ${name}\\n`\n }\n renderedTicketCount = tickets.length\n } else {\n // Need pagination -- iterative clamp\n this.viewportStart = Math.max(\n 0,\n Math.min(\n this.viewportStart,\n tickets.length - 1,\n ),\n )\n\n let hasMoreAbove = this.viewportStart > 0\n let tentativeCount =\n availableRows - (hasMoreAbove ? 1 : 0)\n let hasMoreBelow =\n this.viewportStart + tentativeCount <\n tickets.length\n let ticketsToShow = Math.max(\n 0,\n tentativeCount - (hasMoreBelow ? 1 : 0),\n )\n\n const maxStart = Math.max(\n 0,\n tickets.length - Math.max(1, ticketsToShow),\n )\n if (this.viewportStart > maxStart) {\n this.viewportStart = maxStart\n hasMoreAbove = this.viewportStart > 0\n tentativeCount = availableRows - (hasMoreAbove ? 1 : 0)\n hasMoreBelow = (\n this.viewportStart +\n tentativeCount < tickets.length\n )\n ticketsToShow = Math.max(\n 0,\n tentativeCount - (hasMoreBelow ? 1 : 0)\n )\n }\n\n // Show \"more above\" indicator if needed\n if (hasMoreAbove) {\n const moreCount = this.viewportStart\n output += ` ... ${moreCount} more above\\n`\n }\n\n // Get visible tickets and render them\n const visibleTickets = tickets.slice(\n this.viewportStart,\n this.viewportStart + ticketsToShow,\n )\n\n for (const ticket of visibleTickets) {\n const dot = renderDot(ticket.status)\n const name = fitLine(ticket.filename, width - 2)\n output += `${dot} ${name}\\n`\n }\n\n // Show \"more below\" indicator if needed\n if (hasMoreBelow) {\n const moreCount = (\n tickets.length -\n (this.viewportStart + ticketsToShow)\n )\n output += ` ... ${moreCount} more below\\n`\n }\n\n renderedTicketCount = (hasMoreAbove ? 1 : 0) +\n visibleTickets.length +\n (hasMoreBelow ? 1 : 0)\n }\n\n output += '-'.repeat(Math.min(SEPARATOR_WIDTH, width)) + '\\n'\n\n const termHeight = this.config.stream.rows ?? 24\n const logRows = Math.max(\n 0,\n termHeight - 3 - renderedTicketCount,\n )\n const visibleMessages = this.messages.slice(-logRows)\n for (const msg of visibleMessages) {\n output += `${fitLine(msg, width)}\\n`\n }\n const emptyRows = logRows - visibleMessages.length\n for (let i = 0; i < emptyRows; i++) {\n output += '\\n'\n }\n\n this.renderedLines = 2 + renderedTicketCount + logRows\n this.config.stream.write(output)\n }\n}\n\nexport class PlainDisplay implements Display {\n startWave (\n waveNum:number,\n _totalWaves:number,\n tickets:ReadonlyArray<{ num:number; filename:string }>,\n progress?:RunProgress,\n _remaining?:ReadonlyArray<{\n num:number\n filename:string\n }>,\n ):void {\n const pct = progress && progress.total > 0 ?\n ` (${progress.completed}/${progress.total} done)` :\n ''\n console.log(\n `\\nWave ${waveNum}${pct}: ` +\n tickets.map(t => t.filename).join(', ')\n )\n }\n\n setTicketStatus (_num:number, _status:TicketStatus):void {\n // no-op: plain mode reports via log()\n }\n\n log (message:string):void {\n console.log(message)\n }\n\n cleanup ():void {\n // no-op\n }\n}\n\nexport function createDisplay (stream?:NodeJS.WriteStream):Display {\n const s = stream ?? process.stdout\n if (s.isTTY) {\n return new StatusDisplay({ stream:s })\n }\n\n return new PlainDisplay()\n}\n"],
|
|
5
|
-
"mappings": ";;AAmDA,MAAM,MAAM;AAEL,MAAM,OAAO;AAAA,EAChB,SAAU,GAAiB;AACvB,WAAO,GAAG,GAAG,GAAG,CAAC;AAAA,EACrB;AAAA,EACA,SAAU,KAAmB;AACzB,WAAO,GAAG,GAAG,GAAG,GAAG;AAAA,EACvB;AAAA,EACA,YAAoB;AAChB,WAAO,GAAG,GAAG;AAAA,EACjB;AAAA,EACA,WAAY,GAAiB;AACzB,QAAI,MAAM;AACV,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AACxB,aAAO,KAAK,UAAU;AACtB,UAAI,IAAI,IAAI,EAAG,QAAO,KAAK,SAAS,CAAC;AAAA,IACzC;AACA,WAAO;AAAA,EACX;AAAA,EACA,aAAqB;AACjB,WAAO,GAAG,GAAG;AAAA,EACjB;AAAA,EACA,aAAqB;AACjB,WAAO,GAAG,GAAG;AAAA,EACjB;AACJ;AAEO,MAAM,QAAQ;AAAA,EACjB,MAAO,MAAoB;AACvB,WAAO,GAAG,GAAG,MAAM,IAAI,GAAG,GAAG;AAAA,EACjC;AAAA,EACA,OAAQ,MAAoB;AACxB,WAAO,GAAG,GAAG,MAAM,IAAI,GAAG,GAAG;AAAA,EACjC;AAAA,EACA,IAAK,MAAoB;AACrB,WAAO,GAAG,GAAG,MAAM,IAAI,GAAG,GAAG;AAAA,EACjC;AAAA,EACA,IAAK,MAAoB;AACrB,WAAO,GAAG,GAAG,KAAK,IAAI,GAAG,GAAG;AAAA,EAChC;AACJ;AAEA,MAAM,aAAa;AACnB,MAAM,YAAY;AAClB,MAAM,kBAAkB;AACxB,MAAM,kBAAkB;AACxB,MAAM,eAAe;AACrB,MAAM,qBAAqB;AAG3B,MAAM,UAAU;AAOT,SAAS,QAAS,MAAa,OAAqB;AACvD,QAAM,UAAU,KAAK,QAAQ,SAAS,EAAE;AACxC,MAAI,QAAQ,UAAU,MAAO,QAAO;AACpC,SAAO,QAAQ,MAAM,GAAG,KAAK;AACjC;AAJgB;
|
|
4
|
+
"sourcesContent": ["export type TicketStatus =\n | 'pending'\n | 'in-progress'\n | 'done'\n | 'failed'\n\nexport type DisplayTicket = {\n readonly num:number\n readonly filename:string\n status:TicketStatus\n}\n\nexport type WaveState = {\n readonly waveNum:number\n readonly totalWaves:number\n readonly tickets:Array<DisplayTicket>\n}\n\nexport type DisplayConfig = {\n readonly stream:NodeJS.WriteStream\n}\n\n/**\n * Progress across the whole run, not just the current wave.\n * `completed` counts tickets done before the wave started;\n * tickets that finish during the wave are added on top.\n */\nexport type RunProgress = {\n readonly completed:number\n readonly total:number\n}\n\nexport type Display = {\n startWave(\n waveNum:number,\n totalWaves:number,\n tickets:ReadonlyArray<{\n num:number\n filename:string\n }>,\n progress?:RunProgress,\n remaining?:ReadonlyArray<{\n num:number\n filename:string\n }>,\n ):void\n setTicketStatus(num:number, status:TicketStatus):void\n log(message:string):void\n cleanup():void\n}\n\nconst ESC = '\\x1b['\n\nexport const ansi = {\n cursorUp (n:number):string {\n return `${ESC}${n}A`\n },\n cursorTo (col:number):string {\n return `${ESC}${col}G`\n },\n eraseLine ():string {\n return `${ESC}2K`\n },\n eraseLines (n:number):string {\n let out = ''\n for (let i = 0; i < n; i++) {\n out += ansi.eraseLine()\n if (i < n - 1) out += ansi.cursorUp(1)\n }\n return out\n },\n cursorHide ():string {\n return `${ESC}?25l`\n },\n cursorShow ():string {\n return `${ESC}?25h`\n },\n} as const\n\nexport const color = {\n green (text:string):string {\n return `${ESC}32m${text}${ESC}0m`\n },\n yellow (text:string):string {\n return `${ESC}33m${text}${ESC}0m`\n },\n red (text:string):string {\n return `${ESC}31m${text}${ESC}0m`\n },\n dim (text:string):string {\n return `${ESC}2m${text}${ESC}0m`\n },\n} as const\n\nconst FILLED_DOT = '\u25CF'\nconst EMPTY_DOT = '\u25CB'\nconst SEPARATOR_WIDTH = 50\nconst DEFAULT_COLUMNS = 80\nconst MIN_LOG_ROWS = 1\nconst MAX_MESSAGE_BUFFER = 200\n\n// eslint-disable-next-line no-control-regex\nconst ANSI_RE = /\\x1b\\[[0-9;?]*[A-Za-z]/g\n\n/**\n * Clamp a line to `width` visible columns so it can never wrap.\n * A wrapped line occupies more terminal rows than `render`\n * counts, which leaves stale rows behind on the next erase.\n */\nexport function fitLine (line:string, width:number):string {\n const visible = line.replace(ANSI_RE, '')\n if (visible.length <= width) return line\n return visible.slice(0, width)\n}\n\n/**\n * Split a line into rows of at most `width` visible columns,\n * the way a terminal would wrap it. Short lines keep their\n * ANSI styling; long lines are wrapped on visible text only,\n * so `render` can count every row it emits.\n */\nexport function wrapLine (line:string, width:number):Array<string> {\n const visible = line.replace(ANSI_RE, '')\n if (visible.length <= width) return [line]\n const rows:Array<string> = []\n for (let i = 0; i < visible.length; i += width) {\n rows.push(visible.slice(i, i + width))\n }\n return rows\n}\n\nexport function renderDot (status:TicketStatus):string {\n switch (status) {\n case 'pending':\n return color.dim(EMPTY_DOT)\n case 'in-progress':\n return color.yellow(FILLED_DOT)\n case 'done':\n return color.green(FILLED_DOT)\n case 'failed':\n return color.red(FILLED_DOT)\n }\n}\n\nexport class StatusDisplay implements Display {\n private wave:WaveState|null = null\n private progress:RunProgress|null = null\n private remaining:Array<DisplayTicket> = []\n private messages:Array<string> = []\n private renderedLines:number = 0\n private config:DisplayConfig\n private viewportStart:number = 0\n private resizeListener:(() => void)|null = null\n\n constructor (config?:Readonly<Partial<DisplayConfig>>) {\n this.config = {\n stream:config?.stream ?? process.stdout,\n }\n }\n\n private getAvailableRows ():number {\n const termHeight = this.config.stream.rows ?? 24\n const overhead = 2 + MIN_LOG_ROWS\n const available = termHeight - overhead\n return Math.max(1, available)\n }\n\n startWave (\n waveNum:number,\n totalWaves:number,\n tickets:Array<Readonly<{ num:number; filename:string }>>,\n progress?:RunProgress,\n remaining?:ReadonlyArray<{\n num:number\n filename:string\n }>,\n ):void {\n this.wave = {\n waveNum,\n totalWaves,\n tickets:tickets.map(t => ({\n ...t,\n status:'pending' as const,\n })),\n }\n this.remaining = (remaining ?? []).map(t => ({\n ...t,\n status:'pending' as const,\n }))\n this.progress = progress ?? null\n this.messages = []\n this.viewportStart = 0\n this.config.stream.write(ansi.cursorHide())\n\n if (this.config.stream.isTTY) {\n if (this.resizeListener) {\n this.config.stream.removeListener(\n 'resize', this.resizeListener,\n )\n }\n this.resizeListener = () => this.render()\n this.config.stream.on(\n 'resize', this.resizeListener,\n )\n }\n\n this.render()\n }\n\n setTicketStatus (num:number, status:TicketStatus):void {\n if (!this.wave) return\n\n const ticket = this.wave.tickets.find(t => t.num === num)\n if (ticket) {\n ticket.status = status\n\n if (status === 'in-progress') {\n const ticketIndex = this.wave.tickets.indexOf(ticket)\n const availableRows = this.getAvailableRows()\n const totalTickets = this.wave.tickets.length\n\n if (ticketIndex < this.viewportStart) {\n this.viewportStart = ticketIndex\n } else if (ticketIndex >= this.viewportStart + availableRows) {\n // offset by 3 to keep context visible below\n // the active ticket\n this.viewportStart = ticketIndex - availableRows + 3\n }\n this.viewportStart = Math.max(\n 0,\n Math.min(this.viewportStart, totalTickets - 1),\n )\n }\n\n this.render()\n }\n }\n\n log (message:string):void {\n for (const line of message.split('\\n')) {\n this.messages.push(line)\n }\n while (this.messages.length > MAX_MESSAGE_BUFFER) {\n this.messages.shift()\n }\n this.render()\n }\n\n cleanup ():void {\n // Remove resize listener if it exists\n if (this.resizeListener && this.config.stream.isTTY) {\n this.config.stream.removeListener('resize', this.resizeListener)\n }\n this.config.stream.write(ansi.cursorShow())\n }\n\n private render ():void {\n if (!this.wave) return\n\n let output = ''\n\n if (this.renderedLines > 0) {\n output += ansi.eraseLines(this.renderedLines + 1)\n }\n\n const doneInWave = this.wave.tickets.filter(\n t => t.status === 'done',\n ).length\n const completed = doneInWave + (this.progress?.completed ?? 0)\n const total = this.progress?.total ?? this.wave.tickets.length\n const pct = total === 0 ? 0 : Math.round((completed / total) * 100)\n const width = Math.max(\n 1,\n (this.config.stream.columns ?? DEFAULT_COLUMNS) - 1,\n )\n\n output += `Wave ${this.wave.waveNum}/` +\n `${this.wave.totalWaves} - ` +\n `${pct}% complete\\n`\n\n // Pagination logic\n const availableRows = this.getAvailableRows()\n const tickets:ReadonlyArray<DisplayTicket> = [\n ...this.wave.tickets,\n ...this.remaining,\n ]\n let renderedTicketCount = 0\n\n if (tickets.length <= availableRows) {\n // All tickets fit, no pagination\n for (const ticket of tickets) {\n const dot = renderDot(ticket.status)\n const name = fitLine(ticket.filename, width - 2)\n output += `${dot} ${name}\\n`\n }\n renderedTicketCount = tickets.length\n } else {\n // Need pagination -- iterative clamp\n this.viewportStart = Math.max(\n 0,\n Math.min(\n this.viewportStart,\n tickets.length - 1,\n ),\n )\n\n let hasMoreAbove = this.viewportStart > 0\n let tentativeCount =\n availableRows - (hasMoreAbove ? 1 : 0)\n let hasMoreBelow =\n this.viewportStart + tentativeCount <\n tickets.length\n let ticketsToShow = Math.max(\n 0,\n tentativeCount - (hasMoreBelow ? 1 : 0),\n )\n\n const maxStart = Math.max(\n 0,\n tickets.length - Math.max(1, ticketsToShow),\n )\n if (this.viewportStart > maxStart) {\n this.viewportStart = maxStart\n hasMoreAbove = this.viewportStart > 0\n tentativeCount = availableRows - (hasMoreAbove ? 1 : 0)\n hasMoreBelow = (\n this.viewportStart +\n tentativeCount < tickets.length\n )\n ticketsToShow = Math.max(\n 0,\n tentativeCount - (hasMoreBelow ? 1 : 0)\n )\n }\n\n // Show \"more above\" indicator if needed\n if (hasMoreAbove) {\n const moreCount = this.viewportStart\n output += ` ... ${moreCount} more above\\n`\n }\n\n // Get visible tickets and render them\n const visibleTickets = tickets.slice(\n this.viewportStart,\n this.viewportStart + ticketsToShow,\n )\n\n for (const ticket of visibleTickets) {\n const dot = renderDot(ticket.status)\n const name = fitLine(ticket.filename, width - 2)\n output += `${dot} ${name}\\n`\n }\n\n // Show \"more below\" indicator if needed\n if (hasMoreBelow) {\n const moreCount = (\n tickets.length -\n (this.viewportStart + ticketsToShow)\n )\n output += ` ... ${moreCount} more below\\n`\n }\n\n renderedTicketCount = (hasMoreAbove ? 1 : 0) +\n visibleTickets.length +\n (hasMoreBelow ? 1 : 0)\n }\n\n output += '-'.repeat(Math.min(SEPARATOR_WIDTH, width)) + '\\n'\n\n const termHeight = this.config.stream.rows ?? 24\n const logRows = Math.max(\n 0,\n termHeight - 3 - renderedTicketCount,\n )\n const wrapped = this.messages.flatMap(\n msg => wrapLine(msg, width),\n )\n const visibleMessages = wrapped.slice(-logRows)\n for (const row of visibleMessages) {\n output += `${row}\\n`\n }\n const emptyRows = logRows - visibleMessages.length\n for (let i = 0; i < emptyRows; i++) {\n output += '\\n'\n }\n\n this.renderedLines = 2 + renderedTicketCount + logRows\n this.config.stream.write(output)\n }\n}\n\nexport class PlainDisplay implements Display {\n startWave (\n waveNum:number,\n _totalWaves:number,\n tickets:ReadonlyArray<{ num:number; filename:string }>,\n progress?:RunProgress,\n _remaining?:ReadonlyArray<{\n num:number\n filename:string\n }>,\n ):void {\n const pct = progress && progress.total > 0 ?\n ` (${progress.completed}/${progress.total} done)` :\n ''\n console.log(\n `\\nWave ${waveNum}${pct}: ` +\n tickets.map(t => t.filename).join(', ')\n )\n }\n\n setTicketStatus (_num:number, _status:TicketStatus):void {\n // no-op: plain mode reports via log()\n }\n\n log (message:string):void {\n console.log(message)\n }\n\n cleanup ():void {\n // no-op\n }\n}\n\nexport function createDisplay (stream?:NodeJS.WriteStream):Display {\n const s = stream ?? process.stdout\n if (s.isTTY) {\n return new StatusDisplay({ stream:s })\n }\n\n return new PlainDisplay()\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/index.js
CHANGED
|
@@ -242,24 +242,6 @@ function getCurrentBranch() {
|
|
|
242
242
|
return stdout.trim();
|
|
243
243
|
}
|
|
244
244
|
__name(getCurrentBranch, "getCurrentBranch");
|
|
245
|
-
function branchExistsSync(repoRoot, branchName) {
|
|
246
|
-
try {
|
|
247
|
-
execFileSync(
|
|
248
|
-
"git",
|
|
249
|
-
[
|
|
250
|
-
"show-ref",
|
|
251
|
-
"--verify",
|
|
252
|
-
"--quiet",
|
|
253
|
-
`refs/heads/${branchName}`
|
|
254
|
-
],
|
|
255
|
-
{ cwd: repoRoot }
|
|
256
|
-
);
|
|
257
|
-
return true;
|
|
258
|
-
} catch {
|
|
259
|
-
return false;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
__name(branchExistsSync, "branchExistsSync");
|
|
263
245
|
async function branchExists(repoRoot, branchName) {
|
|
264
246
|
try {
|
|
265
247
|
await execFileAsync(
|
|
@@ -273,19 +255,14 @@ async function branchExists(repoRoot, branchName) {
|
|
|
273
255
|
}
|
|
274
256
|
}
|
|
275
257
|
__name(branchExists, "branchExists");
|
|
276
|
-
function buildBranchName(
|
|
258
|
+
function buildBranchName(repoName, ticket) {
|
|
277
259
|
const num = String(ticket.num).padStart(2, "0");
|
|
278
|
-
|
|
279
|
-
return `${repoName}${sep}${num}-${ticket.slug}`;
|
|
260
|
+
return `${repoName}--${num}-${ticket.slug}`;
|
|
280
261
|
}
|
|
281
262
|
__name(buildBranchName, "buildBranchName");
|
|
282
263
|
async function createWorktreeNow(opts) {
|
|
283
264
|
const { repoRoot, repoName, baseBranch, ticket } = opts;
|
|
284
|
-
const branchName = buildBranchName(
|
|
285
|
-
repoRoot,
|
|
286
|
-
repoName,
|
|
287
|
-
ticket
|
|
288
|
-
);
|
|
265
|
+
const branchName = buildBranchName(repoName, ticket);
|
|
289
266
|
const worktreePath = join2(
|
|
290
267
|
repoRoot,
|
|
291
268
|
"..",
|
|
@@ -821,6 +798,16 @@ function fitLine(line, width) {
|
|
|
821
798
|
return visible.slice(0, width);
|
|
822
799
|
}
|
|
823
800
|
__name(fitLine, "fitLine");
|
|
801
|
+
function wrapLine(line, width) {
|
|
802
|
+
const visible = line.replace(ANSI_RE, "");
|
|
803
|
+
if (visible.length <= width) return [line];
|
|
804
|
+
const rows = [];
|
|
805
|
+
for (let i = 0; i < visible.length; i += width) {
|
|
806
|
+
rows.push(visible.slice(i, i + width));
|
|
807
|
+
}
|
|
808
|
+
return rows;
|
|
809
|
+
}
|
|
810
|
+
__name(wrapLine, "wrapLine");
|
|
824
811
|
function renderDot(status) {
|
|
825
812
|
switch (status) {
|
|
826
813
|
case "pending":
|
|
@@ -1015,9 +1002,12 @@ var StatusDisplay = class {
|
|
|
1015
1002
|
0,
|
|
1016
1003
|
termHeight - 3 - renderedTicketCount
|
|
1017
1004
|
);
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
|
|
1005
|
+
const wrapped = this.messages.flatMap(
|
|
1006
|
+
(msg) => wrapLine(msg, width)
|
|
1007
|
+
);
|
|
1008
|
+
const visibleMessages = wrapped.slice(-logRows);
|
|
1009
|
+
for (const row of visibleMessages) {
|
|
1010
|
+
output += `${row}
|
|
1021
1011
|
`;
|
|
1022
1012
|
}
|
|
1023
1013
|
const emptyRows = logRows - visibleMessages.length;
|
|
@@ -1952,7 +1942,6 @@ function dryRunIsolated(opts) {
|
|
|
1952
1942
|
for (const t of ready) {
|
|
1953
1943
|
const num = String(t.num).padStart(2, "0");
|
|
1954
1944
|
const branchName = buildBranchName(
|
|
1955
|
-
opts.repoRoot,
|
|
1956
1945
|
opts.repoName,
|
|
1957
1946
|
t
|
|
1958
1947
|
);
|
|
@@ -1996,6 +1985,9 @@ Test command: ${opts.testCmd}`
|
|
|
1996
1985
|
console.log(
|
|
1997
1986
|
`Retry delay: ${opts.retryDelay ?? 0}s`
|
|
1998
1987
|
);
|
|
1988
|
+
console.log(
|
|
1989
|
+
`Concurrency: ${opts.concurrency ?? DEFAULT_CONCURRENCY}`
|
|
1990
|
+
);
|
|
1999
1991
|
console.log(
|
|
2000
1992
|
`
|
|
2001
1993
|
Merge strategy: ${strategyDesc}`
|
|
@@ -2117,6 +2109,35 @@ async function respawnAndRetryMerge(opts) {
|
|
|
2117
2109
|
__name(respawnAndRetryMerge, "respawnAndRetryMerge");
|
|
2118
2110
|
var DEFAULT_RETRIES = 3;
|
|
2119
2111
|
var DEFAULT_RETRY_DELAY = 30;
|
|
2112
|
+
var DEFAULT_CONCURRENCY = 5;
|
|
2113
|
+
async function runPool(items, limit, fn) {
|
|
2114
|
+
const size = Math.max(1, Math.floor(limit));
|
|
2115
|
+
const results = new Array(
|
|
2116
|
+
items.length
|
|
2117
|
+
);
|
|
2118
|
+
let next = 0;
|
|
2119
|
+
async function worker() {
|
|
2120
|
+
while (next < items.length) {
|
|
2121
|
+
const i = next++;
|
|
2122
|
+
try {
|
|
2123
|
+
results[i] = {
|
|
2124
|
+
status: "fulfilled",
|
|
2125
|
+
value: await fn(items[i])
|
|
2126
|
+
};
|
|
2127
|
+
} catch (reason) {
|
|
2128
|
+
results[i] = { status: "rejected", reason };
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
__name(worker, "worker");
|
|
2133
|
+
const workers = Array.from(
|
|
2134
|
+
{ length: Math.min(size, items.length) },
|
|
2135
|
+
() => worker()
|
|
2136
|
+
);
|
|
2137
|
+
await Promise.all(workers);
|
|
2138
|
+
return results;
|
|
2139
|
+
}
|
|
2140
|
+
__name(runPool, "runPool");
|
|
2120
2141
|
function retryDelayFor(base, attempt) {
|
|
2121
2142
|
return base * 2 ** (attempt - 2);
|
|
2122
2143
|
}
|
|
@@ -2301,11 +2322,12 @@ function runProgress(tickets) {
|
|
|
2301
2322
|
};
|
|
2302
2323
|
}
|
|
2303
2324
|
__name(runProgress, "runProgress");
|
|
2304
|
-
async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, testCmd, display, maxTurns, effort, retries, retryDelay) {
|
|
2325
|
+
async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, testCmd, display, maxTurns, effort, retries, retryDelay, concurrency) {
|
|
2305
2326
|
mkdirSync2(logsDir, { recursive: true });
|
|
2306
2327
|
const d = display ?? createDisplay();
|
|
2307
2328
|
const maxAttempts = (retries ?? DEFAULT_RETRIES) + 1;
|
|
2308
2329
|
const delayBase = retryDelay ?? 0;
|
|
2330
|
+
const poolSize = concurrency ?? DEFAULT_CONCURRENCY;
|
|
2309
2331
|
const providerName = basename2(agentCmd);
|
|
2310
2332
|
const isKnown = providerName in EFFORT_LADDERS;
|
|
2311
2333
|
let totalWaves = 0;
|
|
@@ -2336,8 +2358,10 @@ async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, te
|
|
|
2336
2358
|
filename: t.filename
|
|
2337
2359
|
}))
|
|
2338
2360
|
);
|
|
2339
|
-
const results = await
|
|
2340
|
-
ready
|
|
2361
|
+
const results = await runPool(
|
|
2362
|
+
ready,
|
|
2363
|
+
poolSize,
|
|
2364
|
+
async (t) => {
|
|
2341
2365
|
const prompt = renderPrompt(
|
|
2342
2366
|
specBody,
|
|
2343
2367
|
t,
|
|
@@ -2391,7 +2415,7 @@ async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, te
|
|
|
2391
2415
|
});
|
|
2392
2416
|
}
|
|
2393
2417
|
return { ticket: t, result };
|
|
2394
|
-
}
|
|
2418
|
+
}
|
|
2395
2419
|
);
|
|
2396
2420
|
let waveCompleted = 0;
|
|
2397
2421
|
for (const r of results) {
|
|
@@ -2444,6 +2468,7 @@ async function runWavesIsolated(opts) {
|
|
|
2444
2468
|
const d = opts.display ?? createDisplay();
|
|
2445
2469
|
const retries = opts.retries ?? DEFAULT_RETRIES;
|
|
2446
2470
|
const retryDelay = opts.retryDelay ?? 0;
|
|
2471
|
+
const poolSize = opts.concurrency ?? DEFAULT_CONCURRENCY;
|
|
2447
2472
|
let totalWaves = 0;
|
|
2448
2473
|
for (const _ of simulateWaves(opts.tickets)) totalWaves++;
|
|
2449
2474
|
const repoRoot = getRepoRoot();
|
|
@@ -2495,8 +2520,10 @@ async function runWavesIsolated(opts) {
|
|
|
2495
2520
|
filename: t.filename
|
|
2496
2521
|
}))
|
|
2497
2522
|
);
|
|
2498
|
-
const
|
|
2499
|
-
ready
|
|
2523
|
+
const settled = await runPool(
|
|
2524
|
+
ready,
|
|
2525
|
+
poolSize,
|
|
2526
|
+
(t) => runTicketAttempts({
|
|
2500
2527
|
ticket: t,
|
|
2501
2528
|
repoRoot,
|
|
2502
2529
|
repoName,
|
|
@@ -2512,7 +2539,10 @@ async function runWavesIsolated(opts) {
|
|
|
2512
2539
|
retries,
|
|
2513
2540
|
retryDelay,
|
|
2514
2541
|
d
|
|
2515
|
-
})
|
|
2542
|
+
})
|
|
2543
|
+
);
|
|
2544
|
+
const outcomes = settled.map(
|
|
2545
|
+
(r) => r.status === "fulfilled" ? r.value : { kind: "error", error: r.reason }
|
|
2516
2546
|
);
|
|
2517
2547
|
let waveCompleted = 0;
|
|
2518
2548
|
const successfulBranches = [];
|
|
@@ -2948,6 +2978,10 @@ yargs(hideBin(process.argv)).scriptName("nightralph").usage("$0 <command>").comm
|
|
|
2948
2978
|
type: "number",
|
|
2949
2979
|
default: DEFAULT_RETRY_DELAY,
|
|
2950
2980
|
describe: "Seconds to wait before the first retry; doubles each further retry. 0 disables the wait."
|
|
2981
|
+
}).option("concurrency", {
|
|
2982
|
+
type: "number",
|
|
2983
|
+
default: DEFAULT_CONCURRENCY,
|
|
2984
|
+
describe: "Max agents running at once within a wave."
|
|
2951
2985
|
}),
|
|
2952
2986
|
async (argv) => {
|
|
2953
2987
|
await runExecute({
|
|
@@ -2961,7 +2995,8 @@ yargs(hideBin(process.argv)).scriptName("nightralph").usage("$0 <command>").comm
|
|
|
2961
2995
|
maxTurns: argv.maxTurns ?? argv.turns,
|
|
2962
2996
|
effort: argv.effort,
|
|
2963
2997
|
retries: argv.retries,
|
|
2964
|
-
retryDelay: argv.retryDelay
|
|
2998
|
+
retryDelay: argv.retryDelay,
|
|
2999
|
+
concurrency: argv.concurrency
|
|
2965
3000
|
});
|
|
2966
3001
|
}
|
|
2967
3002
|
).strict().demandCommand(1, "Specify a command or provider").help().parse();
|
|
@@ -3034,19 +3069,18 @@ async function runExecute(argv) {
|
|
|
3034
3069
|
);
|
|
3035
3070
|
if (argv.dryRun) {
|
|
3036
3071
|
if (isGitRepo()) {
|
|
3037
|
-
const repoRoot = getRepoRoot();
|
|
3038
3072
|
const repoName = getRepoName();
|
|
3039
3073
|
dryRunIsolated({
|
|
3040
3074
|
tickets,
|
|
3041
3075
|
specBody,
|
|
3042
|
-
repoRoot,
|
|
3043
3076
|
repoName,
|
|
3044
3077
|
conflictStrategy,
|
|
3045
3078
|
progressPath,
|
|
3046
3079
|
testCmd,
|
|
3047
3080
|
effort: argv.effort,
|
|
3048
3081
|
retries: argv.retries,
|
|
3049
|
-
retryDelay: argv.retryDelay
|
|
3082
|
+
retryDelay: argv.retryDelay,
|
|
3083
|
+
concurrency: argv.concurrency
|
|
3050
3084
|
});
|
|
3051
3085
|
} else {
|
|
3052
3086
|
dryRun(tickets, specBody, testCmd);
|
|
@@ -3078,7 +3112,8 @@ async function runExecute(argv) {
|
|
|
3078
3112
|
maxTurns: argv.maxTurns,
|
|
3079
3113
|
effort: argv.effort,
|
|
3080
3114
|
retries: argv.retries,
|
|
3081
|
-
retryDelay: argv.retryDelay
|
|
3115
|
+
retryDelay: argv.retryDelay,
|
|
3116
|
+
concurrency: argv.concurrency
|
|
3082
3117
|
});
|
|
3083
3118
|
} else {
|
|
3084
3119
|
result = await runWaves(
|
|
@@ -3093,7 +3128,8 @@ async function runExecute(argv) {
|
|
|
3093
3128
|
argv.maxTurns,
|
|
3094
3129
|
argv.effort,
|
|
3095
3130
|
argv.retries,
|
|
3096
|
-
argv.retryDelay
|
|
3131
|
+
argv.retryDelay,
|
|
3132
|
+
argv.concurrency
|
|
3097
3133
|
);
|
|
3098
3134
|
}
|
|
3099
3135
|
if (!result.allDone) {
|