@irythill/orq 0.7.0 → 0.9.0
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 +40 -11
- package/README.pt-BR.md +40 -11
- package/dist/commands/claim.js +3 -1
- package/dist/commands/completion.js +3 -2
- package/dist/commands/conductor.js +123 -33
- package/dist/commands/gc.js +18 -13
- package/dist/commands/instructions.js +54 -12
- package/dist/commands/pick.js +2 -0
- package/dist/commands/setup.js +19 -6
- package/dist/harness.js +69 -0
- package/dist/install.js +24 -8
- package/dist/pick.js +57 -42
- package/dist/suggest.js +1 -1
- package/dist/testrun.js +2 -0
- package/dist/worktree-processes.js +102 -0
- package/package.json +1 -1
- package/skills/issue/SKILL.md +19 -13
- package/skills/orq-setup/SKILL.md +3 -3
- package/templates/conductor.instructions.md +12 -9
- package/templates/workspace.CLAUDE.md +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# orq — orchestrating coding agents
|
|
6
6
|
|
|
7
|
-
You describe the work in a Plane issue. A **conductor** (
|
|
7
|
+
You describe the work in a Plane issue. A **conductor** (any agent that can run commands) reads it, picks the right agent for
|
|
8
8
|
each step, dispatches the **implementer**, the **tests** and a **cross-vendor review** (each in its own tab or window
|
|
9
9
|
in [herdr](https://herdr.dev) or tmux, or in the background), and hands the issue back *In Review* with a summary in
|
|
10
10
|
Plane. Merge, PR and deploy stay with you.
|
|
@@ -20,7 +20,7 @@ that lives in your config.
|
|
|
20
20
|
## How it works
|
|
21
21
|
|
|
22
22
|
```
|
|
23
|
-
You ── "/issue DEMO-123" ──► conductor (
|
|
23
|
+
You ── "/issue DEMO-123" ──► conductor (any agent CLI, in any terminal)
|
|
24
24
|
│ orq pick impl ──► which agent/model implements (config + history + quota)
|
|
25
25
|
├─► implementer orq agent own tab/window or background · worktree from origin/<base> · memory cap
|
|
26
26
|
├─► tests orq verify only the diff's tests · queue of one suite at a time
|
|
@@ -47,9 +47,9 @@ You ── "/issue DEMO-123" ──► conductor (Claude, in any terminal)
|
|
|
47
47
|
|
|
48
48
|
| Agent | CLI | Possible roles | Notes |
|
|
49
49
|
|---|---|---|---|
|
|
50
|
-
| Claude Code | `claude` | conductor, implement, review | several accounts (`claude-accounts`);
|
|
51
|
-
| Codex | `codex` | implement, review | sandbox without network: orq runs `git fetch` before a write run (`network = false`); quota from its session files (the free plan's 30-day window included) |
|
|
52
|
-
| OpenCode | `opencode` | implement, review | any provider (e.g. MiniMax); on MiniMax, quota from the Token Plan API with your key |
|
|
50
|
+
| Claude Code | `claude` | conductor, implement, review | several accounts (`claude-accounts`); Claude as conductor |
|
|
51
|
+
| Codex | `codex` | conductor, implement, review | sandbox without network: orq runs `git fetch` before a write run (`network = false`); quota from its session files (the free plan's 30-day window included) |
|
|
52
|
+
| OpenCode | `opencode` | conductor, implement, review | any provider (e.g. MiniMax); on MiniMax, quota from the Token Plan API with your key |
|
|
53
53
|
| Antigravity | `agy` | review (implement with an allowlist) | headless denies commands outside its allowlist: it reviews by reading the attached diff; it can't commit, so orq commits its write runs (`commit = false`) |
|
|
54
54
|
| GitHub Copilot | `copilot` | second opinion | small allowances on some plans: occasional use |
|
|
55
55
|
|
|
@@ -70,7 +70,7 @@ orq setup
|
|
|
70
70
|
`orq setup` shows what it found (git, terminal, how memory is capped, keyring, your shell), links orq's skills into
|
|
71
71
|
`~/.claude/skills/` and each account folder in `claude-accounts` (a junction on Windows; never over a folder of
|
|
72
72
|
yours), asks about your agents and plans, writes `~/.config/orq/config.toml` (`%APPDATA%\orq\config.toml` on Windows)
|
|
73
|
-
and prepares the folder that groups your repos for the conductor: `.orquestra/`, the map `
|
|
73
|
+
and prepares the folder that groups your repos for the conductor: `.orquestra/`, the map `ORQ.md` and the
|
|
74
74
|
`/issue` playbook in the conductor's format. Pieces on their own: `orq setup --install` (no questions),
|
|
75
75
|
`orq setup --workspace <folder> --conductor <agent>`. Inside Claude, **`/orq-setup`** does the same as a
|
|
76
76
|
conversation and also fills in the repo table. Plane: [docs/plane.md](docs/plane.md).
|
|
@@ -135,7 +135,7 @@ roles = ["impl", "fix", "review"]
|
|
|
135
135
|
quota = ["codex"] # name in agent-quota's output
|
|
136
136
|
[agents.codex.models.impl]
|
|
137
137
|
simple = "gpt-6-sol@low" # model@effort, per issue level
|
|
138
|
-
medium = "gpt-6-sol@medium"
|
|
138
|
+
medium = ["gpt-6-sol@medium", "gpt-6-luna@medium"] # several: each (agent, model) pair is scored on its own history
|
|
139
139
|
heavy = "gpt-6.1-sol@high"
|
|
140
140
|
[agents.codex.models.review]
|
|
141
141
|
simple = "gpt-6-sol@low" # reviews scale with the issue too
|
|
@@ -172,7 +172,7 @@ issue_pattern = "(?<![a-z0-9])(?<issue>[a-z][a-z0-9]*-\\d+)(?![a-z0-9])" # Bra
|
|
|
172
172
|
quota_week = 95 # ≥ 95% of the week → agent out
|
|
173
173
|
quota_5h = { simple = 90, medium = 85, heavy = 80 } # same for the 5h window, by task level
|
|
174
174
|
pacing_penalty = 0.3 # score penalty when usage at the current pace would pass the pick limit (5h by level, week: quota_week) by the reset (0 = off)
|
|
175
|
-
# [
|
|
175
|
+
# [conductor] headroom = 25 # points kept for the active conductor: children stop at limit − 25
|
|
176
176
|
[policy.weights]
|
|
177
177
|
quality = 0.40 # did it deliver? (impl: approved on the first review)
|
|
178
178
|
tokens = 0.45 # did it spend little?
|
|
@@ -187,7 +187,7 @@ rotation = 0.20 # penalty for having been picked recent
|
|
|
187
187
|
the least used.
|
|
188
188
|
2. **Scores:** `quality × delivery + tokens × economy + speed × quickness − rotation × recent picks`. The numbers come
|
|
189
189
|
from the history (`orq stats`); an agent with few runs uses the config's `prior` and gets an exploration bonus.
|
|
190
|
-
3.
|
|
190
|
+
3. **Reserved agents** (`reserve = true`, or the active conductor by default) receive a score penalty to keep quota for orchestration.
|
|
191
191
|
|
|
192
192
|
```bash
|
|
193
193
|
orq pick review --impl-agent codex --explain # every candidate's score and who was left out, and why
|
|
@@ -215,8 +215,37 @@ Whatever is already running keeps its language. Conductors always talk to you in
|
|
|
215
215
|
|
|
216
216
|
Open the conductor in any terminal, in your workspace folder (the one `orq setup` prepared):
|
|
217
217
|
```bash
|
|
218
|
-
cd ~/Code/<workspace> && orq conductor [account] [model] # any terminal: loads the conductor instructions, the workspace
|
|
218
|
+
cd ~/Code/<workspace> && orq conductor [agent] [account|-] [model] # any terminal: loads the conductor instructions, the workspace ORQ.md and the /issue skill
|
|
219
219
|
```
|
|
220
|
+
Choose the conductor with `orq setup --workspace <folder> --conductor codex`, then run `orq conductor` in that folder.
|
|
221
|
+
`orq conductor codex - <model>` overrides the choice for one session; `-r <id>` resumes it. Claude Code is also a
|
|
222
|
+
tested conductor; the old `orq conductor <Claude account> <model>` syntax still works. If an account and an agent
|
|
223
|
+
have the same name, use `--agent <name>` to select the agent explicitly. The `herdr-conductor` wrapper keeps opening Claude.
|
|
224
|
+
|
|
225
|
+
`ORQ.md` holds the workspace map. Setup preserves an existing `CLAUDE.md`, copying it into `ORQ.md` on migration;
|
|
226
|
+
otherwise it creates a `CLAUDE.md` pointer. `AGENTS.md` points to the map and holds the generated issue playbook,
|
|
227
|
+
preserving text outside the generated block. Edit `ORQ.md` after migration. With no background commands, the conductor
|
|
228
|
+
uses `orq wait` in the foreground. `orq handoff` still moves Claude accounts only.
|
|
229
|
+
|
|
230
|
+
Defaults live in `[roles.conductor]` (`agent`, `account`, `model`). Interactive templates are separate from worker
|
|
231
|
+
templates, keeping MCPs and normal permission prompts enabled. Override any built-in or add another CLI:
|
|
232
|
+
|
|
233
|
+
```toml
|
|
234
|
+
[agents.codex.conductor]
|
|
235
|
+
run = "codex [-m {model}] -C {dir} {prompt_text}"
|
|
236
|
+
resume = "codex resume {session} [-m {model}] -C {dir} {prompt_text}"
|
|
237
|
+
models = { fast = "your-model-id" }
|
|
238
|
+
accounts = { work = { CODEX_HOME = "/path/to/codex-work" } }
|
|
239
|
+
|
|
240
|
+
[conductor]
|
|
241
|
+
headroom = 25 # percentage points kept for whichever agent conducts
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Templates accept `{dir}`, `{instructions}` (file path), `{prompt_text}` (read-instructions prompt), `{model}`,
|
|
245
|
+
`{account}`, `{session}` and `{name}`. Account entries and `env` are environment-variable maps. The running conductor
|
|
246
|
+
exports `ORQ_CONDUCTOR_AGENT`, so `orq pick` applies the reserve penalty and `[conductor].headroom` to that agent,
|
|
247
|
+
even after a session override. Explicit `agents.<name>.reserve` and `headroom` take precedence.
|
|
248
|
+
|
|
220
249
|
With several conductors, give each a fixed name (`export ORQ_CONDUCTOR_ID=<name>`; in herdr, the workspace label is
|
|
221
250
|
used) so `orq claim` can tell them apart: the Claude session is named `conductor-<workspace>:<name>`, and
|
|
222
251
|
`orq claim -l` ends each line with that `session=`, so conductors can message each other.
|
|
@@ -279,7 +308,7 @@ Day to day, shortcuts and several issues at once: [docs/herdr.md](docs/herdr.md)
|
|
|
279
308
|
| `orq wait <prefix>...` | Blocks until those agents finish; prints `status=` and the answer file (any conductor, any terminal) |
|
|
280
309
|
| `orq verify ...` (`herdr-verify`) | Tests related to the diff, queued and memory-capped; failures that also fail on the base count as pre-existing |
|
|
281
310
|
| `orq board` (`herdr-board`) | Live board: queue, agents, conductors, quota, blocks, scoreboard |
|
|
282
|
-
| `orq conductor [account] [model] [-r <id>]` (`herdr-conductor`) | Opens the conductor
|
|
311
|
+
| `orq conductor [agent] [account|-] [model] [-r <id>]` (`herdr-conductor`) | Opens the configured conductor in the current terminal with its instructions, account and model; `-r` resumes a session |
|
|
283
312
|
| `orq claim` (`herdr-claim`) | Owner of each issue among conductors |
|
|
284
313
|
| `orq deps` (`herdr-deps`) | Links the worktree's dependencies to the main repo's (no reinstall) |
|
|
285
314
|
| `orq baseline ...` (`herdr-baseline`) | Manual snapshot of the tests failing on the base (outside the flow) |
|
package/README.pt-BR.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# orq — orquestrando agentes de código
|
|
6
6
|
|
|
7
|
-
Você descreve o trabalho numa issue do Plane. Um **conductor** (
|
|
7
|
+
Você descreve o trabalho numa issue do Plane. Um **conductor** (qualquer agente que execute comandos) lê a issue, escolhe o
|
|
8
8
|
agente certo para cada etapa, despacha o **implementador**, os **testes** e uma **review cross-vendor** (cada um
|
|
9
9
|
em sua própria aba ou janela no [herdr](https://herdr.dev) ou no tmux, ou em segundo plano), e devolve a issue
|
|
10
10
|
*In Review* com um resumo no Plane. Merge, PR e deploy continuam com você.
|
|
@@ -20,7 +20,7 @@ trabalhar. Tudo isso fica na sua config.
|
|
|
20
20
|
## Como funciona
|
|
21
21
|
|
|
22
22
|
```
|
|
23
|
-
You ── "/issue DEMO-123" ──► conductor (
|
|
23
|
+
You ── "/issue DEMO-123" ──► conductor (any agent CLI, in any terminal)
|
|
24
24
|
│ orq pick impl ──► which agent/model implements (config + history + quota)
|
|
25
25
|
├─► implementer orq agent own tab/window or background · worktree from origin/<base> · memory cap
|
|
26
26
|
├─► tests orq verify only the diff's tests · queue of one suite at a time
|
|
@@ -49,9 +49,9 @@ You ── "/issue DEMO-123" ──► conductor (Claude, in any terminal)
|
|
|
49
49
|
|
|
50
50
|
| Agente | CLI | Papéis possíveis | Observações |
|
|
51
51
|
|---|---|---|---|
|
|
52
|
-
| Claude Code | `claude` | conductor, implement, review | várias contas (`claude-accounts`);
|
|
53
|
-
| Codex | `codex` | implement, review | sandbox sem rede: o orq roda o `git fetch` antes de uma execução de escrita (`network = false`); cota lida dos arquivos de sessão (inclusive a janela de 30 dias do plano free) |
|
|
54
|
-
| OpenCode | `opencode` | implement, review | qualquer provider (ex.: MiniMax); na MiniMax, cota pela API do Token Plan com a sua chave |
|
|
52
|
+
| Claude Code | `claude` | conductor, implement, review | várias contas (`claude-accounts`); Claude como conductor |
|
|
53
|
+
| Codex | `codex` | conductor, implement, review | sandbox sem rede: o orq roda o `git fetch` antes de uma execução de escrita (`network = false`); cota lida dos arquivos de sessão (inclusive a janela de 30 dias do plano free) |
|
|
54
|
+
| OpenCode | `opencode` | conductor, implement, review | qualquer provider (ex.: MiniMax); na MiniMax, cota pela API do Token Plan com a sua chave |
|
|
55
55
|
| Antigravity | `agy` | review (implement com allowlist) | em modo headless nega comandos fora da allowlist: faz a review lendo o diff anexado; não consegue commitar, então o orq commita as execuções de escrita dele (`commit = false`) |
|
|
56
56
|
| GitHub Copilot | `copilot` | segunda opinião | cota pequena em alguns planos: uso ocasional |
|
|
57
57
|
|
|
@@ -73,7 +73,7 @@ O `orq setup` mostra o que encontrou (git, terminal, como a memória é limitada
|
|
|
73
73
|
do orq em `~/.claude/skills/` e em cada pasta de conta do `claude-accounts` (uma junction no Windows; nunca por cima
|
|
74
74
|
de uma pasta sua), pergunta sobre seus agentes e planos, escreve o `~/.config/orq/config.toml`
|
|
75
75
|
(`%APPDATA%\orq\config.toml` no Windows) e prepara a pasta que agrupa seus repos para o conductor: `.orquestra/`, o
|
|
76
|
-
mapa `
|
|
76
|
+
mapa `ORQ.md` e o playbook do `/issue` no formato do conductor. Partes avulsas: `orq setup --install` (sem
|
|
77
77
|
perguntas), `orq setup --workspace <pasta> --conductor <agente>`. Dentro do Claude, o **`/orq-setup`** faz o mesmo
|
|
78
78
|
como conversa e ainda preenche a tabela de repos. Plane: [docs/pt-BR/plane.md](docs/pt-BR/plane.md).
|
|
79
79
|
|
|
@@ -138,7 +138,7 @@ roles = ["impl", "fix", "review"]
|
|
|
138
138
|
quota = ["codex"] # name in agent-quota's output
|
|
139
139
|
[agents.codex.models.impl]
|
|
140
140
|
simple = "gpt-6-sol@low" # model@effort, per issue level
|
|
141
|
-
medium = "gpt-6-sol@medium"
|
|
141
|
+
medium = ["gpt-6-sol@medium", "gpt-6-luna@medium"] # several: each (agent, model) pair is scored on its own history
|
|
142
142
|
heavy = "gpt-6.1-sol@high"
|
|
143
143
|
[agents.codex.models.review]
|
|
144
144
|
simple = "gpt-6-sol@low" # reviews scale with the issue too
|
|
@@ -175,7 +175,7 @@ issue_pattern = "(?<![a-z0-9])(?<issue>[a-z][a-z0-9]*-\\d+)(?![a-z0-9])" # Nom
|
|
|
175
175
|
quota_week = 95 # ≥ 95% of the week → agent out
|
|
176
176
|
quota_5h = { simple = 90, medium = 85, heavy = 80 } # same for the 5h window, by task level
|
|
177
177
|
pacing_penalty = 0.3 # score penalty when usage at the current pace would pass the pick limit (5h by level, week: quota_week) by the reset (0 = off)
|
|
178
|
-
# [
|
|
178
|
+
# [conductor] headroom = 25 # pontos reservados para o conductor ativo: filhos param em limite − 25
|
|
179
179
|
[policy.weights]
|
|
180
180
|
quality = 0.40 # did it deliver? (impl: approved on the first review)
|
|
181
181
|
tokens = 0.45 # did it spend little?
|
|
@@ -190,7 +190,7 @@ rotation = 0.20 # penalty for having been picked recent
|
|
|
190
190
|
desempate, a menos usada.
|
|
191
191
|
2. **Pontuação:** `quality × delivery + tokens × economy + speed × quickness − rotation × recent picks`. Os números vêm
|
|
192
192
|
do histórico (`orq stats`); um agente com poucas execuções usa o `prior` da config e ganha um bônus de exploração.
|
|
193
|
-
3. **Agentes
|
|
193
|
+
3. **Agentes reservados** (`reserve = true`, ou o conductor ativo por padrão) recebem uma penalidade na pontuação para preservar cota para a orquestração.
|
|
194
194
|
|
|
195
195
|
```bash
|
|
196
196
|
orq pick review --impl-agent codex --explain # every candidate's score and who was left out, and why
|
|
@@ -217,8 +217,37 @@ O que já está rodando continua no idioma em que começou. Os conductors sempre
|
|
|
217
217
|
|
|
218
218
|
Abra o conductor em qualquer terminal, na pasta do seu workspace (a que o `orq setup` preparou):
|
|
219
219
|
```bash
|
|
220
|
-
cd ~/Code/<workspace> && orq conductor [conta] [modelo] # qualquer terminal: carrega as instruções do conductor, o
|
|
220
|
+
cd ~/Code/<workspace> && orq conductor [agente] [conta|-] [modelo] # qualquer terminal: carrega as instruções do conductor, o ORQ.md do workspace e a skill /issue
|
|
221
221
|
```
|
|
222
|
+
Escolha o conductor com `orq setup --workspace <pasta> --conductor codex` e rode `orq conductor` nessa pasta.
|
|
223
|
+
`orq conductor codex - <modelo>` troca o agente só nessa sessão; `-r <id>` a retoma. Claude Code também é um
|
|
224
|
+
conductor testado; a sintaxe antiga `orq conductor <conta Claude> <modelo>` continua funcionando. Se uma conta e um
|
|
225
|
+
agente têm o mesmo nome, use `--agent <nome>` para selecionar o agente explicitamente. O wrapper `herdr-conductor` continua abrindo Claude.
|
|
226
|
+
|
|
227
|
+
`ORQ.md` guarda o mapa do workspace. O setup preserva um `CLAUDE.md` existente e copia seu conteúdo para `ORQ.md`
|
|
228
|
+
na migração; se ele não existe, cria um `CLAUDE.md` apontando para o mapa. `AGENTS.md` aponta para o mapa e contém o
|
|
229
|
+
playbook gerado, preservando o texto fora do bloco gerado. Depois da migração, edite `ORQ.md`. Sem comandos em segundo
|
|
230
|
+
plano, o conductor usa `orq wait` em primeiro plano. `orq handoff` continua transferindo apenas contas Claude.
|
|
231
|
+
|
|
232
|
+
Os padrões ficam em `[roles.conductor]` (`agent`, `account`, `model`). Os templates interativos são separados dos
|
|
233
|
+
templates dos filhos, mantendo MCPs e os pedidos normais de permissão. Sobrescreva um agente embutido ou adicione outra CLI:
|
|
234
|
+
|
|
235
|
+
```toml
|
|
236
|
+
[agents.codex.conductor]
|
|
237
|
+
run = "codex [-m {model}] -C {dir} {prompt_text}"
|
|
238
|
+
resume = "codex resume {session} [-m {model}] -C {dir} {prompt_text}"
|
|
239
|
+
models = { fast = "id-do-seu-modelo" }
|
|
240
|
+
accounts = { work = { CODEX_HOME = "/caminho/codex-work" } }
|
|
241
|
+
|
|
242
|
+
[conductor]
|
|
243
|
+
headroom = 25 # pontos percentuais reservados para o agente que estiver conduzindo
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Os templates aceitam `{dir}`, `{instructions}` (caminho do arquivo), `{prompt_text}` (prompt para ler as instruções),
|
|
247
|
+
`{model}`, `{account}`, `{session}` e `{name}`. Contas e `env` são mapas de variáveis de ambiente. O conductor exporta
|
|
248
|
+
`ORQ_CONDUCTOR_AGENT`, então o `orq pick` aplica a penalidade de reserva e `[conductor].headroom` ao agente em uso,
|
|
249
|
+
inclusive após uma troca só nessa sessão. Valores explícitos de `agents.<nome>.reserve` e `headroom` têm precedência.
|
|
250
|
+
|
|
222
251
|
Com vários conductors, dê a cada um um nome fixo (`export ORQ_CONDUCTOR_ID=<nome>`; no herdr, vale o label do
|
|
223
252
|
workspace) para o `orq claim` distingui-los: a sessão do Claude se chama `conductor-<workspace>:<nome>`, e o
|
|
224
253
|
`orq claim -l` termina cada linha com esse `session=`, para os conductors conseguirem se falar.
|
|
@@ -283,7 +312,7 @@ Dia a dia, atalhos e várias issues ao mesmo tempo: [docs/pt-BR/herdr.md](docs/p
|
|
|
283
312
|
| `orq wait <prefixo>...` | Bloqueia até esses agentes terminarem; imprime `status=` e o arquivo da resposta (qualquer conductor, qualquer terminal) |
|
|
284
313
|
| `orq verify ...` (`herdr-verify`) | Testes relacionados ao diff, em fila e com limite de memória; falhas que também falham na base contam como pré-existentes |
|
|
285
314
|
| `orq board` (`herdr-board`) | Painel ao vivo: fila, agentes, conductors, cota, bloqueios, placar |
|
|
286
|
-
| `orq conductor [conta] [modelo] [-r <id>]` (`herdr-conductor`) | Abre o conductor
|
|
315
|
+
| `orq conductor [agente] [conta|-] [modelo] [-r <id>]` (`herdr-conductor`) | Abre o conductor configurado no terminal atual com as instruções, conta e modelo; `-r` retoma uma sessão |
|
|
287
316
|
| `orq claim` (`herdr-claim`) | Dono de cada issue entre os conductors |
|
|
288
317
|
| `orq deps` (`herdr-deps`) | Liga as dependências do worktree às do repo principal (sem reinstalar) |
|
|
289
318
|
| `orq baseline ...` (`herdr-baseline`) | Snapshot manual dos testes que falham na base (fora do fluxo) |
|
package/dist/commands/claim.js
CHANGED
|
@@ -141,7 +141,9 @@ export async function claim(argv) {
|
|
|
141
141
|
const kind = process.env.HERDR_PANE_ID ? "herdr" : "id";
|
|
142
142
|
const me = process.env.HERDR_PANE_ID || process.env.ORQ_CONDUCTOR_ID || `${userInfo().username}@${hostname()}`;
|
|
143
143
|
const myName = (kind === "herdr" ? herdrName(me) : "") || process.env.ORQ_CONDUCTOR_ID || "";
|
|
144
|
-
const account =
|
|
144
|
+
const account = process.env.ORQ_CONDUCTOR_AGENT && process.env.ORQ_CONDUCTOR_AGENT !== "claude"
|
|
145
|
+
? `${process.env.ORQ_CONDUCTOR_AGENT}:${process.env.ORQ_CONDUCTOR_ACCOUNT || "default"}`
|
|
146
|
+
: basename(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"));
|
|
145
147
|
const ownerLine = () => `pane=${me} kind=${kind} account=${account} at=${new Date().toISOString()}`
|
|
146
148
|
+ (myName ? ` name=${myName}` : "") + "\n";
|
|
147
149
|
if (mode === "register") {
|
|
@@ -11,12 +11,13 @@ const DESC = {
|
|
|
11
11
|
quota: "quota per agent/account", pick: "pick agent, model and account for a role", stats: "scoreboard per agent", rounds: "rounds, time and repeated findings of an issue",
|
|
12
12
|
board: "live board", lock: "test queue", run: "run a command under a memory cap", agent: "run a child agent",
|
|
13
13
|
wait: "wait for child agents", verify: "tests of a diff", baseline: "tests already failing on the base",
|
|
14
|
-
instructions: "write the /issue playbook", conductor: "open the conductor
|
|
14
|
+
instructions: "write the /issue playbook", conductor: "open the configured conductor in this terminal", claim: "issue owner among conductors", deps: "link node_modules into a worktree", gc: "remove merged issue worktrees", ci: "CI/CD status per claimed issue", handoff: "move a conductor low on quota to another account",
|
|
15
15
|
block: "block an agent or account", completion: "shell completion script",
|
|
16
16
|
};
|
|
17
17
|
/** Fixed words after a subcommand. */
|
|
18
18
|
export const WORDS = {
|
|
19
|
-
setup: ["--preset", "--detect", "--answers", "--write", "--install", "--workspace"],
|
|
19
|
+
setup: ["--preset", "--detect", "--answers", "--write", "--install", "--workspace", "--conductor"],
|
|
20
|
+
conductor: ["claude", "codex", "opencode", "antigravity", "copilot", "--agent", "-r", "--help"],
|
|
20
21
|
config: ["get", "files", "-w"], lang: ["pt", "en", "auto", "toggle"], lock: ["wait", "release", "run", "status"],
|
|
21
22
|
pick: ["impl", "fix", "worker", "review", "opinion", "watchdog"], instructions: ["claude", "agents"], board: ["--once", "--json", "-w"],
|
|
22
23
|
completion: ["bash", "zsh", "fish", "powershell"], stats: ["--days", "--json", "-w"], wait: ["--any", "--timeout", "--answer"],
|
|
@@ -1,32 +1,62 @@
|
|
|
1
|
-
// orq conductor — opens the
|
|
1
|
+
// orq conductor — opens the configured agent in the current terminal, with the workspace's conductor
|
|
2
2
|
// instructions, the chosen account and model (port of setup/herdr-conductor, for any terminal and OS).
|
|
3
|
-
// orq conductor [account|-] [model] [-r <session>]
|
|
3
|
+
// orq conductor [agent] [account|-] [model] [-r <session>]
|
|
4
4
|
// Workspace: the current folder if it has .orquestra-conductor.md, else $CONDUCTOR_WS. $CONDUCTOR_FILE overrides the
|
|
5
5
|
// instructions file. The session is named conductor-<workspace folder>:<herdr label or ORQ_CONDUCTOR_ID> (see sessionName).
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
import { existsSync } from "node:fs";
|
|
8
8
|
import { join, resolve } from "node:path";
|
|
9
9
|
import { claudeAccounts } from "../accounts.js";
|
|
10
|
+
import { ALIASES, expandTemplate, expandString, onPath } from "../agents.js";
|
|
11
|
+
import { getPath, loadConfig } from "../config.js";
|
|
10
12
|
import { m } from "../i18n.js";
|
|
11
13
|
import { runCapped } from "../process.js";
|
|
12
14
|
import { claim, herdrName, sessionName } from "./claim.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
import { instructions } from "./instructions.js";
|
|
16
|
+
export const USAGE = `orq conductor [agent] [account|-] [model] [-r <session>] open the configured conductor here
|
|
17
|
+
defaults: roles.conductor.agent/account/model; without config: claude
|
|
18
|
+
legacy: orq conductor <Claude account|-> [model]; -r resumes that conversation
|
|
19
|
+
--agent <name> disambiguates an agent from a Claude account with the same name
|
|
20
|
+
templates: agents.<agent>.conductor.run/resume/models/accounts/env`;
|
|
15
21
|
export const MODELS = {
|
|
16
22
|
sonnet: "claude-sonnet-5",
|
|
17
23
|
opus: "claude-opus-5-5",
|
|
18
24
|
haiku: "claude-haiku-4-5-20251001",
|
|
19
25
|
};
|
|
20
26
|
export const modelId = (alias) => MODELS[alias] ?? alias;
|
|
21
|
-
/**
|
|
22
|
-
export
|
|
27
|
+
/** Interactive launch templates retain the conductor's MCPs and normal permission prompts. */
|
|
28
|
+
export const CONDUCTORS = {
|
|
29
|
+
claude: {
|
|
30
|
+
run: "claude -n {name} [--model {model}] --append-system-prompt-file {instructions}",
|
|
31
|
+
resume: "claude --resume {session} -n {name} [--model {model}] --append-system-prompt-file {instructions}",
|
|
32
|
+
models: MODELS,
|
|
33
|
+
},
|
|
34
|
+
codex: { run: "codex [-m {model}] -C {dir} {prompt_text}",
|
|
35
|
+
resume: "codex resume {session} [-m {model}] -C {dir} {prompt_text}" },
|
|
36
|
+
opencode: { run: "opencode [-m {model}] --prompt {prompt_text} {dir}",
|
|
37
|
+
resume: "opencode -s {session} [-m {model}] --prompt {prompt_text} {dir}" },
|
|
38
|
+
copilot: { run: "copilot -C {dir} [--model {model}] -i {prompt_text}",
|
|
39
|
+
resume: "copilot -C {dir} --resume={session} [--model {model}] -i {prompt_text}" },
|
|
40
|
+
antigravity: { run: "agy [--model {model}] -i {prompt_text}",
|
|
41
|
+
resume: "agy --conversation {session} [--model {model}] -i {prompt_text}" },
|
|
42
|
+
};
|
|
43
|
+
/** Optional agent, then [account|-] [model] [-r|--resume <id>]; old Claude positionals still parse. */
|
|
44
|
+
export function parseArgs(argv, names = Object.keys(CONDUCTORS), accounts = []) {
|
|
23
45
|
const out = { account: "", model: "", resume: "", help: false };
|
|
24
46
|
const pos = [];
|
|
25
47
|
for (let i = 0; i < argv.length; i++) {
|
|
26
48
|
const a = argv[i];
|
|
27
|
-
if (a === "
|
|
49
|
+
if (a === "--agent") {
|
|
28
50
|
const v = argv[++i];
|
|
29
|
-
if (!v) {
|
|
51
|
+
if (!v || v.startsWith("-")) {
|
|
52
|
+
out.error = USAGE;
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
out.agent = ALIASES[v] ?? v;
|
|
56
|
+
}
|
|
57
|
+
else if (a === "-r" || a === "--resume") {
|
|
58
|
+
const v = argv[++i];
|
|
59
|
+
if (!v || v.startsWith("-")) {
|
|
30
60
|
out.error = m("give the session id", "informe o id da sessão");
|
|
31
61
|
return out;
|
|
32
62
|
}
|
|
@@ -37,9 +67,53 @@ export function parseArgs(argv) {
|
|
|
37
67
|
else
|
|
38
68
|
pos.push(a);
|
|
39
69
|
}
|
|
70
|
+
if (!out.agent && pos.length === 2 && pos[1] !== "-" && accounts.includes(pos[0])) {
|
|
71
|
+
out.agent = "claude";
|
|
72
|
+
}
|
|
73
|
+
else if (!out.agent && pos.length && names.includes(ALIASES[pos[0]] ?? pos[0])) {
|
|
74
|
+
const agent = pos.shift();
|
|
75
|
+
out.agent = ALIASES[agent] ?? agent;
|
|
76
|
+
}
|
|
40
77
|
[out.account = "", out.model = ""] = pos;
|
|
78
|
+
if (pos.length > 2)
|
|
79
|
+
out.error = USAGE;
|
|
41
80
|
return out;
|
|
42
81
|
}
|
|
82
|
+
/** Resolve a conductor without reusing worker templates (which disable MCPs and permissions). */
|
|
83
|
+
export function launch(cfg, a, ws, file, name = "") {
|
|
84
|
+
const agent = a.agent ?? String(getPath(cfg, "roles.conductor.agent") ?? "claude");
|
|
85
|
+
const user = (getPath(cfg, `agents.${agent}.conductor`) ?? {});
|
|
86
|
+
const base = CONDUCTORS[agent];
|
|
87
|
+
const def = { ...base, ...user, models: { ...base?.models, ...user.models } };
|
|
88
|
+
const template = a.resume ? def.resume : def.run;
|
|
89
|
+
if (!template)
|
|
90
|
+
throw new Error(`orq conductor: ${agent} needs agents.${agent}.conductor.${a.resume ? "resume" : "run"}`);
|
|
91
|
+
const same = !a.agent || a.agent === getPath(cfg, "roles.conductor.agent");
|
|
92
|
+
const account = a.account || (same ? String(getPath(cfg, "roles.conductor.account") ?? "") : "");
|
|
93
|
+
const model = a.model || (same ? String(getPath(cfg, "roles.conductor.model") ?? "") : "");
|
|
94
|
+
const vars = { dir: ws, instructions: file, name: sessionName(ws, name), session: a.resume,
|
|
95
|
+
account: account === "-" ? "" : account, model: def.models[model] ?? model,
|
|
96
|
+
prompt_text: `Read ${JSON.stringify(file)} for your conductor instructions and follow them. Read AGENTS.md for the issue playbook. Read ORQ.md for the workspace map; in an older workspace without ORQ.md, read CLAUDE.md.` };
|
|
97
|
+
const env = { ...process.env };
|
|
98
|
+
for (const [k, v] of Object.entries(def.env ?? {}))
|
|
99
|
+
env[k] = expandString(v, vars);
|
|
100
|
+
if (account && account !== "-") {
|
|
101
|
+
const custom = def.accounts?.[account];
|
|
102
|
+
if (custom)
|
|
103
|
+
Object.assign(env, custom);
|
|
104
|
+
else if (agent === "claude") {
|
|
105
|
+
const accounts = claudeAccounts(), found = accounts.find((x) => x.name === account);
|
|
106
|
+
if (!found)
|
|
107
|
+
throw new Error(`account '${account}' does not exist. Accounts: ${accounts.map((x) => `${x.name} ${x.dir}`).join(", ")}`);
|
|
108
|
+
env.CLAUDE_CONFIG_DIR = found.dir;
|
|
109
|
+
}
|
|
110
|
+
else
|
|
111
|
+
throw new Error(`orq conductor: account '${account}' needs agents.${agent}.conductor.accounts.${account}`);
|
|
112
|
+
}
|
|
113
|
+
env.ORQ_CONDUCTOR_AGENT = agent;
|
|
114
|
+
env.ORQ_CONDUCTOR_ACCOUNT = account && account !== "-" ? account : "default";
|
|
115
|
+
return { agent, argv: expandTemplate(template, vars), env };
|
|
116
|
+
}
|
|
43
117
|
/** The workspace folder: cwd when it holds .orquestra-conductor.md, else $CONDUCTOR_WS (resolved); undefined if neither. */
|
|
44
118
|
export function findWorkspace(cwd, env, exists = existsSync) {
|
|
45
119
|
if (exists(join(cwd, ".orquestra-conductor.md")))
|
|
@@ -53,7 +127,13 @@ export function claudeArgs(ws, a, file, name = "") {
|
|
|
53
127
|
...(a.model ? ["--model", modelId(a.model)] : []), "--append-system-prompt-file", file];
|
|
54
128
|
}
|
|
55
129
|
export async function conductor(argv) {
|
|
56
|
-
|
|
130
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
131
|
+
console.log(USAGE);
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
const ws = findWorkspace(process.cwd(), process.env);
|
|
135
|
+
const { cfg } = loadConfig(ws);
|
|
136
|
+
const a = parseArgs(argv, [...Object.keys(CONDUCTORS), ...Object.keys((cfg.agents ?? {}))], claudeAccounts().map((a) => a.name));
|
|
57
137
|
if (a.help) {
|
|
58
138
|
console.log(USAGE);
|
|
59
139
|
return 0;
|
|
@@ -62,7 +142,6 @@ export async function conductor(argv) {
|
|
|
62
142
|
console.error(a.error);
|
|
63
143
|
return 2;
|
|
64
144
|
}
|
|
65
|
-
const ws = findWorkspace(process.cwd(), process.env);
|
|
66
145
|
if (!ws) {
|
|
67
146
|
console.error(m("no .orquestra-conductor.md here: cd into a workspace (orq setup --workspace <dir>) or set CONDUCTOR_WS", "não há .orquestra-conductor.md aqui: entre num workspace (orq setup --workspace <pasta>) ou defina CONDUCTOR_WS"));
|
|
68
147
|
return 2;
|
|
@@ -72,34 +151,45 @@ export async function conductor(argv) {
|
|
|
72
151
|
console.error(`${m("conductor instructions not found", "instruções do conductor não encontradas")}: ${file}`);
|
|
73
152
|
return 2;
|
|
74
153
|
}
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
env.CLAUDE_CONFIG_DIR = found.dir;
|
|
154
|
+
const name = (process.env.HERDR_PANE_ID ? herdrName(process.env.HERDR_PANE_ID) : "") || process.env.ORQ_CONDUCTOR_ID || "";
|
|
155
|
+
let command;
|
|
156
|
+
try {
|
|
157
|
+
command = launch(cfg, a, ws, file, name);
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
console.error(e.message);
|
|
161
|
+
return 1;
|
|
85
162
|
}
|
|
163
|
+
const { env } = command;
|
|
164
|
+
if (!command.argv.length || !onPath(command.argv[0])) {
|
|
165
|
+
console.error(`orq conductor: executable not found: ${command.argv[0] ?? "(empty template)"}`);
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
if (command.agent !== "claude")
|
|
169
|
+
await instructions(["agents", "-w", ws]);
|
|
86
170
|
if (process.env.HERDR_ENV === "1" && process.env.HERDR_PANE_ID)
|
|
87
171
|
spawnSync("herdr", ["pane", "rename", process.env.HERDR_PANE_ID, "conductor"], { stdio: "ignore", timeout: 5_000 });
|
|
88
172
|
process.chdir(ws);
|
|
89
173
|
// Registration (.orquestra/.conductors) is a herdr thing (keyed by pane id; `claim --register` does nothing elsewhere),
|
|
90
174
|
// so only inside herdr. claim reads the account from this process's env (best effort, silent).
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
175
|
+
const keys = ["CLAUDE_CONFIG_DIR", "ORQ_CONDUCTOR_AGENT", "ORQ_CONDUCTOR_ACCOUNT"];
|
|
176
|
+
const saved = keys.map((k) => process.env[k]);
|
|
177
|
+
try {
|
|
178
|
+
for (const k of keys) {
|
|
179
|
+
if (env[k] === undefined)
|
|
180
|
+
delete process.env[k];
|
|
181
|
+
else
|
|
182
|
+
process.env[k] = env[k];
|
|
97
183
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
184
|
+
if (process.env.HERDR_PANE_ID)
|
|
185
|
+
await claim(["-w", ws, "--register"]);
|
|
186
|
+
}
|
|
187
|
+
catch { /* best effort */ }
|
|
188
|
+
finally {
|
|
189
|
+
keys.forEach((k, i) => { if (saved[i] === undefined)
|
|
190
|
+
delete process.env[k];
|
|
191
|
+
else
|
|
192
|
+
process.env[k] = saved[i]; });
|
|
193
|
+
}
|
|
194
|
+
return runCapped(command.argv[0], command.argv.slice(1), { mode: "off", env, cwd: ws, quote: true, tty: true });
|
|
105
195
|
}
|
package/dist/commands/gc.js
CHANGED
|
@@ -7,6 +7,7 @@ import { m } from "../i18n.js";
|
|
|
7
7
|
import { issueFromName, issuePattern, issuesWithLiveAgent } from "../issue.js";
|
|
8
8
|
import { alive } from "../lock.js";
|
|
9
9
|
import { stateDir } from "../paths.js";
|
|
10
|
+
import { describe, stopWorktreeProcesses, worktreeProcesses } from "../worktree-processes.js";
|
|
10
11
|
import { aliveOwner } from "./claim.js";
|
|
11
12
|
export const USAGE = "orq gc [-w <workspace>] [--yes] [--days N] [--json] remove merged issue worktrees and stale agent state";
|
|
12
13
|
const git = (cwd, ...args) => spawnSync("git", ["-C", cwd, ...args], { encoding: "utf8" });
|
|
@@ -168,20 +169,24 @@ export async function gc(argv, clock = Date.now) {
|
|
|
168
169
|
if (ghUnavailable)
|
|
169
170
|
console.error(m("NOTE: gh unavailable or failed; PR signals were skipped", "NOTA: gh indisponível ou falhou; sinais de PR ignorados"));
|
|
170
171
|
let failed = false;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
git(join(ws, row.repo), "worktree", "prune");
|
|
172
|
+
for (const row of rows.filter((r) => r.verdict === "REMOVABLE")) {
|
|
173
|
+
const pids = yes ? stopWorktreeProcesses(row.path) : worktreeProcesses(row.path).map(describe);
|
|
174
|
+
if (pids.length)
|
|
175
|
+
row.reason += `; ${yes ? m("stopped processes", "processos encerrados") : m("processes to stop", "processos a encerrar")}: ${pids.join(", ")}`;
|
|
176
|
+
if (!yes)
|
|
177
|
+
continue;
|
|
178
|
+
const removed = git(join(ws, row.repo), "worktree", "remove", row.path);
|
|
179
|
+
if (removed.status !== 0) {
|
|
180
|
+
failed = true;
|
|
181
|
+
row.reason += `; ${m("worktree removal failed", "falha ao remover worktree")}`;
|
|
182
|
+
continue;
|
|
184
183
|
}
|
|
184
|
+
if (git(join(ws, row.repo), "branch", "-D", row.branch).status !== 0) {
|
|
185
|
+
failed = true;
|
|
186
|
+
row.reason += `; ${m("branch deletion failed", "falha ao excluir branch")}`;
|
|
187
|
+
}
|
|
188
|
+
git(join(ws, row.repo), "worktree", "prune");
|
|
189
|
+
}
|
|
185
190
|
const stale = staleAgentStates(ws, days, clock());
|
|
186
191
|
if (yes)
|
|
187
192
|
for (const f of stale)
|