@orkastery/cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) 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/onboarding.md +21 -0
  7. package/adapters/claude-code/commands/ork.md +99 -27
  8. package/adapters/claude-code/hooks/hooks.json +6 -1
  9. package/adapters/claude-code/hooks/ork-sensor.js +66 -0
  10. package/adapters/codex/skills/ork/SKILL.md +57 -0
  11. package/adapters/hermes/README.md +163 -0
  12. package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
  13. package/adapters/hermes/bin/ork-brain.sh +4 -0
  14. package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
  15. package/adapters/hermes/bin/ork-maestro.sh +6 -0
  16. package/adapters/hermes/bin/ork-master-enviar.py +84 -0
  17. package/adapters/hermes/bin/ork-objective-message.sh +17 -0
  18. package/adapters/hermes/bin/ork-objective-status.sh +12 -0
  19. package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
  20. package/adapters/hermes/hermes.plugin.json +26 -4
  21. package/adapters/hermes/hitl-ingress/__init__.py +356 -0
  22. package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
  23. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
  24. package/adapters/openclaw/README.md +135 -26
  25. package/adapters/openclaw/bin/ork-brain.sh +4 -0
  26. package/adapters/openclaw/construir.sh +19 -0
  27. package/adapters/openclaw/dist/hitl-ingress.js +199 -0
  28. package/adapters/openclaw/dist/index.js +437 -0
  29. package/adapters/openclaw/openclaw.plugin.json +39 -134
  30. package/adapters/openclaw/package.json +26 -0
  31. package/adapters/openclaw/src/hitl-ingress.ts +180 -0
  32. package/adapters/openclaw/src/index.ts +458 -0
  33. package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
  34. package/adapters/openclaw/tsconfig.json +15 -0
  35. package/assets/docs/markdownlint-cli2.jsonc +22 -0
  36. package/assets/docs/padroes/documentacao-de-produto.md +133 -0
  37. package/assets/docs/padroes/roadmap-de-produto.md +98 -0
  38. package/assets/docs/produto/README.md +14 -0
  39. package/assets/docs/produto/_modelo-feature.md +59 -0
  40. package/assets/docs/roadmap/README.md +14 -0
  41. package/assets/docs/roadmap/_modelo-item.md +73 -0
  42. package/assets/orkmind-native-schema.json +32 -0
  43. package/assets/orkmind_bridge.py +357 -0
  44. package/assets/orkmind_fixture.py +67 -0
  45. package/assets/orkmind_prospective.py +138 -0
  46. package/assets/reference-tariffs-i07.json +23 -0
  47. package/dist/adapters/claude-bg.js +488 -39
  48. package/dist/adapters/codex-controller-sensor.js +449 -0
  49. package/dist/adapters/codex-controller-worker.js +361 -0
  50. package/dist/adapters/codex-controller.js +252 -0
  51. package/dist/adapters/codex-events.js +205 -0
  52. package/dist/adapters/codex-question.js +26 -0
  53. package/dist/adapters/codex-runner.js +133 -0
  54. package/dist/adapters/codex.js +394 -0
  55. package/dist/agents-md.js +84 -0
  56. package/dist/auditoria.js +11 -8
  57. package/dist/auditrun.js +5 -4
  58. package/dist/board.js +173 -22
  59. package/dist/branch-de-estado.js +136 -0
  60. package/dist/canarios-hitl.js +177 -0
  61. package/dist/canarios-i43.js +543 -0
  62. package/dist/canarios-pulse.js +147 -0
  63. package/dist/canarios-sensores.js +129 -0
  64. package/dist/canarios.js +111 -2
  65. package/dist/catalogo.js +9 -0
  66. package/dist/ci.js +216 -0
  67. package/dist/ciclos.js +2 -1
  68. package/dist/claim-lint.js +64 -0
  69. package/dist/claims.js +60 -0
  70. package/dist/company-brain-capture.js +195 -0
  71. package/dist/company-brain-cli.js +123 -0
  72. package/dist/company-brain-client.js +61 -0
  73. package/dist/company-brain-contract.js +166 -0
  74. package/dist/company-brain-journal.js +192 -0
  75. package/dist/company-brain-mcp.js +33 -0
  76. package/dist/company-brain-migration.js +60 -0
  77. package/dist/company-brain-source.js +177 -0
  78. package/dist/company-brain-worker.js +18 -0
  79. package/dist/conducao-texto.js +61 -0
  80. package/dist/conducao.js +876 -0
  81. package/dist/contrato-publico.js +39 -0
  82. package/dist/creation-operation-store.js +251 -0
  83. package/dist/creation-operation.js +148 -0
  84. package/dist/decisao-autonoma.js +183 -0
  85. package/dist/delegation.js +79 -0
  86. package/dist/demo.js +120 -0
  87. package/dist/docs.js +828 -0
  88. package/dist/doctor.js +183 -24
  89. package/dist/entrega-pr.js +117 -0
  90. package/dist/escopo-escrita.js +71 -0
  91. package/dist/estado-thread.js +222 -0
  92. package/dist/evalrunner.js +12 -0
  93. package/dist/fabrica-estado.js +329 -0
  94. package/dist/fabrica-publicar.js +76 -0
  95. package/dist/fix.js +21 -0
  96. package/dist/gates.js +55 -9
  97. package/dist/handoff.js +39 -23
  98. package/dist/hitl-canais.js +333 -0
  99. package/dist/hitl-classificacao.js +131 -0
  100. package/dist/hitl-contract.js +465 -0
  101. package/dist/hitl-estado.js +136 -0
  102. package/dist/hitl-gates.js +554 -0
  103. package/dist/hitl-ingress-receipt.js +382 -0
  104. package/dist/hitl-local-atestado.js +57 -0
  105. package/dist/hitl-local-receipt.js +328 -0
  106. package/dist/hitl-local.js +143 -0
  107. package/dist/hitl-lock.js +139 -0
  108. package/dist/hitl-lote.js +223 -0
  109. package/dist/hitl-native-offer.js +97 -0
  110. package/dist/hitl-native.js +65 -0
  111. package/dist/hitl-presentation.js +209 -0
  112. package/dist/hitl-public-receipt.js +176 -0
  113. package/dist/hitl-resumo.js +209 -0
  114. package/dist/hitl-sessions.js +475 -0
  115. package/dist/hitl.js +711 -0
  116. package/dist/horario.js +269 -0
  117. package/dist/hosts.js +198 -22
  118. package/dist/index.js +1736 -93
  119. package/dist/indice.js +99 -0
  120. package/dist/init.js +28 -3
  121. package/dist/integracoes-locais.js +17 -0
  122. package/dist/leases.js +65 -21
  123. package/dist/ledger-stats.js +272 -0
  124. package/dist/ledger.js +107 -2
  125. package/dist/licoes.js +215 -0
  126. package/dist/liveness.js +218 -0
  127. package/dist/maestro-actions.js +51 -0
  128. package/dist/maestro-authority.js +152 -0
  129. package/dist/maestro-cli.js +97 -0
  130. package/dist/maestro-contract.js +55 -0
  131. package/dist/maestro-discovery.js +132 -0
  132. package/dist/maestro-runtime.js +268 -0
  133. package/dist/maestro-snapshot.js +86 -0
  134. package/dist/maestro-sources.js +224 -0
  135. package/dist/manifest.js +165 -6
  136. package/dist/maquina.js +102 -0
  137. package/dist/master-audit.js +101 -0
  138. package/dist/master-batch.js +43 -0
  139. package/dist/master-digest.js +149 -0
  140. package/dist/master-migracao.js +155 -0
  141. package/dist/master.js +361 -77
  142. package/dist/mcp-artifacts.js +234 -0
  143. package/dist/mcp-git.js +433 -0
  144. package/dist/mcp-install.js +310 -0
  145. package/dist/mcp-maestro.js +25 -0
  146. package/dist/mcp-server.js +469 -0
  147. package/dist/mcp-ship.js +436 -0
  148. package/dist/mcp-verify.js +173 -0
  149. package/dist/memoria-humana.js +207 -0
  150. package/dist/memoria.js +314 -78
  151. package/dist/memory-migration.js +279 -0
  152. package/dist/memory-prospective.js +148 -0
  153. package/dist/modos-migracao.js +191 -0
  154. package/dist/modos.js +151 -15
  155. package/dist/monitor-lock.js +110 -0
  156. package/dist/objective.js +449 -0
  157. package/dist/ocupacao.js +267 -0
  158. package/dist/onboarding.js +303 -0
  159. package/dist/orkmind.js +389 -90
  160. package/dist/orquestracao.js +96 -69
  161. package/dist/phase.js +596 -150
  162. package/dist/playbook-capabilities.js +150 -0
  163. package/dist/playbook-contracts.js +188 -0
  164. package/dist/portfolio-context.js +61 -0
  165. package/dist/portfolio.js +180 -0
  166. package/dist/preflight.js +207 -0
  167. package/dist/process-audit.js +112 -0
  168. package/dist/project-state.js +93 -0
  169. package/dist/prompts.js +25 -5
  170. package/dist/prova-minima.js +112 -0
  171. package/dist/pulse-cadencia.js +163 -0
  172. package/dist/pulse-consentimento.js +260 -0
  173. package/dist/pulse-delivery.js +323 -0
  174. package/dist/pulse-resposta.js +542 -0
  175. package/dist/pulse.js +262 -0
  176. package/dist/ratelimit.js +35 -2
  177. package/dist/recall.js +80 -3
  178. package/dist/redacao-saida.js +45 -0
  179. package/dist/redacao-url.js +66 -0
  180. package/dist/retry.js +637 -115
  181. package/dist/roadmap-reservas.js +243 -0
  182. package/dist/runtime-ambiente.js +38 -0
  183. package/dist/runtime-context.js +215 -0
  184. package/dist/runtime-profiles.js +869 -0
  185. package/dist/runtimes.js +103 -0
  186. package/dist/sandbox.js +9 -0
  187. package/dist/session-events.js +203 -0
  188. package/dist/session-watcher-claude.js +637 -0
  189. package/dist/session-watcher.js +683 -0
  190. package/dist/sessoes-adopt.js +154 -0
  191. package/dist/sessoes-inventario.js +159 -0
  192. package/dist/sessoes.js +16 -35
  193. package/dist/setup.js +691 -0
  194. package/dist/ship.js +150 -15
  195. package/dist/slug.js +1 -1
  196. package/dist/thread-close.js +115 -0
  197. package/dist/thread.js +142 -28
  198. package/dist/tokens.js +1 -1
  199. package/dist/util.js +4 -2
  200. package/dist/verify-sandbox.js +265 -0
  201. package/dist/verify.js +270 -25
  202. package/dist/versao.js +60 -0
  203. package/dist/worktree.js +31 -2
  204. package/dist/write-activation.js +291 -0
  205. package/dist/yaml.js +2 -0
  206. package/eval/casos/onboarding.json +102 -0
  207. package/eval/casos/orkastery-bootstrap.json +35 -1
  208. package/eval/casos/scope-check-capability-map.json +45 -7
  209. package/eval/casos/ship-release.json +2 -2
  210. package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
  211. package/eval/fixtures/b2-master-log/caso.json +1 -1
  212. package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
  213. package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
  214. package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
  215. package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
  216. package/eval/fixtures/fx-codex-dry/caso.json +18 -0
  217. package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
  218. package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
  219. package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
  220. package/eval/fixtures/fx-happy/caso.json +6 -4
  221. package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
  222. package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
  223. package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
  224. package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
  225. package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
  226. package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
  227. package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
  228. package/eval/fixtures/fx-omnicanal/caso.json +17 -0
  229. package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
  230. package/monitor/company-brain.cjs +17 -0
  231. package/monitor/pulse-scope.cjs +45 -0
  232. package/monitor/pulse.cron +18 -0
  233. package/monitor/varredura-pulse.sh +27 -0
  234. package/package.json +25 -4
  235. package/schemas/claims.schema.json +25 -0
  236. package/schemas/company-brain.schema.json +1158 -0
  237. package/schemas/creation-operation.schema.json +365 -0
  238. package/schemas/maestro-snapshot.schema.json +2716 -0
  239. package/skills/core/onboarding/SKILL.md +50 -0
  240. package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
  241. package/skills/core/thread-state/SKILL.md +3 -1
  242. package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
@@ -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` | Retoma o ticket canônico compartilhado com o Kanban |
61
+ | `ork_objective_message` | Preserva no ticket uma mensagem do canal autenticado |
62
+ | `ork_portfolio_list` | Lista produtos, projetos e iniciativas canônicos |
63
+ | `ork_gate_answer` | Resposta humana correlacionada a um gate |
64
+ | `ork_session_answer` | Resposta humana correlacionada a uma sessão |
31
65
  | `ork_ship` | Merge serializado por lease e push provado contra o remoto |
32
66
  | `ork_master` | POSTMORTEM tipado e o score HUMANO de 0 a 5 |
33
67
  | `ork_board` / `ork_master_batch` | Escalonador e fila de score |
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
+ }