@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
package/dist/fix.js CHANGED
@@ -74,6 +74,7 @@ const ledger_1 = require("./ledger");
74
74
  const thread_1 = require("./thread");
75
75
  const util_1 = require("./util");
76
76
  const verify_1 = require("./verify");
77
+ const conducao_1 = require("./conducao");
77
78
  /** Armazem append-only das correcoes da thread. */
78
79
  function caminhoDosFixes(raiz, threadId) {
79
80
  return path.join((0, thread_1.dirThread)(raiz, threadId), 'fixes.jsonl');
@@ -186,6 +187,20 @@ function specDoGoFix(thread, rodada, correcoes) {
186
187
  linhas.push('- Nao ha pausa humana dentro deste sub-loop; o veredito final e que sobe.');
187
188
  return linhas.join('\n');
188
189
  }
190
+ /**
191
+ * I-36 (T5): o GO-FIX e o CHECK-REVERIFY executam na worktree, entao seguram a conducao da thread
192
+ * do comeco ao fim. O `verificar` de dentro reentra no mesmo processo.
193
+ */
194
+ function sobConducao(carregado, threadId, operacao, conducao, executar) {
195
+ return (0, conducao_1.comConducao)(carregado.raiz, threadId, {
196
+ canal: conducao.canal ?? (0, conducao_1.canalDoProcesso)(),
197
+ correlacao: conducao.correlacao ?? null,
198
+ operacao,
199
+ identidade: conducao.identidade ?? (0, conducao_1.identidadeDoAmbiente)(threadId),
200
+ prazoMs: (0, conducao_1.prazoDaVerificacao)(carregado.manifesto.verify.timeout_ms ?? 10 * 60 * 1000, 8),
201
+ esperarMs: conducao.esperarMs,
202
+ }, executar);
203
+ }
189
204
  /**
190
205
  * Abre a rodada de GO-FIX a partir de um CHECK reprovado.
191
206
  *
@@ -193,6 +208,9 @@ function specDoGoFix(thread, rodada, correcoes) {
193
208
  * CHECK nenhum, nem no do proprio agente que acabou de rodar.
194
209
  */
195
210
  function abrirRodada(carregado, threadId, opcoes = {}) {
211
+ return sobConducao(carregado, threadId, 'fix.open', opcoes.conducao ?? {}, () => abrirRodadaSobConducao(carregado, threadId, opcoes));
212
+ }
213
+ function abrirRodadaSobConducao(carregado, threadId, opcoes) {
196
214
  const { raiz } = carregado;
197
215
  const thread = (0, thread_1.lerThread)(raiz, threadId);
198
216
  const resultado = (0, verify_1.verificar)(carregado, threadId, { soClaims: opcoes.soClaims });
@@ -256,6 +274,9 @@ function carimbar(raiz, correcao, aprovada, detalhe) {
256
274
  * frouxa do que o metodo manda.
257
275
  */
258
276
  function reverificar(carregado, threadId, opcoes = {}) {
277
+ return sobConducao(carregado, threadId, 'fix.reverify', opcoes.conducao ?? {}, () => reverificarSobConducao(carregado, threadId, opcoes));
278
+ }
279
+ function reverificarSobConducao(carregado, threadId, opcoes) {
259
280
  const { raiz, manifesto } = carregado;
260
281
  const thread = (0, thread_1.lerThread)(raiz, threadId);
261
282
  const rodada = opcoes.rodada ?? ultimaRodada(raiz, threadId);
@@ -360,7 +381,7 @@ function reverificar(carregado, threadId, opcoes = {}) {
360
381
  modo: thread.modo,
361
382
  detalhe: razao,
362
383
  evidencia: `rodada ${rodada} de GO-FIX/CHECK-REVERIFY na thread ${threadId}`,
363
- correcao: `ork gate approve ${threadId} evidencias --por <quem>`,
384
+ correcao: `ork gate request ${threadId}`,
364
385
  rodada,
365
386
  limite,
366
387
  pausaQualquerModo: true,
package/dist/gates.js CHANGED
@@ -19,23 +19,36 @@ exports.registrarGateBloqueado = registrarGateBloqueado;
19
19
  exports.registrarGateLiberado = registrarGateLiberado;
20
20
  exports.descreverMotivo = descreverMotivo;
21
21
  exports.aprovarGateHumano = aprovarGateHumano;
22
+ exports.aprovacaoHumanaProvada = aprovacaoHumanaProvada;
22
23
  exports.aprovacoesHumanas = aprovacoesHumanas;
23
24
  const ledger_1 = require("./ledger");
24
25
  const modos_1 = require("./modos");
25
26
  const thread_1 = require("./thread");
27
+ const hitl_ingress_receipt_1 = require("./hitl-ingress-receipt");
28
+ const hitl_local_receipt_1 = require("./hitl-local-receipt");
29
+ const hitl_contract_1 = require("./hitl-contract");
26
30
  /** O que cada motivo tipado significa, em uma linha, para a saida do CLI. */
27
31
  exports.DESCRICAO_DO_MOTIVO = {
28
32
  'artifact.missing': 'artefato exigido pela fase nao existe no disco',
29
33
  'claims.failed': 'alegacao verificavel reprovou na reexecucao no HEAD real',
30
34
  'claims.unverifiable': 'alegacao sem comando de verificacao declarado',
31
35
  'policy.violation': 'policy do manifesto com severidade block foi violada',
36
+ 'runtime.autoconferencia': 'o CHECK foi despachado no mesmo runtime que fez o GO, e a thread exige validacao cruzada',
32
37
  'verify.regression': 'comando que passava na baseline falha agora',
33
38
  'verify.failed': 'comando de verificacao falhou (sem baseline para separar divida pre-existente)',
39
+ 'verify.timeout': 'comando de verificacao estourou o prazo antes de terminar: nao ha veredito sobre ele',
40
+ 'verify.sem-veredito': 'a rodada nao produziu veredito valido: comando que nao chegou a rodar, ou produto alterado entre o preparo e o fim',
41
+ 'ci.failed': 'CHECK independente ausente, pendente ou reprovado no commit candidato',
34
42
  'runtime.unavailable': 'runtime adapter indisponivel para o despacho',
43
+ 'runtime.silencio': 'fase despachada sem heartbeat dentro do limite de liveness',
35
44
  'runtime.rate-limited': 'o runtime bateu o limite de uso da assinatura e a fase morreu antes de terminar (bloco B3: entra na fila duravel e e retomada na janela seguinte)',
45
+ 'runtime.quota-exhausted': 'a cota ou os creditos da conta do runtime acabaram (I-33: o perfil sai do rodizio e a fase segue em outro perfil, outro runtime ou na fila)',
46
+ 'runtime.auth-missing': 'a conta do runtime nao esta autenticada (I-33: o perfil nunca recebe despacho ate o login ser refeito pelo proprio CLI)',
36
47
  'cost.violation': 'o despacho seria redirecionado para provider pago (violacao de custo: e o unico motivo que NUNCA recebe retry automatico)',
37
48
  'tree.blocked': 'a arvore de destino nao esta disponivel para o merge (branch em check-out com alteracao nao commitada, ou worktree que nao pode ser montada)',
38
49
  'lease.busy': 'lease ocupado por outra thread',
50
+ 'conducao.em-andamento': 'outra conducao ja executa na worktree da thread (I-36): o pedido foi recusado com quem conduz e as tres acoes (esperar, acompanhar, assumir)',
51
+ 'hitl.formato': 'o pedido HITL saiu fora do formato obrigatorio (pergunta em uma frase, de 2 a 4 alternativas rotuladas com consequencia, exatamente uma recomendada com o porque, corpo em lista)',
39
52
  'human.pending': 'gate humano do modo de conducao ainda nao autorizou',
40
53
  };
41
54
  /**
@@ -73,20 +86,55 @@ function descreverMotivo(motivo, detalhe) {
73
86
  return `motivo tipado: ${motivo} (${exports.DESCRICAO_DO_MOTIVO[motivo]})\n detalhe: ${detalhe}`;
74
87
  }
75
88
  /**
76
- * `ork gate approve`: o humano autoriza uma pausa do modo pela linha de comando.
89
+ * `ork gate approve`, APOSENTADO: aprovacao sem pedido e recusada. A pausa se libera pela
90
+ * resposta ao `ork gate request`, que chega pelo ingresso humano autenticado.
77
91
  *
78
- * E o que torna a "autorizacao antecipada de push" do `#Ork` verificavel: ela vira um
79
- * evento no ledger com quem autorizou e quando, em vez de memoria de conversa.
92
+ * Antes da aposentadoria, era o que tornava verificavel a "autorizacao antecipada de push"
93
+ * do `#Classic`: ela virava um evento no ledger com quem autorizou e quando, em vez de
94
+ * memoria de conversa.
80
95
  */
81
- function aprovarGateHumano(raiz, threadId, sobre, por, observacao) {
82
- return (0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, threadId), threadId, ledger_1.TIPOS_DE_EVENTO.pausaHumana, {
83
- estado: 'aprovado',
84
- sobre,
85
- autorizadoPor: por,
86
- observacao: observacao ?? '',
87
- });
96
+ function aprovarGateHumano(_raiz, _threadId, _sobre, _por, _observacao) {
97
+ throw new Error('aprovação cega aposentada: use ork gate request e responda ao pedido pelo ingresso humano autenticado');
98
+ }
99
+ /**
100
+ * FX2: a FORMA do evento. Sozinha ela nunca autorizou nada de verdade.
101
+ *
102
+ * Esta era a checagem inteira: campos presentes e `recibo` com cara de sha256. Quem
103
+ * conseguisse escrever uma linha no `ledger.jsonl` fabricava uma aprovacao humana que
104
+ * `retry` e `ship` aceitavam, porque nenhum dos dois reconferia o MAC do ingresso. A forma
105
+ * continua valendo como primeiro filtro barato; ela so nao decide mais.
106
+ */
107
+ function formaDeAprovacao(e) {
108
+ return e.tipo === ledger_1.TIPOS_DE_EVENTO.pausaHumana && e.estado === 'aprovado' &&
109
+ // I-41 (T4c): as duas versoes do pedido, por lista fechada. Um `human_gate` copia o contrato
110
+ // do pedido que respondeu; recusar v2 aqui faria a aprovacao do dono parar de valer para
111
+ // `retry` e `ship` no dia em que o nucleo passasse a emitir v2. Contrato fora da lista
112
+ // continua recusado, e a prova do recibo, que e quem de fato autoriza, nao muda.
113
+ (0, hitl_contract_1.ehContratoDePedido)(e.contrato) && typeof e.pedidoId === 'string' && typeof e.recibo === 'string' &&
114
+ ((e.origem === 'telegram' || e.origem === 'native') && typeof e.mensagem === 'string' ||
115
+ e.origem === 'mcp-local' && e.proveniencia === 'ork.mcp-elicitation/v1' && e.source === 'human' &&
116
+ ['mcp-local:codex', 'mcp-local:claude-code'].includes(String(e.autorizadoPor)) &&
117
+ typeof e.conexao === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/.test(e.conexao) &&
118
+ typeof e.solicitacao === 'string' && /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(e.solicitacao) &&
119
+ /^[a-f0-9]{64}$/.test(e.recibo));
120
+ }
121
+ /**
122
+ * FX2: a PROVA do evento, reconferida no momento em que ela autoriza.
123
+ *
124
+ * Cada canal ja sabia revalidar o proprio recibo durável; ninguem chamava essas funcoes no
125
+ * caminho de `retry` e `ship`. Aqui elas passam a ser chamadas, e o custo e o certo: uma
126
+ * aprovacao com `recibo` adulterado no ledger deixa de autorizar, mesmo com a forma
127
+ * perfeita. As duas funcoes ja recusam por si so evento sem arquivo, sem MAC, com bytes
128
+ * trocados no disco ou com canal trocado no ledger.
129
+ */
130
+ function aprovacaoHumanaProvada(raiz, threadId, e) {
131
+ if (!formaDeAprovacao(e))
132
+ return false;
133
+ if (e.origem === 'telegram' || e.origem === 'native')
134
+ return (0, hitl_ingress_receipt_1.validarEvidenciaDoIngresso)(raiz, threadId, e);
135
+ return (0, hitl_local_receipt_1.validarEvidenciaLocal)(raiz, threadId, e);
88
136
  }
89
137
  /** Aprovacoes humanas ja registradas no ledger da thread, para os gates consultarem. */
90
138
  function aprovacoesHumanas(raiz, threadId) {
91
- return (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, threadId)).filter((e) => e.tipo === ledger_1.TIPOS_DE_EVENTO.pausaHumana && e.estado === 'aprovado');
139
+ return (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, threadId)).filter((e) => aprovacaoHumanaProvada(raiz, threadId, e));
92
140
  }
package/dist/handoff.js CHANGED
@@ -1,24 +1,4 @@
1
1
  "use strict";
2
- /**
3
- * Handoff triado entre sessoes, regime `files` (visao unificada, secoes 6.1 e 6.2).
4
- *
5
- * Quando o gate de tokens veredicta `new-session`, a sessao que abre NAO recebe copia
6
- * integral da anterior. Ela recebe um handoff com triagem em 3 niveis:
7
- *
8
- * CRITICO -> vai INLINE, sempre: estado do mundo, decisoes locked, criterios de
9
- * sucesso, claims pendentes, baseline.
10
- * IMPORTANTE-> vira PONTEIRO `path#ancora`, com instrucao de recuperacao e o momento
11
- * de resolve-lo. Nao entra no prompt: entra o endereco.
12
- * RESUMIVEL -> vira resumo curto COM proveniencia: historico de tentativas, logs,
13
- * exploracao ja concluida.
14
- *
15
- * Proveniencia e obrigatoria em todo item: source, location e sha256 do arquivo de
16
- * origem. Nada entra no contexto da nova sessao sem dizer de onde veio, e tudo o que
17
- * ficou de fora esta listado como ponteiro, com onde esta e como recuperar.
18
- *
19
- * Sem OrkMind, a recuperacao tardia e leitura dirigida (`ork handoff recall`), nao busca
20
- * semantica. O campo `memory` declara o regime, entao a sessao seguinte sabe o que pedir.
21
- */
22
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
23
3
  if (k2 === undefined) k2 = k;
24
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -62,6 +42,27 @@ exports.exportarHandoff = exportarHandoff;
62
42
  exports.ancoraDeTitulo = ancoraDeTitulo;
63
43
  exports.recall = recall;
64
44
  exports.textoDoHandoff = textoDoHandoff;
45
+ const write_activation_1 = require("./write-activation");
46
+ /**
47
+ * Handoff triado entre sessoes, regime `files` (guia: docs/guias/memoria-e-handoff.md).
48
+ *
49
+ * Quando o gate de tokens veredicta `new-session`, a sessao que abre NAO recebe copia
50
+ * integral da anterior. Ela recebe um handoff com triagem em 3 niveis:
51
+ *
52
+ * CRITICO -> vai INLINE, sempre: estado do mundo, decisoes locked, criterios de
53
+ * sucesso, claims pendentes, baseline.
54
+ * IMPORTANTE-> vira PONTEIRO `path#ancora`, com instrucao de recuperacao e o momento
55
+ * de resolve-lo. Nao entra no prompt: entra o endereco.
56
+ * RESUMIVEL -> vira resumo curto COM proveniencia: historico de tentativas, logs,
57
+ * exploracao ja concluida.
58
+ *
59
+ * Proveniencia e obrigatoria em todo item: source, location e sha256 do arquivo de
60
+ * origem. Nada entra no contexto da nova sessao sem dizer de onde veio, e tudo o que
61
+ * ficou de fora esta listado como ponteiro, com onde esta e como recuperar.
62
+ *
63
+ * Sem OrkMind, a recuperacao tardia e leitura dirigida (`ork handoff recall`), nao busca
64
+ * semantica. O campo `memory` declara o regime, entao a sessao seguinte sabe o que pedir.
65
+ */
65
66
  const node_crypto_1 = require("node:crypto");
66
67
  const fs = __importStar(require("node:fs"));
67
68
  const path = __importStar(require("node:path"));
@@ -132,6 +133,9 @@ function estadoDoMundo(thread) {
132
133
  /** Exporta o handoff triado da thread. Deterministico: mesma entrada, mesmo arquivo. */
133
134
  function exportarHandoff(carregado, threadId, opcoes = {}) {
134
135
  const { raiz, manifesto } = carregado;
136
+ if (opcoes.memoria?.ativo && opcoes.memoria.leituraRestrita) {
137
+ throw Error('memory.query.broad-operation: handoff export exige contexto de manutencao ou regime files');
138
+ }
135
139
  const thread = (0, thread_1.lerThread)(raiz, threadId);
136
140
  const dir = (0, thread_1.dirThread)(raiz, threadId);
137
141
  const proximaFase = opcoes.proximaFase ?? thread.faseAtual;
@@ -215,6 +219,8 @@ function exportarHandoff(carregado, threadId, opcoes = {}) {
215
219
  }
216
220
  // IMPORTANTE: os prompts ja despachados, com o pedido original do builder.
217
221
  for (const s of thread.sessoes) {
222
+ if (s.origem === 'adocao')
223
+ continue;
218
224
  const caminhoPrompt = path.resolve(raiz, s.promptPath);
219
225
  if (!fs.existsSync(caminhoPrompt))
220
226
  continue;
@@ -235,8 +241,8 @@ function exportarHandoff(carregado, threadId, opcoes = {}) {
235
241
  }
236
242
  }
237
243
  if (thread.sessoes.length > 0) {
238
- addResumo(`${thread.sessoes.length} sessao(oes) despachada(s): ` +
239
- thread.sessoes.map((s) => `${s.slug} (${s.fase})`).join(', ') +
244
+ addResumo(`${thread.sessoes.length} sessao(oes) registrada(s): ` +
245
+ thread.sessoes.map((s) => `${s.slug} (${s.fase}, ${s.origem === 'adocao' ? 'adotada' : 'despachada'})`).join(', ') +
240
246
  '. Os prompts estao nos ponteiros acima; a exploracao ja concluida nao vai inline.', proveniencia(raiz, caminhoThreadJson, 'json:sessoes'));
241
247
  }
242
248
  // Bloco B6: com o regime `orkmind` valendo, o que a memoria semantica JA guarda deste
@@ -289,7 +295,17 @@ function exportarHandoff(carregado, threadId, opcoes = {}) {
289
295
  (0, util_1.gravarJson)(caminhoHistorico, handoff);
290
296
  // Publicacao na memoria semantica: o pacote de fase (colecao `handoff`) e as decisoes
291
297
  // fechadas (colecao `decision`), que sao o que uma thread futura precisa herdar.
292
- if (memoria.ativo) {
298
+ let escritaAutorizada = true;
299
+ if (!opcoes.memoria && manifesto.memory.mode === 'orkmind') {
300
+ try {
301
+ (0, write_activation_1.exigirAtivacao)(carregado, threadId, 'memory');
302
+ }
303
+ catch {
304
+ escritaAutorizada = false;
305
+ (0, ledger_1.registrar)(dir, threadId, 'memory_publication_pending', { origem: 'handoff export', motivo: 'write.activation.required', arquivoPreservado: true });
306
+ }
307
+ }
308
+ if (memoria.ativo && escritaAutorizada) {
293
309
  const publicado = (0, memoria_1.gravarHandoff)(memoria, manifesto, thread, handoff, relativo(raiz, caminhoHistorico));
294
310
  const decisoes = (0, memoria_1.gravarDecisoes)(memoria, manifesto, thread);
295
311
  (0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.memoriaGravada, {
@@ -0,0 +1,333 @@
1
+ "use strict";
2
+ /**
3
+ * D12: domicilio unico dos canais HITL homologados.
4
+ *
5
+ * Um CANAL nao e um transporte. `telegram` e `mcp-local` sao dois transportes, e cada um
6
+ * serve dois hosts; o canal e o par host + ingresso, porque e ele que o humano escolhe e e
7
+ * ele que precisa aparecer no recibo. Antes deste registro, uma decisao que entrava pelo
8
+ * Telegram gravava `origem: 'telegram'` e nada mais: depois, ninguem sabia se ela tinha
9
+ * vindo do Hermes ou do OpenClaw. O `mcp-local` ja distinguia (`mcp-local:claude-code` e
10
+ * `mcp-local:codex`); metade dos canais homologados era anonima no proprio recibo.
11
+ *
12
+ * Tres regras estruturais moram aqui, e so aqui:
13
+ *
14
+ * 1. **Sao quatro canais, e a lista e fechada.** Reduzir a lista e mudanca de contrato, nao
15
+ * configuracao: `CANAIS` cobre `Host` inteiro, entao um host novo sem canal nao compila,
16
+ * e um canal removido reprova em `conferirRegistro`.
17
+ * 2. **Canal sem os tres atributos nao entra.** Identidade, correlacao e recibo sao o que
18
+ * torna dois canais equivalentes. Um canal que so tenha dois deles nao e um canal mais
19
+ * fraco: e um canal que nao pode responder, e `conferirRegistro` diz isso com nome.
20
+ * 3. **Transcrito nativo entra por adaptador, nunca como texto de tool.** O conteudo que o
21
+ * humano escreveu chega assinado pelo evento nativo do host (Telegram) ou pelo
22
+ * `elicitInput` da propria conexao MCP. Texto que um agente cole num argumento de
23
+ * ferramenta e autoaprovacao com outro nome, e `recusarIngressoNaoHomologado` recusa.
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.ORDEM_DOS_CANAIS = exports.CANAIS = exports.VARIAVEL_DA_CONTA = exports.VARIAVEL_DA_CHAVE = exports.VARIAVEL_DA_CHAVE_V1 = exports.CONTRATO_CANAIS = void 0;
27
+ exports.selecaoDeCanal = selecaoDeCanal;
28
+ exports.reservaDoPedido = reservaDoPedido;
29
+ exports.conferirCanalSelecionado = conferirCanalSelecionado;
30
+ exports.variavelDaChaveDoCanal = variavelDaChaveDoCanal;
31
+ exports.canaisHomologados = canaisHomologados;
32
+ exports.parseCanal = parseCanal;
33
+ exports.definicaoDoCanal = definicaoDoCanal;
34
+ exports.canaisDoTransporte = canaisDoTransporte;
35
+ exports.transporteDoCanal = transporteDoCanal;
36
+ exports.equivalenciaDoCanal = equivalenciaDoCanal;
37
+ exports.recusarIngressoNaoHomologado = recusarIngressoNaoHomologado;
38
+ exports.conferirRegistro = conferirRegistro;
39
+ exports.ofertaNativa = ofertaNativa;
40
+ exports.ofertaDeCanais = ofertaDeCanais;
41
+ exports.canalDaResposta = canalDaResposta;
42
+ const node_crypto_1 = require("node:crypto");
43
+ const hosts_1 = require("./hosts");
44
+ const hitl_native_1 = require("./hitl-native");
45
+ function selecaoDeCanal(eventos, pedidoId) {
46
+ const last = eventos.filter(e => ['hitl_channel_selected', 'hitl_channel_released'].includes(e.tipo) && e.pedidoId === pedidoId).at(-1);
47
+ return last?.tipo === 'hitl_channel_selected' ? last : undefined;
48
+ }
49
+ /**
50
+ * Leitura para a oferta native: o que impede um ingresso native novo deste pedido agora.
51
+ * Ingresso native nunca seleciona canal, então toda seleção vigente é reserva de outro canal
52
+ * (MCP), e `conferirCanalSelecionado` recusaria a resposta. Não reserva, não libera e não
53
+ * prova abandono: devolve motivo curto ou vazio.
54
+ */
55
+ function reservaDoPedido(eventos, pedidoId) {
56
+ if (eventos.some(e => ['human_gate', 'session_answered'].includes(e.tipo) && e.pedidoId === pedidoId))
57
+ return 'hitl.channel.answered';
58
+ if (eventos.some(e => e.tipo === 'session_answer_sending' && e.pedidoId === pedidoId))
59
+ return 'hitl.channel.delivery-uncertain';
60
+ return selecaoDeCanal(eventos, pedidoId) ? 'hitl.channel.in-use' : '';
61
+ }
62
+ /** Consulta de oferta não autoriza efeito. A seleção vigente amarra o ingresso real. */
63
+ function conferirCanalSelecionado(eventos, pedidoId, canal, transporte, connectionId, requestId) {
64
+ if (!eventos.some(e => e.tipo === 'hitl_channel_selected' && e.pedidoId === pedidoId))
65
+ return; // legado
66
+ const s = selecaoDeCanal(eventos, pedidoId);
67
+ // Release devolve o pedido ao ingresso autenticado. Um callback MCP antigo
68
+ // ainda exige sua seleção exata; envio incerto nunca permite trocar de canal.
69
+ if (!s && transporte !== 'mcp-local' && !eventos.some(e => e.tipo === 'session_answer_sending' && e.pedidoId === pedidoId))
70
+ return;
71
+ if (!s || s.canal !== canal || s.transporte !== transporte || s.connectionId !== connectionId || s.requestId !== requestId)
72
+ throw Error('hitl.channel.selection-mismatch');
73
+ }
74
+ exports.CONTRATO_CANAIS = 'ork.hitl-canais/v1';
75
+ /**
76
+ * FX1: a chave de ingresso e do CANAL, nao do transporte.
77
+ *
78
+ * Hermes e OpenClaw compartilhavam `ORK_HITL_INGRESS_KEY`. Poe-los no mesmo segredo e o
79
+ * mesmo defeito que D12 conserta no recibo, so que em criptografia: quem vaza a chave de um
80
+ * host assina envelopes do outro, e nenhuma conferencia posterior percebe, porque a
81
+ * assinatura confere. Cada canal de telegram passa a ter a sua variavel, e a global fica
82
+ * EXCLUSIVAMENTE para o envelope v1, que nao declara canal e nao pode ganhar um por
83
+ * inferencia. Um v1 continua valendo byte a byte; o risco legado dele esta documentado.
84
+ */
85
+ exports.VARIAVEL_DA_CHAVE_V1 = 'ORK_HITL_INGRESS_KEY';
86
+ /** `null` no canal MCP local: ali a prova vem da conexao, nunca de chave de ambiente. */
87
+ exports.VARIAVEL_DA_CHAVE = {
88
+ openclaw: 'ORK_HITL_INGRESS_KEY_OPENCLAW',
89
+ hermes: 'ORK_HITL_INGRESS_KEY_HERMES',
90
+ 'claude-code': null,
91
+ codex: null,
92
+ };
93
+ /**
94
+ * FX5: a CONTA homologada do canal, quando ele tem uma.
95
+ *
96
+ * O OpenClaw ja conferia `accountId` contra `ORK_HITL_OPENCLAW_ACCOUNT`, mas so no
97
+ * adaptador, de forma transiente: nada disso entrava no corpo assinado nem no recibo
98
+ * duravel, entao o recibo provava transporte telegram generico, nunca a conta autorizada.
99
+ * Quem lesse o recibo depois nao tinha como reconferir de qual conta a decisao entrou.
100
+ * Hermes nao tem conta propria (a allowlist dele e de usuario e chat), e por isso declara
101
+ * `null`: um envelope que traga conta num canal sem conta e recusado, e nao ignorado.
102
+ */
103
+ exports.VARIAVEL_DA_CONTA = {
104
+ openclaw: 'ORK_HITL_OPENCLAW_ACCOUNT',
105
+ hermes: null,
106
+ 'claude-code': null,
107
+ codex: null,
108
+ };
109
+ /** Nome da variavel que autentica este canal. `null` (v1 legado) usa a chave global. */
110
+ function variavelDaChaveDoCanal(canal) {
111
+ if (canal === null)
112
+ return exports.VARIAVEL_DA_CHAVE_V1;
113
+ const nome = exports.VARIAVEL_DA_CHAVE[canal];
114
+ if (!nome)
115
+ throw new Error(`hitl.canal.sem-chave-de-ingresso: ${canal} autentica pela conexao`);
116
+ return nome;
117
+ }
118
+ /**
119
+ * Os quatro canais. `Record<Canal, ...>` e o que impede a lista de encolher em silencio:
120
+ * apagar uma entrada aqui e erro de tipo, nao um canal a menos descoberto em producao.
121
+ */
122
+ exports.CANAIS = {
123
+ openclaw: {
124
+ canal: 'openclaw',
125
+ transporte: 'telegram',
126
+ ingresso: 'evento-nativo-openclaw',
127
+ adaptador: 'adapters/openclaw/src/hitl-ingress.ts',
128
+ identidade: 'telegram:<userId> da allowlist, com canal=openclaw e conta autorizada assinados no corpo do envelope',
129
+ correlacao: 'pedidoId e mensagem telegram:<chatId>:<messageId>, ambos dentro do HMAC',
130
+ recibo: 'ingresso duravel em .orkastery/threads/<t>/hitl-ingress/<sha>.json, com conta e canal dentro do MAC e sha256 no ledger',
131
+ exigencias: ['ORK_HITL_ROOT', 'ORK_HITL_TELEGRAM_BOT_ID', 'ORK_HITL_INGRESS_KEY_OPENCLAW',
132
+ 'ORK_HITL_OPENCLAW_ACCOUNT', 'ORK_HITL_TELEGRAM_USERS', 'ORK_HITL_TELEGRAM_CHATS'],
133
+ },
134
+ hermes: {
135
+ canal: 'hermes',
136
+ transporte: 'telegram',
137
+ ingresso: 'evento-nativo-hermes',
138
+ adaptador: 'adapters/hermes/hitl-ingress/__init__.py',
139
+ identidade: 'telegram:<userId> da allowlist, com canal=hermes assinado no corpo do envelope',
140
+ correlacao: 'pedidoId e mensagem telegram:<chatId>:<messageId>, ambos dentro do HMAC',
141
+ recibo: 'ingresso duravel em .orkastery/threads/<t>/hitl-ingress/<sha>.json, com MAC proprio e sha256 no ledger',
142
+ exigencias: ['ORK_HITL_ROOT', 'ORK_HITL_TELEGRAM_BOT_ID', 'ORK_HITL_INGRESS_KEY_HERMES',
143
+ 'ORK_HITL_TELEGRAM_USERS', 'ORK_HITL_TELEGRAM_CHATS'],
144
+ },
145
+ 'claude-code': {
146
+ canal: 'claude-code',
147
+ transporte: 'mcp-local',
148
+ ingresso: 'elicitation-mcp',
149
+ adaptador: 'core/src/mcp-server.ts',
150
+ identidade: 'mcp-local:claude-code, com o connectionId da conexao que elicitou',
151
+ correlacao: 'requestId da elicitation, contexto da thread e pedidoSha256 conferidos sob lock',
152
+ recibo: 'evidencia local em .orkastery/threads/<t>/hitl-ingress-local/<sha>.json, com sha256 no ledger',
153
+ exigencias: [],
154
+ },
155
+ codex: {
156
+ canal: 'codex',
157
+ transporte: 'mcp-local',
158
+ ingresso: 'elicitation-mcp',
159
+ adaptador: 'core/src/mcp-server.ts',
160
+ identidade: 'mcp-local:codex, com o connectionId da conexao que elicitou',
161
+ correlacao: 'requestId da elicitation, contexto da thread e pedidoSha256 conferidos sob lock',
162
+ recibo: 'evidencia local em .orkastery/threads/<t>/hitl-ingress-local/<sha>.json, com sha256 no ledger',
163
+ exigencias: [],
164
+ },
165
+ };
166
+ /** A ordem de apresentacao e a mesma dos hosts: o humano ve sempre a mesma fila. */
167
+ exports.ORDEM_DOS_CANAIS = hosts_1.ORDEM_DOS_HOSTS;
168
+ function canaisHomologados() {
169
+ return exports.ORDEM_DOS_CANAIS.map((c) => exports.CANAIS[c]);
170
+ }
171
+ /** Reconhece o canal com os mesmos apelidos que o builder ja digita para o host. */
172
+ function parseCanal(bruto) {
173
+ return (0, hosts_1.parseHost)(bruto);
174
+ }
175
+ function definicaoDoCanal(bruto) {
176
+ const canal = parseCanal(bruto);
177
+ if (!canal)
178
+ throw new Error('hitl.canal.desconhecido');
179
+ return exports.CANAIS[canal];
180
+ }
181
+ /** Os canais de um transporte. Preserva a ordem canonica. */
182
+ function canaisDoTransporte(transporte) {
183
+ return exports.ORDEM_DOS_CANAIS.filter((c) => exports.CANAIS[c].transporte === transporte);
184
+ }
185
+ function transporteDoCanal(bruto) {
186
+ return definicaoDoCanal(bruto).transporte;
187
+ }
188
+ /**
189
+ * Os tres atributos que tornam dois canais equivalentes. Vazio em qualquer um deles nao e
190
+ * um canal pior: e um canal que nao responde, e `conferirRegistro` o nomeia.
191
+ */
192
+ function equivalenciaDoCanal(bruto) {
193
+ const d = definicaoDoCanal(bruto);
194
+ return { identidade: d.identidade, correlacao: d.correlacao, recibo: d.recibo };
195
+ }
196
+ /**
197
+ * Recusa qualquer ingresso que nao seja um dos adaptadores homologados. O nome do erro e
198
+ * proposital: quem le o ledger precisa distinguir "canal errado" de "texto de agente".
199
+ */
200
+ function recusarIngressoNaoHomologado(ingresso) {
201
+ const homologados = new Set(canaisHomologados().map((c) => c.ingresso));
202
+ if (typeof ingresso !== 'string' || !homologados.has(ingresso)) {
203
+ throw new Error('hitl.canal.transcrito-de-tool: ingresso nao homologado nao entrega resposta humana');
204
+ }
205
+ return ingresso;
206
+ }
207
+ /** Invariantes do registro. O teste chama isto; o produto tambem, no canario. */
208
+ function conferirRegistro() {
209
+ const canais = canaisHomologados();
210
+ if (canais.length !== hosts_1.ORDEM_DOS_HOSTS.length || canais.length !== 4) {
211
+ throw new Error('hitl.canal.registro-incompleto: os quatro canais homologados sao obrigatorios');
212
+ }
213
+ const vistos = new Set(), chaves = new Set();
214
+ for (const d of canais) {
215
+ if (d.canal !== exports.ORDEM_DOS_CANAIS[canais.indexOf(d)])
216
+ throw new Error('hitl.canal.ordem-divergente');
217
+ for (const [nome, valor] of Object.entries(equivalenciaDoCanal(d.canal))) {
218
+ if (!valor.trim())
219
+ throw new Error(`hitl.canal.sem-${nome}: ${d.canal}`);
220
+ }
221
+ if (!d.adaptador.trim())
222
+ throw new Error(`hitl.canal.sem-adaptador: ${d.canal}`);
223
+ recusarIngressoNaoHomologado(d.ingresso);
224
+ // Identidade repetida entre canais e o defeito que D12 conserta: dois canais
225
+ // indistinguiveis no recibo sao, na pratica, um canal so com dois nomes.
226
+ if (vistos.has(d.identidade))
227
+ throw new Error(`hitl.canal.identidade-ambigua: ${d.canal}`);
228
+ vistos.add(d.identidade);
229
+ // FX1: chave compartilhada e identidade ambigua em criptografia. Dois canais que
230
+ // assinam com o mesmo segredo podem assinar um pelo outro, e a conferencia aprova.
231
+ const chave = exports.VARIAVEL_DA_CHAVE[d.canal];
232
+ if (chave !== null) {
233
+ if (chave === exports.VARIAVEL_DA_CHAVE_V1)
234
+ throw new Error(`hitl.canal.chave-global: ${d.canal}`);
235
+ if (chaves.has(chave))
236
+ throw new Error(`hitl.canal.chave-ambigua: ${d.canal}`);
237
+ chaves.add(chave);
238
+ if (!d.exigencias.includes(chave))
239
+ throw new Error(`hitl.canal.chave-fora-das-exigencias: ${d.canal}`);
240
+ }
241
+ const conta = exports.VARIAVEL_DA_CONTA[d.canal];
242
+ if (conta !== null && !d.exigencias.includes(conta))
243
+ throw new Error(`hitl.canal.conta-fora-das-exigencias: ${d.canal}`);
244
+ }
245
+ return { contrato: exports.CONTRATO_CANAIS, canais: canais.length,
246
+ transportes: new Set(canais.map((c) => c.transporte)).size, chaves: chaves.size };
247
+ }
248
+ /** Oferta adicional; disponibilidade exige vínculo privado e callback homologado pelo host. */
249
+ function ofertaNativa(canal, callbackProved, env = process.env) {
250
+ try {
251
+ if (!callbackProved)
252
+ throw Error('hitl.native.capability-unavailable');
253
+ (0, hitl_native_1.nativeBinding)(canal, env);
254
+ (0, hitl_native_1.nativeKey)(canal, env);
255
+ return { canal, transporte: 'native', estado: 'disponivel', motivo: '' };
256
+ }
257
+ catch {
258
+ return { canal, transporte: 'native', estado: 'indisponivel', motivo: 'hitl.native.capability-unavailable' };
259
+ }
260
+ }
261
+ /**
262
+ * A oferta que o humano ve. Nao autoriza nada e nao abre pedido: diz, para um pedido ja
263
+ * aberto, por quais canais da para responder e por que os outros nao servem agora. Sem
264
+ * oferta nao existe escolha de canal, e sem escolha D12 seria so uma tabela bonita.
265
+ */
266
+ function ofertaDeCanais(ambiente = {}) {
267
+ const env = ambiente.env ?? {};
268
+ const conexoes = new Set(ambiente.conexoesMcp ?? []);
269
+ const chaveHermes = env.ORK_HITL_INGRESS_KEY_HERMES ?? '';
270
+ const chaveOpenClaw = env.ORK_HITL_INGRESS_KEY_OPENCLAW ?? '';
271
+ const chavesTelegramIguais = chaveHermes.length > 0 && chaveOpenClaw.length > 0 &&
272
+ (0, node_crypto_1.createHash)('sha256').update(chaveHermes).digest('hex') ===
273
+ (0, node_crypto_1.createHash)('sha256').update(chaveOpenClaw).digest('hex');
274
+ return canaisHomologados().map((d) => {
275
+ const faltando = d.exigencias.filter((nome) => !(env[nome] ?? '').trim());
276
+ if (faltando.length) {
277
+ return { canal: d.canal, transporte: d.transporte, estado: 'indisponivel',
278
+ motivo: `hitl.credencial: ausente ${faltando.join(', ')}` };
279
+ }
280
+ if (d.transporte === 'telegram') {
281
+ const raiz = env.ORK_HITL_ROOT ?? '';
282
+ const bot = env.ORK_HITL_TELEGRAM_BOT_ID ?? '';
283
+ const usuarios = (env.ORK_HITL_TELEGRAM_USERS ?? '').split(',').map(v => v.trim()).filter(Boolean);
284
+ const chats = (env.ORK_HITL_TELEGRAM_CHATS ?? '').split(',').map(v => v.trim()).filter(Boolean);
285
+ const nomeChave = exports.VARIAVEL_DA_CHAVE[d.canal];
286
+ const chave = nomeChave ? env[nomeChave] ?? '' : '';
287
+ const invalidas = [
288
+ !pathAbsoluto(raiz) ? 'ORK_HITL_ROOT deve ser absoluto' : '',
289
+ !/^\d+$/.test(bot) ? 'ORK_HITL_TELEGRAM_BOT_ID deve ser numerico' : '',
290
+ usuarios.length === 0 || usuarios.some(v => !/^\d+$/.test(v))
291
+ ? 'ORK_HITL_TELEGRAM_USERS deve ser allowlist numerica' : '',
292
+ chats.length === 0 || chats.some(v => !/^-?\d+$/.test(v))
293
+ ? 'ORK_HITL_TELEGRAM_CHATS deve ser allowlist numerica' : '',
294
+ Buffer.byteLength(chave) < 32 ? `${nomeChave} deve ter ao menos 32 bytes` : '',
295
+ chavesTelegramIguais ? 'chaves de Hermes e OpenClaw devem ser distintas' : '',
296
+ ].filter(Boolean);
297
+ if (invalidas.length) {
298
+ return { canal: d.canal, transporte: d.transporte, estado: 'indisponivel',
299
+ motivo: `hitl.credencial: invalida (${invalidas.join('; ')})` };
300
+ }
301
+ }
302
+ if (d.transporte === 'mcp-local' && !conexoes.has(d.canal)) {
303
+ return { canal: d.canal, transporte: d.transporte, estado: 'indisponivel',
304
+ motivo: 'hitl.canal.sem-conexao: nenhuma sessao MCP deste host esta ligada a esta thread' };
305
+ }
306
+ return { canal: d.canal, transporte: d.transporte, estado: 'disponivel', motivo: '' };
307
+ });
308
+ }
309
+ /** Equivalente sintatico de `path.isAbsolute`, sem tocar no filesystem nem no cwd. */
310
+ function pathAbsoluto(valor) {
311
+ return valor.startsWith('/') || /^[A-Za-z]:[\\/]/.test(valor);
312
+ }
313
+ /**
314
+ * Le o canal de um `human_gate` ja gravado. Recibo anterior a D12 nao ganha canal por
315
+ * inferencia: `telegram` sem canal assinado poderia ser Hermes ou OpenClaw, e chutar qual
316
+ * foi e pior do que admitir que o recibo daquela epoca nao guardava a informacao.
317
+ */
318
+ function canalDaResposta(evento) {
319
+ const declarado = parseCanal(typeof evento.canal === 'string' ? evento.canal : null);
320
+ if (declarado)
321
+ return { canal: declarado, equivalencia: 'completa', motivo: '' };
322
+ const origem = typeof evento.origem === 'string' ? evento.origem : '';
323
+ if (origem === 'telegram') {
324
+ return { canal: null, equivalencia: 'legado',
325
+ motivo: 'recibo ork.hitl-answer/v1: transporte telegram sem canal assinado' };
326
+ }
327
+ const porMcp = /^mcp-local:(.+)$/.exec(String(evento.autorizadoPor ?? ''));
328
+ const canal = porMcp ? parseCanal(porMcp[1]) : null;
329
+ if (canal && exports.CANAIS[canal].transporte === 'mcp-local') {
330
+ return { canal, equivalencia: 'completa', motivo: '' };
331
+ }
332
+ return { canal: null, equivalencia: 'legado', motivo: 'recibo sem canal homologado' };
333
+ }