@orkastery/cli 0.2.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +86 -65
  3. package/adapters/README.md +30 -15
  4. package/adapters/claude-code/.claude-plugin/plugin.json +11 -5
  5. package/adapters/claude-code/README.md +110 -8
  6. package/adapters/claude-code/commands/check.md +4 -3
  7. package/adapters/claude-code/commands/goal.md +3 -2
  8. package/adapters/claude-code/commands/master.md +3 -2
  9. package/adapters/claude-code/commands/onboarding.md +21 -0
  10. package/adapters/claude-code/commands/ork.md +99 -27
  11. package/adapters/claude-code/hooks/hooks.json +6 -1
  12. package/adapters/claude-code/hooks/ork-guard.js +1 -1
  13. package/adapters/claude-code/hooks/ork-sensor.js +66 -0
  14. package/adapters/codex/skills/ork/SKILL.md +57 -0
  15. package/adapters/hermes/README.md +161 -0
  16. package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
  17. package/adapters/hermes/bin/ork-brain.sh +4 -0
  18. package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
  19. package/adapters/hermes/bin/ork-maestro.sh +6 -0
  20. package/adapters/hermes/bin/ork-master-enviar.py +84 -0
  21. package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
  22. package/adapters/hermes/hermes.plugin.json +24 -4
  23. package/adapters/hermes/hitl-ingress/__init__.py +356 -0
  24. package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
  25. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
  26. package/adapters/openclaw/README.md +136 -27
  27. package/adapters/openclaw/bin/ork-brain.sh +4 -0
  28. package/adapters/openclaw/construir.sh +19 -0
  29. package/adapters/openclaw/dist/hitl-ingress.js +199 -0
  30. package/adapters/openclaw/dist/index.js +437 -0
  31. package/adapters/openclaw/openclaw.plugin.json +39 -134
  32. package/adapters/openclaw/package.json +26 -0
  33. package/adapters/openclaw/src/hitl-ingress.ts +180 -0
  34. package/adapters/openclaw/src/index.ts +460 -0
  35. package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
  36. package/adapters/openclaw/tsconfig.json +15 -0
  37. package/assets/docs/markdownlint-cli2.jsonc +22 -0
  38. package/assets/docs/padroes/documentacao-de-produto.md +133 -0
  39. package/assets/docs/padroes/roadmap-de-produto.md +98 -0
  40. package/assets/docs/produto/README.md +14 -0
  41. package/assets/docs/produto/_modelo-feature.md +59 -0
  42. package/assets/docs/roadmap/README.md +14 -0
  43. package/assets/docs/roadmap/_modelo-item.md +73 -0
  44. package/assets/orkmind-native-schema.json +32 -0
  45. package/assets/orkmind_bridge.py +357 -0
  46. package/assets/orkmind_fixture.py +67 -0
  47. package/assets/orkmind_prospective.py +138 -0
  48. package/assets/reference-tariffs-i07.json +23 -0
  49. package/dist/adapters/claude-bg.js +488 -39
  50. package/dist/adapters/codex-controller-sensor.js +449 -0
  51. package/dist/adapters/codex-controller-worker.js +361 -0
  52. package/dist/adapters/codex-controller.js +252 -0
  53. package/dist/adapters/codex-events.js +205 -0
  54. package/dist/adapters/codex-question.js +26 -0
  55. package/dist/adapters/codex-runner.js +133 -0
  56. package/dist/adapters/codex.js +394 -0
  57. package/dist/agents-md.js +84 -0
  58. package/dist/auditoria.js +12 -9
  59. package/dist/auditrun.js +5 -4
  60. package/dist/board.js +174 -23
  61. package/dist/branch-de-estado.js +136 -0
  62. package/dist/canarios-hitl.js +177 -0
  63. package/dist/canarios-i43.js +543 -0
  64. package/dist/canarios-pulse.js +147 -0
  65. package/dist/canarios-sensores.js +129 -0
  66. package/dist/canarios.js +111 -2
  67. package/dist/catalogo.js +9 -0
  68. package/dist/ci.js +223 -0
  69. package/dist/ciclos.js +2 -1
  70. package/dist/claim-lint.js +64 -0
  71. package/dist/claims.js +60 -0
  72. package/dist/company-brain-capture.js +195 -0
  73. package/dist/company-brain-cli.js +123 -0
  74. package/dist/company-brain-client.js +61 -0
  75. package/dist/company-brain-contract.js +166 -0
  76. package/dist/company-brain-journal.js +192 -0
  77. package/dist/company-brain-mcp.js +33 -0
  78. package/dist/company-brain-migration.js +60 -0
  79. package/dist/company-brain-source.js +177 -0
  80. package/dist/company-brain-worker.js +18 -0
  81. package/dist/conducao-texto.js +61 -0
  82. package/dist/conducao.js +876 -0
  83. package/dist/contrato-publico.js +39 -0
  84. package/dist/creation-operation-store.js +251 -0
  85. package/dist/creation-operation.js +148 -0
  86. package/dist/decisao-autonoma.js +183 -0
  87. package/dist/delegation.js +79 -0
  88. package/dist/demo.js +120 -0
  89. package/dist/docs.js +828 -0
  90. package/dist/doctor.js +184 -25
  91. package/dist/entrega-pr.js +117 -0
  92. package/dist/escopo-escrita.js +71 -0
  93. package/dist/estado-thread.js +222 -0
  94. package/dist/evalrunner.js +12 -0
  95. package/dist/fabrica-estado.js +329 -0
  96. package/dist/fabrica-publicar.js +76 -0
  97. package/dist/fix.js +22 -1
  98. package/dist/gates.js +59 -11
  99. package/dist/handoff.js +39 -23
  100. package/dist/hitl-canais.js +333 -0
  101. package/dist/hitl-classificacao.js +131 -0
  102. package/dist/hitl-contract.js +465 -0
  103. package/dist/hitl-estado.js +136 -0
  104. package/dist/hitl-gates.js +554 -0
  105. package/dist/hitl-ingress-receipt.js +382 -0
  106. package/dist/hitl-local-atestado.js +57 -0
  107. package/dist/hitl-local-receipt.js +328 -0
  108. package/dist/hitl-local.js +143 -0
  109. package/dist/hitl-lock.js +139 -0
  110. package/dist/hitl-lote.js +223 -0
  111. package/dist/hitl-native-offer.js +97 -0
  112. package/dist/hitl-native.js +65 -0
  113. package/dist/hitl-presentation.js +209 -0
  114. package/dist/hitl-public-receipt.js +176 -0
  115. package/dist/hitl-resumo.js +209 -0
  116. package/dist/hitl-sessions.js +475 -0
  117. package/dist/hitl.js +711 -0
  118. package/dist/horario.js +269 -0
  119. package/dist/hosts.js +198 -22
  120. package/dist/index.js +1741 -94
  121. package/dist/indice.js +99 -0
  122. package/dist/init.js +28 -3
  123. package/dist/integracoes-locais.js +17 -0
  124. package/dist/leases.js +65 -21
  125. package/dist/ledger-stats.js +272 -0
  126. package/dist/ledger.js +109 -2
  127. package/dist/licoes.js +221 -0
  128. package/dist/liveness.js +218 -0
  129. package/dist/maestro-actions.js +51 -0
  130. package/dist/maestro-authority.js +152 -0
  131. package/dist/maestro-cli.js +97 -0
  132. package/dist/maestro-contract.js +55 -0
  133. package/dist/maestro-discovery.js +132 -0
  134. package/dist/maestro-runtime.js +268 -0
  135. package/dist/maestro-snapshot.js +86 -0
  136. package/dist/maestro-sources.js +224 -0
  137. package/dist/manifest.js +165 -6
  138. package/dist/maquina.js +102 -0
  139. package/dist/master-audit.js +101 -0
  140. package/dist/master-batch.js +43 -0
  141. package/dist/master-digest.js +149 -0
  142. package/dist/master-migracao.js +155 -0
  143. package/dist/master.js +361 -77
  144. package/dist/mcp-artifacts.js +234 -0
  145. package/dist/mcp-git.js +433 -0
  146. package/dist/mcp-install.js +310 -0
  147. package/dist/mcp-maestro.js +25 -0
  148. package/dist/mcp-server.js +469 -0
  149. package/dist/mcp-ship.js +436 -0
  150. package/dist/mcp-verify.js +173 -0
  151. package/dist/memoria-humana.js +207 -0
  152. package/dist/memoria.js +314 -78
  153. package/dist/memory-migration.js +279 -0
  154. package/dist/memory-prospective.js +148 -0
  155. package/dist/modos-migracao.js +191 -0
  156. package/dist/modos.js +151 -15
  157. package/dist/monitor-lock.js +110 -0
  158. package/dist/objective.js +449 -0
  159. package/dist/ocupacao.js +267 -0
  160. package/dist/onboarding.js +303 -0
  161. package/dist/orkmind.js +389 -90
  162. package/dist/orquestracao.js +96 -69
  163. package/dist/phase.js +612 -151
  164. package/dist/playbook-capabilities.js +150 -0
  165. package/dist/playbook-contracts.js +188 -0
  166. package/dist/policies.js +72 -0
  167. package/dist/portfolio-context.js +61 -0
  168. package/dist/portfolio.js +180 -0
  169. package/dist/preflight.js +207 -0
  170. package/dist/process-audit.js +112 -0
  171. package/dist/project-state.js +93 -0
  172. package/dist/prompts.js +25 -5
  173. package/dist/prova-minima.js +112 -0
  174. package/dist/pulse-cadencia.js +163 -0
  175. package/dist/pulse-consentimento.js +260 -0
  176. package/dist/pulse-delivery.js +323 -0
  177. package/dist/pulse-resposta.js +542 -0
  178. package/dist/pulse.js +262 -0
  179. package/dist/ratelimit.js +35 -2
  180. package/dist/recall.js +80 -3
  181. package/dist/redacao-saida.js +45 -0
  182. package/dist/redacao-url.js +66 -0
  183. package/dist/retry.js +640 -118
  184. package/dist/roadmap-reservas.js +243 -0
  185. package/dist/runtime-ambiente.js +38 -0
  186. package/dist/runtime-context.js +215 -0
  187. package/dist/runtime-profiles.js +869 -0
  188. package/dist/runtimes.js +103 -0
  189. package/dist/sandbox.js +9 -0
  190. package/dist/session-events.js +203 -0
  191. package/dist/session-watcher-claude.js +637 -0
  192. package/dist/session-watcher.js +700 -0
  193. package/dist/sessoes-adopt.js +154 -0
  194. package/dist/sessoes-inventario.js +159 -0
  195. package/dist/sessoes.js +16 -35
  196. package/dist/setup.js +691 -0
  197. package/dist/ship.js +168 -19
  198. package/dist/slug.js +1 -1
  199. package/dist/thread-close.js +115 -0
  200. package/dist/thread.js +142 -28
  201. package/dist/tokens.js +1 -1
  202. package/dist/util.js +4 -2
  203. package/dist/verify-sandbox.js +265 -0
  204. package/dist/verify.js +270 -25
  205. package/dist/versao.js +60 -0
  206. package/dist/worktree.js +31 -2
  207. package/dist/write-activation.js +291 -0
  208. package/dist/yaml.js +5 -1
  209. package/eval/casos/master-metrics.json +1 -1
  210. package/eval/casos/onboarding.json +102 -0
  211. package/eval/casos/orkastery-bootstrap.json +37 -3
  212. package/eval/casos/scope-check-capability-map.json +45 -7
  213. package/eval/casos/ship-release.json +3 -3
  214. package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
  215. package/eval/fixtures/b2-master-log/caso.json +1 -1
  216. package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
  217. package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
  218. package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
  219. package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
  220. package/eval/fixtures/fx-codex-dry/caso.json +18 -0
  221. package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
  222. package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
  223. package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
  224. package/eval/fixtures/fx-happy/caso.json +6 -4
  225. package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
  226. package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
  227. package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
  228. package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
  229. package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
  230. package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
  231. package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
  232. package/eval/fixtures/fx-omnicanal/caso.json +17 -0
  233. package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
  234. package/monitor/company-brain.cjs +17 -0
  235. package/monitor/pulse-scope.cjs +45 -0
  236. package/monitor/pulse.cron +18 -0
  237. package/monitor/varredura-pulse.sh +27 -0
  238. package/package.json +28 -7
  239. package/references/definition-of-done.md +2 -2
  240. package/schemas/claims.schema.json +25 -0
  241. package/schemas/company-brain.schema.json +1158 -0
  242. package/schemas/creation-operation.schema.json +365 -0
  243. package/schemas/maestro-snapshot.schema.json +2716 -0
  244. package/skills/README.md +3 -3
  245. package/skills/core/onboarding/SKILL.md +50 -0
  246. package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
  247. package/skills/core/thread-state/SKILL.md +3 -1
  248. package/skills/governance/decision-triage/SKILL.md +4 -4
  249. package/skills/governance/narrative-guardian/SKILL.md +1 -1
  250. package/skills/governance/roadmap-keeper/SKILL.md +1 -1
  251. package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
  252. package/skills/observability/thread-tracing/SKILL.md +1 -1
  253. package/skills/phases/check-quality/SKILL.md +2 -2
  254. package/skills/phases/goal-definition/SKILL.md +1 -1
  255. package/skills/phases/master-metrics/SKILL.md +9 -7
  256. package/skills/phases/plan-specification/SKILL.md +1 -1
  257. package/skills/phases/ship-release/SKILL.md +1 -1
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: orkastery-devmaster
3
- description: "Roteador do Orkastery no Hermes: le a #TAG de conducao do pedido do builder, chama o `ork` para abrir e conduzir a thread pelas 6 fases, e apresenta os gates ao humano. Zero regra de negocio: quem decide e o humano, quem verifica e o `ork`."
3
+ description: "Reconhece orkastery maestro no Hermes: consulta panorama pelo ork_maestro, conduz demandas autorizadas e apresenta HITL na conversa."
4
4
  bucket: hermes
5
5
  roteia: "ork modos --do-pedido | ork thread new | ork phase run | ork ship | ork master"
6
6
  license: MIT
@@ -8,6 +8,20 @@ license: MIT
8
8
 
9
9
  # Orkastery no Hermes
10
10
 
11
+ Mensagem literal `orkastery maestro`: use `ork_maestro` (wrapper de `ork maestro --json`)
12
+ para consultar panorama do projeto atual. Não abra thread por essa consulta. Apresente
13
+ fontes/lacunas e próximas ações; ausência/ambiguidade de projeto exige contexto permitido.
14
+ Em sessão de fase já despachada, siga o bloco recebido sem abrir outra orquestração.
15
+ Ações usam os comandos tipados do núcleo com precondições e readback.
16
+
17
+ Usabilidade HITL tem prioridade máxima: tópicos, recomendação e opções claras, UUID
18
+ interno. Telegram é opcional. Só anuncie ingresso humano disponível quando o callback
19
+ do host estiver homologado e configurado; texto de tool não é identidade humana.
20
+ Sem ingresso no canal escolhido, conserve o pedido pendente e explique o motivo.
21
+ Não altere runtime, provider, perfil filho ou sandbox para contornar falha.
22
+ Horário para o dono sai no fuso dele (`owner.timezone`): use `prazoLocal` e os fatos
23
+ `*Local` do JSON (ex.: `19/09 15:16 (horário de Brasília)`), nunca o ISO em UTC.
24
+
11
25
  ## O que esta skill e
12
26
 
13
27
  O roteador do Orkastery dentro do Hermes, e **so isso**. Ela nao conduz fase por conta propria,
@@ -23,30 +37,47 @@ mora no `ork`; aqui ficou o roteador.**
23
37
 
24
38
  Sempre que o builder pedir trabalho de produto num repositorio que tem `orkastery.yaml`.
25
39
 
40
+ ## Onboarding do projeto
41
+
42
+ Quando o builder pedir onboarding, execute `ork onboarding` para obter a pauta do núcleo.
43
+ Consulte `ork onboarding show --json` para retomar as etapas pendentes. Conduza a conversa
44
+ pela pauta devolvida e grave cada resposta pública com `ork onboarding set <etapa>
45
+ --conteudo <JSON> --por <quem>`, transportando argumentos como dados. A pauta e a validação
46
+ pertencem ao núcleo; não mantenha lista de etapas no host nem invente respostas.
47
+ Credenciais são somente nomes de variáveis; valores secretos ficam em `~/.hermes/.env`.
48
+ Use `ork onboarding reset [etapa]` quando solicitado e `ork onboarding sync --json` para
49
+ publicação opcional. Apresente o motivo tipado de degradação quando a memória não publicar.
50
+ Onboarding não exige criar thread de produto. Ao concluir, apresente `ork onboarding show --json`.
51
+
26
52
  ## Passo 1: o modo sai do pedido, nao de configuracao
27
53
 
28
54
  ```bash
29
55
  MODO=$(ork modos --do-pedido "<pedido inteiro do builder>")
30
56
  ```
31
57
 
32
- O comando usa a mesma funcao do nucleo que reconhece `#Look`, `#Ork`, `#Classic`, `#Maestro` e
33
- `#Auto`. Sem tag no texto vale o `conduction.default_mode` do manifesto. **Nao reimplemente esse
34
- parse aqui**, e nao valide o modo: `conduction.allowed_modes` e conferido dentro do
35
- `ork thread new`, que recusa com erro tipado quando o projeto nao permite o modo pedido.
58
+ O comando usa a mesma funcao do nucleo que reconhece `#Classic`, `#Maestro`, `#Auto` e `#Fast`.
59
+ Sem tag no texto vale o `conduction.default_mode` do manifesto. **Nao reimplemente esse parse
60
+ aqui**, e nao valide o modo: `ork thread new` confere `conduction.allowed_modes` e recusa com erro tipado.
61
+
62
+ `#Look` e `#Ork` foram aposentados pela I-43. Um pedido com uma dessas tags faz o comando
63
+ sair com codigo != 0 e a recusa tipada `modo.aposentado`, que ja nomeia o substituto vivo.
64
+ **Repasse a recusa ao builder como ela veio**: nao traduza para outro modo e nao siga com o
65
+ default, porque trocar o regime de supervisao pelas costas de quem pediu e o defeito que a
66
+ recusa existe para impedir.
36
67
 
37
68
  | #TAG | Pausas | Quando o builder escolhe |
38
69
  |---|---|---|
39
- | `#Look` | 6 | Risco alto, passo irreversivel, dominio novo |
40
- | `#Ork` | 5 | Veredito separado da execucao e da evidencia |
41
- | `#Classic` | 3 | O padrao: premissas delicadas, entrega solta, score em batch |
42
- | `#Maestro` | 1 | Solucao clara e agil, score em batch |
70
+ | `#Classic` | 3 | O padrao do `ork init`: premissas delicadas |
71
+ | `#Maestro` | 1 | Solucao clara e agil: uma pausa para as premissas |
43
72
  | `#Auto` | 0 | Docs, estudos, configuracoes, auditorias |
73
+ | `#Fast` | 0 | Pedido pequeno e claro: so a GO; o push pede autorizacao |
44
74
 
45
75
  ## Passo 2: antes de abrir, confira a maquina
46
76
 
47
77
  ```bash
48
78
  ork doctor # sai != 0 quando o despacho nao vale (custo, runtime, ambiente)
49
79
  ork board plan # quem avanca agora, quem espera e por que
80
+ ork brain status # contrato Company Brain e tenant efetivo
50
81
  ```
51
82
 
52
83
  Um `fail` do doctor para o ciclo antes de ele abrir. Isso e economia, nao obstaculo.
@@ -63,33 +94,54 @@ ork phase run <thread> GO --prompt "<uma tarefa do PLAN por vez>"
63
94
  ork verify <thread>
64
95
  ork phase run <thread> CHECK --prompt "<escopo da verificacao>"
65
96
  ork ship <thread> --para main --autorizar-push "<quem>"
66
- ork master <thread> --score <0-5> --justificativa "<texto>" --por "<quem>"
97
+ ork master <thread>
67
98
  ```
68
99
 
100
+ Quando o dono pedir estado do Company Brain, use o binário instalado `ork_brain` para
101
+ `status`, `query` ou `get`. A identidade vem exclusivamente do login autenticado do
102
+ OrkMind; nunca aceite principal, DSN ou raiz vindos da conversa. Consultas devem levar a
103
+ thread explícita e continuar somente leitura.
104
+
69
105
  Cada `phase run` grava o prompt exato com sha256 e registra no ledger. O `ork` reverifica no
70
106
  runtime que a sessao existe: self-report de despacho nao vale como evidencia.
71
107
 
72
108
  ## Passo 4: apresentar os gates ao humano
73
109
 
74
110
  O Hermes e o podio: e aqui que o builder ve a evidencia e da o veredito. Quando o bloco pausa,
75
- mostre o que o `ork` produziu e espere. A liberacao vira evento:
111
+ mostre o que o `ork` produziu e espere. A resposta entra pelo gateway autenticado como update Telegram original, com o remetente e o chat validados por allowlist. Use `ork_gate_answer` ou `ork_session_answer`, preservando o id do pedido e a referência da mensagem. Nunca sintetize um update nem use o nome do builder em uma aprovação criada pelo agente.
76
112
 
77
113
  ```bash
78
- ork gate approve <thread> <objetivo|premissas|evidencias|push|score> --por "<quem>"
114
+ python3 adapters/hermes/bin/ork-hitl-answer.py ork_gate_answer <thread> <pedido> < update.json
115
+ python3 adapters/hermes/bin/ork-hitl-answer.py ork_session_answer <thread> <pedido> < update.json
79
116
  ```
80
117
 
81
- Quando o bloco nao pausa (`#Maestro`, `#Auto`), o `ork` registra a decisao autonoma com quem
82
- decidiu, com que evidencia e por que. **O modo afrouxa a pausa, NUNCA a verificacao.**
118
+ `ORK_HITL_TELEGRAM_USERS` e `ORK_HITL_TELEGRAM_CHATS` contêm os ids permitidos, separados por vírgula. Sem configuração o comando recusa. A profundidade já vem do núcleo. Expiração só espera ou escala, nunca aprova. Push e score não são delegáveis.
83
119
 
84
- ## Passo 5: o score
120
+ ### Atencao em camadas (I-41) e os formatos que o ingresso aceita
85
121
 
86
- O score de 0 a 5 e humano em todos os modos. Nos modos sem pausa de MASTER, ele vai para a fila:
122
+ O nucleo monta e o Hermes so transporta: (1) UM resumo recorrente com as contagens e
123
+ `Posso te mandar as perguntas agora?`; (2) o dono responde com o codigo e a letra (`K7QX a`);
124
+ (3) com o sim, ate cinco objetivas a–d com uma recomendada, respondidas numa linha (`1a 2c 3b`).
125
+ Decisao obvia chega tomada e informada no resumo (`ork decisao registrar`). Cadencia (I-50): so a tag na mensagem (`#OrkPulseOff` 8h, `#OrkPulseOn` 2h, `#OrkPulseOn-15m`, `-30m`, `-60m`); pedida no meio da frase, devolva a tag exata para o dono mandar sozinha.
87
126
 
88
- ```bash
89
- ork master --batch
90
- ```
127
+ O plugin `orkastery-hitl` so registra esses tres formatos e `/ork gate|session <thread> <pedido> <resposta>`,
128
+ em texto puro, da allowlist, com menos de 60 s. "1. Aprovado" em conversa **nao registra nada**:
129
+ devolva na hora a linha exata, com o id atual de `ork gate request <thread>` (o id muda a cada
130
+ renovacao). **Voce nunca responde pelo dono**: `ork gate answer` do terminal falha com
131
+ `proveniencia em argv diverge do envelope`, e essa e a defesa funcionando.
132
+
133
+ Quando o bloco nao pausa (`#Maestro`, `#Auto`, `#Fast`), o `ork` registra a decisao autonoma com quem
134
+ decidiu, com que evidencia e por que. **O modo afrouxa a pausa, NUNCA a verificacao.**
135
+
136
+ ## Passo 5: a entrega e o MASTER
137
+
138
+ O MASTER fecha com POSTMORTEM tipado e indice derivado do ledger; ninguem digita nota para a fila
139
+ andar. `ork master` mostra as entregas; `ork master --aceitar-omissao` aceita as pendentes com
140
+ registro; a nota humana sobrescreve. `ork master --batch` saiu na I-43 (`master.fila-aposentada`).
91
141
 
92
- Fila que ninguem passa e score que nao existe. Traga a fila ao builder.
142
+ Memoria "degradada" tem dois portoes: a variavel da DSN no ambiente do processo
143
+ (`memory_degraded` com `dsn.env-ausente`) e a ativacao de escrita com aceite humano
144
+ (`write.activation.disabled`). Diagnostico: `ork memory status` e `ork doctor`.
93
145
 
94
146
  ## Racionalizacoes comuns
95
147
 
@@ -99,7 +151,8 @@ Fila que ninguem passa e score que nao existe. Traga a fila ao builder.
99
151
  | "Guardo as regras da metodologia nesta skill" | Metodologia em prosa e metodologia que ninguem verifica. Ela mora no `ork`; aqui fica o roteamento. |
100
152
  | "Valido o modo antes de chamar o `ork`" | Validacao duplicada e validacao que vai divergir. `conduction.allowed_modes` e do nucleo. |
101
153
  | "O builder confia, pulo o gate desta vez" | Gate pulado e gate que nao existe. Se o modo nao pausa, o `ork` registra a decisao autonoma; se pausa, ele espera. |
102
- | "Score eu estimo pelo resultado" | O score e do humano em todos os modos. Estimar nota destroi a unica metrica de conducao do produto. |
154
+ | "Score eu estimo pelo resultado" | O indice sai do ledger e a nota, quando existe, e do humano. Estimar nota destroi a metrica de conducao. |
155
+ | "O dono respondeu na conversa, eu registro" | So o ingresso registra, com prova HMAC do update. Devolva a linha exata para ele mandar. |
103
156
 
104
157
  ## Bandeiras vermelhas
105
158
 
@@ -107,7 +160,7 @@ Fila que ninguem passa e score que nao existe. Traga a fila ao builder.
107
160
  - Chamada de `git` direta em vez de `ork ship`.
108
161
  - Modo escolhido por configuracao do host, e nao pela #TAG do pedido.
109
162
  - Thread aberta com `ork doctor` em `fail`.
110
- - Fila de `ork master --batch` crescendo sem ninguem passar nela.
163
+ - Resposta do dono "registrada" pelo agente, sem o update autenticado do canal.
111
164
 
112
165
  ## Verificacao antes de responder ao builder
113
166
 
@@ -1,18 +1,48 @@
1
1
  # Adaptador OpenClaw
2
2
 
3
- O OpenClaw recebe o Orkastery como um conjunto de **tools `ork_*`**: cada tool e uma chamada de CLI
4
- declarada em `openclaw.plugin.json`, sem codigo de cola e sem regra de negocio no host.
3
+ O OpenClaw recebe o Orkastery como uma **extensao** no formato 2026.7.1: um pacote com
4
+ `package.json` (`openclaw.extensions: ["./dist/index.js"]`), um entry JS que registra as
5
+ **21 tools `ork_*`** via `defineToolPlugin` do SDK do proprio OpenClaw, e o manifesto
6
+ `openclaw.plugin.json` gerado (`id`, `activation`, `contracts.tools`). Cada tool e uma
7
+ chamada de CLI do `ork`, sem regra de negocio no host.
8
+
9
+ ## Estrutura do pacote
10
+
11
+ | Arquivo | Papel |
12
+ |---|---|
13
+ | `package.json` | `openclaw.extensions` aponta o entry; sem dependencia de runtime |
14
+ | `dist/index.js` | Entry construido; unico arquivo com o placeholder `{{ork_bin}}` |
15
+ | `openclaw.plugin.json` | Manifesto GERADO por `openclaw plugins build` (nao editar na mao) |
16
+ | `src/index.ts` | Fonte TS; `construir.sh` compila com o tsc do core |
17
+ | `bin/ork-abrir-thread.sh` | Atalho de abertura de thread sem reimplementar o parse da #TAG |
18
+
19
+ O entry importa `openclaw/plugin-sdk/tool-plugin`, que o loader do OpenClaw resolve por
20
+ alias: o pacote nao precisa de `node_modules` no destino. Os parametros das tools sao JSON
21
+ Schema literal (sem typebox). Se uma versao futura do `openclaw plugins validate` exigir
22
+ typebox, adicione-o em `dependencies` e rode `npm install` no destino.
5
23
 
6
24
  ## Instalacao
7
25
 
8
26
  ```bash
9
- ork adapter install openclaw # instala em <projeto>/.openclaw
10
- ork adapter install openclaw --dir ~/.openclaw # instala para a maquina
27
+ ork adapter install openclaw # instala em <projeto>/.openclaw/extensions/orkastery
28
+ ork adapter install openclaw --dir ~/.openclaw # instala para a maquina, no source root global
11
29
  ork adapter install openclaw --dry-run # lista o que seria escrito
12
30
  ```
13
31
 
14
- O instalador renderiza `{{ork_bin}}` com o caminho absoluto do `ork` desta maquina e grava
15
- `INSTALADO.json` com a origem e o sha256 de cada arquivo.
32
+ O instalador renderiza `{{ork_bin}}` no `dist/index.js` com o caminho absoluto do `ork`
33
+ desta maquina e grava `INSTALADO.json` com a origem e o sha256 de cada arquivo.
34
+
35
+ **Registro no OpenClaw.** Com `--dir ~/.openclaw`, o destino
36
+ `~/.openclaw/extensions/orkastery` ja e o source root `global` que o
37
+ `openclaw plugins list` varre: reinicie o gateway e confira. Alternativas equivalentes:
38
+
39
+ ```bash
40
+ openclaw plugins install ~/.openclaw/extensions/orkastery # registra explicitamente
41
+ # ou aponte plugins.load.paths para o diretorio instalado na config do OpenClaw
42
+ ```
43
+
44
+ Se a copia nao passou pelo instalador (placeholder nao renderizado), o entry cai no
45
+ fallback: usa `ORK_BIN` do ambiente ou o `ork` do PATH.
16
46
 
17
47
  ## As tools
18
48
 
@@ -27,37 +57,116 @@ O instalador renderiza `{{ork_bin}}` com o caminho absoluto do `ork` desta maqui
27
57
  | `ork_claims_add` | Registra alegacao verificavel com o comando que a comprova |
28
58
  | `ork_verify` / `ork_verify_baseline` | Reexecuta no HEAD real; grava a baseline antes do GO |
29
59
  | `ork_worktree_ensure` / `ork_worktree_audit` | Worktree isolada, conferida no proprio git |
30
- | `ork_gate_approve` | Autorizacao humana registrada no ledger |
60
+ | `ork_objective_status` / `ork_objective_message` | Aposentadas com o `ork objective` na I-43: devolvem a recusa tipada `objective.aposentado` |
61
+ | `ork_portfolio_list` | Lista produtos, projetos e iniciativas canônicos |
62
+ | `ork_gate_answer` | Resposta humana correlacionada a um gate |
63
+ | `ork_session_answer` | Resposta humana correlacionada a uma sessão |
31
64
  | `ork_ship` | Merge serializado por lease e push provado contra o remoto |
32
65
  | `ork_master` | POSTMORTEM tipado e o score HUMANO de 0 a 5 |
33
- | `ork_board` / `ork_master_batch` | Escalonador e fila de score |
66
+ | `ork_board` | Escalonador: quem avanca agora e quem espera |
67
+ | `ork_master_batch` | Todas as entregas, com o indice do ledger (`ork master --todas`; a fila de score saiu na I-43) |
34
68
 
35
69
  ## A #TAG de conducao
36
70
 
37
- `ork_modo_do_pedido` devolve JSON com o modo, a tag, se veio do pedido ou do
38
- `conduction.default_mode`, e quantas pausas humanas aquele modo tem. O host passa o resultado a
39
- `ork_thread_new` e **nao decide nada**: `conduction.allowed_modes` e conferido dentro do
40
- `ork thread new`, que recusa com erro tipado quando o projeto nao permite o modo.
71
+ O manifesto novo do OpenClaw nao tem campo proprio para isso, entao a regra vive aqui e
72
+ nas descricoes das tools: `ork_modo_do_pedido` devolve JSON com o modo, a tag, se veio do
73
+ pedido ou do `conduction.default_mode`, e quantas pausas humanas aquele modo tem. O host
74
+ passa o resultado a `ork_thread_new` e **nao decide nada**: `conduction.allowed_modes` e
75
+ conferido dentro do `ork thread new`, que recusa com erro tipado quando o projeto nao
76
+ permite o modo.
41
77
 
42
78
  ## Os 3 pitfalls de instalacao
43
79
 
44
- 1. **Placeholder `{{ork_bin}}` que fica no arquivo.** O OpenClaw executa o vetor de comando como
45
- ele esta: um `{{ork_bin}}` nao renderizado vira "comando nao encontrado" em toda tool, e a
46
- sessao so descobre isso quando ja abriu thread na cabeca do agente. O instalador renderiza o
47
- caminho absoluto; confira depois com
48
- `grep -c '{{' <destino>/openclaw.plugin.json`, que precisa devolver `0`.
80
+ 1. **Placeholder `{{ork_bin}}` que fica no arquivo.** O unico placeholder renderizavel
81
+ vive no `dist/index.js`. Nao renderizado, o entry cai no fallback (`ORK_BIN` ou o `ork`
82
+ do PATH); sem fallback possivel, toda tool viraria "comando nao encontrado", e a sessao
83
+ so descobriria depois de ja ter aberto thread na cabeca do agente. Confira com
84
+ `grep -c '{{' <destino>/extensions/orkastery/dist/index.js`, que precisa devolver `0`.
85
+
86
+ 2. **Argumento com espaco quebrado em varios argumentos.** Cada tool monta o comando como
87
+ **vetor de argv** e chama o `ork` sem shell, exatamente para que um pedido com espaco,
88
+ aspas ou quebra de linha chegue inteiro em `--prompt`. Se voce adaptar uma tool,
89
+ mantenha o vetor: uma string de shell aqui vira injecao de comando com o texto do
90
+ builder dentro.
91
+
92
+ 3. **Tool que reimplementa regra do nucleo.** A tentacao e criar uma tool "esperta" que
93
+ decide o modo, monta o prompt ou escolhe a base. Toda tool deste plugin e uma chamada
94
+ de CLI e nada mais; a decisao que parece faltar aqui ja existe do outro lado, com
95
+ teste. Tool nova que precisa de logica e sinal de que a logica pertence ao `ork`.
49
96
 
50
- 2. **Argumento com espaco quebrado em varios argumentos.** Cada tool declara o comando como
51
- **vetor**, nunca como string de shell, exatamente para que um pedido com espaco, aspas ou
52
- quebra de linha chegue inteiro em `--prompt`. Se voce adaptar uma tool, mantenha o vetor: uma
53
- string de shell aqui vira injecao de comando com o texto do builder dentro.
97
+ ## Como reconstruir
98
+
99
+ ```bash
100
+ bash adapters/openclaw/construir.sh # tsc do core compila src/ para dist/
101
+ cd adapters/openclaw && openclaw plugins build --entry ./dist/index.js # regenera o manifesto
102
+ cd adapters/openclaw && openclaw plugins validate --entry ./dist/index.js # Plugin orkastery is valid.
103
+ ```
54
104
 
55
- 3. **Tool que reimplementa regra do nucleo.** A tentacao e criar uma tool "esperta" que decide o
56
- modo, monta o prompt ou escolhe a base. Toda tool deste plugin e uma chamada de CLI e nada
57
- mais; a decisao que parece faltar aqui ja existe do outro lado, com teste. Tool nova que precisa
58
- de logica e sinal de que a logica pertence ao `ork`.
105
+ O `dist/` fica commitado: o instalador copia arquivos do repositorio, nao roda build.
59
106
 
60
107
  ## O que este adaptador nao faz
61
108
 
62
- Nao escreve codigo de produto, nao decide gate e nao valida modo. Ele traduz intencao em chamada de
63
- `ork`, apresenta o resultado ao humano e registra o que o humano decidiu.
109
+ Nao escreve codigo de produto, nao decide gate e nao valida modo. Ele traduz intencao em
110
+ chamada de `ork`, apresenta o resultado ao humano e registra o que o humano decidiu.
111
+
112
+ ## Contas por runtime (I-33)
113
+
114
+ Este host tem superfície CLI, e a paridade com os hosts MCP vem pelos mesmos comandos do
115
+ núcleo; a superfície MCP de `accounts` não existe neste ciclo (o `add` é interativo e local, e
116
+ a leitura de estado já sai de `ork_observe`).
117
+
118
+ ```bash
119
+ ork accounts list [--json] # perfis por runtime, sem segredo
120
+ ork accounts add <id> --runtime claude-bg|codex --dir <diretorio>
121
+ ork accounts check [<id>] # confere o login pelo proprio CLI
122
+ ork accounts remove <id> # desativa; diretorio e login ficam
123
+ ork setup <modo> --bloco N --fallback codex:<modelo>
124
+ ```
125
+
126
+ O login roda no terminal do operador, pelo próprio CLI do runtime com o env do perfil; o
127
+ adaptador nunca recebe, guarda ou repassa credencial, e a conversa nunca é canal de login.
128
+ Quando uma conta esgota a cota, o crédito ou o limite do plano, ou perde o login, o
129
+ `ork retry run` rotaciona e grava `runtime_profile_rotated`; o host só apresenta o evento e a
130
+ pendência humana quando houver. A troca para outro perfil do mesmo runtime por esgotamento vem
131
+ ligada por padrão desde a decisão do dono de 19/09/2026
132
+ (`runtime_profiles.rotate_same_runtime_on_quota: true`; `false` desliga), vale só para perfis
133
+ que o operador cadastrou e autenticou pelo CLI oficial, para contas que ele tem direito de usar
134
+ sob os termos do provedor, e nunca acontece no rate limit comum, que espera a janela na fila
135
+ (ver `SECURITY.md` e `docs/guias/verificacao.md` na raiz do repositório).
136
+
137
+ ## Respostas HITL
138
+
139
+ `ork_gate_answer` e `ork_session_answer` recebem `thread`, `pedido` e `updateTelegram` original do gateway autenticado. Configure `ORK_HITL_TELEGRAM_USERS` e `ORK_HITL_TELEGRAM_CHATS` com os ids permitidos, separados por vírgula. A mensagem responde ao alerta com a linha `ork-hitl <thread> <pedido>`; callback contém `ork:<thread>:<pedido>:<opção>`. Bots, origem não autorizada e correlação incorreta são recusados antes do CLI.
140
+
141
+ O gateway autenticado é a fronteira de confiança: conteúdo de conversa ou JSON criado por agente não comprova resposta humana. `ork_gate_approve` foi retirado do catálogo; migre o host para os comandos de resposta. Expiração nunca aprova, e delegação não pode aprovar push nem score. O núcleo fornece a profundidade da apresentação.
142
+
143
+ Verificação: `sh adapters/openclaw/construir.sh && node --test adapters/openclaw/test/hitl-answer.test.mjs`.
144
+ # Entrada Maestro
145
+
146
+ O callback `inbound_claim` possui ingresso adicional `native` sem Telegram quando
147
+ o SDK comprova `commandAuthorized`, `senderIsOwner`, sessão e identidade do contexto.
148
+ Bindings/chave privados `ORK_HITL_NATIVE_BINDING_OPENCLAW`/`ORK_HITL_NATIVE_KEY_OPENCLAW`
149
+ pertencem ao processo do gateway e ao núcleo, nunca ao modelo. Ausência de campo,
150
+ conta/canal/sessão divergente ou recibo inválido mantém o pedido pendente. A resposta
151
+ usa `/ork gate|session <thread> <pedido> <resposta>` na mesma conversa. Replay não
152
+ reenvia confirmação. A homologação live exige instalação/ativação pós-SHIP.
153
+
154
+ `orkastery maestro` roteia para `ork_maestro`, consulta JSON do núcleo com argv sem
155
+ shell. O panorama não abre trabalho; fonte indisponível permanece explícita.
156
+ Sessão filha executa o bloco recebido. A apresentação HITL prioriza tópicos,
157
+ recomendação e opções rotuladas, sem UUID exposto. Telegram é opcional; ingresso
158
+ nativo adicional só fica disponível após prova do callback e configuração do host.
159
+ Instalação fixture/SDK simulado não comprova ativação em uma sessão real.
160
+
161
+ Na callback autenticada, `/ork offer <thread> <pedido>` consulta a pergunta e a
162
+ oferta nativa sem responder ao pedido. O gateway assina uma prova efêmera da
163
+ mensagem e chama `gate context --native-offer-stdin`; só uma prova válida para
164
+ aquele host, sessão e pedido anuncia disponibilidade na conversa. Configuração
165
+ isolada, prova vencida ou texto produzido pelo modelo não demonstram callback.
166
+
167
+ Antes de executar tools, o helper fixo `ork receipt-verifiers --json` autentica e
168
+ prepara provas públicas dos recibos legados no projeto de `ORK_HITL_ROOT`, definido
169
+ pelo gateway. Os bytes dos recibos e o ledger permanecem preservados. Só os
170
+ verificadores públicos seguem ao executor; variáveis `ORK_HITL_*` são removidas.
171
+ Autoridade presente com projeto, preparação ou verificadores indisponíveis recusa
172
+ a execução da tool (`hitl.receipt.verifiers-unavailable`).
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ # Same Brain API as other hosts; no authority inferred from the launcher.
3
+ set -eu
4
+ exec "{{ork_bin}}" brain "$@"
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env sh
2
+ # Compila o entry TS do plugin para dist/ usando o tsc do core.
3
+ #
4
+ # Script de DESENVOLVIMENTO, rodado no repositorio do Orkastery; o dist/ gerado
5
+ # fica commitado porque o `ork adapter install openclaw` copia arquivos do
6
+ # repo, nao roda build. No diretorio instalado este script nao funciona (nao ha
7
+ # core/ do lado) e nao precisa: o que roda la e o dist/index.js.
8
+
9
+ set -eu
10
+
11
+ DIR="$(cd "$(dirname "$0")" && pwd)"
12
+ TSC="$DIR/../../core/node_modules/.bin/tsc"
13
+
14
+ if [ ! -x "$TSC" ]; then
15
+ echo "tsc nao encontrado em $TSC; rode npm ci em core/ antes" >&2
16
+ exit 2
17
+ fi
18
+
19
+ exec "$TSC" -p "$DIR/tsconfig.json"
@@ -0,0 +1,199 @@
1
+ /** Native inbound_claim hook only. Never register this callback as a model tool. */
2
+ /** D12: this host is the `openclaw` channel, and the channel is signed, not labelled. */
3
+ import { createHash, createHmac } from 'node:crypto';
4
+ import { execFile } from 'node:child_process';
5
+ /**
6
+ * I-41 (GO-FIX 1): the two shapes of an answer to the pulse summary, typed with no slash and no
7
+ * identifier: "P4EJ a" answers the summary and "1a 2c" answers the questions. The SAME sources
8
+ * live in the core (GRAMATICA_DO_PULSE, core/src/pulse-resposta.ts) and a core test checks they
9
+ * stay equal. The code starts with a letter and has a digit: ordinary chat never has this shape,
10
+ * and it never reads as an answer to the batch, which starts with the question number.
11
+ */
12
+ const PULSE_CONSENT = new RegExp("^[ \\t]*(?=[A-HJKMNP-TV-Z][2-9A-HJKMNP-TV-Z]{0,2}[2-9])[A-HJKMNP-TV-Z][2-9A-HJKMNP-TV-Z]{3}[ \\t]+[^\\r\\n]{1,40}$", 'i');
13
+ const PULSE_LOTE = new RegExp("^[ \\t]*[0-9]{1,2}[ \\t]*[a-zA-Z](?:[ \\t,;]*[0-9]{1,2}[ \\t]*[a-zA-Z])*[ \\t]*[.!]?[ \\t]*$");
14
+ /** I-50 (RM-039): the pulse cadence tag, alone in the message. It starts with '#', so it never reads as the other two. */
15
+ const PULSE_CADENCIA = new RegExp("^[ \\t]*#OrkPulse(?:On(?:-(?:15|30|60)m)?|Off)[ \\t]*[.!]?[ \\t]*$", 'i');
16
+ /** The pulse address inside the signed body: neither a thread nor a request. The core translates. */
17
+ const PULSE_ALVO = 'pulse', PULSE_ENDERECO = 'resposta';
18
+ export function isPulseAnswer(text) {
19
+ return !text.includes('\0') && (PULSE_CONSENT.test(text) || PULSE_LOTE.test(text) || PULSE_CADENCIA.test(text));
20
+ }
21
+ export function registerHitlIngress(api, bin) {
22
+ api.on('inbound_claim', async (event, ctx) => {
23
+ if (typeof event.content !== 'string')
24
+ return { handled: false };
25
+ const pulse = !event.content.startsWith('/ork ') && isPulseAnswer(event.content);
26
+ if (!event.content.startsWith('/ork ') && !pulse)
27
+ return { handled: false };
28
+ const denied = { handled: true, reply: { text: 'Resposta HITL não confirmada. Confira ingresso e ledger do pedido.' } };
29
+ // The pulse summary goes out on Telegram; only there is an answer to it recognized.
30
+ if (event.channel !== 'telegram')
31
+ return pulse ? { handled: false } : nativeIngress(event, ctx, bin).catch(() => denied);
32
+ try {
33
+ const env = process.env, user = event.senderId ?? '', chat = event.conversationId ?? '', message = event.messageId ?? '';
34
+ const allowed = (key, value) => (env[key] ?? '').split(',').map(s => s.trim()).filter(Boolean).includes(value);
35
+ const age = Date.now() - Number(event.timestamp);
36
+ if (event.channel !== 'telegram' || ctx.channelId !== 'telegram' || event.commandAuthorized !== true ||
37
+ !env.ORK_HITL_OPENCLAW_ACCOUNT || event.accountId !== env.ORK_HITL_OPENCLAW_ACCOUNT || event.accountId !== ctx.accountId ||
38
+ !env.ORK_HITL_TELEGRAM_BOT_ID || user === env.ORK_HITL_TELEGRAM_BOT_ID ||
39
+ !/^\d+$/.test(user) || !/^-?\d+$/.test(chat) || !/^\d+$/.test(message) ||
40
+ user !== ctx.senderId || chat !== ctx.conversationId || message !== ctx.messageId ||
41
+ !allowed('ORK_HITL_TELEGRAM_USERS', user) || !allowed('ORK_HITL_TELEGRAM_CHATS', chat) ||
42
+ !Number.isFinite(age) || age < 0 || age > 60000)
43
+ return denied;
44
+ const match = /^\/ork (gate|session) ([a-zA-Z0-9][a-zA-Z0-9._-]{0,79}) ([a-zA-Z0-9][a-zA-Z0-9._-]{0,79}) ([\s\S]{1,4096})$/.exec(event.content);
45
+ // FX1: this channel signs with ITS OWN key. The shared ORK_HITL_INGRESS_KEY is kept
46
+ // for the legacy v1 envelope only, which this adapter no longer produces.
47
+ const key = env.ORK_HITL_INGRESS_KEY_OPENCLAW, root = env.ORK_HITL_ROOT;
48
+ if ((!pulse && !match) || event.content.includes('\0') || !key || Buffer.byteLength(key) < 32 || !root?.startsWith('/'))
49
+ return denied;
50
+ if (pulse)
51
+ return pulseIngress(event.content, { user, chat, message, key, root, bin, timestamp: Number(event.timestamp) }, denied);
52
+ const [, operation, thread, pedido, resposta] = match;
53
+ const canal = 'openclaw';
54
+ // FX5: the authorized account goes INSIDE the signed body. Checking `accountId` only
55
+ // here left the durable receipt proving generic telegram transport and nothing else:
56
+ // nobody reading it later could tell which OpenClaw account the decision came from.
57
+ const conta = env.ORK_HITL_OPENCLAW_ACCOUNT;
58
+ const envelope = { resposta, canal, conta, origem: 'telegram', por: `telegram:${user}`, mensagem: `telegram:${chat}:${message}`,
59
+ recebidoEm: new Date(Number(event.timestamp)).toISOString(), prova: '' };
60
+ envelope.prova = createHmac('sha256', key).update(JSON.stringify(['ork.hitl-answer/v2', thread, pedido, canal, conta,
61
+ envelope.origem, envelope.por, envelope.mensagem, envelope.recebidoEm, resposta])).digest('hex');
62
+ // `gate answer` exige --resposta-stdin; `sessions answer` exige --stdin. A mesma flag
63
+ // para os dois fazia o CLI recusar toda resposta de sessao antes de conferir qualquer coisa.
64
+ const args = [operation === 'gate' ? 'gate' : 'sessions', 'answer', thread, pedido,
65
+ operation === 'gate' ? '--resposta-stdin' : '--stdin',
66
+ '--origem', 'telegram', '--canal', canal, '--conta', conta, '--por', envelope.por, '--mensagem', envelope.mensagem];
67
+ // FX5: `ok` alone was never a receipt. The core answers with the typed verdict, and a
68
+ // reply to the human must only claim what that verdict says: a gate lands in one of
69
+ // three states, a session answer lands `entregue` with the session it reached, and
70
+ // `repetida` says whether this call caused the effect or merely found it already done.
71
+ const estados = operation === 'gate' ? ['aprovado', 'recusado', 'aguardando'] : ['entregue'];
72
+ const ok = await new Promise(resolve => {
73
+ const child = execFile(bin, args, { cwd: root, timeout: 10000, maxBuffer: 65536 }, (err, stdout) => {
74
+ try {
75
+ const r = JSON.parse(stdout);
76
+ resolve(!err && r.ok === true && r.pedidoId === pedido && estados.includes(r.estado) &&
77
+ typeof r.repetida === 'boolean' &&
78
+ (operation === 'gate' || typeof r.sessionId === 'string' && r.sessionId.length > 0));
79
+ }
80
+ catch {
81
+ resolve(false);
82
+ }
83
+ });
84
+ child.stdin?.on('error', () => resolve(false));
85
+ child.stdin?.end(JSON.stringify(envelope));
86
+ });
87
+ return ok ? { handled: true, reply: { text: `Resposta recebida pelo núcleo para o pedido ${pedido}.` } } : denied;
88
+ }
89
+ catch {
90
+ return denied;
91
+ }
92
+ });
93
+ }
94
+ /**
95
+ * I-41 (GO-FIX 1): the owner answered the pulse summary or its questions. The whole text is the
96
+ * answer and the signed address is the pulse's; this adapter knows no request and picks nothing.
97
+ * The reply is the text the core composed, transported as is.
98
+ */
99
+ async function pulseIngress(texto, m, denied) {
100
+ const canal = 'openclaw', conta = process.env.ORK_HITL_OPENCLAW_ACCOUNT ?? '';
101
+ const envelope = { resposta: texto, canal, conta, origem: 'telegram', por: `telegram:${m.user}`,
102
+ mensagem: `telegram:${m.chat}:${m.message}`, recebidoEm: new Date(m.timestamp).toISOString(), prova: '' };
103
+ envelope.prova = createHmac('sha256', m.key).update(JSON.stringify(['ork.hitl-answer/v2', PULSE_ALVO, PULSE_ENDERECO, canal, conta,
104
+ envelope.origem, envelope.por, envelope.mensagem, envelope.recebidoEm, texto])).digest('hex');
105
+ const args = ['pulse', 'responder', '--resposta-stdin', '--origem', 'telegram', '--canal', canal, '--conta', conta,
106
+ '--por', envelope.por, '--mensagem', envelope.mensagem];
107
+ const receipt = await new Promise(resolve => {
108
+ const child = execFile(m.bin, args, { cwd: m.root, timeout: 10000, maxBuffer: 65536 }, (err, stdout) => {
109
+ try {
110
+ resolve(err ? null : JSON.parse(stdout));
111
+ }
112
+ catch {
113
+ resolve(null);
114
+ }
115
+ });
116
+ child.stdin?.on('error', () => resolve(null));
117
+ child.stdin?.end(JSON.stringify(envelope));
118
+ });
119
+ if (!receipt || receipt.ok !== true || receipt.contrato !== 'ork.pulse-resposta/v1' ||
120
+ typeof receipt.mensagem !== 'string' || !receipt.mensagem || typeof receipt.repetida !== 'boolean')
121
+ return denied;
122
+ // Same message already handled: never resend blindly.
123
+ return receipt.repetida ? { handled: true } : { handled: true, reply: { text: receipt.mensagem } };
124
+ }
125
+ /** SDK instalado: inbound_claim fornece commandAuthorized, senderIsOwner e sessionKey.
126
+ * Somente esta callback usa a chave privada; nenhuma tool registra nativeIngress. */
127
+ async function nativeIngress(event, ctx, bin) {
128
+ const denied = { handled: true, reply: { text: 'Ingresso nativo indisponível ou identidade não comprovada. O pedido permanece pendente.' } };
129
+ const env = process.env, root = env.ORK_HITL_ROOT, key = env.ORK_HITL_NATIVE_KEY_OPENCLAW;
130
+ const binding = JSON.parse(env.ORK_HITL_NATIVE_BINDING_OPENCLAW ?? 'null');
131
+ const age = Date.now() - Number(event.timestamp);
132
+ if (!binding || binding.host !== 'openclaw' || !root?.startsWith('/') || !key || Buffer.byteLength(key) < 32 ||
133
+ event.commandAuthorized !== true || event.senderIsOwner !== true ||
134
+ !event.sessionKey || event.sessionKey !== ctx.sessionKey || event.sessionKey !== binding.sessionId ||
135
+ !event.messageId || event.messageId !== ctx.messageId || !binding.installationId || !binding.connectionId ||
136
+ event.channel !== ctx.channelId || event.channel !== binding.channelId ||
137
+ event.accountId !== ctx.accountId || event.accountId !== binding.accountId ||
138
+ event.senderId !== ctx.senderId || event.senderId !== binding.personId ||
139
+ event.conversationId !== ctx.conversationId || event.conversationId !== binding.conversationId ||
140
+ !Number.isFinite(age) || age < 0 || age > 60000)
141
+ return denied;
142
+ const match = /^\/ork (offer|gate|session) ([a-zA-Z0-9][a-zA-Z0-9._-]{0,79}) ([a-zA-Z0-9][a-zA-Z0-9._-]{0,79})(?: ([\s\S]{1,4096}))?$/.exec(event.content);
143
+ if (!match || event.content.includes('\0') || (match[1] === 'offer' ? match[4] !== undefined : !match[4]))
144
+ return denied;
145
+ const [, operation, thread, pedido, resposta] = match;
146
+ const call = (args, input) => new Promise((resolve, reject) => {
147
+ const child = execFile(bin, args, { cwd: root, timeout: 10000, maxBuffer: 65536 }, (error, stdout) => {
148
+ if (error) {
149
+ reject(Error('native.core.unavailable'));
150
+ return;
151
+ }
152
+ try {
153
+ resolve(JSON.parse(stdout));
154
+ }
155
+ catch {
156
+ reject(Error('native.core.invalid'));
157
+ }
158
+ });
159
+ child.stdin?.on('error', () => reject(Error('native.core.unavailable')));
160
+ if (input !== undefined)
161
+ child.stdin?.end(JSON.stringify(input));
162
+ });
163
+ const offer = { native: { ...binding, messageId: event.messageId }, recebidoEm: new Date(Number(event.timestamp)).toISOString(), prova: '' };
164
+ const b = offer.native;
165
+ offer.prova = createHmac('sha256', key).update(JSON.stringify(['ork.hitl-native-offer/v1', thread, pedido,
166
+ b.host, b.installationId, b.connectionId, b.sessionId, b.accountId, b.channelId,
167
+ b.conversationId, b.personId, b.messageId, offer.recebidoEm])).digest('hex');
168
+ const view = await call(['gate', 'context', thread, pedido, '--native-offer-stdin'], offer);
169
+ if (view.pedido?.id !== pedido || view.pedido?.thread !== thread || (operation !== 'offer' && view.pedido?.alvo?.tipo !== operation) ||
170
+ !view.canais?.some((c) => c.canal === 'openclaw' && c.transporte === 'native' && c.estado === 'disponivel') ||
171
+ !/^[a-f0-9]{64}$/.test(view.contexto) || !/^[a-f0-9]{64}$/.test(view.pedidoSha256))
172
+ return denied;
173
+ if (operation === 'offer') {
174
+ if (typeof view.apresentacao?.mensagem !== 'string')
175
+ return denied;
176
+ // I-35: o prazo chega ao dono no fuso dele; núcleo de outra versão pode não ter posto o
177
+ // prazo local na mensagem, então o campo local vai junto.
178
+ const prazo = typeof view.prazoLocal === 'string' && view.prazoLocal && !view.apresentacao.mensagem.includes(view.prazoLocal)
179
+ ? `\nPrazo (fuso do dono): ${view.prazoLocal}` : '';
180
+ return { handled: true, reply: { text: view.apresentacao.mensagem + prazo + '\nCanal nativo OpenClaw disponível nesta conversa.' } };
181
+ }
182
+ const n = { ...binding, messageId: event.messageId, context: view.contexto,
183
+ pedidoSha256: view.pedidoSha256, expiresAt: view.pedido.prazo };
184
+ const message = 'native:' + createHash('sha256').update(JSON.stringify([n.host, n.installationId, n.connectionId,
185
+ n.sessionId, n.channelId, n.accountId, n.conversationId, n.messageId])).digest('hex');
186
+ const r = { origem: 'native', canal: 'openclaw', conta: n.accountId, native: n, por: `native:openclaw:${n.personId}`,
187
+ mensagem: message, recebidoEm: new Date(Number(event.timestamp)).toISOString(), resposta, prova: '' };
188
+ r.prova = createHmac('sha256', key).update(JSON.stringify(['ork.hitl-native/v1', thread, pedido,
189
+ n.host, n.installationId, n.connectionId, n.sessionId, n.accountId, n.channelId, n.conversationId, n.personId,
190
+ n.messageId, n.context, n.pedidoSha256, n.expiresAt, r.origem, r.canal, r.conta, r.por, r.mensagem, r.recebidoEm, resposta])).digest('hex');
191
+ const receipt = await call([operation === 'gate' ? 'gate' : 'sessions', 'answer', thread, pedido,
192
+ operation === 'gate' ? '--resposta-stdin' : '--stdin', '--origem', 'native', '--canal', 'openclaw',
193
+ '--conta', r.conta, '--por', r.por, '--mensagem', message], r);
194
+ if (receipt.ok !== true || receipt.pedidoId !== pedido || typeof receipt.repetida !== 'boolean' ||
195
+ !(operation === 'gate' ? ['aprovado', 'recusado', 'aguardando'] : ['entregue']).includes(receipt.estado) ||
196
+ (operation === 'session' && !receipt.sessionId))
197
+ return denied;
198
+ return receipt.repetida ? { handled: true } : { handled: true, reply: { text: 'Resposta nativa confirmada pelo núcleo.' } };
199
+ }