@orkastery/cli 0.5.1 → 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/agents/ork-master.md +2 -0
- package/adapters/claude-code/commands/master.md +8 -0
- package/adapters/claude-code/commands/ork.md +9 -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/codex/skills/ork/SKILL.md +18 -3
- package/adapters/hermes/README.md +1 -0
- package/adapters/hermes/bin/ork-network-status.sh +10 -0
- package/adapters/hermes/hermes.plugin.json +1 -0
- package/adapters/hermes/hitl-ingress/__init__.py +2 -2
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +7 -6
- package/adapters/openclaw/README.md +26 -5
- package/adapters/openclaw/dist/hitl-ingress.js +2 -2
- package/adapters/openclaw/dist/index.js +46 -11
- package/adapters/openclaw/openclaw.plugin.json +5 -1
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +2 -2
- package/adapters/openclaw/src/index.ts +49 -11
- 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/assets/orkmind_bridge.py +117 -9
- package/dist/adapters/claude-bg.js +116 -18
- package/dist/adapters/codex.js +14 -4
- package/dist/agents-md.js +24 -14
- package/dist/auditrun.js +22 -3
- package/dist/board.js +20 -17
- package/dist/branch-de-estado.js +88 -10
- package/dist/busca-semantica.js +52 -12
- package/dist/canarios-rm057.js +149 -0
- package/dist/canarios.js +2 -0
- package/dist/catalogo.js +33 -0
- package/dist/ci.js +21 -8
- package/dist/claims-conferir.js +342 -0
- package/dist/claims.js +41 -1
- package/dist/company-brain-client.js +36 -1
- package/dist/company-brain-context.js +158 -22
- 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 +70 -17
- package/dist/doctor-pulse-cron.js +349 -0
- package/dist/doctor-rede.js +150 -0
- package/dist/doctor.js +195 -111
- package/dist/entrega-pr.js +42 -29
- package/dist/escopo-escrita.js +10 -1
- package/dist/esgotamentos.js +181 -0
- package/dist/estado-thread.js +23 -1
- package/dist/experiencia-instalacao.js +45 -25
- package/dist/experiencia.js +12 -2
- package/dist/fabrica-batida.js +310 -0
- package/dist/fabrica-estado.js +52 -14
- package/dist/fabrica-publicar.js +9 -1
- package/dist/fechamento.js +20 -66
- package/dist/fila-pesada.js +444 -0
- package/dist/fix.js +3 -2
- package/dist/forja.js +31 -7
- package/dist/gates.js +3 -24
- package/dist/handoff.js +10 -6
- package/dist/hitl-contract.js +59 -4
- package/dist/hitl-curto.js +11 -6
- package/dist/hitl-gates.js +5 -0
- package/dist/hitl-local-receipt.js +2 -0
- package/dist/hitl-lote.js +5 -2
- package/dist/hitl-resumo.js +38 -3
- package/dist/hitl-tempo-parado.js +201 -0
- package/dist/hitl-texto-livre.js +5 -5
- package/dist/hitl.js +29 -33
- package/dist/horario.js +41 -7
- package/dist/hosts.js +104 -11
- package/dist/impedimento.js +299 -0
- package/dist/index.js +961 -542
- package/dist/indice-vetorial.js +87 -10
- package/dist/indice.js +9 -1
- package/dist/init.js +56 -43
- package/dist/instabilidade.js +144 -0
- package/dist/integracoes-locais.js +4 -0
- package/dist/intelligence-graph-cli.js +170 -16
- package/dist/intelligence-graph-contexto.js +254 -0
- 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-parsers.js +6 -4
- package/dist/intelligence-graph-query.js +66 -19
- package/dist/leases.js +664 -87
- package/dist/ledger-stats.js +15 -1
- package/dist/ledger.js +18 -1
- package/dist/licoes.js +1 -0
- 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/maestro-runtime.js +4 -1
- package/dist/manifest.js +149 -4
- package/dist/maquina.js +23 -4
- package/dist/master-digest.js +6 -1
- package/dist/master-nota.js +18 -11
- package/dist/master.js +145 -31
- package/dist/mcp-artifacts.js +43 -3
- package/dist/mcp-git.js +42 -8
- package/dist/mcp-grafo-worker.js +5 -3
- package/dist/mcp-grafo.js +55 -6
- package/dist/mcp-server.js +22 -3
- package/dist/mcp-ship.js +3 -1
- package/dist/mcp-verify.js +6 -1
- package/dist/memoria.js +48 -6
- package/dist/modos.js +3 -6
- package/dist/network-roadmap.js +196 -18
- 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 +40 -5
- package/dist/onboarding.js +40 -21
- package/dist/orkmind.js +99 -7
- package/dist/orquestracao.js +24 -21
- package/dist/parado-no-condutor.js +219 -38
- package/dist/phase.js +160 -52
- package/dist/policies.js +56 -5
- package/dist/portfolio.js +8 -3
- package/dist/postura-local.js +234 -0
- package/dist/preflight.js +11 -10
- package/dist/procedencia.js +225 -0
- package/dist/projeto-alvo.js +132 -5
- package/dist/prompts.js +40 -1
- package/dist/prova-ativacao.js +75 -6
- package/dist/pulse-delivery.js +47 -4
- package/dist/pulse.js +98 -19
- package/dist/ratelimit.js +24 -6
- package/dist/recall.js +3 -1
- package/dist/redacao-saida.js +63 -0
- package/dist/rede-adesao.js +399 -0
- package/dist/rede-forja.js +289 -0
- package/dist/rede-projetos.js +239 -0
- package/dist/rede-status.js +337 -0
- package/dist/rede.js +980 -0
- package/dist/retry.js +291 -140
- package/dist/roadmap-reservas.js +20 -3
- package/dist/roadmap-status.js +150 -34
- package/dist/runtime-context.js +181 -3
- package/dist/runtime-profiles.js +22 -1
- package/dist/runtimes.js +2 -1
- package/dist/saida-segura.js +58 -0
- package/dist/session-events.js +7 -3
- package/dist/session-watcher-claude.js +120 -24
- package/dist/session-watcher.js +185 -33
- package/dist/sessoes-contas.js +187 -0
- package/dist/sessoes-inventario.js +6 -18
- package/dist/sessoes.js +83 -2
- package/dist/ship-publicar.js +800 -0
- package/dist/ship.js +157 -88
- package/dist/steal.js +77 -0
- package/dist/thread-close.js +3 -2
- package/dist/thread.js +299 -49
- 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 +25 -2
- package/dist/yaml.js +27 -8
- package/eval/fixtures/fx-pedido-colado/caso.json +20 -0
- package/package.json +7 -3
- package/references/definition-of-done.md +1 -1
- package/schemas/code-artifact-graph.v1.schema.json +2 -1
- package/skills/README.md +1 -1
- package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/master-metrics/SKILL.md +8 -3
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
// A Mesa do Dono do Orkastery (RM-059, fatia F5; ficha M4 do plano dos mods). Na sessao do dono, um painel com tudo
|
|
2
|
+
// que espera o dono, lido do nucleo pela ponte (`$.ork.retrato` e `$.ork.ler`) e por tres ferramentas MCP, nunca de
|
|
3
|
+
// `.orkastery/` por conta propria. O botao Decidir so pede ao nucleo o dialogo nativo do pedido
|
|
4
|
+
// (`ork_request_decision`, com os dois ids); a decisao obvia, pelo teste das quatro condicoes, vira decisao autonoma do
|
|
5
|
+
// nucleo (`ork_decision_record`). Nenhum caminho daqui responde o pedido. A logica fica nos modulos puros ao lado; aqui
|
|
6
|
+
// so os ganchos e as chamadas ao `$`, sempre escritas como `x.noun.metodo(...)`, como o validador exige.
|
|
7
|
+
import { atom, read, update } from 'claude-code'
|
|
8
|
+
import type { EngineInterface, Register } from 'claude-code'
|
|
9
|
+
import type { MesaDecidindo, MesaFila, MesaObvia, MesaPergunta } from '../types'
|
|
10
|
+
import type { MesaMostrado } from './cartoes'
|
|
11
|
+
import { desenharMesa, linhasDaMesa, mostradosVisiveis, paginaDoLote } from './cartoes'
|
|
12
|
+
import { argsDaDecisaoObvia, argsDoDecidir, jaDecidida, motivoDaNotificacao, resultadoDoDecidir, soltarAbrindo } from './decidir'
|
|
13
|
+
import {
|
|
14
|
+
assinaturaDoRetrato, conferirNoNucleo, decisoesVistas, esperamODono, FILA_VAZIA, filaDoPulse, jsonDoMcp, ordenarPerguntas,
|
|
15
|
+
pedidosNotificados, textoDaFaixa, threadsComPergunta, umaLinha,
|
|
16
|
+
} from './fila'
|
|
17
|
+
import { testeDaObvia } from './obvia'
|
|
18
|
+
|
|
19
|
+
const MOD = 'ork-mesa'
|
|
20
|
+
const PAINEL = 'mesa'
|
|
21
|
+
const TITULO = 'Mesa do Dono'
|
|
22
|
+
const fila = atom({ plugin: 'ork-mesa', key: 'fila' } as const, FILA_VAZIA)
|
|
23
|
+
const foco = atom({ plugin: 'ork-mesa', key: 'foco' } as const, null as string | null)
|
|
24
|
+
const pagina = atom({ plugin: 'ork-mesa', key: 'pagina' } as const, 0)
|
|
25
|
+
const decidindo = atom({ plugin: 'ork-mesa', key: 'decidindo' } as const, {} as Record<string, MesaDecidindo>)
|
|
26
|
+
const obvias = atom({ plugin: 'ork-mesa', key: 'obvias' } as const, {} as Record<string, MesaObvia>)
|
|
27
|
+
|
|
28
|
+
/** O tique: a vista, o retrato a cada um; escondido, a cada quatro (D3). */
|
|
29
|
+
const PERIODO_DO_TIQUE_MS = 30000
|
|
30
|
+
const TIQUES_ESCONDIDO = 4
|
|
31
|
+
/** O pulse velho e relido mesmo sem mudanca no retrato. */
|
|
32
|
+
const IDADE_DO_PULSE_A_VISTA_MS = 5 * 60000
|
|
33
|
+
const IDADE_DO_PULSE_ESCONDIDO_MS = 15 * 60000
|
|
34
|
+
/** Teto de cada leitura do nucleo; o Decidir espera o dialogo, que o ingresso local fecha em ate 300 s. */
|
|
35
|
+
const PRAZO_DA_LEITURA_MS = 20000
|
|
36
|
+
const PRAZO_DO_DECIDIR_MS = 310000
|
|
37
|
+
/** A gravacao da decisao obvia e local e rapida; passado este teto ela fica incerta, nunca recusada de cara. */
|
|
38
|
+
const PRAZO_DA_DECISAO_MS = 60000
|
|
39
|
+
/** A decisao incerta que o pulse nao mostra nesse tempo vira recusada, e a pergunta volta a esperar o dono. */
|
|
40
|
+
const INCERTEZA_MAXIMA_MS = 10 * 60000
|
|
41
|
+
/** O servidor do nucleo pode conectar depois do inicio da sessao: o papel e conferido de novo nestas esperas. */
|
|
42
|
+
const ESPERAS_DO_PAPEL_MS = [3000, 10000, 30000]
|
|
43
|
+
const ESPERA_LONGA_DO_PAPEL_MS = 300000
|
|
44
|
+
const MAXIMO_DE_AVISOS = 5
|
|
45
|
+
/** O pulse que falha volta com recuo: 30 s, depois o dobro, ate 5 min (o retrato que muda nao fura o recuo). */
|
|
46
|
+
const RECUO_INICIAL_MS = 30000
|
|
47
|
+
const RECUO_MAXIMO_MS = 300000
|
|
48
|
+
const ID = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/
|
|
49
|
+
|
|
50
|
+
// O que o modulo guarda entre ganchos e nao desenha. Uma recarga do modulo zera tudo, e o `session.start` dispara
|
|
51
|
+
// de novo nela; o estado em `$.state` (a fila, o foco, o Decidir e as decisoes obvias) fica.
|
|
52
|
+
let ativa = false
|
|
53
|
+
let desistiu = false
|
|
54
|
+
let ciclo: { cancel: () => void } | undefined
|
|
55
|
+
let tiques = 0
|
|
56
|
+
let assinatura: string | null = null
|
|
57
|
+
let pulseLidoEm: number | null = null
|
|
58
|
+
/** A ultima falha do pulse e o recuo ate a proxima tentativa automatica. */
|
|
59
|
+
let pulseFalhouEm: number | null = null
|
|
60
|
+
let recuoMs = 0
|
|
61
|
+
/** O retrato mudou e o pulse ainda nao foi relido por causa disso (a mudanca vista no recuo nao se perde). */
|
|
62
|
+
let mudancaPendente = false
|
|
63
|
+
let lendo: Promise<void> | undefined
|
|
64
|
+
let pendente = false
|
|
65
|
+
/** Pedidos que esta sessao ja levou ao dono por notificacao. */
|
|
66
|
+
const notificados = new Set<string>()
|
|
67
|
+
/** Pedidos com a decisao obvia sendo registrada agora. */
|
|
68
|
+
const registrando = new Set<string>()
|
|
69
|
+
/** Pedidos com o Decidir em curso. */
|
|
70
|
+
const emDialogo = new Set<string>()
|
|
71
|
+
/** O que o desenho pos na janela de rolagem e ainda nao foi ao registro da ponte. */
|
|
72
|
+
const vistosNoDesenho = new Map<string, MesaMostrado>()
|
|
73
|
+
/** O que ja foi ao registro nesta sessao, por chave e texto. */
|
|
74
|
+
const jaMostrado = new Set<string>()
|
|
75
|
+
/** O ultimo texto publicado na faixa; null antes da primeira publicacao. */
|
|
76
|
+
let ultimaFaixa: string | undefined | null = null
|
|
77
|
+
/** A falha de um trabalho em segundo plano que nem o aviso do painel conseguiu guardar. */
|
|
78
|
+
let problemaSemAviso: string | null = null
|
|
79
|
+
|
|
80
|
+
function mensagem(erro: unknown): string {
|
|
81
|
+
return umaLinha(erro instanceof Error ? erro.message : String(erro))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** A leitura com teto proprio: o relogio da sessao recusa a espera que passar dele. */
|
|
85
|
+
function comPrazo<T>($: EngineInterface, leitura: Promise<T>, ms = PRAZO_DA_LEITURA_MS): Promise<T> {
|
|
86
|
+
return new Promise<T>((resolver, recusar) => {
|
|
87
|
+
const prazo = $.clock.after(ms, () => recusar(new Error(`a leitura passou de ${Math.round(ms / 1000)} s`)))
|
|
88
|
+
leitura.then(v => { prazo.cancel(); resolver(v) }, e => { prazo.cancel(); recusar(e) })
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Um aviso no painel, sem repetir e com teto: o que falhou fica a vista do dono. */
|
|
93
|
+
async function anotarAviso($: EngineInterface, texto: string): Promise<void> {
|
|
94
|
+
await update($, fila, f => (f.avisos.includes(texto) ? f : { ...f, avisos: [...f.avisos, texto].slice(-MAXIMO_DE_AVISOS) }))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Trabalho em segundo plano (o tique, a primeira leitura, os gestos do painel): a falha vai ao aviso do painel; sem
|
|
99
|
+
* como gravar o aviso (a sessao acabou ou o estado recusou), ela fica guardada aqui e o proximo desenho a mostra.
|
|
100
|
+
*/
|
|
101
|
+
function emSegundoPlano($: EngineInterface, trabalho: Promise<unknown>, oQue: string): void {
|
|
102
|
+
trabalho.catch(async erro => {
|
|
103
|
+
const texto = `${oQue} parou: ${mensagem(erro)}`
|
|
104
|
+
try {
|
|
105
|
+
await anotarAviso($, texto)
|
|
106
|
+
} catch (erroDoAviso) {
|
|
107
|
+
problemaSemAviso = `${texto} (o aviso também falhou: ${mensagem(erroDoAviso)})`
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* O papel pela ponte. `null` so quando o `$.ork` nao existe (a ponte nao carregou); uma falha da propria ponte conta
|
|
114
|
+
* como dormente e o papel e conferido de novo depois.
|
|
115
|
+
*/
|
|
116
|
+
async function papelDaSessao($: EngineInterface): Promise<string | null> {
|
|
117
|
+
try {
|
|
118
|
+
return String(await $.ork.papel())
|
|
119
|
+
} catch (erro) {
|
|
120
|
+
return erro instanceof TypeError ? null : 'dormente'
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Liga a Mesa na sessao do dono: o `/mesa`, o tique e a primeira leitura. */
|
|
125
|
+
async function ativar($: EngineInterface): Promise<void> {
|
|
126
|
+
if (ativa) return
|
|
127
|
+
ativa = true
|
|
128
|
+
try {
|
|
129
|
+
await $.command.register({ name: 'mesa', description: 'Abre a Mesa do Dono: o que espera você no Orkastery, com a recomendação na frente', immediate: true })
|
|
130
|
+
} catch (erro) {
|
|
131
|
+
// Sem o /mesa o painel nao abre: a Mesa volta a dormente e tenta de novo na espera longa.
|
|
132
|
+
ativa = false
|
|
133
|
+
$.clock.after(ESPERA_LONGA_DO_PAPEL_MS, () => { emSegundoPlano($, conferirPapel($, ESPERAS_DO_PAPEL_MS.length), 'a conferência do papel') })
|
|
134
|
+
throw erro
|
|
135
|
+
}
|
|
136
|
+
// Depois de uma recarga do modulo, nenhum Decidir esta em curso: o que ficou "abrindo" no estado volta a ter botao.
|
|
137
|
+
await update($, decidindo, soltarAbrindo)
|
|
138
|
+
ciclo?.cancel()
|
|
139
|
+
ciclo = $.clock.every(PERIODO_DO_TIQUE_MS, () => { emSegundoPlano($, tique($), 'o tique da Mesa') })
|
|
140
|
+
emSegundoPlano($, atualizar($), 'a primeira leitura da fila')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Confere o papel: liga no dono, desiste na fase ou sem a ponte, e tenta de novo enquanto dormente. */
|
|
144
|
+
async function conferirPapel($: EngineInterface, tentativa: number): Promise<void> {
|
|
145
|
+
if (ativa || desistiu) return
|
|
146
|
+
const papel = await papelDaSessao($)
|
|
147
|
+
if (papel === 'dono') return ativar($)
|
|
148
|
+
if (papel === 'fase' || papel === null) {
|
|
149
|
+
desistiu = true
|
|
150
|
+
if (papel === null) $.ui.toast('mesa: a ponte ork-ponte não carregou; a Mesa do Dono fica desligada nesta sessão')
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
const espera = ESPERAS_DO_PAPEL_MS[tentativa] ?? ESPERA_LONGA_DO_PAPEL_MS
|
|
154
|
+
$.clock.after(espera, () => { emSegundoPlano($, conferirPapel($, tentativa + 1), 'a conferência do papel') })
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** O painel da Mesa esta posto e a vista do dono. */
|
|
158
|
+
async function painelAVista($: EngineInterface): Promise<boolean> {
|
|
159
|
+
const paineis = await $.ui.panes().catch(() => [])
|
|
160
|
+
return paineis.some(p => p.id === PAINEL && p.isPlaced && p.isShown)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** O tique: a conferencia do retrato (a vista, toda vez; escondido, a cada quatro) e o registro do mostrado. */
|
|
164
|
+
async function tique($: EngineInterface): Promise<void> {
|
|
165
|
+
if (!ativa) return
|
|
166
|
+
tiques++
|
|
167
|
+
const aVista = await painelAVista($)
|
|
168
|
+
if (aVista || tiques % TIQUES_ESCONDIDO === 0) await conferirMudanca($, aVista)
|
|
169
|
+
await registrarMostrados($)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** O retrato da secao `hitl` como sinal de mudanca, e o pulse quando o retrato muda ou o pulse fica velho (D3). */
|
|
173
|
+
async function conferirMudanca($: EngineInterface, aVista: boolean): Promise<void> {
|
|
174
|
+
const agora = await $.clock.now()
|
|
175
|
+
try {
|
|
176
|
+
const r = await comPrazo($, $.ork.retrato({ secao: 'hitl' }))
|
|
177
|
+
const nova = assinaturaDoRetrato(r.retrato)
|
|
178
|
+
if (assinatura !== null && nova !== assinatura) mudancaPendente = true
|
|
179
|
+
assinatura = nova
|
|
180
|
+
} catch (erro) {
|
|
181
|
+
await anotarAviso($, `não li o retrato do núcleo: ${mensagem(erro)}`)
|
|
182
|
+
}
|
|
183
|
+
const idade = pulseLidoEm === null ? Number.POSITIVE_INFINITY : agora - pulseLidoEm
|
|
184
|
+
if (pulseFalhouEm !== null && agora - pulseFalhouEm < recuoMs) return
|
|
185
|
+
// Decisao obvia incerta de pedido ainda na fila: o pulse e relido a cada conferencia ate ela se resolver.
|
|
186
|
+
const naFila = new Set((await read($, fila)).perguntas.map(p => p.pedidoId))
|
|
187
|
+
const incerta = Object.entries(await read($, obvias)).some(([id, o]) => o.estado === 'incerta' && naFila.has(id))
|
|
188
|
+
if (mudancaPendente || incerta || idade >= (aVista ? IDADE_DO_PULSE_A_VISTA_MS : IDADE_DO_PULSE_ESCONDIDO_MS)) {
|
|
189
|
+
mudancaPendente = false
|
|
190
|
+
await atualizar($)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Uma leitura por vez; pedido que chega no meio volta a rodar logo depois, e quem pediu espera as duas. */
|
|
195
|
+
async function atualizar($: EngineInterface): Promise<void> {
|
|
196
|
+
if (!ativa) return
|
|
197
|
+
if (lendo) {
|
|
198
|
+
pendente = true
|
|
199
|
+
return lendo
|
|
200
|
+
}
|
|
201
|
+
lendo = (async () => {
|
|
202
|
+
try {
|
|
203
|
+
do {
|
|
204
|
+
pendente = false
|
|
205
|
+
await lerUmaVez($)
|
|
206
|
+
} while (pendente)
|
|
207
|
+
} finally {
|
|
208
|
+
lendo = undefined
|
|
209
|
+
}
|
|
210
|
+
})()
|
|
211
|
+
return lendo
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** O pulse, as decisoes ja mostradas e os pedidos de cada thread com pergunta; depois a decisao obvia e o aviso. */
|
|
215
|
+
async function lerUmaVez($: EngineInterface): Promise<void> {
|
|
216
|
+
const agora = await $.clock.now()
|
|
217
|
+
let pulse: unknown
|
|
218
|
+
try {
|
|
219
|
+
pulse = await comPrazo($, $.ork.ler({ consulta: 'pulse' }))
|
|
220
|
+
} catch (erro) {
|
|
221
|
+
const motivo = mensagem(erro)
|
|
222
|
+
pulseFalhouEm = agora
|
|
223
|
+
recuoMs = Math.min(recuoMs ? recuoMs * 2 : RECUO_INICIAL_MS, RECUO_MAXIMO_MS)
|
|
224
|
+
await update($, fila, anterior => ({ ...anterior, erro: motivo }))
|
|
225
|
+
await publicarFaixa($)
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
pulseLidoEm = agora
|
|
229
|
+
pulseFalhouEm = null
|
|
230
|
+
recuoMs = 0
|
|
231
|
+
const avisos: string[] = []
|
|
232
|
+
let vistas = new Set<string>()
|
|
233
|
+
let jaNotificados = new Set<string>()
|
|
234
|
+
try {
|
|
235
|
+
const registro = await comPrazo($, $.ork.mostrados())
|
|
236
|
+
vistas = decisoesVistas(registro)
|
|
237
|
+
jaNotificados = pedidosNotificados(registro)
|
|
238
|
+
} catch (erro) {
|
|
239
|
+
avisos.push(`não li o registro do que a Mesa já mostrou: ${mensagem(erro)}`)
|
|
240
|
+
}
|
|
241
|
+
const base = filaDoPulse(pulse, vistas, agora)
|
|
242
|
+
const perguntas: MesaPergunta[] = []
|
|
243
|
+
for (const thread of threadsComPergunta(base.perguntas)) {
|
|
244
|
+
const daThread = base.perguntas.filter(p => p.thread === thread)
|
|
245
|
+
try {
|
|
246
|
+
const pendentes = jsonDoMcp(await comPrazo($, $.mcp.call('orkastery', 'ork_hitl_pending', { threadId: thread })))
|
|
247
|
+
for (const p of daThread) {
|
|
248
|
+
const conferida = conferirNoNucleo(p, pendentes)
|
|
249
|
+
if (conferida) perguntas.push(conferida)
|
|
250
|
+
}
|
|
251
|
+
} catch (erro) {
|
|
252
|
+
avisos.push(`não conferi os pedidos da ${thread} no núcleo: ${mensagem(erro)}`)
|
|
253
|
+
perguntas.push(...daThread)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const nova: MesaFila = { ...base, perguntas: ordenarPerguntas(perguntas), lidoEm: agora, erro: null, avisos }
|
|
257
|
+
await update($, fila, () => nova)
|
|
258
|
+
const registrou = await registrarObvias($, nova, agora)
|
|
259
|
+
await notificarNovas($, nova, jaNotificados)
|
|
260
|
+
await publicarFaixa($)
|
|
261
|
+
await registrarMostrados($)
|
|
262
|
+
// A decisao pedida agora chega no lote seguinte: o pulse e relido logo depois (e a incerta se resolve nele).
|
|
263
|
+
if (registrou) pendente = true
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* A decisao obvia (D5 e D6): para cada pergunta conferida no nucleo que passa no teste das quatro condicoes e que a
|
|
268
|
+
* Mesa ainda nao decidiu (nesta sessao, nem na janela do pulse), uma chamada de `ork_decision_record`. Nunca responde
|
|
269
|
+
* o pedido e nunca abre o dialogo. Chamada que falha ou passa do prazo fica `incerta`, porque a escrita pode ter
|
|
270
|
+
* chegado ao nucleo: o pulse e relido ate ela aparecer (`registrada`) ou vencer a incerteza (`recusada`: a pergunta
|
|
271
|
+
* volta a esperar o dono, com o motivo no cartao). Decisao que aparece no pulse depois sempre vira `registrada`.
|
|
272
|
+
*/
|
|
273
|
+
async function registrarObvias($: EngineInterface, f: MesaFila, agora: number): Promise<boolean> {
|
|
274
|
+
let pediu = false
|
|
275
|
+
const ja = await read($, obvias)
|
|
276
|
+
// A incerta de pedido que saiu da fila (respondido, substituido ou vencido) nao tem mais o que confirmar.
|
|
277
|
+
const naFila = new Set(f.perguntas.map(p => p.pedidoId))
|
|
278
|
+
const sairam = Object.keys(ja).filter(id => ja[id]?.estado === 'incerta' && !naFila.has(id))
|
|
279
|
+
if (sairam.length) await update($, obvias, o => Object.fromEntries(Object.entries(o).filter(([id]) => !sairam.includes(id))))
|
|
280
|
+
for (const p of f.perguntas) {
|
|
281
|
+
const anterior = ja[p.pedidoId]
|
|
282
|
+
if (anterior) {
|
|
283
|
+
// A incerteza vence em 10 min ou no prazo do pedido, o que vier antes: o dono ainda precisa de tempo para decidir.
|
|
284
|
+
const prazo = Date.parse(p.prazo ?? '')
|
|
285
|
+
const vence = Math.min(anterior.em + INCERTEZA_MAXIMA_MS, Number.isFinite(prazo) ? prazo : Number.POSITIVE_INFINITY)
|
|
286
|
+
if (anterior.estado !== 'registrada' && jaDecidida(f.decisoes, p)) {
|
|
287
|
+
await update($, obvias, o => ({ ...o, [p.pedidoId]: { estado: 'registrada', texto: 'registrada no núcleo (conferida no pulse)', em: agora } }))
|
|
288
|
+
} else if (anterior.estado === 'incerta' && agora >= vence) {
|
|
289
|
+
await update($, obvias, o => ({ ...o, [p.pedidoId]: { estado: 'recusada',
|
|
290
|
+
texto: `o pulse não mostrou a decisão gravada a tempo (${anterior.texto}); vale como pergunta`, em: agora } }))
|
|
291
|
+
}
|
|
292
|
+
continue
|
|
293
|
+
}
|
|
294
|
+
if (registrando.has(p.pedidoId) || !p.conferida) continue
|
|
295
|
+
const teste = testeDaObvia(p, agora)
|
|
296
|
+
if (!teste.obvia) continue
|
|
297
|
+
if (jaDecidida(f.decisoes, p)) {
|
|
298
|
+
await update($, obvias, o => ({ ...o, [p.pedidoId]: { estado: 'registrada', texto: 'já registrada pela Mesa', em: agora } }))
|
|
299
|
+
continue
|
|
300
|
+
}
|
|
301
|
+
registrando.add(p.pedidoId)
|
|
302
|
+
let obvia: MesaObvia
|
|
303
|
+
try {
|
|
304
|
+
const args = argsDaDecisaoObvia(p, teste.alternativa, teste.referencia, new Date(agora).toISOString())
|
|
305
|
+
let resposta: unknown
|
|
306
|
+
try {
|
|
307
|
+
resposta = await comPrazo($, $.mcp.call('orkastery', 'ork_decision_record', args), PRAZO_DA_DECISAO_MS)
|
|
308
|
+
} catch (erro) {
|
|
309
|
+
// Sem resposta do nucleo (prazo ou transporte): a escrita pode ter chegado.
|
|
310
|
+
resposta = undefined
|
|
311
|
+
obvia = { estado: 'incerta', texto: mensagem(erro), em: agora }
|
|
312
|
+
}
|
|
313
|
+
if (resposta !== undefined) {
|
|
314
|
+
try {
|
|
315
|
+
jsonDoMcp(resposta)
|
|
316
|
+
obvia = { estado: 'registrada', texto: 'registrada no núcleo', em: agora }
|
|
317
|
+
} catch (erro) {
|
|
318
|
+
// O nucleo respondeu com recusa: nada foi gravado.
|
|
319
|
+
obvia = { estado: 'recusada', texto: mensagem(erro), em: agora }
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
} finally {
|
|
323
|
+
registrando.delete(p.pedidoId)
|
|
324
|
+
}
|
|
325
|
+
const final = obvia!
|
|
326
|
+
await update($, obvias, o => ({ ...o, [p.pedidoId]: final }))
|
|
327
|
+
pediu = true
|
|
328
|
+
}
|
|
329
|
+
return pediu
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* A pergunta nova que espera o dono (nao obvia, ainda nao notificada nesta sessao nem no registro da ponte) vira uma
|
|
334
|
+
* notificacao do sistema, uma so por leitura. So a que saiu de fato conta como notificada e entra no registro do
|
|
335
|
+
* mostrado com o canal `notificacao` (D8 e M5): a que nao saiu fica no aviso do painel e volta na leitura seguinte.
|
|
336
|
+
*/
|
|
337
|
+
async function notificarNovas($: EngineInterface, f: MesaFila, jaNotificados: ReadonlySet<string>): Promise<void> {
|
|
338
|
+
const novas = esperamODono(f, await read($, obvias)).filter(p => !notificados.has(p.pedidoId) && !jaNotificados.has(p.pedidoId))
|
|
339
|
+
if (!novas.length) return
|
|
340
|
+
const primeira = novas[0] as MesaPergunta
|
|
341
|
+
const texto = novas.length === 1
|
|
342
|
+
? umaLinha(`${primeira.thread} espera você: ${primeira.pergunta}`)
|
|
343
|
+
: umaLinha(`${novas.length} pedidos esperam você: ${[...new Set(novas.map(p => p.thread))].join(', ')}`)
|
|
344
|
+
let motivo: string | null
|
|
345
|
+
try {
|
|
346
|
+
const enviada = await $.ui.notify(texto, { title: TITULO })
|
|
347
|
+
if (!enviada.isSent && enviada.reason === 'disabled') {
|
|
348
|
+
// O dono desligou as notificacoes: escolha dele, sem aviso e sem nova tentativa; a faixa e o painel seguem.
|
|
349
|
+
for (const p of novas) notificados.add(p.pedidoId)
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
motivo = enviada.isSent ? null : motivoDaNotificacao(enviada.reason)
|
|
353
|
+
} catch (erro) {
|
|
354
|
+
motivo = mensagem(erro)
|
|
355
|
+
}
|
|
356
|
+
if (motivo !== null) {
|
|
357
|
+
await anotarAviso($, `não avisei o dono do pedido novo: ${motivo}; a faixa e o painel seguem`)
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
for (const p of novas) notificados.add(p.pedidoId)
|
|
361
|
+
try {
|
|
362
|
+
for (const p of novas) await $.ork.mostrado({ mod: MOD, canal: 'notificacao', chave: `hitl:${p.pedidoId}`, thread: p.thread, texto })
|
|
363
|
+
} catch (erro) {
|
|
364
|
+
await anotarAviso($, `não registrei a notificação na ponte: ${mensagem(erro)}`)
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** A linha da faixa pela ponte, so quando o texto muda; sem nada, a linha sai da faixa. */
|
|
369
|
+
async function publicarFaixa($: EngineInterface): Promise<void> {
|
|
370
|
+
const texto = textoDaFaixa(await read($, fila), await read($, obvias))
|
|
371
|
+
if (texto === ultimaFaixa) return
|
|
372
|
+
try {
|
|
373
|
+
if (texto === undefined) await $.ork.faixa({ mod: MOD })
|
|
374
|
+
else await $.ork.faixa({ mod: MOD, texto })
|
|
375
|
+
ultimaFaixa = texto
|
|
376
|
+
} catch (erro) {
|
|
377
|
+
await anotarAviso($, `não publiquei a linha da faixa: ${mensagem(erro)}`)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** O desenho anota o que pos na janela; quem registra e o laco de leitura e o tique, fora do desenho. */
|
|
382
|
+
function anotarVistos(vistos: readonly MesaMostrado[]): void {
|
|
383
|
+
for (const m of vistos) vistosNoDesenho.set(`${m.chave}|${m.texto}`, m)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* O que o desenho pos na janela do painel posto e a vista vai ao registro da ponte (M5 do dono, D8), uma vez por
|
|
388
|
+
* chave e texto nesta sessao. Com o painel escondido, o anotado e descartado: o proximo desenho a vista anota de novo.
|
|
389
|
+
*/
|
|
390
|
+
async function registrarMostrados($: EngineInterface): Promise<void> {
|
|
391
|
+
if (vistosNoDesenho.size === 0) return
|
|
392
|
+
if (!await painelAVista($)) {
|
|
393
|
+
vistosNoDesenho.clear()
|
|
394
|
+
return
|
|
395
|
+
}
|
|
396
|
+
for (const [vista, m] of [...vistosNoDesenho]) {
|
|
397
|
+
vistosNoDesenho.delete(vista)
|
|
398
|
+
if (jaMostrado.has(vista)) continue
|
|
399
|
+
try {
|
|
400
|
+
const thread = m.thread && ID.test(m.thread) ? { thread: m.thread } : {}
|
|
401
|
+
await $.ork.mostrado({ mod: MOD, canal: 'painel', chave: m.chave, ...thread, texto: m.texto })
|
|
402
|
+
jaMostrado.add(vista)
|
|
403
|
+
} catch (erro) {
|
|
404
|
+
vistosNoDesenho.set(vista, m)
|
|
405
|
+
await anotarAviso($, `não registrei o que o painel mostrou: ${mensagem(erro)}`)
|
|
406
|
+
return
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* O Decidir (D7): so com o canal `claude-code` disponivel para o pedido, um por pedido, em segundo plano. A chamada
|
|
413
|
+
* leva exatamente o `threadId` e o `pedidoId`; quem responde e o dono, no dialogo nativo do host.
|
|
414
|
+
*/
|
|
415
|
+
async function decidir($: EngineInterface, pedidoId: string): Promise<void> {
|
|
416
|
+
if (!ID.test(pedidoId) || emDialogo.has(pedidoId)) return
|
|
417
|
+
// Marcado antes de qualquer espera: dois toques ao mesmo tempo abrem um dialogo so.
|
|
418
|
+
emDialogo.add(pedidoId)
|
|
419
|
+
let emCurso = false
|
|
420
|
+
try {
|
|
421
|
+
const p = (await read($, fila)).perguntas.find(x => x.pedidoId === pedidoId)
|
|
422
|
+
if (!p || p.canalClaudeCode !== 'disponivel') return
|
|
423
|
+
const pedidoEm = await $.clock.now()
|
|
424
|
+
await update($, decidindo, d => ({ ...d, [pedidoId]: { estado: 'abrindo', texto: 'diálogo pedido', em: pedidoEm } }))
|
|
425
|
+
emCurso = true
|
|
426
|
+
pedirDialogo($, p)
|
|
427
|
+
} finally {
|
|
428
|
+
if (!emCurso) emDialogo.delete(pedidoId)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** O pedido do dialogo ao nucleo, em segundo plano; quem libera o pedido para outro Decidir e o fim desta espera. */
|
|
433
|
+
function pedirDialogo($: EngineInterface, p: MesaPergunta): void {
|
|
434
|
+
const pedidoId = p.pedidoId
|
|
435
|
+
emSegundoPlano($, (async () => {
|
|
436
|
+
let r: MesaDecidindo
|
|
437
|
+
try {
|
|
438
|
+
const resposta = jsonDoMcp(await comPrazo($, $.mcp.call('orkastery', 'ork_request_decision', argsDoDecidir(p)), PRAZO_DO_DECIDIR_MS))
|
|
439
|
+
r = resultadoDoDecidir(resposta, await $.clock.now())
|
|
440
|
+
} catch (erro) {
|
|
441
|
+
r = { estado: 'erro', texto: `o Decidir falhou: ${mensagem(erro)}; use a reserva`, em: await $.clock.now() }
|
|
442
|
+
} finally {
|
|
443
|
+
emDialogo.delete(pedidoId)
|
|
444
|
+
}
|
|
445
|
+
await update($, decidindo, d => ({ ...d, [pedidoId]: r }))
|
|
446
|
+
await atualizar($)
|
|
447
|
+
})(), `o Decidir do pedido ${pedidoId.slice(0, 8)}`)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export const register: Register = on => {
|
|
451
|
+
on('session.start', async ($, e, next) => {
|
|
452
|
+
if (e.isInteractive) await conferirPapel($, 0)
|
|
453
|
+
return next(e)
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
// `/mesa` abre o painel e rele a fila; `/mesa <thread>` (a Torre o usa para a acao `hitl.decide`) mostra so a
|
|
457
|
+
// thread pedida. O comando so existe com a Mesa ligada; a conferencia do papel aqui cobre o comando que sobrou de
|
|
458
|
+
// uma carga anterior.
|
|
459
|
+
on('command.run', { command: 'mesa' }, async ($, e) => {
|
|
460
|
+
if (!ativa && !desistiu) await conferirPapel($, ESPERAS_DO_PAPEL_MS.length)
|
|
461
|
+
if (!ativa) return { text: 'A Mesa do Dono só abre na sessão do dono de um projeto do Orkastery.' }
|
|
462
|
+
const pedida = String(e.args ?? '').trim()
|
|
463
|
+
const thread = ID.test(pedida) ? pedida : null
|
|
464
|
+
await update($, foco, () => thread)
|
|
465
|
+
await update($, pagina, () => 0)
|
|
466
|
+
const aberto = await $.ui.open({ id: PAINEL, title: TITULO })
|
|
467
|
+
await atualizar($)
|
|
468
|
+
const sufixo = thread ? ` (só ${thread})` : ''
|
|
469
|
+
return { text: aberto.isPlaced ? `Mesa do Dono aberta${sufixo}.` : `Mesa do Dono aberta${sufixo}, à espera de lugar: ${aberto.reason}` }
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
// O painel: o placar, o lote de perguntas, as decisoes por voce e as paradas. O desenho nunca escreve: so anota o
|
|
473
|
+
// que pos na janela de rolagem, para o registro do mostrado. Os gestos mudam o estado da Mesa e pedem ao nucleo.
|
|
474
|
+
on('ui.render', { component: 'Pane', requestId: PAINEL }, async ($, e) => {
|
|
475
|
+
const { Box, Text, Button } = $.ui.resolve(e)
|
|
476
|
+
const lida = await read($, fila)
|
|
477
|
+
const estado = {
|
|
478
|
+
fila: problemaSemAviso ? { ...lida, avisos: [...lida.avisos, problemaSemAviso] } : lida, foco: await read($, foco),
|
|
479
|
+
pagina: await read($, pagina), decidindo: await read($, decidindo), obvias: await read($, obvias),
|
|
480
|
+
}
|
|
481
|
+
const linhas = linhasDaMesa(estado, { agora: await $.clock.now() })
|
|
482
|
+
anotarVistos(mostradosVisiveis(linhas, e.props.scroll))
|
|
483
|
+
const total = estado.foco ? estado.fila.perguntas.filter(p => p.thread === estado.foco).length : estado.fila.perguntas.length
|
|
484
|
+
return desenharMesa({ Box, Text, Button }, linhas, {
|
|
485
|
+
decidir: pedidoId => { emSegundoPlano($, decidir($, pedidoId), 'o Decidir') },
|
|
486
|
+
atualizar: () => { emSegundoPlano($, atualizar($), 'a releitura da fila') },
|
|
487
|
+
verTodas: () => { emSegundoPlano($, update($, foco, () => null), 'o Ver todas') },
|
|
488
|
+
// A pagina guardada pode passar do fim depois que a fila encolhe: o passo parte da pagina que esta na tela.
|
|
489
|
+
anteriores: () => { emSegundoPlano($, update($, pagina, n => paginaDoLote(total, paginaDoLote(total, n).pagina - 1).pagina), 'o lote anterior') },
|
|
490
|
+
proximas: () => { emSegundoPlano($, update($, pagina, n => paginaDoLote(total, paginaDoLote(total, n).pagina + 1).pagina), 'o lote seguinte') },
|
|
491
|
+
})
|
|
492
|
+
})
|
|
493
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Contrato do estado da Mesa do Dono (`ork-mesa`, RM-059 fatia F5; ficha M4 do plano dos mods). Sem import nem
|
|
2
|
+
// referencia, como o motor exige de um contrato: as chaves de `$.state` que a Mesa usa ficam declaradas aqui. A
|
|
3
|
+
// Mesa le o nucleo pela ponte (`$.ork.retrato` e `$.ork.ler`) e por tres ferramentas MCP; nada aqui e resposta
|
|
4
|
+
// do dono, e nenhum campo guarda a opcao que o dono escolheu.
|
|
5
|
+
|
|
6
|
+
/** Uma alternativa de um pedido, como a Mesa a mostra: letra no `ork.hitl/v2`, numero no `ork.hitl/v1`. */
|
|
7
|
+
export type MesaAlternativa = {
|
|
8
|
+
chave: string
|
|
9
|
+
texto: string
|
|
10
|
+
acao: string
|
|
11
|
+
/** O que acontece se escolher, em uma linha (so no v2). */
|
|
12
|
+
consequencia: string | null
|
|
13
|
+
recomendada: boolean
|
|
14
|
+
/** O porque da recomendada (so no v2). */
|
|
15
|
+
porque: string | null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A oferta do canal `claude-code` para o pedido, lida do `ork_hitl_pending`; sem leitura, desconhecida. */
|
|
19
|
+
export type MesaCanal = 'disponivel' | 'indisponivel' | 'desconhecido'
|
|
20
|
+
|
|
21
|
+
/** Um pedido aberto que espera o dono, montado do pulse e conferido no `ork_hitl_pending`. */
|
|
22
|
+
export type MesaPergunta = {
|
|
23
|
+
thread: string
|
|
24
|
+
fase: string | null
|
|
25
|
+
modo: string | null
|
|
26
|
+
pedidoId: string
|
|
27
|
+
codigo: string | null
|
|
28
|
+
contrato: 'ork.hitl/v1' | 'ork.hitl/v2'
|
|
29
|
+
alvo: 'gate' | 'session'
|
|
30
|
+
motivo: string
|
|
31
|
+
pergunta: string
|
|
32
|
+
/** A recomendacao em texto, como o nucleo a escreve. */
|
|
33
|
+
recomendacao: string
|
|
34
|
+
alternativas: MesaAlternativa[]
|
|
35
|
+
irreversivel: boolean
|
|
36
|
+
ato: string | null
|
|
37
|
+
tipoDeResposta: string | null
|
|
38
|
+
respostaAceita: string | null
|
|
39
|
+
dependenciaTecnica: boolean
|
|
40
|
+
acaoAoExpirar: string | null
|
|
41
|
+
prazo: string | null
|
|
42
|
+
prazoLocal: string | null
|
|
43
|
+
desdeEm: string | null
|
|
44
|
+
/** O estado do pedido no nucleo (`aberto`, `esperar`, `escalar`, `seguir-recomendada`); `desconhecido` sem conferir. */
|
|
45
|
+
estado: string
|
|
46
|
+
/** O pedido foi conferido agora no `ork_hitl_pending`; sem isso a Mesa nao decide nem oferece o Decidir. */
|
|
47
|
+
conferida: boolean
|
|
48
|
+
canalClaudeCode: MesaCanal
|
|
49
|
+
/** O que o dono digita pelo Telegram ou no terminal, quando o dialogo nao serve. */
|
|
50
|
+
reserva: string | null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Uma parada sem pedido ou um impedimento que so o dono resolve: so leitura. */
|
|
54
|
+
export type MesaOutro = {
|
|
55
|
+
chave: string
|
|
56
|
+
thread: string | null
|
|
57
|
+
fase: string | null
|
|
58
|
+
motivo: string
|
|
59
|
+
texto: string
|
|
60
|
+
recomendacao: string
|
|
61
|
+
comando: string
|
|
62
|
+
desdeEm: string | null
|
|
63
|
+
impedimento: boolean
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Uma decisao que a fabrica (ou a Mesa) tomou pelo dono, como o pulse a traz. */
|
|
67
|
+
export type MesaDecisao = {
|
|
68
|
+
id: string
|
|
69
|
+
thread: string
|
|
70
|
+
fase: string
|
|
71
|
+
em: string
|
|
72
|
+
decidido: string
|
|
73
|
+
porque: string
|
|
74
|
+
comoMudar: string
|
|
75
|
+
custoAgora: string
|
|
76
|
+
custoDepois: string
|
|
77
|
+
quemDecidiu: string
|
|
78
|
+
/** A decisao que esta desfaz, quando desfaz uma. */
|
|
79
|
+
reverte: string | null
|
|
80
|
+
/** A decisao posterior que desfez esta, quando ha. */
|
|
81
|
+
revertidaPor: string | null
|
|
82
|
+
/** Ainda nao mostrada ao dono pela Mesa (registro do mostrado da ponte), das ultimas 24 h e nao revertida. */
|
|
83
|
+
nova: boolean
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type MesaFila = {
|
|
87
|
+
perguntas: MesaPergunta[]
|
|
88
|
+
outros: MesaOutro[]
|
|
89
|
+
decisoes: MesaDecisao[]
|
|
90
|
+
/** Quantas decisoes a janela do pulse tinha; o painel mostra so as primeiras. */
|
|
91
|
+
totalDeDecisoes: number
|
|
92
|
+
lidoEm: number | null
|
|
93
|
+
erro: string | null
|
|
94
|
+
/** O que nao saiu inteiro na ultima leitura (uma conferencia, a faixa, o registro): o painel mostra cada um. */
|
|
95
|
+
avisos: string[]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** O Decidir de um pedido: o dialogo pedido ao nucleo e o que ele devolveu. */
|
|
99
|
+
export type MesaDecidindo = {
|
|
100
|
+
estado: 'abrindo' | 'respondido' | 'sem-resposta' | 'erro'
|
|
101
|
+
texto: string
|
|
102
|
+
em: number
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* A decisao obvia que a Mesa pediu ao nucleo: `registrada` quando ele aceitou ou o pulse a mostra; `incerta` quando a
|
|
107
|
+
* chamada falhou ou passou do prazo e a escrita pode ter chegado (a proxima leitura do pulse decide); `recusada` quando
|
|
108
|
+
* a leitura seguinte nao a mostra.
|
|
109
|
+
*/
|
|
110
|
+
export type MesaObvia = {
|
|
111
|
+
estado: 'registrada' | 'incerta' | 'recusada'
|
|
112
|
+
texto: string
|
|
113
|
+
em: number
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
declare module 'claude-code' {
|
|
117
|
+
interface PluginState {
|
|
118
|
+
'ork-mesa': {
|
|
119
|
+
/** A fila lida do nucleo. */
|
|
120
|
+
fila: MesaFila
|
|
121
|
+
/** A thread que o `/mesa <thread>` pediu; `null` mostra todas. */
|
|
122
|
+
foco: string | null
|
|
123
|
+
/** A pagina do lote de perguntas, de cinco em cinco. */
|
|
124
|
+
pagina: number
|
|
125
|
+
/** O Decidir de cada pedido, pelo id do pedido. */
|
|
126
|
+
decidindo: Record<string, MesaDecidindo>
|
|
127
|
+
/** As decisoes obvias desta sessao, pelo id do pedido. */
|
|
128
|
+
obvias: Record<string, MesaObvia>
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ork-pista",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pista de Entrega do Orkastery no Claude Code: do CHECK verde ao merge, cada entrega numa pista com a prova de cada etapa (bundle, branch publicada, PR, CI ork-verify, conflito com a base e ship_done) e o próximo passo que o núcleo declara. Publica só pelo ork ship publicar, no modo que já autoriza o push; nunca faz merge nem tag.",
|
|
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
|
+
}
|