@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,66 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // Sensor passivo: stdout vazio e exit 0 não aprovam nem negam uma ferramenta.
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { spawnSync } = require('node:child_process');
7
+ const { createHash, randomUUID } = require('node:crypto');
8
+
9
+ function executar() {
10
+ const limite = 1024 * 1024;
11
+ const buffer = Buffer.alloc(limite + 1);
12
+ let bytes = 0;
13
+ while (bytes <= limite) {
14
+ const n = fs.readSync(0, buffer, bytes, buffer.length - bytes, null);
15
+ if (!n) break;
16
+ bytes += n;
17
+ if (bytes > limite) return;
18
+ }
19
+ const h = JSON.parse(buffer.subarray(0, bytes).toString('utf8'));
20
+ if (!h || typeof h.cwd !== 'string' || !path.isAbsolute(h.cwd) ||
21
+ typeof h.session_id !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/.test(h.session_id)) return;
22
+ const tipos = { PermissionRequest: 'permission_request', Notification: 'notification',
23
+ Stop: 'stop', SubagentStop: 'subagent_stop', PostToolUse: 'heartbeat' };
24
+ let tipo = tipos[h.hook_event_name];
25
+ if (!tipo) return;
26
+ const payload = { observedAt: new Date().toISOString() };
27
+ if (h.hook_event_name === 'Notification') payload.notificationType = h.notification_type;
28
+ // tool_use_id identifica replay da mesma ferramenta. Sem id do protocolo,
29
+ // cada invocação é uma ocorrência; stat do transcript não identifica eventos.
30
+ payload.eventId = typeof h.tool_use_id === 'string'
31
+ ? createHash('sha256').update(JSON.stringify([h.hook_event_name, h.tool_use_id, h.agent_id])).digest('hex')
32
+ : randomUUID();
33
+ if (h.hook_event_name === 'PostToolUse' && h.tool_name === 'Bash') {
34
+ const resposta = h.tool_response;
35
+ if (resposta?.interrupted || (resposta?.exit_code !== undefined && resposta.exit_code !== 0) ||
36
+ (resposta?.exitCode !== undefined && resposta.exitCode !== 0)) return;
37
+ const comando = h.tool_input?.command;
38
+ const saida = typeof resposta === 'string' ? resposta : resposta?.stdout;
39
+ const shaCurto = typeof saida === 'string' ? /^\[[^\]\n]+ ([a-f0-9]{7,64})\]/m.exec(saida)?.[1] : null;
40
+ if (typeof comando === 'string' && /(?:^|[;&|\n])\s*git\s+commit(?:\s|$)/.test(comando) && shaCurto) {
41
+ const git = spawnSync('git', ['log', '-1', '--format=%H'], {
42
+ cwd: h.cwd, encoding: 'utf8', timeout: 1000, maxBuffer: 4096,
43
+ });
44
+ const sha = git.stdout?.trim();
45
+ if (git.status === 0 && /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(sha) && sha.startsWith(shaCurto)) {
46
+ tipo = 'commit'; payload.commit = sha;
47
+ }
48
+ }
49
+ }
50
+ const r = spawnSync(process.env.ORK_SENSOR_CLI || 'ork',
51
+ ['sessions', 'event', '--tipo', tipo, '--sessao', h.session_id], {
52
+ cwd: h.cwd, input: JSON.stringify(payload), encoding: 'utf8', timeout: 3000,
53
+ maxBuffer: 32768, windowsHide: true,
54
+ });
55
+ if (r.status !== 0) {
56
+ // Recusa conhecida não identifica o papel da sessão nem comprova ingestão.
57
+ const semVinculo = r.status === 1 && !r.error && !r.stdout?.trim() &&
58
+ r.stderr?.trim() === 'evento recusado: sessão desconhecida; confira ork thread status';
59
+ process.stderr.write(semVinculo
60
+ ? 'ork sensor: sessão sem fase vinculada; evento não ingerido. Se foi despachada pelo Ork, confira o vínculo com ork thread status.\n'
61
+ : 'ork sensor: evento não ingerido; confira o CLI e a sessão registrada.\n');
62
+ }
63
+ }
64
+ try { executar(); }
65
+ catch { process.stderr.write('ork sensor: entrada inválida ou sensor indisponível.\n'); }
66
+ process.exitCode = 0;
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: ork
3
+ description: "Reconhece orkastery maestro em sessão limpa: consulta panorama, retoma threads e apresenta decisões nativas na conversa Codex com o Maestro/Builder."
4
+ license: MIT
5
+ ---
6
+
7
+ # Orkastery no Codex
8
+
9
+ Ao receber literalmente `orkastery maestro`, descubra `ork_maestro` no namespace
10
+ efetivamente exposto e consulte o projeto fixado pelo MCP. A frase pede panorama,
11
+ sem abrir trabalho. Siga a seleção de contexto e os limites do bootstrap comum.
12
+ Sem MCP, a consulta `ork maestro --json` continua somente leitura se o CLI estiver
13
+ disponível; mutações sem transporte nativo permanecem pendentes. Nunca use Bash Git
14
+ ou mudança de sandbox como substitutos. Sessão filha continua a fase recebida.
15
+
16
+ HITL tem prioridade máxima na apresentação: tópicos curtos, recomendação e opções
17
+ com rótulos claros; UUID interno. Use `ork_request_decision` na condutora e preserve
18
+ a resposta literal/cancelamento da elicitation. Não use pergunta genérica do agente
19
+ como aprovação de gate nem texto em toolargs como identidade humana.
20
+
21
+ Use a skill `orkastery-bootstrap` do mesmo catalogo instalado para conduzir o pedido.
22
+ O dono conversa sobre o objetivo; voce opera o nucleo `ork` e apresenta o resultado e o proximo
23
+ passo. Preserve o modo e as autorizacoes da thread. Se ja recebeu uma fase do Orkastery,
24
+ execute essa fase no escopo recebido, sem redespachar a si mesmo.
25
+
26
+ Quando descobertas, prefira as ferramentas MCP vinculadas ao projeto pelo servidor:
27
+ `ork_thread_status`, `ork_phase_list`, `ork_hitl_pending`, `ork_thread_new(nome, modo)`,
28
+ `ork_phase_run(threadId, fase, prompt, runtime?, model?, effort?, dryRun?)`, `ork_observe(threadId)`
29
+ e `ork_request_decision(threadId, pedidoId)`. Use o namespace efetivamente exposto, sem supor outro projeto.
30
+
31
+ - Pedido novo: resolva o modo pelo `ork`, confira preflight e abra/despache a thread.
32
+ - Retomada: leia `ork thread status <thread>` e o ultimo resultado em `ork phase list <thread>`;
33
+ continue o trabalho autorizado sem repetir onboarding, aprovacao ou implementacao.
34
+ - Status: apresente a evidencia da thread; `ork pulse --json` mostra quem precisa agir.
35
+ As pausas dos blocos do modo sao previstas: so diga "aguardando voce" com pendencia
36
+ humana atual comprovada no estado/ultimo resultado, identificando gate, pedido ou evento.
37
+ Uma thread nova sem fase executada nem pedido aberto ainda nao aguarda veredito.
38
+ - Despacho existente: consulte `ork_observe` para progresso ou bloqueio nativo atual;
39
+ nao espere conclusao com loops longos de `phase list`, sleeps ou regex sobre historico.
40
+ - Decisao: se pausa/escalacao real permitir e faltar pedido, use `ork_gate_request(threadId, motivo?)`;
41
+ nunca crie pedido por status. Correlacione thread/pedido e use `ork_request_decision` para apresentar
42
+ o dialogo nativo. A resposta e fornecida pelo dono ao host; nunca invente identidade humana
43
+ nem passe resposta como argumento do modelo. Permissao nativa nao aprova outro gate.
44
+ - Entrega: distinga o que foi implementado, validado, publicado e o que ainda esta pendente.
45
+
46
+ As fases, verificacoes e gates pertencem ao nucleo. As skills de fase e as checklists do
47
+ catalogo acompanham esta entrada. Se a skill comum estiver ausente, informe instalacao
48
+ incompleta em vez de inventar outra metodologia.
49
+
50
+ Use o executavel `ork` do projeto. Se nao estiver no PATH da sessao, o instalador registrou
51
+ este caminho: `{{ork_bin}}`. Nao altere configuracao global para resolver o PATH.
52
+
53
+ Sem MCP e com `.git` protegido pelo sandbox, informe instalacao pendente do transporte
54
+ nativo; nao repita a mutacao negada, nao crie a thread por fora e nao amplie o sandbox.
55
+
56
+ Nao ha monitor em segundo plano criado por esta skill. Acompanhe enquanto a sessao estiver
57
+ ativa e deixe checkpoint para a retomada, com ultimo resultado e proximo passo comprovados.
@@ -1,5 +1,30 @@
1
1
  # Adaptador Hermes
2
2
 
3
+ Na callback Discord autenticada, `/ork offer <thread> <pedido>` apresenta a
4
+ pergunta e a oferta nativa sem responder. O gateway assina uma prova efêmera de
5
+ host, sessão, identidade e mensagem e consulta `gate context --native-offer-stdin`.
6
+ Somente a prova aceita pelo núcleo anuncia o canal nativo disponível naquela
7
+ conversa, sem exigir Telegram. Chave/binding isolados ou payload do modelo não
8
+ comprovam callback. Testes simulados não substituem homologação live pós-SHIP.
9
+
10
+ ## Transporte determinístico do pulse
11
+
12
+ `bin/ork-pulse-enviar.py` chama a API `send_message_tool` do Hermes instalado e retorna
13
+ código 0 somente com `success: true`. Não abre chat nem chama modelo. Use o Python do
14
+ venv do Hermes e um destino explícito `telegram:chat_id[:topic_id]`:
15
+
16
+ ```json
17
+ {
18
+ "executavel": "/caminho/hermes-agent/venv/bin/python3",
19
+ "argumentos": ["/repo/adapters/hermes/bin/ork-pulse-enviar.py", "--target", "telegram:CHAT_ID", "--message", "{{mensagem}}"]
20
+ }
21
+ ```
22
+
23
+ Grave a configuração em `.orkastery/monitor/pulse-host.json`. `HERMES_HOME` e
24
+ `HERMES_AGENT_DIR` permitem localizar outra instalação; por padrão usa `~/.hermes`.
25
+ O adaptador carrega o ambiente do host internamente, sem imprimir credenciais. Marcadores
26
+ `MEDIA:` presentes no texto do runtime são neutralizados para não anexar arquivos locais.
27
+
3
28
  O Hermes vira **podio** do Orkastery: uma skill roteadora fina, um plugin que a declara e um script
4
29
  que abre thread a partir do pedido cru do builder.
5
30
 
@@ -18,6 +43,17 @@ ork adapter install hermes --dry-run # lista o que seria escrito
18
43
  | `skills/orkastery-devmaster/SKILL.md` | O roteador: le a #TAG, chama o `ork`, apresenta os gates |
19
44
  | `hermes.plugin.json` | Declara a skill, o binario do `ork` e as tags de conducao |
20
45
  | `bin/ork-abrir-thread.sh` | Abre a thread com o modo lido do pedido, em um comando |
46
+ | `bin/ork-brain.sh` | Consulta o Company Brain pelo contrato e identidade autenticada do OrkMind |
47
+ | `bin/ork-objective-status.sh` | Lê o ticket canônico compartilhado com o Kanban |
48
+ | `bin/ork-objective-message.sh` | Grava a mensagem do canal no histórico do ticket |
49
+
50
+ ## Continuidade com o Kanban
51
+
52
+ Quando uma conversa mencionar um `obj-*`, use `ork_objective_status` para retomar o estado
53
+ canônico e `ork_objective_message` para preservar a mensagem do Telegram. O Kanban relê o
54
+ mesmo `messages.jsonl`, então o dono pode trocar de canal sem duplicar a demanda. O identificador
55
+ do autor deve vir do gateway autenticado. Mensagens de conversa não aprovam gates: decisões
56
+ continuam passando pelos comandos HITL correlacionados.
21
57
 
22
58
  ## O encolhimento, que e o ponto
23
59
 
@@ -29,6 +65,31 @@ real roda antes e depois; o que mudou e quem garante.
29
65
  Se esta skill comecar a crescer de novo com metodologia, isso e bandeira vermelha: a regra nova
30
66
  pertence ao nucleo, com teste, e nao a esta prosa.
31
67
 
68
+ ## Contas por runtime (I-33)
69
+
70
+ Este host tem superfície CLI, e a paridade com os hosts MCP vem pelos mesmos comandos do
71
+ núcleo; a superfície MCP de `accounts` não existe neste ciclo (o `add` é interativo e local, e
72
+ a leitura de estado já sai de `ork_observe`).
73
+
74
+ ```bash
75
+ ork accounts list [--json] # perfis por runtime, sem segredo
76
+ ork accounts add <id> --runtime claude-bg|codex --dir <diretorio>
77
+ ork accounts check [<id>] # confere o login pelo proprio CLI
78
+ ork accounts remove <id> # desativa; diretorio e login ficam
79
+ ork setup <modo> --bloco N --fallback codex:<modelo>
80
+ ```
81
+
82
+ O login roda no terminal do operador, pelo próprio CLI do runtime com o env do perfil; o
83
+ adaptador nunca recebe, guarda ou repassa credencial, e a conversa nunca é canal de login.
84
+ Quando uma conta esgota a cota, o crédito ou o limite do plano, ou perde o login, o
85
+ `ork retry run` rotaciona e grava `runtime_profile_rotated`; o host só apresenta o evento e a
86
+ pendência humana quando houver. A troca para outro perfil do mesmo runtime por esgotamento vem
87
+ ligada por padrão desde a decisão do dono de 19/09/2026
88
+ (`runtime_profiles.rotate_same_runtime_on_quota: true`; `false` desliga), vale só para perfis
89
+ que o operador cadastrou e autenticou pelo CLI oficial, para contas que ele tem direito de usar
90
+ sob os termos do provedor, e nunca acontece no rate limit comum, que espera a janela na fila
91
+ (ver `SECURITY.md` e `docs/guias/verificacao.md` na raiz do repositório).
92
+
32
93
  ## Os 3 pitfalls de instalacao
33
94
 
34
95
  1. **`ork` fora do PATH do Hermes.** O host costuma rodar com um ambiente mais enxuto que o do
@@ -43,3 +104,105 @@ pertence ao nucleo, com teste, e nao a esta prosa.
43
104
  3. **Validar `allowed_modes` no host.** Um host que recusa o modo antes de chamar o `ork` recusa
44
105
  com a regra que ele tem em cache, nao com a do manifesto do projeto. Deixe o
45
106
  `ork thread new` recusar: o erro vem tipado, com a lista permitida do projeto certo.
107
+
108
+ ## Transporte do digest MASTER
109
+
110
+ `bin/ork-master-enviar.py --target telegram:<chat_id>[:<topico>]` recebe no stdin uma
111
+ página JSON `ork.master-digest-page/v1`, com `texto` e `opcoes`. Usa a configuração Telegram
112
+ do Hermes e só devolve `success: true` quando recebe `message_id`. Um erro não imprime
113
+ credencial nem resposta bruta da API. O transporte não executa comandos do Ork.
114
+
115
+ As opções são um teclado de respostas: o toque envia o texto escolhido como mensagem do
116
+ humano pelo gateway já autenticado. Não há callback privado para o host interpretar.
117
+ O núcleo gera o texto com thread, classe e identificador da proposta, e o host encaminha
118
+ a resposta ao comando de ratificação com o nome do humano autenticado. Enviar a página
119
+ não ratifica nada. Cada página oferece no máximo doze opções e cabe em 3.900 unidades UTF-16.
120
+
121
+ Contrato do teclado: [Telegram ReplyKeyboardMarkup](https://core.telegram.org/bots/api#replykeyboardmarkup).
122
+ Prova local: `python3 -m unittest discover -s adapters/hermes/test -p master_transport_test.py`.
123
+
124
+ ## Respostas HITL no Telegram
125
+
126
+ O gateway autenticado entrega o update original por stdin a `bin/ork-hitl-answer.py ork_gate_answer <thread> <pedido>` ou `ork_session_answer`. Configure `ORK_BIN`, `ORK_HITL_TELEGRAM_USERS` e `ORK_HITL_TELEGRAM_CHATS` com os ids permitidos. Responda ao alerta que contém a linha `ork-hitl <thread> <pedido>`; botões usam `ork:<thread>:<pedido>:<número>`. O transporte recusa bots, chat/usuário não autorizado e resposta sem correlação. O núcleo decide sobre prazo, modo e validade do pedido.
127
+
128
+ O script deve receber eventos diretamente do gateway, cuja autenticação do Telegram é a fronteira de confiança. JSON criado por um agente não é evidência humana. O canário usa um transporte controlado; ele não mede a rede Telegram real. Para aposentar `ork-watchdog-5min`, desative o job no agendador do host e guarde o recibo; nenhum watchdog deve chamar aprovação em nome do builder.
129
+
130
+ Verificação: `python3 -m unittest discover -s adapters/hermes/test -p hitl_answer_test.py -v`.
131
+
132
+ ## Confirmação pelo plugin nativo HITL
133
+
134
+ O plugin `hitl-ingress/` recebe `/ork gate <thread> <pedido> <resposta>` e
135
+ `/ork session <thread> <pedido> <resposta>` em `pre_gateway_dispatch`. Esse hook
136
+ instalado é **síncrono e anterior à autenticação do gateway**: o callback valida
137
+ `MessageEvent`/`telegram.Message`, a autorização do host, usuário, chat, bot,
138
+ texto/id coincidentes, encaminhamento, data de até 60 s e configuração HMAC.
139
+ Todo comando `/ork ` é consumido com `action: skip`, inclusive quando recusado.
140
+ Sem loop ativo, ele recusa; não cria outro loop para o cliente HTTP do bot.
141
+
142
+ O callback reserva a mensagem e agenda o trabalho no loop do gateway. O CLI recebe
143
+ o envelope HMAC por stdin UTF-8, sem shell, em executor, com timeout de 10 s
144
+ (11 s incluindo espera pelo executor). Somente `ok: true`, `pedidoId` coincidente,
145
+ `estado` compatível e `repetida` booleano permitem confirmar o efeito. Sessão exige
146
+ também `sessionId`. A correlação de thread e mensagem vem da invocação CLI exata
147
+ e do envelope validado pelo núcleo; o recibo atual do CLI não ecoa esses campos.
148
+ A prova dirigida simula esse núcleo e não substitui seu CHECK integrado.
149
+
150
+ `Message.reply_text` usa o bot e chat do objeto autenticado no mesmo loop. Os únicos
151
+ textos são “Orkastery: resposta ao gate registrada pelo núcleo.” e “Orkastery:
152
+ resposta entregue à sessão pelo núcleo.” Não há quote da resposta humana,
153
+ parse mode, stdout ou exceção no conteúdo. Recusa de gate também é uma resposta
154
+ registrada; a mensagem não diz que houve aprovação. A confirmação tem orçamento
155
+ total de 3 s e os quatro timeouts do PTB explícitos. Sucesso exige um `Message`
156
+ nativo retornado, id positivo e bot/chat/texto correspondentes.
157
+
158
+ Retorno do hook e logs distinguem `effect` de `confirmation`:
159
+
160
+ | Situação | effect | confirmation |
161
+ |---|---|---|
162
+ | Validação recusada, sem loop ou limite atingido | unconfirmed | not_attempted |
163
+ | Trabalho agendado | pending | not_attempted |
164
+ | CLI iniciado sem recibo válido, timeout ou cancelamento | unknown | not_attempted |
165
+ | Recibo do núcleo válido; confirmação falhou ou ficou ambígua | confirmed | unknown |
166
+ | Recibo do núcleo e recibo Telegram válidos | confirmed | sent |
167
+
168
+ O retorno inicial é um retrato do agendamento; o log de conclusão registra o
169
+ resultado final com referência opaca derivada da identidade da mensagem. Não é
170
+ um ledger durável de entrega. Há no máximo 16 trabalhos pendentes e 1.024 reservas;
171
+ reservas concluídas podem ser removidas após 61 s, e mensagens com data vencida são
172
+ recusadas. O cache guarda apenas metadados, sem resposta humana, chave ou stdout.
173
+
174
+ Replay idêntico no mesmo processo apenas consulta o estado: não chama o CLI nem
175
+ repete a confirmação. Reutilizar a mesma mensagem com operação, resposta, usuário,
176
+ thread ou pedido divergente é recusado. Após restart/perda do cache, a idempotência
177
+ durável é do núcleo: `repetida: true` confirma o efeito, mas deixa o canal como
178
+ `unknown` e suprime novo envio. Timeout do CLI pode ocorrer depois do efeito;
179
+ timeout do Telegram pode ocorrer depois da entrega. Não há rollback do efeito,
180
+ retry automático ou garantia de entrega/leitura exatamente uma vez. Em particular,
181
+ um crash entre efeito e confirmação pode deixar o remetente sem confirmação.
182
+
183
+ Prova dirigida com ambiente removido antes dos imports, casas temporárias, bloqueio
184
+ de rede no verificador e transportes **SIMULADOS**. Exercita as classes, o loader,
185
+ a descoberta opt-in/cache e `invoke_hook` do Hermes instalado, além das regressões
186
+ Python deste adaptador. Não inicia gateway, não usa chaves operacionais nem API real:
187
+
188
+ ```sh
189
+ env -i HOME="$HOME" PATH=/usr/bin:/bin PYTHONDONTWRITEBYTECODE=1 "$HOME/.hermes/hermes-agent/venv/bin/python" adapters/hermes/test/verify_hitl_confirmation.py
190
+ ```
191
+
192
+ O comando imprime contagem, falhas, limites e hashes das fontes instaladas; falha
193
+ se faltar o contrato local, houver teste ignorado ou tentativa bloqueada de ler
194
+ segredo operacional/rede. Não exige build de core. O plano de instalação revisável,
195
+ hashes do pacote e preservação/rollback do agente Hermes instalado pertencem ao
196
+ checkpoint de instalação; instalação e aceite Telegram real são etapas posteriores.
197
+ # HITL nativo e Maestro
198
+
199
+ `orkastery maestro` consulta panorama por `ork_maestro`. Prioridade máxima da
200
+ conversa: tópicos, recomendação e escolhas claras, preservando resposta e cancelamento.
201
+ Telegram é opcional. O callback instalado `pre_gateway_dispatch` pode receber
202
+ Discord sem Telegram, com `discord.Message`, usuário autorizado, sessão existente
203
+ e binding privado `ORK_HITL_NATIVE_BINDING_HERMES`/`ORK_HITL_NATIVE_KEY_HERMES`.
204
+ Chaves e bindings nunca são publicados ao modelo ou herdados pela sessão filha.
205
+ Terminal/ACP sem callback de identidade homologado permanece indisponível; hooks de
206
+ aprovação de comando são observadores e não aprovam gates Orkastery.
207
+ O plugin lê o pedido pelo CLI do núcleo e responde na mesma conversa só após recibo.
208
+ Loader fixture não prova homologação live; instalação e sessão nova pós-SHIP são pendentes.
@@ -8,6 +8,8 @@
8
8
  # Uso: ork-abrir-thread.sh "<nome curto>" "<pedido inteiro do builder>" [--worktree auto]
9
9
 
10
10
  set -eu
11
+ # I-36 (D6): o adaptador declara o canal de conducao do Hermes; descreve a porta, nao da autoridade.
12
+ export ORK_CANAL="${ORK_CANAL:-hermes}"
11
13
 
12
14
  if [ "$#" -lt 2 ]; then
13
15
  echo "uso: ork-abrir-thread.sh \"<nome curto>\" \"<pedido do builder>\" [argumentos extras]" >&2
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ # Host routing only. Authenticated principal and grants belong to the transport.
3
+ set -eu
4
+ exec "{{ork_bin}}" brain "$@"
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env python3
2
+ """Responde HITL a partir de um update Telegram entregue pelo gateway autenticado."""
3
+ import argparse
4
+ import json
5
+ import os
6
+ import re
7
+ import subprocess
8
+ import sys
9
+
10
+
11
+ def lista_permitida(nome):
12
+ return {v.strip() for v in os.environ.get(nome, '').split(',') if v.strip()}
13
+
14
+
15
+ def argumentos(update, operacao, thread, pedido):
16
+ if operacao not in ('ork_gate_answer', 'ork_session_answer'):
17
+ raise ValueError('comando HITL inválido')
18
+ if not re.fullmatch(r'[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}', thread):
19
+ raise ValueError('thread inválida')
20
+ if not re.fullmatch(r'[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}', pedido):
21
+ raise ValueError('pedido inválido')
22
+ callback = update.get('callback_query')
23
+ mensagem = callback.get('message', {}) if callback else update.get('message', {})
24
+ remetente = callback.get('from', {}) if callback else mensagem.get('from', {})
25
+ usuario = str(remetente.get('id', ''))
26
+ chat = str(mensagem.get('chat', {}).get('id', ''))
27
+ if (not usuario or not chat or remetente.get('is_bot') is not False
28
+ or usuario not in lista_permitida('ORK_HITL_TELEGRAM_USERS')
29
+ or chat not in lista_permitida('ORK_HITL_TELEGRAM_CHATS')
30
+ or mensagem.get('forward_origin')):
31
+ raise ValueError('origem humana não autorizada pelo gateway')
32
+ marker = f'ork-hitl {thread} {pedido}'
33
+ if callback:
34
+ prefixo = f'ork:{thread}:{pedido}:'
35
+ dado = callback.get('data', '')
36
+ if not isinstance(dado, str) or not dado.startswith(prefixo):
37
+ raise ValueError('callback não corresponde ao pedido')
38
+ resposta = dado[len(prefixo):]
39
+ if not re.fullmatch(r'[1-9][0-9]?', resposta):
40
+ raise ValueError('opção de callback inválida')
41
+ referencia = callback.get('id')
42
+ else:
43
+ origem = mensagem.get('reply_to_message', {}).get('text', '')
44
+ if marker not in origem.splitlines():
45
+ raise ValueError('responda à mensagem do pedido HITL')
46
+ resposta = mensagem.get('text', '')
47
+ referencia = mensagem.get('message_id')
48
+ if (not isinstance(resposta, str) or not resposta.strip() or len(resposta) > 4096
49
+ or '\x00' in resposta or not referencia):
50
+ raise ValueError('resposta ou referência de mensagem inválida')
51
+ return ['gate' if operacao == 'ork_gate_answer' else 'sessions', 'answer', thread, pedido,
52
+ '--resposta-stdin', '--por', f'telegram:{usuario}',
53
+ '--mensagem', f'telegram:{chat}:{referencia}', '--origem', 'telegram'], resposta
54
+
55
+
56
+ def main():
57
+ parser = argparse.ArgumentParser(description=__doc__)
58
+ parser.add_argument('comando', choices=['ork_gate_answer', 'ork_session_answer'])
59
+ parser.add_argument('thread')
60
+ parser.add_argument('pedido')
61
+ args = parser.parse_args()
62
+ try:
63
+ # O gateway fornece o update original por stdin, nunca uma descrição criada por LLM.
64
+ bruto = sys.stdin.read(65537)
65
+ if len(bruto) > 65536:
66
+ raise ValueError('update excede limite')
67
+ argv, resposta = argumentos(json.loads(bruto), args.comando, args.thread, args.pedido)
68
+ result = subprocess.run([os.environ.get('ORK_BIN', 'ork'), *argv],
69
+ input=resposta, text=True, capture_output=True, timeout=55, check=False)
70
+ # O núcleo devolve um recibo sem repetir a resposta nem credenciais.
71
+ if result.returncode != 0:
72
+ print(json.dumps({'ok': False, 'erro': 'núcleo recusou a resposta', 'codigo': result.returncode}))
73
+ return 1
74
+ print(result.stdout.strip())
75
+ return 0
76
+ except (ValueError, TypeError, AttributeError, OSError, subprocess.TimeoutExpired):
77
+ print(json.dumps({'ok': False, 'erro': 'resposta HITL não confirmada'}))
78
+ return 1
79
+
80
+
81
+ if __name__ == '__main__':
82
+ sys.exit(main())
@@ -0,0 +1,6 @@
1
+ #!/bin/sh
2
+ # Somente leitura; cwd é o projeto do host. Argumentos continuam dados.
3
+ set -eu
4
+ # I-36 (D6): o adaptador declara o canal de conducao do Hermes; descreve a porta, nao da autoridade.
5
+ export ORK_CANAL="${ORK_CANAL:-hermes}"
6
+ exec "${ORK_BIN:-{{ork_bin}}}" maestro --json "$@"
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env python3
2
+ """Entrega uma página de digest com escolhas que chegam ao Hermes como texto humano."""
3
+ import argparse
4
+ import asyncio
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ import re
9
+ import sys
10
+
11
+
12
+ def validar(payload, target):
13
+ if not re.fullmatch(r'telegram:-?\d+(?::\d+)?', target):
14
+ raise ValueError('destino Telegram explícito obrigatório')
15
+ if not isinstance(payload, dict) or payload.get('contrato') != 'ork.master-digest-page/v1':
16
+ raise ValueError('contrato de página inválido')
17
+ texto = payload.get('texto')
18
+ if not isinstance(texto, str) or not texto.strip() or len(texto.encode('utf-16-le')) // 2 > 3900:
19
+ raise ValueError('texto ausente ou maior que uma página')
20
+ opcoes = payload.get('opcoes')
21
+ if not isinstance(opcoes, list) or len(opcoes) > 12:
22
+ raise ValueError('opções inválidas')
23
+ if any(not isinstance(opcao, str) or not opcao.strip() or len(opcao) > 200 for opcao in opcoes):
24
+ raise ValueError('opção inválida')
25
+ partes = target.split(':')
26
+ return partes[1], int(partes[2]) if len(partes) == 3 else None
27
+
28
+
29
+ async def entregar(payload, target, bot):
30
+ chat, topico = validar(payload, target)
31
+ # Teclado de texto usa o fluxo humano já autenticado pelo gateway do Hermes.
32
+ # Não depende de um callback desconhecido nem executa comando ao enviar o digest.
33
+ argumentos = {'chat_id': chat, 'text': payload['texto']}
34
+ if topico is not None:
35
+ argumentos['message_thread_id'] = topico
36
+ if payload['opcoes']:
37
+ argumentos['reply_markup'] = {
38
+ 'keyboard': [[{'text': opcao}] for opcao in payload['opcoes']],
39
+ 'resize_keyboard': True,
40
+ 'one_time_keyboard': True,
41
+ 'input_field_placeholder': 'Escolha a classe para ratificar a proposta',
42
+ }
43
+ resposta = await bot.send_message(**argumentos)
44
+ recibo = getattr(resposta, 'message_id', None)
45
+ if not isinstance(recibo, int) or recibo <= 0:
46
+ raise ValueError('Telegram não confirmou a mensagem')
47
+ return {'success': True, 'message_id': recibo, 'platform': 'telegram'}
48
+
49
+
50
+ async def enviar_pelo_hermes(payload, target):
51
+ validar(payload, target)
52
+ home = Path(os.environ.get('HERMES_HOME', Path.home() / '.hermes'))
53
+ agent = Path(os.environ.get('HERMES_AGENT_DIR', home / 'hermes-agent'))
54
+ sys.path.insert(0, str(agent))
55
+ from dotenv import load_dotenv
56
+ load_dotenv(home / '.env', override=False)
57
+ from gateway.config import load_gateway_config, Platform
58
+ from telegram import Bot
59
+ config = load_gateway_config().platforms.get(Platform.TELEGRAM)
60
+ if not config or not config.enabled or not config.token:
61
+ raise ValueError('Telegram não configurado no Hermes')
62
+ async with Bot(token=config.token) as bot:
63
+ return await entregar(payload, target, bot)
64
+
65
+
66
+ def main():
67
+ parser = argparse.ArgumentParser(description=__doc__)
68
+ parser.add_argument('--target', required=True)
69
+ args = parser.parse_args()
70
+ try:
71
+ bruto = sys.stdin.read(65537)
72
+ if len(bruto.encode('utf-8')) > 65536:
73
+ raise ValueError('payload excede o limite')
74
+ recibo = asyncio.run(enviar_pelo_hermes(json.loads(bruto), args.target))
75
+ print(json.dumps(recibo))
76
+ return 0
77
+ except Exception as exc:
78
+ # Configuração, credenciais e resposta bruta nunca entram no erro de transporte.
79
+ print(json.dumps({'success': False, 'erro': type(exc).__name__}))
80
+ return 1
81
+
82
+
83
+ if __name__ == '__main__':
84
+ sys.exit(main())
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env sh
2
+ # Acrescenta uma mensagem do canal ao mesmo histórico durável visto no Kanban.
3
+ set -eu
4
+ # I-36 (D6): o adaptador declara o canal de conducao do Hermes; descreve a porta, nao da autoridade.
5
+ export ORK_CANAL="${ORK_CANAL:-hermes}"
6
+
7
+ if [ "$#" -lt 3 ]; then
8
+ echo "uso: ork-objective-message.sh <objective-id> <autor-do-canal> <texto>" >&2
9
+ exit 2
10
+ fi
11
+
12
+ OBJECTIVE="$1"
13
+ AUTHOR="$2"
14
+ shift 2
15
+
16
+ exec "${ORK_BIN:-ork}" objective message "$OBJECTIVE" \
17
+ --text "$*" --por "$AUTHOR" --role human
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env sh
2
+ # Lê o ticket canônico para que o Hermes continue a conversa iniciada no Kanban.
3
+ set -eu
4
+ # I-36 (D6): o adaptador declara o canal de conducao do Hermes; descreve a porta, nao da autoridade.
5
+ export ORK_CANAL="${ORK_CANAL:-hermes}"
6
+
7
+ if [ "$#" -ne 1 ]; then
8
+ echo "uso: ork-objective-status.sh <objective-id>" >&2
9
+ exit 2
10
+ fi
11
+
12
+ exec "${ORK_BIN:-ork}" objective status "$1" --json
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env python3
2
+ """Transporte de texto do pulse pela API de mensagem do Hermes, sem inferência."""
3
+ import argparse
4
+ import json
5
+ import os
6
+ from pathlib import Path
7
+ import re
8
+ import sys
9
+
10
+
11
+ def texto_seguro(message):
12
+ message = re.sub(r'(?i)MEDIA\s*:', 'MEDIA [texto]:', message)
13
+ return re.sub(r'(?i)\[\[(?:audio_as_voice|as_document)\]\]', '[diretiva em texto]', message)
14
+
15
+
16
+ def main():
17
+ parser = argparse.ArgumentParser(description=__doc__)
18
+ parser.add_argument('--target', required=True, help='Destino explícito: telegram:chat_id[:topic_id]')
19
+ parser.add_argument('--message', required=True)
20
+ args = parser.parse_args()
21
+ if not re.fullmatch(r'telegram:-?\d+(?::\d+)?', args.target):
22
+ parser.error('use identificador explícito do chat Telegram autorizado')
23
+ home = Path(os.environ.get('HERMES_HOME', Path.home() / '.hermes'))
24
+ agent = Path(os.environ.get('HERMES_AGENT_DIR', home / 'hermes-agent'))
25
+ sys.path.insert(0, str(agent))
26
+ try:
27
+ from dotenv import load_dotenv
28
+ load_dotenv(home / '.env', override=False)
29
+ from tools.send_message_tool import send_message_tool
30
+ # Conteúdo do runtime é texto. Não pode pedir ao host que anexe arquivo local.
31
+ message = texto_seguro(args.message)
32
+ result = send_message_tool({'action': 'send', 'target': args.target, 'message': message})
33
+ data = json.loads(result) if isinstance(result, str) else result
34
+ if (not isinstance(data, dict) or data.get('success') is not True or data.get('skipped')
35
+ or not (data.get('message_id') or data.get('message_ids'))):
36
+ print(json.dumps({'success': False, 'erro': 'Hermes não confirmou a entrega'}))
37
+ return 1
38
+ print(json.dumps({k: data[k] for k in ['success', 'message_id', 'message_ids', 'platform'] if k in data}))
39
+ return 0
40
+ except Exception as exc:
41
+ # Não imprimir configuração, credencial ou resposta bruta de uma API.
42
+ print(json.dumps({'success': False, 'erro': type(exc).__name__}))
43
+ return 1
44
+
45
+
46
+ if __name__ == '__main__':
47
+ sys.exit(main())
@@ -5,17 +5,39 @@
5
5
  "description": "Conducao de looping threads em 6 fases pelo nucleo `ork`. O Hermes vira podio: le a #TAG do pedido, chama o `ork` e apresenta os gates ao humano.",
6
6
  "license": "MIT",
7
7
  "kind": "skill-router",
8
- "skills": ["./skills/orkastery-devmaster"],
8
+ "skills": [
9
+ "./skills/orkastery-devmaster"
10
+ ],
9
11
  "bin": {
10
- "ork-abrir-thread": "./bin/ork-abrir-thread.sh"
12
+ "ork_maestro": "./bin/ork-maestro.sh",
13
+ "ork-abrir-thread": "./bin/ork-abrir-thread.sh",
14
+ "ork_brain": "./bin/ork-brain.sh",
15
+ "ork_objective_status": "./bin/ork-objective-status.sh",
16
+ "ork_objective_message": "./bin/ork-objective-message.sh",
17
+ "ork_gate_answer": "./bin/ork-hitl-answer.py",
18
+ "ork_session_answer": "./bin/ork-hitl-answer.py"
11
19
  },
12
20
  "requires": {
13
21
  "ork": ">=0.1.0",
14
22
  "comando": "{{ork_bin}}"
15
23
  },
16
24
  "conduction": {
17
- "tags": ["#Look", "#Ork", "#Classic", "#Maestro", "#Auto"],
25
+ "tags": [
26
+ "#Classic",
27
+ "#Maestro",
28
+ "#Auto",
29
+ "#Fast"
30
+ ],
31
+ "aposentadas": [
32
+ "#Look",
33
+ "#Ork"
34
+ ],
18
35
  "parse": "ork modos --do-pedido \"<pedido>\"",
19
- "nota": "O host transporta a tag. A validacao contra conduction.allowed_modes e do nucleo."
36
+ "nota": "O host transporta a tag. A validacao contra conduction.allowed_modes e do nucleo. Uma #TAG aposentada recebe recusa tipada modo.aposentado do nucleo, com saida != 0; o host nao a traduz nem a ignora."
37
+ },
38
+ "onboarding": {
39
+ "pauta": "ork onboarding",
40
+ "estado": "ork onboarding show --json",
41
+ "nota": "Etapas e validação são do núcleo; valores secretos somente em ~/.hermes/.env."
20
42
  }
21
43
  }