@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.
- package/LICENSE +1 -1
- package/README.md +86 -65
- package/adapters/README.md +30 -15
- package/adapters/claude-code/.claude-plugin/plugin.json +11 -5
- package/adapters/claude-code/README.md +110 -8
- package/adapters/claude-code/commands/onboarding.md +21 -0
- package/adapters/claude-code/commands/ork.md +99 -27
- package/adapters/claude-code/hooks/hooks.json +6 -1
- package/adapters/claude-code/hooks/ork-sensor.js +66 -0
- package/adapters/codex/skills/ork/SKILL.md +57 -0
- package/adapters/hermes/README.md +163 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
- package/adapters/hermes/bin/ork-brain.sh +4 -0
- package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
- package/adapters/hermes/bin/ork-maestro.sh +6 -0
- package/adapters/hermes/bin/ork-master-enviar.py +84 -0
- package/adapters/hermes/bin/ork-objective-message.sh +17 -0
- package/adapters/hermes/bin/ork-objective-status.sh +12 -0
- package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
- package/adapters/hermes/hermes.plugin.json +26 -4
- package/adapters/hermes/hitl-ingress/__init__.py +356 -0
- package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
- package/adapters/openclaw/README.md +135 -26
- package/adapters/openclaw/bin/ork-brain.sh +4 -0
- package/adapters/openclaw/construir.sh +19 -0
- package/adapters/openclaw/dist/hitl-ingress.js +199 -0
- package/adapters/openclaw/dist/index.js +437 -0
- package/adapters/openclaw/openclaw.plugin.json +39 -134
- package/adapters/openclaw/package.json +26 -0
- package/adapters/openclaw/src/hitl-ingress.ts +180 -0
- package/adapters/openclaw/src/index.ts +458 -0
- package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
- package/adapters/openclaw/tsconfig.json +15 -0
- package/assets/docs/markdownlint-cli2.jsonc +22 -0
- package/assets/docs/padroes/documentacao-de-produto.md +133 -0
- package/assets/docs/padroes/roadmap-de-produto.md +98 -0
- package/assets/docs/produto/README.md +14 -0
- package/assets/docs/produto/_modelo-feature.md +59 -0
- package/assets/docs/roadmap/README.md +14 -0
- package/assets/docs/roadmap/_modelo-item.md +73 -0
- package/assets/orkmind-native-schema.json +32 -0
- package/assets/orkmind_bridge.py +357 -0
- package/assets/orkmind_fixture.py +67 -0
- package/assets/orkmind_prospective.py +138 -0
- package/assets/reference-tariffs-i07.json +23 -0
- package/dist/adapters/claude-bg.js +488 -39
- package/dist/adapters/codex-controller-sensor.js +449 -0
- package/dist/adapters/codex-controller-worker.js +361 -0
- package/dist/adapters/codex-controller.js +252 -0
- package/dist/adapters/codex-events.js +205 -0
- package/dist/adapters/codex-question.js +26 -0
- package/dist/adapters/codex-runner.js +133 -0
- package/dist/adapters/codex.js +394 -0
- package/dist/agents-md.js +84 -0
- package/dist/auditoria.js +11 -8
- package/dist/auditrun.js +5 -4
- package/dist/board.js +173 -22
- package/dist/branch-de-estado.js +136 -0
- package/dist/canarios-hitl.js +177 -0
- package/dist/canarios-i43.js +543 -0
- package/dist/canarios-pulse.js +147 -0
- package/dist/canarios-sensores.js +129 -0
- package/dist/canarios.js +111 -2
- package/dist/catalogo.js +9 -0
- package/dist/ci.js +216 -0
- package/dist/ciclos.js +2 -1
- package/dist/claim-lint.js +64 -0
- package/dist/claims.js +60 -0
- package/dist/company-brain-capture.js +195 -0
- package/dist/company-brain-cli.js +123 -0
- package/dist/company-brain-client.js +61 -0
- package/dist/company-brain-contract.js +166 -0
- package/dist/company-brain-journal.js +192 -0
- package/dist/company-brain-mcp.js +33 -0
- package/dist/company-brain-migration.js +60 -0
- package/dist/company-brain-source.js +177 -0
- package/dist/company-brain-worker.js +18 -0
- package/dist/conducao-texto.js +61 -0
- package/dist/conducao.js +876 -0
- package/dist/contrato-publico.js +39 -0
- package/dist/creation-operation-store.js +251 -0
- package/dist/creation-operation.js +148 -0
- package/dist/decisao-autonoma.js +183 -0
- package/dist/delegation.js +79 -0
- package/dist/demo.js +120 -0
- package/dist/docs.js +828 -0
- package/dist/doctor.js +183 -24
- package/dist/entrega-pr.js +117 -0
- package/dist/escopo-escrita.js +71 -0
- package/dist/estado-thread.js +222 -0
- package/dist/evalrunner.js +12 -0
- package/dist/fabrica-estado.js +329 -0
- package/dist/fabrica-publicar.js +76 -0
- package/dist/fix.js +21 -0
- package/dist/gates.js +55 -9
- package/dist/handoff.js +39 -23
- package/dist/hitl-canais.js +333 -0
- package/dist/hitl-classificacao.js +131 -0
- package/dist/hitl-contract.js +465 -0
- package/dist/hitl-estado.js +136 -0
- package/dist/hitl-gates.js +554 -0
- package/dist/hitl-ingress-receipt.js +382 -0
- package/dist/hitl-local-atestado.js +57 -0
- package/dist/hitl-local-receipt.js +328 -0
- package/dist/hitl-local.js +143 -0
- package/dist/hitl-lock.js +139 -0
- package/dist/hitl-lote.js +223 -0
- package/dist/hitl-native-offer.js +97 -0
- package/dist/hitl-native.js +65 -0
- package/dist/hitl-presentation.js +209 -0
- package/dist/hitl-public-receipt.js +176 -0
- package/dist/hitl-resumo.js +209 -0
- package/dist/hitl-sessions.js +475 -0
- package/dist/hitl.js +711 -0
- package/dist/horario.js +269 -0
- package/dist/hosts.js +198 -22
- package/dist/index.js +1736 -93
- package/dist/indice.js +99 -0
- package/dist/init.js +28 -3
- package/dist/integracoes-locais.js +17 -0
- package/dist/leases.js +65 -21
- package/dist/ledger-stats.js +272 -0
- package/dist/ledger.js +107 -2
- package/dist/licoes.js +215 -0
- package/dist/liveness.js +218 -0
- package/dist/maestro-actions.js +51 -0
- package/dist/maestro-authority.js +152 -0
- package/dist/maestro-cli.js +97 -0
- package/dist/maestro-contract.js +55 -0
- package/dist/maestro-discovery.js +132 -0
- package/dist/maestro-runtime.js +268 -0
- package/dist/maestro-snapshot.js +86 -0
- package/dist/maestro-sources.js +224 -0
- package/dist/manifest.js +165 -6
- package/dist/maquina.js +102 -0
- package/dist/master-audit.js +101 -0
- package/dist/master-batch.js +43 -0
- package/dist/master-digest.js +149 -0
- package/dist/master-migracao.js +155 -0
- package/dist/master.js +361 -77
- package/dist/mcp-artifacts.js +234 -0
- package/dist/mcp-git.js +433 -0
- package/dist/mcp-install.js +310 -0
- package/dist/mcp-maestro.js +25 -0
- package/dist/mcp-server.js +469 -0
- package/dist/mcp-ship.js +436 -0
- package/dist/mcp-verify.js +173 -0
- package/dist/memoria-humana.js +207 -0
- package/dist/memoria.js +314 -78
- package/dist/memory-migration.js +279 -0
- package/dist/memory-prospective.js +148 -0
- package/dist/modos-migracao.js +191 -0
- package/dist/modos.js +151 -15
- package/dist/monitor-lock.js +110 -0
- package/dist/objective.js +449 -0
- package/dist/ocupacao.js +267 -0
- package/dist/onboarding.js +303 -0
- package/dist/orkmind.js +389 -90
- package/dist/orquestracao.js +96 -69
- package/dist/phase.js +596 -150
- package/dist/playbook-capabilities.js +150 -0
- package/dist/playbook-contracts.js +188 -0
- package/dist/portfolio-context.js +61 -0
- package/dist/portfolio.js +180 -0
- package/dist/preflight.js +207 -0
- package/dist/process-audit.js +112 -0
- package/dist/project-state.js +93 -0
- package/dist/prompts.js +25 -5
- package/dist/prova-minima.js +112 -0
- package/dist/pulse-cadencia.js +163 -0
- package/dist/pulse-consentimento.js +260 -0
- package/dist/pulse-delivery.js +323 -0
- package/dist/pulse-resposta.js +542 -0
- package/dist/pulse.js +262 -0
- package/dist/ratelimit.js +35 -2
- package/dist/recall.js +80 -3
- package/dist/redacao-saida.js +45 -0
- package/dist/redacao-url.js +66 -0
- package/dist/retry.js +637 -115
- package/dist/roadmap-reservas.js +243 -0
- package/dist/runtime-ambiente.js +38 -0
- package/dist/runtime-context.js +215 -0
- package/dist/runtime-profiles.js +869 -0
- package/dist/runtimes.js +103 -0
- package/dist/sandbox.js +9 -0
- package/dist/session-events.js +203 -0
- package/dist/session-watcher-claude.js +637 -0
- package/dist/session-watcher.js +683 -0
- package/dist/sessoes-adopt.js +154 -0
- package/dist/sessoes-inventario.js +159 -0
- package/dist/sessoes.js +16 -35
- package/dist/setup.js +691 -0
- package/dist/ship.js +150 -15
- package/dist/slug.js +1 -1
- package/dist/thread-close.js +115 -0
- package/dist/thread.js +142 -28
- package/dist/tokens.js +1 -1
- package/dist/util.js +4 -2
- package/dist/verify-sandbox.js +265 -0
- package/dist/verify.js +270 -25
- package/dist/versao.js +60 -0
- package/dist/worktree.js +31 -2
- package/dist/write-activation.js +291 -0
- package/dist/yaml.js +2 -0
- package/eval/casos/onboarding.json +102 -0
- package/eval/casos/orkastery-bootstrap.json +35 -1
- package/eval/casos/scope-check-capability-map.json +45 -7
- package/eval/casos/ship-release.json +2 -2
- package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
- package/eval/fixtures/b2-master-log/caso.json +1 -1
- package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
- package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
- package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
- package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
- package/eval/fixtures/fx-codex-dry/caso.json +18 -0
- package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
- package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
- package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
- package/eval/fixtures/fx-happy/caso.json +6 -4
- package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
- package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
- package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
- package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
- package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
- package/eval/fixtures/fx-omnicanal/caso.json +17 -0
- package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
- package/monitor/company-brain.cjs +17 -0
- package/monitor/pulse-scope.cjs +45 -0
- package/monitor/pulse.cron +18 -0
- package/monitor/varredura-pulse.sh +27 -0
- package/package.json +25 -4
- package/schemas/claims.schema.json +25 -0
- package/schemas/company-brain.schema.json +1158 -0
- package/schemas/creation-operation.schema.json +365 -0
- package/schemas/maestro-snapshot.schema.json +2716 -0
- package/skills/core/onboarding/SKILL.md +50 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
- package/skills/core/thread-state/SKILL.md +3 -1
- 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,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": [
|
|
8
|
+
"skills": [
|
|
9
|
+
"./skills/orkastery-devmaster"
|
|
10
|
+
],
|
|
9
11
|
"bin": {
|
|
10
|
-
"
|
|
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": [
|
|
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
|
}
|