@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
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CLASSES_QUE_NAO_BLOQUEIAM = exports.JANELA_PADRAO_MIN = void 0;
4
+ exports.atoIrreversivelDoItem = atoIrreversivelDoItem;
5
+ exports.classificarItem = classificarItem;
6
+ exports.contarClassificacoes = contarClassificacoes;
7
+ /**
8
+ * I-41 (D16): as tres classificacoes que o resumo recorrente conta, cada uma com definicao
9
+ * verificavel e domicilio unico aqui.
10
+ *
11
+ * A ordem do dono era "urgente, bloqueando, critico", e a armadilha declarada era inventar tres
12
+ * nomes para a mesma coisa. Os tres predicados abaixo perguntam coisas diferentes:
13
+ *
14
+ * BLOQUEANTE pergunta sobre ESTRUTURA: tem alguem parado esperando por isso?
15
+ * URGENTE pergunta sobre RELOGIO: isso vence antes do proximo resumo?
16
+ * CRITICO pergunta sobre NATUREZA: responder isso autoriza um ato que nao se desfaz?
17
+ *
18
+ * Elas nao sao independentes nos dados de hoje, e esconder isso seria pior que dizer. Com
19
+ * `ork.hitl/v1`, prazo so existe dentro de um pedido, e pedido so e anexado a item que tem
20
+ * sessao ou fase parada (`core/src/pulse.ts`, laco de `precisaDeHumanoAgora`): logo hoje
21
+ * URGENTE esta contido em BLOQUEANTE. Sob `ork.hitl/v2` isso se separa, porque uma pergunta
22
+ * com `seguir-recomendada` corre contra o relogio SEM segurar ninguem: a fabrica segue com a
23
+ * recomendada quando o prazo vence. O teste desta entrega prova os dois fatos, o de hoje e o
24
+ * de depois, em vez de prometer independencia que a medicao nega.
25
+ *
26
+ * I-41 (GO-FIX 1, A2), a resposta a "as duas colapsam?": NAO colapsam, porque BLOQUEANTE e
27
+ * estritamente maior. Hoje URGENTE esta contido nele (nenhum emissor usa `seguir-recomendada`,
28
+ * entao nenhum prazo corre sem alguem parado), mas a maioria do que trava nao vence antes do
29
+ * proximo resumo. As duas linhas ficam porque respondem perguntas diferentes: "tem alguem
30
+ * parado?" e "isso acaba antes do proximo aviso?". Somar as duas e o erro que o texto nao convida.
31
+ *
32
+ * I-41 (GO-FIX 1, A1): CRITICO deixou de ser zero por construcao. O emissor do gate declara o ato
33
+ * quando a pausa libera push na base protegida (`atoDaPausa`), e o item que espera nesse gate e
34
+ * contado como sem volta mesmo antes de o pedido nascer (`atoDoItem`).
35
+ *
36
+ * Nenhum dos tres predicado olha para texto. Todos leem campo estruturado.
37
+ */
38
+ const hitl_contract_1 = require("./hitl-contract");
39
+ /**
40
+ * Janela default da cadencia do resumo, em minutos. O dono pediu de hora em hora em 20/09; as
41
+ * tags dele preveem 8h, 2h, 60min, 30min e 15min, entao a janela e parametro, nao constante
42
+ * escondida. Ela entra em URGENTE porque "urgente" so quer dizer alguma coisa em relacao ao
43
+ * proximo aviso: o que vence depois do proximo resumo pode esperar por ele.
44
+ */
45
+ exports.JANELA_PADRAO_MIN = 60;
46
+ /**
47
+ * A unica classe de item que NAO bloqueia ninguem, e por isso a unica excecao escrita aqui.
48
+ * O score pendente nasce depois do `ship_done`: a entrega ja aconteceu, nenhuma fase espera por
49
+ * ele. Chamar 13 scores de "bloqueio" foi parte do que fez o produto dizer que 45 itens
50
+ * precisavam do dono agora quando 44 deles nao eram decisao dele.
51
+ */
52
+ exports.CLASSES_QUE_NAO_BLOQUEIAM = ['score_pendente'];
53
+ /**
54
+ * O ato irreversivel que responder este item autoriza, ou `undefined`.
55
+ *
56
+ * Duas fontes, nesta ordem. Primeiro o proprio pedido, quando ele declara (`ork.hitl/v2`);
57
+ * depois o mapa congelado por motivo, que e o que existe para um pedido `ork.hitl/v1`, que nao
58
+ * tem onde declarar. A declaracao vence o mapa porque e mais especifica: dois pedidos do mesmo
59
+ * motivo podem autorizar atos diferentes.
60
+ */
61
+ function atoIrreversivelDoItem(item) {
62
+ const declarado = item.pedido?.ato;
63
+ if (typeof declarado === 'string' && hitl_contract_1.ATOS_IRREVERSIVEIS.includes(declarado)) {
64
+ return declarado;
65
+ }
66
+ return hitl_contract_1.ATO_IRREVERSIVEL_DO_MOTIVO[item.motivo];
67
+ }
68
+ /** Nome curto da sessao, do jeito que o dono ja ve em `ork board` e nos comandos de log. */
69
+ const sessaoCurta = (id) => id.slice(0, 8);
70
+ function classificarItem(item, opcoes) {
71
+ const agoraMs = Date.parse(opcoes.quando);
72
+ if (!Number.isFinite(agoraMs))
73
+ throw new Error('classificação HITL: instante de consulta inválido');
74
+ const janelaMin = opcoes.janelaMin ?? exports.JANELA_PADRAO_MIN;
75
+ if (!Number.isFinite(janelaMin) || janelaMin <= 0)
76
+ throw new Error('classificação HITL: janela da cadência inválida');
77
+ // BLOQUEANTE: existe sessao ou fase parada esperando por ele.
78
+ let bloqueante, porqueBloqueante;
79
+ if (exports.CLASSES_QUE_NAO_BLOQUEIAM.includes(item.classe)) {
80
+ bloqueante = false;
81
+ porqueBloqueante = 'a entrega já aconteceu; a nota é fila, não bloqueio';
82
+ }
83
+ else if (item.sessionId) {
84
+ bloqueante = true;
85
+ porqueBloqueante = `a sessão ${sessaoCurta(item.sessionId)} está parada esperando`;
86
+ }
87
+ else if (item.thread) {
88
+ bloqueante = true;
89
+ porqueBloqueante = item.fase
90
+ ? `a fase ${item.fase} de ${item.thread} está parada esperando`
91
+ : `${item.thread} está parada esperando`;
92
+ }
93
+ else {
94
+ bloqueante = false;
95
+ porqueBloqueante = 'não aponta sessão nem fase parada';
96
+ }
97
+ // URGENTE: o prazo vence antes do proximo resumo, ou ja venceu.
98
+ const prazoMs = item.pedido ? Date.parse((0, hitl_contract_1.prazoDoPedido)(item.pedido) ?? '') : NaN;
99
+ const urgente = Number.isFinite(prazoMs) && prazoMs <= agoraMs + janelaMin * 60000;
100
+ const porqueUrgente = !Number.isFinite(prazoMs)
101
+ ? 'não tem prazo correndo; antiguidade sozinha não cria urgência'
102
+ : prazoMs <= agoraMs ? 'o prazo já venceu' : `o prazo vence dentro dos próximos ${janelaMin} min`;
103
+ // CRITICO: responder autoriza um ato que nao se desfaz.
104
+ const ato = atoIrreversivelDoItem(item) ?? opcoes.atoDoItem?.(item);
105
+ const critico = ato !== undefined;
106
+ return {
107
+ urgente, bloqueante, critico, ...(ato ? { ato } : {}),
108
+ porque: {
109
+ urgente: porqueUrgente,
110
+ bloqueante: porqueBloqueante,
111
+ critico: ato ? `responder autoriza um ato que não se desfaz: ${ato}` : 'não autoriza nenhum ato da lista congelada',
112
+ },
113
+ };
114
+ }
115
+ function contarClassificacoes(itens, opcoes) {
116
+ const threads = new Set();
117
+ let urgentes = 0, bloqueantes = 0, criticos = 0;
118
+ for (const item of itens) {
119
+ const c = classificarItem(item, opcoes);
120
+ if (c.urgente)
121
+ urgentes++;
122
+ if (c.critico)
123
+ criticos++;
124
+ if (c.bloqueante) {
125
+ bloqueantes++;
126
+ if (item.thread)
127
+ threads.add(item.thread);
128
+ }
129
+ }
130
+ return { total: itens.length, urgentes, bloqueantes, criticos, threadsBloqueadas: [...threads].sort() };
131
+ }
@@ -0,0 +1,465 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CONTRATOS_DE_PEDIDO = exports.CAMPOS_DE_GATE = exports.TETOS_HITL_V2 = exports.LETRAS_DE_ALTERNATIVA = exports.CONTRATO_HITL_V2 = exports.ATO_IRREVERSIVEL_DO_MOTIVO = exports.ATOS_IRREVERSIVEIS = exports.CONTRATO_HITL = void 0;
4
+ exports.atoDaPausa = atoDaPausa;
5
+ exports.profundidadeDoModo = profundidadeDoModo;
6
+ exports.profundidadeDoPedido = profundidadeDoPedido;
7
+ exports.validarPedidoHitlV1 = validarPedidoHitlV1;
8
+ exports.estadoDoPedido = estadoDoPedido;
9
+ exports.respostaAceitaDoPedido = respostaAceitaDoPedido;
10
+ exports.validarRespostaHitl = validarRespostaHitl;
11
+ exports.vereditoDoGate = vereditoDoGate;
12
+ exports.validarPedidoHitlV2 = validarPedidoHitlV2;
13
+ exports.validarPedidoHitl = validarPedidoHitl;
14
+ exports.ehV2 = ehV2;
15
+ exports.classificarDecisao = classificarDecisao;
16
+ exports.motivoDaRecusaDeFormato = motivoDaRecusaDeFormato;
17
+ exports.escolhasDoPedido = escolhasDoPedido;
18
+ exports.chaveDaEscolha = chaveDaEscolha;
19
+ exports.prazoDoPedido = prazoDoPedido;
20
+ exports.expiracaoDoPedido = expiracaoDoPedido;
21
+ exports.alvoDoPedido = alvoDoPedido;
22
+ exports.textoDoPedido = textoDoPedido;
23
+ exports.chavesAceitas = chavesAceitas;
24
+ exports.recomendacaoDoPedido = recomendacaoDoPedido;
25
+ exports.ehContratoDePedido = ehContratoDePedido;
26
+ exports.motivoDoPedido = motivoDoPedido;
27
+ /** D1: contrato de pedido, independente do host. Expiração nunca autoriza. */
28
+ const modos_1 = require("./modos");
29
+ const types_1 = require("./types");
30
+ exports.CONTRATO_HITL = 'ork.hitl/v1';
31
+ /**
32
+ * D5: a lista FECHADA de atos que nao se desfazem. Ela mora no nucleo, congelada, e nao no
33
+ * manifesto: `owner.timezone` pode ser configuracao, "nao gaste meu dinheiro sozinho" nao pode.
34
+ * Uma lista de seguranca que a configuracao do projeto afrouxa deixa de ser seguranca.
35
+ */
36
+ exports.ATOS_IRREVERSIVEIS = ['dinheiro', 'publicacao-externa', 'apagar-dado', 'push-base-protegida'];
37
+ /**
38
+ * D16: qual ato irreversivel um motivo tipado autoriza. O mapa e PARCIAL de proposito. A maioria
39
+ * dos motivos nao autoriza ato nenhum, e inventar um ato para cada motivo transformaria a lista
40
+ * congelada em decoracao. Sob `ork.hitl/v2` a fonte autoritativa passa a ser o proprio pedido,
41
+ * que declara qual dos quatro; este mapa e o que responde por um pedido `ork.hitl/v1`, que nao
42
+ * tem onde declarar. Ausencia aqui significa "nao se sabe que seja irreversivel", nunca
43
+ * "reversivel provado": e por isso que ele nunca libera nada, so acende a marca de critico.
44
+ */
45
+ exports.ATO_IRREVERSIVEL_DO_MOTIVO = Object.freeze({ 'cost.violation': 'dinheiro' });
46
+ /**
47
+ * I-41 (GO-FIX 1, A1): o ato que APROVAR uma pausa autoriza, lido do que a pausa declara.
48
+ *
49
+ * Ate aqui `criticos` era zero por construcao: o mapa por motivo tem uma entrada so, e nenhum
50
+ * emissor declarava ato. Mas a pausa ja diz sobre o que ela e, e isso e dado do nucleo
51
+ * (`modos.ts`), nao texto do agente. A pausa do `#Classic` sobre "evidencias, com autorizacao
52
+ * antecipada de push" e a do `#Look` e do `#Ork` sobre "push" liberam o push na base protegida:
53
+ * o SHIP le exatamente essa aprovacao como autorizacao (`autorizacaoDePush`). Aprovar uma delas
54
+ * e ato sem volta, e o resumo precisa dizer isso em vez de mostrar zero.
55
+ */
56
+ function atoDaPausa(motivo, pausaSobre) {
57
+ const doMotivo = exports.ATO_IRREVERSIVEL_DO_MOTIVO[motivo];
58
+ if (doMotivo)
59
+ return doMotivo;
60
+ return motivo === 'human.pending' && /\bpush\b/i.test(pausaSobre) ? 'push-base-protegida' : undefined;
61
+ }
62
+ const identificador = (v) => typeof v === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/.test(v);
63
+ const texto = (v, limite) => typeof v === 'string' && !!v.trim() && v.length <= limite && !/[\x00-\x08\x0b-\x1f\x7f]/.test(v);
64
+ const objeto = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
65
+ const iso = (v) => typeof v === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(v) &&
66
+ Number.isFinite(Date.parse(v)) && new Date(v).toISOString().replace('.000Z', 'Z') === v.replace('.000Z', 'Z');
67
+ /**
68
+ * Profundidade derivada do modo do pedido.
69
+ *
70
+ * I-43 alargou SO a assinatura, de `Modo` para `ModoLegado`, e nao encostou no corpo:
71
+ * um pedido `ork.hitl/v1` gravado com `modo: look` precisa continuar devolvendo
72
+ * `profunda`, senao o recibo historico deixa de bater com o que ele diz.
73
+ *
74
+ * COSTURA COM A I-41: e a I-41 que torna a profundidade propriedade do PEDIDO, e e
75
+ * esta funcao inteira que ela substitui. O mapa abaixo fica intacto de proposito, para
76
+ * que o merge das duas threads nao escolha um mapa e perca o outro em silencio; quando
77
+ * a I-41 chegar, ele vira o fallback de leitura de recibo antigo.
78
+ */
79
+ function profundidadeDoModo(modo) {
80
+ return modo === 'look' ? 'profunda' : modo === 'classic' || modo === 'ork' ? 'detalhada' : 'resumo';
81
+ }
82
+ /**
83
+ * D1/T6: a profundidade com que um pedido NASCE.
84
+ *
85
+ * Sob `ork.hitl/v2` ela e escolha do PEDIDO e vale nos cinco modos: `profundidadeDoModo` continua
86
+ * sendo o default sugerido, nao mais criterio de validacao. GOAL e PLAN sobem para `profunda`
87
+ * porque e ali que se decide premissa, e premissa sem artefato e sem diff e decidida no escuro.
88
+ * Em 20/09 um pedido de PLAN sobre premissas saiu em `resumo` (sem artefato, sem diff) porque o
89
+ * modo mandava; o modo nao sabe sobre o que se esta perguntando, a fase sabe.
90
+ *
91
+ * Isto governa somente o BLOCO DE EVIDENCIA. A pergunta, as alternativas, as consequencias e a
92
+ * recomendada saem identicas nas tres profundidades.
93
+ */
94
+ function profundidadeDoPedido(fase, modo) {
95
+ return fase === 'GOAL' || fase === 'PLAN' ? 'profunda' : profundidadeDoModo(modo);
96
+ }
97
+ /**
98
+ * D1: o validador de `ork.hitl/v1`, CONGELADO.
99
+ *
100
+ * Ele e o que era `validarPedidoHitl` ate a I-41, movido sem alterar uma regra, e continua sendo
101
+ * a unica coisa que julga um pedido v1. Tres recibos v1 estao gravados em disco e pode haver
102
+ * pedido em voo; um validador compartilhado faria qualquer aperto futuro em v2 mudar o
103
+ * comportamento sobre eles. Congelar v1 em funcao propria e o que torna a promessa "v1 lido para
104
+ * sempre" verificavel por diff, e nao por intencao.
105
+ *
106
+ * Erros não ecoam o conteúdo recebido, que pode conter credencial.
107
+ */
108
+ function validarPedidoHitlV1(v) {
109
+ if (!objeto(v) || v.contrato !== exports.CONTRATO_HITL || !identificador(v.id) || !identificador(v.thread) ||
110
+ !types_1.FASES.includes(v.fase) || !modos_1.MODOS_LEGADOS.includes(v.modo) ||
111
+ !texto(v.motivo, 100) || !texto(v.pergunta, 2000) || !texto(v.recomendacao, 1000)) {
112
+ throw new Error('pedido HITL: identificação, modo ou pergunta inválidos');
113
+ }
114
+ if (!iso(v.criadoEm) || !iso(v.prazo) || Date.parse(v.prazo) <= Date.parse(v.criadoEm) ||
115
+ !['esperar', 'escalar'].includes(v.acaoPadraoAoExpirar)) {
116
+ throw new Error('pedido HITL: prazo ou ação de expiração inválidos');
117
+ }
118
+ const alvo = v.alvo;
119
+ if (!objeto(alvo) || !(alvo.tipo === 'gate' && texto(alvo.sobre, 100) ||
120
+ alvo.tipo === 'session' && identificador(alvo.sessionId) && ['claude-bg', 'codex'].includes(alvo.runtime))) {
121
+ throw new Error('pedido HITL: alvo inválido');
122
+ }
123
+ const aceita = v.respostaAceita;
124
+ if (!objeto(aceita) || !['opcao', 'texto'].includes(aceita.tipo) ||
125
+ !Number.isInteger(aceita.maxCaracteres) || Number(aceita.maxCaracteres) < 1 || Number(aceita.maxCaracteres) > 4096 ||
126
+ alvo.tipo === 'gate' && aceita.tipo !== 'opcao')
127
+ throw new Error('pedido HITL: resposta aceita inválida');
128
+ if (!Array.isArray(v.opcoes) || v.opcoes.length > 12 || (aceita.tipo === 'opcao' && v.opcoes.length < 1) ||
129
+ v.opcoes.some((o, i) => !objeto(o) || o.numero !== i + 1 || !texto(o.texto, 200) ||
130
+ !['aprovar', 'recusar', 'responder', 'esperar'].includes(o.acao))) {
131
+ throw new Error('pedido HITL: opções devem ser numeradas, consecutivas e explícitas');
132
+ }
133
+ if (v.profundidade !== profundidadeDoModo(v.modo))
134
+ throw new Error('pedido HITL: profundidade incompatível com o modo');
135
+ }
136
+ function estadoDoPedido(pedido, quando = new Date().toISOString()) {
137
+ validarPedidoHitl(pedido);
138
+ if (!iso(quando))
139
+ throw new Error('instante de consulta HITL inválido');
140
+ if (Date.parse(quando) < Date.parse(pedido.criadoEm))
141
+ throw new Error('pedido HITL ainda não criado');
142
+ const prazo = prazoDoPedido(pedido), expiracao = expiracaoDoPedido(pedido);
143
+ // Fato consumado não tem relógio: ele não expira, não escala e não avança.
144
+ if (prazo === undefined || expiracao === undefined)
145
+ return 'aberto';
146
+ return Date.parse(quando) >= Date.parse(prazo) ? expiracao : 'aberto';
147
+ }
148
+ /** A forma de resposta que o pedido aceita. `decidido` não aceita nenhuma. */
149
+ function respostaAceitaDoPedido(p) {
150
+ return ehV2(p) ? (p.classe === 'pergunta' ? p.respostaAceita : undefined) : p.respostaAceita;
151
+ }
152
+ function validarRespostaHitl(pedido, resposta, quando) {
153
+ if (estadoDoPedido(pedido, quando) !== 'aberto')
154
+ throw new Error('pedido HITL expirado; nenhuma autorização concedida');
155
+ const aceita = respostaAceitaDoPedido(pedido);
156
+ // Responder a um fato consumado não é recusa de conteúdo: é não haver o que responder.
157
+ if (!aceita)
158
+ throw new Error('decisão informada não aceita resposta');
159
+ if (!texto(resposta, aceita.maxCaracteres))
160
+ throw new Error('resposta HITL inválida');
161
+ if (aceita.tipo === 'texto')
162
+ return null;
163
+ const digitado = resposta.trim().toLowerCase();
164
+ const escolhas = escolhasDoPedido(pedido);
165
+ // No v2 o dono digita a LETRA; o número continua aceito porque recusá-lo não protege nada e
166
+ // quem lê a mensagem antiga do terminal digitaria o número sem saber que mudou.
167
+ const opcao = escolhas.find((o, i) => chaveDaEscolha(pedido, i + 1).toLowerCase() === digitado || String(o.numero) === digitado);
168
+ if (!opcao)
169
+ throw new Error('resposta HITL deve escolher uma opção explícita');
170
+ return opcao;
171
+ }
172
+ /** Deriva todos os campos da decisão que podem alimentar memória humana. */
173
+ function vereditoDoGate(pedido, opcao) {
174
+ validarPedidoHitl(pedido);
175
+ const alvo = alvoDoPedido(pedido);
176
+ if (!alvo)
177
+ throw new Error('decisão informada não tem gate: ela não pede nada');
178
+ if (alvo.tipo !== 'gate')
179
+ throw new Error('pedido destina-se à sessão, não ao gate');
180
+ const motivo = ehV2(pedido) ? (pedido.classe === 'pergunta' ? pedido.motivo : '') : pedido.motivo;
181
+ return {
182
+ fase: pedido.fase,
183
+ sobre: alvo.sobre,
184
+ estado: opcao?.acao === 'aprovar' && motivo === 'human.pending'
185
+ ? 'aprovado' : opcao?.acao === 'recusar' ? 'recusado' : 'aguardando',
186
+ opcao: opcao?.numero ?? null,
187
+ };
188
+ }
189
+ // ---------------------------------------------------------------------------
190
+ // I-41 (D1): `ork.hitl/v2`. v1 continua sendo LIDO para sempre; v2 e o que passa a ser ESCRITO.
191
+ //
192
+ // O caminho e este e nao "v1 com campos opcionais" porque campo opcional que todo mundo precisa
193
+ // preencher e ambiguidade que vira bug: nove arquivos de teste constroem pedido literal e nenhum
194
+ // deles saberia quais opcionais sao obrigatorios na pratica.
195
+ //
196
+ // A uniao e discriminada por `classe`, e a diferenca entre as duas nao e de grau:
197
+ //
198
+ // `decidido` e fato consumado. Nao tem prazo, nao tem identificador para colar, nao tem
199
+ // caminho de resposta, e nao segura fase nenhuma. Trazer campo de gate e RECUSA.
200
+ // `pergunta` e o unico que pode segurar, e o unico que o dono precisa responder.
201
+ // ---------------------------------------------------------------------------
202
+ exports.CONTRATO_HITL_V2 = 'ork.hitl/v2';
203
+ exports.LETRAS_DE_ALTERNATIVA = ['a', 'b', 'c', 'd'];
204
+ /** D13: tetos com origem na maior medida ja gravada nos catorze pedidos do historico. */
205
+ exports.TETOS_HITL_V2 = Object.freeze({
206
+ pergunta: 200, alternativaTexto: 140, consequencia: 140, porque: 140,
207
+ corpoLinha: 200, corpoLinhas: 8, campoDaDecisao: 200, custo: 140, traducao: 120,
208
+ });
209
+ /** Campos de gate que um `decidido` NAO pode trazer. Trazer qualquer um e recusa. */
210
+ exports.CAMPOS_DE_GATE = ['prazo', 'acaoPadraoAoExpirar', 'respostaAceita', 'opcoes', 'alternativas', 'codigo'];
211
+ const linhaUnica = (v, limite) => texto(v, limite) && !/[\r\n]/.test(v);
212
+ function validarComumV2(v) {
213
+ if (!identificador(v.id) || !identificador(v.thread) || !types_1.FASES.includes(v.fase) ||
214
+ !modos_1.MODOS_LEGADOS.includes(v.modo) || !iso(v.criadoEm) ||
215
+ !['resumo', 'detalhada', 'profunda'].includes(v.profundidade)) {
216
+ throw new Error('pedido HITL v2: identificação, modo, instante ou profundidade inválidos');
217
+ }
218
+ }
219
+ function validarDecidido(v) {
220
+ for (const campo of exports.CAMPOS_DE_GATE) {
221
+ if (v[campo] !== undefined)
222
+ throw new Error(`pedido HITL v2: decisão informada não pode trazer ${campo}`);
223
+ }
224
+ if (!linhaUnica(v.decidido, exports.TETOS_HITL_V2.campoDaDecisao) || !linhaUnica(v.porque, exports.TETOS_HITL_V2.campoDaDecisao) ||
225
+ !linhaUnica(v.comoMudar, exports.TETOS_HITL_V2.campoDaDecisao)) {
226
+ throw new Error('pedido HITL v2: decisão informada exige o que foi decidido, o porquê e como mudar');
227
+ }
228
+ const custo = v.custoDeReverter;
229
+ if (!objeto(custo) || !linhaUnica(custo.agora, exports.TETOS_HITL_V2.custo) || !linhaUnica(custo.depois, exports.TETOS_HITL_V2.custo)) {
230
+ throw new Error('pedido HITL v2: custo de reverter exige agora e depois, os dois presentes');
231
+ }
232
+ const criterio = v.criterio;
233
+ if (!objeto(criterio) || !['manifesto', 'ledger', 'medicao'].includes(criterio.tipo) ||
234
+ !linhaUnica(criterio.referencia, 300)) {
235
+ throw new Error('pedido HITL v2: decisão informada exige critério citado e resolvível');
236
+ }
237
+ // R1, porta fechada: ato irreversivel nunca se qualifica como decisao informada.
238
+ if (v.irreversivel === true)
239
+ throw new Error('pedido HITL v2: ato irreversível nunca é decisão informada');
240
+ }
241
+ function validarAlternativas(v) {
242
+ const alternativas = v.alternativas;
243
+ if (!Array.isArray(alternativas) || alternativas.length < 2 || alternativas.length > exports.LETRAS_DE_ALTERNATIVA.length) {
244
+ throw new Error('pedido HITL v2: de 2 a 4 alternativas, rotuladas de a a d');
245
+ }
246
+ let recomendadas = 0;
247
+ alternativas.forEach((bruta, i) => {
248
+ if (!objeto(bruta) || bruta.letra !== exports.LETRAS_DE_ALTERNATIVA[i]) {
249
+ throw new Error('pedido HITL v2: de 2 a 4 alternativas, rotuladas de a a d');
250
+ }
251
+ if (!linhaUnica(bruta.texto, exports.TETOS_HITL_V2.alternativaTexto))
252
+ throw new Error('pedido HITL v2: alternativa sem texto');
253
+ if (!['aprovar', 'recusar', 'responder', 'esperar'].includes(bruta.acao)) {
254
+ throw new Error('pedido HITL v2: alternativa sem ação explícita');
255
+ }
256
+ if (!linhaUnica(bruta.consequencia, exports.TETOS_HITL_V2.consequencia)) {
257
+ throw new Error('pedido HITL v2: alternativa sem consequência em uma linha');
258
+ }
259
+ if (bruta.recomendada === undefined) {
260
+ if (bruta.porque !== undefined)
261
+ throw new Error('pedido HITL v2: só a alternativa recomendada diz o porquê');
262
+ return;
263
+ }
264
+ if (bruta.recomendada !== true)
265
+ throw new Error('pedido HITL v2: recomendada só aceita true ou ausência');
266
+ if (!linhaUnica(bruta.porque, exports.TETOS_HITL_V2.porque)) {
267
+ throw new Error('pedido HITL v2: a alternativa recomendada precisa dizer o porquê em uma linha');
268
+ }
269
+ recomendadas++;
270
+ });
271
+ if (recomendadas !== 1)
272
+ throw new Error('pedido HITL v2: exatamente uma alternativa é recomendada');
273
+ }
274
+ function validarPergunta(v) {
275
+ if (!linhaUnica(v.pergunta, exports.TETOS_HITL_V2.pergunta)) {
276
+ throw new Error('pedido HITL v2: a pergunta é uma frase, sem quebra de linha');
277
+ }
278
+ if (!texto(v.motivo, 100))
279
+ throw new Error('pedido HITL v2: motivo inválido');
280
+ validarAlternativas(v);
281
+ if (!Array.isArray(v.corpo) || v.corpo.length > exports.TETOS_HITL_V2.corpoLinhas ||
282
+ v.corpo.some(l => !linhaUnica(l, exports.TETOS_HITL_V2.corpoLinha))) {
283
+ throw new Error('pedido HITL v2: o corpo é lista de linhas, nunca prosa corrida');
284
+ }
285
+ if (!['objetiva', 'aberta'].includes(v.tipoDeResposta))
286
+ throw new Error('pedido HITL v2: tipo de resposta inválido');
287
+ if (!/^[0-9A-Za-z]{3,8}$/.test(String(v.codigo ?? '')))
288
+ throw new Error('pedido HITL v2: código curto inválido');
289
+ const alvo = v.alvo;
290
+ if (!objeto(alvo) || !(alvo.tipo === 'gate' && texto(alvo.sobre, 100) ||
291
+ alvo.tipo === 'session' && identificador(alvo.sessionId) && ['claude-bg', 'codex'].includes(alvo.runtime))) {
292
+ throw new Error('pedido HITL v2: alvo inválido');
293
+ }
294
+ const aceita = v.respostaAceita;
295
+ if (!objeto(aceita) || !['opcao', 'texto'].includes(aceita.tipo) ||
296
+ !Number.isInteger(aceita.maxCaracteres) || Number(aceita.maxCaracteres) < 1 || Number(aceita.maxCaracteres) > 4096) {
297
+ throw new Error('pedido HITL v2: resposta aceita inválida');
298
+ }
299
+ if (!iso(v.prazo) || Date.parse(v.prazo) <= Date.parse(v.criadoEm) ||
300
+ !['esperar', 'escalar', 'seguir-recomendada'].includes(v.acaoPadraoAoExpirar)) {
301
+ throw new Error('pedido HITL v2: prazo ou ação de expiração inválidos');
302
+ }
303
+ if (typeof v.irreversivel !== 'boolean')
304
+ throw new Error('pedido HITL v2: irreversível precisa ser declarado');
305
+ if (v.irreversivel) {
306
+ if (!exports.ATOS_IRREVERSIVEIS.includes(String(v.ato))) {
307
+ throw new Error('pedido HITL v2: ato irreversível precisa nomear um da lista congelada');
308
+ }
309
+ // D5, primeira das quatro barreiras: a combinacao nao chega a existir.
310
+ if (v.acaoPadraoAoExpirar === 'seguir-recomendada') {
311
+ throw new Error('pedido HITL v2: ato irreversível nunca avança por expiração');
312
+ }
313
+ }
314
+ else if (v.ato !== undefined) {
315
+ throw new Error('pedido HITL v2: só pedido irreversível nomeia ato');
316
+ }
317
+ }
318
+ /** Valida `ork.hitl/v2`. Nunca aceita um v1: quem julga v1 e o validador congelado. */
319
+ function validarPedidoHitlV2(v) {
320
+ if (!objeto(v) || v.contrato !== exports.CONTRATO_HITL_V2)
321
+ throw new Error('pedido HITL v2: contrato inválido');
322
+ validarComumV2(v);
323
+ if (v.classe === 'decidido')
324
+ return validarDecidido(v);
325
+ if (v.classe === 'pergunta')
326
+ return validarPergunta(v);
327
+ throw new Error('pedido HITL v2: classe precisa ser decidido ou pergunta');
328
+ }
329
+ /**
330
+ * O leitor das duas versoes. Despacha por contrato: v1 pelo validador congelado, v2 pelo novo.
331
+ *
332
+ * Os predicados basicos (`texto`, `iso`, `identificador`, `objeto`) sao compartilhados; so as
333
+ * regras de FORMA diferem, que e justamente o que mudou entre as versoes.
334
+ */
335
+ function validarPedidoHitl(v) {
336
+ if (objeto(v) && v.contrato === exports.CONTRATO_HITL_V2)
337
+ return validarPedidoHitlV2(v);
338
+ validarPedidoHitlV1(v);
339
+ }
340
+ /** `true` para pedido `ork.hitl/v2`, para quem precisa escolher caminho sem repetir a string. */
341
+ function ehV2(p) {
342
+ return p.contrato === exports.CONTRATO_HITL_V2;
343
+ }
344
+ function classificarDecisao(sinais) {
345
+ const pergunta = (motivo) => ({ classe: 'pergunta', motivo });
346
+ // As portas fechadas vêm primeiro: nenhuma quantidade de evidência as abre.
347
+ if (sinais.irreversivel)
348
+ return pergunta('o ato é irreversível');
349
+ if (sinais.gastaDinheiro)
350
+ return pergunta('o ato gasta dinheiro');
351
+ if (sinais.mudaEscopoOuProduto)
352
+ return pergunta('o ato muda escopo ou produto');
353
+ // Depois as quatro condições, na ordem em que o GOAL as escreveu.
354
+ const referencia = sinais.criterio?.referencia;
355
+ if (!sinais.criterio || typeof referencia !== 'string' || !referencia.trim()) {
356
+ return pergunta('não há critério escrito que sustente a escolha');
357
+ }
358
+ if (!sinais.alternativasEstritamentePiores)
359
+ return pergunta('as alternativas não são estritamente piores pelo critério');
360
+ if (!sinais.reversivelAntesDaEntrega)
361
+ return pergunta('não dá para desfazer antes da entrega');
362
+ if (!sinais.erroBaratoEDetectavel)
363
+ return pergunta('errar não sai barato, ou o erro não apareceria');
364
+ return { classe: 'decidido', motivo: `as quatro condições valem, pelo critério ${sinais.criterio.tipo}` };
365
+ }
366
+ /**
367
+ * D10: a recusa de FORMA do contrato v2 vira o motivo tipado `hitl.formato`.
368
+ *
369
+ * Ela existe para que quem emite um pedido malformado receba um motivo do catalogo, e nao uma
370
+ * string solta. `hitl.formato` tem politica de retry propria: `corrigir-dirigido` e automatica,
371
+ * porque o defeito e de quem escreveu o pedido. Escalar formato para o dono seria pedir que ele
372
+ * revise a sintaxe do robo.
373
+ *
374
+ * Recusa que nao e de forma (por exemplo contrato desconhecido) devolve `null`: inventar
375
+ * `hitl.formato` para tudo tornaria o motivo tao generico quanto a string que ele substitui.
376
+ */
377
+ function motivoDaRecusaDeFormato(erro) {
378
+ const mensagem = erro instanceof Error ? erro.message : String(erro);
379
+ return mensagem.startsWith('pedido HITL v2: ') && !mensagem.includes('contrato inválido')
380
+ ? 'hitl.formato' : null;
381
+ }
382
+ // ---------------------------------------------------------------------------
383
+ // I-41 (T4): a leitura COMUM as duas versoes.
384
+ //
385
+ // Quem consome um pedido (apresentacao, pulse, gate, sessao) nao deveria precisar saber qual
386
+ // contrato ele usa. Estas funcoes dao a visao uniforme, e por isso `validarRespostaHitl` e
387
+ // `vereditoDoGate` seguem com a mesma assinatura de antes: o que muda e o que elas aceitam.
388
+ // ---------------------------------------------------------------------------
389
+ /** As escolhas do pedido na forma que o resto do nucleo ja consome. `decidido` nao tem nenhuma. */
390
+ function escolhasDoPedido(p) {
391
+ if (!ehV2(p))
392
+ return p.opcoes;
393
+ if (p.classe === 'decidido')
394
+ return [];
395
+ return p.alternativas.map((a, i) => ({ numero: i + 1, texto: a.texto, acao: a.acao }));
396
+ }
397
+ /**
398
+ * O que o dono DIGITA para escolher a n-esima alternativa (1-based).
399
+ *
400
+ * No v1 e o numero; no v2 e a letra. E esta a unica diferenca que o humano percebe entre as
401
+ * duas versoes, e ela existe porque letra dentro de um lote e o que o ponto (9) pede.
402
+ */
403
+ function chaveDaEscolha(p, numero) {
404
+ return ehV2(p) ? (exports.LETRAS_DE_ALTERNATIVA[numero - 1] ?? String(numero)) : String(numero);
405
+ }
406
+ /** O prazo do pedido, ou `undefined` quando ele nao tem um. Fato consumado nao tem prazo. */
407
+ function prazoDoPedido(p) {
408
+ return ehV2(p) ? (p.classe === 'pergunta' ? p.prazo : undefined) : p.prazo;
409
+ }
410
+ /** A acao ao expirar, ou `undefined` quando o pedido nao expira. */
411
+ function expiracaoDoPedido(p) {
412
+ return ehV2(p) ? (p.classe === 'pergunta' ? p.acaoPadraoAoExpirar : undefined) : p.acaoPadraoAoExpirar;
413
+ }
414
+ /** O alvo do pedido, ou `undefined` para `decidido`, que nao se dirige a gate nem a sessao. */
415
+ function alvoDoPedido(p) {
416
+ return ehV2(p) ? (p.classe === 'pergunta' ? p.alvo : undefined) : p.alvo;
417
+ }
418
+ /** A frase que o dono le. Para `decidido`, o que foi decidido; para `pergunta`, a pergunta. */
419
+ function textoDoPedido(p) {
420
+ return ehV2(p) ? (p.classe === 'decidido' ? p.decidido : p.pergunta) : p.pergunta;
421
+ }
422
+ /**
423
+ * Tudo que o dono pode digitar para escolher a n-esima alternativa (1-based).
424
+ *
425
+ * No v1 e so o numero. No v2 e a letra E o numero: recusar o numero nao protege nada, e quem le
426
+ * uma mensagem antiga do terminal digitaria o numero sem saber que a forma mudou. Quem confere
427
+ * recibo precisa da lista inteira, senao uma resposta legitima deixaria de bater com o pedido.
428
+ */
429
+ function chavesAceitas(p, numero) {
430
+ const chave = chaveDaEscolha(p, numero);
431
+ return chave === String(numero) ? [chave] : [chave, String(numero)];
432
+ }
433
+ /**
434
+ * A recomendacao em texto, nas duas versoes.
435
+ *
436
+ * No v1 e o campo livre `recomendacao`, que e justamente o que esta thread conserta. No v2 ela e
437
+ * DERIVADA da alternativa marcada: o texto dela mais o porque em uma linha. Derivar em vez de
438
+ * guardar um campo livre e o que impede a recomendacao de voltar a nao apontar alternativa
439
+ * nenhuma, como "Confira artefatos, claims e riscos antes de responder." nao apontava.
440
+ */
441
+ function recomendacaoDoPedido(p) {
442
+ if (!ehV2(p))
443
+ return p.recomendacao;
444
+ if (p.classe === 'decidido')
445
+ return p.porque;
446
+ const escolhida = p.alternativas.find(a => a.recomendada);
447
+ return escolhida ? `${escolhida.letra}) ${escolhida.texto}: ${escolhida.porque ?? ''}`.trim() : '';
448
+ }
449
+ /** O motivo tipado do pedido, ou string vazia para `decidido`, que nao tem gate. */
450
+ /**
451
+ * Os contratos que um recibo de pedido pode carregar, e SO eles.
452
+ *
453
+ * Um evento `human_gate` copia o contrato do pedido que respondeu. Quem confere autoria precisa
454
+ * aceitar as duas versoes, senao uma aprovacao legitima sob v2 vira autoria ambigua e o dono
455
+ * deixa de conseguir liberar a propria fase. Aceitar por lista fechada, e nao por prefixo, e o
456
+ * que mantem contrato desconhecido recusado: a promessa e ler v1 para sempre, nao ler qualquer
457
+ * coisa que comece com "ork.hitl".
458
+ */
459
+ exports.CONTRATOS_DE_PEDIDO = [exports.CONTRATO_HITL, exports.CONTRATO_HITL_V2];
460
+ function ehContratoDePedido(v) {
461
+ return exports.CONTRATOS_DE_PEDIDO.includes(v);
462
+ }
463
+ function motivoDoPedido(p) {
464
+ return ehV2(p) ? (p.classe === 'pergunta' ? p.motivo : '') : p.motivo;
465
+ }