@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,356 @@
|
|
|
1
|
+
"""Ingresso nativo Hermes: callback síncrono, I/O limitado no loop do host.
|
|
2
|
+
|
|
3
|
+
D12: este host é o canal `hermes`. O canal entra no corpo assinado do envelope, não
|
|
4
|
+
num campo solto: Hermes e OpenClaw compartilham transporte, mas têm chaves e identidades
|
|
5
|
+
próprias; sem o canal dentro do HMAC o recibo perderia essa proveniência.
|
|
6
|
+
"""
|
|
7
|
+
import asyncio
|
|
8
|
+
import hashlib
|
|
9
|
+
import hmac
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import subprocess
|
|
15
|
+
import time
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
|
|
18
|
+
CANAL = 'hermes'
|
|
19
|
+
COMMAND = re.compile(r'^/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})$')
|
|
20
|
+
NATIVE_COMMAND = re.compile(r'^/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}))?$')
|
|
21
|
+
# I-41 (GO-FIX 1): as duas formas da resposta ao resumo do pulse, que o dono digita sem barra e
|
|
22
|
+
# sem identificador: "P4EJ a" ao resumo e "1a 2c" as perguntas. As MESMAS fontes vivem no nucleo
|
|
23
|
+
# (GRAMATICA_DO_PULSE, core/src/pulse-resposta.ts) e um teste do nucleo confere que continuam
|
|
24
|
+
# iguais. O codigo comeca por letra e tem digito: conversa comum nao tem esta forma, e ele
|
|
25
|
+
# nunca se confunde com a resposta ao lote, que comeca pelo numero da pergunta.
|
|
26
|
+
PULSE_CONSENT = re.compile(r'^[ \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}$', re.IGNORECASE)
|
|
27
|
+
PULSE_LOTE = re.compile(r'^[ \t]*[0-9]{1,2}[ \t]*[a-zA-Z](?:[ \t,;]*[0-9]{1,2}[ \t]*[a-zA-Z])*[ \t]*[.!]?[ \t]*$')
|
|
28
|
+
# I-50 (RM-039): a tag da cadencia do resumo, sozinha na mensagem. Comeca por '#': nunca se
|
|
29
|
+
# confunde com as outras duas formas, e tag no meio de uma frase continua indo para o assistente.
|
|
30
|
+
PULSE_CADENCIA = re.compile(r'^[ \t]*#OrkPulse(?:On(?:-(?:15|30|60)m)?|Off)[ \t]*[.!]?[ \t]*$', re.IGNORECASE)
|
|
31
|
+
# O endereco do pulse no corpo assinado: nem thread nem pedido. Quem traduz e o nucleo.
|
|
32
|
+
PULSE_ALVO, PULSE_ENDERECO = 'pulse', 'resposta'
|
|
33
|
+
CORE_TIMEOUT = 10
|
|
34
|
+
CONFIRM_TIMEOUT = 3
|
|
35
|
+
MAX_ENTRIES = 1024
|
|
36
|
+
MAX_PENDING = 16
|
|
37
|
+
CONFIRM_TEXT = {
|
|
38
|
+
'gate': 'Orkastery: resposta ao gate registrada pelo núcleo.',
|
|
39
|
+
'session': 'Orkastery: resposta entregue à sessão pelo núcleo.',
|
|
40
|
+
}
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
# Só metadados: nunca guardar resposta, chave, stdout ou exceção nestes registros.
|
|
43
|
+
_entries = {}
|
|
44
|
+
_tasks = set()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _allowed(name, value):
|
|
48
|
+
return value in {s.strip() for s in os.environ.get(name, '').split(',') if s.strip()}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _status(entry=None, replay=False):
|
|
52
|
+
effect = entry['effect'] if entry else 'unconfirmed'
|
|
53
|
+
confirmation = entry['confirmation'] if entry else 'not_attempted'
|
|
54
|
+
return {'action': 'skip', 'reason': f'ork.hitl: effect={effect}; confirmation={confirmation}',
|
|
55
|
+
'effect': effect, 'confirmation': confirmation, 'replay': replay}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _is_pulse(text):
|
|
59
|
+
return '\x00' not in text and bool(PULSE_CONSENT.fullmatch(text) or PULSE_LOTE.fullmatch(text)
|
|
60
|
+
or PULSE_CADENCIA.fullmatch(text))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _receipt_ok(receipt, operation, pedido):
|
|
64
|
+
if operation == 'pulse':
|
|
65
|
+
# O texto que volta ao dono e montado pelo nucleo; este adaptador so o transporta.
|
|
66
|
+
return (isinstance(receipt, dict) and receipt.get('ok') is True
|
|
67
|
+
and receipt.get('contrato') == 'ork.pulse-resposta/v1'
|
|
68
|
+
and isinstance(receipt.get('mensagem'), str) and bool(receipt['mensagem'])
|
|
69
|
+
and type(receipt.get('repetida')) is bool)
|
|
70
|
+
states = {'aprovado', 'recusado', 'aguardando'} if operation == 'gate' else {'entregue'}
|
|
71
|
+
return (isinstance(receipt, dict) and receipt.get('ok') is True
|
|
72
|
+
and receipt.get('pedidoId') == pedido and receipt.get('estado') in states
|
|
73
|
+
and type(receipt.get('repetida')) is bool
|
|
74
|
+
and (operation == 'gate' or isinstance(receipt.get('sessionId'), str)
|
|
75
|
+
and bool(receipt['sessionId'])))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _call_core(argv, envelope, root, env):
|
|
79
|
+
return subprocess.run(argv, input=json.dumps(envelope, ensure_ascii=False), cwd=root,
|
|
80
|
+
env=env, text=True, encoding='utf-8', capture_output=True,
|
|
81
|
+
timeout=CORE_TIMEOUT, check=False)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def _deliver(entry, msg, operation, pedido, argv, envelope, root, env):
|
|
85
|
+
try:
|
|
86
|
+
# A espera no executor também tem orçamento. Cancelamento não prova rollback.
|
|
87
|
+
entry['effect'] = 'unknown'
|
|
88
|
+
result = await asyncio.wait_for(
|
|
89
|
+
asyncio.to_thread(_call_core, argv, envelope, root, env), CORE_TIMEOUT + 1)
|
|
90
|
+
receipt = json.loads(result.stdout) if result.returncode == 0 else None
|
|
91
|
+
if not _receipt_ok(receipt, operation, pedido):
|
|
92
|
+
return
|
|
93
|
+
entry['effect'] = 'confirmed'
|
|
94
|
+
if receipt['repetida']:
|
|
95
|
+
# Após restart não sabemos se o canal já recebeu. Nunca reenviar às cegas.
|
|
96
|
+
entry['confirmation'] = 'unknown'
|
|
97
|
+
return
|
|
98
|
+
entry['confirmation'] = 'unknown'
|
|
99
|
+
reply = receipt['mensagem'] if operation == 'pulse' else CONFIRM_TEXT[operation]
|
|
100
|
+
sent = await asyncio.wait_for(msg.reply_text(
|
|
101
|
+
reply, do_quote=False, parse_mode=None,
|
|
102
|
+
read_timeout=CONFIRM_TIMEOUT, write_timeout=CONFIRM_TIMEOUT,
|
|
103
|
+
connect_timeout=CONFIRM_TIMEOUT, pool_timeout=CONFIRM_TIMEOUT), CONFIRM_TIMEOUT)
|
|
104
|
+
from telegram import Message
|
|
105
|
+
if (isinstance(sent, Message) and type(sent.message_id) is int and sent.message_id > 0
|
|
106
|
+
and sent.chat.id == msg.chat.id and sent.from_user.id == msg.get_bot().id
|
|
107
|
+
and sent.from_user.is_bot is True and sent.text == reply):
|
|
108
|
+
entry['confirmation'] = 'sent'
|
|
109
|
+
except (Exception, asyncio.CancelledError):
|
|
110
|
+
# Timeout/exceção/recibo inválido: efeito pode existir; canal pode ter recebido.
|
|
111
|
+
# Nunca ecoar detalhes do CLI/PTB, nem transformar incerteza em sucesso.
|
|
112
|
+
pass
|
|
113
|
+
finally:
|
|
114
|
+
entry['done'] = True
|
|
115
|
+
logger.info('ork.hitl: ref=%s effect=%s confirmation=%s',
|
|
116
|
+
entry['ref'], entry['effect'], entry['confirmation'])
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _ingress(*, event, gateway, **_kwargs):
|
|
120
|
+
text = getattr(event, 'text', '')
|
|
121
|
+
if not isinstance(text, str):
|
|
122
|
+
return None
|
|
123
|
+
pulse = not text.startswith('/ork ') and _is_pulse(text)
|
|
124
|
+
if not text.startswith('/ork ') and not pulse:
|
|
125
|
+
return None
|
|
126
|
+
if getattr(getattr(event, 'source', None), 'platform', None) and event.source.platform.value != 'telegram':
|
|
127
|
+
# O resumo do pulse sai pelo Telegram; a resposta a ele so e reconhecida la.
|
|
128
|
+
return None if pulse else _native_ingress(event, gateway, _kwargs.get('session_store'))
|
|
129
|
+
# invoke_hook instalado NÃO resolve coroutine. Todo caminho /ork, e a resposta ao pulse, retorna skip.
|
|
130
|
+
try:
|
|
131
|
+
from gateway.platforms.base import MessageEvent
|
|
132
|
+
from telegram import Message
|
|
133
|
+
if not isinstance(event, MessageEvent) or getattr(event, 'internal', False):
|
|
134
|
+
return _status()
|
|
135
|
+
msg, source = event.raw_message, event.source
|
|
136
|
+
if (not isinstance(msg, Message) or source.platform.value != 'telegram'
|
|
137
|
+
or gateway._is_user_authorized(source) is not True):
|
|
138
|
+
return _status()
|
|
139
|
+
user, chat = str(msg.from_user.id), str(msg.chat.id)
|
|
140
|
+
bot = os.environ.get('ORK_HITL_TELEGRAM_BOT_ID', '')
|
|
141
|
+
if (msg.from_user.is_bot is not False or not bot or str(msg.get_bot().id) != bot
|
|
142
|
+
or user == bot or getattr(msg, 'forward_origin', None) or getattr(msg, 'sender_chat', None)
|
|
143
|
+
or str(source.user_id) != user or str(source.chat_id) != chat
|
|
144
|
+
or type(msg.message_id) is not int or msg.message_id <= 0
|
|
145
|
+
or str(event.message_id) != str(msg.message_id) or msg.text != text
|
|
146
|
+
or not _allowed('ORK_HITL_TELEGRAM_USERS', user)
|
|
147
|
+
or not _allowed('ORK_HITL_TELEGRAM_CHATS', chat)):
|
|
148
|
+
return _status()
|
|
149
|
+
if pulse:
|
|
150
|
+
# O dono respondeu ao resumo ou ao lote. O texto inteiro e a resposta, e o endereco
|
|
151
|
+
# assinado e o do pulse: este adaptador nao conhece pedido e nao escolhe nada.
|
|
152
|
+
operation, thread, pedido, answer = 'pulse', PULSE_ALVO, PULSE_ENDERECO, text
|
|
153
|
+
else:
|
|
154
|
+
match = COMMAND.fullmatch(text)
|
|
155
|
+
if not match or '\x00' in text:
|
|
156
|
+
return _status()
|
|
157
|
+
operation, thread, pedido, answer = match.groups()
|
|
158
|
+
now = datetime.now(timezone.utc)
|
|
159
|
+
age = (now - msg.date).total_seconds()
|
|
160
|
+
# FX1: este canal assina com a SUA chave. ORK_HITL_INGRESS_KEY fica so para o
|
|
161
|
+
# envelope v1 legado, que este adaptador nao produz mais.
|
|
162
|
+
key = os.environ.get('ORK_HITL_INGRESS_KEY_HERMES', '')
|
|
163
|
+
root = os.environ.get('ORK_HITL_ROOT', '')
|
|
164
|
+
if not 0 <= age <= 60 or len(key.encode()) < 32 or not os.path.isabs(root):
|
|
165
|
+
return _status()
|
|
166
|
+
envelope = {'resposta': answer, 'canal': CANAL, 'origem': 'telegram', 'por': f'telegram:{user}',
|
|
167
|
+
'mensagem': f'telegram:{chat}:{msg.message_id}',
|
|
168
|
+
'recebidoEm': msg.date.astimezone(timezone.utc).isoformat(timespec='milliseconds').replace('+00:00', 'Z')}
|
|
169
|
+
# FX5: `conta` tem posicao fixa no corpo v2, e e `None` neste canal: a allowlist do
|
|
170
|
+
# Hermes e de usuario e chat, nao de conta. Declarar conta aqui seria recusado pelo
|
|
171
|
+
# nucleo, e omitir a posicao mudaria a aridade do corpo assinado.
|
|
172
|
+
body = ['ork.hitl-answer/v2', thread, pedido, CANAL, None, envelope['origem'], envelope['por'],
|
|
173
|
+
envelope['mensagem'], envelope['recebidoEm'], answer]
|
|
174
|
+
envelope['prova'] = hmac.new(key.encode(), json.dumps(body, ensure_ascii=False, separators=(',', ':')).encode(), hashlib.sha256).hexdigest()
|
|
175
|
+
# Cada subcomando tem a sua flag de stdin: `gate answer` exige --resposta-stdin e
|
|
176
|
+
# `sessions answer` exige --stdin. Mandar a mesma flag para os dois fazia toda
|
|
177
|
+
# resposta de sessao ser recusada pelo proprio CLI, antes de qualquer conferencia.
|
|
178
|
+
if operation == 'pulse':
|
|
179
|
+
argv = [os.environ.get('ORK_BIN', 'ork'), 'pulse', 'responder', '--resposta-stdin',
|
|
180
|
+
'--origem', 'telegram', '--canal', CANAL,
|
|
181
|
+
'--por', envelope['por'], '--mensagem', envelope['mensagem']]
|
|
182
|
+
else:
|
|
183
|
+
argv = [os.environ.get('ORK_BIN', 'ork'), 'gate' if operation == 'gate' else 'sessions',
|
|
184
|
+
'answer', thread, pedido,
|
|
185
|
+
'--resposta-stdin' if operation == 'gate' else '--stdin',
|
|
186
|
+
'--origem', 'telegram', '--canal', CANAL,
|
|
187
|
+
'--por', envelope['por'], '--mensagem', envelope['mensagem']]
|
|
188
|
+
# Usar o loop que possui o bot HTTP; nunca asyncio.run/thread com esse bot.
|
|
189
|
+
loop = asyncio.get_running_loop()
|
|
190
|
+
identity = (root, bot, chat, str(msg.message_id))
|
|
191
|
+
fingerprint = operation + ':' + envelope['prova']
|
|
192
|
+
previous = _entries.get(identity)
|
|
193
|
+
if previous:
|
|
194
|
+
return _status(previous, replay=True) if hmac.compare_digest(previous['fingerprint'], fingerprint) else _status()
|
|
195
|
+
for identity_old, old in list(_entries.items()):
|
|
196
|
+
if old['done'] and time.monotonic() >= old['expires']:
|
|
197
|
+
del _entries[identity_old]
|
|
198
|
+
if len(_entries) >= MAX_ENTRIES or len(_tasks) >= MAX_PENDING:
|
|
199
|
+
return _status()
|
|
200
|
+
entry = {'fingerprint': fingerprint, 'effect': 'pending', 'confirmation': 'not_attempted',
|
|
201
|
+
'done': False, 'expires': time.monotonic() + 61,
|
|
202
|
+
'ref': hashlib.sha256(json.dumps(identity).encode()).hexdigest()[:16]}
|
|
203
|
+
_entries[identity] = entry # Reserva antes de agendar, sem await no callback.
|
|
204
|
+
work = _deliver(entry, msg, operation, pedido, argv, envelope, root, dict(os.environ))
|
|
205
|
+
try:
|
|
206
|
+
task = loop.create_task(work)
|
|
207
|
+
except Exception:
|
|
208
|
+
work.close()
|
|
209
|
+
entry.update(effect='unconfirmed', done=True)
|
|
210
|
+
return _status(entry)
|
|
211
|
+
_tasks.add(task)
|
|
212
|
+
task.add_done_callback(_tasks.discard)
|
|
213
|
+
return _status(entry)
|
|
214
|
+
except Exception:
|
|
215
|
+
pass
|
|
216
|
+
return _status()
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def register(ctx):
|
|
220
|
+
ctx.register_hook('pre_gateway_dispatch', _ingress)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
async def _native_core(argv, root, envelope=None):
|
|
224
|
+
# Subprocesso assíncrono limitado: não prende executor thread no shutdown.
|
|
225
|
+
proc = await asyncio.create_subprocess_exec(*argv, cwd=root,
|
|
226
|
+
stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.DEVNULL,
|
|
227
|
+
limit=65536)
|
|
228
|
+
try:
|
|
229
|
+
data = None if envelope is None else json.dumps(envelope, ensure_ascii=False).encode()
|
|
230
|
+
out, _ = await asyncio.wait_for(proc.communicate(data), CORE_TIMEOUT)
|
|
231
|
+
if proc.returncode or len(out) > 65536:
|
|
232
|
+
raise ValueError('native.core.unavailable')
|
|
233
|
+
return json.loads(out)
|
|
234
|
+
except BaseException:
|
|
235
|
+
if proc.returncode is None:
|
|
236
|
+
proc.kill()
|
|
237
|
+
await proc.wait()
|
|
238
|
+
raise
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
async def _native_deliver(entry, msg, match, binding, key, root):
|
|
242
|
+
operation, thread, pedido, answer = match.groups()
|
|
243
|
+
cli = os.environ.get('ORK_BIN', 'ork')
|
|
244
|
+
try:
|
|
245
|
+
compact = lambda v: json.dumps(v, ensure_ascii=False, separators=(',', ':'))
|
|
246
|
+
offer = dict(native=dict(binding, messageId=str(msg.id)),
|
|
247
|
+
recebidoEm=msg.created_at.astimezone(timezone.utc).isoformat(timespec='milliseconds').replace('+00:00', 'Z'))
|
|
248
|
+
body = ['ork.hitl-native-offer/v1', thread, pedido] + [offer['native'][k] for k in
|
|
249
|
+
['host', 'installationId', 'connectionId', 'sessionId', 'accountId', 'channelId', 'conversationId', 'personId', 'messageId']] + [offer['recebidoEm']]
|
|
250
|
+
offer['prova'] = hmac.new(key.encode(), compact(body).encode(), hashlib.sha256).hexdigest()
|
|
251
|
+
view = await _native_core([cli, 'gate', 'context', thread, pedido, '--native-offer-stdin'], root, offer)
|
|
252
|
+
p = view.get('pedido', {})
|
|
253
|
+
if (p.get('id') != pedido or p.get('thread') != thread or (operation != 'offer' and p.get('alvo', {}).get('tipo') != operation)
|
|
254
|
+
or not any(c.get('canal') == 'hermes' and c.get('transporte') == 'native' and c.get('estado') == 'disponivel' for c in view.get('canais', []))
|
|
255
|
+
or not re.fullmatch('[a-f0-9]{64}', view.get('contexto', ''))
|
|
256
|
+
or not re.fullmatch('[a-f0-9]{64}', view.get('pedidoSha256', ''))):
|
|
257
|
+
return
|
|
258
|
+
if operation == 'offer':
|
|
259
|
+
text = view.get('apresentacao', {}).get('mensagem')
|
|
260
|
+
if not isinstance(text, str):
|
|
261
|
+
return
|
|
262
|
+
# I-35: o prazo chega ao dono no fuso dele; nucleo de outra versao pode nao
|
|
263
|
+
# ter posto o prazo local na mensagem, entao o campo local vai junto.
|
|
264
|
+
prazo_local = view.get('prazoLocal')
|
|
265
|
+
if isinstance(prazo_local, str) and prazo_local and prazo_local not in text:
|
|
266
|
+
text += '\nPrazo (fuso do dono): ' + prazo_local
|
|
267
|
+
import discord
|
|
268
|
+
entry['effect'] = 'presented'
|
|
269
|
+
entry['confirmation'] = 'unknown'
|
|
270
|
+
text += '\nCanal nativo Hermes disponível nesta conversa.'
|
|
271
|
+
sent = await asyncio.wait_for(msg.reply(text, mention_author=False,
|
|
272
|
+
allowed_mentions=discord.AllowedMentions.none()), CONFIRM_TIMEOUT)
|
|
273
|
+
if isinstance(sent, discord.Message) and sent.channel.id == msg.channel.id and sent.content == text:
|
|
274
|
+
entry['confirmation'] = 'sent'
|
|
275
|
+
return
|
|
276
|
+
n = dict(binding, messageId=str(msg.id), context=view['contexto'],
|
|
277
|
+
pedidoSha256=view['pedidoSha256'], expiresAt=p['prazo'])
|
|
278
|
+
identity = [n[k] for k in ['host', 'installationId', 'connectionId', 'sessionId', 'channelId', 'accountId', 'conversationId', 'messageId']]
|
|
279
|
+
envelope = dict(origem='native', canal='hermes', conta=n['accountId'], native=n,
|
|
280
|
+
por='native:hermes:' + n['personId'], mensagem='native:' + hashlib.sha256(compact(identity).encode()).hexdigest(),
|
|
281
|
+
recebidoEm=msg.created_at.astimezone(timezone.utc).isoformat(timespec='milliseconds').replace('+00:00', 'Z'), resposta=answer)
|
|
282
|
+
body = ['ork.hitl-native/v1', thread, pedido] + [n[k] for k in ['host', 'installationId', 'connectionId', 'sessionId',
|
|
283
|
+
'accountId', 'channelId', 'conversationId', 'personId', 'messageId', 'context', 'pedidoSha256', 'expiresAt']] + [envelope[k] for k in ['origem', 'canal', 'conta', 'por', 'mensagem', 'recebidoEm', 'resposta']]
|
|
284
|
+
envelope['prova'] = hmac.new(key.encode(), compact(body).encode(), hashlib.sha256).hexdigest()
|
|
285
|
+
entry['effect'] = 'unknown'
|
|
286
|
+
receipt = await _native_core([cli, 'gate' if operation == 'gate' else 'sessions', 'answer', thread, pedido,
|
|
287
|
+
'--resposta-stdin' if operation == 'gate' else '--stdin', '--origem', 'native', '--canal', 'hermes',
|
|
288
|
+
'--conta', n['accountId'], '--por', envelope['por'], '--mensagem', envelope['mensagem']], root, envelope)
|
|
289
|
+
if not _receipt_ok(receipt, operation, pedido):
|
|
290
|
+
return
|
|
291
|
+
entry['effect'] = 'confirmed'
|
|
292
|
+
if receipt['repetida']:
|
|
293
|
+
entry['confirmation'] = 'unknown'
|
|
294
|
+
return
|
|
295
|
+
entry['confirmation'] = 'unknown'
|
|
296
|
+
import discord
|
|
297
|
+
sent = await asyncio.wait_for(msg.reply(CONFIRM_TEXT[operation], mention_author=False,
|
|
298
|
+
allowed_mentions=discord.AllowedMentions.none()), CONFIRM_TIMEOUT)
|
|
299
|
+
if isinstance(sent, discord.Message) and sent.channel.id == msg.channel.id and sent.content == CONFIRM_TEXT[operation]:
|
|
300
|
+
entry['confirmation'] = 'sent'
|
|
301
|
+
except (Exception, asyncio.CancelledError):
|
|
302
|
+
pass
|
|
303
|
+
finally:
|
|
304
|
+
entry['done'] = True
|
|
305
|
+
logger.info('ork.hitl.native: ref=%s effect=%s confirmation=%s', entry['ref'], entry['effect'], entry['confirmation'])
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _native_ingress(event, gateway, session_store):
|
|
309
|
+
"""Callback Discord do gateway instalado; terminal/ACP sem atestado fica indisponível."""
|
|
310
|
+
try:
|
|
311
|
+
from gateway.platforms.base import MessageEvent
|
|
312
|
+
import discord
|
|
313
|
+
if not isinstance(event, MessageEvent) or getattr(event, 'internal', False):
|
|
314
|
+
return _status()
|
|
315
|
+
msg, source = event.raw_message, event.source
|
|
316
|
+
binding = json.loads(os.environ.get('ORK_HITL_NATIVE_BINDING_HERMES', 'null'))
|
|
317
|
+
key, root = os.environ.get('ORK_HITL_NATIVE_KEY_HERMES', ''), os.environ.get('ORK_HITL_ROOT', '')
|
|
318
|
+
fields = {'host', 'installationId', 'connectionId', 'sessionId', 'accountId', 'channelId', 'conversationId', 'personId'}
|
|
319
|
+
if (not isinstance(binding, dict) or set(binding) != fields or any(not isinstance(v, str) or not v for v in binding.values())
|
|
320
|
+
or binding['host'] != 'hermes' or binding['channelId'] != 'discord' or source.platform.value != 'discord'
|
|
321
|
+
or not isinstance(msg, discord.Message) or source.is_bot is not False or msg.author.bot is not False
|
|
322
|
+
or msg.webhook_id is not None or msg.application_id is not None
|
|
323
|
+
or gateway._is_user_authorized(source) is not True or len(key.encode()) < 32 or not os.path.isabs(root)
|
|
324
|
+
or str(msg.author.id) != source.user_id or source.user_id != binding['personId']
|
|
325
|
+
or str(msg.channel.id) != source.chat_id or source.chat_id != binding['conversationId']
|
|
326
|
+
or str(getattr(msg.guild, 'id', '')) != binding['accountId']
|
|
327
|
+
or str(event.message_id) != str(msg.id) or msg.content != event.text):
|
|
328
|
+
return _status()
|
|
329
|
+
age = (datetime.now(timezone.utc) - msg.created_at).total_seconds()
|
|
330
|
+
if not 0 <= age <= 60:
|
|
331
|
+
return _status()
|
|
332
|
+
# API observada no SessionStore instalado; não cria nem reseta sessão por consulta.
|
|
333
|
+
entries = [s for s in session_store.list_sessions() if s.session_id == binding['sessionId']
|
|
334
|
+
and s.session_key == session_store._generate_session_key(source) and not s.suspended]
|
|
335
|
+
if len(entries) != 1:
|
|
336
|
+
return _status()
|
|
337
|
+
match = NATIVE_COMMAND.fullmatch(event.text)
|
|
338
|
+
if not match or '\x00' in event.text or (match[1] == 'offer' and match[4] is not None) or (match[1] != 'offer' and not match[4]):
|
|
339
|
+
return _status()
|
|
340
|
+
identity = (root, binding['connectionId'], binding['sessionId'], source.chat_id, str(msg.id))
|
|
341
|
+
if identity in _entries:
|
|
342
|
+
return _status(_entries[identity], replay=True)
|
|
343
|
+
for old_id, old in list(_entries.items()):
|
|
344
|
+
if old['done'] and time.monotonic() >= old['expires']:
|
|
345
|
+
del _entries[old_id]
|
|
346
|
+
if len(_entries) >= MAX_ENTRIES or len(_tasks) >= MAX_PENDING:
|
|
347
|
+
return _status()
|
|
348
|
+
entry = dict(effect='pending', confirmation='not_attempted', done=False, expires=time.monotonic() + 61,
|
|
349
|
+
ref=hashlib.sha256(json.dumps(identity).encode()).hexdigest()[:16])
|
|
350
|
+
_entries[identity] = entry
|
|
351
|
+
task = asyncio.get_running_loop().create_task(_native_deliver(entry, msg, match, binding, key, root))
|
|
352
|
+
_tasks.add(task)
|
|
353
|
+
task.add_done_callback(_tasks.discard)
|
|
354
|
+
return _status(entry)
|
|
355
|
+
except Exception:
|
|
356
|
+
return _status()
|
|
@@ -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
|
|