@orkastery/cli 0.5.3 → 0.5.6
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/adapters/claude-code/README.md +158 -0
- package/adapters/claude-code/mods/autoridade.json +56 -0
- package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
- package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
- package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
- package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
- package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
- package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
- package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
- package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
- package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
- package/adapters/claude-code/mods/ork-mesa/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
- package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
- package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
- package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +117 -0
- package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
- package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
- package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
- package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
- package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
- package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
- package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
- package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
- package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
- package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
- package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
- package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
- package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
- package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
- package/adapters/claude-code/mods/ork-sentinela/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
- package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
- package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
- package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
- package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
- package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
- package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
- package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
- package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +1 -1
- package/adapters/openclaw/README.md +2 -1
- package/adapters/openclaw/openclaw.plugin.json +1 -1
- package/adapters/openclaw/package.json +1 -1
- package/assets/docs-en/markdownlint-cli2.jsonc +22 -0
- package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
- package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
- package/assets/docs-en/produto/README.md +14 -0
- package/assets/docs-en/produto/_modelo-feature.md +62 -0
- package/assets/docs-en/roadmap/README.md +14 -0
- package/assets/docs-en/roadmap/_modelo-item.md +75 -0
- package/dist/adapters/claude-bg.js +77 -13
- package/dist/adapters/codex.js +10 -1
- package/dist/agents-md.js +24 -15
- package/dist/auditrun.js +17 -2
- package/dist/board.js +4 -3
- package/dist/branch-de-estado.js +23 -1
- package/dist/catalogo.js +33 -0
- package/dist/ci.js +13 -7
- package/dist/claims-conferir.js +342 -0
- package/dist/claims.js +3 -1
- package/dist/company-brain-client.js +36 -1
- package/dist/company-brain-journal.js +2 -1
- package/dist/company-brain-migration.js +36 -1
- package/dist/conducao-sentinela.js +388 -0
- package/dist/conducao-texto.js +12 -18
- package/dist/conducao.js +122 -30
- package/dist/conhecimento.js +490 -0
- package/dist/docs.js +22 -8
- package/dist/doctor-pulse-cron.js +182 -27
- package/dist/doctor-rede.js +18 -17
- package/dist/doctor.js +115 -111
- package/dist/entrega-pr.js +22 -21
- package/dist/escopo-escrita.js +10 -1
- package/dist/estado-thread.js +23 -1
- package/dist/experiencia-instalacao.js +45 -25
- package/dist/fabrica-batida.js +310 -0
- package/dist/fabrica-estado.js +38 -8
- package/dist/fabrica-publicar.js +3 -1
- package/dist/fila-pesada.js +444 -0
- package/dist/fix.js +3 -2
- package/dist/forja.js +1 -1
- package/dist/gates.js +3 -28
- package/dist/handoff.js +10 -6
- package/dist/hitl-local-receipt.js +2 -0
- package/dist/hitl-resumo.js +29 -3
- package/dist/hitl-tempo-parado.js +4 -4
- package/dist/hitl.js +29 -33
- package/dist/horario.js +14 -7
- package/dist/hosts.js +52 -1
- package/dist/impedimento.js +108 -14
- package/dist/index.js +623 -521
- package/dist/indice.js +7 -1
- package/dist/init.js +43 -47
- package/dist/instabilidade.js +144 -0
- package/dist/integracoes-locais.js +2 -0
- package/dist/intelligence-graph-cli.js +92 -21
- package/dist/intelligence-graph-contexto.js +56 -17
- package/dist/intelligence-graph-contract.js +2 -1
- package/dist/intelligence-graph-extract-md.js +56 -4
- package/dist/intelligence-graph-extract-ts.js +71 -3
- package/dist/intelligence-graph-query.js +66 -19
- package/dist/ledger.js +15 -1
- package/dist/liveness.js +6 -3
- package/dist/locale-ajuda.js +761 -0
- package/dist/locale-gerados.js +160 -0
- package/dist/locale-master.js +99 -0
- package/dist/locale-onboarding.js +92 -0
- package/dist/locale-phase.js +151 -0
- package/dist/locale-pulse.js +211 -0
- package/dist/locale-retry.js +385 -0
- package/dist/locale-roadmap.js +102 -0
- package/dist/locale-ship.js +494 -0
- package/dist/locale-thread.js +277 -0
- package/dist/locale-verify.js +125 -0
- package/dist/locale.js +714 -0
- package/dist/manifest.js +129 -3
- package/dist/maquina.js +23 -4
- package/dist/master-digest.js +6 -1
- package/dist/master-nota.js +4 -6
- package/dist/master.js +21 -24
- package/dist/mcp-artifacts.js +11 -2
- package/dist/mcp-git.js +23 -6
- package/dist/mcp-grafo.js +7 -4
- package/dist/mcp-server.js +8 -2
- package/dist/mcp-ship.js +3 -1
- package/dist/mcp-verify.js +6 -1
- package/dist/modos.js +3 -6
- package/dist/network-roadmap.js +5 -2
- package/dist/noctum-arco.js +911 -0
- package/dist/noctum-capacidade.js +141 -0
- package/dist/noctum-envelope.js +810 -0
- package/dist/noctum-execucao.js +304 -0
- package/dist/noctum-fila.js +280 -0
- package/dist/ocupacao.js +29 -2
- package/dist/onboarding.js +40 -21
- package/dist/orkmind.js +9 -2
- package/dist/orquestracao.js +13 -12
- package/dist/parado-no-condutor.js +34 -9
- package/dist/phase.js +94 -39
- package/dist/policies.js +4 -4
- package/dist/portfolio.js +4 -2
- package/dist/postura-local.js +234 -0
- package/dist/preflight.js +11 -10
- package/dist/procedencia.js +225 -0
- package/dist/projeto-alvo.js +127 -4
- package/dist/prompts.js +6 -2
- package/dist/pulse-delivery.js +13 -2
- package/dist/pulse.js +52 -19
- package/dist/ratelimit.js +4 -2
- package/dist/redacao-saida.js +63 -0
- package/dist/rede-status.js +3 -2
- package/dist/retry.js +232 -148
- package/dist/roadmap-reservas.js +20 -3
- package/dist/roadmap-status.js +129 -33
- package/dist/runtime-context.js +181 -3
- package/dist/runtimes.js +2 -1
- package/dist/session-events.js +7 -3
- package/dist/session-watcher-claude.js +7 -1
- package/dist/session-watcher.js +185 -33
- package/dist/sessoes-contas.js +1 -1
- package/dist/ship-publicar.js +800 -0
- package/dist/ship.js +84 -81
- package/dist/steal.js +77 -0
- package/dist/thread-close.js +3 -2
- package/dist/thread.js +153 -82
- package/dist/util.js +8 -1
- package/dist/verify.js +141 -52
- package/dist/vida-da-thread.js +254 -0
- package/dist/worktree-local.js +165 -0
- package/dist/worktree.js +20 -0
- package/dist/yaml.js +27 -8
- package/package.json +1 -1
- package/schemas/code-artifact-graph.v1.schema.json +2 -1
|
@@ -151,6 +151,164 @@ Sem `tool_use_id`, cada invocação do hook recebe uma identidade de ocorrência
|
|
|
151
151
|
do transcript não identifica uma ocorrência; eventos de um novo episódio de permissão
|
|
152
152
|
podem chegar sem alteração do arquivo. Replays com `tool_use_id` continuam idempotentes.
|
|
153
153
|
|
|
154
|
+
## Mods nas sessões de fase (RM-059)
|
|
155
|
+
|
|
156
|
+
Mod é plugin de *function hooks* do Claude Code 2.1.287 ou mais novo. Cada mod do Orkastery mora
|
|
157
|
+
em `adapters/claude-code/mods/<mod>/`, com o próprio `.claude-plugin/plugin.json`, e declara o
|
|
158
|
+
papel em `orkastery-mod.json`:
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{"formato":"ork.mod/v1","papeis":["fase"],"claudeCodeMinimo":"2.1.287"}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
O papel `dono` roda na sessão de quem conduz; o papel `fase`, nas sessões de fase que o `ork`
|
|
165
|
+
despacha. O `ork adapter install claude-code` copia os mods com o resto do adaptador e grava cada
|
|
166
|
+
arquivo deles no `INSTALADO.json`. No despacho `claude-bg`, cada mod de papel `fase` entra com o
|
|
167
|
+
próprio `--plugin-dir`, logo depois do plugin do Orkastery, quando:
|
|
168
|
+
|
|
169
|
+
- a pasta instalada do mod tem exatamente os arquivos do recibo, com os mesmos bytes e sem link;
|
|
170
|
+
os tipos que o próprio Claude Code escreve em `.claude-plugin/types/` (`*.d.ts` e
|
|
171
|
+
`tsconfig.json`) ficam fora da conta;
|
|
172
|
+
- o `claude --version` atende o `claudeCodeMinimo` do mod e o 2.1.287 da API de mods.
|
|
173
|
+
|
|
174
|
+
Mod que pede versão mais nova fica fora do comando, e o despacho segue: o núcleo não depende de
|
|
175
|
+
mod. Mod cuja dependência ficou de fora (pela versão, ou por ser só de papel `dono`) também fica.
|
|
176
|
+
Declaração inválida, manifesto fora do recibo ou arquivo a mais na pasta recusa o despacho com
|
|
177
|
+
`runtime.context.installation`, como um byte divergente. Arquivo de mod que saiu do catálogo sai da
|
|
178
|
+
cópia na reinstalação: o `ork adapter install claude-code` tira o que ele mesmo instalou e ninguém
|
|
179
|
+
editou. Arquivo editado ou acrescentado à mão fica, e a recusa diz o caminho para apagar. A dependência (`dependencies` do
|
|
180
|
+
`plugin.json` do mod) entra antes de quem depende; o resto segue o nome. Os mods que foram ficam no
|
|
181
|
+
comando gravado no evento `phase_dispatch` do ledger da thread.
|
|
182
|
+
|
|
183
|
+
O arquivo comum da raiz de `mods/`, como a lista de autoridade que a ponte lê em `../autoridade.json`
|
|
184
|
+
antes da própria pasta, segue a mesma regra quando há mod de fase no despacho: fora do recibo, link
|
|
185
|
+
ou arquivo especial recusam, e o que saiu do catálogo sai da cópia na reinstalação.
|
|
186
|
+
|
|
187
|
+
Para tirar um mod das sessões de fase, mude o papel no `orkastery-mod.json` do catálogo, ou tire o
|
|
188
|
+
mod, e rode `ork adapter install claude-code` de novo.
|
|
189
|
+
|
|
190
|
+
O mod de papel `dono` não vai ao despacho: ele roda na sessão de quem conduz. Num projeto com o
|
|
191
|
+
adaptador instalado, a cópia dele já está em `.claude/plugins/orkastery/mods/<mod>`, com cada
|
|
192
|
+
arquivo registrado no `INSTALADO.json` e os arquivos comuns da raiz de `mods/` ao lado. Nesse
|
|
193
|
+
carregamento ninguém confere os bytes: a conferência contra o recibo roda no despacho, para mod de
|
|
194
|
+
fase. Carregue-a na sessão com:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
claude --plugin-dir .claude/plugins/orkastery/mods/<mod>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Sem o adaptador local, cada mod tem uma entrada no marketplace próprio do repositório
|
|
201
|
+
(`.claude-plugin/marketplace.json`, gerado por `node core/scripts/gerar-marketplaces.cjs`), que aponta
|
|
202
|
+
para uma cópia gerada em `marketplaces/claude-code/mods/<mod>/`. Instalado pelo marketplace, o plugin
|
|
203
|
+
é só a própria pasta, então o arquivo comum da raiz de `adapters/claude-code/mods/` (a lista de
|
|
204
|
+
autoridade da ponte, por exemplo) vai dentro de cada cópia. A instalação é no escopo do projeto:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
claude plugin marketplace add orkastery/orkastery
|
|
208
|
+
claude plugin install <mod>@orkastery --scope project
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Os dois marketplaces se chamam `orkastery`, e o local, registrado na instalação acima a partir de
|
|
212
|
+
`.claude/plugins/orkastery`, não lista os mods. Não troque um pelo outro num projeto que usa o
|
|
213
|
+
plugin do adaptador: remover um marketplace desinstala os plugins que vieram dele (medido no
|
|
214
|
+
Claude Code 2.1.296), e o plugin do marketplace do repositório não traz o guard `PreToolUse` nem o
|
|
215
|
+
MCP do adaptador.
|
|
216
|
+
|
|
217
|
+
### Freio de Lote (`ork-freio`)
|
|
218
|
+
|
|
219
|
+
O primeiro mod de papel `fase`. Na sessão de fase, depois de cada lote de ferramentas, ele:
|
|
220
|
+
|
|
221
|
+
- acha os arquivos que o lote mexeu: os caminhos de `Edit`, `Write`, `MultiEdit` e `NotebookEdit` e a
|
|
222
|
+
diferença do `git status` entre um lote e outro, que pega também a edição feita por `Bash`;
|
|
223
|
+
- acha as claims da thread que citam esses arquivos, pelo `arquivo` ou pelo texto do comando, e avisa
|
|
224
|
+
quando o lote mexeu fora dos `touch_paths` do plano;
|
|
225
|
+
- pede ao núcleo, em segundo plano, a conferência dessas claims com
|
|
226
|
+
`ork claims conferir <thread> --claims ... --json`, no sandbox do verify, uma rodada por vez;
|
|
227
|
+
- devolve ao modelo, no lote seguinte do laço principal, o que quebrou: o id da claim, o comando e o
|
|
228
|
+
fim da saída. Lote sem claim afetada passa sem texto, e a linha de status mostra as claims em risco.
|
|
229
|
+
O lote de um subagente conta os arquivos tocados, mas não leva a entrega nem o aviso.
|
|
230
|
+
|
|
231
|
+
O Freio nunca roda o comando de uma claim, nunca grava estado de claim e nunca para o laço: quem
|
|
232
|
+
verifica de verdade continua sendo o `ork verify`. Ele lê o papel e a thread pelo `$.ork` da ponte, e
|
|
233
|
+
as claims e o plano pelas ferramentas de leitura `ork_claims_list` e `ork_artifact_read`, nunca do
|
|
234
|
+
disco em `.orkastery/`. Precisa de um `ork` com `ork claims conferir` no `PATH` da sessão; sem ele, ou
|
|
235
|
+
sem o sandbox do verify, avisa uma vez que a conferência está indisponível e segue. Teste que cria
|
|
236
|
+
processo pode reprovar no sandbox e passar no `ork verify`, e o aviso diz isso junto da falha. O
|
|
237
|
+
resultado chega no lote seguinte: se a sessão terminar antes dele, a última conferência fica sem
|
|
238
|
+
entrega. Falha de claim cujo arquivo foi tocado de novo durante a conferência não é entregue, porque
|
|
239
|
+
uma conferência nova já foi pedida. Um `Edit` que falhou também conta como toque e pode pedir uma
|
|
240
|
+
conferência a mais.
|
|
241
|
+
|
|
242
|
+
### Portão de Tarefas (`ork-portao`)
|
|
243
|
+
|
|
244
|
+
O segundo mod de papel `fase`. Os portões do quadro agem só na sessão de fase cujo bloco atual tem o GO
|
|
245
|
+
(pelo slug do bloco no retrato do núcleo) e cuja thread tem plano com ao menos uma tarefa `T<n>` que
|
|
246
|
+
serve ao quadro: com `touch_paths` (caminhos entre crases, com barra, ponto ou curinga) e um verify com um
|
|
247
|
+
comando só entre crases, a regra do `ork claims conferir --tarefa`. Plano sem nenhuma tarefa assim não
|
|
248
|
+
ganha portão, para não travar o quadro do GO. Com o portão ligado:
|
|
249
|
+
|
|
250
|
+
- **criação:** `TaskCreated` recusa a tarefa cujo assunto não traz o id de uma tarefa do plano
|
|
251
|
+
(`T2.1 ...`), ou cuja tarefa do plano não serve ao quadro. A recusa volta ao modelo com o motivo e as
|
|
252
|
+
tarefas do plano que servem;
|
|
253
|
+
- **conclusão:** `TaskCompleted` da tarefa registrada na criação, ou da que cita no assunto uma tarefa do
|
|
254
|
+
plano que serve, pede ao núcleo `ork claims conferir <thread> --tarefa T2.1 --prazo 540 --json`, no
|
|
255
|
+
sandbox do verify, e só deixa a tarefa fechar com o item aprovado. Reprovação volta com o comando, o
|
|
256
|
+
código e o fim da saída; sem veredito, recusa tipada, outra condução na thread, conferência que não roda
|
|
257
|
+
ou falha do próprio Portão também bloqueiam, sempre com a saída: corrigir e concluir de novo, ou devolver
|
|
258
|
+
a tarefa para `pending` e seguir. A tarefa registrada continua conferida mesmo com o plano ilegível na
|
|
259
|
+
hora, porque o núcleo lê o plano por conta própria. Uma conferência por vez: a conclusão que chega no
|
|
260
|
+
meio de outra é recusada na hora. Da terceira reprovação igual em diante, o texto manda deixar a tarefa
|
|
261
|
+
aberta;
|
|
262
|
+
- **teammate ocioso:** com agent teams no piloto (abaixo), `TeammateIdle` entrega ao teammate a próxima
|
|
263
|
+
tarefa do plano que serve, não fechou nesta sessão e cuja região está livre: nenhum lease `path:` ativo,
|
|
264
|
+
de nenhuma thread, cruza os `touch_paths` dela e ela não cruza as tarefas dos outros teammates. Os leases
|
|
265
|
+
vêm do retrato de todas as threads pela consulta `maestro` da ponte
|
|
266
|
+
(`ork maestro --json --section leases --offset N`), seguindo o `nextOffset` das páginas que o teto de
|
|
267
|
+
bytes do retrato corta, até 10 páginas, porque o servidor MCP da sessão de fase só mostra os da própria
|
|
268
|
+
thread. O Portão não toma nem solta lease: o `ork_git_commit` toma o `path:` de cada arquivo na hora do
|
|
269
|
+
commit e recusa o commit com qualquer lease no caminho. Quem fica ocioso sem concluir devolve a tarefa,
|
|
270
|
+
que sai da distribuição automática desta sessão. Sem a lista de leases inteira (seção indisponível,
|
|
271
|
+
lacuna, retrato em conflito ou mais de 10 páginas), no teto de custo ou sem tarefa livre, o teammate
|
|
272
|
+
fica ocioso.
|
|
273
|
+
|
|
274
|
+
O Portão nunca roda o verify por conta própria: quem verifica de verdade continua sendo o `ork verify`.
|
|
275
|
+
Ele lê o papel pelo `$.ork.contexto` da ponte, o estado e o bloco pelo `$.ork.retrato`, os leases pela
|
|
276
|
+
consulta `maestro` da ponte e o plano pelo `ork_artifact_read`, nunca do disco em `.orkastery/`. Teste que
|
|
277
|
+
cria processo pode reprovar no sandbox e passar no `ork verify`, e o bloqueio diz isso.
|
|
278
|
+
|
|
279
|
+
Agent teams na sessão de fase só rodam no piloto, desligado por padrão. Quem conduz liga no manifesto da
|
|
280
|
+
raiz:
|
|
281
|
+
|
|
282
|
+
```yaml
|
|
283
|
+
runtime:
|
|
284
|
+
agent_teams:
|
|
285
|
+
ligado: true
|
|
286
|
+
teammates: 2 # teto de teammates vivos, de 1 a 8
|
|
287
|
+
custo_usd: 10 # teto do custo da sessão, como o /cost soma, até 500
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Com a chave ligada, o GO no bloco da fase atual e o Portão entre os mods que vão à sessão, o despacho
|
|
291
|
+
`claude-bg` acrescenta ao `--settings` a variável `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS`, os dois tetos
|
|
292
|
+
(`ORK_AGENT_TEAMS_TEAMMATES` e `ORK_AGENT_TEAMS_CUSTO_USD`) e `teammateMode` `in-process`, porque a sessão
|
|
293
|
+
de fundo não tem `tmux`. Teto inválido vira aviso do manifesto e deixa desligado. Em toda sessão de fase,
|
|
294
|
+
com ou sem o portão do quadro, o Portão recusa o nascimento de teammate sem os tetos, no teto de teammates
|
|
295
|
+
vivos ou com o custo da sessão no teto, e no teto de custo não entrega tarefa nova.
|
|
296
|
+
|
|
297
|
+
Limites conhecidos:
|
|
298
|
+
|
|
299
|
+
- o quadro do Portão vale para a sessão: numa sessão de GO redespachada, tarefa fechada na anterior pode
|
|
300
|
+
ser entregue de novo ao teammate, que a encontra pronta no quadro e no git;
|
|
301
|
+
- o líder não recebe aviso quando um teammate devolve uma tarefa; ele a vê aberta no quadro;
|
|
302
|
+
- a conferência do Freio, em segundo plano, e a do Portão, na conclusão, podem rodar ao mesmo tempo na
|
|
303
|
+
mesma worktree; cada mod só serializa as próprias;
|
|
304
|
+
- os tetos dependem de o Portão carregar de verdade: o despacho decide pelo `--plugin-dir`, e se o mod não
|
|
305
|
+
registrar na sessão, agent teams fica ligado sem ninguém aplicando os tetos;
|
|
306
|
+
- se o turno for interrompido no meio da conferência, o Claude Code segue sem a resposta do gancho;
|
|
307
|
+
- nome de lease que o retrato redige (caminho absoluto ou texto com cara de segredo) chega irreconhecível e
|
|
308
|
+
não cruza nenhuma região;
|
|
309
|
+
- o comportamento desses eventos numa sessão `claude --bg` real com agent teams ainda não foi medido; os
|
|
310
|
+
testes do kit e do núcleo cobrem o mod e o despacho.
|
|
311
|
+
|
|
154
312
|
## A #TAG de conducao
|
|
155
313
|
|
|
156
314
|
O comando `/goal` extrai o modo do proprio pedido, sem reimplementar nada:
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"formato": "ork.mods-autoridade/v1",
|
|
3
|
+
"regra": "Nenhum mod aprova gate, permissão, nota do MASTER, push ou tag pelo dono, nem fala como o dono (D5 do plano dos mods, com o nome que o dono deu a ela). A decisão autônoma registrada pelo núcleo não entra nesta lista: está em permitidos.",
|
|
4
|
+
"leitores": [
|
|
5
|
+
"core/scripts/checar-mods.cjs",
|
|
6
|
+
"adapters/claude-code/mods/ork-ponte/hooks/register.ts"
|
|
7
|
+
],
|
|
8
|
+
"ganchosProibidos": [
|
|
9
|
+
{ "id": "elicitation", "evento": "classic.Elicitation", "porque": "responderia o diálogo nativo do MCP no lugar do dono" },
|
|
10
|
+
{ "id": "elicitation-result", "evento": "classic.ElicitationResult", "porque": "trocaria a resposta que o dono deu no diálogo nativo" },
|
|
11
|
+
{ "id": "permission-request", "evento": "classic.PermissionRequest", "porque": "aprovaria permissão pelo dono" },
|
|
12
|
+
{ "id": "ask-user-question", "evento": "ui.render", "componente": "AskUserQuestion", "porque": "redesenharia a pergunta feita ao dono" }
|
|
13
|
+
],
|
|
14
|
+
"decisoesProibidas": [
|
|
15
|
+
{ "id": "tool-check-allow", "evento": "tool.check", "decisao": "allow", "porque": "aprovaria uma chamada de ferramenta pelo dono" }
|
|
16
|
+
],
|
|
17
|
+
"chamadasProibidas": [
|
|
18
|
+
{ "id": "prompt-submit", "chamada": "prompt.submit", "porque": "manda texto como se fosse o dono" },
|
|
19
|
+
{ "id": "session-append-usuario", "chamada": "session.append", "tipo": "user", "porque": "grava linha de usuário que o modelo lê como palavra do dono" }
|
|
20
|
+
],
|
|
21
|
+
"argvProibido": [
|
|
22
|
+
{ "id": "gate-answer", "sequencia": ["gate", "answer"], "porque": "responde gate pelo dono" },
|
|
23
|
+
{ "id": "sessions-answer", "sequencia": ["sessions", "answer"], "porque": "entrega resposta a uma sessão como se fosse do dono" },
|
|
24
|
+
{ "id": "pulse-responder", "sequencia": ["pulse", "responder"], "porque": "responde o resumo pelo dono" },
|
|
25
|
+
{ "id": "master-nota", "sequencia": ["master"], "opcao": "--score", "porque": "dá ou propõe a nota do MASTER" },
|
|
26
|
+
{ "id": "master-batch", "sequencia": ["master", "batch"], "porque": "aceita MASTER em lote pelo dono" },
|
|
27
|
+
{ "id": "master-digest-responder", "sequencia": ["master", "digest", "responder"], "porque": "responde o digest pelo dono" },
|
|
28
|
+
{ "id": "retry-run-por", "sequencia": ["retry", "run"], "opcao": "--por", "porque": "autoriza o retry quando o modo pausa" },
|
|
29
|
+
{ "id": "ship-autorizar-push", "sequencia": ["ship"], "opcao": "--autorizar-push", "porque": "autoriza push pelo dono" },
|
|
30
|
+
{ "id": "git-push", "programa": "git", "subcomando": "push", "porque": "publica sem a prova do núcleo" },
|
|
31
|
+
{ "id": "git-tag", "programa": "git", "subcomando": "tag", "porque": "tag é ato do dono; listar tags sai por outro comando de leitura" },
|
|
32
|
+
{ "id": "gh", "programa": "gh", "porque": "PR, merge e release ficam com o núcleo e o dono" }
|
|
33
|
+
],
|
|
34
|
+
"ambienteProibido": [
|
|
35
|
+
{ "id": "hitl-env", "prefixo": "ORK_HITL_", "porque": "credenciais e verificadores do ingresso humano" }
|
|
36
|
+
],
|
|
37
|
+
"caminhosProibidos": [
|
|
38
|
+
{ "id": "hermes-env", "trecho": ".hermes/.env", "porque": "segredos do canal do dono" }
|
|
39
|
+
],
|
|
40
|
+
"reservadosAPonte": [
|
|
41
|
+
{ "id": "processos", "evento": "process.*", "porque": "a guarda da ponte vigia os processos" },
|
|
42
|
+
{ "id": "ambiente", "evento": "env.*", "porque": "a guarda vigia o ambiente, e o papel da sessão sai dele" },
|
|
43
|
+
{ "id": "arquivos", "evento": "fs.*", "porque": "a guarda vigia leituras, e a lista de autoridade sai do disco" },
|
|
44
|
+
{ "id": "mcp", "evento": "mcp.*", "porque": "o retrato do núcleo sai do MCP" },
|
|
45
|
+
{ "id": "store", "evento": "store.*", "porque": "o registro do que foi mostrado ao dono fica no store da ponte" },
|
|
46
|
+
{ "id": "estado", "evento": "state.*", "porque": "a faixa, os caminhos vigiados e o cache ficam no estado da ponte" },
|
|
47
|
+
{ "id": "ferramentas", "evento": "tool.list", "porque": "o papel dono sai da lista de ferramentas" },
|
|
48
|
+
{ "id": "raiz", "evento": "session.root", "porque": "o papel e a raiz do estado saem da raiz da sessão" },
|
|
49
|
+
{ "id": "registro", "evento": "plugin.register", "porque": "só a ponte confere os mods que carregam" },
|
|
50
|
+
{ "id": "prompt", "evento": "prompt.submit", "porque": "só a ponte decide sobre texto enviado por mod" },
|
|
51
|
+
{ "id": "transcript", "evento": "session.append", "porque": "só a ponte decide sobre linha anexada por mod" }
|
|
52
|
+
],
|
|
53
|
+
"permitidos": [
|
|
54
|
+
{ "id": "decisao-autonoma", "sequencia": ["decisao", "registrar"], "porque": "decisão autônoma registrada pelo núcleo, com critério e evidência, que vai ao dono no lote seguinte com o custo de reverter (M4)" }
|
|
55
|
+
]
|
|
56
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ork-clima",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Clima de Cota do Orkastery no Claude Code: prevê quando a cota da conta acaba no ritmo atual, avisa na faixa antes de a fase morrer no meio e diz qual retomada não cabe. Só lê e avisa.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Orkastery contributors",
|
|
7
|
+
"url": "https://github.com/orkastery"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://orkastery.com/",
|
|
10
|
+
"repository": "https://github.com/orkastery/orkastery",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"types": "./types/index.d.ts",
|
|
13
|
+
"dependencies": ["ork-ponte"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// O cruzamento do Clima de Cota (RM-059, onda 1, F4; T4.2): a previsao das janelas da conta desta sessao
|
|
2
|
+
// contra os despachos que dependem de cota (a fila de retomada inteira, de `ork retry list --json`, e as
|
|
3
|
+
// threads abertas paradas por cota, no retrato do nucleo) e contra as marcas de esgotamento por conta do
|
|
4
|
+
// registro compartilhado (RM-040). D7, D8 e D13 a D15 da thread. Modulo puro: le o que a ponte trouxe, classifica
|
|
5
|
+
// e escreve; nenhum `$` passa por aqui.
|
|
6
|
+
import type { ClimaEstado, ClimaNaoCabe, ClimaPrevisao } from '../types'
|
|
7
|
+
import { duracao, emAlerta, formatarHora, rotuloDaJanela, textoDaFaixa } from './previsao'
|
|
8
|
+
|
|
9
|
+
/** Uma retomada da fila do nucleo que ainda vai despachar. */
|
|
10
|
+
export type ClimaRetomada = {
|
|
11
|
+
id: string
|
|
12
|
+
thread: string
|
|
13
|
+
/** Quando a fila libera, em ms desde a epoca; `null` quando a fila nao disse. */
|
|
14
|
+
liberaEm: number | null
|
|
15
|
+
tentativas: number | null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Uma thread aberta cujo ultimo gate reprovado foi por cota. */
|
|
19
|
+
export type ClimaParada = { thread: string; motivo: string }
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* O que o retrato trouxe dos bloqueios: as paradas, se a pagina veio cortada e quantas threads ficaram sem ledger
|
|
23
|
+
* (`lacunasCortadas` quando o retrato cortou a lista de lacunas e o numero e um piso).
|
|
24
|
+
*/
|
|
25
|
+
export type ClimaParadas = { itens: ClimaParada[]; paginaCortada: boolean; lacunas: number; lacunasCortadas: boolean }
|
|
26
|
+
|
|
27
|
+
/** Uma marca do registro compartilhado de contas (`ork.esgotamentos/v1`), com a conta opaca. */
|
|
28
|
+
export type ClimaMarca = {
|
|
29
|
+
conta: string
|
|
30
|
+
runtime: string
|
|
31
|
+
estado: string
|
|
32
|
+
motivo: string
|
|
33
|
+
esgotadoAte: number | null
|
|
34
|
+
projeto: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type ClimaClasse = 'nao-cabe' | 'cabe' | 'depois-de-renovar'
|
|
38
|
+
|
|
39
|
+
export type ClimaDespacho = {
|
|
40
|
+
retomada: ClimaRetomada
|
|
41
|
+
/** Quando a retomada despacha: a hora de liberar, ou agora se ja liberou. */
|
|
42
|
+
quando: number
|
|
43
|
+
classe: ClimaClasse
|
|
44
|
+
/** A janela que decide a classe; `null` sem janela em alerta. */
|
|
45
|
+
janela: string | null
|
|
46
|
+
estadoDaJanela: ClimaEstado | null
|
|
47
|
+
acabaEm: number | null
|
|
48
|
+
renovaEm: number | null
|
|
49
|
+
/** Na classe `cabe`, a cota que sobra no ritmo atual ate o primeiro esgotamento. */
|
|
50
|
+
folgaMs: number | null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** O que o `/clima` cruzou: `null` onde a leitura falhou, com o motivo ao lado. */
|
|
54
|
+
export type ClimaCruzamento = {
|
|
55
|
+
despachos: ClimaDespacho[] | null
|
|
56
|
+
erroDaFila: string | null
|
|
57
|
+
paradas: ClimaParadas | null
|
|
58
|
+
erroDoRetrato: string | null
|
|
59
|
+
marcas: ClimaMarca[] | null
|
|
60
|
+
erroDoRegistro: string | null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Os motivos de gate que sao de cota: a politica do nucleo espera a janela ou roda o perfil (D7). */
|
|
64
|
+
export const MOTIVOS_DE_COTA: readonly string[] = ['runtime.quota-exhausted', 'runtime.rate-limited']
|
|
65
|
+
export const CONTRATO_DOS_ESGOTAMENTOS = 'ork.esgotamentos/v1'
|
|
66
|
+
/** O que o nucleo ja sabe fazer, com os comandos da propria politica dele; o Clima nunca executa (D7). */
|
|
67
|
+
export const SUGESTOES: readonly string[] = [
|
|
68
|
+
'esperar a janela: a retomada fica na fila até renovar, e ork retry list mostra a fila;',
|
|
69
|
+
'seguir em outro perfil: ork accounts list mostra os perfis e quem está esgotado, e ork retry run <thread> segue no próximo perfil do rodízio;',
|
|
70
|
+
'adiar a fase: segurar o despacho da fase seguinte até a janela renovar.',
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
const ID_DE_THREAD = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/
|
|
74
|
+
const ID_DE_RETOMADA = /^R[1-9][0-9]{0,8}$/
|
|
75
|
+
const CONTA_OPACA = /^[a-f0-9]{12}$/
|
|
76
|
+
const NAO_REGISTRADO = 'nao registrado'
|
|
77
|
+
const ORDEM_DA_CLASSE: Readonly<Record<ClimaClasse, number>> = { 'nao-cabe': 0, 'cabe': 1, 'depois-de-renovar': 2 }
|
|
78
|
+
|
|
79
|
+
type Bruto = Record<string, unknown>
|
|
80
|
+
|
|
81
|
+
function objeto(v: unknown): Bruto | null {
|
|
82
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v) ? (v as Bruto) : null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function texto(v: unknown, teto: number): string | null {
|
|
86
|
+
return typeof v === 'string' && v.trim() !== '' ? v.replace(/\s+/g, ' ').trim().slice(0, teto) : null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function instante(v: unknown): number | null {
|
|
90
|
+
const t = typeof v === 'string' ? Date.parse(v) : NaN
|
|
91
|
+
return Number.isFinite(t) ? t : null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* As retomadas que ainda vao despachar, da fila inteira (`ork retry list --json`, D13): a fila e um JSONL so de
|
|
96
|
+
* acrescimo, entao a ultima gravacao de um id vence, e so o estado `aguardando` conta. `null` fora do formato.
|
|
97
|
+
*/
|
|
98
|
+
export function retomadasDaFila(fila: unknown): ClimaRetomada[] | null {
|
|
99
|
+
if (!Array.isArray(fila)) return null
|
|
100
|
+
const porId = new Map<string, Bruto>()
|
|
101
|
+
for (const bruto of fila) {
|
|
102
|
+
const p = objeto(bruto)
|
|
103
|
+
if (p !== null && typeof p.id === 'string' && ID_DE_RETOMADA.test(p.id)) porId.set(p.id, p)
|
|
104
|
+
}
|
|
105
|
+
const itens: ClimaRetomada[] = []
|
|
106
|
+
for (const p of porId.values()) {
|
|
107
|
+
if (p.estado !== 'aguardando' || typeof p.thread !== 'string' || !ID_DE_THREAD.test(p.thread)) continue
|
|
108
|
+
const tentativas = p.tentativas
|
|
109
|
+
itens.push({
|
|
110
|
+
id: p.id as string, thread: p.thread, liberaEm: instante(p.liberaEm),
|
|
111
|
+
tentativas: typeof tentativas === 'number' && Number.isInteger(tentativas) && tentativas >= 0 ? tentativas : null,
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
return itens
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** As threads abertas, de `ork thread list --json` (que sem `--todas` so lista as nao fechadas); `null` fora do formato. */
|
|
118
|
+
export function threadsAbertas(lista: unknown): string[] | null {
|
|
119
|
+
const threads = objeto(lista)?.threads
|
|
120
|
+
if (!Array.isArray(threads)) return null
|
|
121
|
+
return threads.map(objeto).filter((t): t is Bruto => t !== null && typeof t.id === 'string' && t.status !== 'fechada')
|
|
122
|
+
.map(t => t.id as string)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function threadDasRefs(refs: unknown): string | null {
|
|
126
|
+
for (const r of Array.isArray(refs) ? refs : []) {
|
|
127
|
+
const o = objeto(r)
|
|
128
|
+
if (o?.kind === 'thread' && typeof o.id === 'string' && ID_DE_THREAD.test(o.id)) return o.id
|
|
129
|
+
}
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* As threads paradas por cota, da secao `blockers` do retrato (D7), so as abertas quando a lista delas veio
|
|
135
|
+
* (o retrato tambem traz thread fechada). A pagina cortada e a lacuna de ledger sao ditas a parte (D13).
|
|
136
|
+
*/
|
|
137
|
+
export function paradasDoRetrato(retrato: unknown, abertas: readonly string[] | null): ClimaParadas {
|
|
138
|
+
const s = objeto(objeto(objeto(retrato)?.sections)?.blockers)
|
|
139
|
+
const itens: ClimaParada[] = []
|
|
140
|
+
const lista = s?.items
|
|
141
|
+
for (const bruto of Array.isArray(lista) ? lista : []) {
|
|
142
|
+
const i = objeto(bruto)
|
|
143
|
+
const motivo = objeto(i?.facts)?.reason
|
|
144
|
+
if (i === null || typeof motivo !== 'string' || !MOTIVOS_DE_COTA.includes(motivo)) continue
|
|
145
|
+
const thread = threadDasRefs(i.refs)
|
|
146
|
+
if (thread === null || (abertas !== null && !abertas.includes(thread)) || itens.some(p => p.thread === thread)) continue
|
|
147
|
+
itens.push({ thread, motivo })
|
|
148
|
+
}
|
|
149
|
+
const proxima = objeto(s?.coverage)?.nextOffset
|
|
150
|
+
const gaps = Array.isArray(s?.gaps) ? (s?.gaps as unknown[]).filter((g): g is string => typeof g === 'string') : []
|
|
151
|
+
const lacunas = gaps.filter(g => g.startsWith('ledger.unavailable:')).length
|
|
152
|
+
// O retrato guarda so as primeiras lacunas e conta o resto em `maestro.messages.omitted:N` (D14).
|
|
153
|
+
return { itens, paginaCortada: typeof proxima === 'number', lacunas, lacunasCortadas: gaps.some(g => g.startsWith('maestro.messages.omitted:')) }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** As marcas do registro de contas; `null` quando o relatorio nao e do contrato `ork.esgotamentos/v1`. */
|
|
157
|
+
export function marcasDoRelatorio(relatorio: unknown): ClimaMarca[] | null {
|
|
158
|
+
const r = objeto(relatorio)
|
|
159
|
+
if (r === null || r.contrato !== CONTRATO_DOS_ESGOTAMENTOS || !Array.isArray(r.marcas)) return null
|
|
160
|
+
const marcas: ClimaMarca[] = []
|
|
161
|
+
for (const bruta of r.marcas) {
|
|
162
|
+
const m = objeto(bruta)
|
|
163
|
+
if (m === null || typeof m.conta !== 'string' || !CONTA_OPACA.test(m.conta)) continue
|
|
164
|
+
const runtime = texto(m.runtime, 40)
|
|
165
|
+
const estado = texto(m.estado, 40)
|
|
166
|
+
if (runtime === null || estado === null) continue
|
|
167
|
+
marcas.push({
|
|
168
|
+
conta: m.conta, runtime, estado, motivo: texto(m.motivo, 80) ?? 'sem motivo',
|
|
169
|
+
esgotadoAte: instante(m.esgotadoAte), projeto: texto(m.projeto, 80) ?? NAO_REGISTRADO,
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
return marcas
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** O trecho sem cota de uma janela em alerta: do esgotamento (ou da leitura, se ja esgotou) ate renovar. */
|
|
176
|
+
function trechoSemCota(p: ClimaPrevisao): { p: ClimaPrevisao; inicio: number; fim: number } | null {
|
|
177
|
+
if (!emAlerta(p)) return null
|
|
178
|
+
const inicio = p.estado === 'esgotada' ? p.lidaEm : p.acabaEm
|
|
179
|
+
if (inicio === null) return null
|
|
180
|
+
return { p, inicio, fim: p.renovaEm ?? Number.POSITIVE_INFINITY }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Cada retomada contra os trechos sem cota das janelas em alerta (D7): `nao-cabe` quando despacha dentro de
|
|
185
|
+
* um deles (na hora exata da renovacao a janela ja renovou); `cabe`, com a folga ate o primeiro esgotamento,
|
|
186
|
+
* quando despacha antes; `depois-de-renovar` quando todos ja renovaram. Sem janela em alerta, `cabe` sem folga.
|
|
187
|
+
*/
|
|
188
|
+
export function cruzar(previsoes: readonly ClimaPrevisao[], retomadas: readonly ClimaRetomada[], agora: number): ClimaDespacho[] {
|
|
189
|
+
const trechos = previsoes.map(trechoSemCota).filter((t): t is NonNullable<ReturnType<typeof trechoSemCota>> => t !== null)
|
|
190
|
+
const despacho = (r: ClimaRetomada, quando: number, classe: ClimaClasse, t: (typeof trechos)[number] | undefined,
|
|
191
|
+
folgaMs: number | null): ClimaDespacho => ({
|
|
192
|
+
retomada: r, quando, classe, janela: t?.p.kind ?? null, estadoDaJanela: t?.p.estado ?? null,
|
|
193
|
+
acabaEm: t?.inicio ?? null, renovaEm: t?.p.renovaEm ?? null, folgaMs,
|
|
194
|
+
})
|
|
195
|
+
return retomadas.map(r => {
|
|
196
|
+
const quando = Math.max(agora, r.liberaEm ?? agora)
|
|
197
|
+
const dentro = trechos.filter(t => quando >= t.inicio && quando < t.fim)
|
|
198
|
+
if (dentro.length > 0) return despacho(r, quando, 'nao-cabe', dentro.reduce((a, b) => (b.fim > a.fim ? b : a)), null)
|
|
199
|
+
const antes = trechos.filter(t => quando < t.inicio)
|
|
200
|
+
if (antes.length > 0) {
|
|
201
|
+
const primeiro = antes.reduce((a, b) => (b.inicio < a.inicio ? b : a))
|
|
202
|
+
return despacho(r, quando, 'cabe', primeiro, primeiro.inicio - quando)
|
|
203
|
+
}
|
|
204
|
+
if (trechos.length > 0) return despacho(r, quando, 'depois-de-renovar', trechos.reduce((a, b) => (b.fim > a.fim ? b : a)), null)
|
|
205
|
+
return despacho(r, quando, 'cabe', undefined, null)
|
|
206
|
+
}).sort((a, b) => ORDEM_DA_CLASSE[a.classe] - ORDEM_DA_CLASSE[b.classe] || a.quando - b.quando)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** As threads das retomadas que nao cabem, sem repetir, na ordem do cruzamento. */
|
|
210
|
+
export function threadsQueNaoCabem(despachos: readonly ClimaDespacho[]): string[] {
|
|
211
|
+
const threads: string[] = []
|
|
212
|
+
for (const d of despachos) if (d.classe === 'nao-cabe' && !threads.includes(d.retomada.thread)) threads.push(d.retomada.thread)
|
|
213
|
+
return threads
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Uma retomada no `/clima`. */
|
|
217
|
+
export function textoDoDespacho(d: ClimaDespacho, fuso: string, agora: number): string {
|
|
218
|
+
const volta = d.quando <= agora ? 'volta agora' : `volta ${formatarHora(d.quando, fuso, agora)}`
|
|
219
|
+
const quem = `${d.retomada.id} da ${d.retomada.thread} ${volta}`
|
|
220
|
+
const janela = d.janela === null ? 'a janela' : `a janela de ${rotuloDaJanela(d.janela)}`
|
|
221
|
+
if (d.classe === 'nao-cabe') {
|
|
222
|
+
const acaba = d.estadoDaJanela === 'esgotada' || d.acabaEm === null ? 'já esgotou' : `acaba ~${formatarHora(d.acabaEm, fuso, agora)}`
|
|
223
|
+
const renova = d.renovaEm === null ? 'não tem hora de renovar' : `renova ${formatarHora(d.renovaEm, fuso, agora)}`
|
|
224
|
+
return `${quem} e não cabe: ${janela} ${acaba} e ${renova}.`
|
|
225
|
+
}
|
|
226
|
+
if (d.classe === 'depois-de-renovar') return `${quem}, depois de ${janela} renovar.`
|
|
227
|
+
if (d.folgaMs !== null) return `${quem}, com ~${duracao(d.folgaMs)} de cota no ritmo atual.`
|
|
228
|
+
return `${quem}.`
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** As retomadas que nao cabiam, como o estado guardou; a entrada fora do formato fica de fora (D15). */
|
|
232
|
+
export function naoCabemGuardados(valor: unknown): ClimaNaoCabe[] {
|
|
233
|
+
if (!Array.isArray(valor)) return []
|
|
234
|
+
return valor.filter((n): n is ClimaNaoCabe => !!n && typeof n.id === 'string' && typeof n.thread === 'string'
|
|
235
|
+
&& typeof n.texto === 'string' && typeof n.registrada === 'boolean')
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** O que vai ao registro do mostrado por retomada que nao cabe: so o que nao muda a cada leitura (D13). */
|
|
239
|
+
export function textoDoNaoCabe(d: ClimaDespacho, fuso: string, agora: number): string {
|
|
240
|
+
const janela = d.janela === null ? 'na janela' : `na janela de ${rotuloDaJanela(d.janela)}`
|
|
241
|
+
const renova = d.renovaEm === null ? ', que não tem hora de renovar' : `, que renova ${formatarHora(d.renovaEm, fuso, agora)}`
|
|
242
|
+
return `${d.retomada.id} da ${d.retomada.thread} não cabe ${janela}${renova}.`
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** O resumo que o `/clima` registra como mostrado (D8): a linha da faixa, ou que nada pede atencao. */
|
|
246
|
+
export function resumoDoClima(previsoes: readonly ClimaPrevisao[], naoCabem: readonly string[], fuso: string, agora: number): string {
|
|
247
|
+
return textoDaFaixa(previsoes, fuso, agora, naoCabem) ?? 'nenhuma janela pede atenção'
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function textoDaParada(p: ClimaParada): string {
|
|
251
|
+
return `${p.thread} parada por ${p.motivo}.`
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function textoDaMarca(m: ClimaMarca, fuso: string, agora: number): string {
|
|
255
|
+
const prazo = m.esgotadoAte === null ? 'sem prazo' : `até ${formatarHora(m.esgotadoAte, fuso, agora)}`
|
|
256
|
+
const onde = m.projeto === NAO_REGISTRADO ? 'em projeto não registrado' : `em ${m.projeto}`
|
|
257
|
+
return `conta ${m.conta} (${m.runtime}): ${m.estado} ${prazo}, visto ${onde} (${m.motivo}).`
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** As linhas do cruzamento no `/clima`: fila, paradas, contas do registro e, se algo aperta, as sugestoes. */
|
|
261
|
+
export function linhasDoCruzamento(c: ClimaCruzamento, previsoes: readonly ClimaPrevisao[], fuso: string, agora: number): string[] {
|
|
262
|
+
const linhas = ['Despachos que dependem da cota (fila de retomada do núcleo):']
|
|
263
|
+
if (c.despachos === null) linhas.push(`- Fila de retomada indisponível (${c.erroDaFila ?? 'fora do formato'}).`)
|
|
264
|
+
else if (c.despachos.length === 0) linhas.push('- Nenhuma retomada na fila.')
|
|
265
|
+
else for (const d of c.despachos) linhas.push(`- ${textoDoDespacho(d, fuso, agora)}`)
|
|
266
|
+
linhas.push('Threads abertas paradas por cota (retrato do núcleo):')
|
|
267
|
+
if (c.paradas === null) linhas.push(`- Não deu para conferir as paradas (${c.erroDoRetrato ?? 'sem resposta'}).`)
|
|
268
|
+
else {
|
|
269
|
+
if (c.paradas.itens.length === 0) linhas.push('- Nenhuma.')
|
|
270
|
+
for (const p of c.paradas.itens) linhas.push(`- ${textoDaParada(p)}`)
|
|
271
|
+
if (c.paradas.paginaCortada) linhas.push('- Os bloqueios vieram em mais de uma página; o Clima leu a primeira.')
|
|
272
|
+
if (c.paradas.lacunas > 0) {
|
|
273
|
+
linhas.push(`- O retrato não leu o ledger de ${c.paradas.lacunasCortadas ? 'pelo menos ' : ''}${c.paradas.lacunas} thread(s).`)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
linhas.push('Contas no registro do núcleo (ork accounts esgotamentos):')
|
|
277
|
+
if (c.marcas === null) linhas.push(`- Registro de contas indisponível (${c.erroDoRegistro ?? 'fora do contrato ork.esgotamentos/v1'}).`)
|
|
278
|
+
else if (c.marcas.length === 0) linhas.push('- Nenhuma conta marcada no registro.')
|
|
279
|
+
else for (const m of c.marcas) linhas.push(`- ${textoDaMarca(m, fuso, agora)}`)
|
|
280
|
+
const aperta = previsoes.some(emAlerta) || (c.despachos ?? []).some(d => d.classe === 'nao-cabe') || (c.paradas?.itens.length ?? 0) > 0
|
|
281
|
+
if (aperta) linhas.push('O que o núcleo já sabe fazer:', ...SUGESTOES.map(s => `- ${s}`))
|
|
282
|
+
return linhas
|
|
283
|
+
}
|