@orkastery/cli 0.2.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/check.md +4 -3
- package/adapters/claude-code/commands/goal.md +3 -2
- package/adapters/claude-code/commands/master.md +3 -2
- 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-guard.js +1 -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 +161 -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-pulse-enviar.py +47 -0
- package/adapters/hermes/hermes.plugin.json +24 -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 +136 -27
- 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 +460 -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 +12 -9
- package/dist/auditrun.js +5 -4
- package/dist/board.js +174 -23
- 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 +223 -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 +184 -25
- 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 +22 -1
- package/dist/gates.js +59 -11
- 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 +1741 -94
- 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 +109 -2
- package/dist/licoes.js +221 -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 +612 -151
- package/dist/playbook-capabilities.js +150 -0
- package/dist/playbook-contracts.js +188 -0
- package/dist/policies.js +72 -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 +640 -118
- 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 +700 -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 +168 -19
- 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 +5 -1
- package/eval/casos/master-metrics.json +1 -1
- package/eval/casos/onboarding.json +102 -0
- package/eval/casos/orkastery-bootstrap.json +37 -3
- package/eval/casos/scope-check-capability-map.json +45 -7
- package/eval/casos/ship-release.json +3 -3
- 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 +28 -7
- package/references/definition-of-done.md +2 -2
- 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/README.md +3 -3
- 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/decision-triage/SKILL.md +4 -4
- package/skills/governance/narrative-guardian/SKILL.md +1 -1
- package/skills/governance/roadmap-keeper/SKILL.md +1 -1
- package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
- package/skills/observability/thread-tracing/SKILL.md +1 -1
- package/skills/phases/check-quality/SKILL.md +2 -2
- package/skills/phases/goal-definition/SKILL.md +1 -1
- package/skills/phases/master-metrics/SKILL.md +9 -7
- package/skills/phases/plan-specification/SKILL.md +1 -1
- package/skills/phases/ship-release/SKILL.md +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: orkastery-devmaster
|
|
3
|
-
description: "
|
|
3
|
+
description: "Reconhece orkastery maestro no Hermes: consulta panorama pelo ork_maestro, conduz demandas autorizadas e apresenta HITL na conversa."
|
|
4
4
|
bucket: hermes
|
|
5
5
|
roteia: "ork modos --do-pedido | ork thread new | ork phase run | ork ship | ork master"
|
|
6
6
|
license: MIT
|
|
@@ -8,6 +8,20 @@ license: MIT
|
|
|
8
8
|
|
|
9
9
|
# Orkastery no Hermes
|
|
10
10
|
|
|
11
|
+
Mensagem literal `orkastery maestro`: use `ork_maestro` (wrapper de `ork maestro --json`)
|
|
12
|
+
para consultar panorama do projeto atual. Não abra thread por essa consulta. Apresente
|
|
13
|
+
fontes/lacunas e próximas ações; ausência/ambiguidade de projeto exige contexto permitido.
|
|
14
|
+
Em sessão de fase já despachada, siga o bloco recebido sem abrir outra orquestração.
|
|
15
|
+
Ações usam os comandos tipados do núcleo com precondições e readback.
|
|
16
|
+
|
|
17
|
+
Usabilidade HITL tem prioridade máxima: tópicos, recomendação e opções claras, UUID
|
|
18
|
+
interno. Telegram é opcional. Só anuncie ingresso humano disponível quando o callback
|
|
19
|
+
do host estiver homologado e configurado; texto de tool não é identidade humana.
|
|
20
|
+
Sem ingresso no canal escolhido, conserve o pedido pendente e explique o motivo.
|
|
21
|
+
Não altere runtime, provider, perfil filho ou sandbox para contornar falha.
|
|
22
|
+
Horário para o dono sai no fuso dele (`owner.timezone`): use `prazoLocal` e os fatos
|
|
23
|
+
`*Local` do JSON (ex.: `19/09 15:16 (horário de Brasília)`), nunca o ISO em UTC.
|
|
24
|
+
|
|
11
25
|
## O que esta skill e
|
|
12
26
|
|
|
13
27
|
O roteador do Orkastery dentro do Hermes, e **so isso**. Ela nao conduz fase por conta propria,
|
|
@@ -23,30 +37,47 @@ mora no `ork`; aqui ficou o roteador.**
|
|
|
23
37
|
|
|
24
38
|
Sempre que o builder pedir trabalho de produto num repositorio que tem `orkastery.yaml`.
|
|
25
39
|
|
|
40
|
+
## Onboarding do projeto
|
|
41
|
+
|
|
42
|
+
Quando o builder pedir onboarding, execute `ork onboarding` para obter a pauta do núcleo.
|
|
43
|
+
Consulte `ork onboarding show --json` para retomar as etapas pendentes. Conduza a conversa
|
|
44
|
+
pela pauta devolvida e grave cada resposta pública com `ork onboarding set <etapa>
|
|
45
|
+
--conteudo <JSON> --por <quem>`, transportando argumentos como dados. A pauta e a validação
|
|
46
|
+
pertencem ao núcleo; não mantenha lista de etapas no host nem invente respostas.
|
|
47
|
+
Credenciais são somente nomes de variáveis; valores secretos ficam em `~/.hermes/.env`.
|
|
48
|
+
Use `ork onboarding reset [etapa]` quando solicitado e `ork onboarding sync --json` para
|
|
49
|
+
publicação opcional. Apresente o motivo tipado de degradação quando a memória não publicar.
|
|
50
|
+
Onboarding não exige criar thread de produto. Ao concluir, apresente `ork onboarding show --json`.
|
|
51
|
+
|
|
26
52
|
## Passo 1: o modo sai do pedido, nao de configuracao
|
|
27
53
|
|
|
28
54
|
```bash
|
|
29
55
|
MODO=$(ork modos --do-pedido "<pedido inteiro do builder>")
|
|
30
56
|
```
|
|
31
57
|
|
|
32
|
-
O comando usa a mesma funcao do nucleo que reconhece `#
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
58
|
+
O comando usa a mesma funcao do nucleo que reconhece `#Classic`, `#Maestro`, `#Auto` e `#Fast`.
|
|
59
|
+
Sem tag no texto vale o `conduction.default_mode` do manifesto. **Nao reimplemente esse parse
|
|
60
|
+
aqui**, e nao valide o modo: `ork thread new` confere `conduction.allowed_modes` e recusa com erro tipado.
|
|
61
|
+
|
|
62
|
+
`#Look` e `#Ork` foram aposentados pela I-43. Um pedido com uma dessas tags faz o comando
|
|
63
|
+
sair com codigo != 0 e a recusa tipada `modo.aposentado`, que ja nomeia o substituto vivo.
|
|
64
|
+
**Repasse a recusa ao builder como ela veio**: nao traduza para outro modo e nao siga com o
|
|
65
|
+
default, porque trocar o regime de supervisao pelas costas de quem pediu e o defeito que a
|
|
66
|
+
recusa existe para impedir.
|
|
36
67
|
|
|
37
68
|
| #TAG | Pausas | Quando o builder escolhe |
|
|
38
69
|
|---|---|---|
|
|
39
|
-
| `#
|
|
40
|
-
| `#
|
|
41
|
-
| `#Classic` | 3 | O padrao: premissas delicadas, entrega solta, score em batch |
|
|
42
|
-
| `#Maestro` | 1 | Solucao clara e agil, score em batch |
|
|
70
|
+
| `#Classic` | 3 | O padrao do `ork init`: premissas delicadas |
|
|
71
|
+
| `#Maestro` | 1 | Solucao clara e agil: uma pausa para as premissas |
|
|
43
72
|
| `#Auto` | 0 | Docs, estudos, configuracoes, auditorias |
|
|
73
|
+
| `#Fast` | 0 | Pedido pequeno e claro: so a GO; o push pede autorizacao |
|
|
44
74
|
|
|
45
75
|
## Passo 2: antes de abrir, confira a maquina
|
|
46
76
|
|
|
47
77
|
```bash
|
|
48
78
|
ork doctor # sai != 0 quando o despacho nao vale (custo, runtime, ambiente)
|
|
49
79
|
ork board plan # quem avanca agora, quem espera e por que
|
|
80
|
+
ork brain status # contrato Company Brain e tenant efetivo
|
|
50
81
|
```
|
|
51
82
|
|
|
52
83
|
Um `fail` do doctor para o ciclo antes de ele abrir. Isso e economia, nao obstaculo.
|
|
@@ -63,33 +94,54 @@ ork phase run <thread> GO --prompt "<uma tarefa do PLAN por vez>"
|
|
|
63
94
|
ork verify <thread>
|
|
64
95
|
ork phase run <thread> CHECK --prompt "<escopo da verificacao>"
|
|
65
96
|
ork ship <thread> --para main --autorizar-push "<quem>"
|
|
66
|
-
ork master <thread>
|
|
97
|
+
ork master <thread>
|
|
67
98
|
```
|
|
68
99
|
|
|
100
|
+
Quando o dono pedir estado do Company Brain, use o binário instalado `ork_brain` para
|
|
101
|
+
`status`, `query` ou `get`. A identidade vem exclusivamente do login autenticado do
|
|
102
|
+
OrkMind; nunca aceite principal, DSN ou raiz vindos da conversa. Consultas devem levar a
|
|
103
|
+
thread explícita e continuar somente leitura.
|
|
104
|
+
|
|
69
105
|
Cada `phase run` grava o prompt exato com sha256 e registra no ledger. O `ork` reverifica no
|
|
70
106
|
runtime que a sessao existe: self-report de despacho nao vale como evidencia.
|
|
71
107
|
|
|
72
108
|
## Passo 4: apresentar os gates ao humano
|
|
73
109
|
|
|
74
110
|
O Hermes e o podio: e aqui que o builder ve a evidencia e da o veredito. Quando o bloco pausa,
|
|
75
|
-
mostre o que o `ork` produziu e espere. A
|
|
111
|
+
mostre o que o `ork` produziu e espere. A resposta entra pelo gateway autenticado como update Telegram original, com o remetente e o chat validados por allowlist. Use `ork_gate_answer` ou `ork_session_answer`, preservando o id do pedido e a referência da mensagem. Nunca sintetize um update nem use o nome do builder em uma aprovação criada pelo agente.
|
|
76
112
|
|
|
77
113
|
```bash
|
|
78
|
-
ork
|
|
114
|
+
python3 adapters/hermes/bin/ork-hitl-answer.py ork_gate_answer <thread> <pedido> < update.json
|
|
115
|
+
python3 adapters/hermes/bin/ork-hitl-answer.py ork_session_answer <thread> <pedido> < update.json
|
|
79
116
|
```
|
|
80
117
|
|
|
81
|
-
|
|
82
|
-
decidiu, com que evidencia e por que. **O modo afrouxa a pausa, NUNCA a verificacao.**
|
|
118
|
+
`ORK_HITL_TELEGRAM_USERS` e `ORK_HITL_TELEGRAM_CHATS` contêm os ids permitidos, separados por vírgula. Sem configuração o comando recusa. A profundidade já vem do núcleo. Expiração só espera ou escala, nunca aprova. Push e score não são delegáveis.
|
|
83
119
|
|
|
84
|
-
|
|
120
|
+
### Atencao em camadas (I-41) e os formatos que o ingresso aceita
|
|
85
121
|
|
|
86
|
-
O
|
|
122
|
+
O nucleo monta e o Hermes so transporta: (1) UM resumo recorrente com as contagens e
|
|
123
|
+
`Posso te mandar as perguntas agora?`; (2) o dono responde com o codigo e a letra (`K7QX a`);
|
|
124
|
+
(3) com o sim, ate cinco objetivas a–d com uma recomendada, respondidas numa linha (`1a 2c 3b`).
|
|
125
|
+
Decisao obvia chega tomada e informada no resumo (`ork decisao registrar`). Cadencia (I-50): so a tag na mensagem (`#OrkPulseOff` 8h, `#OrkPulseOn` 2h, `#OrkPulseOn-15m`, `-30m`, `-60m`); pedida no meio da frase, devolva a tag exata para o dono mandar sozinha.
|
|
87
126
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
127
|
+
O plugin `orkastery-hitl` so registra esses tres formatos e `/ork gate|session <thread> <pedido> <resposta>`,
|
|
128
|
+
em texto puro, da allowlist, com menos de 60 s. "1. Aprovado" em conversa **nao registra nada**:
|
|
129
|
+
devolva na hora a linha exata, com o id atual de `ork gate request <thread>` (o id muda a cada
|
|
130
|
+
renovacao). **Voce nunca responde pelo dono**: `ork gate answer` do terminal falha com
|
|
131
|
+
`proveniencia em argv diverge do envelope`, e essa e a defesa funcionando.
|
|
132
|
+
|
|
133
|
+
Quando o bloco nao pausa (`#Maestro`, `#Auto`, `#Fast`), o `ork` registra a decisao autonoma com quem
|
|
134
|
+
decidiu, com que evidencia e por que. **O modo afrouxa a pausa, NUNCA a verificacao.**
|
|
135
|
+
|
|
136
|
+
## Passo 5: a entrega e o MASTER
|
|
137
|
+
|
|
138
|
+
O MASTER fecha com POSTMORTEM tipado e indice derivado do ledger; ninguem digita nota para a fila
|
|
139
|
+
andar. `ork master` mostra as entregas; `ork master --aceitar-omissao` aceita as pendentes com
|
|
140
|
+
registro; a nota humana sobrescreve. `ork master --batch` saiu na I-43 (`master.fila-aposentada`).
|
|
91
141
|
|
|
92
|
-
|
|
142
|
+
Memoria "degradada" tem dois portoes: a variavel da DSN no ambiente do processo
|
|
143
|
+
(`memory_degraded` com `dsn.env-ausente`) e a ativacao de escrita com aceite humano
|
|
144
|
+
(`write.activation.disabled`). Diagnostico: `ork memory status` e `ork doctor`.
|
|
93
145
|
|
|
94
146
|
## Racionalizacoes comuns
|
|
95
147
|
|
|
@@ -99,7 +151,8 @@ Fila que ninguem passa e score que nao existe. Traga a fila ao builder.
|
|
|
99
151
|
| "Guardo as regras da metodologia nesta skill" | Metodologia em prosa e metodologia que ninguem verifica. Ela mora no `ork`; aqui fica o roteamento. |
|
|
100
152
|
| "Valido o modo antes de chamar o `ork`" | Validacao duplicada e validacao que vai divergir. `conduction.allowed_modes` e do nucleo. |
|
|
101
153
|
| "O builder confia, pulo o gate desta vez" | Gate pulado e gate que nao existe. Se o modo nao pausa, o `ork` registra a decisao autonoma; se pausa, ele espera. |
|
|
102
|
-
| "Score eu estimo pelo resultado" | O
|
|
154
|
+
| "Score eu estimo pelo resultado" | O indice sai do ledger e a nota, quando existe, e do humano. Estimar nota destroi a metrica de conducao. |
|
|
155
|
+
| "O dono respondeu na conversa, eu registro" | So o ingresso registra, com prova HMAC do update. Devolva a linha exata para ele mandar. |
|
|
103
156
|
|
|
104
157
|
## Bandeiras vermelhas
|
|
105
158
|
|
|
@@ -107,7 +160,7 @@ Fila que ninguem passa e score que nao existe. Traga a fila ao builder.
|
|
|
107
160
|
- Chamada de `git` direta em vez de `ork ship`.
|
|
108
161
|
- Modo escolhido por configuracao do host, e nao pela #TAG do pedido.
|
|
109
162
|
- Thread aberta com `ork doctor` em `fail`.
|
|
110
|
-
-
|
|
163
|
+
- Resposta do dono "registrada" pelo agente, sem o update autenticado do canal.
|
|
111
164
|
|
|
112
165
|
## Verificacao antes de responder ao builder
|
|
113
166
|
|
|
@@ -1,18 +1,48 @@
|
|
|
1
1
|
# Adaptador OpenClaw
|
|
2
2
|
|
|
3
|
-
O OpenClaw recebe o Orkastery como
|
|
4
|
-
|
|
3
|
+
O OpenClaw recebe o Orkastery como uma **extensao** no formato 2026.7.1: um pacote com
|
|
4
|
+
`package.json` (`openclaw.extensions: ["./dist/index.js"]`), um entry JS que registra as
|
|
5
|
+
**21 tools `ork_*`** via `defineToolPlugin` do SDK do proprio OpenClaw, e o manifesto
|
|
6
|
+
`openclaw.plugin.json` gerado (`id`, `activation`, `contracts.tools`). Cada tool e uma
|
|
7
|
+
chamada de CLI do `ork`, sem regra de negocio no host.
|
|
8
|
+
|
|
9
|
+
## Estrutura do pacote
|
|
10
|
+
|
|
11
|
+
| Arquivo | Papel |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `package.json` | `openclaw.extensions` aponta o entry; sem dependencia de runtime |
|
|
14
|
+
| `dist/index.js` | Entry construido; unico arquivo com o placeholder `{{ork_bin}}` |
|
|
15
|
+
| `openclaw.plugin.json` | Manifesto GERADO por `openclaw plugins build` (nao editar na mao) |
|
|
16
|
+
| `src/index.ts` | Fonte TS; `construir.sh` compila com o tsc do core |
|
|
17
|
+
| `bin/ork-abrir-thread.sh` | Atalho de abertura de thread sem reimplementar o parse da #TAG |
|
|
18
|
+
|
|
19
|
+
O entry importa `openclaw/plugin-sdk/tool-plugin`, que o loader do OpenClaw resolve por
|
|
20
|
+
alias: o pacote nao precisa de `node_modules` no destino. Os parametros das tools sao JSON
|
|
21
|
+
Schema literal (sem typebox). Se uma versao futura do `openclaw plugins validate` exigir
|
|
22
|
+
typebox, adicione-o em `dependencies` e rode `npm install` no destino.
|
|
5
23
|
|
|
6
24
|
## Instalacao
|
|
7
25
|
|
|
8
26
|
```bash
|
|
9
|
-
ork adapter install openclaw # instala em <projeto>/.openclaw
|
|
10
|
-
ork adapter install openclaw --dir ~/.openclaw # instala para a maquina
|
|
27
|
+
ork adapter install openclaw # instala em <projeto>/.openclaw/extensions/orkastery
|
|
28
|
+
ork adapter install openclaw --dir ~/.openclaw # instala para a maquina, no source root global
|
|
11
29
|
ork adapter install openclaw --dry-run # lista o que seria escrito
|
|
12
30
|
```
|
|
13
31
|
|
|
14
|
-
O instalador renderiza `{{ork_bin}}` com o caminho absoluto do `ork`
|
|
15
|
-
`INSTALADO.json` com a origem e o sha256 de cada arquivo.
|
|
32
|
+
O instalador renderiza `{{ork_bin}}` no `dist/index.js` com o caminho absoluto do `ork`
|
|
33
|
+
desta maquina e grava `INSTALADO.json` com a origem e o sha256 de cada arquivo.
|
|
34
|
+
|
|
35
|
+
**Registro no OpenClaw.** Com `--dir ~/.openclaw`, o destino
|
|
36
|
+
`~/.openclaw/extensions/orkastery` ja e o source root `global` que o
|
|
37
|
+
`openclaw plugins list` varre: reinicie o gateway e confira. Alternativas equivalentes:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
openclaw plugins install ~/.openclaw/extensions/orkastery # registra explicitamente
|
|
41
|
+
# ou aponte plugins.load.paths para o diretorio instalado na config do OpenClaw
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Se a copia nao passou pelo instalador (placeholder nao renderizado), o entry cai no
|
|
45
|
+
fallback: usa `ORK_BIN` do ambiente ou o `ork` do PATH.
|
|
16
46
|
|
|
17
47
|
## As tools
|
|
18
48
|
|
|
@@ -27,37 +57,116 @@ O instalador renderiza `{{ork_bin}}` com o caminho absoluto do `ork` desta maqui
|
|
|
27
57
|
| `ork_claims_add` | Registra alegacao verificavel com o comando que a comprova |
|
|
28
58
|
| `ork_verify` / `ork_verify_baseline` | Reexecuta no HEAD real; grava a baseline antes do GO |
|
|
29
59
|
| `ork_worktree_ensure` / `ork_worktree_audit` | Worktree isolada, conferida no proprio git |
|
|
30
|
-
| `
|
|
60
|
+
| `ork_objective_status` / `ork_objective_message` | Aposentadas com o `ork objective` na I-43: devolvem a recusa tipada `objective.aposentado` |
|
|
61
|
+
| `ork_portfolio_list` | Lista produtos, projetos e iniciativas canônicos |
|
|
62
|
+
| `ork_gate_answer` | Resposta humana correlacionada a um gate |
|
|
63
|
+
| `ork_session_answer` | Resposta humana correlacionada a uma sessão |
|
|
31
64
|
| `ork_ship` | Merge serializado por lease e push provado contra o remoto |
|
|
32
65
|
| `ork_master` | POSTMORTEM tipado e o score HUMANO de 0 a 5 |
|
|
33
|
-
| `ork_board`
|
|
66
|
+
| `ork_board` | Escalonador: quem avanca agora e quem espera |
|
|
67
|
+
| `ork_master_batch` | Todas as entregas, com o indice do ledger (`ork master --todas`; a fila de score saiu na I-43) |
|
|
34
68
|
|
|
35
69
|
## A #TAG de conducao
|
|
36
70
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
71
|
+
O manifesto novo do OpenClaw nao tem campo proprio para isso, entao a regra vive aqui e
|
|
72
|
+
nas descricoes das tools: `ork_modo_do_pedido` devolve JSON com o modo, a tag, se veio do
|
|
73
|
+
pedido ou do `conduction.default_mode`, e quantas pausas humanas aquele modo tem. O host
|
|
74
|
+
passa o resultado a `ork_thread_new` e **nao decide nada**: `conduction.allowed_modes` e
|
|
75
|
+
conferido dentro do `ork thread new`, que recusa com erro tipado quando o projeto nao
|
|
76
|
+
permite o modo.
|
|
41
77
|
|
|
42
78
|
## Os 3 pitfalls de instalacao
|
|
43
79
|
|
|
44
|
-
1. **Placeholder `{{ork_bin}}` que fica no arquivo.** O
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
`grep -c '{{' <destino>/
|
|
80
|
+
1. **Placeholder `{{ork_bin}}` que fica no arquivo.** O unico placeholder renderizavel
|
|
81
|
+
vive no `dist/index.js`. Nao renderizado, o entry cai no fallback (`ORK_BIN` ou o `ork`
|
|
82
|
+
do PATH); sem fallback possivel, toda tool viraria "comando nao encontrado", e a sessao
|
|
83
|
+
so descobriria depois de ja ter aberto thread na cabeca do agente. Confira com
|
|
84
|
+
`grep -c '{{' <destino>/extensions/orkastery/dist/index.js`, que precisa devolver `0`.
|
|
85
|
+
|
|
86
|
+
2. **Argumento com espaco quebrado em varios argumentos.** Cada tool monta o comando como
|
|
87
|
+
**vetor de argv** e chama o `ork` sem shell, exatamente para que um pedido com espaco,
|
|
88
|
+
aspas ou quebra de linha chegue inteiro em `--prompt`. Se voce adaptar uma tool,
|
|
89
|
+
mantenha o vetor: uma string de shell aqui vira injecao de comando com o texto do
|
|
90
|
+
builder dentro.
|
|
91
|
+
|
|
92
|
+
3. **Tool que reimplementa regra do nucleo.** A tentacao e criar uma tool "esperta" que
|
|
93
|
+
decide o modo, monta o prompt ou escolhe a base. Toda tool deste plugin e uma chamada
|
|
94
|
+
de CLI e nada mais; a decisao que parece faltar aqui ja existe do outro lado, com
|
|
95
|
+
teste. Tool nova que precisa de logica e sinal de que a logica pertence ao `ork`.
|
|
49
96
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
97
|
+
## Como reconstruir
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
bash adapters/openclaw/construir.sh # tsc do core compila src/ para dist/
|
|
101
|
+
cd adapters/openclaw && openclaw plugins build --entry ./dist/index.js # regenera o manifesto
|
|
102
|
+
cd adapters/openclaw && openclaw plugins validate --entry ./dist/index.js # Plugin orkastery is valid.
|
|
103
|
+
```
|
|
54
104
|
|
|
55
|
-
|
|
56
|
-
modo, monta o prompt ou escolhe a base. Toda tool deste plugin e uma chamada de CLI e nada
|
|
57
|
-
mais; a decisao que parece faltar aqui ja existe do outro lado, com teste. Tool nova que precisa
|
|
58
|
-
de logica e sinal de que a logica pertence ao `ork`.
|
|
105
|
+
O `dist/` fica commitado: o instalador copia arquivos do repositorio, nao roda build.
|
|
59
106
|
|
|
60
107
|
## O que este adaptador nao faz
|
|
61
108
|
|
|
62
|
-
Nao escreve codigo de produto, nao decide gate e nao valida modo. Ele traduz intencao em
|
|
63
|
-
`ork`, apresenta o resultado ao humano e registra o que o humano decidiu.
|
|
109
|
+
Nao escreve codigo de produto, nao decide gate e nao valida modo. Ele traduz intencao em
|
|
110
|
+
chamada de `ork`, apresenta o resultado ao humano e registra o que o humano decidiu.
|
|
111
|
+
|
|
112
|
+
## Contas por runtime (I-33)
|
|
113
|
+
|
|
114
|
+
Este host tem superfície CLI, e a paridade com os hosts MCP vem pelos mesmos comandos do
|
|
115
|
+
núcleo; a superfície MCP de `accounts` não existe neste ciclo (o `add` é interativo e local, e
|
|
116
|
+
a leitura de estado já sai de `ork_observe`).
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
ork accounts list [--json] # perfis por runtime, sem segredo
|
|
120
|
+
ork accounts add <id> --runtime claude-bg|codex --dir <diretorio>
|
|
121
|
+
ork accounts check [<id>] # confere o login pelo proprio CLI
|
|
122
|
+
ork accounts remove <id> # desativa; diretorio e login ficam
|
|
123
|
+
ork setup <modo> --bloco N --fallback codex:<modelo>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
O login roda no terminal do operador, pelo próprio CLI do runtime com o env do perfil; o
|
|
127
|
+
adaptador nunca recebe, guarda ou repassa credencial, e a conversa nunca é canal de login.
|
|
128
|
+
Quando uma conta esgota a cota, o crédito ou o limite do plano, ou perde o login, o
|
|
129
|
+
`ork retry run` rotaciona e grava `runtime_profile_rotated`; o host só apresenta o evento e a
|
|
130
|
+
pendência humana quando houver. A troca para outro perfil do mesmo runtime por esgotamento vem
|
|
131
|
+
ligada por padrão desde a decisão do dono de 19/09/2026
|
|
132
|
+
(`runtime_profiles.rotate_same_runtime_on_quota: true`; `false` desliga), vale só para perfis
|
|
133
|
+
que o operador cadastrou e autenticou pelo CLI oficial, para contas que ele tem direito de usar
|
|
134
|
+
sob os termos do provedor, e nunca acontece no rate limit comum, que espera a janela na fila
|
|
135
|
+
(ver `SECURITY.md` e `docs/guias/verificacao.md` na raiz do repositório).
|
|
136
|
+
|
|
137
|
+
## Respostas HITL
|
|
138
|
+
|
|
139
|
+
`ork_gate_answer` e `ork_session_answer` recebem `thread`, `pedido` e `updateTelegram` original do gateway autenticado. Configure `ORK_HITL_TELEGRAM_USERS` e `ORK_HITL_TELEGRAM_CHATS` com os ids permitidos, separados por vírgula. A mensagem responde ao alerta com a linha `ork-hitl <thread> <pedido>`; callback contém `ork:<thread>:<pedido>:<opção>`. Bots, origem não autorizada e correlação incorreta são recusados antes do CLI.
|
|
140
|
+
|
|
141
|
+
O gateway autenticado é a fronteira de confiança: conteúdo de conversa ou JSON criado por agente não comprova resposta humana. `ork_gate_approve` foi retirado do catálogo; migre o host para os comandos de resposta. Expiração nunca aprova, e delegação não pode aprovar push nem score. O núcleo fornece a profundidade da apresentação.
|
|
142
|
+
|
|
143
|
+
Verificação: `sh adapters/openclaw/construir.sh && node --test adapters/openclaw/test/hitl-answer.test.mjs`.
|
|
144
|
+
# Entrada Maestro
|
|
145
|
+
|
|
146
|
+
O callback `inbound_claim` possui ingresso adicional `native` sem Telegram quando
|
|
147
|
+
o SDK comprova `commandAuthorized`, `senderIsOwner`, sessão e identidade do contexto.
|
|
148
|
+
Bindings/chave privados `ORK_HITL_NATIVE_BINDING_OPENCLAW`/`ORK_HITL_NATIVE_KEY_OPENCLAW`
|
|
149
|
+
pertencem ao processo do gateway e ao núcleo, nunca ao modelo. Ausência de campo,
|
|
150
|
+
conta/canal/sessão divergente ou recibo inválido mantém o pedido pendente. A resposta
|
|
151
|
+
usa `/ork gate|session <thread> <pedido> <resposta>` na mesma conversa. Replay não
|
|
152
|
+
reenvia confirmação. A homologação live exige instalação/ativação pós-SHIP.
|
|
153
|
+
|
|
154
|
+
`orkastery maestro` roteia para `ork_maestro`, consulta JSON do núcleo com argv sem
|
|
155
|
+
shell. O panorama não abre trabalho; fonte indisponível permanece explícita.
|
|
156
|
+
Sessão filha executa o bloco recebido. A apresentação HITL prioriza tópicos,
|
|
157
|
+
recomendação e opções rotuladas, sem UUID exposto. Telegram é opcional; ingresso
|
|
158
|
+
nativo adicional só fica disponível após prova do callback e configuração do host.
|
|
159
|
+
Instalação fixture/SDK simulado não comprova ativação em uma sessão real.
|
|
160
|
+
|
|
161
|
+
Na callback autenticada, `/ork offer <thread> <pedido>` consulta a pergunta e a
|
|
162
|
+
oferta nativa sem responder ao pedido. O gateway assina uma prova efêmera da
|
|
163
|
+
mensagem e chama `gate context --native-offer-stdin`; só uma prova válida para
|
|
164
|
+
aquele host, sessão e pedido anuncia disponibilidade na conversa. Configuração
|
|
165
|
+
isolada, prova vencida ou texto produzido pelo modelo não demonstram callback.
|
|
166
|
+
|
|
167
|
+
Antes de executar tools, o helper fixo `ork receipt-verifiers --json` autentica e
|
|
168
|
+
prepara provas públicas dos recibos legados no projeto de `ORK_HITL_ROOT`, definido
|
|
169
|
+
pelo gateway. Os bytes dos recibos e o ledger permanecem preservados. Só os
|
|
170
|
+
verificadores públicos seguem ao executor; variáveis `ORK_HITL_*` são removidas.
|
|
171
|
+
Autoridade presente com projeto, preparação ou verificadores indisponíveis recusa
|
|
172
|
+
a execução da tool (`hitl.receipt.verifiers-unavailable`).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Compila o entry TS do plugin para dist/ usando o tsc do core.
|
|
3
|
+
#
|
|
4
|
+
# Script de DESENVOLVIMENTO, rodado no repositorio do Orkastery; o dist/ gerado
|
|
5
|
+
# fica commitado porque o `ork adapter install openclaw` copia arquivos do
|
|
6
|
+
# repo, nao roda build. No diretorio instalado este script nao funciona (nao ha
|
|
7
|
+
# core/ do lado) e nao precisa: o que roda la e o dist/index.js.
|
|
8
|
+
|
|
9
|
+
set -eu
|
|
10
|
+
|
|
11
|
+
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
12
|
+
TSC="$DIR/../../core/node_modules/.bin/tsc"
|
|
13
|
+
|
|
14
|
+
if [ ! -x "$TSC" ]; then
|
|
15
|
+
echo "tsc nao encontrado em $TSC; rode npm ci em core/ antes" >&2
|
|
16
|
+
exit 2
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
exec "$TSC" -p "$DIR/tsconfig.json"
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/** Native inbound_claim hook only. Never register this callback as a model tool. */
|
|
2
|
+
/** D12: this host is the `openclaw` channel, and the channel is signed, not labelled. */
|
|
3
|
+
import { createHash, createHmac } from 'node:crypto';
|
|
4
|
+
import { execFile } from 'node:child_process';
|
|
5
|
+
/**
|
|
6
|
+
* I-41 (GO-FIX 1): the two shapes of an answer to the pulse summary, typed with no slash and no
|
|
7
|
+
* identifier: "P4EJ a" answers the summary and "1a 2c" answers the questions. The SAME sources
|
|
8
|
+
* live in the core (GRAMATICA_DO_PULSE, core/src/pulse-resposta.ts) and a core test checks they
|
|
9
|
+
* stay equal. The code starts with a letter and has a digit: ordinary chat never has this shape,
|
|
10
|
+
* and it never reads as an answer to the batch, which starts with the question number.
|
|
11
|
+
*/
|
|
12
|
+
const PULSE_CONSENT = new RegExp("^[ \\t]*(?=[A-HJKMNP-TV-Z][2-9A-HJKMNP-TV-Z]{0,2}[2-9])[A-HJKMNP-TV-Z][2-9A-HJKMNP-TV-Z]{3}[ \\t]+[^\\r\\n]{1,40}$", 'i');
|
|
13
|
+
const PULSE_LOTE = new RegExp("^[ \\t]*[0-9]{1,2}[ \\t]*[a-zA-Z](?:[ \\t,;]*[0-9]{1,2}[ \\t]*[a-zA-Z])*[ \\t]*[.!]?[ \\t]*$");
|
|
14
|
+
/** I-50 (RM-039): the pulse cadence tag, alone in the message. It starts with '#', so it never reads as the other two. */
|
|
15
|
+
const PULSE_CADENCIA = new RegExp("^[ \\t]*#OrkPulse(?:On(?:-(?:15|30|60)m)?|Off)[ \\t]*[.!]?[ \\t]*$", 'i');
|
|
16
|
+
/** The pulse address inside the signed body: neither a thread nor a request. The core translates. */
|
|
17
|
+
const PULSE_ALVO = 'pulse', PULSE_ENDERECO = 'resposta';
|
|
18
|
+
export function isPulseAnswer(text) {
|
|
19
|
+
return !text.includes('\0') && (PULSE_CONSENT.test(text) || PULSE_LOTE.test(text) || PULSE_CADENCIA.test(text));
|
|
20
|
+
}
|
|
21
|
+
export function registerHitlIngress(api, bin) {
|
|
22
|
+
api.on('inbound_claim', async (event, ctx) => {
|
|
23
|
+
if (typeof event.content !== 'string')
|
|
24
|
+
return { handled: false };
|
|
25
|
+
const pulse = !event.content.startsWith('/ork ') && isPulseAnswer(event.content);
|
|
26
|
+
if (!event.content.startsWith('/ork ') && !pulse)
|
|
27
|
+
return { handled: false };
|
|
28
|
+
const denied = { handled: true, reply: { text: 'Resposta HITL não confirmada. Confira ingresso e ledger do pedido.' } };
|
|
29
|
+
// The pulse summary goes out on Telegram; only there is an answer to it recognized.
|
|
30
|
+
if (event.channel !== 'telegram')
|
|
31
|
+
return pulse ? { handled: false } : nativeIngress(event, ctx, bin).catch(() => denied);
|
|
32
|
+
try {
|
|
33
|
+
const env = process.env, user = event.senderId ?? '', chat = event.conversationId ?? '', message = event.messageId ?? '';
|
|
34
|
+
const allowed = (key, value) => (env[key] ?? '').split(',').map(s => s.trim()).filter(Boolean).includes(value);
|
|
35
|
+
const age = Date.now() - Number(event.timestamp);
|
|
36
|
+
if (event.channel !== 'telegram' || ctx.channelId !== 'telegram' || event.commandAuthorized !== true ||
|
|
37
|
+
!env.ORK_HITL_OPENCLAW_ACCOUNT || event.accountId !== env.ORK_HITL_OPENCLAW_ACCOUNT || event.accountId !== ctx.accountId ||
|
|
38
|
+
!env.ORK_HITL_TELEGRAM_BOT_ID || user === env.ORK_HITL_TELEGRAM_BOT_ID ||
|
|
39
|
+
!/^\d+$/.test(user) || !/^-?\d+$/.test(chat) || !/^\d+$/.test(message) ||
|
|
40
|
+
user !== ctx.senderId || chat !== ctx.conversationId || message !== ctx.messageId ||
|
|
41
|
+
!allowed('ORK_HITL_TELEGRAM_USERS', user) || !allowed('ORK_HITL_TELEGRAM_CHATS', chat) ||
|
|
42
|
+
!Number.isFinite(age) || age < 0 || age > 60000)
|
|
43
|
+
return denied;
|
|
44
|
+
const match = /^\/ork (gate|session) ([a-zA-Z0-9][a-zA-Z0-9._-]{0,79}) ([a-zA-Z0-9][a-zA-Z0-9._-]{0,79}) ([\s\S]{1,4096})$/.exec(event.content);
|
|
45
|
+
// FX1: this channel signs with ITS OWN key. The shared ORK_HITL_INGRESS_KEY is kept
|
|
46
|
+
// for the legacy v1 envelope only, which this adapter no longer produces.
|
|
47
|
+
const key = env.ORK_HITL_INGRESS_KEY_OPENCLAW, root = env.ORK_HITL_ROOT;
|
|
48
|
+
if ((!pulse && !match) || event.content.includes('\0') || !key || Buffer.byteLength(key) < 32 || !root?.startsWith('/'))
|
|
49
|
+
return denied;
|
|
50
|
+
if (pulse)
|
|
51
|
+
return pulseIngress(event.content, { user, chat, message, key, root, bin, timestamp: Number(event.timestamp) }, denied);
|
|
52
|
+
const [, operation, thread, pedido, resposta] = match;
|
|
53
|
+
const canal = 'openclaw';
|
|
54
|
+
// FX5: the authorized account goes INSIDE the signed body. Checking `accountId` only
|
|
55
|
+
// here left the durable receipt proving generic telegram transport and nothing else:
|
|
56
|
+
// nobody reading it later could tell which OpenClaw account the decision came from.
|
|
57
|
+
const conta = env.ORK_HITL_OPENCLAW_ACCOUNT;
|
|
58
|
+
const envelope = { resposta, canal, conta, origem: 'telegram', por: `telegram:${user}`, mensagem: `telegram:${chat}:${message}`,
|
|
59
|
+
recebidoEm: new Date(Number(event.timestamp)).toISOString(), prova: '' };
|
|
60
|
+
envelope.prova = createHmac('sha256', key).update(JSON.stringify(['ork.hitl-answer/v2', thread, pedido, canal, conta,
|
|
61
|
+
envelope.origem, envelope.por, envelope.mensagem, envelope.recebidoEm, resposta])).digest('hex');
|
|
62
|
+
// `gate answer` exige --resposta-stdin; `sessions answer` exige --stdin. A mesma flag
|
|
63
|
+
// para os dois fazia o CLI recusar toda resposta de sessao antes de conferir qualquer coisa.
|
|
64
|
+
const args = [operation === 'gate' ? 'gate' : 'sessions', 'answer', thread, pedido,
|
|
65
|
+
operation === 'gate' ? '--resposta-stdin' : '--stdin',
|
|
66
|
+
'--origem', 'telegram', '--canal', canal, '--conta', conta, '--por', envelope.por, '--mensagem', envelope.mensagem];
|
|
67
|
+
// FX5: `ok` alone was never a receipt. The core answers with the typed verdict, and a
|
|
68
|
+
// reply to the human must only claim what that verdict says: a gate lands in one of
|
|
69
|
+
// three states, a session answer lands `entregue` with the session it reached, and
|
|
70
|
+
// `repetida` says whether this call caused the effect or merely found it already done.
|
|
71
|
+
const estados = operation === 'gate' ? ['aprovado', 'recusado', 'aguardando'] : ['entregue'];
|
|
72
|
+
const ok = await new Promise(resolve => {
|
|
73
|
+
const child = execFile(bin, args, { cwd: root, timeout: 10000, maxBuffer: 65536 }, (err, stdout) => {
|
|
74
|
+
try {
|
|
75
|
+
const r = JSON.parse(stdout);
|
|
76
|
+
resolve(!err && r.ok === true && r.pedidoId === pedido && estados.includes(r.estado) &&
|
|
77
|
+
typeof r.repetida === 'boolean' &&
|
|
78
|
+
(operation === 'gate' || typeof r.sessionId === 'string' && r.sessionId.length > 0));
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
resolve(false);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
child.stdin?.on('error', () => resolve(false));
|
|
85
|
+
child.stdin?.end(JSON.stringify(envelope));
|
|
86
|
+
});
|
|
87
|
+
return ok ? { handled: true, reply: { text: `Resposta recebida pelo núcleo para o pedido ${pedido}.` } } : denied;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return denied;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* I-41 (GO-FIX 1): the owner answered the pulse summary or its questions. The whole text is the
|
|
96
|
+
* answer and the signed address is the pulse's; this adapter knows no request and picks nothing.
|
|
97
|
+
* The reply is the text the core composed, transported as is.
|
|
98
|
+
*/
|
|
99
|
+
async function pulseIngress(texto, m, denied) {
|
|
100
|
+
const canal = 'openclaw', conta = process.env.ORK_HITL_OPENCLAW_ACCOUNT ?? '';
|
|
101
|
+
const envelope = { resposta: texto, canal, conta, origem: 'telegram', por: `telegram:${m.user}`,
|
|
102
|
+
mensagem: `telegram:${m.chat}:${m.message}`, recebidoEm: new Date(m.timestamp).toISOString(), prova: '' };
|
|
103
|
+
envelope.prova = createHmac('sha256', m.key).update(JSON.stringify(['ork.hitl-answer/v2', PULSE_ALVO, PULSE_ENDERECO, canal, conta,
|
|
104
|
+
envelope.origem, envelope.por, envelope.mensagem, envelope.recebidoEm, texto])).digest('hex');
|
|
105
|
+
const args = ['pulse', 'responder', '--resposta-stdin', '--origem', 'telegram', '--canal', canal, '--conta', conta,
|
|
106
|
+
'--por', envelope.por, '--mensagem', envelope.mensagem];
|
|
107
|
+
const receipt = await new Promise(resolve => {
|
|
108
|
+
const child = execFile(m.bin, args, { cwd: m.root, timeout: 10000, maxBuffer: 65536 }, (err, stdout) => {
|
|
109
|
+
try {
|
|
110
|
+
resolve(err ? null : JSON.parse(stdout));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
resolve(null);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
child.stdin?.on('error', () => resolve(null));
|
|
117
|
+
child.stdin?.end(JSON.stringify(envelope));
|
|
118
|
+
});
|
|
119
|
+
if (!receipt || receipt.ok !== true || receipt.contrato !== 'ork.pulse-resposta/v1' ||
|
|
120
|
+
typeof receipt.mensagem !== 'string' || !receipt.mensagem || typeof receipt.repetida !== 'boolean')
|
|
121
|
+
return denied;
|
|
122
|
+
// Same message already handled: never resend blindly.
|
|
123
|
+
return receipt.repetida ? { handled: true } : { handled: true, reply: { text: receipt.mensagem } };
|
|
124
|
+
}
|
|
125
|
+
/** SDK instalado: inbound_claim fornece commandAuthorized, senderIsOwner e sessionKey.
|
|
126
|
+
* Somente esta callback usa a chave privada; nenhuma tool registra nativeIngress. */
|
|
127
|
+
async function nativeIngress(event, ctx, bin) {
|
|
128
|
+
const denied = { handled: true, reply: { text: 'Ingresso nativo indisponível ou identidade não comprovada. O pedido permanece pendente.' } };
|
|
129
|
+
const env = process.env, root = env.ORK_HITL_ROOT, key = env.ORK_HITL_NATIVE_KEY_OPENCLAW;
|
|
130
|
+
const binding = JSON.parse(env.ORK_HITL_NATIVE_BINDING_OPENCLAW ?? 'null');
|
|
131
|
+
const age = Date.now() - Number(event.timestamp);
|
|
132
|
+
if (!binding || binding.host !== 'openclaw' || !root?.startsWith('/') || !key || Buffer.byteLength(key) < 32 ||
|
|
133
|
+
event.commandAuthorized !== true || event.senderIsOwner !== true ||
|
|
134
|
+
!event.sessionKey || event.sessionKey !== ctx.sessionKey || event.sessionKey !== binding.sessionId ||
|
|
135
|
+
!event.messageId || event.messageId !== ctx.messageId || !binding.installationId || !binding.connectionId ||
|
|
136
|
+
event.channel !== ctx.channelId || event.channel !== binding.channelId ||
|
|
137
|
+
event.accountId !== ctx.accountId || event.accountId !== binding.accountId ||
|
|
138
|
+
event.senderId !== ctx.senderId || event.senderId !== binding.personId ||
|
|
139
|
+
event.conversationId !== ctx.conversationId || event.conversationId !== binding.conversationId ||
|
|
140
|
+
!Number.isFinite(age) || age < 0 || age > 60000)
|
|
141
|
+
return denied;
|
|
142
|
+
const match = /^\/ork (offer|gate|session) ([a-zA-Z0-9][a-zA-Z0-9._-]{0,79}) ([a-zA-Z0-9][a-zA-Z0-9._-]{0,79})(?: ([\s\S]{1,4096}))?$/.exec(event.content);
|
|
143
|
+
if (!match || event.content.includes('\0') || (match[1] === 'offer' ? match[4] !== undefined : !match[4]))
|
|
144
|
+
return denied;
|
|
145
|
+
const [, operation, thread, pedido, resposta] = match;
|
|
146
|
+
const call = (args, input) => new Promise((resolve, reject) => {
|
|
147
|
+
const child = execFile(bin, args, { cwd: root, timeout: 10000, maxBuffer: 65536 }, (error, stdout) => {
|
|
148
|
+
if (error) {
|
|
149
|
+
reject(Error('native.core.unavailable'));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
resolve(JSON.parse(stdout));
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
reject(Error('native.core.invalid'));
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
child.stdin?.on('error', () => reject(Error('native.core.unavailable')));
|
|
160
|
+
if (input !== undefined)
|
|
161
|
+
child.stdin?.end(JSON.stringify(input));
|
|
162
|
+
});
|
|
163
|
+
const offer = { native: { ...binding, messageId: event.messageId }, recebidoEm: new Date(Number(event.timestamp)).toISOString(), prova: '' };
|
|
164
|
+
const b = offer.native;
|
|
165
|
+
offer.prova = createHmac('sha256', key).update(JSON.stringify(['ork.hitl-native-offer/v1', thread, pedido,
|
|
166
|
+
b.host, b.installationId, b.connectionId, b.sessionId, b.accountId, b.channelId,
|
|
167
|
+
b.conversationId, b.personId, b.messageId, offer.recebidoEm])).digest('hex');
|
|
168
|
+
const view = await call(['gate', 'context', thread, pedido, '--native-offer-stdin'], offer);
|
|
169
|
+
if (view.pedido?.id !== pedido || view.pedido?.thread !== thread || (operation !== 'offer' && view.pedido?.alvo?.tipo !== operation) ||
|
|
170
|
+
!view.canais?.some((c) => c.canal === 'openclaw' && c.transporte === 'native' && c.estado === 'disponivel') ||
|
|
171
|
+
!/^[a-f0-9]{64}$/.test(view.contexto) || !/^[a-f0-9]{64}$/.test(view.pedidoSha256))
|
|
172
|
+
return denied;
|
|
173
|
+
if (operation === 'offer') {
|
|
174
|
+
if (typeof view.apresentacao?.mensagem !== 'string')
|
|
175
|
+
return denied;
|
|
176
|
+
// I-35: o prazo chega ao dono no fuso dele; núcleo de outra versão pode não ter posto o
|
|
177
|
+
// prazo local na mensagem, então o campo local vai junto.
|
|
178
|
+
const prazo = typeof view.prazoLocal === 'string' && view.prazoLocal && !view.apresentacao.mensagem.includes(view.prazoLocal)
|
|
179
|
+
? `\nPrazo (fuso do dono): ${view.prazoLocal}` : '';
|
|
180
|
+
return { handled: true, reply: { text: view.apresentacao.mensagem + prazo + '\nCanal nativo OpenClaw disponível nesta conversa.' } };
|
|
181
|
+
}
|
|
182
|
+
const n = { ...binding, messageId: event.messageId, context: view.contexto,
|
|
183
|
+
pedidoSha256: view.pedidoSha256, expiresAt: view.pedido.prazo };
|
|
184
|
+
const message = 'native:' + createHash('sha256').update(JSON.stringify([n.host, n.installationId, n.connectionId,
|
|
185
|
+
n.sessionId, n.channelId, n.accountId, n.conversationId, n.messageId])).digest('hex');
|
|
186
|
+
const r = { origem: 'native', canal: 'openclaw', conta: n.accountId, native: n, por: `native:openclaw:${n.personId}`,
|
|
187
|
+
mensagem: message, recebidoEm: new Date(Number(event.timestamp)).toISOString(), resposta, prova: '' };
|
|
188
|
+
r.prova = createHmac('sha256', key).update(JSON.stringify(['ork.hitl-native/v1', thread, pedido,
|
|
189
|
+
n.host, n.installationId, n.connectionId, n.sessionId, n.accountId, n.channelId, n.conversationId, n.personId,
|
|
190
|
+
n.messageId, n.context, n.pedidoSha256, n.expiresAt, r.origem, r.canal, r.conta, r.por, r.mensagem, r.recebidoEm, resposta])).digest('hex');
|
|
191
|
+
const receipt = await call([operation === 'gate' ? 'gate' : 'sessions', 'answer', thread, pedido,
|
|
192
|
+
operation === 'gate' ? '--resposta-stdin' : '--stdin', '--origem', 'native', '--canal', 'openclaw',
|
|
193
|
+
'--conta', r.conta, '--por', r.por, '--mensagem', message], r);
|
|
194
|
+
if (receipt.ok !== true || receipt.pedidoId !== pedido || typeof receipt.repetida !== 'boolean' ||
|
|
195
|
+
!(operation === 'gate' ? ['aprovado', 'recusado', 'aguardando'] : ['entregue']).includes(receipt.estado) ||
|
|
196
|
+
(operation === 'session' && !receipt.sessionId))
|
|
197
|
+
return denied;
|
|
198
|
+
return receipt.repetida ? { handled: true } : { handled: true, reply: { text: 'Resposta nativa confirmada pelo núcleo.' } };
|
|
199
|
+
}
|